genesis-mesh-sdk 0.59.1 → 0.61.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +8 -0
  3. package/dist/cjs/auth.js +10 -3
  4. package/dist/cjs/canonical.js +22 -0
  5. package/dist/cjs/client.js +103 -23
  6. package/dist/cjs/data_intent.js +35 -0
  7. package/dist/cjs/errors.js +20 -0
  8. package/dist/cjs/health.js +59 -0
  9. package/dist/cjs/index.js +18 -2
  10. package/dist/cjs/verify.js +71 -0
  11. package/dist/esm/auth.js +10 -3
  12. package/dist/esm/auth.js.map +1 -1
  13. package/dist/esm/canonical.js +19 -0
  14. package/dist/esm/canonical.js.map +1 -1
  15. package/dist/esm/client.js +103 -23
  16. package/dist/esm/client.js.map +1 -1
  17. package/dist/esm/data_intent.js +33 -0
  18. package/dist/esm/data_intent.js.map +1 -0
  19. package/dist/esm/errors.js +19 -0
  20. package/dist/esm/errors.js.map +1 -1
  21. package/dist/esm/health.js +56 -0
  22. package/dist/esm/health.js.map +1 -0
  23. package/dist/esm/index.js +10 -3
  24. package/dist/esm/index.js.map +1 -1
  25. package/dist/esm/verify.js +69 -1
  26. package/dist/esm/verify.js.map +1 -1
  27. package/dist/types/auth.d.ts +5 -3
  28. package/dist/types/auth.d.ts.map +1 -1
  29. package/dist/types/canonical.d.ts +5 -1
  30. package/dist/types/canonical.d.ts.map +1 -1
  31. package/dist/types/client.d.ts +24 -3
  32. package/dist/types/client.d.ts.map +1 -1
  33. package/dist/types/data_intent.d.ts +27 -0
  34. package/dist/types/data_intent.d.ts.map +1 -0
  35. package/dist/types/errors.d.ts +11 -0
  36. package/dist/types/errors.d.ts.map +1 -1
  37. package/dist/types/health.d.ts +23 -0
  38. package/dist/types/health.d.ts.map +1 -0
  39. package/dist/types/index.d.ts +11 -4
  40. package/dist/types/index.d.ts.map +1 -1
  41. package/dist/types/types.d.ts +65 -14
  42. package/dist/types/types.d.ts.map +1 -1
  43. package/dist/types/verify.d.ts +33 -1
  44. package/dist/types/verify.d.ts.map +1 -1
  45. package/docs/high-availability.md +86 -0
  46. package/docs/offline-verification.md +38 -1
  47. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,48 @@ Versions align with the [Genesis Mesh release sequence](https://github.com/Genes
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.61.0] - 2026-10-02
11
+
12
+ Coordinated Genesis Mesh v0.61.0 release: the cross-language interoperability
13
+ proof. The core's interop scenario uses this SDK to sign data access intents,
14
+ submit them to the NA, and verify Python records.
15
+
16
+ ### Added
17
+
18
+ - `verifyAgreement`, `verifyDataLicensePolicySignature` and
19
+ `verifyDataAccessIntent`: offline verification with the reason codes of the
20
+ Python reference.
21
+ - `createDataAccessIntent`: build and sign a data access intent as the agent.
22
+ - `agreementCanonical`, `dataLicensePolicyCanonical`,
23
+ `dataAccessIntentCanonical`.
24
+ - The shared `interop` conformance vectors (25) in
25
+ `tests/fixtures/conformance/interop.json`, all passing.
26
+
27
+ ### Fixed
28
+
29
+ - `AgreementRecord`, `AgreementTerms`, `CapabilityOffer`, `CapabilityCounter`,
30
+ `DataAccessIntent` and `DataSourceDescriptor` now match the wire format.
31
+ - `parseJson` keeps integer literals beyond `Number.MAX_SAFE_INTEGER` exactly
32
+ (as `bigint`), and `canonicalJson` encodes them.
33
+
34
+ ## [0.60.0] - 2026-10-01
35
+
36
+ Coordinated Genesis Mesh v0.60.0 release: client support for the Network
37
+ Authority's optional high availability.
38
+
39
+ - Add `baseUrls`: several NA instances without a load balancer. Idempotent
40
+ requests fail over on transport errors and 502/503/504. Non-idempotent
41
+ requests move only when the connection was never made, and are never
42
+ replayed after it may have been.
43
+ - Add `NetworkError.connectFailed`.
44
+ - Add `client.health`: `liveness()`, `readiness()` (typed `/readyz`, not-ready
45
+ returned as `ready: false` with the failing checks), `health()`, and
46
+ `endpoints()` to probe every configured instance.
47
+ - Add `isRetryableConflict()` and the `HaConflictCode` type for the 409 codes
48
+ an NA instance returns when it loses a race the database decided.
49
+ - Add a live HA test (`tests/e2e-ha.test.ts`): two instances on PostgreSQL
50
+ behind nginx, one killed mid-run. CI runs it against core `main`.
51
+
10
52
  ## [0.59.1] - 2026-10-01
11
53
 
12
54
  Coordinated Genesis Mesh v0.59.1 release: TypeScript support for governed
package/README.md CHANGED
@@ -37,6 +37,7 @@ execution recording, evidence-store queries and offline verification.
37
37
  - [Governed actions and reconciliation](docs/governance.md)
