Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .abcd/development/brief/02-constraints/04-ethics.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,14 @@
The sensitive artefacts — a participant's voice and screen — stay on local
hardware. ASR is local; raw audio/video never leave the machine. Only derived
text (transcript, serialised events, and any keyframes the analyst explicitly
releases) reaches a cloud LLM; a fully local variant (local LLM for analysis)
is the fallback if an ethics protocol requires it.
releases) reaches an LLM at all. Whether that LLM is in the cloud is the
operator's choice, not the tool's: the oracle is host-delegated, so `analyze`
emits a request and something else answers it. Answer it on the same machine and
no session content leaves the machine at any step — the fallback an ethics
protocol requires. Which route a session took is **recorded rather than
assumed**: `analyze -ingest` writes the operator's declared backend and model
into `findings.jsonl` and `report.md` prints it, with the standing caveat that
the CLI cannot verify a claim about a program it never called.

For sessions with external participants:

Expand Down
30 changes: 29 additions & 1 deletion .abcd/development/brief/04-surfaces/06-analyze.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,15 @@ page ([`../05-internals/02-schemas.md`](../05-internals/02-schemas.md)).
| `-session` | (required) | session directory |
| `-out` | *(stdout)* | write the emitted request to `FILE` instead of stdout (emit mode) |
| `-ingest` | *(off)* | validate the answer JSON at `FILE` (or `-` for stdin) into `findings.jsonl` (ingest mode) |
| `-backend` | *(unrecorded)* | record which backend answered the request: `local` or `cloud` (ingest mode) |
| `-model` | *(not recorded)* | record the model that answered the request; free text, at most 200 characters, refused when it renders as nothing under `session.CodeRendersEmpty` (ingest mode) |

`analyze` runs in exactly one mode: emit (no `-ingest`) or ingest (`-ingest`).
`-out` and `-ingest` together is an error. Emit reads `manifest.json` and
`-out` and `-ingest` together is an error. `-backend` and `-model` belong to
ingest alone — emit mutates nothing, so there is nothing to record against — and
either in emit mode is a usage error; so is `-model` without `-backend`, and so
is `-backend unrecorded`, which is what the flag's absence records rather than a
value an operator states. Emit reads `manifest.json` and
`timeline.jsonl`; ingest reads `timeline.jsonl` only. Both hint to run `merge`
first when the timeline is missing (matching [`report`](04-report.md)).

Expand Down Expand Up @@ -70,6 +76,28 @@ first when the timeline is missing (matching [`report`](04-report.md)).
- An answer with no findings (a bare `[]`, `{"findings":[]}`, or a truncated
file) is refused rather than written: the write truncates, so an empty answer
would otherwise erase a prior good `findings.jsonl` and report success.
- Every ingest writes one **provenance record** (`kind:"provenance"`) as the
FIRST line of `findings.jsonl`, in the same `session.CommitRecords` call as the
findings: the rubric version (from the package constant, not the answer's
claim), the backend, the model when given, and the date. First position because
ingest replaces the whole file while `review` appends verdicts to its end, so a
last-position record would be overtaken by the first verdict; riding in the
same commit is what makes a re-ingest replace the declaration together with the
findings it describes. The verdict-overwrite guard is untouched and outranks
it — a file holding verdicts refuses a re-ingest whatever the flags say.
- The record is the operator's **declaration**, not a measurement: the CLI never
calls a model and cannot observe where the request ran. With no `-backend` the
record states `unrecorded` and the run announces the intention on stderr, so
the choice is visible in the output of the run that made it rather than
silently absent; the notice says "will record" because it prints before
validation, and a run that then fails writes nothing.
- `Ingest` refuses a `Provenance` that is not one (`Provenance.Valid`: the kind
literal, a backend in the closed set, a non-empty rubric and date) before it
reads a byte, so the package cannot write a first line its own `ParseRecords`
would refuse. The only way to satisfy the check is to have built the record
through `NewProvenance`.
A `findings.jsonl` written before the record existed carries none and reads as
"not recorded" everywhere.

## Deferred

Expand Down
36 changes: 32 additions & 4 deletions .abcd/development/brief/05-internals/02-schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ sessions/<timestamp>/
interactions.jsonl # normalised interaction events (epoch ms)
transcript.jsonl # word-aligned utterances (session-relative seconds)
timeline.jsonl # merged, session-relative timeline
findings.jsonl # analysis findings + appended verdicts (written by analyze/review)
findings.jsonl # provenance + analysis findings + appended verdicts (written by analyze/review)
tests.jsonl # regression-test drafts + appended decisions (written by draft-tests/review)
report.md # human-readable session report
```
Expand Down Expand Up @@ -93,18 +93,46 @@ file.
{"t":129.01,"src":"event","id":"ev-001","payload":{"kind":"click","selector":"[data-testid=save-btn]","text":"Save","route":"/settings"}}
```

