Skip to content

Commit 51cd36b

Browse files
committed
feat: rename author to issuer with URI-based identity
Replace the opaque `author` string with `issuer`, a URI-based identity field (`mailto:`, `https:`, `urn:qualifier:`). Rename `author_type` to `issuer_type` and `AuthorType` to `IssuerType`. This is a breaking change to the Metabox envelope — all content-addressed IDs change due to the MCF field rename and body field reordering.
1 parent a669b65 commit 51cd36b

17 files changed

Lines changed: 328 additions & 313 deletions

METABOX.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ this canonical order:
2727
| 1 | `metabox` | string | yes | Envelope version. Always `"1"`. |
2828
| 2 | `type` | string | yes | Body schema identifier. |
2929
| 3 | `subject` | string | yes | What this record is about. |
30-
| 4 | `author` | string | yes | Who or what created this record. |
30+
| 4 | `issuer` | string | yes | Who or what created this record (URI). |
3131
| 5 | `created_at` | string | yes | RFC 3339 timestamp. |
3232
| 6 | `id` | string | yes | Content-addressed BLAKE3 hash (see section 3). |
3333
| 7 | `body` | object | yes | Type-specific payload. |
@@ -66,10 +66,10 @@ conventions are a project-level decision.
6666
Examples: `"src/parser.rs"`, `"pkg:npm/lodash@4.17.21"`, `"service/health"`,
6767
`"https://example.com/api/v2"`.
6868

69-
### 1.4 `author`
69+
### 1.4 `issuer`
7070

71-
Who or what created this record. Typically an email address, tool identifier,
72-
or service account name. The string is opaque to Metabox.
71+
Who or what created this record (URI). Typically a `mailto:` URI, `https:`
72+
service URL, or other URI-scheme identifier. The string is opaque to Metabox.
7373

7474
### 1.5 `created_at`
7575

@@ -126,7 +126,7 @@ Before serialization:
126126
### 3.2 Field Order
127127

128128
1. **Envelope fields** appear in the fixed order defined in section 1:
129-
`metabox`, `type`, `subject`, `author`, `created_at`, `id`, `body`.
129+
`metabox`, `type`, `subject`, `issuer`, `created_at`, `id`, `body`.
130130
2. **Body fields** are sorted lexicographically by key. Sorting is recursive:
131131
nested objects also have their keys sorted lexicographically.
132132

@@ -192,22 +192,22 @@ define:
192192
A Qualifier attestation in Metabox format:
193193

194194
```json
195-
{"metabox":"1","type":"attestation","subject":"src/parser.rs","author":"alice@example.com","created_at":"2026-02-24T10:00:00Z","id":"a1b2c3d4e5f6a7b8a1b2c3d4e5f6a7b8a1b2c3d4e5f6a7b8a1b2c3d4e5f6a7b8","body":{"author_type":"human","kind":"concern","ref":"git:3aba500","score":-30,"summary":"Panics on malformed input"}}
195+
{"metabox":"1","type":"attestation","subject":"src/parser.rs","issuer":"mailto:alice@example.com","created_at":"2026-02-24T10:00:00Z","id":"a1b2c3d4e5f6a7b8a1b2c3d4e5f6a7b8a1b2c3d4e5f6a7b8a1b2c3d4e5f6a7b8","body":{"issuer_type":"human","kind":"concern","ref":"git:3aba500","score":-30,"summary":"Panics on malformed input"}}
196196
```
197197

198-
Note that body fields are sorted lexicographically: `author_type`, `kind`,
198+
Note that body fields are sorted lexicographically: `issuer_type`, `kind`,
199199
`ref`, `score`, `summary`.
200200

201201
A minimal record with an empty body:
202202

203203
```json
204-
{"metabox":"1","type":"ping","subject":"service/health","author":"monitor","created_at":"2026-02-25T12:00:00Z","id":"f9e8d7c6b5a4f9e8d7c6b5a4f9e8d7c6b5a4f9e8d7c6b5a4f9e8d7c6b5a4f9e8","body":{}}
204+
{"metabox":"1","type":"ping","subject":"service/health","issuer":"https://monitor.example.com","created_at":"2026-02-25T12:00:00Z","id":"f9e8d7c6b5a4f9e8d7c6b5a4f9e8d7c6b5a4f9e8d7c6b5a4f9e8d7c6b5a4f9e8","body":{}}
205205
```
206206

207207
A dependency declaration:
208208

209209
```json
210-
{"metabox":"1","type":"dependency","subject":"bin/server","author":"build-system","created_at":"2026-02-25T10:00:00Z","id":"1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b","body":{"depends_on":["lib/auth","lib/http","lib/db"]}}
210+
{"metabox":"1","type":"dependency","subject":"bin/server","issuer":"https://build.example.com","created_at":"2026-02-25T10:00:00Z","id":"1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d5e6f1a2b","body":{"depends_on":["lib/auth","lib/http","lib/db"]}}
211211
```
212212

213213
## 7. Qualifier Mapping
@@ -221,7 +221,7 @@ Qualifier v3 records map to Metabox as follows:
221221
| `v: 3` | `metabox: "1"` | Version field changes name/value |
222222
| `type` | `type` | Unchanged |
223223
| `artifact` | `subject` | Renamed for generality |
224-
| `author` | `author` | Unchanged |
224+
| `author` | `issuer` | Renamed to issuer (URI-based identity) |
225225
| `created_at` | `created_at` | Unchanged |
226226
| `id` | `id` | Unchanged |
227227

@@ -232,11 +232,11 @@ All non-frame fields move into the `body` object:
232232
**Attestation** (`type: "attestation"`):
233233

234234
`span`, `kind`, `score`, `summary`, `detail`, `suggested_fix`, `tags`,
235-
`author_type`, `ref`, `supersedes``body.*`
235+
`issuer_type`, `ref`, `supersedes``body.*`
236236

237237
**Epoch** (`type: "epoch"`):
238238

239-
`span`, `score`, `summary`, `refs`, `author_type``body.*`
239+
`span`, `score`, `summary`, `refs`, `issuer_type``body.*`
240240

241241
**Dependency** (`type: "dependency"`):
242242

SPEC.md

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ structured quality signals — and to compute aggregate quality scores that
1616
propagate through dependency graphs.
1717

1818
Records use the [Metabox](METABOX.md) envelope format: a fixed envelope
19-
(`metabox`, `type`, `subject`, `author`, `created_at`, `id`) wrapping a
19+
(`metabox`, `type`, `subject`, `issuer`, `created_at`, `id`) wrapping a
2020
type-specific `body` object. Records are content-addressed, append-only, and
2121
human-writable. No server, no database, no PKI required.
2222

@@ -73,7 +73,7 @@ Every record uses the [Metabox](METABOX.md) envelope format with these fields:
7373
| `metabox` | string | yes | Envelope version. MUST be `"1"`. |
7474
| `type` | string | yes* | Record type identifier (see 2.5). *May be omitted in `.qual` files; defaults to `"attestation"`. |
7575
| `subject` | string | yes | Qualified name of the target artifact |
76-
| `author` | string | yes | Who or what created this record |
76+
| `issuer` | string | yes | Who or what created this record (URI) |
7777
| `created_at` | string | yes | RFC 3339 timestamp |
7878
| `id` | string | yes | Content-addressed BLAKE3 hash (see 2.8) |
7979
| `body` | object | yes | Type-specific payload (see 2.6, 3.2, 3.4) |
@@ -212,8 +212,8 @@ Metabox envelope fields (section 2.2) plus body fields:
212212

213213
| Field | Type | Required | Description |
214214
|-----------------|----------|----------|-------------|
215-
| `author_type` | string | no | Author classification: `human`, `ai`, `tool`, `unknown` |
216215
| `detail` | string | no | Extended description, markdown allowed |
216+
| `issuer_type` | string | no | Issuer classification: `human`, `ai`, `tool`, `unknown` |
217217
| `kind` | string | yes | The type of attestation (see 2.7) |
218218
| `ref` | string | no | VCS reference pin (e.g., `"git:3aba500"`). Opaque to qualifier. |
219219
| `score` | integer | yes | Signed quality delta, -100..100 |
@@ -229,14 +229,14 @@ Canonical Form (MCF) serialization order.
229229
**Example:**
230230

231231
```json
232-
{"metabox":"1","type":"attestation","subject":"src/parser.rs","author":"alice@example.com","created_at":"2026-02-25T10:00:00Z","id":"a1b2c3d4...","body":{"author_type":"human","kind":"concern","ref":"git:3aba500","score":-10,"span":{"start":{"line":42},"end":{"line":58}},"suggested_fix":"Use the ? operator instead of unwrap()","summary":"Panics on malformed input","tags":["robustness"]}}
232+
{"metabox":"1","type":"attestation","subject":"src/parser.rs","issuer":"mailto:alice@example.com","created_at":"2026-02-25T10:00:00Z","id":"a1b2c3d4...","body":{"issuer_type":"human","kind":"concern","ref":"git:3aba500","score":-10,"span":{"start":{"line":42},"end":{"line":58}},"suggested_fix":"Use the ? operator instead of unwrap()","summary":"Panics on malformed input","tags":["robustness"]}}
233233
```
234234

235235
**Shorthand (equivalent):** Since `type` defaults to `"attestation"`, it may
236236
be omitted:
237237

238238
```json
239-
{"metabox":"1","subject":"src/parser.rs","author":"alice@example.com","created_at":"2026-02-25T10:00:00Z","id":"a1b2c3d4...","body":{"kind":"concern","score":-10,"summary":"Panics on malformed input"}}
239+
{"metabox":"1","subject":"src/parser.rs","issuer":"mailto:alice@example.com","created_at":"2026-02-25T10:00:00Z","id":"a1b2c3d4...","body":{"kind":"concern","score":-10,"summary":"Panics on malformed input"}}
240240
```
241241

242242
### 2.7 Attestation Kinds
@@ -303,7 +303,7 @@ obey the following rules:
303303
- `id` MUST be set to `""` (the empty string).
304304

305305
2. **Envelope field order.** Envelope fields MUST appear in this fixed order:
306-
`metabox`, `type`, `subject`, `author`, `created_at`, `id`, `body`.
306+
`metabox`, `type`, `subject`, `issuer`, `created_at`, `id`, `body`.
307307

308308
3. **Body field order.** Body fields MUST appear in lexicographic
309309
(alphabetical) order. Nested objects (like `span`) also have their fields
@@ -332,13 +332,13 @@ See the [Metabox specification](METABOX.md) for the full MCF definition.
332332
Given an attestation with no optional body fields, the MCF is:
333333

334334
```json
335-
{"metabox":"1","type":"attestation","subject":"src/parser.rs","author":"alice@example.com","created_at":"2026-02-24T10:00:00Z","id":"","body":{"kind":"concern","score":-30,"summary":"Panics on malformed input"}}
335+
{"metabox":"1","type":"attestation","subject":"src/parser.rs","issuer":"mailto:alice@example.com","created_at":"2026-02-24T10:00:00Z","id":"","body":{"kind":"concern","score":-30,"summary":"Panics on malformed input"}}
336336
```
337337

338-
With a span and author_type:
338+
With a span and issuer_type:
339339