38
38
  - [Evidence store, policies, signers and retries](docs/evidence-store.md)
39
39
  - [Offline verification and integration tests](docs/offline-verification.md)
40
+ - [High availability: failover, readiness and conflicts](docs/high-availability.md)
40
41
 
41
42
  `governedAction` requires verification keys and explicit expected policies. It
42
43
  verifies the signed response and its request context before calling the supplied
@@ -219,6 +220,13 @@ const dv = await client.dataUsage.verify({ intent, policy: pol });
219
220
  console.log(dv.valid, dv.violations);
220
221
  ```
221
222
 
223
+ ## Offline verification
224
+
225
+ The SDK verifies boundary decisions, evidence exports, and from 0.61.0
226
+ agreements, data license policies and data access intents offline, and signs
227
+ data access intents with `createDataAccessIntent`. See
228
+ [docs/offline-verification.md](docs/offline-verification.md).
229
+
222
230
  ## Raw admin calls
223
231
 
224
232
  For NA routes not yet covered by a sub-client (e.g. `/admin/recognition-treaties`),
package/dist/cjs/auth.js CHANGED
@@ -74,12 +74,17 @@ function pythonNumber(value, pythonFloat = false) {
74
74
  */
75
75
  const PYTHON_FLOATS = new WeakMap();
76
76
  /**
77
- * JSON.parse that remembers integral-valued floats so canonicalJson reproduces
78
- * them. Use it for any NA JSON that will be verified or digested. Requires
79
- * JSON.parse source text access (Node.js 22+).
77
+ * JSON.parse that keeps what Python would sign: integral-valued floats are
78
+ * remembered so canonicalJson reproduces them (`90.0`), and integer literals
79
+ * beyond Number.MAX_SAFE_INTEGER become `bigint` so no digit is lost. Use it
80
+ * for any NA JSON that will be verified or digested. Requires JSON.parse source
81
+ * text access (Node.js 22+).
80
82
  */
81
83
  function parseJson(text) {
82
84
  return JSON.parse(text, function (key, value, context) {
85
+ if (typeof value === 'number' && !Number.isSafeInteger(value) && context?.source && /^-?\d+$/.test(context.source)) {
86
+ return BigInt(context.source);
87
+ }
83
88
  if (typeof value === 'number' && Number.isInteger(value) && context?.source && /[.eE]/.test(context.source)) {
84
89
  let keys = PYTHON_FLOATS.get(this);
85
90
  if (!keys)
@@ -112,6 +117,8 @@ function canonicalJson(value) {
112
117
  return asciiString(value);
113
118
  if (typeof value === 'number')
114
119
  return pythonNumber(value);
120
+ if (typeof value === 'bigint')
121
+ return value.toString();
115
122
  if (typeof value === 'boolean')
116
123
  return value ? 'true' : 'false';
117
124
  if (Array.isArray(value))
@@ -20,6 +20,9 @@ exports.freshnessProofCanonical = freshnessProofCanonical;
20
20
  exports.checkpointCanonical = checkpointCanonical;
21
21
  exports.entryDigest = entryDigest;
22
22
  exports.payloadDigest = payloadDigest;
23
+ exports.agreementCanonical = agreementCanonical;
24
+ exports.dataLicensePolicyCanonical = dataLicensePolicyCanonical;
25
+ exports.dataAccessIntentCanonical = dataAccessIntentCanonical;
23
26
  const auth_js_1 = require("./auth.js");
24
27
  /** v0.59 resource-chain fields, omitted from the execution canonical form when absent. */
25
28
  exports.RESOURCE_CHAIN_FIELDS = [
@@ -87,3 +90,22 @@ function entryDigest(entry) {
87
90
  function payloadDigest(payload) {
88
91
  return (0, auth_js_1.canonicalDigest)(payload);
89
92
  }
93
+ /** Fields both parties sign; identical for CapabilityCounter and AgreementRecord (excludes ids and timestamps). */
94
+ const AGREEMENT_CANONICAL_FIELDS = [
95
+ 'agreed_terms', 'graph_digest', 'offer_id', 'offerer_evidence',
96
+ 'offerer_sovereign_id', 'responder_evidence', 'responder_sovereign_id',
97
+ ];
98
+ /** The body both parties of an agreement sign. */
99
+ function agreementCanonical(record) {
100
+ const source = record;
101
+ const body = {};
102
+ for (const key of AGREEMENT_CANONICAL_FIELDS)
103
+ body[key] = source[key] ?? null;
104
+ return (0, auth_js_1.canonicalJson)(body);
105
+ }
106
+ function dataLicensePolicyCanonical(policy) {
107
+ return (0, auth_js_1.canonicalJson)(without(policy, ['signature']));
108
+ }
109
+ function dataAccessIntentCanonical(intent) {
110
+ return (0, auth_js_1.canonicalJson)(without(intent, ['signature']));
111
+ }
@@ -21,8 +21,24 @@ function buildPath(path, query) {
21
21
  const qs = params.toString();
22
22
  return qs ? `${path}?${qs}` : path;
23
23
  }
24
+ const FAILOVER_STATUS = new Set([502, 503, 504]);
25
+ /** Errors meaning no connection was made, so the request was never sent. */
26
+ const CONNECT_FAILURE_CODES = new Set([
27
+ 'ECONNREFUSED', 'ENOTFOUND', 'EHOSTUNREACH', 'ENETUNREACH', 'EAI_AGAIN', 'UND_ERR_CONNECT_TIMEOUT',
28
+ ]);
29
+ function isConnectFailure(err) {
30
+ const cause = err?.cause;
31
+ if (!cause)
32
+ return false;
33
+ if (typeof cause.code === 'string' && CONNECT_FAILURE_CODES.has(cause.code))
34
+ return true;
35
+ return Array.isArray(cause.errors) && cause.errors.length > 0
36
+ && cause.errors.every(e => typeof e?.code === 'string' && CONNECT_FAILURE_CODES.has(e.code));
37
+ }
24
38
  class HttpTransport {
25
- baseUrl;
39
+ /** Every configured NA endpoint, in preference order. */
40
+ baseUrls;
41
+ active = 0;
26
42
  /** Key ID sent in X-Admin-Key-Id. */
27
43
  keyId;
28
44
  signer;
@@ -31,7 +47,14 @@ class HttpTransport {
31
47
  headers;
32
48
  _fetch;
33
49
  constructor(options) {
34
- this.baseUrl = options.baseUrl.replace(/\/$/, '');
50
+ const urls = options.baseUrls ?? (options.baseUrl !== undefined ? [options.baseUrl] : []);
51
+ if (urls.length === 0 || urls.some(u => typeof u !== 'string' || !/^https?:\/\//.test(u))) {
52
+ throw new Error('baseUrl or baseUrls must give at least one http(s) URL');
53
+ }
54
+ if (options.baseUrl !== undefined && options.baseUrls !== undefined) {
55
+ throw new Error('pass either baseUrl or baseUrls, not both');
56
+ }
57
+ this.baseUrls = Object.freeze(urls.map(u => u.replace(/\/$/, '')));
35
58
  this.signer = options.signer
36
59
  ?? (options.signingKeyBase64
37
60
  ? (0, auth_js_1.seedSigner)(options.signingKeyBase64, options.keyId ?? 'operator-local')
@@ -46,6 +69,15 @@ class HttpTransport {
46
69
  this.headers = options.headers ?? {};
47
70
  this._fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
48
71
  }
72
+ /** The endpoint requests currently go to. */
73
+ get baseUrl() {
74
+ return this.baseUrls[this.active];
75
+ }
76
+ /** Move to the next endpoint (no-op with one endpoint). */
77
+ failover() {
78
+ if (this.baseUrls.length > 1)
79
+ this.active = (this.active + 1) % this.baseUrls.length;
80
+ }
49
81
  /** Signed admin POST. Not retried unless `idempotent` is set. */
50
82
  async adminPost(path, body, idempotent = false) {
51
83
  const response = await this._send({ method: 'POST', path, body, admin: true, idempotent });
@@ -73,30 +105,76 @@ class HttpTransport {
73
105
  return this._parse(response);
74
106
  }
75
107
  async _send(spec) {
76
- const attempts = spec.idempotent ? this.retry.attempts : 0;
77
- for (let attempt = 0;; attempt++) {
108
+ // Idempotent requests may try every other endpoint once, plus the
109
+ // configured retries (with backoff). Non-idempotent requests move to
110
+ // another endpoint only when the connection was never established; after
111
+ // any other failure they are not replayed, and the next request starts at
112
+ // another endpoint.
113
+ const failovers = this.baseUrls.length - 1;
114
+ const retries = spec.idempotent ? this.retry.attempts : 0;
115
+ let failoversLeft = failovers;
116
+ let retriesUsed = 0;
117
+ for (;;) {
118
+ let response;
119
+ let error;
78
120
  try {
79
- const response = await this._once(spec);
80
- if (attempt < attempts && RETRYABLE_STATUS.has(response.status)) {
81
- await this._backoff(attempt);
82
- continue;
83
- }
84
- let body;
85
- try {
86
- body = await response.text();
87
- }
88
- catch {
89
- throw new errors_js_1.NetworkError(`Failed to read response body (${spec.method} ${spec.path})`);
90
- }
91
- return { ok: response.ok, status: response.status, text: async () => body };
121
+ response = await this._attempt(spec);
92
122
  }
93
123
  catch (err) {
94
- if (attempt < attempts && err instanceof errors_js_1.NetworkError) {
95
- await this._backoff(attempt);
96
- continue;
97
- }
98
- throw err;
124
+ if (!(err instanceof errors_js_1.NetworkError))
125
+ throw err;
126
+ error = err;
127
+ }
128
+ const failoverWorthy = error !== undefined || (response !== undefined && FAILOVER_STATUS.has(response.status));
129
+ if (failoverWorthy)
130
+ this.failover();
131
+ // A request that may have reached an instance is replayed elsewhere only
132
+ // if it is idempotent; one that never connected can always move on.
133
+ const mayMove = spec.idempotent || (error instanceof errors_js_1.NetworkError && error.connectFailed);
134
+ if (failoverWorthy && mayMove && failoversLeft > 0) {
135
+ failoversLeft -= 1;
136
+ continue;
99
137
  }
138
+ const retryable = error !== undefined || (response !== undefined && RETRYABLE_STATUS.has(response.status));
139
+ if (retryable && retriesUsed < retries) {
140
+ await this._backoff(retriesUsed);
141
+ retriesUsed += 1;
142
+ failoversLeft = failovers;
143
+ continue;
144
+ }
145
+ if (error !== undefined)
146
+ throw error;
147
+ return response;
148
+ }
149
+ }
150
+ /** One request to the active endpoint, with the body fully read (a dropped body is a NetworkError). */
151
+ async _attempt(spec) {
152
+ const res = await this._once(spec);
153
+ let body;
154
+ try {
155
+ body = await res.text();
156
+ }
157
+ catch {
158
+ throw new errors_js_1.NetworkError(`Failed to read response body (${spec.method} ${spec.path})`);
159
+ }
160
+ return { ok: res.ok, status: res.status, text: async () => body };
161
+ }
162
+ /** Unauthenticated GET against one specific endpoint (no failover), e.g. per-instance readiness. */
163
+ async publicGetAt(baseUrl, path) {
164
+ const url = baseUrl.replace(/\/$/, '') + path;
165
+ let response;
166
+ try {
167
+ response = await this._fetch(url, { method: 'GET', headers: { ...this.headers }, signal: AbortSignal.timeout(this.timeout) });
168
+ }
169
+ catch (err) {
170
+ throw new errors_js_1.NetworkError(`GET ${url} failed: ${err.message}`);
171
+ }
172
+ const text = await response.text();
173
+ try {
174
+ return { status: response.status, body: (0, auth_js_1.parseJson)(text) };
175
+ }
176
+ catch {
177
+ throw new errors_js_1.NetworkError(`Failed to parse response body from ${url} (HTTP ${response.status})`);
100
178
  }
101
179
  }
102
180
  async _once(spec) {
@@ -117,7 +195,9 @@ class HttpTransport {
117
195
  return await this._fetch(this.baseUrl + path, init);
118
196
  }
119
197
  catch (err) {
120
- throw new errors_js_1.NetworkError(`${spec.method} ${spec.path} failed: ${err.message}`);
198
+ const error = new errors_js_1.NetworkError(`${spec.method} ${spec.path} failed: ${err.message}`);
199
+ error.connectFailed = isConnectFailure(err);
200
+ throw error;
121
201
  }
122
202
  }
123
203
  _backoff(attempt) {
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ /**
3
+ * Agent-side data access intents (v0.61): build and sign a DataAccessIntent
4
+ * exactly as the Python reference `create_data_access_intent` does, so the NA
5
+ * and every SDK verify it.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.createDataAccessIntent = createDataAccessIntent;
9
+ const node_crypto_1 = require("node:crypto");
10
+ const auth_js_1 = require("./auth.js");
11
+ const canonical_js_1 = require("./canonical.js");
12
+ /**
13
+ * Build and sign a DataAccessIntent with the agent's signer. The reference
14
+ * implementation uses the agent sovereign id as the signature key id, so pass
15
+ * a signer whose `keyId` is the agent sovereign id for byte-identical output.
16
+ */
17
+ async function createDataAccessIntent(params, signer) {
18
+ const validFor = params.valid_for_seconds ?? 300;
19
+ if (!Number.isSafeInteger(validFor) || validFor <= 0)
20
+ throw new Error('valid_for_seconds must be a positive integer');
21
+ const now = params.now ?? new Date();
22
+ const intent = {
23
+ intent_id: params.intent_id ?? (0, node_crypto_1.randomUUID)(),
24
+ agent_sovereign_id: params.agent_sovereign_id,
25
+ decision_id: params.decision_id,
26
+ declared_sources: params.sources.map(s => ({ ...s, classification_tags: [...(s.classification_tags ?? [])] })),
27
+ declared_access_types: [...params.access_types],
28
+ estimated_volume_bytes: params.estimated_volume_bytes ?? null,
29
+ declared_at: (0, auth_js_1.pythonTimestamp)(now),
30
+ expires_at: (0, auth_js_1.pythonTimestamp)(new Date(now.getTime() + validFor * 1000)),
31
+ signature: null,
32
+ };
33
+ intent.signature = await (0, auth_js_1.signCanonical)((0, canonical_js_1.dataAccessIntentCanonical)(intent), signer);
34
+ return intent;
35
+ }
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.BadRequestError = exports.NetworkError = exports.RateLimitError = exports.NotFoundError = exports.ServiceUnavailableError = exports.ConflictError = exports.ForbiddenError = exports.ValidationError = exports.UnauthorizedError = exports.GenesisMeshError = void 0;
5
5
  exports.fromHttpError = fromHttpError;
6
+ exports.isRetryableConflict = isRetryableConflict;
6
7
  class GenesisMeshError extends Error {
7
8
  code;
8
9
  status;
@@ -70,6 +71,12 @@ class RateLimitError extends GenesisMeshError {
70
71
  }
71
72
  exports.RateLimitError = RateLimitError;
72
73
  class NetworkError extends GenesisMeshError {
74
+ /**
75
+ * True when the connection could not be established (refused, unresolvable,
76
+ * unreachable): the request never reached the NA, so another instance may
77
+ * take it even when it is not idempotent (v0.60).
78
+ */
79
+ connectFailed = false;
73
80
  constructor(message, code = 'network_error') {
74
81
  super(message, code, 0);
75
82
  this.name = 'NetworkError';
@@ -127,3 +134,16 @@ function errorForStatus(status, message, code) {
127
134
  default: return new GenesisMeshError(message, code, status);
128
135
  }
129
136
  }
137
+ const HA_CONFLICT_CODES = new Set([
138
+ 'boundary_policy_activation_conflict',
139
+ 'boundary_policy_version_conflict',
140
+ 'crl_publish_contention',
141
+ 'retention_in_progress',
142
+ ]);
143
+ /**
144
+ * True for a 409 that only means another NA instance won a race the database
145
+ * decided (v0.60). The request changed nothing and can be retried.
146
+ */
147
+ function isRetryableConflict(err) {
148
+ return err instanceof ConflictError && HA_CONFLICT_CODES.has(err.code);
149
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HealthClient = void 0;
4
+ const errors_js_1 = require("./errors.js");
5
+ function normalise(body, ready) {
6
+ return { ...body, status: ready ? 'ready' : 'not_ready', ready };
7
+ }
8
+ /** Liveness, readiness and health of the Network Authority (v0.60). */
9
+ class HealthClient {
10
+ http;
11
+ constructor(http) {
12
+ this.http = http;
13
+ }
14
+ /** Process liveness only (`GET /healthz`). */
15
+ liveness() {
16
+ return this.http.publicGet('/healthz');
17
+ }
18
+ /**
19
+ * Readiness (`GET /readyz`): database writable at the expected schema, key
20
+ * loaded, shared state in HA mode. A not-ready NA (503 `service_not_ready`)
21
+ * is returned as `ready: false` with the failing checks, not thrown. With
22
+ * several `baseUrls`, a not-ready instance is skipped for the next one.
23
+ */
24
+ async readiness() {
25
+ try {
26
+ return normalise(await this.http.publicGet('/readyz'), true);
27
+ }
28
+ catch (err) {
29
+ if (err instanceof errors_js_1.ServiceUnavailableError && err.code === 'service_not_ready') {
30
+ return normalise(err.details, false);
31
+ }
32
+ throw err;
33
+ }
34
+ }
35
+ /** Network, version, boundary policy health and evidence store mode (`GET /health`). */
36
+ health() {
37
+ return this.http.publicGet('/health');
38
+ }
39
+ /** Probe every configured endpoint's `/readyz` directly, for monitoring an HA deployment. */
40
+ async endpoints() {
41
+ return Promise.all(this.http.baseUrls.map(async (baseUrl) => {
42
+ try {
43
+ const { status, body } = await this.http.publicGetAt(baseUrl, '/readyz');
44
+ if (status === 200)
45
+ return { base_url: baseUrl, reachable: true, ready: true, readiness: normalise(body, true) };
46
+ const details = body?.['error']?.details;
47
+ return {
48
+ base_url: baseUrl, reachable: true, ready: false,
49
+ ...(details ? { readiness: normalise(details, false) } : {}),
50
+ error: `HTTP ${status}`,
51
+ };
52
+ }
53
+ catch (err) {
54
+ return { base_url: baseUrl, reachable: false, ready: false, error: err.message };
55
+ }
56
+ }));
57
+ }
58
+ }
59
+ exports.HealthClient = HealthClient;
package/dist/cjs/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.verifyRetentionCheckpoint = exports.verifyRevocationFeedSignature = exports.verifyJustificationSignature = exports.verifyPolicySignature = exports.verifyAttestationSignature = exports.verifyDecisionSignature = exports.verifyBoundaryDecision = exports.MAX_METADATA_BYTES = exports.SecretMaterialError = exports.checkMetadataOnly = exports.ExecutionRecorder = exports.payloadDigest = exports.entryDigest = exports.checkpointCanonical = exports.justificationCanonical = exports.policySetDigest = exports.policyDigest = exports.policyCanonical = exports.revocationFeedCanonical = exports.attestationDigest = exports.attestationCanonical = exports.executionDigest = exports.executionCanonical = exports.decisionCanonical = exports.RESOURCE_CHAIN_FIELDS = exports.buildAdminHeadersWithSigner = exports.buildAdminHeaders = exports.pythonTimestamp = exports.verifyCanonical = exports.signCanonical = exports.seedSigner = exports.publicKeyFromSeed = exports.verifyBytes = exports.signBytes = exports.sha256Hex = exports.parseJson = exports.canonicalDigest = exports.canonicalJson = exports.buildPath = exports.HttpTransport = exports.DataUsageClient = exports.ConsensusClient = exports.DisclosureClient = exports.AttestationClient = exports.EvidenceStoreClient = exports.EvidenceClient = exports.PolicyClient = exports.BoundaryClient = exports.AgreementClient = exports.GenesisMeshClient = void 0;
4
- exports.BadRequestError = exports.NetworkError = exports.ServiceUnavailableError = exports.RateLimitError = exports.ConflictError = exports.NotFoundError = exports.ValidationError = exports.ForbiddenError = exports.UnauthorizedError = exports.GenesisMeshError = exports.DecisionVerificationError = exports.GovernedActionError = exports.reconcileResources = exports.summarizeDecision = exports.governedAction = exports.parseTimestampMicros = exports.parseExportLines = exports.verifyEvidenceEvents = exports.verifyExecutionSignature = void 0;
3
+ exports.verifyAttestationSignature = exports.verifyDecisionSignature = exports.verifyBoundaryDecision = exports.MAX_METADATA_BYTES = exports.SecretMaterialError = exports.checkMetadataOnly = exports.ExecutionRecorder = exports.payloadDigest = exports.entryDigest = exports.checkpointCanonical = exports.justificationCanonical = exports.dataAccessIntentCanonical = exports.dataLicensePolicyCanonical = exports.agreementCanonical = exports.policySetDigest = exports.policyDigest = exports.policyCanonical = exports.revocationFeedCanonical = exports.attestationDigest = exports.attestationCanonical = exports.executionDigest = exports.executionCanonical = exports.decisionCanonical = exports.RESOURCE_CHAIN_FIELDS = exports.buildAdminHeadersWithSigner = exports.buildAdminHeaders = exports.pythonTimestamp = exports.verifyCanonical = exports.signCanonical = exports.seedSigner = exports.publicKeyFromSeed = exports.verifyBytes = exports.signBytes = exports.sha256Hex = exports.parseJson = exports.canonicalDigest = exports.canonicalJson = exports.buildPath = exports.HttpTransport = exports.DataUsageClient = exports.ConsensusClient = exports.DisclosureClient = exports.AttestationClient = exports.HealthClient = exports.EvidenceStoreClient = exports.EvidenceClient = exports.PolicyClient = exports.BoundaryClient = exports.AgreementClient = exports.GenesisMeshClient = void 0;
4
+ exports.BadRequestError = exports.NetworkError = exports.isRetryableConflict = exports.ServiceUnavailableError = exports.RateLimitError = exports.ConflictError = exports.NotFoundError = exports.ValidationError = exports.ForbiddenError = exports.UnauthorizedError = exports.GenesisMeshError = exports.DecisionVerificationError = exports.GovernedActionError = exports.reconcileResources = exports.summarizeDecision = exports.governedAction = exports.createDataAccessIntent = exports.verifyDataAccessIntent = exports.verifyDataLicensePolicySignature = exports.verifyAgreement = exports.parseTimestampMicros = exports.parseExportLines = exports.verifyEvidenceEvents = exports.verifyExecutionSignature = exports.verifyRetentionCheckpoint = exports.verifyRevocationFeedSignature = exports.verifyJustificationSignature = exports.verifyPolicySignature = void 0;
5
5
  const client_js_1 = require("./client.js");
6
6
  const agreement_js_1 = require("./agreement.js");
7
7
  const boundary_js_1 = require("./boundary.js");
@@ -12,6 +12,7 @@ const consensus_js_1 = require("./consensus.js");
12
12
  const data_usage_js_1 = require("./data_usage.js");
13
13
  const policy_js_1 = require("./policy.js");
14
14
  const evidence_store_js_1 = require("./evidence_store.js");
15
+ const health_js_1 = require("./health.js");
15
16
  class GenesisMeshClient {
16
17
  agreement;
17
18
  boundary;
@@ -23,6 +24,8 @@ class GenesisMeshClient {
23
24
  disclosure;
24
25
  consensus;
25
26
  dataUsage;
27
+ /** Liveness, readiness and health (v0.60). */
28
+ health;
26
29
  constructor(options) {
27
30
  const http = new client_js_1.HttpTransport(options);
28
31
  this.agreement = new agreement_js_1.AgreementClient(http);
@@ -30,6 +33,7 @@ class GenesisMeshClient {
30
33
  this.policy = new policy_js_1.PolicyClient(http);
31
34
  this.evidence = new evidence_js_1.EvidenceClient(http);
32
35
  this.evidenceStore = new evidence_store_js_1.EvidenceStoreClient(http);
36
+ this.health = new health_js_1.HealthClient(http);
33
37
  this.attestation = new attestation_js_1.AttestationClient(http);
34
38
  this.disclosure = new disclosure_js_1.DisclosureClient(http);
35
39
  this.consensus = new consensus_js_1.ConsensusClient(http);
@@ -48,6 +52,8 @@ var evidence_js_2 = require("./evidence.js");
48
52
  Object.defineProperty(exports, "EvidenceClient", { enumerable: true, get: function () { return evidence_js_2.EvidenceClient; } });
49
53
  var evidence_store_js_2 = require("./evidence_store.js");
50
54
  Object.defineProperty(exports, "EvidenceStoreClient", { enumerable: true, get: function () { return evidence_store_js_2.EvidenceStoreClient; } });
55
+ var health_js_2 = require("./health.js");
56
+ Object.defineProperty(exports, "HealthClient", { enumerable: true, get: function () { return health_js_2.HealthClient; } });
51
57
  var attestation_js_2 = require("./attestation.js");
52
58
  Object.defineProperty(exports, "AttestationClient", { enumerable: true, get: function () { return attestation_js_2.AttestationClient; } });
53
59
  var disclosure_js_2 = require("./disclosure.js");
@@ -87,6 +93,9 @@ Object.defineProperty(exports, "revocationFeedCanonical", { enumerable: true, ge
87
93
  Object.defineProperty(exports, "policyCanonical", { enumerable: true, get: function () { return canonical_js_1.policyCanonical; } });
88
94
  Object.defineProperty(exports, "policyDigest", { enumerable: true, get: function () { return canonical_js_1.policyDigest; } });
89
95
  Object.defineProperty(exports, "policySetDigest", { enumerable: true, get: function () { return canonical_js_1.policySetDigest; } });
96
+ Object.defineProperty(exports, "agreementCanonical", { enumerable: true, get: function () { return canonical_js_1.agreementCanonical; } });
97
+ Object.defineProperty(exports, "dataLicensePolicyCanonical", { enumerable: true, get: function () { return canonical_js_1.dataLicensePolicyCanonical; } });
98
+ Object.defineProperty(exports, "dataAccessIntentCanonical", { enumerable: true, get: function () { return canonical_js_1.dataAccessIntentCanonical; } });
90
99
  Object.defineProperty(exports, "justificationCanonical", { enumerable: true, get: function () { return canonical_js_1.justificationCanonical; } });
91
100
  Object.defineProperty(exports, "checkpointCanonical", { enumerable: true, get: function () { return canonical_js_1.checkpointCanonical; } });
92
101
  Object.defineProperty(exports, "entryDigest", { enumerable: true, get: function () { return canonical_js_1.entryDigest; } });
@@ -110,6 +119,12 @@ Object.defineProperty(exports, "verifyExecutionSignature", { enumerable: true, g
110
119
  Object.defineProperty(exports, "verifyEvidenceEvents", { enumerable: true, get: function () { return verify_js_1.verifyEvidenceEvents; } });
111
120
  Object.defineProperty(exports, "parseExportLines", { enumerable: true, get: function () { return verify_js_1.parseExportLines; } });
112
121
  Object.defineProperty(exports, "parseTimestampMicros", { enumerable: true, get: function () { return verify_js_1.parseTimestampMicros; } });
122
+ Object.defineProperty(exports, "verifyAgreement", { enumerable: true, get: function () { return verify_js_1.verifyAgreement; } });
123
+ Object.defineProperty(exports, "verifyDataLicensePolicySignature", { enumerable: true, get: function () { return verify_js_1.verifyDataLicensePolicySignature; } });
124
+ Object.defineProperty(exports, "verifyDataAccessIntent", { enumerable: true, get: function () { return verify_js_1.verifyDataAccessIntent; } });
125
+ // Data access intents (v0.61)
126
+ var data_intent_js_1 = require("./data_intent.js");
127
+ Object.defineProperty(exports, "createDataAccessIntent", { enumerable: true, get: function () { return data_intent_js_1.createDataAccessIntent; } });
113
128
  // Governed lifecycles
114
129
  var governance_js_1 = require("./governance.js");
115
130
  Object.defineProperty(exports, "governedAction", { enumerable: true, get: function () { return governance_js_1.governedAction; } });
@@ -127,5 +142,6 @@ Object.defineProperty(exports, "NotFoundError", { enumerable: true, get: functio
127
142
  Object.defineProperty(exports, "ConflictError", { enumerable: true, get: function () { return errors_js_1.ConflictError; } });
128
143
  Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return errors_js_1.RateLimitError; } });
129
144
  Object.defineProperty(exports, "ServiceUnavailableError", { enumerable: true, get: function () { return errors_js_1.ServiceUnavailableError; } });
145
+ Object.defineProperty(exports, "isRetryableConflict", { enumerable: true, get: function () { return errors_js_1.isRetryableConflict; } });
130
146
  Object.defineProperty(exports, "NetworkError", { enumerable: true, get: function () { return errors_js_1.NetworkError; } });
131
147
  Object.defineProperty(exports, "BadRequestError", { enumerable: true, get: function () { return errors_js_1.BadRequestError; } });
@@ -11,6 +11,9 @@ exports.verifyExecutionSignature = verifyExecutionSignature;
11
11
  exports.verifyBoundaryDecision = verifyBoundaryDecision;
12
12
  exports.parseExportLines = parseExportLines;
13
13
  exports.verifyEvidenceEvents = verifyEvidenceEvents;
14
+ exports.verifyAgreement = verifyAgreement;
15
+ exports.verifyDataLicensePolicySignature = verifyDataLicensePolicySignature;
16
+ exports.verifyDataAccessIntent = verifyDataAccessIntent;
14
17
  const validation_js_1 = require("./validation.js");
15
18
  /**
16
19
  * Offline verification of NA-signed artifacts and evidence exports.
@@ -297,3 +300,71 @@ function verifyEvidenceEvents(events, options) {
297
300
  }
298
301
  return result;
299
302
  }
303
+ /**
304
+ * Verify an AgreementRecord offline: at least one signature by an offerer key
305
+ * and one by a responder key over the shared canonical body, and the graph
306
+ * digest when `expectedGraphDigest` is given. Port of `verify_agreement`.
307
+ */
308
+ function verifyAgreement(record, offererPublicKeys, responderPublicKeys, expectedGraphDigest) {
309
+ const result = (accepted, reason) => ({ accepted, reason, agreement_id: record.agreement_id });
310
+ const signatures = record.signatures ?? [];
311
+ if (signatures.length === 0)
312
+ return result(false, 'missing_offerer_signature');
313
+ const canonical = (0, canonical_js_1.agreementCanonical)(record);
314
+ if (!anySigned(canonical, signatures, offererPublicKeys))
315
+ return result(false, 'invalid_offerer_signature');
316
+ if (!anySigned(canonical, signatures, responderPublicKeys)) {
317
+ return result(false, signatures.length < 2 ? 'missing_responder_signature' : 'invalid_responder_signature');
318
+ }
319
+ if (expectedGraphDigest !== undefined && record.graph_digest !== expectedGraphDigest) {
320
+ return result(false, 'graph_digest_mismatch');
321
+ }
322
+ return result(true, 'accepted');
323
+ }
324
+ // ── Data usage (v0.61) ────────────────────────────────────────────────────────
325
+ /** True when the licensor signed the DataLicensePolicy. */
326
+ function verifyDataLicensePolicySignature(policy, licensorPublicKeys) {
327
+ return signedBy((0, canonical_js_1.dataLicensePolicyCanonical)(policy), policy.signature, licensorPublicKeys);
328
+ }
329
+ /**
330
+ * Check an agent-signed DataAccessIntent against a DataLicensePolicy offline:
331
+ * the agent signature, then expiry, licensed sources, prohibited
332
+ * classifications, permitted access types and the volume cap, in the order of
333
+ * the reference `verify_data_access_intent`.
334
+ */
335
+ function verifyDataAccessIntent(intent, policy, agentPublicKeys, at = new Date()) {
336
+ const fail = (violations) => ({ valid: false, violation_reason: violations[0].violation_type, violations });
337
+ if (!intent.signature)
338
+ return fail([{ violation_type: 'intent_exceeds_license', detail: 'Missing intent signature' }]);
339
+ if (!signedBy((0, canonical_js_1.dataAccessIntentCanonical)(intent), intent.signature, agentPublicKeys)) {
340
+ return fail([{ violation_type: 'intent_exceeds_license', detail: 'Invalid intent signature' }]);
341
+ }
342
+ const now = micros(at);
343
+ const violations = [];
344
+ const add = (violation_type, detail) => violations.push({ violation_type, detail });
345
+ if (now > micros(intent.expires_at))
346
+ add('intent_expired', `Intent expired at ${intent.expires_at}`);
347
+ if (now > micros(policy.valid_until) || now < micros(policy.valid_from))
348
+ add('policy_expired', 'Policy not valid at verification time');
349
+ const allowed = new Set(policy.allowed_source_ids);
350
+ const prohibited = new Set(policy.prohibited_classification_tags);
351
+ for (const source of intent.declared_sources) {
352
+ if (!allowed.has(source.source_id))
353
+ add('source_not_licensed', `Source '${source.source_id}' not in allowed_source_ids`);
354
+ const overlap = source.classification_tags.filter(t => prohibited.has(t)).sort(auth_js_1.compareCodePoints);
355
+ if (overlap.length > 0) {
356
+ add('prohibited_classification', `Source '${source.source_id}' has prohibited tags: ${overlap.join(', ')}`);
357
+ }
358
+ }
359
+ const allowedTypes = new Set(policy.allowed_access_types.length > 0 ? policy.allowed_access_types : ['read']);
360
+ for (const accessType of intent.declared_access_types) {
361
+ if (!allowedTypes.has(accessType))
362
+ add('access_type_not_permitted', `Access type '${accessType}' not in allowed_access_types`);
363
+ }
364
+ const cap = policy.max_volume_bytes_per_session;
365
+ const volume = intent.estimated_volume_bytes;
366
+ if (cap !== null && cap !== undefined && volume !== null && volume !== undefined && volume > cap) {
367
+ add('volume_cap_exceeded', `Volume ${volume} > max ${cap}`);
368
+ }
369
+ return violations.length > 0 ? fail(violations) : { valid: true, violation_reason: null, violations: [] };
370
+ }
package/dist/esm/auth.js CHANGED
@@ -58,12 +58,17 @@ function pythonNumber(value, pythonFloat = false) {
58
58
  */
59
59
  const PYTHON_FLOATS = new WeakMap();
60
60
  /**
61
- * JSON.parse that remembers integral-valued floats so canonicalJson reproduces
62
- * them. Use it for any NA JSON that will be verified or digested. Requires
63
- * JSON.parse source text access (Node.js 22+).
61
+ * JSON.parse that keeps what Python would sign: integral-valued floats are
62
+ * remembered so canonicalJson reproduces them (`90.0`), and integer literals
63
+ * beyond Number.MAX_SAFE_INTEGER become `bigint` so no digit is lost. Use it
64
+ * for any NA JSON that will be verified or digested. Requires JSON.parse source
65
+ * text access (Node.js 22+).
64
66
  */
65
67
  export function parseJson(text) {
66
68
  return JSON.parse(text, function (key, value, context) {
69
+ if (typeof value === 'number' && !Number.isSafeInteger(value) && context?.source && /^-?\d+$/.test(context.source)) {
70
+ return BigInt(context.source);
71
+ }
67
72
  if (typeof value === 'number' && Number.isInteger(value) && context?.source && /[.eE]/.test(context.source)) {
68
73
  let keys = PYTHON_FLOATS.get(this);
69
74
  if (!keys)
@@ -96,6 +101,8 @@ export function canonicalJson(value) {
96
101
  return asciiString(value);
97
102
  if (typeof value === 'number')
98
103
  return pythonNumber(value);
104
+ if (typeof value === 'bigint')
105
+ return value.toString();
99
106
  if (typeof value === 'boolean')
100
107
  return value ? 'true' : 'false';
101
108
  if (Array.isArray(value))