@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 +49 -0
- package/README.md +50 -4
- package/conformance.mjs +21 -10
- package/dist/index.d.ts +34 -3
- package/dist/index.js +55 -10
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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.
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
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
|
|
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.
|
|
144
|
-
//
|
|
145
|
-
//
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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",
|