@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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0
4
+
5
+ - Add durable single-host SQLite workflow/transaction/nonce/queue/event/spending storage and local-only reset.
6
+ - Add organization-approved agent enrollment, challenge proofs, credentials, public-key verification, revocation and renewal.
7
+ - Require escrow pre-sign/rebroadcast authorization; provide atomic per-transaction/day/total spending reservations.
8
+ - Add EOA/ERC-1271 consent, fresh evidence validation, reorg quarantine, allowlisted multi-deployment routing, log indexing, managed signing/delegation and bounded same-nonce replacement.
9
+ - Add task/evidence schemas, keeper/resume, checkpointed execution, signed webhook outbox, resumable SSE and fresh JSON/PDF receipts.
10
+ - Add shared app/chat projections, wallet consent/dashboard shell, safe legacy migration planning, local-EVM integration tests and dry-run deployment/source-verification tooling.
11
+ - Retain explicit reference-contract constraints. No external deployment or independent audit is claimed.
12
+
3
13
  ## 0.3.0
4
14
 
5
15
  - Add optional ethers-based `/escrow` entry point and a compiled ERC-20 reference contract.
package/ESCROW.md CHANGED
@@ -1,11 +1,11 @@
1
- # Reference on-chain escrow (0.3.0)
1
+ # Reference on-chain escrow (0.4.0)
2
2
 
3
3
  This opt-in module implements a real ERC-20 escrow path. The generic workflow remains provider-independent. It includes Solidity source, reproducibly compiled ABI/bytecode, an ethers v6 adapter and local-EVM tests. It is experimental and has not received an independent security audit; deploy to a local chain/testnet for evaluation. No contract is deployed automatically and no funds move merely by installing the package.
4
4
 
5
5
  ## Installation and deployment
6
6
 
7
7
  ```sh
8
- npm install @allorc/a423-sdk@0.3.0 ethers@^6.15.0
8
+ npm install @allorc/a423-sdk@0.4.0 ethers@^6.15.0
9
9
  ```
10
10
 
11
11
  ```ts
@@ -44,7 +44,7 @@ const terms = {
44
44
 
45
45
  All deadlines are absolute Unix seconds and strictly increasing. The work deadline must be in the future at funding. `amount` is a positive uint256 decimal string. The SHA-256 hash of the entire canonical terms object is stored on-chain; it includes payment rules and the task criteria. The Solidity EIP-712 terms digest is a separate encoding for worker consent. `jobId` is SHA-256 of UTF-8 `a423:escrow:v1:` plus the application job ID. One job ID may only be funded once per deployed contract.
46
46
 
47
- The arbitrator must differ from payer and worker. The verifier may be the payer: this is a trusted verifier policy, not trustless proof of output quality. The reference contract supports EOA EIP-712 worker signatures using ECDSA; ERC-1271 smart account worker signatures are not implemented. Use another adapter/contract for that capability.
47
+ The arbitrator must differ from payer and worker. The verifier may be the payer: this is a trusted verifier policy, not trustless proof of output quality. The 0.4 reference contract supports EOA EIP-712 worker signatures and ERC-1271 wallet signatures. Existing 0.3 deployments remain EOA-only. For smart-account transaction execution, submit through the account wallet and supply verified `transaction_hash` evidence to the SDK; the raw-transaction sender itself is still an EOA/managed signer.
48
48
 
49
49
  ## Connect Next.js and the workflow
50
50
 
@@ -61,6 +61,7 @@ const adapters = createEscrowAdapters({
61
61
  transactions: durableTransactionStore,
62
62
  signerFor: async actor => signerRegistry.getSigner(actor),
63
63
  addressFor: async actor => signerRegistry.getAddress(actor),
64
+ authorizeTransaction, // REQUIRED: spending/delegation policy; see OPERATIONS.md
64
65
  validateArtifact: async ({ job, resultHash }) => {
65
66
  // Fetch exact bytes, check digest and evaluate accepted criteria.
66
67
  // Return true only after all checks pass.
@@ -119,7 +120,7 @@ To automatically initiate funding after the second acceptance, orchestrate the a
119
120
 
120
121
  A verifier can approve and release before a dispute is opened; there is no guaranteed challenge window or objective quality oracle. Rejected artifact verification leaves the workflow submitted and clears pending, allowing a dispute. The contract enforces that submission and verifier approval exist; the application verifier still judges task quality. For resubmission/milestones, use a separate contract policy: the reference contract permits one immutable submission and one settlement, not partial milestone releases. Arbitration can split the one escrow.
121
122
 
122
- Cancellation and quote expiry before funding remain workflow/application policies and move no tokens. The reference module does not supply a scheduler or a governor registry.
123
+ Cancellation and quote expiry before funding remain workflow/application policies and move no tokens. The optional operations/authority modules provide keeper ticks and an explicit registry; configure and run them as described in OPERATIONS.md.
123
124
 
124
125
  ## Recovery and journal requirements
125
126
 
@@ -131,13 +132,13 @@ Implement `TransactionStore`:
131
132
  - `create(id, record)`: atomic insert-if-absent; return boolean. Never overwrite an existing signed transaction.
132
133
  - `reserveNonce(scope, minimum, operationId)`: transactionally allocate per-chain/address nonces, at least the RPC pending nonce; the same operation must always retrieve its original reservation. Every process using the account must coordinate through this allocator. External wallet activity requires an explicit operational policy.
133
134
 
134
- `createMemoryTransactionStore()` is only for tests. Production needs a real database implementation. A crash after nonce reservation but before journaling must resume that operation to fill its reserved nonce; later transactions may remain blocked behind it. No automatic fee replacement/cancellation is included. A genuinely stuck transaction requires deliberate recovery with the same nonce and updated journal lineage; do not blindly assign a new nonce. Prefer dedicated signer accounts to uncontrolled mixed wallet usage.
135
+ `createMemoryTransactionStore()` is only for tests. Production needs a real database implementation. A crash after nonce reservation but before journaling must resume that operation to fill its reserved nonce; later transactions may remain blocked behind it. The optional bounded fee replacement worker can journal a same-nonce fee increase under explicit caps; there is no automatic cancellation. A genuinely stuck transaction requires deliberate recovery with the same nonce and updated journal lineage; do not blindly assign a new nonce. Prefer dedicated signer accounts to uncontrolled mixed wallet usage.
135
136
 
136
137
  `reconcile` only observes existing journal entries and never signs/broadcasts. If an approval is confirmed but the funding transaction has not yet been created, reconcile returns pending; retry the original fund action/key to authorise progression. After a signed transaction is journaled but not broadcast, execute retries rebroadcast the identical bytes. After broadcast/mining but before the workflow write, reconcile verifies and adopts its evidence. Explicit `rejected` results permit a deliberate new attempt with a new action key; an unknown network outcome stays pending or throws with the pending record retained.
137
138
 
138
139
  For externally submitted transactions, pass `transaction_hash` in the action input from the beginning (in addition to the normal fields). The adapter observes it without broadcasting and checks the configured contract, exact agreement, action event and allocation. This supports recording permissionless refunds/timeouts submitted by a keeper or another account. Use the correct workflow action; it cannot adopt arbitrary unrelated transfers. Supplying an incorrect hash fails closed and leaves pending for investigation; do not change persisted input to hide errors.
139
140
 
140
- RPC chain ID and immutable token are checked; event emitter, job ID, agreement hash, all stored terms, event amounts and results are verified. Evidence contains transaction/block hash and required confirmation depth. Readers need historical state access for the receipt block. The adapter checks current canonical inclusion at observation time; confirmations are a configurable risk policy, not an absolute finality guarantee. There is no continuous reorg watcher or automatic rollback of already completed workflow states. A production chain integration must monitor disappearing evidence and suspend/reconcile affected work explicitly. Chain outages preserve uncertainty and cannot be repaired by SDK code.
141
+ RPC chain ID and immutable token are checked; event emitter, job ID, agreement hash, all stored terms, event amounts and results are verified. Evidence contains transaction/block hash and required confirmation depth. Readers need historical state access for the receipt block. The adapter checks current canonical inclusion at observation time; confirmations are a configurable risk policy, not an absolute finality guarantee. The operations watcher can continuously revalidate evidence and place affected jobs on a safety hold. It does not automatically roll back completed financial states or erase observations; reconcile re-included transactions through an explicitly reviewed recovery procedure. Chain outages preserve uncertainty and cannot be repaired by SDK code.
141
142
 
142
143
  No timeout is automatic: a keeper or participant must submit a transaction, and the chain must process it. Deadlines provide an eligible escape path under the agreed allocation; they do not guarantee execution during censorship, chain failure or broken token transfers.
143
144
 
package/INTEGRATION.md CHANGED
@@ -1,9 +1,15 @@
1
+ # Integration update for 0.4
2
+
3
+ Read OPERATIONS.md before following the older integration checklist below. Persistence, authority credentials, renewal, spending authorization, application projections/chat tools, keeper/resume, execution queue, watcher/indexer, PDF receipts and a local UI shell are now provided as concrete modules. Choose the supplied implementation or adapt deliberately.
4
+
5
+ The remote MVP was not available in the SDK workspace; these utilities do not imply its existing routes or configuration have changed. Wire them into the actual app and run its tests. Deploy new contracts only with explicit deployment authorization; retain legacy custody records on their original contracts.
6
+
1
7
  # Integration instructions for the MVP coding agent
2
8
 
3
9
  Install after the maintainer publishes:
4
10
 
5
11
  ```sh
