@allorc/a423-sdk 0.3.0 → 0.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
@@ -1,355 +1,103 @@
1
- # Reference escrow in 0.3.0
1
+ # A423 SDK — 0.4.0
2
2
 
3
- The optional `@allorc/a423-sdk/escrow` module now provides a deployable ERC-20 reference contract and matching ethers adapter. It implements actual on-chain custody, worker-signed funding consent, submission and verifier approval before release, expiry refunds, disputes, arbitration splits and an agreed deadline fallback. It does not deploy or move funds automatically.
3
+ Experimental Node.js/Next.js middleware for agent tasks, agreements and escrow. Version 0.4 adds working persistence, authority credentials and operational services around the configurable workflow and reference contract. Installing the SDK never publishes endpoints or deploys a contract automatically.
4
4
 
5
- See [ESCROW.md](./ESCROW.md) for exact terms, Next.js setup, wallet authorisation, transaction journal requirements, lifecycle inputs, recovery and limitations. Use `escrowTransitions` with this adapter: the default generic dispute transition alone does not write to the chain. Core/workflow APIs remain available without ethers.
6
-
7
- # Configurable workflows in 0.2.0
8
-
9
- The new `@allorc/a423-sdk/workflow` entry point provides middleware for agent job lifecycles. It supports different task types and payment systems through application-supplied policies and adapters. It does not assume a particular blockchain, token, wallet, artifact format, database or AI framework. This is experimental software with a reference lifecycle; it does not promise every possible business workflow.
10
-
11
- The original root exports remain compatible. Choose the workflow entry point for the full lifecycle; do not mix its records with the original root API. See [integration and migration instructions](./INTEGRATION.md).
12
-
13
- ```ts
14
- import { createWorkflow, createWorkflowHandler } from '@allorc/a423-sdk/workflow';
15
-
16
- const workflow = createWorkflow({
17
- store: durableWorkflowStore,
18
- adapters: applicationAdapters,
19
- validateTerms: validateYourTerms,
20
- authorize: checkYourPolicy,
21
- });
22
- const handler = createWorkflowHandler({ workflow, authenticate: authenticateRequest });
23
- // app/api/a423/[...path]/route.ts
24
- export const runtime = 'nodejs';
25
- export { handler as GET, handler as POST };
26
- ```
27
-
28
- The names `durableWorkflowStore`, `applicationAdapters`, `validateYourTerms`, `checkYourPolicy` and `authenticateRequest` are application implementations, not SDK exports. Configure them once in a server module and import the handler into your route.
29
-
30
- ## Workflow coverage
31
-
32
- | Action | From | Default role | Result | Required adapter |
33
- |---|---|---|---|---|
34
- | create | — | requester | draft | none |
35
- | quote | draft / quoted / accepted | requester or worker | quoted, acceptances reset | none |
36
- | accept | quoted | requester and worker separately | accepted after both | none |
37
- | fund | accepted | requester | funded | fund |
38
- | start | funded | worker | running | start (funding/work gate) |
39
- | submit | running | worker | submitted | submit |
40
- | verify | submitted | verifier | verified | verify |
41
- | release | verified | verifier | paid | release |
42
- | refund | funded / running | requester | refunded | refund |
43
- | cancel | draft / quoted / accepted | requester | cancelled | none |
44
- | expire | draft / quoted / accepted | requester or worker | expired | expire |
45
- | dispute | submitted / verified | any of the three parties | disputed | none |
46
- | resolve | disputed | verifier | resolved | resolve |
47
-
48
- `resolved` records a completed resolution process; it does not mean paid or refunded. The resolution adapter must record its actual outcome in evidence. Refund eligibility, quote expiry, deadlines, spending limits, asset rules and verification semantics belong in application policies/adapters. There is no automatic timer. A scheduler calls the API using an authenticated participant or an explicitly configured scheduler role.
49
-
50
- ### Terms and acceptance
51
-
52
- Use JSON terms for your domain: task specification, price, asset, raw amount, deadline, wallet addresses, verifier policy and artifact expectations. Put every term you want committed into this object, including parties and settlement configuration where relevant. The SDK does not invent or validate those domain fields; `validateTerms` should throw on invalid terms. Use decimal strings for raw token amounts, not floating point or JSON bigint.
53
-
54
- The SDK hashes sorted-key canonical JSON with SHA-256 (array order preserved, no whitespace). Hash inputs must be plain JSON; avoid cyclic structures and sparse arrays. This is the SDK's own format, not a claim of RFC 8785 compatibility. Names and IDs are case-sensitive. A quote stores revision, terms, hash and acceptances. Accept requires both `agreement_hash` and `revision`; editing a quote clears acceptance. Terms cannot be revised after funding begins under the default rules. Hashes establish integrity, not caller identity or legal consent.
55
-
56
- ### Adapter contract and recovery
57
-
58
- Each effect adapter is either an async function or `{ execute, reconcile }`. It receives `{ job, input, operation }` and returns:
59
-
60
- ```js
61
- { status: 'pending', reference: 'provider-operation-reference' }
62
- { status: 'confirmed', evidence: { /* domain-specific verified observations */ } }
63
- { status: 'rejected', reason: 'definitive rejection' }
5
+ ```sh
6
+ npm install @allorc/a423-sdk@0.4.0 ethers@^6.15.0 pdf-lib@^1.17.1
64
7
  ```
65
8
 
66
- Confirmed results require an evidence object. The SDK trusts the adapter's verdict; it cannot independently establish payment, artifact quality or chain finality. An arbitrary reference is not payment proof. Chain adapters should compare configured chain/contract, job ID, amount, token, participants, deadline and commitments against the accepted terms, plus relevant successful transaction events and the chosen finality policy. `start` must recheck eligibility before work is dispatched.
67
-
68
- The SDK atomically stores a pending operation before invoking an effect. Its `operation.id` is stable across retries: use it as the provider idempotency key. **Adapters must durably deduplicate effects, including concurrent calls.** Compare-and-set does not make an external payment exactly-once. If execution throws or returns an invalid result, pending remains; inspect/reconcile it. A pending result retains its observation. A rejected result clears pending without advancing state; repeat the same action key returns the stored job, so use a new key only after a definitive rejection and a deliberate new attempt.
9
+ Node.js 22.13+ is required for the SQLite module. Node 24 is the tested runtime. `ethers` is optional unless you import `/escrow`; `pdf-lib` is optional unless you render PDFs. Core/workflow remain framework-independent.
69
10
 
70
- `reconcile(id, actor)` calls only the pending adapter's `reconcile`, never its execute method. That implementation should inspect known provider operations, not blindly repeat a transaction. Supply reconciliation for every external effect. Reuse the same adapter configuration and transitions across restarts. Reconciliation can record additional pending observations. No other action runs while an operation remains pending. If a final database save fails, recovery relies on the same persisted operation and provider key.
11
+ ## Modules
71
12
 
72
- The SDK does not run agents or background queues. Persist intent and schedule work in adapters; use a durable worker to submit actual output. Do not mark work submitted merely because a worker was queued. Custom transitions can add milestones, rejection/revision loops, manual review, nonpayment completion, or alternative arbitration. Override whole rules with `{ from, roles, to, effect }`; applications assume responsibility for the resulting invariants. Effect adapters must be trusted server code.
13
+ | Import | Implemented behaviour |
14
+ |---|---|
15
+ | `/workflow` | Versioned terms, bilateral acceptance, role guards, CAS state/events, pending operations, explicit observe/resume and safety holds |
16
+ | `/escrow` | Reference ERC-20 contract, full custody lifecycle, EOA/ERC-1271 worker consent, strict chain evidence, managed signing/delegation, allowlisted deployment registry, bounded fee replacements and incremental log indexing |
17
+ | `/storage` | Single-host SQLite workflow store, transaction/replacement journal, atomic nonce reservations, leased queue/checkpoints, spending reservations and authority/event storage |
18
+ | `/authority` | Operator-approved organization registry, organization-signed agent enrollment, single-use challenges, signed audience-bound bearer credentials, revocation, public-key verification and renewal |
19
+ | `/operations` | Typed task/evidence validation, spending authorizer, maintenance/keeper ticks, durable execution dispatcher, fresh receipts/PDF, reorg quarantine, SSE, webhook outbox, safe migration planning and app/chat integration handler |
20
+ | Root | Preserved original simple offer/accept/submit/approve API; [legacy documentation](./LEGACY.md) |
73
21
 
74
- ### Storage, permissions and receipts
22
+ ## Start with the actual lifecycle
75
23
 
76
- A workflow store implements `get`, atomic insert-if-absent `create`, and atomic version-checked `compareAndSet`. Persist the entire record (state, pending operation, evidence, events, idempotency history) atomically. Use a separate table/namespace from legacy records. The root `createMemoryStore()` also works structurally at runtime for development, but is not durable or suitable for distributed deployment; a typed workflow store should implement the interface from the workflow entry point.
24
+ Create a draft → propose immutable terms → both parties accept the exact revision/hash → worker signs consent → payer authorizes funding → contract holds tokens → worker passes fresh eligibility gate → deliver/commit artifact → verify → release. Expired unsubmitted jobs can refund; submitted disagreements can dispute/resolve; the agreed timeout allocation remains available if trusted roles disappear.
77
25
 
78
- The handler requires authentication. It obtains actor identity from your callback, never from action bodies. Default role checks always run; `authorize` adds application policy and must return exactly true. Creation requires requester authentication, and requester must differ from worker. The verifier may be the requester or a separate participant. Authentication, wallet/key custody, identity-to-wallet binding and proof of ownership remain application responsibilities. `@allorc/identity` only derives public identifiers.
26
+ **Acceptance, token approval and confirmed custody are different states.** Only matching confirmed chain evidence establishes that money is locked. Neither a public identity hash nor a caller-supplied agent ID authenticates the caller.
79
27
 
80
- Receipts are JSON snapshots of agreement, submission, stored adapter evidence, events and pending state. They are not freshly queried chain proofs or signed attestations. Render PDFs separately, and revalidate provider evidence if fresh verification is required. Protect credentials and never put secrets in terms, evidence, metadata or events: all participants can read these fields. Records/events grow over time; production storage should enforce application quotas. The HTTP client limits responses to 1 MiB.
28
+ Read [ESCROW.md](./ESCROW.md) for exact fields, inputs, roles and contract limits. Read [OPERATIONS.md](./OPERATIONS.md) for configuration, authority enrollment, storage, worker scheduling, spending limits, fresh receipts, monitoring, UI/chat wiring and migration.
81
29
 
82
- ### HTTP and client
83
-
84
- | Method | Path beneath `/api/a423` | Body |
85
- |---|---|---|
86
- | POST | `/jobs` | `{ id, participants, metadata? }` |
87
- | GET | `/jobs/:id` | — |
88
- | POST | `/jobs/:id/actions` | `{ action, input }` plus `Idempotency-Key` |
89
- | POST | `/jobs/:id/reconcile` | `{}` |
90
- | GET | `/jobs/:id/receipts` | — |
91
-
92
- The handler uses standard Web Request/Response, bounded streaming JSON (64 KiB default), explicit errors, and no background HTTP work. IDs and idempotency keys use 1–128 ASCII letters/digits/underscores/hyphens. Create with a stable application-assigned ID; duplicate creation returns conflict, then retrieve and compare the existing record. Every action needs an actor-scoped key; reusing it with different input fails. Transient conflicts should reload and retry using the same key. Authentication callbacks must inspect headers/cookies or a cloned request if they need the body.
30
+ ## Minimal server setup
93
31
 
94
32
  ```ts
95
- import { createWorkflowClient } from '@allorc/a423-sdk/workflow';
96
- const client = createWorkflowClient({
97
- baseUrl: 'https://your-app.example/api/a423',
98
- headers: () => ({ Authorization: `Bearer ${serverCredential}` }),
33
+ import { createSqliteStorage } from '@allorc/a423-sdk/storage';
34
+ import { createWorkflow } from '@allorc/a423-sdk/workflow';
35
+ import { createEscrowAdapters, escrowTransitions } from '@allorc/a423-sdk/escrow';
36
+ import { createSpendingAuthorizer, createApplicationHandler } from '@allorc/a423-sdk/operations';
37
+
38
+ const storage = createSqliteStorage({ filename: '/your-protected-local-disk/a423.sqlite' });
39
+ const authorizeTransaction = createSpendingAuthorizer({
40
+ spending: storage.spending,
41
+ limitsFor: loadApprovedRawTokenLimits,
42
+ authorize: checkSignerDelegationAndJobSafety,
99
43
  });
100
- const quote = await client.act(jobId, 'quote', { terms }, { idempotencyKey: 'quote_1' });
101
- await client.act(jobId, 'accept', {
102
- agreement_hash: quote.agreement!.hash,
103
- revision: quote.agreement!.revision,
104
- }, { idempotencyKey: 'accept_1' });
105
- ```
106
-
107
- Each participant needs its own credentials. The client does not automatically retry. Default timeout is 30 seconds; adapters should return pending quickly for long operations. Use TLS and appropriate CSRF protection for cookie-authenticated mutations.
108
-
109
- ## Original 0.1 API documentation
110
-
111
- # @allorc/a423-sdk
112
-
113
- One experimental package for exposing an agent job API from a Next.js App Router application. Configure a handler, export it from a catch-all route, and use the typed client to offer, accept, submit and approve work.
114
-
115
- **Status:** local experimental v0.1.0. The npm package has not been published by this checkout. Source repository: https://github.com/Allorc-official/A423-SDK. This implements an initial workflow, not a finalized A423 standard or a production escrow system. Node.js 22+. ESM, CommonJS and TypeScript declarations; zero runtime dependencies.
116
-
117
- ## Quick setup in Next.js
118
-
119
- Until publication, install the local package into your existing Next.js application:
120
-
121
- ```sh
122
- npm install /absolute/path/to/servers/server/a423_sdk
123
- ```
124
-
125
- After publication: `npm install @allorc/a423-sdk`.
126
-
127
- Create `lib/a423.ts`:
128
-
129
- ```ts
130
- import 'server-only';
131
- import { createA423Handler, createMemoryStore } from '@allorc/a423-sdk';
132
-
133
- // Development only. Replace with a durable StateStore before deployment.
134
- const store = createMemoryStore();
135
- const token = process.env.A423_API_TOKEN;
136
- const actorId = process.env.A423_AGENT_ID;
137
- if (!token || !actorId) throw new Error('Configure A423_API_TOKEN and A423_AGENT_ID');
138
-
139
- export const a423 = createA423Handler({
140
- store,
141
- authenticate(request) {
142
- return request.headers.get('authorization') === `Bearer ${token}`
143
- ? actorId
144
- : null;
145
- }
44
+ const adapters = createEscrowAdapters({
45
+ provider, chainId, contractAddress, tokenAddress, confirmations,
46
+ transactions: storage.transactions, signerFor, addressFor,
47
+ authorizeTransaction, validateArtifact,
146
48
  });
147
- ```
148
-
149
- This minimal authentication example maps one secret credential to one public actor ID. A complete workflow requires separate requester and worker credentials. The two-actor example is in `examples/nextjs/lib/a423.ts`; replace it with your own authenticated identity integration when connecting real agents.
150
-
151
- Create `app/api/a423/[...path]/route.ts`:
152
-
153
- ```ts
154
- import { a423 } from '@/lib/a423';
155
-
156
- export const runtime = 'nodejs';
157
- export const GET = a423;
158
- export const POST = a423;
159
- ```
160
-
161
- The `@/` import assumes your application's usual root alias; adjust it if your app uses `src/` or relative imports. This is one catch-all route, not one file per action. Do not run it in the Edge runtime. The SDK does not start a listener or publish routes on its own: Next.js exposes the route you configure.
162
-
163
- Keep credentials in server environment variables without `NEXT_PUBLIC_`. The package's client is designed for Node/server use, not for embedding worker credentials in client-side React. Your existing Next.js application supplies Next.js, React, TypeScript and `server-only`; the SDK itself does not import framework internals.
164
-
165
- ## Use the typed client
166
-
167
- ```ts
168
- import { createA423Client } from '@allorc/a423-sdk';
169
-
170
- const requester = createA423Client({
171
- baseUrl: 'https://your-app.example/api/a423',
172
- headers: { authorization: `Bearer ${requesterToken}` }
49
+ const workflow = createWorkflow({
50
+ store: storage.jobs, adapters, transitions: escrowTransitions,
51
+ validateTerms, authorize: authorizeWorkflowAction,
173
52
  });
174
- const worker = createA423Client({
175
- baseUrl: 'https://your-app.example/api/a423',
176
- headers: { authorization: `Bearer ${workerToken}` }
53
+ const handler = createApplicationHandler({
54
+ workflow, store: storage.jobs, queue: storage.queue,
55
+ authenticate, revalidate, consentFor,
177
56
  });
178
-
179
- const job = await requester.offer({
180
- worker_id: workerAgentId,
181
- title: 'Prepare a short brief',
182
- description: 'Research the topic and return a deliverable link.',
183
- price: { amount_minor: 500, currency: 'USD' }
184
- }, { idempotencyKey: 'brief-request-001' });
185
-
186
- await worker.accept(job.id);
187
- await worker.submit(job.id, { deliverable: 'https://example.com/brief.md' });
188
- await requester.approve(job.id);
189
- const receipts = await requester.receipts(job.id);
57
+ // Import this configured handler in the appropriate Next.js catch-all route:
58
+ export const runtime = 'nodejs';
59
+ export { handler as GET, handler as POST };
190
60
  ```
191
61
 
192
- `requesterToken`, `workerToken` and `workerAgentId` come from your application, not the SDK. The requester and worker are distinct authenticated actors. The example orchestrates both sides for demonstration; in a real integration they normally run independently.
193
-
194
- The price is an agreed term: 500 USD minor units means $5.00 when your adapter uses USD cents. Currency codes are uppercase strings of 3–12 letters/digits starting with a letter. The SDK does not calculate exchange rates, currency precision, fees or blockchain token decimals; define those semantics in your settlement integration. `amount_minor` must be a nonnegative safe integer. Zero-price jobs are allowed.
195
-
196
- A deliverable is an opaque string, such as a URL, repository reference or storage path. The SDK stores it and never downloads or executes it. It does not perform the work, evaluate quality or run an agent automatically.
197
-
198
- Client methods return `Job`, except `receipts`, which returns `Receipts`. `headers` may be an async function for fresh credentials. `fetch` can be injected for tests or custom networking. Requests default to a 30-second timeout, refuse redirects and do not retry automatically.
199
-
200
- ## HTTP API
62
+ Names such as `loadApprovedRawTokenLimits`, `provider`, `signerFor`, `validateArtifact`, `authenticate`, `revalidate` and `consentFor` are application services, not invented SDK exports. Complete recipes and actual exports are in OPERATIONS.md and TypeScript declarations. SQLite files must live on persistent local disk; use a different store for serverless/multi-host deployment.
201
63
 
202
- Every request requires successful `authenticate(request)`. POSTs use `Content-Type: application/json`.
64
+ ## Integration surfaces
203
65
 
204
- | Method | Route | Body | Who can call |
205
- | --- | --- | --- | --- |
206
- | POST | `/api/a423/offers` | `{worker_id,title,description,price}` and `Idempotency-Key` header | Authenticated requester |
207
- | GET | `/api/a423/jobs/:id` | None | Requester or assigned worker |
208
- | POST | `/api/a423/jobs/:id/accept` | `{}` | Assigned worker |
209
- | POST | `/api/a423/jobs/:id/submit` | `{deliverable}` | Assigned worker |
210
- | POST | `/api/a423/jobs/:id/approve` | `{}` | Requester |
211
- | GET | `/api/a423/jobs/:id/receipts` | None | Requester or assigned worker |
66
+ `createApplicationHandler` serves authenticated workflow routes under `/api/a423`, projections under `/api/state`, resumable events at `/api/a423-events`, wallet typed-data at `/api/a423-worker-consent/:id`, and freshly revalidated JSON/PDF at `/api/a423-receipts/:id[.pdf]`. An optional `legacyState(actor)` callback preserves legacy dashboard data alongside `a423.jobs` and `a423.wallet_transactions`.
212
67
 
213
- Offer creation returns HTTP 201 with `{job}`; an identical retry returns 200 with the existing job. Other actions return `{job}`. Receipts return `{job_id,state,settlement,events}`. Responses use `Cache-Control: no-store`. Unknown jobs and jobs belonging to other actors both return 404.
68
+ `createChatTools` maps offer, quote, accept, fund, start, submit, verify, release, refund, dispute, resolve and timeout tools to the same workflow records. Work is dispatched through the durable queue; it is never marked submitted because an AI run was merely scheduled. The HTML application shell includes wallet consent, action controls, states, parties, evidence and receipts.
214
69
 
215
- External agents can use plain HTTP without installing the SDK. For example:
70
+ Run the supplied shell and operational worker with an explicitly configured application module:
216
71
 
217
72
  ```sh
218
- curl https://your-app.example/api/a423/offers \
219
- -H "Authorization: Bearer $A423_REQUESTER_TOKEN" \
220
- -H 'Content-Type: application/json' \
221
- -H 'Idempotency-Key: brief-request-001' \
222
- -d '{"worker_id":"worker-agent-id","title":"Brief","description":"Prepare a short brief","price":{"amount_minor":500,"currency":"USD"}}'
73
+ A423_CONFIG_MODULE=/absolute/path/config.mjs node examples/operations-server.mjs
74
+ A423_CONFIG_MODULE=/absolute/path/config.mjs node scripts/operations-worker.mjs
223
75
  ```
224
76
 
225
- Adjust `basePath` if you mount at a different URL. This initial implementation has no list/search, cancellation, disputes, amendments, webhooks, discovery endpoint, background scheduler or UI. Poll known job IDs or coordinate workers through your own queue. Authentication is mandatory; signatures and message-level cryptographic verification are not built in.
77
+ The shell binds to localhost. There is no bootstrap endpoint, default credential, built-in private key or automatically provisioned arbiter. Supply distinct signer references for treasury, worker, verifier and arbitrator according to your policy.
226
78
 
227
- ## Workflow and retry behaviour
79
+ ## Upgrade from 0.3
228
80
 
229
- A job follows `offered → accepted → submitted → completed`. With a settlement adapter it follows `submitted → settlement_pending → completed` during approval.
81
+ - `authorizeTransaction` is now mandatory for escrow adapters. Explicitly configure spending/delegation checks; a blanket `true` is appropriate only for controlled tests.
82
+ - The transaction-store interface adds `list` and atomic `appendReplacement`. The supplied SQLite and memory implementations include them.
83
+ - Use a new deployment for ERC-1271 support. Existing 0.3 contracts/locked jobs stay on their original ABI/contract; an npm upgrade does not upgrade a deployed contract.
84
+ - `workflow.resume(id, actor)` explicitly authorizes the next pending step. `reconcile` remains observe-only. `runMaintenanceOnce` only resumes when `allowExecute: true`.
85
+ - Reference evidence now includes settlement reason/dispute commitment. Existing incomplete evidence needs explicit refresh/migration; do not fabricate fields or relabel old financial states.
86
+ - `getSafety`/`setSafety` enable quarantine. Monitor evidence continuously and require fresh eligibility immediately before executing real work.
230
87
 
231
- The server checks the actor and current state before each change. Every successful change increments the job version and appends an event. Submission is immutable: retrying with the same deliverable succeeds, but replacing it is rejected. Repeating acceptance after acceptance or approval after completion returns the current job without adding a duplicate event.
88
+ ## Boundaries
232
89
 
233
- Offer IDs are deterministic SHA-256 values scoped by protocol version, authenticated requester ID and `Idempotency-Key`. Reuse the same key and exact logical offer when retrying after a network failure. Reusing it with different offer details returns 409. Store the key on your side; a freshly generated key means a new offer. Keys are visible ASCII without spaces, at most 200 characters, and should not contain secrets.
90
+ This remains experimental and independently unaudited. Verifiers/arbitrators remain trusted; there is no objective quality oracle or guaranteed challenge window. The reference contract retains one token, one immutable submission and one settlement per job. It does not add partial milestone releases, resubmission, admin withdrawals, pause/upgrades or fee-on-transfer/rebasing support. Separate deployments are selected through the registry. A timeout needs a transaction and a functioning chain/token.
234
91
 
235
- Storage changes use compare-and-set. Concurrent writes can return 409 `CONFLICT`; reload the job and retry the intended action. The SDK does not keep an HTTP request open for the duration of a task. Worker execution and scheduling belong in your application's background jobs.
92
+ KMS/HSM providers are integrated through a checked digest signer; the package does not provision vendor accounts or certify hardware. Authority approval establishes trust within your configured registry, not government recognition or automatic legal-company verification. AI execution uses your explicitly supplied handler, never an implicit model subscription. Reorg handling quarantines invalid observations; it does not silently rewrite accepted agreements or erase paid history. Fee replacement uses explicit caps and the same intent/nonce; it cannot force a censored transaction to execute.
236
93
 
237
- ## Storage adapter
238
-
239
- `store` is required. `createMemoryStore()` is only for local development/tests: it loses data on restart and is not shared across serverless instances or processes. There is no silent in-memory default.
240
-
241
- Implement the `StateStore` interface against your database:
242
-
243
- ```ts
244
- interface StateStore {
245
- get(id: string): Promise<Job | null>;
246
- create(job: Job): Promise<boolean>;
247
- compareAndSet(id: string, expectedVersion: number, job: Job): Promise<boolean>;
248
- }
249
- ```
250
-
251
- `create` must atomically insert only if the ID is absent; enforce a unique ID constraint. `compareAndSet` must atomically update only when both ID and version match, then report whether one row changed. Store job fields and events in the same transaction. A read followed by an unconditional update is not sufficient. Return independent snapshots so caller mutations do not change stored state.
252
-
253
- For example, a relational adapter can use an ID primary key, a version column and a JSON document. Create with insert-on-conflict-do-nothing; update using `WHERE id = ? AND version = ?`. This is a contract, not a shipped SQL adapter. Durable storage must also support pending settlement recovery.
254
-
255
- ## Identity and authentication
256
-
257
- `authenticate(request)` is the integration point for your identity system. It returns an authenticated actor's public agent ID or `null` to reject. It may verify a bearer credential, a session, or an externally signed request. The returned ID determines permissions; an untrusted ID in a request body/header does not.
258
-
259
- `@allorc/identity` 0.3.0 can form a public agent ID:
260
-
261
- ```ts
262
- import { VERSION, createIdentity } from '@allorc/identity';
263
-
264
- const identity = createIdentity({
265
- version: VERSION,
266
- organization_public_key: 'externally-supplied-public-value',
267
- agent_reference: 'worker-42',
268
- agent_name: 'Alice',
269
- agent_email: 'alice@example.com'
270
- });
271
- // Register identity.agent_id with your credential/authority system separately.
272
- ```
273
-
274
- Install that optional package separately when needed. A hash match proves record consistency, not ownership or authentication. This SDK does not claim to verify the organisation behind a public key, and does not depend on the identity package to authenticate actors. Changes to that package's public ingredients change its derived agent ID; your identity integration must manage those updates.
275
-
276
- For cookie-based authentication, your integration must also enforce its CSRF/origin policy. The SDK does not configure sessions, CORS, network access, rate limits or tenant onboarding for you. The supplied example uses server-issued bearer tokens with fixed actor bindings.
277
-
278
- ## Optional settlement adapter
279
-
280
- Without an adapter, approval marks the job completed with:
281
-
282
- ```json
283
- {"status":"not_configured","reference":null}
284
- ```
285
-
286
- **This records task approval and moves no money.** A configured adapter is called only after requester approval and after the job is persisted as `settlement_pending`:
287
-
288
- ```ts
289
- const handler = createA423Handler({
290
- store,
291
- authenticate,
292
- settlement: {
293
- async settle({ job, idempotencyKey }) {
294
- // Invoke your own escrow/payment service with this stable key.
295
- // Persistently deduplicate that key, including concurrent invocations.
296
- const result = await yourSettlementService.release({
297
- jobId: job.id,
298
- requesterId: job.requester_id,
299
- workerId: job.worker_id,
300
- price: job.price,
301
- idempotencyKey
302
- });
303
- return { reference: result.receiptId };
304
- }
305
- }
306
- });
307
- ```
308
-
309
- `yourSettlementService` is your external integration, not an included export. The SDK passes `a423:settle:<job-id>` as a stable provider idempotency key. Your adapter MUST durably deduplicate it and return the same result on retries, including simultaneous calls from multiple server instances. The SDK cannot guarantee exactly-once movement of funds across an external provider and a database transaction.
310
-
311
- On a provider error the server returns 502 and leaves the job pending; retry `approve` as the requester. If the provider succeeds but the database write fails, retrying approval uses the same key. If you run a recovery worker, route it through equivalent actor and idempotency rules. No automatic retry queue is included.
312
-
313
- This is not funded escrow: the SDK does not reserve funds at offer/acceptance, validate wallets, sign financial authorisations, collect mandates or guarantee payment. Your adapter must enforce those rules before moving funds. `settled` records the adapter's reported success, not an independently verified blockchain confirmation.
314
-
315
- ## Errors and limits
316
-
317
- Errors are `{error:{code,message}}`. The client throws `A423Error` with `code` and `status` for HTTP failures. Network/timeout errors propagate from fetch. Internal exception details stay server-side; optional `onError(error)` lets your application record them.
318
-
319
- | HTTP status | Typical code |
320
- | --- | --- |
321
- | 400 | `INVALID_INPUT`, `INVALID_JSON` |
322
- | 401 | `UNAUTHENTICATED` |
323
- | 403 | `FORBIDDEN` |
324
- | 404 | `NOT_FOUND` |
325
- | 405 | `METHOD_NOT_ALLOWED` |
326
- | 409 | `INVALID_STATE`, `CONFLICT`, `IDEMPOTENCY_CONFLICT` |
327
- | 413 | `BODY_TOO_LARGE` |
328
- | 415 | `UNSUPPORTED_MEDIA_TYPE` |
329
- | 500 | `INTERNAL_ERROR` |
330
- | 502 | `SETTLEMENT_UNAVAILABLE` |
331
-
332
- Request bodies default to 64 KiB and are limited while streaming, even without `Content-Length`; `maxBodyBytes` can set 1 byte through 1 MiB. Description limit: 16,000 characters; deliverable: 8,000; actor IDs and titles: 256. Client response bodies are limited to 1 MiB. Bound requests at your ingress too. There is no multipart/file upload or arbitrary server-side URL fetching.
333
-
334
- ## Package exports
335
-
336
- | Export | Purpose |
337
- | --- | --- |
338
- | `createA423Handler(options)` | Standard Web Request/Response handler usable in Next.js routes |
339
- | `createA423Client(options)` | Typed HTTP client |
340
- | `createMemoryStore()` | Explicit local development store |
341
- | `A423Error` | Structured client/protocol error |
342
- | `PROTOCOL_VERSION` | `a423.experimental.v1` |
343
-
344
- ESM and CommonJS expose the same names. Interfaces for `Job`, `StateStore`, `SettlementAdapter` and configuration are in `src/index.d.ts`. The handler is framework-independent at runtime, but the included integration targets Next.js App Router. Installing this SDK does not publish an API or establish protocol compatibility with other A423 implementations.
345
-
346
- ## Development
94
+ ## Validation and release
347
95
 
348
96
  ```sh
97
+ npm ci --ignore-scripts
98
+ npm run typecheck
349
99
  npm test
350
- npm run pack:check
100
+ npm pack --dry-run --ignore-scripts
351
101
  ```
352
102
 
353
- Tests exercise the complete workflow through actual Web Request/Response objects, permissions, malformed input, streaming limits, concurrent updates, retry semantics and settlement recovery. An additional socket-based test runs where local listeners are permitted, and explicitly skips when the execution environment blocks them. Next.js example files are templates for an existing application; they are not a standalone generated Next.js app.
354
-
355
- No runtime dependencies or build step. Before a public release, review the package allowlist, configure the repository/private security reporting channel, and verify a real Next.js deployment and durable adapter. Publication is a separate maintainer action; this package has not been published by creating these files.
103
+ Tests compile and deploy the contract on a local EVM, exercise payment/refund/dispute/timeout, check consent and permissions, and cover credential-protected HTTP → durable SQLite → escrow → fresh PDF evidence. Tests also cover storage leases, spend caps, renewal/revocation, queue dispatch, SSE/webhooks, fee replacement and migration restrictions. External testnet deployment, source-verification completion and an independent audit are separate operations; no result is inferred from a hook or submitted verification ticket.
package/SECURITY.md CHANGED
@@ -1,14 +1,15 @@
1
- # Security policy
1
+ # Security
2
2
 
3
- Experimental SDK; no independent audit or stable support commitment. The built-in memory store and fixed bearer-token example are for development, not a production deployment.
3
+ Experimental SDK and independently unaudited reference escrow. Use local/testnet evaluation until the deployment and operational model receive independent review.
4
4
 
5
- Authentication must be supplied. Never treat a public ID, public ingredients or a matching hash as evidence that a request belongs to an agent. Verify credentials/signatures using your own identity adapter; enforce session/CSRF policy when using cookies. Configure TLS, ingress limits, rate limits, CORS and durable atomic storage in your host application.
5
+ Reference-contract custody is distinct from the legacy root API, which has no funded escrow. Authority credentials identify keys approved by your operator registry; they are not proof of legal-company status. Administrative enrollment/revocation capabilities must not be exposed to untrusted callers. Use explicit trust roots, TLS, short credential lifetimes, revocation checks and rate-limited proof endpoints.
6
6
 
7
- Settlement adapters must durably deduplicate the SDK's stable key across retries and concurrent calls. This SDK does not fund escrow, establish mandates, custody keys or guarantee exactly-once payments. Validate recipient, amount, permission and provider status independently. Completion without an adapter explicitly means no settlement is configured.
7
+ Keep private keys, raw signed transaction journals, SQLite files and webhook signing secrets private. The supplied SQLite driver is single-host and requires persistent local disk, restricted filesystem access and backups. Do not use memory stores for production custody, share nonces across uncoordinated writers, or reuse an arbitrator key for treasury/bootstrap. Use the checked managed signer and bounded delegation or your reviewed signing service.
8
8
 
9
- Do not include tokens, private keys, customer data or live exploit details in public reports. Use a private repository reporting channel when configured; maintainers should establish one before requesting sensitive reports. Never log credentials. No private security contact is invented by this initial package.
9
+ Authorize before signing and rebroadcasting, enforce raw-token spend limits atomically, and preserve unknown outcomes for reconciliation. Queue handlers and webhook receivers must be idempotent; leases do not imply exactly-once external effects. Only configure webhook destinations you control/approve; redirects are disabled, but DNS and egress controls remain deployment responsibilities.
10
10
 
11
+ The reference token must support exact transfers. Unsupported fee/rebase/blacklist behaviours can prevent settlement. EOA/ERC-1271 consent is supported by the new contract; verifiers/arbitrators still decide quality and may have conflicts of interest. Timeout allocation must be explicitly agreed. Contract upgrades, admin recovery, challenge windows and partial releases are not introduced by this version.
11
12
 
12
- ## Reference escrow
13
+ Reorg watchers quarantine stale/unavailable evidence; they cannot guarantee finality or undo already executed external work. Continuous operation, monitoring, deliberate recovery and chain availability are still necessary. Do not clear holds without current matching evidence.
13
14
 
14
- The 0.3 reference contract is experimental and not independently audited. Its trust model includes the chosen verifier/arbitrator and the timeout allocation both parties accepted. EOA signatures only; standard exact-transfer tokens only. Read ESCROW.md before deployment. Never expose raw signed transaction journals, use memory stores for production custody, or share signer nonces across uncoordinated processes. Confirmation policy is not continuous reorg monitoring.
15
+ Report vulnerabilities privately through the repository's configured security reporting channel. Do not post credentials, customer records or live exploit details publicly. No completed independent audit is claimed.
@@ -6,6 +6,7 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol
6
6
  import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
7
7
  import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
8
8
  import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
9
+ import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol";
9
10
 
10
11
  /// @notice Experimental reference escrow for standard, non-rebasing ERC20 tokens.
11
12
  /// No admin withdrawals/upgrades. Arbitration and timeout allocation are agreed before funding.
@@ -54,7 +55,10 @@ contract A423Escrow is ReentrancyGuard, EIP712 {
54
55
  if (jobs[t.jobId].state != State.None) revert WrongState();
55
56
  if (t.jobId == bytes32(0) || t.agreementHash == bytes32(0) || t.amount == 0 || t.worker == address(0) || t.worker == t.payer || t.verifier == address(0) || t.arbitrator == address(0) || t.arbitrator == t.payer || t.arbitrator == t.worker || t.timeoutWorkerBps > 10000) revert InvalidTerms();
56
57
  if (t.workDeadline <= block.timestamp || t.reviewDeadline <= t.workDeadline || t.resolutionDeadline <= t.reviewDeadline) revert InvalidTerms();
57
- if (ECDSA.recover(termsDigest(t), workerSignature) != t.worker) revert Unauthorized();
58
+ bool valid;
59
+ if (t.worker.code.length == 0) { valid = ECDSA.recover(termsDigest(t), workerSignature) == t.worker; }
60
+ else { try IERC1271(t.worker).isValidSignature(termsDigest(t), workerSignature) returns (bytes4 magic) { valid = magic == IERC1271.isValidSignature.selector; } catch { valid = false; } }
61
+ if (!valid) revert Unauthorized();
58
62
  jobs[t.jobId] = Job(t, State.Funded, bytes32(0), 0);
59
63
  uint256 beforeBalance = token.balanceOf(address(this));
60
64
  token.safeTransferFrom(t.payer, address(this), t.amount);