verdifax 1.2.0 → 1.4.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 CHANGED
@@ -90,21 +90,45 @@ if (v.verified) {
90
90
  | `client.testPolicy({ inputs, document?, documentHash? })` | `POST /policies/test` | required |
91
91
  | `client.getPolicyHistory()` | `GET /policies/history` | required |
92
92
  | `client.activatePolicy(documentHash)` | `POST /policies/{document_hash}/activate` | required |
93
- | `client.adminCreateKey(name)` | `POST /admin/keys` | admin |
93
+ | `client.getAccount()` | `GET /account` | owner key |
94
+ | `client.createAccountKey(name, { expiresAt? })` | `POST /account/keys` | owner key |
95
+ | `client.listAccountKeys()` | `GET /account/keys` | owner key |
96
+ | `client.revokeAccountKey(id)` | `DELETE /account/keys/{id}` | owner key |
97
+ | `client.registerAccountRequestKey()` | `POST /account/request-key` | owner key |
98
+ | `client.clearAccountRequestKey()` | `DELETE /account/request-key` | owner key |
99
+ | `client.listAccountRuns(params)` | `GET /account/runs` | owner key |
100
+ | `client.exportAccountRuns(params)` | `GET /account/runs/export` | owner key |
101
+ | `client.exportAccountRunsCSV(params)` | `GET /account/runs/export?format=csv` | owner key |
102
+ | `client.uploadAccountPolicy(document)` | `POST /account/policies` | owner key |
103
+ | `client.listAccountPolicies()` | `GET /account/policies` | owner key |
104
+ | `client.getAccountPolicy(documentHash)` | `GET /account/policies/{document_hash}` | owner key |
105
+ | `client.testAccountPolicy({ inputs, document?, documentHash? })` | `POST /account/policies/test` | owner key |
106
+ | `client.getAccountPolicyHistory()` | `GET /account/policies/history` | owner key |
107
+ | `client.activateAccountPolicy(documentHash)` | `POST /account/policies/{document_hash}/activate` | owner key |
108
+ | `client.registerApprover(name, approver)` | `POST /account/approvers` | owner key |
109
+ | `client.listApprovers()` | `GET /account/approvers` | owner key |
110
+ | `client.revokeApprover(id)` | `DELETE /account/approvers/{id}` | owner key |
111
+ | `client.getSignOffHistory()` | `GET /account/signoffs/history` | owner key |
112
+ | `client.getNewestSignOff({ asOwner? })` | `GET /signoffs/newest`, or `GET /account/signoffs/newest` with `asOwner` | required, or owner key |
113
+ | `client.signOff(target, decision, approverKey, { note?, asOwner? })` | `POST /signoffs`, or `POST /account/signoffs` with `asOwner` | required, or owner key |
114
+ | `client.getSignOffs(runId, { asOwner? })` | `GET /runs/{id}/signoffs`, or `GET /account/runs/{id}/signoffs` with `asOwner` | required, or owner key |
115
+ | `client.adminCreateKey(name, { accountId? })` | `POST /admin/keys` | admin |
94
116
  | `client.adminListKeys()` | `GET /admin/keys` | admin |
95
117
  | `client.adminRevokeKey(id)` | `DELETE /admin/keys/{id}` | admin |
96
118
  | `client.adminClearRequestKey(id)` | `DELETE /admin/keys/{id}/request-key` | admin |
119
+ | `client.adminCreateAccount(name, { tier?, expiresAt? })` | `POST /admin/accounts` | admin |
120
+ | `client.adminListAccounts()` | `GET /admin/accounts` | admin |
97
121
  | `client.adminErase(req)` | `POST /admin/erase` | admin |
98
122
 
99
123
  `client.job(jobId)` returns a [job helper](#jobs) and makes no request until a step runs.
100
124
 
101
- These functions make no request: `captureEnvironment`, `formatProviderPayload`, `signRequest`, `signRequestV2`, `requestKeyRegistrationBody`, `requestKeyChangeApproval`, `requestKeyChangePreimage`, `policyActivationApproval`, `policyActivationPreimage`, `requestSignaturePreimage`, `requestSignaturePreimageV2`, `generateRequestKeyPair`, `validateHex64`, `validateJobName`, `validateRouteId`, `dseDispatchHash`, `dcaeClosureHash` and `cresReceiptHash`. `RUN_STATUSES` lists every run status the server uses, and `EXECUTE_FIELDS` lists every top-level key a run request may hold.
125
+ These functions make no request: `captureEnvironment`, `formatProviderPayload`, `signRequest`, `signRequestV2`, `requestKeyRegistrationBody`, `requestKeyChangeApproval`, `requestKeyChangePreimage`, `policyActivationApproval`, `policyActivationPreimage`, `signOffPreimage`, `approverKeyProof`, `requestSignaturePreimage`, `requestSignaturePreimageV2`, `generateRequestKeyPair`, `validateHex64`, `validateJobName`, `validateRouteId`, `dseDispatchHash`, `dcaeClosureHash` and `cresReceiptHash`. `RUN_STATUSES` lists every run status the server uses, and `EXECUTE_FIELDS` lists every top-level key a run request may hold.
102
126
 
103
127
  `getRunArtifacts`, `getPublicBundle` and `getPublicBundleByToken` return the exact JSON text the server sent, so you can save it and check it with `verdifax-verify`.
104
128
 
105
129
  ## Versioning
106
130
 
107
- From 1.0, a breaking change needs a new major version.
131
+ From 1.0, a breaking change needs a new major version. See [CHANGELOG.md](CHANGELOG.md) for what each release added.
108
132
 
109
133
  ## Settings
110
134
 
@@ -232,6 +256,100 @@ A policy judges what you declared. It cannot show the declared facts are true.
232
256
 
233
257
  A refusal stops the action only if your system waits for the answer.
234
258
 
259
+ ## Accounts
260
+
261
+ An account is a company on Verdifax. Every API key belongs to one account for life. An account has an owner key, which manages the account: it creates and revokes the member keys, sets the account policy and reads the account's runs. The member keys are the keys that run things.
262
+
263
+ ```typescript
264
+ import { readFileSync, writeFileSync } from "node:fs";
265
+ import { VerdifaxClient, generateRequestKeyPair } from "verdifax";
266
+
267
+ // Verdifax creates the account and its first owner key.
268
+ const admin = new VerdifaxClient({ apiKey: process.env.VERDIFAX_ADMIN_KEY! });
269
+ const { account, owner_key } = await admin.adminCreateAccount("Acme", { tier: "production" });
270
+ console.log(account.account_id, owner_key.secret); // the secret is shown once
271
+
272
+ // The owner registers a request key, which must sign every account policy change.
273
+ const { privateKey } = generateRequestKeyPair();
274
+ const owner = new VerdifaxClient({ apiKey: owner_key.secret, signingKey: privateKey });
275
+ await owner.registerAccountRequestKey();
276
+
277
+ // The owner creates member keys. Each one stays in the account for life.
278
+ const member = await owner.createAccountKey("loan-bot");
279
+ console.log(member.id, member.secret); // the secret is shown once
280
+
281
+ // The owner loads, tests and activates the account policy, signed by its request key.
282
+ const stored = await owner.uploadAccountPolicy(readFileSync("account-policy.json", "utf8"));
283
+ await owner.testAccountPolicy({ documentHash: stored.document_hash, inputs: { verb: "approve-loan" } });
284
+ await owner.activateAccountPolicy(stored.document_hash);
285
+
286
+ // The owner exports the runs of every key in the account, with the account policy and its changes.
287
+ const evidence = await owner.exportAccountRuns({ from: "2026-11-01T00:00:00Z", to: "2026-12-01T00:00:00Z" });
288
+ writeFileSync("account-runs.json", JSON.stringify(evidence));
289
+ ```
290
+
291
+ An owner key cannot run anything. Every route that needs a key refuses it, apart from the `/account` routes, so give the member keys to the systems that run things.
292
+
293
+ The account number, such as `account-7`, is a label Verdifax assigned. It is not a verified company identity.
294
+
295
+ An account policy is the customer's own. Verdifax did not write, review or approve it. Every key in the account must pass the account policy on top of its own policy, and a run is admitted only when both allow it.
296
+
297
+ The account's view covers the runs its keys made through Verdifax. It does not cover runs made elsewhere.
298
+
299
+ `activateAccountPolicy` reads `listAccountPolicies` first, names the newest change as the one it follows and signs the change over the account's chain, `account-N`, with the same signature `activatePolicy` uses. The server refuses an unsigned account policy change, so `activateAccountPolicy` needs the `signingKey` option, set to the owner key's registered request key, and refuses before sending anything without it. It also refuses a `chain_id` that is not of the form `account-N`. The other account policy methods take and return what the matching key policy methods do. `getAccountPolicyHistory` returns the account's changes oldest first, in the form `verdifax-verify --policy-history FILE` reads.
300
+
301
+ `exportAccountRuns` and `exportAccountRunsCSV` take the same period and `job_id` as `exportRuns` and check the CSV counts the same way. The JSON form also carries `account_id`, and `account_policy_documents` and `account_policy_history` once the account has loaded a policy. `listAccountRuns` pages like `listRuns`. `revokeAccountKey` revokes a member key, and a key in another account, or an owner key, throws `NotFoundError`. `adminCreateKey(name, { accountId })` puts a new customer key in an existing account, by its number.
302
+
303
+ ## Sign-off
304
+
305
+ A sign-off is an approver's decision, approve or reject, on one exact result: a completed run's record, a refused or halted run's receipt, or a whole job by its final step. The approver signs it on their own machine with an approver key, and Verdifax links it to the account's previous sign-off and logs it on the public log.
306
+
307
+ ```typescript
308
+ import { writeFileSync } from "node:fs";
309
+ import { VerdifaxClient, approverKeyProof, generateRequestKeyPair } from "verdifax";
310
+
311
+ // The approver creates a key on their own machine and keeps the private key.
312
+ const approverKey = generateRequestKeyPair().privateKey;
313
+ const proof = approverKeyProof(approverKey); // { public_key, proof }, safe to hand to the owner
314
+
315
+ // The account's owner registers the approver key.
316
+ const owner = new VerdifaxClient({ apiKey: process.env.VERDIFAX_OWNER_KEY! });
317
+ await owner.registerApprover("credit review", proof);
318
+
319
+ // Any key in the account submits the approver's signed decision on one exact result.
320
+ const member = new VerdifaxClient({ apiKey: process.env.VERDIFAX_API_KEY! });
321
+ const bundle = JSON.parse(await member.getRunArtifacts(1042));
322
+ const signOff = await member.signOff(
323
+ { kind: "record", run_id: 1042, digest: bundle.bundle_anchor.anchored_digest },
324
+ "approve",
325
+ approverKey,
326
+ { note: "Checked against the loan file." }, // only its SHA-256 is sent
327
+ );
328
+ console.log(signOff.sequence, signOff.signoff_hash);
329
+
330
+ // Save the run's sign-offs and check them with verdifax-verify --signoff signoffs.json.
331
+ writeFileSync("signoffs.json", JSON.stringify(await member.getSignOffs(1042)));
332
+
333
+ // The owner saves the account's whole chain for verdifax-verify --signoff-history.
334
+ writeFileSync("signoff-history.json", JSON.stringify(await owner.getSignOffHistory()));
335
+ ```
336
+
337
+ An approver key is registered by the account's owner, and it is never a request key in the same account. The server refuses to register a key that is, so the key that requested a run cannot sign it off. `registerApprover` takes the approver's signing key, from which it makes the proof on this machine, or the `{ public_key, proof }` that `approverKeyProof` made on the approver's own machine, so the owner need not hold the approver's private key. It refuses a proof that does not verify before sending. `revokeApprover` revokes a key, and its earlier sign-offs stay on the record. `listApprovers` lists revoked keys too.
338
+
339
+ `signOff` reads `getNewestSignOff` first for the account's chain, `account-N`, and its newest sign-off, then signs over them, the target, the decision, the note's hash, the time and a fresh nonce. The approver's private key is never sent. The target's `digest` is what the result's public-log entry covers: for a record, the `bundle_anchor.anchored_digest` of the bundle `getRunArtifacts` returns; for a receipt, its hash; for a job, the final step's record digest, read the same way as a record's, with `job_id`. The note is hashed on your machine and only its SHA-256 is sent and signed: the note's text never reaches Verdifax, so keep it yourself. `signOff` refuses a bad target, decision, key or note before sending anything. It also checks that the record the server returns carries the signature, `approver_key`, `decision`, `previous_signoff_hash` and `target` that were sent, and throws `VerdifaxError` if not. An HTTP 409 means another sign-off came first or the target is not this account's result: it throws the usual `APIError` and is not retried.
340
+
341
+ With `asOwner: true`, `signOff`, `getNewestSignOff` and `getSignOffs` use the owner key's `/account` routes. `getSignOffs` returns a run's sign-offs oldest first, in the form `verdifax-verify --signoff FILE` reads: for a member key, a run it made; with `asOwner`, any run of the account. `getSignOffHistory` returns the account's whole chain, oldest first, in the form `verdifax-verify --signoff-history FILE` reads, and `exportAccountRuns` carries the same chain as `signoffs`. The SDK returns `signoffs` as the server sent it, without checking it: check it with `verdifax-verify --signoff-history`.
342
+
343
+ A sign-off shows that the holder of a registered approver key signed that decision on that exact result, and that Verdifax logged it on the public log. It does not show that the review was careful, that the result is right, or who the person is. A key is not an identity.
344
+
345
+ A sign-off gates nothing on its own.
346
+
347
+ A later sign-off on the same result supersedes the earlier one, and nothing is deleted.
348
+
349
+ Verdifax did not review the result.
350
+
351
+ Since v0.22.0, the verifier reads sign-offs with `--signoff` and `--signoff-history`.
352
+
235
353
  ## Evidence export
236
354
 
237
355
  `exportRuns` lists the runs the server holds for your key in a period, failures included, with up to 10,000 rows per export. `exportRunsCSV` returns the same rows as raw CSV text.