From 06b1796bfd79f8e97c8a16ac4d86cf8be70097cc Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Tue, 11 Aug 2026 07:39:43 -0500 Subject: [PATCH] ledger: amend #1212 (built, refuted, reverted) and #122 (owner ruled it in) Both items STAY OPEN; neither status glyph is touched. #1212 -- the record must not read as merely unbuilt, because the obvious implementation is now known to be WRONG. It was built, refuted by another session, verified by execution, and reverted at ed8a09d7. The defect is the inversion of the item's intent. serve's posture gate is wrapped in 'if not settings.retention.allow_unbounded_phi:', so when the AUDITED keep-forever opt-out is TRUE the auto-bound block is SKIPPED and the field keeps its model default. Measured: RetentionSettings(allow_unbounded_phi=True).messages_days -> 60. The runner then purges PHI bodies after 60 days on exactly the instances whose operator consciously chose to keep them forever. And it was a no-op where aimed: the auto-bound keys on model_fields_set, so an operator who says nothing still resolves to 30 and always did. The amendment also retires a STALE PREMISE IN THE ITEM'S OWN TRAP 5. PR #71 already inverted the prod gate -- a production PHI instance with an unset window no longer refuses, it auto-bounds at 30 and starts, and tests/test_cli.py:1619 flipped with it. So the 'less explicit by default' trade was already made and was never this item's to weigh. PHI.md section 8 still described the old refusal, which is exactly how that stale premise reached the filing: the doc-vs-code gap recorded under ASVS 14.2.4 ground (5), misleading a second engineer in practice. Recorded for whoever picks it up: if the 60 is still wanted the value to change is auto_bound_days in retention_classification.py, NOT the field default -- and 30 -> 60 LOOSENS the shipped PHI bound rather than tightening it. That module carries nine windows behind MIN_PHI_RETENTION_WINDOWS = 9 with a two-way drift test against PHI.md section 2, so the tuple and the doc must move together. #122 -- the owner ruled it IN, and that ruling POST-DATES the DEMAND-GATE and the 2/10 above: 'we never want to process stuff if the processing cannot be logged.' That coupling IS the count-and-log invariant, so the gate is discharged and the trigger is moot. The 2/10 came from conflating VISIBILITY with ENFORCEMENT. stdout, NSSM rotation, the TLS syslog forwarder and #50's disk metering make the log visible; none makes processing STOP when it cannot be written. A guard that logs a warning and continues does not satisfy this item -- that is the specific thing to check at review. Work exists on w3-log-write-failure (d26d66a6, five commits, pushed and anchored) and is UNVERIFIED -- the lane died mid-flight on a usage limit. ADR 0162's number is confirmed sound (real alloc record, main's highest is 0161), but the index row should be re-checked before landing. The claim most worth executing is the partial halt: a halt that stops intake while routed and outbound rows keep draining still violates the invariant. Gates: 484 items each declaring exactly one status (248 live / 236 archived); ledger_check clean; citation check 272 in scope, same 4 pre-existing advisory warnings. Open 183 on main -- unchanged by this commit; the 188 I quoted earlier was before the C train (PR #319) landed its five closures. --- docs/BACKLOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 448a38ba..7919610a 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -1729,6 +1729,13 @@ lane; demand-gated on a first enterprise Windows/AD deployment. > 🔢 **Re-scored 2026-08-03 → DEMAND-GATE.** Value **2/10** · Difficulty **6/10** · _money pit_. Value 2 stands — stdout + NSSM rotation, the RFC 5425 TLS syslog forwarder (`_TlsSysLogHandler`, logging_setup.py:281) and #50's disk metering already carry log durability and visibility, so this is marginal and substantially covered. But difficulty 5 prices the wrong shape of work. D5 is "a new connector/codec behind the transport registry" — this is not a connector. logging_setup.py's module docstring (lines 3-13) records that the engine "deliberately do[es] not add file handlers here" because NSSM owns rotation, and `grep FileHandler _(was 2/10 · 5/10.)_ > **On-trigger / demand-gate.** Numbered for tracking only — build when the trigger below fires (“demand-gate, don’t schedule”). +> ⚠️ **AMENDED 2026-08-11 — THE OWNER RULED THIS ITEM IN, and that ruling POST-DATES the DEMAND-GATE and the 2/10 above.** The owner's words: *"we never want to process stuff if the processing cannot be logged."* That coupling **is** the count-and-log invariant CLAUDE.md marks do-not-break — every received message is persisted before the ACK and nothing is accepted-and-dropped — so the gate is discharged and the trigger is moot. + +> **The 2/10 was arrived at by conflating VISIBILITY with ENFORCEMENT.** stdout, NSSM rotation, the TLS syslog forwarder and #50's disk metering make the log *visible*; none of them makes processing *stop* when the log cannot be written. The item is the enforcement, and that is what the owner ruled in. **A guard that logs a warning and lets processing continue does not satisfy this item** — that is the specific defect to check for at review. + +> **Work exists and is UNVERIFIED.** `w3-log-write-failure` (`d26d66a6`, pushed and anchored) carries five commits whose subjects claim: the halt let the backlog keep routing so the internal stages are stopped too; a reload-recovery claim that *"was a guess"* was measured and three docs corrected; two PHI-inventory anti-rot gates found red on the branch; and a console-contract seam bump. **The lane died mid-flight on a usage limit, so none of it is verified.** It carries **ADR 0162**, whose number was independently confirmed to come from `alloc.ps1` — a real allocation record exists and `main`'s highest is 0161 — so the number is sound, but the index row should be re-checked before landing since a rebase can drop it. + +> **The claim most worth verifying by execution is the partial halt:** a halt that stops intake while routed and outbound rows keep draining would still violate the invariant this item exists to protect. **Cluster:** Logging & Audit. **Priority:** P3. **Verdict:** demand-gate. **Severity (vs Corepoint):** minor. @@ -7581,6 +7588,17 @@ gate is the wrong shape, validation of the walk is the right one. > 5. **It weakens a forcing function, and that trade should be made deliberately.** `allow_unbounded_phi` (#186a) currently makes a PHI instance REFUSE TO START in prod unless both windows are bounded -- which forces every operator to choose a retention window consciously. With a bounded default that gate rarely fires: **more secure by default, less explicit by default.** > **Framing note:** 60 days is an OPERATIONAL default, not a compliance number. The compliance retention here is the audit chain, which stays keep-forever. Nothing in this item should imply 60 days satisfies a regulatory requirement. +> ⚠️ **AMENDED 2026-08-11 — BUILT, REFUTED, AND REVERTED. Do not read this item as merely unbuilt; the obvious implementation is now known to be WRONG.** The change was made (default `messages_days` / `dead_letter_days` `0` -> `60`), reviewed by another session, verified by execution, and **reverted at `ed8a09d7`** on `a-1212-retention`. Forward-only, so the two reverted commits stay readable as the record. + +> **THE DEFECT, and it is the inversion of this item's intent.** `serve`'s posture gate is wrapped in `if not settings.retention.allow_unbounded_phi:` — so when the **audited, deliberate keep-forever opt-out is TRUE, the auto-bound block is SKIPPED ENTIRELY and the field keeps its model default.** Measured: `RetentionSettings(allow_unbounded_phi=True).messages_days` returns `60` under the change. The `RetentionRunner` then starts and **purges PHI bodies after 60 days on exactly the instances whose operator consciously chose to keep them forever.** Silent PHI deletion against an explicit audited decision, landing *only* on the opt-in path. That is worse than the unbounded default this item was filed to fix. + +> **AND IT WAS A NO-OP WHERE IT WAS AIMED.** The auto-bound keys on `model_fields_set` — whether the operator *wrote* the key, not what its value is — so an operator who says nothing still resolves to **30** and always did. Changing the model default never survives the gate on any instance the gate runs on. + +> **A PREMISE IN THIS ITEM'S OWN TRAP 5 IS STALE.** Trap 5 warns the change *"weakens a forcing function"* because a PHI instance would otherwise refuse to start unbounded. **PR #71 already inverted that gate:** a production PHI instance under `enforce` with an unset window **no longer refuses — it auto-bounds at 30 and starts**, and the repo's own test flipped with it (`tests/test_cli.py:1619` is now `test_serve_auto_bounds_an_unset_body_window_in_prod`, asserting `rc == 0`). So the "more secure by default, less explicit by default" trade **was already made** and is not this item's to weigh. Note `docs/PHI.md` §8 still described the old refusal, which is how that stale premise survived into this filing — the doc-vs-code gap recorded under ASVS 14.2.4 ground (5), misleading a second engineer in practice. + +> **IF THE 60 IS STILL WANTED, the value to change is `auto_bound_days` in `messagefoundry/config/retention_classification.py`** — what the gate actually applies — **not the field default. And moving it `30` -> `60` LOOSENS the shipped PHI bound rather than tightening it**, which sharpens the owner's question rather than answering it. That module carries **nine** classified windows behind `MIN_PHI_RETENTION_WINDOWS = 9` with a **two-way** drift test against `docs/PHI.md` §2, so any real window change must move the tuple **and** the doc together or `tests/test_retention_classification_drift.py` reds. + +> **`audit_days` staying `0` was the one part of this item that held up** — the audit log is a tamper-evident chain with a ~6-year HIPAA expectation, and a sweep that "fixed all the zero defaults" would convert a compliance property into a defect while looking finished. **Cluster:** Store / Retention. **Priority:** P2. **Verdict:** build. **Severity:** conditional -- on a first deployment an operator who omits the setting would retain full raw PHI bodies indefinitely; there are zero deployments today.