@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 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, author |
60
- | `adminToken` (Firebase ID token, ~1h) | everything above plus authoring and the content loop | outlive the hour |
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. `adminToken` is for a person, or for a script a
64
- person is watching.
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=… POUCHY_ADMIN_TOKEN=… POUCHY_SECRET_KEY=… \
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
- adminToken: env('POUCHY_ADMIN_TOKEN'),
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.11.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 world READ. Prefers the machine lane when an admin key is present.
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 read through two doors; the
790
- * server answers both from one shared function, so which door you came in
791
- * by does not change the answer. The admin door drops `projectId` from the
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.11.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.owner('GET', `/projects/${this.projectId}/story-packages`);
247
+ return this.machine('GET', `/projects/${this.projectId}/story-packages`, '/admin/story-packages');
248
248
  }
249
249
  createStoryPackage(content) {
250
- return this.owner('POST', `/projects/${this.projectId}/story-packages`, content);
250
+ return this.machine('POST', `/projects/${this.projectId}/story-packages`, '/admin/story-packages', content);
251
251
  }
252
252
  getStoryPackage(packageId) {
253
- return this.owner('GET', `/projects/${this.projectId}/story-packages/${packageId}`);
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.owner('PATCH', `/projects/${this.projectId}/story-packages/${packageId}`, content);
258
+ return this.machine('PATCH', `/projects/${this.projectId}/story-packages/${packageId}`, `/admin/story-packages/${packageId}`, content);
259
259
  }
260
260
  listWorlds() {
261
- return this.owner('GET', `/projects/${this.projectId}/environments`);
261
+ return this.machine('GET', `/projects/${this.projectId}/environments`, '/admin/environments');
262
262
  }
263
263
  createWorld(definition) {
264
- return this.owner('POST', `/projects/${this.projectId}/environments`, definition);
264
+ return this.machine('POST', `/projects/${this.projectId}/environments`, '/admin/environments', definition);
265
265
  }
266
266
  getWorld(environmentId) {
267
- return this.owner('GET', `/projects/${this.projectId}/environments/${environmentId}`);
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.owner('PATCH', `/projects/${this.projectId}/environments/${environmentId}`, definition);
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 world READ. Prefers the machine lane when an admin key is present.
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 read through two doors; the
541
- * server answers both from one shared function, so which door you came in
542
- * by does not change the answer. The admin door drops `projectId` from the
543
- * path because the key already names the project. */
544
- async read(ownerPath, adminPath, query = '') {
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('GET', `${adminPath}${query}`, {
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('GET', `${ownerPath}${query}`);
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.11.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",