Skip to content

feat!: make products safe to compose under deferred task grants, fresh grant status, and original receipts - #1976

Merged
jeremi merged 11 commits into
mainfrom
feat/coordinator-seams
Oct 12, 2026
Merged

jeremi merged 11 commits into
mainfrom
feat/coordinator-seams

Conversation

@jeremi

@jeremi jeremi commented Oct 11, 2026 •

Copy link
Copy Markdown
Member

Why

An orchestrator that drives Casework, Scheduling, Messaging, and the Base
Registry Engine (BReg) together has to wait for people, survive restarts, and
recover from lost responses. These seams let any orchestrator do that without
taking authority from the products it composes:

  • Casework can approve a task grant for delayed work.
  • Every resource server checks that grant's current status through one shared
    transport, configured by one shared block.
  • Scheduling refuses capacity work on a long-lived grant it cannot check.
  • A caller can read the original receipt for its own keyed request without
    submitting the command again.

Nothing here is specific to the Coordinator. The Coordinator PR follows this
one and is the first consumer.

Pieces

Main fixed the BReg client's refusal of statisticalDatasets metadata
independently, so this PR no longer carries its own fix.

Render serve fixtures get distinct ports

Port discovery releases the socket before the child process binds it, so
parallel registry-render serve fixtures could reach each other's server. The
fixtures now keep every issued port distinct, retry on a fresh port when
another socket takes theirs before the bind, and report any other child that
exits before it becomes healthy, with its output. Test-only.

The Casework installer test covers macOS on both sides of the bundle boundary

The Casework installer takes raw binaries through v0.32.9 and a bundle carrying
the FIPS runtime from v0.33.0; BReg's installer test already covers its own
bundle, and Casework's covered Linux only. A test now installs a v0.32.9 and a
v0.33.0 macOS release and checks each layout. The fixture writes a bundle for
the later release, and the test mv shim translates the pointer switch in both
directions, so the forced macOS run also passes on a Linux runner. Test-only.

Pinned prerequisite images come from digest-identical mirrors

CI services, the release recipe roots, and the bregctl, caseworkctl, and
messagingctl dev databases now pull PostgreSQL, Debian, and Mailpit from the
ECR Public Docker Official Images mirror and from GHCR. Each image keeps the
same manifest digest, so only the registry endpoint changes. The bregctl and
caseworkctl dev start help and the messagingctl dev help name the exact
mirror reference each session pulls, so an egress allowlist can be written from
--help; a test holds each help to the binary's own image constant. A retained
dev container is accepted under either exact reference. release/OPERATIONS.md documents the procedure under
"Pull pinned prerequisite images". Digests and the PostGIS exception are listed
under Security review notes.

Shared task-grant status transport and the TaskGrantStatusConfig block

registry-platform-oidc gains a task_grant module with the exact status
transport that BReg and Scheduling share:

  • TaskGrantStatusEndpoint
  • TaskGrantStatusRegistry, with an offline validate_configuration, plus
    activate and check
  • TaskGrantStatusClient
  • TaskGrantStatusBinding
  • TaskGrantError

registry-platform-config defines the taskGrantStatus entry once, as the
TaskGrantStatusConfig shared block. It joins the CFG-SCHEMA-5 list in
products/platform/CONFIG-CONVENTIONS.md and is registered with the
conformance tooling and the canonical shared-block schema. Its doc comments
name no particular authority; Casework appears only as the default.

The block maps onto TaskGrantStatusEndpoint through a From impl. That impl
lives in registry-platform-oidc, because registry-platform-oidc already
depends on registry-platform-config and the reverse dependency would form a
cycle.

The YAML keys are unchanged, so existing adopter files still load. The block
adds two optional members, statusScope and statusPath. They default to
Casework's casework:grants:status scope and its
/v1/task-grants/{grantId}/status route.

BReg keeps its persisted binding and its BReg-only bounds validation, and now
calls the shared transport. registry-platform-httputil exposes
validate_requested_scopes and PrivateKeyJwtConfig::check_identity, so a
configuration can be validated without a key, a certificate, or network
access.

AGENTS.md records why Scheduling calling a status endpoint over HTTP is not a
crate dependency on Casework. The dependency-direction gate is unchanged.

Casework deferred task grants

A task template can opt into authorizationMode: deferred with a
lifetimeSeconds of at most 604800 (seven days). Omitting the mode keeps it
immediate, with its 900-second ceiling. The preview, the grant view, the Rust
client, the Node.js and Python clients, the facade, and the OpenAPI document
all carry the mode. Approval still names only the template id and version.

Migration 0022 does three things:

  • It replaces the two grant check constraints with
    casework_task_grants_authorization_window and
    casework_review_task_grants_authorization_window. These bound expiry by
    mode in elapsed seconds, so database timezone and daylight saving changes do
    not affect them.
  • It adds casework_review_tasks.assignment_generation.
  • It adds the BEFORE UPDATE trigger casework_review_assignment_generation,
    which maintains that generation.

A deferred review grant pins the generation. A release, reassignment, queue
move, absence-cover change, decision, or completion therefore ends the grant,
and a later reclaim does not revive it. A draft revision does not end it.
Retained work-item grants reuse the existing invalidation triggers.

The approving officer's own source access is checked once, at approval. Later
observations read the source through the service reader, so a withdrawal of
that officer's source access alone leaves a deferred grant active until its
deadline, up to seven days rather than 900 seconds. TASK_GRANTS.md states
this and tells operators to revoke the officer's live grants, or remove their
Casework eligibility, when source access is withdrawn; either ends each grant
at its next observation.

Scheduling checks fresh grant status before capacity work

The runtime file must now state a top-level taskGrantStatus. It takes one of
two forms, and omitting it is refused at startup as
scheduling.runtime.missing-task-grant-status:

  • A list of one to 32 task authorities, each in the shared block. Every
    capacity commitment and every idempotent replay first gets a fresh exact
    status answer from the grant's authority. No source falls back: a grant from
    an unlisted authority is refused, however short its remaining lifetime.
  • The keyword unchecked-final-fifteen-minutes. Scheduling accepts a grant
    only while it has at most 900 seconds left when the request arrives, and
    checks no revocation. The keyword names the weakening, so a reader of the
    file sees what it accepts.

An empty list is refused, naming both forms. On either path, the capacity
transaction rechecks the earlier of the token and grant deadlines after it
acquires its locks. SCHEDULING-SEC-03 is now enforced, and the SCHEDULING-DEF-01
deferral is closed.

Original receipts without replay

  • Scheduling serves POST /v1/appointments/receipt under ordinary read
    authentication.
  • Messaging serves POST /v1/messages/receipt under current ordinary read
    authority.

Either route returns the retained original success receipt for the caller's
verified identity, original Idempotency-Key, and exact original request body.
Neither route creates, replays, renders, submits, sends, or claims capacity. A
missing, expired, changed, or refused receipt answers receipt.unresolved
(409). That answer is not evidence that no effect occurred.

Receipt reads are not throttled. Neither route spends or requires a rate
budget, and neither answers 429. Messaging's per-caller budget stays a
submission budget, charged only when a message is accepted, and Scheduling keeps
no request budget of its own on any route. Each read is authenticated, costs
one bounded indexed lookup, and is journaled. An operator bounds an
authenticated flood at the deployment's ingress, as for every other route. The
Messaging recorded decisions and both products' security review notes record
this acceptance.

The Rust clients (appointment_receipt, message_receipt), the Node.js and
Python bindings, the unified facades, the generated OpenAPI documents, the API
reference pages, and the security contracts carry both routes.

Dev sessions wire local task-grant status and resource grants

bregctl dev:

  • resolves a $localClientSubject fixture marker for sub journeys, on top of
    the one common principal claim main's bregctl dev already derives from the
    compiled access profiles;
  • binds up to eight declared taskGrantStatus services, each with its own
    status-only client.

A machine client can declare grants to get ordinary tokens with exact
registered scopes at other resources. These go through the ThunderID tooling's
explicit ordinary_resource_permissions allowlist, which is empty by default.

caseworkctl dev refuses a borrowed session whose access profiles use sub.
It reports five integration and shared-registration mismatches under their own
codes, each pointing at the clients-file section to correct.

The task-grant invalidation suite runs in CI

The Casework task_grant_invalidation suite proves that a committed loss of
directory membership, holder, or proposal permanently invalidates a task grant,
that transient writes and atomic replacement do not, and that item erasure
removes the grant records. It now runs in the existing Casework assignment and
routing CI step, against the same PostgreSQL service, and the gates inventory
pins the command. The Casework README lists it with the other suites.

Product changelogs

The BReg, Casework, Messaging, platform, and Scheduling changelogs record each
change under ## Unreleased, the v0.41.0 development section, with a migration
for every breaking one. The BReg, Casework, and Messaging entries name the
mirror registries an egress allowlist must admit.

Commits

Based on 008968a35, main on 13 October. One commit per piece above, in order,
plus the help fix for the mirrors piece:

  1. test(render): give each serve fixture a distinct port
  2. test(casework): cover the macOS installer on both sides of the bundle boundary
  3. build: pull pinned prerequisite images from digest-identical mirrors
  4. refactor(platform,breg): share the exact task-grant status transport
  5. feat(casework): approve deferred task grants for delayed work
  6. feat(scheduling): check fresh task-grant status before capacity work
  7. feat(scheduling,messaging): read original receipts without replaying commands
  8. feat(breg,casework): wire local task-grant status and resource grants in dev sessions
  9. ci(casework): run the task-grant invalidation suite on every Casework change
  10. fix(dev): name the registry each dev session pulls from in its help
  11. docs(release): record the task-grant seams in the product changelogs

Breaking changes

  1. BReg status-check outages. When the status endpoint answers 401 or 403,
    BReg treats it as an outage and answers service.unavailable (503) instead
    of precondition.failed (412). A 404 still answers precondition.failed.
    The status user agent changes from registry-breg-task-status to
    registry-task-status.
    Migration: alert on service.unavailable for a misconfigured or revoked
    status client, and match the new user agent in Casework access logs.
  2. BReg checks taskGrantStatus when it reads the file. An invalid entry is
    refused as breg.runtime.invalid-task-grant-status at /taskGrantStatus.
    It used to be refused only at activation, as
    breg.runtime.invalid-binding. The members are typed.
    Migration: none for an entry that already activated. A script that matched
    the old code must match the new one.
  3. caseworkctl dev reports five configuration refusals by their own codes
    and exits with status 1, where each was caseworkctl.operational-failure
    with exit status 3.
    Migration: read exit status 1 and the code.
  4. caseworkctl dev refuses a borrowed session when a Casework access
    profile uses principalClaim: sub, as
    caseworkctl.dev.borrowed-principal-invalid.
    Migration: use a stable claim such as registry_principal in every access
    profile.
  5. CaseworkTaskAssertionSource reports refusals as invalid credentials.
    A Casework 4xx other than 408 and 429 becomes TokenError::Invalid
    (invalid-credential) instead of unavailable. A 408, 429, or 5xx stays
    unavailable.
    Migration: treat invalid-credential as a refusal that needs a new approval,
    and stop retrying it.
  6. Scheduling requires taskGrantStatus. A runtime file without it is
    refused at startup as scheduling.runtime.missing-task-grant-status, at
    /taskGrantStatus. v0.40.0 had no such member and accepted every unexpired
    task grant. Under the keyword, a grant with more than 900 seconds left
    answers operation.not-authorized (403) and is audited as refused; a
    Casework immediate grant never lasts that long, so only deferred grants and
    longer grants from other authorities meet the refusal.
    Migration: list the task authorities under taskGrantStatus, or write
    taskGrantStatus: unchecked-final-fifteen-minutes to keep the offline
    fifteen-minute bound without a revocation check.
  7. The Rust problem enums gain ReceiptUnresolved.
    registry_scheduling_core::ProblemCode and
    registry_messaging_core::ProblemCode, which the Rust clients' errors carry,
    are not #[non_exhaustive], so an exhaustive match on either no longer
    compiles. Only Rust code that matches on the enums is affected.
    Migration: add a ReceiptUnresolved arm or a wildcard arm.

Security review notes

This change touches authentication, authorization, task grants, and release
provenance.

Authorization and task grants

  • Deferred grants cannot carry Evidence bounds (CASEWORK-SEC-27). Evidence
    checks the earlier of the token and grant deadlines but does not check grant
    status. Its rule that an assertion stays usable after revocation only until
    the immutable grant deadline assumes the 900-second immediate window.

  • Where the rule is enforced. Core template validation refuses
    authorizationMode: deferred with bounds.type: evidence as
    casework.task-template.deferred-evidence. caseworkctl check, package
    activation, and runtime project load all run that validation, and the
    generated project schema carries the same condition. The assertion signer also
    refuses a stored deferred Evidence grant, so no assertion is signed under a
    deferred window.
    Negative tests: deferred_authorization_cannot_carry_evidence_bounds and
    task_assertion_refuses_a_deferred_evidence_grant.

  • Deferred grants never widen or extend authority. Short assertions and
    execution tokens do not extend the original grant. The PostgreSQL window
    constraints hold immediate grants to 900 elapsed seconds and deferred grants
    to 604800, whatever the database timezone.

  • Revival. A release followed by a reclaim, or a reassignment back, cannot
    revive a deferred review grant, because the trigger owns the assignment
    generation, even against an update that tries to reset it. A missing deferred
    counter fails closed. CASEWORK-SEC-18 is backed by
    deferred_review_grants_ignore_drafts_but_never_revive_after_responsibility_changes.

  • Deferred regressions. These cover restart, expiry, revocation, release
    and reclaim, and daylight saving:

    • deferred_grants_resume_after_fifteen_minutes_and_restart_without_widening_authority
    • deferred_grants_expire_and_revoke_without_extending_the_approved_window
    • deferred_work_item_grants_ignore_drafts_but_never_revive_after_release_and_reclaim
    • deferred_database_window_uses_elapsed_seconds_across_daylight_saving_changes

    They simulate elapsed time with synthetic timestamps; none waits sixteen real
    minutes.

