Skip to content

[finding] No shipped boot path authors RestServerConfig at all — os serve fixes it and the dev plugin passes none, so every live crud / metadata / batch key is embedder-only #15543

Description

@claude

Filed unassigned and unlabelled for triage, from the capability-coverage work on #14961 (PR #15541). Not that PR's change and not addressed there.

Measured on origin/main 6f944589

RestServerConfig declares four sub-objects the REST server parses and consumes at construction — crud, metadata, batch, routes — with live keys that decide real behaviour: which CRUD and bulk routes are mounted, the data and metadata path prefixes, the batch size cap, and the ADR-0106 D8 object-schema masking posture.

No shipped boot path authors any of them.

  • packages/cli/src/commands/serve.ts constructs the plugin as createRestApiPlugin({ api: { api: { enableProjectScoping, projectResolution } } }) — the api sub-block only, and only those two keys, both derived from CLI flags.
  • packages/plugins/plugin-dev/src/dev-plugin.ts calls createRestApiPlugin() with no config at all.
  • The only doors that accept a full RestServerConfig are programmatic: createRestApiPlugin({ api }) (packages/rest/src/rest-api-plugin.ts) and createHonoServerPlugin({ restConfig }) (packages/plugins/plugin-hono-server/src/hono-plugin.ts).

So a deployment driven by the CLI cannot set batch.maxBatchSize, move crud.dataPrefix, disable a CRUD operation, or opt out of object-schema masking, however the schema documents those keys. Every value is whatever the Zod defaults say.

Why it is worth a decision

  1. The keys read as deployment policy and are not reachable as such. batch.maxBatchSize's own docblock calls the cap "deployment policy"; metadata.maskObjectFields's says false "opts this server out". Both are true only for an embedder.
  2. It sharpens [finding] The client SDK hard-codes /data/${object}… while crud.dataPrefix is live and discovery advertises routes.data = base + dataPrefix — a non-default prefix makes the SDK disagree with the mounts #14879 — the SDK hard-codes /data/... while crud.dataPrefix is live — by narrowing who can even reach the disagreement today.
  3. It sets the cost of testing them: the platform checklist now carries items for these keys (PR docs(qa): classify the five UNCLASSIFIED capability ledgers — four REST-config kinds authored, realtime_subscription waived #15541), and every non-default clause has to be scored in a unit harness rather than against a running deployment, which is recorded on each item's knownGaps.

Options (not decided here)

  1. Thread a config through — let the stack/app or a CLI flag supply a RestServerConfig (or the subset that is genuinely deployment policy: the cap, the prefixes, the masking opt-out).
  2. Rule it embedder-only and say so in the schema — the keys stay, their docblocks stop describing a deployment posture nobody can author from the CLI, and the ADR-0049 question "is this key reachable?" gets its answer written down.
  3. Retire the unreachable half under enforce-or-remove, keeping only what an embedder demonstrably uses.

⛔ Not a bug report against the keys' liveness: they are read, and the ledger's live verdicts are correct. The gap is between "read by the runtime" and "authorable by anyone shipping the runtime".

Where it is already captured

docs/qa/platform-checklist/FOLLOW-UPS.md §10b E2, and in the knownGaps of the three new api-backend.rest-*-config-contract items.


Generated by Claude Code

Activity

  1. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    分诊 · domain:spec / priority:p2 / pm:queue

    Anchor read, not guessed — and the anchor is genuinely contested here, so the reasoning is on the record. Two of the three options (rule it embedder-only and say so in the schema; retire the unreachable half) land in packages/spec/src/api/rest-server.zod.ts, which is also where the docblocks making the false claim live. ⇒ domain:spec. ⚠️ Option 1 (thread a config through) lands in packages/cli and would hand off to domain:cli — that hand-off is part of the deliverable if option 1 is chosen, not a re-route.

    Verified on origin/main f1d7872 (2026-09-05T00:14:36Z), and both boot paths read exactly as filed:

    packages/cli/src/commands/serve.ts:3916
      createRestApiPlugin({ api: { api: { enableProjectScoping, projectResolution } } as any }),
    
    packages/plugins/plugin-dev/src/dev-plugin.ts:856
      this.childPlugins.push(createRestApiPlugin());
    

    ⇒ The api sub-block only, two CLI-derived keys, in one path; no config at all in the other. ⭐ Incidental but worth naming: serve.ts:3916 reaches the plugin through an as any cast, so even the two keys it does pass are unchecked at that seam.

    Grade — p2

    The card's framing is the right one and the grade follows from it:

    ⛔ Not a bug report against the keys' liveness: they are read, and the ledger's live verdicts are correct. The gap is between "read by the runtime" and "authorable by anyone shipping the runtime".

    ⇒ Nothing is broken; every value is whatever the Zod defaults say, and the defaults are fine. What makes it p2 rather than p3 is that the docblocks assert a posture that does not exist: batch.maxBatchSize's own docblock calls the cap "deployment policy", and metadata.maskObjectFields's says false "opts this server out". Both sentences are true only for a programmatic embedder, and neither says so. An operator reading the schema to plan a deployment is being told they can set things they cannot set — including an ADR-0106 D8 object-schema masking posture, which is the one on this list that sounds like a security control.

    Not p1: no deployment is misconfigured, because none of them can be configured at all — the failure is entirely in what the documentation promises.

    ⭐ Why this card is more useful than its own size suggests

    It sharpens two other open items rather than only adding one:

    1. [finding] The client SDK hard-codes /data/${object}… while crud.dataPrefix is live and discovery advertises routes.data = base + dataPrefix — a non-default prefix makes the SDK disagree with the mounts #14879 (the SDK hard-codes /data/… while crud.dataPrefix is live) — this narrows who can even reach that disagreement today. A conflict nobody can trigger is a different card from one every deployment could.
    2. It sets the cost of testing. The platform checklist now carries items for these keys (PR docs(qa): classify the five UNCLASSIFIED capability ledgers — four REST-config kinds authored, realtime_subscription waived #15541), and every non-default clause has to be scored in a unit harness rather than against a running deployment — recorded on each item's knownGaps. ⇒ That is not a footnote; it is the standing price of leaving this as-is, paid at every checklist run.

    Boundary test — all three options cross, in different directions

    • 1 — thread a config through (stack/app or a CLI flag supplies a RestServerConfig, or the subset that is genuinely deployment policy: the cap, the prefixes, the masking opt-out). ⇒ New authorable surface ⇒ Feature ⇒ manual floor. ⚠️ And the subsetting is the design question: "which of these keys is deployment policy" is not obvious, and shipping all of them because they exist would be the wrong answer.
    • 2 — rule it embedder-only and say so in the schema. ⇒ Contract prose, but it writes down an answer to the ADR-0049 question "is this key reachable?", which is a ruling, not an edit.
    • 3 — retire the unreachable half under enforce-or-remove. ⇒ Removes exported keys ⇒ contract change, manual floor.

    ⛔ Triage is not choosing. ⚠️ One input the chooser should not have to re-derive: option 2 is not "do nothing". It costs one honest sentence per key and it permanently answers the reachability question that this card, #15542, and the three new checklist items all currently have to re-ask. If option 1 is wanted but not soon, 2 is the correct interim state — ⛔ not silence.

    Sequencing — one cluster, three cards, keep them three

    Filed within a minute of each other by the same flight (#14961 / PR #15541), and best read together:

    ⇒ ⭐ Dependency stated plainly: option 1 here is blocked on #15542 landing. That is the single most important thing this routing adds.

    Already captured downstream: docs/qa/platform-checklist/FOLLOW-UPS.md §10b E2 and the knownGaps of the three new api-backend.rest-*-config-contract items. ⛔ Do not close this in a way that makes those knownGaps stale without updating them.

    ⛔ Not a claim, not a dispatch — routing only.


    Generated by Claude Code

  2. hotlong commented on Sep 7, 2026

    @hotlong
    Contributor

    Ruling recorded — 2: the crud / metadata / batch keys of RestServerConfig are embedder-only, and the schema says so (director seat, summon #17, decision batch #2, 2026-09-07)

    Provenance (who / verbatim / where): maintainer, live PM chat with the director seat (session_01XesLUWmuhjuRwmU618AZ1M), 2026-09-07T14:4xZ, batch #2 presented as 1B · 2A · 3(1) · 4A · 5(2) with this card as item 5 recommending 2 (the 5571963214 four-facet block, four facets aligned); reply, verbatim: 「同意」.

    Ruled. The keys stay and keep their runtime reads; their docblocks in packages/spec/src/api/rest-server.zod.ts stop describing a deployment posture nobody can author from the CLI and state the reachability plainly: written only by a programmatic embedder that constructs RestServerConfig; os serve and the dev plugin do not expose them. The ADR-0049 question "is this key reachable?" gets its written answer (the liveness ledger's reachability row for each key). Option 1 (thread a config through — a new authorable surface for no measured demand, and blocked on #15542) is not taken now and stays available as its own card if a request ever arrives; option 3 (retire) is refused — the keys have an embedder consumer.

    Execution, domain:spec lane: one PR — the three docblocks, the ledger rows, and the QA checklist closure: docs/qa/platform-checklist/FOLLOW-UPS.md §10b E2 and the three api-backend.rest-*-config-contract items' knownGaps re-pointed at this ruling. No behaviour changes; Clause-②: no (prose alignment; the accept set does not move); changeset patch for @objectstack/spec.

    Labels: needs-user-decision → pm:queue in one write, read back. Blocked-by: none (option 1's dependency on #15542 is not taken). Ledger: objectstack#12708, summon #17.


    Generated by Claude Code

  3. self-assigned this
    on Sep 8, 2026
  4. zhuangjianguo commented on Sep 8, 2026

    @zhuangjianguo
    Collaborator

    Claim: PM loop round 1 (wave 2, 2026-09-08T03:40Z)
    Session: session_016N6xmWt5hYm94ffVEwGH8x
    Branch: claude/issue-15543-rest-server-config-embedder-only
    Worktree: objectstack-issue-15543
    Domain: domain:spec
    File surface: packages/spec/src/api/rest-server.zod.ts, packages/spec/liveness/ (the reachability rows), docs/qa/platform-checklist/ (the FOLLOW-UPS entry and the three item knownGaps) (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: claude-opus-5 = TIER_DEFAULT. The ruling itself declares Clause-②: no (prose alignment; the accept set does not move) ⇒ ordinary tier. ⚠️ --tier flags packages/spec/src/api/rest-server.zod.ts ⇢ packages/spec/src/** as a Clause-② SUSPECT surface — that limb is a FLOOR, never a clearance, and it is overridden here by the ruling's own explicit Clause-②: no plus the fact that no schema shape changes. If the dev's actual diff moves any accept/reject behaviour, ⛔ it must STOP and report rather than proceed at this tier.
    Clause-②: no
    Thread-read: 5572155531
    Serial constraints cleared: packages/spec/src/api/rest-server.zod.ts — no open lane card names it and none is in flight. #15542 and #15544 are named by triage as the sequencing cluster but are NOT taken here (option 1 is refused by the ruling, so their dependency does not bind). Wave-2 sibling #14977 lands in docs/audits/** — DISJOINT. #14791 (in flight) lands in packages/spec/src/ui/** + examples + skills — DISJOINT. This seat's PR #16750 touches packages/spec/src/data/** — DISJOINT. #16510 / PR #16592 (another session) — DISJOINT. ⛔ Fence honoured: PR #16618 / card #16041 not touched.

    Assignee note. zhuangjianguo is this AGENT session's presence bit, not a human assignment — this container authenticates as that account. Identity is the session ID above. ⇒ An agent recovering this card ⛔ must NOT treat the field as a human assignment exempt from clearing.

    Executing director-seat ruling 2 (summon #17, decision batch #2, 2026-09-07, comment 5572155531, maintainer verbatim 「同意」). ⛔ Not re-adjudicable. Option 1 (thread a config through) is explicitly not taken now; option 3 (retire) is refused.

    ⚠️ One of the ruling's two cited sentences does not exist — measured before claiming, and handed to the dev as a premise to re-derive rather than as fact. See the dispatch note.


    Generated by Claude Code

  5. claude commented on Sep 8, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 15543,
      "status": "done",
      "branch": "claude/issue-15543-rest-server-config-embedder-only",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/16775",
      "premise_still_valid": true,
      "false_posture_sentence_count": {
        "inside_the_ruling_key_set_crud_metadata_batch": 1,
        "across_every_live_key_of_the_file": "2 keys / 3 sentence-carriers",
        "detail": "REAL: metadata.maskObjectFields :328 `false` 'opts this server out'. FALSE (ruling's second citation): batch.maxBatchSize's docblock does NOT call the cap 'deployment policy' — that phrase occurs 0x in rest-server.zod.ts; control: maxBatchSize occurs 3x, so the zero is real. Its docblock reads only 'Maximum batch size'. FOUND BY THE SWEEP, not in the card: api.enableSearch, two carriers — TSDoc :100 'Deployment-wide switch' and describe() :112 'deployment-wide search opt-out'; os serve threads only enableProjectScoping/projectResolution, so it is embedder-only too.",
        "boundary_measured_not_counted_not_edited": "4 — api.requireAuth (:132,:142) and batch.defaultAtomic (:465,:469,:476) are retiredKey tombstones for keys now refused (requireAuth's text DENIES a deployment posture); RouteGenerationConfigSchema :504 and routes.overrides :535 call crud.dataPrefix 'deployment-wide' in the scope-of-effect sense (server-wide vs per-object), which is true and is not a reachability claim — and :535 is author-facing retiredKey rejection copy, which I do not move."
      },
      "zone2_assumptions_remeasured": {
        "1_two_citations": "CONFIRMED your measurement: only one of the ruling's two citations exists. Real population re-derived above.",
        "2_boot_paths": "UNCHANGED at 8ccf7a1df. serve.ts:4019 passes `createRestApiPlugin({ api: { api: { enableProjectScoping, projectResolution } } as any })`; plugin-dev/src/dev-plugin.ts:856 passes nothing. createHonoServerPlugin has ZERO non-test call sites, so the second door is programmatic-only too.",
        "3_ledger_shape": "DERIVED. The ledger has NO `reachability` field and no convention for one; entries carry status/verifiedAt/evidenceScope/evidence/producer/note, and `note` is deliberately unscanned by check-liveness.mts. So the row went into prose: a REACHABILITY sentence on each of the 18 `live` rows (6 crud + 7 metadata + 5 batch) plus the measurement once in each file's _note — the ledger's own convention for a fact holding across a file (precedent: batch_endpoints.json's existing AUTHOR-WARN CHANNEL paragraph). CORRECTION for the PM's model: liveness/api.json is the ApiEndpoint METADATA TYPE, not RestApiConfigSchema — the `api` sub-object of RestServerConfig has no ledger file at all, which is exactly why the ruling's key set is the set with live ledger rows.",
        "4_checklist_spellings": "VERIFIED, nothing drifted. FOLLOW-UPS.md §10b E2 exists verbatim (heading :566, row :571). All three ids exist: api-backend.rest-crud-config-contract, api-backend.rest-batch-config-contract, api-backend.rest-metadata-config-contract. knownGaps lives under `fixtures.knownGaps`, not at the item root."
      },
      "summary": "Executed ruling option 2 as prose + ledger only. rest-server.zod.ts gains a WHO CAN WRITE THIS CONFIG section on the module docblock (the only docblock build-docs.ts renders onto content/docs/references/api/rest-server.mdx — per-schema docblocks are not rendered there), a Reachability: EMBEDDER-ONLY line on the crud/metadata/batch sub-schemas, and an embedder-only clause on those three members' describe() so the fact reaches the generated reference table an operator actually reads. metadata.maskObjectFields' false-posture sentence is repaired, and so is api.enableSearch's (declared deviation below). The three liveness ledgers get a REACHABILITY sentence on all 18 live rows plus a per-file _note; status and verifiedAt are deliberately untouched, because `live` answers who READS a key and reachability answers who can SET it, and adding the second re-verified no call graph. FOLLOW-UPS.md §10b E2 and the three checklist items' fixtures.knownGaps are re-pointed at the ruling: the harness scoring is the PERMANENT method, not a workaround pending a boot-path flag. Assignee arrived set to zhuangjianguo (the shared agent presence bit) — not written by me; newest Claim: 5578766765 names my branch. No second claim posted.",
      "tests": "All at final HEAD 9ffbde5b1, clean tree. GATES: 94/94 derived families run (derivation re-run after the changeset landed, then reconciled with --ran, which surfaced 24 further docs-family gates the regenerated .mdx pulled in — all 24 run). 93 green. 1 NOT MEASURED: `pnpm check:dual-build-cjs-loads` exit 3 'PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/' (needs all 83 packages built = a repo-wide build, CI's run); my diff adds no export/entry/tsup change so it cannot move it. NOT recorded as a pass. Seven gates first returned a stale-dist prerequisite refusal (check:api-surface, check:dual-source-exports, check:entry-nameability, check:exported-any, check:browser-reachable-entries, check:doc-formula-expressions, check:skill-examples) — NOT MEASURED, not failures; all seven re-ran GREEN after building the packages each named. TIER PROOF: `pnpm --filter @objectstack/spec check:generated` reports 15/15 artifacts current, with check:authorable-surface AND check:api-surface both green needing NO regeneration; only check:docs went stale, and only from the describe() edits — an accept-set or export move could not produce that result. TESTS: `pnpm --filter @objectstack/spec test` -> 'Test Files 465 passed (465)', 'Tests 12952 passed (12952)', wrapper 'VERDICT command-exit 0 · held the lock 593s'. `pnpm --filter @objectstack/spec typecheck` -> 'VERDICT command-exit 0', test layer compiles under tsconfig.test.json. Both via scripts/pm/os-verify-lock.sh (slot issue-15543-dev); verdicts read from its VERDICT command-exit line, never a bare $?. One acquisition returned 99 (queue-timeout, 540s, holder pid 26819) — spent as lock-free gate work and re-acquired on the same slot name, never re-queued from the tail. STEP 1 (dependency-closure build) is an empty run by construction: packages/spec has no workspace dependencies. LINT — declared narrowing with its three evidence pieces: (1) population read from eslint's own config, not guessed — of the 8 changed paths eslint supplies a matching configuration for exactly ONE (packages/spec/src/api/rest-server.zod.ts); the other seven are reported by eslint itself as 'File ignored because no matching configuration was supplied'; (2) counts from --format json: 8 entries, 1 in population, 0 errors, 0 in-population warnings, exit 0; (3) invariance — eslint.config.mjs states in its own words (line 328) that this repo 'never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file', measured there with a positive control, so no edit of mine can move an untouched file's verdict. All three hold, so this is a measurement, not a skipped run; the repo-wide pnpm lint scan stays CI's. BYTES: check:nul-bytes green plus an independent control-character scan of every changed file, zero hits. ABLATION: none — this change adds no guard and no test, so there is nothing whose failure could be demonstrated; the tier proof above is the substitute and it is a real reading, not a stand-in.",
      "gates": {
        "derived": 94,
        "run": 94,
        "green": 93,
        "not_measured": 1,
        "not_measured_detail": "pnpm check:dual-build-cjs-loads — exit 3, PREREQUISITE NOT MET (reads emitted bytes from all 83 packages' dist/; a repo-wide build is CI's run). Declared, never read as a pass.",
        "derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; merge-base change set), re-derived after the changeset existed, reconciled with --ran",
        "reconcile_second_pass": "24 further families surfaced by --ran (the docs family, pulled in by the regenerated content/docs/references/api/rest-server.mdx) — all 24 run, all green",
        "at_commit": "9ffbde5b1"
      },
      "line_budget": "n/a — the diff touches no `skills/**` path (the published catalog). Governed surfaces are all absent: docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md. No line ratchet applies.",
      "deviations": [
        "DECLARED SCOPE EXTENSION: api.enableSearch is outside the ruling's named crud/metadata/batch key set and I repaired it anyway, under the bounded in-place-fix exemption (all four conditions hold: same defect class, same file, mechanical, same gate family, no other claim on the file). Decisive reason: this PR puts the measured boot-path fact in the file header, after which a docblock three screens below still saying 'Deployment-wide switch' is an internal contradiction IN MY OWN EDIT, and the contrast would read as evidence that enableSearch IS deployment-reachable. Prose only — type, default(true) and runtime read unchanged. Named in the PR body so the seat can strip it.",
        "BEYOND THE LITERAL RULING: the ruling says 'docblocks'; I also appended an embedder-only clause to the crud/metadata/batch members' describe(). Reason measured, not assumed: build-docs.ts renders ONLY the module docblock and the describe() strings onto content/docs/references/api/rest-server.mdx — the per-schema docblocks reach no operator-facing page at all, so a docblock-only change would have satisfied the ruling's words and missed its stated intent.",
        "LEDGER SHAPE: no `reachability` field was invented. The gate validates no such field, so a new one would be declared-and-unenforced — the exact shape this ledger exists to catch. The rows are prose in each live prop's `note` plus each file's `_note`, per the ledger's own convention.",
        "verifiedAt NOT bumped on any ledger row, deliberately. Bumping it would silently reset the liveness staleness clock on a call graph nobody re-measured. Each row and each _note states this.",
        "The contributor-facing rule ('no docblock here may describe a key as a deployment posture without saying who can set it') is a line comment, NOT a docblock — found by reading the regenerated page, which had rendered it verbatim onto the public reference before the fix.",
        "check:dual-build-cjs-loads declared NOT MEASURED (above).",
        "Lint narrowed to the changed files with the three evidence pieces (above)."
      ],
      "files_changed": [
        "packages/spec/src/api/rest-server.zod.ts",
        "packages/spec/liveness/crud_endpoints.json",
        "packages/spec/liveness/metadata_endpoints.json",
        "packages/spec/liveness/batch_endpoints.json",
        "docs/qa/platform-checklist/FOLLOW-UPS.md",
        "docs/qa/platform-checklist/areas/api-backend.json",
        "content/docs/references/api/rest-server.mdx",
        ".changeset/rest-server-config-embedder-only-reachability.md"
      ],
      "mcp_calls": "0 — every GitHub read and write went through the container's REST channel (repo-scoped probe returned 200) plus git; no MCP GitHub call was made. No duplicate search was owed because no card was filed.",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: serve.ts:4019 reaches createRestApiPlugin through an `as any` cast, so even the two keys it passes are unchecked at that seam. Named by triage as incidental. Unreachable from this PR without moving packages/cli, which the ruling refuses. Successor: whoever takes option 1, if a request ever arrives.",
        "noted, not filed: RestApiConfigSchema's other nine live keys (version, basePath, enableCrud, enableMetadata, enableUi, enableBatch, enableDiscovery, enableOpenApi, plus documentation/responseFormat) are embedder-only on the same measurement, but none carries a false-posture sentence — their describe() strings are plain capability statements. The file-header paragraph covers them; I did not append a per-key line to nine honest docblocks. Successor: the same option-1 card.",
        "noted, not filed: `api` has no liveness ledger file, so its keys carry no reachability row and no liveness verdict at all. Seeding one is a ledger-governance decision (SPEC_ONLY_SCHEMAS in check-liveness.mts), not a rider here. Successor: none identified — recorded so the next reader of these three ledgers does not read `api`'s absence as a clean bill.",
        "noted, not filed: FOLLOW-UPS.md E3 records a separate refresh still owed — re-pointing two of the three items' `automated.ref` at packages/rest/src/rest-config-mount-table.pin.test.ts (#15544). Different sentence, different card; I did not touch it and it remains owed.",
        "labels: none applied. The claim declares `Clause-②: no`, so no needs:contract-review pairing; the dispatch named no other label and the PR ships a changeset, so skip-changeset does not apply. Nothing to write, so nothing to read back."
      ]
    }

    Generated by Claude Code

  6. zhuangjianguo commented on Sep 8, 2026

    @zhuangjianguo
    Collaborator

    Contract review — REWORK: Clause-② holds, but the published text names a function that does not exist

    domain:spec seat, session session_016N6xmWt5hYm94ffVEwGH8x, R1, 2026-09-08T05:16Z. Carrier PR #16775 at head 9ffbde5b1.

    Tier fuse — verified, ⛔ not self-reported: 95 harness-stamped "model" values, all claude-fable-5-1 = CONTRACT_REVIEW_TIER. The review was fed the card, the ruling and the PR only.

    Why an at-tier review happened on a Clause-②: no card

    The enqueue gate's PATH limb fires mechanically on any diff touching packages/spec/src/**, and this diff touches rest-server.zod.ts. The ruling had predicted 「路径肢不会触发,只动台账与生成文档」 — but 「diff 是事实,卡片语义是预测」, and the docblocks live in that file. Dispatch tier was TIER_DEFAULT, below contract-review, so an at-tier read was owed before enqueue. It earned its cost on the first item.

    Clause-② — independently verdicted TRUE

    Every hunk in rest-server.zod.ts is a comment block, a // line comment, or a .describe() string. No .optional(), .default(), .min/.max, refinement, retiredKey(), type or export moves; api-surface/api.json and authorable-surface.base.json untouched; the reviewer's own build-docs.ts --check reports the mdx as the only moved projection. ⇒ The accept set does not move; Clause-②: no is right and TIER_DEFAULT was the right tier. The path-limb hit is a false positive at this tier — but the gate was still correct to force the read.

    ⛔ D1 — BLOCKING. The PR publishes a function that does not exist

    rest-server.zod.ts:28-31, and from there onto content/docs/references/api/rest-server.mdx, the changeset, and all three liveness ledgers' _note:

    "Both doors are programmatic — createRestApiPlugin({ api }) … and createHonoServerPlugin({ restConfig })."

    createHonoServerPlugin is defined nowhere. Verified independently by this seat on origin/main: a definition probe (export function / export const) returns zero, against a positive control that resolves createRestApiPlugin at packages/rest/src/rest-api-plugin.ts:115. Its only two occurrences in the whole tree are the pre-existing QA-checklist prose this card inherited (FOLLOW-UPS.md:571, areas/api-backend.json:1983) — i.e. the sentence asserting it is the only evidence for it.

    ⚠️ The real shape: the class is HonoServerPlugin; its restConfig option has exactly one reader (hono-plugin.ts:595) which takes api.basePath for the SPA fallback and never constructs a REST server — new RestServer( has one non-test site, fed by the REST plugin's own config.

    ⇒ This is not a wording nit. A card whose entire subject is 「声明了一个够不到的姿态」 would have shipped a declared door that does not exist, onto the public reference page and into three permanent ledgers. The error was inherited, but the dispatch handed the premises over to be re-derived, and publishing is what makes it this PR's.

    ⛔ D2 / D3 — BLOCKING. Two more sentences the code does not support

    • :40-41 publishes 「no flag, env var or config file moves it」. False for metadata.maskObjectFields: normalizeConfig folds an env var in (rest-server.ts:4156 → object-schema-fls.ts:198-209, OS_ALLOW_UNMASKED_OBJECT_METADATA). Same carve-out owed in metadata_endpoints.json's _note and its maskObjectFields row.
    • :382-384 says 「under os serve and the dev plugin the mask is on and stays on」 — false when that env var is set, and the docblock's own next paragraph (:389-394) says so. The file contradicts itself.

    D4 (over-claim on "CLI-derived keys" / "never a file the CLI reads" — the two keys are copied from the stack config's declared api: block) and D6 (the three knownGaps were semantically re-characterised without the README-mandated revision bump + history entry) are non-blocking but ride along.

    ⚠️ D5 — and this one's cause is MINE

    FOLLOW-UPS.md:571 now records 「the card's and the ruling's citation … is wrong — that phrase never occurred in the file」. The phrase exists, verbatim: packages/rest/src/rest-server.ts:2071 — "The cap is deployment policy — RestServerConfig.batch.maxBatchSize" (verified by this seat; it also appears in two CHANGELOGs).

    My dispatch measured that "deployment policy" does not occur in packages/spec/src/api/rest-server.zod.ts — true, and correctly scoped. But I framed it to the dev as 「the ruling's citation is already measured FALSE」, and that framing invited the broader record now sitting in a checklist. ⇒ The ruling's citation was misattributed to the wrong file, not fabricated, and the correction is to say where the sentence lives and file it to a successor (domain:rest), ⛔ not to record that it never existed.

    ⭐ Same failure mode as my two earlier errors this round: a true, narrowly-scoped reading restated as a general claim. Three instances in one round, each surviving only because someone measured again.

    Accepted as in scope, ⛔ not stripped

    Both extensions the dev flagged are justified and stay: (i) api.enableSearch — same file, same defect class, and a header saying the rest of api is embedder-only three screens above a docblock saying "Deployment-wide switch" would be a contradiction this PR itself creates; (ii) the .describe() edits — the reviewer confirmed build-docs.ts renders only the module docblock and .describe(), so a docblock-only change would satisfy the ruling's words and miss its stated intent.

    Ledgers clean: 18 live rows, note only; status / verifiedAt untouched (correct — nothing about reads was re-measured); ⭐ no invented field — the dev refused to mint a reachability key the gate does not validate, on the grounds that it would be declared-and-unenforced, which is the exact shape this ledger exists to catch. Right call.

    Red line clean: content/docs/releases/** not touched, and v12.mdx is not falsified — its only touch-point is a requireAuth release record this diff does not mention. Nothing to file.

    Disposition

    Patch round 1 of 2 dispatched to the original dev on the same claim. ⛔ PR stays draft; ⛔ not ready, ⛔ not enqueued. ⚠️ The PASS on Clause-② binds to 9ffbde5b1 and will not follow the next push, but the fixes are prose-only and touch no measured claim's subject, so the follow-up is a wording re-read rather than a third full review.

    CI at review time: 31 success/skipped, Test Core (5/6) and Lint & Repo Gates still in_progress — ⛔ not a pass.


    Generated by Claude Code

  7. claude commented on Sep 8, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 15543,
      "status": "done",
      "round": "patch round 1 (plus the CI-red follow-up in the same round)",
      "branch": "claude/issue-15543-rest-server-config-embedder-only",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/16775",
      "head_sha": "b8cc67dab",
      "pr_state": "open, draft: true, auto_merge: null — not readied, not enqueued, not armed",
      "premise_still_valid": true,
      "fixes": {
        "D1_nonexistent_door": "FIXED in all seven published places + one the round surfaced. Independently re-verified before editing: a definition probe (`export function|const|class` createHonoServerPlugin) returns ZERO across the tree, against a positive control finding createRestApiPlugin at packages/rest/src/rest-api-plugin.ts#createRestApiPlugin. Real shape confirmed: HonoServerPlugin is a CLASS (hono-plugin.ts#HonoServerPlugin); its restConfig option has exactly one reader, taking `config.api?.basePath` for the SPA fallback; `new RestServer(` has ONE non-test site, packages/rest/src/rest-api-plugin.ts#createRestApiPlugin's start(), fed by the REST plugin's own config. Corrected in: the zod module docblock, the regenerated mdx, the changeset, the three ledger _notes, the FOLLOW-UPS E2 finding column, and the crud item's knownGaps. The three ledger _notes and the E2 row and the knownGaps carry the RETRACTION by name (naming the non-function so a future reader cannot re-introduce it), not a silent deletion.",
        "D2_env_var_carveout": "FIXED. Re-verified the mechanism myself: rest-server.ts#normalizeConfig writes `maskObjectFields: isObjectSchemaMaskingEnabled(metadata.maskObjectFields)`, and packages/metadata-core/src/object-schema-fls.ts#isObjectSchemaMaskingEnabled returns false when OS_ALLOW_UNMASKED_OBJECT_METADATA is any non-empty value other than `0`/`false`. 'no flag, env var or config file moves it' is now 'no flag, config file or CLI option moves it' plus an explicit ONE CARVE-OUT paragraph naming the env var as the only thing outside an embedder's argument that changes any value in the file. Carried into metadata_endpoints.json's _note and its maskObjectFields row, which no longer says 'always gets the schema default'.",
        "D3_self_contradiction": "FIXED. The docblock now separates the two facts it was conflating: the KEY is always true under os serve and the dev plugin (it is unreachable), and the MASK is on UNLESS OS_ALLOW_UNMASKED_OBJECT_METADATA is set. The contradiction with its own next paragraph is gone.",
        "D4_over_claim": "FIXED. Verified at packages/cli/src/commands/serve.ts#apiConfig: the two keys are read from `(config as any).api ?? {}` — the stack config's own top-level `api:` block. 'never a file the CLI reads' and 'two CLI-derived keys' are replaced by: os serve reads the stack config's top-level `api:` block and forwards exactly two keys out of it, through an `as any` cast, so the CLI DOES read a config file and just forwards those two keys.",
        "D5_record_correction": "FIXED, and the record now says WHERE rather than whether. Verified myself: 'The cap is deployment policy — `RestServerConfig.batch.maxBatchSize` (1..1000, default 200)' exists verbatim at packages/rest/src/rest-server.ts#enforceBatchSize. FOLLOW-UPS E2 no longer says the phrase 'never occurred in the file'; it now records that the phrase does not occur in packages/spec/src/api/rest-server.zod.ts (control: maxBatchSize occurs there 3x) but DOES exist in the REST server, that the ruling MISATTRIBUTED it to the wrong file rather than inventing it, and that the carrier is real, same defect class, and OWED TO A SUCCESSOR CARD in domain:rest (this PR does not touch packages/rest).",
        "D6_revision_history": "FIXED. api-backend.rest-crud-config-contract 1 -> 2, api-backend.rest-batch-config-contract 1 -> 2, api-backend.rest-metadata-config-contract 2 -> 3, each with a history entry dated 2026-09-08, ref #15543, stating the semantic change (workaround -> permanent method, so a runner is not waiting for a boot-path flag that is not coming) and both factual corrections. Acceptance clauses, steps, variants and oracles untouched.",
        "CI_red_followup": "FIXED. check:docs went red at 167ef867 because the D1-D3 edits changed the module docblock and describe() strings — the exact two inputs build-docs.ts renders — and I did not regenerate in the same push. Regenerated with gen:schema && gen:docs (⛔ never hand-edited), committed at 506e5c864. Verified the corrections reached the RENDERED page, not just the source: `createHonoServerPlugin` count on the mdx is 0, and the three stale claims ('no flag, env var or config file moves it', 'never a file the CLI reads', 'two CLI-derived keys') return zero hits there."
      },
      "new_finding_this_round": "check:platform-checklist went RED on my own patch prose — 6 x SOURCE LINE CITATION. The checklist tree forbids `file.ts:NNN` pins (README: 'Every call cites framework source as `file` plus the symbol it lands in'); my D1/D4/D5 corrections had cited rest-api-plugin.ts:115, serve.ts:3966-3968 and rest-server.ts:2071. All six converted to the `path#Symbol` form (#createRestApiPlugin, #apiConfig, #enforceBatchSize) and the gate now reports 'symbol anchors: 630/630 resolved against 307 cited sources'. Note the scope: this convention governs docs/qa/platform-checklist/** only, so the line citations in the liveness ledgers and the zod docblock are a different tree's convention and were left alone.",
      "summary": "Patch round 1 applied in full: D1 (a published door that does not exist), D2 and D3 (the env-var carve-out and the docblock contradicting itself), D4 (the CLI-reads-no-file over-claim), D5 (the record correction — the ruling misattributed the 'deployment policy' sentence to the wrong file, it did not invent it) and D6 (revision/history bumps). Every claim was re-measured before editing rather than carried over, which is the lesson the round named and which is also what caught the seventh problem: my own corrections tripped the checklist tree's source-line-citation ban, now fixed. The regenerated reference page is part of the fix, not a follow-up — that was the CI-red cause and it is closed with check:docs green on the final head. Diff unchanged in shape: still prose, ledger rows and one generated artifact; no schema shape, default, bound or refusal moved.",
      "tests": "All at final head b8cc67dab, clean tree. GATES: re-derived on the NEW head (94 families — the 24 doc-family gates now fall out of the derivation directly, since the mdx change is in the committed diff from the start; that set equals my round-1 union of 70 + 24). Reconciled: 'Run reconciliation — 94 derived, 94 run, 0 NOT-MEASURED, 0 UNRUN' and '94 derived famil(ies) accounted for'. 93 green. 1 declared NOT MEASURED: pnpm check:dual-build-cjs-loads exit 3, 'PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/' (needs all 83 packages built; a repo-wide build is CI's run). Five further gates first returned a prerequisite refusal in the recreated worktree (check:doc-formula-expressions, check:doc-security-posture, check:skill-examples, check:docs-transcript-drift, and check:platform-checklist's real finding above) — the four prerequisite ones re-ran GREEN after building formula/lint/client-react/client. THE PM'S SPECIFIC ASK: check:docs exit 0, and check:generated reports 'All 15 generated artifacts are up to date'. RATCHET FAMILY RE-RUN ON THE FINAL HEAD after the last commit moved it: check:generated, check:docs, check:liveness, check:api-surface, check:authorable-surface, check:nul-bytes, check:platform-checklist — all exit 0. TESTS re-run on the final head, not carried over from round 1: pnpm --filter @objectstack/spec test -> 'Test Files 465 passed (465)', 'Tests 12952 passed (12952)', VERDICT command-exit 0; pnpm --filter @objectstack/spec typecheck -> VERDICT command-exit 0. Both via scripts/pm/os-verify-lock.sh, slot issue-15543-dev; verdicts read from the VERDICT command-exit line, never a bare $?. TIER unchanged and re-proved on this head: check:api-surface and check:authorable-surface both green with no regeneration; only check:docs ever went stale, and only from the describe()/docblock edits.",
      "gates": {
        "derived": 94,
        "run": 94,
        "green": 93,
        "not_measured": 1,
        "not_measured_detail": "pnpm check:dual-build-cjs-loads — exit 3, PREREQUISITE NOT MET (repo-wide build; CI's run). Declared, never read as a pass.",
        "reconcile": "94 derived, 94 run, 0 NOT-MEASURED, 0 UNRUN",
        "at_commit": "b8cc67dab"
      },
      "line_budget": "n/a — no `skills/**` path touched; no governed surface touched (docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md all absent from the diff).",
      "deviations": [
        "⚠️ dispatch-gates printed a STALE TREE warning on this head: the branch is at least 10 commits behind origin/main and 6 files the derivation reads changed across that range. I did NOT merge main — the round said do not widen the diff beyond the corrections and the regeneration. So the 94 is this tree's derivation; CI on the merge ref is the authoritative set, and a family added on main since the branch point would not appear above. Declared rather than silently assumed away.",
        "check:dual-build-cjs-loads declared NOT MEASURED (repo-wide build).",
        "Source-line citations were converted to file#symbol ONLY inside docs/qa/platform-checklist/**, which is the tree whose README carries that ban and whose gate enforces it. The liveness ledgers' and the zod docblock's line references were left as they are — a different tree with a different convention, and changing them would widen the diff."
      ],
      "files_changed": [
        "packages/spec/src/api/rest-server.zod.ts",
        "packages/spec/liveness/crud_endpoints.json",
        "packages/spec/liveness/metadata_endpoints.json",
        "packages/spec/liveness/batch_endpoints.json",
        "docs/qa/platform-checklist/FOLLOW-UPS.md",
        "docs/qa/platform-checklist/areas/api-backend.json",
        "content/docs/references/api/rest-server.mdx",
        ".changeset/rest-server-config-embedder-only-reachability.md"
      ],
      "commits_this_round": [
        "167ef867b — D1-D6 corrections in source, ledgers, checklist and changeset",
        "506e5c864 — regenerate content/docs/references/api/rest-server.mdx (the CI-red fix)",
        "b8cc67dab — cite file plus symbol, never a source line number, in the checklist prose"
      ],
      "mcp_calls": "0 for this round as well — REST plus git throughout; cumulative 0.",
      "open_questions": [],
      "out_of_scope_findings": [
        "FOR PM / A SUCCESSOR CARD (domain:rest): packages/rest/src/rest-server.ts#enforceBatchSize's docblock says 'The cap is deployment policy — RestServerConfig.batch.maxBatchSize (1..1000, default 200)'. That is the ruling's second citation, real but in the wrong package, and it is the same defect class this card closes: a cap described as deployment policy that no CLI-started deployment can set. Not filed by me — this PR does not touch packages/rest and the ruling's scope is packages/spec — and recorded in FOLLOW-UPS E2 as owed. ⇒ Recommend PM file it, or hand it to the next domain:rest dispatch.",
        "noted, not filed: serve.ts reaches createRestApiPlugin through an `as any` cast, so even the two forwarded keys are unchecked at that seam. Successor: whoever takes option 1.",
        "noted, not filed: RestApiConfigSchema's other nine live keys are embedder-only on the same measurement but carry no false-posture sentence; the file-header paragraph covers them. Successor: the same option-1 card.",
        "noted, not filed: `api` has no liveness ledger file, so its keys carry no reachability row and no liveness verdict; seeding one is a ledger-governance decision (SPEC_ONLY_SCHEMAS in check-liveness.mts). Successor: none identified.",
        "noted, not filed: FOLLOW-UPS E3's separate owed refresh (two items' automated.ref -> rest-config-mount-table.pin.test.ts, #15544) is untouched and still owed.",
        "labels: none applied — Clause-②: no, no label named by the dispatch, and the PR ships a changeset."
      ]
    }

    Generated by Claude Code

  8. claude commented on Sep 8, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 15543,
      "status": "done",
      "round": "merge round (patch round 2) — sync with origin/main, everything re-measured on the merged head",
      "branch": "claude/issue-15543-rest-server-config-embedder-only",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/16775",
      "head_sha": "fcbbbebfe",
      "pr_state": "open, draft: true, auto_merge: null, mergeable_state: clean — not readied, not enqueued, not armed",
      "premise_still_valid": true,
      "merge": {
        "tool": "bash scripts/pm/os-regen-merge.sh — ALREADY the path this merge took, before the addendum arrived. Its log shows step 1 (git merge origin/main), step 2 (per-file side selection on the os-regen paths) and step 3 (commit the merge BEFORE any regeneration); step 4 was then executed by hand as the script intends, since the regen chain varies by surface.",
        "commit": "fcbbbebfe, a real merge commit with two parents (b8cc67dab + a749dcc4b). ⛔ No rebase, no amend, no force-push.",
        "distance_closed": "15 commits behind at the start of this round (the PM measured 12, then 20 — main moves continuously).",
        "generated_file_collision_the_script_exists_for": "Handled exactly as predicted, and the script chose per file: it printed 'KEEPING the branch's bytes of content/docs/references/api/rest-server.mdx — the branch changed it and main did not', and took main's side of packages/spec/liveness/action.json and the generated state-counts.md (#16784). ⛔ Nothing was hand-edited to reconcile.",
        "regeneration_needed_after_the_merge": "NONE, and this is a measurement, not an assumption: after building the spec on the merged tree, `check:generated` reports 'All 15 generated artifacts are up to date'. state-counts.md did not need regenerating because my three ledgers' diffs are note/_note prose only — no `status` moved — so the counts #16784 recomputed are still correct over the merged set.",
        "swallow_check_step_4_3": "Asserted by quoted-exact-name comparison against origin/main, not by reading the merge's exit code. Sibling #16784: action.json `operation` and `patch` are `live` on the merged tree and on origin/main alike. Sibling #16792: docs/audits/2026-07-unknown-key-strictness-ledger.md is byte-identical to origin/main (empty diff). My own implementation body survived: 'WHO CAN WRITE THIS CONFIG' 2x in the zod + 1x in the mdx, 'Reachability: EMBEDDER-ONLY' 3x, 'ONE CARVE-OUT' 1x + 1x, the embedder-only describe() clause 3x + 3x, and 21 REACHABILITY hits across the three ledgers (18 live rows + 3 _notes).",
        "drift_since_the_merge": "5 commits behind origin/main as this report is written — main moved during the build and gate run. NOT chased: the three files the PM named as the collision risk (action.json, state-counts.md, the strictness-ledger audit) are byte-identical to current origin/main, so no new collision exists, and re-merging would invalidate the gate run this report cites. mergeable_state reads `clean`, and the merge queue rebuilds on the current main at land time."
      },
      "summary": "Merged origin/main into the branch with scripts/pm/os-regen-merge.sh — which is what this merge already used — and re-derived and re-ran everything on the merged head rather than carrying round-1 results forward. The stale-tree deviation I declared last round is now closed with a measurement rather than an assumption: the derivation on the merged head is 94 families, byte-identical to the derivation on the stale head, so the merge added no families. No regeneration was owed after the merge, and no sibling entry was swallowed. The PR's own diff is unchanged in shape — still 8 files, +216/-51, zero governed-surface and zero content/docs/releases/ paths.",
      "tests": "All at merged head fcbbbebfe, clean tree. DERIVATION: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on the merged head — 94 families, and `diff` against the stale-head derivation is EMPTY, which is the direct answer to 'the merge may add families': it did not. The STALE TREE warning that accompanied the previous derivation is gone. GATES: 94 run, 93 green. Reconciled: 'Run reconciliation — 94 derived, 94 run, 0 NOT-MEASURED, 0 UNRUN' and '94 derived famil(ies) accounted for'. NOT MEASURED, unchanged and NOT a pass: pnpm check:dual-build-cjs-loads, exit 3, verbatim 'PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/' listing @objectstack/account, setup, studio, cloud-connection, connector-mcp, connector-openapi and more — it needs all 83 packages built, which is a repo-wide build and CI's run; my diff adds no export, entry or tsup change so it cannot move it. RATCHET FAMILY, all re-run on the merged head after the last commit: check:generated (exit 0, 'All 15 generated artifacts are up to date'), check:docs 0, check:liveness 0, check:api-surface 0, check:authorable-surface 0, check:nul-bytes 0, check:platform-checklist 0. TESTS re-run on the merged head: pnpm --filter @objectstack/spec test -> 'Test Files 465 passed (465)', 'Tests 12955 passed (12955)' — three more than round 1's 12952, which is the sibling work the merge brought in — VERDICT command-exit 0. pnpm --filter @objectstack/spec typecheck -> VERDICT command-exit 0. Both via scripts/pm/os-verify-lock.sh, slot issue-15543-dev; verdicts read from the VERDICT command-exit line, never a bare $?. TIER re-proved on the merged head: check:api-surface and check:authorable-surface green with no regeneration required.",
      "gates": {
        "derived": 94,
        "run": 94,
        "green": 93,
        "not_measured": 1,
        "not_measured_detail": "pnpm check:dual-build-cjs-loads — exit 3, 'PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/'. Repo-wide build; CI's run. ⛔ Never a pass.",
        "reconcile": "94 derived, 94 run, 0 NOT-MEASURED, 0 UNRUN",
        "derivation_delta_vs_stale_head": "empty — the merge added no families",
        "at_commit": "fcbbbebfe"
      },
      "line_budget": "n/a — no `skills/**` path; no governed surface (docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md) and no content/docs/releases/** path in the diff: verified as 0 files by path filter on the diff against the current merge-base.",
      "deviations": [
        "5 commits behind origin/main at report time (main moved during this round's build and gate run). Not re-merged: the three files named as the collision risk are byte-identical to current origin/main, mergeable_state is clean, and a second merge would invalidate the run this report cites. Declared rather than silently carried.",
        "check:dual-build-cjs-loads remains NOT MEASURED with its exit-3 prerequisite text, as instructed.",
        "The previous round's stale-tree deviation is now RETIRED, by measurement: the merged-head derivation is identical to the stale-head one."
      ],
      "files_changed": [
        "packages/spec/src/api/rest-server.zod.ts",
        "packages/spec/liveness/crud_endpoints.json",
        "packages/spec/liveness/metadata_endpoints.json",
        "packages/spec/liveness/batch_endpoints.json",
        "docs/qa/platform-checklist/FOLLOW-UPS.md",
        "docs/qa/platform-checklist/areas/api-backend.json",
        "content/docs/references/api/rest-server.mdx",
        ".changeset/rest-server-config-embedder-only-reachability.md"
      ],
      "commits_this_round": [
        "fcbbbebfe — Merge remote-tracking branch 'origin/main' (via scripts/pm/os-regen-merge.sh; no regeneration commit was owed, check:generated proves it)"
      ],
      "mcp_calls": "0 this round; cumulative 0 — REST plus git throughout.",
      "open_questions": [],
      "out_of_scope_findings": [
        "ACKNOWLEDGED, and it is yours: the packages/rest finding (rest-server.ts#enforceBatchSize's 'deployment policy' docblock) is filed by you, not me, and I have touched no packages/rest file in this PR. Correction accepted on my framing: I wrote `domain:rest`, and this repo's table has no such lane — packages/rest belongs to `domain:cli`. FOLLOW-UPS E2's prose records the measurement and that it is owed to a successor, and it does NOT name a domain label, so nothing there needs changing for this correction.",
        "noted, not filed: serve.ts reaches createRestApiPlugin through an `as any` cast, so even the two forwarded keys are unchecked at that seam. Successor: whoever takes option 1.",
        "noted, not filed: RestApiConfigSchema's other nine live keys are embedder-only on the same measurement but carry no false-posture sentence; the file-header paragraph covers them. Successor: the same option-1 card.",
        "noted, not filed: `api` has no liveness ledger file, so its keys carry no reachability row and no liveness verdict; seeding one is a ledger-governance decision (SPEC_ONLY_SCHEMAS in check-liveness.mts). Successor: none identified.",
        "noted, not filed: FOLLOW-UPS E3's separate owed refresh (two items' automated.ref -> rest-config-mount-table.pin.test.ts, #15544) is untouched and still owed.",
        "labels: none applied — Clause-②: no, no label named by the dispatch, and the PR ships a changeset."
      ]
    }

    Generated by Claude Code

  9. removed their assignment
    on Sep 8, 2026
  10. zhuangjianguo commented on Sep 8, 2026

    @zhuangjianguo
    Collaborator

    Closed by PR #16775 — pm:dispatched and the assignee released in one write

    domain:spec execution seat, session session_016N6xmWt5hYm94ffVEwGH8x, 2026-09-08T07:24Z (clock read).

    PR #16775 merged at 07:19:54Z and closed this card via Fixes (state_reason: completed).

    One label write, read back: removed pm:dispatched, cleared the assignee; kept priority:p2, domain:spec, finding.

    Option 2 executed (the 2026-09-07 ruling, director seat summon #17, batch #2, maintainer verbatim 「同意」): the crud / metadata / batch keys stay and keep their runtime reads, and the schema now says plainly who can write them — Reachability: EMBEDDER-ONLY in the docblocks plus a per-key REACHABILITY row in packages/spec/liveness/{crud,metadata,batch}_endpoints.json. A runner no longer re-derives it from the boot paths every run.

    Corrections that rode along, each measured rather than inherited:

    • ⛔ A door that does not exist. Seven published places named createHonoServerPlugin({ restConfig }). A definition probe returns zero across the tree, against a positive control finding createRestApiPlugin at packages/rest/src/rest-api-plugin.ts#createRestApiPlugin. HonoServerPlugin is a class whose restConfig option has one reader taking api.basePath for the SPA fallback; it never constructs a REST server. ⭐ Every site carries the retraction by name rather than a silent deletion — a deletion leaves nothing to stop the next reader reintroducing it.
    • The env-var carve-out: OS_ALLOW_UNMASKED_OBJECT_METADATA is the one thing outside an embedder's argument that moves a value in this file, and the docblock now says so instead of claiming nothing does.
    • An over-claim withdrawn: os serve does read a config file — the stack config's own top-level api: block — and forwards exactly two keys out of it through an as any cast.
    • ⚠️ A correction to the record itself: the card and its ruling cite batch.maxBatchSize's docblock calling the cap "deployment policy" and attribute it to packages/spec/src/api/rest-server.zod.ts. The phrase does not occur in that file (control: maxBatchSize occurs there 3×, so the zero is real). It exists verbatim in packages/rest/src/rest-server.ts#enforceBatchSize. ⇒ The ruling misattributed the sentence; it did not invent it. That carrier is real, is the same defect class, and is filed as [finding] rest-server.ts#enforceBatchSize calls the batch cap "deployment policy", but no shipped boot path can set it — the same defect #15543 just closed in packages/spec, one package over #16801 — ⛔ correctly refused in-scope here, since this PR touches no packages/rest file. Routing corrected there too: packages/rest is domain:cli; there is no domain:rest lane.

    The merge that landed it went through scripts/pm/os-regen-merge.sh (a real merge commit, two parents, ⛔ no rebase or force-push), and the swallow check was made against origin/main rather than an exit code: #16784's action.json (operation / patch both live) and #16792's strictness ledger are both byte-identical to main. Nothing was dropped. The previous round's stale-tree deviation closed by measurement — the merged-head derivation is 94 families and diffs empty against the stale-head one.

    https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x


    Generated by Claude Code

  11. claude commented on Sep 8, 2026

    @claude
    ContributorAuthor

    Record correction, closing half — the misattributed carrier is now fixed

    domain:cli execution seat, out of successor card #16801. ⛔ Not a re-post: comment 5581004629 already corrected the record on 2026-09-08T07:24Z, before this dispatch existed. This adds only the half that comment could not carry — the landing — plus an independent re-take of its measurement, because a citation nobody re-measured is how the original misattribution travelled.

    The measurement, re-taken on this seat's own tree (base 70f7d6d735)

    Not relayed from #16801 or from comment 5581004629 — grepped here:

    probe tree reading
    deployment policy packages/spec/ 2 hits, both in CHANGELOG.md
    deployment policy packages/spec/src/ 0
    deployment policy packages/spec/src/api/rest-server.zod.ts (the file this card and its ruling named) 0
    maxBatchSize packages/spec/src/ hits in 11 files — api/batch.zod.ts 6, api/rest-server.zod.ts 4, api/contract.zod.ts 1, …
    maxBatchSize packages/spec/src/api/rest-server.zod.ts 4

    The control fires in the same tree and in the same file, so each zero is a reading rather than a probe that never reached. ⇒ The ruling misattributed the sentence to packages/spec/src/api/rest-server.zod.ts; it did not invent it. Confirmed, independently.

    Where the sentence actually lived, and where it went

    Verbatim carrier, located by symbol (its line number has moved three times across the readings on this card and #16801, so a line citation is not usable):

    packages/rest/src/rest-server.ts#enforceBatchSize — "The cap is deployment policy — RestServerConfig.batch.maxBatchSize (1..1000, default 200)"

    That carrier is fixed on branch claude/issue-16801-batch-cap-embedder-only-prose, opening as a draft PR against main now. Route 1 only — the prose is narrowed onto the fact this card already landed, using this card's own vocabulary rather than a second phrasing: Reachability: EMBEDDER-ONLY, matching the BatchEndpointsConfigSchema docblock and the WHO CAN WRITE THIS CONFIG header in packages/spec/src/api/rest-server.zod.ts, plus the per-key REACHABILITY row in packages/spec/liveness/batch_endpoints.json. ⛔ No batch config is threaded through any boot path: that would be a new authorable key, which the 2026-09-07 ruling declined for the spec-side siblings, and reversing it is its own decision.

    ⇒ Anyone arriving at this card looking for the "deployment policy" sentence should read packages/rest/src/rest-server.ts#enforceBatchSize, not rest-server.zod.ts, and will now find it already corrected there.

    One more carrier, still open

    The same claim has a third carrier that neither this card nor #16801 names: content/docs/api/data-api.mdx (hand-written) tells an operator the cap is "the deployment's batch.maxBatchSize (default 200, configurable 1–1000)". Filed separately rather than folded in — it is outside #16801's fenced file surface, and it is invisible to the docs-drift tool, which anchors only on the symbol RestServer and never on this claim's inputs.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions