@prampta/sdk 0.9.1 → 0.11.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.js CHANGED
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  HARD_REFUSAL_CODES: () => HARD_REFUSAL_CODES,
34
+ NotGeneratedError: () => NotGeneratedError,
34
35
  Prampta: () => Prampta,
35
36
  PramptaApiError: () => PramptaApiError,
36
37
  PramptaError: () => PramptaError,
@@ -42,21 +43,28 @@ __export(index_exports, {
42
43
  PramptaSchemaError: () => PramptaSchemaError,
43
44
  PramptaSignatureError: () => PramptaSignatureError,
44
45
  PramptaTimeoutError: () => PramptaTimeoutError,
46
+ PregenError: () => PregenError,
45
47
  REFUSAL_DESCRIPTIONS: () => REFUSAL_DESCRIPTIONS,
46
48
  RegistryDirectoryError: () => RegistryDirectoryError,
47
49
  SOFT_REFUSAL_CODES: () => SOFT_REFUSAL_CODES,
48
50
  TrustedRegistryDirectory: () => TrustedRegistryDirectory,
49
51
  canonicalJson: () => canonicalJson,
52
+ createPregen: () => createPregen,
50
53
  hashPrompt: () => hashPrompt,
51
- loadPregenDirectory: () => import_verify2.loadDirectory,
54
+ loadPregenDirectory: () => import_verify3.loadDirectory,
52
55
  matchSubjects: () => matchSubjects,
56
+ memoryStore: () => memoryStore,
57
+ messageId: () => messageId,
53
58
  normalizeForMatch: () => normalizeForMatch,
59
+ postgresSchema: () => postgresSchema,
60
+ postgresStore: () => postgresStore,
61
+ signingInput: () => signingInput,
54
62
  singleProcessJournal: () => singleProcessJournal,
55
63
  verifyLicenseSignatures: () => verifyLicenseSignatures,
56
64
  verifyMerkleProof: () => verifyMerkleProof
57
65
  });
58
66
  module.exports = __toCommonJS(index_exports);
59
- var ed3 = __toESM(require("@noble/ed25519"));
67
+ var ed4 = __toESM(require("@noble/ed25519"));
60
68
 
61
69
  // src/canonical.ts
62
70
  function canonicalJson(obj) {
@@ -88,8 +96,8 @@ function canonicalJson(obj) {
88
96
  }
89
97
 
90
98
  // src/index.ts
91
- var import_verify = require("@pregen/verify");
92
99
  var import_verify2 = require("@pregen/verify");
100
+ var import_verify3 = require("@pregen/verify");
93
101
 
94
102
  // src/directory.ts
95
103
  var ed = __toESM(require("@noble/ed25519"));
@@ -268,9 +276,9 @@ var TrustedRegistryDirectory = class _TrustedRegistryDirectory {
268
276
  // src/license.ts
269
277
  var ed2 = __toESM(require("@noble/ed25519"));
270
278
  async function verifyLicenseSignatures(input) {
271
- const bytes = (hex, size) => {
272
- if (typeof hex !== "string" || hex.length !== size * 2 || !/^[0-9a-fA-F]+$/.test(hex)) throw new TypeError("Invalid hex");
273
- return Uint8Array.from(hex.match(/../g), (s) => parseInt(s, 16));
279
+ const bytes = (hex2, size) => {
280
+ if (typeof hex2 !== "string" || hex2.length !== size * 2 || !/^[0-9a-fA-F]+$/.test(hex2)) throw new TypeError("Invalid hex");
281
+ return Uint8Array.from(hex2.match(/../g), (s) => parseInt(s, 16));
274
282
  };
275
283
  let body;
276
284
  let subjectSig;
@@ -410,6 +418,576 @@ var PramptaReporter = class {
410
418
  }
411
419
  };
412
420
 
421
+ // src/pregen.ts
422
+ var ed3 = __toESM(require("@noble/ed25519"));
423
+ var import_verify = require("@pregen/verify");
424
+ var NotGeneratedError = class extends Error {
425
+ notGenerated = true;
426
+ constructor(message = "not generated") {
427
+ super(message);
428
+ this.name = "NotGeneratedError";
429
+ }
430
+ };
431
+ var PregenError = class extends Error {
432
+ constructor(code, message) {
433
+ super(`${code}: ${message}`);
434
+ this.code = code;
435
+ this.name = "PregenError";
436
+ }
437
+ code;
438
+ };
439
+ var PROFILE = "likeness/1";
440
+ var MAX_LIFETIME = 900;
441
+ var CLOCK_TOLERANCE = 60;
442
+ var JOB_ID = /^[A-Za-z0-9_.:-]{1,100}$/;
443
+ var HEX64 = /^[0-9a-f]{64}$/;
444
+ var STATUSES = /* @__PURE__ */ new Set(["allow", "deny", "review", "not_blocked"]);
445
+ var LEVELS = /* @__PURE__ */ new Set(["self", "asserted", "consented", "verified"]);
446
+ var PURPOSES = /* @__PURE__ */ new Set(["personal", "educational", "research", "editorial", "commercial"]);
447
+ var ANSWER = {
448
+ required: ["signers", "request_id", "profile", "status", "level", "obligations", "issued_at", "expires_at"],
449
+ optional: ["reason", "basis", "ext", "critical"]
450
+ };
451
+ var ACK_FIELDS = ["signers", "report", "answer", "answer_status", "counted", "released", "provider_key_id", "received_at"];
452
+ var enc = new TextEncoder();
453
+ var now = () => Math.floor(Date.now() / 1e3);
454
+ var hex = (b) => Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
455
+ var unhex = (s) => {
456
+ if (typeof s !== "string" || !/^(?:[0-9a-fA-F]{2})+$/.test(s)) throw new PregenError("PG_CONFIG", "not hex");
457
+ return Uint8Array.from(s.match(/../g), (h) => parseInt(h, 16));
458
+ };
459
+ var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
460
+ var str = (v) => typeof v === "string" && v.length > 0;
461
+ var keysWithin = (o, allowed) => Object.keys(o).every((k) => allowed.includes(k));
462
+ var isPgCode = (s) => {
463
+ try {
464
+ (0, import_verify.issuerOf)(s);
465
+ return true;
466
+ } catch {
467
+ return false;
468
+ }
469
+ };
470
+ var malformed = (why) => new PregenError("PG_MALFORMED", why);
471
+ async function sha2562(data) {
472
+ return hex(new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", data)));
473
+ }
474
+ function signingInput(type, body) {
475
+ return enc.encode(`PREGEN-V5\0${type}\0${canonicalJson(body)}`);
476
+ }
477
+ function messageId(type, body) {
478
+ return sha2562(enc.encode(`PREGEN-V5-ID\0${type}\0${canonicalJson(body)}`));
479
+ }
480
+ var keyIdOf = async (pub) => "pg-ed25519:" + (await sha2562(pub)).slice(0, 32);
481
+ async function verifyEnvelope(env, type, role, keyFor) {
482
+ const e = env;
483
+ if (!isObj(e) || Object.keys(e).sort().join() !== "body,signatures,type" || e.type !== type || !isObj(e.body) || !Array.isArray(e.signatures)) {
484
+ throw malformed(`not a ${type}`);
485
+ }
486
+ const s = e.body.signers;
487
+ const pub = Array.isArray(s) && s.length === 1 && s[0]?.role === role && e.signatures.length === 1 && typeof s[0].key_id === "string" ? await keyFor(s[0].key_id) : void 0;
488
+ if (!pub) throw new PregenError("PG_SIGNATURE", `not signed by a pinned ${role} key`);
489
+ let ok = false;
490
+ try {
491
+ ok = await ed3.verifyAsync(unhex(e.signatures[0]), signingInput(type, e.body), pub);
492
+ } catch {
493
+ }
494
+ if (!ok) throw new PregenError("PG_SIGNATURE", `${type} signature does not verify`);
495
+ return e.body;
496
+ }
497
+ function checkBasis(b) {
498
+ if (!isObj(b)) return false;
499
+ if (b.kind === "grant") {
500
+ const g = b.grant;
501
+ return keysWithin(b, ["kind", "grant"]) && isObj(g) && keysWithin(g, ["format", "id", "license_id"]) && str(g.format) && typeof g.id === "string" && Object.values(g).every((v) => typeof v === "string");
502
+ }
503
+ return b.kind === "policy" && keysWithin(b, ["kind", "rule"]) && str(b.rule);
504
+ }
505
+ async function checkAnswer(body, request, at) {
506
+ if (!ANSWER.required.every((k) => k in body) || !keysWithin(body, [...ANSWER.required, ...ANSWER.optional])) throw malformed("fields");
507
+ if (!str(body.request_id) || !str(body.profile) || !STATUSES.has(body.status) || !LEVELS.has(body.level)) throw malformed("values");
508
+ if (!Number.isSafeInteger(body.issued_at) || !Number.isSafeInteger(body.expires_at)) throw malformed("times must be integers");
509
+ if (!Array.isArray(body.obligations) || !body.obligations.every((o) => isObj(o) && Object.keys(o).sort().join() === "id,params" && str(o.id) && isObj(o.params))) throw malformed("obligations");
510
+ if ("ext" in body && !isObj(body.ext)) throw malformed("ext");
511
+ if ("critical" in body && !(Array.isArray(body.critical) && body.critical.every(str))) throw malformed("critical");
512
+ const shaped = body.status === "allow" ? checkBasis(body.basis) && !("reason" in body) : str(body.reason) && !("basis" in body);
513
+ if (!shaped) throw malformed("an allow has a valid basis and no reason; a refusal a reason and no basis");
514
+ if (body.critical?.length) throw new PregenError("PG_CRITICAL", body.critical.join());
515
+ if (!(body.expires_at - body.issued_at > 0 && body.expires_at - body.issued_at <= MAX_LIFETIME)) throw new PregenError("PG_TIME", "lifetime");
516
+ if (body.issued_at > at + CLOCK_TOLERANCE) throw new PregenError("PG_TIME", "issued in the future");
517
+ if (body.request_id !== await messageId("pg.request.v1", request)) throw new PregenError("PG_BINDING", "answer is for another request");
518
+ if (body.profile !== request.profile) throw new PregenError("PG_BINDING", "profile");
519
+ }
520
+ function readInput(input) {
521
+ let c;
522
+ try {
523
+ c = structuredClone(input);
524
+ } catch {
525
+ throw malformed("the input must be plain data");
526
+ }
527
+ const u = c?.use;
528
+ if (!isObj(c) || !JOB_ID.test(c.jobId ?? "")) throw malformed("jobId: 1-100 of A-Z a-z 0-9 _ . : -");
529
+ if (!Array.isArray(c.subjects) || !c.subjects.length || !c.subjects.every(str)) {
530
+ throw malformed("subjects: name at least one; with no registered person there is nothing to ask");
531
+ }
532
+ if (!str(c.model) || !str(c.modality)) throw malformed("model and modality");
533
+ if (c.prompt !== void 0 && typeof c.prompt !== "string") throw malformed("prompt");
534
+ if (c.promptHash !== void 0 && !HEX64.test(c.promptHash)) throw malformed("promptHash: 64 lowercase hex");
535
+ if (c.prompt === void 0 && c.promptHash === void 0) throw malformed("give prompt or promptHash");
536
+ if (c.licenseeId !== void 0 && typeof c.licenseeId !== "string") throw malformed("licenseeId");
537
+ if (!isObj(u) || !PURPOSES.has(u.purpose) || !keysWithin(u, ["purpose", "rights", "territory", "channel"]) || u.rights !== void 0 && !(Array.isArray(u.rights) && u.rights.every((r) => typeof r === "string")) || u.territory !== void 0 && typeof u.territory !== "string" || u.channel !== void 0 && typeof u.channel !== "string") {
538
+ throw malformed("use: purpose personal|educational|research|editorial|commercial; optional rights, territory, channel");
539
+ }
540
+ return c;
541
+ }
542
+ function createPregen(config) {
543
+ const fail2 = (why) => {
544
+ throw new PregenError("PG_CONFIG", why);
545
+ };
546
+ if (!isObj(config)) fail2("a configuration object is required");
547
+ let url;
548
+ try {
549
+ url = new URL(config.baseUrl);
550
+ } catch {
551
+ return fail2("baseUrl must be a URL");
552
+ }
553
+ const local = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
554
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && local)) fail2("use an HTTPS registry URL");
555
+ if (!str(config.providerId) || !str(config.token)) fail2("providerId and token are required strings");
556
+ const store = config.store;
557
+ if (!isObj(store) || !["claim", "update", "get", "pending"].every((m) => typeof store[m] === "function")) {
558
+ fail2("a store is required (postgresStore(pool), or memoryStore() for one process)");
559
+ }
560
+ const mode = config.mode === void 0 ? "enforce" : config.mode;
561
+ if (mode !== "enforce" && mode !== "observe") fail2(`mode must be "enforce" or "observe", not ${JSON.stringify(mode)}`);
562
+ const timeoutMs = config.timeoutMs ?? 5e3;
563
+ if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 6e4) fail2("timeoutMs: 1 to 60000");
564
+ const base = url.origin, providerId = config.providerId, dir = config.pregenDirectory;
565
+ const secret = unhex(config.providerSigningKeyHex);
566
+ if (secret.length !== 32) fail2("providerSigningKeyHex must be a 32-byte Ed25519 private key");
567
+ const rawPins = config.operatorPublicKeyHex;
568
+ const pins = (Array.isArray(rawPins) ? rawPins : typeof rawPins === "string" ? rawPins.split(/[\s,]+/) : []).filter(Boolean);
569
+ if (!pins.length && !dir || pins.some((p) => unhex(p).length !== 32)) {
570
+ fail2("operatorPublicKeyHex: one or more 32-byte public keys, hex (or give pregenDirectory)");
571
+ }
572
+ const sameBase = (api) => {
573
+ try {
574
+ const u = new URL(api);
575
+ return u.origin === base && /^\/*$/.test(u.pathname);
576
+ } catch {
577
+ return false;
578
+ }
579
+ };
580
+ const listed = dir?.registries.find((r) => sameBase(r.api));
581
+ if (dir && !listed) fail2(`${base} is not a registry listed in the PRE-GEN directory`);
582
+ const ready = (async () => {
583
+ const registry = /* @__PURE__ */ new Map();
584
+ for (const p of pins) registry.set(await keyIdOf(unhex(p)), unhex(p));
585
+ const pub = await ed3.getPublicKeyAsync(secret);
586
+ return { registry, pub, keyId: await keyIdOf(pub) };
587
+ })();
588
+ const fromDirectory = /* @__PURE__ */ new Map();
589
+ async function keyFor(keyId) {
590
+ const { registry } = await ready;
591
+ if (pins.length) return registry.get(keyId);
592
+ const code = listed.prefix ? `PG-${listed.prefix}-0` : "PG-0";
593
+ let p = fromDirectory.get(keyId);
594
+ if (!p) {
595
+ p = dir.publicKey(code, keyId).then(unhex, () => {
596
+ fromDirectory.delete(keyId);
597
+ return void 0;
598
+ });
599
+ fromDirectory.set(keyId, p);
600
+ }
601
+ return p;
602
+ }
603
+ const directoryVerdict = (subject, keyId) => !dir || !isPgCode(subject) ? "valid" : dir.checkSigner(subject, keyId);
604
+ async function post(path, payload) {
605
+ const resp = await fetch(base + path, {
606
+ method: "POST",
607
+ redirect: "error",
608
+ signal: AbortSignal.timeout(timeoutMs),
609
+ body: JSON.stringify(payload),
610
+ headers: { "Content-Type": "application/json", "X-Provider-ID": providerId, Authorization: `Bearer ${config.token}` }
611
+ });
612
+ let data = null;
613
+ try {
614
+ data = await resp.json();
615
+ } catch {
616
+ }
617
+ return { status: resp.status, data };
618
+ }
619
+ const errorCode = (r) => typeof r.data?.detail?.error === "string" && r.data.detail.error || `HTTP_${r.status}`;
620
+ async function seal(type, body) {
621
+ const { keyId } = await ready;
622
+ body = { ...body, signers: [{ role: "provider", key_id: keyId }] };
623
+ return { type, body, signatures: [hex(await ed3.signAsync(signingInput(type, body), secret))] };
624
+ }
625
+ async function ask(request) {
626
+ const subject = request.subject_ref;
627
+ if (dir && isPgCode(subject) && !(dir.registryFor(subject) && sameBase(dir.registryFor(subject).api))) {
628
+ return { request, answer: null, error: "PG_WRONG_REGISTRY" };
629
+ }
630
+ let r = null;
631
+ for (let attempt = 0; attempt < 3; attempt++) {
632
+ if (attempt) await new Promise((ok) => setTimeout(ok, 200 * 2 ** attempt));
633
+ try {
634
+ r = await post("/v2/requests", { type: "pg.request.v1", body: request, signatures: [] });
635
+ } catch {
636
+ r = null;
637
+ continue;
638
+ }
639
+ if (r.status !== 429 && r.status < 500) break;
640
+ }
641
+ if (!r) return { request, answer: null, error: "PG_UNREACHABLE" };
642
+ if (r.status !== 200) return { request, answer: null, error: errorCode(r) };
643
+ const answer = r.data?.answer;
644
+ try {
645
+ await verifyEnvelope(answer, "pg.answer.v1", "registry", keyFor);
646
+ const verdict = directoryVerdict(subject, answer.body.signers[0].key_id);
647
+ if (verdict !== "valid") throw new PregenError(`PG_KEY_${verdict.toUpperCase()}`, "the directory does not list this key");
648
+ } catch (e) {
649
+ return { request, answer: null, error: e instanceof PregenError ? e.code : "PG_MALFORMED" };
650
+ }
651
+ try {
652
+ await checkAnswer(answer.body, request, now());
653
+ return { request, answer };
654
+ } catch (e) {
655
+ const code = e.code;
656
+ return code === "PG_BINDING" ? { request, answer: null, error: code } : { request, answer, error: code };
657
+ }
658
+ }
659
+ const usableAllow = (a, at) => !a.error && a.answer?.body.status === "allow" && a.answer.body.expires_at > at;
660
+ const obligationsOfAsk = (a) => a.answer && a.error !== "PG_MALFORMED" ? a.answer.body.obligations.map((o) => ({ subject: a.request.subject_ref, id: o.id, params: o.params })) : [];
661
+ const obligationsOf = (job) => job.asks.flatMap(obligationsOfAsk);
662
+ const applies = (applied, o) => applied.some((x) => x.subject === o.subject && x.id === o.id);
663
+ async function report(a, outcome, outputHash, applied) {
664
+ const answer = a.answer ? await messageId("pg.answer.v1", a.answer.body) : null;
665
+ const body = {
666
+ request_id: await messageId("pg.request.v1", a.request),
667
+ answer,
668
+ mode: a.request.mode,
669
+ job_id: a.request.job_id,
670
+ outcome,
671
+ output_hash: outcome === "generated" ? outputHash : "",
672
+ obligations_applied: outcome === "generated" ? [...new Set(obligationsOfAsk(a).filter((o) => applies(applied, o)).map((o) => o.id))].sort() : [],
673
+ occurred_at: now()
674
+ };
675
+ if (answer === null) body.request = a.request;
676
+ return { subject: a.request.subject_ref, answerStatus: a.answer?.body.status ?? "none", report: await seal("pg.report.v1", body) };
677
+ }
678
+ async function checkAck(r, env) {
679
+ const { keyId } = await ready;
680
+ const ack = await verifyEnvelope(env, "pg.report_ack.v1", "registry", keyFor);
681
+ const verdict = directoryVerdict(r.subject, ack.signers[0].key_id);
682
+ if (verdict !== "valid") throw new PregenError(`PG_KEY_${verdict.toUpperCase()}`, "the directory does not list this key");
683
+ if (Object.keys(ack).sort().join() !== [...ACK_FIELDS].sort().join()) throw malformed("acknowledgement fields");
684
+ if (ack.report !== await messageId("pg.report.v1", r.report.body) || ack.answer !== r.report.body.answer || ack.answer_status !== r.answerStatus || ack.provider_key_id !== keyId) {
685
+ throw new PregenError("PG_BINDING", "the acknowledgement is not for this report");
686
+ }
687
+ if (!(ack.counted === null || HEX64.test(ack.counted)) || !Array.isArray(ack.released) || !ack.released.every((x) => typeof x === "string" && HEX64.test(x)) || !Number.isSafeInteger(ack.received_at)) {
688
+ throw malformed("acknowledgement values");
689
+ }
690
+ }
691
+ const reread = async (job) => await store.get(providerId, job.jobId) ?? job;
692
+ async function send(job) {
693
+ const { keyId } = await ready;
694
+ for (const r of job.reports) {
695
+ if (r.ack || r.rejected) continue;
696
+ let res;
697
+ try {
698
+ res = await post("/v2/reports", r.report);
699
+ if (res.status === 401 && r.report.body.signers?.[0]?.key_id !== keyId) {
700
+ const { signers: _, ...body } = r.report.body;
701
+ r.report = await seal("pg.report.v1", body);
702
+ res = await post("/v2/reports", r.report);
703
+ }
704
+ } catch {
705
+ r.error = "PG_UNREACHABLE";
706
+ continue;
707
+ }
708
+ if (res.status === 409 || res.status === 422) {
709
+ r.rejected = true;
710
+ r.error = errorCode(res);
711
+ continue;
712
+ }
713
+ if (res.status !== 200) {
714
+ r.error = errorCode(res);
715
+ continue;
716
+ }
717
+ try {
718
+ await checkAck(r, res.data?.ack);
719
+ r.ack = res.data.ack;
720
+ delete r.error;
721
+ } catch (e) {
722
+ r.error = e instanceof PregenError ? e.code : "PG_MALFORMED";
723
+ }
724
+ }
725
+ const next = { ...job, stage: job.reports.every((r) => r.ack || r.rejected) ? "done" : "finished" };
726
+ return await store.update(next, "finished") ? next : reread(job);
727
+ }
728
+ function result(job, output, override) {
729
+ const unknown = job.stage === "claimed" || job.stage === "asked";
730
+ const rejected = job.reports.some((r) => r.rejected);
731
+ return {
732
+ jobId: job.jobId,
733
+ status: unknown || rejected || override ? "needs_check" : job.stage === "done" ? "confirmed" : "report_pending",
734
+ generated: unknown ? null : !!job.generated,
735
+ ...output !== void 0 ? { output } : {},
736
+ ...job.outputHash ? { outputHash: job.outputHash } : {},
737
+ answers: job.asks.map((a) => ({
738
+ subject: a.request.subject_ref,
739
+ status: a.answer?.body.status ?? null,
740
+ ...typeof a.answer?.body.reason === "string" ? { reason: a.answer.body.reason } : {},
741
+ ...a.error ? { error: a.error } : {}
742
+ })),
743
+ unmetObligations: job.unmetObligations ?? [],
744
+ detail: override ?? (unknown ? "the outcome is unknown (interrupted, the generator threw, or running in another worker): check your generator for this job, then resolve()" : rejected ? "the registry refused a report: see its error" : void 0)
745
+ };
746
+ }
747
+ async function finish(job, generated, outputHash = "", applied = []) {
748
+ const asks = generated ? job.asks : job.asks.filter((a) => a.answer?.body.status === "allow");
749
+ const next = {
750
+ ...job,
751
+ stage: "finished",
752
+ generated,
753
+ ...generated ? { outputHash } : {},
754
+ reports: await Promise.all(asks.map((a) => report(a, generated ? "generated" : "not_generated", outputHash, applied))),
755
+ unmetObligations: generated ? obligationsOf(job).filter((o) => !applies(applied, o)) : []
756
+ };
757
+ if (await store.update(next, "asked")) return send(next);
758
+ const stored = await reread(job);
759
+ return stored.stage === "finished" ? send(stored) : stored;
760
+ }
761
+ async function outputHashOf(r) {
762
+ if (!isObj(r)) throw malformed("the generator must return { output, outputHash | outputBytes }");
763
+ if (r.outputHash !== void 0) {
764
+ if (typeof r.outputHash !== "string" || !HEX64.test(r.outputHash)) throw malformed("outputHash must be 64 lowercase hex characters");
765
+ return r.outputHash;
766
+ }
767
+ if (typeof r.outputBytes === "string") return sha2562(enc.encode(r.outputBytes));
768
+ if (r.outputBytes instanceof Uint8Array) return sha2562(r.outputBytes);
769
+ throw malformed("return outputHash or outputBytes");
770
+ }
771
+ function appliedOf(list) {
772
+ if (list === void 0) return [];
773
+ if (!Array.isArray(list) || !list.every((x) => isObj(x) && str(x.subject) && str(x.id))) {
774
+ throw malformed("obligationsApplied: the { subject, id } obligations you carried out");
775
+ }
776
+ return list.map((x) => ({ subject: x.subject, id: x.id }));
777
+ }
778
+ async function currentKeyset() {
779
+ const resp = await fetch(`${base}/v2/keys/${encodeURIComponent(providerId)}`, { redirect: "error", signal: AbortSignal.timeout(timeoutMs) });
780
+ const chain = resp.ok ? (await resp.json())?.keysets : null;
781
+ const keyset = Array.isArray(chain) && chain.length ? chain[chain.length - 1] : null;
782
+ if (!keyset || !isObj(keyset.body) || !Array.isArray(keyset.body.keys)) return null;
783
+ return {
784
+ keyset,
785
+ id: await messageId("pg.keyset.v1", keyset.body),
786
+ current: keyset.body.keys.find((k) => k.status === "current")
787
+ };
788
+ }
789
+ async function flush() {
790
+ const out = [];
791
+ for (const job of await store.pending(providerId)) out.push(result(await send(job)));
792
+ return out;
793
+ }
794
+ async function run(input, fn) {
795
+ const subjects = [...new Set(input.subjects)].sort();
796
+ const promptHash = input.promptHash ?? await sha2562(enc.encode(input.prompt));
797
+ const manifest = {
798
+ job_id: input.jobId,
799
+ provider_id: providerId,
800
+ licensee_id: input.licenseeId ?? "",
801
+ prompt_hash: promptHash,
802
+ model: input.model,
803
+ modality: input.modality,
804
+ mode,
805
+ asks: subjects.map((s) => ({ subject_ref: s, registry: "", profile: PROFILE, use: input.use }))
806
+ };
807
+ const jobHash = await sha2562(enc.encode(canonicalJson(manifest)));
808
+ const fresh = { providerId, jobId: input.jobId, jobHash, stage: "claimed", asks: [], reports: [] };
809
+ const existing = await store.claim(fresh);
810
+ if (existing) {
811
+ if (existing.jobHash !== jobHash) throw new PregenError("PG_JOB_CONFLICT", `job ${input.jobId} was used for another generation`);
812
+ return result(existing.stage === "finished" ? await send(existing) : existing);
813
+ }
814
+ const asked = { ...fresh, stage: "asked", asks: await Promise.all(subjects.map((s) => ask({
815
+ job_id: input.jobId,
816
+ job_hash: jobHash,
817
+ subject_ref: s,
818
+ provider_id: providerId,
819
+ licensee_id: manifest.licensee_id,
820
+ prompt_hash: promptHash,
821
+ model: input.model,
822
+ modality: input.modality,
823
+ profile: PROFILE,
824
+ use: input.use,
825
+ mode
826
+ }))) };
827
+ if (!await store.update(asked, "claimed")) return result(await reread(fresh));
828
+ const at = now();
829
+ if (mode === "enforce" && !asked.asks.every((a) => usableAllow(a, at))) return result(await finish(asked, false));
830
+ let out;
831
+ try {
832
+ out = await fn({ obligations: obligationsOf(asked) });
833
+ } catch (e) {
834
+ if (e?.notGenerated === true) await finish(asked, false);
835
+ throw e;
836
+ }
837
+ const done = await finish(asked, true, await outputHashOf(out), appliedOf(out.obligationsApplied));
838
+ return result(done, out.output, done.generated ? void 0 : "another worker recorded that this job was not generated");
839
+ }
840
+ return {
841
+ /** Ask, verify, generate (only when permitted), report. See the module comment. */
842
+ async generate(input, fn) {
843
+ const copy = readInput(input);
844
+ if (typeof fn !== "function") throw malformed("pass your generator function");
845
+ return run(copy, fn);
846
+ },
847
+ /** Resend every report still waiting (after a restart, an outage). Run at startup and every minute or so. */
848
+ flush,
849
+ /** Where a job stands, or null if this store never saw it. */
850
+ async status(jobId) {
851
+ const job = await store.get(providerId, jobId);
852
+ return job && result(job);
853
+ },
854
+ /** After `needs_check`: record what your generator actually did with this job. Safe to call from several workers:
855
+ * the first recorded outcome stands. */
856
+ async resolve(jobId, outcome) {
857
+ if (!isObj(outcome) || typeof outcome.generated !== "boolean") throw malformed("outcome: { generated: true | false, ... }");
858
+ const job = await store.get(providerId, jobId);
859
+ if (!job) throw new PregenError("PG_UNKNOWN_JOB", jobId);
860
+ if (job.stage === "finished") return result(await send(job));
861
+ if (job.stage === "done") return result(job);
862
+ if (!outcome.generated) {
863
+ if (job.stage === "claimed") {
864
+ const closed = { ...job, stage: "done", generated: false };
865
+ if (await store.update(closed, "claimed")) return result(closed);
866
+ const stored = await reread(job);
867
+ return result(stored.stage === "finished" ? await send(stored) : stored);
868
+ }
869
+ return result(await finish(job, false));
870
+ }
871
+ if (job.stage === "claimed") throw new PregenError("PG_JOB", "generation cannot have started before the answers were stored");
872
+ return result(await finish(job, true, await outputHashOf(outcome), appliedOf(outcome.obligationsApplied)));
873
+ },
874
+ /** Register this provider's signing key with the registry, once (a `first` keyset, §4.9). Calling it again
875
+ * with the same key is harmless. */
876
+ async registerKey() {
877
+ const { pub, keyId } = await ready;
878
+ const keyset = await seal("pg.keyset.v1", {
879
+ kind: "first",
880
+ provider_id: providerId,
881
+ previous: null,
882
+ recovery_registry: "",
883
+ keys: [{ key_id: keyId, public_key_hex: hex(pub), status: "current" }]
884
+ });
885
+ const r = await post("/v2/keys", keyset);
886
+ if (r.status === 409) {
887
+ const latest = await currentKeyset();
888
+ if (latest?.current?.key_id === keyId) return { keyId, keysetId: latest.id };
889
+ }
890
+ if (r.status !== 200) throw new PregenError(errorCode(r), r.data?.detail?.error_description ?? "key not registered");
891
+ return { keyId, keysetId: r.data.keyset_id };
892
+ },
893
+ /** Replace this client's signing key with `newSigningKeyHex` (a `rotation` keyset signed by both keys).
894
+ * Then create the client again with the new key, in every worker. Reports already acknowledged keep their
895
+ * acknowledgements; reports still waiting, or sent by a worker that still has the old key, are signed again
896
+ * with the new key by the next `flush()` of a client that has it. */
897
+ async rotateKey(newSigningKeyHex) {
898
+ const next = unhex(newSigningKeyHex);
899
+ if (next.length !== 32) throw new PregenError("PG_CONFIG", "the new key must be a 32-byte Ed25519 private key");
900
+ const { pub, keyId } = await ready;
901
+ await flush();
902
+ const chain = await currentKeyset();
903
+ if (!chain || chain.current?.key_id !== keyId) {
904
+ throw new PregenError("PG_KEYSET", "the registry's current key is not this client's key: registerKey() first");
905
+ }
906
+ const latest = chain.keyset;
907
+ const nextPub = await ed3.getPublicKeyAsync(next), nextId = await keyIdOf(nextPub);
908
+ const body = {
909
+ kind: "rotation",
910
+ provider_id: providerId,
911
+ previous: await messageId("pg.keyset.v1", latest.body),
912
+ recovery_registry: latest.body.recovery_registry,
913
+ keys: [
914
+ { key_id: nextId, public_key_hex: hex(nextPub), status: "current" },
915
+ { key_id: keyId, public_key_hex: hex(pub), status: "retired" }
916
+ ],
917
+ signers: [{ role: "provider", key_id: nextId }, { role: "provider_previous", key_id: keyId }]
918
+ };
919
+ const data = signingInput("pg.keyset.v1", body);
920
+ const keyset = { type: "pg.keyset.v1", body, signatures: [hex(await ed3.signAsync(data, next)), hex(await ed3.signAsync(data, secret))] };
921
+ const r = await post("/v2/keys", keyset);
922
+ if (r.status !== 200) throw new PregenError(errorCode(r), r.data?.detail?.error_description ?? "key not rotated");
923
+ return { keyId: nextId, keysetId: r.data.keyset_id };
924
+ }
925
+ };
926
+ }
927
+ function memoryStore() {
928
+ const jobs = /* @__PURE__ */ new Map();
929
+ const k = (p, j) => `${p}\0${j}`;
930
+ return {
931
+ async claim(job) {
932
+ const found = jobs.get(k(job.providerId, job.jobId));
933
+ if (found) return structuredClone(found);
934
+ jobs.set(k(job.providerId, job.jobId), structuredClone(job));
935
+ return null;
936
+ },
937
+ async update(job, from) {
938
+ if (jobs.get(k(job.providerId, job.jobId))?.stage !== from) return false;
939
+ jobs.set(k(job.providerId, job.jobId), structuredClone(job));
940
+ return true;
941
+ },
942
+ async get(p, j) {
943
+ const f = jobs.get(k(p, j));
944
+ return f ? structuredClone(f) : null;
945
+ },
946
+ async pending(p) {
947
+ return [...jobs.values()].filter((j) => j.providerId === p && j.stage === "finished").map((j) => structuredClone(j));
948
+ }
949
+ };
950
+ }
951
+ var postgresSchema = `CREATE TABLE IF NOT EXISTS pregen_jobs (
952
+ provider_id text NOT NULL, job_id text NOT NULL, stage text NOT NULL, job jsonb NOT NULL,
953
+ updated_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY (provider_id, job_id))`;
954
+ function postgresStore(pool) {
955
+ let ready = null;
956
+ const q = async (text, values) => {
957
+ ready ??= pool.query(`DO $$ BEGIN
958
+ IF to_regclass('pregen_jobs') IS NULL THEN
959
+ PERFORM pg_advisory_xact_lock(hashtext('pregen_jobs'));
960
+ ${postgresSchema};
961
+ END IF;
962
+ END $$`).catch((e) => {
963
+ ready = null;
964
+ throw e;
965
+ });
966
+ await ready;
967
+ return (await pool.query(text, values)).rows;
968
+ };
969
+ const one = async (p, j) => (await q("SELECT job FROM pregen_jobs WHERE provider_id = $1 AND job_id = $2", [p, j]))[0]?.job ?? null;
970
+ return {
971
+ async claim(job) {
972
+ const won = await q(`INSERT INTO pregen_jobs (provider_id, job_id, stage, job) VALUES ($1, $2, $3, $4)
973
+ ON CONFLICT DO NOTHING RETURNING job_id`, [job.providerId, job.jobId, job.stage, JSON.stringify(job)]);
974
+ return won.length ? null : one(job.providerId, job.jobId);
975
+ },
976
+ async update(job, from) {
977
+ return (await q(
978
+ `UPDATE pregen_jobs SET stage = $3, job = $4, updated_at = now()
979
+ WHERE provider_id = $1 AND job_id = $2 AND stage = $5 RETURNING job_id`,
980
+ [job.providerId, job.jobId, job.stage, JSON.stringify(job), from]
981
+ )).length > 0;
982
+ },
983
+ get: one,
984
+ async pending(p) {
985
+ return (await q(`SELECT job FROM pregen_jobs WHERE provider_id = $1 AND stage = 'finished'
986
+ ORDER BY updated_at LIMIT 100`, [p])).map((r) => r.job);
987
+ }
988
+ };
989
+ }
990
+
413
991
  // src/index.ts
414
992
  function singleProcessJournal() {
415
993
  const entries = /* @__PURE__ */ new Map();
@@ -554,16 +1132,16 @@ function envVar(key) {
554
1132
  return "";
555
1133
  }
556
1134
  async function hashPrompt(prompt) {
557
- return sha2562(prompt);
1135
+ return sha2563(prompt);
558
1136
  }
559
- async function sha2562(input) {
1137
+ async function sha2563(input) {
560
1138
  const data = new TextEncoder().encode(input);
561
1139
  return sha256Bytes(data);
562
1140
  }
563
1141
  async function verifyMerkleProof(leafHash, proof, rootHash) {
564
1142
  let current = leafHash;
565
1143
  for (const step of proof) {
566
- current = step.position === "right" ? await sha2562(current + step.hash) : await sha2562(step.hash + current);
1144
+ current = step.position === "right" ? await sha2563(current + step.hash) : await sha2563(step.hash + current);
567
1145
  }
568
1146
  return current === rootHash;
569
1147
  }
@@ -583,13 +1161,13 @@ async function sha256Bytes(data) {
583
1161
  const { createHash } = await import("crypto");
584
1162
  return createHash("sha256").update(data).digest("hex");
585
1163
  }
586
- function hexToBytes(hex) {
587
- if (typeof hex !== "string" || !/^(?:[0-9a-fA-F]{2})+$/.test(hex)) {
1164
+ function hexToBytes(hex2) {
1165
+ if (typeof hex2 !== "string" || !/^(?:[0-9a-fA-F]{2})+$/.test(hex2)) {
588
1166
  throw new PramptaSignatureError("Invalid hexadecimal key or signature");
589
1167
  }
590
- const bytes = new Uint8Array(hex.length / 2);
591
- for (let i = 0; i < hex.length; i += 2) {
592
- bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
1168
+ const bytes = new Uint8Array(hex2.length / 2);
1169
+ for (let i = 0; i < hex2.length; i += 2) {
1170
+ bytes[i / 2] = parseInt(hex2.substring(i, i + 2), 16);
593
1171
  }
594
1172
  return bytes;
595
1173
  }
@@ -718,7 +1296,7 @@ var Prampta = class _Prampta {
718
1296
  assertPregenEndpoint(subjectId) {
719
1297
  if (!this.pregenDirectory) return;
720
1298
  try {
721
- (0, import_verify.issuerOf)(subjectId);
1299
+ (0, import_verify2.issuerOf)(subjectId);
722
1300
  } catch {
723
1301
  return;
724
1302
  }
@@ -741,7 +1319,7 @@ var Prampta = class _Prampta {
741
1319
  if (!currentHex || !sig) throw new PramptaSignatureError("Operator key set from /keys is malformed");
742
1320
  const body = { ...ks };
743
1321
  delete body.signature;
744
- const ok = await ed3.verifyAsync(hexToBytes(sig), new TextEncoder().encode(canonicalJson(body)), hexToBytes(currentHex));
1322
+ const ok = await ed4.verifyAsync(hexToBytes(sig), new TextEncoder().encode(canonicalJson(body)), hexToBytes(currentHex));
745
1323
  if (!ok) throw new PramptaSignatureError("Operator key set signature is invalid");
746
1324
  this.keySetCache = ks;
747
1325
  return ks;
@@ -756,8 +1334,8 @@ var Prampta = class _Prampta {
756
1334
  * from the self-consistent /keys set with a warning.
757
1335
  */
758
1336
  async resolveOperatorKey(keyId) {
759
- for (const hex of this.pinnedKeys) {
760
- if (await keyFingerprint(hex) === keyId) return hex;
1337
+ for (const hex2 of this.pinnedKeys) {
1338
+ if (await keyFingerprint(hex2) === keyId) return hex2;
761
1339
  }
762
1340
  if (this.pinnedKeys.length > 0) {
763
1341
  throw new PramptaSignatureError(
@@ -790,11 +1368,11 @@ var Prampta = class _Prampta {
790
1368
  * copy of the subject index. Call verify()/assertAllowed() per hit.
791
1369
  */
792
1370
  async matchSubjects(text, opts) {
793
- const now = Date.now();
794
- if (opts?.forceRefresh || !this.subjectIndexEntries || now - this.subjectIndexFetchedAt > _Prampta.SUBJECT_INDEX_TTL_MS) {
1371
+ const now2 = Date.now();
1372
+ if (opts?.forceRefresh || !this.subjectIndexEntries || now2 - this.subjectIndexFetchedAt > _Prampta.SUBJECT_INDEX_TTL_MS) {
795
1373
  const payload = await this.fetchSubjectIndex();
796
1374
  this.subjectIndexEntries = payload.subjects || [];
797
- this.subjectIndexFetchedAt = now;
1375
+ this.subjectIndexFetchedAt = now2;
798
1376
  }
799
1377
  return matchSubjects(text, this.subjectIndexEntries);
800
1378
  }
@@ -882,7 +1460,7 @@ var Prampta = class _Prampta {
882
1460
  const sigBytes = hexToBytes(sig);
883
1461
  let valid;
884
1462
  try {
885
- valid = await ed3.verifyAsync(sigBytes, messageBytes, pubKeyBytes);
1463
+ valid = await ed4.verifyAsync(sigBytes, messageBytes, pubKeyBytes);
886
1464
  } catch (e) {
887
1465
  throw new PramptaSignatureError(`Ed25519 verification error: ${e}`);
888
1466
  }
@@ -891,13 +1469,13 @@ var Prampta = class _Prampta {
891
1469
  "Operator signature is invalid \u2014 decision cannot be trusted. This may indicate a compromised backend, proxy tampering, or key mismatch."
892
1470
  );
893
1471
  }
894
- const now = Math.floor(Date.now() / 1e3);
895
- if (!Number.isSafeInteger(raw.expires_at) || !Number.isSafeInteger(raw.issued_at) || raw.issued_at <= 0 || raw.expires_at <= raw.issued_at || raw.expires_at - raw.issued_at > 900 || raw.issued_at > now + 60) {
1472
+ const now2 = Math.floor(Date.now() / 1e3);
1473
+ if (!Number.isSafeInteger(raw.expires_at) || !Number.isSafeInteger(raw.issued_at) || raw.issued_at <= 0 || raw.expires_at <= raw.issued_at || raw.expires_at - raw.issued_at > 900 || raw.issued_at > now2 + 60) {
896
1474
  throw new PramptaSchemaError("Decision has invalid validity timestamps");
897
1475
  }
898
- if (decision.expiresAt <= now) {
1476
+ if (decision.expiresAt <= now2) {
899
1477
  throw new PramptaSignatureError(
900
- `Decision expired at ${decision.expiresAt}, current time ${now}`
1478
+ `Decision expired at ${decision.expiresAt}, current time ${now2}`
901
1479
  );
902
1480
  }
903
1481
  }
@@ -983,7 +1561,7 @@ var Prampta = class _Prampta {
983
1561
  } : void 0 };
984
1562
  this.registryDirectory?.assertEndpoint(request.subjectId, this.baseUrl);
985
1563
  this.assertPregenEndpoint(request.subjectId);
986
- const promptHash = request.promptHash || (request.prompt ? await sha2562(request.prompt) : null);
1564
+ const promptHash = request.promptHash || (request.prompt ? await sha2563(request.prompt) : null);
987
1565
  if (!promptHash) {
988
1566
  throw new PramptaSchemaError(
989
1567
  "Either prompt or promptHash is required. SDK does not allow empty prompt hashes to prevent unbound decisions."
@@ -1056,7 +1634,7 @@ var Prampta = class _Prampta {
1056
1634
  });
1057
1635
  if (this.pregenDirectory && decision.disposition === "allow") {
1058
1636
  try {
1059
- await (0, import_verify.checkDecision)(
1637
+ await (0, import_verify2.checkDecision)(
1060
1638
  data,
1061
1639
  { ...payload, provider_id: this.providerId, licensee_id: this.licenseeId || "" },
1062
1640
  { directory: this.pregenDirectory }
@@ -1160,7 +1738,7 @@ var Prampta = class _Prampta {
1160
1738
  /** SHA-256 of everything that defines the job. Same id + different hash = a different job. */
1161
1739
  async requestHash(request) {
1162
1740
  const { generationId, subjectIds, prompt, promptHash, idempotencyKey, returnUrl, detectionId, ...rest } = request;
1163
- return sha2562(canonicalJson(JSON.parse(JSON.stringify({
1741
+ return sha2563(canonicalJson(JSON.parse(JSON.stringify({
1164
1742
  ...rest,
1165
1743
  generationId,
1166
1744
  subjectIds: [...new Set(subjectIds)].sort(),
@@ -1318,7 +1896,7 @@ var Prampta = class _Prampta {
1318
1896
  provider_id: this.providerId,
1319
1897
  event_type: eventType
1320
1898
  };
1321
- const signature = await ed3.signAsync(
1899
+ const signature = await ed4.signAsync(
1322
1900
  new TextEncoder().encode(canonicalJson(body)),
1323
1901
  hexToBytes(keyHex)
1324
1902
  );
@@ -1374,7 +1952,7 @@ var Prampta = class _Prampta {
1374
1952
  return this.get("/version");
1375
1953
  }
1376
1954
  static async hashPrompt(prompt) {
1377
- return sha2562(prompt);
1955
+ return sha2563(prompt);
1378
1956
  }
1379
1957
  // ── Internal ─────────────────────────────────────────────────────────
1380
1958
  parseDecision(data) {
@@ -1503,6 +2081,7 @@ function sameEndpoint(a, b) {
1503
2081
  // Annotate the CommonJS export names for ESM import in node:
1504
2082
  0 && (module.exports = {
1505
2083
  HARD_REFUSAL_CODES,
2084
+ NotGeneratedError,
1506
2085
  Prampta,
1507
2086
  PramptaApiError,
1508
2087
  PramptaError,
@@ -1514,15 +2093,22 @@ function sameEndpoint(a, b) {
1514
2093
  PramptaSchemaError,
1515
2094
  PramptaSignatureError,
1516
2095
  PramptaTimeoutError,
2096
+ PregenError,
1517
2097
  REFUSAL_DESCRIPTIONS,
1518
2098
  RegistryDirectoryError,
1519
2099
  SOFT_REFUSAL_CODES,
1520
2100
  TrustedRegistryDirectory,
1521
2101
  canonicalJson,
2102
+ createPregen,
1522
2103
  hashPrompt,
1523
2104
  loadPregenDirectory,
1524
2105
  matchSubjects,
2106
+ memoryStore,
2107
+ messageId,
1525
2108
  normalizeForMatch,
2109
+ postgresSchema,
2110
+ postgresStore,
2111
+ signingInput,
1526
2112
  singleProcessJournal,
1527
2113
  verifyLicenseSignatures,
1528
2114
  verifyMerkleProof