Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 43 additions & 5 deletions .github/workflows/half-state-patrol.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,8 +8,31 @@ name: Half-State Patrol
# prose below is upstream's and its issue numbers (#9844, #4449, #9575, #4690,
# #7412, #11217 …) are OBJECTSTACK numbers — do not read them as objectui cards.
#
# Every divergence from upstream is listed here, once, so a future re-sync knows
# exactly what it must not clobber:
# ## Where the divergence list lives (objectui#6642)
#
# ⚠️ It is no longer this comment. This header used to carry the whole list and
# to end with "nothing else diverges; the predicates are untouched" — and that
# sentence rotted, silently, for months. Measured 2026-08-28: the ported copy
# stood at 9,340 lines against upstream's 12,948 (a 4,637-line `diff`), running
# 1,116 self-test cases where upstream ran 1,574. The three deliberate
# adaptations below were accurate the whole time; what the list could not say
# was that ~3,600 lines of upstream fixes had never arrived, because a
# hand-maintained enumeration can only describe what someone remembered to
# write down.
#
# The enumeration is therefore MACHINE-CHECKED now, in
# `scripts/upstream-port-pin.json`: every declared divergence as an exact text
# pair, with the upstream commit and the SHA-256 of the blob it was taken from.
# `scripts/check-upstream-port-parity.mjs` reverses those pairs out of the
# ported files and requires the reconstruction to hash to the pinned digest, so
# a drift beyond the declared set — here or upstream — is RED in `lint.yml`
# rather than invisible. Bumping the pin (`--resync`) is the deliberate re-sync
# act. ⛔ Do not maintain a second copy of the divergence list in this comment;
# that is what just failed.
#
# What the pin CANNOT see, and what therefore still belongs here, is the wiring:
# the pin judges file contents, and the two items below are decisions taken in
# THIS file about how the sweeper is called.
#
# 1. `PM_SWEEP_CLOSED_FLOOR` on the sweep step — H22's closed-card reader is
# ON here, but judges only cards closed on/after the cutover date. It read
Expand DownExpand Up@@ -46,10 +69,25 @@ name: Half-State Patrol
# and it treats recent closed residue as a live duty.
# 2. `scripts/invoked-as.mjs` is in the `paths:` filter below — the sweeper
# imports it, and it was ported alongside.
# 3. `scripts/pm/check-half-states.mjs` carries `DEFAULT_SWEEP_REPO =
# 'objectstack-ai/objectui'` so a bare terminal run here sweeps THIS board.
#
# Nothing else diverges; the predicates are untouched.
# The IN-SCRIPT divergences are the pin's business, not this comment's, but the
# classes are worth knowing before you open it — three of them, and the set is
# meant to shrink:
#
# a. `DEFAULT_SWEEP_REPO = 'objectstack-ai/objectui'`, so a bare terminal run
# here sweeps THIS board, plus the two self-test rows that pinned the
# constant against objectstack's literal.
# b. `PM_SWEEP_CLOSED_WINDOW_PAGES` / `resolveClosedWindowPages` — an
# objectui-only escape hatch that can switch H22's closed reader fully OFF,
# authored during the port and never upstreamed. UNSET here since
# 2026-08-28 (the floor in (1) is what holds the historical residue out),
# so it is dormant, but it is live code and its "the surface is UNREAD, not
# clean" summary branch is the #4690 property the port turned on.
# c. Three H32 self-test rows. `seatLane` compares a seat title's `@ <repo>`
# suffix against the LIVE resolved sweep repo, so which name is FOREIGN is
# install-dependent and upstream's specimens invert here. ⚠️ This class is
# upstream's to remove: derive the specimen from the resolved repo there
# and all three entries disappear.
#
# ## Why a workflow, and not "a seat should run it"
#
Expand Down
38 changes: 38 additions & 0 deletions .github/workflows/lint.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -201,6 +201,44 @@ jobs:
node scripts/check-entry-guard.mjs --self-test
node scripts/check-entry-guard.mjs

