sello 0.1.9 → 0.1.11

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 (42) hide show
  1. package/README.md +81 -122
  2. package/dist/cli/sello.js +83 -4
  3. package/docs/paper/notarized-agents.md +1 -1
  4. package/docs/paper/notarized-agents.tex +1 -1
  5. package/docs/protocol-walkthrough.md +74 -0
  6. package/docs/release-checklist.md +17 -3
  7. package/docs/sdk-quickstart.md +39 -3
  8. package/docs/sdk-security-audit.md +1 -1
  9. package/package.json +17 -17
  10. package/sdks/README.md +17 -0
  11. package/{src → sdks/typescript/src}/cli/sello.ts +83 -4
  12. package/docs/sdk-build-plan.md +0 -214
  13. /package/{examples → sdks/typescript/examples}/mcp-minimal-server.ts +0 -0
  14. /package/{examples → sdks/typescript/examples}/mcp-tool-server.ts +0 -0
  15. /package/{examples → sdks/typescript/examples}/quickstart-tool.ts +0 -0
  16. /package/{fixtures → sdks/typescript/fixtures}/vectors/.gitkeep +0 -0
  17. /package/{fixtures → sdks/typescript/fixtures}/vectors/sello-v0.1.json +0 -0
  18. /package/{src → sdks/typescript/src}/cbor.ts +0 -0
  19. /package/{src → sdks/typescript/src}/cli/bench.ts +0 -0
  20. /package/{src → sdks/typescript/src}/cli/demo.ts +0 -0
  21. /package/{src → sdks/typescript/src}/cose/protected-header.ts +0 -0
  22. /package/{src → sdks/typescript/src}/cose/sign1.ts +0 -0
  23. /package/{src → sdks/typescript/src}/crypto/ed25519.ts +0 -0
  24. /package/{src → sdks/typescript/src}/crypto/identifiers.ts +0 -0
  25. /package/{src → sdks/typescript/src}/hpke/base.ts +0 -0
  26. /package/{src → sdks/typescript/src}/hpke/receipt.ts +0 -0
  27. /package/{src → sdks/typescript/src}/index.ts +0 -0
  28. /package/{src → sdks/typescript/src}/log/canonical-url.ts +0 -0
  29. /package/{src → sdks/typescript/src}/log/mock-log.ts +0 -0
  30. /package/{src → sdks/typescript/src}/log/rekor.ts +0 -0
  31. /package/{src → sdks/typescript/src}/log/types.ts +0 -0
  32. /package/{src → sdks/typescript/src}/mcp/middleware.ts +0 -0
  33. /package/{src → sdks/typescript/src}/owner/verify.ts +0 -0
  34. /package/{src → sdks/typescript/src}/receipt/body.ts +0 -0
  35. /package/{src → sdks/typescript/src}/registry/json-registry.ts +0 -0
  36. /package/{src → sdks/typescript/src}/sdk/index.ts +0 -0
  37. /package/{src → sdks/typescript/src}/sdk/keys.ts +0 -0
  38. /package/{src → sdks/typescript/src}/sdk/logs.ts +0 -0
  39. /package/{src → sdks/typescript/src}/sdk/publisher.ts +0 -0
  40. /package/{src → sdks/typescript/src}/sdk/service.ts +0 -0
  41. /package/{src → sdks/typescript/src}/service/create-receipt.ts +0 -0
  42. /package/{src → sdks/typescript/src}/token/jws-profile.ts +0 -0
package/README.md CHANGED
@@ -1,6 +1,31 @@
1
1
  ![Sello banner](docs/assets/sello-banner.png)
2
2
 
3
- # Sello
3
+ <h1 align="center">Sello</h1>
4
+
5
+ <p align="center">
6
+ <a href="https://github.com/juanfiguera/sello/actions/workflows/ci.yml"><img alt="build status" src="https://img.shields.io/github/actions/workflow/status/juanfiguera/sello/ci.yml?branch=main&style=flat-square&label=build&labelColor=0b1011&color=e8f7ef"></a>
7
+ <a href="https://www.npmjs.com/package/sello"><img alt="npm version" src="https://img.shields.io/npm/v/sello?style=flat-square&label=npm&labelColor=0b1011&color=e8f7ef"></a>
8
+ <a href="https://pypi.org/project/sello/"><img alt="PyPI version" src="https://img.shields.io/pypi/v/sello?style=flat-square&label=pypi&labelColor=0b1011&color=e8f7ef"></a>
9
+ <a href="https://www.npmjs.com/package/sello"><img alt="npm downloads" src="https://img.shields.io/npm/dm/sello?style=flat-square&label=downloads&labelColor=0b1011&color=e8f7ef"></a>
10
+ <a href="LICENSE"><img alt="license Apache-2.0" src="https://img.shields.io/npm/l/sello?style=flat-square&label=license&labelColor=0b1011&color=e8f7ef"></a>
11
+ <a href="package.json"><img alt="Node.js 22.7 or newer" src="https://img.shields.io/badge/node-%3E%3D22.7-e8f7ef?style=flat-square&labelColor=0b1011"></a>
12
+ <a href="sdks/python/pyproject.toml"><img alt="Python 3.9 or newer" src="https://img.shields.io/badge/python-%3E%3D3.9-e8f7ef?style=flat-square&labelColor=0b1011"></a>
13
+ <a href="https://arxiv.org/abs/2606.04193"><img alt="arXiv 2606.04193" src="https://img.shields.io/badge/arXiv-2606.04193-e8f7ef?style=flat-square&labelColor=0b1011"></a>
14
+ </p>
15
+
16
+ <p align="center">
17
+ <a href="#try-it">Quickstart</a> &middot;
18
+ <a href="#add-sello-to-a-tool">Add Sello</a> &middot;
19
+ <a href="sdks/README.md">SDKs</a> &middot;
20
+ <a href="#see-logged-actions">Actions</a> &middot;
21
+ <a href="#how-it-works">How It Works</a> &middot;
22
+ <a href="#learn-more">Learn More</a> &middot;
23
+ <a href="SPEC.md">Protocol</a> &middot;
24
+ <a href="https://arxiv.org/abs/2606.04193">Paper</a> &middot;
25
+ <a href="SECURITY.md">Security</a> &middot;
26
+ <a href="CONTRIBUTING.md">Contributing</a> &middot;
27
+ <a href="#license">License</a>
28
+ </p>
4
29
 
5
30
  Sello is a protocol for independently-verifiable records of AI agent actions.
6
31
 
@@ -36,70 +61,16 @@ Then open:
36
61
  http://localhost:8787/actions
37
62
  ```
38
63
 
39
- To see the tiny wrapped-tool source:
64
+ `sello dev` creates local keys, a demo token, a service registry, and a local transparency log under `.sello/`. The log stores encrypted receipt entries, not plaintext action details.
40
65
 
41
- ```bash
42
- npx --yes sello init-demo
43
- ```
66
+ ## Add Sello to a Tool
44
67
 
45
- To scaffold a small HTTP route that emits receipts:
68
+ TypeScript:
46
69
 
47
70
  ```bash
48
- npx --yes sello init-http-demo
49
- npx --yes sello call-http-demo
71
+ npm install sello
50
72
  ```
51
73
 
52
- ## Why Sello?
53
-
54
- Most agent logs are written by the same system whose behavior they describe. If the agent, runtime, or operator is compromised, those logs can be incomplete or false.
55
-
56
- Sello moves receipt-writing to the services the agent calls. The service was present for the action, but it is outside the agent's own logging path. The architectural inversion is simple: the signer is not the agent or its operator, but the receiver that observed the action.
57
-
58
- ## What Sello Gives You
59
-
60
- Sello helps an owner verify that:
61
-
62
- - A specific service signed a specific receipt.
63
- - The receipt was encrypted for the owner.
64
- - The receipt was included in a trusted transparency log.
65
- - The receipt body was not modified after signing.
66
-
67
- Sello does not prove that the agent called every service it should have called, that every service is honest, or that unauthenticated log indexes returned complete results. Those limits are intentional and documented in the spec.
68
-
69
- ## Repository Status
70
-
71
- This repository currently contains:
72
-
73
- - [SPEC.md](SPEC.md): the Sello protocol draft.
74
- - A TypeScript reference implementation in [`src/`](src/).
75
- - Implementation-backed v0.1 test vectors in [`fixtures/vectors/sello-v0.1.json`](fixtures/vectors/sello-v0.1.json).
76
- - Security review notes in [`docs/security-review.md`](docs/security-review.md).
77
- - SDK security audit notes in [`docs/sdk-security-audit.md`](docs/sdk-security-audit.md).
78
-
79
- The implementation includes a local end-to-end demo, compact JWS token verification, COSE_Sign1 receipt envelopes, HPKE encryption, a mock transparency log, a Rekor discovery adapter, owner verification, an MCP middleware prototype, security review notes, and a local benchmark. Live Rekor proof verification and production identity operations are still future work.
80
-
81
- 📄 **Paper:** [Notarized Agents: Receiver-Attested Confidential Receipts for AI Agent Actions](https://arxiv.org/abs/2606.04193) (arXiv:2606.04193, submitted June 2026). Local PDF: [docs/paper/notarized-agents.pdf](docs/paper/notarized-agents.pdf).
82
-
83
- ## Start Here
84
-
85
- | Goal | Read |
86
- |------|------|
87
- | Add Sello in a few lines | [SDK Quickstart](docs/sdk-quickstart.md) |
88
- | Emit your first receipt | `npx --yes sello dev`, then `npx --yes sello emit-demo` |
89
- | Wrap one HTTP route | `npx --yes sello init-http-demo`, then `npx --yes sello call-http-demo` |
90
- | Try a wrapped tool locally | `node --run dev`, then `node --run example:tool` |
91
- | Try an MCP-style tool call | `node --run dev`, then `node --run example:mcp` |
92
- | See a minimal MCP integration | [examples/mcp-minimal-server.ts](examples/mcp-minimal-server.ts) |
93
- | Understand the protocol | [SPEC.md](SPEC.md) Quick Start |
94
- | Run the local demo | `node --run demo` |
95
- | Run the test suite | `node --run test` |
96
- | Measure local size/performance | `node --run bench -- --json` |
97
- | Emit receipts from a service | [SPEC.md](SPEC.md) §§3.1, 4.1, 5, 6.2 |
98
- | Verify receipts as an owner | [SPEC.md](SPEC.md) §§4.2, 5, 6.2, 7.1 |
99
- | Build the reference implementation | Start with "The First 10 Minutes" below |
100
-
101
- ## Add Sello in a Few Lines
102
-
103
74
  ```ts
104
75
  import { sello } from "sello";
105
76
 
@@ -110,96 +81,84 @@ export const createEvent = receipts.tool("calendar.create_event", async (request
110
81
  });
111
82
  ```
112
83
 
113
- Then inspect verified actions:
84
+ Python:
114
85
 
115
86
  ```bash
116
- npx sello actions --token <agent-token>
87
+ pip install sello
117
88
  ```
118
89
 
119
- In local dev, `sello dev` prints and saves a demo token:
90
+ ```py
91
+ import sello
120
92
 
121
- ```bash
122
- SELLO_ACTION_TOKEN=eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...
123
- ```
93
+ receipts = sello.service()
124
94
 
125
- You usually do not need to copy it. `npx sello actions` reads the latest dev token from `.sello/dev.json`. Use `--token` when you want to inspect receipts for a specific agent authorization token from your own system.
126
-
127
- Sello works with your own log server. `sello.build` is optional convenience, not a protocol requirement.
95
+ @receipts.tool("calendar.create_event")
96
+ def create_event(request):
97
+ return calendar.events.create(request)
98
+ ```
128
99
 
129
- To try the repo's local loop:
100
+ In local dev, `npx sello dev` supplies the config this snippet needs. In production, configure your service with a service id, service signing key, token issuer, and log URL:
130
101
 
131
102
  ```bash
132
- # Terminal 1
133
- node --run dev
134
-
135
- # Terminal 2
136
- node --run example:tool
137
- node --run actions
103
+ SELLO_SERVICE_ID=calendar.example.com/mcp/v1
104
+ SELLO_SERVICE_KEY=sello_live_local_...
105
+ SELLO_TOKEN_ISSUER_JWKS=https://auth.example.com/.well-known/jwks.json
106
+ SELLO_LOG_URL=https://logs.example.com/api
107
+ SELLO_SUBMIT_MODE=background
138
108
  ```
139
109
 
140
- The example wraps a fake calendar tool, emits a service-signed encrypted receipt, and lets the owner verify it from the local action log.
110
+ Sello works with your own log server. Using `sello.build` is an optional convenience, not a protocol requirement.
141
111
 
142
- For an MCP-shaped `tools/call` boundary, run `node --run example:mcp` instead of `node --run example:tool`.
112
+ To scaffold a tiny emitter or HTTP route:
143
113
 
144
- For a smaller production-shaped MCP example, see [examples/mcp-minimal-server.ts](examples/mcp-minimal-server.ts). It wraps one `tools/call` handler with `sello.service()` and leaves unknown tools unreceipted.
145
-
146
- For an installed-project bridge from demo to app, run `npx sello init-http-demo`. It writes a small dependency-free HTTP route that imports `sello`, reads the local dev config, verifies a bearer token, runs a handler, and emits a receipt. With the local log and route running, `npx sello call-http-demo` sends the demo request for you.
114
+ ```bash
115
+ npx --yes sello init-demo
116
+ npx --yes sello init-http-demo
117
+ ```
147
118
 
148
- ## The First 10 Minutes
119
+ ## See Logged Actions
149
120
 
150
- If you are implementing Sello, start with one local loop:
121
+ ```bash
122
+ npx sello actions
123
+ ```
151
124
 
152
- 1. Generate a fixed owner HPKE key pair.
153
- 2. Generate a fixed service Ed25519 signing key.
154
- 3. Create one mock compact JWS token containing `owner_hpke_pk` and `sello_logs`.
155
- 4. Have the service create one receipt for one fake action.
156
- 5. Store the receipt in a mock log under `sello_token_ref`.
157
- 6. Have the owner fetch, verify, and decrypt the receipt.
125
+ In local dev, `sello actions` reads the latest dev token and owner key from `.sello/dev.json`. To inspect actions for a specific agent token, pass it explicitly:
158
126
 
159
- Do not start with Rekor, MCP middleware, distributed identity, or CLI polish. Those become much easier once one local receipt works end to end.
127
+ ```bash
128
+ npx sello actions --token <agent-token>
129
+ ```
160
130
 
161
- You know the first loop works when the owner can print one verified receipt:
131
+ The token is the same authorization token the agent used when it called services. Sello hashes the exact token bytes into `sello_token_ref`, queries trusted logs, verifies matching receipts, and decrypts them with the owner key.
162
132
 
163
- ```json
164
- {
165
- "service": "example.com/tool/v1",
166
- "action-type": "tools/call",
167
- "result-status": "success",
168
- "verified": true
169
- }
170
- ```
133
+ Public logs store encrypted receipts. Viewing action details requires the owner private key or an explicitly delegated viewer key.
171
134
 
172
- ## Service Integration
135
+ ## How It Works
173
136
 
174
- A Sello-aware service does this for each agent action:
137
+ Most agent logs are written by the same system whose behavior they describe. If the agent, runtime, or operator is compromised, those logs can be incomplete or false.
175
138
 
176
- 1. Verify the agent's authorization token.
177
- 2. Read `owner_hpke_pk` and `sello_logs` from the verified token.
178
- 3. Compute `sello_token_ref = SHA-256(raw compact JWS bytes)`.
179
- 4. Build a CBOR receipt body describing the action.
180
- 5. Encrypt the receipt body to the owner with HPKE.
181
- 6. Sign the COSE_Sign1 envelope with the service key.
182
- 7. Publish the envelope to an owner-trusted transparency log.
139
+ Sello moves receipt-writing to the services the agent calls. The service was present for the action, but it is outside the agent's own logging path.
183
140
 
184
- The service signs what it observed. It does not need the owner's private key, and it does not need to understand the owner's downstream audit workflow.
141
+ 1. The agent calls a service with an authorization token.
142
+ 2. The service verifies the token, runs the action, and signs an encrypted receipt for what it observed.
143
+ 3. A transparency log stores the encrypted receipt.
144
+ 4. The owner later fetches, verifies, and decrypts the receipt.
185
145
 
186
- For most services, Sello should fit as middleware around an existing request handler: verify token, run action, emit receipt.
146
+ Sello helps an owner verify that a specific service signed a specific receipt, the receipt was encrypted for the owner, the receipt was included in a trusted transparency log, and the receipt body was not modified after signing.
187
147
 
188
- ## Owner Verification
148
+ Sello does not prove that the agent called every service it should have called, that every service is honest, or that unauthenticated log indexes returned complete results. Those limits are intentional and documented in the spec.
189
149
 
190
- An owner verifier does this when reconstructing activity:
150
+ ## Learn More
191
151
 
192
- 1. Compute `sello_token_ref` from the same raw compact JWS bytes.
193
- 2. Query every trusted log for matching receipts.
194
- 3. Confirm each receipt's `sello_log_url` exactly matches the log that returned the proof.
195
- 4. Verify log inclusion.
196
- 5. Resolve the service signing key from `kid`.
197
- 6. Apply revocation rules using log integrated time.
198
- 7. Verify the COSE signature.
199
- 8. Decrypt the HPKE payload.
200
- 9. Validate and display the receipt body.
152
+ - [SDK Quickstart](docs/sdk-quickstart.md): local dev, HTTP demo, self-hosted config, and hosted config.
153
+ - [SDKs](sdks/README.md): TypeScript and Python package layout.
154
+ - [Python SDK](sdks/python/README.md): Python package install command, scope, and test command.
155
+ - [Protocol Walkthrough](docs/protocol-walkthrough.md): the primitive receipt loop for implementers.
156
+ - [SPEC.md](SPEC.md): the Sello protocol draft.
157
+ - [Notarized Agents paper](https://arxiv.org/abs/2606.04193): design rationale, threat model, and prior art.
158
+ - [sdks/typescript/examples/mcp-minimal-server.ts](sdks/typescript/examples/mcp-minimal-server.ts): a small MCP-shaped integration.
159
+ - [docs/security-review.md](docs/security-review.md) and [docs/sdk-security-audit.md](docs/sdk-security-audit.md): current review notes.
201
160
 
202
- For most owners, Sello should fit as a pull-based audit tool: provide the token and owner key, then retrieve the verified trail from trusted logs.
161
+ The TypeScript SDK is published on [npm](https://www.npmjs.com/package/sello) and lives in [`sdks/typescript/`](sdks/typescript/). The Python SDK is published on [PyPI](https://pypi.org/project/sello/) and lives in [`sdks/python/`](sdks/python/). Live Rekor proof verification and production identity operations are still future work.
203
162
 
204
163
  ## Core Terms
205
164
 
@@ -229,7 +188,7 @@ Much of this prior work surfaced after Sello's design had already converged on s
229
188
 
230
189
  ## Feedback
231
190
 
232
- Issues and pull requests are welcome. This is an early draft; adversarial review is the point.
191
+ Issues and pull requests are welcome. This is an early draft; adversarial review is the point. See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution notes and [SECURITY.md](SECURITY.md) for sensitive reports.
233
192
 
234
193
  ## License
235
194
 
package/dist/cli/sello.js CHANGED
@@ -1,7 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { createServer, } from "node:http";
4
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
+ import {
5
+ appendFileSync,
6
+ existsSync,
7
+ mkdirSync,
8
+ readFileSync,
9
+ writeFileSync,
10
+ } from "node:fs";
5
11
  import { dirname, join } from "node:path";
6
12
 
7
13
  import { generateEd25519KeyPair } from "../cose/sign1.js";
@@ -163,7 +169,7 @@ async function devCommand(args ) {
163
169
  "calendar.example.com/mcp/v1";
164
170
  const logEndpoint = `http://localhost:${port}/api`;
165
171
  const logUrl = toCanonicalLogUrl(`http://localhost:${port}/api`);
166
- const state = createDevState({ serviceId, logUrl, logEndpoint });
172
+ const state = loadOrCreateDevState({ serviceId, logUrl, logEndpoint });
167
173
  saveDevState(state);
168
174
 
