1. Two-level approval

A change originating from a requestor passes two gates:

  1. Intent (requestor). The requestor reviews the preview — the actual rendered page, not a diff — and confirms: “this is what I meant”. The confirmation is a reply to the agent’s message (typically email), quoting the nonce.
  2. Publication (publisher). A human authorises live publication by merging the PR. Merging is the only mechanism that changes production (spec/05).

Owner-maintained sites where requestor = publisher = one human collapse both levels into the PR review — legitimately: the dial records it, and the PR trail still shows who did what.

2. The nonce

  • The editor agent generates a random nonce per proposed change and embeds it in its reply to the requestor (“Reply APPROVE to confirm, or tell me what to change”).
  • An approval is valid only if: it comes from the allowlisted sender address, it is in the same thread, and it quotes the nonce.
  • A nonce is single-use and expires (SHOULD: 14 days).

Why: this binds an approval to a specific proposed change in a specific thread, closing the forge-a-reply and approve-the-old-draft paths.

3. The trust dial

The dial maps (sender × change class) to approval requirements. It is data (trust.yaml), versioned in the site repository; changing the dial is a structural change and goes through the full pipeline.

3.1 Change classes

Class Examples
typo fixing a misspelling, broken link
content-update edit to an existing page’s body; setting, moving, or clearing a featured flag (spec/02 §9); repointing an existing menu item’s ref or editing its label
event-update date/time/venue change
new-content a new post, news item, document
structural new page type, menu structure changes (adding, removing or reordering items), template/block changes, slug changes, trust dial changes, chrome changes

3.2 Dial levels

Level Intent gate Publication gate Audit
L1 dual (default) requestor nonce-approval publisher merge full
L2 delegated requestor nonce-approval alone triggers merge publisher reviews post-merge (notification within the review SLA) full, marked post-hoc
L3 session pre-arranged agent session, human present human present full, PR opened in-session

Constraints:

  • L2 is bounded: it MUST apply only to typo and event-update classes from senders explicitly marked for it, and MAY be revoked per-sender at any time.
  • structural changes are L1 always, for every sender including the owner’s own agent sessions (Constitution §4).
  • There is no auto-publish-with-no-human-involvement level. L3 keeps a human present by construction.

Why: the bottleneck failure mode (every typo waits on one human) must not be solved by quietly deleting the review — it is solved by moving the publisher’s review after publication for the lowest-risk classes, with the post-hoc nature recorded.

4. trust.yaml

trust: z.object({
  allowlist: z.array(z.object({
    address: z.string().email(),          // the sender's verified address
    name: z.string().optional(),          // human-readable
    classes: z.array(changeClass),        // classes this sender may request
    dialLevel: z.enum(["L1", "L2"]).default("L1"),
  })),
  nonce: z.object({
    ttlDays: z.number().default(14),
  }).optional(),
  agents: z.array(z.object({              // agent principals for sessions
    principal: z.string(),
    dialLevel: z.enum(["L3"]),
  })).optional(),
}).strict()

5. The audit record

Every merged PR records, in its body, a structured block:

requested-by:  <allowlisted address or session principal>
classification: <class> / <targets>
intent:        <nonce-approval date + thread link, or "session">
published-by:  <publisher, or "L2-post-hoc: pending">

Git makes this immutable. A conformance check (§07) verifies the block is present on every merged content PR.

6. Revocation and incident path

  • A sender’s trust may be reduced (L2 → L1) or removed by a one-line PR; the dial change itself follows structural handling but MAY be fast-tracked by the publisher when revoking.
  • Suspected defacement or spoofing: revoke, audit git log for the sender’s merged PRs, revert affected commits — a revert PR, never a hand-edit of production storage.