The Quorum mark: two signers, one agreement
Digital-Pulse-HQ/quorum · Open source · Apache-2.0

A sign-off check for teams of AI agents.

You set the rule, such as reviewed by a different AI from the one that wrote it. Quorum refuses any approval that breaks the rule or changed after it was signed. Free, open source, and it runs in any CI.

npm install --save-dev @digital-pulse-hq/quorum

npm ↗GitHub ↗

The problem

You run more than one agent.

One of them writes a change. Another one approves it. Later you find out what “approves” meant.

  • The reviewer was the same model as the author, under a different name.
  • The approval was a comment, and comments can be edited after the fact.
  • The review never actually ran. The green came from nothing having been checked.
  • The approval was for an earlier version of the change, and nobody noticed the change moved.

None of these are exotic. We built a probe for every one of them while running our own crew. The common thread is that the thing saying “approved” was also the thing being trusted to say it.

What Quorum is

A command-line verifier and a signed record format.

  1. 01The agent that produced the work signs what it produced: which change, which attempt, who it is.
  2. 02A reviewer, from a different model family if your policy requires it, signs the verdict on top, and that second signature covers the first one. The reviewer's identity, the verdict, the pull-request number and the evidence document's digest are all under it, so none of them can change without breaking it. Whether the pull request you name and the evidence document you hand in must match them is a rule your policy states.
  3. 03You run quorum verify and hand it the facts you know: which commit, which attempt, which PR. Quorum checks the record against those facts. It never reads the record to check the record.

If anything your policy requires is missing, altered, stale or unenrolled — or, under a policy that requires cross-family review, same-family — the answer is no, and the exit code says so.

What you get
A yes or no you can re-run.
From committed files alone, in any CI, on any machine with Node.js 20.11 or later. Quorum has no dependencies beyond Node.js itself.
“Nothing was checked” is not “passed.”
Quorum reports two different refusals: UNKNOWN when it had nothing to compare, INVALID when it compared and rejected. Both fail. Conflating them is how a review that never ran comes to look like one that passed.
A self-description cannot be upgraded to proof.
Every identity carries a trust label, declared or observed, under a signature. The producer's label is inside the producer's signature, which the reviewer's signature covers in turn; the reviewer's label is inside the reviewer's signature. A policy that demands observed refuses declared evidence rather than waving it through.
The commit is the record. A comment is a view.
Quorum can render a readable summary for a pull request, and it labels that summary non-authoritative. The summary points at the signed record. It is never accepted as the record.
Check it

Every claim, and where its evidence lives.

