Repository navigation
feat(OMN-16485): pre-mutation lane-ownership gate — cross-lane gh close/cancel/dispatch fails closed on the shared identity - #2028
Conversation
…verbs Wires the zero-caller pr_claim_registry as a PreToolUse Bash guard: gh pr close/reopen and api-PATCH state=closed fail closed on absent/expired/ unreadable/lane-less claims; gh workflow run / gh run cancel are first-writer-wins exclusivity. Lane identity resolves ONEX_LANE_ID -> worktree path -> session id, INDETERMINATE refuses. Registry gains lane_id + tests; CLI gains the claim escape-hatch command.
|
Warning Review limit reachedNext included review available in 14 minutes. View limit detailsLimit details: You’ve used the included review currently available. Your 123 included PR review attempts over the past 7 days set your current allowance at 1 review per hour. Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. Review configuration: ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (9)
Comment |
…iclaude#2028 (#7012) * evidence(OMN-16485): author OCC companion for OmniNode-ai/omniclaude#2028 OCC companion by node_pr_lifecycle_fix_effect (OMN-13317 F1 / OMN-13990 / OMN-14285). Product PR head 4cf30edcba6c459bc9536b140db444ca4995330e. * evidence(OMN-16485): self-bind OCC#7012 + rebind contract_sha256 --------- Co-authored-by: omnimarket-bot <bot@omninode.ai>
|
| Verdict | Meaning | Blocks merge? |
|---|---|---|
passed |
No critical/error findings | No |
blocked |
CRITICAL or ERROR findings found | Yes |
degraded |
Reviewer ran but every model failed mid-run | No (pilot) |
infra_error |
cli_review failed to run at all (install/crash) after 2 attempts | Yes |
Powered by omniintelligence.review_pairing.cli_review — node-based adversarial review via HandlerLlmCliSubprocess (OMN-8524)
OMN-16485 — pre-mutation lane-ownership gate for destructive
ghverbsCloses friction-report F-11 (Urgent): every concurrent lane on this host mutates GitHub through ONE shared
ghidentity (jonahgabriel), so per-command attribution is structurally INDETERMINATE and nothing mechanically stops a lane from closing a peer lane's PR. Observed >=5 times in 48h — omniclaude#2019 was authored byandywu42and closed byjonahgabriel; a duplicate concurrentworkflow_dispatchfired ~19s after a peer lane's on 2026-08-20T00:52Z because neither lane could see the other.The mechanism existed and was never wired
plugins/onex/hooks/lib/pr_claim_registry.py(576 lines) already shipped canonical PR keys, atomicos.linkacquire, and heartbeat/two-condition expiry — with zero production callers and zero tests. Textbook Operating Rule #5: a detector that is not a gate gets ignored. This PR wires it as the gate instead of inventing a second vocabulary (the OMN-15483 net-negative precedent).What ships
1.
plugins/onex/hooks/lib/pr_ownership_guard.py— pure decision core. No network I/O; ownership resolves from the filesystem claims registry + locally-resolved lane identity, safe for the PreToolUse hot path. Quote-tracking tokenizer so a commit message containing the literal textgh pr closeis never read as a mutation.Two mutation classes with deliberately different verdicts:
gh pr close/reopen,gh api -X PATCH .../pulls/<n>withstate=closed) — FAIL-CLOSED. Absent, expired, unreadable, or lane-less claim REFUSES. "Nobody claimed it" is never read as "therefore anyone may." The refusal names the exact one-line claim command, so the escape hatch IS the act of producing the missing attribution record.gh workflow run,gh run cancel) — first-writer-wins. An active peer claim refuses; otherwise the mutation is allowed AND the claim is recorded, which is what makes the racing second lane refuse.Lane identity resolves deterministically:
ONEX_LANE_ID/agent-name env → worktree path under the worktrees root (wt:<ticket>/<repo>, per Operating Rule #9 a real per-lane discriminator) → session id →None= INDETERMINATE = refuse.2.
plugins/onex/hooks/scripts/pre_tool_use_pr_ownership_guard.sh— PreToolUse Bash wrapper. Cheap grep pre-filter so commands with no guarded verb never invoke Python at all — a guard bug cannot brick unrelated Bash traffic. A command that DOES contain a guarded verb and whose evaluation errors is BLOCKED, not allowed. Gated by the existingBASH_GUARDbit (a new bit needs an omnibase_coreEnumHookBitchange + release chain; all 60 default-mask bits are allocated;pre_tool_use_bash_guard.sh's own header records gh-PR-mutation blocking as its historical scope).3.
pr_claim_registrygainslane_idin the claim schema (the field the guard reads) plus its first-ever test coverage. A claim without a lane proves someone holds the target but not which lane — INDETERMINATE, cannot authorize.4.
scripts/pr_claim_registry_cli.pygains theclaimcommand — the escape hatch every refusal message points at.listnow shows the owning lane.Registration + enforcement, not doctrine
hooks.jsonv1.10.0 → v1.11.0: guard appended to the PreToolUseBashmatcher group (after the worktree guard), preserving the OMN-16471 lane-open and OMN-16478 lane-liveness carve-outs landed mid-flight.tests/hooks/test_hooks_baseline_omn_13244.pyasserts the EXACT five-command PreToolUse set, so a silently-dropped guard fails CI.plugins/distribution_manifest.yaml(Phase-0 classification gate, 273/273 OK).Acceptance criteria → tests (all falsifiable)
gh pr closerefused, refusal names the owner —test_peer_lane_close_is_refused_and_names_the_owner,test_the_observed_incident_is_refused(the omniclaude#2019 shape).test_unresolvable_lane_fails_closed,test_unreadable_claim_fails_closed,test_laneless_claim_fails_closed,test_evaluate_fails_closed_on_corrupt_claim_file.test_owner_may_close_its_own_pr,test_evaluate_allows_owner_and_refuses_peer.test_expired_claim_does_not_strand_the_pr_forever(re-claim succeeds after both expiry conditions).test_racing_peer_dispatch_is_refused,test_same_lane_redispatch_is_allowed.test_registry_persists_lane_id,test_registry_refuses_second_lane_while_claim_is_active,test_registry_release_is_scoped_to_the_holding_run.Also fixed in passing:
_claim_path()read the module-levelCLAIMS_DIRglobal directly, which isNoneuntil first use — the function raisedTypeErroron first call. Now resolves lazily via_get_claims_dir().dod_evidence
44 new unit tests. Governed pre-push selector ran and passed on push (secret detection, enum governance,
__all__exports, DoD advisory). NoPREPUSH_FULL_SUITE, noENABLE_SMART_TESTS=off, no--no-verify.Explicitly out of scope (Phase 2, separate ticket)
Per-lane GitHub App installation tokens — the credential-plane change that makes mutations attributable at GitHub (
timeline.actor.loginresolving to the lane). Needs an org-level App install, Infisical-held private key, short-TTL per-lane token minting, and coordinated shared-PAT retirement (with OMN-16373). Phase 1 makes mutations refusable and locally attributable today without blocking on any of that.Residual, stated plainly
This guard binds the local Bash chokepoint every interactive lane uses. A lane calling the GitHub API from Python code, or a process outside the Claude hook surface, is not intercepted — that is what Phase 2's per-lane credentials close. Co-located lanes sharing one worktree collapse to one lane id (deliberate under-block: it can permit a mutation between co-located lanes, never blocks an unrelated lane's own work).
Lane provenance: this work was started by the
friction-f11-lane-identitylane (ledger CLAIM 2026-08-24T12:59:42Z), which died in the session-limit outage with the work uncommitted; thefriction-f11-reconcilelane superseded the claim (ledger 15:05:25Z), reviewed, rebased onto dev across the OMN-16471/OMN-16478 hook merges, and landed it.Refs: OMN-16485;
omni_home/docs/tracking/2026-08-24-system-friction-report.md§F-11. Precedents: OMN-15483 (extend the shipped vocabulary), OMN-14330 (carve-out registration), OMN-13244 (baseline assertion). Siblings landed mid-flight: OMN-16471, OMN-16478.Evidence-Ticket: OMN-16485
Evidence-Source: OCC#7012