From 1144126deb60b851e054163feb84151d06e03418 Mon Sep 17 00:00:00 2001 From: Jim Pudar Date: Wed, 20 May 2026 06:20:34 -0400 Subject: [PATCH] Modularize secrets provider handling --- README.md | 24 ++-- home.nix | 4 +- secrets.env.defaults | 6 +- src/rootcell/env.ts | 32 ++++- .../providers/macos-lima-user-v2/provider.ts | 5 + src/rootcell/providers/factory.ts | 5 + src/rootcell/providers/types.ts | 2 + src/rootcell/rootcell.test.ts | 124 +++++++++++++++++- src/rootcell/rootcell.ts | 32 ++--- src/rootcell/secrets/macos-keychain.ts | 39 ++++++ src/rootcell/secrets/registry.ts | 42 ++++++ src/rootcell/secrets/types.ts | 38 ++++++ src/rootcell/types.ts | 8 -- 13 files changed, 302 insertions(+), 59 deletions(-) create mode 100644 src/rootcell/secrets/macos-keychain.ts create mode 100644 src/rootcell/secrets/registry.ts create mode 100644 src/rootcell/secrets/types.ts diff --git a/README.md b/README.md index f5b241e..47a9783 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,8 @@ rootcell is early and intentionally narrow. Today it targets: - **Coding harness:** [Pi](https://pi.dev) inside the agent VM. The agent and firewall environments are NixOS VMs, but the host-side lifecycle, -networking, Keychain integration, and VM lifecycle currently assume macOS. +networking, default Keychain-backed secrets provider, and VM lifecycle currently +assume macOS. ## Why This Exists @@ -35,8 +36,8 @@ VM without receiving broad access to your Mac: - A separate firewall VM with the only public internet route. - DNS, HTTPS, and SSH allowlists you can review and hot-reload. - A per-VM SSH key for Git pushes. -- Provider secrets read from macOS Keychain at runtime, not stored in the VM or - the Nix store. +- Provider secrets read from host-side secret providers at runtime, not stored + in the VM or the Nix store. Use it when you want the agent to go wild inside the VM, while keeping an explicit network boundary around the work. @@ -73,7 +74,7 @@ The two VMs have different jobs: Rootcell supports named instances. Plain `./rootcell` uses the `default` instance and creates VMs named `agent` and `firewall`. `./rootcell --instance dev` creates `agent-dev` and `firewall-dev`, with separate CA material, -allowlists, Keychain mappings, and a separate private VM link. +allowlists, secret mappings, and a separate private VM link. HTTPS egress is transparent from inside the agent VM. A normal command like `curl https://github.com` either works because the host is allowlisted, or fails @@ -331,7 +332,7 @@ firewall-vm.nix firewall VM services and nftables rules home.nix pi, Git, SSH, and developer tools for the agent VM network.nix default inter-VM network settings .env.defaults seed values for per-instance `.env` -secrets.env.defaults seed Keychain secret mappings for per-instance `secrets.env` +secrets.env.defaults seed provider-qualified secret mappings for per-instance `secrets.env` instances/ per-instance state, allowlists, CA, SSH keys, and generated files proxy/ allowlists and mitmproxy/dnsmasq firewall code @@ -356,7 +357,7 @@ the private user-v2 address. Use `./rootcell list` to show known VMs and their state. `./rootcell stop` stops the selected instance's VMs, and `./rootcell remove` stops the selected instance and deletes its Lima VM state. Instance-local configuration such as -allowlists, Keychain mappings, CA files, and subnet allocation remains in +allowlists, secret mappings, CA files, and subnet allocation remains in the instance state directory so the next start keeps the same instance settings. @@ -390,18 +391,19 @@ NETWORK_PREFIX=24 `./rootcell` also seeds `/secrets.env` from `secrets.env.defaults` on first run. This file maps agent VM environment -variable names to macOS Keychain service names; it does not contain the secret -values themselves: +variable names to provider-qualified secret references; it does not contain the +secret values themselves. The provider id is required on each line, so different +secrets may come from different providers: ```sh -AWS_BEARER_TOKEN_BEDROCK=aws-bedrock-api-key +AWS_BEARER_TOKEN_BEDROCK=macos-keychain:aws-bedrock-api-key ``` For example, to inject an additional `ANTHROPIC_API_KEY`: ```sh security add-generic-password -a "$USER" -s anthropic-api-key -w "" -echo 'ANTHROPIC_API_KEY=anthropic-api-key' >> "$INSTANCE_DIR/secrets.env" +echo 'ANTHROPIC_API_KEY=macos-keychain:anthropic-api-key' >> "$INSTANCE_DIR/secrets.env" ``` If you want to use Anthropic or OpenAI subscriptions, you can log in from @@ -448,7 +450,7 @@ Named instances are isolated from each other: ./rootcell --instance review ``` -Each instance gets its own VMs, state directory, CA, allowlists, Keychain mapping +Each instance gets its own VMs, state directory, CA, allowlists, secret mapping file, control SSH key, private-link state, and `/24`. The `default` instance still seeds from legacy repo-local `.env`, `secrets.env`, diff --git a/home.nix b/home.nix index b45f0a4..b810e79 100644 --- a/home.nix +++ b/home.nix @@ -5,8 +5,8 @@ # # Pi reads provider keys from the env. DON'T put them in this file — the # Nix store is world-readable. Configure secret entries in secrets.env; `rootcell` -# reads those macOS Keychain secrets on the host and exports them on -# guest sessions. +# reads those provider-backed secrets on the host and exports them on guest +# sessions. let net = import ./network.nix; diff --git a/secrets.env.defaults b/secrets.env.defaults index 319595a..8dea56b 100644 --- a/secrets.env.defaults +++ b/secrets.env.defaults @@ -1,5 +1,5 @@ -# Defaults for Keychain secrets to inject into the agent VM. Copied to +# Defaults for provider-backed secrets to inject into the agent VM. Copied to # secrets.env on first run if secrets.env is missing. -# Format: = +# Format: =: -AWS_BEARER_TOKEN_BEDROCK=aws-bedrock-api-key +AWS_BEARER_TOKEN_BEDROCK=macos-keychain:aws-bedrock-api-key diff --git a/src/rootcell/env.ts b/src/rootcell/env.ts index 0f42ae5..fef0418 100644 --- a/src/rootcell/env.ts +++ b/src/rootcell/env.ts @@ -1,6 +1,10 @@ import { existsSync, readFileSync } from "node:fs"; import { EnvironmentVariableNameSchema } from "./schema.ts"; -import { SecretMappingSchema, type SecretMapping } from "./types.ts"; +import { + SecretEnvMappingSchema, + SecretProviderIdSchema, + type SecretEnvMapping, +} from "./secrets/types.ts"; export function loadDotEnv(path: string, env: NodeJS.ProcessEnv): void { if (!existsSync(path)) { @@ -23,8 +27,8 @@ export function loadDotEnv(path: string, env: NodeJS.ProcessEnv): void { } } -export function parseSecretMappings(text: string): SecretMapping[] { - const mappings: SecretMapping[] = []; +export function parseSecretMappings(text: string): SecretEnvMapping[] { + const mappings: SecretEnvMapping[] = []; for (const line of text.split(/\r?\n/)) { if (line.length === 0 || line.startsWith("#")) { continue; @@ -39,9 +43,27 @@ export function parseSecretMappings(text: string): SecretMapping[] { throw new Error(`invalid secret environment variable name in secrets.env: ${envName}`); } if (service.length === 0) { - throw new Error(`empty Keychain service name for ${envName}`); + throw new Error(`empty secret reference for ${envName}`); } - mappings.push(SecretMappingSchema.parse({ envName, service })); + const separatorAt = service.indexOf(":"); + if (separatorAt === -1) { + throw new Error(`secret reference for ${envName} must include a provider id, for example macos-keychain:${service}`); + } + const providerId = service.slice(0, separatorAt); + const reference = service.slice(separatorAt + 1); + if (providerId.length === 0) { + throw new Error(`empty secret provider id for ${envName}`); + } + if (!SecretProviderIdSchema.safeParse(providerId).success) { + throw new Error(`invalid secret provider id in secrets.env for ${envName}: ${providerId}`); + } + if (reference.length === 0) { + throw new Error(`empty secret reference for ${envName}`); + } + mappings.push(SecretEnvMappingSchema.parse({ + envName, + secret: { providerId, reference }, + })); } return mappings; } diff --git a/src/rootcell/integration/providers/macos-lima-user-v2/provider.ts b/src/rootcell/integration/providers/macos-lima-user-v2/provider.ts index 92f3e9d..5166cc2 100644 --- a/src/rootcell/integration/providers/macos-lima-user-v2/provider.ts +++ b/src/rootcell/integration/providers/macos-lima-user-v2/provider.ts @@ -18,6 +18,8 @@ import { } from "../../../providers/macos-lima-user-v2-network.ts"; import type { ProviderBundle } from "../../../providers/types.ts"; import { preflightMacOsLimaUserV2Integration } from "./preflight.ts"; +import { MacOsKeychainSecretProvider } from "../../../secrets/macos-keychain.ts"; +import { StaticSecretProviderRegistry } from "../../../secrets/registry.ts"; const JsonObjectSchema = z.record(z.string(), z.unknown()); @@ -39,6 +41,9 @@ export function createBundle( return { network: new MacOsLimaUserV2NetworkProvider(config, log), vm: new LimaVmProvider(config, log), + secrets: new StaticSecretProviderRegistry([ + new MacOsKeychainSecretProvider(), + ]), }; } diff --git a/src/rootcell/providers/factory.ts b/src/rootcell/providers/factory.ts index e26d3e5..8e6eef7 100644 --- a/src/rootcell/providers/factory.ts +++ b/src/rootcell/providers/factory.ts @@ -2,6 +2,8 @@ import type { RootcellConfig } from "../types.ts"; import type { ProviderBundle } from "./types.ts"; import { LimaVmProvider } from "./lima.ts"; import { MacOsLimaUserV2NetworkProvider, type LimaUserV2NetworkAttachment } from "./macos-lima-user-v2-network.ts"; +import { MacOsKeychainSecretProvider } from "../secrets/macos-keychain.ts"; +import { StaticSecretProviderRegistry } from "../secrets/registry.ts"; export function createProviderBundle( config: RootcellConfig, @@ -10,5 +12,8 @@ export function createProviderBundle( return { network: new MacOsLimaUserV2NetworkProvider(config, log), vm: new LimaVmProvider(config, log), + secrets: new StaticSecretProviderRegistry([ + new MacOsKeychainSecretProvider(), + ]), }; } diff --git a/src/rootcell/providers/types.ts b/src/rootcell/providers/types.ts index f3af33f..81ee747 100644 --- a/src/rootcell/providers/types.ts +++ b/src/rootcell/providers/types.ts @@ -1,4 +1,5 @@ import type { CommandResult, InheritedCommandResult } from "../types.ts"; +import type { SecretProviderRegistry } from "../secrets/types.ts"; export type VmRole = "agent" | "firewall"; @@ -77,4 +78,5 @@ export interface VmProvider { readonly network: NetworkProvider; readonly vm: VmProvider; + readonly secrets: SecretProviderRegistry; } diff --git a/src/rootcell/rootcell.test.ts b/src/rootcell/rootcell.test.ts index 1caed91..9234567 100644 --- a/src/rootcell/rootcell.test.ts +++ b/src/rootcell/rootcell.test.ts @@ -33,10 +33,12 @@ import { ParsedRootcellRunArgsSchema, RootcellConfigSchema, RootcellInstanceSchema, - SecretMappingSchema, type ParsedRootcellRunArgs, type RootcellInstance, } from "./types.ts"; +import { MacOsKeychainSecretProvider } from "./secrets/macos-keychain.ts"; +import { StaticSecretProviderRegistry } from "./secrets/registry.ts"; +import { SecretEnvMappingSchema } from "./secrets/types.ts"; const EmptyStringArraySchema = z.array(z.string()).length(0); const DefaultSpyOptionsSchema = z.object({ @@ -207,14 +209,37 @@ describe("environment parsing", () => { }); test("validates secret mappings", () => { - const mappings = parseSecretMappings("AWS_BEARER_TOKEN_BEDROCK=aws-bedrock-api-key\n"); - expect(mappings).toEqual(expect.schemaMatching(z.array(SecretMappingSchema))); + const mappings = parseSecretMappings([ + "AWS_BEARER_TOKEN_BEDROCK=macos-keychain:aws-bedrock-api-key", + "AWS_SECRET_ACCESS_KEY=aws-prod:arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/key", + "ONEPASSWORD_TOKEN=1password:op://Private/token/password", + "", + ].join("\n")); + expect(mappings).toEqual(expect.schemaMatching(z.array(SecretEnvMappingSchema))); expect(mappings).toEqual([ - { envName: "AWS_BEARER_TOKEN_BEDROCK", service: "aws-bedrock-api-key" }, + { + envName: "AWS_BEARER_TOKEN_BEDROCK", + secret: { providerId: "macos-keychain", reference: "aws-bedrock-api-key" }, + }, + { + envName: "AWS_SECRET_ACCESS_KEY", + secret: { + providerId: "aws-prod", + reference: "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/key", + }, + }, + { + envName: "ONEPASSWORD_TOKEN", + secret: { providerId: "1password", reference: "op://Private/token/password" }, + }, ]); - expect(() => parseSecretMappings("1BAD=service\n")).toThrow("invalid secret environment variable name"); + expect(() => parseSecretMappings("1BAD=macos-keychain:service\n")).toThrow("invalid secret environment variable name"); expect(() => parseSecretMappings("BAD\n")).toThrow("invalid secret entry"); - expect(() => parseSecretMappings("BAD=\n")).toThrow("empty Keychain service name"); + expect(() => parseSecretMappings("BAD=\n")).toThrow("empty secret reference"); + expect(() => parseSecretMappings("BAD=service\n")).toThrow("must include a provider id"); + expect(() => parseSecretMappings("BAD=:service\n")).toThrow("empty secret provider id"); + expect(() => parseSecretMappings("BAD=bad/id:service\n")).toThrow("invalid secret provider id"); + expect(() => parseSecretMappings("BAD=macos-keychain:\n")).toThrow("empty secret reference"); }); test("builds config from instance state", () => { @@ -277,11 +302,96 @@ describe("host tool resolution", () => { }); }); +describe("secret providers", () => { + test("registry routes provider-qualified references", async () => { + const calls: string[] = []; + const registry = new StaticSecretProviderRegistry([ + { + id: "macos-keychain", + read: (reference) => { + calls.push(`macos-keychain:${reference}`); + return Promise.resolve(`mac:${reference}`); + }, + }, + { + id: "aws-prod", + read: (reference) => { + calls.push(`aws-prod:${reference}`); + return Promise.resolve(`prod:${reference}`); + }, + }, + { + id: "aws-dev", + read: (reference) => { + calls.push(`aws-dev:${reference}`); + return Promise.resolve(`dev:${reference}`); + }, + }, + ]); + + await expect(registry.read({ providerId: "macos-keychain", reference: "service" })).resolves.toBe("mac:service"); + await expect(registry.read({ providerId: "aws-prod", reference: "secret/name" })).resolves.toBe("prod:secret/name"); + await expect(registry.read({ providerId: "aws-dev", reference: "secret/name" })).resolves.toBe("dev:secret/name"); + expect(calls).toEqual([ + "macos-keychain:service", + "aws-prod:secret/name", + "aws-dev:secret/name", + ]); + }); + + test("registry rejects unknown or duplicate secret providers", async () => { + const registry = new StaticSecretProviderRegistry([ + { id: "macos-keychain", read: () => Promise.resolve("secret") }, + ]); + + await expect(registry.read({ providerId: "missing", reference: "do-not-print" })).rejects.toThrow("unknown secret provider 'missing'"); + try { + await registry.read({ providerId: "missing", reference: "do-not-print" }); + throw new Error("expected secret lookup to fail"); + } catch (error) { + expect(error instanceof Error ? error.message : String(error)).not.toContain("do-not-print"); + } + expect(() => new StaticSecretProviderRegistry([ + { id: "aws-prod", read: () => Promise.resolve("one") }, + { id: "aws-prod", read: () => Promise.resolve("two") }, + ])).toThrow("duplicate secret provider id"); + }); + + test("macOS Keychain provider reads generic passwords", async () => { + const calls: { command: string; args: readonly string[]; allowFailure: boolean | undefined }[] = []; + const provider = new MacOsKeychainSecretProvider("macos-keychain", (command, args, options) => { + calls.push({ command, args, allowFailure: options?.allowFailure }); + return { status: 0, stdout: "secret-value\n", stderr: "" }; + }); + + await expect(provider.read("aws-bedrock-api-key")).resolves.toBe("secret-value"); + expect(calls).toEqual([ + { + command: "security", + args: ["find-generic-password", "-s", "aws-bedrock-api-key", "-w"], + allowFailure: true, + }, + ]); + }); + + test("macOS Keychain provider reports missing secrets with add guidance", async () => { + const provider = new MacOsKeychainSecretProvider("macos-keychain", () => ({ + status: 44, + stdout: "", + stderr: "not found", + })); + + await expect(provider.read("anthropic-api-key")).rejects.toThrow("macOS Keychain secret not found"); + await expect(provider.read("anthropic-api-key")).rejects.toThrow("security add-generic-password"); + }); +}); + describe("VM and network providers", () => { test("factory defaults to Lima providers", () => { const providers = createProviderBundle(buildConfig("/repo", {}, fakeInstance("dev")), ignoreLog); expect(providers.network.id).toBe("macos-lima-user-v2"); expect(providers.vm.id).toBe("lima"); + expect(providers.secrets.ids).toEqual(["macos-keychain"]); }); test("macOS Lima user-v2 provider exposes egress firewall and private-only agent attachments", () => { @@ -864,7 +974,7 @@ function makeInstanceRepo(): string { const repo = mkdtempSync(join(tmpdir(), "rootcell-instance-test-")); mkdirSync(join(repo, "proxy"), { recursive: true }); writeFileSync(join(repo, ".env.defaults"), "AWS_REGION=us-east-1\n", "utf8"); - writeFileSync(join(repo, "secrets.env.defaults"), "AWS_BEARER_TOKEN_BEDROCK=aws-bedrock-api-key\n", "utf8"); + writeFileSync(join(repo, "secrets.env.defaults"), "AWS_BEARER_TOKEN_BEDROCK=macos-keychain:aws-bedrock-api-key\n", "utf8"); for (const file of ["allowed-https.txt", "allowed-ssh.txt", "allowed-dns.txt"]) { writeFileSync(join(repo, "proxy", `${file}.defaults`), "\n", "utf8"); } diff --git a/src/rootcell/rootcell.ts b/src/rootcell/rootcell.ts index f439214..5430a30 100644 --- a/src/rootcell/rootcell.ts +++ b/src/rootcell/rootcell.ts @@ -184,7 +184,7 @@ export class RootcellApp { return 0; } - const injectedSecretEnv = this.readKeychainSecrets(); + const injectedSecretEnv = await this.readSecretEnv(); const command = rest.length === 0 ? ["bash", "-l"] : [...rest]; return await this.providers.vm.execInteractive(this.config.agentVm, command, { allowFailure: true, @@ -767,36 +767,22 @@ nix run .#home-manager -- switch --flake .#${this.config.guestUser} } } - private readKeychainSecrets(): string[] { + private async readSecretEnv(): Promise { const path = this.config.secretsPath; if (!existsSync(path)) { return []; } - let mappings; - try { - mappings = parseSecretMappings(readFileSync(path, "utf8")); - } catch (error) { - log(messageFromUnknown(error)); - process.exit(1); - } + const mappings = parseSecretMappings(readFileSync(path, "utf8")); const injected: string[] = []; for (const mapping of mappings) { - const value = runCapture("security", ["find-generic-password", "-s", mapping.service, "-w"], { - allowFailure: true, - }); - if (value.status !== 0) { - const serviceArg = shellQuote(mapping.service); - process.stderr.write(`rootcell: Keychain secret not found for ${mapping.envName}. - -Add it with: - security add-generic-password -a "$USER" -s ${serviceArg} -w "" - -Then re-run. -`); - process.exit(1); + let value; + try { + value = await this.providers.secrets.read(mapping.secret); + } catch (error) { + throw new Error(`secret lookup failed for ${mapping.envName} (${mapping.secret.providerId}): ${messageFromUnknown(error)}`, { cause: error }); } - injected.push(`${mapping.envName}=${value.stdout.replace(/\r?\n$/, "")}`); + injected.push(`${mapping.envName}=${value}`); } return injected; } diff --git a/src/rootcell/secrets/macos-keychain.ts b/src/rootcell/secrets/macos-keychain.ts new file mode 100644 index 0000000..4fdd3d8 --- /dev/null +++ b/src/rootcell/secrets/macos-keychain.ts @@ -0,0 +1,39 @@ +import { runCapture, type RunOptions } from "../process.ts"; +import type { CommandResult } from "../types.ts"; +import type { SecretProvider } from "./types.ts"; + +export type SecretCommandRunner = ( + command: string, + args: readonly string[], + options?: RunOptions, +) => CommandResult; + +export class MacOsKeychainSecretProvider implements SecretProvider { + constructor( + readonly id = "macos-keychain", + private readonly capture: SecretCommandRunner = runCapture, + ) {} + + read(reference: string): Promise { + const value = this.capture("security", ["find-generic-password", "-s", reference, "-w"], { + allowFailure: true, + }); + if (value.status !== 0) { + const serviceArg = shellQuote(reference); + return Promise.reject(new Error(`macOS Keychain secret not found. + +Add it with: + security add-generic-password -a "$USER" -s ${serviceArg} -w "" + +Then re-run.`)); + } + return Promise.resolve(value.stdout.replace(/\r?\n$/, "")); + } +} + +function shellQuote(value: string): string { + if (/^[A-Za-z0-9_./:=@%+-]+$/.test(value)) { + return value; + } + return `'${value.replaceAll("'", "'\\''")}'`; +} diff --git a/src/rootcell/secrets/registry.ts b/src/rootcell/secrets/registry.ts new file mode 100644 index 0000000..1b71604 --- /dev/null +++ b/src/rootcell/secrets/registry.ts @@ -0,0 +1,42 @@ +import { parseSchema } from "../schema.ts"; +import { + SecretProviderIdSchema, + type SecretProvider, + type SecretProviderRegistry, + type SecretReference, +} from "./types.ts"; + +export class StaticSecretProviderRegistry implements SecretProviderRegistry { + private readonly providers: ReadonlyMap; + + constructor(providers: readonly SecretProvider[]) { + const entries: [string, SecretProvider][] = []; + const seen = new Set(); + for (const provider of providers) { + const id = parseSchema(SecretProviderIdSchema, provider.id, "invalid secret provider id"); + if (seen.has(id)) { + throw new Error(`duplicate secret provider id '${id}'`); + } + seen.add(id); + entries.push([id, provider]); + } + this.providers = new Map(entries); + } + + get ids(): readonly string[] { + return [...this.providers.keys()]; + } + + async read(secret: SecretReference): Promise { + const provider = this.providers.get(secret.providerId); + if (provider === undefined) { + throw new Error(`unknown secret provider '${secret.providerId}' in secrets.env; configured providers: ${this.providerList()}`); + } + return await provider.read(secret.reference); + } + + private providerList(): string { + const ids = this.ids; + return ids.length === 0 ? "(none)" : ids.join(", "); + } +} diff --git a/src/rootcell/secrets/types.ts b/src/rootcell/secrets/types.ts new file mode 100644 index 0000000..fef90ff --- /dev/null +++ b/src/rootcell/secrets/types.ts @@ -0,0 +1,38 @@ +import { z } from "zod"; +import { + EnvironmentVariableNameSchema, + NonEmptyStringSchema, +} from "../schema.ts"; + +export const SecretProviderIdSchema = z.string() + .regex(/^[A-Za-z0-9][A-Za-z0-9_.-]*$/, "must be a valid secret provider id"); + +export const SecretReferenceSchema = z.object({ + providerId: SecretProviderIdSchema, + reference: NonEmptyStringSchema, +}).strict(); + +export type SecretReference = Readonly>; + +export const SecretEnvMappingSchema = z.object({ + envName: EnvironmentVariableNameSchema, + secret: SecretReferenceSchema, +}).strict(); + +type SecretEnvMappingOutput = z.infer; + +export type SecretEnvMapping = Readonly< + Omit & { + readonly secret: SecretReference; + } +>; + +export interface SecretProvider { + readonly id: string; + read(reference: string): Promise; +} + +export interface SecretProviderRegistry { + readonly ids: readonly string[]; + read(secret: SecretReference): Promise; +} diff --git a/src/rootcell/types.ts b/src/rootcell/types.ts index 38f47ca..f12dc9e 100644 --- a/src/rootcell/types.ts +++ b/src/rootcell/types.ts @@ -2,7 +2,6 @@ import type { SpawnSyncReturns } from "node:child_process"; import { z } from "zod"; import { isRootcellSubcommand, type RootcellSubcommand } from "./metadata.ts"; import { - EnvironmentVariableNameSchema, NonEmptyStringSchema, NonNegativeSafeIntegerSchema, } from "./schema.ts"; @@ -97,13 +96,6 @@ export const InstanceStateSchema = z.object({ export type InstanceState = Readonly>; -export const SecretMappingSchema = z.object({ - envName: EnvironmentVariableNameSchema, - service: NonEmptyStringSchema, -}); - -export type SecretMapping = Readonly>; - export const RootcellInstanceSchema = z.object({ name: NonEmptyStringSchema, dir: NonEmptyStringSchema,