@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.
Files changed (38) hide show
  1. package/CHANGELOG.md +77 -1
  2. package/COMPATIBILITY.md +6 -0
  3. package/README.md +11 -2
  4. package/docs/commands.md +18 -13
  5. package/docs/configuration.md +78 -12
  6. package/docs/getting-started.md +14 -4
  7. package/docs/production-source.md +138 -16
  8. package/docs/runtime-policies.md +4 -2
  9. package/docs/sanitization.md +73 -7
  10. package/docs/security-model.md +8 -0
  11. package/docs/standalone-workflow.md +24 -9
  12. package/docs/troubleshooting.md +26 -8
  13. package/docs/tutorial.md +2 -1
  14. package/package.json +4 -3
  15. package/src/baseline/artifact.mjs +27 -4
  16. package/src/baseline/privacy_engine.mjs +220 -9
  17. package/src/cli/arguments.mjs +3 -0
  18. package/src/cli/guided.mjs +8 -6
  19. package/src/cli/init_command.mjs +17 -0
  20. package/src/cli/rehearsal.mjs +20 -2
  21. package/src/cli/renderers.mjs +41 -4
  22. package/src/cli/runtime_commands.mjs +92 -9
  23. package/src/cli/source_commands.mjs +26 -5
  24. package/src/identity/provider_configuration.mjs +177 -0
  25. package/src/project/configuration.d.mts +11 -0
  26. package/src/project/configuration.mjs +155 -19
  27. package/src/project/setup.mjs +5 -23
  28. package/src/runtime/plan.mjs +40 -3
  29. package/src/runtime/restore.mjs +13 -5
  30. package/src/shared/diagnostics.mjs +7 -0
  31. package/src/shared/node_runtime.mjs +31 -0
  32. package/src/source/access.mjs +224 -49
  33. package/src/source/asset_transfer.mjs +35 -5
  34. package/src/source/baseline.mjs +42 -6
  35. package/src/source/external_reader.mjs +255 -0
  36. package/src/source/postgresql_access.mjs +143 -42
  37. package/src/source/privacy_paths.mjs +38 -0
  38. package/src/targets/supabase.mjs +11 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,81 @@ 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
+
48
+ ## [0.1.0-beta.12] - 2026-10-04
49
+
50
+ ### Added
51
+
52
+ - Supabase projects can declaratively configure local Google or GitHub OAuth in
53
+ `rehearsal.config.mjs`. Rehearsal derives the credential allowlist, injects only the
54
+ ignored owner-only environment values, and generates the exact loopback callback in
55
+ the disposable runtime without tracking secrets. Local signup requires an explicit
56
+ acknowledgment and is enabled only in that generated runtime copy.
57
+ - Branded, responsive documentation is now hosted on GitHub Pages with searchable
58
+ navigation, light and dark themes, and automatic deployment from `main`.
59
+ - The documentation home page and packaged README show the live monthly npm download
60
+ count and link directly to the npm package.
61
+ - Packed-package onboarding acceptance now proves both fresh root-config creation and a
62
+ real beta.11 upgrade that preserves the user's existing configuration byte for byte.
63
+
64
+ ### Changed
65
+
66
+ - The packaged README now links prominently to the hosted documentation, npm package,
67
+ and issue tracker. Package homepage metadata also points to the hosted documentation.
68
+ - GitHub Pages publishing uses the current Node.js 24 action generations without legacy
69
+ runtime warnings.
70
+ - `rehearsal init` now calls out important optional settings that are available but not
71
+ enabled before showing the current installed-release template.
72
+
73
+ ### Fixed
74
+
75
+ - Existing Google or GitHub provider sections can transition to declarative ownership
76
+ without editing the tracked Supabase template. Rehearsal replaces only a compatible
77
+ section in the disposable runtime copy, while `doctor` refuses ambiguous or
78
+ safety-weakening provider settings before reset.
79
+ - Multi-target plain-text plans now print each declarative OAuth callback, and `init`
80
+ advertises the declarative replacement even when a project still uses legacy provider
81
+ fields.
82
+
8
83
  ## [0.1.0-beta.11] - 2026-10-03
9
84
 
10
85
  ### Added
@@ -314,7 +389,8 @@ a Changelog, and versions will follow Semantic Versioning after the package exis
314
389
  publication uses short-lived trusted OIDC, and every release tag must already exist on
315
390
  protected `main`.
316
391
 
317
- [Unreleased]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-beta.11...HEAD
392
+ [Unreleased]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-beta.12...HEAD
393
+ [0.1.0-beta.12]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-beta.11...v0.1.0-beta.12
318
394
  [0.1.0-beta.11]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-beta.10...v0.1.0-beta.11
319
395
  [0.1.0-beta.10]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-beta.9...v0.1.0-beta.10
320
396
  [0.1.0-beta.9]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-beta.8...v0.1.0-beta.9
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
@@ -1,5 +1,9 @@
1
1
  # Rehearsal
2
2
 
3
+ [![Monthly npm downloads](https://img.shields.io/npm/dm/%40rehearsal-db%2Fcore?style=flat-square&logo=npm&logoColor=white&label=npm%20downloads&labelColor=1c2a34&color=287b5c)](https://www.npmjs.com/package/@rehearsal-db/core)
4
+
5
+ [Read the documentation](https://ddupasquier.github.io/rehearsal-db/) · [View on npm](https://www.npmjs.com/package/@rehearsal-db/core) · [Report a problem](https://github.com/Ddupasquier/rehearsal-db/issues)
6
+
3
7
  Rehearsal tests PostgreSQL and Supabase migrations on your computer before you run them
4
8
  anywhere important. It restores safe test data into a disposable local database, applies
5
9
  only the migrations you approve, and runs your project's own test command.
@@ -18,7 +22,7 @@ production deployment tool.
18
22
 
19
23
  ## What you need
20
24
 
21
- - Node.js 24 and npm
25
+ - Node.js 22, 24, or 26 and npm (Node.js 24 LTS recommended)
22
26
  - Docker Desktop, Colima, or another Docker-compatible engine
23
27
  - timestamped `.sql` migration files
24
28
  - a project test command that can prove the migrated application works
@@ -55,6 +59,11 @@ docker pull postgres:17-alpine
55
59
 
56
60
  Rehearsal itself never downloads a database image during a rehearsal.
57
61
 
62
+ For local Google or GitHub sign-in, the generated config includes a commented
63
+ `supabase.authentication` example. It names an ignored owner-only credential file;
64
+ Rehearsal generates the provider block and local callback inside the disposable runtime.
65
+ See [Supabase authentication](docs/configuration.md#supabase-authentication).
66
+
58
67
  ## Three terms you will see
59
68
 
60
69
  - **Baseline:** a locked, safe starting copy of your schema and test data.
@@ -160,7 +169,7 @@ problems belong in the private process described in [SECURITY.md](SECURITY.md).
160
169
 
161
170
  ## For contributors
162
171
 
163
- 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:
164
173
 
165
174
  ```bash
166
175
  npm run check
package/docs/commands.md CHANGED
@@ -34,25 +34,26 @@ Storage files. `baseline create` and `baseline prepare` are local-only.
34
34
 
35
35
  `setup --write` is idempotent: supported existing config files and completed runtime
36
36
  files remain unchanged, while missing safe ignore entries may be appended. On upgrades,
37
- `init` prints the installed release's template for comparison; `init --write` never
38
- replaces an existing config.
37
+ `init` first lists important optional settings that are available but not enabled, then
38
+ prints the installed release's template for comparison. `init --write` never replaces an
39
+ existing config.
39
40
 
40
41
  ## Optional source preparation
41
42
 
42
43
  These commands are separate from ordinary rehearsals. Configure `preparation` only after
43
44
  reviewing [the standalone security contract](standalone-workflow.md).
44
45
 
45
- | Command | What it does |
46
- | ------------------------------------------------------------- | -------------------------------------------------------------------- |
47
- | `npx rehearsal privacy key` | Preview the owner-only pseudonym-key path. |
48
- | `npx rehearsal privacy key --write` | Create the key once; refuses to replace an existing key. |
49
- | `npx rehearsal source plan` | Preview the exact temporary reader, views, columns, and asset scope. |
50
- | `npx rehearsal source apply --confirm-source-access=<sha256>` | Create only the reviewed, time-limited source access. |
51
- | `npx rehearsal baseline refresh` | Replace only the baseline from a reviewed source. |
52
- | `npx rehearsal refresh` | Preview a complete safe refresh of the baseline and local runtime. |
53
- | Add `--confirm-refresh=<sha256>` | Create the replacement, reset locally, and prune listed old copies. |
54
- | `npx rehearsal source retire` | Preview exact reader/view/credential retirement. |
55
- | 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. |
56
57
 
57
58
  Source credentials are read from environment variables named in the reviewed source
58
59
  policy. Never paste a connection string into an argument. `baseline refresh` leaves the
@@ -175,6 +176,8 @@ belonging to other projects are never included. This follows
175
176
 
176
177
  | Option | Meaning |
177
178
  | -------------------------------------- | ---------------------------------------------------------------- |
179
+ | `--help`, `-h` | Show the installed command reference. |
180
+ | `--version`, `-V` | Print the installed package version and exit. |
178
181
  | `--json` | Return the versioned machine-readable result. |
179
182
  | `--verbose` | Show more safe detail. |
180
183
  | `--debug` | Show the most diagnostic detail; still review it before sharing. |
@@ -192,6 +195,8 @@ belonging to other projects are never included. This follows
192
195
  | `--write` | Apply a setup, policy, or cleanup preview. |
193
196
 
194
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.
195
200
 
196
201
  When a project has more than one config, pass `--config=<path>` on every command for the
197
202
  non-default one. Rehearsal keeps that exact config selected through status, run, reset,
@@ -42,9 +42,21 @@ export default defineRehearsalConfig({
42
42
  rehearsalConfig: "infrastructure/rehearsal/supabase/config.toml",
43
43
  runtimeWorkdir: ".rehearsal/runtime",
44
44
 
45
- // Optional; configure both keys together.
46
- // serviceEnvironmentFile: ".env.rehearsal-service.local",
47
- // serviceEnvironmentVariables: ["LOCAL_IDP_CLIENT_ID", "LOCAL_IDP_SECRET"],
45
+ // Optional local OAuth connection. Secrets stay in the ignored owner-only file.
46
+ // Register http://127.0.0.1:58321/auth/v1/callback with the provider.
47
+ // authentication: {
48
+ // enableLocalSignup: true,
49
+ // environmentFile: ".env.rehearsal-service.local",
50
+ // providers: [
51
+ // {
52
+ // name: "google",
53
+ // clientIdEnvironmentVariable: "REHEARSAL_GOOGLE_CLIENT_ID",
54
+ // clientSecretEnvironmentVariable: "REHEARSAL_GOOGLE_CLIENT_SECRET",
55
+ // skipNonceCheck: false,
56
+ // emailOptional: false,
57
+ // },
58
+ // ],
59
+ // },
48
60
  },
49
61
  baseline: {
50
62
  artifactDirectory: ".rehearsal",
@@ -124,7 +136,8 @@ safe, run setup again, and compare the two files before deleting either one.
124
136
  After upgrading, run `npx rehearsal init` to print the current release's generated
125
137
  template beside the path of your existing config. This comparison is read-only; adding a
126
138
  new optional key remains your decision. `npx rehearsal init --write` still refuses to
127
- replace an existing config.
139
+ replace an existing config. Rehearsal lists important optional settings that are
140
+ available but not enabled before printing the template, so they are easier to find.
128
141
 
129
142
  ### Workspaces and custom paths
130
143
 
@@ -247,19 +260,72 @@ negative controls, such as unauthorized, malformed, withheld, or missing records
247
260
  response that merely avoids a server error does not prove that eligible data was
248
261
  published or can be found.
249
262
 
250
- ## Supabase service environment
263
+ ## Supabase authentication
251
264
 
252
- Some local identity providers need a client ID and secret. Configure both fields or
253
- neither:
265
+ Rehearsal can configure a Google or GitHub OAuth connection in its disposable local
266
+ Supabase runtime. Declare the provider and the names of its credential variables:
254
267
 
255
268
  ```ts
256
- serviceEnvironmentFile: ".env.rehearsal-service.local",
257
- serviceEnvironmentVariables: ["LOCAL_IDP_CLIENT_ID", "LOCAL_IDP_SECRET"],
269
+ authentication: {
270
+ enableLocalSignup: true,
271
+ environmentFile: ".env.rehearsal-service.local",
272
+ providers: [
273
+ {
274
+ name: "google",
275
+ clientIdEnvironmentVariable: "REHEARSAL_GOOGLE_CLIENT_ID",
276
+ clientSecretEnvironmentVariable: "REHEARSAL_GOOGLE_CLIENT_SECRET",
277
+ skipNonceCheck: false,
278
+ emailOptional: false,
279
+ },
280
+ ],
281
+ },
282
+ ```
283
+
284
+ Create the ignored file, add the values, then restrict it to your account:
285
+
286
+ ```dotenv
287
+ REHEARSAL_GOOGLE_CLIENT_ID=your-local-oauth-client-id
288
+ REHEARSAL_GOOGLE_CLIENT_SECRET=your-local-oauth-client-secret
289
+ ```
290
+
291
+ ```bash
292
+ chmod 600 .env.rehearsal-service.local
258
293
  ```
259
294
 
260
- The file must be ignored, owner-readable only, and contain only the exact allowlisted
261
- names. These credentials may authorize an identity handshake, but the callback must
262
- terminate at local Auth. They do not grant hosted database access.
295
+ Register `http://127.0.0.1:<apiPort>/auth/v1/callback` with the provider, using the
296
+ `runtime.apiPort` value from this config. `rehearsal explain` also prints the exact URL.
297
+ Rehearsal writes that callback and `env(...)` references into the disposable runtime; it
298
+ never copies credential values into tracked configuration. Use `name: "github"` and
299
+ distinct GitHub variable names for GitHub. Duplicate providers, reused variable names,
300
+ unsupported providers, extra keys, and a template that already owns the same provider
301
+ are refused. This follows Supabase's documented
302
+ [`env()` configuration pattern](https://supabase.com/docs/guides/local-development/managing-config).
303
+
304
+ `enableLocalSignup: true` explicitly acknowledges that disposable local Auth may create
305
+ the temporary user returned by OAuth. Rehearsal changes the generated runtime copy only;
306
+ the tracked Supabase template and every hosted project remain unchanged.
307
+
308
+ The older `serviceEnvironmentFile`, `serviceEnvironmentVariables`, and
309
+ `safety.authenticationProviders` fields remain compatible for projects that manually
310
+ own their Supabase provider TOML. Do not combine those fields with the declarative
311
+ `authentication` block.
312
+
313
+ To migrate a manual Google or GitHub setup, replace those three legacy config fields
314
+ with the `authentication` block together. The tracked Supabase template may keep its
315
+ existing provider section: Rehearsal replaces a compatible legacy section only in the
316
+ generated disposable runtime copy. The section may contain `enabled`, `client_id`,
317
+ `secret`, `redirect_uri`, `skip_nonce_check`, and `email_optional`. The latter two must
318
+ match the explicit `skipNonceCheck` and `emailOptional` declarations, which default to
319
+ `false`. Keep them false unless the provider integration has a reviewed need; Supabase
320
+ warns in its [CLI configuration reference](https://supabase.com/docs/guides/local-development/cli/config#auth.external.provider.skip_nonce_check)
321
+ that skipping nonce validation reduces replay protection. Unknown settings,
322
+ duplicate sections, nested provider tables, or mismatched options fail
323
+ `rehearsal doctor` before reset. Rehearsal never edits the tracked template during this
324
+ transition.
325
+
326
+ Provider connection and copied-account association are separate. This block lets the
327
+ ordinary application sign-in reach local Auth. An optional `identityPolicy` decides
328
+ whether that verified local identity may claim a reviewed copied account.
263
329
 
264
330
  ## Safety fields
265
331
 
@@ -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
 
@@ -55,6 +56,13 @@ Supabase or PostgreSQL, and approve the same safe setup preview.
55
56
  The proof command should check behavior changed by the migration. A test that only checks
56
57
  whether the home page loads is usually too weak.
57
58
 
59
+ If the sandbox needs Google or GitHub sign-in, uncomment
60
+ `supabase.authentication` in `rehearsal.config.mjs`. Add the named credentials to
61
+ `.env.rehearsal-service.local`, run `chmod 600 .env.rehearsal-service.local`, and
62
+ register the local callback printed in the config. See
63
+ [Supabase authentication](configuration.md#supabase-authentication). Rehearsal keeps the
64
+ values out of tracked files and generates the disposable provider configuration for you.
65
+
58
66
  ## 3. Prepare safe baseline inputs
59
67
 
60
68
  A **baseline** is the locked starting point restored before each rehearsal. Start with a
@@ -168,7 +176,9 @@ complete reference and [Troubleshooting](troubleshooting.md) when a check fails.
168
176
 
169
177
  Running setup again is safe: existing configuration and runtime files are reported as
170
178
  unchanged. Rehearsal only adds missing ignore entries. It never refreshes an existing
171
- config during an upgrade.
179
+ config during an upgrade. After upgrading, run `npx rehearsal init` to see important
180
+ optional settings you have not enabled and compare your file with the installed
181
+ release's current template.
172
182
 
173
183
  ## Files Rehearsal creates
174
184
 
@@ -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
 
@@ -157,8 +157,10 @@ graph without an application callback patch:
157
157
 
158
158
  Authentication has three separate parts:
159
159
 
160
- 1. The local Supabase configuration enables Google, GitHub, or email sign-in and reads
161
- any provider credentials from the environment.
160
+ 1. `config.supabase.authentication` can enable a Google or GitHub connection in local
161
+ Supabase and reads its credentials from the ignored owner-only environment file.
162
+ Existing projects may still own that local provider TOML manually. Email sign-in does
163
+ not use an OAuth credential declaration.
162
164
  2. The application completes the ordinary sign-in and receives a normal Supabase
163
165
  session. Rehearsal does not intercept the callback.
164
166
  3. The identity policy matches that verified local identity and transfers only the