Skip to content

M2-06 Invocation token with EdDSA and JWKS

No Date Ruling Reason
D-M2-06-0 2026-10-22 Built on the old integration line (base int/m1-wave50 with M2-05 merged in) and ported to main after M2-05 landed: only the ticket's own commits were brought over. The spec commit that copied section 4.5 with 4.5.1 of the agents chapter into the old base is dropped, because docs/spec/09-agents.md on main already carries it unchanged; the ADR, threat model and README lines go to their English files on main. The ticket adds no migration. The rules of the landing replace the integration branches; main is the reference.
D-M2-06-1 2026-10-20 InvocationTokens stays in runs/app; the root package internal/runs gets no type for it. M2-14, the first consumer in another module, adds the port with its own DTOs. Every consumer of this milestone up to M2-17 is inside runs (02 section 5 rule 1).
D-M2-06-2 2026-10-20 The triggering object of followup is the follow-up message (message_id), of autocomplete the command of the request (command_id, no run_id). A grant that breaks the table, a command without run_id included until M2-16 adds the control command grant, is an internal error and nothing is signed. 05 section 5 names one object per token; AgentFollowupPayload.message_id and CommandAutocompleteRequestedPayload.command_id are those objects; fail closed.
D-M2-06-3 2026-10-20 The header has exactly alg, kid and typ; iat is the issuing time truncated to seconds and exp is iat plus agent.invocation_token_ttl truncated to seconds; aud is one string; absent claims are omitted, never null. The issuer is set by the app layer (WithIssuer), not by the grant, and Payload refuses claims without one. RFC 7519 NumericDate and section 4.1.3, ADR 0053; the issuer is the instance, not a decision about the delivery.
D-M2-06-4 2026-10-20 Verify looks the key up by purpose invocation and accepts next, active and retiring; it also compares the purpose, the algorithm and the state of the returned key. An unreadable key store is returned as it is and is not invalid. A process may lag a promotion by signing_key.cache_ttl; no next key ever signs. An outage is no verdict about a token and must not make an adapter finish the run as invalid_invocation_token.
D-M2-06-5 2026-10-20 typ is compared byte for byte after the case-insensitive profile check of kernel/jose; a header jwk is never used. 09 4.5 item 2 says "exactly".
D-M2-06-6 2026-10-20 Every cause of invalid is checked before the expiry: expired only when the token is otherwise valid; IgnoreExpiry skips only the expiry. 09 4.5 reason table: "exp passed, otherwise valid and current"; 09 4.5.1 step 1.
D-M2-06-7 2026-10-20 Unknown payload members are ignored; member names are matched exactly (no case folding) and the issuer writes exactly the set of 05 section 5. RFC 7519 section 4: a token of a newer server version stays verifiable during an upgrade.
D-M2-06-8 2026-10-20 The server applies auth.clock_leeway to iat and exp of its own invocation tokens: valid while now is before exp plus the leeway. 08 7.2 defines the leeway for exp, nbf and iat; the retiring window of 09 4.5 "Keys and rotation" point 2 already contains this margin, so an accepted token always has a published key.
D-M2-06-9 2026-10-20 The reference verifier is test support in runs/adapters/runstest, written with the standard library only and independent of kernel/jose; it reports in this precedence: points 1 to 4 and 6 (ErrRefInvalid, ErrRefUnknownKey for a kid missing after one forced reload), then 8 and 10 (the mismatches), then 5 (ErrRefExpired, ErrRefInvalid for an iat in the future), then 7 (ErrRefReplay). A duplicate header or payload member is ErrRefInvalid. A defect shared by issuer and server verifier cannot hide behind one parser (as G10 for webhooks); the precedence follows Design note 6 of the brief.
D-M2-06-10 2026-10-20 This ticket proves AG-22 at token level: scheduled, no token fails at three adapters or a second process inside its validity through two rotations and no adapter needs a forced reload; emergency, the old key fails at once in the same process and within signing_key.cache_ttl in another, and a new token verifies after at most one reload. The run level of both halves is M2-31's. The late check of an adapter that saw the token before expects ErrRefReplay, which proves every other point held. PLAN row M2-06 and M2-31; the replay check is the last point of the adapter check.
D-M2-06-11 2026-10-20 InvocationToken wraps the compact JWS and renders as [redacted] through String, GoString, Format, LogValue, MarshalText and MarshalJSON; only Reveal returns it. SEC-LOG-2, SEC-SEC-3; the same type as the terminal session token of M2-44.
D-M2-06-12 2026-10-20 internal/wire builds the invocation tokens in the signing keys step (Runs.BindInvocationTokens), not where the runs module is built in the chat step. The chat step runs before the signing keys exist; the bind is once per process and adds no route, subscription or job.