Skip to content

CI (lane GO): split the full checks into full-checks and full-checks-2 (DEC-2334) - #2195

Merged
kunwarshivam merged 12 commits into
mainfrom
claude/go-gitops-full-checks-split
Oct 23, 2026
Merged

kunwarshivam merged 12 commits into
mainfrom
claude/go-gitops-full-checks-split

Conversation

@kunwarshivam

@kunwarshivam kunwarshivam commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Story

CI capacity under DEC-464, at the coordinator's request. Decisions: DEC-2334, assigned by the coordinator, and DEC-2325 (via #2142).

This PR contains #2142. #2142 at its approved head adc790f was green except full-checks, which hit the cap again (05:54:12 to 06:04:23), so full failed and #2142 can't land green alone. This PR merges that head and current main and adds the split, so landing it lands both. Its base is main, and its diff over main is #2142's 6 files plus DEC-2334.

On #2142 at 72a6297, a full selection's single full-checks was cancelled at the ten-minute cap at ci exchange-release. Every part before it had passed.

  • full-checks keeps the parts that need its services (PostgreSQL, OpenBao, S3), plus refcases, whose build the Postgres tests reuse: refcases, postgres, openbao, objectstore, each as its own step.
  • full-checks-2, new, runs on its own runner with no service: reference, schemas, supply-chain, tofu, stub-release, exchange-release. cargo-deny, gitleaks, OpenTofu, tflint and MANDATE_TOFU_REQUIRED move with the parts that use them. It needs select and starts on needs.select.outputs.full_checks, exactly as full-checks does.
  • full needs both. Each must have succeeded where selected and been skipped where not.
  • Locally nothing changes: cargo xtask ci full and check still run every part.
  • From CI (lane GO): four fast-test shards, with the longest tests started first (DEC-2325) #2142 (DEC-2325): four fast-test shards, and xtask/nextest.toml starting the two longest xtask tests first.

Timings on full selections

Run full-checks full-checks-2
#2142 at 72a6297, one job cancelled at 10 min —
#2195 at 8ead751, first split (reference, schemas and supply-chain still in full-checks) 9 min 57 s 1 min 12 s
#2195 at 6f3fb9f, rebalanced 4 min 07 s (07:24:45 to 07:28:52) 4 min 15 s (07:24:45 to 07:29:00)

fast-test shards on 6f3fb9f: 1/4 8.7 min, 2/4 6.3, 3/4 7.8, 4/4 7.0. fast and full are green.

Spec clause → test

Spec clause or invariant Test name
The two jobs run exactly ci full's parts, each once and in order, and no other job runs one. Each is capped at 10 min, needs select, starts on full_checks, and is needed by full. Seeded: dropping full-checks-2 from full's needs, or a duplicated part, both fail it the_full_jobs_run_exactly_the_full_group (new)
full's script: either job off its selection fails the verdict full_accepts_a_skipped_matrix_only_beside_a_plan_of_zero (eight new cases)
The database and S3 store start before the parts that need them every_mutation_shard_has_the_database_…, …_the_store_… (gates now ci postgres and ci objectstore)
Every scoped job starts on its switch; one checkout per source-reading job; draft guards the_scoped_jobs_follow_the_selection (SELECT_GATES 16), ci_leaves_the_base_to_the_event (22 checkouts), the guard table
#2142's pins the_fast_jobs_run_exactly_the_fast_group, the_nextest_partition_is_checked

Dependencies

None.

Journal events added

None.

Not done

Nothing.

Decisions needed

None.

Safety checklist (safety-critical changes)

Not a safety-critical path. Only the CI job layout and test order change; no check is removed.

Checks

  • cargo nextest run -p xtask: 542 passed at 6f3fb9f.
  • clippy -p xtask -D warnings, fmt, actionlint, typos, markers and decision numbers are clean.
  • CI timings of full-checks and full-checks-2 on a full selection (table above).
  • Every job green on 6f3fb9f. cell-kill-switch's first attempt failed in k3d setup (an image-import race; see the comment), and its one re-run passed (07:59:25 to 08:06:35).

🤖 Generated with Claude Code

https://claude.ai/code/session_01UoxcMViCbnGUTPSaduuGYU

…st tests first

A full selection's third shard ran 293 s of tests after about five minutes of setup and build,
with xtask's 104 s leak test alone at the tail, and hit the ten-minute cap (DEC-464). Four counted
partitions take a quarter each, and a nextest priority override starts the leak test and the
live-build check first, so they overlap the rest; no test is dropped or moved out of a shard.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UoxcMViCbnGUTPSaduuGYU
A top-level `.config/` is a path the outside-path scan reads in every `.config` field access, so
`every_outside_path_a_crate_names_is_one_the_scan_covers` failed on mandate-risk and
mandate-version-backtest; xtask now hands every nextest run `xtask/nextest.toml` instead.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UoxcMViCbnGUTPSaduuGYU
… by key

#2129 turned `kubeApi.enabled` on in vault-staging.yaml, and the chart's lookup has no cluster in
`cargo xtask ci cell`'s chart checks, so `helm template` of the staging values failed and the
`cell` job went red. The checks now give the vault chart the same k3s address as journal-db, set
per key so the values file's `enabled` survives, as test_gitops does since #2129.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UoxcMViCbnGUTPSaduuGYU
A full selection ran `cargo xtask ci full` past the ten-minute cap in one job (DEC-464). The parts
that need Postgres, OpenBao or the S3 store stay in `full-checks`; `tofu`, `stub-release` and
`exchange-release` move to `full-checks-2`, started by the same selection and needed by `full`.
`the_full_jobs_run_exactly_the_full_group` pins that the two jobs run exactly `ci full`'s parts,
each once.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UoxcMViCbnGUTPSaduuGYU
@kunwarshivam
kunwarshivam changed the base branch from claude/go-gitops-fast-test-shards to main October 22, 2026 06:18
@kunwarshivam
kunwarshivam marked this pull request as ready for review October 22, 2026 06:18
…full-checks-2

On #2195's full selection `full-checks` ran 9 min 57 s, three seconds under the cap, and
`full-checks-2` 1 min 12 s. The three parts that need neither a service nor the Postgres build move
to `full-checks-2`, with cargo-deny and gitleaks; DEC-2334 records the timings.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UoxcMViCbnGUTPSaduuGYU

Copy link
Copy Markdown
Owner Author

cell-kill-switch failed on 6f3fb9f during cell setup, before any drill ran. k3d image import mandate.local/mandate:… hit ctr: open /k3d/images/k3d-mandate-cell-images-….tar: no such file or directory, and k3d still reported success. The workspace image was never loaded, so the cell's API server never became ready.

This PR changes only full-checks' job layout, xtask's CI pins, the nextest config and docs. Nothing in the cell path changed. The same job passed on 8ead751, whose cell inputs are identical. I'll re-run the failed job once when the workflow run finishes; GitHub refuses a re-run while the run is still in progress.


Generated by Claude Code

