Current assessment · Round 5 · 13 September 2026

The remaining voice blocker is fixed and pushed to #15.

c7325e061 · Preserve uncertain voice reservations until confirmed closure

Each account reservation now has its own database-generated identity. A null upstream id represents an in-flight or unknown creation outcome. Failed identity writes, lost responses, timeouts and interruption retain that reservation through expiry and restarts. Only confirmed rejection or closure frees it. Cleanup and release address the exact reservation, protecting replacement sessions from delayed operations.

The migration preserves known sessions and converts legacy empty ids to null. The runbook describes inspection and recovery. Unknown outcomes can require operator recovery if the upstream identity cannot be recovered; elapsed local time is never used as proof of closure.

Validation: 44 focused voice/HTTP tests passed, relay typecheck passed, targeted lint passed. Real PostgreSQL verified migration of seeded legacy rows, both identity writes failing, expiry refusing replacement, and recovery through the exact reservation identity. A separate combined-source check of #15 and #16 passed 41 voice/device-policy tests. No real paid voice calls or microphone acceptance were run.

CI: fully green on #15 at c7325e061. Follow the run.

All actionable review findings are resolved. #15 can now merge first; retarget and recheck #16 against main afterward. The no-key path and auto-disabled device label remain accepted follow-ups. The command-center wireframe remains available below.

Earlier assessments are preserved as history. This Round 5 section is current.

Current assessment · Round 4 · 13 September 2026

Device-cap fix verified. One voice blocker remains.

#16 at d10702816 passes the cap review. Hold #15 at e4cc4bd42 and the stacked merge. Both heads are green and conflict-free on GitHub. f3 is resolved; f15 is partially fixed and remains the only actionable blocker.

Real PostgreSQL proof for #16: all four new checks pass against PostgreSQL 16 with the PR’s migrations and independent connection pools. Concurrent enables stay at five; upsert and enable share the account lock; a failed final enable rolls back eviction; reviving at capacity stays disabled and refreshing preserves the disabled state. These tests use the real Drizzle and RelayTransactions implementation, without a semaphore fake.

Remaining f15: the new recovery write is best-effort. If both finalization and the recovery identity write fail, the upstream may remain active while the row retains an empty session id. The immediate retry is refused, but expiry treats that empty id as no upstream session.

  1. Create upstream session successfully.
  2. Fail both writes that would save its identity, and fail upstream close.
  3. Restore database writes and advance the test clock by 11 minutes.
  4. Retry creation: the old empty reservation is deleted and a second upstream session is created.

Observed: two active sessions; expected: at most one. This fails on both the combined #16 snapshot and standalone #15. The successful-recovery-write case, later release after closure recovers, and confirmed compensation all pass. The original f15 text already required handling incomplete reservations through expiry; this is the remaining part of that finding.

Fix direction: an empty identity must not prove that upstream creation never happened. Persist the uncertain/in-progress state before the upstream call, and keep that state blocked through expiry until cleanup is confirmed or an enforced upstream lifetime proves it ended. Retrying a failed identity write once cannot supply that guarantee.

Validation: 40 focused tests passed and one failed; four real PostgreSQL checks passed. Separately confirming the three added voice checks on #15 gave two passes and the same failure. Voice uses a fake upstream and TestClock, not paid calls or microphone acceptance. The PostgreSQL container was stopped after testing.

The no-key end-to-end path and auto-disabled device label remain nonblocking follow-ups. After f15 is fixed, merge #15 first, then retarget/recheck #16 against main. This assessment does not certify product distribution or real-device voice acceptance.

#15 green CI · #16 green CI

Earlier rounds below are historical. Round 4 is the current verdict.

Current assessment · Round 3 · 13 September 2026

Closer, but do not merge either PR yet.

Both pushed heads are green and GitHub reports CLEAN / MERGEABLE. Source review and focused negative tests still identify one blocker in each PR. f3 was never accepted as a nonblocking follow-up in this review.

37 existing focused tests pass; two new negative regressions fail. This includes the sideband socket success and unavailable tests. No real cloud audio or PostgreSQL concurrency test was run.

Confirmed fixes: f2 expiry now ends the account’s old session before deletion; f12 release deletion is session-qualified; f13 refresh preserves disabled active links; f14 the integration stub is complete. Typed persistence errors and release upstream errors are also preserved.

Merge decision: request changes on #15 at d73250a1c and #16 at cb3a0e6f8. Each scores 4/5 under the issue-count rubric, but each still has a major correctness defect. Once those fixes pass, merge #15 first and retarget/recheck #16.

The no-key end-to-end path and auto-disabled device label remain accepted follow-ups. CodeRabbit’s quota is not the blocker; these reproduced failures are.

DB sizing changed from PS_20 to PS_5 and removed explicit replicas:2. That configuration change does not address the account races; capacity, availability and savings were not verified here.

#15 green CI · #16 green CI

Earlier rounds below are preserved as historical review evidence. The current verdict is this Round 3 section.

Current assessment · Round 2 · 13 September 2026

Real fixes, but both PRs still need changes.

Six of the original eight actionable findings are resolved. The account-ownership query, target validation, disabled voice policy, managed-use timestamp, mutation retry, and tooltip corrections are present. They improved the code.

#15: 3/5. #16: 2/5. Do not merge yet. The slot still expires without closing its upstream session; the device cap is serialized only inside one service instance. Two new behavior regressions also need correction.

33 existing focused tests pass; four new negative regressions fail. These are controlled service tests with fake database/upstream adapters, not production cloud or real-Postgres acceptance.

#15 CI is green. #16 Test is green, but Check fails because the two-node server integration stub is missing setEnvironmentEnabled. The current blocker is not the mobile highlighter.

Verified #15 at 787dd56dd and #16 at ac0484b00. The no-key product path and naming the auto-disabled machine remain accepted follow-up work; neither is why this review requests changes.

