@toon-protocol/rig 3.3.1 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { c as GitObjectType, P as Publisher, F as FeeRates, b as PublishReceipt } from './publisher-BFcnZeJI.js';
2
- export { C as COMMENT_KIND, d as GitObject, G as GitObjectUpload, M as MAINTAINERS_TAG, e as MAX_OBJECT_SIZE, R as REPOSITORY_STATE_KIND, S as StatusKind, U as UnsignedEvent, a as UploadReceipt, f as authorizedStatusAuthors, g as buildComment, h as buildIssue, i as buildPatch, j as buildRepoAnnouncement, k as buildRepoRefs, l as buildStatus, m as createGitBlob, n as createGitCommit, o as createGitTag, p as createGitTree, q as hashGitObject, r as parseMaintainers } from './publisher-BFcnZeJI.js';
1
+ import { c as GitObjectType, P as Publisher, F as FeeRates, b as PublishReceipt, U as UnsignedEvent } from './publisher-BFcnZeJI.js';
2
+ export { C as COMMENT_KIND, d as GitObject, G as GitObjectUpload, M as MAINTAINERS_TAG, e as MAX_OBJECT_SIZE, R as REPOSITORY_STATE_KIND, S as StatusKind, a as UploadReceipt, f as authorizedStatusAuthors, g as buildComment, h as buildIssue, i as buildPatch, j as buildRepoAnnouncement, k as buildRepoRefs, l as buildStatus, m as createGitBlob, n as createGitCommit, o as createGitTag, p as createGitTree, q as hashGitObject, r as parseMaintainers } from './publisher-BFcnZeJI.js';
3
3
  import '@toon-protocol/core/nip34';
4
4
 
5
5
  /**
@@ -1045,4 +1045,367 @@ declare function serializeEventReceipt(kind: number, receipt: PublishReceipt): G
1045
1045
  /** Serialize a PushResult onto the wire (bigints → strings, Maps → records). */
1046
1046
  declare function serializePushResult(plan: PushPlan, result: PushResult): GitPushResponse;
1047
1047
 
1048
- export { type ClosureResult, type CollectRepoObjectsOptions, type CollectRepoObjectsResult, DEFAULT_CONCURRENCY, type DownloadOptions, type DownloadResult, type ExecutePushOptions, FeeRates, type FetchLike, type FetchRemoteStateOptions, type FetchedObject, 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 MissingObject, NonFastForwardError, type NostrEvent, type NostrFilter, ObjectIntegrityError, type ObjectStat, type ObjectWithPath, ObjectWriteMismatchError, 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 RemoteState, type RepoRefs, type StatObjectsResult, type UploadStepResult, type WebSocketFactory, type WebSocketLike, collectRepoObjects, downloadGitObjects, executePush, fetchRemoteState, fetchTxBytes, hexToNpub, isSafeRefname, missingObjectsMessage, npubToHex, ownerToHex, planPush, queryRelay, referencedShas, serializeEventReceipt, serializeFeeEstimate, serializePushPlan, serializePushResult, setHeadSymref, updateRef, verifyObjectBody, walkClosure, writeGitObject, writeGitObjects };
1048
+ /**
1049
+ * Factory job protocol — increment schedule planner (#52).
1050
+ *
1051
+ * `planFactoryJob` is the network-free half of the adapter (mirrors
1052
+ * `planPush`'s split from `executePush` in push.ts): given a job's `bid` and
1053
+ * the ticket list the factory's plan phase produced, it prices the
1054
+ * milestone boundaries decision 6 of toon-meta#262 fixed as increments —
1055
+ * plan → implement ×N tickets → review — and returns the schedule that
1056
+ * becomes the `kind:7000 status:"quote"` content (§3.2 of
1057
+ * `docs/factory-job-protocol.md`).
1058
+ *
1059
+ * Per the issue gotcha, `implement` must be many increments, not one: this
1060
+ * always emits one increment per ticket rather than a single lump-sum
1061
+ * implement increment, using the factory's existing parallel per-issue
1062
+ * fan-out (`.sandcastle/main.ts`'s Plan phase output — `{issues: [{id,
1063
+ * title, branch}]}`) as the boundary.
1064
+ */
1065
+ /** One ticket the factory's plan phase fanned the implement milestone into. */
1066
+ interface FactoryTicket {
1067
+ id: string;
1068
+ title: string;
1069
+ branch: string;
1070
+ }
1071
+ type FactoryMilestone = 'plan' | 'implement' | 'review';
1072
+ /** One priced increment in the schedule (§3.2's `increments[]` shape). */
1073
+ interface IncrementSpec {
1074
+ n: number;
1075
+ of: number;
1076
+ milestone: FactoryMilestone;
1077
+ /** Set only for `milestone: "implement"` increments. */
1078
+ ticket?: FactoryTicket;
1079
+ priceUsdc: string;
1080
+ }
1081
+ /**
1082
+ * Build the increment schedule for a job, given the tickets its plan phase
1083
+ * produced. Plan and review each get a fixed 10% of the bid; the remaining
1084
+ * 80% ("implement") splits evenly across tickets, with the integer-division
1085
+ * remainder distributed one micro-USDC at a time to the earliest tickets —
1086
+ * so `sum(priceUsdc) === bid` exactly (§3.2 only requires `<=`, but an exact
1087
+ * match avoids "where did the leftover micro-USDC go" questions).
1088
+ *
1089
+ * Per decision 7, this schedule is agreed before any paid work starts and is
1090
+ * never repriced — a job that runs larger than `tickets` implied is a
1091
+ * lesson, not a renegotiation.
1092
+ *
1093
+ * Throws if `tickets` is empty (a job with no implementation work has no
1094
+ * increments to schedule) or `bidMicroUsdc` is not a positive integer.
1095
+ */
1096
+ declare function planFactoryJob(bidMicroUsdc: string, tickets: FactoryTicket[]): IncrementSpec[];
1097
+
1098
+ /**
1099
+ * Factory job protocol — reproducible gate result per increment (#53).
1100
+ *
1101
+ * Decision 5 of toon-meta#262 splits trust into three layers: delivery (the
1102
+ * hashlock), the objective floor (this — "did this increment pass the
1103
+ * repo's own gate?"), and quality (reputation, out of scope here). Per
1104
+ * `FACTORY.md`, the pinned pipeline's gate is lint/typecheck/test/build run
1105
+ * before a PR is opened; this module is the pure, network-free shape for
1106
+ * recording exactly what ran, not a summary verdict.
1107
+ *
1108
+ * This proves conformance, not fitness for purpose — a gate-passing
1109
+ * increment can still completely miss the brief. Reputation is what
1110
+ * distinguishes those, never this.
1111
+ */
1112
+ /** One command the gate ran, and whether it passed. */
1113
+ interface GateCheck {
1114
+ /** e.g. "lint", "typecheck", "test", "build" — whatever actually ran,
1115
+ * never a canonical list (gates differ per repo and drift over time). */
1116
+ name: string;
1117
+ /** The literal command, e.g. "eslint .", so a buyer can reproduce it verbatim. */
1118
+ command: string;
1119
+ pass: boolean;
1120
+ }
1121
+ /**
1122
+ * A reproducible gate result: enough for a buyer to re-run the gate
1123
+ * themselves and get the same answer. Omitting the commit or the command
1124
+ * set turns this from a reproducible result into a provider's unverifiable
1125
+ * claim of having done well — both are required, not advisory.
1126
+ */
1127
+ interface GateResult {
1128
+ /** The commit this gate ran against. */
1129
+ commit: string;
1130
+ /** Toolchain versions relevant to reproducing the result, e.g. `{ node: "20.11.0", pnpm: "9.1.0" }`. */
1131
+ toolchain: Record<string, string>;
1132
+ checks: GateCheck[];
1133
+ }
1134
+ /** 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. */
1135
+ declare function gatePassed(gate: GateResult): boolean;
1136
+
1137
+ /**
1138
+ * Factory job protocol — pure NIP-90 event builders/parser (#52).
1139
+ *
1140
+ * Wire format per toon-meta's `docs/factory-job-protocol.md`
1141
+ * (toon-meta#263), fixed 2026-08-03: kind:5097 job request, kind:6097 job
1142
+ * result, kind:7000 job feedback (quote / increment offer / narration,
1143
+ * disambiguated by the `status` tag). Mirrors nip34-events.ts: every builder
1144
+ * returns an UnsignedEvent — the caller signs and publishes (e.g. through a
1145
+ * Publisher, publisher.ts) elsewhere. No crypto, no network, no payment.
1146
+ */
1147
+
1148
+ /** Factory job request (the brief). */
1149
+ declare const FACTORY_JOB_REQUEST_KIND = 5097;
1150
+ /** Factory job result — terminal state (completed/abandoned-*). */
1151
+ declare const FACTORY_JOB_RESULT_KIND = 6097;
1152
+ /** Factory job feedback — quote, increment offer, or free narration. */
1153
+ declare const FACTORY_JOB_FEEDBACK_KIND = 7000;
1154
+ /** The three §5.2 terminal outcomes — decision 8 has no fourth. */
1155
+ type FactoryJobOutcome = 'completed' | 'abandoned-provider' | 'abandoned-buyer';
1156
+ /** A parsed kind:5097 job request (§2). */
1157
+ interface FactoryJobRequest {
1158
+ /** The requesting kind:5097 event's id. */
1159
+ requestEventId: string;
1160
+ /** The requesting kind:5097 event's pubkey (who to reply/bill). */
1161
+ buyerPubkey: string;
1162
+ /** The brief — free text or a chained milestone reference (§2.3). */
1163
+ brief: string;
1164
+ /** Maximum the buyer will pay across the whole job (micro-USDC, §2.1). */
1165
+ bidMicroUsdc: string;
1166
+ /** `param repo` — target repo, e.g. `toon-protocol/buzz`. */
1167
+ repo?: string;
1168
+ /** `param target` — the ticket/issue this job resolves. */
1169
+ target?: string;
1170
+ /** `param constraints` — gate requirements / scope limits. */
1171
+ constraints?: string;
1172
+ /** `output` — expected deliverable MIME type. */
1173
+ outputMime?: string;
1174
+ }
1175
+ /** Minimal shape of a relay event this module reads/builds — no signing. */
1176
+ interface RelayEvent {
1177
+ id: string;
1178
+ pubkey: string;
1179
+ tags: string[][];
1180
+ content: string;
1181
+ }
1182
+ /**
1183
+ * Parse a kind:5097 job request event's tags into a {@link FactoryJobRequest}.
1184
+ * Throws if the required `i` (brief) or `bid` tag is missing — both are
1185
+ * REQUIRED per §2.1.
1186
+ */
1187
+ declare function parseFactoryJobRequest(event: RelayEvent): FactoryJobRequest;
1188
+ /**
1189
+ * Build the `kind:7000 status:"quote"` reply: the proposed increment
1190
+ * schedule, committing nothing (§3.3 — there is no accept message; payment
1191
+ * of increment 1 is acceptance).
1192
+ */
1193
+ declare function buildQuoteEvent(job: FactoryJobRequest, schedule: IncrementSpec[]): UnsignedEvent;
1194
+ /** Where the encrypted increment artifact lives, and its hashlock condition. */
1195
+ interface EncryptedArtifactRef {
1196
+ /** Arweave tx id the ciphertext was uploaded to. */
1197
+ arweaveTxId: string;
1198
+ /** sha256 of the ciphertext, hex — lets the buyer detect a bad fetch. */
1199
+ ciphertextSha256: string;
1200
+ /** `sha256(key)` hex, where `key` decrypts the artifact (§4.2 — the join). */
1201
+ conditionHex: string;
1202
+ }
1203
+ interface BuildIncrementOfferOptions {
1204
+ job: FactoryJobRequest;
1205
+ /** The quote event's id for increment 1, else the previous offer's id. */
1206
+ parentEventId: string;
1207
+ increment: IncrementSpec;
1208
+ artifact: EncryptedArtifactRef;
1209
+ /**
1210
+ * The reproducible gate result for this increment's work (#53 — the
1211
+ * objective floor), when one ran. Omitted for milestones with nothing to
1212
+ * gate (e.g. `plan` — there is no code to lint against a brief).
1213
+ */
1214
+ gate?: GateResult;
1215
+ }
1216
+ /**
1217
+ * Build the `kind:7000 status:"partial"` increment offer — published once
1218
+ * the provider has done that increment's work and uploaded the encrypted
1219
+ * artifact. This is the join between the relay plane and the connector
1220
+ * plane (§4.2): `condition` here MUST equal the paying PREPARE's
1221
+ * `executionCondition`, byte for byte.
1222
+ *
1223
+ * When `gate` is supplied, the offer also carries a `["gate", "pass"|"fail"]`
1224
+ * tag (cheap to scan for a gate-pass rate, decision 8) and the full
1225
+ * reproducible result in `content` — visible to the buyer before they pay,
1226
+ * per the issue: a gate-failing increment is still offered, never hidden.
1227
+ */
1228
+ declare function buildIncrementOfferEvent(options: BuildIncrementOfferOptions): UnsignedEvent;
1229
+ interface BuildNarrationOptions {
1230
+ job: FactoryJobRequest;
1231
+ /** The prior event in the thread (quote, offer, or narration). */
1232
+ parentEventId: string;
1233
+ message: string;
1234
+ }
1235
+ /**
1236
+ * Build a free narration event — no artifact, no `amount`/`condition`, MUST
1237
+ * NOT be paid against (§6). This is what makes a provider look alive between
1238
+ * increments.
1239
+ */
1240
+ declare function buildNarrationEvent(options: BuildNarrationOptions): UnsignedEvent;
1241
+ interface BuildResultEventOptions {
1242
+ job: FactoryJobRequest;
1243
+ /** The original kind:5097 request event, verbatim (the `request` tag). */
1244
+ requestEvent: RelayEvent;
1245
+ /** The last kind:7000 event in the thread (offer or narration). */
1246
+ lastEventId: string;
1247
+ outcome: FactoryJobOutcome;
1248
+ /** How far the job got — `n-reached == of` only when outcome is completed. */
1249
+ reachedIncrement: number;
1250
+ totalIncrements: number;
1251
+ /** Required (and only meaningful) when outcome is `"completed"` (§5.2). */
1252
+ finalArtifact?: {
1253
+ arweaveTxId: string;
1254
+ };
1255
+ }
1256
+ /**
1257
+ * Build the terminal `kind:6097` job result. Reputation (decision 8) is
1258
+ * computed from exactly this event and nothing else.
1259
+ */
1260
+ declare function buildResultEvent(options: BuildResultEventOptions): UnsignedEvent;
1261
+
1262
+ /**
1263
+ * JobDeliveryPort — the paid-increment seam for `executeFactoryJob` (#52).
1264
+ * The upload and event-publish legs reuse the EXISTING `Publisher`
1265
+ * (publisher.ts — `uploadBlob` for the ciphertext, `publishEvent` for the
1266
+ * quote/offer/result events); this port covers only what `Publisher` can't:
1267
+ * the job-specific crypto (encrypt-under-a-fresh-key → condition) and
1268
+ * waiting for the increment's payment to settle.
1269
+ *
1270
+ * Per decision 5 of toon-meta#262 and §4.2 of `docs/factory-job-protocol.md`,
1271
+ * delivering an increment is: encrypt the artifact with a fresh key, upload
1272
+ * the ciphertext, publish `condition = sha256(key)`, then wait for an ILP
1273
+ * PREPARE whose `executionCondition` matches — releasing `key` as the
1274
+ * FULFILL preimage is simultaneously payment settling and key handoff.
1275
+ * `@toon-protocol/client` 0.25.1 (the latest published version as of this
1276
+ * ticket) does not yet export the earning-API/hashlock helpers merged to
1277
+ * its `main` branch today (toon-client#496, toon-client#498) — this
1278
+ * interface is the injection point a follow-up ticket wires a real
1279
+ * implementation into once a release carries them. Do not re-derive the
1280
+ * encrypt/condition logic locally in an implementation of this port — use
1281
+ * `@toon-protocol/client`'s `encryptArtifact`/`fulfillIncrement` once
1282
+ * available, per the issue's explicit instruction not to hand-roll it.
1283
+ */
1284
+ /** A freshly-encrypted increment artifact, ready to upload. */
1285
+ interface EncryptedArtifact {
1286
+ /** The encrypted bytes — upload this, never the plaintext. */
1287
+ ciphertext: Uint8Array;
1288
+ /** sha256 of `ciphertext`, hex — lets the buyer detect a bad fetch. */
1289
+ ciphertextSha256: string;
1290
+ /** `sha256(key)` hex, where `key` decrypts the artifact (§4.2 — the join). */
1291
+ conditionHex: string;
1292
+ }
1293
+ /** One increment offered for payment — what {@link JobDeliveryPort.waitForPayment} waits on. */
1294
+ interface OfferedIncrement {
1295
+ /** The kind:7000 status:"partial" event id this offer was published as. */
1296
+ offerEventId: string;
1297
+ /** Must equal the offer's `condition` tag, byte for byte (§4.2). */
1298
+ conditionHex: string;
1299
+ priceUsdc: string;
1300
+ }
1301
+ /**
1302
+ * Injected transport for `executeFactoryJob` — implemented against
1303
+ * `@toon-protocol/client`'s earning API in a follow-up ticket. Must be safe
1304
+ * to call sequentially (increments are delivered and paid for one at a
1305
+ * time, never in parallel) and should throw on a provider-side failure —
1306
+ * `executeFactoryJob` does not retry an increment.
1307
+ */
1308
+ interface JobDeliveryPort {
1309
+ /**
1310
+ * Encrypt `bytes` under a fresh, single-use key and return the
1311
+ * ciphertext plus `condition = sha256(key)`. Per the issue's gotchas:
1312
+ * NEVER reuse a key across increments — paying for the plan must not
1313
+ * unlock the implementation.
1314
+ */
1315
+ encryptArtifact(bytes: Uint8Array): Promise<EncryptedArtifact>;
1316
+ /**
1317
+ * Wait for the buyer's ILP PREPARE against `offer.conditionHex` to
1318
+ * settle. Resolves `true` once the FULFILL is released (the increment is
1319
+ * paid); resolves `false` if the buyer never pays (implementation-defined
1320
+ * timeout/backoff) — `executeFactoryJob` treats `false` as the signal to
1321
+ * halt (decision 6: one unpaid increment and work stops).
1322
+ */
1323
+ waitForPayment(offer: OfferedIncrement): Promise<boolean>;
1324
+ }
1325
+
1326
+ /**
1327
+ * executeFactoryJob — the adapter's imperative shell (#52): runs the
1328
+ * factory's own milestones (`.sandcastle/main.ts`'s plan → per-ticket
1329
+ * implement+review → merge phases) through `hooks`, and turns each
1330
+ * milestone boundary into a paid increment via `publisher` (existing,
1331
+ * publisher.ts — upload + event publish) and `delivery` (new,
1332
+ * factory-job-delivery.ts — encrypt + payment wait).
1333
+ *
1334
+ * Mirrors `executePush`'s split from `planPush`: `planFactoryJob` is the
1335
+ * network-free planning half; this is the half that spends money.
1336
+ *
1337
+ * Decision 6 (toon-meta#262): work happens BEFORE the packet, so every
1338
+ * increment's artifact is produced and uploaded before that increment is
1339
+ * offered for payment. Decision 7: the schedule is fixed once the quote is
1340
+ * published — this never reprices mid-job. Per the issue: "Stop on
1341
+ * non-payment. One unpaid increment and work halts" — the symmetric
1342
+ * protection that neither side is ever exposed for more than one increment.
1343
+ */
1344
+
1345
+ /** One milestone's deliverable, plus the gate result that ran against it, when one did. */
1346
+ interface FactoryJobWork {
1347
+ artifact: Uint8Array;
1348
+ /**
1349
+ * The reproducible gate result (#53) for this milestone's work, per
1350
+ * `FACTORY.md`'s pinned pipeline (lint/typecheck/test/build). Omitted for
1351
+ * `plan` — there is no code to gate against a brief.
1352
+ */
1353
+ gate?: GateResult;
1354
+ }
1355
+ /** Runs one factory milestone and returns its deliverable, plus that milestone's gate result. */
1356
+ interface FactoryJobHooks {
1357
+ /**
1358
+ * Runs the factory's plan phase for `job` (mirrors `.sandcastle/main.ts`'s
1359
+ * Phase 1): returns the per-ticket fan-out the implement milestone splits
1360
+ * into, plus the plan document itself as increment 1's artifact. No gate
1361
+ * result — planning produces no code to lint/typecheck/test/build.
1362
+ */
1363
+ plan(job: FactoryJobRequest): Promise<{
1364
+ tickets: FactoryTicket[];
1365
+ artifact: Uint8Array;
1366
+ }>;
1367
+ /**
1368
+ * Runs one ticket's implement sandbox pipeline (mirrors Phase 2's
1369
+ * per-issue `sandbox.run`, which ends in the gate per `FACTORY.md`) and
1370
+ * returns its deliverable (e.g. the ticket's diff) plus that run's gate
1371
+ * result.
1372
+ */
1373
+ implement(ticket: FactoryTicket): Promise<FactoryJobWork>;
1374
+ /**
1375
+ * Runs the factory's merge/review phase over every completed ticket
1376
+ * (mirrors Phase 3, which re-runs the gate per `merge-prompt.md`) and
1377
+ * returns the final deliverable plus that run's gate result.
1378
+ */
1379
+ review(tickets: FactoryTicket[]): Promise<FactoryJobWork>;
1380
+ }
1381
+ interface ExecuteFactoryJobOptions {
1382
+ job: FactoryJobRequest;
1383
+ /** The original kind:5097 request event, verbatim — carried in the result's `request` tag. */
1384
+ requestEvent: RelayEvent;
1385
+ hooks: FactoryJobHooks;
1386
+ publisher: Publisher;
1387
+ delivery: JobDeliveryPort;
1388
+ relayUrls: string[];
1389
+ }
1390
+ interface FactoryJobExecution {
1391
+ outcome: FactoryJobOutcome;
1392
+ /** The last increment successfully paid for (0 if the buyer never paid increment 1). */
1393
+ reachedIncrement: number;
1394
+ totalIncrements: number;
1395
+ /** The published kind:6097 result event's id. */
1396
+ resultEventId: string;
1397
+ }
1398
+ /**
1399
+ * Run a job end to end: plan → quote → one increment per milestone boundary
1400
+ * (encrypt, upload, offer, wait for payment) → terminal kind:6097 result.
1401
+ * Halts at the first unpaid increment (`abandoned-buyer`) or the first
1402
+ * hook failure once work is underway (`abandoned-provider`) — see §5.2 of
1403
+ * `docs/factory-job-protocol.md` for the three terminal outcomes.
1404
+ *
1405
+ * Throws if `hooks.plan` itself fails — with no quote ever published, there
1406
+ * is no prior kind:7000 event for a result's `reply` e-tag to reference, so
1407
+ * no protocol event can be honestly emitted for that failure.
1408
+ */
1409
+ declare function executeFactoryJob(options: ExecuteFactoryJobOptions): Promise<FactoryJobExecution>;
1410
+
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
@@ -51,9 +51,286 @@ import {
51
51
  parseMaintainers,
52
52
  queryRelay
53
53
  } from "./chunk-2KJJ7LSO.js";
54
+
55
+ // src/factory-job-gate.ts
56
+ function gatePassed(gate) {
57
+ return gate.checks.every((check) => check.pass);
58
+ }
59
+
60
+ // src/factory-job-events.ts
61
+ var FACTORY_JOB_REQUEST_KIND = 5097;
62
+ var FACTORY_JOB_RESULT_KIND = 6097;
63
+ var FACTORY_JOB_FEEDBACK_KIND = 7e3;
64
+ function parseFactoryJobRequest(event) {
65
+ const briefTag = event.tags.find((t) => t[0] === "i" && t[2] === "text");
66
+ if (!briefTag?.[1]) {
67
+ throw new Error(
68
+ `kind:${FACTORY_JOB_REQUEST_KIND} event ${event.id} is missing its required brief ("i", "<brief>", "text") tag`
69
+ );
70
+ }
71
+ const bidTag = event.tags.find((t) => t[0] === "bid");
72
+ if (!bidTag?.[1]) {
73
+ throw new Error(
74
+ `kind:${FACTORY_JOB_REQUEST_KIND} event ${event.id} is missing its required "bid" tag`
75
+ );
76
+ }
77
+ const params = event.tags.filter((t) => t[0] === "param");
78
+ const paramValue = (name) => params.find((t) => t[1] === name)?.[2];
79
+ const repo = paramValue("repo");
80
+ const target = paramValue("target");
81
+ const constraints = paramValue("constraints");
82
+ const outputMime = event.tags.find((t) => t[0] === "output")?.[1];
83
+ return {
84
+ requestEventId: event.id,
85
+ buyerPubkey: event.pubkey,
86
+ brief: briefTag[1],
87
+ bidMicroUsdc: bidTag[1],
88
+ ...repo !== void 0 ? { repo } : {},
89
+ ...target !== void 0 ? { target } : {},
90
+ ...constraints !== void 0 ? { constraints } : {},
91
+ ...outputMime !== void 0 ? { outputMime } : {}
92
+ };
93
+ }
94
+ function buildQuoteEvent(job, schedule) {
95
+ return {
96
+ kind: FACTORY_JOB_FEEDBACK_KIND,
97
+ content: JSON.stringify({
98
+ increments: schedule.map((s) => ({
99
+ n: s.n,
100
+ of: s.of,
101
+ milestone: s.milestone,
102
+ priceUsdc: s.priceUsdc
103
+ }))
104
+ }),
105
+ tags: [
106
+ ["e", job.requestEventId, "", "root"],
107
+ ["p", job.buyerPubkey],
108
+ ["status", "quote"]
109
+ ],
110
+ created_at: Math.floor(Date.now() / 1e3)
111
+ };
112
+ }
113
+ function assertReproducible(gate) {
114
+ if (gate.checks.length === 0) {
115
+ throw new Error(
116
+ "gate result must record at least one check for the offer to be reproducible"
117
+ );
118
+ }
119
+ if (!gate.commit) {
120
+ throw new Error(
121
+ "gate result must record the commit it ran against for the offer to be reproducible"
122
+ );
123
+ }
124
+ }
125
+ function buildIncrementOfferEvent(options) {
126
+ const { job, parentEventId, increment, artifact, gate } = options;
127
+ if (gate) assertReproducible(gate);
128
+ const tags = [
129
+ ["e", job.requestEventId, "", "root"],
130
+ ["e", parentEventId, "", "reply"],
131
+ ["p", job.buyerPubkey],
132
+ ["status", "partial"],
133
+ ["increment", String(increment.n), String(increment.of)],
134
+ ["i", artifact.arweaveTxId, "url"],
135
+ ["i", artifact.ciphertextSha256, "text", "", "hash"],
136
+ ["amount", increment.priceUsdc, "usdc"],
137
+ ["condition", artifact.conditionHex]
138
+ ];
139
+ if (gate) tags.push(["gate", gatePassed(gate) ? "pass" : "fail"]);
140
+ return {
141
+ kind: FACTORY_JOB_FEEDBACK_KIND,
142
+ content: gate ? JSON.stringify({ gate }) : "",
143
+ tags,
144
+ created_at: Math.floor(Date.now() / 1e3)
145
+ };
146
+ }
147
+ function buildNarrationEvent(options) {
148
+ const { job, parentEventId, message } = options;
149
+ return {
150
+ kind: FACTORY_JOB_FEEDBACK_KIND,
151
+ content: message,
152
+ tags: [
153
+ ["e", job.requestEventId, "", "root"],
154
+ ["e", parentEventId, "", "reply"],
155
+ ["p", job.buyerPubkey],
156
+ ["status", "processing"]
157
+ ],
158
+ created_at: Math.floor(Date.now() / 1e3)
159
+ };
160
+ }
161
+ function buildResultEvent(options) {
162
+ const {
163
+ job,
164
+ requestEvent,
165
+ lastEventId,
166
+ outcome,
167
+ reachedIncrement,
168
+ totalIncrements,
169
+ finalArtifact
170
+ } = options;
171
+ const tags = [
172
+ ["e", job.requestEventId, "", "root"],
173
+ ["e", lastEventId, "", "reply"],
174
+ ["p", job.buyerPubkey],
175
+ ["request", JSON.stringify(requestEvent)],
176
+ ["outcome", outcome],
177
+ ["increment", String(reachedIncrement), String(totalIncrements)]
178
+ ];
179
+ if (outcome === "completed" && finalArtifact) {
180
+ tags.push(["i", finalArtifact.arweaveTxId, "url"]);
181
+ }
182
+ return {
183
+ kind: FACTORY_JOB_RESULT_KIND,
184
+ content: "",
185
+ tags,
186
+ created_at: Math.floor(Date.now() / 1e3)
187
+ };
188
+ }
189
+
190
+ // src/factory-job-plan.ts
191
+ var PLAN_SHARE_BPS = 1000n;
192
+ var REVIEW_SHARE_BPS = 1000n;
193
+ var BPS_DENOM = 10000n;
194
+ function planFactoryJob(bidMicroUsdc, tickets) {
195
+ if (tickets.length === 0) {
196
+ throw new Error(
197
+ "planFactoryJob requires at least one implementation ticket from the plan phase"
198
+ );
199
+ }
200
+ let bid;
201
+ try {
202
+ bid = BigInt(bidMicroUsdc);
203
+ } catch {
204
+ throw new Error(`bidMicroUsdc must be an integer string, got ${bidMicroUsdc}`);
205
+ }
206
+ if (bid <= 0n) {
207
+ throw new Error(`bidMicroUsdc must be positive, got ${bidMicroUsdc}`);
208
+ }
209
+ const planPrice = bid * PLAN_SHARE_BPS / BPS_DENOM;
210
+ const reviewPrice = bid * REVIEW_SHARE_BPS / BPS_DENOM;
211
+ const implementTotal = bid - planPrice - reviewPrice;
212
+ const ticketCount = BigInt(tickets.length);
213
+ const base = implementTotal / ticketCount;
214
+ const remainder = implementTotal % ticketCount;
215
+ const total = tickets.length + 2;
216
+ const schedule = [
217
+ { n: 1, of: total, milestone: "plan", priceUsdc: planPrice.toString() }
218
+ ];
219
+ tickets.forEach((ticket, i) => {
220
+ const price = base + (BigInt(i) < remainder ? 1n : 0n);
221
+ schedule.push({
222
+ n: i + 2,
223
+ of: total,
224
+ milestone: "implement",
225
+ ticket,
226
+ priceUsdc: price.toString()
227
+ });
228
+ });
229
+ schedule.push({
230
+ n: total,
231
+ of: total,
232
+ milestone: "review",
233
+ priceUsdc: reviewPrice.toString()
234
+ });
235
+ return schedule;
236
+ }
237
+
238
+ // src/factory-job-execute.ts
239
+ async function workFor(spec, tickets, planArtifact, hooks) {
240
+ if (spec.milestone === "plan") return { artifact: planArtifact };
241
+ if (spec.milestone === "implement") {
242
+ if (!spec.ticket) {
243
+ throw new Error(
244
+ `internal: implement increment ${spec.n} is missing its ticket`
245
+ );
246
+ }
247
+ return hooks.implement(spec.ticket);
248
+ }
249
+ return hooks.review(tickets);
250
+ }
251
+ async function executeFactoryJob(options) {
252
+ const { job, requestEvent, hooks, publisher, delivery, relayUrls } = options;
253
+ if (!publisher.uploadBlob) {
254
+ throw new Error(
255
+ "the active publisher cannot upload an encrypted increment artifact (uploadBlob unavailable) \u2014 executeFactoryJob requires the standalone publisher"
256
+ );
257
+ }
258
+ const uploadBlob = publisher.uploadBlob.bind(publisher);
259
+ const { tickets, artifact: planArtifact } = await hooks.plan(job);
260
+ const schedule = planFactoryJob(job.bidMicroUsdc, tickets);
261
+ const totalIncrements = schedule.length;
262
+ const quoteEvent = buildQuoteEvent(job, schedule);
263
+ const quoteReceipt = await publisher.publishEvent(quoteEvent, relayUrls);
264
+ let parentEventId = quoteReceipt.eventId;
265
+ let reachedIncrement = 0;
266
+ let finalArtifactTxId;
267
+ const haltWith = async (outcome) => {
268
+ const resultEvent = buildResultEvent({
269
+ job,
270
+ requestEvent,
271
+ lastEventId: parentEventId,
272
+ outcome,
273
+ reachedIncrement,
274
+ totalIncrements,
275
+ ...outcome === "completed" && finalArtifactTxId ? { finalArtifact: { arweaveTxId: finalArtifactTxId } } : {}
276
+ });
277
+ const resultReceipt = await publisher.publishEvent(
278
+ resultEvent,
279
+ relayUrls
280
+ );
281
+ return {
282
+ outcome,
283
+ reachedIncrement,
284
+ totalIncrements,
285
+ resultEventId: resultReceipt.eventId
286
+ };
287
+ };
288
+ for (const spec of schedule) {
289
+ let work;
290
+ try {
291
+ work = await workFor(spec, tickets, planArtifact, hooks);
292
+ } catch {
293
+ return haltWith("abandoned-provider");
294
+ }
295
+ const encrypted = await delivery.encryptArtifact(work.artifact);
296
+ const uploadReceipt = await uploadBlob({
297
+ body: Buffer.from(encrypted.ciphertext),
298
+ contentType: "application/octet-stream",
299
+ ...job.repo ? { repoId: job.repo } : {}
300
+ });
301
+ const offerEvent = buildIncrementOfferEvent({
302
+ job,
303
+ parentEventId,
304
+ increment: spec,
305
+ artifact: {
306
+ arweaveTxId: uploadReceipt.txId,
307
+ ciphertextSha256: encrypted.ciphertextSha256,
308
+ conditionHex: encrypted.conditionHex
309
+ },
310
+ ...work.gate ? { gate: work.gate } : {}
311
+ });
312
+ const offerReceipt = await publisher.publishEvent(offerEvent, relayUrls);
313
+ const paid = await delivery.waitForPayment({
314
+ offerEventId: offerReceipt.eventId,
315
+ conditionHex: encrypted.conditionHex,
316
+ priceUsdc: spec.priceUsdc
317
+ });
318
+ if (!paid) {
319
+ parentEventId = offerReceipt.eventId;
320
+ return haltWith("abandoned-buyer");
321
+ }
322
+ reachedIncrement = spec.n;
323
+ parentEventId = offerReceipt.eventId;
324
+ if (spec.milestone === "review") finalArtifactTxId = uploadReceipt.txId;
325
+ }
326
+ return haltWith("completed");
327
+ }
54
328
  export {
55
329
  COMMENT_KIND,
56
330
  DEFAULT_CONCURRENCY,
331
+ FACTORY_JOB_FEEDBACK_KIND,
332
+ FACTORY_JOB_REQUEST_KIND,
333
+ FACTORY_JOB_RESULT_KIND,
57
334
  GitError,
58
335
  GitRepoReader,
59
336
  MAINTAINERS_TAG,
@@ -65,10 +342,14 @@ export {
65
342
  REPOSITORY_STATE_KIND,
66
343
  authorizedStatusAuthors,
67
344
  buildComment,
345
+ buildIncrementOfferEvent,
68
346
  buildIssue,
347
+ buildNarrationEvent,
69
348
  buildPatch,
349
+ buildQuoteEvent,
70
350
  buildRepoAnnouncement,
71
351
  buildRepoRefs,
352
+ buildResultEvent,
72
353
  buildStatus,
73
354
  collectRepoObjects,
74
355
  createGitBlob,
@@ -76,16 +357,20 @@ export {
76
357
  createGitTag,
77
358
  createGitTree,
78
359
  downloadGitObjects,
360
+ executeFactoryJob,
79
361
  executePush,
80
362
  fetchRemoteState,
81
363
  fetchTxBytes,
364
+ gatePassed,
82
365
  hashGitObject,
83
366
  hexToNpub,
84
367
  isSafeRefname,
85
368
  missingObjectsMessage,
86
369
  npubToHex,
87
370
  ownerToHex,
371
+ parseFactoryJobRequest,
88
372
  parseMaintainers,
373
+ planFactoryJob,
89
374
  planPush,
90
375
  queryRelay,
91
376
  referencedShas,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toon-protocol/rig",
3
- "version": "3.3.1",
3
+ "version": "3.4.0",
4
4
  "description": "Git-to-TOON write path core — build git objects and NIP-34 events for the Rig control plane",
5
5
  "license": "MIT",
6
6
  "author": "Jonathan Green",