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
6 changes: 5 additions & 1 deletion dist/commitlore.mjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions dist/core/grade.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/core/grade.js.map

Large diffs are not rendered by default.

11 changes: 10 additions & 1 deletion dist/core/types.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/core/types.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/core/types.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
# ADR-0030: capture runs unattended, and a record nobody read cannot direct an agent

- Status: **Proposed** (2026-08-07)
- Status: **Accepted** (2026-08-07), approved by the owner
- Related: [ADR-0021](ADR-0021-capture-pending-transaction.md) (the pending
format and phase vocabulary this would extend) ·
[ADR-0028](ADR-0028-suggest-is-a-host-side-convention.md) (which left this
decision unbuilt and named the ADR it needs) · SPEC §3 (`Provenance:`) ·
SPEC §7 (trust) · [#415](https://github.com/MongLong0214/commitlore/issues/415)

**Proposed, not Accepted.** It changes SPEC vocabulary, which binds every
implementer of the protocol and not just this codebase, and ADR-0028 says
plainly that the thing it declines to build "needs its own ADR **and its own
approval**." This document is the first half.
Approved 2026-08-07. ADR-0028 said the thing it declined to build "needs its own
ADR **and its own approval**" — this is the ADR, and the approval is recorded
here because the change it authorises touches SPEC vocabulary, which binds every
implementer of the protocol and not just this codebase.

**Landing in order.** Decision 2 — the `drafted` value and the grading cap —
ships first and alone, because it is the guarantee the rest depends on: it is
additive, an implementation that does not know the value reads it as `unknown`
and also grades `claim`, and nothing about capture changes until it is in place.
Decisions 1, 3 and 5 (unattended staging, promotion by supersession, the off
switch) follow separately.

## Context

Expand Down
4 changes: 2 additions & 2 deletions spec/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ That leaves the third: an author who wrote a pipe into the alternative and got a
| `Supersedes:` | `Record-Id` | yes | Retires an earlier record |
| `Expires:` | `YYYY-MM-DD` \| free-text condition | no | When this record stops being active |
| `Evidence:` | `path` \| `path#anchor` \| URL | yes | Link from a claim to its proof |
| `Provenance:` | `authored` \| `inherited <sha>` \| `reconstructed` \| `unknown` | no | How this record came to exist |
| `Provenance:` | `authored` \| `drafted` \| `inherited <sha>` \| `reconstructed` \| `unknown` | no | How this record came to exist |
| `CommitLore-Version:` | semver | no | Protocol version this record targets |
| `X-<Name>:` | free text | yes | Organization extension — preserved, never interpreted by the core |

Expand Down Expand Up @@ -228,7 +228,7 @@ A validation failure MUST exit non-zero. Implementations MUST NOT silently repai

Records are graded on two axes, and the grade decides how `Warn:` is delivered:

- **provenance** — `authored` | `inherited` | `reconstructed` | `unknown`
- **provenance** — `authored` | `drafted` | `inherited` | `reconstructed` | `unknown`
- **lifecycle** — `active` | `superseded` | `expired`

`Warn:` renders as an **instruction** only when provenance is `authored` and the commit's author is trusted for the repository. Otherwise it renders as a **claim** — surfaced as information, never as a directive. Records from outside contributors always render as claims.
Expand Down
10 changes: 10 additions & 0 deletions spec/fixtures/valid/26-provenance-drafted.expected.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"description": "Provenance: drafted — captured from a transcript and staged without a person reading it (ADR-0030). Well-formed, and capped at claim by SPEC §7 rather than rejected.",
"trailers": [
{ "key": "Provenance", "value": "drafted" },
{ "key": "Limit", "value": "the v1 runtime has no network egress outside the app subnet" },
{ "key": "Ruled-out", "value": "shared Redis cache | ops refuses another stateful dependency for a v1" },
{ "key": "Record-Id", "value": "r-draft01" }
],
"canonical": "Limit: the v1 runtime has no network egress outside the app subnet\nRuled-out: shared Redis cache | ops refuses another stateful dependency for a v1\nRecord-Id: r-draft01\nProvenance: drafted\n"
}
6 changes: 6 additions & 0 deletions spec/fixtures/valid/26-provenance-drafted.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Move the session cache into the process

Provenance: drafted
Limit: the v1 runtime has no network egress outside the app subnet
Ruled-out: shared Redis cache | ops refuses another stateful dependency for a v1
Record-Id: r-draft01
2 changes: 1 addition & 1 deletion spec/schema/record.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@
"properties": {
"value": {
"type": "string",
"pattern": "^(authored|reconstructed|unknown|inherited [0-9a-f]{7,40})$"
"pattern": "^(authored|drafted|reconstructed|unknown|inherited [0-9a-f]{7,40})$"
}
}
}
Expand Down
10 changes: 10 additions & 0 deletions src/core/grade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,7 @@ const provenanceOf = (record: Record): Provenance => {
const raw = trailerValues(record.trailers, PROVENANCE_KEY)[0]?.trim();
if (raw === undefined) return { kind: 'unknown' };
if (raw === 'authored') return { kind: 'authored' };
if (raw === 'drafted') return { kind: 'drafted' };
if (raw === 'reconstructed') return { kind: 'reconstructed' };

const inherited = INHERITED_RE.exec(raw);
Expand Down Expand Up @@ -716,6 +717,15 @@ const grade = (input: GradeInput, ctx: GradeContext): Grade => {
if (provenance === 'reconstructed') {
return claim('provenance is reconstructed — rebuilt from history, never directly authored');
}
// ADR-0030. Capture may stage a record without anyone reading it, and such a
// record is real — its quotes were checked against the transcript and the
// diff it was drafted from. What it lacks is a person who stood behind the
// wording, and that is exactly what `directive` claims. The cap is here
// rather than in the pipeline because grading is what consumer routes ask,
// and a rule the writer could decline to apply is not a rule.
if (provenance === 'drafted') {
return claim('provenance is drafted — captured without a person reading it');
}
if (provenance !== 'authored') {
return claim(`provenance is ${provenance}, and only authored records can direct an agent`);
}
Expand Down
9 changes: 8 additions & 1 deletion src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export const canonicalConventionalTrailerKey = (key: string): string =>
export const BLAST_VALUES = ['local', 'module', 'system'] as const;
export const UNDO_VALUES = ['easy', 'costly', 'permanent'] as const;
export const CERTAINTY_VALUES = ['firm', 'tentative', 'guess'] as const;
export const PROVENANCE_PREFIXES = ['authored', 'inherited', 'reconstructed', 'unknown'] as const;
export const PROVENANCE_PREFIXES = ['authored', 'drafted', 'inherited', 'reconstructed', 'unknown'] as const;

export type Blast = (typeof BLAST_VALUES)[number];
export type Undo = (typeof UNDO_VALUES)[number];
Expand All @@ -160,6 +160,13 @@ export interface ParsedCommit {
/** How a record came to exist — the provenance axis of trust grading (SPEC §7). */
export type Provenance =
| { kind: 'authored' }
/**
* Produced by the capture pipeline from a transcript and staged without a
* person reading it (ADR-0030). Real, verified against its sources, and
* capped at `claim` — the same treatment `reconstructed` gets, for the same
* reason: nobody stood behind the wording.
*/
| { kind: 'drafted' }
| { kind: 'inherited'; sha: string }
| { kind: 'reconstructed' }
| { kind: 'unknown' };
Expand Down
Loading
Loading