@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,147 @@
1
+ /** Typed, normalized contract shared by privacy validation and execution stages. */
2
+ export type PrivacyAction = "KEEP" | "PSEUDONYMIZE" | "REPLACE" | "EXCLUDE" | "DERIVE";
3
+ export type PrivacyObject = Record<string, unknown>;
4
+ export interface PseudonymRecipe {
5
+ format: "uuid" | "email" | "text" | "integer" | "hex" | "gtin" | "url";
6
+ namespace: string;
7
+ maxLength?: number;
8
+ length?: number;
9
+ allowedLengths?: readonly number[];
10
+ origin?: string;
11
+ }
12
+ export interface ConstantRecipe {
13
+ kind: "constant";
14
+ value: unknown;
15
+ }
16
+ export interface DateShiftRecipe {
17
+ kind: "date-shift";
18
+ days: number;
19
+ representation: "iso-string" | "epoch-milliseconds";
20
+ group?: string;
21
+ }
22
+ export interface EnumRecipe {
23
+ kind: "enum";
24
+ allowNull: boolean;
25
+ values: readonly (string | number | boolean)[];
26
+ }
27
+ export interface ValidatedStringRecipe {
28
+ kind: "validated-string";
29
+ format: "portable-code";
30
+ maximumBytes: number;
31
+ allowNull: boolean;
32
+ }
33
+ export interface DigestRecipe {
34
+ kind: "digest";
35
+ format: "hex";
36
+ length: number;
37
+ namespace: string;
38
+ inputs: readonly string[];
39
+ }
40
+ export interface BindingSubstituteRecipe {
41
+ kind: "binding-substitute";
42
+ binding: string;
43
+ format: "uuid";
44
+ namespace: string;
45
+ maximumBytes: number;
46
+ }
47
+ export interface PathMapRecipe {
48
+ kind: "path-map";
49
+ mapping: string;
50
+ }
51
+ export interface ApprovedOwnerRecipe {
52
+ kind: "approved-owner";
53
+ approved: NormalizedDeclaration;
54
+ otherwise: NormalizedDeclaration;
55
+ }
56
+ export interface JsonArrayRecipe {
57
+ kind: "json-array";
58
+ allowNull: boolean;
59
+ maximumBytes: number;
60
+ maximumDepth: number;
61
+ maximumItems: number;
62
+ items: NormalizedDeclaration;
63
+ }
64
+ export interface JsonObjectRecipe {
65
+ kind: "json-object";
66
+ allowNull: boolean;
67
+ maximumBytes: number;
68
+ maximumDepth: number;
69
+ fields: Readonly<Record<string, NormalizedDeclaration>>;
70
+ }
71
+ export interface JsonUnionRecipe {
72
+ kind: "json-union";
73
+ allowNull: boolean;
74
+ maximumBytes: number;
75
+ maximumDepth: number;
76
+ maximumItems: number;
77
+ variants: Readonly<Record<string, NormalizedDeclaration>>;
78
+ }
79
+ export interface JsonDictionaryRecipe {
80
+ kind: "json-dictionary";
81
+ allowNull: boolean;
82
+ maximumBytes: number;
83
+ maximumDepth: number;
84
+ maximumItems: number;
85
+ keys: Readonly<Record<string, unknown>>;
86
+ values: NormalizedDeclaration;
87
+ }
88
+ export type DerivationRecipe = DateShiftRecipe | EnumRecipe | ValidatedStringRecipe | DigestRecipe | BindingSubstituteRecipe | PathMapRecipe | ApprovedOwnerRecipe | JsonArrayRecipe | JsonObjectRecipe | JsonUnionRecipe | JsonDictionaryRecipe;
89
+ export type NormalizedRecipe = PseudonymRecipe | ConstantRecipe | DerivationRecipe | null;
90
+ export interface NormalizedDeclaration {
91
+ action: PrivacyAction;
92
+ recipe: NormalizedRecipe;
93
+ required?: boolean;
94
+ }
95
+ export interface PrivacyBinding {
96
+ environmentVariable: string;
97
+ approvedValueSha256: string;
98
+ }
99
+ export interface PrivacyPathMapping extends PseudonymRecipe {
100
+ binding: string;
101
+ format: "uuid" | "text";
102
+ maximumBytes: number;
103
+ maximumSegments: number;
104
+ }
105
+ export interface PrivacyForeignKey {
106
+ readonly schema: string;
107
+ readonly table: string;
108
+ readonly column: string;
109
+ }
110
+ export interface PrivacyColumn extends NormalizedDeclaration {
111
+ name: string;
112
+ generated: "ALWAYS" | "NEVER";
113
+ identity: "YES" | "NO";
114
+ foreignKey: PrivacyForeignKey | null;
115
+ }
116
+ export interface PrivacyTable {
117
+ schema: string;
118
+ name: string;
119
+ sourceRows: "STREAM AND SANITIZE" | "EXCLUDE";
120
+ ownerBinding: Readonly<{
121
+ binding: string;
122
+ columns: readonly string[];
123
+ match: "any";
124
+ nullBehavior?: "otherwise";
125
+ }> | null;
126
+ columns: readonly PrivacyColumn[];
127
+ }
128
+ export interface ExecutablePrivacyPolicy {
129
+ readonly policyVersion: 2;
130
+ readonly migrationCutoff: string;
131
+ readonly bindings: Readonly<Record<string, PrivacyBinding>>;
132
+ readonly pathMappings: Readonly<Record<string, PrivacyPathMapping>>;
133
+ readonly tables: readonly PrivacyTable[];
134
+ }
135
+ export interface PrivacySourceRecord {
136
+ schema?: string;
137
+ table: string;
138
+ row: Record<string, unknown>;
139
+ }
140
+ export interface PrivacyEngine {
141
+ readonly keyFingerprint: string;
142
+ remapPath(input: {
143
+ mapping: string;
144
+ value: string;
145
+ }): string;
146
+ sanitize(record: unknown): PrivacySourceRecord | null;
147
+ }
@@ -0,0 +1,2 @@
1
+ /** Typed, normalized contract shared by privacy validation and execution stages. */
2
+ export {};
@@ -0,0 +1,7 @@
1
+ /** Validate cross-rule bindings, dependencies, and shared deterministic groups. */
2
+ import type { PrivacyBinding, PrivacyPathMapping, PrivacyTable } from "./privacy_contract.mjs";
3
+ export declare const validatePrivacyDependencyGraph: ({ tables, bindings, pathMappings, }: {
4
+ tables: readonly PrivacyTable[];
5
+ bindings: Readonly<Record<string, PrivacyBinding>>;
6
+ pathMappings: Readonly<Record<string, PrivacyPathMapping>>;
7
+ }) => void;
@@ -0,0 +1,110 @@
1
+ /** Validate cross-rule bindings, dependencies, and shared deterministic groups. */
2
+ import { structuredJsonDeclarations } from "./privacy_structured_json.mjs";
3
+ const usesApprovedOwner = (declaration) => (declaration.recipe !== null &&
4
+ "kind" in declaration.recipe &&
5
+ declaration.recipe.kind === "approved-owner") ||
6
+ structuredJsonDeclarations(declaration.recipe).some(usesApprovedOwner);
7
+ const visitRecipes = (declaration, visit) => {
8
+ visit(declaration.recipe);
9
+ if (declaration.recipe !== null &&
10
+ "kind" in declaration.recipe &&
11
+ declaration.recipe.kind === "approved-owner") {
12
+ visitRecipes(declaration.recipe.approved, visit);
13
+ visitRecipes(declaration.recipe.otherwise, visit);
14
+ }
15
+ else {
16
+ structuredJsonDeclarations(declaration.recipe).forEach((nested) => visitRecipes(nested, visit));
17
+ }
18
+ };
19
+ const digestInputs = (declaration) => {
20
+ if (declaration.recipe !== null &&
21
+ "kind" in declaration.recipe &&
22
+ declaration.recipe.kind === "digest") {
23
+ return [...declaration.recipe.inputs];
24
+ }
25
+ if (declaration.recipe !== null &&
26
+ "kind" in declaration.recipe &&
27
+ declaration.recipe.kind === "approved-owner") {
28
+ return [
29
+ ...digestInputs(declaration.recipe.approved),
30
+ ...digestInputs(declaration.recipe.otherwise),
31
+ ];
32
+ }
33
+ return structuredJsonDeclarations(declaration.recipe).flatMap(digestInputs);
34
+ };
35
+ const validateTableDependencies = ({ table, index, bindings, pathMappings, }) => {
36
+ const label = `policy.tables[${index}]`;
37
+ if (table.columns.some(usesApprovedOwner) && !table.ownerBinding) {
38
+ throw new Error(`${label} uses approved-owner without ownerBinding.`);
39
+ }
40
+ for (const column of table.columns) {
41
+ visitRecipes(column, (recipe) => {
42
+ if (recipe &&
43
+ "kind" in recipe &&
44
+ recipe.kind === "path-map" &&
45
+ !Object.hasOwn(pathMappings, recipe.mapping)) {
46
+ throw new Error(`${label} references unknown path mapping ${recipe.mapping}.`);
47
+ }
48
+ if (recipe !== null &&
49
+ "kind" in recipe &&
50
+ recipe.kind === "binding-substitute" &&
51
+ !Object.hasOwn(bindings, recipe.binding)) {
52
+ throw new Error(`${label} references unknown privacy binding ${recipe.binding}.`);
53
+ }
54
+ });
55
+ }
56
+ const dependencies = new Map(table.columns.map((column) => [column.name, []]));
57
+ for (const column of table.columns) {
58
+ const inputs = [...new Set(digestInputs(column))];
59
+ for (const input of inputs) {
60
+ const inputColumn = table.columns.find((entry) => entry.name === input);
61
+ if (!inputColumn) {
62
+ throw new Error(`${label}.${column.name} digest references unknown column ${input}.`);
63
+ }
64
+ if (inputColumn.action === "EXCLUDE") {
65
+ throw new Error(`${label}.${column.name} digest input ${input} is excluded instead of sanitized.`);
66
+ }
67
+ }
68
+ dependencies.set(column.name, inputs);
69
+ }
70
+ const resolved = new Set();
71
+ const resolving = new Set();
72
+ const visitDependency = (columnName) => {
73
+ if (resolved.has(columnName))
74
+ return;
75
+ if (resolving.has(columnName)) {
76
+ throw new Error(`${label} contains a cyclic digest dependency.`);
77
+ }
78
+ resolving.add(columnName);
79
+ for (const input of dependencies.get(columnName) ?? []) {
80
+ visitDependency(input);
81
+ }
82
+ resolving.delete(columnName);
83
+ resolved.add(columnName);
84
+ };
85
+ table.columns.forEach((column) => visitDependency(column.name));
86
+ };
87
+ export const validatePrivacyDependencyGraph = ({ tables, bindings, pathMappings, }) => {
88
+ tables.forEach((table, index) => validateTableDependencies({ table, index, bindings, pathMappings }));
89
+ const dateShiftGroups = new Map();
90
+ const validateDateShiftGroup = (declaration) => {
91
+ const recipe = declaration.recipe;
92
+ if (!recipe || !("kind" in recipe))
93
+ return;
94
+ if (recipe.kind === "date-shift" && recipe.group) {
95
+ const existing = dateShiftGroups.get(recipe.group);
96
+ if (existing !== undefined && existing !== recipe.days) {
97
+ throw new Error(`Privacy date-shift group ${recipe.group} must use one days value.`);
98
+ }
99
+ dateShiftGroups.set(recipe.group, recipe.days);
100
+ }
101
+ if (recipe.kind === "approved-owner") {
102
+ validateDateShiftGroup(recipe.approved);
103
+ validateDateShiftGroup(recipe.otherwise);
104
+ }
105
+ else {
106
+ structuredJsonDeclarations(recipe).forEach(validateDateShiftGroup);
107
+ }
108
+ };
109
+ tables.forEach((table) => table.columns.forEach(validateDateShiftGroup));
110
+ };
@@ -0,0 +1,5 @@
1
+ /** Stable public facade for the versioned, fail-closed privacy pipeline. */
2
+ export { validateExecutablePrivacyPolicy } from "./privacy_policy.mjs";
3
+ export { createPrivacyEngine } from "./privacy_pipeline.mjs";
4
+ export { assertPrivacyKeyFingerprint, createPrivacyKey, readPrivacyKey, } from "./privacy_key.mjs";
5
+ export type { ExecutablePrivacyPolicy, PrivacyColumn, PrivacyEngine, PrivacyForeignKey, PrivacySourceRecord, PrivacyTable, } from "./privacy_policy.mjs";
@@ -0,0 +1,4 @@
1
+ /** Stable public facade for the versioned, fail-closed privacy pipeline. */
2
+ export { validateExecutablePrivacyPolicy } from "./privacy_policy.mjs";
3
+ export { createPrivacyEngine } from "./privacy_pipeline.mjs";
4
+ export { assertPrivacyKeyFingerprint, createPrivacyKey, readPrivacyKey, } from "./privacy_key.mjs";
@@ -0,0 +1,7 @@
1
+ /** Create and verify the owner-only secret used by deterministic privacy rules. */
2
+ export declare const PRIVACY_KEY_BYTES = 32;
3
+ export declare const createPrivacyKey: (path: string) => Promise<{
4
+ fingerprint: string;
5
+ }>;
6
+ export declare const readPrivacyKey: (path: string) => Promise<Buffer>;
7
+ export declare const assertPrivacyKeyFingerprint: (key: Uint8Array, expected: unknown) => void;
@@ -0,0 +1,42 @@
1
+ /** Create and verify the owner-only secret used by deterministic privacy rules. */
2
+ import { createHash, randomBytes } from "node:crypto";
3
+ import { chmod, mkdir, open, readFile, stat } from "node:fs/promises";
4
+ import { dirname } from "node:path";
5
+ export const PRIVACY_KEY_BYTES = 32;
6
+ const HEX_64 = /^[a-f0-9]{64}$/u;
7
+ export const createPrivacyKey = async (path) => {
8
+ await mkdir(dirname(path), { recursive: true, mode: 0o700 });
9
+ const key = randomBytes(PRIVACY_KEY_BYTES);
10
+ const handle = await open(path, "wx", 0o600);
11
+ try {
12
+ await handle.writeFile(`${key.toString("base64")}\n`);
13
+ await handle.sync();
14
+ }
15
+ finally {
16
+ await handle.close();
17
+ }
18
+ await chmod(path, 0o600);
19
+ return { fingerprint: createHash("sha256").update(key).digest("hex") };
20
+ };
21
+ export const readPrivacyKey = async (path) => {
22
+ const details = await stat(path);
23
+ if (!details.isFile() || (details.mode & 0o077) !== 0) {
24
+ throw new Error("Privacy key must be an owner-only regular file.");
25
+ }
26
+ const encoded = (await readFile(path, "utf8")).trim();
27
+ const key = Buffer.from(encoded, "base64");
28
+ if (key.length !== PRIVACY_KEY_BYTES ||
29
+ !/^[A-Za-z0-9+/]+={0,2}$/u.test(encoded)) {
30
+ throw new Error("Privacy key file is invalid.");
31
+ }
32
+ return key;
33
+ };
34
+ export const assertPrivacyKeyFingerprint = (key, expected) => {
35
+ if (typeof expected !== "string" || !HEX_64.test(expected)) {
36
+ throw new Error("Privacy key fingerprint is invalid.");
37
+ }
38
+ const actual = createHash("sha256").update(key).digest("hex");
39
+ if (actual !== expected) {
40
+ throw new Error("Privacy key does not match the reviewed baseline receipt.");
41
+ }
42
+ };
@@ -0,0 +1,7 @@
1
+ /** Resolve reviewed bindings and execute the dependency-aware row privacy pipeline. */
2
+ import type { PrivacyEngine } from "./privacy_contract.mjs";
3
+ export declare const createPrivacyEngine: ({ policy, key, environment, }: {
4
+ policy: unknown;
5
+ key: Uint8Array | string;
6
+ environment?: NodeJS.ProcessEnv;
7
+ }) => Readonly<PrivacyEngine>;
@@ -0,0 +1,127 @@
1
+ /** Resolve reviewed bindings and execute the dependency-aware row privacy pipeline. */
2
+ import { createHash } from "node:crypto";
3
+ import { PRIVACY_KEY_BYTES } from "./privacy_key.mjs";
4
+ import { validateExecutablePrivacyPolicy } from "./privacy_policy.mjs";
5
+ import { executePrivacyDeclaration } from "./privacy_rule_registry.mjs";
6
+ import { remapPrivacyPath } from "./privacy_transforms.mjs";
7
+ import { IDENTIFIER, isPrivacyObject } from "./privacy_validation_shared.mjs";
8
+ export const createPrivacyEngine = ({ policy, key, environment = process.env, }) => {
9
+ const normalized = validateExecutablePrivacyPolicy(policy);
10
+ const secret = Buffer.isBuffer(key) ? key : Buffer.from(key ?? "");
11
+ if (secret.length < PRIVACY_KEY_BYTES) {
12
+ throw new Error(`Privacy key must contain at least ${PRIVACY_KEY_BYTES} bytes.`);
13
+ }
14
+ const bindingValues = new Map(Object.entries(normalized.bindings).map(([name, binding]) => {
15
+ const value = environment[binding.environmentVariable];
16
+ if (typeof value !== "string" || !value) {
17
+ throw new Error(`Privacy binding ${name} requires ${binding.environmentVariable}.`);
18
+ }
19
+ const receipt = createHash("sha256").update(value).digest("hex");
20
+ if (receipt !== binding.approvedValueSha256) {
21
+ throw new Error(`Privacy binding ${name} does not match its review receipt.`);
22
+ }
23
+ return [name, value];
24
+ }));
25
+ const tables = new Map(normalized.tables.map((table) => [
26
+ table.schema === "public" ? table.name : `${table.schema}.${table.name}`,
27
+ table,
28
+ ]));
29
+ return Object.freeze({
30
+ keyFingerprint: createHash("sha256").update(secret).digest("hex"),
31
+ remapPath({ mapping: mappingName, value, }) {
32
+ const mapping = normalized.pathMappings[mappingName];
33
+ if (!mapping) {
34
+ throw new Error(`Privacy path mapping ${mappingName} is not declared.`);
35
+ }
36
+ return remapPrivacyPath({
37
+ key: secret,
38
+ mapping,
39
+ bindingValue: bindingValues.get(mapping.binding),
40
+ value,
41
+ label: `Privacy path mapping ${mappingName}`,
42
+ });
43
+ },
44
+ sanitize(record) {
45
+ if (!isPrivacyObject(record) ||
46
+ typeof (record.schema ?? "public") !== "string" ||
47
+ !IDENTIFIER.test(String(record.schema ?? "public")) ||
48
+ typeof record.table !== "string" ||
49
+ !IDENTIFIER.test(record.table) ||
50
+ !isPrivacyObject(record.row)) {
51
+ throw new Error("Source record has an invalid privacy-engine shape.");
52
+ }
53
+ const schema = String(record.schema ?? "public");
54
+ const rowSource = record.row;
55
+ const relation = schema === "public" ? record.table : `${schema}.${record.table}`;
56
+ const table = tables.get(relation);
57
+ if (!table) {
58
+ throw new Error(`Privacy policy does not classify table ${relation}.`);
59
+ }
60
+ if (table.sourceRows === "EXCLUDE")
61
+ return null;
62
+ const actual = Object.keys(rowSource).sort();
63
+ const expected = table.columns.map((column) => column.name).sort();
64
+ if (actual.join("\0") !== expected.join("\0")) {
65
+ throw new Error(`Source table ${relation} contains an unclassified or missing column.`);
66
+ }
67
+ let ownerApproved;
68
+ if (table.ownerBinding) {
69
+ const approvedValue = bindingValues.get(table.ownerBinding.binding);
70
+ const owners = table.ownerBinding.columns.map((column) => rowSource[column]);
71
+ for (const owner of owners) {
72
+ if (owner === null) {
73
+ if (table.ownerBinding.nullBehavior !== "otherwise") {
74
+ throw new Error(`${relation} has an invalid owner binding value.`);
75
+ }
76
+ continue;
77
+ }
78
+ if (!["string", "number"].includes(typeof owner) ||
79
+ owner === "" ||
80
+ (typeof owner === "number" && !Number.isFinite(owner))) {
81
+ throw new Error(`${relation} has an invalid owner binding value.`);
82
+ }
83
+ }
84
+ ownerApproved = owners.some((owner) => owner !== null && String(owner) === approvedValue);
85
+ }
86
+ const sanitizedColumns = new Map();
87
+ const resolvingColumns = new Set();
88
+ const sanitizeColumn = (columnName) => {
89
+ if (sanitizedColumns.has(columnName)) {
90
+ return sanitizedColumns.get(columnName);
91
+ }
92
+ if (resolvingColumns.has(columnName)) {
93
+ throw new Error(`${relation} contains a cyclic digest dependency.`);
94
+ }
95
+ const column = table.columns.find((entry) => entry.name === columnName);
96
+ if (!column) {
97
+ throw new Error(`${relation} digest references unknown column ${columnName}.`);
98
+ }
99
+ resolvingColumns.add(columnName);
100
+ try {
101
+ const transformed = executePrivacyDeclaration({
102
+ declaration: column,
103
+ value: rowSource[column.name],
104
+ key: secret,
105
+ path: `${relation}.${column.name}`,
106
+ ownerApproved,
107
+ pathMappings: normalized.pathMappings,
108
+ bindingValues,
109
+ resolveInput: sanitizeColumn,
110
+ });
111
+ sanitizedColumns.set(columnName, transformed);
112
+ return transformed;
113
+ }
114
+ finally {
115
+ resolvingColumns.delete(columnName);
116
+ }
117
+ };
118
+ const row = Object.fromEntries(table.columns.flatMap((column) => {
119
+ const transformed = sanitizeColumn(column.name);
120
+ return transformed === undefined ? [] : [[column.name, transformed]];
121
+ }));
122
+ return schema === "public"
123
+ ? { table: record.table, row }
124
+ : { schema, table: record.table, row };
125
+ },
126
+ });
127
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Validate and normalize the complete reviewed privacy policy version 2.
3
+ */
4
+ import type { ExecutablePrivacyPolicy } from "./privacy_contract.mjs";
5
+ export type { ExecutablePrivacyPolicy, PrivacyColumn, PrivacyEngine, PrivacyForeignKey, PrivacySourceRecord, PrivacyTable, } from "./privacy_contract.mjs";
6
+ export declare const validateExecutablePrivacyPolicy: (policy: unknown) => ExecutablePrivacyPolicy;