A standing permission is not an intent to hold. The cardholder taps once to let an agent buy on their behalf, within limits, for a while; the producer signs that as a signal; the agent's intents carry it as evidence; the authority verifies the producer's signature, writes what the signal says and what of each intent it covers as facts, and policy decides. Nothing is pre-authorised: every purchase is still its own intent, evaluated and bound on its own.
// Protected header
{ "alg": "EdDSA", "typ": "decionis-signed-signal+jwt", "kid": "koard-2026-10" }
// Claims
{
"iss": "koard",
"aud": "<bank org id>",
"sub": "cardholder:ch_91ad",
"iat": 1760270460, "exp": 1762862460, "jti": "delegation-0001",
"signal": {
"name": "card_delegation",
"method": "card_tap",
"result": "VERIFIED",
"agent": "shopping-agent",
"scope": { "actions": ["card.purchase"] },
"limits": { "currency": "EUR", "amount_minor": 50000 },
"card_token_ref": "tok_7f3a"
}
}
// Presented with every intent it is meant to cover, with or without a tap
{
"...": "the intent",
"evidence": { "signals": [{ "token": "<compact JWS>" }] }
}
| Claim | Meaning | Rule |
|---|
| iss | The producer. | Equal to the issuer registered with the key; never presence; at most 100 characters. |
| aud | The organisation the signal is for. | The bank's organisation id. |
| sub | Whom the signal is about. | An opaque reference of at most 200 characters; a value shaped like a card number is refused. |
| iat, exp | When it was signed and until when it stands. | exp after iat and at most thirty days later; iat at most 300 s in the future; an expired signal is refused, with a 300 s tolerance. |
| jti | The signal's id. | At most 200 characters; recorded as signal_id. |
| signal.name | The fact the signal is written under. | A lower-case name of at most 64 characters; two signals under one name in the same intent are refused. |
| signal.method | How the producer established it. | An open name such as card_tap, at most 64 characters. |
| signal.result | VERIFIED or FAILED. | FAILED is refused with SIGNED_SIGNAL_NOT_VERIFIED. |
| signal.agent | The actor the permission is for. | Optional, at most 200 characters; compared with actor.id. |
| signal.scope.actions | The action types permitted. | Optional; 1 to 32 names; compared with action.type. |
| signal.limits | A ceiling: currency and amount_minor. | Optional; a three-letter currency and a whole number of minor units; compared with parameters.currency and parameters.amountMinor. |
| signal.card_token_ref | The card the permission is for. | Optional; compared with parameters.cardTokenRef or a card: resource; never a card number. |
| Any other key of signal | Not part of the contract. | Refused as SIGNED_SIGNAL_MALFORMED: the shape is closed. |
// Written by the authority for each verified signal, never by the caller;
// anything the caller put under agent_safe is overwritten.
"agent_safe": {
"signed_signals": {
"card_delegation": {
"verified": true,
"producer": "koard", "method": "card_tap", "subject": "cardholder:ch_91ad",
"signal_id": "delegation-0001", "key_id": "koard-2026-10",
"issued_at": "2026-10-12T12:01:00.000Z", "expires_at": "2026-11-11T12:01:00.000Z",
"evidence_digest": "sha256:...",
"agent": "shopping-agent",
"scope": { "actions": ["card.purchase"] },
"limits": { "currency": "EUR", "amount_minor": 50000 },
"card_token_ref": "tok_7f3a",
"covers": { "action": true, "agent": true, "amount": true, "currency": true, "card": true },
"covers_intent": true
}
}
}
| Fact | Meaning |
|---|
| covers.action, .agent, .amount, .currency, .card | What of this intent the signal covers: true or false where the signal asserts it (scope, agent, limits, card_token_ref) and null where it does not. A limit the intent does not name, no amountMinor or no currency, is not covered. |
| covers_intent | True when no entry of covers is false: the signal, as signed, covers this intent. A purchase over the ceiling, in another currency, on another card, by another agent or of another action type is not covered, and the rule below does not match. |
| evidence_digest | The SHA-256 of the token. The token itself is never recorded: not in the evaluation, not in the dossier, not in the binding. |
{
"required_signals": ["agent_safe.signed_signals.card_delegation"],
"rules": [
{
"rule_id": "delegated-purchase",
"when": { "field": "agent_safe.signed_signals.card_delegation.covers_intent", "operator": "EQ", "value": true },
"then": { "action": "AUTO_APPROVE" },
"priority": 200
},
{
"rule_id": "cardholder-tap",
"when": { "field": "agent_safe.action.type", "operator": "EQ", "value": "card.purchase" },
"then": {
"action": "ESCALATE",
"authority": {
"reason_codes": ["CARDHOLDER_TAP_REQUIRED"],
"selector": { "kind": "ROLE", "role_id": "CARDHOLDER" },
"verification_requirements": {
"level": "STANDARD",
"methods": ["CARD_TAP"],
"hardware_pki_required": false,
"disallow_virtual_cameras": false,
"provider": "koard"
},
"resolution": { "quorum": 1, "delegation": "DISALLOWED", "fallback_role_ids": [] },
"expires_in_seconds": 300
}
},
"priority": 100
}
]
}
| Line | What it does |
|---|
| required_signals | A dotted path under the facts. An intent that carries no verified signal of that name resolves to ESCALATE as missing_required_signals before any rule runs: a negative predicate never fires against a fact that was never supplied. |
| delegated-purchase | The signal covers this intent: ALLOW, with a grant bound to this purchase. The authority wrote covers_intent; the caller cannot. |
| cardholder-tap | Otherwise the hold goes to the cardholder's tap, exactly as in step 2. |
POST/v1/execution/provider-keysOrg API key (policy:write)
The producer's key: the public half of an Ed25519 key with purpose signed_signal and the iss it signs with. A key only ever verifies its own purpose, so an attestation key signs no signal and a signal key signs no attestation. A provider the bank admitted from the directory needs no registration: the keys its JWKS publishes sign its signals too.
{ "kid": "koard-2026-10", "issuer": "koard", "algorithm": "EdDSA", "purpose": "signed_signal",
"public_jwk": { "kty": "OKP", "crv": "Ed25519", "x": "..." } }
| Refusal | Meaning |
|---|
| SIGNED_SIGNAL_MALFORMED | Not a compact EdDSA JWS of the expected type, a claim missing or ill-shaped, a key of signal outside the contract, or a lifetime over thirty days. |
| SIGNED_SIGNAL_KEY_UNKNOWN | No active key under that kid for purpose signed_signal: neither registered by the bank nor published by an admitted provider. |
| SIGNED_SIGNAL_SIGNATURE_INVALID | The signature does not verify under the key. |
| SIGNED_SIGNAL_EXPIRED | exp has passed, within a 300 s tolerance. |
| SIGNED_SIGNAL_AUDIENCE_MISMATCH | aud is not the bank's organisation. |
| SIGNED_SIGNAL_ISSUER_MISMATCH | iss is not the issuer the key was registered or published for. |
| SIGNED_SIGNAL_NOT_VERIFIED | signal.result is not VERIFIED. |
| SIGNED_SIGNAL_DUPLICATE_NAME | Two signals in one intent carry the same signal.name. |
What is recorded, and what is checked again
The dossier and the signed execution binding record the facts: the producer, the method, the subject, the signal id, the key id, the window, what the signal asserted, what it covered, and the SHA-256 of the token as evidence_digest. Never the token. A refused signal is a 409 with its code; no evaluation runs and no event fires, since a signal is the producer's standing word rather than a person's answer to a hold. At the claim, the authority looks the producer's key up again under purpose signed_signal, requires its issuer to be the signal's producer and the signal's expiry to lie ahead, and rebuilds the same facts for the policy re-run: a key revoked since, or a delegation that ran out, stops the grant from being claimed. After a managed Presence ceremony the signals are verified again before the re-evaluation: one that has run out contributes no fact, and the policy decides without it.