@prampta/sdk 0.9.0 → 0.10.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 CHANGED
@@ -13,7 +13,7 @@ support is not claimed: CommonJS loads the ESM-only crypto dependency through
13
13
  [`require(esm)`](https://nodejs.org/en/blog/release/v20.19.0). Local ESM/CommonJS
14
14
  smoke tests run on Node 22.20; a wider runtime/browser matrix is still a release gate.
15
15
 
16
- This README describes SDK 0.7.0.
16
+ This README describes SDK 0.10.0; sections name the version that added them.
17
17
  In older 0.4.0, `assertAllowed` throws on
18
18
  personal-use `not_blocked` and `PramptaReporter` is unavailable. Use `verify()`
19
19
  and inspect `disposition` when supporting older installations;
@@ -36,6 +36,86 @@ 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
+ ## PRE-GEN v5: one setup, one call (0.10.0, `/v2`)
40
+
41
+ `createPregen` speaks the simplified PRE-GEN v5 formats at `/v2`: signed
42
+ answers bound to your request, signed reports, signed acknowledgements. Set it
43
+ up once; wrap each generation in one call.
44
+
45
+ ```typescript
46
+ import pg from "pg";
47
+ import { createPregen, postgresStore } from "@prampta/sdk";
48
+
49
+ const pregen = createPregen({
50
+ baseUrl: "https://api2.prampta.com",
51
+ providerId: "my-ai",
52
+ token: process.env.PRAMPTA_TOKEN!, // your exchange_secret, server-side only
53
+ providerSigningKeyHex: process.env.PREGEN_SIGNING_KEY!, // your Ed25519 private key (32 bytes hex): signs your reports
54
+ operatorPublicKeyHex: process.env.PRAMPTA_OPERATOR_PUBLIC_KEY!, // obtained independently, not from the API
55
+ store: postgresStore(new pg.Pool()), // shared by all your workers; creates table pregen_jobs
56
+ });
57
+
58
+ await pregen.registerKey(); // once: registers your public key with the registry
59
+ setInterval(() => pregen.flush(), 60_000); // and at startup: resends reports still waiting
60
+
61
+ const result = await pregen.generate(
62
+ { jobId: task.id, // YOUR stable task id: a retry reuses it, a new generation gets a new one
63
+ subjects: ["PGXX-..."], // everyone in the output
64
+ prompt, model: "my-model", modality: "image", use: { purpose: "commercial" },
65
+ licenseeId }, // the connected user, or omit for a provider-level request
66
+ async ({ obligations }) => {
67
+ const image = await render(prompt, obligations); // YOU apply the obligations (watermark, disclosure...)
68
+ return { output: image, outputBytes: image.bytes, obligationsApplied: obligations.map((o) => o.id) };
69
+ },
70
+ );
71
+ ```
72
+
73
+ **What one call does.** Asks the registry about every subject under one job
74
+ hash; verifies every answer (your pinned key, bound to your request, times);
75
+ in `enforce` mode generates only when **all** subjects allow, re-checking
76
+ right before your function runs; stores the outcome and the signed reports
77
+ **before** sending them; counts a report as filed only when the registry's
78
+ signed acknowledgement of exactly it verifies. Refused, or your function
79
+ threw: nothing is generated and every allow is given back.
80
+
81
+ **The result tells you where the job stands:**
82
+
83
+ | `status` | Meaning | What you do |
84
+ |---|---|---|
85
+ | `confirmed` | Finished; every report acknowledged. `generated` says whether it generated. | Nothing. |
86
+ | `report_pending` | Finished; a report has not been acknowledged yet (network, outage). | Nothing: `flush()` resends it, byte for byte. |
87
+ | `needs_check` | The outcome is unknown: the job was interrupted after it may have started generating, or is running in another worker; or the registry refused a report for good. | Check your generator for this `jobId`, then `pregen.resolve(jobId, { generated: true, outputBytes })` or `{ generated: false }`. |
88
+
89
+ The same `jobId` never generates twice: calling again returns where the job
90
+ stands. The same `jobId` with a different generation is refused
91
+ (`PG_JOB_CONFLICT`). The SDK never guesses whether an interrupted generation
92
+ finished; only your generator knows, so it asks you.
93
+
94
+ **Obligations** are handed to your function and listed in the report as you
95
+ declare them in `obligationsApplied`; any you did not apply come back in
96
+ `unmetObligations`. How to apply a watermark or a disclosure depends on your
97
+ medium (image, video, audio) and is yours to implement.
98
+
99
+ **Which subjects.** You name them. Matching names in a prompt
100
+ (`matchSubjects`) only suggests candidates; faces, voices and uploaded
101
+ references are for your own pipeline to identify.
102
+
103
+ **`observe` mode** (`mode: "observe"`): generates whatever the answers say and
104
+ reports every generation. Nothing is blocked and nothing is granted: use it
105
+ to trial the integration, then switch to `enforce`; nothing else changes.
106
+
107
+ **Your signing key.** Reports must be signed. `registerKey()` sends a `first`
108
+ keyset signed by the key it names. After that, the registry also requires
109
+ your `/v1` receipts to be signed with the same key (`providerSigningKeyHex`
110
+ in `submitReceipt`). Rotation is not available yet.
111
+
112
+ **Stores.** `postgresStore(pool)` takes any node-postgres `Pool` or `Client`
113
+ (the `pg` package is yours; the SDK does not depend on it). `memoryStore()`
114
+ is for tests and a single process: it is lost on restart.
115
+
116
+ Not in this release: Python, other stores, several registries in one job,
117
+ key rotation.
118
+
39
119
  ## Quick Start: the safe setup (0.9.0)
40
120
 
41
121
  One configuration turns on every check: the signed PRE-GEN directory, your
@@ -147,8 +227,11 @@ const decision = await pg.assertLicensed(subjectCode, {
147
227
  - asks fresh for **every subject in the output** and generates only if
148
228
  **all** of them return a licence-backed `allow`;
149
229
  - 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;
230
+ - takes its own copy of the request before doing anything, so changing your
231
+ object afterwards cannot change the job;
232
+ - stores the output hash and the **exact receipts** (signed, if you sign)
233
+ before sending them, and after a failure resends those receipts byte for
234
+ byte — receipt options passed on a later call do not replace them;
152
235
  - a receipt counts as filed only when the registry accepted exactly it:
153
236
  `unreported` lists receipts to resend (call again with the same id and
154
237
  request), `conflicts` lists decisions for which the registry holds a
package/dist/index.d.mts CHANGED
@@ -122,6 +122,159 @@ declare class PramptaReporter {
122
122
  deliver(event: Observation): Promise<DeliveryResult>;
123
123
  }
124
124
 
125
+ type Envelope = {
126
+ type: string;
127
+ body: Record<string, any>;
128
+ signatures: string[];
129
+ };
130
+ type Purpose = "personal" | "educational" | "research" | "editorial" | "commercial";
131
+ interface Obligation {
132
+ subject: string;
133
+ id: string;
134
+ params: Record<string, unknown>;
135
+ }
136
+ interface PregenConfig {
137
+ baseUrl: string;
138
+ providerId: string;
139
+ /** Your runtime credential (`exchange_secret`). */
140
+ token: string;
141
+ /** Your Ed25519 private key, 32 bytes hex. Signs reports; register its public key once with `registerKey()`. */
142
+ providerSigningKeyHex: string;
143
+ /** The registry's public key(s), hex, obtained from its documentation, not from its API. */
144
+ operatorPublicKeyHex: string | string[];
145
+ store: PregenStore;
146
+ /** `enforce` (default): generate only when every subject allows. `observe`: generate anyway and report; nothing is blocked or granted. */
147
+ mode?: "enforce" | "observe";
148
+ timeoutMs?: number;
149
+ }
150
+ interface PregenJobInput {
151
+ /** Your own stable id for this generation, e.g. your task id. Calling again with the same id
152
+ * never generates twice; a new generation needs a new id. */
153
+ jobId: string;
154
+ /** PRE-GEN codes of everyone in the output. Name matching can suggest them; you decide. */
155
+ subjects: string[];
156
+ /** Hashed locally; never sent. */
157
+ prompt?: string;
158
+ promptHash?: string;
159
+ model: string;
160
+ modality: string;
161
+ use: {
162
+ purpose: Purpose;
163
+ rights?: string[];
164
+ territory?: string;
165
+ channel?: string;
166
+ };
167
+ /** The connected end user's licensee id; empty for a provider-level request. */
168
+ licenseeId?: string;
169
+ }
170
+ type GenerateFn<T> = (ctx: {
171
+ obligations: Obligation[];
172
+ }) => Promise<{
173
+ output: T;
174
+ /** One of these is required: the SHA-256 of the output, or the bytes to hash. */
175
+ outputHash?: string;
176
+ outputBytes?: Uint8Array | string;
177
+ /** Ids of the obligations you applied (watermark, disclosure...). */
178
+ obligationsApplied?: string[];
179
+ }>;
180
+ interface AskResult {
181
+ subject: string;
182
+ status: "allow" | "deny" | "review" | "not_blocked" | null;
183
+ reason?: string;
184
+ /** Why the answer is missing or unusable: PG_UNREACHABLE, PG_SIGNATURE, PG_TIME, PG_BINDING... */
185
+ error?: string;
186
+ }
187
+ interface PregenResult<T = unknown> {
188
+ jobId: string;
189
+ /** confirmed: finished, every report acknowledged. report_pending: finished, a report waits
190
+ * for acknowledgement; `flush()` resends it. needs_check: the outcome is unknown (interrupted,
191
+ * or running in another worker) or the registry holds a different report; check, then `resolve()`. */
192
+ status: "confirmed" | "report_pending" | "needs_check";
193
+ /** null while the outcome is unknown. */
194
+ generated: boolean | null;
195
+ /** Only from the call that generated. */
196
+ output?: T;
197
+ outputHash?: string;
198
+ answers: AskResult[];
199
+ /** Obligations the answers set that `obligationsApplied` did not list. */
200
+ unmetObligations: Obligation[];
201
+ detail?: string;
202
+ }
203
+ /** One job as the store keeps it. */
204
+ interface PregenJob {
205
+ providerId: string;
206
+ jobId: string;
207
+ /** SHA-256 of the manifest; the same job id with another manifest is refused. */
208
+ jobHash: string;
209
+ /** claimed: one worker owns it. asked: answers stored, generation may have started.
210
+ * finished: outcome and signed reports stored, some not acknowledged. done: nothing left to send. */
211
+ stage: "claimed" | "asked" | "finished" | "done";
212
+ asks: {
213
+ request: Record<string, any>;
214
+ answer: Envelope | null;
215
+ error?: string;
216
+ }[];
217
+ generated?: boolean;
218
+ outputHash?: string;
219
+ unmetObligations?: Obligation[];
220
+ /** `rejected`: the registry refused this report for good (it holds a different one, or found it malformed). */
221
+ reports: {
222
+ report: Envelope;
223
+ ack?: Envelope;
224
+ rejected?: boolean;
225
+ error?: string;
226
+ }[];
227
+ }
228
+ interface PregenStore {
229
+ /** ATOMIC across every worker: store `job` if none exists for (providerId, jobId) and return
230
+ * null; otherwise change nothing and return the existing job. */
231
+ claim(job: PregenJob): Promise<PregenJob | null>;
232
+ /** Replace the job. Resolve only after a durable write. */
233
+ put(job: PregenJob): Promise<void>;
234
+ get(providerId: string, jobId: string): Promise<PregenJob | null>;
235
+ /** Jobs in stage `finished`, oldest first. */
236
+ pending(providerId: string): Promise<PregenJob[]>;
237
+ }
238
+ declare class PregenError extends Error {
239
+ readonly code: string;
240
+ constructor(code: string, message: string);
241
+ }
242
+ declare function signingInput(type: string, body: Record<string, unknown>): Uint8Array;
243
+ /** The id of a message: covers type and body, not signatures. */
244
+ declare function messageId(type: string, body: Record<string, unknown>): Promise<string>;
245
+ declare function createPregen(config: PregenConfig): {
246
+ /** Ask, verify, generate (only when permitted), report. See the module comment. */
247
+ generate<T>(input: PregenJobInput, fn: GenerateFn<T>): Promise<PregenResult<T>>;
248
+ /** Resend every report still waiting (after a restart, an outage). Run at startup and every minute or so. */
249
+ flush(): Promise<PregenResult[]>;
250
+ /** Where a job stands, or null if this store never saw it. */
251
+ status(jobId: string): Promise<PregenResult | null>;
252
+ /** After `needs_check`: record what your generator actually did with this job. */
253
+ resolve(jobId: string, outcome: {
254
+ generated: false;
255
+ } | {
256
+ generated: true;
257
+ outputHash?: string;
258
+ outputBytes?: Uint8Array | string;
259
+ obligationsApplied?: string[];
260
+ }): Promise<PregenResult>;
261
+ /** Register this provider's signing key with the registry, once (a `first` keyset, §4.9). */
262
+ registerKey(): Promise<{
263
+ keyId: string;
264
+ }>;
265
+ };
266
+ type Pregen = ReturnType<typeof createPregen>;
267
+ /** In memory. Correct for ONE process only, and lost on restart: tests and trials. */
268
+ declare function memoryStore(): PregenStore;
269
+ /** Anything with node-postgres' `query(text, values)`: a `pg` Pool or Client. */
270
+ interface PgQueryable {
271
+ query(text: string, values?: unknown[]): Promise<{
272
+ rows: any[];
273
+ }>;
274
+ }
275
+ /** Postgres, shared by every worker. Creates its table `pregen_jobs` on first use. */
276
+ declare function postgresStore(pool: PgQueryable): PregenStore;
277
+
125
278
  /**
126
279
  * PRAMPTA SDK for TypeScript / Node.js
127
280
  *
@@ -398,8 +551,10 @@ interface GenerationJournalEntry {
398
551
  stage: "claimed" | "authorized" | "generated" | "reported" | "released";
399
552
  decisions: SignedDecision[];
400
553
  outputHash?: string;
401
- /** Kept so a resent receipt is byte-identical to the first. */
402
554
  generatedAt?: number;
555
+ /** The exact receipt bodies (signed, if a key was given), stored before the
556
+ * first send and resent unchanged. No private key is stored. */
557
+ receipts?: Record<string, Record<string, unknown>>;
403
558
  }
404
559
  interface GenerationJournal {
405
560
  /** ATOMIC across every worker: store `entry` if no entry exists for its
@@ -666,6 +821,8 @@ declare class Prampta {
666
821
  /** 32-byte Ed25519 private key, hex; keep server-side. Never sent to PRAMPTA. */
667
822
  providerSigningKeyHex?: string;
668
823
  }): Promise<Record<string, unknown>>;
824
+ /** The exact receipt body submitReceipt() would send, signed if a key is given. */
825
+ buildReceipt(decision: SignedDecision, result?: NonNullable<Parameters<Prampta["submitReceipt"]>[1]>): Promise<Record<string, unknown>>;
669
826
  /**
670
827
  * Get (issuing if needed) the signed `pg.assertion.v1` for a decision this
671
828
  * provider/licensee pair already submitted a receipt for — embed it in
@@ -701,4 +858,4 @@ declare class Prampta {
701
858
  private getOnce;
702
859
  }
703
860
 
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 };
861
+ export { type AskResult, type AuthorizedGeneration, type AuthorizedRequest, type DeliveryResult, type Envelope, type GenerateFn, type GenerationJournal, type GenerationJournalEntry, HARD_REFUSAL_CODES, type LicenseRequestInput, type LicenseRequestResult, type LicenseSignatureInput, type Obligation, type Observation, type ObservationOutbox, type OutputMetadata, type PgQueryable, Prampta, PramptaApiError, type PramptaConfig, PramptaError, PramptaFailClosedError, PramptaJobConflictError, PramptaNetworkError, PramptaRefusalError, PramptaReporter, PramptaSchemaError, PramptaSignatureError, PramptaTimeoutError, type Pregen, type PregenConfig, PregenError, type PregenJob, type PregenJobInput, type PregenResult, type PregenStore, type Purpose, REFUSAL_DESCRIPTIONS, type ReceiptOptions, RegistryDirectoryError, type RegistryDirectoryOptions, type ReportInput, SOFT_REFUSAL_CODES, type SignedDecision, type SubjectIndexEntry, type SubjectInfo, TrustedRegistryDirectory, type VerifyOptions, type VerifyRequest, canonicalJson, createPregen, hashPrompt, matchSubjects, memoryStore, messageId, normalizeForMatch, postgresStore, signingInput, singleProcessJournal, verifyLicenseSignatures, verifyMerkleProof };
package/dist/index.d.ts CHANGED
@@ -122,6 +122,159 @@ declare class PramptaReporter {
122
122
  deliver(event: Observation): Promise<DeliveryResult>;
123
123
  }
124
124
 
125
+ type Envelope = {
126
+ type: string;
127
+ body: Record<string, any>;
128
+ signatures: string[];
129
+ };
130
+ type Purpose = "personal" | "educational" | "research" | "editorial" | "commercial";
131
+ interface Obligation {
132
+ subject: string;
133
+ id: string;
134
+ params: Record<string, unknown>;
135
+ }
136
+ interface PregenConfig {
137
+ baseUrl: string;
138
+ providerId: string;
139
+ /** Your runtime credential (`exchange_secret`). */
140
+ token: string;
141
+ /** Your Ed25519 private key, 32 bytes hex. Signs reports; register its public key once with `registerKey()`. */
142
+ providerSigningKeyHex: string;
143
+ /** The registry's public key(s), hex, obtained from its documentation, not from its API. */
144
+ operatorPublicKeyHex: string | string[];
145
+ store: PregenStore;
146
+ /** `enforce` (default): generate only when every subject allows. `observe`: generate anyway and report; nothing is blocked or granted. */
147
+ mode?: "enforce" | "observe";
148
+ timeoutMs?: number;
149
+ }
150
+ interface PregenJobInput {
151
+ /** Your own stable id for this generation, e.g. your task id. Calling again with the same id
152
+ * never generates twice; a new generation needs a new id. */
153
+ jobId: string;
154
+ /** PRE-GEN codes of everyone in the output. Name matching can suggest them; you decide. */
155
+ subjects: string[];
156
+ /** Hashed locally; never sent. */
157
+ prompt?: string;
158
+ promptHash?: string;
159
+ model: string;
160
+ modality: string;
161
+ use: {
162
+ purpose: Purpose;
163
+ rights?: string[];
164
+ territory?: string;
165
+ channel?: string;
166
+ };
167
+ /** The connected end user's licensee id; empty for a provider-level request. */
168
+ licenseeId?: string;
169
+ }
170
+ type GenerateFn<T> = (ctx: {
171
+ obligations: Obligation[];
172
+ }) => Promise<{
173
+ output: T;
174
+ /** One of these is required: the SHA-256 of the output, or the bytes to hash. */
175
+ outputHash?: string;
176
+ outputBytes?: Uint8Array | string;
177
+ /** Ids of the obligations you applied (watermark, disclosure...). */
178
+ obligationsApplied?: string[];
179
+ }>;
180
+ interface AskResult {
181
+ subject: string;
182
+ status: "allow" | "deny" | "review" | "not_blocked" | null;
183
+ reason?: string;
184
+ /** Why the answer is missing or unusable: PG_UNREACHABLE, PG_SIGNATURE, PG_TIME, PG_BINDING... */
185
+ error?: string;
186
+ }
187
+ interface PregenResult<T = unknown> {
188
+ jobId: string;
189
+ /** confirmed: finished, every report acknowledged. report_pending: finished, a report waits
190
+ * for acknowledgement; `flush()` resends it. needs_check: the outcome is unknown (interrupted,
191
+ * or running in another worker) or the registry holds a different report; check, then `resolve()`. */
192
+ status: "confirmed" | "report_pending" | "needs_check";
193
+ /** null while the outcome is unknown. */
194
+ generated: boolean | null;
195
+ /** Only from the call that generated. */
196
+ output?: T;
197
+ outputHash?: string;
198
+ answers: AskResult[];
199
+ /** Obligations the answers set that `obligationsApplied` did not list. */
200
+ unmetObligations: Obligation[];
201
+ detail?: string;
202
+ }
203
+ /** One job as the store keeps it. */
204
+ interface PregenJob {
205
+ providerId: string;
206
+ jobId: string;
207
+ /** SHA-256 of the manifest; the same job id with another manifest is refused. */
208
+ jobHash: string;
209
+ /** claimed: one worker owns it. asked: answers stored, generation may have started.
210
+ * finished: outcome and signed reports stored, some not acknowledged. done: nothing left to send. */
211
+ stage: "claimed" | "asked" | "finished" | "done";
212
+ asks: {
213
+ request: Record<string, any>;
214
+ answer: Envelope | null;
215
+ error?: string;
216
+ }[];
217
+ generated?: boolean;
218
+ outputHash?: string;
219
+ unmetObligations?: Obligation[];
220
+ /** `rejected`: the registry refused this report for good (it holds a different one, or found it malformed). */
221
+ reports: {
222
+ report: Envelope;
223
+ ack?: Envelope;
224
+ rejected?: boolean;
225
+ error?: string;
226
+ }[];
227
+ }
228
+ interface PregenStore {
229
+ /** ATOMIC across every worker: store `job` if none exists for (providerId, jobId) and return
230
+ * null; otherwise change nothing and return the existing job. */
231
+ claim(job: PregenJob): Promise<PregenJob | null>;
232
+ /** Replace the job. Resolve only after a durable write. */
233
+ put(job: PregenJob): Promise<void>;
234
+ get(providerId: string, jobId: string): Promise<PregenJob | null>;
235
+ /** Jobs in stage `finished`, oldest first. */
236
+ pending(providerId: string): Promise<PregenJob[]>;
237
+ }
238
+ declare class PregenError extends Error {
239
+ readonly code: string;
240
+ constructor(code: string, message: string);
241
+ }
242
+ declare function signingInput(type: string, body: Record<string, unknown>): Uint8Array;
243
+ /** The id of a message: covers type and body, not signatures. */
244
+ declare function messageId(type: string, body: Record<string, unknown>): Promise<string>;
245
+ declare function createPregen(config: PregenConfig): {
246
+ /** Ask, verify, generate (only when permitted), report. See the module comment. */
247
+ generate<T>(input: PregenJobInput, fn: GenerateFn<T>): Promise<PregenResult<T>>;
248
+ /** Resend every report still waiting (after a restart, an outage). Run at startup and every minute or so. */
249
+ flush(): Promise<PregenResult[]>;
250
+ /** Where a job stands, or null if this store never saw it. */
251
+ status(jobId: string): Promise<PregenResult | null>;
252
+ /** After `needs_check`: record what your generator actually did with this job. */
253
+ resolve(jobId: string, outcome: {
254
+ generated: false;
255
+ } | {
256
+ generated: true;
257
+ outputHash?: string;
258
+ outputBytes?: Uint8Array | string;
259
+ obligationsApplied?: string[];
260
+ }): Promise<PregenResult>;
261
+ /** Register this provider's signing key with the registry, once (a `first` keyset, §4.9). */
262
+ registerKey(): Promise<{
263
+ keyId: string;
264
+ }>;
265
+ };
266
+ type Pregen = ReturnType<typeof createPregen>;
267
+ /** In memory. Correct for ONE process only, and lost on restart: tests and trials. */
268
+ declare function memoryStore(): PregenStore;
269
+ /** Anything with node-postgres' `query(text, values)`: a `pg` Pool or Client. */
270
+ interface PgQueryable {
271
+ query(text: string, values?: unknown[]): Promise<{
272
+ rows: any[];
273
+ }>;
274
+ }
275
+ /** Postgres, shared by every worker. Creates its table `pregen_jobs` on first use. */
276
+ declare function postgresStore(pool: PgQueryable): PregenStore;
277
+
125
278
  /**
126
279
  * PRAMPTA SDK for TypeScript / Node.js
127
280
  *
@@ -398,8 +551,10 @@ interface GenerationJournalEntry {
398
551
  stage: "claimed" | "authorized" | "generated" | "reported" | "released";
399
552
  decisions: SignedDecision[];
400
553
  outputHash?: string;
401
- /** Kept so a resent receipt is byte-identical to the first. */
402
554
  generatedAt?: number;
555
+ /** The exact receipt bodies (signed, if a key was given), stored before the
556
+ * first send and resent unchanged. No private key is stored. */
557
+ receipts?: Record<string, Record<string, unknown>>;
403
558
  }
404
559
  interface GenerationJournal {
405
560
  /** ATOMIC across every worker: store `entry` if no entry exists for its
@@ -666,6 +821,8 @@ declare class Prampta {
666
821
  /** 32-byte Ed25519 private key, hex; keep server-side. Never sent to PRAMPTA. */
667
822
  providerSigningKeyHex?: string;
668
823
  }): Promise<Record<string, unknown>>;
824
+ /** The exact receipt body submitReceipt() would send, signed if a key is given. */
825
+ buildReceipt(decision: SignedDecision, result?: NonNullable<Parameters<Prampta["submitReceipt"]>[1]>): Promise<Record<string, unknown>>;
669
826
  /**
670
827
  * Get (issuing if needed) the signed `pg.assertion.v1` for a decision this
671
828
  * provider/licensee pair already submitted a receipt for — embed it in
@@ -701,4 +858,4 @@ declare class Prampta {
701
858
  private getOnce;
702
859
  }
703
860
 
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 };
861
+ export { type AskResult, type AuthorizedGeneration, type AuthorizedRequest, type DeliveryResult, type Envelope, type GenerateFn, type GenerationJournal, type GenerationJournalEntry, HARD_REFUSAL_CODES, type LicenseRequestInput, type LicenseRequestResult, type LicenseSignatureInput, type Obligation, type Observation, type ObservationOutbox, type OutputMetadata, type PgQueryable, Prampta, PramptaApiError, type PramptaConfig, PramptaError, PramptaFailClosedError, PramptaJobConflictError, PramptaNetworkError, PramptaRefusalError, PramptaReporter, PramptaSchemaError, PramptaSignatureError, PramptaTimeoutError, type Pregen, type PregenConfig, PregenError, type PregenJob, type PregenJobInput, type PregenResult, type PregenStore, type Purpose, REFUSAL_DESCRIPTIONS, type ReceiptOptions, RegistryDirectoryError, type RegistryDirectoryOptions, type ReportInput, SOFT_REFUSAL_CODES, type SignedDecision, type SubjectIndexEntry, type SubjectInfo, TrustedRegistryDirectory, type VerifyOptions, type VerifyRequest, canonicalJson, createPregen, hashPrompt, matchSubjects, memoryStore, messageId, normalizeForMatch, postgresStore, signingInput, singleProcessJournal, verifyLicenseSignatures, verifyMerkleProof };