Skip to content

fix(pm): re-sync the ported half-state sweeper and pin it against upstream - #6672

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-6642-sweeper-resync-parity
Aug 28, 2026
Merged

fix(pm): re-sync the ported half-state sweeper and pin it against upstream#6672
os-litant merged 1 commit into
mainfrom
claude/issue-6642-sweeper-resync-parity

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#6642

The ported scripts/pm/check-half-states.mjs is re-synced to a named upstream commit, and a parity gate now pins it there so the next drift is red instead of silent.

The drift, before and after

Measured 2026-08-28 against objectstack-ai/objectstack at 2b4178aa53ca62089f43e2cfae0b7838cf340dd1:

ported copy (before)upstreamported copy (after)
lines9,34012,94813,089
--self-test cases1,1161,5741,574
diff vs upstream4,763 lines0 outside the declared divergences

So 458 predicate cases and ~3,600 lines of upstream fixes had never arrived here, while the patrol went on rendering a confident anchor body with the corresponding rows simply absent. The re-synced copy is now larger than upstream (13,089 vs 12,948) because the objectui-only closed-window mechanism sits on top of it.

scripts/invoked-as.mjs was measured too, and needed no re-sync: its predicate is byte-identical to upstream's already. Only its prose and one self-test fixture diverge (nine adaptations from the port), and those are now declared in the same pin rather than left undeclared.

The re-sync is mechanical, not hand-merged

The ported file is upstream blob + 14 declared divergences applied forward, each divergence an exact text pair sliced out of the two real files. Nothing was retyped, and the pairs round-trip: reversing them out of the result reproduces upstream's blob byte for byte.

