Skip to content

feat: deliver Phase 2 envelope and intake foundations - #123

Merged
aryeko merged 40 commits into
mainfrom
feat/phase-2-envelope-intake
Jul 31, 2026
Merged

feat: deliver Phase 2 envelope and intake foundations#123
aryeko merged 40 commits into
mainfrom
feat/phase-2-envelope-intake

Conversation

@aryeko

@aryeko aryeko commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

  • deliver the Phase 2 work-source contract, envelope policy composition, and exact provider admission foundation
  • add the fail-closed structured JSON source provider and immutable qualification-certificate boundary
  • add local-file ledger, registry, intake, preflight, witness, snapshot, and artifact provider oracles with crash/recovery and hostile-input coverage
  • record the mandatory qualification and GF-023/GF-024 completion tasks that must close before Phase 3 starts

Verification

  • pnpm delivery:check — PASS (48 stories, 7 phases)
  • pnpm check — PASS (21/21 tasks)
  • git diff --check — PASS
  • exact reviewed head: 34f29484a29436447c76f381e2f9c940493bfd05

Required before Phase 3

  • resolve JIG_DATA_HOME in the execution environment, provision ${JIG_DATA_HOME}/work-sources/work-plan.json, and record current exact GF-020 source/provider qualification evidence
  • resolve JIG_WITNESS_ROOT to an absolute canonical independently administered mount, verify backup/filesystem separation, and record current GF-025/GF-026 qualification evidence
  • complete GF-023 and GF-024 and close the declared Phase 2 exit gate

The durable task record is in docs/delivery/README.md. This PR intentionally keeps all unqualified providers unavailable and does not claim that the Phase 2 exit gate is closed.

aryeko added 30 commits July 31, 2026 12:39
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This change adds runtime source, envelope, provider-admission, and qualification contracts. It adds conformance provenance tracking and filesystem-backed local providers with witnesses, recovery, artifact lifecycle controls, package boundary enforcement, and Phase 2 delivery requirements.

Changes

Runtime contracts and qualification

Layer / File(s) Summary
Source, envelope, policy, and ledger contracts
packages/runtime-contracts/src/*, packages/runtime-contracts/tests/*
Adds canonical source and envelope validation, digest binding, bounded policies, scripted execution, evidence handling, immutable results, and preflight readback.
Provider admission and certificates
packages/runtime-contracts/src/provider.ts, packages/runtime-contracts/src/qualification-*.ts, packages/runtime-contracts/tests/provider-admission.test.mjs
Adds authority-bound admission, structured-file qualification gates, certificate validation, retry lineage, and disabled-provider eligibility.
Conformance provenance
packages/conformance/src/*, packages/conformance/tests/conformance.test.mjs
Adds exact provider observations, private recorder provenance, structured-file qualification execution, and opaque certificate checks.

Local-file providers

Layer / File(s) Summary
Filesystem confinement and witnesses
packages/local-file-providers/src/path-confinement.ts, packages/local-file-providers/src/local-file-witness.ts
Adds canonical JSON handling, hashing, confined paths, independent roots, secure reads, exclusive writes, and immutable witness chains.
Ledger and supporting stores
packages/local-file-providers/src/local-file-{ledger,intake,preflight,registry,snapshot}.ts, packages/local-file-providers/tests/local-file-ledger.test.mjs
Adds witnessed persistence, fencing, readback, recovery, immutable replay, registry journaling, and structured failures.
Artifact lifecycle
packages/local-file-providers/src/local-file-artifact.ts, packages/local-file-providers/tests/local-file-artifact.test.mjs
Adds protected and disposable routing, two-pin transitions, durable operations, reconciliation, disposal guards, snapshots, and integrity checks.

Package wiring and delivery

Layer / File(s) Summary
Package integration and delivery gate
packages/local-file-providers/package.json, packages/local-file-providers/src/index.ts, packages/runtime-contracts/package.json, tools/repo-guard/*, docs/delivery/README.md
Adds package exports, native capability allowances, restricted qualification-certificate imports, local provider entry points, and mandatory Phase 2 exit requirements.

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 10.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main Phase 2 envelope and intake foundation changes.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/phase-2-envelope-intake

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

@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: d17a269c0b

ℹ️ 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 packages/local-file-providers/src/path-confinement.ts
Comment thread packages/local-file-providers/src/path-confinement.ts Outdated
Comment thread packages/local-file-providers/src/local-file-registry.ts
Comment thread packages/local-file-providers/src/local-file-preflight.ts
Comment thread packages/local-file-providers/src/local-file-snapshot.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: 17

Note

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

🟡 Minor comments (6)
tools/repo-guard/bin/check-package-boundaries.mjs-203-211 (1)

203-211: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

The dist check accepts traversal outside dist.

startsWith('./dist/') accepts ./dist/../src/internal.js. The error text states that exports must resolve only from dist, but the check does not prevent escape. Reject any target that contains a .. segment.

Also note two fail-closed effects of plainExportMap: a nested conditional export map (for example { "./x": { "import": "./dist/x.js" } }) and a null target both produce the "exports must resolve only from dist" error. Confirm that no package needs those forms.

🛡️ Proposed fix
+const distTarget = (value) => value.startsWith('./dist/') && !value.split('/').includes('..');
+
 function plainExportMap(value) {
   return (
     typeof value === 'object' &&
     value !== null &&
     !Array.isArray(value) &&
     Object.values(value).every((entry) => typeof entry === 'string')
   );
 }
   if (
     manifest.exports &&
     !(
-      (typeof manifest.exports === 'string' && manifest.exports.startsWith('./dist/')) ||
-      (plainExportMap(manifest.exports) &&
-        Object.values(manifest.exports).every((value) => value.startsWith('./dist/')))
+      (typeof manifest.exports === 'string' && distTarget(manifest.exports)) ||
+      (plainExportMap(manifest.exports) && Object.values(manifest.exports).every(distTarget))
     )
   )

Also applies to: 236-243

🤖 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 `@tools/repo-guard/bin/check-package-boundaries.mjs` around lines 203 - 211,
Update the dist-target validation in the manifest exports check to reject any
export target containing a parent-directory (“..”) path segment, not merely
targets prefixed with “./dist/”, so paths cannot escape dist. Preserve the
existing fail-closed behavior of plainExportMap for nested conditional maps and
null targets, and verify that no supported package relies on those forms.
docs/delivery/README.md-102-103 (1)

102-103: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the repeated "either".

The sentence uses "either" twice for a three-part condition. Use "any" for both parts.

📝 Proposed wording
-- Complete GF-023 and GF-024 from those current qualification facts. Phase 3 is not ready while
-  either external qualification task or either remaining Phase 2 story is incomplete.
+- Complete GF-023 and GF-024 from those current qualification facts. Phase 3 is not ready while
+  any external qualification task or any remaining Phase 2 story is incomplete.
🤖 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 `@docs/delivery/README.md` around lines 102 - 103, Update the Phase 3 readiness
sentence in the delivery documentation to replace both uses of “either” with
“any,” preserving the existing three-part incompleteness condition and wording
otherwise.

Source: Linters/SAST tools

packages/local-file-providers/tests/local-file-provider.test.mjs-6-15 (1)

6-15: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Assert that encodeSourceRequest succeeded in the helper.

The helper reads .value without checking ok. If encodeSourceRequest rejects an out-of-range deadline or limit itself, request(...) returns undefined. The negative assertions at Line 41 then pass because the frame is undefined, not because validateStructuredFileSourceRequest enforced its bounds. Assert the encode result so each case exercises the adapter bound.

💚 Proposed fix
-const request = (deadline, limit) =>
-  runtime.encodeSourceRequest({
-    version: 'jig.source.v1',
-    sourceIdentity: 'source/structured-json-file-source',
-    basis: { track: 'track/one' },
-    track: 'track/one',
-    deadline,
-    retry: { ordinal: 0, limit },
-    predecessor: null,
-  }).value;
+const request = (deadline, limit) => {
+  const encoded = runtime.encodeSourceRequest({
+    version: 'jig.source.v1',
+    sourceIdentity: 'source/structured-json-file-source',
+    basis: { track: 'track/one' },
+    track: 'track/one',
+    deadline,
+    retry: { ordinal: 0, limit },
+    predecessor: null,
+  });
+  assert.equal(encoded.ok, true);
+  return encoded.value;
+};

If encodeSourceRequest legitimately rejects these values, split the suite: assert the encoder failure for those inputs, and keep validateStructuredFileSourceRequest cases that the encoder accepts.

🤖 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 `@packages/local-file-providers/tests/local-file-provider.test.mjs` around
lines 6 - 15, Update the request helper around encodeSourceRequest to assert its
result is successful before reading .value, so rejected deadline or limit inputs
fail the test instead of producing undefined. If the encoder rejects those
boundary inputs, separate the encoder-failure assertions from validator cases
using values encodeSourceRequest accepts.
packages/runtime-contracts/package.json-6-9 (1)

6-9: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Enforce the qualification trust boundary for relative imports. src/index.ts does not re-export executionClaims or certificateClaims. The allow-list restricts the friend subpath to jig-conformance, and pnpm check runs the guard. However, the guard scans only src/**/*.[cm]?ts and skips all relative imports. This test bypasses the friend-subpath check with ../../runtime-contracts/dist/qualification-registry.js. Replace this access with an approved friend helper, or extend the guard and document this deliberate white-box exception.

