No description
  • Rust 49.5%
  • Nickel 23.7%
  • NCL 12.6%
  • Shell 8.1%
  • Nushell 5.1%
  • Other 1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
claude-bot c0c9c098ee
style: cargo fmt after the rename; imports only
Renaming mandatum_authority to signatio_attest moved it in rustfmt's order, and
the shorter paths fit on one line again. No other change: clippy -D warnings
and the workspace's tests pass as before.
2026-09-24 23:28:38 +01:00
.cargo
.governance feat(boot): service status probes the signer and the daemon, not only their units 2026-09-24 23:26:15 +01:00
crates style: cargo fmt after the rename; imports only 2026-09-24 23:28:38 +01:00
examples refactor: renamed from mandatum to signatio; the pack names no domain 2026-09-24 14:11:18 +01:00
install feat(boot): service status probes the signer and the daemon, not only their units 2026-09-24 23:26:15 +01:00
justfiles refactor: renamed from mandatum to signatio; the pack names no domain 2026-09-24 14:11:18 +01:00
pack refactor: renamed from mandatum to signatio; the pack names no domain 2026-09-24 14:11:18 +01:00
schemas refactor: renamed from mandatum to signatio; the pack names no domain 2026-09-24 14:11:18 +01:00
scripts feat(boot): service status probes the signer and the daemon, not only their units 2026-09-24 23:26:15 +01:00
.gitignore
Cargo.lock feat(boot): service status probes the signer and the daemon, not only their units 2026-09-24 23:26:15 +01:00
Cargo.toml refactor: renamed from mandatum to signatio; the pack names no domain 2026-09-24 14:11:18 +01:00
CHANGELOG.md docs: service status tells attending from loaded 2026-09-24 23:28:01 +01:00
justfile refactor: renamed from mandatum to signatio; the pack names no domain 2026-09-24 14:11:18 +01:00
README.md docs: service status tells attending from loaded 2026-09-24 23:28:01 +01:00

signatio

A signature grants a declared reach. signatio produces that grant and checks it.

An agent — usually an AI agent — works under a Work Order. A person signs the scope the agent may act within. signatio is the mechanism behind that signature: how it is produced without the agent ever holding a key, how anyone verifies it with public material only, and how a verifier decides whether the signer's roles cover what the scope asks for.

It is the signing capability that ontoref's gov domain exercises: an optional requirement a project chooses by installing the pack and declaring the provider (ADR-001; ontoref ADR-122). It is consumed by contract — a project depends on the shape of what it produces, not on its implementation — and it is its own constellation: this repository is its code member.

Formerly mandatum. Until 2026-09-24 this project was named mandatum and was a member of the stratumiops constellation. Everything was renamed — the crates, the binaries, the pack (share/signatio), the launcher (signatio-boot), the service units (dev.signatio.*), the Cedar namespace (Signatio::) and the signature domain tags (signatio-*-v1, changed before any attestation was issued) — except the Work Orders signed before the rename: they keep their names (wo-mandatum-*, wo-mandatumd-*), in .governance/ and wherever they are cited, because renaming a signed file's content would invalidate its signature.

The contract

Attestation = subject_digest + scope + proof { scheme }

verify(Attestation) → { principal?, roles, ial, aal, isolation }
  • subject_digest — the digest of the Work Order being granted.
  • scope — the paths and aspects the grant covers.
  • proof — a signature, or any other proof, tagged with the scheme that produced it. Verification dispatches on the tag and fails closed on a scheme it cannot evaluate.
  • principal is optional. A verifier receives the attributes its decision reads, not necessarily who the signer is.

Three mechanisms, one responsibility each

  • custody answers who holds the key, and whether a person was present when it signed. It knows nothing about what is signed, or why.
  • verify answers whether a proof is valid for its subject and scope, using public material only. It knows nothing about private keys or roles.
  • authorize answers whether the signer's roles cover what is asked — each project's Cedar policies, evaluated over the principals and the hats they hold. It knows nothing about how the proof was produced.

verify and authorize touch public material only, so they may run anywhere: a daemon, a plugin, a CI job, the agent's own session. sign is packaged apart; where it may be installed is declared, never assumed.

Identity, roles, assignments

  • A key identifies a person — one authenticator per person, not one per area.
  • A role grants attributions: which surfaces and which aspects it governs.
  • An assignment binds persons to roles, and changes without touching any key.

A person with five responsibilities signs with one key; the verifier derives the roles. While one person holds every role the separation between roles is declarative; it becomes cryptographic when a second person holds a key.

Principles

  • Only a person signs. An agent never signs — not with a person's key, not with its own.
  • Keys stay out of the agent's reach, by mechanism, not by instruction. The agent reads public keys only.
  • Nothing grants itself. No actor widens its own authorization under any pretext.
  • The agent leaves a trace, not a signature. The trace authorizes nothing.
  • A signature authorizes a declared scope. Changing the scope is signing again.
  • Outside the scope, stop and ask the role that governs that surface — a matter of roles, not hierarchy.
  • Declared, never imposed. No mechanism is required. The floor is a software ed25519 key anyone can generate; hardware authenticators raise the assurance level. The attestation states what was used — scheme, assurance, whether the signing environment was shared with an agent — and the consumer's policy decides whether it is enough.
  • Exposure is declared per audience. What a signed artefact carries travels with it through every channel and cannot be withdrawn; which field reaches which audience is decided at issuance.

What signatio is not

  • Not a secrets store. sops, age and a vault's key-value engine decide who reads values; signatio decides who granted what.
  • Not identity or sessions. That is stratum-ui's auth; signatio complements it with authorization and attestation.
  • Not needed to check what it already attested. An attestation verifies offline with the authority's pinned public key, forever after; only new governance acts — tokens, verifications, decisions — need the service.

Status

signatio runs as an authority: a TLS service, a custodian for its key, and an administrator's CLI. Each piece was delivered under a signed Work Order in .governance/, with its receipt.

Crate What it is
signatio-verify Verification of a minisign proof over an attestation's canonical payload, with public material only. Links no library able to sign.
signatio-attest The core: verifying a person's signature once into a signed attestation, the authority's domain-separated signatures, and the Project and System chains of the log. Signs only through an AuthoritySigner it does not implement.
signatio-signer The custodian: holds the authority's key (a minisign -G -W key) in its own process and answers raw Ed25519 signatures on a local socket to one peer uid. Configured in Nickel; refuses to start on any config or key it cannot check.
signatio-custody The client of that socket, as the service links it. No signing library.
signatiod The service over TLS: tokens for registered clients; VerifySignature, Authorize (Cedar), Record, RegisterProject, Whoami, Holders, HatsOf, Log, RotationNotice; and the administration ops, PrepareRotation among them. Every answer signed through the custodian of the key in effect, every operation written to redb.
signatio-cli signatio: bootstrap of the first administrator, and administration over rustls — rotate and rotation-notice included; verify-connection checks a consumer's declaration against the contract and the running authority; probe-custodian and probe-authority tell whether an installed custodian and service attend.

