iop/agent-task/m-iop-owned-single-request-agent-execution/11+10_workspace_command/PLAN-cloud-G09.md

14 KiB

Bounded Workspace Command Executor

For the Implementing Agent

Do not start until packet 10 has complete.log. Use only operator-owned exact command templates, implement within the listed boundary, run every verification command, and fill CODE_REVIEW-cloud-G10.md. Do not introduce shell/PTY/general argv execution or own review finalization.

Background

Packet 10 deliberately returns typed unsupported for command. This packet activates only exact operator-configured command ids in the opened admitted workspace cwd, with environment allowlisting, shared output bounds, deadline, process-group cancellation, and race-safe result ownership.

Archive Evidence Snapshot

  • The first-pass pair is preserved at agent-task/m-iop-owned-single-request-agent-execution/11+10_workspace_command/plan_cloud_G08_0.log and agent-task/m-iop-owned-single-request-agent-execution/11+10_workspace_command/code_review_cloud_G10_0.log; it contains no implementation evidence or review verdict.
  • Self-review found that assigning cmd.Dir to the configured path re-resolves that path at process start and can leave the admitted workspace after a rename/replacement. Plan 1 requires an internal child-launch shim to fchdir packet 10's opened root descriptor before executing the fixed template and fails before target start when identity cannot be preserved.

Analysis

Files Read

  • AGENTS.md
  • agent-ops/rules/project/rules.md
  • agent-ops/rules/common/rules-roadmap.md
  • agent-ops/rules/common/rules-agent-spec.md
  • agent-ops/rules/project/domain/node/rules.md
  • agent-ops/rules/project/domain/platform-common/rules.md
  • agent-test/local/rules.md
  • agent-test/local/node-smoke.md
  • agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/iop-owned-single-request-agent-execution.md
  • agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-owned-single-request-agent-execution/SDD.md
  • apps/node/internal/node/node.go
  • apps/node/internal/node/cancel_handler.go
  • apps/node/internal/node/run_handler.go
  • apps/node/internal/transport/session.go
  • packages/go/config/edge_types.go
  • agent-contract/inner/edge-node-runtime-wire.md
  • agent-contract/inner/execution-runtime.md
  • agent-spec/runtime/edge-node-execution.md

SDD Criteria

  • S05 requires command success/failure/timeout/large output plus process cancel and consistent typed results.
  • D06 excludes interactive terminal, shell, desktop, scheduler, and long-lived agent processes.
  • Cwd must be the opened admitted root identity rather than a later pathname lookup; process group, output cap, timeout, and environment allowlist are mandatory and fail closed.

Verification Context

  • Current source has no Node subprocess path; the only exec.Command use is host setup, so there is no compatible executor to extend.
  • Target is Mac, while CI host is Linux. Unix process-group implementation must be build-tagged for Darwin/Linux and tested on Linux; an unsupported fallback keeps other builds explicit.
  • Tests use the Go test binary as an exact configured executable, not /bin/sh.
  • Packet 10 retains an opened directory handle for the admitted root. Each command duplicates and inherits that handle into a short-lived internal launch shim, which verifies the directory identity, calls fchdir, and replaces itself with the fixed target executable. It never resolves the configured root string again.

State and Concurrency Findings

  • One tool call owns one process group and one terminal result; timeout, explicit cancel, context cancel, exit, and output overflow race through a single completion path.
  • Output writers must share one total cap and continue draining after truncation so child pipes cannot deadlock.
  • Cancel addresses only (request_id, tool_call_id) and cannot kill another request's process.

Test Coverage Gaps

  • No exact-template command lookup, minimal environment builder, process group owner, capped writer, or cancel race exists.

Symbol References

  • Packet 07 defines command templates; packet 09 defines command/cancel messages; packet 10 owns the runtime and Node handler.
  • Do not reuse provider runManager, OnCancel, exec.CommandContext's single-process kill, or any caller shell codec.

Split Judgment

  • Command/process correctness is one indivisible slice: start, output drain, timeout/cancel group kill, and wait/result ownership must be reviewed together.
  • Cleanup of request artifacts and all processes remains packet 13, which builds on this per-tool primitive.

Scope Rationale

  • Include exact command id execution, cwd/env/bounds, Unix group lifecycle, typed cancel/result, tests, contract/spec.
  • Exclude arbitrary argv/shell, PTY, network sandbox claims, cleanup orchestration, provider loop, and public output.