Divergences preserved (the workflow header's checklist, plus what it did not know about):

  1. DEFAULT_SWEEP_REPO = 'objectstack-ai/objectui' — a bare terminal run here sweeps THIS board. Two of upstream's own self-test rows pinned the objectstack leg against that constant (equal strings upstream, not here), so they are pinned to their literal instead and the default gets its own row. default-sweep-repo, sweep-repo-self-test.
  2. The closed-window mechanismPM_SWEEP_CLOSED_WINDOW_PAGES / resolveClosedWindowPages, authored during the port and never upstreamed, with its CLOSED_WINDOW constant, its request-free early return, the closedWindowDisabled count, the UNREAD-not-clean summary branch, the exit-2 refusal of a malformed page count, and its self-test rows. Entries: closed-window-resolver, closed-window-fetch-gate, closed-window-counts, summary-unread-branch, summary-counts-typedef, closed-window-cli-refusal, closed-window-self-test, floor-window-independence, summary-disabled-beats-floor. Kept, not removed: the card's own DISABLED-branch pin still asserts it, unset it is byte-equivalent to upstream (4 pages, source default), and it is the one place the fix(types): retire VectorFieldMetadata.indexed and .distance_metric #4690 property "a disabled reader is UNREAD, not clean" lives.
  3. Three H32 self-test rows — a divergence this re-sync DISCOVERED, not one it authored.seatLane decides whether a seat title's @ repo suffix names a foreign board by comparing against the live resolved sweep repo. Upstream's rows hard-code @ objectui as the foreign specimen and @ objectstack as the own-board one; here that is exactly backwards, and a verbatim copy fails its own suite on three cases. The specimens are swapped, the property is identical. ⚠️ This class belongs upstream: the day objectstack derives its specimen from the resolved sweep repo instead of writing the name, all three entries disappear. Handed to the PM as an out-of-scope finding rather than filed from here — this seat's GraphQL quota was exhausted at report time.

The closure floor is not in that list, deliberately: it is upstream code now (objectstack#12906 / objectui#6641), so a verbatim re-sync keeps it by construction. That was verified rather than assumed, below.

Prose that upstream has since rewritten to describe this board's measurement (the resolveClosureFloor docblock, the h22ClosedCardPmResidue@param note) is taken from upstream verbatim rather than kept as a divergence. Behaviour is unaffected; the divergence set is smaller for it.

The parity gate

scripts/check-upstream-port-parity.mjs + scripts/upstream-port-pin.json, wired in lint.yml before pnpm install next to check-entry-guard, with a check:upstream-port-parity alias and scripts/__tests__/upstream-port-parity-wiring.test.ts holding the wiring.

The pin carries, per ported file: the upstream repo, the 40-character commit, the SHA-256 of the upstream blob, and the declared divergences as exact text pairs with a stated reason each. Verification runs backwards — reverse the pairs out of the ported file, hash the reconstruction, require it to equal the pinned digest. Byte equality is the assertion, so there is no threshold to tune.

Three design points worth review:

  • A digest of upstream means no checked-in copy of upstream. The obvious spelling (commit the 784 KB blob, apply a patch, diff) puts a second copy of the predicates in the tree, which is the disease one level up. The pin is 31 KB.
  • It fetches nothing. A gate that reached api.github.com would be red on a network hiccup and green on a cached 200 — and this repo's whole reason for owning a patrol is that a check which cannot read its input must never read as clean. The digest is the input; the ref beside it is provenance, and the output says so instead of implying it was verified.
  • Bumping the pin is the deliberate re-sync act, and it is mechanical: --resync upstreamfile --ref sha forward-applies the divergences, writes the ported file and rewrites the digest. Editing a digest by hand until the gate goes green is indistinguishable from baselining the drift, so the only supported path is the one that also re-applies the adaptations. A divergence whose upstream anchor vanished fails that command loudly rather than being dropped.

--self-test (37 cases) drives the real comparer over fixtures: parity holds on an undrifted copy; drift outside every declared region reds as a digest mismatch and says so; drift inside one names its divergence; an ambiguous anchor is refused rather than applied to the first match; the pin-bump procedure round-trips and a vanished anchor fails it; and fourteen malformed-pin shapes are refused (exit 2) rather than read as clean. The last block drives the shipped pin, because a pin this gate cannot parse is the one state a fixture can never show.

Ablation — the gate really reds

Run against the real tree, each mutation confirmed on disk by anchor count before reading any result, each restore confirmed by git hash-object against the HEAD blob (0f70eb029c5607df421b3f3b8e2822e9698abe87, matched after every leg):

mutationexitwhat it printed
delete one upstream line outside every declared region1reconstruction does not match the pinned upstream blobthe difference is OUTSIDE all of them
edit DEFAULT_SWEEP_REPOinside a declared region1divergence `default-sweep-repo`: expected its ported text exactly once, found 0 — the region it declares has been edited
hand-edit the pinned digest (the baseline-the-drift move)2files[0].upstreamSha256 is not a 64-character SHA-256 digestA pin that cannot be read is not a clean tree.

The closure floor survives the re-sync — measured, not assumed

Driven end-to-end on the re-synced copy with the value read out of half-state-patrol.yml rather than retyped (PM_SWEEP_CLOSED_FLOOR: '2026-08-28', no PM_SWEEP_CLOSED_WINDOW_PAGES), loading the module with that env in place so the module-scope constants resolve as they will on the runner. All 16 cases pass:

  • the workflow value parses valid and resolves to 2026-08-28T00:00:00.000Z;
  • the closed reader is ON — 4 pages, source default, i.e. upstream's default rather than an env value (the property objectui#6641 relied on when it removed the : 0 hold);
  • a pre-floor closed carrier (2026-01-01) is skipped, and so is 2026-08-27T23:59:59Z;
  • a post-floor one (2026-08-29) is reported, and the cutover day itself is judged;
  • unfloored, the same old card is still a finding — the floor is opt-in and the predicate is upstream's;
  • the summary names the floor date and says earlier closures are NOT a reading about them;
  • through the real CLI on the re-synced file, a malformed floor (yesterday) and a shape-valid impossible date (2026-02-31) each exit 2 with the refusal message, while the workflow's own value is accepted (it gets as far as the transport and exits 3, PREREQUISITE NOT MET, which is the sweeper's documented split).

REPLACED PIN disclosures

  • scripts/__tests__/check-half-states.test.ts header said the sweeper carries a "~1,077-case" self-test. It was 1,116 by then and is 1,574 now. The figure is deleted rather than refreshed — a hand-copied count drifts by construction and a stale one reads exactly as authoritative as a fresh one. The assertion below it never read the number and still does not.
  • .github/workflows/half-state-patrol.yml header carried the whole divergence list and ended Nothing else diverges; the predicates are untouched. Both halves were true of the three deliberate adaptations and blind to the 4,637-line gap. The enumeration is replaced by a pointer to the machine-checked pin; what stays in the header is the wiring the pin cannot see (the floor, the paths: entry) plus the three in-script classes named for orientation.
  • No pin in check-half-states.test.ts needed respelling for the re-sync: all 18 of its cases pass unchanged against the re-synced copy. Added rows pin the newly-discovered H32 install-coupling, including the source line that causes it, so upstream removing that coupling is what retires the block.

Gate verdicts

Union re-run on the final commit 0034eb6; exit codes captured before any pipe.

gateexitverdict line
node scripts/pm/check-half-states.mjs --self-test0✓ check-half-states self-test: 1574 cases pass.
node scripts/check-upstream-port-parity.mjs --self-test0✓ check-upstream-port-parity self-test: 37 cases pass …
node scripts/check-upstream-port-parity.mjs0✓ check-upstream-port-parity: 2 ported file(s) match objectstack-ai/objectstack@2b4178aa5 modulo their declared divergences.
node scripts/check-entry-guard.mjs --self-test0✓ check-entry-guard self-test: 63 cases pass …
node scripts/check-entry-guard.mjs0✓ check:entry-guard: 51 scripts/ file(s) — no entry guard outside the baseline …
node scripts/check-control-bytes.mjs0✅ check-control-bytes: OK (scanned 5517 tracked text file(s); skipped 85 binary).
node scripts/check-pre-install-import-graph.mjs0✅ check-pre-install-import-graph: OK — 18 pre-install step(s) in 16 job(s) run 18 scripts/ gate(s); 20 module(s) walked, every non-relative leaf a node builtin.
node scripts/check-changeset-presence.mjs0✅ No source of a released package changed in this range, so no changeset is owed.
node scripts/check-lint-coverage.mjs0✅ lint coverage: 46/46 packages linted, 0 with outstanding errors (0 total).
pnpm type-check:scripts0clean tsc -p tsconfig.scripts.json
npx vitest run --maxWorkers=2 scripts/__tests__0Test Files 84 passed (84) · Tests 2371 passed (2371)

No changeset: the gate's own verdict, quoted above, is the authority. Nothing under a published package's src/ changed. ⛔ No skip-changeset label was applied — in this repo that label is a phantom.

ESLint was narrowed and the narrowing is measured, not assumed: npx eslint --no-inline-config --format json over the four changed source files reports 4 files linted, 0 errors, 0 warnings, exit 0 (file count read from the JSON output, not from a summary line); none of the four was skipped as ignored (an ignored path would have produced a warning and the warning count is 0); and the resolved config for these paths sets neither parserOptions.project nor projectService, so type-aware linting is off and this diff cannot move an ESLint verdict on any untouched file. pnpm lint is turbo run lint, per package — scripts/ is not a workspace package, so the repo-wide run does not read these files at all.

Also verified rather than assumed: tsc --listFiles really contains both edited test files (1 match each), so "type-check clean" is a statement about them.

Not done here, on purpose

The card asked for a re-sync and a detector, not for the port to be re-litigated. The closed-window mechanism is kept dormant rather than removed; the three H32 entries are carried rather than fixed upstream from here. Both are named in the pin with their reasons, and both shrink the day the other repo moves.

Generated by Claude Code


Generated by Claude Code

…tream
The ported `scripts/pm/check-half-states.mjs` had drifted 4,637 lines behind
objectstack with nothing able to detect it. Re-synced verbatim to a named
upstream commit, preserving the declared divergences, and added a parity gate
so the next drift is red rather than silent.
Measured before, on 2026-08-28:
ported copy 9,340 lines / 1,116 self-test cases
objectstack upstream 12,948 lines / 1,574 self-test cases
diff between them 4,637 lines
The re-sync is mechanical: upstream's blob plus 14 declared divergences, each
an exact text pair, applied forward. `scripts/upstream-port-pin.json` carries
the upstream commit, the SHA-256 of its blob and those pairs;
`scripts/check-upstream-port-parity.mjs` reverses them out of the ported files
and requires the reconstruction to hash to the pinned digest. It fetches
nothing — the digest is the input.
`scripts/invoked-as.mjs` needed no re-sync: its predicate is already
byte-identical to upstream's. Its nine port adaptations (prose and one fixture)
are declared in the same pin, so it is covered too.
Three divergences are new and were DISCOVERED by the re-sync rather than
authored by it: upstream's H32 self-test rows compare a seat title's `@ <repo>`
suffix against the LIVE resolved sweep repo, so their specimens invert here and
a verbatim copy fails its own suite. That class is upstream's to retire.
Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] The ported check-half-states.mjs has drifted ~212 KB behind objectstack, and nothing detects it

2 participants

@os-litant@claude