# Maildown Conformance

Use this checklist to test a second Maildown implementation against the current
reference behavior.

## Versions

An implementation must recognize:

- plaintext message version: `maildown: 1`
- signing prefix: `maildown-signature-v1`
- envelope version: `maildownEnvelope: 2`
- envelope algorithm: `maildown-envelope-v2`
- payload algorithm: `aes-256-gcm`
- recipient key algorithm: `ml-kem-768`

Unknown required versions or algorithms must fail closed.

## Message Parser

The parser must:

- require a leading `---` header delimiter
- read header lines until the second `---`
- parse `key: value` header lines
- parse bracketed lists such as `[athena, odysseus]`
- parse JSON-quoted strings in scalar or list values
- keep the body after the second delimiter
- trim trailing whitespace from parsed body

The parser should ignore blank header lines and comment lines that start with
`#`.

## Renderer

The renderer must include these header fields when present:

```text
maildown
messageId
ts
from
to
cc
bcc
subject
type
priority
thread
replyTo
files
role
sigAlg
sigKey
sig
pqSigAlg
pqSigKey
pqSig
```

The renderer should write:

```text
---
<header>
---
<body>
```

## Canonical Signing Payload

The signing payload must be:

```text
maildown-signature-v1
<canonical-json>
```

The canonical JSON object must contain:

```text
maildown
messageId
ts
from
to
cc
subject
type
priority
thread
replyTo
files
role
body
data
```

Rules:

- sort object keys lexicographically
- omit fields with `undefined` values
- preserve array order
- encode primitives with JSON rules
- normalize `to`, `cc`, and `files` to arrays
- normalize missing `data` to `null`
- normalize missing `maildown` to `1`

`bcc` must be excluded from the signature version 1 signing payload. A
closed-circle implementation must reject accepted-delivery messages with any
`bcc` value until a future signature version defines signed recipient-private
metadata.

## Signature Verification

An implementation must verify:

- Ed25519 with SPKI DER public keys
- ML-DSA-65 with raw public keys
- key id matches an active public key for the sender
- key algorithm matches the signature algorithm

When `requireHybridSignatures` is true, both Ed25519 and ML-DSA-65 signatures
must verify.

## Envelope Encryption

Envelope encryption must:

- generate a random 32-byte content key
- encrypt the rendered Markdown payload with AES-256-GCM
- generate a 12-byte nonce for each AES-GCM record
- store GCM tag separately as base64
- use ML-KEM-768 to wrap the content key for each recipient
- derive wrapping keys with HKDF-SHA-256
- use the envelope AAD for payload encryption
- use `key:<identity>:<keyId>` as AAD for wrapped content keys

HKDF input:

```text
ikm = ml_kem_shared_secret
salt = envelope_aad
info = "maildown-envelope-v2:<identity>:<keyId>:<base64_sha256_kemCipherText>"
len = 32
```

## Envelope Decryption

Envelope decryption must:

- reject unsupported envelope versions
- select the recipient entry by local identity
- require recipient algorithm `ml-kem-768`
- decapsulate with the local ML-KEM-768 secret key
- derive the same wrapping key
- decrypt the content key
- decrypt the payload
- parse and verify the plaintext message before trusting it

## Policy

Closed-circle policy must reject a message when:

- sender is not a circle member
- recipient is not a circle member
- sender identity is missing or inactive
- sender lacks send permission for the recipient
- required signatures are missing or invalid

## Reference Commands

Run the core conformance smoke:

```sh
cd tvl-maildown
npm test
```

Run the MCP smoke:

```sh
cd tvl-maildown-mcp
npm test
```

Run a deployed smoke:

```sh
cd tvl-maildown-mcp
MAILDOWN_LOGIN_PASSWORD=<password> npm run smoke:remote
```

## Fixture Status

The repository does not yet include fixed conformance fixtures. The next useful
step is to add:

- one signed plaintext message
- one envelope with one recipient
- one envelope with two recipients
- one policy rejection case
- one malformed envelope case
