@allorc/a423-sdk 0.4.0 → 0.5.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 +9 -0
- package/ESCROW.md +4 -2
- package/INTEGRATION.md +5 -1
- package/OPERATIONS.md +3 -1
- package/README.md +4 -2
- package/SECURITY.md +2 -0
- package/SETTLEMENT.md +140 -0
- package/contracts/A423Escrow.sol +68 -6
- package/contracts/standard-input.json +1 -1
- package/examples/operations-dashboard.html +3 -3
- package/package.json +4 -2
- package/src/closing.cjs +17 -0
- package/src/escrow/artifact.json +292 -1
- package/src/escrow/index.cjs +38 -5
- package/src/escrow/index.d.mts +4 -1
- package/src/escrow/index.d.ts +4 -1
- package/src/escrow/index.mjs +1 -1
- package/src/escrow/negotiation.cjs +13 -0
- package/src/escrow/services.cjs +4 -1
- package/src/operations/application.cjs +4 -2
- package/src/operations/factory.cjs +2 -2
- package/src/operations/index.cjs +8 -7
- package/src/operations/index.d.ts +3 -3
- package/src/workflow.cjs +28 -4
- package/src/workflow.d.ts +6 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const {createWorkflow,hashAgreement}=require('../workflow.cjs');
|
|
3
3
|
const {createCredentialAuthenticator,createAuthorityHandler}=require('../authority/index.cjs');
|
|
4
4
|
function createEscrowApplication({storage,authority,escrows,signerFor,addressFor,limitsFor,approveSigning,validateArtifact,execute,keeperEnabled=false,allowAuthorityRequest=async()=>false,requireIndependentArbitrator=true}={}){
|
|
5
|
-
const {createEscrowRegistry,createEvidenceRevalidator,escrowTerms,workerConsent,escrowTransitions}=require('../escrow/index.cjs');
|
|
5
|
+
const {createEscrowRegistry,createEvidenceRevalidator,escrowTerms,workerConsent,settlementConsent,escrowTransitions}=require('../escrow/index.cjs');
|
|
6
6
|
const {validateTask,validateEscrowEvidence,createSpendingAuthorizer,createExpiryAdapter,createApplicationHandler,createChatTools}=require('./index.cjs');
|
|
7
7
|
if(!storage||!authority||!Array.isArray(escrows)||!escrows.length||typeof approveSigning!=='function'||typeof limitsFor!=='function')throw new TypeError('Explicit storage, authority, escrow configurations and spending/signing approvals required');
|
|
8
8
|
const registry=new Map(escrows.map(c=>[String(c.chainId)+':'+c.contractAddress.toLowerCase(),c]));
|
|
@@ -24,7 +24,7 @@ function createEscrowApplication({storage,authority,escrows,signerFor,addressFor
|
|
|
24
24
|
const validators=new Map(escrows.map(c=>[String(c.chainId)+':'+c.contractAddress.toLowerCase(),createEvidenceRevalidator(c)]));
|
|
25
25
|
const revalidate=(job,evidence)=>validators.get(String(configFor(job).chainId)+':'+configFor(job).contractAddress.toLowerCase())(job,evidence);
|
|
26
26
|
const authenticate=createCredentialAuthenticator({authority});
|
|
27
|
-
const applicationOptions={workflow,store:storage.jobs,queue:storage.queue,authenticate,revalidate,consentFor:async job=>workerConsent(job,configFor(job)),authorityHandler:createAuthorityHandler({authority,allowRequest:allowAuthorityRequest})};
|
|
27
|
+
const applicationOptions={workflow,store:storage.jobs,queue:storage.queue,authenticate,revalidate,consentFor:async job=>workerConsent(job,configFor(job)),settlementConsentFor:async job=>settlementConsent(job,configFor(job)),authorityHandler:createAuthorityHandler({authority,allowRequest:allowAuthorityRequest})};
|
|
28
28
|
const workerOptions={authority,actorFor:async job=>job.pending?.actor||job.participants.requester,allowExecute:keeperEnabled,
|
|
29
29
|
execute,beforeExecute:async job=>{await workflow.assertSafe(job.id,job.participants.worker);if(!await authority.isActive(job.participants.worker))return false;const result=await adapters.start.execute({job,input:{},operation:{actor:job.participants.worker}});return result.status==='confirmed';}
|
|
30
30
|
};
|
package/src/operations/index.cjs
CHANGED
|
@@ -16,8 +16,8 @@ function validateEscrowEvidence(e){
|
|
|
16
16
|
for(const key of ['contract','token','payer','worker','verifier','arbitrator'])assert(/^0x[0-9a-fA-F]{40}$/.test(e[key]),'Invalid evidence '+key);
|
|
17
17
|
for(const key of ['job_id','agreement_hash','transaction_hash','block_hash','result_hash'])assert(/^0x[0-9a-fA-F]{64}$/.test(e[key]),'Invalid evidence '+key);
|
|
18
18
|
assert(Number.isSafeInteger(e.block_number)&&e.block_number>=0&&Number.isSafeInteger(e.confirmations)&&e.confirmations>0,'Invalid evidence block');
|
|
19
|
-
assert(['Funded','Submitted','Verified','Disputed','Settled'].includes(e.event),'Invalid evidence event');
|
|
20
|
-
assert(Number.isInteger(e.state)&&e.state>=1&&e.state<=
|
|
19
|
+
assert(['Funded','Submitted','Verified','Disputed','Settled','SettlementProposed'].includes(e.event),'Invalid evidence event');
|
|
20
|
+
assert(Number.isInteger(e.state)&&e.state>=1&&e.state<=8&&BigInt(e.worker_paid)<=BigInt(e.amount),'Invalid evidence allocation/state');
|
|
21
21
|
return structuredClone(e);
|
|
22
22
|
}
|
|
23
23
|
function createSpendingAuthorizer({spending,limitsFor,authorize}={}){
|
|
@@ -40,8 +40,8 @@ async function runMaintenanceOnce({workflow,store,actorFor,allowExecute=false,no
|
|
|
40
40
|
let result=job;
|
|
41
41
|
if(job.pending){result=await workflow.reconcile(job.id,actor);if(result.pending&&allowExecute)result=await workflow.resume(job.id,actor);}
|
|
42
42
|
else if(job.agreement){const e=job.agreement.terms.escrow;const seconds=Math.floor(now/1000);let action;
|
|
43
|
-
if(e&&['funded','running'].includes(job.state)&&seconds>e.work_deadline)action='timeout';
|
|
44
|
-
if(e&&['submitted','verified','disputed'].includes(job.state)&&seconds>e.resolution_deadline)action='timeout';
|
|
43
|
+
if(e&&(['funded','running'].includes(job.state)||(['settlement_negotiation','disputed'].includes(job.state)&&!job.submission))&&seconds>e.work_deadline)action='timeout';
|
|
44
|
+
if(e&&['submitted','verified','disputed','settlement_negotiation'].includes(job.state)&&seconds>e.resolution_deadline)action='timeout';
|
|
45
45
|
const expiry=job.agreement.terms.expires_at;
|
|
46
46
|
if(['quoted','accepted'].includes(job.state)&&Number.isSafeInteger(expiry)&&seconds>expiry)action='expire';
|
|
47
47
|
if(action&&allowExecute)result=await workflow.act(job.id,action,{}, {actor,idempotencyKey:'keeper_'+hash(job.id+':'+action)});
|
|
@@ -58,10 +58,11 @@ function createExpiryAdapter({now=()=>Date.now()}={}){const run=async({job})=>{
|
|
|
58
58
|
};return {execute:run,reconcile:run};}
|
|
59
59
|
async function runExecutionOnce({workflow,queue,execute,beforeExecute,leaseMs=30000}={}){
|
|
60
60
|
const item=await queue.claim({kind:'execution',leaseMs});if(!item)return null;
|
|
61
|
-
const
|
|
61
|
+
const controller=new AbortController();
|
|
62
|
+
const heartbeat=setInterval(async()=>{try{if(!await queue.heartbeat(item.id,item.lease,{leaseMs}))throw Error('Execution lease lost');const current=await workflow.assertSafe(item.payload.job_id,item.payload.actor);if(current.state!=='running')throw Error('Work paused');}catch(error){controller.abort(error);}},Math.max(1,Math.min(1000,Math.floor(leaseMs/3))));
|
|
62
63
|
try{
|
|
63
64
|
let result=item.result;
|
|
64
|
-
if(!result){const job=await workflow.assertSafe(item.payload.job_id,item.payload.actor);assert(job.state==='running','Work requires running state');validateTask(job.agreement.terms.task);assert(typeof beforeExecute==='function'&&await beforeExecute(job)===true,'Execution eligibility check required');result=await execute({job,idempotencyKey:item.id});assert(await queue.checkpoint(item.id,item.lease,result),'Execution lease lost');}
|
|
65
|
+
if(!result){const job=await workflow.assertSafe(item.payload.job_id,item.payload.actor);assert(job.state==='running','Work requires running state');validateTask(job.agreement.terms.task);assert(typeof beforeExecute==='function'&&await beforeExecute(job)===true,'Execution eligibility check required');result=await execute({job,idempotencyKey:item.id,signal:controller.signal});assert(await queue.checkpoint(item.id,item.lease,result),'Execution lease lost');}
|
|
65
66
|
assert(typeof result.result_hash==='string','Execution must return artifact commitment');
|
|
66
67
|
const job=await workflow.act(item.payload.job_id,'submit',result,{actor:item.payload.actor,idempotencyKey:'execution_'+hash(item.id)});
|
|
67
68
|
assert(await queue.complete(item.id,item.lease,{job_id:job.id,state:job.state}),'Execution lease lost');return job;
|
|
@@ -91,7 +92,7 @@ function createWebhookDispatcher({queue,kv,subscriptions,secretFor,fetch:fetchIm
|
|
|
91
92
|
}
|
|
92
93
|
async function watchEvidenceOnce({store,revalidate}){const results=[];let after='';while(true){const jobs=await store.list({after,limit:100});if(!jobs.length)break;after=jobs.at(-1).id;for(const job of jobs){if(!job.evidence.length)continue;const checks=[];for(const item of job.evidence)if(item.evidence.transaction_hash){try{checks.push(await revalidate(job,item.evidence));}catch(error){checks.push({status:'unavailable',message:'Evidence provider unavailable'});}}
|
|
93
94
|
const blocked=checks.some(c=>c.status!=='valid');const safety={blocked,checks,checked_at:new Date().toISOString()};await store.setSafety(job.id,safety);results.push({id:job.id,...safety});}}return results;}
|
|
94
|
-
async function freshReceipts({workflow,id,actor,revalidate}){const job=await workflow.get(id,actor);const checks=[];for(const item of job.evidence)if(item.evidence.transaction_hash){const result=await revalidate(job,item.evidence);assert(result.status==='valid','Receipt evidence is not currently valid: '+result.status);checks.push(result);}return {...await workflow.receipts(id,actor),revalidated_at:new Date().toISOString(),checks};}
|
|
95
|
+
async function freshReceipts({workflow,id,actor,revalidate}){const job=await workflow.get(id,actor);for(const p of job.negotiation?.history||[]){const canonical=require('../closing.cjs').settlementProposal(job,{worker_amount:p.worker_amount,payer_amount:p.payer_amount,expected_revision:p.escrow_version-1,expires_at:p.expires_at,reason:p.reason,claims:p.claims});assert(canonical.proposal_hash===p.proposal_hash,'Stored settlement claims changed');}const checks=[];for(const item of job.evidence)if(item.evidence.transaction_hash){const result=await revalidate(job,item.evidence);assert(result.status==='valid','Receipt evidence is not currently valid: '+result.status);checks.push(result);}return {...await workflow.receipts(id,actor),revalidated_at:new Date().toISOString(),checks};}
|
|
95
96
|
async function renderReceiptPdf(receipt){const {PDFDocument,StandardFonts}=require('pdf-lib');const pdf=await PDFDocument.create(),font=await pdf.embedFont(StandardFonts.Courier);let page,y;const newPage=()=>{page=pdf.addPage([595,842]);y=800;};newPage();const text=JSON.stringify(receipt,null,2).replace(/[^\x20-\x7e\n]/g,'?');for(const line of ['A423 Evidence Receipt',...text.split('\n')])for(let i=0;i<Math.max(1,line.length);i+=88){if(y<40)newPage();page.drawText(line.slice(i,i+88),{x:32,y,size:9,font});y-=12;}return pdf.save();}
|
|
96
97
|
function planLegacyMigration(records){return records.map(record=>({id:record.id,state:record.state,automatic:false,reason:['funded','running','submitted','verified','paid','settlement_pending','completed'].includes(String(record.state).toLowerCase())?'Existing custody/evidence must stay with original contract; do not relabel':'Requires reviewed participant and accepted-terms mapping; create a new job with provenance'}));}
|
|
97
98
|
module.exports={validateTask,validateEscrowEvidence,createSpendingAuthorizer,runMaintenanceOnce,createExpiryAdapter,runExecutionOnce,enqueueRunningJobs,createEventStream,createWebhookDispatcher,watchEvidenceOnce,freshReceipts,renderReceiptPdf,planLegacyMigration};
|
|
@@ -15,7 +15,7 @@ export function createSpendingAuthorizer(options: {spending: SpendingStore;limit
|
|
|
15
15
|
export function runMaintenanceOnce(options: {workflow: Workflow;store: SqliteJobStore;actorFor(job: WorkflowJob): Promise<string | null>;allowExecute?: boolean;now?: number}): Promise<Array<{id: string;state?: string;pending?: boolean;error?: string}>>;
|
|
16
16
|
export function createExpiryAdapter(options?: {now?(): number}): {execute(context: {job: WorkflowJob}): Promise<any>;reconcile(context: {job: WorkflowJob}): Promise<any>};
|
|
17
17
|
export function enqueueRunningJobs(options: {store: SqliteJobStore;queue: DurableQueue}): Promise<number>;
|
|
18
|
-
export function runExecutionOnce(options: {workflow: Workflow;queue: DurableQueue;execute(context: {job: WorkflowJob;idempotencyKey: string}): Promise<{result_hash: string;[key: string]: Json}>;beforeExecute(job: WorkflowJob): Promise<boolean>;leaseMs?: number}): Promise<WorkflowJob | null>;
|
|
18
|
+
export function runExecutionOnce(options: {workflow: Workflow;queue: DurableQueue;execute(context: {job: WorkflowJob;idempotencyKey: string;signal: AbortSignal}): Promise<{result_hash: string;[key: string]: Json}>;beforeExecute(job: WorkflowJob): Promise<boolean>;leaseMs?: number}): Promise<WorkflowJob | null>;
|
|
19
19
|
export function createEventStream(options: {store: SqliteJobStore;authenticate(request: Request): Promise<string | null>;pollMs?: number;maxDurationMs?: number}): (request: Request) => Promise<Response>;
|
|
20
20
|
export function createWebhookDispatcher(options: {queue: DurableQueue;kv: KeyValueStore;subscriptions: Array<{id: string;actor: string;url: string;after?: number}>;secretFor(id: string): Promise<string>;fetch?: typeof fetch;timeoutMs?: number}): {project(store: SqliteJobStore): Promise<void>;deliverOne(): Promise<any>};
|
|
21
21
|
export function watchEvidenceOnce(options: {store: SqliteJobStore;revalidate: Revalidator}): Promise<any[]>;
|
|
@@ -24,6 +24,6 @@ export function renderReceiptPdf(receipt: object): Promise<Uint8Array>;
|
|
|
24
24
|
export function planLegacyMigration(records: Array<{id: string;state: string}>): Array<{id: string;state: string;automatic: false;reason: string}>;
|
|
25
25
|
export function migrateUnfundedLegacy(options: {records: Array<{id: string;state: string}>;workflow: Workflow;actor: string;map(source: any): Promise<Parameters<Workflow['create']>[0]>}): Promise<WorkflowJob[]>;
|
|
26
26
|
export function createChatTools(options: {workflow: Workflow;store: SqliteJobStore;queue: DurableQueue;actor: string}): Record<string,(input: any) => Promise<WorkflowJob>>;
|
|
27
|
-
export function createApplicationHandler(options: {workflow: Workflow;store: SqliteJobStore;queue: DurableQueue;authenticate(request: Request): Promise<string | null>;revalidate: Revalidator;consentFor?(job: WorkflowJob): Promise<object>;legacyState?(actor: string): Promise<object>;authorityHandler?(request: Request): Promise<Response>;basePath?: string}): (request: Request) => Promise<Response>;
|
|
27
|
+
export function createApplicationHandler(options: {workflow: Workflow;store: SqliteJobStore;queue: DurableQueue;authenticate(request: Request): Promise<string | null>;revalidate: Revalidator;consentFor?(job: WorkflowJob): Promise<object>;settlementConsentFor?(job: WorkflowJob): Promise<object>;legacyState?(actor: string): Promise<object>;authorityHandler?(request: Request): Promise<Response>;basePath?: string}): (request: Request) => Promise<Response>;
|
|
28
28
|
|
|
29
|
-
export function createEscrowApplication(options: {storage: import('../storage/index.js').SqliteStorage;authority: import('../authority/index.js').Authority;escrows: Array<any>;signerFor(actor: string): Promise<any>;addressFor(actor: string): Promise<string>;limitsFor(context: SigningContext): Promise<{perTransaction: string;daily: string;total: string}>;approveSigning(context: SigningContext): Promise<boolean>;validateArtifact(context: {job: WorkflowJob;resultHash: string}): Promise<boolean>;execute?(context: {job: WorkflowJob;idempotencyKey: string}): Promise<{result_hash: string;[key: string]: Json}>;keeperEnabled?: boolean;allowAuthorityRequest?(request: Request): Promise<boolean>;requireIndependentArbitrator?: boolean}): {workflow: Workflow;applicationOptions: Parameters<typeof createApplicationHandler>[0];workerOptions: any;handler: (request: Request) => Promise<Response>;chatTools(actor: string): ReturnType<typeof createChatTools>};
|
|
29
|
+
export function createEscrowApplication(options: {storage: import('../storage/index.js').SqliteStorage;authority: import('../authority/index.js').Authority;escrows: Array<any>;signerFor(actor: string): Promise<any>;addressFor(actor: string): Promise<string>;limitsFor(context: SigningContext): Promise<{perTransaction: string;daily: string;total: string}>;approveSigning(context: SigningContext): Promise<boolean>;validateArtifact(context: {job: WorkflowJob;resultHash: string}): Promise<boolean>;execute?(context: {job: WorkflowJob;idempotencyKey: string;signal: AbortSignal}): Promise<{result_hash: string;[key: string]: Json}>;keeperEnabled?: boolean;allowAuthorityRequest?(request: Request): Promise<boolean>;requireIndependentArbitrator?: boolean}): {workflow: Workflow;applicationOptions: Parameters<typeof createApplicationHandler>[0];workerOptions: any;handler: (request: Request) => Promise<Response>;chatTools(actor: string): ReturnType<typeof createChatTools>};
|
package/src/workflow.cjs
CHANGED
|
@@ -75,7 +75,31 @@ function createWorkflow({ store, adapters = {}, authorize, validateTerms, valida
|
|
|
75
75
|
next.operations[pending.key] = { request_hash: pending.request_hash, status: 'confirmed', result: copy(result) };
|
|
76
76
|
next.evidence.push({ action: pending.action, operation_id: pending.id, evidence: copy(result.evidence) });
|
|
77
77
|
if (pending.action === 'submit') next.submission = copy(pending.input);
|
|
78
|
-
|
|
78
|
+
let eventData=copy(result.evidence);
|
|
79
|
+
if(['propose_settlement','counter_settlement'].includes(pending.action)){
|
|
80
|
+
const proposal=require('./closing.cjs').settlementProposal(job,pending.input);
|
|
81
|
+
if(result.evidence.proposal_hash!==proposal.proposal_hash || result.evidence.escrow_version!==proposal.escrow_version)error('INVALID_ADAPTER_RESULT','Confirmed proposal must match exact claimed terms',502);
|
|
82
|
+
const record={...proposal,proposer_id:pending.actor,at:stamp()};
|
|
83
|
+
next.negotiation={status:'open',origin_state:job.negotiation?.origin_state||job.state,current:record,history:[...(job.negotiation?.history||[]),record]};
|
|
84
|
+
next.work_status='paused';eventData={...eventData,proposal:record};
|
|
85
|
+
}
|
|
86
|
+
if(pending.action==='accept_settlement'){
|
|
87
|
+
const p=job.negotiation?.current;
|
|
88
|
+
if(!p || result.evidence.settlement_reason!==4 || result.evidence.worker_paid!==p.worker_amount || result.evidence.proposal_hash!==p.proposal_hash || result.evidence.escrow_version!==p.escrow_version)error('INVALID_ADAPTER_RESULT','Closing settlement evidence mismatch',502);
|
|
89
|
+
next.negotiation={...copy(job.negotiation),status:'settled',accepted_by:pending.actor,settled_at:stamp()};next.work_status='stopped';
|
|
90
|
+
}
|
|
91
|
+
if(pending.action==='dispute'){next.work_status='paused';if(next.negotiation)next.negotiation.status='disputed';}
|
|
92
|
+
if(['release','refund','resolve','timeout'].includes(pending.action)){
|
|
93
|
+
next.work_status=pending.action==='release'?'completed':'stopped';
|
|
94
|
+
if(next.negotiation)next.negotiation={...next.negotiation,status:'settled',settled_at:stamp()};
|
|
95
|
+
}
|
|
96
|
+
if(['start','submit','verify'].includes(pending.action))next.work_status={start:'running',submit:'submitted',verify:'verified'}[pending.action];
|
|
97
|
+
const e=result.evidence;
|
|
98
|
+
if(typeof e.amount==='string'&&typeof e.worker_paid==='string'&&Number.isInteger(e.state)){
|
|
99
|
+
const closed=[5,6,7].includes(e.state);
|
|
100
|
+
next.escrow_ledger={funded_amount:e.amount,worker_paid:e.worker_paid,payer_refunded:closed?(BigInt(e.amount)-BigInt(e.worker_paid)).toString():'0',locked_amount:closed?'0':e.amount,escrow_version:e.escrow_version??0};
|
|
101
|
+
}
|
|
102
|
+
return save(job, next, pending.action + '.confirmed', pending.actor, eventData);
|
|
79
103
|
}
|
|
80
104
|
async function execute(job, reconcile = false) {
|
|
81
105
|
const pending = job.pending;
|
|
@@ -94,13 +118,13 @@ function createWorkflow({ store, adapters = {}, authorize, validateTerms, valida
|
|
|
94
118
|
if (participants.requester === participants.worker || actor !== participants.requester) error('FORBIDDEN', 'Requester must create the job and differ from worker', 403);
|
|
95
119
|
canonical(metadata); canonical(participants);
|
|
96
120
|
const at = stamp();
|
|
97
|
-
const job = { id, protocol: 'a423.workflow.v1', version: 1, state: 'draft', participants: copy(participants), metadata: copy(metadata), agreement: null, submission: null, pending: null, operations: {}, evidence: [], events: [{ sequence: 1, type: 'created', actor_id: actor, at, data: {} }], created_at: at, updated_at: at };
|
|
121
|
+
const job = { work_status:'not_started',negotiation:null,escrow_ledger:null,id, protocol: 'a423.workflow.v1', version: 1, state: 'draft', participants: copy(participants), metadata: copy(metadata), agreement: null, submission: null, pending: null, operations: {}, evidence: [], events: [{ sequence: 1, type: 'created', actor_id: actor, at, data: {} }], created_at: at, updated_at: at };
|
|
98
122
|
if (authorize && await authorize({ job: copy(job), action: 'create', actor, input: {} }) !== true) error('FORBIDDEN', 'Application policy rejected creation', 403);
|
|
99
123
|
if (!await store.create(job)) error('CONFLICT', 'Job ID already exists');
|
|
100
124
|
return copy(job);
|
|
101
125
|
},
|
|
102
126
|
get,
|
|
103
|
-
async assertSafe(id,actor){const job=await get(id,actor);const safety=store.getSafety?await store.getSafety(id):null;if(safety?.blocked)error('SAFETY_HOLD','Evidence requires reconciliation',409);return job;},
|
|
127
|
+
async assertSafe(id,actor){const job=await get(id,actor);const safety=store.getSafety?await store.getSafety(id):null;if(safety?.blocked)error('SAFETY_HOLD','Evidence requires reconciliation',409);if(['propose_settlement','counter_settlement','dispute'].includes(job.pending?.action)||job.state==='settlement_negotiation')error('WORK_PAUSED','Settlement negotiation pauses work',409);return job;},
|
|
104
128
|
async act(id, action, input, { actor, idempotencyKey } = {}) {
|
|
105
129
|
identifier(actor); identifier(idempotencyKey); identifier(action); canonical(input);
|
|
106
130
|
let job = await get(id, actor);
|
|
@@ -156,7 +180,7 @@ function createWorkflow({ store, adapters = {}, authorize, validateTerms, valida
|
|
|
156
180
|
},
|
|
157
181
|
async receipts(id, actor) {
|
|
158
182
|
const job = await get(id, actor);
|
|
159
|
-
return { protocol: job.protocol, job_id: id, state: job.state, agreement: job.agreement, submission: job.submission, evidence: job.evidence, events: job.events, pending: job.pending };
|
|
183
|
+
return { protocol: job.protocol, job_id: id, state: job.state, agreement: job.agreement, submission: job.submission, work_status:job.work_status,negotiation:job.negotiation,escrow_ledger:job.escrow_ledger,evidence: job.evidence, events: job.events, pending: job.pending };
|
|
160
184
|
}
|
|
161
185
|
};
|
|
162
186
|
}
|
package/src/workflow.d.ts
CHANGED
|
@@ -2,7 +2,12 @@ export type Json = null | boolean | number | string | Json[] | { [key: string]:
|
|
|
2
2
|
export interface Agreement { revision: number; terms: { [key: string]: Json }; hash: string; accepted_by: string[] }
|
|
3
3
|
export interface Operation { id: string; key: string; action: string; actor: string; input: Json; request_hash: string; target: string; created_at: string; observation: AdapterResult | null }
|
|
4
4
|
export interface AdapterResult { status: 'pending' | 'confirmed' | 'rejected'; evidence?: { [key: string]: Json }; [key: string]: Json | undefined }
|
|
5
|
+
export interface SettlementClaim {kind: 'work' | 'expense' | 'other';description: string;amount?: string;evidence?: string[]}
|
|
6
|
+
export interface SettlementProposal {version: 'a423.closing.v1';job_id: string;agreement_hash: string;escrow_version: number;worker_amount: string;payer_amount: string;expires_at: number;reason: string;claims: SettlementClaim[];closes_job: true;proposal_hash: string;proposer_id?: string;at?: string}
|
|
7
|
+
export interface Negotiation {status: 'open' | 'disputed' | 'settled';origin_state: string;current: SettlementProposal;history: SettlementProposal[];accepted_by?: string;settled_at?: string}
|
|
8
|
+
export interface EscrowLedger {funded_amount: string;worker_paid: string;payer_refunded: string;locked_amount: string;escrow_version: number}
|
|
5
9
|
export interface WorkflowJob {
|
|
10
|
+
work_status?: string;negotiation?: Negotiation | null;escrow_ledger?: EscrowLedger | null;
|
|
6
11
|
id: string; protocol: 'a423.workflow.v1'; version: number; state: string;
|
|
7
12
|
participants: { requester: string; worker: string; verifier: string; [role: string]: string };
|
|
8
13
|
metadata: Json; agreement: Agreement | null; submission: Json;
|
|
@@ -38,7 +43,7 @@ export interface Workflow {
|
|
|
38
43
|
act(id: string, action: string, input: Json, options: { actor: string; idempotencyKey: string }): Promise<WorkflowJob>;
|
|
39
44
|
resume(id: string, actor: string): Promise<WorkflowJob>;
|
|
40
45
|
reconcile(id: string, actor: string): Promise<WorkflowJob>;
|
|
41
|
-
receipts(id: string, actor: string): Promise<Pick<WorkflowJob, 'protocol' | 'state' | 'agreement' | 'submission' | 'evidence' | 'events' | 'pending'> & { job_id: string }>;
|
|
46
|
+
receipts(id: string, actor: string): Promise<Pick<WorkflowJob, 'protocol' | 'state' | 'agreement' | 'submission' | 'evidence' | 'events' | 'pending' | 'work_status' | 'negotiation' | 'escrow_ledger'> & { job_id: string }>;
|
|
42
47
|
}
|
|
43
48
|
export function hashAgreement(terms: Json): string;
|
|
44
49
|
export function createWorkflow(options: WorkflowOptions): Workflow;
|