@pouchy_ai/world-sdk 0.18.0 → 0.20.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 +46 -0
- package/README.md +29 -5
- package/conformance.mjs +52 -5
- package/dist/index.d.ts +106 -2
- package/dist/index.js +84 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
# @pouchy_ai/world-sdk
|
|
2
2
|
|
|
3
|
+
## 0.20.0
|
|
4
|
+
|
|
5
|
+
- **Seven methods for routes that already existed.** `getWorldOverview`,
|
|
6
|
+
`getWorldCost`, `listWorldVersions`, `listStoryPackageVersions`,
|
|
7
|
+
`setWorldDisabled`, `setStoryPackageDisabled` (all `adminKey`-capable), and
|
|
8
|
+
`preflightWorld` (owner plane only — no `/admin` mirror).
|
|
9
|
+
- **Two of them the README already promised.** Its credential table listed
|
|
10
|
+
`overview` and `cost` as `adminKey` reads, and there was no method to make
|
|
11
|
+
them with. `getWorldOverview` is the one to reach for first: it carries
|
|
12
|
+
`ready` and the `blockers` behind a false, and unlike `preflightWorld` a
|
|
13
|
+
machine can call it.
|
|
14
|
+
- **`conformance.mjs` no longer fails a correctly-configured project.** Its
|
|
15
|
+
header claimed an admin key was enough; two of its own gates call
|
|
16
|
+
`replayLedgerToEnd` and `createScriptDraft`, whose routes have no `/admin`
|
|
17
|
+
mirror, so on a key alone they threw "needs an owner token". Those gates now
|
|
18
|
+
SKIP with the reason printed, and skips are counted apart from passes rather
|
|
19
|
+
than folded into the ratio.
|
|
20
|
+
- No API change — `WORLD_API_VERSION` stays at 1.24.0. Every route these
|
|
21
|
+
methods call has been served on both planes since Batch 1; only the client
|
|
22
|
+
was missing.
|
|
23
|
+
|
|
24
|
+
## 0.19.0
|
|
25
|
+
|
|
26
|
+
- `getApprovedExport(environmentId, worldInstanceId, draftId, editorialId, exportId)`
|
|
27
|
+
— **read an approved script, body included, from a backend.** With `adminKey`
|
|
28
|
+
set it goes to the `/admin` mirror and needs no browser anywhere in your
|
|
29
|
+
deployment; same mechanism as `getProgress` and `getTurn`.
|
|
30
|
+
- **This closes the loop.** Until now nothing returned an export body: the
|
|
31
|
+
collection listing strips it, so the only response that ever carried a script
|
|
32
|
+
was the POST that minted one. Reading required the create verb and an
|
|
33
|
+
hour-lived browser token, which meant a person had to forward the file.
|
|
34
|
+
- **Every field the call needs is in the `world.script_approved` webhook** —
|
|
35
|
+
`exportId` at the top level, and `environmentId`, `worldInstanceId`,
|
|
36
|
+
`evidenceDraftId`, `editorialDraftId` inside `lineage`. Webhook → read, with
|
|
37
|
+
nobody in the middle. The payload still carries no script.
|
|
38
|
+
- **Creating an export is still a human act** and is deliberately NOT mirrored:
|
|
39
|
+
an export is a reviewer's signature on a specific text. `deriveStoryPackageCandidate`
|
|
40
|
+
is not mirrored either — it leads to published canon.
|
|
41
|
+
- New `ApprovedScriptExportSummary`, and `listApprovedExports` is now typed with
|
|
42
|
+
it instead of `unknown[]`. **The summary carries no `content`** — that is the
|
|
43
|
+
point of the split, and treating one as a full row is now a compile error
|
|
44
|
+
rather than a crash reading `content.exportDigest` off `undefined`.
|
|
45
|
+
- Any miss on the item read — wrong project, environment, instance, draft,
|
|
46
|
+
editorial, or no such id — answers the **same** 404, so refusals cannot be used
|
|
47
|
+
to map which objects exist.
|
|
48
|
+
|
|
3
49
|
## 0.18.0
|
|
4
50
|
|
|
5
51
|
- `createApprovedExport({ version: 2 })` — the V2 production hand-off: a
|
package/README.md
CHANGED
|
@@ -52,7 +52,10 @@ directions a beat could take, then commit the one the player picked. Off unless
|
|
|
52
52
|
the world's published revision declares it; the `envelope` is server-side only.
|
|
53
53
|
|
|
54
54
|
**Reading** — `getWorldState`, `getProgress`, `getTurn`, `listTurns`,
|
|
55
|
-
`listTurnsSince`, `replayLedger`, `replayLedgerToEnd`, `scanConsistency
|
|
55
|
+
`listTurnsSince`, `replayLedger`, `replayLedgerToEnd`, `scanConsistency`,
|
|
56
|
+
`getWorldOverview`, `getWorldCost`, `listWorldVersions`,
|
|
57
|
+
`listStoryPackageVersions`, `setWorldDisabled`, `setStoryPackageDisabled`,
|
|
58
|
+
`preflightWorld`.
|
|
56
59
|
|
|
57
60
|
**Which credential does what.** This matters more than it looks: one of them
|
|
58
61
|
expires within the hour.
|
|
@@ -64,9 +67,22 @@ expires within the hour.
|
|
|
64
67
|
| `adminToken` (Firebase ID token, ~1h) | everything above plus the content loop | outlive the hour |
|
|
65
68
|
|
|
66
69
|
A server holds `secretKey` + `signing` + `adminKey` and needs **no browser login
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
for the runtime loop**: author a story package and a world, mint sessions, drive
|
|
71
|
+
turns, and read everything back. `adminToken` is for a person, and the content
|
|
72
|
+
loop's review step is a human decision — the point of it rather than an obstacle.
|
|
73
|
+
|
|
74
|
+
**Which calls have no `/admin` mirror, and therefore need `adminToken`:** the
|
|
75
|
+
whole content loop (`*ScriptDraft*`, `*EditorialDraft*`, `createApprovedExport`,
|
|
76
|
+
`deriveStoryPackageCandidate`), the delivery-queue ACTIONS (`drainDeliveries`,
|
|
77
|
+
`requeueDelivery`, `rehydrateDelivery`, `resolveDeliveryGap`), `replayLedger`,
|
|
78
|
+
`archiveLedger`, `evaluateWorld`, and `preflightWorld`. Reading an approved
|
|
79
|
+
export back IS mirrored (`getApprovedExport`) — that is how a machine collects
|
|
80
|
+
what a person approved, with the id arriving on the `world.script_approved`
|
|
81
|
+
webhook.
|
|
82
|
+
|
|
83
|
+
`conformance.mjs` follows the same line: on an admin key alone it runs every
|
|
84
|
+
runtime gate and SKIPS the two whose routes are owner-plane, printing why.
|
|
85
|
+
Setting `POUCHY_ADMIN_TOKEN` as well runs all of them.
|
|
70
86
|
|
|
71
87
|
```ts
|
|
72
88
|
const world = new PouchyWorldClient({
|
|
@@ -160,7 +176,7 @@ copies, and `prune` — the one call here that deletes — refuses without
|
|
|
160
176
|
`confirm`, and again unless the archive verifies and has outlived retention.
|
|
161
177
|
|
|
162
178
|
**Production hand-off** — `createApprovedExport`, `listApprovedExports`,
|
|
163
|
-
`deriveStoryPackageCandidate`. An approved editorial draft becomes a versioned
|
|
179
|
+
`getApprovedExport`, `deriveStoryPackageCandidate`. An approved editorial draft becomes a versioned
|
|
164
180
|
approved script export carrying the whole chain: original story package → world
|
|
165
181
|
instance → ledger range → evidence draft → editorial draft → reviewer.
|
|
166
182
|
Idempotent on content, so replaying an export is a no-op.
|
|
@@ -177,6 +193,14 @@ yields two different, individually stable ids. `content` is a union — narrow o
|
|
|
177
193
|
`contractVersion` before reading version-specific fields, and expect an
|
|
178
194
|
unrecognised `version` to be a 400 rather than a quiet fall back to 1.
|
|
179
195
|
|
|
196
|
+
`listApprovedExports` returns **summaries** — identity, provenance, a scene
|
|
197
|
+
count, and no script. `getApprovedExport` returns **one export with its body**,
|
|
198
|
+
and it is the call an unattended backend makes: with `adminKey` set it routes to
|
|
199
|
+
the `/admin` mirror, so no browser is involved anywhere in your deployment.
|
|
200
|
+
Everything it needs arrives in the `world.script_approved` webhook, so the loop
|
|
201
|
+
closes without a person in the middle. Creating an export is deliberately NOT on
|
|
202
|
+
that lane — an export is a reviewer's signature on a specific text.
|
|
203
|
+
|
|
180
204
|
The Story Package candidate is validated and *returned*; publishing it is a
|
|
181
205
|
separate act by a person, and only evidence-origin material becomes canon. It
|
|
182
206
|
takes **version 1 exports only** and refuses a V2 export with 422 — it is the
|
package/conformance.mjs
CHANGED
|
@@ -94,12 +94,26 @@ console.log(`Pouchy World conformance — ${scenario.label} scenario\n`);
|
|
|
94
94
|
const results = [];
|
|
95
95
|
let failed = false;
|
|
96
96
|
|
|
97
|
+
/** Marks a gate that CANNOT run on the credentials in hand.
|
|
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. */
|
|
104
|
+
class NeedsOwnerToken extends Error {}
|
|
105
|
+
|
|
97
106
|
async function check(name, fn) {
|
|
98
107
|
try {
|
|
99
108
|
const detail = await fn();
|
|
100
109
|
results.push({ name, ok: true, detail });
|
|
101
110
|
console.log(`PASS ${name}${detail ? ` — ${detail}` : ''}`);
|
|
102
111
|
} catch (err) {
|
|
112
|
+
if (err instanceof NeedsOwnerToken) {
|
|
113
|
+
results.push({ name, ok: null, detail: err.message });
|
|
114
|
+
console.log(`SKIP ${name} — ${err.message}`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
103
117
|
failed = true;
|
|
104
118
|
const detail =
|
|
105
119
|
err instanceof WorldApiError
|
|
@@ -114,14 +128,21 @@ async function check(name, fn) {
|
|
|
114
128
|
|
|
115
129
|
const world = new PouchyWorldClient({
|
|
116
130
|
projectId: env('POUCHY_PROJECT_ID'),
|
|
117
|
-
// Batch 10: an ADMIN KEY
|
|
131
|
+
// Batch 10: an ADMIN KEY carries the RUNTIME loop, which is the point.
|
|
118
132
|
//
|
|
119
133
|
// This harness used to demand POUCHY_ADMIN_TOKEN — a Firebase ID token,
|
|
120
134
|
// browser-minted and good for about an hour. So the one check that proves a
|
|
121
135
|
// project's world plane is wired correctly could not run without a person
|
|
122
136
|
// opening a browser first, which is exactly the thing the rest of this SDK
|
|
123
|
-
// stopped requiring.
|
|
124
|
-
//
|
|
137
|
+
// stopped requiring.
|
|
138
|
+
//
|
|
139
|
+
// CORRECTED: "either credential works" was too strong, and this file was the
|
|
140
|
+
// proof — two of its own gates call `replayLedgerToEnd` and
|
|
141
|
+
// `createScriptDraft`, whose routes have NO `/admin` mirror. On an admin key
|
|
142
|
+
// 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.
|
|
125
146
|
...(process.env.POUCHY_ADMIN_KEY
|
|
126
147
|
? { adminKey: process.env.POUCHY_ADMIN_KEY }
|
|
127
148
|
: { adminToken: env('POUCHY_ADMIN_TOKEN') }),
|
|
@@ -133,6 +154,15 @@ const world = new PouchyWorldClient({
|
|
|
133
154
|
},
|
|
134
155
|
...(process.env.POUCHY_BASE_URL ? { baseUrl: process.env.POUCHY_BASE_URL } : {})
|
|
135
156
|
});
|
|
157
|
+
/** Throws a SKIP for a gate whose route is owner-plane only. */
|
|
158
|
+
function requireOwnerToken(route) {
|
|
159
|
+
if (!process.env.POUCHY_ADMIN_TOKEN) {
|
|
160
|
+
throw new NeedsOwnerToken(
|
|
161
|
+
`${route} has no /admin mirror — set POUCHY_ADMIN_TOKEN to run this gate`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
136
166
|
const agentA = env('POUCHY_AGENT_A');
|
|
137
167
|
const agentB = env('POUCHY_AGENT_B');
|
|
138
168
|
const stamp = Date.now().toString(36);
|
|
@@ -237,6 +267,7 @@ await check('world state reads back (user projection, no private layer)', async
|
|
|
237
267
|
});
|
|
238
268
|
|
|
239
269
|
await check('replay verifies the ledger against the served state', async () => {
|
|
270
|
+
requireOwnerToken('/ledger/replay');
|
|
240
271
|
const report = await world.replayLedgerToEnd(created.environmentId, instanceId);
|
|
241
272
|
if (report.verdict !== 'consistent') {
|
|
242
273
|
throw new Error(`${report.verdict}${report.detail ? `: ${report.detail}` : ''}`);
|
|
@@ -246,6 +277,7 @@ await check('replay verifies the ledger against the served state', async () => {
|
|
|
246
277
|
});
|
|
247
278
|
|
|
248
279
|
await check('a script draft generates and refuses export before review', async () => {
|
|
280
|
+
requireOwnerToken('/script-drafts');
|
|
249
281
|
const draft = await world.createScriptDraft(created.environmentId, instanceId);
|
|
250
282
|
if (draft.content?.humanReviewRequired !== true) {
|
|
251
283
|
throw new Error('draft did not carry humanReviewRequired');
|
|
@@ -284,9 +316,24 @@ await check('a WRONG signature is refused (the second proof is real)', async ()
|
|
|
284
316
|
});
|
|
285
317
|
|
|
286
318
|
console.log('');
|
|
287
|
-
|
|
319
|
+
// SKIPPED is counted apart from PASSED, never folded into a ratio. A gate that
|
|
320
|
+
// could not run is not a gate that passed, and "8/10 passed" would read as two
|
|
321
|
+
// failures — the opposite of what happened.
|
|
322
|
+
const passed = results.filter((r) => r.ok === true).length;
|
|
323
|
+
const skipped = results.filter((r) => r.ok === null).length;
|
|
324
|
+
const ran = results.length - skipped;
|
|
325
|
+
console.log(
|
|
326
|
+
`${passed}/${ran} checks passed` + (skipped ? ` · ${skipped} skipped (no owner token)` : '')
|
|
327
|
+
);
|
|
288
328
|
if (failed) {
|
|
289
329
|
console.error('Conformance FAILED — fix the first failure above before writing product code.');
|
|
290
330
|
process.exit(1);
|
|
291
331
|
}
|
|
292
|
-
|
|
332
|
+
if (skipped) {
|
|
333
|
+
console.log(
|
|
334
|
+
`The RUNTIME plane is wired correctly. ${skipped} content-loop gate(s) did not run: ` +
|
|
335
|
+
`their routes are owner-plane only. Set POUCHY_ADMIN_TOKEN to cover them too.`
|
|
336
|
+
);
|
|
337
|
+
} else {
|
|
338
|
+
console.log('Conformance passed. The world plane is wired correctly for this project.');
|
|
339
|
+
}
|
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.20.0";
|
|
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. */
|
|
@@ -643,6 +643,29 @@ export interface ApprovedScriptExportContentV2 {
|
|
|
643
643
|
* reading anything version-specific; a stored row keeps the version it was
|
|
644
644
|
* written with, forever. */
|
|
645
645
|
export type ApprovedScriptExportContent = ApprovedScriptExportContentV1 | ApprovedScriptExportContentV2;
|
|
646
|
+
/** Identity and provenance for every export on an editorial draft, WITHOUT the
|
|
647
|
+
* script. This is what `listApprovedExports` returns.
|
|
648
|
+
*
|
|
649
|
+
* Split from `ApprovedScriptExportRow` deliberately, and the difference is the
|
|
650
|
+
* whole script. Treating a summary as a full row reads `content.exportDigest`
|
|
651
|
+
* off `undefined`; keeping them apart makes that a compile error rather than a
|
|
652
|
+
* crash in front of a user. To get the body, call `getApprovedExport`. */
|
|
653
|
+
export interface ApprovedScriptExportSummary {
|
|
654
|
+
exportId: string;
|
|
655
|
+
draftId: string;
|
|
656
|
+
editorialId: string;
|
|
657
|
+
worldInstanceId: string;
|
|
658
|
+
environmentId: string;
|
|
659
|
+
/** How many scenes the body carries — the shape of what is not here. */
|
|
660
|
+
scenes: number;
|
|
661
|
+
createdAt: string;
|
|
662
|
+
createdBy: string;
|
|
663
|
+
deliveredAt?: string;
|
|
664
|
+
deliveryAttempted?: number;
|
|
665
|
+
deliveryOk?: number;
|
|
666
|
+
approvalTimeSource?: 'stamped' | 'legacy_updated_at';
|
|
667
|
+
approvedContentDigest?: string;
|
|
668
|
+
}
|
|
646
669
|
export interface ApprovedScriptExportRow {
|
|
647
670
|
exportId: string;
|
|
648
671
|
draftId: string;
|
|
@@ -655,6 +678,8 @@ export interface ApprovedScriptExportRow {
|
|
|
655
678
|
deliveredAt?: string;
|
|
656
679
|
deliveryAttempted?: number;
|
|
657
680
|
deliveryOk?: number;
|
|
681
|
+
approvalTimeSource?: 'stamped' | 'legacy_updated_at';
|
|
682
|
+
approvedContentDigest?: string;
|
|
658
683
|
}
|
|
659
684
|
/** What `POST /v1/sessions` actually returns for a WORLD mint.
|
|
660
685
|
*
|
|
@@ -867,6 +892,63 @@ export declare class PouchyWorldClient {
|
|
|
867
892
|
contentHash: string;
|
|
868
893
|
}>;
|
|
869
894
|
getWorld(environmentId: string): Promise<unknown>;
|
|
895
|
+
/** The world's readiness and operational aggregate — what the dashboard's
|
|
896
|
+
* World Overview binds to.
|
|
897
|
+
*
|
|
898
|
+
* This is the call to make BEFORE driving anything: `ready` plus the
|
|
899
|
+
* `blockers` that explain a false, instance counts, and the delivery
|
|
900
|
+
* queue's shape. It was reachable on both planes from the start and simply
|
|
901
|
+
* had no method here, which meant the README's own credential table
|
|
902
|
+
* promised an `adminKey` read the SDK could not perform. */
|
|
903
|
+
getWorldOverview(environmentId: string): Promise<unknown>;
|
|
904
|
+
/** What this world has cost, by month.
|
|
905
|
+
*
|
|
906
|
+
* `months` is clamped server-side; omit it for the default window. */
|
|
907
|
+
getWorldCost(environmentId: string, months?: number): Promise<unknown>;
|
|
908
|
+
/** Every published revision of this world.
|
|
909
|
+
*
|
|
910
|
+
* An instance is pinned to the revision it was created on, so this is how a
|
|
911
|
+
* backend learns which definition a running story is actually playing. */
|
|
912
|
+
listWorldVersions(environmentId: string): Promise<{
|
|
913
|
+
versions: unknown[];
|
|
914
|
+
}>;
|
|
915
|
+
/** Every published revision of a story package.
|
|
916
|
+
*
|
|
917
|
+
* The response carries `hasUnsupported` / `unsupportedCount` as well as the
|
|
918
|
+
* versions: a history this build cannot read whole is reported as PARTIAL
|
|
919
|
+
* rather than passing its newest readable revision off as the newest one.
|
|
920
|
+
* Check that flag before treating the last entry as current. */
|
|
921
|
+
listStoryPackageVersions(packageId: string): Promise<{
|
|
922
|
+
versions: unknown[];
|
|
923
|
+
unsupported?: unknown;
|
|
924
|
+
unsupportedCount?: number;
|
|
925
|
+
hasUnsupported?: boolean;
|
|
926
|
+
}>;
|
|
927
|
+
/** Stop (or restart) a world.
|
|
928
|
+
*
|
|
929
|
+
* A disabled world refuses new sessions and new turns. Existing instances
|
|
930
|
+
* are not deleted and their ledgers are untouched — this is a door being
|
|
931
|
+
* shut, not history being erased, and passing `false` opens it again. */
|
|
932
|
+
setWorldDisabled(environmentId: string, disabled: boolean): Promise<{
|
|
933
|
+
environmentId: string;
|
|
934
|
+
disabled: boolean;
|
|
935
|
+
}>;
|
|
936
|
+
/** Stop (or restart) a story package.
|
|
937
|
+
*
|
|
938
|
+
* Worlds already PINNED to one of its revisions keep running: an instance
|
|
939
|
+
* holds its own revision for life. This stops new pins. */
|
|
940
|
+
setStoryPackageDisabled(packageId: string, disabled: boolean): Promise<{
|
|
941
|
+
packageId: string;
|
|
942
|
+
disabled: boolean;
|
|
943
|
+
}>;
|
|
944
|
+
/** The pre-launch checklist for a world: every reason it would refuse to
|
|
945
|
+
* run, gathered before you try.
|
|
946
|
+
*
|
|
947
|
+
* OWNER TOKEN ONLY — there is no `/admin` mirror for this route, so an
|
|
948
|
+
* unattended backend cannot call it. That is not an oversight to work
|
|
949
|
+
* around: `getWorldOverview` carries `ready` and `blockers` and IS
|
|
950
|
+
* mirrored, so a machine has the readiness answer it needs. */
|
|
951
|
+
preflightWorld(environmentId: string): Promise<unknown>;
|
|
870
952
|
/** Publish the next world revision. Existing world INSTANCES keep the
|
|
871
953
|
* revision they were created on — a published change reaches new instances
|
|
872
954
|
* only, which is what keeps a running story from changing runtime or rules
|
|
@@ -1012,8 +1094,30 @@ export declare class PouchyWorldClient {
|
|
|
1012
1094
|
version?: 1 | 2;
|
|
1013
1095
|
}): Promise<ApprovedScriptExportRow>;
|
|
1014
1096
|
listApprovedExports(environmentId: string, worldInstanceId: string, draftId: string, editorialId: string): Promise<{
|
|
1015
|
-
exports:
|
|
1097
|
+
exports: ApprovedScriptExportSummary[];
|
|
1016
1098
|
}>;
|
|
1099
|
+
/** Read ONE approved export, script included — the machine lane's read.
|
|
1100
|
+
*
|
|
1101
|
+
* This is the call an unattended backend makes. With `adminKey` set it goes
|
|
1102
|
+
* to the `/admin` mirror and needs no browser anywhere in your deployment;
|
|
1103
|
+
* with an `adminToken` it goes to the owner plane. Same mechanism as
|
|
1104
|
+
* `getProgress` and `getTurn`.
|
|
1105
|
+
*
|
|
1106
|
+
* Every field it needs is in the `world.script_approved` webhook you
|
|
1107
|
+
* received: `exportId` at the top level, and `environmentId`,
|
|
1108
|
+
* `worldInstanceId`, `evidenceDraftId` and `editorialDraftId` inside
|
|
1109
|
+
* `lineage`. So the webhook → read loop closes without a person in it.
|
|
1110
|
+
*
|
|
1111
|
+
* It READS. It does not create, approve or re-approve: minting an export is
|
|
1112
|
+
* a reviewer signing off on a specific text, and that stays on the owner
|
|
1113
|
+
* plane with a human behind it. What comes back cannot change — `exportId`
|
|
1114
|
+
* is the content digest of the body it returns.
|
|
1115
|
+
*
|
|
1116
|
+
* Narrow `content.contractVersion` before reading version-specific fields.
|
|
1117
|
+
* A miss of any kind — wrong instance, wrong draft, wrong id — is one 404;
|
|
1118
|
+
* they are not told apart, so a caller cannot map an id space by reading
|
|
1119
|
+
* refusals. */
|
|
1120
|
+
getApprovedExport(environmentId: string, worldInstanceId: string, draftId: string, editorialId: string, exportId: string): Promise<ApprovedScriptExportRow>;
|
|
1017
1121
|
/** The next Story Package as a CANDIDATE — validated, and returned rather
|
|
1018
1122
|
* than published. Publishing it is a separate act by a person, through
|
|
1019
1123
|
* `createStoryPackage`. Only evidence-origin material becomes canon.
|
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.20.0';
|
|
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
|
|
@@ -287,6 +287,65 @@ export class PouchyWorldClient {
|
|
|
287
287
|
getWorld(environmentId) {
|
|
288
288
|
return this.machine('GET', `/projects/${this.projectId}/environments/${environmentId}`, `/admin/environments/${environmentId}`);
|
|
289
289
|
}
|
|
290
|
+
/** The world's readiness and operational aggregate — what the dashboard's
|
|
291
|
+
* World Overview binds to.
|
|
292
|
+
*
|
|
293
|
+
* This is the call to make BEFORE driving anything: `ready` plus the
|
|
294
|
+
* `blockers` that explain a false, instance counts, and the delivery
|
|
295
|
+
* queue's shape. It was reachable on both planes from the start and simply
|
|
296
|
+
* had no method here, which meant the README's own credential table
|
|
297
|
+
* promised an `adminKey` read the SDK could not perform. */
|
|
298
|
+
getWorldOverview(environmentId) {
|
|
299
|
+
return this.machine('GET', `/projects/${this.projectId}/environments/${environmentId}/overview`, `/admin/environments/${environmentId}/overview`);
|
|
300
|
+
}
|
|
301
|
+
/** What this world has cost, by month.
|
|
302
|
+
*
|
|
303
|
+
* `months` is clamped server-side; omit it for the default window. */
|
|
304
|
+
getWorldCost(environmentId, months) {
|
|
305
|
+
const query = months === undefined ? '' : `?months=${encodeURIComponent(String(months))}`;
|
|
306
|
+
return this.machine('GET', `/projects/${this.projectId}/environments/${environmentId}/cost`, `/admin/environments/${environmentId}/cost`, undefined, query);
|
|
307
|
+
}
|
|
308
|
+
/** Every published revision of this world.
|
|
309
|
+
*
|
|
310
|
+
* An instance is pinned to the revision it was created on, so this is how a
|
|
311
|
+
* backend learns which definition a running story is actually playing. */
|
|
312
|
+
listWorldVersions(environmentId) {
|
|
313
|
+
return this.machine('GET', `/projects/${this.projectId}/environments/${environmentId}/versions`, `/admin/environments/${environmentId}/versions`);
|
|
314
|
+
}
|
|
315
|
+
/** Every published revision of a story package.
|
|
316
|
+
*
|
|
317
|
+
* The response carries `hasUnsupported` / `unsupportedCount` as well as the
|
|
318
|
+
* versions: a history this build cannot read whole is reported as PARTIAL
|
|
319
|
+
* rather than passing its newest readable revision off as the newest one.
|
|
320
|
+
* Check that flag before treating the last entry as current. */
|
|
321
|
+
listStoryPackageVersions(packageId) {
|
|
322
|
+
return this.machine('GET', `/projects/${this.projectId}/story-packages/${packageId}/versions`, `/admin/story-packages/${packageId}/versions`);
|
|
323
|
+
}
|
|
324
|
+
/** Stop (or restart) a world.
|
|
325
|
+
*
|
|
326
|
+
* A disabled world refuses new sessions and new turns. Existing instances
|
|
327
|
+
* are not deleted and their ledgers are untouched — this is a door being
|
|
328
|
+
* shut, not history being erased, and passing `false` opens it again. */
|
|
329
|
+
setWorldDisabled(environmentId, disabled) {
|
|
330
|
+
return this.machine('POST', `/projects/${this.projectId}/environments/${environmentId}/disable`, `/admin/environments/${environmentId}/disable`, { disabled });
|
|
331
|
+
}
|
|
332
|
+
/** Stop (or restart) a story package.
|
|
333
|
+
*
|
|
334
|
+
* Worlds already PINNED to one of its revisions keep running: an instance
|
|
335
|
+
* holds its own revision for life. This stops new pins. */
|
|
336
|
+
setStoryPackageDisabled(packageId, disabled) {
|
|
337
|
+
return this.machine('POST', `/projects/${this.projectId}/story-packages/${packageId}/disable`, `/admin/story-packages/${packageId}/disable`, { disabled });
|
|
338
|
+
}
|
|
339
|
+
/** The pre-launch checklist for a world: every reason it would refuse to
|
|
340
|
+
* run, gathered before you try.
|
|
341
|
+
*
|
|
342
|
+
* OWNER TOKEN ONLY — there is no `/admin` mirror for this route, so an
|
|
343
|
+
* unattended backend cannot call it. That is not an oversight to work
|
|
344
|
+
* around: `getWorldOverview` carries `ready` and `blockers` and IS
|
|
345
|
+
* mirrored, so a machine has the readiness answer it needs. */
|
|
346
|
+
preflightWorld(environmentId) {
|
|
347
|
+
return this.owner('GET', `/projects/${this.projectId}/environments/${environmentId}/preflight`);
|
|
348
|
+
}
|
|
290
349
|
/** Publish the next world revision. Existing world INSTANCES keep the
|
|
291
350
|
* revision they were created on — a published change reaches new instances
|
|
292
351
|
* only, which is what keeps a running story from changing runtime or rules
|
|
@@ -475,6 +534,30 @@ export class PouchyWorldClient {
|
|
|
475
534
|
listApprovedExports(environmentId, worldInstanceId, draftId, editorialId) {
|
|
476
535
|
return this.owner('GET', `/projects/${this.projectId}/environments/${environmentId}/instances/${worldInstanceId}/script-drafts/${draftId}/editorial/${editorialId}/approved-export`);
|
|
477
536
|
}
|
|
537
|
+
/** Read ONE approved export, script included — the machine lane's read.
|
|
538
|
+
*
|
|
539
|
+
* This is the call an unattended backend makes. With `adminKey` set it goes
|
|
540
|
+
* to the `/admin` mirror and needs no browser anywhere in your deployment;
|
|
541
|
+
* with an `adminToken` it goes to the owner plane. Same mechanism as
|
|
542
|
+
* `getProgress` and `getTurn`.
|
|
543
|
+
*
|
|
544
|
+
* Every field it needs is in the `world.script_approved` webhook you
|
|
545
|
+
* received: `exportId` at the top level, and `environmentId`,
|
|
546
|
+
* `worldInstanceId`, `evidenceDraftId` and `editorialDraftId` inside
|
|
547
|
+
* `lineage`. So the webhook → read loop closes without a person in it.
|
|
548
|
+
*
|
|
549
|
+
* It READS. It does not create, approve or re-approve: minting an export is
|
|
550
|
+
* a reviewer signing off on a specific text, and that stays on the owner
|
|
551
|
+
* plane with a human behind it. What comes back cannot change — `exportId`
|
|
552
|
+
* is the content digest of the body it returns.
|
|
553
|
+
*
|
|
554
|
+
* Narrow `content.contractVersion` before reading version-specific fields.
|
|
555
|
+
* A miss of any kind — wrong instance, wrong draft, wrong id — is one 404;
|
|
556
|
+
* they are not told apart, so a caller cannot map an id space by reading
|
|
557
|
+
* refusals. */
|
|
558
|
+
getApprovedExport(environmentId, worldInstanceId, draftId, editorialId, exportId) {
|
|
559
|
+
return this.read(`/projects/${this.projectId}/environments/${environmentId}/instances/${worldInstanceId}/script-drafts/${draftId}/editorial/${editorialId}/approved-export/${encodeURIComponent(exportId)}`, `/admin/environments/${environmentId}/instances/${worldInstanceId}/script-drafts/${draftId}/editorial/${editorialId}/approved-export/${encodeURIComponent(exportId)}`);
|
|
560
|
+
}
|
|
478
561
|
/** The next Story Package as a CANDIDATE — validated, and returned rather
|
|
479
562
|
* than published. Publishing it is a separate act by a person, through
|
|
480
563
|
* `createStoryPackage`. Only evidence-origin material becomes canon.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pouchy_ai/world-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.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",
|