A claim is listed here only if you can open its evidence yourself, at a commit that cannot change under you. A claim that fails that test is cut, not softened.

  1. Claim 01
    Open source under Apache-2.0.
    Where to look
    LICENSE and NOTICE at the repository root; "license": "Apache-2.0" in package.json.
    Evidence
    Open evidence ↗
  2. Claim 02
    Under a policy that requires cross-family review, a review signed by the same model family as the author is refused.
    Where to look
    Guard Q16_FAMILY_INEQUALITY (require_family_inequality) in src/verify.mjs; schema/quorum-policy-1.schema.json, where every policy must state require_family_inequality; fixture trailer-2-repaired-verdict (refused by Q16_FAMILY_INEQUALITY) in fixtures/index.json.
    Evidence
    Open evidence ↗
  3. Claim 03
    The reviewer’s signature covers the verdict.
    Where to look
    SPEC.md §3; guard Q12_REVIEWER_SIGNATURE in src/verify.mjs; fixture mutated-verdict (refused by Q12_REVIEWER_SIGNATURE).
    Evidence
    Open evidence ↗
  4. Claim 04
    Changing anything in the signed record is refused.
    Where to look
    Fixtures mutated-pr (refused by Q12_REVIEWER_SIGNATURE), mutated-commit (refused by Q07_ARTIFACT_BINDING), mutated-attempt (refused by Q06_ATTEMPT_BINDING), mutated-reviewer-principal (refused by Q11_REVIEWER_ENROLMENT), mutated-producer-signature (refused by Q10_PRODUCER_SIGNATURE) and mutated-policy-version (refused by Q05_POLICY_VERSION) in fixtures/index.json.
    Evidence
    Open evidence ↗
  5. Claim 05
    The producing agent signs which change, which attempt, and who it is.
    Where to look
    SPEC.md §3; src/envelope.mjs.
    Evidence
    Open evidence ↗
  6. Claim 06
    The reviewer’s signature covers the producer’s signature.
    Where to look
    SPEC.md §3; reviewerPayload in src/envelope.mjs, which puts the producer’s signature inside what the reviewer signs; guard Q12_REVIEWER_SIGNATURE in src/verify.mjs. Fixture mutated-producer-signature (refused by Q10_PRODUCER_SIGNATURE) is stopped earlier, at the producer’s own signature, so it does not exercise this.
    Evidence
    Open evidence ↗
  7. Claim 07
    The pull-request number and the evidence document’s digest cannot move without breaking the reviewer’s signature; a policy that requires them also checks them against the pull request you name and the evidence document you hand in.
    Where to look
    reviewerPayload in src/envelope.mjs; fixture mutated-pr (refused by Q12_REVIEWER_SIGNATURE). Guards Q17_PR_BINDING (require_pr_binding) and Q18_EVIDENCE_BINDING (require_evidence) in src/verify.mjs; fixtures deleted-evidence (refused by Q18_EVIDENCE_BINDING) and swapped-evidence (refused by Q18_EVIDENCE_BINDING).
    Evidence
    Open evidence ↗
  8. Claim 08
    You supply the commit, attempt and pull request; Quorum never reads the record to check the record.
    Where to look
    The USAGE block in bin/quorum.mjs (--commit, --attempt, --pr); SPEC.md §5; the self-referential fail-open mutation in test/invariant-mutations.test.mjs.
    Evidence
    Open evidence ↗
  9. Claim 09
    Anything your policy requires that is missing, altered, stale or unenrolled exits non-zero, and so does same-family input under a policy that requires cross-family review.
    Where to look
    README.md ("Exit `1` on any refusal"); guard Q00_INPUT_PRESENT in src/verify.mjs; fixtures unenrolled-producer (refused by Q09_PRODUCER_ENROLMENT), stale-attempt (refused by Q06_ATTEMPT_BINDING) and malformed-envelope-json (refused by Q01_ENVELOPE_JSON).
    Evidence
    Open evidence ↗
  10. Claim 10
    The verifier runs from committed files alone, in an empty directory, on Node 20.11 or later.
    Where to look
    test/clean-project.test.mjs; "engines" in package.json.
    Evidence
    Open evidence ↗
  11. Claim 11
    No dependencies: nothing is imported except Node’s own modules and Quorum’s own files.
    Where to look
    "dependencies": {} in package.json; SPEC.md §8; the import lines of src/ and bin/.
    Evidence
    Open evidence ↗
  12. Claim 12
    Two refusals, both failures: UNKNOWN when there was nothing to compare, INVALID when it compared and rejected.
    Where to look
    SPEC.md §5, "Two refusal states".
    Evidence
    Open evidence ↗
  13. Claim 13
    Every identity carries a declared-or-observed trust label under a signature: the producer’s inside the producer’s signature, which the reviewer’s signature covers; the reviewer’s inside the reviewer’s signature.
    Where to look
    SPEC.md §4; schema/quorum-envelope-1.schema.json; producerPayload and reviewerPayload in src/envelope.mjs.
    Evidence
    Open evidence ↗
  14. Claim 14
    A policy that demands observed refuses declared evidence.
    Where to look
    Guard Q15_TRUST_POLICY in src/verify.mjs; fixture declared-under-observed-policy (refused by Q15_TRUST_POLICY), with the synthetic positive control observed-enrolment-under-observed-policy (passes).
    Evidence
    Open evidence ↗
  15. Claim 15
    The readable pull-request summary is labelled non-authoritative and is never accepted as the record.
    Where to look
    SPEC.md §6; src/render.mjs; "a rendered PR comment is not accepted as verification input" in test/invariants.test.mjs.
    Evidence
    Open evidence ↗
  16. Claim 16
    Every real enrolment we operate is declared; observed is exercised only by the test suite.
    Where to look
    fixtures/README.md; fixture observed-enrolment-under-observed-policy (passes).
    Evidence
    Open evidence ↗
  17. Claim 17
    Quorum does not yet gate merges in our own repository; the required check exists and is tested.
    Where to look
    README.md, Status; ci/required-check.mjs and test/required-check.test.mjs.
    Evidence
    Open evidence ↗
What a pass does not mean

A pass means the configured conditions were met. It does not mean the work is correct, and it does not mean the reviewer was diligent. Two things are true today and stay on the page:

  • Every real enrolment we operate is declared. A model family written in a registry is a self-description. Quorum's observed level exists and is exercised by the test suite, but no production source emits it yet.
  • Quorum does not yet gate merges in our own repository. The required check exists and is tested; it is not enforced, because no real key is enrolled. We say that here rather than let a page imply it.
Where it fits

The Agent Client Protocol standardises how one agent is driven inside one session. Durable records and governance are outside its scope. Digital-Pulse-HQ/quorum adds those controls above the session; it complements ACP and does not replace it. The idea of a separate attester is not ours. Quorum is a working implementation of it that you can read, run, and refuse.

Read the ACP scope ↗
One next step

Install it where your agents coordinate.

npm install --save-dev @digital-pulse-hq/quorum