Final Routing

  • evaluation_mode=isolated-reassessment; review_rework_count=0; evidence_integrity_failure=false; build closures true, scores 2/2/2/1/2 = G09.
  • Finalizer route grade-boundary, lane cloud, filename PLAN-cloud-G09.md; risks temporal_state, concurrent_consistency, boundary_contract, variant_product (4).
  • Review scores 2/2/2/2/2 = G10; official filename CODE_REVIEW-cloud-G10.md; no recovery/capability gap.

Dependencies and Execution Order

  1. Require packet 10 completion.
  2. Implement capped output and process-group lifecycle before runtime dispatch.
  3. Wire command and cancel through the existing workspace handler, then update docs.

Implementation Checklist

  • Resolve only operator-defined command ids to absolute executable/fixed args, enter the opened admitted root with an internal fchdir/exec shim, and build a minimal allowlisted environment.
  • Own Unix process groups with one terminal result across exit, timeout, context cancel, explicit cancel, and shared stdout/stderr truncation races.
  • Integrate command/cancel into the workspace runtime and Node handler without touching provider cancellation or permitting shell/PTY/arbitrary argv.
  • Prove success/nonzero/timeout/cancel/group-child/output/env/cross-request behavior plus root rename/replacement resistance, and synchronize command contract/spec limits.
  • Run dependency, focused race, package, vet, cross-build, documentation, and whitespace verification.
  • Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

Implementation Plan

[API-1] Implement exact-template process execution

Problem

  • Packet 10's planned file_executor.go returns unsupported for command; no process owner or bounded writer exists.
  • apps/node/internal/node/cancel_handler.go:12 cancels provider runs and must not be overloaded with workspace process identity.

Solution

No command executor exists. Add:

type commandExecution struct {
	done chan struct{}
	cancelOnce sync.Once
	// process/result state guarded by one owner
}

func (r *Runtime) executeCommand(ctx context.Context, request Request, input CommandInput) Result
func (r *Runtime) Cancel(requestID, toolCallID string) CancelResult

Resolve command_id to the immutable config template and run its absolute executable plus fixed args only. Reject request argv, unknown ids, disabled capability, unapproved env names/invalid values, and timeout/output bounds. Duplicate packet 10's opened admitted directory handle into a fixed inherited fd and launch only the current trusted Node/test executable in an internal shim mode. Transfer a bounded, versioned launch record over inherited pipes; it is assembled solely from the immutable command template and validated environment, never caller argv. In the Unix shim, fstat the inherited directory fd against the admitted device/inode, call fchdir, close control fds, and unix.Exec the configured absolute executable/fixed args with the explicit minimal target environment. The exec replacement preserves the shim's process-group identity. Report a closed pre-exec error code to the parent if record validation, identity, fchdir, or exec fails; do not start the target on those paths. Never use cmd.Dir, a descriptor pathname, a shell, or a re-opened configured root. Route stdout/stderr through one concurrency-safe total byte budget, retain separate bounded streams, mark truncation, and continue discarding overflow.

Modified Files and Checklist

  • apps/node/internal/workspace/command_executor.go — lookup, minimal env, start/wait/result arbitration, shared capped output.
  • apps/node/internal/workspace/command_process_unix.go — Darwin/Linux inherited-fd launch record, fstat/fchdir/exec, process group creation, and group signal/kill.
  • apps/node/internal/workspace/command_process_other.go — explicit unsupported fallback for non-Unix builds.
  • apps/node/internal/workspace/command_executor_test.go — helper-process success, exit, descriptor cwd/env, root rename/replacement, timeout, cancel, child group, output, and request isolation.
  • apps/node/cmd/node/main.go — enter the internal workspace launch shim before Cobra parsing; normal CLI behavior remains unchanged.
  • apps/node/cmd/node/main_test.go — prove absent/malformed shim control cannot execute a target and normal commands remain compatible.

Test Strategy

  • Use os.Executable() plus -test.run=TestWorkspaceCommandHelperProcess as the exact configured target. TestMain enters the same internal shim mode used by the Node binary, and the helper emits stdout/stderr, spawns a child, blocks, exits non-zero, and records cwd/env as directed. Open a workspace, rename its configured root and replace the old path with a foreign directory/symlink before command start, then assert the target either runs in the originally admitted directory identity or never starts; it must never enter the replacement. Corrupt the inherited record/fd identity and assert a closed pre-exec failure with no target sentinel.

Verification

  • go test -race ./apps/node/internal/workspace -run 'Test(CommandExecutor|WorkspaceCommandHelperProcess)' -count=1
  • Expected: one typed outcome wins every race, descendants die, retained output never exceeds the shared cap, and path replacement cannot redirect cwd.

[API-2] Activate typed command and cancel handling

Problem

  • Packet 10 leaves WorkspaceToolOperation_COMMAND and WorkspaceCancelRequest unsupported at the Node handler boundary.

Solution

Before (packet 10 contract):

case iop.WORKSPACE_TOOL_OPERATION_COMMAND:
	return unsupportedResult(req)

After:

case iop.WORKSPACE_TOOL_OPERATION_COMMAND:
	return n.workspaceRuntime.Execute(ctx, decodeCommand(req))

Decode only command id/env/timeout/output cap; validate identity before dispatch. Implement OnWorkspaceCancel through the workspace runtime, make duplicate cancel idempotent, and return typed not-found without touching another execution. Keep cleanup unsupported.

Modified Files and Checklist

  • apps/node/internal/workspace/runtime.go — track active commands by exact request/tool identity and expose race-safe cancel.
  • apps/node/internal/node/workspace_handler.go — decode command and map cancel/result status.
  • apps/node/internal/node/workspace_handler_test.go — command/cancel mapping, duplicate/not-found, and raw-free errors.
  • agent-contract/inner/edge-node-runtime-wire.md — define exact-template trust boundary, stable descriptor cwd/env/output/process semantics, and exclusions.
  • agent-spec/runtime/edge-node-execution.md — mark command/cancel implemented with named tests.

Test Strategy

  • Extend direct Node handler tests and assert raw sentinels never appear in logged/typed error text.

Verification

  • go test -race ./apps/node/internal/node -run 'TestNodeWorkspace(Command|Cancel)' -count=1
  • rg --sort path -n 'command id|fixed args|process group|environment allowlist|stdout|stderr|PTY|shell' agent-contract/inner/edge-node-runtime-wire.md agent-spec/runtime/edge-node-execution.md
  • Expected: typed command/cancel is active and the fixed-template/non-interactive boundary is explicit.

Modified Files Summary

File Item
apps/node/internal/workspace/command_executor.go API-1
apps/node/internal/workspace/command_process_unix.go API-1
apps/node/internal/workspace/command_process_other.go API-1
apps/node/internal/workspace/command_executor_test.go API-1
apps/node/cmd/node/main.go API-1
apps/node/cmd/node/main_test.go API-1
apps/node/internal/workspace/runtime.go API-2
apps/node/internal/node/workspace_handler.go API-2
apps/node/internal/node/workspace_handler_test.go API-2
agent-contract/inner/edge-node-runtime-wire.md API-2
agent-spec/runtime/edge-node-execution.md API-2
agent-task/m-iop-owned-single-request-agent-execution/11+10_workspace_command/CODE_REVIEW-cloud-G10.md API-1, API-2

Final Verification

  1. test -f agent-task/m-iop-owned-single-request-agent-execution/10+09_workspace_files/complete.log || test "$(compgen -G 'agent-task/archive/*/*/m-iop-owned-single-request-agent-execution/10+09_workspace_files/complete.log' | wc -l)" -eq 1
  2. go test -race ./apps/node/internal/workspace -run 'Test(CommandExecutor|WorkspaceCommandHelperProcess)' -count=1
  3. go test -race ./apps/node/internal/node -run 'TestNodeWorkspace(Command|Cancel)' -count=1
  4. go test ./apps/node/internal/workspace ./apps/node/internal/node ./apps/node/internal/transport ./apps/node/cmd/node -count=1
  5. go vet ./apps/node/internal/workspace ./apps/node/internal/node ./apps/node/cmd/node
  6. GOOS=darwin GOARCH=arm64 go test -c -o /tmp/iop-workspace-command-darwin.test ./apps/node/internal/workspace
  7. rg --sort path -n 'command id|fixed args|fchdir|exec|cwd|process group|environment allowlist|stdout|stderr|PTY|shell' agent-contract/inner/edge-node-runtime-wire.md agent-spec/runtime/edge-node-execution.md
  8. git diff --check

Expected: packet 10 is uniquely complete; process and Node mapping tests pass under race; root rename/replacement cannot redirect cwd; Darwin compilation succeeds; exact-template boundaries are documented. Cached tests are not acceptable.

After completing all code changes, fill implementation-owned sections in CODE_REVIEW-*-G??.md.