Manav.id
Spec

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.

← All specification documents