Skip to content

A reset refused by a bounded execution answers 409, not a bare 422 - #2008

Merged
jhgaylor merged 2 commits into
mainfrom
fix/execution-fenced-door
Sep 12, 2026
Merged

jhgaylor merged 2 commits into
mainfrom
fix/execution-fenced-door

Conversation

@jhgaylor

Copy link
Copy Markdown
Collaborator

:execution_fenced reached API callers through the unmapped-atom safety net: a 422 with no body at all, plus a warning log on every refusal. It is the third reason reset_sandbox/2 refuses (ADR 0046), and it should read like the other two.

It now answers 409 execution_fenced with a message, beside sandbox_mid_turn and sandbox_reset_pending. The message carries what separates it from both: a running turn ends by itself and a pending reset wants an operator, but an unconfirmed remote stop clears on its own once the deadline coordinator ages the obligation out — so the caller waits rather than fetching anyone.

Both defects are live on main today, after #1744 and #1745 landed. This does not touch the held #1746#1752.

The regression is the interesting part

It builds a journal that genuinely owes a stop: claim the spawn, bind an identity, then fail the turn, which lands the row on ready. Without those two steps complete/3 takes the "no spawn was ever submitted" path straight to stopped, nothing is owed, and the reset succeeds — my first draft passed against the unfixed code. Reverting the clause turns the 409 back into a 422, which is what the test exists to catch.

ADR 0046 corrections

  • Sprites chore: clean up compile warnings #33 merged on 2026-09-08. It was still written as an outstanding external dependency for provider identity notifications.
  • The activation blocker is now stated. FOUNTAIN_EXECUTION_LIMITS is a single host-wide ceiling with no per-provider scope, and _unsafe_register_bounded/3 rolls back :provider_not_supported inside the admission transaction. So setting it on a deployment that also runs E2B, Daytona or self-hosted runners stops those conversations opening a turn at all; runtime.exs validates only the JSON shape and nothing warns the operator. The per-account ceiling is usable only for an account that never touches another provider, unchecked. Per-provider granularity is follow-up under Track remaining lifecycle extractions from #1754 #1864 — until it lands, the host ceiling is unsettable in production and the per-account one is Sprites-only.

Validation

  • sandbox_reset_controller_test: 7 tests, 0 failures; the new one fails (422) with the clause reverted.
  • Controllers + sandbox reset suites: 1,178 tests, 0 failures.
  • mix format --check-formatted and mix compile --warnings-as-errors clean; okf validate decisions reports valid: true; the index needs no refresh (frontmatter unchanged).

🤖 Generated with Claude Code

https://claude.ai/code/session_01R9reKpmXUUJf4eUMULogie

…are 422

`:execution_fenced` reached API callers through the unmapped-atom safety net:
a 422 carrying no body at all, plus a warning log on every refusal. It is the
third reason `reset_sandbox/2` refuses (ADR 0046) and it needs to read like
the other two.

It answers `409 execution_fenced` now, beside `sandbox_mid_turn` and
`sandbox_reset_pending`. The message says the thing that distinguishes it from
both: a running turn ends by itself and a pending reset wants an operator, but
an unconfirmed remote stop clears on its own once the deadline coordinator
ages the obligation out, so the caller waits rather than fetching anyone.

The regression builds a journal that genuinely owes a stop — claim the spawn,
bind an identity, then fail the turn, which lands the row on `ready`. Without
those two steps `complete/3` takes the `no spawn was ever submitted` path
straight to `stopped`, nothing is owed, and the reset succeeds; that first
draft passed against the unfixed code. Reverting the clause turns the 409 back
into a 422, which is what the test is for.

ADR 0046 also gets two corrections:

- Sprites #33 merged on 2026-09-08. It was still written as an outstanding
  external dependency for provider identity notifications.
- The activation blocker is now stated. `FOUNTAIN_EXECUTION_LIMITS` is a
  single host-wide ceiling with no per-provider scope, and
  `_unsafe_register_bounded/3` rolls back `:provider_not_supported` inside the
  admission transaction, so setting it on a deployment that also runs E2B,
  Daytona or self-hosted runners stops those conversations opening a turn at
  all. runtime.exs validates the JSON shape and nothing warns the operator.
  Per-provider granularity is follow-up under #1864; until it lands the host
  ceiling is unsettable in production and the per-account one is Sprites-only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R9reKpmXUUJf4eUMULogie
…ty map

`inference_stamp_only?/1` is `Map.keys(usage) -- ~w(inference model) == []`,
so `%{}` satisfies it. The docstring carved out the "accounting"-only case
and said nothing about this one, which left a reader to discover it from the
implementation.

Recorded as a widening rather than a decision, with the two reasons it is
harmless: no runtime produces an empty usage map, and a row holding one
debited nothing, so a later figure over it debits exactly once.

Found in review of the #1732 stack; the predicate itself is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R9reKpmXUUJf4eUMULogie
@jhgaylor
jhgaylor enabled auto-merge September 12, 2026 00:49
@jhgaylor
jhgaylor added this pull request to the merge queue Sep 12, 2026
Merged via the queue into main with commit 9601966 Sep 12, 2026
24 checks passed
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