# ── The ported objectstack tooling is a PINNED copy (objectui#6642) ───
# `scripts/pm/check-half-states.mjs` came from objectstack (objectui#5791)
# under a workflow header calling it a verbatim copy and enumerating the
# three things a re-sync must not clobber. Nothing checked either half.
# Measured 2026-08-28, before this step existed: the ported copy stood at
# 9,340 lines against upstream's 12,948 — a 4,637-line `diff` — and its
# own `--self-test` ran 1,116 cases where upstream's ran 1,574. So 458
# predicate cases had landed upstream and never arrived here, while the
# patrol went on rendering a confident report with the corresponding rows
# simply missing.
#
# The direction of harm is this repository's least visible one: a drifted
# copy does not fail, it REPORTS. It became load-bearing once already —
# objectui#6641 had to hand-port H22's closure floor into this copy,
# because wiring the new environment variable in the workflow alone would
# have set a variable this copy did not read.
#
# The gate reverses the DECLARED divergences out of each ported file and
# requires the reconstruction to hash to the pinned upstream digest, so
# drift beyond the declared set is byte-detectable in both directions —
# an edit here, or upstream moving. ⛔ 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 (#4690).
#
# Runs before install, next to the two gates above and for the same
# reason: node builtins and one local module only, so an install failure
# cannot take it down with it. `--self-test` runs FIRST — it drives the
# real comparer over fixtures (parity holds, drift outside a region,
# drift inside one, an ambiguous anchor, the pin-bump procedure, and
# every malformed-pin shape), which is what stops a comparer that
# recognises nothing from reading as a clean tree.
- name: Verify the ported objectstack tooling still matches its pin
if: steps.relevant.outputs.should_run == 'true'
run: |
node scripts/check-upstream-port-parity.mjs --self-test
node scripts/check-upstream-port-parity.mjs

- name: Turbo Cache
if: steps.relevant.outputs.should_run == 'true'
uses: actions/cache@v6
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -58,6 +58,7 @@
"check:eager-closure": "node scripts/check-eager-closure-budget.mjs",
"check:docs-route-closure": "node scripts/check-docs-route-eager-closure.mjs",
"check:entry-guard": "node scripts/check-entry-guard.mjs",
"check:upstream-port-parity": "node scripts/check-upstream-port-parity.mjs",
"check:pre-install-import-graph": "node scripts/check-pre-install-import-graph.mjs",
"check:vi-mock-specifiers": "node scripts/check-vi-mock-specifiers.mjs",
"check:shell-escape-residue": "node scripts/check-shell-escape-residue.mjs",
Expand Down
74 changes: 68 additions & 6 deletions scripts/__tests__/check-half-states.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,7 @@ import {
resolveClosedWindowPages,
resolveClosureFloor,
resolveSweepRepo,
seatLane,
summaryLine,
} from '../pm/check-half-states.mjs';

Expand All@@ -21,18 +22,35 @@ import {
*
* ## What this file is for, and what it deliberately is not
*
* The sweeper carries its own ~1,077-case `--self-test`, and that suite is the
* authority on the twenty-odd predicates. Re-asserting predicates here would
* fork the pin: two copies drifting apart, one of them not the one upstream
* maintains. So the first test below simply RUNS that suite in CI — the point
* being that a port whose self-test nobody executes is the #4690 shape again
* (a check that reads as enforcement while nothing invokes it).
* The sweeper carries its own `--self-test`, and that suite is the authority on
* the twenty-odd predicates. Re-asserting predicates here would fork the pin:
* two copies drifting apart, one of them not the one upstream maintains. So the
* first test below simply RUNS that suite in CI — the point being that a port
* whose self-test nobody executes is the #4690 shape again (a check that reads
* as enforcement while nothing invokes it).
*
* ⚠️ REPLACED PIN (objectui#6642): that sentence used to say "~1,077-case", and
* the figure was 1,116 by then and is 1,574 after the re-sync. The count is
* deliberately gone rather than refreshed — a hand-copied enumeration drifts by
* construction and a stale one reads exactly as authoritative as a fresh one,
* which is the lesson `lint-workflow.test.ts` records at length for this repo.
* The assertion below never read the number and still does not.
*
* Everything after it pins the ADAPTATIONS instead — the handful of places this
* install diverges from upstream. Those are exactly the lines a future verbatim
* re-sync from objectstack would clobber silently, and each one is load-bearing:
* dropping any of them does not break the patrol loudly, it makes the patrol
* report something false quietly.
*
* ## What this file cannot see, and what now can (objectui#6642)
*
* By construction it looks only at THIS copy. It cannot tell whether upstream
* has moved, which is how the port drifted 4,637 lines behind while every test
* here stayed green. `scripts/check-upstream-port-parity.mjs` is the half that
* looks the other way: it pins the ported files against a named upstream commit
* modulo the same adaptations, byte-for-byte. The two are complements — that
* gate proves the copy still IS the copy; this file proves the adaptations
* survived being one.
*/

const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
Expand DownExpand Up@@ -82,6 +100,50 @@ describe('check-half-states — sweeps THIS board (objectui#5791 adaptation)', (
});
});

