Skip to content

feat(coordinator): route declared BReg action refusals - #2029

Open
jeremi wants to merge 9 commits into
codex/coordinator-pilotfrom
codex/coordinator-declared-refusal
Open

jeremi wants to merge 9 commits into
codex/coordinator-pilotfrom
codex/coordinator-declared-refusal

Conversation

@jeremi

@jeremi jeremi commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

Summary

An invoke-breg-action business refusal currently ends a Coordinator run and loses its refusal code. This adds an optional onDeclaredRefusal edge whose output is exactly {refusalCode}. Successful calls keep their receipt on next. Authorization, transport, validation and protocol failures keep their existing failure behavior, and a call without the edge still fails the run with product-refused on a declared refusal.

Only invoke-breg-action declares the capability. Mapping checks are route-aware: a step reached only through next reads the operation output, a step reached only through onDeclaredRefusal reads {refusalCode}, and at or beyond a join of the two the call output cannot be read. Scenarios, the governed-action example and the generated operation reference cover the new route.

There is one adapter ABI, coordinator/product-operations/v1. The capability sits outside the operation identity and the snapshot carries the authored edge itself. A snapshot without the edge is byte-identical to one written before this change and restores with the same digest, identities and behavior. Restoring refuses an edge on an operation that does not declare the capability (definition.operation), and a binary that predates the edge refuses a snapshot carrying it (definition.snapshot). No database migration and no persisted-state change.

Stacked on #1956 (codex/coordinator-pilot) and merged with its current head.

Closes #1988. Note: the issue will not close automatically while this pull request targets a branch other than the default branch; close it by hand when the stack lands.

Security review notes

Threat. A response that is not a genuine no-effect refusal is treated as one, so a run continues down a business branch although the action may have committed, or a cancellation is recorded as effect-free when it was not. Second, text supplied by the called service reaches workflow data.

Enforcement point. The BReg action adapter (breg_action.rs) classifies a declared refusal only for the mutating invocation, only on HTTP 422 with the typed problem action.refused, only with a refusal code that is non-empty, at most 128 characters and free of control characters, and only after the expired-receipt and unknown-outcome guards have run. The worker routes it only when the workflow authored the edge and the step has no prior uncertain attempt. Authoring, snapshot restore and the mapping check refuse the edge on any other operation and refuse a mapping that reads the wrong route's output.

What still refuses.

  • A 5xx response carrying an action.refused-shaped body, and a 422 action.refused without refusalCode, stay transport-uncertain and are never routed.
  • Any other status or problem code, a non-mutating call, and an over-long or control-character code are not declared refusals.
  • A declared refusal after a prior uncertain attempt fails with product-refused and stays held as uncertain.
  • A declared refusal with no authored edge fails the run with product-refused, as before.
  • Problem detail, field paths and inputs never enter the refusal output; it is exactly {refusalCode}.

Proven and not proven.

  • Coordinator performs no catalogue membership check on the refusal code. It accepts any bounded code string from a typed 422 action.refused; the Base Registry Engine alone enforces that the code is one the action declared. The routed code is therefore service-supplied text that a workflow can copy into its finish output. Coordinator bounds its shape, not its vocabulary.
  • A cancellation that races a routed declared refusal now records cancelled instead of cancelled-after-effect. This rests on the engine's guarantee that a refused action commits no effect; Coordinator does not verify that independently. Calls without the edge, and every other outcome, keep cancelled-after-effect.
  • Proven by tests: adapter classification guards (unit and mock-server), opt-in routing and the prior-uncertainty hold (worker and PostgreSQL), encrypted checkpoint and reconstruction without reinvocation, the original deadline, the cancellation race, snapshot round trip and both snapshot refusals, route-aware mapping checks, and one real declared handler refusal against current-source BReg that leaves the record revision unchanged.
  • Not proven: the normal pull request workflow has not run, because repository CI targets main and release/1.0 and this pull request is stacked on another base. The checks below were run locally.

Evidence

Local macOS validation with a disposable PostgreSQL 17.11 database:

  • cargo test --locked -p registry-coordinator --features schema: 376 passed.
  • products/coordinator/scripts/test-postgres.sh: 206 passed, 3 ignored. The ignored tests are the existing opt-in activation-fence and backup/restore exercises; their separate runners were not run.
  • python3 products/coordinator/scripts/test-breg-action.py: 2 passed against current-source BReg and Messaging executables.
  • cargo test --locked -p registry-coordinator-client -p registry-stack-client: 37 passed.
  • cargo clippy --locked -p registry-coordinator --all-targets -- -D warnings, also with --features schema and with --features schema,postgres-test: passed.
  • cargo fmt --check and git diff --check: passed.
  • products/coordinator/scripts/check-schemas.sh, check_dependency_direction.py, products/identifiers/scripts/generate.py --check-references, the configuration convention lint (with the pilot branch as baseline, and --rule-coverage), check-config-conformance.py --check-generated, and the configuration conformance corpus for the three Coordinator formats (323 cells, 271 pass, 52 not applicable, 0 fail): passed. Coordinator script tests: 26 passed.

Not run: workspace-wide check, clippy, test and deny; the full strict conformance corpus across all products; the docs site checks.

Notes

The public reference page docs/site/src/content/docs/reference/coordinator-configuration.mdx does not yet describe onDeclaredRefusal or the route-aware reading rule; that belongs to a docs-site change. Release timing follows #1956.

DCO

  • Every commit includes a Signed-off-by trailer.
  • I reviewed the submitted changes and am responsible for the contribution.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
@jeremi
jeremi marked this pull request as ready for review October 13, 2026 04:12
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 13, 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-13T09:50:29.001610Z 782e788 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.

…ch/pr2029

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Registry Coordinator has never been released, so a second adapter ABI
selected by the presence of onDeclaredRefusal guards no deployed
snapshot. The edge is an optional member of the frozen call step: a
snapshot without it keeps its bytes, digest and behavior, and the closed
step decoder refuses a member it does not know instead of dropping it.

Restore still refuses the edge on an operation that does not declare
the capability, now held by its own snapshot test.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
An optional member with no default says in its description what leaving
it out does. Without the edge a declared refusal fails the run with
product-refused, like every other refusal.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
A server error that carries an action.refused body, and a 422
action.refused without a refusal code, are outside the typed contract
the refusal route trusts. Both leave the dispatched command uncertain
and never reach the authored branch.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The adapter classifies a refusal only after an unknown outcome has
already returned, so the classifier needs no flag for it. No runtime
path validates a value against the declared-refusal contract, so the
contract keeps the schema and drops the compiled validator and its
check. The generated reference links the refusal schema by operation
identifier, and the refusal code pattern spells its control-character
bound the way the other Coordinator schemas do.

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

This branch has not been deployed

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

1 participant