@prampta/sdk 0.8.0 → 0.9.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 +68 -34
- package/dist/index.d.mts +57 -34
- package/dist/index.d.ts +57 -34
- package/dist/index.js +162 -55
- package/dist/index.mjs +160 -55
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -36,7 +36,48 @@ production PRAMPTA deployment running the v1.0.0-default trust posture
|
|
|
36
36
|
refuses it with `PG_NO_PAIR`. Don't build a new integration against it; it
|
|
37
37
|
exists solely so an already-connected provider keeps working mid-migration.
|
|
38
38
|
|
|
39
|
-
## Quick Start
|
|
39
|
+
## Quick Start: the safe setup (0.9.0)
|
|
40
|
+
|
|
41
|
+
One configuration turns on every check: the signed PRE-GEN directory, your
|
|
42
|
+
pinned operator key, and a journal in your own database that lets exactly
|
|
43
|
+
one worker run each job.
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { Prampta, loadPregenDirectory } from "@prampta/sdk";
|
|
47
|
+
|
|
48
|
+
const pregenDirectory = await loadPregenDirectory({ previous: saved }); // PRE-GEN steward key built in
|
|
49
|
+
const pg = new Prampta({
|
|
50
|
+
baseUrl: "https://api2.prampta.com", providerId, licenseeId,
|
|
51
|
+
token: process.env.PRAMPTA_TOKEN, // server-side only
|
|
52
|
+
operatorPublicKeyHex: process.env.PRAMPTA_OPERATOR_PUBLIC_KEY, // obtained independently
|
|
53
|
+
pregenDirectory,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// The journal's claim must be ATOMIC across all your servers, e.g. in Postgres:
|
|
57
|
+
// INSERT INTO pg_jobs (id, entry) VALUES ($1, $2) ON CONFLICT (id) DO NOTHING RETURNING id
|
|
58
|
+
// -- no row returned: SELECT entry FROM pg_jobs WHERE id = $1 and return it
|
|
59
|
+
const journal = {
|
|
60
|
+
claim: async (entry) => (await db.insertIfAbsent(entry.generationId, entry)) ? null : db.get(entry.generationId),
|
|
61
|
+
put: async (entry) => db.update(entry.generationId, entry),
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const result = await pg.generateAuthorized(
|
|
65
|
+
{ subjectIds: [subjectCode], generationId: jobId, promptHash, model, modality: "image",
|
|
66
|
+
intendedUse: { useCase: "commercial" } },
|
|
67
|
+
async (decisions) => {
|
|
68
|
+
const image = await render(prompt); // apply every decision's obligations
|
|
69
|
+
return { output: image, outputBytes: image.bytes };
|
|
70
|
+
},
|
|
71
|
+
{ journal },
|
|
72
|
+
);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`verify`, `assertAllowed` and `assertLicensed` below are lower-level calls:
|
|
76
|
+
they check one decision and leave the job (claiming, retries, receipts) to
|
|
77
|
+
you. Without `pregenDirectory` they check the signature against your pinned
|
|
78
|
+
key, but not the PRE-GEN directory (namespace, revocation).
|
|
79
|
+
|
|
80
|
+
### Lower-level calls
|
|
40
81
|
|
|
41
82
|
```typescript
|
|
42
83
|
import { Prampta } from "@prampta/sdk";
|
|
@@ -95,38 +136,30 @@ const decision = await pg.assertLicensed(subjectCode, {
|
|
|
95
136
|
// Apply the decision's obligations, generate, then submit a receipt.
|
|
96
137
|
```
|
|
97
138
|
|
|
98
|
-
## The whole cycle in one call (0.
|
|
99
|
-
|
|
100
|
-
`generateAuthorized`
|
|
101
|
-
|
|
102
|
-
-
|
|
103
|
-
|
|
104
|
-
-
|
|
105
|
-
|
|
106
|
-
-
|
|
139
|
+
## The whole cycle in one call (0.9.0)
|
|
140
|
+
|
|
141
|
+
`generateAuthorized` binds one `generationId` to one unchanging request:
|
|
142
|
+
|
|
143
|
+
- **one worker per job**: the journal's atomic `claim` lets exactly one
|
|
144
|
+
worker, on any of your servers, run a job; the others get an error;
|
|
145
|
+
- **same id, same request**: a different request under a used
|
|
146
|
+
`generationId` throws `PramptaJobConflictError`, while running or after;
|
|
147
|
+
- asks fresh for **every subject in the output** and generates only if
|
|
148
|
+
**all** of them return a licence-backed `allow`;
|
|
149
|
+
- re-checks every allow **immediately before** your `generate` runs;
|
|
150
|
+
- stores the output hash **before** sending receipts, and resends the
|
|
151
|
+
byte-identical receipt after a failure;
|
|
152
|
+
- a receipt counts as filed only when the registry accepted exactly it:
|
|
153
|
+
`unreported` lists receipts to resend (call again with the same id and
|
|
154
|
+
request), `conflicts` lists decisions for which the registry holds a
|
|
155
|
+
different receipt;
|
|
107
156
|
- gives the allows back (`releaseDecision`) when a subject refuses or your
|
|
108
|
-
`generate` throws
|
|
109
|
-
-
|
|
110
|
-
a repeat call resends missing receipts, or refuses a job that crashed
|
|
111
|
-
midway instead of rerunning it.
|
|
112
|
-
|
|
113
|
-
```typescript
|
|
114
|
-
const result = await pg.generateAuthorized(
|
|
115
|
-
{ subjectIds: [personCode, voiceCode], generationId: jobId, promptHash,
|
|
116
|
-
modality: "video", model: modelName,
|
|
117
|
-
intendedUse: { useCase: "commercial", rights: ["output_generation"] } },
|
|
118
|
-
async (decisions) => {
|
|
119
|
-
// Apply every decision's obligations here. Throw only if nothing was produced.
|
|
120
|
-
const video = await render(prompt);
|
|
121
|
-
return { output: video, outputBytes: video.bytes };
|
|
122
|
-
},
|
|
123
|
-
{ journal: { get: (id) => db.jobs.get(id), put: (e) => db.jobs.put(e) } },
|
|
124
|
-
);
|
|
125
|
-
if (result.unreported.length) retryLater(jobId); // receipts are resent by calling again
|
|
126
|
-
```
|
|
157
|
+
`generate` throws; that job is then closed, use a new `generationId`;
|
|
158
|
+
- never reruns a job that may have started generating.
|
|
127
159
|
|
|
128
|
-
|
|
129
|
-
|
|
160
|
+
`singleProcessJournal()` is an in-memory journal for tests and a single
|
|
161
|
+
process; two servers each holding one can both run a job. 0.9.0 replaced the
|
|
162
|
+
journal's `get` with `claim` and made the journal required.
|
|
130
163
|
|
|
131
164
|
## Check answers against PRE-GEN (recommended)
|
|
132
165
|
|
|
@@ -180,9 +213,10 @@ Persisted `previousSnapshot` detects rollback and signed different-content
|
|
|
180
213
|
snapshots at the same sequence. `minSequence` is also available for a sequence
|
|
181
214
|
floor alone. Storage and concurrent update serialization belong to the host.
|
|
182
215
|
|
|
183
|
-
The
|
|
184
|
-
|
|
185
|
-
|
|
216
|
+
The PRE-GEN steward key is published (pregen.org, DNS) and built into
|
|
217
|
+
`@pregen/verify`; for that directory use `loadPregenDirectory()` above. With
|
|
218
|
+
your own root, never take the trust anchor from the directory itself. An
|
|
219
|
+
authenticated directory snapshot does not prove freshness or rights-holder consent;
|
|
186
220
|
`minSequence` alone does not prevent stale first-boot snapshots. Full live
|
|
187
221
|
federation needs the separately specified trust-management work.
|
|
188
222
|
|
package/dist/index.d.mts
CHANGED
|
@@ -32,6 +32,8 @@ declare class TrustedRegistryDirectory {
|
|
|
32
32
|
assertEndpoint(subject: string, baseUrl: string): void;
|
|
33
33
|
/** V-10: use only AFTER independently verifying the object's signature. */
|
|
34
34
|
assertSigner(code: string, keyId: string): void;
|
|
35
|
+
/** V-14 */
|
|
36
|
+
private assertNotRevoked;
|
|
35
37
|
assertDecision(subject: string, license: string | null, keyId: string, baseUrl: string): void;
|
|
36
38
|
}
|
|
37
39
|
|
|
@@ -381,23 +383,37 @@ interface LicenseRequestResult {
|
|
|
381
383
|
requestId: string;
|
|
382
384
|
status: string;
|
|
383
385
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
+
type AuthorizedRequest = Omit<VerifyRequest, "subjectId" | "generationId"> & {
|
|
387
|
+
subjectIds: string[];
|
|
388
|
+
generationId: string;
|
|
389
|
+
};
|
|
390
|
+
/** One job as `generateAuthorized` records it in the provider's own database. */
|
|
386
391
|
interface GenerationJournalEntry {
|
|
387
392
|
generationId: string;
|
|
388
|
-
/**
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
393
|
+
/** SHA-256 of the request; the same generationId with another request is an error. */
|
|
394
|
+
requestHash: string;
|
|
395
|
+
/** claimed: one worker owns the job. authorized: allows held, generation may
|
|
396
|
+
* have started. generated: output exists, receipts may be missing.
|
|
397
|
+
* reported: receipts accepted. released: nothing produced, allows given back. */
|
|
398
|
+
stage: "claimed" | "authorized" | "generated" | "reported" | "released";
|
|
392
399
|
decisions: SignedDecision[];
|
|
393
400
|
outputHash?: string;
|
|
401
|
+
/** Kept so a resent receipt is byte-identical to the first. */
|
|
402
|
+
generatedAt?: number;
|
|
394
403
|
}
|
|
395
404
|
interface GenerationJournal {
|
|
396
|
-
|
|
397
|
-
|
|
405
|
+
/** ATOMIC across every worker: store `entry` if no entry exists for its
|
|
406
|
+
* generationId and return null; otherwise change nothing and return the
|
|
407
|
+
* existing entry. E.g. INSERT ... ON CONFLICT DO NOTHING, then SELECT.
|
|
408
|
+
* A read followed by a write is not enough. */
|
|
409
|
+
claim(entry: GenerationJournalEntry): Promise<GenerationJournalEntry | null>;
|
|
410
|
+
/** Replace the entry for its generationId. Resolve only after a durable write. */
|
|
398
411
|
put(entry: GenerationJournalEntry): Promise<void>;
|
|
399
412
|
}
|
|
400
|
-
|
|
413
|
+
/** An in-memory journal. Correct for ONE process only: two servers each
|
|
414
|
+
* holding one can both run the same job. Use your shared database otherwise. */
|
|
415
|
+
declare function singleProcessJournal(): GenerationJournal;
|
|
416
|
+
type ReceiptOptions = Omit<NonNullable<Parameters<Prampta["submitReceipt"]>[1]>, "outputHash" | "generatedAt">;
|
|
401
417
|
interface AuthorizedGeneration<T> {
|
|
402
418
|
/** generated: ran now. recovered: output existed from an earlier run, only
|
|
403
419
|
* receipts were sent. already_reported: nothing left to do. */
|
|
@@ -407,9 +423,12 @@ interface AuthorizedGeneration<T> {
|
|
|
407
423
|
output?: T;
|
|
408
424
|
decisions: SignedDecision[];
|
|
409
425
|
outputHash: string;
|
|
410
|
-
/** Receipts
|
|
411
|
-
* generationId
|
|
426
|
+
/** Receipts not filed yet (network, server error). Call again with the same
|
|
427
|
+
* generationId and request to resend them. */
|
|
412
428
|
unreported: SignedDecision[];
|
|
429
|
+
/** The registry already holds a DIFFERENT receipt for these decisions.
|
|
430
|
+
* Resending will not help; find out why. */
|
|
431
|
+
conflicts: SignedDecision[];
|
|
413
432
|
}
|
|
414
433
|
interface OutputMetadata {
|
|
415
434
|
prampta_decision_id: string;
|
|
@@ -449,6 +468,11 @@ declare class PramptaRefusalError extends PramptaError {
|
|
|
449
468
|
get decisionId(): string;
|
|
450
469
|
get licenseId(): string | null;
|
|
451
470
|
}
|
|
471
|
+
/** The same generationId was used for a different request. */
|
|
472
|
+
declare class PramptaJobConflictError extends PramptaError {
|
|
473
|
+
readonly generationId: string;
|
|
474
|
+
constructor(generationId: string);
|
|
475
|
+
}
|
|
452
476
|
declare class PramptaFailClosedError extends PramptaError {
|
|
453
477
|
readonly cause_: unknown;
|
|
454
478
|
constructor(cause_: unknown);
|
|
@@ -591,36 +615,35 @@ declare class Prampta {
|
|
|
591
615
|
metadata: OutputMetadata;
|
|
592
616
|
}>;
|
|
593
617
|
/**
|
|
594
|
-
* The whole cycle in one call, for every subject in the output:
|
|
595
|
-
* ask
|
|
596
|
-
*
|
|
597
|
-
* subject. If any subject refuses, or `generate` throws, the allows already
|
|
598
|
-
* held are released so they do not count against usage limits.
|
|
618
|
+
* The whole cycle in one call, for every subject in the output (PRE-GEN P-9):
|
|
619
|
+
* ask fresh for each subject, require a licence-backed allow from all of
|
|
620
|
+
* them, generate, file one receipt per subject.
|
|
599
621
|
*
|
|
600
|
-
* `
|
|
601
|
-
*
|
|
602
|
-
*
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
*
|
|
622
|
+
* One `generationId` is bound to one unchanging request. The journal's
|
|
623
|
+
* atomic `claim` lets exactly one worker run a job, across every server
|
|
624
|
+
* that shares the journal; a different request under a used id is an
|
|
625
|
+
* error. Every allow is re-checked immediately before `generate` runs. The
|
|
626
|
+
* output hash is stored before receipts are sent, and a receipt counts as
|
|
627
|
+
* filed only when the registry accepted exactly it. A job that may have
|
|
628
|
+
* started generating is never run again: check your own records,
|
|
629
|
+
* releaseDecision() if nothing was produced, and use a new generationId.
|
|
630
|
+
* If a subject refuses or `generate` throws, the allows are given back.
|
|
631
|
+
* `generate` must throw only when nothing was produced.
|
|
606
632
|
*/
|
|
607
|
-
generateAuthorized<T>(request:
|
|
608
|
-
subjectIds: string[];
|
|
609
|
-
generationId: string;
|
|
610
|
-
}, generate: (decisions: SignedDecision[]) => Promise<{
|
|
633
|
+
generateAuthorized<T>(request: AuthorizedRequest, generate: (decisions: SignedDecision[]) => Promise<{
|
|
611
634
|
output: T;
|
|
612
635
|
outputHash?: string;
|
|
613
636
|
outputBytes?: Uint8Array | string;
|
|
614
|
-
}>, options
|
|
615
|
-
journal
|
|
637
|
+
}>, options: {
|
|
638
|
+
journal: GenerationJournal;
|
|
616
639
|
receipt?: ReceiptOptions;
|
|
617
640
|
}): Promise<AuthorizedGeneration<T>>;
|
|
641
|
+
/** SHA-256 of everything that defines the job. Same id + different hash = a different job. */
|
|
642
|
+
private requestHash;
|
|
618
643
|
private runAuthorized;
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
private
|
|
622
|
-
/** True when every allow was given back. */
|
|
623
|
-
private releaseAll;
|
|
644
|
+
private report;
|
|
645
|
+
/** Release every allow; the job is marked released only when all of them were given back. */
|
|
646
|
+
private giveBack;
|
|
624
647
|
/** Give back an allow that produced nothing (failed or cancelled job), so
|
|
625
648
|
* it does not count against the licence's usage limit. Only before a
|
|
626
649
|
* receipt and within 24 hours; the release is recorded in the audit log. */
|
|
@@ -678,4 +701,4 @@ declare class Prampta {
|
|
|
678
701
|
private getOnce;
|
|
679
702
|
}
|
|
680
703
|
|
|
681
|
-
export { type AuthorizedGeneration, type DeliveryResult, type GenerationJournal, type GenerationJournalEntry, HARD_REFUSAL_CODES, type LicenseRequestInput, type LicenseRequestResult, type LicenseSignatureInput, type Observation, type ObservationOutbox, type OutputMetadata, Prampta, PramptaApiError, type PramptaConfig, PramptaError, PramptaFailClosedError, PramptaNetworkError, PramptaRefusalError, PramptaReporter, PramptaSchemaError, PramptaSignatureError, PramptaTimeoutError, REFUSAL_DESCRIPTIONS, type ReceiptOptions, RegistryDirectoryError, type RegistryDirectoryOptions, type ReportInput, SOFT_REFUSAL_CODES, type SignedDecision, type SubjectIndexEntry, type SubjectInfo, TrustedRegistryDirectory, type VerifyOptions, type VerifyRequest, canonicalJson, hashPrompt, matchSubjects, normalizeForMatch, verifyLicenseSignatures, verifyMerkleProof };
|
|
704
|
+
export { type AuthorizedGeneration, type AuthorizedRequest, type DeliveryResult, type GenerationJournal, type GenerationJournalEntry, HARD_REFUSAL_CODES, type LicenseRequestInput, type LicenseRequestResult, type LicenseSignatureInput, type Observation, type ObservationOutbox, type OutputMetadata, Prampta, PramptaApiError, type PramptaConfig, PramptaError, PramptaFailClosedError, PramptaJobConflictError, PramptaNetworkError, PramptaRefusalError, PramptaReporter, PramptaSchemaError, PramptaSignatureError, PramptaTimeoutError, REFUSAL_DESCRIPTIONS, type ReceiptOptions, RegistryDirectoryError, type RegistryDirectoryOptions, type ReportInput, SOFT_REFUSAL_CODES, type SignedDecision, type SubjectIndexEntry, type SubjectInfo, TrustedRegistryDirectory, type VerifyOptions, type VerifyRequest, canonicalJson, hashPrompt, matchSubjects, normalizeForMatch, singleProcessJournal, verifyLicenseSignatures, verifyMerkleProof };
|
package/dist/index.d.ts
CHANGED
|
@@ -32,6 +32,8 @@ declare class TrustedRegistryDirectory {
|
|
|
32
32
|
assertEndpoint(subject: string, baseUrl: string): void;
|
|
33
33
|
/** V-10: use only AFTER independently verifying the object's signature. */
|
|
34
34
|
assertSigner(code: string, keyId: string): void;
|
|
35
|
+
/** V-14 */
|
|
36
|
+
private assertNotRevoked;
|
|
35
37
|
assertDecision(subject: string, license: string | null, keyId: string, baseUrl: string): void;
|
|
36
38
|
}
|
|
37
39
|
|
|
@@ -381,23 +383,37 @@ interface LicenseRequestResult {
|
|
|
381
383
|
requestId: string;
|
|
382
384
|
status: string;
|
|
383
385
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
+
type AuthorizedRequest = Omit<VerifyRequest, "subjectId" | "generationId"> & {
|
|
387
|
+
subjectIds: string[];
|
|
388
|
+
generationId: string;
|
|
389
|
+
};
|
|
390
|
+
/** One job as `generateAuthorized` records it in the provider's own database. */
|
|
386
391
|
interface GenerationJournalEntry {
|
|
387
392
|
generationId: string;
|
|
388
|
-
/**
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
393
|
+
/** SHA-256 of the request; the same generationId with another request is an error. */
|
|
394
|
+
requestHash: string;
|
|
395
|
+
/** claimed: one worker owns the job. authorized: allows held, generation may
|
|
396
|
+
* have started. generated: output exists, receipts may be missing.
|
|
397
|
+
* reported: receipts accepted. released: nothing produced, allows given back. */
|
|
398
|
+
stage: "claimed" | "authorized" | "generated" | "reported" | "released";
|
|
392
399
|
decisions: SignedDecision[];
|
|
393
400
|
outputHash?: string;
|
|
401
|
+
/** Kept so a resent receipt is byte-identical to the first. */
|
|
402
|
+
generatedAt?: number;
|
|
394
403
|
}
|
|
395
404
|
interface GenerationJournal {
|
|
396
|
-
|
|
397
|
-
|
|
405
|
+
/** ATOMIC across every worker: store `entry` if no entry exists for its
|
|
406
|
+
* generationId and return null; otherwise change nothing and return the
|
|
407
|
+
* existing entry. E.g. INSERT ... ON CONFLICT DO NOTHING, then SELECT.
|
|
408
|
+
* A read followed by a write is not enough. */
|
|
409
|
+
claim(entry: GenerationJournalEntry): Promise<GenerationJournalEntry | null>;
|
|
410
|
+
/** Replace the entry for its generationId. Resolve only after a durable write. */
|
|
398
411
|
put(entry: GenerationJournalEntry): Promise<void>;
|
|
399
412
|
}
|
|
400
|
-
|
|
413
|
+
/** An in-memory journal. Correct for ONE process only: two servers each
|
|
414
|
+
* holding one can both run the same job. Use your shared database otherwise. */
|
|
415
|
+
declare function singleProcessJournal(): GenerationJournal;
|
|
416
|
+
type ReceiptOptions = Omit<NonNullable<Parameters<Prampta["submitReceipt"]>[1]>, "outputHash" | "generatedAt">;
|
|
401
417
|
interface AuthorizedGeneration<T> {
|
|
402
418
|
/** generated: ran now. recovered: output existed from an earlier run, only
|
|
403
419
|
* receipts were sent. already_reported: nothing left to do. */
|
|
@@ -407,9 +423,12 @@ interface AuthorizedGeneration<T> {
|
|
|
407
423
|
output?: T;
|
|
408
424
|
decisions: SignedDecision[];
|
|
409
425
|
outputHash: string;
|
|
410
|
-
/** Receipts
|
|
411
|
-
* generationId
|
|
426
|
+
/** Receipts not filed yet (network, server error). Call again with the same
|
|
427
|
+
* generationId and request to resend them. */
|
|
412
428
|
unreported: SignedDecision[];
|
|
429
|
+
/** The registry already holds a DIFFERENT receipt for these decisions.
|
|
430
|
+
* Resending will not help; find out why. */
|
|
431
|
+
conflicts: SignedDecision[];
|
|
413
432
|
}
|
|
414
433
|
interface OutputMetadata {
|
|
415
434
|
prampta_decision_id: string;
|
|
@@ -449,6 +468,11 @@ declare class PramptaRefusalError extends PramptaError {
|
|
|
449
468
|
get decisionId(): string;
|
|
450
469
|
get licenseId(): string | null;
|
|
451
470
|
}
|
|
471
|
+
/** The same generationId was used for a different request. */
|
|
472
|
+
declare class PramptaJobConflictError extends PramptaError {
|
|
473
|
+
readonly generationId: string;
|
|
474
|
+
constructor(generationId: string);
|
|
475
|
+
}
|
|
452
476
|
declare class PramptaFailClosedError extends PramptaError {
|
|
453
477
|
readonly cause_: unknown;
|
|
454
478
|
constructor(cause_: unknown);
|
|
@@ -591,36 +615,35 @@ declare class Prampta {
|
|
|
591
615
|
metadata: OutputMetadata;
|
|
592
616
|
}>;
|
|
593
617
|
/**
|
|
594
|
-
* The whole cycle in one call, for every subject in the output:
|
|
595
|
-
* ask
|
|
596
|
-
*
|
|
597
|
-
* subject. If any subject refuses, or `generate` throws, the allows already
|
|
598
|
-
* held are released so they do not count against usage limits.
|
|
618
|
+
* The whole cycle in one call, for every subject in the output (PRE-GEN P-9):
|
|
619
|
+
* ask fresh for each subject, require a licence-backed allow from all of
|
|
620
|
+
* them, generate, file one receipt per subject.
|
|
599
621
|
*
|
|
600
|
-
* `
|
|
601
|
-
*
|
|
602
|
-
*
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
*
|
|
622
|
+
* One `generationId` is bound to one unchanging request. The journal's
|
|
623
|
+
* atomic `claim` lets exactly one worker run a job, across every server
|
|
624
|
+
* that shares the journal; a different request under a used id is an
|
|
625
|
+
* error. Every allow is re-checked immediately before `generate` runs. The
|
|
626
|
+
* output hash is stored before receipts are sent, and a receipt counts as
|
|
627
|
+
* filed only when the registry accepted exactly it. A job that may have
|
|
628
|
+
* started generating is never run again: check your own records,
|
|
629
|
+
* releaseDecision() if nothing was produced, and use a new generationId.
|
|
630
|
+
* If a subject refuses or `generate` throws, the allows are given back.
|
|
631
|
+
* `generate` must throw only when nothing was produced.
|
|
606
632
|
*/
|
|
607
|
-
generateAuthorized<T>(request:
|
|
608
|
-
subjectIds: string[];
|
|
609
|
-
generationId: string;
|
|
610
|
-
}, generate: (decisions: SignedDecision[]) => Promise<{
|
|
633
|
+
generateAuthorized<T>(request: AuthorizedRequest, generate: (decisions: SignedDecision[]) => Promise<{
|
|
611
634
|
output: T;
|
|
612
635
|
outputHash?: string;
|
|
613
636
|
outputBytes?: Uint8Array | string;
|
|
614
|
-
}>, options
|
|
615
|
-
journal
|
|
637
|
+
}>, options: {
|
|
638
|
+
journal: GenerationJournal;
|
|
616
639
|
receipt?: ReceiptOptions;
|
|
617
640
|
}): Promise<AuthorizedGeneration<T>>;
|
|
641
|
+
/** SHA-256 of everything that defines the job. Same id + different hash = a different job. */
|
|
642
|
+
private requestHash;
|
|
618
643
|
private runAuthorized;
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
private
|
|
622
|
-
/** True when every allow was given back. */
|
|
623
|
-
private releaseAll;
|
|
644
|
+
private report;
|
|
645
|
+
/** Release every allow; the job is marked released only when all of them were given back. */
|
|
646
|
+
private giveBack;
|
|
624
647
|
/** Give back an allow that produced nothing (failed or cancelled job), so
|
|
625
648
|
* it does not count against the licence's usage limit. Only before a
|
|
626
649
|
* receipt and within 24 hours; the release is recorded in the audit log. */
|
|
@@ -678,4 +701,4 @@ declare class Prampta {
|
|
|
678
701
|
private getOnce;
|
|
679
702
|
}
|
|
680
703
|
|
|
681
|
-
export { type AuthorizedGeneration, type DeliveryResult, type GenerationJournal, type GenerationJournalEntry, HARD_REFUSAL_CODES, type LicenseRequestInput, type LicenseRequestResult, type LicenseSignatureInput, type Observation, type ObservationOutbox, type OutputMetadata, Prampta, PramptaApiError, type PramptaConfig, PramptaError, PramptaFailClosedError, PramptaNetworkError, PramptaRefusalError, PramptaReporter, PramptaSchemaError, PramptaSignatureError, PramptaTimeoutError, REFUSAL_DESCRIPTIONS, type ReceiptOptions, RegistryDirectoryError, type RegistryDirectoryOptions, type ReportInput, SOFT_REFUSAL_CODES, type SignedDecision, type SubjectIndexEntry, type SubjectInfo, TrustedRegistryDirectory, type VerifyOptions, type VerifyRequest, canonicalJson, hashPrompt, matchSubjects, normalizeForMatch, verifyLicenseSignatures, verifyMerkleProof };
|
|
704
|
+
export { type AuthorizedGeneration, type AuthorizedRequest, type DeliveryResult, type GenerationJournal, type GenerationJournalEntry, HARD_REFUSAL_CODES, type LicenseRequestInput, type LicenseRequestResult, type LicenseSignatureInput, type Observation, type ObservationOutbox, type OutputMetadata, Prampta, PramptaApiError, type PramptaConfig, PramptaError, PramptaFailClosedError, PramptaJobConflictError, PramptaNetworkError, PramptaRefusalError, PramptaReporter, PramptaSchemaError, PramptaSignatureError, PramptaTimeoutError, REFUSAL_DESCRIPTIONS, type ReceiptOptions, RegistryDirectoryError, type RegistryDirectoryOptions, type ReportInput, SOFT_REFUSAL_CODES, type SignedDecision, type SubjectIndexEntry, type SubjectInfo, TrustedRegistryDirectory, type VerifyOptions, type VerifyRequest, canonicalJson, hashPrompt, matchSubjects, normalizeForMatch, singleProcessJournal, verifyLicenseSignatures, verifyMerkleProof };
|
package/dist/index.js
CHANGED
|
@@ -35,6 +35,7 @@ __export(index_exports, {
|
|
|
35
35
|
PramptaApiError: () => PramptaApiError,
|
|
36
36
|
PramptaError: () => PramptaError,
|
|
37
37
|
PramptaFailClosedError: () => PramptaFailClosedError,
|
|
38
|
+
PramptaJobConflictError: () => PramptaJobConflictError,
|
|
38
39
|
PramptaNetworkError: () => PramptaNetworkError,
|
|
39
40
|
PramptaRefusalError: () => PramptaRefusalError,
|
|
40
41
|
PramptaReporter: () => PramptaReporter,
|
|
@@ -50,6 +51,7 @@ __export(index_exports, {
|
|
|
50
51
|
loadPregenDirectory: () => import_verify2.loadDirectory,
|
|
51
52
|
matchSubjects: () => matchSubjects,
|
|
52
53
|
normalizeForMatch: () => normalizeForMatch,
|
|
54
|
+
singleProcessJournal: () => singleProcessJournal,
|
|
53
55
|
verifyLicenseSignatures: () => verifyLicenseSignatures,
|
|
54
56
|
verifyMerkleProof: () => verifyMerkleProof
|
|
55
57
|
});
|
|
@@ -140,16 +142,18 @@ function registryNamespace(code) {
|
|
|
140
142
|
return "";
|
|
141
143
|
}
|
|
142
144
|
var TrustedRegistryDirectory = class _TrustedRegistryDirectory {
|
|
143
|
-
constructor(registries, sequence, snapshotHash, seal) {
|
|
145
|
+
constructor(registries, revokedKeys, sequence, snapshotHash, seal) {
|
|
144
146
|
this.sequence = sequence;
|
|
145
147
|
this.snapshotHash = snapshotHash;
|
|
146
148
|
if (seal !== verifiedConstruction) fail("Construct directories through signature verification");
|
|
147
149
|
this.#registries = registries;
|
|
150
|
+
this.#revokedKeys = new Set(revokedKeys);
|
|
148
151
|
Object.freeze(this);
|
|
149
152
|
}
|
|
150
153
|
sequence;
|
|
151
154
|
snapshotHash;
|
|
152
155
|
#registries;
|
|
156
|
+
#revokedKeys;
|
|
153
157
|
static async verify(input, options) {
|
|
154
158
|
const min = options.minSequence ?? 0;
|
|
155
159
|
if (!Number.isSafeInteger(min) || min < 0) fail("minSequence must be a nonnegative safe integer");
|
|
@@ -189,6 +193,16 @@ var TrustedRegistryDirectory = class _TrustedRegistryDirectory {
|
|
|
189
193
|
}
|
|
190
194
|
}
|
|
191
195
|
if (!prefixes.has("")) fail("Missing origin namespace");
|
|
196
|
+
for (const r of d.registries) {
|
|
197
|
+
if (r.revoked !== void 0 && !(r.revoked && typeof r.revoked === "object" && typeof r.revoked.at === "string")) {
|
|
198
|
+
fail("Invalid registry revocation");
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (d.revoked_keys !== void 0 && !(Array.isArray(d.revoked_keys) && d.revoked_keys.every(
|
|
202
|
+
(k) => k && /^pg-ed25519:[0-9a-f]{32}$/.test(String(k.key_id)) && typeof k.at === "string"
|
|
203
|
+
))) {
|
|
204
|
+
fail("Invalid revoked key list");
|
|
205
|
+
}
|
|
192
206
|
const publicKey = hexBytes(options.stewardPublicKeyHex, 32);
|
|
193
207
|
const hash = await sha256(publicKey);
|
|
194
208
|
const fp = "pg-ed25519:" + hash.slice(0, 32);
|
|
@@ -206,7 +220,13 @@ var TrustedRegistryDirectory = class _TrustedRegistryDirectory {
|
|
|
206
220
|
if (!valid) fail("Invalid directory signature");
|
|
207
221
|
const snapshotHash = await sha256(message);
|
|
208
222
|
if (previous && previous.sequence === d.sequence && previous.hash !== snapshotHash) fail("Directory equivocation detected");
|
|
209
|
-
return new _TrustedRegistryDirectory(
|
|
223
|
+
return new _TrustedRegistryDirectory(
|
|
224
|
+
d.registries,
|
|
225
|
+
(d.revoked_keys ?? []).map((k) => k.key_id),
|
|
226
|
+
d.sequence,
|
|
227
|
+
snapshotHash,
|
|
228
|
+
verifiedConstruction
|
|
229
|
+
);
|
|
210
230
|
}
|
|
211
231
|
registryFor(code) {
|
|
212
232
|
const prefix = registryNamespace(code);
|
|
@@ -222,7 +242,13 @@ var TrustedRegistryDirectory = class _TrustedRegistryDirectory {
|
|
|
222
242
|
}
|
|
223
243
|
/** V-10: use only AFTER independently verifying the object's signature. */
|
|
224
244
|
assertSigner(code, keyId) {
|
|
225
|
-
|
|
245
|
+
const owner = this.registryFor(code);
|
|
246
|
+
if (!owner.key_fingerprints.includes(keyId)) fail("Registry issuer/key mismatch");
|
|
247
|
+
this.assertNotRevoked(owner, keyId);
|
|
248
|
+
}
|
|
249
|
+
/** V-14 */
|
|
250
|
+
assertNotRevoked(owner, keyId) {
|
|
251
|
+
if (owner.revoked || this.#revokedKeys.has(keyId)) fail("Revoked registry or key");
|
|
226
252
|
}
|
|
227
253
|
assertDecision(subject, license, keyId, baseUrl) {
|
|
228
254
|
this.assertEndpoint(subject, baseUrl);
|
|
@@ -230,6 +256,7 @@ var TrustedRegistryDirectory = class _TrustedRegistryDirectory {
|
|
|
230
256
|
else {
|
|
231
257
|
const owner = this.#registries.find((r) => apiUrl(r.api) === apiUrl(baseUrl));
|
|
232
258
|
if (!owner?.key_fingerprints.includes(keyId)) fail("Registry issuer/key mismatch");
|
|
259
|
+
this.assertNotRevoked(owner, keyId);
|
|
233
260
|
}
|
|
234
261
|
if (license) {
|
|
235
262
|
this.assertEndpoint(license, baseUrl);
|
|
@@ -384,6 +411,20 @@ var PramptaReporter = class {
|
|
|
384
411
|
};
|
|
385
412
|
|
|
386
413
|
// src/index.ts
|
|
414
|
+
function singleProcessJournal() {
|
|
415
|
+
const entries = /* @__PURE__ */ new Map();
|
|
416
|
+
return {
|
|
417
|
+
async claim(entry) {
|
|
418
|
+
const found = entries.get(entry.generationId);
|
|
419
|
+
if (found) return structuredClone(found);
|
|
420
|
+
entries.set(entry.generationId, structuredClone(entry));
|
|
421
|
+
return null;
|
|
422
|
+
},
|
|
423
|
+
async put(entry) {
|
|
424
|
+
entries.set(entry.generationId, structuredClone(entry));
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
}
|
|
387
428
|
var PramptaError = class extends Error {
|
|
388
429
|
constructor(message) {
|
|
389
430
|
super(message);
|
|
@@ -449,6 +490,14 @@ var PramptaRefusalError = class extends PramptaError {
|
|
|
449
490
|
return this.decision.licenseId;
|
|
450
491
|
}
|
|
451
492
|
};
|
|
493
|
+
var PramptaJobConflictError = class extends PramptaError {
|
|
494
|
+
constructor(generationId) {
|
|
495
|
+
super(`generationId ${generationId} was already used for a different request`);
|
|
496
|
+
this.generationId = generationId;
|
|
497
|
+
this.name = "PramptaJobConflictError";
|
|
498
|
+
}
|
|
499
|
+
generationId;
|
|
500
|
+
};
|
|
452
501
|
var PramptaFailClosedError = class extends PramptaError {
|
|
453
502
|
constructor(cause_) {
|
|
454
503
|
super(`PRAMPTA fail-closed: generation blocked because verification failed (${cause_})`);
|
|
@@ -584,6 +633,42 @@ function matchSubjects(text, entries) {
|
|
|
584
633
|
}
|
|
585
634
|
return hits;
|
|
586
635
|
}
|
|
636
|
+
var UNDERSTOOD_DECISION_MEMBERS = /* @__PURE__ */ new Set([
|
|
637
|
+
"schema_version",
|
|
638
|
+
"decision_id",
|
|
639
|
+
"nonce",
|
|
640
|
+
"disposition",
|
|
641
|
+
"allowed",
|
|
642
|
+
"reason",
|
|
643
|
+
"policy_version",
|
|
644
|
+
"subject_id",
|
|
645
|
+
"licensee_id",
|
|
646
|
+
"provider_id",
|
|
647
|
+
"license_id",
|
|
648
|
+
"prompt_hash",
|
|
649
|
+
"model",
|
|
650
|
+
"modality",
|
|
651
|
+
"intended_use",
|
|
652
|
+
"obligations",
|
|
653
|
+
"generation_id",
|
|
654
|
+
"issued_at",
|
|
655
|
+
"expires_at",
|
|
656
|
+
"critical",
|
|
657
|
+
"operator_key_id",
|
|
658
|
+
"operator_signature",
|
|
659
|
+
"subject_authority",
|
|
660
|
+
"rules_text",
|
|
661
|
+
"rules_text_hash",
|
|
662
|
+
"watermark_payload",
|
|
663
|
+
"is_hard_refusal",
|
|
664
|
+
"revocation_epoch",
|
|
665
|
+
"max_cache_age_seconds",
|
|
666
|
+
"cache_scope",
|
|
667
|
+
"provider_user_binding",
|
|
668
|
+
"provider_identity_link_id",
|
|
669
|
+
"detection_id",
|
|
670
|
+
"remediation"
|
|
671
|
+
]);
|
|
587
672
|
var Prampta = class _Prampta {
|
|
588
673
|
baseUrl;
|
|
589
674
|
providerId;
|
|
@@ -752,6 +837,11 @@ var Prampta = class _Prampta {
|
|
|
752
837
|
*/
|
|
753
838
|
async verifyDecision(raw, decision) {
|
|
754
839
|
if (typeof raw.allowed !== "boolean") throw new PramptaSchemaError("allowed must be a boolean");
|
|
840
|
+
if (raw.critical !== void 0) {
|
|
841
|
+
if (!Array.isArray(raw.critical)) throw new PramptaSchemaError("critical must be an array");
|
|
842
|
+
const unknown = raw.critical.filter((m) => typeof m !== "string" || !UNDERSTOOD_DECISION_MEMBERS.has(m));
|
|
843
|
+
if (unknown.length) throw new PramptaSchemaError(`Decision lists critical members this SDK does not understand: ${unknown.join(", ")}`);
|
|
844
|
+
}
|
|
755
845
|
const disposition = raw.disposition ?? (raw.allowed ? "allow" : "deny");
|
|
756
846
|
if (!["allow", "deny", "review", "not_blocked"].includes(disposition) || raw.allowed !== (disposition === "allow")) {
|
|
757
847
|
throw new PramptaSchemaError("allowed and disposition are inconsistent or unsupported");
|
|
@@ -802,7 +892,7 @@ var Prampta = class _Prampta {
|
|
|
802
892
|
);
|
|
803
893
|
}
|
|
804
894
|
const now = Math.floor(Date.now() / 1e3);
|
|
805
|
-
if (!Number.isSafeInteger(raw.expires_at) ||
|
|
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) {
|
|
806
896
|
throw new PramptaSchemaError("Decision has invalid validity timestamps");
|
|
807
897
|
}
|
|
808
898
|
if (decision.expiresAt <= now) {
|
|
@@ -1037,86 +1127,101 @@ var Prampta = class _Prampta {
|
|
|
1037
1127
|
return { output, decision, metadata };
|
|
1038
1128
|
}
|
|
1039
1129
|
/**
|
|
1040
|
-
* The whole cycle in one call, for every subject in the output:
|
|
1041
|
-
* ask
|
|
1042
|
-
*
|
|
1043
|
-
* subject. If any subject refuses, or `generate` throws, the allows already
|
|
1044
|
-
* held are released so they do not count against usage limits.
|
|
1130
|
+
* The whole cycle in one call, for every subject in the output (PRE-GEN P-9):
|
|
1131
|
+
* ask fresh for each subject, require a licence-backed allow from all of
|
|
1132
|
+
* them, generate, file one receipt per subject.
|
|
1045
1133
|
*
|
|
1046
|
-
* `
|
|
1047
|
-
*
|
|
1048
|
-
*
|
|
1049
|
-
*
|
|
1050
|
-
*
|
|
1051
|
-
*
|
|
1134
|
+
* One `generationId` is bound to one unchanging request. The journal's
|
|
1135
|
+
* atomic `claim` lets exactly one worker run a job, across every server
|
|
1136
|
+
* that shares the journal; a different request under a used id is an
|
|
1137
|
+
* error. Every allow is re-checked immediately before `generate` runs. The
|
|
1138
|
+
* output hash is stored before receipts are sent, and a receipt counts as
|
|
1139
|
+
* filed only when the registry accepted exactly it. A job that may have
|
|
1140
|
+
* started generating is never run again: check your own records,
|
|
1141
|
+
* releaseDecision() if nothing was produced, and use a new generationId.
|
|
1142
|
+
* If a subject refuses or `generate` throws, the allows are given back.
|
|
1143
|
+
* `generate` must throw only when nothing was produced.
|
|
1052
1144
|
*/
|
|
1053
|
-
async generateAuthorized(request, generate, options
|
|
1145
|
+
async generateAuthorized(request, generate, options) {
|
|
1054
1146
|
const id = request.generationId;
|
|
1055
1147
|
if (!id) throw new PramptaSchemaError("generateAuthorized requires a generationId");
|
|
1148
|
+
if (!options?.journal) throw new PramptaSchemaError("generateAuthorized requires a journal (singleProcessJournal() for one process)");
|
|
1149
|
+
const requestHash = await this.requestHash(request);
|
|
1056
1150
|
const running = this.inFlight.get(id);
|
|
1057
|
-
if (running)
|
|
1058
|
-
|
|
1059
|
-
|
|
1151
|
+
if (running) {
|
|
1152
|
+
if (running.requestHash !== requestHash) throw new PramptaJobConflictError(id);
|
|
1153
|
+
return running.run;
|
|
1154
|
+
}
|
|
1155
|
+
const run = this.runAuthorized(request, requestHash, generate, options).finally(() => this.inFlight.delete(id));
|
|
1156
|
+
this.inFlight.set(id, { requestHash, run });
|
|
1060
1157
|
return run;
|
|
1061
1158
|
}
|
|
1062
|
-
|
|
1159
|
+
/** SHA-256 of everything that defines the job. Same id + different hash = a different job. */
|
|
1160
|
+
async requestHash(request) {
|
|
1161
|
+
const { generationId, subjectIds, prompt, promptHash, idempotencyKey, returnUrl, detectionId, ...rest } = request;
|
|
1162
|
+
return sha2562(canonicalJson(JSON.parse(JSON.stringify({
|
|
1163
|
+
...rest,
|
|
1164
|
+
generationId,
|
|
1165
|
+
subjectIds: [...new Set(subjectIds)].sort(),
|
|
1166
|
+
promptHash: promptHash ?? (prompt !== void 0 ? await hashPrompt(prompt) : ""),
|
|
1167
|
+
providerId: this.providerId,
|
|
1168
|
+
licenseeId: this.licenseeId
|
|
1169
|
+
}))));
|
|
1170
|
+
}
|
|
1171
|
+
async runAuthorized(request, requestHash, generate, { journal, receipt }) {
|
|
1063
1172
|
const { subjectIds, generationId, ...rest } = request;
|
|
1064
1173
|
const subjects = [...new Set(subjectIds)];
|
|
1065
1174
|
if (subjects.length === 0) throw new PramptaSchemaError("generateAuthorized requires at least one subjectId");
|
|
1066
|
-
const
|
|
1067
|
-
if (
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
const unreported2 = await this.fileReceipts(saved.decisions, saved.outputHash ?? "", receipt);
|
|
1075
|
-
if (unreported2.length === 0) await journal.put({ ...saved, stage: "reported" });
|
|
1076
|
-
return { status: "recovered", decisions: saved.decisions, outputHash: saved.outputHash ?? "", unreported: unreported2 };
|
|
1175
|
+
const existing = await journal.claim({ generationId, requestHash, stage: "claimed", decisions: [] });
|
|
1176
|
+
if (existing) {
|
|
1177
|
+
if (existing.requestHash !== requestHash) throw new PramptaJobConflictError(generationId);
|
|
1178
|
+
if (existing.stage === "reported") {
|
|
1179
|
+
return { status: "already_reported", decisions: existing.decisions, outputHash: existing.outputHash ?? "", unreported: [], conflicts: [] };
|
|
1180
|
+
}
|
|
1181
|
+
if (existing.stage === "generated") return this.report(journal, existing, "recovered", void 0, receipt);
|
|
1182
|
+
throw new PramptaError(existing.stage === "released" ? `Generation ${generationId} produced nothing and its allows were given back; use a new generationId` : `Generation ${generationId} is running elsewhere or was interrupted after it was claimed; it is not run again. If nothing was produced, releaseDecision() each decision and use a new generationId.`);
|
|
1077
1183
|
}
|
|
1078
1184
|
const settled = await Promise.allSettled(subjects.map((s) => this.assertLicensed(s, { ...rest, generationId })));
|
|
1079
1185
|
const decisions = settled.flatMap((r) => r.status === "fulfilled" ? [r.value] : []);
|
|
1080
1186
|
const failed = settled.find((r) => r.status === "rejected");
|
|
1081
1187
|
if (failed) {
|
|
1082
|
-
await this.
|
|
1188
|
+
await this.giveBack(journal, { generationId, requestHash, stage: "claimed", decisions }, "another subject in the same output was refused");
|
|
1083
1189
|
throw failed.reason;
|
|
1084
1190
|
}
|
|
1191
|
+
const entry = { generationId, requestHash, stage: "authorized", decisions };
|
|
1192
|
+
await journal.put(entry);
|
|
1085
1193
|
if (decisions.some((d) => d.expiresAt <= Math.floor(Date.now() / 1e3))) {
|
|
1086
|
-
await this.
|
|
1087
|
-
throw new PramptaError("An authorization expired before generation started; ask again");
|
|
1194
|
+
await this.giveBack(journal, entry, "expired before generation started");
|
|
1195
|
+
throw new PramptaError("An authorization expired before generation started; ask again with a new generationId");
|
|
1088
1196
|
}
|
|
1089
|
-
await journal?.put({ generationId, stage: "authorized", decisions });
|
|
1090
1197
|
let result;
|
|
1091
1198
|
try {
|
|
1092
1199
|
result = await generate(decisions);
|
|
1093
1200
|
} catch (e) {
|
|
1094
|
-
|
|
1095
|
-
await journal?.put({ generationId, stage: "released", decisions });
|
|
1096
|
-
}
|
|
1201
|
+
await this.giveBack(journal, entry, "generation failed");
|
|
1097
1202
|
throw e;
|
|
1098
1203
|
}
|
|
1099
1204
|
const bytes = typeof result.outputBytes === "string" ? new TextEncoder().encode(result.outputBytes) : result.outputBytes;
|
|
1100
1205
|
const outputHash = result.outputHash ?? (bytes ? await sha256Bytes(bytes) : "");
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
const r = results[i];
|
|
1112
|
-
if (r.status === "fulfilled") return false;
|
|
1113
|
-
return !(r.reason instanceof PramptaApiError && r.reason.status === 409 && /already exists/.test(r.reason.detail));
|
|
1206
|
+
const generated = { ...entry, stage: "generated", outputHash, generatedAt: Math.floor(Date.now() / 1e3) };
|
|
1207
|
+
await journal.put(generated);
|
|
1208
|
+
return this.report(journal, generated, "generated", result.output, receipt);
|
|
1209
|
+
}
|
|
1210
|
+
async report(journal, entry, status, output, receipt) {
|
|
1211
|
+
const results = await Promise.allSettled(entry.decisions.map((d) => this.submitReceipt(d, { ...receipt, outputHash: entry.outputHash ?? "", generatedAt: entry.generatedAt })));
|
|
1212
|
+
const unreported = [], conflicts = [];
|
|
1213
|
+
results.forEach((r, i) => {
|
|
1214
|
+
if (r.status === "fulfilled") return;
|
|
1215
|
+
(r.reason instanceof PramptaApiError && r.reason.status === 409 ? conflicts : unreported).push(entry.decisions[i]);
|
|
1114
1216
|
});
|
|
1217
|
+
if (!unreported.length && !conflicts.length) await journal.put({ ...entry, stage: "reported" });
|
|
1218
|
+
return { status, output, decisions: entry.decisions, outputHash: entry.outputHash ?? "", unreported, conflicts };
|
|
1115
1219
|
}
|
|
1116
|
-
/**
|
|
1117
|
-
async
|
|
1118
|
-
const results = await Promise.allSettled(decisions.map((d) => this.releaseDecision(d.decisionId, reason)));
|
|
1119
|
-
|
|
1220
|
+
/** Release every allow; the job is marked released only when all of them were given back. */
|
|
1221
|
+
async giveBack(journal, entry, reason) {
|
|
1222
|
+
const results = await Promise.allSettled(entry.decisions.map((d) => this.releaseDecision(d.decisionId, reason)));
|
|
1223
|
+
const all = results.every((r) => r.status === "fulfilled" || r.reason instanceof PramptaApiError && r.reason.status === 409 && /nothing_to_release/.test(r.reason.detail));
|
|
1224
|
+
if (all) await journal.put({ ...entry, stage: "released" });
|
|
1120
1225
|
}
|
|
1121
1226
|
/** Give back an allow that produced nothing (failed or cancelled job), so
|
|
1122
1227
|
* it does not count against the licence's usage limit. Only before a
|
|
@@ -1391,6 +1496,7 @@ function sameEndpoint(a, b) {
|
|
|
1391
1496
|
PramptaApiError,
|
|
1392
1497
|
PramptaError,
|
|
1393
1498
|
PramptaFailClosedError,
|
|
1499
|
+
PramptaJobConflictError,
|
|
1394
1500
|
PramptaNetworkError,
|
|
1395
1501
|
PramptaRefusalError,
|
|
1396
1502
|
PramptaReporter,
|
|
@@ -1406,6 +1512,7 @@ function sameEndpoint(a, b) {
|
|
|
1406
1512
|
loadPregenDirectory,
|
|
1407
1513
|
matchSubjects,
|
|
1408
1514
|
normalizeForMatch,
|
|
1515
|
+
singleProcessJournal,
|
|
1409
1516
|
verifyLicenseSignatures,
|
|
1410
1517
|
verifyMerkleProof
|
|
1411
1518
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -85,16 +85,18 @@ function registryNamespace(code) {
|
|
|
85
85
|
return "";
|
|
86
86
|
}
|
|
87
87
|
var TrustedRegistryDirectory = class _TrustedRegistryDirectory {
|
|
88
|
-
constructor(registries, sequence, snapshotHash, seal) {
|
|
88
|
+
constructor(registries, revokedKeys, sequence, snapshotHash, seal) {
|
|
89
89
|
this.sequence = sequence;
|
|
90
90
|
this.snapshotHash = snapshotHash;
|
|
91
91
|
if (seal !== verifiedConstruction) fail("Construct directories through signature verification");
|
|
92
92
|
this.#registries = registries;
|
|
93
|
+
this.#revokedKeys = new Set(revokedKeys);
|
|
93
94
|
Object.freeze(this);
|
|
94
95
|
}
|
|
95
96
|
sequence;
|
|
96
97
|
snapshotHash;
|
|
97
98
|
#registries;
|
|
99
|
+
#revokedKeys;
|
|
98
100
|
static async verify(input, options) {
|
|
99
101
|
const min = options.minSequence ?? 0;
|
|
100
102
|
if (!Number.isSafeInteger(min) || min < 0) fail("minSequence must be a nonnegative safe integer");
|
|
@@ -134,6 +136,16 @@ var TrustedRegistryDirectory = class _TrustedRegistryDirectory {
|
|
|
134
136
|
}
|
|
135
137
|
}
|
|
136
138
|
if (!prefixes.has("")) fail("Missing origin namespace");
|
|
139
|
+
for (const r of d.registries) {
|
|
140
|
+
if (r.revoked !== void 0 && !(r.revoked && typeof r.revoked === "object" && typeof r.revoked.at === "string")) {
|
|
141
|
+
fail("Invalid registry revocation");
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (d.revoked_keys !== void 0 && !(Array.isArray(d.revoked_keys) && d.revoked_keys.every(
|
|
145
|
+
(k) => k && /^pg-ed25519:[0-9a-f]{32}$/.test(String(k.key_id)) && typeof k.at === "string"
|
|
146
|
+
))) {
|
|
147
|
+
fail("Invalid revoked key list");
|
|
148
|
+
}
|
|
137
149
|
const publicKey = hexBytes(options.stewardPublicKeyHex, 32);
|
|
138
150
|
const hash = await sha256(publicKey);
|
|
139
151
|
const fp = "pg-ed25519:" + hash.slice(0, 32);
|
|
@@ -151,7 +163,13 @@ var TrustedRegistryDirectory = class _TrustedRegistryDirectory {
|
|
|
151
163
|
if (!valid) fail("Invalid directory signature");
|
|
152
164
|
const snapshotHash = await sha256(message);
|
|
153
165
|
if (previous && previous.sequence === d.sequence && previous.hash !== snapshotHash) fail("Directory equivocation detected");
|
|
154
|
-
return new _TrustedRegistryDirectory(
|
|
166
|
+
return new _TrustedRegistryDirectory(
|
|
167
|
+
d.registries,
|
|
168
|
+
(d.revoked_keys ?? []).map((k) => k.key_id),
|
|
169
|
+
d.sequence,
|
|
170
|
+
snapshotHash,
|
|
171
|
+
verifiedConstruction
|
|
172
|
+
);
|
|
155
173
|
}
|
|
156
174
|
registryFor(code) {
|
|
157
175
|
const prefix = registryNamespace(code);
|
|
@@ -167,7 +185,13 @@ var TrustedRegistryDirectory = class _TrustedRegistryDirectory {
|
|
|
167
185
|
}
|
|
168
186
|
/** V-10: use only AFTER independently verifying the object's signature. */
|
|
169
187
|
assertSigner(code, keyId) {
|
|
170
|
-
|
|
188
|
+
const owner = this.registryFor(code);
|
|
189
|
+
if (!owner.key_fingerprints.includes(keyId)) fail("Registry issuer/key mismatch");
|
|
190
|
+
this.assertNotRevoked(owner, keyId);
|
|
191
|
+
}
|
|
192
|
+
/** V-14 */
|
|
193
|
+
assertNotRevoked(owner, keyId) {
|
|
194
|
+
if (owner.revoked || this.#revokedKeys.has(keyId)) fail("Revoked registry or key");
|
|
171
195
|
}
|
|
172
196
|
assertDecision(subject, license, keyId, baseUrl) {
|
|
173
197
|
this.assertEndpoint(subject, baseUrl);
|
|
@@ -175,6 +199,7 @@ var TrustedRegistryDirectory = class _TrustedRegistryDirectory {
|
|
|
175
199
|
else {
|
|
176
200
|
const owner = this.#registries.find((r) => apiUrl(r.api) === apiUrl(baseUrl));
|
|
177
201
|
if (!owner?.key_fingerprints.includes(keyId)) fail("Registry issuer/key mismatch");
|
|
202
|
+
this.assertNotRevoked(owner, keyId);
|
|
178
203
|
}
|
|
179
204
|
if (license) {
|
|
180
205
|
this.assertEndpoint(license, baseUrl);
|
|
@@ -329,6 +354,20 @@ var PramptaReporter = class {
|
|
|
329
354
|
};
|
|
330
355
|
|
|
331
356
|
// src/index.ts
|
|
357
|
+
function singleProcessJournal() {
|
|
358
|
+
const entries = /* @__PURE__ */ new Map();
|
|
359
|
+
return {
|
|
360
|
+
async claim(entry) {
|
|
361
|
+
const found = entries.get(entry.generationId);
|
|
362
|
+
if (found) return structuredClone(found);
|
|
363
|
+
entries.set(entry.generationId, structuredClone(entry));
|
|
364
|
+
return null;
|
|
365
|
+
},
|
|
366
|
+
async put(entry) {
|
|
367
|
+
entries.set(entry.generationId, structuredClone(entry));
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
}
|
|
332
371
|
var PramptaError = class extends Error {
|
|
333
372
|
constructor(message) {
|
|
334
373
|
super(message);
|
|
@@ -394,6 +433,14 @@ var PramptaRefusalError = class extends PramptaError {
|
|
|
394
433
|
return this.decision.licenseId;
|
|
395
434
|
}
|
|
396
435
|
};
|
|
436
|
+
var PramptaJobConflictError = class extends PramptaError {
|
|
437
|
+
constructor(generationId) {
|
|
438
|
+
super(`generationId ${generationId} was already used for a different request`);
|
|
439
|
+
this.generationId = generationId;
|
|
440
|
+
this.name = "PramptaJobConflictError";
|
|
441
|
+
}
|
|
442
|
+
generationId;
|
|
443
|
+
};
|
|
397
444
|
var PramptaFailClosedError = class extends PramptaError {
|
|
398
445
|
constructor(cause_) {
|
|
399
446
|
super(`PRAMPTA fail-closed: generation blocked because verification failed (${cause_})`);
|
|
@@ -529,6 +576,42 @@ function matchSubjects(text, entries) {
|
|
|
529
576
|
}
|
|
530
577
|
return hits;
|
|
531
578
|
}
|
|
579
|
+
var UNDERSTOOD_DECISION_MEMBERS = /* @__PURE__ */ new Set([
|
|
580
|
+
"schema_version",
|
|
581
|
+
"decision_id",
|
|
582
|
+
"nonce",
|
|
583
|
+
"disposition",
|
|
584
|
+
"allowed",
|
|
585
|
+
"reason",
|
|
586
|
+
"policy_version",
|
|
587
|
+
"subject_id",
|
|
588
|
+
"licensee_id",
|
|
589
|
+
"provider_id",
|
|
590
|
+
"license_id",
|
|
591
|
+
"prompt_hash",
|
|
592
|
+
"model",
|
|
593
|
+
"modality",
|
|
594
|
+
"intended_use",
|
|
595
|
+
"obligations",
|
|
596
|
+
"generation_id",
|
|
597
|
+
"issued_at",
|
|
598
|
+
"expires_at",
|
|
599
|
+
"critical",
|
|
600
|
+
"operator_key_id",
|
|
601
|
+
"operator_signature",
|
|
602
|
+
"subject_authority",
|
|
603
|
+
"rules_text",
|
|
604
|
+
"rules_text_hash",
|
|
605
|
+
"watermark_payload",
|
|
606
|
+
"is_hard_refusal",
|
|
607
|
+
"revocation_epoch",
|
|
608
|
+
"max_cache_age_seconds",
|
|
609
|
+
"cache_scope",
|
|
610
|
+
"provider_user_binding",
|
|
611
|
+
"provider_identity_link_id",
|
|
612
|
+
"detection_id",
|
|
613
|
+
"remediation"
|
|
614
|
+
]);
|
|
532
615
|
var Prampta = class _Prampta {
|
|
533
616
|
baseUrl;
|
|
534
617
|
providerId;
|
|
@@ -697,6 +780,11 @@ var Prampta = class _Prampta {
|
|
|
697
780
|
*/
|
|
698
781
|
async verifyDecision(raw, decision) {
|
|
699
782
|
if (typeof raw.allowed !== "boolean") throw new PramptaSchemaError("allowed must be a boolean");
|
|
783
|
+
if (raw.critical !== void 0) {
|
|
784
|
+
if (!Array.isArray(raw.critical)) throw new PramptaSchemaError("critical must be an array");
|
|
785
|
+
const unknown = raw.critical.filter((m) => typeof m !== "string" || !UNDERSTOOD_DECISION_MEMBERS.has(m));
|
|
786
|
+
if (unknown.length) throw new PramptaSchemaError(`Decision lists critical members this SDK does not understand: ${unknown.join(", ")}`);
|
|
787
|
+
}
|
|
700
788
|
const disposition = raw.disposition ?? (raw.allowed ? "allow" : "deny");
|
|
701
789
|
if (!["allow", "deny", "review", "not_blocked"].includes(disposition) || raw.allowed !== (disposition === "allow")) {
|
|
702
790
|
throw new PramptaSchemaError("allowed and disposition are inconsistent or unsupported");
|
|
@@ -747,7 +835,7 @@ var Prampta = class _Prampta {
|
|
|
747
835
|
);
|
|
748
836
|
}
|
|
749
837
|
const now = Math.floor(Date.now() / 1e3);
|
|
750
|
-
if (!Number.isSafeInteger(raw.expires_at) ||
|
|
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 > now + 60) {
|
|
751
839
|
throw new PramptaSchemaError("Decision has invalid validity timestamps");
|
|
752
840
|
}
|
|
753
841
|
if (decision.expiresAt <= now) {
|
|
@@ -982,86 +1070,101 @@ var Prampta = class _Prampta {
|
|
|
982
1070
|
return { output, decision, metadata };
|
|
983
1071
|
}
|
|
984
1072
|
/**
|
|
985
|
-
* The whole cycle in one call, for every subject in the output:
|
|
986
|
-
* ask
|
|
987
|
-
*
|
|
988
|
-
* subject. If any subject refuses, or `generate` throws, the allows already
|
|
989
|
-
* held are released so they do not count against usage limits.
|
|
1073
|
+
* The whole cycle in one call, for every subject in the output (PRE-GEN P-9):
|
|
1074
|
+
* ask fresh for each subject, require a licence-backed allow from all of
|
|
1075
|
+
* them, generate, file one receipt per subject.
|
|
990
1076
|
*
|
|
991
|
-
* `
|
|
992
|
-
*
|
|
993
|
-
*
|
|
994
|
-
*
|
|
995
|
-
*
|
|
996
|
-
*
|
|
1077
|
+
* One `generationId` is bound to one unchanging request. The journal's
|
|
1078
|
+
* atomic `claim` lets exactly one worker run a job, across every server
|
|
1079
|
+
* that shares the journal; a different request under a used id is an
|
|
1080
|
+
* error. Every allow is re-checked immediately before `generate` runs. The
|
|
1081
|
+
* output hash is stored before receipts are sent, and a receipt counts as
|
|
1082
|
+
* filed only when the registry accepted exactly it. A job that may have
|
|
1083
|
+
* started generating is never run again: check your own records,
|
|
1084
|
+
* releaseDecision() if nothing was produced, and use a new generationId.
|
|
1085
|
+
* If a subject refuses or `generate` throws, the allows are given back.
|
|
1086
|
+
* `generate` must throw only when nothing was produced.
|
|
997
1087
|
*/
|
|
998
|
-
async generateAuthorized(request, generate, options
|
|
1088
|
+
async generateAuthorized(request, generate, options) {
|
|
999
1089
|
const id = request.generationId;
|
|
1000
1090
|
if (!id) throw new PramptaSchemaError("generateAuthorized requires a generationId");
|
|
1091
|
+
if (!options?.journal) throw new PramptaSchemaError("generateAuthorized requires a journal (singleProcessJournal() for one process)");
|
|
1092
|
+
const requestHash = await this.requestHash(request);
|
|
1001
1093
|
const running = this.inFlight.get(id);
|
|
1002
|
-
if (running)
|
|
1003
|
-
|
|
1004
|
-
|
|
1094
|
+
if (running) {
|
|
1095
|
+
if (running.requestHash !== requestHash) throw new PramptaJobConflictError(id);
|
|
1096
|
+
return running.run;
|
|
1097
|
+
}
|
|
1098
|
+
const run = this.runAuthorized(request, requestHash, generate, options).finally(() => this.inFlight.delete(id));
|
|
1099
|
+
this.inFlight.set(id, { requestHash, run });
|
|
1005
1100
|
return run;
|
|
1006
1101
|
}
|
|
1007
|
-
|
|
1102
|
+
/** SHA-256 of everything that defines the job. Same id + different hash = a different job. */
|
|
1103
|
+
async requestHash(request) {
|
|
1104
|
+
const { generationId, subjectIds, prompt, promptHash, idempotencyKey, returnUrl, detectionId, ...rest } = request;
|
|
1105
|
+
return sha2562(canonicalJson(JSON.parse(JSON.stringify({
|
|
1106
|
+
...rest,
|
|
1107
|
+
generationId,
|
|
1108
|
+
subjectIds: [...new Set(subjectIds)].sort(),
|
|
1109
|
+
promptHash: promptHash ?? (prompt !== void 0 ? await hashPrompt(prompt) : ""),
|
|
1110
|
+
providerId: this.providerId,
|
|
1111
|
+
licenseeId: this.licenseeId
|
|
1112
|
+
}))));
|
|
1113
|
+
}
|
|
1114
|
+
async runAuthorized(request, requestHash, generate, { journal, receipt }) {
|
|
1008
1115
|
const { subjectIds, generationId, ...rest } = request;
|
|
1009
1116
|
const subjects = [...new Set(subjectIds)];
|
|
1010
1117
|
if (subjects.length === 0) throw new PramptaSchemaError("generateAuthorized requires at least one subjectId");
|
|
1011
|
-
const
|
|
1012
|
-
if (
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
const unreported2 = await this.fileReceipts(saved.decisions, saved.outputHash ?? "", receipt);
|
|
1020
|
-
if (unreported2.length === 0) await journal.put({ ...saved, stage: "reported" });
|
|
1021
|
-
return { status: "recovered", decisions: saved.decisions, outputHash: saved.outputHash ?? "", unreported: unreported2 };
|
|
1118
|
+
const existing = await journal.claim({ generationId, requestHash, stage: "claimed", decisions: [] });
|
|
1119
|
+
if (existing) {
|
|
1120
|
+
if (existing.requestHash !== requestHash) throw new PramptaJobConflictError(generationId);
|
|
1121
|
+
if (existing.stage === "reported") {
|
|
1122
|
+
return { status: "already_reported", decisions: existing.decisions, outputHash: existing.outputHash ?? "", unreported: [], conflicts: [] };
|
|
1123
|
+
}
|
|
1124
|
+
if (existing.stage === "generated") return this.report(journal, existing, "recovered", void 0, receipt);
|
|
1125
|
+
throw new PramptaError(existing.stage === "released" ? `Generation ${generationId} produced nothing and its allows were given back; use a new generationId` : `Generation ${generationId} is running elsewhere or was interrupted after it was claimed; it is not run again. If nothing was produced, releaseDecision() each decision and use a new generationId.`);
|
|
1022
1126
|
}
|
|
1023
1127
|
const settled = await Promise.allSettled(subjects.map((s) => this.assertLicensed(s, { ...rest, generationId })));
|
|
1024
1128
|
const decisions = settled.flatMap((r) => r.status === "fulfilled" ? [r.value] : []);
|
|
1025
1129
|
const failed = settled.find((r) => r.status === "rejected");
|
|
1026
1130
|
if (failed) {
|
|
1027
|
-
await this.
|
|
1131
|
+
await this.giveBack(journal, { generationId, requestHash, stage: "claimed", decisions }, "another subject in the same output was refused");
|
|
1028
1132
|
throw failed.reason;
|
|
1029
1133
|
}
|
|
1134
|
+
const entry = { generationId, requestHash, stage: "authorized", decisions };
|
|
1135
|
+
await journal.put(entry);
|
|
1030
1136
|
if (decisions.some((d) => d.expiresAt <= Math.floor(Date.now() / 1e3))) {
|
|
1031
|
-
await this.
|
|
1032
|
-
throw new PramptaError("An authorization expired before generation started; ask again");
|
|
1137
|
+
await this.giveBack(journal, entry, "expired before generation started");
|
|
1138
|
+
throw new PramptaError("An authorization expired before generation started; ask again with a new generationId");
|
|
1033
1139
|
}
|
|
1034
|
-
await journal?.put({ generationId, stage: "authorized", decisions });
|
|
1035
1140
|
let result;
|
|
1036
1141
|
try {
|
|
1037
1142
|
result = await generate(decisions);
|
|
1038
1143
|
} catch (e) {
|
|
1039
|
-
|
|
1040
|
-
await journal?.put({ generationId, stage: "released", decisions });
|
|
1041
|
-
}
|
|
1144
|
+
await this.giveBack(journal, entry, "generation failed");
|
|
1042
1145
|
throw e;
|
|
1043
1146
|
}
|
|
1044
1147
|
const bytes = typeof result.outputBytes === "string" ? new TextEncoder().encode(result.outputBytes) : result.outputBytes;
|
|
1045
1148
|
const outputHash = result.outputHash ?? (bytes ? await sha256Bytes(bytes) : "");
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
const r = results[i];
|
|
1057
|
-
if (r.status === "fulfilled") return false;
|
|
1058
|
-
return !(r.reason instanceof PramptaApiError && r.reason.status === 409 && /already exists/.test(r.reason.detail));
|
|
1149
|
+
const generated = { ...entry, stage: "generated", outputHash, generatedAt: Math.floor(Date.now() / 1e3) };
|
|
1150
|
+
await journal.put(generated);
|
|
1151
|
+
return this.report(journal, generated, "generated", result.output, receipt);
|
|
1152
|
+
}
|
|
1153
|
+
async report(journal, entry, status, output, receipt) {
|
|
1154
|
+
const results = await Promise.allSettled(entry.decisions.map((d) => this.submitReceipt(d, { ...receipt, outputHash: entry.outputHash ?? "", generatedAt: entry.generatedAt })));
|
|
1155
|
+
const unreported = [], conflicts = [];
|
|
1156
|
+
results.forEach((r, i) => {
|
|
1157
|
+
if (r.status === "fulfilled") return;
|
|
1158
|
+
(r.reason instanceof PramptaApiError && r.reason.status === 409 ? conflicts : unreported).push(entry.decisions[i]);
|
|
1059
1159
|
});
|
|
1160
|
+
if (!unreported.length && !conflicts.length) await journal.put({ ...entry, stage: "reported" });
|
|
1161
|
+
return { status, output, decisions: entry.decisions, outputHash: entry.outputHash ?? "", unreported, conflicts };
|
|
1060
1162
|
}
|
|
1061
|
-
/**
|
|
1062
|
-
async
|
|
1063
|
-
const results = await Promise.allSettled(decisions.map((d) => this.releaseDecision(d.decisionId, reason)));
|
|
1064
|
-
|
|
1163
|
+
/** Release every allow; the job is marked released only when all of them were given back. */
|
|
1164
|
+
async giveBack(journal, entry, reason) {
|
|
1165
|
+
const results = await Promise.allSettled(entry.decisions.map((d) => this.releaseDecision(d.decisionId, reason)));
|
|
1166
|
+
const all = results.every((r) => r.status === "fulfilled" || r.reason instanceof PramptaApiError && r.reason.status === 409 && /nothing_to_release/.test(r.reason.detail));
|
|
1167
|
+
if (all) await journal.put({ ...entry, stage: "released" });
|
|
1065
1168
|
}
|
|
1066
1169
|
/** Give back an allow that produced nothing (failed or cancelled job), so
|
|
1067
1170
|
* it does not count against the licence's usage limit. Only before a
|
|
@@ -1335,6 +1438,7 @@ export {
|
|
|
1335
1438
|
PramptaApiError,
|
|
1336
1439
|
PramptaError,
|
|
1337
1440
|
PramptaFailClosedError,
|
|
1441
|
+
PramptaJobConflictError,
|
|
1338
1442
|
PramptaNetworkError,
|
|
1339
1443
|
PramptaRefusalError,
|
|
1340
1444
|
PramptaReporter,
|
|
@@ -1350,6 +1454,7 @@ export {
|
|
|
1350
1454
|
loadDirectory as loadPregenDirectory,
|
|
1351
1455
|
matchSubjects,
|
|
1352
1456
|
normalizeForMatch,
|
|
1457
|
+
singleProcessJournal,
|
|
1353
1458
|
verifyLicenseSignatures,
|
|
1354
1459
|
verifyMerkleProof
|
|
1355
1460
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prampta/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "PRAMPTA SDK — authorization before generation",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@noble/ed25519": "^3.1.0",
|
|
50
|
-
"@pregen/verify": "^0.
|
|
50
|
+
"@pregen/verify": "^0.6.0"
|
|
51
51
|
},
|
|
52
52
|
"repository": {
|
|
53
53
|
"type": "git",
|