@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 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.11.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,119 @@ 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.11.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. Use 0.11.0 or later: 0.10.0 had
44
+ defects found by an external review (see CHANGELOG).
45
+
46
+ ```typescript
47
+ import pg from "pg";
48
+ import { createPregen, postgresStore, loadPregenDirectory, NotGeneratedError } from "@prampta/sdk";
49
+
50
+ const pregen = createPregen({
51
+ baseUrl: "https://api2.prampta.com",
52
+ providerId: "my-ai",
53
+ token: process.env.PRAMPTA_TOKEN!, // your exchange_secret, server-side only
54
+ providerSigningKeyHex: process.env.PREGEN_SIGNING_KEY!, // your Ed25519 private key (32 bytes hex): signs your reports
55
+ pregenDirectory: await loadPregenDirectory({ previous: saved }), // PRAMPTA's keys, namespaces, revocations
56
+ store: postgresStore(new pg.Pool()), // shared by all your workers; table pregen_jobs
57
+ });
58
+
59
+ await pregen.registerKey(); // once (again is harmless): registers your public key
60
+ setInterval(() => pregen.flush(), 60_000); // and at startup: resends reports still waiting
61
+
62
+ const result = await pregen.generate(
63
+ { jobId: task.id, // YOUR stable task id: a retry reuses it, a new generation gets a new one
64
+ subjects: ["PG-..."], // everyone in the output
65
+ prompt, model: "my-model", modality: "image", use: { purpose: "commercial" },
66
+ licenseeId }, // the connected user, or omit for a provider-level request
67
+ async ({ obligations }) => {
68
+ if (queueFull) throw new NotGeneratedError("queue full"); // ONLY when you are sure nothing was produced
69
+ const image = await render(prompt, obligations); // YOU apply the obligations
70
+ return { output: image, outputBytes: image.bytes, obligationsApplied: obligations }; // the ones you carried out
71
+ },
72
+ );
73
+ ```
74
+
75
+ **What one call does.** Checks and copies your input before anything else
76
+ (changing your object afterwards changes nothing). Asks the registry about
77
+ every subject under one job hash; verifies every answer: your pinned key, the
78
+ directory if given, every field of the body, bound to your request, times. In
79
+ `enforce` mode generates only when **all** subjects allow, re-checking right
80
+ before your function runs. Stores the outcome and the signed reports
81
+ **before** sending them, and counts a report as filed only when the
82
+ registry's signed acknowledgement matches it: the report, the answer, its
83
+ status, your key.
84
+
85
+ **The result tells you where the job stands:**
86
+
87
+ | `status` | Meaning | What you do |
88
+ |---|---|---|
89
+ | `confirmed` | Finished; every report acknowledged. `generated` says whether it generated. | Nothing. |
90
+ | `report_pending` | Finished; a report has not been acknowledged yet (network, outage). | Nothing: `flush()` resends it, byte for byte. |
91
+ | `needs_check` | The outcome is unknown: your function threw, the job was interrupted after it may have started generating, or it is running in another worker; or the registry refused a report for good. | Find this `jobId` in your generator, then `pregen.resolve(jobId, { generated: true, outputBytes })` or `{ generated: false }`. |
92
+
93
+ **When your function throws.** An error does not prove that nothing was
94
+ produced: a connection reset can arrive after the generator accepted the job.
95
+ So any error leaves the job `needs_check`, nothing is reported, and the use
96
+ stays reserved until you `resolve()` it. Throw `NotGeneratedError` only when
97
+ you are sure the generator never started; then the use is given back at once.
98
+
99
+ The same `jobId` never generates twice: calling again returns where the job
100
+ stands. The same `jobId` with a different generation is refused
101
+ (`PG_JOB_CONFLICT`). Several workers may call `resolve()` or `flush()` for the
102
+ same job at once: the first recorded outcome stands and everyone sends that
103
+ one report.
104
+
105
+ **Obligations** are handed to your function per subject (`{ subject, id,
106
+ params }`). Return the ones you carried out in `obligationsApplied`; the
107
+ report lists them, and any you did not apply come back in `unmetObligations`.
108
+ How to apply a watermark or a disclosure depends on your medium (image,
109
+ video, audio) and is yours to implement.
110
+
111
+ **Which subjects.** You name them. Matching names in a prompt
112
+ (`matchSubjects`) only suggests candidates; faces, voices and uploaded
113
+ references are for your own pipeline to identify.
114
+
115
+ **`mode`** is `enforce` (default) or `observe`; anything else is refused when
116
+ you create the client. `observe` generates whatever the answers say and
117
+ reports every generation. Nothing is blocked and nothing is granted: use it
118
+ to trial the integration, then switch to `enforce`; nothing else changes.
119
+
120
+ **Registry keys.** With `pregenDirectory` the SDK takes the registry's keys
121
+ from its key set, keeping only those the steward-signed directory lists and
122
+ has not revoked (the steward key is built into `@pregen/verify`); answers and
123
+ acknowledgements must be signed by one of them. Without a directory, pin them
124
+ yourself with `operatorPublicKeyHex`, obtained independently.
125
+
126
+ **Your signing key.** Reports must be signed. `registerKey()` sends a `first`
127
+ keyset signed by the key it names. After that, the registry also requires
128
+ your `/v1` receipts to be signed with the same key (`providerSigningKeyHex`
129
+ in `submitReceipt`). To replace the key, `rotateKey(newKeyHex)` sends a
130
+ `rotation` keyset signed by both keys; then create the client again with the
131
+ new key in every worker. Acknowledged reports stand; reports still waiting,
132
+ or sent by a worker that still has the old key, are signed again with the
133
+ new key by the next `flush()`. Recovering a lost key through the registry is
134
+ not available yet. A provider's keysets are public: `GET /v2/keys/{provider_id}`.
135
+
136
+ **Limits.** Per IP address, 200 requests and 300 reports a minute. Above
137
+ them a job is not generated (`error: "HTTP_429"` in its answers) and waiting
138
+ reports go out with the next `flush()`.
139
+
140
+ **Stores.** `postgresStore(pool)` takes any node-postgres `Pool`, `Client` or
141
+ pooled client (the `pg` package is yours; the SDK does not depend on it). It
142
+ creates its table on first use under a lock, so many workers may start at
143
+ once; if your database role may not create tables, run `postgresSchema` in
144
+ your migrations. `memoryStore()` is for tests and a single process: it is
145
+ lost on restart. A custom store must make `claim` and `update` atomic.
146
+
147
+ Not in this release: Python, other stores, several registries in one job,
148
+ key recovery through the registry. The directory is checked in the
149
+ `pregen.registries.v2` format of `@pregen/verify` 0.7.0. From zero, with the
150
+ failures to handle before production: https://prampta.com/get-started-v2.md.
151
+
39
152
  ## Quick Start: the safe setup (0.9.0)
