Compare commits
2 commits
c73848250c
...
a798a4c771
| Author | SHA1 | Date | |
|---|---|---|---|
| a798a4c771 | |||
| a1961a8f6f |
6 changed files with 510 additions and 1 deletions
7
Makefile
7
Makefile
|
|
@ -1,11 +1,12 @@
|
|||
PY := python3
|
||||
TOOLS := tools
|
||||
|
||||
.PHONY: help register check
|
||||
.PHONY: help register check checked
|
||||
|
||||
help:
|
||||
@echo "make register - rebuild REGISTER.md from findings/"
|
||||
@echo "make check - verify the index is current, then report what is going quiet"
|
||||
@echo "make checked - record a check outcome: make checked ARGS=\"RISK-F-0002 clean\""
|
||||
|
||||
register:
|
||||
@$(PY) $(TOOLS)/register_index.py
|
||||
|
|
@ -14,3 +15,7 @@ check:
|
|||
@$(PY) $(TOOLS)/register_index.py --check
|
||||
@echo
|
||||
@$(PY) $(TOOLS)/register_check.py
|
||||
|
||||
checked:
|
||||
@$(PY) $(TOOLS)/record_check.py $(ARGS)
|
||||
@$(PY) $(TOOLS)/register_index.py
|
||||
|
|
|
|||
90
tools/record_check.py
Normal file
90
tools/record_check.py
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Record the outcome of a check and move the finding along the ladder.
|
||||
|
||||
python3 tools/record_check.py RISK-F-0002 clean
|
||||
python3 tools/record_check.py RISK-F-0002 moved "flex-auth shipped the fix"
|
||||
python3 tools/record_check.py RISK-F-0002 defer 2026-09-01 "operator: after the migration"
|
||||
|
||||
`clean` climbs one rung, `moved` resets to `instant`, `defer` parks it until a
|
||||
date by explicit operator decision. Writes the front-matter and appends a dated
|
||||
line to the finding's Reviews section — the check is not recorded until it is
|
||||
written down, which is the same rule the register applies to everyone else.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime as dt
|
||||
import re
|
||||
import sys
|
||||
|
||||
import register_lib as lib
|
||||
|
||||
|
||||
def fail(msg: str) -> None:
|
||||
print(msg)
|
||||
raise SystemExit(2)
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
if len(argv) < 2:
|
||||
fail(__doc__)
|
||||
fid, outcome, rest = argv[0], argv[1], argv[2:]
|
||||
matches = [f for f in lib.findings() if f["id"] == fid]
|
||||
if not matches:
|
||||
fail(f"no finding with id {fid}")
|
||||
f = matches[0]
|
||||
path = f["_path"]
|
||||
text = path.read_text(encoding="utf-8")
|
||||
now = lib.now()
|
||||
stamp = now.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
rung = f.get("cadence", "instant")
|
||||
|
||||
if outcome == "clean":
|
||||
new_rung = lib.climb(rung)
|
||||
streak = int(f.get("clean_streak", 0)) + 1
|
||||
nxt = now + lib.interval(new_rung)
|
||||
note = rest[0] if rest else "checked, nothing moved"
|
||||
line = f"- **{now:%Y-%m-%d}** — clean check: {note}. Cadence {rung} → {new_rung} ({streak} clean in a row); next check {nxt:%Y-%m-%d %H:%MZ}."
|
||||
defer = ""
|
||||
elif outcome == "moved":
|
||||
if not rest:
|
||||
fail("`moved` needs a reason: what changed")
|
||||
new_rung, streak, nxt = lib.reset(), 0, now
|
||||
line = f"- **{now:%Y-%m-%d}** — not clean: {rest[0]} Cadence {rung} → instant; checked again immediately."
|
||||
defer = ""
|
||||
elif outcome == "defer":
|
||||
if len(rest) < 2:
|
||||
fail("`defer` needs a date and the operator's reason")
|
||||
until, why = rest[0], rest[1]
|
||||
new_rung, streak, nxt = rung, int(f.get("clean_streak", 0)), lib.moment(until)
|
||||
line = f"- **{now:%Y-%m-%d}** — deferred to {until} by explicit operator decision: {why}"
|
||||
defer = until
|
||||
else:
|
||||
fail("outcome must be one of: clean, moved, defer")
|
||||
|
||||
subs = {
|
||||
"last_checked": stamp,
|
||||
"next_check": nxt.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
||||
"cadence": new_rung,
|
||||
"clean_streak": str(streak),
|
||||
}
|
||||
if defer:
|
||||
subs["deferred_to"] = defer
|
||||
for key, value in subs.items():
|
||||
quoted = f'"{value}"' if key not in ("cadence", "clean_streak") else value
|
||||
if re.search(rf"(?m)^{key}:", text):
|
||||
text = re.sub(rf"(?m)^{key}:.*$", f"{key}: {quoted}", text)
|
||||
else:
|
||||
text = text.replace("\n---\n", f"\n{key}: {quoted}\n---\n", 1)
|
||||
|
||||
if "\n## Reviews\n" in text:
|
||||
text = text.rstrip("\n") + "\n" + line + "\n"
|
||||
else:
|
||||
text = text.rstrip("\n") + "\n\n## Reviews\n\n" + line + "\n"
|
||||
path.write_text(text, encoding="utf-8")
|
||||
print(f"{fid}: {outcome} — cadence {rung} → {new_rung}, next check {subs['next_check']}")
|
||||
print("REGISTER.md is stale; run `make register`.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv[1:]))
|
||||
|
|
@ -26,6 +26,16 @@ def main() -> int:
|
|||
lines.append(f" {quiet}")
|
||||
lines.append("")
|
||||
|
||||
seen: dict[str, str] = {}
|
||||
dupes = []
|
||||
for f in lib.findings():
|
||||
prior = seen.get(f["id"])
|
||||
if prior:
|
||||
dupes.append(f"{f['id']} — filed twice: {prior} and {f['_path'].name}; renumber the later commit")
|
||||
seen[f["id"]] = f["_path"].name
|
||||
if dupes:
|
||||
section("Duplicate ids", dupes, "none")
|
||||
|
||||
unknown = [
|
||||
f"{f['id']} — status '{f.get('status')}' is not one of {', '.join(lib.KNOWN_STATUSES)}; watched anyway"
|
||||
for f in fs
|
||||
|
|
|
|||
110
workplans/RISK-WP-0002-publication-handover.md
Normal file
110
workplans/RISK-WP-0002-publication-handover.md
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
---
|
||||
id: RISK-WP-0002
|
||||
type: workplan
|
||||
title: "Hand the publishable findings to policy-nexus, and decide what else is a public document"
|
||||
domain: infotech
|
||||
repo: risk-nexus
|
||||
status: proposed
|
||||
owner: the-custodian
|
||||
topic_slug: risk-nexus
|
||||
created: "2026-08-20"
|
||||
updated: "2026-08-20"
|
||||
depends_on_workplans:
|
||||
- RISK-WP-0001
|
||||
---
|
||||
|
||||
# RISK-WP-0002 — publication handover
|
||||
|
||||
**Draft.** Sized deliberately small: two documents are ready and the rest is a
|
||||
decision, not a project.
|
||||
|
||||
## Goal
|
||||
|
||||
`RISK-F-0001` and `RISK-F-0008` carry `disclosure: public` and
|
||||
`publication: pending-handover`. Get them onto `policy.coulomb.social` under
|
||||
`policy-nexus`'s existing contract, and settle whether this repo's method
|
||||
documents are public too.
|
||||
|
||||
Done means: both findings have a permanent address, `publication: published`,
|
||||
and a recorded answer on the method documents.
|
||||
|
||||
## Why now
|
||||
|
||||
Six findings are embargoed with lift conditions, and `RISK-F-0009` has already
|
||||
demonstrated that a condition can be met and the embargo still hold. When those
|
||||
conditions start clearing, publication will happen in a trickle rather than a
|
||||
batch — so the route wants to exist before it is needed, not during.
|
||||
|
||||
`policy-nexus` has been told this is coming (2026-08-20) and asked for nothing.
|
||||
|
||||
## Tasks
|
||||
|
||||
### T01 — Publish the two ready findings
|
||||
|
||||
```task
|
||||
id: RISK-WP-0002-T01
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
Follow `policy-nexus`'s publication contract as it stands. Do not invent an
|
||||
address scheme: `POLICY-NEXUS-WP-0001` settled addressing and permanence, and
|
||||
this repo is a consumer of that decision.
|
||||
|
||||
Open question for T01 rather than an assumption: **is a finding published whole,
|
||||
or as a summary?** `RISK-F-0001` contains a full ruling, a re-grade, a review
|
||||
log and this register's own process defect. Some of that is register-internal
|
||||
work product. Decide once, here, and apply it to every later publication.
|
||||
|
||||
### T02 — Rule on the method documents
|
||||
|
||||
```task
|
||||
id: RISK-WP-0002-T02
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
`docs/method/severity.md`, `disclosure.md`, `escalation.md`, `review.md`.
|
||||
|
||||
The case for publishing: they say how the estate grades and holds risk, which
|
||||
is exactly what an outside reader needs to judge whether a published finding
|
||||
means anything.
|
||||
|
||||
The case against: the escalation rule names the operator's own thresholds, and
|
||||
the severity scale is a judgement instrument this repo revises freely. A
|
||||
published instrument invites argument about the instrument.
|
||||
|
||||
Suggested split, to be ruled on rather than assumed: severity and disclosure
|
||||
public, escalation and review internal. Escalation in particular describes when
|
||||
the operator is interrupted, which is not the estate's business to advertise.
|
||||
|
||||
### T03 — The standing route
|
||||
|
||||
```task
|
||||
id: RISK-WP-0002-T03
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
Write down what happens when an embargo lifts: who hands over, in what shape,
|
||||
and how `publication: published` gets recorded back on the finding.
|
||||
|
||||
Small. It is a paragraph in `docs/method/disclosure.md` plus whatever
|
||||
`policy-nexus` needs on their side, not a mechanism.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- No publication surface here. `policy-nexus` hosts; this repo hands over.
|
||||
- No timed release, no coordinated disclosure, no notification tiers. Those stay
|
||||
deferred (`docs/method/disclosure.md`) until there are real users.
|
||||
- No re-grading of anything to make it publishable.
|
||||
|
||||
## Risks
|
||||
|
||||
**A finding is published with an internal ruling attached.** Mitigation: T01
|
||||
decides whole-versus-summary before anything ships.
|
||||
|
||||
**The handover becomes a project.** Mitigation: three tasks, one of which is a
|
||||
paragraph. If it grows, that is a signal the publication contract does not fit
|
||||
findings, and that is a conversation with `policy-nexus` rather than more tasks
|
||||
here.
|
||||
125
workplans/RISK-WP-0003-regulatory-intake.md
Normal file
125
workplans/RISK-WP-0003-regulatory-intake.md
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
---
|
||||
id: RISK-WP-0003
|
||||
type: workplan
|
||||
title: "Make regulatory intake a working remit rather than one record"
|
||||
domain: infotech
|
||||
repo: risk-nexus
|
||||
status: proposed
|
||||
owner: the-custodian
|
||||
topic_slug: risk-nexus
|
||||
created: "2026-08-20"
|
||||
updated: "2026-08-20"
|
||||
depends_on_workplans:
|
||||
- RISK-WP-0001
|
||||
---
|
||||
|
||||
# RISK-WP-0003 — regulatory intake
|
||||
|
||||
**Draft.** The half of this repo's remit that `RISK-WP-0001` deliberately did
|
||||
not touch.
|
||||
|
||||
## Goal
|
||||
|
||||
`INTENT.md` says regulation was previously "consulted and discarded" — the same
|
||||
question asked twice and the answer silently expiring. `RISK-REG-0001` is one
|
||||
record against that. Make it a remit: a format that expires, a way for repos to
|
||||
ask, and the open items that record is carrying.
|
||||
|
||||
Done means: the retention question is answered as far as it can be without
|
||||
buying advice, the trigger list for buying advice is ruled, and a repo with a
|
||||
regulatory question knows where to put it.
|
||||
|
||||
## The open items this inherits
|
||||
|
||||
From `RISK-REG-0001` and `RISK-F-0008`, both already written down:
|
||||
|
||||
1. **A defensible retention period per category.** The determination names this
|
||||
as the weakest point in the estate's whole position: supervisory practice
|
||||
accepts audit logging under legitimate interest and then asks how long, and
|
||||
"we keep audit because it is audit" is the form that fails.
|
||||
2. **`audit-core`'s co-residency horizon.** At `P1` the real erasure horizon is
|
||||
the maximum across every co-resident on `platform-pg`, not the declared
|
||||
value. An infrastructure fact is doing load-bearing work in a legal position,
|
||||
which is an uncomfortable place for it to be. Blocked on `audit-core`.
|
||||
3. **The trigger list.** First real person's data, first counterparty contract
|
||||
requiring a stated position, first Art 17 request. Proposed 2026-08-19, not
|
||||
ruled.
|
||||
|
||||
## Tasks
|
||||
|
||||
### T01 — Rule the trigger list
|
||||
|
||||
```task
|
||||
id: RISK-WP-0003-T01
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
Custodian decision. Cheap, and it is what stops the estate either buying advice
|
||||
it does not need or discovering it needed it. Until it is ruled, `RISK-F-0008`
|
||||
stays escalated as `partially-answered`.
|
||||
|
||||
### T02 — Retention periods per category
|
||||
|
||||
```task
|
||||
id: RISK-WP-0003-T02
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
State a period and a reason per category in `RISK-REG-0001`, or state plainly
|
||||
that the estate cannot yet and why. The second is an acceptable outcome and a
|
||||
better record than a number nobody can defend.
|
||||
|
||||
Depends on `audit-core` answering the co-residency horizon, which has been
|
||||
asked for. If they cannot, that dependency is itself the answer to record.
|
||||
|
||||
### T03 — Intake route for regulatory questions
|
||||
|
||||
```task
|
||||
id: RISK-WP-0003-T03
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
`audit-core` routed theirs by messaging this repo and asking for an owner,
|
||||
which worked. Write that down as the route rather than leaving it as one repo's
|
||||
good instinct: what a regulatory question needs when it arrives, what it gets
|
||||
back, and what this repo will not answer (legal advice, and what the owning
|
||||
repo must therefore do).
|
||||
|
||||
Extend `findings/README.md` or give `docs/regulatory/README.md` the reporter's
|
||||
half. Do not invent an intake system.
|
||||
|
||||
### T04 — Expiry
|
||||
|
||||
```task
|
||||
id: RISK-WP-0003-T04
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
Regulatory records expire; that is why the remit moved here. Put them on the
|
||||
same cadence ladder as findings (`docs/method/review.md`) rather than inventing
|
||||
a second review mechanism — a record that has held still for a quarter is
|
||||
making the same statement a finding at `1q` makes.
|
||||
|
||||
`make check` should report a regulatory record due for a check exactly as it
|
||||
reports a finding.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- **No legal advice.** `INTENT.md`, and the records say so in their own text.
|
||||
- **No survey of every regime that might apply.** Regulation is scoped to rules
|
||||
bearing on data the estate holds, markets it sells into, or obligations it has
|
||||
taken on. A general compliance programme is not this.
|
||||
- **No answering what a repo must therefore do.** That is the owning repo's.
|
||||
|
||||
## Risks
|
||||
|
||||
**The remit becomes a compliance function.** Mitigation: records answer
|
||||
questions that were actually asked, by a repo, with a date.
|
||||
|
||||
**A record states a legal conclusion with false confidence.** Mitigation: every
|
||||
record names where it is weak, and `external_review: none` is a required field
|
||||
rather than an omission.
|
||||
169
workplans/RISK-WP-0004-run-the-register.md
Normal file
169
workplans/RISK-WP-0004-run-the-register.md
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
---
|
||||
id: RISK-WP-0004
|
||||
type: workplan
|
||||
title: "Run the register: make the cadence actually happen, and close the gaps the first week found"
|
||||
domain: infotech
|
||||
repo: risk-nexus
|
||||
status: proposed
|
||||
owner: the-custodian
|
||||
topic_slug: risk-nexus
|
||||
created: "2026-08-20"
|
||||
updated: "2026-08-20"
|
||||
depends_on_workplans:
|
||||
- RISK-WP-0001
|
||||
---
|
||||
|
||||
# RISK-WP-0004 — run the register
|
||||
|
||||
**Draft.** `RISK-WP-0001` built the instruments. This one is about the register
|
||||
being *operated*, which is a different thing and the one that fails quietly.
|
||||
|
||||
## Goal
|
||||
|
||||
Nine findings, two notes, a regulatory record and an adaptive cadence exist.
|
||||
**Nothing currently performs a check.** `make check` says what is due; a person
|
||||
or an agent has to read the finding, ask the five questions, and record the
|
||||
outcome. If that does not happen, every finding sits at `instant` forever and
|
||||
the ladder becomes decoration.
|
||||
|
||||
Done means: checks happen on their own schedule without the operator
|
||||
remembering, the register's own known gaps are closed, and the failure modes
|
||||
found in the first week cannot recur silently.
|
||||
|
||||
## What the first week actually found
|
||||
|
||||
Every task below traces to something that happened, not something imagined.
|
||||
|
||||
| What happened | Task |
|
||||
| --- | --- |
|
||||
| Graded `RISK-F-0001` `critical` while its fix notice sat unread in the inbox | T02 |
|
||||
| `RISK-F-0003` returned as `mitigated`, a word the tooling did not know, and vanished from the nag | fixed ad hoc; T03 generalises it |
|
||||
| `ops-warden` and this register both used `RISK-F-0004` | fixed ad hoc; duplicate check now in `make check` |
|
||||
| Two gradings rest on file comparison because an operator token is expired | T05 |
|
||||
| `RISK-F-0007` accepted with an on-request path nobody has exercised | T04 |
|
||||
|
||||
## Tasks
|
||||
|
||||
### T01 — Something performs the checks
|
||||
|
||||
```task
|
||||
id: RISK-WP-0004-T01
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
`tools/record_check.py` moves a finding along the ladder and writes the dated
|
||||
line. What is missing is the thing that *calls* it on schedule.
|
||||
|
||||
Options, in ascending order of how much this repo should want them: a scheduled
|
||||
agent session that runs `make check`, works the due list, and records outcomes;
|
||||
a cron that only reports; a human habit. The first is the only one that
|
||||
survives the operator being busy, which is the condition the whole ladder is
|
||||
designed for.
|
||||
|
||||
Whatever it is, the check must remain a **judgement** — re-read the grade,
|
||||
re-check every stated blocker, read the owner's tracking record. A job that
|
||||
stamps `clean` without doing that is worse than no job, because it manufactures
|
||||
a stability signal that is false. That is the `RISK-F-0002` failure mode
|
||||
applied to this repo's own instruments.
|
||||
|
||||
### T02 — Inbox before grading, as a check rather than a habit
|
||||
|
||||
```task
|
||||
id: RISK-WP-0004-T02
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
`RISK-WP-0001`'s residual says: if it slips again, make it a check. It has
|
||||
slipped once, on the register's first day, and cost a wrong grade and a nearly
|
||||
sent escalation.
|
||||
|
||||
`make check` should compare each finding's `last_checked` against the newest
|
||||
message this repo has received about that system, and report any finding whose
|
||||
inbox has spoken more recently than its register has. That is mechanical and
|
||||
does not require the tooling to understand the message.
|
||||
|
||||
### T03 — Fail loud everywhere else too
|
||||
|
||||
```task
|
||||
id: RISK-WP-0004-T03
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
The `mitigated` defect was one instance of a class: the tooling quietly
|
||||
tolerating something it did not expect. Sweep for the rest — unknown disclosure
|
||||
states, unparseable dates, a `constraint_on` pointing at a finding that does
|
||||
not exist, an `embargo_condition` on a finding that is not embargoed.
|
||||
|
||||
Every one of them should be reported by name and keep the finding watched.
|
||||
|
||||
### T04 — Exercise the on-request verification path once
|
||||
|
||||
```task
|
||||
id: RISK-WP-0004-T04
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
`RISK-F-0007` is accepted until production on the strength of a path nobody has
|
||||
used. A route that has never been walked is a plan, not a route.
|
||||
|
||||
Pick one named consumer boundary, request verification through the documented
|
||||
path, and see what actually happens: who answers, what evidence comes back,
|
||||
whether the finding's likelihood moves for that consumer, and what the record
|
||||
looks like. The value is in the friction it exposes.
|
||||
|
||||
### T05 — The verification credential
|
||||
|
||||
```task
|
||||
id: RISK-WP-0004-T05
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
`RISK-F-0009` and `RISK-F-0003`'s mitigation both rest on comparing files
|
||||
because `ops-warden`'s operator token is expired and `bao policy read` cannot
|
||||
be run.
|
||||
|
||||
That is not a finding — nothing is wrong with the estate because a token
|
||||
expired — but it means the register is grading deployed controls from their
|
||||
source. Establish what this repo can legitimately verify itself, and what it
|
||||
must always take from owners. **If the answer is "nothing", that is worth
|
||||
knowing and writing down**, because it bounds every grade in the register.
|
||||
|
||||
### T06 — Regulatory records on the same ladder
|
||||
|
||||
```task
|
||||
id: RISK-WP-0004-T06
|
||||
status: todo
|
||||
priority: low
|
||||
```
|
||||
|
||||
Duplicate of `RISK-WP-0003-T04`, kept here as a pointer rather than a second
|
||||
copy. Whichever workplan reaches it first does it; the other closes with a
|
||||
reference.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- **No dashboard.** `REGISTER.md` and `make check` are the surface, and the
|
||||
register is supposed to stay small enough to read.
|
||||
- **No automated grading.** A machine may schedule, list and record. Severity,
|
||||
disclosure and escalation stay judgements this repo makes.
|
||||
- **No monitoring of other systems.** `RISK-N-0003` records that everything
|
||||
here was found by reading rather than watching; this workplan does not
|
||||
attempt to change that, and a register that grows probes becomes a second
|
||||
engineering team.
|
||||
|
||||
## Risks
|
||||
|
||||
**A scheduled check becomes a rubber stamp.** The whole value of the ladder is
|
||||
that a `1q` rung means something. Mitigation: T01 treats stamping without
|
||||
judgement as a defect, and `clean_streak` makes a suspiciously smooth climb
|
||||
visible.
|
||||
|
||||
**The register spends its attention on itself.** Six tasks about the register's
|
||||
own machinery, none of which fix a defect in the estate. Mitigation: T01, T02
|
||||
and T05 all exist because the register got something wrong in week one; if
|
||||
week two produces no such items, this workplan should shrink rather than grow.
|
||||
Loading…
Add table
Add a link
Reference in a new issue