RISK-WP-0005-T01: read the fix records, and two findings moved
fix_tracker.py resolves fix_tracking against the owning repo's workplan file — the ADR-001 source of truth — and uses the file's last commit date as the honest answer to 'has this moved', independent of whether the register looked. Archived workplans are searched too, so a finished fix that was filed away does not read as missing. First run, three findings it should have known about: RISK-F-0005 — AUDIT-WP-0008-T04 has read done since 2026-08-18. The fix this finding asked for has landed and the register spent three days not knowing. Now mitigated, embargo lifted, disclosure public. Not fixed: that needs a probe, and T05's adversarial evidence artifact still reads wait. RISK-F-0002 — both tracked records were closed before the finding was filed: WARDEN-WP-0007 archived 2026-07-08, FLEX-WP-0007 finished 2026-06-29, against a finding of 2026-08-18 that names FLEX-WP-0007 as the blocker. Routed as a question, not a conclusion. Four findings carry no fix tracking at all, which the report now says out loud rather than leaving as an empty field. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
0ff87c22d8
commit
ba3b3f686d
7 changed files with 217 additions and 13 deletions
6
Makefile
6
Makefile
|
|
@ -1,12 +1,13 @@
|
||||||
PY := python3
|
PY := python3
|
||||||
TOOLS := tools
|
TOOLS := tools
|
||||||
|
|
||||||
.PHONY: help register check checked due
|
.PHONY: help register check checked due fixes
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo "make register - rebuild REGISTER.md from findings/"
|
@echo "make register - rebuild REGISTER.md from findings/"
|
||||||
@echo "make check - verify the index, report what is going quiet, and read the inbox"
|
@echo "make check - verify the index, report what is going quiet, and read the inbox"
|
||||||
@echo "make due - just the work list: what needs a check right now"
|
@echo "make due - just the work list: what needs a check right now"
|
||||||
|
@echo "make fixes - the state of every fix this register claims to track"
|
||||||
@echo "make checked - record a check outcome: make checked ARGS=\"RISK-F-0002 clean\""
|
@echo "make checked - record a check outcome: make checked ARGS=\"RISK-F-0002 clean\""
|
||||||
|
|
||||||
register:
|
register:
|
||||||
|
|
@ -26,3 +27,6 @@ checked:
|
||||||
due:
|
due:
|
||||||
@$(PY) $(TOOLS)/register_check.py | sed -n '/Checks due/,/^$$/p'
|
@$(PY) $(TOOLS)/register_check.py | sed -n '/Checks due/,/^$$/p'
|
||||||
@$(PY) $(TOOLS)/inbox_check.py
|
@$(PY) $(TOOLS)/inbox_check.py
|
||||||
|
|
||||||
|
fixes:
|
||||||
|
@$(PY) $(TOOLS)/fix_tracker.py
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Register
|
# Register
|
||||||
|
|
||||||
Generated by `tools/register_index.py` from `findings/`. Do not edit by hand. Last built 2026-08-20.
|
Generated by `tools/register_index.py` from `findings/`. Do not edit by hand. Last built 2026-08-21.
|
||||||
|
|
||||||
8 live of 9 findings; 3 notes below the floor.
|
8 live of 9 findings; 3 notes below the floor.
|
||||||
|
|
||||||
|
|
@ -12,7 +12,7 @@ Generated by `tools/register_index.py` from `findings/`. Do not edit by hand. La
|
||||||
| [RISK-F-0008](findings/RISK-F-0008-audit-retention-legal-basis-assumed.md) | The legal basis for retaining audit facts against an erasure request has been assumed, never established | audit-core | medium | public | **answered** (t2, answered) | risk-nexus | accepted | instant (0) | **due** |
|
| [RISK-F-0008](findings/RISK-F-0008-audit-retention-legal-basis-assumed.md) | The legal basis for retaining audit facts against an erasure request has been assumed, never established | audit-core | medium | public | **answered** (t2, answered) | risk-nexus | accepted | instant (0) | **due** |
|
||||||
| [RISK-F-0007](findings/RISK-F-0007-unverified-tenant-boundary.md) | No consumer's tenant boundary is verified anywhere | estate | **high** | embargoed | **answered** (t4, assigned) | per-consumer, on request | accepted | instant (0) | **due** |
|
| [RISK-F-0007](findings/RISK-F-0007-unverified-tenant-boundary.md) | No consumer's tenant boundary is verified anywhere | estate | **high** | embargoed | **answered** (t4, assigned) | per-consumer, on request | accepted | instant (0) | **due** |
|
||||||
| [RISK-F-0006](findings/RISK-F-0006-apps-pg-no-backup-configured.md) | apps-pg has no backup configured at all: R0 means no recovery | railiance-platform | **high** | embargoed | **answered** (t3, approved) | railiance-platform | open | 1h (1) | **due** |
|
| [RISK-F-0006](findings/RISK-F-0006-apps-pg-no-backup-configured.md) | apps-pg has no backup configured at all: R0 means no recovery | railiance-platform | **high** | embargoed | **answered** (t3, approved) | railiance-platform | open | 1h (1) | **due** |
|
||||||
| [RISK-F-0005](findings/RISK-F-0005-audit-core-unfiltered-read-path.md) | audit-core read path applies no tenant filter; the bound is deployment, not code | audit-core | medium | embargoed | none | audit-core | open | 1h (1) | **due** |
|
| [RISK-F-0005](findings/RISK-F-0005-audit-core-unfiltered-read-path.md) | audit-core read path applies no tenant filter; the bound is deployment, not code | audit-core | medium | public | none | audit-core | mitigated | instant (0) | **due** |
|
||||||
| [RISK-F-0004](findings/RISK-F-0004-tenant-engine-unfiltered-event-read.md) | tenant-engine events() returns the entire event log unfiltered | tenant-engine | **high** | embargoed | none | tenant-engine | open | 1h (1) | **due** |
|
| [RISK-F-0004](findings/RISK-F-0004-tenant-engine-unfiltered-event-read.md) | tenant-engine events() returns the entire event log unfiltered | tenant-engine | **high** | embargoed | none | tenant-engine | open | 1h (1) | **due** |
|
||||||
| [RISK-F-0003](findings/RISK-F-0003-ops-warden-read-boundary-ungraded-lanes.md) | ops-warden agent read-boundary does not fire on ungraded catalog lanes | ops-warden | medium | embargoed | none | ops-warden | mitigated | 1h (1) | **due** |
|
| [RISK-F-0003](findings/RISK-F-0003-ops-warden-read-boundary-ungraded-lanes.md) | ops-warden agent read-boundary does not fire on ungraded catalog lanes | ops-warden | medium | embargoed | none | ops-warden | mitigated | 1h (1) | **due** |
|
||||||
| [RISK-F-0002](findings/RISK-F-0002-ops-warden-sign-ungated.md) | ops-warden signs SSH certificates with no authorization decision, and its unblock is now unsafe | ops-warden | medium | embargoed | **withdrawn** (t6, withdrawn-hazard-window-closed) | ops-warden | open | instant (0) | **due** |
|
| [RISK-F-0002](findings/RISK-F-0002-ops-warden-sign-ungated.md) | ops-warden signs SSH certificates with no authorization decision, and its unblock is now unsafe | ops-warden | medium | embargoed | **withdrawn** (t6, withdrawn-hazard-window-closed) | ops-warden | open | instant (0) | **due** |
|
||||||
|
|
@ -51,7 +51,6 @@ Held from publication with a stated condition. A hold with no moving condition i
|
||||||
| RISK-F-0009 | 2026-08-20 | railiance-platform reports the deny set covers every high-risk lane with a KV path (live verification refines the grade, it is not the condition) | — |
|
| RISK-F-0009 | 2026-08-20 | railiance-platform reports the deny set covers every high-risk lane with a KV path (live verification refines the grade, it is not the condition) | — |
|
||||||
| RISK-F-0007 | 2026-08-19 | a verification exists for at least one consumer boundary | 2026-09-18 |
|
| RISK-F-0007 | 2026-08-19 | a verification exists for at least one consumer boundary | 2026-09-18 |
|
||||||
| RISK-F-0006 | 2026-08-19 | a backup exists and a restore has been demonstrated once | 2026-09-18 |
|
| RISK-F-0006 | 2026-08-19 | a backup exists and a restore has been demonstrated once | 2026-09-18 |
|
||||||
| RISK-F-0005 | 2026-08-19 | AUDIT-WP-0008-T04 lands a tenant filter in the read path | 2026-11-17 |
|
|
||||||
| RISK-F-0004 | 2026-08-19 | the read path filters by tenant in code | 2026-09-18 |
|
| RISK-F-0004 | 2026-08-19 | the read path filters by tenant in code | 2026-09-18 |
|
||||||
| RISK-F-0003 | 2026-08-19 | RISK-F-0009 resolved — the OpenBao deny set covers every high-risk lane with a KV path | 2026-09-18 |
|
| RISK-F-0003 | 2026-08-19 | RISK-F-0009 resolved — the OpenBao deny set covers every high-risk lane with a KV path | 2026-09-18 |
|
||||||
| RISK-F-0002 | 2026-08-19 | FLEX-WP-0015-T02 shipped and ops-warden policy.enabled true in production | 2026-11-17 |
|
| RISK-F-0002 | 2026-08-19 | FLEX-WP-0015-T02 shipped and ops-warden policy.enabled true in production | 2026-11-17 |
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
| workplan | RISK-WP-0002 | active | — | workplans/RISK-WP-0002-publication-handover.md |
|
| workplan | RISK-WP-0002 | active | — | workplans/RISK-WP-0002-publication-handover.md |
|
||||||
| workplan | RISK-WP-0003 | finished | — | workplans/RISK-WP-0003-regulatory-intake.md |
|
| workplan | RISK-WP-0003 | finished | — | workplans/RISK-WP-0003-regulatory-intake.md |
|
||||||
| workplan | RISK-WP-0004 | finished | — | workplans/RISK-WP-0004-run-the-register.md |
|
| workplan | RISK-WP-0004 | finished | — | workplans/RISK-WP-0004-run-the-register.md |
|
||||||
|
| workplan | RISK-WP-0005 | active | — | workplans/RISK-WP-0005-close-the-intent-gaps.md |
|
||||||
| task | RISK-WP-0001-T01 | done | — | workplans/RISK-WP-0001-make-the-register-decidable.md |
|
| task | RISK-WP-0001-T01 | done | — | workplans/RISK-WP-0001-make-the-register-decidable.md |
|
||||||
| task | RISK-WP-0001-T02 | done | — | workplans/RISK-WP-0001-make-the-register-decidable.md |
|
| task | RISK-WP-0001-T02 | done | — | workplans/RISK-WP-0001-make-the-register-decidable.md |
|
||||||
| task | RISK-WP-0001-T03 | done | — | workplans/RISK-WP-0001-make-the-register-decidable.md |
|
| task | RISK-WP-0001-T03 | done | — | workplans/RISK-WP-0001-make-the-register-decidable.md |
|
||||||
|
|
@ -33,3 +34,10 @@
|
||||||
| task | RISK-WP-0004-T04 | done | — | workplans/RISK-WP-0004-run-the-register.md |
|
| task | RISK-WP-0004-T04 | done | — | workplans/RISK-WP-0004-run-the-register.md |
|
||||||
| task | RISK-WP-0004-T05 | done | — | workplans/RISK-WP-0004-run-the-register.md |
|
| task | RISK-WP-0004-T05 | done | — | workplans/RISK-WP-0004-run-the-register.md |
|
||||||
| task | RISK-WP-0004-T06 | done | — | workplans/RISK-WP-0004-run-the-register.md |
|
| task | RISK-WP-0004-T06 | done | — | workplans/RISK-WP-0004-run-the-register.md |
|
||||||
|
| task | RISK-WP-0005-T01 | todo | — | workplans/RISK-WP-0005-close-the-intent-gaps.md |
|
||||||
|
| task | RISK-WP-0005-T02 | todo | — | workplans/RISK-WP-0005-close-the-intent-gaps.md |
|
||||||
|
| task | RISK-WP-0005-T03 | todo | — | workplans/RISK-WP-0005-close-the-intent-gaps.md |
|
||||||
|
| task | RISK-WP-0005-T04 | todo | — | workplans/RISK-WP-0005-close-the-intent-gaps.md |
|
||||||
|
| task | RISK-WP-0005-T05 | todo | — | workplans/RISK-WP-0005-close-the-intent-gaps.md |
|
||||||
|
| task | RISK-WP-0005-T06 | todo | — | workplans/RISK-WP-0005-close-the-intent-gaps.md |
|
||||||
|
| task | RISK-WP-0005-T07 | todo | — | workplans/RISK-WP-0005-close-the-intent-gaps.md |
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@ embargo_review: "2026-11-17"
|
||||||
escalation: withdrawn
|
escalation: withdrawn
|
||||||
escalation_trigger: 6
|
escalation_trigger: 6
|
||||||
escalation_status: withdrawn-hazard-window-closed
|
escalation_status: withdrawn-hazard-window-closed
|
||||||
last_checked: "2026-08-20T10:02:41Z"
|
last_checked: "2026-08-21T06:29:10Z"
|
||||||
next_check: "2026-08-20T10:02:41Z"
|
next_check: "2026-08-21T06:29:10Z"
|
||||||
cadence: instant
|
cadence: instant
|
||||||
clean_streak: 0
|
clean_streak: 0
|
||||||
waiting_on:
|
waiting_on:
|
||||||
|
|
@ -274,3 +274,53 @@ reason than the one this finding was filed for. That is the second time in two
|
||||||
days that this finding's blocker turned out to be a claim about the world at a
|
days that this finding's blocker turned out to be a claim about the world at a
|
||||||
date.
|
date.
|
||||||
- **2026-08-20** — not clean: RISK-V-0001 found the flex-auth-ops-warden policy admits no ingress; the live question is now availability, not attestation. Cadence instant → instant; checked again immediately.
|
- **2026-08-20** — not clean: RISK-V-0001 found the flex-auth-ops-warden policy admits no ingress; the live question is now availability, not attestation. Cadence instant → instant; checked again immediately.
|
||||||
|
|
||||||
|
## Check — 2026-08-21: both tracked records were already closed when this was filed
|
||||||
|
|
||||||
|
`RISK-WP-0005-T01` made this register read the fix records it had been writing
|
||||||
|
down without reading. The first run answered a question nobody had asked:
|
||||||
|
|
||||||
|
| Tracked | State | Last changed |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `WARDEN-WP-0007` | **archived** | 2026-07-08 |
|
||||||
|
| `FLEX-WP-0007` | **finished** | 2026-06-29 |
|
||||||
|
|
||||||
|
This finding was filed on **2026-08-18** and states that the gate is "blocked
|
||||||
|
on `FLEX-WP-0007`". That workplan had read `finished` for seven weeks by then.
|
||||||
|
`flex-auth` restated the same blocker to this register on 2026-08-19 —
|
||||||
|
"`policy.enabled` remains off (`FLEX-WP-0007`)" — while their own authoritative
|
||||||
|
record said it was done.
|
||||||
|
|
||||||
|
**The register is not concluding that the blocker is gone.** Three readings
|
||||||
|
fit and only the owners can say which: the deployment finished and was later
|
||||||
|
reverted or never applied; "finished" means something narrower than the
|
||||||
|
sentence implies; or the blocker is genuinely stale and two repos have been
|
||||||
|
repeating it.
|
||||||
|
|
||||||
|
What can be said without either owner's help is that **the stated blocker has
|
||||||
|
not matched the stated record since June**, and nothing would have caught that
|
||||||
|
before this register started reading fix state.
|
||||||
|
|
||||||
|
### This is the third time on the same finding
|
||||||
|
|
||||||
|
`RISK-F-0002` argued, when it was filed, that a blocker is a claim about the
|
||||||
|
world at a date, and that its own had been invalidated in a day with nothing
|
||||||
|
re-checking it. Since then:
|
||||||
|
|
||||||
|
1. `RISK-F-0001` was fixed hours after this register graded the ordering hazard;
|
||||||
|
2. `RISK-V-0001` found a NetworkPolicy that may make enabling the gate fail
|
||||||
|
closed for an entirely different reason;
|
||||||
|
3. and now both tracked records turn out to have been closed before the finding
|
||||||
|
was written.
|
||||||
|
|
||||||
|
The finding was right about the estate and right about itself. Severity is
|
||||||
|
unchanged at `medium` — none of this changes what the gate does today — but
|
||||||
|
what is *blocking* it has been wrong in the record for two months.
|
||||||
|
|
||||||
|
### The ask, sharpened
|
||||||
|
|
||||||
|
Previously: probe whether the flex-auth pin admits ingress. Now, additionally:
|
||||||
|
**what actually blocks `policy.enabled` today?** If the answer is "nothing
|
||||||
|
except the ingress question", that is a much shorter path than the one both
|
||||||
|
repos have been describing.
|
||||||
|
- **2026-08-21** — not clean: Fix tracking read for the first time: WARDEN-WP-0007 archived 2026-07-08, FLEX-WP-0007 finished 2026-06-29 — both closed before the finding was filed. Cadence instant → instant; checked again immediately.
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
id: RISK-F-0005
|
id: RISK-F-0005
|
||||||
type: finding
|
type: finding
|
||||||
title: "audit-core read path applies no tenant filter; the bound is deployment, not code"
|
title: "audit-core read path applies no tenant filter; the bound is deployment, not code"
|
||||||
status: open
|
status: mitigated
|
||||||
reported_by: audit-core
|
reported_by: audit-core
|
||||||
reported_via: flex-auth
|
reported_via: flex-auth
|
||||||
routed_by: risk-nexus
|
routed_by: risk-nexus
|
||||||
|
|
@ -20,15 +20,16 @@ impact: I3
|
||||||
likelihood: L2
|
likelihood: L2
|
||||||
fidelity_modifier: false
|
fidelity_modifier: false
|
||||||
production_rescore: true
|
production_rescore: true
|
||||||
disclosure: embargoed
|
disclosure: public
|
||||||
embargo_condition: "AUDIT-WP-0008-T04 lands a tenant filter in the read path"
|
publication: pending-handover
|
||||||
|
embargo_condition: "met 2026-08-18 — AUDIT-WP-0008-T04 reads done in audit-core's workplan"
|
||||||
embargo_since: "2026-08-19"
|
embargo_since: "2026-08-19"
|
||||||
embargo_review: "2026-11-17"
|
embargo_review: "2026-11-17"
|
||||||
escalation: none
|
escalation: none
|
||||||
last_checked: "2026-08-20T10:02:42Z"
|
last_checked: "2026-08-21T06:29:38Z"
|
||||||
next_check: "2026-08-20T11:02:42Z"
|
next_check: "2026-08-21T06:29:38Z"
|
||||||
cadence: 1h
|
cadence: instant
|
||||||
clean_streak: 1
|
clean_streak: 0
|
||||||
waiting_on:
|
waiting_on:
|
||||||
- who: audit-core
|
- who: audit-core
|
||||||
what: "is may_read false on every production credential, or only on the sender"
|
what: "is may_read false on every production credential, or only on the sender"
|
||||||
|
|
@ -85,3 +86,40 @@ No escalation. Known, owned, tracked, moving.
|
||||||
Open at review: has `AUDIT-WP-0008-T04` moved; is `may_read: false` still
|
Open at review: has `AUDIT-WP-0008-T04` moved; is `may_read: false` still
|
||||||
true of every production credential, not only the sender.
|
true of every production credential, not only the sender.
|
||||||
- **2026-08-20** — clean check: checked against the inbox and the owner's record; nothing moved. Cadence instant → 1h (1 clean in a row); next check 2026-08-20 11:02Z.
|
- **2026-08-20** — clean check: checked against the inbox and the owner's record; nothing moved. Cadence instant → 1h (1 clean in a row); next check 2026-08-20 11:02Z.
|
||||||
|
|
||||||
|
## Check — 2026-08-21: the fix landed three days ago and nobody here knew
|
||||||
|
|
||||||
|
`RISK-WP-0005-T01`'s first run read `AUDIT-WP-0008-T04` in `audit-core`'s own
|
||||||
|
workplan file: **`status: done`**, last changed 2026-08-18.
|
||||||
|
|
||||||
|
The task text is the fix this finding asked for — bind the read path to
|
||||||
|
permitted tenants, `permits_tenant` governing read as it governs write at the
|
||||||
|
same choke point, tenant-scoping `/v1/events`, `/v1/events/<id>`,
|
||||||
|
`/v1/dead-letters` and `/v1/secret-findings`, with tests for cross-tenant read
|
||||||
|
refusal.
|
||||||
|
|
||||||
|
**Three days sat between the fix landing and this register knowing.** Nothing
|
||||||
|
was wrong with `audit-core`'s conduct — they did the work and recorded it in
|
||||||
|
the authoritative place. The register was carrying a string it never read,
|
||||||
|
which is exactly the gap `RISK-WP-0005-T01` exists to close, found on the run
|
||||||
|
that closed it.
|
||||||
|
|
||||||
|
**`mitigated`, not `fixed`.** The live gap is closed on the owner's record; the
|
||||||
|
finding is not, for two reasons:
|
||||||
|
|
||||||
|
1. This register has read a workplan status, not a probe. The standard for
|
||||||
|
`fixed` in `docs/method/review.md` is something concrete about the running
|
||||||
|
system, and `audit-core` is the one who can supply it.
|
||||||
|
2. `AUDIT-WP-0008-T05` — the adversarial `E2` evidence artifact, an identity
|
||||||
|
bound to tenant A demonstrably unable to read tenant B — reads `wait`. The
|
||||||
|
control exists; the demonstration that it holds does not.
|
||||||
|
|
||||||
|
**The embargo lifts.** Its condition was `AUDIT-WP-0008-T04` landing a tenant
|
||||||
|
filter, and that is met. The finding describes a read path that no longer
|
||||||
|
applies no filter, so publishing it no longer shortens anyone's path.
|
||||||
|
`disclosure: public`, handover pending with the others.
|
||||||
|
|
||||||
|
The `may_read` question — is it false on every production credential, or only
|
||||||
|
on the sender — stays open and matters less now that the bound is in code
|
||||||
|
rather than in a flag.
|
||||||
|
- **2026-08-21** — not clean: AUDIT-WP-0008-T04 reads done in audit-core's workplan since 2026-08-18; embargo condition met, status mitigated. Cadence 1h → instant; checked again immediately.
|
||||||
|
|
|
||||||
101
tools/fix_tracker.py
Normal file
101
tools/fix_tracker.py
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Read the state of the fixes this register claims to be tracking.
|
||||||
|
|
||||||
|
RISK-WP-0005-T01. Until now `fix_tracking` held ids like `FLEX-WP-0015-T02`
|
||||||
|
that this repo wrote down and never read, so an owner who had gone quiet and a
|
||||||
|
fix that had gone quiet were indistinguishable.
|
||||||
|
|
||||||
|
Resolution is against the owning repo's workplan **file**, which is the
|
||||||
|
ADR-001 source of truth, and the file's last commit date is the honest answer
|
||||||
|
to "has this moved" — independent of whether this register happened to look.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import datetime as dt
|
||||||
|
import pathlib
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import register_lib as lib
|
||||||
|
|
||||||
|
ESTATE = pathlib.Path("/home/worsch")
|
||||||
|
WP_ID = re.compile(r"\b([A-Z]+-WP-[0-9]{4})(-T[0-9]{2,3})?\b")
|
||||||
|
|
||||||
|
# How long a fix may sit unchanged before the register says so, by severity.
|
||||||
|
STALL_DAYS = {"critical": 14, "high": 14, "medium": 30, "low": 60}
|
||||||
|
|
||||||
|
|
||||||
|
def find_workplan(wp_id: str) -> pathlib.Path | None:
|
||||||
|
hits = sorted(ESTATE.glob(f"*/workplans/{wp_id}-*.md"))
|
||||||
|
if hits:
|
||||||
|
return hits[0]
|
||||||
|
# Archived workplans are still the record. A fix that finished and was
|
||||||
|
# archived must not read as "not found in the estate" — that is the
|
||||||
|
# register inventing a gap out of a filing convention.
|
||||||
|
archived = sorted(ESTATE.glob(f"*/workplans/archived/*{wp_id}-*.md"))
|
||||||
|
return archived[0] if archived else None
|
||||||
|
|
||||||
|
|
||||||
|
def last_commit(path: pathlib.Path) -> dt.datetime | None:
|
||||||
|
try:
|
||||||
|
out = subprocess.run(
|
||||||
|
["git", "-C", str(path.parent.parent), "log", "-1", "--format=%cI", "--", str(path)],
|
||||||
|
capture_output=True, text=True, timeout=15,
|
||||||
|
).stdout.strip()
|
||||||
|
return lib.moment(out) if out else None
|
||||||
|
except (subprocess.SubprocessError, OSError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def task_status(path: pathlib.Path, task_id: str) -> str | None:
|
||||||
|
m = re.search(rf"id: {re.escape(task_id)}\s*\nstatus: (\w+)", path.read_text(encoding="utf-8"))
|
||||||
|
return m.group(1) if m else None
|
||||||
|
|
||||||
|
|
||||||
|
def resolve(fix_tracking: str) -> list[dict]:
|
||||||
|
"""One entry per work-record id named in the finding's fix_tracking."""
|
||||||
|
out = []
|
||||||
|
for wp, task in WP_ID.findall(str(fix_tracking or "")):
|
||||||
|
path = find_workplan(wp)
|
||||||
|
if path is None:
|
||||||
|
out.append({"id": wp + (task or ""), "state": "not found in the estate", "moved": None})
|
||||||
|
continue
|
||||||
|
fm = lib.load(path)
|
||||||
|
archived = "archived" in path.parts
|
||||||
|
full = wp + task if task else wp
|
||||||
|
state = task_status(path, full) if task else fm.get("status")
|
||||||
|
out.append({
|
||||||
|
"id": full,
|
||||||
|
"repo": fm.get("repo", path.parent.parent.name),
|
||||||
|
"state": (state or "unreadable") + (" [archived]" if archived else ""),
|
||||||
|
"workplan_status": fm.get("status"),
|
||||||
|
"moved": last_commit(path),
|
||||||
|
})
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def report() -> list[str]:
|
||||||
|
lines, now = [], lib.now()
|
||||||
|
for f in lib.findings():
|
||||||
|
if not lib.watched(f.get("status")):
|
||||||
|
continue
|
||||||
|
tracking = f.get("fix_tracking")
|
||||||
|
if not tracking or str(tracking).startswith("unset"):
|
||||||
|
lines.append(f"{f['id']} ({f.get('severity')}) — NO FIX TRACKING; owner {f.get('fix_owner')}")
|
||||||
|
continue
|
||||||
|
for r in resolve(tracking):
|
||||||
|
if r["moved"]:
|
||||||
|
age = (now - r["moved"]).days
|
||||||
|
window = STALL_DAYS.get(f.get("severity"), 30)
|
||||||
|
done = str(r["state"]).lower() in ("done", "finished", "cancel")
|
||||||
|
flag = "" if done or age <= window else f" ← UNCHANGED {age}d (window {window}d)"
|
||||||
|
lines.append(f"{f['id']} — {r['id']}: {r['state']}, last changed {r['moved']:%Y-%m-%d}{flag}")
|
||||||
|
else:
|
||||||
|
lines.append(f"{f['id']} — {r['id']}: {r['state']}")
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("Fix state, read from the owning repo's workplan file\n")
|
||||||
|
for line in report():
|
||||||
|
print(" ", line)
|
||||||
|
|
@ -9,6 +9,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
|
|
||||||
|
import fix_tracker
|
||||||
import register_lib as lib
|
import register_lib as lib
|
||||||
|
|
||||||
NOW = lib.now()
|
NOW = lib.now()
|
||||||
|
|
@ -143,6 +144,9 @@ def main() -> int:
|
||||||
if ref in waiting_ids and f.get("waiting_on"):
|
if ref in waiting_ids and f.get("waiting_on"):
|
||||||
deep.append(f"{f['id']} waits, and points at {ref} which also waits — depth two, cut one")
|
deep.append(f"{f['id']} waits, and points at {ref} which also waits — depth two, cut one")
|
||||||
section("Waiting on someone", waiting, "nothing is waiting on anyone")
|
section("Waiting on someone", waiting, "nothing is waiting on anyone")
|
||||||
|
# RISK-WP-0005-T01: the fix's own state, read from the owner's workplan
|
||||||
|
# file rather than from our memory of what they told us.
|
||||||
|
section("Fix state", fix_tracker.report(), "no finding claims a tracked fix")
|
||||||
if due_defaults:
|
if due_defaults:
|
||||||
section("Defaults now due — apply them", due_defaults, "none")
|
section("Defaults now due — apply them", due_defaults, "none")
|
||||||
if deep:
|
if deep:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue