@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/README.md +114 -1
- package/dist/index.d.mts +188 -1
- package/dist/index.d.ts +188 -1
- package/dist/index.js +617 -31
- package/dist/index.mjs +605 -27
- package/package.json +5 -3
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import * as
|
|
2
|
+
import * as ed4 from "@noble/ed25519";
|
|
3
3
|
|
|
4
4
|
// src/canonical.ts
|
|
5
5
|
function canonicalJson(obj) {
|
|
@@ -211,9 +211,9 @@ var TrustedRegistryDirectory = class _TrustedRegistryDirectory {
|
|
|
211
211
|
// src/license.ts
|
|
212
212
|
import * as ed2 from "@noble/ed25519";
|
|
213
213
|
async function verifyLicenseSignatures(input) {
|
|
214
|
-
const bytes = (
|
|
215
|
-
if (typeof
|
|
216
|
-
return Uint8Array.from(
|
|
214
|
+
const bytes = (hex2, size) => {
|
|
215
|
+
if (typeof hex2 !== "string" || hex2.length !== size * 2 || !/^[0-9a-fA-F]+$/.test(hex2)) throw new TypeError("Invalid hex");
|
|
216
|
+
return Uint8Array.from(hex2.match(/../g), (s) => parseInt(s, 16));
|
|
217
217
|
};
|
|
218
218
|
let body;
|
|
219
219
|
let subjectSig;
|
|
@@ -353,6 +353,576 @@ var PramptaReporter = class {
|
|
|
353
353
|
}
|
|
354
354
|
};
|
|
355
355
|
|
|
356
|
+
// src/pregen.ts
|
|
357
|
+
import * as ed3 from "@noble/ed25519";
|
|
358
|
+
import { issuerOf } from "@pregen/verify";
|
|
359
|
+
var NotGeneratedError = class extends Error {
|
|
360
|
+
notGenerated = true;
|
|
361
|
+
constructor(message = "not generated") {
|
|
362
|
+
super(message);
|
|
363
|
+
this.name = "NotGeneratedError";
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
var PregenError = class extends Error {
|
|
367
|
+
constructor(code, message) {
|
|
368
|
+
super(`${code}: ${message}`);
|
|
369
|
+
this.code = code;
|
|
370
|
+
this.name = "PregenError";
|
|
371
|
+
}
|
|
372
|
+
code;
|
|
373
|
+
};
|
|
374
|
+
var PROFILE = "likeness/1";
|
|
375
|
+
var MAX_LIFETIME = 900;
|
|
376
|
+
var CLOCK_TOLERANCE = 60;
|
|
377
|
+
var JOB_ID = /^[A-Za-z0-9_.:-]{1,100}$/;
|
|
378
|
+
var HEX64 = /^[0-9a-f]{64}$/;
|
|
379
|
+
var STATUSES = /* @__PURE__ */ new Set(["allow", "deny", "review", "not_blocked"]);
|
|
380
|
+
var LEVELS = /* @__PURE__ */ new Set(["self", "asserted", "consented", "verified"]);
|
|
381
|
+
var PURPOSES = /* @__PURE__ */ new Set(["personal", "educational", "research", "editorial", "commercial"]);
|
|
382
|
+
var ANSWER = {
|
|
383
|
+
required: ["signers", "request_id", "profile", "status", "level", "obligations", "issued_at", "expires_at"],
|
|
384
|
+
optional: ["reason", "basis", "ext", "critical"]
|
|
385
|
+
};
|
|
386
|
+
var ACK_FIELDS = ["signers", "report", "answer", "answer_status", "counted", "released", "provider_key_id", "received_at"];
|
|
387
|
+
var enc = new TextEncoder();
|
|
388
|
+
var now = () => Math.floor(Date.now() / 1e3);
|
|
389
|
+
var hex = (b) => Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
|
|
390
|
+
var unhex = (s) => {
|
|
391
|
+
if (typeof s !== "string" || !/^(?:[0-9a-fA-F]{2})+$/.test(s)) throw new PregenError("PG_CONFIG", "not hex");
|
|
392
|
+
return Uint8Array.from(s.match(/../g), (h) => parseInt(h, 16));
|
|
393
|
+
};
|
|
394
|
+
var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
395
|
+
var str = (v) => typeof v === "string" && v.length > 0;
|
|
396
|
+
var keysWithin = (o, allowed) => Object.keys(o).every((k) => allowed.includes(k));
|
|
397
|
+
var isPgCode = (s) => {
|
|
398
|
+
try {
|
|
399
|
+
issuerOf(s);
|
|
400
|
+
return true;
|
|
401
|
+
} catch {
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
var malformed = (why) => new PregenError("PG_MALFORMED", why);
|
|
406
|
+
async function sha2562(data) {
|
|
407
|
+
return hex(new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", data)));
|
|
408
|
+
}
|
|
409
|
+
function signingInput(type, body) {
|
|
410
|
+
return enc.encode(`PREGEN-V5\0${type}\0${canonicalJson(body)}`);
|
|
411
|
+
}
|
|
412
|
+
function messageId(type, body) {
|
|
413
|
+
return sha2562(enc.encode(`PREGEN-V5-ID\0${type}\0${canonicalJson(body)}`));
|
|
414
|
+
}
|
|
415
|
+
var keyIdOf = async (pub) => "pg-ed25519:" + (await sha2562(pub)).slice(0, 32);
|
|
416
|
+
async function verifyEnvelope(env, type, role, keyFor) {
|
|
417
|
+
const e = env;
|
|
418
|
+
if (!isObj(e) || Object.keys(e).sort().join() !== "body,signatures,type" || e.type !== type || !isObj(e.body) || !Array.isArray(e.signatures)) {
|
|
419
|
+
throw malformed(`not a ${type}`);
|
|
420
|
+
}
|
|
421
|
+
const s = e.body.signers;
|
|
422
|
+
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;
|
|
423
|
+
if (!pub) throw new PregenError("PG_SIGNATURE", `not signed by a pinned ${role} key`);
|
|
424
|
+
let ok = false;
|
|
425
|
+
try {
|
|
426
|
+
ok = await ed3.verifyAsync(unhex(e.signatures[0]), signingInput(type, e.body), pub);
|
|
427
|
+
} catch {
|
|
428
|
+
}
|
|
429
|
+
if (!ok) throw new PregenError("PG_SIGNATURE", `${type} signature does not verify`);
|
|
430
|
+
return e.body;
|
|
431
|
+
}
|
|
432
|
+
function checkBasis(b) {
|
|
433
|
+
if (!isObj(b)) return false;
|
|
434
|
+
if (b.kind === "grant") {
|
|
435
|
+
const g = b.grant;
|
|
436
|
+
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");
|
|
437
|
+
}
|
|
438
|
+
return b.kind === "policy" && keysWithin(b, ["kind", "rule"]) && str(b.rule);
|
|
439
|
+
}
|
|
440
|
+
async function checkAnswer(body, request, at) {
|
|
441
|
+
if (!ANSWER.required.every((k) => k in body) || !keysWithin(body, [...ANSWER.required, ...ANSWER.optional])) throw malformed("fields");
|
|
442
|
+
if (!str(body.request_id) || !str(body.profile) || !STATUSES.has(body.status) || !LEVELS.has(body.level)) throw malformed("values");
|
|
443
|
+
if (!Number.isSafeInteger(body.issued_at) || !Number.isSafeInteger(body.expires_at)) throw malformed("times must be integers");
|
|
444
|
+
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");
|
|
445
|
+
if ("ext" in body && !isObj(body.ext)) throw malformed("ext");
|
|
446
|
+
if ("critical" in body && !(Array.isArray(body.critical) && body.critical.every(str))) throw malformed("critical");
|
|
447
|
+
const shaped = body.status === "allow" ? checkBasis(body.basis) && !("reason" in body) : str(body.reason) && !("basis" in body);
|
|
448
|
+
if (!shaped) throw malformed("an allow has a valid basis and no reason; a refusal a reason and no basis");
|
|
449
|
+
if (body.critical?.length) throw new PregenError("PG_CRITICAL", body.critical.join());
|
|
450
|
+
if (!(body.expires_at - body.issued_at > 0 && body.expires_at - body.issued_at <= MAX_LIFETIME)) throw new PregenError("PG_TIME", "lifetime");
|
|
451
|
+
if (body.issued_at > at + CLOCK_TOLERANCE) throw new PregenError("PG_TIME", "issued in the future");
|
|
452
|
+
if (body.request_id !== await messageId("pg.request.v1", request)) throw new PregenError("PG_BINDING", "answer is for another request");
|
|
453
|
+
if (body.profile !== request.profile) throw new PregenError("PG_BINDING", "profile");
|
|
454
|
+
}
|
|
455
|
+
function readInput(input) {
|
|
456
|
+
let c;
|
|
457
|
+
try {
|
|
458
|
+
c = structuredClone(input);
|
|
459
|
+
} catch {
|
|
460
|
+
throw malformed("the input must be plain data");
|
|
461
|
+
}
|
|
462
|
+
const u = c?.use;
|
|
463
|
+
if (!isObj(c) || !JOB_ID.test(c.jobId ?? "")) throw malformed("jobId: 1-100 of A-Z a-z 0-9 _ . : -");
|
|
464
|
+
if (!Array.isArray(c.subjects) || !c.subjects.length || !c.subjects.every(str)) {
|
|
465
|
+
throw malformed("subjects: name at least one; with no registered person there is nothing to ask");
|
|
466
|
+
}
|
|
467
|
+
if (!str(c.model) || !str(c.modality)) throw malformed("model and modality");
|
|
468
|
+
if (c.prompt !== void 0 && typeof c.prompt !== "string") throw malformed("prompt");
|
|
469
|
+
if (c.promptHash !== void 0 && !HEX64.test(c.promptHash)) throw malformed("promptHash: 64 lowercase hex");
|
|
470
|
+
if (c.prompt === void 0 && c.promptHash === void 0) throw malformed("give prompt or promptHash");
|
|
471
|
+
if (c.licenseeId !== void 0 && typeof c.licenseeId !== "string") throw malformed("licenseeId");
|
|
472
|
+
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") {
|
|
473
|
+
throw malformed("use: purpose personal|educational|research|editorial|commercial; optional rights, territory, channel");
|
|
474
|
+
}
|
|
475
|
+
return c;
|
|
476
|
+
}
|
|
477
|
+
function createPregen(config) {
|
|
478
|
+
const fail2 = (why) => {
|
|
479
|
+
throw new PregenError("PG_CONFIG", why);
|
|
480
|
+
};
|
|
481
|
+
if (!isObj(config)) fail2("a configuration object is required");
|
|
482
|
+
let url;
|
|
483
|
+
try {
|
|
484
|
+
url = new URL(config.baseUrl);
|
|
485
|
+
} catch {
|
|
486
|
+
return fail2("baseUrl must be a URL");
|
|
487
|
+
}
|
|
488
|
+
const local = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
489
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && local)) fail2("use an HTTPS registry URL");
|
|
490
|
+
if (!str(config.providerId) || !str(config.token)) fail2("providerId and token are required strings");
|
|
491
|
+
const store = config.store;
|
|
492
|
+
if (!isObj(store) || !["claim", "update", "get", "pending"].every((m) => typeof store[m] === "function")) {
|
|
493
|
+
fail2("a store is required (postgresStore(pool), or memoryStore() for one process)");
|
|
494
|
+
}
|
|
495
|
+
const mode = config.mode === void 0 ? "enforce" : config.mode;
|
|
496
|
+
if (mode !== "enforce" && mode !== "observe") fail2(`mode must be "enforce" or "observe", not ${JSON.stringify(mode)}`);
|
|
497
|
+
const timeoutMs = config.timeoutMs ?? 5e3;
|
|
498
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 6e4) fail2("timeoutMs: 1 to 60000");
|
|
499
|
+
const base = url.origin, providerId = config.providerId, dir = config.pregenDirectory;
|
|
500
|
+
const secret = unhex(config.providerSigningKeyHex);
|
|
501
|
+
if (secret.length !== 32) fail2("providerSigningKeyHex must be a 32-byte Ed25519 private key");
|
|
502
|
+
const rawPins = config.operatorPublicKeyHex;
|
|
503
|
+
const pins = (Array.isArray(rawPins) ? rawPins : typeof rawPins === "string" ? rawPins.split(/[\s,]+/) : []).filter(Boolean);
|
|
504
|
+
if (!pins.length && !dir || pins.some((p) => unhex(p).length !== 32)) {
|
|
505
|
+
fail2("operatorPublicKeyHex: one or more 32-byte public keys, hex (or give pregenDirectory)");
|
|
506
|
+
}
|
|
507
|
+
const sameBase = (api) => {
|
|
508
|
+
try {
|
|
509
|
+
const u = new URL(api);
|
|
510
|
+
return u.origin === base && /^\/*$/.test(u.pathname);
|
|
511
|
+
} catch {
|
|
512
|
+
return false;
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
const listed = dir?.registries.find((r) => sameBase(r.api));
|
|
516
|
+
if (dir && !listed) fail2(`${base} is not a registry listed in the PRE-GEN directory`);
|
|
517
|
+
const ready = (async () => {
|
|
518
|
+
const registry = /* @__PURE__ */ new Map();
|
|
519
|
+
for (const p of pins) registry.set(await keyIdOf(unhex(p)), unhex(p));
|
|
520
|
+
const pub = await ed3.getPublicKeyAsync(secret);
|
|
521
|
+
return { registry, pub, keyId: await keyIdOf(pub) };
|
|
522
|
+
})();
|
|
523
|
+
const fromDirectory = /* @__PURE__ */ new Map();
|
|
524
|
+
async function keyFor(keyId) {
|
|
525
|
+
const { registry } = await ready;
|
|
526
|
+
if (pins.length) return registry.get(keyId);
|
|
527
|
+
const code = listed.prefix ? `PG-${listed.prefix}-0` : "PG-0";
|
|
528
|
+
let p = fromDirectory.get(keyId);
|
|
529
|
+
if (!p) {
|
|
530
|
+
p = dir.publicKey(code, keyId).then(unhex, () => {
|
|
531
|
+
fromDirectory.delete(keyId);
|
|
532
|
+
return void 0;
|
|
533
|
+
});
|
|
534
|
+
fromDirectory.set(keyId, p);
|
|
535
|
+
}
|
|
536
|
+
return p;
|
|
537
|
+
}
|
|
538
|
+
const directoryVerdict = (subject, keyId) => !dir || !isPgCode(subject) ? "valid" : dir.checkSigner(subject, keyId);
|
|
539
|
+
async function post(path, payload) {
|
|
540
|
+
const resp = await fetch(base + path, {
|
|
541
|
+
method: "POST",
|
|
542
|
+
redirect: "error",
|
|
543
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
544
|
+
body: JSON.stringify(payload),
|
|
545
|
+
headers: { "Content-Type": "application/json", "X-Provider-ID": providerId, Authorization: `Bearer ${config.token}` }
|
|
546
|
+
});
|
|
547
|
+
let data = null;
|
|
548
|
+
try {
|
|
549
|
+
data = await resp.json();
|
|
550
|
+
} catch {
|
|
551
|
+
}
|
|
552
|
+
return { status: resp.status, data };
|
|
553
|
+
}
|
|
554
|
+
const errorCode = (r) => typeof r.data?.detail?.error === "string" && r.data.detail.error || `HTTP_${r.status}`;
|
|
555
|
+
async function seal(type, body) {
|
|
556
|
+
const { keyId } = await ready;
|
|
557
|
+
body = { ...body, signers: [{ role: "provider", key_id: keyId }] };
|
|
558
|
+
return { type, body, signatures: [hex(await ed3.signAsync(signingInput(type, body), secret))] };
|
|
559
|
+
}
|
|
560
|
+
async function ask(request) {
|
|
561
|
+
const subject = request.subject_ref;
|
|
562
|
+
if (dir && isPgCode(subject) && !(dir.registryFor(subject) && sameBase(dir.registryFor(subject).api))) {
|
|
563
|
+
return { request, answer: null, error: "PG_WRONG_REGISTRY" };
|
|
564
|
+
}
|
|
565
|
+
let r = null;
|
|
566
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
567
|
+
if (attempt) await new Promise((ok) => setTimeout(ok, 200 * 2 ** attempt));
|
|
568
|
+
try {
|
|
569
|
+
r = await post("/v2/requests", { type: "pg.request.v1", body: request, signatures: [] });
|
|
570
|
+
} catch {
|
|
571
|
+
r = null;
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
if (r.status !== 429 && r.status < 500) break;
|
|
575
|
+
}
|
|
576
|
+
if (!r) return { request, answer: null, error: "PG_UNREACHABLE" };
|
|
577
|
+
if (r.status !== 200) return { request, answer: null, error: errorCode(r) };
|
|
578
|
+
const answer = r.data?.answer;
|
|
579
|
+
try {
|
|
580
|
+
await verifyEnvelope(answer, "pg.answer.v1", "registry", keyFor);
|
|
581
|
+
const verdict = directoryVerdict(subject, answer.body.signers[0].key_id);
|
|
582
|
+
if (verdict !== "valid") throw new PregenError(`PG_KEY_${verdict.toUpperCase()}`, "the directory does not list this key");
|
|
583
|
+
} catch (e) {
|
|
584
|
+
return { request, answer: null, error: e instanceof PregenError ? e.code : "PG_MALFORMED" };
|
|
585
|
+
}
|
|
586
|
+
try {
|
|
587
|
+
await checkAnswer(answer.body, request, now());
|
|
588
|
+
return { request, answer };
|
|
589
|
+
} catch (e) {
|
|
590
|
+
const code = e.code;
|
|
591
|
+
return code === "PG_BINDING" ? { request, answer: null, error: code } : { request, answer, error: code };
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
const usableAllow = (a, at) => !a.error && a.answer?.body.status === "allow" && a.answer.body.expires_at > at;
|
|
595
|
+
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 })) : [];
|
|
596
|
+
const obligationsOf = (job) => job.asks.flatMap(obligationsOfAsk);
|
|
597
|
+
const applies = (applied, o) => applied.some((x) => x.subject === o.subject && x.id === o.id);
|
|
598
|
+
async function report(a, outcome, outputHash, applied) {
|
|
599
|
+
const answer = a.answer ? await messageId("pg.answer.v1", a.answer.body) : null;
|
|
600
|
+
const body = {
|
|
601
|
+
request_id: await messageId("pg.request.v1", a.request),
|
|
602
|
+
answer,
|
|
603
|
+
mode: a.request.mode,
|
|
604
|
+
job_id: a.request.job_id,
|
|
605
|
+
outcome,
|
|
606
|
+
output_hash: outcome === "generated" ? outputHash : "",
|
|
607
|
+
obligations_applied: outcome === "generated" ? [...new Set(obligationsOfAsk(a).filter((o) => applies(applied, o)).map((o) => o.id))].sort() : [],
|
|
608
|
+
occurred_at: now()
|
|
609
|
+
};
|
|
610
|
+
if (answer === null) body.request = a.request;
|
|
611
|
+
return { subject: a.request.subject_ref, answerStatus: a.answer?.body.status ?? "none", report: await seal("pg.report.v1", body) };
|
|
612
|
+
}
|
|
613
|
+
async function checkAck(r, env) {
|
|
614
|
+
const { keyId } = await ready;
|
|
615
|
+
const ack = await verifyEnvelope(env, "pg.report_ack.v1", "registry", keyFor);
|
|
616
|
+
const verdict = directoryVerdict(r.subject, ack.signers[0].key_id);
|
|
617
|
+
if (verdict !== "valid") throw new PregenError(`PG_KEY_${verdict.toUpperCase()}`, "the directory does not list this key");
|
|
618
|
+
if (Object.keys(ack).sort().join() !== [...ACK_FIELDS].sort().join()) throw malformed("acknowledgement fields");
|
|
619
|
+
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) {
|
|
620
|
+
throw new PregenError("PG_BINDING", "the acknowledgement is not for this report");
|
|
621
|
+
}
|
|
622
|
+
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)) {
|
|
623
|
+
throw malformed("acknowledgement values");
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
const reread = async (job) => await store.get(providerId, job.jobId) ?? job;
|
|
627
|
+
async function send(job) {
|
|
628
|
+
const { keyId } = await ready;
|
|
629
|
+
for (const r of job.reports) {
|
|
630
|
+
if (r.ack || r.rejected) continue;
|
|
631
|
+
let res;
|
|
632
|
+
try {
|
|
633
|
+
res = await post("/v2/reports", r.report);
|
|
634
|
+
if (res.status === 401 && r.report.body.signers?.[0]?.key_id !== keyId) {
|
|
635
|
+
const { signers: _, ...body } = r.report.body;
|
|
636
|
+
r.report = await seal("pg.report.v1", body);
|
|
637
|
+
res = await post("/v2/reports", r.report);
|
|
638
|
+
}
|
|
639
|
+
} catch {
|
|
640
|
+
r.error = "PG_UNREACHABLE";
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
if (res.status === 409 || res.status === 422) {
|
|
644
|
+
r.rejected = true;
|
|
645
|
+
r.error = errorCode(res);
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
if (res.status !== 200) {
|
|
649
|
+
r.error = errorCode(res);
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
try {
|
|
653
|
+
await checkAck(r, res.data?.ack);
|
|
654
|
+
r.ack = res.data.ack;
|
|
655
|
+
delete r.error;
|
|
656
|
+
} catch (e) {
|
|
657
|
+
r.error = e instanceof PregenError ? e.code : "PG_MALFORMED";
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
const next = { ...job, stage: job.reports.every((r) => r.ack || r.rejected) ? "done" : "finished" };
|
|
661
|
+
return await store.update(next, "finished") ? next : reread(job);
|
|
662
|
+
}
|
|
663
|
+
function result(job, output, override) {
|
|
664
|
+
const unknown = job.stage === "claimed" || job.stage === "asked";
|
|
665
|
+
const rejected = job.reports.some((r) => r.rejected);
|
|
666
|
+
return {
|
|
667
|
+
jobId: job.jobId,
|
|
668
|
+
status: unknown || rejected || override ? "needs_check" : job.stage === "done" ? "confirmed" : "report_pending",
|
|
669
|
+
generated: unknown ? null : !!job.generated,
|
|
670
|
+
...output !== void 0 ? { output } : {},
|
|
671
|
+
...job.outputHash ? { outputHash: job.outputHash } : {},
|
|
672
|
+
answers: job.asks.map((a) => ({
|
|
673
|
+
subject: a.request.subject_ref,
|
|
674
|
+
status: a.answer?.body.status ?? null,
|
|
675
|
+
...typeof a.answer?.body.reason === "string" ? { reason: a.answer.body.reason } : {},
|
|
676
|
+
...a.error ? { error: a.error } : {}
|
|
677
|
+
})),
|
|
678
|
+
unmetObligations: job.unmetObligations ?? [],
|
|
679
|
+
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)
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
async function finish(job, generated, outputHash = "", applied = []) {
|
|
683
|
+
const asks = generated ? job.asks : job.asks.filter((a) => a.answer?.body.status === "allow");
|
|
684
|
+
const next = {
|
|
685
|
+
...job,
|
|
686
|
+
stage: "finished",
|
|
687
|
+
generated,
|
|
688
|
+
...generated ? { outputHash } : {},
|
|
689
|
+
reports: await Promise.all(asks.map((a) => report(a, generated ? "generated" : "not_generated", outputHash, applied))),
|
|
690
|
+
unmetObligations: generated ? obligationsOf(job).filter((o) => !applies(applied, o)) : []
|
|
691
|
+
};
|
|
692
|
+
if (await store.update(next, "asked")) return send(next);
|
|
693
|
+
const stored = await reread(job);
|
|
694
|
+
return stored.stage === "finished" ? send(stored) : stored;
|
|
695
|
+
}
|
|
696
|
+
async function outputHashOf(r) {
|
|
697
|
+
if (!isObj(r)) throw malformed("the generator must return { output, outputHash | outputBytes }");
|
|
698
|
+
if (r.outputHash !== void 0) {
|
|
699
|
+
if (typeof r.outputHash !== "string" || !HEX64.test(r.outputHash)) throw malformed("outputHash must be 64 lowercase hex characters");
|
|
700
|
+
return r.outputHash;
|
|
701
|
+
}
|
|
702
|
+
if (typeof r.outputBytes === "string") return sha2562(enc.encode(r.outputBytes));
|
|
703
|
+
if (r.outputBytes instanceof Uint8Array) return sha2562(r.outputBytes);
|
|
704
|
+
throw malformed("return outputHash or outputBytes");
|
|
705
|
+
}
|
|
706
|
+
function appliedOf(list) {
|
|
707
|
+
if (list === void 0) return [];
|
|
708
|
+
if (!Array.isArray(list) || !list.every((x) => isObj(x) && str(x.subject) && str(x.id))) {
|
|
709
|
+
throw malformed("obligationsApplied: the { subject, id } obligations you carried out");
|
|
710
|
+
}
|
|
711
|
+
return list.map((x) => ({ subject: x.subject, id: x.id }));
|
|
712
|
+
}
|
|
713
|
+
async function currentKeyset() {
|
|
714
|
+
const resp = await fetch(`${base}/v2/keys/${encodeURIComponent(providerId)}`, { redirect: "error", signal: AbortSignal.timeout(timeoutMs) });
|
|
715
|
+
const chain = resp.ok ? (await resp.json())?.keysets : null;
|
|
716
|
+
const keyset = Array.isArray(chain) && chain.length ? chain[chain.length - 1] : null;
|
|
717
|
+
if (!keyset || !isObj(keyset.body) || !Array.isArray(keyset.body.keys)) return null;
|
|
718
|
+
return {
|
|
719
|
+
keyset,
|
|
720
|
+
id: await messageId("pg.keyset.v1", keyset.body),
|
|
721
|
+
current: keyset.body.keys.find((k) => k.status === "current")
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
async function flush() {
|
|
725
|
+
const out = [];
|
|
726
|
+
for (const job of await store.pending(providerId)) out.push(result(await send(job)));
|
|
727
|
+
return out;
|
|
728
|
+
}
|
|
729
|
+
async function run(input, fn) {
|
|
730
|
+
const subjects = [...new Set(input.subjects)].sort();
|
|
731
|
+
const promptHash = input.promptHash ?? await sha2562(enc.encode(input.prompt));
|
|
732
|
+
const manifest = {
|
|
733
|
+
job_id: input.jobId,
|
|
734
|
+
provider_id: providerId,
|
|
735
|
+
licensee_id: input.licenseeId ?? "",
|
|
736
|
+
prompt_hash: promptHash,
|
|
737
|
+
model: input.model,
|
|
738
|
+
modality: input.modality,
|
|
739
|
+
mode,
|
|
740
|
+
asks: subjects.map((s) => ({ subject_ref: s, registry: "", profile: PROFILE, use: input.use }))
|
|
741
|
+
};
|
|
742
|
+
const jobHash = await sha2562(enc.encode(canonicalJson(manifest)));
|
|
743
|
+
const fresh = { providerId, jobId: input.jobId, jobHash, stage: "claimed", asks: [], reports: [] };
|
|
744
|
+
const existing = await store.claim(fresh);
|
|
745
|
+
if (existing) {
|
|
746
|
+
if (existing.jobHash !== jobHash) throw new PregenError("PG_JOB_CONFLICT", `job ${input.jobId} was used for another generation`);
|
|
747
|
+
return result(existing.stage === "finished" ? await send(existing) : existing);
|
|
748
|
+
}
|
|
749
|
+
const asked = { ...fresh, stage: "asked", asks: await Promise.all(subjects.map((s) => ask({
|
|
750
|
+
job_id: input.jobId,
|
|
751
|
+
job_hash: jobHash,
|
|
752
|
+
subject_ref: s,
|
|
753
|
+
provider_id: providerId,
|
|
754
|
+
licensee_id: manifest.licensee_id,
|
|
755
|
+
prompt_hash: promptHash,
|
|
756
|
+
model: input.model,
|
|
757
|
+
modality: input.modality,
|
|
758
|
+
profile: PROFILE,
|
|
759
|
+
use: input.use,
|
|
760
|
+
mode
|
|
761
|
+
}))) };
|
|
762
|
+
if (!await store.update(asked, "claimed")) return result(await reread(fresh));
|
|
763
|
+
const at = now();
|
|
764
|
+
if (mode === "enforce" && !asked.asks.every((a) => usableAllow(a, at))) return result(await finish(asked, false));
|
|
765
|
+
let out;
|
|
766
|
+
try {
|
|
767
|
+
out = await fn({ obligations: obligationsOf(asked) });
|
|
768
|
+
} catch (e) {
|
|
769
|
+
if (e?.notGenerated === true) await finish(asked, false);
|
|
770
|
+
throw e;
|
|
771
|
+
}
|
|
772
|
+
const done = await finish(asked, true, await outputHashOf(out), appliedOf(out.obligationsApplied));
|
|
773
|
+
return result(done, out.output, done.generated ? void 0 : "another worker recorded that this job was not generated");
|
|
774
|
+
}
|
|
775
|
+
return {
|
|
776
|
+
/** Ask, verify, generate (only when permitted), report. See the module comment. */
|
|
777
|
+
async generate(input, fn) {
|
|
778
|
+
const copy = readInput(input);
|
|
779
|
+
if (typeof fn !== "function") throw malformed("pass your generator function");
|
|
780
|
+
return run(copy, fn);
|
|
781
|
+
},
|
|
782
|
+
/** Resend every report still waiting (after a restart, an outage). Run at startup and every minute or so. */
|
|
783
|
+
flush,
|
|
784
|
+
/** Where a job stands, or null if this store never saw it. */
|
|
785
|
+
async status(jobId) {
|
|
786
|
+
const job = await store.get(providerId, jobId);
|
|
787
|
+
return job && result(job);
|
|
788
|
+
},
|
|
789
|
+
/** After `needs_check`: record what your generator actually did with this job. Safe to call from several workers:
|
|
790
|
+
* the first recorded outcome stands. */
|
|
791
|
+
async resolve(jobId, outcome) {
|
|
792
|
+
if (!isObj(outcome) || typeof outcome.generated !== "boolean") throw malformed("outcome: { generated: true | false, ... }");
|
|
793
|
+
const job = await store.get(providerId, jobId);
|
|
794
|
+
if (!job) throw new PregenError("PG_UNKNOWN_JOB", jobId);
|
|
795
|
+
if (job.stage === "finished") return result(await send(job));
|
|
796
|
+
if (job.stage === "done") return result(job);
|
|
797
|
+
if (!outcome.generated) {
|
|
798
|
+
if (job.stage === "claimed") {
|
|
799
|
+
const closed = { ...job, stage: "done", generated: false };
|
|
800
|
+
if (await store.update(closed, "claimed")) return result(closed);
|
|
801
|
+
const stored = await reread(job);
|
|
802
|
+
return result(stored.stage === "finished" ? await send(stored) : stored);
|
|
803
|
+
}
|
|
804
|
+
return result(await finish(job, false));
|
|
805
|
+
}
|
|
806
|
+
if (job.stage === "claimed") throw new PregenError("PG_JOB", "generation cannot have started before the answers were stored");
|
|
807
|
+
return result(await finish(job, true, await outputHashOf(outcome), appliedOf(outcome.obligationsApplied)));
|
|
808
|
+
},
|
|
809
|
+
/** Register this provider's signing key with the registry, once (a `first` keyset, §4.9). Calling it again
|
|
810
|
+
* with the same key is harmless. */
|
|
811
|
+
async registerKey() {
|
|
812
|
+
const { pub, keyId } = await ready;
|
|
813
|
+
const keyset = await seal("pg.keyset.v1", {
|
|
814
|
+
kind: "first",
|
|
815
|
+
provider_id: providerId,
|
|
816
|
+
previous: null,
|
|
817
|
+
recovery_registry: "",
|
|
818
|
+
keys: [{ key_id: keyId, public_key_hex: hex(pub), status: "current" }]
|
|
819
|
+
});
|
|
820
|
+
const r = await post("/v2/keys", keyset);
|
|
821
|
+
if (r.status === 409) {
|
|
822
|
+
const latest = await currentKeyset();
|
|
823
|
+
if (latest?.current?.key_id === keyId) return { keyId, keysetId: latest.id };
|
|
824
|
+
}
|
|
825
|
+
if (r.status !== 200) throw new PregenError(errorCode(r), r.data?.detail?.error_description ?? "key not registered");
|
|
826
|
+
return { keyId, keysetId: r.data.keyset_id };
|
|
827
|
+
},
|
|
828
|
+
/** Replace this client's signing key with `newSigningKeyHex` (a `rotation` keyset signed by both keys).
|
|
829
|
+
* Then create the client again with the new key, in every worker. Reports already acknowledged keep their
|
|
830
|
+
* acknowledgements; reports still waiting, or sent by a worker that still has the old key, are signed again
|
|
831
|
+
* with the new key by the next `flush()` of a client that has it. */
|
|
832
|
+
async rotateKey(newSigningKeyHex) {
|
|
833
|
+
const next = unhex(newSigningKeyHex);
|
|
834
|
+
if (next.length !== 32) throw new PregenError("PG_CONFIG", "the new key must be a 32-byte Ed25519 private key");
|
|
835
|
+
const { pub, keyId } = await ready;
|
|
836
|
+
await flush();
|
|
837
|
+
const chain = await currentKeyset();
|
|
838
|
+
if (!chain || chain.current?.key_id !== keyId) {
|
|
839
|
+
throw new PregenError("PG_KEYSET", "the registry's current key is not this client's key: registerKey() first");
|
|
840
|
+
}
|
|
841
|
+
const latest = chain.keyset;
|
|
842
|
+
const nextPub = await ed3.getPublicKeyAsync(next), nextId = await keyIdOf(nextPub);
|
|
843
|
+
const body = {
|
|
844
|
+
kind: "rotation",
|
|
845
|
+
provider_id: providerId,
|
|
846
|
+
previous: await messageId("pg.keyset.v1", latest.body),
|
|
847
|
+
recovery_registry: latest.body.recovery_registry,
|
|
848
|
+
keys: [
|
|
849
|
+
{ key_id: nextId, public_key_hex: hex(nextPub), status: "current" },
|
|
850
|
+
{ key_id: keyId, public_key_hex: hex(pub), status: "retired" }
|
|
851
|
+
],
|
|
852
|
+
signers: [{ role: "provider", key_id: nextId }, { role: "provider_previous", key_id: keyId }]
|
|
853
|
+
};
|
|
854
|
+
const data = signingInput("pg.keyset.v1", body);
|
|
855
|
+
const keyset = { type: "pg.keyset.v1", body, signatures: [hex(await ed3.signAsync(data, next)), hex(await ed3.signAsync(data, secret))] };
|
|
856
|
+
const r = await post("/v2/keys", keyset);
|
|
857
|
+
if (r.status !== 200) throw new PregenError(errorCode(r), r.data?.detail?.error_description ?? "key not rotated");
|
|
858
|
+
return { keyId: nextId, keysetId: r.data.keyset_id };
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
function memoryStore() {
|
|
863
|
+
const jobs = /* @__PURE__ */ new Map();
|
|
864
|
+
const k = (p, j) => `${p}\0${j}`;
|
|
865
|
+
return {
|
|
866
|
+
async claim(job) {
|
|
867
|
+
const found = jobs.get(k(job.providerId, job.jobId));
|
|
868
|
+
if (found) return structuredClone(found);
|
|
869
|
+
jobs.set(k(job.providerId, job.jobId), structuredClone(job));
|
|
870
|
+
return null;
|
|
871
|
+
},
|
|
872
|
+
async update(job, from) {
|
|
873
|
+
if (jobs.get(k(job.providerId, job.jobId))?.stage !== from) return false;
|
|
874
|
+
jobs.set(k(job.providerId, job.jobId), structuredClone(job));
|
|
875
|
+
return true;
|
|
876
|
+
},
|
|
877
|
+
async get(p, j) {
|
|
878
|
+
const f = jobs.get(k(p, j));
|
|
879
|
+
return f ? structuredClone(f) : null;
|
|
880
|
+
},
|
|
881
|
+
async pending(p) {
|
|
882
|
+
return [...jobs.values()].filter((j) => j.providerId === p && j.stage === "finished").map((j) => structuredClone(j));
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
var postgresSchema = `CREATE TABLE IF NOT EXISTS pregen_jobs (
|
|
887
|
+
provider_id text NOT NULL, job_id text NOT NULL, stage text NOT NULL, job jsonb NOT NULL,
|
|
888
|
+
updated_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY (provider_id, job_id))`;
|
|
889
|
+
function postgresStore(pool) {
|
|
890
|
+
let ready = null;
|
|
891
|
+
const q = async (text, values) => {
|
|
892
|
+
ready ??= pool.query(`DO $$ BEGIN
|
|
893
|
+
IF to_regclass('pregen_jobs') IS NULL THEN
|
|
894
|
+
PERFORM pg_advisory_xact_lock(hashtext('pregen_jobs'));
|
|
895
|
+
${postgresSchema};
|
|
896
|
+
END IF;
|
|
897
|
+
END $$`).catch((e) => {
|
|
898
|
+
ready = null;
|
|
899
|
+
throw e;
|
|
900
|
+
});
|
|
901
|
+
await ready;
|
|
902
|
+
return (await pool.query(text, values)).rows;
|
|
903
|
+
};
|
|
904
|
+
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;
|
|
905
|
+
return {
|
|
906
|
+
async claim(job) {
|
|
907
|
+
const won = await q(`INSERT INTO pregen_jobs (provider_id, job_id, stage, job) VALUES ($1, $2, $3, $4)
|
|
908
|
+
ON CONFLICT DO NOTHING RETURNING job_id`, [job.providerId, job.jobId, job.stage, JSON.stringify(job)]);
|
|
909
|
+
return won.length ? null : one(job.providerId, job.jobId);
|
|
910
|
+
},
|
|
911
|
+
async update(job, from) {
|
|
912
|
+
return (await q(
|
|
913
|
+
`UPDATE pregen_jobs SET stage = $3, job = $4, updated_at = now()
|
|
914
|
+
WHERE provider_id = $1 AND job_id = $2 AND stage = $5 RETURNING job_id`,
|
|
915
|
+
[job.providerId, job.jobId, job.stage, JSON.stringify(job), from]
|
|
916
|
+
)).length > 0;
|
|
917
|
+
},
|
|
918
|
+
get: one,
|
|
919
|
+
async pending(p) {
|
|
920
|
+
return (await q(`SELECT job FROM pregen_jobs WHERE provider_id = $1 AND stage = 'finished'
|
|
921
|
+
ORDER BY updated_at LIMIT 100`, [p])).map((r) => r.job);
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
|
|
356
926
|
// src/index.ts
|
|
357
927
|
function singleProcessJournal() {
|
|
358
928
|
const entries = /* @__PURE__ */ new Map();
|
|
@@ -497,16 +1067,16 @@ function envVar(key) {
|
|
|
497
1067
|
return "";
|
|
498
1068
|
}
|
|
499
1069
|
async function hashPrompt(prompt) {
|
|
500
|
-
return
|
|
1070
|
+
return sha2563(prompt);
|
|
501
1071
|
}
|
|
502
|
-
async function
|
|
1072
|
+
async function sha2563(input) {
|
|
503
1073
|
const data = new TextEncoder().encode(input);
|
|
504
1074
|
return sha256Bytes(data);
|
|
505
1075
|
}
|
|
506
1076
|
async function verifyMerkleProof(leafHash, proof, rootHash) {
|
|
507
1077
|
let current = leafHash;
|
|
508
1078
|
for (const step of proof) {
|
|
509
|
-
current = step.position === "right" ? await
|
|
1079
|
+
current = step.position === "right" ? await sha2563(current + step.hash) : await sha2563(step.hash + current);
|
|
510
1080
|
}
|
|
511
1081
|
return current === rootHash;
|
|
512
1082
|
}
|
|
@@ -526,13 +1096,13 @@ async function sha256Bytes(data) {
|
|
|
526
1096
|
const { createHash } = await import("crypto");
|
|
527
1097
|
return createHash("sha256").update(data).digest("hex");
|
|
528
1098
|
}
|
|
529
|
-
function hexToBytes(
|
|
530
|
-
if (typeof
|
|
1099
|
+
function hexToBytes(hex2) {
|
|
1100
|
+
if (typeof hex2 !== "string" || !/^(?:[0-9a-fA-F]{2})+$/.test(hex2)) {
|
|
531
1101
|
throw new PramptaSignatureError("Invalid hexadecimal key or signature");
|
|
532
1102
|
}
|
|
533
|
-
const bytes = new Uint8Array(
|
|
534
|
-
for (let i = 0; i <
|
|
535
|
-
bytes[i / 2] = parseInt(
|
|
1103
|
+
const bytes = new Uint8Array(hex2.length / 2);
|
|
1104
|
+
for (let i = 0; i < hex2.length; i += 2) {
|
|
1105
|
+
bytes[i / 2] = parseInt(hex2.substring(i, i + 2), 16);
|
|
536
1106
|
}
|
|
537
1107
|
return bytes;
|
|
538
1108
|
}
|
|
@@ -684,7 +1254,7 @@ var Prampta = class _Prampta {
|
|
|
684
1254
|
if (!currentHex || !sig) throw new PramptaSignatureError("Operator key set from /keys is malformed");
|
|
685
1255
|
const body = { ...ks };
|
|
686
1256
|
delete body.signature;
|
|
687
|
-
const ok = await
|
|
1257
|
+
const ok = await ed4.verifyAsync(hexToBytes(sig), new TextEncoder().encode(canonicalJson(body)), hexToBytes(currentHex));
|
|
688
1258
|
if (!ok) throw new PramptaSignatureError("Operator key set signature is invalid");
|
|
689
1259
|
this.keySetCache = ks;
|
|
690
1260
|
return ks;
|
|
@@ -699,8 +1269,8 @@ var Prampta = class _Prampta {
|
|
|
699
1269
|
* from the self-consistent /keys set with a warning.
|
|
700
1270
|
*/
|
|
701
1271
|
async resolveOperatorKey(keyId) {
|
|
702
|
-
for (const
|
|
703
|
-
if (await keyFingerprint(
|
|
1272
|
+
for (const hex2 of this.pinnedKeys) {
|
|
1273
|
+
if (await keyFingerprint(hex2) === keyId) return hex2;
|
|
704
1274
|
}
|
|
705
1275
|
if (this.pinnedKeys.length > 0) {
|
|
706
1276
|
throw new PramptaSignatureError(
|
|
@@ -733,11 +1303,11 @@ var Prampta = class _Prampta {
|
|
|
733
1303
|
* copy of the subject index. Call verify()/assertAllowed() per hit.
|
|
734
1304
|
*/
|
|
735
1305
|
async matchSubjects(text, opts) {
|
|
736
|
-
const
|
|
737
|
-
if (opts?.forceRefresh || !this.subjectIndexEntries ||
|
|
1306
|
+
const now2 = Date.now();
|
|
1307
|
+
if (opts?.forceRefresh || !this.subjectIndexEntries || now2 - this.subjectIndexFetchedAt > _Prampta.SUBJECT_INDEX_TTL_MS) {
|
|
738
1308
|
const payload = await this.fetchSubjectIndex();
|
|
739
1309
|
this.subjectIndexEntries = payload.subjects || [];
|
|
740
|
-
this.subjectIndexFetchedAt =
|
|
1310
|
+
this.subjectIndexFetchedAt = now2;
|
|
741
1311
|
}
|
|
742
1312
|
return matchSubjects(text, this.subjectIndexEntries);
|
|
743
1313
|
}
|
|
@@ -825,7 +1395,7 @@ var Prampta = class _Prampta {
|
|
|
825
1395
|
const sigBytes = hexToBytes(sig);
|
|
826
1396
|
let valid;
|
|
827
1397
|
try {
|
|
828
|
-
valid = await
|
|
1398
|
+
valid = await ed4.verifyAsync(sigBytes, messageBytes, pubKeyBytes);
|
|
829
1399
|
} catch (e) {
|
|
830
1400
|
throw new PramptaSignatureError(`Ed25519 verification error: ${e}`);
|
|
831
1401
|
}
|
|
@@ -834,13 +1404,13 @@ var Prampta = class _Prampta {
|
|
|
834
1404
|
"Operator signature is invalid \u2014 decision cannot be trusted. This may indicate a compromised backend, proxy tampering, or key mismatch."
|
|
835
1405
|
);
|
|
836
1406
|
}
|
|
837
|
-
const
|
|
838
|
-
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 >
|
|
1407
|
+
const now2 = Math.floor(Date.now() / 1e3);
|
|
1408
|
+
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) {
|
|
839
1409
|
throw new PramptaSchemaError("Decision has invalid validity timestamps");
|
|
840
1410
|
}
|
|
841
|
-
if (decision.expiresAt <=
|
|
1411
|
+
if (decision.expiresAt <= now2) {
|
|
842
1412
|
throw new PramptaSignatureError(
|
|
843
|
-
`Decision expired at ${decision.expiresAt}, current time ${
|
|
1413
|
+
`Decision expired at ${decision.expiresAt}, current time ${now2}`
|
|
844
1414
|
);
|
|
845
1415
|
}
|
|
846
1416
|
}
|
|
@@ -926,7 +1496,7 @@ var Prampta = class _Prampta {
|
|
|
926
1496
|
} : void 0 };
|
|
927
1497
|
this.registryDirectory?.assertEndpoint(request.subjectId, this.baseUrl);
|
|
928
1498
|
this.assertPregenEndpoint(request.subjectId);
|
|
929
|
-
const promptHash = request.promptHash || (request.prompt ? await
|
|
1499
|
+
const promptHash = request.promptHash || (request.prompt ? await sha2563(request.prompt) : null);
|
|
930
1500
|
if (!promptHash) {
|
|
931
1501
|
throw new PramptaSchemaError(
|
|
932
1502
|
"Either prompt or promptHash is required. SDK does not allow empty prompt hashes to prevent unbound decisions."
|
|
@@ -1103,7 +1673,7 @@ var Prampta = class _Prampta {
|
|
|
1103
1673
|
/** SHA-256 of everything that defines the job. Same id + different hash = a different job. */
|
|
1104
1674
|
async requestHash(request) {
|
|
1105
1675
|
const { generationId, subjectIds, prompt, promptHash, idempotencyKey, returnUrl, detectionId, ...rest } = request;
|
|
1106
|
-
return
|
|
1676
|
+
return sha2563(canonicalJson(JSON.parse(JSON.stringify({
|
|
1107
1677
|
...rest,
|
|
1108
1678
|
generationId,
|
|
1109
1679
|
subjectIds: [...new Set(subjectIds)].sort(),
|
|
@@ -1261,7 +1831,7 @@ var Prampta = class _Prampta {
|
|
|
1261
1831
|
provider_id: this.providerId,
|
|
1262
1832
|
event_type: eventType
|
|
1263
1833
|
};
|
|
1264
|
-
const signature = await
|
|
1834
|
+
const signature = await ed4.signAsync(
|
|
1265
1835
|
new TextEncoder().encode(canonicalJson(body)),
|
|
1266
1836
|
hexToBytes(keyHex)
|
|
1267
1837
|
);
|
|
@@ -1317,7 +1887,7 @@ var Prampta = class _Prampta {
|
|
|
1317
1887
|
return this.get("/version");
|
|
1318
1888
|
}
|
|
1319
1889
|
static async hashPrompt(prompt) {
|
|
1320
|
-
return
|
|
1890
|
+
return sha2563(prompt);
|
|
1321
1891
|
}
|
|
1322
1892
|
// ── Internal ─────────────────────────────────────────────────────────
|
|
1323
1893
|
parseDecision(data) {
|
|
@@ -1445,6 +2015,7 @@ function sameEndpoint(a, b) {
|
|
|
1445
2015
|
}
|
|
1446
2016
|
export {
|
|
1447
2017
|
HARD_REFUSAL_CODES,
|
|
2018
|
+
NotGeneratedError,
|
|
1448
2019
|
Prampta,
|
|
1449
2020
|
PramptaApiError,
|
|
1450
2021
|
PramptaError,
|
|
@@ -1456,15 +2027,22 @@ export {
|
|
|
1456
2027
|
PramptaSchemaError,
|
|
1457
2028
|
PramptaSignatureError,
|
|
1458
2029
|
PramptaTimeoutError,
|
|
2030
|
+
PregenError,
|
|
1459
2031
|
REFUSAL_DESCRIPTIONS,
|
|
1460
2032
|
RegistryDirectoryError,
|
|
1461
2033
|
SOFT_REFUSAL_CODES,
|
|
1462
2034
|
TrustedRegistryDirectory,
|
|
1463
2035
|
canonicalJson,
|
|
2036
|
+
createPregen,
|
|
1464
2037
|
hashPrompt,
|
|
1465
2038
|
loadDirectory as loadPregenDirectory,
|
|
1466
2039
|
matchSubjects,
|
|
2040
|
+
memoryStore,
|
|
2041
|
+
messageId,
|
|
1467
2042
|
normalizeForMatch,
|
|
2043
|
+
postgresSchema,
|
|
2044
|
+
postgresStore,
|
|
2045
|
+
signingInput,
|
|
1468
2046
|
singleProcessJournal,
|
|
1469
2047
|
verifyLicenseSignatures,
|
|
1470
2048
|
verifyMerkleProof
|