@rehearsal-db/core 0.1.0-beta.8 → 0.1.0-beta.9

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 (63) hide show
  1. package/CHANGELOG.md +68 -1
  2. package/COMPATIBILITY.md +15 -3
  3. package/README.md +31 -13
  4. package/docs/README.md +29 -0
  5. package/docs/adapters.md +15 -6
  6. package/docs/architecture.md +58 -0
  7. package/docs/baselines.md +22 -1
  8. package/docs/commands.md +76 -14
  9. package/docs/configuration.md +144 -11
  10. package/docs/getting-started.md +12 -2
  11. package/docs/glossary.md +4 -0
  12. package/docs/production-source.md +176 -33
  13. package/docs/roadmap.md +30 -28
  14. package/docs/runtime-policies.md +191 -0
  15. package/docs/sanitization.md +24 -8
  16. package/docs/security-model.md +29 -8
  17. package/docs/standalone-workflow.md +107 -0
  18. package/docs/troubleshooting.md +8 -0
  19. package/package.json +25 -22
  20. package/scripts/{operations/database/manage_rehearsal_database.mjs → runtime/manage_database.mjs} +2 -2
  21. package/src/README.md +17 -0
  22. package/src/application/session.mjs +313 -0
  23. package/{scripts/lib/rehearsal/baseline_artifact.mjs → src/baseline/artifact.mjs} +110 -14
  24. package/{scripts/lib/rehearsal/baseline_builder.mjs → src/baseline/builder.mjs} +8 -4
  25. package/{scripts/lib/rehearsal/baseline_preparation.mjs → src/baseline/preparation.mjs} +11 -4
  26. package/src/baseline/privacy_engine.mjs +413 -0
  27. package/{scripts/lib/rehearsal → src/baseline}/sanitization_policy.mjs +27 -7
  28. package/{scripts/lib/rehearsal → src/baseline}/schema_snapshot.mjs +1 -1
  29. package/src/cli/arguments.mjs +120 -0
  30. package/src/cli/guided.mjs +807 -0
  31. package/src/cli/rehearsal.mjs +933 -0
  32. package/src/cli/renderers.mjs +584 -0
  33. package/src/cli/runtime_commands.mjs +553 -0
  34. package/src/cli/source_commands.mjs +326 -0
  35. package/src/cli/terminal.mjs +275 -0
  36. package/src/identity/claim.mjs +975 -0
  37. package/src/identity/storage.mjs +165 -0
  38. package/{scripts/lib/rehearsal → src/project}/configuration.d.mts +34 -0
  39. package/{scripts/lib/rehearsal → src/project}/configuration.mjs +353 -3
  40. package/{scripts/lib/rehearsal → src/project}/setup.mjs +1 -1
  41. package/{scripts/lib/rehearsal → src/project}/support_report.mjs +5 -2
  42. package/{scripts/lib/rehearsal → src/runtime}/cleanup.mjs +3 -3
  43. package/{scripts/lib/rehearsal → src/runtime}/plan.mjs +5 -5
  44. package/src/runtime/policy.mjs +438 -0
  45. package/{scripts/lib/rehearsal/runtime_restore.mjs → src/runtime/restore.mjs} +40 -25
  46. package/src/runtime/topology.mjs +177 -0
  47. package/{scripts/lib/rehearsal → src/shared}/diagnostics.mjs +1 -1
  48. package/src/shared/operation_guard.mjs +162 -0
  49. package/{scripts/lib/rehearsal → src/shared}/process_environment.mjs +5 -1
  50. package/src/source/access.mjs +578 -0
  51. package/src/source/asset_transfer.mjs +177 -0
  52. package/src/source/baseline.mjs +446 -0
  53. package/src/source/postgresql_access.mjs +480 -0
  54. package/{scripts/lib/runtime/postgresql_runtime.mjs → src/targets/postgresql.mjs} +45 -10
  55. package/{scripts/lib/runtime/supabase_runtime.mjs → src/targets/supabase.mjs} +50 -13
  56. package/{scripts/lib/environment/local_supabase.mjs → src/targets/supabase_environment.mjs} +52 -20
  57. package/{scripts/lib/runtime/runtime_target.mjs → src/targets/target.mjs} +2 -2
  58. package/scripts/operations/rehearsal/rehearsal_cli.mjs +0 -2169
  59. /package/{scripts/lib/rehearsal → src/baseline}/input_discovery.mjs +0 -0
  60. /package/{scripts/lib/rehearsal → src/baseline}/policy_review.mjs +0 -0
  61. /package/{scripts/lib/rehearsal → src/runtime}/migration_history.mjs +0 -0
  62. /package/{scripts/lib/rehearsal → src/runtime}/service_environment.mjs +0 -0
  63. /package/{scripts/lib/rehearsal → src/shared}/human_output.mjs +0 -0
