@maolon/pi-watcher 0.0.0-stage → 0.1.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.
Files changed (72) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/LICENSE +21 -0
  3. package/README.md +255 -2
  4. package/dist/cli.d.ts +8 -0
  5. package/dist/cli.js +369 -0
  6. package/dist/contracts/interfaces.d.ts +262 -0
  7. package/dist/contracts/interfaces.js +1 -0
  8. package/dist/contracts/policy-defaults.json +61 -0
  9. package/dist/contracts/relay-next.interfaces.d.ts +160 -0
  10. package/dist/contracts/relay-next.interfaces.js +1 -0
  11. package/dist/engine/cards.d.ts +68 -0
  12. package/dist/engine/cards.js +76 -0
  13. package/dist/engine/engine.d.ts +178 -0
  14. package/dist/engine/engine.js +1162 -0
  15. package/dist/engine/hard-rules.d.ts +53 -0
  16. package/dist/engine/hard-rules.js +96 -0
  17. package/dist/engine/semantic.d.ts +49 -0
  18. package/dist/engine/semantic.js +125 -0
  19. package/dist/engine/service.d.ts +62 -0
  20. package/dist/engine/service.js +587 -0
  21. package/dist/engine/tool-actions.d.ts +35 -0
  22. package/dist/engine/tool-actions.js +348 -0
  23. package/dist/engine/widget.d.ts +29 -0
  24. package/dist/engine/widget.js +52 -0
  25. package/dist/ipc/client.d.ts +26 -0
  26. package/dist/ipc/client.js +106 -0
  27. package/dist/ipc/server.d.ts +78 -0
  28. package/dist/ipc/server.js +105 -0
  29. package/dist/jev/client.d.ts +38 -0
  30. package/dist/jev/client.js +239 -0
  31. package/dist/jev/consent.d.ts +12 -0
  32. package/dist/jev/consent.js +37 -0
  33. package/dist/jev/index.d.ts +9 -0
  34. package/dist/jev/index.js +9 -0
  35. package/dist/jev/mock.d.ts +30 -0
  36. package/dist/jev/mock.js +77 -0
  37. package/dist/jev/pi-registry.d.ts +66 -0
  38. package/dist/jev/pi-registry.js +127 -0
  39. package/dist/jev/questions.d.ts +15 -0
  40. package/dist/jev/questions.js +76 -0
  41. package/dist/jev/sanitizer.d.ts +10 -0
  42. package/dist/jev/sanitizer.js +59 -0
  43. package/dist/jev/types.d.ts +78 -0
  44. package/dist/jev/types.js +54 -0
  45. package/dist/pi-extension.d.ts +123 -0
  46. package/dist/pi-extension.js +687 -0
  47. package/dist/relay/managed.d.ts +120 -0
  48. package/dist/relay/managed.js +482 -0
  49. package/dist/relay/negotiate.d.ts +39 -0
  50. package/dist/relay/negotiate.js +112 -0
  51. package/dist/runtime.d.ts +74 -0
  52. package/dist/runtime.js +246 -0
  53. package/dist/source/agent-check/adapter.d.ts +57 -0
  54. package/dist/source/agent-check/adapter.js +224 -0
  55. package/dist/source/agent-file/adapter.d.ts +57 -0
  56. package/dist/source/agent-file/adapter.js +217 -0
  57. package/dist/source/task-status-v1/adapter.d.ts +36 -0
  58. package/dist/source/task-status-v1/adapter.js +263 -0
  59. package/dist/source/task-status-v1/producer.d.ts +81 -0
  60. package/dist/source/task-status-v1/producer.js +127 -0
  61. package/dist/storage/lock.d.ts +14 -0
  62. package/dist/storage/lock.js +66 -0
  63. package/dist/storage/schema.sql +166 -0
  64. package/dist/storage/store.d.ts +352 -0
  65. package/dist/storage/store.js +555 -0
  66. package/dist/util/clock.d.ts +23 -0
  67. package/dist/util/clock.js +34 -0
  68. package/dist/util/ids.d.ts +8 -0
  69. package/dist/util/ids.js +29 -0
  70. package/dist/util/result.d.ts +28 -0
  71. package/dist/util/result.js +54 -0
  72. package/package.json +101 -4
