Skip to content

fix: spawn EMPTY leaks live registry entries into public templates #118

Description

@Jammy2211

Overview

scripts/spawn.py::empty_body() implements the spec's EMPTY action as "keep line 1 of the live file" rather than "keep the header line". For registry files with no H1, line 1 is instance content, so spawn stamps live registry entries into the fresh-slate template repos — violating the hard privacy invariant at docs/pyautobrain/spawn_spec.md:61.

One leak is already published in PyAutoLabs/PyAutoMind-template (commit 3424dba1, 2026-07-27). A second would ship on the next regenerate. The canary scan reports clean throughout because CANARY_TOKENS holds dataset names only, and the spec-mandated privacy test was never written.

This also explains the chronic Spawn Drift red: keying on line 1 couples the template to files that ordinary daily work rewrites.

Plan

  • Make EMPTY never copy source bytes. Named registry files get a deterministic title from an EMPTY_TITLES map; glob-matched bibliography files get a generated header comment (spec rule 2 already specifies this).
  • Treat an EMPTY-ruled file with no mapped title as a hard failure, same doctrine as UNMATCHED — never classify ad hoc.
  • Widen CANARY_TOKENS with the name tokens the spec already names (nightingale, rhayes).
  • Add the privacy test required by spawn_spec.md:62, which does not currently exist.
  • Regenerate and force-sync both templates, clearing the published leak and the five legitimate source drifts.
  • Do not edit spawn_spec.md — the spec is correct; the implementation never mirrored it.
Detailed implementation plan

Affected Repositories

  • PyAutoMind (primary)

Branch Survey

Repository Current Branch Dirty?
./PyAutoMind main clean

Suggested branch: feature/spawn-empty-body-privacy-fix

Evidence

Neither planned.md nor ideas.md contains an H1 anywhere (grep "^# " finds nothing in either), so empty_body() can never yield a header for them:

EMPTY-ruled file live line 1 verdict
active.md # Active Tasks header — fine
parked.md # Parked tasks header — fine
condemned.md # Condemned material header — fine
queue.md # Pytree variant queue header, but instance-flavoured
planned.md ## rhayes-audit-validation-phases-2-4 registry entry — leak
ideas.md - lens_calc_zero_contour_jax autolens workspace guide. registry entry — leak

Implementation Steps

  1. scripts/spawn.py — add EMPTY_TITLES beside CANARY_TOKENS:
    active.md# Active Tasks, planned.md# Planned, parked.md# Parked tasks,
    condemned.md# Condemned material, ideas.md# Ideas, queue.md# Queue,
    reading-queue.md# Reading queue.
  2. scripts/spawn.py:338 — rewrite empty_body(src) to key on src.name: mapped title if
    present; else for .bib/.yaml/.yml emit a generated %/# header comment; else
    raise SystemExit with an UNMATCHED-style message. Delete the splitlines()[0] read
    outright rather than documenting it.
  3. scripts/spawn.pyCANARY_TOKENS += ("nightingale", "rhayes").
  4. New tests/test_spawn_privacy.py (no tests/ dir exists yet): generate both trees into
    tmp_path; assert every EMPTY output equals exactly its mapped title + marker (no byte
    derived from its source); assert canary_scan() returns empty; assert an unmapped EMPTY
    file raises.

A heading-shape test is deliberately not used: ## rhayes-audit-validation-phases-2-4 is a
valid ## heading, so shape cannot separate a title from a registry entry. Only an explicit
map can.

Expected drift after the fix

The fix itself changes ideas.md, planned.md, queue.md, bibkey_aliases.yaml and
pyautomemory.bib in the generated tree — all resolved by the same --apply, alongside the
five pre-existing source drifts (lifecycle.py, repos_sync.py, lifecycle_drift.yml,
Memory validate.yml, Memory validate_structure.py).

Key Files

  • scripts/spawn.pyempty_body() (line 338), CANARY_TOKENS (line 102), canary_scan() (line 430)
  • docs/pyautobrain/spawn_spec.md — rules 5 and 2, privacy invariant at line 61 (read-only)
  • tests/test_spawn_privacy.py — new

Out of scope (follow-up)

Spawn Drift detects drift but nothing regenerates. Every green run to date was a manual
dispatch fired seconds after a human ran spawn --apply (2026-07-13 sync 11:08:46Z
dispatch 11:09:02Z; 2026-07-27 sync 19:30:29Z → dispatch 19:30:48Z), so that leg cannot
fail and is not an independent check. The workflow has no trigger-dependent behaviour — no
actions/checkout, no github.ref, no github.event_name, no if: — both legs run identical
code. A separate prompt will propose the lifecycle_drift.yml self-heal pattern (#116),
opening a PR rather than pushing, since these are force-synced generated views.

Original Prompt

Click to expand starting prompt

The PyAutoMind spawn_drift scheduled workflow has failed on every
scheduled run since 2026-07-20 and passes only on manual dispatch.
Latest failure: PyAutoMind run 30804113655 (2026-08-03T10:05Z), step
"Regenerate + diff", exit 1, with 6 drifts:

PyAutoMind-template: planned.md
.github/workflows/lifecycle_drift.yml
scripts/lifecycle.py
scripts/repos_sync.py
PyAutoMemory-template: .github/workflows/validate.yml
scripts/validate_structure.py

Canary scans were clean and "unmatched: none" on both. Regenerate the
templates from their live sources so the drift check goes green, and say
why the scheduled leg diverges from the manual-dispatch leg — a check
that only passes when a human pushes the button isn't a check.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions