@pouchy_ai/world-sdk 0.31.0 → 0.32.1

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,54 @@
1
1
  # @pouchy_ai/world-sdk
2
2
 
3
+ ## 0.32.1
4
+
5
+ - **Docs only — the eight `proposedPatches` ops are named.** No code change, no
6
+ type change, no method moves. `runTurn` and `sendEvent` have accepted
7
+ `proposedPatches` all along, and neither this package, its README, nor the
8
+ world OpenAPI ever said which ops the closed union holds.
9
+
10
+ An integrator reading this package's `index.d.ts` asked whether there was "an
11
+ entrance that does not depend on a model's judgement" — having searched the
12
+ live spec for `focusNodeId` and found nothing. The entrance was this field and
13
+ `complete_node` was the op they wanted. Publishing an example with one op in it
14
+ is not publishing a union.
15
+
16
+ The list is now in the README with each op's fields, in the field's own doc
17
+ comment (which is what reaches `index.d.ts`), and in the world OpenAPI as of
18
+ world API 1.39.0.
19
+
20
+ ## 0.32.0
21
+
22
+ - **The content-return plane takes a project Secret Key.** Eight methods —
23
+ `listScriptDrafts`, `createScriptDraft`, `getScriptDraft`,
24
+ `exportScriptDraft`, `listEditorialDrafts`, `createEditorialDraft`,
25
+ `getEditorialDraft`, and `exportEditorialDraft({ preview: true })` — now send
26
+ `secretKey` when the client carries one, and fall back to `adminToken`
27
+ otherwise. World API 1.38.0.
28
+
29
+ The rule the server draws, and the reason it is not simply "open the plane":
30
+ **generate and read by machine, decide by human.** Every verb above DERIVES
31
+ material from beats already committed, and none of them writes a name that
32
+ later gates something. The ones that do are unchanged and still owner-only:
33
+ `reviewScriptDraft` (its `reviewedBy` is what the export gate reads),
34
+ `decideEditorialScene` (its verdicts are hashed into the approval digest),
35
+ `setEditorialStatus` (`approved` is what lets an export exist),
36
+ `exportEditorialDraft` WITHOUT `preview` (it stamps the draft exported), the
37
+ whole `approved-export` family, and the draft `DELETE` (which this client
38
+ does not expose at all) — that last one because it is a recursive delete
39
+ reaching approved exports through their parent, so the machine lane stops at a human signature in both directions:
40
+ it may not create one and it may not erase one.
41
+
42
+ No source signature here, unlike the turn and episode doors. Nothing on this
43
+ plane is AUTHORED — a draft is a projection of beats the provider already
44
+ signed for — so a second signature over the request would prove nothing that
45
+ the evidence does not already carry.
46
+
47
+ Asked for by an integrator whose nightly job generated drafts on a token
48
+ that expires in an hour, which meant a person had to be awake for a step
49
+ that reads a ledger. Additive: no method signature changed, and a client
50
+ configured only with `adminToken` behaves exactly as before.
51
+
3
52
  ## 0.31.0
4
53
 
5
54
  - **`WorldStateView`** — `getWorldState().state` is typed, instead of
package/README.md CHANGED
@@ -28,6 +28,7 @@ credential. Drive that with `@pouchy_ai/companion-sdk`.
28
28
  |---|---|---|
29
29
  | owner | a signed-in project admin's ID token | author story packages and worlds; read state, turns, drafts; run replay |
30
30
  | machine | Secret Key **and** `X-Pouchy-Source-Signature` | mint sessions, drive turns, send trusted events |
31
+ | keyed | Secret Key alone, no signature | generate and read script drafts and editorial readings (0.32.0) — nothing on that plane is authored, so there is nothing for a signature to prove |
31
32
 
32
33
  ```ts
33
34
  import { PouchyWorldClient, newTurnId, describeTurn } from '@pouchy_ai/world-sdk';
@@ -65,9 +66,9 @@ expires within the hour.
65
66
 
66
67
  | you hold | you can | you cannot |
67
68
  |---|---|---|
68
- | `secretKey` + `signing` | mint sessions, drive turns, send events | read anything back |
69
+ | `secretKey` + `signing` | mint sessions, drive turns, send events; generate and read script drafts and editorial readings (0.32.0 — the signature is not used for those) | read the world back: state, timeline, metrics; decide anything in the content loop |
69
70
  | `adminKey` (`pchy_admin_…`, 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 |
70
- | `adminToken` (Firebase ID token, ~1h) | everything above plus the content loop | outlive the hour |
71
+ | `adminToken` (Firebase ID token, ~1h) | everything above plus the content loop's DECISIONS | outlive the hour |
71
72
 
72
73
  A server holds `secretKey` + `signing` + `adminKey` and needs **no browser login
73
74
  for the runtime loop**: author a story package and a world, mint sessions, drive
@@ -75,14 +76,35 @@ turns, and read everything back. `adminToken` is for a person, and the content
75
76
  loop's review step is a human decision — the point of it rather than an obstacle.
76
77
 
77
78
  **Which calls have no `/admin` mirror, and therefore need `adminToken`:** the
78
- whole content loop (`*ScriptDraft*`, `*EditorialDraft*`, `createApprovedExport`,
79
- `deriveStoryPackageCandidate`), the delivery-queue ACTIONS (`drainDeliveries`,
79
+ content loop's DECISIONS (below), the delivery-queue ACTIONS (`drainDeliveries`,
80
80
  `requeueDelivery`, `rehydrateDelivery`, `resolveDeliveryGap`), `replayLedger`,
81
81
  `archiveLedger`, `evaluateWorld`, and `preflightWorld`. Reading an approved
82
82
  export back IS mirrored (`getApprovedExport`) — that is how a machine collects
83
83
  what a person approved, with the id arriving on the `world.script_approved`
84
84
  webhook.
85
85
 
86
+ **The content loop splits at the signature (0.32.0 / world API 1.38.0):
87
+ generate and read by machine, decide by human.** A project `secretKey` alone —
88
+ no signature — now works on `listScriptDrafts`, `createScriptDraft`,
89
+ `getScriptDraft`, `exportScriptDraft`, `listEditorialDrafts`,
90
+ `createEditorialDraft`, `getEditorialDraft`, and
91
+ `exportEditorialDraft({ preview: true })`. Every one of those DERIVES material
92
+ from beats already committed, and none of them writes a name that later gates
93
+ something. `adminToken` is still required for the ones that do:
94
+ `reviewScriptDraft` (its reviewer is what the export gate reads),
95
+ `decideEditorialScene` (its verdicts are hashed into the approval digest),
96
+ `setEditorialStatus` (`approved` is what lets an export exist),
97
+ `exportEditorialDraft` WITHOUT `preview` (it stamps the draft exported),
98
+ `createApprovedExport` / `deriveStoryPackageCandidate`, and the draft `DELETE`
99
+ (which this client does not expose at all) — that last one because it is a recursive delete reaching approved exports
100
+ through their parent, so the machine lane stops at a human signature in both
101
+ directions: it may not create one and it may not erase one.
102
+
103
+ So an overnight job can run the whole derivation and a person still signs what
104
+ leaves. The methods fall back to `adminToken` when no `secretKey` is
105
+ configured, so a client that had only the owner token behaves exactly as
106
+ before.
107
+
86
108
  `conformance.mjs` follows the same line: on an admin key alone it runs every
87
109
  runtime gate and SKIPS the two whose routes are owner-plane, printing why.
88
110
  Setting `POUCHY_ADMIN_TOKEN` as well runs all of them.
@@ -118,6 +140,30 @@ await world.runTurn({
118
140
  no single commit for a deterministic write to ride, and sending patches to one
119
141
  answers 422 rather than dropping them quietly.
120
142
 
143
+ **The ops, in full.** A closed union of eight, published in the world OpenAPI
144
+ from 1.39.0 and listed here because an integrator looking for "a way to move the
145
+ story without depending on a model's judgement" had no way to find it:
146
+
147
+ | op | fields | notes |
148
+ |---|---|---|
149
+ | `set_scene` | `sceneId` | must be a declared scene |
150
+ | `advance_clock` | `by` | on top of the coordinator's own tick of 1 |
151
+ | `set_location` | `location` | free text |
152
+ | `set_flag` | `key`, `value` | the value must match the flag's declared `kind` |
153
+ | `reveal_fact` | `factId` | must be a declared fact; append-only |
154
+ | `complete_node` | `nodeId` | refused by name while a prerequisite is unmet |
155
+ | `set_relation` | `between: [a, b]`, `descriptor` | symmetric; both must be declared roles |
156
+ | `set_entity` | `entityId`, `descriptor` | |
157
+
158
+ `note_private` exists in the union but is **not available to a provider** — it
159
+ needs an acting role, and a provider batch has none.
160
+
161
+ Ordered within the array, so a `complete_node` can satisfy a later op's
162
+ prerequisite in the same call. All-or-nothing: one refused op commits nothing and
163
+ the beat answers `rejected` with a per-op reason in `rejectedEffects`. At most 20
164
+ ops. This is the PROVIDER acting as itself — it widens nothing a role may
165
+ propose, and a role's own grant is untouched by it.
166
+
121
167
  **Focusing a beat on some of the cast.** By default every bound role answers a
122
168
  beat (up to the server cap). For an interview or a one-on-one scene, pass
123
169
  `focusRoles` and only those roles run — the rest are not billed and are
package/conformance.mjs CHANGED
@@ -96,11 +96,12 @@ let failed = false;
96
96
 
97
97
  /** Marks a gate that CANNOT run on the credentials in hand.
98
98
  *
99
- * Not a pass and not a failure. Two gates below drive the content loop and
100
- * the ledger replay, and neither route has an `/admin` mirror they are
101
- * owner-plane by design ("creating stays human, reading does not"). Reporting
102
- * them as FAIL would make a correctly-configured project look broken; leaving
103
- * them out silently would make a partial run look complete. */
99
+ * Not a pass and not a failure. ONE gate below the ledger replay — has no
100
+ * `/admin` mirror and no keyed lane; it is owner-plane by design. (The
101
+ * content-loop gate was the second one until world API 1.38.0 gave its
102
+ * derivation steps a Secret-Key lane; it now runs.) Reporting a skip as FAIL
103
+ * would make a correctly-configured project look broken; leaving it out
104
+ * silently would make a partial run look complete. */
104
105
  class NeedsOwnerToken extends Error {}
105
106
 
106
107
  async function check(name, fn) {
@@ -137,12 +138,18 @@ const world = new PouchyWorldClient({
137
138
  // stopped requiring.
138
139
  //
139
140
  // CORRECTED: "either credential works" was too strong, and this file was the
140
- // proof — two of its own gates call `replayLedgerToEnd` and
141
+ // proof — two of its own gates called `replayLedgerToEnd` and
141
142
  // `createScriptDraft`, whose routes have NO `/admin` mirror. On an admin key
142
143
  // alone they threw "needs an owner token", so the harness could not pass a
143
- // correctly-configured project. Those two gates now SKIP with the reason
144
- // printed; every runtime gate runs on the key. Set POUCHY_ADMIN_TOKEN as
145
- // well to run all of them.
144
+ // correctly-configured project. Such gates SKIP with the reason printed;
145
+ // every runtime gate runs on the key. Set POUCHY_ADMIN_TOKEN as well to run
146
+ // all of them.
147
+ //
148
+ // NARROWED to ONE gate by world API 1.38.0 / SDK 0.32.0: the script-draft
149
+ // gate now rides the Secret Key this harness already holds, so `/ledger/replay`
150
+ // is the only remaining skip. The gate it skips is unchanged — the mirror
151
+ // question and the auth question are different questions, and replay still
152
+ // answers "no mirror".
146
153
  ...(process.env.POUCHY_ADMIN_KEY
147
154
  ? { adminKey: process.env.POUCHY_ADMIN_KEY }
148
155
  : { adminToken: env('POUCHY_ADMIN_TOKEN') }),
@@ -276,8 +283,12 @@ await check('replay verifies the ledger against the served state', async () => {
276
283
  return `consistent over ${report.entriesScanned} entr(ies)`;
277
284
  });
278
285
 
286
+ // NO `requireOwnerToken` here since world API 1.38.0 / SDK 0.32.0: generating a
287
+ // draft and reading it back are on the SECRET-KEY lane, which this harness
288
+ // always holds, so the gate runs unattended. What stays owner-only is the
289
+ // REVIEW — which is also why this gate can still prove the refusal below: the
290
+ // export is meant to be refused until a person has read the draft.
279
291
  await check('a script draft generates and refuses export before review', async () => {
280
- requireOwnerToken('/script-drafts');
281
292
  const draft = await world.createScriptDraft(created.environmentId, instanceId);
282
293
  if (draft.content?.humanReviewRequired !== true) {
283
294
  throw new Error('draft did not carry humanReviewRequired');
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * a project is running — which is exactly the field you reach for when a
5
5
  * customer's integration behaves like an older SDK than they say they have.
6
6
  * It sat at '0.1.0' for eight releases before anything compared the two. */
7
- export declare const WORLD_SDK_VERSION = "0.31.0";
7
+ export declare const WORLD_SDK_VERSION = "0.32.1";
8
8
  export declare const DEFAULT_BASE_URL = "https://pouchy.ai/v1";
9
9
  /** One direction a beat could take. The WHOLE of what a deliberation shows a
10
10
  * player: no reasoning, no role secrets, no simulated effects, no scores. */
@@ -1425,7 +1425,9 @@ export declare class PouchyWorldClient {
1425
1425
  createScriptDraft(environmentId: string, worldInstanceId: string): Promise<ScriptDraftRow>;
1426
1426
  getScriptDraft(environmentId: string, worldInstanceId: string, draftId: string): Promise<ScriptDraftRow>;
1427
1427
  /** Mark a draft reviewed. The reviewer is the SIGNED-IN human whose token
1428
- * this client carries — never a field in the body. */
1428
+ * this client carries — never a field in the body, and never a Secret Key:
1429
+ * `reviewedBy` is what the export gate reads, so a machine that could write
1430
+ * it would be approving its own material. */
1429
1431
  reviewScriptDraft(environmentId: string, worldInstanceId: string, draftId: string, note?: string): Promise<ScriptDraftRow>;
1430
1432
  /** The reviewed draft as standard JSON. Refuses with 409 until a human has
1431
1433
  * reviewed it — that gate is the product, not an obstacle. */
@@ -1495,7 +1497,18 @@ export declare class PouchyWorldClient {
1495
1497
  * settled effects, and are validated server-side against the closed
1496
1498
  * StatePatch union and the pinned story package — all-or-nothing, so a
1497
1499
  * batch with one bad op commits nothing. A provider cannot write a
1498
- * role's private notes. */
1500
+ * role's private notes.
1501
+ *
1502
+ * The eight ops a provider may send, published in the world OpenAPI from
1503
+ * 1.39.0 (see the README for each one's fields):
1504
+ *
1505
+ * set_scene · advance_clock · set_location · set_flag · reveal_fact ·
1506
+ * complete_node · set_relation · set_entity
1507
+ *
1508
+ * `complete_node` is the deterministic way to move plot progress, and
1509
+ * the array is ORDERED — one op may satisfy a later op's prerequisite.
1510
+ * At most 20 ops. `note_private` is in the union but refused to a
1511
+ * provider (it needs an acting role). */
1499
1512
  proposedPatches?: readonly Record<string, unknown>[];
1500
1513
  }): Promise<WorldTurnResult>;
1501
1514
  /** Ask for a few PUBLIC directions this beat could take, without taking it.
@@ -1566,6 +1579,24 @@ export declare class PouchyWorldClient {
1566
1579
  /** A world READ through whichever door the caller holds. */
1567
1580
  private read;
1568
1581
  private owner;
1582
+ /** The SCRIPT-DRAFT lane (world API 1.38.0): a project Secret Key alone,
1583
+ * no source signature.
1584
+ *
1585
+ * The rule the server draws here is **generate and read by machine, decide
1586
+ * by human**. Every verb on this lane DERIVES material from beats already
1587
+ * committed — a draft, an editorial reading, a JSON read-back — and none of
1588
+ * them writes a name that later gates something: the reviewer, the scene
1589
+ * decisions hashed into the approval digest, and the approved export all
1590
+ * stay on the owner token. So a backend can run the whole derivation
1591
+ * overnight and a person still signs what leaves.
1592
+ *
1593
+ * No signature, unlike `signed`, because nothing on this lane is AUTHORED:
1594
+ * a draft is a projection of beats the provider already signed for, so a
1595
+ * second signature over the request would prove nothing new.
1596
+ *
1597
+ * Falls back to the owner token when no Secret Key is configured, so a
1598
+ * client that only ever had `adminToken` keeps working unchanged. */
1599
+ private keyed;
1569
1600
  /** The machine lane: Secret Key AND a source signature over the EXACT bytes
1570
1601
  * being sent — the two proofs the world requires of a backend. */
1571
1602
  private signed;
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ import { createHash, createHmac, randomUUID } from 'node:crypto';
23
23
  * a project is running — which is exactly the field you reach for when a
24
24
  * customer's integration behaves like an older SDK than they say they have.
25
25
  * It sat at '0.1.0' for eight releases before anything compared the two. */
26
- export const WORLD_SDK_VERSION = '0.31.0';
26
+ export const WORLD_SDK_VERSION = '0.32.1';
27
27
  export const DEFAULT_BASE_URL = 'https://pouchy.ai/v1';
28
28
  /** Read the commit turn id out of an envelope, so the signature covers the id
29
29
  * the server will commit under. The payload half is base64url JSON; the
@@ -732,40 +732,57 @@ export class PouchyWorldClient {
732
732
  archiveLedger(environmentId, worldInstanceId, options = {}) {
733
733
  return this.owner('POST', `/projects/${this.projectId}/environments/${environmentId}/instances/${worldInstanceId}/ledger/archive`, { action: options.action ?? 'plan', ...(options.confirm ? { confirm: true } : {}) });
734
734
  }
735
+ // ── the content-return plane ─────────────────────────────────────────────
736
+ //
737
+ // TWO LANES since world API 1.38.0: a project **Secret Key** works on every
738
+ // verb that DERIVES material — list/generate/read a draft, list/generate/read
739
+ // an editorial reading, read either one back as JSON, preview an export —
740
+ // and the OWNER TOKEN is still required for every verb that DECIDES:
741
+ // `reviewScriptDraft` (its reviewer gates the export), `decideEditorialScene`
742
+ // (its verdicts are hashed into the approval digest), `setEditorialStatus`
743
+ // (`approved` is what lets an export exist), `exportEditorialDraft` without
744
+ // `preview` (it stamps the draft exported), the whole `approved-export`
745
+ // family, and the draft DELETE — which this client does not expose at all —
746
+ // because it reaches approved exports through their parent). Generate and read by machine, decide by human.
747
+ //
748
+ // The derivation methods fall back to `adminToken` when no `secretKey` is
749
+ // configured, so nothing that worked before needs changing.
735
750
  listScriptDrafts(environmentId, worldInstanceId) {
736
- return this.owner('GET', `/projects/${this.projectId}/environments/${environmentId}/instances/${worldInstanceId}/script-drafts`);
751
+ return this.keyed('GET', `/projects/${this.projectId}/environments/${environmentId}/instances/${worldInstanceId}/script-drafts`);
737
752
  }
738
753
  /** Generate (or return) the draft for this instance's committed history.
739
754
  * Idempotent on the ledger range: the same range is the same draft. */
740
755
  createScriptDraft(environmentId, worldInstanceId) {
741
- return this.owner('POST', `/projects/${this.projectId}/environments/${environmentId}/instances/${worldInstanceId}/script-drafts`, {});
756
+ return this.keyed('POST', `/projects/${this.projectId}/environments/${environmentId}/instances/${worldInstanceId}/script-drafts`, {});
742
757
  }
743
758
  getScriptDraft(environmentId, worldInstanceId, draftId) {
744
- return this.owner('GET', `/projects/${this.projectId}/environments/${environmentId}/instances/${worldInstanceId}/script-drafts/${draftId}`);
759
+ return this.keyed('GET', `/projects/${this.projectId}/environments/${environmentId}/instances/${worldInstanceId}/script-drafts/${draftId}`);
745
760
  }
746
761
  /** Mark a draft reviewed. The reviewer is the SIGNED-IN human whose token
747
- * this client carries — never a field in the body. */
762
+ * this client carries — never a field in the body, and never a Secret Key:
763
+ * `reviewedBy` is what the export gate reads, so a machine that could write
764
+ * it would be approving its own material. */
748
765
  reviewScriptDraft(environmentId, worldInstanceId, draftId, note) {
749
766
  return this.owner('POST', `/projects/${this.projectId}/environments/${environmentId}/instances/${worldInstanceId}/script-drafts/${draftId}/review`, note ? { note } : {});
750
767
  }
751
768
  /** The reviewed draft as standard JSON. Refuses with 409 until a human has
752
769
  * reviewed it — that gate is the product, not an obstacle. */
753
770
  exportScriptDraft(environmentId, worldInstanceId, draftId) {
754
- return this.owner('GET', `/projects/${this.projectId}/environments/${environmentId}/instances/${worldInstanceId}/script-drafts/${draftId}/export`);
771
+ return this.keyed('GET', `/projects/${this.projectId}/environments/${environmentId}/instances/${worldInstanceId}/script-drafts/${draftId}/export`);
755
772
  }
756
773
  /** The EDITORIAL layer over an evidence draft (Batch 6). The evidence draft
757
774
  * is deterministic and never changes; this is a model's reading of it, and
758
775
  * every line it claims came from the story was re-checked server-side. */
759
776
  listEditorialDrafts(environmentId, worldInstanceId, draftId) {
760
- return this.owner('GET', `/projects/${this.projectId}/environments/${environmentId}/instances/${worldInstanceId}/script-drafts/${draftId}/editorial`);
777
+ return this.keyed('GET', `/projects/${this.projectId}/environments/${environmentId}/instances/${worldInstanceId}/script-drafts/${draftId}/editorial`);
761
778
  }
762
779
  /** Generate (or return) an editorial reading. Idempotent per model + prompt
763
780
  * version + input digest, so asking twice is not a second opinion. */
764
781
  createEditorialDraft(environmentId, worldInstanceId, draftId, model) {
765
- return this.owner('POST', `/projects/${this.projectId}/environments/${environmentId}/instances/${worldInstanceId}/script-drafts/${draftId}/editorial`, model ? { model } : {});
782
+ return this.keyed('POST', `/projects/${this.projectId}/environments/${environmentId}/instances/${worldInstanceId}/script-drafts/${draftId}/editorial`, model ? { model } : {});
766
783
  }
767
784
  getEditorialDraft(environmentId, worldInstanceId, draftId, editorialId) {
768
- return this.owner('GET', `/projects/${this.projectId}/environments/${environmentId}/instances/${worldInstanceId}/script-drafts/${draftId}/editorial/${editorialId}`);
785
+ return this.keyed('GET', `/projects/${this.projectId}/environments/${environmentId}/instances/${worldInstanceId}/script-drafts/${draftId}/editorial/${editorialId}`);
769
786
  }
770
787
  /** Move an editorial draft through its review lifecycle. `exported` is not
771
788
  * settable here — `exportEditorialDraft` writes it. */
@@ -781,7 +798,9 @@ export class PouchyWorldClient {
781
798
  * without stamping it exported, so a reviewer can see what would leave. */
782
799
  exportEditorialDraft(environmentId, worldInstanceId, draftId, editorialId, options = {}) {
783
800
  const path = `/projects/${this.projectId}/environments/${environmentId}/instances/${worldInstanceId}/script-drafts/${draftId}/editorial/${editorialId}/export`;
784
- return options.preview ? this.owner('GET', path) : this.owner('POST', path, {});
801
+ // The PREVIEW read is on the two-lane door; the export itself STAMPS the
802
+ // draft exported and stays owner-only.
803
+ return options.preview ? this.keyed('GET', path) : this.owner('POST', path, {});
785
804
  }
786
805
  // ── machine lane (secret key + source signature) ─────────────────────────
787
806
  /** Mint a world SESSION for one end user in one role. The returned token is
@@ -912,6 +931,32 @@ export class PouchyWorldClient {
912
931
  ...(body !== undefined ? { raw: JSON.stringify(body) } : {})
913
932
  });
914
933
  }
934
+ /** The SCRIPT-DRAFT lane (world API 1.38.0): a project Secret Key alone,
935
+ * no source signature.
936
+ *
937
+ * The rule the server draws here is **generate and read by machine, decide
938
+ * by human**. Every verb on this lane DERIVES material from beats already
939
+ * committed — a draft, an editorial reading, a JSON read-back — and none of
940
+ * them writes a name that later gates something: the reviewer, the scene
941
+ * decisions hashed into the approval digest, and the approved export all
942
+ * stay on the owner token. So a backend can run the whole derivation
943
+ * overnight and a person still signs what leaves.
944
+ *
945
+ * No signature, unlike `signed`, because nothing on this lane is AUTHORED:
946
+ * a draft is a projection of beats the provider already signed for, so a
947
+ * second signature over the request would prove nothing new.
948
+ *
949
+ * Falls back to the owner token when no Secret Key is configured, so a
950
+ * client that only ever had `adminToken` keeps working unchanged. */
951
+ async keyed(method, path, body) {
952
+ if (this.secretKey) {
953
+ return this.request(method, path, {
954
+ headers: { authorization: `Bearer ${this.secretKey}` },
955
+ ...(body !== undefined ? { raw: JSON.stringify(body) } : {})
956
+ });
957
+ }
958
+ return this.owner(method, path, body);
959
+ }
915
960
  /** The machine lane: Secret Key AND a source signature over the EXACT bytes
916
961
  * being sent — the two proofs the world requires of a backend. */
917
962
  async signed(path, body, idSlot) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pouchy_ai/world-sdk",
3
- "version": "0.31.0",
3
+ "version": "0.32.1",
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",