340340
```json
341-
{"metabox":"1","type":"attestation","subject":"src/parser.rs","author":"alice@example.com","created_at":"2026-02-24T10:00:00Z","id":"","body":{"author_type":"human","kind":"concern","score":-30,"span":{"start":{"line":42},"end":{"line":42}},"summary":"Panics on malformed input"}}
341+
{"metabox":"1","type":"attestation","subject":"src/parser.rs","issuer":"mailto:alice@example.com","created_at":"2026-02-24T10:00:00Z","id":"","body":{"issuer_type":"human","kind":"concern","score":-30,"span":{"start":{"line":42},"end":{"line":42}},"summary":"Panics on malformed input"}}
342342
```
343343

344344
Note that `span.end` has been materialized (it was omitted in the input,
@@ -401,8 +401,8 @@ All layouts are backwards-compatible and can coexist in the same project.
401401
**Example (mixed record types):**
402402

403403
```jsonl
404-
{"metabox":"1","type":"attestation","subject":"src/parser.rs","author":"alice@example.com","created_at":"2026-02-24T10:00:00Z","id":"a1b2c3d4...","body":{"author_type":"human","kind":"concern","ref":"git:3aba500","score":-30,"span":{"start":{"line":42},"end":{"line":58}},"suggested_fix":"Replace .unwrap() with proper error propagation","summary":"Panics on malformed UTF-8 input","tags":["robustness","error-handling"]}}
405-
{"metabox":"1","type":"attestation","subject":"src/parser.rs","author":"bob@example.com","created_at":"2026-02-24T11:00:00Z","id":"e5f6a7b8...","body":{"author_type":"human","kind":"praise","score":40,"summary":"Excellent property-based test coverage","tags":["testing"]}}
404+
{"metabox":"1","type":"attestation","subject":"src/parser.rs","issuer":"mailto:alice@example.com","created_at":"2026-02-24T10:00:00Z","id":"a1b2c3d4...","body":{"issuer_type":"human","kind":"concern","ref":"git:3aba500","score":-30,"span":{"start":{"line":42},"end":{"line":58}},"suggested_fix":"Replace .unwrap() with proper error propagation","summary":"Panics on malformed UTF-8 input","tags":["robustness","error-handling"]}}
405+
{"metabox":"1","type":"attestation","subject":"src/parser.rs","issuer":"mailto:bob@example.com","created_at":"2026-02-24T11:00:00Z","id":"e5f6a7b8...","body":{"issuer_type":"human","kind":"praise","score":40,"summary":"Excellent property-based test coverage","tags":["testing"]}}
406406
```
407407

408408
## 3. Record Type Specifications
@@ -421,18 +421,18 @@ Body fields (alphabetical):
421421

422422
| Field | Type | Required | Description |
423423
|---------------|----------|----------|-------------|
424-
| `author_type` | string | no | Always `"tool"` for epochs |
424+
| `issuer_type` | string | no | Always `"tool"` for epochs |
425425
| `refs` | string[] | yes | IDs of the compacted records |
426426
| `score` | integer | yes | Raw score at compaction time |
427427
| `span` | object | no | Sub-artifact range |
428428
| `summary` | string | yes | `"Compacted from N records"` |
429429

430-
Epoch records MUST set `author` to `"qualifier/compact"`.
430+
Epoch records MUST set `issuer` to `"urn:qualifier:compact"`.
431431

432432
**Example:**
433433

434434
```json
435-
{"metabox":"1","type":"epoch","subject":"src/parser.rs","author":"qualifier/compact","created_at":"2026-02-25T12:00:00Z","id":"f9e8d7c6...","body":{"author_type":"tool","refs":["a1b2...","c3d4..."],"score":10,"summary":"Compacted from 12 records"}}
435+
{"metabox":"1","type":"epoch","subject":"src/parser.rs","issuer":"urn:qualifier:compact","created_at":"2026-02-25T12:00:00Z","id":"f9e8d7c6...","body":{"issuer_type":"tool","refs":["a1b2...","c3d4..."],"score":10,"summary":"Compacted from 12 records"}}
436436
```
437437

438438
Epoch records are treated as normal scored records by the scoring engine. The
@@ -476,7 +476,7 @@ Body fields:
476476
**Example:**
477477

478478
```json
479-
{"metabox":"1","type":"dependency","subject":"bin/server","author":"build-system","created_at":"2026-02-25T10:00:00Z","id":"1a2b3c4d...","body":{"depends_on":["lib/auth","lib/http","lib/db"]}}
479+
{"metabox":"1","type":"dependency","subject":"bin/server","issuer":"https://build.example.com","created_at":"2026-02-25T10:00:00Z","id":"1a2b3c4d...","body":{"depends_on":["lib/auth","lib/http","lib/db"]}}
480480
```
481481

482482
The dependency graph MUST be a DAG. Implementations MUST detect and reject
@@ -504,7 +504,7 @@ New record types are identified by a string value in the `type` field. Types
504504
defined outside this spec SHOULD use a URI to avoid collisions:
505505

506506
```json
507-
{"metabox":"1","type":"https://example.com/qualifier/license/v1","subject":"src/parser.rs","author":"license-scanner","created_at":"...","id":"...","body":{"license":"MIT"}}
507+
{"metabox":"1","type":"https://example.com/qualifier/license/v1","subject":"src/parser.rs","issuer":"https://license-scanner.example.com","created_at":"...","id":"...","body":{"license":"MIT"}}
508508
```
509509

510510
Types defined in this spec use short aliases (`attestation`, `epoch`,
@@ -608,8 +608,8 @@ predicates for use with DSSE signing and Sigstore distribution.
608608
"span": {"start": {"line": 42}, "end": {"line": 58}},
609609
"summary": "Panics on malformed input",
610610
"tags": ["robustness"],
611-
"author": "alice@example.com",
612-
"author_type": "human",
611+
"issuer": "mailto:alice@example.com",
612+
"issuer_type": "human",
613613
"created_at": "2026-02-25T10:00:00Z",
614614
"ref": "git:3aba500",
615615
"supersedes": null
@@ -624,7 +624,7 @@ predicates for use with DSSE signing and Sigstore distribution.
624624
| `subject` | `subject[0].name` |
625625
| `body.span` | `predicate.span` |
626626
| `id` | `predicate.qualifier_id` |
627-
| `author` | `predicate.author` (also DSSE signer) |
627+
| `issuer` | `predicate.issuer` (also DSSE signer) |
628628
| All body fields | `predicate.*` |
629629

630630
The in-toto `subject[0].digest` contains the content hash of the artifact
@@ -653,8 +653,8 @@ SARIF v2.1.0 results can be converted to qualifier attestations:
653653
| `result.ruleId` | `body.kind` (as custom kind) |
654654
| `result.level` | `body.score` (see mapping below) |
655655
| `result.message.text` | `body.summary` |
656-
| `run.tool.driver.name` | `author` |
657-
| (constant) | `body.author_type: "tool"` |
656+
| `run.tool.driver.name` | `issuer` |
657+
| (constant) | `body.issuer_type: "tool"` |
658658

659659
**Level-to-score mapping:**
660660

@@ -698,7 +698,7 @@ qualifier attest src/parser.rs \
698698
--suggested-fix "Use proper error propagation" \
699699
--tag robustness \
700700
--tag error-handling \
701-
--author "alice@example.com" \
701+
--issuer "mailto:alice@example.com" \
702702
--span 42:58
703703
```
704704

@@ -725,7 +725,7 @@ given kind (see section 2.7.1).
725725
`--file <path>` writes the attestation to a specific `.qual` file instead
726726
of using the default layout resolution.
727727

728-
When `--author` is omitted, defaults to the VCS user identity (see 8.4).
728+
When `--issuer` is omitted, defaults to the VCS user identity (see 8.4).
729729

730730
### 6.3 `qualifier show`
731731

@@ -808,7 +808,7 @@ Qualifier uses layered configuration. Precedence (highest wins):
808808
| Key | CLI flag | Env var | Default |
809809
|-------------|----------------|----------------------|---------|
810810
| `graph` | `--graph` | `QUALIFIER_GRAPH` | `qualifier.graph.jsonl` |
811-
| `author` | `--author` | `QUALIFIER_AUTHOR` | VCS identity (see 8.4) |
811+
| `issuer` | `--issuer` | `QUALIFIER_ISSUER` | VCS identity (see 8.4) |
812812
| `format` | `--format` | `QUALIFIER_FORMAT` | `human` |
813813
| `min_score` | `--min-score` | `QUALIFIER_MIN_SCORE`| `0` |
814814

@@ -852,15 +852,15 @@ pub struct Attestation {
852852
pub metabox: String, // always "1"
853853
pub record_type: String, // "attestation"
854854
pub subject: String,
855-
pub author: String,
855+
pub issuer: String,
856856
pub created_at: DateTime<Utc>,
857857
pub id: String,
858858
pub body: AttestationBody,
859859
}
860860

861861
pub struct AttestationBody {
862-
pub author_type: Option<AuthorType>,
863862
pub detail: Option<String>,
863+
pub issuer_type: Option<IssuerType>,
864864
pub kind: Kind,
865865
pub r#ref: Option<String>,
866866
pub score: i32,
@@ -875,14 +875,14 @@ pub struct Epoch {
875875
pub metabox: String, // always "1"
876876
pub record_type: String, // "epoch"
877877
pub subject: String,
878-
pub author: String,
878+
pub issuer: String,
879879
pub created_at: DateTime<Utc>,
880880
pub id: String,
881881
pub body: EpochBody,
882882
}
883883

884884
pub struct EpochBody {
885-
pub author_type: Option<AuthorType>,
885+
pub issuer_type: Option<IssuerType>,
886886
pub refs: Vec<String>,
887887
pub score: i32,
888888
pub span: Option<Span>,
@@ -893,7 +893,7 @@ pub struct DependencyRecord {
893893
pub metabox: String, // always "1"
894894
pub record_type: String, // "dependency"
895895
pub subject: String,
896-
pub author: String,
896+
pub issuer: String,
897897
pub created_at: DateTime<Utc>,
898898
pub id: String,
899899
pub body: DependencyBody,
@@ -914,7 +914,7 @@ pub struct Position {
914914
}
915915

916916
pub enum Kind { Pass, Fail, Blocker, Concern, Praise, Suggestion, Waiver, Custom(String) }
917-
pub enum AuthorType { Human, Ai, Tool, Unknown }
917+
pub enum IssuerType { Human, Ai, Tool, Unknown }
918918

919919
pub fn generate_id(attestation: &Attestation) -> String;
920920
pub fn generate_epoch_id(epoch: &Epoch) -> String;
@@ -971,13 +971,13 @@ Delegates to the underlying VCS blame/annotate command:
971971
- Mercurial: `hg annotate`
972972
- Fallback: not available (prints guidance)
973973

974-
### 8.4 Author Defaults
974+
### 8.4 Issuer Defaults
975975

976-
When `--author` is omitted:
976+
When `--issuer` is omitted:
977977

978978
- Git: `git config user.email`
979979
- Mercurial: `hg config ui.username`
980-
- Fallback: `$USER@localhost`
980+
- Fallback: `mailto:$USER@localhost`
981981

982982
## 9. Agent Integration
983983

@@ -1014,7 +1014,7 @@ qualifier/
10141014
├── qualifier.graph.jsonl # Example / self-hosted graph
10151015
└── src/
10161016
├── lib.rs # Public library API
1017-
├── attestation.rs # Record types, body structs, Kind, AuthorType, validation
1017+
├── attestation.rs # Record types, body structs, Kind, IssuerType, validation
10181018
├── qual_file.rs # .qual file parsing, appending, discovery
10191019
├── graph.rs # Dependency graph loading, cycle detection
10201020
├── scoring.rs # Raw + effective score computation

0 commit comments

Comments
 (0)