@ankhorage/infra 7.0.0 → 7.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cli/commands/destroy.d.ts.map +1 -1
  3. package/dist/cli/commands/destroy.js +1 -0
  4. package/dist/cli/commands/destroy.js.map +1 -1
  5. package/dist/cli/utils/prepareInfraCommandOperationAsync.d.ts.map +1 -1
  6. package/dist/cli/utils/prepareInfraCommandOperationAsync.js +4 -1
  7. package/dist/cli/utils/prepareInfraCommandOperationAsync.js.map +1 -1
  8. package/dist/features/environment-lifecycle/adapters/outbound/createProjectInfraCredentialPort.d.ts +11 -0
  9. package/dist/features/environment-lifecycle/adapters/outbound/createProjectInfraCredentialPort.d.ts.map +1 -0
  10. package/dist/features/environment-lifecycle/adapters/outbound/createProjectInfraCredentialPort.js +125 -0
  11. package/dist/features/environment-lifecycle/adapters/outbound/createProjectInfraCredentialPort.js.map +1 -0
  12. package/dist/features/environment-lifecycle/adapters/outbound/removeStoredInfraCredentialsAsync.d.ts +4 -0
  13. package/dist/features/environment-lifecycle/adapters/outbound/removeStoredInfraCredentialsAsync.d.ts.map +1 -0
  14. package/dist/features/environment-lifecycle/adapters/outbound/removeStoredInfraCredentialsAsync.js +9 -0
  15. package/dist/features/environment-lifecycle/adapters/outbound/removeStoredInfraCredentialsAsync.js.map +1 -0
  16. package/dist/features/environment-lifecycle/composition/createInfraCommandServices.d.ts.map +1 -1
  17. package/dist/features/environment-lifecycle/composition/createInfraCommandServices.js +9 -3
  18. package/dist/features/environment-lifecycle/composition/createInfraCommandServices.js.map +1 -1
  19. package/dist/features/environment-lifecycle/composition/createProjectInfraLifecycle.d.ts.map +1 -1
  20. package/dist/features/environment-lifecycle/composition/createProjectInfraLifecycle.js +28 -2
  21. package/dist/features/environment-lifecycle/composition/createProjectInfraLifecycle.js.map +1 -1
  22. package/dist/features/environment-lifecycle/utils/resolveInfraEnvironmentStateDirectory.d.ts +4 -0
  23. package/dist/features/environment-lifecycle/utils/resolveInfraEnvironmentStateDirectory.d.ts.map +1 -0
  24. package/dist/features/environment-lifecycle/utils/resolveInfraEnvironmentStateDirectory.js +11 -0
  25. package/dist/features/environment-lifecycle/utils/resolveInfraEnvironmentStateDirectory.js.map +1 -0
  26. package/dist/features/environment-lifecycle/utils/resolveInfraStatePath.d.ts +1 -1
  27. package/dist/features/environment-lifecycle/utils/resolveInfraStatePath.d.ts.map +1 -1
  28. package/dist/features/environment-lifecycle/utils/resolveInfraStatePath.js +2 -6
  29. package/dist/features/environment-lifecycle/utils/resolveInfraStatePath.js.map +1 -1
  30. package/dist/project/index.d.ts +1 -0
  31. package/dist/project/index.d.ts.map +1 -1
  32. package/dist/project/index.js +1 -0
  33. package/dist/project/index.js.map +1 -1
  34. package/dist/types/infraCli.d.ts +7 -1
  35. package/dist/types/infraCli.d.ts.map +1 -1
  36. package/dist/types/infraCli.js.map +1 -1
  37. package/package.json +4 -3
  38. package/src/cli/commands/destroy.ts +1 -0
  39. package/src/cli/utils/prepareInfraCommandOperationAsync.ts +4 -1
  40. package/src/features/environment-lifecycle/adapters/outbound/createProjectInfraCredentialPort.ts +182 -0
  41. package/src/features/environment-lifecycle/adapters/outbound/removeStoredInfraCredentialsAsync.ts +18 -0
  42. package/src/features/environment-lifecycle/composition/createInfraCommandServices.ts +9 -3
  43. package/src/features/environment-lifecycle/composition/createProjectInfraLifecycle.ts +33 -4
  44. package/src/features/environment-lifecycle/utils/resolveInfraEnvironmentStateDirectory.ts +16 -0
  45. package/src/features/environment-lifecycle/utils/resolveInfraStatePath.ts +3 -6
  46. package/src/hetznerK3sProduction.test.ts +29 -3
  47. package/src/hetznerK3sSupabaseR2.test.ts +33 -4
  48. package/src/localDockerComposeProviders.integration.test.ts +90 -51
  49. package/src/localK3sSupabase.test.ts +85 -27
  50. package/src/localMinikubeSupabase.integration.test.ts +175 -122
  51. package/src/localMinikubeSupabaseRecovery.integration.test.ts +37 -10
  52. package/src/project/index.test.ts +3 -1
  53. package/src/project/index.ts +1 -0
  54. package/src/projectCredentialLifecycle.test.ts +161 -0
  55. package/src/projectCredentialStore.test.ts +151 -0
  56. package/src/types/infraCli.ts +11 -1
@@ -2,6 +2,7 @@ import type { AnkhCapabilityId } from '@ankhorage/contracts/cli';
2
2
  import type { AppEnvironmentId } from '@ankhorage/contracts/environments';
3
3
  import type { InfraEnvironmentSpec } from '@ankhorage/contracts/infra';
4
4
  import type { readStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/readStoredInfraStateAsync.js';
5
+ import type { removeStoredInfraCredentialsAsync } from '../features/environment-lifecycle/adapters/outbound/removeStoredInfraCredentialsAsync.js';
5
6
  import type { removeStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/removeStoredInfraStateAsync.js';
6
7
  import type { writeInfraGeneratedArtifactsAsync } from '../features/environment-lifecycle/adapters/outbound/writeInfraGeneratedArtifactsAsync.js';
7
8
  import type { writeStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/writeStoredInfraStateAsync.js';
@@ -60,6 +61,10 @@ export interface InfraLifecycleOperations {
60
61
  readonly down: typeof downInfraEnvironmentAsync;
61
62
  readonly destroy: typeof destroyInfraEnvironmentAsync;
62
63
  }
64
+ interface InfraDependencyScope {
65
+ readonly projectPath: string;
66
+ readonly environment: AppEnvironmentId;
67
+ }
63
68
  export interface InfraCommandServices {
64
69
  readonly resolveProject: (options: {
65
70
  readonly cwd: string;
@@ -68,9 +73,10 @@ export interface InfraCommandServices {
68
73
  readonly readState: typeof readStoredInfraStateAsync;
69
74
  readonly writeState: typeof writeStoredInfraStateAsync;
70
75
  readonly removeState: typeof removeStoredInfraStateAsync;
76
+ readonly removeCredentials: typeof removeStoredInfraCredentialsAsync;
71
77
  readonly writeArtifacts: typeof writeInfraGeneratedArtifactsAsync;
72
78
  readonly operations: InfraLifecycleOperations;
73
- readonly createDependencies: (context: InfraCommandContext) => InfraOrchestrationDependencies;
79
+ readonly createDependencies: (context: InfraCommandContext, scope: InfraDependencyScope) => InfraOrchestrationDependencies;
74
80
  }
75
81
  export interface RunInfraCommandOptions {
76
82
  readonly services?: Partial<InfraCommandServices>;
@@ -1 +1 @@
1
- {"version":3,"file":"infraCli.d.ts","sourceRoot":"","sources":["../../src/types/infraCli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"infraCli.d.ts","sourceRoot":"","sources":["../../src/types/infraCli.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"infraCli.js","sourceRoot":"","sources":["../../src/types/infraCli.ts"],"names":[],"mappings":"","sourcesContent":["import type { AnkhCapabilityId } from '@ankhorage/contracts/cli';\nimport type { AppEnvironmentId } from '@ankhorage/contracts/environments';\nimport type { InfraEnvironmentSpec } from '@ankhorage/contracts/infra';\n\nimport type { readStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/readStoredInfraStateAsync.js';\nimport type { removeStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/removeStoredInfraStateAsync.js';\nimport type { writeInfraGeneratedArtifactsAsync } from '../features/environment-lifecycle/adapters/outbound/writeInfraGeneratedArtifactsAsync.js';\nimport type { writeStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/writeStoredInfraStateAsync.js';\nimport type { destroyInfraEnvironmentAsync } from '../features/environment-lifecycle/application/use-cases/destroyInfraEnvironmentAsync.js';\nimport type { downInfraEnvironmentAsync } from '../features/environment-lifecycle/application/use-cases/downInfraEnvironmentAsync.js';\nimport type { generateInfraEnvironmentAsync } from '../features/environment-lifecycle/application/use-cases/generateInfraEnvironmentAsync.js';\nimport type { getInfraEnvironmentOutputs } from '../features/environment-lifecycle/application/use-cases/getInfraEnvironmentOutputs.js';\nimport type { planInfraEnvironmentAsync } from '../features/environment-lifecycle/application/use-cases/planInfraEnvironmentAsync.js';\nimport type { statusInfraEnvironmentAsync } from '../features/environment-lifecycle/application/use-cases/statusInfraEnvironmentAsync.js';\nimport type { upInfraEnvironmentAsync } from '../features/environment-lifecycle/application/use-cases/upInfraEnvironmentAsync.js';\nimport type { validateInfraEnvironmentAsync } from '../features/environment-lifecycle/application/use-cases/validateInfraEnvironmentAsync.js';\nimport type {\n InfraOperationRequest,\n InfraOrchestrationDependencies,\n InfraStoredState,\n} from './infraOrchestration.js';\nimport type { ResolvedInfraProject } from './infraProject.js';\n\ntype InfraCommandName =\n 'validate' | 'plan' | 'generate' | 'up' | 'status' | 'outputs' | 'down' | 'destroy';\n\nexport interface InfraCommandContext {\n readonly cwd: string;\n readonly env: Readonly<Record<string, string | undefined>>;\n readonly version: string;\n writeStdout(text: string): void;\n writeStderr(text: string): void;\n}\n\nexport interface InfraCommandRunResult {\n readonly exitCode: number;\n}\n\nexport interface ParsedInfraArguments {\n readonly projectId?: string;\n readonly environment?: AppEnvironmentId;\n readonly format: 'human' | 'json' | 'env';\n readonly confirmation?: string;\n readonly deleteResources: readonly string[];\n}\n\nexport interface InfraCommandRunRequest {\n readonly context: InfraCommandContext;\n readonly arguments: ParsedInfraArguments;\n}\n\ntype InfraCommandImplementation = (\n request: InfraCommandRunRequest,\n services: InfraCommandServices,\n) => Promise<InfraCommandRunResult>;\n\nexport interface InfraCommandDefinition {\n readonly capability: AnkhCapabilityId;\n readonly path: readonly [InfraCommandName];\n readonly standaloneName: InfraCommandName;\n readonly summary: string;\n readonly run: InfraCommandImplementation;\n}\n\nexport interface InfraCommandInvocation {\n readonly argv: readonly string[];\n readonly command: InfraCommandDefinition;\n readonly context: InfraCommandContext;\n}\n\nexport interface InfraLifecycleOperations {\n readonly validate: typeof validateInfraEnvironmentAsync;\n readonly plan: typeof planInfraEnvironmentAsync;\n readonly generate: typeof generateInfraEnvironmentAsync;\n readonly up: typeof upInfraEnvironmentAsync;\n readonly status: typeof statusInfraEnvironmentAsync;\n readonly outputs: typeof getInfraEnvironmentOutputs;\n readonly down: typeof downInfraEnvironmentAsync;\n readonly destroy: typeof destroyInfraEnvironmentAsync;\n}\n\nexport interface InfraCommandServices {\n readonly resolveProject: (options: {\n readonly cwd: string;\n readonly projectId?: string;\n }) => Promise<ResolvedInfraProject>;\n readonly readState: typeof readStoredInfraStateAsync;\n readonly writeState: typeof writeStoredInfraStateAsync;\n readonly removeState: typeof removeStoredInfraStateAsync;\n readonly writeArtifacts: typeof writeInfraGeneratedArtifactsAsync;\n readonly operations: InfraLifecycleOperations;\n readonly createDependencies: (context: InfraCommandContext) => InfraOrchestrationDependencies;\n}\n\nexport interface RunInfraCommandOptions {\n readonly services?: Partial<InfraCommandServices>;\n}\n\nexport interface CreateInfraRuntimeProviderOptions {\n readonly runCommandImpl?: RunInfraCommandImpl;\n readonly services?: Partial<InfraCommandServices>;\n}\n\nexport type RunInfraCommandImpl = (\n request: InfraCommandInvocation,\n options?: RunInfraCommandOptions,\n) => Promise<InfraCommandRunResult>;\n\nexport interface PreparedInfraCommandOperation {\n readonly project: ResolvedInfraProject;\n readonly state: InfraStoredState | null;\n readonly desired: InfraEnvironmentSpec;\n readonly operation: InfraOperationRequest;\n readonly dependencies: InfraOrchestrationDependencies;\n}\n"]}
1
+ {"version":3,"file":"infraCli.js","sourceRoot":"","sources":["../../src/types/infraCli.ts"],"names":[],"mappings":"","sourcesContent":["import type { AnkhCapabilityId } from '@ankhorage/contracts/cli';\nimport type { AppEnvironmentId } from '@ankhorage/contracts/environments';\nimport type { InfraEnvironmentSpec } from '@ankhorage/contracts/infra';\n\nimport type { readStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/readStoredInfraStateAsync.js';\nimport type { removeStoredInfraCredentialsAsync } from '../features/environment-lifecycle/adapters/outbound/removeStoredInfraCredentialsAsync.js';\nimport type { removeStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/removeStoredInfraStateAsync.js';\nimport type { writeInfraGeneratedArtifactsAsync } from '../features/environment-lifecycle/adapters/outbound/writeInfraGeneratedArtifactsAsync.js';\nimport type { writeStoredInfraStateAsync } from '../features/environment-lifecycle/adapters/outbound/writeStoredInfraStateAsync.js';\nimport type { destroyInfraEnvironmentAsync } from '../features/environment-lifecycle/application/use-cases/destroyInfraEnvironmentAsync.js';\nimport type { downInfraEnvironmentAsync } from '../features/environment-lifecycle/application/use-cases/downInfraEnvironmentAsync.js';\nimport type { generateInfraEnvironmentAsync } from '../features/environment-lifecycle/application/use-cases/generateInfraEnvironmentAsync.js';\nimport type { getInfraEnvironmentOutputs } from '../features/environment-lifecycle/application/use-cases/getInfraEnvironmentOutputs.js';\nimport type { planInfraEnvironmentAsync } from '../features/environment-lifecycle/application/use-cases/planInfraEnvironmentAsync.js';\nimport type { statusInfraEnvironmentAsync } from '../features/environment-lifecycle/application/use-cases/statusInfraEnvironmentAsync.js';\nimport type { upInfraEnvironmentAsync } from '../features/environment-lifecycle/application/use-cases/upInfraEnvironmentAsync.js';\nimport type { validateInfraEnvironmentAsync } from '../features/environment-lifecycle/application/use-cases/validateInfraEnvironmentAsync.js';\nimport type {\n InfraOperationRequest,\n InfraOrchestrationDependencies,\n InfraStoredState,\n} from './infraOrchestration.js';\nimport type { ResolvedInfraProject } from './infraProject.js';\n\ntype InfraCommandName =\n 'validate' | 'plan' | 'generate' | 'up' | 'status' | 'outputs' | 'down' | 'destroy';\n\nexport interface InfraCommandContext {\n readonly cwd: string;\n readonly env: Readonly<Record<string, string | undefined>>;\n readonly version: string;\n writeStdout(text: string): void;\n writeStderr(text: string): void;\n}\n\nexport interface InfraCommandRunResult {\n readonly exitCode: number;\n}\n\nexport interface ParsedInfraArguments {\n readonly projectId?: string;\n readonly environment?: AppEnvironmentId;\n readonly format: 'human' | 'json' | 'env';\n readonly confirmation?: string;\n readonly deleteResources: readonly string[];\n}\n\nexport interface InfraCommandRunRequest {\n readonly context: InfraCommandContext;\n readonly arguments: ParsedInfraArguments;\n}\n\ntype InfraCommandImplementation = (\n request: InfraCommandRunRequest,\n services: InfraCommandServices,\n) => Promise<InfraCommandRunResult>;\n\nexport interface InfraCommandDefinition {\n readonly capability: AnkhCapabilityId;\n readonly path: readonly [InfraCommandName];\n readonly standaloneName: InfraCommandName;\n readonly summary: string;\n readonly run: InfraCommandImplementation;\n}\n\nexport interface InfraCommandInvocation {\n readonly argv: readonly string[];\n readonly command: InfraCommandDefinition;\n readonly context: InfraCommandContext;\n}\n\nexport interface InfraLifecycleOperations {\n readonly validate: typeof validateInfraEnvironmentAsync;\n readonly plan: typeof planInfraEnvironmentAsync;\n readonly generate: typeof generateInfraEnvironmentAsync;\n readonly up: typeof upInfraEnvironmentAsync;\n readonly status: typeof statusInfraEnvironmentAsync;\n readonly outputs: typeof getInfraEnvironmentOutputs;\n readonly down: typeof downInfraEnvironmentAsync;\n readonly destroy: typeof destroyInfraEnvironmentAsync;\n}\n\ninterface InfraDependencyScope {\n readonly projectPath: string;\n readonly environment: AppEnvironmentId;\n}\n\nexport interface InfraCommandServices {\n readonly resolveProject: (options: {\n readonly cwd: string;\n readonly projectId?: string;\n }) => Promise<ResolvedInfraProject>;\n readonly readState: typeof readStoredInfraStateAsync;\n readonly writeState: typeof writeStoredInfraStateAsync;\n readonly removeState: typeof removeStoredInfraStateAsync;\n readonly removeCredentials: typeof removeStoredInfraCredentialsAsync;\n readonly writeArtifacts: typeof writeInfraGeneratedArtifactsAsync;\n readonly operations: InfraLifecycleOperations;\n readonly createDependencies: (\n context: InfraCommandContext,\n scope: InfraDependencyScope,\n ) => InfraOrchestrationDependencies;\n}\n\nexport interface RunInfraCommandOptions {\n readonly services?: Partial<InfraCommandServices>;\n}\n\nexport interface CreateInfraRuntimeProviderOptions {\n readonly runCommandImpl?: RunInfraCommandImpl;\n readonly services?: Partial<InfraCommandServices>;\n}\n\nexport type RunInfraCommandImpl = (\n request: InfraCommandInvocation,\n options?: RunInfraCommandOptions,\n) => Promise<InfraCommandRunResult>;\n\nexport interface PreparedInfraCommandOperation {\n readonly project: ResolvedInfraProject;\n readonly state: InfraStoredState | null;\n readonly desired: InfraEnvironmentSpec;\n readonly operation: InfraOperationRequest;\n readonly dependencies: InfraOrchestrationDependencies;\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/infra",
3
3
  "type": "module",
4
- "version": "7.0.0",
4
+ "version": "7.1.1",
5
5
  "description": "Project-aware infrastructure generation library and executable Ankh provider for Ankhorage.",
6
6
  "homepage": "https://github.com/ankhorage/infra#readme",
7
7
  "bugs": {
@@ -35,7 +35,8 @@
35
35
  "openapi"
36
36
  ],
37
37
  "dependencies": {
38
- "@ankhorage/contracts": "^21.0.0"
38
+ "@ankhorage/contracts": "^21.0.0",
39
+ "@ankhorage/utility": "^1.2.0"
39
40
  },
40
41
  "files": [
41
42
  "dist",
@@ -120,7 +121,7 @@
120
121
  "@ankhorage/minikube": "^0.4.4",
121
122
  "@ankhorage/paradox": "^0.1.24",
122
123
  "@ankhorage/r2": "^0.2.0",
123
- "@ankhorage/supabase": "^0.5.9",
124
+ "@ankhorage/supabase": "^0.5.10",
124
125
  "@ankhorage/supabase-vault": "^0.3.1",
125
126
  "@types/bun": "^1.4.1",
126
127
  "@types/node": "^25.6.0",
@@ -35,6 +35,7 @@ export const destroy = {
35
35
  await services.writeArtifacts(operation.project.projectPath, [], operation.state?.ledger);
36
36
  if (result.value.ledger === null) {
37
37
  await services.removeState(operation.project.projectPath, environment);
38
+ await services.removeCredentials(operation.project.projectPath, environment);
38
39
  } else {
39
40
  await services.writeState(operation.project.projectPath, {
40
41
  schemaVersion: 1,
@@ -37,7 +37,10 @@ export async function prepareInfraCommandOperationAsync(
37
37
  ...(request.arguments.environment === undefined ? {} : { environment }),
38
38
  ...(state === null ? {} : { previous: state.ledger, previousDesired: state.desired }),
39
39
  },
40
- dependencies: services.createDependencies(request.context),
40
+ dependencies: services.createDependencies(request.context, {
41
+ projectPath: project.projectPath,
42
+ environment,
43
+ }),
41
44
  };
42
45
  }
43
46
 
@@ -0,0 +1,182 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { promises as fs } from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ import type { AppEnvironmentId } from '@ankhorage/contracts/environments';
6
+ import type {
7
+ InfraControlPlaneCredentialRef,
8
+ InfraCredentialPort,
9
+ InfraResult,
10
+ } from '@ankhorage/contracts/infra';
11
+ import { isRecord, isRecordOf } from '@ankhorage/utility/object';
12
+
13
+ import { createInfraFailure } from '../../utils/createInfraFailure.js';
14
+ import { normalizeInfraEnvironmentKey } from '../../utils/normalizeInfraEnvironmentKey.js';
15
+ import { resolveInfraEnvironmentStateDirectory } from '../../utils/resolveInfraEnvironmentStateDirectory.js';
16
+ import { createEnvironmentInfraCredentialPort } from './createEnvironmentInfraCredentialPort.js';
17
+
18
+ /*** Resolve explicit environment overrides before durable project-local bootstrap credentials. */
19
+ export function createProjectInfraCredentialPort(
20
+ options: ProjectInfraCredentialPortOptions,
21
+ ): InfraCredentialPort {
22
+ const environmentPort = createEnvironmentInfraCredentialPort(options.processEnvironment);
23
+ return {
24
+ findAsync: (reference) => findProjectCredentialAsync(options, environmentPort, reference),
25
+ async resolveAsync(reference) {
26
+ const found = await findProjectCredentialAsync(options, environmentPort, reference);
27
+ if (!found.ok) return found;
28
+ if (found.value !== null) {
29
+ return { ok: true, value: found.value, diagnostics: found.diagnostics };
30
+ }
31
+ return missingCredential(reference.name);
32
+ },
33
+ async persistAsync(reference, values) {
34
+ if (!isStringRecord(values)) {
35
+ return createInfraFailure(
36
+ 'infra-control-plane-credential-invalid',
37
+ `Control-plane credential ${reference.name} must contain only string fields.`,
38
+ );
39
+ }
40
+ return writeStoredCredentialAsync(options, reference, values);
41
+ },
42
+ };
43
+ }
44
+
45
+ interface ProjectInfraCredentialPortOptions {
46
+ readonly projectPath: string;
47
+ readonly environment: AppEnvironmentId;
48
+ readonly processEnvironment: Readonly<Record<string, string | undefined>>;
49
+ }
50
+
51
+ interface StoredCredentialBundle {
52
+ readonly schemaVersion: 1;
53
+ readonly name: string;
54
+ readonly values: Readonly<Record<string, string>>;
55
+ }
56
+
57
+ /*** Resolve environment precedence before falling through to project-local state. */
58
+ async function findProjectCredentialAsync(
59
+ options: ProjectInfraCredentialPortOptions,
60
+ environmentPort: InfraCredentialPort,
61
+ reference: InfraControlPlaneCredentialRef,
62
+ ): Promise<InfraResult<Readonly<Record<string, string>> | null>> {
63
+ const overridden = await environmentPort.findAsync(reference);
64
+ if (!overridden.ok || overridden.value !== null) return overridden;
65
+ return readStoredCredentialAsync(options, reference);
66
+ }
67
+
68
+ /*** Read one exact credential bundle without enumerating or exposing sibling credentials. */
69
+ async function readStoredCredentialAsync(
70
+ options: ProjectInfraCredentialPortOptions,
71
+ reference: InfraControlPlaneCredentialRef,
72
+ ): Promise<InfraResult<Readonly<Record<string, string>> | null>> {
73
+ const credentialPath = resolveCredentialPath(options, reference.name);
74
+ try {
75
+ const parsed: unknown = JSON.parse(await fs.readFile(credentialPath, 'utf8'));
76
+ if (!isStoredCredentialBundle(parsed) || parsed.name !== reference.name) {
77
+ return invalidCredentialStore(reference.name);
78
+ }
79
+ return { ok: true, value: parsed.values, diagnostics: [] };
80
+ } catch (error) {
81
+ if (isMissingFile(error)) return { ok: true, value: null, diagnostics: [] };
82
+ if (error instanceof SyntaxError) return invalidCredentialStore(reference.name);
83
+ return createInfraFailure(
84
+ 'infra-control-plane-credential-store-read-failed',
85
+ `Control-plane credential ${reference.name} could not be read from project state.`,
86
+ );
87
+ }
88
+ }
89
+
90
+ /*** Persist one opaque bundle atomically with restrictive local-host permissions. */
91
+ async function writeStoredCredentialAsync(
92
+ options: ProjectInfraCredentialPortOptions,
93
+ reference: InfraControlPlaneCredentialRef,
94
+ values: Readonly<Record<string, string>>,
95
+ ): Promise<InfraResult<null>> {
96
+ const credentialPath = resolveCredentialPath(options, reference.name);
97
+ const directory = path.dirname(credentialPath);
98
+ const temporaryPath = `${credentialPath}.${process.pid}.${randomUUID()}.tmp`;
99
+ const stored: StoredCredentialBundle = { schemaVersion: 1, name: reference.name, values };
100
+ try {
101
+ await fs.mkdir(directory, { recursive: true, mode: 0o700 });
102
+ await restrictPermissionsAsync(directory, 0o700);
103
+ await fs.writeFile(temporaryPath, `${JSON.stringify(stored, null, 2)}\n`, {
104
+ encoding: 'utf8',
105
+ mode: 0o600,
106
+ flag: 'wx',
107
+ });
108
+ await fs.rename(temporaryPath, credentialPath);
109
+ await restrictPermissionsAsync(credentialPath, 0o600);
110
+ return { ok: true, value: null, diagnostics: [] };
111
+ } catch {
112
+ await removeTemporaryCredentialAsync(temporaryPath);
113
+ return createInfraFailure(
114
+ 'infra-control-plane-credential-store-write-failed',
115
+ `Control-plane credential ${reference.name} could not be persisted to project state.`,
116
+ );
117
+ }
118
+ }
119
+
120
+ /*** Remove an abandoned unique temporary credential file without masking the original write failure. */
121
+ async function removeTemporaryCredentialAsync(temporaryPath: string): Promise<void> {
122
+ try {
123
+ await fs.rm(temporaryPath, { force: true });
124
+ } catch {
125
+ return;
126
+ }
127
+ }
128
+
129
+ /*** Resolve one credential to an opaque hash-named file inside its project/environment scope. */
130
+ function resolveCredentialPath(options: ProjectInfraCredentialPortOptions, name: string): string {
131
+ const digest = createHash('sha256').update(name).digest('hex');
132
+ return path.join(
133
+ resolveInfraEnvironmentStateDirectory(options.projectPath, options.environment),
134
+ 'credentials',
135
+ `${digest}.json`,
136
+ );
137
+ }
138
+
139
+ /*** Apply owner-only permissions where the host platform supports POSIX file modes. */
140
+ async function restrictPermissionsAsync(targetPath: string, mode: number): Promise<void> {
141
+ if (process.platform !== 'win32') await fs.chmod(targetPath, mode);
142
+ }
143
+
144
+ /*** Create the canonical missing-credential diagnostic after every trusted source was checked. */
145
+ function missingCredential(name: string): InfraResult<never> {
146
+ const prefixedName = `ANKH_INFRA_CREDENTIAL_${normalizeInfraEnvironmentKey(name)}`;
147
+ return createInfraFailure(
148
+ 'infra-control-plane-credential-missing',
149
+ `Control-plane credential ${name} is unavailable. Set ${name} or ${prefixedName}.`,
150
+ );
151
+ }
152
+
153
+ /*** Reject malformed persisted state without exposing or replacing any stored value. */
154
+ function invalidCredentialStore(name: string): InfraResult<never> {
155
+ return createInfraFailure(
156
+ 'infra-control-plane-credential-store-invalid',
157
+ `Stored control-plane credential ${name} has an invalid project-state shape.`,
158
+ );
159
+ }
160
+
161
+ /*** Recognize the private stored credential record. */
162
+ function isStoredCredentialBundle(value: unknown): value is StoredCredentialBundle {
163
+ if (!isRecord(value)) return false;
164
+ return (
165
+ value.schemaVersion === 1 && typeof value.name === 'string' && isStringRecord(value.values)
166
+ );
167
+ }
168
+
169
+ /*** Recognize string-only opaque provider credential bundles. */
170
+ function isStringRecord(value: unknown): value is Readonly<Record<string, string>> {
171
+ return isRecordOf(value, isString);
172
+ }
173
+
174
+ /*** Narrow one unknown value to a string. */
175
+ function isString(value: unknown): value is string {
176
+ return typeof value === 'string';
177
+ }
178
+
179
+ /*** Recognize only the filesystem's absent-file failure. */
180
+ function isMissingFile(error: unknown): boolean {
181
+ return isRecord(error) && error.code === 'ENOENT';
182
+ }
@@ -0,0 +1,18 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ import type { AppEnvironmentId } from '@ankhorage/contracts/environments';
5
+
6
+ import { resolveInfraEnvironmentStateDirectory } from '../../utils/resolveInfraEnvironmentStateDirectory.js';
7
+
8
+ /*** Remove only persisted generated credential state for one explicit project environment. */
9
+ export async function removeStoredInfraCredentialsAsync(
10
+ projectPath: string,
11
+ environment: AppEnvironmentId,
12
+ ): Promise<void> {
13
+ const credentialDirectory = path.join(
14
+ resolveInfraEnvironmentStateDirectory(projectPath, environment),
15
+ 'credentials',
16
+ );
17
+ await fs.rm(credentialDirectory, { recursive: true, force: true });
18
+ }
@@ -1,8 +1,9 @@
1
1
  import type { InfraCommandServices } from '../../../types/infraCli.js';
2
- import { createEnvironmentInfraCredentialPort } from '../adapters/outbound/createEnvironmentInfraCredentialPort.js';
3
2
  import { createEnvironmentInfraSecretPort } from '../adapters/outbound/createEnvironmentInfraSecretPort.js';
4
3
  import { createNodeInfraAdapterPackageResolver } from '../adapters/outbound/createNodeInfraAdapterPackageResolver.js';
4
+ import { createProjectInfraCredentialPort } from '../adapters/outbound/createProjectInfraCredentialPort.js';
5
5
  import { readStoredInfraStateAsync } from '../adapters/outbound/readStoredInfraStateAsync.js';
6
+ import { removeStoredInfraCredentialsAsync } from '../adapters/outbound/removeStoredInfraCredentialsAsync.js';
6
7
  import { removeStoredInfraStateAsync } from '../adapters/outbound/removeStoredInfraStateAsync.js';
7
8
  import { resolveInfraProjectAsync } from '../adapters/outbound/resolveInfraProjectAsync.js';
8
9
  import { writeInfraGeneratedArtifactsAsync } from '../adapters/outbound/writeInfraGeneratedArtifactsAsync.js';
@@ -25,6 +26,7 @@ export function createInfraCommandServices(
25
26
  readState: overrides.readState ?? readStoredInfraStateAsync,
26
27
  writeState: overrides.writeState ?? writeStoredInfraStateAsync,
27
28
  removeState: overrides.removeState ?? removeStoredInfraStateAsync,
29
+ removeCredentials: overrides.removeCredentials ?? removeStoredInfraCredentialsAsync,
28
30
  writeArtifacts: overrides.writeArtifacts ?? writeInfraGeneratedArtifactsAsync,
29
31
  operations: overrides.operations ?? {
30
32
  validate: validateInfraEnvironmentAsync,
@@ -38,9 +40,13 @@ export function createInfraCommandServices(
38
40
  },
39
41
  createDependencies:
40
42
  overrides.createDependencies ??
41
- ((context) => ({
43
+ ((context, scope) => ({
42
44
  adapterResolver: createNodeInfraAdapterPackageResolver(),
43
- credentials: createEnvironmentInfraCredentialPort(context.env),
45
+ credentials: createProjectInfraCredentialPort({
46
+ projectPath: scope.projectPath,
47
+ environment: scope.environment,
48
+ processEnvironment: context.env,
49
+ }),
44
50
  secrets: createEnvironmentInfraSecretPort(context.env),
45
51
  })),
46
52
  };
@@ -1,9 +1,10 @@
1
- import type { InfraEnvironmentSpec, InfraManifest } from '@ankhorage/contracts/infra';
1
+ import type { InfraEnvironmentSpec, InfraManifest, InfraResult } from '@ankhorage/contracts/infra';
2
2
 
3
3
  import { INFRA_PACKAGE_VERSION } from '../../../constants.js';
4
4
  import type { InfraOperationRequest, InfraStoredState } from '../../../types/infraOrchestration.js';
5
5
  import type {
6
6
  CreateProjectInfraLifecycleOptions,
7
+ ProjectInfraDestroyRequest,
7
8
  ProjectInfraLifecycle,
8
9
  ProjectInfraOperationRequest,
9
10
  } from '../../../types/infraProject.js';
@@ -72,6 +73,9 @@ export function createProjectInfraLifecycle(
72
73
  async destroyAsync(request) {
73
74
  const state = await services.readState(request.projectPath, request.environment);
74
75
  if (state === null) {
76
+ const safety = validateCredentialStateDestroy(request);
77
+ if (!safety.ok) return safety;
78
+ await services.removeCredentials(request.projectPath, request.environment);
75
79
  return {
76
80
  ok: true,
77
81
  value: { environment: request.environment, ledger: null },
@@ -89,9 +93,10 @@ export function createProjectInfraLifecycle(
89
93
  );
90
94
  if (!result.ok) return result;
91
95
  await services.writeArtifacts(request.projectPath, [], prepared.state?.ledger);
92
- if (result.value.ledger === null)
96
+ if (result.value.ledger === null) {
93
97
  await services.removeState(request.projectPath, request.environment);
94
- else {
98
+ await services.removeCredentials(request.projectPath, request.environment);
99
+ } else {
95
100
  await services.writeState(request.projectPath, {
96
101
  schemaVersion: 1,
97
102
  desired: prepared.desired,
@@ -142,10 +147,34 @@ async function prepareProjectOperationAsync(
142
147
  ...(state === null ? {} : { previous: state.ledger, previousDesired: state.desired }),
143
148
  ...(request.signal === undefined ? {} : { signal: request.signal }),
144
149
  },
145
- dependencies: services.createDependencies(context),
150
+ dependencies: services.createDependencies(context, {
151
+ projectPath: request.projectPath,
152
+ environment: request.environment,
153
+ }),
146
154
  };
147
155
  }
148
156
 
157
+ /*** Require exact scope confirmation before deleting orphaned project credential state. */
158
+ function validateCredentialStateDestroy(request: ProjectInfraDestroyRequest): InfraResult<null> {
159
+ if (
160
+ request.confirmation.projectId !== request.projectId ||
161
+ request.confirmation.environment !== request.environment
162
+ ) {
163
+ return {
164
+ ok: false,
165
+ diagnostics: [
166
+ {
167
+ severity: 'error',
168
+ code: 'infra-destroy-confirmation-mismatch',
169
+ message:
170
+ 'Infra destroy confirmation must exactly match the selected project and environment.',
171
+ },
172
+ ],
173
+ };
174
+ }
175
+ return { ok: true, value: null, diagnostics: [] };
176
+ }
177
+
149
178
  /*** Read one exact environment without inventing a fallback. */
150
179
  function readEnvironment(
151
180
  manifest: InfraManifest,
@@ -0,0 +1,16 @@
1
+ import type { AppEnvironmentId } from '@ankhorage/contracts/environments';
2
+ import { APP_ENVIRONMENT_IDS } from '@ankhorage/contracts/environments';
3
+ import path from 'path';
4
+
5
+ /*** Build the canonical project-local state directory for one closed Infra environment. */
6
+ export function resolveInfraEnvironmentStateDirectory(
7
+ projectPath: string,
8
+ environment: AppEnvironmentId,
9
+ ): string {
10
+ if (!APP_ENVIRONMENT_IDS.includes(environment)) {
11
+ throw new Error(`Invalid Infra state environment: ${environment}`);
12
+ }
13
+ return path.join(projectPath, INFRA_STATE_ROOT, environment);
14
+ }
15
+
16
+ const INFRA_STATE_ROOT = '.ankh/infra';
@@ -1,12 +1,9 @@
1
- import { APP_ENVIRONMENT_IDS, type AppEnvironmentId } from '@ankhorage/contracts/environments';
1
+ import type { AppEnvironmentId } from '@ankhorage/contracts/environments';
2
2
  import path from 'path';
3
3
 
4
- const INFRA_STATE_ROOT = '.ankh/infra';
4
+ import { resolveInfraEnvironmentStateDirectory } from './resolveInfraEnvironmentStateDirectory.js';
5
5
 
6
6
  /*** Build an exact state-file path from a closed canonical environment identifier. */
7
7
  export function resolveInfraStatePath(projectPath: string, environment: AppEnvironmentId): string {
8
- if (!APP_ENVIRONMENT_IDS.includes(environment)) {
9
- throw new Error(`Invalid Infra state environment: ${environment}`);
10
- }
11
- return path.join(projectPath, INFRA_STATE_ROOT, environment, 'state.json');
8
+ return path.join(resolveInfraEnvironmentStateDirectory(projectPath, environment), 'state.json');
12
9
  }
@@ -1,4 +1,6 @@
1
1
  import type {
2
+ InfraControlPlaneCredentialRef,
3
+ InfraCredentialPort,
2
4
  InfraLedger,
3
5
  InfraManifest,
4
6
  InfraResourceIdentity,
@@ -230,9 +232,7 @@ function createDependencies(fixture: ProductionFixture): InfraOrchestrationDepen
230
232
  }
231
233
  },
232
234
  },
233
- credentials: {
234
- resolveAsync: ({ name }) => Promise.resolve(resolveCredential(name)),
235
- },
235
+ credentials: createCredentialPort(),
236
236
  secrets: {
237
237
  resolveAsync: () =>
238
238
  Promise.resolve({
@@ -245,6 +245,32 @@ function createDependencies(fixture: ProductionFixture): InfraOrchestrationDepen
245
245
  };
246
246
  }
247
247
 
248
+ function createCredentialPort(): InfraCredentialPort {
249
+ return {
250
+ findAsync: (reference) => Promise.resolve(findCredential(reference)),
251
+ resolveAsync: ({ name }) => Promise.resolve(resolveCredential(name)),
252
+ persistAsync: (reference) =>
253
+ Promise.resolve({
254
+ ok: false,
255
+ diagnostics: [
256
+ {
257
+ severity: 'error',
258
+ code: 'unexpected-credential-persist',
259
+ message: `Production fixture must not persist credential ${reference.name}.`,
260
+ },
261
+ ],
262
+ }),
263
+ };
264
+ }
265
+
266
+ function findCredential(
267
+ reference: InfraControlPlaneCredentialRef,
268
+ ): InfraResult<Readonly<Record<string, string>> | null> {
269
+ return reference.name === 'SUPABASE_BOOTSTRAP'
270
+ ? resolveCredential(reference.name)
271
+ : success(null);
272
+ }
273
+
248
274
  function resolveCredential(name: string): InfraResult<Readonly<Record<string, string>>> {
249
275
  if (name === 'HCLOUD_TOKEN') return success({ token: 'phase10-hcloud-token' });
250
276
  if (name === 'HETZNER_SSH') {
@@ -1,4 +1,9 @@
1
- import type { InfraManifest, InfraResult } from '@ankhorage/contracts/infra';
1
+ import type {
2
+ InfraControlPlaneCredentialRef,
3
+ InfraCredentialPort,
4
+ InfraManifest,
5
+ InfraResult,
6
+ } from '@ankhorage/contracts/infra';
2
7
  import {
3
8
  createInfraAdapter as createHetznerInfraAdapter,
4
9
  type HetznerCloudApi,
@@ -189,9 +194,7 @@ function createDependencies(fixture: ProductionR2Fixture): InfraOrchestrationDep
189
194
  }
190
195
  },
191
196
  },
192
- credentials: {
193
- resolveAsync: ({ name }) => Promise.resolve(resolveCredential(name)),
194
- },
197
+ credentials: createCredentialPort(),
195
198
  secrets: {
196
199
  resolveAsync: () =>
197
200
  Promise.resolve({
@@ -204,6 +207,32 @@ function createDependencies(fixture: ProductionR2Fixture): InfraOrchestrationDep
204
207
  };
205
208
  }
206
209
 
210
+ function createCredentialPort(): InfraCredentialPort {
211
+ return {
212
+ findAsync: (reference) => Promise.resolve(findCredential(reference)),
213
+ resolveAsync: ({ name }) => Promise.resolve(resolveCredential(name)),
214
+ persistAsync: (reference) =>
215
+ Promise.resolve({
216
+ ok: false,
217
+ diagnostics: [
218
+ {
219
+ severity: 'error',
220
+ code: 'unexpected-credential-persist',
221
+ message: `Production fixture must not persist credential ${reference.name}.`,
222
+ },
223
+ ],
224
+ }),
225
+ };
226
+ }
227
+
228
+ function findCredential(
229
+ reference: InfraControlPlaneCredentialRef,
230
+ ): InfraResult<Readonly<Record<string, string>> | null> {
231
+ return reference.name === 'SUPABASE_BOOTSTRAP'
232
+ ? resolveCredential(reference.name)
233
+ : success(null);
234
+ }
235
+
207
236
  function resolveCredential(name: string): InfraResult<Readonly<Record<string, string>>> {
208
237
  if (name === 'HCLOUD_TOKEN') return success({ token: 'phase10-hcloud-token' });
209
238
  if (name === 'HETZNER_SSH') {