Reconciliation with the original review
FindingStatusVerification
f1ResolvedOwnership now selects all non-revoked links independently of notification preferences. Zero/multiple owners are explicitly rejected.
f2Partial / openPartial: explicit release waits on upstream.end, and failed end retains the slot. Creation still deletes every expired reservation without ending its upstream session. A new negative regression leaves two upstream sessions active. The actual LiveVoiceUpstream socket adapter was not exercised by these service tests.
f3Partial / openPartial: migration backfill and conflict-path cap selection were added. The semaphore map exists only within one service instance, upsert does not use it, and eviction/enable remain separate database statements. Two independent service instances sharing the fake database end with six enabled machines.
f4ResolvedUnknown or foreign targets are checked before eviction and return EnvironmentLinkNotFound. The supplied regression passes. Cross-instance atomicity remains tracked separately under f3.
f5ResolvedCreation requires owner.enabled; release permits a disabled owner to clean up. The supplied disabled-create regression passes.
f6ResolvedSuccessful managed connection records lastUsedAt; ordering uses coalesce(lastUsedAt, createdAt). This is last managed connection, not continuous activity or direct-route usage.
f7ResolvedA failed mutation now renders an error and a row Retry action. Source verified; no browser run.
f8ResolvedThe native title attribute was replaced with the shared Tooltip components. The latest CI failure is a different, in-scope type error recorded as f14.
f9Accepted follow-upAccepted follow-up. The no-key end-to-end client/node path is still outside these PRs and is not the reason for requesting changes.
f10ResolvedThe narrowed access contract is documented: new managed relay connections and cloud voice creation only; not existing-session revocation or direct route access.
f11Partial / openPartial, nonblocking: shared default constant and autoDisabledEnvironmentId response added. The client still drops enabledLimit and the UI does not name the auto-disabled machine. A single build-time constant is not runtime server-policy discovery.

Before merging: make the device policy atomic in the shared database across all link mutations; preserve disabled state on active-link refresh; make voice expiry and duplicate release safe for the exact upstream session; fix the missing test-stub method; recheck the two pushed heads.

Everything below retains the first review’s source evidence. Resolved cards are marked and collapsed; original bodies are intentionally preserved.

Jarvis · Product review · 13 September 2026

Good direction. Not ready to distribute.

Keep the cloud broker and account controls, but fix their ownership and lifecycle contracts before shipping. The command center should put work and decisions ahead of machine configuration.

Reviewed #15 at 43e859b6 and #16 at 631bb623; #16 is stacked on #15. GitHub had three open PRs, so #14 at c47bda88 was also checked for release context. Source and CI retrieved 13 September 2026. Existing local work was excluded.

Explore the interactive wireframe →Read the findings
0 actionable open · 15 findings · Round 5+4976 −1380 comments posted to GitHub

LOC counts the two PR diffs independently, including 3,509 generated snapshot additions. #14 is a separate +20 −5.

5/5
All reviewed blockers fixed; CI green

Current: no open actionable findings. f15 is fixed in c7325e061 and f3 passed real PostgreSQL checks. Both score 5/5; full CI is green on #15.

PRJudgmentEvidence
#15 · Cloud live voice5/5 · Fix verifiedUnknown outcomes persist through expiry; exact reservation identity fences cleanup. 44 local focused tests pass; full CI green.
#16 · Device policy5/5 · Approve cap fixReal PostgreSQL concurrency, rollback, upsert, revive and refresh checks pass. The combined source with the #15 fix also passes focused tests.
#14 · Release config5/5 · Approve narrow changeCorrect relay package selector and configurable web hosts. CI green. This is a source/CI judgment, not a verified production release.
Walkthrough and architectural value

What the focused checks proved

28 existing tests passed. Three negative regressions failed. The tests ran against the isolated #16 snapshot, whose cloud-voice implementation is unchanged from #15.

Database and upstream HTTP adapters were fakes. These are focused demonstrations of service behavior, not PostgreSQL isolation tests or real audio calls. The existing notification query test also confirms why its reuse for billing ownership is wrong.

GitHub #16 Check fails on the new native title tooltip. Its Test job includes a mobile shikiReviewHighlighter assertion failure outside this diff; that failure was not attributed to the device-policy change. No repository-wide checks or product runtime launches were performed.

#16 CI evidence · #15 CI evidence · #14 CI evidence

Files changed in the reviewed stack (38 change entries)
PRFileDelta
#15.github/workflows/deploy-relay.yml+1 −0
#15infra/relay/.env.example+7 −0
#15infra/relay/migrations/postgres/20260912190518_live_voice_sessions/migration.sql+26 −0
#15infra/relay/migrations/postgres/20260912190518_live_voice_sessions/snapshot.json+1748 −0
#15infra/relay/src/Config.ts+6 −0
#15infra/relay/src/http/Api.ts+237 −134
#15infra/relay/src/persistence/schema.ts+12 −0
#15infra/relay/src/voice/LiveVoiceSessions.test.ts+269 −0
#15infra/relay/src/voice/LiveVoiceSessions.ts+218 −0
#15infra/relay/src/worker.ts+23 −1
#15packages/client-runtime/src/connection/errors.ts+18 −0
#15packages/client-runtime/src/relay/errorPresentation.ts+6 −0
#15packages/contracts/src/relay.ts+96 −1
#16apps/web/src/cloud/managedRelayState.ts+18 −0
#16apps/web/src/components/jarvis/JarvisControlCenter.css+40 −0
#16apps/web/src/components/jarvis/JarvisControlCenter.tsx+2 −0
#16apps/web/src/components/jarvis/JarvisMeshDevices.tsx+84 −0
#16infra/relay/migrations/postgres/20260912192834_environment_enabled/migration.sql+1 −0
#16infra/relay/migrations/postgres/20260912192834_environment_enabled/snapshot.json+1761 −0
#16infra/relay/src/agentActivity/AgentActivityPublisher.test.ts+1 −0
#16infra/relay/src/agentActivity/FcmDeliveries.test.ts+2 −0
#16infra/relay/src/agentActivity/MobileRegistrations.test.ts+1 −0
#16infra/relay/src/environments/EnvironmentConnector.test.ts+2 −0
#16infra/relay/src/environments/EnvironmentConnector.ts+16 −0
#16infra/relay/src/environments/EnvironmentLinker.test.ts+1 −0
#16infra/relay/src/environments/EnvironmentLinks.enabled.test.ts+141 −0
#16infra/relay/src/environments/EnvironmentLinks.ts+122 −1
#16infra/relay/src/http/Api.test.ts+4 −0
#16infra/relay/src/http/Api.ts+25 −1
#16infra/relay/src/persistence/schema.ts+1 −0
#16packages/client-runtime/src/authorization/layer.test.ts+1 −0
#16packages/client-runtime/src/connection/supervisor.test.ts+1 −0
#16packages/client-runtime/src/relay/discovery.test.ts+3 −0
#16packages/client-runtime/src/relay/errorPresentation.ts+3 −0
#16packages/client-runtime/src/relay/managedRelay.ts+24 −0
#16packages/client-runtime/src/relay/managedRelayState.test.ts+2 −0
#16packages/client-runtime/src/relay/managedRelayState.ts+29 −0
#16packages/contracts/src/relay.ts+24 −0
Issues table
Prompt for all actionable findings · Ctrl+`
Agent handoff
Prompt to re-run this review · Ctrl+Space
Agent handoff

Round 3 finding

Resolved in c7325e061P1 #15 · Retain the reservation when persistence compensation cannot close upstream

Round 3 · infra/relay/src/voice/LiveVoiceSessions.ts:263-276

The newly added post-create persistence compensation ignores failure from upstream.end and then deletes the account reservation. It therefore frees the slot even when the created upstream voice session may still be running.

If the usage insert fails after successful upstream creation and the sideband close is unavailable, the reservation is removed. The regression then retries creation and observes two active upstream sessions. This violates the same one-active-session invariant that ordinary release and expiry now preserve.

Keep the reservation blocked until upstream closure is confirmed. Preserve enough session identity to retry cleanup; an empty reservation that expiry later treats as never-created would only delay the same failure.

Fix direction. Make failed post-create persistence cleanup retain a recoverable reservation tied to the created session until closure is confirmed. Do not swallow a failed end and delete the row. Exercise usage-write failure, finalization failure, failed closure, retry, and expiry of incomplete reservations. Preserve the typed persistence error while maintaining the lifetime invariant.

New negative regression failed: expected at most one active upstream session after persistence/close failure and retry; observed two.

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 `infra/relay/src/voice/LiveVoiceSessions.ts` around lines 263-276, The newly added post-create persistence compensation ignores failure from upstream.end and then deletes the account reservation. It therefore frees the slot even when the created upstream voice session may still be running. Make failed post-create persistence cleanup retain a recoverable reservation tied to the created session until closure is confirmed. Do not swallow a failed end and delete the row. Exercise usage-write failure, finalization failure, failed closure, retry, and expiry of incomplete reservations. Preserve the typed persistence error while maintaining the lifetime invariant.

Round 2 findings

Resolved in round 3 P1 #15 · Delete only the session that this release actually closed

Round 2 · infra/relay/src/voice/LiveVoiceSessions.ts:271-274 · logic

The release lookup is qualified by user and session, but after awaiting the upstream close the final deletion uses only userId. That changes a previously session-qualified delete into an account-wide delete across an asynchronous gap.

Two releases can look up session A and start ending it. The first finishes and deletes A; a new request creates B. When the second release finishes, it deletes B’s reservation even though it only closed A. The controlled regression observes the B row disappear while B is still live.

The account is then free to start another session alongside B. Idempotency needs to hold across retries and replacement sessions, not just when the table is already empty at lookup time.

Fix direction. Qualify the final delete by the exact account/session or reservation identity that was closed. Apply the same identity discipline to creation finalizers and cleanup, and test a delayed duplicate release completing after replacement.

New negative regression failed: expected sess_2 reservation; received undefined.

Changed source excerpt
      yield* db
        .delete(relayLiveVoiceSessions)
        .where(eq(relayLiveVoiceSessions.userId, userId))
        .pipe(Effect.mapError(persistence("release-session")), Effect.orDie);
Prompt for AI agents
f12
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

In `infra/relay/src/voice/LiveVoiceSessions.ts` around lines 271-274, The release lookup is qualified by user and session, but after awaiting the upstream close the final deletion uses only userId. That changes a previously session-qualified delete into an account-wide delete across an asynchronous gap. Qualify the final delete by the exact account/session or reservation identity that was closed. Apply the same identity discipline to creation finalizers and cleanup, and test a delayed duplicate release completing after replacement. Validate the failure with a focused regression using controlled interleavings, preserve the exact account and session identity, and keep unrelated provider execution code unchanged.
Resolved in round 3 P1 #16 · Preserve disabled access when an active link is refreshed

Round 2 · infra/relay/src/environments/EnvironmentLinks.ts:296-309 · logic

The conflict branch now unconditionally writes enabled: enabledOnLink. That fixes the revoked-link capacity case by also replacing enablement on every ordinary refresh of an existing active link.

If an account has room under the cap, an explicitly disabled link becomes enabled as soon as upsert refreshes it. The negative regression begins with an active disabled record and confirms that the record becomes enabled without setEnabled being called.

The distinction between refreshing an active link and reviving a revoked link belongs in this operation. Otherwise the user’s access choice is temporary and normal linking traffic can undo it.

Fix direction. Preserve enabled on non-revoked existing links. Apply the account cap only to new links and deliberate revoked-link restoration, inside the same database-wide atomic account mutation used for enable/disable. Cover disabled refresh, enabled refresh at cap, revoked revival, and concurrent link operations.

New negative regression failed: expected enabled false after refresh; received true.

Changed source excerpt
        .onConflictDoUpdate({
          target: [relayEnvironmentLinks.userId, relayEnvironmentLinks.environmentId],
          set: {
            environmentPublicKey: proof.environmentPublicKey,
            environmentLabel: proof.descriptor.label,
            endpointHttpBaseUrl: endpoint.httpBaseUrl,
            endpointWsBaseUrl: endpoint.wsBaseUrl,
            endpointProviderKind: endpoint.providerKind,
            notificationsEnabled: request.notificationsEnabled,
            liveActivitiesEnabled: request.liveActivitiesEnabled,
            managedTunnelsEnabled: request.managedTunnelsEnabled,
            enabled: enabledOnLink,
            createdByDeviceId: request.deviceId ?? null,
            revokedAt: null,
Prompt for AI agents
f13
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

In `infra/relay/src/environments/EnvironmentLinks.ts` around lines 296-309, The conflict branch now unconditionally writes enabled: enabledOnLink. That fixes the revoked-link capacity case by also replacing enablement on every ordinary refresh of an existing active link. Preserve enabled on non-revoked existing links. Apply the account cap only to new links and deliberate revoked-link restoration, inside the same database-wide atomic account mutation used for enable/disable. Cover disabled refresh, enabled refresh at cap, revoked revival, and concurrent link operations. Validate the failure with a focused regression using controlled interleavings, preserve the exact account and session identity, and keep unrelated provider execution code unchanged.
Resolved in round 3 P1 #16 · Update the two-node integration relay stub for the new method

Round 2 · apps/server/integration/jarvisTwoNode.integration.test.ts:465-478 · syntax

The new required ManagedRelayClient.setEnvironmentEnabled method is absent from the relay service stub in the two-node integration test. The packages checked in the remediation summary do not include this server integration typecheck failure.

The latest Check job for ac0484b00 fails with TS2741 at line 466 for exactly this missing method. The Test job is now green, including the previously failing test run; the remaining red check is directly related to this PR’s contract change.

This is a small fixture update, but the pushed branch is not CI-clean.

Fix direction. Add setEnvironmentEnabled to the unavailable relay stub, matching the other unused methods. Run the targeted server typecheck and verify Check on the resulting commit. Do not edit unrelated mobile highlighting code.

GitHub CI run 34718133912, Check / Typecheck Jarvis runtime packages: missing required setEnvironmentEnabled at integration/jarvisTwoNode.integration.test.ts:466:52.

Changed source excerpt
  const unavailable = () => Effect.die("Relay is outside this proof.");
  const relay = ManagedRelay.ManagedRelayClient.of({
    relayUrl: "https://relay.invalid",
    listEnvironments: unavailable,
    listDevices: unavailable,
    createEnvironmentLinkChallenge: unavailable,
    linkEnvironment: unavailable,
    unlinkEnvironment: unavailable,
    getEnvironmentStatus: unavailable,
    connectEnvironment: unavailable,
    registerDevice: unavailable,
    unregisterDevice: unavailable,
    registerLiveActivity: unavailable,
    getAgentActivitySnapshot: unavailable,
Prompt for AI agents
f14
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

In `apps/server/integration/jarvisTwoNode.integration.test.ts` around lines 465-478, The new required ManagedRelayClient.setEnvironmentEnabled method is absent from the relay service stub in the two-node integration test. The packages checked in the remediation summary do not include this server integration typecheck failure. Add setEnvironmentEnabled to the unavailable relay stub, matching the other unused methods. Run the targeted server typecheck and verify Check on the resulting commit. Do not edit unrelated mobile highlighting code. Validate the failure with a focused regression using controlled interleavings, preserve the exact account and session identity, and keep unrelated provider execution code unchanged.

Round 1 comments and current status

Resolved in round 2 P1 #15 · Resolve the paying account independently of notification preferences

infra/relay/src/voice/LiveVoiceSessions.ts:96-106 · logic · correctness · actionable

Cloud voice uses listUsersForEnvironment to decide which account owns the session. That existing method intentionally selects only active links with notifications or Live Activities enabled. It is a delivery audience query, not an ownership query.

A singly linked account with both notification options off is rejected as unlinked. A shared environment can appear to have just one owner when only one of its accounts has notifications enabled, bypassing the explicit shared-environment rejection. Release uses the same lookup, so changing notification settings can also prevent cleanup.

The existing EnvironmentLinks test explicitly verifies this notification filter. Keep that notification behavior intact and give the Jarvis broker an account-link lookup with the semantics it actually needs.

Fix direction. Resolve active account ownership from all relevant non-revoked links, independent of delivery preferences, with explicit ambiguity and entitlement checks. Test zero, one, and multiple linked accounts with every notification combination.

Evidence: Source trace plus passing existing query-contract test; no real cloud call.

Changed source excerpt
  // Cloud voice is account-scoped, so the calling environment must resolve to
  // exactly one linked user. Shared environments are rejected rather than
  // guessing which account pays.
  const resolveUserId = (environmentId: string) =>
    links.listUsersForEnvironment({ environmentId }).pipe(
      Effect.mapError(persistence("list-users")),
      Effect.flatMap((userIds) =>
        userIds.length === 1
          ? Effect.succeed(userIds[0] as string)
          : Effect.fail(new LiveVoiceEnvironmentNotLinked({ environmentId })),
      ),
Prompt for AI agents
Finding f1
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

In `infra/relay/src/voice/LiveVoiceSessions.ts` around lines 96-106, resolve the paying account independently of notification preferences. Cloud voice uses listUsersForEnvironment to decide which account owns the session. That existing method intentionally selects only active links with notifications or Live Activities enabled. It is a delivery audience query, not an ownership query. Resolve active account ownership from all relevant non-revoked links, independent of delivery preferences, with explicit ambiguity and entitlement checks. Test zero, one, and multiple linked accounts with every notification combination. Validate the user-visible invariant across the original path, concurrent clients, and the relevant reverse state. Keep provider execution on the ordinary adapters; use Jarvis composition and existing public services. Preserve local work and add only the focused regression coverage required for this behavior.
Resolved in round 3 Partial · still open P1 #15 · Keep the account reservation until the upstream voice session has ended

infra/relay/src/voice/LiveVoiceSessions.ts:201-213 · logic · correctness · actionable

Release deletes the account reservation without ending or verifying the end of the upstream session. The browser talks to the upstream service through the negotiated WebRTC transport; deleting a relay row does not terminate that transport.

An authenticated node can create a session, release its reservation while keeping the connection open, and create another. The isolated negative test tracks two open upstream sessions after that sequence. Expiring the row after ten minutes has the same missing lifetime guarantee for a longer-lived connection.

The primary key correctly prevents two simultaneous reservations, but it does not enforce the advertised one-active-conversation policy or a spending ceiling. This becomes material when the relay uses an operator-owned key.

Fix direction. Tie slot release to a verified upstream termination or a server-enforced upstream lifetime. Handle creation timeout, finalization failure, expiry, disconnect, and retry without freeing a slot for a still-live session. Add a bounded account usage policy before exposing an operator-funded service.

Evidence: Negative regression failed: two fake upstream sessions remain open; expected at most one. No paid API requests were made.

Changed source excerpt
    release: Effect.fn("relay.live_voice.release")(function* (input) {
      const userId = yield* resolveUserId(input.environmentId);
      // Deleting by user and session id keeps release idempotent and prevents
      // one environment from clearing another account's session.
      yield* db
        .delete(relayLiveVoiceSessions)
        .where(
          and(
            eq(relayLiveVoiceSessions.userId, userId),
            eq(relayLiveVoiceSessions.sessionId, input.sessionId),
          ),
        )
        .pipe(Effect.mapError(persistence("release-session")), Effect.orDie);
Prompt for AI agents
Finding f2
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

In `infra/relay/src/voice/LiveVoiceSessions.ts` around lines 201-213, keep the account reservation until the upstream voice session has ended. Release deletes the account reservation without ending or verifying the end of the upstream session. The browser talks to the upstream service through the negotiated WebRTC transport; deleting a relay row does not terminate that transport. Tie slot release to a verified upstream termination or a server-enforced upstream lifetime. Handle creation timeout, finalization failure, expiry, disconnect, and retry without freeing a slot for a still-live session. Add a bounded account usage policy before exposing an operator-funded service. Validate the user-visible invariant across the original path, concurrent clients, and the relevant reverse state. Keep provider execution on the ordinary adapters; use Jarvis composition and existing public services. Preserve local work and add only the focused regression coverage required for this behavior.
Resolved in round 4 P1 #16 · Enforce the enabled cap as one account-wide invariant

infra/relay/src/environments/EnvironmentLinks.ts:511-527 · logic · data-integrity · actionable

The service reads enabled peers and then updates rows through separate statements. The API handler does not wrap the change in an account-serialized transaction. The client scheduler only serializes requests within one runtime.

With four enabled machines, two clients can read the same four peers and enable different machines. The negative test synchronizes those reads and ends with six enabled machines. New-link counting has the same check-then-write shape.

The other entry paths also escape the invariant: migration 20260912192834 marks every existing link enabled even though the prior link ceiling is ten, and upsert revives revoked links without applying enabledOnLink to the conflict branch. An account can exceed five without any concurrent request.

Fix direction. Apply cap decisions atomically across enable, new link, and revoked-link restoration, with serialization in the shared database. Define an explicit upgrade policy for accounts already over the cap. Test concurrent clients, rollback after eviction, migration from six-to-ten active links, and relinking a revoked enabled row.

Evidence: Negative concurrency regression failed: six enabled rows; expected five. Upgrade and restoration paths were traced in source.

Changed source excerpt
      // Enabling past the cap turns off the least-recently-used other device so
      // the account never exceeds the limit.
      const others = yield* db
        .select({
          environmentId: relayEnvironmentLinks.environmentId,
          updatedAt: relayEnvironmentLinks.updatedAt,
        })
        .from(relayEnvironmentLinks)
        .where(
          and(
            ownedCondition,
            eq(relayEnvironmentLinks.enabled, true),
            ne(relayEnvironmentLinks.environmentId, input.environmentId),
          ),
        )
        .orderBy(asc(relayEnvironmentLinks.updatedAt))
        .pipe(Effect.mapError(persistence));
Prompt for AI agents
Finding f3
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

In `infra/relay/src/environments/EnvironmentLinks.ts` around lines 511-527, enforce the enabled cap as one account-wide invariant. The service reads enabled peers and then updates rows through separate statements. The API handler does not wrap the change in an account-serialized transaction. The client scheduler only serializes requests within one runtime. Apply cap decisions atomically across enable, new link, and revoked-link restoration, with serialization in the shared database. Define an explicit upgrade policy for accounts already over the cap. Test concurrent clients, rollback after eviction, migration from six-to-ten active links, and relinking a revoked enabled row. Validate the user-visible invariant across the original path, concurrent clients, and the relevant reverse state. Keep provider execution on the ordinary adapters; use Jarvis composition and existing public services. Preserve local work and add only the focused regression coverage required for this behavior.
Resolved in round 2 P1 #16 · Validate the target link before evicting another machine

infra/relay/src/environments/EnvironmentLinks.ts:529-549 · logic · correctness · actionable

setEnabled selects and disables an existing peer before checking that the requested environment exists as an active link for this account. The final update can affect zero rows, yet the HTTP handler returns ok: true.

At the cap, a stale UI request for a link that another device just removed disables a legitimate machine without enabling anything. The isolated regression calls the method with a nonexistent link and leaves four enabled machines instead of preserving five.

This is also an ownership validation problem: a foreign environment ID must not produce side effects on the requesting account. Reordering the operations without an atomic account mutation would still leave a race.

Fix direction. Validate the target active link and perform the complete policy change atomically; return a typed not-found or unavailable outcome for stale or foreign targets. Ensure failed or zero-row mutations cannot evict another machine.

Evidence: Negative regression failed: four machines remain enabled after an unknown-target request; expected five.

Changed source excerpt
      let autoDisabledEnvironmentId: string | null = null;
      if (others.length >= DEFAULT_ENABLED_DEVICE_LIMIT) {
        const oldest = others[0];
        if (oldest) {
          yield* db
            .update(relayEnvironmentLinks)
            .set({ enabled: false, updatedAt: now })
            .where(
              and(ownedCondition, eq(relayEnvironmentLinks.environmentId, oldest.environmentId)),
            )
            .pipe(Effect.mapError(persistence));
          autoDisabledEnvironmentId = oldest.environmentId;
        }
      }

      yield* db
        .update(relayEnvironmentLinks)
        .set({ enabled: true, updatedAt: now })
        .where(and(ownedCondition, eq(relayEnvironmentLinks.environmentId, input.environmentId)))
        .pipe(Effect.mapError(persistence));
      return { autoDisabledEnvironmentId };
Prompt for AI agents
Finding f4
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

In `infra/relay/src/environments/EnvironmentLinks.ts` around lines 529-549, validate the target link before evicting another machine. setEnabled selects and disables an existing peer before checking that the requested environment exists as an active link for this account. The final update can affect zero rows, yet the HTTP handler returns ok: true. Validate the target active link and perform the complete policy change atomically; return a typed not-found or unavailable outcome for stale or foreign targets. Ensure failed or zero-row mutations cannot evict another machine. Validate the user-visible invariant across the original path, concurrent clients, and the relevant reverse state. Keep provider execution on the ordinary adapters; use Jarvis composition and existing public services. Preserve local work and add only the focused regression coverage required for this behavior.
Resolved in round 2 P1 #16 · Apply disabled-account policy to cloud voice creation

infra/relay/src/environments/EnvironmentConnector.ts:578-583 · logic · correctness · actionable

The new enabled check protects only managed connect and status. The stacked cloud-voice endpoint authenticates the environment credential and uses listUsersForEnvironment, which never checks enabled. Disabling a link leaves both that credential and the voice ownership lookup usable.

A disabled environment whose notification link is still active can continue asking the #15 broker to create operator-funded sessions. This contradicts a device access control positioned as part of the account cost policy.

The broker needs the account entitlement at the point of session creation. A connection failure in one client does not authorize or prevent an independent authenticated environment request.

Fix direction. Check the active account-link enablement and voice entitlement within the Jarvis broker authorization path, using the same account identity contract as the cap. Test disabled links, another enabled link on a shared node, revocation during creation, and re-enable. Do not globally revoke a node credential that serves other accounts.

Evidence: Source trace through RelayEnvironmentAuth, LiveVoiceSessions.resolveUserId, and EnvironmentLinks; focused connect/status tests passed. This bypass was not exercised against a deployed relay.

Changed source excerpt
      if (link.enabled === false) {
        return yield* new EnvironmentConnectNotAuthorized({
          environmentId: input.environmentId,
          operation: "connect",
          reason: "environment_disabled",
        });
Prompt for AI agents
Finding f5
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

In `infra/relay/src/environments/EnvironmentConnector.ts` around lines 578-583, apply disabled-account policy to cloud voice creation. The new enabled check protects only managed connect and status. The stacked cloud-voice endpoint authenticates the environment credential and uses listUsersForEnvironment, which never checks enabled. Disabling a link leaves both that credential and the voice ownership lookup usable. Check the active account-link enablement and voice entitlement within the Jarvis broker authorization path, using the same account identity contract as the cap. Test disabled links, another enabled link on a shared node, revocation during creation, and re-enable. Do not globally revoke a node credential that serves other accounts. Validate the user-visible invariant across the original path, concurrent clients, and the relevant reverse state. Keep provider execution on the ordinary adapters; use Jarvis composition and existing public services. Preserve local work and add only the focused regression coverage required for this behavior.
Resolved in round 2 P2 #16 · Do not treat link-update time as last use

infra/relay/src/environments/EnvironmentLinks.ts:513-526 · logic · correctness · actionable

The eviction query sorts by updatedAt, but connecting to or using a machine does not update that field. The value tracks linking and enable-state edits rather than recent use.

A long-linked machine running daily work can be selected for eviction ahead of a recently linked idle machine. The service comment, PR policy, and UI description all promise least-recently-used behavior.

For a product control, I would prefer an explicit replacement choice. It removes the need to infer importance from activity and lets the user see the consequence before access changes.

Fix direction. Either implement a bounded, reliable usage signal for a documented LRU policy or change the policy to an explicit replacement chosen by the user. Return the affected environment IDs through the mutation response instead of discarding autoDisabledEnvironmentId.

Evidence: Source trace: EnvironmentConnector never updates relayEnvironmentLinks.updatedAt; writes occur on link, revoke, and enable changes.

Changed source excerpt
      const others = yield* db
        .select({
          environmentId: relayEnvironmentLinks.environmentId,
          updatedAt: relayEnvironmentLinks.updatedAt,
        })
        .from(relayEnvironmentLinks)
        .where(
          and(
            ownedCondition,
            eq(relayEnvironmentLinks.enabled, true),
            ne(relayEnvironmentLinks.environmentId, input.environmentId),
          ),
        )
        .orderBy(asc(relayEnvironmentLinks.updatedAt))
Prompt for AI agents
Finding f6
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

In `infra/relay/src/environments/EnvironmentLinks.ts` around lines 513-526, do not treat link-update time as last use. The eviction query sorts by updatedAt, but connecting to or using a machine does not update that field. The value tracks linking and enable-state edits rather than recent use. Either implement a bounded, reliable usage signal for a documented LRU policy or change the policy to an explicit replacement chosen by the user. Return the affected environment IDs through the mutation response instead of discarding autoDisabledEnvironmentId. Validate the user-visible invariant across the original path, concurrent clients, and the relevant reverse state. Keep provider execution on the ordinary adapters; use Jarvis composition and existing public services. Preserve local work and add only the focused regression coverage required for this behavior.
Resolved in round 2 P2 #16 · Show failures from the enable/disable mutation

apps/web/src/components/jarvis/JarvisMeshDevices.tsx:32-38 · logic · stability · actionable

The command hook suppresses automatic failure reporting, and handleToggle only handles Success. The error rendered at the bottom comes from the environment list query, not from the enable/disable mutation.

If the POST fails because the relay is unavailable, the token expired, or an older relay does not implement this endpoint, the busy state clears and the switch remains unchanged without an explanation. A successful list query does not clear up what happened to the attempted action.

This is particularly confusing during a staged client/relay rollout, where optional enabled fields otherwise imply compatibility with an older relay.

Fix direction. Render the typed mutation failure beside the affected row and retain a retry action. Distinguish pending, confirmed, and failed state. Disable unsupported controls when the relay does not advertise enablement support, and expose the server limit rather than hardcoding it.

Evidence: Source trace; no new browser or component failure simulation was run.

Changed source excerpt
  const handleToggle = async (environmentId: EnvironmentId, enabled: boolean) => {
    if (pendingId !== null) return;
    setPendingId(environmentId);
    const result = await setEnabled({ accountId, environmentId, enabled });
    setPendingId(null);
    if (result._tag === "Success") environments.refresh();
  };
Prompt for AI agents
Finding f7
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

In `apps/web/src/components/jarvis/JarvisMeshDevices.tsx` around lines 32-38, show failures from the enable/disable mutation. The command hook suppresses automatic failure reporting, and handleToggle only handles Success. The error rendered at the bottom comes from the environment list query, not from the enable/disable mutation. Render the typed mutation failure beside the affected row and retain a retry action. Distinguish pending, confirmed, and failed state. Disable unsupported controls when the relay does not advertise enablement support, and expose the server limit rather than hardcoding it. Validate the user-visible invariant across the original path, concurrent clients, and the relevant reverse state. Keep provider execution on the ordinary adapters; use Jarvis composition and existing public services. Preserve local work and add only the focused regression coverage required for this behavior.
P2 #16 · Replace the native title tooltip required by the repository lint rule

apps/web/src/components/jarvis/JarvisMeshDevices.tsx:61-64 · style · maintainability · nitpick

The added name span uses the native title attribute. The repository requires Tooltip, TooltipTrigger, and TooltipPopup for this interaction.

The Check job fails on this exact line in the pushed commit. This is a small implementation correction, but the branch cannot be treated as green until it is fixed.

Fix direction. Use the existing tooltip components for the truncated name and run the targeted Jarvis lint check. Keep this correction separate from the account-policy redesign.

Evidence: GitHub CI Check failure at JarvisMeshDevices.tsx:62:61.

Changed source excerpt
              <div className="jarvis-device-access-row" key={environment.environmentId}>
                <span className="jarvis-device-access-name" title={environment.label}>
                  {environment.label}
                </span>
Prompt for AI agents
Finding f8
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

In `apps/web/src/components/jarvis/JarvisMeshDevices.tsx` around lines 61-64, replace the native title tooltip required by the repository lint rule. The added name span uses the native title attribute. The repository requires Tooltip, TooltipTrigger, and TooltipPopup for this interaction. Use the existing tooltip components for the truncated name and run the targeted Jarvis lint check. Keep this correction separate from the account-policy redesign. Validate the user-visible invariant across the original path, concurrent clients, and the relevant reverse state. Keep provider execution on the ordinary adapters; use Jarvis composition and existing public services. Preserve local work and add only the focused regression coverage required for this behavior.
Info #15 · Cloud voice is a broker increment, not a shipped no-key user flow

infra/relay/src/voice/LiveVoiceSessions.ts:116-129 · info · maintainability · additional

The PR clearly states that node fallback and client capability gating are follow-ups. The existing JarvisLiveVoice service still uses its node settings, and the new relay endpoints do not have a product caller in this PR.

This is acceptable scope for an internal increment once the broker defects are fixed. It does not yet make the voice button work for a new user without a key. Surface entitlement and busy-session ownership consistently in web, desktop, and the separate mobile client before advertising that behavior.

Fix direction. Complete one end-to-end no-key path through node composition and shared client readiness; preserve explicit BYOK behavior and preset restrictions.

Evidence: PR description and server/client call-site search.

Changed source excerpt
    create: Effect.fn("relay.live_voice.create")(function* (input) {
      const liveVoice = configuration.liveVoice;
      const publicKey = liveVoice?.apiKey ?? null;
      if (publicKey === null) {
        return yield* new LiveVoiceNotConfigured();
      }
      const model = liveVoice?.model ?? JARVIS_LIVE_VOICE_DEFAULT_MODEL;
      const voice = liveVoice?.voice ?? JARVIS_LIVE_VOICE_DEFAULT_VOICE;
      const userId = yield* resolveUserId(input.environmentId);
      const now = yield* DateTime.now;
      const nowIso = DateTime.formatIso(now);
      const expiresAt = DateTime.formatIso(
        DateTime.add(now, { milliseconds: LIVE_VOICE_SESSION_TTL_MILLIS }),
      );
Prompt for AI agents
Finding f9
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

In `infra/relay/src/voice/LiveVoiceSessions.ts` around lines 116-129, cloud voice is a broker increment, not a shipped no-key user flow. The PR clearly states that node fallback and client capability gating are follow-ups. The existing JarvisLiveVoice service still uses its node settings, and the new relay endpoints do not have a product caller in this PR. Complete one end-to-end no-key path through node composition and shared client readiness; preserve explicit BYOK behavior and preset restrictions. Validate the user-visible invariant across the original path, concurrent clients, and the relevant reverse state. Keep provider execution on the ordinary adapters; use Jarvis composition and existing public services. Preserve local work and add only the focused regression coverage required for this behavior.
Info #16 · Account link enablement is not complete session revocation

infra/relay/src/environments/EnvironmentConnector.ts:578-583 · info · security · additional

The new flag is consulted at managed connection bootstrap and status. This PR does not invalidate already-issued node authorization or existing sockets, and direct, LAN, Tailscale, and SSH paths do not consult this relay flag.

Decide what the product promises: permission to start a managed relay connection, or revocation of this account’s access to a node. The wireframe keeps connectivity, execution capability, account access, and signed-in clients distinct so a single toggle does not imply all of them.

If complete session revocation requires a missing public T3 authorization seam, identify that constraint and get explicit approval before changing inherited authentication implementations. Do not build a parallel Jarvis authorization engine.

Fix direction. Document the exact access contract and the effect on existing sessions. Reuse existing public authorization and revocation seams; treat any missing upstream seam as a separate decision.

Evidence: Review of the changed enforcement points and their public contracts; no live session-revocation experiment.

Changed source excerpt
      if (link.enabled === false) {
        return yield* new EnvironmentConnectNotAuthorized({
          environmentId: input.environmentId,
          operation: "connect",
          reason: "environment_disabled",
        });
Prompt for AI agents
Finding f10
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

In `infra/relay/src/environments/EnvironmentConnector.ts` around lines 578-583, account link enablement is not complete session revocation. The new flag is consulted at managed connection bootstrap and status. This PR does not invalidate already-issued node authorization or existing sockets, and direct, LAN, Tailscale, and SSH paths do not consult this relay flag. Document the exact access contract and the effect on existing sessions. Reuse existing public authorization and revocation seams; treat any missing upstream seam as a separate decision. Validate the user-visible invariant across the original path, concurrent clients, and the relevant reverse state. Keep provider execution on the ordinary adapters; use Jarvis composition and existing public services. Preserve local work and add only the focused regression coverage required for this behavior.
Info #16 · Keep policy metadata in the typed response consumed by the UI

packages/contracts/src/relay.ts:766-771 · info · maintainability · duplicate

The list response adds enabledLimit, but ManagedRelayClient.listEnvironments immediately projects only response.environments. JarvisMeshDevices then repeats the number five in the component.

The default currently matches, so this is not a present-day cap mismatch. It does create two owners for policy presentation and makes an operator change or capability rollout harder than it needs to be.

Fix direction. Preserve limit and capability metadata in the client view contract and return auto-disabled identities in the mutation result; let the UI derive its count and controls from those values.

Evidence: Source trace through contracts, ManagedRelayClient.listEnvironments, and JarvisMeshDevices.

Changed source excerpt
export const RelayListEnvironmentsResponse = Schema.Struct({
  environments: Schema.Array(RelayClientEnvironmentRecord),
  /** Maximum enabled devices per account. Older relays omit it; default to 5. */
  enabledLimit: Schema.Number.pipe(Schema.withDecodingDefault(Effect.succeed(5))),
});
export type RelayListEnvironmentsResponse = typeof RelayListEnvironmentsResponse.Type;
Prompt for AI agents
Finding f11
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/contracts/src/relay.ts` around lines 766-771, keep policy metadata in the typed response consumed by the UI. The list response adds enabledLimit, but ManagedRelayClient.listEnvironments immediately projects only response.environments. JarvisMeshDevices then repeats the number five in the component. Preserve limit and capability metadata in the client view contract and return auto-disabled identities in the mutation result; let the UI derive its count and controls from those values. Validate the user-visible invariant across the original path, concurrent clients, and the relevant reverse state. Keep provider execution on the ordinary adapters; use Jarvis composition and existing public services. Preserve local work and add only the focused regression coverage required for this behavior.

What has to be true before a product release

The PRs improve the available building blocks. Neither one establishes the complete new-user journey or a safe operator-funded service.

  1. Finish the account contract. Define account, execution node, signed-in client, connection route, enabled access, and cloud entitlement separately. Make the cap and affected identities authoritative on the server.
  2. Finish the voice lifecycle. Establish reliable upstream termination, account ownership, usage bounds, failure cleanup, and session owner visibility. Then wire cloud fallback and explicit BYOK behavior through node composition and every affected client.
  3. Make access claims honest. Decide the effect on existing sessions and direct routes. If the existing public T3 seams cannot provide it, make that a separate upstream-seam decision rather than slipping Jarvis policy into generic auth internals.
  4. Ship the first-run path. Verify install → choose Full/Controller/Headless → sign in or pair → provider readiness on the execution node → first task → voice readiness where supported → restart/update → disconnect/reconnect.
  5. Validate actual release artifacts. Check clean machines for each distributed OS/preset, signing/update origins, failed upgrades, data preservation, and required real-device voice behavior. Green unit tests and Release Smoke do not replace these checks.

A five-environment switch is not a cloud spending budget. Keep connected-machine limits separate from voice entitlement and metered use. This review does not endorse the PR’s external infrastructure-cost claims.

Release configuration

#14 is a useful, small fix. Its configurable hosts still fall back to upstream T3 domains. For a Jarvis product deployment, require deliberate operator values and verify generated routing and aliases before release. This is distribution guidance, not an introduced regression in #14.

Merge sequence

#14 can proceed within its scope. Fix and recheck #15, merge it first, then retarget #16 to main and validate the resulting diff. Complete the node/client cloud path and product acceptance separately before advertising no-key cloud voice or account-wide revocation.

Command center direction

The current page combines node tabs, a global command target, recent/running task views, and a side rail of device details, providers, projects, agent settings, and now account toggles. Those controls have different scopes. Making that scope visible matters more than adding another card.

Jarvis command center wireframe with tasks needing attention, running work, a qualified command target, and separate machine and voice status
Rendered concept preview. Every task and status shown is sample data.
Open interactive wireframe

The prototype has four sections and six scenarios. Try phone layout, an ambiguous command, an unavailable target, and enabling the sixth machine. Task detail inspection deliberately leaves the command target unchanged.

Production should derive this view from typed task events and the existing command runtime, keeping lists bounded and details lazy. The same states need a React Native presentation on mobile. This is a proposed design, not a change to the Jarvis app.

Proposed ownership flow
Browser / desktop / mobile → typed Jarvis command state → deterministic Director → node-qualified task → ordinary provider adapter

Voice request → relay environment authentication → account entitlement + enabled link → bounded upstream voice lifetime

Machine access change → authenticated account → atomic policy update → affected identities + typed result → refreshed UI

Scope and limits

This was a review and wireframe task. No product code was fixed, no PR comments were posted, and no PR was merged. The working checkout and live Jarvis state were not used for test writes.

Live cloud-session termination, database transaction isolation, signed installer behavior, device permissions, and native client layout remain unverified. The three regressions use controlled test doubles; other findings are labeled with their source evidence.

Latest reconciliation is in the Round 2 section above.
Local sidecar: /home/abstergo/Documents/reviews/Jarvis-pr15-pr16-distribution-abbyview.json
Local report: /home/abstergo/Documents/reviews/Jarvis-pr15-pr16-distribution-abbyview.html
Regression patch and test output are saved alongside those files.