Grant-status freshness before capacity work

  • Configured taskGrantStatus. Every capacity commitment and every
    idempotent replay first gets a fresh exact status answer. The answer depends
    on what comes back:

    • an inactive, mismatched, unlisted, or incompletely bound grant answers
      operation.not-authorized;
    • an authority or credential outage answers service.unavailable.

    The check is bounded at ten seconds and ends before the capacity transaction
    opens. A positive status answer is never cached; only the service credential
    is.

  • Lock waits. Inside the transaction, after lock waits, Scheduling
    rechecks the earlier of the token and grant deadlines.

  • No distributed transaction. Status observation and resource commit are
    not atomic. Revocation is effective from the next check onward.

  • The choice is explicit. A missing taskGrantStatus is refused rather
    than read as the weaker offline path (CFG-EMPTY-3), and the offline path is a
    named keyword rather than an empty list (CFG-EMPTY-2, CFG-NAME-7). The
    generated runtime schema requires the member and states both forms.

  • The 15-minute offline bound. Under unchecked-final-fifteen-minutes,
    Scheduling accepts a grant only while 0 < exp - now <= 900 seconds. A list
    turns this path off for every source, so a configured deployment refuses
    every grant from an unlisted authority, however short. A long-lived grant is
    therefore usable at Scheduling only where its status can be checked.

  • Tests. The PostgreSQL tests run against a wiremock status authority, not
    a running Casework:

    • configured_task_status_is_fresh_for_a_capacity_commit_and_its_replay
    • inactive_mismatched_and_unavailable_task_status_commit_no_capacity
    • inactive_and_unavailable_task_status_refuse_every_mutation_without_changing_capacity
      (hold, release, confirm, reschedule, and cancel, each leaving capacity
      unchanged)
    • an_unlisted_source_is_refused_and_never_reaches_the_offline_path
    • the_offline_path_accepts_at_most_fifteen_minutes_of_remaining_lifetime
      (at exactly 900 and 901 seconds)
    • a_refused_attempt_never_resolves_an_original_appointment_receipt
    • original_hold_confirmation_receipt_is_read_by_its_hold_and_key

    Database-free unit tests cover the verdict and the configuration:
    the_offline_path_accepts_only_an_unexpired_grant_with_at_most_fifteen_minutes_left,
    listed_authorities_refuse_an_unlisted_source_and_an_unbound_grant_without_the_offline_path,
    a_listed_authority_answer_decides_the_grant,
    a_missing_task_grant_status_is_refused_naming_both_choices,
    an_empty_or_unknown_task_grant_status_is_refused_naming_both_choices,
    the_unchecked_keyword_selects_the_offline_path, and
    the_schema_states_both_task_grant_status_choices_and_requires_one.

Accepted residuals

  • Receipt reads are not throttled. Scheduling and Messaging receipt reads
    spend no rate budget and never answer 429. An authenticated caller that
    repeats one costs a bounded indexed lookup and its audit records per
    request. A per-route read budget would be a new configuration surface for a
    cost the deployment's ingress already bounds. Recorded in the Messaging
    recorded decisions and in both security review notes; the Messaging OpenAPI
    document no longer lists 429 for the route, and
    the_receipt_read_publishes_no_rate_limit_answer holds that.
  • Approver source access under deferred grants. See "Casework deferred
    task grants" above. Repeating the approver's own source read at a later
    observation would require Casework to retain a credential it must not keep,
    so the reliance is stated and bounded by the template lifetime instead.

Exact-subject check

  • Casework. Review-grant eligibility now compares the request's current
    subject binding with the approved one for every grant, including immediate
    grants. An immediate grant pins the task revision, which covers the task row
    but not the request row. A changed subject therefore invalidates the grant
    with reason eligibility even when the task revision is unchanged.
    Test: immediate_review_task_grants_refuse_a_changed_request_subject.
  • Status transport. The transport's binding compares the signed subject
    values exactly, along with the principal, client, resource, purpose, bounds,
    and deadline.

