@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.
- package/CHANGELOG.md +68 -1
- package/COMPATIBILITY.md +15 -3
- package/README.md +31 -13
- package/docs/README.md +29 -0
- package/docs/adapters.md +15 -6
- package/docs/architecture.md +58 -0
- package/docs/baselines.md +22 -1
- package/docs/commands.md +76 -14
- package/docs/configuration.md +144 -11
- package/docs/getting-started.md +12 -2
- package/docs/glossary.md +4 -0
- package/docs/production-source.md +176 -33
- package/docs/roadmap.md +30 -28
- package/docs/runtime-policies.md +191 -0
- package/docs/sanitization.md +24 -8
- package/docs/security-model.md +29 -8
- package/docs/standalone-workflow.md +107 -0
- package/docs/troubleshooting.md +8 -0
- package/package.json +25 -22
- package/scripts/{operations/database/manage_rehearsal_database.mjs → runtime/manage_database.mjs} +2 -2
- package/src/README.md +17 -0
- package/src/application/session.mjs +313 -0
- package/{scripts/lib/rehearsal/baseline_artifact.mjs → src/baseline/artifact.mjs} +110 -14
- package/{scripts/lib/rehearsal/baseline_builder.mjs → src/baseline/builder.mjs} +8 -4
- package/{scripts/lib/rehearsal/baseline_preparation.mjs → src/baseline/preparation.mjs} +11 -4
- package/src/baseline/privacy_engine.mjs +413 -0
- package/{scripts/lib/rehearsal → src/baseline}/sanitization_policy.mjs +27 -7
- package/{scripts/lib/rehearsal → src/baseline}/schema_snapshot.mjs +1 -1
- package/src/cli/arguments.mjs +120 -0
- package/src/cli/guided.mjs +807 -0
- package/src/cli/rehearsal.mjs +933 -0
- package/src/cli/renderers.mjs +584 -0
- package/src/cli/runtime_commands.mjs +553 -0
- package/src/cli/source_commands.mjs +326 -0
- package/src/cli/terminal.mjs +275 -0
- package/src/identity/claim.mjs +975 -0
- package/src/identity/storage.mjs +165 -0
- package/{scripts/lib/rehearsal → src/project}/configuration.d.mts +34 -0
- package/{scripts/lib/rehearsal → src/project}/configuration.mjs +353 -3
- package/{scripts/lib/rehearsal → src/project}/setup.mjs +1 -1
- package/{scripts/lib/rehearsal → src/project}/support_report.mjs +5 -2
- package/{scripts/lib/rehearsal → src/runtime}/cleanup.mjs +3 -3
- package/{scripts/lib/rehearsal → src/runtime}/plan.mjs +5 -5
- package/src/runtime/policy.mjs +438 -0
- package/{scripts/lib/rehearsal/runtime_restore.mjs → src/runtime/restore.mjs} +40 -25
- package/src/runtime/topology.mjs +177 -0
- package/{scripts/lib/rehearsal → src/shared}/diagnostics.mjs +1 -1
- package/src/shared/operation_guard.mjs +162 -0
- package/{scripts/lib/rehearsal → src/shared}/process_environment.mjs +5 -1
- package/src/source/access.mjs +578 -0
- package/src/source/asset_transfer.mjs +177 -0
- package/src/source/baseline.mjs +446 -0
- package/src/source/postgresql_access.mjs +480 -0
- package/{scripts/lib/runtime/postgresql_runtime.mjs → src/targets/postgresql.mjs} +45 -10
- package/{scripts/lib/runtime/supabase_runtime.mjs → src/targets/supabase.mjs} +50 -13
- package/{scripts/lib/environment/local_supabase.mjs → src/targets/supabase_environment.mjs} +52 -20
- package/{scripts/lib/runtime/runtime_target.mjs → src/targets/target.mjs} +2 -2
- package/scripts/operations/rehearsal/rehearsal_cli.mjs +0 -2169
- /package/{scripts/lib/rehearsal → src/baseline}/input_discovery.mjs +0 -0
- /package/{scripts/lib/rehearsal → src/baseline}/policy_review.mjs +0 -0
- /package/{scripts/lib/rehearsal → src/runtime}/migration_history.mjs +0 -0
- /package/{scripts/lib/rehearsal → src/runtime}/service_environment.mjs +0 -0
- /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 "../
|
|
19
|
+
} from "../baseline/artifact.mjs";
|
|
20
20
|
import {
|
|
21
21
|
createCleanProcessEnvironment,
|
|
22
22
|
assertLoopbackUrl,
|
|
23
|
-
} from "../
|
|
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 "
|
|
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 "../
|
|
38
|
+
} from "../runtime/restore.mjs";
|
|
39
39
|
import {
|
|
40
40
|
buildMigrationLedgerInventory,
|
|
41
41
|
compareSourceToReplay,
|
|
42
42
|
createMigrationReplayReceipt,
|
|
43
43
|
readMigrationFileInventory,
|
|
44
|
-
} from "../
|
|
45
|
-
import { loadRehearsalConfig } from "../
|
|
46
|
-
import { readRehearsalServiceEnvironment } from "../
|
|
47
|
-
import { readBoundRuntimeSanitizationPolicy } from "../
|
|
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 "../
|
|
52
|
-
import { formatCount } from "../
|
|
53
|
-
import { parseRuntimeInvocation } from "./
|
|
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)
|
|
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(
|
|
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 "../
|
|
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
|
-
|
|
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("./
|
|
58
|
+
await import("./supabase.mjs");
|
|
59
59
|
return;
|
|
60
60
|
case "postgresql":
|
|
61
|
-
await import("./
|
|
61
|
+
await import("./postgresql.mjs");
|
|
62
62
|
return;
|
|
63
63
|
default:
|
|
64
64
|
throw new Error(`No runtime driver is installed for ${resolved.id}.`);
|