🤖 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 `@packages/runtime-contracts/package.json` around lines 6 - 9, Enforce the
qualification trust boundary by updating
packages/conformance/tests/conformance.test.mjs:620-622 to use the approved
qualification friend helper/subpath instead of the relative
qualification-registry dist import. packages/runtime-contracts/package.json:6-9
and packages/runtime-contracts/src/qualification-certificate.ts:1-1 require no
direct change unless the helper is not currently exported; in that case, expose
the approved helper through the existing qualification-certificate entry point
rather than permitting the relative import.
packages/runtime-contracts/tests/source-plan-public.test.mjs-8-14 (1)

8-14: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

This test does not prove special-key rejection.

Adding a key only to policy.capacities makes the capacity map larger than the reserve map. parsePlan then fails on the cardinality rule Object.keys(capacities).length !== Object.keys(reserves).length, before any key name is examined. The assertion therefore passes for every added key name, including a benign one such as cpu2.

To test the key-name rule, add the matching reserves entry. Be aware that constructor matches the current resource-key regex /^[a-z][a-z0-9-]{0,31}$/u in packages/runtime-contracts/src/source.ts Line 291, so the strengthened test states the intended contract and may require an implementation change.

💚 Proposed stronger test
   for (const key of ['__proto__', 'constructor', 'prototype']) {
     const value = structuredClone(plan);
     Object.defineProperty(value.policy.capacities, key, { value: 2, enumerable: true });
+    Object.defineProperty(value.policy.reserves, key, { value: 1, enumerable: true });
     assert.equal(runtime.validateSourcePlan(value).ok, 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 `@packages/runtime-contracts/tests/source-plan-public.test.mjs` around lines 8
- 14, Update the test around “R03 second-review RED” to add a matching entry in
policy.reserves for each injected special key, keeping capacities and reserves
cardinalities equal so validation reaches key-name checking. Ensure the test
still expects validation to fail for __proto__, constructor, and prototype, and
update the source-plan key validation used by parsePlan if needed so all three
special keys are rejected despite the current resource-key regex.
packages/runtime-contracts/tests/provider-admission.test.mjs-74-83 (1)

74-83: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Two assertions pass for a reason other than the stated one.

Line 80 sets maxAgeMs: 1 while observedAt - stored.observedAt is 100. The call therefore fails the age check as well as the non-positive outcome check, so the assertion does not isolate the outcome rule.

Line 82 passes only two keys to admit. fields(input, ['basis','maxAgeMs','observedAt','proof']) returns undefined for that shape, so the result is INVALID_ADMISSION, not the intended capability mismatch. The basis-mismatch path stays uncovered.

Assert the error codes so each rule is checked separately.

💚 Proposed fix
-    assert.equal(bounded.admit({ basis, proof: proof.value, observedAt: 1_200, maxAgeMs: 1 }).ok, false, outcome);
+    assert.deepEqual(
+      bounded.admit({ basis, proof: proof.value, observedAt: 1_200, maxAgeMs: 86_400_000 }),
+      { ok: false, error: { family: 'FC-AUTHORITY', code: 'POSITIVE_EXACT_PROOF_REQUIRED' } },
+      outcome,
+    );
   }
-  assert.equal(fixture.admit({ basis: { ...basis, capability: 'capability/other' }, proof: started.value }).ok, false);
+  assert.deepEqual(
+    fixture.admit({
+      basis: { ...basis, capability: 'capability/other' },
+      proof: started.value,
+      observedAt: 1_200,
+      maxAgeMs: 86_400_000,
+    }),
+    { ok: false, error: { family: 'FC-AUTHORITY', code: 'POSITIVE_EXACT_PROOF_REQUIRED' } },
+  );
🤖 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 `@packages/runtime-contracts/tests/provider-admission.test.mjs` around lines 74
- 83, Update the admission assertions in the provider admission test to isolate
each rule and verify its error code: use a maxAgeMs value that permits the 100ms
age difference when testing negative, timeout, and exhausted outcomes, then
assert the corresponding non-positive-outcome rejection code. For the capability
mismatch case, pass a complete admission input including proof, observedAt, and
maxAgeMs, and assert the capability-mismatch error code rather than only
checking ok is false.
🧹 Nitpick comments (27)
packages/local-file-providers/tests/local-file-provider.test.mjs (1)

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

Add the inclusive boundary cases.

validateStructuredFileSourceRequest accepts SOURCE_WAIT_MIN_MS, SOURCE_WAIT_MAX_MS, SOURCE_RETRY_MIN, and SOURCE_RETRY_MAX. The suite tests only the values just outside those bounds. Add the exact accepted endpoints so an off-by-one change to a comparison operator fails the suite.

💚 Proposed addition
 test('the adapter boundary retains the fixed wait and retry ranges', () => {
   assert.equal(provider.validateStructuredFileSourceRequest(request(900_000, 3)).ok, true);
+  for (const [wait, retry] of [
+    [5_000, 3],
+    [7_200_000, 3],
+    [900_000, 1],
+    [900_000, 5],
+  ])
+    assert.equal(provider.validateStructuredFileSourceRequest(request(wait, retry)).ok, true);
   for (const [wait, retry] of [
🤖 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 `@packages/local-file-providers/tests/local-file-provider.test.mjs` around
lines 33 - 42, Add inclusive boundary assertions to the test named “the adapter
boundary retains the fixed wait and retry ranges” by checking requests using
SOURCE_WAIT_MIN_MS, SOURCE_WAIT_MAX_MS, SOURCE_RETRY_MIN, and SOURCE_RETRY_MAX,
and assert each is accepted. Keep the existing just-outside rejection cases
unchanged.
packages/local-file-providers/src/index.ts (2)

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

Simplify the terminal expression; the trailing-input branch is unreachable.

At Line 173 both parsed === true branches return undefined, so the index === text.length test never changes the result. Trailing input is still rejected later by JSON.parse in strictJson, so behavior is correct, but the expression suggests a check that does not exist.

♻️ Proposed simplification
-  const parsed = value(0);
-  whitespace();
-  return parsed === true && index === text.length ? undefined : parsed === true ? undefined : parsed || undefined;
+  const parsed = value(0);
+  // Trailing input and other syntax faults are rejected by JSON.parse in strictJson.
+  return typeof parsed === 'string' ? parsed : undefined;
🤖 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 `@packages/local-file-providers/src/index.ts` around lines 171 - 173, In the
parser return logic around value and strictJson, remove the redundant index ===
text.length condition and collapse the terminal expression to return parsed when
truthy and undefined otherwise, preserving JSON.parse’s later rejection of
trailing input.

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

Both branches return the same failure, so the gate result is discarded.

structuredFileProviderGate is evaluated at Line 247, but Lines 263-264 return an identical PROVIDER_UNAVAILABLE_UNQUALIFIED failure for both the failed and the successful gate. The if (!gate.ok) check has no observable effect. If the intent is to prove the exact manifest binding on every call, keep the call and add a comment that states the check is assertion-only. If the intent is to distinguish an invalid binding from an unqualified provider, return distinct codes.

♻️ Option: distinguish binding failure from unqualified state
-  if (!gate.ok) return fail('FC-MECHANISM', 'PROVIDER_UNAVAILABLE_UNQUALIFIED');
-  return fail('FC-MECHANISM', 'PROVIDER_UNAVAILABLE_UNQUALIFIED');
+  if (!gate.ok) return fail('FC-AUTHORITY' as never, 'EXACT_MANIFEST_BINDING_REQUIRED');
+  // Eligibility proven; reachability stays closed until live qualification.
+  return fail('FC-MECHANISM', 'PROVIDER_UNAVAILABLE_UNQUALIFIED');

Note that the second option changes the failure family, and packages/local-file-providers/tests/local-file-provider.test.mjs Lines 17-21 assert the current shape. Confirm the intended contract before you change it.

🤖 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 `@packages/local-file-providers/src/index.ts` around lines 247 - 264, Resolve
the redundant result handling after structuredFileProviderGate: either document
the assertion-only intent and remove the ineffective if branch while preserving
the existing failure contract, or return a distinct binding-failure code for
!gate.ok and retain PROVIDER_UNAVAILABLE_UNQUALIFIED only for successful
qualification. Use the gate result and update the related local-file-provider
tests if the failure shape changes.
tools/repo-guard/tests/check-package-boundaries.test.mjs (1)

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

Add a positive test for the permitted friend subpath.

The test proves the deny path. The allow path, where friendSubpaths[specifier] === manifest.name, stays untested. A regression that denies the owner package would pass this suite.

💚 Proposed additional test
+test('permits the qualification friend subpath from conformance', () => {
+  const errors = withPackages((root) =>
+    writeFileSync(
+      join(root, 'packages', 'conformance', 'src', 'index.ts'),
+      "import '`@agentic-workflow-kit/jig-runtime-contracts/qualification-certificate`';\n",
+    ),
+  );
+  assert.equal(
+    errors.some((error) => error.includes('restricted friend subpath')),
+    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 `@tools/repo-guard/tests/check-package-boundaries.test.mjs` around lines 140 -
148, Add a positive test alongside the existing restricted-subpath test that
configures or uses a package whose manifest name matches the relevant
friendSubpaths owner, imports the qualification-certificate subpath from that
package, and asserts no boundary errors are returned. Keep the existing
deny-path coverage unchanged and exercise the friendSubpaths[specifier] ===
manifest.name allow condition.
packages/conformance/tests/conformance.test.mjs (3)

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

The assertion message claims more coverage than the test provides.

Line 645 removes one field, candidateTree. The message at Line 649 states that every exact conformance-subject field is required before carrier registration. The test verifies that claim for one field only. A regression that drops the check on manifestDigest, topologyVersion, or recordedAt still passes.

Loop over the subject field names and assert rejection for each omission. That also removes the need for the lint-appeasement assertion at Line 651.

💚 Proposed per-field coverage
-  const { candidateTree, ...incompleteSubject } = exactClaims.subject;
-  assert.equal(
-    friend.recordExactStructuredFileExecution({ ...exactClaims, subject: incompleteSubject }),
-    undefined,
-    'every exact conformance-subject field is required before carrier registration',
-  );
-  assert.equal(candidateTree, hash);
+  for (const field of Object.keys(exactClaims.subject)) {
+    const { [field]: removed, ...incompleteSubject } = exactClaims.subject;
+    assert.equal(
+      friend.recordExactStructuredFileExecution({ ...exactClaims, subject: incompleteSubject }),
+      undefined,
+      `omitting ${field} must prevent carrier registration`,
+    );
+  }
🤖 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 `@packages/conformance/tests/conformance.test.mjs` around lines 645 - 651,
Update the test around recordExactStructuredFileExecution to iterate over every
exactClaims.subject field, omit each field individually, and assert that each
incomplete subject is rejected before carrier registration. Preserve the
existing assertion message, and remove the candidateTree-specific destructuring
and lint-appeasement assertion.

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

Identify the replacement case in the assertion message.

The loop tries four substitutions per port. Every iteration uses the same message, ${port}. A failure therefore does not say whether removal, the shallow clone, the altered key, or the cross-port record stopped failing the gate.

Name each case, and assert the reason string so that the gate fails for the intended cause rather than an unrelated one.

♻️ Proposed labelled cases
-    for (const replacement of [undefined, { ...record }, { ...record, key: `${record.key}/altered` }, crossPort]) {
+    const cases = [
+      ['removed', undefined],
+      ['cloned', { ...record }],
+      ['altered-key', { ...record, key: `${record.key}/altered` }],
+      ['cross-port', crossPort],
+    ];
+    for (const [label, replacement] of cases) {
       const mutated = [...complete];
       if (replacement === undefined) mutated.splice(index, 1);
       else mutated[index] = replacement;
-      assert.equal(conformance.evaluateProduct(mutated, routes, providerSubject).passed, false, `${port}`);
+      assert.equal(conformance.evaluateProduct(mutated, routes, providerSubject).passed, false, `${port}:${label}`);
     }
🤖 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 `@packages/conformance/tests/conformance.test.mjs` around lines 494 - 503,
Update the replacement loop in the observed-record mutation test to associate
each substitution with a distinct case label, including removal, shallow clone,
altered key, and cross-port record. Use the label in the assertion message and
assert the expected reason string so each iteration verifies the gate fails for
its intended cause rather than only checking passed is false.

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

Add two missing negative cases for evaluateProvider.

The forged-record case at Lines 572-573 covers the identity check well. Two branches of evaluateProvider stay untested:

  1. A subject whose recorderIdentity is not recorder/jig-conformance/v1. That is the first half of the condition at packages/conformance/src/index.ts Line 812.
  2. An input that holds both the observation and its source record. The source record is not in providerRecorderEvidence, so the gate must fail. This case also pins the behavior discussed for packages/conformance/src/index.ts Lines 632-641, where the observation and its source share key and bytes.
💚 Proposed added cases
   const forged = { ...observation.record, independentRecorder: 'recorder/jig-conformance/v1' };
   assert.equal(conformance.evaluateProvider('PORT-SOURCE', [forged], providerSubject).passed, false);
+  assert.equal(
+    conformance.evaluateProvider('PORT-SOURCE', [observation.record], { ...providerSubject, recorderIdentity: 'independent' })
+      .passed,
+    false,
+    'a subject recorder identity other than the conformance recorder must fail',
+  );
+  assert.equal(
+    conformance.evaluateProvider('PORT-SOURCE', [observation.record, source[0]], providerSubject).passed,
+    false,
+    'an unbranded source record alongside the observation must fail',
+  );
   assert.equal(
🤖 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 `@packages/conformance/tests/conformance.test.mjs` around lines 569 - 577, Add
two negative assertions covering evaluateProvider: use a provider subject with a
recorderIdentity other than recorder/jig-conformance/v1, and evaluate an input
containing both the observation and its source record so the
providerRecorderEvidence gate rejects it. Add these cases alongside the existing
PORT-SOURCE forged-record assertions and verify each result has passed equal to
false.
packages/conformance/src/structured-file-qualification.ts (2)

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

Share the exact qualification constants instead of repeating the literals.

resourceDigest, capability, and policyMinimum are literal here, and snapshotQualificationClaims in packages/runtime-contracts/src/qualification-registry.ts compares against the same three literals. The test at packages/conformance/tests/conformance.test.mjs Lines 596 and 641 repeats them again. A change to any one copy makes recordExactStructuredFileExecution return undefined, and the qualification then fails with no message that names the mismatch.

Export the expected values from the qualification registry and import them 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 `@packages/conformance/src/structured-file-qualification.ts` around lines 14 -
19, Export the shared qualification constants used by
snapshotQualificationClaims in qualification-registry.ts, then import and use
those constants for resourceDigest, capability, and policyMinimum in
recordExactStructuredFileExecution instead of duplicating literals. Update the
conformance test references to consume the same exported values so all
qualification checks remain synchronized.

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

The status check is unreachable.

observeProvider sets status: 'pass' on the record it returns (packages/conformance/src/index.ts Line 636). When observed.ok is true, observed.record.status !== 'pass' is therefore always false. Keep the check only if you intend it as a guard against a future change in observeProvider, and record that intent in a comment.

🤖 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 `@packages/conformance/src/structured-file-qualification.ts` around lines 12 -
13, The status check in the structured-file qualification flow is redundant
because observeProvider currently guarantees pass status. Update the condition
near observeProvider to remove observed.record.status !== 'pass', or retain it
only with a nearby comment explicitly documenting it as a future-change guard.
packages/runtime-contracts/src/qualification-certificate.ts (1)

15-23: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Consider consuming the carrier when a certificate is minted.

mintQualificationCertificate reads the carrier but never removes it from executionClaims. One recorded execution can therefore mint an unlimited number of independent certificates. If a certificate is meant to attest one qualification execution, delete the carrier entry after a successful mint.

If repeated minting is intentional, record that decision in the comment at Lines 3-6.

♻️ Proposed single-use carrier
   const input = executionClaims.get(carrier);
   const snapshot = snapshotQualificationClaims(input);
   if (!snapshot) return undefined;
+  executionClaims.delete(carrier);
   const certificate = Object.freeze({});
🤖 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 `@packages/runtime-contracts/src/qualification-certificate.ts` around lines 15
- 23, Update mintQualificationCertificate to consume the carrier after a
successful certificate mint by deleting its entry from executionClaims once the
snapshot is validated and certificateClaims is populated. Preserve the existing
undefined returns for invalid carriers or missing snapshots; only successfully
minted certificates should make the carrier unusable for later minting.
packages/runtime-contracts/tests/fixtures/hostile-source-corpus.json (1)

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

Extend the hostile corpus to the defenses the contracts actually implement.

The four frames cover truncation, a duplicate key, and two version mismatches. The source and envelope contracts also defend against prototype-polluting keys, excessive nesting, oversized strings, non-object roots, and trailing content. No frame exercises those paths, so a regression in them stays invisible.

Add frames such as a __proto__ key, a deeply nested payload beyond the depth limit, a bare array root, and a frame with trailing bytes after the closing brace.

🤖 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 `@packages/runtime-contracts/tests/fixtures/hostile-source-corpus.json` around
lines 2 - 7, Extend the hostile corpus in the frames fixture with cases for
prototype-polluting keys, nesting beyond the supported depth limit, oversized
strings, a non-object root such as a bare array, and trailing bytes after a
valid closing brace. Keep the existing truncation, duplicate-key, and
version-mismatch frames unchanged, and ensure each added frame directly
exercises the corresponding source or envelope contract defense.
packages/conformance/src/index.ts (1)

811-820: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Make the provenance check depend on identity explicitly, and reject an empty match set.

Two properties of this expression are implicit:

  1. The .every callback calls providerRecorderEvidence.has(record) on elements returned by snapshotArray(records). The check only works if snapshotArray preserves object identity. The test at packages/conformance/tests/conformance.test.mjs Line 569 depends on that. If snapshotArray is later changed to copy elements, the gate can never pass. The failure is closed, not open, but the coupling is invisible at this call site.
  2. .every returns true for an empty array. If records holds no record of suite, the provenance check adds no reason. suiteGate at Line 797 still pushes missing:${suite}, so the gate does not pass. The provenance check itself is vacuous in that case.

Iterate the raw records for the WeakSet check, and require at least one match.

♻️ Proposed explicit provenance check
-  if (
-    expectedSubject.recorderIdentity !== PROVIDER_RECORDER_ID ||
-    !snapshotArray(records)
-      ?.filter((record) => {
-        const snapshot = snapshotRecord(record);
-        return snapshot?.suite === suite;
-      })
-      .every((record) => typeof record === 'object' && record !== null && providerRecorderEvidence.has(record))
-  )
-    reasons.push('missing:independent-recorder-provenance');
+  // The WeakSet lookup requires the original object identity, so iterate `records` directly.
+  const forSuite = (Array.isArray(records) ? records : []).filter(
+    (record) => snapshotRecord(record)?.suite === suite,
+  );
+  if (
+    expectedSubject.recorderIdentity !== PROVIDER_RECORDER_ID ||
+    forSuite.length === 0 ||
+    !forSuite.every(
+      (record) => typeof record === 'object' && record !== null && providerRecorderEvidence.has(record),
+    )
+  )
+    reasons.push('missing:independent-recorder-provenance');
🤖 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 `@packages/conformance/src/index.ts` around lines 811 - 820, Update the
provenance condition in the suite gate to inspect raw records directly rather
than relying on snapshotArray, preserving object identity for
providerRecorderEvidence.has. Collect or track records whose snapshotRecord
suite matches suite, and require at least one matching record with every match
being a valid object present in providerRecorderEvidence. Keep the existing
expectedSubject.recorderIdentity check and
missing:independent-recorder-provenance reason.
packages/runtime-contracts/tests/fixtures/provider-authority-manifest.wire (1)

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

Derive the wire fixture from the JSON fixture. JSON.parse accepts the trailing newline, but the test hashes and compares exact bytes, so the newline is intentional. Generate the canonical wire bytes from provider-authority-manifest.json, including its final newline, instead of maintaining duplicate fixtures.

🤖 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 `@packages/runtime-contracts/tests/fixtures/provider-authority-manifest.wire`
at line 1, Replace the manually maintained wire content in
provider-authority-manifest.wire with an automatically derived fixture from
provider-authority-manifest.json, preserving canonical JSON bytes and the source
file’s trailing newline. Update the relevant fixture-generation or test setup so
exact-byte hashing and comparison continue to use the generated wire
representation rather than duplicate content.
packages/runtime-contracts/src/envelope.ts (1)

378-391: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Share the demand memo across top-level calls.

validateSourcePlan rejects unknown dependencies and dependency cycles, so the non-null assertion is safe for validated plans. The local memo still repeats shared dependency traversal for each story and resource class. Hoist it outside the resource loop.

🤖 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 `@packages/runtime-contracts/src/envelope.ts` around lines 378 - 391, In
validateSourcePlan, create one demand memo before the RESOURCE_CLASSES loop and
reuse it for every top-level demandAt call. Remove the per-call default memo
allocation at the loop site while preserving demandAt’s dependency caching and
existing validation assumptions.
packages/runtime-contracts/tests/ledger-contract.test.mjs (1)

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

Add coverage for the rejected readPreflight inputs and the variant split.

The new test covers absence, exact readback, and immutability. Two branches of readPreflight stay uncovered: the INVALID_PREFLIGHT_READ failure for an empty key or an unknown variant, and the variant separation of the key namespace.

💚 Proposed additional assertions
   assert.deepEqual(ledger.readPreflight('provider/readback/1', 'start'), committed);
+  assert.deepEqual(ledger.readPreflight('provider/readback/1', 'result'), {
+    ok: true,
+    value: { kind: 'absent' },
+  });
+  for (const invalid of [
+    ['', 'start'],
+    ['provider/readback/1', 'other'],
+  ])
+    assert.deepEqual(ledger.readPreflight(...invalid), {
+      ok: false,
+      error: { family: 'FC-INPUT', code: 'INVALID_PREFLIGHT_READ' },
+    });
 });
🤖 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 `@packages/runtime-contracts/tests/ledger-contract.test.mjs` around lines 375 -
392, Add assertions to the existing semantic ledger test around readPreflight to
cover INVALID_PREFLIGHT_READ for both an empty key and an unknown variant, and
verify that entries with the same key but different variants remain separate.
Reuse the existing committed preflight setup and assert the expected
failure/readback results without changing the current absence, exact-readback,
or immutability coverage.
packages/runtime-contracts/tests/envelope-policy.test.mjs (1)

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

One envelope fixture is defined twice. Both test files build the same plan, policy, profile, artifacts, setup, ruleSurface, and guidance input. Any change to the envelope contract requires two edits, and the copies can drift.

  • packages/runtime-contracts/tests/envelope-policy.test.mjs#L6-L56: move policy and input() into a shared fixture module, for example tests/fixtures/envelope-input.mjs, and import them here.
  • packages/runtime-contracts/tests/envelope-policy-review.test.mjs#L9-L58: delete the local plan() and input() and import the shared fixture.
🤖 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 `@packages/runtime-contracts/tests/envelope-policy.test.mjs` around lines 6 -
56, Centralize the duplicated envelope fixture in
packages/runtime-contracts/tests/fixtures/envelope-input.mjs by moving the
policy object and input() factory from
packages/runtime-contracts/tests/envelope-policy.test.mjs lines 6-56, then
import them in that test. In
packages/runtime-contracts/tests/envelope-policy-review.test.mjs lines 9-58,
remove the local plan() and input() definitions and import the shared fixture
instead.
packages/runtime-contracts/src/qualification-registry.ts (1)

55-66: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Commit identifiers receive no shape validation.

DIGEST_FIELDS includes candidateTree and executionBaseTree, so both are checked against /^[0-9a-f]{64}$/u. candidateCommit, executionBaseCommit, and mergeBaseCommit pass with any safeText value up to 256 characters. Git commit identifiers have the same hexadecimal shape as the tree identifiers, so the asymmetry appears unintended and it weakens the subject binding.

Add the three commit fields to DIGEST_FIELDS, or add a separate commit-shape check if abbreviated or SHA-1 identifiers must remain valid.

♻️ Proposed change
 const DIGEST_FIELDS = new Set<keyof QualificationSubject>([
   'buildDigest',
+  'candidateCommit',
   'candidateContentDigest',
   'candidateTree',
   'catalogDigest',
   'environmentDigest',
+  'executionBaseCommit',
   'executionBaseTree',
   'fixtureDigest',
   'manifestDigest',
+  'mergeBaseCommit',
   'providerBuildDigest',
   'toolchainDigest',
 ]);

Also applies to: 109-113

🤖 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 `@packages/runtime-contracts/src/qualification-registry.ts` around lines 55 -
66, Extend validation for the commit identifier fields in the qualification
subject schema: update DIGEST_FIELDS to include candidateCommit,
executionBaseCommit, and mergeBaseCommit so they receive the same hexadecimal
shape validation as tree identifiers, unless the implementation explicitly
supports abbreviated or SHA-1 forms and therefore requires a separate
commit-specific check.
packages/runtime-contracts/src/source.ts (3)

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

Consider aligning exact with entryFields prototype checks.

exact accepts any non-array object. entryFields additionally requires Object.getPrototypeOf(value) === Object.prototype and descriptor-safe fields. Today every exact caller receives values that decodeFrame or snapshot produced, so the input is already a plain data object. The weaker check is safe now, but it becomes a hazard if a future caller passes raw caller-owned input to exact directly. Add the prototype guard, or document the precondition on exact.

🤖 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 `@packages/runtime-contracts/src/source.ts` around lines 124 - 130, Strengthen
exact’s object validation to match entryFields by requiring
Object.getPrototypeOf(value) === Object.prototype before accepting the record.
Keep the existing array, null, and key-set checks unchanged, ensuring non-plain
or caller-owned objects return undefined.

467-479: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Freeze nested decoded payloads before returning them.

frozen freezes only the top level. basis (Line 471) and content (Line 559) keep the decoded object graph mutable. A caller can mutate request.basis.track or exchange.content.title after validation, so the returned value no longer matches requestBasisDigest or contentDigest. The tests assert Object.isFrozen(result.value) and Object.isFrozen(result.value.plan) only, so this gap is not covered.

Add a deep freeze for the decoded subtrees.

♻️ Proposed deep-freeze helper
 const frozen = <T>(value: T): T => Object.freeze(value);
+const deepFrozen = <T>(value: T): T => {
+  if (typeof value !== 'object' || value === null) return value;
+  for (const key of Object.keys(value as object))
+    deepFrozen((value as Record<string, unknown>)[key]);
+  return Object.freeze(value);
+};

Then use deepFrozen(raw.basis) and deepFrozen(raw.content) when building the returned request and exchange.

Also applies to: 550-575

🤖 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 `@packages/runtime-contracts/src/source.ts` around lines 467 - 479, Deep-freeze
the decoded nested payloads before returning them: update the request
construction around sourceIdentity to use deepFrozen(raw.basis), and the
exchange construction around content to use deepFrozen(raw.content). Reuse the
proposed deep-freeze helper and preserve the existing top-level frozen wrappers
and digest values.

287-300: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Resource-key regex admits constructor.

numericMap and reserveMap accept any key that matches /^[a-z][a-z0-9-]{0,31}$/u. The string constructor matches. Object.fromEntries then creates an own constructor data property on the returned map. The value stays plain data, so there is no prototype pollution, and later lookups use Object.hasOwn. No functional defect exists today. If resource identifiers must exclude object-shaped names, add an explicit deny list.

Also applies to: 302-306

🤖 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 `@packages/runtime-contracts/src/source.ts` around lines 287 - 300, Update the
key validation in numericMap and reserveMap to explicitly reject constructor and
any other object-shaped resource names required by the contract, while
preserving the existing regex and amount validation. Apply the same deny list
consistently in both functions before creating the frozen map.
packages/local-file-providers/src/path-confinement.ts (1)

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

Use node:crypto for sha256.

createHash('sha256') provides the same digest without maintaining a large compression implementation. This helper runs in resource-key and per-record digest paths. The package targets Node >=22 and has no import policy that limits it to node:fs. Document such a restriction beside sha256 if an authority decision adds 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 `@packages/local-file-providers/src/path-confinement.ts` around lines 34 - 96,
Replace the manual compression implementation in sha256 with node:crypto’s
createHash('sha256') API, updating imports and returning the digest as the
existing lowercase hexadecimal string. Preserve the helper’s Uint8Array input
and all callers’ resource-key and per-record digest behavior; do not retain the
constants, padding, or block-processing logic.
packages/local-file-providers/src/local-file-artifact.ts (3)

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

localFileArtifactRoute duplicates routeContext.

Lines 181-186 repeat the exact holder classification in routeContext at lines 766-772. Two copies of the routing rule can diverge. contextFor at line 347 uses routeContext, and the exported function uses its own copy.

Build the exported function on routeContext.

♻️ Proposed fix
 export function localFileArtifactRoute(
   holder: unknown,
 ): LocalFileArtifactResult<Readonly<{ context: Context; root: string }>> {
-  const context: Context | undefined =
-    typeof holder === 'string' && PROTECTED.has(holder)
-      ? 'protected'
-      : typeof holder === 'string' && DISPOSABLE.has(holder)
-        ? 'disposable'
-        : undefined;
+  const context = routeContext(holder);
   return context
     ? ok(Object.freeze({ context, root: LOCAL_FILE_ARTIFACT_ROOTS[context] }))
     : fail('FC-SUBJECT', 'UNKNOWN_HOLDER_CLASS');
 }
🤖 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 `@packages/local-file-providers/src/local-file-artifact.ts` around lines 178 -
190, Update localFileArtifactRoute to derive its context by calling the existing
routeContext helper instead of duplicating the PROTECTED and DISPOSABLE holder
classification. Preserve the current success result with the resolved context
and corresponding LOCAL_FILE_ARTIFACT_ROOTS entry, and retain the
UNKNOWN_HOLDER_CLASS failure for undefined routing results.

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

canonical is JSON.stringify, so it is order-sensitive and not canonical.

canonical decides identity in many places: the witness comparison at line 301, fact.binding at line 316, the prior-binding comparisons at lines 417, 546, and 597, the snapshot digest at line 641, and the proof basis at line 925. Two objects with the same members in a different insertion order produce different strings.

The current call sites build objects from fixed literal or fields(...) name order, so the behavior is stable today. The name still states a guarantee that the function does not provide, and the ledger and registry paths use the codec stageDigest canonical form instead. A later refactor that reorders one object literal changes every derived digest.

Either rename it to serialize, or route it through the same codec canonical JSON that packages/local-file-providers/src/local-file-registry.ts uses via stagedDigest.

🤖 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 `@packages/local-file-providers/src/local-file-artifact.ts` at line 175,
Resolve the misleading canonicalization in the `canonical` helper: either rename
it to `serialize` and update all visible call sites, including witness, binding,
prior-binding, snapshot, and proof-basis comparisons, or replace its
implementation with the codec’s order-independent canonical JSON used by
`stagedDigest` in the registry path. Keep identity and digest behavior
consistent across these usages.

116-174: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Use node:crypto for SHA-256.

This removes the hand-written implementation and makes the digest behavior explicit. Keep the existing node:fs import and remove the local compression code.

🤖 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 `@packages/local-file-providers/src/local-file-artifact.ts` around lines 116 -
174, Replace the hand-written SHA-256 implementation in hash with node:crypto’s
SHA-256 API, preserving support for string and Uint8Array inputs and the
existing hexadecimal digest output. Keep the node:fs import unchanged and remove
the local constants, padding, compression, and state-processing logic.
packages/local-file-providers/tests/local-file-artifact.test.mjs (1)

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

Add coverage for the exported helpers localFileArtifactRoute and validateLocalFileArtifactPut.

packages/local-file-providers/src/local-file-artifact.ts exports both functions at lines 178 and 193. This suite exercises contextFor, which uses the internal routeContext, but no test calls either exported helper. localFileArtifactRoute also returns the selected absolute root, so a defect there is not caught by the oracle tests, which use temporary roots.

Add assertions for a protected holder, a disposable holder, an unknown holder, and a validateLocalFileArtifactPut call with a protected holder.

🤖 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 `@packages/local-file-providers/tests/local-file-artifact.test.mjs` around
lines 147 - 180, Add coverage in the existing local-file artifact test suite for
the exported helpers localFileArtifactRoute and validateLocalFileArtifactPut:
assert protected and disposable holder routing, unknown-holder handling, and
validation of a put using a protected holder, including the selected absolute
root returned by localFileArtifactRoute.
packages/local-file-providers/tests/local-file-ledger.test.mjs (1)

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

Import the codec through its package specifier.

Declare @agentic-workflow-kit/jig-codec under devDependencies of packages/local-file-providers, then import it by package specifier. The test-only relative path couples the test to the codec output directory. The package-boundary checker does not inspect test 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 `@packages/local-file-providers/tests/local-file-ledger.test.mjs` around lines
17 - 25, Update the local-file-ledger test to import the codec via the
`@agentic-workflow-kit/jig-codec` package specifier instead of the relative
codec/dist path, and add `@agentic-workflow-kit/jig-codec` to
packages/local-file-providers devDependencies. Leave the other runtime and
provider imports unchanged.
packages/local-file-providers/src/node.d.ts (1)

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

Document the intentional omission of @types/node.

This package and the lockfile contain no @types/node reference. Add a short comment in node.d.ts if this dependency exclusion is intentional.

🤖 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 `@packages/local-file-providers/src/node.d.ts` around lines 1 - 46, Add a brief
comment at the top of node.d.ts documenting that the local Node.js declarations
intentionally replace `@types/node` because the package and lockfile exclude that
dependency. Leave the existing declarations unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d2057d0d-9572-49a3-947d-3086c703f0e7

📥 Commits

Reviewing files that changed from the base of the PR and between aea51ea and d17a269.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (43)
  • docs/delivery/README.md
  • packages/conformance/src/index.ts
  • packages/conformance/src/structured-file-qualification.ts
  • packages/conformance/tests/conformance.test.mjs
  • packages/local-file-providers/package.json
  • packages/local-file-providers/src/index.ts
  • packages/local-file-providers/src/local-file-artifact.ts
  • packages/local-file-providers/src/local-file-intake.ts
  • packages/local-file-providers/src/local-file-ledger.ts
  • packages/local-file-providers/src/local-file-preflight.ts
  • packages/local-file-providers/src/local-file-registry.ts
  • packages/local-file-providers/src/local-file-snapshot.ts
  • packages/local-file-providers/src/local-file-witness.ts
  • packages/local-file-providers/src/node.d.ts
  • packages/local-file-providers/src/path-confinement.ts
  • packages/local-file-providers/tests/fixtures/local-file-artifact-routing.json
  • packages/local-file-providers/tests/fixtures/local-file-ledger-oracle.json
  • packages/local-file-providers/tests/local-file-artifact.test.mjs
  • packages/local-file-providers/tests/local-file-ledger.test.mjs
  • packages/local-file-providers/tests/local-file-provider.test.mjs
  • packages/local-file-providers/tsconfig.json
  • packages/runtime-contracts/package.json
  • packages/runtime-contracts/src/envelope.ts
  • packages/runtime-contracts/src/index.ts
  • packages/runtime-contracts/src/ledger.ts
  • packages/runtime-contracts/src/provider.ts
  • packages/runtime-contracts/src/qualification-certificate.ts
  • packages/runtime-contracts/src/qualification-registry.ts
  • packages/runtime-contracts/src/repository-policy-catalogue.ts
  • packages/runtime-contracts/src/source.ts
  • packages/runtime-contracts/tests/envelope-policy-review.test.mjs
  • packages/runtime-contracts/tests/envelope-policy.test.mjs
  • packages/runtime-contracts/tests/fixtures/envelope-bounds-oracle.json
  • packages/runtime-contracts/tests/fixtures/hostile-source-corpus.json
  • packages/runtime-contracts/tests/fixtures/provider-authority-manifest.json
  • packages/runtime-contracts/tests/fixtures/provider-authority-manifest.wire
  • packages/runtime-contracts/tests/fixtures/source-contract-oracle.json
  • packages/runtime-contracts/tests/ledger-contract.test.mjs
  • packages/runtime-contracts/tests/provider-admission.test.mjs
  • packages/runtime-contracts/tests/source-contract.test.mjs
  • packages/runtime-contracts/tests/source-plan-public.test.mjs
  • tools/repo-guard/bin/check-package-boundaries.mjs
  • tools/repo-guard/tests/check-package-boundaries.test.mjs

Comment thread packages/conformance/tests/conformance.test.mjs Outdated
Comment thread packages/local-file-providers/src/local-file-artifact.ts
Comment thread packages/local-file-providers/src/local-file-artifact.ts
Comment thread packages/local-file-providers/src/local-file-ledger.ts
Comment thread packages/local-file-providers/src/local-file-ledger.ts
Comment thread packages/local-file-providers/src/path-confinement.ts Outdated
Comment thread packages/runtime-contracts/src/envelope.ts
Comment thread packages/runtime-contracts/src/provider.ts
Comment thread packages/runtime-contracts/src/provider.ts
Comment thread packages/runtime-contracts/src/qualification-registry.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.

Caution

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

⚠️ Outside diff range comments (1)
packages/local-file-providers/src/path-confinement.ts (1)

390-428: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Use atomic publication and close all descriptors on failure.

writeCreateOnlyJson writes directly to the final path. A crash before fsyncSync(descriptor) completes can leave partial JSON at that path. A retry then returns ALREADY_EXISTS, so the record cannot be repaired. Write to a temporary file, sync it, publish it atomically without replacement, and sync the parent directory. Also close directoryDescriptor in a finally block; an exception from fsyncSync(directoryDescriptor) currently leaks it.

🤖 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 `@packages/local-file-providers/src/path-confinement.ts` around lines 390 -
428, Update writeCreateOnlyJson to stage the JSON in a uniquely named temporary
file within the confined parent directory, write and fsync that file, then
atomically publish it at the final path without replacement before syncing the
parent directory. Track both file descriptors and the temporary path, and ensure
all descriptors are closed and any unpublished temporary file is removed on
every failure, including errors during directory fsync; preserve ALREADY_EXISTS
mapping for an existing final path.
♻️ Duplicate comments (1)
packages/local-file-providers/src/path-confinement.ts (1)

301-374: ⚠️ Potential issue | 🟠 Major

READ_FAILED still hides missing files behind trust and corruption failures.

The single try/catch around Lines 304-372 maps every failure mode to fail('FC-MECHANISM', 'READ_FAILED'): a missing file (ENOENT from lstatSync), a missing parent directory, an intentional throw new Error('untrusted directory') from the directory-identity check, a JSON.parse failure, and a snapshot throw on hostile input. The confirmed downstream consumer in packages/local-file-providers/src/local-file-preflight.ts treats this code as absence: "const decoded = readJsonFile(root, [resourceKey(key), ${variant}.json]); if (!decoded.ok) return decoded.error.code === 'READ_FAILED' ? ok(Object.freeze({ kind: 'absent' })) : decoded;" A tampered or corrupted file that fails a trust check (bad JSON, non-canonical bytes, a swapped directory) is indistinguishable from a file that was never written, so a tampered result.json can be silently treated as absent instead of surfacing an unverifiable/trust failure.

This is the same root cause flagged in the earlier review of this function (then at different line numbers); the fix has not been applied to the rewritten body.

🛡️ Proposed fix to separate absence from corruption/trust failures
   let descriptor: number | undefined;
   try {
     const directoryParts = parts.slice(0, -1);
-    const directories = [
-      root,
-      ...directoryParts.map((_, index) => `${root}/${directoryParts.slice(0, index + 1).join('/')}`),
-    ].map((directory) => {
-      const stat = lstatSync(directory);
-      if (!stat.isDirectory() || stat.isSymbolicLink() || realpathSync(directory) !== directory)
-        throw new Error('untrusted directory');
-      return Object.freeze({ directory, dev: stat.dev, ino: stat.ino });
-    });
-    const pathStat = lstatSync(path.value);
+    let directories: ReadonlyArray<{ directory: string; dev: number; ino: number }>;
+    let pathStat: ReturnType<typeof lstatSync>;
+    try {
+      directories = [
+        root,
+        ...directoryParts.map((_, index) => `${root}/${directoryParts.slice(0, index + 1).join('/')}`),
+      ].map((directory) => {
+        const stat = lstatSync(directory);
+        if (!stat.isDirectory() || stat.isSymbolicLink() || realpathSync(directory) !== directory)
+          throw Object.assign(new Error('untrusted directory'), { untrusted: true });
+        return Object.freeze({ directory, dev: stat.dev, ino: stat.ino });
+      });
+      pathStat = lstatSync(path.value);
+    } catch (error) {
+      const code = typeof error === 'object' && error !== null && 'code' in error ? (error as { code?: unknown }).code : undefined;
+      if ((error as { untrusted?: boolean })?.untrusted) return fail('FC-TRUST', 'UNTRUSTED_PATH_COMPONENT');
+      return fail('FC-MECHANISM', code === 'ENOENT' ? 'FILE_ABSENT' : 'READ_FAILED');
+    }
     if (
       !pathStat.isFile() ||
@@
     closeSync(descriptor);
     descriptor = undefined;
     const text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
     if (!text.endsWith('\n')) return fail('FC-TRUST', 'UNTRUSTED_FILE');
-    const value = snapshot(JSON.parse(text));
-    return `${canonicalText(value)}\n` === text ? ok(value) : fail('FC-TRUST', 'UNTRUSTED_FILE');
+    try {
+      const value = snapshot(JSON.parse(text));
+      return `${canonicalText(value)}\n` === text ? ok(value) : fail('FC-TRUST', 'UNTRUSTED_FILE');
+    } catch {
+      return fail('FC-TRUST', 'UNTRUSTED_FILE');
+    }
   } catch {
     return fail('FC-MECHANISM', 'READ_FAILED');
   } finally {
     if (descriptor !== undefined) closeSync(descriptor);
   }
🤖 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 `@packages/local-file-providers/src/path-confinement.ts` around lines 301 -
374, Update readJsonFile so only genuine absence conditions, such as ENOENT for
the target file or its parent directories, return FC-MECHANISM/READ_FAILED.
Preserve and propagate FC-TRUST failures for untrusted path components, file
identity changes, malformed or non-canonical JSON, and hostile snapshot input
instead of allowing the broad catch to classify them as absent; narrow the catch
or explicitly distinguish these failure paths while keeping descriptor cleanup
intact.
🧹 Nitpick comments (1)
packages/local-file-providers/tests/local-file-ledger.test.mjs (1)

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

Add the fence assertion for a repeated witness-floor advance.

The test proves that advanceWitnessFloor repairs a lagging witness. It does not pin the fence contract for a second call. Assert that the second call returns FC-FENCE / WITNESS_ALREADY_CURRENT. This locks the idempotent recovery behaviour that packages/local-file-providers/src/local-file-registry.ts Line 242 implements.

💚 Proposed additional assertion
   assert.deepEqual(restored.advanceWitnessFloor(binding), { ok: true, value: undefined });
+  assert.deepEqual(restored.advanceWitnessFloor(binding), {
+    ok: false,
+    error: { family: 'FC-FENCE', code: 'WITNESS_ALREADY_CURRENT' },
+  });
   assert.equal(restored.readback({ binding, position: 1 }).value.kind, 'committed');
🤖 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 `@packages/local-file-providers/tests/local-file-ledger.test.mjs` around lines
388 - 398, Extend the test after the successful restored.advanceWitnessFloor
call to invoke restored.advanceWitnessFloor(binding) a second time and assert it
returns { ok: false, error: { family: 'FC-FENCE', code:
'WITNESS_ALREADY_CURRENT' } }, preserving the existing readback assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/local-file-providers/src/path-confinement.ts`:
- Around line 390-428: Update writeCreateOnlyJson to stage the JSON in a
uniquely named temporary file within the confined parent directory, write and
fsync that file, then atomically publish it at the final path without
replacement before syncing the parent directory. Track both file descriptors and
the temporary path, and ensure all descriptors are closed and any unpublished
temporary file is removed on every failure, including errors during directory
fsync; preserve ALREADY_EXISTS mapping for an existing final path.

---

Duplicate comments:
In `@packages/local-file-providers/src/path-confinement.ts`:
- Around line 301-374: Update readJsonFile so only genuine absence conditions,
such as ENOENT for the target file or its parent directories, return
FC-MECHANISM/READ_FAILED. Preserve and propagate FC-TRUST failures for untrusted
path components, file identity changes, malformed or non-canonical JSON, and
hostile snapshot input instead of allowing the broad catch to classify them as
absent; narrow the catch or explicitly distinguish these failure paths while
keeping descriptor cleanup intact.

---

Nitpick comments:
In `@packages/local-file-providers/tests/local-file-ledger.test.mjs`:
- Around line 388-398: Extend the test after the successful
restored.advanceWitnessFloor call to invoke
restored.advanceWitnessFloor(binding) a second time and assert it returns { ok:
false, error: { family: 'FC-FENCE', code: 'WITNESS_ALREADY_CURRENT' } },
preserving the existing readback assertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c184fa62-95b4-4b4d-9ebb-7909194e4b12

📥 Commits

Reviewing files that changed from the base of the PR and between d17a269 and cdce34d.

📒 Files selected for processing (7)
  • packages/local-file-providers/src/local-file-intake.ts
  • packages/local-file-providers/src/local-file-ledger.ts
  • packages/local-file-providers/src/local-file-preflight.ts
  • packages/local-file-providers/src/local-file-registry.ts
  • packages/local-file-providers/src/local-file-snapshot.ts
  • packages/local-file-providers/src/path-confinement.ts
  • packages/local-file-providers/tests/local-file-ledger.test.mjs
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/local-file-providers/src/local-file-snapshot.ts
  • packages/local-file-providers/src/local-file-preflight.ts
  • packages/local-file-providers/src/local-file-intake.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.

Caution

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

⚠️ Outside diff range comments (1)
packages/local-file-providers/src/path-confinement.ts (1)

420-471: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Fix: a post-link cleanup failure reports a write failure even though the write already succeeded.

linkSync(temporaryFile, path.value) at Line 442 is the durability commit point: the content was already flushed and fsynced to the temp file (Line 439) before the link. Once linkSync returns, path.value exists with correct content and readJsonFile can read it back correctly. However, if any of the following steps throws — unlinkSync(temporaryFile), rmdirSync(temporaryDirectory), openSync(parent, ...), fsyncSync(directoryDescriptor), or closeSync(directoryDescriptor) — the function falls into the catch block and returns fail('FC-MECHANISM', 'WRITE_FAILED') (Line 471), misreporting a successful write as failed.

This is not a purely theoretical corner case for this call site. local-file-ledger.ts and local-file-registry.ts call writeCreateOnlyJson for records and immediately propagate any failure (if (!stored.ok) return stored;), without the ALREADY_EXISTS tolerance used for intents writes. A spurious failure here surfaces as a hard error to the ledger/registry caller for an append that actually completed.

Track whether the link already succeeded, and treat later cleanup failures as non-fatal for the return value.

🛠️ Proposed fix to stop reporting failure after a successful link
   let descriptor: number | undefined;
   let directoryDescriptor: number | undefined;
   let temporaryFile: string | undefined;
   let temporaryDirectory: string | undefined;
+  let linked = false;
   try {
     ...
     linkSync(temporaryFile, path.value);
+    linked = true;
     unlinkSync(temporaryFile);
     temporaryFile = undefined;
     rmdirSync(temporaryDirectory);
     temporaryDirectory = undefined;
     const parent = path.value.slice(0, path.value.lastIndexOf('/'));
     directoryDescriptor = openSync(parent, constants.O_RDONLY | constants.O_DIRECTORY);
     fsyncSync(directoryDescriptor);
     closeSync(directoryDescriptor);
     directoryDescriptor = undefined;
     return ok(undefined);
   } catch (error) {
     for (const open of [descriptor, directoryDescriptor]) {
       if (open === undefined) continue;
       try {
         closeSync(open);
       } catch {}
     }
     if (temporaryFile !== undefined) {
       try {
         unlinkSync(temporaryFile);
       } catch {}
     }
     if (temporaryDirectory !== undefined) {
       try {
         rmdirSync(temporaryDirectory);
       } catch {}
     }
+    if (linked) return ok(undefined);
     const code = errorCode(error);
     return fail(code === 'EEXIST' ? 'FC-FENCE' : 'FC-MECHANISM', code === 'EEXIST' ? 'ALREADY_EXISTS' : 'WRITE_FAILED');
   }
🤖 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 `@packages/local-file-providers/src/path-confinement.ts` around lines 420 -
471, Update the write flow around linkSync in writeCreateOnlyJson to track
whether the durability commit has succeeded. Once linkSync returns, preserve a
successful result even if temporary-file cleanup or parent-directory fsync/close
operations throw; retain cleanup attempts and existing error reporting for
failures that occur before the link succeeds.
🧹 Nitpick comments (1)
packages/local-file-providers/src/path-confinement.ts (1)

408-473: 🩺 Stability & Availability | 🔵 Trivial

Verify staging-directory reconciliation on startup.

If the process crashes between mkdtempSync (Line 430) and the final cleanup, or between linkSync (Line 442) and rmdirSync (Line 445), an orphaned staging/write-* directory and payload file remain on disk. Nothing in this file reconciles or prunes stale staging directories on startup. Over repeated crash/retry cycles this accumulates disk usage under the confined root.

Confirm whether a startup or periodic reconciliation pass removes stale entries under staging, similar to the reconciliation described for the artifact provider in this PR.

🤖 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 `@packages/local-file-providers/src/path-confinement.ts` around lines 408 -
473, Add startup or periodic reconciliation for the staging area used by
writeCreateOnlyJson, removing stale staging/write-* directories and their
payload files left by interrupted writes while preserving active entries. Anchor
the implementation to writeCreateOnlyJson and the existing initialization or
reconciliation flow in this module, and ensure cleanup remains confined to the
configured root.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/local-file-providers/src/path-confinement.ts`:
- Around line 420-471: Update the write flow around linkSync in
writeCreateOnlyJson to track whether the durability commit has succeeded. Once
linkSync returns, preserve a successful result even if temporary-file cleanup or
parent-directory fsync/close operations throw; retain cleanup attempts and
existing error reporting for failures that occur before the link succeeds.

---

Nitpick comments:
In `@packages/local-file-providers/src/path-confinement.ts`:
- Around line 408-473: Add startup or periodic reconciliation for the staging
area used by writeCreateOnlyJson, removing stale staging/write-* directories and
their payload files left by interrupted writes while preserving active entries.
Anchor the implementation to writeCreateOnlyJson and the existing initialization
or reconciliation flow in this module, and ensure cleanup remains confined to
the configured root.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a43d67ec-b1a1-4c51-9019-54482ce765e3

📥 Commits

Reviewing files that changed from the base of the PR and between cdce34d and 34f2948.

📒 Files selected for processing (25)
  • docs/delivery/README.md
  • packages/conformance/tests/conformance.test.mjs
  • packages/local-file-providers/src/index.ts
  • packages/local-file-providers/src/local-file-artifact.ts
  • packages/local-file-providers/src/local-file-intake.ts
  • packages/local-file-providers/src/local-file-ledger.ts
  • packages/local-file-providers/src/local-file-preflight.ts
  • packages/local-file-providers/src/local-file-registry.ts
  • packages/local-file-providers/src/local-file-witness.ts
  • packages/local-file-providers/src/node.d.ts
  • packages/local-file-providers/src/path-confinement.ts
  • packages/local-file-providers/tests/fixtures/local-file-ledger-oracle.json
  • packages/local-file-providers/tests/local-file-artifact.test.mjs
  • packages/local-file-providers/tests/local-file-ledger.test.mjs
  • packages/local-file-providers/tests/local-file-provider.test.mjs
  • packages/runtime-contracts/src/envelope.ts
  • packages/runtime-contracts/src/provider.ts
  • packages/runtime-contracts/src/qualification-certificate.ts
  • packages/runtime-contracts/src/qualification-registry.ts
  • packages/runtime-contracts/src/source.ts
  • packages/runtime-contracts/tests/envelope-policy.test.mjs
  • packages/runtime-contracts/tests/provider-admission.test.mjs
  • packages/runtime-contracts/tests/source-plan-public.test.mjs
  • tools/repo-guard/bin/check-package-boundaries.mjs
  • tools/repo-guard/tests/check-package-boundaries.test.mjs
🚧 Files skipped from review as they are similar to previous changes (20)
  • docs/delivery/README.md
  • packages/runtime-contracts/tests/source-plan-public.test.mjs
  • packages/local-file-providers/tests/fixtures/local-file-ledger-oracle.json
  • packages/local-file-providers/tests/local-file-provider.test.mjs
  • packages/conformance/tests/conformance.test.mjs
  • packages/local-file-providers/src/local-file-witness.ts
  • packages/local-file-providers/src/local-file-preflight.ts
  • packages/runtime-contracts/src/qualification-certificate.ts
  • packages/runtime-contracts/tests/provider-admission.test.mjs
  • packages/local-file-providers/tests/local-file-artifact.test.mjs
  • tools/repo-guard/bin/check-package-boundaries.mjs
  • packages/local-file-providers/tests/local-file-ledger.test.mjs
  • packages/local-file-providers/src/local-file-intake.ts
  • packages/local-file-providers/src/node.d.ts
  • packages/local-file-providers/src/index.ts
  • packages/local-file-providers/src/local-file-ledger.ts
  • packages/runtime-contracts/src/envelope.ts
  • packages/local-file-providers/src/local-file-registry.ts
  • packages/runtime-contracts/src/source.ts
  • packages/local-file-providers/src/local-file-artifact.ts

@aryeko
aryeko merged commit 21cdf8d into main Jul 31, 2026
2 checks passed
@aryeko
aryeko deleted the feat/phase-2-envelope-intake branch July 31, 2026 18:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant