@toon-protocol/rig 3.5.3 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -152
- package/dist/chunk-CDSFVZDV.js +290 -0
- package/dist/chunk-CDSFVZDV.js.map +1 -0
- package/dist/{chunk-B5ISARMU.js → chunk-GVXBUL3C.js} +15 -2
- package/dist/chunk-GVXBUL3C.js.map +1 -0
- package/dist/chunk-LTFMPOHU.js +194 -0
- package/dist/chunk-LTFMPOHU.js.map +1 -0
- package/dist/{chunk-LCYCKWAT.js → chunk-YZRR2UO6.js} +86 -5
- package/dist/chunk-YZRR2UO6.js.map +1 -0
- package/dist/{chunk-HJDPM37T.js → chunk-ZZBA5VVL.js} +11 -7
- package/dist/{chunk-HJDPM37T.js.map → chunk-ZZBA5VVL.js.map} +1 -1
- package/dist/cli/rig.js +298 -529
- package/dist/cli/rig.js.map +1 -1
- package/dist/index.d.ts +3 -109
- package/dist/index.js +9 -108
- package/dist/index.js.map +1 -1
- package/dist/{publisher-BFcnZeJI.d.ts → publisher-CcL96ugq.d.ts} +58 -1
- package/dist/standalone/index.d.ts +241 -378
- package/dist/standalone/index.js +19 -11
- package/dist/standalone-mode-LEAC4A2L.js +390 -0
- package/dist/standalone-mode-LEAC4A2L.js.map +1 -0
- package/package.json +5 -4
- package/dist/chunk-4CAOEXGQ.js +0 -631
- package/dist/chunk-4CAOEXGQ.js.map +0 -1
- package/dist/chunk-B5ISARMU.js.map +0 -1
- package/dist/chunk-FFQXFV4W.js +0 -400
- package/dist/chunk-FFQXFV4W.js.map +0 -1
- package/dist/chunk-LCYCKWAT.js.map +0 -1
- package/dist/chunk-TTJCBGKW.js +0 -447
- package/dist/chunk-TTJCBGKW.js.map +0 -1
- package/dist/network-bootstrap-CTYIZMP6.js +0 -40
- package/dist/network-bootstrap-CTYIZMP6.js.map +0 -1
- package/dist/standalone-mode-LBWGU7NV.js +0 -724
- package/dist/standalone-mode-LBWGU7NV.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { C as COMMENT_KIND,
|
|
3
|
-
import { JobHandler, IncrementOfferTags } from '@toon-protocol/client';
|
|
1
|
+
import { d as GitObjectType, P as Publisher, F as FeeRates, c as PublishReceipt, b as UnsignedEvent } from './publisher-CcL96ugq.js';
|
|
2
|
+
export { C as COMMENT_KIND, e as GitObject, G as GitObjectUpload, M as MAINTAINERS_TAG, f as MAX_OBJECT_SIZE, R as REPOSITORY_STATE_KIND, g as StatusKind, U as UploadReceipt, h as authorizedStatusAuthors, i as buildComment, j as buildIssue, k as buildPatch, l as buildRepoAnnouncement, m as buildRepoRefs, n as buildStatus, o as createGitBlob, p as createGitCommit, q as createGitTag, r as createGitTree, s as hashGitObject, t as parseMaintainers } from './publisher-CcL96ugq.js';
|
|
4
3
|
import '@toon-protocol/core/nip34';
|
|
5
4
|
|
|
6
5
|
/**
|
|
@@ -1409,109 +1408,4 @@ interface FactoryJobExecution {
|
|
|
1409
1408
|
*/
|
|
1410
1409
|
declare function executeFactoryJob(options: ExecuteFactoryJobOptions): Promise<FactoryJobExecution>;
|
|
1411
1410
|
|
|
1412
|
-
|
|
1413
|
-
* ClientJobDeliveryPort — the concrete `JobDeliveryPort` (#52, #56) backed
|
|
1414
|
-
* by the real, published `@toon-protocol/client` (>=0.26.0) hashlock
|
|
1415
|
-
* helpers (toon-client#495) and serve-side job handling (toon-client#494).
|
|
1416
|
-
*
|
|
1417
|
-
* Per the issue's explicit instruction, this never re-derives the
|
|
1418
|
-
* encrypt/condition relationship locally: `encryptArtifact` is the only
|
|
1419
|
-
* place a key or condition is minted, and `fulfillIncrement` is the only
|
|
1420
|
-
* place a key becomes an ILP fulfillment.
|
|
1421
|
-
*
|
|
1422
|
-
* Wiring: pass `handleJob` as `ToonClientConfig.jobHandler` for the SAME
|
|
1423
|
-
* identity that runs `executeFactoryJob` (job serving rides the provider's
|
|
1424
|
-
* own BTP session, per that field's doc). The connector delivers the
|
|
1425
|
-
* buyer's paying PREPARE as a server-originated BTP MESSAGE addressed to
|
|
1426
|
-
* this client's own ILP address; `handleJob` reveals the key minted by the
|
|
1427
|
-
* immediately-preceding `encryptArtifact` call as the fulfillment — which
|
|
1428
|
-
* is simultaneously the payment settling and the decrypt-key handoff
|
|
1429
|
-
* (docs/factory-job-protocol.md §4.2, toon-meta).
|
|
1430
|
-
*
|
|
1431
|
-
* One increment in flight at a time, matching `JobDeliveryPort`'s own
|
|
1432
|
-
* sequential-use contract: `encryptArtifact` stages a key, and only
|
|
1433
|
-
* `waitForPayment` for that SAME condition arms `handleJob` to spend it. A
|
|
1434
|
-
* job PREPARE for any other condition — stray, replayed, or for an
|
|
1435
|
-
* increment this port never staged — is refused rather than fulfilled.
|
|
1436
|
-
*/
|
|
1437
|
-
|
|
1438
|
-
interface ClientJobDeliveryPortOptions {
|
|
1439
|
-
/** How long to wait for the buyer's paying PREPARE before giving up on an increment. Default 120s. */
|
|
1440
|
-
paymentTimeoutMs?: number;
|
|
1441
|
-
}
|
|
1442
|
-
declare class ClientJobDeliveryPort implements JobDeliveryPort {
|
|
1443
|
-
private readonly paymentTimeoutMs;
|
|
1444
|
-
private staged?;
|
|
1445
|
-
private armed?;
|
|
1446
|
-
constructor(options?: ClientJobDeliveryPortOptions);
|
|
1447
|
-
/**
|
|
1448
|
-
* The `JobHandler` to register as `ToonClientConfig.jobHandler`. Throws
|
|
1449
|
-
* (answered F99 by `createJobMessageHandler`) when no increment is armed
|
|
1450
|
-
* for the PREPARE's `executionCondition` — this is the only path that
|
|
1451
|
-
* releases a key, and it releases exactly the one the buyer paid for.
|
|
1452
|
-
*/
|
|
1453
|
-
readonly handleJob: JobHandler;
|
|
1454
|
-
encryptArtifact(bytes: Uint8Array): Promise<EncryptedArtifact>;
|
|
1455
|
-
waitForPayment(offer: OfferedIncrement): Promise<boolean>;
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
/**
|
|
1459
|
-
* Buyer-side payment for one factory-job increment offer (#56) — the other
|
|
1460
|
-
* half of the hashlock join `ClientJobDeliveryPort` (the provider side)
|
|
1461
|
-
* answers. Built against `@toon-protocol/client`'s real `buildIncrementPrepare`
|
|
1462
|
-
* / `decryptArtifact` (toon-client#495): this module does not re-derive the
|
|
1463
|
-
* PREPARE fields or the AEAD decrypt itself.
|
|
1464
|
-
*
|
|
1465
|
-
* Per docs/factory-job-protocol.md §4.2 (toon-meta), paying an offer is:
|
|
1466
|
-
* `executionCondition` equal to the offer's `condition` tag byte for byte,
|
|
1467
|
-
* and `data` carrying a reference back to the offer's own kind:7000 event id
|
|
1468
|
-
* — "the payment names the job." The provider returns the decryption key as
|
|
1469
|
-
* the ILP fulfillment; revealing it is the same act as accepting payment.
|
|
1470
|
-
*/
|
|
1471
|
-
|
|
1472
|
-
/** What a buyer reads off an advertised kind:7000 `status:"partial"` increment offer. */
|
|
1473
|
-
interface PayableIncrementOffer extends IncrementOfferTags {
|
|
1474
|
-
/** The offer event's own id — carried in the PREPARE's `data` field (§4.2). */
|
|
1475
|
-
offerEventId: string;
|
|
1476
|
-
}
|
|
1477
|
-
/** The outcome of paying one increment offer. */
|
|
1478
|
-
type PaidIncrementOffer = {
|
|
1479
|
-
paid: true;
|
|
1480
|
-
key: Uint8Array;
|
|
1481
|
-
} | {
|
|
1482
|
-
paid: false;
|
|
1483
|
-
};
|
|
1484
|
-
/** The narrow slice of `ToonClient` a buyer needs to pay an increment offer. */
|
|
1485
|
-
interface SwapPacketSender {
|
|
1486
|
-
sendSwapPacket(params: {
|
|
1487
|
-
destination: string;
|
|
1488
|
-
amount: bigint;
|
|
1489
|
-
toonData: Uint8Array;
|
|
1490
|
-
executionCondition?: Uint8Array;
|
|
1491
|
-
timeout?: number;
|
|
1492
|
-
}): Promise<{
|
|
1493
|
-
accepted: boolean;
|
|
1494
|
-
fulfillment?: string;
|
|
1495
|
-
code?: string;
|
|
1496
|
-
message?: string;
|
|
1497
|
-
}>;
|
|
1498
|
-
}
|
|
1499
|
-
/**
|
|
1500
|
-
* Buyer: pay one advertised increment offer. Sends an ILP PREPARE addressed
|
|
1501
|
-
* to `providerDestination` with `executionCondition` equal to the offer's
|
|
1502
|
-
* `condition` tag and `data` referencing the offer's event id, per §4.2.
|
|
1503
|
-
* Resolves `{ paid: false }` on a REJECT (expired/no such increment/wrong
|
|
1504
|
-
* fulfillment) rather than throwing — a buyer declining or failing to pay is
|
|
1505
|
-
* a protocol outcome (`abandoned-buyer`), not an error.
|
|
1506
|
-
*/
|
|
1507
|
-
declare function payIncrementOffer(client: SwapPacketSender, providerDestination: string, offer: PayableIncrementOffer): Promise<PaidIncrementOffer>;
|
|
1508
|
-
/**
|
|
1509
|
-
* Buyer: decrypt an increment's artifact using the key revealed as the ILP
|
|
1510
|
-
* fulfillment. `offerConditionHex` MUST be the offer's own `condition` tag
|
|
1511
|
-
* as read from the relay before payment — never re-derived from `key` —
|
|
1512
|
-
* so a provider who reveals a different key than it advertised is caught
|
|
1513
|
-
* here rather than trusted.
|
|
1514
|
-
*/
|
|
1515
|
-
declare function decryptIncrementArtifact(ciphertext: Uint8Array, key: Uint8Array, offerConditionHex: string): Uint8Array;
|
|
1516
|
-
|
|
1517
|
-
export { type BuildIncrementOfferOptions, type BuildNarrationOptions, type BuildResultEventOptions, ClientJobDeliveryPort, type ClientJobDeliveryPortOptions, type ClosureResult, type CollectRepoObjectsOptions, type CollectRepoObjectsResult, DEFAULT_CONCURRENCY, type DownloadOptions, type DownloadResult, type EncryptedArtifact, type EncryptedArtifactRef, type ExecuteFactoryJobOptions, type ExecutePushOptions, FACTORY_JOB_FEEDBACK_KIND, FACTORY_JOB_REQUEST_KIND, FACTORY_JOB_RESULT_KIND, type FactoryJobExecution, type FactoryJobHooks, type FactoryJobOutcome, type FactoryJobRequest, type FactoryJobWork, type FactoryMilestone, type FactoryTicket, FeeRates, type FetchLike, type FetchRemoteStateOptions, type FetchedObject, type GateCheck, type GateResult, type GatewayFetchOptions, type GitCommentRequest, GitError, type GitErrorEnvelope, type GitEstimateRequest, type GitEstimateResponse, type GitEventResponse, type GitFeeEstimate, type GitIssueRequest, GitObjectType, type GitPatchRequest, type GitPlannedObject, type GitPublishReceipt, type GitPushRequest, type GitPushResponse, type GitRef, type GitRefUpdate, type GitRepoAddr, GitRepoReader, type GitStatusRequest, type GitStatusValue, type GitUploadStep, type IncrementSpec, type JobDeliveryPort, type MissingObject, NonFastForwardError, type NostrEvent, type NostrFilter, ObjectIntegrityError, type ObjectStat, type ObjectWithPath, ObjectWriteMismatchError, type OfferedIncrement, type OversizeObject, OversizeObjectsError, type PaidIncrementOffer, type PayableIncrementOffer, type PlanPushOptions, type PlannedObject, PublishReceipt, Publisher, type PushFeeEstimate, type PushPlan, type PushResult, type ReadGitObject, type ReadObjectsResult, type RefUpdate, type RefUpdateKind, type RejectedRefUpdate, type RelayEvent, type RemoteState, type RepoRefs, type StatObjectsResult, type SwapPacketSender, UnsignedEvent, type UploadStepResult, type WebSocketFactory, type WebSocketLike, buildIncrementOfferEvent, buildNarrationEvent, buildQuoteEvent, buildResultEvent, collectRepoObjects, decryptIncrementArtifact, downloadGitObjects, executeFactoryJob, executePush, fetchRemoteState, fetchTxBytes, gatePassed, hexToNpub, isSafeRefname, missingObjectsMessage, npubToHex, ownerToHex, parseFactoryJobRequest, payIncrementOffer, planFactoryJob, planPush, queryRelay, referencedShas, serializeEventReceipt, serializeFeeEstimate, serializePushPlan, serializePushResult, setHeadSymref, updateRef, verifyObjectBody, walkClosure, writeGitObject, writeGitObjects };
|
|
1411
|
+
export { type BuildIncrementOfferOptions, type BuildNarrationOptions, type BuildResultEventOptions, type ClosureResult, type CollectRepoObjectsOptions, type CollectRepoObjectsResult, DEFAULT_CONCURRENCY, type DownloadOptions, type DownloadResult, type EncryptedArtifact, type EncryptedArtifactRef, type ExecuteFactoryJobOptions, type ExecutePushOptions, FACTORY_JOB_FEEDBACK_KIND, FACTORY_JOB_REQUEST_KIND, FACTORY_JOB_RESULT_KIND, type FactoryJobExecution, type FactoryJobHooks, type FactoryJobOutcome, type FactoryJobRequest, type FactoryJobWork, type FactoryMilestone, type FactoryTicket, FeeRates, type FetchLike, type FetchRemoteStateOptions, type FetchedObject, type GateCheck, type GateResult, type GatewayFetchOptions, type GitCommentRequest, GitError, type GitErrorEnvelope, type GitEstimateRequest, type GitEstimateResponse, type GitEventResponse, type GitFeeEstimate, type GitIssueRequest, GitObjectType, type GitPatchRequest, type GitPlannedObject, type GitPublishReceipt, type GitPushRequest, type GitPushResponse, type GitRef, type GitRefUpdate, type GitRepoAddr, GitRepoReader, type GitStatusRequest, type GitStatusValue, type GitUploadStep, type IncrementSpec, type JobDeliveryPort, type MissingObject, NonFastForwardError, type NostrEvent, type NostrFilter, ObjectIntegrityError, type ObjectStat, type ObjectWithPath, ObjectWriteMismatchError, type OfferedIncrement, type OversizeObject, OversizeObjectsError, type PlanPushOptions, type PlannedObject, PublishReceipt, Publisher, type PushFeeEstimate, type PushPlan, type PushResult, type ReadGitObject, type ReadObjectsResult, type RefUpdate, type RefUpdateKind, type RejectedRefUpdate, type RelayEvent, type RemoteState, type RepoRefs, type StatObjectsResult, UnsignedEvent, type UploadStepResult, type WebSocketFactory, type WebSocketLike, buildIncrementOfferEvent, buildNarrationEvent, buildQuoteEvent, buildResultEvent, collectRepoObjects, downloadGitObjects, executeFactoryJob, executePush, fetchRemoteState, fetchTxBytes, gatePassed, hexToNpub, isSafeRefname, missingObjectsMessage, npubToHex, ownerToHex, parseFactoryJobRequest, planFactoryJob, planPush, queryRelay, referencedShas, serializeEventReceipt, serializeFeeEstimate, serializePushPlan, serializePushResult, setHeadSymref, updateRef, verifyObjectBody, walkClosure, writeGitObject, writeGitObjects };
|
package/dist/index.js
CHANGED
|
@@ -27,15 +27,7 @@ import {
|
|
|
27
27
|
walkClosure,
|
|
28
28
|
writeGitObject,
|
|
29
29
|
writeGitObjects
|
|
30
|
-
} from "./chunk-
|
|
31
|
-
import {
|
|
32
|
-
MAX_OBJECT_SIZE,
|
|
33
|
-
createGitBlob,
|
|
34
|
-
createGitCommit,
|
|
35
|
-
createGitTag,
|
|
36
|
-
createGitTree,
|
|
37
|
-
hashGitObject
|
|
38
|
-
} from "./chunk-B5ISARMU.js";
|
|
30
|
+
} from "./chunk-ZZBA5VVL.js";
|
|
39
31
|
import {
|
|
40
32
|
COMMENT_KIND,
|
|
41
33
|
MAINTAINERS_TAG,
|
|
@@ -51,6 +43,14 @@ import {
|
|
|
51
43
|
parseMaintainers,
|
|
52
44
|
queryRelay
|
|
53
45
|
} from "./chunk-2KJJ7LSO.js";
|
|
46
|
+
import {
|
|
47
|
+
MAX_OBJECT_SIZE,
|
|
48
|
+
createGitBlob,
|
|
49
|
+
createGitCommit,
|
|
50
|
+
createGitTag,
|
|
51
|
+
createGitTree,
|
|
52
|
+
hashGitObject
|
|
53
|
+
} from "./chunk-GVXBUL3C.js";
|
|
54
54
|
|
|
55
55
|
// src/factory-job-gate.ts
|
|
56
56
|
function gatePassed(gate) {
|
|
@@ -325,105 +325,8 @@ async function executeFactoryJob(options) {
|
|
|
325
325
|
}
|
|
326
326
|
return haltWith("completed");
|
|
327
327
|
}
|
|
328
|
-
|
|
329
|
-
// src/factory-job-delivery-client.ts
|
|
330
|
-
import { createHash } from "crypto";
|
|
331
|
-
import {
|
|
332
|
-
encryptArtifact as clientEncryptArtifact,
|
|
333
|
-
fulfillIncrement
|
|
334
|
-
} from "@toon-protocol/client";
|
|
335
|
-
function toHex(bytes) {
|
|
336
|
-
return Buffer.from(bytes).toString("hex");
|
|
337
|
-
}
|
|
338
|
-
var DEFAULT_PAYMENT_TIMEOUT_MS = 12e4;
|
|
339
|
-
var ClientJobDeliveryPort = class {
|
|
340
|
-
paymentTimeoutMs;
|
|
341
|
-
staged;
|
|
342
|
-
armed;
|
|
343
|
-
constructor(options = {}) {
|
|
344
|
-
this.paymentTimeoutMs = options.paymentTimeoutMs ?? DEFAULT_PAYMENT_TIMEOUT_MS;
|
|
345
|
-
}
|
|
346
|
-
/**
|
|
347
|
-
* The `JobHandler` to register as `ToonClientConfig.jobHandler`. Throws
|
|
348
|
-
* (answered F99 by `createJobMessageHandler`) when no increment is armed
|
|
349
|
-
* for the PREPARE's `executionCondition` — this is the only path that
|
|
350
|
-
* releases a key, and it releases exactly the one the buyer paid for.
|
|
351
|
-
*/
|
|
352
|
-
handleJob = (job) => {
|
|
353
|
-
const conditionHex = toHex(job.executionCondition);
|
|
354
|
-
if (!this.armed || conditionHex !== this.armed.conditionHex) {
|
|
355
|
-
throw new Error(
|
|
356
|
-
`no factory-job increment is awaiting payment for condition ${conditionHex}`
|
|
357
|
-
);
|
|
358
|
-
}
|
|
359
|
-
const { key, resolve } = this.armed;
|
|
360
|
-
this.armed = void 0;
|
|
361
|
-
resolve(true);
|
|
362
|
-
return { fulfillment: fulfillIncrement(key) };
|
|
363
|
-
};
|
|
364
|
-
async encryptArtifact(bytes) {
|
|
365
|
-
const encrypted = clientEncryptArtifact(bytes);
|
|
366
|
-
const conditionHex = toHex(encrypted.condition);
|
|
367
|
-
this.staged = { key: encrypted.key, conditionHex };
|
|
368
|
-
return {
|
|
369
|
-
ciphertext: encrypted.ciphertext,
|
|
370
|
-
ciphertextSha256: createHash("sha256").update(encrypted.ciphertext).digest("hex"),
|
|
371
|
-
conditionHex
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
async waitForPayment(offer) {
|
|
375
|
-
if (!this.staged || offer.conditionHex !== this.staged.conditionHex) {
|
|
376
|
-
throw new Error(
|
|
377
|
-
"waitForPayment was called for a condition that does not match the most recent encryptArtifact() call"
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
|
-
const { key, conditionHex } = this.staged;
|
|
381
|
-
this.staged = void 0;
|
|
382
|
-
return new Promise((resolve) => {
|
|
383
|
-
const timer = setTimeout(() => {
|
|
384
|
-
this.armed = void 0;
|
|
385
|
-
resolve(false);
|
|
386
|
-
}, this.paymentTimeoutMs);
|
|
387
|
-
this.armed = {
|
|
388
|
-
key,
|
|
389
|
-
conditionHex,
|
|
390
|
-
resolve: (paid) => {
|
|
391
|
-
clearTimeout(timer);
|
|
392
|
-
resolve(paid);
|
|
393
|
-
}
|
|
394
|
-
};
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
};
|
|
398
|
-
|
|
399
|
-
// src/factory-job-pay.ts
|
|
400
|
-
import {
|
|
401
|
-
buildIncrementPrepare,
|
|
402
|
-
decryptArtifact
|
|
403
|
-
} from "@toon-protocol/client";
|
|
404
|
-
async function payIncrementOffer(client, providerDestination, offer) {
|
|
405
|
-
const { executionCondition, amount } = buildIncrementPrepare(offer);
|
|
406
|
-
const result = await client.sendSwapPacket({
|
|
407
|
-
destination: providerDestination,
|
|
408
|
-
amount,
|
|
409
|
-
toonData: new TextEncoder().encode(offer.offerEventId),
|
|
410
|
-
executionCondition
|
|
411
|
-
});
|
|
412
|
-
if (!result.accepted || !result.fulfillment) {
|
|
413
|
-
return { paid: false };
|
|
414
|
-
}
|
|
415
|
-
return { paid: true, key: Buffer.from(result.fulfillment, "base64") };
|
|
416
|
-
}
|
|
417
|
-
function decryptIncrementArtifact(ciphertext, key, offerConditionHex) {
|
|
418
|
-
return decryptArtifact(
|
|
419
|
-
ciphertext,
|
|
420
|
-
key,
|
|
421
|
-
Buffer.from(offerConditionHex, "hex")
|
|
422
|
-
);
|
|
423
|
-
}
|
|
424
328
|
export {
|
|
425
329
|
COMMENT_KIND,
|
|
426
|
-
ClientJobDeliveryPort,
|
|
427
330
|
DEFAULT_CONCURRENCY,
|
|
428
331
|
FACTORY_JOB_FEEDBACK_KIND,
|
|
429
332
|
FACTORY_JOB_REQUEST_KIND,
|
|
@@ -453,7 +356,6 @@ export {
|
|
|
453
356
|
createGitCommit,
|
|
454
357
|
createGitTag,
|
|
455
358
|
createGitTree,
|
|
456
|
-
decryptIncrementArtifact,
|
|
457
359
|
downloadGitObjects,
|
|
458
360
|
executeFactoryJob,
|
|
459
361
|
executePush,
|
|
@@ -468,7 +370,6 @@ export {
|
|
|
468
370
|
ownerToHex,
|
|
469
371
|
parseFactoryJobRequest,
|
|
470
372
|
parseMaintainers,
|
|
471
|
-
payIncrementOffer,
|
|
472
373
|
planFactoryJob,
|
|
473
374
|
planPush,
|
|
474
375
|
queryRelay,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/factory-job-gate.ts","../src/factory-job-events.ts","../src/factory-job-plan.ts","../src/factory-job-execute.ts","../src/factory-job-delivery-client.ts","../src/factory-job-pay.ts"],"sourcesContent":["/**\n * Factory job protocol — reproducible gate result per increment (#53).\n *\n * Decision 5 of toon-meta#262 splits trust into three layers: delivery (the\n * hashlock), the objective floor (this — \"did this increment pass the\n * repo's own gate?\"), and quality (reputation, out of scope here). Per\n * `FACTORY.md`, the pinned pipeline's gate is lint/typecheck/test/build run\n * before a PR is opened; this module is the pure, network-free shape for\n * recording exactly what ran, not a summary verdict.\n *\n * This proves conformance, not fitness for purpose — a gate-passing\n * increment can still completely miss the brief. Reputation is what\n * distinguishes those, never this.\n */\n\n/** One command the gate ran, and whether it passed. */\nexport interface GateCheck {\n /** e.g. \"lint\", \"typecheck\", \"test\", \"build\" — whatever actually ran,\n * never a canonical list (gates differ per repo and drift over time). */\n name: string;\n /** The literal command, e.g. \"eslint .\", so a buyer can reproduce it verbatim. */\n command: string;\n pass: boolean;\n}\n\n/**\n * A reproducible gate result: enough for a buyer to re-run the gate\n * themselves and get the same answer. Omitting the commit or the command\n * set turns this from a reproducible result into a provider's unverifiable\n * claim of having done well — both are required, not advisory.\n */\nexport interface GateResult {\n /** The commit this gate ran against. */\n commit: string;\n /** Toolchain versions relevant to reproducing the result, e.g. `{ node: \"20.11.0\", pnpm: \"9.1.0\" }`. */\n toolchain: Record<string, string>;\n checks: GateCheck[];\n}\n\n/** Overall pass/fail — every recorded check passed. A failing gate is not a protocol violation (it is still published, just visibly failing) — this is only the aggregate the `gate` tag and reputation's gate-pass rate read. */\nexport function gatePassed(gate: GateResult): boolean {\n return gate.checks.every((check) => check.pass);\n}\n","/**\n * Factory job protocol — pure NIP-90 event builders/parser (#52).\n *\n * Wire format per toon-meta's `docs/factory-job-protocol.md`\n * (toon-meta#263), fixed 2026-08-03: kind:5097 job request, kind:6097 job\n * result, kind:7000 job feedback (quote / increment offer / narration,\n * disambiguated by the `status` tag). Mirrors nip34-events.ts: every builder\n * returns an UnsignedEvent — the caller signs and publishes (e.g. through a\n * Publisher, publisher.ts) elsewhere. No crypto, no network, no payment.\n */\n\nimport type { UnsignedEvent } from './nip34-events.js';\nimport type { IncrementSpec } from './factory-job-plan.js';\nimport { gatePassed, type GateResult } from './factory-job-gate.js';\n\n// ---------------------------------------------------------------------------\n// Kinds\n// ---------------------------------------------------------------------------\n\n/** Factory job request (the brief). */\nexport const FACTORY_JOB_REQUEST_KIND = 5097;\n/** Factory job result — terminal state (completed/abandoned-*). */\nexport const FACTORY_JOB_RESULT_KIND = 6097;\n/** Factory job feedback — quote, increment offer, or free narration. */\nexport const FACTORY_JOB_FEEDBACK_KIND = 7000;\n\n/** The three §5.2 terminal outcomes — decision 8 has no fourth. */\nexport type FactoryJobOutcome =\n | 'completed'\n | 'abandoned-provider'\n | 'abandoned-buyer';\n\n// ---------------------------------------------------------------------------\n// kind:5097 — job request\n// ---------------------------------------------------------------------------\n\n/** A parsed kind:5097 job request (§2). */\nexport interface FactoryJobRequest {\n /** The requesting kind:5097 event's id. */\n requestEventId: string;\n /** The requesting kind:5097 event's pubkey (who to reply/bill). */\n buyerPubkey: string;\n /** The brief — free text or a chained milestone reference (§2.3). */\n brief: string;\n /** Maximum the buyer will pay across the whole job (micro-USDC, §2.1). */\n bidMicroUsdc: string;\n /** `param repo` — target repo, e.g. `toon-protocol/buzz`. */\n repo?: string;\n /** `param target` — the ticket/issue this job resolves. */\n target?: string;\n /** `param constraints` — gate requirements / scope limits. */\n constraints?: string;\n /** `output` — expected deliverable MIME type. */\n outputMime?: string;\n}\n\n/** Minimal shape of a relay event this module reads/builds — no signing. */\nexport interface RelayEvent {\n id: string;\n pubkey: string;\n tags: string[][];\n content: string;\n}\n\n/**\n * Parse a kind:5097 job request event's tags into a {@link FactoryJobRequest}.\n * Throws if the required `i` (brief) or `bid` tag is missing — both are\n * REQUIRED per §2.1.\n */\nexport function parseFactoryJobRequest(event: RelayEvent): FactoryJobRequest {\n const briefTag = event.tags.find((t) => t[0] === 'i' && t[2] === 'text');\n if (!briefTag?.[1]) {\n throw new Error(\n `kind:${FACTORY_JOB_REQUEST_KIND} event ${event.id} is missing its required brief (\"i\", \"<brief>\", \"text\") tag`\n );\n }\n const bidTag = event.tags.find((t) => t[0] === 'bid');\n if (!bidTag?.[1]) {\n throw new Error(\n `kind:${FACTORY_JOB_REQUEST_KIND} event ${event.id} is missing its required \"bid\" tag`\n );\n }\n\n const params = event.tags.filter((t) => t[0] === 'param');\n const paramValue = (name: string): string | undefined =>\n params.find((t) => t[1] === name)?.[2];\n\n const repo = paramValue('repo');\n const target = paramValue('target');\n const constraints = paramValue('constraints');\n const outputMime = event.tags.find((t) => t[0] === 'output')?.[1];\n\n return {\n requestEventId: event.id,\n buyerPubkey: event.pubkey,\n brief: briefTag[1],\n bidMicroUsdc: bidTag[1],\n ...(repo !== undefined ? { repo } : {}),\n ...(target !== undefined ? { target } : {}),\n ...(constraints !== undefined ? { constraints } : {}),\n ...(outputMime !== undefined ? { outputMime } : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:7000 status:\"quote\" — the RFQ reply (§3)\n// ---------------------------------------------------------------------------\n\n/**\n * Build the `kind:7000 status:\"quote\"` reply: the proposed increment\n * schedule, committing nothing (§3.3 — there is no accept message; payment\n * of increment 1 is acceptance).\n */\nexport function buildQuoteEvent(\n job: FactoryJobRequest,\n schedule: IncrementSpec[]\n): UnsignedEvent {\n return {\n kind: FACTORY_JOB_FEEDBACK_KIND,\n content: JSON.stringify({\n increments: schedule.map((s) => ({\n n: s.n,\n of: s.of,\n milestone: s.milestone,\n priceUsdc: s.priceUsdc,\n })),\n }),\n tags: [\n ['e', job.requestEventId, '', 'root'],\n ['p', job.buyerPubkey],\n ['status', 'quote'],\n ],\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:7000 status:\"partial\" — the increment offer (§4)\n// ---------------------------------------------------------------------------\n\n/** Where the encrypted increment artifact lives, and its hashlock condition. */\nexport interface EncryptedArtifactRef {\n /** Arweave tx id the ciphertext was uploaded to. */\n arweaveTxId: string;\n /** sha256 of the ciphertext, hex — lets the buyer detect a bad fetch. */\n ciphertextSha256: string;\n /** `sha256(key)` hex, where `key` decrypts the artifact (§4.2 — the join). */\n conditionHex: string;\n}\n\nexport interface BuildIncrementOfferOptions {\n job: FactoryJobRequest;\n /** The quote event's id for increment 1, else the previous offer's id. */\n parentEventId: string;\n increment: IncrementSpec;\n artifact: EncryptedArtifactRef;\n /**\n * The reproducible gate result for this increment's work (#53 — the\n * objective floor), when one ran. Omitted for milestones with nothing to\n * gate (e.g. `plan` — there is no code to lint against a brief).\n */\n gate?: GateResult;\n}\n\n/** A gate result missing its commit or check list cannot be reproduced by the buyer — reject it here rather than publish an unverifiable claim. */\nfunction assertReproducible(gate: GateResult): void {\n if (gate.checks.length === 0) {\n throw new Error(\n 'gate result must record at least one check for the offer to be reproducible'\n );\n }\n if (!gate.commit) {\n throw new Error(\n 'gate result must record the commit it ran against for the offer to be reproducible'\n );\n }\n}\n\n/**\n * Build the `kind:7000 status:\"partial\"` increment offer — published once\n * the provider has done that increment's work and uploaded the encrypted\n * artifact. This is the join between the relay plane and the connector\n * plane (§4.2): `condition` here MUST equal the paying PREPARE's\n * `executionCondition`, byte for byte.\n *\n * When `gate` is supplied, the offer also carries a `[\"gate\", \"pass\"|\"fail\"]`\n * tag (cheap to scan for a gate-pass rate, decision 8) and the full\n * reproducible result in `content` — visible to the buyer before they pay,\n * per the issue: a gate-failing increment is still offered, never hidden.\n */\nexport function buildIncrementOfferEvent(\n options: BuildIncrementOfferOptions\n): UnsignedEvent {\n const { job, parentEventId, increment, artifact, gate } = options;\n if (gate) assertReproducible(gate);\n\n const tags: string[][] = [\n ['e', job.requestEventId, '', 'root'],\n ['e', parentEventId, '', 'reply'],\n ['p', job.buyerPubkey],\n ['status', 'partial'],\n ['increment', String(increment.n), String(increment.of)],\n ['i', artifact.arweaveTxId, 'url'],\n ['i', artifact.ciphertextSha256, 'text', '', 'hash'],\n ['amount', increment.priceUsdc, 'usdc'],\n ['condition', artifact.conditionHex],\n ];\n if (gate) tags.push(['gate', gatePassed(gate) ? 'pass' : 'fail']);\n\n return {\n kind: FACTORY_JOB_FEEDBACK_KIND,\n content: gate ? JSON.stringify({ gate }) : '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:7000 status:\"processing\" — free narration (§6)\n// ---------------------------------------------------------------------------\n\nexport interface BuildNarrationOptions {\n job: FactoryJobRequest;\n /** The prior event in the thread (quote, offer, or narration). */\n parentEventId: string;\n message: string;\n}\n\n/**\n * Build a free narration event — no artifact, no `amount`/`condition`, MUST\n * NOT be paid against (§6). This is what makes a provider look alive between\n * increments.\n */\nexport function buildNarrationEvent(\n options: BuildNarrationOptions\n): UnsignedEvent {\n const { job, parentEventId, message } = options;\n return {\n kind: FACTORY_JOB_FEEDBACK_KIND,\n content: message,\n tags: [\n ['e', job.requestEventId, '', 'root'],\n ['e', parentEventId, '', 'reply'],\n ['p', job.buyerPubkey],\n ['status', 'processing'],\n ],\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:6097 — job result (§5)\n// ---------------------------------------------------------------------------\n\nexport interface BuildResultEventOptions {\n job: FactoryJobRequest;\n /** The original kind:5097 request event, verbatim (the `request` tag). */\n requestEvent: RelayEvent;\n /** The last kind:7000 event in the thread (offer or narration). */\n lastEventId: string;\n outcome: FactoryJobOutcome;\n /** How far the job got — `n-reached == of` only when outcome is completed. */\n reachedIncrement: number;\n totalIncrements: number;\n /** Required (and only meaningful) when outcome is `\"completed\"` (§5.2). */\n finalArtifact?: { arweaveTxId: string };\n}\n\n/**\n * Build the terminal `kind:6097` job result. Reputation (decision 8) is\n * computed from exactly this event and nothing else.\n */\nexport function buildResultEvent(\n options: BuildResultEventOptions\n): UnsignedEvent {\n const {\n job,\n requestEvent,\n lastEventId,\n outcome,\n reachedIncrement,\n totalIncrements,\n finalArtifact,\n } = options;\n\n const tags: string[][] = [\n ['e', job.requestEventId, '', 'root'],\n ['e', lastEventId, '', 'reply'],\n ['p', job.buyerPubkey],\n ['request', JSON.stringify(requestEvent)],\n ['outcome', outcome],\n ['increment', String(reachedIncrement), String(totalIncrements)],\n ];\n if (outcome === 'completed' && finalArtifact) {\n tags.push(['i', finalArtifact.arweaveTxId, 'url']);\n }\n\n return {\n kind: FACTORY_JOB_RESULT_KIND,\n content: '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n","/**\n * Factory job protocol — increment schedule planner (#52).\n *\n * `planFactoryJob` is the network-free half of the adapter (mirrors\n * `planPush`'s split from `executePush` in push.ts): given a job's `bid` and\n * the ticket list the factory's plan phase produced, it prices the\n * milestone boundaries decision 6 of toon-meta#262 fixed as increments —\n * plan → implement ×N tickets → review — and returns the schedule that\n * becomes the `kind:7000 status:\"quote\"` content (§3.2 of\n * `docs/factory-job-protocol.md`).\n *\n * Per the issue gotcha, `implement` must be many increments, not one: this\n * always emits one increment per ticket rather than a single lump-sum\n * implement increment, using the factory's existing parallel per-issue\n * fan-out (`.sandcastle/main.ts`'s Plan phase output — `{issues: [{id,\n * title, branch}]}`) as the boundary.\n */\n\n/** One ticket the factory's plan phase fanned the implement milestone into. */\nexport interface FactoryTicket {\n id: string;\n title: string;\n branch: string;\n}\n\nexport type FactoryMilestone = 'plan' | 'implement' | 'review';\n\n/** One priced increment in the schedule (§3.2's `increments[]` shape). */\nexport interface IncrementSpec {\n n: number;\n of: number;\n milestone: FactoryMilestone;\n /** Set only for `milestone: \"implement\"` increments. */\n ticket?: FactoryTicket;\n priceUsdc: string;\n}\n\n/** Plan milestone's share of the bid, in basis points. */\nconst PLAN_SHARE_BPS = 1000n; // 10%\n/** Review milestone's share of the bid, in basis points. */\nconst REVIEW_SHARE_BPS = 1000n; // 10%\nconst BPS_DENOM = 10000n;\n\n/**\n * Build the increment schedule for a job, given the tickets its plan phase\n * produced. Plan and review each get a fixed 10% of the bid; the remaining\n * 80% (\"implement\") splits evenly across tickets, with the integer-division\n * remainder distributed one micro-USDC at a time to the earliest tickets —\n * so `sum(priceUsdc) === bid` exactly (§3.2 only requires `<=`, but an exact\n * match avoids \"where did the leftover micro-USDC go\" questions).\n *\n * Per decision 7, this schedule is agreed before any paid work starts and is\n * never repriced — a job that runs larger than `tickets` implied is a\n * lesson, not a renegotiation.\n *\n * Throws if `tickets` is empty (a job with no implementation work has no\n * increments to schedule) or `bidMicroUsdc` is not a positive integer.\n */\nexport function planFactoryJob(\n bidMicroUsdc: string,\n tickets: FactoryTicket[]\n): IncrementSpec[] {\n if (tickets.length === 0) {\n throw new Error(\n 'planFactoryJob requires at least one implementation ticket from the plan phase'\n );\n }\n\n let bid: bigint;\n try {\n bid = BigInt(bidMicroUsdc);\n } catch {\n throw new Error(`bidMicroUsdc must be an integer string, got ${bidMicroUsdc}`);\n }\n if (bid <= 0n) {\n throw new Error(`bidMicroUsdc must be positive, got ${bidMicroUsdc}`);\n }\n\n const planPrice = (bid * PLAN_SHARE_BPS) / BPS_DENOM;\n const reviewPrice = (bid * REVIEW_SHARE_BPS) / BPS_DENOM;\n const implementTotal = bid - planPrice - reviewPrice;\n\n const ticketCount = BigInt(tickets.length);\n const base = implementTotal / ticketCount;\n const remainder = implementTotal % ticketCount;\n\n const total = tickets.length + 2;\n const schedule: IncrementSpec[] = [\n { n: 1, of: total, milestone: 'plan', priceUsdc: planPrice.toString() },\n ];\n\n tickets.forEach((ticket, i) => {\n const price = base + (BigInt(i) < remainder ? 1n : 0n);\n schedule.push({\n n: i + 2,\n of: total,\n milestone: 'implement',\n ticket,\n priceUsdc: price.toString(),\n });\n });\n\n schedule.push({\n n: total,\n of: total,\n milestone: 'review',\n priceUsdc: reviewPrice.toString(),\n });\n\n return schedule;\n}\n","/**\n * executeFactoryJob — the adapter's imperative shell (#52): runs the\n * factory's own milestones (`.sandcastle/main.ts`'s plan → per-ticket\n * implement+review → merge phases) through `hooks`, and turns each\n * milestone boundary into a paid increment via `publisher` (existing,\n * publisher.ts — upload + event publish) and `delivery` (new,\n * factory-job-delivery.ts — encrypt + payment wait).\n *\n * Mirrors `executePush`'s split from `planPush`: `planFactoryJob` is the\n * network-free planning half; this is the half that spends money.\n *\n * Decision 6 (toon-meta#262): work happens BEFORE the packet, so every\n * increment's artifact is produced and uploaded before that increment is\n * offered for payment. Decision 7: the schedule is fixed once the quote is\n * published — this never reprices mid-job. Per the issue: \"Stop on\n * non-payment. One unpaid increment and work halts\" — the symmetric\n * protection that neither side is ever exposed for more than one increment.\n */\n\nimport type { Publisher } from './publisher.js';\nimport type { JobDeliveryPort } from './factory-job-delivery.js';\nimport {\n buildIncrementOfferEvent,\n buildQuoteEvent,\n buildResultEvent,\n type FactoryJobOutcome,\n type FactoryJobRequest,\n type RelayEvent,\n} from './factory-job-events.js';\nimport type { GateResult } from './factory-job-gate.js';\nimport {\n planFactoryJob,\n type FactoryTicket,\n type IncrementSpec,\n} from './factory-job-plan.js';\n\n/** One milestone's deliverable, plus the gate result that ran against it, when one did. */\nexport interface FactoryJobWork {\n artifact: Uint8Array;\n /**\n * The reproducible gate result (#53) for this milestone's work, per\n * `FACTORY.md`'s pinned pipeline (lint/typecheck/test/build). Omitted for\n * `plan` — there is no code to gate against a brief.\n */\n gate?: GateResult;\n}\n\n/** Runs one factory milestone and returns its deliverable, plus that milestone's gate result. */\nexport interface FactoryJobHooks {\n /**\n * Runs the factory's plan phase for `job` (mirrors `.sandcastle/main.ts`'s\n * Phase 1): returns the per-ticket fan-out the implement milestone splits\n * into, plus the plan document itself as increment 1's artifact. No gate\n * result — planning produces no code to lint/typecheck/test/build.\n */\n plan(\n job: FactoryJobRequest\n ): Promise<{ tickets: FactoryTicket[]; artifact: Uint8Array }>;\n /**\n * Runs one ticket's implement sandbox pipeline (mirrors Phase 2's\n * per-issue `sandbox.run`, which ends in the gate per `FACTORY.md`) and\n * returns its deliverable (e.g. the ticket's diff) plus that run's gate\n * result.\n */\n implement(ticket: FactoryTicket): Promise<FactoryJobWork>;\n /**\n * Runs the factory's merge/review phase over every completed ticket\n * (mirrors Phase 3, which re-runs the gate per `merge-prompt.md`) and\n * returns the final deliverable plus that run's gate result.\n */\n review(tickets: FactoryTicket[]): Promise<FactoryJobWork>;\n}\n\nexport interface ExecuteFactoryJobOptions {\n job: FactoryJobRequest;\n /** The original kind:5097 request event, verbatim — carried in the result's `request` tag. */\n requestEvent: RelayEvent;\n hooks: FactoryJobHooks;\n publisher: Publisher;\n delivery: JobDeliveryPort;\n relayUrls: string[];\n}\n\nexport interface FactoryJobExecution {\n outcome: FactoryJobOutcome;\n /** The last increment successfully paid for (0 if the buyer never paid increment 1). */\n reachedIncrement: number;\n totalIncrements: number;\n /** The published kind:6097 result event's id. */\n resultEventId: string;\n}\n\nasync function workFor(\n spec: IncrementSpec,\n tickets: FactoryTicket[],\n planArtifact: Uint8Array,\n hooks: FactoryJobHooks\n): Promise<FactoryJobWork> {\n if (spec.milestone === 'plan') return { artifact: planArtifact };\n if (spec.milestone === 'implement') {\n if (!spec.ticket) {\n throw new Error(\n `internal: implement increment ${spec.n} is missing its ticket`\n );\n }\n return hooks.implement(spec.ticket);\n }\n return hooks.review(tickets);\n}\n\n/**\n * Run a job end to end: plan → quote → one increment per milestone boundary\n * (encrypt, upload, offer, wait for payment) → terminal kind:6097 result.\n * Halts at the first unpaid increment (`abandoned-buyer`) or the first\n * hook failure once work is underway (`abandoned-provider`) — see §5.2 of\n * `docs/factory-job-protocol.md` for the three terminal outcomes.\n *\n * Throws if `hooks.plan` itself fails — with no quote ever published, there\n * is no prior kind:7000 event for a result's `reply` e-tag to reference, so\n * no protocol event can be honestly emitted for that failure.\n */\nexport async function executeFactoryJob(\n options: ExecuteFactoryJobOptions\n): Promise<FactoryJobExecution> {\n const { job, requestEvent, hooks, publisher, delivery, relayUrls } =\n options;\n\n if (!publisher.uploadBlob) {\n throw new Error(\n 'the active publisher cannot upload an encrypted increment artifact ' +\n '(uploadBlob unavailable) — executeFactoryJob requires the standalone publisher'\n );\n }\n const uploadBlob = publisher.uploadBlob.bind(publisher);\n\n const { tickets, artifact: planArtifact } = await hooks.plan(job);\n const schedule = planFactoryJob(job.bidMicroUsdc, tickets);\n const totalIncrements = schedule.length;\n\n const quoteEvent = buildQuoteEvent(job, schedule);\n const quoteReceipt = await publisher.publishEvent(quoteEvent, relayUrls);\n\n let parentEventId = quoteReceipt.eventId;\n let reachedIncrement = 0;\n let finalArtifactTxId: string | undefined;\n\n const haltWith = async (\n outcome: FactoryJobOutcome\n ): Promise<FactoryJobExecution> => {\n const resultEvent = buildResultEvent({\n job,\n requestEvent,\n lastEventId: parentEventId,\n outcome,\n reachedIncrement,\n totalIncrements,\n ...(outcome === 'completed' && finalArtifactTxId\n ? { finalArtifact: { arweaveTxId: finalArtifactTxId } }\n : {}),\n });\n const resultReceipt = await publisher.publishEvent(\n resultEvent,\n relayUrls\n );\n return {\n outcome,\n reachedIncrement,\n totalIncrements,\n resultEventId: resultReceipt.eventId,\n };\n };\n\n for (const spec of schedule) {\n let work: FactoryJobWork;\n try {\n work = await workFor(spec, tickets, planArtifact, hooks);\n } catch {\n // Provider-side failure mid-job: stop where we are, no renegotiation.\n return haltWith('abandoned-provider');\n }\n\n const encrypted = await delivery.encryptArtifact(work.artifact);\n const uploadReceipt = await uploadBlob({\n body: Buffer.from(encrypted.ciphertext),\n contentType: 'application/octet-stream',\n ...(job.repo ? { repoId: job.repo } : {}),\n });\n\n const offerEvent = buildIncrementOfferEvent({\n job,\n parentEventId,\n increment: spec,\n artifact: {\n arweaveTxId: uploadReceipt.txId,\n ciphertextSha256: encrypted.ciphertextSha256,\n conditionHex: encrypted.conditionHex,\n },\n ...(work.gate ? { gate: work.gate } : {}),\n });\n const offerReceipt = await publisher.publishEvent(offerEvent, relayUrls);\n\n const paid = await delivery.waitForPayment({\n offerEventId: offerReceipt.eventId,\n conditionHex: encrypted.conditionHex,\n priceUsdc: spec.priceUsdc,\n });\n if (!paid) {\n parentEventId = offerReceipt.eventId;\n return haltWith('abandoned-buyer');\n }\n\n reachedIncrement = spec.n;\n parentEventId = offerReceipt.eventId;\n if (spec.milestone === 'review') finalArtifactTxId = uploadReceipt.txId;\n }\n\n return haltWith('completed');\n}\n","/**\n * ClientJobDeliveryPort — the concrete `JobDeliveryPort` (#52, #56) backed\n * by the real, published `@toon-protocol/client` (>=0.26.0) hashlock\n * helpers (toon-client#495) and serve-side job handling (toon-client#494).\n *\n * Per the issue's explicit instruction, this never re-derives the\n * encrypt/condition relationship locally: `encryptArtifact` is the only\n * place a key or condition is minted, and `fulfillIncrement` is the only\n * place a key becomes an ILP fulfillment.\n *\n * Wiring: pass `handleJob` as `ToonClientConfig.jobHandler` for the SAME\n * identity that runs `executeFactoryJob` (job serving rides the provider's\n * own BTP session, per that field's doc). The connector delivers the\n * buyer's paying PREPARE as a server-originated BTP MESSAGE addressed to\n * this client's own ILP address; `handleJob` reveals the key minted by the\n * immediately-preceding `encryptArtifact` call as the fulfillment — which\n * is simultaneously the payment settling and the decrypt-key handoff\n * (docs/factory-job-protocol.md §4.2, toon-meta).\n *\n * One increment in flight at a time, matching `JobDeliveryPort`'s own\n * sequential-use contract: `encryptArtifact` stages a key, and only\n * `waitForPayment` for that SAME condition arms `handleJob` to spend it. A\n * job PREPARE for any other condition — stray, replayed, or for an\n * increment this port never staged — is refused rather than fulfilled.\n */\n\nimport { createHash } from 'node:crypto';\nimport {\n encryptArtifact as clientEncryptArtifact,\n fulfillIncrement,\n type JobAnswer,\n type JobHandler,\n type JobRequest,\n} from '@toon-protocol/client';\nimport type {\n EncryptedArtifact,\n JobDeliveryPort,\n OfferedIncrement,\n} from './factory-job-delivery.js';\n\nfunction toHex(bytes: Uint8Array): string {\n return Buffer.from(bytes).toString('hex');\n}\n\ninterface ArmedIncrement {\n key: Uint8Array;\n conditionHex: string;\n resolve: (paid: boolean) => void;\n}\n\nexport interface ClientJobDeliveryPortOptions {\n /** How long to wait for the buyer's paying PREPARE before giving up on an increment. Default 120s. */\n paymentTimeoutMs?: number;\n}\n\nconst DEFAULT_PAYMENT_TIMEOUT_MS = 120_000;\n\nexport class ClientJobDeliveryPort implements JobDeliveryPort {\n private readonly paymentTimeoutMs: number;\n private staged?: { key: Uint8Array; conditionHex: string };\n private armed?: ArmedIncrement;\n\n constructor(options: ClientJobDeliveryPortOptions = {}) {\n this.paymentTimeoutMs =\n options.paymentTimeoutMs ?? DEFAULT_PAYMENT_TIMEOUT_MS;\n }\n\n /**\n * The `JobHandler` to register as `ToonClientConfig.jobHandler`. Throws\n * (answered F99 by `createJobMessageHandler`) when no increment is armed\n * for the PREPARE's `executionCondition` — this is the only path that\n * releases a key, and it releases exactly the one the buyer paid for.\n */\n readonly handleJob: JobHandler = (job: JobRequest): JobAnswer => {\n const conditionHex = toHex(job.executionCondition);\n if (!this.armed || conditionHex !== this.armed.conditionHex) {\n throw new Error(\n `no factory-job increment is awaiting payment for condition ${conditionHex}`\n );\n }\n const { key, resolve } = this.armed;\n this.armed = undefined;\n resolve(true);\n return { fulfillment: fulfillIncrement(key) };\n };\n\n async encryptArtifact(bytes: Uint8Array): Promise<EncryptedArtifact> {\n const encrypted = clientEncryptArtifact(bytes);\n const conditionHex = toHex(encrypted.condition);\n this.staged = { key: encrypted.key, conditionHex };\n return {\n ciphertext: encrypted.ciphertext,\n ciphertextSha256: createHash('sha256')\n .update(encrypted.ciphertext)\n .digest('hex'),\n conditionHex,\n };\n }\n\n async waitForPayment(offer: OfferedIncrement): Promise<boolean> {\n if (!this.staged || offer.conditionHex !== this.staged.conditionHex) {\n throw new Error(\n 'waitForPayment was called for a condition that does not match the most recent encryptArtifact() call'\n );\n }\n const { key, conditionHex } = this.staged;\n this.staged = undefined;\n\n return new Promise<boolean>((resolve) => {\n const timer = setTimeout(() => {\n this.armed = undefined;\n resolve(false);\n }, this.paymentTimeoutMs);\n this.armed = {\n key,\n conditionHex,\n resolve: (paid) => {\n clearTimeout(timer);\n resolve(paid);\n },\n };\n });\n }\n}\n","/**\n * Buyer-side payment for one factory-job increment offer (#56) — the other\n * half of the hashlock join `ClientJobDeliveryPort` (the provider side)\n * answers. Built against `@toon-protocol/client`'s real `buildIncrementPrepare`\n * / `decryptArtifact` (toon-client#495): this module does not re-derive the\n * PREPARE fields or the AEAD decrypt itself.\n *\n * Per docs/factory-job-protocol.md §4.2 (toon-meta), paying an offer is:\n * `executionCondition` equal to the offer's `condition` tag byte for byte,\n * and `data` carrying a reference back to the offer's own kind:7000 event id\n * — \"the payment names the job.\" The provider returns the decryption key as\n * the ILP fulfillment; revealing it is the same act as accepting payment.\n */\n\nimport {\n buildIncrementPrepare,\n decryptArtifact,\n type IncrementOfferTags,\n} from '@toon-protocol/client';\n\n/** What a buyer reads off an advertised kind:7000 `status:\"partial\"` increment offer. */\nexport interface PayableIncrementOffer extends IncrementOfferTags {\n /** The offer event's own id — carried in the PREPARE's `data` field (§4.2). */\n offerEventId: string;\n}\n\n/** The outcome of paying one increment offer. */\nexport type PaidIncrementOffer =\n | { paid: true; key: Uint8Array }\n | { paid: false };\n\n/** The narrow slice of `ToonClient` a buyer needs to pay an increment offer. */\nexport interface SwapPacketSender {\n sendSwapPacket(params: {\n destination: string;\n amount: bigint;\n toonData: Uint8Array;\n executionCondition?: Uint8Array;\n timeout?: number;\n }): Promise<{\n accepted: boolean;\n fulfillment?: string;\n code?: string;\n message?: string;\n }>;\n}\n\n/**\n * Buyer: pay one advertised increment offer. Sends an ILP PREPARE addressed\n * to `providerDestination` with `executionCondition` equal to the offer's\n * `condition` tag and `data` referencing the offer's event id, per §4.2.\n * Resolves `{ paid: false }` on a REJECT (expired/no such increment/wrong\n * fulfillment) rather than throwing — a buyer declining or failing to pay is\n * a protocol outcome (`abandoned-buyer`), not an error.\n */\nexport async function payIncrementOffer(\n client: SwapPacketSender,\n providerDestination: string,\n offer: PayableIncrementOffer\n): Promise<PaidIncrementOffer> {\n const { executionCondition, amount } = buildIncrementPrepare(offer);\n const result = await client.sendSwapPacket({\n destination: providerDestination,\n amount,\n toonData: new TextEncoder().encode(offer.offerEventId),\n executionCondition,\n });\n if (!result.accepted || !result.fulfillment) {\n return { paid: false };\n }\n return { paid: true, key: Buffer.from(result.fulfillment, 'base64') };\n}\n\n/**\n * Buyer: decrypt an increment's artifact using the key revealed as the ILP\n * fulfillment. `offerConditionHex` MUST be the offer's own `condition` tag\n * as read from the relay before payment — never re-derived from `key` —\n * so a provider who reveals a different key than it advertised is caught\n * here rather than trusted.\n */\nexport function decryptIncrementArtifact(\n ciphertext: Uint8Array,\n key: Uint8Array,\n offerConditionHex: string\n): Uint8Array {\n return decryptArtifact(\n ciphertext,\n key,\n Buffer.from(offerConditionHex, 'hex')\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/factory-job-gate.ts","../src/factory-job-events.ts","../src/factory-job-plan.ts","../src/factory-job-execute.ts"],"sourcesContent":["/**\n * Factory job protocol — reproducible gate result per increment (#53).\n *\n * Decision 5 of toon-meta#262 splits trust into three layers: delivery (the\n * hashlock), the objective floor (this — \"did this increment pass the\n * repo's own gate?\"), and quality (reputation, out of scope here). Per\n * `FACTORY.md`, the pinned pipeline's gate is lint/typecheck/test/build run\n * before a PR is opened; this module is the pure, network-free shape for\n * recording exactly what ran, not a summary verdict.\n *\n * This proves conformance, not fitness for purpose — a gate-passing\n * increment can still completely miss the brief. Reputation is what\n * distinguishes those, never this.\n */\n\n/** One command the gate ran, and whether it passed. */\nexport interface GateCheck {\n /** e.g. \"lint\", \"typecheck\", \"test\", \"build\" — whatever actually ran,\n * never a canonical list (gates differ per repo and drift over time). */\n name: string;\n /** The literal command, e.g. \"eslint .\", so a buyer can reproduce it verbatim. */\n command: string;\n pass: boolean;\n}\n\n/**\n * A reproducible gate result: enough for a buyer to re-run the gate\n * themselves and get the same answer. Omitting the commit or the command\n * set turns this from a reproducible result into a provider's unverifiable\n * claim of having done well — both are required, not advisory.\n */\nexport interface GateResult {\n /** The commit this gate ran against. */\n commit: string;\n /** Toolchain versions relevant to reproducing the result, e.g. `{ node: \"20.11.0\", pnpm: \"9.1.0\" }`. */\n toolchain: Record<string, string>;\n checks: GateCheck[];\n}\n\n/** Overall pass/fail — every recorded check passed. A failing gate is not a protocol violation (it is still published, just visibly failing) — this is only the aggregate the `gate` tag and reputation's gate-pass rate read. */\nexport function gatePassed(gate: GateResult): boolean {\n return gate.checks.every((check) => check.pass);\n}\n","/**\n * Factory job protocol — pure NIP-90 event builders/parser (#52).\n *\n * Wire format per toon-meta's `docs/factory-job-protocol.md`\n * (toon-meta#263), fixed 2026-08-03: kind:5097 job request, kind:6097 job\n * result, kind:7000 job feedback (quote / increment offer / narration,\n * disambiguated by the `status` tag). Mirrors nip34-events.ts: every builder\n * returns an UnsignedEvent — the caller signs and publishes (e.g. through a\n * Publisher, publisher.ts) elsewhere. No crypto, no network, no payment.\n */\n\nimport type { UnsignedEvent } from './nip34-events.js';\nimport type { IncrementSpec } from './factory-job-plan.js';\nimport { gatePassed, type GateResult } from './factory-job-gate.js';\n\n// ---------------------------------------------------------------------------\n// Kinds\n// ---------------------------------------------------------------------------\n\n/** Factory job request (the brief). */\nexport const FACTORY_JOB_REQUEST_KIND = 5097;\n/** Factory job result — terminal state (completed/abandoned-*). */\nexport const FACTORY_JOB_RESULT_KIND = 6097;\n/** Factory job feedback — quote, increment offer, or free narration. */\nexport const FACTORY_JOB_FEEDBACK_KIND = 7000;\n\n/** The three §5.2 terminal outcomes — decision 8 has no fourth. */\nexport type FactoryJobOutcome =\n | 'completed'\n | 'abandoned-provider'\n | 'abandoned-buyer';\n\n// ---------------------------------------------------------------------------\n// kind:5097 — job request\n// ---------------------------------------------------------------------------\n\n/** A parsed kind:5097 job request (§2). */\nexport interface FactoryJobRequest {\n /** The requesting kind:5097 event's id. */\n requestEventId: string;\n /** The requesting kind:5097 event's pubkey (who to reply/bill). */\n buyerPubkey: string;\n /** The brief — free text or a chained milestone reference (§2.3). */\n brief: string;\n /** Maximum the buyer will pay across the whole job (micro-USDC, §2.1). */\n bidMicroUsdc: string;\n /** `param repo` — target repo, e.g. `toon-protocol/buzz`. */\n repo?: string;\n /** `param target` — the ticket/issue this job resolves. */\n target?: string;\n /** `param constraints` — gate requirements / scope limits. */\n constraints?: string;\n /** `output` — expected deliverable MIME type. */\n outputMime?: string;\n}\n\n/** Minimal shape of a relay event this module reads/builds — no signing. */\nexport interface RelayEvent {\n id: string;\n pubkey: string;\n tags: string[][];\n content: string;\n}\n\n/**\n * Parse a kind:5097 job request event's tags into a {@link FactoryJobRequest}.\n * Throws if the required `i` (brief) or `bid` tag is missing — both are\n * REQUIRED per §2.1.\n */\nexport function parseFactoryJobRequest(event: RelayEvent): FactoryJobRequest {\n const briefTag = event.tags.find((t) => t[0] === 'i' && t[2] === 'text');\n if (!briefTag?.[1]) {\n throw new Error(\n `kind:${FACTORY_JOB_REQUEST_KIND} event ${event.id} is missing its required brief (\"i\", \"<brief>\", \"text\") tag`\n );\n }\n const bidTag = event.tags.find((t) => t[0] === 'bid');\n if (!bidTag?.[1]) {\n throw new Error(\n `kind:${FACTORY_JOB_REQUEST_KIND} event ${event.id} is missing its required \"bid\" tag`\n );\n }\n\n const params = event.tags.filter((t) => t[0] === 'param');\n const paramValue = (name: string): string | undefined =>\n params.find((t) => t[1] === name)?.[2];\n\n const repo = paramValue('repo');\n const target = paramValue('target');\n const constraints = paramValue('constraints');\n const outputMime = event.tags.find((t) => t[0] === 'output')?.[1];\n\n return {\n requestEventId: event.id,\n buyerPubkey: event.pubkey,\n brief: briefTag[1],\n bidMicroUsdc: bidTag[1],\n ...(repo !== undefined ? { repo } : {}),\n ...(target !== undefined ? { target } : {}),\n ...(constraints !== undefined ? { constraints } : {}),\n ...(outputMime !== undefined ? { outputMime } : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:7000 status:\"quote\" — the RFQ reply (§3)\n// ---------------------------------------------------------------------------\n\n/**\n * Build the `kind:7000 status:\"quote\"` reply: the proposed increment\n * schedule, committing nothing (§3.3 — there is no accept message; payment\n * of increment 1 is acceptance).\n */\nexport function buildQuoteEvent(\n job: FactoryJobRequest,\n schedule: IncrementSpec[]\n): UnsignedEvent {\n return {\n kind: FACTORY_JOB_FEEDBACK_KIND,\n content: JSON.stringify({\n increments: schedule.map((s) => ({\n n: s.n,\n of: s.of,\n milestone: s.milestone,\n priceUsdc: s.priceUsdc,\n })),\n }),\n tags: [\n ['e', job.requestEventId, '', 'root'],\n ['p', job.buyerPubkey],\n ['status', 'quote'],\n ],\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:7000 status:\"partial\" — the increment offer (§4)\n// ---------------------------------------------------------------------------\n\n/** Where the encrypted increment artifact lives, and its hashlock condition. */\nexport interface EncryptedArtifactRef {\n /** Arweave tx id the ciphertext was uploaded to. */\n arweaveTxId: string;\n /** sha256 of the ciphertext, hex — lets the buyer detect a bad fetch. */\n ciphertextSha256: string;\n /** `sha256(key)` hex, where `key` decrypts the artifact (§4.2 — the join). */\n conditionHex: string;\n}\n\nexport interface BuildIncrementOfferOptions {\n job: FactoryJobRequest;\n /** The quote event's id for increment 1, else the previous offer's id. */\n parentEventId: string;\n increment: IncrementSpec;\n artifact: EncryptedArtifactRef;\n /**\n * The reproducible gate result for this increment's work (#53 — the\n * objective floor), when one ran. Omitted for milestones with nothing to\n * gate (e.g. `plan` — there is no code to lint against a brief).\n */\n gate?: GateResult;\n}\n\n/** A gate result missing its commit or check list cannot be reproduced by the buyer — reject it here rather than publish an unverifiable claim. */\nfunction assertReproducible(gate: GateResult): void {\n if (gate.checks.length === 0) {\n throw new Error(\n 'gate result must record at least one check for the offer to be reproducible'\n );\n }\n if (!gate.commit) {\n throw new Error(\n 'gate result must record the commit it ran against for the offer to be reproducible'\n );\n }\n}\n\n/**\n * Build the `kind:7000 status:\"partial\"` increment offer — published once\n * the provider has done that increment's work and uploaded the encrypted\n * artifact. This is the join between the relay plane and the connector\n * plane (§4.2): `condition` here MUST equal the paying PREPARE's\n * `executionCondition`, byte for byte.\n *\n * When `gate` is supplied, the offer also carries a `[\"gate\", \"pass\"|\"fail\"]`\n * tag (cheap to scan for a gate-pass rate, decision 8) and the full\n * reproducible result in `content` — visible to the buyer before they pay,\n * per the issue: a gate-failing increment is still offered, never hidden.\n */\nexport function buildIncrementOfferEvent(\n options: BuildIncrementOfferOptions\n): UnsignedEvent {\n const { job, parentEventId, increment, artifact, gate } = options;\n if (gate) assertReproducible(gate);\n\n const tags: string[][] = [\n ['e', job.requestEventId, '', 'root'],\n ['e', parentEventId, '', 'reply'],\n ['p', job.buyerPubkey],\n ['status', 'partial'],\n ['increment', String(increment.n), String(increment.of)],\n ['i', artifact.arweaveTxId, 'url'],\n ['i', artifact.ciphertextSha256, 'text', '', 'hash'],\n ['amount', increment.priceUsdc, 'usdc'],\n ['condition', artifact.conditionHex],\n ];\n if (gate) tags.push(['gate', gatePassed(gate) ? 'pass' : 'fail']);\n\n return {\n kind: FACTORY_JOB_FEEDBACK_KIND,\n content: gate ? JSON.stringify({ gate }) : '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:7000 status:\"processing\" — free narration (§6)\n// ---------------------------------------------------------------------------\n\nexport interface BuildNarrationOptions {\n job: FactoryJobRequest;\n /** The prior event in the thread (quote, offer, or narration). */\n parentEventId: string;\n message: string;\n}\n\n/**\n * Build a free narration event — no artifact, no `amount`/`condition`, MUST\n * NOT be paid against (§6). This is what makes a provider look alive between\n * increments.\n */\nexport function buildNarrationEvent(\n options: BuildNarrationOptions\n): UnsignedEvent {\n const { job, parentEventId, message } = options;\n return {\n kind: FACTORY_JOB_FEEDBACK_KIND,\n content: message,\n tags: [\n ['e', job.requestEventId, '', 'root'],\n ['e', parentEventId, '', 'reply'],\n ['p', job.buyerPubkey],\n ['status', 'processing'],\n ],\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:6097 — job result (§5)\n// ---------------------------------------------------------------------------\n\nexport interface BuildResultEventOptions {\n job: FactoryJobRequest;\n /** The original kind:5097 request event, verbatim (the `request` tag). */\n requestEvent: RelayEvent;\n /** The last kind:7000 event in the thread (offer or narration). */\n lastEventId: string;\n outcome: FactoryJobOutcome;\n /** How far the job got — `n-reached == of` only when outcome is completed. */\n reachedIncrement: number;\n totalIncrements: number;\n /** Required (and only meaningful) when outcome is `\"completed\"` (§5.2). */\n finalArtifact?: { arweaveTxId: string };\n}\n\n/**\n * Build the terminal `kind:6097` job result. Reputation (decision 8) is\n * computed from exactly this event and nothing else.\n */\nexport function buildResultEvent(\n options: BuildResultEventOptions\n): UnsignedEvent {\n const {\n job,\n requestEvent,\n lastEventId,\n outcome,\n reachedIncrement,\n totalIncrements,\n finalArtifact,\n } = options;\n\n const tags: string[][] = [\n ['e', job.requestEventId, '', 'root'],\n ['e', lastEventId, '', 'reply'],\n ['p', job.buyerPubkey],\n ['request', JSON.stringify(requestEvent)],\n ['outcome', outcome],\n ['increment', String(reachedIncrement), String(totalIncrements)],\n ];\n if (outcome === 'completed' && finalArtifact) {\n tags.push(['i', finalArtifact.arweaveTxId, 'url']);\n }\n\n return {\n kind: FACTORY_JOB_RESULT_KIND,\n content: '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n","/**\n * Factory job protocol — increment schedule planner (#52).\n *\n * `planFactoryJob` is the network-free half of the adapter (mirrors\n * `planPush`'s split from `executePush` in push.ts): given a job's `bid` and\n * the ticket list the factory's plan phase produced, it prices the\n * milestone boundaries decision 6 of toon-meta#262 fixed as increments —\n * plan → implement ×N tickets → review — and returns the schedule that\n * becomes the `kind:7000 status:\"quote\"` content (§3.2 of\n * `docs/factory-job-protocol.md`).\n *\n * Per the issue gotcha, `implement` must be many increments, not one: this\n * always emits one increment per ticket rather than a single lump-sum\n * implement increment, using the factory's existing parallel per-issue\n * fan-out (`.sandcastle/main.ts`'s Plan phase output — `{issues: [{id,\n * title, branch}]}`) as the boundary.\n */\n\n/** One ticket the factory's plan phase fanned the implement milestone into. */\nexport interface FactoryTicket {\n id: string;\n title: string;\n branch: string;\n}\n\nexport type FactoryMilestone = 'plan' | 'implement' | 'review';\n\n/** One priced increment in the schedule (§3.2's `increments[]` shape). */\nexport interface IncrementSpec {\n n: number;\n of: number;\n milestone: FactoryMilestone;\n /** Set only for `milestone: \"implement\"` increments. */\n ticket?: FactoryTicket;\n priceUsdc: string;\n}\n\n/** Plan milestone's share of the bid, in basis points. */\nconst PLAN_SHARE_BPS = 1000n; // 10%\n/** Review milestone's share of the bid, in basis points. */\nconst REVIEW_SHARE_BPS = 1000n; // 10%\nconst BPS_DENOM = 10000n;\n\n/**\n * Build the increment schedule for a job, given the tickets its plan phase\n * produced. Plan and review each get a fixed 10% of the bid; the remaining\n * 80% (\"implement\") splits evenly across tickets, with the integer-division\n * remainder distributed one micro-USDC at a time to the earliest tickets —\n * so `sum(priceUsdc) === bid` exactly (§3.2 only requires `<=`, but an exact\n * match avoids \"where did the leftover micro-USDC go\" questions).\n *\n * Per decision 7, this schedule is agreed before any paid work starts and is\n * never repriced — a job that runs larger than `tickets` implied is a\n * lesson, not a renegotiation.\n *\n * Throws if `tickets` is empty (a job with no implementation work has no\n * increments to schedule) or `bidMicroUsdc` is not a positive integer.\n */\nexport function planFactoryJob(\n bidMicroUsdc: string,\n tickets: FactoryTicket[]\n): IncrementSpec[] {\n if (tickets.length === 0) {\n throw new Error(\n 'planFactoryJob requires at least one implementation ticket from the plan phase'\n );\n }\n\n let bid: bigint;\n try {\n bid = BigInt(bidMicroUsdc);\n } catch {\n throw new Error(`bidMicroUsdc must be an integer string, got ${bidMicroUsdc}`);\n }\n if (bid <= 0n) {\n throw new Error(`bidMicroUsdc must be positive, got ${bidMicroUsdc}`);\n }\n\n const planPrice = (bid * PLAN_SHARE_BPS) / BPS_DENOM;\n const reviewPrice = (bid * REVIEW_SHARE_BPS) / BPS_DENOM;\n const implementTotal = bid - planPrice - reviewPrice;\n\n const ticketCount = BigInt(tickets.length);\n const base = implementTotal / ticketCount;\n const remainder = implementTotal % ticketCount;\n\n const total = tickets.length + 2;\n const schedule: IncrementSpec[] = [\n { n: 1, of: total, milestone: 'plan', priceUsdc: planPrice.toString() },\n ];\n\n tickets.forEach((ticket, i) => {\n const price = base + (BigInt(i) < remainder ? 1n : 0n);\n schedule.push({\n n: i + 2,\n of: total,\n milestone: 'implement',\n ticket,\n priceUsdc: price.toString(),\n });\n });\n\n schedule.push({\n n: total,\n of: total,\n milestone: 'review',\n priceUsdc: reviewPrice.toString(),\n });\n\n return schedule;\n}\n","/**\n * executeFactoryJob — the adapter's imperative shell (#52): runs the\n * factory's own milestones (`.sandcastle/main.ts`'s plan → per-ticket\n * implement+review → merge phases) through `hooks`, and turns each\n * milestone boundary into a paid increment via `publisher` (existing,\n * publisher.ts — upload + event publish) and `delivery` (new,\n * factory-job-delivery.ts — encrypt + payment wait).\n *\n * Mirrors `executePush`'s split from `planPush`: `planFactoryJob` is the\n * network-free planning half; this is the half that spends money.\n *\n * Decision 6 (toon-meta#262): work happens BEFORE the packet, so every\n * increment's artifact is produced and uploaded before that increment is\n * offered for payment. Decision 7: the schedule is fixed once the quote is\n * published — this never reprices mid-job. Per the issue: \"Stop on\n * non-payment. One unpaid increment and work halts\" — the symmetric\n * protection that neither side is ever exposed for more than one increment.\n */\n\nimport type { Publisher } from './publisher.js';\nimport type { JobDeliveryPort } from './factory-job-delivery.js';\nimport {\n buildIncrementOfferEvent,\n buildQuoteEvent,\n buildResultEvent,\n type FactoryJobOutcome,\n type FactoryJobRequest,\n type RelayEvent,\n} from './factory-job-events.js';\nimport type { GateResult } from './factory-job-gate.js';\nimport {\n planFactoryJob,\n type FactoryTicket,\n type IncrementSpec,\n} from './factory-job-plan.js';\n\n/** One milestone's deliverable, plus the gate result that ran against it, when one did. */\nexport interface FactoryJobWork {\n artifact: Uint8Array;\n /**\n * The reproducible gate result (#53) for this milestone's work, per\n * `FACTORY.md`'s pinned pipeline (lint/typecheck/test/build). Omitted for\n * `plan` — there is no code to gate against a brief.\n */\n gate?: GateResult;\n}\n\n/** Runs one factory milestone and returns its deliverable, plus that milestone's gate result. */\nexport interface FactoryJobHooks {\n /**\n * Runs the factory's plan phase for `job` (mirrors `.sandcastle/main.ts`'s\n * Phase 1): returns the per-ticket fan-out the implement milestone splits\n * into, plus the plan document itself as increment 1's artifact. No gate\n * result — planning produces no code to lint/typecheck/test/build.\n */\n plan(\n job: FactoryJobRequest\n ): Promise<{ tickets: FactoryTicket[]; artifact: Uint8Array }>;\n /**\n * Runs one ticket's implement sandbox pipeline (mirrors Phase 2's\n * per-issue `sandbox.run`, which ends in the gate per `FACTORY.md`) and\n * returns its deliverable (e.g. the ticket's diff) plus that run's gate\n * result.\n */\n implement(ticket: FactoryTicket): Promise<FactoryJobWork>;\n /**\n * Runs the factory's merge/review phase over every completed ticket\n * (mirrors Phase 3, which re-runs the gate per `merge-prompt.md`) and\n * returns the final deliverable plus that run's gate result.\n */\n review(tickets: FactoryTicket[]): Promise<FactoryJobWork>;\n}\n\nexport interface ExecuteFactoryJobOptions {\n job: FactoryJobRequest;\n /** The original kind:5097 request event, verbatim — carried in the result's `request` tag. */\n requestEvent: RelayEvent;\n hooks: FactoryJobHooks;\n publisher: Publisher;\n delivery: JobDeliveryPort;\n relayUrls: string[];\n}\n\nexport interface FactoryJobExecution {\n outcome: FactoryJobOutcome;\n /** The last increment successfully paid for (0 if the buyer never paid increment 1). */\n reachedIncrement: number;\n totalIncrements: number;\n /** The published kind:6097 result event's id. */\n resultEventId: string;\n}\n\nasync function workFor(\n spec: IncrementSpec,\n tickets: FactoryTicket[],\n planArtifact: Uint8Array,\n hooks: FactoryJobHooks\n): Promise<FactoryJobWork> {\n if (spec.milestone === 'plan') return { artifact: planArtifact };\n if (spec.milestone === 'implement') {\n if (!spec.ticket) {\n throw new Error(\n `internal: implement increment ${spec.n} is missing its ticket`\n );\n }\n return hooks.implement(spec.ticket);\n }\n return hooks.review(tickets);\n}\n\n/**\n * Run a job end to end: plan → quote → one increment per milestone boundary\n * (encrypt, upload, offer, wait for payment) → terminal kind:6097 result.\n * Halts at the first unpaid increment (`abandoned-buyer`) or the first\n * hook failure once work is underway (`abandoned-provider`) — see §5.2 of\n * `docs/factory-job-protocol.md` for the three terminal outcomes.\n *\n * Throws if `hooks.plan` itself fails — with no quote ever published, there\n * is no prior kind:7000 event for a result's `reply` e-tag to reference, so\n * no protocol event can be honestly emitted for that failure.\n */\nexport async function executeFactoryJob(\n options: ExecuteFactoryJobOptions\n): Promise<FactoryJobExecution> {\n const { job, requestEvent, hooks, publisher, delivery, relayUrls } =\n options;\n\n if (!publisher.uploadBlob) {\n throw new Error(\n 'the active publisher cannot upload an encrypted increment artifact ' +\n '(uploadBlob unavailable) — executeFactoryJob requires the standalone publisher'\n );\n }\n const uploadBlob = publisher.uploadBlob.bind(publisher);\n\n const { tickets, artifact: planArtifact } = await hooks.plan(job);\n const schedule = planFactoryJob(job.bidMicroUsdc, tickets);\n const totalIncrements = schedule.length;\n\n const quoteEvent = buildQuoteEvent(job, schedule);\n const quoteReceipt = await publisher.publishEvent(quoteEvent, relayUrls);\n\n let parentEventId = quoteReceipt.eventId;\n let reachedIncrement = 0;\n let finalArtifactTxId: string | undefined;\n\n const haltWith = async (\n outcome: FactoryJobOutcome\n ): Promise<FactoryJobExecution> => {\n const resultEvent = buildResultEvent({\n job,\n requestEvent,\n lastEventId: parentEventId,\n outcome,\n reachedIncrement,\n totalIncrements,\n ...(outcome === 'completed' && finalArtifactTxId\n ? { finalArtifact: { arweaveTxId: finalArtifactTxId } }\n : {}),\n });\n const resultReceipt = await publisher.publishEvent(\n resultEvent,\n relayUrls\n );\n return {\n outcome,\n reachedIncrement,\n totalIncrements,\n resultEventId: resultReceipt.eventId,\n };\n };\n\n for (const spec of schedule) {\n let work: FactoryJobWork;\n try {\n work = await workFor(spec, tickets, planArtifact, hooks);\n } catch {\n // Provider-side failure mid-job: stop where we are, no renegotiation.\n return haltWith('abandoned-provider');\n }\n\n const encrypted = await delivery.encryptArtifact(work.artifact);\n const uploadReceipt = await uploadBlob({\n body: Buffer.from(encrypted.ciphertext),\n contentType: 'application/octet-stream',\n ...(job.repo ? { repoId: job.repo } : {}),\n });\n\n const offerEvent = buildIncrementOfferEvent({\n job,\n parentEventId,\n increment: spec,\n artifact: {\n arweaveTxId: uploadReceipt.txId,\n ciphertextSha256: encrypted.ciphertextSha256,\n conditionHex: encrypted.conditionHex,\n },\n ...(work.gate ? { gate: work.gate } : {}),\n });\n const offerReceipt = await publisher.publishEvent(offerEvent, relayUrls);\n\n const paid = await delivery.waitForPayment({\n offerEventId: offerReceipt.eventId,\n conditionHex: encrypted.conditionHex,\n priceUsdc: spec.priceUsdc,\n });\n if (!paid) {\n parentEventId = offerReceipt.eventId;\n return haltWith('abandoned-buyer');\n }\n\n reachedIncrement = spec.n;\n parentEventId = offerReceipt.eventId;\n if (spec.milestone === 'review') finalArtifactTxId = uploadReceipt.txId;\n }\n\n return haltWith('completed');\n}\n"],"mappings":"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","names":[]}
|
|
@@ -281,6 +281,47 @@ interface PublishReceipt {
|
|
|
281
281
|
/** Fee paid for this publish, in the smallest asset unit. */
|
|
282
282
|
feePaid: bigint;
|
|
283
283
|
}
|
|
284
|
+
/**
|
|
285
|
+
* One NIP-90 job for a store node's DVM, carried on the PAID path (#101).
|
|
286
|
+
*
|
|
287
|
+
* The store sits behind the connector's payment termination, so there is no
|
|
288
|
+
* public HTTP endpoint to POST these to — a reachable one would be an
|
|
289
|
+
* anonymous free gateway to a paid handler, which is the failure ADR 0020
|
|
290
|
+
* exists to close. The job therefore travels exactly as a kind:5094 store
|
|
291
|
+
* write does: inside a paid ILP packet, with `/store` as the envelope target
|
|
292
|
+
* BENEATH the route's handler path rather than as a URL path.
|
|
293
|
+
*/
|
|
294
|
+
interface StoreJobRequest {
|
|
295
|
+
/** NIP-90 job kind — 5095 brokered ArNS buy, 5096 gas station. */
|
|
296
|
+
kind: number;
|
|
297
|
+
/** Job arguments, sent as `["param", key, value]` tags. */
|
|
298
|
+
params: [string, string][];
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* A store DVM's answer, opened from the sealed FULFILL.
|
|
302
|
+
*
|
|
303
|
+
* ⚠️ A refusal is a RESULT, not an error. Under ADR 0020 an HTTP status is
|
|
304
|
+
* envelope content and never a packet outcome, so `accept: false` arrives on a
|
|
305
|
+
* FULFILL and the payer was charged for it either way. Implementations must
|
|
306
|
+
* return it rather than throw: the zero-ARIO rehearsal (submit a buy with no
|
|
307
|
+
* `processId` and watch the handler refuse by name before it quotes or touches
|
|
308
|
+
* the registry) is the cheapest proof the whole paid path works, and it only
|
|
309
|
+
* works if the refusal comes back as data.
|
|
310
|
+
*/
|
|
311
|
+
interface StoreJobResponse {
|
|
312
|
+
/** HTTP status the app answered with, inside the envelope. */
|
|
313
|
+
status: number;
|
|
314
|
+
/** The DVM's own accept flag. `false` is a refusal, not a transport fault. */
|
|
315
|
+
accept: boolean;
|
|
316
|
+
/** Machine-readable refusal reason, when the DVM sent one. */
|
|
317
|
+
code?: string;
|
|
318
|
+
/** Human-readable detail, when the DVM sent one. */
|
|
319
|
+
message?: string;
|
|
320
|
+
/** The job's output, shape depending on `kind`. */
|
|
321
|
+
result?: Record<string, unknown>;
|
|
322
|
+
/** Fee paid to carry this job, in the smallest asset unit. */
|
|
323
|
+
feePaid: bigint;
|
|
324
|
+
}
|
|
284
325
|
/**
|
|
285
326
|
* Fee rates used by `planPush` for the pre-push estimate.
|
|
286
327
|
*
|
|
@@ -300,6 +341,12 @@ interface FeeRates {
|
|
|
300
341
|
* price, so an estimate built from it is exactly what a push pays.
|
|
301
342
|
*/
|
|
302
343
|
uploadFee: bigint;
|
|
344
|
+
/**
|
|
345
|
+
* The store route's per-kibibyte rate, when it meters by size (ADR 0065: a
|
|
346
|
+
* price is a schedule `{base, per_kib}`; a flat route has no slope). Absent
|
|
347
|
+
* or zero means every upload costs exactly `uploadFee`.
|
|
348
|
+
*/
|
|
349
|
+
uploadPerKib?: bigint;
|
|
303
350
|
/**
|
|
304
351
|
* Flat cost per published event (smallest asset unit). Implementations
|
|
305
352
|
* already fold any per-packet route-price floor into this flat value, so
|
|
@@ -328,6 +375,16 @@ interface Publisher {
|
|
|
328
375
|
* implement it — `rig site` requires it and errors clearly when absent.
|
|
329
376
|
*/
|
|
330
377
|
uploadBlob?(upload: BlobUpload): Promise<UploadReceipt>;
|
|
378
|
+
/**
|
|
379
|
+
* Submit one NIP-90 job to the store node's DVM over the paid path and
|
|
380
|
+
* return its answer; paid. Used by `rig name buy/set --via` (#101) for the
|
|
381
|
+
* brokered kind:5095 buy and the kind:5096 gas station.
|
|
382
|
+
*
|
|
383
|
+
* Optional for the same reason as {@link uploadBlob}: transports that only
|
|
384
|
+
* move git objects (and pre-#101 test fakes) need not implement it, and
|
|
385
|
+
* `rig name --via` errors clearly when it is absent.
|
|
386
|
+
*/
|
|
387
|
+
submitStoreJob?(request: StoreJobRequest): Promise<StoreJobResponse>;
|
|
331
388
|
/**
|
|
332
389
|
* Sign (implementation-held key) and pay-to-publish one event to the
|
|
333
390
|
* given relay(s).
|
|
@@ -335,4 +392,4 @@ interface Publisher {
|
|
|
335
392
|
publishEvent(event: UnsignedEvent, relayUrls: string[]): Promise<PublishReceipt>;
|
|
336
393
|
}
|
|
337
394
|
|
|
338
|
-
export { type BlobUpload as B, COMMENT_KIND as C, type FeeRates as F, type GitObjectUpload as G, MAINTAINERS_TAG as M, type Publisher as P, REPOSITORY_STATE_KIND as R, type
|
|
395
|
+
export { type BlobUpload as B, COMMENT_KIND as C, type FeeRates as F, type GitObjectUpload as G, MAINTAINERS_TAG as M, type Publisher as P, REPOSITORY_STATE_KIND as R, type StoreJobRequest as S, type UploadReceipt as U, type StoreJobResponse as a, type UnsignedEvent as b, type PublishReceipt as c, type GitObjectType as d, type GitObject as e, MAX_OBJECT_SIZE as f, type StatusKind as g, authorizedStatusAuthors as h, buildComment as i, buildIssue as j, buildPatch as k, buildRepoAnnouncement as l, buildRepoRefs as m, buildStatus as n, createGitBlob as o, createGitCommit as p, createGitTag as q, createGitTree as r, hashGitObject as s, parseMaintainers as t };
|