## `findings.jsonl` — findings plus appended verdicts
## `findings.jsonl` — provenance, findings, plus appended verdicts

The analysis layer's output, written by [`analyze -ingest`](../04-surfaces/06-analyze.md)
and [`review`](../04-surfaces/07-review.md). Two record kinds share the file, one
per line. A finding line carries no `kind`; a verdict line is discriminated by
and [`review`](../04-surfaces/07-review.md). Three record kinds share the file,
one per line. A provenance line is discriminated by `kind: "provenance"`; a
finding line carries no `kind`; a verdict line is discriminated by
`kind: "verdict"`. Verdicts are **appended, never in-place rewrites**, so the
finding's birth state and full decision history survive as the precision measure
([note §2](../../research/2026-07-17-architecture-note.md)). Ingest decodes each
finding with unknown fields disallowed — the shape is closed — and is the sole
validation boundary; every field below is checked, and `status` is forced to
`"unverified"` on ingest whatever the answer JSON claims.

**Provenance record** (`analyze.Provenance`):

The operator's declaration of what answered the analysis request, written by
ingest as the **first** line of the file, in the same `session.CommitRecords`
call as the findings. It is a declaration, not a measurement: the CLI never
calls a model and cannot observe where the request ran. Exactly one per file.

| Field | Type | Required | Notes |
|---|---|---|---|
| `kind` | string | yes | literal `"provenance"` — the discriminator |
| `rubric` | string | yes | the rubric version ingest enforced, from the package constant — never the answer's claimed rubric, which may be absent entirely (a bare-array answer) |
| `backend` | string | yes | one of `local \| cloud \| unrecorded`; `unrecorded` is written when no `-backend` is given and is not claimable from the flag |
| `model` | string | no | operator free text, at most `analyze.MaxModelLength` (200) runes, non-blank once `session.SafeText` is applied; omitted when not given |
| `at` | string | yes | ISO date `YYYY-MM-DD`, supplied by the caller (`NewProvenance`), never `time.Now()` inside the package |

```json
{"kind":"provenance","rubric":"testimony-analysis/v1","backend":"local","model":"llama3.1:70b","at":"2026-09-15"}
```

First position is a writer convention, not a reader requirement — `ParseRecords`
reads the record wherever it sits. A record whose `backend` falls outside the
closed set is **ignored**, as an out-of-enum verdict is, so an uninterpretable
claim never reaches the report; two interpretable records is a **hard error**
naming both lines, as a duplicate finding id is, because an ambiguous
attribution would have the report state a producer that may not be the one. A
file carrying no record reads as "not recorded" everywhere.

**Finding record** (`analyze.Finding`):

| Field | Type | Required | Notes |
Expand Down
14 changes: 10 additions & 4 deletions .abcd/development/brief/06-delivery/02-verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,16 @@ go test -race ./...

The pipeline smoke test asserts that `timeline.jsonl` and `report.md` are
non-empty and that the report renders the sample session's fixed content: the
`## Timeline` and `## Findings` headings, the confirmed `F-001` finding, the
"save button" utterance text, the `save-btn` selector, the exact
`**Utterances:** 10 · **Events:** 10` header count, and one indented event
bullet naming that same selector. The header count is what catches events
`## Timeline` and `## Findings` headings, the `_Provenance` line, the confirmed
`F-001` finding, the "save button" utterance text, the `save-btn` selector, the
exact `**Utterances:** 10 · **Events:** 10` header count, and one indented event
bullet naming that same selector. The provenance assertion pins the line's
presence, not a particular backend: the bundled sample declares none — its
findings were hand-authored for the repository rather than produced by any model,
and a sample claiming `local` would plant a false provenance claim in the one
artefact users copy from — so the grep catches the record being dropped by the
reader or the renderer, which would silently strip the one statement the report
makes about its own origin. The header count is what catches events
going missing from the merge: every other assertion up to it still passes with
`interactions.jsonl` deleted ("save button" comes from the utterance's own
text, and the `save-btn` selector renders from `findings.jsonl` regardless of
Expand Down
54 changes: 0 additions & 54 deletions .abcd/development/intents/drafts/itd-8-local-analysis.md

This file was deleted.

Loading