@rehearsal-db/core 0.1.0-beta.9 → 0.1.0-rc.2
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 +319 -1
- package/COMPATIBILITY.md +15 -0
- package/README.md +73 -16
- package/dist/package.json +100 -0
- package/dist/scripts/install/create_root_config.d.mts +3 -0
- package/dist/scripts/install/create_root_config.mjs +13 -0
- package/dist/scripts/runtime/manage_database.d.mts +6 -0
- package/{scripts → dist/scripts}/runtime/manage_database.mjs +6 -8
- package/dist/src/application/interactive_task.d.mts +30 -0
- package/dist/src/application/interactive_task.mjs +83 -0
- package/dist/src/application/session.d.mts +97 -0
- package/dist/src/application/session.mjs +380 -0
- package/dist/src/baseline/artifact.d.mts +119 -0
- package/dist/src/baseline/artifact.mjs +522 -0
- package/dist/src/baseline/builder.d.mts +19 -0
- package/dist/src/baseline/builder.mjs +142 -0
- package/dist/src/baseline/input_discovery.d.mts +13 -0
- package/dist/src/baseline/input_discovery.mjs +149 -0
- package/dist/src/baseline/policy_review.d.mts +56 -0
- package/dist/src/baseline/policy_review.mjs +73 -0
- package/dist/src/baseline/preparation.d.mts +63 -0
- package/dist/src/baseline/preparation.mjs +295 -0
- package/dist/src/baseline/privacy_contract.d.mts +147 -0
- package/dist/src/baseline/privacy_contract.mjs +2 -0
- package/dist/src/baseline/privacy_dependency_graph.d.mts +7 -0
- package/dist/src/baseline/privacy_dependency_graph.mjs +110 -0
- package/dist/src/baseline/privacy_engine.d.mts +5 -0
- package/dist/src/baseline/privacy_engine.mjs +4 -0
- package/dist/src/baseline/privacy_key.d.mts +7 -0
- package/dist/src/baseline/privacy_key.mjs +42 -0
- package/dist/src/baseline/privacy_pipeline.d.mts +7 -0
- package/dist/src/baseline/privacy_pipeline.mjs +127 -0
- package/dist/src/baseline/privacy_policy.d.mts +6 -0
- package/dist/src/baseline/privacy_policy.mjs +237 -0
- package/dist/src/baseline/privacy_recipe_validation.d.mts +5 -0
- package/dist/src/baseline/privacy_recipe_validation.mjs +289 -0
- package/dist/src/baseline/privacy_rule_registry.d.mts +18 -0
- package/dist/src/baseline/privacy_rule_registry.mjs +143 -0
- package/dist/src/baseline/privacy_structured_json.d.mts +66 -0
- package/dist/src/baseline/privacy_structured_json.mjs +464 -0
- package/dist/src/baseline/privacy_transforms.d.mts +18 -0
- package/dist/src/baseline/privacy_transforms.mjs +142 -0
- package/dist/src/baseline/privacy_validation_shared.d.mts +31 -0
- package/dist/src/baseline/privacy_validation_shared.mjs +127 -0
- package/dist/src/baseline/sanitization_policy.d.mts +69 -0
- package/dist/src/baseline/sanitization_policy.mjs +227 -0
- package/dist/src/baseline/schema_snapshot.d.mts +19 -0
- package/dist/src/baseline/schema_snapshot.mjs +79 -0
- package/dist/src/cli/application_command_handlers.d.mts +8 -0
- package/dist/src/cli/application_command_handlers.mjs +52 -0
- package/dist/src/cli/arguments.d.mts +38 -0
- package/dist/src/cli/arguments.mjs +130 -0
- package/dist/src/cli/basic_command_handlers.d.mts +4 -0
- package/dist/src/cli/basic_command_handlers.mjs +57 -0
- package/dist/src/cli/cli_commands.d.mts +14 -0
- package/dist/src/cli/cli_commands.mjs +34 -0
- package/dist/src/cli/command_contract.d.mts +57 -0
- package/dist/src/cli/command_contract.mjs +39 -0
- package/dist/src/cli/command_output.d.mts +12 -0
- package/dist/src/cli/command_output.mjs +20 -0
- package/dist/src/cli/command_registry.d.mts +7 -0
- package/dist/src/cli/command_registry.mjs +14 -0
- package/dist/src/cli/guided.d.mts +53 -0
- package/dist/src/cli/guided.mjs +727 -0
- package/dist/src/cli/identity_command_handlers.d.mts +12 -0
- package/dist/src/cli/identity_command_handlers.mjs +239 -0
- package/dist/src/cli/identity_renderer.d.mts +8 -0
- package/dist/src/cli/identity_renderer.mjs +33 -0
- package/dist/src/cli/init_command.d.mts +21 -0
- package/dist/src/cli/init_command.mjs +90 -0
- package/dist/src/cli/inspection_command_handlers.d.mts +3 -0
- package/dist/src/cli/inspection_command_handlers.mjs +79 -0
- package/dist/src/cli/project_command_handlers.d.mts +11 -0
- package/dist/src/cli/project_command_handlers.mjs +92 -0
- package/dist/src/cli/project_commands.d.mts +283 -0
- package/dist/src/cli/project_commands.mjs +192 -0
- package/dist/src/cli/rehearsal.d.mts +7 -0
- package/dist/src/cli/rehearsal.mjs +190 -0
- package/dist/src/cli/renderers.d.mts +299 -0
- package/dist/src/cli/renderers.mjs +554 -0
- package/dist/src/cli/runtime_action_handlers.d.mts +12 -0
- package/dist/src/cli/runtime_action_handlers.mjs +201 -0
- package/dist/src/cli/runtime_commands.d.mts +294 -0
- package/dist/src/cli/runtime_commands.mjs +619 -0
- package/dist/src/cli/source_command_handlers.d.mts +8 -0
- package/dist/src/cli/source_command_handlers.mjs +62 -0
- package/dist/src/cli/source_commands.d.mts +436 -0
- package/dist/src/cli/source_commands.mjs +301 -0
- package/dist/src/cli/terminal.d.mts +59 -0
- package/dist/src/cli/terminal.mjs +230 -0
- package/dist/src/identity/claim.d.mts +5 -0
- package/dist/src/identity/claim.mjs +4 -0
- package/dist/src/identity/claim_contract.d.mts +135 -0
- package/dist/src/identity/claim_contract.mjs +6 -0
- package/dist/src/identity/claim_executor.d.mts +10 -0
- package/dist/src/identity/claim_executor.mjs +358 -0
- package/dist/src/identity/claim_identity_lookup.d.mts +23 -0
- package/dist/src/identity/claim_identity_lookup.mjs +134 -0
- package/dist/src/identity/claim_plan.d.mts +8 -0
- package/dist/src/identity/claim_plan.mjs +61 -0
- package/dist/src/identity/claim_policy.d.mts +3 -0
- package/dist/src/identity/claim_policy.mjs +377 -0
- package/dist/src/identity/claim_sql.d.mts +10 -0
- package/dist/src/identity/claim_sql.mjs +46 -0
- package/dist/src/identity/claim_verification.d.mts +13 -0
- package/dist/src/identity/claim_verification.mjs +35 -0
- package/dist/src/identity/connect.d.mts +30 -0
- package/dist/src/identity/connect.mjs +111 -0
- package/dist/src/identity/provider_configuration.d.mts +37 -0
- package/dist/src/identity/provider_configuration.mjs +132 -0
- package/dist/src/identity/storage.d.mts +21 -0
- package/dist/src/identity/storage.mjs +122 -0
- package/dist/src/index.d.mts +4 -0
- package/dist/src/index.mjs +3 -0
- package/dist/src/project/configuration.d.mts +13 -0
- package/dist/src/project/configuration.mjs +9 -0
- package/dist/src/project/configuration_contract.d.mts +125 -0
- package/dist/src/project/configuration_contract.mjs +27 -0
- package/dist/src/project/configuration_discovery.d.mts +7 -0
- package/dist/src/project/configuration_discovery.mjs +37 -0
- package/dist/src/project/configuration_normalization.d.mts +107 -0
- package/dist/src/project/configuration_normalization.mjs +302 -0
- package/dist/src/project/configuration_rendering.d.mts +14 -0
- package/dist/src/project/configuration_rendering.mjs +228 -0
- package/dist/src/project/configuration_resolution.d.mts +125 -0
- package/dist/src/project/configuration_resolution.mjs +81 -0
- package/dist/src/project/configuration_schema.d.mts +22 -0
- package/dist/src/project/configuration_schema.mjs +160 -0
- package/dist/src/project/configuration_validation.d.mts +22 -0
- package/dist/src/project/configuration_validation.mjs +136 -0
- package/dist/src/project/install_scaffold.d.mts +42 -0
- package/dist/src/project/install_scaffold.mjs +263 -0
- package/dist/src/project/project_detection.d.mts +14 -0
- package/dist/src/project/project_detection.mjs +47 -0
- package/dist/src/project/setup.d.mts +87 -0
- package/dist/src/project/setup.mjs +414 -0
- package/dist/src/project/support_report.d.mts +88 -0
- package/dist/src/project/support_report.mjs +83 -0
- package/dist/src/runtime/cleanup.d.mts +85 -0
- package/dist/src/runtime/cleanup.mjs +311 -0
- package/dist/src/runtime/lifecycle_engine.d.mts +63 -0
- package/dist/src/runtime/lifecycle_engine.mjs +114 -0
- package/dist/src/runtime/migration_history.d.mts +51 -0
- package/dist/src/runtime/migration_history.mjs +170 -0
- package/dist/src/runtime/plan.d.mts +134 -0
- package/dist/src/runtime/plan.mjs +640 -0
- package/dist/src/runtime/policy.d.mts +66 -0
- package/dist/src/runtime/policy.mjs +369 -0
- package/dist/src/runtime/restore.d.mts +22 -0
- package/dist/src/runtime/restore.mjs +276 -0
- package/dist/src/runtime/service_environment.d.mts +11 -0
- package/dist/src/runtime/service_environment.mjs +58 -0
- package/dist/src/runtime/topology.d.mts +51 -0
- package/dist/src/runtime/topology.mjs +128 -0
- package/dist/src/shared/diagnostics.d.mts +97 -0
- package/dist/src/shared/diagnostics.mjs +170 -0
- package/dist/src/shared/human_output.d.mts +2 -0
- package/{src → dist/src}/shared/human_output.mjs +1 -3
- package/dist/src/shared/node_runtime.d.mts +14 -0
- package/dist/src/shared/node_runtime.mjs +25 -0
- package/dist/src/shared/operation_guard.d.mts +22 -0
- package/dist/src/shared/operation_guard.mjs +150 -0
- package/dist/src/shared/process_environment.d.mts +15 -0
- package/dist/src/shared/process_environment.mjs +50 -0
- package/dist/src/source/access.d.mts +165 -0
- package/dist/src/source/access.mjs +602 -0
- package/dist/src/source/asset_transfer.d.mts +27 -0
- package/dist/src/source/asset_transfer.mjs +202 -0
- package/dist/src/source/baseline.d.mts +66 -0
- package/dist/src/source/baseline.mjs +372 -0
- package/dist/src/source/external_reader.d.mts +42 -0
- package/dist/src/source/external_reader.mjs +278 -0
- package/dist/src/source/postgresql_access.d.mts +86 -0
- package/dist/src/source/postgresql_access.mjs +454 -0
- package/dist/src/source/privacy_paths.d.mts +10 -0
- package/dist/src/source/privacy_paths.mjs +27 -0
- package/dist/src/targets/driver.d.mts +2 -0
- package/dist/src/targets/driver.mjs +13 -0
- package/dist/src/targets/postgresql.d.mts +5 -0
- package/dist/src/targets/postgresql.mjs +620 -0
- package/dist/src/targets/supabase.d.mts +8 -0
- package/dist/src/targets/supabase.mjs +629 -0
- package/dist/src/targets/supabase_environment.d.mts +54 -0
- package/dist/src/targets/supabase_environment.mjs +184 -0
- package/dist/src/targets/target.d.mts +33 -0
- package/dist/src/targets/target.mjs +39 -0
- package/docs/README.md +35 -16
- package/docs/architecture.md +135 -10
- package/docs/commands.md +53 -15
- package/docs/configuration.md +186 -31
- package/docs/getting-started.md +66 -9
- package/docs/index.md +67 -0
- package/docs/production-source.md +175 -17
- package/docs/releasing.md +24 -12
- package/docs/roadmap.md +3 -2
- package/docs/runtime-lifecycle.md +99 -0
- package/docs/runtime-policies.md +80 -7
- package/docs/sanitization.md +572 -4
- package/docs/security-model.md +8 -0
- package/docs/stability.md +70 -0
- package/docs/standalone-workflow.md +36 -13
- package/docs/troubleshooting.md +82 -10
- package/docs/tutorial.md +7 -1
- package/docs/typescript.md +134 -0
- package/package.json +44 -29
- package/types/configuration.d.mts +5 -0
- package/src/README.md +0 -17
- package/src/application/session.mjs +0 -313
- package/src/baseline/artifact.mjs +0 -661
- package/src/baseline/builder.mjs +0 -164
- package/src/baseline/input_discovery.mjs +0 -155
- package/src/baseline/policy_review.mjs +0 -92
- package/src/baseline/preparation.mjs +0 -323
- package/src/baseline/privacy_engine.mjs +0 -413
- package/src/baseline/sanitization_policy.mjs +0 -299
- package/src/baseline/schema_snapshot.mjs +0 -113
- package/src/cli/arguments.mjs +0 -120
- package/src/cli/guided.mjs +0 -807
- package/src/cli/rehearsal.mjs +0 -933
- package/src/cli/renderers.mjs +0 -584
- package/src/cli/runtime_commands.mjs +0 -553
- package/src/cli/source_commands.mjs +0 -326
- package/src/cli/terminal.mjs +0 -275
- package/src/identity/claim.mjs +0 -975
- package/src/identity/storage.mjs +0 -165
- package/src/project/configuration.d.mts +0 -153
- package/src/project/configuration.mjs +0 -1197
- package/src/project/setup.mjs +0 -379
- package/src/project/support_report.mjs +0 -105
- package/src/runtime/cleanup.mjs +0 -381
- package/src/runtime/migration_history.mjs +0 -220
- package/src/runtime/plan.mjs +0 -685
- package/src/runtime/policy.mjs +0 -438
- package/src/runtime/restore.mjs +0 -329
- package/src/runtime/service_environment.mjs +0 -82
- package/src/runtime/topology.mjs +0 -177
- package/src/shared/diagnostics.mjs +0 -199
- package/src/shared/operation_guard.mjs +0 -162
- package/src/shared/process_environment.mjs +0 -68
- package/src/source/access.mjs +0 -578
- package/src/source/asset_transfer.mjs +0 -177
- package/src/source/baseline.mjs +0 -446
- package/src/source/postgresql_access.mjs +0 -480
- package/src/targets/postgresql.mjs +0 -808
- package/src/targets/supabase.mjs +0 -830
- package/src/targets/supabase_environment.mjs +0 -229
- package/src/targets/target.mjs +0 -66
package/docs/sanitization.md
CHANGED
|
@@ -39,10 +39,578 @@ Two policy versions exist:
|
|
|
39
39
|
- Version 2 is required by `baseline refresh`. It adds bounded declarative recipes so
|
|
40
40
|
Rehearsal can sanitize without project callbacks.
|
|
41
41
|
|
|
42
|
-
Version 2 supports keyed UUID, email, text,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
Version 2 supports keyed UUID, email, text, integer, fixed-width hexadecimal, and GTIN
|
|
43
|
+
pseudonyms; digests derived from named sanitized columns; explicit constant replacement;
|
|
44
|
+
bounded and grouped date shifting; identity-aware path mapping;
|
|
45
|
+
approved-owner conditionals; exhaustively classified JSON objects; bounded JSON arrays;
|
|
46
|
+
reviewed JSON type unions; and runtime-keyed JSON dictionaries. Unknown tables, columns,
|
|
47
|
+
nested keys, formats, recipes, variants, or missing required fields fail closed. It never
|
|
48
|
+
evaluates JavaScript, regular expressions, or SQL from the policy.
|
|
49
|
+
|
|
50
|
+
## Approved-owner values
|
|
51
|
+
|
|
52
|
+
Keep the raw approved owner outside the policy and baseline. The policy stores only its
|
|
53
|
+
environment-variable name and SHA-256 receipt:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"bindings": {
|
|
58
|
+
"approved-owner": {
|
|
59
|
+
"environmentVariable": "REHEARSAL_APPROVED_OWNER_ID",
|
|
60
|
+
"approvedValueSha256": "<lowercase sha256 of the exact owner value>"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"tables": [
|
|
64
|
+
{
|
|
65
|
+
"name": "profiles",
|
|
66
|
+
"sourceRows": "STREAM AND SANITIZE",
|
|
67
|
+
"ownerBinding": { "binding": "approved-owner", "column": "user_id" },
|
|
68
|
+
"columns": [
|
|
69
|
+
{
|
|
70
|
+
"name": "user_id",
|
|
71
|
+
"action": "PSEUDONYMIZE",
|
|
72
|
+
"recipe": { "format": "uuid", "namespace": "account-id" },
|
|
73
|
+
"generated": "NEVER",
|
|
74
|
+
"identity": "NO",
|
|
75
|
+
"foreignKey": null
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"name": "display_name",
|
|
79
|
+
"action": "DERIVE",
|
|
80
|
+
"recipe": {
|
|
81
|
+
"kind": "approved-owner",
|
|
82
|
+
"approved": { "action": "KEEP" },
|
|
83
|
+
"otherwise": {
|
|
84
|
+
"action": "REPLACE",
|
|
85
|
+
"recipe": { "kind": "constant", "value": "Synthetic account" }
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"generated": "NEVER",
|
|
89
|
+
"identity": "NO",
|
|
90
|
+
"foreignKey": null
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Generate the receipt without putting the owner value in shell history:
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
node -e 'const {createHash}=require("node:crypto"); process.stdout.write(createHash("sha256").update(process.env.REHEARSAL_APPROVED_OWNER_ID).digest("hex")+"\n")'
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Every conditional table names its owner column. Missing bindings, hash mismatches,
|
|
105
|
+
missing owner columns, unsupported owner values, and conditionals without an owner
|
|
106
|
+
binding are refused. The owner column must use `PSEUDONYMIZE`, preventing a conditional
|
|
107
|
+
policy from retaining raw account IDs.
|
|
108
|
+
|
|
109
|
+
When ownership may come through several relationships, replace `column` with an explicit
|
|
110
|
+
bounded any-of declaration:
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"ownerBinding": {
|
|
115
|
+
"binding": "approved-owner",
|
|
116
|
+
"columns": ["target_user_id", "actor_user_id"],
|
|
117
|
+
"match": "any",
|
|
118
|
+
"nullBehavior": "otherwise"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Every listed column must exist and be pseudonymized. One matching value selects the
|
|
124
|
+
`approved` branch. `nullBehavior: "otherwise"` explicitly treats a null value as a
|
|
125
|
+
non-match; it never grants owner access. Without that declaration, encountering null is
|
|
126
|
+
an error. Empty strings, non-finite numbers (`NaN`, positive infinity, or negative
|
|
127
|
+
infinity), objects, arrays, booleans, missing columns, duplicate declarations, and
|
|
128
|
+
unsupported matching modes remain errors. Rehearsal validates every declared owner
|
|
129
|
+
column before selecting either branch, even when another owner column matches. A
|
|
130
|
+
single-column binding may also declare `nullBehavior: "otherwise"` when that column is
|
|
131
|
+
reviewed as nullable.
|
|
132
|
+
|
|
133
|
+
`identity` describes PostgreSQL's generated SQL identity metadata, not whether a value
|
|
134
|
+
identifies a person: ordinary UUID primary keys use `"NO"`, while a genuine
|
|
135
|
+
`GENERATED ... AS IDENTITY` column uses `"YES"`. Rehearsal checks the restored PostgreSQL
|
|
136
|
+
schema for a real backing sequence before attempting a sequence reset, so UUID identity
|
|
137
|
+
values never receive numeric sequence operations. Other owners take the declared
|
|
138
|
+
`otherwise` path; every other identity-bearing field still needs its own pseudonymization
|
|
139
|
+
declaration. Supply the binding only to the local server-side Rehearsal process; do not
|
|
140
|
+
put it in the policy, baseline, or tracked files.
|
|
141
|
+
|
|
142
|
+
## Identity-aware Storage paths
|
|
143
|
+
|
|
144
|
+
Use one named mapping when a database column and a physical Storage object both begin
|
|
145
|
+
with the approved owner's ID. Rehearsal pseudonymizes only that first segment and keeps
|
|
146
|
+
the remaining path unchanged:
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"pathMappings": {
|
|
151
|
+
"owner-storage": {
|
|
152
|
+
"binding": "approved-owner",
|
|
153
|
+
"format": "uuid",
|
|
154
|
+
"namespace": "account-id",
|
|
155
|
+
"maximumBytes": 512,
|
|
156
|
+
"maximumSegments": 8
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
"tables": [
|
|
160
|
+
{
|
|
161
|
+
"name": "profiles",
|
|
162
|
+
"sourceRows": "STREAM AND SANITIZE",
|
|
163
|
+
"columns": [
|
|
164
|
+
{
|
|
165
|
+
"name": "avatar_path",
|
|
166
|
+
"action": "DERIVE",
|
|
167
|
+
"recipe": { "kind": "path-map", "mapping": "owner-storage" },
|
|
168
|
+
"generated": "NEVER",
|
|
169
|
+
"identity": "NO",
|
|
170
|
+
"foreignKey": null
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
]
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The source policy uses the same mapping name and names the binding's environment
|
|
179
|
+
variable as `prefixEnvironmentVariable`; it never contains the raw owner ID. During
|
|
180
|
+
`rehearsal refresh`, the package uses the mapping for both database values and physical
|
|
181
|
+
object destinations. A missing mapping, a different environment variable, a path whose
|
|
182
|
+
first segment is not the exact reviewed owner, traversal, or a size/segment limit breach
|
|
183
|
+
stops the refresh.
|
|
184
|
+
|
|
185
|
+
## Related dates
|
|
186
|
+
|
|
187
|
+
Give related timestamps the same `group` and `days` value to shift them by one stable
|
|
188
|
+
offset. This preserves their order and duration:
|
|
189
|
+
|
|
190
|
+
```json
|
|
191
|
+
{
|
|
192
|
+
"name": "starts_at",
|
|
193
|
+
"action": "DERIVE",
|
|
194
|
+
"recipe": { "kind": "date-shift", "days": 30, "group": "event-window" },
|
|
195
|
+
"generated": "NEVER",
|
|
196
|
+
"identity": "NO",
|
|
197
|
+
"foreignKey": null
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Apply the same recipe to `ends_at`. Rehearsal refuses a group that declares conflicting
|
|
202
|
+
`days` values. Leave out `group` when a date should shift independently.
|
|
203
|
+
|
|
204
|
+
`date-shift` returns an ISO string by default, preserving the existing behavior. When
|
|
205
|
+
the reviewed application model stores a date as numeric epoch milliseconds, declare
|
|
206
|
+
that representation explicitly:
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"action": "DERIVE",
|
|
211
|
+
"recipe": {
|
|
212
|
+
"kind": "date-shift",
|
|
213
|
+
"days": 30,
|
|
214
|
+
"group": "saved-recipe-window",
|
|
215
|
+
"representation": "epoch-milliseconds"
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
This form accepts only finite safe integers that JavaScript can represent as dates and
|
|
221
|
+
returns a numeric epoch-millisecond value. It preserves `null`, refuses strings,
|
|
222
|
+
fractions, non-finite numbers, unsafe integers and values outside the supported date
|
|
223
|
+
range. ISO-string and epoch-millisecond declarations may share a group; they receive the
|
|
224
|
+
same stable offset while each retains its declared output representation. The only
|
|
225
|
+
supported representation names are `iso-string` and `epoch-milliseconds`.
|
|
226
|
+
|
|
227
|
+
## Constraint-preserving identifiers and fingerprints
|
|
228
|
+
|
|
229
|
+
Use a keyed `hex` pseudonym when one source value must remain a lowercase 32- or
|
|
230
|
+
64-character hexadecimal value:
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{
|
|
234
|
+
"name": "source_fingerprint",
|
|
235
|
+
"action": "PSEUDONYMIZE",
|
|
236
|
+
"recipe": {
|
|
237
|
+
"format": "hex",
|
|
238
|
+
"namespace": "source-fingerprint",
|
|
239
|
+
"length": 64
|
|
240
|
+
},
|
|
241
|
+
"generated": "NEVER",
|
|
242
|
+
"identity": "NO",
|
|
243
|
+
"foreignKey": null
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Use a `digest` derivation when the fingerprint must describe other sanitized fields.
|
|
248
|
+
Inputs are column names, evaluated in the listed order after their own privacy recipes.
|
|
249
|
+
The existing raw fingerprint is ignored:
|
|
250
|
+
|
|
251
|
+
```json
|
|
252
|
+
{
|
|
253
|
+
"name": "evidence_fingerprint",
|
|
254
|
+
"action": "DERIVE",
|
|
255
|
+
"recipe": {
|
|
256
|
+
"kind": "digest",
|
|
257
|
+
"format": "hex",
|
|
258
|
+
"namespace": "evidence-fingerprint",
|
|
259
|
+
"length": 32,
|
|
260
|
+
"inputs": ["sanitized_owner_id", "evidence_state"]
|
|
261
|
+
},
|
|
262
|
+
"generated": "NEVER",
|
|
263
|
+
"identity": "NO",
|
|
264
|
+
"foreignKey": null
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Digest inputs must exist, must not be excluded, and cannot form a dependency cycle.
|
|
269
|
+
Rehearsal supports only 32- and 64-character hexadecimal output. The namespace and local
|
|
270
|
+
privacy key separate otherwise identical declarations.
|
|
271
|
+
|
|
272
|
+
Use the `gtin` pseudonym format for a normalized GTIN whose length and check digit must
|
|
273
|
+
remain valid:
|
|
274
|
+
|
|
275
|
+
```json
|
|
276
|
+
{
|
|
277
|
+
"name": "barcode",
|
|
278
|
+
"action": "PSEUDONYMIZE",
|
|
279
|
+
"recipe": { "format": "gtin", "namespace": "product-barcode", "length": 14 },
|
|
280
|
+
"generated": "NEVER",
|
|
281
|
+
"identity": "NO",
|
|
282
|
+
"foreignKey": null
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Supported GTIN lengths are 8, 12, 13, and 14. Non-null input must be a normalized string
|
|
287
|
+
of exactly that length with a valid check digit; malformed input stops preparation. Null
|
|
288
|
+
stays null. Rehearsal creates a deterministic keyed payload and recalculates the check
|
|
289
|
+
digit, so related fields using the same namespace, length, source value, and key remain
|
|
290
|
+
aligned.
|
|
291
|
+
|
|
292
|
+
When one reviewed field legitimately contains more than one GTIN length, replace
|
|
293
|
+
`"length": 14` with an explicit allowlist such as `"allowedLengths": [12, 14]`. The two
|
|
294
|
+
keys are mutually exclusive. Each source value must match one allowed length, and its
|
|
295
|
+
result retains that exact length. Empty, duplicated, or unsupported allowlist entries
|
|
296
|
+
are refused. This also works inside structured JSON declarations; a JSON union is not
|
|
297
|
+
needed merely to distinguish GTIN string lengths.
|
|
298
|
+
|
|
299
|
+
Use the `url` pseudonym format when a sanitized field must remain an HTTPS URL:
|
|
300
|
+
|
|
301
|
+
```json
|
|
302
|
+
{
|
|
303
|
+
"name": "evidence_reference",
|
|
304
|
+
"action": "PSEUDONYMIZE",
|
|
305
|
+
"recipe": {
|
|
306
|
+
"format": "url",
|
|
307
|
+
"namespace": "evidence-url",
|
|
308
|
+
"origin": "https://127.0.0.1:58432",
|
|
309
|
+
"maxLength": 500
|
|
310
|
+
},
|
|
311
|
+
"generated": "NEVER",
|
|
312
|
+
"identity": "NO",
|
|
313
|
+
"foreignKey": null
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
`origin` is required and must be an HTTPS loopback origin using `127.0.0.1`, `::1`, or
|
|
318
|
+
`localhost`. Credentials, paths, queries, fragments, non-HTTPS schemes, and non-local
|
|
319
|
+
hosts are refused. The value must be the canonical origin itself, optionally followed by
|
|
320
|
+
one `/`; dot segments and encoded dot segments are rejected before URL normalization.
|
|
321
|
+
`maxLength` is also required, cannot exceed 2,048 characters, and must fit the complete
|
|
322
|
+
generated URL.
|
|
323
|
+
|
|
324
|
+
Non-null input must be a valid HTTPS URL without surrounding or embedded whitespace.
|
|
325
|
+
Rehearsal hashes the entire source value with the local privacy key and namespace, then
|
|
326
|
+
emits only the reviewed origin and a 32-character pseudonymous path. It never copies the
|
|
327
|
+
source host, credentials, path, query, token, or fragment. Null remains null. Equal
|
|
328
|
+
inputs using the same namespace, key, and origin correspond across ordinary columns and
|
|
329
|
+
structured JSON; different inputs and rotated keys produce different paths.
|
|
330
|
+
|
|
331
|
+
The same declaration can be used for items inside bounded `json-array` recipes, which
|
|
332
|
+
covers PostgreSQL text-array projections represented by the source pipeline. Each array
|
|
333
|
+
and object still needs its ordinary item, depth, and byte bounds.
|
|
334
|
+
|
|
335
|
+
### Retained text containing a reviewed identity
|
|
336
|
+
|
|
337
|
+
Use `binding-substitute` when permitted text may contain the exact value of a reviewed
|
|
338
|
+
UUID binding. This keeps the surrounding text but replaces every occurrence of that UUID
|
|
339
|
+
with the same keyed pseudonym used by a `uuid` declaration sharing its namespace:
|
|
340
|
+
|
|
341
|
+
```json
|
|
342
|
+
{
|
|
343
|
+
"action": "DERIVE",
|
|
344
|
+
"recipe": {
|
|
345
|
+
"kind": "approved-owner",
|
|
346
|
+
"approved": {
|
|
347
|
+
"action": "DERIVE",
|
|
348
|
+
"recipe": {
|
|
349
|
+
"kind": "binding-substitute",
|
|
350
|
+
"binding": "approved-owner",
|
|
351
|
+
"format": "uuid",
|
|
352
|
+
"namespace": "account-id",
|
|
353
|
+
"maximumBytes": 256
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
"otherwise": {
|
|
357
|
+
"action": "PSEUDONYMIZE",
|
|
358
|
+
"recipe": {
|
|
359
|
+
"format": "text",
|
|
360
|
+
"namespace": "food-description",
|
|
361
|
+
"maxLength": 128
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
The named binding must already exist in `policy.bindings`, and its environment value must
|
|
369
|
+
match the tracked SHA-256 review receipt. For this recipe the reviewed value must be a
|
|
370
|
+
UUID. `maximumBytes` is required and applies before and after replacement. Null remains
|
|
371
|
+
null; other input must be text. Ordinary text is unchanged, literal UUID occurrences are
|
|
372
|
+
all replaced, and the raw identity is never stored in policy.
|
|
373
|
+
|
|
374
|
+
This is intentionally not a general search-and-replace facility: there are no regular
|
|
375
|
+
expressions, arbitrary patterns, replacement strings, or callbacks. Pair it with an
|
|
376
|
+
`approved-owner` branch when only the reviewed owner's surrounding text may be retained.
|
|
377
|
+
|
|
378
|
+
## Structured JSON
|
|
379
|
+
|
|
380
|
+
`json-object` fields are required by default. Set `"required": false` on an explicitly
|
|
381
|
+
optional field. Unknown keys remain errors. `json-array` declares one item recipe and a
|
|
382
|
+
required `maximumItems`. Both recipes accept `maximumDepth`, `maximumBytes`, and
|
|
383
|
+
`allowNull`; defaults are 8 levels, 65,536 bytes, and no null container. Bounds are
|
|
384
|
+
checked before producing the transformed structure. Nested objects and arrays repeat
|
|
385
|
+
the same declarations, and every nested identity uses an explicit pseudonym recipe.
|
|
386
|
+
|
|
387
|
+
```json
|
|
388
|
+
{
|
|
389
|
+
"action": "DERIVE",
|
|
390
|
+
"recipe": {
|
|
391
|
+
"kind": "json-array",
|
|
392
|
+
"maximumItems": 25,
|
|
393
|
+
"maximumDepth": 4,
|
|
394
|
+
"maximumBytes": 8192,
|
|
395
|
+
"items": {
|
|
396
|
+
"action": "DERIVE",
|
|
397
|
+
"recipe": {
|
|
398
|
+
"kind": "json-object",
|
|
399
|
+
"fields": {
|
|
400
|
+
"actor_id": {
|
|
401
|
+
"action": "PSEUDONYMIZE",
|
|
402
|
+
"recipe": { "format": "uuid", "namespace": "account-id" }
|
|
403
|
+
},
|
|
404
|
+
"note": {
|
|
405
|
+
"action": "REPLACE",
|
|
406
|
+
"required": false,
|
|
407
|
+
"recipe": { "kind": "constant", "value": "Synthetic note" }
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### JSON values with several reviewed types
|
|
417
|
+
|
|
418
|
+
Use `json-union` when one JSON column legitimately stores more than one root type. Every
|
|
419
|
+
accepted type has its own declaration. Null is rejected unless `allowNull` is explicitly
|
|
420
|
+
true. Arrays and objects cannot use whole-value `KEEP`; their contents must be classified
|
|
421
|
+
through `json-array`, `json-object`, `json-dictionary`, another bounded union, or a safe
|
|
422
|
+
replacement or pseudonymization.
|
|
423
|
+
|
|
424
|
+
```json
|
|
425
|
+
{
|
|
426
|
+
"action": "DERIVE",
|
|
427
|
+
"recipe": {
|
|
428
|
+
"kind": "json-union",
|
|
429
|
+
"allowNull": true,
|
|
430
|
+
"maximumItems": 25,
|
|
431
|
+
"maximumDepth": 5,
|
|
432
|
+
"maximumBytes": 8192,
|
|
433
|
+
"variants": {
|
|
434
|
+
"string": { "action": "KEEP" },
|
|
435
|
+
"number": { "action": "KEEP" },
|
|
436
|
+
"array": {
|
|
437
|
+
"action": "DERIVE",
|
|
438
|
+
"recipe": {
|
|
439
|
+
"kind": "json-array",
|
|
440
|
+
"maximumItems": 25,
|
|
441
|
+
"items": {
|
|
442
|
+
"action": "REPLACE",
|
|
443
|
+
"recipe": { "kind": "constant", "value": "Synthetic item" }
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
"object": {
|
|
448
|
+
"action": "DERIVE",
|
|
449
|
+
"recipe": {
|
|
450
|
+
"kind": "json-object",
|
|
451
|
+
"fields": {
|
|
452
|
+
"status": { "action": "KEEP" },
|
|
453
|
+
"note": {
|
|
454
|
+
"action": "REPLACE",
|
|
455
|
+
"required": false,
|
|
456
|
+
"recipe": { "kind": "constant", "value": "Synthetic note" }
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Supported variant names are `string`, `number`, `boolean`, `array`, and `object`.
|
|
467
|
+
Undeclared types, non-finite numbers, excessive bytes, depth or items, and unclassified
|
|
468
|
+
object fields fail closed.
|
|
469
|
+
|
|
470
|
+
### Reviewed scalar domains
|
|
471
|
+
|
|
472
|
+
Use `enum` when a structural string, number, or boolean must remain exact, but only a
|
|
473
|
+
small reviewed set is valid. For example, an image mode can retain its application
|
|
474
|
+
meaning without allowing arbitrary text through:
|
|
475
|
+
|
|
476
|
+
```json
|
|
477
|
+
{
|
|
478
|
+
"action": "DERIVE",
|
|
479
|
+
"recipe": {
|
|
480
|
+
"kind": "enum",
|
|
481
|
+
"values": ["contain", "cover", "custom"],
|
|
482
|
+
"allowNull": true
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
The recipe accepts one through 128 distinct values. Each value must be a JSON string,
|
|
488
|
+
finite number, or boolean; strings are limited to 256 UTF-8 bytes. `null` is accepted
|
|
489
|
+
only when `allowNull` is true. Runtime values match by both type and exact value, so the
|
|
490
|
+
number `1` does not authorize the string `"1"`. Rejected values are never included in
|
|
491
|
+
the error message.
|
|
492
|
+
|
|
493
|
+
An enum can be used directly or inside reviewed objects, arrays, dictionary values, and
|
|
494
|
+
`approved-owner` branches. It validates structure; it is not a place to list private
|
|
495
|
+
data, credentials, tokens, user-entered prose, or identifiers.
|
|
496
|
+
|
|
497
|
+
### Open structural codes
|
|
498
|
+
|
|
499
|
+
Use `validated-string` when an open set of structural codes must remain exact and an
|
|
500
|
+
enum would incorrectly freeze the domain. The built-in `portable-code` format accepts
|
|
501
|
+
case-preserving ASCII letters, digits, `_`, and `-`, starting with a letter or digit:
|
|
502
|
+
|
|
503
|
+
```json
|
|
504
|
+
{
|
|
505
|
+
"action": "DERIVE",
|
|
506
|
+
"recipe": {
|
|
507
|
+
"kind": "validated-string",
|
|
508
|
+
"format": "portable-code",
|
|
509
|
+
"maximumBytes": 64,
|
|
510
|
+
"allowNull": true
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
`maximumBytes` is required and may be 1 through 1,024. Dots, whitespace, slashes,
|
|
516
|
+
colons, control characters, non-ASCII text, wrong JSON types, and over-limit strings
|
|
517
|
+
are rejected without printing the input. This preserves codes such as `PROCNT`, `203`,
|
|
518
|
+
or `energy-kcal_100g`, while refusing JWT-shaped strings. Arbitrary regular expressions
|
|
519
|
+
are not supported.
|
|
520
|
+
|
|
521
|
+
Like enums, validated strings compose inside reviewed objects, arrays, dictionary
|
|
522
|
+
values, and `approved-owner` branches. Use them only for documented structural code
|
|
523
|
+
domains—not prose, URLs, paths, credentials, secrets, personal data, or identifiers
|
|
524
|
+
whose legitimate alphabet requires other characters.
|
|
525
|
+
|
|
526
|
+
### JSON dictionaries with runtime keys
|
|
527
|
+
|
|
528
|
+
Use `json-dictionary` for a reviewed object whose keys come from runtime data. This is
|
|
529
|
+
different from `json-object`, where every field name is known in advance. A dictionary
|
|
530
|
+
requires a key format, an explicit key action, one value declaration, and item, depth and
|
|
531
|
+
byte bounds:
|
|
532
|
+
|
|
533
|
+
```json
|
|
534
|
+
{
|
|
535
|
+
"action": "DERIVE",
|
|
536
|
+
"recipe": {
|
|
537
|
+
"kind": "json-dictionary",
|
|
538
|
+
"maximumItems": 100,
|
|
539
|
+
"maximumDepth": 3,
|
|
540
|
+
"maximumBytes": 8192,
|
|
541
|
+
"keys": { "format": "positive-integer", "action": "KEEP" },
|
|
542
|
+
"values": {
|
|
543
|
+
"action": "DERIVE",
|
|
544
|
+
"recipe": {
|
|
545
|
+
"kind": "json-union",
|
|
546
|
+
"maximumItems": 1,
|
|
547
|
+
"variants": { "number": { "action": "KEEP" } }
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
Key formats are `integer`, `positive-integer`, `uuid`, `identifier`, and bounded `text`.
|
|
555
|
+
Use `integer` when runtime keys may be positive, negative, or zero. It requires inclusive
|
|
556
|
+
safe-integer `minimum` and `maximum` bounds, accepts only canonical decimal keys, and
|
|
557
|
+
preserves their exact spelling:
|
|
558
|
+
|
|
559
|
+
```json
|
|
560
|
+
{
|
|
561
|
+
"keys": {
|
|
562
|
+
"format": "integer",
|
|
563
|
+
"action": "KEEP",
|
|
564
|
+
"minimum": -9007199254740991,
|
|
565
|
+
"maximum": 9007199254740991
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
The reviewed range decides whether zero is allowed. Leading zeroes, `-0`, a leading `+`,
|
|
571
|
+
fractions, exponents, unsafe integers, and unrelated text are rejected. Use
|
|
572
|
+
`positive-integer` for canonical decimal keys starting at 1. Text keys must declare
|
|
573
|
+
`maximumLength`. Every key explicitly uses either `KEEP` or `PSEUDONYMIZE`.
|
|
574
|
+
Identity-bearing keys should use `PSEUDONYMIZE` with the ordinary keyed pseudonym recipe:
|
|
575
|
+
|
|
576
|
+
```json
|
|
577
|
+
{
|
|
578
|
+
"keys": {
|
|
579
|
+
"format": "uuid",
|
|
580
|
+
"action": "PSEUDONYMIZE",
|
|
581
|
+
"recipe": { "format": "uuid", "namespace": "account-id" }
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
When a dictionary is keyed by the same numeric identity stored in a column or nested
|
|
587
|
+
numeric field, use `format: "integer"` for both pseudonym recipes and reuse the exact
|
|
588
|
+
namespace:
|
|
589
|
+
|
|
590
|
+
```json
|
|
591
|
+
{
|
|
592
|
+
"keys": {
|
|
593
|
+
"format": "integer",
|
|
594
|
+
"action": "PSEUDONYMIZE",
|
|
595
|
+
"minimum": -9007199254740991,
|
|
596
|
+
"maximum": 9007199254740991,
|
|
597
|
+
"recipe": { "format": "integer", "namespace": "external:food-id" }
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
Rehearsal validates the object key as canonical decimal text, converts it to its safe
|
|
603
|
+
integer identity, and then applies the keyed mapping. Therefore the source number `203`
|
|
604
|
+
and reviewed dictionary key `"203"` receive the same pseudonym. Positive, negative, and
|
|
605
|
+
reviewed zero identities correspond; namespace changes and privacy-key rotation still
|
|
606
|
+
produce separate mappings. This normalization applies only to explicitly typed
|
|
607
|
+
`integer` dictionary keys. It does not weaken the key syntax or range checks.
|
|
608
|
+
|
|
609
|
+
Invalid keys, duplicate transformed keys, excessive entries, unclassified surrounding
|
|
610
|
+
object fields, and values rejected by the declared value recipe stop preparation. A
|
|
611
|
+
dictionary never infers that runtime keys are safe. Its value declaration also cannot
|
|
612
|
+
retain an unknown JSON shape wholesale; use a bounded union when a scalar value should be
|
|
613
|
+
kept exactly.
|
|
46
614
|
|
|
47
615
|
The package still exports `validateSanitizationCoverage` and
|
|
48
616
|
`applySanitizationAction` for existing version 1 preparation tools:
|
package/docs/security-model.md
CHANGED
|
@@ -26,6 +26,9 @@ runtime.
|
|
|
26
26
|
- successful receipts written only after verification.
|
|
27
27
|
- separate exact-digest approval for source setup, source retirement, and identity claim;
|
|
28
28
|
- short-lived source readers limited to reviewed views/columns and deny checks;
|
|
29
|
+
- externally provisioned readers require canonical view-definition receipts,
|
|
30
|
+
`security_barrier`, independent ownership, an exact non-system readable-column surface,
|
|
31
|
+
no write or role-escalation paths, and database-enforced expiration;
|
|
29
32
|
- bounded read-only extraction with no raw dump or row-value logging;
|
|
30
33
|
- exhaustive privacy policy version 2 with an owner-only local pseudonym key;
|
|
31
34
|
- application children receive only explicit local mappings and stay in an owned process
|
|
@@ -39,6 +42,11 @@ No single environment variable or config edit can redirect runtime commands to
|
|
|
39
42
|
production. Optional source preparation is a separate boundary and never provides a
|
|
40
43
|
hosted execution mode.
|
|
41
44
|
|
|
45
|
+
External source mode verifies access but does not own it. Rehearsal makes no source-side
|
|
46
|
+
changes and removes only its local credential and receipt. The provider owns early
|
|
47
|
+
revocation and the database enforces the reviewed short expiration; Rehearsal never
|
|
48
|
+
reports local cleanup as proof that a provider-owned role was deleted.
|
|
49
|
+
|
|
42
50
|
## Identity providers
|
|
43
51
|
|
|
44
52
|
An optional external identity handshake is distinct from database access. Only declared
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Stability and the first stable release
|
|
2
|
+
|
|
3
|
+
Rehearsal leaves beta when the workflow it already promises is dependable. Stable
|
|
4
|
+
`0.1.0` does not require every planned database, desktop interface, or paid feature.
|
|
5
|
+
|
|
6
|
+
## What becomes a compatibility promise
|
|
7
|
+
|
|
8
|
+
Starting with `0.1.0`, Rehearsal treats these documented surfaces as public:
|
|
9
|
+
|
|
10
|
+
- CLI command names, flags, JSON result fields, and exit categories in
|
|
11
|
+
[CLI commands](commands.md);
|
|
12
|
+
- `rehearsal.config.mjs` schema version 1 and the policy formats documented in
|
|
13
|
+
[Configuration](configuration.md), [Sanitization](sanitization.md), and
|
|
14
|
+
[Runtime and identity policies](runtime-policies.md);
|
|
15
|
+
- package entry points and generated declarations listed in
|
|
16
|
+
[TypeScript](typescript.md);
|
|
17
|
+
- supported Node.js, operating-system, Docker, Supabase, and PostgreSQL boundaries in
|
|
18
|
+
[COMPATIBILITY.md](https://github.com/Ddupasquier/rehearsal-db/blob/main/COMPATIBILITY.md);
|
|
19
|
+
- non-destructive ownership, confirmation, credential, and hosted-access guarantees in
|
|
20
|
+
[Security model](security-model.md).
|
|
21
|
+
|
|
22
|
+
Patch releases may fix defects and improve messages without changing those contracts.
|
|
23
|
+
A future breaking contract change requires a version that clearly communicates it under
|
|
24
|
+
Semantic Versioning. Internal source files, terminal decoration, exact prose, temporary
|
|
25
|
+
file names, and undocumented package paths are not compatibility promises.
|
|
26
|
+
|
|
27
|
+
## What blocks a release candidate
|
|
28
|
+
|
|
29
|
+
An issue blocks the active release candidate when it can cause any of the following within a supported
|
|
30
|
+
configuration:
|
|
31
|
+
|
|
32
|
+
- data loss, credential exposure, hosted-system access, or mutation outside resources
|
|
33
|
+
that Rehearsal can prove it owns;
|
|
34
|
+
- an incorrect baseline, migration set, verification result, or successful status;
|
|
35
|
+
- failure of clean installation, supported-version upgrade, restore, reset, cleanup, or
|
|
36
|
+
interruption recovery;
|
|
37
|
+
- a documented CLI, configuration, policy, or TypeScript contract behaving differently
|
|
38
|
+
from its reference;
|
|
39
|
+
- a supported Node.js, Supabase, PostgreSQL, macOS, or Linux path failing its maintained
|
|
40
|
+
automated gate.
|
|
41
|
+
|
|
42
|
+
Cosmetic improvements, new database families, production-shaped sampling, the VS Code
|
|
43
|
+
extension, desktop work, and commercial capabilities do not block the first stable
|
|
44
|
+
release.
|
|
45
|
+
|
|
46
|
+
## Promotion sequence
|
|
47
|
+
|
|
48
|
+
1. Ship beta.18 with only stability, release-safety, upgrade, and documentation work.
|
|
49
|
+
2. Freeze broad feature development on the release branch.
|
|
50
|
+
3. Publish a release candidate from protected `main` through the reviewed npm environment.
|
|
51
|
+
4. Run the complete Rehearsal Test Lab gate twice from clean isolated workspaces.
|
|
52
|
+
5. Perform one final package-only upgrade in a real consumer. The consumer must use the
|
|
53
|
+
published package rather than duplicate Rehearsal machinery.
|
|
54
|
+
6. Keep the current candidate available for at least seven calendar days. Restart the
|
|
55
|
+
window if a blocking package change is required. The container-lifecycle correction
|
|
56
|
+
in `0.1.0-rc.2` restarts this observation window.
|
|
57
|
+
7. Publish `0.1.0` only when no blocking issue remains and the accepted registry artifact
|
|
58
|
+
matches the tested artifact exactly.
|
|
59
|
+
|
|
60
|
+
Test Lab is the primary verification system. A real consumer is the final compatibility
|
|
61
|
+
check, not a replacement for package-owned coverage. Real Google or GitHub consent does
|
|
62
|
+
not need to run on every build unless authentication behavior changes.
|
|
63
|
+
|
|
64
|
+
## npm channels
|
|
65
|
+
|
|
66
|
+
Beta and release-candidate versions publish under the `beta` tag. Before a stable
|
|
67
|
+
release exists, `latest` follows that reviewed prerelease so the ordinary install command
|
|
68
|
+
remains useful. Once stable `0.1.0` exists, `latest` stays on the stable line and `beta`
|
|
69
|
+
continues to identify the newest prerelease. Stable publication never rewrites the
|
|
70
|
+
`beta` tag.
|