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
5 changes: 2 additions & 3 deletions .github/workflows/deploy-site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,15 @@ jobs:
run: cd site && pnpm install && pnpm run build

- name: Deploy to Cloudflare Pages
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
id: deploy
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy site/_site --project-name=qualifier-dev --branch=main
command: pages deploy site/_site --project-name=qualifier-dev --branch=${{ github.event_name == 'push' && 'main' || github.head_ref }}

- name: Comment preview URL on PR
if: github.event_name == 'pull_request' && steps.deploy.outputs.deployment-url
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
Expand Down
74 changes: 44 additions & 30 deletions METABOX.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
## Abstract

Metabox is a minimal envelope format for content-addressed records. It defines
seven fixed fields that answer "who said what, when" plus a `body` object for
eight fixed fields that answer "who said what, when" plus a `body` object for
domain-specific payload. Records are JSONL, IDs are BLAKE3 hashes of a
canonical form.

Expand All @@ -19,20 +19,22 @@ that benefits from content addressing and a uniform envelope.

## 1. Envelope Fields

Every Metabox record is a JSON object with exactly seven top-level fields, in
Every Metabox record is a JSON object with exactly eight top-level fields, in
this canonical order:

| # | Field | Type | Required | Description |
| --- | ------------ | ------ | -------- | ---------------------------------------------- |
| 1 | `metabox` | string | yes | Envelope version. Always `"1"`. |
| 2 | `type` | string | yes | Body schema identifier. |
| 3 | `subject` | string | yes | What this record is about. |
| 4 | `author` | string | yes | Who or what created this record. |
| 5 | `created_at` | string | yes | RFC 3339 timestamp. |
| 6 | `id` | string | yes | Content-addressed BLAKE3 hash (see section 3). |
| 7 | `body` | object | yes | Type-specific payload. |
| # | Field | Type | Required | Description |
| --- | -------------- | ------ | -------- | ---------------------------------------------- |
| 1 | `metabox` | string | yes | Envelope version. Always `"1"`. |
| 2 | `type` | string | yes | Body schema identifier. |
| 3 | `subject` | string | yes | What this record is about. |
| 4 | `issuer` | string | yes | Who or what created this record (URI). |
| 5 | `issuer_type` | string | no | Issuer classification (see 1.5). |
| 6 | `created_at` | string | yes | RFC 3339 timestamp. |
| 7 | `id` | string | yes | Content-addressed BLAKE3 hash (see section 3). |
| 8 | `body` | object | yes | Type-specific payload. |

All seven fields are required. All seven are present in every record.
Seven fields are required. `issuer_type` is optional. All eight are present in
the canonical field order.

### 1.1 `metabox`

Expand Down Expand Up @@ -66,29 +68,38 @@ conventions are a project-level decision.
Examples: `"src/parser.rs"`, `"pkg:npm/lodash@4.17.21"`, `"service/health"`,
`"https://example.com/api/v2"`.

### 1.4 `author`
### 1.4 `issuer`

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

### 1.5 `created_at`
### 1.5 `issuer_type`

Optional classification of the issuer entity. When present, it is one of:
`"human"`, `"ai"`, `"tool"`, or `"unknown"`. Omitted when not applicable.

This field lives in the envelope (not the body) because it answers an
envelope-level question — "what kind of entity issued this record?" — and is
uniform across all record types.

### 1.6 `created_at`

An RFC 3339 timestamp indicating when the record was created.

### 1.6 `id`
### 1.7 `id`

A lowercase hex-encoded BLAKE3 hash of the record's Metabox Canonical Form
(section 3), 64 characters. Content-addressed: the same record always produces
the same ID.

### 1.7 `body`
### 1.8 `body`

A JSON object containing the type-specific payload. The body is always present.
Types with no fields use an empty object `{}`.

The envelope never looks inside the body. Generic Metabox tooling (indexers,
replicators, filters) can operate on the six envelope fields without
understanding or parsing the body.
replicators, filters) can operate on the envelope fields without understanding
or parsing the body.

## 2. File Format

Expand Down Expand Up @@ -120,13 +131,15 @@ obey the following rules:
Before serialization:

- `id` MUST be set to `""` (the empty string).
- All seven envelope fields MUST be present.
- All eight envelope fields MUST be present (optional fields use their absent
representation: `issuer_type` is omitted when not set).
- `body` MUST be present (empty `{}` if the type has no fields).

### 3.2 Field Order

1. **Envelope fields** appear in the fixed order defined in section 1:
`metabox`, `type`, `subject`, `author`, `created_at`, `id`, `body`.
`metabox`, `type`, `subject`, `issuer`, `issuer_type`, `created_at`, `id`,
`body`. Optional envelope fields (`issuer_type`) are omitted when absent.
2. **Body fields** are sorted lexicographically by key. Sorting is recursive:
nested objects also have their keys sorted lexicographically.

Expand Down Expand Up @@ -192,22 +205,23 @@ define:
A Qualifier attestation in Metabox format:

```json
{"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"}}
{"metabox":"1","type":"attestation","subject":"src/parser.rs","issuer":"mailto:alice@example.com","issuer_type":"human","created_at":"2026-02-24T10:00:00Z","id":"a1b2c3d4e5f6a7b8a1b2c3d4e5f6a7b8a1b2c3d4e5f6a7b8a1b2c3d4e5f6a7b8","body":{"kind":"concern","ref":"git:3aba500","score":-30,"summary":"Panics on malformed input"}}
```

Note that body fields are sorted lexicographically: `author_type`, `kind`,
`ref`, `score`, `summary`.
Note that body fields are sorted lexicographically: `kind`, `ref`, `score`,
`summary`. The `issuer_type` field is in the envelope, between `issuer` and
`created_at`.

A minimal record with an empty body:

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

A dependency declaration:

```json
{"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"]}}
{"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"]}}
```

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

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

`span`, `kind`, `score`, `summary`, `detail`, `suggested_fix`, `tags`,
`author_type`, `ref`, `supersedes` → `body.*`
`ref`, `supersedes` → `body.*`

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

`span`, `score`, `summary`, `refs`, `author_type` → `body.*`
`span`, `score`, `summary`, `refs` → `body.*`

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

Expand Down
Loading