A person signs what was agreed, by exact digest, once for many; an agent signs only as itself, under a grant that expires and carries no act of the person
Accepted
Context
adr-130 fixes the FORM of a signature (an envelope of subjects by digest and signatures by scheme). It leaves out on purpose the ACT: where a signature is given, by whom, how the key is kept and who may reach it. That is this record.
What the protocol already holds, measured on 2026-10-07:
- A person's signature is identity. The hat `maintainer` «commits to what a Work Order changes in this level: ratifies it and signs its delivery receipt» (.governance/authority/registration.ncl:7). Both acts are verdicts: the terms were agreed, the delivery is accepted. - adr-120 `ratification-needs-a-human-act`: the key that ratifies a SOW or signs a receipt cannot be used by an agent process without a human act. adr-122 `a-person-s-act-is-never-granted-to-an-agent`: a decision that allows a person's own act to an agent is refused by the contract. - Agents already sign, as themselves, in one place: commits. `agent-git.nu` signs with the agent's own key declared per machine in ~/.config/ontoref/agent.ncl and refuses when it is absent, because git's fallback identity is the user's (.ontoref/reflection/bin/agent-git.nu:11-25). Identity is separated; authorization is not recorded — nothing signed by the person says what the agent was allowed to do. - adr-121 `agents-never-sign` («no protocol module or script invokes minisign to sign») belongs to a record already superseded by adr-122. - The level ratifies offline with minisign today (.governance/authority/offline.ncl), and gov declares signatio as an optional requirement (code/domains/gov/domain.ncl, adr-122 `the-authority-is-an-optional-requirement-of-gov`). - The agent's identity is already declared per machine, never in a project: ~/.config/ontoref/agent.ncl carries its name, email and keys (`keys.sign` for commits, `keys.auth` for push), and agent-git.nu refuses when the file or a field is missing rather than falling back to the user's identity (.ontoref/reflection/bin/agent-git.nu:17-25, 60-80). The registry credential vault of adr-017 is a different thing — registry and cosign credentials for publication — and declares no signing key nor any `agents` recipient group for this project (.ontoref/project.ncl:17-29). - Where terms are agreed before they are signed, the agreement already names them by digest. SpecDossier records an agreed dossier as `agreed = { digest, by, on, points, preflight? }`, the digest being the sha256 of the governed document's bytes (specdossier code/schemas/dossier.ncl; `digest_of`, specdossierd/src/levels.rs:84) — the same sha256 ontoref computes for a SOW (`target.digest`, ratify.nu:82). land-sow already moves every `<sow>.ncl.*` file beside a draft into the level with it (ratify.nu:600-604). Nothing in ontoref reads an agreement today.
The cost that opened the question: one human act per SOW and per receipt, each a passphrase typed for one file («cansados de firmar cada SOW»). Two answers were examined and refused on the way here: automating the person's signature (a wrapper reading the passphrase from a password manager) makes the person's key usable by any process of the same user while it is unlocked, which is the reach adr-120 and adr-121 deny; and an open delegation by which an agent ratifies what falls in a session's scope would have the agent ratify terms nobody agreed to — the principal's words: «¿cómo vamos a ratificar decisiones que no han sido consensuadas y/o acordadas previamente?».
Decision
TWO KINDS OF SIGNATURE, TWO KINDS OF KEY, NEVER CROSSED.
A PERSON'S ACTS ARE SIGNED ONLY BY THE PERSON, ONLY OVER EXACT DIGESTS. Ratifying a SOW and accepting a delivery (signing its receipt) are the person's verdicts. The person signs them with their own key, and the signature names each subject by its exact digest. One signature may name many subjects — an adr-130 envelope with N subjects and one signature — so one human act ratifies or accepts N documents the person has seen. A verdict over a scope, a pattern or «whatever this session produces» is not a verdict and is refused: what was not agreed cannot be ratified, and what was not seen cannot be accepted. The person's key never leaves the person's custody; minisign offline is enough for it, and it is used rarely.
AN AGENT SIGNS ONLY AS ITSELF, UNDER A GRANT. An agent has its own key, as it already has for commits, kept outside every repository and NAMED per machine in ~/.config/ontoref/agent.ncl beside its commit keys: agent.ncl holds where the key is (a path, as `keys.sign = "~/.ssh/…"` already is), never the key material, so the file can leak a path and an email and never a secret. The key is used only by the CLI process that signs, never by the daemon. A machine whose agent.ncl declares no governance signing key has no agent signer, and the flow refuses rather than falling back to any other key. Its signature attributes; it never authorizes. With it the agent assembles envelopes, presents the person's signatures to the level or to the authority, records acts in the ledger, signs traces and runs. Each such signature is valid only under a GRANT the person signed: the agent's key id, the kinds of the agent's own acts it may sign, the scope (a session, a task, a SOW id), and an expiry. A grant never lists a person's act: a grant naming ratification or acceptance is refused by the contract, whatever its signature.
TO DELEGATE IS TO RATIFY A RULE, NEVER ITS INSTANCES. The legitimate form of «letting the agent act without asking each time» is a standing SOW: its `scope`/`exclude` and its contracts (sow.ncl:62-63, the standing-law fields) agree in advance on a space — within these paths, every change satisfies these contracts. The person ratifies that SOW once, by its exact digest, like any other. What the agent then does inside that space is NOT ratified: it is VERIFIED against the standing SOW's contracts, and the agent signs it as itself under a grant whose scope names that standing SOW. The agreement exists; it was given over the rule. An act that falls outside the space is not covered by it and is not ratified by having happened: it is recorded as a crossing (adr-121 `crossing-asks-the-other-steward`) and the person decides on it afterwards, by digest, over what they can now see.
THE GATE READS BOTH. An agent's signature counts when it verifies AND a grant signed by the person, in force when the act was recorded in the ledger, covers that act and that subject. A person's verdict counts when the person's signature names the subject's exact digest. Expiry, like adr-130's floor, is judged against the ledger's record of the act, never against the signer's own timestamp: an expired grant stops new acts and does not undo recorded ones.
WHAT WAS AGREED IS NAMED BESIDE THE SOW, AND WHAT IS SIGNED MUST BE IT. When a SOW's terms were agreed in a dossier, the agreement is named by a sidecar `<sow>.ncl.agreed.json` — the dossier's path, its id, the agreed digest, by and on — kept beside the draft like its signatures, and carried into the level by land-sow with them. A SOW never carries its agreed digest in its own bytes: a file cannot name the digest of itself, for the same reason sow.ncl admits no signature field. land-sow, when the sidecar is present, re-reads the dossier and lands only if the sha256 of the bytes it moves is the digest the dossier agreed; otherwise it refuses and says the document diverged from what was agreed. Without a sidecar nothing changes: agreeing in a dossier is a layer a level adds, not a gate every SOW passes. registry-sow reads the sidecar and shows it as `agreed_in`; it does not change what it says of a SOW's place or file, which SpecDossier reads by id. The dossier is read as published data — its format is SpecDossier's, ontoref imports none of its code or schema. Once adr-130's envelope exists, the agreed dossier is a subject the person's envelope can name beside the SOW, by its own digest; the sidecar is what lets the agreement be checked before that envelope exists.
NOTHING HERE IS A REQUIRED DEPENDENCY. A level with no agent signing key keeps today's flow: the person signs each file. signatio stays an optional requirement of gov; a level that uses it presents the same signatures to it and keeps its attestations. A custodian process that holds the agent's key under its own OS identity (the signatio-signer pattern) may be added later for a level whose exposure warrants it; the model does not change, only where the agent's key lives.
Constraints
- Hard A ratification or an acceptance counts only when the person's signature names the subject's exact digest; one naming a scope, a pattern or a session is refused
- Hard A grant that lists ratification or acceptance among the acts it allows is refused by the grant contract, whatever its signature
- Hard An agent's signature moves nothing unless a grant signed by the person, in force when the act was recorded in the ledger, covers that act and that subject
- Hard A SOW whose draft carries an `<sow>.ncl.agreed.json` lands only when the sha256 of its bytes equals the digest the named dossier agreed for it; a mismatch, a missing dossier or an unagreed one is refused, and the SOW's own bytes never carry that digest
- Hard gov keeps signatio as an optional requirement: the act defined here needs no authority
- Hard The agent's governance signing key is referenced from agent.ncl by a path outside every repository; a value that is key material, or a path inside a git work tree, is refused by the reader
- Hard The agent's signing key is decrypted only in the CLI process that signs, never by the daemon
Alternatives considered
- A wrapper that reads the person's minisign passphrase from KeePassXC and signs — rejected: While the password manager is unlocked, any process of the same user — an agent session included — can produce the person's signature. It removes the human act instead of batching it, against adr-120 and adr-121.
- An open delegation: the agent ratifies or accepts whatever falls in a session's scope — rejected: It ratifies terms nobody agreed to and accepts deliveries nobody saw. A verdict over a scope is not a verdict; adr-122 refuses granting a person's act to an agent.
- A notary holding the person's own key and signing on their behalf — rejected: The person's signature is identity; a signature made by someone else with it is false attribution, the exact failure agent-git.nu was written to make impossible for commits.
- Make signatio required and move every signature to it — rejected: It turns an optional requirement into a mandatory one for every level (adr-122 keeps it optional), and the offline level that ratifies the authority's own repairs could no longer ratify.
- A field in the Sow naming the dossier that agreed it — rejected: It cannot carry the agreed digest (a file naming the digest of its own bytes), so the digest would live elsewhere anyway; it forces the dossier to exist before the bytes it agrees on, and every consumer copying sow.ncl would pay for a field few SOWs use.
- An `Agreed` event in the level's ledger — rejected: verify-sow trusts no ledger event and rebuilds the evidence from files; the event would be a copy of what the dossier holds, and the gate would have to re-read the dossier regardless.
- Require a hardware or browser presence provider for the person's act — rejected: Not every surface has a browser, and a mandatory provider is a mandatory dependency: without it, no signature. minisign offline already gives a human act; the cost to remove was its repetition, not its kind.
Related ADRs
ADR-017 · ADR-066 · ADR-120 · ADR-121 · ADR-122 · ADR-124 · ADR-130
· reads
Perspectives