@starci/hfs 4.12.2 → 4.13.2

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 (160) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +8 -9
  3. package/cache/app-families.d.mts +30 -0
  4. package/cache/app-families.mjs +94 -0
  5. package/cache/content-index.d.mts +13 -0
  6. package/cache/content-index.mjs +37 -0
  7. package/cache/db-family.d.mts +13 -0
  8. package/cache/db-family.mjs +37 -0
  9. package/cache/fake-session.d.mts +42 -0
  10. package/cache/fake-session.mjs +57 -0
  11. package/cache/fingerprint.d.mts +28 -0
  12. package/cache/fingerprint.mjs +120 -0
  13. package/cache/format-family.d.mts +13 -0
  14. package/cache/format-family.mjs +49 -0
  15. package/cache/git.d.mts +11 -0
  16. package/cache/git.mjs +13 -0
  17. package/cache/hash.d.mts +15 -0
  18. package/cache/hash.mjs +58 -0
  19. package/cache/hfs-family.d.mts +35 -0
  20. package/cache/hfs-family.mjs +164 -0
  21. package/cache/import-graph.d.mts +75 -0
  22. package/cache/import-graph.mjs +204 -0
  23. package/cache/jsonl.d.mts +31 -0
  24. package/cache/jsonl.mjs +115 -0
  25. package/cache/lint-family.d.mts +162 -0
  26. package/cache/lint-family.mjs +433 -0
  27. package/cache/order.d.mts +1 -0
  28. package/cache/order.mjs +2 -0
  29. package/cache/progress.d.mts +19 -0
  30. package/cache/progress.mjs +68 -0
  31. package/cache/quality-baselines.d.mts +109 -0
  32. package/cache/quality-baselines.mjs +235 -0
  33. package/cache/records.d.mts +33 -0
  34. package/cache/records.mjs +54 -0
  35. package/cache/report.d.mts +19 -0
  36. package/cache/report.mjs +32 -0
  37. package/cache/run-log.d.mts +17 -0
  38. package/cache/run-log.mjs +37 -0
  39. package/cache/run.d.mts +33 -0
  40. package/cache/run.mjs +65 -0
  41. package/cache/session.d.mts +29 -0
  42. package/cache/session.mjs +197 -0
  43. package/cache/side-key.d.mts +10 -0
  44. package/cache/side-key.mjs +47 -0
  45. package/cache/test-family.d.mts +130 -0
  46. package/cache/test-family.mjs +258 -0
  47. package/cache/trust.d.mts +53 -0
  48. package/cache/trust.mjs +157 -0
  49. package/cache/types-family.d.mts +74 -0
  50. package/cache/types-family.mjs +154 -0
  51. package/cache/types.d.mts +99 -0
  52. package/cache/types.mjs +8 -0
  53. package/cache/wiring-family.d.mts +14 -0
  54. package/cache/wiring-family.mjs +43 -0
  55. package/coverage/cli.d.mts +4 -2
  56. package/coverage/cli.mjs +44 -5
  57. package/db/migrate.d.mts +16 -0
  58. package/db/migrate.mjs +21 -4
  59. package/db/postgres.d.mts +23 -0
  60. package/db/postgres.mjs +32 -1
  61. package/lint/run.d.mts +47 -1
  62. package/lint/run.mjs +157 -5
  63. package/package.json +2 -1
  64. package/release/gate.d.mts +2 -0
  65. package/release/gate.mjs +4 -2
  66. package/release/records.d.mts +15 -0
  67. package/release/records.mjs +25 -0
  68. package/release/urgent.mjs +23 -4
  69. package/runtime/knowledge/grammars/core/playbook.yaml +12 -18
  70. package/runtime/knowledge/hfs/canon-pins.yaml +8 -8
  71. package/runtime/knowledge/hfs/ci-standard.yaml +29 -6
  72. package/runtime/knowledge/hfs/rules.yaml +97 -7
  73. package/runtime/knowledge/hfs/slots.yaml +20 -8
  74. package/runtime/modules/kernel/failure-codes.yaml +46 -4
  75. package/runtime/modules/schemas/application-stacks.schema.yaml +48 -0
  76. package/runtime/scripts/hfs/architecture/hfs.d.mts +6 -6
  77. package/runtime/scripts/hfs/check.mjs +1 -1
  78. package/runtime/scripts/hfs/gate-threshold.mjs +2 -1
  79. package/runtime/scripts/hfs/rules/boot-proof.d.mts +5 -0
  80. package/runtime/scripts/hfs/rules/boot-proof.mjs +15 -1
  81. package/runtime/scripts/hfs/rules/ci-custody.mjs +3 -1
  82. package/runtime/scripts/hfs/rules/ci-standard.mjs +32 -3
  83. package/runtime/scripts/hfs/rules/database-migrations.d.mts +14 -0
  84. package/runtime/scripts/hfs/rules/database-migrations.mjs +55 -0
  85. package/runtime/scripts/hfs/rules/database.mjs +2 -1
  86. package/runtime/scripts/hfs/rules/deploy-workflow.mjs +53 -6
  87. package/runtime/scripts/hfs/rules/release-provenance.mjs +32 -4
  88. package/runtime/scripts/hfs/rules/stacks.mjs +5 -1
  89. package/runtime/scripts/hfs/rules/supply-chain.mjs +4 -1
  90. package/runtime/scripts/hfs/rules/unused-symbol-translators.mjs +98 -7
  91. package/runtime/scripts/hfs/rules/unused-symbol.d.mts +9 -2
  92. package/runtime/scripts/hfs/rules/unused-symbol.mjs +15 -2
  93. package/runtime/scripts/hfs/runtime-rules/git-triggers.mjs +21 -5
  94. package/runtime/scripts/hfs/stack-deploy-config.d.mts +18 -0
  95. package/runtime/scripts/hfs/stack-deploy-config.mjs +53 -1
  96. package/runtime/scripts/hfs/stacks-gate.mjs +2 -0
  97. package/runtime/scripts/hfs/tree-findings.mjs +3 -1
  98. package/scaffold/add-inbox-lite.mjs +2 -4
  99. package/scaffold/add-queue-lite.mjs +2 -3
  100. package/scaffold/add-table.mjs +2 -4
  101. package/scaffold/migration-stamp.d.mts +11 -0
  102. package/scaffold/migration-stamp.mjs +32 -0
  103. package/src/helpers/hfs-arch-fixture.d.mts +1 -1
  104. package/src/main.d.mts +4 -1
  105. package/src/main.mjs +189 -74
  106. package/src/stack-records-double.d.mts +33 -0
  107. package/stack/ci-key.d.mts +11 -0
  108. package/stack/ci-key.mjs +44 -0
  109. package/stack/cli.d.mts +7 -1
  110. package/stack/cli.mjs +22 -13
  111. package/stack/config.d.mts +15 -0
  112. package/stack/converge.d.mts +2 -0
  113. package/stack/converge.mjs +1 -1
  114. package/stack/db-phases.d.mts +185 -0
  115. package/stack/db-phases.mjs +346 -0
  116. package/stack/deploy.d.mts +22 -5
  117. package/stack/deploy.mjs +242 -72
  118. package/stack/journal.d.mts +146 -0
  119. package/stack/journal.mjs +191 -0
  120. package/stack/manager-backup.d.mts +54 -0
  121. package/stack/manager-backup.mjs +136 -0
  122. package/stack/notify.d.mts +9 -0
  123. package/stack/notify.mjs +32 -0
  124. package/stack/release-store.d.mts +49 -1
  125. package/stack/release-store.mjs +102 -16
  126. package/stack/remote.d.mts +2 -0
  127. package/stack/remote.mjs +32 -11
  128. package/stack/rollback-guard.d.mts +10 -0
  129. package/stack/rollback-guard.mjs +41 -0
  130. package/stack/server-key.d.mts +2 -0
  131. package/stack/server-key.mjs +1 -1
  132. package/stack/ssh-gate.d.mts +35 -0
  133. package/stack/ssh-gate.mjs +145 -0
  134. package/stack/status.d.mts +51 -0
  135. package/stack/status.mjs +115 -0
  136. package/stack/urgent-floor.d.mts +10 -61
  137. package/stack/urgent-floor.mjs +28 -123
  138. package/stack/verify.mjs +26 -0
  139. package/sync/ci-commands.mjs +10 -5
  140. package/sync/format-worker.d.mts +1 -0
  141. package/sync/format-worker.mjs +17 -0
  142. package/sync/format.d.mts +21 -3
  143. package/sync/format.mjs +99 -7
  144. package/sync/index.d.mts +3 -6
  145. package/sync/index.mjs +12 -12
  146. package/sync/stack-images.d.mts +25 -10
  147. package/sync/stack-images.mjs +215 -54
  148. package/templates/app/ci-workflows/github/workflows/ci.yml +114 -7
  149. package/templates/app/ci-workflows/github/workflows/images.yml +21 -65
  150. package/templates/app/ci-workflows/github/workflows/stack-deploy.yml +130 -37
  151. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +114 -7
  152. package/templates/app/ci-workflows-lite/github/workflows/images.yml +21 -65
  153. package/templates/app/ci-workflows-lite/github/workflows/stack-deploy.yml +130 -37
  154. package/templates/app/gitignore +1 -0
  155. package/templates/app/gitignore-lite +1 -0
  156. package/templates/app/package-scripts/db.json +1 -1
  157. package/templates/app/skeleton/.gitattributes +2 -0
  158. package/templates/app/skeleton-lite/.gitattributes +2 -0
  159. package/templates/be/skeleton/src/modules/platform/config/env-source.config.ts +2 -1
  160. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +0 -63
package/stack/cli.mjs CHANGED
@@ -9,7 +9,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
9
9
  // cli.mjs - `starci app stack <action>` for the Swarm deploy tooling: flags, dry run versus --execute, dispatch, text and --json output.
10
10
  // starci app stack <action> --env <env> [the action's FLAGS] (only the READ_ONLY actions default --env to production)
11
11
  // Each action takes only its own flags: a flag of another action is a refusal, never ignored.
12
- // (up, down and status are the test-world verbs and are dispatched by packages/hfs/src/main.mjs.)
12
+ // (`starci app world <up|down|status>` is the test world's, dispatched by packages/hfs/src/main.mjs; `stack status` below is the deploy's drift report.)
13
13
  // Every action module exports one async function (ctx, config, options) -> { ok, lines, data }; a refusal throws StackError.
14
14
  import path from 'node:path';
15
15
  import { stackContext } from './context.mjs';
@@ -19,12 +19,12 @@ import { StackError, EXIT, refusal } from './errors.mjs';
19
19
  import { parseTargetBinding, bindStackTarget, nativeToolEnvironment } from './target-binding.mjs';
20
20
  import { oneCommand, prepareUrgentRelease } from './urgent-release.mjs';
21
21
  /** The actions that drive the docker CLI of the operator machine (pointed at the manager by docker-transport.mjs when the key is sealed). */
22
- const DOCKER_ACTIONS = new Set(['deploy', 'accept', 'converge', 'verify', 'rollback', 'prune']);
23
- export const ACTIONS = Object.freeze(['prepare', 'connect', 'disconnect', 'build-args', 'render', 'deploy', 'converge', 'verify', 'rollback', 'prune', 'backup', 'jobs', 'task', 'ingress', 'retire', 'build', 'accept']);
22
+ const DOCKER_ACTIONS = new Set(['deploy', 'accept', 'converge', 'verify', 'rollback', 'prune', 'status']);
23
+ export const ACTIONS = Object.freeze(['prepare', 'connect', 'disconnect', 'build-args', 'render', 'deploy', 'converge', 'verify', 'rollback', 'prune', 'backup', 'jobs', 'task', 'ingress', 'retire', 'build', 'accept', 'status', 'ci-key']);
24
24
  /** True when `value` names one action of `starci app stack`. */
25
25
  export const isAction = (value) => ACTIONS.includes(value);
26
- export const BOOLEAN = new Set(['--execute', '--json', '--swarm-state', '--init-swarm', '--owner-approved', '--fix-live-restore', '--skip-public', '--skip-internal', '--allow-unpinned', '--push', '--urgent', '--first-start']);
27
- export const VALUE = new Set(['--env', '--image', '--sha', '--previous', '--out', '--release', '--since', '--timeout', '--retry', '--keep', '--app', '--ssh-cmd', '--extra-compose', '--project', '--target-binding', '--db-url-env', '--backup-out', '--db-service', '--reason', '--remote']);
26
+ export const BOOLEAN = new Set(['--execute', '--json', '--swarm-state', '--init-swarm', '--owner-approved', '--fix-live-restore', '--skip-public', '--skip-internal', '--allow-unpinned', '--push', '--urgent', '--first-start', '--redeploy', '--data-acknowledged', '--print-authorized-keys', '--print-gate']);
27
+ export const VALUE = new Set(['--env', '--image', '--sha', '--previous', '--out', '--release', '--since', '--timeout', '--retry', '--keep', '--app', '--ssh-cmd', '--extra-compose', '--project', '--target-binding', '--db-url-env', '--backup-out', '--db-service', '--reason', '--remote', '--ci-run', '--public-key']);
28
28
  export const REPEAT = new Set(['--image', '--keep', '--extra-compose', '--project']);
29
29
  /** `--ssh-cmd` replaces the configured alias for an action whose module (or the Docker transport) reaches the manager over SSH. */
30
30
  const SSH = ['--ssh-cmd'];
@@ -38,10 +38,10 @@ export const FLAGS = Object.freeze({
38
38
  disconnect: ['--execute'],
39
39
  'build-args': ['--app'],
40
40
  render: ['--image', '--sha', '--previous', '--out', '--allow-unpinned', '--extra-compose'],
41
- deploy: ['--image', '--sha', '--execute', '--out', '--timeout', '--retry', '--skip-public', '--target-binding', '--urgent', '--reason', '--remote', '--db-url-env', '--backup-out', '--first-start', '--db-service', ...SSH],
41
+ deploy: ['--image', '--sha', '--execute', '--out', '--timeout', '--retry', '--skip-public', '--target-binding', '--urgent', '--reason', '--remote', '--db-url-env', '--backup-out', '--first-start', '--db-service', '--ci-run', '--redeploy', ...SSH],
42
42
  converge: ['--since', '--timeout', ...SSH],
43
43
  verify: ['--retry', '--skip-public', '--skip-internal', '--target-binding', ...SSH],
44
- rollback: ['--release', '--execute', '--timeout', ...SSH],
44
+ rollback: ['--release', '--execute', '--timeout', '--data-acknowledged', ...SSH],
45
45
  prune: ['--keep', '--execute', ...SSH],
46
46
  backup: ['--swarm-state', '--execute', ...SSH],
47
47
  jobs: ['--execute', ...SSH],
@@ -49,12 +49,14 @@ export const FLAGS = Object.freeze({
49
49
  ingress: ['--execute', ...SSH],
50
50
  retire: ['--project', '--execute', ...SSH],
51
51
  build: ['--push', '--execute'],
52
- accept: ['--release', '--execute', '--retry', ...SSH],
52
+ accept: ['--release', '--execute', '--retry', '--db-url-env', ...SSH],
53
+ status: [...SSH],
54
+ 'ci-key': ['--public-key', '--print-authorized-keys', '--print-gate'],
53
55
  });
54
56
  /** The operands of the actions that take any, as the usage prints them. */
55
57
  export const OPERANDS = Object.freeze({ jobs: '<action> [<arg>...]', task: '<name> [<arg>...]', ingress: '<action> [<arg>...]' });
56
58
  /** The read-only actions, whose `--env` defaults to production. Every action with an effect names its environment, so none reaches production by omission. */
57
- export const READ_ONLY = Object.freeze(['build-args', 'render', 'verify']);
59
+ export const READ_ONLY = Object.freeze(['build-args', 'render', 'verify', 'status', 'ci-key']);
58
60
  /** Splits argv into { flags, positional }; an unknown flag, a flag of another action or a flag without its value is a refusal. */
59
61
  function parseFlags(action, argv) {
60
62
  const own = new Set(['--env', '--json', ...FLAGS[action]]);
@@ -91,8 +93,10 @@ function checkDeployForm(flags) {
91
93
  }
92
94
  if (!flags.urgent && (flags['first-start'] || flags['db-service']))
93
95
  throw refusal('STACK_USAGE', '--first-start and --db-service are the first start of an empty database under the urgent floor: add --urgent');
94
- if (!flags.urgent && (flags['db-url-env'] || flags['backup-out']))
95
- throw refusal('STACK_USAGE', '--db-url-env and --backup-out choose the urgent floor\'s database and backup folder: add --urgent');
96
+ if (flags.urgent && flags['ci-run'])
97
+ throw refusal('STACK_USAGE', '--ci-run names the CI run that deploys; an urgent deploy runs no CI (the CI run of its tag is the back-fill)');
98
+ if (flags['ci-run'] !== undefined && !/^[0-9]{1,20}$/u.test(flags['ci-run']))
99
+ throw refusal('STACK_USAGE', '--ci-run takes the numeric id of the CI run');
96
100
  }
97
101
  /** `--image api=ghcr.io/o/x@sha256:...` (repeatable or comma separated) -> { api: 'ghcr.io/o/x@sha256:...' }. */
98
102
  function parseImages(values = []) {
@@ -130,15 +134,18 @@ const OPTIONS = {
130
134
  out: flags.out ? path.resolve(cwd, flags.out) : null, timeoutSeconds: number(flags, 'timeout'), retrySeconds: number(flags, 'retry'), skipPublic: flags['skip-public'] === true,
131
135
  urgent: flags.urgent === true, dbUrlEnv: flags['db-url-env'], backupOut: flags['backup-out'] ? path.resolve(cwd, flags['backup-out']) : undefined,
132
136
  firstStart: flags['first-start'] === true, dbService: flags['db-service'], reason: flags.reason, remote: flags.remote,
137
+ ciRun: flags['ci-run'], redeploy: flags.redeploy === true,
133
138
  }),
134
139
  converge: ({ flags }) => ({ since: flags.since, timeoutSeconds: number(flags, 'timeout') }),
135
140
  verify: ({ flags }) => ({ retrySeconds: number(flags, 'retry'), skipPublic: flags['skip-public'] === true, skipInternal: flags['skip-internal'] === true }),
136
- rollback: ({ flags }) => ({ release: flags.release ?? null, execute: flags.execute === true, timeoutSeconds: number(flags, 'timeout') }),
141
+ rollback: ({ flags }) => ({ release: flags.release ?? null, execute: flags.execute === true, timeoutSeconds: number(flags, 'timeout'), dataAcknowledged: flags['data-acknowledged'] === true }),
137
142
  prune: ({ flags, cwd }) => ({ keepFiles: (flags.keep ?? []).map((file) => path.resolve(cwd, file)), execute: flags.execute === true }),
138
143
  backup: ({ flags }) => ({ swarmState: flags['swarm-state'] === true, execute: flags.execute === true }),
139
144
  ingress: ({ flags, positional }) => ({ action: positional[0], execute: flags.execute === true, args: positional.slice(1) }),
140
145
  build: ({ flags }) => ({ push: flags.push === true, execute: flags.execute === true }),
141
- accept: ({ flags }) => ({ release: flags.release ?? null, execute: flags.execute === true, retrySeconds: number(flags, 'retry') }),
146
+ accept: ({ flags }) => ({ release: flags.release ?? null, execute: flags.execute === true, retrySeconds: number(flags, 'retry'), dbUrlEnv: flags['db-url-env'] }),
147
+ status: () => ({}),
148
+ 'ci-key': ({ flags, cwd }) => ({ publicKey: flags['public-key'] ? path.resolve(cwd, flags['public-key']) : null, printAuthorizedKeys: flags['print-authorized-keys'] === true, printGate: flags['print-gate'] === true }),
142
149
  retire: ({ flags }) => ({ projects: flags.project ?? [], execute: flags.execute === true }),
143
150
  jobs: ({ flags, positional }) => ({ action: positional[0], args: positional.slice(1), execute: flags.execute === true }),
144
151
  task: ({ flags, positional }) => ({ name: positional[0], args: positional.slice(1), execute: flags.execute === true }),
@@ -154,6 +161,8 @@ const MODULES = {
154
161
  retire: ['./retire.mjs', 'retireCompose'],
155
162
  build: ['./build.mjs', 'buildImages'],
156
163
  accept: ['./deploy.mjs', 'acceptRelease'],
164
+ status: ['./status.mjs', 'statusStack'],
165
+ 'ci-key': ['./ci-key.mjs', 'ciKey'],
157
166
  };
158
167
  async function handlerOf(action, importer) {
159
168
  const [file, name] = MODULES[action];
@@ -84,6 +84,14 @@ export interface BackupBlock {
84
84
  dir: string | null;
85
85
  volumes: BackupVolume[];
86
86
  }
87
+ /** The normalised `deploy.notify` block: the sealed webhook URL and the outcomes announced. */
88
+ export interface NotifyBlock {
89
+ webhook: {
90
+ member: string;
91
+ key: string;
92
+ };
93
+ on: string[];
94
+ }
87
95
  export interface RegistryBlock {
88
96
  host: string;
89
97
  visibility: string;
@@ -158,6 +166,12 @@ export interface DeployBlock {
158
166
  environment: Record<string, ServiceEnvironment>;
159
167
  configs: ConfigEntry[];
160
168
  passEnv: string[];
169
+ /** deploy.auto: a green release tag deploys here by itself (the CI workflow reads it; the verbs do not). */
170
+ auto: boolean;
171
+ /** deploy.approval: none, or reviewers set on the GitHub Environment. */
172
+ approval: 'none' | 'reviewers';
173
+ /** deploy.platforms: the platforms the images are built for (default linux/amd64). */
174
+ platforms: string[];
161
175
  verify: {
162
176
  http: HttpCheck[];
163
177
  exec: ExecCheck[];
@@ -166,6 +180,7 @@ export interface DeployBlock {
166
180
  tasks: Record<string, TaskEntry>;
167
181
  postDeploy: string[];
168
182
  backup: BackupBlock | null;
183
+ notify: NotifyBlock | null;
169
184
  ingress: IngressConfig | null;
170
185
  registry: RegistryBlock;
171
186
  }
@@ -2,6 +2,8 @@ import type { DeployConfig } from './config.mjs';
2
2
  import type { StackContext } from './context.mjs';
3
3
  import type { ActionResult } from './remote.mjs';
4
4
  type Ctx = Pick<StackContext, 'spawn' | 'now' | 'sleep'>;
5
+ /** The JSON text with object keys sorted at every depth, so two equal specs compare equal whatever the key order. */
6
+ export declare function canonical(value: unknown): string;
5
7
  /**
6
8
  * Waits until the stack converged. `since` is the ISO instant the deploy began (required). Returns { ok, lines, data: { services, failed, pending } };
7
9
  * on failure the lines name the service and the Swarm task error text.
@@ -13,7 +13,7 @@ const TAB = String.fromCodePoint(9);
13
13
  const TASK_FORMAT = `{{.DesiredState}}|{{.Status.State}}|{{.Status.Timestamp}}|{{.Status.Err}}${TAB}{{json .Spec}}`;
14
14
  const docker = (ctx, args) => toolOutput(ctx, 'docker', args);
15
15
  /** The JSON text with object keys sorted at every depth, so two equal specs compare equal whatever the key order. */
16
- function canonical(value) {
16
+ export function canonical(value) {
17
17
  if (Array.isArray(value))
18
18
  return `[${value.map(canonical).join(',')}]`;
19
19
  if (value !== null && typeof value === 'object')
@@ -0,0 +1,185 @@
1
+ import type { DbTarget as PostgresTarget } from '../db/postgres.mjs';
2
+ import type { DatabaseDecl, DatabaseTarget, MigrationPhase } from '../db/migrate.mjs';
3
+ import type { DeployConfig } from './config.mjs';
4
+ import type { StackContext } from './context.mjs';
5
+ import type { ArchiveFacts, ManagerBackup } from './manager-backup.mjs';
6
+ import type { ActionResult, Runner } from './remote.mjs';
7
+ /** The receipt of a database backup (db/backup-receipt.mjs). */
8
+ export interface BackupReceipt {
9
+ schemaOnly: boolean;
10
+ at: string;
11
+ file: string;
12
+ bytes: number;
13
+ sha256: string;
14
+ parts: string[];
15
+ }
16
+ /** What a backup hands back: where the receipt is and what it says. */
17
+ export interface BackupData {
18
+ receiptPath: string;
19
+ receipt?: BackupReceipt | undefined;
20
+ }
21
+ /** The database a floor step reaches (db/postgres.mjs): `{ urlEnv }`, `{}` or `{ target }`, spread into the migrate options. */
22
+ export type DbTarget = Record<string, unknown>;
23
+ /** The verdict of one migrate pass; `data` is migrate's (`done`, `push`, `pushFiles`, `pushed`). */
24
+ export interface MigrateResult {
25
+ ok: boolean;
26
+ lines?: string[] | undefined;
27
+ data?: Record<string, unknown> | undefined;
28
+ }
29
+ /** Whether the database is empty, and why not. */
30
+ export interface Emptiness {
31
+ empty: boolean;
32
+ history: boolean;
33
+ publicTables: number;
34
+ }
35
+ /** The outcome of the `first-start` step. */
36
+ export interface FirstStartStepResult {
37
+ ok: boolean;
38
+ code?: string;
39
+ lines: string[];
40
+ }
41
+ /** The collaborators of the database steps, each defaulting to the app's own db verbs; a spec injects stubs. */
42
+ export interface FloorDeps {
43
+ backup: (ctx: StackContext, args: {
44
+ target: DbTarget;
45
+ out: string;
46
+ }) => Promise<BackupData>;
47
+ decrypt: (ctx: StackContext, args: {
48
+ archive: string;
49
+ identity: string | null;
50
+ parts: string[];
51
+ }) => Promise<{
52
+ parts: string[];
53
+ }>;
54
+ migrate: (ctx: StackContext, args: {
55
+ target: DbTarget;
56
+ execute: boolean;
57
+ phase?: MigrationPhase | undefined;
58
+ }) => Promise<MigrateResult>;
59
+ dbContainer: (ctx: StackContext, config: DeployConfig, args: {
60
+ service: string;
61
+ seconds: number;
62
+ }) => Promise<string | undefined>;
63
+ emptiness: (ctx: StackContext, target: PostgresTarget) => Promise<Emptiness>;
64
+ /** The database of a running container, reached with the credentials that container holds. */
65
+ containerTarget: (ctx: StackContext, container: string) => PostgresTarget;
66
+ /** The encrypted dump of a `stack` database, made and kept on the manager. */
67
+ managerBackup: (ctx: StackContext, config: DeployConfig, args: {
68
+ run: Runner;
69
+ container: string;
70
+ url: string;
71
+ recipients: string[];
72
+ at: string;
73
+ }) => Promise<ManagerBackup>;
74
+ /** The test decrypt of that dump on the manager with its own identity: the parts it lists. */
75
+ managerDecrypt: (ctx: StackContext, args: {
76
+ run: Runner;
77
+ archive: string;
78
+ }) => Promise<string[]>;
79
+ }
80
+ /** The code or message of a thrown value, for a refusal line. */
81
+ export declare const reasonOf: (error: unknown) => string;
82
+ /** The message of a thrown value. */
83
+ export declare const messageOf: (error: unknown) => string;
84
+ /** A refused step: the lines so far and the code; the CLI prints both and exits 2. */
85
+ export declare const refused: (lines: string[], code: string, message: string, data?: Record<string, unknown>) => ActionResult;
86
+ export declare const AGE_HEADER = "age-encryption.org/v1\n";
87
+ /** Problems of a backup receipt against the facts measured on its archive: a full backup written by this run, size, age header and sha256 equal. */
88
+ export declare function backupProblems(receipt: BackupReceipt | undefined, facts: ArchiveFacts, startedMs: number): string[];
89
+ /** How long a first start waits for the database service's container to accept connections. */
90
+ export declare const FIRST_START_WAIT_SECONDS = 180;
91
+ /** How long a deploy of a running stack waits for the database service's container to accept connections. */
92
+ export declare const DB_WAIT_SECONDS = 60;
93
+ export declare const FLOOR_DEPS: Readonly<FloorDeps>;
94
+ /**
95
+ * The `first-start` step of a first start's deploy (between the hold deploy and the real one): the database container, the proof it is empty,
96
+ * migrate preview then execute (every migration, contract ones included: no release runs yet) inside it. `targetFor` reaches the container's
97
+ * database; `hint` ends the not-empty message with what the operator does next. Returns { ok, lines, code } where code names the failure.
98
+ */
99
+ export declare function firstStartStep(ctx: StackContext, config: DeployConfig, input: {
100
+ service: string;
101
+ targetFor: (container: string) => PostgresTarget;
102
+ hint: string;
103
+ }, f: FloorDeps): Promise<FirstStartStepResult>;
104
+ /** The database a deploy migrates: the `database:` block of application-stacks.yaml. */
105
+ export interface DeployDatabase {
106
+ decl: DatabaseDecl;
107
+ target: DatabaseTarget;
108
+ service: string;
109
+ }
110
+ /** The `database:` block of the app, or null when it declares none (then a deploy has no database phase at all). */
111
+ export declare function declaredDatabase(ctx: Pick<StackContext, 'cwd'>, config: Pick<DeployConfig, 'repoRoot'>): Promise<DeployDatabase | null>;
112
+ /** What the phases of one deploy are given. */
113
+ export interface PhaseInput {
114
+ ctx: StackContext;
115
+ config: DeployConfig;
116
+ database: DeployDatabase;
117
+ f: FloorDeps;
118
+ /** The manager's runner (a `stack` database's backup is made and kept there). */
119
+ run: Runner | null;
120
+ dbUrlEnv?: string | undefined;
121
+ backupOut?: string | undefined;
122
+ }
123
+ /**
124
+ * A `stack` database must be a service of the stack the deploy renders: an app whose `database:` block exists but whose database is not one of its
125
+ * services declares `target: external`. Returns the refusal, or null when the service is there (or the stack's services are not known).
126
+ */
127
+ export declare function unknownDatabaseService(config: Pick<DeployConfig, 'stack' | 'services'>, database: DeployDatabase): {
128
+ code: string;
129
+ message: string;
130
+ } | null;
131
+ /** One recorded step of a deploy. */
132
+ export interface PhaseStep {
133
+ step: string;
134
+ ok: boolean;
135
+ }
136
+ /** What the deploy keeps of its migrations (the deploy result's `data.migrations`; WP1's journal records it). */
137
+ export interface MigrationRecord {
138
+ /** Something was pending before the deploy. */
139
+ pending: boolean;
140
+ /** The file names of the expand and of the contract migrations that were pending. */
141
+ expand: string[];
142
+ contract: string[];
143
+ /** The verified backup taken because something was pending, else null (nothing pending: no backup). */
144
+ backup: {
145
+ receiptPath: string;
146
+ file: string;
147
+ bytes: number;
148
+ sha256: string;
149
+ where: 'manager' | 'local';
150
+ } | null;
151
+ /** The versions the expand pass and the contract pass applied. */
152
+ applied: string[];
153
+ contractApplied: string[];
154
+ }
155
+ /** The result of a phase: ok, or the refusal that stopped it; the steps it ran and the lines it printed either way. */
156
+ export interface PhaseResult {
157
+ ok: boolean;
158
+ refused?: {
159
+ code: string;
160
+ message: string;
161
+ } | undefined;
162
+ lines: string[];
163
+ steps: PhaseStep[];
164
+ record: MigrationRecord;
165
+ }
166
+ /**
167
+ * Before the stack deploy: preview, backup only when something is pending, then the expand migrations. Any refusal leaves the services untouched
168
+ * and carries its code; the migrations an execute applied before failing stay (the backup is their way back).
169
+ */
170
+ export declare function expandPhase(p: PhaseInput): Promise<PhaseResult>;
171
+ /**
172
+ * After the accept: the `*_contract_*` migrations. A red contract leaves the release accepted (it is serving and verified); the caller reports a
173
+ * red job and the owner decides. Nothing pending is a green no-op without a database call beyond the preview.
174
+ */
175
+ export declare function contractPhase(p: PhaseInput): Promise<{
176
+ ok: boolean;
177
+ lines: string[];
178
+ applied: string[];
179
+ skipped: boolean;
180
+ }>;
181
+ /** The first-start hooks of a `stack` database on an environment with no accepted release (the deploy's `deploy-hold` and `first-start` steps). */
182
+ export declare function firstStartHooks(p: PhaseInput): {
183
+ step: () => Promise<FirstStartStepResult>;
184
+ service: string;
185
+ };