Skip to content

feat(runtime): implement durable Docker state mutation - #8658

Open
ericksoa wants to merge 20 commits into
mainfrom
feat/docker-state-mutation-consumer
Open

feat(runtime): implement durable Docker state mutation#8658
ericksoa wants to merge 20 commits into
mainfrom
feat/docker-state-mutation-consumer

Conversation

@ericksoa

@ericksoa ericksoa commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the B4-C2 slice of #7744 by replacing Docker's unsupported stateMutation surface with a durable, provider-fenced implementation. Hermes Shields is the named managed-image consumer, with fencing retained across publication, validation, rollback, activation, and controller restart; Podman remains unregistered.

Related Issue

Refs #7744

Changes

  • Extends the runtime-provider stateMutation contract to plan schema v2 and registers a real Docker implementation. Hermes Shields requires a canonical AgentDefinition protection projection, exact serialized plan digest, phase-bearing fence, rollback posture, activation proof, and explicit release. A direct filesystem mutation cannot prove that it applies to the current sandbox lifecycle, runtime, mount namespace, state root, or configuration projection. This behavior is covered by state-mutation.test.ts, runtime-provider-contract.test.ts, and docker-state-mutation.test.ts.

  • Adds durable persisted-engine lifecycle claims, exact request/receipt validation, Docker operation authority, and exclusion of ordinary privileged container execution for the full SSH or OpenShell/fallback operation lifetime. Process-local locking or a check immediately before spawning is insufficient because provider publication, subprocess execution, fallback, cleanup, and controller recovery can cross process boundaries. This behavior is covered by persisted-engine-lifecycle.test.ts, docker-operation-authority.test.ts, command-transport.test.ts, privileged-exec.test.ts, and privileged-exec-exclusion.test.ts.

  • Packages fixed, root-owned mutation control, startup-gate, and Hermes publisher helpers plus an exact capability descriptor in the managed image. The image-side controller validates the durable request, publishes or rolls back the protection posture, records activation evidence, and gates gateway startup and recovery. Host-only mutation is insufficient because publication and restart recovery must be enforced inside the image that owns the state mount. This behavior is covered by runtime-state-mutation-control.test.ts, runtime-state-mutation-startup-gate.test.ts, runtime-state-mutation-hermes-publisher.test.ts, hermes-final-image-layout.test.ts, and hermes-gateway-supervisor-recovery.test.ts.

  • Wires Hermes Shields as the named production consumer, including retained-fence recovery, rollback publication, activation proof handling, timer process identity, and transition-lock coordination. Only a current managed Hermes Docker image with the exact root-owned capability uses the provider protocol; images without it retain the existing compatibility path. A global switch is insufficient while older images remain valid deployments. This behavior is covered by hermes-runtime-state-mutation.test.ts, flow.test.ts, legacy-hermes-compat.test.ts, openclaw-transition.test.ts, policy-transition.test.ts, timer-bound-lock.test.ts, and timer-process.test.ts.

  • Hardens Docker operation authority by binding the qualified executable, interpreter chain, credential and SSH helpers, endpoint, PATH semantics, and privileged-execution lease through every capture and spawn. The installer now preserves already-present user-local OpenShell and npm PATH entries so persisted authority survives status and cleanup without weakening real helper or endpoint drift checks.

  • Restricts qualification-only Docker loopback publication to a freshly queried live Docker Engine >= 28.3.3 authority. Registry and model-server publications each consume a separate single-use authority; ordinary managed llama.cpp onboarding retains the private bridge, disables contradictory inherited image healthchecks, and performs no Docker port publication.

  • Documents provider selection, durable fencing and phases, restart hold and recovery, rollback, ledger state, host files, troubleshooting, and the runtime trust boundary.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Quality Gates

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • Docs updated for user-facing behavior changes
  • Docs not applicable — justification:
  • Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging)
  • Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: Independent Codex Desktop security review at exact head 6a88a83 found no actionable findings after checking durable mutation fencing, npm PATH authority stability, provider-owned health/readiness semantics, latest-main CA hardening, and qualification-only single-use Docker publication authority; 80 focused tests plus CLI typecheck, shell syntax, and diff checks passed.
  • Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue:

Documentation Writer Review

  • Documentation writer subagent reviewed the completed changes
  • Result: docs-updated
  • Evidence: docs/inference/set-up-llama-cpp.mdx; docs/manage-sandboxes/runtime-controls.mdx; docs/reference/commands.mdx; docs/reference/host-files-and-state.mdx; docs/reference/troubleshooting.mdx; docs/security/tcb-boundary.mdx
  • Agent: Codex Desktop

DGX Station Hardware Evidence

  • Tested on DGX Station
  • Tested commit:
  • Station profile/scenario:
  • Result:
  • Supporting evidence:

Verification

  • PR description includes a Signed-off-by: line and every commit appears as Verified in GitHub
  • Normal pre-commit, commit-msg, and pre-push hooks passed, or npm run validate:pr passed after refreshing origin/main when hooks were skipped or unavailable
  • Targeted behavior tests pass for the current change set, or tests are marked not applicable above — command/result or justification:
    • Final exact-head B4-C2, Spark Express, installer, qualification, recovery, and latest-main matrix: 12 files, 255 passed, 1 intentionally skipped. Earlier full affected matrix: 20 files, 391 passed, 1 intentionally skipped.
    • Docker authority, health, and publication regressions: 80 of 80 passed in the final focused rerun; forged/reused publication tokens, npm PATH idempotence, inherited healthcheck suppression, and create-boundary propagation are covered.
    • Independent exact-head code review passed with 177 tests; independent security review passed with 80 tests plus CLI typecheck, shell syntax, and diff checks.
    • npm run typecheck:cli and npm run typecheck passed.
    • npm run validate:pr passed at exact head 6a88a83 after refreshing origin/main and preserving the concurrent remote branch update.
  • Applicable broad gate passed — npm test for broad runtime/test-harness changes; npm run check for repo-wide validation/coverage changes — command/result: Exact-head canonical PR CI and protected E2E are pending. An attempted unbounded local npm test saturated the host and was terminated after widespread unrelated timeouts; the bounded affected matrix, isolated timeout rerun, and repository gates pass.
  • Quality Gates section completed with required justifications or waivers
  • No secrets, API keys, or credentials committed
  • npm run docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Signed-off-by: Aaron Erickson aerickson@nvidia.com

Summary by CodeRabbit

  • New Features
    • Added secure runtime state transitions for managed Docker sandboxes, including rollback, recovery, and durable status tracking.
    • Added startup gating, authenticated retries, checkpoint publication, and fail-closed validation.
    • Added stronger timer authorization and safer Shields recovery.
    • Privileged sandbox operations now use execution leases to prevent conflicting changes.
    • Added safer Docker loopback publishing and managed llama.cpp runtime qualification.
  • Bug Fixes
    • Improved handling of Docker, configuration, executable, and runtime drift.
  • Documentation
    • Added guidance for recovery, lifecycle state, troubleshooting, and compatibility.

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
@ericksoa ericksoa self-assigned this Aug 9, 2026
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Hermes runtime state mutation support across Python helpers, Docker provider lifecycle APIs, persisted fencing, startup checkpoints, Shields recovery, timer authorization, and privileged execution leases. It also adds Docker authority qualification, loopback publication controls, integration coverage, and final-image metadata validation.

Changes

Hermes runtime mutation and containment

Layer / File(s) Summary
Contracts and durable lifecycle
src/lib/onboard/runtime-provider/contract.ts, src/lib/onboard/runtime-provider/state-mutation.ts, src/lib/onboard/runtime-provider/persisted-engine-lifecycle.ts, src/lib/onboard/runtime-provider/registry.ts
Version 2 plans support protection transitions, rollback postures, state-lock plans, activation proofs, durable fences, target claims, release receipts, and restart-safe lifecycle operations.
Docker authority and provider implementation
src/lib/onboard/runtime-provider/docker-operation-authority.ts, src/lib/onboard/runtime-provider/docker-state-mutation.ts, src/lib/onboard/runtime-provider/docker-llama-cpp-operation.ts, src/lib/onboard/runtime-provider/docker.ts
Docker commands use qualified authorities. Docker exposes runtime state mutation with inspection, fencing, publication, activation, rollback, release, and recovery.
Hermes startup and publisher controls
scripts/runtime-state-mutation-startup-gate.py, scripts/runtime_state_mutation_hermes_publisher.py, agents/hermes/start.sh, agents/hermes/Dockerfile
Hermes validates startup admission, retries, checkpoints, and release flows. The publisher applies or rolls back validated plans with durable journals and receipts.
Shields recovery and timer authority
src/lib/shields/index.ts, src/lib/shields/hermes-runtime-state-mutation.ts, src/lib/shields/timer-control.ts, src/lib/shields/timer.ts
Shields coordinates Hermes transitions, durable forward policies, timer authorization proofs, process identity checks, provider recovery, and rollback.
Privileged execution and Docker loopback controls
src/lib/sandbox/privileged-exec.ts, src/lib/adapters/sandbox/command-transport.ts, src/lib/actions/sandbox/process-recovery.ts, src/lib/inference/llama-cpp/host-local-runtime.ts, scripts/checks/run-llama-cpp-dgx-spark-qualification.mts
Privileged operations use serialized leases. Docker loopback publication requires a qualified, single-use authority. Qualification and onboarding tests cover the updated Docker behavior.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Shields
  participant PersistedLifecycle
  participant DockerStateMutation
  participant HermesPublisher
  participant HermesStartupGate
  Shields->>PersistedLifecycle: Prepare and claim runtime target
  Shields->>DockerStateMutation: Acquire and establish fence
  DockerStateMutation->>HermesPublisher: Publish target posture
  HermesPublisher->>HermesStartupGate: Coordinate startup checkpoint
  HermesStartupGate-->>HermesPublisher: Return checkpoint or retry receipt
  HermesPublisher-->>DockerStateMutation: Return activation receipt
  DockerStateMutation-->>Shields: Return activation proof and release receipt
Loading

Possibly related issues

Possibly related PRs

  • NVIDIA/NemoClaw#8058 — Provides the runtime-provider contracts and persisted lifecycle APIs extended here.
  • NVIDIA/NemoClaw#8130 — Shares Shields recovery, timer-authority, and transition-lock paths extended here.
  • NVIDIA/NemoClaw#8480 — Shares sandbox command transport paths updated with privileged execution leases.

Suggested labels: feature, area: architecture, area: security, platform: container, integration: hermes

Suggested reviewers: cv, apurvvkumaria

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: a durable Docker state-mutation implementation.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/docker-state-mutation-consumer

Comment @coderabbitai help to get the list of available commands.

@github-code-quality

github-code-quality Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage in commit 64a3a53 in the feat/docker-state-mu... branch remains at 96%, unchanged from commit 04e28aa in the main branch.


Updated August 10, 2026 07:02 UTC

return result


def _canonical(value: object) -> bytes:
Comment thread scripts/runtime-state-mutation-startup-gate.py Fixed
return parent, start


def _parse_uids(raw: bytes) -> tuple[int, int, int, int]:
raise PublisherError(code)


def _canonical(value: object) -> bytes:
return result


def _parse_json(raw: bytes, maximum: int, code: str) -> object:
os.close(fd)
try:
os.unlink(temporary, dir_fd=directory_fd)
except FileNotFoundError:
_atomic_write(directory_fd, JOURNAL_NAME, journal)


def _acquire_lock(directory_fd: int) -> int:
_fail("publisher-guard-untrusted")


def _run_guard(action: str, arguments: list[str]) -> str:
Comment on lines +964 to +969
def _continue_forward(
directory_fd: int,
journal: dict[str, object],
operation: dict[str, object],
normalized: dict[str, object],
) -> dict[str, object]:
Comment on lines +1004 to +1009
def _continue_abort(
directory_fd: int,
journal: dict[str, object],
operation: dict[str, object],
normalized: dict[str, object],
) -> dict[str, object]:
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — Informational

Advisor assessment: Informational / low confidence
Next action: No advisor follow-up needed.
Findings: 0 blockers · 0 warnings · 0 suggestions
Status: PR review advisor failed: PR review advisor SDK execution failed: session: scope-risk-map-analysis omitted required analysis; turn: scope-risk-map-analysis: scope-risk-map-analysis omitted required analysis

Model lanes

  • GPT-5.6 Terra (primary): Failed
  • Nemotron 3 Ultra (second opinion): Failed

Second-opinion terminology and E2E selections are advisory. Live E2E does not run automatically for pull requests.

E2E guidance

Advisory only. A maintainer can dispatch the default E2E suite against this exact revision.

Recommended E2E: cloud-inference, cloud-onboard, full-e2e, hermes-e2e, hermes-inference-switch, managed-image-multiarch-startup, managed-image-protected-runtime, security-posture, bedrock-runtime-compatible-anthropic, channels-stop-start, dashboard-remote-bind, hermes-shields-config, inference-routing, network-policy, onboard-repair, onboard-resume, rebuild-openclaw, state-backup-restore

Workflow run details

This automated review informs maintainers. Warnings and suggestions do not require a response. A maintainer decides whether to merge.

Comment thread src/lib/sandbox/privileged-exec-exclusion.test.ts Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🧹 Nitpick comments (25)
scripts/runtime_state_mutation_hermes_publisher.py (1)

844-907: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Replace the locals() test with an explicit descriptor variable.

Line 906 uses "config_fd" in locals() to decide whether to close the descriptor. That works today, but it couples cleanup to a name lookup rather than to a value. A rename of config_fd inside the try block leaks the descriptor with no diagnostic.

Initialize the descriptor before the try block and test the value.

♻️ Proposed change
 def _verify_state_posture(posture: str, plan_json: str) -> None:
     guard = _load_module(
         STATE_DIR_GUARD_PATH, "_nemoclaw_runtime_state_mutation_state_dir_guard"
     )
+    config_fd = -1
     try:
         plan = guard.parse_agent_state_lock_plan(plan_json)
         identity = guard._production_identity()
         config_fd = guard._open_absolute_dir_nofollow(HERMES_DIR)
@@
     except Exception:
         _fail("publisher-state-posture-invalid")
     finally:
-        if "config_fd" in locals():
-            os.close(config_fd)
+        if config_fd >= 0:
+            os.close(config_fd)
🤖 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 `@scripts/runtime_state_mutation_hermes_publisher.py` around lines 844 - 907,
Update _verify_state_posture to initialize the configuration descriptor variable
before entering the try block, then close it in finally only when its value
indicates a successfully opened descriptor. Remove the "config_fd" in locals()
check while preserving cleanup on all paths.
src/lib/onboard/runtime-provider/state-mutation.test.ts (2)

521-528: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

This case depends on an implicit check-ordering inside the planner.

The override sets readOnlyRoots: ["scripts", "workspace-main"], but the fixture selectors contain no { kind: "path", path: "workspace-main" }. Two checks can reject this input: the root-and-prefix overlap check inside normalizeStateLockPlan, and the selector-scope check in requireStateLockPlanScope. The test asserts the overlap message, so it passes only because normalizeStateLockPlan runs first.

Add the matching path selector to the override. The case then isolates the overlap rule and stays correct if the planner reorders its checks.

♻️ Proposed change
     [
       "overlapping root and prefix policies",
       {
         readOnlyRoots: ["scripts", "workspace-main"],
         readOnlyPrefixes: ["workspace-"],
       },
       /root and prefix policies must not overlap/u,
     ],

Then extend the it.each body to add the selector for the overlapping root, or move this case into its own it that passes selectors: [...base.selectors, { kind: "path", path: "workspace-main" }].

🤖 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 `@src/lib/onboard/runtime-provider/state-mutation.test.ts` around lines 521 -
528, Update the “overlapping root and prefix policies” case in the relevant
it.each fixture to include { kind: "path", path: "workspace-main" } in its
selectors, while preserving the existing readOnlyRoots, readOnlyPrefixes, and
expected overlap error. Ensure the test still exercises the overlap validation
in normalizeStateLockPlan rather than failing selector-scope validation in
requireStateLockPlanScope.

332-463: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use field-specific diagnostics or document the limitation.

requireExactKeys reports only ${label} fields are unsupported, so these assertions prove rejection but not the rejection reason. A defect that rejects an input for a different unsupported field can pass. Add the offending key to the error, or document that these tests provide rejection-only coverage.

🤖 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 `@src/lib/onboard/runtime-provider/state-mutation.test.ts` around lines 332 -
463, Update the rejection cases in the parameterized test around
prepareRuntimeProviderStateMutationPlan to assert field-specific diagnostics
from requireExactKeys, including the offending key in each expected error where
applicable. Ensure each malformed input cannot pass because of an unrelated
unsupported-field rejection; if field-specific diagnostics are not available,
document that these assertions intentionally provide rejection-only coverage.

Source: Path instructions

scripts/runtime-state-mutation-startup-gate.py (3)

485-498: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

raw is None is unreachable here.

_read_at is called without missing=True, so a missing candidate raises gate-file-missing instead of returning None. The raw is None test at line 497 never runs. That changes the refusal code an operator sees for a missing candidate from release-candidate-mismatch to gate-file-missing.

If release-candidate-mismatch is the intended code, pass missing=True. Otherwise drop the dead test.

♻️ Proposed change
         raw = _read_at(
             directory_fd,
             CANDIDATE_NAME,
             uid=os.geteuid(),
             gid=os.getegid(),
             mode=0o600,
+            missing=True,
         )
🤖 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 `@scripts/runtime-state-mutation-startup-gate.py` around lines 485 - 498,
Update _verify_release_candidate’s _read_at call to pass missing=True if missing
candidates must produce the existing release-candidate-mismatch refusal code;
otherwise remove the unreachable raw is None check and preserve the
gate-file-missing behavior.

605-638: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

directory_fd leaks when _active_exists refuses.

_active_directory() returns an open descriptor at line 606. If _active_exists raises GateError("active-gate-invalid"), that descriptor is never closed, because the try/finally starts at line 612. The process exits right after main(), so this does not leak in production. It does leak when the module is exercised in-process by a test or by the Dockerfile import probe.

Move the _active_exists call inside the try block.

🤖 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 `@scripts/runtime-state-mutation-startup-gate.py` around lines 605 - 638, Move
the _active_exists check in _run inside the existing try/finally block so any
GateError or early inactive return still closes directory_fd. Preserve the
current handling for None descriptors and the existing action flow; ensure the
finally does not attempt to close a None descriptor.

336-401: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The canonical round-trip check depends on PERMIT_KEYS, RELEASE_KEYS, and RETRY_KEYS field order.

Line 399 compares raw against _canonical(normalized) + b"\n". normalized is built in a fixed order: schemaVersion, protocol, transactionId, nonce, the protocol binding fields, start, candidateDirectory. That order currently matches each key tuple, so valid receipts pass. A future reorder of a key tuple, or of the producer's field order, silently rejects every receipt as gate-receipt-invalid and holds startup.

Add a short comment that binds the two orders, or derive the emitted order from the key tuple.

🤖 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 `@scripts/runtime-state-mutation-startup-gate.py` around lines 336 - 401,
Update _binding so the canonical round-trip validation remains explicitly tied
to the ordering defined by PERMIT_KEYS, RELEASE_KEYS, and RETRY_KEYS. Prefer
deriving normalized field emission from the selected keys, or add a concise
comment documenting that normalized’s construction order must match each
protocol key tuple; preserve the existing receipt validation behavior.
src/lib/actions/sandbox/snapshot-restore-test-fixture.ts (1)

276-277: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Two hand-maintained copies of the timer-control mock surface. The production timer-control module gained isProcessAlive and readProcessStartIdentity, and both test entry points now mirror that surface by hand. A future export must be added to both files, and a missed site fails only when that path is exercised.

  • src/lib/actions/sandbox/snapshot-restore-test-fixture.ts#L276-L277: export a single shared timer-control mock factory from this fixture, and restore both new implementations in resetSnapshotRestoreMocks.
  • src/lib/actions/sandbox/snapshot-auto-create-failure.test.ts#L100-L104: consume that shared factory in the vi.mock call instead of restating the three functions.
🤖 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 `@src/lib/actions/sandbox/snapshot-restore-test-fixture.ts` around lines 276 -
277, The timer-control mock surface is duplicated across two test entry points.
In src/lib/actions/sandbox/snapshot-restore-test-fixture.ts#L276-L277, export
one shared timer-control mock factory, include both isProcessAlive and
readProcessStartIdentity in resetSnapshotRestoreMocks, and in
src/lib/actions/sandbox/snapshot-auto-create-failure.test.ts#L100-L104 use that
factory from the vi.mock call instead of restating the three mocked functions.
src/lib/adapters/sandbox/command-transport.test.ts (1)

75-77: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

vi.resetAllMocks() is redundant with the project-level isolation.

The cli Vitest project imports test/helpers/vitest-state-isolation.ts and enables clearMocks, restoreMocks, unstubEnvs, and unstubGlobals. Every dependency mock in this file is also rebuilt per test by createDependencies(), and the hoisted mocks receive a fresh mockImplementation in each test that uses them. This hook adds no isolation.

Remove it, or keep it only if a specific hoisted mock must lose its implementation between tests.

Based on the learning that Vitest files under src run in the cli project with clearMocks, restoreMocks, unstubEnvs, and unstubGlobals already enabled, so suite hooks should only clean up resources Vitest does not manage.

🤖 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 `@src/lib/adapters/sandbox/command-transport.test.ts` around lines 75 - 77,
Remove the redundant beforeEach hook calling vi.resetAllMocks() from this test
suite. Rely on the cli Vitest project’s configured isolation and the per-test
createDependencies() and mockImplementation setup; retain a suite hook only if a
specific hoisted mock requires cleanup beyond Vitest’s managed resets.

Source: Learnings

src/lib/onboard/runtime-provider/docker-operation-authority.test.ts (1)

29-32: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop the redundant vi.unstubAllEnvs() call.

The cli Vitest project enables unstubEnvs, so Vitest restores stubbed environment variables between tests. Keep only the temporary-directory cleanup in this hook.

Based on learnings: Vitest test files under src are executed by the cli Vitest project, which enables clearMocks, restoreMocks, unstubEnvs, and unstubGlobals; suite-level teardown should only clean resources Vitest does not manage.

🤖 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 `@src/lib/onboard/runtime-provider/docker-operation-authority.test.ts` around
lines 29 - 32, Remove the redundant vi.unstubAllEnvs() call from the afterEach
hook in the docker-operation authority tests, leaving only the
temporary-directory cleanup for roots.

Source: Learnings

src/lib/onboard/runtime-provider/state-mutation.ts (1)

416-434: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse selectorIdentity instead of repeating its expression.

selectorIdentity is defined at Line 211. The same path: / prefix: expression is written again at Lines 421-423 and Lines 483-484. Three copies can drift, and the identity string is the deduplication and scope-check key.

♻️ Proposed refactor
   const identities = new Set(
-    selectors.map((selector) =>
-      selector.kind === "path" ? `path:${selector.path}` : `prefix:${selector.prefix}`,
-    ),
+    selectors.map(selectorIdentity),
   );
   for (const selector of selectors) {
-    const identity =
-      selector.kind === "path" ? `path:${selector.path}` : `prefix:${selector.prefix}`;
-    uniqueSelectors.set(identity, selector);
+    uniqueSelectors.set(selectorIdentity(selector), selector);
   }

Also applies to: 481-486

🤖 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 `@src/lib/onboard/runtime-provider/state-mutation.ts` around lines 416 - 434,
Update requireStateLockPlanScope and the other affected scope-check logic to
reuse the existing selectorIdentity helper instead of rebuilding path:/prefix:
identity strings inline. Replace both selector and required-root/prefix identity
construction with selectorIdentity-compatible inputs, preserving the current
deduplication and exact-selector validation behavior.
src/lib/onboard/runtime-provider/docker-operation-authority.ts (1)

425-432: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Confirm the per-command context re-inspection cost is acceptable.

The context-branch guard calls resolvedDockerContextEndpoint on every guarded command. That runs an extra docker context inspect child process before each capture and each spawn. Lifecycle flows that issue many commands therefore double their Docker subprocess count. The DOCKER_HOST branch has no equivalent cost because its guard only re-verifies the executable.

If the re-check must stay on every command, consider a short time-bounded cache of the qualified endpoint so a burst of commands re-inspects once.

🤖 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 `@src/lib/onboard/runtime-provider/docker-operation-authority.ts` around lines
425 - 432, Reduce the per-command Docker context re-inspection overhead in the
guard around executable.guard and resolvedDockerContextEndpoint. Add a
short-lived, time-bounded cache for the qualified context endpoint so bursts of
guarded commands reuse one recent inspection while still detecting endpoint
changes promptly; preserve the existing retry error when a fresh validation
differs from qualifiedEndpoint.
src/lib/onboard/runtime-provider/docker-llama-cpp-operation.ts (1)

68-73: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Apply the same argument validation on the injected spawner path.

authority.spawn validates arguments with fixedDockerSpawnArguments and pins cwd, env, and shell: false. The spawnCommand branch only prefixes the endpoint arguments. An injected spawner therefore receives unvalidated arguments and no fixed environment. The parameter is reachable from the exported createDockerLlamaCppHostLocalOperation, so the two paths should enforce the same bound.

Consider exporting a validation helper from docker-operation-authority.ts and calling it before spawnCommand, or restricting spawnCommand to tests only.

🤖 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 `@src/lib/onboard/runtime-provider/docker-llama-cpp-operation.ts` around lines
68 - 73, Update the injected spawner branch in the operation’s spawn method to
apply the same fixedDockerSpawnArguments validation and pinned cwd, env, and
shell:false settings used by authority.spawn. Reuse or export the existing
validation helper from docker-operation-authority.ts, then pass its normalized
arguments and options to spawnCommand while preserving assertAuthority and
endpoint argument prefixing.
src/lib/shields/flow.test.ts (1)

31-49: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse the shared timer authorization proof helper.

test/helpers/hermes-shields-provider-consumer-harness.ts already exports writeTimerAuthorizationProof, and src/lib/shields/legacy-hermes-compat.test.ts imports it. This local copy builds the identical proof payload. Two copies of the same security fixture will drift when the proof schema changes, and one suite will then silently stop proving authorization.

Import the exported helper and pass requireDist as its loadSource argument, or move the shared implementation to a helper both suites import.

🤖 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 `@src/lib/shields/flow.test.ts` around lines 31 - 49, Remove the local
writeTimerAuthorizationProof implementation and import the shared helper from
test/helpers/hermes-shields-provider-consumer-harness.ts. Update its call site
to pass requireDist as the loadSource argument, preserving the existing
authorization-proof setup behavior.
src/lib/shields/legacy-hermes-compat.test.ts (2)

655-656: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the bare block around the new describe.

The describe("Hermes Shields runtime-provider consumer", …) block is wrapped in a bare block statement { … }. The block creates a lexical scope that nothing in the file uses, and its indentation suggests the suite is nested inside the preceding suite when it is not.

♻️ Proposed change
-{
-  describe("Hermes Shields runtime-provider consumer", () => {
+describe("Hermes Shields runtime-provider consumer", () => {
-  });
-}
+});

Re-indent the enclosed body by two spaces.

Also applies to: 1454-1455

🤖 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 `@src/lib/shields/legacy-hermes-compat.test.ts` around lines 655 - 656, Remove
the unnecessary bare block surrounding the `describe("Hermes Shields
runtime-provider consumer", …)` suite and the corresponding occurrence near the
later test section. Re-indent each enclosed test body by two spaces while
leaving the `describe` suites and test behavior unchanged.

1002-1091: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Split the timer-replacement case out of the forward-policy parameterization.

This it.each covers two unrelated failures. missing and tampered assert that nothing runs. timer-replaced asserts the opposite: runSpy and transitionSpy are called, routeSpy runs once, and the transition file is removed. The arrange step also branches at Line 1059, and the expected error at Line 1076 is an alternation of two different messages, so the test passes for either failure text.

The title claims the recovered forward policy is invalid, which does not describe the timer-replacement case. Split timer-replaced into its own it with a single exact error expectation, and keep the it.each for missing and tampered with their shared assertions.

As per path instructions: "Flag copied production algorithms, broad mocks that bypass the behavior under test, and conditionals that make a test pass without exercising its claim."

🤖 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 `@src/lib/shields/legacy-hermes-compat.test.ts` around lines 1002 - 1091, Split
the timer-replaced scenario out of the parameterized test around the failureMode
setup. Keep the it.each limited to missing and tampered, with shared assertions
that no recovery actions run and an exact forward-policy error expectation; add
a dedicated test for timer replacement with its specific arrangement, exact
auto-restore-authority/timer-generation error expectation, and assertions for
runSpy, transitionSpy, routeSpy, and transition cleanup. Remove the conditional
setup and alternated expectation from the shared test.

Source: Path instructions

src/lib/sandbox/agent-config.ts (1)

67-72: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Align the error text with the condition it detects.

agent is loaded from sandbox.agent on the preceding line, so this branch does not detect a concurrent change. It detects a manifest whose name field differs from the requested agent name. The current message will send an operator looking for a race.

♻️ Proposed wording change
   const agent = resolveCurrentAgentDefinition(sandbox.agent);
   if (agent.name !== sandbox.agent) {
     throw new Error(
-      `Sandbox '${sandboxName}' agent-definition authority changed during resolution`,
+      `Sandbox '${sandboxName}' resolved agent manifest '${agent.name}' does not match its registered agent '${sandbox.agent}'`,
     );
   }
🤖 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 `@src/lib/sandbox/agent-config.ts` around lines 67 - 72, Update the error
message in the resolveCurrentAgentDefinition validation to describe a manifest
name mismatch: report that the resolved agent definition’s name differs from the
requested sandbox.agent value, rather than attributing it to an authority change
during resolution.
src/lib/sandbox/privileged-exec.test.ts (1)

105-117: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse the real directory constant in the mock.

PERSISTED_ENGINE_LIFECYCLE_DIRECTORY is duplicated as a string literal here, and the tests at Line 241 and Line 270 create the directory with the same literal. If the real constant changes, assertNoActiveStateMutationTarget will look at a different path, the lstatSync guard will return early, and these tests will pass without exercising the gate.

Import the constant from ../onboard/runtime-provider/persisted-engine-lifecycle before the module is replaced in the require cache, and reuse it in both the mock and the directory setup.

🤖 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 `@src/lib/sandbox/privileged-exec.test.ts` around lines 105 - 117, Update the
privileged-exec test setup to import PERSISTED_ENGINE_LIFECYCLE_DIRECTORY from
../onboard/runtime-provider/persisted-engine-lifecycle before replacing that
module in requireCache, then reuse the imported constant in the mocked exports
and both directory-creation setups instead of duplicated string literals.
src/lib/shields/index.ts (2)

2781-2796: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Provider posture repair discards the first attempt's error in both directions. Both the unlock and the lock provider branches run a same-posture transition, verify the live posture, and on failure retry with the opposite rollback. Each catch block discards the original error, so the reason the live posture disagreed with the state-derived authority is never reported.

  • src/lib/shields/index.ts#L2781-L2796: record the caught verification error with console.error before running the mutable/locked repair transition.
  • src/lib/shields/index.ts#L3229-L3239: record the caught error from verifyProviderLockedPosture with console.error before running the locked/mutable repair transition.
🤖 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 `@src/lib/shields/index.ts` around lines 2781 - 2796, The caught verification
errors are discarded before posture repair transitions. In
src/lib/shields/index.ts lines 2781-2796, update the catch around
verifyHermesProviderMutablePosture to pass the caught error to console.error
before runHermesProviderProtectionTransition performs the mutable/locked repair;
likewise, in lines 3229-3239, log the caught verifyProviderLockedPosture error
with console.error before the locked/mutable repair transition.

4819-4825: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the unreachable timerAuthority guards.

FreshShieldsDownTimerStart declares transition and timerAuthority as required, and startFreshShieldsDownTimer either returns both or throws. After the destructuring at Line 4819 both values are always defined.

The guards at Line 4823, Line 4846, Line 4948, Line 4981, Line 5014, and Line 5016 therefore never take their false branch. The throw at Line 5017 ("Fresh Shields down lost its timer authorization proof") is unreachable. The dead branches make the control flow in this function harder to audit than the invariant it enforces.

Drop the conditionals and call assertFreshShieldsDownAuthority directly.

♻️ Proposed simplification
-  let { transition } = timerStart;
-  const { timerAuthority, policyPathForApply } = timerStart;
+  let transition: ShieldsDownTransition = timerStart.transition;
+  const { timerAuthority, policyPathForApply } = timerStart;
 
   try {
-    if (transition && timerAuthority) {
-      assertFreshShieldsDownAuthority(sandboxName, timerAuthority, transition, "preparing");
-    }
+    assertFreshShieldsDownAuthority(sandboxName, timerAuthority, transition, "preparing");
-      if (!timerAuthority) {
-        throw new Error("Fresh Shields down lost its timer authorization proof");
-      }
       assertFreshShieldsDownAuthority(sandboxName, timerAuthority, transition, "preparing");

Based on learnings: "avoid adding 'defensive' error handling (e.g., try/catch wrappers, fallbacks, or extra validation) around internal helper logic when there is no realistic throwing path or failure mode."

Also applies to: 5016-5019

🤖 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 `@src/lib/shields/index.ts` around lines 4819 - 4825, Remove the unreachable
timerAuthority and transition guards in the Fresh Shields down timer flow. In
the function containing the destructuring of timerStart, call
assertFreshShieldsDownAuthority directly at the guarded sites, including the
paths around lines 4846, 4948, 4981, 5014, and 5016, and delete the unreachable
“lost its timer authorization proof” throw while preserving the existing
authorization arguments and control flow.

Source: Learnings

src/lib/onboard/runtime-provider/persisted-engine-lifecycle.ts (1)

696-746: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Consider limiting the repair scan to publication paths.

readPrivateFile now calls repairInterruptedExclusivePublication on every read. That adds one readdirSync plus two lstatSync calls per candidate entry for each private-file read. loadTransaction reads several phase files, the intent, the release receipt, and lease files, and listUnfinishedTransactions repeats that for every transaction directory and every claim. The directories are small today, so this is not a correctness problem, but the cost grows with the number of unfinished transactions.

Consider running the repair once per directory per store operation, or only from the paths that can observe a post-link crash window (loadPhase, claim loading, and receipt loading), instead of from the generic read helper.

🤖 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 `@src/lib/onboard/runtime-provider/persisted-engine-lifecycle.ts` around lines
696 - 746, Limit repairInterruptedExclusivePublication to publication-sensitive
loading paths instead of invoking it from the generic readPrivateFile helper.
Remove the unconditional repair call from readPrivateFile, then invoke repair
once per directory or only within loadPhase, claim-loading, and receipt-loading
flows, ensuring each store operation preserves interrupted-publication recovery
without rescanning for every private-file read.
test/helpers/shields-flow-harness.ts (2)

236-247: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Both harnesses copy the production timer-authorization proof serialization. src/lib/shields/timer-control.ts exports writeTimerAuthorizationProofForMarker, but that helper requires marker.pid === process.pid, so neither harness can call it for a simulated timer. Each harness therefore re-implements the proof payload by hand. The shared root cause is the missing reusable payload builder in timer-control.ts. If a field is added to TimerAuthorizationProof, hasExactTimerAuthorizationProof starts rejecting harness-written proofs and every dependent suite fails for a reason unrelated to the behavior under test.

Export a pure buildTimerAuthorizationProof(marker): TimerAuthorizationProof from src/lib/shields/timer-control.ts, have writeTimerAuthorizationProofForMarker use it, then call it from both harnesses.

  • test/helpers/shields-flow-harness.ts#L236-L247: replace the inline object literal passed to fs.writeFileSync with JSON.stringify(timerControl.buildTimerAuthorizationProof(marker)).
  • test/helpers/hermes-shields-provider-consumer-harness.ts#L120-L131: replace the inline object literal in writeTimerAuthorizationProof with JSON.stringify(timerControl.buildTimerAuthorizationProof(marker)).

As per path instructions, tests must be reviewed for behavioral confidence rather than implementation lock-in, and copied production algorithms must be flagged.

🤖 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 `@test/helpers/shields-flow-harness.ts` around lines 236 - 247, Extract a pure
buildTimerAuthorizationProof(marker): TimerAuthorizationProof helper in
src/lib/shields/timer-control.ts, and make writeTimerAuthorizationProofForMarker
reuse it. In test/helpers/shields-flow-harness.ts lines 236-247 and
test/helpers/hermes-shields-provider-consumer-harness.ts lines 120-131, replace
each duplicated proof object with
JSON.stringify(timerControl.buildTimerAuthorizationProof(marker)); review tests
for behavioral confidence without coupling them to the implementation.

Source: Path instructions


429-457: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document the second-occurrence failure semantics.

Both injection branches fail on the second matching rename, not the first. The ++stateWrites === 2 and ++transitionWrites === 2 conditions encode that rule as a bare literal. A reader cannot tell which write is the first one and which one is the target.

Add a short comment that names the first rename and the targeted rename. That prevents a future change to the write sequence from silently retargeting the injected failure.

♻️ Proposed change
     const originalRenameSync = fs.renameSync.bind(fs);
+    // The first rename to each destination is the initial shields-down write.
+    // The failure is injected on the second rename, which is the policy
+    // rejection rollback that the tests assert against.
     let stateWrites = 0;
     let transitionWrites = 0;
🤖 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 `@test/helpers/shields-flow-harness.ts` around lines 429 - 457, Add concise
comments in the renameSync mock branches for statePath and transitionPrefix
documenting that the first matching rename succeeds and the second matching
rename is the injected failure. Keep the existing ++stateWrites === 2 and
++transitionWrites === 2 behavior unchanged.
src/lib/shields/timer-control.ts (1)

352-360: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Thread the process-inspection deadline into the new start-identity check.

verifyTimerMarkerIdentity calls readProcessStartIdentity(marker.pid) without a deadline. That call starts a fresh inspection budget and can fall back to execFileSync("ps", ...) when /proc is unavailable.

Two consequences appear on the authority path in src/lib/shields/index.ts:

  • isExactLiveFutureTimerAuthority (Lines 221-233) builds one deadline with processInspectionDeadlineAfter(...) and passes it to isProcessAlive and readProcessStartIdentity. It then calls verifyTimerMarkerIdentity(marker), which escapes that budget and can spawn ps a second time.
  • The same call site already compared readProcessStartIdentity(marker.pid, deadline) against marker.timerProcessStartIdentity. The new block repeats that identical comparison.

Add an optional deadline parameter to verifyTimerMarkerIdentity and forward it to both readProcessStartIdentity and readProcessCommandLine. The callers can then bound the total inspection cost.

♻️ Proposed change
-function verifyTimerMarkerIdentity(marker: ShieldsTimerMarker): {
+function verifyTimerMarkerIdentity(
+  marker: ShieldsTimerMarker,
+  deadline = processInspectionDeadline(),
+): {
   verified: boolean;
   warning?: string;
 } {
   if (
     marker.timerProcessStartIdentity !== undefined &&
-    readProcessStartIdentity(marker.pid) !== marker.timerProcessStartIdentity
+    readProcessStartIdentity(marker.pid, deadline) !== marker.timerProcessStartIdentity
   ) {

Run the following script to list every caller and check whether a deadline is already in scope:

#!/bin/bash
set -euo pipefail
rg -nP --type=ts -C4 '\bverifyTimerMarkerIdentity\s*\(' src test
rg -nP --type=ts -C2 '\bprocessInspectionDeadline(After)?\s*\(' src/lib/shields
🤖 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 `@src/lib/shields/timer-control.ts` around lines 352 - 360, Update
verifyTimerMarkerIdentity to accept an optional deadline and pass it to both
readProcessStartIdentity and readProcessCommandLine. Update every caller,
especially isExactLiveFutureTimerAuthority, to forward the existing
process-inspection deadline so all checks share one bounded budget; avoid
repeating the already-performed start-identity comparison when applicable.
test/runtime-state-mutation-hermes-publisher.test.ts (1)

282-289: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Run the Python harness once in beforeAll.

Each of the three tests calls runHarness(), and every call re-runs all three scenario blocks of the harness. The harness is deterministic and produces one result map that contains the keys for all three tests. The sibling suite test/runtime-state-mutation-control.test.ts already uses beforeAll for the same pattern at lines 1193-1200. Hoisting removes two redundant Python runs, each with a 20-second timeout budget.

♻️ Proposed refactor to run the harness once
-function runHarness(): Record<string, unknown> {
+let harnessResult: Record<string, unknown>;
+
+beforeAll(() => {
   const result = spawnSync("python3", ["-I", "-c", HARNESS, PUBLISHER, STATE_PLAN], {
     encoding: "utf8",
     timeout: 20_000,
   });
   expect(result.status, result.stderr).toBe(0);
-  return JSON.parse(result.stdout) as Record<string, unknown>;
-}
+  harnessResult = JSON.parse(result.stdout) as Record<string, unknown>;
+});

Update the import on line 7 to include beforeAll, then replace each const result = runHarness(); with a reference to harnessResult.

Also applies to: 292-293, 320-321, 333-334

🤖 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 `@test/runtime-state-mutation-hermes-publisher.test.ts` around lines 282 - 289,
Run runHarness() once in a beforeAll hook, storing its result in a shared
harnessResult variable, and import beforeAll. Replace each test’s local
runHarness() invocation with harnessResult while preserving the existing
key-specific assertions.
test/runtime-state-mutation-control.test.ts (1)

15-1189: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Consider extracting the embedded Python harness to a fixture file.

HARNESS holds about 1170 lines of Python inside a TypeScript template literal. No Python linter, formatter, or type checker inspects it. Escaping rules also force workarounds such as the dollar variable at line 1064. Moving the harness to a fixture file, for example test/fixtures/runtime-state-mutation-control-harness.py, and passing the path to spawnSync would restore Python tooling and simplify the string handling. The same applies to the smaller harnesses in the sibling runtime-state-mutation suites.

🤖 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 `@test/runtime-state-mutation-control.test.ts` around lines 15 - 1189, Extract
the embedded HARNESS Python template and the smaller harnesses in the sibling
runtime-state-mutation suites into dedicated fixture files under test/fixtures,
preserving their behavior and imports. Update the corresponding test setup and
spawnSync invocations to pass each fixture path instead of constructing Python
source from template literals, and remove string-escaping workarounds such as
the dollar variable.
🤖 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 `@agents/hermes/start.sh`:
- Around line 71-89: The startup loop around
nemoclaw_runtime_state_mutation_gate must distinguish an active-mutation retry
from gate inspection failures. Update the gate result contract and its handling
in main() so retry-wait retains the one-second retry behavior, while
GateError/OSError or other refusal cases return a separate status that logs an
accurate inspection-failure message instead of repeatedly claiming an active
mutation.

In `@scripts/runtime_state_mutation_hermes_publisher.py`:
- Line 71: Add a CI test that loads the Hermes AgentDefinition via
loadAgent("hermes"), derives selectors from its configPaths including
shieldsFiles, and compares the resulting set with the publisher contract
represented by TOP_SELECTORS. Remove independent selector literals from the test
and fail clearly when the sets diverge, including
publisher-plan-selector-mismatch coverage.

In `@src/lib/onboard/runtime-provider/docker-operation-authority.ts`:
- Around line 151-164: Update fixedDockerCommandEnvironment to read PATH
exclusively from the supplied env object, removing the fallback to
process.env.PATH. Preserve the existing missing-PATH error so curated
environments fail closed without mixing ambient and caller-provided values.

In `@src/lib/onboard/runtime-provider/docker-state-mutation.test.ts`:
- Around line 560-563: Remove await from synchronous state-mutation calls
acquire, assertFenced, publish, rollback, activate, release, and recover, and
remove async from the enclosing it callbacks at the referenced tests. Preserve
the existing assertions and test behavior while ensuring these tests directly
exercise the synchronous contract.
- Around line 102-363: Remove conditional statements from all three affected
test files to satisfy the growth guardrails: in
src/lib/onboard/runtime-provider/docker-state-mutation.test.ts lines 102-363,
move harness, acquireMarker, ownerThatStopsAfterPrepare, and
replayDeferredAcquire into a dedicated fixture module and make the afterHelper
behavior a fixture option; in
src/lib/onboard/runtime-provider/docker-operation-authority.test.ts lines 34-48,
replace args.includes("inspect") branches in contextCapture and the inline
capture with precomputed responses or a command-keyed lookup; in
src/lib/shields/hermes-runtime-state-mutation.test.ts lines 296-344, use it.each
data columns for failure-stage overrides and a scripted-result queue helper for
the call-counter behavior at the cited sites.

In `@src/lib/onboard/runtime-provider/docker-state-mutation.ts`:
- Around line 174-184: Remove the VITEST/NEMOCLAW_TEST_STATE_DIR branch from
resolveDockerStateMutationStateDir so production state always resolves through
resolveShieldsStateDir. Update tests, including the environment-branch case in
docker-state-mutation.test.ts, to provide the temporary state directory via
DockerStateMutationSurfaceOptions.resolveStateDir instead.

In `@src/lib/onboard/runtime-provider/persisted-engine-lifecycle.ts`:
- Around line 1882-1904: Update the privileged-exec consumer to import the
persisted-engine mutation gate through the runtime-provider/access module,
matching access.ts’s re-export, instead of binding directly from
persisted-engine-lifecycle.ts. Preserve use of
hasActivePersistedEngineStateMutationTarget while ensuring the Hermes harness
spy can intercept it.

In `@src/lib/sandbox/privileged-exec-exclusion.test.ts`:
- Around line 41-47: Remove all four if statements from the privileged-exec test
file, including the conditionals in waitForExit and the setup/teardown sections.
Preserve the existing behavior by moving conditional handling into a shared
helper under test/helpers, or replace the guards with the documented
unconditional operations where those operations are safe.

In `@src/lib/shields/index.ts`:
- Around line 5422-5429: Update the temporarily_unlocked handling around
assertTimedAuthority so a legacy timed-DOWN state with a valid marker but no
transition artifact is migrated before provider-state-mutation-v2 is enabled, or
accepted through a bounded compatibility path. Preserve strict validation for
missing/invalid markers and non-active transitions, and ensure shields status no
longer reports this legacy state as drifted solely because transition is null.

In `@src/lib/shields/timer.ts`:
- Around line 204-205: Cache the current process start identity once at module
scope or on first use, then update markerRecordMatchesCurrentTimer to compare
against that cached value instead of calling
readProcessStartIdentity(process.pid) on every invocation. Preserve the existing
undefined-identity matching behavior and retain the comparison for markers with
a stored identity.

In `@test/runtime-state-mutation-control.test.ts`:
- Around line 905-906: Update the waitpid check in the guardian writer-stop
assertion to poll until the stop is reported or the established deadline
expires, matching the existing polling pattern in the last-resort block. Keep
using WUNTRACED|WNOHANG and set results["guardian_writer_stopped"] from the
final selected PID and WIFSTOPPED(status) result.

---

Nitpick comments:
In `@scripts/runtime_state_mutation_hermes_publisher.py`:
- Around line 844-907: Update _verify_state_posture to initialize the
configuration descriptor variable before entering the try block, then close it
in finally only when its value indicates a successfully opened descriptor.
Remove the "config_fd" in locals() check while preserving cleanup on all paths.

In `@scripts/runtime-state-mutation-startup-gate.py`:
- Around line 485-498: Update _verify_release_candidate’s _read_at call to pass
missing=True if missing candidates must produce the existing
release-candidate-mismatch refusal code; otherwise remove the unreachable raw is
None check and preserve the gate-file-missing behavior.
- Around line 605-638: Move the _active_exists check in _run inside the existing
try/finally block so any GateError or early inactive return still closes
directory_fd. Preserve the current handling for None descriptors and the
existing action flow; ensure the finally does not attempt to close a None
descriptor.
- Around line 336-401: Update _binding so the canonical round-trip validation
remains explicitly tied to the ordering defined by PERMIT_KEYS, RELEASE_KEYS,
and RETRY_KEYS. Prefer deriving normalized field emission from the selected
keys, or add a concise comment documenting that normalized’s construction order
must match each protocol key tuple; preserve the existing receipt validation
behavior.

In `@src/lib/actions/sandbox/snapshot-restore-test-fixture.ts`:
- Around line 276-277: The timer-control mock surface is duplicated across two
test entry points. In
src/lib/actions/sandbox/snapshot-restore-test-fixture.ts#L276-L277, export one
shared timer-control mock factory, include both isProcessAlive and
readProcessStartIdentity in resetSnapshotRestoreMocks, and in
src/lib/actions/sandbox/snapshot-auto-create-failure.test.ts#L100-L104 use that
factory from the vi.mock call instead of restating the three mocked functions.

In `@src/lib/adapters/sandbox/command-transport.test.ts`:
- Around line 75-77: Remove the redundant beforeEach hook calling
vi.resetAllMocks() from this test suite. Rely on the cli Vitest project’s
configured isolation and the per-test createDependencies() and
mockImplementation setup; retain a suite hook only if a specific hoisted mock
requires cleanup beyond Vitest’s managed resets.

In `@src/lib/onboard/runtime-provider/docker-llama-cpp-operation.ts`:
- Around line 68-73: Update the injected spawner branch in the operation’s spawn
method to apply the same fixedDockerSpawnArguments validation and pinned cwd,
env, and shell:false settings used by authority.spawn. Reuse or export the
existing validation helper from docker-operation-authority.ts, then pass its
normalized arguments and options to spawnCommand while preserving
assertAuthority and endpoint argument prefixing.

In `@src/lib/onboard/runtime-provider/docker-operation-authority.test.ts`:
- Around line 29-32: Remove the redundant vi.unstubAllEnvs() call from the
afterEach hook in the docker-operation authority tests, leaving only the
temporary-directory cleanup for roots.

In `@src/lib/onboard/runtime-provider/docker-operation-authority.ts`:
- Around line 425-432: Reduce the per-command Docker context re-inspection
overhead in the guard around executable.guard and resolvedDockerContextEndpoint.
Add a short-lived, time-bounded cache for the qualified context endpoint so
bursts of guarded commands reuse one recent inspection while still detecting
endpoint changes promptly; preserve the existing retry error when a fresh
validation differs from qualifiedEndpoint.

In `@src/lib/onboard/runtime-provider/persisted-engine-lifecycle.ts`:
- Around line 696-746: Limit repairInterruptedExclusivePublication to
publication-sensitive loading paths instead of invoking it from the generic
readPrivateFile helper. Remove the unconditional repair call from
readPrivateFile, then invoke repair once per directory or only within loadPhase,
claim-loading, and receipt-loading flows, ensuring each store operation
preserves interrupted-publication recovery without rescanning for every
private-file read.

In `@src/lib/onboard/runtime-provider/state-mutation.test.ts`:
- Around line 521-528: Update the “overlapping root and prefix policies” case in
the relevant it.each fixture to include { kind: "path", path: "workspace-main" }
in its selectors, while preserving the existing readOnlyRoots, readOnlyPrefixes,
and expected overlap error. Ensure the test still exercises the overlap
validation in normalizeStateLockPlan rather than failing selector-scope
validation in requireStateLockPlanScope.
- Around line 332-463: Update the rejection cases in the parameterized test
around prepareRuntimeProviderStateMutationPlan to assert field-specific
diagnostics from requireExactKeys, including the offending key in each expected
error where applicable. Ensure each malformed input cannot pass because of an
unrelated unsupported-field rejection; if field-specific diagnostics are not
available, document that these assertions intentionally provide rejection-only
coverage.

In `@src/lib/onboard/runtime-provider/state-mutation.ts`:
- Around line 416-434: Update requireStateLockPlanScope and the other affected
scope-check logic to reuse the existing selectorIdentity helper instead of
rebuilding path:/prefix: identity strings inline. Replace both selector and
required-root/prefix identity construction with selectorIdentity-compatible
inputs, preserving the current deduplication and exact-selector validation
behavior.

In `@src/lib/sandbox/agent-config.ts`:
- Around line 67-72: Update the error message in the
resolveCurrentAgentDefinition validation to describe a manifest name mismatch:
report that the resolved agent definition’s name differs from the requested
sandbox.agent value, rather than attributing it to an authority change during
resolution.

In `@src/lib/sandbox/privileged-exec.test.ts`:
- Around line 105-117: Update the privileged-exec test setup to import
PERSISTED_ENGINE_LIFECYCLE_DIRECTORY from
../onboard/runtime-provider/persisted-engine-lifecycle before replacing that
module in requireCache, then reuse the imported constant in the mocked exports
and both directory-creation setups instead of duplicated string literals.

In `@src/lib/shields/flow.test.ts`:
- Around line 31-49: Remove the local writeTimerAuthorizationProof
implementation and import the shared helper from
test/helpers/hermes-shields-provider-consumer-harness.ts. Update its call site
to pass requireDist as the loadSource argument, preserving the existing
authorization-proof setup behavior.

In `@src/lib/shields/index.ts`:
- Around line 2781-2796: The caught verification errors are discarded before
posture repair transitions. In src/lib/shields/index.ts lines 2781-2796, update
the catch around verifyHermesProviderMutablePosture to pass the caught error to
console.error before runHermesProviderProtectionTransition performs the
mutable/locked repair; likewise, in lines 3229-3239, log the caught
verifyProviderLockedPosture error with console.error before the locked/mutable
repair transition.
- Around line 4819-4825: Remove the unreachable timerAuthority and transition
guards in the Fresh Shields down timer flow. In the function containing the
destructuring of timerStart, call assertFreshShieldsDownAuthority directly at
the guarded sites, including the paths around lines 4846, 4948, 4981, 5014, and
5016, and delete the unreachable “lost its timer authorization proof” throw
while preserving the existing authorization arguments and control flow.

In `@src/lib/shields/legacy-hermes-compat.test.ts`:
- Around line 655-656: Remove the unnecessary bare block surrounding the
`describe("Hermes Shields runtime-provider consumer", …)` suite and the
corresponding occurrence near the later test section. Re-indent each enclosed
test body by two spaces while leaving the `describe` suites and test behavior
unchanged.
- Around line 1002-1091: Split the timer-replaced scenario out of the
parameterized test around the failureMode setup. Keep the it.each limited to
missing and tampered, with shared assertions that no recovery actions run and an
exact forward-policy error expectation; add a dedicated test for timer
replacement with its specific arrangement, exact
auto-restore-authority/timer-generation error expectation, and assertions for
runSpy, transitionSpy, routeSpy, and transition cleanup. Remove the conditional
setup and alternated expectation from the shared test.

In `@src/lib/shields/timer-control.ts`:
- Around line 352-360: Update verifyTimerMarkerIdentity to accept an optional
deadline and pass it to both readProcessStartIdentity and
readProcessCommandLine. Update every caller, especially
isExactLiveFutureTimerAuthority, to forward the existing process-inspection
deadline so all checks share one bounded budget; avoid repeating the
already-performed start-identity comparison when applicable.

In `@test/helpers/shields-flow-harness.ts`:
- Around line 236-247: Extract a pure buildTimerAuthorizationProof(marker):
TimerAuthorizationProof helper in src/lib/shields/timer-control.ts, and make
writeTimerAuthorizationProofForMarker reuse it. In
test/helpers/shields-flow-harness.ts lines 236-247 and
test/helpers/hermes-shields-provider-consumer-harness.ts lines 120-131, replace
each duplicated proof object with
JSON.stringify(timerControl.buildTimerAuthorizationProof(marker)); review tests
for behavioral confidence without coupling them to the implementation.
- Around line 429-457: Add concise comments in the renameSync mock branches for
statePath and transitionPrefix documenting that the first matching rename
succeeds and the second matching rename is the injected failure. Keep the
existing ++stateWrites === 2 and ++transitionWrites === 2 behavior unchanged.

In `@test/runtime-state-mutation-control.test.ts`:
- Around line 15-1189: Extract the embedded HARNESS Python template and the
smaller harnesses in the sibling runtime-state-mutation suites into dedicated
fixture files under test/fixtures, preserving their behavior and imports. Update
the corresponding test setup and spawnSync invocations to pass each fixture path
instead of constructing Python source from template literals, and remove
string-escaping workarounds such as the dollar variable.

In `@test/runtime-state-mutation-hermes-publisher.test.ts`:
- Around line 282-289: Run runHarness() once in a beforeAll hook, storing its
result in a shared harnessResult variable, and import beforeAll. Replace each
test’s local runHarness() invocation with harnessResult while preserving the
existing key-specific assertions.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: aab0811b-7317-4b94-966e-af305a9f4bc2

📥 Commits

Reviewing files that changed from the base of the PR and between 1a8e6bd and 7a3ed92.

📒 Files selected for processing (65)
  • agents/hermes/Dockerfile
  • agents/hermes/runtime-state-mutation-publisher-v1.json
  • agents/hermes/start.sh
  • scripts/runtime-state-mutation-control.py
  • scripts/runtime-state-mutation-startup-gate.py
  • scripts/runtime_state_mutation_hermes_publisher.py
  • src/lib/actions/sandbox/process-recovery-temp-ssh.test.ts
  • src/lib/actions/sandbox/process-recovery.ts
  • src/lib/actions/sandbox/snapshot-auto-create-failure.test.ts
  • src/lib/actions/sandbox/snapshot-restore-test-fixture.ts
  • src/lib/adapters/sandbox/command-transport.test.ts
  • src/lib/adapters/sandbox/command-transport.ts
  • src/lib/onboard/runtime-provider/access.ts
  • src/lib/onboard/runtime-provider/contract.ts
  • src/lib/onboard/runtime-provider/docker-llama-cpp-operation.ts
  • src/lib/onboard/runtime-provider/docker-operation-authority.test.ts
  • src/lib/onboard/runtime-provider/docker-operation-authority.ts
  • src/lib/onboard/runtime-provider/docker-state-mutation.test.ts
  • src/lib/onboard/runtime-provider/docker-state-mutation.ts
  • src/lib/onboard/runtime-provider/docker.ts
  • src/lib/onboard/runtime-provider/persisted-engine-lifecycle.test.ts
  • src/lib/onboard/runtime-provider/persisted-engine-lifecycle.ts
  • src/lib/onboard/runtime-provider/registry.ts
  • src/lib/onboard/runtime-provider/runtime-provider-contract.test.ts
  • src/lib/onboard/runtime-provider/state-mutation.test.ts
  • src/lib/onboard/runtime-provider/state-mutation.ts
  • src/lib/sandbox/agent-config.ts
  • src/lib/sandbox/config.ts
  • src/lib/sandbox/privileged-exec-exclusion.test.ts
  • src/lib/sandbox/privileged-exec.test.ts
  • src/lib/sandbox/privileged-exec.ts
  • src/lib/shields/flow.test.ts
  • src/lib/shields/hermes-runtime-state-mutation.test.ts
  • src/lib/shields/hermes-runtime-state-mutation.ts
  • src/lib/shields/index.test.ts
  • src/lib/shields/index.ts
  • src/lib/shields/legacy-hermes-compat.test.ts
  • src/lib/shields/mutable-config-repair.ts
  • src/lib/shields/openclaw-transition.test.ts
  • src/lib/shields/policy-transition.test.ts
  • src/lib/shields/timer-bound-lock.test.ts
  • src/lib/shields/timer-bound-lock.ts
  • src/lib/shields/timer-control.ts
  • src/lib/shields/timer-process.test.ts
  • src/lib/shields/timer.ts
  • src/lib/shields/transition-lock.ts
  • src/lib/state/mcp-lifecycle-lock/shields-timer-authority.ts
  • test/config-set-nested-ssrf.test.ts
  • test/config-set-prompt-error.test.ts
  • test/helpers/hermes-shields-provider-consumer-harness.ts
  • test/helpers/shields-flow-harness.ts
  • test/hermes-config-transaction-wiring.test.ts
  • test/hermes-final-image-layout.test.ts
  • test/hermes-gateway-supervisor-recovery.test.ts
  • test/hermes-start.test.ts
  • test/package-contract/cli/config-set-prompt-eof.test.ts
  • test/repro-2681-group-writable.test.ts
  • test/runtime-provider-source-shape.test.ts
  • test/runtime-state-mutation-control.test.ts
  • test/runtime-state-mutation-hermes-publisher.test.ts
  • test/runtime-state-mutation-startup-gate.test.ts
  • test/sandbox-provisioning.test.ts
  • test/sandbox-rlimit-hooks.test.ts
  • test/shields-up-runtime-perms.test.ts
  • test/support/hermes-shell-harness.ts

Comment thread agents/hermes/start.sh
Comment on lines +71 to +89
while :; do
if nemoclaw_runtime_state_mutation_gate admit; then
break
else
_nemoclaw_runtime_state_mutation_gate_status=$?
fi
case "$_nemoclaw_runtime_state_mutation_gate_status" in
10) break ;;
75)
printf '%s\n' '[SECURITY] Hermes startup held by an active runtime state mutation.' >&2
/bin/sleep 1 || true
;;
*)
printf '%s\n' '[SECURITY] Runtime state mutation startup gate failed.' >&2
exit 1
;;
esac
done
unset _nemoclaw_runtime_state_mutation_gate_status

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Exit code 75 conflates "retry-wait" with any refusal, so a wedged gate logs a misleading cause forever.

main() in scripts/runtime-state-mutation-startup-gate.py returns 75 for the retry-wait state and also for every GateError and OSError. This loop maps 75 to a one-second retry with the message "held by an active runtime state mutation". A corrupt permit, a bad directory mode, or an unreadable receipt therefore produces an endless 1 Hz log line that names the wrong cause.

Holding is the intended behavior, so keep the loop. Separate the diagnosis instead: return a distinct code for retry-wait, or rate-limit the message and state that the gate refused inspection.

🤖 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 `@agents/hermes/start.sh` around lines 71 - 89, The startup loop around
nemoclaw_runtime_state_mutation_gate must distinguish an active-mutation retry
from gate inspection failures. Update the gate result contract and its handling
in main() so retry-wait retains the one-second retry behavior, while
GateError/OSError or other refusal cases return a separate status that logs an
accurate inspection-failure message instead of repeatedly claiming an active
mutation.

"abort-state-applied",
}
)
TOP_SELECTORS = (".config-hash", ".env", "config.yaml")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check whether an existing test binds the Hermes AgentDefinition selectors to the publisher TOP_SELECTORS tuple.
set -euo pipefail

rg -n -C 5 'TOP_SELECTORS' 
rg -n -C 5 'config-hash' --glob '**/*.test.ts' --glob '**/*.test.py'
fd -i 'hermes.*state.*mutation.*test' --type f

Repository: NVIDIA/NemoClaw

Length of output: 153


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- publisher ---'
sed -n '1,130p' scripts/runtime_state_mutation_hermes_publisher.py

printf '%s\n' '--- planner and related symbols ---'
rg -n -C 8 'prepareAgentDefinitionProtectionTransitionPlan|configPaths|publisher-plan-selector-mismatch|AgentDefinition' src scripts --glob '*.ts' --glob '*.py'

printf '%s\n' '--- candidate tests ---'
git ls-files | rg '(^|/)(.*test.*|.*spec.*)$' | rg 'runtime|state|mutation|hermes|onboard' || true

Repository: NVIDIA/NemoClaw

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- exact selector references ---'
rg -n -C 12 'TOP_SELECTORS|publisher-plan-selector-mismatch|prepareAgentDefinitionProtectionTransitionPlan|shieldsFiles|configFile:|envFile:' \
  scripts/runtime_state_mutation_hermes_publisher.py \
  src/lib/onboard/runtime-provider/state-mutation.ts \
  src/lib/agent/defs.ts \
  2>/dev/null || true

printf '%s\n' '--- relevant tracked files ---'
git ls-files | rg 'runtime-provider|state-mutation|hermes.*publisher|publisher.*hermes|hermes.*test|state.*mutation.*test' || true

printf '%s\n' '--- selector and planner tests ---'
rg -l 'TOP_SELECTORS|publisher-plan-selector-mismatch|prepareAgentDefinitionProtectionTransitionPlan|shieldsFiles' . \
  --glob '*.test.ts' --glob '*.spec.ts' --glob '*.test.py' --glob '*.py' \
  | head -100 || true

