Repository navigation
chore(scripts): ten ratchet baselines become one-sided — a decrease passes and is reported - #26549
Conversation
… decrease and report it
…d holds Claude-Session: https://claude.ai/code/session_01ADHkmPe3iuugZk1bwH3GNV Co-authored-by: Claude <noreply@anthropic.com>
席位核对 · REVISE · devx seat 2 · 2026-10-27T07:49ZSeat The shape is right and matches Wanted 3. Each of the ten readers now returns One must-fix: a dead scan must stay red. The card keeps anti-vacuity guards ("该留"), but going one-sided removes one that some readers relied on. Your own comment in
Out of scope, noted for the seat: CI on this head was still running when I read it. I will read it again on the patched head. |
Two-sided, a scan that lost part of its population dropped baselined entries out of the measurement and the DOWN direction redded. One-sided, that drop reads as a passing decrease, so it is refused explicitly: - role-word, corpus-claim-drift: a baselined page still on disk that the walk did not read is refused. - slot-lookup, query-options-erasure: a baselined file still on disk that the rule did not run on (not linted, or its ignore not lifted) is refused; query-options also refuses its test surface collapsing to 0 under a non-zero ceiling. - i18n-coverage: a baselined config on disk that was not measured, and every measured config falling to 0 at once (blind classifier), are refused. - optional-error-sink: no file read, no sink found, or a ledgered file on disk the walk never reached, are refused. - objectql-double-limit, both durability ledgers: a ledgered file on disk the walk never read is refused. Each refusal has self-test cases, an empty population among them. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ADHkmPe3iuugZk1bwH3GNV
席位核对 · ACCEPT (patch round 1) · devx seat 2 · 2026-10-27T09:12ZSeat The must-fix is met: a dead scan stays red. The mechanism is the same in each reader. A baselined entry that is still on disk but that this run did not read or measure is refused. A deleted file is not on disk and a swept file was read, so both still pass as decreases. No floor was added.
Every new refusal has self-test cases, including the empty population. The PR body's "Dead scans stay red" table lists 9 dead-scan runs, each exiting 1 or 2. The DOWN/UP acceptance rerun shows deletions still exit 0 and increases still exit 1. Derived gates: 41/41. Accepted trade-off, stated in the PR: two narrow cases red a legitimate final sweep until CI on |
Requested by Steve · project thread
Part of #26444
Part of #26431
Clause-②: no
Metadata-protocol: none
The devx half of #26444 only. The ui floors are #26521 and the CLI floors are #26523, so this PR does not close the card.
维护者速读
--update才能过 CI,跟这个 PR 的工作无关;卡 test(floors): floors set at today's measured value and two-sided baselines red on every legitimate deletion — ui package counts in ~8 places, CLI command counts, catalog entries pinned 4×, slot-lookup-class baselines that fail on a decrease #26444 的 Wanted 3。--update,旧数仍是上限,可以悄悄补回同样多的违例;每条报告都写明了余量。回滚就是 revert 本 PR,没有数据或接线变化。scripts/check-query-options-erasure-ratchet.mjs(唯一带聚合上限的一本)、scripts/check-i18n-coverage.mjs(比较逻辑抽成纯函数)、scripts/check-durability-degradation-log-level.mjs(两本账共用的 reconcileLedger)。rm content/docs/api/error-catalog.mdx && node scripts/check-role-word.mjs; echo $?; git checkout HEAD -- content/docs/api/error-catalog.mdx,应看到1 baselined file(s) improved (passing, reported),退出码 0。What changes
Each reader keeps its error path for the increase direction and gains a pure, separately-named decrease report:
check-slot-lookup-ratchet.mjsslot-lookup-baseline.jsonratchetDownReport()check-query-options-erasure-ratchet.mjsquery-options-erasure-baseline.json(per-file +testSurfaceaggregate)ratchetDownReport()(exported besidediffRatchet)check-role-word.mjsrole-word-baseline.jsonratchetVerdict().downcheck-i18n-coverage.mjsi18n-coverage-baseline.jsonratchetVerdict().down(comparison lifted out of the top level)check-corpus-claim-drift.mjscorpus-claim-drift-baseline.jsonratchetVerdict().downcheck-objectql-double-limit.mjsobjectql-double-limit.baseline.jsonreconcileDown()check-where-matcher-conformance.mjswhere-matcher-conformance.baseline.jsonreconcileDown()check-optional-error-sink-contract.mjsoptional-error-sink-contract.baseline.jsonreconcileLedger().stalecheck-durability-degradation-log-level.mjsdurability-degradation.baseline.json,durability-read-invention.baseline.jsonreconcileLedger().stale(shared by both rules)check-published-readme-exports.mjspublished-readme-exports.baseline.jsonEvery decrease line names its headroom: the old number stays the ceiling, or the stale key keeps licensing that site, until
--update(or the hand edit, for the two gates with no--update) records it.--updateis unchanged and remains the way to write the report down.Kept as errors on purpose:
VOCABULARYno longer holds. That is an unfinished retirement, not a decrease.The
$commentheaders of the seven baselines that described two-sided reconciliation now describe the one-sided one. Only$commenttext changed; no counts or keys moved.Premise, measured on
origin/mainb7f2b0c before the changeAll ten readers were two-sided, and none was already one-sided. A real deletion of a listed file, run on the base tree, went red:
content/docs/api/error-catalog.mdxbaselined file is clean/gone … ratchet DOWN)content/docs/protocol/kernel/http-protocol.mdximproved 1 → 0 … ratchet DOWN)packages/metadata-protocol/src/protocol-publish-drafts-advisories.test.tsclean or gone -- ratchet DOWN)packages/services/service-settings/src/settings-service.types.tsstale entry … shrink-only)clean or gone+ADDED)a fall must be ratcheted down/a cleaned file must be droppedassert 1 errordiffRatchetAcceptance: deleting a listed file reds no ratchet; a new violation still reds
Run on head
60e74485, after the dead-scan round. The first run, onc4087da0, gave the same exits. Each mutation is applied, the gate runs, then the tree is restored withgit checkout HEAD --plus removal of new files, and the restore is checked withgit status(clean every time).error-catalog.mdxremoved → 0, reportedhttp-protocol.mdxremoved → 0, reportedprotocol-publish-drafts-advisories.test.tsremoved → 0, reportedsettings-service.types.tsremoved → 0, stale reportedinterface { error?: … }→ 1syncSchemaloggingwarn→ 1packages/core/examples/kernel-features-example.tsremoved → 0, reportedgetService<any>in that file → 1packages/core/src/security/resolve-authz-context.tsremoved → 0, reportedquery as anyin that file → 1examples/app-todo/objectstack.config.tsremoved → 0, reportedzh-CNlabel line deleted → 1packages/spec/README.md→ 1Dead scans stay red (seat review, round 1)
When the ratchets were two-sided, a scan that lost part of its population dropped those baselined entries out of the measurement, and the DOWN direction went red once per entry. Now that the ratchets are one-sided, that same drop would read as a passing decrease. So the gate now asks the scan itself: if a baselined entry is still on disk but the run did not read it, the gate refuses. A deleted file is not on disk, and a swept file was read, so both still pass. The table lists, for each reader, what reds a scan or walk that read nothing. "New" marks a refusal added in this round. Each new refusal has self-test cases, and one of them is an empty population.
ignoresnot lifted, read fromcalculateConfigForFile)os lintrenamed itsi18n/rules, or the classifier went blind)errorfound at all, or a ledgered file on disk the walk never reached. Before this round nothing caught it: a dead scan reported all five rows as stale and passed.No floor was introduced, so none rots on the next
--update. Each refusal checks an identity (on disk and not read) or a zero.There are two places where a legitimate final sweep is refused until
--updaterecords it. The message names that remedy, and the zero ceiling that--updatewrites disarms the refusal:Deleting a file never trips either refusal.
Dead-scan demos on
60e74485. In each one, the gate's own walk or lift was broken for a single run, the gate was run, and the file was restored withgit checkout HEAD --and a cleangit status:content/docs/apiadded to SKIP_SUBTREEScontent/docs/protocol/kerneladded to SKIP_SUBTREEShttp-protocol.mdxignoresi18nx/instead ofi18n/metadata-protocolpackages/objectql/srcdropped from the read-seam rootsengine.tsTests
--self-testof all ten readers: exit 0, each carrying its new decrease-passes and increase-reds cases (table above).*.test.tsfiles that name one of them only mention it in comments; those were read and none asserts behaviour this PR changes.Acceptance notes
--update. The decrease report names the number on every run. The query-options test-surface line spells outlicenses N new test-code erasure(s).ignoresof theireslint.config.mjsblocks, and both ratchets still measure listed files with the ignore lifted, so growth in a listed file stays red. A key whose file is gone stays anignoresglob until--updatedrops it, which does no harm. If a file is later recreated at that same path, it rides the stale count. The report says to drop the entry..github/workflows/lint.yml(no step's invocation changed). Its comment at line 4209 still calls the query-options test count "decrease-only". It is a governed file and only a comment.engine-double-contract.baseline.jsonandstartup-registry-verdict.baseline.json(both still fail on a stale entry), and the durability vocabulary staleness checks (FAILURE_PROPAGATION_SITES,READ_FAILURE_DISCRIMINATORS), which are declarations rather than baselines.packages/metadata-protocol/src/protocol.metadata-store-outage.test.ts:636-639has a comment saying the durability ledger and code are "pinned to each other in both directions". Only the restored-limb direction still reds. This is a comment outside the file surface, so it is left alone.Verification
Derived gate families — 41/41 exit 0 on 60e7448 (and on c4087da before it), --ran reconciliation 0 NOT-MEASURED
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 41 families, and each was run with its exit captured before any pipe.pnpm check:entry-guardfirst went red, becausereconcileLedgerwas exported from a file with an unguarded top level. The function was made module-private (commit c4087da), and the gate then exited 0.pnpm check:i18n-coverageandpnpm check:published-readme-exportsran after a fullpnpm buildunderos-verify-lock.After the dead-scan round, the same 41 families were derived again (the set was unchanged) and run again on
60e74485, and all 41 exited 0. On that head,node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran FILEprinted:41 derived famil(ies) accounted for — 41 run, 0 NOT-MEASURED (a DERIVED zero …).Changeset
Repository tooling only:
scripts/*.mjsandscripts/*.json. No published package'sfiles[]ships them, so this PR is labelledskip-changeset.🤖 Generated with Claude Code
https://claude.ai/code/session_01ADHkmPe3iuugZk1bwH3GNV
Generated by Claude Code