Skip to content
60 changes: 60 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,66 @@

(none)

## 0.4.0 — 2026-09-30

- Certification gate: `--certify LEDGER` judges a review ledger against the findings the
report derives from the input, instead of printing the report. Exit 0 — every finding
of the five ledger kinds (candidates, gaps, boundary findings, unstated pairs, uncited
spans) dispositioned, a certification record printed; exit 3 — refused, every blocker
named in the record. Feedback loops, self-dependencies and the class-rules audit stay
human-review findings the gate does not disposition. The ledger format and the gate's
rules are SKILL.md §2–§3.
- The review ledger is identity-keyed, not line-keyed: one table
(`Kind | Finding | Disposition | Reason | Reviewer | Date | Fingerprint`), one row per
finding — candidates and gaps as `producer -> consumer: flows` (a blank Flows cell is
the placeholder `?`, which pastes back as the empty identity), unstated pairs
`A -> B`, boundary findings by component name, uncited spans `L7-9@<source sha256>` —
each pinning the content it dispositioned by fingerprint, so an unrelated edit does not
re-open a row. The label is emitted by one helper beside the one parser, so the paste
contract cannot fork.
- A completed certification run (pass or refusal) writes a record beside the ledger
(`<ledger>.cert.md`) binding the input, the report and (when `--source` ran) the
source file by sha256, plus the effective flags; an error (exit 1) writes nothing and
leaves the previous record in place. A passing run also stamps it into the ledger as
its `## Certification record` section — the last passing run and the replay anchor:
every later run re-derives the input and source sha256 it binds and refuses on
mismatch (`drifted: input changed since the last certified run`), so a post-review
edit of the input re-opens the whole review; a later run whose flags do not replay the
recorded ones exits 1 naming the flag.
- Drift detection: a ledger entry whose finding is gone from the input, or changed since
disposition, is a `drifted:` blocker. Dispositions are free text: a candidate or
boundary finding dispositioned rather than `resolved`, and a gap parked `open`, each
stay listed as an advisory in the certification record — what ships stays visible.
- An input that cites a source cannot be certified without `--source`: the first run is
the only window in which span review could be skipped, so the gate refuses it (exit 1)
rather than let a pass pin the hole into the record's flags.
- Under `--certify`, two active input rows sharing one `producer -> consumer: flows`
identity exit 1, as does a component name containing ` -> ` or `: ` (no Finding cell
can express it) and a disposition row placed inside the ledger's certification-record
section — content the record-section scan must not swallow.
- SKILL.md workflow rewritten: review is writing the ledger (the first refusal record is
the worksheet), the finished deliverable is four files shipped together — report,
certification record, input, ledger — five under `--source` (the source file), with
the record's invocation-relative paths replayed verbatim; review runs under separation
of duties (the reviewer of record is someone other than whatever drafted the input),
and a gap not filled now is parked `open` and carried as an advisory, never a blocker.
- Optional, experimental model pins: `decision`, `thinker`, `reviewer` and `judge` keys
under `metadata:` pin a role to a model. They are instructions to the executing agent,
not configuration — the script reads no pins, only its flags.
- README and RUNBOOK document the certification flow: the exit codes (3 added; exit 2's
sharing with argparse usage errors was already true and is now written down), the
certify procedure, and the drift, anchor, citing-input and flag-mismatch playbooks.
- The worked example is now certified: `examples/example-ledger.md` dispositions every
finding `examples/example.md` produces, and `examples/example-ledger.cert.md` is the
record its passing `--certify` run wrote from the repository-root invocation the
ledger documents. The example input no longer cites `S:L42`/`S:L44` — a citing input
must certify with `--source`, and no source file ships — so its report is unchanged
from 0.3.0 (citations do not print in the report).
- The ledger and record writers pin LF newlines, so a Windows re-certification does not
rewrite the whole ledger as CRLF.
- Standard-library additions: `hashlib` and `json` (fingerprints, record bindings). Still
no dependencies, no install step; the self-check now runs 125 tests.

