yam-flow 2.2.1 → 2.3.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.
@@ -9,6 +9,10 @@ export type LoopStageStatus = 'passed' | 'failed' | 'blocked' | 'skipped' | 'par
9
9
  export type LoopEvidenceLevel = 'none' | 'fixture' | 'smoke' | 'local' | 'real';
10
10
  export type ToolIntent = 'read_only' | 'write' | 'destructive' | 'runtime' | 'visual' | 'publish';
11
11
  export type ReadinessState = 'usable' | 'degraded' | 'blocked' | 'unknown';
12
+ export type MissionLaneRole = 'implementer' | 'reviewer' | 'ux_verifier' | 'doctor' | 'other';
13
+ export type MissionAccessMode = 'read_only' | 'write';
14
+ export type MissionLifecycleStatus = 'pending' | 'running' | 'stopped' | 'failed' | 'cancelled';
15
+ export type MissionOutcome = 'passed' | 'failed' | 'blocked' | 'ambiguous';
12
16
  export interface SafetyHit {
13
17
  level: SafetyLevel;
14
18
  reason: string;
@@ -110,6 +114,50 @@ export interface MissionPatchEnvelope {
110
114
  rollback_hint: RollbackHint;
111
115
  truth_status: TruthStatus;
112
116
  }
117
+ export interface MissionContractCheck {
118
+ id: string;
119
+ label: string;
120
+ status: 'pass' | 'fail' | 'warning';
121
+ next_action: string;
122
+ }
123
+ export interface MissionSubagentReceipt {
124
+ schema: 'yam.mission-subagent-receipt.v1';
125
+ receipt_id: string;
126
+ generated_at: string;
127
+ thread_id: string;
128
+ lane_id: string;
129
+ agent_id: string;
130
+ role: MissionLaneRole;
131
+ access_mode: MissionAccessMode;
132
+ lifecycle_status: MissionLifecycleStatus;
133
+ outcome: MissionOutcome;
134
+ assigned_scope: string;
135
+ changed_files: string[];
136
+ verification_evidence: string[];
137
+ remaining_risks: string[];
138
+ checks: MissionContractCheck[];
139
+ blockers: string[];
140
+ warnings: string[];
141
+ completion_eligible: boolean;
142
+ truth_status: TruthStatus;
143
+ }
144
+ export interface MissionCompletionGate {
145
+ schema: 'yam.mission-completion-gate.v1';
146
+ generated_at: string;
147
+ expected_thread_ids: string[];
148
+ receipt_count: number;
149
+ receipts: MissionSubagentReceipt[];
150
+ missing_thread_ids: string[];
151
+ duplicate_thread_ids: string[];
152
+ unexpected_thread_ids: string[];
153
+ invalid_thread_ids: string[];
154
+ read_only_violations: string[];
155
+ blockers: string[];
156
+ warnings: string[];
157
+ ready_to_claim_complete: boolean;
158
+ next_action: string;
159
+ truth_status: TruthStatus;
160
+ }
113
161
  export interface RuntimeBackendEvidence {
114
162
  schema: 'yam.runtime-backend-evidence.v1';
115
163
  backend: RuntimeBackend;
@@ -266,6 +314,8 @@ export interface ProofSummary {
266
314
  unverified?: unknown;
267
315
  visualProvenance?: unknown;
268
316
  missionEnvelope?: unknown;
317
+ missionReceipt?: unknown;
318
+ missionCompletion?: unknown;
269
319
  rollbackHint?: unknown;
270
320
  runtimeBackendEvidence?: unknown;
271
321
  mediaProof?: unknown;
@@ -299,6 +349,8 @@ export interface YamCompletionProof {
299
349
  runtime: string[];
300
350
  visualProvenance: string[];
301
351
  missionEnvelope: string[];
352
+ missionReceipt: string[];
353
+ missionCompletion: string[];
302
354
  rollbackHint: string[];
303
355
  runtimeBackendEvidence: string[];
304
356
  mediaProof: string[];
@@ -319,6 +371,8 @@ export declare function buildUeyeSurfaceContext(input?: Partial<UeyeSurfaceConte
319
371
  export declare function buildUeyeDesignCompletionGate(input?: Partial<UeyeDesignCompletionGate> & Record<string, unknown>): UeyeDesignCompletionGate;
320
372
  export declare function buildRollbackHint(input?: Partial<RollbackHint>): RollbackHint;
321
373
  export declare function buildMissionPatchEnvelope(input?: Partial<MissionPatchEnvelope>): MissionPatchEnvelope;
374
+ export declare function buildMissionSubagentReceipt(input?: Partial<MissionSubagentReceipt> & Record<string, unknown>): MissionSubagentReceipt;
375
+ export declare function buildMissionCompletionGate(input?: Partial<MissionCompletionGate> & Record<string, unknown>): MissionCompletionGate;
322
376
  export declare function buildRuntimeBackendEvidence(input?: Partial<RuntimeBackendEvidence>): RuntimeBackendEvidence;
323
377
  export declare function buildStudyNote(input?: Partial<StudyNote> & Record<string, unknown>): StudyNote;
324
378
  export declare function buildLoopReport(input?: Partial<LoopReport> & Record<string, unknown>): LoopReport;
@@ -162,6 +162,142 @@ export function buildMissionPatchEnvelope(input = {}) {
162
162
  truth_status: isTruthStatus(input.truth_status) ? input.truth_status : 'partial'
163
163
  };
164
164
  }
165
+ export function buildMissionSubagentReceipt(input = {}) {
166
+ const role = normalizeMissionLaneRole(input.role);
167
+ const accessMode = normalizeMissionAccessMode(input.access_mode, role);
168
+ const lifecycleStatus = normalizeMissionLifecycleStatus(input.lifecycle_status);
169
+ const outcome = normalizeMissionOutcome(input.outcome);
170
+ const threadId = String(input.thread_id || '');
171
+ const assignedScope = String(input.assigned_scope || '');
172
+ const changedFiles = asList(input.changed_files);
173
+ const verificationEvidence = asList(input.verification_evidence);
174
+ const remainingRisks = asList(input.remaining_risks);
175
+ const readOnlyRole = role === 'reviewer' || role === 'doctor';
176
+ const checks = [
177
+ missionContractCheck('thread_id', 'Thread id is recorded', Boolean(threadId), 'record --thread-id for every spawned thread'),
178
+ missionContractCheck('assigned_scope', 'Assigned scope is recorded', Boolean(assignedScope), 'record the bounded lane scope'),
179
+ missionContractCheck('read_only_role', 'Reviewer and doctor lanes stay read-only', !readOnlyRole || (accessMode === 'read_only' && changedFiles.length === 0), 'rerun reviewer/doctor without write access or changed files'),
180
+ missionContractCheck('terminal_lifecycle', 'Lifecycle reached a terminal state', ['stopped', 'failed', 'cancelled'].includes(lifecycleStatus), 'wait for a terminal lifecycle event before finalization'),
181
+ missionContractCheck('explicit_outcome', 'Outcome is explicit and unambiguous', outcome !== 'ambiguous', 'record passed, failed, or blocked; a stop event alone is not success evidence'),
182
+ missionContractCheck('passed_evidence', 'Passed outcome has verification evidence', outcome !== 'passed' || verificationEvidence.length > 0, 'attach at least one concrete verification item for a passed outcome')
183
+ ];
184
+ const blockers = checks.filter((check) => check.status === 'fail').map((check) => `${check.id}: ${check.next_action}`);
185
+ if (outcome === 'failed' || outcome === 'blocked')
186
+ blockers.push(`outcome_${outcome}: resolve or hand off this thread before mission completion`);
187
+ if ((lifecycleStatus === 'failed' || lifecycleStatus === 'cancelled') && outcome === 'passed') {
188
+ blockers.push('lifecycle_outcome_conflict: a failed or cancelled lifecycle cannot claim a passed outcome');
189
+ }
190
+ const warnings = remainingRisks.length ? ['remaining risks are recorded and must be included in the mission handoff'] : [];
191
+ const completionEligible = blockers.length === 0 && lifecycleStatus === 'stopped' && outcome === 'passed';
192
+ const truth = completionEligible ? 'verified' : blockers.length ? 'blocked' : 'partial';
193
+ return {
194
+ schema: 'yam.mission-subagent-receipt.v1',
195
+ receipt_id: String(input.receipt_id || `receipt-${threadId || 'unassigned'}`),
196
+ generated_at: String(input.generated_at || new Date().toISOString()),
197
+ thread_id: threadId,
198
+ lane_id: String(input.lane_id || threadId),
199
+ agent_id: String(input.agent_id || ''),
200
+ role,
201
+ access_mode: accessMode,
202
+ lifecycle_status: lifecycleStatus,
203
+ outcome,
204
+ assigned_scope: assignedScope,
205
+ changed_files: changedFiles,
206
+ verification_evidence: verificationEvidence,
207
+ remaining_risks: remainingRisks,
208
+ checks,
209
+ blockers: uniqueStrings(blockers),
210
+ warnings,
211
+ completion_eligible: completionEligible,
212
+ truth_status: truth
213
+ };
214
+ }
215
+ export function buildMissionCompletionGate(input = {}) {
216
+ const expectedThreadIds = uniqueStrings(asList(input.expected_thread_ids));
217
+ const receipts = asObjectList(input.receipts).map((receipt) => buildMissionSubagentReceipt(receipt));
218
+ const receiptCounts = new Map();
219
+ for (const receipt of receipts) {
220
+ receiptCounts.set(receipt.thread_id, (receiptCounts.get(receipt.thread_id) || 0) + 1);
221
+ }
222
+ const missingThreadIds = expectedThreadIds.filter((threadId) => !receiptCounts.has(threadId));
223
+ const duplicateThreadIds = [...receiptCounts.entries()].filter(([, count]) => count > 1).map(([threadId]) => threadId).filter(Boolean);
224
+ const unexpectedThreadIds = uniqueStrings(receipts.map((receipt) => receipt.thread_id).filter((threadId) => threadId && !expectedThreadIds.includes(threadId)));
225
+ const expectedReceipts = receipts.filter((receipt) => expectedThreadIds.includes(receipt.thread_id));
226
+ const invalidThreadIds = uniqueStrings([
227
+ ...expectedReceipts.filter((receipt) => !receipt.completion_eligible).map((receipt) => receipt.thread_id),
228
+ ...receipts.filter((receipt) => !receipt.thread_id).map((receipt) => `missing:${receipt.receipt_id}`)
229
+ ]);
230
+ const readOnlyViolations = uniqueStrings(receipts
231
+ .filter((receipt) => receipt.checks.some((check) => check.id === 'read_only_role' && check.status === 'fail'))
232
+ .map((receipt) => receipt.thread_id || receipt.lane_id));
233
+ const blockers = [];
234
+ if (!expectedThreadIds.length)
235
+ blockers.push('expected_thread_inventory_missing: record every spawned thread before finalization');
236
+ if (missingThreadIds.length)
237
+ blockers.push(`missing_receipts: ${missingThreadIds.join(', ')}`);
238
+ if (duplicateThreadIds.length)
239
+ blockers.push(`duplicate_receipts: ${duplicateThreadIds.join(', ')}`);
240
+ if (unexpectedThreadIds.length)
241
+ blockers.push(`unexpected_receipts: ${unexpectedThreadIds.join(', ')}`);
242
+ if (invalidThreadIds.length)
243
+ blockers.push(`invalid_or_incomplete_receipts: ${invalidThreadIds.join(', ')}`);
244
+ if (readOnlyViolations.length)
245
+ blockers.push(`read_only_contract_violations: ${readOnlyViolations.join(', ')}`);
246
+ blockers.push(...asList(input.blockers));
247
+ const warnings = uniqueStrings(asList(input.warnings));
248
+ const ready = blockers.length === 0 && expectedReceipts.length === expectedThreadIds.length;
249
+ return {
250
+ schema: 'yam.mission-completion-gate.v1',
251
+ generated_at: String(input.generated_at || new Date().toISOString()),
252
+ expected_thread_ids: expectedThreadIds,
253
+ receipt_count: receipts.length,
254
+ receipts,
255
+ missing_thread_ids: missingThreadIds,
256
+ duplicate_thread_ids: duplicateThreadIds,
257
+ unexpected_thread_ids: unexpectedThreadIds,
258
+ invalid_thread_ids: invalidThreadIds,
259
+ read_only_violations: readOnlyViolations,
260
+ blockers: uniqueStrings(blockers),
261
+ warnings,
262
+ ready_to_claim_complete: ready,
263
+ next_action: ready ? 'attach this gate to the mission proof summary' : blockers[0] || 'resolve mission receipt warnings',
264
+ truth_status: ready ? 'verified' : 'blocked'
265
+ };
266
+ }
267
+ function missionContractCheck(id, label, passed, nextAction) {
268
+ return {
269
+ id,
270
+ label,
271
+ status: passed ? 'pass' : 'fail',
272
+ next_action: nextAction
273
+ };
274
+ }
275
+ function normalizeMissionLaneRole(value = '') {
276
+ const text = String(value || '').toLowerCase().replace(/[- ]/g, '_');
277
+ if (['implementer', 'reviewer', 'ux_verifier', 'doctor'].includes(text))
278
+ return text;
279
+ return 'other';
280
+ }
281
+ function normalizeMissionAccessMode(value = '', role = 'other') {
282
+ const text = String(value || '').toLowerCase().replace(/[- ]/g, '_');
283
+ if (text === 'read_only' || text === 'readonly')
284
+ return 'read_only';
285
+ if (text === 'write')
286
+ return 'write';
287
+ return role === 'reviewer' || role === 'doctor' ? 'read_only' : 'write';
288
+ }
289
+ function normalizeMissionLifecycleStatus(value = '') {
290
+ const text = String(value || '').toLowerCase();
291
+ if (['pending', 'running', 'stopped', 'failed', 'cancelled'].includes(text))
292
+ return text;
293
+ return 'pending';
294
+ }
295
+ function normalizeMissionOutcome(value = '') {
296
+ const text = String(value || '').toLowerCase();
297
+ if (['passed', 'failed', 'blocked'].includes(text))
298
+ return text;
299
+ return 'ambiguous';
300
+ }
165
301
  export function buildRuntimeBackendEvidence(input = {}) {
166
302
  const backend = normalizeRuntimeBackend(input.backend);
167
303
  const claim = normalizeRuntimeClaim(input.claim);
@@ -661,6 +797,12 @@ export function classifyEvidenceTruth(summary = {}, options = {}) {
661
797
  for (const envelope of asList(summary.missionEnvelope)) {
662
798
  rows.push(evidenceRow('evidence', envelope, metadataTruth(envelope, 'partial')));
663
799
  }
800
+ for (const receipt of asList(summary.missionReceipt)) {
801
+ rows.push(evidenceRow('evidence', receipt, metadataTruth(receipt, 'partial')));
802
+ }
803
+ for (const completion of asList(summary.missionCompletion)) {
804
+ rows.push(evidenceRow('evidence', completion, metadataTruth(completion, 'partial')));
805
+ }
664
806
  for (const rollback of asList(summary.rollbackHint)) {
665
807
  rows.push(evidenceRow('evidence', rollback, metadataTruth(rollback, 'partial')));
666
808
  }
@@ -715,6 +857,22 @@ export function applyProofTruthCaps(summary = {}, options = {}) {
715
857
  unverified.push('ueye_design_completion_not_verified');
716
858
  }
717
859
  }
860
+ const missionRoute = String(summary.route || '').replace(/^\$/, '').toLowerCase() === 'mission';
861
+ const missionCompletionValues = asValueList(summary.missionCompletion);
862
+ if (missionRoute && ['verified', 'proven'].includes(requested)) {
863
+ if (!missionCompletionValues.length) {
864
+ cappedTruth = weakestTruth(cappedTruth, 'partial');
865
+ unverified.push('mission_completion_gate_missing');
866
+ }
867
+ else {
868
+ const missionTruth = missionCompletionCap(missionCompletionValues);
869
+ cappedTruth = weakestTruth(cappedTruth, missionTruth);
870
+ if (missionTruth === 'blocked')
871
+ blockers.push('mission_completion_gate_blocked');
872
+ if (!['verified', 'proven'].includes(missionTruth))
873
+ unverified.push('mission_completion_not_verified');
874
+ }
875
+ }
718
876
  if (requested !== cappedTruth) {
719
877
  caps.push(`requested ${requested} capped to ${cappedTruth} by evidence`);
720
878
  }
@@ -769,6 +927,8 @@ export function buildYamCompletionProof(summary = {}, options = {}) {
769
927
  runtime: asList(summary.runtime),
770
928
  visualProvenance: asList(summary.visualProvenance),
771
929
  missionEnvelope: asList(summary.missionEnvelope),
930
+ missionReceipt: asList(summary.missionReceipt),
931
+ missionCompletion: asList(summary.missionCompletion),
772
932
  rollbackHint: asList(summary.rollbackHint),
773
933
  runtimeBackendEvidence: asList(summary.runtimeBackendEvidence),
774
934
  mediaProof: asList(summary.mediaProof),
@@ -854,6 +1014,18 @@ function designCompletionCap(value = '') {
854
1014
  return null;
855
1015
  return items.reduce((weakest, item) => weakestTruth(weakest, readStructuredTruth(item, 'partial')), 'proven');
856
1016
  }
1017
+ function missionCompletionCap(value = '') {
1018
+ const items = asValueList(value);
1019
+ if (!items.length)
1020
+ return 'partial';
1021
+ return items.reduce((weakest, item) => {
1022
+ const parsed = parseStructuredValue(item);
1023
+ if (!parsed || typeof parsed !== 'object')
1024
+ return weakestTruth(weakest, 'blocked');
1025
+ const gate = buildMissionCompletionGate(parsed);
1026
+ return weakestTruth(weakest, gate.truth_status);
1027
+ }, 'proven');
1028
+ }
857
1029
  function strongestSupportedTruth(rows = []) {
858
1030
  if (rows.some((row) => row.truth === 'blocked'))
859
1031
  return 'blocked';
@@ -977,6 +1149,28 @@ function asList(value) {
977
1149
  return [];
978
1150
  return Array.isArray(value) ? value.filter(Boolean).map(String) : [String(value)].filter(Boolean);
979
1151
  }
1152
+ function asValueList(value) {
1153
+ if (value === undefined || value === null || value === '')
1154
+ return [];
1155
+ return Array.isArray(value) ? value.filter(Boolean) : [value];
1156
+ }
1157
+ function parseStructuredValue(value) {
1158
+ if (value && typeof value === 'object')
1159
+ return value;
1160
+ try {
1161
+ const parsed = JSON.parse(String(value || ''));
1162
+ return parsed && typeof parsed === 'object' ? parsed : null;
1163
+ }
1164
+ catch {
1165
+ return null;
1166
+ }
1167
+ }
1168
+ function asObjectList(value) {
1169
+ if (!value)
1170
+ return [];
1171
+ const items = Array.isArray(value) ? value : [value];
1172
+ return items.filter((item) => Boolean(item) && typeof item === 'object');
1173
+ }
980
1174
  function uniqueStrings(values = []) {
981
1175
  return [...new Set(values.filter(Boolean).map(String))];
982
1176
  }
@@ -0,0 +1,98 @@
1
+ export interface InspectedImageFile {
2
+ path: string;
3
+ sha256: string;
4
+ bytes: number;
5
+ dimensions: string;
6
+ }
7
+ export interface UeyeAssetEntry {
8
+ id: string;
9
+ file_path: string;
10
+ source_url: string;
11
+ source_page_url: string;
12
+ retrieved_at: string;
13
+ license_note: string;
14
+ operator_provided: boolean;
15
+ do_not_replace: boolean;
16
+ allowed_for_edit: boolean;
17
+ sha256: string;
18
+ bytes: number;
19
+ dimensions: string;
20
+ }
21
+ export interface UeyeAssetManifest {
22
+ schema: 'yam.ueye-asset-manifest.v1';
23
+ updated_at: string;
24
+ assets: UeyeAssetEntry[];
25
+ }
26
+ export interface UeyeRevisionEntry {
27
+ artifact_id: string;
28
+ round: number;
29
+ source_path: string;
30
+ archived_path: string;
31
+ archived_at: string;
32
+ sha256: string;
33
+ bytes: number;
34
+ dimensions: string;
35
+ }
36
+ export interface UeyeRevisionHistory {
37
+ schema: 'yam.ueye-revision-history.v1';
38
+ updated_at: string;
39
+ revisions: UeyeRevisionEntry[];
40
+ }
41
+ export declare function inspectImageFile(file: string): Promise<InspectedImageFile>;
42
+ export declare function upsertUeyeAsset(input: {
43
+ manifest_path: string;
44
+ id: string;
45
+ file: string;
46
+ source_url?: string;
47
+ source_page_url?: string;
48
+ retrieved_at?: string;
49
+ license_note?: string;
50
+ operator_provided?: boolean;
51
+ do_not_replace?: boolean;
52
+ allowed_for_edit?: boolean;
53
+ replace?: boolean;
54
+ }): Promise<{
55
+ schema: string;
56
+ action: string;
57
+ manifest_path: string;
58
+ asset: UeyeAssetEntry;
59
+ warnings: string[];
60
+ next_action: string;
61
+ truth_status: string;
62
+ }>;
63
+ export declare function verifyUeyeAssetManifest(manifestFile: string): Promise<{
64
+ schema: string;
65
+ manifest_path: string;
66
+ asset_count: number;
67
+ protected_count: number;
68
+ editable_count: number;
69
+ errors: string[];
70
+ warnings: string[];
71
+ ready: boolean;
72
+ next_action: string;
73
+ truth_status: string;
74
+ }>;
75
+ export declare function archiveUeyeRevision(input: {
76
+ file: string;
77
+ root: string;
78
+ manifest_path?: string;
79
+ round: number;
80
+ artifact_id?: string;
81
+ }): Promise<{
82
+ schema: string;
83
+ action: string;
84
+ manifest_path: string;
85
+ revision: UeyeRevisionEntry;
86
+ next_action: string;
87
+ truth_status: string;
88
+ }>;
89
+ export declare function verifyUeyeRevisionHistory(manifestFile: string): Promise<{
90
+ schema: string;
91
+ manifest_path: string;
92
+ revision_count: number;
93
+ rounds: number[];
94
+ errors: string[];
95
+ ready: boolean;
96
+ next_action: string;
97
+ truth_status: string;
98
+ }>;