fix(scripts): close an ADR-0087 framed region at the same or a shallower heading, not at any heading - #13142
Merged
Merged
Conversation
…wer heading, not at any heading `findMigrationPrescription`'s framed region opened at a heading matching `MIGRATION_FRAMING_RE` and closed at the next heading of ANY level, so a `## Migration` section organising its rewrite tables under `###` sub-headings lost its framing before the first table row was read. Framing is now tracked per heading depth: a heading closes every region nested at or outside its own level and opens one of its own if it carries framing, so shallower regions survive. The superset property is structural -- the predicate became "some heading on the current path frames", and the most recent heading is always on that path. Adds a FRAMING-SCAN ROT control to `assertInputs`, in both directions: the framing scan's total-failure condition (no section found anywhere) is indistinguishable from its success condition on the live stock, so synthetic controls run on every invocation and refuse rather than report clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw
…gration-framing-depth
os-elon
marked this pull request as ready for review
August 29, 2026 06:41
os-elon
enabled auto-merge
August 29, 2026 06:41
Contributor
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 33240069522 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
This was referenced Aug 29, 2026
This was referenced Aug 29, 2026
Merged
fix(client): declare the response the meta reset door actually sends on both
deleteItem twins
#13148
Open
This was referenced Aug 29, 2026
Merged
This was referenced Aug 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #12996
findMigrationPrescription's framed-table arm opened a framed region at a heading matchingMIGRATION_FRAMING_REand closed it at the next heading of any level. A## Migrationsection that organises its rewrite tables under###sub-headings — the ordinary way to write a two-part rename — therefore lost its framing before the first table row was read.Reproduced on today's
main(4d94823a36), through the gate's own exported functionA and B differ by one line. Isolated: holding the table byte-identical and adding only the
### Method namespaceline flipsframed-tabletonull.The fix
Framing is tracked per heading depth. A heading closes every region nested at or outside its own level and opens one of its own if it carries framing; regions shallower than it survive.
The superset property is structural, one level up from where #6497 and #6559 established it: the predicate became "some heading on the current path carries framing", and the most recent heading — the whole of the old predicate — is always on that path. No body the old scan framed can stop being framed.
Per-depth rather than one remembered depth: a
### Response keyssibling closes only its own level, and the## Migrationregion above it survives (pinned as P67).Stock measurement — one row moves, and it is the live instance
Measured over the stock at
4d94823a36: 419 changesets, 53 declared-breaking.origin/main)One changeset newly flagged, zero no longer flagged, zero changed branch or evidence line:
.changeset/adr0006-d2-client-environments-namespace.md— declared breaking, holdsnot-required (runtime-interface-only ...), evidence| client.projects.list(…) | client.environments.list(…) |on branchframed-table.Is the new refusal correct? Yes. That changeset ships sixteen rows of
client.projects.Xbecomingclient.environments.Xplus a response-key table — a real consumer-rewrite prescription. Its own disposition marker says the author declinedno-migration-prescriptionbecause it "would mechanically pass only through a detector blind spot while contradicting the migration table below it". The fix makes the detector agree with the author's own reading;runtime-interface-onlyinherits the prescription refusal, which is what makes it a narrowing rather than a fifth way around it.Verdict impact on any future PR: none. The gate is forward-only (
git diff --diff-filter=AMR base..head -- .changeset/*.md), and anMrow already breaking at base is skipped. That changeset is onmainand already breaking, so it is never re-judged. What changes is that the next changeset written this way is asked the question instead of being waved through.The card's smaller alternative was measured and rejected
Widening
OLD_COLUMN_REto admitbefore:afteris not a NEW column word, so| before | after |still does not frame. The alternative as literally stated cannot rescue probe B.afteras well takes it to plus three, two declared-breaking, and two of the three are behaviour comparisons rather than rewrites:compound-meta-door-mode-draft.md(| Request | Before | After |, one request's response before and after the change) andoauth-resource-identifier-sourced-255.md(| | before (1024) | after (255) |, twoinformation_schemareadings). The second declares breaking and holdsnot-required (no-migration-prescription), so the false positive would hard-block an author entitled to their exemption.That is exactly the trade #6559 measured and refused for
was/now—before/afteris named in the same paragraph as this repo's behaviour-comparison header vocabulary — and P33 pins the refusal. It also addresses nothing about nesting.No earlier repair is subsumed or removed. #6419 and #6497 added arms, #6559 added an arm that needs no heading at all; this changes only where a heading-opened region ends.
A guard whose success condition equals its total-failure condition
If the framing scan ever stops seeing headings, every region is empty, no prescription is ever found, and both inheriting exemptions become free for everyone — reported as a clean run. "Zero framed sections" is also what a body with no migration section legitimately produces, so the live stock cannot tell the two apart.
assertInputsgains a FRAMING-SCAN ROT control in both directions, synthetic for the #8658 reason the existing CONVENTION ROT control gives, and it runs on every invocation. Demonstrated on the real shipping path by blinding the heading pattern:EXIT=0, cleanEXIT=1, 3 FRAMING-SCAN ROT input problems, "refusing to report a verdict"origin/main, same blindingEXIT=0, "no declared-breaking changeset" — clean, with the scan totally deadThe inverted half of the control pins that a framed region still closes: a region that never closes frames the rest of the document, which is the over-matching #6559 rejected the label-framed arrow arm for.
Pinning the ruling, not just the code
P62-P68. Predicted before running, then run — the map matched exactly:
###sibling, outer region survivesPlus the FRAMING-SCAN ROT control's nested case (RED when reverted), and
--self-testexit 1. The floors are green with the fix and green without it, said out loud rather than counted as coverage, per this file's own convention. Every restore leg was verified by observation — worktree blob equal to the HEAD blob,git diff HEADempty — never by an exit code.Verification
All at
11c00cc038, underscripts/pm/os-verify-lock.sh(VERDICT command-exit 0):node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, re-derived after syncing (the first derivation printed STALE TREE and still exited 0)scripts/pm/bare-root-worklist.mjs --self-test,pnpm check:pm-dispatch-gatespnpm check:objectui-changesetby hand — this gate is staged and executed byscripts/objectui-changeset-digest.mjs, which the path derivation cannot see; that script's own self-test is the first half of itpnpm check:changeset-gate-self-tests— 235 assertions over real temp git repospnpm check:published-files,pnpm check:nul-bytesnode scripts/check-test-completeness.mjsanswers a standalone run withEXIT=1and a usage line. That is an invocation error, not a red gate — the unmodified script does the same, and it is #13110.No changeset
Derived, not asserted: the diff's only path is
scripts/check-adr-0087-registration.mjs, which lies outside every workspace package root, andfileswhitelists are package-relative —pnpm check:published-filesreports all 69 publishable packages whitelistingdist/plus README and CHANGELOG. Nothing publishes, so the PR carries theskip-changesetlabel instead. The gate this PR changes ran on its own diff and reported no declared-breaking changeset.Generated by Claude Code
Generated by Claude Code