## 0.3.0 — 2026-09-29

- Windows is a supported platform: the script reconfigures stdout to UTF-8, so the report
Expand Down
46 changes: 42 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ report it produces is committed beside it as

| Producer | Consumer | Flows | Format | Trigger | Owner | Source | Status |
|---|---|---|---|---|---|---|---|
| Ingest | Store | raw event rows | ndjson file | nightly cron | platform | S:L42 | |
| Store | Scorer | event batches | ? | ? | platform | S:L44 | |
| Ingest | Store | raw event rows | ndjson file | nightly cron | platform | | |
| Store | Scorer | event batches | ? | ? | platform | | |
| ? | Analyst | weekly digest | ? | ? | ? | | |
```

Expand Down Expand Up @@ -110,6 +110,11 @@ Three stated rows, and the report names one unowned digest producer, one interfa
its format and trigger, a component nothing feeds, an output nothing consumes, and ten
component pairs nobody has ruled in or out.

The example is also certified: [`examples/example-ledger.md`](examples/example-ledger.md)
is the review ledger that dispositions every finding it produces, and
[`examples/example-ledger.cert.md`](examples/example-ledger.cert.md) is the certification
record the passing `--certify` run wrote.

## Install

| Harness | Command | Notes |
Expand All @@ -126,7 +131,7 @@ component pairs nobody has ruled in or out.
Verify the install from inside the installed folder with [`scripts/test_interface_matrix.py`](skills/interface-matrix/scripts/test_interface_matrix.py):

```bash
python3 scripts/test_interface_matrix.py # Ran 84 tests ... OK
python3 scripts/test_interface_matrix.py # Ran 125 tests ... OK
```

On Windows the interpreter is `py -3` (`py -3 scripts/interface_matrix.py example.md`);
Expand All @@ -136,7 +141,7 @@ every platform.
### Harnesses tested

CI installs the skill with the [`skills` CLI](https://github.com/vercel-labs/skills) on every push
and pull request, once per agent in its own throwaway home, and runs the 84 tests from each
and pull request, once per agent in its own throwaway home, and runs the 125 tests from each
installed copy. Every agent the CLI supports is covered — 79 at the time of writing (`skills`
1.7.0), of which 77 are installed and tested; the list is read from the CLI at run time. Two
agents are excluded with reasons recorded in `.github/scripts/smoke-install.sh`: `eve` and
Expand Down Expand Up @@ -167,6 +172,39 @@ On Windows use `py -3` in place of `python3`.
`--sample N` sets how many unstated pairs are printed (default 20, `0` = all).
`--source FILE` adds the coverage section over the document the inventory was read from.

### Certifying a reviewed matrix

Findings are reviewed into a ledger — one table,
`Kind | Finding | Disposition | Reason | Reviewer | Date | Fingerprint`, one row per
finding, keyed by identity rather than input line. Start it as nothing but the header row
and certify once; every finding comes back an `unreviewed:` blocker carrying its current
fingerprint, so the refusal record doubles as the review worksheet:

```bash
python3 scripts/interface_matrix.py INPUT.md --certify INPUT.ledger.md
```

Disposition each finding in the ledger — the fingerprints to paste are in the record —
and certify again. Exit 0 writes `<ledger>.cert.md` beside the ledger: the certification
record, binding the input, the report and (under `--source`) the source file by sha256,
plus the flags the review ran under, which every later certification must replay exactly.
A citing input must certify with `--source` — the gate refuses it otherwise — and the
record a pass stamps into the ledger anchors the input and source by sha256, so any
post-review edit re-opens the review. The finished deliverable is four files shipped
together: the report, its certification record, the input, and the ledger — five when
the review ran under `--source`, adding the source file. The record's paths are
invocation-relative and must be replayed verbatim. The
ledger format, the review procedure and the optional experimental model pins (a model may
review only when one is explicitly pinned) are in
[`skills/interface-matrix/SKILL.md`](skills/interface-matrix/SKILL.md).

| Exit | Meaning |
|---|---|
| 0 | Report written — or, under `--certify`, certification passed and the record printed. |
| 1 | Bad input row, of the input or of a ledger; a duplicate interface identity; or a `--certify` whose flags do not replay the recorded review. Every error names its line. |
| 2 | The partition invariant tripping while the report renders — and argparse usage errors, which have always shared it and are now documented. |
| 3 | Certification refused: every blocker (a drifted or unreviewed finding) is named in the record. |

## How it works

1. Read the input file: the Components table, the Interfaces table and the optional Rules table; everything else is ignored.
Expand Down
13 changes: 13 additions & 0 deletions examples/example-ledger.cert.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Interface matrix certification

- input: examples/example.md (sha256 52ec2692280ee34a4c87124e7fe7117a1dbecb87b63dbf83562664b1daebd1a2)
- ledger: examples/example-ledger.md
- gate: certified
- report: sha256 1480d1f3872a8d603afa6ceaf17d868d4c225fe7a477dbd250b8312270cf224a
- flags: --sample 20
- blockers: none
- advisories: 4
- candidate ? -> Analyst: weekly digest (input line 23): accepted — the digest is written by the on-call engineer of the week, a person outside the boundary; no component to declare until the reporting pass names the real producer
- gap Store -> Scorer: event batches (input line 22, missing Format, Trigger): open-parked — format and trigger wait on the storage RFP, due before work packages are cut; gap register G-12
- boundary Ingest (nothing feeds it): accepted — Ingest is the system's source: it reads the external event broker, which is outside the boundary
- boundary Scorer (nothing consumes its output): accepted — scored events are read by the Analyst's ad-hoc queries at this stage; the digest interface will name the producer once the reporting pass lands
41 changes: 41 additions & 0 deletions examples/example-ledger.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Example review ledger for interface-matrix

The review ledger for [`example.md`](example.md): one row per finding the report
derives from the input, keyed by identity rather than input line. It was started as
nothing but the header; the first `--certify` run refused with every finding named
`unreviewed:` and its current fingerprint, and the rows below disposition each one —
fingerprints copied from that refusal record, which is the intended move. Run from the
repository root:

python3 skills/interface-matrix/scripts/interface_matrix.py examples/example.md --certify examples/example-ledger.md

| Kind | Finding | Disposition | Reason | Reviewer | Date | Fingerprint |
|---|---|---|---|---|---|---|
| candidate | ? -> Analyst: weekly digest | accepted | the digest is written by the on-call engineer of the week, a person outside the boundary; no component to declare until the reporting pass names the real producer | J. Merrick | 2026-09-30 | 4e139b0f50474d7629fd7d55b8a86689520d171e5726eba1ff659ea72106ea6c |
| gap | Store -> Scorer: event batches | open-parked | format and trigger wait on the storage RFP, due before work packages are cut; gap register G-12 | J. Merrick | 2026-09-30 | efdae3966ddabbe36042c85f63e7235c49cc231accc8a6638ff20640cdd94a56 |
| boundary | Ingest | accepted | Ingest is the system's source: it reads the external event broker, which is outside the boundary | J. Merrick | 2026-09-30 | 7b2acd46b8fe5961f6e95a9f7a8c2a4d2bb2e847c76fd0b2188585051eafc68c |
| boundary | Scorer | accepted | scored events are read by the Analyst's ad-hoc queries at this stage; the digest interface will name the producer once the reporting pass lands | J. Merrick | 2026-09-30 | b07ad9ad99b47f37ce06a812f6e56d7b2c7cb966d3d130ecb7478c99bcd2418b |
| pair | Ingest -> Scorer | none | Scorer reads event batches from Store, never straight from Ingest | J. Merrick | 2026-09-30 | 6e54deada7063a257816a5093992b19828fd166f36db2f46fffc8bfc36129e10 |
| pair | Ingest -> Analyst | none | raw event rows never reach a human; the Analyst reads digests only | J. Merrick | 2026-09-30 | c37b55540d54122e0418eb7b66ce96402ae435efe482050e68c4a50dddcc0fca |
| pair | Store -> Ingest | none | the event store is write-only for Ingest; no read-back | J. Merrick | 2026-09-30 | 12b6a29a7004ff74da31cac8a1f73f48a4cddd4fbb0c720e72e654a98af04b02 |
| pair | Store -> Analyst | none | the Analyst reads the weekly digest, not the store directly | J. Merrick | 2026-09-30 | 949e4b23e7aa2c743995eb22b42f20691bd7919c57c567d0a2a4c6ebf7e32cae |
| pair | Scorer -> Ingest | none | scoring is downstream of ingest; nothing flows back | J. Merrick | 2026-09-30 | 20e66c9839d4f06130b5a4661c48289ffea19dc00c009b22f6235fc64143564c |
| pair | Scorer -> Store | none | scores are consumed by the Analyst's ad-hoc queries; nothing writes back to the store at this stage | J. Merrick | 2026-09-30 | 848530bc07bfa9cde490f6f6aeba4c506f3b026bea67f8c108829edfe810b686 |
| pair | Scorer -> Analyst | none | the digest is not produced by Scorer; its producer is the unresolved candidate above | J. Merrick | 2026-09-30 | e380fb91cf05a80a6b9d098c719976233f5f57e4d0504c47e9668132bbb8f661 |
| pair | Analyst -> Ingest | none | the Analyst is a read-only consumer; nothing flows into the pipeline | J. Merrick | 2026-09-30 | f1d6bd6de416fb04f3017c8d52d986f397b67d682888a9ee75a7761ca7888688 |
| pair | Analyst -> Store | none | read-only consumer, and external to the boundary | J. Merrick | 2026-09-30 | 0c3cc0de7a486c81adf21e5cf695d515b13be8386e6e75ee4458f4cfff02066f |
| pair | Analyst -> Scorer | none | read-only consumer, and external to the boundary | J. Merrick | 2026-09-30 | 13211bf01419e89a0a02da395a37ab66080baf52eee38caaffa4f127f4ef96fd |

## Certification record

- input: examples/example.md (sha256 52ec2692280ee34a4c87124e7fe7117a1dbecb87b63dbf83562664b1daebd1a2)
- ledger: examples/example-ledger.md
- gate: certified
- report: sha256 1480d1f3872a8d603afa6ceaf17d868d4c225fe7a477dbd250b8312270cf224a
- flags: --sample 20
- blockers: none
- advisories: 4
- candidate ? -> Analyst: weekly digest (input line 23): accepted — the digest is written by the on-call engineer of the week, a person outside the boundary; no component to declare until the reporting pass names the real producer
- gap Store -> Scorer: event batches (input line 22, missing Format, Trigger): open-parked — format and trigger wait on the storage RFP, due before work packages are cut; gap register G-12
- boundary Ingest (nothing feeds it): accepted — Ingest is the system's source: it reads the external event broker, which is outside the boundary
- boundary Scorer (nothing consumes its output): accepted — scored events are read by the Analyst's ad-hoc queries at this stage; the digest interface will name the producer once the reporting pass lands
4 changes: 2 additions & 2 deletions examples/example.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ The input from the README's Example section, as a real file. Run from

| Producer | Consumer | Flows | Format | Trigger | Owner | Source | Status |
|---|---|---|---|---|---|---|---|
| Ingest | Store | raw event rows | ndjson file | nightly cron | platform | S:L42 | |
| Store | Scorer | event batches | ? | ? | platform | S:L44 | |
| Ingest | Store | raw event rows | ndjson file | nightly cron | platform | | |
| Store | Scorer | event batches | ? | ? | platform | | |
| ? | Analyst | weekly digest | ? | ? | ? | | |
Loading
Loading