@zanii/minimization 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/practice.d.ts +98 -0
- package/dist/practice.d.ts.map +1 -0
- package/dist/practice.js +114 -0
- package/dist/practice.js.map +1 -0
- package/package.json +2 -2
- package/src/index.ts +1 -0
- package/src/practice.ts +111 -0
package/README.md
CHANGED
|
@@ -33,3 +33,7 @@ commitment. Composes `@zanii/redact` field commitments + purpose-bound delegatio
|
|
|
33
33
|
Proves **declared-vs-accessed** — that the agent stayed inside the minimum it declared. It
|
|
34
34
|
does NOT prove the minimum was well-chosen (a judgement, not a fact), and it proves the
|
|
35
35
|
*recorded* accesses only.
|
|
36
|
+
|
|
37
|
+
## Changelog
|
|
38
|
+
|
|
39
|
+
- **0.1.3** — Minimisation in practice: starting `MINIMUM_SETS` per purpose, `suggestMinimum` from observed access, `minimiseQuery` (narrows plain SELECTs; refuses SELECT * and anything it cannot parse), `minimiseForPrompt` before data reaches a model, `violationHeatmap`, `dpiaEvidence`, `pseudonymise` with proof, retention per field (`commitFieldPolicy`), `verifyTransferMinimum` and a signed `minimisationStatement`.
|
package/dist/index.d.ts
CHANGED
|
@@ -43,4 +43,5 @@ export interface MinimizationCheck {
|
|
|
43
43
|
* accessed outside the declared minimum is a provable over-collection.
|
|
44
44
|
*/
|
|
45
45
|
export declare function verifyMinimization(proof: AccessProof, declaredFields: string[]): MinimizationCheck;
|
|
46
|
+
export * from './practice.js';
|
|
46
47
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAoCA,yGAAyG;AACzG,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAIzF;AAED,iGAAiG;AACjG,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAE5E;AAED,MAAM,WAAW,WAAW;IAC1B,CAAC,EAAE,CAAC,CAAC;IACL,IAAI,EAAE,qBAAqB,CAAC;IAC5B,wDAAwD;IACxD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,WAAW;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,yFAAyF;AACzF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAgBzG;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,WAAW,GAAG,MAAM,CAEjD;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,oEAAoE;IACpE,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,iBAAiB,CAelG"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAoCA,yGAAyG;AACzG,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAIzF;AAED,iGAAiG;AACjG,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAE5E;AAED,MAAM,WAAW,WAAW;IAC1B,CAAC,EAAE,CAAC,CAAC;IACL,IAAI,EAAE,qBAAqB,CAAC;IAC5B,wDAAwD;IACxD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,WAAW;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,yFAAyF;AACzF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAgBzG;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,WAAW,GAAG,MAAM,CAEjD;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,oEAAoE;IACpE,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,iBAAiB,CAelG;AACD,cAAc,eAAe,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtC,uFAAuF;AACvF,0FAA0F;AAC1F,MAAM,WAAW,GAAG,CAAC,CAAS,EAAE,CAAS,EAAU,EAAE;IACnD,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACtD,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC,WAAW,CAAC,CAAC,CAAE,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC,WAAW,CAAC,CAAC,CAAE,CAAC;QACxD,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;AAC7B,CAAC,CAAC;AAEF,yGAAyG;AACzG,MAAM,UAAU,cAAc,CAAC,MAAgB;IAC7C,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACtD,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACnE,OAAO,EAAE,UAAU,EAAE,gBAAgB,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AACzE,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,cAAc,CAAC,MAAgB,EAAE,UAAkB;IACjE,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC,UAAU,KAAK,UAAU,CAAC;AAC1D,CAAC;AAuBD,yFAAyF;AACzF,MAAM,UAAU,gBAAgB,CAAC,KAAkB;IACjD,IAAI,OAAO,KAAK,CAAC,kBAAkB,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QACvG,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC5E,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACtF,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACvF,MAAM,CAAC,GAAgB;QACrB,CAAC,EAAE,CAAC;QACJ,IAAI,EAAE,qBAAqB;QAC3B,SAAS,EAAE,KAAK,CAAC,kBAAkB;QACnC,QAAQ,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE;QAC7C,EAAE,EAAE,KAAK,CAAC,EAAE;KACb,CAAC;IACF,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS;QAAE,CAAC,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC;IAC/C,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS;QAAE,CAAC,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC3D,OAAO,EAAE,MAAM,EAAE,qBAAqB,EAAE,OAAO,EAAE,CAAuC,EAAE,CAAC;AAC7F,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,CAAc;IACvC,OAAO,OAAO,CAAC,CAAuC,CAAC,CAAC;AAC1D,CAAC;AASD;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAkB,EAAE,cAAwB;IAC7E,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,KAAK,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,qBAAqB,EAAE,CAAC;QAC3D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,sCAAsC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACtF,CAAC;IACD,IAAI,OAAO,KAAK,CAAC,SAAS,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1E,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,oDAAoD,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACpG,CAAC;IACD,IAAI,CAAC,cAAc,CAAC,cAAc,EAAE,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QACrD,OAAO,CAAC,IAAI,CAAC,oGAAoG,CAAC,CAAC;IACrH,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,cAAc,CAAC,CAAC;IACzC,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACrE,KAAK,MAAM,CAAC,IAAI,MAAM;QAAE,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,0CAA0C,CAAC,CAAC;IAC/F,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;AACvD,CAAC;AACD,cAAc,eAAe,CAAC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { type SignedObject } from '@zanii/core';
|
|
2
|
+
import { type AccessProof } from './index.js';
|
|
3
|
+
/** Starting minimum field sets for common purposes. Review them for your case; they are not legal minima. */
|
|
4
|
+
export declare const MINIMUM_SETS: Record<string, string[]>;
|
|
5
|
+
/**
|
|
6
|
+
* Suggest a minimum from real access: fields used in at least `share` of accesses are
|
|
7
|
+
* proposed; the rest must be justified or dropped.
|
|
8
|
+
*/
|
|
9
|
+
export declare function suggestMinimum(proofs: AccessProof[], share?: number): {
|
|
10
|
+
proposed: string[];
|
|
11
|
+
justify: {
|
|
12
|
+
field: string;
|
|
13
|
+
share: number;
|
|
14
|
+
}[];
|
|
15
|
+
commitment: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Narrow a simple `SELECT col, ... FROM ...` to the allowed columns. Refuses `SELECT *` and
|
|
19
|
+
* anything it cannot parse: it never guesses what a query reads.
|
|
20
|
+
*/
|
|
21
|
+
export declare function minimiseQuery(sql: string, allowed: string[]): {
|
|
22
|
+
sql: string;
|
|
23
|
+
removed: string[];
|
|
24
|
+
};
|
|
25
|
+
/** Strip fields outside the minimum from a record before it reaches a model prompt. Paths use dots. */
|
|
26
|
+
export declare function minimiseForPrompt<T extends Record<string, unknown>>(record: T, allowed: string[]): {
|
|
27
|
+
record: Partial<T>;
|
|
28
|
+
removed: string[];
|
|
29
|
+
};
|
|
30
|
+
/** Which agents read which fields outside their declared minimum, and how often. */
|
|
31
|
+
export declare function violationHeatmap(entries: Array<{
|
|
32
|
+
agent: string;
|
|
33
|
+
proof: AccessProof;
|
|
34
|
+
declared: string[];
|
|
35
|
+
}>): {
|
|
36
|
+
byField: Record<string, number>;
|
|
37
|
+
byAgent: Record<string, number>;
|
|
38
|
+
};
|
|
39
|
+
/** DPIA evidence for one processing activity: declared minimum, observed access, violations, period. */
|
|
40
|
+
export declare function dpiaEvidence(input: {
|
|
41
|
+
activity: string;
|
|
42
|
+
purpose: string;
|
|
43
|
+
declared: string[];
|
|
44
|
+
proofs: AccessProof[];
|
|
45
|
+
}): {
|
|
46
|
+
activity: string;
|
|
47
|
+
purpose: string;
|
|
48
|
+
declared: {
|
|
49
|
+
commitment: string;
|
|
50
|
+
fields: string[];
|
|
51
|
+
};
|
|
52
|
+
accesses: number;
|
|
53
|
+
violations: number;
|
|
54
|
+
excessFields: string[];
|
|
55
|
+
period: {
|
|
56
|
+
from: string | null;
|
|
57
|
+
to: string | null;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
/** Replace direct identifiers with keyed pseudonyms before use; the proof lists which fields were pseudonymised. */
|
|
61
|
+
export declare function pseudonymise<T extends Record<string, unknown>>(record: T, fields: string[], key: string): {
|
|
62
|
+
record: T;
|
|
63
|
+
proof: {
|
|
64
|
+
type: 'minimization.pseudonymised';
|
|
65
|
+
fields: string[];
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
/** Commit to the minimum together with how long each field may be kept (days). */
|
|
69
|
+
export declare function commitFieldPolicy(policy: Record<string, number>): {
|
|
70
|
+
fields: string[];
|
|
71
|
+
policy: Record<string, number>;
|
|
72
|
+
commitment: string;
|
|
73
|
+
};
|
|
74
|
+
/** A transfer (cross-border, to a processor) must carry a subset of the base minimum. */
|
|
75
|
+
export declare function verifyTransferMinimum(base: string[], transfer: string[]): {
|
|
76
|
+
ok: boolean;
|
|
77
|
+
extra: string[];
|
|
78
|
+
};
|
|
79
|
+
export type MinimisationStatement = SignedObject<{
|
|
80
|
+
v: 1;
|
|
81
|
+
type: 'minimization.statement';
|
|
82
|
+
controller: string;
|
|
83
|
+
period: string;
|
|
84
|
+
activities: number;
|
|
85
|
+
accesses: number;
|
|
86
|
+
violations: number;
|
|
87
|
+
ts: string;
|
|
88
|
+
}>;
|
|
89
|
+
/** The controller's signed periodic statement for a supervisor. */
|
|
90
|
+
export declare function minimisationStatement(input: {
|
|
91
|
+
controller: string;
|
|
92
|
+
period: string;
|
|
93
|
+
activities: number;
|
|
94
|
+
accesses: number;
|
|
95
|
+
violations: number;
|
|
96
|
+
ts?: string;
|
|
97
|
+
}, controllerPrivateKey: Uint8Array): MinimisationStatement;
|
|
98
|
+
//# sourceMappingURL=practice.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"practice.d.ts","sourceRoot":"","sources":["../src/practice.ts"],"names":[],"mappings":"AAOA,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAsC,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAElF,6GAA6G;AAC7G,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAMjD,CAAC;AAEF;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,KAAK,SAAM;;;;;;;EAOhE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAUhG;AAED,uGAAuG;AACvG,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG;IAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAa5I;AAED,oFAAoF;AACpF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;;;EAQzG;AAED,wGAAwG;AACxG,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,WAAW,EAAE,CAAA;CAAE;;;;;;;;;;;;;;EAOnH;AAED,oHAAoH;AACpH,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE;QAAE,IAAI,EAAE,4BAA4B,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAA;CAAE,CAIxL;AAED,kFAAkF;AAClF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;;;;EAI/D;AAED,yFAAyF;AACzF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE;;;EAGvE;AAED,MAAM,MAAM,qBAAqB,GAAG,YAAY,CAAC;IAAE,CAAC,EAAE,CAAC,CAAC;IAAC,IAAI,EAAE,wBAAwB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAErM,mEAAmE;AACnE,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,oBAAoB,EAAE,UAAU,GAAG,qBAAqB,CAEnN"}
|
package/dist/practice.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data minimisation in practice: starting field sets per purpose, minima suggested from
|
|
3
|
+
* real access, SQL and LLM-context filters that enforce the minimum before data moves,
|
|
4
|
+
* violation heatmaps, DPIA evidence, pseudonymisation with proof, retention per field,
|
|
5
|
+
* stricter sets for transfers, and a controller's signed periodic statement.
|
|
6
|
+
*/
|
|
7
|
+
import { createHmac } from 'node:crypto';
|
|
8
|
+
import { signObject } from '@zanii/core';
|
|
9
|
+
import { commitFieldSet, verifyMinimization } from './index.js';
|
|
10
|
+
/** Starting minimum field sets for common purposes. Review them for your case; they are not legal minima. */
|
|
11
|
+
export const MINIMUM_SETS = {
|
|
12
|
+
kyc: ['full_name', 'date_of_birth', 'nationality', 'id_document_number', 'id_document_expiry', 'address'],
|
|
13
|
+
payroll: ['employee_id', 'full_name', 'bank_iban', 'salary', 'tax_id'],
|
|
14
|
+
triage: ['age_band', 'symptoms', 'vital_signs', 'allergies', 'current_medication'],
|
|
15
|
+
'support-ticket': ['customer_id', 'contact_email', 'product', 'issue_description'],
|
|
16
|
+
delivery: ['recipient_name', 'delivery_address', 'contact_phone'],
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Suggest a minimum from real access: fields used in at least `share` of accesses are
|
|
20
|
+
* proposed; the rest must be justified or dropped.
|
|
21
|
+
*/
|
|
22
|
+
export function suggestMinimum(proofs, share = 0.2) {
|
|
23
|
+
const counts = new Map();
|
|
24
|
+
for (const p of proofs)
|
|
25
|
+
for (const f of p.accessed)
|
|
26
|
+
counts.set(f, (counts.get(f) ?? 0) + 1);
|
|
27
|
+
const n = proofs.length || 1;
|
|
28
|
+
const proposed = [...counts].filter(([, c]) => c / n >= share).map(([f]) => f).sort();
|
|
29
|
+
const justify = [...counts].filter(([, c]) => c / n < share).map(([f, c]) => ({ field: f, share: Math.round((c / n) * 1000) / 1000 }));
|
|
30
|
+
return { proposed, justify, commitment: commitFieldSet(proposed).commitment };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Narrow a simple `SELECT col, ... FROM ...` to the allowed columns. Refuses `SELECT *` and
|
|
34
|
+
* anything it cannot parse: it never guesses what a query reads.
|
|
35
|
+
*/
|
|
36
|
+
export function minimiseQuery(sql, allowed) {
|
|
37
|
+
const m = sql.match(/^\s*select\s+([\s\S]+?)\s+from\s+([\s\S]+)$/i);
|
|
38
|
+
if (!m)
|
|
39
|
+
throw new Error('minimization: only plain SELECT ... FROM ... queries are rewritten');
|
|
40
|
+
if (/\(/.test(m[1]))
|
|
41
|
+
throw new Error('minimization: expressions in the select list are not rewritten');
|
|
42
|
+
const cols = m[1].split(',').map((c) => c.trim());
|
|
43
|
+
if (cols.some((c) => c === '*' || c.endsWith('.*')))
|
|
44
|
+
throw new Error('minimization: SELECT * cannot be minimised; name the columns');
|
|
45
|
+
const name = (c) => c.split(/\s+as\s+|\s+/i)[0].split('.').pop().replace(/["`[\]]/g, '');
|
|
46
|
+
const keep = cols.filter((c) => allowed.includes(name(c)));
|
|
47
|
+
if (!keep.length)
|
|
48
|
+
throw new Error('minimization: no allowed columns left');
|
|
49
|
+
return { sql: `SELECT ${keep.join(', ')} FROM ${m[2]}`, removed: cols.filter((c) => !keep.includes(c)).map(name) };
|
|
50
|
+
}
|
|
51
|
+
/** Strip fields outside the minimum from a record before it reaches a model prompt. Paths use dots. */
|
|
52
|
+
export function minimiseForPrompt(record, allowed) {
|
|
53
|
+
const removed = [];
|
|
54
|
+
const walk = (o, prefix) => {
|
|
55
|
+
const out = {};
|
|
56
|
+
for (const [k, v] of Object.entries(o)) {
|
|
57
|
+
const path = prefix ? `${prefix}.${k}` : k;
|
|
58
|
+
if (allowed.includes(path))
|
|
59
|
+
out[k] = v;
|
|
60
|
+
else if (v && typeof v === 'object' && !Array.isArray(v) && allowed.some((a) => a.startsWith(path + '.')))
|
|
61
|
+
out[k] = walk(v, path);
|
|
62
|
+
else
|
|
63
|
+
removed.push(path);
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
};
|
|
67
|
+
return { record: walk(record, ''), removed };
|
|
68
|
+
}
|
|
69
|
+
/** Which agents read which fields outside their declared minimum, and how often. */
|
|
70
|
+
export function violationHeatmap(entries) {
|
|
71
|
+
const byField = {};
|
|
72
|
+
const byAgent = {};
|
|
73
|
+
for (const e of entries)
|
|
74
|
+
for (const f of verifyMinimization(e.proof, e.declared).excess) {
|
|
75
|
+
byField[f] = (byField[f] ?? 0) + 1;
|
|
76
|
+
byAgent[e.agent] = (byAgent[e.agent] ?? 0) + 1;
|
|
77
|
+
}
|
|
78
|
+
return { byField, byAgent };
|
|
79
|
+
}
|
|
80
|
+
/** DPIA evidence for one processing activity: declared minimum, observed access, violations, period. */
|
|
81
|
+
export function dpiaEvidence(input) {
|
|
82
|
+
const results = input.proofs.map((p) => verifyMinimization(p, input.declared));
|
|
83
|
+
const ts = input.proofs.map((p) => p.ts).sort();
|
|
84
|
+
return {
|
|
85
|
+
activity: input.activity, purpose: input.purpose, declared: commitFieldSet(input.declared), accesses: input.proofs.length,
|
|
86
|
+
violations: results.filter((r) => !r.ok).length, excessFields: [...new Set(results.flatMap((r) => r.excess))].sort(), period: { from: ts[0] ?? null, to: ts.at(-1) ?? null },
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** Replace direct identifiers with keyed pseudonyms before use; the proof lists which fields were pseudonymised. */
|
|
90
|
+
export function pseudonymise(record, fields, key) {
|
|
91
|
+
const out = { ...record };
|
|
92
|
+
for (const f of fields)
|
|
93
|
+
if (out[f] !== undefined)
|
|
94
|
+
out[f] = 'ps_' + createHmac('sha256', key).update(String(out[f])).digest('hex').slice(0, 24);
|
|
95
|
+
return { record: out, proof: { type: 'minimization.pseudonymised', fields: [...fields].sort() } };
|
|
96
|
+
}
|
|
97
|
+
/** Commit to the minimum together with how long each field may be kept (days). */
|
|
98
|
+
export function commitFieldPolicy(policy) {
|
|
99
|
+
for (const [f, d] of Object.entries(policy))
|
|
100
|
+
if (!Number.isInteger(d) || d < 0)
|
|
101
|
+
throw new Error(`minimization: retention for ${f} must be whole days`);
|
|
102
|
+
const fields = Object.keys(policy).sort();
|
|
103
|
+
return { ...commitFieldSet(fields.map((f) => `${f}@${policy[f]}d`)), fields, policy };
|
|
104
|
+
}
|
|
105
|
+
/** A transfer (cross-border, to a processor) must carry a subset of the base minimum. */
|
|
106
|
+
export function verifyTransferMinimum(base, transfer) {
|
|
107
|
+
const extra = transfer.filter((f) => !base.includes(f));
|
|
108
|
+
return { ok: extra.length === 0, extra };
|
|
109
|
+
}
|
|
110
|
+
/** The controller's signed periodic statement for a supervisor. */
|
|
111
|
+
export function minimisationStatement(input, controllerPrivateKey) {
|
|
112
|
+
return signObject({ v: 1, type: 'minimization.statement', ...input, ts: input.ts ?? new Date().toISOString() }, controllerPrivateKey);
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=practice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"practice.js","sourceRoot":"","sources":["../src/practice.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zanii/minimization",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Prove data minimization (GDPR Art. 5(1)(c)) - unprovable today, only assertable. Commit the declared MINIMUM field set, then prove every field an agent accessed was inside it; any access outside the declared minimum is a flagged violation. Composes @zanii/redact field commitments and purpose-bound delegation into a proof a regulator accepts. Proves declared-vs-accessed, not that the minimum itself was well-chosen.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://ledger.zanii.agency",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@noble/hashes": "^1.7.1",
|
|
35
|
-
"@zanii/core": "0.4
|
|
35
|
+
"@zanii/core": "^0.5.4"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsc -p tsconfig.build.json",
|
package/src/index.ts
CHANGED
|
@@ -119,3 +119,4 @@ export function verifyMinimization(proof: AccessProof, declaredFields: string[])
|
|
|
119
119
|
for (const f of excess) reasons.push(`accessed '${f}', which is OUTSIDE the declared minimum`);
|
|
120
120
|
return { ok: reasons.length === 0, reasons, excess };
|
|
121
121
|
}
|
|
122
|
+
export * from './practice.js';
|
package/src/practice.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data minimisation in practice: starting field sets per purpose, minima suggested from
|
|
3
|
+
* real access, SQL and LLM-context filters that enforce the minimum before data moves,
|
|
4
|
+
* violation heatmaps, DPIA evidence, pseudonymisation with proof, retention per field,
|
|
5
|
+
* stricter sets for transfers, and a controller's signed periodic statement.
|
|
6
|
+
*/
|
|
7
|
+
import { createHmac } from 'node:crypto';
|
|
8
|
+
import { signObject, type SignedObject } from '@zanii/core';
|
|
9
|
+
import { commitFieldSet, verifyMinimization, type AccessProof } from './index.js';
|
|
10
|
+
|
|
11
|
+
/** Starting minimum field sets for common purposes. Review them for your case; they are not legal minima. */
|
|
12
|
+
export const MINIMUM_SETS: Record<string, string[]> = {
|
|
13
|
+
kyc: ['full_name', 'date_of_birth', 'nationality', 'id_document_number', 'id_document_expiry', 'address'],
|
|
14
|
+
payroll: ['employee_id', 'full_name', 'bank_iban', 'salary', 'tax_id'],
|
|
15
|
+
triage: ['age_band', 'symptoms', 'vital_signs', 'allergies', 'current_medication'],
|
|
16
|
+
'support-ticket': ['customer_id', 'contact_email', 'product', 'issue_description'],
|
|
17
|
+
delivery: ['recipient_name', 'delivery_address', 'contact_phone'],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Suggest a minimum from real access: fields used in at least `share` of accesses are
|
|
22
|
+
* proposed; the rest must be justified or dropped.
|
|
23
|
+
*/
|
|
24
|
+
export function suggestMinimum(proofs: AccessProof[], share = 0.2) {
|
|
25
|
+
const counts = new Map<string, number>();
|
|
26
|
+
for (const p of proofs) for (const f of p.accessed) counts.set(f, (counts.get(f) ?? 0) + 1);
|
|
27
|
+
const n = proofs.length || 1;
|
|
28
|
+
const proposed = [...counts].filter(([, c]) => c / n >= share).map(([f]) => f).sort();
|
|
29
|
+
const justify = [...counts].filter(([, c]) => c / n < share).map(([f, c]) => ({ field: f, share: Math.round((c / n) * 1000) / 1000 }));
|
|
30
|
+
return { proposed, justify, commitment: commitFieldSet(proposed).commitment };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Narrow a simple `SELECT col, ... FROM ...` to the allowed columns. Refuses `SELECT *` and
|
|
35
|
+
* anything it cannot parse: it never guesses what a query reads.
|
|
36
|
+
*/
|
|
37
|
+
export function minimiseQuery(sql: string, allowed: string[]): { sql: string; removed: string[] } {
|
|
38
|
+
const m = sql.match(/^\s*select\s+([\s\S]+?)\s+from\s+([\s\S]+)$/i);
|
|
39
|
+
if (!m) throw new Error('minimization: only plain SELECT ... FROM ... queries are rewritten');
|
|
40
|
+
if (/\(/.test(m[1]!)) throw new Error('minimization: expressions in the select list are not rewritten');
|
|
41
|
+
const cols = m[1]!.split(',').map((c) => c.trim());
|
|
42
|
+
if (cols.some((c) => c === '*' || c.endsWith('.*'))) throw new Error('minimization: SELECT * cannot be minimised; name the columns');
|
|
43
|
+
const name = (c: string) => c.split(/\s+as\s+|\s+/i)[0]!.split('.').pop()!.replace(/["`[\]]/g, '');
|
|
44
|
+
const keep = cols.filter((c) => allowed.includes(name(c)));
|
|
45
|
+
if (!keep.length) throw new Error('minimization: no allowed columns left');
|
|
46
|
+
return { sql: `SELECT ${keep.join(', ')} FROM ${m[2]}`, removed: cols.filter((c) => !keep.includes(c)).map(name) };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Strip fields outside the minimum from a record before it reaches a model prompt. Paths use dots. */
|
|
50
|
+
export function minimiseForPrompt<T extends Record<string, unknown>>(record: T, allowed: string[]): { record: Partial<T>; removed: string[] } {
|
|
51
|
+
const removed: string[] = [];
|
|
52
|
+
const walk = (o: Record<string, unknown>, prefix: string): Record<string, unknown> => {
|
|
53
|
+
const out: Record<string, unknown> = {};
|
|
54
|
+
for (const [k, v] of Object.entries(o)) {
|
|
55
|
+
const path = prefix ? `${prefix}.${k}` : k;
|
|
56
|
+
if (allowed.includes(path)) out[k] = v;
|
|
57
|
+
else if (v && typeof v === 'object' && !Array.isArray(v) && allowed.some((a) => a.startsWith(path + '.'))) out[k] = walk(v as Record<string, unknown>, path);
|
|
58
|
+
else removed.push(path);
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
};
|
|
62
|
+
return { record: walk(record, '') as Partial<T>, removed };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Which agents read which fields outside their declared minimum, and how often. */
|
|
66
|
+
export function violationHeatmap(entries: Array<{ agent: string; proof: AccessProof; declared: string[] }>) {
|
|
67
|
+
const byField: Record<string, number> = {};
|
|
68
|
+
const byAgent: Record<string, number> = {};
|
|
69
|
+
for (const e of entries) for (const f of verifyMinimization(e.proof, e.declared).excess) {
|
|
70
|
+
byField[f] = (byField[f] ?? 0) + 1;
|
|
71
|
+
byAgent[e.agent] = (byAgent[e.agent] ?? 0) + 1;
|
|
72
|
+
}
|
|
73
|
+
return { byField, byAgent };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** DPIA evidence for one processing activity: declared minimum, observed access, violations, period. */
|
|
77
|
+
export function dpiaEvidence(input: { activity: string; purpose: string; declared: string[]; proofs: AccessProof[] }) {
|
|
78
|
+
const results = input.proofs.map((p) => verifyMinimization(p, input.declared));
|
|
79
|
+
const ts = input.proofs.map((p) => p.ts).sort();
|
|
80
|
+
return {
|
|
81
|
+
activity: input.activity, purpose: input.purpose, declared: commitFieldSet(input.declared), accesses: input.proofs.length,
|
|
82
|
+
violations: results.filter((r) => !r.ok).length, excessFields: [...new Set(results.flatMap((r) => r.excess))].sort(), period: { from: ts[0] ?? null, to: ts.at(-1) ?? null },
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Replace direct identifiers with keyed pseudonyms before use; the proof lists which fields were pseudonymised. */
|
|
87
|
+
export function pseudonymise<T extends Record<string, unknown>>(record: T, fields: string[], key: string): { record: T; proof: { type: 'minimization.pseudonymised'; fields: string[] } } {
|
|
88
|
+
const out: Record<string, unknown> = { ...record };
|
|
89
|
+
for (const f of fields) if (out[f] !== undefined) out[f] = 'ps_' + createHmac('sha256', key).update(String(out[f])).digest('hex').slice(0, 24);
|
|
90
|
+
return { record: out as T, proof: { type: 'minimization.pseudonymised', fields: [...fields].sort() } };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Commit to the minimum together with how long each field may be kept (days). */
|
|
94
|
+
export function commitFieldPolicy(policy: Record<string, number>) {
|
|
95
|
+
for (const [f, d] of Object.entries(policy)) if (!Number.isInteger(d) || d < 0) throw new Error(`minimization: retention for ${f} must be whole days`);
|
|
96
|
+
const fields = Object.keys(policy).sort();
|
|
97
|
+
return { ...commitFieldSet(fields.map((f) => `${f}@${policy[f]}d`)), fields, policy };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** A transfer (cross-border, to a processor) must carry a subset of the base minimum. */
|
|
101
|
+
export function verifyTransferMinimum(base: string[], transfer: string[]) {
|
|
102
|
+
const extra = transfer.filter((f) => !base.includes(f));
|
|
103
|
+
return { ok: extra.length === 0, extra };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type MinimisationStatement = SignedObject<{ v: 1; type: 'minimization.statement'; controller: string; period: string; activities: number; accesses: number; violations: number; ts: string }>;
|
|
107
|
+
|
|
108
|
+
/** The controller's signed periodic statement for a supervisor. */
|
|
109
|
+
export function minimisationStatement(input: { controller: string; period: string; activities: number; accesses: number; violations: number; ts?: string }, controllerPrivateKey: Uint8Array): MinimisationStatement {
|
|
110
|
+
return signObject({ v: 1 as const, type: 'minimization.statement' as const, ...input, ts: input.ts ?? new Date().toISOString() }, controllerPrivateKey);
|
|
111
|
+
}
|