Skip to content

refactor(rest): move registerSharingEndpoints and registerSharingRuleEndpoints into sharing-routes.ts (#23050 slice 4) - #23705

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-23050-s4-sharing-endpoints
Oct 15, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-23050-s4-sharing-endpoints

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #23050
Clause-②: no

Slice 4 of the rest-server.ts split, by slice 1's pattern (landed note 6136102437, steps 1–6), slice 2's getter refinement (6189011700) and slice 3's shape (PR #23670): the bodies of RestServer#registerSharingEndpoints (the per-record share family: GET / POST {basePath}/data/:object/:id/shares and DELETE …/shares/:shareId, 3 routes) and RestServer#registerSharingRuleEndpoints (the sharing-rule family under {basePath}/sharing/rules: list, create, read, delete, evaluate, 5 routes) move, unedited, to ONE new module, packages/rest/src/sharing-routes.ts. It is move-only: routes, the ordered registrations, every handler and the published dist/*.d.ts are byte-identical, and every reader whose reading included a moved line now reads it where it is, with its sum unchanged.

rest-server.ts goes from 12,119 to 11,495 lines (measured on origin/main c9810dd9d1, which this branch merged). The card stays open for the later slices.

The pattern, applied

Members the moved bodies read, from an AST walk of each method (base 361a1c46d; both bodies re-verified byte-identical against c9810dd9d1, where they sit at :11066-:11508 and :11524-:11736):

registrar fields methods
registerSharingEndpoints (5 members) config (1 read), routeManager (3), sharingServiceProvider (2, inside resolveService) resolveExecCtx (3), enforceAuth (3)
registerSharingRuleEndpoints (4 members) routeManager (5), sharingRulesServiceProvider (2, inside resolveService) resolveExecCtx (5), enforceAuth (5)

Neither body has a bare this or an element access on this. The module-level names they read are all imports (ErrorCode, INTERNAL_ERROR_MESSAGE, sendError as sendEnvelopeError, standardErrorCodeForHttpStatus, boundedDeclaredUserMessage, classifiedRefusalAnswer, nestedRefusalEnvelope, sandboxFaultAnswer, logError, refuseRepeatedQueryParams). Every one of them is still used elsewhere in rest-server.ts (counted by AST outside the moved ranges), so each import stays there and the new module imports the same names from the same modules. Nothing declared in rest-server.ts is read, so no export was added anywhere.

Host shape (slice 1's shape B, twice): the class keeps both private methods with their names, docblocks and positions. Each body is now one host literal (SharingRouteHost, SharingRuleRouteHost, one interface per registrar so each lists exactly what its body reads) plus .call(host, basePath). Each interface member is typed RestServer['m']; methods go as late-bound closures, (...a) => this.m(...a). The call sites in registerRoutes are unchanged.

Fields, checked one by one:

field read when reassigned after registration? handed over as
config registration only (const { crud } = this.config;, the body's first line) no (constructor only) value
routeManager registration only (every register call) its one swap wraps registerMetadataEndpointsInner, not these registrars value
sharingServiceProvider request time (resolveService, from each handler) not by the class, but not readonly getter
sharingRulesServiceProvider request time (same) not by the class, but not readonly getter

The getters are measured, not argued. A one-off vitest file (copied in, run, deleted) boots a source RestServer with no provider, drives the door, assigns the field on the instance AFTER registerRoutes(), and drives it again:

  • this head: GET /api/v1/data/:object/:id/shares answers 501 NOT_IMPLEMENTED, then 200 {"data":[{"id":"shr_1"}]}; GET /api/v1/sharing/rules answers 501, then 200 {"data":[{"name":"r1"}]}.
  • by-value ablation (scripts/ablation-replace.mjs, each getter in turn replaced by X: this.X,; anchor 1 → 0, replacement 0 → 1, blob 8fd364065fcf → mutated): the ablated door answers 501 then 501 (frozen at registration) while the other door still reads 200, in both runs. Restored by the tool and a trap: blob 8fd364065fcf equals HEAD, git diff HEAD empty, the one-off test file deleted. The path is source (relative import), so no dist leg applies.

Mechanism hypotheses from the dispatch, measured: (a) PR #23674 landed first (c2635881e0), so this branch merged origin/main c9810dd9d1 once (a merge commit, no conflict) and the whole-file readers were re-run on the merge, the meta-draft-read-door census among them (8 exact). (b) enforceAuth and resolveExecCtx are class methods, handed over as closures, not exported helpers; the rule-family field is spelled sharingRulesServiceProvider; both provider fields go as getters. (c) The :111 import comment ("see its use in registerSharingEndpoints") now says that body lives in sharing-routes.ts.

The registrar list

packages/rest/src/rest-server-registrar-files.ts gains sharing-routes.ts with a [#23050 slice 4] comment (length 4 → 5). Every reader that pins its length re-reads its number, and every sum is unchanged:

reader sum over the list change
rest-server-query-number-census.test.ts 9 = 8 + 1 + 0 + 0 + 0 length 4 → 5
rest-server-canonical-query-ast.test.ts 4 = 1 + 3 + 0 + 0 + 0 query: slots floor sharing-routes.ts: 0, length 4 → 5
meta-draft-read-door-census.test.ts 8 = 8 + 0 + 0 + 0 + 0 length 4 → 5
rest-server-docblock-position.test.ts floors 4 + 3 + 3 + 8 = 18 floor sharing-routes.ts: 8 (header, two host interfaces, two functions, and the three blocks that moved with the share body), length 4 → 5
execctx-consumer-census.test.ts 62 sites = 33 + 9 + 5 + 7 + 8 mentions 89 → 97 (the seam, twice: two forwarders and two interface members); forwarders pinned one per moved REGISTRAR (5 = 4 modules + EXTRA_MOVED_REGISTRARS, since this module carries two)
ui-view-route-identity.measurement.test.ts / ui-view-route-tenancy.measurement.test.ts this.enforceAuth( 52, this.resolveExecCtx( 67 over the list (50 / 65 before, +1 each per forwarder); ownership guard 1 length 4 → 5
dogfood authz-conformance.test.ts (realtime tripwires) 0 hits over the five files length 4 → 5
dogfood authz-probe-blind-spot.census.ts, rest-server.ts row 70 / 19 / 51 unchanged; this.routeManager.register( 63 = 34 + 9 + 5 + 7 + 8 new control sharing-routes.ts: this.routeManager.register( 8; registrar list length 5; enforceAuth 72 → 80, re-anchored (the seam, twice; no call site added)

Proof of move-only

  • .d.ts: packages/rest/dist/index.d.ts and index.d.cts built at origin/main c9810dd9d1 (a separate worktree) and at this branch's merge head are byte-identical: sha256 7489de84bc6feccb7cb1f28bcbe4a97775324959c61c9bfc35e8a8c45a6f3bc9 for all four files, cmp silent. The ESM and CJS bundles both load with 39 exports, one sorted export-name list (sha256 84008c9643d5…) on both sides. SharingRouteHost, SharingRuleRouteHost and sharing-routes appear nowhere in the .d.ts; src/index.ts is untouched. The bundle JS bytes differ, because code moved within it.
  • Routes: a source RestServer on a recording mock server, with a protocol that answers every member, dumped under four configs at c9810dd9d1 and at this head: getRoutes() (functions as arity), the ordered server registration calls, and a whitespace-free sha256 of every handler's source. 12 dumps of 12 are byte-identical. default: routes ee3a764e19bd…, calls 0d002057270d…, handlers 8581c62bc47c… (75 routes, 8 sharing); scoped-optional: eb7a21beb5de… / c49ba58f5cff… / b853abe81f98… (150, 16); scoped-required: d6d0e5068384… / 71d12ded1756… / f06c27a6c600… (75, 8); a fourth config with every provider set equals default. The routes digests match slice 3's for the same configs.
  • The handler digest can fail: a positive control mutated one character of one moved handler (List shares error: → !, through ablation-replace.mjs) and the dump's handler list differed in exactly GET /api/v1/data/:object/:id/shares, with routes and calls unchanged; restored, blob equals HEAD.
  • Moved bodies: c9810dd9d1's rest-server.ts lines 11066-11508 and 11524-11736, with one 4-space indent removed, against sharing-routes.ts lines 102-544 and 553-765: diff exits 0 both times (443 + 213 lines). No moved line holds a multi-line string or template literal (every odd-backtick line is a comment), so re-indenting changes no runtime string.
  • Moved-line accounting: git diff --color-moved=plain --color-moved-ws=allow-indentation-change c9810dd9d1 HEAD over the two files: 655 deleted and 683 added lines detected as moved; rest-server.ts is +33 / -657: the two stubs (23 lines; the diff keeps each method's closing lines in place), the 7-line import and the 3-line re-pointed :111 comment.

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

  • scripts/check-route-envelope.mjs (a gate's target paths): sharing-routes.ts is discovered by the *-routes.ts convention and declared dialectOnly with ratchet [tracking] Envelope-position convergence line for packages/rest's flat dialect — the live ratchet owner #9559, as the three earlier modules are, at its measured stringError 0, siblingCode 4 (the sharing-rule handleError's four { code, error } arms). rest-server.ts siblingCode 22 → 18; stringError stays 17 (no literal-string arm moved). Sums 17 + 13 + 8 + 1 + 0 = 39 and 18 + 15 + 8 + 9 + 4 = 54, unchanged. Output differs from base only in those lines (14 → 15 modules, 5 → 6 ratcheted). --self-test green.
  • New ADR anchor scripts/adr-anchors/packages__rest__src__sharing-routes.ts.json (ADR-0111, ADR-0112). check-adr-anchors verifies ADR-NNNN Dk citations only on the anchored surface; the eight in the moved bodies (ADR-0111 D1/D4/D5/D6/D7, ADR-0112 D4/D5) were on it only because rest-server.ts is anchored, and the move dropped the reading 1756 → 1748 decision-letter citations with the gate still green. Anchoring the module restores 1756, with an invariant that adds no letter citation of its own (66 → 67 anchored files). This is the one reader whose re-point needed a new entry rather than an edit.
  • docs/qa/platform-checklist/areas/access-security.json: the two rest-server.ts#registerSharingRuleEndpoints anchors name sharing-routes.ts#registerSharingRuleEndpoints, where the routes they describe now live (slice 2's precedent for batch-routes.ts#registerBatchEndpoints). Both resolved before and after (the private stub keeps the name), so this one was never red; check:platform-checklist output differs only in its cited-sources count (345 → 346).
  • Prose pointers that named rest-server.ts as the home of respondSharingError: rest-share-declared-code.test.ts and rest-share-user-message.test.ts headers, and two comments in error-response.ts (not in the published .d.ts; the .d.ts above is unchanged).

Red before the re-points, each measured with the move in place: at 12e48a7d36 (the move alone) the rest suite read Test Files 6 failed | 279 passed (285), Tests 7 failed | 5850 passed | 326 skipped (6183) (five list readers' length pins, plus the docblock pin's length pin and its new module, which had no floor yet; canonical-query-ast is in the repo project). The dogfood pair, with its two files at that commit's text, read Tests 2 failed | 106 passed (108) (list length; the census row's registrar list length control). check:route-envelope: sharing-routes.ts NOT DECLARED (both tables) and rest-server.ts siblingCode 18 against 22.

Readers examined and not re-pointed

  • objectql-slot-consumer-census, auth-gate-allowlist-fault-window, execctx-authz-input-seam-reachability, runtime meta-list-projection-parity, core authz-store-unavailable: they read other members of rest-server.ts (objectQLProvider, the gate block, computeExecCtx, /meta, resolveAuthzContext), none of which the moved bodies touch.
  • The ADR anchor packages__rest__src__rest-server.ts.json (ADR-0045, filterAppForUser) is unaffected.
  • No rest-server.ts:NNNNN line citation points into the moved range.
  • Gate outputs, base c9810dd9d1 vs this head, identical: check-durability-degradation-log-level, check-system-context-census, check-single-authz-resolver, check-rest-log-declared, check:liveness, check:cross-package-test-inputs, check:issue-citations, check:test-source-alias, check:rest-log-spy-declared. Identical except the scanned-file count (+1, the new module): check-meta-type-normalized, check-dispatcher-error-vocabulary (57 sites, 57 classified), check:error-status-conformance (463 producer sites), check:doc-authoring, check:nul-bytes, check-undeclared-dep-imports (+3 specifiers). check-scripts-symbol-anchors +4 file-level anchors (the new comments in check-route-envelope.mjs); check-affected-docs 22 → 23 route sources (the new module, admitted by the filename convention).

Acceptance notes

  • packages/spec reader, not edited (the claim keeps packages/spec out): packages/spec/liveness/crud_endpoints.json's dataPrefix evidence names rest-server.ts#registerSharingEndpoints ("each builds its own dataPath the same way"); that dataPath line now lives in sharing-routes.ts. The anchor still resolves (the private method keeps the name) and check:liveness is identical base vs head. It joins the two pointers in the same string that slices 1 and 2 carried to the CRUD slice.
  • packages/spec/src/api/error-code-ledger.zod.ts (about :219 and :1759) names respondSharingError without a file, so it still lands; not edited.
  • Prose in other lanes that names rest-server.ts for code now in sharing-routes.ts, read by nothing: packages/types/src/data-error-classification.ts (about :243, :507, :707), packages/plugins/plugin-sharing/src/sharing-rule-service.ts (about :503) and three plugin-sharing tests (sharing-rule.test.ts :1345, sharing-service.test.ts :1114, federated-phantom-owner-scoping.test.ts :460). Not edited.
  • docs/adr/0111-…md's Consumers line names rest-server.ts for the sharing / sharing-rule routes; a governed surface, not edited here (as slice 3 left ADR-0076).
  • docs/qa/platform-checklist/areas/access-security.json's first re-pointed anchor says the record-shares family is "registered inline, under no registrar of its own"; it is registered by registerSharingEndpoints. Re-pointed, not re-judged.
  • rest-server-docblock-position.test.ts's header still records that the registerSharingEndpoints route table was once orphaned; that docblock stays in rest-server.ts, so the sentence is still true.
  • Lines of rest-server.ts from the new import (about :325) to the two registrars shift by +7, and after them by -624 (c9810dd9d1 :11739 is this head's :11115).

Gates and tests

Measured at this branch: base 361a1c46d, one merge of origin/main c9810dd9d1 (20c00f5280), then two commits that touch no package source (aa4ad8f41c the ADR anchor, 7b101d0a64 a comment in one test). origin/main has since moved to 3bc407d307 (objectql and metadata-protocol only, no file in this diff); not merged, PR CI runs the merge ref. All builds and tests went through os-verify-lock; every exit code was captured to a file before any pipe.

  • pnpm --filter @objectstack/rest exec vitest run --project local at 20c00f5280: Test Files 285 passed (285), Tests 5860 passed / 326 skipped (6186). rest-server-docblock-position.test.ts re-run at 7b101d0a64: 11 passed.
  • pnpm --filter @objectstack/rest exec vitest run --project repo at 20c00f5280: Test Files 5 passed (5), Tests 226 passed / 5 skipped (231).
  • pnpm --filter @objectstack/rest typecheck at 20c00f5280: exit 0 (tsc --noEmit, then check:test-typecheck: 0 file(s) / 0 error(s)).
  • pnpm --filter @objectstack/dogfood exec vitest run test/authz-probe-blind-spot.test.ts test/authz-conformance.test.ts at 20c00f5280: Test Files 2 passed (2), Tests 108 passed (108).
  • Lint (the proven narrowing): eslint --no-inline-config --format json over packages/rest, the two dogfood files, scripts/check-route-envelope.mjs and the checklist JSON: 328 files, 0 errors; the one warning is eslint's "File ignored because no matching configuration was supplied" for the JSON, which shows eslint . never lints it. Every one of the 16 other changed files is in the result set at 0 / 0. The published .d.ts is byte-identical, so no consumer outside packages/rest can lint differently. Repo-wide pnpm lint is CI's.
  • dispatch-gates --commands --repo objectstack-ai/objectstack, no paths, at the final head 7b101d0a64: 81 commands (80 at 20c00f5280; the ADR anchor adds pnpm check:adr-anchors). All 81 ran at 7b101d0a64 and exited 0. --ran: Run reconciliation — 81 derived, 81 run, 0 NOT-MEASURED, 0 UNRUN.
  • Among them pnpm check:route-envelope (its --self-test, then the scan), pnpm check:adr-anchors (1756 decision-letter citations, 67 anchored files), pnpm check:platform-checklist, pnpm check:durability-log-level, pnpm check:dispatcher-error-vocabulary and pnpm check:dual-build-cjs-loads (exit 0: the dogfood closure build left every package it reads built). No test file executes check-route-envelope.mjs.
  • An earlier union at 20c00f5280 read pnpm check:dts-closure exit 1 (runtime and service-automation .d.ts missing) because it ran while this branch's own @objectstack/verify closure build was rewriting those dist/ folders; re-run after that build: exit 0, 175/175 declaration files present. The final union above ran with no build beside it.
  • check:dev-prereqs and check:skill-frame-freshness (local-only) were not run: no packages/spec build input and no four-axis frame file is touched.

Generated by Claude Code

…dies into sharing-routes.ts (#23050 slice 4, move only)

The bodies of RestServer#registerSharingEndpoints and
#registerSharingRuleEndpoints move, unedited and re-indented one level, to
packages/rest/src/sharing-routes.ts. The class keeps both private methods
and their docblocks as a host literal plus .call; the registrar list gains
the module. Readers are re-pointed in the following commits.

Claude-Session: https://claude.ai/code/session_01Q9PZ5Pzqcus4woVvTZwWNU
Co-authored-by: Claude <noreply@anthropic.com>
…odies (#23050 slice 4)

The registrar list's nine length pins go 4 -> 5 with their sums unchanged;
check-route-envelope declares sharing-routes.ts at the dialect counts that
left rest-server.ts; the two platform-checklist anchors, two test headers
and two error-response.ts pointers name sharing-routes.ts.

Claude-Session: https://claude.ai/code/session_01Q9PZ5Pzqcus4woVvTZwWNU
Co-authored-by: Claude <noreply@anthropic.com>
… so the moved letter citations stay checked (#23050 slice 4)

check-adr-anchors verifies ADR-NNNN Dk citations only on the anchored
surface. The eight in the moved sharing bodies were on it only because
rest-server.ts is anchored; the move dropped the reading 1756 -> 1748.
Anchoring the new module restores it to 1756.

Claude-Session: https://claude.ai/code/session_01Q9PZ5Pzqcus4woVvTZwWNU
Co-authored-by: Claude <noreply@anthropic.com>
… base-bound number (#23050 slice 4)

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/rest, touching 51 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/rest/src/error-response.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

34 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 f77ba411e3d07d790ae938a28d4a8ae0f62405a7.

⛔ 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/rest/src/error-response.ts) — pages documenting those are invisible to this run
  • 3 cross-cutting symbol(s) contributed no route anchor: enforceAuth (23 routes), resolveExecCtx (28 routes), routeManager (21 routes)
  • 2 anchor(s) matched too much of the corpus to be a work list: PERMISSION_DENIED (literal, 36 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 — 18 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 f77ba411e3d07d790ae938a28d4a8ae0f62405a7 → packageMentionDocs.

Which tree this was computed on

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

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

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 7b101d0a640f7756a157da0eda72ac35332f1679
Local-runs: none

Face: the declaration face. A move-only refactor of @objectstack/rest that re-points one gate's target paths (scripts/check-route-envelope.mjs), adds one entry to a gate's registry (scripts/adr-anchors/), and grows the registrar list that nine whole-file readers read. The 18-file list holds no published schema and no governed path (record-recognisers --template --paths derives TIER_DEFAULT; Governed Surface Queue Guard success).

Inputs:

The reviewer is an isolated agent working read-only. Every count below was read off the git objects of the merge-base and the head with git show, text diff and grep. Nothing was built, tested or re-run.

① Derived judgments

  1. The moved bodies are byte-identical: right.

    • Base rest-server.ts :11066–:11508 and :11524–:11736, with one 4-space indent level removed, equal head sharing-routes.ts :102–:544 and :553–:765. That is 443 + 213 lines, and the text diff is empty both times. The 11 lines in those ranges that carry no indent are blank.
    • No moved line holds a multi-line string or template literal. All 8 odd-backtick lines are comments, so removing the indent changes no runtime string.
    • Each body's local helpers are consts inside it and moved with it: respondError, respondSharingError, sharingDeclaredExtra and sharingFaultMessage in the share body; respond501 and handleError in the rule body.
    • The rest of the new module is its header, its imports, the two host interfaces and the two functions' docblocks and signatures.
    • The rest-server.ts side, +33 / −657, is:
      • the two stubs;
      • the 7-line import;
      • the re-worded :111 comment.
    • The call sites this.registerSharingEndpoints(bp) and this.registerSharingRuleEndpoints(bp) are unchanged (base :4798–:4799, head :4805–:4806). So the same 8 routes register in the same order.
  2. The host seam holds exactly the members each body reads: right.

    • this. reads in the share body: config 1, routeManager 3, sharingServiceProvider 2, resolveExecCtx 3, enforceAuth 3.
    • In the rule body: routeManager 5, sharingRulesServiceProvider 2, resolveExecCtx 5, enforceAuth 5.
    • Neither body has an element access on this or any other this.
    • SharingRouteHost and SharingRuleRouteHost declare exactly those members, as indexed accesses on RestServer. Each host is an object literal of the declared type, so a missing or an extra member is a compile error. TypeScript Type Check and the four Type Check · lanes are green.
    • The module's module-level reads are imports from the same modules rest-server.ts uses. It imports only type RestServer from rest-server.ts. So there is no runtime cycle, and rest-server.ts gains no export.
  3. Getter or value, member by member: right.

    • config goes by value. It is assigned only in the constructor (head :2154) and read once, at the share body's first line, during registration.
    • routeManager goes by value. It is reassigned only by the wrapper around registerMetadataEndpointsInner (head :5719 / :5723). registerRoutes calls both sharing registrars outside that wrapper, and every register call runs synchronously during registration. So the value handed over is the one each base read saw.
    • sharingServiceProvider and sharingRulesServiceProvider go as getters. Each is read inside resolveService at request time, and both fields are private and not readonly. This is slice 2's refinement (6189011700).
    • Methods go as late-bound closures that call the method on the server.
    • One residual difference, judged harmless: the provider is now called with the host as its receiver instead of the server.
      • Its declared type is a plain function type.
      • The one shipped construction (rest-api-plugin.ts :413, :427) passes arrow functions.
      • Slices 2 and 3 landed the same shape for objectQLProvider and approvalsServiceProvider.
  4. The published surface is unchanged: right.

    • src/index.ts is untouched, and it re-exports neither sharing-routes.ts nor error-response.ts.
    • The rest-server.ts hunks add no export, public, protected or readonly (grepped over the diff).
    • Both private methods keep their name, their docblock (unchanged context lines) and their relative position.
    • The host types appear only inside method bodies, so declaration emit cannot reach them.
    • The error-response.ts edits are comments only, in a module the entry cannot reach.
    • The dev's .d.ts sha readings are theirs, not re-run here, and are what this construction predicts. The bundle's JS bytes move; its behaviour does not.
  5. scripts/check-route-envelope.mjs followed the move and was not loosened: right.

    • Counted on the moved text by the gate's own rule:
      • siblingCode 4: handleError's three mapped arms and its 500 terminal, the { code, error } literals at sharing-routes.ts :573, :579, :582 and :646;
      • stringError 0: each error initialiser is a call, never a literal;
      • neither respond501's { code, message } nor the 404 { code } carries an error key, and the share family answers through respondError.
    • rest-server.ts siblingCode goes 22 → 18 and stringError stays 17. The sums 17 + 13 + 8 + 1 + 0 = 39 and 18 + 15 + 8 + 9 + 4 = 54 are unchanged.
    • The new row is dialectOnly with ratchet #9559, like the three earlier modules. It lands with the file, which the *-routes.ts convention discovers.
    • Verdict: the Response-envelope guard step concluded success in Lint · docs & code guards.
  6. The new scripts/adr-anchors/packages__rest__src__sharing-routes.ts.json is right, and it was needed.

    • check-adr-anchors verifies ADR-NNNN Dk letters only on anchored files.
      • Measured with the gate's own regex on the head file: the moved bodies carry 8 such citations (ADR-0111 D1, D4, D5, D6, D7; ADR-0112 D4, and D5 twice).
      • The move took them out of the anchored rest-server.ts. That is the silent narrowing the card's dead-pin ⛔ forbids, and this entry puts them back under the check.
    • Both listed ids appear in the file (9 and 8 mentions).
    • Every cited letter is a decision of its record: ADR-0111's D1–D10 are headings, and ADR-0112's D4 and D5 are bold-lead paragraphs.
    • The invariant carries no letter citation by the gate's regex. It writes "its D5", never "ADR-0111 D5", so the anchor adds nothing to the count.
    • The invariant's attributions match the records:
      • ADR-0111: D2 is enforcement in the service, D4 the revoke scope, D5 the listing gate, D6 the rule surface's manage_sharing and D7 no inert grants;
      • ADR-0112: D4 is the ErrorCode union and D5 the nested code.
    • Its shape matches the existing entries. It is a registry entry of an existing gate, not a new gate.
    • Verdict: the ADR anchors + number uniqueness step concluded success in Lint · repo & tooling gates.
  7. The registrar list and its readers: nothing loosened, right. Each reader was recounted on the head:

    • execctx census:
      • resolveExecCtx mentions go from 61 + 12 + 7 + 9 = 89 to 57 + 12 + 7 + 9 + 12 = 97. rest-server.ts loses 8 sites and gains 4 forwarder mentions; sharing-routes.ts holds 8 sites and 4 interface members. The 62 sites stay pinned exact.
      • The forwarder lines are 5 (:9377, :11082, :11109, :11319, :11340), which is (5 − 1) + EXTRA_MOVED_REGISTRARS.
      • That control is tighter than its old formula. The extra count is pinned at exactly 1, a respelt forwarder still reads 4, and a module moved without joining the list still reads 6.
      • Its one blind spot: the map's key is not cross-checked against the module that really holds two host literals. A forwarder line sits in rest-server.ts and cannot name its module. A note, not a defect.
    • ui-view pair: over the list, this.enforceAuth( goes 50 → 52 and this.resolveExecCtx( 65 → 67. The floors of 40 are unchanged and the length is pinned at 5.
    • docblock pin:
      • By the test's lexical rule, sharing-routes.ts holds exactly 8 blocks: the header, two interfaces, two functions, respondError, sharingDeclaredExtra and sharingFaultMessage. Its floor sits at its actual count.
      • rest-server.ts goes 170 → 167 blocks, with MIN_BLOCKS 100 unchanged.
      • The floors' sum, 18, is pinned.
    • query-number 9, canonical-query 4, meta-draft 8:
      • sharing-routes.ts holds 0 numeric coercions, 0 query: literals and 0 draft tokens (grepped).
      • Each sum is unchanged, and each length pin reads 5.
    • dogfood blind-spot row:
      • this.routeManager.register( goes from 42 + 9 + 5 + 7 = 63 to 34 + 9 + 5 + 7 + 8 = 63, with a new per-file control of 8.
      • private register*Endpoints( reads 16 on both sides.
      • enforceAuth goes 72 → 80, exact: rest-server.ts 45 → 41, plus 12 in the module (8 sites and 4 in the seam).
      • The population is 63 − 1 + 8 = 70, with 19 reachable and 51 blind, all unchanged.
    • dogfood realtime tripwire: the moved paths (/shares, /sharing/rules) match none of the transport probes. The length is pinned at 5.

    Every list reader pins the length at 5, and the typed floor tables refuse a module with no floor.

  8. Sweep of the other path readers for a missed re-point: right.

    • At head, no rest-server.ts# pointer names a moved symbol except the spec liveness string in ③ item 1.
    • No rest-server.ts: line citation falls in the moved range.
    • The other source readers read members or blocks the moved bodies do not touch:
      • the objectql-slot census, the auth-gate fault window, execctx seam reachability and meta-list parity;
      • authz-store-unavailable, check-single-authz-resolver, check-system-context-census and check-undeclared-dep-imports.
    • The repo-wide scanners take the new module in through their own walk (both Lint lanes success).
    • The two platform-checklist anchors resolve to the exported registerSharingRuleEndpoints in sharing-routes.ts. check:platform-checklist is out of per-PR CI by maintainer decision, so this is a static read.

② Semver level

  • skip-changeset is right. Nothing any released package publishes changes.
    • For @objectstack/rest, the .d.ts, the export list and the routes hold by construction (① items 1, 2 and 4).
    • The other files are tests, QA data, one gate script and the ADR-anchor registry.
    • A move that changes no behaviour is not a feature, an improvement or a fix, so Post-Task Checklist 3 owes no patch changeset.
  • Clause-②: no is right. No accept set widens or narrows. The closed query sets travel unedited (refuseRepeatedQueryParams over object / activeOnly), no route is added or removed, and no spec key moves.
  • The changeset check's history:
    • Check Changeset concluded failure at 17:01Z, before the label.
    • skip-changeset was written at 17:08:49Z by objectstack-fleet[bot], the seat's write path, as the claim says.
    • The re-run concluded skipped at 17:08:52Z.

③ Boundary flags

Every dev flag is answered or escalated: the PR's acceptance notes, and the report's (6197732618) open_questions, deviations and out_of_scope_findings.

  1. open_questions[0]: packages/spec/liveness/crud_endpoints.json's dataPrefix evidence names rest-server.ts#registerSharingEndpoints for a dataPath line that now lives in sharing-routes.ts.

    • Answered: option B, and it is not this PR's to edit.
      • The claim puts packages/spec off limits.
      • The anchor still resolves, because the private method stays.
      • Spec property liveness is success on this head.
      • The re-point target exists: sharing-routes.ts#registerSharingEndpoints.
    • ESCALATED to the seat: add this pointer to the packages/spec set the claim's "Carried" line holds for the CRUD slice, next to slices 1–3's.
  2. docs/adr/0111-…md's Consumers line names rest-server.ts for the sharing and sharing-rule routes.

    • Answered: it is a governed surface (Tier H), so it is not a rider here.
    • ESCALATED to the maintainer through the seat, joining slice 3's ADR-0076 note. Both need one durable carrier.
  3. access-security.json's first re-pointed anchor says the record-shares family is "registered inline, under no registrar of its own". It is registered by registerSharingEndpoints.

    • Answered: re-pointing without re-judging is the move-only act.
    • ESCALATED: the seat's cross-lane declaration to the checklist owner (domain:devx) names this item for a re-judgment, as slice 3's record did for records-forms.json.
  4. Prose with no reader:

    • other-lane prose in packages/types and in plugin-sharing source and tests;
    • the error-code-ledger.zod.ts comments, which name no file.

    Answered: leaving them is right. Note only.

  5. The new ADR anchor and EXTRA_MOVED_REGISTRARS are judged right in ① items 6 and 7. The files beyond the claim's named list are each a reader of a moved line, which the claim's Readers bullet covers. Cross-lane files:

    • check-route-envelope.mjs (domain:spec);
    • access-security.json (domain:devx);
    • the dogfood pair.

    The seat's cross-lane declarations for them are owed, as for slices 1–3. That is process, not a contract defect.

  6. The dogfood pair has no PR-time verdict. Since skills(ci): two-speed CI — fast set on PR and merge-queue runs, full battery hourly on main and before a release, main-red stops the lane, four required contexts plus the guard, timing gates report-only, test budgets not deletions #22998, the dogfood shards run only in main's full battery.

    • On this head both legs skipped, and Dogfood Regression Gate concluded success on zero expected attestations.
    • The two edits are attested by the dev's local run (108 passed at 20c00f5280) and by the static recount in ① item 7, which matches every pinned number.
    • Answered: not blocking, by the repo's own CI design. The seat watches the first full battery after landing; a red there is this lane's main-red.
  7. Tier:

    • The claim promised one CONTRACT_REVIEW_TIER review for a gate-target edit, as in slices 1–3.
    • This file list derives TIER_DEFAULT, and that is the tier this record serves; it claims no higher one.
    • ESCALATED: whether this record discharges the claim's own promise is the seat's call.
  8. Freshness: origin/main has moved 6 commits past c9810dd9d1. None touches a file of this diff or packages/rest/src, and the queue builds on current main regardless.

  9. The commit trailers follow AGENTS.md's pair. Not a contract matter.

  10. Gate verdicts on this head. All 45 runs had completed when read. Taking the latest run per name: 32 success, 9 skipped (roster, full-battery-only and label re-runs) and 0 failure. They include:

    • Build Core, Test Core (1–6 and the aggregate) and TypeScript Type Check;
    • all four Type Check · lanes, Lint & Repo Gates and Lint · ESLint;
    • Governed Surface Queue Guard and Part-of PR must not also close its card.

Implemented-by: claude/issue-23050-s4-sharing-endpoints
Reviewed-by: session_01Q9PZ5Pzqcus4woVvTZwWNU

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

✅ ACCEPT: PR #23705 at 7b101d0a64 (#23050 slice 4), round 0. Every check on this head is green or a roster skip. The isolated review record is 6198400867 (PASS) · 2026-10-15T17:40Z

domain:cli seat 2 (#22648) · sunhaolin · session session_01Q9PZ5Pzqcus4woVvTZwWNU · holder of claim 6194898423. Report: the os-dev-report on #23050.

Checklist, read against GitHub and origin/main, not against the report:

  • PR shape: draft, base main, title refactor(rest):, assignee sunhaolin. Line 1 is Part of #23050, the body's only card reference: the card stays open for later slices. Line 2 is Clause-②: no.
  • Scope: 18 files, +938 / −690.
    • The move: rest-server.ts loses both sharing registrar bodies to the new sharing-routes.ts, and the class keeps two host-literal stubs.
    • The registrar list grows to 5.
    • Every reader whose reading includes a moved line is re-pointed, with its sum unchanged.
    • check-governed-merges --pr 23705: NOT governed (0 of 18 paths). git merge-tree is clean against origin/main 395ca21cc7.
  • Move-only, proven:
    • The moved bodies are byte-identical after de-indentation (443 + 213 lines). The dev diffed them, and the reviewer re-diffed them on the merge base.
    • getRoutes(), the ordered registrations and every handler's hash are 12/12 identical under 4 configurations. A positive control shows the handler hash catches a one-character edit.
    • The published .d.ts / .d.cts are byte-identical (sha256 7489de84… both sides).
    • config and routeManager go by value. Both sharing providers go as getters, measured: 501 then 200 after a post-registration assignment, and by-value ablation stays 501.
  • No reader loosened (the reviewer recounted each number):
    • check-route-envelope: rest-server.ts siblingCode 22 → 18, and the new module declares 4, so the sums stay 39 and 54;
    • the census and measurement tests move their length pins 4 → 5 with their sums unchanged;
    • EXTRA_MOVED_REGISTRARS is pinned at exactly 1;
    • the new ADR anchor (scripts/adr-anchors/packages__rest__src__sharing-routes.ts.json) restores check-adr-anchors' decision-letter reading to 1756. Without it the reading fell to 1748 while staying green.
  • Changeset: none. This seat applied skip-changeset on its own check: no .changeset file is touched, the published surface is byte-identical, and nothing new is exported.
  • Gates:
    • Local, at 7b101d0a64: 81 derived, 81 run, 0 unmeasured, all exit 0. The @objectstack/rest suite: 285 files, 5860 passed. The typecheck exits 0. The dogfood pair: 108 passed.
    • Gate evidence: the check-runs pinned to 7b101d0a64, read at this stamp. 41 distinct checks: 32 success, including Lint · ESLint, the Type Check family, all six Test Core shards, Build Core and Governed Surface Queue Guard, and 9 skipped.
      • 8 of the skips are roster names: Auto Label, Build Docs, Check PR Size, Console Pin Gate, Dogfood Regression Gate (matrix), Dogfood Verify CLI, Packed-tarball smoke, Temporal Conformance.
      • The ninth is Check Changeset, which skipped on its re-run after the skip-changeset label.
      • Nothing is running and nothing failed.
  • The review: an isolated reviewer served it at TIER_DEFAULT, record 6198400867, PASS. record-recognisers.mjs --template --paths derives that tier from this file list (no packages/spec/src/**, no governed path).
    • The claim's "one CONTRACT_REVIEW_TIER review, as slices 1–3 did" is met by an independent review at the tier the face maps to, per ruling 6159870311: the tier follows the face, exactly.

Cross-lane: declared before review on domain:spec's post (6197832077: check-route-envelope.mjs targets; the crud_endpoints.json pointer not edited) and on domain:devx's post (6197843543: the checklist anchors and the new ADR anchor). The dogfood pair is this lane's.

Accepted deviations:

Carried (「承接者」 named):

Landing: pr_ready + automerge_enable through the relay, in this act. On landing, #23050 is released to pm:queue for its next slice.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 15, 2026 17:42
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 15, 2026 17:42
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 15, 2026
Merged via the queue into main with commit 1d80f1e Oct 15, 2026
45 of 46 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-23050-s4-sharing-endpoints branch October 15, 2026 18:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xl skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants