Skip to content

spawn: classify the three files that reddened Spawn Drift, and catch the next one at PR time - #314

Merged
Jammy2211 merged 2 commits into
mainfrom
claude/pyautomind-ci-broken-6uf8w5
Aug 25, 2026
Merged

spawn: classify the three files that reddened Spawn Drift, and catch the next one at PR time#314
Jammy2211 merged 2 commits into
mainfrom
claude/pyautomind-ci-broken-6uf8w5

Conversation

@Jammy2211

Copy link
Copy Markdown
Collaborator

The failure

The weekly spawn_drift run failed on 2026-08-24 (run 32701028244) with exit 2 (UNSAFE). Three files on main matched no MIND_RULES entry, so the partition could not decide whether they travel into PyAutoMind-template:

== PyAutoMind-template
  UNMATCHED (3) — extend the spec's tables, then mirror here:
    ✗ .github/workflows/firewall_gate.yml
    ✗ .github/workflows/pages_dashboard.yml
    ✗ dashboard.html
  canary scan: clean

That is rule 9d working as designed, not a bug: there is deliberately no .github/* catch-all, because a fallback is fail-OPEN and would ride a new workflow into the template carrying whatever schedule and secrets it has. The canary scan was clean, so nothing leaked — the run stopped before proposing a sync.

Everything else in Mind CI was and is green; this was the only red workflow.

Part 1 — classify the three files (bac5102)

All three are DROP under the spec's fresh-repo invariant:

File Rule Why
firewall_gate.yml 9c Checks out PyAutoLabs/{PyAutoBrain,PyAutoHeart,PyAutoHands} by name — dashboard_refresh.yml's failure mode three times over. A fresh org has no such siblings, and owner substitution only turns them into YOURORG/... placeholders
pages_dashboard.yml 9c Needs a GitHub Pages site the default token cannot create on a fresh repo — the Hands lesson already recorded for Memory's knowledge_board.yml — and takes pages: write + id-token: write
dashboard.html new 7c The Pages twin of dashboard.md. DROP rather than 7b's EMPTY: there is no broken-link argument to answer, since no shipped file links it and its publisher is dropped above

Spec tables extended first, then mirrored into spawn.py, per the error message's own instruction. Also fixed a stray "The rest They" in the 9c cell being rewritten.

Part 2 — catch the next one at PR time (661be2b)

The three files reached main because nothing at PR time ever looked at the real file list. Closing that needs two changes; either alone is inert.

Dropped spawn_drift's pull_request: paths filter. It watched the generatorspawn.py, tests/, the spec, itself. That is the wrong input set: the partition's other input is the source tree, so a PR adding a file that matches no rule is exactly what needs checking and exactly what the filter excluded. None of the three offenders touched a watched path, so the workflow never ran on their PRs. This is cheap rather than heavy, because the drift job is already skipped on pull_request — a PR runs only the hermetic privacy job (pytest, synthetic inputs, no clones, no secrets, no writes).

Added test_no_tracked_file_is_unmatched_by_mind_rules. Widening the trigger alone would have caught nothing: every other test in that file builds a synthetic tree, so it only covers file classes somebody remembered to add to the fixture. The new test reads git ls-files and asserts every tracked path resolves to a MIND_RULES entry — the same condition the drift job reports as UNMATCHED/exit 2, minus the clones.

MEMORY_RULES cannot be checked from this checkout (PyAutoMemory is not a sibling), so it stays covered by the weekly run.

Verification

  • 0 of 1395 tracked files now UNMATCHED, down from 3 — the exact condition that failed
  • lifecycle_drift.yml still resolves to KEEP, so rule 9 has not over-dropped
  • 187 tests pass; lifecycle check and lifecycle index --check both OK
  • Workflow YAML re-parsed: pull_request: with no value fires on all PRs, and the drift job's skip gate is unchanged
  • Negative probes, both non-vacuous: removing the firewall_gate rule again fails test_check_exit_codes_are_the_self_heal_contract on exit != EXIT_CLEAN; adding a tracked .github/workflows/zzz_probe.yml fails the new test, naming the file and both places to fix it

Expected follow-on

This converts exit 2 (UNSAFE, hard stop) into exit 1 (DRIFT). Once on main, the next scheduled or dispatched run takes the self-heal path at spawn_drift.yml:92 and proposes a sync PR against the published template repos — the designed happy path, but it does mean a follow-up PR will appear.


Generated by Claude Code

claude added 2 commits August 25, 2026 00:14
The weekly spawn_drift run failed on 2026-08-24 with exit 2 (UNSAFE):
three files on Mind main matched no MIND_RULES entry, so the partition
could not decide whether they travel into PyAutoMind-template.

  ✗ .github/workflows/firewall_gate.yml
  ✗ .github/workflows/pages_dashboard.yml
  ✗ dashboard.html

That is rule 9d working as designed, not a bug: there is deliberately no
.github/* catch-all, because a fallback is fail-OPEN and would ride a new
workflow into the template carrying whatever schedule and secrets it has.
The canary scan was clean, so nothing leaked — the run stopped before
proposing a sync.

All three are DROP under the fresh-repo invariant:

- firewall_gate.yml checks out PyAutoLabs/{PyAutoBrain,PyAutoHeart,
  PyAutoHands} by name. That is dashboard_refresh.yml's failure mode three
  times over — a fresh org has no such siblings, and owner substitution
  only turns them into YOURORG/... placeholders.
- pages_dashboard.yml needs a GitHub Pages site the default token cannot
  create on a fresh repo — the Hands lesson already recorded for Memory's
  knowledge_board.yml — and takes pages:write + id-token:write.
- dashboard.html is the Pages twin of dashboard.md. DROP rather than
  dashboard.md's EMPTY: there is no broken-link argument to answer here,
  since no shipped file links it and its publisher is dropped above.

Spec tables extended first, then mirrored into spawn.py, per the error
message's own instruction. The template contract test gains both
workflows in its fixture and its DROP list; verified non-vacuous by
removing the firewall_gate rule again, which fails
test_check_exit_codes_are_the_self_heal_contract on exit != EXIT_CLEAN.

Verified: 0 of 1395 tracked files now UNMATCHED, lifecycle_drift.yml
still KEEP (rule 9 does not over-drop), 186 tests pass, lifecycle check
and index --check both OK.

Worth noting separately, not fixed here: spawn_drift's pull_request
trigger is path-filtered to scripts/spawn.py, tests/**, the spec and its
own file. A PR adding a workflow or a root file therefore never runs it,
which is why all three sat undetected until the Monday cron.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4MvhHTXxPwffptbXsP8xR
The three UNMATCHED files fixed in bac5102 reached main because nothing at
PR time ever looked at the real file list. Closing that needs two changes,
not one — either alone is inert.

1. Drop spawn_drift's pull_request `paths:` filter.

   It watched the GENERATOR — spawn.py, tests/, the spec, itself. That is
   the wrong input set: the partition's other input is the source tree, so
   a PR adding a file that matches no rule is exactly what needs checking
   and exactly what the filter excluded. All three offenders landed that
   way (two workflows and a root file), and none of them touched a watched
   path, so the workflow never ran on their PRs.

   Cheap, because the `drift` job is already skipped on pull_request: a PR
   runs only the hermetic `privacy` job — pytest over synthetic inputs, no
   clones, no secrets, no writes.

2. Add test_no_tracked_file_is_unmatched_by_mind_rules.

   Widening the trigger alone would have caught nothing. Every other test
   in this file builds a synthetic tree, so it only covers file classes
   somebody remembered to add to the fixture — a new real workflow is
   invisible to all of them. This one reads `git ls-files` and asserts every
   tracked path resolves to a MIND_RULES entry: the same condition the drift
   job reports as UNMATCHED / exit 2, minus the clones.

   MEMORY_RULES cannot be checked from here (PyAutoMemory is not a sibling
   in this checkout), so it stays covered by the weekly run.

Verified non-vacuous: adding a tracked .github/workflows/zzz_probe.yml
fails the new test naming the file and both places to fix it; removing it
passes. 187 tests pass. Workflow YAML re-parsed — `pull_request:` with no
value fires on all PRs, and the drift job's skip gate is unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4MvhHTXxPwffptbXsP8xR
@Jammy2211
Jammy2211 merged commit 1cf58a8 into main Aug 25, 2026
2 checks passed
@github-actions
github-actions Bot deleted the claude/pyautomind-ci-broken-6uf8w5 branch August 25, 2026 14:03
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.

2 participants