@rehearsal-db/core 0.1.0-beta.12 → 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 CHANGED
@@ -5,6 +5,46 @@ a Changelog, and versions will follow Semantic Versioning after the package exis
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ### Added
9
+
10
+ - Runtime and CI compatibility now cover maintained Node.js 22, 24, and 26 releases;
11
+ Node.js 24 LTS remains the recommended default and release-build runtime.
12
+ - Source access policies can use an externally provisioned, database-expiring
13
+ PostgreSQL reader without administrator credentials. Rehearsal binds canonical export
14
+ view definitions, verifies the complete approved read surface and deny controls, and
15
+ never changes or retires provider-owned source objects.
16
+ - `rehearsal --version`, its conventional `-V` alias, and `rehearsal version` now
17
+ report the installed package version without requiring project configuration.
18
+ - Privacy policy version 2 can use one reviewed identity-aware path mapping for both
19
+ database references and physical Storage destinations. Owner prefixes stay in the
20
+ environment, review plans retain only their hashes, and unsafe or mismatched paths
21
+ fail closed.
22
+ - Related timestamps can share a named deterministic date-shift group, preserving their
23
+ ordering and duration without consumer callbacks.
24
+ - The baseline verification corpus now exercises two unrelated target schemas, a
25
+ 10,003-row packed-package stream, concurrent row/schema/ledger changes, malformed and
26
+ truncated results, connection loss, low disk, interruption, and deterministic
27
+ fixed-key output.
28
+
29
+ ### Fixed
30
+
31
+ - Baseline preparation now rejects missing, negative, overflowing, malformed, or
32
+ oversized PostgreSQL estimate and cursor results before activation. Every preparation
33
+ failure preserves the active baseline, removes private staging, and leaves an edited
34
+ runtime untouched.
35
+ - Approved-owner UUID columns are no longer confused with PostgreSQL sequence-backed
36
+ identities. Restore checks the actual local schema before resetting a sequence, while
37
+ genuine numeric identity columns retain correct next-value behavior.
38
+ - A failed Storage transfer now recursively restores write permission only within its
39
+ owned staging tree, removes that incomplete generation, and preserves the original
40
+ safe preparation error instead of replacing it with a cleanup error.
41
+ - Storage preparation now records a canonical media type from reviewed object metadata,
42
+ removing response-only parameters such as a text charset that local bucket MIME
43
+ allowlists reject during restore.
44
+ - Container startup and health failures are now classified as runtime dependency
45
+ failures and include safe Docker/Colima capacity context instead of being mislabeled
46
+ as migration verification failures.
47
+
8
48
  ## [0.1.0-beta.12] - 2026-10-04
9
49
 
10
50
  ### Added
package/COMPATIBILITY.md CHANGED
@@ -21,6 +21,12 @@ The first stable `1.0.0` requires external beta evidence, a support policy, and
21
21
  public API. Supporting additional database families, package managers, or operating
22
22
  systems is not implied by the 0.x contract.
23
23
 
24
+ Rehearsal supports maintained even-numbered Node.js release lines that pass its CI
25
+ matrix. Beta.13 supports Node.js 22, 24, and 26; Node.js 24 LTS is the recommended
26
+ default and the release-build runtime. End-of-life and odd-numbered Node.js releases are
27
+ not supported. A future release may remove a Node.js line after its upstream end-of-life
28
+ date, with that change called out in the changelog.
29
+
24
30
  The npm install hook may create project scaffolding only when it proves one application
25
31
  root. Ordinary npm application installs are covered. A workspace is automatic only when
26
32
  one declaring consumer is already visible in its manifest or lockfile; npm can save a
package/README.md CHANGED
@@ -22,7 +22,7 @@ production deployment tool.
22
22
 
23
23
  ## What you need
24
24
 
25
- - Node.js 24 and npm
25
+ - Node.js 22, 24, or 26 and npm (Node.js 24 LTS recommended)
26
26
  - Docker Desktop, Colima, or another Docker-compatible engine
27
27
  - timestamped `.sql` migration files
28
28
  - a project test command that can prove the migrated application works
@@ -169,7 +169,7 @@ problems belong in the private process described in [SECURITY.md](SECURITY.md).
169
169
 
170
170
  ## For contributors
171
171
 
172
- Use Node.js 24, run `npm ci`, then run:
172
+ Use a supported Node.js release (22, 24, or 26), run `npm ci`, then run:
173
173
 
174
174
  ```bash
175
175
  npm run check
package/docs/commands.md CHANGED
@@ -43,17 +43,17 @@ existing config.
43
43
  These commands are separate from ordinary rehearsals. Configure `preparation` only after
44
44
  reviewing [the standalone security contract](standalone-workflow.md).
45
45
 
46
- | Command | What it does |
47
- | ------------------------------------------------------------- | -------------------------------------------------------------------- |
48
- | `npx rehearsal privacy key` | Preview the owner-only pseudonym-key path. |
49
- | `npx rehearsal privacy key --write` | Create the key once; refuses to replace an existing key. |
50
- | `npx rehearsal source plan` | Preview the exact temporary reader, views, columns, and asset scope. |
51
- | `npx rehearsal source apply --confirm-source-access=<sha256>` | Create only the reviewed, time-limited source access. |
52
- | `npx rehearsal baseline refresh` | Replace only the baseline from a reviewed source. |
53
- | `npx rehearsal refresh` | Preview a complete safe refresh of the baseline and local runtime. |
54
- | Add `--confirm-refresh=<sha256>` | Create the replacement, reset locally, and prune listed old copies. |
55
- | `npx rehearsal source retire` | Preview exact reader/view/credential retirement. |
56
- | Add `--confirm-source-retirement=<sha256>` | Apply that retirement plan and verify the exact object inventory. |
46
+ | Command | What it does |
47
+ | ------------------------------------------------------------- | ----------------------------------------------------------------------- |
48
+ | `npx rehearsal privacy key` | Preview the owner-only pseudonym-key path. |
49
+ | `npx rehearsal privacy key --write` | Create the key once; refuses to replace an existing key. |
50
+ | `npx rehearsal source plan` | Preview the exact temporary reader, views, columns, and asset scope. |
51
+ | `npx rehearsal source apply --confirm-source-access=<sha256>` | Create managed access, or verify external access without source writes. |
52
+ | `npx rehearsal baseline refresh` | Replace only the baseline from a reviewed source. |
53
+ | `npx rehearsal refresh` | Preview a complete safe refresh of the baseline and local runtime. |
54
+ | Add `--confirm-refresh=<sha256>` | Create the replacement, reset locally, and prune listed old copies. |
55
+ | `npx rehearsal source retire` | Preview managed retirement or external local-secret cleanup. |
56
+ | Add `--confirm-source-retirement=<sha256>` | Apply that retirement plan and verify the exact object inventory. |
57
57
 
58
58
  Source credentials are read from environment variables named in the reviewed source
59
59
  policy. Never paste a connection string into an argument. `baseline refresh` leaves the
@@ -176,6 +176,8 @@ belonging to other projects are never included. This follows
176
176
 
177
177
  | Option | Meaning |
178
178
  | -------------------------------------- | ---------------------------------------------------------------- |
179
+ | `--help`, `-h` | Show the installed command reference. |
180
+ | `--version`, `-V` | Print the installed package version and exit. |
179
181
  | `--json` | Return the versioned machine-readable result. |
180
182
  | `--verbose` | Show more safe detail. |
181
183
  | `--debug` | Show the most diagnostic detail; still review it before sharing. |
@@ -193,6 +195,8 @@ belonging to other projects are never included. This follows
193
195
  | `--write` | Apply a setup, policy, or cleanup preview. |
194
196
 
195
197
  Run `npx rehearsal --help` to print the command list available in your installed version.
198
+ Run `npx rehearsal --version` to print only its version number. Add `--json` when a
199
+ script also needs the package name.
196
200
 
197
201
  When a project has more than one config, pass `--config=<path>` on every command for the
198
202
  non-default one. Rehearsal keeps that exact config selected through status, run, reset,
@@ -7,7 +7,8 @@ Want to experiment first? Use the [safe hands-on tutorial](tutorial.md).
7
7
 
8
8
  ## 1. Check the required tools
9
9
 
10
- You need Node.js 24, npm, and a running Docker-compatible engine:
10
+ You need Node.js 22, 24, or 26, npm, and a running Docker-compatible engine. Node.js 24
11
+ LTS is the recommended default:
11
12
 
12
13
  ```bash
13
14
  node --version
@@ -15,8 +16,8 @@ npm --version
15
16
  docker info
16
17
  ```
17
18
 
18
- `node --version` must begin with `v24`. If `docker info` fails, start Docker Desktop,
19
- Colima, or your usual Docker engine.
19
+ `node --version` must begin with `v22`, `v24`, or `v26`. If `docker info` fails, start
20
+ Docker Desktop, Colima, or your usual Docker engine.
20
21
 
21
22
  Choose the extra requirement for your project:
22
23
 
@@ -33,6 +33,7 @@ URLs, or raw owner identifiers.
33
33
  "targetFingerprint": "<sha256 of the approved PostgreSQL host, port, and database>",
34
34
  "administratorEnvironmentVariable": "REHEARSAL_SOURCE_ADMIN_URL",
35
35
  "reader": {
36
+ "mode": "managed",
36
37
  "role": "rehearsal_example_reader",
37
38
  "ownerRole": "rehearsal_example_owner",
38
39
  "credentialFile": ".rehearsal/secrets/source-reader.env",
@@ -61,7 +62,7 @@ URLs, or raw owner identifiers.
61
62
  "view": "approved_owner_profile",
62
63
  "targetSchema": "app_api",
63
64
  "targetTable": "profiles",
64
- "columns": ["id", "email"],
65
+ "columns": ["id", "email", "avatar_path"],
65
66
  "orderBy": ["id"],
66
67
  "rowScope": {
67
68
  "kind": "approved-owner",
@@ -70,10 +71,28 @@ URLs, or raw owner identifiers.
70
71
  }
71
72
  }
72
73
  ],
73
- "assets": []
74
+ "assetReader": {
75
+ "endpointFingerprint": "<sha256 of the approved Storage endpoint>",
76
+ "baseUrlEnvironmentVariable": "REHEARSAL_SOURCE_STORAGE_URL",
77
+ "tokenEnvironmentVariable": "REHEARSAL_SOURCE_STORAGE_TOKEN",
78
+ "maximumObjects": 1000,
79
+ "maximumObjectBytes": 52428800,
80
+ "maximumTotalBytes": 2147483648
81
+ },
82
+ "assets": [
83
+ {
84
+ "bucket": "avatars",
85
+ "prefixEnvironmentVariable": "REHEARSAL_APPROVED_OWNER_ID",
86
+ "rights": "approved-owner",
87
+ "pathMapping": "owner-storage"
88
+ }
89
+ ]
74
90
  }
75
91
  ```
76
92
 
93
+ `managed` is the default for existing policies, but writing it explicitly makes the
94
+ credential boundary easier to review.
95
+
77
96
  Calculate fingerprints with the exported helpers from `@rehearsal-db/core/source-access`
78
97
  or a reviewed local script. A fingerprint binds the destination without recording its
79
98
  address. Changing the host, port, or database invalidates the plan.
@@ -82,13 +101,72 @@ address. Changing the host, port, or database invalidates the plan.
82
101
  use. `approved-owner` requires one explicit owner value from the named environment
83
102
  variable. Rehearsal never chooses an owner by finding an administrator.
84
103
 
104
+ ### Use a provider-managed reader
105
+
106
+ If a database owner already supplies the restricted export views and reader, use
107
+ `mode: "external"`. Omit `administratorEnvironmentVariable`; Rehearsal will not request
108
+ an administrator credential or change the source:
109
+
110
+ ```json
111
+ {
112
+ "reader": {
113
+ "mode": "external",
114
+ "role": "provided_rehearsal_reader",
115
+ "connectionEnvironmentVariable": "REHEARSAL_SOURCE_READER_URL",
116
+ "credentialFile": ".rehearsal/secrets/source-reader.env",
117
+ "maximumValidForMinutes": 30
118
+ },
119
+ "exportSchema": "provided_rehearsal_export",
120
+ "relations": [
121
+ {
122
+ "source": { "schema": "private", "table": "profiles" },
123
+ "view": "approved_owner_profile",
124
+ "targetSchema": "app_api",
125
+ "targetTable": "profiles",
126
+ "columns": ["id", "email", "avatar_path"],
127
+ "orderBy": ["id"],
128
+ "rowScope": {
129
+ "kind": "approved-owner",
130
+ "column": "id",
131
+ "valueEnvironmentVariable": "REHEARSAL_APPROVED_OWNER_ID"
132
+ },
133
+ "viewDefinitionSha256": "<sha256 from externalViewDefinitionFingerprint>"
134
+ }
135
+ ]
136
+ }
137
+ ```
138
+
139
+ The database owner must create a `security_barrier` view owned by a role other than the
140
+ reader. Record PostgreSQL's exact canonical definition, not handwritten SQL:
141
+
142
+ ```js
143
+ import { externalViewDefinitionFingerprint } from "@rehearsal-db/core/source-access";
144
+
145
+ console.log(externalViewDefinitionFingerprint(canonicalViewDefinition));
146
+ ```
147
+
148
+ Obtain `canonicalViewDefinition` with
149
+ `select pg_get_viewdef('provided_rehearsal_export.approved_owner_profile'::regclass, false)`.
150
+ Rehearsal verifies that fingerprint before saving any local access receipt.
151
+ The resulting receipt also binds the normalized source-policy fingerprint. Editing the
152
+ view hash, columns, scopes, or reader declaration requires a new verification.
153
+
154
+ External mode also requires all non-system readable columns to match only the declared
155
+ views and migration-ledger columns. It refuses database, schema, relation, or column
156
+ writes; inherited roles; privileged role attributes; executable non-system
157
+ security-definer functions; a reader-owned view; or a database-enforced expiration
158
+ beyond `maximumValidForMinutes`.
159
+
85
160
  `targetSchema` is optional and defaults to `public`. Set it when the restored relation
