@statewavedev/sdk 0.10.0 → 0.10.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/README.md +39 -3
- package/dist/client.d.ts +67 -1
- package/dist/client.js +95 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/types.d.ts +110 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -97,9 +97,9 @@ console.log(`${timeline.episodes.length} episodes, ${timeline.memories.length} m
|
|
|
97
97
|
await sw.deleteSubject("user-42");
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
## Governance & audit (v0.8)
|
|
100
|
+
## Governance & audit (v0.8+)
|
|
101
101
|
|
|
102
|
-
The SDK surfaces the [state-assembly receipts](https://github.com/smaramwbc/statewave-docs/blob/main/receipts.md) and [sensitivity-labels / policy](https://github.com/smaramwbc/statewave-docs/blob/main/sensitivity-labels.md) layer added in server v0.8.
|
|
102
|
+
The SDK surfaces the [state-assembly receipts](https://github.com/smaramwbc/statewave-docs/blob/main/receipts.md) and [sensitivity-labels / policy](https://github.com/smaramwbc/statewave-docs/blob/main/sensitivity-labels.md) layer added in server v0.8, plus the v0.9 [HMAC signing](https://github.com/smaramwbc/statewave/blob/main/docs/state-assembly-receipts.md) and [as-of replay](https://github.com/smaramwbc/statewave/blob/main/docs/replay.md) surfaces.
|
|
103
103
|
|
|
104
104
|
```typescript
|
|
105
105
|
import { StatewaveClient } from "@statewavedev/sdk";
|
|
@@ -140,6 +140,39 @@ for (const r of receipts) {
|
|
|
140
140
|
console.log(r.receiptId, r.task);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
// Verify the HMAC signature on a stored receipt (v0.9+).
|
|
144
|
+
// `valid` is true | false | null — see ReceiptVerifyResult for the
|
|
145
|
+
// full reason vocabulary (no_signature / key_unavailable / etc.).
|
|
146
|
+
if (bundle.receiptId) {
|
|
147
|
+
const verdict = await sw.verifyReceipt(bundle.receiptId);
|
|
148
|
+
if (verdict.valid === true) {
|
|
149
|
+
console.log(`signature OK — signed by ${verdict.keyId}`);
|
|
150
|
+
} else if (verdict.valid === false) {
|
|
151
|
+
console.log("signature mismatch — body may have been tampered with");
|
|
152
|
+
} else {
|
|
153
|
+
console.log(`verdict undetermined: ${verdict.reason}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Replay the receipt against current memories using the original
|
|
158
|
+
// policy bundle captured on the receipt (v0.9+). Returns a diff
|
|
159
|
+
// envelope showing what changed since emission. Pre-v0.9 receipts
|
|
160
|
+
// throw StatewaveUnreplayableError with reason="missing_policy_snapshot".
|
|
161
|
+
import { StatewaveUnreplayableError } from "@statewavedev/sdk";
|
|
162
|
+
try {
|
|
163
|
+
const replay = await sw.replayReceipt(bundle.receiptId!);
|
|
164
|
+
if (replay.diff.contextHash.changed) {
|
|
165
|
+
console.log(`replay differs from original: new id ${replay.replayReceiptId}`);
|
|
166
|
+
}
|
|
167
|
+
} catch (err) {
|
|
168
|
+
if (err instanceof StatewaveUnreplayableError) {
|
|
169
|
+
// err.reason ∈ {"missing_policy_snapshot", "nested_replay", "invalid_snapshot"}
|
|
170
|
+
console.log(`replay refused: ${err.reason}`);
|
|
171
|
+
} else {
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
143
176
|
// Set per-memory sensitivity labels (server normalizes — dedup, lowercase, trim).
|
|
144
177
|
// Memories with labels become subject to any active policy bundle for the tenant.
|
|
145
178
|
const updated = await sw.setMemoryLabels({
|
|
@@ -246,7 +279,10 @@ All response types are fully typed:
|
|
|
246
279
|
- `BatchCreateResult` — batch ingestion response
|
|
247
280
|
- `SubjectSummary` — subject with episode/memory counts
|
|
248
281
|
- `ListSubjectsResult` — paginated subject listing
|
|
249
|
-
- `Receipt` + `ReceiptSelectedEntry` + `ReceiptPolicy` + `ReceiptOutput` — state-assembly audit artifact (v0.8) and its nested shapes
|
|
282
|
+
- `Receipt` + `ReceiptSelectedEntry` + `ReceiptPolicy` + `ReceiptOutput` — state-assembly audit artifact (v0.8+) and its nested shapes; v0.9 added HMAC signature fields (`receiptSignatureKeyId`, `receiptSignatureAlgorithm`), `policySnapshot` for replay, and `region` for residency
|
|
283
|
+
- `ReceiptVerifyResult` — `valid` (true | false | null) + `keyId` + `algorithm` + `reason` for the v0.9 HMAC verify endpoint
|
|
284
|
+
- `ReceiptReplayResult` / `ReceiptReplayDiff` — original + replay receipt ids plus the structural diff envelope from `POST /v1/receipts/{id}/replay` (v0.9)
|
|
285
|
+
- `StatewaveUnreplayableError` — thrown by `replayReceipt(...)` on HTTP 422; `.reason` is a discriminated union of `"missing_policy_snapshot" | "nested_replay" | "invalid_snapshot"`
|
|
250
286
|
- `ReceiptList` — cursor-paginated receipt listing
|
|
251
287
|
- `Health` + `HealthFactor` — customer health score and its explainable factors
|
|
252
288
|
- `SLASummary` + `SessionSLA` — SLA metrics, aggregate and per-session
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BatchCreateResult, ClientOptions, CompileJob, CompileResult, ContextBundle, CreateEpisodeParams, CreateHandoffParams, CreateResolutionParams, DeleteResult, Episode, GetContextParams, GetSLAParams, Handoff, Health, ListReceiptsParams, ListResolutionsParams, ListSubjectsResult, Memory, Receipt, ReceiptList, Resolution, SLASummary, SearchMemoriesParams, SearchResult, SetMemoryLabelsParams, Timeline } from "./types.js";
|
|
1
|
+
import type { BatchCreateResult, ClientOptions, CompileJob, CompileResult, ContextBundle, CreateEpisodeParams, CreateHandoffParams, CreateResolutionParams, DeleteResult, Episode, GetContextParams, GetSLAParams, Handoff, Health, ListReceiptsParams, ListResolutionsParams, ListSubjectsResult, Memory, Receipt, ReceiptList, ReceiptReplayResult, ReceiptVerifyResult, Resolution, SLASummary, SearchMemoriesParams, SearchResult, SetMemoryLabelsParams, Timeline, UnreplayableReason } from "./types.js";
|
|
2
2
|
/** Structured error from the Statewave API. */
|
|
3
3
|
export declare class StatewaveAPIError extends Error {
|
|
4
4
|
readonly statusCode: number;
|
|
@@ -11,6 +11,26 @@ export declare class StatewaveAPIError extends Error {
|
|
|
11
11
|
export declare class StatewaveConnectionError extends Error {
|
|
12
12
|
constructor(message?: string);
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Raised by `replayReceipt(...)` when the server refuses with HTTP 422.
|
|
16
|
+
* Subclass of `StatewaveAPIError` so generic handlers still catch it;
|
|
17
|
+
* adds a typed `reason` field so callers can branch on the structured
|
|
18
|
+
* refusal vocabulary without parsing the error code.
|
|
19
|
+
*
|
|
20
|
+
* `reason` is a discriminated union of:
|
|
21
|
+
* - `"missing_policy_snapshot"` — pre-v0.9 receipt. No
|
|
22
|
+
* `policySnapshot` was captured at emission and the replay engine
|
|
23
|
+
* cannot synthesise one retroactively.
|
|
24
|
+
* - `"nested_replay"` — the receipt is itself a replay
|
|
25
|
+
* (`mode === "as_of_replay"`). v0.9 ships one level only; replay
|
|
26
|
+
* the source receipt referenced by `parentReceiptId` instead.
|
|
27
|
+
* - `"invalid_snapshot"` — the snapshot's YAML failed to parse.
|
|
28
|
+
* Tampering or corruption at the column level.
|
|
29
|
+
*/
|
|
30
|
+
export declare class StatewaveUnreplayableError extends StatewaveAPIError {
|
|
31
|
+
readonly reason: UnreplayableReason;
|
|
32
|
+
constructor(reason: UnreplayableReason, statusCode: number, code: string, message: string, details?: unknown, requestId?: string);
|
|
33
|
+
}
|
|
14
34
|
export declare class StatewaveClient {
|
|
15
35
|
private baseUrl;
|
|
16
36
|
private defaultHeaders;
|
|
@@ -47,6 +67,52 @@ export declare class StatewaveClient {
|
|
|
47
67
|
* to fetch the next page.
|
|
48
68
|
*/
|
|
49
69
|
listReceipts(params: ListReceiptsParams): Promise<ReceiptList>;
|
|
70
|
+
/**
|
|
71
|
+
* Verify the HMAC signature on a stored receipt (v0.9+ #157).
|
|
72
|
+
*
|
|
73
|
+
* Returns a `ReceiptVerifyResult` with `valid` ∈ `{true, false, null}`:
|
|
74
|
+
* - `true` — signature matches the canonical body (`reason === "ok"`).
|
|
75
|
+
* - `false` — signature does not cover the body
|
|
76
|
+
* (`reason === "signature_mismatch"`).
|
|
77
|
+
* - `null` — verdict could not be determined; `reason` is one of
|
|
78
|
+
* `"no_signature"` (unsigned receipt — pre-v0.9 or tenant didn't
|
|
79
|
+
* opt in), `"key_unavailable"` (the keyId rotated out of operator
|
|
80
|
+
* config), or `"unsupported_algorithm"` (forward-compat).
|
|
81
|
+
*
|
|
82
|
+
* Comparison is constant-time on the server side. The signing key
|
|
83
|
+
* bytes never appear on the response — only the public `keyId` is
|
|
84
|
+
* echoed.
|
|
85
|
+
*
|
|
86
|
+
* Throws `StatewaveAPIError` on 404 (receipt not found or belongs to
|
|
87
|
+
* a different tenant — indistinguishable on the wire) and other
|
|
88
|
+
* non-2xx responses.
|
|
89
|
+
*/
|
|
90
|
+
verifyReceipt(receiptId: string): Promise<ReceiptVerifyResult>;
|
|
91
|
+
/**
|
|
92
|
+
* Re-run the original retrieval against current memories using the
|
|
93
|
+
* original policy bundle captured in the receipt's `policySnapshot`
|
|
94
|
+
* (v0.9+ #159).
|
|
95
|
+
*
|
|
96
|
+
* Emits a new `mode="as_of_replay"` receipt with `parentReceiptId`
|
|
97
|
+
* pointing at the source; the original receipt is **never**
|
|
98
|
+
* modified. Returns the new `replayReceiptId` plus a structural
|
|
99
|
+
* diff envelope (added/removed selected entries, filter changes,
|
|
100
|
+
* context-hash diff).
|
|
101
|
+
*
|
|
102
|
+
* Semantic: current code + original policy. Replay is *not*
|
|
103
|
+
* byte-for-byte reproduction; memories that were added, tombstoned,
|
|
104
|
+
* or supersession-resolved between the original emission and now
|
|
105
|
+
* will appear in the diff. See `docs/replay.md` in the server repo
|
|
106
|
+
* for the design rationale.
|
|
107
|
+
*
|
|
108
|
+
* Throws `StatewaveUnreplayableError` (HTTP 422) when:
|
|
109
|
+
* - `reason === "missing_policy_snapshot"` — pre-v0.9 receipt.
|
|
110
|
+
* - `reason === "nested_replay"` — the receipt is itself a replay.
|
|
111
|
+
* - `reason === "invalid_snapshot"` — snapshot YAML failed to parse.
|
|
112
|
+
*
|
|
113
|
+
* Throws `StatewaveAPIError` on 404 and other non-2xx responses.
|
|
114
|
+
*/
|
|
115
|
+
replayReceipt(receiptId: string): Promise<ReceiptReplayResult>;
|
|
50
116
|
/**
|
|
51
117
|
* Compute the customer health score (0–100) for a subject, with the
|
|
52
118
|
* explainable factors that drove it. Backs proactive risk triage.
|
package/dist/client.js
CHANGED
|
@@ -59,6 +59,40 @@ export class StatewaveConnectionError extends Error {
|
|
|
59
59
|
this.name = "StatewaveConnectionError";
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
+
/** The documented refusal vocabulary for the v0.9 replay endpoint.
|
|
63
|
+
* Mirrors the `UnreplayableReason` type alias in `./types.ts`.
|
|
64
|
+
* Auto-promotion to `StatewaveUnreplayableError` only happens when
|
|
65
|
+
* the server returns a code with one of these reasons — a future
|
|
66
|
+
* unknown reason stays on the generic `StatewaveAPIError` path. */
|
|
67
|
+
const UNREPLAYABLE_REASONS = new Set([
|
|
68
|
+
"missing_policy_snapshot",
|
|
69
|
+
"nested_replay",
|
|
70
|
+
"invalid_snapshot",
|
|
71
|
+
]);
|
|
72
|
+
/**
|
|
73
|
+
* Raised by `replayReceipt(...)` when the server refuses with HTTP 422.
|
|
74
|
+
* Subclass of `StatewaveAPIError` so generic handlers still catch it;
|
|
75
|
+
* adds a typed `reason` field so callers can branch on the structured
|
|
76
|
+
* refusal vocabulary without parsing the error code.
|
|
77
|
+
*
|
|
78
|
+
* `reason` is a discriminated union of:
|
|
79
|
+
* - `"missing_policy_snapshot"` — pre-v0.9 receipt. No
|
|
80
|
+
* `policySnapshot` was captured at emission and the replay engine
|
|
81
|
+
* cannot synthesise one retroactively.
|
|
82
|
+
* - `"nested_replay"` — the receipt is itself a replay
|
|
83
|
+
* (`mode === "as_of_replay"`). v0.9 ships one level only; replay
|
|
84
|
+
* the source receipt referenced by `parentReceiptId` instead.
|
|
85
|
+
* - `"invalid_snapshot"` — the snapshot's YAML failed to parse.
|
|
86
|
+
* Tampering or corruption at the column level.
|
|
87
|
+
*/
|
|
88
|
+
export class StatewaveUnreplayableError extends StatewaveAPIError {
|
|
89
|
+
reason;
|
|
90
|
+
constructor(reason, statusCode, code, message, details, requestId) {
|
|
91
|
+
super(statusCode, code, message, details, requestId);
|
|
92
|
+
this.name = "StatewaveUnreplayableError";
|
|
93
|
+
this.reason = reason;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
62
96
|
export class StatewaveClient {
|
|
63
97
|
baseUrl;
|
|
64
98
|
defaultHeaders;
|
|
@@ -195,6 +229,56 @@ export class StatewaveClient {
|
|
|
195
229
|
qs.set("limit", String(params.limit));
|
|
196
230
|
return this.get(`/v1/receipts?${qs}`);
|
|
197
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Verify the HMAC signature on a stored receipt (v0.9+ #157).
|
|
234
|
+
*
|
|
235
|
+
* Returns a `ReceiptVerifyResult` with `valid` ∈ `{true, false, null}`:
|
|
236
|
+
* - `true` — signature matches the canonical body (`reason === "ok"`).
|
|
237
|
+
* - `false` — signature does not cover the body
|
|
238
|
+
* (`reason === "signature_mismatch"`).
|
|
239
|
+
* - `null` — verdict could not be determined; `reason` is one of
|
|
240
|
+
* `"no_signature"` (unsigned receipt — pre-v0.9 or tenant didn't
|
|
241
|
+
* opt in), `"key_unavailable"` (the keyId rotated out of operator
|
|
242
|
+
* config), or `"unsupported_algorithm"` (forward-compat).
|
|
243
|
+
*
|
|
244
|
+
* Comparison is constant-time on the server side. The signing key
|
|
245
|
+
* bytes never appear on the response — only the public `keyId` is
|
|
246
|
+
* echoed.
|
|
247
|
+
*
|
|
248
|
+
* Throws `StatewaveAPIError` on 404 (receipt not found or belongs to
|
|
249
|
+
* a different tenant — indistinguishable on the wire) and other
|
|
250
|
+
* non-2xx responses.
|
|
251
|
+
*/
|
|
252
|
+
async verifyReceipt(receiptId) {
|
|
253
|
+
return this.get(`/v1/receipts/${encodeURIComponent(receiptId)}/verify`);
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Re-run the original retrieval against current memories using the
|
|
257
|
+
* original policy bundle captured in the receipt's `policySnapshot`
|
|
258
|
+
* (v0.9+ #159).
|
|
259
|
+
*
|
|
260
|
+
* Emits a new `mode="as_of_replay"` receipt with `parentReceiptId`
|
|
261
|
+
* pointing at the source; the original receipt is **never**
|
|
262
|
+
* modified. Returns the new `replayReceiptId` plus a structural
|
|
263
|
+
* diff envelope (added/removed selected entries, filter changes,
|
|
264
|
+
* context-hash diff).
|
|
265
|
+
*
|
|
266
|
+
* Semantic: current code + original policy. Replay is *not*
|
|
267
|
+
* byte-for-byte reproduction; memories that were added, tombstoned,
|
|
268
|
+
* or supersession-resolved between the original emission and now
|
|
269
|
+
* will appear in the diff. See `docs/replay.md` in the server repo
|
|
270
|
+
* for the design rationale.
|
|
271
|
+
*
|
|
272
|
+
* Throws `StatewaveUnreplayableError` (HTTP 422) when:
|
|
273
|
+
* - `reason === "missing_policy_snapshot"` — pre-v0.9 receipt.
|
|
274
|
+
* - `reason === "nested_replay"` — the receipt is itself a replay.
|
|
275
|
+
* - `reason === "invalid_snapshot"` — snapshot YAML failed to parse.
|
|
276
|
+
*
|
|
277
|
+
* Throws `StatewaveAPIError` on 404 and other non-2xx responses.
|
|
278
|
+
*/
|
|
279
|
+
async replayReceipt(receiptId) {
|
|
280
|
+
return this.post(`/v1/receipts/${encodeURIComponent(receiptId)}/replay`, undefined);
|
|
281
|
+
}
|
|
198
282
|
// -- Support: health, SLA, handoff, resolutions ----------------------
|
|
199
283
|
/**
|
|
200
284
|
* Compute the customer health score (0–100) for a subject, with the
|
|
@@ -353,6 +437,17 @@ export class StatewaveClient {
|
|
|
353
437
|
const body = await resp.json();
|
|
354
438
|
const err = body?.error;
|
|
355
439
|
if (err && typeof err.code === "string") {
|
|
440
|
+
// Promote unreplayable.<reason> refusals into a typed
|
|
441
|
+
// exception so callers can `catch (e) { if (e instanceof
|
|
442
|
+
// StatewaveUnreplayableError) ... e.reason }` without
|
|
443
|
+
// string-matching the error code. Forward-compat: an
|
|
444
|
+
// unrecognised future reason stays on the generic path.
|
|
445
|
+
if (resp.status === 422 && err.code.startsWith("unreplayable.")) {
|
|
446
|
+
const reason = err.code.slice("unreplayable.".length);
|
|
447
|
+
if (UNREPLAYABLE_REASONS.has(reason)) {
|
|
448
|
+
throw new StatewaveUnreplayableError(reason, resp.status, err.code, err.message ?? resp.statusText, err.details, err.request_id);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
356
451
|
throw new StatewaveAPIError(resp.status, err.code, err.message ?? resp.statusText, err.details, err.request_id);
|
|
357
452
|
}
|
|
358
453
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { StatewaveClient, StatewaveAPIError, StatewaveConnectionError } from "./client.js";
|
|
1
|
+
export { StatewaveClient, StatewaveAPIError, StatewaveConnectionError, StatewaveUnreplayableError, } from "./client.js";
|
|
2
2
|
export type * from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { StatewaveClient, StatewaveAPIError, StatewaveConnectionError } from "./client.js";
|
|
1
|
+
export { StatewaveClient, StatewaveAPIError, StatewaveConnectionError, StatewaveUnreplayableError, } from "./client.js";
|
package/dist/types.d.ts
CHANGED
|
@@ -101,14 +101,38 @@ export interface ReceiptOutput {
|
|
|
101
101
|
canonicalizationVersion: number;
|
|
102
102
|
tokenEstimate: number;
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* v0.9 (#159) — self-contained policy bundle envelope embedded on
|
|
106
|
+
* every v0.9+ receipt. Self-sufficient: the replay engine evaluates
|
|
107
|
+
* against this bundle even if the live `policy_bundles` row has
|
|
108
|
+
* since been deleted or overwritten.
|
|
109
|
+
*
|
|
110
|
+
* - A null inner pair (`bundleHash` AND `bundleYaml` both null)
|
|
111
|
+
* records "no policy bundle was active at emission" — a valid,
|
|
112
|
+
* replayable state.
|
|
113
|
+
* - The whole envelope being absent (`Receipt.policySnapshot ===
|
|
114
|
+
* undefined`) marks "pre-v0.9 receipt, no snapshot was ever
|
|
115
|
+
* captured" — the replay endpoint refuses those.
|
|
116
|
+
*/
|
|
117
|
+
export interface PolicySnapshot {
|
|
118
|
+
bundleHash: string | null;
|
|
119
|
+
bundleYaml: string | null;
|
|
120
|
+
/** ISO-8601 UTC timestamp captured at receipt emission. */
|
|
121
|
+
capturedAt: string;
|
|
122
|
+
}
|
|
104
123
|
/**
|
|
105
124
|
* Immutable per-retrieval audit artifact for a single context assembly.
|
|
106
125
|
* See `docs/state-assembly-receipts.md` in the server repository.
|
|
126
|
+
*
|
|
127
|
+
* The `mode` discriminator distinguishes:
|
|
128
|
+
* - `"retrieval"` — receipts emitted by `/v1/context` + `/v1/handoff`.
|
|
129
|
+
* - `"as_of_replay"` — receipts emitted by `POST /v1/receipts/{id}/replay`
|
|
130
|
+
* (v0.9+); the `parentReceiptId` points at the source receipt.
|
|
107
131
|
*/
|
|
108
132
|
export interface Receipt {
|
|
109
133
|
receiptId: string;
|
|
110
134
|
parentReceiptId: string | null;
|
|
111
|
-
mode: "retrieval" | string;
|
|
135
|
+
mode: "retrieval" | "as_of_replay" | string;
|
|
112
136
|
queryId: string | null;
|
|
113
137
|
taskId: string | null;
|
|
114
138
|
tenantId: string | null;
|
|
@@ -119,8 +143,24 @@ export interface Receipt {
|
|
|
119
143
|
selectedEntries: ReceiptSelectedEntry[];
|
|
120
144
|
policy: ReceiptPolicy;
|
|
121
145
|
output: ReceiptOutput;
|
|
146
|
+
/** Server region the receipt was emitted from (v0.9+ residency).
|
|
147
|
+
* `null` in single-region deployments. */
|
|
122
148
|
region: string | null;
|
|
149
|
+
/** HMAC-SHA256 hex digest over the canonical body (v0.9+ #157).
|
|
150
|
+
* `null` for pre-v0.9 receipts or tenants without signing
|
|
151
|
+
* configured — those verify cleanly as
|
|
152
|
+
* `{valid: null, reason: "no_signature"}`. */
|
|
123
153
|
receiptSignature: string | null;
|
|
154
|
+
/** Operator key id used to sign (v0.9+). `null`/`undefined` when unsigned. */
|
|
155
|
+
receiptSignatureKeyId?: string | null;
|
|
156
|
+
/** Algorithm + canonical-form version (e.g. "hmac-sha256-canonical-v1")
|
|
157
|
+
* (v0.9+). `null`/`undefined` when unsigned. */
|
|
158
|
+
receiptSignatureAlgorithm?: string | null;
|
|
159
|
+
/** Embedded policy bundle YAML + hash + capture timestamp (v0.9+ #159).
|
|
160
|
+
* See `PolicySnapshot`. `undefined` for pre-v0.9 receipts (the
|
|
161
|
+
* replay endpoint refuses those with
|
|
162
|
+
* `unreplayable.missing_policy_snapshot`). */
|
|
163
|
+
policySnapshot?: PolicySnapshot | null;
|
|
124
164
|
}
|
|
125
165
|
export interface ReceiptList {
|
|
126
166
|
receipts: Receipt[];
|
|
@@ -134,6 +174,75 @@ export interface ListReceiptsParams {
|
|
|
134
174
|
cursor?: string;
|
|
135
175
|
limit?: number;
|
|
136
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Result of `GET /v1/receipts/{id}/verify` (v0.9+ #157).
|
|
179
|
+
*
|
|
180
|
+
* `valid` is the verdict:
|
|
181
|
+
* - `true` — HMAC matches the canonical body. `reason === "ok"`.
|
|
182
|
+
* - `false` — math checked, signature does not cover the body.
|
|
183
|
+
* `reason === "signature_mismatch"`.
|
|
184
|
+
* - `null` — verdict could not be determined. `reason` is one of:
|
|
185
|
+
* - `"no_signature"` — receipt is unsigned (pre-v0.9 or tenant
|
|
186
|
+
* didn't opt in).
|
|
187
|
+
* - `"key_unavailable"` — the `keyId` rotated out of operator
|
|
188
|
+
* config; receipt is no longer verifiable on this binary.
|
|
189
|
+
* - `"unsupported_algorithm"` — receipt signed under a canonical
|
|
190
|
+
* form / algorithm variant this binary doesn't implement.
|
|
191
|
+
*
|
|
192
|
+
* Comparison is constant-time on the server side; the signing key
|
|
193
|
+
* bytes never appear on the response.
|
|
194
|
+
*/
|
|
195
|
+
export interface ReceiptVerifyResult {
|
|
196
|
+
valid: boolean | null;
|
|
197
|
+
keyId: string | null;
|
|
198
|
+
algorithm: string | null;
|
|
199
|
+
reason: "ok" | "signature_mismatch" | "no_signature" | "key_unavailable" | "unsupported_algorithm" | string;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Structural diff envelope returned by `POST /v1/receipts/{id}/replay`.
|
|
203
|
+
* Entries are matched by their `memoryId` / `episodeId` so re-ranking
|
|
204
|
+
* the same entry is reported under `common`, not as add+remove.
|
|
205
|
+
*/
|
|
206
|
+
export interface ReceiptReplayDiff {
|
|
207
|
+
contextHash: {
|
|
208
|
+
original: string | null;
|
|
209
|
+
replay: string | null;
|
|
210
|
+
changed: boolean;
|
|
211
|
+
};
|
|
212
|
+
selectedEntries: {
|
|
213
|
+
added: ReceiptSelectedEntry[];
|
|
214
|
+
removed: ReceiptSelectedEntry[];
|
|
215
|
+
common: number;
|
|
216
|
+
};
|
|
217
|
+
filtersApplied: {
|
|
218
|
+
added: unknown[];
|
|
219
|
+
removed: unknown[];
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Response from `POST /v1/receipts/{id}/replay` (v0.9+ #159).
|
|
224
|
+
*
|
|
225
|
+
* Semantic: current code + original policy. Replay re-runs the
|
|
226
|
+
* original retrieval against the *current* memory state but with
|
|
227
|
+
* the *original* policy bundle frozen on the receipt's
|
|
228
|
+
* `policySnapshot`. The original receipt is never modified;
|
|
229
|
+
* `replayReceiptId` points at a new `mode="as_of_replay"` receipt
|
|
230
|
+
* linked back to the source via `parentReceiptId`.
|
|
231
|
+
*
|
|
232
|
+
* `replayReceiptId` is `null` when the replay-receipt write itself
|
|
233
|
+
* failed (rare, fail-open path). The `diff` envelope is still
|
|
234
|
+
* authoritative in that case.
|
|
235
|
+
*/
|
|
236
|
+
export interface ReceiptReplayResult {
|
|
237
|
+
originalReceiptId: string;
|
|
238
|
+
replayReceiptId: string | null;
|
|
239
|
+
diff: ReceiptReplayDiff;
|
|
240
|
+
}
|
|
241
|
+
/** The set of refusal reasons the server returns from
|
|
242
|
+
* `POST /v1/receipts/{id}/replay` when a receipt cannot be replayed.
|
|
243
|
+
* Used by `StatewaveUnreplayableError.reason` so callers can switch
|
|
244
|
+
* on the structured value without parsing error code strings. */
|
|
245
|
+
export type UnreplayableReason = "missing_policy_snapshot" | "nested_replay" | "invalid_snapshot";
|
|
137
246
|
export interface Timeline {
|
|
138
247
|
subjectId: string;
|
|
139
248
|
episodes: Episode[];
|