40
153
 
41
154
  One configuration turns on every check: the signed PRE-GEN directory, your
package/dist/index.d.mts CHANGED
@@ -122,6 +122,193 @@ 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 independently, not from its API. Optional with `pregenDirectory`:
144
+ * the keys are then taken from the registry's key set, keeping only those the signed directory lists. */
145
+ operatorPublicKeyHex?: string | string[];
146
+ store: PregenStore;
147
+ /** `enforce` (default): generate only when every subject allows. `observe`: generate anyway and report; nothing is
148
+ * blocked or granted. Any other value is refused. */
149
+ mode?: "enforce" | "observe";
150
+ /** The PRE-GEN directory (`loadPregenDirectory()`): the registry must own each subject's namespace, and every answer
151
+ * and acknowledgement must be signed by a key the directory lists for it and has not revoked. Recommended. */
152
+ pregenDirectory?: Directory;
153
+ timeoutMs?: number;
154
+ }
155
+ interface PregenJobInput {
156
+ /** Your own stable id for this generation, e.g. your task id. Calling again with the same id
157
+ * never generates twice; a new generation needs a new id. */
158
+ jobId: string;
159
+ /** PRE-GEN codes of everyone in the output. Name matching can suggest them; you decide. */
160
+ subjects: string[];
161
+ /** Hashed locally; never sent. */
162
+ prompt?: string;
163
+ promptHash?: string;
164
+ model: string;
165
+ modality: string;
166
+ use: {
167
+ purpose: Purpose;
168
+ rights?: string[];
169
+ territory?: string;
170
+ channel?: string;
171
+ };
172
+ /** The connected end user's licensee id; empty for a provider-level request. */
173
+ licenseeId?: string;
174
+ }
175
+ type GenerateFn<T> = (ctx: {
176
+ obligations: Obligation[];
177
+ }) => Promise<{
178
+ output: T;
179
+ /** One of these is required: the SHA-256 of the output, or the bytes to hash. */
180
+ outputHash?: string;
181
+ outputBytes?: Uint8Array | string;
182
+ /** The obligations you applied, per subject: return the ones from `obligations` you carried out. */
183
+ obligationsApplied?: {
184
+ subject: string;
185
+ id: string;
186
+ }[];
187
+ }>;
188
+ /** Throw this from your generator only when you are sure it produced nothing (it never started). Any other error
189
+ * leaves the outcome unknown: the job becomes `needs_check` and nothing is reported until you `resolve()` it. */
190
+ declare class NotGeneratedError extends Error {
191
+ readonly notGenerated = true;
192
+ constructor(message?: string);
193
+ }
194
+ interface AskResult {
195
+ subject: string;
196
+ status: "allow" | "deny" | "review" | "not_blocked" | null;
197
+ reason?: string;
198
+ /** Why the answer is missing or unusable: PG_UNREACHABLE, PG_SIGNATURE, PG_MALFORMED, PG_TIME, PG_BINDING... */
199
+ error?: string;
200
+ }
201
+ interface PregenResult<T = unknown> {
202
+ jobId: string;
203
+ /** confirmed: finished, every report acknowledged. report_pending: finished, a report waits
204
+ * for acknowledgement; `flush()` resends it. needs_check: the outcome is unknown (interrupted, the generator threw,
205
+ * or running in another worker) or the registry refused a report; check, then `resolve()`. */
206
+ status: "confirmed" | "report_pending" | "needs_check";
207
+ /** null while the outcome is unknown. */
208
+ generated: boolean | null;
209
+ /** Only from the call that generated. */
210
+ output?: T;
211
+ outputHash?: string;
212
+ answers: AskResult[];
213
+ /** Obligations the answers set that `obligationsApplied` did not list. */
214
+ unmetObligations: Obligation[];
215
+ detail?: string;
216
+ }
217
+ type Stage = "claimed" | "asked" | "finished" | "done";
218
+ /** One job as the store keeps it. */
219
+ interface PregenJob {
220
+ providerId: string;
221
+ jobId: string;
222
+ /** SHA-256 of the manifest; the same job id with another manifest is refused. */
223
+ jobHash: string;
224
+ /** claimed: one worker owns it. asked: answers stored, generation may have started.
225
+ * finished: outcome and signed reports stored, some not acknowledged. done: nothing left to send. */
226
+ stage: Stage;
227
+ asks: {
228
+ request: Record<string, any>;
229
+ answer: Envelope | null;
230
+ error?: string;
231
+ }[];
232
+ generated?: boolean;
233
+ outputHash?: string;
234
+ unmetObligations?: Obligation[];
235
+ /** `rejected`: the registry refused this report for good (it holds a different one, or found it malformed). */
236
+ reports: {
237
+ subject: string;
238
+ answerStatus: string;
239
+ report: Envelope;
240
+ ack?: Envelope;
241
+ rejected?: boolean;
242
+ error?: string;
243
+ }[];
244
+ }
245
+ interface PregenStore {
246
+ /** ATOMIC across every worker: store `job` if none exists for (providerId, jobId) and return
247
+ * null; otherwise change nothing and return the existing job. */
248
+ claim(job: PregenJob): Promise<PregenJob | null>;
249
+ /** ATOMIC: replace the job only if its stored stage is still `from`; true if it was replaced.
250
+ * Resolve only after a durable write. */
251
+ update(job: PregenJob, from: Stage): Promise<boolean>;
252
+ get(providerId: string, jobId: string): Promise<PregenJob | null>;
253
+ /** Jobs in stage `finished`, oldest first. */
254
+ pending(providerId: string): Promise<PregenJob[]>;
255
+ }
256
+ declare class PregenError extends Error {
257
+ readonly code: string;
258
+ constructor(code: string, message: string);
259
+ }
260
+ declare function signingInput(type: string, body: Record<string, unknown>): Uint8Array;
261
+ /** The id of a message: covers type and body, not signatures. */
262
+ declare function messageId(type: string, body: Record<string, unknown>): Promise<string>;
263
+ declare function createPregen(config: PregenConfig): {
264
+ /** Ask, verify, generate (only when permitted), report. See the module comment. */
265
+ generate<T>(input: PregenJobInput, fn: GenerateFn<T>): Promise<PregenResult<T>>;
266
+ /** Resend every report still waiting (after a restart, an outage). Run at startup and every minute or so. */
267
+ flush: () => Promise<PregenResult[]>;
268
+ /** Where a job stands, or null if this store never saw it. */
269
+ status(jobId: string): Promise<PregenResult | null>;
270
+ /** After `needs_check`: record what your generator actually did with this job. Safe to call from several workers:
271
+ * the first recorded outcome stands. */
272
+ resolve(jobId: string, outcome: {
273
+ generated: false;
274
+ } | {
275
+ generated: true;
276
+ outputHash?: string;
277
+ outputBytes?: Uint8Array | string;
278
+ obligationsApplied?: {
279
+ subject: string;
280
+ id: string;
281
+ }[];
282
+ }): Promise<PregenResult>;
283
+ /** Register this provider's signing key with the registry, once (a `first` keyset, §4.9). Calling it again
284
+ * with the same key is harmless. */
285
+ registerKey(): Promise<{
286
+ keyId: string;
287
+ keysetId: string;
288
+ }>;
289
+ /** Replace this client's signing key with `newSigningKeyHex` (a `rotation` keyset signed by both keys).
290
+ * Then create the client again with the new key, in every worker. Reports already acknowledged keep their
291
+ * acknowledgements; reports still waiting, or sent by a worker that still has the old key, are signed again
292
+ * with the new key by the next `flush()` of a client that has it. */
293
+ rotateKey(newSigningKeyHex: string): Promise<{
294
+ keyId: string;
295
+ keysetId: string;
296
+ }>;
297
+ };
298
+ type Pregen = ReturnType<typeof createPregen>;
299
+ /** In memory. Correct for ONE process only, and lost on restart: tests and trials. */
300
+ declare function memoryStore(): PregenStore;
301
+ /** Anything with node-postgres' `query(text, values)`: a `pg` Pool, Client or PoolClient. */
302
+ interface PgQueryable {
303
+ query(text: string, values?: unknown[]): Promise<{
304
+ rows: any[];
305
+ }>;
306
+ }
307
+ /** The table `postgresStore` uses. Run it in your migrations if your database role may not create tables. */
308
+ declare const postgresSchema = "CREATE TABLE IF NOT EXISTS pregen_jobs (\n provider_id text NOT NULL, job_id text NOT NULL, stage text NOT NULL, job jsonb NOT NULL,\n updated_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY (provider_id, job_id))";
309
+ /** Postgres, shared by every worker. Creates its table on first use, under a lock, so many workers may start at once. */
310
+ declare function postgresStore(pool: PgQueryable): PregenStore;
311
+
125
312
  /**
126
313
  * PRAMPTA SDK for TypeScript / Node.js
127
314
  *
@@ -705,4 +892,4 @@ declare class Prampta {
705
892
  private getOnce;
706
893
  }
707
894
 
708
- 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 };
895
+ 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, NotGeneratedError, 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, postgresSchema, postgresStore, signingInput, singleProcessJournal, verifyLicenseSignatures, verifyMerkleProof };
package/dist/index.d.ts CHANGED
@@ -122,6 +122,193 @@ 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 independently, not from its API. Optional with `pregenDirectory`:
144
+ * the keys are then taken from the registry's key set, keeping only those the signed directory lists. */
145
+ operatorPublicKeyHex?: string | string[];
146
+ store: PregenStore;
147
+ /** `enforce` (default): generate only when every subject allows. `observe`: generate anyway and report; nothing is
148
+ * blocked or granted. Any other value is refused. */
149
+ mode?: "enforce" | "observe";
150
+ /** The PRE-GEN directory (`loadPregenDirectory()`): the registry must own each subject's namespace, and every answer
151
+ * and acknowledgement must be signed by a key the directory lists for it and has not revoked. Recommended. */
152
+ pregenDirectory?: Directory;
153
+ timeoutMs?: number;
154
+ }
155
+ interface PregenJobInput {
156
+ /** Your own stable id for this generation, e.g. your task id. Calling again with the same id
157
+ * never generates twice; a new generation needs a new id. */
158
+ jobId: string;
159
+ /** PRE-GEN codes of everyone in the output. Name matching can suggest them; you decide. */
160
+ subjects: string[];
161
+ /** Hashed locally; never sent. */
162
+ prompt?: string;
163
+ promptHash?: string;
164
+ model: string;
165
+ modality: string;
166
+ use: {
167
+ purpose: Purpose;
168
+ rights?: string[];
169
+ territory?: string;
170
+ channel?: string;
171
+ };
172
+ /** The connected end user's licensee id; empty for a provider-level request. */
173
+ licenseeId?: string;
174
+ }
175
+ type GenerateFn<T> = (ctx: {
176
+ obligations: Obligation[];
177
+ }) => Promise<{
178
+ output: T;
179
+ /** One of these is required: the SHA-256 of the output, or the bytes to hash. */
180
+ outputHash?: string;
181
+ outputBytes?: Uint8Array | string;
182
+ /** The obligations you applied, per subject: return the ones from `obligations` you carried out. */
183
+ obligationsApplied?: {
184
+ subject: string;
185
+ id: string;
186
+ }[];
187
+ }>;
188
+ /** Throw this from your generator only when you are sure it produced nothing (it never started). Any other error
189
+ * leaves the outcome unknown: the job becomes `needs_check` and nothing is reported until you `resolve()` it. */
190
+ declare class NotGeneratedError extends Error {
191
+ readonly notGenerated = true;
192
+ constructor(message?: string);
193
+ }
194
+ interface AskResult {
195
+ subject: string;
196
+ status: "allow" | "deny" | "review" | "not_blocked" | null;
197
+ reason?: string;
198
+ /** Why the answer is missing or unusable: PG_UNREACHABLE, PG_SIGNATURE, PG_MALFORMED, PG_TIME, PG_BINDING... */
199
+ error?: string;
200
+ }
201
+ interface PregenResult<T = unknown> {
202
+ jobId: string;
203
+ /** confirmed: finished, every report acknowledged. report_pending: finished, a report waits
204
+ * for acknowledgement; `flush()` resends it. needs_check: the outcome is unknown (interrupted, the generator threw,
205
+ * or running in another worker) or the registry refused a report; check, then `resolve()`. */
206
+ status: "confirmed" | "report_pending" | "needs_check";
207
+ /** null while the outcome is unknown. */
208
+ generated: boolean | null;
209
+ /** Only from the call that generated. */
210
+ output?: T;
211
+ outputHash?: string;
212
+ answers: AskResult[];
213
+ /** Obligations the answers set that `obligationsApplied` did not list. */
214
+ unmetObligations: Obligation[];
215
+ detail?: string;
216
+ }
217
+ type Stage = "claimed" | "asked" | "finished" | "done";
218
+ /** One job as the store keeps it. */
219
+ interface PregenJob {
220
+ providerId: string;
221
+ jobId: string;
222
+ /** SHA-256 of the manifest; the same job id with another manifest is refused. */
223
+ jobHash: string;
224
+ /** claimed: one worker owns it. asked: answers stored, generation may have started.
225
+ * finished: outcome and signed reports stored, some not acknowledged. done: nothing left to send. */
226
+ stage: Stage;
227
+ asks: {
228
+ request: Record<string, any>;
229
+ answer: Envelope | null;
230
+ error?: string;
231
+ }[];
232
+ generated?: boolean;
233
+ outputHash?: string;
234
+ unmetObligations?: Obligation[];
235
+ /** `rejected`: the registry refused this report for good (it holds a different one, or found it malformed). */
236
+ reports: {
237
+ subject: string;
238
+ answerStatus: string;
239
+ report: Envelope;
240
+ ack?: Envelope;
241
+ rejected?: boolean;
242
+ error?: string;
243
+ }[];
244
+ }
245
+ interface PregenStore {
246
+ /** ATOMIC across every worker: store `job` if none exists for (providerId, jobId) and return
247
+ * null; otherwise change nothing and return the existing job. */
248
+ claim(job: PregenJob): Promise<PregenJob | null>;
249
+ /** ATOMIC: replace the job only if its stored stage is still `from`; true if it was replaced.
250
+ * Resolve only after a durable write. */
251
+ update(job: PregenJob, from: Stage): Promise<boolean>;
252
+ get(providerId: string, jobId: string): Promise<PregenJob | null>;
253
+ /** Jobs in stage `finished`, oldest first. */
254
+ pending(providerId: string): Promise<PregenJob[]>;
255
+ }
256
+ declare class PregenError extends Error {
257
+ readonly code: string;
258
+ constructor(code: string, message: string);
259
+ }
260
+ declare function signingInput(type: string, body: Record<string, unknown>): Uint8Array;
261
+ /** The id of a message: covers type and body, not signatures. */
262
+ declare function messageId(type: string, body: Record<string, unknown>): Promise<string>;
263
+ declare function createPregen(config: PregenConfig): {
264
+ /** Ask, verify, generate (only when permitted), report. See the module comment. */
265
+ generate<T>(input: PregenJobInput, fn: GenerateFn<T>): Promise<PregenResult<T>>;
266
+ /** Resend every report still waiting (after a restart, an outage). Run at startup and every minute or so. */
267
+ flush: () => Promise<PregenResult[]>;
268
+ /** Where a job stands, or null if this store never saw it. */
269
+ status(jobId: string): Promise<PregenResult | null>;
270
+ /** After `needs_check`: record what your generator actually did with this job. Safe to call from several workers:
271
+ * the first recorded outcome stands. */
272
+ resolve(jobId: string, outcome: {
273
+ generated: false;
274
+ } | {
275
+ generated: true;
276
+ outputHash?: string;
277
+ outputBytes?: Uint8Array | string;
278
+ obligationsApplied?: {
279
+ subject: string;
280
+ id: string;
281
+ }[];
282
+ }): Promise<PregenResult>;
283
+ /** Register this provider's signing key with the registry, once (a `first` keyset, §4.9). Calling it again
284
+ * with the same key is harmless. */
285
+ registerKey(): Promise<{
286
+ keyId: string;
287
+ keysetId: string;
288
+ }>;
289
+ /** Replace this client's signing key with `newSigningKeyHex` (a `rotation` keyset signed by both keys).
290
+ * Then create the client again with the new key, in every worker. Reports already acknowledged keep their
291
+ * acknowledgements; reports still waiting, or sent by a worker that still has the old key, are signed again
292
+ * with the new key by the next `flush()` of a client that has it. */
293
+ rotateKey(newSigningKeyHex: string): Promise<{
294
+ keyId: string;
295
+ keysetId: string;
296
+ }>;
297
+ };
298
+ type Pregen = ReturnType<typeof createPregen>;
299
+ /** In memory. Correct for ONE process only, and lost on restart: tests and trials. */
300
+ declare function memoryStore(): PregenStore;
301
+ /** Anything with node-postgres' `query(text, values)`: a `pg` Pool, Client or PoolClient. */
302
+ interface PgQueryable {
303
+ query(text: string, values?: unknown[]): Promise<{
304
+ rows: any[];
305
+ }>;
306
+ }
307
+ /** The table `postgresStore` uses. Run it in your migrations if your database role may not create tables. */
308
+ declare const postgresSchema = "CREATE TABLE IF NOT EXISTS pregen_jobs (\n provider_id text NOT NULL, job_id text NOT NULL, stage text NOT NULL, job jsonb NOT NULL,\n updated_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY (provider_id, job_id))";
309
+ /** Postgres, shared by every worker. Creates its table on first use, under a lock, so many workers may start at once. */
310
+ declare function postgresStore(pool: PgQueryable): PregenStore;
311
+
125
312
  /**
126
313
  * PRAMPTA SDK for TypeScript / Node.js
127
314
  *
@@ -705,4 +892,4 @@ declare class Prampta {
705
892
  private getOnce;
706
893
  }
707
894
 
708
- 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 };
895
+ 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, NotGeneratedError, 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, postgresSchema, postgresStore, signingInput, singleProcessJournal, verifyLicenseSignatures, verifyMerkleProof };