@rehearsal-db/core 0.1.0-beta.1 → 0.1.0-beta.11

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 (72) hide show
  1. package/CHANGELOG.md +271 -1
  2. package/COMPATIBILITY.md +26 -0
  3. package/README.md +150 -346
  4. package/SECURITY.md +2 -1
  5. package/SUPPORT.md +5 -0
  6. package/docs/README.md +38 -0
  7. package/docs/adapters.md +20 -6
  8. package/docs/architecture.md +58 -0
  9. package/docs/baselines.md +102 -22
  10. package/docs/commands.md +202 -33
  11. package/docs/configuration.md +288 -13
  12. package/docs/getting-started.md +114 -181
  13. package/docs/glossary.md +11 -8
  14. package/docs/index.md +47 -0
  15. package/docs/production-source.md +176 -33
  16. package/docs/releasing.md +28 -38
  17. package/docs/roadmap.md +41 -0
  18. package/docs/runtime-policies.md +253 -0
  19. package/docs/sanitization.md +144 -8
  20. package/docs/security-model.md +34 -9
  21. package/docs/standalone-workflow.md +109 -0
  22. package/docs/troubleshooting.md +127 -2
  23. package/docs/tutorial.md +70 -59
  24. package/package.json +39 -23
  25. package/scripts/install/create_root_config.mjs +36 -0
  26. package/scripts/runtime/manage_database.mjs +18 -0
  27. package/src/README.md +17 -0
  28. package/src/application/session.mjs +435 -0
  29. package/{scripts/lib/rehearsal/baseline_artifact.mjs → src/baseline/artifact.mjs} +142 -17
  30. package/{scripts/lib/rehearsal/baseline_builder.mjs → src/baseline/builder.mjs} +19 -10
  31. package/src/baseline/input_discovery.mjs +155 -0
  32. package/src/baseline/policy_review.mjs +92 -0
  33. package/src/baseline/preparation.mjs +323 -0
  34. package/src/baseline/privacy_engine.mjs +741 -0
  35. package/{scripts/lib/rehearsal → src/baseline}/sanitization_policy.mjs +133 -3
  36. package/{scripts/lib/rehearsal → src/baseline}/schema_snapshot.mjs +1 -1
  37. package/src/cli/arguments.mjs +123 -0
  38. package/src/cli/guided.mjs +812 -0
  39. package/src/cli/init_command.mjs +77 -0
  40. package/src/cli/rehearsal.mjs +952 -0
  41. package/src/cli/renderers.mjs +589 -0
  42. package/src/cli/runtime_commands.mjs +624 -0
  43. package/src/cli/source_commands.mjs +326 -0
  44. package/src/cli/terminal.mjs +275 -0
  45. package/src/identity/claim.mjs +1163 -0
  46. package/src/identity/storage.mjs +165 -0
  47. package/{scripts/lib/rehearsal → src/project}/configuration.d.mts +72 -4
  48. package/src/project/configuration.mjs +1201 -0
  49. package/src/project/install_scaffold.mjs +291 -0
  50. package/src/project/setup.mjs +484 -0
  51. package/src/project/support_report.mjs +105 -0
  52. package/src/runtime/cleanup.mjs +381 -0
  53. package/{scripts/lib/rehearsal → src/runtime}/plan.mjs +118 -20
  54. package/src/runtime/policy.mjs +438 -0
  55. package/{scripts/lib/rehearsal/runtime_restore.mjs → src/runtime/restore.mjs} +46 -27
  56. package/src/runtime/topology.mjs +177 -0
  57. package/{scripts/lib/rehearsal → src/shared}/diagnostics.mjs +10 -3
  58. package/src/shared/human_output.mjs +4 -0
  59. package/src/shared/operation_guard.mjs +162 -0
  60. package/{scripts/lib/rehearsal → src/shared}/process_environment.mjs +5 -1
  61. package/src/source/access.mjs +578 -0
  62. package/src/source/asset_transfer.mjs +177 -0
  63. package/src/source/baseline.mjs +446 -0
  64. package/src/source/postgresql_access.mjs +480 -0
  65. package/src/targets/postgresql.mjs +808 -0
  66. package/{scripts/operations/database/manage_rehearsal_database.mjs → src/targets/supabase.mjs} +66 -20
  67. package/{scripts/lib/environment/local_supabase.mjs → src/targets/supabase_environment.mjs} +60 -28
  68. package/src/targets/target.mjs +66 -0
  69. package/scripts/lib/rehearsal/configuration.mjs +0 -559
  70. package/scripts/operations/rehearsal/rehearsal_cli.mjs +0 -565
  71. /package/{scripts/lib/rehearsal → src/runtime}/migration_history.mjs +0 -0
  72. /package/{scripts/lib/rehearsal → src/runtime}/service_environment.mjs +0 -0
package/docs/releasing.md CHANGED
@@ -5,48 +5,27 @@ environment. The workflow builds and hashes the candidate before the environment
5
5
  approval gate, then publishes those exact bytes with npm provenance. A local working
6
6
  tree is never the release source.
7
7
 
8
- The initial publishable `0.1.0-beta.1` release branch is the first change allowed to set
9
- `"private": false`. Publication still requires the exact protected-main tag, prerelease
10
- flag, artifact checks, and protected `npm` environment gate described below.
11
-
12
8
  `0.1.0-beta.0` was prepared under the unavailable `@rehearsal` npm scope and was never
13
9
  published. `0.1.0-beta.1` supersedes that candidate under `@rehearsal-db/core` without
14
10
  rewriting the earlier Git tag or GitHub prerelease.
15
11
 
16
- ## One-time first-package bootstrap
12
+ ## Trusted publication boundary
17
13
 
18
- npm trusted publishing and staged publishing are package-level settings, so they cannot
19
- be configured until `@rehearsal-db/core` exists in the registry. The first public version has
20
- a deliberately narrower bootstrap path:
14
+ `@rehearsal-db/core` trusts only the GitHub Actions publisher for repository
15
+ `Ddupasquier/rehearsal-db`, workflow `publish.yml`, and environment `npm`. The publish job
16
+ requests a short-lived GitHub OIDC identity after the protected environment approval;
17
+ it does not read an npm token or retain a registry credential. Package publishing access
18
+ requires 2FA and disallows bypass tokens.
21
19
 
22
- 1. The npm owner enables 2FA and creates or confirms the public `@rehearsal-db` organization
23
- scope. Do not send a password, OTP, recovery code, or access token to another person.
24
- 2. After the reviewed release change reaches protected `main`, the owner creates the
25
- exact `v0.1.0-beta.1` GitHub release and marks it as a prerelease. The prepare job runs without npm credentials,
26
- packs the tag, and uploads its versioned tarball plus SHA-1 and SHA-256 metadata. The
27
- publish job waits at the protected environment and cannot run yet.
28
- 3. The owner downloads or inspects that prepared artifact, confirms its version and
29
- checksum, and explicitly authorizes those exact bytes.
30
- 4. Only then, the owner creates a short-lived granular npm token with read/write access
31
- limited to the `@rehearsal-db` scope and **Bypass 2FA** enabled. npm requires that bypass
32
- for a non-interactive first publish; the environment approval remains the human
33
- release gate. Store the token only as the `NPM_TOKEN` secret in the protected GitHub
34
- `npm` environment.
35
- 5. The owner approves the waiting `npm` deployment. GitHub Actions publishes the exact
36
- uploaded tarball with provenance. The workflow
37
- refuses to use the bootstrap secret for any version other than `0.1.0-beta.1`.
38
- 6. Immediately after the registry verification passes, the owner deletes the GitHub
39
- environment secret and revokes the temporary npm token.
40
- 7. From the new package's npm settings, configure the trusted GitHub Actions publisher
41
- for repository `Ddupasquier/rehearsal-db`, workflow `publish.yml`, and environment
42
- `npm`. Allow direct `npm publish` for this workflow because the protected GitHub
43
- environment supplies the human gate. A later switch to npm staged publication must
44
- change and prove the workflow before narrowing the trusted publisher permission.
45
- 8. Set package publishing access to require 2FA and disallow traditional tokens.
20
+ The initial `0.1.0-beta.1` package creation required a one-time bootstrap credential
21
+ because npm cannot configure a trusted publisher before a package exists. That
22
+ credential is not part of the maintained release architecture: it was deleted from the
23
+ GitHub environment after publication, must remain revoked at npm, and cannot be consumed
24
+ by this workflow.
46
25
 
47
- This bootstrap token is a one-release compromise imposed by npm's package-creation
48
- boundary. It is never committed, printed, copied into a ticket, or retained for later
49
- versions.
26
+ Direct `npm publish` is allowed only for this trusted publisher because the protected
27
+ GitHub `npm` environment supplies the human gate. A later switch to npm staged
28
+ publication must change and prove the workflow before narrowing that permission.
50
29
 
51
30
  ## Every release candidate
52
31
 
@@ -58,7 +37,7 @@ versions.
58
37
  onboarding. Correct and retest the first confusing, missing, or wrong instruction.
59
38
  5. Change `private` to `false` only in the reviewed release change.
60
39
  6. Record the exact tarball filename, SHA-1, SHA-256, allowlisted files, unpacked size,
61
- executable, and zero-runtime-dependency result.
40
+ executable, declared runtime dependency inventory, and dependency audit result.
62
41
  7. Obtain explicit publication authorization for that exact version and artifact.
63
42
  8. Merge the approved release commit through protected `main` and create the exact
64
43
  `v<package-version>` tag and GitHub release.
@@ -66,12 +45,23 @@ versions.
66
45
  the approved version and checksum.
67
46
  10. Verify public visibility, ownership, provenance, registry SHA-1, README rendering,
68
47
  exact-version clean installation, CLI execution, signatures, and the unrelated
69
- installed Docker fixture.
48
+ installed Docker fixture. Allow up to six minutes for a first package to propagate
49
+ through the public registry before classifying a missing packument as a release
50
+ failure.
70
51
  11. Replace consuming projects' temporary Git/archive references only on their own
71
52
  protected integration branches and rerun their complete verification.
72
53
 
73
54
  The workflow publishes prereleases under the `beta` dist-tag and refuses a stable
74
- version. A stable tag requires a later contract, compatibility, and release decision.
55
+ version. While Rehearsal has no stable release, the workflow also moves `latest` to the
56
+ same reviewed beta. This keeps the npm package page and the ordinary
57
+ `npm install @rehearsal-db/core` command current. When Rehearsal gains a stable release,
58
+ `latest` must switch to the stable line while `beta` continues to identify prereleases.
59
+
60
+ The trusted publisher allows `npm dist-tag` only so this workflow can maintain those two
61
+ tags without a stored npm token. A manual workflow run from `main` can repair the tags
62
+ for the exact prerelease version currently recorded in `package.json`; it cannot publish
63
+ a package or select a different version. The protected `npm` environment still supplies
64
+ the human approval gate.
75
65
 
76
66
  Creating the repository, passing CI, extracting the engine, merging a release branch,
77
67
  or creating a tag does not authorize npm publication. Publication requires explicit
