@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 +233 -13
- package/dist/index.d.mts +318 -19
- package/dist/index.d.ts +318 -19
- package/dist/index.js +667 -52
- package/dist/index.mjs +660 -51
- package/package.json +15 -8
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
|
-
|
|
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: "
|
|
22
|
-
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
|
|
26
|
-
await pg.assertAllowed("
|
|
27
|
-
prompt: "
|
|
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: "
|
|
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("
|
|
34
|
-
prompt: "
|
|
61
|
+
const result = await pg.verify("sbx-allowed", {
|
|
62
|
+
prompt: "A sandbox portrait",
|
|
35
63
|
modality: "image",
|
|
36
|
-
model: "
|
|
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: "
|
|
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` |
|
|
86
|
-
| `token` | `PRAMPTA_TOKEN` | Yes |
|
|
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.
|