@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/README.md CHANGED
@@ -8,7 +8,33 @@ Pre-generation authorization for AI content. Verifies operator Ed25519 signature
8
8
  npm install @prampta/sdk
9
9
  ```
10
10
 
11
- Requires Node.js ≥ 18 (uses native `crypto.subtle` for SHA-256).
11
+ 0.7.0 requires Node **20.19.x or newer 20.x, or 22.12+**. Earlier runtime
12
+ support is not claimed: CommonJS loads the ESM-only crypto dependency through
13
+ [`require(esm)`](https://nodejs.org/en/blog/release/v20.19.0). Local ESM/CommonJS
14
+ smoke tests run on Node 22.20; a wider runtime/browser matrix is still a release gate.
15
+
16
+ This README describes SDK 0.7.0.
17
+ In older 0.4.0, `assertAllowed` throws on
18
+ personal-use `not_blocked` and `PramptaReporter` is unavailable. Use `verify()`
19
+ and inspect `disposition` when supporting older installations;
20
+ `not_blocked` is not a licence in any version.
21
+
22
+ ## Authentication
23
+
24
+ `token` is your **runtime credential** — a `ProviderCredential` `exchange_secret`,
25
+ issued the moment you complete provider onboarding (inline in the
26
+ `verify-email` response for sandbox, `verify-domain/confirm` for production).
27
+ The `prampta_connection_token` returned by the Connect AI code exchange is not
28
+ accepted by production for runtime calls — use the `exchange_secret`, and pass
29
+ the user's `prampta_licensee_id` as `licenseeId`. See
30
+ [docs/PRAMPTA-Provider-Connect-Guide-2026-08.md](../../docs/PRAMPTA-Provider-Connect-Guide-2026-08.md)
31
+ in the main repo for the full onboarding flow.
32
+
33
+ There used to be a second way in — a shared **pair token** — kept alive only
34
+ for deployments still running in `PILOT_MODE` while a provider migrates. A
35
+ production PRAMPTA deployment running the v1.0.0-default trust posture
36
+ refuses it with `PG_NO_PAIR`. Don't build a new integration against it; it
37
+ exists solely so an already-connected provider keeps working mid-migration.
12
38
 
13
39
  ## Quick Start
14
40
 
@@ -18,22 +44,25 @@ import { Prampta } from "@prampta/sdk";
18
44
  const pg = new Prampta({
19
45
  baseUrl: "https://api2.prampta.com",
20
46
  providerId: "my-ai-service",
21
- licenseeId: "acme-corp",
22
- token: "pair-token",
47
+ licenseeId: "lic-sandbox", // synthetic fixture only
48
+ token: process.env.PRAMPTA_TOKEN, // sandbox exchange_secret; server-side only
49
+ operatorPublicKeyHex: process.env.PRAMPTA_OPERATOR_PUBLIC_KEY, // obtained independently
23
50
  });
24
51
 
25
- // Option 1: Assert — throws if denied (fail-closed)
26
- await pg.assertAllowed("leonardo-da-vinci", {
27
- prompt: "Da Vinci in a documentary",
52
+ // Option 1: Assert — throws if denied. This sandbox fixture has a research licence.
53
+ await pg.assertAllowed("sbx-allowed", {
54
+ prompt: "A sandbox portrait",
28
55
  modality: "image",
29
- model: "gpt-image-1",
56
+ model: "your-model",
57
+ intendedUse: { useCase: "research" },
30
58
  });
31
59
 
32
60
  // Option 2: Verify — returns decision object
33
- const result = await pg.verify("leonardo-da-vinci", {
34
- prompt: "Da Vinci in a documentary",
61
+ const result = await pg.verify("sbx-allowed", {
62
+ prompt: "A sandbox portrait",
35
63
  modality: "image",
36
- model: "gpt-image-1",
64
+ model: "your-model",
65
+ intendedUse: { useCase: "research" },
37
66
  });
38
67
 
39
68
  if (result.allowed) {
@@ -43,6 +72,143 @@ if (result.allowed) {
43
72
  }
44
73
  ```
45
74
 
