') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); fix(rest): `/meta/:type/:name/history` and `/diff` state the org partition they read (#13406) by os-steve · Pull Request #13756 · objectstack-ai/objectstack · GitHub
Skip to content

fix(rest): /meta/:type/:name/history and /diff state the org partition they read (#13406) - #13756

Merged
os-steve merged 7 commits into
mainfrom
claude/issue-13406-meta-read-doors-forward-org
Aug 31, 2026
Merged

fix(rest): /meta/:type/:name/history and /diff state the org partition they read (#13406)#13756
os-steve merged 7 commits into
mainfrom
claude/issue-13406-meta-read-doors-forward-org

Conversation

@os-steve

@os-steveos-steve commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Fixes#13406

GET /api/v1/meta/:type/:name/history answered { events: [] }, and GET /api/v1/meta/:type/:name/diff answered an all-empty diff, for metadata whose overlay was authored org-scoped — while sys_metadata_history held the full log. Both doors named no organization.

sys_metadata_history is a per-org table. SysMetadataRepository.history() and diffMetaItem both filter organization_id by strict equality (no $or), and event_seq is declared on the object as a "Per-organization monotonic event log cursor". So a door that states no organization does not read "everything" — it reads the env partition (request.organizationId ?? null). The write door has stated the org since #8805; only these two read doors had not.

Direction is fail-closed: the caller's OWN org data was under-served. There is no cross-org read, and the controls below keep it that way.

Call-side only.packages/spec and protocol.ts are untouched — organizationId was already declared on the request contract, and request.organizationId ?? null is the legitimate expression of env scope that every correct caller depends on.

Generated by Claude Code · session session_01UngCYXF98BVpYA9hfz6NYk


⭐ The escalation gate, answered FIRST — p2 stands, no regrade

Triage made this the executor's first mandatory item: if any of these doors is a compliance / audit evidence surface, "an org cannot see its own history" stops being a functional gap. Measured answer: no. Three independent in-repo classifications agree, and all name the audit table, never the history table:

  1. ADR-0010 §3.6, explicitly: "The audit table is independent of sys_metadata_overlay and sys_metadata_history (ADR-0008) — the latter store state, the former stores intent and provenance. Compliance reports read from the audit table." §8 repeats it: the compliance-grade trail is the audit one.
  2. ADR-0057 §3.1 classifies the audit / compliance-ledger lifecycle class as sys_audit_log and sys_metadata_audit. sys_metadata_history appears nowhere in that ADR.
  3. The object declarations, which is the mechanical half: sys-metadata-audit.object.ts carries lifecycle: { class: 'audit' }; sys-metadata-history.object.ts carries no lifecycle block at all and describes itself as a "Durable event log of metadata overlay changes (per-org, append-only)".

And the compliance-evidence door in this family — /auditalready forwards the org; it was scoped in #8747. So the surface the gate protects is not one of the affected doors.

⇒ Grade proposal: p2 unchanged, bug, no security label. Stated rather than chosen silently.

⭐ Premise re-measurement — symptom 3 was already delivered (a good run, not a scope cut)

The card names three doors. On origin/main today, two were open. The third — "single-item GET /meta/dashboard/:name ignores an org-scoped overlay" — is already correct, threaded by #9454 / #9727 before this branch. The uncached arm that dashboard takes (isDashboardType bypasses the cache) carries readOrganizationId, hoisted above the fork precisely so the two arms cannot disagree about scope, and dashboard is allowOrgOverride: true so the registry predicate returns the tenant.

Rather than assert that in a report, this branch pins the disputed fact next to the two doors that were genuinely open, so the falsification is auditable: the card's third symptom, RE-MEASURED on today's mainsingle-item dashboard read ALREADY serves the org overlay. It authors a real org-scoped overlay, asserts the persisted row carries organization_id, then asserts the door serves it.

The card's line numbers were all dead too (#13521 de-cast the history door after triage wrote its note). Everything here was located by symbol.

⭐ The design decision: organizationIdForMetaRead, not /audit's expression

The card calls /audit "zero-tradeoff prior art". It is prior art for the intent; copying its expression is wrong twice, and both halves are measured rather than argued.

1. The predicate.auditMetaItem reads with $or: [{organization_id: org}, {organization_id: null}] — a union, so a raw ctx?.tenantId there can only add rows. These two doors read with strict equality. Under strict equality a raw tenant id asks the org partition for the history of every allowOrgOverride: false type that is still runtime-writable — object, hook, page, app, dataset — because organizationIdForMetaWrite deliberately lands those env-wide under the #6190 ruling. That answers { events: [] } for them: this card's own defect, newly minted one type family over. Gating the read on the same registry predicate the write uses is what keeps the two sides incapable of drifting.

2. The spelling.HistoryMetaItemRequestSchema declares organizationId: z.string().optional() — optional plain string, not nullable, mirroring the implementation's organizationId?: string. The key is therefore spread, never organizationId: x ?? null.

Both were confirmed by ablation, with the direction predicted before running:

AblationPredictionMeasured
A — swap the registry predicate for a raw ctx?.tenantId on the history doorexactly one control reddens: serves a NON-overridable type's env-wide history to an org session; every org-overridable case stays green1 failed / 19 passed, and the failure is that control and only that control
B — write ?? null on the history door instead of the omit-spreadtsc --noEmit reds naming organizationId and null; TS2322 (null not assignable to string | undefined), not TS2353 — the latter is the code for an undeclared member, and this member is declaredsrc/rest-server.ts(6238,25): error TS2322: Type 'string | null' is not assignable to type 'string | undefined'. — 1 error, exactly as predicted, code included

Both legs were run from a committed implementation and restored with git checkout HEAD -- against the target's ABSOLUTE path under an EXIT INT TERM trap; each mutation was proven on disk by injected/deleted occurrence counts plus a git hash-object comparison against the HEAD blob, and each restore proven the same way plus an empty git diff HEAD. Neither leg needed a rebuild: the pins import ./rest-server.js inside their own package, so the mutation reaches source directly — and ablation A changing exactly one verdict is itself the proof that it did.

⚠️ Worth recording for the next author: the diff door reaches diffMetaItem through (p as any), so the compiler checks nothing about that literal. There, ?? null type-checks and is a silent runtime no-op (null ?? null is null) — a fix-shaped non-fix. The two doors are not identical and were measured separately.

⭐ The mandatory answer, BY ENUMERATION

Every meta read door in rest-server.ts, against /audit's parameter passing. Located by symbol; line numbers omitted deliberately because this file changed three times in a day.

Door (GET)Forwards the caller's org?How / why
/meta/typesn/aregistry type listing; reads no metadata document
/meta/diagnosticsNO — real gapgetMetaDiagnostics declares organizationId and passes it into getMetaItems; the door supplies none. Out of scope → #13753
/meta/_draftsorganizationId: ctx?.tenantId ?? undefined
/meta/:type (list)organizationIdForMetaRead(canonicalMetaUrlType(type), ctx?.tenantId) (#9454)
/meta/:type/:name/referencesNO — real gapfindReferencesToMeta declares organizationId and spreads it into getMetaItems; the door supplies none. Out of scope → #13753
/meta/:type/:name/layerssame registry-gated predicate (#9454)
/meta/book/:name/tree✅ correctly env-widebook and doc are allowOrgOverride: false; naming an org would resurrect #6190's phantom rows
/meta/:type/:name (single item)both arms, hoisted above the cache fork (#9454) — the card's symptom 3, already delivered
/meta/:type/:name/history❌ → ✅ this PRnow organizationIdForMetaRead, omit-spread
/meta/:type/:name/auditorganizationId: ctx?.tenantId ?? null (#8747)
/meta/:type/:name/diff❌ → ✅ this PRnow organizationIdForMetaRead, omit-spread
/meta/object/:name/state/:field✅ correctly env-wideobject is allowOrgOverride: false
/meta/:type/:name/published...(ctx?.tenantId ? { organizationId: ctx.tenantId } : {})

So the enumeration found two doors beyond this card's scope with the identical omission. They are filed unassigned as #13753, not fixed here. The references half is the sharper one: it backs the admin "Used by" panel whose empty case reads "Nothing in the metadata graph points at this item. Safe to delete.", shown to an operator about to delete — the exact false-negative class that door's own 501 refusal was added to prevent.

Tests — positive controls, not just green

packages/rest/src/rest-server-meta-history-diff-org-scope.test.ts drives real REST routes against a real ObjectStackProtocolImplementation over a stub engine, one boot, write-then-read round trips.

The load-bearing difference from the sibling rest-server-meta-read-org-scope.test.ts: its stub returns every sys_metadata_history row unfiltered, so over that engine both doors pass with or without this change — there is no partition to miss. This harness honours the where, organization_id included.

Every read assertion is gated on a fixture proof first (historyRowsFor), because "the read is org-scoped" is worthless if the fixture never created an org-scoped row — the card's own repro bar was "confirm the pg rows exist before hitting the read door":

  • fixture first — for all five allowOrgOverride: true types, a PUT under an active org appends exactly 1 history row in the org partition and 0 env-wide. Both halves asserted: the second is why an org-blind door missed them.
  • /history — two authored revisions come back as two events, version[1, 2].
  • /diff?from=1&to=2 — resolves both org revisions and reports the real change ({ path: 'label', from, to }), not the card's echoed-bounds/empty-buckets shape.
  • the env-scoped control — the same harness still serves env-scoped rows to an env-scoped caller: an org-less session writes and reads its own history unchanged.
  • the registry control (ablation A's target) — an object write under an active org lands env-wide, and the org session still reads it.
  • cross-tenant controls — org B is served neither org A's change log nor a diff of its revisions; an org-less caller is served neither.

Commands, all at 0df058f2 (the implementation commit; see the patch round below for d73a967d)

CommandVerdict line
pnpm --filter @objectstack/rest testTest Files 164 passed (164) · Tests 2760 passed (2760)
pnpm --filter @objectstack/rest typecheckexit 0 — check:test-typecheck: OK — @objectstack/rest's test layer compiles under packages/rest/tsconfig.test.json
pnpm --filter @objectstack/dogfood exec vitest run --shard=1/3Test Files 43 passed (43) · Tests 313 passed (313)
pnpm --filter @objectstack/dogfood exec vitest run --shard=2/3Test Files 43 passed (43) · Tests 288 passed | 1 skipped (289)
pnpm --filter @objectstack/dogfood exec vitest run --shard=3/3Test Files 42 passed | 1 skipped (43) · Tests 379 passed | 2 skipped (381)
pnpm lint (repo-wide, eslint . --no-inline-config)exit 0, no findings — run in full, not narrowed
node scripts/check-system-context-census.mjsOK — 109 elevation read sites in 20 packages across 45 files, all anchored; 145 anchors resolve, 27 declared non-read
pnpm check:engine-double-contractOK — 727 pinned, 134 in the DEBT ledger, 3 exempt
pnpm check:where-matcher320 matcher(s) discovered, 320 answer the combinator battery correctly or refuse it loudly (201 refuse)
pnpm check:type-check-debtOK — 29 ledger entr(ies) re-measured in 255.0s, 1542 raw tsc error(s) total, none above its recorded number
pnpm check:authz-resolver · check:route-envelope · check:nul-bytes · check:cross-package-test-inputs · check:query-options-erasure · check:type-check-coverage · check:dispatcher-error-vocabulary · check:test-source-aliasall exit 0

The typecheck really does cover the new test file, rather than excluding it: tsc -p tsconfig.test.json --listFiles counts it 1, and the package's main program counts it 0.

Not measured, reported separately from the passespnpm --filter '@objectstack/rest^...' build fails on @objectstack/verify, and it is a filter artefact, not a defect in this diff: @objectstack/verify depends on @objectstack/rest, and the ^ excludes rest itself from its own dependency closure, so verify's dts build can never resolve it under that spelling. Building as CI does — turbo run build --filter='./packages/*' --filter='./packages/*/*' — is 70/70 tasks successful. The first dogfood attempt on the unbuilt tree was PREREQUISITE NOT MET (Failed to resolve entry for package "@objectstack/runtime"), not a red; the shards above are the runs on the built tree.

Ratchets moved, and why

  • execctx-consumer-census.test.ts — two new resolveExecCtx sites, both locally caught on the continuation line: 73 → 75 sites, 92 → 95 mentions (+2 calls and +1 prose mention — the two numbers move by different amounts on purpose), 20 → 22 caught, 16 same-line and 53 bare unchanged. Counts re-derived from the tree, not hand-edited.
  • content/docs/permissions/system-context.mdx — re-anchored by regeneration (check-system-context-census.mjs --fix), never by hand, twice: once for the implementation (:6382:6432, :6575:6625) and again for the comment-only patch round below (:6432:6450, :6625:6643). Each run rewrote exactly 2 anchors, matching the 2 it flagged — so unlike the sibling that found 13 silently stale extras, there was no hidden staleness in either pass.
  • scripts/engine-double-contract.pinned.json — 3 new pinned rows for the new file's double (delete, findOne, update), all routed through the assertEngine*Dispatch helpers; registered with --write as the gate's own message prescribes, and the DEBT baseline did not grow.
  • The new stub double refuses any WHERE combinator other than $or rather than reading it as a field name — check:where-matcher executes an $and battery against every discovered matcher, and refusing loudly is the convention 201 of the 320 already follow.

Patch round — d73a967d, comment-only

PM review caught a real defect in the written record: two comments I landed named TS2353 where the measured code is TS2322.

organizationIdis declared on HistoryMetaItemRequestSchema, so ?? null there is an assignability failure (Type 'string | null' is not assignable to type 'string | undefined'), not an unknown-property one. TS2353 is the undeclared-member code. My own ablation leg B predicted TS2322, reasoned about why TS2353 would be wrong, and measured TS2322 — and then the comments said TS2353 anyway.

Mechanism, named in the fix so the next reader does not repeat it: the pre-existing paragraph nine lines above the history door's org comment correctly says "an undeclared member here is now TS2353" — right in its context, which is about undeclared members. Mine sits directly beneath it and describes a declared member. Comment drift by adjacency.

Both sites now say TS2322, and the test-file header keeps the two doors explicitly apart, because this is the half that matters most: /history reddens with TS2322; /diff reddens with NOTHING. The diff door reaches diffMetaItem through (p as any), so ?? null type-checks there and is a silent runtime no-op — the guard is weakest exactly where "the compiler catches this" is easiest to assume. A reader who trusted "TS2353" and went looking for unknown-property behaviour would have concluded the opposite.

Re-run on d73a967d — scoped to what a comment edit can actually disturb, deliberately not the whole union:

CommandVerdict
pnpm lint (repo-wide)exit 0, no findings
node scripts/check-system-context-census.mjsrotted as predicted (the added comment lines shifted rest-server.ts), re-anchored by --fix, then OK — 145 anchors resolve
pnpm check:doc-authoringexit 0
pnpm check:doc-anchorsexit 0
pnpm check:nul-bytesexit 0
execctx-consumer-census counts, re-derived from the patched file75 / 95 / 22 / 16 / 53all five unchanged, because the new prose contains no resolveExecCtx occurrence. Measured, not assumed: a mention count is exactly the ratchet prose can move

Deliberately NOT re-run, and why: the @objectstack/rest suite, the three dogfood shards, check:type-check-debt, and the rest of the path-derived union. The diff is comment-only — verified mechanically, every changed line in both TS files begins with // — so no test behaviour, no type, and no gate population changes. The one gate a comment edit did disturb (the census, via line-number shift) was caught and repaired.

⚠️ Platform reading taken on this PR: a body PATCH DELETES the attribution footer outright

Recorded because the standing note says a PATCH merely downgrades the session-URL footer to the bare form, and that the bare form then survives. Measured here, two trials on this body:

  • CREATE kept it, in session-URL form — read back verbatim.
  • PATCH (the update carrying this section) removed the entire trailing block — both the --- rule and the _Generated by …_ line. Read back: _Generated by occurrences 0, claude.ai/code occurrences 0. Not a downgrade; a deletion.

Mitigation, and it held: the session id was deliberately also written into prose before that PATCH, and it survived at 1 occurrence. The footer above has been moved out of the trailing position into the header for the same reason. ⇒ Never rely on a trailing footer for attribution across an edited body; put the session id in the body text.

Reading requested by review — the sibling harness, measured

Does rest-server-meta-read-org-scope.test.ts assert anything about org scoping of /history or /diff today? No. Its drive helper exposes exactly three routes and its tests reach no others:

  • PUT /meta/:type/:name
  • GET /meta/:type/:name
  • GET /meta/:type

Every occurrence of "history" in that file is stub-engine plumbing for sys_metadata_history (the table SysMetadataRepository.put() appends to during the PUT — without it the write throws). No /history or /diff route is registered, driven, or asserted on. ⇒ No vacuous claim exists there today. Its name is broader than its content, but every assertion it makes is about the single-item and list read doors, and its stub genuinely keeps all of them.

Is the stub nonetheless a trap for anyone who later adds one? Yes — and on both seams:

asyncfindOne(table,opts){assertEngineFindOnePredicate(table,opts);if(table==='sys_metadata_history')returnnull;// where DISCARDED
...
asyncfind(table,opts){if(table==='sys_metadata_history')returnhistoryRows;// where DISCARDED

Both discard opts.where for that table. historyMetaItem (via repo.history()) and diffMetaItem both filter organization_id by strict equality, so over this stub that filter is a no-op: every history row comes back whichever partition was asked for. An org-scoping assertion for /history or /diff added to that file would therefore pass with or without the org being forwarded — vacuously green. That is precisely why the new file carries its own partitioned stub rather than extending this one.

What this does not do

⛔ No packages/spec edit is owed and none is made. The spec anticipated this card by name: HistoryMetaItemRequestSchema's describe text already records that the door "currently sends no organization at all (whether it should is a tenant-scoping question measured separately for that door — declaring the member records the implementation contract, it does not answer that question)", and protocol.test.ts calls it "the #8747-family measurement the card fences to a future issue". This is that issue. Clause-② is no.

protocol.ts is unmodified. ⛔ #13753 is not addressed here and remains open.

@github-actions

github-actionsBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️1 changed file(s) yielded no anchor (packages/rest/src/rest-server.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/rest/src/rest-server.ts) — pages documenting those are invisible to this run
  • 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 — 13 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 c42bc8ee68833297f0273f14b0f6f6e9357ab293packageMentionDocs.

@os-steveClaude

Copy link
Copy Markdown
CollaboratorAuthor

Merge ordering — this PR goes SECOND, behind #13763

Both PRs touch content/docs/permissions/system-context.mdx. Ruling ① makes same-file hard-serial with no region exemption, and #13763 (priority:p1) goes first. Recorded here rather than in the body so the note is additive and the reviewed text is untouched.

⭐ The part that matters for whoever merges this one: that file is NOT text-merged

.gitattributes:109 registers it merge=os-regen, and the driver (merge.os-regen.driverscripts/git-merge-regen.mjs) defers instead of merging. Measured with a simulated merge that touches no worktree — git merge-tree --write-tree against #13763's head exits 0 for the tree, and emits for this file:

⟳ content/docs/permissions/system-context.mdx
not text-merged — it is generated. Regenerate from the merged tree:
pnpm gen:system-context-census
The pre-commit hook will not let this commit through until you do.

⇒ After #13763 lands, do not hand-resolve this file and do not reason about line ranges. The sequence is:

  1. Merge main, and commit the merge first. Regenerating while the merge is still uncommitted reads the pre-merge tip and silently rolls the anchors back to the old fork point — a well-formed, gate-green, wrong answer.
  2. Run pnpm gen:system-context-census (which ischeck-system-context-census.mjs --fix).
  3. Compare the rewrite count against what the plain check flagged beforehand.content/docs/permissions/system-context.mdx is a generated anchor table that 12 of the last 20 main commits rewrite — and it is the one such artifact with no merge=os-regen routing #13646 measured this exact file regenerating 18 anchors when only 5 had genuinely conflicted — 13 silently restated with no marker. Run the check first, record the flagged count, then regenerate, then compare; stop and report if regeneration touched more than was flagged.

That third step is why "regenerate with --fix" and "don't blindly re-run --fix" are not in conflict: the driver requires the regeneration, and the discipline is on the word blindly.

The two PRs are independent in both dimensions anyway

#13763this PR
mdx lines changed112–122, 182, 198161
source files its anchors citeobjectql/src/engine.ts (12), readonly-strict-errors.ts (1)rest-server.ts (:6450, :6643)

Neither PR's source edit shifts the file the other cites, so neither invalidates the other's anchor values. The collision is nominal — same artifact, disjoint content. (Note the cited files: not http-dispatcher.ts, and never rest-server.ts.)

State at ddcf40f195

origin/main merged — 8 commits, no conflicts, merge committed before anything else ran. #13763 had not landed at that point, and system-context.mdx had not moved on main since this branch's base, so there was nothing for the driver to reconcile.

The census check was re-run without--fix, deliberately: green, 145 anchors resolve. The 2 anchors did not move. They stand at :6450 and :6643 — the values after this PR's comment-only patch round, which advanced them from the :6432/:6625 an earlier note cites.

Re-verified on the merged head, since the merge pulled in 38 files of other people's work:

  • Gate union re-derived on ddcf40f195: 53 → 62 families, 9 newly named, none dropped. All nine run greencheck:corpus-claim-drift (new, arrived in this merge), check:merge-driver, check:entry-guard, check:parse-guard, check:bash32-floor, check:pnpm-filter-targets, check:cli-command-ids, check:agent-test-spelling, check:watch-hint-literal.
  • Ratchet family green: census, check:engine-double-contract, check:where-matcher ("baseline key set verified against adf4bf4: no files added"), check:nul-bytes (7579 files), check:cross-package-test-inputs.
  • execctx-consumer-census counts re-derived from the merged tree, not assumed: 75 / 95 / 22 / 16 / 53 — all five still at their pins.
  • pnpm lint repo-wide on the merged tree: exit 0, no findings.

Not re-run, and named as such: the @objectstack/rest suite, the three dogfood shards, and check:type-check-debt. This branch's own diff is unchanged since 0df058f2 — the merge added no line to any file it touches — and CI runs those on the merged head regardless.

⛔ Still draft. Nothing flipped ready, nothing armed.


Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

2 participants

@os-steve@claude