6
- npm install @allorc/a423-sdk@0.3.0
12
+ npm install @allorc/a423-sdk@0.4.0
7
13
  ```
8
14
 
9
15
  For testing before publication, install the maintainer's `npm pack` tarball instead. Do not silently install 0.1 and assume the workflow entry point exists.
@@ -28,7 +34,7 @@ Integrate the opt-in `@allorc/a423-sdk/workflow` API into the existing MVP. Insp
28
34
  8. Implement `release.execute/reconcile`, checking caller role, recorded verification and actual payment evidence. Verify exact amount/recipient and relevant event. The SDK state guard does not change contract rules; document whether the contract itself requires submission. Never infer successful payment from a broadcast hash alone.
29
35
  9. Implement `refund.execute/reconcile` based on actual contract eligibility. The default rule allows requesting a refund while funded/running; the adapter must enforce the deadline and absence of submission. Submitted disputes require an explicit supported resolution policy. Implement `resolve` only when that policy exists; confirmed resolution evidence must state whether money was released, refunded or remains locked. `resolved` alone does not imply payment.
30
36
  10. Add application deadline/expiry checks to authorization and the `expire` adapter. Schedule sweeps with authorized roles. Cancellation only applies before funding in the default workflow. Do not claim automatic sweeps or a dispute timeout exist if they are not implemented.
31
- 11. Configure each adapter's `reconcile` to inspect the provider using durable operation records. Unknown outcomes remain pending. Poll/reconcile in a durable worker. Handle configured confirmations, disappearing observations and reorgs according to the app's policy; the SDK does not include a chain watcher. Add explicit corrective transitions/policies if required rather than editing records outside CAS.
37
+ 11. Configure each adapter's `reconcile` to inspect the provider using durable operation records. Unknown outcomes remain pending. Poll/reconcile in a durable worker. Handle configured confirmations, disappearing observations and reorgs according to the app's policy; configure watchEvidenceOnce and the incremental indexer. Add explicit corrective transitions/policies if required rather than editing records outside CAS.
32
38
  12. Expose `createWorkflowHandler` through a Node-runtime Next.js catch-all route. Use `createWorkflowClient` or documented HTTP endpoints externally. The old root API and new workflow API use distinct models; migrate the client deliberately.
33
39
  13. Build machine-readable receipts through `workflow.receipts`. Revalidate chain evidence before generating chain-backed PDFs; derive amounts/participants from validated observations and use actual token decimals. Distinguish cached observations from freshly checked evidence.
34
40
 
package/LEGACY.md ADDED
@@ -0,0 +1,251 @@
1
+ # Legacy root API
2
+
3
+ This document describes only the original root exports. Use `/workflow`, `/escrow` and `/operations` for the current escrow implementation. Statements below about absent escrow/queues refer only to that original API.
4
+
5
+
6
+
7
+ # @allorc/a423-sdk
8
+
9
+ 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.
10
+
11
+ **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.
12
+
13
+ ## Quick setup in Next.js
14
+
15
+ Until publication, install the local package into your existing Next.js application:
16
+
17
+ ```sh
18
+ npm install /absolute/path/to/servers/server/a423_sdk
19
+ ```
20
+
21
+ After publication: `npm install @allorc/a423-sdk`.
22
+
23
+ Create `lib/a423.ts`:
24
+
25
+ ```ts
26
+ import 'server-only';
27
+ import { createA423Handler, createMemoryStore } from '@allorc/a423-sdk';
28
+
29
+ // Development only. Replace with a durable StateStore before deployment.
30
+ const store = createMemoryStore();
31
+ const token = process.env.A423_API_TOKEN;
32
+ const actorId = process.env.A423_AGENT_ID;
33
+ if (!token || !actorId) throw new Error('Configure A423_API_TOKEN and A423_AGENT_ID');
34
+
35
+ export const a423 = createA423Handler({
36
+ store,
37
+ authenticate(request) {
38
+ return request.headers.get('authorization') === `Bearer ${token}`
39
+ ? actorId
40
+ : null;
41
+ }
42
+ });
43
+ ```
44
+
45
+ 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.
46
+
47
+ Create `app/api/a423/[...path]/route.ts`:
48
+
49
+ ```ts
50
+ import { a423 } from '@/lib/a423';
51
+
52
+ export const runtime = 'nodejs';
53
+ export const GET = a423;
54
+ export const POST = a423;
55
+ ```
56
+
57
+ 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.
58
+
59
+ 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.
60
+
61
+ ## Use the typed client
62
+
63
+ ```ts
64
+ import { createA423Client } from '@allorc/a423-sdk';
65
+
66
+ const requester = createA423Client({
67
+ baseUrl: 'https://your-app.example/api/a423',
68
+ headers: { authorization: `Bearer ${requesterToken}` }
69
+ });
70
+ const worker = createA423Client({
71
+ baseUrl: 'https://your-app.example/api/a423',
72
+ headers: { authorization: `Bearer ${workerToken}` }
73
+ });
74
+
75
+ const job = await requester.offer({
76
+ worker_id: workerAgentId,
77
+ title: 'Prepare a short brief',
78
+ description: 'Research the topic and return a deliverable link.',
79
+ price: { amount_minor: 500, currency: 'USD' }
80
+ }, { idempotencyKey: 'brief-request-001' });
81
+
82
+ await worker.accept(job.id);
83
+ await worker.submit(job.id, { deliverable: 'https://example.com/brief.md' });
84
+ await requester.approve(job.id);
85
+ const receipts = await requester.receipts(job.id);
86
+ ```
87
+
88
+ `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.
89
+
90
+ 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.
91
+
92
+ 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.
93
+
94
+ 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.
95
+
96
+ ## HTTP API
97
+
98
+ Every request requires successful `authenticate(request)`. POSTs use `Content-Type: application/json`.
99
+
100
+ | Method | Route | Body | Who can call |
101
+ | --- | --- | --- | --- |
102
+ | POST | `/api/a423/offers` | `{worker_id,title,description,price}` and `Idempotency-Key` header | Authenticated requester |
103
+ | GET | `/api/a423/jobs/:id` | None | Requester or assigned worker |
104
+ | POST | `/api/a423/jobs/:id/accept` | `{}` | Assigned worker |
105
+ | POST | `/api/a423/jobs/:id/submit` | `{deliverable}` | Assigned worker |
106
+ | POST | `/api/a423/jobs/:id/approve` | `{}` | Requester |
107
+ | GET | `/api/a423/jobs/:id/receipts` | None | Requester or assigned worker |
108
+
109
+ 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.
110
+
111
+ External agents can use plain HTTP without installing the SDK. For example:
112
+
113
+ ```sh
114
+ curl https://your-app.example/api/a423/offers \
115
+ -H "Authorization: Bearer $A423_REQUESTER_TOKEN" \
116
+ -H 'Content-Type: application/json' \
117
+ -H 'Idempotency-Key: brief-request-001' \
118
+ -d '{"worker_id":"worker-agent-id","title":"Brief","description":"Prepare a short brief","price":{"amount_minor":500,"currency":"USD"}}'
119
+ ```
120
+
121
+ 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.
122
+
123
+ ## Workflow and retry behaviour
124
+
125
+ A job follows `offered → accepted → submitted → completed`. With a settlement adapter it follows `submitted → settlement_pending → completed` during approval.
126
+
127
+ 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.
128
+
129
+ 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.
130
+
131
+ 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.
132
+
133
+ ## Storage adapter
134
+
135
+ `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.
136
+
137
+ Implement the `StateStore` interface against your database:
138
+
139
+ ```ts
140
+ interface StateStore {
141
+ get(id: string): Promise<Job | null>;
142
+ create(job: Job): Promise<boolean>;
143
+ compareAndSet(id: string, expectedVersion: number, job: Job): Promise<boolean>;
144
+ }
145
+ ```
146
+
147
+ `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.
148
+
149
+ 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.
150
+
151
+ ## Identity and authentication
152
+
153
+ `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.
154
+
155
+ `@allorc/identity` 0.3.0 can form a public agent ID:
156
+
157
+ ```ts
158
+ import { VERSION, createIdentity } from '@allorc/identity';
159
+
160
+ const identity = createIdentity({
161
+ version: VERSION,
162
+ organization_public_key: 'externally-supplied-public-value',
163
+ agent_reference: 'worker-42',
164
+ agent_name: 'Alice',
165
+ agent_email: 'alice@example.com'
166
+ });
167
+ // Register identity.agent_id with your credential/authority system separately.
168
+ ```
169
+
170
+ 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.
171
+
172
+ 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.
173
+
174
+ ## Optional settlement adapter
175
+
176
+ Without an adapter, approval marks the job completed with:
177
+
178
+ ```json
179
+ {"status":"not_configured","reference":null}
180
+ ```
181
+
182
+ **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`:
183
+
184
+ ```ts
185
+ const handler = createA423Handler({
186
+ store,
187
+ authenticate,
188
+ settlement: {
189
+ async settle({ job, idempotencyKey }) {
190
+ // Invoke your own escrow/payment service with this stable key.
191
+ // Persistently deduplicate that key, including concurrent invocations.
192
+ const result = await yourSettlementService.release({
193
+ jobId: job.id,
194
+ requesterId: job.requester_id,
195
+ workerId: job.worker_id,
196
+ price: job.price,
197
+ idempotencyKey
198
+ });
199
+ return { reference: result.receiptId };
200
+ }
201
+ }
202
+ });
203
+ ```
204
+
205
+ `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.
206
+
207
+ 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.
208
+
209
+ 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.
210
+
211
+ ## Errors and limits
212
+
213
+ 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.
214
+
215
+ | HTTP status | Typical code |
216
+ | --- | --- |
217
+ | 400 | `INVALID_INPUT`, `INVALID_JSON` |
218
+ | 401 | `UNAUTHENTICATED` |
219
+ | 403 | `FORBIDDEN` |
220
+ | 404 | `NOT_FOUND` |
221
+ | 405 | `METHOD_NOT_ALLOWED` |
222
+ | 409 | `INVALID_STATE`, `CONFLICT`, `IDEMPOTENCY_CONFLICT` |
223
+ | 413 | `BODY_TOO_LARGE` |
224
+ | 415 | `UNSUPPORTED_MEDIA_TYPE` |
225
+ | 500 | `INTERNAL_ERROR` |
226
+ | 502 | `SETTLEMENT_UNAVAILABLE` |
227
+
228
+ 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.
229
+
230
+ ## Package exports
231
+
232
+ | Export | Purpose |
233
+ | --- | --- |
234
+ | `createA423Handler(options)` | Standard Web Request/Response handler usable in Next.js routes |
235
+ | `createA423Client(options)` | Typed HTTP client |
236
+ | `createMemoryStore()` | Explicit local development store |
237
+ | `A423Error` | Structured client/protocol error |
238
+ | `PROTOCOL_VERSION` | `a423.experimental.v1` |
239
+
240
+ 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.
241
+
242
+ ## Development
243
+
244
+ ```sh
245
+ npm test
246
+ npm run pack:check
247
+ ```
248
+
249
+ 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.
250
+
251
+ 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.
package/OPERATIONS.md ADDED
@@ -0,0 +1,182 @@
1
+ # A423 0.4 operational integration
2
+
3
+ This guide covers concrete SDK modules and their deployment boundaries. The remote MVP's source was unavailable during this SDK change, so its chat tools, dashboard, environment, reset route and testnet deployment have not been modified. Use the supplied bridge in that repository, then run the actual app's integration tests. Existing contract-level constraints remain explicit.
4
+
5
+ ## Complete application factory
6
+
7
+ `createEscrowApplication` wires the concrete implementations into one handler and worker configuration. Supply `storage`, `authority`, allowlisted `escrows` (provider/chain/contract/token/confirmations), `signerFor`, `addressFor`, raw-token `limitsFor`, explicit `approveSigning`, and real `validateArtifact`/optional `execute` handlers. It composes active-authority checks, spending reservations, job safety, task validation, consent, fresh receipts, chat projections and maintenance. Independent arbitration is required by default at the application policy level (arbitrator differs from payer, worker and verifier), even though the reference contract permits verifier/arbitrator overlap. `keeperEnabled` is false by default.
8
+
9
+ ```ts
10
+ const app = createEscrowApplication({
11
+ storage, authority, escrows: [escrowConfiguration],
12
+ signerFor, addressFor, limitsFor, approveSigning,
13
+ validateArtifact, execute, keeperEnabled: true,
14
+ });
15
+ export const { applicationOptions, workerOptions } = app;
16
+ // Next.js: export const GET = app.handler; export const POST = app.handler;
17
+ ```
18
+
19
+ This factory does not invent wallet approvals or output-quality checks. Its callbacks must call your actual trusted signing/execution services. An inactive registry identity cannot authorize workflow/signing operations. The more granular recipes below are available when integrating with an existing app rather than the full factory.
20
+
21
+ ## 1. Durable state and transaction journal
22
+
23
+ ```ts
24
+ import { createSqliteStorage } from '@allorc/a423-sdk/storage';
25
+ const storage = createSqliteStorage({ filename: '/private/persistent/a423.sqlite' });
26
+ // storage.jobs, transactions, queue, spending, kv, close()
27
+ ```
28
+
29
+ The driver uses Node's built-in SQLite, WAL, full synchronous commits, busy timeout, restricted database permissions and `BEGIN IMMEDIATE` for compound operations. Multiple processes on the same host/file coordinate CAS and nonce reservations. It is not a distributed database or a serverless filesystem substitute. Keep the entire SQLite/WAL set on local persistent storage with appropriate backups and access controls. Raw signed transactions are protected operational records; do not expose `transactions` through an API.
30
+
31
+ Workflow changes and projected events are committed atomically. `jobs.list({ actor, after, limit })` and `jobs.events({ actor, after, limit })` enforce participant filtering when an actor is supplied. Calls without actor are administrative worker capabilities; never expose them unauthenticated. Event cursors are global, monotonic and suitable for resumable SSE.
32
+
33
+ `transactions.reserveNonce` atomically allocates and deduplicates by chain/account/operation. `appendReplacement` preserves original intent and prior hashes under an expected-last-hash check. All writers using a signer account must coordinate through this journal. A reservation with no signed transaction must be resumed to fill the gap; the driver cannot guess whether an external signer has used a nonce.
34
+
35
+ ## 2. Authority and authentication
36
+
37
+ ```ts
38
+ import {
39
+ createAuthority, createAuthorityHandler,
40
+ createCredentialAuthenticator, createCredentialProvider,
41
+ } from '@allorc/a423-sdk/authority';
42
+
43
+ const authority = createAuthority({
44
+ issuer: 'your-authority-id', audience: 'your-app-id',
45
+ privateKey: authoritySigningKey, kv: storage.kv,
46
+ credentialTtlSeconds: 3600,
47
+ });
48
+ const authenticate = createCredentialAuthenticator({ authority });
49
+ const authorityHandler = createAuthorityHandler({
50
+ authority,
51
+ allowRequest: enforceYourRateLimitAndOriginPolicy,
52
+ });
53
+ ```
54
+
55
+ The authority key is externally supplied Ed25519 key material. Store it securely. **Administrative enrollment is not an HTTP bootstrap endpoint.** After external operator approval, call `registerOrganization({id,name,publicKey})`. The organization signs the JSON bytes of `authority.registration({organization,agentId,publicKey,scopes})` with its own Ed25519 private key. Call `registerAgent` with those fields and the base64url `organizationSignature`. The authority checks the organization signature before enrollment. An existing registration cannot be silently overwritten. Use explicit revocation and a reviewed re-enrollment/rotation process.
56
+
57
+ This registry is your chosen trust system, not legal-company verification, government accreditation or proof that a public organization name is genuine. Keep `@allorc/identity` as public deterministic record formation; its ID can be the registered `agentId`, but possession/authentication comes from a separate key.
58
+
59
+ The public proof handler only serves:
60
+
61
+ - `POST /api/a423-auth/challenge` with `{ agent_id }`.
62
+ - `POST /api/a423-auth/exchange` with `{ challengeId, signature }`.
63
+
64
+ Sign the exact UTF-8 `JSON.stringify(challenge)` bytes with the registered agent Ed25519 key and base64url-encode the signature. A challenge is single-use, short-lived, audience-bound and tied to the registered key. The exchange returns `{credential,expires_at}`. Present the credential as `Authorization: Bearer …`. Credentials contain issuer, audience, subject, organization, key fingerprint, scopes, expiry and a unique identifier. Revoked agents/organizations are denied on verification. The handler defaults to denying requests unless `allowRequest` explicitly permits them; supply real rate limiting before exposing it. There is no default secret or demo bootstrap.
65
+
66
+ Use `createCredentialProvider` to renew with a new proof before expiration:
67
+
68
+ ```ts
69
+ const credential = createCredentialProvider({
70
+ agentId,
71
+ challenge: fetchChallengeFromAuthority,
72
+ exchange: exchangeProofAtAuthority,
73
+ signChallenge: async bytes => signUsingAgentKey(bytes), // base64url signature
74
+ });
75
+ // For SDK HTTP clients: headers: async () => ({Authorization: `Bearer ${await credential()}`})
76
+ ```
77
+
78
+ Renewal requires key possession, not just an old bearer token. `pruneChallenges()` removes expired unused challenges. For separate receiver services, `createCredentialVerifier({issuer,audience,publicKey,isActive})` uses only the authority public key and your revocation/status lookup; receivers do not need the authority private key. Wrap it as `{verifyCredential: verifier}` for the credential authenticator. `isActive` must check current organization/agent status, key binding and required scopes; unavailable status should fail closed. An offline token signature by itself does not establish current enrollment.
79
+
80
+ Keep organization, agent, authority, treasury, verifier and arbitrator key references distinct where your trust policy requires it. The SDK does not choose signer indices or fallback to a first key. Protect the whole authority administration capability, not only its HTTP routes.
81
+
82
+ ## 3. Typed tasks and evidence
83
+
84
+ `validateTask` checks this reference task structure. Use it in `validateTerms`, together with application-specific criteria and escrow validation:
85
+
86
+ ```ts
87
+ const task = {
88
+ id: 'research-1', type: 'research', description: 'Produce the agreed report',
89
+ deliverables: [{id: 'report', media_type: 'text/markdown', description: 'Research report'}],
90
+ acceptance: {validator: 'report-validator', version: '1', criteria: {minWords: 200}},
91
+ };
92
+ ```
93
+
94
+ Task types and criteria remain extensible JSON. This schema does not prove output quality. `validateArtifact` must fetch the actual immutable output, hash its bytes, compare the submitted digest, and evaluate accepted criteria. Execute only explicitly trusted task handlers; never execute arbitrary code because it appears in task metadata.
95
+
96
+ `validateEscrowEvidence` requires chain/contract/token, job and agreement commitment, transaction/block hashes, parties, raw amount, worker payout, state and event fields. The reference adapter validates its own evidence. For custom adapters, configure the workflow's `validateEvidence({job,action,evidence})` hook with the appropriate schema. A hook being available does not mean an arbitrary custom provider is verified.
97
+
98
+ ## 4. Mandatory spending and signing authorization
99
+
100
+ ```ts
101
+ import { createSpendingAuthorizer } from '@allorc/a423-sdk/operations';
102
+ const authorizeTransaction = createSpendingAuthorizer({
103
+ spending: storage.spending,
104
+ limitsFor: async context => approvedLimitsFor(context.actor),
105
+ authorize: async context => checkDelegationAndSafety(context),
106
+ });
107
+ ```
108
+
109
+ Every reference adapter must now supply `authorizeTransaction`. It runs before any initial token approval/reset/funding signature and again before a rebroadcast. Use `checkDelegationAndSafety` to confirm the actor's current grant, job safety status and expected signer. The atomic spending store enforces explicit `perTransaction`, `daily` and `total` caps as raw-token integer strings, scoped to payer/chain/token. A reservation is shared across the job's approval and funding retries; callbacks cannot override its amount or scope through limits. UTC day changes recheck today's allowance before signing. Concurrent jobs cannot each spend the same remaining allowance.
110
+
111
+ Reservations are conservative and remain after approvals/unknown outcomes. Do not release them merely because an RPC request failed. `spending.release(id)` is an administrative reconciliation capability; releasing a committed spend would defeat the total cap. Retain committed reservations. Apply equivalent policy to any other app-specific transfer/credit tool outside this adapter.
112
+
113
+ A false pre-sign decision rejects before signing, allowing the workflow to recover or cancel. A denied rebroadcast preserves the pending operation because the transaction may already exist on-chain. Policy exceptions also preserve pending state for investigation.
114
+
115
+ `createManagedSigner({address,provider,signDigest,authorize})` implements ethers transaction/message/typed-data signing using an external digest signer. It verifies that returned signatures match the configured address. Connect your actual KMS/HSM/remote signing API in `signDigest`; no vendor credentials are fabricated. `createDelegationAuthorizer` provides concrete transaction grants with expiry, chain IDs, contract addresses, calldata selectors, native-value/gas/fee limits. It denies message and typed-data signing by default; define separate narrowly scoped policies for those operations.
116
+
117
+ ## 5. Keeper, execution queue and recovery
118
+
119
+ `reconcile` observes only. `resume` explicitly authorizes continuation of the original persisted operation, using its original action/input/idempotency key. This solves the approval-confirmed/funding-not-created recovery case without reconstructing a user's original key.
120
+
121
+ ```ts
122
+ await runMaintenanceOnce({
123
+ workflow, store: storage.jobs,
124
+ actorFor: delegatedMaintenanceActor,
125
+ allowExecute: true,
126
+ });
127
+ ```
128
+
129
+ `actorFor(job)` must return an authorized participant for that action, not an invented system identity. For pending actions it generally returns `job.pending.actor`; timeout/refund can use the agreed requester/worker role. On-chain permissionless timeout does not bypass the workflow's authenticated role policy. When enabled, the keeper resumes pending operations, requests eligible unsubmitted refunds/submitted timeout allocation, and expires unfunded agreements with `terms.expires_at`. Include `createExpiryAdapter()` for the latter. If no expiry is agreed, it is not guessed.
130
+
131
+ For execution, `enqueueRunningJobs` creates one durable execution item per confirmed start event. `runExecutionOnce` uses a lease/heartbeat and checks workflow safety, running state, task schema and your mandatory `beforeExecute(job)` eligibility callback. Revalidate current chain custody/deadline immediately before doing expensive work. Your `execute({job,idempotencyKey})` performs the actual AI/tool work and returns `{result_hash,…}`. The runner checkpoints the result before submitting, so a submission retry can reuse it without re-running the AI. External effects before checkpoint must still deduplicate the supplied execution key. Leases and CAS do not provide exactly-once external side effects.
132
+
133
+ Supply the background-worker configuration via `A423_CONFIG_MODULE`; `scripts/operations-worker.mjs` runs monitoring, optional indexing/replacement, maintenance, dispatch, execution, webhooks and challenge pruning on a five-second loop. Its default does not authorize transaction execution: set `workerOptions.allowExecute` explicitly. It stops on SIGINT/SIGTERM. Run it under your own service manager with restart/alerting/health checks. Long work must not depend on a Next.js request staying open.
134
+
135
+ ## 6. Evidence monitoring, indexing and fresh receipts
136
+
137
+ `createEvidenceRevalidator` checks the configured RPC chain and immutable token, canonical successful receipt and confirmation depth, emitting contract/job/event, all accepted on-chain terms, cached amounts/parties/state/result hash and settlement/dispute fields. It returns valid/invalid/insufficient-confirmations; RPC failures throw rather than being misreported as payment failure. Use the provider corresponding to the job's allowlisted configuration.
138
+
139
+ `watchEvidenceOnce({store,revalidate})` stores per-job safety results. Invalid or unavailable transaction evidence quarantines the job. The SQLite store's `getSafety` is checked by workflow actions, resume and queued execution; the pre-sign policy should also recheck it to close the interval between monitoring and signing. Read access remains available for investigation. Valid rechecks can clear the hold; orphaned observations are not silently rewritten. Deep reorg recovery requires an explicit authoritative reconstruction of the financial record. Do not roll a paid job back to accepted or erase evidence just to allow a retry.
140
+
141
+ `createEscrowIndexer` scans confirmed logs in bounded batches from an explicit deployment block using durable cursors. It parses contract events and persists observations; `onEvent` receives a stable key for idempotent projection. On cursor reorg it invokes `onReorg`, marks orphaned indexed observations, rewinds and reindexes. Configure one indexer worker per cursor and make callbacks idempotent. The indexer discovers facts; it does not bypass quote acceptance or automatically authorize a payment. Link observed contract job IDs back to registered workflow jobs before applying any app mutation.
142
+
143
+ `freshReceipts({workflow,id,actor,revalidate})` refuses stale/unavailable evidence. `renderReceiptPdf(freshReceipt)` renders that structured snapshot; it is a plain evidence document, not a legal attestation or automatically signed certificate. The application handler composes both, so JSON and PDF endpoints perform fresh checks. Formatting raw-token amounts into human display units remains a token-metadata concern. A renderer called directly on arbitrary JSON cannot make those claims true.
144
+
145
+ ## 7. Chat, dashboard, wallet consent and notifications
146
+
147
+ `createChatTools` supplies `send_job_offer`, `propose_quote`, `accept_quote`, `fund_escrow`, `work_on_job`, `submit_job`, `verify_job`, `release_escrow`, `refund_escrow`, `dispute_job`, `resolve_job`, `timeout_job`, `cancel_job`. Instantiate tools with the authenticated actor from your chat session. Replace legacy service calls behind your app's `A423_CHAT_MODE` flag. Do not choose actor IDs from tool arguments.
148
+
149
+ `createApplicationHandler` combines the workflow routes and authenticated app projections. Its `/api/state` merges optional legacy state with `a423.jobs` and chain-evidence-based `a423.wallet_transactions`. It exposes arbitrator and settled/disputed/pending states. Fresh receipt routes use `/api/a423-receipts/:id[.pdf]`; wallet consent uses `/api/a423-worker-consent/:id` and returns **typed data only**, restricted to the assigned worker. The SDK never signs worker consent on the server via this route. An EIP-1193 wallet can sign it; supply the public signature to the payer to fund. ERC-1271 wallets may use a custom signature format and account transaction path; the EOA dashboard button is not a universal smart-account UI.
150
+
151
+ Mount the combined handler on the routes your Next.js app owns (for example `app/api/[...path]/route.ts`) or delegate existing `/api/state`/receipt/chat endpoints explicitly. Do not shadow unrelated APIs. Use `runtime = 'nodejs'`. All state projections remain actor scoped. Configure your CSRF/CORS/origin policies when adding cookie authentication; the SDK does not silently configure your deployment perimeter.
152
+
153
+ The localhost shell `examples/operations-server.mjs` with `operations-dashboard.html` displays workflows, parties, agreement, submission, evidence, pending actions, and fresh receipts. It includes quote/accept/fund/start/submit/verify/release/refund/dispute/resolve/timeout/cancel/expire controls, editable JSON input, persistent retry key, and a worker-wallet consent button. It holds bearer credentials only in page memory, uses DOM text rather than interpolating untrusted HTML, and does not provision identities or keys. It is a reference shell, not a claim that the remote MVP dashboard was edited.
154
+
155
+ `createEventStream` supports authenticated participant-scoped SSE and Last-Event-ID; streams close periodically and reconnect using the cursor. `createWebhookDispatcher` projects durable events to an outbox, signs payloads using HMAC-SHA256 over `timestamp + '.' + exactBody`, and supplies stable delivery IDs. Configure explicit HTTPS endpoints and per-subscription secrets (at least 32 characters), verify timestamps/signatures on receivers, and deduplicate IDs. Redirects are disabled. Destination ownership, DNS/egress restrictions, TLS and quotas remain operator responsibilities. Delivery is at least once with bounded exponential backoff and a failed queue state after exhausted attempts; project and deliver workers must run continuously.
156
+
157
+ ## 8. Multi-deployment routing and bounded fee replacement
158
+
159
+ `createEscrowRegistry([configuration,…])` selects allowlisted adapters by chain/contract. Each entry binds an immutable token and its own provider/confirmation policy. Unknown destinations fail closed. This supports multiple standard-token deployments without pretending one reference contract supports every asset or chain.
160
+
161
+ `replacePendingTransaction` checks that no attempt is mined, raises both EIP-1559 fee fields at least 25%, preserves chain/nonce/to/data/value/gas limit, checks the signer and explicit authorization, and atomically appends the signed replacement **before broadcasting**. `createFeeReplacementWorker` can prepare these replacements automatically under explicit age, fee and attempt caps. The original workflow resume path then broadcasts the latest lineage while checking for any already-mined attempt. Replacement policy and any managed-signer delegation both apply. Legacy gas-price transactions, cancellation and intent changes are not supported. This is bounded automation, not a guarantee against censorship or an exhausted payer balance.
162
+
163
+ ## 9. Local reset, migration and deployment
164
+
165
+ `resetDevelopmentData` is dry-run by default. It requires local chain ID 1337/31337, literal `RESET_LOCAL_A423`, and an application callback confirming the local chain was reset. It never runs automatically on application startup and is not exposed as an unauthenticated HTTP route. Explicitly choose `includeAuthority` when resetting the governor registry; otherwise it survives. Never reset nonce journals independently of the chain/account state.
166
+
167
+ `planLegacyMigration` reports limitations. `migrateUnfundedLegacy` requires an explicit mapping, a new job ID and an unfunded source state. It creates a new draft with source-hash provenance and never carries old acceptances. Existing funded/terminal jobs stay on their original contracts and receipts. There is no safe generic SQL operation that turns old on-chain custody into a new contract's custody. Move funds only through the original agreed release/refund process, then negotiate a new job if appropriate.
168
+
169
+ The contract deploy script is a dry-run unless `--broadcast` is explicitly supplied:
170
+
171
+ ```sh
172
+ A423_CHAIN_ID=31337 A423_TOKEN_ADDRESS=0x... A423_RPC_URL=http://127.0.0.1:8545 \
173
+ node scripts/deploy-escrow.mjs
174
+ # Deployment additionally needs A423_DEPLOYER_KEY and explicit --broadcast.
175
+ # --verify submits complete compiler input to Sourcify after deployment.
176
+ ```
177
+
178
+ The script checks the RPC chain/token, saves deployment metadata before source verification, and distinguishes submitted verification from completed verification. Public Sourcify does not normally index your private local chain. Verify local deployments by recompilation and the local EVM tests; verify supported testnets with the external service and check its returned ticket separately. No live testnet deployment, source-verification completion or audit is inferred from this script existing.
179
+
180
+ ## 10. Remaining boundaries
181
+
182
+ No independently completed security audit is claimed. KMS/HSM provisioning, organization/legal vetting, public testnet deployment and verification completion require real external access and operational decisions. The absent remote app's legacy chat/dashboard/seed/reset/environment must be wired in its own repository. Partial releases/milestones, resubmission, fee/rebasing token support, admin recovery/pause/upgrades, guaranteed challenge windows, and an objective quality oracle remain reference-contract constraints. These are not silently added by operational hooks.