@@ -0,0 +1,166 @@
1
+ -- pi-watcher WatchStore schema (schema_version 1).
2
+ -- Applied only when creating a NEW database; it is not a migration script.
3
+
4
+ PRAGMA foreign_keys = ON;
5
+ PRAGMA journal_mode = WAL;
6
+ PRAGMA synchronous = FULL;
7
+ CREATE TABLE runtime_meta (
8
+ singleton INTEGER PRIMARY KEY CHECK(singleton=1),
9
+ schema_version INTEGER NOT NULL CHECK(schema_version=1),
10
+ runtime_epoch INTEGER NOT NULL CHECK(runtime_epoch>0),
11
+ mode TEXT NOT NULL CHECK(mode IN ('embedded','service')),
12
+ recovery_attention_hold INTEGER NOT NULL DEFAULT 1 CHECK(recovery_attention_hold IN (0,1))
13
+ );
14
+ CREATE TABLE watches (
15
+ watch_id TEXT PRIMARY KEY,
16
+ generation INTEGER NOT NULL CHECK(generation>0),
17
+ mission_revision INTEGER NOT NULL CHECK(mission_revision>0),
18
+ control_revision INTEGER NOT NULL CHECK(control_revision>0),
19
+ lifecycle TEXT NOT NULL CHECK(lifecycle IN ('draft','active','paused','closed','expired')),
20
+ health TEXT NOT NULL CHECK(health IN ('healthy','degraded','blind')),
21
+ owner_session TEXT NOT NULL,
22
+ owner_binding_epoch INTEGER NOT NULL CHECK(owner_binding_epoch>0),
23
+ spec_json TEXT NOT NULL CHECK(json_valid(spec_json)),
24
+ snapshot_json TEXT NOT NULL DEFAULT '{}' CHECK(json_valid(snapshot_json)),
25
+ observation_seq INTEGER NOT NULL DEFAULT 0 CHECK(observation_seq>=0),
26
+ next_due_at INTEGER,
27
+ updated_at INTEGER NOT NULL
28
+ );
29
+ CREATE TABLE evidence (
30
+ evidence_id TEXT PRIMARY KEY,
31
+ watch_id TEXT NOT NULL REFERENCES watches(watch_id),
32
+ sha256 TEXT NOT NULL CHECK(length(sha256)=64),
33
+ byte_count INTEGER NOT NULL CHECK(byte_count>=0),
34
+ relative_locator TEXT NOT NULL,
35
+ captured_at INTEGER NOT NULL,
36
+ pinned INTEGER NOT NULL DEFAULT 0 CHECK(pinned IN (0,1))
37
+ );
38
+ CREATE TABLE observations (
39
+ observation_id TEXT PRIMARY KEY,
40
+ watch_id TEXT NOT NULL REFERENCES watches(watch_id),
41
+ generation INTEGER NOT NULL,
42
+ source_id TEXT NOT NULL,
43
+ attempt_id TEXT NOT NULL,
44
+ source_seq INTEGER NOT NULL CHECK(source_seq>=0),
45
+ local_seq INTEGER NOT NULL CHECK(local_seq>0),
46
+ observed_at INTEGER NOT NULL,
47
+ digest TEXT NOT NULL,
48
+ payload_json TEXT NOT NULL CHECK(json_valid(payload_json)),
49
+ UNIQUE(watch_id,generation,source_id,attempt_id,source_seq),
50
+ UNIQUE(watch_id,local_seq)
51
+ );
52
+ CREATE TABLE cursors (
53
+ watch_id TEXT NOT NULL REFERENCES watches(watch_id),
54
+ source_id TEXT NOT NULL,
55
+ generation INTEGER NOT NULL,
56
+ cursor_json TEXT NOT NULL CHECK(json_valid(cursor_json)),
57
+ PRIMARY KEY(watch_id,source_id,generation)
58
+ );
59
+ CREATE TABLE episodes (
60
+ episode_id TEXT PRIMARY KEY,
61
+ watch_id TEXT NOT NULL REFERENCES watches(watch_id),
62
+ generation INTEGER NOT NULL,
63
+ kind TEXT NOT NULL,
64
+ checkpoint_id TEXT NOT NULL,
65
+ ordinal INTEGER NOT NULL CHECK(ordinal>0),
66
+ revision INTEGER NOT NULL CHECK(revision>0),
67
+ state TEXT NOT NULL CHECK(state IN ('open','snoozed','acknowledged','resolved','superseded')),
68
+ body_json TEXT NOT NULL CHECK(json_valid(body_json)),
69
+ snooze_until INTEGER,
70
+ updated_at INTEGER NOT NULL,
71
+ UNIQUE(watch_id,generation,kind,checkpoint_id,ordinal)
72
+ );
73
+ CREATE UNIQUE INDEX one_active_episode_per_slot ON episodes(watch_id,generation,kind,checkpoint_id)
74
+ WHERE state IN ('open','snoozed','acknowledged');
75
+ CREATE TABLE judgments (
76
+ judgment_id TEXT PRIMARY KEY,
77
+ watch_id TEXT NOT NULL REFERENCES watches(watch_id),
78
+ generation INTEGER NOT NULL,
79
+ mission_revision INTEGER NOT NULL,
80
+ control_revision INTEGER NOT NULL,
81
+ window_digest TEXT NOT NULL,
82
+ model TEXT NOT NULL,
83
+ question_set TEXT NOT NULL,
84
+ status TEXT NOT NULL CHECK(status IN ('pending','inflight','accepted','discarded','failed')),
85
+ data_json TEXT NOT NULL CHECK(json_valid(data_json)),
86
+ created_at INTEGER NOT NULL
87
+ );
88
+ CREATE TABLE probe_runs (
89
+ probe_run_id TEXT PRIMARY KEY,
90
+ watch_id TEXT NOT NULL REFERENCES watches(watch_id),
91
+ episode_id TEXT REFERENCES episodes(episode_id),
92
+ generation INTEGER NOT NULL,
93
+ control_revision INTEGER NOT NULL,
94
+ probe_id TEXT NOT NULL,
95
+ status TEXT NOT NULL CHECK(status IN ('pending','inflight','done','discarded','failed')),
96
+ data_json TEXT NOT NULL CHECK(json_valid(data_json)),
97
+ created_at INTEGER NOT NULL
98
+ );
99
+ CREATE TABLE outbox (
100
+ event_id TEXT PRIMARY KEY,
101
+ watch_id TEXT NOT NULL REFERENCES watches(watch_id),
102
+ episode_id TEXT REFERENCES episodes(episode_id),
103
+ generation INTEGER NOT NULL,
104
+ event_type TEXT NOT NULL,
105
+ event_bytes BLOB NOT NULL,
106
+ event_digest TEXT NOT NULL,
107
+ valid_until INTEGER NOT NULL,
108
+ admission TEXT NOT NULL CHECK(admission IN ('pending','publishing','source-staged','empty-audience','unknown','rejected','expired')),
109
+ admission_json TEXT NOT NULL DEFAULT '{}' CHECK(json_valid(admission_json)),
110
+ created_at INTEGER NOT NULL,
111
+ updated_at INTEGER NOT NULL
112
+ );
113
+ CREATE TRIGGER outbox_identity_immutable BEFORE UPDATE OF event_id,event_bytes,event_digest,valid_until ON outbox
114
+ WHEN OLD.event_id IS NOT NEW.event_id OR OLD.event_bytes IS NOT NEW.event_bytes
115
+ OR OLD.event_digest IS NOT NEW.event_digest OR OLD.valid_until IS NOT NEW.valid_until
116
+ BEGIN SELECT RAISE(ABORT,'immutable outbox identity'); END;
117
+ CREATE TABLE host_acks (
118
+ request_id TEXT PRIMARY KEY,
119
+ episode_id TEXT NOT NULL REFERENCES episodes(episode_id),
120
+ expected_revision INTEGER NOT NULL,
121
+ action TEXT NOT NULL CHECK(action IN ('received','investigating','defer','resolved','dismiss')),
122
+ actor_binding_epoch INTEGER NOT NULL,
123
+ digest TEXT NOT NULL,
124
+ response_json TEXT NOT NULL CHECK(json_valid(response_json)),
125
+ created_at INTEGER NOT NULL
126
+ );
127
+ CREATE TABLE commands (
128
+ request_id TEXT NOT NULL,
129
+ actor_id TEXT NOT NULL,
130
+ digest TEXT NOT NULL,
131
+ response_json TEXT NOT NULL CHECK(json_valid(response_json)),
132
+ committed_at INTEGER NOT NULL,
133
+ PRIMARY KEY(actor_id,request_id)
134
+ );
135
+ CREATE TABLE budget_reservations (
136
+ reservation_id TEXT PRIMARY KEY,
137
+ watch_id TEXT NOT NULL REFERENCES watches(watch_id),
138
+ category TEXT NOT NULL CHECK(category IN ('judge','probe','attention')),
139
+ period_key TEXT NOT NULL,
140
+ units INTEGER NOT NULL CHECK(units>0),
141
+ state TEXT NOT NULL CHECK(state IN ('reserved','spent','unknown-cost','released')),
142
+ created_at INTEGER NOT NULL
143
+ );
144
+ CREATE INDEX due_watches ON watches(next_due_at) WHERE lifecycle='active';
145
+ CREATE TABLE control_outbox (
146
+ operation_id TEXT PRIMARY KEY, watch_id TEXT NOT NULL REFERENCES watches(watch_id),
147
+ scope_id TEXT NOT NULL, expected_revision INTEGER NOT NULL, next_revision INTEGER NOT NULL,
148
+ requested_state TEXT NOT NULL CHECK(requested_state IN ('active','paused','closed')),
149
+ status TEXT NOT NULL CHECK(status IN ('pending','sending','source-applied','complete','unknown','rejected')),
150
+ result_json TEXT NOT NULL DEFAULT '{}' CHECK(json_valid(result_json)), created_at INTEGER NOT NULL,
151
+ CHECK(next_revision=expected_revision+1)
152
+ );
153
+ CREATE TABLE applied_responses (
154
+ response_id TEXT PRIMARY KEY, watch_id TEXT NOT NULL REFERENCES watches(watch_id),
155
+ episode_id TEXT NOT NULL REFERENCES episodes(episode_id), delivery_ref TEXT NOT NULL,
156
+ digest TEXT NOT NULL, result_json TEXT NOT NULL CHECK(json_valid(result_json)), applied_at INTEGER NOT NULL
157
+ );
158
+ CREATE TABLE applied_confirmation_outbox (
159
+ operation_id TEXT PRIMARY KEY, response_id TEXT NOT NULL UNIQUE REFERENCES applied_responses(response_id),
160
+ status TEXT NOT NULL CHECK(status IN ('pending','sending','confirmed','unknown')), result_json TEXT NOT NULL CHECK(json_valid(result_json))
161
+ );
162
+ CREATE TABLE relay_projections (
163
+ event_id TEXT PRIMARY KEY REFERENCES outbox(event_id), source_cursor INTEGER NOT NULL,
164
+ snapshot_json TEXT NOT NULL CHECK(json_valid(snapshot_json)), observed_at INTEGER NOT NULL
165
+ );
166
+ CREATE TABLE relay_cursors (stream_id TEXT PRIMARY KEY, cursor INTEGER NOT NULL CHECK(cursor>=0));
@@ -0,0 +1,352 @@
1
+ /**
2
+ * WatchStore: local truth store (design 07).
3
+ * - SQLite WAL / synchronous=FULL / foreign_keys=ON
4
+ * - Each process holds the root flock (fixed inode); runtimeEpoch increments on every open
5
+ * - transaction(): synchronous, bounded transaction body (external I/O is forbidden inside)
6
+ */
7
+ import type { Json, WatchSpec, Basis } from '../contracts/interfaces.js';
8
+ export interface WatchSnapshot {
9
+ taskState: 'unknown' | 'queued' | 'running' | 'blocked' | 'succeeded' | 'failed' | 'cancelled';
10
+ stage?: string | null;
11
+ exitCode?: number | null;
12
+ summary?: string | null;
13
+ checks?: Array<{
14
+ checkId: string;
15
+ outcome: string;
16
+ artifactDigest: string | null;
17
+ }>;
18
+ artifactIds?: string[];
19
+ lastObservedAtMs?: number;
20
+ lastSourceSeq?: number;
21
+ lastResultId?: string | null;
22
+ coverage: {
23
+ truncated: boolean;
24
+ sourceGap: boolean;
25
+ };
26
+ degradedReason?: string | null;
27
+ /** Legacy local counter (pre-2026-10-07); superseded by relayScope. */
28
+ scopeRevision: number;
29
+ /**
30
+ * Per-watch relay scope (design 9.6): one scope per watch generation + owner bindingEpoch,
31
+ * created lazily on the first managed attention. revision is the latest intended revision;
32
+ * an open control_outbox row means the relay has not confirmed it yet (design 7.7).
33
+ */
34
+ relayScope?: {
35
+ scopeId: string;
36
+ revision: number;
37
+ state: 'active' | 'paused' | 'closed';
38
+ };
39
+ backoffMs: number;
40
+ terminalAt?: number | null;
41
+ tailLines?: string[];
42
+ /** Source evidence: the process has exited (even if the result is unknown) -- outcome-unknown terminal-state signal */
43
+ exited?: boolean;
44
+ /** V3 group-task projection: member state and readiness time */
45
+ members?: Array<{
46
+ watchId: string;
47
+ taskState: string;
48
+ lifecycle: string;
49
+ }>;
50
+ groupReadyAt?: number | null;
51
+ /** V2 semantic-layer projection (design 5.5/5.6): shadow only records candidates */
52
+ semantic?: {
53
+ mode: 'shadow' | 'active';
54
+ lastWindowDigest?: string | null;
55
+ lastJudgedAtMs?: number | null;
56
+ lastJudgedSourceSeq?: number | null;
57
+ /** Local observationSeq high-water mark ("window with new observations", design 5.5) */
58
+ lastJudgedObservationSeq?: number | null;
59
+ repeatingStreak: number;
60
+ meaningfulProgress?: number | null;
61
+ candidates?: Record<string, Json>;
62
+ consentMissing?: boolean;
63
+ budgetExhausted?: boolean;
64
+ error?: string | null;
65
+ };
66
+ }
67
+ export declare function emptySnapshot(): WatchSnapshot;
68
+ export interface WatchRow {
69
+ watchId: string;
70
+ generation: number;
71
+ missionRevision: number;
72
+ controlRevision: number;
73
+ lifecycle: 'draft' | 'active' | 'paused' | 'closed' | 'expired';
74
+ health: 'healthy' | 'degraded' | 'blind';
75
+ ownerSession: string;
76
+ ownerBindingEpoch: number;
77
+ spec: WatchSpec;
78
+ snapshot: WatchSnapshot;
79
+ observationSeq: number;
80
+ nextDueAt: number | null;
81
+ updatedAt: number;
82
+ }
83
+ export interface EpisodeRow {
84
+ episodeId: string;
85
+ watchId: string;
86
+ generation: number;
87
+ kind: string;
88
+ checkpointId: string;
89
+ ordinal: number;
90
+ revision: number;
91
+ state: 'open' | 'snoozed' | 'acknowledged' | 'resolved' | 'superseded';
92
+ body: Record<string, Json>;
93
+ snoozeUntil: number | null;
94
+ updatedAt: number;
95
+ }
96
+ export interface OutboxRow {
97
+ eventId: string;
98
+ watchId: string;
99
+ episodeId: string | null;
100
+ generation: number;
101
+ eventType: string;
102
+ eventBytes: Buffer;
103
+ eventDigest: string;
104
+ validUntil: number;
105
+ admission: string;
106
+ admissionJson: Record<string, Json>;
107
+ createdAt: number;
108
+ updatedAt: number;
109
+ }
110
+ export interface ControlOutboxRow {
111
+ operationId: string;
112
+ watchId: string;
113
+ scopeId: string;
114
+ expectedRevision: number;
115
+ nextRevision: number;
116
+ requestedState: 'active' | 'paused' | 'closed';
117
+ status: string;
118
+ createdAt: number;
119
+ }
120
+ export interface StoreTx {
121
+ meta(): {
122
+ schemaVersion: number;
123
+ runtimeEpoch: number;
124
+ mode: string;
125
+ recoveryAttentionHold: boolean;
126
+ };
127
+ clearRecoveryHold(): void;
128
+ getWatchRow(watchId: string): WatchRow | undefined;
129
+ getWatch(watchId: string): WatchSpec | undefined;
130
+ compareRevision(basis: Basis): boolean;
131
+ persistCommandResult(actorId: string, requestId: string, digest: string, result: Json): {
132
+ duplicate: boolean;
133
+ previous?: Json;
134
+ };
135
+ getCommand(actorId: string, requestId: string): {
136
+ digest: string;
137
+ response: Json;
138
+ } | undefined;
139
+ insertWatch(row: {
140
+ watchId: string;
141
+ generation: number;
142
+ missionRevision: number;
143
+ controlRevision: number;
144
+ lifecycle: WatchRow['lifecycle'];
145
+ health: WatchRow['health'];
146
+ ownerSession: string;
147
+ ownerBindingEpoch: number;
148
+ spec: WatchSpec;
149
+ snapshot: WatchSnapshot;
150
+ nextDueAt: number | null;
151
+ now: number;
152
+ }): void;
153
+ updateWatch(watchId: string, patch: {
154
+ lifecycle?: WatchRow['lifecycle'];
155
+ health?: WatchRow['health'];
156
+ controlRevision?: number;
157
+ missionRevision?: number;
158
+ snapshot?: WatchSnapshot;
159
+ observationSeq?: number;
160
+ nextDueAt?: number | null;
161
+ }, now: number): void;
162
+ bumpObservationSeq(watchId: string): number;
163
+ insertObservation(input: {
164
+ observationId: string;
165
+ watchId: string;
166
+ generation: number;
167
+ sourceId: string;
168
+ attemptId: string;
169
+ sourceSeq: number;
170
+ localSeq: number;
171
+ observedAt: number;
172
+ digest: string;
173
+ payload: Json;
174
+ }): boolean;
175
+ getCursor(watchId: string, sourceId: string, generation: number): Json | undefined;
176
+ putCursor(watchId: string, sourceId: string, generation: number, cursor: Json): void;
177
+ nextEpisodeOrdinal(watchId: string, generation: number, kind: string, checkpointId: string): number;
178
+ insertEpisode(input: {
179
+ episodeId: string;
180
+ watchId: string;
181
+ generation: number;
182
+ kind: string;
183
+ checkpointId: string;
184
+ ordinal: number;
185
+ body: Record<string, Json>;
186
+ now: number;
187
+ }): void;
188
+ getEpisode(episodeId: string): EpisodeRow | undefined;
189
+ getActiveEpisodeBySlot(watchId: string, generation: number, kind: string, checkpointId: string): EpisodeRow | undefined;
190
+ updateEpisode(episodeId: string, expectedRevision: number, patch: {
191
+ state?: EpisodeRow['state'];
192
+ body?: Record<string, Json>;
193
+ snoozeUntil?: number | null;
194
+ }, now: number): number | null;
195
+ listEpisodes(watchId: string, limit: number): EpisodeRow[];
196
+ /** When closing a watch, fold its unresolved episodes (-> superseded); returns the number of affected rows */
197
+ supersedeEpisodesOfWatch(watchId: string, now: number): number;
198
+ /** Sweep: watch is already closed/expired but episode is still open/snoozed/acknowledged -> superseded */
199
+ supersedeEpisodesOfInactiveWatches(now: number): number;
200
+ /** Sweep: attention envelope is past validUntil and still pending/publishing -> expired */
201
+ expireStaleAttentions(now: number): number;
202
+ insertOutbox(input: {
203
+ eventId: string;
204
+ watchId: string;
205
+ episodeId: string | null;
206
+ generation: number;
207
+ eventType: string;
208
+ eventBytes: Buffer;
209
+ eventDigest: string;
210
+ validUntil: number;
211
+ admissionJson?: Record<string, Json>;
212
+ admission?: string;
213
+ now: number;
214
+ }): void;
215
+ setOutboxAdmission(eventId: string, admission: string, admissionJson: Record<string, Json>, now: number): void;
216
+ getOutbox(eventId: string): OutboxRow | undefined;
217
+ listOutboxByWatch(watchId: string, limit: number): OutboxRow[];
218
+ insertControlOutbox(input: {
219
+ operationId: string;
220
+ watchId: string;
221
+ scopeId: string;
222
+ expectedRevision: number;
223
+ nextRevision: number;
224
+ requestedState: 'active' | 'paused' | 'closed';
225
+ now: number;
226
+ }): void;
227
+ completeControlOutbox(operationId: string, status: 'source-applied' | 'complete' | 'unknown' | 'rejected', result?: Record<string, Json>): void;
228
+ getControlOutboxStatus(operationId: string): string | null;
229
+ /** Unconfirmed control ops (pending/sending/unknown), FIFO; optionally for one watch. */
230
+ listOpenControlOutbox(watchId?: string): ControlOutboxRow[];
231
+ /** Shallow-merges keys into an outbox row's admission_json (identity columns stay immutable). */
232
+ mergeOutboxAdmissionJson(eventId: string, patch: Record<string, Json>, now: number): void;
233
+ /** Attention rows whose durable withdraw intent has no recorded relay result yet. */
234
+ listOutboxAwaitingWithdraw(limit: number): OutboxRow[];
235
+ insertAppliedResponse(input: {
236
+ responseId: string;
237
+ watchId: string;
238
+ episodeId: string;
239
+ deliveryRef: string;
240
+ digest: string;
241
+ result: Record<string, Json>;
242
+ now: number;
243
+ }): void;
244
+ getAppliedResponse(responseId: string): {
245
+ responseId: string;
246
+ result: Record<string, Json>;
247
+ } | undefined;
248
+ insertAppliedConfirmOutbox(operationId: string, responseId: string): void;
249
+ listObservations(watchId: string, limit: number): Array<{
250
+ observationId: string;
251
+ sourceSeq: number;
252
+ localSeq: number;
253
+ observedAt: number;
254
+ digest: string;
255
+ payload: Json;
256
+ }>;
257
+ listWatches(ownerSession: string, cursor: string | undefined, limit: number, lifecycles?: Array<WatchRow['lifecycle']>): WatchRow[];
258
+ listAllWatches(limit: number): WatchRow[];
259
+ /** Count by lifecycle (terminal summary of the list projection); no filter = all watches of that owner */
260
+ countWatches(ownerSession: string, lifecycles?: Array<WatchRow['lifecycle']>): number;
261
+ countOpenEpisodes(watchId: string): number;
262
+ /** Count of unresolved episodes across the whole DB or by ownerSession (widget projection) */
263
+ countUnresolvedEpisodes(ownerSession?: string): number;
264
+ /** Count of attention events whose episode is not yet acknowledged by the host (open/snoozed) and whose envelope is still within its validity period (widget projection, optionally filtered by ownerSession) */
265
+ countPendingAttentions(now: number, ownerSession?: string): number;
266
+ insertJudgment(input: {
267
+ judgmentId: string;
268
+ watchId: string;
269
+ generation: number;
270
+ missionRevision: number;
271
+ controlRevision: number;
272
+ windowDigest: string;
273
+ model: string;
274
+ questionSet: string;
275
+ status: 'pending' | 'inflight' | 'accepted' | 'discarded' | 'failed';
276
+ data: Record<string, Json>;
277
+ createdAt: number;
278
+ }): void;
279
+ getJudgmentByWindow(watchId: string, generation: number, windowDigest: string, model: string, questionSet: string): {
280
+ judgmentId: string;
281
+ status: string;
282
+ data: Record<string, Json>;
283
+ } | undefined;
284
+ listJudgments(watchId: string, limit: number): Array<{
285
+ judgmentId: string;
286
+ windowDigest: string;
287
+ model: string;
288
+ questionSet: string;
289
+ status: string;
290
+ data: Record<string, Json>;
291
+ createdAt: number;
292
+ }>;
293
+ insertProbeRun(input: {
294
+ probeRunId: string;
295
+ watchId: string;
296
+ episodeId: string | null;
297
+ generation: number;
298
+ controlRevision: number;
299
+ probeId: string;
300
+ status: 'pending' | 'inflight' | 'done' | 'discarded' | 'failed';
301
+ data: Record<string, Json>;
302
+ createdAt: number;
303
+ }): void;
304
+ setProbeRunStatus(probeRunId: string, status: 'pending' | 'inflight' | 'done' | 'discarded' | 'failed', data: Record<string, Json>): void;
305
+ countBudgetReservations(category: 'judge' | 'probe' | 'attention', periodKey: string, watchId?: string): number;
306
+ insertBudgetReservation(input: {
307
+ reservationId: string;
308
+ watchId: string;
309
+ category: 'judge' | 'probe' | 'attention';
310
+ periodKey: string;
311
+ units: number;
312
+ state: 'reserved' | 'spent' | 'unknown-cost' | 'released';
313
+ createdAt: number;
314
+ }): void;
315
+ setBudgetReservationState(reservationId: string, state: 'reserved' | 'spent' | 'unknown-cost' | 'released'): void;
316
+ getRelayCursor(streamId: string): number | undefined;
317
+ putRelayCursor(streamId: string, cursor: number): void;
318
+ }
319
+ export interface WatchStoreOptions {
320
+ mode?: 'embedded' | 'service';
321
+ /** Skip flock (single-process tests may pass false to open a second view). */
322
+ noLock?: boolean;
323
+ }
324
+ export declare class WatchStore {
325
+ readonly rootDir: string;
326
+ readonly dbPath: string;
327
+ readonly evidenceDir: string;
328
+ readonly resultsDir: string;
329
+ readonly runtimeEpoch: number;
330
+ private readonly db;
331
+ private readonly lock;
332
+ private closed;
333
+ private constructor();
334
+ static open(rootDir: string, options?: WatchStoreOptions): Promise<WatchStore>;
335
+ transaction<T>(body: (tx: StoreTx) => T): T;
336
+ get isClosed(): boolean;
337
+ /** Connection-level pragma diagnostics (WAL persists; synchronous/foreign_keys are connection-level). */
338
+ pragmaReport(): {
339
+ journalMode: unknown;
340
+ synchronous: unknown;
341
+ foreignKeys: unknown;
342
+ };
343
+ close(): void;
344
+ writeEvidenceBlock(content: string): {
345
+ sha256: string;
346
+ relativeLocator: string;
347
+ bytes: number;
348
+ };
349
+ writeResultCard(watchId: string, resultId: string, card: Json): string;
350
+ listResultCards(watchId: string): Json[];
351
+ private makeTx;
352
+ }