Status transport scope and path

  • Scope. The transport requests one private-key-JWT service credential for
    exactly one resource and one scope. The default scope is
    casework:grants:status, and statusScope must be a single scope token.

  • Path. statusPath defaults to /v1/task-grants/{grantId}/status. It must:

    • start with /;
    • hold exactly one whole {grantId} segment;
    • use only unreserved characters in its other segments;
    • contain no dot segments;
    • be at most 256 bytes.

    The URL is built under the configured baseUrl only.

  • Answers. Only a 200 strict JSON document of at most 64 KiB that says the
    grant is active and repeats its exact binding is accepted.

    • A 404 refuses the grant.
    • A 401 or 403 means the status endpoint rejected the resource server's own
      credential. It makes the observation unavailable but does not establish that
      the grant is inactive. BReg maps it to 503.
    • Any other status, a wrong content type, or an outage is unavailable.
  • Bounds. The whole attempt, including getting the token, is bounded at ten
    seconds: five seconds per request and three to connect. BReg's own
    five-second bound ends the check first.

  • Transport identity. The user agent is registry-task-status.

  • Offline validation. validate_configuration checks the same nonsecret
    structure without resolving credentials or contacting any endpoint, so BReg
    refuses a malformed entry when it reads the file.

  • Unknown keys. BReg and Scheduling both refuse an unknown key in the shared
    block, reporting its path, line, and column (CFG-SCHEMA-8).

Release image provenance

Image Pinned digest Mirror check
Debian trixie-slim (recipe roots) sha256:a99cfc517144bc59b1978475ec53b46ecabec7e43635402ee5b77cc54cd1b20a Docker Hub and public.ecr.aws/docker/library/debian serve the same digest
PostgreSQL 17.11 (CI services, dev databases) sha256:67f41722b7a8cbdb868a44a4995c846eddfdc2973bccb291ce937dce88ad5675 Docker Hub and public.ecr.aws/docker/library/postgres serve the same digest
Mailpit v1.31.2 (CI, messagingctl dev) sha256:74d609a42ec279aa63c6b4622a6fa9b5408d1ad5b1d76a1c4be40a265ce0863d docker.io/axllent/mailpit and ghcr.io/axllent/mailpit serve the same digest
PostGIS (BReg CI jobs) postgis/postgis@sha256:01a6a70e41e6c4467c8f55f6063555ed72db2d6662cd0d571040d42eadaeb6f6 Not mirrored
  • What a mirror changes. Every mirrored reference keeps the exact upstream
    manifest digest, so only the registry endpoint changes, not the image bytes.
    The strict recipe check and the gate inventory still require the recorded
    repository and digest.
  • PostGIS stays on Docker Hub. The ECR Public probe answered 401, so no
    digest-identical public mirror could be confirmed.
  • Unchanged. The distroless runtime base gcr.io/distroless/cc-debian13 is
    unchanged.
  • Updating a pin. release/OPERATIONS.md says to verify the returned
    manifest's SHA256 at the chosen endpoint before changing a pin.

Test evidence

Gate and suite results, run locally on macOS (Apple Silicon)

All results below ran locally on macOS (Apple Silicon); none comes from CI,
which runs on push.

The branch is a linear rebase onto 008968a35, main on 13 October, with no merge
commits. Main's changes since the previous base overlap this branch in four
places, resolved as follows:

  • bregctl dev takes its local principal claim from main's derivation. This
    branch's narrower helper, its unit test, its duplicate DEV.md sentences,
    and the principal half of its BReg changelog entry are gone. One
    bregctl dev unit test keeps main's access profiles as written: this
    branch had rewritten them all to sub, which now mixes claims and is
    refused.
  • The Casework installer's macOS test sits on main's runnable-binary fixture.
    Main's mv shim also translates -h back to -T for a forced macOS run on a
    Linux runner.
  • The Casework migration-upgrade tests main deleted stay deleted; the remaining
    tests and the migration 22 test expect 22 migrations.
  • Each changelog keeps main's entries first.

The evidence has two layers:

  1. Gates run on this head.
  2. Earlier results, from heads before this rebase, for suites this head did
    not rerun.

On this head.

Gate Result
cargo fmt --check pass
CLI catalog, regenerated twice no drift, repeatable
cargo check --locked --workspace --all-targets pass
cargo clippy --locked --all-targets -- -D warnings on the 22 Rust crates the branch changes pass
the same on the Casework and Scheduling runtime and tooling crates with postgres-test pass
cargo test --locked --no-fail-fast on the same 22 crates 194 targets: 4820 passed, 0 failed, 20 ignored; Casework installer 13 of 13
generate.py --check-references pass
Casework checkpoint pass: 55 Python tests, 26 Rust checks
Scheduling checkpoint pass: 81 Python tests, 13 Rust checks
Scheduling contracts 16 passed
Messaging checkpoint pass: 48 Python tests, registry-messaging lib 285 passed, 1 offline configuration check
Messaging contracts 26 passed
BReg contracts pass: 99 Python tests, 47 Rust checks
check-config-conformance.py --check-generated 10 products hold
conventions lint 45 recorded findings, 0 unrecorded, 0 stale
check-gates-inventory.py 255 gates, no inert nested workflows
test_check_gates_inventory 77 passed
test_release_workflow_structure 81 passed
test_release_ci_overlap 5 passed
test_registry_release 119 passed
check_breg_gated_tests.py 254 feature-gated tests and targets against 11 CI invocations; 21 recorded as selected by none
test_check_breg_gated_tests 53 passed

PostgreSQL 17:

Suite Passed
Casework postgres_transactions 43
caseworkctl lib with postgres-test 389
Casework activation 30
Casework assignment and routing 23
Casework task_grant_invalidation 3
Casework review migration 4
Scheduling postgres_commitments 118
schedulingctl records apply 3
schedulingctl intents 1
schedulingctl activation 24

Not rerun on this head: cargo deny check, the BReg PostGIS suites, the
Messaging PostgreSQL suites, the language bindings, Vale, and the release
scripts not listed above. CI covers the workspace, cargo deny, and the
bindings; the earlier results below cover the rest.

Earlier: the review round, on the head before the previous rebase. It
changed Scheduling and Messaging code; in BReg and Casework it changed only
docs.

Scheduling:

Gate Result
cargo clippy --locked --all-targets -- -D warnings, the four Scheduling crates and registry-messaging pass
the same on registry-scheduling with schema,postgres-test pass
cargo test --locked on the four Scheduling crates 372 passed, 0 failed
registry-scheduling lib with schema 140 passed
runtime schema, regenerated twice no drift
Scheduling contracts 16 passed
Scheduling checkpoint pass: 81 Python tests, 13 Rust checks
Scheduling demo 15 passed
postgres_commitments 118 passed
records apply 3 passed
intents 1 passed
activation 24 passed
check-config-conformance.py --check-generated 10 products hold
conventions lint 45 recorded findings, 0 unrecorded, 0 stale
rule coverage 0 gaps
strict conformance corpus over target/debug 4522 cells: 3264 pass, 4 expected failures, 0 fail, 0 stale, 0 blocked, 1254 not applicable
Vale on the Scheduling API, Scheduling configuration, and Messaging API pages 0 errors, 0 warnings, 0 suggestions

Messaging:

Gate Result
cargo clippy --locked --all-targets -- -D warnings pass
migrate 6 passed
package 13 passed
messages 35 passed
dispatch 28 passed
callbacks 14 passed
retention 15 passed
postgres_messages_cli 5 passed

The Mailpit SMTP suite was not run.

Earlier: the rounds before the review round, on the a95e1c051 base.
Where the review round or this head reran a gate, the tables above replace
the earlier count.

Previous head, before the review round:

Gate Result
cargo clippy --locked --all-targets -- -D warnings on the 26 crates the branch touches pass
cargo test --locked --no-fail-fast on the same crates, registry-render serve included 4716 passed, 0 failed, 20 ignored; registry-render serve 23 of 23
registry-cli-docs snapshot test 3 passed
check-release-source-model.sh pass
Node.js facade sync check pass
Cargo.lock and products/platform/fuzz/Cargo.lock under --locked consistent at every commit

Every commit passed cargo check --locked --workspace --all-targets with no
warnings on a95e1c051, along with the generator drift checks.

