Skip to content

feat(SUP-3464): add jarvos control plane core - #99

Merged
levineam merged 4 commits into
mainfrom
SUP-3464/control-plane
Jul 17, 2026
Merged

feat(SUP-3464): add jarvos control plane core#99
levineam merged 4 commits into
mainfrom
SUP-3464/control-plane

Conversation

@levineam

@levineam levineam commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add @jarvos/control-plane with v1 public records/contracts, canonical command spec/action-key logic, manager manifest validation, and sensitivity-filtered projections.
  • Add registry, policy, reconciliation, memory/file reference stores, CAS leases, monotonic fences, lifecycle checkpoints, and independent evidence verification.
  • Wire the module into docs, package allowlist, module smoke checks, and structure smoke checks.

Verification

  • npm test (modules/jarvos-control-plane)
  • node tests/modules-smoke-test.js
  • bash scripts/smoke-test.sh
  • npm pack --dry-run --json control-plane file presence check

Known local failure

  • npm test fails in existing modules/jarvos-agent-context tests because the local Obsidian CLI eval path fails/timeouts while mutating temp vault journals; the failures are outside the SUP-3464 control-plane files.

Issue

  • SUP-3464

Summary by CodeRabbit

  • New Features

    • Added the @jarvos/control-plane module for portable machine-wide AI coordination.
    • Introduced deterministic control-plane contracts (request/policy/command/evidence/leases/manifests) with canonicalization and public redaction projections.
    • Added manager registry + policy evaluation + request reconciliation with command deduplication and fenced lease-based execution.
    • Added in-memory and file-backed journaled storage with recovery and stale-evidence rejection.
  • Documentation

    • Added architecture documentation for the Control Plane and updated module READMEs.
  • Tests

    • Added unit and concurrency/reconciliation/storage coverage, plus updated smoke tests for module availability.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@levineam, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 45 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9a2d578f-4ffe-4d3a-af62-5c0802830046

📥 Commits

Reviewing files that changed from the base of the PR and between 755e2ab and a234d17.

📒 Files selected for processing (5)
  • modules/jarvos-control-plane/src/reconciliation/index.js
  • modules/jarvos-control-plane/src/storage/index.js
  • modules/jarvos-control-plane/test/reconciliation.test.js
  • modules/jarvos-control-plane/test/storage.test.js
  • tests/modules-smoke-test.js
📝 Walkthrough

Walkthrough

Adds the new @jarvos/control-plane package with versioned contracts, policy and manager registries, reconciliation, lease/fence storage, evidence handling, tests, documentation, package exports, and smoke-test integration.

Changes

Control Plane Module

Layer / File(s) Summary
Contracts and record lifecycle
modules/jarvos-control-plane/src/contracts/index.js, modules/jarvos-control-plane/test/contracts.test.js
Adds versioned records, canonical hashing and keys, validation, sensitivity-filtered projections, lifecycle transitions, and contract tests.
Policy and manager authority
modules/jarvos-control-plane/src/policy/index.js, modules/jarvos-control-plane/src/registry/index.js, modules/jarvos-control-plane/test/reconciliation.test.js
Adds policy decisions, manager compatibility and trust gating, ownership conflicts, manager selection, and related tests.
Leases, fences, and persistence
modules/jarvos-control-plane/src/storage/index.js, modules/jarvos-control-plane/test/concurrency.test.js, modules/jarvos-control-plane/test/storage.test.js
Adds memory and file-backed stores with journaling, leases, monotonic fences, stale-fence rejection, recovery, persistence, and concurrency tests.
Request reconciliation flow
modules/jarvos-control-plane/src/reconciliation/index.js, modules/jarvos-control-plane/test/reconciliation.test.js
Adds policy-first reconciliation, command deduplication, fenced execution, verification, evidence commitment, and terminal states.
Package, documentation, and repository integration
modules/jarvos-control-plane/package.json, modules/jarvos-control-plane/src/index.js, modules/jarvos-control-plane/README.md, modules/README.md, docs/architecture/control-plane.md, package.json, scripts/smoke-test.sh, tests/modules-smoke-test.js
Adds package exports, documentation, published files, and smoke-test coverage.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Requester
  participant ControlPlane
  participant PolicyEngine
  participant Registry
  participant Store
  participant Manager
  Requester->>ControlPlane: Submit structured request
  ControlPlane->>PolicyEngine: Evaluate policy
  ControlPlane->>Registry: Select executable manager
  ControlPlane->>Store: Deduplicate and acquire fenced lease
  ControlPlane->>Manager: Execute fenced command
  Manager-->>ControlPlane: Return execution result
  ControlPlane->>Manager: Verify side effect
  Manager-->>ControlPlane: Return verification result
  ControlPlane->>Store: Commit evidence and lifecycle state
Loading

Possibly related PRs

  • levineam/jarvOS#11: Extends the existing module smoke-test and documentation integration used by this change.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding the jarvOS control plane core module.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch SUP-3464/control-plane

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@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

🧹 Nitpick comments (1)
modules/jarvos-control-plane/README.md (1)

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

Avoid top-level await with CommonJS require.

Node.js does not support top-level await in CommonJS modules (which the require usage implies). If a user copies and pastes this snippet into a standalone .js script, it will throw a SyntaxError. Consider wrapping the execution in an async IIFE so the example runs seamlessly out of the box.

♻️ Proposed refactor
-await reconciler.reconcileRequest(request);
+(async () => {
+  await reconciler.reconcileRequest(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 `@modules/jarvos-control-plane/README.md` around lines 90 - 145, Update the
README example around createMemoryStore, createReconciler, and reconcileRequest
to avoid top-level await in the CommonJS snippet. Wrap the asynchronous setup
and reconciler.reconcileRequest(request) call in an async IIFE so the copied
standalone .js example executes without a syntax error.
🤖 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 `@modules/jarvos-control-plane/src/contracts/index.js`:
- Around line 89-101: Update canonicalResourceKey to build the key with the
existing stableStringify helper over the ordered tuple [resource.machineId,
resource.type, resource.id] instead of delimiter concatenation. Preserve the
current validation and canonicalMutationKey behavior, including its
mutationClass suffix handling.
- Around line 224-238: Update createCommand to always derive resourceKey,
specDigest, and actionKey from the source fields instead of accepting
caller-provided values. Extend validateRecord to recompute and compare all three
canonical values—resourceKey, specDigest, and buildActionKey(record)—and reject
mismatches, including for imported records.
- Around line 375-395: Update lifecycleTransition to enforce legal status
changes using an allowed-transition map keyed by the command’s current status.
Reject any destination not permitted, including all transitions out of terminal
statuses such as satisfied, failed, and policy_denied, before cloning or
mutating the command; preserve the existing unknown-status validation and valid
transition behavior.
- Around line 350-370: Update toPublicProjection to reject or safely default
unknown options.maxSensitivity values before comparing sensitivity ranks,
ensuring invalid requests cannot expose the full record. Replace direct delete
projection[field.path] field redaction with nested-path deletion so paths such
as principal.credentials.token are removed correctly, and apply this redaction
before returning the projection.
- Around line 333-346: Update validateManagerManifest to require
supportedCoreVersions to be an array before accepting the manifest. Add this
validation alongside the existing manifest field checks so malformed string or
object values are rejected during validation, preventing versionCompatible from
calling .some() on invalid input.

In `@modules/jarvos-control-plane/src/reconciliation/index.js`:
- Around line 57-65: Update the existing-command status filter in the
reconciliation flow to exclude conflict and deferred outcomes from
deduplication, alongside failed, unverifiable, and expired. Ensure commands with
those statuses proceed as new retry attempts while preserving deduplication for
other active or successful statuses.
- Around line 118-121: Wrap the verifier invocation in the reconciliation flow
around verifier and lifecycleTransition so rejected or thrown verifier errors
are caught before the command remains in the persisted verifying state. Convert
failures through the existing evidence-rejection response path, ensuring
reconcileRequest returns a terminal outcome rather than allowing the verifier
error to escape.
- Around line 69-74: Update the reconciliation flow around acquireLease and
commitEvidence to release the acquired lease on every terminal path, including
deferred execution, failures, evidence rejection, and successful completion.
Ensure cleanup runs after each outcome while preserving the existing result
handling, so mutation keys are not blocked until TTL expiry.

In `@modules/jarvos-control-plane/src/registry/index.js`:
- Around line 87-110: The registration flow around mutationOwnershipKey and
managers.set must stage candidate ownership without mutating ownership, detect
all conflicts, and only commit staged keys after the registration remains
executable. Before committing, remove every ownership entry previously belonging
to manifest.managerId so re-registration drops removed mutation classes; then
atomically add the candidate keys, while leaving ownership unchanged for
conflicted or observation-only registrations.

In `@modules/jarvos-control-plane/src/storage/index.js`:
- Around line 79-95: Validate in acquireLease that input.holder is non-empty
before checking the existing lease, and reject missing or empty holders with the
established invalid-input error behavior. Preserve the existing lease-conflict
comparison and acquisition flow for valid holders.
- Around line 56-59: Update putRecord so that when replacing an existing
command, it removes the prior command’s actionKey mapping before registering the
new actionKey; preserve the existing record storage and append behavior.
- Around line 96-105: Update the lease construction in the acquire flow to
derive the default expiresAt from the injected now clock rather than Date.now(),
and use a nullish/default-preserving check for ttlMs so an explicit zero
produces immediate expiration instead of falling back to 60000.
- Around line 111-124: The commitEvidence function must reject submissions from
leases that have expired, in addition to validating the current lease ID and
fence. Check the lease’s validity/expiration before putRecord(record), and
return the existing stale_fence rejection result with evidence.rejected logging
when the lease is no longer valid.
- Around line 155-158: Update createFileStore so startup state is reconstructed
by replaying journal.ndjson after loading state.json, or make state.json the
authoritative durable source and ensure mutations cannot be lost between state
writes and journal appends. Preserve the existing initialState fallback while
handling missing or corrupted state.json through the selected durability
strategy.
- Around line 150-180: Update createFileStore and its wrapped lease-write path
to use a cross-process lock or CAS for each state mutation, reloading the latest
state before applying and persisting the operation so concurrent stores sharing
rootDir cannot grant the same lease. In
modules/jarvos-control-plane/test/concurrency.test.js lines 10-36, replace the
single in-memory-store race with a race between separate file stores using one
rootDir, asserting that only one lease is granted.
- Around line 150-158: Update createFileStore and its persistence path to
coordinate concurrent instances sharing the same directory, using cross-instance
locking or compare-and-swap before committing state. Ensure lease/fence updates
detect conflicts and never unconditionally overwrite a newer state snapshot;
preserve the existing store behavior when no concurrent writer is present.

In `@tests/modules-smoke-test.js`:
- Around line 500-502: Update the smoke test’s control-plane import in the try
block to require the package entrypoint through its package name or package
root, rather than directly loading src/index.js; keep the existing
createRegistry call and assertions unchanged.

---

Nitpick comments:
In `@modules/jarvos-control-plane/README.md`:
- Around line 90-145: Update the README example around createMemoryStore,
createReconciler, and reconcileRequest to avoid top-level await in the CommonJS
snippet. Wrap the asynchronous setup and reconciler.reconcileRequest(request)
call in an async IIFE so the copied standalone .js example executes without a
syntax error.
🪄 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: 8c91d3e2-cc93-45d9-ba62-559c8ba00eeb

📥 Commits

Reviewing files that changed from the base of the PR and between 21abbc4 and 566a7b2.

📒 Files selected for processing (17)
  • docs/architecture/control-plane.md
  • modules/README.md
  • modules/jarvos-control-plane/README.md
  • modules/jarvos-control-plane/package.json
  • modules/jarvos-control-plane/src/contracts/index.js
  • modules/jarvos-control-plane/src/index.js
  • modules/jarvos-control-plane/src/policy/index.js
  • modules/jarvos-control-plane/src/reconciliation/index.js
  • modules/jarvos-control-plane/src/registry/index.js
  • modules/jarvos-control-plane/src/storage/index.js
  • modules/jarvos-control-plane/test/concurrency.test.js
  • modules/jarvos-control-plane/test/contracts.test.js
  • modules/jarvos-control-plane/test/reconciliation.test.js
  • modules/jarvos-control-plane/test/storage.test.js
  • package.json
  • scripts/smoke-test.sh
  • tests/modules-smoke-test.js

Comment on lines +89 to +101
function canonicalResourceKey(resource = {}) {
if (!isObject(resource)) throw new Error('resource must be an object');
for (const field of ['machineId', 'type', 'id']) {
if (!resource[field]) throw new Error(`resource.${field} is required`);
}
return `${resource.machineId}:${resource.type}:${resource.id}`;
}

function canonicalMutationKey({ machineId, resource, mutationClass }) {
if (!mutationClass) throw new Error('mutationClass is required');
const base = resource ? canonicalResourceKey(resource) : machineId;
if (!base) throw new Error('machineId or resource is required');
return `${base}:${mutationClass}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Use collision-safe encoding for canonical keys.

The unrestricted components are joined with :, so distinct resources can share a key—for example, machineId="a:b", type="c" and machineId="a", type="b:c". This can merge action deduplication or mutation ownership across resources.

Use a canonical tuple encoding such as stableStringify([machineId, type, id]) rather than delimiter concatenation.

🤖 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 `@modules/jarvos-control-plane/src/contracts/index.js` around lines 89 - 101,
Update canonicalResourceKey to build the key with the existing stableStringify
helper over the ordered tuple [resource.machineId, resource.type, resource.id]
instead of delimiter concatenation. Preserve the current validation and
canonicalMutationKey behavior, including its mutationClass suffix handling.

Comment on lines +224 to +238
resourceKey: input.resourceKey || canonicalResourceKey(input.resource),
mutationClass: input.mutationClass,
desiredGeneration: input.desiredGeneration,
commandSpec,
specDigest: commandSpecDigest(commandSpec),
actionKey: input.actionKey || null,
status: input.status || 'requested',
lifecycle: input.lifecycle || [],
leaseId: input.leaseId || null,
fence: input.fence || null,
checkpoint: input.checkpoint || null,
approval: input.approval || null,
};
record.actionKey = record.actionKey || buildActionKey(record);
validateRecord(record);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Reject non-canonical command identity fields.

createCommand accepts an arbitrary actionKey, while validateRecord merely computes buildActionKey(record) without comparing it. It also does not verify resourceKey and specDigest against their source fields. Imported records can therefore bypass deduplication by supplying inconsistent derived values.

Always derive these fields during creation and compare all three during validation.

Also applies to: 293-329

🤖 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 `@modules/jarvos-control-plane/src/contracts/index.js` around lines 224 - 238,
Update createCommand to always derive resourceKey, specDigest, and actionKey
from the source fields instead of accepting caller-provided values. Extend
validateRecord to recompute and compare all three canonical values—resourceKey,
specDigest, and buildActionKey(record)—and reject mismatches, including for
imported records.

Comment on lines +333 to +346
function validateManagerManifest(manifest = {}) {
if (!isObject(manifest)) throw new Error('manager manifest must be an object');
if (manifest.schemaVersion !== MANAGER_SCHEMA_VERSION) throw new Error(`Unsupported manager schemaVersion: ${manifest.schemaVersion}`);
assertCompatibleVersion(manifest.contractVersion);
if (!manifest.managerId) throw new Error('managerId is required');
if (!Array.isArray(manifest.capabilities)) throw new Error('capabilities must be an array');
if (!Array.isArray(manifest.mutationClasses)) throw new Error('mutationClasses must be an array');
for (const mutation of manifest.mutationClasses) {
if (!mutation || !mutation.class) throw new Error('mutationClasses entries require class');
if (!mutation.resourceType) throw new Error(`mutation ${mutation.class} requires resourceType`);
}
if (manifest.trust && !['data-only', 'trusted'].includes(manifest.trust.level)) {
throw new Error('trust.level must be data-only or trusted');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Validate supportedCoreVersions as an array.

A manifest containing a string or object passes this validation, then versionCompatible in modules/jarvos-control-plane/src/registry/index.js Line 16 calls .some() and crashes registration instead of returning an incompatible health-only entry.

Proposed validation
   if (!manifest.managerId) throw new Error('managerId is required');
+  if (!Array.isArray(manifest.supportedCoreVersions)) {
+    throw new Error('supportedCoreVersions must be an array');
+  }
   if (!Array.isArray(manifest.capabilities)) throw new Error('capabilities must be an array');
📝 Committable suggestion

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

Suggested change
function validateManagerManifest(manifest = {}) {
if (!isObject(manifest)) throw new Error('manager manifest must be an object');
if (manifest.schemaVersion !== MANAGER_SCHEMA_VERSION) throw new Error(`Unsupported manager schemaVersion: ${manifest.schemaVersion}`);
assertCompatibleVersion(manifest.contractVersion);
if (!manifest.managerId) throw new Error('managerId is required');
if (!Array.isArray(manifest.capabilities)) throw new Error('capabilities must be an array');
if (!Array.isArray(manifest.mutationClasses)) throw new Error('mutationClasses must be an array');
for (const mutation of manifest.mutationClasses) {
if (!mutation || !mutation.class) throw new Error('mutationClasses entries require class');
if (!mutation.resourceType) throw new Error(`mutation ${mutation.class} requires resourceType`);
}
if (manifest.trust && !['data-only', 'trusted'].includes(manifest.trust.level)) {
throw new Error('trust.level must be data-only or trusted');
}
function validateManagerManifest(manifest = {}) {
if (!isObject(manifest)) throw new Error('manager manifest must be an object');
if (manifest.schemaVersion !== MANAGER_SCHEMA_VERSION) throw new Error(`Unsupported manager schemaVersion: ${manifest.schemaVersion}`);
assertCompatibleVersion(manifest.contractVersion);
if (!manifest.managerId) throw new Error('managerId is required');
if (!Array.isArray(manifest.supportedCoreVersions)) {
throw new Error('supportedCoreVersions must be an array');
}
if (!Array.isArray(manifest.capabilities)) throw new Error('capabilities must be an array');
if (!Array.isArray(manifest.mutationClasses)) throw new Error('mutationClasses must be an array');
for (const mutation of manifest.mutationClasses) {
if (!mutation || !mutation.class) throw new Error('mutationClasses entries require class');
if (!mutation.resourceType) throw new Error(`mutation ${mutation.class} requires resourceType`);
}
if (manifest.trust && !['data-only', 'trusted'].includes(manifest.trust.level)) {
throw new Error('trust.level must be data-only or trusted');
}
🤖 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 `@modules/jarvos-control-plane/src/contracts/index.js` around lines 333 - 346,
Update validateManagerManifest to require supportedCoreVersions to be an array
before accepting the manifest. Add this validation alongside the existing
manifest field checks so malformed string or object values are rejected during
validation, preventing versionCompatible from calling .some() on invalid input.

Comment on lines +350 to +370
function toPublicProjection(record, options = {}) {
const projection = clone(record);
const maxLevel = options.maxSensitivity || 'internal';
const order = new Map(SENSITIVITY_LEVELS.map((level, index) => [level, index]));
if (order.get(record.sensitivity && record.sensitivity.level || 'internal') > order.get(maxLevel)) {
return {
schemaVersion: record.schemaVersion,
contractVersion: record.contractVersion,
type: record.type,
id: record.id,
sensitivity: record.sensitivity,
redacted: true,
};
}
if (!options.includeAdapterExtensions) delete projection.adapterExtensions;
if (projection.sensitivity && projection.sensitivity.fields) {
for (const field of projection.sensitivity.fields) {
if (field.level === 'secret' || field.level === 'private') {
delete projection[field.path];
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Make sensitivity projection fail closed.

An unknown maxSensitivity has no rank, causing the comparison at Line 354 to return false and expose the complete record. Field-level redaction also uses delete projection[field.path], which does not remove nested paths such as principal.credentials.token.

Validate the requested level and apply nested-path redaction before returning the projection.

🤖 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 `@modules/jarvos-control-plane/src/contracts/index.js` around lines 350 - 370,
Update toPublicProjection to reject or safely default unknown
options.maxSensitivity values before comparing sensitivity ranks, ensuring
invalid requests cannot expose the full record. Replace direct delete
projection[field.path] field redaction with nested-path deletion so paths such
as principal.credentials.token are removed correctly, and apply this redaction
before returning the projection.

Comment on lines +375 to +395
function lifecycleTransition(command, status, patch = {}) {
if (!COMMAND_STATUSES.includes(status)) throw new Error(`Unknown command status: ${status}`);
const next = clone(command);
next.status = status;
next.updatedAt = normalizeTimestamp(patch.at);
next.lifecycle = Array.isArray(next.lifecycle) ? next.lifecycle : [];
next.lifecycle.push({
status,
at: next.updatedAt,
actor: patch.actor || null,
runId: patch.runId || null,
requestId: next.requestId || null,
generation: next.desiredGeneration || null,
checkpoint: patch.checkpoint || null,
reason: patch.reason || null,
});
if (patch.checkpoint) next.checkpoint = patch.checkpoint;
if (patch.leaseId) next.leaseId = patch.leaseId;
if (patch.fence) next.fence = patch.fence;
validateRecord(next);
return next;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Enforce legal lifecycle transitions.

This accepts any known destination status regardless of the current state, allowing terminal commands such as satisfied, failed, or policy_denied to transition back to dispatching or executed. That undermines lifecycle and evidence integrity.

Define an allowed-transition map and reject transitions from terminal states.

🤖 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 `@modules/jarvos-control-plane/src/contracts/index.js` around lines 375 - 395,
Update lifecycleTransition to enforce legal status changes using an
allowed-transition map keyed by the command’s current status. Reject any
destination not permitted, including all transitions out of terminal statuses
such as satisfied, failed, and policy_denied, before cloning or mutating the
command; preserve the existing unknown-status validation and valid transition
behavior.

Comment thread modules/jarvos-control-plane/src/storage/index.js Outdated
Comment thread modules/jarvos-control-plane/src/storage/index.js Outdated
Comment thread modules/jarvos-control-plane/src/storage/index.js Outdated
Comment thread modules/jarvos-control-plane/src/storage/index.js Outdated
Comment thread tests/modules-smoke-test.js
@levineam

Copy link
Copy Markdown
Owner Author

Changes requested — independent high-tier review found 7 P1 defects

Ran the independent Codex review (gpt-5.6-terra, effort high, read-only) against PR #99 per the high-tier review rule. Do not merge. Seven P1 findings; four are empirically proven by executing the module, and I independently reproduced all four outside the reviewer's sandbox.

Proven by execution (reproduced twice)

unknown version 1.999.0  -> {"compatible":true,"executable":true}   # must fail closed
malformed  1.anything    -> {"compatible":true,"executable":true}   # must fail closed
canonicalResourceKey({machineId:'a:b',type:'c',id:'d'})
  === canonicalResourceKey({machineId:'a',type:'b:c',id:'d'})       # -> both "a:b:c:d"
commandSpecDigest({value: NaN}) === commandSpecDigest({value: null}) # -> true

P1 findings

  1. src/storage/index.js:155 — file store is process-local; cross-process leases do not work. createFileStore reads state.json once into an in-memory store, then makes every lease/action-key decision in process memory. Two processes both read empty state, both grant fence 1, both dispatch; the atomic renames only decide whose state is visible last. This defeats R12 fencing, atomic CAS lease acquisition, and R13 idempotency at the same time. Needs an OS-level lock or CAS around read-modify-write plus action-key insertion.
  2. src/storage/index.js:160 — advertised crash recovery is not implemented. Startup never reads journal.ndjson (confirmed: initialState comes only from state.json); writeState() renames before the journal append; there is no fsync/fdatasync anywhere (confirmed by grep). A crash after rename/before append yields state with no journal evidence; a torn final line is neither detected nor tolerated. R32/R30 require write-ahead, framed, fsynced appends replayed at startup.
  3. src/reconciliation/index.js:106 — fencing is advisory only. The fence is handed to plugin code but never enforced at the external side-effect boundary, and there is no atomic "is this fence current?" operation. A slow command holding fence 1 can be superseded by fence 2 and still perform its side effect; only its later evidence commit is rejected. createManagerManifest also accepts an empty operationContract. R12/R33 require target-side fenced mutation to be declared and validated before execution.
  4. src/policy/index.js:11 — allow-by-default path exists. Callers can pass defaultOutcome: 'allow', making every unmatched/unknown mutation execute. R14/AE7 require unmatched input to deny or defer; an allow default should be rejected outright.
  5. src/contracts/index.js:63 — version check accepts any 1.* string, including unknown (1.999.0) and malformed (1.anything), marking trusted managers executable. Contradicts R8/KTD5 fail-closed. Needs strict semver parsing plus an explicit supported-version table.
  6. src/contracts/index.js:79 — resource keys collide. Colon-joined, unescaped fields mean distinct resource tuples produce identical keys, so independent resources silently share mutation leases and action keys. Hash a structured tuple or escape/length-prefix each component.
  7. src/contracts/index.js:40 — digest canonicalization aliases values. JSON.stringify(NaN) and JSON.stringify(null) are both null, so {value: NaN} and {value: null} share an action key. Reject non-finite numbers/undefined/functions, or use a typed canonical encoding.

P2

  1. src/reconciliation/index.js:121 — verifier failures are uncaught. A rejecting verify leaves the command permanently in verifying; equivalent requests then dedupe onto it and can never retry. Catch, persist an unverifiable/failed terminal transition, return a structured result.

Test-quality note (the reason this got through)

All 17 tests pass, but they cannot detect the defects above. The "concurrency" test runs two microtasks against one in-memory Map — it never exercises two processes. The file-store "recovery" test just reopens state.json — it never kills a write mid-flight or replays the journal. The tests are the evidence this module is trustworthy, so each fix needs a test that fails against the current implementation: a real two-process CAS race, a torn/truncated journal line, and a crash between state rename and journal append.

Not a defect: test/storage.test.js:40 failed with EPERM mkdtemp in my run — that is a read-only-sandbox artifact, not a code bug.

Disposition

Setting back to in_progress for Michael. These are contract-level defects in the module that ten lanes (SUP-3465SUP-3474, SUP-3417) will build against, so they must be fixed before the v1 freeze — this is exactly the RC-validation gate KTD20 of the plan calls for. Re-request review when the findings are resolved and the new failing-first tests are green; the independent review will be re-run until it comes back clean.

@levineam
levineam force-pushed the SUP-3464/control-plane branch from 566a7b2 to 755e2ab Compare July 17, 2026 00:21

@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

♻️ Duplicate comments (1)
tests/modules-smoke-test.js (1)

501-501: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Exercise the package entrypoint instead of importing its source directly.

Requiring src/index.js bypasses modules/jarvos-control-plane/package.json, so this smoke test can pass even if the published entrypoint configuration is broken.

♻️ Proposed fix
-  const controlPlane = require(path.join(ROOT, 'modules/jarvos-control-plane/src/index.js'));
+  const controlPlane = require(path.join(ROOT, 'modules/jarvos-control-plane'));
🤖 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 `@tests/modules-smoke-test.js` at line 501, Update the controlPlane import in
the smoke test to load the jarvos-control-plane package through its package
entrypoint rather than directly requiring src/index.js. Keep the existing
controlPlane usage unchanged and ensure module resolution exercises the
package.json entrypoint configuration.

Source: Linters/SAST tools

🧹 Nitpick comments (1)
modules/jarvos-control-plane/src/storage/index.js (1)

113-121: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

load() fully re-parses and replays the entire journal on every single call, including reads.

mutate() and read() both call load(), and load() always runs parseJournal + replayJournal from scratch (line 121). This makes every getRecord, acquireLease, commitEvidence, etc. an O(n) operation over the full journal history, with no compaction/checkpoint mechanism — cost grows unbounded as the journal accumulates. Notably, state.json is written atomically on every persist() (125-127) but is never read back anywhere in this file, so all that I/O buys no reduction in replay cost.

Consider using state.json as a periodic checkpoint (snapshot + last-replayed sequence) and only replaying journal entries appended after that checkpoint, with a background/opportunistic compaction to truncate the journal once checkpointed.

Also applies to: 129-131

🤖 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 `@modules/jarvos-control-plane/src/storage/index.js` around lines 113 - 121,
Update createFileStore’s load and persistence flow so state.json is used as a
checkpoint containing the saved state and last-replayed journal sequence, rather
than always replaying the entire journal. In load(), read and validate the
checkpoint, replay only journal entries after its sequence, and preserve the
initial-state fallback when no valid checkpoint exists. Add opportunistic or
background compaction that safely writes an updated checkpoint and truncates
only the journal entries already included in it, while keeping mutate/read
behavior 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 `@modules/jarvos-control-plane/src/reconciliation/index.js`:
- Around line 99-104: Release the lease acquired before selecting the manager
execution port before returning from the unavailable-port branch in the
reconciliation flow. Update the branch around managers[selected.managerId] and
executeFenced so it calls store.releaseLease with the same resource and
mutationClass lease key used by the other terminal paths, while preserving the
deferred transition and response.
- Around line 57-69: Remove the non-atomic fallback around
store.putCommandIfAbsent and make that capability mandatory for injected stores.
Validate its presence during reconciliation/store initialization and fail fast
when unavailable, then have reconcileRequest use the atomic insertion method
directly so concurrent actionKey requests remain deduplicated and retryable
statuses are not incorrectly blocked.

In `@modules/jarvos-control-plane/src/storage/index.js`:
- Around line 116-120: Update withLock to detect and reclaim orphaned lock files
before timing out: when exclusive creation encounters EEXIST, inspect lockPath
metadata and remove the lock if it exceeds the configured lockTimeoutMs, then
retry acquisition. Preserve immediate propagation of non-EEXIST errors and
ensure cleanup still closes and unlinks only the lock acquired by the current
invocation.
- Around line 122-128: Update persist so failures during the state.json
temporary-file write, fsync, or rename are caught after the journal append has
been successfully fsynced; log the state persistence error and allow the
mutation to continue. Preserve cleanup and directory fsync behavior where
applicable, and add a regression test covering a successful journal append
followed by a state-file failure.

---

Duplicate comments:
In `@tests/modules-smoke-test.js`:
- Line 501: Update the controlPlane import in the smoke test to load the
jarvos-control-plane package through its package entrypoint rather than directly
requiring src/index.js. Keep the existing controlPlane usage unchanged and
ensure module resolution exercises the package.json entrypoint configuration.

---

Nitpick comments:
In `@modules/jarvos-control-plane/src/storage/index.js`:
- Around line 113-121: Update createFileStore’s load and persistence flow so
state.json is used as a checkpoint containing the saved state and last-replayed
journal sequence, rather than always replaying the entire journal. In load(),
read and validate the checkpoint, replay only journal entries after its
sequence, and preserve the initial-state fallback when no valid checkpoint
exists. Add opportunistic or background compaction that safely writes an updated
checkpoint and truncates only the journal entries already included in it, while
keeping mutate/read behavior 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: a7f74a14-76d1-40d9-94d0-0d8c553c291c

📥 Commits

Reviewing files that changed from the base of the PR and between 566a7b2 and 755e2ab.

📒 Files selected for processing (17)
  • docs/architecture/control-plane.md
  • modules/README.md
  • modules/jarvos-control-plane/README.md
  • modules/jarvos-control-plane/package.json
  • modules/jarvos-control-plane/src/contracts/index.js
  • modules/jarvos-control-plane/src/index.js
  • modules/jarvos-control-plane/src/policy/index.js
  • modules/jarvos-control-plane/src/reconciliation/index.js
  • modules/jarvos-control-plane/src/registry/index.js
  • modules/jarvos-control-plane/src/storage/index.js
  • modules/jarvos-control-plane/test/concurrency.test.js
  • modules/jarvos-control-plane/test/contracts.test.js
  • modules/jarvos-control-plane/test/reconciliation.test.js
  • modules/jarvos-control-plane/test/storage.test.js
  • package.json
  • scripts/smoke-test.sh
  • tests/modules-smoke-test.js
🚧 Files skipped from review as they are similar to previous changes (12)
  • modules/jarvos-control-plane/src/index.js
  • modules/jarvos-control-plane/package.json
  • package.json
  • scripts/smoke-test.sh
  • modules/jarvos-control-plane/test/concurrency.test.js
  • modules/jarvos-control-plane/test/reconciliation.test.js
  • modules/jarvos-control-plane/README.md
  • modules/README.md
  • modules/jarvos-control-plane/src/policy/index.js
  • modules/jarvos-control-plane/src/registry/index.js
  • docs/architecture/control-plane.md
  • modules/jarvos-control-plane/src/contracts/index.js

Comment thread modules/jarvos-control-plane/src/reconciliation/index.js Outdated
Comment thread modules/jarvos-control-plane/src/reconciliation/index.js
Comment thread modules/jarvos-control-plane/src/storage/index.js
Comment thread modules/jarvos-control-plane/src/storage/index.js
@levineam
levineam merged commit ed03605 into main Jul 17, 2026
7 checks passed
@levineam
levineam deleted the SUP-3464/control-plane branch July 17, 2026 00:43
@levineam

Copy link
Copy Markdown
Owner Author

Round-2 independent review of this merged core: round-1 P1s are confirmed genuinely fixed (re-probed empirically). Residual defects found in the fixes — two P1s (stale-lock recovery TOCTOU race; append-after-torn-journal-frame permanently bricks the store, reproduced on ed03605) plus four P2s — are tracked in Paperclip SUP-3477, which now blocks the v1 contract freeze (SUP-3466) and the clawd artifact pin (SUP-3465). Downstream lanes may build, but must consume the post-hardening commit.

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