86
161
  belongs elsewhere, and use the same schema in privacy policy version 2. This is relation
87
162
  identity, not a request to search every schema.
88
163
 
89
- For Storage, add an `assetReader` block and approved `assets` entries. The reader URL and
90
- token come from named environment variables. Objects outside the exact bucket/prefix are
91
- not listed or downloaded. Size, object-count, path, ETag, and exact-byte checks apply.
164
+ For Storage, the reader URL and token come from named environment variables. A public,
165
+ non-identity prefix may use `prefix`. A private owner prefix uses
166
+ `prefixEnvironmentVariable`, `rights: "approved-owner"`, and a `pathMapping` declared in
167
+ the privacy policy below. Its raw value stays out of tracked files and the review plan
168
+ records only its SHA-256. Objects outside the exact bucket/prefix are not listed or
169
+ downloaded. Size, object-count, path, ETag, and exact-byte checks apply.
92
170
 
93
171
  ## 3. Use executable privacy policy version 2
94
172
 
@@ -98,6 +176,21 @@ Every selected column has one action and, where required, a bounded recipe:
98
176
  {
99
177
  "policyVersion": 2,
100
178
  "migrationCutoff": "20260101000000",
179
+ "bindings": {
180
+ "approved-owner": {
181
+ "environmentVariable": "REHEARSAL_APPROVED_OWNER_ID",
182
+ "approvedValueSha256": "<sha256 of the exact approved owner ID>"
183
+ }
184
+ },
185
+ "pathMappings": {
186
+ "owner-storage": {
187
+ "binding": "approved-owner",
188
+ "format": "uuid",
189
+ "namespace": "account-id",
190
+ "maximumBytes": 512,
191
+ "maximumSegments": 8
192
+ }
193
+ },
101
194
  "tables": [
102
195
  {
103
196
  "schema": "app_api",
@@ -119,6 +212,14 @@ Every selected column has one action and, where required, a bounded recipe:
119
212
  "generated": "NEVER",
120
213
  "identity": "NO",
121
214
  "foreignKey": null
215
+ },
216
+ {
217
+ "name": "avatar_path",
218
+ "action": "DERIVE",
219
+ "recipe": { "kind": "path-map", "mapping": "owner-storage" },
220
+ "generated": "NEVER",
221
+ "identity": "NO",
222
+ "foreignKey": null
122
223
  }
123
224
  ]
124
225
  }
@@ -127,9 +228,10 @@ Every selected column has one action and, where required, a bounded recipe:
127
228
  ```
128
229
 
129
230
  Supported recipes are intentionally small: keyed UUID/email/text/integer pseudonyms,
130
- explicit constants, bounded date shifts, and fully classified JSON objects. Unknown
131
- tables, columns, JSON keys, formats, and recipes stop the refresh. There is no JavaScript
132
- or SQL escape hatch.
231
+ explicit constants, bounded or grouped date shifts, identity-aware path mappings, and
232
+ fully classified JSON objects. Unknown tables, columns, JSON keys, formats, and recipes
233
+ stop the refresh. There is no JavaScript or SQL escape hatch. See
234
+ [Sanitization policy](sanitization.md) for complete path and date examples.
133
235
 
134
236
  ## 4. Preview, apply, refresh, and retire
135
237
 
@@ -146,10 +248,12 @@ npx rehearsal source retire
146
248
  npx rehearsal source retire --confirm-source-retirement=<full-sha256>
147
249
  ```
148
250
 
149
- `source apply` creates new time-limited roles and export views only when every exact name
150
- is unused. It verifies the reader cannot read raw tables, write, escalate roles, or call
151
- an exposed security-definer network function. Externally provisioned readers are not yet
152
- supported; use the managed reader so Rehearsal can prove its scope and retirement.
251
+ In managed mode, `source apply` creates new time-limited roles and export views only when
252
+ every exact name is unused. It verifies the reader cannot read raw tables, write,
253
+ escalate roles, or call an exposed security-definer network function. In external mode,
254
+ the same command performs only the read-only checks above and stores the already supplied
255
+ credential in the ignored owner-only local file. It never creates, grants, alters, or
256
+ drops a source object.
153
257
 
154
258
  `refresh` previews the complete replacement first. After exact confirmation, its baseline
155
259
  step uses one read-only repeatable-read transaction for the schema shape,
@@ -160,13 +264,31 @@ runtime is rolled back to the previous baseline and runtime. The lower-level `ba
160
264
  refresh` command remains available when automation should activate a baseline without
161
265
  resetting the runtime or pruning old generations.
162
266
 
267
+ Rows committed after the repeatable-read transaction begins belong to the next refresh;
268
+ they cannot leak into the current snapshot midway through streaming. A schema or ledger
269
+ change during preparation stops activation. Malformed batches, network loss, interruption,
270
+ limit overruns, and low disk also leave the previous baseline active. Because `baseline
271
+ refresh` never resets the runtime, local runtime edits remain in place whether that
272
+ baseline-only preparation succeeds or fails.
273
+
274
+ Privacy `identity` metadata describes PostgreSQL `GENERATED ... AS IDENTITY`, not a
275
+ person or account identifier. UUID account keys normally use `identity: "NO"` while
276
+ remaining explicitly pseudonymized. During restore, Rehearsal consults the actual local
277
+ table for a backing sequence before resetting it; UUID columns never receive numeric
278
+ `max` or `setval` operations.
279
+
163
280
  Storage cannot share PostgreSQL's transaction snapshot. Rehearsal instead verifies each
164
281
  object's inventory version and byte count during transfer, then records its SHA-256 in
165
- the immutable baseline.
282
+ the immutable baseline. For an identity-aware path, the database reference and physical
283
+ destination use the same package-owned pseudonym; the source suffix is preserved.
166
284
 
167
- `source retire` inventories the exact recorded views before removal, then removes only
168
- those views, the two recorded roles, and local credential/receipt files. Unrelated roles,
169
- grants, objects, baselines, runtimes, and Docker resources are preserved.
285
+ In managed mode, `source retire` inventories the exact recorded views before removal,
286
+ then removes only those views, the two recorded roles, and local credential/receipt
287
+ files. In external mode, it removes only Rehearsal's local credential and receipt; the
288
+ provider-owned role and views remain untouched. The database-enforced expiration still
289
+ limits access, and the provider may revoke it sooner. Rehearsal does not claim that local
290
+ cleanup revoked a provider-owned credential. Unrelated roles, grants, objects, baselines,
291
+ runtimes, and Docker resources are preserved.
170
292
 
171
293
  ## Limits
172
294
 
@@ -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; approved-owner conditionals; exhaustively classified
44
- JSON objects; and bounded JSON arrays. Unknown tables, columns, nested keys, formats,
45
- recipes, or missing required fields fail closed. It never evaluates JavaScript or SQL
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": "YES",
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 be marked `identity: "YES"` and use
105
- `PSEUDONYMIZE`, preventing a conditional policy from retaining raw account IDs. Other
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
@@ -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 and source-owner credentials from a
12
- named environment variable. It creates only the declared, time-limited
13
- reader and export surface.
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. It removes only the exact
19
- reader and export objects Rehearsal recorded. It never searches for similarly
20
- named roles or removes unrelated grants.
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 current workflow creates and verifies its own temporary PostgreSQL reader. An
50
- externally provisioned reader is not yet accepted because Rehearsal cannot currently
51
- prove that reader's complete scope and retirement behavior.
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,
@@ -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 24 is required
37
+ ## Setup says this Node.js version is unsupported
38
38
 
39
- Rehearsal intentionally supports one maintained Node.js major in its first beta. Switch
40
- the current shell before installing or running it. With nvm:
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 24
44
- nvm use 24
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:
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 Docker-compatible engine.
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.12",
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": {
@@ -18,7 +18,7 @@
18
18
  },
19
19
  "type": "module",
20
20
  "engines": {
21
- "node": ">=24 <25"
21
+ "node": "^22.0.0 || ^24.0.0 || ^26.0.0"
22
22
  },
23
23
  "bin": {
24
24
  "rehearsal": "src/cli/rehearsal.mjs"