@@ -0,0 +1,41 @@
1
+ # Next steps
2
+
3
+ Rehearsal supports guided Supabase and ordinary PostgreSQL migration rehearsals. The
4
+ current development track removes the need for consumer-owned Rehearsal engines while
5
+ keeping application meaning and privacy decisions project-owned.
6
+
7
+ ## Standalone workflow
8
+
9
+ The implementation sequence is dependency-driven:
10
+
11
+ 1. exact temporary source access and retirement;
12
+ 2. bounded coherent extraction and executable privacy rules;
13
+ 3. approved Storage assets and declarative restore prerequisites;
14
+ 4. verified local identity association;
15
+ 5. ordinary application launch and meaningful positive/negative proofs;
16
+ 6. onboarding, upgrade safety, and exact packaged-artifact acceptance.
17
+
18
+ These capabilities stay behind separate review boundaries. Normal runtime commands never
19
+ receive source credentials. Source-side writes, package publication, consumer cleanup,
20
+ and real provider observation each require their own authorization.
21
+
22
+ Track the full breakdown in
23
+ [standalone roadmap issue #38](https://github.com/Ddupasquier/rehearsal-db/issues/38).
24
+
25
+ ## Acceptance still required
26
+
27
+ - Install the exact packed candidate in clean Supabase and PostgreSQL consumers.
28
+ - Exercise two unrelated synthetic schemas, bounded large data, interruption, drift,
29
+ invalid migrations, identities, images, persistence, reset, and cleanup.
30
+ - Run the released package in the real reporting consumer and record positives and
31
+ negatives.
32
+ - Directly observe any claimed Google account-picker/callback and unfamiliar-developer
33
+ onboarding behavior; automation cannot invent that evidence.
34
+ - Remove consumer integration code only after its replacement passes and that deletion
35
+ is separately approved.
36
+
37
+ ## Later database targets
38
+
39
+ After the PostgreSQL family is stable in real projects, evaluate individual PostgreSQL
40
+ services. MySQL, MongoDB, and unrelated database families require different migration,
41
+ restore, and safety behavior and remain out of scope.
@@ -0,0 +1,253 @@
1
+ # Runtime and identity policies
2
+
3
+ These optional JSON files replace common project-owned Rehearsal adapters. They contain
4
+ declarations only: unknown fields, SQL, JavaScript, and unsupported extensions fail.
5
+
6
+ ## Runtime policy
7
+
8
+ Point `runtimePolicy` in `rehearsal.config.mjs` to a reviewed file:
9
+
10
+ ```json
11
+ {
12
+ "policyVersion": 1,
13
+ "prerequisites": {
14
+ "schemas": ["extensions"],
15
+ "extensions": [{ "name": "pgcrypto", "schema": "extensions" }]
16
+ },
17
+ "triggers": [
18
+ {
19
+ "name": "profile_after_auth_insert",
20
+ "table": { "schema": "auth", "name": "users" },
21
+ "timing": "after",
22
+ "events": ["insert"],
23
+ "function": { "schema": "public", "name": "handle_new_user" }
24
+ }
25
+ ],
26
+ "localRows": [
27
+ {
28
+ "table": { "schema": "public", "name": "runtime_settings" },
29
+ "keyColumns": ["name"],
30
+ "values": { "name": "scheduler_enabled", "value": false }
31
+ },
32
+ {
33
+ "table": { "schema": "public", "name": "app_role_assignments" },
34
+ "keyColumns": ["user_id"],
35
+ "values": {
36
+ "user_id": "11111111-1111-4111-8111-111111111111",
37
+ "role": "owner"
38
+ },
39
+ "identityAssociation": {
40
+ "identity": "approved-owner",
41
+ "column": "user_id"
42
+ }
43
+ }
44
+ ],
45
+ "expectations": [
46
+ {
47
+ "table": { "schema": "public", "name": "profiles" },
48
+ "rowLevelSecurity": true,
49
+ "columns": [{ "name": "id", "generated": false, "identity": false }],
50
+ "foreignKeys": [],
51
+ "policies": []
52
+ }
53
+ ]
54
+ }
55
+ ```
56
+
57
+ Restore order is fixed: Rehearsal creates named schemas and allowlisted extensions
58
+ before dependent schema objects, adds declared triggers after their tables and functions
59
+ exist, then inserts local-only rows after baseline data. Application schema remains in
60
+ immutable migrations or the verified schema snapshot. Verification checks extensions,
61
+ triggers, local-only rows, columns, generated/identity behavior, foreign keys, named RLS
62
+ policies, RLS state, and unvalidated constraints.
63
+
64
+ Use `identityAssociation` when a local seed is intentionally transferred by a named
65
+ identity policy. Verification accepts the configured placeholder before association and
66
+ the exact recorded local identity afterward. Rehearsal stores that mapping only in its
67
+ local internal schema; it does not duplicate the old role or weaken the row check.
68
+
69
+ ## Identity policy
70
+
71
+ The identity policy links one verified local Supabase identity to a copied pseudonymous
72
+ graph without an application callback patch:
73
+
74
+ ```json
75
+ {
76
+ "identityVersion": 1,
77
+ "identities": [
78
+ {
79
+ "name": "approved-owner",
80
+ "matcher": {
81
+ "type": "verified-email",
82
+ "providers": ["google"],
83
+ "emailEnvironmentVariable": "REHEARSAL_APPROVED_OWNER_EMAIL",
84
+ "approvedEmailSha256": "<sha256 of the lowercase approved email>"
85
+ },
86
+ "placeholderUserId": "11111111-1111-4111-8111-111111111111",
87
+ "references": [
88
+ {
89
+ "schema": "public",
90
+ "table": "profiles",
91
+ "column": "user_id",
92
+ "required": true
93
+ },
94
+ {
95
+ "schema": "public",
96
+ "table": "app_role_assignments",
97
+ "column": "user_id",
98
+ "required": true
99
+ },
100
+ {
101
+ "schema": "public",
102
+ "table": "food_compatibility_feedback",
103
+ "column": "reviewed_by",
104
+ "required": true,
105
+ "strategy": "preserve-audit"
106
+ }
107
+ ],
108
+ "jsonReferences": [
109
+ {
110
+ "schema": "public",
111
+ "table": "events",
112
+ "column": "payload",
113
+ "path": ["actor_id"]
114
+ }
115
+ ],
116
+ "signupDefaults": [
117
+ {
118
+ "table": { "schema": "public", "table": "profiles" },
119
+ "identityColumn": "user_id",
120
+ "ignoredColumns": ["created_at", "updated_at"],
121
+ "values": {
122
+ "display_name": {
123
+ "kind": "pattern",
124
+ "pattern": "^User[0-9]{14}$"
125
+ },
126
+ "avatar_path": null
127
+ }
128
+ }
129
+ ],
130
+ "pathReferences": [
131
+ {
132
+ "schema": "public",
133
+ "table": "profiles",
134
+ "column": "avatar_path",
135
+ "valueType": "text"
136
+ }
137
+ ],
138
+ "assets": [
139
+ {
140
+ "bucket": "avatars",
141
+ "prefix": "11111111-1111-4111-8111-111111111111/",
142
+ "rewritePath": true
143
+ }
144
+ ],
145
+ "claims": { "rehearsal_owner": true },
146
+ "tokenHook": {
147
+ "function": {
148
+ "schema": "public",
149
+ "name": "custom_access_token_hook"
150
+ },
151
+ "expectedClaims": { "app_role": "developer" }
152
+ }
153
+ }
154
+ ]
155
+ }
156
+ ```
157
+
158
+ Authentication has three separate parts:
159
+
160
+ 1. The local Supabase configuration enables Google, GitHub, or email sign-in and reads
161
+ any provider credentials from the environment.
162
+ 2. The application completes the ordinary sign-in and receives a normal Supabase
163
+ session. Rehearsal does not intercept the callback.
164
+ 3. The identity policy matches that verified local identity and transfers only the
165
+ reviewed copied-account graph.
166
+
167
+ The email stays in an environment variable; only its reviewed hash is committed. Run
168
+ `rehearsal open`, complete the ordinary local sign-in, and stop the app with `Ctrl+C`.
169
+ Then preview and confirm the association:
170
+
171
+ ```bash
172
+ npx rehearsal identity plan --identity=approved-owner
173
+ npx rehearsal identity claim --identity=approved-owner \
174
+ --confirm-identity=<full-sha256>
175
+ ```
176
+
177
+ Rehearsal requires exactly one verified matching local provider identity. Every
178
+ `required` reference must exist after transfer. Declare application role tables as
179
+ references; `claims` alone is not a replacement for a database-backed token hook.
180
+
181
+ ### Supported identity matchers
182
+
183
+ Rehearsal currently supports the Supabase provider values `email`, `google`, and
184
+ `github`. Other provider names fail closed until their Auth representation has package
185
+ coverage. Two matcher types are supported:
186
+
187
+ - `verified-email` accepts one or more supported providers. It lowercases the email from
188
+ the named environment variable and compares its SHA-256 receipt before querying Auth.
189
+ - `provider-subject` accepts one OAuth provider (`google` or `github`). It compares the
190
+ reviewed SHA-256 to Supabase's stable provider ID/subject without placing the raw
191
+ subject in the policy, baseline, command output, SQL parameters, or receipt.
192
+
193
+ Google by verified email:
194
+
195
+ ```json
196
+ "matcher": {
197
+ "type": "verified-email",
198
+ "providers": ["google"],
199
+ "emailEnvironmentVariable": "REHEARSAL_APPROVED_OWNER_EMAIL",
200
+ "approvedEmailSha256": "<sha256 of the lowercase email>"
201
+ }
202
+ ```
203
+
204
+ Supabase email/password or magic-link sign-in uses the same matcher with
205
+ `"providers": ["email"]`. An account intentionally allowed to use either ordinary
206
+ email or Google can declare `"providers": ["email", "google"]`; the claim still refuses
207
+ zero or multiple matching identities.
208
+
209
+ GitHub by stable provider subject:
210
+
211
+ ```json
212
+ "matcher": {
213
+ "type": "provider-subject",
214
+ "provider": "github",
215
+ "subjectEnvironmentVariable": "REHEARSAL_APPROVED_OWNER_SUBJECT",
216
+ "approvedSubjectSha256": "<sha256 of the exact GitHub subject>"
217
+ }
218
+ ```
219
+
220
+ Set the raw value only in the local shell or an ignored environment file. Existing
221
+ policies using the older top-level `provider`, `emailEnvironmentVariable`, and
222
+ `approvedEmailSha256` fields remain accepted as a single-provider verified-email
223
+ matcher. New policies should use the explicit `matcher` object.
224
+
225
+ References use `"strategy": "transfer"` by default. Use `"preserve-audit"` only for
226
+ immutable historical authorship, such as a completed review. Rehearsal leaves that row
227
+ and its synthetic Auth actor unchanged, but still transfers the active profile, roles,
228
+ and other declared data. Undeclared references still stop the claim.
229
+
230
+ `signupDefaults` resolves a copied-profile collision only when the local row's complete
231
+ non-ignored shape matches. List every remaining column. Use ignored columns only for
232
+ server-managed values such as timestamps. An edited value, extra column, ambiguous row,
233
+ or unrelated local application data rolls back the entire claim.
234
+
235
+ `rewritePath` copies approved Storage objects through the local Supabase Storage API,
236
+ verifies the destination bytes, updates the declared database paths, and then removes
237
+ the old objects. A database failure removes the new copies and leaves the originals
238
+ usable. Restored objects may begin with either the placeholder owner or no owner;
239
+ another owner is refused. `pathReferences` updates matching text or JSONB pointers. A
240
+ configured `tokenHook` must return the declared claim subset before commit. The browser must then
241
+ refresh its session or sign in again because Rehearsal cannot rewrite an issued JWT.
242
+ Run `rehearsal open` again for that fresh-session check; runtime data is preserved.
243
+
244
+ After a claim, sign out and sign in again (or otherwise force a full token refresh) before
245
+ judging application roles. This preserves normal RLS, account blocks, role checks, and
246
+ MFA; identity association is not an authentication bypass.
247
+
248
+ The transaction does not import production passwords, sessions, refresh tokens,
249
+ cookies, MFA secrets, or provider credentials. Local MFA and authorization rules remain
250
+ real.
251
+
252
+ A synthetic test can prove the plumbing and refusal controls. Claim that Google account
253
+ selection or callback behavior works only after directly observing that real flow.
@@ -1,7 +1,7 @@
1
1
  # Sanitization policy
2
2
 
3
- Rehearsal deliberately does not decide what your application may copy. The consuming
4
- project owns an exhaustive, reviewable policy for its export surface.
3
+ Rehearsal never decides what your application may copy. The project owner reviews an
4
+ exhaustive policy; the package validates and executes the supported recipes.
5
5
 
6
6
  Every exported field should receive one action:
7
7
 
@@ -13,8 +13,140 @@ Every exported field should receive one action:
13
13
 
14
14
  Fail if a new column is unclassified. Do not default unknown fields to `KEEP`.
15
15
 
16
- The package exports `validateSanitizationCoverage` and
17
- `applySanitizationAction` as generic primitives:
16
+ For runtime restore, each included column must also classify whether it is generated,
17
+ whether it is an identity column, and its foreign-key target or explicit absence. Run
18
+ `rehearsal baseline prepare` to create a shape-only draft from synthetic NDJSON. The
19
+ draft uses `REVIEW REQUIRED` placeholders and cannot be activated until they are
20
+ replaced and the `draft` marker is removed.
21
+
22
+ Tables default to the `public` schema. Add `"schema": "app_api"` beside `name`
23
+ for another schema. Schema plus table is the identity, so two schemas may safely contain
24
+ tables with the same name.
25
+
26
+ In an interactive terminal, the guided **Review the script** action works table by table.
27
+ For larger tables, a human can explicitly apply safe defaults (`REPLACE`, `NEVER`
28
+ generated, `NO` identity, and no foreign key), then review only exceptions. Likely
29
+ structural columns such as `id`, `*_id`, and timestamps start selected as exceptions.
30
+ Users can instead review every column individually. Retaining a value is explicitly
31
+ labeled as sensitive, every saved field remains classified, and no preset is silently
32
+ applied. The reviewer validates the completed policy and refuses to overwrite a draft
33
+ changed during the session.
34
+
35
+ Two policy versions exist:
36
+
37
+ - Version 1 describes data that a project has already made safe. It remains supported
38
+ for synthetic/local inputs and existing baselines.
39
+ - Version 2 is required by `baseline refresh`. It adds bounded declarative recipes so
40
+ Rehearsal can sanitize without project callbacks.
41
+
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.
47
+
48
+ ## Approved-owner values
49
+
50
+ Keep the raw approved owner outside the policy and baseline. The policy stores only its
51
+ environment-variable name and SHA-256 receipt:
52
+
53
+ ```json
54
+ {
55
+ "bindings": {
56
+ "approved-owner": {
57
+ "environmentVariable": "REHEARSAL_APPROVED_OWNER_ID",
58
+ "approvedValueSha256": "<lowercase sha256 of the exact owner value>"
59
+ }
60
+ },
61
+ "tables": [
62
+ {
63
+ "name": "profiles",
64
+ "sourceRows": "STREAM AND SANITIZE",
65
+ "ownerBinding": { "binding": "approved-owner", "column": "user_id" },
66
+ "columns": [
67
+ {
68
+ "name": "user_id",
69
+ "action": "PSEUDONYMIZE",
70
+ "recipe": { "format": "uuid", "namespace": "account-id" },
71
+ "generated": "NEVER",
72
+ "identity": "YES",
73
+ "foreignKey": null
74
+ },
75
+ {
76
+ "name": "display_name",
77
+ "action": "DERIVE",
78
+ "recipe": {
79
+ "kind": "approved-owner",
80
+ "approved": { "action": "KEEP" },
81
+ "otherwise": {
82
+ "action": "REPLACE",
83
+ "recipe": { "kind": "constant", "value": "Synthetic account" }
84
+ }
85
+ },
86
+ "generated": "NEVER",
87
+ "identity": "NO",
88
+ "foreignKey": null
89
+ }
90
+ ]
91
+ }
92
+ ]
93
+ }
94
+ ```
95
+
96
+ Generate the receipt without putting the owner value in shell history:
97
+
98
+ ```sh
99
+ node -e 'const {createHash}=require("node:crypto"); process.stdout.write(createHash("sha256").update(process.env.REHEARSAL_APPROVED_OWNER_ID).digest("hex")+"\n")'
100
+ ```
101
+
102
+ Every conditional table names its owner column. Missing bindings, hash mismatches,
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
106
+ owners take the declared `otherwise` path; every other identity-bearing field still
107
+ needs its own pseudonymization declaration. Supply the binding only to the local
108
+ server-side Rehearsal process; do not put it in the policy, baseline, or tracked files.
109
+
110
+ ## Structured JSON
111
+
112
+ `json-object` fields are required by default. Set `"required": false` on an explicitly
113
+ optional field. Unknown keys remain errors. `json-array` declares one item recipe and a
114
+ required `maximumItems`. Both recipes accept `maximumDepth`, `maximumBytes`, and
115
+ `allowNull`; defaults are 8 levels, 65,536 bytes, and no null container. Bounds are
116
+ checked before producing the transformed structure. Nested objects and arrays repeat
117
+ the same declarations, and every nested identity uses an explicit pseudonym recipe.
118
+
119
+ ```json
120
+ {
121
+ "action": "DERIVE",
122
+ "recipe": {
123
+ "kind": "json-array",
124
+ "maximumItems": 25,
125
+ "maximumDepth": 4,
126
+ "maximumBytes": 8192,
127
+ "items": {
128
+ "action": "DERIVE",
129
+ "recipe": {
130
+ "kind": "json-object",
131
+ "fields": {
132
+ "actor_id": {
133
+ "action": "PSEUDONYMIZE",
134
+ "recipe": { "format": "uuid", "namespace": "account-id" }
135
+ },
136
+ "note": {
137
+ "action": "REPLACE",
138
+ "required": false,
139
+ "recipe": { "kind": "constant", "value": "Synthetic note" }
140
+ }
141
+ }
142
+ }
143
+ }
144
+ }
145
+ }
146
+ ```
147
+
148
+ The package still exports `validateSanitizationCoverage` and
149
+ `applySanitizationAction` for existing version 1 preparation tools:
18
150
 
19
151
  ```ts
20
152
  import {
@@ -35,15 +167,15 @@ const safeValue = applySanitizationAction({
35
167
  ```
36
168
 
37
169
  Coverage validation requires a one-to-one table and column match: missing and unknown
38
- entries both fail. The package supplies the operation contract; the project supplies
39
- the schema inventory, keys, replacements, and derivation logic.
170
+ entries both fail. New standalone preparation should use the version 2 engine described
171
+ in [Production source](production-source.md).
40
172
 
41
173
  ## Stable identity
42
174
 
43
175
  Use keyed, deterministic pseudonyms when relationships must survive across tables.
44
- Keep the key outside source control and outside the final baseline. The same source ID
176
+ Keep the key under `.rehearsal`, outside source control and the final baseline. The same source ID
45
177
  should map consistently within a generation, while the original value cannot be
46
- recovered from the artifact.
178
+ recovered from the artifact. The manifest records only the key fingerprint.
47
179
 
48
180
  ## High-risk fields
49
181
 
@@ -67,3 +199,7 @@ Before activation, verify:
67
199
 
68
200
  Completeness is not correctness. Human review of the policy and export boundary remains
69
201
  mandatory before any real source is introduced.
202
+
203
+ The exact reviewed policy bytes are checksum-bound to the active baseline. Planning,
204
+ candidate inspection, and runtime restore refuse to continue if that file later changes;
205
+ build a new reviewed baseline instead of editing the active policy in place.
@@ -5,24 +5,39 @@ misconfiguration fail closed before a state-changing operation.
5
5
 
6
6
  ## Trust boundaries
7
7
 
8
- The engine trusts the reviewed package code, strict project configuration, a verified
9
- active baseline, exact migration bytes, and explicitly project-owned proof code. It does
10
- not trust ambient environment variables, hosted project state, unknown config fields,
11
- changed migration history, incomplete artifacts, or an unverified runtime.
8
+ The engine trusts reviewed package code, strict declarations, a verified active baseline,
9
+ and exact migration bytes. It does not trust ambient environment variables, hosted
10
+ project state, unknown fields, changed history, incomplete artifacts, or an unverified
11
+ runtime.
12
12
 
13
13
  ## Independent barriers
14
14
 
15
15
  - loopback-only application and service URLs;
16
- - a dedicated unlinked Supabase workdir and project ID;
16
+ - a dedicated unlinked Supabase workdir, or an exactly named and labeled PostgreSQL
17
+ container and volume;
17
18
  - exact non-overlapping local ports;
18
19
  - a clean child-process environment that omits hosted credentials;
19
20
  - immutable baseline files and checksums;
20
21
  - exact migration-prefix and candidate digests;
21
22
  - local runtime labels used for bounded stop/removal;
23
+ - PostgreSQL images must already exist locally and are never pulled implicitly;
24
+ - plain PostgreSQL receives a fresh random password per disposable runtime, retained
25
+ only in owner-readable ignored runtime files;
22
26
  - successful receipts written only after verification.
27
+ - separate exact-digest approval for source setup, source retirement, and identity claim;
28
+ - short-lived source readers limited to reviewed views/columns and deny checks;
29
+ - bounded read-only extraction with no raw dump or row-value logging;
30
+ - exhaustive privacy policy version 2 with an owner-only local pseudonym key;
31
+ - application children receive only explicit local mappings and stay in an owned process
32
+ group.
33
+ - only one state-changing Rehearsal command may own a project at a time; stale locks are
34
+ recovered after their process exits;
35
+ - a state-changing command fingerprints its installed Rehearsal package and refuses to
36
+ continue if that installation changes while the command is running.
23
37
 
24
- No single environment variable or config edit should redirect the tool to production.
25
- Version 1 provides no hosted execution mode.
38
+ No single environment variable or config edit can redirect runtime commands to
39
+ production. Optional source preparation is a separate boundary and never provides a
40
+ hosted execution mode.
26
41
 
27
42
  ## Identity providers
28
43
 
@@ -31,10 +46,20 @@ credential names are read from an ignored owner-only file, and the callback must
31
46
  local Auth. The local account may represent a sanitized production identity, but its
32
47
  session and writes remain local.
33
48
 
49
+ Account association is one local transaction. Rehearsal removes a signup-created row
50
+ only when its complete declared default shape still matches, refuses unrelated local
51
+ application data, verifies required role references and an optional token hook, and can
52
+ copy, byte-check, and retire only declared placeholder-owned Storage paths. Immutable
53
+ audit references may explicitly retain their synthetic historical actor. Rehearsal also
54
+ refuses to rewrite application paths when copied records refer to files that are absent
55
+ from local Storage. Existing browser JWTs still require an ordinary refresh or new
56
+ sign-in.
57
+
34
58
  ## Remaining responsibilities
35
59
 
36
60
  Rehearsal cannot prove that retained data is lawful, a sanitization rule is ethically
37
- appropriate, a migration has the intended business meaning, or a project adapter is
38
- safe. Repository owners must review those decisions and protect artifacts.
61
+ appropriate, a migration has the intended business meaning, or a real provider flow was
62
+ observed. Repository owners must review those decisions and protect artifacts. The
63
+ configuration barriers are not an operating-system firewall.
39
64
 
40
65
  Report vulnerabilities using the private process in the root security policy.