mathema pin: human-verified decisions¶
The claim record answers what was verified and how hard. This answers the other question a reviewer, an auditor, or a regulator asks about a codebase agents help build: was the sign-off actually made by a person?
by: alone cannot answer it; a free string is typed by an agent as
easily as by a person. A PIN can: a person knows it, an agent does
not, so an acceptance (or an unlock) that carries the stamp
was authorised by someone at a keyboard, and the record proves it
rather than asserts it. The verification is written into the record
twice, permanently: the accepted block carries
verified_by: {method, key}, and so does every event in the claim's
acceptance_history, so the audit trail survives later re-decisions.
mathema pin set # choose a PIN (4-12 digits), prompted twice
mathema pin status # method and key id, never the secret
mathema pin rotate # replace it (verifies the current one first)
mathema pin remove # delete it (also verifies first)
Entirely optional: with no PIN set, nothing prompts and nothing changes. Set one and every acceptance path (claim, intent, scope intent, concepts) and every unlock asks for it before writing.
What it looks like¶
$ mathema accept funcs.settle nonneg --as evidence
accepting funcs.settle :: nonneg (verdict holds) as evidence, by Alonzo Church
- annotate nonneg as accepted evidence at n=130 (bound to form 3eb01e1d9919...)
write this acceptance? [y/N] y
PIN:
written: annotate nonneg as accepted evidence at n=130 (bound to form 3eb01e1d9919...)
and in the record (trimmed):
accepted:
as: "evidence"
at: "2026-09-24"
n: 130
form: "3eb01e1d9919"
by: "Alonzo Church"
verified_by:
method: "pin"
key: "a3f2c1"
verified_by names the method and the credential's key id. The
key id is why a PIN reset is auditable: rotating or replacing the
credential produces a new id, so an acceptance signed by a key that is
not the currently configured one is visible to any reviewer.
The rules that make it mean something¶
- The code is read from the controlling terminal only. A pipe, a
captured subprocess, a caller with no
/dev/tty: refused outright, never silently read from stdin.--yesstill skips the y/N prompt; it never skips the PIN.--format jsonwithout a terminal refuses to apply and says why (the plan-only preview still works). - The gate sits at the write boundary, not at the prompts. Every acceptance path funnels through one library chokepoint, so the JSON path, scripted use, and the concepts curation path (which previously wrote without any prompt) are all covered.
- Rotate and remove verify the current PIN first. Otherwise an agent could reset the credential to one it knows.
- The secret never enters the project. It lives in
~/.config/mathema/auth.yaml(mode 0600, respectingXDG_CONFIG_HOME), outside any repository an agent works in. Records and policy files carry only the key id. Lost PIN: delete that file and set a new one; the old key id stops matching, which is itself the audit trail working.
The honest threat model¶
This is a tripwire and an attestation, not a cryptographic barrier. An
agent with unrestricted shell could delete the credential file or edit
mathema itself; what it cannot do is produce a valid verified_by
stamp in the normal course of work, and every subversion leaves a
visible trace: a missing credential, a key id mismatch, an integrity
checksum failure (see what the checksum covers). Calibrate
trust accordingly, the same way the evidence ladder asks you to.
The record's integrity checksum is unkeyed. It catches accidental
changes, a stray edit or a merge that combined two versions of a
record, but anyone who can run mathema can recompute it, so it does
not stop a record forged on purpose. The gate against a forged record
is re-adjudication in CI: mathema verify --all re-checks every claim
against the code instead of trusting the verdicts stored in the
record, so a forged verdict does not survive the pipeline. A sign-off
is a decision rather than a verdict and cannot be re-derived; its
verified_by stamp names a public key id, so review of the store's
changes (mathema review) is where a sign-off nobody made shows up.
Project policy¶
.mathema/meta/policy.yaml, committed and human-owned, holds the
public constraints:
acceptance:
require_verification: true # every acceptance must carry verified_by
methods: [totp] # allowed methods; omitting pin disables the static PIN
keys: [a3f2c1] # optional allowlist of key ids
Enforced twice. At write time, an acceptance the policy forbids is
refused: no credential when one is required, a static PIN where only
stronger methods are allowed, a key outside the allowlist. At
verify time, every standing acceptance in the record is checked
too, so an unverified sign-off cannot ride in through a hand-edited
YAML file: mathema verify fails the run, which is the CI gate. That
includes the rows of the discoveries, historical and superseded
sections, since each one takes a claim out of the gate. Under
require_verification a record whose integrity checksum no longer
matches its contents also fails the run, where without a policy it
only warns.
CI, GitHub, GitLab¶
Acceptances happen locally, at a terminal, and travel as commits; CI
never holds the PIN. The pipeline runs mathema verify, and with a
policy present that alone enforces that every acceptance was
human-verified by an allowed credential.
Platform protections compose on top: branch protection plus a
CODEOWNERS rule on .mathema/verified/ makes any change to acceptance
blocks require a human PR approval, with the forge's own login 2FA as
the second factor. verified_by is the record-level half that
survives outside any forge.
Planned enhancement: authenticator-app codes¶
mathema pin set --totp (experimental) enrols a standard RFC 6238
TOTP secret into any authenticator app, the same 6-digit
rotate-every-30-seconds codes used for GitHub or Google 2FA. One QR
scan at setup; afterwards the prompt accepts the app's current code,
and a code an agent observes in a transcript is dead half a minute
later. The mechanism is built and tested; the static PIN is the
promoted path for now. The verified_by: {method, key} stamp is an
open seam: a stronger backend verifies its own way and records its own
method and key.