DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path change-requests/cr-2026-215-loomworks-candidate-discard-policy-fence-v0_1.md

DUNIN7-M4 — INFRASTRUCTURE CHANGE REQUEST

CR-2026-215 — Candidate discard: a declared policy for every FK, and a fence that asks the schema (B-93) (v0.1)

Version. 0.1 · Date. 2026-08-15 · Author. Claude Code (drafting, from [B-93] + scoping-notes/loomworks-b93-fk-census-scoping-note-v0_1) / Marvin Percival (approving). Target. /Users/dunin7/loomworks-engine only (the discard, the policy declaration, the fence test). Baseline. Engine 1adcd17. CC confirms at Step 0. Companion to. [B-93] (two named FKs, twice-predicting — now three), the census note, [B-98]'s completion (the pairing's other half). Charter. Autonomous regime. Status. DRAFT — awaiting the gate. Do not execute.


0. Settled — the Operator's rulings. Do not re-raise.

  1. The census settles the shape by evidence, and this CR says so: 27 FKs reference engagements, 4 handled, and a third blocker already live before anyone hit it (companion_notifications, on E0129 today, found by enumeration instead of by toll). Four entries each added after a block is a pattern, not a list — a fifth would arrive the same way. The fence is what breaks that cycle, and it is the fourth application of the source-contract pattern: the AST test (CR-204), the source-scrape (CR-205), the kinds fence (CR-213), and this — a contract that must hold over source constructs, enforced against the source (here, the schema), not against exercised paths.
  2. The declared-policy triple matters MORE than the fence. delete / null / active_only forces someone to decide what each FK MEANS for a candidate rather than reacting to a 500. The declaration is where the thinking happens; the fence catches an undeclared FK, but a policy chosen carelessly passes the fence happily. The twenty declarations below are decisions, made at drafting-or-build with reasons, not defaults filled to green the test.
  3. E0129 is the eye-test specimen, and what it cleans is named: it is the control-test residue from the E0128 walk — the disposable candidate the brief-path drafted before the findings gate held. Carrying all three observed blockers (2 turns, 1 notification, plus focus whenever visited), its successful discard proves the fix AND removes the last artefact of the arc that found it.
  4. The permanent-object pairing, the ranking's ground: [B-98]'s faces half-closed it — a conversationally-created candidate with an open finding can now be unblocked but still can't be discarded. This CR closes the other half. After it, no path mints a permanent object.

1. The changes

A. The policy declaration (the point). One module (engagement/candidate_discard_policy.py or inline in candidate_discard.py — Step 1 decides placement): a mapping from every FK that references engagements(id) to its candidate-discard policy, each with a one-line reason:

B. The discard executes the policy. discard_candidate_engagement iterates the declaration: deletes the delete class, nulls the null class, touches nothing declared active_only — then the row. Order respects any FK-to-FK dependencies (Step 1 verifies; membership_designations already cascades).

C. The fence (the cycle-breaker). A test reads the live schema's referencing FKs (information_schema / SQLAlchemy metadata) and fails when any FK referencing engagements lacks a declared policy — a new FK added anywhere in the system without a discard decision is a failing test at its author's desk, not a 500 on a cleanup months later. Plus behavior tests: discard succeeds for a candidate WITH turns, WITH a notification, WITH a focus pointing at it (the three fixtures the suites never had — each observed failing first); the fence-of-the-fence: an active engagement's rows are never touched by any discard path.

D. Nothing else. The HTTP route's shape, auth, and refusal-for-committed behavior unchanged; no surface change; [B-100]/[B-94]/[B-97] untouched.

2. Consequences named, so they are chosen

3. Tests — each observed failing first

The fence (schema-read, fails on undeclared FK — observed failing trivially before the declaration exists); discard-with-turns; discard-with-notification; discard-with-focus (each currently 500s — the observed-failing state is production truth); the active-untouched fence; policy-execution order. Existing discard/hard-delete suites re-run.

4. seed-mutability impact

Kind C expected: discard plumbing + a declaration module; the seed path untouched (candidate seeds live on the ADMIN log and their events are already in the delete class via memory_events). Checked at Step 1; a null finding is an entry.

5. Build steps

| Step | What | Mode | |---|---|---| | 0 | Pre-flight — baseline, clean tree. | Auto | | 1 | The census re-run against code (the write paths for each of the 27 — every active_only claim verified, not assumed; per the arc: probe what can be probed); placement decision; tests written, observed failing. | Auto | | 2 | The declaration + the discard rewrite + the fence. | Auto | | 3 | Full suite; ruff; mypy (message-level; the baseline gate remembered); the no-delete static-check allow-list updated if the new module carries DELETEs. | Auto | | 4 | Eye-test: E0129 discarded live — three blockers, one 204, the walk arc's residue gone; verified absent; focus confirmed cleared not orphaned. | Auto | | A | Checkpoint. | Checkpoint | | 5 | Tag; push; watch (verdict read directly); redeploy engine. | Auto |

6. Acceptance gate

  1. E0129 discards — the specimen with all three observed blockers — and the walk arc's last artefact is gone.
  2. The fence: an undeclared referencing FK is a failing test (demonstrated by the test's own pre-declaration failure); all 27 carry policies with reasons.
  3. The three blocker fixtures pass; the active-untouched fence holds.
  4. Suite green; gates clean; run watched; redeployed; seed-mutability recorded.

7. Kickoff prompt


Execute CR-2026-215 v0.1 at change-requests/cr-2026-215-loomworks-
candidate-discard-policy-fence-v0_1.md in loomworks-record. Engine
repo only. Confirm baseline first.

§0: the declaration is where the thinking happens — every
active_only verified against code, not defaulted; the fence breaks
the added-after-a-block cycle (source-contract pattern, fourth
application); E0129 is the specimen and its discard cleans the
walk arc's residue. Halt at Checkpoint A.

DUNIN7 — Done In Seven LLC — Miami, Florida CR-2026-215 — candidate discard, policy and fence — v0.1 — 2026-08-15 Twenty-seven references, one decision each. The fence catches the undeclared; the declaration is where the thinking happens.