import { Effect } from "effect";import { StorageError, type SecretProvider } from "@executor/sdk";import { getPassword, setPassword, deletePassword } from "./keyring";// ---------------------------------------------------------------------------// SecretProvider adapter — bridges keyring into SDK resolution chain//// The underlying `@napi-rs/keyring` sync API encodes "no entry" as an// ordinary return value (`getPassword()` → `null`, `deletePassword()` →// `false`), and only throws on real failures (keychain locked, permission// denied, platform init failure, etc.). `keyring.ts` wraps those thrown// failures as `KeychainError`. We translate `KeychainError` →// `StorageError` so the HTTP edge can capture it to telemetry and surface// an opaque `InternalError({ traceId })` — previously `orElseSucceed`// silently converted every failure into "nothing found", which made it// impossible to debug why secrets weren't resolving.// ---------------------------------------------------------------------------const toStorageError = (cause: { readonly message: string; readonly cause?: unknown }) => new StorageError({ message: cause.message, cause: cause.cause ?? cause });// Scope arg is ignored — keychain partitions by `serviceName`, which the// host fixes per executor at construction time. A future refactor could// fold `scope` into the service name, but today a keychain provider// instance is already one-scope.export const makeKeychainProvider = (serviceName: string): SecretProvider => ({ key: "keychain", writable: true, get: (secretId, _scope) => getPassword(serviceName, secretId).pipe(Effect.mapError(toStorageError)), set: (secretId, value, _scope) => setPassword(serviceName, secretId, value).pipe(Effect.mapError(toStorageError)), delete: (secretId, _scope) => deletePassword(serviceName, secretId).pipe(Effect.mapError(toStorageError)), // Keychain doesn't support enumerating — you need to know the account name list: undefined,});
import { Effect } from "effect";
import { StorageError, type SecretProvider } from "@executor/sdk";
import { getPassword, setPassword, deletePassword } from "./keyring";
// ---------------------------------------------------------------------------
// SecretProvider adapter — bridges keyring into SDK resolution chain
//
// The underlying `@napi-rs/keyring` sync API encodes "no entry" as an
// ordinary return value (`getPassword()` → `null`, `deletePassword()` →
// `false`), and only throws on real failures (keychain locked, permission
// denied, platform init failure, etc.). `keyring.ts` wraps those thrown
// failures as `KeychainError`. We translate `KeychainError` →
// `StorageError` so the HTTP edge can capture it to telemetry and surface
// an opaque `InternalError({ traceId })` — previously `orElseSucceed`
// silently converted every failure into "nothing found", which made it
// impossible to debug why secrets weren't resolving.
// ---------------------------------------------------------------------------
const toStorageError = (cause: { readonly message: string; readonly cause?: unknown }) =>
new StorageError({ message: cause.message, cause: cause.cause ?? cause });
// Scope arg is ignored — keychain partitions by `serviceName`, which the
// host fixes per executor at construction time. A future refactor could
// fold `scope` into the service name, but today a keychain provider
// instance is already one-scope.
export const makeKeychainProvider = (serviceName: string): SecretProvider => ({
key: "keychain",
writable: true,
get: (secretId, _scope) =>
getPassword(serviceName, secretId).pipe(Effect.mapError(toStorageError)),
set: (secretId, value, _scope) =>
setPassword(serviceName, secretId, value).pipe(Effect.mapError(toStorageError)),
delete: (secretId, _scope) =>
deletePassword(serviceName, secretId).pipe(Effect.mapError(toStorageError)),
// Keychain doesn't support enumerating — you need to know the account name
list: undefined,
});