75
+ For real users, replace `lic-sandbox` with the connected user's
76
+ `prampta_licensee_id` and use a subject and licence that actually cover the
77
+ declared purpose. For unlicensed personal use, omit `licenseeId`, declare
78
+ `useCase: "personal"`, call `verify()` and handle `not_blocked` separately;
79
+ it grants no rights and has no licence receipt. After an `allow` generation,
80
+ file the receipt described in the [Connect Step 2 guide](https://prampta.com/get-started.md).
81
+
82
+ ## Strict license-backed authorization (0.7.0)
83
+
84
+ `assertAllowed` preserves reporting-only personal use. `assertLicensed` is
85
+ the explicit license-backed path: signature verification and pinned operator
86
+ keys are mandatory, as is your generation id. It rejects `not_blocked`,
87
+ `review`, `deny`, and an internal `allow` without a license, even in monitor
88
+ mode. This verifies a decision, not rights-holder authority or output compliance.
89
+
90
+ ```typescript
91
+ const decision = await pg.assertLicensed(subjectCode, {
92
+ promptHash, generationId: jobId, modality: "video", model: modelName,
93
+ intendedUse: { useCase: "commercial", rights: ["output_generation"] },
94
+ });
95
+ // Apply the decision's obligations, generate, then submit a receipt.
96
+ ```
97
+
98
+ ## Check answers against PRE-GEN (recommended)
99
+
100
+ ```typescript
101
+ import { Prampta, loadPregenDirectory } from "@prampta/sdk";
102
+
103
+ const pregenDirectory = await loadPregenDirectory({ previous: saved }); // steward key built in
104
+ saved = pregenDirectory.toJSON(); // keep it for next start
105
+
106
+ const registry = new Prampta({ baseUrl: "https://api2.prampta.com", providerId, token, licenseeId,
107
+ pregenDirectory });
108
+ ```
109
+
110
+ With `pregenDirectory` the SDK refuses to send a PG code to a registry that
111
+ does not own its namespace, and on every `allow` runs `checkDecision` from
112
+ [`@pregen/verify`](https://www.npmjs.com/package/@pregen/verify): the signing
113
+ key must be one the signed PRE-GEN directory lists for the license's
114
+ namespace, and the origin registry's keys are pinned in that library, so a
115
+ stolen steward key cannot add its own. The directory also refuses an older or
116
+ shrunk copy of itself when you pass the previous one back.
117
+
118
+ For CI without a secret, `createSimulator()` from `@pregen/verify` 0.4.0 is an
119
+ offline sandbox registry: pass `baseUrl: sim.baseUrl`, `operatorPublicKeyHex:
120
+ sim.operatorPublicKeyHex`, `pregenDirectory: sim.directory` and use `sim.fetch`.
121
+
122
+ ## Optional namespace trust with your own root (0.7.0)
123
+
124
+ ```typescript
125
+ import { TrustedRegistryDirectory } from "@prampta/sdk";
126
+ const directory = await TrustedRegistryDirectory.verify(signedDirectory, {
127
+ stewardPublicKeyHex: independentlyObtainedStewardKey,
128
+ previousSnapshot: savedState, // { sequence, hash }, loaded from durable storage
129
+ });
130
+ // Atomically persist { sequence: directory.sequence, hash: directory.snapshotHash }
131
+ // after acceptance, without letting concurrent updates overwrite newer state.
132
+ const registry = new Prampta({
133
+ baseUrl: registryApi, providerId, token: serverSideCredential, licenseeId,
134
+ operatorPublicKeyHex: independentlyObtainedOperatorKey,
135
+ registryDirectory: directory,
136
+ });
137
+ ```
138
+
139
+ This opt-in validates the signed `pregen.registries.v2` directory, requires
140
+ HTTPS registry URLs, rejects ambiguous shared signing keys/endpoints, checks
141
+ the target namespace before sending credentials, and checks a returned
142
+ license's issuer. Origin and prefixed registries get the same checks. Local
143
+ subject ids remain supported at an explicitly listed registry endpoint.
144
+ Authenticated calls do not follow redirects. No automatic multi-registry
145
+ credential sharing or onboarding is provided.
146
+ Persisted `previousSnapshot` detects rollback and signed different-content
147
+ snapshots at the same sequence. `minSequence` is also available for a sequence
148
+ floor alone. Storage and concurrent update serialization belong to the host.
149
+
150
+ The public steward key is still pending: do not obtain a trust anchor from
151
+ the directory itself or invent a production key. An authenticated directory
152
+ snapshot does not prove freshness, key non-revocation or rights-holder consent;
153
+ `minSequence` alone does not prevent stale first-boot snapshots. Full live
154
+ federation needs the separately specified trust-management work.
155
+
156
+ `verifyLicenseSignatures()` checks the subject signature and the operator
157
+ countersignature over body + subject signature + license id offline. Obtain
158
+ keys independently, then verify namespace, authority, status and scope separately.
159
+ Two valid signatures under managed custody are not two independent consents.
160
+
161
+ Security fixes also reject empty request bindings, mismatched generation ids,
162
+ unsupported schemas, contradictory outcomes, missing/invalid expiry and
163
+ non-integer or unsafe signed JSON numbers. Optional advisory fields remain
164
+ signed. An asserted end user cannot silently receive an unbound `allow`.
165
+ Pass a known `providerIdentityLinkId` for exact link matching; resolution of
166
+ `providerUserId` alone still relies on the registry's user-to-link mapping.
167
+ Existing valid signed bodies are unchanged; malformed replies that
168
+ previously passed are intentionally refused. Revalidate against your registry
169
+ before rollout. Python source gets matching decision hardening but not this
170
+ directory API; this release does not restore PyPI distribution.
171
+
172
+ ## Signed lifecycle receipts (opt-in)
173
+
174
+ `submitReceipt()` still sends `pg.receipt.v2` by default. A provider with an
175
+ Ed25519 public key registered on its PRAMPTA organization can opt into
176
+ `pg.receipt.v3`, which signs the reported lifecycle moment. Keep the matching
177
+ 32-byte private key in a **server-side secret**, never in browser code:
178
+
179
+ ```typescript
180
+ await pg.submitReceipt(decision, {
181
+ outputHash: outputSha256,
182
+ eventType: "output_accepted", // or preview, output_delivered, output_published
183
+ providerSigningKeyHex: process.env.PRAMPTA_PROVIDER_SIGNING_KEY_HEX,
184
+ });
185
+ ```
186
+
187
+ The SDK signs locally and sends only the signature. A receipt records what the
188
+ provider attests; it does not prove every use was reported, that the output
189
+ complied with rights, or that a payment is due. Do not use an unregistered key:
190
+ the API will reject its signature. Existing v2 integrations are unchanged.
191
+
192
+ ## C2PA Bridge & Anchored Audit Log (PRE-GEN v1.1)
193
+
194
+ ```typescript
195
+ import { verifyMerkleProof } from "@prampta/sdk";
196
+
197
+ // After verifyGeneration() + submitReceipt(), get a signed assertion to
198
+ // embed in your own C2PA manifest — PRAMPTA does not build the manifest.
199
+ const assertion = await pg.createAssertion(decision.decisionId);
200
+
201
+ // Anyone (not just you) can later verify a decision's audit event was
202
+ // included in a published Merkle root, entirely offline:
203
+ const proof = await pg.getInclusionProof(eventId);
204
+ const root = await pg.getMerkleRoot();
205
+ const ok = await verifyMerkleProof(
206
+ proof.event_hash as string,
207
+ proof.proof as any,
208
+ root.root_hash as string,
209
+ );
210
+ ```
211
+
46
212
  ## Security Features
47
213
 
48
214
  - **Ed25519 signature verification** on every decision (via `@noble/ed25519`)
@@ -63,7 +229,7 @@ const pg = new Prampta({
63
229
  baseUrl: "https://api2.prampta.com",
64
230
  providerId: "my-ai-service",
65
231
  licenseeId: "acme-corp",
66
- token: "pair-token",
232
+ token: "your-provider-credential-secret", // NOT a pair token — see Authentication
67
233
  operatorPublicKeyHex: "<pinned key from PRAMPTA docs>",
68
234
  });
69
235
  ```
@@ -82,8 +248,8 @@ const pg = new Prampta({
82
248
  |-----------|---------|----------|-------------|
83
249
  | `baseUrl` | `PRAMPTA_BASE_URL` | Yes | Registry API URL |
84
250
  | `providerId` | `PRAMPTA_PROVIDER_ID` | Yes | Your provider ID |
85
- | `licenseeId` | `PRAMPTA_LICENSEE_ID` | Yes | Licensee ID |
86
- | `token` | `PRAMPTA_TOKEN` | Yes | Pair auth token |
251
+ | `licenseeId` | `PRAMPTA_LICENSEE_ID` | For licensed use | Connected user's PRAMPTA licensee ID; omit for unlicensed personal use |
252
+ | `token` | `PRAMPTA_TOKEN` | Yes | Runtime credential (`ProviderCredential` secret) — see [Authentication](#authentication) |
87
253
  | `operatorPublicKeyHex` | `PRAMPTA_OPERATOR_PUBLIC_KEY` | No | Pinned operator key (recommended for production) |
88
254
  | `timeoutMs` | — | No | Default `3000`. Request timeout in ms. |
89
255
  | `failClosed` | — | No | Default `true`. Deny on any verification error. |
@@ -144,3 +310,57 @@ Same as `verify()` but throws `PramptaRefusalError` if not allowed.
144
310
  ### `pg.health()` / `pg.version()`
145
311
 
146
312
  Registry health check and version info.
313
+
314
+ ## Connect Step 1: reporting without authorization
315
+
316
+ SDK 0.6.0 adds `PramptaReporter` (Node/server only). Check the installed package
317
+ version before using it. Publishing the SDK and deploying the backend are separate
318
+ steps; this API still requires the `/v1/observations` endpoint on your server.
319
+
320
+ ```ts
321
+ import { PramptaReporter } from '@prampta/sdk';
322
+
323
+ const reporter = new PramptaReporter({
324
+ baseUrl: process.env.PRAMPTA_BASE_URL!,
325
+ providerId: process.env.PRAMPTA_PROVIDER_ID!,
326
+ token: process.env.PRAMPTA_TOKEN!,
327
+ outbox: durableOutbox, // Your DB/job-queue adapter, not an in-memory array.
328
+ });
329
+ await reporter.report({
330
+ eventKey: 'job-123:created', outputId: 'job-123', subjectId: 'selected-subject',
331
+ occurredAt: '2026-09-21T10:00:00Z', output: outputBytes,
332
+ });
333
+ // Your persistent worker calls reporter.deliver(savedObservation).
334
+ ```
335
+
336
+ `report` persists via `ObservationOutbox.put`, without contacting PRAMPTA. That
337
+ adapter must atomically accept identical replays and reject changed payloads
338
+ under the same event ID, separately for each provider/environment. The original
339
+ eventKey and occurredAt must be stable across retries. Raw output stays local;
340
+ for large video use outputHash computed by your streaming storage pipeline.
341
+ The SDK does not download URLs. Keep credentials server-side.
342
+
343
+ `deliver` performs one bounded attempt. Acknowledge `accepted`; retain and
344
+ reschedule `retry` (respect retryAfterMs, add exponential backoff and jitter);
345
+ quarantine and alert on `rejected`. Never silently discard failed events.
346
+ Storage errors reject `report`; retry the completion job without regenerating.
347
+ No in-memory background delivery guarantee is implied.
348
+
349
+ No licensee account or Pair is required. This does NOT grant permission, record
350
+ payment, verify content or alter `assertAllowed` behavior. Use the existing
351
+ `matchSubjects` export for local candidate discovery, not automatic claims that
352
+ every name mentioned in a prompt appears in the output. Step 2 remains the
353
+ separate authorization/licensing flow described above.
354
+
355
+ ## Source-only authority experiment (not shipped)
356
+
357
+ The scoped-authority prototype in `src/experimental*` and its tests remain in
358
+ this repository for review. They are non-normative drafts, not part of the
359
+ `@prampta/sdk` 0.7.0 package. The package builds and exports
360
+ only its stable root API: neither `@prampta/sdk/experimental` nor
361
+ `@prampta/sdk/experimental/node` is available to package consumers.
362
+
363
+ See `spec/drafts/` for the proposed formats and limits. The experimental backend
364
+ bridge lives on branch `experimental/authority-control-plane`, not on `main`.
365
+ No production authority issuance, protected signing provision or certification
366
+ is implied by these sources or tests.