Rust workspace:

  • cargo clippy --workspace --all-targets -- -D warnings passes.
  • cargo test --locked --workspace --no-fail-fast: 9359 passed, 3 failed, 51
    ignored. Two failures, registry-evidencectl access and
    jwk_from_pem, are a local macOS issue: the FIPS aws-lc dylib has no
    LC_RPATH when the binary runs as a child process. After evidencectl is
    relinked with that rpath, both targets pass (14/14 and 11/11). CI on Linux
    is not affected.
  • The third failure came from an intermittent registry-render serve
    fixture (serve_health_and_ready in this run), before the fixtures gained
    their retry: the child exited with status 20 because another socket took its
    port between discovery and its own bind ("Address already in use"). Main's
    version of the test fails the same way. The fixtures now move to a fresh
    port and retry, at most three times, only for that refusal; any other early
    exit fails at once with the server's output. A regression test holds the
    fixture's port to prove the retry, and with it 60 direct runs of the serve
    tests passed, 23 of 23 each.
  • BReg runtime lib: 585 passed, 2 ignored.
  • bregctl expression check: 1 passed.

Casework PostgreSQL suites:

Suite Passed
Evidence fixture 1
postgres_transactions 46
caseworkctl lib 2
activation 30
service visibility 19
assignment 21
routing 2
clocks 1
inbox ordering 8
source retention 2
breg_review_journey 3
review_http 25
review payment fixture 2
review_postgres 60
review migration 4
full lib 176 (2 ignored)
stock-issuer exchange not rerun
stock-issuer local session not rerun

BReg PostgreSQL suites (PostGIS):

  • Full registry-breg run with postgres-test,tooling: 2181 passed, 1
    failed, 15 ignored.
    • lib: 602 passed, 2 ignored.
    • 77 postgres_* and schema-fingerprint targets: 668 passed, 1 failed, 11
      ignored.
    • 55 other targets: 911 passed, 2 ignored.
  • The one failure needs a harness input: the real-Evidence proof requires
    BREG_TEST_EVIDENCE_BINARY. Rerun with a built evidence binary, the way
    products/breg/scripts/test-postgres.sh provides it, the Evidence targets
    pass: postgres_action_evidence 5/5, postgres_action_evidence_retention
    4/4, postgres_action_evidence_targets 1/1.
  • breg-review postgres_breg: 4 passed.
  • breg-mcp postgres_gateway: 6 passed, 1 ignored.

Product gates:

Gate Result
Evidence contracts 4 passed
Evidence source neutrality pass
BReg contracts 41 passed
BReg client contract 223 passed
BReg MCP gateway boundary pass
BReg service dependencies pass

The dependency-direction checks run inside the checkpoints and pass.

Configuration conventions:

Check Result
Conformance unit tests 43
Conventions unit tests 156
Corpus runner unit tests 126

Generators. Every generator this branch touches was rerun with its
documented command, and git status stayed clean. That covers the platform
shared blocks, the BReg runtime, authoring, and tool schemas, the Casework
project and runtime schemas, OpenAPI, and CLI schemas, the Scheduling
authoring and runtime schemas and OpenAPI, the Messaging runtime schema and
OpenAPI, the identifier catalog with --check-references, and the Node.js
facade sync. The review round reran the Scheduling runtime schema, the
Messaging OpenAPI document, and the identifier catalog.

Bindings.

Binding Casework Scheduling Messaging BReg Unified facade
Node.js (tests plus check:types) 29 40 36 55 3, plus sync check
Python 41 not rerun not rerun 46

Release scripts (before the review round; this head's reruns are in the
first table).

Check Result
wheel assembly 11
check-debian13-images.py pass, plus 21 unit tests
rehearse-upgrade 89
registry-release validate-current pass
release source model 14

Docs site.

  • The configuration generators ran.
  • node --test "scripts/**/*.test.mjs": not rerun.
  • npm run check, including the evidence anchors, the CLI reference, and the
    built link check: not rerun.

Issue

Refs #1952

This PR does not close #1952. It delivers the deferred grant lifecycle, its
contracts, and enforcement in each product. The issue also asks for proof that
a real wait of more than fifteen minutes, with the caller restarting, works end
to end through Casework and Scheduling. The tests here simulate elapsed time
and run Scheduling against a stub status authority. The Coordinator PR's native
journey makes that proof with a persisted wait of more than fifteen real
minutes and an actual worker restart, so that PR should close the issue.

Closes #1936: CI now runs
crates/registry-casework/tests/task_grant_invalidation.rs on every Casework
change.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-12T22:02:22.746220Z 1356242 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a036b41c41

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@jeremi
jeremi added this pull request to the merge queue Oct 12, 2026
@jeremi
jeremi removed this pull request from the merge queue due to a manual request Oct 12, 2026
@jeremi
jeremi force-pushed the feat/coordinator-seams branch from a036b41 to 8fb7560 Compare October 12, 2026 17:29
jeremi added 11 commits October 13, 2026 04:57
Port discovery releases the socket before the child binds it, so parallel fixtures could reach each other's server. Keep issued ports distinct and report a child that exits before it becomes healthy. Retry on a fresh port, at most three times, when the server's bind is refused.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
… boundary

The Casework installer takes raw binaries through v0.32.9 and a bundle
carrying the FIPS runtime from v0.33.0, and its test covered Linux only.
The fixture now writes a bundle for the later release and binaries that
answer --version, and the test mv shim translates the pointer switch in
both directions so the forced macOS run also passes on a Linux runner.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
CI services, release recipe roots, and the bregctl, caseworkctl, and messagingctl dev databases pull PostgreSQL, Debian, and Mailpit from the ECR Public Docker Official Images and GHCR mirrors at the same manifest digests. Public help keeps the canonical identities, and retained dev containers accept either exact reference.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Move the task-grant status registry and the exact-status request into registry-platform-oidc, and define the taskGrantStatus entry once as the TaskGrantStatusConfig shared block in registry-platform-config, so every resource server checks a grant through one bounded transport configured by one block. BReg keeps its persisted binding and BReg-only bounds validation and now calls the shared transport. registry-platform-httputil exposes the private_key_jwt identity check and requested-scope validation the transport needs.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
A task template may opt into authorizationMode: deferred for an approved duration of at most seven days; omitted mode stays immediate with its 900-second ceiling. Deferred review grants pin a trigger-maintained assignment generation, so release, reassignment, or completion cannot revive an approval, and retained work-item grants reuse the existing invalidation triggers. The clients, bindings, facade, and OpenAPI carry the mode.

Refs #1952

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
An optional taskGrantStatus registry makes Scheduling obtain a fresh exact status answer from the grant's Casework authority, through the shared platform transport, before every capacity commitment and idempotent replay, with no fallback once configured. Without it, only a grant with at most 900 seconds remaining is accepted. The capacity transaction rechecks the earlier token and grant deadline after lock waits. SCHEDULING-SEC-03 becomes enforced and the SCHEDULING-DEF-01 deferral is closed.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…commands

A caller recovering from a lost response can now observe the retained
original receipt for its own key and exact request without submitting the
command again: Scheduling serves POST /v1/appointments/receipt under
ordinary read authentication, and Messaging serves POST
/v1/messages/receipt. Lookup stays scoped to the caller's verified
identity, and neither route renders, submits, or claims capacity.

The Rust clients, the Node.js and Python bindings, the unified facades,
the generated OpenAPI documents, the API reference pages, and the security
contracts carry the new routes.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
… in dev sessions

bregctl dev derives the generated runtime's principal claim from the
compiled access profiles, resolves a `$localClientSubject` fixture marker
for `sub` journeys, and binds declared Casework task-grant status services
with their own status-only clients. Authored machine-client `grants` give
a local client exact registered scopes on other resources through the
ThunderID tooling's explicit `ordinary_resource_permissions` allowlist,
which stays empty, and bootstrap-only, by default.

caseworkctl dev refuses borrowed sessions whose access profiles use `sub`
and reports known integration and shared-registration mismatches with a
fixed diagnostic category and the clients-file section to correct.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
… change

The suite proves that a committed loss of directory membership, holder, or
proposal permanently invalidates a task grant while transient writes and
atomic replacement do not, and that item erasure removes the grant records.
It runs beside the assignment and routing suites against the same
PostgreSQL service, and the gates inventory pins the command.

Closes #1936

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
An operator writes an egress allowlist from the help, so the bregctl and
caseworkctl dev start help and the messagingctl dev help now name the exact
mirror references the session pulls, and tests hold each help to the
supervisor's own constant.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Adopters upgrading need the deferred grants, the shared status transport,
the Scheduling offline bound, the receipt routes, the stricter BReg status
configuration, and the changed client and caseworkctl refusals in one
place, with a migration for each breaking change. The changelogs also name
the mirror registries each dev session now pulls from, for egress
allowlists, and the receipt problem code the Scheduling and Messaging Rust
clients gain.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
@jeremi
jeremi force-pushed the feat/coordinator-seams branch from 8fb7560 to 1356242 Compare October 12, 2026 21:58
@jeremi
jeremi enabled auto-merge October 12, 2026 22:00
@jeremi
jeremi added this pull request to the merge queue Oct 12, 2026
Merged via the queue into main with commit ab75fd7 Oct 12, 2026
59 checks passed
@jeremi
jeremi deleted the feat/coordinator-seams branch October 12, 2026 22:52
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.

Support deferred task authorization without repeated human approval CI never runs the Casework task_grant_invalidation suite

1 participant