Skip to content

draft: C04 bounded child results and authenticated recovery - #1168

Draft
sethkarten wants to merge 9 commits into
perf/c03-durable-terminal-deliveryfrom
perf/c04-bounded-child-results
Draft

draft: C04 bounded child results and authenticated recovery#1168
sethkarten wants to merge 9 commits into
perf/c03-durable-terminal-deliveryfrom
perf/c04-bounded-child-results

Conversation

@sethkarten

@sethkarten sethkarten commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

C04 bounded child results — validation/security review draft

Draft only: this is not merge-ready. Validation and security review continue through the 1 / 100 / 500 gates.

Scope

  • Adds the bounded producer sink and opaque, owner-local child-result/artifact references.
  • Adds authenticated, parent-bound recovery reservations with PID-incarnation checks and fail-closed recovery.
  • Preserves C03 durable-terminal authority; C04 owns only its bounded result/artifact layer and does not overwrite C03.
  • Current focused tests cover producer buffering/chunking, bounded artifacts, ownership fencing, authenticated recovery, tamper fail-closed behavior, and PID-recycle handling.

Safety and rollback

  • C04 remains exclusively responsible for its child-result artifact namespace and can be rolled back by reverting this PR without changing C03 durable-terminal delivery authority.
  • No client-side limiter, shared semaphore, admission queue, or synthetic local rate limiting is introduced.

Exact integration

  • Base: perf/c03-durable-terminal-delivery at d2776b2d62e87e2ba01675aa1d613f32f8ebd51d (PR C03: durable terminal delivery #1166 current head)
  • Head: perf/c04-bounded-child-results at 02a5f939f11697de1e0163be3fda461dcf7a9392
  • Rebased exactly the seven C04-only commits from old base 42afc919f0e43ded880aa8a2d7088f31b264da95 onto the C03 head. No rebase conflicts occurred.
  • The rebased range contains only C04-owned result/artifact sink changes and their focused tests; C03 durable-terminal authority and bot fixes are retained from the new base.

Validation

  • git diff --check passed.
  • Focused Biome check passed for all six C04 source/test files.
  • npx tsgo --noEmit passed.
  • Focused Vitest passed: 18 tests across c04-producer-sink, rlm-child-results, and daemon-c03-real-production.

Note

Add C04 bounded child result storage with authenticated recovery and artifact streaming

  • Introduces a new rlm-child-results module implementing immutable, owner-bound terminal child result storage with MAC-authenticated reservation journals, artifact streaming, quota enforcement, and capability-based reads.
  • Adds createOrGetTerminalChildResult to atomically publish or idempotently retrieve a terminal child result, with strict ownership validation, inode/digest TOCTOU guards, and reconciliation of abandoned reservations from dead processes.
  • Adds C04ProducerSink in agent-session.ts, a bounded 128 KiB async iterable sink that captures text deltas during child agent runs and feeds the artifact writer; overflow rejects the consumer iterator.
  • Integrates the new storage into AgentSession subagent spawning: on completion/cancellation under a fenced assignment, the parent now receives a canonical C04 result projection instead of a plain completed_without_reply notice.
  • Adds getC04ParentRecoveryAuthority to SessionManager to materialize a 32-byte recovery key under the session artifact directory using atomic link(2) and strict 0600 permissions.
  • Risk: behavioral change — child completions under a fenced assignment now deliver a structured C04 result message to the parent rather than the previous unstructured notice; the fallback path is only used when fenced delivery is not applicable.
📊 Macroscope summarized 2482983. 3 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

Comment thread packages/coding-agent/src/core/rlm-child-results.ts Outdated
Comment thread packages/coding-agent/src/core/agent-session.ts
Comment thread packages/coding-agent/src/core/session-manager.ts
Comment thread packages/coding-agent/src/core/agent-session.ts Outdated
Comment thread packages/coding-agent/src/core/rlm-child-results.ts Outdated
Comment thread packages/coding-agent/src/core/rlm-child-results.ts
Comment on lines +1335 to +1336
safeText(value.summary, MAX_SUMMARY_CHARS, MAX_SUMMARY_BYTES, "summary");
safeText(value.preview, MAX_PREVIEW_CHARS, MAX_PREVIEW_BYTES, "preview");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium core/rlm-child-results.ts:1335

assertReference calls safeText on value.summary, value.preview, and value.error.message but discards the returned sanitized strings, so the original unsanitized text stays on the object. As a result, a projection containing control characters, absolute paths, or API secrets passes validation and gets serialized and persisted by canonicalJson, defeating the redaction that safeText is supposed to enforce. Consider assigning the sanitized results back: value.summary = safeText(...), value.preview = safeText(...), and value.error.message = safeText(...).

-	safeText(value.summary, MAX_SUMMARY_CHARS, MAX_SUMMARY_BYTES, "summary");
-	safeText(value.preview, MAX_PREVIEW_CHARS, MAX_PREVIEW_BYTES, "preview");
+	value.summary = safeText(value.summary, MAX_SUMMARY_CHARS, MAX_SUMMARY_BYTES, "summary");
+	value.preview = safeText(value.preview, MAX_PREVIEW_CHARS, MAX_PREVIEW_BYTES, "preview");
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/core/rlm-child-results.ts around lines 1335-1336:

`assertReference` calls `safeText` on `value.summary`, `value.preview`, and `value.error.message` but discards the returned sanitized strings, so the original unsanitized text stays on the object. As a result, a projection containing control characters, absolute paths, or API secrets passes validation and gets serialized and persisted by `canonicalJson`, defeating the redaction that `safeText` is supposed to enforce. Consider assigning the sanitized results back: `value.summary = safeText(...)`, `value.preview = safeText(...)`, and `value.error.message = safeText(...)`.

Comment thread packages/coding-agent/src/core/rlm-child-results.ts
Comment thread packages/coding-agent/src/core/rlm-child-results.ts Outdated
Comment thread packages/coding-agent/src/core/rlm-child-results.ts
Comment thread packages/coding-agent/src/core/rlm-child-results.ts Outdated
Comment thread packages/coding-agent/src/core/agent-session.ts Outdated
Comment thread packages/coding-agent/src/core/rlm-child-results.ts Outdated
@sethkarten
sethkarten force-pushed the perf/c04-bounded-child-results branch from 588eb41 to 02a5f93 Compare August 10, 2026 19:55
Comment thread packages/coding-agent/src/core/rlm-child-results.ts Outdated
Comment thread packages/coding-agent/src/core/rlm-child-results.ts Outdated
Comment thread packages/coding-agent/src/core/rlm-child-results.ts Outdated
Comment thread packages/coding-agent/src/core/rlm-child-results.ts
Comment on lines +1270 to +1272
const { root } = parse(absolute);
let current = root;
for (const part of relative(root, absolute).split(/[/\\]/).filter(Boolean)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High core/rlm-child-results.ts:1270

canonicalDirectoryNoSymlinks resolves the requested path with realpathSync before walking its ancestors, so the ancestor walk only inspects the canonical (symlink-free) path. When the original requested path contains a symlink component (e.g. /state-link/session-artifacts/id where /state-link is a symlink), the function returns absolute without ever rejecting that symlink. This defeats the stated no-symlink binding check and lets a redirected path pass the C04 filesystem safety boundary that should reject it. Walk the ancestors of the requested path (or check each requested ancestor with lstatSync before resolving) so symlink components in the original path are detected.

  const absolute = realpathSync(requested);
  const { root } = parse(absolute);
  let current = root;
+ const requestedParts = relative(root, requested).split(/[/\\]/).filter(Boolean);
+ let requestedCurrent = root;
+ for (const part of requestedParts) {
+ 	requestedCurrent = join(requestedCurrent, part);
+ 	const rst = lstatSync(requestedCurrent);
+ 	if (!rst.isDirectory() || rst.isSymbolicLink()) throw new Error("C04 rejects symlink/non-directory ancestor");
+ }
  for (const part of relative(root, absolute).split(/[/\\]/).filter(Boolean)) {
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/coding-agent/src/core/rlm-child-results.ts around lines 1270-1272:

`canonicalDirectoryNoSymlinks` resolves the requested path with `realpathSync` before walking its ancestors, so the ancestor walk only inspects the canonical (symlink-free) path. When the original requested path contains a symlink component (e.g. `/state-link/session-artifacts/id` where `/state-link` is a symlink), the function returns `absolute` without ever rejecting that symlink. This defeats the stated no-symlink binding check and lets a redirected path pass the C04 filesystem safety boundary that should reject it. Walk the ancestors of the *requested* path (or check each requested ancestor with `lstatSync` before resolving) so symlink components in the original path are detected.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant