Skip to content

test: [de-fork S2] execution-route matrix + sentinel tests#1014

Open
anandgupta42 wants to merge 4 commits into
mainfrom
defork/s2-route-matrix
Open

test: [de-fork S2] execution-route matrix + sentinel tests#1014
anandgupta42 wants to merge 4 commits into
mainfrom
defork/s2-route-matrix

Conversation

@anandgupta42

@anandgupta42 anandgupta42 commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

PINEAPPLE

Summary

Stage S2 of the de-fork spike: the authoritative map of every tool-execution dispatcher (where a resolved tool's .execute() actually runs), so stage S3's HardPolicy knows every chokepoint it must cover. Analysis + executable sentinel tests only — zero product-code change.

Deliverables:

  • docs/internal/defork-route-matrix.md — ingress surfaces vs execution dispatchers (D1–D8), each classified active / latent / out-of-scope with file:line evidence.
  • packages/opencode/test/altimate/defork/route-sentinels.test.ts — an executable sentinel per active route, driving a harmless tool through the real dispatch code and asserting execution reaches the documented line.

Key findings (these shape S3's HardPolicy)

  • SessionTools.resolve (D3/D4) is latent — zero production callers at HEAD (a guard test flips if that changes), so it isn't a current bypass.
  • The real bypass is BatchTool inner dispatch (tool/batch.ts:104) — it skips Plugin.trigger and source-stamping.
  • All active dispatchers live in 3 files (session/prompt.ts, tool/batch.ts, session/tools.ts) → S3's ≤3-file audited-patch budget is feasible.
  • Flags a pre-existing security gap (out of this spike's scope): the server-v2 shell HTTP route has only a session-existence check, no permission gate.

Test Plan

  • bun test test/altimate/defork/route-sentinels.test.ts: 9 pass / 0 fail (verified in the primary dev worktree).

Checklist

  • Tests added/updated
  • Documentation updated (route matrix)
  • CHANGELOG updated — N/A (internal analysis + tests)

Closes #1011


Summary by cubic

Maps all tool-execution dispatchers and adds sentinel tests to pin and exercise each route; commits the S2 spec and clarifies MCP checks and the BatchTool permission bypass. Adds an explicit coverage contract and expands latent-guard scanning; no product code changes. Closes #1011.

  • Highlights
    • Docs: docs/internal/defork-route-matrix.md updated with a precise “Coverage contract” (D1/D5 have real-execution sentinels; D2/D6 use documented transitive/citation coverage), plus out-of-scope entries and the v1+v2 shell route gap (tracked in Security: shell HTTP routes (v1 + v2) spawn processes with no permission gate #1019); committed docs/internal/2026-07-18-defork-spike-spec.md.
    • Tests: packages/opencode/test/altimate/defork/route-sentinels.test.ts (9 passing) with line- and arg-agnostic chokepoint pins; latent guard now scans the entire src tree including .tsx for SessionTools.resolve( callers; D1 and D5 exercise real dispatch.
    • Clarifications: D2/D4 PermissionNext.ask is deny-capable with wildcard patterns; D5 BatchTool inner dispatch is a real permission bypass; D6 direct task dispatch is ungated today (bypassAgentCheck=true). All active dispatchers remain in session/prompt.ts, tool/batch.ts, session/tools.ts.

Written for commit 235295a. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Documentation

    • Added an internal “S2 de-fork” execution-route and reachability matrix documenting dispatcher expectations, gating/plugin behavior, and noted bypass/coverage gaps.
    • Added the “De-fork Spike” build spec (rev 4) describing staging gates and replay/diff constraints.
  • Tests

    • Added route-sentinel tests covering direct tool dispatch, latent-call-site guards, mocked-layer resolution, batch inner-tool dispatch behavior, and task/tool metadata handling.
    • Added structural and citation checks to ensure existing real-execution and metadata-preservation behavior.

Stage S2 of the de-fork spike: the authoritative map of every tool-execution
DISPATCHER (the point where a resolved tool's `.execute()` actually runs), so
S3's HardPolicy knows every chokepoint it must cover. Analysis + executable
sentinel tests only — zero product-code change.

- `docs/internal/defork-route-matrix.md` — ingress surfaces vs execution
  dispatchers (D1-D8), each classified active / latent / out-of-scope with
  file:line evidence. Key findings: `SessionTools.resolve` (D3/D4) is LATENT
  (zero production callers at HEAD); the real bypass is BatchTool inner
  dispatch (`tool/batch.ts:104`) which skips `Plugin.trigger`; all active
  dispatchers live in 3 files. Also flags a pre-existing security gap: the
  server-v2 `shell` HTTP route has no permission check.
- `packages/opencode/test/altimate/defork/route-sentinels.test.ts` — an
  executable sentinel per active route, driving a harmless tool through the
  real dispatch code and asserting execution reaches the documented line.

Test Plan:
- `bun test test/altimate/defork/route-sentinels.test.ts`: 9 pass, 0 fail.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

@github-actions

Copy link
Copy Markdown

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

Please edit this PR description to address the above within 2 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds a de-fork spike specification, an S2 execution-route matrix, and sentinel tests covering selected direct, batched, and task-tool dispatchers through runtime probes and source-text assertions.

Changes

De-fork spike specification

Layer / File(s) Summary
Spike build specification
docs/internal/2026-07-18-defork-spike-spec.md
Defines S1–S10 sequencing, census and replay conventions, HardPolicy mechanics, evidence expectations, and later-stage work outlines.

De-fork route coverage

Layer / File(s) Summary
Route matrix and coverage contract
docs/internal/defork-route-matrix.md
Documents ingress surfaces, dispatchers D1–D8, reachability states, bypass behavior, compatibility plumbing, sentinel constraints, and verified test mappings.
Resolve-tools dispatch sentinels
packages/opencode/test/altimate/defork/route-sentinels.test.ts
Tests SessionPrompt.resolveTools and SessionTools.resolve through runtime probes, latent-call guards, and source-text chokepoint assertions.
Nested and task dispatch sentinels
packages/opencode/test/altimate/defork/route-sentinels.test.ts
Validates BatchTool inner execution and task-tool dispatch structure, including a citation of existing execution coverage.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

Poem

A rabbit maps each route with care,
While sentinels guard execution there.
Batch tools burrow, task paths leap,
S2 keeps watch while branches sleep. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and accurately summarizes the main change: S2 execution-route matrix plus sentinel tests.
Description check ✅ Passed The description covers the issue, change summary, verification, and checklist, though it doesn't mirror the template headings exactly.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch defork/s2-route-matrix

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.

@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

1 similar comment
@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
docs/internal/defork-route-matrix.md (1)

57-57: 🔒 Security & Privacy | 🔵 Trivial

Consider filing a tracking issue for the flagged shell HTTP permission gap.

This row correctly surfaces that the server-v2 shell route only checks session existence, with no permission/ruleset gate — a real gap even though it's out of this spike's scope. Since it's documented here but not tracked anywhere actionable, worth opening a follow-up issue so it doesn't get lost.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/internal/defork-route-matrix.md` at line 57, File a follow-up tracking
issue for the server v2 shell HTTP route identified in the SessionPrompt.shell
row, documenting that its requireSession check lacks a permission or Ruleset
gate around raw command execution. Keep the documentation row’s out-of-scope
classification unchanged and reference the new issue in the row so the security
gap has an actionable owner.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@docs/internal/defork-route-matrix.md`:
- Line 57: File a follow-up tracking issue for the server v2 shell HTTP route
identified in the SessionPrompt.shell row, documenting that its requireSession
check lacks a permission or Ruleset gate around raw command execution. Keep the
documentation row’s out-of-scope classification unchanged and reference the new
issue in the row so the security gap has an actionable owner.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: bf40245f-9130-4bad-a91f-3d4f6b9e6523

📥 Commits

Reviewing files that changed from the base of the PR and between 8a50ec7 and d3b8119.

📒 Files selected for processing (2)
  • docs/internal/defork-route-matrix.md
  • packages/opencode/test/altimate/defork/route-sentinels.test.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d3b811963f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +123 to +124
const promptSrc = readSrc("session/prompt.ts")
const toolSourceSrc = readSrc("altimate/tool-source.ts")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scan the full production tree for SessionTools callers

This latent-route guard only examines session/prompt.ts and altimate/tool-source.ts, so a future production caller added in any other source file will leave the test green and D3/D4 incorrectly classified as latent. Since the stated purpose is to fail as soon as the resolver gains a real caller, search all production TypeScript sources for SessionTools.resolve or imports of session/tools, excluding the definition itself and comments.

Useful? React with 👍 / 👎.

Comment on lines +11 to +15
This document separates **ingress surfaces** (how a request enters the system)
from **execution dispatchers** (where a tool call is actually dispatched), and
records, for every dispatcher, whether it is currently reachable in production
(`active`), wired but with no current caller (`latent`), or intentionally
outside HardPolicy's model-invoked boundary (`out-of-scope`).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include the direct ReadTool dispatches in the matrix

The matrix says every resolved Tool.Info execution point is classified, but createUserMessage directly initializes and executes ReadTool for text and directory file parts at src/session/prompt.ts:2001 and :2070, with ask: () => Effect.void. These user-attachment routes do not funnel through any listed dispatcher; even if they are intentionally outside the model-invoked HardPolicy boundary, they need an explicit out-of-scope entry so the S3 audit is complete.

Useful? React with 👍 / 👎.

Comment on lines +3 to +5
Status: draft for S3 handoff. Analysis-only; no product code was modified to
produce this document. Governed by S2 of `docs/internal/2026-07-18-defork-spike-spec.md`
(rev 4, Codex-consensus-approved).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add or correct the governing spike specification

The referenced docs/internal/2026-07-18-defork-spike-spec.md exists in neither this commit nor its parent, and a repo-wide path/content search finds no available copy. Consequently reviewers cannot verify the declared S2 requirements or the later citation to line 61; commit the specification or replace these references with the actual accessible source.

Useful? React with 👍 / 👎.

Comment thread docs/internal/defork-route-matrix.md Outdated
| D2 | `SessionPrompt.resolveTools` — MCP-tools loop | `src/session/prompt.ts:1630-1739`, dispatch `const result = await execute(args, opts)` at `1669` | Real, but wildcard: `AppRuntime.runPromise(ctx.ask({permission: key, patterns: ["*"], always: ["*"]}))` at `1656-1667` — always-allow-with-audit-record, not a fine-grained deny-capable gate. Comment documents a **previously fixed bug**: `await ctx.ask(...)` alone only awaited the Effect *object*, never ran `PermissionNext.ask` — MCP tools executed with no permission check until this was corrected (`upstream_fix` marker). | Same merge as D1 (wildcard pattern) | Yes / Yes (`1644-1654`, `1671-1680`) | Yes, if any MCP server is configured | **active** |
| D3 | `SessionTools.resolve` — registry-tools loop | `src/session/tools.ts:27` (fn), context/`ask` at `44-75`, dispatch `yield* item.execute(args, ctx)` at `97` | Real: `permission.ask({..., ruleset}).pipe(Effect.orDie)` at `66-74` | `Permission.merge(input.agent.permission, input.session.permission ?? [])` | Yes / Yes (`92-96`, `109-113`) | N/A — **zero production callers**. Exhaustive grep for `SessionTools` outside its own definition file returns only two doc-comments in `src/session/prompt.ts` (noting the shared `stampRegistryToolSource`/`describeMcpTool` helpers) and two doc-comment references in `src/altimate/tool-source.ts`. No call site exists at HEAD. | **latent** |
| D4 | `SessionTools.resolve` — MCP-tools loop | `src/session/tools.ts:124-216`, dispatch `Effect.promise(() => execute(args, opts))` at `145` | Real (with same wildcard-pattern caveat as D2) at `144` | Same merge as D3 | Yes / Yes (`138-142`, `156-160`) | N/A, same as D3 | **latent** (bundled with D3 — same unused resolver) |
| D5 | `BatchTool` inner dispatch | `src/tool/batch.ts:104` — `await AppRuntime.runPromise(tool.execute(validatedParams, toEffectContext(ctx, partID)))` | **Forwarded, not independently gated.** `toEffectContext` (`batch.ts:16-28`) passes the *same* `ctx.ask` the outer dispatcher (D1/D2/D3/D4/D6, whichever invoked "batch") supplied — BatchTool adds no wrapper-level, tool-specific `ask` of its own. Per-inner-tool permission enforcement therefore depends entirely on whether that inner tool's own implementation calls `ctx.ask` internally (many built-ins, e.g. `bash`, do). | Same ruleset as whichever outer dispatcher invoked "batch" (forwarded, not re-derived) | **No** — confirmed by full read of `tool/batch.ts`: no `Plugin.trigger` call anywhere in the file. `stampRegistryToolSource` is also never applied to inner results. This is the genuine, precisely-scoped bypass: batched inner tool calls skip plugin hooks and source-stamping, not permission checks outright. | Yes — the model can invoke `batch` from any agent that has it enabled, and can batch any tool present in `ToolRegistry.tools(...)` except `batch` itself (`DISALLOWED`, `batch.ts:11`) | **active** |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat batched inner calls as a permission bypass

When batch remains allowed but an inner tool is denied, this route can bypass that deny rather than merely skipping hooks and source stamps. BatchTool obtains every registry tool without the current agent at src/tool/batch.ts:62 and invokes it directly; inner tools such as warehouse_remove (src/altimate/tools/warehouse-remove.ts:10-35) never call ctx.ask, so a configured warehouse_remove: deny rule is never evaluated. The matrix should identify this as a permission bypass so S3 gates every inner dispatch independently.

Useful? React with 👍 / 👎.

Comment thread docs/internal/defork-route-matrix.md Outdated
| # | Dispatcher | File:line | `ctx.ask` wiring | Ruleset | `Plugin.trigger` before/after | Reachable under allow-all | State |
|---|---|---|---|---|---|---|---|
| D1 | `SessionPrompt.resolveTools` — registry-tools loop | `src/session/prompt.ts:1521` (fn), context/`ask` at `1562-1572`, dispatch `await AppRuntime.runPromise(item.execute(args, ctx))` at `1601`, source-stamp `1614` | Real: `PermissionNext.ask({..., ruleset})` wrapped in `Effect.promise` | `PermissionNext.merge(input.agent.permission, input.session.permission ?? [])` | Yes / Yes (`1589-1599`, `1615-1624`) | Yes — this is the primary, always-reachable per-turn tool dispatch path (called from `loop()` at `918`) | **active** |
| D2 | `SessionPrompt.resolveTools` — MCP-tools loop | `src/session/prompt.ts:1630-1739`, dispatch `const result = await execute(args, opts)` at `1669` | Real, but wildcard: `AppRuntime.runPromise(ctx.ask({permission: key, patterns: ["*"], always: ["*"]}))` at `1656-1667` — always-allow-with-audit-record, not a fine-grained deny-capable gate. Comment documents a **previously fixed bug**: `await ctx.ask(...)` alone only awaited the Effect *object*, never ran `PermissionNext.ask` — MCP tools executed with no permission check until this was corrected (`upstream_fix` marker). | Same merge as D1 (wildcard pattern) | Yes / Yes (`1644-1654`, `1671-1680`) | Yes, if any MCP server is configured | **active** |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Correct the MCP permission-gate semantics

The MCP call is not “always-allow-with-audit-record”: PermissionNext.ask evaluates the MCP tool key and the "*" pattern against the merged ruleset, throws on an explicit deny, and blocks for a reply when the result is ask (src/permission/next.ts:138-168). The always field is only used to persist approval patterns after an always reply; the wildcard merely prevents argument-level distinctions. Misclassifying this as non-deny-capable can lead S3 to preserve the wrong policy behavior.

Useful? React with 👍 / 👎.

Comment thread docs/internal/defork-route-matrix.md Outdated
| D3 | `SessionTools.resolve` — registry-tools loop | `src/session/tools.ts:27` (fn), context/`ask` at `44-75`, dispatch `yield* item.execute(args, ctx)` at `97` | Real: `permission.ask({..., ruleset}).pipe(Effect.orDie)` at `66-74` | `Permission.merge(input.agent.permission, input.session.permission ?? [])` | Yes / Yes (`92-96`, `109-113`) | N/A — **zero production callers**. Exhaustive grep for `SessionTools` outside its own definition file returns only two doc-comments in `src/session/prompt.ts` (noting the shared `stampRegistryToolSource`/`describeMcpTool` helpers) and two doc-comment references in `src/altimate/tool-source.ts`. No call site exists at HEAD. | **latent** |
| D4 | `SessionTools.resolve` — MCP-tools loop | `src/session/tools.ts:124-216`, dispatch `Effect.promise(() => execute(args, opts))` at `145` | Real (with same wildcard-pattern caveat as D2) at `144` | Same merge as D3 | Yes / Yes (`138-142`, `156-160`) | N/A, same as D3 | **latent** (bundled with D3 — same unused resolver) |
| D5 | `BatchTool` inner dispatch | `src/tool/batch.ts:104` — `await AppRuntime.runPromise(tool.execute(validatedParams, toEffectContext(ctx, partID)))` | **Forwarded, not independently gated.** `toEffectContext` (`batch.ts:16-28`) passes the *same* `ctx.ask` the outer dispatcher (D1/D2/D3/D4/D6, whichever invoked "batch") supplied — BatchTool adds no wrapper-level, tool-specific `ask` of its own. Per-inner-tool permission enforcement therefore depends entirely on whether that inner tool's own implementation calls `ctx.ask` internally (many built-ins, e.g. `bash`, do). | Same ruleset as whichever outer dispatcher invoked "batch" (forwarded, not re-derived) | **No** — confirmed by full read of `tool/batch.ts`: no `Plugin.trigger` call anywhere in the file. `stampRegistryToolSource` is also never applied to inner results. This is the genuine, precisely-scoped bypass: batched inner tool calls skip plugin hooks and source-stamping, not permission checks outright. | Yes — the model can invoke `batch` from any agent that has it enabled, and can batch any tool present in `ToolRegistry.tools(...)` except `batch` itself (`DISALLOWED`, `batch.ts:11`) | **active** |
| D6 | Direct Task-tool dispatch (subtask replay/continuation) | `src/session/prompt.ts:517-625` inside `loop()`; `Plugin.trigger("tool.execute.before", ...)` at `580-588`, dispatch `await AppRuntime.runPromise(taskTool.execute(taskArgs, taskCtx))` at `625`, `Plugin.trigger("tool.execute.after", ...)` at `636-644` | Real: `PermissionNext.ask({..., ruleset})` at `612-621` | `PermissionNext.merge(taskAgent.permission, session.permission ?? [])` | Yes / Yes | Yes — fires whenever a pending `task`-type item is popped off the session's task queue (subtask continuation), independent of the current turn's LLM tool_calls | **active** |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Record that D6 skips its permission ask

For this exact pending-subtask route, taskCtx.extra.bypassAgentCheck is always set to true at src/session/prompt.ts:597, and TaskTool.execute only calls ctx.ask when that flag is false (src/tool/task.ts:96-107). Therefore the documented PermissionNext.ask closure is never exercised by D6, so describing it as a real permission gate hides that these direct task executions are currently ungated and need dispatcher-level policy enforcement.

Useful? React with 👍 / 👎.

Comment thread docs/internal/defork-route-matrix.md Outdated
Comment on lines +86 to +88
All test names below are verified verbatim against `route-sentinels.test.ts`
as committed (9 tests, all passing via `bun test
packages/opencode/test/altimate/defork/route-sentinels.test.ts`).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Provide a runnable sentinel-test command

The documented command cannot produce the claimed nine passing tests from either plausible working directory: at the repository root, the root bunfig.toml sets the test root to the deliberately nonexistent do-not-run-tests-from-root, while from packages/opencode the prefixed path resolves to a nonexistent nested packages/opencode/... path. Document the package-local invocation, such as cd packages/opencode && bun test test/altimate/defork/route-sentinels.test.ts, so the S2 result is reproducible.

Useful? React with 👍 / 👎.

Comment thread docs/internal/defork-route-matrix.md Outdated
| D6 | Direct Task-tool dispatch (subtask replay/continuation) | `src/session/prompt.ts:517-625` inside `loop()`; `Plugin.trigger("tool.execute.before", ...)` at `580-588`, dispatch `await AppRuntime.runPromise(taskTool.execute(taskArgs, taskCtx))` at `625`, `Plugin.trigger("tool.execute.after", ...)` at `636-644` | Real: `PermissionNext.ask({..., ruleset})` at `612-621` | `PermissionNext.merge(taskAgent.permission, session.permission ?? [])` | Yes / Yes | Yes — fires whenever a pending `task`-type item is popped off the session's task queue (subtask continuation), independent of the current turn's LLM tool_calls | **active** |
| D7 | `TaskTool`'s own recursive prompt call | `src/tool/task.ts` → recursively invokes `SessionPrompt.prompt` for the spawned subagent session | N/A — delegates to D1/D2 for the child session's own tool loop | Child session's own agent/session ruleset | N/A at this layer (delegates) | Yes, whenever `task`/subagent tool is invoked | **active** (not a distinct dispatch primitive — routes back into D1/D2 for the child session) |
| D8 | CLI `debug agent --tool <id>` handler | `src/cli/cmd/debug/agent.handler.ts:60` — `const result = yield* tool.execute(params, toolCtx)` | Real: `ctx.ask` at `196-205` evaluates `Permission.evaluate(req.permission, pattern, ruleset)` and throws `PermissionV1.DeniedError` on `deny` | `Permission.merge(agent.permission, session.permission ?? [])` (`186`) | **No** — full-file read confirms zero `Plugin` import/usage in `agent.handler.ts`. Inconsistent with every other dispatcher above. | Only reachable via the `opencode debug agent` developer CLI command, not via any model-facing surface | **active, but out-of-scope** per the spec's "arbitrary code outside HardPolicy's model-invoked boundary" carve-out — it is a human-operated debug utility, not something the model can trigger. Flagged here because the missing `Plugin.trigger` is a real inconsistency worth fixing regardless of HardPolicy scope. |
| — | `SessionPrompt.shell` | `src/session/prompt.ts:2418-2664`, raw process spawn at `2580` (`spawn(shell, args, {...})`) | N/A — never touches `ToolRegistry`/`resolveTools`/any `Tool.Info.execute` | N/A | Only extensibility hook is `Plugin.trigger("shell.env", {cwd, sessionID, callID}, {env: {}})` at `2575-2579` — this is the spec's own named `out-of-scope` example, matched verbatim. | N/A | **out-of-scope** (matches spec's own example). **Flag:** the server v2 `shell` HTTP route (`src/server/routes/instance/httpapi/handlers/session.ts:367-388`) gates this with **only** `requireSession(ctx.params.sessionID)` (`371`) — a session-existence check, **no permission/Ruleset check of any kind**. Formally out of HardPolicy's model-invoked-tool boundary, but worth a security follow-up outside this spike since it is a raw-exec HTTP route with no ask-gate. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include the v1 shell endpoint in the security follow-up

The same ungated raw-shell path is exposed by the v1 Hono endpoint at src/server/routes/session.ts:1004-1034, whose handler directly calls SessionPrompt.shell without any permission/Ruleset check. Restricting the warning to the v2 handler leaves users of the legacy /session/:sessionID/shell route out of the stated security follow-up even though it reaches the identical process spawn.

Useful? React with 👍 / 👎.

// Documented as `latent`: wired but with zero production callers at HEAD.
// ---------------------------------------------------------------------------

test("D3/D4: SessionTools.resolve has no production caller (latent guard)", () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Latent guard only inspects two known files — a new caller anywhere else in src/ would slip through

The test name and inline comment say this guards SessionTools.resolve against gaining "a real call site outside src/session/tools.ts itself", but the implementation only reads session/prompt.ts and altimate/tool-source.ts — the two files where doc-comment references were already known to exist. A future PR that calls SessionTools.resolve(...) in any other file (e.g. under src/acp/, src/server/, or a brand-new module) would not trip this guard, so D3/D4's latent classification could silently become wrong. Consider scanning every .ts file under src/ for SessionTools.resolve( (e.g. via Bun.Glob or a recursive readdir) and asserting the only hit is inside src/session/tools.ts.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

}

const input: any = {
agent: { name: "build", mode: "primary", permission: {}, options: {} },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Use [] instead of {} for agent.permission to match the documented PermissionV1.Ruleset shape

The D1 test in this same file (lines 75-79) explicitly documents that permission is a PermissionV1.Ruleset (array of rules, not a bare object — confirmed at src/agent/agent.ts:55), and that passing {} instead of [] makes rule.permission/rule.pattern undefined and crashes Wildcard.match() deep inside Skill.available. The D3/D4 test passes here only because every service that would consume input.agent.permission is mocked (Permission.Service.ask returns Effect.void, and InvalidTool never calls ctx.ask), so Permission.merge({}, []) is never computed. Aligning on [] keeps the synthetic input shape-consistent with the rest of the file and prevents a confusing crash if the mocks are ever loosened.

Suggested change
agent: { name: "build", mode: "primary", permission: {}, options: {} },
agent: { name: "build", mode: "primary", permission: [], options: {} },

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Jul 19, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 2
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/opencode/test/altimate/defork/route-sentinels.test.ts 222 Carried forward (unchanged line): agent.permission: {} is the wrong shape — PermissionV1.Ruleset is Rule[] (per src/agent/agent.ts:55 and the D1 test's own warning at lines 75-79). Should be []; only passes today because Permission.Service.ask is fully mocked (InvalidTool never calls ctx.ask). Aligning on [] keeps the synthetic input shape-consistent with the rest of the file and prevents a confusing crash if the mocks are ever loosened.
packages/opencode/test/altimate/defork/route-sentinels.test.ts 338 Carried forward (unchanged line): D6 structural pin dropped the "tool.execute.before" / "tool.execute.after" assertions, so it no longer enforces the Plugin.trigger wrapping that the matrix uses to distinguish D6 (Yes/Yes) from the D5 BatchTool bypass (No). A future refactor removing Plugin.trigger from D6 would leave this sentinel green but invalidate the matrix row.
Incremental Review (26db15d235295a)

The incremental diff (2 files, +34/−6):

  • Resolved the prior line-128 .tsx-glob SUGGESTION — the D3/D4 latent guard now scans **/*.{ts,tsx} (with an inline comment explaining the ~12 .tsx files under src/), closing the same class of gap the previous full-tree refactor aimed at.
  • No new issues in the new "Coverage contract" section of defork-route-matrix.md — claims spot-checked against the current test file: D1 has a real-execution test, D5 has a real-execution test (line 301) plus structural pin (line 304), D2's transitive-coverage rationale is consistent with the file, D6's citation-based coverage (line 341-344) matches the documented exception.

The two prior SUGGESTIONs on lines 222 and 338 remain open on unchanged lines (current-code re-verified).

Files in incremental diff:

  • packages/opencode/test/altimate/defork/route-sentinels.test.ts — 1 resolved, 0 new
  • docs/internal/defork-route-matrix.md — doc-only, no issues
Files Reviewed (2 files in incremental diff)
  • packages/opencode/test/altimate/defork/route-sentinels.test.ts — 2 carried-forward issues
  • docs/internal/defork-route-matrix.md — doc-only

Fix these issues in Kilo Cloud

Previous Review Summaries (3 snapshots, latest commit 26db15d)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 26db15d)

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 3
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/opencode/test/altimate/defork/route-sentinels.test.ts 128 New: latent-guard glob **/*.ts skips the 12 .tsx files under src/ (TUI/plugin components in plugin/tui/altimate/*.tsx, cli/cmd/run/*.tsx) — a SessionTools.resolve( caller added in any of them would slip through, same shape as the original two-file gap this refactor just closed. None currently reference SessionTools, but the test's stated goal is "ENTIRE production src tree".
packages/opencode/test/altimate/defork/route-sentinels.test.ts 220 Carried forward: agent.permission: {} is the wrong shape — PermissionV1.Ruleset is Rule[]. Should be []; only passes today because Permission.Service.ask is fully mocked. Line unchanged in this revision.
packages/opencode/test/altimate/defork/route-sentinels.test.ts 336 Carried forward: D6 structural pin dropped the "tool.execute.before" / "tool.execute.after" assertions, so it no longer enforces the Plugin.trigger wrapping that distinguishes D6 from the D5 BatchTool bypass. Line unchanged in this revision.
Incremental Review (970c04326db15d)

The incremental diff (3 files, +142/−29):

  • Resolved the prior line-122 SUGGESTION — the D3/D4 latent guard now scans the whole src/ tree via Bun.Glob instead of just session/prompt.ts + altimate/tool-source.ts.
  • Verified two security-relevant doc corrections in defork-route-matrix.md: D2's PermissionNext.ask IS deny-capable (src/permission/next.ts:138-168 throws on deny, blocks on ask), and D5's BatchTool IS a real permission bypass (src/tool/batch.ts:62 resolves tools without the agent; batch.ts:104 dispatches with no wrapper-level gate). Both claims are accurate against current source.
  • One new SUGGESTION: the new glob pattern still excludes .tsx files, leaving the same class of gap the refactor aimed to close.

The two prior SUGGESTIONs on lines 220 and 336 remain open on unchanged lines.

Files in incremental diff:

  • packages/opencode/test/altimate/defork/route-sentinels.test.ts — 1 new issue, 1 resolved
  • docs/internal/defork-route-matrix.md — doc-only, no issues
  • docs/internal/2026-07-18-defork-spike-spec.md — new spec doc, no issues
Files Reviewed (3 files)
  • packages/opencode/test/altimate/defork/route-sentinels.test.ts — 3 issues
  • docs/internal/defork-route-matrix.md
  • docs/internal/2026-07-18-defork-spike-spec.md

Fix these issues in Kilo Cloud

Previous review (commit 970c043)

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 3
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/opencode/test/altimate/defork/route-sentinels.test.ts 122 Latent guard only inspects session/prompt.ts and altimate/tool-source.ts; a new SessionTools.resolve(...) caller in any other src/ file would slip through undetected, so the latent classification could silently become wrong.
packages/opencode/test/altimate/defork/route-sentinels.test.ts 224 agent.permission: {} is the wrong shape — PermissionV1.Ruleset is Rule[] (per src/agent/agent.ts:55 and the D1 test's own warning at lines 81-85). Should be []; only passes today because Permission.Service.ask is fully mocked.
packages/opencode/test/altimate/defork/route-sentinels.test.ts 340 Incremental refactor dropped the "tool.execute.before" / "tool.execute.after" assertions — the D6 structural pin no longer enforces the Plugin.trigger wrapping that the matrix uses to distinguish D6 (Yes/Yes) from the D5 BatchTool bypass (No). A future refactor removing Plugin.trigger from D6 would leave this sentinel green but invalidate the matrix row.
Incremental Review (d3b8119970c043)

The incremental diff refactors three structural-pin tests from hardcoded line numbers to content/uniqueness-based assertions (merge-safe with S3 HardPolicy). D1 and D3/D4 preserved full assertion coverage; D6 lost 2 of 4 assertions (the Plugin.trigger flanking checks) — flagged as the one new issue above. The two prior suggestions (lines 122, 224) remain open on unchanged lines.

Files in incremental diff:

  • packages/opencode/test/altimate/defork/route-sentinels.test.ts — 1 new issue
  • docs/internal/defork-route-matrix.md — not in incremental diff

Fix these issues in Kilo Cloud

Previous review (commit d3b8119)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 2
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/opencode/test/altimate/defork/route-sentinels.test.ts 119 Latent guard only inspects session/prompt.ts and altimate/tool-source.ts; a new SessionTools.resolve(...) caller in any other src/ file would slip through undetected, so the latent classification could silently become wrong.
packages/opencode/test/altimate/defork/route-sentinels.test.ts 221 agent.permission: {} is the wrong shape — PermissionV1.Ruleset is Rule[] (per src/agent/agent.ts:55 and the D1 test's own warning at lines 75-79). Should be []; only passes today because Permission.Service.ask is fully mocked.
Files Reviewed (2 files)
  • packages/opencode/test/altimate/defork/route-sentinels.test.ts - 2 issues
  • docs/internal/defork-route-matrix.md - 0 issues (documentation only; cross-checked cited line numbers against src/session/prompt.ts, src/session/tools.ts, src/tool/batch.ts, and test/session/prompt.test.ts — all accurate)

Fix these issues in Kilo Cloud


Reviewed by glm-5.2 · Input: 58.4K · Output: 6.8K · Cached: 479.6K

Review guidance: REVIEW.md from base branch main

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/opencode/test/altimate/defork/route-sentinels.test.ts">

<violation number="1" location="packages/opencode/test/altimate/defork/route-sentinels.test.ts:148">
P2: Hardcoded `/tmp` instead of a disposable temp directory. D1 and D5 tests in this same file use `await using tmp = await tmpdir(...)` for isolation, but D3/D4 hardcodes `/tmp` — a shared system path. `Instance.provide` caches instances by directory, so parallel runs or other tests targeting `/tmp` could collide or reuse stale state.</violation>

<violation number="2" location="packages/opencode/test/altimate/defork/route-sentinels.test.ts:221">
P2: The `permission` field here is `{}` (an empty object), but the same file's D1 test (lines 75–79) explicitly documents that `permission` is a `PermissionV1.Ruleset` — an array of rules — and that passing `{}` instead of `[]` causes `rule.permission`/`rule.pattern` to be undefined, crashing `Wildcard.match()` deep inside `Skill.available`. This test only passes because every service that would consume `agent.permission` is mocked away. Aligning on `[]` keeps the synthetic input consistent with the documented shape and prevents a confusing failure if mocks are loosened.</violation>
</file>

<file name="docs/internal/defork-route-matrix.md">

<violation number="1" location="docs/internal/defork-route-matrix.md:4">
P2: The referenced `docs/internal/2026-07-18-defork-spike-spec.md` does not appear to exist in this commit or its parent. The matrix cites it here as its governing specification, and the D6 test section references a specific line (`:61`) from it. If this file hasn't been committed, reviewers cannot verify the declared S2 requirements or the cited line. Either commit the specification in this PR or replace these references with an accessible source.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

}

const input: any = {
agent: { name: "build", mode: "primary", permission: {}, options: {} },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The permission field here is {} (an empty object), but the same file's D1 test (lines 75–79) explicitly documents that permission is a PermissionV1.Ruleset — an array of rules — and that passing {} instead of [] causes rule.permission/rule.pattern to be undefined, crashing Wildcard.match() deep inside Skill.available. This test only passes because every service that would consume agent.permission is mocked away. Aligning on [] keeps the synthetic input consistent with the documented shape and prevents a confusing failure if mocks are loosened.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/test/altimate/defork/route-sentinels.test.ts, line 221:

<comment>The `permission` field here is `{}` (an empty object), but the same file's D1 test (lines 75–79) explicitly documents that `permission` is a `PermissionV1.Ruleset` — an array of rules — and that passing `{}` instead of `[]` causes `rule.permission`/`rule.pattern` to be undefined, crashing `Wildcard.match()` deep inside `Skill.available`. This test only passes because every service that would consume `agent.permission` is mocked away. Aligning on `[]` keeps the synthetic input consistent with the documented shape and prevents a confusing failure if mocks are loosened.</comment>

<file context>
@@ -0,0 +1,345 @@
+      }
+
+      const input: any = {
+        agent: { name: "build", mode: "primary", permission: {}, options: {} },
+        model: { providerID: "test", api: { id: "test-model" } },
+        session: { id: "ses_fake", permission: [] },
</file context>

Comment thread packages/opencode/test/altimate/defork/route-sentinels.test.ts Outdated

test("D3/D4: SessionTools.resolve dispatches a tool when invoked directly", async () => {
await Instance.provide({
directory: "/tmp",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Hardcoded /tmp instead of a disposable temp directory. D1 and D5 tests in this same file use await using tmp = await tmpdir(...) for isolation, but D3/D4 hardcodes /tmp — a shared system path. Instance.provide caches instances by directory, so parallel runs or other tests targeting /tmp could collide or reuse stale state.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/test/altimate/defork/route-sentinels.test.ts, line 148:

<comment>Hardcoded `/tmp` instead of a disposable temp directory. D1 and D5 tests in this same file use `await using tmp = await tmpdir(...)` for isolation, but D3/D4 hardcodes `/tmp` — a shared system path. `Instance.provide` caches instances by directory, so parallel runs or other tests targeting `/tmp` could collide or reuse stale state.</comment>

<file context>
@@ -0,0 +1,345 @@
+
+test("D3/D4: SessionTools.resolve dispatches a tool when invoked directly", async () => {
+  await Instance.provide({
+    directory: "/tmp",
+    fn: async () => {
+      const invalidInfo = await toolInfo(InvalidTool)
</file context>

Comment thread docs/internal/defork-route-matrix.md Outdated
Comment thread docs/internal/defork-route-matrix.md Outdated
Comment thread docs/internal/defork-route-matrix.md Outdated
# De-fork spike — S2 execution-route trace + bypass matrix

Status: draft for S3 handoff. Analysis-only; no product code was modified to
produce this document. Governed by S2 of `docs/internal/2026-07-18-defork-spike-spec.md`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The referenced docs/internal/2026-07-18-defork-spike-spec.md does not appear to exist in this commit or its parent. The matrix cites it here as its governing specification, and the D6 test section references a specific line (:61) from it. If this file hasn't been committed, reviewers cannot verify the declared S2 requirements or the cited line. Either commit the specification in this PR or replace these references with an accessible source.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/internal/defork-route-matrix.md, line 4:

<comment>The referenced `docs/internal/2026-07-18-defork-spike-spec.md` does not appear to exist in this commit or its parent. The matrix cites it here as its governing specification, and the D6 test section references a specific line (`:61`) from it. If this file hasn't been committed, reviewers cannot verify the declared S2 requirements or the cited line. Either commit the specification in this PR or replace these references with an accessible source.</comment>

<file context>
@@ -0,0 +1,112 @@
+# De-fork spike — S2 execution-route trace + bypass matrix
+
+Status: draft for S3 handoff. Analysis-only; no product code was modified to
+produce this document. Governed by S2 of `docs/internal/2026-07-18-defork-spike-spec.md`
+(rev 4, Codex-consensus-approved).
+
</file context>

Comment thread docs/internal/defork-route-matrix.md Outdated
The D1/D3/D4/D6 structural pins asserted the dispatch chokepoint at a hardcoded
line index and with the exact arg name. S3's HardPolicy insertions shift those
lines and rename the dispatched args to `finalArgs` (it must check the
post-`tool.execute.before` args), which would break these pins once S3 lands.
Rewrite them to assert the chokepoint pattern occurs exactly once, matching both
`args` and `finalArgs`, so this file passes identically with or without S3
applied — mergeable in any order.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

2 similar comments
@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 970c043d12

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

| D5 `BatchTool` inner dispatch | "D5: BatchTool dispatches inner tool.execute without Plugin.trigger" (real execution, via `initTool(BatchTool)`) + "D5: BatchTool never calls Plugin.trigger for inner tool calls (structural bypass)" (structural) |
| D6 direct Task dispatch | "D6: direct Task dispatch chokepoints are pinned at the documented lines" (structural) + "D6: existing real-execution proof exists in test/session/prompt.test.ts" (citation of `it.instance("failed subtask preserves metadata on error tool state"`) — **partial compliance, flagged**: this route does not have a fresh, self-contained real-execution sentinel in `route-sentinels.test.ts` itself; see the compliance note in that file's D6 section for rationale (reconstructing the cited test's harness would duplicate ~250 lines of module-private test infrastructure from `prompt.test.ts` that isn't exported anywhere). Team-lead should decide whether to accept this as a documented S2 exception or require a follow-up to export a shared harness. |

**D2 (`resolveTools` MCP-tools loop) is intentionally absent from this table.**

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add a sentinel for the active MCP dispatcher

When the MCP-only permission or dispatch code at src/session/prompt.ts:1656-1669 regresses, the documented nine-test suite still passes because D2 is intentionally omitted here. The D1 test cannot cover that branch: it invokes the registry-backed InvalidTool, whose implementation never calls ctx.ask, so it exercises neither the MCP wrapper nor its permission gate. Since D2 is classified as active and this document promises an executable sentinel for every active route, add a fake-MCP execution test that asserts both the ask and the underlying execute call.

Useful? React with 👍 / 👎.

Comment on lines +343 to +345
test("D6: existing real-execution proof exists in test/session/prompt.test.ts", () => {
const promptTestSrc = readFileSync(join(SRC, "..", "test", "session", "prompt.test.ts"), "utf8")
expect(promptTestSrc).toContain('it.instance("failed subtask preserves metadata on error tool state"')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Execute the D6 proof instead of checking its name

This assertion only proves that a test declaration string exists; the documented command runs this sentinel file alone and never executes test/session/prompt.test.ts. It therefore remains green if the cited test stops reaching taskTool.execute while retaining its name—for example, that test currently returns without failure when errorTool(taskMsg.parts) is absent at prompt.test.ts:837-838. Run a shared D6 harness or otherwise execute and assert the dispatch here so this active route has the promised real-execution proof.

Useful? React with 👍 / 👎.

const lines = readSrc("session/prompt.ts").split("\n")
// Content-based + arg-agnostic (matches `taskArgs` or S3's `finalTaskArgs`) — see D1's note.
expect(lines.filter((l) => l.includes("PermissionNext.merge(taskAgent.permission, session.permission ?? []),")).length).toBe(1)
expect(lines.filter((l) => l.includes("AppRuntime.runPromise(taskTool.execute(") && l.includes(", taskCtx)")).length).toBe(1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Dropped Plugin.trigger assertions weaken the D5-vs-D6 structural distinction

The previous form of this test asserted "tool.execute.before" (old line 580) and "tool.execute.after" (old line 636) flank the task dispatch. Those markers are exactly what the matrix uses to tell D6 apart from D5 — the BatchTool bypass whose defining property is no Plugin.trigger anywhere in tool/batch.ts (matrix line 53, still structurally enforced by the D5 sentinel at line 305 of this file; D6 is documented Plugin.trigger Yes / Yes at matrix line 54). After this refactor, stripping Plugin.trigger from D6 would invalidate the matrix's D6 row but leave this sentinel green, and the cited real-execution test (prompt.test.ts:806) doesn't assert on Plugin.trigger either. If the assertions were dropped only because those strings aren't unique enough for .length).toBe(1) (they appear 6x in session/prompt.ts), consider an indexOf-style check that the unique dispatch line is wrapped by some preceding tool.execute.before and following tool.execute.after, so the D5/D6 distinction stays enforced on both sides.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

…mit spec

- Commit the governing spec (`2026-07-18-defork-spike-spec.md`) the matrix
  references, so reviewers can verify the S2 requirements (Codex).
- D2/D4 MCP: corrected — `PermissionNext.ask` IS deny-capable (evaluates key +
  `*` pattern, throws on deny, blocks on `ask`); the wildcard only removes
  argument-level distinctions, it isn't "always-allow" (Codex).
- D5 batch: reframed as a genuine PERMISSION bypass — inner tools that don't
  self-call `ctx.ask` (e.g. `warehouse_remove`) escape their deny rules when
  reached via `batch`; this is why S3 gates each inner dispatch (Codex).
- D6: noted `bypassAgentCheck=true` means the task tool's own `ctx.ask` never
  fires, so these executions are ungated today — the reason S3 adds a
  dispatcher-level check at `prompt.ts:625` (Codex).
- Added the direct `ReadTool` (user-attachment) dispatches as an explicit
  out-of-scope entry so the audit covers every `Tool.Info.execute` site (Codex).
- Included the v1 Hono `shell` endpoint in the security note; filed issue #1019
  to track both v1+v2 ungated shell routes (Codex + CodeRabbit).
- Fixed the runnable sentinel-test command (must run from `packages/opencode`).
- Latent guard now scans the ENTIRE production src tree for
  `SessionTools.resolve(` callers, not just two files (Codex).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

2 similar comments
@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/internal/defork-route-matrix.md`:
- Line 50: The route matrix’s “every active route” coverage claim is
inconsistent with D2 and D6 lacking dedicated executable sentinels. Update
route-sentinels.test.ts to add runtime sentinels for D2 and D6, or revise the
matrix coverage contract to explicitly identify these structural/citation-only
exceptions and avoid overstating S3 HardPolicy coverage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0fab006d-a7cf-4d6c-a76f-7d4219fab8d8

📥 Commits

Reviewing files that changed from the base of the PR and between 970c043 and 26db15d.

📒 Files selected for processing (3)
  • docs/internal/2026-07-18-defork-spike-spec.md
  • docs/internal/defork-route-matrix.md
  • packages/opencode/test/altimate/defork/route-sentinels.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/opencode/test/altimate/defork/route-sentinels.test.ts

Comment thread docs/internal/defork-route-matrix.md

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 3 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread docs/internal/defork-route-matrix.md
// `active` so HardPolicy coverage there is re-verified. We look for a real
// `SessionTools.resolve(` CALL on a non-comment line, excluding the resolver's
// own definition file (session/tools.ts) where the name is declared.
const glob = new Bun.Glob("**/*.ts")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Glob **/*.ts skips the 12 .tsx files under src/

The refactor's stated intent ("Scan the ENTIRE production src tree") is undercut by the glob pattern: .tsx files (TUI components in plugin/tui/altimate/*.tsx and cli/cmd/run/*.tsx) aren't scanned, so a SessionTools.resolve( caller added in any of them would slip through — the same shape of gap this refactor just closed for the two-file case. None of the 12 currently reference SessionTools, but the latent guard's whole purpose is to flip the moment one appears anywhere in src/.

Suggested change
const glob = new Bun.Glob("**/*.ts")
const glob = new Bun.Glob("**/*.{ts,tsx}")

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 26db15dfa4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +132 to +137
const src = readFileSync(join(SRC, rel), "utf8")
if (!src.includes("SessionTools.resolve(")) continue
src.split("\n").forEach((line, i) => {
const t = line.trim()
const isComment = t.startsWith("//") || t.startsWith("*") || t.startsWith("/*")
if (!isComment && line.includes("SessionTools.resolve(")) callers.push(`${rel}:${i + 1}`)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Detect flat and aliased resolver imports

The broadened full-tree scan still detects only the literal spelling SessionTools.resolve(. Because session/tools.ts directly exports resolve, a production caller can naturally use import { resolve } from "@/session/tools" (or alias the namespace) and invoke it without this guard noticing, leaving D3/D4 classified as latent and outside S3 coverage. This revision's new full-tree implementation is fresh evidence that the earlier guard was expanded but still does not inspect imports or other call spellings.

Useful? React with 👍 / 👎.


| # | Dispatcher | File:line | `ctx.ask` wiring | Ruleset | `Plugin.trigger` before/after | Reachable under allow-all | State |
|---|---|---|---|---|---|---|---|
| D1 | `SessionPrompt.resolveTools` — registry-tools loop | `src/session/prompt.ts:1521` (fn), context/`ask` at `1562-1572`, dispatch `await AppRuntime.runPromise(item.execute(args, ctx))` at `1601`, source-stamp `1614` | Real: `PermissionNext.ask({..., ruleset})` wrapped in `Effect.promise` | `PermissionNext.merge(input.agent.permission, input.session.permission ?? [])` | Yes / Yes (`1589-1599`, `1615-1624`) | Yes — this is the primary, always-reachable per-turn tool dispatch path (called from `loop()` at `918`) | **active** |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Record D1's session-rule bypass

For the active D1 path, the merged ruleset is only installed on ctx.ask; it is not automatically evaluated at this dispatch. The production prefilter in src/session/llm.ts:309-316 checks only input.agent.permission, so a session-level deny can still expose and execute a tool whose implementation never calls ctx.ask—for example, warehouse_remove at src/altimate/tools/warehouse-remove.ts:10-35. Presenting the merged ruleset as applied without documenting this condition hides an active permission bypass from the S3 handoff.

Useful? React with 👍 / 👎.

Comment on lines +299 to +302
test("D5: BatchTool never calls Plugin.trigger for inner tool calls (structural bypass)", () => {
const src = readSrc("tool/batch.ts")
expect(src).not.toContain("Plugin.trigger(")
expect(src).toContain("await AppRuntime.runPromise(tool.execute(validatedParams, toEffectContext(ctx, partID)))")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Detect injected plugin hooks in the batch sentinel

This sentinel remains green if BatchTool starts using the established Effect-service form plugin.trigger(...) instead of the static Plugin.trigger(...) spelling used by D1. That form already exists in src/session/tools.ts:92-96, so a migration of batch.ts to an injected Plugin.Service would fix the documented hook bypass without invalidating this test or forcing the matrix to be updated. Check for execution-hook calls independent of receiver spelling, or assert hook behavior dynamically.

Useful? React with 👍 / 👎.

| D2 | `SessionPrompt.resolveTools` — MCP-tools loop | `src/session/prompt.ts:1630-1739`, dispatch `const result = await execute(args, opts)` at `1669` | Real and **deny-capable**: `AppRuntime.runPromise(ctx.ask({permission: key, patterns: ["*"], always: ["*"]}))` at `1656-1667`. Correction: `PermissionNext.ask` DOES evaluate the tool key + `"*"` pattern against the merged ruleset — it throws on an explicit `deny` and blocks for a reply when the result is `ask` (`src/permission/next.ts:138-168`). The `always: ["*"]` field only persists approval patterns after an `always` reply; the wildcard `patterns` merely prevents *argument-level* distinctions — it does NOT make the gate non-deny-capable. Comment documents a **previously fixed bug**: `await ctx.ask(...)` alone only awaited the Effect *object*, never ran `PermissionNext.ask` — MCP tools executed with no permission check until this was corrected (`upstream_fix` marker). | Same merge as D1 (wildcard pattern) | Yes / Yes (`1644-1654`, `1671-1680`) | Yes, if any MCP server is configured | **active** |
| D3 | `SessionTools.resolve` — registry-tools loop | `src/session/tools.ts:27` (fn), context/`ask` at `44-75`, dispatch `yield* item.execute(args, ctx)` at `97` | Real: `permission.ask({..., ruleset}).pipe(Effect.orDie)` at `66-74` | `Permission.merge(input.agent.permission, input.session.permission ?? [])` | Yes / Yes (`92-96`, `109-113`) | N/A — **zero production callers**. Exhaustive grep for `SessionTools` outside its own definition file returns only two doc-comments in `src/session/prompt.ts` (noting the shared `stampRegistryToolSource`/`describeMcpTool` helpers) and two doc-comment references in `src/altimate/tool-source.ts`. No call site exists at HEAD. | **latent** |
| D4 | `SessionTools.resolve` — MCP-tools loop | `src/session/tools.ts:124-216`, dispatch `Effect.promise(() => execute(args, opts))` at `145` | Real (with same wildcard-pattern caveat as D2) at `144` | Same merge as D3 | Yes / Yes (`138-142`, `156-160`) | N/A, same as D3 | **latent** (bundled with D3 — same unused resolver) |
| D5 | `BatchTool` inner dispatch | `src/tool/batch.ts:104` — `await AppRuntime.runPromise(tool.execute(validatedParams, toEffectContext(ctx, partID)))` | **Forwarded, not independently gated.** `toEffectContext` (`batch.ts:16-28`) passes the *same* `ctx.ask` the outer dispatcher (D1/D2/D3/D4/D6, whichever invoked "batch") supplied — BatchTool adds no wrapper-level, tool-specific `ask` of its own. Per-inner-tool permission enforcement therefore depends entirely on whether that inner tool's own implementation calls `ctx.ask` internally (many built-ins, e.g. `bash`, do). | Same ruleset as whichever outer dispatcher invoked "batch" (forwarded, not re-derived) | **No** — confirmed by full read of `tool/batch.ts`: no `Plugin.trigger` call anywhere in the file. `stampRegistryToolSource` is also never applied to inner results. **This is also a genuine PERMISSION bypass, not merely a hooks/stamping gap:** `BatchTool` resolves every registry tool WITHOUT the current agent (`batch.ts:62`) and invokes it directly, so an inner tool that does not call `ctx.ask` internally (e.g. `warehouse_remove`, `src/altimate/tools/warehouse-remove.ts`) is executed with NO permission evaluation — a configured `warehouse_remove: deny` rule is never applied when the tool is reached via `batch`. This is exactly why S3 gates each inner dispatch independently at `batch.ts:104` rather than relying on inner tools' own `ctx.ask`. | Yes — the model can invoke `batch` from any agent that has it enabled, and can batch any tool present in `ToolRegistry.tools(...)` except `batch` itself (`DISALLOWED`, `batch.ts:11`) | **active** |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict BatchTool's outer routes to registry dispatchers

BatchTool cannot be invoked by all of the listed outer dispatchers: D2/D4 iterate only MCP-provided tools, while D6 initializes and executes TaskTool explicitly at src/session/prompt.ts:524-625. The registry-backed BatchTool can currently arrive only through D1 (or D3 if that latent resolver is activated), so attributing its forwarded context and ruleset to D2/D4/D6 creates nonexistent route combinations in the authoritative S3 coverage matrix.

Useful? React with 👍 / 👎.

…tsx callers

- coderabbit MAJOR: the matrix claimed "every active route has an executable
  sentinel test", which overstates coverage — D2 (MCP resolveTools loop) has only
  structural pins + transitive coverage via D1, and D6 (direct Task dispatch) has
  structural pins + a citation of an existing prompt.test.ts test rather than a
  fresh self-contained sentinel. Replace the blanket claim with an explicit
  "Coverage contract" section that states exactly what holds: dedicated
  real-execution sentinels for D1 and D5; D2 and D6 as documented
  transitive/citation exceptions; latent-guard for D3/D4; out-of-scope routes
  listed for completeness. No longer reads as full dedicated-sentinel coverage.

- kilo P2: the D3/D4 latent-guard's caller scan globbed `**/*.ts`, skipping the
  ~12 `.tsx` files under `src/` — a new `SessionTools.resolve(` caller added in a
  TUI `.tsx` file would slip past the guard. Broaden to `**/*.{ts,tsx}`. 9 pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

2 similar comments
@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@github-actions

Copy link
Copy Markdown

👋 This PR was automatically closed by our quality checks.

Common reasons:

  • New GitHub account with limited contribution history
  • PR description doesn't meet our guidelines
  • Contribution appears to be AI-generated without meaningful review

If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 235295a65e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +68 to +69
`src/server/routes/experimental.ts` contains only a stale comment; no live
route.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Correct the live experimental-route classification

At this commit, src/server/routes/experimental.ts defines numerous live Hono endpoints and is mounted at /experimental by both src/server/server.ts:324 and src/server/instance.ts:60, so it is not a file containing only a stale comment. Although the current endpoints do not execute tools, this authoritative route inventory should record that the router was inspected and has no dispatch point rather than incorrectly declaring the entire production surface inactive.

Useful? React with 👍 / 👎.


## S2 — Execution-route inventory + sentinel tests (DETAILED, Codex contract adopted)

1. **Separate ingress surfaces from execution dispatchers.** Ingress: CLI `run`, TUI, server v1 routes, server v2 HttpApi, ACP, GitHub/GitLab handlers. Dispatchers (the things HardPolicy must cover): `session/tools.ts` resolver wrapper, `session/prompt.ts` parallel resolver, **BatchTool's direct registry execution (`tool/batch.ts:104` — bypasses both wrappers)**, **direct Task dispatch in `session/prompt.ts:580`**, MCP wrapper path, plus anything discovered (debug/internal direct calls).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Point the Task dispatcher at the actual execute call

In the reviewed tree, src/session/prompt.ts:580 is the tool.execute.before plugin trigger, while the direct Task dispatch defined by this requirement is taskTool.execute(...) at line 625. Since S2 explicitly inventories the seams where execution actually occurs so S3 can place HardPolicy there, citing the hook instead of the execute call contradicts the matrix and can send an implementation to the wrong location.

Useful? React with 👍 / 👎.

@anandgupta42

Copy link
Copy Markdown
Contributor Author

Review comments addressed

Thanks to @coderabbitai, @chatgpt-codex-connector, @cubic-dev-ai, and @kilo-code-bot.

Round 3 — 235295a65e

  • MAJOR (CodeRabbit — coverage contract): the matrix claimed "every active route has an executable sentinel," which overstated coverage. Replaced with an explicit Coverage contract section: dedicated real-execution sentinels for D1 and D5; D2 (transitive via D1 + structural pins) and D6 (structural pins + citation of an existing prompt.test.ts test) are now named as documented exceptions, not full dedicated-sentinel coverage.
  • P2 (kilo): the D3/D4 latent-guard caller scan globbed **/*.ts, skipping ~12 .tsx files under src/ — broadened to **/*.{ts,tsx} so a new SessionTools.resolve( caller in a TUI file can't slip past.

Earlier doc corrections (dispatcher semantics D1/D2/D5/D6, ReadTool out-of-scope row, v1 shell endpoint + #1019, runnable command) are in 26db15dfa4. 9 sentinel tests pass.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create an environment for this repo.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@anandgupta42 Thanks for the thorough update. The revised coverage contract accurately scopes D1/D5 as dedicated real-execution sentinels and explicitly documents the D2/D6 exceptions. Broadening the latent-route scan to .tsx closes the TUI caller gap as well.

🐇✨


✏️ Learnings added
Learnt from: anandgupta42
URL: https://github.com/AltimateAI/altimate-code/pull/1014

Timestamp: 2026-07-20T02:27:04.102Z
Learning: For the de-fork S2 route sentinels, only D1 and D5 have dedicated real-execution sentinel coverage. D2 is covered transitively through D1 plus structural pins, while D6 uses structural pins and a cited existing `packages/opencode/test/session/prompt.test.ts` execution test; documentation must describe these as explicit coverage-contract exceptions rather than claiming every active route has a dedicated executable sentinel. The `SessionTools.resolve` latent-route caller guard must scan both `.ts` and `.tsx` production source files.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

De-fork spike S2: execution-route inventory + sentinel tests

1 participant