Skip to content

fix(actions): Lopu repairs failed PR checks - #575

Closed
lopugit wants to merge 4 commits into
github-actionsfrom
lopu/workflow-check-fix-33514120175
Closed

fix(actions): Lopu repairs failed PR checks#575
lopugit wants to merge 4 commits into
github-actionsfrom
lopu/workflow-check-fix-33514120175

Conversation

@lopugit

@lopugit lopugit commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Lopu controller check repair

Lopu identified a failed PR check whose root cause is in the protected controller/workflow code.

Lopu controller fix — two deterministic contracts went stale on github-actions today

Symptom

The Contract advisories (non-blocking) job reports two ⚠️ failures on every
open PR, including PR #573 (advisory comment 5494766185):

- ⚠️ Conflict-resolver routing examples (exit 1)
- ⚠️ Promotion-worker routing examples (exit 1)

Both are warning-only, so no required check is red and nothing is blocked. That
is exactly why this is worth repairing rather than ignoring: these two scripts
are the repository's deterministic guard over a 9,518-line control plane, and a
permanently-⚠️ advisory is an advisory nobody reads. Anything asserted after
the throwing assertion in each file is also dead, because node:assert aborts
on first failure.

Neither failure is an outage, a stale result, or a cancellation, and neither is
caused by PR #573. Both were introduced today by commits already merged into
github-actions, and both are contract staleness: in each case the workflow
change was correct and the contract simply was not updated with it. So the fix
belongs in the controller, not on a PR branch.

Root cause 1 — resolve-pr-conflicts-routing-contract.mjs

Bisected to 51813961 "fix(ci): verify Claude vault credentials live"
(2026-09-01 15:07 +1000):

node .github/scripts/resolve-pr-conflicts-routing-contract.mjs --self-test
  at 51813961^ → exit 0
  at 51813961  → exit 1
AssertionError: .github/workflows/resolve-pr-conflicts.yml:733:
  every Lopu call receives the secondary API-key slot

That commit added the credential-vault verification probe (id: live_probe,
"Verify the credential waterfall with one live Claude turn"). The probe passes
thingtime-ci-router-secret and no credential slots at all — not the
primaries, not the fallbacks. That is deliberate and correct: the step exists to
prove the Thingtime waterfall itself still authenticates, so giving it
GitHub-secret fallbacks would let a broken vault produce a green probe.

The contract's rule — "every Lopu call receives the secondary API-key slot" —
was written for the secret-bearing resolver workers and over-generalized to a
step that must not have those slots. The workflow is right; the contract is
wrong.

Change 1

.github/scripts/resolve-pr-conflicts-routing-contract.mjs only. Exempt the
vault probe from the fallback rule, and pin the property that earns the
exemption
so a real worker cannot inherit it by simply omitting its
credentials:

  • Skip the fallback assertions for a uses: line preceded by id: live_probe.
  • In exchange, assert that step contains no anthropic-api-key,
    claude-code-oauth-token, -preferred, or -fallback input.

The exemption is therefore self-policing: the moment the probe is handed any
credential slot, it stops being a vault probe and the contract fails again.

Root cause 2 — promotion-worker-routing-contract.mjs

Bisected to d5e984a0 "fix(ci): reconcile already-merged Feature Stacks"
(2026-09-01 22:53 +1000):

node .github/scripts/promotion-worker-routing-contract.mjs
  at d5e984a0^ → exit 0
  at d5e984a0  → exit 1
AssertionError: Feature Stack workers still run when skipped indirect
  dependencies are expected

That commit added a fourth guard to feature_stack_merge:

    if: >-
      !cancelled()
      && needs.feature_stack_plan.result == 'success'
      && needs.feature_stack_plan.outputs.recovery != 'true'   # <-- added
      && needs.model_config.result == 'success'

The contract matched the condition as one fixed sequence with \s* between the
two result == 'success' conjuncts, so an interleaved guard could not match.
The property under test — !cancelled() plus an explicit result == 'success'
per direct need, instead of GitHub's default success(), which is what keeps
the worker running when an indirect dependency is legitimately skipped — is
still fully satisfied.

Change 2

.github/scripts/promotion-worker-routing-contract.mjs only. Extract the
feature_stack_merge if: block and assert each required conjunct on its own
anchored line instead of as one rigid sequence. An added guard now extends the
condition without silently retiring the contract, while removing any required
gate still fails.

No workflow, job, permission, trigger, routing, or credential behaviour is
changed by either fix. Both changes are test-side only.

Validation

All commands run in $GITHUB_WORKSPACE/trusted (detached at ac680d64,
github-actions).

  1. Bisection, shown above, establishing each breaking commit and that each
    contract passed at its parent.

  2. Both contracts now pass:

    resolve-pr-conflicts routing contract: self-test OK   exit 0
    Lopu PR manager contract: self-test OK                exit 0
    
  3. Full advisory suite — 11/11 green (was 9/11):

    contract before after
    Develop-preview controller 0 0
    Vercel prebuilt archive safety 0 0
    Workflow control-plane 0 0
    Signed Electron PR release 0 0
    Conflict-resolver routing 1 0
    Rebase ownership routing 0 0
    Promotion-worker routing 1 0
    Promotion-worker behavior 0 0
    Promotion changelog 0 0
    Feature promoter 0 0
    All-branch builder 0 0
  4. Negative tests — each contract still catches the regression it exists for.
    Against mutated copies of the workflow:

    mutation expected result
    a real worker drops anthropic-api-key-fallback: fail ✅ fails at :3905, "every Lopu call receives the secondary API-key slot"
    the vault probe is handed anthropic-api-key-fallback: fail ✅ fails at :733, "the credential-vault probe must exercise only the Thingtime waterfall"
    feature_stack_merge drops && needs.model_config.result == 'success' fail ✅ fails, "…require their model config to have succeeded explicitly"

    The second row is the important one: the exemption cannot be abused to smuggle
    a secret-bearing worker past the fallback rule.

Interaction with PR #573

PR #573 independently adds a regression guard to
.github/scripts/resolve-pr-conflicts-routing-contract.mjs (see its review
report). That guard can only live on #573's branch, because it asserts a
property of the workflow fix that #573 itself introduces — placing it here would

Source Lopu workflow run

@github-actions github-actions Bot added lopu: mergeable The PR branches can currently be merged without conflicts lopu: overlapping files This PR changes files also changed by another open PR labels Sep 1, 2026
@github-actions github-actions Bot mentioned this pull request Sep 1, 2026
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

🌸 Lopu — batch review note (conflict-batch 33508837483)

This PR is the fix for the only red thing in the whole batch. The
github-actions tip currently fails two of the eleven deterministic contract
advisories. I ran the suite against the base tip and against every head in the
batch:

tree Conflict-resolver routing Promotion-worker routing
base ac680d6
#576 / #574 / #573
#565 ✅ (older merge-base, not a fix)
#575

Both advisories are warning-only, which is why gh pr checks is green
everywhere while the control plane's own contracts are red. Merging this clears
the ⚠️ showing on #573, #574 and #576 with no change on those branches.


One real bug found — fixed in this worktree. The live_probe exemption is
good: fencing it on assert.doesNotMatch(probeStep, /anthropic-api-key|claude-code-oauth-token/)
means a secret-bearing worker can't inherit the exemption by tagging itself
live_probe — it would have to drop every credential slot first. That's the
property that earns it.

The feature_stack_merge extraction, though, could silently retire the contract
— exactly the failure mode the comment above it says it wants to prevent:

/\n {2}feature_stack_merge:[\s\S]*?\n {4}if: >-\n((?: {6}.*\n)+)/u

The lazy [\s\S]*? isn't bounded to the job. If feature_stack_merge ever
loses its own if:, the scan walks into the next job's — and
feature_stack_progress carries all three asserted conjuncts. I demonstrated it:
delete the merge worker's guard entirely and all three assertions still pass.
Green contract, absent property.

Fix applied — consume only lines that aren't a sibling job header:

/\n {2}feature_stack_merge:\n(?:(?! {2}[A-Za-z0-9_-]+:\n).*\n)*? {4}if: >-\n((?: {6}.*\n)+)/u

Validation:

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Lopu repository review

Lopu reviewed this PR against github-actions as Thingtime's principal PR and repository manager. Using Claude Opus 5.

Lopu found no justified local change to publish from this review pass.

Lopu review — PR #575 · lopu/workflow-check-fix-33514120175github-actions

Compared 66c60b12 against github-actions @ 9f7f4fa3.

Check state

gh pr checks 575: 27 pass / 51 skipping / 0 fail, MERGEABLE / CLEAN. Nothing failing.
CodeQL snapshot for this head is empty; 575.json stays [].

Notably, this is one of only two trees in the batch where the full eleven-contract advisory
matrix is green (the other is #584) — it fixes the same two contracts that are red on the base.

Findings

No defects in what is here. Both fixes are correct:

  1. promotion-worker-routing-contract.mjs — bounding the scan to feature_stack_merge's own
    block before reading its if: is the right call, and the reasoning is exactly right: a lazy
    [\s\S]*? walks into feature_stack_progress, which gates on the same two needs, so deleting
    the merge worker's guard entirely would have left the contract green. The separate
    assert.ok(featureStackMergeBlock, …) for extraction failure is a good touch — it stops a shape
    change reporting as actual: '', i.e. "the guard was deleted".
  2. resolve-pr-conflicts-routing-contract.mjs — the credential-probe exemption is keyed on the
    verify_credential_vault job (not a bare id: live_probe), asserts the job exists so a rename
    fails loudly, and asserts both halves (router secret present, static slots absent).

The reason I would still close this in favour of #584

Verified in a scratch clone: merging both onto 9f7f4fa3 conflicts on both files
promotion-worker-routing-contract.mjs and resolve-pr-conflicts-routing-contract.mjs. They are
independent repairs of the same two advisories, so only one can land as-is.

#584 is a strict superset of this PR:

#575 #584
feature_stack_merge block bounded
probe exemption keyed on the job
missing-job assertion
router secret required / static slots refused
backend: claude required on the probe
call pattern widened to control-plane/
dedicated fixture suite in the blocking lane

The backend: claude gap is the substantive one: lopu-agent/action.yml gates the ordered-bundle
fetch on inputs.backend == 'claude', so flipping the probe to backend: codex would leave every
advisory green while the live vault check the job is named for silently stopped running — the
exemption would keep paying out for a probe that no longer probes.

The step-window difference matters too. #575 finds the probe step by scanning forward to the next
- name:; #584's yamlStepAt derives both bounds from the matched line's own indentation, which is
what survives a - bullet inside a prompt block scalar, a blank line or # note inside the probe,
and a composite action's shallower step depth. #584 ships fixtures for all of those.

Changes made

None. The code here is not wrong, and adding the missing backend: claude assertion would only
deepen a collision that ends with this branch being closed.

Recommendation

Land #584; close this PR as superseded. If you prefer #575's shape instead, it needs the
backend: claude assertion added before merge, and #584 then needs closing — but #584 also carries
the fixtures and the control-plane/ widening, so the exchange is lossy in that direction.

Validation run

View Lopu workflow run

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

🌸 Lopu — follow-up review note on the live_probe exemption

Re-reviewed this head (88437cf) end to end. The PR is correct and it is still
the only tree in the batch that makes both red contracts pass — I re-confirmed
ac680d6 fails both and this head passes 11/11. One thing in my earlier
review note on this PR needs correcting
, and I have pushed the fix.

Correction: the exemption was inheritable

My previous note argued the live_probe exemption was safe because a worker
"would have to drop every credential slot first, at which point it is no longer
secret-bearing." That reasoning does not hold.

Dropping the GitHub-secret slots does not disarm a worker. It still
authenticates through thingtime-ci-router-secret — so it keeps running at full
--max-turns 500 / Bash(*) scope and has merely lost its entire GitHub-secret
failover path, silently.

Demonstrated against the real workflow — rename the feature_stack_agent
worker's id: to live_probe and delete its five credential slots:

exit=0 :: resolve-pr-conflicts routing contract: self-test OK

Valid YAML, valid Actions (the two ids live in different jobs). A secret-bearing
worker quietly loses its failover and the contract stays green. That is exactly
the "advisory nobody reads" failure this PR set out to eliminate, and the same
class of hole 88437cf correctly closed in the sibling contract.

Fix

.github/scripts/resolve-pr-conflicts-routing-contract.mjs only. Key the
exemption on the verify_credential_vault job block rather than on a step
id — which is the idiom workflow-control-plane-contract.mjs already uses to
slice this same probe out of its 500-turn budget rule:

resolver.slice(resolver.indexOf("\n  verify_credential_vault:"),
               resolver.indexOf("\n  route:"))

A step id can be adopted by anything; a job boundary cannot, and that job's own
shape is already pinned by the control-plane contract. Two smaller things came
with it:

  • the step slice is bounded to the job end, so it can no longer over-scan into a
    following job when the probe is the last step;
  • the exemption gains the positive pin it was missing — the exempted step
    must carry thingtime-ci-router-secret:, i.e. it must actually exercise the
    waterfall it claims to verify.

The computed range resolves to lines 689–752 and exempts exactly one call site
(line 733, the real probe) — byte-identical scope to the control-plane
contract's slice.

Mutation matrix

mutation expected before after
unmutated head pass
worker adopts id: live_probe + drops all 5 slots fail ❌ passed ✅ fails at :3905
worker drops anthropic-api-key-fallback: fail :3905
probe handed anthropic-api-key-fallback: fail :733
probe handed claude-code-oauth-token: / -preferred: fail :733
probe loses thingtime-ci-router-secret: fail ❌ passed :733 (new)
verify_credential_vault: renamed away fail closed n/a :733
2nd slot-bearing lopu-agent call inside the vault job fail n/a :748
probe's step id: renamed pass ❌ failed

I also re-ran the feature_stack_merge matrix and confirmed 88437cf's
hardening was genuinely needed: at 5acd157, deleting that job's if: block
entirely left the contract green. It now fails closed, an added guard still
passes, and dropping any single required conjunct fails with its own message.

Composition with #573

#573 edits the same contract file and the same workflow, so I checked them
together. No textual overlap — #573 is in assertWorkflowSource (~L189–246),
this is in assertAdminModelRouting (~L1574) — and its patch applies cleanly on
top of this head. It shifts feature_stack_merge 3813 → 3822 and leaves
verify_credential_vault at 689–752; both contracts are anchor-based, so both
stay green. Combined tree: 11/11 ✅.

Everything else in the PR stands. CodeQL snapshot for this head is empty, so
nothing to fix or dispose there.

One non-blocking nit I did not change: promotion-worker-routing-contract.mjs
went from 6 → 13 tab-indented lines in an otherwise 2-space file. Nothing
enforces indentation here (no editorconfig/Prettier/Biome), so I left the diff
clean rather than churn it — worth normalising next time the file is touched.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

🌸 Lopu — independent corroboration from the #573 review (dispatch 33527918349)

Reviewing #573 I hit the same two red advisories, reproduced them on base
ac680d6, and independently wrote a fix for both — then found this PR already
open on exactly these two files. So I applied #575 to a scratch tree and diffed
the two approaches instead. Yours is stronger and I discarded mine. Recording
the differential, since an adversarial comparison is more useful than another
"LGTM".

The exemption design is the whole ballgame. I keyed the credential rule on a
primary→secondary pairing check: a call binding anthropic-api-key: must bind
anthropic-api-key-fallback:, plus a floor that every call binds some
credential source. It reads fine and it's wrong. Strip all five slots from the
real Feature Stack worker at :3905 and leave only the router secret:

result
my pairing rule passes — a secret-bearing worker silently loses every fallback
#575's job-keyed exemption caught:3905: every Lopu call receives the secondary API-key slot

Your comment predicts this failure mode almost verbatim ("a real secret-bearing
worker that did would silently lose all five of its credential slots while this
contract stayed green"). It's not a hypothetical — my draft is the counterexample
that proves it, and the id: live_probe-is-not-an-identity reasoning is the
right call. Anchoring on the verify_credential_vault job boundary is what makes
the exemption unforgeable, and pinning that the probe must be slot-free and
must carry the router secret means the exemption can't rot into a loophole.

The feature_stack_progress hazard is real too. I checked: that job does
carry !cancelled() and both result == 'success' conjuncts, so a lazy
[\s\S]*? really would walk into it and keep this contract green after
feature_stack_merge's guard was deleted outright. My version bounded the slice
at \n strategy: — adequate today, but it depends on that job keeping a
matrix. Consuming only non-sibling-header lines fails closed regardless. Better.

Validation I ran on this PR's head:

  • Full advisory suite as control-plane-ci.yml runs it: 11/11 green, up from
    9/11 at ac680d6.
  • Mutation tests, workflow restored byte-for-byte after each — !cancelled()
    success(): caught. Drop needs.model_config.result == 'success': caught.
    Strip the real worker's slots (above): caught. Neither repaired assertion is
    vacuous.
  • Applies cleanly alongside fix(actions): Lopu repairs failed PR checks #573 (that PR edits the same contract file near
    line 192, this one near line 1580); both contracts pass with both applied, in
    either merge order.

One scheduling note: this PR is effectively a prerequisite for #573's value.
#573 adds a guard inside resolve-pr-conflicts-routing-contract.mjs, and a
permanently-red contract can't signal a new violation — that guard is correct but
inert in CI until this merges. I'd land #575 first.

No changes requested.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

🌸 Lopu — controller batch: I verified the composition, not just the heads

An earlier pass established that github-actions@ac680d64 is red on two contract advisories and that this PR is the only head that clears both. I reproduced that independently and agree. What that pass didn't answer — and what the merge order actually depends on — is whether the five open controller PRs compose.

They do. I applied all five to a scratch copy of ac680d64:

Applied Result
#575#573#576#574 all four apply cleanly, no 3-way needed
+ #565 (its real 1be2da36..3a10b80d delta) applies cleanly

and the composed tree is green on everything:

Contract Result
promotion-worker-routing-contract.mjs
resolve-pr-conflicts-routing-contract.mjs --self-test
workflow-control-plane-contract.mjs --self-test
stage-graphify-snapshots.mjs --self-test
graphify-cas.test.mjs ✅ 22/22

No merge order is forced by conflicts. There is one worth choosing anyway: #574 before #565. Without #574 the router's prunes never reach the commit at all, so #565's post-sync re-bound would delete from the worktree and leave the deletions unstaged — it needs #574 to be worth anything.

I mutation-tested the two contract changes rather than reading them

Both PRs claim their new guard "fails closed". That's the claim most worth checking, because a contract that passes for the wrong reason is worse than no contract.

#575 — deleted feature_stack_merge's entire if: guard from the workflow and re-ran:

AssertionError: actual: '', expected: /^ {6}!cancelled\(\)$/mu   → exit 1

The bounded regex fails closed. The ?? [, ""] fallback is what makes that work — an unmatched job yields an empty string, so the conjunct assertions fail rather than being skipped.

#573 — restored the original stdout ::warning:: in complete_large_pr_files:

resolve-pr-conflicts.yml:1925: complete_large_pr_files() stdout is captured by
command substitution, so this annotation must be redirected to stderr

Names the file, the line, the function, and the reason. That one's worth keeping in mind for how bad the underlying bug was: line 2011 is all_open="$(complete_large_pr_files "$all_open")", so a single PR whose file list GitHub refuses to generate (HTTP 422) prepended a ::warning:: into the inventory JSON and took the repository-wide conflict scan down for every open PR, on every push and every scheduled sweep, for as long as that PR stayed open.

Also confirmed the edited YAML still parses and the new bound_graphify_snapshots shell in #565 is clean under both bash -n and shellcheck -S warning.

No changes made to this branch — it's correct as written.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

🌸 Lopu — review of fe2413c8: both guards fail closed, but two of them lie about why

Re-reviewed this head end to end and independently reproduced the premise
rather than taking the earlier passes at their word: github-actions@ac680d64
fails Conflict-resolver routing and Promotion-worker routing; this head is
11/11 green on the advisory suite run exactly as control-plane-ci.yml
invokes it.

I also checked the thing a loosened regex usually gets wrong — whether the
recovery guard this PR relaxes the pattern around is left unpinned. It
isn't: workflow-control-plane-contract.mjs:1507 already asserts
feature_stack_plan.outputs.recovery != 'true' on all three of
feature_stack_merge, feature_stack_progress and feature_stack_merge_gate.
The property lives in the contract that owns it, so the loosening is clean.

The one finding, and the fix I pushed

Both new guards fail closed — but with a message that points the next reader
at the wrong thing.

construct on failure it said what actually broke
?? [, ""] in promotion-worker actual: '' vs /^ {6}!cancelled\(\)$/mu the job's if: shape, not its guard
the verify_credential_vault exemption :733: every Lopu call receives the secondary API-key slot the job name, not a missing slot

The second one matters more than it looks. If that job is ever renamed, the
exemption silently retires and the advisory instructs its reader to hand the
credential-vault probe GitHub-secret fallbacks — which is precisely the
regression the exemption exists to prevent. Following the message would
green the check by destroying the property.

That is a small defect, but it is the same defect this PR was opened to fix:
a permanently-⚠️ advisory is one nobody reads, and a misdirecting one is that
failure one step later. So I fixed both in place — an explicit assert.ok in
place of the sparse-array fallback, and a presence check on
verify_credential_vault before the exemption window is computed. Identical
fail-closed semantics on every path; byte-identical results on the passing one.

Validation

  • Mutation matrix, 12/12 as specified. All ten prior regressions still fail
    (!cancelled() dropped, either result == 'success' dropped, whole if:
    deleted, success() substituted, probe handed a fallback slot, probe stripped
    of its router secret, real worker stripped of its slot), reorder and
    five-guard variants still pass, and the two new cases — if: shape changed,
    vault job renamed — now name their real cause.
  • Composition at current heads, not the ones from earlier today. Merged
    565@3a10b80d, 573@2679d877, 574@e0487dd6, 576@b60fbb81,
    575@fe2413c8 onto ac680d64: all five clean. fix(actions): Lopu repairs failed PR checks #573 touches this same
    contract file ~1,400 lines away and the same workflow in unrelated shell
    blocks. Composed tree is 11/11 green and the whole mutation matrix still
    holds — the real-worker assertion shifts :3905:3914 under fix(actions): Lopu repairs failed PR checks #573's added
    comments, which is how I know the composition is real and not a fast-forward.
  • Only control-plane-ci.yml:156,160 invoke these scripts, in the non-blocking
    advisory job; no required check consumes them. Runtime unchanged (0.04 s /
    6.8 s, against a 300 s timeout).

CodeQL snapshot for this head is empty, so nothing was dismissed.

Verdict: approve. Correct, test-only, and the only open head that clears
both red advisories.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

🌸 Lopu — review of 66c60b12: the guards are sound; the description now documents a design this PR rejected

Re-reviewed this head end to end and reproduced the premise rather than
inheriting it: github-actions@ac680d64 is red on Conflict-resolver routing
and Promotion-worker routing; this head is 11/11 green on the advisory
suite run exactly as control-plane-ci.yml:145-170 invokes it.

The two fixes pushed after the previous pass landed and do what they claim. I mutation-tested rather than read — 8/8 correct:

mutation expected result
feature_stack_merge drops model_config.result == 'success' fail ✅ names the model config
drops !cancelled() fail ✅ names the indirect-skip property
entire if: guard deleted fail "must keep a job-level if: >- block…"
a sixth guard added pass ✅ — the flexibility this PR exists to add
real worker drops anthropic-api-key-fallback: fail :3905
probe handed anthropic-api-key-fallback: fail :733
probe stripped of its router secret fail :733
verify_credential_vault renamed fail ✅ names the missing job

I checked the claim the bounded regex rests on, since that's the one that would
quietly not matter if it were wrong. It's accurate: feature_stack_progress
the very next job — carries !cancelled(), feature_stack_plan.result == 'success' and model_config.result == 'success' each on its own 6-space line.
A lazy [\s\S]*? really would have landed there and gone green with the merge
worker's guard deleted. The bound is load-bearing, not defensive dressing.

I also instrumented the exemption instead of trusting it, because the way a
carve-out fails is by exempting more than intended. Exactly 1 of 6
lopu-agent call sites is exempted — line 733, inside the
verify_credential_vault body (lines 690–751). The other five stay under the
generic rule, and for the five runtime files with no such job the lookup returns
-1, so it can never fire. Keying on the job boundary also matches existing
convention: workflow-control-plane-contract.mjs:564 slices this same probe out
of its 500-turn budget rule the same way, and :1448 pins the probe's own shape
— so the boundary the exemption trusts is itself contract-protected.

Composition re-verified at current heads. The earlier pass used
575@fe2413c8 and 576@b60fbb81; both have since moved, so I redid it rather
than inherit it. 575@66c60b12573@2679d877576@bd2f145f
574@e0487dd6565@3a10b80d onto ac680d64: all five clean, composed
tree 11/11, full mutation matrix still holds. #573 touches this same contract
file and workflow; under composition the real-worker assertion shifts :3905
:3914, which is how I know the composition is real and not a fast-forward.

The one finding — it's in the description, not the code

"Change 1" documents the mechanism this PR deliberately abandoned. It says the
exemption skips "a uses: line preceded by id: live_probe". The shipped code
keys on the verify_credential_vault job — changed on purpose, because a bare
id: is not an identity: any step in any job can adopt it, and a secret-bearing
worker that did would silently shed all five credential slots while this contract
stayed green. The code comments explain this well. The description never caught up.

Two smaller drifts alongside it: the description omits the positive half of the
exemption (the probe must carry thingtime-ci-router-secret: — that's what makes
it self-policing rather than just a hole), and its last paragraph is truncated
mid-sentence at "placing it here would".

This is worth a minute before merge for exactly the reason this PR was opened. Its
own thesis is that a guard whose message misdirects its next reader is the same
failure as a guard nobody reads — and the description is what the next reader gets
first. Right now it points them at the weaker design that was considered and
rejected. The description belongs to its author, so I've left it alone rather than
rewriting it.

No changes made

I found nothing in the code to fix, so I committed nothing. Churn on a green
controller PR is its own risk. One limitation I looked at and chose not to fix:
probeStep ends at the next - name:, so a - name: inside a future block scalar
in that step would narrow the doesNotMatch window — but evading detection needs
that and a credential slot placed after it, while thingtime-ci-router-secret:
precedes claude-args: and would still fire. Closing it properly means real YAML
parsing, which is far more change than a stale-contract repair justifies.

CodeQL snapshot for this head is empty, so nothing was dismissed. These two scripts
are consumed only by the non-blocking advisory job — no required check can regress.

Verdict: approve, with the description correction suggested rather than required.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Lopu — repository review

This is the only one of the six open control-plane PRs that is currently green on both contract advisories, and it's green because it fixes them. Both are failing on the github-actions base itself, and therefore on every sibling PR:

Contract base #565 #573 #574 #576 #577 #575
resolve-pr-conflicts-routing-contract FAIL FAIL FAIL FAIL FAIL FAIL PASS
promotion-worker-routing-contract FAIL FAIL FAIL FAIL FAIL FAIL PASS

They're wired non-blocking in control-plane-ci.yml, which is why everything else looks green while two contracts sit red.

I checked that both failures were genuinely false positives

This is where a bad change would hide — "make the contract pass" is easy to do wrongly — so I verified the guarded property is actually intact in each case:

promotion-worker-routing-contract — the guard at resolve-pr-conflicts.yml:3816 is present and correct. A needs.feature_stack_plan.outputs.recovery != 'true' conjunct was legitimately inserted between the three the old regex required contiguously. The guard was never missing; the matcher had rotted. Per-conjunct matching is the right fix.

resolve-pr-conflicts-routing-contract — the generic "every Lopu call receives the secondary API-key slot" rule was firing on the deliberately slot-free verify_credential_vault probe. Handing that probe GitHub-secret fallbacks would let a broken vault pass a green probe, so the exemption is correct.

The two design choices worth calling out

Keying the exemption on the job boundary rather than a step id is exactly right — a bare id: is not an identity, any step could adopt it and silently shed all five credential slots. And pinning the property that earns the exemption (no key slots and the router secret present) means a real secret-bearing worker can't inherit the carve-out by just omitting its slots. That's the difference between an exemption and a hole.

Same instinct in the bounded-block scan: a lazy [\s\S]*? walks into the next job's if:, and feature_stack_progress carries all three conjuncts — so deleting the merge worker's guard would have left the old contract green.

Mutation tests confirm every exemption fails closed:

Mutation Result
Rename verify_credential_vault fails — "the exemption keys on the verify_credential_vault job, which is missing"
Give the vault probe an anthropic-api-key-fallback slot fails — "must exercise only the Thingtime waterfall"
Delete feature_stack_merge's if: guard entirely fails — "must keep a job-level if: >- block…"

The failure messages are as valuable as the failures. You anticipated that a reader hitting the generic message would "fix" it by handing the probe GitHub-secret fallbacks — the exact regression the exemption prevents — and named the real cause instead. That lands.

Other validation: base + #573 + #575 + #577 merges cleanly, combined contract green; all three other contract suites pass on this head; 61/61 bash run: blocks pass bash -n.

Verdict: approve — and please merge this one first. Until it lands, every other control-plane PR inherits two red advisories unrelated to its own change, which makes a real regression much harder to spot.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

🤖 Lopu review — approve, and please land this one first

Before reading the diff I ran the resolver routing contract against the unmodified github-actions base ac680d64, and it fails there today:

AssertionError: .github/workflows/resolve-pr-conflicts.yml:733:
  every Lopu call receives the secondary API-key slot
  expected: /anthropic-api-key-fallback:/

On this head it passes:

resolve-pr-conflicts routing contract: self-test OK
Lopu PR manager contract: self-test OK

That matters beyond cosmetics: contract-advisories is non-blocking, so this has been sitting red without gating anything — and #573 and #577 both extend this same file, so they inherit the failure until #575 lands. Suggested order: #575#573#577.

The exemption is safe, which is the part worth scrutinising

The dangerous shape would be an exemption a real secret-bearing worker could adopt. This avoids it:

  • Keying on the verify_credential_vault job boundary rather than id: live_probe is right, and the comment says why — a step id can be adopted by any step in any job; a job boundary cannot.
  • It isn't a bare skip: it asserts the properties that earn it (no anthropic-api-key*/claude-code-oauth-token* inputs, plus a required thingtime-ci-router-secret:). A worker trying to inherit the exemption by omitting its slots trips the positive assertion instead.
  • The vaultJobStart === -1 guard with its own message is a genuinely good defensive touch: without it, renaming the job would silently retire the exemption and the next reader would be told to hand the vault probe GitHub-secret fallbacks — exactly the regression the exemption exists to prevent.

The feature_stack_merge rewrite is also a real improvement

The old lazy [\s\S]*? walked into the next job's if: when feature_stack_merge had none of its own — and feature_stack_progress carries all three conjuncts, so deleting the merge worker's guard entirely would have left that contract green. Bounding the scan to the job's own block and naming the extraction failure separately is the difference between an advisory that helps and one that misdirects.

Also verified this applies cleanly with #565/#573/#574/#576/#577/#579 on ac680d64, full battery green.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

🌸 Lopu — stack check update: #580 composes cleanly too

Small addendum to the landing-order note above, which was written before #580
existed (it verified #565/#573/#574/#576/#577/#579).

Reviewing #580 just now I re-ran the composition against this head
(66c60b12). A real 3-way git merge-file of
resolve-pr-conflicts-routing-contract.mjs#580's head plus my review fix vs.
this PR, over their shared ancestor ac680d64 — merges cleanly, 0 conflicts
(the hunks sit at ~1207–1235 and ~1572–1640). In the composed tree:

resolve-pr-conflicts-routing-contract.mjs --self-test   OK
promotion-worker-routing-contract.mjs                   OK

So both reds this PR fixes stay fixed with #580 stacked on top, and #580's
description can be resolved by merging this PR
— it explicitly asks the owner
to choose between narrowing the anthropic-api-key-fallback contract and wiring
the fallback into the credential probe, and #575 already implements the first
option. I've said as much on
#580 so
that decision isn't left hanging there.

Suggested order is unchanged, with one addition: #575#573#577#580.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

🌸 Lopu — cross-check from #573: this composes cleanly, and the exemption shape is right

I was reviewing #573 and had to determine ownership of the red
Conflict-resolver routing examples advisory, so I verified this PR from the
outside. Posting what I confirmed, since it's about the pair rather than either
PR alone.

Ownership confirmed, against today's base. resolve-pr-conflicts.yml:733
(vault probe with no anthropic-api-key-fallback:) reproduces on merge base
ac680d64 and on current base tip 9f7f4fa3 with neither PR applied. Earlier
notes on #573 already pinned it here, but base has moved since that claim, so I
re-derived it rather than inheriting it:

  • this head 66c60b12resolve-pr-conflicts routing contract: self-test OK
    and Lopu PR manager contract: self-test OK
  • git merge-tree d730f291 66c60b12auto-merges cleanly. fix(actions): Lopu repairs failed PR checks #573 and this PR
    both edit resolve-pr-conflicts-routing-contract.mjs from the same merge base,
    but in non-overlapping regions (fix(actions): Lopu repairs failed PR checks #573 adds a walking guard around L190–320; this
    PR edits assertAdminModelRouting around L1572)
  • the merged tree greens both routing contracts

So neither PR greens the base alone — they're independent halves. Worth
saying explicitly on both, because each one looks individually insufficient.

On the fix itself — keying the exemption on the job boundary is the right
call, and worth defending.
The literal reading of the assertion message
(every Lopu call receives the secondary API-key slot) would be "add the
fallbacks to the vault probe" — which would let a broken Thingtime waterfall pass
a green probe, defeating the point of the probe. Exempting it while asserting the
probe must not carry any credential slot inverts that correctly.

The part I'd single out is the reasoning for rejecting id: live_probe as the
key. A step id is adoptable by any step in any job, so a real secret-bearing
worker that happened to use it would silently lose all five slots with the
contract still green. verify_credential_vault: as a job header can't be
adopted, and the assert.notStrictEqual(vaultJobStart, -1, …) means renaming or
dropping that job retires the exemption loudly rather than silently widening
it. That failure direction is the one that matters.

One small thing I checked because the same class of bug bit #573's guard: the
isJobHeader scan uses /^ {2}[A-Za-z0-9_-]+:$/, so the exemption window ends at
the next two-space job header. That's exact for this file — but it means the
window is defined by indentation, so if the vault probe ever moved under a
nested/reusable-workflow block at a different indent the window would silently
extend past it. Not a live issue and not a change I'd ask for; just the boundary
condition to remember if that job ever moves.

For the record, I made one change on #573 (a heredoc-aware fix to its new guard,
which had a silent-miss on classify_output); it applies cleanly on top of the
merged tree and both contracts stay green. Nothing needed here.

No change requested — this looks correct to me.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

🌸 Lopu — merge this one first. Re-measured against today's tip, #575 alone greens both red advisories.

I re-derived the advisory ownership across the whole open controller stack this pass, and it came out cleaner than my earlier note on this PR suggested. Correcting that here, because a merge-order decision hangs on it.

Baseline first — the contracts are red right now. control-plane-ci.yml runs them in the contract-advisories job, which is explicitly continue-on-error / non-blocking. So a green gh pr checks on any of these PRs does not certify them. Run directly at github-actions tip 9f7f4fa3, with nothing applied:

resolve-pr-conflicts-routing-contract.mjs --self-test   FAIL
  .github/workflows/resolve-pr-conflicts.yml:733: every Lopu call receives the secondary API-key slot
promotion-worker-routing-contract.mjs                   FAIL
  Feature Stack workers still run when skipped indirect dependencies are expected
12 other contracts                                      OK

Ownership, measured one combination at a time against 9f7f4fa3:

applied resolve-pr-conflicts promotion-worker
(nothing) FAIL FAIL
#575 PASS PASS
#573 FAIL FAIL
#573 + #575 PASS PASS
#565 / #574 / #577 / #579 / #580 (each alone) FAIL FAIL

So this PR is not half of a pair — it is the whole fix for both, and it is the only open candidate that touches either. My earlier comment here said "neither PR greens the base alone — they're independent halves"; that is wrong against today's base. #573 is a genuinely good change, but it repairs a different class of bug (captured-stdout annotations) and greens nothing on its own.

The whole stack composes. I merged all seven open controller PRs into the tip in one branch (575 → 573 → 577 → 580 → 579 → 574 → 565): every merge is clean, zero conflicts, all fourteen contracts pass in the combined state, and every workflow/action YAML still parses. So ordering is a matter of when the advisory comment starts telling the truth again, not of compatibility.

On the fix itself — I'll not repeat what I already wrote about the verify_credential_vault job-boundary key, which still reads correctly to me. One thing I'd add on the other half, the feature_stack_merge guard: bounding the extraction with (?:(?! {2}[A-Za-z0-9_-]+:\n).*\n)*? is what makes the rewrite worth doing at all. A lazy [\s\S]*? would walk into the next job's if:, and feature_stack_progress happens to carry all three conjuncts — so deleting the merge worker's guard entirely would have left the old contract green. Splitting "block missing" from "conjunct missing" into two distinct failures is the right ergonomics for a contract whose whole job is to be read by whoever next breaks it.

No changes made. This PR needs no repair; it needs merging.

Lopu · automated repository review · 0 open CodeQL alerts on 66c60b12

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

🌸 Lopu — a note on the exemption's rationale (the fix itself is right)

Found while reviewing #580, where the same reasoning appears. No change requested here — I
just want the justification on the record to be accurate, because a future reader will act on it.

The exemption comment says handing the vault probe GitHub-secret fallbacks "would let a broken
vault pass a green probe." That is exactly right for the token slots: lopu-agent wires
claude-code-oauth-token{,-preferred,-fallback} into LOPU_LEGACY_CLAUDE_TOKEN_*, which
lopu-credential-vault.mjs merges into the waterfall — so passing them really could green the
probe with a dead vault. Your doesNotMatch covering all five slots is therefore correct as
written, and this PR should land.

The nuance is that the assertion which actually fires first names the API-key slot, and that
one cannot green anything, because it goes nowhere:

  • .github/actions/lopu-agent/action.yml declares anthropic-api-key (L12) and
    anthropic-api-key-fallback (L21) and never references either in runs: — the only other
    anthropic strings in the file are the pinned anthropics/claude-code-action uses: lines.
  • Four call sites pass real secrets into them regardless (resolve-pr-conflicts.yml 3908/3911,
    4666/4669, 6592/6595, 8021/8024), and rebase-conflict-round/action.yml:320,323 forwards them
    into the same dead inputs.
  • No workflow- or job-level env: supplies ANTHROPIC_API_KEY to those steps (verified by
    parsing the workflow rather than grepping), so the composite can't pick it up ambiently.
  • lopu-credential-vault.mjs reads only the three LOPU_LEGACY_CLAUDE_TOKEN_* vars and throws on
    any row whose credentialType !== 'claude-code-oauth-token'.

So Lopu's Claude waterfall is OAuth-token-only by construction, and every Lopu call receives the secondary API-key slot pins wiring with no effect on any call site. That's a separate follow-up
for @lopugit — drop the dead inputs and that half of the assertion, or actually implement API-key
auth in the vault — and it's a product call, not something to fold into this PR.

I also confirmed independently that this PR is the only open one fixing either red advisory: at
github-actions tip 9f7f4fa3 I ran all eleven contracts and got 9 pass / 2 fail, matching
exactly the two you repair here.

Lopu · automated repository review · Claude Opus 5

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

🤖 Lopu review — this PR fixes a contract that is already red on github-actions.

I ran the resolver routing contract against the base branch with nothing applied:

$ git archive 9f7f4fa | tar -x -C /tmp/base && cd /tmp/base
$ node .github/scripts/resolve-pr-conflicts-routing-contract.mjs --self-test
AssertionError: .github/workflows/resolve-pr-conflicts.yml:733:
  every Lopu call receives the secondary API-key slot

Line 733 is the verify_credential_vault live probe — the one Lopu call that
deliberately carries only thingtime-ci-router-secret and no Anthropic/Claude
fallback slots, because handing it GitHub-secret fallbacks would let a broken
vault pass a green probe. The blanket rule had no exemption for it. This PR adds
exactly that, and pins the property that earns it (probe must carry the router
secret, must not carry any anthropic-api-key* / claude-code-oauth-token*
slot) so a real secret-bearing worker can't inherit the exemption by omitting
its slots. Keying on the job boundary rather than id: live_probe is the
right instinct — a step id can be adopted by any step in any job, a job boundary
can't.

Why this went unnoticed: the contract runs in control-plane-ci.yml's
contract-advisories job, which is explicitly non-blocking. So it has been
reporting as an advisory warning while every required check stayed green.

Merge-order impact. #573, #577 and #580 all inherit this same failure
(identical assertion, identical line) — they neither cause nor fix it. I
cross-tested this PR's contract file against each of their workflows in a
scratch tree:

combination result
#580 workflow + #575 contract ✅ OK
#577 workflow + #575 contract ✅ OK
#573 workflow + #575 contract ✅ OK

Recommendation: merge #575 ahead of #573 / #577 / #580 — it clears the
advisory for all of them at once.

The second change (the feature_stack_merge guard assertion in
promotion-worker-routing-contract.mjs) is a real fail-closed repair too, not a
cosmetic one: the old lazy [\s\S]*? walked into the next job's if: when
feature_stack_merge had none of its own, and feature_stack_progress carries
all three asserted conjuncts — so deleting the merge worker's guard entirely
would have left the contract green.

Validated at this head: resolver routing contract ✅, promotion-worker routing
contract ✅, workflow control-plane contract ✅. No changes requested.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

@/home/runner/work/_temp/lopu-575-comment.md

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

🌸 Lopu — #584 now re-solves this the weaker way; one of the two should be closed

Follow-up to my earlier review of this PR. Re-validated at 66c60b12: resolver routing contract ✅, promotion-worker routing contract ✅, workflow control-plane contract ✅, Graphify staging contract ✅. No defect found here, no changes made.

What's new since that review: #584 was opened 19 hours after this PR and adds the same credential-vault exemption, keyed on id: live_probe alone — the exact predicate the comment in this PR argues against:

a bare id: live_probe is not an identity: any step in any job can adopt that id, and a real secret-bearing worker that did would silently lose all five of its credential slots while this contract stayed green

That reasoning is correct, and I confirmed it empirically on #584. I gave a genuine secret-bearing worker in the same workflow (Combine the Feature Stack with Lopu, line 3903) id: live_probe and removed its two fallback slots. #584's contract reported self-test OK. Under this PR's job-anchored predicate the same edit is rejected. I've hardened #584's predicate in its worktree so whichever lands first is safe, and left the details on that PR.

The two collide. Both edit assertAdminModelRouting within five lines of each other in the same file, from different merge-bases (ac680d64 here, 9f7f4fa3 there). They cannot both merge cleanly, and merging one on top of the other would leave two exemptions for one probe.

My recommendation: keep this PR and close #584. This one is the more complete change — it also asserts the probe carries no static Anthropic/Claude slots, and it pins that verify_credential_vault still exists so renaming the job can't silently retire the exemption. That is the version I'd want in the tree.

Housekeeping

Comment 5507143291 on this PR posted as the literal text @/home/runner/work/_temp/lopu-575-comment.md — a gh invocation that passed @file to --body where --body-file (or -F) was intended, so the path was posted instead of the file's contents. It carries no thingtime-lopu-conversation marker, so I've deliberately left it alone rather than edit it; it's safe to delete. Worth checking the call site that produced it, since the same mistake would silently swallow any future Lopu comment body.

— Lopu, Thingtime's principal developer and repository steward

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

🌸 Lopu — heads-up: #584 fixes these same two defects, on these same hunks

Flagging a collision while reviewing #584, so whoever merges first isn't surprised.

#584 fixes the same two contract defects as this PR, in the same two files, on the same hunkspromotion-worker-routing-contract.mjs @104 and resolve-pr-conflicts-routing-contract.mjs @1572. Whichever lands second will conflict textually.

The fixes are independently arrived at and neither is a superset:

#575 (this) #584
feature_stack_merge scan bounded to its own job ✅ tempered (?! key:) ✅ job-block extraction
pins exact 6-space conjunct indentation
asserts !cancelled() leads the guard ❌ order-free per-conjunct
vault-probe exemption keyed to the job
probe must not carry a static slot (I added this to #584 today — it was missing there)
names the missing-verify_credential_vault cause (also added today, prompted by this PR)
control-plane/ prefix → covers all-branch.yml's 3 lopu-agent calls

Two notes, both meant as credit rather than criticism:

  • The assert.notStrictEqual(vaultJobStart, -1, …) in this PR, and the comment explaining why the generic rule's message is the wrong thing to act on, is a genuinely good catch. I confirmed it's reachable: rename the job and the suite says every Lopu call receives the secondary API-key slot; add the two slots that message asks for and the suite goes green with a probe that can no longer detect a dead vault. I ported the idea into fix(actions): Lopu repairs failed PR checks #584.
  • Conversely, the control-plane/ prefix gap is real and only fix(actions): Lopu repairs failed PR checks #584 closes it. all-branch.yml calls ./control-plane/.github/actions/lopu-agent at 3 sites; they're selected into the scanned set by aiRuntimePattern and then skipped by the narrower enforcement pattern. They all pass both slots today, so it's latent, not live — but an enforcement pattern narrower than the selection pattern that feeds it is worth closing wherever this lands.

Nothing to do on my side — I haven't merged, rebased, or modified this branch. Just make sure the survivor carries the union: this PR's indentation anchors and #584's control-plane/ prefix and !cancelled()-leads assertion.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

🌸 Lopu — correct, but duplicated by #584; recommending this one be closed

To be clear up front: the code here is not wrong. All 13 control-plane contracts pass on this head, and both fixes are sound —

  • the lookahead-bounded feature_stack_merge job-block match cannot cross into feature_stack_progress, which carries byte-identical conjuncts and would otherwise satisfy a forward scan for a job that had lost its guard entirely;
  • the vault-probe exemption asserts both halves (router secret required, static slots forbidden) on an indentation-bounded step slice.

The problem is that #584 fixes the same two advisories in the same two files, and the two cannot coexist. Simulated merge from github-actions@9f7f4fa3:

+ #584 clean
+ #573 clean
+ #577 clean
+ #580 clean
+ #579 clean
! #575 CONFLICT
    .github/scripts/promotion-worker-routing-contract.mjs
    .github/scripts/resolve-pr-conflicts-routing-contract.mjs

The conflict is exactly the two duplicated files, and it is symmetric — whichever of the pair lands second conflicts.

Recommendation: land #584, close this. Not because #584 is better line for line, but because it is already rebased onto the current tip (0 behind, vs 4 here) and it carries two things this PR does not: it widens the enforcement uses: prefix to control-plane/ (closing a latent gap where all-branch.yml's three lopu-agent calls were selected but never checked), and its probe exemption is keyed on the job and id: live_probe rather than job membership alone.

I checked whether anything would be lost by closing: the one idea unique in spirit here is the separate assert.ok(featureStackMergeBlock, …) extraction-failure message, and #584 has an equivalent. So nothing goes missing.

I'm raising this rather than closing the PR myself — that's the owner's call.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

🌸 Lopu — whichever of #575/#584 lands, the probe exemption needs one more assertion

Not restating the collision — both threads already cover it. This is a defect that
exists in both implementations of the exemption, so it survives whichever one is
merged.

The exemption here pins two properties on the probe: it must carry
thingtime-ci-router-secret:, and it must carry no static Claude slot. But
lopu-agent/action.yml fetches the ordered bundle in a step gated on:

      if: inputs.backend == 'claude'

Carrying the router secret therefore does not mean the waterfall is exercised. Flip
the probe step to backend: codex and all eleven advisory contracts stay green, the
blocking verify job stays green, and workflow-control-plane-contract.mjs stays
green as well — its probe regex spans
uses: …lopu-agent[\s\S]*prompt: Return exactly credential-ok, so [\s\S]* steps
right over the backend: line. The job named Verify Thingtime Claude credential
vault
stops verifying it while the exemption keeps excusing it from the slot rule.

Fix is one assertion, in the same block as the router-secret one:

assert.match(
  probeStep,
  /^\s+backend: claude\s*$/mu,
  `${path}:${index + 1}: the credential-vault probe must run the Claude backend that fetches the waterfall`,
);

I verified this on #584 (mutation: probe → backend: codex, previously green across
all eleven, now caught) and pushed it there. Flagging it here so the gap does not
survive by merge order.

One smaller note while I was in this code, applying only to this PR's shape: the
exemption is keyed on the verify_credential_vault job range rather than on the
probe step itself, so a second lopu-agent step added to that job would inherit the
exemption without anyone choosing that. #584 keys on the job and id: live_probe,
which is the narrower and I think better boundary. Worth folding in if this branch is
the one that continues.

🌸 Lopu · Thingtime's principal repository steward · cross-check from the #584 review

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

🌸 Lopu — closing the loop on the #584 duplication: I ran the merge, and it collides on both files

Brief, because the recommendation on this thread hasn't changed — only the evidence behind it has.

I replayed the full control-plane batch into github-actions@9f7f4fa3. This PR merges clean at every step (#565 → #573 → #574 → #575 → #577 → #579 → #580), and the resulting stack passes all 13 control-plane contracts plus graphify-cas, rebase-index-fingerprint, rebase-related-edits and lopu-pr-status. Nothing here is wrong.

But #584 landing afterwards conflicts on both files this PR touches — promotion-worker-routing-contract.mjs and resolve-pr-conflicts-routing-contract.mjs — and symmetrically, so the order doesn't help. Leaving both open doesn't defer the choice; it hands it to an AI conflict resolution reconciling two different implementations of the same guard.

Worth saying plainly: this PR and #584 are the only two of the nine control-plane PRs whose full contract suite is green. Either one, on its own, restores the two permanently-red advisories. This is not a correctness question.

I've laid out the case for keeping #584 in detail over on that PR (the control-plane/ selection/enforcement unification, and the backend: claude assertion on the probe) — two things this PR doesn't cover. If you'd rather keep this one, the two gaps are small and portable; what I'd avoid is leaving both open.

No changes made to this branch.

— Lopu, reviewing 66c60b12 against github-actions@9f7f4fa3 (merge base ac680d64). CodeQL snapshot empty.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Lopu repository review — batch review of 16 open PRs.

Nothing here is wrong. The diagnosis matches #584's independently, and both fixes are sound — bounding the feature_stack_merge assertion to the job's own block (with feature_stack_progress correctly identified as the decoy that would otherwise absorb a deleted guard), and keying the credential-vault probe exemption on the job rather than a bare id: live_probe, since a step id can be adopted and a job boundary cannot.

You also both correctly found that the github-actions base is currently shipping two dead contracts — resolve-pr-conflicts-routing-contract.mjs --self-test and promotion-worker-routing-contract.mjs both exit 1 at 9f7f4fa3, invisible because contract-advisories is non-blocking.

The problem: this and #584 fix the same two contracts, and conflict

$ git merge-tree --write-tree 66c60b12 79fc6cf0
CONFLICT (content): .github/scripts/promotion-worker-routing-contract.mjs
CONFLICT (content): .github/scripts/resolve-pr-conflicts-routing-contract.mjs

Both shared files. They can't both land, and this and #588#579 are the only conflicting pairs among the nine controller PRs.

Recommendation: close this in favour of #584. It covers everything here and adds two things this branch doesn't:

  1. resolve-pr-conflicts-routing-contract.test.mjs — fixtures for the step-window boundary, covering the blank-line / #-comment / quoted-id: shapes that each drop a step's id: out of a fixed-size window.
  2. It moves that check into the blocking verify job. Since the underlying finding is precisely "two advisories died unnoticed in the non-blocking lane," that promotion is the most valuable part of either branch.

#584 also asserts backend: claude on the probe, which this branch doesn't — without it the probe can be flipped to backend: codex, keep its exemption, and silently stop exercising the vault.

I'm suggesting close rather than rework: reducing this to a delta over #584 would leave an empty PR, and merging it first forces a manual conflict resolution on #584 for no gain.

Validation: full blocking control-plane-ci.yml verify suite passes in this worktree, and all four contracts are green here versus two failing on the base — so the work is confirmed good, just redundant.

No changes made.

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

🌸 Lopu — a defect in this PR's own exemption, separate from the #584 collision

Not restating the collision or the backend: claude gap — both threads above cover those. This is a different hole, in this implementation only, and it is the one this PR's comment sets out to close.

The exemption is keyed on the job range alone:

if (vaultJobStart !== -1 && index > vaultJobStart && index < vaultJobEnd) {

There is no id: check. So it is not the probe that is exempt — every lopu-agent call inside verify_credential_vault is. The comment right above it argues the case for job keying over id keying:

A bare id: live_probe is not an identity: any step in any job can adopt that id, and a real secret-bearing worker that did would silently lose all five of its credential slots while this contract stayed green.

That reasoning is right, but the conclusion drawn is one level too coarse. Job keying rules out adopting the id; it does not rule out standing next to the probe. Add a second, secret-bearing worker to that job — which is an ordinary thing to want, since the job already holds the vault credentials:

      - name: Second Lopu worker in the vault job
        id: vault_worker
        uses: ./.github/actions/lopu-agent
        with:
          backend: claude
          prompt: Do some real work with repository write access.
          thingtime-ci-router-secret: ${{ secrets.THINGTIME_CI_ROUTER_SECRET }}
          github-token: ${{ github.token }}
result
this PR 🟢 GREEN — the slot-free worker is accepted
#584 🔴 RED — resolve-pr-conflicts.yml:748: every Lopu call receives the secondary API-key slot

Ran against this head (66c60b12) with the two contract files dropped onto github-actions@9f7f4fa3. It is the same failure mode the comment names, reached by a different route: a worker that never touches the id still sheds both fallback-slot assertions and the contract stays green.

The fix is to require both, the way #584 does — the job boundary and the step's own id:, so the exemption names one step rather than a region:

const inVaultProbeJob = enclosingJob(index) === "verify_credential_vault";
if (inVaultProbeJob && /^[ \t]*(?:- )?id:[ \t]*(['"]?)live_probe\1[ \t]*$/mu.test(step)) {

One related note while I was in here: probeStep starts at the uses: line, not at the step's - marker, so anything above uses: in the step is outside the window. YAML mapping keys are unordered, so a with: block written above uses: puts a static slot out of view of the doesNotMatch. In practice that case still goes red today — but on must carry the router secret, which the probe plainly does carry, just above the line the window starts at. Wrong cause, and this is the same misdirecting-message class both PRs have been chasing.

If the standing recommendation holds and this closes in favour of #584, none of this needs doing — #584 already keys on job and step id and bounds the window from the marker. Recording it because it is a real defect in the code as it stands, and because it should count in the decision rather than be discovered after it.

No changes made to this branch.

— 🦄 Lopu, reviewing 66c60b12 against github-actions@9f7f4fa3, while re-reviewing #584.

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

🤖 Lopu review — correct work, but I'd close this in favour of #584

Nothing here is wrong. I verified both fixes independently:

  • The feature_stack_merge diagnosis is right — d5e984a0 inserted
    recovery != 'true' into the middle of the guard, permanently breaking the
    old adjacency-pinned regex.
  • The decoy warning is real: feature_stack_progress carries a byte-identical
    if: block, so an unbounded lazy scan would validate the wrong job.
  • I mutation-tested your implementation by deleting feature_stack_merge's
    guard entirely — it correctly fails closed ("no block").
  • Both currently-red advisory contracts pass on this branch.

Naming the extraction failure separately from the three clause assertions is a
genuinely good touch — without it a missing block reports as actual: '', which
reads as "the guard was deleted" when the real change was to the job's shape.

The problem is overlap. git merge-tree across all nine control-plane PRs
finds exactly two conflicting pairs, and this is one of them: #575 × #584 both
rewrite the same region of promotion-worker-routing-contract.mjs and the same
vault-probe exemption.

#584 is a strict superset:

#575 #584
feature_stack_merge block scoping
Vault-probe exemption keyed on the job
…also keyed on the step's id: live_probe
…asserts the probe still runs backend: claude
Fixes the control-plane/ enforcement gap (all-branch.yml)
Ships a fixtured test suite (10 cases)
Wires that suite into blocking CI

The step-id row is the substantive one. This PR exempts any lopu-agent call
inside verify_credential_vault; #584 additionally requires the step to be the
probe itself. A second, secret-bearing call added to that job later would
inherit this exemption and silently shed both slot assertions — which is the
same "an identity that can be adopted" hazard your own comment argues against
for a bare id:.

So: same instinct, and #584 simply arrived with more of it applied. Merging both
costs a conflict resolution for no added coverage. Happy to be overruled if you
would rather land this one and rebase #584 onto it — the reverse direction works
too, it is just more work.

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Lopu review — the work here is correct, but it is fully superseded by #584. Recommending you close this one.

Both PRs fix the same two advisory contracts that are currently red on the github-actions tip:

  • promotion-worker-routing-contract.mjs — the feature_stack_merge guard pinned as one adjacent-text regex, permanently red since an orthogonal guard landed between the conjuncts.
  • resolve-pr-conflicts-routing-contract.mjs — the slot-free verify_credential_vault probe tripping the generic "every Lopu call receives the secondary API-key slot" rule.

Your approach to both is sound — scoping the invariant rather than weakening it, keying the exemption on the job rather than a bare id: live_probe (an id can be adopted by any step, a job boundary can't), and naming the extraction failure separately so the message doesn't misdirect. No objection to any of it.

But #584 does everything here plus:

#575 #584
feature_stack_merge block-scoped guard
Credential-vault probe exemption ✅ job-range keyed ✅ job + step-id keyed, via a real yamlStepAt YAML step-window helper
Probe carries router secret / no static slots
Probe still runs backend: claude
aiRuntimePattern selection/enforcement drift
Step-window fixtures in blocking CI ✅ (10 cases)

Two of those matter more than they look:

backend: claude is load-bearing. lopu-agent/action.yml gates the waterfall fetch on inputs.backend == 'claude', so on any other backend the router secret is inert. Without that assertion the exemption keeps paying out for a probe that no longer probes anything.

The aiRuntimePattern fix closes a real latent hole. File selection accepted control-plane/; slot enforcement didn't. I checked: all-branch.yml has exactly three ./control-plane/.github/actions/lopu-agent calls, so it was pulled into the scanned set and then every one of them was skipped. (All three already carry both fallback slots, so it was a latent hole rather than a live failure — exactly as #584's comment claims.)

They also conflict. Both rewrite the same assertion in promotion-worker-routing-contract.mjs; a sequential merge of this batch hard-conflicts on that file and on resolve-pr-conflicts-routing-contract.mjs.

I verified both paths:

So: close this, or merge it first and resolve in #584's favour. Nothing is lost either way. I deliberately made no code change here — deduplicating a branch that may simply be closed would be churn.

Posted by Lopu, Thingtime's PR manager.

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Lopu · superseded by #584

Reviewed alongside the other eight open lopu/workflow-check-fix-* PRs.

This PR is correct, and it fixes two contracts that are failing on github-actions right now — the verify_credential_vault probe tripping the generic API-key-slot rule, and the feature_stack_merge adjacency regex that went permanently red when d5e984a0 inserted recovery != 'true' between its clauses.

The problem is that #584 makes the same two fixes, strictly better, and the two conflict (git merge-tree reports content conflicts in both promotion-worker-routing-contract.mjs and resolve-pr-conflicts-routing-contract.mjs).

What #584 has that this does not:

  • it also asserts the probe runs backend: claude. That one matters: lopu-agent/action.yml only fetches the ordered Thingtime bundle when inputs.backend == 'claude', so on any other backend the router secret is inert — the exemption would keep paying out for a probe that no longer probes anything, and the job named for the live vault check silently stops checking it;
  • it scopes the probe assertions to a real YAML step (yamlStepAt) rather than a coarse job slice, so a continue-on-error: or a small env: above the uses: cannot slide the id: out of the window;
  • it ships resolve-pr-conflicts-routing-contract.test.mjs and runs it in the blocking verify job.

I checked property by property: nothing here is lost by closing this in favour of #584.

If you would rather keep this one, that works too — land it first and resolve #584's conflict in its favour. The outcome to avoid is a hand-merge that keeps this PR's coarser probe window while dropping #584's backend: claude assertion, since that is the half that stops the exemption becoming an unconditional skip.

Recommendation: close as superseded by #584.


Note for anyone reading the diff: GitHub's two-dot "Files changed" view shows graphify-cas.mjs and graphify-cas.test.mjs as deleted. They are not — this branch forked at ac680d6, before those files landed on github-actions. The real change is 90 added lines across two contract files: git diff $(git merge-base github-actions HEAD)...HEAD.

@lopugit

lopugit commented Sep 3, 2026

Copy link
Copy Markdown
Owner Author

Closing as superseded by #584, which fixes the same two stale controller-contract guards and adds the stronger scoped regression coverage. #584 merged into github-actions at 014687079dec1f651f66ac7e69e9114d51b9a3c3.

@lopugit lopugit closed this Sep 3, 2026
@github-actions github-actions Bot added lopu: conflicting GitHub reports merge conflicts for the current PR snapshot lopu: queued The current PR snapshot is waiting in Lopu's PR-management queue and removed lopu: mergeable The PR branches can currently be merged without conflicts labels Sep 3, 2026
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

🤖 Lopu detected merge conflicts

Status: Work detected — Lopu is taking ownership.

Current phase: Entering Lopu's serialized PR-resolution queue.

Estimated completion: around 09:24 UTC (~20 minutes; this adjusts as the queue moves).

Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run.

Time conversion (UTC source)

Moment UTC Los Angeles Melbourne
Updated 2026-09-03 09:04 UTC (UTC+00:00) 2026-09-03 02:04 PDT (UTC-07:00) 2026-09-03 19:04 AEST (UTC+10:00)
Estimated finish 2026-09-03 09:24 UTC (UTC+00:00) 2026-09-03 02:24 PDT (UTC-07:00) 2026-09-03 19:24 AEST (UTC+10:00)

Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically.

Lopu queue and PR pulse

Scope Metric Count
Repository Open PRs 17
Repository Conflicting 1
Repository Out-of-date with target 3
Repository GitHub state unknown 0
Repository Part of an open stack 0
Repository Touch files changed by another open PR 14
Repository Target a non-root branch without an open parent PR 0
This resolver batch Admitted snapshots 4
This resolver batch Currently resolving 0
This resolver batch Waiting 4
This resolver batch Finished 0

Related PR context

  • Stack: No open parent or child PR currently links to this branch.
  • Target: github-actions is a repository root/integration branch.
  • Changed-file overlap: 1 changed file is also touched by #573, #577, #580.

Exact branch pair: github-actionslopu/workflow-check-fix-33514120175.

Timeline

  • 09:04 UTC — Detected conflicts between github-actions and lopu/workflow-check-fix-33514120175; assigning the exact snapshot to the resolver queue.

@github-actions github-actions Bot removed lopu: conflicting GitHub reports merge conflicts for the current PR snapshot lopu: overlapping files This PR changes files also changed by another open PR lopu: queued The current PR snapshot is waiting in Lopu's PR-management queue labels Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant