@rehearsal-db/core 0.1.0-beta.9 → 0.1.0-rc.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 (247) hide show
  1. package/CHANGELOG.md +319 -1
  2. package/COMPATIBILITY.md +15 -0
  3. package/README.md +73 -16
  4. package/dist/package.json +100 -0
  5. package/dist/scripts/install/create_root_config.d.mts +3 -0
  6. package/dist/scripts/install/create_root_config.mjs +13 -0
  7. package/dist/scripts/runtime/manage_database.d.mts +6 -0
  8. package/{scripts → dist/scripts}/runtime/manage_database.mjs +6 -8
  9. package/dist/src/application/interactive_task.d.mts +30 -0
  10. package/dist/src/application/interactive_task.mjs +83 -0
  11. package/dist/src/application/session.d.mts +97 -0
  12. package/dist/src/application/session.mjs +380 -0
  13. package/dist/src/baseline/artifact.d.mts +119 -0
  14. package/dist/src/baseline/artifact.mjs +522 -0
  15. package/dist/src/baseline/builder.d.mts +19 -0
  16. package/dist/src/baseline/builder.mjs +142 -0
  17. package/dist/src/baseline/input_discovery.d.mts +13 -0
  18. package/dist/src/baseline/input_discovery.mjs +149 -0
  19. package/dist/src/baseline/policy_review.d.mts +56 -0
  20. package/dist/src/baseline/policy_review.mjs +73 -0
  21. package/dist/src/baseline/preparation.d.mts +63 -0
  22. package/dist/src/baseline/preparation.mjs +295 -0
  23. package/dist/src/baseline/privacy_contract.d.mts +147 -0
  24. package/dist/src/baseline/privacy_contract.mjs +2 -0
  25. package/dist/src/baseline/privacy_dependency_graph.d.mts +7 -0
  26. package/dist/src/baseline/privacy_dependency_graph.mjs +110 -0
  27. package/dist/src/baseline/privacy_engine.d.mts +5 -0
  28. package/dist/src/baseline/privacy_engine.mjs +4 -0
  29. package/dist/src/baseline/privacy_key.d.mts +7 -0
  30. package/dist/src/baseline/privacy_key.mjs +42 -0
  31. package/dist/src/baseline/privacy_pipeline.d.mts +7 -0
  32. package/dist/src/baseline/privacy_pipeline.mjs +127 -0
  33. package/dist/src/baseline/privacy_policy.d.mts +6 -0
  34. package/dist/src/baseline/privacy_policy.mjs +237 -0
  35. package/dist/src/baseline/privacy_recipe_validation.d.mts +5 -0
  36. package/dist/src/baseline/privacy_recipe_validation.mjs +289 -0
  37. package/dist/src/baseline/privacy_rule_registry.d.mts +18 -0
  38. package/dist/src/baseline/privacy_rule_registry.mjs +143 -0
  39. package/dist/src/baseline/privacy_structured_json.d.mts +66 -0
  40. package/dist/src/baseline/privacy_structured_json.mjs +464 -0
  41. package/dist/src/baseline/privacy_transforms.d.mts +18 -0
  42. package/dist/src/baseline/privacy_transforms.mjs +142 -0
  43. package/dist/src/baseline/privacy_validation_shared.d.mts +31 -0
  44. package/dist/src/baseline/privacy_validation_shared.mjs +127 -0
  45. package/dist/src/baseline/sanitization_policy.d.mts +69 -0
  46. package/dist/src/baseline/sanitization_policy.mjs +227 -0
  47. package/dist/src/baseline/schema_snapshot.d.mts +19 -0
  48. package/dist/src/baseline/schema_snapshot.mjs +79 -0
  49. package/dist/src/cli/application_command_handlers.d.mts +8 -0
  50. package/dist/src/cli/application_command_handlers.mjs +52 -0
  51. package/dist/src/cli/arguments.d.mts +38 -0
  52. package/dist/src/cli/arguments.mjs +130 -0
  53. package/dist/src/cli/basic_command_handlers.d.mts +4 -0
  54. package/dist/src/cli/basic_command_handlers.mjs +57 -0
  55. package/dist/src/cli/cli_commands.d.mts +14 -0
  56. package/dist/src/cli/cli_commands.mjs +34 -0
  57. package/dist/src/cli/command_contract.d.mts +57 -0
  58. package/dist/src/cli/command_contract.mjs +39 -0
  59. package/dist/src/cli/command_output.d.mts +12 -0
  60. package/dist/src/cli/command_output.mjs +20 -0
  61. package/dist/src/cli/command_registry.d.mts +7 -0
  62. package/dist/src/cli/command_registry.mjs +14 -0
  63. package/dist/src/cli/guided.d.mts +53 -0
  64. package/dist/src/cli/guided.mjs +727 -0
  65. package/dist/src/cli/identity_command_handlers.d.mts +12 -0
  66. package/dist/src/cli/identity_command_handlers.mjs +239 -0
  67. package/dist/src/cli/identity_renderer.d.mts +8 -0
  68. package/dist/src/cli/identity_renderer.mjs +33 -0
  69. package/dist/src/cli/init_command.d.mts +21 -0
  70. package/dist/src/cli/init_command.mjs +90 -0
  71. package/dist/src/cli/inspection_command_handlers.d.mts +3 -0
  72. package/dist/src/cli/inspection_command_handlers.mjs +79 -0
  73. package/dist/src/cli/project_command_handlers.d.mts +11 -0
  74. package/dist/src/cli/project_command_handlers.mjs +92 -0
  75. package/dist/src/cli/project_commands.d.mts +283 -0
  76. package/dist/src/cli/project_commands.mjs +192 -0
  77. package/dist/src/cli/rehearsal.d.mts +7 -0
  78. package/dist/src/cli/rehearsal.mjs +190 -0
  79. package/dist/src/cli/renderers.d.mts +299 -0
  80. package/dist/src/cli/renderers.mjs +554 -0
  81. package/dist/src/cli/runtime_action_handlers.d.mts +12 -0
  82. package/dist/src/cli/runtime_action_handlers.mjs +201 -0
  83. package/dist/src/cli/runtime_commands.d.mts +294 -0
  84. package/dist/src/cli/runtime_commands.mjs +619 -0
  85. package/dist/src/cli/source_command_handlers.d.mts +8 -0
  86. package/dist/src/cli/source_command_handlers.mjs +62 -0
  87. package/dist/src/cli/source_commands.d.mts +436 -0
  88. package/dist/src/cli/source_commands.mjs +301 -0
  89. package/dist/src/cli/terminal.d.mts +59 -0
  90. package/dist/src/cli/terminal.mjs +230 -0
  91. package/dist/src/identity/claim.d.mts +5 -0
  92. package/dist/src/identity/claim.mjs +4 -0
  93. package/dist/src/identity/claim_contract.d.mts +135 -0
  94. package/dist/src/identity/claim_contract.mjs +6 -0
  95. package/dist/src/identity/claim_executor.d.mts +10 -0
  96. package/dist/src/identity/claim_executor.mjs +358 -0
  97. package/dist/src/identity/claim_identity_lookup.d.mts +23 -0
  98. package/dist/src/identity/claim_identity_lookup.mjs +134 -0
  99. package/dist/src/identity/claim_plan.d.mts +8 -0
  100. package/dist/src/identity/claim_plan.mjs +61 -0
  101. package/dist/src/identity/claim_policy.d.mts +3 -0
  102. package/dist/src/identity/claim_policy.mjs +377 -0
  103. package/dist/src/identity/claim_sql.d.mts +10 -0
  104. package/dist/src/identity/claim_sql.mjs +46 -0
  105. package/dist/src/identity/claim_verification.d.mts +13 -0
  106. package/dist/src/identity/claim_verification.mjs +35 -0
  107. package/dist/src/identity/connect.d.mts +30 -0
  108. package/dist/src/identity/connect.mjs +111 -0
  109. package/dist/src/identity/provider_configuration.d.mts +37 -0
  110. package/dist/src/identity/provider_configuration.mjs +132 -0
  111. package/dist/src/identity/storage.d.mts +21 -0
  112. package/dist/src/identity/storage.mjs +122 -0
  113. package/dist/src/index.d.mts +4 -0
  114. package/dist/src/index.mjs +3 -0
  115. package/dist/src/project/configuration.d.mts +13 -0
  116. package/dist/src/project/configuration.mjs +9 -0
  117. package/dist/src/project/configuration_contract.d.mts +125 -0
  118. package/dist/src/project/configuration_contract.mjs +27 -0
  119. package/dist/src/project/configuration_discovery.d.mts +7 -0
  120. package/dist/src/project/configuration_discovery.mjs +37 -0
  121. package/dist/src/project/configuration_normalization.d.mts +107 -0
  122. package/dist/src/project/configuration_normalization.mjs +302 -0
  123. package/dist/src/project/configuration_rendering.d.mts +14 -0
  124. package/dist/src/project/configuration_rendering.mjs +228 -0
  125. package/dist/src/project/configuration_resolution.d.mts +125 -0
  126. package/dist/src/project/configuration_resolution.mjs +81 -0
  127. package/dist/src/project/configuration_schema.d.mts +22 -0
  128. package/dist/src/project/configuration_schema.mjs +160 -0
  129. package/dist/src/project/configuration_validation.d.mts +22 -0
  130. package/dist/src/project/configuration_validation.mjs +136 -0
  131. package/dist/src/project/install_scaffold.d.mts +42 -0
  132. package/dist/src/project/install_scaffold.mjs +263 -0
  133. package/dist/src/project/project_detection.d.mts +14 -0
  134. package/dist/src/project/project_detection.mjs +47 -0
  135. package/dist/src/project/setup.d.mts +87 -0
  136. package/dist/src/project/setup.mjs +414 -0
  137. package/dist/src/project/support_report.d.mts +88 -0
  138. package/dist/src/project/support_report.mjs +83 -0
  139. package/dist/src/runtime/cleanup.d.mts +85 -0
  140. package/dist/src/runtime/cleanup.mjs +311 -0
  141. package/dist/src/runtime/lifecycle_engine.d.mts +63 -0
  142. package/dist/src/runtime/lifecycle_engine.mjs +114 -0
  143. package/dist/src/runtime/migration_history.d.mts +51 -0
  144. package/dist/src/runtime/migration_history.mjs +170 -0
  145. package/dist/src/runtime/plan.d.mts +134 -0
  146. package/dist/src/runtime/plan.mjs +640 -0
  147. package/dist/src/runtime/policy.d.mts +66 -0
  148. package/dist/src/runtime/policy.mjs +369 -0
  149. package/dist/src/runtime/restore.d.mts +22 -0
  150. package/dist/src/runtime/restore.mjs +276 -0
  151. package/dist/src/runtime/service_environment.d.mts +11 -0
  152. package/dist/src/runtime/service_environment.mjs +58 -0
  153. package/dist/src/runtime/topology.d.mts +51 -0
  154. package/dist/src/runtime/topology.mjs +128 -0
  155. package/dist/src/shared/diagnostics.d.mts +97 -0
  156. package/dist/src/shared/diagnostics.mjs +170 -0
  157. package/dist/src/shared/human_output.d.mts +2 -0
  158. package/{src → dist/src}/shared/human_output.mjs +1 -3
  159. package/dist/src/shared/node_runtime.d.mts +14 -0
  160. package/dist/src/shared/node_runtime.mjs +25 -0
  161. package/dist/src/shared/operation_guard.d.mts +22 -0
  162. package/dist/src/shared/operation_guard.mjs +150 -0
  163. package/dist/src/shared/process_environment.d.mts +15 -0
  164. package/dist/src/shared/process_environment.mjs +50 -0
  165. package/dist/src/source/access.d.mts +165 -0
  166. package/dist/src/source/access.mjs +602 -0
  167. package/dist/src/source/asset_transfer.d.mts +27 -0
  168. package/dist/src/source/asset_transfer.mjs +202 -0
  169. package/dist/src/source/baseline.d.mts +66 -0
  170. package/dist/src/source/baseline.mjs +372 -0
  171. package/dist/src/source/external_reader.d.mts +42 -0
  172. package/dist/src/source/external_reader.mjs +278 -0
  173. package/dist/src/source/postgresql_access.d.mts +86 -0
  174. package/dist/src/source/postgresql_access.mjs +454 -0
  175. package/dist/src/source/privacy_paths.d.mts +10 -0
  176. package/dist/src/source/privacy_paths.mjs +27 -0
  177. package/dist/src/targets/driver.d.mts +2 -0
  178. package/dist/src/targets/driver.mjs +13 -0
  179. package/dist/src/targets/postgresql.d.mts +5 -0
  180. package/dist/src/targets/postgresql.mjs +620 -0
  181. package/dist/src/targets/supabase.d.mts +8 -0
  182. package/dist/src/targets/supabase.mjs +629 -0
  183. package/dist/src/targets/supabase_environment.d.mts +54 -0
  184. package/dist/src/targets/supabase_environment.mjs +184 -0
  185. package/dist/src/targets/target.d.mts +33 -0
  186. package/dist/src/targets/target.mjs +39 -0
  187. package/docs/README.md +35 -16
  188. package/docs/architecture.md +135 -10
  189. package/docs/commands.md +53 -15
  190. package/docs/configuration.md +186 -31
  191. package/docs/getting-started.md +66 -9
  192. package/docs/index.md +67 -0
  193. package/docs/production-source.md +175 -17
  194. package/docs/releasing.md +24 -12
  195. package/docs/roadmap.md +3 -2
  196. package/docs/runtime-lifecycle.md +99 -0
  197. package/docs/runtime-policies.md +80 -7
  198. package/docs/sanitization.md +572 -4
  199. package/docs/security-model.md +8 -0
  200. package/docs/stability.md +70 -0
  201. package/docs/standalone-workflow.md +36 -13
  202. package/docs/troubleshooting.md +82 -10
  203. package/docs/tutorial.md +7 -1
  204. package/docs/typescript.md +134 -0
  205. package/package.json +44 -29
  206. package/types/configuration.d.mts +5 -0
  207. package/src/README.md +0 -17
  208. package/src/application/session.mjs +0 -313
  209. package/src/baseline/artifact.mjs +0 -661
  210. package/src/baseline/builder.mjs +0 -164
  211. package/src/baseline/input_discovery.mjs +0 -155
  212. package/src/baseline/policy_review.mjs +0 -92
  213. package/src/baseline/preparation.mjs +0 -323
  214. package/src/baseline/privacy_engine.mjs +0 -413
  215. package/src/baseline/sanitization_policy.mjs +0 -299
  216. package/src/baseline/schema_snapshot.mjs +0 -113
  217. package/src/cli/arguments.mjs +0 -120
  218. package/src/cli/guided.mjs +0 -807
  219. package/src/cli/rehearsal.mjs +0 -933
  220. package/src/cli/renderers.mjs +0 -584
  221. package/src/cli/runtime_commands.mjs +0 -553
  222. package/src/cli/source_commands.mjs +0 -326
  223. package/src/cli/terminal.mjs +0 -275
  224. package/src/identity/claim.mjs +0 -975
  225. package/src/identity/storage.mjs +0 -165
  226. package/src/project/configuration.d.mts +0 -153
  227. package/src/project/configuration.mjs +0 -1197
  228. package/src/project/setup.mjs +0 -379
  229. package/src/project/support_report.mjs +0 -105
  230. package/src/runtime/cleanup.mjs +0 -381
  231. package/src/runtime/migration_history.mjs +0 -220
  232. package/src/runtime/plan.mjs +0 -685
  233. package/src/runtime/policy.mjs +0 -438
  234. package/src/runtime/restore.mjs +0 -329
  235. package/src/runtime/service_environment.mjs +0 -82
  236. package/src/runtime/topology.mjs +0 -177
  237. package/src/shared/diagnostics.mjs +0 -199
  238. package/src/shared/operation_guard.mjs +0 -162
  239. package/src/shared/process_environment.mjs +0 -68
  240. package/src/source/access.mjs +0 -578
  241. package/src/source/asset_transfer.mjs +0 -177
  242. package/src/source/baseline.mjs +0 -446
  243. package/src/source/postgresql_access.mjs +0 -480
  244. package/src/targets/postgresql.mjs +0 -808
  245. package/src/targets/supabase.mjs +0 -830
  246. package/src/targets/supabase_environment.mjs +0 -229
  247. package/src/targets/target.mjs +0 -66
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Purpose: Load and strictly validate the versioned, project-owned Rehearsal
3
+ * configuration contract. Do not run directly; this module is reusable script
4
+ * infrastructure.
5
+ */
6
+ export declare const normalizeRehearsalConfig: (input: unknown) => Readonly<{
7
+ schemaVersion: 1;
8
+ project: Readonly<{
9
+ name: string;
10
+ }>;
11
+ supabase: Readonly<{
12
+ workdir: string;
13
+ migrationDirectory: string;
14
+ rehearsalConfig: string;
15
+ runtimeWorkdir: string;
16
+ authentication: Readonly<{
17
+ enableLocalSignup: true;
18
+ environmentFile: string;
19
+ providers: readonly Readonly<{
20
+ name: "github" | "google";
21
+ clientIdEnvironmentVariable: string;
22
+ clientSecretEnvironmentVariable: string;
23
+ skipNonceCheck: boolean;
24
+ emailOptional: boolean;
25
+ }>[];
26
+ }> | null;
27
+ serviceEnvironmentFile: string | null;
28
+ serviceEnvironmentVariables: readonly string[];
29
+ }> | null;
30
+ postgresql: Readonly<{
31
+ migrationDirectory: string;
32
+ runtimeWorkdir: string;
33
+ image: string;
34
+ database: string;
35
+ user: string;
36
+ }> | null;
37
+ baseline: Readonly<{
38
+ artifactDirectory: string;
39
+ sanitizationPolicy: string;
40
+ }>;
41
+ containerRuntime: Readonly<{
42
+ autoStartColima: boolean;
43
+ }>;
44
+ cleanup: Readonly<{
45
+ retainBaselineGenerations: number;
46
+ }>;
47
+ preparation: Readonly<{
48
+ sourcePolicy: string;
49
+ privacyKey: string;
50
+ batchRows: number;
51
+ maximumRows: number;
52
+ maximumBytes: number;
53
+ diskHeadroomBytes: number;
54
+ }> | null;
55
+ runtimePolicy: string | null;
56
+ identityPolicy: string | null;
57
+ dependentTargets: readonly Readonly<{
58
+ name: string;
59
+ configPath: string;
60
+ prepareCommand: string | null;
61
+ }>[];
62
+ application: Readonly<{
63
+ startCommand: string;
64
+ proofCommand: string;
65
+ environmentFile: string;
66
+ environmentVariables: Readonly<Record<string, string>>;
67
+ readiness: Readonly<{
68
+ url: string;
69
+ expectedStatus: number;
70
+ timeoutSeconds: number;
71
+ }> | null;
72
+ httpProofs: readonly Readonly<{
73
+ name: string;
74
+ kind: "positive" | "negative";
75
+ url: string;
76
+ method: string;
77
+ expectedStatus: number;
78
+ json: Readonly<{
79
+ path: readonly string[];
80
+ equals?: unknown;
81
+ notEquals?: unknown;
82
+ minimumItems?: number;
83
+ }> | null;
84
+ }>[];
85
+ runtimeAdapter: string | null;
86
+ }>;
87
+ runtime: Readonly<{
88
+ target: "postgresql" | "supabase";
89
+ applicationUrl: string;
90
+ projectId: string;
91
+ ports: Readonly<{
92
+ api?: number;
93
+ database: number;
94
+ studio?: number;
95
+ }>;
96
+ }>;
97
+ safety: Readonly<{
98
+ allowedHosts: readonly string[];
99
+ blockedEnvironmentVariables: readonly string[];
100
+ authenticationProviders: readonly string[];
101
+ hostedAccess: "disabled";
102
+ outboundNetwork: "deny";
103
+ }>;
104
+ verification: Readonly<{
105
+ commands: readonly string[];
106
+ }>;
107
+ }>;
@@ -0,0 +1,302 @@
1
+ /**
2
+ * Purpose: Load and strictly validate the versioned, project-owned Rehearsal
3
+ * configuration contract. Do not run directly; this module is reusable script
4
+ * infrastructure.
5
+ */
6
+ import { REHEARSAL_CONFIG_VERSION, REHEARSAL_DEFAULTS, } from "./configuration_contract.mjs";
7
+ import { validateConfigurationSchema } from "./configuration_schema.mjs";
8
+ import { assertBoolean, assertCommand, assertDatabaseIdentifier, assertEnvironmentKey, assertEnvironmentReference, assertHttpStatus, assertIdentifier, assertKnownKeys, assertLoopbackUrl, assertNonEmptyString, assertPlainObject, assertPort, assertPostgresImage, assertPositiveInteger, assertRelativePath, assertStringArray, } from "./configuration_validation.mjs";
9
+ const LOOPBACK_HOSTS = new Set(REHEARSAL_DEFAULTS.safety.allowedHosts);
10
+ const SUPPORTED_SUPABASE_OAUTH_PROVIDERS = new Set(["github", "google"]);
11
+ const normalizeApplicationEnvironmentMappings = (value = {}) => {
12
+ const mappings = assertPlainObject(value, "config.application.environmentVariables");
13
+ return Object.freeze(Object.fromEntries(Object.entries(mappings).map(([key, reference]) => {
14
+ const path = `config.application.environmentVariables.${key}`;
15
+ assertEnvironmentKey(key, path);
16
+ return [key, assertEnvironmentReference(reference, path)];
17
+ })));
18
+ };
19
+ export const normalizeRehearsalConfig = (input) => {
20
+ const { root, project, runtime, runtimeTarget, supabase, postgresql, baseline, containerRuntime, cleanup, preparation, dependentTargets, application, readiness, httpProofs, safety, verification, authentication, } = validateConfigurationSchema(input);
21
+ const projectName = assertIdentifier(project.name, "config.project.name");
22
+ const normalizedDependentTargets = dependentTargets.map((target, index) => {
23
+ const path = `config.dependentTargets[${index}]`;
24
+ const name = assertIdentifier(target.name, `${path}.name`);
25
+ return Object.freeze({
26
+ name,
27
+ configPath: assertRelativePath(target.configPath, `${path}.configPath`),
28
+ prepareCommand: target.prepareCommand === undefined
29
+ ? null
30
+ : assertCommand(target.prepareCommand, `${path}.prepareCommand`),
31
+ });
32
+ });
33
+ if (new Set(normalizedDependentTargets.map((target) => target.name)).size !==
34
+ normalizedDependentTargets.length) {
35
+ throw new Error("config.dependentTargets names must be unique.");
36
+ }
37
+ if (httpProofs.length > 0 &&
38
+ (!httpProofs.some((proof) => proof.kind === "positive") ||
39
+ !httpProofs.some((proof) => proof.kind === "negative"))) {
40
+ throw new Error("config.application.httpProofs must include at least one positive and one negative check.");
41
+ }
42
+ const allowedHosts = assertStringArray(safety.allowedHosts ?? REHEARSAL_DEFAULTS.safety.allowedHosts, "config.safety.allowedHosts");
43
+ if (allowedHosts.some((host) => !LOOPBACK_HOSTS.has(host))) {
44
+ throw new Error("config.safety.allowedHosts may contain loopback hosts only in configuration version 1.");
45
+ }
46
+ if ((safety.hostedAccess ?? "disabled") !== "disabled") {
47
+ throw new Error("config.safety.hostedAccess must remain disabled in configuration version 1.");
48
+ }
49
+ if ((safety.outboundNetwork ?? "deny") !== "deny") {
50
+ throw new Error("config.safety.outboundNetwork must remain deny in configuration version 1.");
51
+ }
52
+ const ports = {
53
+ ...(runtimeTarget === "supabase"
54
+ ? { api: assertPort(runtime.apiPort, "config.runtime.apiPort") }
55
+ : {}),
56
+ database: assertPort(runtime.databasePort, "config.runtime.databasePort"),
57
+ ...(runtimeTarget === "supabase"
58
+ ? { studio: assertPort(runtime.studioPort, "config.runtime.studioPort") }
59
+ : {}),
60
+ };
61
+ if (new Set(Object.values(ports)).size !== Object.values(ports).length) {
62
+ throw new Error("Rehearsal runtime ports must be unique.");
63
+ }
64
+ const commands = verification.commands ?? [];
65
+ if (!Array.isArray(commands)) {
66
+ throw new Error("config.verification.commands must be an array.");
67
+ }
68
+ let normalizedAuthentication = null;
69
+ if (authentication) {
70
+ if (!supabase) {
71
+ throw new Error("config.supabase.authentication requires the Supabase runtime target.");
72
+ }
73
+ if (authentication.enableLocalSignup !== true) {
74
+ throw new Error("config.supabase.authentication.enableLocalSignup must be true to create the disposable local identity used by an OAuth sign-in.");
75
+ }
76
+ if (supabase.serviceEnvironmentFile !== undefined ||
77
+ supabase.serviceEnvironmentVariables !== undefined) {
78
+ throw new Error("config.supabase.authentication cannot be combined with the legacy serviceEnvironmentFile or serviceEnvironmentVariables fields.");
79
+ }
80
+ if (safety.authenticationProviders !== undefined) {
81
+ throw new Error("config.safety.authenticationProviders is derived from config.supabase.authentication and must be omitted.");
82
+ }
83
+ if (!Array.isArray(authentication.providers) ||
84
+ authentication.providers.length === 0) {
85
+ throw new Error("config.supabase.authentication.providers must be a non-empty array.");
86
+ }
87
+ const providers = authentication.providers.map((entry, index) => {
88
+ const path = `config.supabase.authentication.providers[${index}]`;
89
+ const value = entry;
90
+ const reviewedName = assertNonEmptyString(value.name, `${path}.name`);
91
+ if (!SUPPORTED_SUPABASE_OAUTH_PROVIDERS.has(reviewedName)) {
92
+ throw new Error(`${path}.name must be one of: ${[...SUPPORTED_SUPABASE_OAUTH_PROVIDERS].join(", ")}.`);
93
+ }
94
+ const name = reviewedName;
95
+ const environmentVariable = (key) => {
96
+ return assertEnvironmentKey(value[key], `${path}.${key}`);
97
+ };
98
+ const optionalBoolean = (key) => {
99
+ if (value[key] === undefined)
100
+ return false;
101
+ if (typeof value[key] !== "boolean") {
102
+ throw new Error(`${path}.${key} must be a boolean.`);
103
+ }
104
+ return value[key];
105
+ };
106
+ return Object.freeze({
107
+ name,
108
+ clientIdEnvironmentVariable: environmentVariable("clientIdEnvironmentVariable"),
109
+ clientSecretEnvironmentVariable: environmentVariable("clientSecretEnvironmentVariable"),
110
+ skipNonceCheck: optionalBoolean("skipNonceCheck"),
111
+ emailOptional: optionalBoolean("emailOptional"),
112
+ });
113
+ });
114
+ if (new Set(providers.map(({ name }) => name)).size !==
115
+ providers.length) {
116
+ throw new Error("config.supabase.authentication.providers must not contain duplicate providers.");
117
+ }
118
+ const credentialVariables = providers.flatMap((provider) => [
119
+ provider.clientIdEnvironmentVariable,
120
+ provider.clientSecretEnvironmentVariable,
121
+ ]);
122
+ if (new Set(credentialVariables).size !== credentialVariables.length) {
123
+ throw new Error("config.supabase.authentication provider environment variables must be unique.");
124
+ }
125
+ normalizedAuthentication = Object.freeze({
126
+ enableLocalSignup: true,
127
+ environmentFile: assertRelativePath(authentication.environmentFile, "config.supabase.authentication.environmentFile"),
128
+ providers: Object.freeze(providers),
129
+ });
130
+ }
131
+ const serviceEnvironmentVariables = normalizedAuthentication
132
+ ? normalizedAuthentication.providers.flatMap((provider) => [
133
+ provider.clientIdEnvironmentVariable,
134
+ provider.clientSecretEnvironmentVariable,
135
+ ])
136
+ : (supabase?.serviceEnvironmentVariables ?? []);
137
+ if (!Array.isArray(serviceEnvironmentVariables)) {
138
+ throw new Error("config.supabase.serviceEnvironmentVariables must be an array.");
139
+ }
140
+ const normalizedServiceEnvironmentVariables = serviceEnvironmentVariables.map((value, index) => assertNonEmptyString(value, `config.supabase.serviceEnvironmentVariables[${index}]`));
141
+ if (new Set(normalizedServiceEnvironmentVariables).size !==
142
+ normalizedServiceEnvironmentVariables.length) {
143
+ throw new Error("config.supabase.serviceEnvironmentVariables must not contain duplicates.");
144
+ }
145
+ if (Boolean(normalizedAuthentication?.environmentFile ??
146
+ supabase?.serviceEnvironmentFile) !== Boolean(normalizedServiceEnvironmentVariables.length)) {
147
+ throw new Error("config.supabase.serviceEnvironmentFile and serviceEnvironmentVariables must be configured together.");
148
+ }
149
+ const authenticationProviders = normalizedAuthentication
150
+ ? normalizedAuthentication.providers.map(({ name }) => name)
151
+ : safety.authenticationProviders
152
+ ? assertStringArray(safety.authenticationProviders, "config.safety.authenticationProviders")
153
+ : [];
154
+ if (runtimeTarget === "postgresql" && authenticationProviders.length > 0) {
155
+ throw new Error("config.safety.authenticationProviders is available only for the Supabase target.");
156
+ }
157
+ return Object.freeze({
158
+ schemaVersion: REHEARSAL_CONFIG_VERSION,
159
+ project: Object.freeze({ name: projectName }),
160
+ supabase: supabase
161
+ ? Object.freeze({
162
+ workdir: assertRelativePath(supabase.workdir, "config.supabase.workdir"),
163
+ migrationDirectory: assertRelativePath(supabase.migrationDirectory, "config.supabase.migrationDirectory"),
164
+ rehearsalConfig: assertRelativePath(supabase.rehearsalConfig, "config.supabase.rehearsalConfig"),
165
+ runtimeWorkdir: assertRelativePath(supabase.runtimeWorkdir, "config.supabase.runtimeWorkdir"),
166
+ authentication: normalizedAuthentication,
167
+ serviceEnvironmentFile: normalizedAuthentication
168
+ ? normalizedAuthentication.environmentFile
169
+ : supabase.serviceEnvironmentFile
170
+ ? assertRelativePath(supabase.serviceEnvironmentFile, "config.supabase.serviceEnvironmentFile")
171
+ : null,
172
+ serviceEnvironmentVariables: Object.freeze(normalizedServiceEnvironmentVariables),
173
+ })
174
+ : null,
175
+ postgresql: postgresql
176
+ ? Object.freeze({
177
+ migrationDirectory: assertRelativePath(postgresql.migrationDirectory, "config.postgresql.migrationDirectory"),
178
+ runtimeWorkdir: assertRelativePath(postgresql.runtimeWorkdir ?? ".rehearsal/runtime", "config.postgresql.runtimeWorkdir"),
179
+ image: assertPostgresImage(postgresql.image ?? "postgres:17-alpine", "config.postgresql.image"),
180
+ database: assertDatabaseIdentifier(postgresql.database ?? "postgres", "config.postgresql.database"),
181
+ user: assertDatabaseIdentifier(postgresql.user ?? "postgres", "config.postgresql.user"),
182
+ })
183
+ : null,
184
+ baseline: Object.freeze({
185
+ artifactDirectory: assertRelativePath(baseline.artifactDirectory ??
186
+ REHEARSAL_DEFAULTS.baseline.artifactDirectory, "config.baseline.artifactDirectory"),
187
+ sanitizationPolicy: assertRelativePath(baseline.sanitizationPolicy, "config.baseline.sanitizationPolicy"),
188
+ }),
189
+ containerRuntime: Object.freeze({
190
+ autoStartColima: assertBoolean(containerRuntime.autoStartColima ??
191
+ REHEARSAL_DEFAULTS.containerRuntime.autoStartColima, "config.containerRuntime.autoStartColima"),
192
+ }),
193
+ cleanup: Object.freeze({
194
+ retainBaselineGenerations: assertPositiveInteger(cleanup.retainBaselineGenerations ??
195
+ REHEARSAL_DEFAULTS.cleanup.retainBaselineGenerations, "config.cleanup.retainBaselineGenerations"),
196
+ }),
197
+ preparation: preparation
198
+ ? Object.freeze({
199
+ sourcePolicy: assertRelativePath(preparation.sourcePolicy, "config.preparation.sourcePolicy"),
200
+ privacyKey: assertRelativePath(preparation.privacyKey, "config.preparation.privacyKey"),
201
+ batchRows: assertPositiveInteger(preparation.batchRows ?? 500, "config.preparation.batchRows"),
202
+ maximumRows: assertPositiveInteger(preparation.maximumRows ?? 1_000_000, "config.preparation.maximumRows"),
203
+ maximumBytes: assertPositiveInteger(preparation.maximumBytes ?? 2 * 1024 * 1024 * 1024, "config.preparation.maximumBytes"),
204
+ diskHeadroomBytes: assertPositiveInteger(preparation.diskHeadroomBytes ?? 64 * 1024 * 1024, "config.preparation.diskHeadroomBytes"),
205
+ })
206
+ : null,
207
+ runtimePolicy: root.runtimePolicy === undefined
208
+ ? null
209
+ : assertRelativePath(root.runtimePolicy, "config.runtimePolicy"),
210
+ identityPolicy: root.identityPolicy === undefined
211
+ ? null
212
+ : assertRelativePath(root.identityPolicy, "config.identityPolicy"),
213
+ dependentTargets: Object.freeze(normalizedDependentTargets),
214
+ application: Object.freeze({
215
+ startCommand: assertCommand(application.startCommand, "config.application.startCommand"),
216
+ proofCommand: assertCommand(application.proofCommand, "config.application.proofCommand"),
217
+ environmentFile: assertRelativePath(application.environmentFile ?? ".rehearsal/runtime.env", "config.application.environmentFile"),
218
+ environmentVariables: normalizeApplicationEnvironmentMappings(application.environmentVariables),
219
+ readiness: readiness
220
+ ? Object.freeze({
221
+ url: assertLoopbackUrl(readiness.url, "config.application.readiness.url", allowedHosts, LOOPBACK_HOSTS),
222
+ expectedStatus: assertHttpStatus(readiness.expectedStatus, "config.application.readiness.expectedStatus"),
223
+ timeoutSeconds: assertPositiveInteger(readiness.timeoutSeconds ?? 30, "config.application.readiness.timeoutSeconds"),
224
+ })
225
+ : null,
226
+ httpProofs: Object.freeze(httpProofs.map((proof, index) => {
227
+ const path = `config.application.httpProofs[${index}]`;
228
+ const value = assertPlainObject(proof, path);
229
+ assertKnownKeys(value, ["name", "kind", "url", "method", "expectedStatus", "json"], path);
230
+ if (typeof value.kind !== "string" ||
231
+ !["positive", "negative"].includes(value.kind)) {
232
+ throw new Error(`${path}.kind must be positive or negative.`);
233
+ }
234
+ const expectedStatus = assertHttpStatus(value.expectedStatus, `${path}.expectedStatus`);
235
+ const method = assertNonEmptyString(value.method ?? "GET", `${path}.method`).toUpperCase();
236
+ if (!["GET", "HEAD"].includes(method)) {
237
+ throw new Error(`${path}.method must be GET or HEAD.`);
238
+ }
239
+ let json = null;
240
+ if (value.json !== undefined) {
241
+ const assertion = assertPlainObject(value.json, `${path}.json`);
242
+ assertKnownKeys(assertion, ["path", "equals", "notEquals", "minimumItems"], `${path}.json`);
243
+ if (!Array.isArray(assertion.path) ||
244
+ assertion.path.length === 0 ||
245
+ assertion.path.some((segment) => typeof segment !== "string" || !segment)) {
246
+ throw new Error(`${path}.json.path must be a non-empty string array.`);
247
+ }
248
+ const assertionCount = [
249
+ Object.hasOwn(assertion, "equals"),
250
+ Object.hasOwn(assertion, "notEquals"),
251
+ assertion.minimumItems !== undefined,
252
+ ].filter(Boolean).length;
253
+ if (assertionCount !== 1) {
254
+ throw new Error(`${path}.json must declare exactly one assertion.`);
255
+ }
256
+ json = Object.freeze({
257
+ path: Object.freeze([...assertion.path]),
258
+ ...(Object.hasOwn(assertion, "equals")
259
+ ? { equals: assertion.equals }
260
+ : {}),
261
+ ...(Object.hasOwn(assertion, "notEquals")
262
+ ? { notEquals: assertion.notEquals }
263
+ : {}),
264
+ ...(assertion.minimumItems !== undefined
265
+ ? {
266
+ minimumItems: assertPositiveInteger(assertion.minimumItems, `${path}.json.minimumItems`),
267
+ }
268
+ : {}),
269
+ });
270
+ }
271
+ return Object.freeze({
272
+ name: assertNonEmptyString(value.name, `${path}.name`),
273
+ kind: value.kind,
274
+ url: assertLoopbackUrl(value.url, `${path}.url`, allowedHosts, LOOPBACK_HOSTS),
275
+ method,
276
+ expectedStatus,
277
+ json,
278
+ });
279
+ })),
280
+ runtimeAdapter: application.runtimeAdapter === undefined
281
+ ? null
282
+ : assertRelativePath(application.runtimeAdapter, "config.application.runtimeAdapter"),
283
+ }),
284
+ runtime: Object.freeze({
285
+ target: runtimeTarget,
286
+ applicationUrl: assertLoopbackUrl(runtime.applicationUrl ?? REHEARSAL_DEFAULTS.runtime.applicationUrl, "config.runtime.applicationUrl", allowedHosts, LOOPBACK_HOSTS),
287
+ projectId: assertNonEmptyString(runtime.projectId ?? REHEARSAL_DEFAULTS.runtime.projectId, "config.runtime.projectId"),
288
+ ports: Object.freeze(ports),
289
+ }),
290
+ safety: Object.freeze({
291
+ allowedHosts: Object.freeze(allowedHosts),
292
+ blockedEnvironmentVariables: Object.freeze(assertStringArray(safety.blockedEnvironmentVariables ??
293
+ REHEARSAL_DEFAULTS.safety.blockedEnvironmentVariables, "config.safety.blockedEnvironmentVariables")),
294
+ authenticationProviders: Object.freeze(authenticationProviders),
295
+ hostedAccess: "disabled",
296
+ outboundNetwork: "deny",
297
+ }),
298
+ verification: Object.freeze({
299
+ commands: Object.freeze(commands.map((command, index) => assertCommand(command, `config.verification.commands[${index}]`))),
300
+ }),
301
+ });
302
+ };
@@ -0,0 +1,14 @@
1
+ /** Render reviewed starter configurations without reading or writing a project. */
2
+ import type { DetectedProject } from "./project_detection.mjs";
3
+ export declare const renderDetectedConfig: (detected: DetectedProject, { applicationUrl, ports, }?: {
4
+ applicationUrl?: string;
5
+ ports?: {
6
+ api: number;
7
+ database: number;
8
+ studio: number;
9
+ };
10
+ }) => string;
11
+ export declare const renderDetectedPostgresqlConfig: (detected: DetectedProject, { applicationUrl, databasePort }?: {
12
+ applicationUrl?: string | undefined;
13
+ databasePort?: number | undefined;
14
+ }) => string;
@@ -0,0 +1,228 @@
1
+ /** Render reviewed starter configurations without reading or writing a project. */
2
+ export const renderDetectedConfig = (detected, { applicationUrl = "http://localhost:5175", ports = { api: 58321, database: 58322, studio: 58323 }, } = {}) => `// @ts-check
3
+ /**
4
+ * Rehearsal configuration for ${detected.projectName}.
5
+ * Generated from this project by \`npx rehearsal\` and safe to commit.
6
+ * Review the CHECK comments. Keep passwords, tokens, and production URLs out of this file.
7
+ * Docs: https://github.com/Ddupasquier/rehearsal-db/blob/main/docs/configuration.md
8
+ */
9
+ import { defineRehearsalConfig } from "@rehearsal-db/core";
10
+
11
+ export default defineRehearsalConfig({
12
+ // Configuration format. Rehearsal will explain if an upgrade is ever needed.
13
+ schemaVersion: 1,
14
+ // Stable local name used in Rehearsal labels and reports.
15
+ project: { name: ${JSON.stringify(detected.projectName)} },
16
+
17
+ // Project files Rehearsal reads. Every path stays inside this repository.
18
+ supabase: {
19
+ workdir: ".",
20
+ migrationDirectory: "supabase/migrations",
21
+ rehearsalConfig: "infrastructure/rehearsal/supabase/config.toml",
22
+ runtimeWorkdir: ".rehearsal/runtime",
23
+
24
+ // Optional local OAuth connection. Secrets stay in the ignored owner-only file.
25
+ // Register http://127.0.0.1:${ports.api}/auth/v1/callback with the provider.
26
+ // authentication: {
27
+ // enableLocalSignup: true,
28
+ // environmentFile: ".env.rehearsal-service.local",
29
+ // providers: [
30
+ // {
31
+ // name: "google",
32
+ // clientIdEnvironmentVariable: "REHEARSAL_GOOGLE_CLIENT_ID",
33
+ // clientSecretEnvironmentVariable: "REHEARSAL_GOOGLE_CLIENT_SECRET",
34
+ // skipNonceCheck: false,
35
+ // emailOptional: false,
36
+ // },
37
+ // ],
38
+ // },
39
+ },
40
+
41
+ // Immutable local baseline files. Rehearsal never reads production on your behalf.
42
+ baseline: {
43
+ artifactDirectory: ".rehearsal",
44
+ sanitizationPolicy: "infrastructure/rehearsal/sanitization-policy.json",
45
+ },
46
+
47
+ // Optional source refresh. Enable only after reviewing docs/standalone-workflow.md.
48
+ // Credentials are read from the environment named by source-access-policy.json.
49
+ // preparation: {
50
+ // sourcePolicy: "infrastructure/rehearsal/source-access-policy.json",
51
+ // privacyKey: ".rehearsal/secrets/privacy.key",
52
+ // batchRows: 500,
53
+ // maximumRows: 1000000,
54
+ // maximumBytes: 2147483648,
55
+ // diskHeadroomBytes: 67108864,
56
+ // },
57
+ // Optional reviewed schemas/extensions/triggers/local-only rows.
58
+ // runtimePolicy: "infrastructure/rehearsal/runtime-policy.json",
59
+ // Optional copied-account association. The JSON stores only reviewed matcher hashes;
60
+ // raw emails/subjects and provider credentials stay in ignored environment files.
61
+ // After setup, run: npx rehearsal identity connect
62
+ // identityPolicy: "infrastructure/rehearsal/identity-policy.json",
63
+
64
+ // Reuse any running Docker-compatible engine. Keep startup explicit so a
65
+ // Rehearsal command cannot wake unrelated containers after Docker was stopped.
66
+ containerRuntime: {
67
+ autoStartColima: false,
68
+ },
69
+
70
+ // Keep this many baseline generations, always including the active one.
71
+ cleanup: {
72
+ retainBaselineGenerations: 2,
73
+ },
74
+
75
+ // Optional: manage another isolated database as part of the same rehearsal.
76
+ // Give that database its own complete config, ports, project ID, and .rehearsal folder.
77
+ // dependentTargets: [
78
+ // {
79
+ // name: "publication-api",
80
+ // configPath: "rehearsal.publication.config.mjs",
81
+ // prepareCommand: "npm run rehearsal:prepare-publication",
82
+ // },
83
+ // ],
84
+
85
+ application: {
86
+ // CHECK: commands detected from package.json. Change them if they are not correct.
87
+ startCommand: ${JSON.stringify(detected.applicationCommand)},
88
+ proofCommand: ${JSON.stringify(detected.verificationCommand)},
89
+ environmentFile: ".rehearsal/runtime.env",
90
+ // Map generated local runtime values directly into the normal app command.
91
+ // environmentVariables: { DATABASE_URL: "primary:DATABASE_URL" },
92
+ // Required by \`rehearsal open\`; use a route that responds only when the app is ready.
93
+ readiness: { url: ${JSON.stringify(applicationUrl)}, expectedStatus: 200, timeoutSeconds: 30 },
94
+ // Add at least one real positive and one negative before enabling HTTP proofs.
95
+ // httpProofs: [],
96
+ // Optional project-specific restore hook:
97
+ // runtimeAdapter: "infrastructure/rehearsal/runtime-adapter.mjs",
98
+ },
99
+
100
+ // Disposable local runtime identity, URL, and dedicated ports.
101
+ runtime: {
102
+ target: "supabase",
103
+ applicationUrl: ${JSON.stringify(applicationUrl)},
104
+ projectId: "${detected.projectName}-rehearsal",
105
+ apiPort: ${ports.api},
106
+ databasePort: ${ports.database},
107
+ studioPort: ${ports.studio},
108
+ },
109
+
110
+ // Only local runtime URLs are accepted; common hosted credentials are quarantined.
111
+ safety: {
112
+ allowedHosts: ["127.0.0.1", "::1", "localhost"],
113
+ blockedEnvironmentVariables: [
114
+ "SUPABASE_ACCESS_TOKEN",
115
+ "SUPABASE_DB_PASSWORD",
116
+ "SUPABASE_PROJECT_ID",
117
+ ],
118
+ hostedAccess: "disabled",
119
+ outboundNetwork: "deny",
120
+ },
121
+ });
122
+ `.replaceAll("\t", " ");
123
+ export const renderDetectedPostgresqlConfig = (detected, { applicationUrl = "http://localhost:5175", databasePort = 58322 } = {}) => `// @ts-check
124
+ /**
125
+ * Rehearsal configuration for ${detected.projectName}.
126
+ * Generated from this project by \`npx rehearsal\` and safe to commit.
127
+ * Review the CHECK comments. Keep passwords, tokens, and production URLs out of this file.
128
+ * Docs: https://github.com/Ddupasquier/rehearsal-db/blob/main/docs/configuration.md
129
+ */
130
+ import { defineRehearsalConfig } from "@rehearsal-db/core";
131
+
132
+ export default defineRehearsalConfig({
133
+ // Configuration format. Rehearsal will explain if an upgrade is ever needed.
134
+ schemaVersion: 1,
135
+ // Stable local name used in Rehearsal labels and reports.
136
+ project: { name: ${JSON.stringify(detected.projectName)} },
137
+
138
+ // Disposable PostgreSQL settings. The image must already exist locally.
139
+ postgresql: {
140
+ migrationDirectory: ${JSON.stringify(detected.postgresqlMigrationDirectory)},
141
+ runtimeWorkdir: ".rehearsal/runtime",
142
+ image: "postgres:17-alpine",
143
+ database: "postgres",
144
+ user: "postgres",
145
+ },
146
+
147
+ // Immutable local baseline files. Rehearsal never reads production on your behalf.
148
+ baseline: {
149
+ artifactDirectory: ".rehearsal",
150
+ sanitizationPolicy: "infrastructure/rehearsal/sanitization-policy.json",
151
+ },
152
+
153
+ // Optional source refresh. Enable only after reviewing docs/standalone-workflow.md.
154
+ // Credentials are read from the environment named by source-access-policy.json.
155
+ // preparation: {
156
+ // sourcePolicy: "infrastructure/rehearsal/source-access-policy.json",
157
+ // privacyKey: ".rehearsal/secrets/privacy.key",
158
+ // batchRows: 500,
159
+ // maximumRows: 1000000,
160
+ // maximumBytes: 2147483648,
161
+ // diskHeadroomBytes: 67108864,
162
+ // },
163
+ // Optional reviewed schemas/extensions/triggers/local-only rows.
164
+ // runtimePolicy: "infrastructure/rehearsal/runtime-policy.json",
165
+ // Optional copied-account association. The JSON stores only reviewed matcher hashes;
166
+ // raw emails/subjects and provider credentials stay in ignored environment files.
167
+ // After setup, run: npx rehearsal identity connect
168
+ // identityPolicy: "infrastructure/rehearsal/identity-policy.json",
169
+
170
+ // Reuse any running Docker-compatible engine. Keep startup explicit so a
171
+ // Rehearsal command cannot wake unrelated containers after Docker was stopped.
172
+ containerRuntime: {
173
+ autoStartColima: false,
174
+ },
175
+
176
+ // Keep this many baseline generations, always including the active one.
177
+ cleanup: {
178
+ retainBaselineGenerations: 2,
179
+ },
180
+
181
+ // Optional: manage another isolated database as part of the same rehearsal.
182
+ // Give that database its own complete config, ports, project ID, and .rehearsal folder.
183
+ // dependentTargets: [
184
+ // {
185
+ // name: "publication-api",
186
+ // configPath: "rehearsal.publication.config.mjs",
187
+ // prepareCommand: "npm run rehearsal:prepare-publication",
188
+ // },
189
+ // ],
190
+
191
+ application: {
192
+ // CHECK: commands detected from package.json. Change them if they are not correct.
193
+ startCommand: ${JSON.stringify(detected.applicationCommand)},
194
+ proofCommand: ${JSON.stringify(detected.verificationCommand)},
195
+ environmentFile: ".rehearsal/runtime.env",
196
+ // Map generated local runtime values directly into the normal app command.
197
+ // environmentVariables: { DATABASE_URL: "primary:DATABASE_URL" },
198
+ // Required by \`rehearsal open\`; use a route that responds only when the app is ready.
199
+ readiness: { url: ${JSON.stringify(applicationUrl)}, expectedStatus: 200, timeoutSeconds: 30 },
200
+ // Add at least one real positive and one negative before enabling HTTP proofs.
201
+ // httpProofs: [],
202
+ // Optional project-specific restore hook:
203
+ // runtimeAdapter: "infrastructure/rehearsal/runtime-adapter.mjs",
204
+ },
205
+
206
+ // Disposable local runtime identity, URL, and dedicated database port.
207
+ runtime: {
208
+ target: "postgresql",
209
+ applicationUrl: ${JSON.stringify(applicationUrl)},
210
+ projectId: "${detected.projectName}-rehearsal",
211
+ databasePort: ${databasePort},
212
+ },
213
+
214
+ // Only local runtime URLs are accepted; common hosted credentials are quarantined.
215
+ safety: {
216
+ allowedHosts: ["127.0.0.1", "::1", "localhost"],
217
+ blockedEnvironmentVariables: [
218
+ "DATABASE_URL",
219
+ "PGHOST",
220
+ "PGPASSWORD",
221
+ "PGPORT",
222
+ "PGUSER",
223
+ ],
224
+ hostedAccess: "disabled",
225
+ outboundNetwork: "deny",
226
+ },
227
+ });
228
+ `.replaceAll("\t", " ");