@a2a-exposed/encryption 0.0.0-stage → 0.8.56

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/ledger.mjs ADDED
@@ -0,0 +1,330 @@
1
+ // Local durable acceptance only. No network/model callbacks are invoked by this API.
2
+ import {randomUUID,createHash}from'node:crypto';
3
+ import {LocalState,LocalStateError}from'./local-state.mjs';
4
+ import {EXTENSION,EncryptionError,canonicalJSON,parseJSON,validateEnvelopeStructure,verifyEnvelopeSignature,verifyBundle,open,seal}from'./index.mjs';
5
+ import {createAssignment,envelopeDigest,verifyAssignment,validateAssignmentStructure,openAssignedResponse}from'./assignment.mjs';
6
+ import {validateContent}from'./content.mjs';
7
+ import {makeReceiptCarrier}from'./carriers.mjs';
8
+ import {readTaskCarrier}from'./tasks.mjs';
9
+ export class LedgerError extends LocalStateError{constructor(code,next='Preserve local state and inspect pending delivery/receipt IDs; no automatic new-message retry was performed.'){super(code,next);this.code=code;this.next=next;}}
10
+ const fail=(code,next)=>{throw new LedgerError(code,next);};
11
+ const id=value=>typeof value==='string'&&/^[\x21-\x7e]{1,200}$/.test(value);
12
+ const bundleHash=value=>createHash('sha256').update('a2a-exposed sealed bundle v1\n'+canonicalJSON(value)).digest('base64url');
13
+ const binding=h=>Object.fromEntries(['contextId','messageId','taskId','role','state','kind'].map(key=>[key,h[key]]));
14
+ function page(limit,cursor){if(!Number.isSafeInteger(limit)||limit<1||limit>100||!Number.isSafeInteger(cursor)||cursor<0)fail('listing_inputs','Use an explicit limit 1..100 and the exact nonnegative returned cursor.');}
15
+ export class DeliveryLedger{
16
+ constructor(state){if(!(state instanceof LocalState))fail('local_store_required');this.state=state;}
17
+ resolve(localId,peer,{pairingIdentity,pairingVerified}={}){
18
+ this.state.ready();if(this.state.db.prepare("SELECT value FROM meta WHERE key='restore_paused'").get()?.value==='1')fail('restore_archive_only');
19
+ if(pairingVerified!==true)fail('pairing_unverified','The trusted local transport must verify its existing human-authorized pairing; peer/server assertions do not grant it.');
20
+ const inspection=this.state.inspect(peer),record=inspection.current,local=this.state.row(localId),bundle=parseJSON(local.bundle);
21
+ if(!record||!['tofu-pinned','independently-verified'].includes(inspection.live_state)||record.state==='revoked'||record.policy==='off')fail('trust_not_ready','Inspect the exact local peer proposal/policy; pending, revoked or off trust cannot authorize sealed delivery.');
22
+ if(record.pairingIdentity!==pairingIdentity||record.configIdentity!==this.state.configId()||record.localId!==localId||local.live_state!=='local-only'||record.localBundleDigest!==bundleHash(bundle)||record.localKeyId!==bundle.keyId)fail('trust_binding_changed');
23
+ if(bundle.identityKey===record.bundle.identityKey)fail('same_identity');
24
+ return {localId,peer,bundle,remote:record.bundle,record,digest:inspection.current_digest,localIdentity:bundle.identityKey,peerIdentity:record.bundle.identityKey,pairingIdentity};
25
+ }
26
+ assertPin(pin,attestation){const current=this.resolve(pin.localId,pin.peer,attestation);if(current.digest!==pin.digest)fail('trust_binding_changed');return current;}
27
+ async receiveInitial({localId,peer,envelope,observedTaskId,protocol,activatedExtension,pairingIdentity,pairingVerified=false,allowOsConsent=false,now=Date.now()}={}){
28
+ envelope=validateEnvelopeStructure(envelope);if(activatedExtension!==EXTENSION||!['0.3','1.0'].includes(protocol))fail('extension_not_activated');
29
+ if(!id(observedTaskId)||envelope.header.taskId!==null||envelope.header.role!=='user'||envelope.header.kind!=='message')fail('initial_assignment_inputs');
30
+ const attestation={pairingIdentity,pairingVerified},pin=this.resolve(localId,peer,attestation);
31
+ // Reject bad public signatures/bindings before any native/file credential read.
32
+ await verifyEnvelopeSignature({envelope,sender:pin.remote,recipient:pin.bundle,now});
33
+ const identity=await this.state.loadIdentity(localId,{allowOsConsent});
34
+ const prepared=await createAssignment({identity,senderBundle:pin.remote,requestEnvelope:envelope,taskId:observedTaskId,now});
35
+ const digest=await envelopeDigest(envelope),deliveryId=randomUUID(),h=envelope.header;
36
+ return this.state.locked(async()=>{
37
+ this.assertPin(pin,attestation);
38
+ const prior=this.state.db.prepare('SELECT * FROM replay WHERE local_id=? AND sender=? AND message_id=?').get(pin.localIdentity,pin.peerIdentity,h.messageId);
39
+ if(prior){
40
+ if(prior.digest!==digest||prior.context!==h.contextId||prior.task_id!==observedTaskId)fail('replay_conflict');
41
+ const assignment=this.state.db.prepare("SELECT * FROM assignments WHERE local_identity=? AND peer_identity=? AND request_message_id=? AND direction='inbound'").get(pin.localIdentity,pin.peerIdentity,h.messageId);
42
+ const delivery=this.state.db.prepare('SELECT id,state FROM deliveries WHERE local_id=? AND sender=? AND message_id=?').get(pin.localIdentity,pin.peerIdentity,h.messageId);
43
+ if(!assignment||!delivery)fail('ledger_corrupt');
44
+ return {ok:true,duplicate:true,deliveryId:delivery.id,deliveryState:delivery.state,receipt:parseJSON(assignment.receipt),taskId:assignment.task_id,next:'The original durable acceptance is reused. Do not deliver a second task or create a new acknowledgment.'};
45
+ }
46
+ if(this.state.db.prepare("SELECT request_message_id FROM assignments WHERE local_identity=? AND peer_identity=? AND task_id=? AND direction='inbound'").get(pin.localIdentity,pin.peerIdentity,observedTaskId))fail('assignment_conflict');
47
+ this.state.db.prepare('INSERT INTO replay VALUES(?,?,?,?,?,?,?)').run(pin.localIdentity,pin.peerIdentity,h.messageId,digest,h.contextId,observedTaskId,now);
48
+ this.state.db.prepare('INSERT INTO assignments VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)').run(pin.localIdentity,pin.peerIdentity,h.messageId,'inbound',prepared.receipt.ackId,peer,h.contextId,digest,observedTaskId,canonicalJSON(prepared.receipt),protocol,pin.digest,localId,'pending');
49
+ this.state.db.prepare('INSERT INTO deliveries VALUES(?,?,?,?,?,?,?)').run(deliveryId,pin.localIdentity,pin.peerIdentity,h.messageId,canonicalJSON(envelope),'pending',now);
50
+ this.state.db.prepare('INSERT INTO delivery_provenance VALUES(?,?,?,?,?,?)').run(deliveryId,peer,pin.digest,pin.record.version,localId,digest);
51
+ // Receipt escapes only after LocalState.locked has committed all four rows.
52
+ return {ok:true,duplicate:false,deliveryId,deliveryState:'pending',receipt:prepared.receipt,taskId:observedTaskId,next:'Request locally opened and durably queued. The local consumer has not acknowledged it; publish only this stable signed receipt.'};
53
+ });
54
+ }
55
+ async receiveFollowup({localId,peer,envelope,observedTaskId,protocol,activatedExtension,pairingIdentity,pairingVerified=false,allowOsConsent=false,now=Date.now()}={}){
56
+ envelope=validateEnvelopeStructure(envelope);const h=envelope.header;
57
+ if(activatedExtension!==EXTENSION||!['0.3','1.0'].includes(protocol))fail('extension_not_activated');
58
+ if(!id(observedTaskId)||h.taskId!==observedTaskId||h.role!=='user'||h.kind!=='message')fail('followup_inputs');
59
+ const attestation={pairingIdentity,pairingVerified},pin=this.resolve(localId,peer,attestation);
60
+ const check=()=>{const assignment=this.state.db.prepare("SELECT * FROM assignments WHERE local_identity=? AND peer_identity=? AND task_id=? AND direction='inbound'").get(pin.localIdentity,pin.peerIdentity,observedTaskId);
61
+ if(!assignment||assignment.context!==h.contextId||assignment.protocol!==protocol||assignment.trust_digest!==pin.digest)fail('assignment_required');return assignment;};check();
62
+ await verifyEnvelopeSignature({envelope,sender:pin.remote,recipient:pin.bundle,now});const identity=await this.state.loadIdentity(localId,{allowOsConsent});
63
+ validateContent(await open({envelope,identity,sender:pin.remote,recipient:pin.bundle,expected:binding(h),now}),h);const digest=await envelopeDigest(envelope);
64
+ return this.state.locked(async()=>{
65
+ this.assertPin(pin,attestation);check();const prior=this.state.db.prepare('SELECT * FROM replay WHERE local_id=? AND sender=? AND message_id=?').get(pin.localIdentity,pin.peerIdentity,h.messageId);
66
+ if(prior){if(prior.digest!==digest||prior.context!==h.contextId||prior.task_id!==h.taskId)fail('replay_conflict');const row=this.state.db.prepare('SELECT id FROM deliveries WHERE local_id=? AND sender=? AND message_id=?').get(pin.localIdentity,pin.peerIdentity,h.messageId);if(!row)fail('ledger_corrupt');return {ok:true,deliveryId:row.id,duplicate:true};}
67
+ const head=this.state.db.prepare('SELECT terminal FROM chains WHERE local_identity=? AND peer_identity=? AND context=? AND task_id=? AND author_identity=?').get(pin.localIdentity,pin.peerIdentity,h.contextId,h.taskId,pin.localIdentity);if(head?.terminal)fail('task_terminal');
68
+ const deliveryId=randomUUID();this.state.db.prepare('INSERT INTO replay VALUES(?,?,?,?,?,?,?)').run(pin.localIdentity,pin.peerIdentity,h.messageId,digest,h.contextId,h.taskId,now);
69
+ this.state.db.prepare('INSERT INTO deliveries VALUES(?,?,?,?,?,?,?)').run(deliveryId,pin.localIdentity,pin.peerIdentity,h.messageId,canonicalJSON(envelope),'pending',now);
70
+ this.state.db.prepare('INSERT INTO delivery_provenance VALUES(?,?,?,?,?,?)').run(deliveryId,peer,pin.digest,pin.record.version,localId,digest);
71
+ return {ok:true,deliveryId,duplicate:false,taskId:h.taskId,next:'Follow-up durably queued under the original signed assignment; peer content remains untrusted and unconsumed.'};
72
+ });
73
+ }
74
+ async prepareRequest({localId,peer,content,contextId=randomUUID(),messageId=randomUUID(),taskId=null,protocol,remoteExtension,pairingIdentity,pairingVerified=false,allowOsConsent=false,now=Date.now()}={}){
75
+ if(remoteExtension!==EXTENSION||!['0.3','1.0'].includes(protocol))fail('extension_not_activated');
76
+ const header={contextId,messageId,taskId,role:'user',state:null,kind:'message'};content=validateContent(content,header);
77
+ const attestation={pairingIdentity,pairingVerified},pin=this.resolve(localId,peer,attestation);
78
+ if(this.state.db.prepare('SELECT message_id FROM outbound WHERE local_identity=? AND peer_identity=? AND message_id=?').get(pin.localIdentity,pin.peerIdentity,messageId))fail('message_id_used','Inspect the original outbound ID/state. Do not re-seal or send a new ID as an automatic retry.');
79
+ const checkTask=()=>{
80
+ if(taskId===null)return;
81
+ const assignment=this.state.db.prepare("SELECT trust_digest FROM assignments WHERE local_identity=? AND peer_identity=? AND context=? AND task_id=? AND direction='outbound'").get(pin.localIdentity,pin.peerIdentity,contextId,taskId);
82
+ if(!assignment||assignment.trust_digest!==pin.digest)fail('assignment_required','Task follow-up requires the locally verified signed assignment, never a provisional Worker task ID.');
83
+ const head=this.state.db.prepare('SELECT terminal FROM chains WHERE local_identity=? AND peer_identity=? AND context=? AND task_id=? AND author_identity=?').get(pin.localIdentity,pin.peerIdentity,contextId,taskId,pin.peerIdentity);if(head?.terminal)fail('task_terminal','Start a new request/task for new work; the authenticated terminal task cannot be reopened.');
84
+ };checkTask();
85
+ const identity=await this.state.loadIdentity(localId,{allowOsConsent}),envelope=await seal({identity,recipient:pin.remote,binding:header,content,now}),digest=await envelopeDigest(envelope);
86
+ return this.state.locked(async()=>{
87
+ this.assertPin(pin,attestation);checkTask();
88
+ if(this.state.db.prepare('SELECT message_id FROM outbound WHERE local_identity=? AND peer_identity=? AND message_id=?').get(pin.localIdentity,pin.peerIdentity,messageId))fail('message_id_used');
89
+ this.state.db.prepare('INSERT INTO outbound VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)').run(pin.localIdentity,pin.peerIdentity,messageId,peer,contextId,taskId,digest,canonicalJSON(envelope),pin.digest,localId,null,'prepared',null,protocol,now);
90
+ return {ok:true,messageId,contextId,taskId,protocol,deliveryStatus:'not-sent',state:'prepared',next:'Ciphertext is durably prepared and self-opened. Begin exactly this ID once; no transport request was made.'};
91
+ });
92
+ }
93
+ async prepareResponse({localId,peer,requestMessageId,content,messageId=randomUUID(),protocol,remoteExtension,pairingIdentity,pairingVerified=false,allowOsConsent=false,now=Date.now()}={}){
94
+ content=parseJSON(canonicalJSON(content));
95
+ if(remoteExtension!==EXTENSION||!['0.3','1.0'].includes(protocol))fail('extension_not_activated');
96
+ const attestation={pairingIdentity,pairingVerified},pin=this.resolve(localId,peer,attestation);
97
+ const request=this.state.db.prepare('SELECT r.*,d.envelope,p.trust_digest FROM replay r JOIN deliveries d ON d.local_id=r.local_id AND d.sender=r.sender AND d.message_id=r.message_id JOIN delivery_provenance p ON p.id=d.id WHERE r.local_id=? AND r.sender=? AND r.message_id=?').get(pin.localIdentity,pin.peerIdentity,requestMessageId);
98
+ if(!request||request.trust_digest!==pin.digest)fail('request_binding_mismatch');
99
+ const incoming=validateEnvelopeStructure(request.envelope);
100
+ const assignment=this.state.db.prepare("SELECT * FROM assignments WHERE local_identity=? AND peer_identity=? AND task_id=? AND direction='inbound'").get(pin.localIdentity,pin.peerIdentity,request.task_id);
101
+ if(!assignment||assignment.context!==request.context||assignment.trust_digest!==pin.digest||assignment.protocol!==protocol||incoming.header.role!=='user'||(incoming.header.taskId===null?assignment.request_message_id!==requestMessageId:incoming.header.taskId!==assignment.task_id))fail('assignment_required');
102
+ if(await envelopeDigest(incoming)!==request.digest)fail('ledger_corrupt');
103
+ const headQuery=this.state.db.prepare('SELECT * FROM chains WHERE local_identity=? AND peer_identity=? AND context=? AND task_id=? AND author_identity=?');
104
+ const headArgs=[pin.localIdentity,pin.peerIdentity,request.context,request.task_id,pin.localIdentity],head=headQuery.get(...headArgs);
105
+ if(head?.terminal)fail('task_terminal');
106
+ if(content.requestDigest!==undefined&&content.requestDigest!==request.digest)fail('request_binding_mismatch');content.requestDigest=request.digest;
107
+ if(content.kind==='snapshot'){
108
+ const revision=head?head.revision+1:0,previousDigest=head?head.digest:null;
109
+ if((content.revision!==undefined&&content.revision!==revision)||(content.previousDigest!==undefined&&content.previousDigest!==previousDigest))fail('chain_conflict');
110
+ content.revision=revision;content.previousDigest=previousDigest;
111
+ }
112
+ const header={contextId:request.context,messageId,taskId:request.task_id,role:'agent',state:content.kind==='snapshot'?content.state:null,kind:content.kind==='snapshot'?'status':'message'};content=validateContent(content,header);
113
+ const identity=await this.state.loadIdentity(localId,{allowOsConsent}),envelope=await seal({identity,recipient:pin.remote,binding:header,content,now}),digest=await envelopeDigest(envelope);
114
+ return this.state.locked(async()=>{
115
+ this.assertPin(pin,attestation);const current=headQuery.get(...headArgs);if((current?.digest??null)!==(head?.digest??null))fail('chain_conflict');
116
+ if(this.state.db.prepare('SELECT message_id FROM outbound WHERE local_identity=? AND peer_identity=? AND message_id=?').get(pin.localIdentity,pin.peerIdentity,messageId))fail('message_id_used');
117
+ this.state.db.prepare('INSERT INTO outbound VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)').run(pin.localIdentity,pin.peerIdentity,messageId,peer,request.context,request.task_id,digest,canonicalJSON(envelope),pin.digest,localId,request.digest,'prepared',null,protocol,now);
118
+ if(content.kind==='snapshot')this.state.db.prepare('INSERT INTO chains VALUES(?,?,?,?,?,?,?,?,?) ON CONFLICT(local_identity,peer_identity,context,task_id,author_identity) DO UPDATE SET revision=excluded.revision,digest=excluded.digest,state=excluded.state,terminal=excluded.terminal').run(...headArgs,content.revision,digest,content.state,content.terminal?1:0);
119
+ return {ok:true,messageId,contextId:request.context,taskId:request.task_id,deliveryStatus:'not-sent',state:'prepared',...(content.kind==='snapshot'?{revision:content.revision}:{}),next:'Correlated response ciphertext and any snapshot head are committed together. Begin this exact ID once; no transport was performed.'};
120
+ });
121
+ }
122
+ outboundRow(pin,messageId){const row=this.state.db.prepare('SELECT * FROM outbound WHERE local_identity=? AND peer_identity=? AND message_id=?').get(pin.localIdentity,pin.peerIdentity,messageId);if(!row)fail('outbound_unknown');if(row.trust_digest!==pin.digest||row.local_key_id!==pin.localId)fail('delivery_trust_changed');return row;}
123
+ async beginTransmission({localId,peer,messageId,pairingIdentity,pairingVerified=false,now=Date.now()}={}){
124
+ const attestation={pairingIdentity,pairingVerified},pin=this.resolve(localId,peer,attestation),row=this.outboundRow(pin,messageId);
125
+ if(row.state!=='prepared')fail('outbound_already_attempted','Delivery may already have occurred. Inspect this exact outbound ID; never automatically retry with another ID or plaintext.');
126
+ const envelope=validateEnvelopeStructure(row.envelope),checkTerminal=()=>{if(envelope.header.role==='user'&&row.task_id!==null&&this.state.db.prepare('SELECT terminal FROM chains WHERE local_identity=? AND peer_identity=? AND context=? AND task_id=? AND author_identity=?').get(pin.localIdentity,pin.peerIdentity,row.context,row.task_id,pin.peerIdentity)?.terminal)fail('task_terminal');};checkTerminal();
127
+ await verifyEnvelopeSignature({envelope,sender:pin.bundle,recipient:pin.remote,now});if(await envelopeDigest(envelope)!==row.digest)fail('ledger_corrupt');
128
+ return this.state.locked(async()=>{
129
+ this.assertPin(pin,attestation);checkTerminal();const current=this.outboundRow(pin,messageId);if(current.state!=='prepared')fail('outbound_already_attempted');if(current.envelope!==row.envelope||current.digest!==row.digest)fail('ledger_corrupt');
130
+ this.state.db.prepare("UPDATE outbound SET state='transmitting' WHERE local_identity=? AND peer_identity=? AND message_id=? AND state='prepared'").run(pin.localIdentity,pin.peerIdentity,messageId);
131
+ return {ok:true,messageId,envelope,protocol:row.protocol,deliveryStatus:'unknown',retryable:false,next:'One transmission is authorized for this cached ciphertext. Persist the observed result; a crash/timeout remains unknown, not permission to re-send.'};
132
+ });
133
+ }
134
+ async recordTransport({localId,peer,messageId,pairingIdentity,pairingVerified=false,outcome,provisionalTaskId=null,extensionEcho}={}){
135
+ if(!['unknown','rejected','transport-accepted'].includes(outcome))fail('transport_result_invalid');
136
+ const attestation={pairingIdentity,pairingVerified},pin=this.resolve(localId,peer,attestation);
137
+ return this.state.locked(async()=>{
138
+ this.assertPin(pin,attestation);const row=this.outboundRow(pin,messageId);if(row.state==='prepared')fail('outbound_not_started');
139
+ let observed=outcome;if(observed==='transport-accepted'&&(extensionEcho!==EXTENSION||!id(provisionalTaskId)||(row.task_id!==null&&row.task_id!==provisionalTaskId)))observed='unknown';
140
+ const rank={'prepared':0,'transmitting':1,'unknown':2,'rejected':2,'transport-accepted':3,'peer-assigned':4};
141
+ if(rank[row.state]===undefined)fail('ledger_corrupt');
142
+ if(rank[observed]>=rank[row.state])this.state.db.prepare('UPDATE outbound SET state=?,transport_task_id=? WHERE local_identity=? AND peer_identity=? AND message_id=?').run(observed,id(provisionalTaskId)?provisionalTaskId:row.transport_task_id,pin.localIdentity,pin.peerIdentity,messageId);
143
+ const current=this.outboundRow(pin,messageId);return {ok:true,messageId,state:current.state,deliveryStatus:current.state==='peer-assigned'?'locally-opened':current.state==='transport-accepted'?'transport-accepted':current.state==='rejected'?'rejected':'unknown',retryable:false,provisionalTaskId:current.transport_task_id,next:'A Worker ID/extension echo is routing metadata only. Wait for the exact peer-signed assignment; never infer peer/model consumption or use plaintext fallback.'};
144
+ });
145
+ }
146
+ listOutbound({limit=20,cursor=0}={}){
147
+ page(limit,cursor);this.state.ready();const rows=this.state.db.prepare('SELECT rowid AS cursor,message_id,peer,context,task_id,state,transport_task_id,protocol FROM outbound WHERE rowid>? ORDER BY rowid LIMIT ?').all(cursor,limit+1);
148
+ return {ok:true,configIdentity:this.state.configId(),outbound:rows.slice(0,limit).map(row=>({messageId:row.message_id,peer:row.peer,contextId:row.context,taskId:row.task_id,state:row.state,provisionalTaskId:row.transport_task_id,protocol:row.protocol,retryable:false})),nextCursor:rows.length>limit?rows[limit-1].cursor:null,next:'Inspect pending IDs before another action; unknown/transmitting entries never authorize an automatic fresh-message retry.'};
149
+ }
150
+ #answeredUserRequest(pin,digest,contextId,taskId){
151
+ const request=this.state.db.prepare('SELECT * FROM outbound WHERE local_identity=? AND peer_identity=? AND digest=?').get(pin.localIdentity,pin.peerIdentity,digest);
152
+ // Only prepareRequest writes a null reference_digest. Responses always name the inbound request they answer.
153
+ if(!request||request.reference_digest!==null||request.context!==contextId||request.trust_digest!==pin.digest||request.local_key_id!==pin.localId||!['transmitting','unknown','rejected','transport-accepted','peer-assigned'].includes(request.state))fail('request_binding_mismatch');
154
+ const envelope=validateEnvelopeStructure(request.envelope),h=envelope.header,identity=bundle=>({cardUrl:bundle.cardUrl,identityKey:bundle.identityKey,keyId:bundle.keyId});
155
+ if(h.role!=='user'||h.kind!=='message'||h.state!==null||h.contextId!==contextId||h.messageId!==request.message_id||h.taskId!==request.task_id||createHash('sha256').update(canonicalJSON(envelope)).digest('base64url')!==digest||canonicalJSON(h.from)!==canonicalJSON(identity(pin.bundle))||canonicalJSON(h.to)!==canonicalJSON(identity(pin.remote)))fail('request_binding_mismatch');
156
+ const assignment=this.state.db.prepare("SELECT * FROM assignments WHERE local_identity=? AND peer_identity=? AND context=? AND task_id=? AND direction='outbound'").get(pin.localIdentity,pin.peerIdentity,contextId,taskId);
157
+ if(!assignment||assignment.trust_digest!==pin.digest||(request.task_id===null?assignment.request_message_id!==request.message_id:request.task_id!==taskId))fail('assignment_required');
158
+ return {request,assignment};
159
+ }
160
+ #queueVerifiedResponse(pin,envelope,content,digest,now){
161
+ const h=envelope.header;this.#answeredUserRequest(pin,content.requestDigest,h.contextId,h.taskId);
162
+ const prior=this.state.db.prepare('SELECT * FROM replay WHERE local_id=? AND sender=? AND message_id=?').get(pin.localIdentity,pin.peerIdentity,h.messageId);
163
+ if(prior){if(prior.digest!==digest||prior.context!==h.contextId||prior.task_id!==h.taskId)fail('replay_conflict');const delivery=this.state.db.prepare('SELECT id FROM deliveries WHERE local_id=? AND sender=? AND message_id=?').get(pin.localIdentity,pin.peerIdentity,h.messageId);if(!delivery)fail('ledger_corrupt');return {deliveryId:delivery.id,duplicate:true};}
164
+ if(content.kind!=='snapshot'&&this.state.db.prepare('SELECT terminal FROM chains WHERE local_identity=? AND peer_identity=? AND context=? AND task_id=? AND author_identity=?').get(pin.localIdentity,pin.peerIdentity,h.contextId,h.taskId,pin.peerIdentity)?.terminal)fail('task_terminal');
165
+ if(content.kind==='snapshot'){
166
+ const head=this.state.db.prepare('SELECT * FROM chains WHERE local_identity=? AND peer_identity=? AND context=? AND task_id=? AND author_identity=?').get(pin.localIdentity,pin.peerIdentity,h.contextId,h.taskId,pin.peerIdentity);
167
+ if(!head){if(content.revision!==0||content.previousDigest!==null)fail('provenance_incomplete');}
168
+ else if(head.terminal||content.revision!==head.revision+1||content.previousDigest!==head.digest)fail(content.revision>head.revision+1?'provenance_incomplete':'chain_conflict');
169
+ this.state.db.prepare('INSERT INTO chains VALUES(?,?,?,?,?,?,?,?,?) ON CONFLICT(local_identity,peer_identity,context,task_id,author_identity) DO UPDATE SET revision=excluded.revision,digest=excluded.digest,state=excluded.state,terminal=excluded.terminal').run(pin.localIdentity,pin.peerIdentity,h.contextId,h.taskId,pin.peerIdentity,content.revision,digest,content.state,content.terminal?1:0);
170
+ }
171
+ const deliveryId=randomUUID();this.state.db.prepare('INSERT INTO replay VALUES(?,?,?,?,?,?,?)').run(pin.localIdentity,pin.peerIdentity,h.messageId,digest,h.contextId,h.taskId,now);
172
+ this.state.db.prepare('INSERT INTO deliveries VALUES(?,?,?,?,?,?,?)').run(deliveryId,pin.localIdentity,pin.peerIdentity,h.messageId,canonicalJSON(envelope),'pending',now);
173
+ this.state.db.prepare('INSERT INTO delivery_provenance VALUES(?,?,?,?,?,?)').run(deliveryId,pin.peer,pin.digest,pin.record.version,pin.localId,digest);
174
+ return {deliveryId,duplicate:false};
175
+ }
176
+ #recordAssignment(pin,requestMessageId,verified,protocol){
177
+ const prior=this.state.db.prepare("SELECT receipt FROM assignments WHERE local_identity=? AND peer_identity=? AND request_message_id=? AND direction='outbound'").get(pin.localIdentity,pin.peerIdentity,requestMessageId);
178
+ if(prior){if(prior.receipt!==canonicalJSON(verified))fail('assignment_conflict');}
179
+ else{
180
+ if(this.state.db.prepare("SELECT request_message_id FROM assignments WHERE local_identity=? AND peer_identity=? AND task_id=? AND direction='outbound'").get(pin.localIdentity,pin.peerIdentity,verified.taskId))fail('assignment_conflict');
181
+ this.state.db.prepare('INSERT INTO assignments VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)').run(pin.localIdentity,pin.peerIdentity,requestMessageId,'outbound',verified.ackId,pin.peer,verified.contextId,verified.requestDigest,verified.taskId,canonicalJSON(verified),protocol,pin.digest,pin.localId,'received');
182
+ }
183
+ this.state.db.prepare("UPDATE outbound SET state='peer-assigned' WHERE local_identity=? AND peer_identity=? AND message_id=?").run(pin.localIdentity,pin.peerIdentity,requestMessageId);return prior;
184
+ }
185
+ async acceptAssignment({localId,peer,requestMessageId,receipt,responseEnvelope=null,enqueueResponse=true,observedTaskId,observedContextId,responseKind,protocol,activatedExtension,pairingIdentity,pairingVerified=false,allowOsConsent=false,now=Date.now()}={}){
186
+ if(typeof enqueueResponse!=='boolean')fail('response_mode_invalid');
187
+ receipt=validateAssignmentStructure(receipt);responseEnvelope=responseEnvelope===null?null:validateEnvelopeStructure(responseEnvelope);
188
+ if(responseKind!=='task'||activatedExtension!==EXTENSION||!id(observedTaskId)||receipt.contextId!==observedContextId)fail('task_response_required');
189
+ const attestation={pairingIdentity,pairingVerified},pin=this.resolve(localId,peer,attestation),outbound=this.outboundRow(pin,requestMessageId);
190
+ if(outbound.task_id!==null||outbound.state==='prepared'||protocol!==outbound.protocol)fail('initial_assignment_inputs');
191
+ const request=validateEnvelopeStructure(outbound.envelope);if(await envelopeDigest(request)!==outbound.digest)fail('ledger_corrupt');
192
+ const verified=await verifyAssignment({receipt,requestEnvelope:request,senderBundle:pin.bundle,recipientBundle:pin.remote,responseEnvelope,expectedTaskId:observedTaskId,now});
193
+ let responseContent,digest;
194
+ if(responseEnvelope!==null){await verifyEnvelopeSignature({envelope:responseEnvelope,sender:pin.remote,recipient:pin.bundle,now});if(enqueueResponse){const identity=await this.state.loadIdentity(localId,{allowOsConsent});responseContent=(await openAssignedResponse({receipt:verified,requestEnvelope:request,responseEnvelope,identity,recipientBundle:pin.remote,now})).content;digest=await envelopeDigest(responseEnvelope);}}
195
+ return this.state.locked(async()=>{
196
+ this.assertPin(pin,attestation);const current=this.outboundRow(pin,requestMessageId);if(current.state==='prepared'||current.digest!==outbound.digest||current.envelope!==outbound.envelope)fail('ledger_corrupt');
197
+ const prior=this.#recordAssignment(pin,requestMessageId,verified,protocol);
198
+ const delivery=responseContent?this.#queueVerifiedResponse(pin,responseEnvelope,responseContent,digest,now):null;
199
+ return {ok:true,taskId:verified.taskId,contextId:verified.contextId,assignmentAuthenticated:true,responseHeld:responseEnvelope!==null&&!enqueueResponse,duplicate:!!prior,...(delivery||{}),next:'Task assignment is peer-signed and locally pinned. Any response is durably queued, not yet consumed by the local model.'};
200
+ });
201
+ }
202
+ async acceptResponse({localId,peer,envelope,observedTaskId,observedContextId,responseKind,activatedExtension,pairingIdentity,pairingVerified=false,allowOsConsent=false,now=Date.now()}={}){
203
+ envelope=validateEnvelopeStructure(envelope);const h=envelope.header;
204
+ if(responseKind!=='task'||activatedExtension!==EXTENSION||h.taskId!==observedTaskId||h.contextId!==observedContextId||h.role!=='agent')fail('task_response_required');
205
+ const attestation={pairingIdentity,pairingVerified},pin=this.resolve(localId,peer,attestation);
206
+ const assignment=this.state.db.prepare("SELECT trust_digest FROM assignments WHERE local_identity=? AND peer_identity=? AND context=? AND task_id=? AND direction='outbound'").get(pin.localIdentity,pin.peerIdentity,h.contextId,h.taskId);if(!assignment||assignment.trust_digest!==pin.digest)fail('assignment_required');
207
+ await verifyEnvelopeSignature({envelope,sender:pin.remote,recipient:pin.bundle,now});const identity=await this.state.loadIdentity(localId,{allowOsConsent});
208
+ const content=validateContent(await open({envelope,identity,sender:pin.remote,recipient:pin.bundle,expected:binding(h),now}),h),digest=await envelopeDigest(envelope);
209
+ return this.state.locked(async()=>{this.assertPin(pin,attestation);return {ok:true,...this.#queueVerifiedResponse(pin,envelope,content,digest,now),next:'Authenticated response queued under its existing assignment; local model consumption remains separate.'};});
210
+ }
211
+
212
+ async #verifyRetainedTaskAssignment(pin,outbound,existing,request,task,protocol,now){
213
+ // Closed local-cache path only. Never use this to admit a new request, assignment or incoming envelope.
214
+ const {assignment}=this.#answeredUserRequest(pin,outbound.digest,task.contextId,task.taskId);
215
+ if(existing.receipt!==assignment.receipt||existing.protocol!==protocol||existing.trust_digest!==pin.digest||existing.request_digest!==outbound.digest||existing.local_key_id!==pin.localId)fail('assignment_conflict');
216
+ const receipt=validateAssignmentStructure(existing.receipt),identity=b=>({cardUrl:b.cardUrl,identityKey:b.identityKey,keyId:b.keyId});
217
+ if(receipt.requestDigest!==outbound.digest||receipt.requestMessageId!==outbound.message_id||receipt.contextId!==outbound.context||receipt.taskId!==existing.task_id||receipt.ackId!==existing.ack_id||canonicalJSON(receipt.from)!==canonicalJSON(identity(pin.remote))||canonicalJSON(receipt.to)!==canonicalJSON(identity(pin.bundle)))fail('assignment_conflict');
218
+ await verifyBundle(pin.bundle,{cardUrl:pin.bundle.cardUrl,identityKey:pin.bundle.identityKey,now});await verifyBundle(pin.remote,{cardUrl:pin.remote.cardUrl,identityKey:pin.remote.identityKey,now});
219
+ for(const [value,key,domain] of [[request,pin.bundle.identityKey,'a2a-exposed sealed envelope v1\n'],[receipt,pin.remote.identityKey,'a2a-exposed sealed assignment v1\n']]){
220
+ const {signature,...body}=value,publicKey=await crypto.subtle.importKey('raw',Buffer.from(key,'base64url'),'Ed25519',false,['verify']);
221
+ if(!await crypto.subtle.verify('Ed25519',publicKey,Buffer.from(signature,'base64url'),new TextEncoder().encode(domain+canonicalJSON(body))))fail('verification_failed');
222
+ }
223
+ return receipt;
224
+ }
225
+ async acceptTask({localId,peer,requestMessageId,rawTask,protocol,activatedExtension,pairingIdentity,pairingVerified=false,allowOsConsent=false,authorizationGuard,withAuthorizationLease,now=Date.now()}={}){
226
+ if((authorizationGuard!==undefined||withAuthorizationLease!==undefined)&&(typeof authorizationGuard!=='function'||typeof withAuthorizationLease!=='function'))fail('authorization_guard_invalid');
227
+ const task=readTaskCarrier(rawTask,protocol);if(activatedExtension!==EXTENSION)fail('extension_not_activated');
228
+ const attestation={pairingIdentity,pairingVerified},pin=this.resolve(localId,peer,attestation),outbound=this.outboundRow(pin,requestMessageId),request=validateEnvelopeStructure(outbound.envelope);
229
+ if(outbound.reference_digest!==null||outbound.task_id!==null||outbound.context!==task.contextId||outbound.protocol!==protocol||!['transmitting','unknown','rejected','transport-accepted','peer-assigned'].includes(outbound.state)||request.header.role!=='user'||request.header.kind!=='message'||request.header.taskId!==null||request.header.messageId!==requestMessageId||request.header.contextId!==task.contextId||await envelopeDigest(request)!==outbound.digest)fail('request_binding_mismatch');
230
+ const events=[...task.history,...(task.status?[task.status]:[])],control=events.find(item=>item.receipt),existing=this.state.db.prepare("SELECT * FROM assignments WHERE local_identity=? AND peer_identity=? AND request_message_id=? AND direction='outbound'").get(pin.localIdentity,pin.peerIdentity,requestMessageId);
231
+ let receipt;
232
+ if(existing){
233
+ if(control&&canonicalJSON(control.receipt)!==existing.receipt)fail('assignment_conflict');
234
+ receipt=await this.#verifyRetainedTaskAssignment(pin,outbound,existing,request,task,protocol,now);
235
+ }else{
236
+ await verifyEnvelopeSignature({envelope:request,sender:pin.bundle,recipient:pin.remote,now});
237
+ if(!control)fail('assignment_required');receipt=await verifyAssignment({receipt:control.receipt,requestEnvelope:request,senderBundle:pin.bundle,recipientBundle:pin.remote,responseEnvelope:control.envelope,expectedTaskId:task.taskId,now});
238
+ }
239
+ if(receipt.requestDigest!==outbound.digest||receipt.requestMessageId!==requestMessageId||receipt.contextId!==task.contextId||receipt.taskId!==task.taskId)fail('assignment_conflict');
240
+ if(existing&&(existing.trust_digest!==pin.digest||existing.protocol!==protocol||existing.receipt!==canonicalJSON(receipt)))fail('assignment_conflict');
241
+ const candidates=new Map();
242
+ for(const item of events){if(!item.envelope)continue;const envelope=item.envelope,h=envelope.header,digest=await envelopeDigest(envelope);
243
+ if(h.role==='user'){
244
+ const cached=this.outboundRow(pin,h.messageId);
245
+ if(cached.reference_digest!==null||cached.context!==task.contextId||cached.protocol!==protocol||!['transmitting','unknown','rejected','transport-accepted','peer-assigned'].includes(cached.state)||cached.digest!==digest||cached.envelope!==canonicalJSON(envelope)||(h.taskId===null?h.messageId!==requestMessageId:h.taskId!==task.taskId))fail('request_binding_mismatch');
246
+ }else{await verifyEnvelopeSignature({envelope,sender:pin.remote,recipient:pin.bundle,now});candidates.set(digest,{envelope,digest});}
247
+ }
248
+ // Authenticate all public signatures/receipt before any credential read or ledger mutation.
249
+ const verified=[];
250
+ if(candidates.size){const identity=await this.state.loadIdentity(localId,{allowOsConsent});for(const candidate of candidates.values()){
251
+ const content=validateContent(await open({envelope:candidate.envelope,identity,sender:pin.remote,recipient:pin.bundle,expected:binding(candidate.envelope.header),now}),candidate.envelope.header,{expectedRequestDigest:receipt.responseDigest===candidate.digest?receipt.requestDigest:undefined});verified.push({...candidate,content});
252
+ }}
253
+ // Only decrypted, commitment-checked revision numbers order snapshots. Server array order/timestamps are ignored.
254
+ verified.sort((a,b)=>(a.content.kind==='snapshot'?1:0)-(b.content.kind==='snapshot'?1:0)||(a.content.kind==='snapshot'?a.content.revision-b.content.revision:0));
255
+ return this.state.locked(async()=>{
256
+ this.assertPin(pin,attestation);const current=this.outboundRow(pin,requestMessageId);if(!['transmitting','unknown','rejected','transport-accepted','peer-assigned'].includes(current.state)||current.reference_digest!==null||current.task_id!==null||current.protocol!==protocol||current.digest!==outbound.digest||current.envelope!==outbound.envelope)fail('request_binding_mismatch');
257
+ this.#recordAssignment(pin,requestMessageId,receipt,protocol);
258
+ const knownHead=this.state.db.prepare('SELECT terminal FROM chains WHERE local_identity=? AND peer_identity=? AND context=? AND task_id=? AND author_identity=?').get(pin.localIdentity,pin.peerIdentity,task.contextId,task.taskId,pin.peerIdentity);
259
+ if(knownHead?.terminal||verified.some(item=>item.content.kind==='snapshot'&&item.content.terminal)){
260
+ for(const item of verified)if(item.content.kind==='message'&&!this.state.db.prepare('SELECT digest FROM replay WHERE local_id=? AND sender=? AND message_id=?').get(pin.localIdentity,pin.peerIdentity,item.envelope.header.messageId))fail('task_terminal','Unsequenced agent messages cannot be placed before or after the known/incoming terminal snapshot. Use signed snapshot content; archive opening is not enabled. No new delivery was committed.');
261
+ }
262
+ const deliveries=verified.map(item=>this.#queueVerifiedResponse(pin,item.envelope,item.content,item.digest,now));
263
+ const head=this.state.db.prepare('SELECT revision,digest,state,terminal FROM chains WHERE local_identity=? AND peer_identity=? AND context=? AND task_id=? AND author_identity=?').get(pin.localIdentity,pin.peerIdentity,task.contextId,task.taskId,pin.peerIdentity);
264
+ return {ok:true,taskId:task.taskId,contextId:task.contextId,assignmentAuthenticated:true,deliveries,currentPeerState:head?{revision:head.revision,digest:head.digest,state:head.state,terminal:head.terminal===1}:null,transportRoutingState:task.routingState,transportStateAuthenticated:false,next:'Only signed contiguous snapshots establish peer state. Content remains durably queued and untrusted until explicit local read/consumption; no network request or model callback was made.'};
265
+ },{authorizationGuard,withAuthorizationLease});
266
+ }
267
+
268
+ listDeliveries({limit=20,cursor=0}={}){
269
+ page(limit,cursor);this.state.ready();if(this.state.db.prepare('SELECT d.id FROM deliveries d LEFT JOIN delivery_provenance p ON p.id=d.id WHERE p.id IS NULL LIMIT 1').get())fail('ledger_corrupt');const rows=this.state.db.prepare('SELECT d.rowid AS cursor,d.id,d.message_id,d.state,d.created_ms,p.peer FROM deliveries d JOIN delivery_provenance p ON p.id=d.id WHERE d.rowid>? ORDER BY d.rowid LIMIT ?').all(cursor,limit+1);
270
+ return {ok:true,configIdentity:this.state.configId(),deliveries:rows.slice(0,limit).map(row=>({deliveryId:row.id,messageId:row.message_id,state:row.state,createdMs:row.created_ms,peer:row.peer})),nextCursor:rows.length>limit?rows[limit-1].cursor:null,next:'Use the same stable delivery ID for read/ack; repeated reads are at-least-once until explicit acknowledgment.'};
271
+ }
272
+ async readDelivery({deliveryId,pairingIdentity,pairingVerified=false,allowOsConsent=false,now=Date.now()}={}){
273
+ this.state.ready();const row=this.state.db.prepare('SELECT d.*,p.peer,p.trust_digest,p.local_key_id,p.envelope_digest FROM deliveries d JOIN delivery_provenance p ON p.id=d.id WHERE d.id=?').get(deliveryId);
274
+ if(!row)fail('delivery_unknown');if(row.state==='acknowledged')return {ok:true,deliveryId,alreadyAcknowledged:true,content:null};
275
+ const attestation={pairingIdentity,pairingVerified},pin=this.resolve(row.local_key_id,row.peer,attestation);
276
+ if(pin.digest!==row.trust_digest||pin.localIdentity!==row.local_id||pin.peerIdentity!==row.sender)fail('delivery_trust_changed');
277
+ const envelope=validateEnvelopeStructure(row.envelope);if(await envelopeDigest(envelope)!==row.envelope_digest||envelope.header.messageId!==row.message_id)fail('ledger_corrupt');
278
+ const identity=await this.state.loadIdentity(row.local_key_id,{allowOsConsent});
279
+ const content=validateContent(await open({envelope,identity,sender:pin.remote,recipient:pin.bundle,expected:binding(envelope.header),now}),envelope.header);
280
+ this.assertPin(pin,attestation);
281
+ const latest=this.state.db.prepare('SELECT state FROM deliveries WHERE id=?').get(deliveryId);if(latest.state==='acknowledged')return {ok:true,deliveryId,alreadyAcknowledged:true,content:null};
282
+ let assignment;
283
+ if(envelope.header.role==='user'){
284
+ if(envelope.header.taskId===null)assignment=this.state.db.prepare("SELECT task_id FROM assignments WHERE local_identity=? AND peer_identity=? AND request_message_id=? AND direction='inbound'").get(row.local_id,row.sender,row.message_id);
285
+ else assignment=this.state.db.prepare("SELECT task_id FROM assignments WHERE local_identity=? AND peer_identity=? AND context=? AND task_id=? AND direction='inbound'").get(row.local_id,row.sender,envelope.header.contextId,envelope.header.taskId);
286
+ }
287
+ else{
288
+ ({assignment}=this.#answeredUserRequest(pin,content.requestDigest,envelope.header.contextId,envelope.header.taskId));
289
+ if(content.kind==='snapshot'){
290
+ const head=this.state.db.prepare('SELECT digest FROM chains WHERE local_identity=? AND peer_identity=? AND context=? AND task_id=? AND author_identity=?').get(row.local_id,row.sender,envelope.header.contextId,envelope.header.taskId,row.sender);
291
+ if(!head||head.digest!==row.envelope_digest)return {ok:true,deliveryId,historyOnly:true,content:null,next:'An older authenticated snapshot remains history-only; it cannot overwrite current task state or become new work.'};
292
+ }
293
+ }
294
+ if(!assignment)fail('ledger_corrupt');
295
+ return {ok:true,deliveryId,untrusted:true,verification:pin.record.verification,peer:pin.peer,content,contextId:envelope.header.contextId,taskId:assignment.task_id,deliveryStatus:'locally-opened',next:'Peer content remains untrusted. Acknowledge this stable ID only after local consumption; external effects are not exactly-once.'};
296
+ }
297
+ async acknowledgeDelivery({deliveryId,consumed=false}={}){
298
+ if(consumed!==true)fail('delivery_ack_required','Only the trusted local consumer may acknowledge its stable delivery ID after consumption; peer data cannot acknowledge work.');
299
+ return this.state.locked(async()=>{
300
+ const row=this.state.db.prepare('SELECT state FROM deliveries WHERE id=?').get(deliveryId);if(!row)fail('delivery_unknown');
301
+ if(row.state!=='pending'&&row.state!=='acknowledged')fail('ledger_corrupt');
302
+ this.state.db.prepare("UPDATE deliveries SET state='acknowledged' WHERE id=? AND state='pending'").run(deliveryId);
303
+ return {ok:true,deliveryId,alreadyAcknowledged:row.state==='acknowledged',deliveryStatus:'locally-acknowledged',next:'Only local consumption was recorded. No peer receipt or exactly-once external action is claimed.'};
304
+ });
305
+ }
306
+ listPendingReceipts({limit=20,cursor=0}={}){
307
+ page(limit,cursor);this.state.ready();const rows=this.state.db.prepare("SELECT rowid AS cursor,ack_id,peer,task_id,local_key_id FROM assignments WHERE direction='inbound' AND ack_state='pending' AND rowid>? ORDER BY rowid LIMIT ?").all(cursor,limit+1);
308
+ return {ok:true,configIdentity:this.state.configId(),receipts:rows.slice(0,limit).map(row=>({ackId:row.ack_id,peer:row.peer,taskId:row.task_id,localId:row.local_key_id})),nextCursor:rows.length>limit?rows[limit-1].cursor:null,next:'Prepare the exact cached receipt under current pairing/trust before publishing; never re-sign another assignment.'};
309
+ }
310
+ prepareReceipt({localId,peer,ackId,pairingIdentity,pairingVerified=false}={}){
311
+ const pin=this.resolve(localId,peer,{pairingIdentity,pairingVerified});
312
+ const row=this.state.db.prepare("SELECT * FROM assignments WHERE local_identity=? AND peer_identity=? AND ack_id=? AND direction='inbound'").get(pin.localIdentity,pin.peerIdentity,ackId);
313
+ if(!row)fail('assignment_unknown');if(row.trust_digest!==pin.digest||row.local_key_id!==localId)fail('delivery_trust_changed');
314
+ const receipt=parseJSON(row.receipt);return {ok:true,ackId,taskId:row.task_id,protocol:row.protocol,carrier:makeReceiptCarrier(receipt,row.protocol),transportStatus:row.ack_state,next:'Publish this exact cached control receipt only. This API did not send it and does not claim peer receipt.'};
315
+ }
316
+ async markReceiptTransport({localId,peer,ackId,pairingIdentity,pairingVerified=false,transportAccepted=false}={}){
317
+ if(transportAccepted!==true)fail('receipt_transport_unknown','Keep the cached receipt pending; an unknown transport result is not proof of publication.');
318
+ const attestation={pairingIdentity,pairingVerified},pin=this.resolve(localId,peer,attestation);
319
+ return this.state.locked(async()=>{
320
+ this.assertPin(pin,attestation);const row=this.state.db.prepare("SELECT trust_digest FROM assignments WHERE local_identity=? AND peer_identity=? AND ack_id=? AND direction='inbound'").get(pin.localIdentity,pin.peerIdentity,ackId);
321
+ if(!row||row.trust_digest!==pin.digest)fail('assignment_unknown');
322
+ this.state.db.prepare("UPDATE assignments SET ack_state='transport-accepted' WHERE local_identity=? AND peer_identity=? AND ack_id=? AND direction='inbound'").run(pin.localIdentity,pin.peerIdentity,ackId);
323
+ return {ok:true,ackId,deliveryStatus:'transport-accepted',next:'Transport acceptance is recorded; peer verification/model consumption is not attested.'};
324
+ });
325
+ }
326
+ }
327
+ for(const name of ['receiveInitial','receiveFollowup','prepareRequest','prepareResponse','beginTransmission','recordTransport','listOutbound','acceptAssignment','acceptResponse','acceptTask','listDeliveries','readDelivery','acknowledgeDelivery','listPendingReceipts','prepareReceipt','markReceiptTransport']){
328
+ const action=DeliveryLedger.prototype[name],map=error=>{if(error instanceof LedgerError||error instanceof LocalStateError||error instanceof EncryptionError)return error;return new LedgerError('ledger_unavailable');};
329
+ DeliveryLedger.prototype[name]=action.constructor.name==='AsyncFunction'?async function(...args){try{return await action.apply(this,args);}catch(error){throw map(error);}}:function(...args){try{return action.apply(this,args);}catch(error){throw map(error);}};
330
+ }