@@ -16,11 +16,11 @@ import { pathToFileURL } from "node:url";
16
16
  import {
17
17
  resolveActiveBaselinePaths,
18
18
  verifyActiveBaseline,
19
- } from "../rehearsal/baseline_artifact.mjs";
19
+ } from "../baseline/artifact.mjs";
20
20
  import {
21
21
  createCleanProcessEnvironment,
22
22
  assertLoopbackUrl,
23
- } from "../rehearsal/process_environment.mjs";
23
+ } from "../shared/process_environment.mjs";
24
24
  import {
25
25
  localCommandSucceeds,
26
26
  readLocalSupabaseEnvironment,
@@ -28,29 +28,36 @@ import {
28
28
  runLocalCommand,
29
29
  startLocalSupabase,
30
30
  stopLocalSupabase,
31
- } from "../environment/local_supabase.mjs";
31
+ } from "./supabase_environment.mjs";
32
32
  import {
33
33
  buildRestoreSqlPrefix,
34
34
  buildRestoreSqlSuffix,
35
35
  createCandidateMigrationReceipt,
36
36
  encodeBaselineRecordForCopy,
37
37
  summarizeRestoreError,
38
- } from "../rehearsal/runtime_restore.mjs";
38
+ } from "../runtime/restore.mjs";
39
39
  import {
40
40
  buildMigrationLedgerInventory,
41
41
  compareSourceToReplay,
42
42
  createMigrationReplayReceipt,
43
43
  readMigrationFileInventory,
44
- } from "../rehearsal/migration_history.mjs";
45
- import { loadRehearsalConfig } from "../rehearsal/configuration.mjs";
46
- import { readRehearsalServiceEnvironment } from "../rehearsal/service_environment.mjs";
47
- import { readBoundRuntimeSanitizationPolicy } from "../rehearsal/sanitization_policy.mjs";
44
+ } from "../runtime/migration_history.mjs";
45
+ import { loadRehearsalConfig } from "../project/configuration.mjs";
46
+ import { readRehearsalServiceEnvironment } from "../runtime/service_environment.mjs";
47
+ import { readBoundRuntimeSanitizationPolicy } from "../baseline/sanitization_policy.mjs";
48
48
  import {
49
49
  captureLocalPublicSchema,
50
50
  compareProductionSchemaDumps,
51
- } from "../rehearsal/schema_snapshot.mjs";
52
- import { formatCount } from "../rehearsal/human_output.mjs";
53
- import { parseRuntimeInvocation } from "./runtime_target.mjs";
51
+ } from "../baseline/schema_snapshot.mjs";
52
+ import { formatCount } from "../shared/human_output.mjs";
53
+ import { parseRuntimeInvocation } from "./target.mjs";
54
+ import {
55
+ buildRuntimeFinalizationSql,
56
+ buildRuntimePostSchemaSql,
57
+ buildRuntimePrerequisiteSql,
58
+ buildRuntimeVerificationSql,
59
+ validateRuntimePolicy,
60
+ } from "../runtime/policy.mjs";
54
61
 
55
62
  const repositoryRoot = process.cwd();
56
63
  const invocation = parseRuntimeInvocation();
@@ -78,6 +85,11 @@ const projectId = config.runtime.projectId;
78
85
  const runtimeAdapter = configuredPaths.runtimeAdapter
79
86
  ? await import(pathToFileURL(configuredPaths.runtimeAdapter).href)
80
87
  : null;
88
+ const runtimePolicy = configuredPaths.runtimePolicy
89
+ ? validateRuntimePolicy(
90
+ JSON.parse(await readFile(configuredPaths.runtimePolicy, "utf8")),
91
+ )
92
+ : null;
81
93
  const environmentKeyPattern = /^[A-Z][A-Z0-9_]*$/u;
82
94
  const action = invocation.action;
83
95
 
@@ -334,6 +346,12 @@ const writeRuntimeEnvironment = async ({
334
346
  const variables = {
335
347
  REHEARSAL_RUNTIME_ENVIRONMENT: "rehearsal",
336
348
  REHEARSAL_BASELINE_ID: baseline.generationId,
349
+ DATABASE_URL: environment.databaseUrl,
350
+ PGHOST: environment.databaseHost,
351
+ PGPORT: environment.databasePort,
352
+ PGDATABASE: environment.databaseName,
353
+ PGUSER: environment.databaseUser,
354
+ PGPASSWORD: environment.databasePassword,
337
355
  SUPABASE_URL: environment.apiUrl,
338
356
  SUPABASE_PUBLISHABLE_KEY: environment.publishableKey,
339
357
  SUPABASE_SERVICE_ROLE_KEY: environment.serviceRoleKey,
@@ -375,7 +393,13 @@ const runDatabaseSql = (sql) => {
375
393
 
376
394
  const restoreProductionSchema = async ({ baseline, paths }) => {
377
395
  const receipt = baseline.files?.["production-schema.sql"];
378
- if (baseline.formatVersion === 1 && receipt === undefined) return null;
396
+ if (baseline.formatVersion === 1 && receipt === undefined) {
397
+ if (runtimePolicy) {
398
+ runDatabaseSql(buildRuntimePrerequisiteSql(runtimePolicy));
399
+ runDatabaseSql(buildRuntimePostSchemaSql(runtimePolicy));
400
+ }
401
+ return null;
402
+ }
379
403
  if (
380
404
  baseline.formatVersion !== 2 ||
381
405
  !/^[a-f0-9]{64}$/u.test(receipt?.sha256 ?? "") ||
@@ -386,7 +410,12 @@ const restoreProductionSchema = async ({ baseline, paths }) => {
386
410
  );
387
411
  }
388
412
  const schemaSql = await readFile(paths.schemaPath, "utf8");
389
- runDatabaseSql("drop schema if exists public cascade;");
413
+ runDatabaseSql(`drop schema if exists public cascade;
414
+ create schema public authorization pg_database_owner;
415
+ grant usage on schema public to public;`);
416
+ if (runtimePolicy) {
417
+ runDatabaseSql(buildRuntimePrerequisiteSql(runtimePolicy));
418
+ }
390
419
  if (runtimeAdapter?.prepareSchema) {
391
420
  const preparation = runtimeAdapter.prepareSchema({
392
421
  runSql: runDatabaseSql,
@@ -398,6 +427,9 @@ const restoreProductionSchema = async ({ baseline, paths }) => {
398
427
  }
399
428
  }
400
429
  runDatabaseSql(schemaSql);
430
+ if (runtimePolicy) {
431
+ runDatabaseSql(buildRuntimePostSchemaSql(runtimePolicy));
432
+ }
401
433
  const restoredSchema = await captureLocalPublicSchema({
402
434
  repositoryRoot,
403
435
  artifactRoot,
@@ -480,6 +512,10 @@ const resetRuntime = async () => {
480
512
  paths: active.paths,
481
513
  environment,
482
514
  });
515
+ if (runtimePolicy && runtimePolicy.localRows.length > 0) {
516
+ runDatabaseSql(buildRuntimeFinalizationSql(runtimePolicy));
517
+ console.log("Applied reviewed local-only recipes.");
518
+ }
483
519
  const projectRuntime = configureProjectRuntime({
484
520
  environment,
485
521
  baseline: active.baseline,
@@ -616,6 +652,7 @@ const verifyRuntime = async () => {
616
652
  currentFiles: candidateReceipt.currentFiles,
617
653
  });
618
654
  let projectVerification;
655
+ if (runtimePolicy) runDatabaseSql(buildRuntimeVerificationSql(runtimePolicy));
619
656
  if (runtimeAdapter?.verifyRuntime) {
620
657
  projectVerification = runtimeAdapter.verifyRuntime({
621
658
  baseline,
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { spawnSync } from "node:child_process";
7
- import { createCleanProcessEnvironment } from "../rehearsal/process_environment.mjs";
7
+ import { createCleanProcessEnvironment } from "../shared/process_environment.mjs";
8
8
 
9
9
  export const parseSupabaseStatusEnvironment = (output) => {
10
10
  const values = {};
@@ -18,6 +18,56 @@ export const parseSupabaseStatusEnvironment = (output) => {
18
18
  return values;
19
19
  };
20
20
 
21
+ export const parseLocalSupabaseEnvironment = (output) => {
22
+ const values = parseSupabaseStatusEnvironment(output);
23
+ const localEnvironment = {
24
+ apiUrl: values.API_URL,
25
+ databaseUrl: values.DB_URL,
26
+ publishableKey: values.PUBLISHABLE_KEY ?? values.ANON_KEY,
27
+ serviceRoleKey: values.SERVICE_ROLE_KEY ?? values.SECRET_KEY,
28
+ studioUrl: values.STUDIO_URL,
29
+ };
30
+ for (const [label, value] of [
31
+ ["API URL", localEnvironment.apiUrl],
32
+ ["database URL", localEnvironment.databaseUrl],
33
+ ["publishable key", localEnvironment.publishableKey],
34
+ ["service-role key", localEnvironment.serviceRoleKey],
35
+ ]) {
36
+ if (!value) throw new Error(`Local Supabase status omitted its ${label}.`);
37
+ }
38
+ const api = new URL(localEnvironment.apiUrl);
39
+ const database = new URL(localEnvironment.databaseUrl);
40
+ const loopbackHosts = ["127.0.0.1", "::1", "localhost"];
41
+ if (
42
+ !loopbackHosts.includes(api.hostname) ||
43
+ !loopbackHosts.includes(database.hostname) ||
44
+ !["postgres:", "postgresql:"].includes(database.protocol)
45
+ ) {
46
+ throw new Error("Refusing to use a non-local Supabase status target.");
47
+ }
48
+ const databaseName = decodeURIComponent(
49
+ database.pathname.replace(/^\//u, ""),
50
+ );
51
+ if (
52
+ !database.port ||
53
+ !databaseName ||
54
+ !database.username ||
55
+ !database.password
56
+ ) {
57
+ throw new Error(
58
+ "Local Supabase status returned an incomplete database URL.",
59
+ );
60
+ }
61
+ return {
62
+ ...localEnvironment,
63
+ databaseHost: database.hostname,
64
+ databasePort: database.port,
65
+ databaseName,
66
+ databaseUser: decodeURIComponent(database.username),
67
+ databasePassword: decodeURIComponent(database.password),
68
+ };
69
+ };
70
+
21
71
  export const runLocalCommand = (
22
72
  command,
23
73
  args,
@@ -90,25 +140,7 @@ export const readLocalSupabaseEnvironment = ({
90
140
  withWorkdir(["status", "-o", "env"], workdir),
91
141
  { capture: true, cwd, environment },
92
142
  );
93
- const values = parseSupabaseStatusEnvironment(output);
94
- const localEnvironment = {
95
- apiUrl: values.API_URL,
96
- publishableKey: values.PUBLISHABLE_KEY ?? values.ANON_KEY,
97
- serviceRoleKey: values.SERVICE_ROLE_KEY ?? values.SECRET_KEY,
98
- studioUrl: values.STUDIO_URL,
99
- };
100
- for (const [label, value] of [
101
- ["API URL", localEnvironment.apiUrl],
102
- ["publishable key", localEnvironment.publishableKey],
103
- ["service-role key", localEnvironment.serviceRoleKey],
104
- ]) {
105
- if (!value) throw new Error(`Local Supabase status omitted its ${label}.`);
106
- }
107
- const hostname = new URL(localEnvironment.apiUrl).hostname;
108
- if (!["127.0.0.1", "::1", "localhost"].includes(hostname)) {
109
- throw new Error("Refusing to use a non-loopback Supabase status target.");
110
- }
111
- return localEnvironment;
143
+ return parseLocalSupabaseEnvironment(output);
112
144
  };
113
145
 
114
146
  export const startLocalSupabase = ({
@@ -55,10 +55,10 @@ export const runRuntimeTarget = async (target) => {
55
55
  const resolved = resolveRuntimeTarget(target);
56
56
  switch (resolved.id) {
57
57
  case "supabase":
58
- await import("./supabase_runtime.mjs");
58
+ await import("./supabase.mjs");
59
59
  return;
60
60
  case "postgresql":
61
- await import("./postgresql_runtime.mjs");
61
+ await import("./postgresql.mjs");
62
62
  return;
63
63
  default:
64
64
  throw new Error(`No runtime driver is installed for ${resolved.id}.`);