Store Cleanup Coordinator, One-Time Program
Prompt version: v4.22 (tracked in
prompts/CHANGELOG.mdwith the master prompts). Status: open. The program ends by retiring itself (§ C9), after which no cleanup of old entries is needed again.How it runs: an interactive Claude Code session started with "Read
prompts/cleanup-coordinator.mdonmainand continue the store cleanup program." The first such session is the coordinator: it plans the program, runs the first wave and hands each later wave to a fresh session. Every later session reads the same file, takes the next wave from the tracker and hands over in turn. The repository is the shared memory:docs/cleanup/STATUS.md(tracker) andwork/cleanup/plan.json(machine-readable plan) onmain.
Mandate and definition of done
Clean up everything in ctipilot that is old, wrong or no longer used, so that afterwards the weekly quality audit is the only quality loop the store needs. Done means all of these hold on main:
- Every published entry has been truth-verified under the current standard at least once since its last substantive change: claim by claim against fetched sources, with a rating, an ATT&CK mapping and inline citations as its kind requires. The legacy queue is empty (
python3 tools/legacy_review.py --stats→ 0 pending), and no entry is left that no audit truth pass ever covered. - Every known finding is applied or recorded as declined with a reason:
- the 188 out-of-week verdicts of the 2026-10-18 audit (
work/2026-10-18T1307Z-audit/truth-*.yaml; 77 factual errors, 68 imprecisions); - the residuals and(pre-existing)advisories that audit loops carried forward; - the portable hunks on the reference branch. - No duplicate findings remain, whether they share a CVE or not; each pair is folded with
tools/fold_entries.pyor linked throughreferences[]. - The registry is clean:
- no duplicate record: an orphan that duplicates another key is tombstoned onto it (keys are permanent, so none is deleted);
- no relation whose
sourceentry is gone; - the known summary defects fixed: Eurail's retracted DPA/EDPS review,incident:daemon-tools-supply-chain-2026"EU governments targeted", and BeaverTail "an initial loader". - Sources and state are current:
- the 2026-10-11 operator recommendations 5–7 are decided on their evidence (Red Canary,
ssd-disclosure,google-tagvsmandiant-gtig); - nostate/warning_acknowledgments.jsonrow matches nothing; -state/coverage_backlog.mdholds only live rows. - No dead weight remains in the repository: unused tools, stale docs, retired code paths and stale
work/material are removed (rules in § C7). Known gate false positives are fixed intools/check_run.py. - The program is retired (§ C9).
Throughout: python3 tools/check_run.py --all ends 0 warn · 0 fail after every wave, and python3 site/build.py is warning-free.
Binding rules
- All of
CLAUDE.mdapplies. In particular:- entries change only through changelog records (
correction/improvement/update,internal: truefor metadata-only), with no silent edit and never a second entry for one finding; - run records are immutable after their fire;
- no IOCs, feature branches only,
check_run.pyexits 0 before every commit; - the verifier loop always runs at least once;
- push notifications only for pipeline breakage.
- entries change only through changelog records (
- One cleanup session at a time. In October 2026 three audits ran at once in separate sessions and exhausted the account-wide session limit.
- Never start a second cleanup session while one is open: an open
partialclaim onmain(work/*-audit/audit.claimwith no run record yet) means one is open. - The intel fires (daily, about 02:56Z) and the scheduled weekly audit (Sundays, about 13:07Z) draw on the same limit. Do not start a wave between Saturday 18:00Z and the Sunday audit's record landing on
main. - Under v4.22 a wave's
partialclaim never blocks the scheduled audit. A wave that sees the scheduled audit's open claim spawns nothing until that record lands, then mergesorigin/mainand continues (prompts/quality-audit.mdPhase 0 step 2).
- Never start a second cleanup session while one is open: an open
- One wave per session, at most 20 sub-agent spawns per wave, retries and continuations included (the v4.21 bound). Log every spawn in
work/<run-id>/budget.tsvwith thesubagent_tokensits completion notification reports.- A spent budget is a forced cut: no further spawns,
verification.budget_cutset, and the remaining rows go back to the tracker. - The run record and the report always ship.
- A spent budget is a forced cut: no further spawns,
- Merge continuously.
- Every finished wave is committed and pushed on its own
claude/cleanup-wave-<NN>-<suffix>branch; the auto-merge workflow lands it onmain. - A wave is finished only when Phase 7 shows its record on
mainand on the site. - A small standalone change, such as a tool fix or a doc removal, ships the same way the moment it is done and green; it never waits for a wave.
- Never push to
main.
- Every finished wave is committed and pushed on its own
- The reference branch
cleanup/2026-10-18-carry-forwardis never merged.- It holds unverified drafts from the 2026-10-18 audit: its
docs/cleanup/CARRY-FORWARD-2026-10-18.mdsays what is portable and what the 2026-10-11 audit superseded. - Ports are re-derived on top of
main, re-verified and re-recorded under the porting wave's own run id.
- It holds unverified drafts from the 2026-10-18 audit: its
- Sub-agents (pinned definitions in
.claude/agents/):cti-verificationfor truth passes and the Phase 5.7 verifier;cti-researchonly when a fix needs a source nobody has found yet;Explorefor read-only repository inventories;- repair workers (§ C4) as
subagent_type: claude: they compose from verified findings and neither research nor verify, so the "nevergeneral-purposefor research or verification" rule is not touched. - Give every sub-agent its own subdirectory,
work/<run-id>/<name>/, for helpers and scratch files. Sub-agents that shared one scratch directory have overwritten each other's helpers. - Sub-agents never commit or push, and never edit shared state files (
entities/registry.yaml,state/*.json,sources/sources.json); they list the change they need in their report and the main agent applies it.
C0, Bootstrap (coordinator only; skip when docs/cleanup/STATUS.md exists on main)
- Sync:
git fetch origin main && git merge --no-edit origin/main. Read: -CLAUDE.md; -prompts/quality-audit.md: § Session budget, Phase 0, § Secondary work, Phases 5 to 7; -prompts/cti-run.md: Phase 4 (§ Updating an existing entry, § The actionability contract,actions[]), Phases 5.5, 5.7, 6 and 7; -docs/pipeline.md§ Entry lifecycle and § Entity registry; -docs/audits/2026-10-18-quality-audit.mdand the recommendations and watch items ofdocs/audits/2026-10-11-quality-audit.md; -git show origin/cleanup/2026-10-18-carry-forward:docs/cleanup/CARRY-FORWARD-2026-10-18.md. - Build the inventory with the main agent plus at most two
Explorespawns (they count toward the 20). Size each workstream: - W1 carry-forward findings: the 2026-10-18 out-of-week YAMLs, plus every(pre-existing)advisory and residual named in audit reports and records since 2026-09-27, plus the portable reference-branch hunks. - W2 legacy queue:python3 tools/legacy_review.py --statsand--next. - W3 never-verified entries: entries that are not in the legacy queue and that no audit truth pass ever covered. Derive them by collecting every entry id named inwork/*-audit/{truth-*.yaml,audit-plan.json}andruns/*-audit.mdupdated_entry_ids. - W4 duplicates: every CVE-sharing pair store-wide, plus non-CVE twins by entity and title, such as RoguePlanet 2026-06-19 vs 2026-07-09, the 2026-05-17 Pwn2Own roundup, and Nightmare Eclipse 2026-05-30 vs YellowKey 2026-05-15. - W5 registry, W6 sources and state, W7 repository dead weight and tooling: see § C7. - Write
docs/cleanup/STATUS.md: - one table row per work item: workstream, item or entry ids, size, planned wave, status (open/in-wave <NN>/done <run-id>/declined: <reason>); - a header with the definition of done and the current totals.Write the same plan as
work/cleanup/plan.json. Order the waves:- W7 tooling first, which makes every later wave cheaper (§ C7 items 1–2);
- W1 non-legacy factual errors (16 entries from passes T1–T9, filed 2026-05-18 to 2026-10-10);
- W4 duplicates, before their members are re-verified, so twins are not verified twice;
- W2 legacy, oldest first, about 60 to 100 entries a wave, then W3;
- W5 and W6 interleaved as small standalone merges;
- C9 last.
- Ship the tracker and the plan alone on a
claude/cleanup-plan-<suffix>branch, wait for them to land onmain, then run wave 1 (§ C1–C6).
C1, Wave preflight
- Run
prompts/cti-run.mdPhase 0 step 0 verbatim (verified clock, stale-clone check) with the run-id suffix-audit, soRUN_IDlooks like2026-10-21T0912Z-audit. - Sync
main, then writework/${RUN_ID}/base.shaandwork/${RUN_ID}/main.started_at. - Run the claim check from
prompts/quality-audit.mdPhase 0 step 2 and the Saturday/Sunday rule above. An open cleanup claim from another session (any age under 48 h) → stop and report; never two cleanup sessions at once. - Write
partial $STARTEDtowork/${RUN_ID}/audit.claim, commit that file alone onclaude/cleanup-wave-<NN>-<suffix>and push. Re-run the check before every spawn batch. - Mark the wave's rows
in-wave <NN>in the tracker (this ships with the wave).
C2, Choose the wave's rows
Take the next open rows that fit the budget. Spend about 10 of the 20 spawns on producing and applying fixes and keep about 10 for the verifier loop. Rows with fresh findings (W1) need no truth pass. Rows without them (W2, W3, W4) get one cti-verification truth pass per batch of about 20 entries, using the envelope of prompts/quality-audit.md Phase 1: the findings YAML, claim rows and the progress file.
C3, Truth passes (only for rows without fresh findings)
Spawn the batches in one message. Each pass re-verifies its entries whole against fetched primaries and writes work/${RUN_ID}/truth-<n>.yaml. The pass reports duplicates it notices within its batch as groups, with a proposed survivor.
C4, Repairs
Hand each batch of findings (about 12 to 20 entries) to one repair worker (subagent_type: claude). Small sets go to the main agent instead. The spawn message carries:
- the findings file and entry ids;
- the wave's
RUN_ID; - the rules of
prompts/cti-run.mdPhase 4 § Updating an existing entry, the actionability contract and the no-IOC rule; - the record tool from § C7 item 1;
tools/fold_entries.py --run "$RUN_ID"for folds.
The worker's duties:
- Re-read the cited or ground-truth source for every sentence it writes, quote only verbatim, never construct a URL, and cite the specific page.
- Fix a wrong statement where it stands. Rewrite the analysis from its sources when most of it is unsupported.
- Give a legacy entry the rating, ATT&CK mapping (active ids in
attack/enterprise-attack.json) and labelled lines (**Exposure:**,**Detection:**,**Triage:**only with an honest discriminator,**Defender takeaway:**) its kind requires, where the sources support them. - Append exactly one record per entry for the wave, whose
fieldsname every changed field plusbody. A reader-facing change gets its## <Type> — <at>section; a metadata-only one isinternal: true. - Decline a finding it cannot confirm, with the reason.
- Write
work/${RUN_ID}/repair-<n>.md, listing per entry the record type, the fields, the declined findings, and the registry andcves_seenchanges it needs. Return a compact summary.
C5, Gate, verify, publish
- The main agent:
- applies the shared-state changes the workers listed;
- runs
python3 tools/sync_products.pyandpython3 tools/legacy_review.py --done "$RUN_ID" <ids>for legacy rows; - runspython3 tools/check_run.py "$RUN_ID" --pre-verifyand fixes every FAIL and every warning the wave caused. - Phase 5.7 as
prompts/quality-audit.mdscopes it for audits: -python3 tools/claim_ledger.py "$RUN_ID" --iteration N(it readsbase.sha, so updated entries contribute only their changed claims, and folded survivors are read whole); - slices of about 300 claims, at most three for iteration 1 and for a confirmation pass, one for a post-fix pass; - double-CLEAN gate, cap 8, fail-open, andbudget_cutwhen the spawns run out. - Write
docs/audits/<RUN_DATE>-cleanup-wave-<NN>.mdcovering scope, clean/total, findings applied and declined, folds, tool and doc changes, what the budget did not reach, and the tracker delta. - Write the run record
runs/<RUN_DATE>/<RUN_ID>.md: -kind: auditand the currentprompt_version; -sub_agentskeystruth-<n>/repair-<n>/explore-<n>, neverG…(aGkey would move the weekly audit's window anchor); -entries_updatedandupdated_entry_idsnaming every entry with a record from this wave; - the spawn total and token sum in the notes. - Update the tracker rows to
done <RUN_ID>or back toopen, with reasons. - Run
check_run.py "$RUN_ID"and--all: 0 FAIL, and no warning but the run's own telemetry. Runsite/build.pyandsite/test_build.py. - Stage specific paths, then commit, sync
mainand push with the Phase 6 retry shape. - Phase 7: poll until the record is on
mainand the run is inhttps://ctipilot.ch/data/briefbook.json, then ship the publish-status amendment.
C6, Hand over
When the wave is published (or cut and published), hand over before the session ends:
- If rows remain, queue the next session with the kickoff line from the banner. Use the session tool that suggests a new task (
spawn_task) when it is available; otherwise print the line for the operator. - Name the next wave's rows and the earliest start time (the Saturday/Sunday rule) in the tracker's header.
- Never start the next wave in the same session.
C7, Non-entry cleanup (W5–W7)
- Record tool (first). Promote
work/2026-10-18T1307Z-audit/add_record.py(append one changelog record with a strictly laterat, the section andupdated_atrule, re-parse) andresum.py(re-fold this fire's own unpublished record) into one testedtools/entry_record.pywith--run-id. Add a row to theCLAUDE.mdKey commands table. Every repair worker uses it. - Gate false positives:
- Port the reference branch's
_page_skeletonsfix: extract text skips the tag stripper,meta description/og:descriptiontext is kept, and "(opens in a new window/tab)" labels are removed. - Makecitation-cvereport an image-only PDF as unreadable, not as "does not mention". - Add tests for both. - Registry (W5):
- Registry keys are permanent: never delete or rename one. An orphan (no entry, relation or alias uses it) that duplicates another key is tombstoned with
merged_into: <canonical-key>; an orphan with no canonical twin stays as it is. - Repair the known summaries. - Re-point or drop relations whosesourceentry was folded away. - Sources and state (W6):
- Decide the 2026-10-11 recommendations 5–7 on the evidence in
state/source_health.jsonand the feeds: retire or repoint the source record, with the reason in its notes. - Drop acknowledgment rows that match no current warning. - Trim struck backlog rows older than 30 days. - Dead weight (W7):
- Remove what nothing reads: a tool no workflow, prompt, test or doc command uses (check
tools/migrate_briefs.pyandtools/migrate_updates.py, one-time v2 migrations), stale doc sections describing retired behaviour, and code paths for fields or kinds that are already retired. Keep the FAIL checks that guard against their return. - Committedwork/<run-id>/directories older than 60 days are forensic history that git keeps, so remove them from the tree only when no tool, prompt or test reads them (checkbuild_prior_coverage.py, thecheck_run.pypage-check caches and liveness ledgers,claim_ledger.py). List what went and the commit that removed it in the wave report. - Never remove a run record, an entry outside a fold, an audit report orprompts/CHANGELOG.mdhistory. - Each item ships as its own small merge when green (§ Binding rules).
C8, When something breaks
- A rate limit or stalled sub-agent follows
prompts/quality-audit.md: never block the run record, record the cut, and return unfinished rows to the tracker. - A conflict on shared state files is resolved with
python3 tools/merge_state.py resolve. - A real FAIL the wave cannot fix is fixed first or its change is reverted; nothing ships red.
C9, Retirement (last wave)
When every tracker row is done or declined, ship one versioned change (banner bump and prompts/CHANGELOG.md entry; cti-run.md and quality-audit.md in lockstep):
prompts/quality-audit.md§ Secondary work is removed. The weekly audit, which verifies every new or changed entry each week and carries(pre-existing)advisories to the next truth pass, is the only steady-state quality loop.tools/legacy_review.pyandstate/legacy_review.jsonare removed (their final stats go in the report), and so are theirCLAUDE.mdanddocs/mentions.- This file's banner reads
Status: completed <date>, <run-id>. It stays as history. - A dated memory note records the closure.
- The final report
docs/audits/<date>-cleanup-final.mdgives before/after metrics: entries verified, corrections, folds, registry records, removed files, and warnings.
Finally, delete the reference branch cleanup/2026-10-18-carry-forward (git push origin --delete cleanup/2026-10-18-carry-forward) once the tracker shows every one of its rows ported or discarded.