Skip to content

fix(openshell): drop withdrawn git routes and report store closures - #3271

Merged
freeqaz merged 5 commits into
mainfrom
fix/openshell-git-route-bookkeeping
Oct 23, 2026
Merged

freeqaz merged 5 commits into
mainfrom
fix/openshell-git-route-bookkeeping

Conversation

@freeqaz

@freeqaz freeqaz commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Two bookkeeping gaps in the OpenShell Git runtime (apps/controller/src/drivers/egress-proxy/openshell-git-runtime.ts):

  1. withdraw()/withdrawNamespace() kept every withdrawn route's entry (the full AgentRevision, provider and binding) for the life of the worker. The 1 s reconcile tick and every unmatched lookup walked them all.
  2. The route store's owns() answered false both when another worker took the fence and when the row was closed at the holder's own fence. Withdrawal and expiry close the row without moving the fence, so a route that another worker withdrew or expired was logged as worker_replaced and dropped. Its Agent then got a generic denial instead of access_withdrawn. withdraw() also wrote the store fire-and-forget with an empty catch.

Change

  • OpenShellGitRouteStore.owns() returns true, false (fence moved or no row), or the closed row's reason when the row is closed at the holder's fence. The Postgres store and the in-memory test store implement the same contract.
  • fenced() and persist() turn a refusal into the store's reason. After a refused write reservation or record they ask owns() why. A store-reported closure travels as its own error class. block() keeps the entry for it (so lookups answer access_withdrawn), writes nothing, and logs a closed route warning rather than OPENSHELL_GIT_RECONCILIATION_REQUIRED. A moved fence is still worker_replaced, as before.
  • A closed route is dropped once its Sandbox is gone. On each tick, a blocked entry (after any preview closure has settled) reads its Sandbox, first after 10 s and then backing off to 60 s while the Sandbox stays. When the Sandbox is absent, the worker deletes the entry and keeps a slim { blocked: reason } state. A Sandbox the route never observed may still be in creation, so it must read absent twice, a refresh interval apart. That state keeps prepare(), verifyExecution() and route() refusing the revision in this process, without asking the store. The store's tombstone refuses it to any other worker. Waiting for the Sandbox to go, rather than dropping at withdraw(), keeps the access_withdrawn answer while the Sandbox can still ask. It also releases routes that another worker withdrew, whose cleanup runs elsewhere.
  • A withdrawal that the store does not record is logged as worker.error OPENSHELL_GIT_WITHDRAWAL_UNRECORDED, with the selector and error class.
  • Docs: the route reference names the new closed reasons and the new code, and gives the release conditions. The egress flow's installation-custody step names release (that page stays at 2,494 of its 2,500 words).

Verification

  • New conformance tests in tests/conformance/openshell-git-runtime.test.mjs (both fail on main):
    • "a route another worker withdrew or expired closes with that reason, not as replaced": for withdrawn and repository_expired, the closure is seen by a lookup, by a reconcile pass and by a refused rotation reservation. In each case the Agent gets access_withdrawn, there is no worker.error, and nothing is written. An adopted fence is still worker_replaced.
    • "a withdrawn route is dropped once its Sandbox is gone and never prepared again": the entry stays while the Sandbox exists or its read fails, the read is throttled, and the entry is dropped once the Sandbox is absent. After the drop, prepare() is refused without any store call (no re-adoption, no re-bootstrap), verifyExecution() answers nothing, and a restarted worker hits the store tombstone. A store that misses the withdrawal is logged. The revision is still refused after the drop, and a standing execution is not verified from the store's unclosed row. A route withdrawn before its Sandbox was observed needs two absent reads. A read that answers after close() puts nothing back. Namespace withdrawal behaves the same.
    • Mutants killed: no slim tombstone, no Sandbox read, a failed read taken as "gone", a one-read drop of a never-observed route, no verifyExecution guard, no identity re-check after the read, no throttle, and store closures inferred from the reason.
    • The existing case where another worker withdraws during a provider write now expects access_withdrawn instead of no_binding.
  • tests/integration/postgres-openshell-git-routes.test.ts now pins owns(): it answers supervisor_replaced, withdrawn and repository_expired at the holder's fence, and false for a stale fence.
  • Local, Node 24:
    • openshell-git-runtime conformance: 41/41 passed.
    • repository-runtime-material, kubernetes-compute, repository-credentials-admission, protected-driver-registration and sandbox-driver-startup: all passed.
    • Real PostgreSQL 18.6, on the final head: postgres-openshell-git-routes (8/8), postgres-openshell-standing-route (6/6), postgres-openshell-standing-git-transfer (1/1), protected-driver-registration (5/5) and repository-credentials-admission (35/35) all passed. postgres-protected-git-recipient-custody (3/3) passed on the first commit.
    • The first CI run failed postgres-openshell-standing-route: its synthetic preview entry has no binding, and the release read built its request outside the guarded call. The read is now built inside it, so any failure keeps the entry.
    • tsc --build (repo + tests), ESLint and Prettier on the touched files: clean.
  • Internal review (security-adjacent, high effort): approved with nits. Its should-fixes are applied: pin the verifyExecution guard, release a route closed before its Sandbox was observed, back off the reads, and mark store closures by error class.
  • Synced with main after fix(openshell): resend an unapplied git detach under its request id #3272 (the expiry fix for finding 2219) landed: the conformance file passes 45/45, and postgres-openshell-git-routes passes 8/8.
  • Pinned: tests/conformance/openshell-git-runtime.test.mjs passed 20/20 runs at 2 CPUs each, on the final head.

Risks

  • One GetSandbox read per closed route, every 10 s backing off to 60 s, until its Sandbox is gone. A withdrawn route usually loses its Sandbox within seconds (cleanup deletes it right after withdraw()). A route closed for another reason, or one retained after Git expiry, keeps polling at 60 s until its revision is cleaned up.
  • A slim { blocked } record per closed revision stays for the life of the worker. That keeps a missed store write from letting this process re-prepare a withdrawn revision.
  • A Sandbox the gateway hides (NOT_FOUND for an identity that cannot see it) reads as gone. Only the reason code its Agent sees changes, from access_withdrawn to generic. The route stays closed either way.

A withdrawn route stayed in the worker's maps for its whole life, and the
1 s tick and unmatched lookups walked every one. A closed route whose
Sandbox is gone is now dropped to its reason: the revision stays refused
in this process, and the store's tombstone refuses it elsewhere.

The route store's owns() answered false both for a moved fence and for a
row closed at the holder's fence, so a route another worker withdrew or
expired was dropped as worker_replaced and the Agent got a generic denial.
owns() now answers the closed row's reason, the write and record refusals
ask for it, and the route closes with access_withdrawn. A withdrawal the
store does not record is logged instead of swallowed.

Finding 2218.
…store closures

Review follow-ups: an entry closed before its Sandbox was observed is
dropped after two absent reads; Sandbox reads back off from 10 s to 60 s
while it stays; store-reported closures travel as their own error class
instead of being inferred from the reason; tests pin the verifyExecution
guard, the read throttle, closure during a pending read and the
never-observed case.
@freeqaz
freeqaz requested a review from a team as a code owner October 23, 2026 04:03
@clawsweeper

clawsweeper Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

ClawSweeper review complete

ClawSweeper finished reviewing this revision. The review result is being finalized.

View the workflow run.

@clawsweeper clawsweeper Bot added clawsweeper:intake:bug Preliminary submission classification; full source review pending. clawsweeper:intake:needs-environment Preliminary submission classification; full source review pending. clawsweeper:intake:needs-repro Preliminary submission classification; full source review pending. P2 Normal priority bug or improvement with limited blast radius. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed clawsweeper:intake:bug Preliminary submission classification; full source review pending. clawsweeper:intake:needs-environment Preliminary submission classification; full source review pending. clawsweeper:intake:needs-repro Preliminary submission classification; full source review pending. labels Oct 23, 2026
@clawsweeper

clawsweeper Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge.

What this changes

This PR releases closed OpenShell Git route entries after Sandbox removal, preserves store closure reasons, and reports failed withdrawal writes.

Example: Another worker withdraws revision rev-one while its Sandbox remains running.

  • Before: The holder classifies the closure as worker_replaced and returns a generic denial.
  • After: The holder preserves withdrawn and returns access_withdrawn, then releases the full entry after the Sandbox disappears while retaining refusal.

Review scores

Measure Result What it means
Overall readiness 🦐 gold shrimp (3/6) A focused, source-supported repair with meaningful regression coverage; supplied execution evidence remains below the shipped-entry-point boundary.
Proof confidence 🦐 gold shrimp (3/6) This collaborator-authored repair is outside the ordinary contributor proof gate; supplied evidence covers runtime behavior with fake dependencies and real PostgreSQL storage separately, without a deployed-worker trace.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Product

Kind: Bug fix · Worth it: Yes · Fix scope: Complete
User problem: Withdrawn routes accumulate in worker memory, and cross-worker withdrawal produces misleading generic denials.
Reason: The maintainer-authored direction repairs demonstrated defects without adding configuration, public APIs, or another lifecycle owner.

Merge readiness

✅ Ready for maintainer review

Keep this PR open: it repairs concrete defects still present on main, resolves the prior documentation finding, and introduces no actionable defect found in this review.

Priority: P2
Reviewed head: c72b8cc195eb66d591b0b3efe9600fdb8448271d

Before merge

None.

Findings

None.

Tests

  • Missing end-to-end proof: The supplied coverage does not show a deployed worker observing cross-worker withdrawal, releasing the removed Sandbox’s entry, and continuing to refuse the revision.
Agent review details

How this fits together

The controller’s OpenShell Git runtime combines revision and Sandbox observations with PostgreSQL route fences to supply request bindings, readiness states, and denial diagnostics.

flowchart TD
  A[Sandbox lifecycle] --> B[OpenShell Git runtime]
  C[Agent Git request] --> D[Online Git authority]
  D --> B
  B --> E[PostgreSQL route store]
  B --> F[OpenShell Gateway]
  B --> G[Blocked revision record]
Loading

Technical review

Best possible solution:

Retain this focused runtime-and-store repair with its blocked revision records and updated lifecycle documentation.

Do we have a high-confidence way to reproduce the issue?

Current-main source directly establishes retained withdrawn entries and misclassification of same-fence closures; no target code was executed during this read-only review.

Is this the best way to solve the issue?

The patch uses the existing runtime and persistence owners, preserves fence enforcement, and prevents releasing heavyweight entries from reopening the revision.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning medium; reviewed against fcebb0939c09.

Provenance checked

  • Route ownership and store refusals keeps the original intent (3115679: Credential-proxy delivery state preserves route custody and fences so stale holders stop using routes.)
  • Withdrawal tombstones keeps the original intent (3115679: Terminal state prevents recreation under the same execution identity; the patch preserves that invariant with a smaller blocked record.)
  • Late provider-write results keeps the original intent (299934c: Writes completing after withdrawal or replacement must never reopen the route; the updated expectation preserves rejection while improving its reason.)

Testing

Proof path: in-process harness.

Security

None.

Evidence

What I checked:

  • Current-main defects: Main treats refused ownership and persistence as worker replacement, retains withdrawn full entries, and skips further blocked-entry reconciliation. (apps/controller/src/drivers/egress-proxy/openshell-git-runtime.ts:1031, fcebb0939c09)
  • Pinned repair preserves refusal: Only literal true means ownership; same-fence closure reasons remain terminal, and release replaces the full entry with a blocked revision record checked by prepare and verifyExecution. (apps/controller/src/drivers/egress-proxy/openshell-git-runtime.ts:1935, c72b8cc195eb)
  • Previous finding resolved: The installation-custody flow now names release and retained refusal, linking to the reference that explains absent-Sandbox checks, failed reads, and continued prepare/adopt/verify refusal. (docs/flows/credential-source-egress.md:271, c72b8cc195eb)
  • Focused behavioral coverage: Added cases cover cross-worker withdrawal and expiry, reservation refusal, failed Sandbox reads, release, restart refusal, failed withdrawal recording, shutdown races, and Namespace withdrawal; PostgreSQL assertions separately verify same-fence closure results. (tests/conformance/openshell-git-runtime.test.mjs:1132, c72b8cc195eb)
  • Owner direction and accepted costs: The collaborator-authored body explicitly accepts capped Sandbox polling, lifetime slim tombstones, and generic denials after hidden or removed Sandboxes. Full body inspected; supplied source identity: 1732c7c400f5b99a95ef0fa253874fadb81a99d959b5f3291048d393e00c6c6a. (c72b8cc195eb)
  • Feature history: Merged history identifies Free Wortley across credential-proxy implementation, installation-observation separation, provider cleanup, transient-failure repair, and expiry recovery. Older blob traversal encountered unavailable objects; commit history, supplied provenance, and GitHub PR descriptions supplied the relevant intent. (apps/controller/src/drivers/egress-proxy/openshell-git-runtime.ts:1366, fcebb0939c09)

Review metrics

Metric Value Why it matters
Change distribution Production +136/-26; tests and fixtures +192/-6; documentation +8/-1 The implementation stays within existing owners, with coverage for distinct closure and cleanup failure cases.

Labels

Label changes:

  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR.
  • remove status: ⏳ waiting on author: Current PR status label is status: 👀 ready for maintainer look.

Label justifications:

  • P2: This addresses worker-local retention and incorrect withdrawal diagnostics without evidence of an urgent outage or authorization bypass.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR.

Rating scale

6/6 🦀 challenger crab · 5/6 🦞 diamond lobster · 4/6 🐚 platinum hermit · 3/6 🦐 gold shrimp · 2/6 🦪 silver shellfish · 1/6 🧂 unranked krab. Overall follows the weaker of proof and patch quality; ✨ marks media proof (a screenshot, video, or linked artifact) that directly shows the changed behavior.

Workflow

ClawSweeper edits this one comment on every review. Comment @clawsweeper re-review for a fresh review only. Repair and merge execution lanes are retired.

History

Review history (1 earlier review cycle)
  • reviewed 2026-10-23T04:16:45.236Z sha 94a3206 :: needs changes before merge. :: [P3] Update the owning flow for closed-route release

Reviewed October 23, 2026, 12:44 AM ET / 04:44 UTC (Revision 2).

The never-observed release path read entry.binding before the guarded
call, so an entry without one (a synthetic preview entry in the
standing-route test) threw out of reconcile. Any failure building or
answering the read now keeps the entry, as a failed read already did.
… route reference

The installation-custody flow names release(); the reference gives its
conditions (absent Sandbox, twice if never observed; a failed read keeps
the route) and the refusal that stays after the drop.
@freeqaz

freeqaz commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

🦞👀
Exact review queued.

Re-review progress:

@clawsweeper clawsweeper Bot added clawsweeper:intake:bug Preliminary submission classification; full source review pending. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. clawsweeper:intake:bug Preliminary submission classification; full source review pending. labels Oct 23, 2026
@freeqaz
freeqaz merged commit 880cc3d into main Oct 23, 2026
58 of 60 checks passed
@freeqaz
freeqaz deleted the fix/openshell-git-route-bookkeeping branch October 23, 2026 04:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal priority bug or improvement with limited blast radius. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant