@prampta/sdk 0.4.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,3 +1,125 @@
1
+ import { Directory } from '@pregen/verify';
2
+ export { Directory as PregenDirectory, loadDirectory as loadPregenDirectory } from '@pregen/verify';
3
+
4
+ /** PRE-GEN v5 serialization. Do not silently replace it with JCS: existing
5
+ * signatures use these bytes. A new canonicalization needs a new version. */
6
+ declare function canonicalJson(obj: unknown): string;
7
+
8
+ declare class RegistryDirectoryError extends Error {
9
+ constructor(message: string);
10
+ }
11
+ interface RegistryDirectoryOptions {
12
+ /** Trust anchor obtained independently, NOT from the directory itself. */
13
+ stewardPublicKeyHex: string;
14
+ /** Highest accepted sequence, persisted by the application across restarts. */
15
+ minSequence?: number;
16
+ /** Last accepted state from durable storage; detects same-sequence forks. */
17
+ previousSnapshot?: {
18
+ sequence: number;
19
+ hash: string;
20
+ };
21
+ }
22
+ /** Immutable, signature-verified directory snapshot. No network discovery,
23
+ * automatic root replacement, registry accreditation or rights attestation. */
24
+ declare class TrustedRegistryDirectory {
25
+ #private;
26
+ readonly sequence: number;
27
+ readonly snapshotHash: string;
28
+ private constructor();
29
+ static verify(input: unknown, options: RegistryDirectoryOptions): Promise<TrustedRegistryDirectory>;
30
+ private registryFor;
31
+ /** V-11: checked before forwarding a provider's credential to an endpoint. */
32
+ assertEndpoint(subject: string, baseUrl: string): void;
33
+ /** V-10: use only AFTER independently verifying the object's signature. */
34
+ assertSigner(code: string, keyId: string): void;
35
+ assertDecision(subject: string, license: string | null, keyId: string, baseUrl: string): void;
36
+ }
37
+
38
+ interface LicenseSignatureInput {
39
+ body: Record<string, unknown>;
40
+ licenseId: string;
41
+ subjectPublicKeyHex: string;
42
+ subjectSignatureHex: string;
43
+ operatorPublicKeyHex: string;
44
+ operatorSignatureHex: string;
45
+ }
46
+ /** V-9 crypto check only. Obtain keys independently and check namespace,
47
+ * authority, status and scope separately; true is NOT a proof of consent. */
48
+ declare function verifyLicenseSignatures(input: LicenseSignatureInput): Promise<{
49
+ subjectValid: boolean;
50
+ operatorValid: boolean;
51
+ }>;
52
+
53
+ /** Connect Step 1. Server-side only; reporting NEVER authorizes generation. */
54
+ interface Observation {
55
+ schema_version: "pg.observation.v1";
56
+ event_id: string;
57
+ output_id: string;
58
+ subject_id: string;
59
+ event_type: "output.created" | "output.modified" | "output.published" | "publication.removed";
60
+ output_hash: string;
61
+ occurred_at: string;
62
+ declared_purpose: "personal" | "commercial" | "educational" | "research" | "editorial" | "unknown";
63
+ parent_output_hash?: string;
64
+ publication_url?: string;
65
+ }
66
+ interface ReportInput {
67
+ /** Stable provider job/event key. Different publications/edits need different keys. */
68
+ eventKey: string;
69
+ outputId: string;
70
+ /** Provider-confirmed association, NOT automatically a text matcher hit. */
71
+ subjectId: string;
72
+ /** Original event time, retained across retries. */
73
+ occurredAt: string;
74
+ eventType?: Observation["event_type"];
75
+ purpose?: Observation["declared_purpose"];
76
+ /** Actual output bytes or text, never a URL to be downloaded. Stays local. */
77
+ output?: Uint8Array | string;
78
+ /** For large files, compute incrementally in your storage pipeline. */
79
+ outputHash?: string;
80
+ parentOutputHash?: string;
81
+ publicationUrl?: string;
82
+ }
83
+ interface ObservationOutbox {
84
+ /** Persist atomically under event_id; same payload is a no-op, changed payload
85
+ * must reject. Namespace storage by provider AND environment. Resolve only
86
+ * after durable commit. Implement using the provider's existing DB/job queue. */
87
+ put(event: Observation): Promise<void>;
88
+ }
89
+ type DeliveryResult = {
90
+ status: "accepted";
91
+ observationId: string;
92
+ } | {
93
+ status: "retry";
94
+ httpStatus?: number;
95
+ retryAfterMs: number;
96
+ } | {
97
+ status: "rejected";
98
+ httpStatus: number;
99
+ };
100
+ declare class PramptaReporter {
101
+ private readonly config;
102
+ private readonly baseUrl;
103
+ private readonly timeoutMs;
104
+ constructor(config: {
105
+ baseUrl: string;
106
+ providerId: string;
107
+ token: string;
108
+ outbox: ObservationOutbox;
109
+ timeoutMs?: number;
110
+ });
111
+ /** No PRAMPTA network call: hash locally and commit to the durable outbox.
112
+ * A storage failure is surfaced, never reported as success. Run from the
113
+ * provider's generation-completed job, not as an authorization gate. */
114
+ report(input: ReportInput): Promise<{
115
+ status: "queued";
116
+ eventId: string;
117
+ }>;
118
+ /** One bounded attempt by your queue worker. Keep/retry on `retry`, quarantine
119
+ * on `rejected`, acknowledge only `accepted`. Never changes /verify behavior. */
120
+ deliver(event: Observation): Promise<DeliveryResult>;
121
+ }
122
+
1
123
  /**
2
124
  * PRAMPTA SDK for TypeScript / Node.js
3
125
  *
@@ -11,26 +133,47 @@
11
133
  * const pg = new Prampta({
12
134
  * baseUrl: "https://api2.prampta.com",
13
135
  * providerId: "my-ai-service",
14
- * licenseeId: "acme-corp",
15
- * token: "pair-token",
136
+ * licenseeId: "lic-sandbox", // synthetic sandbox fixture only
137
+ * // Your runtime credential's `exchange_secret` — from your provider
138
+ * // application's verify-email/verify-domain response (see the Provider
139
+ * // Connect Guide). NOT the legacy shared pair token; see `token` below.
140
+ * token: process.env.PRAMPTA_TOKEN,
16
141
  * });
17
142
  *
18
143
  * // Throws if denied — fail-closed by default
19
- * await pg.assertAllowed("leonardo-da-vinci", {
20
- * prompt: "Da Vinci in a documentary",
144
+ * await pg.assertAllowed("sbx-allowed", {
145
+ * prompt: "A sandbox portrait",
21
146
  * modality: "image",
22
- * model: "gpt-image-1",
147
+ * model: "your-model",
148
+ * intendedUse: { useCase: "research" },
23
149
  * });
24
150
  * ```
25
151
  */
152
+
26
153
  interface PramptaConfig {
27
154
  /** The PRAMPTA registry URL. Falls back to PRAMPTA_BASE_URL env var. */
28
155
  baseUrl?: string;
29
156
  /** Your provider ID. Falls back to PRAMPTA_PROVIDER_ID env var. */
30
157
  providerId?: string;
31
- /** The licensee ID. Falls back to PRAMPTA_LICENSEE_ID env var. */
158
+ /** The licensee ID (`prampta_licensee_id` from Connect AI). Falls back to
159
+ * PRAMPTA_LICENSEE_ID. Optional: without it, verify is a provider-level
160
+ * check that can only answer `not_blocked` for `useCase: "personal"`. */
32
161
  licenseeId?: string;
33
- /** The pair authentication token. Falls back to PRAMPTA_TOKEN env var. */
162
+ /**
163
+ * The runtime bearer credential. This is your `ProviderCredential`'s
164
+ * `exchange_secret` — issued inline by `verify-email` (sandbox) or
165
+ * `verify-domain/confirm` (production) during provider onboarding.
166
+ * The `prampta_connection_token` returned by Connect AI is NOT accepted
167
+ * for production runtime calls.
168
+ * Falls back to PRAMPTA_TOKEN env var.
169
+ *
170
+ * The legacy shared PAIR TOKEN also works here ONLY while the server is
171
+ * running in `PILOT_MODE` (pre-migration deployments) — production
172
+ * deployments with the trust flags on (the v1.0.0 default) reject it with
173
+ * `PG_NO_PAIR`. Do not build new integrations against the pair token; it
174
+ * exists solely so an already-connected provider keeps working during
175
+ * migration. See docs/PRAMPTA-Provider-Connect-Guide-2026-08.md.
176
+ */
34
177
  token?: string;
35
178
  /** Request timeout in milliseconds (default 3000). */
36
179
  timeoutMs?: number;
@@ -65,6 +208,27 @@ interface PramptaConfig {
65
208
  * Set to false ONLY for local development — never in production.
66
209
  */
67
210
  verifyDecisionSignature?: boolean;
211
+ /**
212
+ * If true, verify() throws PramptaSchemaError LOCALLY — before any network
213
+ * call — when neither providerUserId nor providerIdentityLinkId was
214
+ * supplied. Off by default (most integrations have legitimately unbound
215
+ * calls); turn it on once your integration always acts for a specific end
216
+ * user, so a caller that forgot to pass one fails fast and loud in your own
217
+ * code rather than silently reaching the server as an "unbound" request.
218
+ * Independent of, and a cheaper backstop for, the server-side
219
+ * REQUIRE_PROVIDER_USER_BINDING enforcement — this catches the mistake
220
+ * before spending a round-trip. Default false.
221
+ */
222
+ requireUserBinding?: boolean;
223
+ /** Out-of-band verified namespace directory. Opt-in while the public
224
+ * steward root is not yet published. Requires pinned operator keys. */
225
+ registryDirectory?: TrustedRegistryDirectory;
226
+ /** The PRE-GEN directory from `loadPregenDirectory()`, with the steward key built in.
227
+ * Before a request the SDK checks that `baseUrl` owns the subject's namespace; on an
228
+ * `allow` it also runs @pregen/verify `checkDecision`: the signing key must be one the
229
+ * directory lists for the license's namespace (origin keys pinned), on top of the checks
230
+ * below. Recommended over `registryDirectory`. */
231
+ pregenDirectory?: Directory;
68
232
  }
69
233
  interface VerifyRequest {
70
234
  /** Subject to verify authorization for. */
@@ -84,7 +248,43 @@ interface VerifyRequest {
84
248
  projectName?: string;
85
249
  categories?: string[];
86
250
  territory?: string;
251
+ /** Commercial-model campaign binding — required by premium billing terms. */
252
+ campaignId?: string;
253
+ /** The use-case tier: "personal" | "educational" | "research" |
254
+ * "editorial" | "commercial". Personal use with no licence answers
255
+ * disposition "not_blocked" only when this is declared (NMP Q13). */
256
+ useCase?: string;
257
+ /** Rights requested in addition to the generation modality. */
258
+ rights?: string[];
87
259
  };
260
+ /** ── Provider-user binding (prampta.identity.v2) ──
261
+ * WHO you are acting for. Optional, so existing integrations are
262
+ * unaffected — but once supplied it is held to: it must resolve to an
263
+ * identity link PRAMPTA verified, and a licence belonging to a different
264
+ * end user is refused. Supply either identifier (or both — they must then
265
+ * describe the same person). */
266
+ providerUserId?: string;
267
+ providerIdentityLinkId?: string;
268
+ /** Your own id for this generation attempt. Echoed into the decision so a
269
+ * later receipt can be tied to it without a provider-side lookup table. */
270
+ generationId?: string;
271
+ /** Lets you safely retry a preflight you are unsure landed. */
272
+ idempotencyKey?: string;
273
+ /** Links this call to a prior reportDetection()/confirmDetection() advisory
274
+ * record, purely for audit — never used to decide the outcome. */
275
+ detectionId?: string;
276
+ /** Where to send the end user back to after they buy/request a licence via
277
+ * the returned decision's `remediation.url` (set only on PG_NO_LICENSE) —
278
+ * same idea as Connect AI's connect_return_url. Omit to leave them on
279
+ * prampta.com after they act. */
280
+ returnUrl?: string;
281
+ /** Protocol version negotiation: declares the decision schema THIS CALL
282
+ * expects (e.g. "pg.decision.v1"). A mismatch refuses with 409 before any
283
+ * licensing work happens, instead of silently misreading a future schema.
284
+ * Optional — omit to keep today's behavior. Discover the server's current
285
+ * version via GET /version -> protocol_versions.decision_schema. See
286
+ * docs/PRAMPTA-Protocol-Versions.md. */
287
+ expectedSchemaVersion?: string;
88
288
  }
89
289
  /** Options for the flat `pg.verify(subjectId, options)` convenience call. */
90
290
  interface VerifyOptions extends Omit<VerifyRequest, "subjectId"> {
@@ -93,6 +293,9 @@ interface VerifyOptions extends Omit<VerifyRequest, "subjectId"> {
93
293
  productName?: string;
94
294
  projectName?: string;
95
295
  territory?: string;
296
+ campaignId?: string;
297
+ useCase?: string;
298
+ rights?: string[];
96
299
  }
97
300
  interface SignedDecision {
98
301
  schemaVersion: string;
@@ -120,6 +323,42 @@ interface SignedDecision {
120
323
  * consented | verified. "self" is only the registrant's own claim. */
121
324
  subjectAuthority: string;
122
325
  denied: boolean;
326
+ /** Outcome (§8): "allow" | "deny" | "review" | "not_blocked". `allowed` stays
327
+ * the boolean; a REVIEW is allowed=false + disposition="review" — hold for a
328
+ * human, not a flat refusal. "not_blocked" (NMP Q13) is personal use with no
329
+ * licence: PRAMPTA does not object but grants nothing (reason
330
+ * PG_STD_TRACKING_ONLY). Older code reading only `allowed` fails closed. */
331
+ disposition: string;
332
+ /** Policy version that produced this decision. */
333
+ policyVersion: string;
334
+ /** §7 freshness controls. `revocationEpoch` is the subject's epoch at
335
+ * issuance — compare it against GET /subjects/{id}/epoch to learn cheaply
336
+ * whether anything happened since (revoke, dispute, lifecycle change)
337
+ * without re-running verify(). `maxCacheAgeSeconds` and `cacheScope` say
338
+ * whether and how long this exact decision may be reused without a live
339
+ * re-check; only a plain ALLOW is ever cacheable. */
340
+ revocationEpoch: number;
341
+ maxCacheAgeSeconds: number;
342
+ cacheScope: string;
343
+ /** Provider-user binding: "verified" — bound to an end-user identity
344
+ * PRAMPTA verified; "unbound" — no end user was asserted; "invalid" — an
345
+ * assertion was made and failed (always accompanies a deny). */
346
+ providerUserBinding: string;
347
+ /** Which identity link was used, when one was. */
348
+ providerIdentityLinkId: string;
349
+ /** Echoed back from the request, for receipt matching. */
350
+ generationId: string;
351
+ /** Echoed back from the request — links to a detection advisory record. */
352
+ detectionId: string;
353
+ /** Set only when reason === "PG_NO_LICENSE": a direct link to buy/request a
354
+ * licence for this exact subject, plus the one canonical explanation text
355
+ * (show it before redirecting — every integration should read the same
356
+ * wording). null on every other outcome, including allow and refusals a
357
+ * licence purchase would not fix. */
358
+ remediation: {
359
+ url: string;
360
+ message: string;
361
+ } | null;
123
362
  }
124
363
  interface SubjectInfo {
125
364
  subjectId: string;
@@ -191,16 +430,22 @@ declare const SOFT_REFUSAL_CODES: Set<string>;
191
430
  * `Prampta.hashPrompt` so it can be imported directly. */
192
431
  declare function hashPrompt(prompt: string): Promise<string>;
193
432
  /**
194
- * Canonical JSON — deterministic serialization matching Python's
195
- * `json.dumps(obj, sort_keys=True, separators=(",",":"), ensure_ascii=False)`.
196
- * Required for signature verification across languages.
433
+ * Client-side Merkle inclusion-proof verification (PRE-GEN v1.1 addendum
434
+ * §10, Anchored Audit Profile). Mirror of the registry's own
435
+ * `app/core/merkle.py` — same algorithm, same odd-node handling — so a
436
+ * proof from `Prampta.getInclusionProof()` can be checked entirely
437
+ * offline against a root hash you already trust, without the registry
438
+ * being a party to its own audit.
197
439
  *
198
- * Exported so external verifiers can rebuild signed bodies and so the
199
- * cross-implementation spec test vectors (spec/test-vectors) can pin it.
200
- * Field names in signed protocol bodies are ASCII by construction — key
201
- * sorting is identical to Python's code-point sort in that range.
440
+ * `proof` is the array PRAMPTA returns as `inclusion_proof.proof` — each
441
+ * step `{hash: <sibling hash>, position: "left"|"right"}`. Returns true
442
+ * only if `leafHash` is genuinely one of the leaves committed to by
443
+ * `rootHash`.
202
444
  */
203
- declare function canonicalJson(obj: unknown): string;
445
+ declare function verifyMerkleProof(leafHash: string, proof: Array<{
446
+ hash: string;
447
+ position: "left" | "right";
448
+ }>, rootHash: string): Promise<boolean>;
204
449
  /** One entry of the /v1/subjects/index detection index. */
205
450
  interface SubjectIndexEntry {
206
451
  subject_id: string;
@@ -232,11 +477,16 @@ declare class Prampta {
232
477
  private readonly failClosed;
233
478
  private readonly verifySignature;
234
479
  private readonly enforce;
480
+ private readonly requireUserBinding;
481
+ private readonly registryDirectory?;
482
+ private readonly pregenDirectory?;
235
483
  private pinnedKeys;
236
484
  private keySetCache;
237
485
  private keySetPromise;
238
486
  private tofuWarned;
239
487
  constructor(config?: PramptaConfig);
488
+ /** V-11: a PG code is answered only by the registry that owns its namespace. */
489
+ private assertPregenEndpoint;
240
490
  /** Fetch /keys and check it is self-signed by its current key. Cached. */
241
491
  private fetchVerifiedKeySet;
242
492
  /**
@@ -262,6 +512,27 @@ declare class Prampta {
262
512
  matchSubjects(text: string, opts?: {
263
513
  forceRefresh?: boolean;
264
514
  }): Promise<SubjectIndexEntry[]>;
515
+ /** Record what a local text match found for a prompt, BEFORE the user
516
+ * answers. Purely advisory — creates no entitlement or licence. Pass the
517
+ * returned detectionId to confirmDetection() and optionally to verify(). */
518
+ reportDetection(promptHash: string, candidateSubjectIds: string[]): Promise<{
519
+ detectionId: string;
520
+ status: string;
521
+ candidateSubjectIds: string[];
522
+ confirmedSubjectId: string | null;
523
+ createdAt: string;
524
+ confirmedAt: string | null;
525
+ }>;
526
+ /** Record what the user actually said. `subjectId` undefined/null means
527
+ * they rejected every candidate. Still advisory-only. */
528
+ confirmDetection(detectionId: string, subjectId?: string | null): Promise<{
529
+ detectionId: string;
530
+ status: string;
531
+ candidateSubjectIds: string[];
532
+ confirmedSubjectId: string | null;
533
+ createdAt: string;
534
+ confirmedAt: string | null;
535
+ }>;
265
536
  /**
266
537
  * Verify operator Ed25519 signature over decision body.
267
538
  * Fails closed on any error.
@@ -279,6 +550,10 @@ declare class Prampta {
279
550
  * the prompt hash and the SDK refuses to request unbound ones. */
280
551
  verify(subjectId: string, options?: VerifyOptions): Promise<SignedDecision>;
281
552
  assertAllowed(subjectId: string, options?: Omit<VerifyRequest, "subjectId">): Promise<SignedDecision>;
553
+ /** Strict license-backed path. Unlike assertAllowed, this never accepts
554
+ * reporting-only not_blocked, an internal no-license allow, or monitor mode.
555
+ * It validates an authorization; it does not prove output compliance. */
556
+ assertLicensed(subjectId: string, options?: Omit<VerifyRequest, "subjectId">): Promise<SignedDecision>;
282
557
  withAuthorization<T>(request: VerifyRequest, generateFn: (decision: SignedDecision) => Promise<T>): Promise<{
283
558
  output: T;
284
559
  decision: SignedDecision;
@@ -288,9 +563,9 @@ declare class Prampta {
288
563
  requestLicense(input: LicenseRequestInput): Promise<LicenseRequestResult>;
289
564
  createOutputMetadata(decision: SignedDecision): OutputMetadata;
290
565
  /**
291
- * Submit a generation receipt AFTER generating — the provider's enforcement
292
- * proof, bound to the decision it acted on. Records that this generation was
293
- * authorized and executed under the decision's terms (verifiable post-hoc).
566
+ * Report an output after generation. With a registered provider signing key,
567
+ * opt into v3 so eventType is bound to the provider's signature. This is a
568
+ * provider attestation, not proof of complete reporting or actual execution.
294
569
  */
295
570
  submitReceipt(decision: SignedDecision, result?: {
296
571
  outputHash?: string;
@@ -298,7 +573,31 @@ declare class Prampta {
298
573
  watermarkEmbedded?: boolean;
299
574
  obligationsApplied?: Record<string, unknown>;
300
575
  generatedAt?: number;
576
+ eventType?: "preview" | "output_accepted" | "output_delivered" | "output_published";
577
+ /** 32-byte Ed25519 private key, hex; keep server-side. Never sent to PRAMPTA. */
578
+ providerSigningKeyHex?: string;
301
579
  }): Promise<Record<string, unknown>>;
580
+ /**
581
+ * Get (issuing if needed) the signed `pg.assertion.v1` for a decision this
582
+ * provider/licensee pair already submitted a receipt for — embed it in
583
+ * your OWN C2PA manifest (PRAMPTA does not build the manifest itself).
584
+ * Idempotent: safe to call again for the same decisionId, always returns
585
+ * the same assertion. Requires `submitReceipt()` to have run first.
586
+ */
587
+ createAssertion(decisionId: string): Promise<Record<string, unknown>>;
588
+ /** Fetch a previously issued assertion. Public — no auth required, since a
589
+ * third party checking a C2PA manifest's embedded assertion needs to
590
+ * verify it without going through PRAMPTA's own auth. */
591
+ getAssertion(decisionId: string): Promise<Record<string, unknown>>;
592
+ /** The most recently published Merkle root over the audit log. Public,
593
+ * unauthenticated. */
594
+ getMerkleRoot(): Promise<Record<string, unknown>>;
595
+ /** Proof that `eventId` is one of the leaves committed to by a Merkle
596
+ * root, without downloading the rest of the log. Check it offline with
597
+ * the standalone `verifyMerkleProof(eventHash, proof, rootHash)` — don't
598
+ * just trust the 200 response; the proof exists so you don't have to.
599
+ * Public, unauthenticated. */
600
+ getInclusionProof(eventId: string): Promise<Record<string, unknown>>;
302
601
  health(): Promise<boolean>;
303
602
  version(): Promise<Record<string, unknown>>;
304
603
  static hashPrompt(prompt: string): Promise<string>;
@@ -313,4 +612,4 @@ declare class Prampta {
313
612
  private getOnce;
314
613
  }
315
614
 
316
- export { HARD_REFUSAL_CODES, type LicenseRequestInput, type LicenseRequestResult, type OutputMetadata, Prampta, PramptaApiError, type PramptaConfig, PramptaError, PramptaFailClosedError, PramptaNetworkError, PramptaRefusalError, PramptaSchemaError, PramptaSignatureError, PramptaTimeoutError, REFUSAL_DESCRIPTIONS, SOFT_REFUSAL_CODES, type SignedDecision, type SubjectIndexEntry, type SubjectInfo, type VerifyOptions, type VerifyRequest, canonicalJson, hashPrompt, matchSubjects, normalizeForMatch };
615
+ export { type DeliveryResult, 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, 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 };