Category: spec-conformance Severity: minor
Location: Sources/ARCP/Runtime/JobManager.swift:162-183, Sources/ARCP/Messages/Execution.swift:8-19
Spec: ARCP v1.1 §9.5 ("expires_at is ISO 8601 with timezone, MUST be UTC (Z suffix) ... Past or invalid values are rejected with INVALID_REQUEST.")
What
LeaseConstraints.expiresAt decodes through the envelope's custom Date strategy, which accepts any RFC3339 offset (e.g. +02:00) via ISO8601DateFormatter. §9.5 requires the value be UTC with a Z suffix and that non-UTC/invalid values be rejected with INVALID_REQUEST. The runtime only checks expiresAt <= Date() (past), not the timezone form. A client sending 2026-05-13T23:42:00+02:00 is silently accepted and converted, rather than rejected. Because Date discards the original zone, this must be enforced at the string-decoding boundary.
Evidence
if let constraints = payload.leaseConstraints, constraints.expiresAt <= Date() {
// only the "past" check; no UTC/Z-suffix validation
LeaseConstraints uses the default Date codable (envelope custom strategy parses any offset).
Proposed fix
Give LeaseConstraints a custom decoder that decodes expires_at as a String, requires a Z suffix (or +00:00), and throws INVALID_REQUEST otherwise before converting to Date.
Acceptance criteria
Category: spec-conformance Severity: minor
Location:
Sources/ARCP/Runtime/JobManager.swift:162-183,Sources/ARCP/Messages/Execution.swift:8-19Spec: ARCP v1.1 §9.5 ("
expires_atis ISO 8601 with timezone, MUST be UTC (Zsuffix) ... Past or invalid values are rejected withINVALID_REQUEST.")What
LeaseConstraints.expiresAtdecodes through the envelope's customDatestrategy, which accepts any RFC3339 offset (e.g.+02:00) viaISO8601DateFormatter. §9.5 requires the value be UTC with aZsuffix and that non-UTC/invalid values be rejected withINVALID_REQUEST. The runtime only checksexpiresAt <= Date()(past), not the timezone form. A client sending2026-05-13T23:42:00+02:00is silently accepted and converted, rather than rejected. BecauseDatediscards the original zone, this must be enforced at the string-decoding boundary.Evidence
LeaseConstraintsuses the defaultDatecodable (envelope custom strategy parses any offset).Proposed fix
Give
LeaseConstraintsa custom decoder that decodesexpires_atas aString, requires aZsuffix (or+00:00), and throwsINVALID_REQUESTotherwise before converting toDate.Acceptance criteria
Zexpires_atis rejected withINVALID_REQUEST.