@pouchy_ai/world-sdk 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @pouchy_ai/world-sdk
2
2
 
3
+ ## 0.16.0
4
+
5
+ - `scanConsistency()` — what a world's own record proves about itself. Ten
6
+ deterministic findings, each a comparison between committed facts and the
7
+ pinned story package. Read-only: it commits nothing, asks no model, and
8
+ repairs nothing.
9
+ - **`code` is a closed set that may grow.** A published member is never removed,
10
+ renamed, or widened in meaning; a criterion found to be wrong is deprecated
11
+ and replaced beside it. Handle an unknown code as informational rather than
12
+ failing on it — that is what makes a minor bump safe to take.
13
+ - **Read `coverage` before trusting an empty `findings`.** Some checks read
14
+ fields older ledger rows do not carry, and those entries are counted there
15
+ rather than skipped in silence. A clean report with non-empty coverage is
16
+ clean about less than the whole world.
17
+ - `incomplete` is a status, not an empty result: the walk is capped, a `cursor`
18
+ comes back, and the state- and chain-level checks do not run on a partial
19
+ walk. `entriesScanned` stays honest even once `findingsTruncated` is true.
20
+ - Rate limited on two axes (project and IP, 30/min each, both required).
21
+ `status: 'rate_limited'` arrives with HTTP 429 and `retryAfterSec`. If the
22
+ shared limiter is unreachable the scan still runs — a read-only diagnostic
23
+ should not vanish when a world is being investigated.
24
+
3
25
  ## 0.15.0
4
26
 
5
27
  - `getProgress()` — where a story is and whether it can go on, as a DERIVED
package/README.md CHANGED
@@ -52,7 +52,7 @@ 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`.
55
+ `listTurnsSince`, `replayLedger`, `replayLedgerToEnd`, `scanConsistency`.
56
56
 
57
57
  **Which credential does what.** This matters more than it looks: one of them
58
58
  expires within the hour.
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.15.0";
7
+ export declare const WORLD_SDK_VERSION = "0.16.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. */
@@ -641,6 +641,65 @@ export interface WorldClientOptions {
641
641
  * default is generous on purpose, and lower is usually wrong. */
642
642
  timeoutMs?: number;
643
643
  }
644
+ /** What a world's own record proves about itself. World API 1.19.
645
+ *
646
+ * DETERMINISTIC ONLY. Every finding is a comparison between committed facts
647
+ * and the story package the instance pins — never a judgement about writing.
648
+ * Nothing here repairs anything, and nothing here is a suggestion.
649
+ *
650
+ * `code` is a CLOSED set that may GROW. A published member is never removed,
651
+ * renamed, or widened in meaning; a criterion later found to be wrong is
652
+ * deprecated and replaced beside it. So handle an unknown code as
653
+ * informational rather than failing on it — that is what makes a minor bump
654
+ * safe to take. */
655
+ export type WorldConsistencyFindingCode = 'revision_mismatch' | 'ledger_drift' | 'ledger_missing_entries' | 'unrecorded_beat_claim' | 'orphan_branch_reference' | 'node_prereq_violation' | 'effect_without_grant' | 'flag_schema_violation' | 'relation_change_without_cause' | 'verbatim_secret_disclosure';
656
+ export interface WorldConsistencyEvidence {
657
+ turnId?: string;
658
+ seq?: number;
659
+ roleId?: string;
660
+ sceneId?: string;
661
+ nodeId?: string;
662
+ branchId?: string;
663
+ flagKey?: string;
664
+ effectKind?: string;
665
+ /** Only on verbatim matches, and only the matched fragment — never a line. */
666
+ quote?: string;
667
+ /** Passed through from a committed rejection AS EVIDENCE. It is not this
668
+ * finding's classification; the two vocabularies stay separate. */
669
+ ledgerRejectionCode?: string;
670
+ }
671
+ export interface WorldConsistencyFinding {
672
+ code: WorldConsistencyFindingCode;
673
+ layer: 'deterministic';
674
+ status: 'proven';
675
+ severity: 'blocking' | 'warning' | 'info';
676
+ /** Never empty. A finding nobody can check is an accusation. */
677
+ evidence: WorldConsistencyEvidence[];
678
+ }
679
+ /** What a check could NOT examine. Read this before trusting an empty
680
+ * `findings`: a clean report with non-empty coverage is clean about less than
681
+ * the whole world. Absence of a finding is not proof of absence. */
682
+ export interface WorldConsistencyCoverage {
683
+ code: WorldConsistencyFindingCode;
684
+ skipped: number;
685
+ reason: 'evidence_field_absent' | 'scan_incomplete';
686
+ }
687
+ export type WorldConsistencyReport = {
688
+ status: 'complete' | 'incomplete';
689
+ contractVersion: number;
690
+ findings: WorldConsistencyFinding[];
691
+ coverage: WorldConsistencyCoverage[];
692
+ entriesScanned: number;
693
+ findingsTruncated: boolean;
694
+ /** Present only when `status` is `incomplete`. Pass it back to continue. */
695
+ cursor?: number;
696
+ } | {
697
+ status: 'rate_limited';
698
+ retryAfterSec: number;
699
+ } | {
700
+ status: 'unavailable';
701
+ reason: 'world_unavailable';
702
+ };
644
703
  export declare class PouchyWorldClient {
645
704
  readonly projectId: string;
646
705
  private readonly baseUrl;
@@ -692,6 +751,28 @@ export declare class PouchyWorldClient {
692
751
  environmentRevision: number;
693
752
  state: Record<string, unknown>;
694
753
  }>;
754
+ /** What this world's own record proves about itself. World API 1.19.
755
+ *
756
+ * A read-only scan: it commits nothing, asks no model, and repairs nothing.
757
+ * Findings are deterministic — each one is two recorded things that cannot
758
+ * both be true — so they are safe to act on without a human re-reading the
759
+ * story, which is exactly what the model-assisted layer is not.
760
+ *
761
+ * Check `status` first. `incomplete` means the walk was capped or the
762
+ * datastore stopped answering: pass `cursor` back to continue, and do NOT
763
+ * read an empty `findings` on that arm as a clean bill of health.
764
+ *
765
+ * READ `coverage` BEFORE TRUSTING AN EMPTY `findings`. Some checks depend
766
+ * on fields that older ledger rows do not carry, and those entries are
767
+ * counted there rather than skipped in silence. A clean report that could
768
+ * not examine half the history is not the same as a clean world.
769
+ *
770
+ * Rate limited on two axes, project and IP. `status: 'rate_limited'`
771
+ * arrives with HTTP 429 and `retryAfterSec`.
772
+ */
773
+ scanConsistency(environmentId: string, worldInstanceId: string, opts?: {
774
+ cursor?: number;
775
+ }): Promise<WorldConsistencyReport>;
695
776
  /** Where the story is, and whether it can go on. World API 1.18.
696
777
  *
697
778
  * A DERIVED checkpoint: nothing is stored to answer it, no model is asked,
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.15.0';
26
+ export const WORLD_SDK_VERSION = '0.16.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
@@ -297,6 +297,36 @@ export class PouchyWorldClient {
297
297
  getWorldState(environmentId, worldInstanceId) {
298
298
  return this.read(`/projects/${this.projectId}/environments/${environmentId}/instances/${worldInstanceId}/state`, `/admin/environments/${environmentId}/instances/${worldInstanceId}/state`);
299
299
  }
300
+ /** What this world's own record proves about itself. World API 1.19.
301
+ *
302
+ * A read-only scan: it commits nothing, asks no model, and repairs nothing.
303
+ * Findings are deterministic — each one is two recorded things that cannot
304
+ * both be true — so they are safe to act on without a human re-reading the
305
+ * story, which is exactly what the model-assisted layer is not.
306
+ *
307
+ * Check `status` first. `incomplete` means the walk was capped or the
308
+ * datastore stopped answering: pass `cursor` back to continue, and do NOT
309
+ * read an empty `findings` on that arm as a clean bill of health.
310
+ *
311
+ * READ `coverage` BEFORE TRUSTING AN EMPTY `findings`. Some checks depend
312
+ * on fields that older ledger rows do not carry, and those entries are
313
+ * counted there rather than skipped in silence. A clean report that could
314
+ * not examine half the history is not the same as a clean world.
315
+ *
316
+ * Rate limited on two axes, project and IP. `status: 'rate_limited'`
317
+ * arrives with HTTP 429 and `retryAfterSec`.
318
+ */
319
+ scanConsistency(environmentId, worldInstanceId, opts = {}) {
320
+ // The query rides the third argument, not the path template. Both are
321
+ // sent identically; keeping the path a pure template is what lets the
322
+ // admin-lane gate check that every `/admin/...` the SDK sends is a route
323
+ // that actually exists.
324
+ const q = new URLSearchParams();
325
+ if (opts.cursor !== undefined)
326
+ q.set('cursor', String(opts.cursor));
327
+ const suffix = q.toString() ? `?${q}` : '';
328
+ return this.read(`/projects/${this.projectId}/environments/${environmentId}/instances/${worldInstanceId}/consistency`, `/admin/environments/${environmentId}/instances/${worldInstanceId}/consistency`, suffix);
329
+ }
300
330
  /** Where the story is, and whether it can go on. World API 1.18.
301
331
  *
302
332
  * A DERIVED checkpoint: nothing is stored to answer it, no model is asked,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pouchy_ai/world-sdk",
3
- "version": "0.15.0",
3
+ "version": "0.16.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",