169
175
  if (dryRun) {
@@ -174,10 +180,12 @@ async function devCommand(args ) {
174
180
  }
175
181
 
176
182
  const log = new MockTransparencyLog(logUrl);
183
+ const logPath = devLogPath();
184
+ const loadedEntries = loadDevLogEntries(log, logPath);
177
185
  const registry = parseRegistry(textEncoder.encode(state.registryJson));
178
186
  const server = createServer(async (request, response) => {
179
187
  try {
180
- await handleDevRequest({ request, response, log, state, registry });
188
+ await handleDevRequest({ request, response, log, logPath, state, registry });
181
189
  } catch (error) {
182
190
  sendJson(response, 500, {
183
191
  error: error instanceof Error ? error.message : String(error),
@@ -187,6 +195,9 @@ async function devCommand(args ) {
187
195
 
188
196
  server.listen(port, () => {
189
197
  printDevConfig(port, state);
198
+ console.log("");
199
+ console.log(`Local dev log: ${logPath}`);
200
+ console.log(`Loaded ${devLogEntryCountLabel(loadedEntries)}.`);
190
201
  });
191
202
  }
192
203
 
@@ -364,9 +375,10 @@ async function handleDevRequest(input
364
375
 
365
376
 
366
377
 
378
+
367
379
 
368
380
  ) {
369
- const { request, response, log, state, registry } = input;
381
+ const { request, response, log, logPath, state, registry } = input;
370
382
  const url = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`);
371
383
 
372
384
  if (request.method === "GET" && (url.pathname === "/" || url.pathname === "/actions")) {
@@ -391,6 +403,7 @@ async function handleDevRequest(input
391
403
  decodeBase64url(body.envelope, "envelope"),
392
404
  typeof body.integratedTime === "string" ? body.integratedTime : undefined,
393
405
  );
406
+ appendDevLogEntry(logPath, entry);
394
407
  sendJson(response, 200, serializeEntry(entry));
395
408
  return;
396
409
  }
@@ -412,6 +425,24 @@ async function handleDevRequest(input
412
425
  sendJson(response, 404, { error: "not found" });
413
426
  }
414
427
 
428
+ function loadOrCreateDevState(input
429
+
430
+
431
+
432
+ ) {
433
+ const existing = loadDevStateIfPresent();
434
+ if (
435
+ existing &&
436
+ existing.serviceId === input.serviceId &&
437
+ existing.logUrl === input.logUrl &&
438
+ existing.logEndpoint === input.logEndpoint
439
+ ) {
440
+ return existing;
441
+ }
442
+
443
+ return createDevState(input);
444
+ }
445
+
415
446
  function verifyDevActions(input
416
447
 
417
448
 
@@ -527,6 +558,54 @@ function devStatePath() {
527
558
  return join(process.cwd(), ".sello", "dev.json");
528
559
  }
529
560
 
561
+ function devLogPath() {
562
+ return join(process.cwd(), ".sello", "dev-log.jsonl");
563
+ }
564
+
565
+ function loadDevLogEntries(log , path ) {
566
+ if (!existsSync(path)) {
567
+ return 0;
568
+ }
569
+
570
+ const lines = textDecoder
571
+ .decode(readFileBytes(path))
572
+ .split(/\r?\n/)
573
+ .filter((line) => line.trim().length > 0);
574
+
575
+ let loaded = 0;
576
+ for (const [index, line] of lines.entries()) {
577
+ let entry;
578
+ try {
579
+ entry = deserializeEntry(JSON.parse(line));
580
+ if (entry.logUrl !== log.logUrl) {
581
+ continue;
582
+ }
583
+ log.append(entry.envelope, entry.integratedTime);
584
+ loaded += 1;
585
+ } catch (error) {
586
+ throw new TypeError(
587
+ `invalid local dev log entry ${index + 1} in ${path}: ${
588
+ error instanceof Error ? error.message : String(error)
589
+ }`,
590
+ );
591
+ }
592
+ }
593
+
594
+ return loaded;
595
+ }
596
+
597
+ function appendDevLogEntry(
598
+ path ,
599
+ entry ,
600
+ ) {
601
+ mkdirSync(dirname(path), { recursive: true });
602
+ appendFileSync(path, `${JSON.stringify(serializeEntry(entry))}\n`, { mode: 0o600 });
603
+ }
604
+
605
+ function devLogEntryCountLabel(count ) {
606
+ return count === 1 ? "1 encrypted receipt" : `${count} encrypted receipts`;
607
+ }
608
+
530
609
  function readFileBytes(path ) {
531
610
  return new Uint8Array(readFileSync(path));
532
611
  }
@@ -323,7 +323,7 @@ We present first-party microbenchmarks of the cryptographic operations Sello per
323
323
 
324
324
  ### 7.1 Methodology
325
325
 
326
- The reference implementation [@sello-repo] ships with a benchmark tool (`src/cli/bench.ts`) that exercises the full receipt lifecycle. For each iteration the bench: (1) constructs a verified compact JWS authorization token, (2) creates a Sello receipt at the service side (HPKE seal of the CBOR-encoded receipt body to the owner's X25519 public key, followed by COSE_Sign1 signing with the service's Ed25519 private key), (3) submits the signed envelope to an in-process mock transparency log that returns an inclusion proof, and (4) performs full owner-side verification (proof verification, registry resolution, COSE_Sign1 signature verification, HPKE open, and receipt body validation). The mock log replaces network submission with deterministic in-process bookkeeping; this isolates the cryptographic costs from log-operator-specific network latency. Log submission latency to a hosted public log is discussed separately in §7.3.
326
+ The reference implementation [@sello-repo] ships with a benchmark tool (`sdks/typescript/src/cli/bench.ts`) that exercises the full receipt lifecycle. For each iteration the bench: (1) constructs a verified compact JWS authorization token, (2) creates a Sello receipt at the service side (HPKE seal of the CBOR-encoded receipt body to the owner's X25519 public key, followed by COSE_Sign1 signing with the service's Ed25519 private key), (3) submits the signed envelope to an in-process mock transparency log that returns an inclusion proof, and (4) performs full owner-side verification (proof verification, registry resolution, COSE_Sign1 signature verification, HPKE open, and receipt body validation). The mock log replaces network submission with deterministic in-process bookkeeping; this isolates the cryptographic costs from log-operator-specific network latency. Log submission latency to a hosted public log is discussed separately in §7.3.
327
327
 
328
328
  The cryptographic primitives are Node.js bindings to the system OpenSSL: X25519 key agreement and Ed25519 signing via `node:crypto`, ChaCha20-Poly1305 AEAD via `createCipheriv`, HKDF-SHA256 via `createHmac`, and SHA-256 via `createHash`. HPKE is implemented per RFC 9180 base mode with the suite specified in §2.1; the implementation is pinned to RFC 9180 Appendix A.2.1 test vectors and validated in the test suite.
329
329
 
@@ -994,7 +994,7 @@ on the Sello protocol itself.
994
994
  \subsubsection{7.1 Methodology}\label{methodology}}
995
995
 
996
996
  The reference implementation \citep{sello-repo} ships with a benchmark
997
- tool (\texttt{src/cli/bench.ts}) that exercises the full receipt
997
+ tool (\texttt{sdks/typescript/src/cli/bench.ts}) that exercises the full receipt
998
998
  lifecycle. For each iteration the bench: (1) constructs a verified
999
999
  compact JWS authorization token, (2) creates a Sello receipt at the
1000
1000
  service side (HPKE seal of the CBOR-encoded receipt body to the owner's
@@ -0,0 +1,74 @@
1
+ # Protocol Walkthrough
2
+
3
+ This guide is for people implementing Sello itself or studying the receipt flow below the SDK. If you only want to try Sello or add it to a service, start with the README or [`docs/sdk-quickstart.md`](sdk-quickstart.md).
4
+
5
+ ## Goal
6
+
7
+ Build the smallest complete Sello loop:
8
+
9
+ 1. One mock action happens.
10
+ 2. The service creates one encrypted signed receipt.
11
+ 3. A mock transparency log stores it under `sello_token_ref`.
12
+ 4. The owner fetches, verifies, and decrypts it.
13
+
14
+ Run the complete loop first:
15
+
16
+ ```bash
17
+ node --run demo
18
+ ```
19
+
20
+ The runnable source is [`sdks/typescript/src/cli/demo.ts`](../sdks/typescript/src/cli/demo.ts).
21
+
22
+ ## Local Pieces
23
+
24
+ The primitive loop needs these pieces:
25
+
26
+ | Piece | Local helper | Why it exists |
27
+ |-------|--------------|---------------|
28
+ | Owner key | `generateHpkeKeyPair()` | The public key goes in the token. The private key decrypts receipts later. |
29
+ | Service key | `generateEd25519KeyPair()` | The service signs receipts for actions it observed. |
30
+ | Token issuer key | `generateEd25519KeyPair()` | The issuer signs the mock agent token. |
31
+ | Transparency log | `new MockTransparencyLog(...)` | The log stores encrypted signed receipts by `sello_token_ref`. |
32
+ | Service registry | `loadSignedRegistry(...)` | The owner uses it to resolve service public keys and revocation status. |
33
+
34
+ Start with the owner key:
35
+
36
+ ```ts
37
+ import { base64urlEncode, generateHpkeKeyPair } from "sello";
38
+
39
+ const owner = generateHpkeKeyPair();
40
+ const ownerHpkePk = base64urlEncode(owner.publicKey);
41
+ ```
42
+
43
+ Put `ownerHpkePk` in the token's `owner_hpke_pk` claim. Keep `owner.privateKey` for the owner verifier.
44
+
45
+ Then create the other local pieces:
46
+
47
+ ```ts
48
+ import { generateEd25519KeyPair, MockTransparencyLog } from "sello";
49
+
50
+ const service = generateEd25519KeyPair();
51
+ const tokenIssuer = generateEd25519KeyPair();
52
+ const trustRoot = generateEd25519KeyPair();
53
+ const log = new MockTransparencyLog("https://rekor.example.com/api");
54
+ ```
55
+
56
+ ## Flow
57
+
58
+ 1. Sign a compact JWS token with `signSelloJwsToken(...)`.
59
+ 2. Include `owner_hpke_pk` and `sello_logs` in the signed token.
60
+ 3. Create a receipt with `createReceiptFromJwsToken(...)`.
61
+ 4. Verify with `verifyReceipts(...)` using the same raw token, owner private key, mock log, and service registry.
62
+
63
+ You know the loop works when the owner can print one verified receipt:
64
+
65
+ ```json
66
+ {
67
+ "service": "example.com/tool/v1",
68
+ "action-type": "tools/call",
69
+ "result-status": "success",
70
+ "verified": true
71
+ }
72
+ ```
73
+
74
+ Do not start with Rekor, MCP middleware, distributed identity, or CLI polish. Those become much easier once one local receipt works end to end.
@@ -1,6 +1,6 @@
1
1
  # Release Checklist
2
2
 
3
- Use this checklist before publishing a Sello npm release.
3
+ Use this checklist before publishing a Sello release.
4
4
 
5
5
  ## Preflight
6
6
 
@@ -8,6 +8,7 @@ Use this checklist before publishing a Sello npm release.
8
8
  - Confirm `node -v` is `v22.7.0` or newer.
9
9
  - If multiple Node versions are installed, confirm `PATH` resolves `node` to Node 22.7 or newer before running package scripts.
10
10
  - Confirm `package.json` has the intended version.
11
+ - Confirm `sdks/python/pyproject.toml` has the intended version when publishing Python.
11
12
  - Confirm `README.md` and `docs/sdk-quickstart.md` match the current CLI and examples.
12
13
  - Confirm the paper link and local PDF are current, if the paper changed.
13
14
 
@@ -28,8 +29,8 @@ node -v # must be v22.7.0 or newer
28
29
  node --run test
29
30
  node --run package:test
30
31
  npm pack --dry-run
31
- node --experimental-strip-types src/cli/sello.ts --help
32
- node --experimental-strip-types src/cli/sello.ts dev --dry-run
32
+ node --experimental-strip-types sdks/typescript/src/cli/sello.ts --help
33
+ node --experimental-strip-types sdks/typescript/src/cli/sello.ts dev --dry-run
33
34
  ```
34
35
 
35
36
  ## npm Verification
@@ -42,6 +43,18 @@ npm publish --dry-run
42
43
 
43
44
  If the package name is already published, confirm the local version is greater than the registry version before publishing.
44
45
 
46
+ ## PyPI Verification
47
+
48
+ ```bash
49
+ python -m pip install --upgrade build twine
50
+ cd sdks/python
51
+ rm -rf dist
52
+ python -m build
53
+ python -m twine check dist/*
54
+ ```
55
+
56
+ The PyPI project uses trusted publishing from `.github/workflows/release.yml`.
57
+
45
58
  ## Publish
46
59
 
47
60
  ```bash
@@ -53,5 +66,6 @@ git push origin main --tags
53
66
  After publishing:
54
67
 
55
68
  - Confirm `npm view sello version` shows the new version.
69
+ - Confirm `python -m pip index versions sello` shows the new version after PyPI publishing.
56
70
  - Confirm `npx sello --help` works from a clean temp directory.
57
71
  - Confirm GitHub Actions passes on `main`.
@@ -2,7 +2,15 @@
2
2
 
3
3
  Sello's SDK is designed to make the first receipt easy: wrap a tool handler, run it, and inspect verified actions.
4
4
 
5
- Requires Node.js 22.7 or newer.
5
+ The local demo CLI and action viewer require Node.js 22.7 or newer. The Python SDK requires Python 3.9 or newer.
6
+
7
+ ## Install
8
+
9
+ TypeScript:
10
+
11
+ ```bash
12
+ npm install sello
13
+ ```
6
14
 
7
15
  ```ts
8
16
  import { sello } from "sello";
@@ -14,6 +22,22 @@ export const createEvent = receipts.tool("calendar.create_event", async (request
14
22
  });
15
23
  ```
16
24
 
25
+ Python:
26
+
27
+ ```bash
28
+ pip install sello
29
+ ```
30
+
31
+ ```py
32
+ import sello
33
+
34
+ receipts = sello.service()
35
+
36
+ @receipts.tool("calendar.create_event")
37
+ def create_event(request):
38
+ return calendar.events.create(request)
39
+ ```
40
+
17
41
  The service process emits receipts. It does not need the owner private key.
18
42
 
19
43
  ## Local Development
@@ -48,6 +72,18 @@ npx --yes sello call-http-demo
48
72
  npx --yes sello actions
49
73
  ```
50
74
 
75
+ ## What Just Happened?
76
+
77
+ `sello dev` created local development keys, a demo authorization token, a service registry, and a local transparency log. The wrapped tool or route verified the token before running your handler. After the handler returned, Sello signed an encrypted receipt for the observed action and submitted it to the local log. The log stored encrypted receipt data, not plaintext action details. `sello actions` used the owner key from local dev state to fetch, verify, decrypt, and print the action.
78
+
79
+ Local dev state lives under `.sello/`. The encrypted dev log is stored in `.sello/dev-log.jsonl`, so receipts survive restarting `sello dev` without being committed to git.
80
+
81
+ ## Troubleshooting
82
+
83
+ - **Port already in use:** run `npx sello dev --port 8791`.
84
+ - **No actions found:** make sure `sello dev` is running from the same project folder where you emitted the receipt.
85
+ - **Missing token:** run `npx sello dev` first so `.sello/dev.json` exists.
86
+
51
87
  Inside this repo, start the local log and action viewer:
52
88
 
53
89
  ```bash
@@ -74,9 +110,9 @@ or open:
74
110
  http://localhost:8787/actions
75
111
  ```
76
112
 
77
- Both examples read `.sello/dev.json`, wrap a fake calendar handler, submit one encrypted receipt, and let the owner verify it locally.
113
+ Both examples read `.sello/dev.json`, wrap a mock calendar handler, submit one encrypted receipt, and let the owner verify it locally.
78
114
 
79
- For the smallest production-shaped MCP boundary, see [`examples/mcp-minimal-server.ts`](../examples/mcp-minimal-server.ts). It wraps one `tools/call` handler with `sello.service()` and leaves unknown tools unreceipted.
115
+ For the smallest production-shaped MCP boundary, see [`sdks/typescript/examples/mcp-minimal-server.ts`](../sdks/typescript/examples/mcp-minimal-server.ts). It wraps one `tools/call` handler with `sello.service()` and leaves unknown tools unreceipted.
80
116
 
81
117
  For your own tool server, copy the printed service env:
82
118
 
@@ -7,7 +7,7 @@ These notes cover the first Stripe-style SDK implementation pass. They are writt
7
7
  - Service emission and owner viewing are documented as separate environments.
8
8
  - The service process is not required to hold `SELLO_OWNER_KEY`.
9
9
  - Hosted `sello.build` is described as optional convenience, not as a protocol dependency.
10
- - Deferred production features are named in `docs/sdk-build-plan.md`.
10
+ - Deferred production features are named in the README, quickstart, and this audit note.
11
11
 
12
12
  ## Phase 1: Env-First Facade
13
13
 
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "sello",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Reference implementation of the Sello protocol for service-signed AI agent receipts.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
7
  "sideEffects": false,
8
8
  "exports": {
9
9
  ".": "./dist/index.js",
10
- "./fixtures/vectors/sello-v0.1.json": "./fixtures/vectors/sello-v0.1.json",
10
+ "./fixtures/vectors/sello-v0.1.json": "./sdks/typescript/fixtures/vectors/sello-v0.1.json",
11
11
  "./spec": "./SPEC.md",
12
12
  "./package.json": "./package.json"
13
13
  },
@@ -18,27 +18,27 @@
18
18
  },
19
19
  "files": [
20
20
  "dist",
21
- "examples",
22
- "src",
23
- "fixtures/vectors",
21
+ "sdks/typescript/examples",
22
+ "sdks/typescript/src",
23
+ "sdks/typescript/fixtures/vectors",
24
24
  "docs",
25
25
  "SPEC.md",
26
26
  "README.md",
27
27
  "LICENSE"
28
28
  ],
29
29
  "scripts": {
30
- "actions": "node --experimental-strip-types src/cli/sello.ts actions",
31
- "bench": "node --experimental-strip-types src/cli/bench.ts",
32
- "demo": "node --experimental-strip-types src/cli/demo.ts",
33
- "dev": "node --experimental-strip-types src/cli/sello.ts dev",
34
- "example:mcp:minimal": "node --test --experimental-strip-types test/examples/mcp-minimal-server.test.ts",
35
- "example:mcp": "node --experimental-strip-types examples/mcp-tool-server.ts",
36
- "example:tool": "node --experimental-strip-types examples/quickstart-tool.ts",
37
- "build": "node --disable-warning=ExperimentalWarning scripts/build-dist.mjs",
38
- "package:test": "node scripts/package-smoke.mjs",
39
- "pack:check": "npm pack --dry-run",
40
- "prepack": "node --disable-warning=ExperimentalWarning scripts/build-dist.mjs",
41
- "test": "node --test --experimental-strip-types"
30
+ "actions": "node --experimental-strip-types sdks/typescript/src/cli/sello.ts actions",
31
+ "bench": "node --experimental-strip-types sdks/typescript/src/cli/bench.ts",
32
+ "demo": "node --experimental-strip-types sdks/typescript/src/cli/demo.ts",
33
+ "dev": "node --experimental-strip-types sdks/typescript/src/cli/sello.ts dev",
34
+ "example:mcp:minimal": "node --test --experimental-strip-types sdks/typescript/test/examples/mcp-minimal-server.test.ts",
35
+ "example:mcp": "node --experimental-strip-types sdks/typescript/examples/mcp-tool-server.ts",
36
+ "example:tool": "node --experimental-strip-types sdks/typescript/examples/quickstart-tool.ts",
37
+ "build": "node --disable-warning=ExperimentalWarning sdks/typescript/scripts/build-dist.mjs",
38
+ "package:test": "node sdks/typescript/scripts/package-smoke.mjs",
39
+ "pack:check": "node sdks/typescript/scripts/pack-check.mjs",
40
+ "prepack": "node --disable-warning=ExperimentalWarning sdks/typescript/scripts/build-dist.mjs",
41
+ "test": "cd sdks/typescript && node --test --experimental-strip-types"
42
42
  },
43
43
  "engines": {
44
44
  "node": ">=22.7.0"
package/sdks/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # Sello SDKs
2
+
3
+ The protocol, paper, and project docs live at the repository root. Language SDKs live here:
4
+
5
+ - [`typescript/`](typescript/): npm package source, CLI, examples, fixtures, and Node tests.
6
+ - [`python/`](python/): Python package source and tests.
7
+
8
+ Root commands still work from the repository top level:
9
+
10
+ ```bash
11
+ node --run test
12
+ node --run package:test
13
+ python -m pip install ./sdks/python
14
+ python -m unittest discover -s sdks/python/tests
15
+ ```
16
+
17
+ This keeps the first screen of the repo focused on the protocol while giving each language a predictable home.
@@ -1,7 +1,13 @@
1
1
  #!/usr/bin/env -S node --experimental-strip-types
2
2
 
3
3
  import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
4
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
+ import {
5
+ appendFileSync,
6
+ existsSync,
7
+ mkdirSync,
8
+ readFileSync,
9
+ writeFileSync,
10
+ } from "node:fs";
5
11
  import { dirname, join } from "node:path";
6
12
 
7
13
  import { generateEd25519KeyPair } from "../cose/sign1.ts";
@@ -164,7 +170,7 @@ async function devCommand(args: string[]): Promise<void> {
164
170
  "calendar.example.com/mcp/v1";
165
171
  const logEndpoint = `http://localhost:${port}/api`;
166
172
  const logUrl = toCanonicalLogUrl(`http://localhost:${port}/api`);
167
- const state = createDevState({ serviceId, logUrl, logEndpoint });
173
+ const state = loadOrCreateDevState({ serviceId, logUrl, logEndpoint });
168
174
  saveDevState(state);
169
175
 
170
176
  if (dryRun) {
@@ -175,10 +181,12 @@ async function devCommand(args: string[]): Promise<void> {
175
181
  }
176
182
 
177
183
  const log = new MockTransparencyLog(logUrl);
184
+ const logPath = devLogPath();
185
+ const loadedEntries = loadDevLogEntries(log, logPath);
178
186
  const registry = parseRegistry(textEncoder.encode(state.registryJson));
179
187
  const server = createServer(async (request, response) => {
180
188
  try {
181
- await handleDevRequest({ request, response, log, state, registry });
189
+ await handleDevRequest({ request, response, log, logPath, state, registry });
182
190
  } catch (error) {
183
191
  sendJson(response, 500, {
184
192
  error: error instanceof Error ? error.message : String(error),
@@ -188,6 +196,9 @@ async function devCommand(args: string[]): Promise<void> {
188
196
 
189
197
  server.listen(port, () => {
190
198
  printDevConfig(port, state);
199
+ console.log("");
200
+ console.log(`Local dev log: ${logPath}`);
201
+ console.log(`Loaded ${devLogEntryCountLabel(loadedEntries)}.`);
191
202
  });
192
203
  }
193
204
 
@@ -364,10 +375,11 @@ async function handleDevRequest(input: {
364
375
  request: IncomingMessage;
365
376
  response: ServerResponse;
366
377
  log: MockTransparencyLog;
378
+ logPath: string;
367
379
  state: DevState;
368
380
  registry: ReturnType<typeof parseRegistry>;
369
381
  }): Promise<void> {
370
- const { request, response, log, state, registry } = input;
382
+ const { request, response, log, logPath, state, registry } = input;
371
383
  const url = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`);
372
384
 
373
385
  if (request.method === "GET" && (url.pathname === "/" || url.pathname === "/actions")) {
@@ -392,6 +404,7 @@ async function handleDevRequest(input: {
392
404
  decodeBase64url(body.envelope, "envelope"),
393
405
  typeof body.integratedTime === "string" ? body.integratedTime : undefined,
394
406
  );
407
+ appendDevLogEntry(logPath, entry);
395
408
  sendJson(response, 200, serializeEntry(entry));
396
409
  return;
397
410
  }
@@ -413,6 +426,24 @@ async function handleDevRequest(input: {
413
426
  sendJson(response, 404, { error: "not found" });
414
427
  }
415
428
 
429
+ function loadOrCreateDevState(input: {
430
+ serviceId: string;
431
+ logUrl: CanonicalLogUrl;
432
+ logEndpoint: string;
433
+ }): DevState {
434
+ const existing = loadDevStateIfPresent();
435
+ if (
436
+ existing &&
437
+ existing.serviceId === input.serviceId &&
438
+ existing.logUrl === input.logUrl &&
439
+ existing.logEndpoint === input.logEndpoint
440
+ ) {
441
+ return existing;
442
+ }
443
+
444
+ return createDevState(input);
445
+ }
446
+
416
447
  function verifyDevActions(input: {
417
448
  log: MockTransparencyLog;
418
449
  state: DevState;
@@ -528,6 +559,54 @@ function devStatePath(): string {
528
559
  return join(process.cwd(), ".sello", "dev.json");
529
560
  }
530
561
 
562
+ function devLogPath(): string {
563
+ return join(process.cwd(), ".sello", "dev-log.jsonl");
564
+ }
565
+
566
+ function loadDevLogEntries(log: MockTransparencyLog, path: string): number {
567
+ if (!existsSync(path)) {
568
+ return 0;
569
+ }
570
+
571
+ const lines = textDecoder
572
+ .decode(readFileBytes(path))
573
+ .split(/\r?\n/)
574
+ .filter((line) => line.trim().length > 0);
575
+
576
+ let loaded = 0;
577
+ for (const [index, line] of lines.entries()) {
578
+ let entry;
579
+ try {
580
+ entry = deserializeEntry(JSON.parse(line));
581
+ if (entry.logUrl !== log.logUrl) {
582
+ continue;
583
+ }
584
+ log.append(entry.envelope, entry.integratedTime);
585
+ loaded += 1;
586
+ } catch (error) {
587
+ throw new TypeError(
588
+ `invalid local dev log entry ${index + 1} in ${path}: ${
589
+ error instanceof Error ? error.message : String(error)
590
+ }`,
591
+ );
592
+ }
593
+ }
594
+
595
+ return loaded;
596
+ }
597
+
598
+ function appendDevLogEntry(
599
+ path: string,
600
+ entry: ReturnType<MockTransparencyLog["append"]>,
601
+ ): void {
602
+ mkdirSync(dirname(path), { recursive: true });
603
+ appendFileSync(path, `${JSON.stringify(serializeEntry(entry))}\n`, { mode: 0o600 });
604
+ }
605
+
606
+ function devLogEntryCountLabel(count: number): string {
607
+ return count === 1 ? "1 encrypted receipt" : `${count} encrypted receipts`;
608
+ }
609
+
531
610
  function readFileBytes(path: string): Uint8Array {
532
611
  return new Uint8Array(readFileSync(path));
533
612
  }
@@ -1,214 +0,0 @@
1
- # Stripe-Style Sello SDK Integration Plan
2
-
3
- ## Summary
4
-
5
- Build the first Sello SDK around a complete, Stripe-like loop:
6
-
7
- 1. Add one import.
8
- 2. Call `sello.service()`.
9
- 3. Wrap an existing tool handler.
10
- 4. Run the tool.
11
- 5. See verified logged actions.
12
-
13
- Quickstart service code:
14
-
15
- ```ts
16
- import { sello } from "sello";
17
-
18
- const receipts = sello.service();
19
-
20
- export const createEvent = receipts.tool("calendar.create_event", async (request) => {
21
- return calendar.events.create(request);
22
- });
23
- ```
24
-
25
- Quickstart viewing:
26
-
27
- ```bash
28
- npx sello actions --token <agent-token>
29
- ```
30
-
31
- Local dev can also show:
32
-
33
- ```text
34
- http://localhost:8787/actions
35
- ```
36
-
37
- Sello must work without `sello.build`. Self-hosting is first-class. Hosted `sello.build` is optional convenience.
38
-
39
- ## Configuration Model
40
-
41
- Keep service emission and owner viewing separate.
42
-
43
- ### Service Runtime Env
44
-
45
- Used by the app/tool server that emits receipts.
46
-
47
- Self-hosted development:
48
-
49
- ```bash
50
- SELLO_SERVICE_ID=calendar.example.com/mcp/v1
51
- SELLO_SERVICE_KEY=sello_dev_...
52
- SELLO_TOKEN_ISSUER_PUBLIC_KEY=...
53
- SELLO_LOG_URL=https://localhost:8787/api
54
- SELLO_LOG_ENDPOINT=http://localhost:8787/api
55
- SELLO_SUBMIT_MODE=background
56
- ```
57
-
58
- Self-hosted production:
59
-
60
- ```bash
61
- SELLO_SERVICE_ID=calendar.example.com/mcp/v1
62
- SELLO_SERVICE_KEY=sello_live_local_...
63
- SELLO_TOKEN_ISSUER_JWKS=https://auth.example.com/.well-known/jwks.json
64
- SELLO_LOG_URL=https://logs.example.com/api
65
- SELLO_SUBMIT_MODE=background
66
- ```
67
-
68
- Hosted `sello.build`:
69
-
70
- ```bash
71
- SELLO_SECRET_KEY=sello_test_...
72
- SELLO_SECRET_KEY=sello_live_...
73
- ```
74
-
75
- Hosted mode uses one server-side secret to fetch service config and enable local receipt signing by default. Managed remote signing must be a later explicit opt-in because it changes the trust model.
76
-
77
- ### Viewer Runtime Env
78
-
79
- Used by the owner CLI, local UI, or hosted dashboard to verify/decrypt actions.
80
-
81
- ```bash
82
- SELLO_OWNER_KEY=sello_owner_...
83
- SELLO_REGISTRY_URL=https://registry.example.com/sello.json
84
- SELLO_REGISTRY_SIGNATURE=...
85
- SELLO_REGISTRY_TRUST_ROOT_PUBLIC_KEY=...
86
- SELLO_LOG_URL=https://logs.example.com/api
87
- ```
88
-
89
- The owner key must not be required in the service process.
90
-
91
- ## Public API
92
-
93
- ```ts
94
- const receipts = sello.service();
95
- const receipts = sello.service("calendar.example.com/mcp/v1");
96
- const receipts = sello.service({ service, serviceKey, tokenIssuer, log, submit });
97
- ```
98
-
99
- ```ts
100
- const wrapped = receipts.tool(actionType, handler, options);
101
- await receipts.flush();
102
- ```
103
-
104
- Tool behavior:
105
-
106
- - Verify token before handler execution.
107
- - Emit `success`, `error`, or `denied` receipts.
108
- - Return handler response unchanged.
109
- - Rethrow handler errors.
110
- - Use canonical JSON hashing by default.
111
- - Submit in background by default.
112
- - Support `submit.mode: "await"` for strict durability.
113
- - Support explicit custom log adapters for self-hosting.
114
-
115
- ## Logged Actions UX
116
-
117
- ```bash
118
- npx sello actions --token <agent-token>
119
- ```
120
-
121
- Behavior:
122
-
123
- - Loads viewer config, not service config.
124
- - Computes `sello_token_ref`.
125
- - Queries trusted logs.
126
- - Verifies inclusion, service signature, revocation, HPKE decryption, and receipt body shape.
127
- - Prints verified actions and rejected receipts.
128
-
129
- Local dev:
130
-
131
- ```bash
132
- npx sello dev
133
- npx sello actions
134
- ```
135
-
136
- In dev mode, `sello dev` remembers the latest dev token and owner key in ignored local state so `npx sello actions` works without extra flags.
137
-
138
- Privacy rule:
139
-
140
- - Public logs store encrypted receipts.
141
- - Viewing action details requires the owner private key or delegated viewer key.
142
- - Self-hosted CLI decrypts locally.
143
- - Hosted dashboard must use client-side decryption or explicit delegated viewer keys.
144
-
145
- ## Phase Plan
146
-
147
- ### Phase 0: Baseline And SDK Contract
148
-
149
- - Write SDK examples into docs first.
150
- - Define env names, config precedence, and friendly errors.
151
- - Document service env versus viewer env.
152
- - Record deferred items: production Rekor proof verification, hosted dashboard, managed signing, durable queue.
153
- - Run `node --run test` and `node --run pack:check`.
154
- - Security audit: check the SDK contract does not weaken receiver-side signing or owner-side decryption.
155
-
156
- ### Phase 1: Env-First SDK Facade
157
-
158
- - Add `sello.service()`.
159
- - Add env config loader.
160
- - Add key normalization for `Uint8Array` and encoded string inputs.
161
- - Add explicit config override path.
162
- - Add friendly missing-config errors.
163
- - Tests: env loading, service id override, explicit config override, hosted config selection, no owner key required for service emission.
164
- - Security audit: check secret handling, env precedence, errors, logging, and hosted/self-hosted boundaries.
165
-
166
- ### Phase 2: Receipt Build/Submit Split And Background Publisher
167
-
168
- - Refactor receipt creation into build/encrypt/sign first, append second.
169
- - Keep existing `createReceipt()` behavior by composing build + append.
170
- - Add bounded background publisher with concurrency, `flush()`, `onSubmitError`, and `onDrop`.
171
- - Default to background submission.
172
- - Support `submit.mode: "await"`.
173
- - Tests: existing receipt tests, built envelope verification, background latency, `flush()`, overflow, failures, await mode, concurrency.
174
- - Security audit: check durability wording, queue bounds, memory pressure, failure handling, and unchanged cryptography.
175
-
176
- ### Phase 3: Tool Wrapper MVP
177
-
178
- - Implement `receipts.tool(actionType, handler, options)`.
179
- - Default token extraction, input hashing, output hashing, and error hashing.
180
- - Support custom token extraction and canonicalizers.
181
- - Preserve handler semantics exactly.
182
- - Tests: success, error, denied, invalid token, custom extraction, custom canonicalizers, unchanged response, MCP compatibility.
183
- - Security audit: check token verification ordering, plaintext leakage, and service identity binding.
184
-
185
- ### Phase 4: Logs And Action Viewing
186
-
187
- - Add `sello.logs.memory(url)` for local tests/dev.
188
- - Add `sello.logs.http(url, options?)` for self-hosted Sello-compatible log servers.
189
- - Add `npx sello actions --token <token>`.
190
- - Add local dev `/actions` page.
191
- - Action viewing uses owner-side verification and decryption.
192
- - Tests: memory log, HTTP append/query, CLI output, dev-mode token fallback, rejected reason codes, `/actions` states, no action details without owner/delegated key.
193
- - Security audit: check owner-side decryption, no plaintext in public logs, and safe rejected-receipt display.
194
-
195
- ### Phase 5: Docs Polish And First-Run Flow
196
-
197
- - Add README section: **Add Sello in a Few Lines**.
198
- - Add `docs/sdk-quickstart.md`.
199
- - Document self-hosted development, self-hosted production, `sello.build` development, and `sello.build` production.
200
- - Document action viewing, token requirement, and privacy model.
201
- - Keep advanced explicit config separate from quickstart.
202
- - Tests: `node --run test`, `node --run pack:check`, manual quickstart run, docs do not require `sello.build`.
203
- - Security audit: review README, quickstart, CLI help, and examples with the full plan in hand.
204
-
205
- ## Assumptions
206
-
207
- - MVP ships inside the current `sello` package.
208
- - One excellent generic tool wrapper beats many shallow adapters.
209
- - Testing is required for every phase before the next phase starts.
210
- - A security audit is required after every phase before the next phase starts.
211
- - Each auditor receives the full plan, current phase scope, deferred-item list, and threat model.
212
- - The service process emits receipts but does not need the owner private key.
213
- - Local/self-hosted usage is first-class.
214
- - `sello.build` is optional convenience, not a protocol requirement.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes