') + ')', '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(devx): route the elevation census page to os-regen, and pin the CI gate that is the real guard by claude[bot] · Pull Request #13732 · objectstack-ai/objectstack · GitHub
Skip to content

fix(devx): route the elevation census page to os-regen, and pin the CI gate that is the real guard - #13732

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-13646-census-anchor-regen-guard
Aug 31, 2026
Merged

fix(devx): route the elevation census page to os-regen, and pin the CI gate that is the real guard#13732
os-project-manager merged 2 commits into
mainfrom
claude/issue-13646-census-anchor-regen-guard

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes#13646

Both halves ship. But the load-bearing half turned out to already exist, and the
measurement that shows it also changes what the merge-driver half is allowed to be.

Verified on d32b0f501. Diff is 4 files, +131/−1, and zero lines of
.github/workflows/lint.yml
— see "No workflow edit" below.

⭐ Zone 2 falsified — and so is Zone 1's premise for half 1

Triage ruled that the load-bearing half is "一道「重生成并 diff」的 CI 检查", on the
grounds that it is the only instrument reaching the 13 anchors that went stale with no
conflict marker. That instrument is already in the tree, already required, and already
fires on exactly that case.

scripts/check-system-context-census.mjs re-derives the census from the tree with an AST
walk and compares it to the committed page — its POPULATION check requires every read site
the census finds to be anchored at its exact file:line. It runs in
.github/workflows/lint.yml as the job lint, whose name: is the required status
context Lint & Repo Gates (node scripts/check-required-contexts.mjs), on
pull_request + merge_group + push:main, with no paths: filter.

Reproducing the card's own silent case on cc837dbfec — shift
plugin-sharing/src/sharing-service.ts down 29 lines (main's real delta in the #13625
window) and leave the page untouched, which is precisely the
branch-never-touched-that-file shape git merges clean:

gate on the shifted tree exit 1, 16 findings
[site-without-a-row] sharing-service.ts:683 / :949 / :1036 / :1237 / :1315 / :1367 / :1626
[anchor-is-not-a-read-site] the page still anchors :654 / :920 / :1007 / :1338 / :1597
[ledger-row-unused] NON_READ_ANCHORS row 34
`--fix` then the gate 109 sites re-anchored, exit 0

Those are the card's own +29 values (625→654, 891→920, 978→1007, 1568→1597,
1309→1338) — the gate names every one of them. ⇒ The silent 13 are not silent to CI.

What was actually missing: nothing asserted the gate is scheduled

check-self-test-wired is conditional in the wrong direction for this — it requires that
a script CI runs also has its --self-test run. Delete both invocations from
lint.yml and it goes green, silently retiring the only instrument that catches a stale
anchor. Ablated, with the mutation confirmed on disk (2 invocations → 0):

--self-test on the ablated tree exit 1 — both WIRING cases FAIL
production run, same tree exit 0 — GREEN

The production run cannot see its own descheduling, which is exactly why the assertion
belongs in --self-test. Added there, following the precedent check-doc-frontmatter,
check-aggregator-roster and check-ci-filter-parity set. Self-test now 37 cases, green.

No workflow edit — deliberately

