@ziffer-io/client 0.1.0 → 0.1.1

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 (2) hide show
  1. package/README.md +62 -49
  2. package/package.json +14 -11
package/README.md CHANGED
@@ -1,69 +1,82 @@
1
- # `@ziffer-io/client`
1
+ # @ziffer-io/client
2
2
 
3
- The TypeScript SDK for the ZIFFER decision API: submit a proposal, wait for the decision, and
4
- verify the receipt before you act on it.
3
+ The ZIFFER client for TypeScript. Propose an action, wait for the decision, and verify the signed
4
+ receipt in your own process before you act.
5
+
6
+ ## Install
5
7
 
6
8
  ```bash
7
9
  npm install @ziffer-io/client
8
10
  ```
9
11
 
10
- That one line brings the wire types (`@ziffer-io/types`) and the verifier (`@ziffer-io/verify`)
11
- with it. `verifyReceipt` is **re-exported unchanged** from the verifier's own package, so there is
12
- one verifier in your dependency tree rather than two that can disagree — the tests assert it is
13
- the same function object, so the re-export cannot decay into a copy quietly.
12
+ Node 22 or later. The wire types and the receipt verifier are installed with it.
14
13
 
15
- ## The whole integration
14
+ ## Quickstart
16
15
 
17
16
  ```ts
18
- import { ZifferClient, verifyReceipt, Refusal, type TrustAnchor } from '@ziffer-io/client';
17
+ import { readFileSync } from 'node:fs';
18
+ import { ZifferClient, verifyReceipt, type TrustAnchor } from '@ziffer-io/client';
19
19
 
20
- const client = new ZifferClient(process.env.ZIFFER_API_URL, process.env.ZIFFER_API_KEY);
21
- const anchor: TrustAnchor = await loadAnchor(process.env.ZIFFER_TRUST_ANCHOR);
20
+ const keys = JSON.parse(readFileSync(process.env.ZIFFER_TRUST_ANCHOR, 'utf8'));
21
+ const anchor: TrustAnchor = {
22
+ classical: Buffer.from(keys.ed25519_pk_hex, 'hex'),
23
+ pq: Buffer.from(keys.mldsa65_pk_hex, 'hex'),
24
+ minSuite: process.env.ZIFFER_SUITE_FLOOR,
25
+ };
22
26
 
27
+ const client = new ZifferClient(process.env.ZIFFER_API_URL, process.env.ZIFFER_API_KEY);
23
28
  const submitted = await client.propose(proposal);
24
29
  const decision = await client.wait(submitted.decision_id, { timeoutMs: 30_000 });
25
30
  if (decision.outcome !== 'ALLOW') throw new Error(`ziffer refused: ${decision.clause}`);
26
31
 
27
32
  verifyReceipt(decision.receipt, new TextEncoder().encode(JSON.stringify(proposal)), anchor);
28
- await bank.transfer(amount, toAccount); // your line, unchanged
33
+ await bank.transfer(amount, toAccount); // your line, unchanged
29
34
  ```
30
35
 
31
- `verifyReceipt` throws on any defect. Catch `Refusal` and read `.clause` — narrow with
32
- `instanceof`, never a cast: a `catch` binds `unknown` and also sees the errors you did not plan
33
- for, so an `as Refusal` would read a `clause` off a `TypeError` and report a protocol refusal that
34
- never happened.
35
-
36
- `docs/onboarding/sdk.md` section 8 is the same integration at length, with what each refusal means.
37
-
38
- ## What it does not do
39
-
40
- - **It computes no security value, and that is deliberate.** The proposal goes out as you wrote it
41
- and the receipt comes back as the gateway stored it. A compromised client writes the whole
42
- message, so nothing a client derives about its own request is evidence (RES-8). Every check that
43
- matters happens inside `verifyReceipt`, over bytes you supply.
44
- - **`ALLOW` is not permission to act; the verified receipt is.** The outcome field is transmitted
45
- data. Nothing is gated until `verifyReceipt` has returned without throwing, and a handler that
46
- calls it without branching on it is not gated at all.
47
- - **It does not fetch your trust anchor.** `TrustAnchor` is an argument. Obtain the
48
- `acp-bundle pubkey` document out of band; an anchor the API served you proves nothing about the
49
- API.
50
- - **It does not choose your signature-suite floor.** There is no default, deliberately.
51
- - **It has no `approve` and no sandbox shortcut that mints a receipt.** Approval happens inside
52
- the deployment, under keys your process never holds.
53
- - **A pass from `verifyReceipt` is the stateless half of §9.3 only.** `@ziffer-io/verify`'s README
54
- lists the six steps that need a signed bundle, a ledger and a context store, and are absent
55
- rather than approximated.
56
-
57
- ## Development
58
-
59
- ```sh
60
- pnpm --filter @ziffer-io/client test # tsc -b && node --test
61
- ```
36
+ You pass the proposal twice on purpose. The verifier hashes the bytes you hand it and compares
37
+ them with the receipt's claim. The check is against your copy, not against ours. Key order and
38
+ spacing do not matter.
39
+
40
+ `verifyReceipt` checks the answer. Your own `if` is what stops the action.
41
+
42
+ An action that needs human approval answers `ATTEST` and no receipt. Keep polling
43
+ `client.wait` for the same decision id until `decision.receipt` is present, then verify it.
44
+
45
+ Under TypeScript's strict settings `process.env` values are typed `string | undefined`. Read them
46
+ through a small helper that throws when one is not set. The SDK guide has one.
47
+
48
+ ## Configuration
49
+
50
+ | Variable | What it is | Where the value comes from |
51
+ | --- | --- | --- |
52
+ | `ZIFFER_API_KEY` | Your API key. It carries your tenant, so no request names a tenant. | We issue it. It expires after 90 days unless you ask for another lifetime. |
53
+ | `ZIFFER_TRUST_ANCHOR` | Path to the public key file your receipts are signed under. | We give you the file. Take it from us, never from the API you are checking. |
54
+ | `ZIFFER_SUITE_FLOOR` | The weakest signature suite you will accept. | You choose it. There is no default. |
55
+ | `ZIFFER_API_URL` | The base URL of the ZIFFER deployment you call. | We give it to you with your key. |
56
+
57
+ ## When a request is refused
58
+
59
+ Every refusal is a thrown `Refusal` whose `clause` names the rule that fired; the table of every
60
+ clause, what it means and what to do is at https://ziffer.io/docs/refusals. Narrow with
61
+ `instanceof Refusal`, record the clause, and do not retry it.
62
+
63
+ ## Documentation
64
+
65
+ - Quickstart: https://ziffer.io/docs/quickstart
66
+ - Integrating the SDK: https://ziffer.io/docs/developers/sdk
67
+ - Sandbox tenants: https://ziffer.io/docs/developers/sandbox
68
+ - Every refusal: https://ziffer.io/docs/refusals
69
+ - Policy by example: https://ziffer.io/docs/policy/by-example
70
+ - Glossary: https://ziffer.io/docs/glossary
71
+
72
+ ## Support
73
+
74
+ Write to hello@ziffer.io. Your API key, your trust anchor file and your suite floor come from us.
75
+ So does an answer about a refusal you cannot explain.
62
76
 
63
- ## Licence
77
+ ## License
64
78
 
65
- Proprietary. Copyright (c) 2026 code75 SASU, Paris, France. ZIFFER is a registered trademark of code75
66
- SASU. This package is **not open source**: it is licensed for use with the ZIFFER service under
67
- your agreement with code75, on the terms in `LICENSE` beside this file. The third-party
68
- open-source components it redistributes are listed in `THIRD-PARTY-NOTICES` and are governed by
69
- their own licences.
79
+ Proprietary. Copyright (c) 2026 code75 SASU, Paris, France. ZIFFER is a registered trademark of
80
+ code75 SASU. This package is not open source. Its use is governed by your agreement with code75
81
+ and by `LICENSE` beside this file. The open-source components it redistributes are listed in
82
+ `THIRD-PARTY-NOTICES`, under their own licences.
package/package.json CHANGED
@@ -1,10 +1,17 @@
1
1
  {
2
2
  "name": "@ziffer-io/client",
3
- "version": "0.1.0",
4
- "description": "The ZIFFER decision API client for TypeScript, with receipt verification re-exported from one home.",
3
+ "version": "0.1.1",
4
+ "description": "ZIFFER TypeScript client: propose an action, wait for the decision, verify the receipt before you act",
5
+ "keywords": [
6
+ "ziffer",
7
+ "agent",
8
+ "ai-agent",
9
+ "guardrail",
10
+ "approval",
11
+ "receipt"
12
+ ],
5
13
  "author": "code75 SASU",
6
14
  "license": "SEE LICENSE IN LICENSE",
7
- "comment-license": "The SDK is proprietary (code75 SASU). \"license\" is the SPDX escape hatch for exactly this case: there is no SPDX identifier for these terms, so the field points at the file that states them, and LICENSE ships in the tarball beside THIRD-PARTY-NOTICES. Do not put an OSI identifier here -- Apache-2.0 stood in these four files until ACP-214 and was wrong the whole time.",
8
15
  "type": "module",
9
16
  "main": "./dist/index.js",
10
17
  "types": "./dist/index.d.ts",
@@ -32,16 +39,12 @@
32
39
  "publishConfig": {
33
40
  "access": "public"
34
41
  },
35
- "comment-no-provenance": "There is deliberately no \"provenance\": true here, and it was removed rather than never added. npm generates a provenance attestation only from a recognised CI runner, and its documentation states plainly that provenance is NOT SUPPORTED for private repositories (docs.npmjs.com/trusted-publishers, read 2026-09-03) -- ziffer-hq/ziffer is private (`gh api` says so). Setting the flag does not degrade to a warning: npm attempts the attestation and the publish FAILS, so the field would have broken the operator's very first publish from a laptop and every CI publish after it. tools/release-npm.sh asserts the field stays absent, and that assertion is the thing to delete on the day this repository becomes public -- at which point trusted publishing generates provenance on its own, with no flag at all.",
36
42
  "ziffer": {
37
- "enginePin": "fed43d10b427e0a4435a8f04e474334d88a5aa6e"
43
+ "enginePin": "8ae262196dbe75210a975a456785df5d9f0524a3"
38
44
  },
39
- "comment-enginePin": "The engine commit whose wire types this client speaks and whose verifier it re-exports. A COPY of the rev in Cargo.toml, which is the one authority tools/guard.sh reads; tools/release-npm.sh refuses to release when the two differ, by name. tools/bump-pin.sh does not move this field -- see packages/types/package.json for why and what closing it costs.",
40
- "comment-deps": "Exactly two: @ziffer-io/types because the wire format is the one permitted shared definition (it IS the schemas, generated), and @ziffer-io/verify because verification has ONE home and this client re-exports it rather than growing a second (ACP-197 runbook section 6). No HTTP dependency: Node's own fetch carries two routes; a framework here is surface without a claim.",
41
- "comment-types-was-a-git-dependency": "Until ACP-214 the first of those two was `git+https://ziffer-hq@github.com/ziffer-hq/agent-control-plane.git#<pin>&path:/packages/acp-types`. That is a private repository, so the published client would have been uninstallable rather than merely awkward; and pnpm runs `prepare` for a git dependency, so a consumer who could reach it would have compiled our wire types on their own machine. It is now a workspace dependency on packages/types, which `pnpm pack` rewrites to that package's exact published version -- measured, and asserted again by tools/release-npm.sh over every tarball.",
42
45
  "dependencies": {
43
- "@ziffer-io/verify": "0.1.0",
44
- "@ziffer-io/types": "0.1.0"
46
+ "@ziffer-io/types": "0.1.1",
47
+ "@ziffer-io/verify": "0.1.1"
45
48
  },
46
49
  "devDependencies": {
47
50
  "@types/node": "^22.15.0",
@@ -50,7 +53,7 @@
50
53
  "scripts": {
51
54
  "build": "tsc -b",
52
55
  "typecheck": "tsc -b",
53
- "pretest": "tsc -b && if ! find dist -name '*.test.js' -print -quit | grep -q .; then echo 'ACP-248: no compiled test file under dist/ -- node --test reports 0 tests and exits 0, so this package would go green having run nothing' >&2; exit 1; fi",
56
+ "pretest": "tsc -b && if ! find dist -name '*.test.js' -print -quit | grep -q .; then echo 'no compiled test file under dist/ -- node --test reports 0 tests and exits 0, so this package would go green having run nothing' >&2; exit 1; fi",
54
57
  "test": "tsc -b && node --test \"dist/**/*.test.js\""
55
58
  }
56
59
  }