Repository: NVIDIA/NemoClaw

Length of output: 30671


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- Hermes publisher tests ---'
rg -n -C 10 'TOP_SELECTORS|config-hash|config.yaml|publisher-plan-selector-mismatch|selectors|projection' \
  test/runtime-state-mutation-hermes-publisher.test.ts

printf '%s\n' '--- state mutation tests ---'
rg -n -C 12 'prepareAgentDefinitionProtectionTransitionPlan|configFile|envFile|shieldsFiles|selectors|Hermes|hermes' \
  src/lib/onboard/runtime-provider/state-mutation.test.ts

printf '%s\n' '--- Hermes definition/config sources ---'
git ls-files agents src | rg 'hermes.*(json|ya?ml|ts)$|agent.*hermes|defs'
rg -n -C 8 '"config_file"|"env_file"|"shields_files"|config_file|env_file|shields_files' \
  agents src/lib/agent --glob '*.json' --glob '*.yaml' --glob '*.yml' --glob '*.ts'

Repository: NVIDIA/NemoClaw

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- Hermes manifest configuration ---'
rg -n -C 6 '^(config:|  dir:|  config_file:|  env_file:|  format:|  shields_files:|state_lock_plan)' \
  agents/hermes/manifest.yaml

printf '%s\n' '--- publisher test setup and assertions ---'
sed -n '1,180p' test/runtime-state-mutation-hermes-publisher.test.ts
sed -n '260,330p' test/runtime-state-mutation-hermes-publisher.test.ts

printf '%s\n' '--- agent loader and manifest test helpers ---'
sed -n '1,130p' src/lib/agent/defs.ts
rg -n -C 8 'loadAgent\\("hermes"|manifest.yaml|configShieldsFiles' src/lib/agent/defs.test.ts src/lib/agent/defs.ts

Repository: NVIDIA/NemoClaw

Length of output: 15451


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import ast
import re
from pathlib import Path

publisher = Path("scripts/runtime_state_mutation_hermes_publisher.py").read_text()
module = ast.parse(publisher)
top = None
for node in module.body:
    if isinstance(node, ast.Assign) and any(
        isinstance(target, ast.Name) and target.id == "TOP_SELECTORS"
        for target in node.targets
    ):
        top = ast.literal_eval(node.value)
        break

manifest = Path("agents/hermes/manifest.yaml").read_text()
config = re.search(
    r"(?ms)^config:\n(?P<body>(?:^[ \t]+.*\n?)*)",
    manifest,
)
if not config:
    raise SystemExit("Hermes config block not found")
body = config.group("body")
config_file = re.search(r"(?m)^[ \t]+config_file:\s*([^\s#]+)", body).group(1)
env_file = re.search(r"(?m)^[ \t]+env_file:\s*([^\s#]+)", body).group(1)
shields = re.findall(r"(?m)^[ \t]+- ([^\s#]+)", body.split("shields_files:", 1)[1].split("auth_file:", 1)[0])
projection = tuple(dict.fromkeys((config_file, ".config-hash", env_file, *shields)))

tests = "\n".join(
    str(path.read_text())
    for path in Path("test").rglob("*.test.ts")
    if "runtime-state-mutation" in path.name
)
print({
    "publisher_TOP_SELECTORS": top,
    "manifest_projection_top_paths": projection,
    "values_match": tuple(top) == projection,
    "publisher_test_references_loadAgent_hermes": 'loadAgent("hermes")' in tests,
    "publisher_test_has_literal_selector_tuple": '(".config-hash", ".env", "config.yaml")' in tests,
})
PY

Repository: NVIDIA/NemoClaw

Length of output: 423


Bind Hermes selector coverage to AgentDefinition

TOP_SELECTORS hard-codes the current paths, while prepareAgentDefinitionProtectionTransitionPlan derives them from configPaths, including shieldsFiles. The current tests use independent literals and do not load loadAgent("hermes"). If configPaths changes, a Shields transition fails with publisher-plan-selector-mismatch at runtime. Add a CI test that compares the derived Hermes selector set with the publisher contract.

🤖 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 `@scripts/runtime_state_mutation_hermes_publisher.py` at line 71, Add a CI test
that loads the Hermes AgentDefinition via loadAgent("hermes"), derives selectors
from its configPaths including shieldsFiles, and compares the resulting set with
the publisher contract represented by TOP_SELECTORS. Remove independent selector
literals from the test and fail clearly when the sets diverge, including
publisher-plan-selector-mismatch coverage.

Comment thread src/lib/onboard/runtime-provider/docker-operation-authority.ts
Comment thread src/lib/onboard/runtime-provider/docker-state-mutation.test.ts Outdated
Comment thread src/lib/onboard/runtime-provider/docker-state-mutation.test.ts
Comment on lines +1882 to 1904
export function hasActivePersistedEngineStateMutationTarget(
lifecycleStore: PersistedEngineLifecycleStore,
sandboxName: string,
runtimeId?: string,
): boolean {
const exactSandboxName = exactName(sandboxName, "sandbox name");
if (runtimeId !== undefined && !RUNTIME_ID.test(runtimeId)) {
fail("runtime identity is malformed");
}
const exactRuntimeId = runtimeId;
return lifecycleStore
.listUnfinished()
.some(
(record) =>
record.action === "state-mutation" &&
record.sandboxName === exactSandboxName &&
record.resources.some(
(resource) =>
resource.role === "target" &&
(exactRuntimeId === undefined || resource.runtimeId === exactRuntimeId),
),
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Resolve every definition and consumer of the active state-mutation target gate.
set -euo pipefail

rg -nP --type=ts -C3 '\bhasActivePersistedEngineStateMutationTarget\b'
fd -t f 'access.ts' src/lib/onboard/runtime-provider --exec sed -n '1,120p'

Repository: NVIDIA/NemoClaw

Length of output: 153


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- matching files ---'
git ls-files | rg '(^|/)(persisted-engine-lifecycle\.ts|access\.ts|privileged-exec\.ts|hermes-shields-provider-consumer-harness\.ts)$' || true

printf '%s\n' '--- symbol references ---'
rg -n -C4 'hasActivePersistedEngineStateMutationTarget|runtime-provider/access|privileged-exec|hermes-shields-provider-consumer-harness' . \
  -g '*.ts' -g '*.js' -g '*.mjs' -g '*.tsx' || true

printf '%s\n' '--- runtime-provider files ---'
fd -t f . | rg 'onboard/runtime-provider' || true

Repository: NVIDIA/NemoClaw

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- access.ts exports and imports ---'
rg -n -C5 'persisted-engine-lifecycle|hasActivePersistedEngineStateMutationTarget|export' \
  src/lib/onboard/runtime-provider/access.ts

printf '%s\n' '--- privileged-exec import and call ---'
sed -n '1,32p;200,228p' src/lib/sandbox/privileged-exec.ts

printf '%s\n' '--- harness loader and spy ---'
sed -n '110,205p' test/helpers/hermes-shields-provider-consumer-harness.ts

printf '%s\n' '--- all non-test direct gate references ---'
rg -n --glob '*.ts' --glob '!**/*.test.ts' \
  'hasActivePersistedEngineStateMutationTarget' src test

Repository: NVIDIA/NemoClaw

Length of output: 9653


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- test harness gate spy usage ---'
rg -n -C6 'lifecycleGateSpy|hasActivePersistedEngineStateMutationTarget' \
  test/helpers/hermes-shields-provider-consumer-harness.ts \
  src/lib/shields/*.test.ts

printf '%s\n' '--- compiler and test module settings ---'
rg -n -C3 '"(type|module|moduleResolution|transform|test|build)"|moduleResolution|module":|commonjs|esbuild|tsx|vitest' \
  package.json tsconfig*.json vite.config.* vitest.config.* 2>/dev/null || true

printf '%s\n' '--- relevant import/export graph ---'
sed -n '1,55p' src/lib/onboard/runtime-provider/access.ts
sed -n '1,30p;240,265p' src/lib/shields/hermes-runtime-state-mutation.ts
sed -n '1,48p;1975,2050p' src/lib/onboard/runtime-provider/docker-state-mutation.ts

Repository: NVIDIA/NemoClaw

Length of output: 41522


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- source loader implementation ---'
rg -n -C8 'function (source|load)|loadSource|sourceLoaderNodeOptions|createRequire|requireDist' \
  test/helpers vitest.config.ts

printf '%s\n' '--- CommonJS output assumptions ---'
sed -n '1,45p' tsconfig.src.json
sed -n '1,120p' test/helpers/source-loader-options.ts 2>/dev/null || true

printf '%s\n' '--- direct imports of the gate ---'
rg -n -C2 'hasActivePersistedEngineStateMutationTarget' \
  src/lib/sandbox/privileged-exec.ts \
  src/lib/shields/hermes-runtime-state-mutation.ts \
  src/lib/onboard/runtime-provider/docker-state-mutation.ts \
  src/lib/onboard/runtime-provider/access.ts

Repository: NVIDIA/NemoClaw

Length of output: 50371


Route the privileged-exec gate through runtime-provider/access.

access.ts re-exports the gate, but privileged-exec.ts imports it directly from persisted-engine-lifecycle.ts. The Hermes harness spy does not replace this direct binding. Use the same module path in both consumers, or spy on the direct module.

🤖 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 `@src/lib/onboard/runtime-provider/persisted-engine-lifecycle.ts` around lines
1882 - 1904, Update the privileged-exec consumer to import the persisted-engine
mutation gate through the runtime-provider/access module, matching access.ts’s
re-export, instead of binding directly from persisted-engine-lifecycle.ts.
Preserve use of hasActivePersistedEngineStateMutationTarget while ensuring the
Hermes harness spy can intercept it.

Comment thread src/lib/sandbox/privileged-exec-exclusion.test.ts Outdated
Comment thread src/lib/shields/index.ts
Comment on lines +5422 to +5429
const marker = mode === "temporarily_unlocked" ? readTimerMarker(sandboxName) : null;
const assertTimedAuthority = () => {
if (mode !== "temporarily_unlocked") return;
if (!marker || !transition || transition.phase !== "active") {
throw new Error("Timed mutable status lost its exact active auto-restore authority");
}
assertFreshShieldsDownAuthority(sandboxName, marker, transition, "active");
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Determine whether a timed DOWN record can persist without a transition artifact.
set -euo pipefail

rg -nP --type=ts -C5 'readTimerBoundShieldsDownTransition' src/lib/shields
rg -nP --type=ts -C8 'forwardPolicy' src/lib/shields/index.ts | head -80
rg -nP --type=ts -C5 'shields-transition-' src/lib/shields

Repository: NVIDIA/NemoClaw

Length of output: 29612


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- timed-DOWN creation, marker lifecycle, and transition cleanup ---'
rg -n -C8 'startFreshShieldsDownTimer|writeTimerMarker|clearTimer|readTimerMarker|clearShieldsDownTransition|phase: "active"|shieldsDown: true' src/lib/shields/index.ts

printf '%s\n' '--- protocol selection and Hermes status path ---'
rg -n -C12 'inspectHermesShieldsProtocol|provider-state-mutation-v2|verifyHermesProviderMutableStatus|getShieldsPostureWithoutHostLock|temporarily_unlocked' src/lib/shields/index.ts

printf '%s\n' '--- rebuild and persisted state handling ---'
rg -n -C10 'rebuild|recreate|managed image|capability|shields.*state|shieldsDown' src/lib/shields src/lib | head -300

Repository: NVIDIA/NemoClaw

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- protocol capability and active-provider detection ---'
sed -n '850,970p' src/lib/shields/index.ts
rg -n -C12 'function hasActiveRuntimeProviderStateMutation|hasActiveRuntimeProviderStateMutation\(' src/lib/shields/index.ts src/lib

printf '%s\n' '--- posture and Hermes status implementation ---'
sed -n '1478,1510p' src/lib/shields/index.ts
sed -n '5398,5498p' src/lib/shields/index.ts

printf '%s\n' '--- rebuild/state preservation paths ---'
rg -n -C8 'clearShieldsStateWithoutHostLock|rebuild|lifecycleGeneration|state file|shields-timer' src/lib --glob '*.ts' | head -240

printf '%s\n' '--- timed status and legacy compatibility tests ---'
rg -n -C10 'temporarily_unlocked|timed|transition.*null|missing.*transition|provider-state-mutation-v2|rebuild' src/lib/shields/*.test.ts | head -300

Repository: NVIDIA/NemoClaw

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- runtime-provider mutation state helper ---'
rg -n -C20 'hasActiveHermesRuntimeProviderStateMutation|runtime-provider-lifecycle|active.*mutation|provider.*state.*mutation' src --glob '*.ts' | head -260

printf '%s\n' '--- sandbox rebuild lifecycle entrypoints ---'
rg -n -C15 'upgrade.*sandbox|rebuild.*sandbox|rebuildSandbox|recreate.*sandbox|destroy.*rebuild|lifecycleGeneration|clearShieldsStateWithoutHostLock' src --glob '*.ts' | head -320

printf '%s\n' '--- timer marker persistence and timer child behavior ---'
sed -n '1,260p' src/lib/shields/timer-control.ts
rg -n -C12 'timerMarkerPath|writeTimerMarkerAtomic|clearTimerMarker|processToken|shieldsDown' src/lib/shields/timer-control.ts src/lib/shields/index.ts | head -260

Repository: NVIDIA/NemoClaw

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- rebuild action files ---'
git ls-files 'src/lib/actions/**' 'src/lib/**' | rg '/(rebuild|destroy|upgrade)(/|\.|-)'
fd -i 'rebuild|destroy|upgrade' src/lib/actions src/lib/commands src/commands

printf '%s\n' '--- rebuild implementation and callers ---'
rg -n -C18 'export async function rebuildSandbox|function rebuildSandbox|rebuildSandbox\(|destroySandbox\(|clearShieldsStateWithoutHostLock|recoverSandboxWithHermesCronRestore' src/lib/actions src/commands src/lib --glob '*.ts' | head -400

printf '%s\n' '--- state cleanup helpers and lifecycle-store cleanup ---'
rg -n -C15 'clear.*Runtime|remove.*Lifecycle|delete.*Lifecycle|PERSISTED_ENGINE_LIFECYCLE_DIRECTORY|destroy.*state|clear.*state' src/lib/onboard src/lib/state src/lib/actions --glob '*.ts' | head -300

Repository: NVIDIA/NemoClaw

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- rebuild Shields phase ---'
sed -n '1,280p' src/lib/actions/sandbox/rebuild-shields.ts
sed -n '1,260p' src/lib/actions/sandbox/rebuild-shields-phase.ts

printf '%s\n' '--- rebuild destroy phase ---'
sed -n '1,300p' src/lib/actions/sandbox/rebuild-destroy-phase.ts
sed -n '1,260p' src/lib/actions/sandbox/destroy-execution.ts

printf '%s\n' '--- rebuild orchestration call sites ---'
rg -n -C12 'rebuildShields|runRebuildShields|rebuild-shields|destroyPhase|executeSandboxDestroy|wipeSandboxState|cleanupShieldsDestroyArtifacts' src/lib/actions/sandbox/rebuild*.ts src/lib/actions/sandbox/destroy*.ts

printf '%s\n' '--- state wipe implementation ---'
sed -n '380,455p' src/lib/actions/sandbox/destroy.ts
rg -n -C12 'function removeShieldsState|export.*removeShieldsState|runtime-provider-lifecycle|shields-forward-policy|shields-transition' src/lib/actions/sandbox src/lib/state src/lib/shields --glob '*.ts' | head -220

Repository: NVIDIA/NemoClaw

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- backup Shields window implementation ---'
sed -n '1,360p' src/lib/actions/sandbox/backup-shields-window.ts

printf '%s\n' '--- rebuild state helper ---'
rg -n -C25 'openBackupShieldsWindowForState|rebuildShieldsWindow|staleSandboxWasLocked|wasLocked|temporarily_unlocked|isShieldsDown' src/lib/actions/sandbox/rebuild-flow-helpers.ts src/lib/actions/sandbox/backup-shields-window.ts

printf '%s\n' '--- tests for already-down rebuild behavior ---'
rg -n -C18 'already.*down|temporarily_unlocked|wasLocked|openBackupShieldsWindowForState|rebuild.*down|timer.*rebuild' src/lib/actions/sandbox/*rebuild*.test.ts src/lib/actions/sandbox/*backup*.test.ts

Repository: NVIDIA/NemoClaw

Length of output: 34147


Handle timed-DOWN state without a transition artifact.

openBackupShieldsWindow leaves an existing temporarily_unlocked state unchanged. A rebuild can therefore retain its host timer marker and Shields state while replacing the sandbox image. If that state predates the transition artifact, transition is null; assertTimedAuthority then throws and shields status reports DOWN (DRIFTED …) with exit code 2. Migrate this legacy state before enabling provider-state-mutation-v2, or handle the missing transition as a bounded compatibility case.

🤖 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 `@src/lib/shields/index.ts` around lines 5422 - 5429, Update the
temporarily_unlocked handling around assertTimedAuthority so a legacy timed-DOWN
state with a valid marker but no transition artifact is migrated before
provider-state-mutation-v2 is enabled, or accepted through a bounded
compatibility path. Preserve strict validation for missing/invalid markers and
non-active transitions, and ensure shields status no longer reports this legacy
state as drifted solely because transition is null.

Comment thread src/lib/shields/timer.ts
Comment on lines +204 to +205
(marker.timerProcessStartIdentity === undefined ||
marker.timerProcessStartIdentity === readProcessStartIdentity(process.pid)) &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Cache the current process start identity instead of reading it on every match.

markerRecordMatchesCurrentTimer now calls readProcessStartIdentity(process.pid) on each invocation. The authorityPoll interval at Line 685 calls this function every AUTO_RESTORE_AUTHORITY_POLL_MS, and the restore timeout calls it again.

The start identity of the current process cannot change while the process runs. Each call reads /proc/self/stat, and on a host without /proc it falls back to execFileSync("ps", ...), which forks a process on every poll tick.

Resolve the identity once at module scope or on first use, then compare against the cached value.

♻️ Proposed change
+let currentProcessStartIdentity: string | null | undefined;
+
+function selfStartIdentity(): string | null {
+  if (currentProcessStartIdentity === undefined) {
+    currentProcessStartIdentity = readProcessStartIdentity(process.pid);
+  }
+  return currentProcessStartIdentity;
+}
+
 function markerRecordMatchesCurrentTimer(
   marker: ShieldsTimerMarker | null,
   args: TimerArgs,
 ): boolean {
@@
     (marker.timerProcessStartIdentity === undefined ||
-      marker.timerProcessStartIdentity === readProcessStartIdentity(process.pid)) &&
+      marker.timerProcessStartIdentity === selfStartIdentity()) &&
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
(marker.timerProcessStartIdentity === undefined ||
marker.timerProcessStartIdentity === readProcessStartIdentity(process.pid)) &&
let currentProcessStartIdentity: string | null | undefined;
function selfStartIdentity(): string | null {
if (currentProcessStartIdentity === undefined) {
currentProcessStartIdentity = readProcessStartIdentity(process.pid);
}
return currentProcessStartIdentity;
}
function markerRecordMatchesCurrentTimer(
marker: ShieldsTimerMarker | null,
args: TimerArgs,
): boolean {
(marker.timerProcessStartIdentity === undefined ||
marker.timerProcessStartIdentity === selfStartIdentity()) &&
🤖 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 `@src/lib/shields/timer.ts` around lines 204 - 205, Cache the current process
start identity once at module scope or on first use, then update
markerRecordMatchesCurrentTimer to compare against that cached value instead of
calling readProcessStartIdentity(process.pid) on every invocation. Preserve the
existing undefined-identity matching behavior and retain the comparison for
markers with a stored identity.

Comment on lines +905 to +906
selected, status = os.waitpid(writer_pid, os.WUNTRACED | os.WNOHANG)
results["guardian_writer_stopped"] = selected == writer_pid and os.WIFSTOPPED(status)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Poll waitpid until the stop is reported, to avoid a flaky assertion.

Line 905 calls os.waitpid once with WNOHANG. If the kernel has not yet reported the stop that guardian_hold sent at line 857, waitpid returns (0, 0) and guardian_writer_stopped becomes false. Line 1402 then fails intermittently. The last-resort block at lines 1003-1007 already polls until a deadline for the same condition. Use the same form here.

🐛 Proposed fix to poll for the stop report
-        selected, status = os.waitpid(writer_pid, os.WUNTRACED | os.WNOHANG)
-        results["guardian_writer_stopped"] = selected == writer_pid and os.WIFSTOPPED(status)
+        selected = 0
+        status = 0
+        deadline = time.monotonic() + 2
+        while time.monotonic() < deadline:
+            selected, status = os.waitpid(writer_pid, os.WUNTRACED | os.WNOHANG)
+            if selected == writer_pid and os.WIFSTOPPED(status):
+                break
+            time.sleep(0.01)
+        results["guardian_writer_stopped"] = selected == writer_pid and os.WIFSTOPPED(status)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
selected, status = os.waitpid(writer_pid, os.WUNTRACED | os.WNOHANG)
results["guardian_writer_stopped"] = selected == writer_pid and os.WIFSTOPPED(status)
selected = 0
status = 0
deadline = time.monotonic() + 2
while time.monotonic() < deadline:
selected, status = os.waitpid(writer_pid, os.WUNTRACED | os.WNOHANG)
if selected == writer_pid and os.WIFSTOPPED(status):
break
time.sleep(0.01)
results["guardian_writer_stopped"] = selected == writer_pid and os.WIFSTOPPED(status)
🤖 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 `@test/runtime-state-mutation-control.test.ts` around lines 905 - 906, Update
the waitpid check in the guardian writer-stop assertion to poll until the stop
is reported or the established deadline expires, matching the existing polling
pattern in the last-resort block. Keep using WUNTRACED|WNOHANG and set
results["guardian_writer_stopped"] from the final selected PID and
WIFSTOPPED(status) result.

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Comment thread scripts/runtime_state_mutation_hermes_publisher.py Fixed
return directory_access | os.O_DIRECTORY | os.O_NOFOLLOW | os.O_CLOEXEC


def _open_absolute_directory(path: str, *, readable_final: bool = False) -> int:

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/runtime-state-mutation-startup-gate.py (1)

443-489: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Prevent concurrent publication from replacing a conflicting payload.

Each function checks for an existing file before it calls os.replace. Two concurrent writers can both observe no file. The later os.replace then silently replaces the first payload. This bypasses candidate-conflict or retry-ack-conflict and can persist a receipt that does not match the first validated binding.

Use a no-replace publication primitive, or serialize writers. On a destination collision, reread the final file and accept only byte-identical content.

  • scripts/runtime-state-mutation-startup-gate.py#L443-L489: publish startup-complete.json without replacing an existing candidate.
  • scripts/runtime-state-mutation-startup-gate.py#L555-L611: publish retry-ack.json without replacing an existing acknowledgement.
🤖 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 `@scripts/runtime-state-mutation-startup-gate.py` around lines 443 - 489,
Prevent concurrent publication races in the startup-complete.json path at
scripts/runtime-state-mutation-startup-gate.py:443-489 and the retry-ack.json
path at scripts/runtime-state-mutation-startup-gate.py:555-611. Replace the
unconditional os.replace publication with a no-replace or serialized operation;
when publication collides, reread the final file and accept only byte-identical
payloads, otherwise invoke the existing candidate-conflict or retry-ack-conflict
handling. Update both affected publication flows consistently.
🧹 Nitpick comments (1)
test/helpers/docker-state-mutation-harness.ts (1)

345-355: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

replayDeferredAcquire skips the response hooks that the capture path applies.

The capture path runs options.afterHelper and the lostAcquireResponsesRemaining counter after it builds an acquire receipt. replayDeferredAcquire reproduces only acquireMarker. If a test combines deferAcquireOnce with afterHelper or loseAcquireResponseOnce, the replayed acquire and the captured acquire diverge, and the test asserts against a state the real helper path never produces.

Route the replay through the same tail as the capture path.

♻️ Proposed refactor
   const replayDeferredAcquire = () => {
     if (deferredAcquireRequest === null) throw new Error("No deferred acquire request exists.");
     const serializedRequest = deferredAcquireRequest;
     deferredAcquireRequest = null;
     helperActions.push("acquire");
     acquireRequests.push(serializedRequest);
     const request = JSON.parse(serializedRequest) as Record<string, unknown>;
     const conflict = acquireMarker(request);
     if (conflict) throw new Error(conflict.stderr);
+    options.afterHelper?.("acquire", state);
     return marker;
   };
🤖 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 `@test/helpers/docker-state-mutation-harness.ts` around lines 345 - 355, Update
replayDeferredAcquire to execute the same post-receipt tail as the capture path,
including options.afterHelper and lostAcquireResponsesRemaining handling after
acquireMarker succeeds. Reuse the existing capture-path helper or shared logic
rather than duplicating behavior, while preserving the deferred request reset
and conflict validation.
🤖 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.

Outside diff comments:
In `@scripts/runtime-state-mutation-startup-gate.py`:
- Around line 443-489: Prevent concurrent publication races in the
startup-complete.json path at
scripts/runtime-state-mutation-startup-gate.py:443-489 and the retry-ack.json
path at scripts/runtime-state-mutation-startup-gate.py:555-611. Replace the
unconditional os.replace publication with a no-replace or serialized operation;
when publication collides, reread the final file and accept only byte-identical
payloads, otherwise invoke the existing candidate-conflict or retry-ack-conflict
handling. Update both affected publication flows consistently.

---

Nitpick comments:
In `@test/helpers/docker-state-mutation-harness.ts`:
- Around line 345-355: Update replayDeferredAcquire to execute the same
post-receipt tail as the capture path, including options.afterHelper and
lostAcquireResponsesRemaining handling after acquireMarker succeeds. Reuse the
existing capture-path helper or shared logic rather than duplicating behavior,
while preserving the deferred request reset and conflict validation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 8b8e63ec-5ba1-41f6-9446-1068fa8a40c5

📥 Commits

Reviewing files that changed from the base of the PR and between 7a3ed92 and 01ec592.

📒 Files selected for processing (22)
  • scripts/runtime-state-mutation-control.py
  • scripts/runtime-state-mutation-startup-gate.py
  • src/lib/onboard/runtime-provider/docker-operation-authority.test.ts
  • src/lib/onboard/runtime-provider/docker-operation-authority.ts
  • src/lib/onboard/runtime-provider/docker-state-mutation.test.ts
  • src/lib/onboard/runtime-provider/persisted-engine-lifecycle.test.ts
  • src/lib/onboard/runtime-provider/state-mutation.test.ts
  • src/lib/sandbox/privileged-exec-exclusion.test.ts
  • src/lib/sandbox/privileged-exec.test.ts
  • src/lib/shields/flow.test.ts
  • src/lib/shields/hermes-runtime-state-mutation.test.ts
  • src/lib/shields/legacy-hermes-compat.test.ts
  • test/helpers/docker-operation-authority-test-helpers.ts
  • test/helpers/docker-state-mutation-harness.ts
  • test/helpers/hermes-runtime-state-mutation-test-helpers.ts
  • test/helpers/hermes-shields-provider-consumer-harness.ts
  • test/helpers/privileged-exec-test-helpers.ts
  • test/helpers/runtime-provider-state-mutation-test-helpers.ts
  • test/helpers/shields-flow-harness.ts
  • test/hermes-doctor-config-hash.test.ts
  • test/runtime-state-mutation-control.test.ts
  • test/runtime-state-mutation-startup-gate.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/lib/sandbox/privileged-exec-exclusion.test.ts
  • src/lib/sandbox/privileged-exec.test.ts
  • src/lib/shields/hermes-runtime-state-mutation.test.ts
  • src/lib/onboard/runtime-provider/state-mutation.test.ts
  • test/runtime-state-mutation-control.test.ts

Comment thread test/helpers/privileged-exec-test-helpers.ts Fixed
@copy-pr-bot

copy-pr-bot Bot commented Aug 10, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
@github-actions

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
src/lib/onboard/runtime-provider/docker-operation-authority.ts (2)

264-271: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Require a regular file in executableCandidate.

fs.accessSync(candidate, fs.constants.X_OK) also succeeds for a directory that carries the execute bit. A directory named ssh or docker-credential-* on PATH is therefore selected, and qualifiedExecutableMetadata then throws "Docker operation executable is not one regular file." That aborts all Docker authority qualification instead of skipping the entry.

Align the candidate filter with the later requirement.

♻️ Proposed fix
 function executableCandidate(candidate: string): boolean {
   try {
+    if (!fs.statSync(candidate).isFile()) return false;
     fs.accessSync(candidate, fs.constants.X_OK);
     return true;
   } catch {
     return false;
   }
 }
🤖 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 `@src/lib/onboard/runtime-provider/docker-operation-authority.ts` around lines
264 - 271, Update executableCandidate to require that candidate is a regular
file in addition to having execute permission, using filesystem stat
information; return false for directories or other non-regular entries so
qualifiedExecutableMetadata can skip them.

572-576: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Per-command guards repeat full qualification. The binding guard re-runs every qualification step on each guarded Docker command, so filesystem scans and an extra Docker invocation are paid per command.

  • src/lib/onboard/runtime-provider/docker-operation-authority.ts#L572-L576: bound the re-qualification, for example with a freshness window or one re-qualification per operation, instead of running executable.guard(), delegatedCommands.guard(), and endpointGuard() on each command.
  • src/lib/onboard/runtime-provider/docker-operation-authority.ts#L618-L634: reuse the bounded result for the context re-inspection, and report an inspection failure separately from an identity mismatch.
🤖 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 `@src/lib/onboard/runtime-provider/docker-operation-authority.ts` around lines
572 - 576, In docker-operation-authority.ts, update the binding guard around the
guard callback at lines 572-576 to bound re-qualification instead of invoking
executable.guard(), delegatedCommands.guard(), and endpointGuard() for every
command, using a freshness window or once-per-operation result. At lines
618-634, reuse that bounded qualification result for context re-inspection and
distinguish inspection failures from identity mismatches in the reported
outcome.
src/lib/onboard/runtime-provider/docker-operation-authority.test.ts (1)

187-209: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Split the two independent rejection claims into separate tests.

This test asserts two unrelated rejections: relative PATH entries and env-based delegated interpreters. A failure in either assertion reports the same test name, so the failing claim is not identifiable from the report. Two it blocks give exact failure attribution and let each claim fail independently.

🤖 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 `@src/lib/onboard/runtime-provider/docker-operation-authority.test.ts` around
lines 187 - 209, Split the combined test into two independent it blocks: keep
the relative PATH rejection assertion in one, and move the credential-helper
setup and env-based delegated interpreter assertion into another. Give each test
a specific name identifying its rejection behavior, while preserving the
existing inputs and expected errors.
🤖 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 `@src/lib/inference/llama-cpp/host-local-runtime.ts`:
- Around line 374-386: Require and validate bindings.hostPort in
buildLlamaCppRequestGuardDockerArgv before constructing the --publish argument,
failing closed when it is undefined instead of emitting an empty host-port
field. Preserve the existing validated-port publication format for defined
hostPort values.

In `@test/helpers/privileged-exec-test-helpers.ts`:
- Around line 43-47: Update releaseAndStopChild so failures from
createReleaseMarker do not bypass child termination: preserve the marker error,
attempt the existing conditional SIGKILL and waitForChildExit cleanup, then
rethrow the original error after cleanup completes.
- Around line 33-37: Update the release block around createReleaseMarker so
released is assigned true only after marker creation completes successfully.
Keep the existing guard and ensure a failure from createReleaseMarker leaves
released false, allowing later calls to retry marker creation.

---

Nitpick comments:
In `@src/lib/onboard/runtime-provider/docker-operation-authority.test.ts`:
- Around line 187-209: Split the combined test into two independent it blocks:
keep the relative PATH rejection assertion in one, and move the
credential-helper setup and env-based delegated interpreter assertion into
another. Give each test a specific name identifying its rejection behavior,
while preserving the existing inputs and expected errors.

In `@src/lib/onboard/runtime-provider/docker-operation-authority.ts`:
- Around line 264-271: Update executableCandidate to require that candidate is a
regular file in addition to having execute permission, using filesystem stat
information; return false for directories or other non-regular entries so
qualifiedExecutableMetadata can skip them.
- Around line 572-576: In docker-operation-authority.ts, update the binding
guard around the guard callback at lines 572-576 to bound re-qualification
instead of invoking executable.guard(), delegatedCommands.guard(), and
endpointGuard() for every command, using a freshness window or
once-per-operation result. At lines 618-634, reuse that bounded qualification
result for context re-inspection and distinguish inspection failures from
identity mismatches in the reported outcome.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 55d0c51a-ae1f-4a08-ba46-f04d9d0a54dd

📥 Commits

Reviewing files that changed from the base of the PR and between b0c481d and 70beb5c.

📒 Files selected for processing (20)
  • agents/hermes/Dockerfile
  • docs/inference/set-up-llama-cpp.mdx
  • docs/manage-sandboxes/runtime-controls.mdx
  • docs/reference/commands.mdx
  • docs/reference/host-files-and-state.mdx
  • docs/reference/troubleshooting.mdx
  • docs/security/tcb-boundary.mdx
  • scripts/checks/run-llama-cpp-dgx-spark-qualification.mts
  • scripts/install.sh
  • src/lib/inference/llama-cpp/host-local-runtime.test.ts
  • src/lib/inference/llama-cpp/host-local-runtime.ts
  • src/lib/onboard/runtime-provider/docker-operation-authority.test.ts
  • src/lib/onboard/runtime-provider/docker-operation-authority.ts
  • src/lib/sandbox/privileged-exec-exclusion.test.ts
  • src/lib/shields/hermes-runtime-state-mutation.test.ts
  • src/lib/shields/hermes-runtime-state-mutation.ts
  • test/helpers/hermes-shields-provider-consumer-harness.ts
  • test/helpers/privileged-exec-test-helpers.ts
  • test/install-preflight.test.ts
  • test/llama-cpp-dgx-spark-qualification-runner.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • agents/hermes/Dockerfile
  • src/lib/shields/hermes-runtime-state-mutation.ts
  • src/lib/shields/hermes-runtime-state-mutation.test.ts
  • test/helpers/hermes-shields-provider-consumer-harness.ts

Comment on lines +374 to +386
export function buildLlamaCppRequestGuardDockerArgv(
contract: LlamaCppHostLocalLaunchContract,
bindings: LlamaCppHostLocalRuntimeBindings,
loopbackPublishAuthority: DockerLoopbackPublishAuthority,
): string[] {
consumeDockerLoopbackPublishAuthority(loopbackPublishAuthority);
validateContract(contract);
validateBindings(contract, bindings);
const { limits } = contract.serve;
return [
...buildLlamaCppHostLocalDockerRunArgv(contract, bindings),
"--publish",
`127.0.0.1:${bindings.hostPort === undefined ? "" : String(bindings.hostPort)}:${String(contract.serve.port)}`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Require hostPort before you build the loopback publish specification.

When bindings.hostPort is undefined, line 386 emits --publish 127.0.0.1::8081. Docker reads that as a request for an ephemeral host port on loopback. The container starts on an unpredictable port instead of the documented port 8081.

validateBindings treats hostPort as optional (lines 290-292), and the downstream caller buildServerContainerArgv in scripts/checks/run-llama-cpp-dgx-spark-qualification.mts spreads hostPort only when it is defined. The undefined path is therefore reachable from the qualification runner.

This function exists to fence loopback publication. A missing host port must fail closed, not select a random port silently.

🐛 Proposed fix to require the host port
 export function buildLlamaCppRequestGuardDockerArgv(
   contract: LlamaCppHostLocalLaunchContract,
   bindings: LlamaCppHostLocalRuntimeBindings,
   loopbackPublishAuthority: DockerLoopbackPublishAuthority,
 ): string[] {
   consumeDockerLoopbackPublishAuthority(loopbackPublishAuthority);
   validateContract(contract);
   validateBindings(contract, bindings);
+  if (bindings.hostPort === undefined) {
+    throw new Error(
+      "llama.cpp request-guard loopback publishing requires an explicit host port.",
+    );
+  }
   const { limits } = contract.serve;
   return [
     ...buildLlamaCppHostLocalDockerRunArgv(contract, bindings),
     "--publish",
-    `127.0.0.1:${bindings.hostPort === undefined ? "" : String(bindings.hostPort)}:${String(contract.serve.port)}`,
+    `127.0.0.1:${String(bindings.hostPort)}:${String(contract.serve.port)}`,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function buildLlamaCppRequestGuardDockerArgv(
contract: LlamaCppHostLocalLaunchContract,
bindings: LlamaCppHostLocalRuntimeBindings,
loopbackPublishAuthority: DockerLoopbackPublishAuthority,
): string[] {
consumeDockerLoopbackPublishAuthority(loopbackPublishAuthority);
validateContract(contract);
validateBindings(contract, bindings);
const { limits } = contract.serve;
return [
...buildLlamaCppHostLocalDockerRunArgv(contract, bindings),
"--publish",
`127.0.0.1:${bindings.hostPort === undefined ? "" : String(bindings.hostPort)}:${String(contract.serve.port)}`,
export function buildLlamaCppRequestGuardDockerArgv(
contract: LlamaCppHostLocalLaunchContract,
bindings: LlamaCppHostLocalRuntimeBindings,
loopbackPublishAuthority: DockerLoopbackPublishAuthority,
): string[] {
consumeDockerLoopbackPublishAuthority(loopbackPublishAuthority);
validateContract(contract);
validateBindings(contract, bindings);
if (bindings.hostPort === undefined) {
throw new Error(
"llama.cpp request-guard loopback publishing requires an explicit host port.",
);
}
const { limits } = contract.serve;
return [
...buildLlamaCppHostLocalDockerRunArgv(contract, bindings),
"--publish",
`127.0.0.1:${String(bindings.hostPort)}:${String(contract.serve.port)}`,
🤖 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 `@src/lib/inference/llama-cpp/host-local-runtime.ts` around lines 374 - 386,
Require and validate bindings.hostPort in buildLlamaCppRequestGuardDockerArgv
before constructing the --publish argument, failing closed when it is undefined
instead of emitting an empty host-port field. Preserve the existing
validated-port publication format for defined hostPort values.

Comment thread test/helpers/privileged-exec-test-helpers.ts
Comment thread test/helpers/privileged-exec-test-helpers.ts
ericksoa and others added 5 commits August 9, 2026 19:15
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Comment thread scripts/runtime_state_mutation_hermes_publisher.py Fixed
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
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.

2 participants