@zanii/residency 0.1.0 → 0.2.0

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/dist/index.d.ts CHANGED
@@ -61,6 +61,9 @@ export declare function verifyResidency(claim: ResidencyClaim, opts: {
61
61
  * Across an ordered sequence of verified regions (each `{ ts, region }`, oldest first),
62
62
  * find every point where the region left the declared allow-set. Each crossing names the
63
63
  * ts and the region it moved to — the evidence for "when did it leave, and to where."
64
+ * Data is presumed to originate in-jurisdiction, so a sequence that STARTS outside the
65
+ * allow-set emits an `exit` at its first event — "no crossings" never masks "was never
66
+ * inside." Events with a missing/unparseable ts are ignored.
64
67
  */
65
68
  export declare function detectCrossings(sequence: Array<{
66
69
  ts: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA6BA,6FAA6F;AAC7F,MAAM,WAAW,iBAAiB;IAChC,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,yFAAyF;IACzF,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,2FAA2F;AAC3F,MAAM,MAAM,cAAc,GAAG,CAAC,WAAW,EAAE,iBAAiB,KAAK,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC;AAEhK,oFAAoF;AACpF,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAIzD,CAAC;AAEF,MAAM,WAAW,cAAc;IAC7B,CAAC,EAAE,CAAC,CAAC;IACL,IAAI,EAAE,iBAAiB,CAAC;IACxB,kFAAkF;IAClF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,wFAAwF;IACxF,YAAY,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,kBAAkB,EAAE,iBAAiB,CAAC;IACtC,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,mGAAmG;AACnG,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAa/G;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAEvD;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,+EAA+E;IAC/E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,kFAAkF;IAClF,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE;IAAE,cAAc,EAAE,MAAM,EAAE,CAAC;IAAC,YAAY,EAAE,cAAc,CAAA;CAAE,GAC/D,OAAO,CAAC,cAAc,CAAC,CAYzB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,EAC/C,cAAc,EAAE,MAAM,EAAE,GACvB,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,CAAA;CAAE,CAAC,CAYrE;AAED,wGAAwG;AACxG,wBAAgB,gBAAgB,CAAC,eAAe,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAEnF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA6BA,6FAA6F;AAC7F,MAAM,WAAW,iBAAiB;IAChC,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,yFAAyF;IACzF,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,2FAA2F;AAC3F,MAAM,MAAM,cAAc,GAAG,CAAC,WAAW,EAAE,iBAAiB,KAAK,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC;AAEhK,oFAAoF;AACpF,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAIzD,CAAC;AAEF,MAAM,WAAW,cAAc;IAC7B,CAAC,EAAE,CAAC,CAAC;IACL,IAAI,EAAE,iBAAiB,CAAC;IACxB,kFAAkF;IAClF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,wFAAwF;IACxF,YAAY,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,kBAAkB,EAAE,iBAAiB,CAAC;IACtC,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAMD,mGAAmG;AACnG,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAa/G;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAEvD;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,+EAA+E;IAC/E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,kFAAkF;IAClF,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE;IAAE,cAAc,EAAE,MAAM,EAAE,CAAC;IAAC,YAAY,EAAE,cAAc,CAAA;CAAE,GAC/D,OAAO,CAAC,cAAc,CAAC,CAiBzB;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,EAC/C,cAAc,EAAE,MAAM,EAAE,GACvB,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,CAAA;CAAE,CAAC,CAcrE;AAED,wGAAwG;AACxG,wBAAgB,gBAAgB,CAAC,eAAe,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAEnF"}
package/dist/index.js CHANGED
@@ -32,13 +32,15 @@ export const JURISDICTION_PRESETS = {
32
32
  GCC: ['AE', 'SA', 'BH', 'KW', 'OM', 'QA', 'me-central-1', 'me-south-1'],
33
33
  EU: ['AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'eu-central-1', 'eu-west-1', 'eu-west-3', 'eu-north-1'],
34
34
  };
35
+ // strict ISO check — Date.parse alone accepts 'Aug 20 2026', which Python's verifier rejects
36
+ const validTs = (ts) => typeof ts === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(ts) && !Number.isNaN(Date.parse(ts));
35
37
  /** Build a residency claim for a touch. Record it (or attach to a `@zanii/data-custody` touch). */
36
38
  export function buildResidencyClaim(input) {
37
39
  if (!input.jurisdiction)
38
40
  throw new Error('jurisdiction is required');
39
41
  if (!input.regionAttestation?.region)
40
42
  throw new Error('regionAttestation.region is required');
41
- if (Number.isNaN(Date.parse(input.ts)))
43
+ if (!validTs(input.ts))
42
44
  throw new Error('ts must be an ISO timestamp');
43
45
  const claim = {
44
46
  v: 1,
@@ -64,7 +66,13 @@ export async function verifyResidency(claim, opts) {
64
66
  if (claim?.v !== 1 || claim.type !== 'residency.claim') {
65
67
  return { ok: false, reasons: ['not a residency claim (v1)'], region: null, crossed: false };
66
68
  }
67
- const verified = await opts.verifyRegion(claim.region_attestation);
69
+ let verified;
70
+ try {
71
+ verified = await opts.verifyRegion(claim.region_attestation);
72
+ }
73
+ catch {
74
+ return { ok: false, reasons: ['the injected region verifier threw'], region: null, crossed: false };
75
+ }
68
76
  if (!verified) {
69
77
  return { ok: false, reasons: ['region signal failed the injected verifier'], region: null, crossed: false };
70
78
  }
@@ -77,14 +85,19 @@ export async function verifyResidency(claim, opts) {
77
85
  * Across an ordered sequence of verified regions (each `{ ts, region }`, oldest first),
78
86
  * find every point where the region left the declared allow-set. Each crossing names the
79
87
  * ts and the region it moved to — the evidence for "when did it leave, and to where."
88
+ * Data is presumed to originate in-jurisdiction, so a sequence that STARTS outside the
89
+ * allow-set emits an `exit` at its first event — "no crossings" never masks "was never
90
+ * inside." Events with a missing/unparseable ts are ignored.
80
91
  */
81
92
  export function detectCrossings(sequence, allowedRegions) {
82
93
  const crossings = [];
83
- let inside = null;
84
- const ordered = [...sequence].sort((a, b) => Date.parse(a.ts) - Date.parse(b.ts));
94
+ let inside = true; // presumed in-jurisdiction until a signal says otherwise
95
+ const ordered = sequence
96
+ .filter((s) => validTs(s?.ts))
97
+ .sort((a, b) => Date.parse(a.ts) - Date.parse(b.ts));
85
98
  for (const s of ordered) {
86
99
  const now = allowedRegions.includes(s.region);
87
- if (inside !== null && now !== inside) {
100
+ if (now !== inside) {
88
101
  crossings.push({ ts: s.ts, region: s.region, direction: now ? 'return' : 'exit' });
89
102
  }
90
103
  inside = now;
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":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zanii/residency",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Prove where an agent's data physically stayed - bind a region attestation to each data touch, confirm the attested region is inside a declared jurisdiction, and emit a receipt the moment it crosses a border. Answers 'prove it never left the UAE / the EU.' The region signal is INJECTED (cloud-provider region / TEE geolocation) - we verify the binding, you supply the geo trust root; the proof is only as strong as that source.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://ledger.zanii.agency",
package/src/index.ts CHANGED
@@ -66,11 +66,15 @@ export interface ResidencyInput {
66
66
  ref?: string;
67
67
  }
68
68
 
69
+ // strict ISO check — Date.parse alone accepts 'Aug 20 2026', which Python's verifier rejects
70
+ const validTs = (ts: unknown): boolean =>
71
+ typeof ts === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(ts) && !Number.isNaN(Date.parse(ts));
72
+
69
73
  /** Build a residency claim for a touch. Record it (or attach to a `@zanii/data-custody` touch). */
70
74
  export function buildResidencyClaim(input: ResidencyInput): { target: string; payload: Record<string, unknown> } {
71
75
  if (!input.jurisdiction) throw new Error('jurisdiction is required');
72
76
  if (!input.regionAttestation?.region) throw new Error('regionAttestation.region is required');
73
- if (Number.isNaN(Date.parse(input.ts))) throw new Error('ts must be an ISO timestamp');
77
+ if (!validTs(input.ts)) throw new Error('ts must be an ISO timestamp');
74
78
  const claim: ResidencyClaim = {
75
79
  v: 1,
76
80
  type: 'residency.claim',
@@ -108,7 +112,12 @@ export async function verifyResidency(
108
112
  if (claim?.v !== 1 || claim.type !== 'residency.claim') {
109
113
  return { ok: false, reasons: ['not a residency claim (v1)'], region: null, crossed: false };
110
114
  }
111
- const verified = await opts.verifyRegion(claim.region_attestation);
115
+ let verified: { region: string; source: string } | null;
116
+ try {
117
+ verified = await opts.verifyRegion(claim.region_attestation);
118
+ } catch {
119
+ return { ok: false, reasons: ['the injected region verifier threw'], region: null, crossed: false };
120
+ }
112
121
  if (!verified) {
113
122
  return { ok: false, reasons: ['region signal failed the injected verifier'], region: null, crossed: false };
114
123
  }
@@ -121,17 +130,22 @@ export async function verifyResidency(
121
130
  * Across an ordered sequence of verified regions (each `{ ts, region }`, oldest first),
122
131
  * find every point where the region left the declared allow-set. Each crossing names the
123
132
  * ts and the region it moved to — the evidence for "when did it leave, and to where."
133
+ * Data is presumed to originate in-jurisdiction, so a sequence that STARTS outside the
134
+ * allow-set emits an `exit` at its first event — "no crossings" never masks "was never
135
+ * inside." Events with a missing/unparseable ts are ignored.
124
136
  */
125
137
  export function detectCrossings(
126
138
  sequence: Array<{ ts: string; region: string }>,
127
139
  allowedRegions: string[],
128
140
  ): Array<{ ts: string; region: string; direction: 'exit' | 'return' }> {
129
141
  const crossings: Array<{ ts: string; region: string; direction: 'exit' | 'return' }> = [];
130
- let inside: boolean | null = null;
131
- const ordered = [...sequence].sort((a, b) => Date.parse(a.ts) - Date.parse(b.ts));
142
+ let inside = true; // presumed in-jurisdiction until a signal says otherwise
143
+ const ordered = sequence
144
+ .filter((s) => validTs(s?.ts))
145
+ .sort((a, b) => Date.parse(a.ts) - Date.parse(b.ts));
132
146
  for (const s of ordered) {
133
147
  const now = allowedRegions.includes(s.region);
134
- if (inside !== null && now !== inside) {
148
+ if (now !== inside) {
135
149
  crossings.push({ ts: s.ts, region: s.region, direction: now ? 'return' : 'exit' });
136
150
  }
137
151
  inside = now;