Mandate 0.5.0
Waves I, J and K: nine stories, each attacked twice. The clause count moves for the first time since
wave D: 191 clauses (one split in two), 85 decided on the real path.
Changed
- Breaking.
mandate_sts::IdentityAllocatorrequiresreserve_credential_id,
commit_credential_idandrelease_credential_id; there are no defaults. A reservation holds —
the next draw skips it — and a released identity an overtaking draw passed stays a gap
(story:sts-refusal-draws-nothing). - Breaking.
IncrementSecurityEpochneeds the newTargetTenancyport beside
SecurityEpochWrite(story:identity-tenant-containment). - The event log is pinned at
0.3.0(eventlog-core,eventlog-sqlite); nothing calls it yet
(story:eventlog-repin-0-3-0). - An obligation clause row may carry
decided_in: <workspace member>naming the crate whose test
decides it. The registry refuses the entry's own crate, a non-member,decided_inoff a clause
row, an empty or joiner-only clause, one test id under two kinds, paths or doubles, and a double
whose module path is not public.AuthorizePublicClient's "STS code issuance/narrowing" is split:
issuance is decided inmandate-sts; narrowing is deferred tostory:agent-authority-kernel
(story:cross-crate-clauses).
Fixed
- A just-in-time login records the
mandate.identity.Principalits provisioning event declares, and
a provisioning either fold refuses keeps neither (story:jit-principal-record). - An issuer's own origin compares two address literals as addresses (
[::1]and
[0:0:0:0:0:0:0:1]are one origin), andoriginreads an authority the way the fetcher does:
nothing but:after], only an IPv6 address inside brackets, digits-only ports. A JWKS
destination[addr]x:Pis no longer admitted at the default port and fetched onP
(story:folded-is-not-an-address-fold). register_resource_serverrefuses a profile whosemax_ttl, added to3000-01-01T00:00:00Z,
leaves the four-digit year, asProfileUnadmitted; both issuance commands and the
authorization-code redemption refuse, asExpiryUnbounded, any expiryinstant::atwould render
in a form the crate cannot read back — past9999-12-31T23:59:59Zor before
0000-01-01T00:00:00Z(story:sts-lifetime-bounds).- A plaintext issuer's loopback check reads each host form by its own parser: userinfo is refused,
a bracketed host must be IPv6, an unbracketed host may not contain:, at most one trailing dot is
folded, and a spelled port is digits only.http://localhost:80@evil.exampleis refused
(story:control-plane-loopback-fold). IncrementSecurityEpochrefusesTenantMismatchfor a target any record places in another
organization. The check is a read before the compare-and-set and not atomic with it; that is
decision-blocker:epoch-atomicity's (story:identity-tenant-containment).- Registration refuses a second organization's tenant rule on one issuer when one proof could
satisfy both — a rule on a different claim name — asTenantResolutionUnadmitted
(story:federation-rule-disjointness). - A refused STS redemption or self-contained issuance draws nothing from the secret source or the
identity allocator; a redemption decides every refusal before it mints (story:sts-refusal-draws-nothing). - The login-road test lane takes its child's address from the child's own
listening online, and
its concurrent copies no longer start copies of their own (story:road-lane-child-prints-address).
Known
- A bracketed issuer literal with a trailing dot (
[::1.]) is admitted as the issuer's own origin
and cannot be fetched; it fails closed (story:bracketed-literal-trailing-dot). DisableOAuthClient's "disablement cannot stop the issuance of new authorization codes to it" is
decided by nothing and deferred todecision-blocker:epoch-atomicity.