Skip to content

fix(lifecycle): a checkout is disposable only if it says so itself - #1689

Merged
njrini99-code merged 3 commits into
mainfrom
agent/workspace-park-intent
Aug 30, 2026
Merged

fix(lifecycle): a checkout is disposable only if it says so itself#1689
njrini99-code merged 3 commits into
mainfrom
agent/workspace-park-intent

Conversation

@njrini99-code

Copy link
Copy Markdown
Owner

Closes WORKTREE_PARK_NO_PR_OWNERSHIP.

The #1681 fix made an OPEN PR's checkout unparkable without recorded owner intent. It never reached the window before a PR exists — which is where a session starts. A worktree five minutes old is clean, pushed, has no PR to key a disposition on, and is invisible to lsof between two tool calls: every signal the old rule read as "disposable", and every one wrong.

The mechanism

.helm/workspace.json  ->  { "parkPolicy": "KEEP" }   at creation, always
                          "PARK_IF_REPRODUCIBLE"     only if a human sets it

One field on a file new-worktree.sh already wrote. No new store. Releasing a checkout is a positive act; everything else KEEPs — no marker, no key, unknown value, unparseable file. Verdict KEEP_WORKSPACE_INTENT_REQUIRED, outside the standing authorization.

Runs before the reproducibility checks and independently of any PR, so both gates must permit — an OPEN PR its owner released cannot override a workspace KEEP.

workspace identity   may this CHECKOUT go?
PR state             may this BRANCH be deleted?

Branch retirement is untouched: PR MERGED + exact head OID.

Closed differently from how it was opened

The gap's closing condition asked for a session id checked for liveness. That is the same unsound negative-evidence inference in a new costume — a session between two tool calls looks dead. Declared intent needs no probe.

What changes for the one live worktree: nothing observable

agent/round-type-reclassify (#1681) predates the marker, so it now reports KEEP_WORKSPACE_INTENT_REQUIRED"no .helm/workspace.json" — instead of KEEP_PR_OWNER_INTENT_REQUIRED. Still kept, for a stronger reason.

worktree_policy is deliberately NOT removed yet

It is now the weaker of two gates, but #1659 is parked — no checkout on disk to carry a marker — so deleting the field would delete the only recorded authorization for it. The file records the condition for removing it.

Verification

Three injections, each red for its own reason: unwiring the gate (8 tests), letting a missing marker mean released (5), moving the gate after the reproducibility checks (1). Library restored byte-identical (sha256).

End-to-end --park runs against real git worktrees with the marker absent / KEEP / released. The fixture repo gained the .helm/ gitignore line the real repo has at .gitignore:257 — without it the marker itself reads as uncommitted work, and the fixture would disagree with production about what "clean" means.

preflight 0 · 220 tests in src/test/scripts · control-plane:verify VERIFIED, 5 registered gaps.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NbDxyygyXRUEERuGocpZZH

The #1681 fix made an OPEN PR's checkout unparkable without recorded owner
intent. It never covered the window BEFORE a PR exists — and that window is
where a session actually starts. A worktree five minutes old is clean, pushed,
has no PR to key a disposition on, and is invisible to `lsof` between two tool
calls: every signal the old rule read as "disposable", and every one of them
wrong. That residue was registered as WORKTREE_PARK_NO_PR_OWNERSHIP.

Disposability now comes from the workspace's own identity, in the marker
scripts/new-worktree.sh already wrote:

    .helm/workspace.json  ->  { "parkPolicy": "KEEP" }   at creation, always
                              "PARK_IF_REPRODUCIBLE"     only if a human sets it

No new store, no new registry — one field on a file that existed. Releasing a
checkout is a positive act; everything else KEEPS: no marker, no key, an unknown
value, a file that will not parse. Verdict KEEP_WORKSPACE_INTENT_REQUIRED, which
sits outside the standing autonomous authorization.

The gate runs BEFORE the reproducibility checks and independently of any PR, so
both gates must permit. An OPEN PR its owner released still cannot override a
workspace KEEP. The two questions stay separate, because conflating them is what
caused this:

    workspace identity   may this CHECKOUT go?
    PR state             may this BRANCH be deleted?

Branch retirement is untouched: PR MERGED plus an exact head-OID match, exactly
as before.

CLOSED DIFFERENTLY FROM THE CONDITION IT WAS OPENED WITH, deliberately. That
asked for a session id checked for LIVENESS — the same unsound negative-evidence
inference in a new costume, since a session between two tool calls looks dead.
Declared intent needs no probe.

WHAT THIS CHANGES FOR THE ONE LIVE WORKTREE: nothing observable, and the reason
matters. agent/round-type-reclassify (#1681) predates the marker entirely, so it
now reports KEEP_WORKSPACE_INTENT_REQUIRED with "no .helm/workspace.json" rather
than KEEP_PR_OWNER_INTENT_REQUIRED. Still kept, for a stronger reason.

worktree_policy in open-pr-dispositions.json is NOT removed here, though it is
now the weaker of two gates. #1659 is PARKED — there is no checkout on disk to
carry a marker — so deleting the field would delete the only recorded
authorization for it. The file says so, with the condition for removing it.

Verified: three injections, each red for its own reason — unwiring the gate
(8 tests), letting a missing marker mean released (5), and moving the gate after
the reproducibility checks (1) — with the library restored byte-identical
(sha256). The end-to-end tests run real `--park` against real git worktrees with
the marker absent, KEEP, and released; the fixture repo gained the `.helm/`
gitignore line the real repo has at .gitignore:257, without which the marker
itself reads as uncommitted work. preflight 0, 220 tests across
src/test/scripts, control-plane:verify VERIFIED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbDxyygyXRUEERuGocpZZH
@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@vercel

vercel Bot commented Aug 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
helmv3 Ignored Ignored Preview Aug 30, 2026 7:02pm

Request Review

@supabase

supabase Bot commented Aug 30, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project qmnssrrolpinvwjjnufo because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d411c896-70fb-4ed2-a33d-b7564c13fce6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

njrini99-code and others added 2 commits August 30, 2026 15:01
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbDxyygyXRUEERuGocpZZH
…orkflow

#1688 shipped the transitional merge-identity exception so a PR's own row could
survive its own merge without a cleanup push to main. That exception is scoped
to exactly one commit: it holds while main's HEAD IS the merge commit, and stops
the moment anything else lands. From this branch, HEAD is this branch's tip, so
#1688's row is stale again and the check says so, naming the merge SHA it no
longer matches.

Clearing it here IS the workflow — the next ordinary PR removes the previous
row while adding its own. First exercise of it, and it behaved exactly as
designed rather than needing a bypass, which was the whole point.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbDxyygyXRUEERuGocpZZH
@njrini99-code
njrini99-code merged commit a321550 into main Aug 30, 2026
48 checks passed
@njrini99-code
njrini99-code deleted the agent/workspace-park-intent branch August 30, 2026 19:13
njrini99-code added a commit that referenced this pull request Aug 30, 2026
The designed workflow, second exercise: the next ordinary PR clears the
previous PR's transitionally-closed row while adding its own. No push to main.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbDxyygyXRUEERuGocpZZH
njrini99-code added a commit that referenced this pull request Aug 30, 2026
… reporting (#1690)

* feat(knowledge): map the CRM surface, and find a P0 the red check was reporting

Three tasks, one thread: the permanently-red `Supabase Preview` check turned out
to be telling the truth.

1. A COMMITTED P0 MIGRATION IS NOT APPLIED TO PRODUCTION

supabase/migrations/20260819200000_preserve_golf_history_on_account_deletion.sql
carries the owner decision of 2026-08-18 — "There should be no deletion of golf
shot history" — and measures that 93 of 94 players reach the cascade. Verified
read-only against production 2026-08-30, it is not live:

    golf_players.user_id            still NOT NULL      (migration: nullable)
    golf_players_user_id_fkey       ON DELETE CASCADE   (migration: SET NULL)
    golf_player_anonymize_on_unlink does not exist

So DELETE /api/account/delete still destroys that player's rounds, holes, shots,
reviews and stats cache. Filed as
memory/incidents/golf_round_lifecycle/INC-2026-08-30-account-deletion-still-cascades-golf-history.md.
NOT fixed here: applying a production migration needs explicit owner
authorization, and `supabase db push` / `migration up` are denied by
permissions.deny, correctly.

The wider divergence is registered as
MIGRATIONS_REPO_PRODUCTION_LEDGER_DIVERGENCE: 846 ledger versions against 337
tracked files. Most of that gap is explainable — 240 versions predate the
squashed baseline, and applying via dashboard/MCP stamps its own timestamp, so
46 of 54 "missing" August versions are the same migration under a different
version. Two of this session's own first guesses were WRONG for the same reason
(baseball_event_telemetry is a migration name, not a table; the table is
baseball_timeline_event_acks), and the incident records that rather than hiding
it. Supabase Preview is therefore NOT decommissioned: it reports a real
condition, and turning it off would silence a true alarm.

2. THE CRM SURFACE IS MAPPED

memory/features/crm_outreach.md, written from verified code — 3 routes, 20
action files (two of which break the crm- naming convention and are listed
individually), 11 API routes, 20 owned tables, 6 integrations. Registered as a
feature and now owns the crm_recruiting_pipeline runtime key, closing the last
feature_awareness_gap.

KNOWLEDGE ROUTING ONLY, and checked rather than assumed: src/lib/admin/
feature-registry.ts carries an owner directive that CRM is never wrapped,
tagged, or shown on the Bridge board. That row is untouched — still
excluded: 'crm', still actions: {}. Verified that memory/registry.yml is read
only by scripts/knowledge/**, scripts/repo-doctor and the drift gates, and that
src/lib/reliability/collect.ts resolves feature ids from a hardcoded route table
because it "cannot read a build-time artifact". Nothing here reaches the Bridge.

Also recorded, not silently fixed: sending is human-triggered by omission —
process-sequences is fully wired but deliberately absent from vercel.json, so
adding it to "fix a missing schedule" would start sending cold email.

3. TWO STALE CLAIMS CORRECTED FORWARD

DATABASE_STATUS.md's "NOT FIXED — needs a product decision" heading sat above a
section that had since grown an "✅ APPLIED" block with role-impersonation
evidence. Re-verified against live pg_policies (not the migration file — trap
G8): baseball_coaches_select is org-scoped. Already fixed.

And the convergence report said "two items marked NOT FIXED". There was one,
plus one separately-declared deliberate gap — a count written into prose, wrong,
in the run whose thesis is that stale counts read as current forever.

4. THE GATE THAT CAUGHT THE INCIDENT

docs:schema-drift failed on the incident, correctly: it names four identifiers
that are not in the schema. That is the incident's SUBJECT — deleting them
deletes the finding — and baselining them would be wrong twice, since the
baseline is for known-bad references that should shrink. So a document may now
DECLARE, per identifier, that it names something because it is absent:

    <!-- schema-drift-absent: name_one, name_two -->

Exempt only those names, only in that file, printed on every run. A typo two
lines away is still caught; another document's declaration covers nothing.
Injection: making the declaration file-global turns that test red, script
restored byte-identical (sha256).

Verified: preflight 0, helm-os:check 0, control-plane:verify VERIFIED (6
registered gaps), 223 tests across src/test/scripts, ratchet 30514 unchanged.
All production reads were read-only through the account MCP.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbDxyygyXRUEERuGocpZZH

* chore(control-plane): record #1690, clear #1689's transitional row

The designed workflow, second exercise: the next ordinary PR clears the
previous PR's transitionally-closed row while adding its own. No push to main.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbDxyygyXRUEERuGocpZZH

* fix(knowledge): regenerate the inventory AFTER staging, not before

CI caught this and local runs did not, for a reason worth writing down: the
generator enumerates `git ls-files`, which reports the INDEX. Running it before
`git add` of a new document counts the tree without that document — 1693 instead
of 1694 — and the local --check then agrees with itself, because both halves
read the same stale index. CI checks out the merge commit, where the file IS
tracked, and disagrees.

So the generator is deterministic across checkouts (the convergence run proved
that by reading CI's own log); what is order-dependent is WHEN you run it
relative to staging. Regenerate after `git add`, not before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbDxyygyXRUEERuGocpZZH

* fix(incident): verify the CONSEQUENCE against the route, not the migration header

The schema half was verified first-hand — user_id NOT NULL, FK ON DELETE
CASCADE, no anonymize function — and that proves the migration is unapplied. But
the impact sentence ("account deletion still destroys the history", "93 of 94
players reach the cascade") came from the MIGRATION'S OWN HEADER, written
2026-08-19. Eleven days and many merges later that is a quoted comment, not
evidence, in a record filed at R4 for irreversible data loss. G8 inverted, and
in the worst possible place.

Read the route as it stands today. It holds:

  route.ts:150  USER_BLOCKING_TABLES is still exactly the three attribution
                tables — nothing blocks on golf_rounds or golf_shots
  route.ts:308  still ends in admin.from('users').delete().eq('id', user.id)
  whole file    names golf_rounds / golf_shots / golf_players NOWHERE, so no
                application-side preservation was added either

So the finding stands, now on first-hand evidence with file:line anchors instead
of a quotation. The 93-of-94 ratio is re-attributed as 2026-08-18's measurement,
explicitly not re-measured here — it is a live-data figure this session cannot
cheaply reproduce, and pretending otherwise would be the same error one level
down.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbDxyygyXRUEERuGocpZZH

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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