@rehearsal-db/core 0.1.0-beta.15 → 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 (102) hide show
  1. package/CHANGELOG.md +34 -1
  2. package/dist/package.json +10 -9
  3. package/dist/src/baseline/privacy_contract.d.mts +147 -0
  4. package/dist/src/baseline/privacy_contract.mjs +2 -0
  5. package/dist/src/baseline/privacy_dependency_graph.d.mts +7 -0
  6. package/dist/src/baseline/privacy_dependency_graph.mjs +110 -0
  7. package/dist/src/baseline/privacy_engine.d.mts +5 -162
  8. package/dist/src/baseline/privacy_engine.mjs +4 -1162
  9. package/dist/src/baseline/privacy_key.d.mts +7 -0
  10. package/dist/src/baseline/privacy_key.mjs +42 -0
  11. package/dist/src/baseline/privacy_pipeline.d.mts +7 -0
  12. package/dist/src/baseline/privacy_pipeline.mjs +127 -0
  13. package/dist/src/baseline/privacy_policy.d.mts +6 -0
  14. package/dist/src/baseline/privacy_policy.mjs +237 -0
  15. package/dist/src/baseline/privacy_recipe_validation.d.mts +5 -0
  16. package/dist/src/baseline/privacy_recipe_validation.mjs +289 -0
  17. package/dist/src/baseline/privacy_rule_registry.d.mts +18 -0
  18. package/dist/src/baseline/privacy_rule_registry.mjs +143 -0
  19. package/dist/src/baseline/privacy_transforms.d.mts +18 -0
  20. package/dist/src/baseline/privacy_transforms.mjs +142 -0
  21. package/dist/src/baseline/privacy_validation_shared.d.mts +31 -0
  22. package/dist/src/baseline/privacy_validation_shared.mjs +127 -0
  23. package/dist/src/cli/application_command_handlers.d.mts +8 -0
  24. package/dist/src/cli/application_command_handlers.mjs +52 -0
  25. package/dist/src/cli/arguments.d.mts +0 -5
  26. package/dist/src/cli/arguments.mjs +0 -7
  27. package/dist/src/cli/basic_command_handlers.d.mts +4 -0
  28. package/dist/src/cli/basic_command_handlers.mjs +56 -0
  29. package/dist/src/cli/cli_commands.d.mts +14 -0
  30. package/dist/src/cli/cli_commands.mjs +32 -0
  31. package/dist/src/cli/command_contract.d.mts +57 -0
  32. package/dist/src/cli/command_contract.mjs +39 -0
  33. package/dist/src/cli/command_output.d.mts +12 -0
  34. package/dist/src/cli/command_output.mjs +20 -0
  35. package/dist/src/cli/command_registry.d.mts +7 -0
  36. package/dist/src/cli/command_registry.mjs +14 -0
  37. package/dist/src/cli/guided.d.mts +3 -1
  38. package/dist/src/cli/guided.mjs +13 -7
  39. package/dist/src/cli/identity_command_handlers.d.mts +8 -0
  40. package/dist/src/cli/identity_command_handlers.mjs +43 -0
  41. package/dist/src/cli/init_command.d.mts +1 -1
  42. package/dist/src/cli/inspection_command_handlers.d.mts +3 -0
  43. package/dist/src/cli/inspection_command_handlers.mjs +79 -0
  44. package/dist/src/cli/project_command_handlers.d.mts +11 -0
  45. package/dist/src/cli/project_command_handlers.mjs +92 -0
  46. package/dist/src/cli/project_commands.d.mts +6 -4
  47. package/dist/src/cli/project_commands.mjs +6 -6
  48. package/dist/src/cli/rehearsal.d.mts +2 -2
  49. package/dist/src/cli/rehearsal.mjs +51 -567
  50. package/dist/src/cli/runtime_action_handlers.d.mts +12 -0
  51. package/dist/src/cli/runtime_action_handlers.mjs +201 -0
  52. package/dist/src/cli/runtime_commands.d.mts +7 -3
  53. package/dist/src/cli/runtime_commands.mjs +10 -8
  54. package/dist/src/cli/source_command_handlers.d.mts +8 -0
  55. package/dist/src/cli/source_command_handlers.mjs +62 -0
  56. package/dist/src/cli/source_commands.d.mts +5 -1
  57. package/dist/src/cli/source_commands.mjs +3 -1
  58. package/dist/src/identity/claim.d.mts +5 -144
  59. package/dist/src/identity/claim.mjs +4 -947
  60. package/dist/src/identity/claim_contract.d.mts +135 -0
  61. package/dist/src/identity/claim_contract.mjs +6 -0
  62. package/dist/src/identity/claim_executor.d.mts +10 -0
  63. package/dist/src/identity/claim_executor.mjs +358 -0
  64. package/dist/src/identity/claim_identity_lookup.d.mts +17 -0
  65. package/dist/src/identity/claim_identity_lookup.mjs +125 -0
  66. package/dist/src/identity/claim_plan.d.mts +8 -0
  67. package/dist/src/identity/claim_plan.mjs +61 -0
  68. package/dist/src/identity/claim_policy.d.mts +3 -0
  69. package/dist/src/identity/claim_policy.mjs +377 -0
  70. package/dist/src/identity/claim_sql.d.mts +10 -0
  71. package/dist/src/identity/claim_sql.mjs +46 -0
  72. package/dist/src/identity/claim_verification.d.mts +13 -0
  73. package/dist/src/identity/claim_verification.mjs +35 -0
  74. package/dist/src/index.d.mts +1 -1
  75. package/dist/src/project/configuration.d.mts +11 -179
  76. package/dist/src/project/configuration.mjs +7 -956
  77. package/dist/src/project/configuration_contract.d.mts +125 -0
  78. package/dist/src/project/configuration_contract.mjs +27 -0
  79. package/dist/src/project/configuration_discovery.d.mts +7 -0
  80. package/dist/src/project/configuration_discovery.mjs +37 -0
  81. package/dist/src/project/configuration_normalization.d.mts +107 -0
  82. package/dist/src/project/configuration_normalization.mjs +302 -0
  83. package/dist/src/project/configuration_rendering.d.mts +14 -0
  84. package/dist/src/project/configuration_rendering.mjs +224 -0
  85. package/dist/src/project/configuration_resolution.d.mts +125 -0
  86. package/dist/src/project/configuration_resolution.mjs +81 -0
  87. package/dist/src/project/configuration_schema.d.mts +22 -0
  88. package/dist/src/project/configuration_schema.mjs +160 -0
  89. package/dist/src/project/configuration_validation.d.mts +22 -0
  90. package/dist/src/project/configuration_validation.mjs +136 -0
  91. package/dist/src/project/project_detection.d.mts +14 -0
  92. package/dist/src/project/project_detection.mjs +47 -0
  93. package/dist/src/runtime/lifecycle_engine.d.mts +63 -0
  94. package/dist/src/runtime/lifecycle_engine.mjs +114 -0
  95. package/dist/src/runtime/plan.mjs +2 -1
  96. package/dist/src/targets/postgresql.mjs +142 -163
  97. package/dist/src/targets/supabase.mjs +142 -174
  98. package/docs/architecture.md +118 -3
  99. package/docs/releasing.md +8 -3
  100. package/docs/typescript.md +57 -10
  101. package/package.json +10 -9
  102. package/types/configuration.d.mts +5 -164
package/CHANGELOG.md CHANGED
@@ -5,6 +5,36 @@ a Changelog, and versions will follow Semantic Versioning after the package exis
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ### Changed
9
+
10
+ - The CLI now routes guided and scripted requests through focused typed command
11
+ handlers and one result/output contract. The executable retains only process
12
+ lifecycle, project locking, guided-loop coordination, and top-level error handling;
13
+ command-session caches no longer live in public parsed flags.
14
+ - Configuration now passes through separate discovery, raw-schema validation,
15
+ normalization, project-root path resolution, project detection, and starter-rendering
16
+ modules behind the existing import path. Public declarations come from the same
17
+ TypeScript source, including the new `NormalizedRehearsalConfig` type.
18
+ - Installed-consumer verification now shares one scenario catalog, artifact installer,
19
+ isolated workspace boundary, process boundary, and persistent application-session
20
+ helper. The complete release gate runs every scenario against one immutable tarball
21
+ and records its exact version, SHA-256, tool environment, commands, timings, and
22
+ results.
23
+ - Identity policy validation, deterministic claim planning, SQL generation, local Auth
24
+ lookup, pure verification, and transactional execution now live in separate typed
25
+ stages behind the unchanged public claim API. Generated reference SQL is independently
26
+ testable, while only the executor can mutate the local database or Storage runtime.
27
+ - PostgreSQL and Supabase now share one typed runtime lifecycle engine for command
28
+ dispatch, active-baseline loading, migration confirmation and receipts, runtime
29
+ markers, and rollback. Candidate receipts are bound to their exact database target,
30
+ while each driver retains its own ownership proof and database-specific behavior.
31
+ - Privacy policy types, recipe normalization, dependency validation, deterministic
32
+ transforms, structured JSON, rule dispatch, row execution, and key handling now live
33
+ in focused typed stages behind the unchanged public privacy import. An explicit closed
34
+ registry covers every supported action and derivation without widening policy v2.
35
+
36
+ ## [0.1.0-beta.15] - 2026-10-08
37
+
8
38
  ### Added
9
39
 
10
40
  - A strict, no-emit TypeScript gate now checks the first shared runtime utilities and
@@ -482,7 +512,10 @@ a Changelog, and versions will follow Semantic Versioning after the package exis
482
512
  publication uses short-lived trusted OIDC, and every release tag must already exist on
483
513
  protected `main`.
484
514
 
485
- [Unreleased]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-beta.12...HEAD
515
+ [Unreleased]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-beta.15...HEAD
516
+ [0.1.0-beta.15]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-beta.14...v0.1.0-beta.15
517
+ [0.1.0-beta.14]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-beta.13...v0.1.0-beta.14
518
+ [0.1.0-beta.13]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-beta.12...v0.1.0-beta.13
486
519
  [0.1.0-beta.12]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-beta.11...v0.1.0-beta.12
487
520
  [0.1.0-beta.11]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-beta.10...v0.1.0-beta.11
488
521
  [0.1.0-beta.10]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-beta.9...v0.1.0-beta.10
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rehearsal-db/core",
3
- "version": "0.1.0-beta.15",
3
+ "version": "0.1.0-beta.16",
4
4
  "private": false,
5
5
  "description": "Safely rehearse PostgreSQL and Supabase migrations against sanitized, production-shaped data.",
6
6
  "repository": {
@@ -60,16 +60,17 @@
60
60
  "test:unit": "npm run build && vitest run tests/unit",
61
61
  "test:integration": "npm run build && vitest run tests/integration tests/contracts",
62
62
  "test:docs": "npm run build && vitest run tests/contracts/documentationCommands.test.mjs",
63
- "test:fixture": "npm run build && node scripts/verification/fixtures/supabase.mjs",
64
- "test:fixture:postgresql": "npm run build && node scripts/verification/fixtures/postgresql.mjs",
65
- "test:fixture:dependent": "npm run build && node scripts/verification/fixtures/dependent.mjs",
66
- "test:fixture:onboarding": "npm run build && node scripts/verification/fixtures/onboarding.mjs",
67
- "test:fixture:typescript": "npm run build && node scripts/verification/fixtures/typescript.mjs",
68
- "test:fixture:standalone": "npm run build && node scripts/verification/fixtures/standalone.mjs",
69
- "test:fixture:privacy": "npm run build && node scripts/verification/fixtures/privacy.mjs",
63
+ "test:fixture": "npm run build && node scripts/verification/run_scenario.mjs supabase",
64
+ "test:fixture:postgresql": "npm run build && node scripts/verification/run_scenario.mjs postgresql",
65
+ "test:fixture:dependent": "npm run build && node scripts/verification/run_scenario.mjs dependent-target",
66
+ "test:fixture:onboarding": "npm run build && node scripts/verification/run_scenario.mjs onboarding",
67
+ "test:fixture:typescript": "npm run build && node scripts/verification/run_scenario.mjs typescript",
68
+ "test:fixture:standalone": "npm run build && node scripts/verification/run_scenario.mjs standalone",
69
+ "test:fixture:privacy": "npm run build && node scripts/verification/run_scenario.mjs privacy",
70
+ "release:verify": "node scripts/verification/release_gate.mjs",
70
71
  "check": "npm run check:syntax && npm run typecheck && npm run test && npm run docs:build && npm run format:check && npm run package:audit",
71
72
  "check:syntax": "find scripts tests -type f -name '*.mjs' -exec node --check {} +",
72
- "typecheck": "tsc --project tsconfig.json",
73
+ "typecheck": "npm run build && tsc --project tsconfig.json",
73
74
  "format": "prettier --write .",
74
75
  "format:check": "prettier --check .",
75
76
  "package:audit": "node scripts/verification/audit_package.mjs"
@@ -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
+ };
@@ -1,162 +1,5 @@
1
- /**
2
- * Execute reviewed privacy policy version 2 without project-owned callbacks.
3
- * The engine is intentionally small and fail-closed: unsupported shapes and
4
- * recipes must be reviewed and added here instead of evaluated as code.
5
- */
6
- type PrivacyAction = "KEEP" | "PSEUDONYMIZE" | "REPLACE" | "EXCLUDE" | "DERIVE";
7
- interface PseudonymRecipe {
8
- format: "uuid" | "email" | "text" | "integer" | "hex" | "gtin" | "url";
9
- namespace: string;
10
- maxLength?: number;
11
- length?: number;
12
- allowedLengths?: readonly number[];
13
- origin?: string;
14
- }
15
- interface ConstantRecipe {
16
- kind: "constant";
17
- value: unknown;
18
- }
19
- interface DateShiftRecipe {
20
- kind: "date-shift";
21
- days: number;
22
- representation: "iso-string" | "epoch-milliseconds";
23
- group?: string;
24
- }
25
- interface EnumRecipe {
26
- kind: "enum";
27
- allowNull: boolean;
28
- values: readonly (string | number | boolean)[];
29
- }
30
- interface ValidatedStringRecipe {
31
- kind: "validated-string";
32
- format: "portable-code";
33
- maximumBytes: number;
34
- allowNull: boolean;
35
- }
36
- interface DigestRecipe {
37
- kind: "digest";
38
- format: "hex";
39
- length: number;
40
- namespace: string;
41
- inputs: readonly string[];
42
- }
43
- interface BindingSubstituteRecipe {
44
- kind: "binding-substitute";
45
- binding: string;
46
- format: "uuid";
47
- namespace: string;
48
- maximumBytes: number;
49
- }
50
- interface PathMapRecipe {
51
- kind: "path-map";
52
- mapping: string;
53
- }
54
- interface ApprovedOwnerRecipe {
55
- kind: "approved-owner";
56
- approved: NormalizedDeclaration;
57
- otherwise: NormalizedDeclaration;
58
- }
59
- interface JsonArrayRecipe {
60
- kind: "json-array";
61
- allowNull: boolean;
62
- maximumBytes: number;
63
- maximumDepth: number;
64
- maximumItems: number;
65
- items: NormalizedDeclaration;
66
- }
67
- interface JsonObjectRecipe {
68
- kind: "json-object";
69
- allowNull: boolean;
70
- maximumBytes: number;
71
- maximumDepth: number;
72
- fields: Readonly<Record<string, NormalizedDeclaration>>;
73
- }
74
- interface JsonUnionRecipe {
75
- kind: "json-union";
76
- allowNull: boolean;
77
- maximumBytes: number;
78
- maximumDepth: number;
79
- maximumItems: number;
80
- variants: Readonly<Record<string, NormalizedDeclaration>>;
81
- }
82
- interface JsonDictionaryRecipe {
83
- kind: "json-dictionary";
84
- allowNull: boolean;
85
- maximumBytes: number;
86
- maximumDepth: number;
87
- maximumItems: number;
88
- keys: Readonly<Record<string, unknown>>;
89
- values: NormalizedDeclaration;
90
- }
91
- type DerivationRecipe = DateShiftRecipe | EnumRecipe | ValidatedStringRecipe | DigestRecipe | BindingSubstituteRecipe | PathMapRecipe | ApprovedOwnerRecipe | JsonArrayRecipe | JsonObjectRecipe | JsonUnionRecipe | JsonDictionaryRecipe;
92
- type NormalizedRecipe = PseudonymRecipe | ConstantRecipe | DerivationRecipe | null;
93
- interface NormalizedDeclaration {
94
- action: PrivacyAction;
95
- recipe: NormalizedRecipe;
96
- required?: boolean;
97
- }
98
- interface PrivacyBinding {
99
- environmentVariable: string;
100
- approvedValueSha256: string;
101
- }
102
- interface PrivacyPathMapping extends PseudonymRecipe {
103
- binding: string;
104
- format: "uuid" | "text";
105
- maximumBytes: number;
106
- maximumSegments: number;
107
- }
108
- export interface PrivacyForeignKey {
109
- readonly schema: string;
110
- readonly table: string;
111
- readonly column: string;
112
- }
113
- export interface PrivacyColumn extends NormalizedDeclaration {
114
- name: string;
115
- generated: "ALWAYS" | "NEVER";
116
- identity: "YES" | "NO";
117
- foreignKey: PrivacyForeignKey | null;
118
- }
119
- export interface PrivacyTable {
120
- schema: string;
121
- name: string;
122
- sourceRows: "STREAM AND SANITIZE" | "EXCLUDE";
123
- ownerBinding: Readonly<{
124
- binding: string;
125
- columns: readonly string[];
126
- match: "any";
127
- nullBehavior?: "otherwise";
128
- }> | null;
129
- columns: readonly PrivacyColumn[];
130
- }
131
- export interface ExecutablePrivacyPolicy {
132
- readonly policyVersion: 2;
133
- readonly migrationCutoff: string;
134
- readonly bindings: Readonly<Record<string, PrivacyBinding>>;
135
- readonly pathMappings: Readonly<Record<string, PrivacyPathMapping>>;
136
- readonly tables: readonly PrivacyTable[];
137
- }
138
- export declare const validateExecutablePrivacyPolicy: (policy: unknown) => ExecutablePrivacyPolicy;
139
- export interface PrivacySourceRecord {
140
- schema?: string;
141
- table: string;
142
- row: Record<string, unknown>;
143
- }
144
- export interface PrivacyEngine {
145
- readonly keyFingerprint: string;
146
- remapPath(input: {
147
- mapping: string;
148
- value: string;
149
- }): string;
150
- sanitize(record: unknown): PrivacySourceRecord | null;
151
- }
152
- export declare const createPrivacyEngine: ({ policy, key, environment, }: {
153
- policy: unknown;
154
- key: Uint8Array | string;
155
- environment?: NodeJS.ProcessEnv;
156
- }) => Readonly<PrivacyEngine>;
157
- export declare const createPrivacyKey: (path: string) => Promise<{
158
- fingerprint: string;
159
- }>;
160
- export declare const readPrivacyKey: (path: string) => Promise<Buffer>;
161
- export declare const assertPrivacyKeyFingerprint: (key: Uint8Array, expected: unknown) => void;
162
- export {};
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";