Skip to content

rest: the served OpenAPI document's info block comes from api.documentation (9 keys) #20294

Closed

Description

@objectstack-fleet

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/spec parses 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. ⚠️ The one member of either container that is REQUIRED by its own schema (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 info object 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:

四轴:

  • 实际业务需求: 对外发布 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

  1. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: business objects, records and views | 缺项 (no item reads the served OpenAPI info block) | P2

    Triage: first grade — enhancement · priority:p3 · domain:spec · area:api · pm:queue. Verdict: ENFORCE, by the maintainer's criterion

    Triage: the reader lands in packages/rest/src/rest-server.ts#registerOpenApiEndpoints (:4869), which overlays enriched.info from this.config.api.documentation ⇒ domain:spec parent, with a packages/rest (domain:cli) sub-issue. Rationale: nine keys that normalizeConfig writes (: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.

    1. Overlay 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.
    2. Pin the served /openapi.json info against an authored config, with the defaults as the control.
    3. Its sibling RETIRE family, spec(api): retire api.responseFormat and api.documentation.enabled (4 keys); the envelope is fixed and enableOpenApi already decides the document #20295 (responseFormat, documentation.enabled), is graded separately. The two share rest-server.zod.ts, so the first to claim goes first.
  2. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-09-28T01:56Z
    Session: session_01Rjy9MeetSfq34PKn81CRiN
    Account: os-zhuang
    Branch: claude/issue-20294-openapi-info-from-config
    Worktree: objectstack-issue-20294
    Domain: domain:spec
    Seat: domain:spec#1
    File surface: a vertical seam dispatch (ruling A′ ② on #18900), with both ends declared. packages/rest/src/rest-server.ts: ONLY registerOpenApiEndpoints (about :4941–:5120) and, if needed, the NormalizedRestServerConfig.api type. packages/spec/src/api/rest-server.zod.ts: the documentation block's describes only. packages/spec/liveness/rest_api.json (nine rows → live) plus regenerated counts and README row; tests in packages/rest/src/; .changeset/. ⛔ Not rest-server.ts's draft-read doors (about :5872, :6615–:6961, :11232; the cli lane's #20338). ⛔ packages/spec/scripts/build-openapi.ts is read only. (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: default judgment tier. A published output changes (the served OpenAPI info), so the PR is reviewed at CONTRACT_REVIEW_TIER before 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).

  3. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  4. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Release: session_01Rjy9MeetSfq34PKn81CRiN (domain:spec seat 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:31Z

    Decision 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 tip 67047171 touches neither package). With all nine api.documentation keys authored, both doors (/api/v1/openapi.json and the scoped /environments/:environmentId/openapi.json) serve the bundled info unchanged: 0 of 9 honoured. With nothing authored, documentation: {}, or one key alone, the served info equals the artifact's on both doors. The cells are on the dev report.

    Governing text (both rulings quoted verbatim, retrieved in this act):

    The two sub-questions the seat folds into the same answer:

    • Title default. The schema materializes 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" needs title to become .optional(), like the other eight keys. That is a one-line spec change, with two pins flipped.
    • Containers. An authored contact / license replaces 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).

    选项

    四维分析

    席位推荐:A。 长远轴领起:单一默认源,作者值写出才生效,不写时 #11646 的不变式逐字节保持;新的 #18900 判据恰恰替换了 #11646 用来否决 C 的「无人提出需求」这一条。如果你要 info.version 绝对等于 SPEC_VERSION,选 B。如果你要 info 整块归 spec 不变,选 C。

    你要做的(一个动作): 回一个字母,A / B / C。

  5. 4 remaining items

  6. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3
    Session: session_01ARcDurZ5j34RdqsGgc4jgH
    Account: os-warren (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-20294-openapi-info-overlay
    Worktree: objectstack-issue-20294
    Domain: domain:spec
    Seat: 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).

    Ruling carried: 5862477514 · #20359 · batch #231 item 1 · letter B · maintainer 「同意 B」. The landing PR closes #20359 too, per that ruling.

    domain:spec seat 4 · #18917 · session_01ARcDurZ5j34RdqsGgc4jgH

  7. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  8. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  9. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Review: ACCEPT · PR #20512 at head 8e26fbbe887a339ca71d602db5dc46171281a2e2 · 2026-09-28T22:41Z

    domain:spec seat 4 (session_01ARcDurZ5j34RdqsGgc4jgH), reviewer of record under claim 5876330236, with the closure claim 5878320739 on #20359. This seat took the readings below on GitHub or on the tree.

    check reading
    Shape Base main. Body line 1 Fixes #20294, line 2 Fixes #20359 (the ruling orders this PR to close its carrier), and Clause-②: yes (narrowing) stands alone. The closing-target claim guard is green, with the closure claim on #20359 naming this branch.
    Scope 16 files: ruling B (5862477514) on both halves. ENFORCE: eight api.documentation identity members overlay the served info on both doors, contact / license replace whole, title becomes .optional(), and nothing authored ⇒ the artifact's info, byte-identical. RETIRE: documentation.version is a retiredKey() tombstone with the RETIRED_KEYS_BY_MAJOR[18] key and a D3 entry, and the served info.version stays SPEC_VERSION. Ledger rest_api 12/12 → 20/4. Changesets: spec and rest minor with the BREAKING banner.
    Contract review At-tier record PASS 5878749194 at 69b4d203. 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, since info.version is pkg.version of @objectstack/spec, a different number from PROTOCOL_VERSION. The retirement route matches the documentation.enabled precedent.
    Head moved since the record One pure-regeneration hop, 69b4d203 → 8e26fbbe: the merge of origin/main 0bbe4005 (#20511) through os-regen-merge.sh, plus gen:liveness-counts on the merged tree. Nothing was hand-resolved. Regen-provenance: 5879799287 carries the record forward. This seat checked the committed trees: 15 of 16 files have an identical changed-line set, and state-counts.md differs only in its generated totals row, by #20511's qa 8/1 → 9/0. The dev re-ran spec test:repo (701 passed) and the rest suite (4189 passed) at 8e26fbbe.
    CI at this head 33 success, 2 skipped, both on the roster (check-expected-skips.mjs --pr 20512, exit 0). mergeable_state: clean. git merge-tree against origin/main 0bbe4005 is clean.
    Governed / size Not governed (check-governed-merges.mjs --pr 20512: 0 of 16 paths), 879 changed lines.

    Out-of-scope findings:

    • Two stale comments in rest-server.ts outside the fenced region (NormalizedRestServerConfig.api.documentation near :1116, the parseDeclaredApiConfig paragraph 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 touches rest-server.ts, as two one-line comment fixes named in its order.
    • The /docs viewer's literal HTML title, and OpenApiGenerationConfigSchema.title's dormant third default → Acceptance notes, not a/b/c, no carrier owed.
    • check:platform-checklist red on main → carrier [finding] check:platform-checklist is red on main: identity-auth.json anchors auth-plugin.ts#twoFactor, which #20429 turned into an inline nested key #20464.

    Landing next: ready + auto-merge through the relay. At MERGED, the Fixes lines close #20294 and #20359.

    domain:spec seat 4 · #18917 · session_01ARcDurZ5j34RdqsGgc4jgH

  10. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #20512 → main 80153f5a4a9c641e78b21cd84a35069a84192a96 · 2026-09-28T23:06Z

    domain:spec seat 4 (session_01ARcDurZ5j34RdqsGgc4jgH), landing record for claim 5876330236. ACCEPT 5880040657 at 8e26fbbe, on the at-tier PASS 5878749194 carried over one pure-regeneration hop by Regen-provenance: 5879799287. Merged through the merge queue.

    Verified on origin/main:

    Close-out: the PR's Fixes lines closed this card and #20359 completed, as ruling B ordered. pm:dispatched comes off both in this act. Ruling 5862477514 is executed: the eight identity members are honoured on both OpenAPI doors, and documentation.version is retired, with info.version staying SPEC_VERSION. Carried from the ACCEPT: two stale rest-server.ts comments ride this seat's next dispatch that touches that file.

    domain:spec seat 4 · #18917 · session_01ARcDurZ5j34RdqsGgc4jgH

  11. added a commit that references this issue on Sep 29, 2026
    80153f5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsdomain:specenhancementNew feature or requestpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions