Repository navigation
rest: the served OpenAPI document's info block comes from api.documentation (9 keys) #20294
Description
Filing gate: ① a declared≠enforced family, filed as one sweep card per family under ruling A′ item ④ on #18900 (5727134555). This is triage's standing request 5857165909 on the seat post. Family rest-api-documentation, seat verdict ENFORCE.
reach:the declared authoring door.packages/specparses these keys and publishes them in the reference docs. The liveness ledger rows cited below record them as not enforced, and the census re-measured the reader side (§5 cross-checks, each with a lit control).- The criterion is the maintainer's: 「每族该问的是:主流平台有没有这个能力 —— 有 ⇒ 补消费端(一次做对);没有 ⇒ 退役,而不是看仓里有没有人读」.
- The maintainer's one word, per ruling A′ ④: ENFORCE (the seat's proposal: the mainstream has it, so build the consumer once, correctly) or RETIRE (retire the keys together with their ledger rows).
Census by the domain:spec execution seat 1 (session_01Rjy9MeetSfq34PKn81CRiN, seat post #6017), 2026-09-27. Bases: objectstack a9fb83ef, re-checked against 4d7e740d, where no ledger file or cited surface moved; objectui 6fa5f64a1 (pin f8a9d0fb); cloud 96eb092. Ledger instrument: check-liveness.mts --json, whose byStatus equals the committed state-counts.md row for row. ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim. The ranking is by value, user-visible risk × keys. This family's rank is 8 of 16. The sibling family cards filed so far are #20273, #20274, #20281, #20282, #20287, #20288 and #20289. Ranks 8 and 9 are the two halves of rest_api: one ENFORCE, one RETIRE.
Capability: API documentation metadata (title, description, version, terms, contact, license) in the served API description
| key | ledger status | ledger row | what the ledger cites |
|---|---|---|---|
rest_api.documentation.title |
dead (verified 2026-09-21) | packages/spec/liveness/rest_api.json:115 |
note: Normalized by RestServer and read by NOTHING — the ADR-0049 fourth state (parsed, unmarked, unenforced). normalizeConfig lists documentation: api.documentation straight into this.config.api and no site ever reads it back. `api.documentation.title: '… |
rest_api.documentation.description |
dead (verified 2026-09-21) | packages/spec/liveness/rest_api.json:121 |
note: Normalized by RestServer and read by NOTHING — the ADR-0049 fourth state (parsed, unmarked, unenforced). normalizeConfig lists documentation: api.documentation straight into this.config.api and no site ever reads it back. no consumer; the served `in… |
rest_api.documentation.version |
dead (verified 2026-09-21) | packages/spec/liveness/rest_api.json:127 |
note: Normalized by RestServer and read by NOTHING — the ADR-0049 fourth state (parsed, unmarked, unenforced). normalizeConfig lists documentation: api.documentation straight into this.config.api and no site ever reads it back. no consumer; the served `in… |
rest_api.documentation.termsOfService |
dead (verified 2026-09-21) | packages/spec/liveness/rest_api.json:133 |
note: Normalized by RestServer and read by NOTHING — the ADR-0049 fourth state (parsed, unmarked, unenforced). normalizeConfig lists documentation: api.documentation straight into this.config.api and no site ever reads it back. no consumer, and the served… |
rest_api.documentation.contact.name |
dead (verified 2026-09-21) | packages/spec/liveness/rest_api.json:145 |
note: The contact name an OpenAPI info.contact would carry. No consumer: the served document's info.contact is the literal { name: 'ObjectStack', url: 'https://objectstack.io' } written by packages/spec/scripts/build-openapi.ts, and rest-server.ts#registerO… |
rest_api.documentation.contact.url |
dead (verified 2026-09-21) | packages/spec/liveness/rest_api.json:151 |
note: The contact URL an OpenAPI info.contact would carry. No consumer; the served value is the build-openapi.ts literal. Drilled rather than left riding on its container's blanket verdict, and NOT by fanning the parent's status out over its children — the call… |
rest_api.documentation.contact.email |
dead (verified 2026-09-21) | packages/spec/liveness/rest_api.json:157 |
note: The contact email an OpenAPI info.contact would carry. No consumer, and the served document carries no info.contact.email at all — build-openapi.ts writes only name and url. Drilled rather than left riding on its container's blanket verdict, and NOT… |
rest_api.documentation.license.name |
dead (verified 2026-09-21) | packages/spec/liveness/rest_api.json:171 |
note: The license identifier an OpenAPI info.license would carry. z.string(), no .optional()), so authoring a license object at all forces a value the runtime then ignores. No consu… |
rest_api.documentation.license.url |
dead (verified 2026-09-21) | packages/spec/liveness/rest_api.json:177 |
note: The license URL an OpenAPI info.license would carry. No consumer; the served value is the Apache-2.0 literal URL from build-openapi.ts. Drilled rather than left riding on its container's blanket verdict, and NOT by fanning the parent's status out over its… |
Mainstream evidence:
- The OpenAPI
infoobject is the standard carrier. Azure API Management and Apigee let the API owner set it. - ServiceNow Scripted REST APIs have a name, description and documentation per API, shown in the REST API Explorer.
- Power Platform custom connectors have a "General information" tab (description, icon, host).
- Salesforce and Dataverse publish fixed platform API docs with no admin-set info. The capability exists where the platform lets owners publish an API, which is this surface.
Verdict: ENFORCE — the mainstream has the capability, so build the consumer once, correctly.
Reader that must exist / disposition: objectstack packages/rest/src/rest-server.ts#registerOpenApiEndpoints (:4869) builds enriched from the bundled spec and overrides servers, paths and tags. It must also overlay enriched.info from this.config.api.documentation. Today normalizeConfig writes the key (:4252) and nothing reads it back.
User-visible risk (1): Developer-facing documentation text; the silent no-op is cosmetic.
Acceptance: Every ledger row listed leaves dead/planned/experimental for live, citing the new reader as file#symbol (and a producer where the read depends on a supplied input); pnpm check:liveness green; the family's byStatus in state-counts.md regenerated.
Lane: domain:spec parent + packages/rest sub-issue (objectstack)
File surface: packages/spec/src/api/rest-server.zod.ts:206-225 · packages/rest/src/rest-server.ts (normalizeConfig, registerOpenApiEndpoints) · packages/spec/liveness/rest_api.json
Dedupe: rest_api\b \| RestApiConfig \| responseFormat \| termsOfService \| documentation\.(enabled\|title\|contact\|license) \| includePagination → 1 open hit. None carries a key of this family:
- decision(pm): this seat dispatched 5 clause-② reviews at the RETIRED model tier after the maintainer ruling landed — two PRs merged on void verdicts #19603 — process decision about the feat(spec): enrol
RestApiConfigSchemain the liveness ledger asrest_api#19594rest_apiledger enrolment; carries no key
四轴:
- 实际业务需求: 对外发布 API 的团队需要在文档里写上标题、版本、联系人和许可。这是 OpenAPI 的标准字段。
- 项目长远合理性: 消费端只需在已有的 OpenAPI 组装函数里加一步覆盖,把声明与产物接起来。
- 防 AI 写错: 写进去却不出现在文档里,会让 AI 以为配置位置错了而反复改写。接上之后结果可见、可测。
- 创业阶段不扩散: 一个函数里十几行,零新机制。
Unblocked by ruling 5862477514 on #20359 (B, batch #231 item 1; note 5862495365) — was blocked by #20359 from 2026-09-28T02:45Z. Execution: eight keys ENFORCE + documentation.version RETIRE, per the ruling.
Activity
objectstack-fleet commentedon Sep 27, 2026
Path: business objects, records and views | 缺项 (no item reads the served OpenAPI
infoblock) | P2Triage: first grade —
enhancement·priority:p3·domain:spec·area:api·pm:queue. Verdict: ENFORCE, by the maintainer's criterionTriage: the reader lands in
packages/rest/src/rest-server.ts#registerOpenApiEndpoints(:4869), which overlaysenriched.infofromthis.config.api.documentation⇒domain:specparent, with apackages/rest(domain:cli) sub-issue. Rationale: nine keys thatnormalizeConfigwrites (:4252) and nothing reads back. Developer-facing and cosmetic ⇒ p3.Triage seat (objectstack-wide, seat post #6015) ·
session_01W89enF2dYV7K4N2Fbfj33f· 2026-09-27T20:33Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card (no comments), the criterion on #18900 (5727134555), and the #20273 / #20274 / #20282 grades that applied it this week.Verdict. Where a platform lets owners publish an API, the owner sets its
info: Azure API Management, Apigee, ServiceNow Scripted REST APIs, and Power Platform custom connectors. By the criterion ⇒ ENFORCE, 「补消费端(一次做对)」. It is not a decision-box round-trip. The verdict records the direction, and the priority keeps it behind the road.Execution notes.
info(title, description, version, contact, license, and the rest of the nine) onto the served document, with the bundled defaults where a key is unset./openapi.jsoninfoagainst an authored config, with the defaults as the control.api.responseFormatandapi.documentation.enabled(4 keys); the envelope is fixed andenableOpenApialready decides the document #20295 (responseFormat,documentation.enabled), is graded separately. The two sharerest-server.zod.ts, so the first to claim goes first.objectstack-fleet commentedon Sep 28, 2026
Claim: PM loop round 1 · 2026-09-28T01:56Z
Session:
session_01Rjy9MeetSfq34PKn81CRiNAccount:
os-zhuangBranch:
claude/issue-20294-openapi-info-from-configWorktree:
objectstack-issue-20294Domain:
domain:specSeat:
domain:spec#1File surface: a vertical seam dispatch (ruling A′ ② on #18900), with both ends declared.
packages/rest/src/rest-server.ts: ONLYregisterOpenApiEndpoints(about :4941–:5120) and, if needed, theNormalizedRestServerConfig.apitype.packages/spec/src/api/rest-server.zod.ts: thedocumentationblock's describes only.packages/spec/liveness/rest_api.json(nine rows → live) plus regenerated counts and README row; tests inpackages/rest/src/;.changeset/. ⛔ Notrest-server.ts's draft-read doors (about :5872, :6615–:6961, :11232; the cli lane's #20338). ⛔packages/spec/scripts/build-openapi.tsis read only. (stop on breach; explain in the report)Container & model:
S,mode:subagent,model: default judgment tier. A published output changes (the served OpenAPIinfo), so the PR is reviewed atCONTRACT_REVIEW_TIERbefore enqueue.Clause-②: no
Thread-read: 5859594397
Serial constraints cleared:
Read at {{NOW}} on origin/main 26daf0b0. #20295 (the sibling RETIRE, the same schema block) landed as PR #20343 → 26daf0b0 (landing record on #20295). The only other claim naming rest-server.ts is #20338 (cli, 5861662698): its draft-read sites are disjoint from registerOpenApiEndpoints, so the later lander merges main. No open PR touches rest-server.zod.ts, liveness/rest_api.json or build-openapi.ts (15 read).objectstack-fleet commentedon Sep 28, 2026
os-dev-report
{ "issue": 20294, "status": "needs_decision", "branch": "claude/issue-20294-openapi-info-from-config", "pr": null, "session": "session_01Rjy9MeetSfq34PKn81CRiN — the PM's session (mode:subagent); this dev's identity is the branch", "premise_still_valid": true, "summary": "Stopped on the dispatch's own Zone 2 stop condition (item 2), before writing any code. The card's premise holds. With all nine keys authored, both doors that serve the document (GET /api/v1/openapi.json and the scoped /api/v1/environments/:environmentId/openapi.json) serve the bundled `info` unchanged and ignore every authored value. Measured at 26daf0b0; the three commits up to the current tip 67047171 touch neither packages/spec nor packages/rest. The fork: the two title defaults differ. The bundled info.title is 'ObjectStack REST API' (build-openapi.ts:138, pinned at openapi-self-consistency.test.ts:301). The schema MATERIALIZES a different title, 'ObjectStack API', whenever the block is present: parse of documentation: {} yields { title: 'ObjectStack API' }, and parse of {} yields no block. So any overlay that reads the parsed title retitles the document for an owner who authored only, say, description. The only fixes that keep 'bundled default where a key is unset' are a schema change, which the dispatch fences, or a rest-side read of the raw input, which is the consumer-side second source of defaults that contract-first forbids. A second conflict the grade never cites: ENFORCE reverses part of the #11646 maintainer ruling ('the info block returns to being wholly packages/spec-owned') and its whole-block pin (rest-openapi-route.test.ts:320-355). The ledger rows' own notes flag it ('reopening who owns info, which is above this ledger'). The card body truncates those notes before that sentence, and triage read only the card. The rest-side code is the same under every option. What is open is one schema line and which ruling governs; resume_plan is ready to apply.", "tests": "No diff, so no gate ran on a change. Measurement only. (1) Closure build under the lock: `pnpm --workspace-concurrency=2 --filter '@objectstack/rest^...' build` gave `os-verify-lock: VERDICT command-exit 0 · held the lock 337s`. (2) A scratch single-file vitest measurement, never committed and deleted afterwards, ran under the lock: `vitest run --project local --maxWorkers=2 src/zz-scratch-20294-measure.test.ts` gave Test Files 1 passed, VERDICT command-exit 0. `git status --porcelain` read empty after the delete. The file booted RestServer plus registerRoutes() the way rest-openapi-route.test.ts#makeRest does, with enableProjectScoping true / projectResolution auto so both doors mount, and drove each openapi.json handler. CELLS, all-nine authored → served on both doors: title 'Acme Orders API' → 'ObjectStack REST API'; description → the bundled 'Auto-generated OpenAPI specification…'; version '2.3.0' → '17.4.0'; termsOfService → absent; contact.name 'Acme API Team' → 'ObjectStack'; contact.url → 'https://objectstack.io'; contact.email → absent; license.name 'Proprietary' → 'Apache-2.0'; license.url → the Apache-2.0 URL. Honoured: 0 of 9. Cases absent, documentation: {}, description only, license.name only and contact.email only: the served info equals the artifact info on both doors, and the scoped info equals the unscoped. The normalized config DOES carry the authored values (normalizeConfig forwards the block; confirmed, no change owed there). ITEM 3, license with url and no name: schema door → ZodError issue code invalid_type, path documentation.license.name, message 'Invalid input: expected string, received undefined'; server door → construction throws 'REST API configuration is invalid: `api` does not satisfy `RestApiConfigSchema` (@objectstack/spec/api)… api.documentation.license.name: Invalid input: expected string, received undefined'. No schema change is proposed for it, so this refusal stays verbatim. ITEM 4, cache: the handler's clone is shallow ({ ...spec, servers }), so enriched.info IS the cached artifact's object by reference. Today it is unmutated in every case (cacheUnchangedAfterServe true on all 6 served cases), and the overlay must build a new info object, never assign into enriched.info. Cells on disk: scratchpad/issue-20294/zone2-cells.json and zone2-cells.md.", "gates": [ { "cmd": "bash scripts/pm/os-verify-lock.sh -c \"pnpm --workspace-concurrency=2 --filter '@objectstack/rest^...' build\"", "exit": 0, "verdict_line": "VERDICT command-exit 0 · held the lock 337s", "tree": "26daf0b0" }, { "cmd": "bash scripts/pm/os-verify-lock.sh -c \"pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2 src/zz-scratch-20294-measure.test.ts\"", "exit": 0, "verdict_line": "VERDICT command-exit 0 · held the lock 9s · waited 51s", "tree": "26daf0b0", "note": "measurement only; scratch file deleted, never committed" }, { "cmd": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack", "exit": 2, "tree": "67047171", "note": "'this branch changes nothing against origin/main — nothing to derive'. Expected: the diff is empty; not a gate failure." }, { "cmd": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (resume surface: rest-server.ts, rest-openapi-route.test.ts, rest-server.zod.ts, liveness/rest_api.json, state-counts.md, README.md)", "exit": 0, "tree": "67047171", "note": "83 projected lines, NOT RUN (no diff); saved at scratchpad/issue-20294/gates-projected.log for the resumed round" } ], "files_changed": [], "line_budget": "n/a — no diff", "deviations": [ "Worktree /home/user/objectstack-issue-20294 and its node_modules are kept after the report is read back, not removed. No PR was opened, and the resumed round reuses it: the closure build at 26daf0b0 is still valid for packages/spec and packages/rest at 67047171.", "The remote branch stays at the empty probe push (26daf0b0). The local branch is fast-forwarded to 67047171 with no commits, so there is nothing to push.", "Shared-ref race at fetch: 'cannot lock ref refs/remotes/origin/main: is at 67047171… but expected d498113b…', because a sibling's fetch advanced the ref concurrently. It resolved to 67047171, and BASE is pinned to that sha, not to the moving name.", "On resume the harness reported the primary working directory as /home/user/objectstack-seat-ro. Every command after that ran with an absolute cd into /home/user/objectstack-issue-20294; nothing ran in, or read from, the seat-ro tree." ], "clause2_projection": "Clause-②: no — projected for the recommended options (Q1 B, Q2 A, Q3 A); nothing is declared yet because nothing is written. Accept set: unchanged in both directions. Every config the schema accepts today it accepts after, and none it refuses becomes accepted; Q1 B removes a default, not an accepted input. Public surface: no new key, route, export or accepted value. The served document changes: owner values land in OpenAPI Info Object members the document already has, and termsOfService and contact.email appear only when authored. That is a new reader of already-accepted keys, closing a declared≠enforced gap. It does not widen, and the unset control stays byte-identical. Arm: none. Q1 B loosens the parsed OUTPUT type (title: string → string or undefined), which narrows no accept set; the key has 0 non-test readers. Changeset: @objectstack/rest minor (a served output now reflects config), @objectstack/spec patch (a describe and, under Q1 B, a default).", "mcp_calls": "0", "api_writes": "1 — POST /repos/objectstack-ai/objectstack/issues/20294/comments (this os-dev-report, via scripts/pm/post-stamped.mjs through the fleet-write relay). Also 1 `git push` (the empty branch probe), which is not a REST write. The REST calls to /issues/20294, /issues/20294/comments, /issues/11646(+comments) and /issues/comments/5727134555 were all GET reads.", "open_questions": [ { "question": "Q1 — The schema MATERIALIZES documentation.title = 'ObjectStack API' whenever the block is present, and the bundled info.title is 'ObjectStack REST API'. Which fix keeps 'bundled default where a key is unset' for title?", "options": [ "A — No schema change: the overlay reads the parsed title. Any authored block, even { description } alone or {}, retitles the served document to 'ObjectStack API', a string no deployment has ever served. This violates triage note 1 and the dispatch's rule that the schema default must not silently replace the bundled title.", "B — spec: `title: z.string().default('ObjectStack API')` → `z.string().optional()`, the shape the other eight keys already have. An unset title is unset, and the overlay leaves the bundled one. Accept set unchanged; the parsed output type loosens (title string → string or undefined). Two pins assert the materialized default and flip: spec rest-api-config-dead-keys-retirement.test.ts:172 and rest rest-config-parse-not-cast.test.ts:352. rest-api-config-dead-keys-refused.test.ts:153 authors its title, so it stays green. The describe and the reference row (content/docs/references/api/rest-server.mdx:260) regenerate.", "C — spec: change the default VALUE to 'ObjectStack REST API'. Shape and type unchanged, but two literals (the schema default and build-openapi.ts:138) must be held equal by a new cross-package pin, or a later retitle of the artifact drifts in silence.", "D — rest reads the RAW config to tell an authored title from a defaulted one. Rejected: it rebuilds the consumer-side second source of defaults that normalizeConfig dropped when it moved to parse-and-consume, and AGENTS.md PD#12 forbids it." ], "recommendation": "B. Need: the observable, published title is the bundled one (the @objectstack/spec/openapi.json export and its pin); 'ObjectStack API' has never reached a served byte (ledger: 0 read sites). Long-term: one source of defaults, the bundled document, which is the triage note's own rule. AI-proof: 'unset = bundled' becomes uniform across all nine keys, with no silent side effect from authoring a sibling key. Startup scope: one line, two pin flips and a regenerated reference row, no new mechanism. C is second-best: the same observable behaviour, but it buys a coupling that needs its own pin. The dispatch fences a spec shape change, hence the stop." }, { "question": "Q2 — Executing ENFORCE reverses part of the #11646 maintainer ruling: 'the info block returns to being wholly packages/spec-owned', and C there, 'a config key for a demand nobody expressed', was declared dead. Its pin (rest-openapi-route.test.ts:320-355, 'serve-time enrichment must not touch info — the WHOLE block is the artifact's') asserts body.info toEqual artifact.info. Is the ENFORCE grade the superseding record for OWNER-AUTHORED values?", "options": [ "A — Yes, for owner-authored values only. An absent block, or one with every key unset, serves info byte-identical to the artifact, and the #11646 whole-block pin keeps running as exactly that control. #11646's exclusions stay verbatim: api.version and OS_RUNTIME_VERSION never reach info.version (rest-openapi-route.test.ts:388-497 unchanged). An authored documentation.version becomes info.version. Only a host that writes the block sees a change, and that host is an embedder: os serve forwards only enableProjectScoping and projectResolution out of api, never documentation (#15543 reachability).", "B — Keep #11646 whole for version: enforce eight keys and RETIRE documentation.version (tombstone, ADR-0087 D3, changeset). This contradicts the ENFORCE grade for one key and splits the family.", "C — Keep #11646 entirely: ENFORCE cannot be executed, and the card returns to triage or the maintainer." ], "recommendation": "A. #11646 chose between two NON-authored facts, the route identifier and SPEC_VERSION, and rejected a config key for 'a demand nobody expressed'. The later #18900 criterion (5727134555) replaced exactly that demand test with the mainstream one, and triage applied it: Azure APIM and Apigee let the owner set info.version. Every value #11646 excluded stays excluded, and its invariant holds byte for byte in the unset control. Whether that reading is the PM's to make or needs the maintainer's word is the PM's call; the ledger itself says the question sits above the ledger." }, { "question": "Q3 — Overlay granularity for the two containers. The triage note says 'bundled defaults where a key is unset', read per key. Per LEAF publishes mixed-party blocks: license { name: 'MIT' } would be served as { name: 'MIT', url: the Apache-2.0 URL }, and contact { email } as ObjectStack's name and URL with the owner's email.", "options": [ "A — Per CONTAINER: an authored contact or license replaces the bundled one whole, and its unset members are absent, never inherited. The four scalars (title, description, version, termsOfService) overlay per key. info.license then always carries name, because the schema requires it (item 3).", "B — Per LEAF, as the note is literally worded." ], "recommendation": "A. Per leaf would make the served document state an MIT license at the Apache URL, a false statement in a machine-read contract (AGENTS.md route rule 4, machine-readable surfaces must not lie). Per container never publishes one. It adds no mechanism." } ], "resume_plan": "On the PM's answers (as recommended: Q1 B, Q2 A, Q3 A). (1) rest-server.ts, inside the registerOpenApiEndpoints region only: a pure exported helper overlayDocumentationInfo(bundledInfo, documentation) that returns a NEW info object (the cache holds info by reference); the handler sets enriched.info from it just before res.json. The same closure serves both doors, so both carry the overlay. The #11646 comment at :5095-5113 is rewritten to 'unset = the artifact's, authored = the owner's; api.version and the runtime version never'. (2) rest-server.zod.ts: title → .optional(); describes on the nine members state that the served /openapi.json info carries them, with the bundled value where unset; gen:schema and gen:docs. (3) Tests: a table-driven helper pin (each key alone, all nine, none = control equal to the artifact, license without url, contact partial), an HTTP pin on both doors with the no-config control, the #11646 whole-block pin kept as the unset control, and the two default pins flipped with their new substance asserted. (4) liveness/rest_api.json: the nine leaf rows plus the documentation.contact and documentation.license container rows → live; evidence packages/rest/src/rest-server.ts#overlayDocumentationInfo, producer packages/rest/src/rest-server.ts#normalizeConfig. Counts are regenerated by gen:liveness-counts (rest_api 12 live / 12 dead → 21 / 3, total 24), and the README rest_api row is edited, since its Notes prose is hand-written. (5) Changesets as in clause2_projection. The file surface stays inside the claim; build-openapi.ts is untouched, and runtime/qa need no pin.", "out_of_scope_findings": [ "carrier: this card's resumed PR (the same function) · noted, not filed — the /docs viewer page's HTML title is the literal 'ObjectStack API Docs' (rest-server.ts registerOpenApiEndpoints, the /docs handler). That door serves a title from outside `info`, so a retitled API keeps that browser-tab title. It is not an `info` field and falls in no a/b/c class; it is an Acceptance-notes candidate. Dedupe words: 'ObjectStack API Docs', /docs viewer title, Scalar.", "carrier: 承接者:无 · noted, not filed — OpenApiGenerationConfigSchema.title (packages/spec/src/api/plugin-rest-api.zod.ts:570) carries a third OpenAPI title default, also 'ObjectStack API'. Its readers were not measured here, so this is an observation only. Dedupe words: OpenApiGenerationConfigSchema, plugin-rest-api title default, openApi.title." ] }Generated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
Release:
session_01Rjy9MeetSfq34PKn81CRiN(domain:specseat 1) · reason: a premise was falsified before any edit. Executing the ENFORCE grade reverses part of an earlier maintainer ruling · to:needs-user-decision, assignee cleared in this act · 2026-09-28T02:31ZDecision needed: does the #18900 ENFORCE grade supersede #11646's 「
info块整块归packages/spec」 for owner-authored values?The dev (report above, on claim
5861942248) stopped on the order's own stop condition, before any edit. No PR exists. Its worktree and a ready resume plan are kept, so an answer re-dispatches in one step.Measured (at
26daf0b0; the tip67047171touches neither package). With all nineapi.documentationkeys authored, both doors (/api/v1/openapi.jsonand the scoped/environments/:environmentId/openapi.json) serve the bundledinfounchanged: 0 of 9 honoured. With nothing authored,documentation: {}, or one key alone, the servedinfoequals the artifact's on both doors. The cells are on the dev report.Governing text (both rulings quoted verbatim, retrieved in this act):
info.versionthatpackages/specowns, so the published artifact says17.2.0and the served document saysv1#11646, maintainer ruling5405070350(2026-08-25, 「全部同意」): 「theinfoblock returns to being whollypackages/spec-owned」 … 「A (keep the two-meanings split) and C (a config key for a demand nobody expressed) are dead.」 Pinned atrest-openapi-route.test.ts(the wholeinfoblock equals the artifact's).Seam:line on filing, vertical dispatch by default in the spec lane, automatic parent + sub-issues for spec↔objectui seams, Journey as a filter, bulk retirement per spec family, Console Pin Gate back to required (the maintainer's 「同意」 on the five-line batch, 2026-09-18) #18900, maintainer criterion5727134555: 「每族该问的是:主流平台有没有这个能力 —— 有 ⇒ 补消费端(一次做对)」.5859594397): 「Verdict: ENFORCE, by the maintainer's criterion」 … 「Overlayinfo… with the bundled defaults where a key is unset」. Triage read the card and [Decision] Route declared≠enforced work by the SEAM, not the layer — aSeam:line on filing, vertical dispatch by default in the spec lane, automatic parent + sub-issues for spec↔objectui seams, Journey as a filter, bulk retirement per spec family, Console Pin Gate back to required (the maintainer's 「同意」 on the five-line batch, 2026-09-18) #18900, but not [finding] the served openapi.json overrides theinfo.versionthatpackages/specowns, so the published artifact says17.2.0and the served document saysv1#11646. The ledger rows' own notes say ENFORCE means 「reopening who owns info, which is above this ledger」.The two sub-questions the seat folds into the same answer:
documentation.title = 'ObjectStack API'whenever the block is present. The bundled title is'ObjectStack REST API', and'ObjectStack API'has never been served. So "bundled default where unset" needstitleto become.optional(), like the other eight keys. That is a one-line spec change, with two pins flipped.contact/licensereplaces the bundled one whole, never member by member. Otherwise the document could state an MIT licence at the Apache-2.0 URL, which is a false statement in a machine-read contract. The seat takes this as settled (AGENTS.md: machine-readable surfaces must not lie).选项
api.documentation九个键若由作者写出,就覆盖到被服务文档的info。info与制品逐字节相同:[finding] the served openapi.json overrides theinfo.versionthatpackages/specowns, so the published artifact says17.2.0and the served document saysv1#11646 的整块钉子原样保留,作为对照。info.versionthatpackages/specowns, so the published artifact says17.2.0and the served document saysv1#11646 排除的两样(路由版本api.version、运行时版本)照旧永不进info.version。title改为.optional();contact/license按整块替换。documentation.version。info.version恒为SPEC_VERSION,[finding] the served openapi.json overrides theinfo.versionthatpackages/specowns, so the published artifact says17.2.0and the served document saysv1#11646 的版本结论绝对成立;代价是同一族一半 ENFORCE、一半 RETIRE。info.versionthatpackages/specowns, so the published artifact says17.2.0and the served document saysv1#11646 整条优先,这一族改判 RETIRE。 九个键全部立墓碑(ADR-0087 D3),info保持整块归 spec;[Decision] Route declared≠enforced work by the SEAM, not the layer — aSeam:line on filing, vertical dispatch by default in the spec lane, automatic parent + sub-issues for spec↔objectui seams, Journey as a filter, bulk retirement per spec family, Console Pin Gate back to required (the maintainer's 「同意」 on the five-line batch, 2026-09-18) #18900 的「主流有 ⇒ 补消费端」在此让位。四维分析
api.documentation。os serve只从api转发enableProjectScoping/projectResolution,只有嵌入方能写到这个块。info(标题、描述、版本、联系人、许可)。Seam:line on filing, vertical dispatch by default in the spec lane, automatic parent + sub-issues for spec↔objectui seams, Journey as a filter, bulk retirement per spec family, Console Pin Gate back to required (the maintainer's 「同意」 on the five-line batch, 2026-09-18) #18900 换掉的那条判据。version也能写。registerOpenApiEndpoints一处加一条 schema 行。席位推荐:A。 长远轴领起:单一默认源,作者值写出才生效,不写时 #11646 的不变式逐字节保持;新的 #18900 判据恰恰替换了 #11646 用来否决 C 的「无人提出需求」这一条。如果你要
info.version绝对等于SPEC_VERSION,选 B。如果你要info整块归 spec 不变,选 C。你要做的(一个动作): 回一个字母,A / B / C。
4 remaining items
feat(spec,rest)!: retire api.responseFormat and api.documentation.ena…
objectstack-fleet commentedon Sep 28, 2026
Claim: PM loop round 3
Session:
session_01ARcDurZ5j34RdqsGgc4jgHAccount:
os-warren(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-20294-openapi-info-overlayWorktree:
objectstack-issue-20294Domain:
domain:specSeat:
domain:spec#4(seat post #18917)File surface: a vertical dispatch (ruling A′ ② on #18900), with both ends declared. It executes ruling B on #20359 (
5862477514).packages/rest/src/rest-server.ts(domain:cli, the cross-lane surface): ONLY theregisterOpenApiEndpointsregion (a pure overlay helper plus the handler line that uses it, on both doors, and the [finding] the served openapi.json overrides theinfo.versionthatpackages/specowns, so the published artifact says17.2.0and the served document saysv1#11646 comment there). Plus its tests inpackages/rest/src/.packages/spec/src/api/rest-server.zod.ts:documentation.title→.optional(), aretiredKey()tombstone ondocumentation.version, and the describes of the eight enforced members. Plus regenerated JSON schema and reference docs.api.documentation.version, and whatever thespec-property-retirementroute requires with it: the retired-key entry,registry.tsand the step-18 lists, regenerated where a tool generates them. The precedent is spec(api): retireapi.responseFormatandapi.documentation.enabled(4 keys); the envelope is fixed andenableOpenApialready decides the document #20295'sdocumentation.enabled(PR feat(spec,rest)!: retire api.responseFormat and api.documentation.enabled (ADR-0049) #20343,26daf0b0).packages/spec/liveness/rest_api.json: the eight rows (and the two container rows) → live, and theversionrow retires with the key. Plusstate-counts.mdfromgen:liveness-counts, and the README row.'ObjectStack API'default flip. The [finding] the served openapi.json overrides theinfo.versionthatpackages/specowns, so the published artifact says17.2.0and the served document saysv1#11646 whole-block pin inrest-openapi-route.test.tsstays as the unset control..changeset/:@objectstack/specminor, and@objectstack/restper its bump policy.build-openapi.ts(the artifact stays the default source). ⛔api.versionand the runtime version never enterinfo.version. ⛔ No otherrest-server.tsregion. ⛔ Notpackages/runtime: [finding] the runtime dispatcher's/packagesdomain takes the organization from the raw session claim (resolveActiveOrganizationId, 9 sites), bypassing ruling B on #15409: a session naming a left organization may read and write it #20477's claim holdshttp-dispatcher.ts.(stop on breach; explain in the report)
Container & model:
M,mode:subagent,model: default judgment tier(no path-derived mandate).Clause-②: yesper the ruling, so an at-tier contract review atCONTRACT_REVIEW_TIERis owed before enqueue.Clause-②: yes
Thread-read: 5862495365
Serial constraints cleared: read at 2026-09-28T18:45Z on
origin/mainfc0db22b.rest-server.ts,rest-server.zod.ts,rest_api.jsonorrest-openapi-route.test.ts. PR feat(spec)!: retire the inner name on cube measures and dimensions — the record key is the member's name (#20300) #20458 (spec(analytics): retire the innernameon cube measures and dimensions; the record key is the identity (2 keys) #20300, seat 1) editspackages/spec/liveness/README.md, and PRs feat(spec)!: retire the inner name on cube measures and dimensions — the record key is the member's name (#20300) #20458 / feat(spec,cli)!: one stack authoring shape — os validate / os build refuse a default export defineStack did not build #20460 / fix(spec): reword the CurrencyConfigSchema.precision clause in two major-18 D3 entries #20488 editmigrations/registry.ts. Both are ordinary concurrency: regenerate on merge, and edit the README's hand-written row after mergingmain./packagesdomain takes the organization from the raw session claim (resolveActiveOrganizationId, 9 sites), bypassing ruling B on #15409: a session naming a left organization may read and write it #20477 (domain:cli, p0) holdspackages/runtime/src/http-dispatcher.tsand readspackages/rest/**only.rest-server.tschange onmainis5c7aa467(fix(rest, runtime): the dispatcher's /meta doors scope to the vetted organization, and its item read, book tree and list answer what RestServer answers (#20408) #20473), in the/metadoors, a different region.area:api's other in-flight cards, objectql: refuse a non-numeric string compared against a number field at the engine's field-aware filter door (400, every driver and position) — the door half of #20336 #20351 (packages/objectql) and this seat's [finding] once PR #20218 lands,PackageInstallBodySchema's published docblock still says the install door answers 201 to residual classes 1b, 2, 3 and 4, which it then refuses 400 #20219 (package-api.zod.ts, prose), have disjoint file surfaces.5862210322) in seat 1's own container, which this seat cannot reach. The resume plan is carried as text, and the dev rebuilds fromorigin/main.Ruling carried:
5862477514· #20359 · batch #231 item 1 · letter B · maintainer 「同意 B」. The landing PR closes #20359 too, per that ruling.domain:specseat 4 · #18917 ·session_01ARcDurZ5j34RdqsGgc4jgHobjectstack-fleet commentedon Sep 28, 2026
os-dev-report
{ "issue": 20294, "status": "done", "branch": "claude/issue-20294-openapi-info-overlay", "pr": "https://github.com/objectstack-ai/objectstack/pull/20512", "session": "session_01ARcDurZ5j34RdqsGgc4jgH — the PM's session (mode:subagent); this dev's identity is the branch named by claim 5876330236, the newest Claim on the card (verified before any edit)", "premise_still_valid": true, "summary": "Ruling B on #20359 executed on draft PR #20512 (head 69b4d203; origin/main 2b24b8b8 merged in through os-regen-merge.sh, no generated path moved). ENFORCE half: RestServer.overlayDocumentationInfo, a pure private static helper directly above registerOpenApiEndpoints, lays the authored title / description / termsOfService and a whole-replaced contact / license over the bundled info. It returns the bundled object itself when nothing is authored (byte-identical, the #11646 control kept unedited) and a NEW object otherwise, never writing into the cached artifact. One handler line applies it before res.json; the same closure serves both doors. RETIRE half: documentation.version is a retiredKey() tombstone whose prescription names the protocol version (spelled as the @objectstack/spec package version, since the in-repo PROTOCOL_VERSION constant 17.0.0 is a different value) and sends an app release number to description. It is registered in RETIRED_KEYS_BY_MAJOR[18] with its own D3 entry rest-api-documentation-version-retired and no D2 conversion. title is .optional(); the ledger moves rest_api from 12 live / 12 dead to 20 / 4; changeset spec+rest minor, Clause-②: yes (narrowing). Premise re-measured at fc0db22b before editing: 0 of 9 honoured on both doors, unset controls byte-identical, served info === cached artifact info. One thing needs the PM: line 2 Fixes #20359 (as ordered) turns the advisory check \"The card this PR closes must claim this branch\" red, because #20359 carries no Claim naming this branch and a dispatched dev may not post one (see open_questions).", "mechanism_assumptions": [ "1 HOLDS — measured at fc0db22b with a scratch vitest (deleted, never committed) driving both openapi.json handlers with project scoping on. All nine authored: served info equals the artifact on both doors, 0 of 9 honoured. Absent, {}, description-only and license-name-only: the served info equals the artifact, and the scoped door equals the unscoped one. sameRefAsCache was true in every case, so the overlay builds a new object; reverse-verified by leg B.", "2 HOLDS with one refinement. The route is retiredKey() on the non-strict inline documentation object, following the #20295 documentation.enabled precedent. It owes exactly: the RETIRED_KEYS_BY_MAJOR[18] exact-key entry api/RestApiConfig:documentation.version (retired-keys/18.api__RestApiConfig__documentation.version.ts), one D3 semantic entry per family rest-api-documentation-version-retired (semantic/18.rest-api-documentation-version-retired.ts), registry.ts regenerated by gen:migration-registry, no D2 conversion and so no MIGRATIONS_BY_MAJOR[18].conversionIds entry, and the ledger row kept dead with a REMOVED note. The Clause-② line takes the narrowing arm: Clause-②: yes (narrowing), where yes is the ruling's value and (narrowing) is required because a formerly accepted input is now refused (breakingDeclaration reads that arm). Refinement: the adr-0087 marker lives in the CHANGESET body, not the PR body. check-adr-0087-registration reads it there, as the HTML-comment marker registered rest-api-documentation-version-retired. The PR body carries only the Clause-② line and no marker, since HTML comments are banned on GitHub text.", "3 HOLDS. spec rest-api-config-dead-keys-retirement.test.ts (the documentation: {} control, was :172) now asserts {} with no title plus a description-only parse. rest rest-config-parse-not-cast.test.ts §D (was :352) now asserts no title and the block equal to exactly what was authored. Both flipped with the new substance asserted. The pins that authored documentation.version moved to the tombstone: the spec retirement CONTROL block and the two fixtures in packages/spec/src/api/rest-server.test.ts (:138 and the integration fixture). The tsc sweep found no other author, and the extended tree-scoped absence walk found none in packages/examples/skills/content/scripts.", "4 NEITHER is class a/b/c; this PR is not widened. (i) The /docs viewer HTML title literal ObjectStack API Docs is the Scalar page browser-tab title, not an info field. The page body renders the served (overlaid) info.title, no declared contract ties the tab to it and no key authors it, so it is polish. (ii) OpenApiGenerationConfigSchema.title third default: 0 non-test readers under packages/** (only migration-entry prose names the schema) against a lit control (RestApiConfigSchema found in rest-server.ts). No runtime parser, no ledger enrolment, no producer, so it is dormant. Both are in the PR Acceptance notes." ], "tests": "All at HEAD 69b4d203, heavy runs through os-verify-lock.sh, exit codes written to disk before any pipe. rest local: vitest run --project local exit 0, 219 files, 4185 passed, 34 skipped (VERDICT command-exit 0, held 172s). rest repo: exit 0, 1 file, 8 passed. spec local: exit 0, 572 files, 16794 passed, 1 todo (held 195s). spec repo (test:repo): exit 0, 38 files, 697 passed (held 583s; the tree-scoped absence walk runs here). rest typecheck and spec typecheck: both exit 0 (tsc --noEmit + check:test-typecheck; spec also check:scripts-typecheck). check:test-typecheck green proves the new @ts-expect-error pin bites. check:generated exit 0 (all 15 current); check:liveness exit 0. Narrowed eslint --no-inline-config --format json over the 11 changed code files: 11 files, 0 errors, 0 warnings. Population: eslint.config.mjs catch-all files glob; invariance: the config enables no type-aware linting. Pre-restart targeted run at 38a73bb4 (6 files) caught one wrong assertion of mine (the prescription itself names api.documentation.description); fixed in a98ace43 to assert the located-issue line. Reverse verification, one-time, from committed 69b4d203, each mutation through scripts/ablation-replace.mjs (anchor x1 to x0 on disk, blob changed, restore proven blob == HEAD and empty git diff HEAD). Control 11/11 passed. A, the overlay line removed (blob 3f01f03a to 5c7dde2c): 9 failed / 2 passed, the survivors being exactly the nothing-authored controls. B, the helper writes into the cached info via Object.assign (3f01f03a to 662c8f32): 3 failed / 8 passed. C, the tombstone neutralised as version: z.string().optional() ?? retiredKey( (e69191ec to 1acff1f8): spec retirement test 5 failed / 18 passed. D, cross-package type proof: a planted probe in packages/rest/src made rest tsc exit 1 with src/zz-typeproof-20294.ts(3,76): error TS2322 and 0 other errors, so rest reads the rebuilt spec .d.ts; the probe was removed and its absence confirmed. Post: both blobs == HEAD, git status empty. All subjects import source, so no dist preflight applies.", "gates": [ { "cmd": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack", "exit": 0, "verdict_line": "115 derived commands; change set 16 paths vs merge base 2b24b8b82; 879 changed lines under the 5000 threshold", "tree": "69b4d203" }, { "cmd": "node scripts/pm/dispatch-gates.mjs --ran ran2.list --repo objectstack-ai/objectstack", "exit": 1, "verdict_line": "Run reconciliation — 115 derived, 113 run, 0 NOT-MEASURED, 2 UNRUN. ✗ dispatch-gates --ran: 2 of 115 derived famil(ies) UNRUN.", "tree": "69b4d203", "note": "the 2 UNRUN are the declared skips below" }, { "cmd": "113 derived gate commands (full list and per-command exit codes in the run record)", "exit": 0, "verdict_line": "112 exit 0, 1 exit 1", "tree": "69b4d203" }, { "cmd": "pnpm check:platform-checklist", "exit": 1, "verdict_line": "check-platform-checklist: 1 problem(s) — areas/identity-auth.json: ABSENT SYMBOL — packages/plugins/plugin-auth/src/auth-plugin.ts#twoFactor", "tree": "69b4d203", "note": "INHERITED from main: 0 diff lines vs origin/main under docs/qa, packages/plugins/plugin-auth and scripts; stale since 7d630889; the gate is outside per-PR CI" }, { "cmd": "pnpm --filter @objectstack/spec run check:skill-examples", "exit": 0, "verdict_line": "259 prose examples type-check across 3 surface(s)", "tree": "69b4d203", "note": "first run exit 3 PREREQUISITE NOT MET (no client-react .d.ts); rerun after building @objectstack/client + @objectstack/client-react under the lock" }, { "cmd": "pnpm check:dual-build-cjs-loads", "exit": null, "verdict_line": "NOT MEASURED", "tree": "69b4d203", "note": "reason: reads every package dist/; a whole-tree build is outside this card's local scope (CI Build Core)" }, { "cmd": "pnpm check:type-check-debt", "exit": null, "verdict_line": "NOT MEASURED", "tree": "69b4d203", "note": "reason: --re-measure rebuilds every package; the diff touches no DEBT/EXEMPT package; check:type-check-coverage ran exit 0" }, { "cmd": "pnpm --filter @objectstack/spec check:generated", "exit": 0, "verdict_line": "✓ All 15 generated artifacts are up to date", "tree": "69b4d203" }, { "cmd": "pnpm --filter @objectstack/spec check:liveness", "exit": 0, "verdict_line": "✓ packages/spec/liveness/state-counts.md is current", "tree": "69b4d203" }, { "cmd": "bash scripts/pm/os-verify-lock.sh -c \"pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2\"", "exit": 0, "verdict_line": "VERDICT command-exit 0 · held the lock 172s", "tree": "69b4d203" }, { "cmd": "bash scripts/pm/os-verify-lock.sh -c \"pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2\"", "exit": 0, "verdict_line": "VERDICT command-exit 0 · held the lock 195s", "tree": "69b4d203" }, { "cmd": "bash scripts/pm/os-verify-lock.sh -c \"pnpm --filter @objectstack/spec exec vitest run --project repo --maxWorkers=2\"", "exit": 0, "verdict_line": "VERDICT command-exit 0 · held the lock 583s", "tree": "69b4d203" }, { "cmd": "bash scripts/pm/os-verify-lock.sh -c \"pnpm --filter @objectstack/rest exec vitest run --project repo --maxWorkers=2\"", "exit": 0, "verdict_line": "VERDICT command-exit 0 · held the lock 1s", "tree": "69b4d203" }, { "cmd": "bash scripts/pm/os-verify-lock.sh -c \"pnpm --filter @objectstack/rest run typecheck\"", "exit": 0, "verdict_line": "VERDICT command-exit 0 · held the lock 25s", "tree": "69b4d203" }, { "cmd": "bash scripts/pm/os-verify-lock.sh -c \"pnpm --filter @objectstack/spec run typecheck\"", "exit": 0, "verdict_line": "VERDICT command-exit 0 · held the lock 63s", "tree": "69b4d203" }, { "cmd": "bash scripts/pm/os-verify-lock.sh -c \"bash ablate.sh\" (legs A-D)", "exit": 0, "verdict_line": "VERDICT command-exit 0; ABLATION-DONE; every leg red as expected, all restores blob == HEAD", "tree": "69b4d203" }, { "cmd": "PR #20512 CI at read time (one read, no polling)", "exit": null, "verdict_line": "32 check runs: 8 success, 2 skipped, 21 in_progress, 1 failure = \"The card this PR closes must claim this branch\" (PR #20512 closes #20359 with no Claim on the card naming this branch)", "tree": "69b4d203", "note": "advisory, not one of the seven required contexts; see open_questions" } ], "line_budget": "n/a — no skills/** or line-ratcheted ledger touched; diff 16 files, +777 / -102 (879 changed lines) vs the 5000 human-merge threshold: under", "files_changed": [ ".changeset/20294-openapi-info-publisher-overlay.md", "content/docs/references/api/rest-server.mdx", "packages/rest/src/rest-api-config-dead-keys-refused.test.ts", "packages/rest/src/rest-api-config-defaults-follow-spec.pin.test.ts", "packages/rest/src/rest-config-parse-not-cast.test.ts", "packages/rest/src/rest-openapi-info-overlay.test.ts", "packages/rest/src/rest-server.ts", "packages/spec/liveness/README.md", "packages/spec/liveness/rest_api.json", "packages/spec/liveness/state-counts.md", "packages/spec/src/api/rest-api-config-dead-keys-retirement.test.ts", "packages/spec/src/api/rest-server.test.ts", "packages/spec/src/api/rest-server.zod.ts", "packages/spec/src/migrations/entries/retired-keys/18.api__RestApiConfig__documentation.version.ts", "packages/spec/src/migrations/entries/semantic/18.rest-api-documentation-version-retired.ts", "packages/spec/src/migrations/registry.ts" ], "deviations": [ "PR body line 2 is Fixes #20359, as the dispatch orders. The advisory check \"The card this PR closes must claim this branch\" (scripts/check-closing-target-claim.mjs) is therefore red on #20512: #20359 has no Claim naming claude/issue-20294-openapi-info-overlay, and a dispatched dev may not post a second claim. The guard re-reads comments live, so a re-run clears it once the thread carries such a Claim. It is not among the seven required contexts.", "packages/spec/src/api/rest-server.test.ts is edited: two fixtures dropped documentation.version and one comment was updated. That file is not in the named landing sites; it falls under the dispatch's \"a pin that authors documentation.version today moves to the tombstone\". The tombstone would otherwise fail tsc and parse there.", "The rest-server.ts fence was obeyed. Two comments outside the registerOpenApiEndpoints region are now stale and were left unedited: NormalizedRestServerConfig.api.documentation near :1116 (\"inner defaults applied (.title)\") and the parseDeclaredApiConfig #14366 paragraph near :4112 (\"zero read sites\"). Neither is emitted into a published .d.ts. One-line fixes are named in the PR Acceptance notes.", "A container restart around 19:43Z killed the first full rest run, whose result was lost. Everything reported was re-run at 69b4d203 after merging origin/main 2b24b8b8 with os-regen-merge.sh. The merge left no conflict and no generated path to regenerate (check:generated all current), then pnpm install --frozen-lockfile and a closure rebuild.", "The verify lock queue-timed-out (exit 99) three times behind other seats' long holders (issue-20233 verifyC.sh about 10 min, issue-20289 verify2.sh about 15 min). Each retry used the same OS_VERIFY_LOCK_SLOT=dev-20294, and no heavy run went unlocked. Long waits were spanned by one background lock call plus a foreground tail --pid, never by polling.", "Commit trailers are the model-free pair (Claude-Session + Co-authored-by: Claude) per AGENTS.md, not the harness reminder's model-bearing Co-Authored-By. The merge commit is os-regen-merge.sh's own. The PR body footer is the session-URL form AGENTS.md specifies, not the reminder's wording.", "check:skill-examples needed @objectstack/client and @objectstack/client-react built (9s under the lock) to leave PREREQUISITE NOT MET." ], "cleanup": "git status empty at 69b4d203 (== the remote head). /home/user/objectstack-issue-20294/node_modules removed before this report was posted, and the worktree removed with a plain git worktree remove (no --force) right after. No dev server or monitor was started; every background run launched here had exited before the report.", "mcp_calls": "0", "api_writes": "3 — each one repository_dispatch through the fleet-write relay, executed as objectstack-fleet[bot]: (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft #20512, relay run 36481580438); (2) label-write --assign os-warren → POST /repos/objectstack-ai/objectstack/issues/20512/assignees (relay run 36481673037; read back: assignee os-warren, labels documentation/size/l/tests/tooling untouched); (3) this os-dev-report → POST /repos/objectstack-ai/objectstack/issues/20294/comments via post-stamped.mjs. git push is not a REST write. The REST calls to /issues/20294(+comments), /issues/20359(+comments), /issues/comments/5405070350, /pulls?head=, /pulls/20512 and its check-runs / annotations were GETs.", "open_questions": [ { "question": "Closing-target claim guard vs line 2 of the PR body: PR #20512 says Fixes #20359 (ordered by the dispatch, and the ruling says the landing PR closes #20359). The advisory guard is red because #20359 has no Claim naming claude/issue-20294-openapi-info-overlay. How should it clear?", "options": [ "A — The PM posts a Claim on #20359 naming claude/issue-20294-openapi-info-overlay (it is the ruling carrier, so a claim-for-closure only), then re-runs the guard job. No PR edit is needed.", "B — The PM edits the PR body so line 2 reads Part of #20359, and closes #20359 by hand after the merge. The body is edited once, by the seat.", "C — Leave it red. It is advisory, outside the seven required contexts, and the queue ignores it." ], "recommendation": "A, because it keeps the ruled closure mechanical. The guard exists to prove that the closer owns the card, and a single claim line records that ownership without a body rewrite, which by protocol only the seat may make." } ], "out_of_scope_findings": [ "class: a · reach: named producer — commit 7d630889 (#20429) rewrote plugin-auth auth-plugin.ts so twoFactor is now only an inline plugins: { twoFactor: true } key. On origin/main 2b24b8b8, pnpm check:platform-checklist exits 1: \"areas/identity-auth.json: ABSENT SYMBOL — packages/plugins/plugin-auth/src/auth-plugin.ts#twoFactor\". This diff has 0 lines under docs/qa, packages/plugins/plugin-auth and scripts. The gate is outside per-PR CI, so the red is silent until someone runs it. Dedupe words: identity-auth twoFactor anchor, ABSENT SYMBOL auth-plugin.ts, check:platform-checklist red", "carrier: 承接者:无 · noted, not filed — /docs viewer HTML title literal ObjectStack API Docs (rest-server.ts registerOpenApiEndpoints, the /docs handler). Polish, not an info field (in the PR Acceptance notes). Dedupe words: ObjectStack API Docs, /docs viewer title, Scalar tab title", "carrier: 承接者:无 · noted, not filed — OpenApiGenerationConfigSchema.title third default ObjectStack API (packages/spec/src/api/plugin-rest-api.zod.ts). Dormant: 0 non-test readers against a lit control. Dedupe words: OpenApiGenerationConfigSchema title, plugin-rest-api openApi title default", "carrier: whoever next touches the rest-server.ts normalizeConfig / NormalizedRestServerConfig regions · noted, not filed — two comments there are now stale (see deviations). One-line fixes are in the PR Acceptance notes." ] }Generated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
os-dev-report
{ "issue": 20294, "status": "done", "round": "merge-only (sync with main + regenerate), requested after at-tier record 5878749194 PASSed at 69b4d203", "branch": "claude/issue-20294-openapi-info-overlay", "pr": "https://github.com/objectstack-ai/objectstack/pull/20512", "session": "session_01ARcDurZ5j34RdqsGgc4jgH — the PM's session (mode:subagent); identity = the branch named by claim 5876330236", "premise_still_valid": true, "new_head": "8e26fbbe887a339ca71d602db5dc46171281a2e2", "previous_head": "69b4d20391b1b2170d3b1c482859b81d0887647c", "merge_commit": "0945eb2169846a457ca35ef7b17e7df79ea95d2c (parents 69b4d203 + origin/main 0bbe4005)", "regeneration_commit": "8e26fbbe88 chore(spec): regenerate liveness state-counts on the merged tree", "regenerated_paths": [ "packages/spec/liveness/state-counts.md (pnpm --filter @objectstack/spec gen:liveness-counts)" ], "resolved_by_hand": "nothing. git merge auto-committed 0945eb21 with no conflict on any path. os-regen-merge.sh step 2 took main's side of the one os-regen path both sides changed, state-counts.md, and its step-3 commit was refused by pre-commit as designed (\"1 generated artifact merged WITHOUT a text merge\"). I cleared that refusal by regenerating the file with gen:liveness-counts, then git add -A and commit. No hunk was hand-edited, and there was no rebase and no force-push.", "summary": "Worktree re-created at the remote head 69b4d203 (local == remote). origin/main 0bbe4005 (#20511 landed) was merged with scripts/pm/os-regen-merge.sh. The one generated path both sides touched was regenerated on the merged tree, then pushed with a plain git push. Nothing outside the regenerated path moved. git diff origin/main..HEAD is the same 16 files, +777 / -102, as the pre-merge delta 2b24b8b8..69b4d203. Per file the changed-line set is identical for 15 of 16; state-counts.md differs only in its generated totals row, 941/147 to 949/139 instead of 940/148 to 948/140, because main moved qa from 8/1 to 9/0. state-counts.md carries both rows: `qa` 9 | 0 | 0 | 0 | 0 | 9 (#20511) and `rest_api` 20 | 0 | 0 | 4 | 0 | 24 (this PR). The hand-written README rest_api row survived (the \"Live 20 =\" and \"documentation.version tombstone (RETIRED 2026-09-28\" sentences each grep 1). registry.ts carries both of this PR's entries (2 hits) and passes its check. PR body not edited: no line in it is made false by the merge; its measurements are stated at 69b4d203.", "gates": [ { "cmd": "pnpm --filter @objectstack/spec check:generated", "exit": 0, "verdict_line": "✓ All 15 generated artifacts are up to date — measured against packages/spec/dist AS FOUND ON DISK", "tree": "8e26fbbe", "note": "dist rebuilt first: pnpm install --frozen-lockfile (the merge moved pnpm-lock.yaml), then pnpm --workspace-concurrency=2 --filter '@objectstack/rest^...' build under the lock, VERDICT command-exit 0 · held the lock 127s" }, { "cmd": "pnpm --filter @objectstack/spec check:liveness", "exit": 0, "verdict_line": "✓ packages/spec/liveness/state-counts.md is current — the same 40 row(s), no count column left in the README.", "tree": "8e26fbbe" }, { "cmd": "pnpm --filter @objectstack/spec check:migration-registry", "exit": 0, "verdict_line": "✓ src/migrations/registry.ts is current (313 semantic, 232 retired-key, 206 retired-def)", "tree": "8e26fbbe" }, { "cmd": "bash scripts/pm/os-verify-lock.sh -c \"pnpm --filter @objectstack/spec exec vitest run --project repo --maxWorkers=2\"", "exit": 0, "verdict_line": "Test Files 39 passed (39) · Tests 701 passed (701) · VERDICT command-exit 0 · held the lock 486s", "tree": "8e26fbbe" }, { "cmd": "bash scripts/pm/os-verify-lock.sh -c \"pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2\"", "exit": 0, "verdict_line": "Test Files 220 passed (220) · Tests 4189 passed | 40 skipped (4229) · VERDICT command-exit 0 · held the lock 188s", "tree": "8e26fbbe", "note": "the first attempt was queue-timeout exit 99 behind another seat's holder; retried on the same OS_VERIFY_LOCK_SLOT" } ], "files_changed": [ "packages/spec/liveness/state-counts.md (regeneration commit only; the merge commit brings main's own files)" ], "deviations": [], "mcp_calls": "0", "api_writes": "1 — this os-dev-report: POST /repos/objectstack-ai/objectstack/issues/20294/comments via post-stamped.mjs through the fleet-write relay. The only other write is git push 69b4d203..8e26fbbe (not REST). No PR body edit.", "cleanup": "worktree /home/user/objectstack-issue-20294: node_modules removed before this post, then removed with a plain git worktree remove.", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
Review: ACCEPT · PR #20512 at head
8e26fbbe887a339ca71d602db5dc46171281a2e2· 2026-09-28T22:41Zdomain:specseat 4 (session_01ARcDurZ5j34RdqsGgc4jgH), reviewer of record under claim5876330236, with the closure claim5878320739on #20359. This seat took the readings below on GitHub or on the tree.main. Body line 1Fixes #20294, line 2Fixes #20359(the ruling orders this PR to close its carrier), andClause-②: yes (narrowing)stands alone. The closing-target claim guard is green, with the closure claim on #20359 naming this branch.5862477514) on both halves. ENFORCE: eightapi.documentationidentity members overlay the servedinfoon both doors,contact/licensereplace whole,titlebecomes.optional(), and nothing authored ⇒ the artifact'sinfo, byte-identical. RETIRE:documentation.versionis aretiredKey()tombstone with theRETIRED_KEYS_BY_MAJOR[18]key and a D3 entry, and the servedinfo.versionstaysSPEC_VERSION. Ledgerrest_api12/12 → 20/4. Changesets: spec and restminorwith the BREAKING banner.5878749194at69b4d203. It verifies every ruled parameter and the four pins per door, and the cache discipline: the overlay returns the bundled object when nothing is authored, otherwise a new object, and never writes into the cached artifact; ablation leg B's 3 red were read off the assertions. The tombstone message is true of the served value, sinceinfo.versionispkg.versionof@objectstack/spec, a different number fromPROTOCOL_VERSION. The retirement route matches thedocumentation.enabledprecedent.69b4d203→8e26fbbe: the merge oforigin/main0bbe4005(#20511) throughos-regen-merge.sh, plusgen:liveness-countson the merged tree. Nothing was hand-resolved.Regen-provenance:5879799287carries the record forward. This seat checked the committed trees: 15 of 16 files have an identical changed-line set, andstate-counts.mddiffers only in its generated totals row, by #20511'sqa8/1 → 9/0. The dev re-ran spectest:repo(701 passed) and the rest suite (4189 passed) at8e26fbbe.check-expected-skips.mjs --pr 20512, exit 0).mergeable_state:clean.git merge-treeagainstorigin/main0bbe4005is clean.check-governed-merges.mjs --pr 20512: 0 of 16 paths), 879 changed lines.Out-of-scope findings:
rest-server.tsoutside the fenced region (NormalizedRestServerConfig.api.documentationnear :1116, theparseDeclaredApiConfigparagraph near :4112). The record corrects the dev: the :4112 JSDoc most likely ships on a private stub in@objectstack/rest's.d.ts. Both are prose with nil reach, and not an a/b/c finding. Carrier: this seat's next dispatch that touchesrest-server.ts, as two one-line comment fixes named in its order./docsviewer's literal HTML title, andOpenApiGenerationConfigSchema.title's dormant third default → Acceptance notes, not a/b/c, no carrier owed.check:platform-checklistred onmain→ carrier [finding]check:platform-checklistis red onmain:identity-auth.jsonanchorsauth-plugin.ts#twoFactor, which #20429 turned into an inline nested key #20464.Landing next: ready + auto-merge through the relay. At MERGED, the
Fixeslines close #20294 and #20359.domain:specseat 4 · #18917 ·session_01ARcDurZ5j34RdqsGgc4jgHobjectstack-fleet commentedon Sep 28, 2026
Landed: PR #20512 →
main80153f5a4a9c641e78b21cd84a35069a84192a96· 2026-09-28T23:06Zdomain:specseat 4 (session_01ARcDurZ5j34RdqsGgc4jgH), landing record for claim5876330236. ACCEPT5880040657at8e26fbbe, on the at-tier PASS5878749194carried over one pure-regeneration hop byRegen-provenance:5879799287. Merged through the merge queue.Verified on
origin/main:80153f5ahas one parent,9e1689f8, and is an ancestor oforigin/main.overlayDocumentationInfoinpackages/rest/src/rest-server.ts0 → 3; the retired-key entry18.api__RestApiConfig__documentation.version.tsis present.state-counts.mdreadsrest_api20 live / 4 dead / 24, beside feat(spec,core,cli)!: a scenario's requires is checked before it runs — unmet params or services skip it with a reason; requires.plugins retires into requires.services #20511'sqarow.Close-out: the PR's
Fixeslines closed this card and #20359completed, as ruling B ordered.pm:dispatchedcomes off both in this act. Ruling5862477514is executed: the eight identity members are honoured on both OpenAPI doors, anddocumentation.versionis retired, withinfo.versionstayingSPEC_VERSION. Carried from the ACCEPT: two stalerest-server.tscomments ride this seat's next dispatch that touches that file.domain:specseat 4 · #18917 ·session_01ARcDurZ5j34RdqsGgc4jgHfeat(spec,rest)!: the served OpenAPI info carries the publisher's api…
test(spec): the second api/ file group's test titles state each cited…