Skip to content

refactor(core): deepen Caplets architecture seams#205

Merged
ian-pascoe merged 9 commits into
mainfrom
worktree/green-harbor-e0e3
Jul 11, 2026
Merged

refactor(core): deepen Caplets architecture seams#205
ian-pascoe merged 9 commits into
mainfrom
worktree/green-harbor-e0e3

Conversation

@ian-pascoe

@ian-pascoe ian-pascoe commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Caplets now derives cross-host behavior from seven authoritative modules instead of letting each adapter reconstruct policy, callable identity, result locality, or lifecycle ordering. The refactor preserves the established CLI, MCP, Attach, dashboard, native, and public-site experiences while making failures generation-safe, authorization-safe, privacy-safe, and retryable.

Seam What is now guaranteed Deliberate adapter boundary
Media results Every producer returns inline, local-artifact, or remote-reference under shared byte, MIME, integrity, path-visibility, and transactional-write policy Hosts retain presentation and link affordances
Backend operations Seven backend kinds share one typed dispatch for check, discovery, calls, compacting, and search MCP resources, prompts, templates, and completions remain explicitly MCP-only
Exposure projection Registration facts and Code Mode identities come from one generation-bound projection; stale and out-of-order refreshes fail closed MCP, Attach, native, CLI, and Doctor retain registration and presentation mechanics
Vault recovery Quarantine emits structured facts once, independent of warning prose Setup and Doctor render their own stable recovery output
Current Host administration Dashboard sessions and Operator bearers execute the same semantic operations, activity, redaction, and sessionEnded policy Cookies, CSRF, HTTP serialization, and Raw Vault Reveal ceremony remain dashboard-owned
Browser telemetry PostHog's final augmented payload is allowlisted and capture/init failures cannot break site behavior Each site keeps local SDK initialization and categorical event authoring
Native Project Binding Start, accepted-ID updates, replacement, close, deadlines, and cleanup retry ordering share one lifecycle owner Cloud and self-hosted adapters retain their distinct heartbeat and recovery policies

Design decisions

  • Media acquisition remains bounded and streaming. GraphQL error inspection happens before representation selection, artifact publication rolls back transactionally, and remote boundaries cannot regain managed filesystem paths.
  • Exposure callbacks bind to the engine generation that produced them. Initial discovery, failed refresh, same-ID reload, and newest-first overlapping refreshes cannot execute through stale identities.
  • Remote Client roles remain exact rather than hierarchical. Current Host administration requires Operator authority; MCP, Attach, and Project Binding require Access authority; credential self-revoke remains owner-only and role-neutral.
  • Raw Vault Reveal remains an explicit human ceremony: exact confirmation, Cache-Control: no-store, ephemeral in-memory display, cancellation of stale responses, and no persistence across expiry, navigation, or refresh.
  • PostHog keeps only its public routing token, anonymous distinct_id, approved categorical properties, and required privacy controls. The unified production/preview PostHog project was re-read with IP capture configured to Discard on 2026-07-10.
  • Project Binding mutations are serialized and bounded by abortable deadlines. Replacement commits only after old-owner cleanup and newest accepted-ID reconciliation; ambiguous DELETE completion converges through idempotent terminal 404 handling.

Validation

  • pnpm verify — formatting, lint, generated Code Mode API, config schema, public docs, all package typechecks, 158 test files / 2,237 tests, deterministic benchmark freshness, all package builds, and runtime package checks passed.
  • Focused Core verification — 128 files / 1,957 tests passed, including Media streaming/rollback, projection generation races, Current Host authorization, Project Binding queueing, WebSocket authority revalidation, deadline aborts, replacement retry, and workspace cleanup.
  • Focused browser-observability verification — shared provider contract (22 tests) and landing/docs/catalog adapters (23 tests) passed.
  • Live dashboard QA — created a disposable Vault value, confirmed the raw value was hidden before reveal, completed the exact confirmation ceremony, observed a 200 response with Cache-Control: no-store, verified automatic removal after 30 seconds, and verified no value persisted or flashed across Activity navigation or a full refresh.
  • The branch contains exactly seven ordered, behavior-complete commits matching the approved U1–U7 delivery sequence and includes the required @caplets/core Changeset.

Compound Engineering
GPT 5.6 sol

Summary by CodeRabbit

  • New Features
    • Unified media result handling across inline, local artifacts, and remote references, improving rendering in Pi/Attach and tool outputs.
    • Added completion request context support and improved discovery/capability detection.
    • Introduced Current Host administration for catalog, clients, and Vault operations.
    • Added native Project Binding lifecycle management with safer updates and reconnection behavior.
  • Bug Fixes
    • Eliminated stale/overlapping vault “reveal” and session confirmations from updating UI after invalidation.
    • Hardened analytics sanitization so telemetry setup/capture failures don’t break page interactions.
    • Improved artifact streaming, inline thresholds, and sensitive-data redaction.
  • Documentation
    • Updated privacy, troubleshooting, API, and architecture docs to reflect the new behaviors.

Unify HTTP-like inline, local-artifact, and remote-reference results across progressive, direct, native, and hosted adapters. Preserve mixed MCP content, enforce GraphQL artifact bounds, and keep filesystem paths behind explicit host capability.
Construct one named runtime over the seven backend managers, route progressive, direct, completion-discovery, and nested calls through its six-operation interface, and retain MCP-only resource, prompt, and completion behavior on an explicit capability.
Make one generation-bound projection authoritative for MCP, Attach, Native, and Code Mode registration. Preserve contextual completion and fail closed on stale or malformed partial discovery state.
Keep recovery facts at the config seam so setup and doctor no longer parse warning prose. Preserve safe warning presentation while distinguishing recoverable quarantine from loader failure.
Route dashboard and Operator bearer administration through one typed operations boundary with exact role enforcement, shared redaction, and actor-attributed outcomes. Keep Raw Vault Reveal dashboard-only with no-store responses and ephemeral browser state.
Reconstruct approved anonymous event envelopes at PostHog before-send time, preserve transport identity without person profiles, and isolate provider failures across all public sites. Record verified provider-side IP discard separately from event-level GeoIP suppression.
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR unifies media results, backend dispatch, exposure projection, Current Host administration, telemetry sanitization, Vault quarantine reporting, Project Binding lifecycle handling, and dashboard reveal invalidation. It also adds extensive coverage for streaming artifacts, stale projections, authorization, races, and observability failures.

Changes

Architecture and runtime refactor

Layer / File(s) Summary
Media results and backend dispatch
packages/core/src/media/*, packages/core/src/http/response.ts, packages/core/src/graphql.ts, packages/core/src/tools.ts
HTTP-like results distinguish inline, local-artifact, and remote-reference variants; backend calls use a named runtime and streamed artifact publication.
Exposure projection and serving
packages/core/src/exposure/*, packages/core/src/native/*, packages/core/src/serve/session.ts, packages/core/src/attach/api.ts
Projection entries drive registrations, manifests, Code Mode identities, completions, routing metadata, and generation checks.
Current Host and Vault administration
packages/core/src/current-host/*, packages/core/src/config.ts, packages/core/src/serve/http.ts, packages/core/src/remote-control/*
Typed administration operations centralize catalog, client, vault, and runtime actions with principal validation, redaction, and structured quarantine outcomes.
Project Binding lifecycle
packages/core/src/native/project-binding-lifecycle.ts, packages/core/src/cloud/*, packages/core/src/project-binding/*
Binding mutations, heartbeats, replacement, termination, lease persistence, and cleanup are serialized and guarded against stale work.
Telemetry and dashboard safety
packages/web-observability/*, apps/catalog/*, apps/docs/*, apps/landing/*, apps/dashboard/*
PostHog capture is sanitized and failure-isolated, while Vault reveal requests are invalidated on dismissal, navigation, unmount, and supersession.

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

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant CapletsMcpSession
  participant CapletsEngine
  participant BackendOperationRuntime
  participant MediaArtifactWriter
  Client->>CapletsMcpSession: invoke projected tool
  CapletsMcpSession->>CapletsEngine: execute generation-bound route
  CapletsEngine->>BackendOperationRuntime: callTool(server, request)
  BackendOperationRuntime->>MediaArtifactWriter: stream oversized response
  MediaArtifactWriter-->>BackendOperationRuntime: local-artifact or remote-reference
  BackendOperationRuntime-->>CapletsEngine: structured result
  CapletsEngine-->>CapletsMcpSession: annotated result
  CapletsMcpSession-->>Client: content and artifact metadata
Loading

Poem

A rabbit saw artifacts hop,
From local paths to remote drop.
Stale reveals fade, tools align,
Safe events pass the boundary line.
Bindings queue and leases rest. 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.38% 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 accurately reflects the core architecture refactor and broad cross-module seam changes in this pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree/green-harbor-e0e3

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.

@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

@greptile-apps

greptile-apps Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR refactors the Caplets platform into seven authoritative shared modules ("seams") that centralize media result handling, backend operation dispatch, exposure projection, vault recovery, current host administration, browser telemetry sanitization, and native project binding lifecycle. All existing adapters (CLI, MCP, Attach, dashboard, native, public site) are wired through these seams without changing their external interfaces.

  • Media artifacts gain async streaming writes with transactional rename-based publication, a file-system publication lock for cross-process serialization, and rollback on failure.
  • Exposure projection adds generation-bound callbacks so stale and out-of-order refreshes fail closed, and NativeProjectBindingLifecycle serializes start/update/replace/close mutations with abortable deadlines.
  • Dashboard vault reveal introduces revealGeneration/isMounted staleness guards and ACTION_DISCARDED sentinel so in-flight HTTP responses arriving after navigation or component unmount never populate the UI.

Confidence Score: 5/5

The PR is safe to merge. All seven new seams are well-structured, the authorization model is consistently enforced, and the behavioral guarantees described in the PR description are evident in the code.

The refactoring is large but mechanically correct. Each new module has a clear responsibility and matching tests. The only findings are two residual synchronous FS calls inside the otherwise-async media publication path — a style inconsistency rather than a functional defect. No logic errors, authorization gaps, or data-correctness issues were found across the seven seams.

packages/core/src/media/artifacts.ts has two minor residual lstatSync/existsSync calls inside the async publication path; no other files require special attention.

Important Files Changed

Filename Overview
packages/core/src/media/artifacts.ts Major rewrite: converts sync FS to async streams with a transactional publication flow, in-process and file-system locking, and backup/rollback. Mixes a few remaining lstatSync/existsSync calls with the otherwise-async surface.
packages/core/src/media/results.ts New file defining the inline/local-artifact/remote-reference MediaResult union type and JSON-Schema helper. Clean, no issues.
packages/core/src/exposure/projection.ts Major expansion adding manifest-based projection builders and the NativeProjectionMerge helpers. Logic is well-structured and the shadowing/suppression rules are consistently applied.
packages/core/src/native/project-binding-lifecycle.ts New NativeProjectBindingLifecycle class with correct serialization of start/update/replace/close. State machine handles the closing-during-replacement and cleanup-failed cases.
packages/core/src/current-host/vault-operations.ts Extracts vault CRUD and access-grant logic from the dashboard adapter. No new issues beyond the previously-flagged rollback edge case.
packages/core/src/current-host/operations.ts Central dispatch for all Current Host operations. assertOperatorPrincipal enforces role + client-ID format before any operation; error messages are comprehensively redacted.
packages/core/src/backend-operation-dispatch.ts New typed dispatch table for all seven backend kinds. Clean adapter-table pattern; MCP-only operations are kept separate in McpOperationAdapter.
apps/dashboard/src/components/DashboardApp.tsx Vault reveal hardened with generation counters, isMounted guard, ACTION_DISCARDED sentinel, and ephemeral expiry timer. Confirmation stale-response fix via confirmationRef is correct.
apps/dashboard/src/lib/ephemeral-reveal.ts New small module: EphemeralRevealExpiry with replace/cancel. Clean and well-tested.
packages/web-observability/src/posthog.ts New centralized PostHog sanitization. sanitizePostHogCapture validates UUID, token, distinct_id, and privacy flags; categoricalProperties drops unknown keys via assertWebEventSafeProperties. Failures are silently swallowed as designed.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant C as Caller
    participant W as ArtifactWriter
    participant L as PublicationLock
    participant FS as FileSystem

    C->>W: createMediaArtifactWriter(input)
    W->>FS: mkdir(dir)
    W->>FS: open(tempPath, wx)
    W-->>C: writer

    C->>W: write(chunk)
    W->>FS: file.write(chunk)
    W->>W: hash.update + byteLength++

    C->>W: complete()
    W->>FS: file.close()
    W->>L: serializeArtifactPublication(target)
    L->>FS: mkdir(lockDir) exclusive
    L->>FS: writeFile(owner.json)
    L->>W: run operation()
    W->>FS: recoverOrScavengeArtifactBackups()
    W->>FS: chmod(tempPath)
    W->>FS: rename target to backupPath if exists
    W->>FS: rename tempPath to target
    W->>W: "completed = true"
    W->>FS: removeArtifactBackups(backupPath)
    L->>FS: rm(lockDir)
    W-->>C: MediaArtifact

    alt Error during publish
        W->>FS: restorePreviousArtifacts rollback
        W-->>C: throw error
    end

    alt Caller aborts
        C->>W: abort()
        W->>FS: closeFile + removeStaging
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant C as Caller
    participant W as ArtifactWriter
    participant L as PublicationLock
    participant FS as FileSystem

    C->>W: createMediaArtifactWriter(input)
    W->>FS: mkdir(dir)
    W->>FS: open(tempPath, wx)
    W-->>C: writer

    C->>W: write(chunk)
    W->>FS: file.write(chunk)
    W->>W: hash.update + byteLength++

    C->>W: complete()
    W->>FS: file.close()
    W->>L: serializeArtifactPublication(target)
    L->>FS: mkdir(lockDir) exclusive
    L->>FS: writeFile(owner.json)
    L->>W: run operation()
    W->>FS: recoverOrScavengeArtifactBackups()
    W->>FS: chmod(tempPath)
    W->>FS: rename target to backupPath if exists
    W->>FS: rename tempPath to target
    W->>W: "completed = true"
    W->>FS: removeArtifactBackups(backupPath)
    L->>FS: rm(lockDir)
    W-->>C: MediaArtifact

    alt Error during publish
        W->>FS: restorePreviousArtifacts rollback
        W-->>C: throw error
    end

    alt Caller aborts
        C->>W: abort()
        W->>FS: closeFile + removeStaging
    end
Loading

Reviews (4): Last reviewed commit: "fix(core): harden artifact and reveal re..." | Re-trigger Greptile

Comment thread packages/core/src/current-host/vault-operations.ts
Comment thread packages/web-observability/src/posthog.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (10)
packages/core/test/mcp-test-client.ts (1)

20-42: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

close() doesn't propagate to the peer transport.

close() only invokes this.onclose, never this.peer?.onclose. Any test relying on the server-side session observing disconnect (e.g., session cleanup, resource release assertions) won't see it fire when the client side closes, and vice versa.

🔧 Proposed fix
   async close(): Promise<void> {
+    const peer = this.peer;
+    this.peer = undefined;
+    if (peer) {
+      peer.peer = undefined;
+      peer.onclose?.();
+    }
     this.onclose?.();
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/test/mcp-test-client.ts` around lines 20 - 42,
LinkedTransport.close() only notifies the local transport, so peer disconnect
handlers never run. Update close() to invoke both this.onclose and
this.peer?.onclose, preserving safe handling when no peer exists and preventing
duplicate notifications if both sides close.
packages/core/src/google-discovery/manager.ts (1)

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

Inline-threshold fallback omitted here vs. explicit DEFAULT_MAX_RESPONSE_BYTES in http-actions.ts.

When mediaInlineThresholdBytes is unset, this manager omits maxInlineBytes entirely (relying on readHttpLikeResponse's internal default), whereas http-actions.ts explicitly falls back to DEFAULT_MAX_RESPONSE_BYTES. If those two defaults ever diverge, inline/artifact behavior will differ across backends for the same underlying response-parsing utility. Consider aligning both managers on the same explicit fallback.

Also applies to: 215-217

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

In `@packages/core/src/google-discovery/manager.ts` around lines 142 - 144, Align
the fallback handling in the manager’s response options with http-actions.ts:
update the option construction near both mediaInlineThresholdBytes usages to
always set maxInlineBytes, using this.options.mediaInlineThresholdBytes when
provided and DEFAULT_MAX_RESPONSE_BYTES otherwise. Import or reference the
shared constant consistently so both backends use the same explicit default.
packages/core/src/current-host/vault-operations.ts (1)

245-265: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Overlay config is reloaded from disk on every grant/set-with-grant call.

vaultAccessOrigin calls loadLocalOverlayConfigWithSources synchronously per operation. Given this runs on the request path for Current Host administration, consider caching the overlay per request/session if this proves to be a hot path under load.

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

In `@packages/core/src/current-host/vault-operations.ts` around lines 245 - 265,
Avoid reloading overlay configuration from disk on every Vault grant or
set-with-grant request. Refactor vaultAccessOrigin and its callers to reuse a
per-request or session-scoped overlay, while preserving control-context,
missing-caplet, and shadow validation; invalidate or refresh the cached overlay
when configuration changes.
apps/dashboard/src/components/DashboardApp.test.tsx (1)

205-237: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Missing assertion for the "current" (non-stale) reveal outcome.

After current.resolve(...) (Lines 233-236) there is no assertion. The test proves the stale response is ignored, but never proves the newer response is actually applied (e.g. toast.success called / vault refreshed). Without this, a bug that silently discards all reveal responses (not just stale ones) would still pass.

✅ Suggested addition
     await act(async () => {
       current.resolve({ value: "current secret" });
     });
     await flush();
+
+    expect(dashboardApi).toHaveBeenCalled();
+    expect(toast.success).toHaveBeenCalled();
   });
🤖 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 `@apps/dashboard/src/components/DashboardApp.test.tsx` around lines 205 - 237,
Add assertions after resolving current in “does not refresh or toast when a
newer reveal invalidates an older response” to verify the non-stale result is
applied, including the expected success toast and vault refresh/API call with
“current secret” as appropriate. Keep the existing stale-response assertions
unchanged.
packages/core/test/runtime.test.ts (1)

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

Remove mock-shape assertions.

These assertions only prove that mockServer() lacks registration methods. Lines 422-425 already validate the meaningful runtime behavior: failed refresh removes tools and reports the unsupported registration failure.

Proposed fix
-    expect(server).not.toHaveProperty("registerResource");
-    expect(server).not.toHaveProperty("registerPrompt");
     expect(runtime.registeredToolIds()).toEqual([]);

As per coding guidelines, avoid tests that “merely restate implementation literals” when stronger behavior coverage exists.

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

In `@packages/core/test/runtime.test.ts` around lines 420 - 421, Remove the
mock-shape assertions checking that `server` lacks `registerResource` and
`registerPrompt`; retain the existing runtime behavior assertions that verify
failed refresh removes tools and reports the unsupported registration failure.

Source: Coding guidelines

apps/dashboard/src/components/DashboardApp.tsx (1)

2382-2384: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Prefer useRef for the expiry controller. createEphemeralRevealExpiry(...) returns mutable timer state, so useMemo is the wrong storage mechanism here; React may discard memoized values. A ref or lazy useState initializer gives this controller the stable identity it needs.

🤖 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 `@apps/dashboard/src/components/DashboardApp.tsx` around lines 2382 - 2384,
Replace the useMemo-based expiry controller in DashboardApp with a stable useRef
or lazy useState initializer, preserving the setRevealed callback and ensuring
createEphemeralRevealExpiry is invoked only once for the component lifetime.
packages/core/src/http/response.ts (1)

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

Duplicated JSON-MIME matching logic.

The application/json / +json / /json suffix check used to gate inspectChunk (Lines 63-66) duplicates the same matching logic inside shouldInline (Lines 190-192). Consider extracting a shared isJsonMime(mimeType: string): boolean helper to avoid drift if the JSON-detection rule changes later.

♻️ Proposed refactor
+function isJsonMime(mimeType: string): boolean {
+  return (
+    mimeType === "application/json" || mimeType.endsWith("+json") || mimeType.endsWith("/json")
+  );
+}
+
   const inspectChunk =
-    settings.mimeType === "application/json" ||
-    settings.mimeType.endsWith("+json") ||
-    settings.mimeType.endsWith("/json")
+    isJsonMime(settings.mimeType)
       ? options.inspectChunk
       : undefined;
 function shouldInline(response: Response, mimeType: string): boolean {
   if (isAttachment(response)) {
     return false;
   }
   return (
     mimeType === "" ||
-    mimeType === "application/json" ||
-    mimeType.endsWith("+json") ||
-    mimeType.endsWith("/json") ||
+    isJsonMime(mimeType) ||
     mimeType.startsWith("text/")
   );
 }

Also applies to: 184-195

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

In `@packages/core/src/http/response.ts` around lines 62 - 67, Extract the
repeated JSON MIME detection from the inspectChunk assignment and shouldInline
into a shared isJsonMime(mimeType: string): boolean helper. Implement the helper
with the existing application/json, +json, and /json checks, then use it in both
locations to keep the matching rule consistent.
packages/core/src/project-binding/workspaces.ts (1)

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

Solid resiliency hardening: atomic writes + defensive JSON parsing.

Temp-file + renameSync atomic writes with restrictive permissions, plus try/catch + runtime type guards for metadata/lease parsing, correctly harden this store against torn/corrupt files and partial writes.

One optional follow-up: leftover *.tmp files from a crash between writeFileSync and renameSync are never swept since leasesFor() only globs .json. Consider having cleanup() also remove stale *.tmp artifacts.

Also applies to: 234-268, 289-325

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

In `@packages/core/src/project-binding/workspaces.ts` at line 6, The cleanup logic
does not remove crash-leftover temporary files. Update cleanup-related logic,
including leasesFor() and cleanup(), to discover and delete stale *.tmp
artifacts alongside JSON lease files, while preserving the existing atomic-write
behavior.
packages/core/test/google-discovery.test.ts (1)

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

artifactByteLength manual narrowing works but is verbose; low priority.

A small type guard or shared helper would reduce the repeated in-checks, but this is test-only code with no current pain point.

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

In `@packages/core/test/google-discovery.test.ts` around lines 1661 - 1678,
Refactor the verbose manual narrowing in artifactByteLength into a reusable type
guard or shared artifact-result helper, preserving support for local-artifact
and remote-reference results and the existing error behavior.
packages/core/src/caplet-sets.ts (1)

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

callTool widened to BackendCallToolResult, error path still casts to the narrower CompatibilityCallToolResult.

Functionally correct since CompatibilityCallToolResult is a member of the BackendCallToolResult union, but the cast on Line 154 no longer matches the declared return type shape used elsewhere in this function.

♻️ Optional cleanup
-      return errorResult(error) as CompatibilityCallToolResult;
+      return errorResult(error) as BackendCallToolResult;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/caplet-sets.ts` around lines 133 - 156, Update callTool’s
catch branch to return errorResult(error) using the declared
BackendCallToolResult type, removing the outdated CompatibilityCallToolResult
cast while preserving the existing error handling.
🤖 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 `@packages/core/src/current-host/catalog-operations.ts`:
- Around line 104-126: Update each catalog install/update failure handler,
including the blocks around the current catch and the handlers near the other
referenced sections, to record one failure activity per requested caplet ID
instead of only capletIds[0]. Use appendFailureActivity with an array derived
from all capletIds, falling back to a single current-host catalog target when no
IDs are provided, preserving the catalog type for every target.

In `@packages/core/test/dashboard-catalog.test.ts`:
- Around line 193-195: Remove the duplicate error property declarations from the
response type literals used by the dashboard response parsing, including the
declarations near dashboardError and the corresponding block around the second
referenced location; retain a single error member with the intended { code:
string; message: string } shape in each type.

---

Nitpick comments:
In `@apps/dashboard/src/components/DashboardApp.test.tsx`:
- Around line 205-237: Add assertions after resolving current in “does not
refresh or toast when a newer reveal invalidates an older response” to verify
the non-stale result is applied, including the expected success toast and vault
refresh/API call with “current secret” as appropriate. Keep the existing
stale-response assertions unchanged.

In `@apps/dashboard/src/components/DashboardApp.tsx`:
- Around line 2382-2384: Replace the useMemo-based expiry controller in
DashboardApp with a stable useRef or lazy useState initializer, preserving the
setRevealed callback and ensuring createEphemeralRevealExpiry is invoked only
once for the component lifetime.

In `@packages/core/src/caplet-sets.ts`:
- Around line 133-156: Update callTool’s catch branch to return
errorResult(error) using the declared BackendCallToolResult type, removing the
outdated CompatibilityCallToolResult cast while preserving the existing error
handling.

In `@packages/core/src/current-host/vault-operations.ts`:
- Around line 245-265: Avoid reloading overlay configuration from disk on every
Vault grant or set-with-grant request. Refactor vaultAccessOrigin and its
callers to reuse a per-request or session-scoped overlay, while preserving
control-context, missing-caplet, and shadow validation; invalidate or refresh
the cached overlay when configuration changes.

In `@packages/core/src/google-discovery/manager.ts`:
- Around line 142-144: Align the fallback handling in the manager’s response
options with http-actions.ts: update the option construction near both
mediaInlineThresholdBytes usages to always set maxInlineBytes, using
this.options.mediaInlineThresholdBytes when provided and
DEFAULT_MAX_RESPONSE_BYTES otherwise. Import or reference the shared constant
consistently so both backends use the same explicit default.

In `@packages/core/src/http/response.ts`:
- Around line 62-67: Extract the repeated JSON MIME detection from the
inspectChunk assignment and shouldInline into a shared isJsonMime(mimeType:
string): boolean helper. Implement the helper with the existing
application/json, +json, and /json checks, then use it in both locations to keep
the matching rule consistent.

In `@packages/core/src/project-binding/workspaces.ts`:
- Line 6: The cleanup logic does not remove crash-leftover temporary files.
Update cleanup-related logic, including leasesFor() and cleanup(), to discover
and delete stale *.tmp artifacts alongside JSON lease files, while preserving
the existing atomic-write behavior.

In `@packages/core/test/google-discovery.test.ts`:
- Around line 1661-1678: Refactor the verbose manual narrowing in
artifactByteLength into a reusable type guard or shared artifact-result helper,
preserving support for local-artifact and remote-reference results and the
existing error behavior.

In `@packages/core/test/mcp-test-client.ts`:
- Around line 20-42: LinkedTransport.close() only notifies the local transport,
so peer disconnect handlers never run. Update close() to invoke both
this.onclose and this.peer?.onclose, preserving safe handling when no peer
exists and preventing duplicate notifications if both sides close.

In `@packages/core/test/runtime.test.ts`:
- Around line 420-421: Remove the mock-shape assertions checking that `server`
lacks `registerResource` and `registerPrompt`; retain the existing runtime
behavior assertions that verify failed refresh removes tools and reports the
unsupported registration failure.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b58fc530-9cad-4041-b1ca-1dfccd7046e5

📥 Commits

Reviewing files that changed from the base of the PR and between d4e5184 and 2780afe.

📒 Files selected for processing (107)
  • .changeset/calm-media-seam.md
  • apps/catalog/src/scripts/observability.ts
  • apps/catalog/test/observability.test.ts
  • apps/dashboard/src/components/DashboardApp.test.tsx
  • apps/dashboard/src/components/DashboardApp.tsx
  • apps/dashboard/src/lib/ephemeral-reveal.test.ts
  • apps/dashboard/src/lib/ephemeral-reveal.ts
  • apps/dashboard/vitest.config.ts
  • apps/docs/src/content/docs/privacy/indexing.mdx
  • apps/docs/src/content/docs/reference/code-mode-api.mdx
  • apps/docs/src/content/docs/troubleshooting.mdx
  • apps/docs/src/scripts/observability.ts
  • apps/docs/test/observability.test.ts
  • apps/landing/src/scripts/observability.ts
  • apps/landing/test/observability.test.ts
  • docs/architecture.md
  • docs/plans/2026-07-09-001-refactor-caplets-architecture-deepening-plan.md
  • docs/product/anonymous-telemetry.md
  • docs/product/telemetry-provider-readiness.md
  • packages/core/src/attach/api.ts
  • packages/core/src/backend-operation-dispatch.ts
  • packages/core/src/caplet-sets.ts
  • packages/core/src/cli.ts
  • packages/core/src/cli/code-mode.ts
  • packages/core/src/cli/doctor.ts
  • packages/core/src/cloud/client.ts
  • packages/core/src/cloud/presence.ts
  • packages/core/src/code-mode/runtime-api.d.ts
  • packages/core/src/code-mode/runtime-api.generated.ts
  • packages/core/src/config.ts
  • packages/core/src/current-host/catalog-operations.ts
  • packages/core/src/current-host/catalog.ts
  • packages/core/src/current-host/client-operations.ts
  • packages/core/src/current-host/operations.ts
  • packages/core/src/current-host/vault-operations.ts
  • packages/core/src/downstream.ts
  • packages/core/src/engine.ts
  • packages/core/src/exposure/discovery.ts
  • packages/core/src/exposure/projection.ts
  • packages/core/src/generated-tool-input-schema.ts
  • packages/core/src/google-discovery/manager.ts
  • packages/core/src/google-discovery/operations.ts
  • packages/core/src/graphql.ts
  • packages/core/src/http-actions.ts
  • packages/core/src/http/response.ts
  • packages/core/src/index.ts
  • packages/core/src/media/artifacts.ts
  • packages/core/src/media/index.ts
  • packages/core/src/media/input.ts
  • packages/core/src/media/results.ts
  • packages/core/src/native/project-binding-lifecycle.ts
  • packages/core/src/native/remote.ts
  • packages/core/src/native/service.ts
  • packages/core/src/openapi.ts
  • packages/core/src/project-binding/workspaces.ts
  • packages/core/src/remote-control/dispatch.ts
  • packages/core/src/remote-control/types.ts
  • packages/core/src/result-content.ts
  • packages/core/src/runtime.ts
  • packages/core/src/serve/http.ts
  • packages/core/src/serve/index.ts
  • packages/core/src/serve/session.ts
  • packages/core/src/tools.ts
  • packages/core/test/attach-api.test.ts
  • packages/core/test/backend-operation-dispatch.test.ts
  • packages/core/test/backend-operation-runtime.ts
  • packages/core/test/caplet-sets.test.ts
  • packages/core/test/cli-tools.test.ts
  • packages/core/test/cli.test.ts
  • packages/core/test/cloud-presence.test.ts
  • packages/core/test/code-mode-cli.test.ts
  • packages/core/test/code-mode-mcp.test.ts
  • packages/core/test/config.test.ts
  • packages/core/test/current-host-administration.test.ts
  • packages/core/test/dashboard-api.test.ts
  • packages/core/test/dashboard-catalog.test.ts
  • packages/core/test/dashboard-session.test.ts
  • packages/core/test/dashboard-vault.test.ts
  • packages/core/test/doctor-cli.test.ts
  • packages/core/test/downstream.test.ts
  • packages/core/test/exposure-discovery.test.ts
  • packages/core/test/exposure-projection.test.ts
  • packages/core/test/fixtures/stdio-mixed-content-server.ts
  • packages/core/test/fixtures/stdio-server.ts
  • packages/core/test/google-discovery.test.ts
  • packages/core/test/graphql.test.ts
  • packages/core/test/http-actions.test.ts
  • packages/core/test/mcp-test-client.ts
  • packages/core/test/media-artifacts.test.ts
  • packages/core/test/native-remote.test.ts
  • packages/core/test/native.test.ts
  • packages/core/test/openapi.test.ts
  • packages/core/test/project-binding-integration.test.ts
  • packages/core/test/remote-control-dispatch.test.ts
  • packages/core/test/result-content.test.ts
  • packages/core/test/runtime.test.ts
  • packages/core/test/serve-http.test.ts
  • packages/core/test/serve-session.test.ts
  • packages/core/test/telemetry-docs.test.ts
  • packages/core/test/tools.test.ts
  • packages/pi/src/index.ts
  • packages/pi/test/pi.test.ts
  • packages/web-observability/src/index.ts
  • packages/web-observability/src/posthog.ts
  • packages/web-observability/src/privacy.ts
  • packages/web-observability/test/posthog-provider-contract.test.ts
  • packages/web-observability/test/web-observability.test.ts
💤 Files with no reviewable changes (3)
  • packages/core/test/telemetry-docs.test.ts
  • packages/web-observability/src/privacy.ts
  • packages/web-observability/test/web-observability.test.ts

Comment thread packages/core/src/current-host/catalog-operations.ts
Comment thread packages/core/test/dashboard-catalog.test.ts
Own accepted Caplet IDs, serialized updates, cleanup-last close, and atomic replacement behind one native lifecycle while preserving distinct Cloud and self-hosted failure policies. Reauthorize and serialize self-hosted lease mutations so stale sockets cannot revive terminal bindings.
@ian-pascoe ian-pascoe force-pushed the worktree/green-harbor-e0e3 branch from 2780afe to 3e58bd2 Compare July 10, 2026 15:04
Close linked MCP transports symmetrically, preserve per-target catalog failure activity, and make media and privacy boundaries explicit. Harden Vault reveal race coverage and simplify redundant assertions and casts.
@ian-pascoe ian-pascoe changed the title refactor: deepen Caplets architecture seams refactor(core): deepen Caplets architecture seams Jul 10, 2026
@ian-pascoe

Copy link
Copy Markdown
Contributor Author

Review disposition — df6879d

Addressed the valid findings:

  • linked MCP test transports now close both endpoints exactly once;
  • Google Discovery now passes the shared explicit inline-byte default in both response paths;
  • the Vault reveal race test proves the current response renders, refreshes Vault, and emits the success toast;
  • the expiry controller uses a lazy useState initializer for stable component-lifetime identity;
  • redundant runtime mock-shape assertions were removed;
  • JSON MIME matching uses one helper;
  • the stale CompatibilityCallToolResult cast now matches BackendCallToolResult;
  • PostHog property validation no longer needs per-property never casts;
  • failed batch catalog operations record every requested caplet target;
  • the PR title now names the architectural refactor.

Verified but intentionally unchanged:

  • dashboard-catalog.test.ts already has exactly one error member per response type; the duplicate-member report was a false positive.
  • Vault overlay reads remain uncached because grant validation must observe current config; introducing cache invalidation for a speculative request-path optimization would weaken that invariant.
  • crash-leftover workspace *.tmp sweeping remains out of scope: there is no stale-age contract, and deleting PID/timestamp temp files during concurrent atomic writes can race an active writer.
  • the test-only artifactByteLength narrowing remains local; extracting a helper would add abstraction without another caller.

Validation: full pnpm verify passed — formatting, lint, generated API/schema/docs checks, typecheck, 158 test files / 2,238 tests, benchmark freshness, and all builds.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@apps/dashboard/src/components/DashboardApp.test.tsx`:
- Around line 217-219: Update the waitFor predicate in the DashboardApp test to
synchronize on the new confirm request by requiring the second "vault/reveal"
call, or clear dashboardApi.mock before triggering it and wait for the first
subsequent call. Ensure the predicate cannot pass due to the earlier reveal
request.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0dc8226f-caa2-4f80-9734-b200686fb445

📥 Commits

Reviewing files that changed from the base of the PR and between 2780afe and 6383454.

📒 Files selected for processing (26)
  • .changeset/calm-media-seam.md
  • apps/dashboard/src/components/DashboardApp.test.tsx
  • apps/dashboard/src/components/DashboardApp.tsx
  • docs/architecture.md
  • packages/core/src/caplet-sets.ts
  • packages/core/src/cloud/client.ts
  • packages/core/src/cloud/presence.ts
  • packages/core/src/current-host/catalog-operations.ts
  • packages/core/src/google-discovery/manager.ts
  • packages/core/src/http/response.ts
  • packages/core/src/media/artifacts.ts
  • packages/core/src/native/project-binding-lifecycle.ts
  • packages/core/src/native/service.ts
  • packages/core/src/project-binding/workspaces.ts
  • packages/core/src/serve/http.ts
  • packages/core/test/cloud-presence.test.ts
  • packages/core/test/current-host-administration.test.ts
  • packages/core/test/mcp-test-client.ts
  • packages/core/test/media-artifacts.test.ts
  • packages/core/test/native-remote.test.ts
  • packages/core/test/native.test.ts
  • packages/core/test/project-binding-integration.test.ts
  • packages/core/test/runtime.test.ts
  • packages/core/test/serve-http.test.ts
  • packages/web-observability/src/posthog.ts
  • packages/web-observability/src/privacy.ts
💤 Files with no reviewable changes (1)
  • packages/core/test/runtime.test.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/calm-media-seam.md
🚧 Files skipped from review as they are similar to previous changes (22)
  • packages/core/src/google-discovery/manager.ts
  • packages/core/test/project-binding-integration.test.ts
  • packages/core/test/mcp-test-client.ts
  • docs/architecture.md
  • packages/web-observability/src/posthog.ts
  • packages/core/test/cloud-presence.test.ts
  • packages/core/test/media-artifacts.test.ts
  • packages/core/src/project-binding/workspaces.ts
  • packages/core/src/cloud/client.ts
  • packages/core/src/caplet-sets.ts
  • packages/core/src/http/response.ts
  • packages/core/test/current-host-administration.test.ts
  • packages/core/src/current-host/catalog-operations.ts
  • apps/dashboard/src/components/DashboardApp.tsx
  • packages/core/src/media/artifacts.ts
  • packages/core/src/native/project-binding-lifecycle.ts
  • packages/core/src/cloud/presence.ts
  • packages/core/test/native-remote.test.ts
  • packages/core/src/native/service.ts
  • packages/core/test/serve-http.test.ts
  • packages/core/test/native.test.ts
  • packages/core/src/serve/http.ts

Comment on lines +217 to +219
await waitFor(() =>
dashboardApi.mock.calls.some(([path]) => path === "vault/reveal") ? true : undefined,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect confirmReveal / reveal helpers to confirm whether dispatch is awaited
rg -nP 'const (confirmReveal|reveal|openRevealConfirmation)\b' -A 15 apps/dashboard/src/components/DashboardApp.test.tsx

Repository: spiritledsoftware/caplets

Length of output: 163


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant test section and nearby helper definitions.
sed -n '180,240p' apps/dashboard/src/components/DashboardApp.test.tsx
printf '\n---\n'
rg -n 'confirmReveal|reveal|openRevealConfirmation|dashboardApi\.mock\.calls|toast\.error|waitFor' apps/dashboard/src/components/DashboardApp.test.tsx

Repository: spiritledsoftware/caplets

Length of output: 4714


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '120,136p' apps/dashboard/src/components/DashboardApp.test.tsx

Repository: spiritledsoftware/caplets

Length of output: 626


Make the wait gate on the second vault/reveal call. dashboardApi.mock.calls already contains the earlier reveal request, so this predicate is true before the new confirm request lands. Capture the call count or clear the mock first, otherwise this waitFor doesn’t synchronize anything and can hide a race.

🤖 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 `@apps/dashboard/src/components/DashboardApp.test.tsx` around lines 217 - 219,
Update the waitFor predicate in the DashboardApp test to synchronize on the new
confirm request by requiring the second "vault/reveal" call, or clear
dashboardApi.mock before triggering it and wait for the first subsequent call.
Ensure the predicate cannot pass due to the earlier reveal request.

@ian-pascoe ian-pascoe merged commit 870a599 into main Jul 11, 2026
8 checks passed
@ian-pascoe ian-pascoe deleted the worktree/green-harbor-e0e3 branch July 11, 2026 11:00
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