@rehearsal-db/core 0.1.0-beta.14 → 0.1.0-beta.16

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 (236) hide show
  1. package/CHANGELOG.md +51 -1
  2. package/README.md +1 -0
  3. package/dist/package.json +101 -0
  4. package/dist/scripts/install/create_root_config.d.mts +3 -0
  5. package/dist/scripts/install/create_root_config.mjs +13 -0
  6. package/dist/scripts/runtime/manage_database.d.mts +6 -0
  7. package/{scripts → dist/scripts}/runtime/manage_database.mjs +6 -8
  8. package/dist/src/application/session.d.mts +96 -0
  9. package/dist/src/application/session.mjs +380 -0
  10. package/dist/src/baseline/artifact.d.mts +119 -0
  11. package/dist/src/baseline/artifact.mjs +522 -0
  12. package/dist/src/baseline/builder.d.mts +19 -0
  13. package/dist/src/baseline/builder.mjs +142 -0
  14. package/dist/src/baseline/input_discovery.d.mts +13 -0
  15. package/dist/src/baseline/input_discovery.mjs +149 -0
  16. package/dist/src/baseline/policy_review.d.mts +56 -0
  17. package/dist/src/baseline/policy_review.mjs +73 -0
  18. package/dist/src/baseline/preparation.d.mts +63 -0
  19. package/dist/src/baseline/preparation.mjs +295 -0
  20. package/dist/src/baseline/privacy_contract.d.mts +147 -0
  21. package/dist/src/baseline/privacy_contract.mjs +2 -0
  22. package/dist/src/baseline/privacy_dependency_graph.d.mts +7 -0
  23. package/dist/src/baseline/privacy_dependency_graph.mjs +110 -0
  24. package/dist/src/baseline/privacy_engine.d.mts +5 -0
  25. package/dist/src/baseline/privacy_engine.mjs +4 -0
  26. package/dist/src/baseline/privacy_key.d.mts +7 -0
  27. package/dist/src/baseline/privacy_key.mjs +42 -0
  28. package/dist/src/baseline/privacy_pipeline.d.mts +7 -0
  29. package/dist/src/baseline/privacy_pipeline.mjs +127 -0
  30. package/dist/src/baseline/privacy_policy.d.mts +6 -0
  31. package/dist/src/baseline/privacy_policy.mjs +237 -0
  32. package/dist/src/baseline/privacy_recipe_validation.d.mts +5 -0
  33. package/dist/src/baseline/privacy_recipe_validation.mjs +289 -0
  34. package/dist/src/baseline/privacy_rule_registry.d.mts +18 -0
  35. package/dist/src/baseline/privacy_rule_registry.mjs +143 -0
  36. package/dist/src/baseline/privacy_structured_json.d.mts +66 -0
  37. package/dist/src/baseline/privacy_structured_json.mjs +464 -0
  38. package/dist/src/baseline/privacy_transforms.d.mts +18 -0
  39. package/dist/src/baseline/privacy_transforms.mjs +142 -0
  40. package/dist/src/baseline/privacy_validation_shared.d.mts +31 -0
  41. package/dist/src/baseline/privacy_validation_shared.mjs +127 -0
  42. package/dist/src/baseline/sanitization_policy.d.mts +69 -0
  43. package/dist/src/baseline/sanitization_policy.mjs +227 -0
  44. package/dist/src/baseline/schema_snapshot.d.mts +19 -0
  45. package/dist/src/baseline/schema_snapshot.mjs +79 -0
  46. package/dist/src/cli/application_command_handlers.d.mts +8 -0
  47. package/dist/src/cli/application_command_handlers.mjs +52 -0
  48. package/dist/src/cli/arguments.d.mts +38 -0
  49. package/dist/src/cli/arguments.mjs +128 -0
  50. package/dist/src/cli/basic_command_handlers.d.mts +4 -0
  51. package/dist/src/cli/basic_command_handlers.mjs +56 -0
  52. package/dist/src/cli/cli_commands.d.mts +14 -0
  53. package/dist/src/cli/cli_commands.mjs +32 -0
  54. package/dist/src/cli/command_contract.d.mts +57 -0
  55. package/dist/src/cli/command_contract.mjs +39 -0
  56. package/dist/src/cli/command_output.d.mts +12 -0
  57. package/dist/src/cli/command_output.mjs +20 -0
  58. package/dist/src/cli/command_registry.d.mts +7 -0
  59. package/dist/src/cli/command_registry.mjs +14 -0
  60. package/dist/src/cli/guided.d.mts +53 -0
  61. package/dist/src/cli/guided.mjs +719 -0
  62. package/dist/src/cli/identity_command_handlers.d.mts +8 -0
  63. package/dist/src/cli/identity_command_handlers.mjs +43 -0
  64. package/dist/src/cli/init_command.d.mts +21 -0
  65. package/dist/src/cli/init_command.mjs +90 -0
  66. package/dist/src/cli/inspection_command_handlers.d.mts +3 -0
  67. package/dist/src/cli/inspection_command_handlers.mjs +79 -0
  68. package/dist/src/cli/project_command_handlers.d.mts +11 -0
  69. package/dist/src/cli/project_command_handlers.mjs +92 -0
  70. package/dist/src/cli/project_commands.d.mts +159 -0
  71. package/dist/src/cli/project_commands.mjs +183 -0
  72. package/dist/src/cli/rehearsal.d.mts +7 -0
  73. package/dist/src/cli/rehearsal.mjs +190 -0
  74. package/dist/src/cli/renderers.d.mts +299 -0
  75. package/dist/src/cli/renderers.mjs +554 -0
  76. package/dist/src/cli/runtime_action_handlers.d.mts +12 -0
  77. package/dist/src/cli/runtime_action_handlers.mjs +201 -0
  78. package/dist/src/cli/runtime_commands.d.mts +269 -0
  79. package/dist/src/cli/runtime_commands.mjs +601 -0
  80. package/dist/src/cli/source_command_handlers.d.mts +8 -0
  81. package/dist/src/cli/source_command_handlers.mjs +62 -0
  82. package/dist/src/cli/source_commands.d.mts +436 -0
  83. package/dist/src/cli/source_commands.mjs +301 -0
  84. package/dist/src/cli/terminal.d.mts +59 -0
  85. package/dist/src/cli/terminal.mjs +230 -0
  86. package/dist/src/identity/claim.d.mts +5 -0
  87. package/dist/src/identity/claim.mjs +4 -0
  88. package/dist/src/identity/claim_contract.d.mts +135 -0
  89. package/dist/src/identity/claim_contract.mjs +6 -0
  90. package/dist/src/identity/claim_executor.d.mts +10 -0
  91. package/dist/src/identity/claim_executor.mjs +358 -0
  92. package/dist/src/identity/claim_identity_lookup.d.mts +17 -0
  93. package/dist/src/identity/claim_identity_lookup.mjs +125 -0
  94. package/dist/src/identity/claim_plan.d.mts +8 -0
  95. package/dist/src/identity/claim_plan.mjs +61 -0
  96. package/dist/src/identity/claim_policy.d.mts +3 -0
  97. package/dist/src/identity/claim_policy.mjs +377 -0
  98. package/dist/src/identity/claim_sql.d.mts +10 -0
  99. package/dist/src/identity/claim_sql.mjs +46 -0
  100. package/dist/src/identity/claim_verification.d.mts +13 -0
  101. package/dist/src/identity/claim_verification.mjs +35 -0
  102. package/dist/src/identity/provider_configuration.d.mts +37 -0
  103. package/dist/src/identity/provider_configuration.mjs +132 -0
  104. package/dist/src/identity/storage.d.mts +21 -0
  105. package/dist/src/identity/storage.mjs +122 -0
  106. package/dist/src/index.d.mts +4 -0
  107. package/dist/src/index.mjs +3 -0
  108. package/dist/src/project/configuration.d.mts +13 -0
  109. package/dist/src/project/configuration.mjs +9 -0
  110. package/dist/src/project/configuration_contract.d.mts +125 -0
  111. package/dist/src/project/configuration_contract.mjs +27 -0
  112. package/dist/src/project/configuration_discovery.d.mts +7 -0
  113. package/dist/src/project/configuration_discovery.mjs +37 -0
  114. package/dist/src/project/configuration_normalization.d.mts +107 -0
  115. package/dist/src/project/configuration_normalization.mjs +302 -0
  116. package/dist/src/project/configuration_rendering.d.mts +14 -0
  117. package/dist/src/project/configuration_rendering.mjs +224 -0
  118. package/dist/src/project/configuration_resolution.d.mts +125 -0
  119. package/dist/src/project/configuration_resolution.mjs +81 -0
  120. package/dist/src/project/configuration_schema.d.mts +22 -0
  121. package/dist/src/project/configuration_schema.mjs +160 -0
  122. package/dist/src/project/configuration_validation.d.mts +22 -0
  123. package/dist/src/project/configuration_validation.mjs +136 -0
  124. package/dist/src/project/install_scaffold.d.mts +42 -0
  125. package/dist/src/project/install_scaffold.mjs +263 -0
  126. package/dist/src/project/project_detection.d.mts +14 -0
  127. package/dist/src/project/project_detection.mjs +47 -0
  128. package/dist/src/project/setup.d.mts +87 -0
  129. package/dist/src/project/setup.mjs +414 -0
  130. package/dist/src/project/support_report.d.mts +88 -0
  131. package/dist/src/project/support_report.mjs +83 -0
  132. package/dist/src/runtime/cleanup.d.mts +85 -0
  133. package/dist/src/runtime/cleanup.mjs +311 -0
  134. package/dist/src/runtime/lifecycle_engine.d.mts +63 -0
  135. package/dist/src/runtime/lifecycle_engine.mjs +114 -0
  136. package/dist/src/runtime/migration_history.d.mts +51 -0
  137. package/dist/src/runtime/migration_history.mjs +170 -0
  138. package/dist/src/runtime/plan.d.mts +134 -0
  139. package/dist/src/runtime/plan.mjs +637 -0
  140. package/dist/src/runtime/policy.d.mts +66 -0
  141. package/dist/src/runtime/policy.mjs +369 -0
  142. package/dist/src/runtime/restore.d.mts +22 -0
  143. package/dist/src/runtime/restore.mjs +276 -0
  144. package/dist/src/runtime/service_environment.d.mts +11 -0
  145. package/dist/src/runtime/service_environment.mjs +58 -0
  146. package/dist/src/runtime/topology.d.mts +51 -0
  147. package/dist/src/runtime/topology.mjs +128 -0
  148. package/dist/src/shared/diagnostics.d.mts +97 -0
  149. package/dist/src/shared/diagnostics.mjs +170 -0
  150. package/dist/src/shared/human_output.d.mts +2 -0
  151. package/{src → dist/src}/shared/human_output.mjs +1 -3
  152. package/dist/src/shared/node_runtime.d.mts +14 -0
  153. package/dist/src/shared/node_runtime.mjs +25 -0
  154. package/dist/src/shared/operation_guard.d.mts +22 -0
  155. package/dist/src/shared/operation_guard.mjs +150 -0
  156. package/dist/src/shared/process_environment.d.mts +15 -0
  157. package/dist/src/shared/process_environment.mjs +50 -0
  158. package/dist/src/source/access.d.mts +165 -0
  159. package/dist/src/source/access.mjs +602 -0
  160. package/dist/src/source/asset_transfer.d.mts +27 -0
  161. package/dist/src/source/asset_transfer.mjs +202 -0
  162. package/dist/src/source/baseline.d.mts +66 -0
  163. package/dist/src/source/baseline.mjs +372 -0
  164. package/dist/src/source/external_reader.d.mts +42 -0
  165. package/dist/src/source/external_reader.mjs +278 -0
  166. package/dist/src/source/postgresql_access.d.mts +86 -0
  167. package/dist/src/source/postgresql_access.mjs +454 -0
  168. package/dist/src/source/privacy_paths.d.mts +10 -0
  169. package/dist/src/source/privacy_paths.mjs +27 -0
  170. package/dist/src/targets/driver.d.mts +2 -0
  171. package/dist/src/targets/driver.mjs +13 -0
  172. package/dist/src/targets/postgresql.d.mts +5 -0
  173. package/dist/src/targets/postgresql.mjs +620 -0
  174. package/dist/src/targets/supabase.d.mts +8 -0
  175. package/dist/src/targets/supabase.mjs +627 -0
  176. package/dist/src/targets/supabase_environment.d.mts +49 -0
  177. package/dist/src/targets/supabase_environment.mjs +162 -0
  178. package/dist/src/targets/target.d.mts +33 -0
  179. package/dist/src/targets/target.mjs +39 -0
  180. package/docs/README.md +1 -0
  181. package/docs/architecture.md +133 -10
  182. package/docs/configuration.md +9 -3
  183. package/docs/index.md +1 -0
  184. package/docs/releasing.md +8 -3
  185. package/docs/typescript.md +134 -0
  186. package/package.json +36 -29
  187. package/types/configuration.d.mts +5 -0
  188. package/scripts/install/create_root_config.mjs +0 -19
  189. package/src/README.md +0 -17
  190. package/src/application/session.mjs +0 -435
  191. package/src/baseline/artifact.mjs +0 -684
  192. package/src/baseline/builder.mjs +0 -164
  193. package/src/baseline/input_discovery.mjs +0 -155
  194. package/src/baseline/policy_review.mjs +0 -92
  195. package/src/baseline/preparation.mjs +0 -323
  196. package/src/baseline/privacy_engine.mjs +0 -1399
  197. package/src/baseline/privacy_structured_json.mjs +0 -554
  198. package/src/baseline/sanitization_policy.mjs +0 -299
  199. package/src/baseline/schema_snapshot.mjs +0 -113
  200. package/src/cli/arguments.mjs +0 -126
  201. package/src/cli/guided.mjs +0 -814
  202. package/src/cli/init_command.mjs +0 -94
  203. package/src/cli/rehearsal.mjs +0 -972
  204. package/src/cli/renderers.mjs +0 -638
  205. package/src/cli/runtime_commands.mjs +0 -707
  206. package/src/cli/source_commands.mjs +0 -355
  207. package/src/cli/terminal.mjs +0 -275
  208. package/src/identity/claim.mjs +0 -1165
  209. package/src/identity/provider_configuration.mjs +0 -177
  210. package/src/identity/storage.mjs +0 -165
  211. package/src/project/configuration.d.mts +0 -164
  212. package/src/project/configuration.mjs +0 -1337
  213. package/src/project/install_scaffold.mjs +0 -325
  214. package/src/project/setup.mjs +0 -474
  215. package/src/project/support_report.mjs +0 -105
  216. package/src/runtime/cleanup.mjs +0 -381
  217. package/src/runtime/migration_history.mjs +0 -220
  218. package/src/runtime/plan.mjs +0 -722
  219. package/src/runtime/policy.mjs +0 -438
  220. package/src/runtime/restore.mjs +0 -337
  221. package/src/runtime/service_environment.mjs +0 -82
  222. package/src/runtime/topology.mjs +0 -177
  223. package/src/shared/diagnostics.mjs +0 -207
  224. package/src/shared/node_runtime.mjs +0 -31
  225. package/src/shared/operation_guard.mjs +0 -162
  226. package/src/shared/process_environment.mjs +0 -68
  227. package/src/source/access.mjs +0 -798
  228. package/src/source/asset_transfer.mjs +0 -259
  229. package/src/source/baseline.mjs +0 -482
  230. package/src/source/external_reader.mjs +0 -368
  231. package/src/source/postgresql_access.mjs +0 -638
  232. package/src/source/privacy_paths.mjs +0 -38
  233. package/src/targets/postgresql.mjs +0 -808
  234. package/src/targets/supabase.mjs +0 -840
  235. package/src/targets/supabase_environment.mjs +0 -229
  236. package/src/targets/target.mjs +0 -66
@@ -0,0 +1,142 @@
1
+ /** Deterministic primitive, identifier-shape, URL, and path transformations. */
2
+ import { createHmac } from "node:crypto";
3
+ import { URL_PATH_PREFIX, URL_TOKEN_LENGTH, UUID, } from "./privacy_validation_shared.mjs";
4
+ export const privacyDigest = (key, namespace, value) => {
5
+ const serialized = JSON.stringify(value);
6
+ if (serialized === undefined) {
7
+ throw new Error("Privacy input must be JSON-compatible.");
8
+ }
9
+ return createHmac("sha256", key)
10
+ .update(namespace)
11
+ .update("\0")
12
+ .update(serialized)
13
+ .digest();
14
+ };
15
+ const gtinCheckDigit = (payload) => {
16
+ const sum = [...payload]
17
+ .reverse()
18
+ .reduce((total, digit, index) => total + Number(digit) * (index % 2 === 0 ? 3 : 1), 0);
19
+ return String((10 - (sum % 10)) % 10);
20
+ };
21
+ const isValidGtin = (value, length) => typeof value === "string" &&
22
+ value.length === length &&
23
+ /^\d+$/u.test(value) &&
24
+ value.at(-1) === gtinCheckDigit(value.slice(0, -1));
25
+ export const pseudonymize = (key, recipe, value) => {
26
+ if (value === null)
27
+ return null;
28
+ const bytes = privacyDigest(key, recipe.namespace, value);
29
+ if (recipe.format === "uuid") {
30
+ const copy = Buffer.from(bytes.subarray(0, 16));
31
+ copy[6] = (copy[6] & 0x0f) | 0x40;
32
+ copy[8] = (copy[8] & 0x3f) | 0x80;
33
+ const hex = copy.toString("hex");
34
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
35
+ }
36
+ if (recipe.format === "email") {
37
+ return `rehearsal+${bytes.toString("hex").slice(0, 24)}@example.invalid`;
38
+ }
39
+ if (recipe.format === "integer") {
40
+ return Number(bytes.readBigUInt64BE(0) % 9007199254740991n) + 1;
41
+ }
42
+ if (recipe.format === "hex") {
43
+ return bytes.toString("hex").slice(0, recipe.length);
44
+ }
45
+ if (recipe.format === "gtin") {
46
+ const allowedLengths = recipe.allowedLengths ??
47
+ (recipe.length === undefined ? [] : [recipe.length]);
48
+ const sourceLength = typeof value === "string" ? value.length : null;
49
+ if (sourceLength === null ||
50
+ !allowedLengths.includes(sourceLength) ||
51
+ !isValidGtin(value, sourceLength)) {
52
+ if (recipe.length !== undefined) {
53
+ throw new Error(`A gtin pseudonym source must be a valid normalized GTIN-${recipe.length} string.`);
54
+ }
55
+ throw new Error(`A gtin pseudonym source must be a valid normalized GTIN string with an allowed length (${allowedLengths.join(", ")}).`);
56
+ }
57
+ const modulus = 10n ** BigInt(sourceLength - 1);
58
+ const payload = (BigInt(`0x${bytes.toString("hex")}`) % modulus)
59
+ .toString()
60
+ .padStart(sourceLength - 1, "0");
61
+ return `${payload}${gtinCheckDigit(payload)}`;
62
+ }
63
+ if (recipe.format === "url") {
64
+ if (typeof value !== "string" ||
65
+ value.trim() !== value ||
66
+ /\s/u.test(value)) {
67
+ throw new Error("A url pseudonym source must be a valid HTTPS URL.");
68
+ }
69
+ let source;
70
+ try {
71
+ source = new URL(value);
72
+ }
73
+ catch {
74
+ throw new Error("A url pseudonym source must be a valid HTTPS URL.");
75
+ }
76
+ if (source.protocol !== "https:") {
77
+ throw new Error("A url pseudonym source must be a valid HTTPS URL.");
78
+ }
79
+ const output = `${recipe.origin}${URL_PATH_PREFIX}${bytes
80
+ .toString("hex")
81
+ .slice(0, URL_TOKEN_LENGTH)}`;
82
+ if (output.length > recipe.maxLength) {
83
+ throw new Error("A url pseudonym exceeded its reviewed maximum length.");
84
+ }
85
+ return output;
86
+ }
87
+ const token = `rehearsal_${bytes.toString("hex")}`;
88
+ return token.slice(0, recipe.maxLength);
89
+ };
90
+ export const remapPrivacyPath = ({ key, mapping, bindingValue, value, label, }) => {
91
+ if (typeof value !== "string") {
92
+ throw new Error(`${label} must be a Storage-style path string.`);
93
+ }
94
+ if (Buffer.byteLength(value) > mapping.maximumBytes) {
95
+ throw new Error(`${label} exceeds its reviewed byte limit.`);
96
+ }
97
+ const segments = value.split("/");
98
+ if (!value ||
99
+ value.startsWith("/") ||
100
+ value.endsWith("/") ||
101
+ segments.length > mapping.maximumSegments ||
102
+ segments.some((segment) => !segment || segment === "." || segment === "..")) {
103
+ throw new Error(`${label} is not a safe bounded Storage path.`);
104
+ }
105
+ if (typeof bindingValue !== "string" ||
106
+ !bindingValue ||
107
+ bindingValue.includes("/") ||
108
+ bindingValue === "." ||
109
+ bindingValue === "..") {
110
+ throw new Error(`${label} has an invalid reviewed identity binding.`);
111
+ }
112
+ if (segments[0] !== bindingValue) {
113
+ throw new Error(`${label} does not begin with its exact reviewed identity.`);
114
+ }
115
+ const destination = [
116
+ String(pseudonymize(key, mapping, bindingValue)),
117
+ ...segments.slice(1),
118
+ ].join("/");
119
+ if (Buffer.byteLength(destination) > mapping.maximumBytes) {
120
+ throw new Error(`${label} destination exceeds its reviewed byte limit.`);
121
+ }
122
+ return destination;
123
+ };
124
+ export const substitutePrivacyBinding = ({ key, recipe, bindingValue, value, label, }) => {
125
+ if (typeof bindingValue !== "string" || !UUID.test(bindingValue)) {
126
+ throw new Error(`${label} has an invalid reviewed UUID binding.`);
127
+ }
128
+ if (value === null)
129
+ return null;
130
+ if (typeof value !== "string") {
131
+ throw new Error(`${label} must be text or null for binding substitution.`);
132
+ }
133
+ if (Buffer.byteLength(value) > recipe.maximumBytes) {
134
+ throw new Error(`${label} exceeds its reviewed byte limit.`);
135
+ }
136
+ const replacement = String(pseudonymize(key, recipe, bindingValue));
137
+ const destination = value.replaceAll(bindingValue, replacement);
138
+ if (Buffer.byteLength(destination) > recipe.maximumBytes) {
139
+ throw new Error(`${label} destination exceeds its reviewed byte limit.`);
140
+ }
141
+ return destination;
142
+ };
@@ -0,0 +1,31 @@
1
+ /** Redacted primitive validators and closed vocabulary for privacy policy v2. */
2
+ import type { PrivacyObject } from "./privacy_contract.mjs";
3
+ export declare const IDENTIFIER: RegExp;
4
+ export declare const BINDING_NAME: RegExp;
5
+ export declare const ENVIRONMENT_KEY: RegExp;
6
+ export declare const HEX_64: RegExp;
7
+ export declare const UUID: RegExp;
8
+ export declare const PORTABLE_CODE: RegExp;
9
+ export declare const ACTIONS: Set<string>;
10
+ export declare const PSEUDONYM_FORMATS: Set<string>;
11
+ export declare const HEX_LENGTHS: Set<number>;
12
+ export declare const GTIN_LENGTHS: Set<number>;
13
+ export declare const DATE_REPRESENTATIONS: Set<string>;
14
+ export declare const DERIVATION_KINDS: Set<string>;
15
+ export declare const URL_PATH_PREFIX = "/rehearsal/";
16
+ export declare const URL_TOKEN_LENGTH = 32;
17
+ export declare const MAXIMUM_URL_LENGTH = 2048;
18
+ export declare const MAXIMUM_TEXT_SUBSTITUTION_BYTES = 65536;
19
+ export declare const LOOPBACK_HOSTNAMES: Set<string>;
20
+ export declare const DEFAULT_PATH_MAXIMUM_BYTES = 2048;
21
+ export declare const DEFAULT_PATH_MAXIMUM_SEGMENTS = 32;
22
+ export declare const MAXIMUM_POLICY_DEPTH = 16;
23
+ export declare const MAXIMUM_ENUM_VALUES = 128;
24
+ export declare const MAXIMUM_ENUM_STRING_BYTES = 256;
25
+ export declare const isPrivacyObject: (value: unknown) => value is PrivacyObject;
26
+ export declare const assertKnownKeys: (value: PrivacyObject, allowed: readonly string[], label: string) => void;
27
+ export declare const validateIdentifier: (value: unknown, label: string) => string;
28
+ export declare const validateNonEmpty: (value: unknown, label: string) => string;
29
+ export declare const validatePositiveInteger: (value: unknown, label: string, maximum?: number) => number;
30
+ export declare const validateReviewedScalar: (value: unknown, label: string) => string | number | boolean;
31
+ export declare const validateHttpsLoopbackOrigin: (value: unknown, label: string) => string;
@@ -0,0 +1,127 @@
1
+ /** Redacted primitive validators and closed vocabulary for privacy policy v2. */
2
+ export const IDENTIFIER = /^[a-z][a-z0-9_]{0,62}$/u;
3
+ export const BINDING_NAME = /^[a-z][a-z0-9-]{0,62}$/u;
4
+ export const ENVIRONMENT_KEY = /^[A-Z][A-Z0-9_]*$/u;
5
+ export const HEX_64 = /^[a-f0-9]{64}$/u;
6
+ export const UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/iu;
7
+ export const PORTABLE_CODE = /^[A-Za-z0-9][A-Za-z0-9_-]*$/u;
8
+ export const ACTIONS = new Set([
9
+ "KEEP",
10
+ "PSEUDONYMIZE",
11
+ "REPLACE",
12
+ "EXCLUDE",
13
+ "DERIVE",
14
+ ]);
15
+ export const PSEUDONYM_FORMATS = new Set([
16
+ "uuid",
17
+ "email",
18
+ "text",
19
+ "integer",
20
+ "hex",
21
+ "gtin",
22
+ "url",
23
+ ]);
24
+ export const HEX_LENGTHS = new Set([32, 64]);
25
+ export const GTIN_LENGTHS = new Set([8, 12, 13, 14]);
26
+ export const DATE_REPRESENTATIONS = new Set([
27
+ "iso-string",
28
+ "epoch-milliseconds",
29
+ ]);
30
+ export const DERIVATION_KINDS = new Set([
31
+ "date-shift",
32
+ "digest",
33
+ "enum",
34
+ "validated-string",
35
+ "path-map",
36
+ "json-object",
37
+ "json-array",
38
+ "json-union",
39
+ "json-dictionary",
40
+ "approved-owner",
41
+ "binding-substitute",
42
+ ]);
43
+ export const URL_PATH_PREFIX = "/rehearsal/";
44
+ export const URL_TOKEN_LENGTH = 32;
45
+ export const MAXIMUM_URL_LENGTH = 2_048;
46
+ export const MAXIMUM_TEXT_SUBSTITUTION_BYTES = 65_536;
47
+ export const LOOPBACK_HOSTNAMES = new Set([
48
+ "127.0.0.1",
49
+ "::1",
50
+ "[::1]",
51
+ "localhost",
52
+ ]);
53
+ export const DEFAULT_PATH_MAXIMUM_BYTES = 2_048;
54
+ export const DEFAULT_PATH_MAXIMUM_SEGMENTS = 32;
55
+ export const MAXIMUM_POLICY_DEPTH = 16;
56
+ export const MAXIMUM_ENUM_VALUES = 128;
57
+ export const MAXIMUM_ENUM_STRING_BYTES = 256;
58
+ export const isPrivacyObject = (value) => value !== null &&
59
+ typeof value === "object" &&
60
+ !Array.isArray(value) &&
61
+ Object.getPrototypeOf(value) === Object.prototype;
62
+ export const assertKnownKeys = (value, allowed, label) => {
63
+ for (const key of Object.keys(value)) {
64
+ if (!allowed.includes(key))
65
+ throw new Error(`${label}.${key} is unknown.`);
66
+ }
67
+ };
68
+ export const validateIdentifier = (value, label) => {
69
+ if (typeof value !== "string" || !IDENTIFIER.test(value)) {
70
+ throw new Error(`${label} must be a lowercase PostgreSQL identifier.`);
71
+ }
72
+ return value;
73
+ };
74
+ export const validateNonEmpty = (value, label) => {
75
+ if (typeof value !== "string" || !value.trim()) {
76
+ throw new Error(`${label} must be a non-empty string.`);
77
+ }
78
+ return value.trim();
79
+ };
80
+ export const validatePositiveInteger = (value, label, maximum = Number.MAX_SAFE_INTEGER) => {
81
+ if (typeof value !== "number" ||
82
+ !Number.isSafeInteger(value) ||
83
+ value < 1 ||
84
+ value > maximum) {
85
+ throw new Error(`${label} must be an integer from 1 through ${maximum}.`);
86
+ }
87
+ return value;
88
+ };
89
+ export const validateReviewedScalar = (value, label) => {
90
+ if (typeof value === "string") {
91
+ if (Buffer.byteLength(value) > MAXIMUM_ENUM_STRING_BYTES) {
92
+ throw new Error(`${label} must contain at most ${MAXIMUM_ENUM_STRING_BYTES} UTF-8 bytes.`);
93
+ }
94
+ return value;
95
+ }
96
+ if (typeof value === "boolean")
97
+ return value;
98
+ if (typeof value === "number" &&
99
+ Number.isFinite(value) &&
100
+ !Object.is(value, -0)) {
101
+ return value;
102
+ }
103
+ throw new Error(`${label} must be a JSON string, finite number, or boolean.`);
104
+ };
105
+ export const validateHttpsLoopbackOrigin = (value, label) => {
106
+ if (typeof value !== "string" || value.trim() !== value || !value) {
107
+ throw new Error(`${label} must be a safe HTTPS loopback origin.`);
108
+ }
109
+ let parsed;
110
+ try {
111
+ parsed = new URL(value);
112
+ }
113
+ catch {
114
+ throw new Error(`${label} must be a safe HTTPS loopback origin.`);
115
+ }
116
+ if (parsed.protocol !== "https:" ||
117
+ !LOOPBACK_HOSTNAMES.has(parsed.hostname) ||
118
+ ![parsed.origin, `${parsed.origin}/`].includes(value) ||
119
+ parsed.username ||
120
+ parsed.password ||
121
+ (parsed.pathname !== "/" && parsed.pathname !== "") ||
122
+ parsed.search ||
123
+ parsed.hash) {
124
+ throw new Error(`${label} must be a safe HTTPS loopback origin.`);
125
+ }
126
+ return parsed.origin;
127
+ };
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Generic, fail-closed sanitization policy validation and value operations.
3
+ * Projects own the policy, schema inventory, pseudonym keys, and derivation logic.
4
+ */
5
+ import { type ExecutablePrivacyPolicy } from "./privacy_engine.mjs";
6
+ export declare const SANITIZATION_ACTIONS: Readonly<{
7
+ KEEP: "KEEP";
8
+ PSEUDONYMIZE: "PSEUDONYMIZE";
9
+ REPLACE: "REPLACE";
10
+ EXCLUDE: "EXCLUDE";
11
+ DERIVE: "DERIVE";
12
+ }>;
13
+ export declare const EXCLUDED_VALUE: unique symbol;
14
+ export type SanitizationAction = (typeof SANITIZATION_ACTIONS)[keyof typeof SANITIZATION_ACTIONS];
15
+ export interface LegacySanitizationPolicyColumn extends PolicyObject {
16
+ readonly name: string;
17
+ readonly action: SanitizationAction;
18
+ readonly generated: "ALWAYS" | "NEVER";
19
+ readonly identity: "YES" | "NO";
20
+ readonly foreignKey: Readonly<{
21
+ schema: string;
22
+ table: string;
23
+ column: string;
24
+ }> | null;
25
+ }
26
+ export interface LegacySanitizationPolicyTable {
27
+ readonly schema?: string;
28
+ readonly name: string;
29
+ readonly sourceRows: "STREAM AND SANITIZE" | "EXCLUDE";
30
+ readonly columns: readonly LegacySanitizationPolicyColumn[];
31
+ }
32
+ export interface LegacySanitizationPolicy {
33
+ readonly policyVersion: 1;
34
+ readonly migrationCutoff: string;
35
+ readonly tables: readonly LegacySanitizationPolicyTable[];
36
+ }
37
+ export type RuntimeSanitizationPolicy = LegacySanitizationPolicy | ExecutablePrivacyPolicy;
38
+ type PolicyObject = Record<string, unknown>;
39
+ type SanitizationCallback = (value: unknown, context: Record<string, unknown>) => unknown;
40
+ export declare const normalizeSanitizationAction: (action: unknown) => SanitizationAction;
41
+ export declare const validateRuntimeSanitizationPolicy: (policy: unknown) => RuntimeSanitizationPolicy;
42
+ export declare const readBoundRuntimeSanitizationPolicy: ({ bytes, expectedSha256, }: {
43
+ bytes: Uint8Array | string;
44
+ expectedSha256: string;
45
+ }) => RuntimeSanitizationPolicy;
46
+ export declare const validateSanitizationCoverage: ({ policy, schemaTables, }: {
47
+ policy: unknown;
48
+ schemaTables: unknown;
49
+ }) => Readonly<{
50
+ policyVersion: unknown;
51
+ tableCount: number;
52
+ columnCount: number;
53
+ tables: readonly {
54
+ schema: string;
55
+ columns: {
56
+ name: string;
57
+ action: SanitizationAction;
58
+ }[];
59
+ }[];
60
+ }>;
61
+ export declare const applySanitizationAction: ({ action, value, replace, pseudonymize, derive, context, }: {
62
+ action: unknown;
63
+ value: unknown;
64
+ replace?: SanitizationCallback;
65
+ pseudonymize?: SanitizationCallback;
66
+ derive?: SanitizationCallback;
67
+ context?: Record<string, unknown>;
68
+ }) => unknown | typeof EXCLUDED_VALUE;
69
+ export {};
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Generic, fail-closed sanitization policy validation and value operations.
3
+ * Projects own the policy, schema inventory, pseudonym keys, and derivation logic.
4
+ */
5
+ import { createHash } from "node:crypto";
6
+ import { validateExecutablePrivacyPolicy, } from "./privacy_engine.mjs";
7
+ export const SANITIZATION_ACTIONS = Object.freeze({
8
+ KEEP: "KEEP",
9
+ PSEUDONYMIZE: "PSEUDONYMIZE",
10
+ REPLACE: "REPLACE",
11
+ EXCLUDE: "EXCLUDE",
12
+ DERIVE: "DERIVE",
13
+ });
14
+ export const EXCLUDED_VALUE = Symbol("REHEARSAL_EXCLUDED_VALUE");
15
+ const isPolicyObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
16
+ const assertPolicyObject = (value, label) => {
17
+ if (!isPolicyObject(value))
18
+ throw new Error(`${label} must be an object.`);
19
+ return value;
20
+ };
21
+ const actionAliases = new Map([
22
+ ["KEEP", SANITIZATION_ACTIONS.KEEP],
23
+ ["KEEP EXACTLY", SANITIZATION_ACTIONS.KEEP],
24
+ ["PSEUDONYMIZE", SANITIZATION_ACTIONS.PSEUDONYMIZE],
25
+ ["REPLACE", SANITIZATION_ACTIONS.REPLACE],
26
+ ["REPLACE WITH SYNTHETIC", SANITIZATION_ACTIONS.REPLACE],
27
+ ["EXCLUDE", SANITIZATION_ACTIONS.EXCLUDE],
28
+ ["DERIVE", SANITIZATION_ACTIONS.DERIVE],
29
+ ]);
30
+ const identifierPattern = /^[a-z][a-z0-9_]{0,62}$/u;
31
+ const assertIdentifier = (value, label) => {
32
+ if (typeof value !== "string" || !identifierPattern.test(value)) {
33
+ throw new Error(`${label} must be a lowercase PostgreSQL identifier.`);
34
+ }
35
+ return value;
36
+ };
37
+ const tableSchema = (table) => assertIdentifier(table.schema ?? "public", "Sanitization policy table schema");
38
+ export const normalizeSanitizationAction = (action) => {
39
+ const normalized = actionAliases.get(String(action ?? "")
40
+ .trim()
41
+ .toUpperCase());
42
+ if (!normalized) {
43
+ throw new Error(`Unknown Rehearsal sanitization action: ${action}.`);
44
+ }
45
+ return normalized;
46
+ };
47
+ export const validateRuntimeSanitizationPolicy = (policy) => {
48
+ if (!isPolicyObject(policy)) {
49
+ throw new Error("A Rehearsal sanitization policy object is required.");
50
+ }
51
+ if (policy.draft === true) {
52
+ throw new Error("The sanitization policy is still a REVIEW REQUIRED draft.");
53
+ }
54
+ if (policy.policyVersion === 2) {
55
+ return validateExecutablePrivacyPolicy(policy);
56
+ }
57
+ if (policy.policyVersion !== 1) {
58
+ throw new Error("The sanitization policy must use policyVersion 1 or 2.");
59
+ }
60
+ if (typeof policy.migrationCutoff !== "string" ||
61
+ !/^\d{14}$/u.test(policy.migrationCutoff)) {
62
+ throw new Error("The sanitization policy must declare a timestamp migrationCutoff.");
63
+ }
64
+ if (!Array.isArray(policy.tables) || policy.tables.length === 0) {
65
+ throw new Error("The sanitization policy does not list tables.");
66
+ }
67
+ const tables = new Set();
68
+ for (const tableValue of policy.tables) {
69
+ const table = assertPolicyObject(tableValue, "Sanitization policy table");
70
+ const schemaName = assertIdentifier(tableSchema(table), "Sanitization policy table schema");
71
+ const tableName = assertIdentifier(table?.name, "Sanitization policy table name");
72
+ const key = schemaName === "public" ? tableName : `${schemaName}.${tableName}`;
73
+ if (tables.has(key)) {
74
+ throw new Error(`Sanitization policy duplicates table ${key}.`);
75
+ }
76
+ tables.add(key);
77
+ if (typeof table.sourceRows !== "string" ||
78
+ !["STREAM AND SANITIZE", "EXCLUDE"].includes(table.sourceRows)) {
79
+ throw new Error(`Sanitization policy table ${tableName} has an invalid sourceRows decision.`);
80
+ }
81
+ if (!Array.isArray(table.columns) || table.columns.length === 0) {
82
+ throw new Error(`Sanitization policy table ${tableName} must list its columns.`);
83
+ }
84
+ const columns = new Set();
85
+ for (const columnValue of table.columns) {
86
+ const column = assertPolicyObject(columnValue, `Sanitization policy column in ${tableName}`);
87
+ const columnName = assertIdentifier(column?.name, `Sanitization policy column in ${tableName}`);
88
+ if (columns.has(columnName)) {
89
+ throw new Error(`Sanitization policy duplicates column ${tableName}.${columnName}.`);
90
+ }
91
+ columns.add(columnName);
92
+ normalizeSanitizationAction(column.action);
93
+ if (typeof column.generated !== "string" ||
94
+ !["ALWAYS", "NEVER"].includes(column.generated)) {
95
+ throw new Error(`Sanitization policy column ${tableName}.${columnName} must classify generated as ALWAYS or NEVER.`);
96
+ }
97
+ if (typeof column.identity !== "string" ||
98
+ !["YES", "NO"].includes(column.identity)) {
99
+ throw new Error(`Sanitization policy column ${tableName}.${columnName} must classify identity as YES or NO.`);
100
+ }
101
+ if (column.foreignKey !== null) {
102
+ if (!column.foreignKey ||
103
+ typeof column.foreignKey !== "object" ||
104
+ Array.isArray(column.foreignKey)) {
105
+ throw new Error(`Sanitization policy column ${tableName}.${columnName} has an invalid foreignKey decision.`);
106
+ }
107
+ const foreignKey = column.foreignKey;
108
+ assertIdentifier(foreignKey.schema, `Foreign-key schema for ${tableName}.${columnName}`);
109
+ assertIdentifier(foreignKey.table, `Foreign-key table for ${tableName}.${columnName}`);
110
+ assertIdentifier(foreignKey.column, `Foreign-key column for ${tableName}.${columnName}`);
111
+ }
112
+ }
113
+ }
114
+ return policy;
115
+ };
116
+ export const readBoundRuntimeSanitizationPolicy = ({ bytes, expectedSha256, }) => {
117
+ const source = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes);
118
+ const policy = validateRuntimeSanitizationPolicy(JSON.parse(source.toString("utf8")));
119
+ const actualSha256 = createHash("sha256").update(source).digest("hex");
120
+ if (actualSha256 !== expectedSha256) {
121
+ throw new Error("The active sanitization policy does not match the reviewed baseline policy checksum.");
122
+ }
123
+ return policy;
124
+ };
125
+ const indexTables = (tables, label) => {
126
+ if (!Array.isArray(tables))
127
+ throw new Error(`${label} must be an array.`);
128
+ const index = new Map();
129
+ for (const tableValue of tables) {
130
+ const table = assertPolicyObject(tableValue, `${label} table`);
131
+ const schema = assertIdentifier(tableSchema(table), `${label} table schema`);
132
+ const name = assertIdentifier(table?.name, `${label} table name`);
133
+ const key = schema === "public" ? name : `${schema}.${name}`;
134
+ if (index.has(key))
135
+ throw new Error(`${label} duplicates table ${key}.`);
136
+ if (!Array.isArray(table.columns)) {
137
+ throw new Error(`${label} table ${name} must list its columns.`);
138
+ }
139
+ const columns = new Map();
140
+ for (const column of table.columns) {
141
+ const columnName = assertIdentifier(typeof column === "string"
142
+ ? column
143
+ : isPolicyObject(column)
144
+ ? column.name
145
+ : undefined, `${label} column name`);
146
+ if (columns.has(columnName)) {
147
+ throw new Error(`${label} duplicates column ${name}.${columnName}.`);
148
+ }
149
+ columns.set(columnName, column);
150
+ }
151
+ index.set(key, { table: { ...table, schema }, columns });
152
+ }
153
+ return index;
154
+ };
155
+ export const validateSanitizationCoverage = ({ policy, schemaTables, }) => {
156
+ if (!isPolicyObject(policy)) {
157
+ throw new Error("A Rehearsal sanitization policy object is required.");
158
+ }
159
+ const schema = indexTables(schemaTables, "Schema inventory");
160
+ const classified = indexTables(policy.tables, "Sanitization policy");
161
+ const normalizedTables = [];
162
+ for (const [tableName, schemaEntry] of schema) {
163
+ const policyEntry = classified.get(tableName);
164
+ if (!policyEntry) {
165
+ throw new Error(`Sanitization policy does not classify table ${tableName}.`);
166
+ }
167
+ const normalizedColumns = [];
168
+ for (const columnName of schemaEntry.columns.keys()) {
169
+ const column = policyEntry.columns.get(columnName);
170
+ if (!column) {
171
+ throw new Error(`Sanitization policy does not classify column ${tableName}.${columnName}.`);
172
+ }
173
+ const policyColumn = assertPolicyObject(column, `Sanitization policy column ${tableName}.${columnName}`);
174
+ normalizedColumns.push({
175
+ ...policyColumn,
176
+ name: columnName,
177
+ action: normalizeSanitizationAction(policyColumn.action),
178
+ });
179
+ }
180
+ for (const columnName of policyEntry.columns.keys()) {
181
+ if (!schemaEntry.columns.has(columnName)) {
182
+ throw new Error(`Sanitization policy references unknown column ${tableName}.${columnName}.`);
183
+ }
184
+ }
185
+ normalizedTables.push({
186
+ ...policyEntry.table,
187
+ schema: tableSchema(policyEntry.table),
188
+ columns: normalizedColumns,
189
+ });
190
+ }
191
+ for (const tableName of classified.keys()) {
192
+ if (!schema.has(tableName)) {
193
+ throw new Error(`Sanitization policy references unknown table ${tableName}.`);
194
+ }
195
+ }
196
+ return Object.freeze({
197
+ policyVersion: policy.policyVersion,
198
+ tableCount: normalizedTables.length,
199
+ columnCount: normalizedTables.reduce((total, table) => total + table.columns.length, 0),
200
+ tables: Object.freeze(normalizedTables),
201
+ });
202
+ };
203
+ export const applySanitizationAction = ({ action, value, replace, pseudonymize, derive, context = {}, }) => {
204
+ switch (normalizeSanitizationAction(action)) {
205
+ case SANITIZATION_ACTIONS.KEEP:
206
+ return value;
207
+ case SANITIZATION_ACTIONS.EXCLUDE:
208
+ return EXCLUDED_VALUE;
209
+ case SANITIZATION_ACTIONS.REPLACE:
210
+ if (typeof replace !== "function") {
211
+ throw new Error("REPLACE requires a project-owned replacement function.");
212
+ }
213
+ return replace(value, context);
214
+ case SANITIZATION_ACTIONS.PSEUDONYMIZE:
215
+ if (typeof pseudonymize !== "function") {
216
+ throw new Error("PSEUDONYMIZE requires a project-owned pseudonymization function.");
217
+ }
218
+ return pseudonymize(value, context);
219
+ case SANITIZATION_ACTIONS.DERIVE:
220
+ if (typeof derive !== "function") {
221
+ throw new Error("DERIVE requires a project-owned derivation function.");
222
+ }
223
+ return derive(value, context);
224
+ default:
225
+ throw new Error("Unreachable Rehearsal sanitization action.");
226
+ }
227
+ };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Purpose: Capture and compare bounded, data-free PostgreSQL public-schema
3
+ * snapshots for a local Rehearsal runtime. Do not run directly.
4
+ */
5
+ export declare const assertProductionSchemaDump: (schemaSql: string) => string;
6
+ export declare const compareProductionSchemaDumps: ({ expected, actual, }: {
7
+ expected: string;
8
+ actual: string;
9
+ }) => string;
10
+ export declare const captureSupabasePublicSchema: ({ repositoryRoot, artifactRoot, arguments_, }: {
11
+ repositoryRoot: string;
12
+ artifactRoot: string;
13
+ arguments_: readonly string[];
14
+ }) => Promise<string>;
15
+ export declare const captureLocalPublicSchema: ({ repositoryRoot, artifactRoot, workdir, }: {
16
+ repositoryRoot: string;
17
+ artifactRoot: string;
18
+ workdir: string;
19
+ }) => Promise<string>;