Skip to content

refactor(rest): move registerFormEndpoints into form-routes.ts (#23050 slice 11) - #24054

Draft
objectstack-fleet[bot] wants to merge 9 commits into
mainfrom
claude/issue-23050-s11-form-endpoints
Draft

objectstack-fleet[bot] wants to merge 9 commits into
mainfrom
claude/issue-23050-s11-form-endpoints

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #23050
Clause-②: no

Slice 11 of the rest-server.ts split, by slice 1's pattern (landed note 6136102437, steps 1–6), slice 2's getter refinement (6189011700) and slices 3–10's shape (slice 10: PR #23990). The body of RestServer#registerFormEndpoints — the public (anonymous) form doors, GET {basePath}/forms/:slug and POST {basePath}/forms/:slug/submit — moves to ONE new module, packages/rest/src/form-routes.ts. The route table, the ordered registrations, the published dist/*.d.ts and both bundles' export lists are byte-identical. Every handler's source is identical except the resolve door's, which differs by the two ruled receiver tokens alone (below).

rest-server.ts goes from 9,915 to 9,493 lines (base fd0e72abc). #23050 stays open for the later slices (CRUD, /meta).

⚠ One reader is NOT re-pointed, by the dispatch's own rule, and it is red: packages/spec/liveness/view.json cites the moved code in three evidence strings, and pnpm --filter @objectstack/spec run check:liveness exits 1 on this PR because two of them anchor rest-server.ts#findPublicFormView, a closure that now lives in form-routes.ts. packages/spec/** is not this lane's to edit, so the slice stops at it. The exact re-point and the routing question are in the section "The packages/spec reader stopped at" below. This PR cannot go green until that reader is routed.

The pattern, applied

Members the moved body reads, from an AST walk of the method (TypeScript compiler API with a type checker, symbol-resolved, base fd0e72abc): registerFormEndpoints (JSDoc :9048, method :9083–:9528, body :9084–:9527, 444 lines).

kind member reads class declaration
method resolveProtocol 3 (:9149, :9230, :9489) :2371, private async
method resolveAnonymousFormTenancy 1 (:9151) :8973, private async
method readFormObjectDefinitions 1 (:9170) :8997, private async
field routeManager 2, the two register calls (:9177, :9382) :1993, private
method resolveI18nService 1 (:9269) :2390, private async
method buildTranslationBundle 1 (:9270) :3881, private
method extractLocale 1 (:9271) :3932, private
static translateOptionsFor 2, as RestServer.translateOptionsFor(…) (:9281, :9312) :3978, private static
method packagedObjectBase 2 (:9282, :9313) :3909, private

The body has no bare this, no element access on this, no this alias and no function keyword. The seat's text count (the same eight instance members with the same counts, plus the two static accesses) is confirmed; this.resolveI… is resolveI18nService.

Control walk (the same walker, same base): anonymousFormIntakeWarnings (:9017) reads resolveAnonymousFormTenancy and readFormObjectDefinitions once each, and the registerEmailEndpoints stub (:8952) reads emailServiceProvider, routeManager, resolveExecCtx and enforceAuth once each — slice 8's host literal. Both match their source.

Module-level reads (the same walk), every one an import; no module-level declaration of rest-server.ts is read, so no export was added anywhere:

  • anonymousFormIntakeCandidates, anonymousFormIntakePosture, anonymousFormIntakeUnavailability, anonymousFormObjectName (@objectstack/metadata-core), GetMetaItemsRequest, CreateDataRequest (types, @objectstack/spec/api), mapDataError (./error-response.js), logError (./log.js): still read elsewhere in rest-server.ts, so their imports stay and the module imports the same names from the same modules.
  • anonymousFormIntakeWithdrawnIn, PUBLIC_FORM_SERVER_MANAGED_FIELDS and isExpectedRouteError: read ONLY by this body (the import line was their one other occurrence). Their import names moved with the body, leaving a pointer comment each in rest-server.ts's import block, as slices 5–7 and 10 did.
  • TransportScopedMetaRequest (type): imported from ui-routes.ts, as rest-server.ts imports it — slice 10's seat ruling A (6220753007). Not moved, not copied.
  • ServerScopedDataRequest (type): imported from data-action-routes.ts, where slice 1 moved it, as rest-server.ts imports it.

Host shape (slice 1's shape B): FormRouteHost (9 members) lists exactly the members the body reads, in the order it first reads them, each typed RestServer['m'] — and the static as (typeof RestServer)['translateOptionsFor'], as slice 7 typed overlayDocumentationInfo. The class keeps the private method with its name, docblock and position (JSDoc + signature, base :9048–:9083 against head :9053–:9088, diff to 0). Its body is the host literal plus registerFormEndpoints.call(host, basePath). Methods go as late-bound closures, (...a) => this.m(...a); the static as (...a) => RestServer.translateOptionsFor(...a). The call site in registerRoutes' registerForBase (this.registerFormEndpoints(bp);, base :4782, head :4787, under no switch) is unchanged: registerRoutes (75 lines) diffs to 0 between base and head; the call moved 5 lines only because of the import block above it.

Fields, checked one by one:

field read when reassigned after registration? handed over as
routeManager registration only (two register calls, top-level statements of the body) its one swap (:5151/:5155 at head) wraps registerMetadataEndpointsInner, not this registrar value

No other field is read, and no provider field: the protocol, the tenancy service, the object definitions, the i18n service, its bundle and locale, and the packaged base are each resolved per request by a METHOD, so no getter is owed and the provider ruling (6215084478) has nothing to apply to.

The static exception (6205442867), its two tokens: RestServer.translateOptionsFor(i18n, locale) at base :9281 (the object schema's translation) and :9312 (the section headings' translation) are now this.translateOptionsFor(i18n, locale) at form-routes.ts :307 and :338. translateOptionsFor is a private static whose whole body is return metaReadGate.metaTranslateOptions(i18n, locale);: it reads no this and nothing but its two arguments, so its behaviour does not depend on its receiver (assumption 2, confirmed). No other token changed.

Route switches (assumption 4): the registrar has no switch of its own — registerForBase calls it unconditionally, and RestServerConfig declares no api.enable* flag or forms config for it — so no tenth configuration exists to add. Slice 10's nine configurations are used.

Late binding, measured (a one-off vitest file, copied in, run under the verify lock, deleted; not committed). A source RestServer gets the seven instance methods planted BEFORE registerRoutes() and the class static replaced; both doors are driven. Then seven new methods are planted and the static replaced again AFTER registration, and both doors are driven again. Each records its call.

  • Head: MEASURE before: GET 200 POST 201 {"id":"rec_early"} | order early:proto,early:tenancy,early:objects,early:proto,early:i18n,early:bundle,early:locale,early:opts,early:packaged,early:opts,early:packaged,early:proto,early:tenancy,early:objects,early:proto and MEASURE after: GET 200 POST 201 {"id":"rec_late"} | order late:proto,…,late:opts,late:packaged,late:opts,late:packaged,… | early calls after re-plant 0, Tests 1 passed (1). Every member, the static included, is reached at request time.
  • Ablation A (scripts/ablation-replace.mjs): the host literal's resolveAnonymousFormTenancy closure replaced by this.resolveAnonymousFormTenancy.bind(this) (anchor 1 → 0, blob 7958e3cf728f → e7a647bd6557): late:tenancy never runs, the early member is called twice more, Tests 1 failed (1). Restored by the tool: blob 7958e3cf728f equals HEAD, git diff HEAD empty.
  • Ablation B: the static's closure replaced by RestServer.translateOptionsFor read at registration (anchor 1 → 0, blob 7958e3cf728f → c8a0d8f605b8): late:opts never runs, the early static is called twice more, Tests 1 failed (1). Restored, blob equals HEAD, diff empty.
  • Base leg: the base rest-server.ts (blob b429a90b19d3) put in the tree under a trap gave the same two MEASURE lines, Tests 1 passed (1); restored with git checkout HEAD, blob and diff proof.

Proof of move-only

  • Moved body: base rest-server.ts lines 9084–9527, one 4-space indent removed, against form-routes.ts lines 110–553: diff reports exactly the two receiver lines (body lines 198 and 229); with this.translateOptionsFor( mapped back to RestServer.translateOptionsFor(, diff exits 0 over all 444 lines. Every non-empty line of the range carries the method indent, no line has trailing whitespace, and the two lines with an odd backtick count are // comment lines (inline code wrapped across them), so no template literal spans the cut.
  • .d.ts: packages/rest/dist/index.d.ts and index.d.cts, built at base fd0e72abc before any edit and at the re-point head de5848dae, are both sha256 7489de84bc6feccb7cb1f28bcbe4a97775324959c61c9bfc35e8a8c45a6f3bc9 (the value slice 10 reported). The head bundles carry a head-only marker (registerFormEndpoints.call: 1 hit in each of index.js and index.cjs, 0 at base), so the declaration was rebuilt from the moved source. src/index.ts is untouched.
  • Export lists: the base and head ESM and CJS bundles each load with 39 exports and one sorted export-name list (sha256 b3246be6a1718700… on all four). No form-module name is among them.
  • Routes: a source RestServer on a recording mock server, with a protocol answering every member, dumped under nine configs: default; scoped-optional; scoped-required; every provider set; enableDiscovery: false; enableOpenApi: false; both off under scoped-optional; enableSearch: false under scoped-optional; enableUi: false under scoped-optional. Each dump holds getRoutes() (functions as arity), the ordered server registration calls, a whitespace-free sha256 of every handler's source, and that source.
    • Route counts 75 / 150 / 75 / 75 / 73 / 73 / 142 / 148 / 148 — slice 10's — with 2 or 4 form doors each. A second base run was identical to the first.
    • routes.json and calls.json are byte-identical between base and head in all nine configs.
    • Handler digests differ in exactly the resolve door, GET …/forms/:slug, once per mounted base (13 handlers over the nine configs), and in each of them the head source equals base once this.translateOptionsFor( is mapped back to RestServer.translateOptionsFor( (13 of 13). The submit door's digest is identical everywhere.
  • The handler digest can fail: a positive control added one character to the moved resolve handler ('[REST] Public form resolve error:' → '…error:!', through ablation-replace.mjs; anchor 1 → 0, blob 464bbaa4d12c → 9ec2ba34d8cf). Routes and calls were unchanged in all nine configs, and the handler digests moved in exactly the resolve door of each mounted base (1 or 2 per config) and nowhere else. Restored: blob 464bbaa4d12c equals HEAD, git diff HEAD empty.

Readers re-pointed (sums unchanged, no gate loosened)

packages/rest/src/rest-server-registrar-files.ts gains form-routes.ts with a [#23050 slice 11] comment (length 11 → 12). Every reader that pins its length re-reads its number:

reader sum over the list change
rest-server-query-number-census.test.ts 9 = 8 + 1 + 0 ×10 the form doors read only the slug path parameter, as a string; length 11 → 12
rest-server-canonical-query-ast.test.ts (repo project) 4 = 1 + 3 + 0 ×10 query: slots floor form-routes.ts: 0 (the Record over the list needs the key: without it the floor reads undefined); length 11 → 12
meta-draft-read-door-census.test.ts 8 = 6 + 0 + 0 + 0 + 0 + 2 + 0 ×6 the form doors read the environment's lists, never a draft; length 11 → 12
rest-server-docblock-position.test.ts floors 4 + 3 + 3 + 8 + 3 + 8 + 5 + 3 + 3 + 4 + 3 = 47 floor form-routes.ts: 3 (the module header, the host interface's block and the function's). The form body held no TSDoc block, so rest-server.ts keeps its 163. Length 11 → 12
execctx-consumer-census.test.ts 62 sites = 22 + 9 + 5 + 7 + 8 + 1 + 5 + 2 + 1 + 1 + 1 + 0; 125 mentions the form body reads no resolveExecCtx, so its host literal forwards none: MOVED_REGISTRARS_READING_NO_EXECCTX gains form-routes.ts: 1 (1 → 2), as slice 7 entered the discovery body. Forwarders stay 12 = (12 − 1) + 3 − 2. Length 11 → 12
ui-view-route-identity.measurement.test.ts this.enforceAuth( 59, this.resolveExecCtx( 74 unchanged the form body reads neither term; length 11 → 12
ui-view-route-tenancy.measurement.test.ts as above; the ownership guard 1 call site + 1 forwarder, unchanged length 11 → 12
dogfood authz-conformance.test.ts (realtime tripwires) 0 hits over the twelve files length 11 → 12
dogfood authz-probe-blind-spot.census.ts, rest-server.ts row 70 / 19 / 51 unchanged; this.routeManager.register( 63 = 18 + 9 + 5 + 7 + 8 + 1 + 6 + 4 + 1 + 1 + 1 + 2; private register*Endpoints( 16; enforceAuth 108 new control form-routes.ts: this.routeManager.register( 2; registrar list length 12; populationRule names the new module. enforceAuth does not move: the doors never call it, and the seam names no such member

Readers outside the list:

  • scripts/check-route-envelope.mjs (a gate's target table, domain:spec): form-routes.ts is declared dialectOnly with ratchet [tracking] Envelope-position convergence line for packages/rest's flat dialect — the live ratchet owner #9559, as the ten earlier modules are, at its measured stringError 4 (each door's 400 INVALID_REQUEST 'slug is required' and each door's 404 FORM_NOT_FOUND, a template literal) and siblingCode 6 (those four, plus the resolve door's 500 FORM_RESOLVE_FAILED and the submit door's 400 VALIDATION_ERROR). rest-server.ts drops by exactly those bodies: stringError 15 → 11 and siblingCode 13 → 7, each recorded as "NOT progress — the same MOVE". The sums are unchanged: 11 + 13 + 8 + 1 + 0 + 0 + 0 + 0 + 1 + 0 + 1 + 4 = 39, and 7 + 15 + 8 + 9 + 4 + 1 + 0 + 0 + 3 + 0 + 1 + 6 = 54. Its output differs from base only in the rest-server.ts row and the new row; --self-test is green.
  • scripts/adr-anchors/packages__rest__src__form-routes.ts.json (new): the moved body carries two ADR-0131 D6 citations, which left rest-server.ts, an anchored file. check-adr-anchors' anchored decision-letter reading went 1772 → 1770 at the move commit; the new anchor (ADR-0131, ADR-0056 and ADR-0112, each named in the module) brings it back to 1772. Its invariant spells decisions as "its D6", so it adds no letter citation of its own. As slices 4, 6 and 7 did.
  • docs/qa/platform-checklist/areas/access-security.json (domain:devx): the one source string reading rest-server.ts#registerFormEndpoints (resolveFormBySlug: …) now names form-routes.ts#registerFormEndpoints, where resolveFormBySlug lives. The text after the anchor is unchanged, and no history entry is added (slice 7's precedent).

Red before the re-points, each measured with the readers at their base text:

  • At the move commit 73d2a78fa (the list not yet grown): the dogfood blind-spot census read expected 68 to be 70 (population) and control "this.routeManager.register(" moved … expected 61 to be 63: the two form registrations had left the reach of a reader that did not yet know the module. @objectstack/rest local (287 files) and repo (5 files) were green there, which is the reach loss the list exists to close. check:route-envelope read form-routes.ts NOT DECLARED, stringError: found 11, declared 15 and siblingCode: found 7, declared 13; check-adr-anchors read 1770 decision-letter citations (base 1772).
  • At 9c26739ec (the list grown, the readers not yet re-pointed): local read Test Files 6 failed | 281 passed (287), Tests 7 failed | 5897 passed | 326 skipped (6230) — the six length pins (execctx, meta-draft, query-number, docblock, and both ui-view tests) and the docblock pin's unfloored new module (finds at least undefined TSDoc blocks). repo read Tests 2 failed | 252 passed | 12 skipped (266) — the canonical-query-AST length pin and its missing floor (expected value must be number or bigint, received "undefined"). The dogfood pair read Tests 2 failed | 106 passed (108) — the tripwire's length pin and control "registrar list length" moved … expected 12 to be 11.
  • Each is green after the re-point (de5848dae, then the merge head).

The packages/spec reader stopped at (red, not edited)

pnpm --filter @objectstack/spec run check:liveness exits 0 at base and 1 on this PR:

✗ 2 anchored citation(s) name a symbol the cited file does not contain:
    view/form.sharing → packages/rest/src/rest-server.ts#findPublicFormView
    view/formViews → packages/rest/src/rest-server.ts#findPublicFormView
✗ 1 'live' / 'planned' / 'experimental' / 'live-elsewhere' citation(s) name a file that never names the property:
    view/form.sections → packages/rest/src/rest-server.ts

All three are evidence strings in packages/spec/liveness/view.json that read the moved body: findPublicFormView and resolveFormBySlug are closures of registerFormEndpoints, and sections is read only there. A fourth citation in the same file, form.sharing's rest-server.ts#resolveFormBySlug, stays green only because a docblock left in rest-server.ts names resolveFormBySlug in prose; it also reads a moved line. The re-point is mechanical, the consumer text unchanged:

  • props.form.children.sections: packages/rest/src/rest-server.ts registerPublicFormEndpoints (…) → packages/rest/src/form-routes.ts registerFormEndpoints (…);
  • props.form.children.sharing: packages/rest/src/rest-server.ts#findPublicFormView → packages/rest/src/form-routes.ts#findPublicFormView, and packages/rest/src/rest-server.ts#resolveFormBySlug → packages/rest/src/form-routes.ts#resolveFormBySlug;
  • props.formViews: packages/rest/src/rest-server.ts#findPublicFormView → packages/rest/src/form-routes.ts#findPublicFormView.

packages/spec/** is not edited by this lane (the dispatch's rule), so the slice stops at it and the routing is asked of the seat in the report on #23050. These anchors cannot be pointed at form-routes.ts before this PR lands (the file does not exist on main), and the PR cannot land with them pointing at rest-server.ts, so whichever route is chosen, the edit lands with this PR or ahead of it at a home that resolves on both trees.

Readers examined and not re-pointed

  • Prose that names the registrar without a file or line, still true: content/docs/ui/forms.mdx ("rest-server scans views" names the component, and RestServer still serves both doors); packages/verify/src/handle.ts and its public-form-door test; packages/metadata-core/src/anonymous-form-intake.ts; packages/spec/src/ui/anonymous-form-intake.ts; packages/metadata-protocol/src/protocol.org-scoped-write-refused.test.ts; the dogfood anonymous-door-sweep.classification.ts (enforcedBy: 'RestServer.registerFormEndpoints; …') and the conformance matrix row anonymous-door-public-form, whose enforcement must name RestServer.registerFormEndpoints (ADR-0138 D2's table; the method is still that class's). No gate reds on any of them.
  • docs/adr/0138-… cites packages/rest/src/rest-server.ts#RestServer.registerFormEndpoints twice; the symbol still resolves. Tier H, not edited; it joins the seat's ADR carry (0076, 0111, 0138).
  • packages/runtime/src/domains/meta-list-projection-parity.test.ts reads rest-server.ts by path, but only the GET ${metaPath}/:type handler, which did not move.
  • Gates that walk packages/rest/src reach the moved code in its new file. Their outputs at origin/main 2b274600b and at the final head differ only by the one scanned file or by this diff's own re-points: check-meta-type-normalized 39 → 40 files; check-undeclared-dep-imports +1 file, +2 specifiers; check-dispatcher-error-vocabulary and check-error-status-conformance +1 file (classified sites unchanged); check-affected-docs route sources 29 → 30 and registration call sites 24 → 25, by the *-routes.ts convention; check-scripts-symbol-anchors 3904 → 3908 file-level anchors (the route-envelope gate's new comments); check:platform-checklist cited sources 396 → 397 with 834 of 844 anchors resolving on both (the re-pointed source); check-adr-anchors 69 → 70 anchored files, 1772 decision-letter citations on both. Identical: check-system-context-census, check-single-authz-resolver, check-rest-log-declared, check-durability-degradation-log-level and check-filter-alias-parity.
  • Released CHANGELOG.md text: not edited.

Acceptance notes

Gates and tests

Measured at this branch: base fd0e72abc; the move 73d2a78fa; the list 9c26739ec; the re-points de5848dae; one merge of origin/main 2b274600b (22352ffd7: #24026, #24005, #24004, #24015, #24009, #24013, #24019; none touches a file in this diff, and rest-server.ts' blob is the same at fd0e72abc and 2b274600b). All builds and tests went through os-verify-lock, and every exit code was written to a file before any pipe. Final head: 22352ffd7.

  • .d.ts at the merge head: main moved @objectstack/rest's declaration (meta-item-read-gate.ts, fix(rest)!: the /meta item and list reads serve a view stored with no label without an invented label #24013), so the merge head is compared with main's own: a base leg put origin/main's rest-server.ts (blob b429a90b19d3) into the merged tree under a trap and built @objectstack/rest, giving dist/index.d.ts / .d.cts sha256 0a3edce6ab41821f… with the head-only marker at 0; restored (blob and git diff HEAD proof) and rebuilt, the head gives the same 0a3edce6ab41821f… with the marker at 1. The export lists at the merge head are the base's (39, b3246be6a1718700…, ESM and CJS). Route dumps: origin/main's base equals fd0e72abc's, and the merge head's equal the re-point head's, over all nine configs (diff -r exit 0 both). The late-binding legs ran at de5848dae; the merge changed neither rest-server.ts nor form-routes.ts.
  • pnpm --workspace-concurrency=2 --filter '@objectstack/rest...' --filter '@objectstack/verify...' build: exit 0 at base and at the merge head. Then pnpm turbo run build --concurrency=2, the whole workspace: exit 0, Tasks: 76 successful, 76 total (3 cached), after which the .d.ts still reads 0a3edce6….
  • pnpm --filter @objectstack/rest typecheck: exit 0 (tsc --noEmit, then check:test-typecheck: 0 file(s) / 0 error(s)).
  • pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2: Test Files 288 passed (288), Tests 5943 passed | 326 skipped (6269).
  • pnpm --filter @objectstack/rest exec vitest run --project repo --maxWorkers=2: Test Files 5 passed (5), Tests 254 passed | 12 skipped (266).
  • pnpm --filter @objectstack/dogfood exec vitest run test/authz-probe-blind-spot.test.ts test/authz-conformance.test.ts: Tests 4 failed | 104 passed (108) at the merge head — all four are main's, not this diff's: packages/runtime/src/route-ledger.ts — population … expected 92 to be 88, its positive control (same), and the matrix docblock's packages/runtime/src/route-ledger.ts: docblock says 88 rows, the table holds 92 (two cases). fix(runtime): mount every /api/v1/keys verb so an anonymous caller is answered 401 on the standalone server #24009 (9e797a897) added four rows to packages/runtime/src/route-ledger.ts and none of these pins moved. The same two blind-spot failures reproduce at origin/main 2b274600b in a clean comparison tree without this diff (the conformance file did not load there, its @objectstack/verify unbuilt); every reader this PR re-points passes, and at de5848dae (before the merge) the pair read Test Files 2 passed (2), Tests 108 passed (108).
  • Lint: pnpm exec eslint --no-inline-config --format json over the 13 changed .ts/.mjs files: exit 0, 13 results, 0 errors, 0 warnings. Repo-wide pnpm lint is CI's (Lint & Repo Gates).
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths, at 22352ffd7: its stderr names that commit, the --repo assertion holds, and it reads 15 paths against merge base 2b274600b, 1167 changed lines (under the 3000 human-merge threshold). It derived 82 commands, all run at 22352ffd7 after the whole-workspace build: 81 exit 0, and pnpm --filter @objectstack/spec run check:liveness exits 1 (the packages/spec reader above). pnpm check:type-check-debt first answered 3 (PREREQUISITE NOT MET: its closure build never got a lock turn), and re-run under the lock it exits 0 (1 ledger entr(ies) re-measured … 26 raw tsc error(s) total, none above its recorded number). pnpm check:pm-dispatch-gates ran on its own (nohup, the battery took 305.5s) and exits 0. --ran reads: Run reconciliation — 82 derived, 82 run, 0 NOT-MEASURED, 0 UNRUN.
  • check:dev-prereqs and check:skill-frame-freshness (local-only) were not run: this diff touches no packages/spec build input and no four-axis frame file.
  • node scripts/pm/check-governed-merges.mjs --test over the 15 paths with --additions 701 --deletions 466: 0 of 15 path(s) hit the register — NOT governed.
  • No changeset: nothing any released package publishes changes (the .d.ts, the export lists and the routes above). skip-changeset is the seat's to apply on its own check.
  • The verify lock queued twice past its budget (another holder at 10–20 minutes); each 99 was re-queued under the same slot, and nothing was read off a run that did not get a turn.

Generated by Claude Code

…23050 slice 11)

The body of RestServer#registerFormEndpoints (GET {basePath}/forms/:slug and
POST {basePath}/forms/:slug/submit) moves, re-indented one level, to one new
module, packages/rest/src/form-routes.ts. The class keeps the private method,
its docblock and its position as a FormRouteHost literal plus .call. Two
tokens change: the two RestServer.translateOptionsFor(...) calls become
this.translateOptionsFor(...) through the host, because a private static is
not reachable by name outside the class. Body-only imports move with the
body. Readers are re-pointed in the next commit.

Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT
Co-authored-by: Claude <noreply@anthropic.com>
REST_SERVER_REGISTRAR_FILES grows 11 -> 12, so every whole-file reader of
the registrars' code reads the moved form doors too. The readers that pin
the list's length re-read their numbers in the next commit.

Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT
Co-authored-by: Claude <noreply@anthropic.com>
…ce 11)

Each reader whose reading included a moved line now reads it in
form-routes.ts, with its sum unchanged:

- the registrar-list readers re-read their numbers (length 11 -> 12): the
  query-number, canonical-query-AST, meta-draft read-door and execctx
  consumer censuses, the docblock-position pin, the two ui-view
  measurements and both dogfood authz readers;
- check-route-envelope declares form-routes.ts at stringError 4 and
  siblingCode 6, and the rest-server.ts row goes 15 -> 11 and 13 -> 7
  (sums 39 and 54 unchanged);
- a new ADR anchor for form-routes.ts keeps check-adr-anchors' anchored
  decision-letter reading at 1772;
- the access-security checklist source naming resolveFormBySlug points at
  form-routes.ts.

Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

25 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 8133cc78052c95b6efd59487b64fe78f69860759.

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

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/liveness/view.json) — pages documenting those are invisible to this run
  • 2 cross-cutting symbol(s) contributed no route anchor: routeManager (19 routes), resolveProtocol (25 routes)
  • 2 anchor(s) matched too much of the corpus to be a work list: master_detail (literal, 33 pages), /data/:object (route, 66 pages)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 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.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 145 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 8133cc78052c95b6efd59487b64fe78f69860759 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 2967f94728bedcc75c10067bf4b2c4039e3b3327 — the merge of head e62024d87b7e8a2be6340cafcaf0f4dfae496e11 into base 8133cc78052c95b6efd59487b64fe78f69860759, 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 2967f94728bedcc75c10067bf4b2c4039e3b3327 && git checkout 2967f94728bedcc75c10067bf4b2c4039e3b3327
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8133cc78052c95b6efd59487b64fe78f69860759 e62024d87b7e8a2be6340cafcaf0f4dfae496e11 && git checkout -B drift-repro 8133cc78052c95b6efd59487b64fe78f69860759 && git merge --no-ff e62024d87b7e8a2be6340cafcaf0f4dfae496e11

node scripts/docs-audit/affected-docs.mjs --json 8133cc78052c95b6efd59487b64fe78f69860759

⚠️ 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 8133cc78052c95b6efd59487b64fe78f69860759 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…s.ts (#23050 slice 11)

The seat's ruling A on slice 11's open question: packages/spec/liveness/view.json
reads the moved form doors in three evidence strings, so their four path
references follow the code to packages/rest/src/form-routes.ts
(form.sections, form.sharing x2, formViews). Consumer text, verdicts and
notes unchanged.

Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT
Co-authored-by: Claude <noreply@anthropic.com>
…eness rows (#23050 slice 11)

packages/spec ships liveness/ in its files[], so the three re-pointed
evidence strings change what @objectstack/spec publishes: a patch
changeset, as the earlier liveness-only evidence rewrites carried.

Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT
Co-authored-by: Claude <noreply@anthropic.com>
…rs read it through (#23050 slice 11)

The seat's amendment to ruling A for this one row: form-routes.ts never
spells formViews, so the key-mention check refused the re-pointed citation.
findPublicFormView reaches every form view through
anonymousFormIntakeCandidates (re-exported by @objectstack/metadata-core as
the same binding), declared in packages/spec/src/ui/anonymous-form-intake.ts,
which reads v.formViews. The row cites that symbol; its quoted text is left
as it stands.

Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT
Co-authored-by: Claude <noreply@anthropic.com>
…'s new citation (#23050 slice 11)

Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT
Co-authored-by: Claude <noreply@anthropic.com>

This branch has not been deployed

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

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants