Statement reference
Statement members, rules and reject codes, types and registered actions.
Statement v1 (`manav-stmt/1`)
Last updated 24 September 2026
The statement is what a relying party asks a human to confirm. It says the same thing twice: fields for the machine that will execute the action, render for the human. Rules R1–R6 make the second a complete, safe function of the first. Each rule has its own reject code (core/Statement.php).
{
"v": "manav-stmt/1",
"action": "payment.release",
"source": { "system": "netsuite-ap", "schema_hash": "sha256:…" },
"fields": {
"amount": { "type": "money", "value": "42000.00", "currency": "USD" },
"payee": { "type": "string", "value": "Acme Corp" },
"account": { "type": "account", "value": "****4471", "full_hash": "sha256:…" },
"changed": { "type": "flag", "value": "Bank details changed 3 days ago" }
},
"render": [["Action","RELEASE PAYMENT"],["Amount","$42,000.00 USD"],["To","Acme Corp"],["Account","****4471"],["Notice","Bank details changed 3 days ago"]],
"one_shot": true,
"nbf": "2026-09-05T14:22:00Z", "exp": "2026-09-05T14:27:00Z",
"nonce": "b3d9…",
"rp": { "id": "ap.acme.com", "kid": "acme-2026-09" },
"rp_sig": "<Ed25519 over JCS(all fields above)>"
}
| Rule | Requirement | Reject code |
|---|---|---|
| R1 total rendering | every entry in fields appears in render, via its type's fixed render function; no override exists |
r1_field_not_rendered:<name> |
| R2 schema pinning | source.schema_hash = SHA-256(JCS(schema)) of a schema published at /.well-known/manav-schemas/<action>.json; field names and types must equal the schema's |
r2_unknown_schema, r2_fields_do_not_match_schema |
| R3 RP pre-signing | rp_sig (Ed25519) verifies under a registered relying-party key before any browser sees the statement |
r3_bad_rp_sig, r3_unknown_rp_key |
| R4 no lossy abbreviation | masked values (account, ****…) carry full_hash; no … truncation except in digest renders |
r4_lossy_without_hash:<name> |
| R5 frozen canonicalisation | JCS; NFC; no invisible/bidi/zero-width code points; no mixed scripts in one string; integers only, money as fixed-precision strings with explicit currency | r5_unsafe_string:<detail> |
| R6 short expiry, one shot | exp − nbf ≤ 300 s, one_shot: true, nonce consumed exactly once at /v2/sign |
r6_window_too_long, r6_not_one_shot, r6_expired, nonce_replayed |
Property (test group 19): no two distinct statements produce the same render.
Types and render functions
money ("42000.00" + ISO currency → $42,000.00 USD), string, flag, enum (values listed in the schema), uri, account (masked; needs full_hash), digest (sha256:<64 hex> → sha256:ab12…), int (→ 50,000), datetime (RFC 3339 → 2026-09-25 00:00 UTC), list, scope, budgets (the last two are rendered by the action type; see workload.grant).
Registered actions
| action | schema | used by |
|---|---|---|
payment.release |
amount, payee, account, changed | payment-release gate, demos |
contract.sign |
contract, counterparty, annual_value, effective, reference, document | /sign/ |
slack.owner_promote |
workspace, user, role, requested_by | /sign/slack-admin/, /app/ |
agent.payment_grant |
agent, per_payment_limit, period, purpose, grant_exp | /sign/agent-grant/, /app/ (actions under it: POST /v2/grant-actions) |
ai.declare |
document, reference, declaration | /sign/ai-declare/ |
deploy.approve |
repository, ref, commit, environment | CI/CD gate |
workload.grant |
see the Guard reference; rules GR1–GR6 | Manav Guard |
host.enroll, host.revoke |
host_id, hostname/reason, … | Guard enrollment |
workload.grant extra rules
| Rule | Constraint | Reject code |
|---|---|---|
| GR1 | grant_exp − nbf ≤ 90 days |
gr1_grant_too_long |
| GR2 | offline_cap_s ∈ [0, 86400] |
gr2_offline_cap_out_of_range |
| GR3 | code_digest names one build |
gr3_wildcard_digest |
| GR4 | last render row = the diff from the previous grant for the same program and asset (computed by the RP, re-checked by the issuer) | gr4_missing_diff_row |
| GR5 | postgres-role/1: role_privileges read from the catalogue (role_privileges_source: "catalogue") |
gr5_privileges_not_from_catalogue |
| GR6 | http-json/1 argument schemas inside fragment F; a read-write role may not use offline_mode: read_only |
gr6_scope_outside_fragment:<why>, gr6_read_only_offline_mode_needs_read_only_role |
Fragment F: type, const, enum, integer minimum/maximum, string minLength/maxLength, anchored pattern without alternation of unbounded parts, back-references or unbounded quantifiers, object with properties, required and mandatory additionalProperties: false, array with items and mandatory maxItems. Every leaf constraint is rendered.