import { Effect, Schema } from "effect";import type { StorageFailure } from "@executor-js/storage-core";import { SecretId, ScopeId } from "./ids";// ---------------------------------------------------------------------------// SecretProvider — what a concrete backend (keychain, 1password, file,// memory, workos-vault, …) implements. Providers are contributed by// plugins via `plugin.secretProviders` and registered in the executor// at startup; there's no runtime registration.//// The `key` field is the provider's identifier in the secret table's// `provider` column and in `executor.secrets.set({ provider, ... })`.// Unique per executor.// ---------------------------------------------------------------------------export interface SecretProvider { /** Unique key (e.g. "keychain", "env", "1password", "memory"). */ readonly key: string; /** If false, `set` and `delete` are never called. The executor * honours this before routing writes — trying to write to a * read-only provider is an error, not a silent drop. */ readonly writable: boolean; /** Get a secret value. `scope` is the executor scope the lookup is * being made on behalf of — providers that partition their storage * by scope (memory, keychain via service name, per-vault in * 1password) use it; providers without tenancy ignore it and fall * back to a flat lookup. Failures (provider unreachable, decryption * failed, etc.) surface as `StorageFailure` — the executor treats * a provider call the same as a DB call; `StorageError` is captured * at the HTTP edge to `InternalError`, `UniqueViolationError` dies. */ readonly get: (id: string, scope: string) => Effect.Effect<string | null, StorageFailure>; /** Check whether a provider has a backing value without returning it. * Providers that can answer this cheaply should implement it so * stale core routing rows don't appear as selectable secrets. */ readonly has?: (id: string, scope: string) => Effect.Effect<boolean, StorageFailure>; /** Set a secret value at a named scope. Only called on writable * providers. Providers that partition by scope use this arg to * decide where to write; flat providers ignore it. */ readonly set?: (id: string, value: string, scope: string) => Effect.Effect<void, StorageFailure>; /** Delete a secret at a named scope. Only called on writable providers. * Returns true if something was deleted. */ readonly delete?: (id: string, scope: string) => Effect.Effect<boolean, StorageFailure>; /** Enumerate known secret entries. Optional — not all backends can * enumerate (env-backed providers, for example). */ readonly list?: () => Effect.Effect< readonly { readonly id: string; readonly name: string }[], StorageFailure >; /** Whether the provider may be asked during id-only fallback resolution. * Providers whose own auth depends on `ctx.secrets.get` should opt out to * avoid recursive fallback through themselves. */ readonly allowFallback?: boolean;}// ---------------------------------------------------------------------------// SecretRef — metadata about a stored secret. Returned from// `executor.secrets.list()`. The actual value lives in the provider// and is only reachable via `executor.secrets.get(id)`.// ---------------------------------------------------------------------------export class SecretRef extends Schema.Class<SecretRef>("SecretRef")({ id: SecretId, scopeId: ScopeId, /** Human-readable label (e.g. "Cloudflare API Token") */ name: Schema.String, /** Which provider holds the value */ provider: Schema.String, createdAt: Schema.Date,}) {}// ---------------------------------------------------------------------------// SetSecretInput — all the metadata to write a secret in one call.// `executor.secrets.set(input)` takes this and writes both the// value (to the provider) and the ref (to the `secret` table).//// `scope` is required — there's no default write target. Callers name// which scope in the executor's stack should own the secret. Typical// pattern: UI wiring up org-level API keys writes to the org scope;// OAuth token exchange writes to the innermost per-user scope.// ---------------------------------------------------------------------------export class SetSecretInput extends Schema.Class<SetSecretInput>("SetSecretInput")({ id: SecretId, /** Scope id to own this secret. Must be one of the executor's * configured scopes. */ scope: ScopeId, /** Display name shown in secret-list UI. */ name: Schema.String, /** The secret value itself — never persisted outside the provider. */ value: Schema.String, /** Optional provider routing. If unset the executor picks the first * writable provider in registration order. */ provider: Schema.optional(Schema.String),}) {}export class RemoveSecretInput extends Schema.Class<RemoveSecretInput>("RemoveSecretInput")({ id: SecretId, /** Scope id whose secret row/value should be removed. Must be one of * the executor's configured scopes. */ targetScope: ScopeId,}) {}
import { Effect, Schema } from "effect";
import type { StorageFailure } from "@executor-js/storage-core";
import { SecretId, ScopeId } from "./ids";
// ---------------------------------------------------------------------------
// SecretProvider — what a concrete backend (keychain, 1password, file,
// memory, workos-vault, …) implements. Providers are contributed by
// plugins via `plugin.secretProviders` and registered in the executor
// at startup; there's no runtime registration.
//
// The `key` field is the provider's identifier in the secret table's
// `provider` column and in `executor.secrets.set({ provider, ... })`.
// Unique per executor.
// ---------------------------------------------------------------------------
export interface SecretProvider {
/** Unique key (e.g. "keychain", "env", "1password", "memory"). */
readonly key: string;
/** If false, `set` and `delete` are never called. The executor
* honours this before routing writes — trying to write to a
* read-only provider is an error, not a silent drop. */
readonly writable: boolean;
/** Get a secret value. `scope` is the executor scope the lookup is
* being made on behalf of — providers that partition their storage
* by scope (memory, keychain via service name, per-vault in
* 1password) use it; providers without tenancy ignore it and fall
* back to a flat lookup. Failures (provider unreachable, decryption
* failed, etc.) surface as `StorageFailure` — the executor treats
* a provider call the same as a DB call; `StorageError` is captured
* at the HTTP edge to `InternalError`, `UniqueViolationError` dies. */
readonly get: (id: string, scope: string) => Effect.Effect<string | null, StorageFailure>;
/** Check whether a provider has a backing value without returning it.
* Providers that can answer this cheaply should implement it so
* stale core routing rows don't appear as selectable secrets. */
readonly has?: (id: string, scope: string) => Effect.Effect<boolean, StorageFailure>;
/** Set a secret value at a named scope. Only called on writable
* providers. Providers that partition by scope use this arg to
* decide where to write; flat providers ignore it. */
readonly set?: (id: string, value: string, scope: string) => Effect.Effect<void, StorageFailure>;
/** Delete a secret at a named scope. Only called on writable providers.
* Returns true if something was deleted. */
readonly delete?: (id: string, scope: string) => Effect.Effect<boolean, StorageFailure>;
/** Enumerate known secret entries. Optional — not all backends can
* enumerate (env-backed providers, for example). */
readonly list?: () => Effect.Effect<
readonly { readonly id: string; readonly name: string }[],
StorageFailure
>;
/** Whether the provider may be asked during id-only fallback resolution.
* Providers whose own auth depends on `ctx.secrets.get` should opt out to
* avoid recursive fallback through themselves. */
readonly allowFallback?: boolean;
}
// ---------------------------------------------------------------------------
// SecretRef — metadata about a stored secret. Returned from
// `executor.secrets.list()`. The actual value lives in the provider
// and is only reachable via `executor.secrets.get(id)`.
// ---------------------------------------------------------------------------
export class SecretRef extends Schema.Class<SecretRef>("SecretRef")({
id: SecretId,
scopeId: ScopeId,
/** Human-readable label (e.g. "Cloudflare API Token") */
name: Schema.String,
/** Which provider holds the value */
provider: Schema.String,
createdAt: Schema.Date,
}) {}
// ---------------------------------------------------------------------------
// SetSecretInput — all the metadata to write a secret in one call.
// `executor.secrets.set(input)` takes this and writes both the
// value (to the provider) and the ref (to the `secret` table).
//
// `scope` is required — there's no default write target. Callers name
// which scope in the executor's stack should own the secret. Typical
// pattern: UI wiring up org-level API keys writes to the org scope;
// OAuth token exchange writes to the innermost per-user scope.
// ---------------------------------------------------------------------------
export class SetSecretInput extends Schema.Class<SetSecretInput>("SetSecretInput")({
id: SecretId,
/** Scope id to own this secret. Must be one of the executor's
* configured scopes. */
scope: ScopeId,
/** Display name shown in secret-list UI. */
name: Schema.String,
/** The secret value itself — never persisted outside the provider. */
value: Schema.String,
/** Optional provider routing. If unset the executor picks the first
* writable provider in registration order. */
provider: Schema.optional(Schema.String),
}) {}
export class RemoveSecretInput extends Schema.Class<RemoveSecretInput>("RemoveSecretInput")({
id: SecretId,
/** Scope id whose secret row/value should be removed. Must be one of
* the executor's configured scopes. */
targetScope: ScopeId,
}) {}