@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.
- package/CHANGELOG.md +18 -0
- package/LICENSE +21 -0
- package/README.md +255 -2
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +369 -0
- package/dist/contracts/interfaces.d.ts +262 -0
- package/dist/contracts/interfaces.js +1 -0
- package/dist/contracts/policy-defaults.json +61 -0
- package/dist/contracts/relay-next.interfaces.d.ts +160 -0
- package/dist/contracts/relay-next.interfaces.js +1 -0
- package/dist/engine/cards.d.ts +68 -0
- package/dist/engine/cards.js +76 -0
- package/dist/engine/engine.d.ts +178 -0
- package/dist/engine/engine.js +1162 -0
- package/dist/engine/hard-rules.d.ts +53 -0
- package/dist/engine/hard-rules.js +96 -0
- package/dist/engine/semantic.d.ts +49 -0
- package/dist/engine/semantic.js +125 -0
- package/dist/engine/service.d.ts +62 -0
- package/dist/engine/service.js +587 -0
- package/dist/engine/tool-actions.d.ts +35 -0
- package/dist/engine/tool-actions.js +348 -0
- package/dist/engine/widget.d.ts +29 -0
- package/dist/engine/widget.js +52 -0
- package/dist/ipc/client.d.ts +26 -0
- package/dist/ipc/client.js +106 -0
- package/dist/ipc/server.d.ts +78 -0
- package/dist/ipc/server.js +105 -0
- package/dist/jev/client.d.ts +38 -0
- package/dist/jev/client.js +239 -0
- package/dist/jev/consent.d.ts +12 -0
- package/dist/jev/consent.js +37 -0
- package/dist/jev/index.d.ts +9 -0
- package/dist/jev/index.js +9 -0
- package/dist/jev/mock.d.ts +30 -0
- package/dist/jev/mock.js +77 -0
- package/dist/jev/pi-registry.d.ts +66 -0
- package/dist/jev/pi-registry.js +127 -0
- package/dist/jev/questions.d.ts +15 -0
- package/dist/jev/questions.js +76 -0
- package/dist/jev/sanitizer.d.ts +10 -0
- package/dist/jev/sanitizer.js +59 -0
- package/dist/jev/types.d.ts +78 -0
- package/dist/jev/types.js +54 -0
- package/dist/pi-extension.d.ts +123 -0
- package/dist/pi-extension.js +687 -0
- package/dist/relay/managed.d.ts +120 -0
- package/dist/relay/managed.js +482 -0
- package/dist/relay/negotiate.d.ts +39 -0
- package/dist/relay/negotiate.js +112 -0
- package/dist/runtime.d.ts +74 -0
- package/dist/runtime.js +246 -0
- package/dist/source/agent-check/adapter.d.ts +57 -0
- package/dist/source/agent-check/adapter.js +224 -0
- package/dist/source/agent-file/adapter.d.ts +57 -0
- package/dist/source/agent-file/adapter.js +217 -0
- package/dist/source/task-status-v1/adapter.d.ts +36 -0
- package/dist/source/task-status-v1/adapter.js +263 -0
- package/dist/source/task-status-v1/producer.d.ts +81 -0
- package/dist/source/task-status-v1/producer.js +127 -0
- package/dist/storage/lock.d.ts +14 -0
- package/dist/storage/lock.js +66 -0
- package/dist/storage/schema.sql +166 -0
- package/dist/storage/store.d.ts +352 -0
- package/dist/storage/store.js +555 -0
- package/dist/util/clock.d.ts +23 -0
- package/dist/util/clock.js +34 -0
- package/dist/util/ids.d.ts +8 -0
- package/dist/util/ids.js +29 -0
- package/dist/util/result.d.ts +28 -0
- package/dist/util/result.js +54 -0
- package/package.json +101 -4
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/** pi-watcher core ports and domain types (engine, store, sources, judge, managed delivery). */
|
|
2
|
+
export type Id = string;
|
|
3
|
+
export type Json = null | boolean | number | string | Json[] | {
|
|
4
|
+
[key: string]: Json;
|
|
5
|
+
};
|
|
6
|
+
export type SemanticMode = 'off' | 'shadow' | 'active';
|
|
7
|
+
export type RunTarget = {
|
|
8
|
+
kind: 'run';
|
|
9
|
+
sourceId: Id;
|
|
10
|
+
taskId: Id;
|
|
11
|
+
runId: Id;
|
|
12
|
+
attemptId: Id; /**
|
|
13
|
+
* decision-delta 2026-09-21 (universal monitor mode, not in design package v0.2.0):
|
|
14
|
+
* agent-declared
|
|
15
|
+
* generic evidence sources. The typed source enum is removed as a product surface (unified-exec deleted);
|
|
16
|
+
* agent-file / agent-check are the only two agent registration surfaces.
|
|
17
|
+
*
|
|
18
|
+
* file: tail a declared file path; an okPattern/failPattern hit in the tail window is the only
|
|
19
|
+
* content terminal-state evidence (fail takes precedence over ok); growth means progress; file missing/rotated -> degraded, non-terminal;
|
|
20
|
+
* having no fd holder is not exit evidence (a writer may open the file per write).
|
|
21
|
+
*/
|
|
22
|
+
file?: {
|
|
23
|
+
path: string;
|
|
24
|
+
okPattern?: string;
|
|
25
|
+
failPattern?: string;
|
|
26
|
+
}; /**
|
|
27
|
+
* check: an agent-declared bounded check command. The watcher runs cmd at a fixed cadence (within timeoutMs,
|
|
28
|
+
* SIGTERM->SIGKILL); a non-zero exit code has three meanings, mapped by the declaration: exit 0 / okPattern ->
|
|
29
|
+
* succeeded; failCodes / failPattern -> failed; 126/127/timeout/spawn error ->
|
|
30
|
+
* unknown+degraded (the check is broken; do not sentence the task to death); any other non-zero -> running (pending).
|
|
31
|
+
* The original cmd text is stored with the spec as evidence -- it must not contain secrets.
|
|
32
|
+
*/
|
|
33
|
+
check?: {
|
|
34
|
+
cmd: string;
|
|
35
|
+
cwd?: string;
|
|
36
|
+
timeoutMs?: number;
|
|
37
|
+
okPattern?: string;
|
|
38
|
+
failPattern?: string;
|
|
39
|
+
failCodes?: number[];
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
export type ChildRef = {
|
|
43
|
+
watchId: Id;
|
|
44
|
+
generation: number;
|
|
45
|
+
};
|
|
46
|
+
export type Target = RunTarget | {
|
|
47
|
+
kind: 'group';
|
|
48
|
+
members: ChildRef[];
|
|
49
|
+
readyWhen: 'all_terminal' | 'all_succeeded';
|
|
50
|
+
} | {
|
|
51
|
+
kind: 'obligation';
|
|
52
|
+
dependencies: ChildRef[];
|
|
53
|
+
readyWhen: 'all_terminal' | 'all_succeeded';
|
|
54
|
+
hostAction: string;
|
|
55
|
+
};
|
|
56
|
+
export type Owner = {
|
|
57
|
+
sessionId: Id;
|
|
58
|
+
originAnchor: string | null;
|
|
59
|
+
bindingEpoch: number;
|
|
60
|
+
profileId: Id;
|
|
61
|
+
};
|
|
62
|
+
export interface WatchSpec {
|
|
63
|
+
schemaVersion: 1;
|
|
64
|
+
watchId: Id;
|
|
65
|
+
generation: number;
|
|
66
|
+
missionRevision: number;
|
|
67
|
+
controlRevision: number;
|
|
68
|
+
mode: 'embedded' | 'service';
|
|
69
|
+
owner: Owner;
|
|
70
|
+
target: Target;
|
|
71
|
+
mission: {
|
|
72
|
+
objective: string;
|
|
73
|
+
scope: string;
|
|
74
|
+
checkpointId: Id;
|
|
75
|
+
requiredArtifacts: Id[];
|
|
76
|
+
requiresChecks: boolean;
|
|
77
|
+
businessAcceptance: 'host' | 'not_required';
|
|
78
|
+
deadlineAt?: string;
|
|
79
|
+
};
|
|
80
|
+
policy: {
|
|
81
|
+
transport: 'local-display' | 'relay';
|
|
82
|
+
semanticMode: SemanticMode;
|
|
83
|
+
notificationOwner: 'watcher' | 'external';
|
|
84
|
+
requestKinds: string[];
|
|
85
|
+
maxRequestsPerEpisode: number;
|
|
86
|
+
episodeCooldownMs: number;
|
|
87
|
+
attentionTtlMs: number;
|
|
88
|
+
};
|
|
89
|
+
limits: {
|
|
90
|
+
pollMinMs: number;
|
|
91
|
+
pollMaxMs: number;
|
|
92
|
+
maxSilenceMs: number;
|
|
93
|
+
maxProbesPerEpisode: number;
|
|
94
|
+
maxJudgeRequestsPerDay: number;
|
|
95
|
+
expiresAt: string;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
export type RegisterCandidate = Omit<WatchSpec, 'watchId' | 'generation' | 'missionRevision' | 'controlRevision' | 'owner'>;
|
|
99
|
+
export interface ActorContext {
|
|
100
|
+
actorId: Id;
|
|
101
|
+
owner: Owner;
|
|
102
|
+
profileRevision: number;
|
|
103
|
+
permitted: ReadonlySet<string>;
|
|
104
|
+
}
|
|
105
|
+
export interface Basis {
|
|
106
|
+
watchId: Id;
|
|
107
|
+
generation: number;
|
|
108
|
+
missionRevision: number;
|
|
109
|
+
controlRevision: number;
|
|
110
|
+
observationSeq: number;
|
|
111
|
+
windowDigest: string;
|
|
112
|
+
}
|
|
113
|
+
export interface EvidenceRef {
|
|
114
|
+
evidenceId: Id;
|
|
115
|
+
sourceId: Id;
|
|
116
|
+
capturedAt: string;
|
|
117
|
+
sha256: string;
|
|
118
|
+
bytes: number;
|
|
119
|
+
sampled: boolean;
|
|
120
|
+
}
|
|
121
|
+
export interface Observation {
|
|
122
|
+
schemaVersion: 1;
|
|
123
|
+
observationId: Id;
|
|
124
|
+
target: RunTarget;
|
|
125
|
+
sourceSeq: number;
|
|
126
|
+
observedAt: string;
|
|
127
|
+
kind: 'status' | 'heartbeat' | 'log_delta' | 'validation' | 'artifact_manifest' | 'decision_request';
|
|
128
|
+
payload: Json;
|
|
129
|
+
evidenceRefs: EvidenceRef[];
|
|
130
|
+
}
|
|
131
|
+
export interface Probe {
|
|
132
|
+
probeId: Id;
|
|
133
|
+
revision: number;
|
|
134
|
+
target: RunTarget;
|
|
135
|
+
kind: 'read-status' | 'read-log-delta' | 'read-artifact-manifest' | 'read-validation-result';
|
|
136
|
+
scopeId: Id;
|
|
137
|
+
expiresAt: string;
|
|
138
|
+
timeoutMs: number;
|
|
139
|
+
maxBytes: number;
|
|
140
|
+
}
|
|
141
|
+
export interface SourceCapabilities {
|
|
142
|
+
stableAttempt: boolean;
|
|
143
|
+
terminalEvents: boolean;
|
|
144
|
+
incrementalCursor: boolean;
|
|
145
|
+
exclusiveNotificationOwner: boolean;
|
|
146
|
+
}
|
|
147
|
+
export interface SourceAdapter {
|
|
148
|
+
readonly adapterId: string;
|
|
149
|
+
readonly capabilities: SourceCapabilities;
|
|
150
|
+
read(target: RunTarget, cursor: Json | undefined, signal: AbortSignal): Promise<{
|
|
151
|
+
observations: Observation[];
|
|
152
|
+
nextCursor: Json;
|
|
153
|
+
gap: boolean;
|
|
154
|
+
}>;
|
|
155
|
+
probes(target: RunTarget, basis: Basis): Promise<readonly Probe[]>;
|
|
156
|
+
execute(probe: Probe, signal: AbortSignal): Promise<Observation[]>;
|
|
157
|
+
close(): Promise<void>;
|
|
158
|
+
}
|
|
159
|
+
export type QuestionKey = 'meaningful_progress' | 'unresolved_blocker' | 'needs_host_decision' | 'repeating_without_new_information' | 'claim_conflicts_with_evidence' | 'context_sufficient';
|
|
160
|
+
export interface Judgment {
|
|
161
|
+
basis: Basis;
|
|
162
|
+
model: string;
|
|
163
|
+
questionSet: 'watcher-q1';
|
|
164
|
+
probabilities: Record<QuestionKey, number>;
|
|
165
|
+
receivedAt: string;
|
|
166
|
+
inputTokens: number;
|
|
167
|
+
discarded: boolean;
|
|
168
|
+
}
|
|
169
|
+
export interface JudgePort {
|
|
170
|
+
evaluate(basis: Basis, state: Json, signal: AbortSignal): Promise<Judgment>;
|
|
171
|
+
chooseProbe(basis: Basis, state: Json, candidates: readonly Probe[], signal: AbortSignal): Promise<Id | 'none'>;
|
|
172
|
+
/** Optional availability check (e.g. credentials resolved lazily from the host); absent means always ready. */
|
|
173
|
+
ready?(): Promise<{
|
|
174
|
+
ready: boolean;
|
|
175
|
+
reason?: string;
|
|
176
|
+
model?: string;
|
|
177
|
+
}>;
|
|
178
|
+
}
|
|
179
|
+
export type AttentionReason = 'task.failed' | 'task.terminal' | 'deadline.exceeded' | 'decision.required' | 'blocker.unresolved' | 'progress.repeating' | 'claim.conflict' | 'evidence.insufficient' | 'dependency.ready' | 'monitor.degraded' | 'target.changed';
|
|
180
|
+
export interface AttentionEnvelope {
|
|
181
|
+
schemaVersion: 1;
|
|
182
|
+
envelopeId: Id;
|
|
183
|
+
episodeId: Id;
|
|
184
|
+
episodeRevision: number;
|
|
185
|
+
watchId: Id;
|
|
186
|
+
generation: number;
|
|
187
|
+
missionRevision: number;
|
|
188
|
+
controlRevision: number;
|
|
189
|
+
ownerBindingEpoch: number;
|
|
190
|
+
target: Target;
|
|
191
|
+
reasonCode: AttentionReason;
|
|
192
|
+
summary: string;
|
|
193
|
+
evidenceRefs: EvidenceRef[];
|
|
194
|
+
requiredNextStep: 'inspect-current-watch-before-acting';
|
|
195
|
+
occurredAt: string;
|
|
196
|
+
validUntil: string;
|
|
197
|
+
}
|
|
198
|
+
export interface HostAck {
|
|
199
|
+
schemaVersion: 1;
|
|
200
|
+
requestId: Id;
|
|
201
|
+
watchId: Id;
|
|
202
|
+
generation: number;
|
|
203
|
+
episodeId: Id;
|
|
204
|
+
expectedEpisodeRevision: number;
|
|
205
|
+
action: 'received' | 'investigating' | 'defer' | 'resolved' | 'dismiss';
|
|
206
|
+
reason: string;
|
|
207
|
+
until?: string;
|
|
208
|
+
evidenceIds: Id[];
|
|
209
|
+
}
|
|
210
|
+
export interface VerifiedConsumerResponse {
|
|
211
|
+
responseId: Id;
|
|
212
|
+
deliveryRef: Id;
|
|
213
|
+
ownerBindingEpoch: number;
|
|
214
|
+
digest: string;
|
|
215
|
+
body: HostAck;
|
|
216
|
+
}
|
|
217
|
+
export interface ManagedDeliveryPort {
|
|
218
|
+
/** New managed relay adapter. Never fallback to direct Pi injection. */
|
|
219
|
+
/**
|
|
220
|
+
* decision-delta 2026-10-07 (per-watch scope, design 9.6): optional scope the event is
|
|
221
|
+
* captured under. Omitted -> the adapter's source-wide default scope (legacy behaviour).
|
|
222
|
+
*/
|
|
223
|
+
publish(frozenRequestBytes: Uint8Array, scope?: {
|
|
224
|
+
scopeId: Id;
|
|
225
|
+
revision: number;
|
|
226
|
+
}): Promise<import('./relay-next.interfaces').ManagedReceipt>;
|
|
227
|
+
reconcile(eventId: Id): Promise<import('./relay-next.interfaces').ManagedReceipt>;
|
|
228
|
+
advanceScope(operationId: Id, scopeId: Id, expectedRevision: number, nextRevision: number, state: 'active' | 'paused' | 'closed'): Promise<Json>;
|
|
229
|
+
withdraw(operationId: Id, eventId: Id, reason: string): Promise<import('./relay-next.interfaces').WithdrawResult>;
|
|
230
|
+
readResponses(after: number): Promise<{
|
|
231
|
+
cursor: number;
|
|
232
|
+
responses: VerifiedConsumerResponse[];
|
|
233
|
+
resyncRequired: boolean;
|
|
234
|
+
}>;
|
|
235
|
+
confirmApplied(operationId: Id, responseId: Id, result: import('./relay-next.interfaces').ApplicationResult): Promise<void>;
|
|
236
|
+
close(): Promise<void>;
|
|
237
|
+
}
|
|
238
|
+
export interface Clock {
|
|
239
|
+
wallNow(): number;
|
|
240
|
+
monotonicNow(): number;
|
|
241
|
+
}
|
|
242
|
+
export interface Transaction {
|
|
243
|
+
getWatch(watchId: Id): WatchSpec | undefined;
|
|
244
|
+
compareRevision(basis: Basis): boolean;
|
|
245
|
+
persistCommandResult(requestId: Id, digest: string, result: Json): void;
|
|
246
|
+
}
|
|
247
|
+
export interface Store {
|
|
248
|
+
/** Synchronous, bounded transaction body: external I/O is forbidden. */
|
|
249
|
+
transaction<T>(runtimeEpoch: number, body: (tx: Transaction) => T): T;
|
|
250
|
+
close(): void;
|
|
251
|
+
}
|
|
252
|
+
export interface WatchService {
|
|
253
|
+
register(requestId: Id, candidate: RegisterCandidate, actor: ActorContext): Promise<WatchSpec>;
|
|
254
|
+
list(cursor: string | undefined, limit: number, actor: ActorContext): Promise<Json>;
|
|
255
|
+
inspect(watchId: Id, actor: ActorContext): Promise<Json>;
|
|
256
|
+
check(requestId: Id, watchId: Id, expectedControlRevision: number, actor: ActorContext): Promise<{
|
|
257
|
+
inspectionId: Id;
|
|
258
|
+
}>;
|
|
259
|
+
/** Internal response pump only. No independent network ACK endpoint. */
|
|
260
|
+
applyResponse(response: VerifiedConsumerResponse): Promise<import('./relay-next.interfaces').ApplicationResult>;
|
|
261
|
+
control(requestId: Id, watchId: Id, expectedControlRevision: number, action: 'pause' | 'close' | 'resume', reason: string, actor: ActorContext): Promise<Json>;
|
|
262
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"status": "design-initial-values-not-performance-results",
|
|
4
|
+
"semanticMode": "shadow",
|
|
5
|
+
"jevConsentDefault": false,
|
|
6
|
+
"model": "jev-1.13.0",
|
|
7
|
+
"questionSet": "watcher-q1",
|
|
8
|
+
"thresholds": {
|
|
9
|
+
"contextSufficient": 0.75,
|
|
10
|
+
"needsHostDecision": 0.85,
|
|
11
|
+
"unresolvedBlocker": 0.85,
|
|
12
|
+
"repeating": 0.9,
|
|
13
|
+
"claimConflict": 0.85
|
|
14
|
+
},
|
|
15
|
+
"scheduler": {
|
|
16
|
+
"pollMinMs": 30000,
|
|
17
|
+
"pollMaxMs": 300000,
|
|
18
|
+
"maxSilenceMs": 900000,
|
|
19
|
+
"maxJevInFlight": 2,
|
|
20
|
+
"maxProbeInFlight": 4,
|
|
21
|
+
"maxJudgeRequestsPerWatchDay": 200,
|
|
22
|
+
"maxJudgeRequestsPerRootDay": 2000,
|
|
23
|
+
"judgeTimeoutMs": 10000,
|
|
24
|
+
"maxJudgeAttempts": 3
|
|
25
|
+
},
|
|
26
|
+
"capacity": {
|
|
27
|
+
"maxActiveWatches": 32,
|
|
28
|
+
"maxGroupChildren": 16,
|
|
29
|
+
"maxEvidenceEvents": 16,
|
|
30
|
+
"stateMaxBytes": 24576,
|
|
31
|
+
"evidenceMaxBytes": 12288,
|
|
32
|
+
"maxProbesPerInspection": 1,
|
|
33
|
+
"maxProbesPerEpisode": 2,
|
|
34
|
+
"rootSoftBytes": 1073741824,
|
|
35
|
+
"rootHardBytes": 2147483648
|
|
36
|
+
},
|
|
37
|
+
"attention": {
|
|
38
|
+
"transport": "relay",
|
|
39
|
+
"defaultModelMode": "display",
|
|
40
|
+
"episodeCooldownMs": 60000,
|
|
41
|
+
"ttlMs": 120000,
|
|
42
|
+
"resultTtlMs": 86400000,
|
|
43
|
+
"maxRequestsPerEpisode": 1,
|
|
44
|
+
"batchSize": 1
|
|
45
|
+
},
|
|
46
|
+
"retention": {
|
|
47
|
+
"closedEvidenceDays": 7,
|
|
48
|
+
"closedStructuredDays": 30,
|
|
49
|
+
"retainOpenAndUnknown": true
|
|
50
|
+
},
|
|
51
|
+
"relayManaged": {
|
|
52
|
+
"requiredWireMinor": 2,
|
|
53
|
+
"dispatchCooldownMs": 60000,
|
|
54
|
+
"batchSize": 1,
|
|
55
|
+
"sourceScopeProofMaxMs": 1000,
|
|
56
|
+
"consumerGuardTimeoutMs": 1000,
|
|
57
|
+
"maximumEventAgeMs": 86400000,
|
|
58
|
+
"maximumEventTtlMs": 86400000,
|
|
59
|
+
"dedupRetentionDays": 30
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watcher-side view of the pi-relay managed-delivery shapes (receipts, withdraw results,
|
|
3
|
+
* consumer responses). pi-relay does not re-export these types from its public entry points,
|
|
4
|
+
* so they are declared here; keep them in sync with the @maolon/pi-relay version in package.json.
|
|
5
|
+
*/
|
|
6
|
+
export type Json = null | boolean | number | string | Json[] | {
|
|
7
|
+
[key: string]: Json;
|
|
8
|
+
};
|
|
9
|
+
export interface Event {
|
|
10
|
+
kind: 'event';
|
|
11
|
+
id: string;
|
|
12
|
+
type: string;
|
|
13
|
+
schemaVersion: number;
|
|
14
|
+
subject?: string;
|
|
15
|
+
occurredAt: string;
|
|
16
|
+
validUntil: string;
|
|
17
|
+
data: {
|
|
18
|
+
[key: string]: Json;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export interface Scope {
|
|
22
|
+
id: string;
|
|
23
|
+
revision: number;
|
|
24
|
+
}
|
|
25
|
+
export interface ManagedOptions {
|
|
26
|
+
audienceRef: string;
|
|
27
|
+
scope: Scope;
|
|
28
|
+
consumerProfileId: string;
|
|
29
|
+
requestedMode: 'display' | 'resume';
|
|
30
|
+
}
|
|
31
|
+
export interface RouteReceipt {
|
|
32
|
+
routeRef: string;
|
|
33
|
+
targetRevision: number;
|
|
34
|
+
targetUpdatedAt?: string;
|
|
35
|
+
freshness: 'fresh' | 'stale' | 'offline';
|
|
36
|
+
admission: 'accepted' | 'staged' | 'rejected' | 'unknown';
|
|
37
|
+
delivery: 'pending' | 'held' | 'intent' | 'submitted' | 'recorded' | 'unknown' | 'suppressed' | 'expired' | 'withdrawn';
|
|
38
|
+
withdrawal: 'none' | 'pending' | 'prevented' | 'too_late' | 'unknown';
|
|
39
|
+
}
|
|
40
|
+
export interface ApplicationResult {
|
|
41
|
+
outcome: 'applied' | 'stale' | 'rejected';
|
|
42
|
+
applicationRevision: number;
|
|
43
|
+
code: 'APPLIED' | 'STALE_EPISODE' | 'OWNER_MISMATCH' | 'ALREADY_CLOSED' | 'INVALID_RESPONSE';
|
|
44
|
+
}
|
|
45
|
+
export interface ConsumerResponse {
|
|
46
|
+
responseId: string;
|
|
47
|
+
deliveryRef: string;
|
|
48
|
+
responseType: string;
|
|
49
|
+
schemaVersion: number;
|
|
50
|
+
data: {
|
|
51
|
+
[key: string]: Json;
|
|
52
|
+
};
|
|
53
|
+
createdAt: string;
|
|
54
|
+
state: 'target_staged' | 'source_recorded' | 'application_applied';
|
|
55
|
+
applicationResult?: ApplicationResult;
|
|
56
|
+
}
|
|
57
|
+
export interface ManagedReceipt {
|
|
58
|
+
eventId: string;
|
|
59
|
+
sourceState: 'captured' | 'empty_audience' | 'rejected' | 'unknown';
|
|
60
|
+
sourceCursor: number;
|
|
61
|
+
scope: Scope;
|
|
62
|
+
routes: RouteReceipt[];
|
|
63
|
+
responses: ConsumerResponse[];
|
|
64
|
+
}
|
|
65
|
+
export interface WithdrawResult {
|
|
66
|
+
operationId: string;
|
|
67
|
+
eventId: string;
|
|
68
|
+
sourceApplied: boolean;
|
|
69
|
+
routes: Array<{
|
|
70
|
+
routeRef: string;
|
|
71
|
+
disposition: 'prevented' | 'too_late' | 'pending' | 'unknown';
|
|
72
|
+
}>;
|
|
73
|
+
}
|
|
74
|
+
export interface ScopeAdvance {
|
|
75
|
+
operationId: string;
|
|
76
|
+
scopeId: string;
|
|
77
|
+
expectedRevision: number;
|
|
78
|
+
nextRevision: number;
|
|
79
|
+
state: 'active' | 'paused' | 'closed';
|
|
80
|
+
}
|
|
81
|
+
export interface ManagedSource {
|
|
82
|
+
publishManaged(event: Event, options: ManagedOptions): Promise<ManagedReceipt>;
|
|
83
|
+
getManagedReceipt(eventId: string): Promise<ManagedReceipt>;
|
|
84
|
+
watchManagedUpdates(after: number, limit: number): Promise<{
|
|
85
|
+
cursor: number;
|
|
86
|
+
resyncRequired: boolean;
|
|
87
|
+
updates: ManagedReceipt[];
|
|
88
|
+
}>;
|
|
89
|
+
withdraw(operationId: string, eventId: string, reason: string): Promise<WithdrawResult>;
|
|
90
|
+
advanceScope(input: ScopeAdvance): Promise<{
|
|
91
|
+
revision: number;
|
|
92
|
+
state: 'active' | 'paused' | 'closed';
|
|
93
|
+
pendingRouteControls: number;
|
|
94
|
+
}>;
|
|
95
|
+
confirmApplied(operationId: string, responseId: string, result: ApplicationResult): Promise<void>;
|
|
96
|
+
dispose(): void;
|
|
97
|
+
}
|
|
98
|
+
export interface GateResult {
|
|
99
|
+
decision: 'allow' | 'defer' | 'drop';
|
|
100
|
+
reasonCode: 'CURRENT' | 'BUSY' | 'SOURCE_UNAVAILABLE' | 'GUARD_UNAVAILABLE' | 'STALE_REQUEST' | 'CANCELLED' | 'WRONG_OWNER' | 'EXPIRED';
|
|
101
|
+
guardEpoch: number;
|
|
102
|
+
validUntil?: string;
|
|
103
|
+
}
|
|
104
|
+
export interface ConsumerContext {
|
|
105
|
+
readonly deliveryRef: string;
|
|
106
|
+
readonly bindingEpoch: number;
|
|
107
|
+
readonly profileId: string;
|
|
108
|
+
}
|
|
109
|
+
export interface ConsumerRegistration {
|
|
110
|
+
profileId: string;
|
|
111
|
+
eventManifestDigest: string;
|
|
112
|
+
responseManifestDigest: string;
|
|
113
|
+
guardImplementationId: string;
|
|
114
|
+
timeoutMs: number;
|
|
115
|
+
}
|
|
116
|
+
export interface ConsumerAPI {
|
|
117
|
+
registerConsumer(profile: ConsumerRegistration, assess: (event: Event, context: ConsumerContext, signal: AbortSignal) => Promise<GateResult>): () => void;
|
|
118
|
+
respond(input: {
|
|
119
|
+
operationId: string;
|
|
120
|
+
deliveryRef: string;
|
|
121
|
+
responseType: string;
|
|
122
|
+
schemaVersion: number;
|
|
123
|
+
data: {
|
|
124
|
+
[key: string]: Json;
|
|
125
|
+
};
|
|
126
|
+
}): Promise<{
|
|
127
|
+
responseId: string;
|
|
128
|
+
state: 'target_staged' | 'source_recorded' | 'application_applied';
|
|
129
|
+
applicationResult?: ApplicationResult;
|
|
130
|
+
}>;
|
|
131
|
+
}
|
|
132
|
+
export interface SetupPlan {
|
|
133
|
+
sourceRef: string;
|
|
134
|
+
channelId: string;
|
|
135
|
+
consumerProfileId: string;
|
|
136
|
+
audienceIntent: 'single-current-owner';
|
|
137
|
+
requestedMode: 'display' | 'resume';
|
|
138
|
+
grant: {
|
|
139
|
+
maxClaims: number;
|
|
140
|
+
ttlMs: number;
|
|
141
|
+
eventTypes: string[];
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
export interface OwnerAPI {
|
|
145
|
+
prepareSetup(plan: SetupPlan): Promise<{
|
|
146
|
+
operationId: string;
|
|
147
|
+
planDigest: string;
|
|
148
|
+
ownerPrompt: string;
|
|
149
|
+
}>;
|
|
150
|
+
/** consentReceiptRef comes from trusted owner UI, never from an LLM boolean. */
|
|
151
|
+
commitSetup(input: {
|
|
152
|
+
operationId: string;
|
|
153
|
+
planDigest: string;
|
|
154
|
+
consentReceiptRef: string;
|
|
155
|
+
}): Promise<{
|
|
156
|
+
state: 'complete' | 'partial';
|
|
157
|
+
audienceRef?: string;
|
|
158
|
+
receipts: Json[];
|
|
159
|
+
}>;
|
|
160
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result card (design 6.6): deterministic template built from verified structured facts.
|
|
3
|
+
* Unknown fields are explicitly unknown; a zero exit code does not mean tests/business passed (I14).
|
|
4
|
+
*/
|
|
5
|
+
import type { WatchSpec } from '../contracts/interfaces.js';
|
|
6
|
+
import type { WatchSnapshot } from '../storage/store.js';
|
|
7
|
+
export interface ResultCard {
|
|
8
|
+
schemaVersion: 1;
|
|
9
|
+
resultId: string;
|
|
10
|
+
watchId: string;
|
|
11
|
+
generation: number;
|
|
12
|
+
target: WatchSpec['target'];
|
|
13
|
+
executorState: string;
|
|
14
|
+
checks: Array<{
|
|
15
|
+
checkId: string;
|
|
16
|
+
outcome: string;
|
|
17
|
+
artifactDigest: string | null;
|
|
18
|
+
}>;
|
|
19
|
+
artifactIds: string[];
|
|
20
|
+
businessAcceptance: string;
|
|
21
|
+
health: string;
|
|
22
|
+
observedAt: string;
|
|
23
|
+
summary: string;
|
|
24
|
+
/** display-only declaration: local display copy, not a relay delivery fact */
|
|
25
|
+
deliveryNote?: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function buildResultCard(input: {
|
|
28
|
+
resultId: string;
|
|
29
|
+
spec: WatchSpec;
|
|
30
|
+
snapshot: WatchSnapshot;
|
|
31
|
+
requiredCheckIds: readonly string[];
|
|
32
|
+
health: string;
|
|
33
|
+
observedAtMs: number;
|
|
34
|
+
deliveryNote?: string;
|
|
35
|
+
}): ResultCard;
|
|
36
|
+
export interface GroupResultCard {
|
|
37
|
+
schemaVersion: 1;
|
|
38
|
+
resultId: string;
|
|
39
|
+
watchId: string;
|
|
40
|
+
generation: number;
|
|
41
|
+
target: WatchSpec['target'];
|
|
42
|
+
kind: 'group-summary';
|
|
43
|
+
readyWhen: string;
|
|
44
|
+
members: Array<{
|
|
45
|
+
watchId: string;
|
|
46
|
+
taskState: string;
|
|
47
|
+
lifecycle: string;
|
|
48
|
+
}>;
|
|
49
|
+
overall: 'all-succeeded' | 'all-terminal-with-failures' | 'partial';
|
|
50
|
+
health: string;
|
|
51
|
+
observedAt: string;
|
|
52
|
+
summary: string;
|
|
53
|
+
deliveryNote?: string;
|
|
54
|
+
}
|
|
55
|
+
/** V3 group result card: member projection summary; no member-level acceptance judgment (each member's own card is responsible). */
|
|
56
|
+
export declare function buildGroupResultCard(input: {
|
|
57
|
+
resultId: string;
|
|
58
|
+
spec: WatchSpec;
|
|
59
|
+
members: Array<{
|
|
60
|
+
watchId: string;
|
|
61
|
+
taskState: string;
|
|
62
|
+
lifecycle: string;
|
|
63
|
+
}>;
|
|
64
|
+
readyWhen: 'all_terminal' | 'all_succeeded';
|
|
65
|
+
health: string;
|
|
66
|
+
observedAtMs: number;
|
|
67
|
+
deliveryNote?: string;
|
|
68
|
+
}): GroupResultCard;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result card (design 6.6): deterministic template built from verified structured facts.
|
|
3
|
+
* Unknown fields are explicitly unknown; a zero exit code does not mean tests/business passed (I14).
|
|
4
|
+
*/
|
|
5
|
+
import { completionLevel } from './hard-rules.js';
|
|
6
|
+
export function buildResultCard(input) {
|
|
7
|
+
const { resultId, spec, snapshot, requiredCheckIds, health, observedAtMs, deliveryNote } = input;
|
|
8
|
+
const completion = completionLevel(spec, snapshot, requiredCheckIds);
|
|
9
|
+
const summaryLines = [];
|
|
10
|
+
switch (completion.overall) {
|
|
11
|
+
case 'succeeded-evidence':
|
|
12
|
+
summaryLines.push('Succeeded; required checks passed and required artifacts present (verified tier).');
|
|
13
|
+
break;
|
|
14
|
+
case 'failed':
|
|
15
|
+
summaryLines.push(`Failed (exitCode=${snapshot.exitCode ?? 'unknown'}); root cause may still be unknown.`);
|
|
16
|
+
break;
|
|
17
|
+
case 'cancelled':
|
|
18
|
+
summaryLines.push('Execution was cancelled.');
|
|
19
|
+
break;
|
|
20
|
+
case 'incomplete-evidence':
|
|
21
|
+
summaryLines.push('Reported terminal but completion evidence is incomplete (missing checks or artifacts).');
|
|
22
|
+
break;
|
|
23
|
+
case 'in-progress':
|
|
24
|
+
summaryLines.push(`Still running (state=${snapshot.taskState}).`);
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
if (completion.businessAcceptance === 'pending_host') {
|
|
28
|
+
summaryLines.push('Business acceptance pending host (pending_host).');
|
|
29
|
+
}
|
|
30
|
+
const card = {
|
|
31
|
+
schemaVersion: 1,
|
|
32
|
+
resultId,
|
|
33
|
+
watchId: spec.watchId,
|
|
34
|
+
generation: spec.generation,
|
|
35
|
+
target: spec.target,
|
|
36
|
+
executorState: completion.executorState,
|
|
37
|
+
checks: completion.checks,
|
|
38
|
+
artifactIds: snapshot.artifactIds ?? [],
|
|
39
|
+
businessAcceptance: completion.businessAcceptance,
|
|
40
|
+
health,
|
|
41
|
+
observedAt: new Date(observedAtMs).toISOString(),
|
|
42
|
+
summary: summaryLines.join(' ')
|
|
43
|
+
};
|
|
44
|
+
if (deliveryNote) {
|
|
45
|
+
card.deliveryNote = deliveryNote;
|
|
46
|
+
}
|
|
47
|
+
return card;
|
|
48
|
+
}
|
|
49
|
+
/** V3 group result card: member projection summary; no member-level acceptance judgment (each member's own card is responsible). */
|
|
50
|
+
export function buildGroupResultCard(input) {
|
|
51
|
+
const { resultId, spec, members, readyWhen, health, observedAtMs, deliveryNote } = input;
|
|
52
|
+
const succeeded = members.filter(m => m.taskState === 'succeeded').length;
|
|
53
|
+
const terminal = members.filter(m => ['succeeded', 'failed', 'cancelled'].includes(m.taskState)).length;
|
|
54
|
+
const overall = members.length > 0 && succeeded === members.length
|
|
55
|
+
? 'all-succeeded'
|
|
56
|
+
: members.length > 0 && terminal === members.length
|
|
57
|
+
? 'all-terminal-with-failures'
|
|
58
|
+
: 'partial';
|
|
59
|
+
const card = {
|
|
60
|
+
schemaVersion: 1,
|
|
61
|
+
resultId,
|
|
62
|
+
watchId: spec.watchId,
|
|
63
|
+
generation: spec.generation,
|
|
64
|
+
target: spec.target,
|
|
65
|
+
kind: 'group-summary',
|
|
66
|
+
readyWhen,
|
|
67
|
+
members,
|
|
68
|
+
overall,
|
|
69
|
+
health,
|
|
70
|
+
observedAt: new Date(observedAtMs).toISOString(),
|
|
71
|
+
summary: `group ${readyWhen}: ${terminal}/${members.length} terminal, ${succeeded}/${members.length} succeeded`
|
|
72
|
+
};
|
|
73
|
+
if (deliveryNote)
|
|
74
|
+
card.deliveryNote = deliveryNote;
|
|
75
|
+
return card;
|
|
76
|
+
}
|