@kunwarshivam
kunwarshivam merged commit 30ff01a into main Oct 23, 2026
79 of 80 checks passed
@kunwarshivam
kunwarshivam deleted the claude/go-gitops-full-checks-split branch October 23, 2026 06:53
kunwarshivam added a commit that referenced this pull request Oct 23, 2026
…s for RUSTSEC-2026-0385 (DEC-2334, DEC-2338) (#2322)

* CI (lane GO): run api-release in a full-checks-3 job of its own (DEC-2334, DEC-2338)

#2289 added `api-release` to `cargo xtask ci full` (DEC-2338 item 5) after
#2195 had split `full`'s parts into `full-checks` and `full-checks-2`
(DEC-2334). No CI job ran `api-release`, and
`the_full_jobs_run_exactly_the_full_group` failed on main.

Its release build of `mandate-api-server` takes about five minutes on its own.
Neither existing job has room for that under ten minutes (DEC-464):
`full-checks-2`'s `reference` alone is about 4.5 minutes, before its two
release builds. So it runs in `full-checks-3`, on the same selection, and
`full` needs all three. The verdict table now shows a failed, cancelled or
skipped `full-checks-3` failing `full`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UoxcMViCbnGUTPSaduuGYU

* Supply chain: bump ctutils 0.4.2 -> 0.4.5 for RUSTSEC-2026-0385

`cargo deny --locked check` fails on main. In ctutils 0.4.2, the constant-time
`Choice` mask lacked an optimization barrier (RUSTSEC-2026-0385,
RustCrypto/utils#1569); 0.4.4 and later fix it. ctutils is a transitive
RustCrypto crate, reached through aes-gcm, crypto-bigint, digest, sec1 and
universal-hash (webpush, p256, sqlx-postgres). `cargo update -p ctutils` makes
a patch-level bump. Nothing else in Cargo.lock moves, and no manifest or
dependency row changes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UoxcMViCbnGUTPSaduuGYU

---------

Co-authored-by: Claude <[email protected]>
kunwarshivam added a commit that referenced this pull request Oct 23, 2026
… from /v2/account alone and kept in the account entry's one byte form (DEC-2339) (#2228)

* E21-32 PRV-1a: Kubernetes auth's templated policies, the provisioner's policy and its role requests, tests pending (DEC-2060)

Reserves DEC-2060. Each per-connection process logs in with Kubernetes auth as
the ServiceAccount named by its connection id's hex; one templated policy per
kind reads the path from the login's own ServiceAccount name, so the dedicated
provisioner only ever creates a role bound to exactly that ServiceAccount, every
other parameter pinned.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-1b: Kubernetes auth's templated policies, the provisioner's policy and its role requests (DEC-2060)

Implements the stubs and deletes the pending markers.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-1a: both kinds in the workspace's namespace, the exchange's ServiceAccount named x and the hex (DEC-2060)

A separate exchange namespace would leave its pods outside the workspace's
default deny, egress policies and admission pin. A second spike on OpenBao
2.7.1 showed a literal x before the template within one path segment works:
the executor, SA <hex>, reads connections/x<hex>/token, which the exchange, SA
x<hex>, writes; each role binds its one SA, and an even-length hex name is never
an odd-length x name.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-1b: one namespace, the exchange's ServiceAccount x and the hex (DEC-2060)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-1a: the Kubernetes auth mounts live in Mounts, and the namespace is the workspace's (DEC-2060)

So OpenBaoConfig names every mount the login and the provisioner use, from one
place, and no second layout can disagree with it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2a: login_kubernetes and the projected ServiceAccount token's reader, tests pending (DEC-2060)

The contract agreed with lane LV: OpenBaoSource::login_kubernetes(config,
identity, jwt, now) for a connection's executor or token exchange, and
mounted::read_service_account_token(dir) with SERVICE_ACCOUNT_TOKEN_DIR.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2a: the Kubernetes login's path and its verdict as pure functions, tests pending (DEC-2060)

So the role and ServiceAccount checks are tested and mutated without a cluster.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2b: login_kubernetes and the projected ServiceAccount token's reader (DEC-2060)

Implements the stubs and deletes the pending markers. read_mounted and the
token reader share one bounded, link-safe read, differing only in the mode bits
they refuse and their bound.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-1a: one storage segment for a connection, x and its hex, and the cross-contract tests, pending (DEC-2060)

kv_path and openbao_policy become fallible so kv_path can place a
connection's entries through connection_segment, a stub here. The pending
tests pin the segment, roles named by their ServiceAccount (OpenBao folds
role and policy names to lowercase), every role parameter required, a
malformed accessor refused, and a cross-contract test: each kind's policy
with its bound ServiceAccount put in is exactly the policy written from
kv_path, over ids that collide under a raw or case-folding layout. The
process-name test reads processes.json. DEC-2060 is written in full.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2a: a Kubernetes login reaches the role named by its ServiceAccount, tests pending (DEC-2060)

OpenBao folds role names to lowercase, so PRV-1 names each role by its
ServiceAccount; the login's path and its verdict follow, with an Ab case.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* DEC-2060: the AppRole executor and exchange are retired, and the deployment manager makes the ServiceAccounts

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2c-1a: provisioning sets the Kubernetes auth mounts up, and a missing role is its own refusal, tests pending (DEC-2060)

SourceError::RoleMissing (role_missing, not an outage) lets the token
exchange's login wait for a role the provisioner has not made yet, as
agreed with lane D1; kubernetes_login_refusal is that mapping, a stub.
provision takes an optional KubernetesAuth and, given one, sets up both
Kubernetes mounts and their templated policies through stubs
kubernetes_mount_requests, mount_accessor and provision_kubernetes. A
server test pins the result on the dev server, a missing role included.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2c-1b: provisioning sets the Kubernetes auth mounts up, and a missing role is its own refusal (DEC-2060)

provision, given a KubernetesAuth, enables each Kubernetes mount the
vault's sys/auth does not list, points it at the API server, and writes
each kind's templated policy keyed by its mount's accessor. A Kubernetes
login reads a refusal's body only to tell RoleMissing from the rest, and
wipes it like every other body.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* Reserve DEC-2061: the token exchange may overwrite its own connection's code

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2c-2a: the exchange takes its own code, and a Kubernetes login is held to the workspace's namespace, tests pending (DEC-2061)

Access::Take is a new access that only a connection's token exchange holds,
on its own connection's code, granted only by its Kubernetes-auth templated
policy as update without create; the AppRole policies never grant it.
login_kubernetes gains namespace, checked by kubernetes_namespace_verdict
(a stub), and read_service_account_namespace (a stub) reads the downward
API's namespace beside the projected token. DEC-2061 is written in full
and infrastructure §5.2's token-exchange row names the take. Three tests
that widen implemented tables are behaviour-only rows until PRV-2c-2b.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2c-2a: the executor reads its namespace beside its token for its Kubernetes login

login_kubernetes now takes the workspace's namespace; #2009's executor
reads it from the projected volume with read_service_account_namespace,
so no pod names its own namespace in its arguments.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2c-2a: name the behaviour-only rows by their in-file path, and read the executor's namespace before it dials

The pending gate names a test by its path inside its file, so each row's
test is the bare function name. The executor reads its namespace before
vault_login, as LV asked, so an unreadable one is
vault_credentials_unreadable and nothing dials without it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2c-2a: an AppRole policy never grants a take on a secret, not never update at all

The connection manager's AppRole grant paths rightly carry update, so the
check is over the KV data paths only.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2c-2b: the exchange takes its own code, and a Kubernetes login is held to the workspace's namespace (DEC-2061)

allows grants Access::Take to a connection's token exchange on its own
code, and the exchange's templated policy gives that code read and
update, never create. read_service_account_namespace reads the projected
namespace by the token's rules and holds it to a label;
kubernetes_namespace_verdict accepts only the workspace's namespace. The
three behaviour-only rows go.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2c-3a: a connection's exchange takes its code once, on both backends, tests pending (DEC-2061)

SecretSource::take_code takes a code once: of overlapping takers exactly
one gets it, and a source that holds no codes refuses by default. The
local and OpenBao takes are stubs. The conformance suite pins, on both
backends, that only the code's own exchange takes it, once, and that of
two racing takers exactly one wins; on OpenBao that a tombstone is never
a code and that no version is left after a take. The OpenBao harness's
taker holds the Kubernetes-auth exchange's own policy through an operator
token, the only way to hold it without a cluster.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2c-3a: each taker's policy is named by its ServiceAccount, so a second taker does not rewrite the first's

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2c-3b: a connection's exchange takes its code once, on both backends (DEC-2061)

The local take is the local vault's own rename take (DEC-2628 item 2),
after the policy check. The OpenBao take reads the code with its version,
overwrites that version alone with a tombstone by check-and-set, then
deletes every version; a lost overwrite, a tombstone or no code is None.
A read of a tombstone is no code. Http::post_judged lets the login and
the take each judge a refusal's body.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2c-3b: a take refused after the code is gone is a lost race, not a denial

The exchange may update its code but never create it, so once another
taker has deleted the code OpenBao refuses the overwrite as 403 before
it checks the version. The take then reads again, and reports None only
when the code is gone or a tombstone; any other refusal stays Denied.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2c-3a: pin a lost overwrite's refusal, an exchange without the take, and provisioning over a planted policy

cas_refusal (a stub) is pinned by a pure test, so a lost overwrite is
caught without a race. The AppRole exchange, whose policy lacks the
take, is refused and leaves the code. Provisioning replaces a policy
planted under its name, as the reviewer of #2079 asked.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2c-3a: the exchange's update never creates a code, as the reviewer of #2087 asked

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2c-3b: run the update-never-creates test

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2c-3a: pin the take's read and its read again as pure verdicts, the code a secret from its decoding

taken_read and lost_or_denied_verdict are stubs pinned by unit tests, so
the tombstone, the lost race and the denial are caught without a server,
and the read's answer never shows the code, as the reviewer of #2117
asked.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2c-3a: pin DEC-2321's take, which destroys the version it took and keeps the version count

The exchange's policy swaps its metadata delete for a delete on its code's data and an update on
its code's destroy path; a taker stalled across a take cannot overwrite the code a re-connect
stores. DEC-2321 records it and DEC-2061's Status line points at it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2c-3b: a take destroys the version it took and deletes its tombstone, keeping the version count (DEC-2321)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2c-3a: a taken code's versions hold no data, though the kept metadata still answers

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2c-3a: the take deletes its tombstone's version alone, and a code decodes at its exact size

A plain DELETE of the data path deletes whatever version is latest, which hides a code a
re-connect stored between the tombstone and the delete; deleting version v+1 through delete/
never does. The exchange's grant becomes update on delete/ in place of delete on data. The
decoding test covers every padding length and malformed lengths, for an exact-size decode.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2c-3b: delete the tombstone's version alone, and decode a code at its exact size

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32 PRV-2c-3b: drop exact_decode's two guards the decode already makes

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32: a connection's broker account id is kept in the personal-data vault, created once by its own executor, tests pending (DEC-2339)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32: a connection's broker account id is kept in the personal-data vault, created once by its own executor (DEC-2339)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32: a source that holds no personal data refuses a create (DEC-2339)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E10-13: the broker account id is a shared secret type, read from /v2/account alone and kept in the account entry's one byte form, tests pending (DEC-2339)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E10-13: the broker account id is a shared secret type, read from /v2/account alone and kept in the account entry's one byte form (DEC-2339)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E10-13: the account id form test names a stub's report when it fails

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E10-13: two ids are equal only by their text, and an entry is written only under a pii_ reference

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E10-13: the id equality and pii_ reference tests run

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32: a refused account entry create fails closed, as an unreadable account (DEC-2339)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E10-13: the entry decode test's name is spelled as typos reads it

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32: DEC-2339's fingerprint is the vault's keyed hash, per connections spec §3.1

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32: no identity reads the personal-data vault; the manager gets the vault's keyed hash alone, per connections spec §3.1, tests pending (DEC-2339)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32: one account entry per request, below its connection and account stream, tests pending (DEC-2339)

A request again on the same connection to another account stream (DEC-699) has
an entry of its own, so nothing is overwritten and a refused request's entry is
kept until its erasure. The executor creates below its own connection's
templated segment with one '+', the account stream, create only: the one
wildcard of any templated policy, pinned by a test.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32: one account entry per request, below its connection and account stream (DEC-2339)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-32: the take test keeps its own connection alone

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* CI (lane GO): run api-release in a full-checks-3 job of its own (DEC-2334, DEC-2338)

#2289 added `api-release` to `cargo xtask ci full` (DEC-2338 item 5) after
#2195 had split `full`'s parts into `full-checks` and `full-checks-2`
(DEC-2334). No CI job ran `api-release`, and
`the_full_jobs_run_exactly_the_full_group` failed on main.

Its release build of `mandate-api-server` takes about five minutes on its own.
Neither existing job has room for that under ten minutes (DEC-464):
`full-checks-2`'s `reference` alone is about 4.5 minutes, before its two
release builds. So it runs in `full-checks-3`, on the same selection, and
`full` needs all three. The verdict table now shows a failed, cancelled or
skipped `full-checks-3` failing `full`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UoxcMViCbnGUTPSaduuGYU

* Supply chain: bump ctutils 0.4.2 -> 0.4.5 for RUSTSEC-2026-0385

`cargo deny --locked check` fails on main. In ctutils 0.4.2, the constant-time
`Choice` mask lacked an optimization barrier (RUSTSEC-2026-0385,
RustCrypto/utils#1569); 0.4.4 and later fix it. ctutils is a transitive
RustCrypto crate, reached through aes-gcm, crypto-bigint, digest, sec1 and
universal-hash (webpush, p256, sqlx-postgres). `cargo update -p ctutils` makes
a patch-level bump. Nothing else in Cargo.lock moves, and no manifest or
dependency row changes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UoxcMViCbnGUTPSaduuGYU

---------

Co-authored-by: Claude <[email protected]>
kunwarshivam added a commit that referenced this pull request Oct 23, 2026
…EC-2350 S4c follow-up) (#2311)

* Test the env-name pin, the vault CA mount and the exchange's launch on the deploy-manager's pods

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Pin each managed container's environment, mount the vault's CA, and launch the exchange with its vault flags

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Cite DEC-2332 for the env pin, the vault CA and the exchange's launch

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Bind the API to the vault with its AppRole and the vault's CA, copied owner-only, where its binding is on (DEC-2338)

api.vaultBinding projects the vault-approle-api Secret's two keys and the
vault-ca CA into one read-only source directory, which an init container copies
into a memory-backed directory at 0600 as api.role-id, api.secret-id and
ca.crt, since mandate_vault's reader refuses any group or other bit; the API
mounts it read-only and names it in MANDATE_API_VAULT_DIR, beside
MANDATE_API_VAULT_ADDRESS, and its egress names the vault on 8200. Off by
default; staging's values turn it on, production, which runs no vault, may
not. Infrastructure design 3.1's API row says so.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Check that the API's AppRole is named by the api alone, its egress exactly, and its vault address exactly (DEC-2338)

Every workload's volumes, environment, arguments and init containers are scanned for the AppRole's
Secret, which only the api's projection may name. The api's whole egress is its unbound rules with
the one vault rule added, and the vault rule is exactly its workspace's vault pod on 8200. Seeded:
the AppRole mounted into each other process, an ipBlock or namespaceSelector on the vault rule, a
wide peer on 443, a portless rule, and a foreign vault address.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Check that only the copy and the api mount the api pod's vault volumes (DEC-2338)

With the binding on, vault-sources is mounted by the vault-credentials init container alone, read-only,
and vault by it read-write and by the api read-only; with it off, neither anywhere. Seeded with a
copy of the api container, hardened as it is, mounting either volume as a container or an init
container.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Add the role provisioner's connection-role records and start an exchange only once its roles exist (journal v0.77, DEC-2350)

Journal spec §9.33 adds ConnectionRolesMade, ConnectionRolesRefused and ConnectionRolesRemoved on the
control stream at schema version 1, with rule 261 (a system actor and a cause) and stream rules 262
and 263 (each record tied to its OAuth request; a removal after the roles were made, each role once,
on its occasion, and the executor's role on a revocation only once no generation of its executor is
left running). §9.21 starts a token exchange only once its request's ConnectionRolesMade is read.
Connections spec §5.2 step 4 orders the roles first. The reference module connection_roles builds
the vectors: 36 invalid and 4 valid drafts, 19 rule-262 and rule-263 streams, 4 validator, 8 stream
and 7 vector mutants caught. Reserves and fills DEC-2350; adds the provisioner's backlog row.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Name only the added delete as DEC-2350's: the provisioner's + on its role paths is DEC-2630 item 7's

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Never remove the executor's role: the provisioner removes the token exchange's alone (DEC-2350 item 3)

Rule 263 narrows to one removal per made roles, of the token exchange's role, on a closed request or
a revocation. A kill switch's start after a revocation can always log in; the cost, one executor role
per closed or revoked connection granting a read of its own destroyed token, is recorded in DEC-2350
with the sweep left in the backlog. The executor's role is an invalid draft; its stream cases and the
process fold go.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Tie each role removal to its own request's close, refresh the fixture, and grant delete on the exchange's mount alone (DEC-2350, review)

Rule 263 now holds a removal's causation_id to a closer of its connection journaled after the
latest ConnectionRolesMade, so it never cites an earlier request's refusal under a new request of
the same id (DEC-699). The fold and the oracle each read it their own way; four new streams and a
vector mutant pin it. DEC-2350 item 5 adds delete on the token exchange's mount's block only, so
the policy itself keeps the executor's role. fixtures/refcases/journal.json is regenerated.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Drop read from both provisioner role blocks, and send pinned list parameters as strings (DEC-2350 item 5, V2's live check)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* E21-33: the role provisioner's identity and AppRole, delete on the exchange's roles alone, tests pending (DEC-2350)

S2 of DEC-2350, the vault side, option (B): the executor's role is never
removed.

- Identity::Provisioner, provisioned with an AppRole and a secret id
  only when provisioning is given the workspace's namespace
  (KubernetesAuth::namespace, from --secrets-namespace); its policy is
  provisioner_policy for that namespace and nothing else.
- provisioner_policy grants delete on the token exchange's mount's
  block alone (stubbed seam: provisioner_capabilities).
- role_request sends the pinned list parameters, the namespace and the
  policy, as plain strings: OpenBao's allowed_parameters pin refuses a
  JSON array however it is filled, so every role request was refused
  (verified on OpenBao 2.7.1; stubbed seam: pinned).
- A pin test that the provisioner's + sits on exactly the two
  Kubernetes mounts' role paths, and a live test that the provisioner's
  own token makes both roles, is refused a widened role, an executor
  role's removal, a policy and a secret, and removes the exchange's.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the role provisioner's identity and AppRole, delete on the exchange's roles alone (DEC-2350)

S2's implementation: provisioner_capabilities grants delete on the
token exchange's roles alone, and pinned sends a pinned list parameter
as a plain string. Deletes the pending markers.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the provisioner's role blocks grant no read (DEC-2350 item 5)

DEC-2350 item 5 now drops read from both blocks, since OpenBao refuses
a role read under required_parameters however it is granted: the
executor's block is create and update, the token exchange's create,
update and delete. The pin test asserts that, and the live test that
the provisioner reads neither role it makes. Merges #2278's head.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the role provisioner's journal records and rules 261 to 263, tests pending (DEC-2350)

S3 of DEC-2350: journal spec v0.77 §9.33's ConnectionRolesMade,
ConnectionRolesRefused and ConnectionRolesRemoved on the control
stream, against the vectors' connection_roles section. Pending: the
catalogue entry, each base and valid draft parsing, every invalid
draft refused at its path, append judging rule 261 alone, and the
verifier's fold verify_connection_roles answering every stream case of
rules 262 and 263. The fold is stubbed; the parse tests fail on the
unknown event type, with behaviour-only rows.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the role provisioner's journal records and rules 261 to 263 (DEC-2350)

S3's implementation: the journal registers ConnectionRolesMade,
ConnectionRolesRefused and ConnectionRolesRemoved on the control
stream at schema version 1, catalogued with no configuration, and
append checks rule 261 on each (a system actor, then a cause). The
verifier's fold verify_connection_roles judges rules 262 and 263 as
the reference does: an answer names its own version-2 OAuth request,
once; a removal follows its connection's latest roles made, once, and
names a closer of those roles of its own occasion, a connect refusal
or a revocation journaled after them. Deletes the pending markers and
the four behaviour-only rows.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the policy provisioning writes for the provisioner's role is pinned without a server, test pending (DEC-2350)

Review item on S2: only a live server told provisioner_policy from
openbao_policy for the provisioner's role, so the gate missed a
mutant there. provisioned_policy, stubbed here, is the one choice
provision makes, and a unit test pins it: provisioner_policy for the
provisioner given the namespace, openbao_policy for every other
identity and for the provisioner without one.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the policy provisioning writes for the provisioner's role is pinned without a server (DEC-2350)

provision writes each role's policy through provisioned_policy.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the role provisioner makes a connection's roles and removes the exchange's, tests pending (DEC-2350)

S4a of DEC-2350: RoleProvisioner, the role provisioner's session with
the vault, as OpenBaoSource is when it logs in as the provisioner:
make_roles makes the executor's and then the token exchange's role as
role_request forms them, and remove_exchange_role removes the
exchange's. No operation removes an executor's role (DEC-2350 item 3).
RoleRefusal names a failure in ConnectionRolesRefused's two reasons.
Both operations and the error mapping are stubbed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the role provisioner makes a connection's roles and removes the exchange's (DEC-2350)

S4a's implementation: make_roles POSTs the executor's and then the token
exchange's role_request, and remove_exchange_role DELETEs the
exchange's, a 404 being a role already gone. A refusal maps to
vault_unavailable for an outage or a sealed vault and to role_refused
for every other. Removes the temporary Unimplemented variant and the
pending markers.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: a removal names a connect refusal of its own occasion, test pending (DEC-2350)

Mutants found two readings of rule 263 the vectors leave open: a
ConnectionRefused of another occasion taken for a request's close, and
a removal whose occasion does not name its closer's kind. A pending
test edits the removed_on_close case into each and expects rule 263
at the removal.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: a removal names a connect refusal of its own occasion (DEC-2350)

The fold compares a removal's named occasion against its closer's kind
in one step, an occasion that names neither standing for no closer, and
deletes the pending marker.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the role provisioner's plan and pass, tests pending (DEC-2350)

S4b of DEC-2350: mandate-provisioner, the role provisioner's library.
plan folds the control stream into the steps owed: roles to make for
each unanswered version-2 OAuth request, and the exchange's role to
remove once a request whose roles were made closes, or its connection
is revoked. Provisioner::tick performs each on the vault within the
30 s deadline and journals it, with the opaque alert
connection_roles_refused (RB-12) on a refusal. The records' drafts are
real, with event ids derived from the step; the plan and the pass are
stubbed. Pending: a request's roles once, a refusal and a slow vault,
a close and a revocation removing only the exchange's role, a step
the journal missed done again and recorded once, two racing writers,
an unopened stream, and the plan against every valid vector stream.

The crate is at layer 9, above mandate-vault, with its layers row and
CODEOWNERS line.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the role provisioner's plan and pass (DEC-2350)

S4b's implementation. plan folds the control stream as the journal's
verify_connection_roles reads rules 262 and 263: a version-2 OAuth
request with no answer is owed its roles, unless it closed first, and
a connection whose roles were made is owed the exchange's removal once
its request closes or it is revoked, with that first closer as cause.
tick performs the first step owed on the vault within the deadline,
journals it, and reads the stream again, so each record is drafted
against the stream as it is; another writer's record under it is read
again at most APPEND_TRIES times. A refusal raises
connection_roles_refused (RB-12) with no ids; a removal the vault does
not do is retried by the next pass. No step removes an executor's
role. Deletes the temporary Unimplemented variants and the pending
markers.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the live suite's vault set-up names the provisioning error it fails on

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the plan's own connection, version and occasion readings, tests pending; the feature map's entry (DEC-2350)

Mutants found readings of the plan the vectors leave open: an API-key
or version-1 request, a close of one connection's request before its
answer, two connections' made roles, and a refusal of another occasion.
Three pending tests pin each over synthetic streams. The role
provisioner's feature-map entry names its code, tests and runs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: a provisioner's pass ends after the steps it owed (DEC-2350)

A pass takes at most the steps it first owed and APPEND_TRIES more, so
a stream that never shows a record committed ends the pass as a
journal halt rather than looping; the plan tests' pending markers go.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: only the provisioner's session makes or removes roles, test pending

A session that never logged in, built for unit tests, lets the identity check be
tested with no vault running.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: only the provisioner's session makes or removes roles

A role operation from any other session is refused role_refused before the vault
is reached. The DELETE's doc now says why a missing auth mount counts as removed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the role provisioner's process, mandate-provisionerd (DEC-2350)

The provisioner over the Postgres control stream, written as mandate-deployd
writes it, and the vault, logged in by AppRole as the provisioner and again every
hour. A session near its token's end that no login replaced stops the process,
failed, so it never calls the vault with a lapsed token. --fixture runs it over
memory and a vault that keeps nothing, and a release build refuses it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the fixture run is a debug build's block, not a release stub

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the workspace services' pinned list names the role provisioner

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the workspace services' pinned list names the provisioner's process

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the API's owner-only vault copy becomes the chart's shared helper, by role

mandate.vaultCopy and mandate.vaultVolumes take the process's vault role; the
API's render is byte-for-byte the same.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the workspace chart's role provisioner, its AppRole alone and no Kubernetes credential (DEC-2350)

The role-provisioner process: one Deployment per workspace once its image is
named, reading ctl:<api.workspace> as mandate_role_provisioner, binding roles in
its own namespace, with its AppRole and the vault's CA copied owner-only by the
shared vault helper, no service-account token, and egress to the journal and its
vault on 8200 alone. Infrastructure design §3.1 gains its row; roles.sql, the
Compose file and initdb.sh are regenerated.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the role provisioner's image, and its process marked built (DEC-2350)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the role provisioner's pod holds exactly its own volumes, and no token or ConfigMap besides (DEC-2350, review G1)

Its volume set is pinned by name and kind, and its projected volume to its Secret
and the vault's CA, with seeds for a projected service-account token, a token
in its vault sources, an extra ConfigMap, a host path, a downward API volume,
and a second kind on one volume. Infrastructure design §3.1 says its writing only
the three role records is its convention, not a database grant.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* CI (lane GO): run api-release in a full-checks-3 job of its own (DEC-2334, DEC-2338)

#2289 added `api-release` to `cargo xtask ci full` (DEC-2338 item 5) after
#2195 had split `full`'s parts into `full-checks` and `full-checks-2`
(DEC-2334). No CI job ran `api-release`, and
`the_full_jobs_run_exactly_the_full_group` failed on main.

Its release build of `mandate-api-server` takes about five minutes on its own.
Neither existing job has room for that under ten minutes (DEC-464):
`full-checks-2`'s `reference` alone is about 4.5 minutes, before its two
release builds. So it runs in `full-checks-3`, on the same selection, and
`full` needs all three. The verdict table now shows a failed, cancelled or
skipped `full-checks-3` failing `full`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UoxcMViCbnGUTPSaduuGYU

* Supply chain: bump ctutils 0.4.2 -> 0.4.5 for RUSTSEC-2026-0385

`cargo deny --locked check` fails on main. In ctutils 0.4.2, the constant-time
`Choice` mask lacked an optimization barrier (RUSTSEC-2026-0385,
RustCrypto/utils#1569); 0.4.4 and later fix it. ctutils is a transitive
RustCrypto crate, reached through aes-gcm, crypto-bigint, digest, sec1 and
universal-hash (webpush, p256, sqlx-postgres). `cargo update -p ctutils` makes
a patch-level bump. Nothing else in Cargo.lock moves, and no manifest or
dependency row changes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UoxcMViCbnGUTPSaduuGYU

---------

Co-authored-by: Claude <[email protected]>
kunwarshivam added a commit that referenced this pull request Oct 23, 2026
#2313)

* Test the env-name pin, the vault CA mount and the exchange's launch on the deploy-manager's pods

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Pin each managed container's environment, mount the vault's CA, and launch the exchange with its vault flags

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Cite DEC-2332 for the env pin, the vault CA and the exchange's launch

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Start an OAuth connect's executor on its exchange's success (journal v0.74, DEC-2335)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Name J2 PR #2200 as an ES-22 atomic exception (DEC-2335 item 6)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Test the connect's executor: started on its exchange's success, stopped when its connection closes (E21-33 K2a, DEC-2336)

The tests half of DEC-77's pair. Phase::Succeeded joins the orchestrator port
and the fake can succeed an instance; the manager's fold and decision have no
stub form, so the tests that need a success read are pending as behaviour-only
rows until K2a's implementation lands.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Start the connect's executor on its exchange's success and stop it when its connection closes (E21-33 K2a, DEC-2336)

The implementation half of DEC-77's pair: the manager reads the request's
account, journals the executor's exchanged start while the request is open and
the account's executor never ran, wants it until a refusal or revocation, and
names each executor stop by the last reason taken away. The pending tests'
ignore lines and behaviour-only rows go.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Name K2b's executor self-check dependency and the Deployment executor's account in DEC-2336's risks

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Test that each executor runs as its connection's own account (E21-33 K2b, DEC-2336 items 6 to 10)

The tests half of DEC-77's pair. The orchestrator port's start takes a Launch
of the exchange's lifetime and the executor's connection, and its stop the
connection; the driver passes none yet and the adapter ignores it, so the
tests that need it are pending as behaviour-only rows. DEC-2336 gains K2b's
items and the coordinator's F2 wording.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Run each manager-started executor as its connection's own account, and pin it at admission (E21-33 K2b, DEC-2336 items 6 to 10)

The implementation half of DEC-77's pair. The manager names an executor's
connection on its start and stop and asks once per account per pass; the
adapter makes and deletes the executor's hex account, names it and the
namespace in its environment, and reads a succeeded pod as Succeeded. The
chart pins a manager-started executor to an even-length hex account its
environment names, admits that account with the executor's label, and mounts
the vault token only on pods the manager starts. The pending tests' ignore
lines and behaviour-only rows go.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Test that an executor's start and stop name the same connection, the deployment's or the connect's (E21-33 K2b, review F1)

Four pending tests: a revoked connection the deployment does not name is never
chosen; a restart after the connect ended names the deployment's connection,
across the manager's restart; a stop names its generation's connection once
nothing else does; and no executor takes another account's connection.
DEC-2336 item 6 says which connection a start names.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Name each executor generation's connection when its start is folded, from its deployment or its connect (E21-33 K2b, review F1)

An executor's start names the connection a running agent's deployment of its
account names, else its open connect's; the manager records it per generation
as it folds the start, so the stop names the same account and a restarted
manager names the same one. A revoked connection no deployment names is no
longer chosen. The pending tests' ignore lines and rows go.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Test that a kill switch reaches an executor no agent wants, once per switch (DEC-2344 items 1 and 3)

Pending E21-33. A covering kill switch gives an account's executor that every agent's retirement
left down exactly one start, cause kill_switch, journaled first and caused by the switch; each
later covering switch one more; a generation that ends on its own is never restarted; nothing
else starts it; and a switch the executor has not answered keeps the account's last retirement
from stopping it. A property over random deploys, retirements, kill switches of every scope,
ends, clock steps and manager restarts checks both against an oracle computed from the operations
alone. Each pending test has its behaviour-only row: the manager answers, no stub.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Test that a kill-switch start crash-looping before it ends raises kill_switch_unapplied (DEC-2344 item 1)

Pending E21-33. A crash of the start is restarted, so one crash loses no switch; at the crash-loop
bound the manager journals ProcessCrashLooped and raises the opaque operator alert
kill_switch_unapplied, once, in place of crash_loop.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Extend the kill-switch property to crashes and the unapplied-switch alert (DEC-2344 item 1)

The executor no agent wants may now crash before it ends. The oracle allows a crash's restart only
before its loop and with no switch pending, requires the switch's one start when one is, never lets
the manager stop a generation a switch keeps, admits a crash loop only after a kill switch's
crashing start, and requires each such loop to raise kill_switch_unapplied once and every other
alert never.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Read a successful end as answering only the switches committed before its start (DEC-2344, journal v0.76 §9.21)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Add the role provisioner's connection-role records and start an exchange only once its roles exist (journal v0.77, DEC-2350)

Journal spec §9.33 adds ConnectionRolesMade, ConnectionRolesRefused and ConnectionRolesRemoved on the
control stream at schema version 1, with rule 261 (a system actor and a cause) and stream rules 262
and 263 (each record tied to its OAuth request; a removal after the roles were made, each role once,
on its occasion, and the executor's role on a revocation only once no generation of its executor is
left running). §9.21 starts a token exchange only once its request's ConnectionRolesMade is read.
Connections spec §5.2 step 4 orders the roles first. The reference module connection_roles builds
the vectors: 36 invalid and 4 valid drafts, 19 rule-262 and rule-263 streams, 4 validator, 8 stream
and 7 vector mutants caught. Reserves and fills DEC-2350; adds the provisioner's backlog row.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Name only the added delete as DEC-2350's: the provisioner's + on its role paths is DEC-2630 item 7's

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Never remove the executor's role: the provisioner removes the token exchange's alone (DEC-2350 item 3)

Rule 263 narrows to one removal per made roles, of the token exchange's role, on a closed request or
a revocation. A kill switch's start after a revocation can always log in; the cost, one executor role
per closed or revoked connection granting a read of its own destroyed token, is recorded in DEC-2350
with the sweep left in the backlog. The executor's role is an invalid draft; its stream cases and the
process fold go.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Tie each role removal to its own request's close, refresh the fixture, and grant delete on the exchange's mount alone (DEC-2350, review)

Rule 263 now holds a removal's causation_id to a closer of its connection journaled after the
latest ConnectionRolesMade, so it never cites an earlier request's refusal under a new request of
the same id (DEC-699). The fold and the oracle each read it their own way; four new streams and a
vector mutant pin it. DEC-2350 item 5 adds delete on the token exchange's mount's block only, so
the policy itself keeps the executor's role. fixtures/refcases/journal.json is regenerated.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Start an executor no agent wants once per covering kill switch, and hold it until its switches are answered (DEC-2344 items 1 and 3)

The manager starts such an executor, cause kill_switch, journaled first, once for the covering
switches it has not answered, as the account of the connection its last generation ran as. It never
stops a generation a switch started or one a switch read while it ran is waiting on; a success ends
it, a crash restarts with back-off, and a crash loop raises kill_switch_unapplied in place of
crash_loop, again once from a restarted manager. The six pending retired.rs tests run, their
behaviour-only rows go, and new tests cover a revoked connection, the restarted alert, a wanted
executor's successful exit, and the oracle's seeded bugs. Two older tests follow DEC-2344's change.
The alert has its RB-14 section and its infrastructure design §8.2 row.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Drop read from both provisioner role blocks, and send pinned list parameters as strings (DEC-2350 item 5, V2's live check)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Move the implementation's new tests here as pending: a revoked connection's start, the restarted alert, the oracle's seeded bugs (DEC-2344 item 1, review)

The four tests the implementation PR wrote fail on today's manager and are pending E21-33 with
behaviour-only rows: the N3 revoked-connection start, the restarted manager's one re-raise, the
seeded-bug harness, and manager.rs's stopped-agent test with DEC-2344's extra start. The property
expects the re-raise, and properties.rs exempts an executor's unwanted start, which retired.rs's
property checks more strictly. A wanted executor's restart after a success already passes, so it
is a plain test. Record::CrashLooped carries the alert it raises, crash_loop for every loop today,
so the harness can seed a loop.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Reflow RB-14's kill_switch_unapplied escalation step

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Test that a later switch after an unapplied loop starts a new run, that a held crash waits its back-off, and that a redeploy ends a kill-switch run (DEC-2344 item 1, review)

Three more tests, pending E21-33 with behaviour-only rows, each failing on today's manager: a
covering switch after an unapplied crash loop starts a new run that is restarted and can loop and
alert again; a crashed kill-switch generation waits backoff(0) before exactly one restart; and a
deployment during a kill-switch run makes its later loop an ordinary crash_loop, after a manager
restart too. The property's oracle lets such a start begin a new run.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Begin a new run at a kill switch's start after an unapplied loop, and merge #2257's new tests (DEC-2344 item 1, review)

A kill_switch start of an executor no agent wants clears the last run's restarts, crash loop and
alert, so a later covering switch after an unapplied loop is restarted after a crash, loops again,
and is reported again: a second ProcessCrashLooped and a second kill_switch_unapplied. The merge
takes #2257's three new tests and deletes their ignore lines and rows; a held crash's back-off and
a redeploy's ordinary loop already held and are now pinned.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Test that a deploy and retire during a kill switch's generation does not stop it (DEC-2344 item 3, review)

Pending E21-33 with its behaviour-only row: the generation a covering switch started stays
running through an agent's deployment and retirement on the account, and its own end is not
restarted.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Let the property's oracle keep a kill-switch run held through a deployment (DEC-2344 item 3, review)

A deployment on the account no longer ends a held run in the oracle's view: only its own
successful end does, so a crash of it is still restarted after the agent retires.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Keep a kill switch's run held through a deployment and retirement, and classify its loop at the loop (DEC-2344 item 3, review)

resume no longer clears held, and a kill_switch start made while the executor is wanted no longer
clears it either, so an agent deployed and retired while a switch's generation runs never gets
that generation stopped before it ends itself (rule 13). A stop the manager journals is only ever
of a generation it does not hold, so folding it clears held, and a replay agrees. A crash loop
leaves a switch unapplied only when no agent wants the executor at the loop. #2257's new test runs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* E21-33: the role provisioner's journal records and rules 261 to 263, tests pending (DEC-2350)

S3 of DEC-2350: journal spec v0.77 §9.33's ConnectionRolesMade,
ConnectionRolesRefused and ConnectionRolesRemoved on the control
stream, against the vectors' connection_roles section. Pending: the
catalogue entry, each base and valid draft parsing, every invalid
draft refused at its path, append judging rule 261 alone, and the
verifier's fold verify_connection_roles answering every stream case of
rules 262 and 263. The fold is stubbed; the parse tests fail on the
unknown event type, with behaviour-only rows.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the role provisioner's journal records and rules 261 to 263 (DEC-2350)

S3's implementation: the journal registers ConnectionRolesMade,
ConnectionRolesRefused and ConnectionRolesRemoved on the control
stream at schema version 1, catalogued with no configuration, and
append checks rule 261 on each (a system actor, then a cause). The
verifier's fold verify_connection_roles judges rules 262 and 263 as
the reference does: an answer names its own version-2 OAuth request,
once; a removal follows its connection's latest roles made, once, and
names a closer of those roles of its own occasion, a connect refusal
or a revocation journaled after them. Deletes the pending markers and
the four behaviour-only rows.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* Test a kill switch's start during an open connect, a connect during a held run, a connect's ordinary loop, and a stop a restart must not undo (DEC-2351 item 2, review)

Three pending E21-33 with rows, each failing on today's manager, and one plain test that passes
today: an executor only a connect wants loops as crash_loop.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Hold a kill switch's run by the agents alone, and say so in the loop's alert, and merge #2257's tests (DEC-2351 item 2, review)

A kill_switch start of an executor holds its run when no agent of its account is deployed and not
stopped, whatever connect request wants it, and the crash loop's alert says kill_switch_unapplied
when the run is held and no agent wants the executor at the loop, as journal spec rule 264 reads it;
a runtime's start never holds. A deployment on a connection revoked since counts until its
AgentStopped, as the rule reads it. The merge takes #2257's four new tests.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* E21-33: a removal names a connect refusal of its own occasion, test pending (DEC-2350)

Mutants found two readings of rule 263 the vectors leave open: a
ConnectionRefused of another occasion taken for a request's close, and
a removal whose occasion does not name its closer's kind. A pending
test edits the removed_on_close case into each and expects rule 263
at the removal.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: a removal names a connect refusal of its own occasion (DEC-2350)

The fold compares a removal's named occasion against its closer's kind
in one step, an occasion that names neither standing for no closer, and
deletes the pending marker.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the token exchange waits for its request's roles, tests pending (DEC-2350 S6)

A connect's token exchange starts only once its request's ConnectionRolesMade is
read (journal spec §9.21, §9.33). The new roles suite and the exchange property's
oracle say so; the existing connect tests gain the Made row their scenarios now
need. The pending tests fail on the manager's behaviour, which has no stub to stop
at, and are named in xtask/behaviour-only until the implementation lands.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the seeded double launch waits for a first launch to copy (DEC-2350 S6)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the token exchange waits for its request's roles (DEC-2350 S6)

The manager reads the role provisioner's ConnectionRolesMade and keeps each
request's event id; a connect's token exchange is wanted only once the Made that
answers its own request is read, so a refusal, a record for another request, or
none starts nothing, and the connect ends by its deadline (journal spec §9.21).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* CI (lane GO): run api-release in a full-checks-3 job of its own (DEC-2334, DEC-2338)

#2289 added `api-release` to `cargo xtask ci full` (DEC-2338 item 5) after
#2195 had split `full`'s parts into `full-checks` and `full-checks-2`
(DEC-2334). No CI job ran `api-release`, and
`the_full_jobs_run_exactly_the_full_group` failed on main.

Its release build of `mandate-api-server` takes about five minutes on its own.
Neither existing job has room for that under ten minutes (DEC-464):
`full-checks-2`'s `reference` alone is about 4.5 minutes, before its two
release builds. So it runs in `full-checks-3`, on the same selection, and
`full` needs all three. The verdict table now shows a failed, cancelled or
skipped `full-checks-3` failing `full`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UoxcMViCbnGUTPSaduuGYU

* Supply chain: bump ctutils 0.4.2 -> 0.4.5 for RUSTSEC-2026-0385

`cargo deny --locked check` fails on main. In ctutils 0.4.2, the constant-time
`Choice` mask lacked an optimization barrier (RUSTSEC-2026-0385,
RustCrypto/utils#1569); 0.4.4 and later fix it. ctutils is a transitive
RustCrypto crate, reached through aes-gcm, crypto-bigint, digest, sec1 and
universal-hash (webpush, p256, sqlx-postgres). `cargo update -p ctutils` makes
a patch-level bump. Nothing else in Cargo.lock moves, and no manifest or
dependency row changes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UoxcMViCbnGUTPSaduuGYU

---------

Co-authored-by: Claude <[email protected]>
kunwarshivam added a commit that referenced this pull request Oct 23, 2026
…EC-2350 S4b-2 follow-up) (#2308)

* Add the role provisioner's connection-role records and start an exchange only once its roles exist (journal v0.77, DEC-2350)

Journal spec §9.33 adds ConnectionRolesMade, ConnectionRolesRefused and ConnectionRolesRemoved on the
control stream at schema version 1, with rule 261 (a system actor and a cause) and stream rules 262
and 263 (each record tied to its OAuth request; a removal after the roles were made, each role once,
on its occasion, and the executor's role on a revocation only once no generation of its executor is
left running). §9.21 starts a token exchange only once its request's ConnectionRolesMade is read.
Connections spec §5.2 step 4 orders the roles first. The reference module connection_roles builds
the vectors: 36 invalid and 4 valid drafts, 19 rule-262 and rule-263 streams, 4 validator, 8 stream
and 7 vector mutants caught. Reserves and fills DEC-2350; adds the provisioner's backlog row.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Name only the added delete as DEC-2350's: the provisioner's + on its role paths is DEC-2630 item 7's

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Never remove the executor's role: the provisioner removes the token exchange's alone (DEC-2350 item 3)

Rule 263 narrows to one removal per made roles, of the token exchange's role, on a closed request or
a revocation. A kill switch's start after a revocation can always log in; the cost, one executor role
per closed or revoked connection granting a read of its own destroyed token, is recorded in DEC-2350
with the sweep left in the backlog. The executor's role is an invalid draft; its stream cases and the
process fold go.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Tie each role removal to its own request's close, refresh the fixture, and grant delete on the exchange's mount alone (DEC-2350, review)

Rule 263 now holds a removal's causation_id to a closer of its connection journaled after the
latest ConnectionRolesMade, so it never cites an earlier request's refusal under a new request of
the same id (DEC-699). The fold and the oracle each read it their own way; four new streams and a
vector mutant pin it. DEC-2350 item 5 adds delete on the token exchange's mount's block only, so
the policy itself keeps the executor's role. fixtures/refcases/journal.json is regenerated.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* Drop read from both provisioner role blocks, and send pinned list parameters as strings (DEC-2350 item 5, V2's live check)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LmJtE8JXVQjyqTteJ5b9Cm

* E21-33: the role provisioner's identity and AppRole, delete on the exchange's roles alone, tests pending (DEC-2350)

S2 of DEC-2350, the vault side, option (B): the executor's role is never
removed.

- Identity::Provisioner, provisioned with an AppRole and a secret id
  only when provisioning is given the workspace's namespace
  (KubernetesAuth::namespace, from --secrets-namespace); its policy is
  provisioner_policy for that namespace and nothing else.
- provisioner_policy grants delete on the token exchange's mount's
  block alone (stubbed seam: provisioner_capabilities).
- role_request sends the pinned list parameters, the namespace and the
  policy, as plain strings: OpenBao's allowed_parameters pin refuses a
  JSON array however it is filled, so every role request was refused
  (verified on OpenBao 2.7.1; stubbed seam: pinned).
- A pin test that the provisioner's + sits on exactly the two
  Kubernetes mounts' role paths, and a live test that the provisioner's
  own token makes both roles, is refused a widened role, an executor
  role's removal, a policy and a secret, and removes the exchange's.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the role provisioner's identity and AppRole, delete on the exchange's roles alone (DEC-2350)

S2's implementation: provisioner_capabilities grants delete on the
token exchange's roles alone, and pinned sends a pinned list parameter
as a plain string. Deletes the pending markers.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the provisioner's role blocks grant no read (DEC-2350 item 5)

DEC-2350 item 5 now drops read from both blocks, since OpenBao refuses
a role read under required_parameters however it is granted: the
executor's block is create and update, the token exchange's create,
update and delete. The pin test asserts that, and the live test that
the provisioner reads neither role it makes. Merges #2278's head.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the role provisioner's journal records and rules 261 to 263, tests pending (DEC-2350)

S3 of DEC-2350: journal spec v0.77 §9.33's ConnectionRolesMade,
ConnectionRolesRefused and ConnectionRolesRemoved on the control
stream, against the vectors' connection_roles section. Pending: the
catalogue entry, each base and valid draft parsing, every invalid
draft refused at its path, append judging rule 261 alone, and the
verifier's fold verify_connection_roles answering every stream case of
rules 262 and 263. The fold is stubbed; the parse tests fail on the
unknown event type, with behaviour-only rows.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the role provisioner's journal records and rules 261 to 263 (DEC-2350)

S3's implementation: the journal registers ConnectionRolesMade,
ConnectionRolesRefused and ConnectionRolesRemoved on the control
stream at schema version 1, catalogued with no configuration, and
append checks rule 261 on each (a system actor, then a cause). The
verifier's fold verify_connection_roles judges rules 262 and 263 as
the reference does: an answer names its own version-2 OAuth request,
once; a removal follows its connection's latest roles made, once, and
names a closer of those roles of its own occasion, a connect refusal
or a revocation journaled after them. Deletes the pending markers and
the four behaviour-only rows.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the policy provisioning writes for the provisioner's role is pinned without a server, test pending (DEC-2350)

Review item on S2: only a live server told provisioner_policy from
openbao_policy for the provisioner's role, so the gate missed a
mutant there. provisioned_policy, stubbed here, is the one choice
provision makes, and a unit test pins it: provisioner_policy for the
provisioner given the namespace, openbao_policy for every other
identity and for the provisioner without one.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the policy provisioning writes for the provisioner's role is pinned without a server (DEC-2350)

provision writes each role's policy through provisioned_policy.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the role provisioner makes a connection's roles and removes the exchange's, tests pending (DEC-2350)

S4a of DEC-2350: RoleProvisioner, the role provisioner's session with
the vault, as OpenBaoSource is when it logs in as the provisioner:
make_roles makes the executor's and then the token exchange's role as
role_request forms them, and remove_exchange_role removes the
exchange's. No operation removes an executor's role (DEC-2350 item 3).
RoleRefusal names a failure in ConnectionRolesRefused's two reasons.
Both operations and the error mapping are stubbed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the role provisioner makes a connection's roles and removes the exchange's (DEC-2350)

S4a's implementation: make_roles POSTs the executor's and then the token
exchange's role_request, and remove_exchange_role DELETEs the
exchange's, a 404 being a role already gone. A refusal maps to
vault_unavailable for an outage or a sealed vault and to role_refused
for every other. Removes the temporary Unimplemented variant and the
pending markers.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: a removal names a connect refusal of its own occasion, test pending (DEC-2350)

Mutants found two readings of rule 263 the vectors leave open: a
ConnectionRefused of another occasion taken for a request's close, and
a removal whose occasion does not name its closer's kind. A pending
test edits the removed_on_close case into each and expects rule 263
at the removal.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: a removal names a connect refusal of its own occasion (DEC-2350)

The fold compares a removal's named occasion against its closer's kind
in one step, an occasion that names neither standing for no closer, and
deletes the pending marker.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the role provisioner's plan and pass, tests pending (DEC-2350)

S4b of DEC-2350: mandate-provisioner, the role provisioner's library.
plan folds the control stream into the steps owed: roles to make for
each unanswered version-2 OAuth request, and the exchange's role to
remove once a request whose roles were made closes, or its connection
is revoked. Provisioner::tick performs each on the vault within the
30 s deadline and journals it, with the opaque alert
connection_roles_refused (RB-12) on a refusal. The records' drafts are
real, with event ids derived from the step; the plan and the pass are
stubbed. Pending: a request's roles once, a refusal and a slow vault,
a close and a revocation removing only the exchange's role, a step
the journal missed done again and recorded once, two racing writers,
an unopened stream, and the plan against every valid vector stream.

The crate is at layer 9, above mandate-vault, with its layers row and
CODEOWNERS line.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the role provisioner's plan and pass (DEC-2350)

S4b's implementation. plan folds the control stream as the journal's
verify_connection_roles reads rules 262 and 263: a version-2 OAuth
request with no answer is owed its roles, unless it closed first, and
a connection whose roles were made is owed the exchange's removal once
its request closes or it is revoked, with that first closer as cause.
tick performs the first step owed on the vault within the deadline,
journals it, and reads the stream again, so each record is drafted
against the stream as it is; another writer's record under it is read
again at most APPEND_TRIES times. A refusal raises
connection_roles_refused (RB-12) with no ids; a removal the vault does
not do is retried by the next pass. No step removes an executor's
role. Deletes the temporary Unimplemented variants and the pending
markers.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the live suite's vault set-up names the provisioning error it fails on

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the plan's own connection, version and occasion readings, tests pending; the feature map's entry (DEC-2350)

Mutants found readings of the plan the vectors leave open: an API-key
or version-1 request, a close of one connection's request before its
answer, two connections' made roles, and a refusal of another occasion.
Three pending tests pin each over synthetic streams. The role
provisioner's feature-map entry names its code, tests and runs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: a provisioner's pass ends after the steps it owed (DEC-2350)

A pass takes at most the steps it first owed and APPEND_TRIES more, so
a stream that never shows a record committed ends the pass as a
journal halt rather than looping; the plan tests' pending markers go.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: only the provisioner's session makes or removes roles, test pending

A session that never logged in, built for unit tests, lets the identity check be
tested with no vault running.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: only the provisioner's session makes or removes roles

A role operation from any other session is refused role_refused before the vault
is reached. The DELETE's doc now says why a missing auth mount counts as removed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the role provisioner's process, mandate-provisionerd (DEC-2350)

The provisioner over the Postgres control stream, written as mandate-deployd
writes it, and the vault, logged in by AppRole as the provisioner and again every
hour. A session near its token's end that no login replaced stops the process,
failed, so it never calls the vault with a lapsed token. --fixture runs it over
memory and a vault that keeps nothing, and a release build refuses it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the fixture run is a debug build's block, not a release stub

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the workspace services' pinned list names the role provisioner

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: the workspace services' pinned list names the provisioner's process

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* E21-33: CI builds the role provisioner in release and checks it refuses its fixture

cargo xtask ci provisionerd-release, in full and in check, like stub-release:
it builds mandate-provisionerd with --release when the diff selects it, runs it
with --fixture, and requires exit 2 with fixture_not_built alone on stderr.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

* CI (lane GO): run api-release in a full-checks-3 job of its own (DEC-2334, DEC-2338)

#2289 added `api-release` to `cargo xtask ci full` (DEC-2338 item 5) after
#2195 had split `full`'s parts into `full-checks` and `full-checks-2`
(DEC-2334). No CI job ran `api-release`, and
`the_full_jobs_run_exactly_the_full_group` failed on main.

Its release build of `mandate-api-server` takes about five minutes on its own.
Neither existing job has room for that under ten minutes (DEC-464):
`full-checks-2`'s `reference` alone is about 4.5 minutes, before its two
release builds. So it runs in `full-checks-3`, on the same selection, and
`full` needs all three. The verdict table now shows a failed, cancelled or
skipped `full-checks-3` failing `full`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UoxcMViCbnGUTPSaduuGYU

* Supply chain: bump ctutils 0.4.2 -> 0.4.5 for RUSTSEC-2026-0385

`cargo deny --locked check` fails on main. In ctutils 0.4.2, the constant-time
`Choice` mask lacked an optimization barrier (RUSTSEC-2026-0385,
RustCrypto/utils#1569); 0.4.4 and later fix it. ctutils is a transitive
RustCrypto crate, reached through aes-gcm, crypto-bigint, digest, sec1 and
universal-hash (webpush, p256, sqlx-postgres). `cargo update -p ctutils` makes
a patch-level bump. Nothing else in Cargo.lock moves, and no manifest or
dependency row changes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01UoxcMViCbnGUTPSaduuGYU

* Run the role provisioner's release check in full-checks-3

Main's full-job split has no room for it in full-checks-2, so it runs beside the API's release
build, about two minutes more, under ten (DEC-464).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B5V2t72QcDywyLPViJtmFF

---------

Co-authored-by: Claude <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants