Opt-in decryption for o11y tooling (CLI + web)#1256
Conversation
🦋 Changeset detectedLatest commit: f3e3ded The changes in this PR will be included in the next version bump. This PR includes changesets to release 15 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
🧪 E2E Test Results❌ Some tests failed Summary
❌ Failed Tests🌍 Community Worlds (48 failed)turso (48 failed):
Details by Category✅ ▲ Vercel Production
✅ 💻 Local Development
✅ 📦 Local Production
✅ 🐘 Local Postgres
✅ 🪟 Windows
❌ 🌍 Community Worlds
✅ 📋 Other
|
There was a problem hiding this comment.
Pull request overview
This PR makes observability decryption explicitly opt-in across the CLI and web dashboard, so encrypted run/step/event payloads render as “Encrypted” placeholders unless the user requests decryption (via --decrypt in CLI or a Decrypt button in the UI). It builds on the existing wire encryption work by adding encrypted-marker detection/display and on-demand key retrieval for hydration.
Changes:
- CLI: adds
--decryptsupport with per-run key resolution/caching and encrypted placeholders in inspect output. - Web: adds a Decrypt button that retrieves the per-run key via an RPC server action and threads the key into event/resource hydration.
- Core/web-shared/world-vercel: adds encrypted data detection utilities and marker rendering; updates Vercel key lookup flow and passes
teamIdto the run-key endpoint.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/world-vercel/src/index.ts | Refactors Vercel World to inline getEncryptionKeyForRun logic and changes exports. |
| packages/world-vercel/src/encryption.ts | Updates run-key fetch contract/parsing and request headers. |
| packages/web/app/server/workflow-server-actions.server.ts | Adds server action to retrieve run encryption key for client-side decryption; switches to createVercelWorld. |
| packages/web/app/routes/api.rpc.tsx | Exposes getEncryptionKeyForRun via CBOR RPC handler. |
| packages/web/app/lib/rpc-client.ts | Adds RPC client wrapper for getEncryptionKeyForRun. |
| packages/web/app/components/run-detail-view.tsx | Adds Decrypt button and threads key through event hydration + trace viewer props. |
| packages/web-shared/src/lib/hydration.ts | Adds encrypted markers, encrypted detection helpers, and async hydration with key. |
| packages/web-shared/src/index.ts | Adjusts barrel exports for new encryption-related helpers. |
| packages/web-shared/src/components/workflow-trace-view.tsx | Threads encryptionKey prop to entity detail panel/events list. |
| packages/web-shared/src/components/ui/data-inspector.tsx | Adds encrypted marker rendering and minor deep-equality change. |
| packages/web-shared/src/components/sidebar/events-list.tsx | Adds key-aware reload behavior for expanded events. |
| packages/web-shared/src/components/sidebar/entity-detail-panel.tsx | Adds encryptionKey prop wiring (but currently introduces duplicate rendering). |
| packages/web-shared/src/components/sidebar/attribute-panel.tsx | Adds encrypted-field UI block for run/step/hook/event attributes. |
| packages/web-shared/src/components/event-list-view.tsx | Preserves custom constructors in parsing and reloads expanded events on key availability. |
| packages/core/src/serialization-format.ts | Adds ENCRYPTED format + isEncryptedData, and async hydrateDataWithKey. |
| packages/core/src/serialization-format.test.ts | Adds tests for encrypted prefix detection and pass-through behavior. |
| packages/cli/src/lib/inspect/output.ts | Wires --decrypt into CLI inspect commands and shows encrypted placeholders. |
| packages/cli/src/lib/inspect/hydration.ts | Adds encrypted placeholder ref + optional decryption during hydration. |
| packages/cli/src/lib/config/types.ts | Adds decrypt?: boolean inspect option. |
| packages/cli/src/commands/inspect.ts | Adds --decrypt flag to CLI command definition. |
| .changeset/opt-in-decrypt.md | Adds changeset for CLI/core/web/web-shared but currently omits world-vercel. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
The rebase incorrectly picked up older versions of these files from early encryption branch commits. The main versions are correct and up-to-date.
… collapsed preview
Cherry-pick conflict resolution incorrectly took the older opt-in-decrypt versions of these files, reverting improvements from main (dispatcher, createGetEncryptionKeyForRun extraction, nullable key response).
- Remove duplicate AttributePanel/EventsList rendering in entity-detail-panel.tsx. Thread encryptionKey into the existing EventsList render instead. - Restore missing re-exports (isClassInstanceRef, isStreamId, isStreamRef) in web-shared/src/index.ts to maintain backwards compatibility. - Add 'error' to replaceEncryptedWithMarkers field list in web-shared hydration.ts to match the decrypt path. - Extend CLI hydration eventData decrypt/placeholder to cover all known serialized fields (output, metadata, payload) not just result/input. - Add 'error' to CLI replaceEncryptedWithRef field list. - Remove invalid encryptionKey option from useWorkflowResourceData call (hook doesn't support it yet), add TODO. - Add 4 unit tests for hydrateDataWithKey in serialization-format.test.ts: encrypted+key decrypts, encrypted+noKey returns raw, non-encrypted hydrates normally, non-Uint8Array legacy data passes through.
Instead of leaving a TODO, implement the encryptionKey support directly: - Add optional encryptionKey to useWorkflowResourceData options - When key is available, use hydrateResourceIOWithKey (async decrypt) instead of hydrateResourceIO for all resource types - Remove redundant hydrateResourceIO from fetchResourceWithCorrelationId
High priority: - Gate showStream key fetch on --decrypt flag, warn when --decrypt used without --run - Fix workflow-server-actions.server.ts missing cryptoKey params (undefined for both getExternalRevivers and getDeserializeStream) - Add hydration + decryption to listEvents (was completely missing) - Fix error/eventData display: check isEncryptedMarker before hasDisplayContent so encrypted markers don't silently disappear Medium priority: - handleDecrypt: use toast.error() instead of console.error for user-visible feedback on key fetch failures - CLI maybeDecryptFields: add try/catch with graceful fallback to encrypted placeholders + warning, also decrypt error field - use-resource-data: wrap hook/sleep hydrate() in try/catch to prevent stuck loading state on decryption errors - Decrypt button: also check run.error and step input/output for encrypted markers, not just run.input/output Low priority: - event-list-view: add .catch() to re-load useEffect promise - Export ENCRYPTED_DISPLAY_NAME from hydration.ts and import in data-inspector.tsx instead of raw 'Encrypted' string
📊 Benchmark Results
workflow with no steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) workflow with 1 step💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) | Express workflow with 10 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) workflow with 25 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Express | Nitro workflow with 50 sequential steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Express | Nitro | Next.js (Turbopack) Promise.all with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) | Express Promise.all with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) Promise.all with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Next.js (Turbopack) | Express Promise.race with 10 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Nitro | Express | Next.js (Turbopack) Promise.race with 25 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Nitro | Express Promise.race with 50 concurrent steps💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Nitro | Express Stream Benchmarks (includes TTFB metrics)workflow with stream💻 Local Development
▲ Production (Vercel)
🔍 Observability: Next.js (Turbopack) | Nitro | Express SummaryFastest Framework by WorldWinner determined by most benchmark wins
Fastest World by FrameworkWinner determined by most benchmark wins
Column Definitions
Worlds:
|
Summary
Makes decryption an explicit opt-in for observability tooling (CLI inspect and web dashboard), building on the wire encryption from #1251.
Key changes
--decryptflag forworkflow inspectcommands. Encrypted data displays as[Encrypted]by default. When--decryptis passed, fetches the encryption key (viagetEncryptionKeyForRun) and hydrates data in-place. Key is cached perrunId.EncryptedDataRefsentinel class withutil.inspect.customfor CLI,isEncryptedMarker()checks for web UI components. Lock icon shown for encrypted fields.teamIdparameter passed to the get-key endpoint.Packages changed
@workflow/cli--decryptflag,EncryptedDataRefdisplay, key caching per runId@workflow/coreserialization-format.tsencrypted marker utilities@workflow/web-sharedisEncryptedMarker,hydrateResourceIOWithKey, encrypted field UI (Lock icon,EncryptedFieldBlock)@workflow/web@workflow/world-vercelteamIdin get-key endpointRebased from
nate/opt-in-decryptonto the latestnate/wire-encryption.