adr: a decided record needs assent from a second account (four-eyes, token comment) #528

Closed
opened 2026-09-16 18:08:44 +00:00 by hexajon · 2 comments
Owner

Problem

adr check verifies that an undecided record is tracked by open issues (#483). It does not verify how a record became decided. Today one actor writes the record, sets status: "accepted", and merges it. When that actor is an agent, the record is self-approved: it says a decision was made, and nothing outside the file says who made it.

That is the failure I care about. An over-eager agent archives its own reasoning as settled doctrine, and the tree then reads as if I ruled on it.

Rule

A record in a DECIDED status must link assent from an account that is not the record's author.

  • Decided statuses: accepted, rejected, deprecated, superseded by ADR-NNNN.
  • Assent evidence: a comment on an issue the record links under ## More Information, carrying the exact token ADR-ACCEPT <record> (or ADR-REJECT <record> for rejected).
  • <record> is the four-digit number, optionally prefixed by the repo (0007 or rubric/0007). A prefix that names a different repo does not match.
  • The record's author is the author of the commit that set the decided status.
  • The check fails when the token is absent, when no linked issue carries it, or when the token comment's author equals the record's author.

Four eyes, and no configuration. There is no decider allowlist, no named-human setting, nothing a project has to set up to be protected. The property that binds is authorship, which the writing account cannot forge.

Why a token and not a phrase

I considered matching a list of words (approved, ratified, looks good). It fails in the dangerous direction: "this looks good but the second option is wrong" reads as assent, silently. An exact token is typed only on purpose, and a missing token fails loudly.

Non-goals

  • No branch protection or required review. A record must be able to land and merge without waiting on a human; only the STATUS is gated, never the merge.
  • No decider allowlist or named-account configuration.
  • No phrase or sentiment matching.
  • This is not a defence against an actor holding my credentials. It stops self-approval, not impersonation.

Acceptance criteria

  • A decided record with a valid token comment by another account passes.
  • Each of these fails, naming the file and the rule: no token anywhere on the linked issues; a token naming a different record; a token whose comment author equals the record's author; a decided record with no linked issue at all.
  • ADR-REJECT is required for rejected; ADR-ACCEPT for the other decided statuses.
  • --skip-remote-issues skips this rule with the existing skip line, since it needs the forge.
  • A record whose status is proposed is unaffected.
  • madt_help topic=adr, the CLI help, and the README document the token and the four-eyes rule.
  • Tests cover every failure line above, through the existing fake forge client.

Out of scope

  • A CLI action that writes the status flip. Status stays hand-edited.
  • Batch or range tokens covering several records in one comment.
## Problem `adr check` verifies that an undecided record is tracked by open issues (#483). It does not verify how a record became decided. Today one actor writes the record, sets `status: "accepted"`, and merges it. When that actor is an agent, the record is self-approved: it says a decision was made, and nothing outside the file says who made it. That is the failure I care about. An over-eager agent archives its own reasoning as settled doctrine, and the tree then reads as if I ruled on it. ## Rule A record in a DECIDED status must link assent from an account that is not the record's author. - Decided statuses: `accepted`, `rejected`, `deprecated`, `superseded by ADR-NNNN`. - Assent evidence: a comment on an issue the record links under `## More Information`, carrying the exact token `ADR-ACCEPT <record>` (or `ADR-REJECT <record>` for `rejected`). - `<record>` is the four-digit number, optionally prefixed by the repo (`0007` or `rubric/0007`). A prefix that names a different repo does not match. - The record's author is the author of the commit that set the decided status. - The check fails when the token is absent, when no linked issue carries it, or when the token comment's author equals the record's author. Four eyes, and no configuration. There is no decider allowlist, no named-human setting, nothing a project has to set up to be protected. The property that binds is authorship, which the writing account cannot forge. ## Why a token and not a phrase I considered matching a list of words (`approved`, `ratified`, `looks good`). It fails in the dangerous direction: "this looks good but the second option is wrong" reads as assent, silently. An exact token is typed only on purpose, and a missing token fails loudly. ## Non-goals - No branch protection or required review. A record must be able to land and merge without waiting on a human; only the STATUS is gated, never the merge. - No decider allowlist or named-account configuration. - No phrase or sentiment matching. - This is not a defence against an actor holding my credentials. It stops self-approval, not impersonation. ## Acceptance criteria - A decided record with a valid token comment by another account passes. - Each of these fails, naming the file and the rule: no token anywhere on the linked issues; a token naming a different record; a token whose comment author equals the record's author; a decided record with no linked issue at all. - `ADR-REJECT` is required for `rejected`; `ADR-ACCEPT` for the other decided statuses. - `--skip-remote-issues` skips this rule with the existing skip line, since it needs the forge. - A record whose status is `proposed` is unaffected. - `madt_help topic=adr`, the CLI help, and the README document the token and the four-eyes rule. - Tests cover every failure line above, through the existing fake forge client. ## Out of scope - A CLI action that writes the status flip. Status stays hand-edited. - Batch or range tokens covering several records in one comment.
Author
Owner

Single-identity repositories

Some projects have exactly one actor. One maintainer, one account, every commit and every comment under that name. The rule cannot bind there: the record's author and the assent comment's author are the same account by construction. SLSA takes the same position on two-party review, which it keeps as a separate policy rather than a level, so levels 1 to 3 stay reachable by a single-maintainer project (https://slsa.dev/spec/v1.2/source-requirements).

So check reports "not applicable" there instead of failing. Two things stop that from becoming a hiding place.

The exemption is computed, never configured. No flag, no setting. check collects the distinct actors it can see: the authors of the commits that touched each record, and the authors of the comments on the linked issues. One distinct actor means not applicable. Two or more means the rule is enforced from then on, including on records written while the repository was still single-identity, and a decided record whose assent comment shares its author fails like any other violation. A project cannot declare itself single-identity. It either is or it is not, by its own history.

That is the part that matters for automation. An agent with its own account cannot quietly do everything under the maintainer's name to stay exempt: one use of its own account, anywhere in the repository, turns the rule on for good. Using the maintainer's credentials instead is impersonation, which the issue already lists as out of scope.

It says so on every run, and names the way out. The check prints its posture rather than staying silent:

adr: assent rule ENFORCED (3 decided records, each assented by a second account)

or

adr: assent rule NOT APPLICABLE - every record and every linked comment in this
     repository is authored by "<account>", so a second account cannot be checked.
     Give automation its own forge account to make assent verifiable.

The second form is a warning on every run, not a quiet skip. A project running automation under the maintainer's account gets told, every time, that it is one account away from a real check.

Acceptance criteria, added to the list in the body

  • A repository with one distinct actor reports NOT APPLICABLE with the warning text, and exits zero.
  • A repository with two or more distinct actors enforces the rule, including on records written while it was still single-identity.
  • The posture line appears on every check run in MADR mode, in both states.
  • --skip-remote-issues reports that the posture could not be determined, since the actor set needs the comments.
## Single-identity repositories Some projects have exactly one actor. One maintainer, one account, every commit and every comment under that name. The rule cannot bind there: the record's author and the assent comment's author are the same account by construction. SLSA takes the same position on two-party review, which it keeps as a separate policy rather than a level, so levels 1 to 3 stay reachable by a single-maintainer project (https://slsa.dev/spec/v1.2/source-requirements). So `check` reports "not applicable" there instead of failing. Two things stop that from becoming a hiding place. **The exemption is computed, never configured.** No flag, no setting. `check` collects the distinct actors it can see: the authors of the commits that touched each record, and the authors of the comments on the linked issues. One distinct actor means not applicable. Two or more means the rule is enforced from then on, including on records written while the repository was still single-identity, and a decided record whose assent comment shares its author fails like any other violation. A project cannot declare itself single-identity. It either is or it is not, by its own history. That is the part that matters for automation. An agent with its own account cannot quietly do everything under the maintainer's name to stay exempt: one use of its own account, anywhere in the repository, turns the rule on for good. Using the maintainer's credentials instead is impersonation, which the issue already lists as out of scope. **It says so on every run, and names the way out.** The check prints its posture rather than staying silent: ``` adr: assent rule ENFORCED (3 decided records, each assented by a second account) ``` or ``` adr: assent rule NOT APPLICABLE - every record and every linked comment in this repository is authored by "<account>", so a second account cannot be checked. Give automation its own forge account to make assent verifiable. ``` The second form is a warning on every run, not a quiet skip. A project running automation under the maintainer's account gets told, every time, that it is one account away from a real check. ## Acceptance criteria, added to the list in the body - A repository with one distinct actor reports NOT APPLICABLE with the warning text, and exits zero. - A repository with two or more distinct actors enforces the rule, including on records written while it was still single-identity. - The posture line appears on every `check` run in MADR mode, in both states. - `--skip-remote-issues` reports that the posture could not be determined, since the actor set needs the comments.
Author
Owner

Decided in 0047-decided-record-needs-assent-from-a-second-contributor (ADR 0047). The record holds the design, with these changes from the text above: the author of a decided status is the forge account that opened the PR that set it; only accounts with more than read access to the repo count as contributors; a single-contributor repo reports the rule as not applicable, and a plugin hook asks the person for the token before an agent can commit the change; ADR-DEPRECATE NNNN covers a deprecated record. The build gets its own epic.

Decided in 0047-decided-record-needs-assent-from-a-second-contributor (ADR 0047). The record holds the design, with these changes from the text above: the author of a decided status is the forge account that opened the PR that set it; only accounts with more than read access to the repo count as contributors; a single-contributor repo reports the rule as not applicable, and a plugin hook asks the person for the token before an agent can commit the change; `ADR-DEPRECATE NNNN` covers a deprecated record. The build gets its own epic.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sixfold-space/madtea#528
No description provided.