@pouchy_ai/world-sdk 0.11.0 → 0.12.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/CHANGELOG.md +23 -0
- package/README.md +6 -5
- package/conformance.mjs +12 -2
- package/dist/index.d.ts +17 -6
- package/dist/index.js +32 -18
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @pouchy_ai/world-sdk
|
|
2
2
|
|
|
3
|
+
## 0.12.0
|
|
4
|
+
|
|
5
|
+
Additive: authoring joins the machine lane, so nothing in an integration needs
|
|
6
|
+
a browser.
|
|
7
|
+
|
|
8
|
+
- `createStoryPackage`, `publishStoryPackage`, `getStoryPackage`,
|
|
9
|
+
`listStoryPackages`, `createWorld`, `publishWorld`, `getWorld` and
|
|
10
|
+
`listWorlds` now go to the `/v1/admin` plane when `adminKey` is set, exactly
|
|
11
|
+
as the reads have since 0.10.0.
|
|
12
|
+
- These admin equivalents have existed since Batch 1. The SDK simply never used
|
|
13
|
+
them, so authoring kept demanding `adminToken` — a Firebase ID token, minted
|
|
14
|
+
by a browser sign-in and good for about an hour — for operations a project
|
|
15
|
+
admin key could always perform.
|
|
16
|
+
- **`conformance.mjs` runs on machine credentials now.** That is what this
|
|
17
|
+
release is for. The one check that proves a project's world plane is wired
|
|
18
|
+
correctly could not run without a person opening a browser first, which is
|
|
19
|
+
precisely what the rest of this SDK stopped requiring in 0.10.0. Set
|
|
20
|
+
`POUCHY_ADMIN_KEY`; `POUCHY_ADMIN_TOKEN` still works if you prefer it.
|
|
21
|
+
- `adminToken` remains supported and unchanged. It is still the only credential
|
|
22
|
+
for the content loop (script drafts, editorial review), where a person is the
|
|
23
|
+
point rather than an obstacle.
|
|
24
|
+
- No method changed shape.
|
|
25
|
+
|
|
3
26
|
## 0.11.0
|
|
4
27
|
|
|
5
28
|
Additive: your backend can tell the world what it already knows. World API 1.8.
|
package/README.md
CHANGED
|
@@ -56,12 +56,13 @@ expires within the hour.
|
|
|
56
56
|
| you hold | you can | you cannot |
|
|
57
57
|
|---|---|---|
|
|
58
58
|
| `secretKey` + `signing` | mint sessions, drive turns, send events | read anything back |
|
|
59
|
-
| `adminKey` (`pak_…`, long-lived) | read the world: overview, state, timeline, turn read-back, metrics, delivery queue | drive a turn, act on the queue,
|
|
60
|
-
| `adminToken` (Firebase ID token, ~1h) | everything above plus
|
|
59
|
+
| `adminKey` (`pak_…`, long-lived) | author story packages and worlds; read the world: overview, state, timeline, turn read-back, metrics, delivery queue, cost | drive a turn, act on the delivery queue, run the content loop |
|
|
60
|
+
| `adminToken` (Firebase ID token, ~1h) | everything above plus the content loop | outlive the hour |
|
|
61
61
|
|
|
62
|
-
A server holds `secretKey` + `signing` + `adminKey` and needs no browser login
|
|
63
|
-
anywhere in its deployment
|
|
64
|
-
person is
|
|
62
|
+
A server holds `secretKey` + `signing` + `adminKey` and needs **no browser login
|
|
63
|
+
anywhere in its deployment** — including `conformance.mjs`, which used to demand
|
|
64
|
+
one. `adminToken` is for a person: the content loop's review step is a human
|
|
65
|
+
decision, which is the point of it rather than an obstacle.
|
|
65
66
|
|
|
66
67
|
```ts
|
|
67
68
|
const world = new PouchyWorldClient({
|
package/conformance.mjs
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
// has to answer AND touch world state, which is the failure mode that matters
|
|
21
21
|
// there (an NPC that chats without the world moving looks fine and is not).
|
|
22
22
|
//
|
|
23
|
-
// POUCHY_PROJECT_ID=…
|
|
23
|
+
// POUCHY_PROJECT_ID=… POUCHY_ADMIN_KEY=… POUCHY_SECRET_KEY=… \
|
|
24
24
|
// POUCHY_SOURCE=… POUCHY_SOURCE_KID=… POUCHY_SOURCE_SECRET=… \
|
|
25
25
|
// POUCHY_AGENT_A=… POUCHY_AGENT_B=… node conformance.mjs [--scenario=drama|npc]
|
|
26
26
|
|
|
@@ -114,7 +114,17 @@ async function check(name, fn) {
|
|
|
114
114
|
|
|
115
115
|
const world = new PouchyWorldClient({
|
|
116
116
|
projectId: env('POUCHY_PROJECT_ID'),
|
|
117
|
-
|
|
117
|
+
// Batch 10: an ADMIN KEY is enough, and is the point.
|
|
118
|
+
//
|
|
119
|
+
// This harness used to demand POUCHY_ADMIN_TOKEN — a Firebase ID token,
|
|
120
|
+
// browser-minted and good for about an hour. So the one check that proves a
|
|
121
|
+
// project's world plane is wired correctly could not run without a person
|
|
122
|
+
// opening a browser first, which is exactly the thing the rest of this SDK
|
|
123
|
+
// stopped requiring. Either credential works now; the key is preferred and
|
|
124
|
+
// needs no human.
|
|
125
|
+
...(process.env.POUCHY_ADMIN_KEY
|
|
126
|
+
? { adminKey: process.env.POUCHY_ADMIN_KEY }
|
|
127
|
+
: { adminToken: env('POUCHY_ADMIN_TOKEN') }),
|
|
118
128
|
secretKey: env('POUCHY_SECRET_KEY'),
|
|
119
129
|
signing: {
|
|
120
130
|
source: env('POUCHY_SOURCE'),
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const WORLD_SDK_VERSION = "0.
|
|
1
|
+
export declare const WORLD_SDK_VERSION = "0.12.0";
|
|
2
2
|
export declare const DEFAULT_BASE_URL = "https://pouchy.ai/v1";
|
|
3
3
|
/** The refusal classes a world call can produce. `unknown` is deliberate: an
|
|
4
4
|
* unrecognized status is never quietly folded into a neighbour. */
|
|
@@ -784,12 +784,23 @@ export declare class PouchyWorldClient {
|
|
|
784
784
|
* dropped — you will hear about it, which is the point. */
|
|
785
785
|
proposedPatches?: readonly Record<string, unknown>[];
|
|
786
786
|
}): Promise<Record<string, unknown>>;
|
|
787
|
-
/** A
|
|
787
|
+
/** A call that BOTH planes serve. Prefers the machine lane when an admin key
|
|
788
|
+
* is present.
|
|
788
789
|
*
|
|
789
|
-
* `ownerPath` and `adminPath` address the same
|
|
790
|
-
* server answers both from one shared
|
|
791
|
-
* by does not change the answer. The admin door drops
|
|
792
|
-
* path because the key already names the project
|
|
790
|
+
* `ownerPath` and `adminPath` address the same operation through two doors,
|
|
791
|
+
* and the server answers both from one shared implementation — so which
|
|
792
|
+
* door you came in by does not change the answer. The admin door drops
|
|
793
|
+
* `projectId` from the path because the key already names the project,
|
|
794
|
+
* which is also what makes it unable to address another project's work.
|
|
795
|
+
*
|
|
796
|
+
* Batch 10 PR-B2.1 widened this from reads to AUTHORING. The admin
|
|
797
|
+
* equivalents for story packages and world definitions have existed since
|
|
798
|
+
* Batch 1; the SDK simply never used them, which left `createStoryPackage`
|
|
799
|
+
* and friends demanding an hour-lived browser token for an operation a
|
|
800
|
+
* machine key could always perform. `conformance.mjs` is the proof: it
|
|
801
|
+
* could not run without a person opening a browser first. */
|
|
802
|
+
private machine;
|
|
803
|
+
/** A world READ through whichever door the caller holds. */
|
|
793
804
|
private read;
|
|
794
805
|
private owner;
|
|
795
806
|
/** The machine lane: Secret Key AND a source signature over the EXACT bytes
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// SIGNING a request the way the server verifies it, and choosing turn ids that
|
|
18
18
|
// make a retry idempotent instead of a second beat.
|
|
19
19
|
import { createHash, createHmac, randomUUID } from 'node:crypto';
|
|
20
|
-
export const WORLD_SDK_VERSION = '0.
|
|
20
|
+
export const WORLD_SDK_VERSION = '0.12.0';
|
|
21
21
|
export const DEFAULT_BASE_URL = 'https://pouchy.ai/v1';
|
|
22
22
|
// ── errors ─────────────────────────────────────────────────────────────────
|
|
23
23
|
/** The refusal classes a world call can produce. `unknown` is deliberate: an
|
|
@@ -244,34 +244,34 @@ export class PouchyWorldClient {
|
|
|
244
244
|
}
|
|
245
245
|
// ── control plane (owner token) ──────────────────────────────────────────
|
|
246
246
|
listStoryPackages() {
|
|
247
|
-
return this.
|
|
247
|
+
return this.machine('GET', `/projects/${this.projectId}/story-packages`, '/admin/story-packages');
|
|
248
248
|
}
|
|
249
249
|
createStoryPackage(content) {
|
|
250
|
-
return this.
|
|
250
|
+
return this.machine('POST', `/projects/${this.projectId}/story-packages`, '/admin/story-packages', content);
|
|
251
251
|
}
|
|
252
252
|
getStoryPackage(packageId) {
|
|
253
|
-
return this.
|
|
253
|
+
return this.machine('GET', `/projects/${this.projectId}/story-packages/${packageId}`, `/admin/story-packages/${packageId}`);
|
|
254
254
|
}
|
|
255
255
|
/** Publish the next IMMUTABLE story revision. Idempotent on content: the
|
|
256
256
|
* same bytes return the existing revision rather than minting a twin. */
|
|
257
257
|
publishStoryPackage(packageId, content) {
|
|
258
|
-
return this.
|
|
258
|
+
return this.machine('PATCH', `/projects/${this.projectId}/story-packages/${packageId}`, `/admin/story-packages/${packageId}`, content);
|
|
259
259
|
}
|
|
260
260
|
listWorlds() {
|
|
261
|
-
return this.
|
|
261
|
+
return this.machine('GET', `/projects/${this.projectId}/environments`, '/admin/environments');
|
|
262
262
|
}
|
|
263
263
|
createWorld(definition) {
|
|
264
|
-
return this.
|
|
264
|
+
return this.machine('POST', `/projects/${this.projectId}/environments`, '/admin/environments', definition);
|
|
265
265
|
}
|
|
266
266
|
getWorld(environmentId) {
|
|
267
|
-
return this.
|
|
267
|
+
return this.machine('GET', `/projects/${this.projectId}/environments/${environmentId}`, `/admin/environments/${environmentId}`);
|
|
268
268
|
}
|
|
269
269
|
/** Publish the next world revision. Existing world INSTANCES keep the
|
|
270
270
|
* revision they were created on — a published change reaches new instances
|
|
271
271
|
* only, which is what keeps a running story from changing runtime or rules
|
|
272
272
|
* underneath its players. */
|
|
273
273
|
publishWorld(environmentId, definition) {
|
|
274
|
-
return this.
|
|
274
|
+
return this.machine('PATCH', `/projects/${this.projectId}/environments/${environmentId}`, `/admin/environments/${environmentId}`, definition);
|
|
275
275
|
}
|
|
276
276
|
getWorldState(environmentId, worldInstanceId) {
|
|
277
277
|
return this.read(`/projects/${this.projectId}/environments/${environmentId}/instances/${worldInstanceId}/state`, `/admin/environments/${environmentId}/instances/${worldInstanceId}/state`);
|
|
@@ -535,19 +535,33 @@ export class PouchyWorldClient {
|
|
|
535
535
|
return this.signed(`/projects/${this.projectId}/events`, body, eventId);
|
|
536
536
|
}
|
|
537
537
|
// ── transport ────────────────────────────────────────────────────────────
|
|
538
|
-
/** A
|
|
538
|
+
/** A call that BOTH planes serve. Prefers the machine lane when an admin key
|
|
539
|
+
* is present.
|
|
539
540
|
*
|
|
540
|
-
* `ownerPath` and `adminPath` address the same
|
|
541
|
-
* server answers both from one shared
|
|
542
|
-
* by does not change the answer. The admin door drops
|
|
543
|
-
* path because the key already names the project
|
|
544
|
-
|
|
541
|
+
* `ownerPath` and `adminPath` address the same operation through two doors,
|
|
542
|
+
* and the server answers both from one shared implementation — so which
|
|
543
|
+
* door you came in by does not change the answer. The admin door drops
|
|
544
|
+
* `projectId` from the path because the key already names the project,
|
|
545
|
+
* which is also what makes it unable to address another project's work.
|
|
546
|
+
*
|
|
547
|
+
* Batch 10 PR-B2.1 widened this from reads to AUTHORING. The admin
|
|
548
|
+
* equivalents for story packages and world definitions have existed since
|
|
549
|
+
* Batch 1; the SDK simply never used them, which left `createStoryPackage`
|
|
550
|
+
* and friends demanding an hour-lived browser token for an operation a
|
|
551
|
+
* machine key could always perform. `conformance.mjs` is the proof: it
|
|
552
|
+
* could not run without a person opening a browser first. */
|
|
553
|
+
async machine(method, ownerPath, adminPath, body, query = '') {
|
|
545
554
|
if (this.adminKey) {
|
|
546
|
-
return this.request(
|
|
547
|
-
headers: { authorization: `Bearer ${this.adminKey}` }
|
|
555
|
+
return this.request(method, `${adminPath}${query}`, {
|
|
556
|
+
headers: { authorization: `Bearer ${this.adminKey}` },
|
|
557
|
+
...(body !== undefined ? { raw: JSON.stringify(body) } : {})
|
|
548
558
|
});
|
|
549
559
|
}
|
|
550
|
-
return this.owner(
|
|
560
|
+
return this.owner(method, `${ownerPath}${query}`, body);
|
|
561
|
+
}
|
|
562
|
+
/** A world READ through whichever door the caller holds. */
|
|
563
|
+
async read(ownerPath, adminPath, query = '') {
|
|
564
|
+
return this.machine('GET', ownerPath, adminPath, undefined, query);
|
|
551
565
|
}
|
|
552
566
|
async owner(method, path, body) {
|
|
553
567
|
if (!this.adminToken) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pouchy_ai/world-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Server-side TypeScript client for Pouchy World \u2014 story packages, world definitions, world sessions, coordinated turns, trusted events, replay verification and script drafts. Node only: it holds a project Secret Key and a source signing key, which never belong in a browser or a mobile app.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|