Schemas: protocol.ncl (the authority's protocol: the generic core, the consumer Profile and the contracts that are functions of a profile), attestation.ncl (the Attestation contract), signer-config.ncl, signatiod-config.ncl and signatio.cedarschema. The protocol is signatio's and consumers import it, never copy it (ADR-001). The core names no consumer's vocabulary: each project registers its profile, and the authority applies it — actions, acts, statements, reserved hats, its Cedar schema. Reads and VerifySignature are granted by the authority's own permissions (authority.holders, authority.hats-of, authority.log, authority.verify-signature), which protocol.ncl exports as core_permissions; its ClientActions profiles kind states what a client may be allowed, and the core's put_client agrees with it.

connection.ncl is how a project or a service connects: what installing signatio takes (Installation), what an installed signatio hands over (Installed: the endpoint, the CA, the authority's key with its id and scheme, an administrator's client whose proof is a command or a path, never the secret) and what a consumer declares (Consumer: its profile, its registration with the out-of-band channel, its clients and where it pins keys). examples/consumer/ implements it with curl, openssl, minisign, jq and nickel only, and signatio verify-connection <declaration> --import-path <pack root>/schemas checks a declaration against the contract and the running authority: TLS with the declared CA, a token by each declared proof, every answer verified with the pinned key, the rotation notices as a chain.

signatio ships as an optional pack (ADR-001). just install puts signatiod, signatio-signer, signatio and the launcher signatio-boot into <prefix>/bin, and the pack root into <prefix>/share/signatio: the schemas and a manifest naming where they are (schema_path), which provides no domain. Every way the custodian and the service run goes through the launcher: signatio-boot layout prepares configs and directories from the operator's Installation (it never generates a key and refuses a CA certificate), signatio-boot service install|uninstall|restart|status owns their user units (launchd, systemd --user, restarted only on failure; status says whether each attends, not only whether it is loaded — the custodian by the key it answers, the daemon by an answer that key signed, or by its last refusal to start), signatio-boot handover writes the Installed a consumer receives — with the pack root and root_cmd, the command a consumer declares to find it on any host —, and signatio-boot root prints the pack root. pack/bundle.ncl declares the bundle in the shape of ontoref's ADR-038 content manifest.

The authority's key rotates as agreed with ontoref: a second signatio-signer holds the next key, a RotationNotice is signed by both custodians, and the service switches keys at effective_at — at once for a compromise. After the switch the operator points signer_socket at the new custodian and restarts.

The proof scheme of the authority is minisign. A person's signature is declared by scheme: minisign is built, ml-signify is recognised and refused with its own reason. sops and rops were considered and set aside: they encrypt, they do not attribute.

Using and operating it

How to use and how to operate signatio is kept as governed Q&A in signatio's spine, not in this file. From the constellation root, signatio/:

ontoref qa show signatio-operate-howto                      # install, run, associate a project
ontoref qa show signatio-admin-cli-howto                    # administer with the signatio CLI
ontoref qa show signatio-rotation-howto                     # rotate the authority's key
ontoref qa show signatio-verify-howto                       # verify an attestation
ontoref qa show signatio-governed-work-howto                # Work Orders here
ontoref qa show signatio-proof-scheme-not-secrets-custody   # why minisign

From signatio/, just lists the recipes:

just build | test | lint | fmt | lint-nickel | check-tools
just e2e | e2e-governance | e2e-admin | e2e-rotation | e2e-dev-stack   # end to end over TLS
just test-protocol | test-protocol-mutations | test-statement-differential   # protocol.ncl, and the core against it
just e2e-protocol                                # signatiod's answers checked against protocol.ncl
just test-connection | test-connection-mutations # connection.ncl: its oracle, the example consumer among it
just e2e-connection                              # the example consumer connects; verify-connection accepts and refuses
just install [prefix] [profile]          # the pack: four binaries → <prefix>/bin, the pack root → <prefix>/share/signatio
just pack-manifest                       # pack/bundle.ncl: it exports and what it names exists
just e2e-pack                            # installed, laid out, served as user services, connected from outside, removed
just dev-init; just dev-seed; just dev-signer; just dev-daemon   # a local stack in .dev/
just dev-next-signer; just dev-daemon .dev next; just dev-signatio .dev rotate --reason Compromised

Open

Recorded as open, not decided:

  • Whether SSHSIG over ed25519 enters as a second proof scheme.
  • How exposure per field and audience is expressed in the contract, and which unlinkable schemes (per-context keys, anonymous credentials, group signatures with a declared opener) enter as schemes.
  • Keeping a person's signing key out of an agent's reach by mechanism. The authority's key lives in its own process; running that process as its own system user is an operator's act, not yet done.
  • Who may assign hats within a project according to its own policy — today only the authority's administrators assign.
  • How signatio is packaged, installed as services and associated with an ontoref project beyond just install.
  • Assurance levels (presence, isolation) carried in the attestation.