| 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. |