@rehearsal-db/core 0.1.0-beta.11 → 0.1.0-beta.13
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 +77 -1
- package/COMPATIBILITY.md +6 -0
- package/README.md +11 -2
- package/docs/commands.md +18 -13
- package/docs/configuration.md +78 -12
- package/docs/getting-started.md +14 -4
- package/docs/production-source.md +138 -16
- package/docs/runtime-policies.md +4 -2
- package/docs/sanitization.md +73 -7
- package/docs/security-model.md +8 -0
- package/docs/standalone-workflow.md +24 -9
- package/docs/troubleshooting.md +26 -8
- package/docs/tutorial.md +2 -1
- package/package.json +4 -3
- package/src/baseline/artifact.mjs +27 -4
- package/src/baseline/privacy_engine.mjs +220 -9
- package/src/cli/arguments.mjs +3 -0
- package/src/cli/guided.mjs +8 -6
- package/src/cli/init_command.mjs +17 -0
- package/src/cli/rehearsal.mjs +20 -2
- package/src/cli/renderers.mjs +41 -4
- package/src/cli/runtime_commands.mjs +92 -9
- package/src/cli/source_commands.mjs +26 -5
- package/src/identity/provider_configuration.mjs +177 -0
- package/src/project/configuration.d.mts +11 -0
- package/src/project/configuration.mjs +155 -19
- package/src/project/setup.mjs +5 -23
- package/src/runtime/plan.mjs +40 -3
- package/src/runtime/restore.mjs +13 -5
- package/src/shared/diagnostics.mjs +7 -0
- package/src/shared/node_runtime.mjs +31 -0
- package/src/source/access.mjs +224 -49
- package/src/source/asset_transfer.mjs +35 -5
- package/src/source/baseline.mjs +42 -6
- package/src/source/external_reader.mjs +255 -0
- package/src/source/postgresql_access.mjs +143 -42
- package/src/source/privacy_paths.mjs +38 -0
- package/src/targets/supabase.mjs +11 -1
package/docs/sanitization.md
CHANGED
|
@@ -40,10 +40,10 @@ Two policy versions exist:
|
|
|
40
40
|
Rehearsal can sanitize without project callbacks.
|
|
41
41
|
|
|
42
42
|
Version 2 supports keyed UUID, email, text, and integer pseudonyms; explicit constant
|
|
43
|
-
replacement; bounded date shifting;
|
|
44
|
-
JSON objects; and bounded JSON
|
|
45
|
-
|
|
46
|
-
from the policy.
|
|
43
|
+
replacement; bounded and grouped date shifting; identity-aware path mapping;
|
|
44
|
+
approved-owner conditionals; exhaustively classified JSON objects; and bounded JSON
|
|
45
|
+
arrays. Unknown tables, columns, nested keys, formats, recipes, or missing required
|
|
46
|
+
fields fail closed. It never evaluates JavaScript or SQL from the policy.
|
|
47
47
|
|
|
48
48
|
## Approved-owner values
|
|
49
49
|
|
|
@@ -69,7 +69,7 @@ environment-variable name and SHA-256 receipt:
|
|
|
69
69
|
"action": "PSEUDONYMIZE",
|
|
70
70
|
"recipe": { "format": "uuid", "namespace": "account-id" },
|
|
71
71
|
"generated": "NEVER",
|
|
72
|
-
"identity": "
|
|
72
|
+
"identity": "NO",
|
|
73
73
|
"foreignKey": null
|
|
74
74
|
},
|
|
75
75
|
{
|
|
@@ -101,12 +101,78 @@ node -e 'const {createHash}=require("node:crypto"); process.stdout.write(createH
|
|
|
101
101
|
|
|
102
102
|
Every conditional table names its owner column. Missing bindings, hash mismatches,
|
|
103
103
|
missing owner columns, null/unsupported owner values, and conditionals without an owner
|
|
104
|
-
binding are refused. The owner column must
|
|
105
|
-
|
|
104
|
+
binding are refused. The owner column must use `PSEUDONYMIZE`, preventing a conditional
|
|
105
|
+
policy from retaining raw account IDs. `identity` describes PostgreSQL's generated SQL
|
|
106
|
+
identity metadata, not whether a value identifies a person: ordinary UUID primary keys
|
|
107
|
+
use `"NO"`, while a genuine `GENERATED ... AS IDENTITY` column uses `"YES"`. Rehearsal
|
|
108
|
+
checks the restored PostgreSQL schema for a real backing sequence before attempting a
|
|
109
|
+
sequence reset, so UUID identity values never receive numeric sequence operations. Other
|
|
106
110
|
owners take the declared `otherwise` path; every other identity-bearing field still
|
|
107
111
|
needs its own pseudonymization declaration. Supply the binding only to the local
|
|
108
112
|
server-side Rehearsal process; do not put it in the policy, baseline, or tracked files.
|
|
109
113
|
|
|
114
|
+
## Identity-aware Storage paths
|
|
115
|
+
|
|
116
|
+
Use one named mapping when a database column and a physical Storage object both begin
|
|
117
|
+
with the approved owner's ID. Rehearsal pseudonymizes only that first segment and keeps
|
|
118
|
+
the remaining path unchanged:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"pathMappings": {
|
|
123
|
+
"owner-storage": {
|
|
124
|
+
"binding": "approved-owner",
|
|
125
|
+
"format": "uuid",
|
|
126
|
+
"namespace": "account-id",
|
|
127
|
+
"maximumBytes": 512,
|
|
128
|
+
"maximumSegments": 8
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
"tables": [
|
|
132
|
+
{
|
|
133
|
+
"name": "profiles",
|
|
134
|
+
"sourceRows": "STREAM AND SANITIZE",
|
|
135
|
+
"columns": [
|
|
136
|
+
{
|
|
137
|
+
"name": "avatar_path",
|
|
138
|
+
"action": "DERIVE",
|
|
139
|
+
"recipe": { "kind": "path-map", "mapping": "owner-storage" },
|
|
140
|
+
"generated": "NEVER",
|
|
141
|
+
"identity": "NO",
|
|
142
|
+
"foreignKey": null
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The source policy uses the same mapping name and names the binding's environment
|
|
151
|
+
variable as `prefixEnvironmentVariable`; it never contains the raw owner ID. During
|
|
152
|
+
`rehearsal refresh`, the package uses the mapping for both database values and physical
|
|
153
|
+
object destinations. A missing mapping, a different environment variable, a path whose
|
|
154
|
+
first segment is not the exact reviewed owner, traversal, or a size/segment limit breach
|
|
155
|
+
stops the refresh.
|
|
156
|
+
|
|
157
|
+
## Related dates
|
|
158
|
+
|
|
159
|
+
Give related timestamps the same `group` and `days` value to shift them by one stable
|
|
160
|
+
offset. This preserves their order and duration:
|
|
161
|
+
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"name": "starts_at",
|
|
165
|
+
"action": "DERIVE",
|
|
166
|
+
"recipe": { "kind": "date-shift", "days": 30, "group": "event-window" },
|
|
167
|
+
"generated": "NEVER",
|
|
168
|
+
"identity": "NO",
|
|
169
|
+
"foreignKey": null
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Apply the same recipe to `ends_at`. Rehearsal refuses a group that declares conflicting
|
|
174
|
+
`days` values. Leave out `group` when a date should shift independently.
|
|
175
|
+
|
|
110
176
|
## Structured JSON
|
|
111
177
|
|
|
112
178
|
`json-object` fields are required by default. Set `"required": false` on an explicitly
|
package/docs/security-model.md
CHANGED
|
@@ -26,6 +26,9 @@ runtime.
|
|
|
26
26
|
- successful receipts written only after verification.
|
|
27
27
|
- separate exact-digest approval for source setup, source retirement, and identity claim;
|
|
28
28
|
- short-lived source readers limited to reviewed views/columns and deny checks;
|
|
29
|
+
- externally provisioned readers require canonical view-definition receipts,
|
|
30
|
+
`security_barrier`, independent ownership, an exact non-system readable-column surface,
|
|
31
|
+
no write or role-escalation paths, and database-enforced expiration;
|
|
29
32
|
- bounded read-only extraction with no raw dump or row-value logging;
|
|
30
33
|
- exhaustive privacy policy version 2 with an owner-only local pseudonym key;
|
|
31
34
|
- application children receive only explicit local mappings and stay in an owned process
|
|
@@ -39,6 +42,11 @@ No single environment variable or config edit can redirect runtime commands to
|
|
|
39
42
|
production. Optional source preparation is a separate boundary and never provides a
|
|
40
43
|
hosted execution mode.
|
|
41
44
|
|
|
45
|
+
External source mode verifies access but does not own it. Rehearsal makes no source-side
|
|
46
|
+
changes and removes only its local credential and receipt. The provider owns early
|
|
47
|
+
revocation and the database enforces the reviewed short expiration; Rehearsal never
|
|
48
|
+
reports local cleanup as proof that a provider-owned role was deleted.
|
|
49
|
+
|
|
42
50
|
## Identity providers
|
|
43
51
|
|
|
44
52
|
An optional external identity handshake is distinct from database access. Only declared
|
|
@@ -8,16 +8,18 @@ The optional preparation workflow is a separate security boundary. It has
|
|
|
8
8
|
three deliberately separate phases:
|
|
9
9
|
|
|
10
10
|
1. `source plan` reads declarations and produces a redacted, exact plan.
|
|
11
|
-
2. `source apply` requires the plan digest
|
|
12
|
-
named environment variable
|
|
13
|
-
|
|
11
|
+
2. `source apply` requires the plan digest. Managed mode uses source-owner credentials
|
|
12
|
+
from a named environment variable and creates only the declared, time-limited reader
|
|
13
|
+
and export surface. External mode verifies an already restricted reader without an
|
|
14
|
+
administrator credential or source-side writes.
|
|
14
15
|
3. `refresh` uses that reader in a read-only repeatable-read transaction, builds
|
|
15
16
|
privately, activates only after verification, resets the local runtime, and applies
|
|
16
17
|
reviewed retention. `baseline refresh` is the lower-level baseline-only form.
|
|
17
18
|
|
|
18
|
-
`source retire` has its own preview and digest.
|
|
19
|
-
reader and export objects Rehearsal recorded.
|
|
20
|
-
|
|
19
|
+
`source retire` has its own preview and digest. Managed mode removes only the exact
|
|
20
|
+
reader and export objects Rehearsal recorded. External mode removes only local Rehearsal
|
|
21
|
+
credentials and receipts, preserving provider-owned objects. It never searches for
|
|
22
|
+
similarly named roles or removes unrelated grants.
|
|
21
23
|
|
|
22
24
|
## What belongs in declarations
|
|
23
25
|
|
|
@@ -46,9 +48,12 @@ separate. A declaration contains only the name of an environment variable or
|
|
|
46
48
|
owner-only secret file. Rehearsal never puts credential values in command-line
|
|
47
49
|
arguments, reports, baselines, receipts, or a launched application.
|
|
48
50
|
|
|
49
|
-
The
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
The default managed workflow creates and verifies its own temporary PostgreSQL reader.
|
|
52
|
+
External mode accepts a provider-managed reader only when its complete non-system
|
|
53
|
+
readable-column surface exactly matches the reviewed export views and migration ledger.
|
|
54
|
+
It also verifies canonical view-definition fingerprints, `security_barrier`, ownership,
|
|
55
|
+
write denial, role isolation, security-definer denial, and a short database-enforced
|
|
56
|
+
expiration. Rehearsal never claims ownership or revocation of provider-managed objects.
|
|
52
57
|
|
|
53
58
|
## Snapshot and privacy guarantees
|
|
54
59
|
|
|
@@ -57,6 +62,16 @@ read-only repeatable-read PostgreSQL transaction. Rehearsal rechecks the source
|
|
|
57
62
|
identity and migration evidence before commit. Rows stream in bounded batches;
|
|
58
63
|
record bodies never appear in progress or error output.
|
|
59
64
|
|
|
65
|
+
The snapshot boundary is deliberate: row commits made after extraction starts are left
|
|
66
|
+
for the next baseline, while concurrent schema or migration-ledger changes block the
|
|
67
|
+
current activation. Invalid estimates, malformed batches, connection loss, interruption,
|
|
68
|
+
and disk or configured-limit failures remove private staging and preserve the previously
|
|
69
|
+
active baseline.
|
|
70
|
+
|
|
71
|
+
Cleanup recursively makes only Rehearsal's private staging tree removable. A failed
|
|
72
|
+
Storage transfer therefore reports the original safe transfer error, removes incomplete
|
|
73
|
+
files, and does not alter the active generation.
|
|
74
|
+
|
|
60
75
|
Storage objects do not share the database transaction guarantee. Their inventory records
|
|
61
76
|
size and version metadata; transfer requires that version, verifies the exact byte count,
|
|
62
77
|
and records a SHA-256 in the immutable baseline. Transfer refuses an object that changes,
|
package/docs/troubleshooting.md
CHANGED
|
@@ -34,14 +34,15 @@ Choose **Set the stage** and approve the preview. For pnpm and Yarn, use this gu
|
|
|
34
34
|
automatic root generation is currently tested with npm. An existing supported config is
|
|
35
35
|
kept in place, including `infrastructure/rehearsal/rehearsal.config.mjs`.
|
|
36
36
|
|
|
37
|
-
## Setup says Node.js
|
|
37
|
+
## Setup says this Node.js version is unsupported
|
|
38
38
|
|
|
39
|
-
Rehearsal
|
|
40
|
-
the current shell before installing or
|
|
39
|
+
Rehearsal supports the maintained Node.js 22, 24, and 26 release lines. Node.js 20 and
|
|
40
|
+
odd-numbered releases are end-of-life. Switch the current shell before installing or
|
|
41
|
+
running Rehearsal. The latest LTS is the recommended default:
|
|
41
42
|
|
|
42
43
|
```bash
|
|
43
|
-
nvm install
|
|
44
|
-
nvm use
|
|
44
|
+
nvm install --lts
|
|
45
|
+
nvm use --lts
|
|
45
46
|
node --version
|
|
46
47
|
```
|
|
47
48
|
|
|
@@ -53,6 +54,12 @@ writes setup files.
|
|
|
53
54
|
Start Docker Desktop or Colima, confirm `docker info`, then rerun `rehearsal doctor`.
|
|
54
55
|
Restarting the computer is rarely necessary.
|
|
55
56
|
|
|
57
|
+
When a container stays in `starting` or becomes `unhealthy`, Rehearsal reports a runtime
|
|
58
|
+
dependency failure rather than a migration failure. The safe diagnostic includes Docker
|
|
59
|
+
CPU, memory, and container counts; Colima also includes available VM memory when it can
|
|
60
|
+
be read. Stop unrelated local stacks or increase the engine's resources before retrying.
|
|
61
|
+
Rehearsal never changes engine settings or bypasses service health checks.
|
|
62
|
+
|
|
56
63
|
## Docker or Colima is out of disk space
|
|
57
64
|
|
|
58
65
|
Preview what Rehearsal can safely remove:
|
|
@@ -146,9 +153,20 @@ changes.
|
|
|
146
153
|
|
|
147
154
|
## Local authentication fails
|
|
148
155
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
156
|
+
For Google or GitHub, confirm `supabase.authentication` is declared in
|
|
157
|
+
`rehearsal.config.mjs`, its ignored environment file exists with `chmod 600`, and the
|
|
158
|
+
provider has the printed local Auth callback registered. Rehearsal generates the
|
|
159
|
+
disposable Supabase provider block.
|
|
160
|
+
|
|
161
|
+
When migrating from the legacy service environment fields, replace those fields together
|
|
162
|
+
with `supabase.authentication` and rerun `rehearsal doctor`. A compatible Google or
|
|
163
|
+
GitHub provider section may remain in the tracked template: Rehearsal supersedes it only
|
|
164
|
+
inside the generated runtime copy. Doctor refuses unknown provider settings, duplicate
|
|
165
|
+
or nested provider sections, and nonce or optional-email settings that do not match the
|
|
166
|
+
new declaration. Keep both options false unless there is a reviewed provider-specific
|
|
167
|
+
need; skipping nonce validation reduces replay protection. Otherwise keep using the
|
|
168
|
+
documented legacy configuration.
|
|
169
|
+
Never paste provider secrets into tracked config or terminal output.
|
|
152
170
|
|
|
153
171
|
If `identity plan` refuses the matcher, read the message literally:
|
|
154
172
|
|
package/docs/tutorial.md
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
This tutorial runs a complete PostgreSQL rehearsal in a temporary fictional project. It
|
|
4
4
|
does not use your application, production data, or a hosted database.
|
|
5
5
|
|
|
6
|
-
Allow about five minutes. You need Node.js 24 and a running
|
|
6
|
+
Allow about five minutes. You need Node.js 22, 24, or 26 and a running
|
|
7
|
+
Docker-compatible engine. Node.js 24 LTS is recommended.
|
|
7
8
|
|
|
8
9
|
## 1. Prepare Rehearsal
|
|
9
10
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rehearsal-db/core",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.13",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Safely rehearse PostgreSQL and Supabase migrations against sanitized, production-shaped data.",
|
|
6
6
|
"repository": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"bugs": {
|
|
11
11
|
"url": "https://github.com/Ddupasquier/rehearsal-db/issues"
|
|
12
12
|
},
|
|
13
|
-
"homepage": "https://github.
|
|
13
|
+
"homepage": "https://ddupasquier.github.io/rehearsal-db/",
|
|
14
14
|
"publishConfig": {
|
|
15
15
|
"access": "public",
|
|
16
16
|
"tag": "beta",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"type": "module",
|
|
20
20
|
"engines": {
|
|
21
|
-
"node": "
|
|
21
|
+
"node": "^22.0.0 || ^24.0.0 || ^26.0.0"
|
|
22
22
|
},
|
|
23
23
|
"bin": {
|
|
24
24
|
"rehearsal": "src/cli/rehearsal.mjs"
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"test:fixture": "node scripts/verification/fixtures/supabase.mjs",
|
|
63
63
|
"test:fixture:postgresql": "node scripts/verification/fixtures/postgresql.mjs",
|
|
64
64
|
"test:fixture:dependent": "node scripts/verification/fixtures/dependent.mjs",
|
|
65
|
+
"test:fixture:onboarding": "node scripts/verification/fixtures/onboarding.mjs",
|
|
65
66
|
"test:fixture:standalone": "node scripts/verification/fixtures/standalone.mjs",
|
|
66
67
|
"test:fixture:privacy": "node scripts/verification/fixtures/privacy.mjs",
|
|
67
68
|
"check": "npm run check:syntax && npm run test && npm run docs:build && npm run format:check && npm run package:audit",
|
|
@@ -372,12 +372,35 @@ export const createAndActivateBaseline = async ({
|
|
|
372
372
|
}
|
|
373
373
|
return manifest;
|
|
374
374
|
} catch (error) {
|
|
375
|
+
let cleanupFailure;
|
|
375
376
|
if (dataHandle) await dataHandle.close().catch(() => undefined);
|
|
376
|
-
await
|
|
377
|
-
await rm(buildDirectory, { recursive: true, force: true })
|
|
377
|
+
await makeArtifactTreeWritable(buildDirectory).catch(() => undefined);
|
|
378
|
+
await rm(buildDirectory, { recursive: true, force: true }).catch(
|
|
379
|
+
(cleanupError) => {
|
|
380
|
+
cleanupFailure = cleanupError;
|
|
381
|
+
},
|
|
382
|
+
);
|
|
378
383
|
if (finalCreated) {
|
|
379
|
-
await
|
|
380
|
-
await rm(finalDirectory, { recursive: true, force: true })
|
|
384
|
+
await makeArtifactTreeWritable(finalDirectory).catch(() => undefined);
|
|
385
|
+
await rm(finalDirectory, { recursive: true, force: true }).catch(
|
|
386
|
+
(cleanupError) => {
|
|
387
|
+
cleanupFailure ??= cleanupError;
|
|
388
|
+
},
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
if (
|
|
392
|
+
cleanupFailure &&
|
|
393
|
+
error &&
|
|
394
|
+
(typeof error === "object" || typeof error === "function")
|
|
395
|
+
) {
|
|
396
|
+
try {
|
|
397
|
+
Object.defineProperty(error, "cleanupError", {
|
|
398
|
+
value: cleanupFailure,
|
|
399
|
+
enumerable: false,
|
|
400
|
+
});
|
|
401
|
+
} catch {
|
|
402
|
+
// Preserve the preparation failure even when its Error is immutable.
|
|
403
|
+
}
|
|
381
404
|
}
|
|
382
405
|
throw error;
|
|
383
406
|
}
|