Skip to content

Commit e5e6ab9

Browse files
committed
fix(hooks): keep authorized publication reachable
Cause: the portable command hook hard-denied every protected-branch publication even though Codex could not project the owner's trusted approval into the hook process. The same raw-string classifier treated inert search queries as executable effects. Scope: additive and atomic protected-branch publication is now advisory on the host's native authority path. History rewrites and branch deletion remain exact-digest guarded. Quoted rg/grep query payloads are ignored only when the whole command is a non-expanding search, and a chained destructive effect still fails closed. Canonical product, architecture, isolation, compatibility, hook, README, and changelog contracts now describe that boundary. Verification: pytest hooks/tests/test_v026_hooks.py -q -k protected_branch_publication_is_advisory_when_host_authority_is_not_projected (red: 1 failed; green: 1 passed); pytest hooks/tests/test_v026_hooks.py -q -k read_only_search_payload_is_not_classified_as_a_shell_effect (red: 1 failed; green: 1 passed); pytest hooks/tests/test_v026_hooks.py -q -k protected_branch_history_rewrite_remains_exact_digest_guarded (red: 1 failed; green: 1 passed); npm run verify:release (Node 111 passed, Hooks 13 passed, audit 0 vulnerabilities); npm pack --dry-run --json (0.26.1, 113 entries). Residual risk: portable hooks still cannot consume a uniform native approval receipt across all five hosts. Other destructive classes therefore retain the explicit environment-scoped digest path, and inert-query suppression is intentionally limited to rg/grep-family commands. Rollback: revert this commit to restore exact-digest denial for every protected-branch publication.
1 parent 95d90a8 commit e5e6ab9

12 files changed

Lines changed: 195 additions & 30 deletions

.ultra/specs/architecture.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,13 @@ Adapters generate host metadata and wire payloads without editing shared workflo
7676
| `mid_workflow_recall.py` | active acceptance and source operation | bounded reminder | silent outside Ultra; diagnostic failure |
7777
| `compact_context.py` | files and Git | disposable snapshot | rebuild from authority |
7878
| `post_edit_guard.py` | edited path, ledger, task trace | normalized mechanical progress observation | malformed prior observation is repaired; semantic gaps stay advisory |
79-
| `block_dangerous_commands.py` | exact shell command and optional exact authorization digest | deny protected push, destructive data operation, funds, secret, or eval | denial names protected effect and reachable authorization path |
79+
| `block_dangerous_commands.py` | exact shell command and optional exact authorization digest | advise additive protected push; deny protected history rewrite/deletion, destructive data operation, funds, secret, or eval | advisory preserves the host authority path; denial names protected effect and reachable authorization path |
8080

81-
A database migration is advisory because migration meaning and reversibility are not
82-
decidable from a command pattern. Only the named externally verifiable effects fail
83-
closed.
81+
An additive protected push is advisory because Git publication is recoverable and
82+
portable Hook wiring cannot consume every host's trusted approval receipt. History
83+
rewrites and branch deletion remain guarded. A database migration is also advisory
84+
because migration meaning and reversibility are not decidable from a command pattern.
85+
Only the remaining named externally verifiable destructive effects fail closed.
8486

8587
## Delegation Boundary
8688

.ultra/specs/product.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ artifact audit, and resume contracts.
7878

7979
Hooks are idle when `.ultra/` is absent. Context Hooks inject bounded canonical facts;
8080
observation Hooks write only derived data; only named destructive effects can be denied,
81-
with an exact authorization repair. Verification: Python Hook suite.
81+
with an exact authorization repair. Recoverable additive protected-branch publication
82+
stays advisory when trusted host authority is not projected into the portable Hook;
83+
history rewrites and branch deletion remain guarded. Verification: Python Hook suite.
8284

8385
### Requirement FR-06: Bounded delegation
8486

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3737
rest of an isolated checkout.
3838
- Made dangerous-command authorization reachable by hashing the protected effect
3939
independently of its retry spelling.
40+
- Kept additive protected-branch publication on the host's native authority path instead
41+
of hard-blocking an already owner-authorized release, retained exact-digest guards for
42+
history rewrites and branch deletion, and stopped treating inert searches as effects.
4043
- Kept expandable interpreter heredocs inside dangerous-effect inspection while
4144
continuing to treat quoted commit-message heredocs as data.
4245
- Required explicit owner authority before `ultra-review` can launch a cross-host

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ Five optional hooks accelerate file reading and protect a narrow effect boundary
219219
| `mid_workflow_recall.py` | Restate acceptance before relevant source operations |
220220
| `compact_context.py` | Save and restore a disposable Git/file snapshot |
221221
| `post_edit_guard.py` | Record mechanical evidence observations after edits |
222-
| `block_dangerous_commands.py` | Deny named destructive shell effects until the exact command is authorized |
222+
| `block_dangerous_commands.py` | Advise on additive protected-branch publication; deny history rewrites and named destructive shell effects until the exact command is authorized |
223223

224224
All five exit silently when `.ultra/` is absent. Details are in
225225
[Plugin Isolation](docs/PLUGIN-ISOLATION-CONTRACT.md).

docs/ARCHITECTURE.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,10 @@ the parent model runs the same references sequentially.
8787
Five hooks read canonical files and emit bounded context or observations. Their output
8888
under `.ultra/.runtime/`, `.ultra/progress/`, or `.ultra/reviews/` is derived and
8989
disposable. The dangerous-command hook is the only hard effect guard; it classifies a
90-
small named set of shell effects and provides an exact-command authorization repair.
90+
small named destructive set and provides an exact-command authorization repair.
91+
Additive protected-branch publication remains advisory when portable host wiring cannot
92+
project a trusted owner-approval receipt; history rewrites and branch deletion remain
93+
guarded.
9194

9295
## Completion and recovery
9396

docs/DECISIONS.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,10 @@ No single sensor is promoted into a semantic completion gate.
5252

5353
The hook surface is session context, mid-workflow acceptance recall, compact snapshot,
5454
post-edit evidence observation, and dangerous-command protection. Every hook is silent
55-
without `.ultra/`. Only a narrow named destructive effect can be denied, and its repair
56-
is authorization scoped to the exact command digest.
55+
without `.ultra/`. Additive protected-branch publication is advisory because the
56+
portable hook cannot receive every host's trusted owner-approval receipt. History
57+
rewrites, branch deletion, and the narrower named destructive effects can be denied;
58+
their repair is authorization scoped to the exact command digest.
5759

5860
## Delegation is a process boundary
5961

docs/PHILOSOPHY.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,20 @@ agents to find loopholes (rename `mock` to `stub`, etc.). The cheaper path must
6161
6262
### C3 — Sensors not Blockers
6363

64-
Hooks emit signal; agents and users decide. Block only the **truly irreversible**:
64+
Hooks emit signal; agents and users decide. Block only the **truly irreversible or
65+
privileged destructive** effects for which the hook has an exact repair:
6566

66-
- `git push` to protected branches
6767
- Funds transfer / on-chain transactions
68-
- DB migrations / `DROP` / `TRUNCATE`
68+
- Protected-branch history rewrite or deletion
69+
- `DROP` / `TRUNCATE`
6970
- Hardcoded secret commit
7071
- Arbitrary code execution driven by user input
7172

72-
Everything else is advisory on stderr — the work stands, the signal is delivered, the agent
73-
proceeds.
73+
Additive protected-branch publication and database migrations are advisory: the former
74+
remains recoverable through Git and the portable hook cannot receive every host's
75+
trusted approval receipt; the latter is not mechanically classifiable as reversible or
76+
irreversible. The work stands, the signal is delivered, and the agent proceeds inside
77+
the owner's authorized frame.
7478

7579
> **Why this is absolute**: hard final gates and hard pre-commit gates each produced the same
7680
> over-correction loop in v7 — the agent weakened tests to escape the gate and drifted from the

docs/PLUGIN-ISOLATION-CONTRACT.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,12 @@ Every hook resolves the current project and checks for `.ultra/` before reading,
7070
writing, injecting context, or denying an effect. Without `.ultra/`, it emits no stdout,
7171
exits zero, and leaves the project untouched.
7272

73-
The only hook denial is a named dangerous shell effect in an active Ultra project. The
74-
denial identifies the protected effect and supplies an exact-command SHA-256
75-
authorization path. All other hook failures are fail-open diagnostics.
73+
The only hook denial is a named destructive shell effect in an active Ultra project.
74+
The denial identifies the protected effect and supplies an exact-command SHA-256
75+
authorization path. Additive protected-branch publication is an advisory observation
76+
because a portable hook cannot consume every host's trusted owner-approval receipt;
77+
history rewrites and branch deletion remain guarded. All other hook failures are
78+
fail-open diagnostics.
7679

7780
## Project data boundary
7881

docs/RUNTIME-COMPAT-MATRIX.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ host feature OpenCode does not expose.
6060
| mid-workflow recall | yes | handles `apply_patch` | native bridge | normalized | executed |
6161
| compact context | pre/start | pre/post | native compact event | pre/post | pre-compact |
6262
| post-edit observation | Write/Edit | Write/Edit/`apply_patch` | write/edit/patch | Write/Edit | Write/Edit/patch |
63-
| dangerous command guard | Bash | Bash | bash | Bash | Bash deny mapping |
63+
| dangerous command guard | Bash advisory/deny | Bash advisory/deny | bash advisory/deny | Bash advisory/deny | Bash allow/deny mapping |
6464

6565
All hooks share the same `.ultra/` idle guard. Wire adapters normalize payload and
6666
output fields only; they do not change semantic policy. Codex installation health and

hooks/README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ directory and exits silently when the workflow is not active.
99
| `mid_workflow_recall.py` | Before source reads or edits | Restate current acceptance only |
1010
| `compact_context.py` | Before/after compaction | Save and restore a disposable snapshot derived from files and Git |
1111
| `post_edit_guard.py` | After edits | Record mechanical evidence observations without deciding completion |
12-
| `block_dangerous_commands.py` | Before shell execution | Block a narrow named external effect until the exact command is owner-authorized |
12+
| `block_dangerous_commands.py` | Before shell execution | Advise on additive protected-branch publication; block history rewrites and a narrow destructive set until the exact command is owner-authorized |
1313

1414
`_common.py` is a shared library, not a lifecycle registration. Compact snapshots and
1515
progress observations live under ignored `.ultra/.runtime/` or `.ultra/progress/`; the
@@ -18,5 +18,7 @@ canonical semantic facts remain the owner-readable files and Git.
1818
Claude Code runs the hooks directly. Codex, Kimi Code, and Grok Build use the small
1919
wire adapters under `hooks/adapters/`. OpenCode calls the same scripts from its native
2020
JavaScript plugin. A hook failure is advisory except for the explicitly classified
21-
external effects in `block_dangerous_commands.py`, whose denial includes a reachable
22-
SHA-256-scoped authorization path.
21+
destructive effects in `block_dangerous_commands.py`, whose denial includes a reachable
22+
SHA-256-scoped authorization path. Additive protected-branch publication stays advisory
23+
because portable hooks cannot consume every host's trusted owner-approval receipt;
24+
history rewrites and branch deletion remain guarded.

0 commit comments

Comments
 (0)