@pouchy_ai/world-sdk 0.8.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.
@@ -0,0 +1,631 @@
1
+ export declare const WORLD_SDK_VERSION = "0.1.0";
2
+ export declare const DEFAULT_BASE_URL = "https://pouchy.ai/v1";
3
+ /** The refusal classes a world call can produce. `unknown` is deliberate: an
4
+ * unrecognized status is never quietly folded into a neighbour. */
5
+ export declare const WORLD_ERROR_CODES: readonly ["unauthorized", "forbidden", "not_found", "conflict", "unprocessable", "rate_limited", "payload_too_large", "server_error", "network", "unknown"];
6
+ export type WorldErrorCode = (typeof WORLD_ERROR_CODES)[number];
7
+ export declare class WorldApiError extends Error {
8
+ readonly code: WorldErrorCode;
9
+ readonly status: number;
10
+ /** The server's own message, when it sent one. */
11
+ readonly detail?: string;
12
+ readonly retryAfterSec?: number;
13
+ constructor(input: {
14
+ code: WorldErrorCode;
15
+ status: number;
16
+ message: string;
17
+ detail?: string;
18
+ retryAfterSec?: number;
19
+ });
20
+ /** Worth trying again with the SAME idempotency key. A 409 is not: it means
21
+ * the world disagreed with the request, and repeating it will disagree
22
+ * again. */
23
+ get retryable(): boolean;
24
+ }
25
+ export declare const SOURCE_SIGNATURE_HEADER = "X-Pouchy-Source-Signature";
26
+ /** Build the `X-Pouchy-Source-Signature` header for one request body.
27
+ *
28
+ * The canonical string is five newline-joined lines — scheme, unix seconds,
29
+ * the declared source, the event/turn id, and the sha256 of the EXACT body
30
+ * bytes. Sign at SEND time, every attempt: a legitimate retry of the same id
31
+ * days later carries a fresh timestamp and passes the ±5 minute skew, because
32
+ * the signature proves origin and never doubles as a dedupe key.
33
+ *
34
+ * Pass the same string you will actually send as the body. Serializing twice
35
+ * (once to sign, once to send) is the classic way to sign bytes you did not
36
+ * send. */
37
+ export declare function signSourceRequest(input: {
38
+ source: string;
39
+ /** eventId for `/events`, turnId for the turns door — the id slot. */
40
+ id: string;
41
+ body: string;
42
+ keyId: string;
43
+ secret: string;
44
+ nowMs?: number;
45
+ }): string;
46
+ /** A fresh turn id for a NEW beat. Retrying a beat means re-sending the SAME
47
+ * id — that is what makes the retry free of a second commit, a second model
48
+ * call and a second message. Generate once, store it with whatever you are
49
+ * about to do, and reuse it until you get an answer. */
50
+ export declare function newTurnId(prefix?: string): string;
51
+ /** `evt:` is the platform's own namespace for turns derived from trusted
52
+ * events; the turns door refuses caller ids in it. */
53
+ export declare function isReservedTurnId(turnId: string): boolean;
54
+ export type WorldCompletionStatus = 'complete' | 'partial' | 'duplicate' | 'conflict' | 'rejected' | 'refused';
55
+ export type WorldExecutionStatus = 'committed' | 'not_committed';
56
+ export type WorldDeliveryStatus = 'delivered' | 'pending' | 'none';
57
+ export interface WorldTurnResult {
58
+ turnId: string;
59
+ worldInstanceId: string;
60
+ beforeStateRevision: number;
61
+ afterStateRevision: number;
62
+ selectedRoles: string[];
63
+ roleMessages: Array<{
64
+ roleId: string;
65
+ message: string;
66
+ fallback?: true;
67
+ }>;
68
+ committedStateDiff: unknown[];
69
+ rejectedEffects: Array<{
70
+ index: number;
71
+ reason: string;
72
+ roleId?: string;
73
+ kind?: string;
74
+ /** Batch 7 — WHY, as a code. `narrative_conflict` is the only one that
75
+ * describes content: two characters wanted incompatible things and the
76
+ * story rule settled it. Every other code is a DEFECT in the story
77
+ * package, the prompt or the integration, and should be treated as one
78
+ * rather than as colour. Absent on turns committed before the taxonomy —
79
+ * read that as unknown, never as narrative. */
80
+ code?: WorldRejectionCode;
81
+ }>;
82
+ skippedRoles: Array<{
83
+ roleId: string;
84
+ reason: string;
85
+ }>;
86
+ nextOptions: Array<{
87
+ branchId: string;
88
+ condition: string;
89
+ }>;
90
+ repairs?: Array<{
91
+ roleId: string;
92
+ outcome: 'repaired' | 'fallback';
93
+ reason?: string;
94
+ }>;
95
+ traceId?: string;
96
+ executionStatus: WorldExecutionStatus;
97
+ deliveryStatus: WorldDeliveryStatus;
98
+ completionStatus: WorldCompletionStatus;
99
+ }
100
+ /** Read a turn result without guessing. The two questions are separate: did
101
+ * the world move, and did the audience hear about it. */
102
+ /** Read a delivery row without guessing what its status means. `blocking` is
103
+ * the field that matters most: PR-A delivers a session's beats in order, so a
104
+ * blocking row is not one failure — it is a reader who has stopped receiving
105
+ * the story until someone acts. */
106
+ /** Why a redacted dead letter could not be rebuilt. A closed vocabulary: an
107
+ * operator deciding whether to resolve a gap is making a judgement call, and a
108
+ * free-text reason is one they cannot compare across incidents. */
109
+ export type WorldRehydrateFailure = 'source_missing' | 'archive_unavailable' | 'hash_mismatch' | 'ledger_drift' | 'identity_mismatch';
110
+ /** What to DO about a stuck delivery, in one call. The three answers are the
111
+ * three verbs, in the order they should be tried: re-send what is still there,
112
+ * rebuild it from the record, or tell the reader the beat is gone. */
113
+ export declare function describeDeliveryResolution(row: WorldDeliveryOpsRow): {
114
+ action: 'none' | 'requeue' | 'rehydrate' | 'resolve_gap' | 'wait';
115
+ blocking: boolean;
116
+ summary: string;
117
+ };
118
+ export declare function describeDelivery(row: WorldDeliveryOpsRow): {
119
+ state: 'landed' | 'in_flight' | 'waiting' | 'stuck';
120
+ blocking: boolean;
121
+ summary: string;
122
+ };
123
+ export declare function describeTurn(result: WorldTurnResult): {
124
+ worldMoved: boolean;
125
+ audienceHeard: boolean;
126
+ /** Sending the SAME turnId again is safe and correct. */
127
+ shouldRetrySameTurn: boolean;
128
+ /** The request itself needs changing before it can succeed. */
129
+ needsDifferentRequest: boolean;
130
+ summary: string;
131
+ };
132
+ export interface WorldReplayReport {
133
+ worldInstanceId: string;
134
+ verdict: 'consistent' | 'drift' | 'missing_entries' | 'illegal_patch' | 'incomplete' | 'unavailable';
135
+ detail?: string;
136
+ fromRevision: number;
137
+ rebuiltRevision: number;
138
+ rebuiltHash: string;
139
+ currentRevision: number | null;
140
+ currentHash: string | null;
141
+ entriesScanned: number;
142
+ cursor?: string;
143
+ checkpointWritten?: number;
144
+ saturated: boolean;
145
+ elapsedMs: number;
146
+ dryRun: true;
147
+ }
148
+ export interface ScriptDraftRow {
149
+ draftId: string;
150
+ jobId: string;
151
+ worldInstanceId: string;
152
+ environmentId: string;
153
+ content: Record<string, unknown>;
154
+ status: 'draft' | 'reviewed';
155
+ createdAt: string;
156
+ createdBy: string;
157
+ reviewedAt?: string;
158
+ reviewedBy?: string;
159
+ reviewNote?: string;
160
+ }
161
+ /** The EDITORIAL layer: a model's reading of an evidence draft, and the human
162
+ * review state attached to it. `suggestionRatio` is the share of lines the
163
+ * server could NOT match to a committed line — read it before you read the
164
+ * prose. */
165
+ export interface EditorialDraftRow {
166
+ editorialId: string;
167
+ draftId: string;
168
+ worldInstanceId: string;
169
+ environmentId: string;
170
+ content: {
171
+ contractVersion: number;
172
+ title: string;
173
+ scenes: Array<Record<string, unknown>>;
174
+ provenance: Record<string, unknown>;
175
+ suggestionRatio: number;
176
+ humanReviewRequired: true;
177
+ };
178
+ status: 'draft' | 'under_review' | 'changes_requested' | 'approved' | 'rejected' | 'exported';
179
+ sceneDecisions?: Record<string, Record<string, unknown>>;
180
+ createdAt: string;
181
+ createdBy: string;
182
+ updatedAt?: string;
183
+ statusBy?: string;
184
+ statusNote?: string;
185
+ exportedAt?: string;
186
+ }
187
+ /** Operator metrics for one world instance. Two families, kept apart on
188
+ * purpose: one word answering both is how a delivery outage reads as a
189
+ * content problem. */
190
+ /** The shared refusal taxonomy (Batch 7). Only `narrative_conflict` is content. */
191
+ export type WorldRejectionCode = 'narrative_conflict' | 'policy_rejection' | 'schema_rejection' | 'canon_rejection' | 'concurrency_rejection' | 'repair_failure';
192
+ /** True for every code except `narrative_conflict`. */
193
+ export declare function isDefectRejection(code: WorldRejectionCode | undefined): boolean;
194
+ export interface WorldInstanceMetrics {
195
+ worldInstanceId: string;
196
+ generatedAt: number;
197
+ stateRevision: number | null;
198
+ delivery: {
199
+ byStatus: Record<string, number>;
200
+ retried: number;
201
+ attemptsTotal: number;
202
+ dead: number;
203
+ oldestUndeliveredAgeMs: number | null;
204
+ /** Sessions whose oldest unlanded line cannot move. Delivery is ordered
205
+ * per session, so this is the number of readers currently missing a
206
+ * beat — usually a dead letter holding the stream. */
207
+ blockedSessions: number;
208
+ /** Batch 7.1 — receipt growth on a long-running world, and how much of it
209
+ * is collectable. A receipt exists for as long as any legal path can put
210
+ * its delivery back in flight; `eligibleForCleanup` counts the delivered
211
+ * rows past retention, which is the only population whose receipts go. */
212
+ receiptCount: number;
213
+ eligibleForCleanup: number;
214
+ latencyP50Ms: number | null;
215
+ latencyP95Ms: number | null;
216
+ scanned: number;
217
+ truncated: boolean;
218
+ };
219
+ turns: {
220
+ turns: number;
221
+ effectsProposed: number;
222
+ effectsAccepted: number;
223
+ effectsRejected: number;
224
+ effectRejectionRate: number;
225
+ /** Batch 7 — refusals split by the shared taxonomy. Judge a world by
226
+ * these, not by the aggregate above: the aggregate mixes drama with
227
+ * defects. */
228
+ rejectionsByCode: Record<WorldRejectionCode, number>;
229
+ narrativeConflictRate: number;
230
+ policyRejectionRate: number;
231
+ schemaRejectionRate: number;
232
+ canonRejectionRate: number;
233
+ concurrencyConflictRate: number;
234
+ repairAttemptRate: number;
235
+ repairSuccessRate: number;
236
+ neutralFallbackRate: number;
237
+ turnsWithRejectedEffects: number;
238
+ repairTurnRate: number;
239
+ linesDelivered: number;
240
+ fallbackLines: number;
241
+ fallbackRate: number;
242
+ linesByRole: Record<string, number>;
243
+ rejectionsByRole: Record<string, number>;
244
+ scanned: number;
245
+ truncated: boolean;
246
+ };
247
+ archive: {
248
+ floor: number;
249
+ prunedThrough: number;
250
+ };
251
+ }
252
+ /** A quality suite's report. `notApplicable` metrics are excluded from
253
+ * `passRate` — a metric with no data is a finding, not a pass. */
254
+ export interface WorldEvalReport {
255
+ contractVersion: number;
256
+ suite: 'drama' | 'npc';
257
+ worldInstanceId: string;
258
+ turns: number;
259
+ metrics: Array<{
260
+ id: string;
261
+ label: string;
262
+ direction: 'higher' | 'lower' | 'exact' | 'band';
263
+ value: number;
264
+ threshold: number;
265
+ thresholdMax?: number;
266
+ pass: boolean;
267
+ notApplicable?: boolean;
268
+ detail: string;
269
+ }>;
270
+ passed: number;
271
+ failed: number;
272
+ notApplicable: number;
273
+ passRate: number;
274
+ ok: boolean;
275
+ }
276
+ /** One row of the operator queue view. No message text, no signing material,
277
+ * no payload hash — a queue question, not a transcript. */
278
+ export interface WorldDeliveryOpsRow {
279
+ deliveryId: string;
280
+ turnId: string;
281
+ roleId: string;
282
+ uid: string;
283
+ sessionId: string;
284
+ ledgerSeq?: number;
285
+ sequence?: number;
286
+ status: 'pending' | 'delivering' | 'delivered' | 'retry_wait' | 'dead' | 'rehydrating' | 'unrecoverable' | 'resolved_gap';
287
+ attempts: number;
288
+ ageMs: number;
289
+ nextAttemptAt: number;
290
+ createdAt: number;
291
+ deliveredAt?: number;
292
+ lastErrorClass?: string;
293
+ payloadRedactedAt?: number;
294
+ requeueCount?: number;
295
+ rehydratedAt?: number;
296
+ rehydratedFrom?: 'ledger' | 'archive';
297
+ unrecoverableReason?: WorldRehydrateFailure;
298
+ resolvedGapAt?: number;
299
+ replacesDeliveryId?: string;
300
+ /** How many times the settled row's atomic cleanup has failed, and when it
301
+ * crossed the threshold at which it becomes something to look at. Present
302
+ * only once a cleanup has actually failed — an ordinary row omits both.
303
+ * They are cleanup bookkeeping, never a delivery status: a row carrying
304
+ * them is still `delivered` and still terminal. A persistently non-zero
305
+ * `cleanupRetryCount` across a session means its receipt store is refusing
306
+ * writes and the receipts are not shrinking. */
307
+ cleanupRetryCount?: number;
308
+ cleanupStalledAt?: number;
309
+ /** This row is the oldest unlanded line of its session AND cannot move — so
310
+ * it is why that reader is waiting. */
311
+ blocking: boolean;
312
+ }
313
+ export interface WorldDeliveryPage {
314
+ rows: WorldDeliveryOpsRow[];
315
+ cursor?: string;
316
+ truncated: boolean;
317
+ scanned: number;
318
+ }
319
+ export interface WorldDeliveryDetail extends WorldDeliveryOpsRow {
320
+ channel: string;
321
+ payloadType: string;
322
+ /** Only when `includePayload` was passed — and that read was audited. */
323
+ payload?: {
324
+ text: string;
325
+ userText: string;
326
+ };
327
+ }
328
+ export interface WorldDrainReport {
329
+ scanned: number;
330
+ claimed: number;
331
+ delivered: number;
332
+ failed: number;
333
+ dead: number;
334
+ /** Sessions still owing their oldest line after this pass. */
335
+ blocked: number;
336
+ saturated: boolean;
337
+ }
338
+ /** The production hand-off (Batch 7). Every line keeps the origin the SERVER
339
+ * verified: `evidence` with the turn it came from, or `suggestion` because
340
+ * nothing committed matched it — whatever the model claimed. A suggestion is
341
+ * never laundered into a fact downstream. */
342
+ export interface ApprovedScriptExportRow {
343
+ exportId: string;
344
+ draftId: string;
345
+ editorialId: string;
346
+ worldInstanceId: string;
347
+ environmentId: string;
348
+ content: {
349
+ contractVersion: number;
350
+ exportId: string;
351
+ title: string;
352
+ lineage: Record<string, unknown>;
353
+ review: {
354
+ reviewer: string;
355
+ approvedAt: string;
356
+ sceneDecisions: Record<string, unknown>;
357
+ };
358
+ characters: Array<{
359
+ roleId: string;
360
+ storyRoleId?: string;
361
+ lines: number;
362
+ }>;
363
+ scenes: Array<Record<string, unknown>>;
364
+ stateChanges: Array<{
365
+ summary: string;
366
+ sourceTurnId: string;
367
+ }>;
368
+ suggestionRatio: number;
369
+ exportDigest: string;
370
+ };
371
+ createdAt: string;
372
+ createdBy: string;
373
+ deliveredAt?: string;
374
+ deliveryAttempted?: number;
375
+ deliveryOk?: number;
376
+ }
377
+ export interface WorldClientOptions {
378
+ /** The project this client acts for. */
379
+ projectId: string;
380
+ /** An OWNER-plane credential (a signed-in admin's ID token) for the control
381
+ * plane: story packages, world definitions, drafts, replay, reads. */
382
+ adminToken?: string;
383
+ /** A project Secret Key (`pchy_sk_…`) for the MACHINE lane: world sessions,
384
+ * turns and trusted events. It also carries the test/live axis. */
385
+ secretKey?: string;
386
+ /** The event-source signing key pair the turns door and `/events` verify.
387
+ * `source` must be the world's own `providerRef`. */
388
+ signing?: {
389
+ source: string;
390
+ keyId: string;
391
+ secret: string;
392
+ };
393
+ baseUrl?: string;
394
+ fetch?: typeof globalThis.fetch;
395
+ /** Per-request timeout. A coordinated turn is a multi-model act — the
396
+ * default is generous on purpose, and lower is usually wrong. */
397
+ timeoutMs?: number;
398
+ }
399
+ export declare class PouchyWorldClient {
400
+ readonly projectId: string;
401
+ private readonly baseUrl;
402
+ private readonly doFetch;
403
+ private readonly adminToken?;
404
+ private readonly secretKey?;
405
+ private readonly signing?;
406
+ private readonly timeoutMs;
407
+ constructor(options: WorldClientOptions);
408
+ listStoryPackages(): Promise<{
409
+ packages: unknown[];
410
+ }>;
411
+ createStoryPackage(content: unknown): Promise<{
412
+ packageId: string;
413
+ revision: number;
414
+ contentHash: string;
415
+ }>;
416
+ getStoryPackage(packageId: string): Promise<unknown>;
417
+ /** Publish the next IMMUTABLE story revision. Idempotent on content: the
418
+ * same bytes return the existing revision rather than minting a twin. */
419
+ publishStoryPackage(packageId: string, content: unknown): Promise<{
420
+ packageId: string;
421
+ revision: number;
422
+ contentHash: string;
423
+ created: boolean;
424
+ }>;
425
+ listWorlds(): Promise<{
426
+ environments: unknown[];
427
+ }>;
428
+ createWorld(definition: unknown): Promise<{
429
+ environmentId: string;
430
+ environmentRevision: number;
431
+ contentHash: string;
432
+ }>;
433
+ getWorld(environmentId: string): Promise<unknown>;
434
+ /** Publish the next world revision. Existing world INSTANCES keep the
435
+ * revision they were created on — a published change reaches new instances
436
+ * only, which is what keeps a running story from changing runtime or rules
437
+ * underneath its players. */
438
+ publishWorld(environmentId: string, definition: unknown): Promise<{
439
+ environmentId: string;
440
+ environmentRevision: number;
441
+ contentHash: string;
442
+ created: boolean;
443
+ }>;
444
+ getWorldState(environmentId: string, worldInstanceId: string): Promise<{
445
+ worldInstanceId: string;
446
+ environmentRevision: number;
447
+ state: Record<string, unknown>;
448
+ }>;
449
+ /** Read back a COMMITTED turn. The recovery path when a response was lost:
450
+ * it re-runs nothing, and a 404 means the turn never committed. */
451
+ getTurn(environmentId: string, worldInstanceId: string, turnId: string): Promise<Record<string, unknown>>;
452
+ /** Verify that the materialized state is what the committed ledger says.
453
+ * Always a dry run — it reports, it never repairs. Pass the previous
454
+ * report's `cursor` to continue a run that came back `incomplete`. */
455
+ replayLedger(environmentId: string, worldInstanceId: string, options?: {
456
+ cursor?: string;
457
+ maxEntries?: number;
458
+ }): Promise<WorldReplayReport>;
459
+ /** Walk a replay to completion, page by page. Bounded by `maxPages` so a
460
+ * caller can never spin: an unfinished walk returns its last report, and
461
+ * the cursor is in it. */
462
+ replayLedgerToEnd(environmentId: string, worldInstanceId: string, options?: {
463
+ maxPages?: number;
464
+ }): Promise<WorldReplayReport>;
465
+ /** The delivery queue for one instance (Batch 7). Statuses, timings, attempt
466
+ * counts, error classes, and which row is blocking its session — never the
467
+ * message bodies. */
468
+ listDeliveries(environmentId: string, worldInstanceId: string, filters?: {
469
+ status?: 'pending' | 'delivering' | 'delivered' | 'retry_wait' | 'dead';
470
+ sessionId?: string;
471
+ turnId?: string;
472
+ cursor?: string;
473
+ limit?: number;
474
+ }): Promise<WorldDeliveryPage>;
475
+ /** One delivery. `includePayload` returns the line itself and writes an audit
476
+ * row naming you — pass it deliberately, not by default. */
477
+ getDelivery(environmentId: string, worldInstanceId: string, deliveryId: string, options?: {
478
+ includePayload?: boolean;
479
+ }): Promise<WorldDeliveryDetail>;
480
+ /** Run one bounded drain now — for the moment after a requeue. */
481
+ drainDeliveries(environmentId: string, worldInstanceId: string): Promise<WorldDrainReport>;
482
+ /** Put a dead letter back in the queue. There is deliberately no payload
483
+ * parameter: the beat was committed by the coordinator, and supplying a
484
+ * different line here would be authoring world history through the delivery
485
+ * plane. Idempotent — an already-queued row is a no-op. */
486
+ requeueDelivery(environmentId: string, worldInstanceId: string, deliveryId: string): Promise<{
487
+ ok: boolean;
488
+ status?: string;
489
+ reason?: string;
490
+ }>;
491
+ /** Turn an APPROVED editorial draft into a versioned production hand-off
492
+ * (Batch 7). Idempotent on content — the same approved review exports to
493
+ * the same id forever, so replaying one is a no-op rather than a second
494
+ * script. `notify` also sends `world.script_approved` to the project's
495
+ * webhooks, carrying lineage and identifiers but never the script body. */
496
+ createApprovedExport(environmentId: string, worldInstanceId: string, draftId: string, editorialId: string, options?: {
497
+ notify?: boolean;
498
+ }): Promise<ApprovedScriptExportRow>;
499
+ listApprovedExports(environmentId: string, worldInstanceId: string, draftId: string, editorialId: string): Promise<{
500
+ exports: unknown[];
501
+ }>;
502
+ /** The next Story Package as a CANDIDATE — validated, and returned rather
503
+ * than published. Publishing it is a separate act by a person, through
504
+ * `createStoryPackage`. Only evidence-origin material becomes canon. */
505
+ deriveStoryPackageCandidate(environmentId: string, worldInstanceId: string, draftId: string, editorialId: string, exportId: string, options?: {
506
+ name?: string;
507
+ summary?: string;
508
+ }): Promise<{
509
+ exportId: string;
510
+ status: 'candidate';
511
+ publishWith: string;
512
+ droppedSuggestions: number;
513
+ content: Record<string, unknown>;
514
+ }>;
515
+ /** Rebuild a dead letter whose text was redacted, FROM THE RECORD (Batch
516
+ * 7.1). Takes no body — nobody types a replacement line and no model
517
+ * regenerates one. Accepted only if the rebuild re-derives the same delivery
518
+ * id and the same content digest captured before the text was destroyed;
519
+ * otherwise it refuses with a code and the row becomes `unrecoverable`,
520
+ * which still blocks the session. */
521
+ rehydrateDelivery(environmentId: string, worldInstanceId: string, deliveryId: string): Promise<{
522
+ ok: boolean;
523
+ status?: string;
524
+ from?: 'ledger' | 'archive';
525
+ error?: WorldRehydrateFailure;
526
+ }>;
527
+ /** Account for a beat that cannot be recovered. The only exit from
528
+ * `unrecoverable`, and it takes NO text: the notice is generated
529
+ * server-side from a fixed catalogue and rendered in the reader's language.
530
+ * The original resolves when the notice LANDS, not when you call this. */
531
+ resolveDeliveryGap(environmentId: string, worldInstanceId: string, deliveryId: string): Promise<{
532
+ ok: boolean;
533
+ noticeDeliveryId?: string;
534
+ created?: boolean;
535
+ noticeKey?: string;
536
+ }>;
537
+ /** Operator metrics for one instance (Batch 6): delivery and turn families,
538
+ * kept apart because "the lines arrived" and "the world played well" are
539
+ * different questions. */
540
+ getWorldMetrics(environmentId: string, worldInstanceId: string): Promise<WorldInstanceMetrics>;
541
+ /** Score the committed history against a quality suite. Deterministic — no
542
+ * model judges the output, so a score is something you can regress. */
543
+ evaluateWorld(environmentId: string, worldInstanceId: string, options?: {
544
+ suite?: 'drama' | 'npc';
545
+ replayVerdict?: string;
546
+ }): Promise<WorldEvalReport>;
547
+ /** Ledger archival. The default action is `plan`, which writes nothing;
548
+ * `execute` copies and never deletes; `prune` is the only call in this
549
+ * client that destroys a row, and it refuses without `confirm` — and again
550
+ * unless the archive verifies and has outlived its retention. */
551
+ archiveLedger(environmentId: string, worldInstanceId: string, options?: {
552
+ action?: 'plan' | 'execute' | 'verify' | 'prune';
553
+ confirm?: boolean;
554
+ }): Promise<Record<string, unknown>>;
555
+ listScriptDrafts(environmentId: string, worldInstanceId: string): Promise<{
556
+ drafts: unknown[];
557
+ }>;
558
+ /** Generate (or return) the draft for this instance's committed history.
559
+ * Idempotent on the ledger range: the same range is the same draft. */
560
+ createScriptDraft(environmentId: string, worldInstanceId: string): Promise<ScriptDraftRow>;
561
+ getScriptDraft(environmentId: string, worldInstanceId: string, draftId: string): Promise<ScriptDraftRow>;
562
+ /** Mark a draft reviewed. The reviewer is the SIGNED-IN human whose token
563
+ * this client carries — never a field in the body. */
564
+ reviewScriptDraft(environmentId: string, worldInstanceId: string, draftId: string, note?: string): Promise<ScriptDraftRow>;
565
+ /** The reviewed draft as standard JSON. Refuses with 409 until a human has
566
+ * reviewed it — that gate is the product, not an obstacle. */
567
+ exportScriptDraft(environmentId: string, worldInstanceId: string, draftId: string): Promise<Record<string, unknown>>;
568
+ /** The EDITORIAL layer over an evidence draft (Batch 6). The evidence draft
569
+ * is deterministic and never changes; this is a model's reading of it, and
570
+ * every line it claims came from the story was re-checked server-side. */
571
+ listEditorialDrafts(environmentId: string, worldInstanceId: string, draftId: string): Promise<{
572
+ editorials: unknown[];
573
+ }>;
574
+ /** Generate (or return) an editorial reading. Idempotent per model + prompt
575
+ * version + input digest, so asking twice is not a second opinion. */
576
+ createEditorialDraft(environmentId: string, worldInstanceId: string, draftId: string, model?: string): Promise<EditorialDraftRow>;
577
+ getEditorialDraft(environmentId: string, worldInstanceId: string, draftId: string, editorialId: string): Promise<EditorialDraftRow>;
578
+ /** Move an editorial draft through its review lifecycle. `exported` is not
579
+ * settable here — `exportEditorialDraft` writes it. */
580
+ setEditorialStatus(environmentId: string, worldInstanceId: string, draftId: string, editorialId: string, status: 'under_review' | 'changes_requested' | 'approved' | 'rejected', note?: string): Promise<EditorialDraftRow>;
581
+ /** One human verdict on one scene. `edited` carries the editor's own text —
582
+ * the only way text in this layer changes after generation. */
583
+ decideEditorialScene(environmentId: string, worldInstanceId: string, draftId: string, editorialId: string, input: {
584
+ sceneId: string;
585
+ decision: 'accepted' | 'edited' | 'rejected';
586
+ editedText?: string;
587
+ note?: string;
588
+ }): Promise<EditorialDraftRow>;
589
+ /** Export an APPROVED editorial draft. `preview: true` reads the same payload
590
+ * without stamping it exported, so a reviewer can see what would leave. */
591
+ exportEditorialDraft(environmentId: string, worldInstanceId: string, draftId: string, editorialId: string, options?: {
592
+ preview?: boolean;
593
+ }): Promise<Record<string, unknown>>;
594
+ /** Mint a world SESSION for one end user in one role. The returned token is
595
+ * what your own frontend uses with the companion SDK; it is scoped to that
596
+ * instance and role and carries no project credential. */
597
+ createWorldSession(input: {
598
+ environment: string;
599
+ role: string;
600
+ externalUserId: string;
601
+ worldInstance?: string;
602
+ requestId?: string;
603
+ }): Promise<Record<string, unknown>>;
604
+ /** Drive ONE coordinated beat. `turnId` is the idempotency key: re-send the
605
+ * same one to retry, mint a new one for a new beat. */
606
+ runTurn(input: {
607
+ environmentId: string;
608
+ worldInstanceId: string;
609
+ turnId?: string;
610
+ text: string;
611
+ sourceRoleId?: string;
612
+ traceId?: string;
613
+ }): Promise<WorldTurnResult>;
614
+ /** Send a trusted EVENT into a world. On a `coordinated` world this becomes
615
+ * one coordinator turn; on an `actor` world it wakes each subscribed role.
616
+ * Either way `eventId` is the dedupe key — re-send it freely. */
617
+ sendEvent(input: {
618
+ name: string;
619
+ eventId?: string;
620
+ environment: string;
621
+ worldInstance: string;
622
+ data: Record<string, unknown>;
623
+ schemaVersion?: number;
624
+ occurredAt?: number;
625
+ }): Promise<Record<string, unknown>>;
626
+ private owner;
627
+ /** The machine lane: Secret Key AND a source signature over the EXACT bytes
628
+ * being sent — the two proofs the world requires of a backend. */
629
+ private signed;
630
+ private request;
631
+ }