Skip to content

feat(spec): retire the ten inert RestServerConfig keys the liveness ledger recorded as dead (ADR-0049 enforce-or-remove) - #14868

Draft
os-sam wants to merge 9 commits into
mainfrom
claude/issue-14691-rest-server-config-dead-keys
Draft

feat(spec): retire the ten inert RestServerConfig keys the liveness ledger recorded as dead (ADR-0049 enforce-or-remove)#14868
os-sam wants to merge 9 commits into
mainfrom
claude/issue-14691-rest-server-config-dead-keys

Conversation

@os-sam

@os-sam os-sam commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Fixes #14691

Retires, under ADR-0049 enforce-or-remove, the 15 RestServerConfig rows the liveness ledger recorded as dead on #14369's landing (PR #14638). Every family resolved to REMOVE; the PM's two ENFORCE candidates (routes.overrides, crud.patterns) were measured and falsified, so no split card was filed. Clause ②: this narrows the accept set of published authorable keys, so the PR opens as draft with needs:contract-review for the seat review.

Route (H1 — measured)

All four sub-schemas are non-strict z.object() (source; and #11984's own §C pin "STRIPS an unknown key … the schemas are non-strict" was green on the base). Route: retiredKey() tombstones, ledger rows kept dead with the REMOVED note template, two refusal channels (tsc never + parse prescription). One value def became an orphan (CrudEndpointPatternSchema, sole consumer crud.patterns) and left with its key — the only whole-def removal, declared in RETIRED_DEFS_BY_MAJOR[18]. CrudOperation stays: GeneratedEndpointSchema.operation still reads it (the first attempt to remove it was caught by that read, so it was restored).

Per-family verdicts (H2 — measured, all REMOVE)

Family (rows) Verdict Why REMOVE, not ENFORCE Ledger action
crud.patterns (4: method/path/summary/description) REMOVE 0 read sites; every CRUD route is mounted from fixed method/path pairs in registerCrudEndpoints. The mounted paths ARE the contract the client SDK (packages/client/src/index.ts hard-codes /data/${object}…), the discovery document (routes.data) and the served /openapi.json (pre-generated contract, enriched) all describe — a per-operation method/path knob could only make them lie (Route & surface ownership §4). The live door for a custom path/method/summary is a declarative api endpoint (type: 'object_operation', matched/executed/documented — the api ledger). tombstone on patterns; the 4 child rows collapse into one leaf row (children left the walked shape → would report ORPHAN); CrudEndpointPattern def removed
crud.objectParamStyle REMOVE 0 read sites; object name is always a path segment, 'query' mounted what 'path' mounts tombstone, row kept
metadata.cacheTtl REMOVE 0 read sites; enableCache selects the protocol's getMetaItemCached path, which takes no TTL; no cache header built from it. Negative-bound defect dies with it tombstone, row kept
metadata.endpoints.schema REMOVE gates GET /meta/:type/:name/schema, which does not exist (no /schema mount in packages/rest/src) nested tombstone inside the live endpoints block, row kept
batch.defaultAtomic REMOVE no handler consults it. Atomicity is the per-request options.atomic (BatchOptionsSchema, ADR-0119 D4: opt-in, default false, deliberately aligned to what callers get). A server default that flips it silently changes failure semantics for callers who send nothing — the move ADR-0119 D4 refused. Enforce would re-open a settled decision tombstone, row kept
batch.operations.upsertMany REMOVE no upsertMany route, no protocol member; upsert exists as an operation TYPE of the generic POST /data/:object/batch (BatchOperationType 'upsert'), gated by enableBatchEndpoint nested tombstone, row kept
routes.includeObjects + excludeObjects REMOVE 0 read sites. The capability exists at its proper seat: per-object exposure is declared ON the object and enforced by rest-server.ts#enforceApiAccess (enable.apiEnabled: false → 404, enable.apiMethods whitelist → 405). A server-config selector is a second, unread dialect of an enforced contract (PD #12) tombstones, rows kept; the @example advertising routes: { excludeObjects: [...] } corrected
routes.nameTransform REMOVE enum validated (since #11984) and ignored. The object name is the canonical id on every surface incl. the REST path segment (PD #6) — a URL transform contradicts the one-name rule tombstone, row kept
routes.overrides (3: enabled/basePath/operations) REMOVE (PM hypothesis ENFORCE-candidate falsified) enabled/operations duplicate enable.apiEnabled/enable.apiMethods (enforced, per object, on the object); basePath per object contradicts the one deployment-wide data base crud.dataPrefix the discovery document advertises. Nothing to build — the feature exists where the contract belongs tombstone on overrides; 3 child rows collapse into one leaf row

Four-axis reading for the two falsified candidates: real business need — zero authors in-repo, objectui @d4c6a86, cloud @9b6abe0f2fd5 (structural: cloud never authors a RestServerConfig); long-term soundness — both would fork a contract that already has one enforced home (object exposure keys; declarative endpoints), and per-deployment CRUD path shapes break every SDK/discovery/OpenAPI consumer; AI-error-proofing — one declared seat per capability beats a server-side dialect an author can pick wrongly; startup scope — zero pull, no expansion.

Coupling: #14365 (z.partialRecord for crud.patterns / overrides.*.operations) closes with the removal — there is no record left to reshape; its measurement is cited on the retired-key entries. Not closed here (the PM does). #14640 (api.*) untouched. The #11984 pins are REVERSED into refusal pins (§E of rest-sub-config-parse-not-cast.test.ts), never deleted; the live-key regression guards are unchanged.

Ledger discipline

  • 15 rows rewritten: status: dead kept, verifiedAt: 2026-09-03, evidenceScope: cross-repo (the vocabulary is in-repo | cross-repo; the dispatch's literal in-repo + cloud@… would fail the gate, so the cloud/objectui pins are in the note), house REMOVED note template + what to do instead + the pre-retirement census note preserved.
  • Two container rows (patterns, overrides) collapse to leaf rows: a retiredKey() is a leaf, so child rows would report ORPHAN / "children on a non-container".
  • README per-type rows extended; state-counts.md regenerated (crud_endpoints dead 5→2, route_generation 6→4, total dead 89→84).
  • check:liveness ✓ (all 35 types), check:empty-state ✓ after removing the rest-server.zod.ts includeObjects scope entry (its "(empty = all)" statement left with the key — the gate would otherwise report STALE).

ADR-0087 (H6)

No D2 conversion: a RestServerConfig is plugin TS configuration (REST plugin constructor / plugin-hono-server restConfig), never a stack collection member or a sys_metadata row — the openApi31 (#4579) and MetadataPluginConfig.additionalTypes precedents — so there is no source for the chain to rewrite and the prescriptions carry no os migrate meta sentence (the sentence pin only judges sentences that are present). Registered under major 18 (launch-window convention): 10 entries/retired-keys/18.api__*.ts, 1 entries/retired-defs/18.api__CrudEndpointPattern.ts, 1 entries/semantic/18.rest-server-config-dead-keys-retired.ts; gen:migration-registry regenerated registry.ts. Gate (b) accepted the 8 surface-level keys as [RETIRED] lines; the two nested keys (endpoints.schema, operations.upsertMany) have no surface line of their own (the kernel/Manifest:contributes.routes shape) and are registered the same way.

Ratchets (H4 — expectation decided from the route BEFORE reading)

Instrument Expected Observed
authorable-surface/api.json tombstones → 8 lines flip to [RETIRED]; whole-def removal → 4 CrudEndpointPattern:* lines disappear (gate (a) tripwire, deleted deliberately) exactly that; gate (a) fired once on the 4 lines and accepted the deletion under check (c) proof 1
json-schema.manifest/api.json whole-def removal → manifest deletion gate fires, requires RETIRED_DEFS_BY_MAJOR fired on api/CrudEndpointPattern.json; line deleted deliberately; accepted "RETIRED_DEFS_BY_MAJOR, major 18"
api-surface/api.json −2 (CrudEndpointPattern (type), CrudEndpointPatternSchema (const)); tombstones invisible −2 exactly
export-origins/, declaration-map/ −2 / −2 as expected (regenerated by check:generated --fix)
authorable-defaults/api.json retired defaults leave regenerated; "1232 default(s) unchanged" vs upstream (the retired ones are no longer defaults)
content/docs/references/api/rest-server.mdx, references/index.mdx CrudEndpointPattern section gone, tombstone [REMOVED] descriptions regenerated (1590→1589 schemas)
docs/audits/…strictness-ledger.counts.md, liveness/state-counts.md counts move regenerated
spec-changes.json, docs/protocol-upgrade-guide.md check:spec-changes / check:upgrade-guide ✓ up to date (the D3 semantic entry does not feed either)
test-typecheck-debt.json one ledgered TS18048 on the deleted "custom patterns" test VANISHES (ratchet tightens) re-recorded with gen:test-typecheck-debt (SHRANK, not maintainer-only)
type-alias-convention.pin.test.ts −1 pin (Iso188), count 832→831 with history note done
content/docs/permissions/system-context.mdx line anchors into rest-server.ts rot by −2 (type block shrank) check-system-context-census --fix re-anchored 5 anchors; recheck ✓

normalizeConfig (H3)

NormalizedRestServerConfig loses crud.patterns/objectParamStyle, metadata.cacheTtl/endpoints.schema, batch.defaultAtomic/operations.upsertMany; routes becomes an empty record type (a Record of string to never) (still parsed, so an authored key is refused with its prescription; nothing threaded). No live key's threading changed — the 17 live rows are the regression set and their pins (§C/§D) are unchanged and green. main was the base at f3ae441fa; #14801/#14677 had not landed, so no merge was needed — hunks are confined to the type block, the threading, one docblock line and one import.

Changeset

.changeset/rest-server-config-dead-keys-retired.md@objectstack/spec: minor, @objectstack/rest: minor (the launch-window convention check-changeset-no-major enforces: breaking ships as minor; the BREAKING banner + the ADR-0087 marker are the carriers), the ADR-0087 disposition marker adr-0087: registered rest-server-config-dead-keys-retired (as an HTML comment in the changeset body), FROM → TO per key with the one-line fix, the retirement kit paragraph.

Verification record

CI is GREEN on the current head ba875f3 — all six required contexts pass (Lint & Repo Gates, TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL)); 33 success / 5 skipped / 0 failure.

main has been merged three times since the first push. The one red this PR ever carried was Test Core (1/6)packages/clirun-dev-unbuilt-workspace.e2e.test.ts, the never-read-reader hang — not this PR's: it was red on main at 2263ca4, the base this branch merged, and its cause is #14832 (os dev parks in a blocking write(2) on fd 2; ~27/30 on a cold-tsx-cache runner, which is every CI runner). It cleared here by merging main once 392f410 (#14871, the maintainer's ruling-A quarantine of that single case) landed; the product fix is #14875, still in flight in its own lane. Nothing was skipped, quarantined or re-run to get this PR green — the standing-down comment on the failure is 5521890188.

The per-check readings below are from the tree at head 3c53eda (the last commit changes only the ten prescription strings, the regenerated rest-server.mdx, the census anchors and the test-typecheck ledger; every gate and suite named was re-run after it, on that tree). Every command went through scripts/pm/os-verify-lock.sh; verdict lines quoted from the gates' own output.

Check Reading
pnpm --filter @objectstack/spec build VERDICT command-exit 0 — gen:schema: "authorable-surface/ updated (8055 keys)", manifest deletion gate: "json-schema/api/CrudEndpointPattern.json — RETIRED_DEFS_BY_MAJOR, major 18"
pnpm --filter @objectstack/spec check:generated (after --fix) exit 0 — 15/15 artifacts current (check:spec-changes / check:upgrade-guide / check:authorable-surface / check:api-surface / check:docs / check:liveness / check:strictness-ledger / check:test-typecheck … all ✓)
pnpm --filter @objectstack/spec check:liveness "✓ every governed-type property … is classified … no ledger row outlives its property"; crud_endpoints 8 (live 6, dead 2), metadata_endpoints 8 (6/2), batch_endpoints 7 (5/2), route_generation 4 (dead 4)
pnpm --filter @objectstack/spec check:empty-state "✓ all classified (2 closed, 2 open, 4 output, 8 scope)"
pnpm --filter @objectstack/spec typecheck tsc --noEmit ✓, check:scripts-typecheck ✓, check:test-typecheck "OK — 54 file(s) / 261 error(s) / 145 pinned signature(s)" after the SHRANK re-record (one TS18048 on the deleted "custom patterns" test VANISHED)
pnpm --filter @objectstack/spec test (full) "Test Files 457 passed | 1 skipped (458) · Tests 12278 passed | 1 skipped" — VERDICT command-exit 0
pnpm --filter @objectstack/rest typecheck VERDICT command-exit 0 (tsc --noEmit && check:test-typecheck)
pnpm --filter @objectstack/rest test (full) "Test Files 173 passed (173) · Tests 2883 passed (2883)" — VERDICT command-exit 0
Targeted re-runs on the final tree spec rest-server.test.ts + retired-key-migrate-sentence.test.ts + migrations.test.ts: 178 passed; rest rest-sub-config-parse-not-cast.test.ts: 25 passed (§E refusal pins green; the reversed §A/§C/§D cases removed by design)
node scripts/pm/dispatch-gates.mjs --commands (no paths; derived from the merge-base at 04f4c1f) 77 commands; 68 exit 0 on first pass. The 9 non-zero: check-system-context-census (REAL — line rot in content/docs/permissions/system-context.mdx from the shrunk type block; --fix re-anchored 5 anchors, recheck ✓); check:doc-authoring (REAL — #14691 inside the ten prescriptions; removed, recheck ✓); check:api-surface / check:dual-source-exports / check:entry-nameability / check:exported-any / check:generated (dist-freshness prerequisite after test-file edits; rebuilt and re-run, all exit 0); check:skill-examples (needed client-react dist; built, re-run, exit 0); check-dev-prereqs, check:dual-build-cjs-loads, check:type-check-debt (PREREQUISITE NOT MET — 41 unbuilt package dists in this worktree, no full pnpm build; NOT MEASURED locally, CI reads them); check-test-completeness (exit 3, needs a turbo run test log — NOT MEASURED locally by design)
Ratchet direction api-surface/api.json −2, authorable-surface/api.json 8 flips to [RETIRED] + 4 lines gone, json-schema.manifest/api.json −1, state-counts.md dead 89→84 — all as expected from the route (table above)

Not run: pnpm lint repo-wide (CI-owned run). Not applicable: forms / i18n (no *.form.ts authors a RestServerConfig), check:variant-docs (no union removed; it ran green in the gate family anyway).

Out of scope / not touched

🤖 Generated with Claude Code

https://claude.ai/code/session_01J5bnauQVfR8NbAm4QagXzj

Generated by Claude Code


Generated by Claude Code


Generated by Claude Code

Tombstone crud.patterns/objectParamStyle, metadata.cacheTtl/endpoints.schema,
batch.operations.upsertMany/defaultAtomic and all of routes.* under ADR-0049
enforce-or-remove (#14369 census); drop the orphaned CrudEndpointPatternSchema;
keep the ledger rows dead with REMOVED notes; reverse the #11984 pins.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5bnauQVfR8NbAm4QagXzj
…e artifacts

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5bnauQVfR8NbAm4QagXzj
…or census docs; re-record test-typecheck debt

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5bnauQVfR8NbAm4QagXzj
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation tests tooling labels Sep 3, 2026
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/rest, @objectstack/spec, touching 21 documentable anchor(s). ⚠️ 21 changed file(s) yielded no anchor (packages/spec/api-surface/api.json, packages/spec/authorable-defaults/api.json, packages/spec/authorable-surface/api.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

32 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json a6d278ba263fbf12dd859dc54820d335c62f52f9.

4 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 21 changed file(s) yielded no anchor (packages/spec/api-surface/api.json, packages/spec/authorable-defaults/api.json, packages/spec/authorable-surface/api.json, …) — pages documenting those are invisible to this run
  • 1 anchor(s) matched too much of the corpus to be a work list: /data/{object} (route, 65 pages)
  • 8 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 130 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json a6d278ba263fbf12dd859dc54820d335c62f52f9packageMentionDocs.

Which tree this was computed on

This run read content/docs from cbf712266dff7477403bf68c67762acf6b68e97c — the merge of head ba875f3fbd4dd25c22de9bbc89af44e4a54c16c9 into base a6d278ba263fbf12dd859dc54820d335c62f52f9, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin cbf712266dff7477403bf68c67762acf6b68e97c && git checkout cbf712266dff7477403bf68c67762acf6b68e97c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a6d278ba263fbf12dd859dc54820d335c62f52f9 ba875f3fbd4dd25c22de9bbc89af44e4a54c16c9 && git checkout -B drift-repro a6d278ba263fbf12dd859dc54820d335c62f52f9 && git merge --no-ff ba875f3fbd4dd25c22de9bbc89af44e4a54c16c9

node scripts/docs-audit/affected-docs.mjs --json a6d278ba263fbf12dd859dc54820d335c62f52f9

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs a6d278ba263fbf12dd859dc54820d335c62f52f9 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

os-sam commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

CI: Test Core (1/6) red on head 2b86dec — not this PR's failure.


Generated by Claude Code

os-sam commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

CI on 2b86dec27 (seat reading, 07:05Z): Test Core (1/6) (job 100548872674, run 33723979376) is the one red of 34 checks — and it is not this PR's failure.

FAIL  integration  test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 180109 ms; case 1 measured the same child at 7240 ms on this runner minutes earlier: expected 'SIGKILL' to be null
Test Files  1 failed | 235 passed (236)   ·   Tests  1 failed | 2734 passed (2735)

That is #14832 — the os dev hang in an unbuilt workspace, a packages/cli product defect (pm:dispatched to the domain:cli seat; no fix merged yet, so nothing to port). This PR's diff is packages/spec + packages/rest + generated docs and does not touch packages/cli; every spec and rest shard passed. The one permitted re-run was already in flight when this note was written: the dev session requested it at 07:03:28Z (run 33723979376 attempt 2, its own standing-down note 5521890188 above, which also reads the same assertion red on main at 2263ca4); the seat's own rerun_failed_jobs at 07:05Z returned 403 "already running" — so attempt 2 is the single re-run, not a second one. Green ⇒ the landing path continues after the contract review (in progress: isolated reviewer at CONTRACT_REVIEW_TIER). Red again on the same line ⇒ no second re-run: card #14691 goes pm:blocked on #14832 with Unlock-action: re-check PR #14868.

Dev session: no action needed on this branch for the CI red; the review verdict lands on the card.


Generated by Claude Code

os-sam commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

Standing down until #14832 closes — the single permitted re-run is spent; nothing on this branch needs to change.

Attempt 2 of run 33723979376 (Test Core (1/6), job 100555546175, 07:28:22Z) failed on the same line as attempt 1: run-dev-unbuilt-workspace.e2e.test.ts:418, the child SIGKILLed at the 180000 ms ceiling (180105 ms vs a 7896 ms calibration on the same runner; attempt 1: 180109 ms vs 7240 ms). Same shard totals both times (1 failed / 2734 passed). That is the #14832 hang in packages/cli, which this PR does not touch; no fix exists to port yet.

State: contract review PASS (5522043706 on #14691), needs:contract-review cleared on both carriers, trial merge clean vs origin/main 2263ca4d6, NOT governed. A red required check keeps the PR out of the merge queue, so card #14691 is now pm:blocked on #14832 with Unlock-action: re-check PR #14868; when #14832 closes this PR gets a trial merge, a checks read, and the landing path (ready + auto-merge, squash). The PR stays draft until then.


Generated by Claude Code

@os-sam
os-sam marked this pull request as ready for review September 3, 2026 09:49
@os-sam
os-sam enabled auto-merge September 3, 2026 09:49
@os-sam
os-sam added this pull request to the merge queue Sep 3, 2026
Any commits made after this event will not be merged.
@os-sam
os-sam marked this pull request as draft September 3, 2026 09:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants