16 KiB
Independent Code Review — feature/agent-stack-audit-remediation-b1
- Reviewer: independent CLI review session (agent-stack-b1-independent-review unit)
- Date: 2026-10-21
- Review object: Dev worktree branch
feature/agent-stack-audit-remediation-b1@d55e57dd(merged tip) versus main @b25846d2; five pinned components diffed old-pin → new-pin:- deps/agent-native
2fc66be6→ac91b843 - deps/agent-squad
c7121bda→0f1daa8b - deps/agent-intent
651a2e1e→765a7e71 - deps/agent-command
4f583ca3→fe7987dc - modules/backend
528e3a4c→b6141800
- deps/agent-native
- Plan: docs/superpowers/plans/2026-10-21-agent-stack-audit-remediation-plan.md (audit baseline
d9e9c0560a) - Claimed scope: batch 1 = U1, U2, U3a/U3b, U4, U5, U6, U7a/U7b, U10.
- Method: read-only review of
git diffper component plus Dev-level files; focused package tests where this worktree permits (see Verification runs). This review made no changes other than this report file.
Findings
Blockers
None.
Major
None.
Minor
- M1 — SessionScope identity string can collide (native).
adapters/native/contract_bind.go:60-66builds the scope identity astenant+"/"+project+"/"+session. Distinct scopes containing/can collide (tenanta/b+ projectcvs tenanta+ projectb/c), which would bind two identities to the same hashed transcript file (contract_bind.go:72-77) and pass the exact-match check inbindSessionScope(adapters/native/backend.go:645-669). Today product identifiers do not contain/, so no legal path regresses; recommend length-prefixed or JSON-encoded identity in a follow-up. - M2 — Reflection/JSON "probe" of statically present fields (squad).
adapters/native/register.go:217-247marshals the native result through JSON to discoverSideEffects/Canceled/Aborted. The squad module is pinned to nativeac91b843, where these fields exist at compile time; direct field copy is identical, simpler, and the "deps 尚未跟进" rationale in the comment cannot occur in a statically linked Go build. Behaviorally equivalent today; flagged as avoidable indirection that can silently mask future field renames (JSON key drift would quietly fall back to "possible"). - M3 —
trustedUnknownOpIDdoes not survive JSON roundtrip (command).result.go:44-49(unexported flag) +result.go:76-95(Validate): an Unknown receipt carrying a trusted OperationID failsValidate()after a marshal/unmarshal cycle. Verified by grep that no in-repo path re-validates a deserialized receipt (backend persists audit envelopes, not receipts;internal/cmdbushas no ExecReceipt unmarshal), so nothing breaks today — but the library contract is sharper than its docs admit. Suggest documenting the roundtrip limitation or providing a rehydration constructor. - M4 — Stale recovery header comment (squad).
squad/engine.go:1053-1069still states "auto 允许一次重试", butHitlAutonow falls into thedefault:reconciliation branch (engine.go:1137-1178) and no longer auto-retries running steps without a trusted record. The code is the intended F05 tightening; only the comment contradicts it. - M5 — Writable preflight does not cover
delegate_task(native). U2 preflight (adapters/native/backend.go:213-227) rejects non-empty Writable with exec/MCP/host tools, but the workspace registry still registerssubagent.Register(backend.go:278) and code orchestration (run_tool_script).run_tool_scriptonly invokes registry tools (which enforce the allowlist, runtime/tools/registry.go:369-388) and bans the interpreter, so the residual exposure is a spawned sub-agent not inheriting the parent's write-set. The plan scoped this batch's preflight to "进程/MCP/宿主工具"; a follow-up decision on subagent scoping is warranted, not a blocker here. - M6 — Canceled mapping keyed on ctx, not on the result (native).
adapters/native/contract.go:100-105maps toOutcomeCanceledonly viaerrors.Is(ctx.Err(), context.Canceled). ArunRes.Canceled/StopAbortedrun that is not caused by ctx cancellation falls to the failed branch (withRetryable=falsebecause backend.go:385-396 already clears it, so no unsafe retry — only the outcome label is less precise). Edge case; native abort paths set ctx.
Notes
- N1 — Squad
AGENT-EXECUTION.mddisables cursor/antigravity adapters (component diff, +10/-2). Operational agent-execution config, not product code; documented on the task card with fail-stop rationale. It will ride the component branch into the component repo — restoreenabled: trueas the file itself instructs. - N2 — Command
AGENT-EXECUTION.mdadded (+67). Host-documented deviation (component lacked one; template parity with agent-intent), recorded on the task card. - N3 — Executor failures without
SideEffects=nonelose auto-retry.squad/workflow.go:455-459+squad/port.go:99-117intentionally default missing qualifier to "possible" (no bounded retry). This is the specified KTD4/U3 behavior change ("缺少副作用证明的失败默认禁止自动重放"), covered byTestSideEffectsPossibleNotRetried/TestSideEffectsNoneRetriedWithinBounds; third-party executors must now declarenoneto keep transient retry. Listed so the API change is visible to integrators. - N4 — Generated-command ActionRef now resolves against the full snapshot, not only the top-k catalog (
intent/recognizer.go:384-437resolveActionRef, replacinggeneratedSpecOf). Deliberate widening that fixes F08's "合法异名目标被拒"; slot keys/values and dispatch still gated byvalidateProvidedSlots(intent/slots.go:174-214) and backend admission (internal/server/intent_admission.go:41-63). - N5 — Pin coherence verified. Nested gitlinks agree at every level: intent
765a7e71(native→deps, squad→deps, backend→deps), nativeac91b843(squad→deps, backend→deps), commandfe7987dc(backend→deps), squad0f1daa8b(backend→deps). - N6 — Audit-failure unknown receipts keep the trusted ID via
unknownReceipt/NewUnknownAfterExecution(internal/cmdbus/cmdbus.go:690-712, deps/agent-command/result.go:64-71); rejected never carries an ID (cmdbus.go:706-712). Confirms U7b's F10 requirement end-to-end withTestCommittedWithAuditFailureKeepsOperationID.
Unit-by-unit review (spec lens, with trigger-condition evidence)
| Unit / Finding | Trigger in plan | Blocking change in diff (file:line) | Verdict |
|---|---|---|---|
| U1 / F01 | reasoning Targets with ../, absolute, or escaping symlink wrote files before delivery validation |
Authorization pass before any write: pathInWriteSet + tools.Resolve per target, then a second write loop (adapters/native/backend.go:336-378); zero-write asserted by TestBackendReasoningProfileRejectsPathEscape (traversal/absolute/symlink, safe.md also absent) |
blocked |
| U1 / F03 | ExecConfig env nil → host env inherited | normalizeExecEnv nil→empty whitelist at registration and per-process (runtime/tools/exec.go:42-51, 165); tests nil/explicit-empty/whitelist |
blocked |
| U1 / F15a | CombinedOutput unbounded before 64KiB trim | Continuous drain into fixed head+tail ring (headTailBuffer, exec.go:192-276; runProcess 157-175); bounded-memory + cancel-drain tests |
blocked |
| U2 / F02 | Same WorkDir reused across SessionScopes; Writable set on workspace | Scope-keyed transcript transcriptPathForScope (contract_bind.go:72-77); fail-closed identity binding incl. refusal to adopt identity-less stores (backend.go:645-669); Writable enforced on write/edit/multi_edit (runtime/tools/registry.go:356-388, builtin.go:106-109/155-158/339-342) with exec/MCP/host preflight rejection (backend.go:213-227); empty scope keeps legacy path (compat verified) |
blocked |
| U2 / F04 | Torn JSONL tail then append → mid-file corruption | repairTailLocked final-line-only repair, valid fragment gets newline, invalid truncated, mid-file untouched (runtime/session/tree.go:156-234); torn/missing-newline/mid-file/append-then-load tests |
blocked |
| U2 / F15b | CapText declared but Text empty | Contract captures final assistant text and surfaces it; pure-text success no longer forces implicit file delivery (withAllowEmptyTargets, contract.go:62-66, 88-96); TestContractPureTextSuccessCarriesText |
blocked |
| U3a | canceled treated as retryable; side-effect honesty | Result gains Canceled/Unknown/SideEffects with EffectiveSideEffects (exec/backend.go:79-105); loop propagates ToolCalls and Canceled, clears Retryable on cancel (runtime/loop.go:202-213, 343-361); adapter maps definite-preflight → none, tool dispatch → possible, run-started transport failure → Unknown+possible (backend.go:334-341, 381-409, sideEffectsForTools 593-599) |
blocked |
| U3b / F05 | success=patches-only; failed-with-patches promoted; file-presence completion on recovery; new ExecutionID per retry | (nil,nil)/nil-result stable failure (workflow.go:429-439); promotion path removed — patches materialize only after explicit Success, or as diagnostics without flipping status (workflow.go:444-461); canceled/aborted never retried (workflow.go:440-452, port.go:121-123); recovery trusts only step_terminals.json records, otherwise needs_reconciliation (engine.go:1070-1180, models.go:299-314); RunWaitingReconciliation resume inspects without advancing (engine.go:1199-1206); ExecutionID reused across attempts and recovery (workflow.go:402-409, engine.go:1130-1133; TestRetryReusesExecutionIDAndDeadline) |
blocked |
| U4 / F06 | config update reuses stale executor cache | Cache key adds ConfigIdentity(role) + factory generation (squad/backend.go:171-175, config_identity.go:14-33 incl. BashPermission); Register/RegisterInstance invalidate per-backend cache (backend.go:62-75); concurrency gates persist across generations and update max on resolve (backend.go:182-285); allow→deny / factory swap / gate-cap tests |
blocked |
| U5 / F07(intent) | caller mutates registry input or returned nested objects | Deep clone at Register/compile/Snapshot/Options/Catalog boundaries (intent/fold.go:57-129, registry.go:488, 520-528, 554, 588-592, catalog.go:36-60,142); isolation asserted in u5 tests | blocked |
| U5 / F08 | generated fallback bypasses slot validation / loses risk metadata; valid alias rejected | Generated Top built from authoritative compiledSpec with full slot validation, missing/invalid projection, risk/confirm/action metadata (recognizer.go:384-437, slots.go:174-214); classifier scores NaN/Inf/out-of-range rejected and quantized before threshold (recognizer.go:176-201, 322-330); non-finite/low-confidence keep ambiguous | blocked |
| U6 / F09 | InvalidSlots dropped; router only sees Start; text-only params | Gate projects Missing and Invalid with reasons + risk/confirm/action (intentgate/gate.go:42-52, 198-232); router blocks dispatch unless Complete() and structured Clarification (route/route.go:60-72, 173-180); unsupported cancel/set_slot honestly fall back to turn without dispatching same-sentence Start (route/route.go:83-90, 121-131); params mapped per declared schema with type casts and required-param admission (server/intent_admission.go:41-113); high-risk still confirmed by cmdbus (TestHighRiskCommandStillRequiresConfirmation) |
blocked |
| U7a / F07(command) | ParamsSchema/ExposeAsTool shared references | Deep copy in/out of Register/Resolve/List (registry.go:37-47, 62-66, 117, 124-130); mutation conformance test | blocked |
| U7b / F10 | any handler error → "no side effects rejected"; audit failure drops op ID | ErrNotExecuted/NotExecuted() marker; unmarked handler errors → Unknown with trusted op ID (cmdbus.go:75-118, 536-553); audit-failure path preserves ID for committed/accepted/unknown and never for rejected (cmdbus.go:687-712); confirm-card unknown lands warn terminal with reconciliation note (cmdbus.go:615-640); owner/project-scoped bounded operation query + endpoint (cmdbus.go:263-296, server/operation_query.go) |
blocked |
| U10 / F13 | squad resolves model/base/key from default account/project | Factory resolves entire route from the run's effective snapshot + profile via the same ResolveChatModel/OpenRouteClient rules as direct path (app/squad_native.go:57-84, runtime/run_config.go:17-62, squad_runner.go:83-86, llm.go:255/604); fallback to default only when host lacks the Settings port; owner-crossing secret tests (TestSquadNativeOptionsNeverBorrowsAnotherOwnerSecret), route-contract EOF parity test |
blocked |
Regression lens
- Empty SessionScope: legacy fixed transcript path, no identity checks,
Targetsdefaulting preserved (backend.go:144-146, contract_bind.go:72-74). Verified byTestLegacyTranscriptStillLoadsand empty-scope branches in regression tests. - Zero-value results:
ExecResult{}zero value = Success false, SideEffects empty → read as "possible" (port.go:110-117) → stable failure, no panic (TestNilNilResultIsStableFailure,TestSideEffectsZeroMeansLegacy). - Default configs: U10 keeps default-account fallback only for hosts without the run Settings port (squad_native.go:60-66);
routeContractForlogic untouched, only re-exported (llm.go:604). - Retry semantics change is intentional and covered (Note N3): transport failures from executors that do not declare
SideEffects=nonenow fail fast instead of text-heuristic retry — exactly the F05/KTD4 remedy. - Route fallback for legal matched intents:
unconsumedCommandKindsonly fires when cancel/set_slot/chitchat/handoff commands are present; the rule funnel emits those only on expectation-hit/ambiguous branches, and matched+complete emits Start alone (intent/command.go:88-113), so normal command dispatch is unaffected (TestValidSlotsReachHandlerMappedPerSchema).
Scope lens
All component diffs map to declared units: native (U1/U2/U3a), squad (U3b/U4), intent (U5, generator.go comment-only), command (U7a), backend (U6/U7b/U10 + mechanical OpenRouteClient rename and nested gitlinks). Dev-level changes are the task card, the plan document, and the five gitlinks. Out-of-unit but documented: squad AGENT-EXECUTION.md adapter disablement and command AGENT-EXECUTION.md addition (Notes N1/N2 — execution-infra config, explicitly recorded on the task card; no product code outside units). No undeclared product behavior changes found.
Test lens
New tests assert the original trigger conditions, not just happy paths: F01 asserts zero writes for all three escape classes; F02 asserts no shared transcript, resume-with-matching-scope, and refusal to adopt identity-less stores; F04 covers torn tail, missing newline, mid-file error, repair-then-append; F05 covers patches-only success, failed-with-full-patches stays failed, canceled/aborted not retried, side-effect-gated retry bounds, all four recovery quadrants (record/files × present/missing), ExecutionID+deadline reuse; F06 covers tighten/loosen/factory-swap/tool-change/cache-reuse/parallel-first/in-flight-cap; F07 mutation attempts on both registries; F08/F09 missing vs invalid vs unknown-key vs required-param blocks with reasons; F10 unknown-with-ID, not-executed-stays-rejected, audit-failure-keeps-ID, repeated-confirm-once, cross-owner query scope; F13 owner-secret isolation, project layer, direct-route parity, default fallback.
Verification runs (this review worktree)
Focused tests with GOWORK=off, per-directory, foreground:
- deps/agent-command:
go test ./...— ok (2 packages, includes new registry/result/conformance tests). - deps/agent-native:
go test ./runtime/session/ ./runtime/tools/— ok (torn-JSONL, env whitelist, bounded buffer, writable allowlist suites).adapters/native— NOT_RUN here: nested deps (error-code-system etc.) are uninitialized in this review worktree and cannot be materialized offline; covered by the recorded green combination run. - deps/agent-intent, deps/agent-squad, modules/backend: NOT_RUN here for the same missing-nested-deps reason (backend imports all components). Covered by the host-recorded combination
make testPASS on the batch candidate (verify-combo-make-test4.json, exit=0), which this review did not re-run per packet instruction.
Verdict
REVIEW VERDICT: APPROVE
Blocking reasons: none. The diff implements the declared batch-1 units with trigger-condition regression coverage; the six minor findings (M1-M6) are quality follow-ups that do not block merge: none of them breaks a legal existing path, all have safe defaults (fail-closed), and the intentional behavior changes (retry tightening, cancel/no-retry semantics) are the specified F05 remedies with test coverage. Recommend filing M1-M5 as follow-up increments and restoring the temporarily disabled agent-execution adapters (N1) per the file's own instruction.