@nomosprotocol/sdk 2.3.1 → 2.5.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/README.md +34 -0
- package/dist/index.cjs +49 -10
- package/dist/index.d.cts +56 -2
- package/dist/index.d.ts +56 -2
- package/dist/index.js +49 -10
- package/package.json +12 -3
package/README.md
CHANGED
|
@@ -161,10 +161,44 @@ const verdict = await nomos.can({
|
|
|
161
161
|
});
|
|
162
162
|
```
|
|
163
163
|
|
|
164
|
+
Name your agent so the transcript records who asked, and mark test traffic so it stays out of the authority's public history and counts:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
const verdict = await nomos.can(
|
|
168
|
+
{ authority: "eu-ai-act", ai_categorises_biometric_data_for_sensitive_attributes: true },
|
|
169
|
+
{ caller: { agent_id: "acme-hiring-agent", purpose: process.env.CI ? "test" : "production" } },
|
|
170
|
+
);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The name is declared, not verified, and is shown as such. Don't know which authority applies? `GET /api/exchange/find?q=<what you're about to do>`, or the MCP tool `find_authority`.
|
|
174
|
+
|
|
164
175
|
If a paid authority has not been purchased, NOMOS throws `NomosPaymentRequiredError` with the price and purchase URL attached.
|
|
165
176
|
|
|
166
177
|
NOMOS never auto-charges on your behalf.
|
|
167
178
|
|
|
179
|
+
### Escalations that reach a person
|
|
180
|
+
|
|
181
|
+
Click **Use this law** on an authority's Exchange page (signed in). From then on, when that law
|
|
182
|
+
answers ESCALATED to a call made with your API key, your approvers get an email and approve on a
|
|
183
|
+
page. An API key can never approve, so an agent can't approve itself. Your code can wait for the answer:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
import { Nomos } from "@nomosprotocol/sdk";
|
|
187
|
+
|
|
188
|
+
const client = new Nomos(process.env.NOMOS_API_KEY!);
|
|
189
|
+
const r = await client.can(
|
|
190
|
+
{ authority: "data-protection-law-difc-law-no-5-of-2020", days_since_access_request: 45 },
|
|
191
|
+
{ awaitApproval: { timeoutMs: 10 * 60_000, onApproval: (a) => console.log("waiting on", a.url) } },
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
if (r.allowed) proceed(); // AUTHORIZED, or a person approved this exact call (spent once)
|
|
195
|
+
else console.log(r.verdict, r.approval?.status); // DENIED, or rejected / timed_out
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The approval is bound to the authority, the action and the facts: different facts need a new one.
|
|
199
|
+
When the law gets a new version, you get an email with what changed. Turn either email off on the
|
|
200
|
+
same page.
|
|
201
|
+
|
|
168
202
|
### 2. Your organization's authority
|
|
169
203
|
|
|
170
204
|
Use an authenticated client for artifacts belonging to your organization:
|
package/dist/index.cjs
CHANGED
|
@@ -191,7 +191,7 @@ function isRetryable(status) {
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
// src/http-client.ts
|
|
194
|
-
var SDK_VERSION = "2.
|
|
194
|
+
var SDK_VERSION = "2.5.0";
|
|
195
195
|
var HttpClient = class {
|
|
196
196
|
constructor(opts) {
|
|
197
197
|
this.opts = opts;
|
|
@@ -215,6 +215,7 @@ var HttpClient = class {
|
|
|
215
215
|
"User-Agent": `nomos-node/${SDK_VERSION}`,
|
|
216
216
|
"NOMOS-SDK-Version": SDK_VERSION
|
|
217
217
|
};
|
|
218
|
+
if (reqOpts?.headers) Object.assign(headers, reqOpts.headers);
|
|
218
219
|
if (reqOpts?.idempotencyKey) {
|
|
219
220
|
headers["Idempotency-Key"] = reqOpts.idempotencyKey;
|
|
220
221
|
}
|
|
@@ -720,9 +721,18 @@ function normalizeAuthorityQueryResult(data, authority) {
|
|
|
720
721
|
// NOMOS-SPEC-006 — present only when the issuer has actually revoked this exact sealed
|
|
721
722
|
// version; omitted (not false) when revocation doesn't structurally apply here, so "checked
|
|
722
723
|
// and clean" and "couldn't check" never look the same to a caller.
|
|
723
|
-
...data.revoked === true ? { revoked: true, revocation_reason: data.revocation_reason } : {}
|
|
724
|
+
...data.revoked === true ? { revoked: true, revocation_reason: data.revocation_reason } : {},
|
|
725
|
+
...data.signature ? { signature: data.signature } : {},
|
|
726
|
+
...data.evaluator_version ? { evaluator_version: data.evaluator_version } : {}
|
|
724
727
|
};
|
|
725
728
|
}
|
|
729
|
+
function callerHeaders(c) {
|
|
730
|
+
const h = {};
|
|
731
|
+
if (c?.agent_id) h["X-Nomos-Agent"] = c.agent_id;
|
|
732
|
+
if (c?.acts_for) h["X-Nomos-Acts-For"] = c.acts_for;
|
|
733
|
+
if (c?.purpose) h["X-Nomos-Purpose"] = c.purpose;
|
|
734
|
+
return h;
|
|
735
|
+
}
|
|
726
736
|
async function canViaExchange(authority, facts, action, opts) {
|
|
727
737
|
const base = (opts?.baseUrl ?? DEFAULT_EXCHANGE_BASE).replace(/\/$/, "");
|
|
728
738
|
const url = `${base}/api/v1/can`;
|
|
@@ -730,7 +740,7 @@ async function canViaExchange(authority, facts, action, opts) {
|
|
|
730
740
|
try {
|
|
731
741
|
res = await fetch(url, {
|
|
732
742
|
method: "POST",
|
|
733
|
-
headers: { "Content-Type": "application/json" },
|
|
743
|
+
headers: { "Content-Type": "application/json", "User-Agent": `nomos-node/${SDK_VERSION}`, ...callerHeaders(opts?.caller) },
|
|
734
744
|
body: JSON.stringify({ artifact: authority, action, ...facts }),
|
|
735
745
|
signal: opts?.signal
|
|
736
746
|
});
|
|
@@ -743,9 +753,34 @@ async function canViaExchange(authority, facts, action, opts) {
|
|
|
743
753
|
if (!res.ok) mapHttpError(res.status, data, requestId);
|
|
744
754
|
return normalizeAuthorityQueryResult(data, authority);
|
|
745
755
|
}
|
|
746
|
-
async function canViaExchangeAuthenticated(http, authority, facts, action) {
|
|
747
|
-
const
|
|
748
|
-
|
|
756
|
+
async function canViaExchangeAuthenticated(http, authority, facts, action, opts) {
|
|
757
|
+
const ask = async () => (await http.post("/api/v1/can", { artifact: authority, action, ...facts }, { headers: callerHeaders(opts?.caller) })).data;
|
|
758
|
+
const data = await ask();
|
|
759
|
+
const result = normalizeAuthorityQueryResult(data, authority);
|
|
760
|
+
const approval = data?.approval;
|
|
761
|
+
if (!approval) return result;
|
|
762
|
+
result.approval = { id: approval.id, url: approval.url, status: approval.status };
|
|
763
|
+
if (!opts?.awaitApproval || result.verdict !== "ESCALATED") return result;
|
|
764
|
+
return awaitApproval(http, result, typeof opts.awaitApproval === "object" ? opts.awaitApproval : {});
|
|
765
|
+
}
|
|
766
|
+
var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
767
|
+
async function awaitApproval(http, result, o) {
|
|
768
|
+
const a = result.approval;
|
|
769
|
+
o.onApproval?.({ id: a.id, url: a.url });
|
|
770
|
+
const deadline = Date.now() + (o.timeoutMs ?? 6e5);
|
|
771
|
+
let status = a.status;
|
|
772
|
+
while (status === "pending" && Date.now() < deadline) {
|
|
773
|
+
await sleep2(Math.max(500, o.pollMs ?? 3e3));
|
|
774
|
+
status = (await http.get(`/api/v1/approvals/${a.id}`)).data?.status ?? "pending";
|
|
775
|
+
}
|
|
776
|
+
if (status === "pending") return { ...result, approval: { ...a, status: "timed_out" } };
|
|
777
|
+
if (status !== "approved") return { ...result, approval: { ...a, status: "rejected" } };
|
|
778
|
+
try {
|
|
779
|
+
await http.post(`/api/v1/approvals/${a.id}/consume`, {});
|
|
780
|
+
} catch {
|
|
781
|
+
return { ...result, approval: { ...a, status: "not_usable" } };
|
|
782
|
+
}
|
|
783
|
+
return { ...result, allowed: true, approval: { ...a, status: "approved" } };
|
|
749
784
|
}
|
|
750
785
|
async function canViaArtifact(http, artifactId, facts, action) {
|
|
751
786
|
const isPublic = artifactId.startsWith("pub_");
|
|
@@ -841,7 +876,6 @@ async function can(params, opts) {
|
|
|
841
876
|
return canViaExchange(params.authority, facts, action, opts);
|
|
842
877
|
}
|
|
843
878
|
async function canAuthenticated(http, params, opts) {
|
|
844
|
-
void opts;
|
|
845
879
|
const { facts, action } = splitFacts(params);
|
|
846
880
|
if (params.artifact !== void 0) {
|
|
847
881
|
if (!params.key_certs || !params.root_public_key_pem) {
|
|
@@ -850,7 +884,7 @@ async function canAuthenticated(http, params, opts) {
|
|
|
850
884
|
return canViaChain(params.artifact, params.key_certs, params.root_public_key_pem, facts, action, opts);
|
|
851
885
|
}
|
|
852
886
|
if (params.artifact_id) return canViaArtifact(http, params.artifact_id, facts, action);
|
|
853
|
-
if (params.authority) return canViaExchangeAuthenticated(http, params.authority, facts, action);
|
|
887
|
+
if (params.authority) return canViaExchangeAuthenticated(http, params.authority, facts, action, opts);
|
|
854
888
|
throw new Error("can(): `authority` (a public Exchange artifact), `artifact_id` (your own artifact), or `artifact` (chain-of-trust) is required.");
|
|
855
889
|
}
|
|
856
890
|
var nomos = { can };
|
|
@@ -5051,9 +5085,14 @@ var TIE_ORDERS = {
|
|
|
5051
5085
|
function evaluate(artifact, facts) {
|
|
5052
5086
|
const logic = artifact.logic;
|
|
5053
5087
|
const res = logic.resolution ?? {};
|
|
5054
|
-
|
|
5055
|
-
if (!Array.isArray(decisions))
|
|
5088
|
+
if (!Array.isArray(logic.decisions))
|
|
5056
5089
|
throw new Error("logic.decisions missing");
|
|
5090
|
+
const declared = new Set(Object.keys(artifact.execution?.actions ?? {}));
|
|
5091
|
+
const isObligation = (d) => {
|
|
5092
|
+
const t = d?.then?.[0];
|
|
5093
|
+
return t?.type === "emit" || t?.type === "action" && declared.has(t?.action_name);
|
|
5094
|
+
};
|
|
5095
|
+
const decisions = logic.decisions.filter((d) => !isObligation(d));
|
|
5057
5096
|
const order = TIE_ORDERS[res.tie_breaker ?? "deny_wins"];
|
|
5058
5097
|
if (!order)
|
|
5059
5098
|
throw new Error("unknown tie_breaker");
|
package/dist/index.d.cts
CHANGED
|
@@ -88,10 +88,50 @@ interface CanOptions {
|
|
|
88
88
|
baseUrl?: string;
|
|
89
89
|
/** AbortSignal to cancel the request. */
|
|
90
90
|
signal?: AbortSignal;
|
|
91
|
+
/** Who is asking, recorded on the transcript (sent as X-Nomos-Agent / -Acts-For / -Purpose).
|
|
92
|
+
* `purpose: 'test'` queries are answered and sealed but kept out of the authority's public
|
|
93
|
+
* history and counts — set it from tests and CI. Exchange authorities only. */
|
|
94
|
+
caller?: CanCaller;
|
|
95
|
+
/**
|
|
96
|
+
* Wait for a person when the answer is ESCALATED (client.can() with an API key, on a law you
|
|
97
|
+
* follow with "Use this law" on the Exchange). Your approvers get an email; when one approves this
|
|
98
|
+
* exact action and these facts, the approval is spent once and the result comes back with
|
|
99
|
+
* `allowed: true`. A rejection or the timeout returns `allowed: false`. `true` waits up to 10 minutes.
|
|
100
|
+
*/
|
|
101
|
+
awaitApproval?: boolean | AwaitApprovalOptions;
|
|
102
|
+
}
|
|
103
|
+
interface AwaitApprovalOptions {
|
|
104
|
+
/** How long to wait for an answer, ms. Default 600000 (10 minutes). */
|
|
105
|
+
timeoutMs?: number;
|
|
106
|
+
/** How often to check, ms. Default 3000. */
|
|
107
|
+
pollMs?: number;
|
|
108
|
+
/** Called once when the request is made, with the page your approver answers on. */
|
|
109
|
+
onApproval?: (approval: {
|
|
110
|
+
id: number;
|
|
111
|
+
url: string;
|
|
112
|
+
}) => void;
|
|
113
|
+
}
|
|
114
|
+
/** A person's decision on an ESCALATED answer (see CanOptions.awaitApproval). */
|
|
115
|
+
interface CanApproval {
|
|
116
|
+
id: number;
|
|
117
|
+
/** The page a signed-in approver answers on. */
|
|
118
|
+
url: string;
|
|
119
|
+
/** pending until answered; approved means spent for this call; timed_out means nobody answered in time. */
|
|
120
|
+
status: 'pending' | 'approved' | 'rejected' | 'timed_out' | 'not_usable';
|
|
121
|
+
}
|
|
122
|
+
interface CanCaller {
|
|
123
|
+
/** A stable name for your agent, e.g. "acme-credit-agent". */
|
|
124
|
+
agent_id?: string;
|
|
125
|
+
/** The organisation it acts for. Ignored when the request carries an API key (the key's account is used). */
|
|
126
|
+
acts_for?: string;
|
|
127
|
+
purpose?: 'production' | 'test';
|
|
91
128
|
}
|
|
92
129
|
interface CanResult {
|
|
93
|
-
/** True
|
|
130
|
+
/** True for an AUTHORIZED verdict, or for an ESCALATED one a person approved for this exact call
|
|
131
|
+
* (awaitApproval; the approval is then spent). Otherwise `false`. */
|
|
94
132
|
allowed: boolean;
|
|
133
|
+
/** Present when an ESCALATED answer became an approval request (a law you follow, with a key). */
|
|
134
|
+
approval?: CanApproval;
|
|
95
135
|
/** The authority's own verdict, verbatim, in ONE consistent vocabulary regardless of which
|
|
96
136
|
* rail answered it — the public Exchange or your own authenticated artifact. */
|
|
97
137
|
verdict: CanVerdict;
|
|
@@ -156,6 +196,18 @@ interface CanResult {
|
|
|
156
196
|
* `"unchecked"` (neither — e.g. the pinned root, which nothing else can vouch for). Omitted
|
|
157
197
|
* (not a guess) on the Exchange/private-artifact rails, where this concept doesn't apply. */
|
|
158
198
|
revocation_checked?: 'live' | 'staple' | 'unchecked';
|
|
199
|
+
/** Exchange rail: the platform's Ed25519 signature over what was decided, made when the query was
|
|
200
|
+
* answered (docs/design-notes/signed-query-records.md). Check the full record offline with
|
|
201
|
+
* `node verify-nomos.mjs <record.json> --pubkey <key.pem>` (record from `url`). Absent on records
|
|
202
|
+
* answered by a deployment with no signing key. */
|
|
203
|
+
signature?: {
|
|
204
|
+
algorithm: 'Ed25519';
|
|
205
|
+
kid: string;
|
|
206
|
+
value: string;
|
|
207
|
+
statement: Record<string, unknown>;
|
|
208
|
+
};
|
|
209
|
+
/** Exchange rail: the evaluator version that decided this, as named in the signed statement. */
|
|
210
|
+
evaluator_version?: string;
|
|
159
211
|
}
|
|
160
212
|
interface ConservedStateInfo {
|
|
161
213
|
artifact_id: string;
|
|
@@ -411,6 +463,8 @@ interface RequestOptions {
|
|
|
411
463
|
idempotencyKey?: string;
|
|
412
464
|
/** Per-request API key — useful for multi-tenant platforms */
|
|
413
465
|
apiKey?: string;
|
|
466
|
+
/** Extra request headers. */
|
|
467
|
+
headers?: Record<string, string>;
|
|
414
468
|
}
|
|
415
469
|
interface NomosRawResponse {
|
|
416
470
|
/** x-request-id header value — include in support tickets */
|
|
@@ -1622,4 +1676,4 @@ declare function approver(o: ApproverOptions): {
|
|
|
1622
1676
|
pending: Map<string, Pending>;
|
|
1623
1677
|
};
|
|
1624
1678
|
|
|
1625
|
-
export { ACT_MEDIA_TYPE, type AcceptOptions, type AcceptStore, type AcceptTransaction, type AcceptedAuthority, type KeyCertificate as ActKeyCertificate, type ActOptions, type ActResult, type ActStatus, type Action, type Answer, type ApproverOptions, type Artifact, type ArtifactList, type ArtifactSchema, type ArtifactSchemaField, type AttestationInfo, type BindingProposal, type BindingSuggestion, type BuildActOptions, type CanOptions, type CanParams, type CanResult, type CanVerdict, type CertificateRegistry, type ChainVerdictDecision, type ConservedStateInfo, type ConservedStateReceipt, type Contradiction, type DetectContradictionsParams, type DetectContradictionsResult, type EnrolmentClaims, type FactBinding, type GenerateGovernanceParams, type GenerateGovernanceResult, type IssueOptions, type IssuerDocument, type IssuerOptions, type KeyCertificate$1 as KeyCertificate, type ListArtifactsParams, MAX_PRESENTED, Nomos, NomosAPIError, NomosActError, NomosAuthenticationError, NomosAuthorizationError, type NomosConfig, NomosDiscoveryError, NomosError, type NomosErrorCode, NomosIssuerNotTrustedError, NomosNetworkError, NomosNotFoundError, NomosPaymentRequiredError, NomosRateLimitError, type NomosRawResponse, NomosValidationError, type NomosWebhookEvent, type NomosWebhookEventType, NomosWebhookSignatureError, type PasskeyApproverOptions, type PasskeyCredential, type PasskeyStore, type PendingApproval, type PgClient, type PgPool, type PolicyField, type PostgresStoreOptions, type Receipt, RegistryError, type RegistryStore, type RelyingPartyDocument, type RequestOptions, type Reservation, type ReserveByActParams, type ReserveByQueryParams, type ReserveParams, type RevocationSourceOptions, RevocationUnavailable, type SignatureSuite, type Signer, type ToolBindings, type ToolDescription, type UnresolvedFact, type Verdict, type VerifyChainOptions, type VerifyChainParams, type VerifyChainResult, type VerifyDecisionParams, type VerifyDecisionResult, WELL_KNOWN, type WithLastResponse, type WitnessDocument, type WitnessOptions, accept, act, applyToolBindings, approver, buildAct, can, certificateRegistry, certify, describeSuggestion, fetchDocument, generateSigner, issuer, kidOf, localSigner, memoryPasskeyStore, memoryRegistryStore, memoryStore, nomos, originOf, parseToolBindings, passkeyApprover, postgresPasskeyStore, postgresRegistryStore, postgresStore, presentedCertificates, readAttestation, reliedKids, revocationListCertificates, revocationSource, seal, seededSigner, signConsent, signEnrolmentToken, signInForce, signRevocation, signRevocationList, signTestimony, statusFor, suggestToolBindings, verifyChain, verifyEnrolmentToken, witness };
|
|
1679
|
+
export { ACT_MEDIA_TYPE, type AcceptOptions, type AcceptStore, type AcceptTransaction, type AcceptedAuthority, type KeyCertificate as ActKeyCertificate, type ActOptions, type ActResult, type ActStatus, type Action, type Answer, type ApproverOptions, type Artifact, type ArtifactList, type ArtifactSchema, type ArtifactSchemaField, type AttestationInfo, type AwaitApprovalOptions, type BindingProposal, type BindingSuggestion, type BuildActOptions, type CanApproval, type CanCaller, type CanOptions, type CanParams, type CanResult, type CanVerdict, type CertificateRegistry, type ChainVerdictDecision, type ConservedStateInfo, type ConservedStateReceipt, type Contradiction, type DetectContradictionsParams, type DetectContradictionsResult, type EnrolmentClaims, type FactBinding, type GenerateGovernanceParams, type GenerateGovernanceResult, type IssueOptions, type IssuerDocument, type IssuerOptions, type KeyCertificate$1 as KeyCertificate, type ListArtifactsParams, MAX_PRESENTED, Nomos, NomosAPIError, NomosActError, NomosAuthenticationError, NomosAuthorizationError, type NomosConfig, NomosDiscoveryError, NomosError, type NomosErrorCode, NomosIssuerNotTrustedError, NomosNetworkError, NomosNotFoundError, NomosPaymentRequiredError, NomosRateLimitError, type NomosRawResponse, NomosValidationError, type NomosWebhookEvent, type NomosWebhookEventType, NomosWebhookSignatureError, type PasskeyApproverOptions, type PasskeyCredential, type PasskeyStore, type PendingApproval, type PgClient, type PgPool, type PolicyField, type PostgresStoreOptions, type Receipt, RegistryError, type RegistryStore, type RelyingPartyDocument, type RequestOptions, type Reservation, type ReserveByActParams, type ReserveByQueryParams, type ReserveParams, type RevocationSourceOptions, RevocationUnavailable, type SignatureSuite, type Signer, type ToolBindings, type ToolDescription, type UnresolvedFact, type Verdict, type VerifyChainOptions, type VerifyChainParams, type VerifyChainResult, type VerifyDecisionParams, type VerifyDecisionResult, WELL_KNOWN, type WithLastResponse, type WitnessDocument, type WitnessOptions, accept, act, applyToolBindings, approver, buildAct, can, certificateRegistry, certify, describeSuggestion, fetchDocument, generateSigner, issuer, kidOf, localSigner, memoryPasskeyStore, memoryRegistryStore, memoryStore, nomos, originOf, parseToolBindings, passkeyApprover, postgresPasskeyStore, postgresRegistryStore, postgresStore, presentedCertificates, readAttestation, reliedKids, revocationListCertificates, revocationSource, seal, seededSigner, signConsent, signEnrolmentToken, signInForce, signRevocation, signRevocationList, signTestimony, statusFor, suggestToolBindings, verifyChain, verifyEnrolmentToken, witness };
|
package/dist/index.d.ts
CHANGED
|
@@ -88,10 +88,50 @@ interface CanOptions {
|
|
|
88
88
|
baseUrl?: string;
|
|
89
89
|
/** AbortSignal to cancel the request. */
|
|
90
90
|
signal?: AbortSignal;
|
|
91
|
+
/** Who is asking, recorded on the transcript (sent as X-Nomos-Agent / -Acts-For / -Purpose).
|
|
92
|
+
* `purpose: 'test'` queries are answered and sealed but kept out of the authority's public
|
|
93
|
+
* history and counts — set it from tests and CI. Exchange authorities only. */
|
|
94
|
+
caller?: CanCaller;
|
|
95
|
+
/**
|
|
96
|
+
* Wait for a person when the answer is ESCALATED (client.can() with an API key, on a law you
|
|
97
|
+
* follow with "Use this law" on the Exchange). Your approvers get an email; when one approves this
|
|
98
|
+
* exact action and these facts, the approval is spent once and the result comes back with
|
|
99
|
+
* `allowed: true`. A rejection or the timeout returns `allowed: false`. `true` waits up to 10 minutes.
|
|
100
|
+
*/
|
|
101
|
+
awaitApproval?: boolean | AwaitApprovalOptions;
|
|
102
|
+
}
|
|
103
|
+
interface AwaitApprovalOptions {
|
|
104
|
+
/** How long to wait for an answer, ms. Default 600000 (10 minutes). */
|
|
105
|
+
timeoutMs?: number;
|
|
106
|
+
/** How often to check, ms. Default 3000. */
|
|
107
|
+
pollMs?: number;
|
|
108
|
+
/** Called once when the request is made, with the page your approver answers on. */
|
|
109
|
+
onApproval?: (approval: {
|
|
110
|
+
id: number;
|
|
111
|
+
url: string;
|
|
112
|
+
}) => void;
|
|
113
|
+
}
|
|
114
|
+
/** A person's decision on an ESCALATED answer (see CanOptions.awaitApproval). */
|
|
115
|
+
interface CanApproval {
|
|
116
|
+
id: number;
|
|
117
|
+
/** The page a signed-in approver answers on. */
|
|
118
|
+
url: string;
|
|
119
|
+
/** pending until answered; approved means spent for this call; timed_out means nobody answered in time. */
|
|
120
|
+
status: 'pending' | 'approved' | 'rejected' | 'timed_out' | 'not_usable';
|
|
121
|
+
}
|
|
122
|
+
interface CanCaller {
|
|
123
|
+
/** A stable name for your agent, e.g. "acme-credit-agent". */
|
|
124
|
+
agent_id?: string;
|
|
125
|
+
/** The organisation it acts for. Ignored when the request carries an API key (the key's account is used). */
|
|
126
|
+
acts_for?: string;
|
|
127
|
+
purpose?: 'production' | 'test';
|
|
91
128
|
}
|
|
92
129
|
interface CanResult {
|
|
93
|
-
/** True
|
|
130
|
+
/** True for an AUTHORIZED verdict, or for an ESCALATED one a person approved for this exact call
|
|
131
|
+
* (awaitApproval; the approval is then spent). Otherwise `false`. */
|
|
94
132
|
allowed: boolean;
|
|
133
|
+
/** Present when an ESCALATED answer became an approval request (a law you follow, with a key). */
|
|
134
|
+
approval?: CanApproval;
|
|
95
135
|
/** The authority's own verdict, verbatim, in ONE consistent vocabulary regardless of which
|
|
96
136
|
* rail answered it — the public Exchange or your own authenticated artifact. */
|
|
97
137
|
verdict: CanVerdict;
|
|
@@ -156,6 +196,18 @@ interface CanResult {
|
|
|
156
196
|
* `"unchecked"` (neither — e.g. the pinned root, which nothing else can vouch for). Omitted
|
|
157
197
|
* (not a guess) on the Exchange/private-artifact rails, where this concept doesn't apply. */
|
|
158
198
|
revocation_checked?: 'live' | 'staple' | 'unchecked';
|
|
199
|
+
/** Exchange rail: the platform's Ed25519 signature over what was decided, made when the query was
|
|
200
|
+
* answered (docs/design-notes/signed-query-records.md). Check the full record offline with
|
|
201
|
+
* `node verify-nomos.mjs <record.json> --pubkey <key.pem>` (record from `url`). Absent on records
|
|
202
|
+
* answered by a deployment with no signing key. */
|
|
203
|
+
signature?: {
|
|
204
|
+
algorithm: 'Ed25519';
|
|
205
|
+
kid: string;
|
|
206
|
+
value: string;
|
|
207
|
+
statement: Record<string, unknown>;
|
|
208
|
+
};
|
|
209
|
+
/** Exchange rail: the evaluator version that decided this, as named in the signed statement. */
|
|
210
|
+
evaluator_version?: string;
|
|
159
211
|
}
|
|
160
212
|
interface ConservedStateInfo {
|
|
161
213
|
artifact_id: string;
|
|
@@ -411,6 +463,8 @@ interface RequestOptions {
|
|
|
411
463
|
idempotencyKey?: string;
|
|
412
464
|
/** Per-request API key — useful for multi-tenant platforms */
|
|
413
465
|
apiKey?: string;
|
|
466
|
+
/** Extra request headers. */
|
|
467
|
+
headers?: Record<string, string>;
|
|
414
468
|
}
|
|
415
469
|
interface NomosRawResponse {
|
|
416
470
|
/** x-request-id header value — include in support tickets */
|
|
@@ -1622,4 +1676,4 @@ declare function approver(o: ApproverOptions): {
|
|
|
1622
1676
|
pending: Map<string, Pending>;
|
|
1623
1677
|
};
|
|
1624
1678
|
|
|
1625
|
-
export { ACT_MEDIA_TYPE, type AcceptOptions, type AcceptStore, type AcceptTransaction, type AcceptedAuthority, type KeyCertificate as ActKeyCertificate, type ActOptions, type ActResult, type ActStatus, type Action, type Answer, type ApproverOptions, type Artifact, type ArtifactList, type ArtifactSchema, type ArtifactSchemaField, type AttestationInfo, type BindingProposal, type BindingSuggestion, type BuildActOptions, type CanOptions, type CanParams, type CanResult, type CanVerdict, type CertificateRegistry, type ChainVerdictDecision, type ConservedStateInfo, type ConservedStateReceipt, type Contradiction, type DetectContradictionsParams, type DetectContradictionsResult, type EnrolmentClaims, type FactBinding, type GenerateGovernanceParams, type GenerateGovernanceResult, type IssueOptions, type IssuerDocument, type IssuerOptions, type KeyCertificate$1 as KeyCertificate, type ListArtifactsParams, MAX_PRESENTED, Nomos, NomosAPIError, NomosActError, NomosAuthenticationError, NomosAuthorizationError, type NomosConfig, NomosDiscoveryError, NomosError, type NomosErrorCode, NomosIssuerNotTrustedError, NomosNetworkError, NomosNotFoundError, NomosPaymentRequiredError, NomosRateLimitError, type NomosRawResponse, NomosValidationError, type NomosWebhookEvent, type NomosWebhookEventType, NomosWebhookSignatureError, type PasskeyApproverOptions, type PasskeyCredential, type PasskeyStore, type PendingApproval, type PgClient, type PgPool, type PolicyField, type PostgresStoreOptions, type Receipt, RegistryError, type RegistryStore, type RelyingPartyDocument, type RequestOptions, type Reservation, type ReserveByActParams, type ReserveByQueryParams, type ReserveParams, type RevocationSourceOptions, RevocationUnavailable, type SignatureSuite, type Signer, type ToolBindings, type ToolDescription, type UnresolvedFact, type Verdict, type VerifyChainOptions, type VerifyChainParams, type VerifyChainResult, type VerifyDecisionParams, type VerifyDecisionResult, WELL_KNOWN, type WithLastResponse, type WitnessDocument, type WitnessOptions, accept, act, applyToolBindings, approver, buildAct, can, certificateRegistry, certify, describeSuggestion, fetchDocument, generateSigner, issuer, kidOf, localSigner, memoryPasskeyStore, memoryRegistryStore, memoryStore, nomos, originOf, parseToolBindings, passkeyApprover, postgresPasskeyStore, postgresRegistryStore, postgresStore, presentedCertificates, readAttestation, reliedKids, revocationListCertificates, revocationSource, seal, seededSigner, signConsent, signEnrolmentToken, signInForce, signRevocation, signRevocationList, signTestimony, statusFor, suggestToolBindings, verifyChain, verifyEnrolmentToken, witness };
|
|
1679
|
+
export { ACT_MEDIA_TYPE, type AcceptOptions, type AcceptStore, type AcceptTransaction, type AcceptedAuthority, type KeyCertificate as ActKeyCertificate, type ActOptions, type ActResult, type ActStatus, type Action, type Answer, type ApproverOptions, type Artifact, type ArtifactList, type ArtifactSchema, type ArtifactSchemaField, type AttestationInfo, type AwaitApprovalOptions, type BindingProposal, type BindingSuggestion, type BuildActOptions, type CanApproval, type CanCaller, type CanOptions, type CanParams, type CanResult, type CanVerdict, type CertificateRegistry, type ChainVerdictDecision, type ConservedStateInfo, type ConservedStateReceipt, type Contradiction, type DetectContradictionsParams, type DetectContradictionsResult, type EnrolmentClaims, type FactBinding, type GenerateGovernanceParams, type GenerateGovernanceResult, type IssueOptions, type IssuerDocument, type IssuerOptions, type KeyCertificate$1 as KeyCertificate, type ListArtifactsParams, MAX_PRESENTED, Nomos, NomosAPIError, NomosActError, NomosAuthenticationError, NomosAuthorizationError, type NomosConfig, NomosDiscoveryError, NomosError, type NomosErrorCode, NomosIssuerNotTrustedError, NomosNetworkError, NomosNotFoundError, NomosPaymentRequiredError, NomosRateLimitError, type NomosRawResponse, NomosValidationError, type NomosWebhookEvent, type NomosWebhookEventType, NomosWebhookSignatureError, type PasskeyApproverOptions, type PasskeyCredential, type PasskeyStore, type PendingApproval, type PgClient, type PgPool, type PolicyField, type PostgresStoreOptions, type Receipt, RegistryError, type RegistryStore, type RelyingPartyDocument, type RequestOptions, type Reservation, type ReserveByActParams, type ReserveByQueryParams, type ReserveParams, type RevocationSourceOptions, RevocationUnavailable, type SignatureSuite, type Signer, type ToolBindings, type ToolDescription, type UnresolvedFact, type Verdict, type VerifyChainOptions, type VerifyChainParams, type VerifyChainResult, type VerifyDecisionParams, type VerifyDecisionResult, WELL_KNOWN, type WithLastResponse, type WitnessDocument, type WitnessOptions, accept, act, applyToolBindings, approver, buildAct, can, certificateRegistry, certify, describeSuggestion, fetchDocument, generateSigner, issuer, kidOf, localSigner, memoryPasskeyStore, memoryRegistryStore, memoryStore, nomos, originOf, parseToolBindings, passkeyApprover, postgresPasskeyStore, postgresRegistryStore, postgresStore, presentedCertificates, readAttestation, reliedKids, revocationListCertificates, revocationSource, seal, seededSigner, signConsent, signEnrolmentToken, signInForce, signRevocation, signRevocationList, signTestimony, statusFor, suggestToolBindings, verifyChain, verifyEnrolmentToken, witness };
|
package/dist/index.js
CHANGED
|
@@ -107,7 +107,7 @@ function isRetryable(status) {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
// src/http-client.ts
|
|
110
|
-
var SDK_VERSION = "2.
|
|
110
|
+
var SDK_VERSION = "2.5.0";
|
|
111
111
|
var HttpClient = class {
|
|
112
112
|
constructor(opts) {
|
|
113
113
|
this.opts = opts;
|
|
@@ -131,6 +131,7 @@ var HttpClient = class {
|
|
|
131
131
|
"User-Agent": `nomos-node/${SDK_VERSION}`,
|
|
132
132
|
"NOMOS-SDK-Version": SDK_VERSION
|
|
133
133
|
};
|
|
134
|
+
if (reqOpts?.headers) Object.assign(headers, reqOpts.headers);
|
|
134
135
|
if (reqOpts?.idempotencyKey) {
|
|
135
136
|
headers["Idempotency-Key"] = reqOpts.idempotencyKey;
|
|
136
137
|
}
|
|
@@ -636,9 +637,18 @@ function normalizeAuthorityQueryResult(data, authority) {
|
|
|
636
637
|
// NOMOS-SPEC-006 — present only when the issuer has actually revoked this exact sealed
|
|
637
638
|
// version; omitted (not false) when revocation doesn't structurally apply here, so "checked
|
|
638
639
|
// and clean" and "couldn't check" never look the same to a caller.
|
|
639
|
-
...data.revoked === true ? { revoked: true, revocation_reason: data.revocation_reason } : {}
|
|
640
|
+
...data.revoked === true ? { revoked: true, revocation_reason: data.revocation_reason } : {},
|
|
641
|
+
...data.signature ? { signature: data.signature } : {},
|
|
642
|
+
...data.evaluator_version ? { evaluator_version: data.evaluator_version } : {}
|
|
640
643
|
};
|
|
641
644
|
}
|
|
645
|
+
function callerHeaders(c) {
|
|
646
|
+
const h = {};
|
|
647
|
+
if (c?.agent_id) h["X-Nomos-Agent"] = c.agent_id;
|
|
648
|
+
if (c?.acts_for) h["X-Nomos-Acts-For"] = c.acts_for;
|
|
649
|
+
if (c?.purpose) h["X-Nomos-Purpose"] = c.purpose;
|
|
650
|
+
return h;
|
|
651
|
+
}
|
|
642
652
|
async function canViaExchange(authority, facts, action, opts) {
|
|
643
653
|
const base = (opts?.baseUrl ?? DEFAULT_EXCHANGE_BASE).replace(/\/$/, "");
|
|
644
654
|
const url = `${base}/api/v1/can`;
|
|
@@ -646,7 +656,7 @@ async function canViaExchange(authority, facts, action, opts) {
|
|
|
646
656
|
try {
|
|
647
657
|
res = await fetch(url, {
|
|
648
658
|
method: "POST",
|
|
649
|
-
headers: { "Content-Type": "application/json" },
|
|
659
|
+
headers: { "Content-Type": "application/json", "User-Agent": `nomos-node/${SDK_VERSION}`, ...callerHeaders(opts?.caller) },
|
|
650
660
|
body: JSON.stringify({ artifact: authority, action, ...facts }),
|
|
651
661
|
signal: opts?.signal
|
|
652
662
|
});
|
|
@@ -659,9 +669,34 @@ async function canViaExchange(authority, facts, action, opts) {
|
|
|
659
669
|
if (!res.ok) mapHttpError(res.status, data, requestId);
|
|
660
670
|
return normalizeAuthorityQueryResult(data, authority);
|
|
661
671
|
}
|
|
662
|
-
async function canViaExchangeAuthenticated(http, authority, facts, action) {
|
|
663
|
-
const
|
|
664
|
-
|
|
672
|
+
async function canViaExchangeAuthenticated(http, authority, facts, action, opts) {
|
|
673
|
+
const ask = async () => (await http.post("/api/v1/can", { artifact: authority, action, ...facts }, { headers: callerHeaders(opts?.caller) })).data;
|
|
674
|
+
const data = await ask();
|
|
675
|
+
const result = normalizeAuthorityQueryResult(data, authority);
|
|
676
|
+
const approval = data?.approval;
|
|
677
|
+
if (!approval) return result;
|
|
678
|
+
result.approval = { id: approval.id, url: approval.url, status: approval.status };
|
|
679
|
+
if (!opts?.awaitApproval || result.verdict !== "ESCALATED") return result;
|
|
680
|
+
return awaitApproval(http, result, typeof opts.awaitApproval === "object" ? opts.awaitApproval : {});
|
|
681
|
+
}
|
|
682
|
+
var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
683
|
+
async function awaitApproval(http, result, o) {
|
|
684
|
+
const a = result.approval;
|
|
685
|
+
o.onApproval?.({ id: a.id, url: a.url });
|
|
686
|
+
const deadline = Date.now() + (o.timeoutMs ?? 6e5);
|
|
687
|
+
let status = a.status;
|
|
688
|
+
while (status === "pending" && Date.now() < deadline) {
|
|
689
|
+
await sleep2(Math.max(500, o.pollMs ?? 3e3));
|
|
690
|
+
status = (await http.get(`/api/v1/approvals/${a.id}`)).data?.status ?? "pending";
|
|
691
|
+
}
|
|
692
|
+
if (status === "pending") return { ...result, approval: { ...a, status: "timed_out" } };
|
|
693
|
+
if (status !== "approved") return { ...result, approval: { ...a, status: "rejected" } };
|
|
694
|
+
try {
|
|
695
|
+
await http.post(`/api/v1/approvals/${a.id}/consume`, {});
|
|
696
|
+
} catch {
|
|
697
|
+
return { ...result, approval: { ...a, status: "not_usable" } };
|
|
698
|
+
}
|
|
699
|
+
return { ...result, allowed: true, approval: { ...a, status: "approved" } };
|
|
665
700
|
}
|
|
666
701
|
async function canViaArtifact(http, artifactId, facts, action) {
|
|
667
702
|
const isPublic = artifactId.startsWith("pub_");
|
|
@@ -757,7 +792,6 @@ async function can(params, opts) {
|
|
|
757
792
|
return canViaExchange(params.authority, facts, action, opts);
|
|
758
793
|
}
|
|
759
794
|
async function canAuthenticated(http, params, opts) {
|
|
760
|
-
void opts;
|
|
761
795
|
const { facts, action } = splitFacts(params);
|
|
762
796
|
if (params.artifact !== void 0) {
|
|
763
797
|
if (!params.key_certs || !params.root_public_key_pem) {
|
|
@@ -766,7 +800,7 @@ async function canAuthenticated(http, params, opts) {
|
|
|
766
800
|
return canViaChain(params.artifact, params.key_certs, params.root_public_key_pem, facts, action, opts);
|
|
767
801
|
}
|
|
768
802
|
if (params.artifact_id) return canViaArtifact(http, params.artifact_id, facts, action);
|
|
769
|
-
if (params.authority) return canViaExchangeAuthenticated(http, params.authority, facts, action);
|
|
803
|
+
if (params.authority) return canViaExchangeAuthenticated(http, params.authority, facts, action, opts);
|
|
770
804
|
throw new Error("can(): `authority` (a public Exchange artifact), `artifact_id` (your own artifact), or `artifact` (chain-of-trust) is required.");
|
|
771
805
|
}
|
|
772
806
|
var nomos = { can };
|
|
@@ -4967,9 +5001,14 @@ var TIE_ORDERS = {
|
|
|
4967
5001
|
function evaluate(artifact, facts) {
|
|
4968
5002
|
const logic = artifact.logic;
|
|
4969
5003
|
const res = logic.resolution ?? {};
|
|
4970
|
-
|
|
4971
|
-
if (!Array.isArray(decisions))
|
|
5004
|
+
if (!Array.isArray(logic.decisions))
|
|
4972
5005
|
throw new Error("logic.decisions missing");
|
|
5006
|
+
const declared = new Set(Object.keys(artifact.execution?.actions ?? {}));
|
|
5007
|
+
const isObligation = (d) => {
|
|
5008
|
+
const t = d?.then?.[0];
|
|
5009
|
+
return t?.type === "emit" || t?.type === "action" && declared.has(t?.action_name);
|
|
5010
|
+
};
|
|
5011
|
+
const decisions = logic.decisions.filter((d) => !isObligation(d));
|
|
4973
5012
|
const order = TIE_ORDERS[res.tie_breaker ?? "deny_wins"];
|
|
4974
5013
|
if (!order)
|
|
4975
5014
|
throw new Error("unknown tie_breaker");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nomosprotocol/sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "NOMOS for TypeScript: accept() for the system that makes a change, act() for the agent that asks, the issuer, witness and approver roles, and the Exchange client. Verification is local (@nomosprotocol/verify).",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -32,7 +32,9 @@
|
|
|
32
32
|
"build": "tsup",
|
|
33
33
|
"dev": "tsup --watch",
|
|
34
34
|
"clean": "rm -rf dist",
|
|
35
|
-
"type-check": "tsc --noEmit"
|
|
35
|
+
"type-check": "tsc --noEmit",
|
|
36
|
+
"check:verify": "npm --prefix ../packages/verify run build && node scripts/check-bundled-verify.mjs",
|
|
37
|
+
"prepublishOnly": "npm run check:verify && npm run build"
|
|
36
38
|
},
|
|
37
39
|
"tsup": {
|
|
38
40
|
"entry": [
|
|
@@ -64,6 +66,13 @@
|
|
|
64
66
|
],
|
|
65
67
|
"author": "NOMOS",
|
|
66
68
|
"license": "MIT",
|
|
69
|
+
"repository": {
|
|
70
|
+
"type": "git",
|
|
71
|
+
"url": "git+https://github.com/nomos-spec/spec.git"
|
|
72
|
+
},
|
|
73
|
+
"bugs": {
|
|
74
|
+
"url": "https://github.com/nomos-spec/spec/issues"
|
|
75
|
+
},
|
|
67
76
|
"engines": {
|
|
68
77
|
"node": ">=20"
|
|
69
78
|
},
|
|
@@ -74,6 +83,6 @@
|
|
|
74
83
|
"dependencies": {
|
|
75
84
|
"@noble/curves": "^2.4.0",
|
|
76
85
|
"@noble/hashes": "^2.4.0",
|
|
77
|
-
"@nomosprotocol/verify": "^1.
|
|
86
|
+
"@nomosprotocol/verify": "^1.3.0"
|
|
78
87
|
}
|
|
79
88
|
}
|