ci: install the parameterised half-state patrol from objectstack - #237
Merged
Conversation
Adopts the patrol by verbatim copy, so this repo's board gets machine-swept for the dispatch protocol's label/assignee half-states — it never has been. Three files, not the two the card names: check-half-states.mjs:803 imports `../invoked-as.mjs`, a separate module one directory up, so the two-file copy fails at import. Bringing the third file keeps the sweeper byte-identical to upstream rather than localising its import. All three are byte-identical to objectstack origin/main (sha256 recorded in the PR body). The file stays authored upstream; this copy tracks it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016TUrhcggSFrYctvp5dsV1A
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 #165
Installs the parameterised half-state patrol from
objectstackso this repo's board gets machine-swept for the dispatch protocol's label/assignee half-states. It never has been — and the read-only verification run below found 82 of them on the live board.It is a trio, not the pair the card names
scripts/pm/check-half-states.mjsline 803 is:scripts/invoked-as.mjsis a separate module one directory up. Copying only the two named files ships a sweeper that dies at import — a loudly red patrol, but a broken install.Chosen: bring the third file, do not localise the import. That keeps
check-half-states.mjsbyte-identical to upstream, which is the single-writer premise the card rests on, and it follows the precedent that module's own header records: objectui already carriesscripts/invoked-as.mjsunder the same name, ported from the same source, and the header calls the pairing a cross-repo obligation. Localising the import would have been the one edit that makes future drift-detection between the copies impossible to do by hash.This repo had no
scripts/tree at all; this PR creates it.Diff size, stated plainly
scripts/pm/check-half-states.mjsscripts/invoked-as.mjs.github/workflows/half-state-patrol.ymlThat is what "verbatim, single-writer upstream" costs. Nothing was tidied, reformatted or trimmed. Zero localised lines — see the audit below for why that turned out to be enforceable rather than merely preferred.
Verbatim, certified by git rather than by eye
All three files have the same git blob OID and the same file mode as
objectstackorigin/main:sha256, for a checkout that cannot see the other repo:
Source:
objectstackorigin/mainata81aa9d. (Per-file authoring commits are not quotable — that checkout is shallow, sogit log -- pathreturns the shallow boundary rather than real provenance. The hashes above are the honest identity statement.)The evidence that matters: a real sweep, naming THIS repo
The upstream header warns that a copy with a hardcoded default could sweep the wrong board and write a fully green report about it. So the install was proved by running the sweep read-only against this repo — no anchor write, no label write, no state change.
The rendered summary line, verbatim from that run:
It names
objectstack-ai/objectos, and the rows are unmistakably this board: H19 on #75 (blocked on #74, closed 2026-08-18, and on #68, still open — a partial discharge), H26 on #75 (#68 isneeds-user-decision, so that leg can never close), H14 on #68, and a long tail of H22 rows for closed cards still carryingpm:dispatched. The H17 trigger-file oracle read 565 tracked files — this repo's, viagit ls-filesin the checkout it runs in.Resolution table, measured rather than reasoned:
GITHUB_REPOSITORYalone, what Actions sets)objectstack-ai/objectosGITHUB_REPOSITORYobjectstack-ai/objectosPM_SWEEP_REPOobjectstack-ai/objectstackdefaultThat third row is the one residual objectstack-specific value in the tree, and it is discussed below.
Card acceptance criterion
tools/ci-scripts/run-self-tests.mjs; it scans.github/scripts/top level only, so a script atscripts/pm/is outside it by construction. Confirmed empirically rather than by reading —pnpm turbo run teststill reports✓ 4 self-test(s) passed, not 5. Cuts both ways: this PR does not trip the repo's unregistered-self-test rule, and the card's executable criterion is enforced by nothing going forward. Wiring it in would change this repo's gate topology and is deliberately not done here; see the report on #165 for the follow-up recommendation.Testing the "change NOTHING here" assumption
The upstream header claims a sibling repo needs no edit at all. Audited against this repo's actual shape (three workflows before this one, no
scripts/tree). It holds — with one caveat that lands on the maintainer step, not on the files.Verified clean:
github.repository/GITHUB_REPOSITORY, never hardcoded. Measured above.vars.HALF_STATE_ANCHOR_ISSUE || (github.repository == 'objectstack-ai/objectstack' && '9857') || ''. The literal is guarded by repo name, so here it evaluates to empty and the job refuses loudly. The dead branch is kept deliberately: it costs nothing, and removing it would make the two copies diverge by hash for no behavioural gain. The folded scalar survives the copy — YAML-parsed to a single-line expression, confirmed.actions/checkout@v7andactions/setup-node@v7match what this repo's three existing workflows already pin.actions/github-script@v9is new to this repo; first-party, but it is the oneuses:here without local precedent.node-version: '22', matching.node-versionand the>=22.12.0floor.check-node-floor.mjsdoes not read workflows, so this adds no declaration it governs (its gate stays green, output below).contents: read+issues: write. No existing workflow here requestsissues: write; if this repo's default workflow-token setting is read-only, confirm the first scheduled run can write before treating a failure as a code defect.half-state-patrolcollides with nothing (deploy-docs,translations-*are the only groups here) and no other workflow usesscheduleat all.pm:queue,pm:blocked,pm:dispatched,pm:on-hold,pm:blocking,pm:retriage,needs-user-decision,priority:p0,domain:*. Every one exists on this board, which the live run demonstrates by firing rows that use them.scripts/is not a pnpm workspace (apps/*,tools/*), is in no turbo task'sinputs, and there is no root eslint/prettier. The tree adds zero CI cost outside its own workflow.node:process,node:child_processand globalfetch, and shells out togit ls-filesonce. The workflow runs nopnpm install, so it cannot fail on a lockfile.The one caveat — a label this repo does not have. The recipe says to open a
tracking-labelled anchor issue. This repo has 24 labels andtrackingis not among them, so that instruction cannot be followed literally until someone creates the label. It is not load-bearing for correctness:trackingis consulted by exactly one predicate (H13_EXEMPT_LABELS), and H13 only fires on a card carrying adomain:*label with no pm-state label — so an anchor with no labels is safe from every predicate either way. ⛔ But do not substitutedomain:docsto make it look triaged: that combination is precisely what makes the anchor issue appear as a finding in the sweep it hosts, two hours later.Why nothing was localised, measured
The tempting edit is
DEFAULT_SWEEP_REPO = 'objectstack-ai/objectstack'(the third row of the resolution table). It is unreachable on a runner — Actions always setsGITHUB_REPOSITORY, and the workflow passesPM_SWEEP_REPOon top — so it only affects a maintainer running the script by hand from a local checkout here, which would read objectstack's board.Ablation, to find out whether "verbatim" is a preference or a constraint: commit first, mutate the constant to
objectstack-ai/objectos, confirm the change landed on disk (injected line present 1, removed line present 0, blob hash9ffcdb7to81bb724), run, restore viagit checkout HEAD -- path, confirm restoration (git diff HEADempty, hash back to9ffcdb7).So localising that constant turns the card's own acceptance criterion red. Upstream pins it by name; verbatim is enforced, not merely asked for. Left alone.
Expected first-run behaviour, and why it is not a defect
Until a maintainer creates the anchor issue here and sets the repository variable
HALF_STATE_ANCHOR_ISSUE(Settings, then Secrets and variables, then Actions, then Variables), a scheduled run will fail loudly at "Resolve the anchor issue" withNo anchor issue configured for objectstack-ai/objectos. The sweep still runs and its findings are preserved in the run summary. That is the designed behaviour and this PR deliberately adds no fallback, no default anchor number and no silent skip.The
pull_requesttrigger is the exception and applies to this PR: it runs the sweep, skips the anchor resolution and the anchor write entirely, and publishes the rendered body to the run summary. A PR must never rewrite the board's pinned view.Verification
All at
a85b502, working tree clean, exit codes captured before any pipe.node scripts/pm/check-half-states.mjs --self-test✓ check-half-states self-test: 1551 cases pass.node scripts/invoked-as.mjs --self-test✓ invoked-as self-test: 11 cases passpnpm install --frozen-lockfileDone in 2.8s using pnpm v10.28.2pnpm turbo run build --forceTasks: 1 successful, 1 totalpnpm turbo run test --force✓ 4 self-test(s) passednode .github/scripts/check-locale-surface.mjs✓ every advertised URL has a source file and every source file is advertisednode .github/scripts/check-node-floor.mjs --self-test✓ self-test: 17 rule case(s), 24 satisfies case(s) and 18 range case(s)node .github/scripts/check-node-floor.mjs✅ Every declared floor clears what the dependency tree requiresnode apps/docs/scripts/gen-zh-hant.mjs --check✓ zh-Hant: 73 generated file(s) match the zh-Hans sources byte for byte.check-node-floorandgen-zh-hant --checkwere not in the dispatch list; they were re-derived from the actual diff (a.github/workflows/**edit) and run anyway. No changeset flow and nopackages/in this repo.Generated by Claude Code