describe('check-half-states — H32 lane foreignness inverts here (objectui#6642)', () => {
/**
* A divergence the re-sync DISCOVERED rather than one it authored, and the
* only one that made a verbatim copy impossible outright: upstream's H32 rows
* do not merely read oddly here, they FAIL. Three of them, on the first run
* of upstream's suite against this install.
*
* `seatLane` decides whether a seat post's lane is readable from this sweep by
* comparing the title's `@ <repo>` suffix against the RESOLVED sweep repo —
* a live value, not a constant. Upstream's self-test rows hard-code
* `@ objectui` as the foreign specimen and `@ objectstack` as the own-board
* one, which is correct there and exactly backwards here.
*
* The property being asserted is identical in both installs; only the
* specimens swap. ⚠️ Which is why this block is the one adaptation that
* should NOT be defended: the day upstream derives its specimen from the
* resolved repo instead of writing the name, three pin entries disappear and
* these rows become redundant with upstream's own.
*/
const seat = (title: string) => ({ title });

it('reads a SIBLING board\'s lane as foreign — and the sibling here is objectstack', () => {
expect(seatLane(seat('[PM seat] domain:devx @ objectstack — 🟢 os-x')).foreign).toBe(true);
});

it('…and this board\'s own lane as readable, keeping the bare label', () => {
const own = seatLane(seat('[PM seat] domain:devx @ objectui — 🟢 os-x'));
expect(own.foreign).toBe(false);
expect(own.lane).toBe('domain:devx');
});

it('the specimens follow the RESOLVED sweep repo, which is what makes this a divergence', () => {
// Pinning the coupling itself rather than a value: this line is the reason
// upstream's rows cannot be carried verbatim, and its removal upstream is
// the event that retires this whole block plus three pin entries.
const src = fs.readFileSync(sweeperPath, 'utf8');
expect(src).toContain("SWEEP_REPO.repo.split('/')[1]");
// Both places this install resolves that value agree, so the rows above
// hold on a runner (GITHUB_REPOSITORY) and in a bare terminal (the default).
expect(resolveSweepRepo({}).repo).toBe('objectstack-ai/objectui');
expect(resolveSweepRepo({ GITHUB_REPOSITORY: 'objectstack-ai/objectui' }).repo).toBe('objectstack-ai/objectui');
});
});