lint.yml already invokes both legs, so the pin reads the workflow instead of adding
to it. That is the same move check-doc-frontmatter records for its second docs root
("lint.yml is the repo's busiest file and a second step would have been a third
concurrent edit of it"). ⇒ This PR does not touch lint.yml at all, so it does not
contend with #13584 and does not consume the slot #13582 is held for.

Half 2 — the merge driver route, scoped to the FILE

Registered in scripts/regen-artifacts.mjs (the single source of truth) plus
.gitattributes, with gen:/check: in the root manifest. This is the first
production row owned by ROOT tooling
— it exercises the owner field #13585 added,
which until now only synthetic self-test cases touched:

✓ .gitattributes ↔ regen-artifacts.mjs agree on 14 path(s)
✓ all 28 gen:/check: names resolve in their declared owner
(@objectstack/spec (packages/spec/package.json), @objectstack/spec-monorepo (package.json))

⚠️A2.3 is falsified as written. The card and the dispatch both proposed routing
content/docs/permissions/**. That glob is not safe: unlike the routed sibling
content/docs/references/**, which is generated whole, content/docs/permissions/ is
22 hand-written prose pages around exactly one generated page. The directory route
would defer 21 prose files to OURS and drop the other side's edits silently — the exact
trade packages/spec/src/migrations/registry.ts is kept out of the table for. Routed as
the file; the glob is recorded in NOT_DRIVER_MANAGED so the next author meets the
reasoning instead of the omission.

content/docs/permissions/system-context.mdx: merge: os-regen
content/docs/permissions/authorization.mdx: merge: unspecified
content/docs/permissions/index.mdx: merge: unspecified
content/docs/references/index.mdx: merge: os-regen (control)

The header's own warning, answered rather than accepted

os-regen-merge.sh warns that a routed path can "merge with exit 0 and zero conflict
markers while SILENTLY DROPPING one side's changes". Here something still reddens: the
census gate above, on every PR, from a required job, re-deriving from the tree rather than
reading the page back. The driver removes hand-merge rounds; it is never the only signal.
⇒ Triage's ordering constraint ("第 1 项未落地前 ⛔ 不得合入第 2 项") is satisfied — the
CI instrument is in place before the routing, because it was already there.

Nor can the gen leg launder a population change. Injecting a new elevation read site
and running --fix:

exit 1, 0 anchors rewritten, page left byte-identical
::error::[declared-count] `ruling-sites` says 109, the census says 110

--fix re-anchors a pure shift and REFUSES when a site arrived or vanished, so the
pre-commit gate — which spawns check, never gen — stays red until a human writes the row.

A2.1 cost and A2.2 determinism — both hold

measurementresult
A2.1 gate on a clean tree3.0s (node scripts/check-system-context-census.mjs)
--fix round trip~3s
driver self-test0.75s
A2.2--fix on a clean treestrict no-op: 0 anchors rewritten, page byte-identical
A2.2 two independent runs from a shifted treebyte-identical, 31633 bytes
A2.2 second --fix0 rewritten (idempotent)
date / clock embeddingnone — zero new Date() / toISOString in the script

⇒ A2.1 holds (3s is not "minutes"; it was already affordable enough to be running). A2.2
holds — the output is a pure function of the tree, so the check cannot red on itself.

Scope question the card raised: which other generated artifacts are outside the routing?

Measured across all 79 manifests: 21 generator-ish scripts (gen:*, or a --fix /
--update mode), of which 10 are named as the gen of a driver-managed row. The
remaining 11 are the open set; several are already deliberately excluded
(gen:migration-registry, gen:authorable-surface-base, and the gen:test-typecheck-debt
family are all shrink-only or MIXED by declaration). The genuinely unaccounted candidates
are gen:sdui-lockstep, gen:openapi, gen:skill-refs, gen:skill-docs, gen:sbom,
gen:react-blocks, and the per-package test-typecheck-debt.json copies outside
packages/spec. ⛔ Not fixed here — one card, one route. Filed unassigned as #13731,
which also links #13335 (gen:skill-refs) as a previously recorded member of the
same class rather than a duplicate of it.

Verification

Gate family derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
(no stale-tree warning) on d32b0f501; all 18 run.

  • 16 green, including check:merge-driver, check:entry-guard, check:pm-dispatch-gates,
    check:turbo-task-graph, check:pnpm-filter-targets, check:agent-test-spelling,
    check:cross-package-test-inputs, check:parse-guard, and the census gate itself.
  • Also green beyond the derived list, because this PR adds a root manifest key:
    check-self-test-wired, check-self-test-workflow-commands, check-aggregator-roster,
    check-required-contexts, check:filter-alias-parity, check:nul-bytes, check:doc-anchors
    (self-test + production leg each).
  • 2 NOT MEASURED, neither a finding — both exit 3, the code these gates reserve for
    a refusal: check-test-completeness ("there is no local log to hand it … ⛔ It is not a
    red"), and check:dual-build-cjs-loads ("PREREQUISITE NOT MET — this gate reads built
    output"; it needs a full pnpm build of 100+ packages). Declared narrowing: this PR's
    root-manifest edit adds two keys to the scripts map of a private: true non-workspace
    manifest that has no dist/ and no exports either gate reads, so neither can move.
    CI runs both on a built tree.
  • Lint, narrowed with its evidence: 0 errors, 0 warnings over the 2 lintable changed
    files. (1) The population comes from eslint's own config, which states and measures with
    a positive control that this repo "never enables type-aware linting … for ANY file" — so
    no untouched file's verdict can depend on this diff. (2) File count read from
    --format json: 2. (3) Invariance follows from (1). .gitattributes and package.json
    are not eslint inputs.
  • Control-byte self-scan over all four edited files: clean.

Changeset

skip-changeset. This PR publishes nothing: zero packages/**, and the only manifest
touched is the root, which is private: true and not a workspace member. An empty
changeset is not the alternative here — check-empty-changeset rejects newly added ones
in this repo, and its own ledger records that the label is strictly safer.

Draft for the domain:devx seat to arm after review.

Generated by Claude Code


Generated by Claude Code

@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/m dependencies Pull requests that update a dependency file labels Aug 31, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 31, 2026 09:05
@os-project-manager
os-project-manager added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 1221712Aug 31, 2026
37 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-13646-census-anchor-regen-guard branch August 31, 2026 09:32
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependenciesPull requests that update a dependency filesize/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-project-manager@claude