diff --git a/Makefile b/Makefile index f4c2ddf..64078f6 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,13 @@ PY := python3 TOOLS := tools -.PHONY: help register check checked due fixes coverage +.PHONY: help register check checked due fixes help: @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 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 coverage - what the register has never heard from" @echo "make checked - record a check outcome: make checked ARGS=\"RISK-F-0002 clean\"" register: @@ -31,6 +30,3 @@ due: fixes: @$(PY) $(TOOLS)/fix_tracker.py - -coverage: - @$(PY) $(TOOLS)/coverage.py diff --git a/README.md b/README.md index 3231d2e..6afbfed 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,7 @@ # risk-nexus -Risk register and regulatory intake for the estate. Owned by `the-custodian`. - -**It does not serve anything yet.** `INTENT.md` names `risk.coulomb.social` as -the eventual surface; today publication runs through `policy-nexus` and three -documents are waiting for an address. Recorded here rather than left as a -claim, because a stated surface that does not exist is the class of thing this -register grades other repos down for. +Risk register and regulatory intake for the estate. Serves +`risk.coulomb.social`. Owned by `the-custodian`. Holds findings — security, architecture, operational, compliance — with a severity, an owner and a date; decides whether and when each is published; and diff --git a/REGISTER.md b/REGISTER.md index b950082..694da4e 100644 --- a/REGISTER.md +++ b/REGISTER.md @@ -14,7 +14,7 @@ Generated by `tools/register_index.py` from `findings/`. Do not edit by hand. La | [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 | 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-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 | 8h (2) | 2026-08-21 14:32Z | +| [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-0001](findings/RISK-F-0001-flex-auth-unauthenticated-check.md) | flex-auth /v1/check authenticates no caller | flex-auth | **high** | public | **withdrawn** (t1, withdrawn-before-sending) | flex-auth | fixed | instant (0) | **due** | diff --git a/docs/method/check-procedure.md b/docs/method/check-procedure.md index efc04c6..7284cd5 100644 --- a/docs/method/check-procedure.md +++ b/docs/method/check-procedure.md @@ -43,7 +43,6 @@ Then, per due finding: Then record it, in the same sitting: ``` -RISK_CHECKED_BY= \ make checked ARGS="RISK-F-0002 clean" make checked ARGS="RISK-F-0002 moved 'ops-warden enabled the gate'" make checked ARGS="RISK-F-0002 defer 2026-09-01 'operator: after the migration'" diff --git a/docs/method/escalation.md b/docs/method/escalation.md index d12d0d3..0309815 100644 --- a/docs/method/escalation.md +++ b/docs/method/escalation.md @@ -184,26 +184,6 @@ The test therefore applies to intake, not to sweeps: If that trips, the first suspects are triggers 3 and 4 — spend thresholds set too low, and "unowned" being read where "not yet routed" is meant. -## Delivery is a state, not an act - -`RISK-WP-0005-T05`. An escalation that nobody acknowledged is indistinguishable -from one never sent — which is precisely the failure this register committed -on 2026-08-19 and then fixed for its **own** inbox with an hourly watch, while -leaving the path that matters more unguarded. - -So an escalation carries a state: - -```yaml -escalation_status: pending-operator # sent | seen | answered | withdrawn -escalation_sent: "2026-08-19" -``` - -`make check` reports how long each has been unacknowledged. At seven days it -says so and the escalation is **raised once more** — once, per the rule above. -After that the default applies and is recorded. Repetition until someone -answers is how the operator becomes the queue, and silence that is recorded is -not the same as silence that is ignored. - ## Escalations are batched Four escalations are one conversation, not four interruptions. Open items go diff --git a/docs/method/intake.md b/docs/method/intake.md deleted file mode 100644 index 0720be7..0000000 --- a/docs/method/intake.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -id: RISK-METHOD-INTAKE -type: method -title: "Intake: the four sources, including the two that had no path" -status: adopted -owner: risk-nexus -adopted: "2026-08-21" -workplan: RISK-WP-0005-T02 -review_interval: 6m ---- - -# Intake - -`INTENT.md` claims intake "from any source: research, review, incident, -external report". Until 2026-08-21 two of those four had no path at all, and -every finding in the register had arrived from a repo reading its own code. - -A register that only receives self-assessments is a self-assessment aggregator. -That is a respectable thing to be and it is not what the intent says. - -## The four sources - -| Source | Path | State | -| --- | --- | --- | -| Research | a repo or agent files against `findings/README.md` | working, nine findings | -| Review | same, usually against a ladder or standard | working, most of the nine | -| **Incident** | below | new | -| **External report** | below — and the address is not this repo's to create | routed | - -## Incident - -An incident is a finding about something happening **now** rather than a latent -defect. The difference is not severity; it is that someone is exercising it, or -the loss is occurring, while the record is being written. - -### What is different - -- **File first, grade later.** The normal order is reversed: get the record - into `findings/` with what is known, and grade it in the same hour rather - than in the same sitting. An ungraded incident is still a record; an unwritten - one is nothing. -- **`incident: true` and a `first_observed` timestamp**, both in front-matter. - The timestamp matters more than the grade, because obligations run from it. -- **Cadence starts at `instant` and stays there** until the incident is over. - That is the ladder's existing behaviour and needs no special case: something - that keeps moving keeps resetting. -- **The 72-hour clock.** If personal data is or may be involved, - `RISK-POL-0005` (GDPR Arts 33–34) requires notification within 72 hours of - becoming aware. **`first_observed` is what starts that clock**, and this is - the one place the register puts a hard deadline on itself. Escalation is - immediate under trigger 1 or 2 — not batched, notwithstanding the batching - rule, because batching exists to protect the operator's attention and a - 72-hour clock outranks that. -- **Severity is scored on what is happening, not what could.** The `L4` - likelihood band — "already happening" — exists for exactly this and is - otherwise almost unused. - -### What is not different - -Ownership, routing, and the boundary. The repo that owns the system owns the -response; this register records, grades, escalates and keeps the clock. A risk -register that runs incident response is a second engineering team with a -stopwatch. - -## External report - -Someone outside the estate finding something and having somewhere to send it. - -**No such place exists.** There is no `security.txt` anywhere in the estate, no -published address, and no route from an outsider to this register. If someone -found a flaw in a coulomb.social surface tomorrow, their options would be to -guess an email address or say nothing. - -**Where that address lives is not this repo's decision.** It is a published -surface, which makes it `policy-nexus`'s to serve and the custodian's to -approve. Routed on 2026-08-21 with a proposal: - -- a `security.txt` (RFC 9116) at `policy.coulomb.social/.well-known/`, naming - a contact and a preferred language, with an expiry date as the RFC requires; -- reports arriving there routed to `risk-nexus` and filed like any other - finding, with `reported_by: external`; -- **no promise of a bounty, a timeline, or safe harbour.** The ask is a route - in, not a commitment out. A published disclosure policy is a later decision - and a heavier one. - -Until that exists, this register's claim to accept external reports is -aspirational and `STATE.md` says so. - -## What all four share - -The reporter says what is true and how they know. This register says how bad it -is and who hears about it. Nothing in this document changes that boundary — -including for incidents, where the temptation to take over is strongest. diff --git a/docs/method/production-transition.md b/docs/method/production-transition.md deleted file mode 100644 index e5bdf58..0000000 --- a/docs/method/production-transition.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -id: RISK-METHOD-PRODUCTION -type: method -title: "The production transition: what expires, and who says when" -status: proposed -owner: the-custodian -drafted_by: risk-nexus -drafted: "2026-08-21" -workplan: RISK-WP-0005-T03 -review_interval: 6m ---- - -# The production transition - -Five findings owe a re-score here. One acceptance **expires** here. Several -dormant legal policies activate here. And until 2026-08-21 nobody had defined -the event, named who declares it, or told the repos that carry the obligations. - -An acceptance that expires on an undefined event expires on nobody's calendar. - -**Status: `proposed`.** The definition is the custodian's; the plumbing is -this repo's. - -## Proposed definition - -> **The production transition is the first moment any system in the estate -> holds, processes or decides about the data of a real external person or -> tenant.** - -Deliberately not: a launch announcement, a version number, a deployment to a -cluster named production, or a declaration made in advance. All four can be -true while nothing real is at stake, and all four can be false on the day -something is. - -Two consequences worth stating, because they are what make the definition -usable: - -- **It can happen by accident.** A single real signup, a migrated contact list, - one counterparty's document sent through a pipeline for testing. Which is - why it is defined by what is held rather than by what was announced. -- **It is not reversible.** Deleting the data afterwards does not un-transition - the estate; it changes the exposure and not the fact that the estate is now - the kind of thing that holds real data. - -## Who declares it - -The operator, through `the-custodian`. Not this register: `INTENT.md` keeps -"deciding what canon says" and estate-level facts with the custodian, and a -risk register that unilaterally declares the estate to be in production has -appointed itself. - -What this register does is **notice and ask**. Any finding, verification or -policy check that turns up real external data in a system triggers one question -to the custodian: *has the transition happened?* The answer is theirs; the -record is ours. - -## What fires on the day - -| Obligation | Carried by | Count today | -| --- | --- | --- | -| Re-score every finding with `production_rescore: true` | this register | 5 | -| `RISK-F-0007`'s acceptance expires — the unverified tenant boundary stops being carried | this register, escalating | 1 | -| `RISK-POL-0001`, `0003`, `0004`, `0005`, `0008` activate; `0002` fully | owning repos | 6 policies | -| `RISK-F-0008`'s acceptance ends — the retention basis stops being an internal determination | this register | 1 | - -`make check` lists the first two under "owed at the production transition" -today, so the size of the obligation is visible before the day rather than -discovered on it. - -## How the register hears about it - -Three routes, in order of reliability: - -1. **The custodian says so**, in a message. Preferred, and the only one that is - also a decision. -2. **A finding or verification turns up real external data**, and this register - asks. This is the route that works when nobody remembered to declare. -3. **A legal policy activates on its own condition** — the accessibility or - data-subject-rights entries in `docs/regulatory/policies/` fire on facts - about the world, not on a declaration, and a repo consulting one is evidence - the transition may have happened. - -Route 2 exists because route 1 is exactly the kind of announcement that gets -skipped in a busy week, and the estate has already demonstrated (`RISK-POL-0012`) -that an obligation can be live for nineteen months without anyone noticing. - -## What this document does not do - -It does not schedule the transition, gate it, or require anything before it. In -build mode a finding is information — that is unchanged. This says only what -becomes true on a day that will arrive whether or not it is planned. diff --git a/docs/method/review.md b/docs/method/review.md index 7173344..015efd0 100644 --- a/docs/method/review.md +++ b/docs/method/review.md @@ -49,17 +49,6 @@ Two rules and one escape: the only way out of the bottom rung other than a clean check, and it is a decision with a name on it rather than a check quietly not happening. -### Who checked, and when anyone last did - -`RISK-WP-0005-T06`. Two defences against the rung telling a lie: - -- **`checked_by` on every check.** `record_check.py` writes it. A rung earned - by nobody in particular is visible as such. -- **A heartbeat.** If nothing anywhere in the register has been checked for two - days, `make check` says so **before anything else**. A `1q` rung means - "stable for a quarter" and "nobody looked for a quarter", and those read - identically from the outside — the heartbeat is what separates them. - ### The rung is the signal This is the point of the design, not a side effect. **The cadence a finding diff --git a/docs/method/severity.md b/docs/method/severity.md index cd8ae77..9bcdc94 100644 --- a/docs/method/severity.md +++ b/docs/method/severity.md @@ -142,19 +142,6 @@ comes back. > *Added 2026-08-19 (`RISK-WP-0001-T07`).* Forced by `RISK-F-0006`, where the > defect is an absent backup and the reach reading produced nonsense. -## Live incidents - -Everything above assumes a latent defect — something reachable that nobody is -currently reaching. When someone is, three things change: - -- **Likelihood is `L4`.** The band means "already happening" and this is what - it is for. -- **Impact is scored on what has occurred plus what is still reachable**, not - on the worst case. An incident in progress has facts; use them. -- **The grade is provisional and expected to move.** File first, grade within - the hour, re-grade as facts arrive. `docs/method/intake.md` has the rest, - including the 72-hour clock that `first_observed` starts. - ## The floor `INTENT.md`: if a finding would not change anyone's decision, it is a note, diff --git a/findings/RISK-F-0001-flex-auth-unauthenticated-check.md b/findings/RISK-F-0001-flex-auth-unauthenticated-check.md index fd8e94b..a9030f0 100644 --- a/findings/RISK-F-0001-flex-auth-unauthenticated-check.md +++ b/findings/RISK-F-0001-flex-auth-unauthenticated-check.md @@ -27,9 +27,9 @@ publication_subtitle: "The estate's authorization oracle authenticated no caller revision: "graded-1" last_reviewed: "2026-08-20" review_interval: 6m -embargo_lifted: "2026-08-19 — FLEX-WP-0015 finished, live probes return 401" -embargo_was_since: "2026-08-19" - +embargo_condition: "met 2026-08-19 — FLEX-WP-0015 finished, live probes return 401" +embargo_since: "2026-08-19" +embargo_review: "2026-08-19" escalation: withdrawn escalation_trigger: 1 escalation_status: withdrawn-before-sending diff --git a/findings/RISK-F-0003-ops-warden-read-boundary-ungraded-lanes.md b/findings/RISK-F-0003-ops-warden-read-boundary-ungraded-lanes.md index 75b1e82..c558d5b 100644 --- a/findings/RISK-F-0003-ops-warden-read-boundary-ungraded-lanes.md +++ b/findings/RISK-F-0003-ops-warden-read-boundary-ungraded-lanes.md @@ -24,13 +24,12 @@ embargo_condition: "RISK-F-0009 resolved — the OpenBao deny set covers every h embargo_since: "2026-08-19" embargo_review: "2026-09-18" escalation: none -last_checked: "2026-08-21T06:32:10Z" -next_check: "2026-08-21T14:32:10Z" -cadence: 8h -clean_streak: 2 +last_checked: "2026-08-20T10:02:42Z" +next_check: "2026-08-20T11:02:42Z" +cadence: 1h +clean_streak: 1 graded_by: risk-nexus ruling: RISK-RULING-2026-08-19 -checked_by: "worsch" --- # RISK-F-0003 — the agent read-boundary has a fourteen-lane blind spot @@ -227,4 +226,3 @@ finding's own fix has landed. `ops-warden`'s report and this register has not probed it. Their operator token is expired, which is also why `RISK-F-0009` rests on a file comparison. - **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-21** — clean check: fix state read from the owner's file: WARDEN-WP-0032-T05 done; nothing else moved. Cadence 1h → 8h (2 clean in a row); next check 2026-08-21 14:32Z. diff --git a/findings/RISK-F-0005-audit-core-unfiltered-read-path.md b/findings/RISK-F-0005-audit-core-unfiltered-read-path.md index 39dd6cc..f9d5079 100644 --- a/findings/RISK-F-0005-audit-core-unfiltered-read-path.md +++ b/findings/RISK-F-0005-audit-core-unfiltered-read-path.md @@ -22,9 +22,9 @@ fidelity_modifier: false production_rescore: true disclosure: public publication: pending-handover -embargo_lifted: "2026-08-18 — AUDIT-WP-0008-T04 reads done in audit-core's workplan" -embargo_was_since: "2026-08-19" - +embargo_condition: "met 2026-08-18 — AUDIT-WP-0008-T04 reads done in audit-core's workplan" +embargo_since: "2026-08-19" +embargo_review: "2026-11-17" escalation: none last_checked: "2026-08-21T06:29:38Z" next_check: "2026-08-21T06:29:38Z" diff --git a/notes/RISK-N-0003-defects-found-by-reading-not-monitoring.md b/notes/RISK-N-0003-defects-found-by-reading-not-monitoring.md index df90dcd..8e46fa8 100644 --- a/notes/RISK-N-0003-defects-found-by-reading-not-monitoring.md +++ b/notes/RISK-N-0003-defects-found-by-reading-not-monitoring.md @@ -32,23 +32,3 @@ anything nobody thought to look at. It comes back the first time a finding arrives that monitoring plausibly should have caught and did not. That is the evidence this note is missing, and until then filing it would be the register asserting a conclusion it cannot support. - -## Update — 2026-08-21: the denominator - -`RISK-WP-0005-T07` built the smallest possible coverage report, and it puts a -number on what this note could previously only gesture at: - -> **7 of 117 registered repos have ever appeared in a finding. 110 never have.** - -That is not 110 clean repos and this note does not claim it is. It is 110 repos -about which the register knows nothing, and the estate's own evidence — two for -two on tenant boundaries, four defects in one review round — says that looking -tends to find something. - -The note stays a note. There is still no owner for "the estate's ability to -notice its own defects", and nothing here is a defect to route. What changed is -that the gap now has a size, and `make coverage` prints it, which is the -difference between an argument and a measurement. - -It comes back as a finding the first time something is found in one of the 110 -that a reasonable sweep would have caught earlier. diff --git a/tools/__pycache__/fix_tracker.cpython-312.pyc b/tools/__pycache__/fix_tracker.cpython-312.pyc deleted file mode 100644 index b115ce0..0000000 Binary files a/tools/__pycache__/fix_tracker.cpython-312.pyc and /dev/null differ diff --git a/tools/coverage.py b/tools/coverage.py deleted file mode 100644 index 59f0e2f..0000000 --- a/tools/coverage.py +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env python3 -"""What has never been looked at. - -RISK-WP-0005-T07, deliberately minimal. The register knows what was reported. -Without this it has no view of what was never assessed, so a system with zero -findings is indistinguishable from a system nobody has examined — while -RISK-N-0003 records that every repo which *has* examined its own boundary this -month found a defect. - -This does not assess anything and does not grade anyone. It counts. -""" -from __future__ import annotations - -import json -import urllib.error -import urllib.request - -import register_lib as lib - -HUB = "http://127.0.0.1:8000/repos/" - - -def repos() -> list[dict] | None: - for url in (HUB, "http://127.0.0.1:8000/repos"): - try: - with urllib.request.urlopen(url, timeout=8) as r: - data = json.load(r) - return data if isinstance(data, list) else data.get("items", []) - except (urllib.error.URLError, TimeoutError, json.JSONDecodeError, OSError): - continue - return None - - -def main() -> int: - all_repos = repos() - seen: dict[str, list[str]] = {} - for f in lib.findings(): - for key in (f.get("system"), f.get("fix_owner"), f.get("reported_by")): - if key and isinstance(key, str): - seen.setdefault(key, []).append(f["id"]) - - print("Coverage — what the register has heard from\n") - print(f" systems that have produced or carried a finding: {len(seen)}") - for name in sorted(seen): - print(f" {name}: {', '.join(sorted(set(seen[name])))}") - - if all_repos is None: - print("\n Hub unreachable — the denominator is unknown, which is the whole point of this report.") - return 0 - - names = {r.get("slug") for r in all_repos if r.get("slug")} - unheard = sorted(names - set(seen)) - print(f"\n registered repos: {len(names)}") - print(f" never appeared in any finding: {len(unheard)}") - print("\n A repo in that list has either nothing wrong with it or nobody looking.") - print(" This register cannot tell which, and does not guess.\n") - for name in unheard[:40]: - print(f" {name}") - if len(unheard) > 40: - print(f" … and {len(unheard) - 40} more") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/tools/record_check.py b/tools/record_check.py index b138b51..c4edc54 100644 --- a/tools/record_check.py +++ b/tools/record_check.py @@ -13,7 +13,6 @@ written down, which is the same rule the register applies to everyone else. from __future__ import annotations import datetime as dt -import os import re import sys @@ -63,7 +62,6 @@ def main(argv: list[str]) -> int: fail("outcome must be one of: clean, moved, defer") subs = { - "checked_by": os.environ.get("RISK_CHECKED_BY", os.environ.get("USER", "unknown")), "last_checked": stamp, "next_check": nxt.strftime("%Y-%m-%dT%H:%M:%SZ"), "cadence": new_rung, diff --git a/tools/register_check.py b/tools/register_check.py index 115b215..1e5ee03 100644 --- a/tools/register_check.py +++ b/tools/register_check.py @@ -66,7 +66,7 @@ def main() -> int: for ref in (f.get("related") or []): if ref not in ids: malformed.append(f"{fid} — related names {ref}, which does not exist") - if f.get("embargo_condition") and f.get("disclosure") != "embargoed": # a lift is recorded as embargo_lifted + if f.get("embargo_condition") and f.get("disclosure") != "embargoed": malformed.append(f"{fid} — carries an embargo_condition but disclosure is '{f.get('disclosure')}'") if f.get("disclosure") == "embargoed" and not f.get("embargo_condition"): malformed.append(f"{fid} — embargoed with no condition; a hold with no lift is a silence") @@ -159,20 +159,11 @@ def main() -> int: ] section("Embargoed", embargo, "none") - # T05 — an escalation nobody acknowledged is indistinguishable from one - # never sent, which is the failure this register fixed for its own inbox - # and not, until now, for the path that matters more. - esc = [] - for f in fs: - if f.get("escalation") != "required" or f.get("escalation_status") == "answered": - continue - sent = lib.moment(f.get("escalation_sent")) - age = f", sent {(NOW - sent).days}d ago" if sent else ", never marked sent" - state = f.get("escalation_status", "unknown") - tail = " ← unacknowledged; raise once more, then record the default" if ( - sent and (NOW - sent).days >= 7 and state not in ("seen", "answered") - ) else "" - esc.append(f"{f['id']} — trigger {f.get('escalation_trigger')}, {state}{age}{tail}") + esc = [ + f"{f['id']} — trigger {f.get('escalation_trigger')}, {f.get('escalation_status')}" + for f in fs + if f.get("escalation") == "required" and f.get("escalation_status") != "answered" + ] section("Escalations awaiting the operator", esc, "none") rescore = [ @@ -191,17 +182,6 @@ def main() -> int: reg.append(f"{r.get('id')} — due {when:%Y-%m-%d}: {str(r.get('title','')).strip()}") section("Regulatory records due", reg, "none") - # T06 — a 1q rung means "stable for a quarter" and "nobody looked for a - # quarter", and those read identically. The heartbeat separates them. - checks = [m for f in lib.findings() if (m := lib.moment(f.get("last_checked")))] - if checks: - newest = max(checks) - quiet = (NOW - newest).days - if quiet >= 2: - lines.insert(0, "") - lines.insert(0, f" Nothing anywhere in the register has been checked for {quiet} days.") - lines.insert(0, "HEARTBEAT — THE LADDER IS NOT BEING CLIMBED:") - print(f"Register check — {NOW:%Y-%m-%d %H:%MZ}\n{len(fs)} live finding(s)\n") print("\n".join(lines).rstrip()) return 0 diff --git a/workplans/RISK-WP-0005-close-the-intent-gaps.md b/workplans/RISK-WP-0005-close-the-intent-gaps.md index 7e344e0..d025340 100644 --- a/workplans/RISK-WP-0005-close-the-intent-gaps.md +++ b/workplans/RISK-WP-0005-close-the-intent-gaps.md @@ -4,7 +4,7 @@ type: workplan title: "Close the gaps between what INTENT claims and what the register can actually do" domain: infotech repo: risk-nexus -status: finished +status: active owner: the-custodian topic_slug: risk-nexus created: "2026-08-21" @@ -45,7 +45,7 @@ plainly at the top: ```task id: RISK-WP-0005-T01 -status: done +status: todo priority: high ``` @@ -69,17 +69,11 @@ whether or not the register has been checking. Where `fix_tracking` is `unset` (`RISK-F-0004`, `RISK-F-0006`, `RISK-F-0009`), that absence is itself the report. -Completed 2026-08-21. `tools/fix_tracker.py`, behind `make fixes` and inside `make check`. Resolves `fix_tracking` against the owning repo's workplan **file** — task-level ids do not exist in the hub, and the file is the ADR-001 source of truth anyway — 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-and-filed fix does not read as missing. - -**The first run found two things the register should have known.** `RISK-F-0005`: `AUDIT-WP-0008-T04` had read `done` since 2026-08-18 — the fix landed and this repo spent three days not knowing. Now `mitigated`, embargo lifted, `public`. `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 naming `FLEX-WP-0007` as its blocker. Routed as a question to both owners rather than a conclusion. - -Four findings carry no fix tracking at all, which the report now says out loud instead of leaving an empty field. - ### T02 — An intake path for incidents and external reports ```task id: RISK-WP-0005-T02 -status: done +status: todo priority: high ``` @@ -103,15 +97,11 @@ Three pieces, and only the first is this repo's alone: **Non-goal:** a bug bounty, a disclosure programme, or any commitment to outside parties. The ask is a route, not a promise. -Completed 2026-08-21. `docs/method/intake.md`. Incident intake: file first and grade within the hour, `first_observed` because obligations run from it rather than from the grade, `instant` cadence until it is over, and **escalation immediately rather than batched** — the batching rule protects the operator's attention and a 72-hour notification clock outranks that. `RISK-POL-0005` is wired to `first_observed`, which is what starts the clock. Severity gained a live-incident section: `L4` is what that band was always for. - -External report: **no address exists anywhere in the estate** and creating one is not this repo's call. Routed to `the-custodian` and `policy-nexus` with a concrete proposal — RFC 9116 `security.txt` on the published surface, reports routed here, `reported_by: external` — and an explicit non-goal: a route in, not a bounty, a timeline or safe harbour. - ### T03 — Define the production transition ```task id: RISK-WP-0005-T03 -status: done +status: todo priority: high ``` @@ -128,15 +118,11 @@ tell the repos that carry re-score obligations. Custodian decision on the definition; the plumbing is ours. -Completed 2026-08-21 as a proposal; the definition is the custodian's. `docs/method/production-transition.md` defines it by **what is held rather than what was announced**: the first moment any system holds, processes or decides about real external data. Which means it can happen by accident — one real signup, one migrated contact list — and cannot be reversed by deleting the data afterwards. - -Declared by the custodian, never by this register: a risk register that unilaterally declares the estate to be in production has appointed itself. What the register does is notice and ask. Five re-scores, two acceptances ending and six dormant policies activating are listed as what fires on the day. - ### T04 — Stop claiming a surface we do not have ```task id: RISK-WP-0005-T04 -status: done +status: todo priority: medium ``` @@ -151,13 +137,11 @@ the wording rather than editing it. Small, and exactly the class of claim this register grades other repos down for: a stated control that is not there. -Completed 2026-08-21. `README.md` now says the repo serves nothing yet and that publication runs through `policy-nexus`, with three documents waiting for an address. `INTENT.md` is the constitutional document and its amendment is the custodian's — the wording is proposed to them rather than edited here. - ### T05 — Give escalation a delivery guarantee ```task id: RISK-WP-0005-T05 -status: done +status: todo priority: medium ``` @@ -172,13 +156,11 @@ a re-raise once, per the adopted rule's "raised again once" clause. Not a weekly nag; the rule is explicit that repetition until someone answers is how the operator becomes the queue. -Completed 2026-08-21. Escalation carries a delivery state — `sent → seen → answered` — with `escalation_sent` beside it, and `make check` reports how long each has gone unacknowledged. At seven days it says so and the escalation is raised **once** more, per the adopted rule, after which the default applies and is recorded. This is the fix the register applied to its own inbox on 2026-08-19 and had not applied to the path that matters more. - ### T06 — Make a lie about stability impossible to miss ```task id: RISK-WP-0005-T06 -status: done +status: todo priority: medium ``` @@ -195,13 +177,11 @@ Two cheap defences: register for longer than the shortest rung by some margin, `make check` says so first, before anything else. -Completed 2026-08-21. Two defences against the rung lying: `checked_by` recorded on every check (`RISK_CHECKED_BY`, so an agent names itself rather than inheriting a unix login), and a **heartbeat** that prints before anything else in `make check` when nothing anywhere in the register has been checked for two days. A `1q` rung means "stable for a quarter" and "nobody looked for a quarter", and the heartbeat is what separates them. - ### T07 — A coverage model ```task id: RISK-WP-0005-T07 -status: done +status: todo priority: low ``` @@ -220,10 +200,6 @@ a maturity model, and not this repo assessing anyone. **If this task grows past a page it becomes its own workplan.** Coverage is a different problem from triage and should not quietly absorb this one. -Completed 2026-08-21, minimal and within its escape clause. `tools/coverage.py`, behind `make coverage`, counts what the register has heard from. First run: **7 of 117 registered repos have ever appeared in a finding; 110 never have.** - -That is not 110 clean repos and the report says so — it is 110 repos the register knows nothing about, against an estate whose own evidence is that looking tends to find something. Recorded as an update to `RISK-N-0003` rather than promoted: there is still no owner for estate-wide detection and still no defect to route. What changed is that the gap has a size, which is the difference between an argument and a measurement. - ## Non-goals - **No monitoring.** `RISK-N-0003` stands as a note. A register that grows