describe('check-half-states — H22 runs here behind a DATED CLOSURE FLOOR (objectui#5985)', () => {
/**
* ⚠️ REPLACED PIN, not a respelled one. Until 2026-08-28 this block pinned the
Expand Down
115 changes: 115 additions & 0 deletions scripts/__tests__/upstream-port-parity-wiring.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
import { describe, expect, it } from 'vitest';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { parse as parseYaml } from 'yaml';

const ROOT = path.resolve(fileURLToPath(import.meta.url), '../../..');
const GATE = 'scripts/check-upstream-port-parity.mjs';
const PIN = 'scripts/upstream-port-pin.json';

/**
* objectui#6642: `scripts/pm/check-half-states.mjs` was copied here from
* objectstack (objectui#5791) and then drifted for months with nothing able to
* see it. Measured the day this gate landed: 9,340 lines here against 12,948
* upstream — a 4,637-line `diff` — and 1,116 self-test cases here against
* upstream's 1,574. The patrol kept rendering a confident report with the
* missing predicates' rows simply absent.
*
* The gate closes that. This file pins the gate to its WIRING, in the direction
* that goes wrong quietly: a parity check nobody runs is indistinguishable from
* a parity check that passes — which is exactly the state the ported sweeper
* was already in, one level down.
*
* Deliberately NOT asserted here: any digest, any line count, or the number of
* declared divergences. Those live in the pin, they move every time someone
* re-syncs, and a copy of them here would be a second thing to keep honest —
* the lesson `lint-workflow.test.ts` records at length for this same workflow.
* What is asserted is that the mechanism is reachable, runs, and is not
* vacuous.
*/
describe('check-upstream-port-parity is wired, not merely present', () => {
const workflow = parseYaml(fs.readFileSync(path.join(ROOT, '.github/workflows/lint.yml'), 'utf8'));
const steps: Array<Record<string, unknown>> = workflow.jobs.lint.steps;
const gateSteps = steps.filter((s) => typeof s.run === 'string' && (s.run as string).includes(GATE));

it('the gate script and its pin both exist', () => {
expect(fs.existsSync(path.join(ROOT, GATE))).toBe(true);
expect(fs.existsSync(path.join(ROOT, PIN))).toBe(true);
});

it('package.json aliases it, and the alias points at the script that exists', () => {
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
const alias = pkg.scripts['check:upstream-port-parity'];
expect(alias).toBeTruthy();
expect(alias).toContain(GATE);
});

it('lint.yml runs it — exactly one step, both legs', () => {
expect(gateSteps).toHaveLength(1);
const run = gateSteps[0].run as string;
expect(run).toContain(`node ${GATE} --self-test`);
expect(run.split('\n').some((l) => l.trim() === `node ${GATE}`)).toBe(true);
});

it('that step is not disabled — it runs whenever the job runs its other steps', () => {
// The same guard the rest of the job uses, read off a sibling rather than
// hard-coded: objectui#3523's shape may be renamed, and a test pinning the
// literal would fail on a rename while a step commented out with
// `if: false` would not.
const condition = gateSteps[0].if;
const others = steps
.filter((s) => s !== gateSteps[0] && typeof s.uses !== 'undefined')
.map((s) => s.if);
expect(others).toContain(condition);
});

it('it runs BEFORE pnpm install, so an install failure cannot take it with it', () => {
const gateIndex = steps.indexOf(gateSteps[0]);
const installIndex = steps.findIndex(
(s) => typeof s.run === 'string' && (s.run as string).includes('pnpm install'),
);
expect(installIndex).toBeGreaterThan(-1);
expect(gateIndex).toBeLessThan(installIndex);
});

it('the pin really pins the ported sweeper — the file the card is about', () => {
// The one content assertion, and it is about COVERAGE rather than about
// values: a pin that stopped naming `check-half-states.mjs` would leave the
// gate green while the drift it was written for resumed.
const pin = JSON.parse(fs.readFileSync(path.join(ROOT, PIN), 'utf8'));
const pinned = pin.files.map((f: { ported: string }) => f.ported);
expect(pinned).toContain('scripts/pm/check-half-states.mjs');
// …and its helper, which the patrol workflow's own `paths:` filter already
// treats as part of the same unit.
expect(pinned).toContain('scripts/invoked-as.mjs');
});

it('the pinned files are the ones the patrol workflow watches', () => {
// Both directions of the same claim: a file added to the patrol's paths
// filter but not to the pin drifts unwatched, and a file in the pin that
// the patrol no longer uses is a stale obligation.
const patrol = parseYaml(
fs.readFileSync(path.join(ROOT, '.github/workflows/half-state-patrol.yml'), 'utf8'),
);
const watched: string[] = patrol.on.pull_request.paths;
const pin = JSON.parse(fs.readFileSync(path.join(ROOT, PIN), 'utf8'));
const pinned: string[] = pin.files.map((f: { ported: string }) => f.ported);
for (const p of pinned) expect(watched).toContain(p);
});

it('its self-test passes — the half that makes a green comparison mean something', () => {
const out = execFileSync('node', [GATE, '--self-test'], { cwd: ROOT, encoding: 'utf8' });
expect(out).toMatch(/check-upstream-port-parity self-test: \d+ cases pass/);
});

it('and the tree itself is at parity right now', () => {
// Not a duplicate of the CI step: this is the assertion that the pin
// shipped in this commit describes the files shipped in this commit. A pin
// updated without its file, or the reverse, fails here at review time
// rather than on someone else's branch.
const out = execFileSync('node', [GATE], { cwd: ROOT, encoding: 'utf8' });
expect(out).toMatch(/ported file\(s\) match/);
});
});
Loading
Loading