Skip to content
This repository was archived by the owner on Aug 4, 2026. It is now read-only.

feat(coupling): complete server-only protected materialization - #3

Merged
Yeusepe merged 28 commits into
mainfrom
feat/server-only-coupling
Jul 27, 2026
Merged

Yeusepe merged 28 commits into
mainfrom
feat/server-only-coupling

Conversation

@Yeusepe

@Yeusepe Yeusepe commented Jul 26, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Enforce Linux server-only materialization and attribution boundaries.
  • Add adaptive PNG embedding profiles for small protected textures.
  • Add deterministic compact-mesh attribution for small binary FBX assets.
  • Keep per-asset self-verification and signed receipt generation mandatory.
  • Harden codec sandbox limits, attribution requests, and native runtime validation.
  • Publish the verified Linux x64 runtime with its matching SHA-256 record.

Why

Real JAMMR assets exposed valid files that did not contain enough capacity for the fixed PNG and mesh profiles. The materializer rejected those assets with MATERIALIZATION_CODEC_ASSET_UNCARRYABLE. The codec now selects a bounded profile that fits each supported asset while preserving deterministic attribution and blind recovery.

Validation

  • bun run typecheck
  • bun test with 36 passing tests
  • bun run test:linux-rendition with 4 passing real tests
  • Real JAMMR materialization probe across all five protected assets
  • Native Linux runtime hash: da18c771ed89d1648b679c88cbb95d06a6b32aedfcbe024517cfb4f3cc91bb95

Boundaries

This repository remains the only owner of proprietary coupling code. Creator Assistant calls the authenticated server interface and contains no coupling implementation.

Coordinated changes

Summary by CodeRabbit

  • New Features
    • Added a Linux materialization server with authenticated job processing, attribution evaluation, and deterministic personalized rendition ZIP creation.
    • Added Docker-isolated codec/tree/attribution workers plus a persistent materialization chunk cache.
    • Added DPoP-based request signing and master key brokering for deterministic key derivation.
  • Deployment
    • Added hardened local WSL setup/runner scripts, locked-down master-epoch credential initialization, and a sandboxed systemd service.
    • Replaced the environment template with MATERIALIZATION_* configuration.
  • API Changes
    • Migrated from the legacy coupling HTTP workflow to the new materialization server workflow.
  • Bug Fixes
    • Significantly tightened request validation, sandbox isolation, and end-to-end output integrity checks.
  • Tests / Chores
    • Expanded Linux worker/sandbox regression coverage and updated lint/typecheck/test scripts.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026 •

Copy link
Copy Markdown

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

This PR replaces the coupling server with a Linux materialization service. It adds credential-backed key brokering, DPoP authentication, sandboxed rendition and attribution workflows, deterministic archive generation, control-plane orchestration, hardened deployment scripts, and server-only native watermark codecs.

Changes

Materialization service

Layer / File(s) Summary
Deployment and environment boundary
.env.example, deploy/*, package.json, src/env.*, src/sandbox*, src/materializationBuilds.*, src/serverArchitecture.test.ts
Deployment configures and launches the materialization server with local credentials, systemd hardening, WSL support, sanitized Docker execution, materialization-specific environment variables, deterministic build identity hashing, and repository architecture invariants.
Key broker and DPoP contracts
src/keyBroker.*, src/dpopSigner.*, src/boundedJson.*
Adds epoch-key parsing, subject pseudonym preparation, deterministic file-key derivation, DPoP proof creation, and bounded UTF-8/JSON handling with validation and zeroization.
Sandboxed tree, codec, and attribution workflows
src/attribution.*, src/linux*Sandbox.*, src/linux*Worker.py, src/linuxWorkers_test.py, src/sandboxContainer.*, src/sandboxImage.*, src/codecLimits.*
Adds confined Python workers and Docker wrappers for tree extraction, deterministic archive building, native codec execution, sandbox container lifecycle, and archive attribution.
Personalized rendition pipeline
src/rendition.*, src/linuxRendition.realtest.ts
Adds protected-tree validation, codec execution, output verification, attribution records, deterministic ZIP creation, classified failures, cleanup, and Linux integration coverage.
Capability consumption and source assembly
src/materializationService.*, src/materializationChunkCache.*
Adds capability validation, manifest and chunk verification, persistent chunk caching, source-tree assembly, rendition upload/completion, scoped cleanup, and typed execution failures.
Materialization server and HTTP boundary
src/materializationServer.*
Adds the worker claim loop, health and authenticated broker/attribution routes, configuration loading, isolation checks, failure reporting, and lifecycle management.
Linux native codec and watermarking
yucp_coupling/*, src/nativeHardening.test.ts
Constrains native builds to Linux x64 and updates image and mesh watermarking with profile selection, adaptive repetition, fallback carriers, token-hash verification, and Linux artifact checks.

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

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.25% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the main shift to server-only protected materialization, though it omits other major Linux-only and hardening changes.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

Yeusepe commented Jul 26, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

Yeusepe commented Jul 26, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@coderabbitai

coderabbitai Bot commented Jul 26, 2026 •

Copy link
Copy Markdown
✅ Action performed

Full review finished.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9aaaa6f874

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/materializationService.ts Outdated
Comment thread src/materializationServer.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 15

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (9)
src/materializationService.ts-1462-1473 (1)

1462-1473: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

materializerId is validated only inside the consume call.

requireString(input.materializerId, ...) runs at Line 1471, but the raw input.materializerId is what's sent to the upload-ticket and completion endpoints. Normalize once at the top of materializeCapabilityJob and reuse it.

Also applies to: 1594-1634

🤖 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/materializationService.ts` around lines 1462 - 1473, Normalize and
validate input.materializerId once at the start of materializeCapabilityJob,
then reuse that normalized value throughout the job. Replace the raw
input.materializerId used by the upload-ticket and completion endpoint flows, as
well as the consumeCapability call, while preserving the existing materializerId
validation behavior.
src/materializationServer.test.ts-127-156 (1)

127-156: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Test name claims the maximum boundary but exercises half of it.

MAXIMUM_CAPABILITY_BYTES is 2 MiB while this uses 1 MiB. Worth noting the real boundary is unreachable: MAXIMUM_RESPONSE_BYTES equals MAXIMUM_CAPABILITY_BYTES, so a 2 MiB capability plus the surrounding JSON envelope trips the response-size check in readJsonResponse before requireBase64Url ever sees it. Either rename this to reflect the value tested, or add a case at 2 MiB to pin the intended behavior.

🤖 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/materializationServer.test.ts` around lines 127 - 156, Update the
boundary test around createMaterializationWorker so its name and capability
length accurately reflect the 1 MiB value currently exercised, or add a separate
2 MiB case that verifies the intended maximum behavior while accounting for
readJsonResponse’s response-size limit before requireBase64Url validation.
src/materializationServer.ts-618-628 (1)

618-628: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Reject non-string subject fields at the HTTP boundary.
The broker rejects empty strings and unsafe epochs, but String(...) still turns arrays/objects into valid-looking IDs ("[object Object]", "a,b"), so malformed JSON can reach prepareSubject as if it were real input. Validate the raw body here instead of relying on coercion.

🤖 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/materializationServer.ts` around lines 618 - 628, Update the request
handling around readBoundedRequestJson and input.keyBroker.prepareSubject to
validate buyerId, creatorId, jobId, and productId as actual non-empty strings
before calling the broker. Remove String coercion, reject arrays, objects, and
other non-string values at the HTTP boundary using the existing request error
response pattern, while preserving numeric keyEpoch validation and the
successful prepareSubject flow.
src/linuxAttributionWorker.py-33-49 (1)

33-49: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Partially-read keys aren't zeroized on a truncated request.

If read_exact raises mid-comprehension while building keys (line 46), the file-key bytearrays already read for earlier candidates are discarded without being wiped — inconsistent with the explicit zeroization elsewhere in this file (lines 118, 174-176).

🔒 Proposed fix to zero partially-read keys on failure
 def read_request():
     header_length = struct.unpack(">I", read_exact(4))[0]
     if header_length <= 0 or header_length > MAX_HEADER_BYTES:
         raise ValueError("attribution pipe header length is invalid")
     header = json.loads(read_exact(header_length))
     if header.get("schemaVersion") != 2:
         raise ValueError("attribution pipe schema is unsupported")
     assets = header.get("assets")
     candidates = header.get("candidates")
     if not isinstance(assets, list) or not 1 <= len(assets) <= 512:
         raise ValueError("assets are outside the attribution limit")
     if not isinstance(candidates, list) or not 1 <= len(candidates) <= 4096:
         raise ValueError("candidates are outside the attribution limit")
-    keys = [bytearray(read_exact(FILE_KEY_BYTES)) for _ in candidates]
+    keys = []
+    try:
+        for _ in candidates:
+            keys.append(bytearray(read_exact(FILE_KEY_BYTES)))
+    except Exception:
+        for file_key in keys:
+            for index in range(len(file_key)):
+                file_key[index] = 0
+        raise
     if sys.stdin.buffer.read(1):
         raise ValueError("attribution pipe frame contains trailing bytes")
     return assets, candidates, keys
🤖 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/linuxAttributionWorker.py` around lines 33 - 49, Update read_request so
key bytearrays accumulated during the candidates read are zeroized if read_exact
fails partway through the comprehension. Replace the direct comprehension with
cleanup-safe accumulation, wipe all already-read keys in the exception path,
then re-raise the original failure; preserve the existing successful return
behavior and trailing-byte validation.
src/linuxTreeSandbox.ts-78-79 (1)

78-79: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Await child.stdin.end() before waiting on child.exited. If the flush is async, Promise.all([...]) can race with the final write and truncate the worker request.

🤖 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/linuxTreeSandbox.ts` around lines 78 - 79, Update the request-writing
flow around child.stdin.write and child.stdin.end so stdin closure is awaited
before awaiting child.exited. Ensure the final serialized request is fully
flushed before worker completion is observed, preserving the existing request
and exit handling.
src/linuxCodecSandbox.ts-188-223 (1)

188-223: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Run the container as non-root and cap swap
docker run still defaults to root here, and without --memory-swap=256m the 256m limit can spill into host swap on swap-enabled machines. The same isolation flags are duplicated in linuxTreeSandbox.ts, so a shared helper would keep both sandboxes aligned.

🤖 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/linuxCodecSandbox.ts` around lines 188 - 223, Update the Docker arguments
in the sandbox command around PINNED_PYTHON_IMAGE to run as a non-root user and
add a memory-swap limit matching the existing 256m memory limit. Extract the
shared isolation arguments into a reusable helper and apply it in both
linuxCodecSandbox and linuxTreeSandbox so their security settings remain
aligned.
yucp_coupling/guard.c-1997-1998 (1)

1997-1998: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

The placement-epoch search loops execute exactly once.

placement_epoch < 1u means only epoch 0 is ever tried in both xg_0124 and xg_0125, so the multi-epoch carrier search the surrounding structure implies is inert — the loops are just obfuscated single passes. Either raise the bound to the intended epoch count (and keep encoder/decoder in lockstep) or drop the loop and pass 0u directly.

Note the encoder and decoder must always agree on this bound; a future change to one side alone silently breaks recovery of previously published assets.

Also applies to: 2144-2145

🤖 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 `@yucp_coupling/guard.c` around lines 1997 - 1998, The placement-epoch loops in
xg_0124 and xg_0125 are hardcoded to a single iteration; either replace the
bound with the intended shared epoch count on both encoder and decoder paths, or
remove the loops and pass 0u directly. Keep both sides synchronized so published
assets remain recoverable.
yucp_coupling/guard.c-1710-1724 (1)

1710-1724: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Use calloc for the slot table to get a checked multiplication.

vl.n > SIZE_MAX / 3u bounds the element count but not slot_capacity * sizeof(size_t), so the allocation size can still wrap. calloc performs the overflow-checked product.

🛡️ Proposed fix
-  size_t* valid_slots = (size_t*)malloc(slot_capacity * sizeof(size_t));
+  size_t* valid_slots = (size_t*)calloc(slot_capacity, sizeof(size_t));
🤖 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 `@yucp_coupling/guard.c` around lines 1710 - 1724, Replace the valid_slots
allocation in the slot-capacity handling with calloc so the multiplication by
sizeof(size_t) is overflow-checked, while preserving the existing capacity
validation, cleanup, and failure return behavior.

Source: Linters/SAST tools

yucp_coupling/build.sh-41-60 (1)

41-60: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Build breaks if the repository path contains spaces.

LINK_FLAGS and SODIUM_CFLAGS embed $HERE/$SODIUM_PREFIX and are deliberately word-split at Line 60, so any space in the checkout path splits the --version-script= and -I arguments. Keep the path-bearing arguments as separate quoted words.

♻️ Proposed change
-LINK_FLAGS="$LINK_MODE -pthread -Wl,-z,relro,-z,now,-z,noexecstack -Wl,--version-script=$HERE/yucp_coupling.exports.map"
+LINK_FLAGS="$LINK_MODE -pthread -Wl,-z,relro,-z,now,-z,noexecstack"
@@
-cc $CFLAGS $SODIUM_CFLAGS "$SRC" $LINK_FLAGS -o "$OUT_LIB" $SODIUM_LINK -lm
+cc $CFLAGS -I"$SODIUM_PREFIX/include" "$SRC" $LINK_FLAGS \
+  "-Wl,--version-script=$HERE/yucp_coupling.exports.map" \
+  -o "$OUT_LIB" "$SODIUM_LINK" -lm
🤖 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 `@yucp_coupling/build.sh` around lines 41 - 60, Update the build command using
LINK_FLAGS and SODIUM_CFLAGS so path-bearing version-script and include
arguments remain separate quoted words when $HERE or $SODIUM_PREFIX contains
spaces. Preserve the existing linker flags and vendored static archive usage,
and avoid relying on word splitting of those variables.
🧹 Nitpick comments (21)
src/materializationService.test.ts (2)

322-357: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Catch-all branch masks unexpected URLs.

Any URL that doesn't match the earlier branches is answered with a capability-consume payload, so a wrong-URL regression would still pass. Assert the consume path explicitly and return a 404 otherwise.

🤖 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/materializationService.test.ts` around lines 322 - 357, The request
handler in the test’s URL-matching mock should only return the
capability-consume payload for the expected consume endpoint. Add an explicit
assertion for that URL or branch, and return a 404 response for all unmatched
URLs so incorrect requests cannot be accepted.

178-215: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fixture has no classification: 'common' file, so source assembly of common content is untested.

Adding one common file (and asserting it lands in the assembled tree) would cover the gap flagged in src/materializationService.ts, where loadSourceManifest returns only protected files.

🤖 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/materializationService.test.ts` around lines 178 - 215, Extend the
sourceManifest fixture with a file classified as 'common', including its chunk
metadata and content, then update the source assembly assertions to verify that
file appears in the assembled tree. Keep the existing protected-file coverage
intact while exercising the common-content path used by loadSourceManifest.
src/rendition.test.ts (1)

16-59: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicate PNG fixture helpers.

crc32, pngChunk, and makeGrayPng are byte-identical to those in src/linuxRendition.realtest.ts. Extract into a shared test fixture module to keep the two suites in sync.

🤖 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/rendition.test.ts` around lines 16 - 59, Extract the duplicate crc32,
pngChunk, and makeGrayPng helpers from rendition.test.ts into a shared test
fixture module, then import and reuse them in both rendition.test.ts and
linuxRendition.realtest.ts. Preserve their current behavior and remove the
duplicate local definitions so the suites stay synchronized.
src/linuxRendition.realtest.ts (1)

112-123: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Runtime path resolution repeated four times; digest pinned only in the first test.

Hoist the runtimePath/expected-digest resolution into a module-level helper so every rendition test runs against a verified runtime binary rather than whatever .so happens to be on disk.

Also applies to: 191-199, 250-258, 305-313

🤖 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/linuxRendition.realtest.ts` around lines 112 - 123, Create a module-level
helper in linuxRendition.realtest.ts that resolves the runtimePath, reads and
normalizes the expected digest, verifies the binary hash, and returns the
verified runtime path. Replace the repeated runtimePath and digest-resolution
blocks in all four rendition tests with calls to this helper, ensuring each test
validates the runtime binary before using it.
src/rendition.ts (1)

263-298: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

arguments is misleading when a real runtime path is used.

When runtimePath is set the sandbox is invoked directly with runtimePath, yet request.arguments claims a docker run command line. The field only reaches an injected runCodec, so the branch is effectively cosmetic and easy to misread as the real execution 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 `@src/rendition.ts` around lines 263 - 298, Update the LinuxCodecRequest
construction in the codec batch loop so arguments reflects the actual execution
path: remove the cosmetic docker command-line branch when runtimePath is
provided, and align the request metadata with the direct runtimePath-based
sandbox invocation while preserving the injected runCodec behavior.
src/materializationService.ts (1)

1094-1108: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Cache reuse/unlink is TOCTOU-prone across concurrent workers.

hashFile → unlink → later rename on a shared chunk-cache root races if more than one job or process ever shares workRoot. Today the worker is single-flight, but the rename-into-place path is already atomic; consider skipping the pre-emptive unlink and letting the rename overwrite, so a concurrent reader never observes a missing cache entry.

🤖 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/materializationService.ts` around lines 1094 - 1108, Remove the
pre-emptive unlink operations from the cache reuse logic around hashFile,
including the catch-path unlink, and let the existing atomic rename-into-place
operation replace stale or invalid entries. Preserve returning destination for
matching cached chunks while ensuring concurrent readers are not exposed to a
missing cache entry.
src/serverArchitecture.test.ts (1)

41-49: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Python workers are outside the forbidden-string sweep.

linuxCodecWorker.py and linuxAttributionWorker.py are production sources in the same boundary but are never scanned. Extend the glob to cover them if the intent is a repository-wide guarantee.

🤖 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/serverArchitecture.test.ts` around lines 41 - 49, Extend the production
source collection used by the server architecture test to include
linuxCodecWorker.py and linuxAttributionWorker.py, ensuring the existing
forbidden-string assertions scan these Python workers along with the current
sources. Keep the forbidden list and assertion behavior unchanged.
src/materializationServer.test.ts (1)

365-376: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Source-string architecture assertions duplicate serverArchitecture.test.ts.

These forbidden-string scans belong with the other architecture guards rather than inside a behavioral handler test; keeping them in one place avoids drift.

🤖 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/materializationServer.test.ts` around lines 365 - 376, Move the
source-string architecture assertions for “/v2/materializations/renditions” and
“serviceSecret” out of the handler test and into the existing architecture guard
suite in serverArchitecture.test.ts. Keep the legacy request status assertion in
the behavioral test, and preserve both forbidden-string checks in their
centralized location.
src/linuxCodecSandbox.test.ts (1)

24-27: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add negative cases for the non-JSON fallback.

Only 'not-json' is covered. Empty string, valid JSON with an unrecognized errorType, and a mismatched schemaVersion all reach the same fallback path and are worth pinning so a future refactor cannot silently leak private detail through the default branch.

🤖 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/linuxCodecSandbox.test.ts` around lines 24 - 27, Add negative test cases
alongside the existing classifyLinuxCodecFailure('not-json') assertion in
linuxCodecSandbox.test.ts for an empty string, valid JSON with an unrecognized
errorType, and valid JSON with a mismatched schemaVersion. Assert that each
input returns MATERIALIZATION_CODEC_SANDBOX_FAILED through the non-JSON fallback
path.
src/materializationServer.ts (2)

129-139: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

stderr is piped but never drained.

docker info failures write diagnostics to stderr; since nothing reads that pipe, a verbose failure can block the child until the 10s kill timer fires. Either drain it alongside stdout or set stderr: 'ignore'.

♻️ Drain stderr
   const timer = setTimeout(() => child.kill(), 10_000);
   try {
-    const [exitCode, stdout] = await Promise.all([
-      child.exited,
-      new Response(child.stdout).text(),
-    ]);
+    const [exitCode, stdout] = await Promise.all([
+      child.exited,
+      new Response(child.stdout).text(),
+      new Response(child.stderr).text(),
+    ]);
🤖 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/materializationServer.ts` around lines 129 - 139, Update the Docker
subprocess handling around child.spawn and the Promise.all that awaits
child.exited and stdout so the piped child.stderr stream is also drained,
preventing verbose failures from blocking. Preserve the existing stdout and
exit-code behavior; alternatively configure stderr as ignored if diagnostics are
intentionally discarded.

642-648: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

64 MiB attribution bodies are fully buffered and amplified in memory.

Each request holds the streamed chunks, the concatenated Uint8Array, the decoded UTF-8 string, and one Buffer per decoded asset — roughly 3–4× the body size resident. With no concurrency cap on Bun.serve, a handful of simultaneous authenticated requests at the limit can exhaust the materializer's heap. Consider a lower per-asset cap plus an in-flight request semaphore.

Also applies to: 660-702

🤖 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/materializationServer.ts` around lines 642 - 648, Reduce the attribution
request body limit used by readBoundedRequestJson and add an in-flight request
semaphore around the attribution handling flow beginning near derivedKeys and
failureCode. Acquire the semaphore before buffering the request, reject or fail
requests when capacity is unavailable, and release it on every completion path,
including errors, while preserving the existing attribution failure handling.
src/keyBroker.test.ts (1)

1-97: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding coverage for the normalizeFiles duplicate/ordering edge case.

Once the ordering-check fix in keyBroker.ts (normalizeFiles) lands, a regression test asserting that two entries differing only by incidental whitespace (e.g. "Assets/x.png " followed by "Assets/x.png") are rejected would guard against reintroducing the bug.

🤖 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/keyBroker.test.ts` around lines 1 - 97, The key broker tests lack
regression coverage for the duplicate/ordering edge case in normalizeFiles. Add
a test that supplies protectedFiles with paths differing only by incidental
whitespace, such as “Assets/x.png ” followed by “Assets/x.png”, and assert that
the derivation flow rejects the input after normalization; keep the test focused
on this duplicate detection behavior.
src/linuxTreeSandbox.ts (2)

80-87: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Response is fully buffered before the size limit is enforced.

new Response(child.stdout).text() materializes the entire output in memory before Buffer.byteLength(...) > MAX_RESPONSE_BYTES is checked, so the guard doesn't actually bound peak memory usage — it only rejects after the fact.

🤖 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/linuxTreeSandbox.ts` around lines 80 - 87, Update the output handling
around child.stdout and child.stderr so response size is enforced while
streaming, before the complete content is materialized. Preserve the existing
MAX_RESPONSE_BYTES rejection behavior and ensure the bounded output remains
available for subsequent processing alongside exitCode.

143-179: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

No host-side verification of the built rendition against its self-reported digest.

buildPersonalizedTreeArchive returns renditionPath/renditionSha256 purely from the worker's self-report; the host process never re-reads rendition.zip to confirm the digest, unlike file extraction which is verified byte-for-byte inside the worker. If downstream consumers rely solely on this value, a compromised or buggy worker could report a mismatched digest undetected at this layer.

🤖 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/linuxTreeSandbox.ts` around lines 143 - 179, Update
buildPersonalizedTreeArchive to read the generated rendition at renditionPath
and compute its SHA-256 digest on the host before returning. Compare the
computed digest with result.renditionSha256 and throw the existing
invalid-result error on mismatch; return the worker-reported digest only after
verification succeeds.
src/linuxCodecSandbox.ts (1)

62-100: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Failure classification is coupled to exact native error-message text.

classifyLinuxCodecFailure distinguishes MATERIALIZATION_CODEC_ASSET_UNCARRYABLE from MATERIALIZATION_CODEC_NATIVE_ENCODE_FAILED purely by matching the literal string 'native encode failed with status 1'. If the worker/native codec's error message format changes, this silently misclassifies failures (business-relevant "uncarryable" cases could be reported as generic encode failures, or vice versa).

Since linuxCodecWorker.py isn't in this review batch, please confirm the exact error string/format it emits for the "uncarryable" case remains stable, or consider emitting a structured discriminant field (e.g., errorReason: 'uncarryable') instead of a status-embedded message.

🤖 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/linuxCodecSandbox.ts` around lines 62 - 100, Update
classifyLinuxCodecFailure to avoid identifying uncarryable assets solely through
the exact parsed.error text. Use a stable structured discriminant emitted by the
worker, such as parsed.errorReason === 'uncarryable', to return
MATERIALIZATION_CODEC_ASSET_UNCARRYABLE, while retaining native encode
classification for other encode failures and the existing fallback behavior.
src/attribution.ts (2)

66-85: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Result entries are returned unvalidated.

Only the array length and schema version are checked; each element is cast to AttributionResult without verifying assetPath/assetType/matched shapes, and callers may treat a malformed matched as truthy. Consider per-entry validation before returning.

🤖 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/attribution.ts` around lines 66 - 85, Update parseResults to validate
every payload.results entry before returning it, confirming assetPath and
assetType have the expected shapes and matched is a valid boolean or expected
value. Reject any malformed entry with the existing invalid-response error,
while preserving the current schema, length, and credentialEnvironment checks.

123-157: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Consider running the container as a non-root user.

The hardening set is strong (no network, read-only, cap-drop ALL, no-new-privileges, pids/memory/cpu limits), but the process still runs as the image's default UID (root). Adding --user with a fixed non-root UID/GID closes the remaining gap cheaply.

♻️ Proposed change
         '--security-opt',
         'no-new-privileges',
+        '--user',
+        '65534:65534',
         '--pids-limit',
🤖 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/attribution.ts` around lines 123 - 157, Update the Docker argument list
in the Bun.spawn invocation to add --user with a fixed non-root UID/GID before
PINNED_PYTHON_IMAGE, ensuring the worker container runs without root privileges
while preserving the existing hardening options.
src/attribution.test.ts (2)

118-151: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a negative attribution case.

Both tests only assert matched: true on the correct key. A case with a mismatched candidateFileKeys entry (or an unwatermarked PNG) asserting matched: false would guard against a decoder that trivially returns the first candidate.

🤖 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/attribution.test.ts` around lines 118 - 151, Add a negative case in the
attribution test around attributeArchiveAssets, using a mismatched
candidateFileKeys entry or unwatermarked PNG, and assert the returned
attribution result has matched: false. Keep the existing positive attribution
assertions unchanged and ensure the case verifies that an unrelated candidate is
not attributed.

60-124: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

These tests hard-require Docker and a prebuilt .so.

Both cases silently depend on yucp_coupling/out/linux-x64/Release/yucp_coupling.so existing and a working Docker daemon; without them the failure surfaces as an opaque non-zero exit from docker run. Consider gating with test.skipIf(...) on runtime presence, or asserting the artifact exists up front so the failure message is actionable.

🤖 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/attribution.test.ts` around lines 60 - 124, Make the server attribution
tests around materializePersonalizedZip and attributeArchiveAssets explicitly
handle missing prerequisites. Check for the required yucp_coupling.so artifact
and usable Docker before running these cases, using test.skipIf(...) or an
equivalent upfront assertion with an actionable message; avoid allowing setup
failures to surface only as opaque docker run errors.
yucp_coupling/guard.c (1)

807-842: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Worst case is 12 full embed+extract passes over the image.

When no profile verifies, this loop runs WM_IMG_PROFILE_COUNT iterations, each doing a full-image memcpy plus forward/inverse DCT over every selected block, then another full extraction pass. At WM_IMG_MAX_PIXELS scale that is a substantial per-asset cost on the materialization path. Consider ordering profiles by expected success and/or bounding the retry count for large images.

Note: the Cppcheck "memory leak"/"syntax error" hints on Lines 829/836 are parse artifacts of the multi-line call — original_px is released in the done 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 `@yucp_coupling/guard.c` around lines 807 - 842, Reduce the worst-case retry
cost in the profile verification loop around wm_img_embed_px and
wm_img_extract_px by trying profiles in expected-success order and/or limiting
retries for large images. Preserve verification correctness by retaining the
successful profile path and ensuring the bounded loop still uses the existing
original_px restoration and done cleanup.

Source: Linters/SAST tools

yucp_coupling/build.sh (1)

62-71: 📐 Maintainability & Code Quality | 🔵 Trivial

The committed SHA-256 record depends on toolchain reproducibility.

The script overwrites yucp_coupling.sha256 on every build with no comparison against the committed value, and nothing pins the compiler or enables deterministic flags. Anyone rebuilding with a different cc will produce a different hash than the one the runtime validation expects. Consider pinning the build in a container image and adding a verify mode that fails when the produced hash differs from the recorded one.

🤖 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 `@yucp_coupling/build.sh` around lines 62 - 71, The build script’s SHA-256
output is not reproducibility-checked and can overwrite the committed record
with compiler-dependent results. Update the build flow around HASH and SHA_FILE
to support a verification mode that compares the produced hash with the existing
recorded value and fails on mismatch, while preserving normal build output; also
make the compilation deterministic by using pinned toolchain/container settings
or deterministic compiler flags already appropriate to this script.
🤖 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 `@deploy/initialize-local-master-credential.sh`:
- Around line 16-26: Update the existing-target branch in
initialize-local-master-credential.sh to inspect the credential JSON for the
requested epoch before exiting successfully. Preserve the invalid path and
permission checks, but when ${epoch} is absent, generate the new credential and
merge it with all existing JSON keys, replacing the current single-entry output
path; only exit 0 when the requested epoch is already present.

In `@src/attribution.ts`:
- Around line 158-176: Update the sandboxed worker flow around child.exited and
the stdout/stderr Response reads to enforce a timeout and bounded output. Add a
deadline that kills the child when it expires, ensure the caller receives a
timeout failure, and read/truncate worker output to the configured maximum
before buffering or parsing; preserve existing exit-code errors and parseResults
behavior for successful, bounded responses.

In `@src/keyBroker.ts`:
- Around line 123-167: Update normalizeFiles to track the previously normalized
path as each file is processed, and compare that trimmed value with the current
normalizedPath in the ordering/duplicate check. Replace the raw files[index -
1]?.normalizedPath lookup while preserving the existing compareUtf8 >= 0
rejection and path validation behavior.

In `@src/linuxCodecSandbox.ts`:
- Around line 241-267: Update the child.stdin handling in the codec execution
flow so the asynchronous stdin end/drain operation is awaited before
frame.fill(0) runs. Preserve the existing finally-based cleanup and ensure the
frame is cleared only after all input bytes have been consumed.

In `@src/linuxCodecWorker.py`:
- Around line 318-323: Update the batch-processing flow around process_file in
the top-level handler so every key in keys is zeroized even when processing an
entry raises. Ensure cleanup runs for unprocessed keys before the exception is
serialized and the handler exits, while preserving the existing sorted results
behavior on success.
- Around line 203-216: Update the nested ZIP extraction flow around
source_archive.read(info) to use a bounded read that cannot materialize more
than the permitted per-entry or remaining total byte limits, rather than
trusting info.file_size. Detect oversized decompressed output during reading,
and update total_bytes using len(data) after the bounded read; retain the
existing invalid-length validation and archive-writing behavior for entries
within both caps.

In `@src/linuxTreeSandbox.ts`:
- Around line 64-94: Update runWorker to enforce a wall-clock timeout while
awaiting the spawned Docker worker, including termination of the child process
when the timeout expires. Ensure the timeout path rejects with a clear error and
does not leave stdout, stderr, or the materialization request hanging; preserve
the existing response-size and nonzero-exit handling for workers that finish in
time.
- Around line 24-61: Update the Docker argument list returned by the
container-command builder to run as a non-root user by adding the appropriate
--user setting, and add --memory-swap 512m alongside the existing --memory 512m
limit. Preserve the existing sandbox mounts, capabilities, and other resource
restrictions.

In `@src/materializationServer.ts`:
- Around line 951-952: Update the materialization server host configuration to
default MATERIALIZATION_HEALTH_HOST to 127.0.0.1 instead of 0.0.0.0, while
preserving the existing readTrimmedEnv override so broader binding requires
explicit configuration.
- Around line 318-340: Update postJson to pass an AbortSignal to
fetchImplementation, combining the worker shutdown signal with a bounded
per-request timeout so hung control-plane calls terminate. Ensure timeout or
cancellation errors propagate through runOnce while preserving existing response
validation and JSON handling.
- Around line 471-481: Update the polling wait Promise around the abort listener
to retain the listener callback and explicitly remove it when the timeout
completes, while preserving cleanup on abort. Ensure both timer and abort paths
clear the timeout as appropriate, resolve only once, and detach the listener
from signal so repeated polling does not accumulate registrations.

In `@src/materializationService.ts`:
- Around line 395-414: Update every fetchFn request in the materialization flow,
including capability consume, manifest retrieval, per-chunk download, rendition
upload, and completion, to pass an AbortSignal.timeout value. Use the standard
request timeout for control-plane requests and a larger timeout budget for
chunk/download and upload streams, ensuring no request can hang the worker loop
indefinitely.
- Around line 1031-1073: Update loadSourceManifest to retain the complete
manifest file list alongside the protected-file validation, rather than
returning only protectedFiles. Change assembleSourceTree to iterate that full
file-list property so common and protected entries are both downloaded and
written, while preserving protected-file capability checks and existing manifest
metadata.

In `@src/rendition.ts`:
- Around line 342-354: The attribution-record construction in completeRendition
must fail when the protected-file lookup is missing instead of defaulting
materializerType to 'png' or sourceSha256 to ''. Reuse the suggested
protected-file map to perform one lookup per output, validate that the matching
source exists, and use its assetType and sourceSha256 for the record.
- Around line 306-318: Validate each codec result’s normalizedPath against the
requested protectedFiles set before using it. In the loop over
codecResults.flatMap(result => result.files), resolve or look up the result by
the validated requested path and reject or skip any path not present in that
set, including traversal segments, before path.join and readFile; use the
validated path for output access.

---

Minor comments:
In `@src/linuxAttributionWorker.py`:
- Around line 33-49: Update read_request so key bytearrays accumulated during
the candidates read are zeroized if read_exact fails partway through the
comprehension. Replace the direct comprehension with cleanup-safe accumulation,
wipe all already-read keys in the exception path, then re-raise the original
failure; preserve the existing successful return behavior and trailing-byte
validation.

In `@src/linuxCodecSandbox.ts`:
- Around line 188-223: Update the Docker arguments in the sandbox command around
PINNED_PYTHON_IMAGE to run as a non-root user and add a memory-swap limit
matching the existing 256m memory limit. Extract the shared isolation arguments
into a reusable helper and apply it in both linuxCodecSandbox and
linuxTreeSandbox so their security settings remain aligned.

In `@src/linuxTreeSandbox.ts`:
- Around line 78-79: Update the request-writing flow around child.stdin.write
and child.stdin.end so stdin closure is awaited before awaiting child.exited.
Ensure the final serialized request is fully flushed before worker completion is
observed, preserving the existing request and exit handling.

In `@src/materializationServer.test.ts`:
- Around line 127-156: Update the boundary test around
createMaterializationWorker so its name and capability length accurately reflect
the 1 MiB value currently exercised, or add a separate 2 MiB case that verifies
the intended maximum behavior while accounting for readJsonResponse’s
response-size limit before requireBase64Url validation.

In `@src/materializationServer.ts`:
- Around line 618-628: Update the request handling around readBoundedRequestJson
and input.keyBroker.prepareSubject to validate buyerId, creatorId, jobId, and
productId as actual non-empty strings before calling the broker. Remove String
coercion, reject arrays, objects, and other non-string values at the HTTP
boundary using the existing request error response pattern, while preserving
numeric keyEpoch validation and the successful prepareSubject flow.

In `@src/materializationService.ts`:
- Around line 1462-1473: Normalize and validate input.materializerId once at the
start of materializeCapabilityJob, then reuse that normalized value throughout
the job. Replace the raw input.materializerId used by the upload-ticket and
completion endpoint flows, as well as the consumeCapability call, while
preserving the existing materializerId validation behavior.

In `@yucp_coupling/build.sh`:
- Around line 41-60: Update the build command using LINK_FLAGS and SODIUM_CFLAGS
so path-bearing version-script and include arguments remain separate quoted
words when $HERE or $SODIUM_PREFIX contains spaces. Preserve the existing linker
flags and vendored static archive usage, and avoid relying on word splitting of
those variables.

In `@yucp_coupling/guard.c`:
- Around line 1997-1998: The placement-epoch loops in xg_0124 and xg_0125 are
hardcoded to a single iteration; either replace the bound with the intended
shared epoch count on both encoder and decoder paths, or remove the loops and
pass 0u directly. Keep both sides synchronized so published assets remain
recoverable.
- Around line 1710-1724: Replace the valid_slots allocation in the slot-capacity
handling with calloc so the multiplication by sizeof(size_t) is
overflow-checked, while preserving the existing capacity validation, cleanup,
and failure return behavior.

---

Nitpick comments:
In `@src/attribution.test.ts`:
- Around line 118-151: Add a negative case in the attribution test around
attributeArchiveAssets, using a mismatched candidateFileKeys entry or
unwatermarked PNG, and assert the returned attribution result has matched:
false. Keep the existing positive attribution assertions unchanged and ensure
the case verifies that an unrelated candidate is not attributed.
- Around line 60-124: Make the server attribution tests around
materializePersonalizedZip and attributeArchiveAssets explicitly handle missing
prerequisites. Check for the required yucp_coupling.so artifact and usable
Docker before running these cases, using test.skipIf(...) or an equivalent
upfront assertion with an actionable message; avoid allowing setup failures to
surface only as opaque docker run errors.

In `@src/attribution.ts`:
- Around line 66-85: Update parseResults to validate every payload.results entry
before returning it, confirming assetPath and assetType have the expected shapes
and matched is a valid boolean or expected value. Reject any malformed entry
with the existing invalid-response error, while preserving the current schema,
length, and credentialEnvironment checks.
- Around line 123-157: Update the Docker argument list in the Bun.spawn
invocation to add --user with a fixed non-root UID/GID before
PINNED_PYTHON_IMAGE, ensuring the worker container runs without root privileges
while preserving the existing hardening options.

In `@src/keyBroker.test.ts`:
- Around line 1-97: The key broker tests lack regression coverage for the
duplicate/ordering edge case in normalizeFiles. Add a test that supplies
protectedFiles with paths differing only by incidental whitespace, such as
“Assets/x.png ” followed by “Assets/x.png”, and assert that the derivation flow
rejects the input after normalization; keep the test focused on this duplicate
detection behavior.

In `@src/linuxCodecSandbox.test.ts`:
- Around line 24-27: Add negative test cases alongside the existing
classifyLinuxCodecFailure('not-json') assertion in linuxCodecSandbox.test.ts for
an empty string, valid JSON with an unrecognized errorType, and valid JSON with
a mismatched schemaVersion. Assert that each input returns
MATERIALIZATION_CODEC_SANDBOX_FAILED through the non-JSON fallback path.

In `@src/linuxCodecSandbox.ts`:
- Around line 62-100: Update classifyLinuxCodecFailure to avoid identifying
uncarryable assets solely through the exact parsed.error text. Use a stable
structured discriminant emitted by the worker, such as parsed.errorReason ===
'uncarryable', to return MATERIALIZATION_CODEC_ASSET_UNCARRYABLE, while
retaining native encode classification for other encode failures and the
existing fallback behavior.

In `@src/linuxRendition.realtest.ts`:
- Around line 112-123: Create a module-level helper in
linuxRendition.realtest.ts that resolves the runtimePath, reads and normalizes
the expected digest, verifies the binary hash, and returns the verified runtime
path. Replace the repeated runtimePath and digest-resolution blocks in all four
rendition tests with calls to this helper, ensuring each test validates the
runtime binary before using it.

In `@src/linuxTreeSandbox.ts`:
- Around line 80-87: Update the output handling around child.stdout and
child.stderr so response size is enforced while streaming, before the complete
content is materialized. Preserve the existing MAX_RESPONSE_BYTES rejection
behavior and ensure the bounded output remains available for subsequent
processing alongside exitCode.
- Around line 143-179: Update buildPersonalizedTreeArchive to read the generated
rendition at renditionPath and compute its SHA-256 digest on the host before
returning. Compare the computed digest with result.renditionSha256 and throw the
existing invalid-result error on mismatch; return the worker-reported digest
only after verification succeeds.

In `@src/materializationServer.test.ts`:
- Around line 365-376: Move the source-string architecture assertions for
“/v2/materializations/renditions” and “serviceSecret” out of the handler test
and into the existing architecture guard suite in serverArchitecture.test.ts.
Keep the legacy request status assertion in the behavioral test, and preserve
both forbidden-string checks in their centralized location.

In `@src/materializationServer.ts`:
- Around line 129-139: Update the Docker subprocess handling around child.spawn
and the Promise.all that awaits child.exited and stdout so the piped
child.stderr stream is also drained, preventing verbose failures from blocking.
Preserve the existing stdout and exit-code behavior; alternatively configure
stderr as ignored if diagnostics are intentionally discarded.
- Around line 642-648: Reduce the attribution request body limit used by
readBoundedRequestJson and add an in-flight request semaphore around the
attribution handling flow beginning near derivedKeys and failureCode. Acquire
the semaphore before buffering the request, reject or fail requests when
capacity is unavailable, and release it on every completion path, including
errors, while preserving the existing attribution failure handling.

In `@src/materializationService.test.ts`:
- Around line 322-357: The request handler in the test’s URL-matching mock
should only return the capability-consume payload for the expected consume
endpoint. Add an explicit assertion for that URL or branch, and return a 404
response for all unmatched URLs so incorrect requests cannot be accepted.
- Around line 178-215: Extend the sourceManifest fixture with a file classified
as 'common', including its chunk metadata and content, then update the source
assembly assertions to verify that file appears in the assembled tree. Keep the
existing protected-file coverage intact while exercising the common-content path
used by loadSourceManifest.

In `@src/materializationService.ts`:
- Around line 1094-1108: Remove the pre-emptive unlink operations from the cache
reuse logic around hashFile, including the catch-path unlink, and let the
existing atomic rename-into-place operation replace stale or invalid entries.
Preserve returning destination for matching cached chunks while ensuring
concurrent readers are not exposed to a missing cache entry.

In `@src/rendition.test.ts`:
- Around line 16-59: Extract the duplicate crc32, pngChunk, and makeGrayPng
helpers from rendition.test.ts into a shared test fixture module, then import
and reuse them in both rendition.test.ts and linuxRendition.realtest.ts.
Preserve their current behavior and remove the duplicate local definitions so
the suites stay synchronized.

In `@src/rendition.ts`:
- Around line 263-298: Update the LinuxCodecRequest construction in the codec
batch loop so arguments reflects the actual execution path: remove the cosmetic
docker command-line branch when runtimePath is provided, and align the request
metadata with the direct runtimePath-based sandbox invocation while preserving
the injected runCodec behavior.

In `@src/serverArchitecture.test.ts`:
- Around line 41-49: Extend the production source collection used by the server
architecture test to include linuxCodecWorker.py and linuxAttributionWorker.py,
ensuring the existing forbidden-string assertions scan these Python workers
along with the current sources. Keep the forbidden list and assertion behavior
unchanged.

In `@yucp_coupling/build.sh`:
- Around line 62-71: The build script’s SHA-256 output is not
reproducibility-checked and can overwrite the committed record with
compiler-dependent results. Update the build flow around HASH and SHA_FILE to
support a verification mode that compares the produced hash with the existing
recorded value and fails on mismatch, while preserving normal build output; also
make the compilation deterministic by using pinned toolchain/container settings
or deterministic compiler flags already appropriate to this script.

In `@yucp_coupling/guard.c`:
- Around line 807-842: Reduce the worst-case retry cost in the profile
verification loop around wm_img_embed_px and wm_img_extract_px by trying
profiles in expected-success order and/or limiting retries for large images.
Preserve verification correctness by retaining the successful profile path and
ensuring the bounded loop still uses the existing original_px restoration and
done cleanup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

Comment thread deploy/initialize-local-master-credential.sh
Comment thread src/attribution.ts Outdated
Comment thread src/keyBroker.ts
Comment thread src/linuxCodecSandbox.ts Outdated
Comment thread src/linuxCodecWorker.py Outdated
Comment thread src/materializationServer.ts Outdated
Comment thread src/materializationService.ts Outdated
Comment thread src/materializationService.ts
Comment thread src/rendition.ts
Comment thread src/rendition.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 13

Caution

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

⚠️ Outside diff range comments (2)
yucp_coupling/guard.c (2)

1874-1897: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

40-iteration bisection with pow() over every vertex, repeated per embed attempt.

The inner loop evaluates pow(uu, gamma) for every vertex in the bin on each of 40 bisection steps, so a single embed costs ~40·N pow() calls. xg_0124 then runs up to 3 attempts × 2 carrier ranks of embed plus a verification decode each time, and the codec sandbox is --cpus 1. For a multi-million-vertex FBX this dominates materialization latency.

Cheap mitigations: exit the bisection early once fabs(gm - target) is within tolerance (typically ~10 iterations rather than 40), and hoist the per-bin u values into the array once instead of recomputing (pr[r].k - blo) / span on every iteration.

🤖 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 `@yucp_coupling/guard.c` around lines 1874 - 1897, Optimize the exponent
bisection in the embedding loop by precomputing each bin’s clamped normalized u
values before iterating on gamma, then reuse them for every mean calculation
instead of recomputing from pr[r].k. Add an early exit when fabs(gm - target)
reaches an appropriate tolerance, while preserving the existing target selection
and bisection behavior for bins that do not converge early.

325-338: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Keep a v2 decode path for mesh watermarks yucp_coupling/guard.c:325-338

The image profile reshuffle is compatible (WM_IMG_PROFILES[0] matches the old fixed channel/coeff/qstep setup and WM_EPOCH stays 0), but yucp.wm.mesh.embed.v3 changes the HKDF domain and makes existing mesh marks undecodable. Add a v2 fallback in the decoder if old FBX watermarks must survive upgrades.

🤖 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 `@yucp_coupling/guard.c` around lines 325 - 338, Preserve backward
compatibility for existing mesh watermarks by adding a decoder fallback that
derives the mesh seed with the legacy “yucp.wm.mesh.embed.v2” HKDF domain when
the current v3 decode path fails. Keep the existing v3 path as the primary
behavior and limit the fallback to mesh decoding; update the relevant decoder
flow rather than changing wm_basis_seed’s current profile behavior.
♻️ Duplicate comments (3)
src/linuxTreeSandbox.ts (2)

64-94: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

No timeout on the spawned Docker worker.

Unresolved from a prior review: runWorker awaits child.exited with no bound, so a stalled docker run hangs the materialization request indefinitely. Bun.spawn's built-in timeout/killSignal options can address this directly.

⏱️ Proposed fix
   const child = Bun.spawn(treeWorkerArguments(input), {
     stderr: 'pipe',
     stdin: 'pipe',
     stdout: 'pipe',
+    timeout: 60_000,
+    killSignal: 'SIGKILL',
   });
🤖 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/linuxTreeSandbox.ts` around lines 64 - 94, Update runWorker to configure
a finite Bun.spawn timeout and appropriate killSignal for the Docker worker,
using the existing worker lifecycle options so stalled docker runs are
terminated and child.exited cannot hang indefinitely.

17-62: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Run this container as non-root and cap swap.

Unresolved from a prior review: no --user is set (default Python image runs as root), and --memory-swap is omitted, letting Docker grant swap up to 2× the 512m --memory limit.

🔒 Proposed fix
     '--memory',
     '512m',
+    '--memory-swap',
+    '512m',
+    '--user',
+    '65534:65534',
     '--cpus',
     '1',
🤖 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/linuxTreeSandbox.ts` around lines 17 - 62, Update treeWorkerArguments to
run the container as a non-root user by adding the appropriate Docker --user
setting, and cap total memory including swap by setting --memory-swap to the
intended limit alongside the existing --memory 512m option. Preserve the current
container mounts, capabilities, and resource limits.
src/linuxCodecSandbox.ts (1)

241-251: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Await child.stdin.end() before clearing frame.

Same issue flagged previously for this file: FileSink.end()/write() can be asynchronous, so zeroing frame in finally immediately after write/end risks corrupting in-flight codec input.

🔒 Proposed fix
   try {
     child.stdin.write(frame);
-    child.stdin.end();
+    await child.stdin.end();
   } finally {
     frame.fill(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 `@src/linuxCodecSandbox.ts` around lines 241 - 251, Update the child stdin
write flow around Bun.spawn so it awaits child.stdin.end() before clearing
frame. Keep frame.fill(0) in the finally block, but ensure both the write and
stream finalization complete before releasing the frame contents.
🧹 Nitpick comments (17)
src/rendition.test.ts (1)

258-261: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

rejects.not.toThrow('private codec details') is a near-tautological assertion.

RenditionStageFailure always has the fixed message 'Rendition failed at a classified execution stage', so this passes regardless of leakage. Assert the message (and that cause is not serialized) explicitly instead.

♻️ Proposed tightening
       await expect(failure).rejects.toMatchObject({
         errorCode: 'MATERIALIZATION_CODEC_BATCH_FAILED',
+        message: 'Rendition failed at a classified execution stage',
       });
-      await expect(failure).rejects.not.toThrow('private codec details');
🤖 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/rendition.test.ts` around lines 258 - 261, Update the
RenditionStageFailure rejection assertions in the relevant test to verify the
exact public error message explicitly and confirm that the serialized error does
not include a cause field or private codec details. Replace the tautological
rejects.not.toThrow assertion while preserving the existing
MATERIALIZATION_CODEC_BATCH_FAILED errorCode check.
src/materializationService.test.ts (1)

178-215: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fixture has no classification: 'common' file, so the source-assembly path is untested.

Adding one common entry (with its own chunk and logicalFiles: 2) would have caught the dropped-common-files defect in assembleSourceTree, and it's the only regression guard for that path.

Also applies to: 363-375

🤖 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/materializationService.test.ts` around lines 178 - 215, The source
manifest fixture in the materialization test only covers protected files,
leaving the common-file branch of assembleSourceTree untested. Extend the
manifest’s files array with a separate classification: 'common' entry, including
its own chunk metadata and logicalFiles: 2, and provide the corresponding source
object data needed by the fixture while preserving the existing protected-file
setup.
src/materializationServer.ts (4)

129-139: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

stderr: 'pipe' is never consumed.

The stderr pipe is opened but never read, so the child can block on a full stderr buffer (bounded only by the 10s kill) and the stream is left undrained. Since the error output is unused, 'ignore' is simpler.

♻️ Drop the unused pipe
   const child = Bun.spawn(['docker', 'info', '--format', format], {
-    stderr: 'pipe',
+    stderr: 'ignore',
     stdin: 'ignore',
     stdout: 'pipe',
   });
🤖 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/materializationServer.ts` around lines 129 - 139, Update the Bun.spawn
options in the Docker info execution flow to set stderr to 'ignore' instead of
opening an unconsumed pipe. Keep the existing stdout capture, timeout, and
child.exited handling unchanged.

221-233: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Response size is only enforced after full buffering.

When the control plane replies with chunked encoding (no content-length), response.text() buffers the whole body before the MAXIMUM_RESPONSE_BYTES check, so the limit cannot prevent memory exhaustion. readBoundedRequestJson already does this correctly with an incremental reader — consider reusing that streaming approach here.

🤖 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/materializationServer.ts` around lines 221 - 233, Update readJsonResponse
to enforce MAXIMUM_RESPONSE_BYTES while reading the response stream
incrementally, rather than calling response.text() before validation. Reuse the
bounded streaming approach from readBoundedRequestJson, preserving the existing
content-length validation and oversized-response error behavior.

703-811: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Up to 4096 synchronous key derivations per attribution request block the event loop.

deriveFileKeys is called once per candidate inside a synchronous map, so a maximum-size request performs 4096 derivations with no yielding, stalling health checks and other routes on the same server. Consider lowering the candidate ceiling or batching derivations with periodic yields.

🤖 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/materializationServer.ts` around lines 703 - 811, Update the attribution
candidate processing around the synchronous candidates.map and deriveFileKeys
call to prevent up to 4096 derivations from blocking the event loop. Preserve
validation and derived-key behavior while either reducing the accepted candidate
limit or converting processing to an asynchronous loop that yields periodically
between bounded batches.

1020-1028: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Async signal handler rejection is unobserved.

shutdown returns a promise that process.once discards; if server.stop() rejects it surfaces as an unhandled rejection instead of a logged shutdown failure.

♻️ Catch and log
   const shutdown = async () => {
     abortController.abort();
-    await server.stop();
+    try {
+      await server.stop();
+    } catch (error) {
+      console.error(
+        JSON.stringify({
+          event: 'materialization.shutdown_failed',
+          materializerId: config.materializerId,
+        })
+      );
+    }
   };
🤖 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/materializationServer.ts` around lines 1020 - 1028, Update the shutdown
handler around shutdown and the SIGINT/SIGTERM process.once registrations so
rejections from server.stop() are caught and logged as shutdown failures instead
of becoming unhandled promise rejections. Preserve the existing abort and
server-stop ordering, and use the surrounding module’s established logging
mechanism.
src/serverArchitecture.test.ts (1)

25-39: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Glob *.ts is non-recursive, so nested sources escape the forbidden-token scan.

Any file under src/<subdir>/ is silently excluded, making this guardrail pass for code it should reject. Use **/*.ts.

🔧 Cover the whole tree
-      ...new Bun.Glob('*.ts').scanSync({
+      ...new Bun.Glob('**/*.ts').scanSync({
🤖 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/serverArchitecture.test.ts` around lines 25 - 39, Update the Bun.Glob
pattern in the productionSources collection to recursively match TypeScript
files throughout src and its subdirectories, while preserving the existing
test-file filters and file-reading logic.
src/materializationServer.test.ts (2)

44-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing negative cases for the OS/architecture branch.

validateDockerIsolationInfo rejects non-linux and non-x64 hosts, but no test covers that branch. Add cases for operatingSystem: 'windows' and architecture: 'aarch64'.

🤖 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/materializationServer.test.ts` around lines 44 - 69, The
validateDockerIsolationInfo tests cover rootless and cgroup failures but omit
invalid platform values. Add negative cases asserting that operatingSystem
'windows' and architecture 'aarch64' each throw the expected validation error,
while preserving the existing valid Linux x86_64 case.

363-364: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

This assertion checks zeroization, not derivation.

The handler's finally block wipes derivedKeys before this runs, so toEqual(new Uint8Array(32)) only proves the buffer was zeroed. If the intent is to verify the derived key material, capture a copy inside attributeAssets; otherwise a comment clarifying the intent avoids future confusion.

🤖 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/materializationServer.test.ts` around lines 363 - 364, Update the test
around attributeAssets to distinguish derivation from zeroization: capture a
copy of the derived key material inside attributeAssets before the handler’s
finally block wipes derivedKeys, then assert the captured copy for derivation
and retain the existing derivedKeys assertion only as the zeroization check.
src/linuxTreeWorker.py (1)

211-216: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Failures surface only as a bare exception class name.

Every distinct validation failure in this worker raises ValueError, so the caller sees ValueError with no discriminator — unlike the codec and attribution workers, which emit structured JSON. Consider emitting the same {"errorType", "error", "schemaVersion"} shape; the messages here are already static strings with no paths or secrets.

🤖 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/linuxTreeWorker.py` around lines 211 - 216, Update the __main__ exception
handler in linuxTreeWorker.py to emit structured JSON matching the codec and
attribution workers, including errorType, error, and schemaVersion fields. Use
the caught exception’s class name and message, serialize the payload to stderr,
and preserve the existing nonzero exit behavior.
yucp_coupling/build.sh (1)

41-55: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: validate yucp_coupling.exports.map alongside the libsodium precondition.

The vendored libsodium archive gets an explicit, well-messaged existence check, but the version script referenced in LINK_FLAGS does not — a missing map surfaces as a raw linker error. Also consider -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 to avoid a redefinition warning on toolchains that predefine it (Ubuntu/Debian GCC do at -O2).

♻️ Proposed change
+if [ ! -f "$HERE/yucp_coupling.exports.map" ]; then
+  echo "Missing the symbol version script" >&2
+  exit 1
+fi
 if [ ! -f "$SODIUM_PREFIX/lib/libsodium.a" ] || [ ! -d "$SODIUM_PREFIX/include" ]; then
🤖 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 `@yucp_coupling/build.sh` around lines 41 - 55, Validate the version-script
file referenced by LINK_FLAGS alongside the existing SODIUM_PREFIX checks in the
build precondition, emitting a clear error and exiting before linking when it is
missing. Also update the CFLAGS assignment to undefine any pre-existing
FORTIFY_SOURCE definition before setting it to 2, avoiding redefinition
warnings.
src/attribution.test.ts (2)

222-237: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No negative-path coverage.

Both tests only assert successful attribution. The security-relevant property of this module is that it does not attribute — an unrelated asset, a wrong file key, or a candidate whose attributionTokenHash doesn't match should all yield matched: false. Worth adding at least a wrong-key case, since that exercises the hash re-verification at linuxAttributionWorker.py Line 162.

🤖 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/attribution.test.ts` around lines 222 - 237, The attribution tests only
cover successful matches and need negative-path coverage. Extend the tests
around attributeArchiveAssets to include at least a wrong candidateFileKeys/file
key case, asserting the result is not attributed with matched: false; also cover
an unrelated asset or mismatched attributionTokenHash if supported by the
existing fixtures, exercising the hash re-verification behavior.

61-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move this Docker/native-runtime integration test out of the default suite.

This test needs Docker and yucp_coupling/out/linux-x64/Release/yucp_coupling.so, but it lives in src/attribution.test.ts and will run under bun test. The repo already uses .realtest.ts for this kind of integration coverage, so rename it or add a runtime skip guard.

🤖 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/attribution.test.ts` around lines 61 - 72, Move the Docker/native-runtime
test identified by “identifies a personalized output through its v2 derivation
record” out of the default Bun test suite by placing it in a .realtest.ts file,
or add an equivalent runtime skip guard when Docker or yucp_coupling.so is
unavailable; preserve the test’s existing assertions and behavior when the
required runtime is present.
yucp_coupling/guard.c (3)

1997-2001: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The placement-epoch loops iterate exactly once.

placement_epoch < 1u here and at Line 2144 in xg_0125 means only epoch 0 is ever tried — the multi-epoch search the surrounding structure implies doesn't happen. It's consistent between encoder and decoder today, so nothing is broken, but it's a trap: bumping one bound without the other makes previously-embedded meshes silently undecodable.

Either collapse to a plain epoch-0 call, or introduce a shared WM_MESH_PLACEMENT_EPOCHS constant used by both sites so they can't drift.

♻️ Shared bound
+#define WM_MESH_PLACEMENT_EPOCHS 1u
...
-  for (uint8_t placement_epoch = 0u; placement_epoch < 1u && !verified;
+  for (uint8_t placement_epoch = 0u;
+       placement_epoch < WM_MESH_PLACEMENT_EPOCHS && !verified;
        placement_epoch++) {
🤖 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 `@yucp_coupling/guard.c` around lines 1997 - 2001, Replace the duplicated
placement-epoch bound in both the encoder loop and decoder function xg_0125 with
a shared WM_MESH_PLACEMENT_EPOCHS constant, preserving the current single-epoch
behavior by setting it to 1u. Use that constant wherever placement_epoch is
bounded so the two paths cannot drift.

1710-1724: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Use calloc for the slot array so the × sizeof(size_t) product is overflow-checked.

Line 1710 guards vl.n > SIZE_MAX / 3, which protects slot_capacity itself, but slot_capacity * sizeof(size_t) multiplies by another 8 and is unguarded. Not reachable given FBX size limits, but calloc makes it free.

Separately, worth recording as a comment: the carrier's correctness depends on wm_mesh_scalar_set_bit never changing a coordinate's finiteness (it only touches mantissa bit 12/41, never the exponent), so valid_count is identical on the encode and decode sides. That invariant is load-bearing for blind recovery and currently undocumented.

♻️ Proposed change
-  size_t* valid_slots = (size_t*)malloc(slot_capacity * sizeof(size_t));
+  size_t* valid_slots = (size_t*)calloc(slot_capacity, sizeof(size_t));
🤖 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 `@yucp_coupling/guard.c` around lines 1710 - 1724, Replace the valid_slots
allocation in the surrounding guard function with calloc so the element-count
and sizeof(size_t) multiplication are overflow-checked, while preserving the
existing failure cleanup and return behavior. Also add a concise comment
documenting that wm_mesh_scalar_set_bit only changes mantissa bits, preserves
coordinate finiteness, and therefore keeps valid_count identical during encoding
and decoding.

Source: Linters/SAST tools


1573-1600: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Group detection silently assumes same-property vertices are contiguous in vl.

Both passes advance only while vl->d[end].p == property, so if wm_collect_verts ever emits a property's vertices in more than one run, that property yields multiple groups, the size ranking shifts, and encode/decode can select different carriers — an unrecoverable watermark rather than a loud failure. Nothing in the code enforces the invariant.

The comparator itself is fine: order is unique so it's a strict total order, making the qsort result deterministic despite qsort not being stable.

Consider asserting the invariant (or grouping via a property→index map) so a future change to wm_collect_verts can't break attribution silently.

🤖 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 `@yucp_coupling/guard.c` around lines 1573 - 1600, Update
wm_vlist_keep_mesh_rank to validate that each property appears in only one
contiguous run across vl->d before ranking groups, and fail loudly if a property
reappears after another property. Apply the validation consistently with the
existing grouping passes, preserving the current comparator and deterministic
ordering; alternatively, replace contiguous-run grouping with property-based
aggregation if that is the established design.
src/attribution.ts (1)

123-157: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Container hardening is strong but the worker still runs as the image's default user, and mount specs are built by string interpolation.

Two smaller gaps in an otherwise well-locked-down invocation:

  • No --user, so the Python worker runs as root inside the container. With --cap-drop ALL, --read-only, and no-new-privileges the blast radius is small, but --user 65534:65534 costs nothing here since every mount is read-only and scratch is a tmpfs.
  • type=bind,source=${input.runtimePath},... is caller-supplied and unescaped. There's no shell (argv array), but a , or = in the path silently corrupts the mount option list. Prefer validating runtimePath (absolute, no ,) before use.
🔒️ Proposed change
         '--rm',
         '--interactive',
+        '--user',
+        '65534:65534',
         '--network',
         'none',
🤖 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/attribution.ts` around lines 123 - 157, Harden the Bun.spawn Docker
invocation by adding the fixed non-root user 65534:65534 before the image
argument. Validate input.runtimePath before constructing its bind-mount
specification, requiring an absolute path and rejecting paths containing commas
or other characters that would corrupt Docker mount options; preserve the
existing read-only mount behavior.
🤖 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 @.env.example:
- Line 10: Update the MATERIALIZATION_RUNTIME_LIBRARY_PATH example to use the
current ca-coupling checkout layout and point to the built
yucp_coupling/out/linux-x64/Release/yucp_coupling.so artifact instead of the
obsolete /opt/yucp/coupling path.

In `@deploy/run-local-wsl-materializer.sh`:
- Around line 26-34: Export MATERIALIZATION_WORK_ROOT using the resolved
work_root value before invoking the local materializer server, ensuring the
server uses the same directory that was created and locked. Update the launch
flow around the work_root initialization and flock setup without changing the
existing lock behavior.

In `@src/attribution.ts`:
- Around line 70-84: Validate each element of payload.results before returning
from the response parser: require matched to be boolean, verify assetPath
matches the normalized requested asset path from attributeArchiveAssets, and
ensure attributionId is one of that asset’s submitted candidates. Reject the
response with the existing invalid-response error when any element fails
validation, while preserving the existing schema, length, and
credentialEnvironment checks.

In `@src/linuxAttributionWorker.py`:
- Around line 135-172: Update read_request to reject requests whose assets and
candidates counts exceed a defined maximum product before entering the nested
processing loops. Preserve existing validation and error behavior, and ensure
the guard prevents the assets × candidates native-decoding work from starting
for oversized requests.

In `@src/linuxCodecSandbox.ts`:
- Around line 188-223: Harden the Docker invocation in the Linux codec sandbox
by adding a non-root `--user` setting and explicitly limiting `--memory-swap` to
the same 256m value as `--memory`. Update the `arguments` list used by the
sandbox container while preserving the existing resource and security options.
- Around line 241-245: Add a finite wall-clock timeout and appropriate
killSignal to the Bun.spawn options in the Docker launch flow around child,
ensuring stalled docker run processes are terminated so child.exited cannot
remain pending indefinitely.
- Around line 17-23: Update runLinuxCodecSandbox() to pass the request’s
environment to Bun.spawn as an explicit minimal env, preserving environment
isolation while including at least PATH. Use the environment configured by
createLinuxCodecRequest() rather than allowing the docker process to inherit the
server environment.

In `@src/linuxCodecWorker.py`:
- Around line 297-303: Update the output handling near the return payload to
hash the file incrementally instead of reading it entirely into output_bytes.
Reuse the streaming digest pattern from linuxTreeWorker.py, accumulating the
byte count and SHA-256 digest while reading bounded chunks, then populate
outputBytes and outputSha256 from those streaming results while preserving the
existing attributionTokenHex and normalizedPath values.
- Around line 220-241: The entry staging flow around entry_input, entry_output,
and couple_path can exceed the codec sandbox’s 16 MiB /tmp capacity for accepted
100 MiB entries. Remove the redundant entry_input write/copy and write data
directly to entry_output, or configure the scratch directory on storage with
sufficient capacity, while preserving couple_path decoding and coupled_entries
handling.

In `@src/materializationServer.test.ts`:
- Around line 127-156: Update the capability fixture in the test "accepts the
largest capability permitted by the protected-file contract" to use the actual
MAXIMUM_CAPABILITY_BYTES limit of 2 MiB, so the test exercises the advertised
boundary while preserving its existing assertions and setup.

In `@src/rendition.ts`:
- Line 251: Enforce the computed required flag in the materialization handling
around the file entries at this location and the corresponding 306–309 flow.
When an asset is marked required and its materialization result is uncarryable,
throw MATERIALIZATION_CODEC_ASSET_UNCARRYABLE instead of skipping it; preserve
the existing skip behavior for optional assets.

In `@yucp_coupling/guard.c`:
- Around line 2010-2033: Update the retry loop around wm_mesh_run so every
attempt starts from pristine mesh data rather than the vertices modified by a
prior attempt. Re-parse or restore br from b0 at the beginning of each attempt
while preserving the existing carrier_index and verification flow.
- Around line 478-491: Update WM_IMG_PROFILES to prevent channel 3 profiles from
being selected for RGBA data with constant alpha. Either remove the channel-3
entries entirely or gate their use on detecting genuinely non-constant source
alpha before encoding; preserve the existing colour-channel profile selection
and self-verification behavior.

---

Outside diff comments:
In `@yucp_coupling/guard.c`:
- Around line 1874-1897: Optimize the exponent bisection in the embedding loop
by precomputing each bin’s clamped normalized u values before iterating on
gamma, then reuse them for every mean calculation instead of recomputing from
pr[r].k. Add an early exit when fabs(gm - target) reaches an appropriate
tolerance, while preserving the existing target selection and bisection behavior
for bins that do not converge early.
- Around line 325-338: Preserve backward compatibility for existing mesh
watermarks by adding a decoder fallback that derives the mesh seed with the
legacy “yucp.wm.mesh.embed.v2” HKDF domain when the current v3 decode path
fails. Keep the existing v3 path as the primary behavior and limit the fallback
to mesh decoding; update the relevant decoder flow rather than changing
wm_basis_seed’s current profile behavior.

---

Duplicate comments:
In `@src/linuxCodecSandbox.ts`:
- Around line 241-251: Update the child stdin write flow around Bun.spawn so it
awaits child.stdin.end() before clearing frame. Keep frame.fill(0) in the
finally block, but ensure both the write and stream finalization complete before
releasing the frame contents.

In `@src/linuxTreeSandbox.ts`:
- Around line 64-94: Update runWorker to configure a finite Bun.spawn timeout
and appropriate killSignal for the Docker worker, using the existing worker
lifecycle options so stalled docker runs are terminated and child.exited cannot
hang indefinitely.
- Around line 17-62: Update treeWorkerArguments to run the container as a
non-root user by adding the appropriate Docker --user setting, and cap total
memory including swap by setting --memory-swap to the intended limit alongside
the existing --memory 512m option. Preserve the current container mounts,
capabilities, and resource limits.

---

Nitpick comments:
In `@src/attribution.test.ts`:
- Around line 222-237: The attribution tests only cover successful matches and
need negative-path coverage. Extend the tests around attributeArchiveAssets to
include at least a wrong candidateFileKeys/file key case, asserting the result
is not attributed with matched: false; also cover an unrelated asset or
mismatched attributionTokenHash if supported by the existing fixtures,
exercising the hash re-verification behavior.
- Around line 61-72: Move the Docker/native-runtime test identified by
“identifies a personalized output through its v2 derivation record” out of the
default Bun test suite by placing it in a .realtest.ts file, or add an
equivalent runtime skip guard when Docker or yucp_coupling.so is unavailable;
preserve the test’s existing assertions and behavior when the required runtime
is present.

In `@src/attribution.ts`:
- Around line 123-157: Harden the Bun.spawn Docker invocation by adding the
fixed non-root user 65534:65534 before the image argument. Validate
input.runtimePath before constructing its bind-mount specification, requiring an
absolute path and rejecting paths containing commas or other characters that
would corrupt Docker mount options; preserve the existing read-only mount
behavior.

In `@src/linuxTreeWorker.py`:
- Around line 211-216: Update the __main__ exception handler in
linuxTreeWorker.py to emit structured JSON matching the codec and attribution
workers, including errorType, error, and schemaVersion fields. Use the caught
exception’s class name and message, serialize the payload to stderr, and
preserve the existing nonzero exit behavior.

In `@src/materializationServer.test.ts`:
- Around line 44-69: The validateDockerIsolationInfo tests cover rootless and
cgroup failures but omit invalid platform values. Add negative cases asserting
that operatingSystem 'windows' and architecture 'aarch64' each throw the
expected validation error, while preserving the existing valid Linux x86_64
case.
- Around line 363-364: Update the test around attributeAssets to distinguish
derivation from zeroization: capture a copy of the derived key material inside
attributeAssets before the handler’s finally block wipes derivedKeys, then
assert the captured copy for derivation and retain the existing derivedKeys
assertion only as the zeroization check.

In `@src/materializationServer.ts`:
- Around line 129-139: Update the Bun.spawn options in the Docker info execution
flow to set stderr to 'ignore' instead of opening an unconsumed pipe. Keep the
existing stdout capture, timeout, and child.exited handling unchanged.
- Around line 221-233: Update readJsonResponse to enforce MAXIMUM_RESPONSE_BYTES
while reading the response stream incrementally, rather than calling
response.text() before validation. Reuse the bounded streaming approach from
readBoundedRequestJson, preserving the existing content-length validation and
oversized-response error behavior.
- Around line 703-811: Update the attribution candidate processing around the
synchronous candidates.map and deriveFileKeys call to prevent up to 4096
derivations from blocking the event loop. Preserve validation and derived-key
behavior while either reducing the accepted candidate limit or converting
processing to an asynchronous loop that yields periodically between bounded
batches.
- Around line 1020-1028: Update the shutdown handler around shutdown and the
SIGINT/SIGTERM process.once registrations so rejections from server.stop() are
caught and logged as shutdown failures instead of becoming unhandled promise
rejections. Preserve the existing abort and server-stop ordering, and use the
surrounding module’s established logging mechanism.

In `@src/materializationService.test.ts`:
- Around line 178-215: The source manifest fixture in the materialization test
only covers protected files, leaving the common-file branch of
assembleSourceTree untested. Extend the manifest’s files array with a separate
classification: 'common' entry, including its own chunk metadata and
logicalFiles: 2, and provide the corresponding source object data needed by the
fixture while preserving the existing protected-file setup.

In `@src/rendition.test.ts`:
- Around line 258-261: Update the RenditionStageFailure rejection assertions in
the relevant test to verify the exact public error message explicitly and
confirm that the serialized error does not include a cause field or private
codec details. Replace the tautological rejects.not.toThrow assertion while
preserving the existing MATERIALIZATION_CODEC_BATCH_FAILED errorCode check.

In `@src/serverArchitecture.test.ts`:
- Around line 25-39: Update the Bun.Glob pattern in the productionSources
collection to recursively match TypeScript files throughout src and its
subdirectories, while preserving the existing test-file filters and file-reading
logic.

In `@yucp_coupling/build.sh`:
- Around line 41-55: Validate the version-script file referenced by LINK_FLAGS
alongside the existing SODIUM_PREFIX checks in the build precondition, emitting
a clear error and exiting before linking when it is missing. Also update the
CFLAGS assignment to undefine any pre-existing FORTIFY_SOURCE definition before
setting it to 2, avoiding redefinition warnings.

In `@yucp_coupling/guard.c`:
- Around line 1997-2001: Replace the duplicated placement-epoch bound in both
the encoder loop and decoder function xg_0125 with a shared
WM_MESH_PLACEMENT_EPOCHS constant, preserving the current single-epoch behavior
by setting it to 1u. Use that constant wherever placement_epoch is bounded so
the two paths cannot drift.
- Around line 1710-1724: Replace the valid_slots allocation in the surrounding
guard function with calloc so the element-count and sizeof(size_t)
multiplication are overflow-checked, while preserving the existing failure
cleanup and return behavior. Also add a concise comment documenting that
wm_mesh_scalar_set_bit only changes mantissa bits, preserves coordinate
finiteness, and therefore keeps valid_count identical during encoding and
decoding.
- Around line 1573-1600: Update wm_vlist_keep_mesh_rank to validate that each
property appears in only one contiguous run across vl->d before ranking groups,
and fail loudly if a property reappears after another property. Apply the
validation consistently with the existing grouping passes, preserving the
current comparator and deterministic ordering; alternatively, replace
contiguous-run grouping with property-based aggregation if that is the
established design.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

Comment thread .env.example Outdated
Comment thread deploy/run-local-wsl-materializer.sh
Comment thread src/attribution.ts Outdated
Comment thread src/linuxAttributionWorker.py
Comment thread src/linuxCodecSandbox.ts
Comment thread src/linuxCodecWorker.py Outdated
Comment thread src/linuxCodecWorker.py Outdated
Comment thread src/materializationServer.test.ts
Comment thread src/rendition.ts
Comment thread yucp_coupling/guard.c

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@deploy/run-local-wsl-materializer.sh`:
- Around line 16-22: After the individual validations for source_proxy_port and
control_proxy_port, reject configurations where both ports are equal by emitting
an error to stderr and exiting with status 64. Add this check before either
socat instance starts, while preserving the existing range validation behavior.
- Around line 12-15: Update the windows_host validation in
run-local-wsl-materializer.sh to validate each IPv4 octet is within 0–255, not
merely one or more digits. Keep invalid addresses on the existing error path
that prints the diagnostic and exits with status 64.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 384c2b63-c0e6-4ec0-b400-19365d06343a

📥 Commits

Reviewing files that changed from the base of the PR and between 9aaaa6f and 63c978a.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • deploy/prepare-local-wsl-materializer.sh
  • deploy/run-local-wsl-materializer.sh
  • deploy/run-local-wsl-materializer.test.ts
  • deploy/yucp-materializer.service
  • package.json
  • src/env.test.ts
  • src/linuxRendition.realtest.ts
  • src/materializationServer.test.ts
  • src/materializationServer.ts
  • src/materializationService.test.ts
  • src/materializationService.ts
  • src/serverArchitecture.test.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • deploy/prepare-local-wsl-materializer.sh
  • package.json
  • src/serverArchitecture.test.ts
  • deploy/yucp-materializer.service
  • src/env.test.ts
  • src/materializationServer.test.ts
  • src/materializationService.test.ts
  • src/materializationService.ts
  • src/materializationServer.ts

Comment thread deploy/run-local-wsl-materializer.sh

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 63c978ae9b

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/materializationServer.ts
Comment thread src/materializationServer.ts Outdated
Comment thread src/materializationService.ts
Comment thread src/linuxCodecSandbox.ts Outdated
Comment thread src/materializationServer.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
src/materializationServer.ts (1)

571-608: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Failure reporting is bound to the shutdown signal. On SIGTERM, parentSignal is already aborted, so the FAIL_PATH post aborts immediately and the job stays leased until expiry. Consider using a detached signal (timeout only) for the terminal failure report.

🤖 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/materializationServer.ts` around lines 571 - 608, The failure-reporting
request in the catch block must not reuse the already-aborted parentSignal
during shutdown. Update the postJson call for FAIL_PATH to use a detached signal
governed only by the existing control-plane timeout, while preserving the
failure payload and cleanup behavior in the surrounding finally block.
🧹 Nitpick comments (3)
src/materializationService.ts (1)

78-102: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider sharing this helper with materializationServer.ts. postJson in src/materializationServer.ts (Lines 358-395) re-implements the same parent-signal + timeout + dispose logic; extracting one utility keeps the abort semantics from drifting.

🤖 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/materializationService.ts` around lines 78 - 102, Extract
createBoundedRequestSignal into a shared utility and update both
createBoundedRequestSignal callers, including postJson in
materializationServer.ts, to use it. Preserve the existing parent-abort
propagation, timeout reason, and dispose behavior without maintaining duplicate
implementations.
src/nativeHardening.test.ts (1)

124-132: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Guard against missing markers so the alpha-channel assertion can't pass vacuously. If either indexOf returns -1 (marker renamed), slice yields an empty/incorrect region and not.toMatch trivially passes.

💚 Proposed fix
-    const profiles = guardSource.slice(
-      guardSource.indexOf('static const wm_img_profile WM_IMG_PROFILES[]'),
-      guardSource.indexOf('`#define` WM_IMG_PROFILE_COUNT')
-    );
+    const start = guardSource.indexOf(
+      'static const wm_img_profile WM_IMG_PROFILES[]'
+    );
+    const end = guardSource.indexOf('`#define` WM_IMG_PROFILE_COUNT');
+    expect(start).toBeGreaterThanOrEqual(0);
+    expect(end).toBeGreaterThan(start);
+    const profiles = guardSource.slice(start, end);
🤖 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/nativeHardening.test.ts` around lines 124 - 132, Update the profiles
extraction in the “never writes image attribution into the alpha channel” test
to validate that both marker lookups succeed before slicing. Fail the test
explicitly when either marker is missing, then retain the existing assertion
against the extracted WM_IMG_PROFILES region.
src/serverArchitecture.test.ts (1)

140-148: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Line 145 asserts the test file contains its own literal — a tautology. It passes by construction and adds no coverage; the recursive-scan behavior is already exercised by Line 26.

🤖 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/serverArchitecture.test.ts` around lines 140 - 148, Remove the
self-referential architectureTestSource assertion for "new Bun.Glob('**/*.ts')"
from the test, since it only verifies the test file contains its own literal.
Keep the serverSource environment-host assertion and the existing recursive-scan
coverage unchanged.
🤖 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 `@deploy/initialize-local-master-credential.sh`:
- Around line 30-58: Serialize the entire credential read–merge–rename
transaction in the initialization flow, using a lock associated with target so
concurrent runs cannot overwrite epoch rotations. Replace the shared
`${target}.tmp` temporary path with a unique per-process temporary file, then
atomically rename it while the lock is held and ensure cleanup and lock release
on success or failure.

In `@src/linuxCodecSandbox.ts`:
- Around line 226-228: Update the output-directory setup before
createLinuxCodecRequest and Docker spawning so the recursively created codec
output tree is writable by uid 65534, either by applying sandbox-accessible
permissions during creation or by chowning the directory afterward; preserve the
existing request validation flow.

In `@src/linuxCodecWorker.py`:
- Around line 60-70: Move the trailing-byte validation using
sys.stdin.buffer.read(1) inside the existing try block in read_request(), so a
ValueError for extra frame bytes triggers the current key-zeroization except
handler. Preserve the existing trailing-bytes error and successful return
behavior.

In `@src/sandboxIsolation.ts`:
- Line 7: Update the PATH assignment in the sandbox environment configuration to
always use the fixed DEFAULT_PATH allowlist instead of inheriting
environment.PATH; ensure sandbox subprocesses cannot resolve commands through
user-writable directories or empty path components.

---

Outside diff comments:
In `@src/materializationServer.ts`:
- Around line 571-608: The failure-reporting request in the catch block must not
reuse the already-aborted parentSignal during shutdown. Update the postJson call
for FAIL_PATH to use a detached signal governed only by the existing
control-plane timeout, while preserving the failure payload and cleanup behavior
in the surrounding finally block.

---

Nitpick comments:
In `@src/materializationService.ts`:
- Around line 78-102: Extract createBoundedRequestSignal into a shared utility
and update both createBoundedRequestSignal callers, including postJson in
materializationServer.ts, to use it. Preserve the existing parent-abort
propagation, timeout reason, and dispose behavior without maintaining duplicate
implementations.

In `@src/nativeHardening.test.ts`:
- Around line 124-132: Update the profiles extraction in the “never writes image
attribution into the alpha channel” test to validate that both marker lookups
succeed before slicing. Fail the test explicitly when either marker is missing,
then retain the existing assertion against the extracted WM_IMG_PROFILES region.

In `@src/serverArchitecture.test.ts`:
- Around line 140-148: Remove the self-referential architectureTestSource
assertion for "new Bun.Glob('**/*.ts')" from the test, since it only verifies
the test file contains its own literal. Keep the serverSource environment-host
assertion and the existing recursive-scan coverage unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 089dc174-fd00-4879-99eb-2b067b9d09b0

📥 Commits

Reviewing files that changed from the base of the PR and between 63c978a and 5fbb079.

⛔ Files ignored due to path filters (1)
  • yucp_coupling/out/linux-x64/Release/yucp_coupling.so is excluded by !**/*.so
📒 Files selected for processing (25)
  • .env.example
  • deploy/initialize-local-master-credential.sh
  • deploy/run-local-wsl-materializer.sh
  • deploy/run-local-wsl-materializer.test.ts
  • src/attribution.test.ts
  • src/attribution.ts
  • src/keyBroker.test.ts
  • src/keyBroker.ts
  • src/linuxAttributionWorker.py
  • src/linuxCodecSandbox.test.ts
  • src/linuxCodecSandbox.ts
  • src/linuxCodecWorker.py
  • src/linuxTreeSandbox.ts
  • src/materializationServer.test.ts
  • src/materializationServer.ts
  • src/materializationService.test.ts
  • src/materializationService.ts
  • src/nativeHardening.test.ts
  • src/rendition.test.ts
  • src/rendition.ts
  • src/sandboxIsolation.ts
  • src/serverArchitecture.test.ts
  • yucp_coupling/build.sh
  • yucp_coupling/guard.c
  • yucp_coupling/out/linux-x64/Release/yucp_coupling.sha256
🚧 Files skipped from review as they are similar to previous changes (12)
  • yucp_coupling/out/linux-x64/Release/yucp_coupling.sha256
  • src/keyBroker.test.ts
  • src/materializationService.test.ts
  • src/linuxTreeSandbox.ts
  • src/rendition.ts
  • src/attribution.test.ts
  • yucp_coupling/build.sh
  • src/materializationServer.test.ts
  • src/keyBroker.ts
  • src/attribution.ts
  • src/linuxAttributionWorker.py
  • yucp_coupling/guard.c

Comment thread deploy/initialize-local-master-credential.sh
Comment thread src/linuxCodecSandbox.ts Outdated
Comment thread src/linuxCodecWorker.py Outdated
Comment thread src/sandboxIsolation.ts Outdated

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5fbb079dd6

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/linuxCodecSandbox.ts
Comment thread src/linuxTreeWorker.py Outdated
Comment thread src/keyBroker.ts Outdated
Comment thread src/materializationServer.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/materializationBuilds.ts (1)

11-16: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Python test-exclusion pattern only matches suffix convention.

EXCLUDED_SOURCE_FILE_PATTERNS excludes _test.py$ but not the common pytest prefix convention (test_*.py). If a future Python test file is added using the prefix style, it would be included in the helper digest, causing the build identity to change on test-only edits.

♻️ Suggested broadening
 const EXCLUDED_SOURCE_FILE_PATTERNS = [
   /\.test\.tsx?$/,
   /\.realtest\.tsx?$/,
   /_test\.py$/,
+  /^test_.*\.py$/,
   /\.pyc$/,
 ];
🤖 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/materializationBuilds.ts` around lines 11 - 16, Update
EXCLUDED_SOURCE_FILE_PATTERNS to also exclude Python files using the pytest
prefix convention, matching test_*.py while preserving the existing _test.py
exclusion.
src/materializationBuilds.test.ts (1)

15-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Solid path-aware hashing coverage, but exclusion-path branches are untested.

The test thoroughly proves order-independence and that most implementation inputs affect the helper/codec/runtime digests. However, two exclusion-sensitive branches in materializationBuilds.ts are never exercised:

  • src/nested/ignored_test.py (the _test.py$ Python exclusion pattern) is created in the fixture but never mutated/asserted against, unlike src/ignored.test.ts at Lines 56-65.
  • The symlink-rejection and path-escape guards in collectRuntimeSourceEntries (materializationBuilds.ts Lines 64-66, 75-81) have no corresponding test.

Given this digest feeds mandatory self-verification/signed receipts, gaps here are worth closing.

♻️ Suggested additions
+      await writeFile(
+        path.join(first.root, 'src', 'nested', 'ignored_test.py'),
+        'test-only-change\n'
+      );
+      expect(
+        await computeMaterializationBuilds({
+          runtimePath: first.runtimePath,
+          serviceRoot: first.root,
+        })
+      ).toEqual(baseline);

Also applies to: 41-115

🤖 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/materializationBuilds.test.ts` around lines 15 - 38, Extend the
materialization build tests to mutate src/nested/ignored_test.py and assert the
relevant digest remains unchanged, covering the _test.py exclusion alongside the
existing ignored.test.ts case. Add focused coverage for
collectRuntimeSourceEntries that verifies symlinked entries are rejected and
paths escaping the source root are excluded or rejected according to the
existing contract.
🤖 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 `@deploy/initialize-local-master-credential.test.sh`:
- Around line 17-18: Update the background initializer wait logic to collect the
exit statuses for both first_pid and second_pid before triggering set -e
termination. Ensure both processes are reaped even when the first wait fails,
then fail the test if either status is nonzero while preserving the existing
cleanup behavior.

In `@src/sandboxIsolation.test.ts`:
- Around line 25-32: Update the test named “uses a fixed non-root identity with
bounded swap” to assert the complete security flag/value pairs, verifying --user
is paired with 65534:65534 and --memory-swap is paired with the expected memory
value derived from 768m, rather than checking individual tokens.

In `@src/sandboxMounts.ts`:
- Around line 121-148: Update the file-staging flow around copyFile so
chmod(destinationPath, 0o444) runs after both the new-file and EEXIST
deduplication branches complete. Preserve the existing validation of matching
staged content, and add a regression test covering a pre-created matching
destination with mode 0666 that is returned with read-only permissions.

---

Nitpick comments:
In `@src/materializationBuilds.test.ts`:
- Around line 15-38: Extend the materialization build tests to mutate
src/nested/ignored_test.py and assert the relevant digest remains unchanged,
covering the _test.py exclusion alongside the existing ignored.test.ts case. Add
focused coverage for collectRuntimeSourceEntries that verifies symlinked entries
are rejected and paths escaping the source root are excluded or rejected
according to the existing contract.

In `@src/materializationBuilds.ts`:
- Around line 11-16: Update EXCLUDED_SOURCE_FILE_PATTERNS to also exclude Python
files using the pytest prefix convention, matching test_*.py while preserving
the existing _test.py exclusion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 90ee8862-5d73-4dbc-b4d5-e8a87ff99c64

📥 Commits

Reviewing files that changed from the base of the PR and between 5fbb079 and 487917f.

📒 Files selected for processing (21)
  • deploy/initialize-local-master-credential.sh
  • deploy/initialize-local-master-credential.test.sh
  • src/attribution.ts
  • src/keyBroker.test.ts
  • src/keyBroker.ts
  • src/linuxCodecSandbox.test.ts
  • src/linuxCodecSandbox.ts
  • src/linuxCodecWorker.py
  • src/linuxTreeSandbox.ts
  • src/linuxTreeWorker.py
  • src/linuxWorkers.test.ts
  • src/linuxWorkers_test.py
  • src/materializationBuilds.test.ts
  • src/materializationBuilds.ts
  • src/materializationServer.ts
  • src/rendition.ts
  • src/sandboxIsolation.test.ts
  • src/sandboxIsolation.ts
  • src/sandboxMounts.test.ts
  • src/sandboxMounts.ts
  • src/serverArchitecture.test.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • src/sandboxIsolation.ts
  • src/linuxCodecSandbox.test.ts
  • src/keyBroker.test.ts
  • deploy/initialize-local-master-credential.sh
  • src/linuxTreeSandbox.ts
  • src/serverArchitecture.test.ts
  • src/keyBroker.ts
  • src/rendition.ts
  • src/attribution.ts
  • src/linuxCodecWorker.py

Comment thread deploy/initialize-local-master-credential.test.sh Outdated
Comment thread src/sandboxIsolation.test.ts Outdated

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 487917ff89

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/linuxTreeSandbox.ts Outdated
Comment thread src/materializationService.ts Outdated
Comment thread src/materializationServer.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@src/boundedJson.ts`:
- Around line 1-7: Validate input.maximumBytes at the start of
readBoundedJsonResponse before using it for any size checks, rejecting values
that are not finite non-negative safe integers, including NaN, Infinity,
negatives, and unsafe integers. Preserve the existing bounded JSON response
behavior for valid limits.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 83c1aedc-39a9-4792-91c4-0e055b6524c4

📥 Commits

Reviewing files that changed from the base of the PR and between 487917f and c44aba3.

📒 Files selected for processing (11)
  • deploy/initialize-local-master-credential.test.sh
  • src/boundedJson.ts
  • src/linuxTreeSandbox.test.ts
  • src/linuxTreeSandbox.ts
  • src/materializationServer.test.ts
  • src/materializationServer.ts
  • src/materializationService.test.ts
  • src/materializationService.ts
  • src/sandboxIsolation.test.ts
  • src/sandboxMounts.test.ts
  • src/sandboxMounts.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/sandboxIsolation.test.ts
  • src/sandboxMounts.test.ts
  • deploy/initialize-local-master-credential.test.sh
  • src/materializationService.test.ts
  • src/sandboxMounts.ts
  • src/materializationServer.ts
  • src/materializationServer.test.ts
  • src/materializationService.ts

Comment thread src/boundedJson.ts Outdated

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c44aba33d0

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/linuxCodecSandbox.ts Outdated
Comment thread src/materializationService.ts Outdated
@Yeusepe

Yeusepe commented Jul 27, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@Yeusepe

Yeusepe commented Jul 27, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
src/sandboxContainer.ts (1)

68-71: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Success path is still blocked on the stderr read (previously flagged, not applied).

await stderr runs unconditionally before checking outcome.exitCode === 0. If the bounded stderr read rejects (oversized/garbled daemon output), a container that was actually removed successfully will still throw a cleanup failure. Check success first and only await/parse stderr for non-zero exits.

🛡️ Proposed fix: check success before awaiting stderr
-  const errorText = await stderr;
   if (outcome.exitCode === 0) {
+    void stderr.catch(() => undefined);
     return;
   }
+  const errorText = await stderr.catch(() => '');
🤖 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/sandboxContainer.ts` around lines 68 - 71, Update the cleanup flow around
outcome.exitCode so the successful exit check occurs before awaiting stderr.
Return immediately when exitCode is 0, and only await and process stderr for
non-zero exits, preserving the existing failure handling.
🧹 Nitpick comments (18)
src/materializationChunkCache.ts (1)

1393-1406: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

dependencies.now() is sampled twice, so the metadata record and the in-memory index can disagree.

If the injected clock advances between Lines 1396 and 1403, the persisted lastAccessedAt and the index entry diverge, and eviction ordering then depends on whether the index was rebuilt from disk. Sample once.

♻️ Sample the clock once
+          const lastAccessedAt = dependencies.now();
           await dependencies.writeJsonAtomic(metadataPath(chunkId), {
             bytes: actual.bytes,
             chunkId,
-            lastAccessedAt: dependencies.now(),
+            lastAccessedAt,
             schemaVersion: 1,
             sha256: actual.sha256,
           } satisfies MetadataRecord);
           index.set(chunkId, {
             bytes: actual.bytes,
             chunkId,
-            lastAccessedAt: dependencies.now(),
+            lastAccessedAt,
             objectPath: destination,
             sha256: actual.sha256,
           });
🤖 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/materializationChunkCache.ts` around lines 1393 - 1406, In the
materialization flow around the metadata write and index.set call, sample
dependencies.now() once into a shared timestamp before constructing either
record. Reuse that timestamp for both the persisted MetadataRecord and the
in-memory index entry so lastAccessedAt remains identical.
src/materializationChunkCache.test.ts (2)

160-167: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Source-text assertions will break on any harmless rename.

expect(source.match(/scanCacheEntries\(\)/g)).toHaveLength(2) counts the declaration plus its single call site, so renaming the helper or adding a second legitimate call fails the test without any behavioral regression. A behavioral assertion (e.g. commit N chunks with an injected statfs/readdir spy and assert the scan count stays constant) would encode the same intent more durably.

🤖 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/materializationChunkCache.test.ts` around lines 160 - 167, Replace the
brittle source-text assertions in the “does not rescan every committed object
for each capacity decision” test with a behavioral test: commit multiple chunks
using injected statfs/readdir spies, then assert cache scanning remains bounded
or constant rather than counting scanCacheEntries() occurrences. Retain coverage
that capacity-generation.json is used through observable behavior, not
source-text matching.

640-693: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Wall-clock choreography here is flake-prone.

The test depends on Bun.sleep(45) landing inside a 90 ms injected deletion; on a loaded CI runner the second maintain() can start after the first finished, and the toHaveLength(1) assertion then still passes for the wrong reason (or fails if both evict). Consider gating with an explicit "deletion in progress" promise that the second maintenance awaits, instead of a sleep.

🤖 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/materializationChunkCache.test.ts` around lines 640 - 693, Replace the
fixed Bun.sleep(45) timing in the “keeps the kernel capacity lock for the
complete slow operation” test with an explicit synchronization gate: start the
second maintain only after deletionStarted confirms deletion has begun, and keep
deletion blocked until the concurrent maintenance has reached the intended
contention point. Remove the wall-clock dependency while preserving the
assertion that exactly one chunk is evicted.
src/materializationServer.ts (1)

512-567: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The consecutive-stale-renewal guard is unreachable.

armLeaseDeadline(leaseExpiresAt) (Line 553) aborts materializationAbort immediately when leaseExpiresAt - Date.now() <= 0, and the very next materializationSignal.throwIfAborted() (Line 554) exits the loop. So the leaseExpiresAt <= Date.now() branch at Lines 555-567 — and therefore maximumConsecutiveStaleRenewals — can never increment past the first stale response. materializationServer.test.ts Line 499 confirms this by asserting exactly one renewal request.

Either drop the counter and the constant, or move the tolerance into armLeaseDeadline so a single stale expiry does not immediately kill the job.

🤖 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/materializationServer.ts` around lines 512 - 567, The
consecutive-stale-renewal counter in renewLease is unreachable because
armLeaseDeadline aborts before the stale-response branch can run. Remove
consecutiveStaleRenewals and maximumConsecutiveStaleRenewals, along with the
related stale-expiry handling, while preserving validation, lease deadline
arming, and abort behavior for expired leases.
src/materializationService.ts (2)

1990-1996: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

A fresh chunk cache is constructed per job, forcing a full object-store rescan each time.

createMaterializationChunkCache runs refreshCacheIndex under the capacity lock during construction (materializationChunkCache.ts Lines 1184-1186), and the in-process index lives inside that closure, so a per-job instance always starts cold and re-stats plus re-reads metadata for every committed object. At the configured 7 GiB quota with ~256 KiB chunks that is ~28k stat + ~28k readFile before the first byte is downloaded, on every job.

startMaterializationServer (materializationServer.ts Line 1207) already builds one long-lived cache; consider threading that instance through materializeCapabilityJob (with the current construction kept as a fallback for tests) so the warm index is reused.

🤖 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/materializationService.ts` around lines 1990 - 1996, Reuse the long-lived
chunk cache created by startMaterializationServer by threading that instance
into materializeCapabilityJob and using it instead of constructing a new cache
for each job. Preserve the existing createMaterializationChunkCache construction
as a fallback when no cache is supplied, including its current failure
classification, so tests and standalone callers continue to work.

1730-1732: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

localeCompare breaks the byte-ordering convention used everywhere else.

Chunk ids are validated lowercase hex so the practical outcome is the same today, but localeCompare is locale- and ICU-version dependent, whereas compareNormalizedPathsUtf8 (Line 54) and the cache's compareUtf8 deliberately sort by UTF-8 bytes. Download order feeds eviction recency, so keep it deterministic across hosts.

♻️ Use the existing byte comparison
-  const chunks = [...uniqueChunks.values()].sort((left, right) =>
-    left.id.localeCompare(right.id)
-  );
+  const chunks = [...uniqueChunks.values()].sort((left, right) =>
+    compareNormalizedPathsUtf8(left.id, right.id)
+  );
🤖 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/materializationService.ts` around lines 1730 - 1732, Update the sort
comparator for uniqueChunks in the materialization flow to use the existing
UTF-8 byte-order comparison utility, compareNormalizedPathsUtf8 or the
established equivalent, instead of left.id.localeCompare(right.id). Preserve
sorting by chunk ID and ensure the order remains deterministic across hosts.
deploy/initialize-local-master-credential.test.sh (1)

1-39: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Consider covering the symlink/permission-rejection branches.

The suite exercises epoch validation and concurrent rotation well, but doesn't test the credential-path symlink rejection or wrong-permission rejection branches in initialize-local-master-credential.sh. Worth adding given this script guards a security-sensitive credential file.

🤖 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 `@deploy/initialize-local-master-credential.test.sh` around lines 1 - 39,
Extend the test coverage for initialize-local-master-credential.sh to exercise
both security rejection branches: pre-create the credential path as a symlink
and verify initialization fails, then create the credential file with unsafe
permissions and verify it is rejected. Keep the existing epoch, concurrency, and
cleanup assertions intact, using isolated temporary HOME state where needed.
src/linuxCodecSandbox.test.ts (1)

136-144: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Unnecessary as unknown as {…} casts for already-exported symbols.

parseLinuxCodecResult, prepareLinuxCodecSandboxInput, and runLinuxCodecContainer are all exported from ./linuxCodecSandbox; importing them directly removes the hand-written structural types that can silently drift from the real signatures.

Also applies to: 179-183, 211-229

🤖 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/linuxCodecSandbox.test.ts` around lines 136 - 144, The tests
unnecessarily access exported linuxCodecSandbox functions through unknown
structural casts. Import parseLinuxCodecResult, prepareLinuxCodecSandboxInput,
and runLinuxCodecContainer directly from ./linuxCodecSandbox, then call them
without hand-written type assertions at the referenced test sections.
src/attribution.ts (1)

268-286: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Codec error codes escape from the attribution path.

Both helpers throw LinuxCodecFailure('MATERIALIZATION_CODEC_SANDBOX_FAILED', …). src/linuxTreeSandbox.ts wraps these into LinuxTreeSandboxFailure for exactly this reason; attribution surfaces a codec code for an attribution worker fault, which misroutes error classification and metrics. Wrap with an attribution-specific error here.

🤖 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/attribution.ts` around lines 268 - 286, Update the attribution flow
around writeLinuxCodecProcessInput and collectLinuxCodecProcessOutput to catch
LinuxCodecFailure exceptions and wrap them in the attribution-specific error
type and code, preserving the original error as context. Ensure codec error
codes do not escape attribution worker failures, matching the wrapping behavior
used by LinuxTreeSandbox.
src/attribution.test.ts (1)

34-55: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Source-text assertions are brittle.

These grep the implementation file rather than exercising behavior, so a formatting change (e.g. frame ?. fill(0)) fails the test while a genuine regression that keeps the string passes. Behavioral coverage for the same properties already exists in src/linuxCodecSandbox.test.ts (termination/draining/zeroization); consider asserting behavior via injected stubs instead.

🤖 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/attribution.test.ts` around lines 34 - 55, The tests in the attribution
worker response-boundary suite rely on brittle source-text matching instead of
runtime behavior. Replace the assertions in the two tests with behavioral tests
using injected stubs, covering bounded input/output handling and container
naming, cleanup, and frame zeroization; reuse the existing patterns from
linuxCodecSandbox.test.ts rather than checking implementation strings.
src/linuxTreeSandbox.ts (1)

174-186: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

JSON.parse(stdout) result is cast to T with no shape check inside runWorker.

Both callers validate the fields they read, so this is contained today, but a malformed/oversized-but-under-cap payload reaching JSON.parse throws a raw SyntaxError rather than a LinuxTreeSandboxFailure, bypassing the error-code contract the class establishes. Consider wrapping the parse.

🤖 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/linuxTreeSandbox.ts` around lines 174 - 186, Wrap the JSON.parse call in
runWorker with error handling so malformed worker output does not escape as a
raw SyntaxError. Convert parse failures into the established
LinuxTreeSandboxFailure type and preserve its error-code contract, while keeping
valid JSON returned as T unchanged.
src/linuxWorkers.test.ts (1)

34-35: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hardcoded test count makes this assertion churn on every added Python test.

'Ran 10 tests' must be edited whenever linuxWorkers_test.py grows, and a failure surfaces as a substring mismatch rather than the actual Python failure output. Asserting the exit code plus a non-zero run count keeps the signal without the coupling.

♻️ Proposed change
-    expect(`${stdout}${stderr}`).toContain('Ran 10 tests');
-    expect(exitCode).toBe(0);
+    const output = `${stdout}${stderr}`;
+    expect(output).toMatch(/Ran [1-9]\d* tests/);
+    expect(output).toContain('OK');
+    expect(exitCode).toBe(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 `@src/linuxWorkers.test.ts` around lines 34 - 35, Update the test assertion
near the Python worker execution to stop checking the exact “Ran 10 tests”
string. Keep the exitCode success assertion, and instead verify the combined
stdout/stderr indicates a non-zero test count while preserving the failure
output for diagnosis.
src/linuxCodecWorker.py (1)

317-324: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Uncarryable fallback emits the raw source archive, bypassing the normalization just performed.

shutil.copyfile(source_path, output_path) replaces the sanitized/rebuilt archive with the untrusted original, so normalized entry names (backslash rewriting, directory-suffix handling) and the deterministic rebuild are discarded for optional ZIPs with no carryable asset. Since the rebuilt archive already passed every validation, writing it out keeps outputs uniform and byte-deterministic.

♻️ Proposed change
     if coupled_entries == 0:
         if required:
             raise RuntimeError("nested ZIP contains no carryable visual asset")
-        shutil.copyfile(source_path, output_path)
         return "uncarryable"
🤖 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/linuxCodecWorker.py` around lines 317 - 324, Update the optional
no-carryable-asset branch in the nested ZIP handling logic around the
coupled_entries check to write the already normalized and deterministically
rebuilt archive to output_path instead of copying source_path. Preserve the
existing required error and "uncarryable" return behavior.
src/materializationBuilds.test.ts (2)

233-244: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Source-text scraping does not verify the symlink rejection.

This asserts that materializationBuilds.ts contains lstat/isSymbolicLink() strings; it stays green if the guard becomes unreachable or the error is swallowed, and breaks on any harmless rename. Exercise the boundary instead by symlinking a required input in the fixture and asserting the rejection.

💚 Proposed behavioral test
-  it('rejects symbolic links at the required build-input boundary', async () => {
-    const source = await Bun.file(
-      new URL('./materializationBuilds.ts', import.meta.url)
-    ).text();
-    const boundary = source.slice(
-      source.indexOf('async function requiredFileEntry'),
-      source.indexOf('export async function computeMaterializationBuilds')
-    );
-
-    expect(boundary).toContain('await lstat(filePath)');
-    expect(boundary).toContain('details.isSymbolicLink()');
-  });
+  it('rejects symbolic links at the required build-input boundary', async () => {
+    const fixture = await createFixture();
+    try {
+      const lockPath = path.join(fixture.root, 'bun.lock');
+      const realLockPath = path.join(fixture.root, 'bun.lock.real');
+      await rename(lockPath, realLockPath);
+      await symlink(realLockPath, lockPath);
+
+      await expect(
+        computeMaterializationBuilds({
+          runtimePath: fixture.runtimePath,
+          serviceRoot: fixture.root,
+        })
+      ).rejects.toThrow('not a regular file');
+    } finally {
+      await rm(fixture.root, { force: true, recursive: true });
+    }
+  });

Requires adding rename and symlink to the node:fs/promises 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/materializationBuilds.test.ts` around lines 233 - 244, Replace the
source-text assertion in the “rejects symbolic links at the required build-input
boundary” test with a behavioral fixture: create or rename a required build
input, symlink it using the node:fs/promises helpers, invoke
computeMaterializationBuilds, and assert that it rejects. Update the relevant
imports to include rename and symlink, while preserving the existing fixture
setup and validating the actual boundary behavior rather than implementation
text.

53-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Drop the optional-cast indirection; the symbol is a direct export.

pinMaterializationBuildArtifacts is exported from ./materializationBuilds and is already called directly at Line 123, so the as unknown as { …?: … } cast plus the if (!…) return; early exit is dead scaffolding that also lets the test pass vacuously if the export ever disappears at runtime.

🤖 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/materializationBuilds.test.ts` around lines 53 - 74, Use the direct
pinMaterializationBuildArtifacts export in the test instead of retrieving it
through an optional cast. Remove the local type assertion, optional property
typing, and the if (!pinMaterializationBuildArtifacts) early return, while
preserving the existing direct invocation and assertions.
src/sandboxContainer.test.ts (1)

1-50: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a regression test for stderr-read failures on successful removal.

Coverage gap: no test exercises a rejecting/oversized stderr stream paired with exitCode === 0, which is exactly the scenario the still-open ordering bug in sandboxContainer.ts (Line 68) would break.

🤖 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/sandboxContainer.test.ts` around lines 1 - 50, Add a regression test in
the “sandbox container cleanup” suite for a successful forced removal whose
process exits with code 0 but whose stderr stream rejects or exceeds the
supported read limit. Assert cleanup resolves successfully, covering the
stderr-read ordering behavior in removeSandboxContainer.
src/sandboxMounts.ts (2)

151-157: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Full in-memory read for dedup comparison on large files.

On the EEXIST (concurrent-stager) path, both source and destination are fully read into memory via readFile to compare bytes. For large materialization assets (textures, meshes), this doubles peak memory per concurrent staging collision. A streaming hash comparison (e.g., crypto.createHash piped from both file streams) would avoid buffering entire files.

♻️ Sketch using streaming hashes
-    const [sourceBytes, destinationBytes] = await Promise.all([
-      readFile(input.sourcePath),
-      readFile(destinationPath),
-    ]);
-    if (!sourceBytes.equals(destinationBytes)) {
+    const [sourceHash, destinationHash] = await Promise.all([
+      hashFile(input.sourcePath),
+      hashFile(destinationPath),
+    ]);
+    if (sourceHash !== destinationHash) {
       throw new Error('The staged sandbox file does not match its source');
     }
🤖 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/sandboxMounts.ts` around lines 151 - 157, Update the EEXIST comparison in
the sandbox staging flow around sourcePath and destinationPath to avoid loading
both files with readFile; compare their contents using streaming hashes or an
equivalent chunked stream-based approach, and retain the existing mismatch error
behavior.

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

Consider consolidating the three near-duplicate recursive tree walkers.

exposeReadonlyEntry, restorePrivateSandboxEntry, and exposeWritableEntry all repeat the same lstat → reject-symlink → recurse-into-directory → chmod skeleton, differing only in the target permission bits and ENOENT tolerance. Since this is security-sensitive permission-setting code, duplication increases the risk that a future symlink-check or ENOENT fix only lands in one copy.

♻️ Sketch of a shared walker
+async function walkSandboxTree(
+  entryPath: string,
+  options: {
+    tolerateMissing?: boolean;
+    chmodDir: (mode: number) => number;
+    chmodFile: (mode: number) => number;
+  }
+): Promise<void> {
+  // shared lstat/symlink/recurse logic, delegating final chmod to callers
+}

Also applies to: 39-69, 84-104

🤖 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/sandboxMounts.ts` around lines 15 - 31, Consolidate exposeReadonlyEntry,
restorePrivateSandboxEntry, and exposeWritableEntry around one shared recursive
walker that performs lstat, rejects symbolic links, recurses through
directories, and applies caller-specified file and directory permissions.
Preserve each existing function’s permission values and ENOENT tolerance by
passing those behaviors into the shared helper, while retaining the current
errors and security checks.
🤖 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 `@deploy/yucp-materializer.service`:
- Line 8: Update the WorkingDirectory setting in the materializer service unit
to /home/yucp/ca-coupling, matching the checkout path used by
prepare-local-wsl-materializer.sh, run-local-wsl-materializer.sh, and
.env.example so ExecStart can locate the project files.

In `@src/attribution.ts`:
- Around line 300-303: Update the cleanup call to removeSandboxContainer within
the try/finally flow so its rejection is swallowed and cannot replace the
original attribution error; await it with a catch that resolves to undefined
while preserving the existing conditional containerName check.

In `@src/linuxCodecSandbox.ts`:
- Around line 431-439: Update the catch block around the sandbox operation to
suppress any rejection from removeContainer, ensuring cleanup failure cannot
replace the original cause or classified LinuxCodecFailure. Preserve the
existing rethrow for LinuxCodecFailure and wrapping behavior for other causes.

---

Duplicate comments:
In `@src/sandboxContainer.ts`:
- Around line 68-71: Update the cleanup flow around outcome.exitCode so the
successful exit check occurs before awaiting stderr. Return immediately when
exitCode is 0, and only await and process stderr for non-zero exits, preserving
the existing failure handling.

---

Nitpick comments:
In `@deploy/initialize-local-master-credential.test.sh`:
- Around line 1-39: Extend the test coverage for
initialize-local-master-credential.sh to exercise both security rejection
branches: pre-create the credential path as a symlink and verify initialization
fails, then create the credential file with unsafe permissions and verify it is
rejected. Keep the existing epoch, concurrency, and cleanup assertions intact,
using isolated temporary HOME state where needed.

In `@src/attribution.test.ts`:
- Around line 34-55: The tests in the attribution worker response-boundary suite
rely on brittle source-text matching instead of runtime behavior. Replace the
assertions in the two tests with behavioral tests using injected stubs, covering
bounded input/output handling and container naming, cleanup, and frame
zeroization; reuse the existing patterns from linuxCodecSandbox.test.ts rather
than checking implementation strings.

In `@src/attribution.ts`:
- Around line 268-286: Update the attribution flow around
writeLinuxCodecProcessInput and collectLinuxCodecProcessOutput to catch
LinuxCodecFailure exceptions and wrap them in the attribution-specific error
type and code, preserving the original error as context. Ensure codec error
codes do not escape attribution worker failures, matching the wrapping behavior
used by LinuxTreeSandbox.

In `@src/linuxCodecSandbox.test.ts`:
- Around line 136-144: The tests unnecessarily access exported linuxCodecSandbox
functions through unknown structural casts. Import parseLinuxCodecResult,
prepareLinuxCodecSandboxInput, and runLinuxCodecContainer directly from
./linuxCodecSandbox, then call them without hand-written type assertions at the
referenced test sections.

In `@src/linuxCodecWorker.py`:
- Around line 317-324: Update the optional no-carryable-asset branch in the
nested ZIP handling logic around the coupled_entries check to write the already
normalized and deterministically rebuilt archive to output_path instead of
copying source_path. Preserve the existing required error and "uncarryable"
return behavior.

In `@src/linuxTreeSandbox.ts`:
- Around line 174-186: Wrap the JSON.parse call in runWorker with error handling
so malformed worker output does not escape as a raw SyntaxError. Convert parse
failures into the established LinuxTreeSandboxFailure type and preserve its
error-code contract, while keeping valid JSON returned as T unchanged.

In `@src/linuxWorkers.test.ts`:
- Around line 34-35: Update the test assertion near the Python worker execution
to stop checking the exact “Ran 10 tests” string. Keep the exitCode success
assertion, and instead verify the combined stdout/stderr indicates a non-zero
test count while preserving the failure output for diagnosis.

In `@src/materializationBuilds.test.ts`:
- Around line 233-244: Replace the source-text assertion in the “rejects
symbolic links at the required build-input boundary” test with a behavioral
fixture: create or rename a required build input, symlink it using the
node:fs/promises helpers, invoke computeMaterializationBuilds, and assert that
it rejects. Update the relevant imports to include rename and symlink, while
preserving the existing fixture setup and validating the actual boundary
behavior rather than implementation text.
- Around line 53-74: Use the direct pinMaterializationBuildArtifacts export in
the test instead of retrieving it through an optional cast. Remove the local
type assertion, optional property typing, and the if
(!pinMaterializationBuildArtifacts) early return, while preserving the existing
direct invocation and assertions.

In `@src/materializationChunkCache.test.ts`:
- Around line 160-167: Replace the brittle source-text assertions in the “does
not rescan every committed object for each capacity decision” test with a
behavioral test: commit multiple chunks using injected statfs/readdir spies,
then assert cache scanning remains bounded or constant rather than counting
scanCacheEntries() occurrences. Retain coverage that capacity-generation.json is
used through observable behavior, not source-text matching.
- Around line 640-693: Replace the fixed Bun.sleep(45) timing in the “keeps the
kernel capacity lock for the complete slow operation” test with an explicit
synchronization gate: start the second maintain only after deletionStarted
confirms deletion has begun, and keep deletion blocked until the concurrent
maintenance has reached the intended contention point. Remove the wall-clock
dependency while preserving the assertion that exactly one chunk is evicted.

In `@src/materializationChunkCache.ts`:
- Around line 1393-1406: In the materialization flow around the metadata write
and index.set call, sample dependencies.now() once into a shared timestamp
before constructing either record. Reuse that timestamp for both the persisted
MetadataRecord and the in-memory index entry so lastAccessedAt remains
identical.

In `@src/materializationServer.ts`:
- Around line 512-567: The consecutive-stale-renewal counter in renewLease is
unreachable because armLeaseDeadline aborts before the stale-response branch can
run. Remove consecutiveStaleRenewals and maximumConsecutiveStaleRenewals, along
with the related stale-expiry handling, while preserving validation, lease
deadline arming, and abort behavior for expired leases.

In `@src/materializationService.ts`:
- Around line 1990-1996: Reuse the long-lived chunk cache created by
startMaterializationServer by threading that instance into
materializeCapabilityJob and using it instead of constructing a new cache for
each job. Preserve the existing createMaterializationChunkCache construction as
a fallback when no cache is supplied, including its current failure
classification, so tests and standalone callers continue to work.
- Around line 1730-1732: Update the sort comparator for uniqueChunks in the
materialization flow to use the existing UTF-8 byte-order comparison utility,
compareNormalizedPathsUtf8 or the established equivalent, instead of
left.id.localeCompare(right.id). Preserve sorting by chunk ID and ensure the
order remains deterministic across hosts.

In `@src/sandboxContainer.test.ts`:
- Around line 1-50: Add a regression test in the “sandbox container cleanup”
suite for a successful forced removal whose process exits with code 0 but whose
stderr stream rejects or exceeds the supported read limit. Assert cleanup
resolves successfully, covering the stderr-read ordering behavior in
removeSandboxContainer.

In `@src/sandboxMounts.ts`:
- Around line 151-157: Update the EEXIST comparison in the sandbox staging flow
around sourcePath and destinationPath to avoid loading both files with readFile;
compare their contents using streaming hashes or an equivalent chunked
stream-based approach, and retain the existing mismatch error behavior.
- Around line 15-31: Consolidate exposeReadonlyEntry,
restorePrivateSandboxEntry, and exposeWritableEntry around one shared recursive
walker that performs lstat, rejects symbolic links, recurses through
directories, and applies caller-specified file and directory permissions.
Preserve each existing function’s permission values and ENOENT tolerance by
passing those behaviors into the shared helper, while retaining the current
errors and security checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d8675b81-eade-4c44-a392-1c5e4a93eb66

📥 Commits

Reviewing files that changed from the base of the PR and between 2beeaf2 and 86491e2.

⛔ Files ignored due to path filters (18)
  • bun.lock is excluded by !**/*.lock
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/out/copilot-probe/coupling_runtime.obj is excluded by !**/*.obj
  • yucp_coupling/out/copilot-probe/yucp_coupling.dll is excluded by !**/*.dll
  • yucp_coupling/out/linux-x64/Release/runtime-helper/yucp_coupling.so is excluded by !**/*.so
  • yucp_coupling/out/linux-x64/Release/yucp_coupling.so is excluded by !**/*.so
  • yucp_coupling/out/win-x64/Release/coupling_runtime.obj is excluded by !**/*.obj
  • yucp_coupling/out/win-x64/Release/runtime-helper/coupling_runtime.obj is excluded by !**/*.obj
  • yucp_coupling/out/win-x64/Release/runtime-helper/yucp_coupling.dll is excluded by !**/*.dll
  • yucp_coupling/out/win-x64/Release/yucp_coupling.dll is excluded by !**/*.dll
  • yucp_coupling/yucp_coupling.exports.map is excluded by !**/*.map
📒 Files selected for processing (229)
  • .env.example
  • deploy/initialize-local-master-credential.sh
  • deploy/initialize-local-master-credential.test.sh
  • deploy/prepare-local-wsl-materializer.sh
  • deploy/run-local-wsl-materializer.sh
  • deploy/run-local-wsl-materializer.test.ts
  • deploy/yucp-materializer.service
  • package.json
  • src/attestation.native.test.ts
  • src/attestation.test.ts
  • src/attestation.ts
  • src/attribution.test.ts
  • src/attribution.ts
  • src/boundedJson.test.ts
  • src/boundedJson.ts
  • src/codecLimits.test.ts
  • src/codecLimits.ts
  • src/config.ts
  • src/couplingSeed.test.ts
  • src/couplingSeed.ts
  • src/dpopSigner.test.ts
  • src/dpopSigner.ts
  • src/env.test.ts
  • src/env.ts
  • src/ffi.test.ts
  • src/ffi.ts
  • src/keyBroker.test.ts
  • src/keyBroker.ts
  • src/linuxAttributionWorker.py
  • src/linuxCodecSandbox.test.ts
  • src/linuxCodecSandbox.ts
  • src/linuxCodecWorker.py
  • src/linuxRendition.realtest.ts
  • src/linuxTreeSandbox.test.ts
  • src/linuxTreeSandbox.ts
  • src/linuxTreeWorker.py
  • src/linuxWorkers.test.ts
  • src/linuxWorkers_test.py
  • src/materializationBuilds.test.ts
  • src/materializationBuilds.ts
  • src/materializationChunkCache.test.ts
  • src/materializationChunkCache.ts
  • src/materializationServer.test.ts
  • src/materializationServer.ts
  • src/materializationService.test.ts
  • src/materializationService.ts
  • src/materialize.test.ts
  • src/materialize.ts
  • src/nativeExports.test.ts
  • src/nativeHardening.test.ts
  • src/nativePlatform.test.ts
  • src/nativePlatform.ts
  • src/publicAuthority.ts
  • src/rendition.test.ts
  • src/rendition.ts
  • src/roundtrip.test.ts
  • src/runtimeArtifacts.test.ts
  • src/runtimeArtifacts.ts
  • src/runtimeTokens.test.ts
  • src/runtimeTokens.ts
  • src/sandboxContainer.test.ts
  • src/sandboxContainer.ts
  • src/sandboxImage.test.ts
  • src/sandboxImage.ts
  • src/sandboxIsolation.test.ts
  • src/sandboxIsolation.ts
  • src/sandboxMounts.test.ts
  • src/sandboxMounts.ts
  • src/server.nativeAvailability.test.ts
  • src/server.ts
  • src/serverArchitecture.test.ts
  • src/yucpTrust.ts
  • src/zipArchive.test.ts
  • src/zipArchive.ts
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/dynamic/libsodium.ilk
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/static/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/dynamic/libsodium.ilk
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/static/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/ltcg/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/static/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/ltcg/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/static/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/core.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_aead_aegis128l.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_aead_aegis256.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_aead_aes256gcm.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_aead_chacha20poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_aead_xchacha20poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_auth.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_auth_hmacsha256.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_auth_hmacsha512.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_auth_hmacsha512256.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_box.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_box_curve25519xchacha20poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_box_curve25519xsalsa20poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_core_ed25519.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_core_hchacha20.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_core_hsalsa20.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_core_ristretto255.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_core_salsa20.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_core_salsa2012.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_core_salsa208.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_generichash.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_generichash_blake2b.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_hash.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_hash_sha256.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_hash_sha512.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_kdf.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_kdf_blake2b.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_kdf_hkdf_sha256.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_kdf_hkdf_sha512.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_kx.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_onetimeauth.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_onetimeauth_poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_pwhash.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_pwhash_argon2i.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_pwhash_argon2id.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_pwhash_scryptsalsa208sha256.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_scalarmult.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_scalarmult_curve25519.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_scalarmult_ed25519.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_scalarmult_ristretto255.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_secretbox.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_secretbox_xchacha20poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_secretbox_xsalsa20poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_secretstream_xchacha20poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_shorthash.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_shorthash_siphash24.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_sign.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_sign_ed25519.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_sign_edwards25519sha512batch.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_stream.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_stream_chacha20.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_stream_salsa20.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_stream_salsa2012.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_stream_salsa208.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_stream_xchacha20.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_stream_xsalsa20.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_verify_16.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_verify_32.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_verify_64.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/export.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/randombytes.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/randombytes_internal_random.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/randombytes_sysrandom.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/runtime.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/utils.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/version.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/dynamic/libsodium.ilk
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/static/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/dynamic/libsodium.ilk
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/static/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/ltcg/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/static/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/ltcg/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/static/libsodium.pdb
  • yucp_coupling/_deps/minhook/include/MinHook.h
  • yucp_coupling/_deps/minhook/src/buffer.c
  • yucp_coupling/_deps/minhook/src/buffer.h
  • yucp_coupling/_deps/minhook/src/hde/hde32.c
  • yucp_coupling/_deps/minhook/src/hde/hde32.h
  • yucp_coupling/_deps/minhook/src/hde/hde64.c
  • yucp_coupling/_deps/minhook/src/hde/hde64.h
  • yucp_coupling/_deps/minhook/src/hde/pstdint.h
  • yucp_coupling/_deps/minhook/src/hde/table32.h
  • yucp_coupling/_deps/minhook/src/hde/table64.h
  • yucp_coupling/_deps/minhook/src/hook.c
  • yucp_coupling/_deps/minhook/src/trampoline.c
  • yucp_coupling/_deps/minhook/src/trampoline.h
  • yucp_coupling/build.ps1
  • yucp_coupling/build.sh
  • yucp_coupling/coupling_runtime.c
  • yucp_coupling/coupling_runtime.exp
  • yucp_coupling/coupling_runtime.lib
  • yucp_coupling/guard.c
  • yucp_coupling/out/copilot-probe/yucp_coupling.compile.pdb
  • yucp_coupling/out/copilot-probe/yucp_coupling.pdb
  • yucp_coupling/out/copilot-probe/yucp_coupling.sha256
  • yucp_coupling/out/linux-x64/Release/runtime-helper/yucp_coupling.sha256
  • yucp_coupling/out/linux-x64/Release/yucp_coupling.sha256
  • yucp_coupling/out/win-x64/Release/runtime-helper/yucp_coupling.compile.pdb
  • yucp_coupling/out/win-x64/Release/runtime-helper/yucp_coupling.pdb
  • yucp_coupling/out/win-x64/Release/runtime-helper/yucp_coupling.sha256
  • yucp_coupling/out/win-x64/Release/yucp_coupling.compile.pdb
  • yucp_coupling/out/win-x64/Release/yucp_coupling.pdb
  • yucp_coupling/out/win-x64/Release/yucp_coupling.sha256
  • yucp_coupling/yucp_coupling.def
  • yucp_coupling/yucp_coupling.runtime-helper.def
💤 Files with no reviewable changes (18)
  • src/nativePlatform.test.ts
  • src/nativeExports.test.ts
  • src/env.ts
  • src/attestation.test.ts
  • src/attestation.native.test.ts
  • src/ffi.test.ts
  • src/publicAuthority.ts
  • src/nativePlatform.ts
  • src/runtimeArtifacts.test.ts
  • src/ffi.ts
  • src/materialize.test.ts
  • src/couplingSeed.test.ts
  • src/materialize.ts
  • src/roundtrip.test.ts
  • src/couplingSeed.ts
  • src/attestation.ts
  • src/config.ts
  • src/runtimeArtifacts.ts

Comment thread deploy/yucp-materializer.service
Comment thread src/attribution.ts Outdated
Comment thread src/linuxCodecSandbox.ts

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 013f149113

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/materializationChunkCache.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (3)
src/linuxCodecWorker.py (1)

271-315: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Peak memory/scratch usage per nested entry is ~2× the 100 MiB entry cap.

For one entry the worker holds data (up to MAX_NESTED_ZIP_ENTRY_BYTES = 100 MiB) plus coupled (another full copy) in RSS, and simultaneously stages entry_input and entry_output on /tmp. entry_input is written only to be copyfile'd to entry_output and then deleted — it is pure overhead. In a memory- and tmpfs-capped codec sandbox this is the difference between succeeding and an OOM/ENOSPC on an otherwise valid archive.

Writing data straight to entry_output halves the scratch footprint and removes a full 100 MiB copy.

🛡️ Proposed fix
                 if asset_type is not None:
-                    entry_input = os.path.join(
-                        scratch,
-                        f"{index:05d}.source.{asset_type}",
-                    )
                     entry_output = os.path.join(
                         scratch,
                         f"{index:05d}.coupled.{asset_type}",
                     )
                     try:
-                        with open(entry_input, "wb") as entry_file:
+                        with open(entry_output, "wb") as entry_file:
                             entry_file.write(data)
-                        shutil.copyfile(entry_input, entry_output)
                         decoded_token = couple_path(
                             runtime,
                             asset_type,
                             entry_output,
                             token_hex,
                             file_key,
                         )
                         if decoded_token is not None:
                             with open(entry_output, "rb") as entry_file:
                                 coupled = entry_file.read()
                             total_bytes = total_bytes - len(data) + len(coupled)
                             if total_bytes > MAX_NESTED_ZIP_TOTAL_BYTES:
                                 raise ValueError(
                                     "nested ZIP exceeds its expanded size limit"
                                 )
                             data = coupled
                             coupled_entries += 1
                         else:
                             uncarryable_visual_entries += 1
                     finally:
-                        for staged_path in (entry_input, entry_output):
-                            try:
-                                os.remove(staged_path)
-                            except FileNotFoundError:
-                                pass
+                        try:
+                            os.remove(entry_output)
+                        except FileNotFoundError:
+                            pass
🤖 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/linuxCodecWorker.py` around lines 271 - 315, Remove the redundant
entry_input staging from the nested-entry processing around couple_path: write
data directly to entry_output, pass that path to couple_path, and retain the
existing coupled readback and cleanup behavior. Update the finally block to
remove only entry_output while preserving archive output and size-limit
handling.
src/attribution.ts (1)

300-303: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Cleanup rejection in finally still masks the original failure.

removeSandboxContainer can reject (docker daemon unreachable — likely exactly when the run already failed), replacing the real attribution error. Swallow it.

🛡️ Proposed fix
     try {
       if (containerName) {
-        await removeSandboxContainer({ containerName });
+        await removeSandboxContainer({ containerName }).catch(() => undefined);
       }
     } finally {
🤖 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/attribution.ts` around lines 300 - 303, Update the cleanup flow in the
surrounding try/finally block so a rejection from removeSandboxContainer does
not escape or replace the original attribution error. Catch and swallow cleanup
failures while preserving the existing cleanup attempt and the primary error
outcome.
src/linuxCodecSandbox.ts (1)

431-439: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Container cleanup failure still replaces the classified codec failure.

await removeContainer(...) inside catch propagates its own rejection, discarding cause and the LinuxCodecErrorCode.

🛡️ Proposed fix
   } catch (cause) {
-    await removeContainer(input.request.containerName);
+    await removeContainer(input.request.containerName).catch(() => undefined);
     if (cause instanceof LinuxCodecFailure) {
🤖 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/linuxCodecSandbox.ts` around lines 431 - 439, Update the catch block
around the sandbox operation to ensure removeContainer failure cannot replace
the original cause or its LinuxCodecFailure classification. Isolate and safely
handle cleanup errors around removeContainer, then preserve the existing
LinuxCodecFailure rethrow and wrapping behavior for the original cause.
🧹 Nitpick comments (26)
src/attribution.test.ts (2)

14-32: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the PNG fixture helpers into a shared test module.

crc32, pngChunk, makeGrayPng, and sha256Hex are byte-for-byte duplicates of the versions in src/rendition.test.ts. A single src/testPngFixtures.ts (or similar) keeps the two suites from drifting on fixture format.

Also applies to: 156-183

🤖 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/attribution.test.ts` around lines 14 - 32, Extract the duplicated PNG
fixture helpers crc32, pngChunk, makeGrayPng, and sha256Hex from
attribution.test.ts and rendition.test.ts into a shared test module such as
testPngFixtures.ts. Export and reuse those shared helpers in both test suites,
removing their local duplicate implementations while preserving byte-for-byte
fixture behavior.

34-55: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Source-text assertions are brittle guardrails.

These tests read attribution.ts and match implementation substrings, so they break on any harmless rename/reformat while proving nothing about runtime behavior. The bounded-IO and container-cleanup contracts are better asserted by injecting a spawn stub and observing the argv/cleanup calls, as sandboxContainer.test.ts already does.

🤖 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/attribution.test.ts` around lines 34 - 55, Replace the source-text
assertions in the attribution worker tests with runtime tests that inject a
spawn stub, invoke the worker behavior, and observe bounded input/output
handling plus container naming and cleanup. Follow the dependency-injection and
call-observation pattern used by sandboxContainer.test.ts, asserting the
resulting argv and removeSandboxContainer call rather than matching
implementation substrings.
src/rendition.test.ts (1)

95-118: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Cover the digest-mismatch branch too.

Only the byte-count mismatch is asserted. The SHA-256 comparison is the part that actually detects tampered output, and it's currently unexercised.

💚 Suggested addition
       await expect(
         verifyRenditionOutputFile({
           expectedBytes: bytes.byteLength + 1,
           expectedSha256: sha256Hex(bytes),
           outputPath,
         })
       ).rejects.toThrow('length did not match');
+      await expect(
+        verifyRenditionOutputFile({
+          expectedBytes: bytes.byteLength,
+          expectedSha256: 'ff'.repeat(32),
+          outputPath,
+        })
+      ).rejects.toThrow();
🤖 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/rendition.test.ts` around lines 95 - 118, Extend the test around
verifyRenditionOutputFile to also assert rejection when expectedSha256 differs
from the file’s actual sha256Hex, while keeping expectedBytes equal to
bytes.byteLength. Verify the rejection matches the digest-mismatch error
message.
src/sandboxContainer.test.ts (1)

4-6: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add coverage for the input validation guards.

removeSandboxContainer rejects malformed container names and out-of-range cleanupTimeoutMs (the name regex is what keeps attacker-controlled strings out of the docker rm argv), but no test exercises either path.

♻️ Suggested additional cases
it('rejects a malformed container name before spawning', async () => {
  let spawned = false;
  await expect(
    removeSandboxContainer({
      containerName: 'yucp-codec-0123; rm -rf /',
      spawn: () => {
        spawned = true;
        return { exited: Promise.resolve(0) };
      },
    })
  ).rejects.toThrow('invalid');
  expect(spawned).toBeFalse();
});

it('rejects an out-of-range cleanup timeout', async () => {
  await expect(
    removeSandboxContainer({
      cleanupTimeoutMs: 60_000,
      containerName,
      spawn: () => ({ exited: Promise.resolve(0) }),
    })
  ).rejects.toThrow('timeout is invalid');
});
🤖 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/sandboxContainer.test.ts` around lines 4 - 6, Add tests in the “sandbox
container cleanup” suite covering both validation guards in
removeSandboxContainer: verify a malformed containerName rejects with an
invalid-name error and does not invoke spawn, and verify an out-of-range
cleanupTimeoutMs rejects with the timeout validation error. Reuse the existing
containerName fixture and preserve current valid cleanup coverage.
src/materializationBuilds.test.ts (2)

233-244: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert both slice markers were found.

A -1 from either indexOf yields a bogus slice, so the symlink-boundary guard could pass without inspecting requiredFileEntry.

💚 Proposed fix
-    const boundary = source.slice(
-      source.indexOf('async function requiredFileEntry'),
-      source.indexOf('export async function computeMaterializationBuilds')
-    );
+    const start = source.indexOf('async function requiredFileEntry');
+    const end = source.indexOf(
+      'export async function computeMaterializationBuilds'
+    );
+    expect(start).toBeGreaterThanOrEqual(0);
+    expect(end).toBeGreaterThan(start);
+    const boundary = source.slice(start, end);
🤖 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/materializationBuilds.test.ts` around lines 233 - 244, Update the test
case around the requiredFileEntry boundary to assert that both source.indexOf
markers return valid positions before slicing. Ensure the test fails when either
marker is missing, while preserving the existing lstat and
details.isSymbolicLink checks on the extracted boundary.

53-74: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop the optional cast; the same export is called directly at Line 122.

The as unknown as { pin...?: ... } shim plus existence guard adds noise and would mask a missing export as a pass-through return. Use the typed module export consistently.

♻️ Proposed simplification
-      const pinMaterializationBuildArtifacts = (
-        materializationBuildsModule as unknown as {
-          pinMaterializationBuildArtifacts?: (input: {...}) => Promise<{...}>;
-        }
-      ).pinMaterializationBuildArtifacts;
-      expect(pinMaterializationBuildArtifacts).toBeFunction();
-      if (!pinMaterializationBuildArtifacts) {
-        return;
-      }
-      const pinned = await pinMaterializationBuildArtifacts({
+      const pinned =
+        await materializationBuildsModule.pinMaterializationBuildArtifacts({
🤖 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/materializationBuilds.test.ts` around lines 53 - 74, Update the
materializationBuilds test to use the typed pinMaterializationBuildArtifacts
module export directly, matching the invocation later in the test. Remove the
unknown optional cast, function-existence assertion, and early return so a
missing export fails the test rather than being silently skipped.
src/linuxCodecSandbox.test.ts (1)

359-370: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Source-slicing test can silently degrade if either marker moves.

If indexOf returns -1 (rename/reorder), the slice covers an unintended region and the lstat guard is no longer actually verified. Assert both offsets first.

💚 Proposed fix
-    const validator = source.slice(
-      source.indexOf('export async function validateLinuxCodecOutputLengths'),
-      source.indexOf('export async function runLinuxCodecSandbox')
-    );
+    const start = source.indexOf(
+      'export async function validateLinuxCodecOutputLengths'
+    );
+    const end = source.indexOf('export async function runLinuxCodecSandbox');
+    expect(start).toBeGreaterThanOrEqual(0);
+    expect(end).toBeGreaterThan(start);
+    const validator = source.slice(start, end);
🤖 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/linuxCodecSandbox.test.ts` around lines 359 - 370, Update the
source-slicing test around the validator marker lookups to store both indexOf
results and assert each is non-negative before calling slice. Keep the existing
lstat/stat assertions unchanged so the test fails explicitly if either
validateLinuxCodecOutputLengths or runLinuxCodecSandbox marker moves.
src/sandboxMounts.ts (1)

131-163: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Atomic publish via link() looks right; the dedup comparison is the remaining cost.

Two full readFile calls buffer both copies in memory on every EEXIST, which scales with worker/runtime artifact size and multiplies under concurrent stagers (see sandboxMounts.test.ts staging-race test). Compare sizes first and fall back to a streaming hash.

♻️ Proposed change
     const destination = await lstat(destinationPath);
     if (!destination.isFile() || destination.isSymbolicLink()) {
       throw new Error('The staged sandbox file is invalid');
     }
-    const [sourceBytes, destinationBytes] = await Promise.all([
-      readFile(input.sourcePath),
-      readFile(destinationPath),
-    ]);
-    if (!sourceBytes.equals(destinationBytes)) {
+    if (destination.size !== source.size) {
+      throw new Error('The staged sandbox file does not match its source');
+    }
+    const [sourceDigest, destinationDigest] = await Promise.all([
+      fileDigest(input.sourcePath),
+      fileDigest(destinationPath),
+    ]);
+    if (sourceDigest !== destinationDigest) {
       throw new Error('The staged sandbox file does not match its source');
     }

with a small helper:

import { createHash } from 'node:crypto';
import { createReadStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';

async function fileDigest(filePath: string): Promise<string> {
  const hash = createHash('sha256');
  await pipeline(createReadStream(filePath), hash);
  return hash.digest('hex');
}
🤖 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/sandboxMounts.ts` around lines 131 - 163, Replace the full-buffer read
comparison in the EEXIST handling of the sandbox staging function with a
size-first check, then compare matching files using streaming SHA-256 digests.
Add and use a small fileDigest helper based on createReadStream, createHash, and
pipeline; preserve the existing invalid-destination and mismatch errors while
avoiding simultaneous whole-file buffering.
src/sandboxMounts.test.ts (1)

97-125: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

64 MiB × 8 concurrent stagers makes this test memory-hungry.

Each EEXIST loser in stageReadonlySandboxFile reads both source and destination fully into memory (readFile ×2), so seven losers can hold ~900 MiB transiently plus ~512 MiB of temp copies on disk. A few MiB is enough to exercise the race window without risking OOM on constrained CI runners.

♻️ Proposed change
-    const bytes = Buffer.alloc(64 * 1024 * 1024, 0x5a);
+    const bytes = Buffer.alloc(4 * 1024 * 1024, 0x5a);
🤖 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/sandboxMounts.test.ts` around lines 97 - 125, Reduce the test fixture
size in the concurrent stageReadonlySandboxFile test while preserving the
eight-way race and completeness assertions; use a payload of only a few MiB
instead of 64 MiB so loser retries do not create excessive memory or disk usage.
src/materializationChunkCache.test.ts (2)

653-706: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Wall-clock sleeps make this lock test timing-sensitive.

The 90 ms injected deletion plus the 45 ms gap before the second maintain() assumes the slow eviction is still in flight; a loaded CI runner can invert that ordering and flip committedObjects. A deterministic gate (resolve the deletion only after the second maintain() has been started) would remove the flake window.

🤖 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/materializationChunkCache.test.ts` around lines 653 - 706, Make the lock
test deterministic by replacing the wall-clock sleeps in “keeps the kernel
capacity lock for the complete slow operation” with a synchronization gate: have
the injected removeObject deletion pause on a promise, start the concurrent
maintain() while that deletion is blocked, then resolve the gate and await both
maintenance operations. Preserve the existing eviction and committedObjects
assertions.

160-180: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

These two tests assert on the implementation's source text, not behavior.

toContain('withInProcessFileLock(lockPath, (signal) =>') and the scanCacheEntries() occurrence count break on any harmless reformat/rename while proving nothing about runtime semantics. The behavioral equivalents already exist elsewhere in this file (serializes capacity work through one kernel-held lock, keeps the kernel capacity lock for the complete slow operation). Consider dropping these or replacing them with an assertion on observed lock/scan counts via injected dependencies.

🤖 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/materializationChunkCache.test.ts` around lines 160 - 180, Replace the
source-text assertions in the two tests around Linux lock acquisition and cache
scanning with behavioral tests. Reuse the existing lock/scan instrumentation or
injected dependencies to observe actual lock serialization and scan counts,
matching the behaviors covered by “serializes capacity work through one
kernel-held lock” and “keeps the kernel capacity lock for the complete slow
operation”; remove brittle source-string and occurrence-count checks.
src/materializationService.test.ts (1)

255-263: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer a behavioral NFC-collision test over a source-text match.

This asserts the literal expression rather than the guarantee. A manifest containing both NFC and NFD spellings of the same path, expected to be rejected, would keep passing across refactors and actually prove the 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 `@src/materializationService.test.ts` around lines 255 - 263, Replace the
source-text assertion in the “normalizes source-manifest collision keys before
case folding” test with a behavioral case: construct a manifest containing NFC
and NFD spellings of the same path, invoke the relevant materialization flow,
and assert that the collision is rejected. Preserve the test’s focus on
normalization before case folding and avoid matching implementation source text.
src/linuxCodecWorker.py (1)

256-259: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

.upper() case-folding can reject legitimate archives.

Unicode uppercasing is not length-preserving (ß → SS, fi → FI), so distinct entry names can fold to the same collision_key and trip the duplicate-path error. str.casefold() is the intended primitive for case-insensitive matching and avoids the expansion collisions that upper() introduces.

♻️ Proposed change
-                collision_key = normalized.upper()
+                collision_key = normalized.casefold()
🤖 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/linuxCodecWorker.py` around lines 256 - 259, Update the duplicate-path
normalization in the ZIP validation flow to use normalized.casefold() instead of
normalized.upper() when assigning collision_key. Keep the existing seen_paths
membership check, ValueError, and set insertion unchanged.
src/serverArchitecture.test.ts (1)

153-167: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Self-referential assertion can never fail.

The test reads its own source and asserts it contains new Bun.Glob('**/*.ts'), which is trivially true by construction and guards nothing. The serverSource assertion below is the only meaningful check here.

♻️ Proposed cleanup
-    const architectureTestSource = readFileSync(
-      path.join(repoRoot, 'src', 'serverArchitecture.test.ts'),
-      'utf8'
-    );
-
-    expect(architectureTestSource).toContain("new Bun.Glob('**/*.ts')");
     expect(serverSource).toContain(
       "readTrimmedEnv(env, 'MATERIALIZATION_HEALTH_HOST') ?? '127.0.0.1'"
     );
🤖 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/serverArchitecture.test.ts` around lines 153 - 167, Remove the
self-referential architectureTestSource read and its assertion for `new
Bun.Glob('**/*.ts')` from the test `uses recursive source scanning and a
loopback health default`. Keep the meaningful `serverSource` assertion verifying
the `MATERIALIZATION_HEALTH_HOST` loopback default.
src/materializationBuilds.ts (1)

207-241: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Startup prune deletes every instance-* directory, including one another process is actively using.

pruneObsoletePinnedArtifacts runs before this process creates its own directory, so it only stays safe while exactly one materializer per MATERIALIZATION_WORK_ROOT can run. The systemd unit's flock --no-fork --nonblock enforces that on the deployed path, but any manual/second start against the same work root would pull the pinned workers and yucp_coupling.so out from under a live instance (files are already open-read at 0o400, but subsequent lstat/CDLL loads would fail).

Consider skipping directories whose per-instance lock is still held, or documenting the single-instance requirement next to workRoot.

🤖 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/materializationBuilds.ts` around lines 207 - 241, The startup cleanup in
pruneObsoletePinnedArtifacts must not remove an instance-* directory that
another materializer is actively using. Add or reuse a per-instance lock for
each pinned directory, and prune only directories whose lock can be acquired;
preserve cleanup of stale unlocked directories and ensure
pinMaterializationBuildArtifacts establishes the lock before the directory
becomes eligible for use.
src/boundedJson.test.ts (1)

5-15: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Assert the response was actually left unconsumed.

The test name claims the limit is rejected "before consuming the response", but only the error message is checked — the same assertion would pass if the body had been fully read first.

♻️ Proposed strengthening
     async (maximumBytes) => {
+      const response = Response.json({ success: true });
       await expect(
-        readBoundedJsonResponse(Response.json({ success: true }), {
+        readBoundedJsonResponse(response, {
           errorPrefix: 'Control-plane response',
           maximumBytes,
         })
       ).rejects.toThrow('maximum byte limit is invalid');
+      expect(response.bodyUsed).toBe(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/boundedJson.test.ts` around lines 5 - 15, Strengthen the parameterized
test for readBoundedJsonResponse by asserting that the response body remains
unconsumed after the invalid maximumBytes rejection. Keep the existing error
assertion and verify the Response body’s unread state, such as its bodyUsed
property, remains false.
src/linuxTreeSandbox.ts (1)

174-190: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Only the stdin path yields LinuxTreeSandboxFailure; output and exit failures escape as plain Error.

writeLinuxCodecProcessInput failures are wrapped at Line 172, but collectLinuxTreeProcessOutput rejections, the nonzero-exit error, and JSON.parse syntax errors propagate unwrapped — so callers get three different shapes and errorCode: 'MATERIALIZATION_TREE_SANDBOX_FAILED' is present only for one of them. Wrap the whole worker interaction uniformly.

♻️ Proposed change
     } catch (cause) {
       await removeSandboxContainer({ containerName }).catch(() => undefined);
-      throw cause;
+      throw cause instanceof LinuxTreeSandboxFailure
+        ? cause
+        : new LinuxTreeSandboxFailure(cause);
     }
🤖 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/linuxTreeSandbox.ts` around lines 174 - 190, Update the worker
interaction surrounding collectLinuxTreeProcessOutput, the nonzero-exit check,
and JSON.parse so all failures are wrapped in the existing
LinuxTreeSandboxFailure shape with errorCode
MATERIALIZATION_TREE_SANDBOX_FAILED, matching writeLinuxCodecProcessInput
failures. Preserve sandbox cleanup and the original cause while ensuring callers
receive the same failure type for stdin, output, exit, and parse errors.
src/linuxTreeSandbox.test.ts (1)

81-111: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Source-text assertions are brittle proxies for behavior.

These three tests match raw linuxTreeSandbox.ts text, including exact spacing and occurrence counts. Any reformat or benign rename breaks them, and none of them proves the runtime property they describe. The last one in particular is testable directly: runWorker's cleanup path can be exercised by injecting a failing worker plus a rejecting container removal and asserting the original cause propagates.

🤖 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/linuxTreeSandbox.test.ts` around lines 81 - 111, Replace the three raw
source-text assertions in the tests with behavior-focused tests: exercise the
guarded process-input path and verify its failure behavior, validate the shared
sandbox work-directory behavior through the relevant exported API, and invoke
runWorker with a failing worker plus rejecting container cleanup to assert the
original worker error propagates. Avoid exact source formatting, occurrence
counts, and implementation-text matching.
deploy/run-local-wsl-materializer.sh (1)

125-134: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Require Bash 5.1+ for wait -n -p

wait -n -p needs Bash 5.1, so this will fail on older WSL distros before the supervision loop starts. Add a version check up front if those environments need to be supported.

♻️ Optional guard
 set -euo pipefail
+
+if (( BASH_VERSINFO[0] < 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] < 1) )); then
+  echo "Bash 5.1 or newer is required." >&2
+  exit 78
+fi
🤖 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 `@deploy/run-local-wsl-materializer.sh` around lines 125 - 134, Add an upfront
Bash version check before the supervision logic using wait -n -p, and exit with
a clear error when the running Bash is older than 5.1. Keep the existing
process-waiting behavior unchanged for supported Bash versions.
src/linuxWorkers.test.ts (1)

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

Hard-coded test count makes this wrapper break whenever a Python test is added.

linuxWorkers_test.py currently has exactly 10 tests, so this passes today, but the assertion carries no signal beyond "discovery found something" and forces an unrelated edit on every new case. Assert a non-zero count plus OK instead.

♻️ Proposed refactor
-    expect(`${stdout}${stderr}`).toContain('Ran 10 tests');
+    const output = `${stdout}${stderr}`;
+    expect(output).toMatch(/Ran [1-9]\d* tests/);
+    expect(output).toContain('OK');
     expect(exitCode).toBe(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 `@src/linuxWorkers.test.ts` at line 34, Replace the hard-coded “Ran 10 tests”
assertion in the linuxWorkers test wrapper with assertions that the combined
stdout/stderr reports a non-zero test count and contains “OK”.
src/linuxTreeWorker.py (2)

98-113: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Remove the destination file when the digest check fails.

copy_verified writes the full copy before comparing digests, so a mismatch leaves a partially-trusted file at destination. The job aborts today, but the file lingers in /input for the rest of the container's lifetime.

♻️ Proposed refactor
     if digest.hexdigest() != expected_digest:
+        os.unlink(destination)
         raise ValueError("protected source digest did not match")
🤖 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/linuxTreeWorker.py` around lines 98 - 113, Update copy_verified so a
digest mismatch removes the newly written destination file before propagating
the ValueError. Keep the successful-copy return path unchanged, and ensure
cleanup targets only the destination created by this invocation.

62-69: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Reject non-object request bodies explicitly.

json.loads happily returns a list, string, or number, and request.get(...) then raises AttributeError, which the top-level handler reports as an opaque AttributeError instead of the intended ValueError("unsupported schema") contract.

♻️ Proposed refactor
     request = json.loads(raw)
+    if not isinstance(request, dict):
+        raise ValueError("invalid request")
     if request.get("schemaVersion") != 3:
🤖 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/linuxTreeWorker.py` around lines 62 - 69, Update read_request to validate
that the json.loads result is an object/dictionary before calling request.get;
reject lists, strings, numbers, and other non-object bodies with
ValueError("unsupported schema"), while preserving the existing schemaVersion
validation for object requests.
src/linuxRendition.realtest.ts (2)

115-123: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hoist the repeated runtimePath resolution to module scope.

The same nine-line path.resolve(...) appears in all five tests. A single module-level constant (plus a shared createJobInput helper for the near-identical materializePersonalizedZip payloads) would cut most of this file's bulk.

♻️ Proposed refactor
+const runtimePath = path.resolve(
+  import.meta.dir,
+  '..',
+  'yucp_coupling',
+  'out',
+  'linux-x64',
+  'Release',
+  'yucp_coupling.so'
+);
+
 describe('real Linux rendition sandbox', () => {
   test(
     'creates different verified outputs for two buyers without returning key material',
     async () => {
-      const runtimePath = path.resolve(
-        import.meta.dir,
-        '..',
-        'yucp_coupling',
-        'out',
-        'linux-x64',
-        'Release',
-        'yucp_coupling.so'
-      );

Also applies to: 196-204, 255-263, 310-318, 365-373

🤖 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/linuxRendition.realtest.ts` around lines 115 - 123, Hoist the repeated
path.resolve expression into a single module-level runtimePath constant in
src/linuxRendition.realtest.ts, then reuse it in all five tests, including the
locations around materializePersonalizedZip. Also extract the near-identical
materializePersonalizedZip payload construction into a shared createJobInput
helper and update each test to use it.

10-19: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use zlib.crc32 here instead of the hand-rolled helper. This file already targets Bun, which exposes it via node:zlib, so the built-in keeps the checksum code out of the test.

🤖 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/linuxRendition.realtest.ts` around lines 10 - 19, Replace the hand-rolled
crc32 helper with Bun’s built-in crc32 from node:zlib, updating its call sites
to use the imported function and removing the local helper implementation.
deploy/run-local-wsl-materializer.test.ts (1)

6-10: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hoist the script read into a shared helper.

The same readFile is repeated in all three tests. A module-level lazy loader (or beforeAll) removes the duplication and the repeated path literal.

♻️ Proposed refactor
+const scriptPath = path.join(import.meta.dir, 'run-local-wsl-materializer.sh');
+let cached: string | undefined;
+const loadScript = async () => (cached ??= await readFile(scriptPath, 'utf8'));
+
 describe('local WSL materializer entry point', () => {
   test('uses the isolated runtime proxy ports instead of fixed shared ports', async () => {
-    const source = await readFile(
-      path.join(import.meta.dir, 'run-local-wsl-materializer.sh'),
-      'utf8'
-    );
+    const source = await loadScript();

Also applies to: 24-28, 42-46

🤖 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 `@deploy/run-local-wsl-materializer.test.ts` around lines 6 - 10, Extract the
repeated script-loading logic from the three tests into a shared module-level
lazy helper or beforeAll setup. Reuse that helper in the tests, including the
cases around the existing first test and the other two readFile calls, while
keeping the same script path and UTF-8 decoding behavior.
src/linuxWorkers_test.py (1)

18-30: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The fake resource module is inserted into sys.modules permanently.

Once any test loads an attribution/codec worker on a platform without resource, every later import in the session resolves to this two-attribute stub. Scoping it to the load (or registering an addCleanup from the caller) keeps the interpreter state honest.

🤖 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/linuxWorkers_test.py` around lines 18 - 30, Update load_worker so the
fallback resource module is only present for the duration of the worker import:
track whether the stub was inserted, execute spec.loader.exec_module(module),
then restore the prior sys.modules state by removing the stub afterward.
Preserve an existing resource module unchanged and ensure cleanup occurs even if
module loading raises.
🤖 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 `@deploy/yucp-materializer.service`:
- Line 15: Ensure the %h/.local/share/yucp-materializer directory exists before
systemd applies ReadWritePaths=, since ExecStartPre runs too late. Update the
unit configuration to use an appropriate StateDirectory= or deploy-time
creation, or mark the ReadWritePaths= entry optional with a “-” prefix if the
directory may be absent.

In `@src/linuxTreeWorker.py`:
- Line 210: Update the umask configuration in the worker initialization to use
0o077 instead of 0o000, ensuring files and directories created by the worker are
not world-accessible while preserving the existing creation flow.

In `@src/materializationChunkCache.ts`:
- Around line 1397-1410: Capture dependencies.now() once before writing the
metadata in the materialization flow, then reuse that timestamp for both the
persisted MetadataRecord and the index.set entry. Update the relevant code
surrounding metadataPath(chunkId) and index.set so both lastAccessedAt values
are identical.

In `@src/sandboxContainer.ts`:
- Around line 72-83: Update the failure handling around the stderr read in the
sandbox container cleanup flow so readBoundedUtf8Stream errors cannot replace
the cleanup failure message. Preserve the missing-container detection when
stderr is successfully read, but catch read failures and continue throwing the
status-based error that includes outcome.exitCode.

In `@src/sandboxIsolation.ts`:
- Around line 55-62: Update requireDockerBindSource to reject any colon that is
not the Windows drive-letter colon at index 1, while preserving valid Unix
absolute paths and Windows drive-prefixed paths. Keep the existing comma,
null-byte, and absolute-path validation intact.

---

Duplicate comments:
In `@src/attribution.ts`:
- Around line 300-303: Update the cleanup flow in the surrounding try/finally
block so a rejection from removeSandboxContainer does not escape or replace the
original attribution error. Catch and swallow cleanup failures while preserving
the existing cleanup attempt and the primary error outcome.

In `@src/linuxCodecSandbox.ts`:
- Around line 431-439: Update the catch block around the sandbox operation to
ensure removeContainer failure cannot replace the original cause or its
LinuxCodecFailure classification. Isolate and safely handle cleanup errors
around removeContainer, then preserve the existing LinuxCodecFailure rethrow and
wrapping behavior for the original cause.

In `@src/linuxCodecWorker.py`:
- Around line 271-315: Remove the redundant entry_input staging from the
nested-entry processing around couple_path: write data directly to entry_output,
pass that path to couple_path, and retain the existing coupled readback and
cleanup behavior. Update the finally block to remove only entry_output while
preserving archive output and size-limit handling.

---

Nitpick comments:
In `@deploy/run-local-wsl-materializer.sh`:
- Around line 125-134: Add an upfront Bash version check before the supervision
logic using wait -n -p, and exit with a clear error when the running Bash is
older than 5.1. Keep the existing process-waiting behavior unchanged for
supported Bash versions.

In `@deploy/run-local-wsl-materializer.test.ts`:
- Around line 6-10: Extract the repeated script-loading logic from the three
tests into a shared module-level lazy helper or beforeAll setup. Reuse that
helper in the tests, including the cases around the existing first test and the
other two readFile calls, while keeping the same script path and UTF-8 decoding
behavior.

In `@src/attribution.test.ts`:
- Around line 14-32: Extract the duplicated PNG fixture helpers crc32, pngChunk,
makeGrayPng, and sha256Hex from attribution.test.ts and rendition.test.ts into a
shared test module such as testPngFixtures.ts. Export and reuse those shared
helpers in both test suites, removing their local duplicate implementations
while preserving byte-for-byte fixture behavior.
- Around line 34-55: Replace the source-text assertions in the attribution
worker tests with runtime tests that inject a spawn stub, invoke the worker
behavior, and observe bounded input/output handling plus container naming and
cleanup. Follow the dependency-injection and call-observation pattern used by
sandboxContainer.test.ts, asserting the resulting argv and
removeSandboxContainer call rather than matching implementation substrings.

In `@src/boundedJson.test.ts`:
- Around line 5-15: Strengthen the parameterized test for
readBoundedJsonResponse by asserting that the response body remains unconsumed
after the invalid maximumBytes rejection. Keep the existing error assertion and
verify the Response body’s unread state, such as its bodyUsed property, remains
false.

In `@src/linuxCodecSandbox.test.ts`:
- Around line 359-370: Update the source-slicing test around the validator
marker lookups to store both indexOf results and assert each is non-negative
before calling slice. Keep the existing lstat/stat assertions unchanged so the
test fails explicitly if either validateLinuxCodecOutputLengths or
runLinuxCodecSandbox marker moves.

In `@src/linuxCodecWorker.py`:
- Around line 256-259: Update the duplicate-path normalization in the ZIP
validation flow to use normalized.casefold() instead of normalized.upper() when
assigning collision_key. Keep the existing seen_paths membership check,
ValueError, and set insertion unchanged.

In `@src/linuxRendition.realtest.ts`:
- Around line 115-123: Hoist the repeated path.resolve expression into a single
module-level runtimePath constant in src/linuxRendition.realtest.ts, then reuse
it in all five tests, including the locations around materializePersonalizedZip.
Also extract the near-identical materializePersonalizedZip payload construction
into a shared createJobInput helper and update each test to use it.
- Around line 10-19: Replace the hand-rolled crc32 helper with Bun’s built-in
crc32 from node:zlib, updating its call sites to use the imported function and
removing the local helper implementation.

In `@src/linuxTreeSandbox.test.ts`:
- Around line 81-111: Replace the three raw source-text assertions in the tests
with behavior-focused tests: exercise the guarded process-input path and verify
its failure behavior, validate the shared sandbox work-directory behavior
through the relevant exported API, and invoke runWorker with a failing worker
plus rejecting container cleanup to assert the original worker error propagates.
Avoid exact source formatting, occurrence counts, and implementation-text
matching.

In `@src/linuxTreeSandbox.ts`:
- Around line 174-190: Update the worker interaction surrounding
collectLinuxTreeProcessOutput, the nonzero-exit check, and JSON.parse so all
failures are wrapped in the existing LinuxTreeSandboxFailure shape with
errorCode MATERIALIZATION_TREE_SANDBOX_FAILED, matching
writeLinuxCodecProcessInput failures. Preserve sandbox cleanup and the original
cause while ensuring callers receive the same failure type for stdin, output,
exit, and parse errors.

In `@src/linuxTreeWorker.py`:
- Around line 98-113: Update copy_verified so a digest mismatch removes the
newly written destination file before propagating the ValueError. Keep the
successful-copy return path unchanged, and ensure cleanup targets only the
destination created by this invocation.
- Around line 62-69: Update read_request to validate that the json.loads result
is an object/dictionary before calling request.get; reject lists, strings,
numbers, and other non-object bodies with ValueError("unsupported schema"),
while preserving the existing schemaVersion validation for object requests.

In `@src/linuxWorkers_test.py`:
- Around line 18-30: Update load_worker so the fallback resource module is only
present for the duration of the worker import: track whether the stub was
inserted, execute spec.loader.exec_module(module), then restore the prior
sys.modules state by removing the stub afterward. Preserve an existing resource
module unchanged and ensure cleanup occurs even if module loading raises.

In `@src/linuxWorkers.test.ts`:
- Line 34: Replace the hard-coded “Ran 10 tests” assertion in the linuxWorkers
test wrapper with assertions that the combined stdout/stderr reports a non-zero
test count and contains “OK”.

In `@src/materializationBuilds.test.ts`:
- Around line 233-244: Update the test case around the requiredFileEntry
boundary to assert that both source.indexOf markers return valid positions
before slicing. Ensure the test fails when either marker is missing, while
preserving the existing lstat and details.isSymbolicLink checks on the extracted
boundary.
- Around line 53-74: Update the materializationBuilds test to use the typed
pinMaterializationBuildArtifacts module export directly, matching the invocation
later in the test. Remove the unknown optional cast, function-existence
assertion, and early return so a missing export fails the test rather than being
silently skipped.

In `@src/materializationBuilds.ts`:
- Around line 207-241: The startup cleanup in pruneObsoletePinnedArtifacts must
not remove an instance-* directory that another materializer is actively using.
Add or reuse a per-instance lock for each pinned directory, and prune only
directories whose lock can be acquired; preserve cleanup of stale unlocked
directories and ensure pinMaterializationBuildArtifacts establishes the lock
before the directory becomes eligible for use.

In `@src/materializationChunkCache.test.ts`:
- Around line 653-706: Make the lock test deterministic by replacing the
wall-clock sleeps in “keeps the kernel capacity lock for the complete slow
operation” with a synchronization gate: have the injected removeObject deletion
pause on a promise, start the concurrent maintain() while that deletion is
blocked, then resolve the gate and await both maintenance operations. Preserve
the existing eviction and committedObjects assertions.
- Around line 160-180: Replace the source-text assertions in the two tests
around Linux lock acquisition and cache scanning with behavioral tests. Reuse
the existing lock/scan instrumentation or injected dependencies to observe
actual lock serialization and scan counts, matching the behaviors covered by
“serializes capacity work through one kernel-held lock” and “keeps the kernel
capacity lock for the complete slow operation”; remove brittle source-string and
occurrence-count checks.

In `@src/materializationService.test.ts`:
- Around line 255-263: Replace the source-text assertion in the “normalizes
source-manifest collision keys before case folding” test with a behavioral case:
construct a manifest containing NFC and NFD spellings of the same path, invoke
the relevant materialization flow, and assert that the collision is rejected.
Preserve the test’s focus on normalization before case folding and avoid
matching implementation source text.

In `@src/rendition.test.ts`:
- Around line 95-118: Extend the test around verifyRenditionOutputFile to also
assert rejection when expectedSha256 differs from the file’s actual sha256Hex,
while keeping expectedBytes equal to bytes.byteLength. Verify the rejection
matches the digest-mismatch error message.

In `@src/sandboxContainer.test.ts`:
- Around line 4-6: Add tests in the “sandbox container cleanup” suite covering
both validation guards in removeSandboxContainer: verify a malformed
containerName rejects with an invalid-name error and does not invoke spawn, and
verify an out-of-range cleanupTimeoutMs rejects with the timeout validation
error. Reuse the existing containerName fixture and preserve current valid
cleanup coverage.

In `@src/sandboxMounts.test.ts`:
- Around line 97-125: Reduce the test fixture size in the concurrent
stageReadonlySandboxFile test while preserving the eight-way race and
completeness assertions; use a payload of only a few MiB instead of 64 MiB so
loser retries do not create excessive memory or disk usage.

In `@src/sandboxMounts.ts`:
- Around line 131-163: Replace the full-buffer read comparison in the EEXIST
handling of the sandbox staging function with a size-first check, then compare
matching files using streaming SHA-256 digests. Add and use a small fileDigest
helper based on createReadStream, createHash, and pipeline; preserve the
existing invalid-destination and mismatch errors while avoiding simultaneous
whole-file buffering.

In `@src/serverArchitecture.test.ts`:
- Around line 153-167: Remove the self-referential architectureTestSource read
and its assertion for `new Bun.Glob('**/*.ts')` from the test `uses recursive
source scanning and a loopback health default`. Keep the meaningful
`serverSource` assertion verifying the `MATERIALIZATION_HEALTH_HOST` loopback
default.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

Comment thread deploy/yucp-materializer.service Outdated
Comment thread src/linuxTreeWorker.py Outdated
Comment thread src/materializationChunkCache.ts Outdated
Comment thread src/sandboxContainer.ts Outdated
Comment thread src/sandboxIsolation.ts
@Yeusepe

Yeusepe commented Jul 27, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@codex review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown

@Yeusepe: Starting a full review of PR #3.

✅ Action performed

Full review finished.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: fa9994ab33

ℹ️ About Codex in GitHub

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

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

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

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (15)
src/nativeHardening.test.ts (1)

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

Regex leaves xg_0115–xg_0121 unchecked.

0[0-9a-f]|1[0-4] stops at xg_0114, so the seven identifiers just below the server range would slip through. The exact-export assertion at Line 269 is a backstop, but a non-exported leftover helper would not be caught.

♻️ Widen the range
-    expect(guardSource).not.toMatch(/\bxg_01(?:0[0-9a-f]|1[0-4])\b/);
+    expect(guardSource).not.toMatch(/\bxg_01(?:0[0-9a-f]|1[0-9a-f]|2[01])\b/);
🤖 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/nativeHardening.test.ts` at line 93, Widen the forbidden-identifier regex
in the guardSource assertion so it also matches xg_0115 through xg_0121, while
preserving the existing coverage for xg_0100 through xg_0114.
src/materializationChunkCache.ts (1)

1146-1163: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Every verified read bumps the global capacity generation, forcing other cache instances to full-rescan.

beginCacheIndexMutation publishes dirty: true with generation + 1 and finishCacheIndexMutation publishes it again — two fsync'd atomic writes plus directory syncs per chunk read. Worse, any other cache instance sharing workRoot sees a new generation and re-runs scanCacheEntries() (stat + metadata read per object) on its next operation. At 256 KiB chunks and multi-GiB packages this is the hot path.

Consider only bumping the generation when the index membership actually changes (add/evict), and publishing lastAccessedAt refreshes as metadata-only writes that consumers merge without invalidating the whole index.

🤖 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/materializationChunkCache.ts` around lines 1146 - 1163, Update the
verified-read path around beginCacheIndexMutation, index.set, and
finishCacheIndexMutation so refreshing lastAccessedAt does not bump the global
generation or trigger full rescans. Keep generation changes and mutation
publication for actual index membership changes such as add or evict, and
persist/read access-time metadata through a metadata-only path that other cache
instances can merge without invalidating the index.
src/linuxRendition.realtest.ts (1)

10-57: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

crc32, pngChunk, makeGrayPng, and sha256Hex are byte-for-byte duplicates of the helpers in src/attribution.test.ts. Both suites depend on the fixture producing a PNG the native codec accepts, so a fix applied to one copy and not the other yields confusing divergent failures. Extract them into a shared test fixture 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/linuxRendition.realtest.ts` around lines 10 - 57, Extract the duplicated
crc32, pngChunk, makeGrayPng, and sha256Hex helpers from
src/linuxRendition.realtest.ts and src/attribution.test.ts into a shared test
fixture module. Update both suites to import and reuse those shared symbols,
preserving the existing PNG generation and hashing behavior.
src/linuxTreeSandbox.test.ts (1)

81-111: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Source-text assertions are brittle. These three tests read linuxTreeSandbox.ts and match literal substrings (including exact formatting like 'await removeSandboxContainer({ containerName }).catch(() => undefined);' and a regex counting '.sandbox-work' occurrences). A reformat or a semantically-equivalent refactor breaks them while a genuine regression (e.g. cleanup rejection propagating) would still pass. Prefer behavioral coverage — runWorker already accepts injectable inputs elsewhere in this suite via spawn.

🤖 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/linuxTreeSandbox.test.ts` around lines 81 - 111, Replace the three
source-text assertions in the linuxTreeSandbox tests with behavioral tests that
exercise runWorker through its injectable spawn dependency. Verify guarded
process input, shared sandbox work-directory behavior, and preservation of the
worker failure when removeSandboxContainer cleanup rejects, without depending on
source formatting or literal occurrence counts.
src/linuxCodecSandbox.ts (1)

301-302: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Default worker path is duplicated. path.join(import.meta.dir, 'linuxCodecWorker.py') appears in both createLinuxCodecRequest and runLinuxCodecSandbox; since the latter always stages and passes an explicit workerPath, the fallback in the former is effectively dead. Extract a single defaultCodecWorkerPath() helper (or drop the fallback) so the two cannot drift.

Also applies to: 479-484

🤖 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/linuxCodecSandbox.ts` around lines 301 - 302, Consolidate the duplicated
linuxCodecWorker.py path handling between createLinuxCodecRequest and
runLinuxCodecSandbox by introducing one shared defaultCodecWorkerPath() helper,
or remove the unreachable fallback from createLinuxCodecRequest. Ensure both
code paths use the same worker path source so the defaults cannot diverge.
src/attribution.test.ts (1)

34-57: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Source-text assertions duplicate the pattern in linuxTreeSandbox.test.ts. Matching literals such as 'await removeSandboxContainer({ containerName }).catch(() => undefined);' and 'frame?.fill(0)' against attribution.ts breaks on reformatting and proves nothing about runtime behavior. Same recommendation as the tree sandbox suite: assert on observable behavior via injected spawn/cleanup doubles.

🤖 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/attribution.test.ts` around lines 34 - 57, Replace the source-text
assertions in the attribution worker tests with runtime behavior tests using
injected spawn and cleanup doubles, following the approach in
linuxTreeSandbox.test.ts. Exercise the attribution worker with controlled
child-process input/output and verify the bounded boundary helpers, named
container cleanup, and frame zeroing through observable calls and effects rather
than matching literals in attribution.ts.
src/linuxCodecSandbox.test.ts (2)

422-433: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Asserting on implementation source text is brittle and proves nothing behavioral.

Slicing linuxCodecSandbox.ts between two export async function markers breaks on any rename, reordering, or formatter change, and it still would not catch lstat being called on the wrong path. Exercise the behavior instead: point normalizedPath at a symlink and assert validateLinuxCodecOutputLengths rejects.

♻️ Sketch
-  it('checks the output entry without following symbolic links', async () => {
-    const source = await Bun.file(
-      new URL('./linuxCodecSandbox.ts', import.meta.url)
-    ).text();
-    const validator = source.slice(
-      source.indexOf('export async function validateLinuxCodecOutputLengths'),
-      source.indexOf('export async function runLinuxCodecSandbox')
-    );
-
-    expect(validator).toContain('await lstat(outputPath)');
-    expect(validator).not.toContain('await stat(outputPath)');
-  });
+  it('rejects an output entry that is a symbolic link', async () => {
+    const root = await mkdtemp(path.join(tmpdir(), 'yucp-codec-output-link-'));
+    try {
+      const target = path.join(root, 'target.bin');
+      await mkdir(path.join(root, 'Assets'), { recursive: true });
+      await writeFile(target, new Uint8Array([1, 2, 3]));
+      await symlink(target, path.join(root, 'Assets', 'output.png'));
+
+      await expect(
+        validateLinuxCodecOutputLengths(root, [
+          {
+            attributionTokenHex: '00'.repeat(8),
+            normalizedPath: 'Assets/output.png',
+            outputBytes: 3,
+            outputSha256: '11'.repeat(32),
+            status: 'coupled',
+          },
+        ])
+      ).rejects.toThrow();
+    } finally {
+      await rm(root, { force: true, recursive: true });
+    }
+  });

(requires importing symlink from node:fs/promises; skip on win32 where symlink creation needs privileges)

🤖 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/linuxCodecSandbox.test.ts` around lines 422 - 433, Replace the
source-text inspection test with a behavioral test for
validateLinuxCodecOutputLengths: create a temporary regular file and a symlink,
set normalizedPath to the symlink, and assert validation rejects it; import
symlink from node:fs/promises and skip the test on win32 where symlink creation
may require privileges.

140-148: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Four near-identical as unknown as casts to reach module internals.

Each call site re-declares a large structural type for the same private functions. Extract one test-local internals accessor (or export the symbols under an __testing namespace) so the shapes are declared once and drift is caught by the compiler.

Also applies to: 183-187, 215-233, 274-292

🤖 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/linuxCodecSandbox.test.ts` around lines 140 - 148, Consolidate the
repeated private-function access in the linuxCodecSandbox tests by defining one
test-local internals accessor with the shared structural type, then reuse it in
the cases around parseLinuxCodecResult and the other noted call sites. Remove
the four duplicated as unknown as declarations while preserving compiler-checked
signatures for all accessed internals.
src/sandboxMounts.test.ts (1)

97-125: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

64 MiB × 8 concurrent stagers risks a flaky, slow test.

With the current dedup path each losing caller buffers two full copies, so this test allocates on the order of a gigabyte and runs under Bun's default per-test timeout. A few MiB is enough to exercise the interleaving; pair it with an explicit timeout.

♻️ Proposed change
-  it('publishes one complete staged file to concurrent callers', async () => {
+  it('publishes one complete staged file to concurrent callers', async () => {
@@
-    const bytes = Buffer.alloc(64 * 1024 * 1024, 0x5a);
+    const bytes = Buffer.alloc(4 * 1024 * 1024, 0x5a);
@@
-  });
+  }, 30_000);
🤖 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/sandboxMounts.test.ts` around lines 97 - 125, Reduce the test fixture in
“publishes one complete staged file to concurrent callers” from 64 MiB to a few
MiB while preserving the concurrent staging assertions, and add an explicit
sufficiently generous timeout to the test so it remains reliable under Bun’s
default limits.
src/sandboxIsolation.test.ts (1)

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

as unknown as cast hides an untyped injection point.

If dockerIsolationArguments genuinely accepts an optional hostIdentity, declare it in the exported parameter type so the test type-checks the real contract; otherwise this test can keep passing after the option is renamed or removed.

🤖 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/sandboxIsolation.test.ts` around lines 51 - 77, Update the exported
parameter type for dockerIsolationArguments to explicitly declare the optional
hostIdentity field, including uid, gid, and platform, so the test can invoke the
real typed contract. Remove the test’s as unknown as cast and preserve the
existing Linux user mapping behavior.
src/sandboxMounts.ts (1)

147-157: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Dedup path loads both files fully into memory.

stageReadonlySandboxFile is used for large artifacts — the test at sandboxMounts.test.ts Line 101 stages 64 MiB with 8 concurrent callers, so seven of them each buffer ~128 MiB simultaneously. Compare size first and then stream a digest instead of readFile.

🛡️ Proposed change
-    const destination = await lstat(destinationPath);
-    if (!destination.isFile() || destination.isSymbolicLink()) {
-      throw new Error('The staged sandbox file is invalid');
-    }
-    const [sourceBytes, destinationBytes] = await Promise.all([
-      readFile(input.sourcePath),
-      readFile(destinationPath),
-    ]);
-    if (!sourceBytes.equals(destinationBytes)) {
-      throw new Error('The staged sandbox file does not match its source');
-    }
+    const destination = await lstat(destinationPath);
+    if (!destination.isFile() || destination.isSymbolicLink()) {
+      throw new Error('The staged sandbox file is invalid');
+    }
+    if (destination.size !== source.size) {
+      throw new Error('The staged sandbox file does not match its source');
+    }
+    const [sourceDigest, destinationDigest] = await Promise.all([
+      streamedSha256(input.sourcePath),
+      streamedSha256(destinationPath),
+    ]);
+    if (sourceDigest !== destinationDigest) {
+      throw new Error('The staged sandbox file does not match its source');
+    }

with a small helper that pipes createReadStream into createHash('sha256').

🤖 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/sandboxMounts.ts` around lines 147 - 157, Update stageReadonlySandboxFile
to avoid loading both artifacts fully into memory: compare source and
destination sizes first, then compute and compare SHA-256 digests by streaming
each file through a small createReadStream/createHash helper. Preserve the
existing invalid-destination validation and mismatch errors, and use the size
check to reject differing files before hashing.
src/sandboxContainer.test.ts (1)

7-30: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the status-1-with-unrelated-stderr case.

The suite pins status 125 (reject) and status 1 + "No such container" (suppress), but nothing asserts that a status 1 with any other stderr still rejects. That is the exact boundary where an over-broad suppression regex would silently swallow real cleanup failures.

🤖 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/sandboxContainer.test.ts` around lines 7 - 30, Add a test alongside the
existing removeSandboxContainer cases that uses exit status 1 with stderr
unrelated to the Docker “No such container” response and asserts rejection with
the cleanup error. Keep the existing status-125 rejection and already-removed
suppression tests unchanged.
src/materializationBuilds.test.ts (2)

53-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Drop the optional-export gymnastics and import the function directly.

The cast plus if (!pin...) return; silently turns a missing export into a passing test, and line 123 already calls it directly off the namespace.

♻️ Proposed simplification
-      const pinMaterializationBuildArtifacts = (
-        materializationBuildsModule as unknown as {
-          pinMaterializationBuildArtifacts?: (input: {
-            runtimePath: string;
-            serviceRoot: string;
-            workRoot: string;
-          }) => Promise<{
-            builds: unknown;
-            runtimePath: string;
-            serviceRoot: string;
-            workerPaths: {
-              attribution: string;
-              codec: string;
-              tree: string;
-            };
-          }>;
-        }
-      ).pinMaterializationBuildArtifacts;
-      expect(pinMaterializationBuildArtifacts).toBeFunction();
-      if (!pinMaterializationBuildArtifacts) {
-        return;
-      }
-      const pinned = await pinMaterializationBuildArtifacts({
+      const pinned = await pinMaterializationBuildArtifacts({

with import { computeMaterializationBuilds, pinMaterializationBuildArtifacts } from './materializationBuilds'; at the top.

🤖 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/materializationBuilds.test.ts` around lines 53 - 74, Import
pinMaterializationBuildArtifacts directly alongside computeMaterializationBuilds
from materializationBuilds, then remove the namespace cast, optional-function
assertion, and early return. Use the direct import in the existing test call so
a missing export causes the test to fail.

233-244: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the symlink rejection behaviorally.

Matching implementation source text passes even if the guard is moved or neutered. Creating a symlinked bun.lock (or runtime .so) and expecting the rejection tests the actual boundary.

🧪 Proposed test
-    const source = await Bun.file(
-      new URL('./materializationBuilds.ts', import.meta.url)
-    ).text();
-    const boundary = source.slice(
-      source.indexOf('async function requiredFileEntry'),
-      source.indexOf('export async function computeMaterializationBuilds')
-    );
-
-    expect(boundary).toContain('await lstat(filePath)');
-    expect(boundary).toContain('details.isSymbolicLink()');
+    const fixture = await createFixture();
+    try {
+      const lockPath = path.join(fixture.root, 'bun.lock');
+      await rm(lockPath);
+      await symlink(path.join(fixture.root, 'package.json'), lockPath);
+      await expect(
+        computeMaterializationBuilds({
+          runtimePath: fixture.runtimePath,
+          serviceRoot: fixture.root,
+        })
+      ).rejects.toThrow('not a regular file');
+    } finally {
+      await rm(fixture.root, { force: true, recursive: true });
+    }

(add symlink to the node:fs/promises 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/materializationBuilds.test.ts` around lines 233 - 244, Replace the
source-text assertions in the test “rejects symbolic links at the required
build-input boundary” with a behavioral test that creates a symlinked required
input such as bun.lock or the runtime .so, invokes the relevant
materialization-build API, and asserts it rejects. Import and use the existing
filesystem promises symlink facility as needed, while preserving cleanup and the
test’s boundary coverage.
src/boundedJson.test.ts (1)

4-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the actual size bound. Only invalid-limit inputs are tested; the streaming cap, the content-length rejection, and invalid UTF-8 are untested despite being the security-relevant behavior.

💚 Suggested additional cases
   );
+
+  it('rejects a body larger than the limit', async () => {
+    await expect(
+      readBoundedJsonResponse(new Response('x'.repeat(64)), {
+        errorPrefix: 'Control-plane response',
+        maximumBytes: 8,
+      })
+    ).rejects.toThrow('too large');
+  });
+
+  it('rejects an oversized declared content length', async () => {
+    await expect(
+      readBoundedJsonResponse(
+        new Response('{}', { headers: { 'content-length': '9999' } }),
+        { errorPrefix: 'Control-plane response', maximumBytes: 8 }
+      )
+    ).rejects.toThrow('too large');
+  });
 });
🤖 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/boundedJson.test.ts` around lines 4 - 16, Add tests for
readBoundedJsonResponse covering the valid maximumBytes streaming cap, rejection
when content-length exceeds the limit, and invalid UTF-8 response bodies; assert
each case rejects with the appropriate error while preserving the existing
invalid-limit coverage.
🤖 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 @.env.example:
- Line 16: The MATERIALIZATION_MASTER_EPOCH_KEYS_FILE example must include the
systemd unit directory in the credential path. Update its value to point under
/run/credentials/yucp-materializer.service/ while preserving the
materialization-master-epochs filename.

In `@deploy/initialize-local-master-credential.test.sh`:
- Line 9: Update the test script’s PATH setup to avoid hard-coded host-specific
directories, preserving the existing environment PATH and only adding the
required Bun location through a portable mechanism. Ensure the script works
across CI and developer machines where Bun may be installed in different
locations.

In `@deploy/run-local-wsl-materializer.sh`:
- Around line 122-134: Add an explicit Bash version guard before the wait block
containing wait -n -p, requiring Bash 5.1 or newer and exiting with a clear
error message when unsupported. Keep the existing completed_pid and proxy
termination handling unchanged for supported Bash versions.

In `@src/linuxTreeSandbox.ts`:
- Around line 162-190: Update the outer error-handling flow around the worker
interaction in the relevant sandbox method so every failure from writing input,
collecting output, non-zero exit handling, or JSON.parse is wrapped in
LinuxTreeSandboxFailure. Remove the inner write-specific wrapping and preserve
cleanup via removeSandboxContainer before rethrowing the classified failure.

In `@src/linuxWorkers_test.py`:
- Around line 195-196: Update the test around worker.main() to assert the
specific exception expected from the umask failure, such as struct.error or
ValueError, rather than the broad Exception type. Use assertRaisesRegex if the
error message must also be validated, while preserving the existing test setup
and execution path.
- Around line 191-193: Update the test setup in
test_tree_worker_sets_a_private_creation_mask and the corresponding setup around
the second os.umask assignment to patch the process-wide os.umask with
addCleanup restoration. Also restore sys.stdin through the existing
replace_stdin cleanup mechanism instead of assigning it directly, while
preserving the tests’ observation behavior.

In `@src/sandboxImage.ts`:
- Around line 2-3: Update the PINNED_PYTHON_IMAGE constant to use the official
published digest for python:3.13.11-slim-bookworm, keeping the existing tag and
pinned-image format unchanged.

In `@src/sandboxMounts.ts`:
- Around line 77-104: Update exposeWritableEntry so the 0o777 directory and
0o666 file permission widening is applied only on non-Linux platforms, while
preserving the existing traversal and validation behavior. Use the
platform-detection mechanism already used by dockerIsolationArguments rather
than changing prepareWritableSandboxDirectory.

---

Nitpick comments:
In `@src/attribution.test.ts`:
- Around line 34-57: Replace the source-text assertions in the attribution
worker tests with runtime behavior tests using injected spawn and cleanup
doubles, following the approach in linuxTreeSandbox.test.ts. Exercise the
attribution worker with controlled child-process input/output and verify the
bounded boundary helpers, named container cleanup, and frame zeroing through
observable calls and effects rather than matching literals in attribution.ts.

In `@src/boundedJson.test.ts`:
- Around line 4-16: Add tests for readBoundedJsonResponse covering the valid
maximumBytes streaming cap, rejection when content-length exceeds the limit, and
invalid UTF-8 response bodies; assert each case rejects with the appropriate
error while preserving the existing invalid-limit coverage.

In `@src/linuxCodecSandbox.test.ts`:
- Around line 422-433: Replace the source-text inspection test with a behavioral
test for validateLinuxCodecOutputLengths: create a temporary regular file and a
symlink, set normalizedPath to the symlink, and assert validation rejects it;
import symlink from node:fs/promises and skip the test on win32 where symlink
creation may require privileges.
- Around line 140-148: Consolidate the repeated private-function access in the
linuxCodecSandbox tests by defining one test-local internals accessor with the
shared structural type, then reuse it in the cases around parseLinuxCodecResult
and the other noted call sites. Remove the four duplicated as unknown as
declarations while preserving compiler-checked signatures for all accessed
internals.

In `@src/linuxCodecSandbox.ts`:
- Around line 301-302: Consolidate the duplicated linuxCodecWorker.py path
handling between createLinuxCodecRequest and runLinuxCodecSandbox by introducing
one shared defaultCodecWorkerPath() helper, or remove the unreachable fallback
from createLinuxCodecRequest. Ensure both code paths use the same worker path
source so the defaults cannot diverge.

In `@src/linuxRendition.realtest.ts`:
- Around line 10-57: Extract the duplicated crc32, pngChunk, makeGrayPng, and
sha256Hex helpers from src/linuxRendition.realtest.ts and
src/attribution.test.ts into a shared test fixture module. Update both suites to
import and reuse those shared symbols, preserving the existing PNG generation
and hashing behavior.

In `@src/linuxTreeSandbox.test.ts`:
- Around line 81-111: Replace the three source-text assertions in the
linuxTreeSandbox tests with behavioral tests that exercise runWorker through its
injectable spawn dependency. Verify guarded process input, shared sandbox
work-directory behavior, and preservation of the worker failure when
removeSandboxContainer cleanup rejects, without depending on source formatting
or literal occurrence counts.

In `@src/materializationBuilds.test.ts`:
- Around line 53-74: Import pinMaterializationBuildArtifacts directly alongside
computeMaterializationBuilds from materializationBuilds, then remove the
namespace cast, optional-function assertion, and early return. Use the direct
import in the existing test call so a missing export causes the test to fail.
- Around line 233-244: Replace the source-text assertions in the test “rejects
symbolic links at the required build-input boundary” with a behavioral test that
creates a symlinked required input such as bun.lock or the runtime .so, invokes
the relevant materialization-build API, and asserts it rejects. Import and use
the existing filesystem promises symlink facility as needed, while preserving
cleanup and the test’s boundary coverage.

In `@src/materializationChunkCache.ts`:
- Around line 1146-1163: Update the verified-read path around
beginCacheIndexMutation, index.set, and finishCacheIndexMutation so refreshing
lastAccessedAt does not bump the global generation or trigger full rescans. Keep
generation changes and mutation publication for actual index membership changes
such as add or evict, and persist/read access-time metadata through a
metadata-only path that other cache instances can merge without invalidating the
index.

In `@src/nativeHardening.test.ts`:
- Line 93: Widen the forbidden-identifier regex in the guardSource assertion so
it also matches xg_0115 through xg_0121, while preserving the existing coverage
for xg_0100 through xg_0114.

In `@src/sandboxContainer.test.ts`:
- Around line 7-30: Add a test alongside the existing removeSandboxContainer
cases that uses exit status 1 with stderr unrelated to the Docker “No such
container” response and asserts rejection with the cleanup error. Keep the
existing status-125 rejection and already-removed suppression tests unchanged.

In `@src/sandboxIsolation.test.ts`:
- Around line 51-77: Update the exported parameter type for
dockerIsolationArguments to explicitly declare the optional hostIdentity field,
including uid, gid, and platform, so the test can invoke the real typed
contract. Remove the test’s as unknown as cast and preserve the existing Linux
user mapping behavior.

In `@src/sandboxMounts.test.ts`:
- Around line 97-125: Reduce the test fixture in “publishes one complete staged
file to concurrent callers” from 64 MiB to a few MiB while preserving the
concurrent staging assertions, and add an explicit sufficiently generous timeout
to the test so it remains reliable under Bun’s default limits.

In `@src/sandboxMounts.ts`:
- Around line 147-157: Update stageReadonlySandboxFile to avoid loading both
artifacts fully into memory: compare source and destination sizes first, then
compute and compare SHA-256 digests by streaming each file through a small
createReadStream/createHash helper. Preserve the existing invalid-destination
validation and mismatch errors, and use the size check to reject differing files
before hashing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 15e26669-d006-42f3-bd7d-cc70a2ec80fc

📥 Commits

Reviewing files that changed from the base of the PR and between 2beeaf2 and fa9994a.

⛔ Files ignored due to path filters (18)
  • bun.lock is excluded by !**/*.lock
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/out/copilot-probe/coupling_runtime.obj is excluded by !**/*.obj
  • yucp_coupling/out/copilot-probe/yucp_coupling.dll is excluded by !**/*.dll
  • yucp_coupling/out/linux-x64/Release/runtime-helper/yucp_coupling.so is excluded by !**/*.so
  • yucp_coupling/out/linux-x64/Release/yucp_coupling.so is excluded by !**/*.so
  • yucp_coupling/out/win-x64/Release/coupling_runtime.obj is excluded by !**/*.obj
  • yucp_coupling/out/win-x64/Release/runtime-helper/coupling_runtime.obj is excluded by !**/*.obj
  • yucp_coupling/out/win-x64/Release/runtime-helper/yucp_coupling.dll is excluded by !**/*.dll
  • yucp_coupling/out/win-x64/Release/yucp_coupling.dll is excluded by !**/*.dll
  • yucp_coupling/yucp_coupling.exports.map is excluded by !**/*.map
📒 Files selected for processing (229)
  • .env.example
  • deploy/initialize-local-master-credential.sh
  • deploy/initialize-local-master-credential.test.sh
  • deploy/prepare-local-wsl-materializer.sh
  • deploy/run-local-wsl-materializer.sh
  • deploy/run-local-wsl-materializer.test.ts
  • deploy/yucp-materializer.service
  • package.json
  • src/attestation.native.test.ts
  • src/attestation.test.ts
  • src/attestation.ts
  • src/attribution.test.ts
  • src/attribution.ts
  • src/boundedJson.test.ts
  • src/boundedJson.ts
  • src/codecLimits.test.ts
  • src/codecLimits.ts
  • src/config.ts
  • src/couplingSeed.test.ts
  • src/couplingSeed.ts
  • src/dpopSigner.test.ts
  • src/dpopSigner.ts
  • src/env.test.ts
  • src/env.ts
  • src/ffi.test.ts
  • src/ffi.ts
  • src/keyBroker.test.ts
  • src/keyBroker.ts
  • src/linuxAttributionWorker.py
  • src/linuxCodecSandbox.test.ts
  • src/linuxCodecSandbox.ts
  • src/linuxCodecWorker.py
  • src/linuxRendition.realtest.ts
  • src/linuxTreeSandbox.test.ts
  • src/linuxTreeSandbox.ts
  • src/linuxTreeWorker.py
  • src/linuxWorkers.test.ts
  • src/linuxWorkers_test.py
  • src/materializationBuilds.test.ts
  • src/materializationBuilds.ts
  • src/materializationChunkCache.test.ts
  • src/materializationChunkCache.ts
  • src/materializationServer.test.ts
  • src/materializationServer.ts
  • src/materializationService.test.ts
  • src/materializationService.ts
  • src/materialize.test.ts
  • src/materialize.ts
  • src/nativeExports.test.ts
  • src/nativeHardening.test.ts
  • src/nativePlatform.test.ts
  • src/nativePlatform.ts
  • src/publicAuthority.ts
  • src/rendition.test.ts
  • src/rendition.ts
  • src/roundtrip.test.ts
  • src/runtimeArtifacts.test.ts
  • src/runtimeArtifacts.ts
  • src/runtimeTokens.test.ts
  • src/runtimeTokens.ts
  • src/sandboxContainer.test.ts
  • src/sandboxContainer.ts
  • src/sandboxImage.test.ts
  • src/sandboxImage.ts
  • src/sandboxIsolation.test.ts
  • src/sandboxIsolation.ts
  • src/sandboxMounts.test.ts
  • src/sandboxMounts.ts
  • src/server.nativeAvailability.test.ts
  • src/server.ts
  • src/serverArchitecture.test.ts
  • src/yucpTrust.ts
  • src/zipArchive.test.ts
  • src/zipArchive.ts
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/dynamic/libsodium.ilk
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/static/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/dynamic/libsodium.ilk
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/static/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/ltcg/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/static/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/ltcg/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/static/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/core.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_aead_aegis128l.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_aead_aegis256.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_aead_aes256gcm.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_aead_chacha20poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_aead_xchacha20poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_auth.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_auth_hmacsha256.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_auth_hmacsha512.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_auth_hmacsha512256.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_box.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_box_curve25519xchacha20poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_box_curve25519xsalsa20poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_core_ed25519.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_core_hchacha20.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_core_hsalsa20.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_core_ristretto255.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_core_salsa20.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_core_salsa2012.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_core_salsa208.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_generichash.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_generichash_blake2b.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_hash.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_hash_sha256.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_hash_sha512.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_kdf.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_kdf_blake2b.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_kdf_hkdf_sha256.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_kdf_hkdf_sha512.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_kx.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_onetimeauth.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_onetimeauth_poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_pwhash.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_pwhash_argon2i.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_pwhash_argon2id.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_pwhash_scryptsalsa208sha256.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_scalarmult.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_scalarmult_curve25519.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_scalarmult_ed25519.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_scalarmult_ristretto255.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_secretbox.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_secretbox_xchacha20poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_secretbox_xsalsa20poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_secretstream_xchacha20poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_shorthash.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_shorthash_siphash24.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_sign.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_sign_ed25519.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_sign_edwards25519sha512batch.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_stream.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_stream_chacha20.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_stream_salsa20.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_stream_salsa2012.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_stream_salsa208.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_stream_xchacha20.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_stream_xsalsa20.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_verify_16.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_verify_32.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_verify_64.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/export.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/randombytes.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/randombytes_internal_random.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/randombytes_sysrandom.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/runtime.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/utils.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/version.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/dynamic/libsodium.ilk
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/static/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/dynamic/libsodium.ilk
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/static/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/ltcg/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/static/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/ltcg/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/static/libsodium.pdb
  • yucp_coupling/_deps/minhook/include/MinHook.h
  • yucp_coupling/_deps/minhook/src/buffer.c
  • yucp_coupling/_deps/minhook/src/buffer.h
  • yucp_coupling/_deps/minhook/src/hde/hde32.c
  • yucp_coupling/_deps/minhook/src/hde/hde32.h
  • yucp_coupling/_deps/minhook/src/hde/hde64.c
  • yucp_coupling/_deps/minhook/src/hde/hde64.h
  • yucp_coupling/_deps/minhook/src/hde/pstdint.h
  • yucp_coupling/_deps/minhook/src/hde/table32.h
  • yucp_coupling/_deps/minhook/src/hde/table64.h
  • yucp_coupling/_deps/minhook/src/hook.c
  • yucp_coupling/_deps/minhook/src/trampoline.c
  • yucp_coupling/_deps/minhook/src/trampoline.h
  • yucp_coupling/build.ps1
  • yucp_coupling/build.sh
  • yucp_coupling/coupling_runtime.c
  • yucp_coupling/coupling_runtime.exp
  • yucp_coupling/coupling_runtime.lib
  • yucp_coupling/guard.c
  • yucp_coupling/out/copilot-probe/yucp_coupling.compile.pdb
  • yucp_coupling/out/copilot-probe/yucp_coupling.pdb
  • yucp_coupling/out/copilot-probe/yucp_coupling.sha256
  • yucp_coupling/out/linux-x64/Release/runtime-helper/yucp_coupling.sha256
  • yucp_coupling/out/linux-x64/Release/yucp_coupling.sha256
  • yucp_coupling/out/win-x64/Release/runtime-helper/yucp_coupling.compile.pdb
  • yucp_coupling/out/win-x64/Release/runtime-helper/yucp_coupling.pdb
  • yucp_coupling/out/win-x64/Release/runtime-helper/yucp_coupling.sha256
  • yucp_coupling/out/win-x64/Release/yucp_coupling.compile.pdb
  • yucp_coupling/out/win-x64/Release/yucp_coupling.pdb
  • yucp_coupling/out/win-x64/Release/yucp_coupling.sha256
  • yucp_coupling/yucp_coupling.def
  • yucp_coupling/yucp_coupling.runtime-helper.def
💤 Files with no reviewable changes (17)
  • src/nativeExports.test.ts
  • src/publicAuthority.ts
  • src/attestation.native.test.ts
  • src/nativePlatform.test.ts
  • src/nativePlatform.ts
  • src/runtimeArtifacts.test.ts
  • src/roundtrip.test.ts
  • src/materialize.test.ts
  • src/materialize.ts
  • src/env.ts
  • src/ffi.test.ts
  • src/ffi.ts
  • src/attestation.test.ts
  • src/config.ts
  • src/attestation.ts
  • src/couplingSeed.ts
  • src/couplingSeed.test.ts

Comment thread .env.example Outdated
Comment thread deploy/initialize-local-master-credential.test.sh Outdated
Comment thread deploy/run-local-wsl-materializer.sh
Comment thread src/linuxTreeSandbox.ts
Comment thread src/linuxWorkers_test.py Outdated
Comment thread src/linuxWorkers_test.py Outdated
Comment thread src/sandboxImage.ts
Comment thread src/sandboxMounts.ts

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dd9caeca54

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/materializationService.ts Outdated
@Yeusepe

Yeusepe commented Jul 27, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@codex review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown

@Yeusepe: Starting a full review of PR #3.

✅ Action performed

Full review finished.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dd9caeca54

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread .env.example Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
deploy/yucp-materializer.service (1)

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

WorkingDirectory still points to /opt/yucp/ca-coupling, inconsistent with the rest of this PR's deployment paths.

.env.example in this same PR sets MATERIALIZATION_RUNTIME_LIBRARY_PATH=/home/yucp/ca-coupling/..., and this same unit uses %h elsewhere (lines 11, 17) implying the checkout lives under the service user's home directory. With WorkingDirectory=/opt/yucp/ca-coupling, ExecStart's bun run start will fail to locate package.json/the entrypoint unless a separate, undocumented step populates /opt/yucp/ca-coupling. A prior review flagged this exact mismatch and it was marked as addressed in earlier commits, but the final state still shows the old value.

🔧 Proposed fix
-WorkingDirectory=/opt/yucp/ca-coupling
+WorkingDirectory=%h/ca-coupling
#!/bin/bash
# Confirm the deployment checkout path used elsewhere in the repo.
rg -n "ca-coupling" -C1 deploy .env.example 2>/dev/null
🤖 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 `@deploy/yucp-materializer.service` at line 9, Update the WorkingDirectory
setting in the deploy/yucp-materializer.service unit to use the checkout path
under the service user's home directory, consistent with the %h-based paths and
MATERIALIZATION_RUNTIME_LIBRARY_PATH. Keep ExecStart unchanged so bun runs from
the corrected project directory.
🧹 Nitpick comments (21)
src/sandboxMounts.test.ts (1)

111-139: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

64 MiB × 8 concurrent copies is a heavy fixture for a unit test. This writes ~0.5 GiB to tmpdir() per run and will be slow on constrained CI. A few MiB is usually enough to keep the copy window open; consider shrinking unless the large size is required to reproduce the race reliably.

🤖 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/sandboxMounts.test.ts` around lines 111 - 139, Reduce the `bytes` fixture
in `publishes one complete staged file to concurrent callers` from 64 MiB to a
few MiB while preserving the concurrent-copy assertions and race coverage; no
larger size is needed unless required by the reproduction.
src/sandboxIsolation.test.ts (1)

25-49: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Platform-conditional expectation mirrors the implementation. expectedUser recomputes the same branch as sandboxUserIdentity, so this case can't catch a flipped mapping. The third test pins Linux; consider adding a platform: 'win32' case pinning 65534:65534 and dropping the conditional here.

🤖 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/sandboxIsolation.test.ts` around lines 25 - 49, Update the
rootless-daemon identity test to use a fixed expected identity instead of
recomputing the platform branch from sandboxUserIdentity. Add a separate test
invoking dockerIsolationArguments with platform set to win32 and assert the user
identity is 65534:65534, while retaining the Linux case assertion for 0:0.
src/materializationServer.test.ts (2)

795-991: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

This case asserts ~8 independent contracts in ~200 lines. Health, subject-prepare authz, malformed subject bodies, attribution success, malformed candidates, legacy-route 404, and source-text checks are all in one it. A failure anywhere hides the rest. Splitting per contract (sharing the handler via a factory) would localize failures.

🤖 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/materializationServer.test.ts` around lines 795 - 991, Split the
monolithic test around the health and authenticated subject preparation flow
into separate tests for each independent contract: health, subject
authorization, malformed subject bodies, successful attribution, malformed
candidates, legacy-route rejection, and source-text assertions. Add a shared
handler factory or equivalent setup so each test can create the required handler
without duplicating configuration, while preserving all existing assertions and
behavior.

321-382: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Wall-clock lease tests are flaky under load. These rely on real timers with narrow margins (1 s lease vs. renewal deadline, 4.5 s safety abort inside a 6 s timeout, Bun.sleep(leaseDurationMs + 100)), so a stalled CI runner can produce spurious failures rather than real regressions. Consider injecting a clock/timer seam into the worker so the renewal scheduler can be driven deterministically.

Also applies to: 461-527, 529-606, 608-647

🤖 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/materializationServer.test.ts` around lines 321 - 382, Replace wall-clock
timing in the lease-related tests around createMaterializationWorker with an
injectable clock/timer seam in the worker. Update the renewal scheduler and
affected tests at the referenced scenarios to use controlled time advancement
rather than real sleeps, narrow deadlines, or safety abort timeouts, while
preserving assertions that renewal occurs before expiry and lease expiration
behavior remains correct.
src/linuxAttributionWorker.py (3)

226-235: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Ruff BLE001 here is a false positive. This is the process-level error serializer that converts any failure into the structured stderr frame the TypeScript side parses; narrowing it would break the contract. Consider # noqa: BLE001 with a short reason to keep the lint clean.

🤖 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/linuxAttributionWorker.py` around lines 226 - 235, Add a targeted `#
noqa: BLE001` with a brief justification to the process-level `except Exception
as error` handler surrounding `main()`, preserving broad exception capture so
every failure remains serialized into the structured stderr contract.

Source: Linters/SAST tools


196-198: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Use hmac.compare_digest for the token-hash equality. != on the hex strings short-circuits on the first differing character. The exposure is limited here, but constant-time comparison is the idiomatic default for authentication-token material.

♻️ Proposed change
+import hmac
...
-                token_hash = hashlib.sha256(token_hex.encode("ascii")).hexdigest()
-                if token_hash != candidate.get("attributionTokenHash"):
+                token_hash = hashlib.sha256(token_hex.encode("ascii")).hexdigest()
+                if not hmac.compare_digest(
+                    token_hash, candidate.get("attributionTokenHash")
+                ):
                     continue
🤖 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/linuxAttributionWorker.py` around lines 196 - 198, Replace the direct
token-hash inequality check in the attribution-token matching loop with
hmac.compare_digest, ensuring the hashlib-generated token_hash is compared
against candidate.get("attributionTokenHash") using a constant-time equality
result while preserving the existing continue behavior for mismatches.

43-48: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The 512/4096 bounds are unreachable once the product guard applies. With MAX_CANDIDATE_EVALUATIONS = 64 and both lists required to be non-empty, len(assets) can never exceed 64. The wider limits read as a contract that the worker does not actually honor; consider dropping them to the effective bound or adding a comment explaining they mirror attribution.ts.

🤖 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/linuxAttributionWorker.py` around lines 43 - 48, The validation around
the assets and candidates bounds is inconsistent with MAX_CANDIDATE_EVALUATIONS:
because both lists are non-empty, the product guard already limits each list to
at most 64. Update the checks to reflect the effective worker contract, or add a
concise comment documenting that the 512/4096 limits intentionally mirror
attribution.ts.
src/linuxCodecSandbox.test.ts (1)

422-433: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Source-text assertion is brittle. Slicing between two indexOf hits on function names silently degrades if either function is renamed or reordered (indexOf returns -1, and slice then covers the wrong span). Prefer asserting the behavior — e.g. point normalizedPath at a symlink to an oversized file and expect the length check to fail — rather than grepping the implementation.

🤖 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/linuxCodecSandbox.test.ts` around lines 422 - 433, Replace the
source-text assertion in the “checks the output entry without following symbolic
links” test with a behavioral test: create a symlink at normalizedPath pointing
to an oversized file, run validateLinuxCodecOutputLengths, and assert that
validation fails based on the link’s own metadata. Remove the fragile
indexOf/slice inspection and direct lstat/stat string checks.
src/sandboxMounts.ts (1)

169-179: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

The dedup path reads both files fully into memory. For the worker/runtime binaries this stages (tens of MiB), every concurrent loser allocates two full buffers and reads 2× the file. Since link() publishes atomically and the destination is 0444, comparing size plus a streamed SHA-256 — or simply trusting the existing entry — would avoid the allocation spike without weakening the check.

♻️ Cheaper equivalence check
-    const [sourceBytes, destinationBytes] = await Promise.all([
-      readFile(input.sourcePath),
-      readFile(destinationPath),
-    ]);
-    if (!sourceBytes.equals(destinationBytes)) {
+    if (destination.size !== source.size) {
+      throw new Error('The staged sandbox file does not match its source');
+    }
+    const [sourceDigest, destinationDigest] = await Promise.all([
+      fileSha256(input.sourcePath),
+      fileSha256(destinationPath),
+    ]);
+    if (sourceDigest !== destinationDigest) {
       throw new Error('The staged sandbox file does not match its source');
     }
🤖 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/sandboxMounts.ts` around lines 169 - 179, Update the deduplication
validation around destination and source reads to avoid loading both files fully
into memory; compare file sizes first and compute a streamed SHA-256 for each
file only when needed, preserving rejection of mismatched staged files and the
existing atomic publish behavior.
src/linuxRendition.realtest.ts (1)

115-123: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

runtimePath is resolved identically in all five tests. Hoist it to a module-level constant.

♻️ Proposed change
+const RUNTIME_PATH = path.resolve(
+  import.meta.dir,
+  '..',
+  'yucp_coupling',
+  'out',
+  'linux-x64',
+  'Release',
+  'yucp_coupling.so'
+);

Also applies to: 196-204, 255-263, 310-318, 365-373

🤖 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/linuxRendition.realtest.ts` around lines 115 - 123, Hoist the shared
runtimePath resolution into a module-level constant in
src/linuxRendition.realtest.ts, then replace the duplicated local declarations
in all five tests with that constant while preserving the existing resolved
path.
src/linuxWorkers.test.ts (1)

34-35: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Ran 12 tests breaks whenever a worker test is added. It currently matches, but consider asserting on /Ran \d+ tests/ plus OK instead of a fixed count.

♻️ Proposed change
-    expect(`${stdout}${stderr}`).toContain('Ran 12 tests');
+    expect(`${stdout}${stderr}`).toMatch(/Ran \d+ tests/);
+    expect(`${stdout}${stderr}`).toContain('OK');
     expect(exitCode).toBe(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 `@src/linuxWorkers.test.ts` around lines 34 - 35, Update the linux worker test
assertion combining stdout and stderr to match the dynamic test summary with a
/Ran \d+ tests/ pattern and separately assert that the output contains OK, while
preserving the exitCode success assertion.
src/linuxCodecSandbox.ts (1)

117-152: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

frame.fill(0) runs in two nested finally blocks. writeLinuxCodecProcessInput already zeroes the frame, so runLinuxCodecContainer's own finally is redundant (and zeroes a buffer the caller may still hold a reference to for other reasons). Keep ownership in one place.

Also applies to: 149-151, 440-442

🤖 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/linuxCodecSandbox.ts` around lines 117 - 152, Remove the redundant frame
cleanup from the finally block in runLinuxCodecContainer and any corresponding
duplicate locations, leaving writeLinuxCodecProcessInput as the sole owner of
frame.fill(0). Preserve the existing process cleanup and error-handling behavior
while avoiding mutation of a caller-held buffer outside that function.
src/linuxTreeSandbox.ts (1)

154-198: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Three nested try blocks for two concerns. The inner classify-only try can be folded into the middle one:

♻️ Proposed change
   try {
     try {
-      try {
-        ...
-        return JSON.parse(stdout) as T;
-      } catch (cause) {
-        throw classifyLinuxTreeSandboxFailure(cause);
-      }
+      ...
+      return JSON.parse(stdout) as T;
     } catch (cause) {
       await removeSandboxContainer({ containerName }).catch(() => undefined);
-      throw cause;
+      throw classifyLinuxTreeSandboxFailure(cause);
     }
   } finally {
🤖 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/linuxTreeSandbox.ts` around lines 154 - 198, Fold the classify-only inner
try/catch into the surrounding cleanup try in the Linux tree worker flow. Keep
the tree process execution and result handling unchanged, ensure
removeSandboxContainer still runs on failures, and classify the caught cause via
classifyLinuxTreeSandboxFailure before rethrowing it.
yucp_coupling/build.sh (1)

49-63: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Consider pinning/verifying the vendored libsodium.a checksum.

The build hard-fails if the vendored archive is missing but doesn't verify its contents. Given this PR already computes and publishes a SHA-256 for the built runtime, extending the same discipline to the vendored static dependency would catch accidental corruption or tampering before it's silently linked into the shipped artifact.

🔧 Proposed check
 if [ ! -f "$SODIUM_PREFIX/lib/libsodium.a" ] || [ ! -d "$SODIUM_PREFIX/include" ]; then
   echo "Missing the vendored Linux x64 libsodium dependency" >&2
   exit 1
 fi
+EXPECTED_SODIUM_SHA256="<pin-the-known-good-hash-here>"
+ACTUAL_SODIUM_SHA256="$(sha256sum "$SODIUM_PREFIX/lib/libsodium.a" | awk '{print $1}')"
+if [ "$ACTUAL_SODIUM_SHA256" != "$EXPECTED_SODIUM_SHA256" ]; then
+  echo "Vendored libsodium.a checksum mismatch" >&2
+  exit 1
+fi
🤖 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 `@yucp_coupling/build.sh` around lines 49 - 63, Extend the dependency
validation around SODIUM_LINK in build.sh to verify the vendored libsodium.a
against a pinned SHA-256 checksum before linking it. Add or reuse a checked-in
expected checksum and fail with a clear error when the archive is missing or its
digest does not match, while preserving the existing include-directory
validation and runtime checksum publication.
src/boundedJson.test.ts (1)

4-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extend coverage to the actual size bound.

Only the limit-validation guard is exercised. The security-relevant paths — oversized streamed body (no content-length), spoofed/oversized content-length, invalid UTF-8, and a successful parse — are untested.

🤖 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/boundedJson.test.ts` around lines 4 - 16, Extend the bounded JSON
response tests around readBoundedJsonResponse to cover an oversized streamed
body without content-length, a spoofed or oversized content-length header,
invalid UTF-8 input, and a valid response that parses successfully. Assert each
relevant rejection or successful parsed result, while retaining the existing
maximumBytes validation coverage.
src/keyBroker.ts (1)

69-88: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Zeroize already-parsed epoch keys when a later entry is rejected.

Line 79 wipes only the offending key; keys accepted earlier in the loop stay resident after the throw. Cheap to make the whole failure path clean.

🔒 Proposed fix
   const keys = new Map<number, Uint8Array>();
-  for (const [epochText, encoded] of Object.entries(parsed)) {
-    if (
-      !/^(0|[1-9]\d*)$/.test(epochText) ||
-      typeof encoded !== 'string' ||
-      !BASE64URL_PATTERN.test(encoded)
-    ) {
-      throw new Error('Materialization master epoch credential has an invalid entry');
-    }
-    const key = Buffer.from(encoded, 'base64url');
-    if (key.byteLength !== SHA256_BYTES) {
-      key.fill(0);
-      throw new Error('Materialization master epoch credential has an invalid entry');
-    }
-    keys.set(Number(epochText), key);
-  }
+  try {
+    for (const [epochText, encoded] of Object.entries(parsed)) {
+      if (
+        !/^(0|[1-9]\d*)$/.test(epochText) ||
+        typeof encoded !== 'string' ||
+        !BASE64URL_PATTERN.test(encoded)
+      ) {
+        throw new Error('Materialization master epoch credential has an invalid entry');
+      }
+      const key = Buffer.from(encoded, 'base64url');
+      if (key.byteLength !== SHA256_BYTES) {
+        key.fill(0);
+        throw new Error('Materialization master epoch credential has an invalid entry');
+      }
+      keys.set(Number(epochText), key);
+    }
+  } catch (error) {
+    for (const key of keys.values()) {
+      key.fill(0);
+    }
+    throw error;
+  }
🤖 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/keyBroker.ts` around lines 69 - 88, Update the parsed-key validation loop
in the credential parsing function to zeroize every key already stored in keys
before throwing when a later entry is invalid or has the wrong length. Preserve
wiping the offending key and ensure the no-keys error path remains unchanged.
src/rendition.ts (1)

300-343: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

request.arguments/containerName are only meaningful for the injected runCodec path.

When input.runCodec is absent, runLinuxCodecSandbox builds its own invocation and this LinuxCodecRequest is discarded, so the docker run ... argv here is effectively test-only scaffolding that can drift from the real sandbox command. Consider building the request only when input.runCodec is set.

🤖 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/rendition.ts` around lines 300 - 343, Update the codec batch loop around
runRenditionStage so LinuxCodecRequest is constructed only when input.runCodec
is provided. Pass that request to input.runCodec in the injected path, while
leaving the runLinuxCodecSandbox path to build its own invocation without
request.arguments or containerName scaffolding.
src/materializationService.ts (1)

1730-1732: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use byte ordering instead of localeCompare for chunk ids.

String.prototype.localeCompare is locale/ICU-dependent; every other ordering in this stack (compareNormalizedPathsUtf8, compareUtf8 in materializationChunkCache.ts) uses UTF-8 byte comparison. Only download batching order depends on it today, but the inconsistency is easy to remove.

♻️ Proposed change
-  const chunks = [...uniqueChunks.values()].sort((left, right) =>
-    left.id.localeCompare(right.id)
-  );
+  const chunks = [...uniqueChunks.values()].sort((left, right) =>
+    compareNormalizedPathsUtf8(left.id, right.id)
+  );
🤖 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/materializationService.ts` around lines 1730 - 1732, Update the chunk
sorting in the download batching flow around uniqueChunks to use the existing
UTF-8 byte comparison utility instead of left.id.localeCompare(right.id). Match
the ordering behavior used by compareNormalizedPathsUtf8 and compareUtf8,
preserving ascending chunk-id order.
src/materializationChunkCache.ts (1)

438-449: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The composed in-process signal is never aborted, so signal.throwIfAborted() is dead.

withInProcessFileLock hands the operation a fresh new AbortController().signal that nothing ever aborts, so Line 446 can never throw and the kernel-lock abort signal (from withLinuxFileLock) is the only live one. Harmless today, but it reads as if queue cancellation is wired up when it isn't.

🤖 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/materializationChunkCache.ts` around lines 438 - 449, Update
withDefaultFileLock to remove the ineffective signal.throwIfAborted() call from
the withInProcessFileLock callback, since that composed signal is never aborted.
Preserve the existing withLinuxFileLock invocation and lock behavior on Linux,
as well as the direct in-process path on other platforms.
src/materializationChunkCache.test.ts (1)

258-278: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

These assert on implementation source text rather than behavior.

toContain('withInProcessFileLock(lockPath, (signal) =>') and the scanCacheEntries() occurrence count break on any harmless rename/reformat while proving nothing about runtime behavior. The lock-composition property is already observable (a single kernel lock per path, in-process queueing), and the rescan property could be asserted by counting readdir/stat calls through an injected dependency.

🤖 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/materializationChunkCache.test.ts` around lines 258 - 278, Replace the
source-text assertions in the tests around the lock and capacity behavior with
runtime-focused tests. Exercise the materialization cache’s lock acquisition to
verify concurrent operations on the same path share a single kernel lock while
remaining queued in process, and inject or spy on the filesystem dependency to
assert capacity decisions do not repeatedly call readdir/stat for every
committed object. Remove brittle checks based on withInProcessFileLock,
scanCacheEntries(), and capacity-generation.json source-text counts.
src/materializationService.test.ts (1)

481-510: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a rejecting case to the order-independence test.

The test only proves reordered-but-matching input passes; a assertProtectedFilesMatch that ignored sha256/materializerType entirely would still pass. A second expectation with a mismatched digest (or duplicate normalizedPath) would lock the contract down.

🤖 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/materializationService.test.ts` around lines 481 - 510, The
order-independence test around assertProtectedFilesMatch should also verify
rejection when matched paths have an incorrect sha256 or materializerType, such
as by adding a second expectation that throws for a mismatched digest or
duplicate normalizedPath. Keep the existing reordered matching assertion
unchanged.
🤖 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/attribution.ts`:
- Around line 304-310: Update the cleanup finally block around
restorePrivateSandboxTree so a rejection from restoring inputDirectory is
swallowed, preserving the original operation failure. Keep scratch removal in
its nested finally so rm still runs regardless of restore success, matching the
existing non-throwing cleanup behavior of removeSandboxContainer.

In `@src/sandboxMounts.ts`:
- Around line 69-82: Update restorePrivateSandboxEntry, used by
restorePrivateSandboxTree cleanup, so symlinks and other non-regular entries are
skipped or otherwise handled without throwing. Preserve the existing ENOENT
tolerance and ensure cleanup failures do not replace the original worker error
from finally blocks.

---

Duplicate comments:
In `@deploy/yucp-materializer.service`:
- Line 9: Update the WorkingDirectory setting in the
deploy/yucp-materializer.service unit to use the checkout path under the service
user's home directory, consistent with the %h-based paths and
MATERIALIZATION_RUNTIME_LIBRARY_PATH. Keep ExecStart unchanged so bun runs from
the corrected project directory.

---

Nitpick comments:
In `@src/boundedJson.test.ts`:
- Around line 4-16: Extend the bounded JSON response tests around
readBoundedJsonResponse to cover an oversized streamed body without
content-length, a spoofed or oversized content-length header, invalid UTF-8
input, and a valid response that parses successfully. Assert each relevant
rejection or successful parsed result, while retaining the existing maximumBytes
validation coverage.

In `@src/keyBroker.ts`:
- Around line 69-88: Update the parsed-key validation loop in the credential
parsing function to zeroize every key already stored in keys before throwing
when a later entry is invalid or has the wrong length. Preserve wiping the
offending key and ensure the no-keys error path remains unchanged.

In `@src/linuxAttributionWorker.py`:
- Around line 226-235: Add a targeted `# noqa: BLE001` with a brief
justification to the process-level `except Exception as error` handler
surrounding `main()`, preserving broad exception capture so every failure
remains serialized into the structured stderr contract.
- Around line 196-198: Replace the direct token-hash inequality check in the
attribution-token matching loop with hmac.compare_digest, ensuring the
hashlib-generated token_hash is compared against
candidate.get("attributionTokenHash") using a constant-time equality result
while preserving the existing continue behavior for mismatches.
- Around line 43-48: The validation around the assets and candidates bounds is
inconsistent with MAX_CANDIDATE_EVALUATIONS: because both lists are non-empty,
the product guard already limits each list to at most 64. Update the checks to
reflect the effective worker contract, or add a concise comment documenting that
the 512/4096 limits intentionally mirror attribution.ts.

In `@src/linuxCodecSandbox.test.ts`:
- Around line 422-433: Replace the source-text assertion in the “checks the
output entry without following symbolic links” test with a behavioral test:
create a symlink at normalizedPath pointing to an oversized file, run
validateLinuxCodecOutputLengths, and assert that validation fails based on the
link’s own metadata. Remove the fragile indexOf/slice inspection and direct
lstat/stat string checks.

In `@src/linuxCodecSandbox.ts`:
- Around line 117-152: Remove the redundant frame cleanup from the finally block
in runLinuxCodecContainer and any corresponding duplicate locations, leaving
writeLinuxCodecProcessInput as the sole owner of frame.fill(0). Preserve the
existing process cleanup and error-handling behavior while avoiding mutation of
a caller-held buffer outside that function.

In `@src/linuxRendition.realtest.ts`:
- Around line 115-123: Hoist the shared runtimePath resolution into a
module-level constant in src/linuxRendition.realtest.ts, then replace the
duplicated local declarations in all five tests with that constant while
preserving the existing resolved path.

In `@src/linuxTreeSandbox.ts`:
- Around line 154-198: Fold the classify-only inner try/catch into the
surrounding cleanup try in the Linux tree worker flow. Keep the tree process
execution and result handling unchanged, ensure removeSandboxContainer still
runs on failures, and classify the caught cause via
classifyLinuxTreeSandboxFailure before rethrowing it.

In `@src/linuxWorkers.test.ts`:
- Around line 34-35: Update the linux worker test assertion combining stdout and
stderr to match the dynamic test summary with a /Ran \d+ tests/ pattern and
separately assert that the output contains OK, while preserving the exitCode
success assertion.

In `@src/materializationChunkCache.test.ts`:
- Around line 258-278: Replace the source-text assertions in the tests around
the lock and capacity behavior with runtime-focused tests. Exercise the
materialization cache’s lock acquisition to verify concurrent operations on the
same path share a single kernel lock while remaining queued in process, and
inject or spy on the filesystem dependency to assert capacity decisions do not
repeatedly call readdir/stat for every committed object. Remove brittle checks
based on withInProcessFileLock, scanCacheEntries(), and capacity-generation.json
source-text counts.

In `@src/materializationChunkCache.ts`:
- Around line 438-449: Update withDefaultFileLock to remove the ineffective
signal.throwIfAborted() call from the withInProcessFileLock callback, since that
composed signal is never aborted. Preserve the existing withLinuxFileLock
invocation and lock behavior on Linux, as well as the direct in-process path on
other platforms.

In `@src/materializationServer.test.ts`:
- Around line 795-991: Split the monolithic test around the health and
authenticated subject preparation flow into separate tests for each independent
contract: health, subject authorization, malformed subject bodies, successful
attribution, malformed candidates, legacy-route rejection, and source-text
assertions. Add a shared handler factory or equivalent setup so each test can
create the required handler without duplicating configuration, while preserving
all existing assertions and behavior.
- Around line 321-382: Replace wall-clock timing in the lease-related tests
around createMaterializationWorker with an injectable clock/timer seam in the
worker. Update the renewal scheduler and affected tests at the referenced
scenarios to use controlled time advancement rather than real sleeps, narrow
deadlines, or safety abort timeouts, while preserving assertions that renewal
occurs before expiry and lease expiration behavior remains correct.

In `@src/materializationService.test.ts`:
- Around line 481-510: The order-independence test around
assertProtectedFilesMatch should also verify rejection when matched paths have
an incorrect sha256 or materializerType, such as by adding a second expectation
that throws for a mismatched digest or duplicate normalizedPath. Keep the
existing reordered matching assertion unchanged.

In `@src/materializationService.ts`:
- Around line 1730-1732: Update the chunk sorting in the download batching flow
around uniqueChunks to use the existing UTF-8 byte comparison utility instead of
left.id.localeCompare(right.id). Match the ordering behavior used by
compareNormalizedPathsUtf8 and compareUtf8, preserving ascending chunk-id order.

In `@src/rendition.ts`:
- Around line 300-343: Update the codec batch loop around runRenditionStage so
LinuxCodecRequest is constructed only when input.runCodec is provided. Pass that
request to input.runCodec in the injected path, while leaving the
runLinuxCodecSandbox path to build its own invocation without request.arguments
or containerName scaffolding.

In `@src/sandboxIsolation.test.ts`:
- Around line 25-49: Update the rootless-daemon identity test to use a fixed
expected identity instead of recomputing the platform branch from
sandboxUserIdentity. Add a separate test invoking dockerIsolationArguments with
platform set to win32 and assert the user identity is 65534:65534, while
retaining the Linux case assertion for 0:0.

In `@src/sandboxMounts.test.ts`:
- Around line 111-139: Reduce the `bytes` fixture in `publishes one complete
staged file to concurrent callers` from 64 MiB to a few MiB while preserving the
concurrent-copy assertions and race coverage; no larger size is needed unless
required by the reproduction.

In `@src/sandboxMounts.ts`:
- Around line 169-179: Update the deduplication validation around destination
and source reads to avoid loading both files fully into memory; compare file
sizes first and compute a streamed SHA-256 for each file only when needed,
preserving rejection of mismatched staged files and the existing atomic publish
behavior.

In `@yucp_coupling/build.sh`:
- Around line 49-63: Extend the dependency validation around SODIUM_LINK in
build.sh to verify the vendored libsodium.a against a pinned SHA-256 checksum
before linking it. Add or reuse a checked-in expected checksum and fail with a
clear error when the archive is missing or its digest does not match, while
preserving the existing include-directory validation and runtime checksum
publication.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e5a2d8ff-77ac-4979-b733-902629ed2cef

📥 Commits

Reviewing files that changed from the base of the PR and between 2beeaf2 and dd9caec.

⛔ Files ignored due to path filters (18)
  • bun.lock is excluded by !**/*.lock
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/dynamic/libsodium.dll is excluded by !**/*.dll
  • yucp_coupling/out/copilot-probe/coupling_runtime.obj is excluded by !**/*.obj
  • yucp_coupling/out/copilot-probe/yucp_coupling.dll is excluded by !**/*.dll
  • yucp_coupling/out/linux-x64/Release/runtime-helper/yucp_coupling.so is excluded by !**/*.so
  • yucp_coupling/out/linux-x64/Release/yucp_coupling.so is excluded by !**/*.so
  • yucp_coupling/out/win-x64/Release/coupling_runtime.obj is excluded by !**/*.obj
  • yucp_coupling/out/win-x64/Release/runtime-helper/coupling_runtime.obj is excluded by !**/*.obj
  • yucp_coupling/out/win-x64/Release/runtime-helper/yucp_coupling.dll is excluded by !**/*.dll
  • yucp_coupling/out/win-x64/Release/yucp_coupling.dll is excluded by !**/*.dll
  • yucp_coupling/yucp_coupling.exports.map is excluded by !**/*.map
📒 Files selected for processing (229)
  • .env.example
  • deploy/initialize-local-master-credential.sh
  • deploy/initialize-local-master-credential.test.sh
  • deploy/prepare-local-wsl-materializer.sh
  • deploy/run-local-wsl-materializer.sh
  • deploy/run-local-wsl-materializer.test.ts
  • deploy/yucp-materializer.service
  • package.json
  • src/attestation.native.test.ts
  • src/attestation.test.ts
  • src/attestation.ts
  • src/attribution.test.ts
  • src/attribution.ts
  • src/boundedJson.test.ts
  • src/boundedJson.ts
  • src/codecLimits.test.ts
  • src/codecLimits.ts
  • src/config.ts
  • src/couplingSeed.test.ts
  • src/couplingSeed.ts
  • src/dpopSigner.test.ts
  • src/dpopSigner.ts
  • src/env.test.ts
  • src/env.ts
  • src/ffi.test.ts
  • src/ffi.ts
  • src/keyBroker.test.ts
  • src/keyBroker.ts
  • src/linuxAttributionWorker.py
  • src/linuxCodecSandbox.test.ts
  • src/linuxCodecSandbox.ts
  • src/linuxCodecWorker.py
  • src/linuxRendition.realtest.ts
  • src/linuxTreeSandbox.test.ts
  • src/linuxTreeSandbox.ts
  • src/linuxTreeWorker.py
  • src/linuxWorkers.test.ts
  • src/linuxWorkers_test.py
  • src/materializationBuilds.test.ts
  • src/materializationBuilds.ts
  • src/materializationChunkCache.test.ts
  • src/materializationChunkCache.ts
  • src/materializationServer.test.ts
  • src/materializationServer.ts
  • src/materializationService.test.ts
  • src/materializationService.ts
  • src/materialize.test.ts
  • src/materialize.ts
  • src/nativeExports.test.ts
  • src/nativeHardening.test.ts
  • src/nativePlatform.test.ts
  • src/nativePlatform.ts
  • src/publicAuthority.ts
  • src/rendition.test.ts
  • src/rendition.ts
  • src/roundtrip.test.ts
  • src/runtimeArtifacts.test.ts
  • src/runtimeArtifacts.ts
  • src/runtimeTokens.test.ts
  • src/runtimeTokens.ts
  • src/sandboxContainer.test.ts
  • src/sandboxContainer.ts
  • src/sandboxImage.test.ts
  • src/sandboxImage.ts
  • src/sandboxIsolation.test.ts
  • src/sandboxIsolation.ts
  • src/sandboxMounts.test.ts
  • src/sandboxMounts.ts
  • src/server.nativeAvailability.test.ts
  • src/server.ts
  • src/serverArchitecture.test.ts
  • src/yucpTrust.ts
  • src/zipArchive.test.ts
  • src/zipArchive.ts
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/dynamic/libsodium.ilk
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v142/static/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/dynamic/libsodium.ilk
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Debug/v143/static/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/ltcg/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v142/static/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/ltcg/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/Win32/Release/v143/static/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/core.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_aead_aegis128l.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_aead_aegis256.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_aead_aes256gcm.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_aead_chacha20poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_aead_xchacha20poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_auth.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_auth_hmacsha256.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_auth_hmacsha512.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_auth_hmacsha512256.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_box.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_box_curve25519xchacha20poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_box_curve25519xsalsa20poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_core_ed25519.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_core_hchacha20.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_core_hsalsa20.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_core_ristretto255.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_core_salsa20.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_core_salsa2012.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_core_salsa208.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_generichash.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_generichash_blake2b.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_hash.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_hash_sha256.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_hash_sha512.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_kdf.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_kdf_blake2b.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_kdf_hkdf_sha256.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_kdf_hkdf_sha512.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_kx.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_onetimeauth.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_onetimeauth_poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_pwhash.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_pwhash_argon2i.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_pwhash_argon2id.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_pwhash_scryptsalsa208sha256.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_scalarmult.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_scalarmult_curve25519.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_scalarmult_ed25519.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_scalarmult_ristretto255.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_secretbox.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_secretbox_xchacha20poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_secretbox_xsalsa20poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_secretstream_xchacha20poly1305.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_shorthash.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_shorthash_siphash24.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_sign.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_sign_ed25519.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_sign_edwards25519sha512batch.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_stream.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_stream_chacha20.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_stream_salsa20.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_stream_salsa2012.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_stream_salsa208.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_stream_xchacha20.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_stream_xsalsa20.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_verify_16.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_verify_32.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/crypto_verify_64.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/export.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/randombytes.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/randombytes_internal_random.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/randombytes_sysrandom.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/runtime.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/utils.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/include/sodium/version.h
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/dynamic/libsodium.ilk
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v142/static/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/dynamic/libsodium.ilk
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Debug/v143/static/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/ltcg/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v142/static/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/dynamic/libsodium.exp
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/dynamic/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/dynamic/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/ltcg/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/ltcg/libsodium.pdb
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/static/libsodium.lib
  • yucp_coupling/_deps/libsodium-msvc/libsodium/x64/Release/v143/static/libsodium.pdb
  • yucp_coupling/_deps/minhook/include/MinHook.h
  • yucp_coupling/_deps/minhook/src/buffer.c
  • yucp_coupling/_deps/minhook/src/buffer.h
  • yucp_coupling/_deps/minhook/src/hde/hde32.c
  • yucp_coupling/_deps/minhook/src/hde/hde32.h
  • yucp_coupling/_deps/minhook/src/hde/hde64.c
  • yucp_coupling/_deps/minhook/src/hde/hde64.h
  • yucp_coupling/_deps/minhook/src/hde/pstdint.h
  • yucp_coupling/_deps/minhook/src/hde/table32.h
  • yucp_coupling/_deps/minhook/src/hde/table64.h
  • yucp_coupling/_deps/minhook/src/hook.c
  • yucp_coupling/_deps/minhook/src/trampoline.c
  • yucp_coupling/_deps/minhook/src/trampoline.h
  • yucp_coupling/build.ps1
  • yucp_coupling/build.sh
  • yucp_coupling/coupling_runtime.c
  • yucp_coupling/coupling_runtime.exp
  • yucp_coupling/coupling_runtime.lib
  • yucp_coupling/guard.c
  • yucp_coupling/out/copilot-probe/yucp_coupling.compile.pdb
  • yucp_coupling/out/copilot-probe/yucp_coupling.pdb
  • yucp_coupling/out/copilot-probe/yucp_coupling.sha256
  • yucp_coupling/out/linux-x64/Release/runtime-helper/yucp_coupling.sha256
  • yucp_coupling/out/linux-x64/Release/yucp_coupling.sha256
  • yucp_coupling/out/win-x64/Release/runtime-helper/yucp_coupling.compile.pdb
  • yucp_coupling/out/win-x64/Release/runtime-helper/yucp_coupling.pdb
  • yucp_coupling/out/win-x64/Release/runtime-helper/yucp_coupling.sha256
  • yucp_coupling/out/win-x64/Release/yucp_coupling.compile.pdb
  • yucp_coupling/out/win-x64/Release/yucp_coupling.pdb
  • yucp_coupling/out/win-x64/Release/yucp_coupling.sha256
  • yucp_coupling/yucp_coupling.def
  • yucp_coupling/yucp_coupling.runtime-helper.def
💤 Files with no reviewable changes (16)
  • src/publicAuthority.ts
  • src/nativePlatform.ts
  • src/materialize.test.ts
  • src/couplingSeed.ts
  • src/env.ts
  • src/couplingSeed.test.ts
  • src/attestation.test.ts
  • src/roundtrip.test.ts
  • src/nativeExports.test.ts
  • src/attestation.native.test.ts
  • src/ffi.test.ts
  • src/materialize.ts
  • src/nativePlatform.test.ts
  • src/attestation.ts
  • src/config.ts
  • src/ffi.ts

Comment thread src/attribution.ts Outdated
Comment thread src/sandboxMounts.ts

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 595bbf4c1c

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/materializationService.ts
Comment thread src/materializationServer.ts Outdated
@Yeusepe

Yeusepe commented Jul 27, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@codex review

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown

@Yeusepe: Starting a review of PR #3.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 595bbf4c1c

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/materializationChunkCache.ts Outdated
@Yeusepe

Yeusepe commented Jul 27, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review
@codex review

Please review the latest incremental fixes for Linux workspace admission, committed-result finality, cleanup diagnostics, portable runtime configuration, safe sandbox restoration, and zero-byte cache reservations.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown

@Yeusepe: Reviewing the latest incremental fixes, with particular attention to the listed Linux admission, finality, cleanup, runtime portability, sandbox restoration, and zero-byte reservation paths.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0b96749349

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread src/materializationChunkCache.ts
@Yeusepe

Yeusepe commented Jul 27, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026 •

Copy link
Copy Markdown

@Yeusepe: Starting a review of the latest changes.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/materializationWorkspaceCapacity.test.ts (1)

99-114: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add a failure-path reservation test
Exercise operation throwing so this cleanup path is covered and quota reuse after a failed archive write is protected.

🤖 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/materializationWorkspaceCapacity.test.ts` around lines 99 - 114, Add a
test alongside the existing withWorkspaceReservation success case that makes the
operation callback throw, asserts the error propagates, and verifies the
reservation is released so a subsequent reservation can reuse the quota. Use the
existing cache, workspace paths, and event tracking in the test setup, and
retain cleanup in the finally block.
src/linuxTreeWorker.py (1)

232-241: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Redundant full re-read to compute the rendition digest.

The archive is fully streamed/written once during build_rendition, then re-read in its entirety here just to compute renditionSha256/renditionBytes. Tracking a running hashlib.sha256() and byte counter inside BoundedArchiveFile.write() would give the same deterministic result without a second full-file I/O pass — relevant for large renditions on this hot path.

♻️ Sketch: hash incrementally in the wrapper
 class BoundedArchiveFile:
     def __init__(self, output, maximum_bytes):
         self.output = output
         self.maximum_bytes = maximum_bytes
+        self.digest = hashlib.sha256()
+        self.size = 0

     def write(self, data):
         if self.output.tell() + len(data) > self.maximum_bytes:
             raise ValueError("rendition archive exceeds its reserved capacity")
-        return self.output.write(data)
+        written = self.output.write(data)
+        self.digest.update(data)
+        self.size += len(data)
+        return written

Then use bounded_archive.digest.hexdigest() / bounded_archive.size after the with zipfile.ZipFile(...) block instead of re-reading RENDITION_PATH.

🤖 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/linuxTreeWorker.py` around lines 232 - 241, Update
BoundedArchiveFile.write() to maintain a running hashlib.sha256 digest and byte
count for every written chunk, exposing them through the wrapper’s digest and
size state. In build_rendition, use bounded_archive.digest.hexdigest() and
bounded_archive.size after the ZipFile write completes, and remove the full-file
re-read used to compute renditionSha256 and renditionBytes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/linuxTreeWorker.py`:
- Around line 232-241: Update BoundedArchiveFile.write() to maintain a running
hashlib.sha256 digest and byte count for every written chunk, exposing them
through the wrapper’s digest and size state. In build_rendition, use
bounded_archive.digest.hexdigest() and bounded_archive.size after the ZipFile
write completes, and remove the full-file re-read used to compute
renditionSha256 and renditionBytes.

In `@src/materializationWorkspaceCapacity.test.ts`:
- Around line 99-114: Add a test alongside the existing withWorkspaceReservation
success case that makes the operation callback throw, asserts the error
propagates, and verifies the reservation is released so a subsequent reservation
can reuse the quota. Use the existing cache, workspace paths, and event tracking
in the test setup, and retain cleanup in the finally block.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d7142fed-33dc-4f51-89ea-24c3c1149932

📥 Commits

Reviewing files that changed from the base of the PR and between dd9caec and d1a2d2a.

📒 Files selected for processing (16)
  • .env.example
  • src/attribution.test.ts
  • src/attribution.ts
  • src/linuxTreeSandbox.ts
  • src/linuxTreeWorker.py
  • src/materializationChunkCache.test.ts
  • src/materializationChunkCache.ts
  • src/materializationServer.test.ts
  • src/materializationServer.ts
  • src/materializationService.test.ts
  • src/materializationService.ts
  • src/materializationWorkspaceCapacity.test.ts
  • src/rendition.ts
  • src/sandboxMounts.test.ts
  • src/sandboxMounts.ts
  • src/serverArchitecture.test.ts
🚧 Files skipped from review as they are similar to previous changes (12)
  • src/attribution.test.ts
  • src/sandboxMounts.test.ts
  • src/materializationService.test.ts
  • src/sandboxMounts.ts
  • src/rendition.ts
  • src/serverArchitecture.test.ts
  • src/materializationServer.test.ts
  • src/attribution.ts
  • src/materializationChunkCache.test.ts
  • src/materializationChunkCache.ts
  • src/materializationServer.ts
  • src/materializationService.ts

@Yeusepe
Yeusepe merged commit a18a1bd into main Jul 27, 2026
1 check passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant