yam-flow 2.2.0 → 2.3.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 +23 -0
- package/COMMANDS.md +8 -1
- package/README.md +12 -0
- package/ROADMAP.md +25 -0
- package/dist/bin/yam.js +237 -62
- package/dist/lib/trust-kernel.d.ts +54 -0
- package/dist/lib/trust-kernel.js +194 -0
- package/dist/lib/ueye-artifacts.d.ts +98 -0
- package/dist/lib/ueye-artifacts.js +317 -0
- package/package.json +1 -1
- package/references/final-report.md +2 -0
- package/references/honest-completion.md +1 -0
- package/references/mission.md +16 -0
- package/references/scout-wide-scan.md +150 -0
- package/references/scout.md +9 -0
- package/references/tool-trust-layer.md +3 -0
- package/references/trust-kernel.md +3 -0
- package/references/truth-matrix.md +2 -0
- package/references/ueye-proof.md +6 -0
- package/references/ueye.md +12 -0
- package/references/verification-levels.md +2 -1
- package/skills/mission/SKILL.md +19 -4
- package/skills/scout/SKILL.md +18 -5
- package/skills/ueye/SKILL.md +11 -0
- package/src/bin/yam.ts +227 -56
- package/src/lib/trust-kernel.ts +239 -0
- package/src/lib/ueye-artifacts.ts +354 -0
- package/templates/mission-plan.md +25 -2
- package/templates/ueye-comparison.md +12 -0
- package/templates/ueye-review.md +26 -0
- package/yam.manifest.json +7 -7
package/src/bin/yam.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from 'node:child_process';
|
|
3
|
-
import crypto from 'node:crypto';
|
|
4
3
|
import fs from 'node:fs';
|
|
5
4
|
import fsp from 'node:fs/promises';
|
|
6
5
|
import { createRequire } from 'node:module';
|
|
@@ -12,7 +11,9 @@ import {
|
|
|
12
11
|
TRUTH_STATUSES,
|
|
13
12
|
buildLoopReport,
|
|
14
13
|
buildMediaGenerationProof,
|
|
14
|
+
buildMissionCompletionGate,
|
|
15
15
|
buildMissionPatchEnvelope,
|
|
16
|
+
buildMissionSubagentReceipt,
|
|
16
17
|
buildRollbackHint,
|
|
17
18
|
buildRuntimeBackendEvidence,
|
|
18
19
|
buildStudyNote,
|
|
@@ -25,6 +26,13 @@ import {
|
|
|
25
26
|
isTruthStatus
|
|
26
27
|
} from '../lib/trust-kernel.js';
|
|
27
28
|
import type { LoopEvidenceLevel, ReadinessState, ToolIntent } from '../lib/trust-kernel.js';
|
|
29
|
+
import {
|
|
30
|
+
archiveUeyeRevision,
|
|
31
|
+
inspectImageFile,
|
|
32
|
+
upsertUeyeAsset,
|
|
33
|
+
verifyUeyeAssetManifest,
|
|
34
|
+
verifyUeyeRevisionHistory
|
|
35
|
+
} from '../lib/ueye-artifacts.js';
|
|
28
36
|
|
|
29
37
|
type AnyRecord = Record<string, any>;
|
|
30
38
|
|
|
@@ -151,9 +159,13 @@ Usage:
|
|
|
151
159
|
yam ueye compare --reference ref.png --actual screenshot.png [--json]
|
|
152
160
|
yam ueye preflight [dir] [--json]
|
|
153
161
|
yam ueye report [--reference ref.png] [--actual screenshot.png] [--provider-context local] [--execution-surface in-app-browser] [--json]
|
|
162
|
+
yam ueye asset <add|verify> [options]
|
|
163
|
+
yam ueye revision <archive|verify> [options]
|
|
154
164
|
yam media proof [--requested] [--attempted] [--output file] [--json]
|
|
155
165
|
yam runtime evidence [--backend terminal|in-app-browser|playwright|tmux|zellij] [--claim observed|started|stopped|cleanup-verified] [--json]
|
|
156
166
|
yam mission queue [--agent-id id] [--scope text] [--changed file] [--verification-hint text] [--json]
|
|
167
|
+
yam mission receipt [--thread-id id] [--role reviewer] [--lifecycle stopped] [--outcome passed] [--evidence text] [--json]
|
|
168
|
+
yam mission gate [--expected-thread id] [--receipt file] [--json]
|
|
157
169
|
yam benchmark report [--baseline n] [--current n] [--unit ms] [--target lower|higher] [--json]
|
|
158
170
|
yam release report [--json]
|
|
159
171
|
yam safety [text...]
|
|
@@ -367,7 +379,7 @@ async function verify({ quiet = false } = {}) {
|
|
|
367
379
|
for (const template of ['ueye-review.md', 'ueye-comparison.md', 'mission-plan.md', 'runtime-proof.md', 'tuning-log.md']) {
|
|
368
380
|
if (!await exists(path.join(ROOT, 'templates', template))) issues.push(`missing template: ${template}`);
|
|
369
381
|
}
|
|
370
|
-
for (const module of ['trust-kernel.js']) {
|
|
382
|
+
for (const module of ['trust-kernel.js', 'ueye-artifacts.js']) {
|
|
371
383
|
if (!await exists(path.join(ROOT, 'dist', 'lib', module))) issues.push(`missing dist lib module: ${module}`);
|
|
372
384
|
}
|
|
373
385
|
|
|
@@ -2251,6 +2263,8 @@ async function ueye(args = []) {
|
|
|
2251
2263
|
if (subcommand === 'compare') return ueyeCompare(args.slice(1));
|
|
2252
2264
|
if (subcommand === 'preflight') return ueyePreflight(args.slice(1));
|
|
2253
2265
|
if (subcommand === 'report') return ueyeReport(args.slice(1));
|
|
2266
|
+
if (subcommand === 'asset') return ueyeAsset(args.slice(1));
|
|
2267
|
+
if (subcommand === 'revision') return ueyeRevision(args.slice(1));
|
|
2254
2268
|
console.error(`unknown ueye command: ${subcommand}`);
|
|
2255
2269
|
return ueyeUsage();
|
|
2256
2270
|
}
|
|
@@ -2264,15 +2278,92 @@ Usage:
|
|
|
2264
2278
|
yam ueye capture --url URL --out screenshot.png [--viewport 1440x900] [--full-page] [--json]
|
|
2265
2279
|
yam ueye compare --reference ref.png --actual screenshot.png [--json]
|
|
2266
2280
|
yam ueye preflight [dir] [--json]
|
|
2267
|
-
yam ueye report [--reference ref.png] [--actual screenshot.png] [--preflight-id id] [--p0-risk text] [--quality-gate-note text] [--brief-dimension text] [--constraint text] [--anti-slop text] [--invented-metric] [--placeholder-copy] [--generic-visual] [--acceptance-criterion text] [--touched-file file] [--read-file file] [--verified-file file] [--skipped-check text] [--residual-risk text] [--stop-condition text] [--resume-hint text] [--deep-visual-check text] [--design-system-evidence text] [--implementation-evidence text] [--state-check default:pass] [--review-session-id id] [--provider-context local] [--execution-surface in-app-browser] [--app-surface codex-app] [--browser-surface in-app-browser] [--control-mode manual|automated] [--preserved-state] [--preserved-url URL] [--completion-claim draft|needs-polish|done] [--strict] [--design-score n] [--p0 text] [--p1 text] [--states-checked] [--mobile-checked] [--contrast-checked] [--similar text] [--different text] [--missing text] [--resolved text] [--new-finding text] [--still-open text] [--regression text] [--viewport 1440x900] [--state default] [--design-quality pass|needs-polish|fails|not-checked] [--json]
|
|
2281
|
+
yam ueye report [--reference ref.png] [--actual screenshot.png] [--asset-manifest file] [--revision-manifest file] [--preflight-id id] [--p0-risk text] [--quality-gate-note text] [--brief-dimension text] [--constraint text] [--anti-slop text] [--invented-metric] [--placeholder-copy] [--generic-visual] [--acceptance-criterion text] [--touched-file file] [--read-file file] [--verified-file file] [--skipped-check text] [--residual-risk text] [--stop-condition text] [--resume-hint text] [--deep-visual-check text] [--design-system-evidence text] [--implementation-evidence text] [--state-check default:pass] [--review-session-id id] [--provider-context local] [--execution-surface in-app-browser] [--app-surface codex-app] [--browser-surface in-app-browser] [--control-mode manual|automated] [--preserved-state] [--preserved-url URL] [--completion-claim draft|needs-polish|done] [--strict] [--design-score n] [--p0 text] [--p1 text] [--states-checked] [--mobile-checked] [--contrast-checked] [--similar text] [--different text] [--missing text] [--resolved text] [--new-finding text] [--still-open text] [--regression text] [--viewport 1440x900] [--state default] [--design-quality pass|needs-polish|fails|not-checked] [--json]
|
|
2282
|
+
yam ueye asset add [--manifest file] --id id --file image [--source-url URL] [--source-page-url URL] [--license-note text] [--operator-provided] [--do-not-replace] [--allowed-for-edit] [--json]
|
|
2283
|
+
yam ueye asset verify [--manifest file] [--json]
|
|
2284
|
+
yam ueye revision archive --file image --round n [--artifact-id id] [--root dir] [--manifest file] [--json]
|
|
2285
|
+
yam ueye revision verify [--root dir] [--manifest file] [--json]
|
|
2268
2286
|
|
|
2269
2287
|
Notes:
|
|
2270
2288
|
capture uses a locally available Playwright install when present. It does not download browsers or install dependencies.
|
|
2271
2289
|
compare uses local files only and reports sha256, dimensions, comparison_result, and proof-ready visual provenance.
|
|
2272
2290
|
report produces a proof-ready Ueye visual run report, design completion gate, and continuity/comparison record without requiring a new capture.
|
|
2291
|
+
asset records local reference provenance and protection flags; it never downloads remote content.
|
|
2292
|
+
revision copies the current local artifact into a non-overwriting round archive before the live file is edited.
|
|
2273
2293
|
`);
|
|
2274
2294
|
}
|
|
2275
2295
|
|
|
2296
|
+
async function ueyeAsset(args = []) {
|
|
2297
|
+
const subcommand = args[0] || 'help';
|
|
2298
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') return ueyeUsage();
|
|
2299
|
+
const flags = parseSimpleFlags(args.slice(1), new Set(['manifest', 'id', 'file', 'source-url', 'source-page-url', 'retrieved-at', 'license-note', 'operator-provided', 'do-not-replace', 'allowed-for-edit', 'replace', 'json']));
|
|
2300
|
+
const manifest = path.resolve(expandHome(flags.manifest || path.join(process.cwd(), '.yam', 'ueye', 'assets.json')));
|
|
2301
|
+
try {
|
|
2302
|
+
if (subcommand === 'add') {
|
|
2303
|
+
if (!flags.id || !flags.file) throw new Error('asset add requires --id and --file');
|
|
2304
|
+
const result = await upsertUeyeAsset({
|
|
2305
|
+
manifest_path: manifest,
|
|
2306
|
+
id: String(flags.id),
|
|
2307
|
+
file: path.resolve(expandHome(flags.file)),
|
|
2308
|
+
source_url: flags.source_url,
|
|
2309
|
+
source_page_url: flags.source_page_url,
|
|
2310
|
+
retrieved_at: flags.retrieved_at,
|
|
2311
|
+
license_note: flags.license_note,
|
|
2312
|
+
operator_provided: flags.operator_provided ? true : undefined,
|
|
2313
|
+
do_not_replace: flags.do_not_replace ? true : undefined,
|
|
2314
|
+
allowed_for_edit: flags.allowed_for_edit ? true : undefined,
|
|
2315
|
+
replace: Boolean(flags.replace)
|
|
2316
|
+
});
|
|
2317
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Ueye asset');
|
|
2318
|
+
return;
|
|
2319
|
+
}
|
|
2320
|
+
if (subcommand === 'verify') {
|
|
2321
|
+
const result = await verifyUeyeAssetManifest(manifest);
|
|
2322
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Ueye asset verification');
|
|
2323
|
+
if (result.truth_status === 'blocked') process.exitCode = 1;
|
|
2324
|
+
return;
|
|
2325
|
+
}
|
|
2326
|
+
throw new Error(`unknown ueye asset command: ${subcommand}`);
|
|
2327
|
+
} catch (error) {
|
|
2328
|
+
const result = { schema: 'yam.ueye-asset-error.v1', status: 'blocked', reason: errorMessage(error), truth_status: 'blocked' };
|
|
2329
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Ueye asset');
|
|
2330
|
+
process.exitCode = 1;
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
async function ueyeRevision(args = []) {
|
|
2335
|
+
const subcommand = args[0] || 'help';
|
|
2336
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') return ueyeUsage();
|
|
2337
|
+
const flags = parseSimpleFlags(args.slice(1), new Set(['file', 'round', 'artifact-id', 'root', 'manifest', 'json']));
|
|
2338
|
+
const root = path.resolve(expandHome(flags.root || path.join(process.cwd(), '.yam', 'ueye', 'revisions')));
|
|
2339
|
+
const manifest = path.resolve(expandHome(flags.manifest || path.join(root, 'manifest.json')));
|
|
2340
|
+
try {
|
|
2341
|
+
if (subcommand === 'archive') {
|
|
2342
|
+
if (!flags.file || !flags.round) throw new Error('revision archive requires --file and --round');
|
|
2343
|
+
const result = await archiveUeyeRevision({
|
|
2344
|
+
file: path.resolve(expandHome(flags.file)),
|
|
2345
|
+
root,
|
|
2346
|
+
manifest_path: manifest,
|
|
2347
|
+
round: Number(flags.round),
|
|
2348
|
+
artifact_id: flags.artifact_id
|
|
2349
|
+
});
|
|
2350
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Ueye revision');
|
|
2351
|
+
return;
|
|
2352
|
+
}
|
|
2353
|
+
if (subcommand === 'verify') {
|
|
2354
|
+
const result = await verifyUeyeRevisionHistory(manifest);
|
|
2355
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Ueye revision verification');
|
|
2356
|
+
if (result.truth_status === 'blocked') process.exitCode = 1;
|
|
2357
|
+
return;
|
|
2358
|
+
}
|
|
2359
|
+
throw new Error(`unknown ueye revision command: ${subcommand}`);
|
|
2360
|
+
} catch (error) {
|
|
2361
|
+
const result = { schema: 'yam.ueye-revision-error.v1', status: 'blocked', reason: errorMessage(error), truth_status: 'blocked' };
|
|
2362
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Ueye revision');
|
|
2363
|
+
process.exitCode = 1;
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2276
2367
|
async function ueyePreflight(args = []) {
|
|
2277
2368
|
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') return ueyeUsage();
|
|
2278
2369
|
const parsed = parseDirJsonArgs(args);
|
|
@@ -2501,7 +2592,7 @@ async function ueyeCompare(args = []) {
|
|
|
2501
2592
|
|
|
2502
2593
|
async function ueyeReport(args = []) {
|
|
2503
2594
|
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') return ueyeUsage();
|
|
2504
|
-
const flags = parseSimpleFlags(args, new Set(['reference', 'ref', 'actual', 'screenshot', 'capture-backend', 'compare-backend', 'design-quality', 'blocked-reason', 'next-action', 'next-visual-action', 'review-session-id', 'preflight-id', 'p0-risk', 'quality-gate-note', 'brief-dimension', 'constraint', 'anti-slop', 'invented-metric', 'placeholder-copy', 'generic-visual', 'acceptance-criterion', 'touched-file', 'read-file', 'verified-file', 'skipped-check', 'residual-risk', 'stop-condition', 'resume-hint', 'deep-visual-check', 'design-system-evidence', 'implementation-evidence', 'state-check', 'reference-id', 'screenshot-id', 'previous-screenshot-id', 'current-screenshot-id', 'previous-report', 'comparison-notes', 'similar', 'different', 'missing', 'resolved', 'new-finding', 'still-open', 'regression', 'viewport', 'state', 'provider-context', 'provider-badge', 'execution-surface', 'app-surface', 'browser-surface', 'control-mode', 'preserved-state', 'preserved-url', 'url', 'evidence-id', 'completion-claim', 'completion-status', 'gate-mode', 'strict', 'design-score', 'min-design-score', 'p0', 'p1', 'open-p0', 'open-p1', 'states-checked', 'mobile-checked', 'responsive-checked', 'contrast-checked', 'accessibility-checked', 'cta-checked', 'direction-locked', 'reference-read', 'comparison-result', 'json']));
|
|
2595
|
+
const flags = parseSimpleFlags(args, new Set(['reference', 'ref', 'actual', 'screenshot', 'capture-backend', 'compare-backend', 'design-quality', 'blocked-reason', 'next-action', 'next-visual-action', 'review-session-id', 'preflight-id', 'p0-risk', 'quality-gate-note', 'brief-dimension', 'constraint', 'anti-slop', 'invented-metric', 'placeholder-copy', 'generic-visual', 'acceptance-criterion', 'touched-file', 'read-file', 'verified-file', 'skipped-check', 'residual-risk', 'stop-condition', 'resume-hint', 'deep-visual-check', 'design-system-evidence', 'implementation-evidence', 'state-check', 'asset-manifest', 'revision-manifest', 'reference-id', 'screenshot-id', 'previous-screenshot-id', 'current-screenshot-id', 'previous-report', 'comparison-notes', 'similar', 'different', 'missing', 'resolved', 'new-finding', 'still-open', 'regression', 'viewport', 'state', 'provider-context', 'provider-badge', 'execution-surface', 'app-surface', 'browser-surface', 'control-mode', 'preserved-state', 'preserved-url', 'url', 'evidence-id', 'completion-claim', 'completion-status', 'gate-mode', 'strict', 'design-score', 'min-design-score', 'p0', 'p1', 'open-p0', 'open-p1', 'states-checked', 'mobile-checked', 'responsive-checked', 'contrast-checked', 'accessibility-checked', 'cta-checked', 'direction-locked', 'reference-read', 'comparison-result', 'json']));
|
|
2505
2596
|
const reference = String(flags.reference || flags.ref || '');
|
|
2506
2597
|
const actual = String(flags.actual || flags.screenshot || '');
|
|
2507
2598
|
const referenceSources = [];
|
|
@@ -2580,6 +2671,12 @@ async function ueyeReport(args = []) {
|
|
|
2580
2671
|
blockedReason = errorMessage(error);
|
|
2581
2672
|
}
|
|
2582
2673
|
|
|
2674
|
+
const assetManifest = await optionalUeyeAssetVerification(flags.asset_manifest);
|
|
2675
|
+
const revisionHistory = await optionalUeyeRevisionVerification(flags.revision_manifest);
|
|
2676
|
+
const doneClaim = ['done', 'complete', 'completed'].includes(String(flags.completion_claim || flags.completion_status || '').toLowerCase());
|
|
2677
|
+
if (doneClaim && assetManifest?.truth_status === 'blocked') blockedReason = blockedReason || `asset manifest blocked: ${assetManifest.next_action || 'verification failed'}`;
|
|
2678
|
+
if (doneClaim && revisionHistory?.truth_status === 'blocked') blockedReason = blockedReason || `revision history blocked: ${revisionHistory.next_action || 'verification failed'}`;
|
|
2679
|
+
|
|
2583
2680
|
const report = buildUeyeRunReport({
|
|
2584
2681
|
reference_sources: referenceSources,
|
|
2585
2682
|
implementation_sources: implementationSources,
|
|
@@ -2662,12 +2759,32 @@ async function ueyeReport(args = []) {
|
|
|
2662
2759
|
capture_backend: String(flags.capture_backend || 'not-recorded'),
|
|
2663
2760
|
compare_backend: String(flags.compare_backend || 'local-file-hash'),
|
|
2664
2761
|
continuity: comparisonReport.continuity,
|
|
2665
|
-
comparison_report: comparisonReport
|
|
2762
|
+
comparison_report: comparisonReport,
|
|
2763
|
+
asset_manifest: assetManifest,
|
|
2764
|
+
revision_history: revisionHistory
|
|
2666
2765
|
};
|
|
2667
2766
|
printJsonOrHuman(result, Boolean(flags.json), 'Ueye report');
|
|
2668
2767
|
if (report.truth_status === 'blocked') process.exitCode = 1;
|
|
2669
2768
|
}
|
|
2670
2769
|
|
|
2770
|
+
async function optionalUeyeAssetVerification(file = '') {
|
|
2771
|
+
if (!file) return null;
|
|
2772
|
+
try {
|
|
2773
|
+
return await verifyUeyeAssetManifest(path.resolve(expandHome(file)));
|
|
2774
|
+
} catch (error) {
|
|
2775
|
+
return { schema: 'yam.ueye-asset-verification.v1', truth_status: 'blocked', reason: errorMessage(error), next_action: 'repair or recreate the asset manifest before a done claim' };
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2779
|
+
async function optionalUeyeRevisionVerification(file = '') {
|
|
2780
|
+
if (!file) return null;
|
|
2781
|
+
try {
|
|
2782
|
+
return await verifyUeyeRevisionHistory(path.resolve(expandHome(file)));
|
|
2783
|
+
} catch (error) {
|
|
2784
|
+
return { schema: 'yam.ueye-revision-verification.v1', truth_status: 'blocked', reason: errorMessage(error), next_action: 'repair or recreate the revision manifest before a done claim' };
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2671
2788
|
function buildUeyeDesignBrief(flags: AnyRecord = {}) {
|
|
2672
2789
|
const dimensions = arrayFlag(flags.brief_dimension);
|
|
2673
2790
|
const constraints = arrayFlag(flags.constraint);
|
|
@@ -2942,6 +3059,8 @@ async function mission(args = []) {
|
|
|
2942
3059
|
const subcommand = args[0] || 'help';
|
|
2943
3060
|
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') return missionUsage();
|
|
2944
3061
|
if (subcommand === 'queue' || subcommand === 'patch') return missionQueue(args.slice(1));
|
|
3062
|
+
if (subcommand === 'receipt') return missionReceipt(args.slice(1));
|
|
3063
|
+
if (subcommand === 'gate' || subcommand === 'close') return missionGate(args.slice(1));
|
|
2945
3064
|
console.error(`unknown mission command: ${subcommand}`);
|
|
2946
3065
|
return missionUsage();
|
|
2947
3066
|
}
|
|
@@ -2951,12 +3070,76 @@ function missionUsage() {
|
|
|
2951
3070
|
|
|
2952
3071
|
Usage:
|
|
2953
3072
|
yam mission queue [--lane-id id] [--agent-id id] [--status pending|applied|verified|blocked|reverted] [--scope text] [--changed file] [--depends-on lane] [--generated file] [--verification-hint text] [--rollback-hint text] [--before-check text] [--out file] [--truth status] [--json]
|
|
3073
|
+
yam mission receipt --thread-id id --role implementer|reviewer|ux-verifier|doctor --lifecycle pending|running|stopped|failed|cancelled --outcome passed|failed|blocked|ambiguous [--access-mode read-only|write] [--scope text] [--changed file] [--evidence text] [--remaining-risk text] [--out file] [--json]
|
|
3074
|
+
yam mission gate --expected-thread id --receipt file [--expected-thread id --receipt file] [--out file] [--json]
|
|
2954
3075
|
|
|
2955
3076
|
Notes:
|
|
2956
3077
|
Produces a patch queue item for mission handoff/review. It does not run workers; persistence is opt-in with --out.
|
|
3078
|
+
Reviewer and doctor receipts default to read-only and are blocked if they claim changed files or write access.
|
|
3079
|
+
A stopped lifecycle is not success by itself; a passed outcome needs explicit verification evidence.
|
|
2957
3080
|
`);
|
|
2958
3081
|
}
|
|
2959
3082
|
|
|
3083
|
+
async function missionReceipt(args = []) {
|
|
3084
|
+
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') return missionUsage();
|
|
3085
|
+
const flags = parseSimpleFlags(args, new Set(['receipt-id', 'thread-id', 'lane-id', 'agent-id', 'role', 'access-mode', 'lifecycle', 'outcome', 'scope', 'changed', 'evidence', 'remaining-risk', 'out', 'json']));
|
|
3086
|
+
const receipt = buildMissionSubagentReceipt({
|
|
3087
|
+
receipt_id: String(flags.receipt_id || `receipt-${timestampId()}`),
|
|
3088
|
+
thread_id: String(flags.thread_id || ''),
|
|
3089
|
+
lane_id: String(flags.lane_id || flags.thread_id || ''),
|
|
3090
|
+
agent_id: String(flags.agent_id || ''),
|
|
3091
|
+
role: flags.role,
|
|
3092
|
+
access_mode: flags.access_mode,
|
|
3093
|
+
lifecycle_status: flags.lifecycle,
|
|
3094
|
+
outcome: flags.outcome,
|
|
3095
|
+
assigned_scope: String(flags.scope || ''),
|
|
3096
|
+
changed_files: arrayFlag(flags.changed),
|
|
3097
|
+
verification_evidence: arrayFlag(flags.evidence),
|
|
3098
|
+
remaining_risks: arrayFlag(flags.remaining_risk)
|
|
3099
|
+
});
|
|
3100
|
+
const result = {
|
|
3101
|
+
...receipt,
|
|
3102
|
+
persistence: flags.out ? 'written' : 'not-persisted',
|
|
3103
|
+
out: flags.out ? path.resolve(expandHome(flags.out)) : '',
|
|
3104
|
+
next_action: receipt.completion_eligible ? 'include this receipt in `yam mission gate`' : receipt.blockers[0] || 'complete the receipt evidence'
|
|
3105
|
+
};
|
|
3106
|
+
if (flags.out) await writeJsonArtifact(flags.out, result);
|
|
3107
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Mission receipt');
|
|
3108
|
+
if (receipt.truth_status === 'blocked') process.exitCode = 1;
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
async function missionGate(args = []) {
|
|
3112
|
+
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') return missionUsage();
|
|
3113
|
+
const flags = parseSimpleFlags(args, new Set(['expected-thread', 'receipt', 'out', 'json']));
|
|
3114
|
+
const receipts = [];
|
|
3115
|
+
const loadErrors: string[] = [];
|
|
3116
|
+
for (const file of arrayFlag(flags.receipt)) {
|
|
3117
|
+
const target = path.resolve(expandHome(file));
|
|
3118
|
+
try {
|
|
3119
|
+
receipts.push(await readJson(target));
|
|
3120
|
+
} catch (error) {
|
|
3121
|
+
loadErrors.push(`${target}: ${errorMessage(error)}`);
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
const gate = buildMissionCompletionGate({
|
|
3125
|
+
expected_thread_ids: arrayFlag(flags.expected_thread),
|
|
3126
|
+
receipts
|
|
3127
|
+
});
|
|
3128
|
+
const blockers = [...gate.blockers, ...loadErrors.map((error) => `receipt_load_failed: ${error}`)];
|
|
3129
|
+
const result = {
|
|
3130
|
+
...gate,
|
|
3131
|
+
blockers,
|
|
3132
|
+
ready_to_claim_complete: gate.ready_to_claim_complete && loadErrors.length === 0,
|
|
3133
|
+
next_action: blockers[0] || gate.next_action,
|
|
3134
|
+
truth_status: blockers.length ? 'blocked' : gate.truth_status,
|
|
3135
|
+
persistence: flags.out ? 'written' : 'not-persisted',
|
|
3136
|
+
out: flags.out ? path.resolve(expandHome(flags.out)) : ''
|
|
3137
|
+
};
|
|
3138
|
+
if (flags.out) await writeJsonArtifact(flags.out, result);
|
|
3139
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Mission completion gate');
|
|
3140
|
+
if (result.truth_status === 'blocked') process.exitCode = 1;
|
|
3141
|
+
}
|
|
3142
|
+
|
|
2960
3143
|
async function missionQueue(args = []) {
|
|
2961
3144
|
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') return missionUsage();
|
|
2962
3145
|
const flags = parseSimpleFlags(args, new Set(['lane-id', 'agent-id', 'status', 'scope', 'changed', 'generated', 'depends-on', 'verification-hint', 'rollback-hint', 'before-check', 'safe-revert-note', 'truth', 'intent', 'out', 'json']));
|
|
@@ -3103,7 +3286,7 @@ function parseSimpleFlags(args = [], allowed = new Set<string>()) {
|
|
|
3103
3286
|
const key = rawKey.slice(2);
|
|
3104
3287
|
if (allowed.size && !allowed.has(key)) continue;
|
|
3105
3288
|
const normalizedKey = key.replace(/-/g, '_');
|
|
3106
|
-
if (['json', 'full-page', 'requested', 'attempted', 'available', 'wait-loop', 'cleanup-checked', 'cleanup-observed', 'left-running-intentionally', 'strict', 'preserved-state', 'states-checked', 'mobile-checked', 'responsive-checked', 'contrast-checked', 'accessibility-checked', 'cta-checked', 'direction-locked', 'reference-read', 'invented-metric', 'placeholder-copy', 'generic-visual'].includes(key)) {
|
|
3289
|
+
if (['json', 'full-page', 'requested', 'attempted', 'available', 'wait-loop', 'cleanup-checked', 'cleanup-observed', 'left-running-intentionally', 'strict', 'preserved-state', 'states-checked', 'mobile-checked', 'responsive-checked', 'contrast-checked', 'accessibility-checked', 'cta-checked', 'direction-locked', 'reference-read', 'invented-metric', 'placeholder-copy', 'generic-visual', 'operator-provided', 'do-not-replace', 'allowed-for-edit', 'replace'].includes(key)) {
|
|
3107
3290
|
flags[normalizedKey] = true;
|
|
3108
3291
|
continue;
|
|
3109
3292
|
}
|
|
@@ -3143,53 +3326,7 @@ async function loadOptionalPlaywright() {
|
|
|
3143
3326
|
}
|
|
3144
3327
|
|
|
3145
3328
|
async function imageFileInfo(file) {
|
|
3146
|
-
|
|
3147
|
-
const buffer = await fsp.readFile(absolute);
|
|
3148
|
-
const stat = await fsp.stat(absolute);
|
|
3149
|
-
return {
|
|
3150
|
-
path: absolute,
|
|
3151
|
-
sha256: crypto.createHash('sha256').update(buffer).digest('hex'),
|
|
3152
|
-
bytes: stat.size,
|
|
3153
|
-
dimensions: imageDimensions(buffer)
|
|
3154
|
-
};
|
|
3155
|
-
}
|
|
3156
|
-
|
|
3157
|
-
function imageDimensions(buffer) {
|
|
3158
|
-
const png = pngDimensions(buffer);
|
|
3159
|
-
if (png) return png;
|
|
3160
|
-
const jpeg = jpegDimensions(buffer);
|
|
3161
|
-
if (jpeg) return jpeg;
|
|
3162
|
-
const gif = gifDimensions(buffer);
|
|
3163
|
-
if (gif) return gif;
|
|
3164
|
-
return 'unknown';
|
|
3165
|
-
}
|
|
3166
|
-
|
|
3167
|
-
function pngDimensions(buffer) {
|
|
3168
|
-
if (buffer.length < 24) return '';
|
|
3169
|
-
if (buffer.readUInt32BE(0) !== 0x89504e47 || buffer.readUInt32BE(4) !== 0x0d0a1a0a) return '';
|
|
3170
|
-
return `${buffer.readUInt32BE(16)}x${buffer.readUInt32BE(20)}`;
|
|
3171
|
-
}
|
|
3172
|
-
|
|
3173
|
-
function gifDimensions(buffer) {
|
|
3174
|
-
if (buffer.length < 10) return '';
|
|
3175
|
-
const signature = buffer.toString('ascii', 0, 6);
|
|
3176
|
-
if (signature !== 'GIF87a' && signature !== 'GIF89a') return '';
|
|
3177
|
-
return `${buffer.readUInt16LE(6)}x${buffer.readUInt16LE(8)}`;
|
|
3178
|
-
}
|
|
3179
|
-
|
|
3180
|
-
function jpegDimensions(buffer) {
|
|
3181
|
-
if (buffer.length < 4 || buffer[0] !== 0xff || buffer[1] !== 0xd8) return '';
|
|
3182
|
-
let offset = 2;
|
|
3183
|
-
while (offset < buffer.length) {
|
|
3184
|
-
if (buffer[offset] !== 0xff) return '';
|
|
3185
|
-
const marker = buffer[offset + 1];
|
|
3186
|
-
const length = buffer.readUInt16BE(offset + 2);
|
|
3187
|
-
if (marker >= 0xc0 && marker <= 0xc3 && offset + 8 < buffer.length) {
|
|
3188
|
-
return `${buffer.readUInt16BE(offset + 7)}x${buffer.readUInt16BE(offset + 5)}`;
|
|
3189
|
-
}
|
|
3190
|
-
offset += 2 + length;
|
|
3191
|
-
}
|
|
3192
|
-
return '';
|
|
3329
|
+
return inspectImageFile(path.resolve(expandHome(file)));
|
|
3193
3330
|
}
|
|
3194
3331
|
|
|
3195
3332
|
function printJsonOrHuman(result, json = false, label = 'yam') {
|
|
@@ -3208,6 +3345,12 @@ function printJsonOrHuman(result, json = false, label = 'yam') {
|
|
|
3208
3345
|
}
|
|
3209
3346
|
}
|
|
3210
3347
|
|
|
3348
|
+
async function writeJsonArtifact(file, value) {
|
|
3349
|
+
const target = path.resolve(expandHome(file));
|
|
3350
|
+
await fsp.mkdir(path.dirname(target), { recursive: true });
|
|
3351
|
+
await fsp.writeFile(target, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3211
3354
|
function detectDbSafetyText(text = '') {
|
|
3212
3355
|
return detectTrustDbSafetyText(text);
|
|
3213
3356
|
}
|
|
@@ -3251,6 +3394,8 @@ async function proof(args = []) {
|
|
|
3251
3394
|
printProofList('Runtime backend evidence', summary.runtimeBackendEvidence || []);
|
|
3252
3395
|
printProofList('Visual provenance', summary.visualProvenance || []);
|
|
3253
3396
|
printProofList('Mission patch envelope', summary.missionEnvelope || []);
|
|
3397
|
+
printProofList('Mission subagent receipt', summary.missionReceipt || []);
|
|
3398
|
+
printProofList('Mission completion gate', summary.missionCompletion || []);
|
|
3254
3399
|
printProofList('Rollback hint', summary.rollbackHint || []);
|
|
3255
3400
|
printProofList('Media proof', summary.mediaProof || []);
|
|
3256
3401
|
printProofList('Design completion', summary.designCompletion || []);
|
|
@@ -3341,6 +3486,14 @@ function buildProofSummary(parsed, artifact) {
|
|
|
3341
3486
|
...structuredEvidenceList(artifact.missionEnvelope),
|
|
3342
3487
|
...structuredEvidenceList(parsed.flags.mission_envelope)
|
|
3343
3488
|
],
|
|
3489
|
+
missionReceipt: [
|
|
3490
|
+
...structuredEvidenceList(artifact.missionReceipt),
|
|
3491
|
+
...structuredEvidenceList(parsed.flags.mission_receipt)
|
|
3492
|
+
],
|
|
3493
|
+
missionCompletion: [
|
|
3494
|
+
...structuredEvidenceList(artifact.missionCompletion),
|
|
3495
|
+
...structuredEvidenceList(parsed.flags.mission_completion)
|
|
3496
|
+
],
|
|
3344
3497
|
rollbackHint: [
|
|
3345
3498
|
...structuredEvidenceList(artifact.rollbackHint),
|
|
3346
3499
|
...structuredEvidenceList(parsed.flags.rollback_hint)
|
|
@@ -3412,6 +3565,12 @@ function renderProofMarkdown(summary) {
|
|
|
3412
3565
|
'## Mission patch envelope',
|
|
3413
3566
|
...renderProofMarkdownList(summary.missionEnvelope || []),
|
|
3414
3567
|
'',
|
|
3568
|
+
'## Mission subagent receipt',
|
|
3569
|
+
...renderProofMarkdownList(summary.missionReceipt || []),
|
|
3570
|
+
'',
|
|
3571
|
+
'## Mission completion gate',
|
|
3572
|
+
...renderProofMarkdownList(summary.missionCompletion || []),
|
|
3573
|
+
'',
|
|
3415
3574
|
'## Rollback hint',
|
|
3416
3575
|
...renderProofMarkdownList(summary.rollbackHint || []),
|
|
3417
3576
|
'',
|
|
@@ -3453,7 +3612,7 @@ function renderProofMarkdownList(values) {
|
|
|
3453
3612
|
function parseProofArgs(args = []) {
|
|
3454
3613
|
const flags: AnyRecord = {};
|
|
3455
3614
|
const positionals: string[] = [];
|
|
3456
|
-
const aliases = new Set(['goal', 'route', 'truth', 'command', 'evidence', 'visual', 'runtime', 'runtime-backend', 'runtime-claim', 'runtime-evidence-id', 'runtime-command', 'runtime-pid', 'runtime-port', 'runtime-exit-code', 'runtime-started-at', 'runtime-stopped-at', 'runtime-cleanup-method', 'cleanup-observed', 'left-running-intentionally', 'cleanup-checked', 'runtime-note', 'runtime-backend-evidence', 'visual-provenance', 'mission-envelope', 'rollback-hint', 'media-proof', 'design-completion', 'cleanup', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified', 'from', 'format', 'out', 'file', 'json', 'require-runtime', 'require-real-runtime', 'require-tmux', 'require-visual']);
|
|
3615
|
+
const aliases = new Set(['goal', 'route', 'truth', 'command', 'evidence', 'visual', 'runtime', 'runtime-backend', 'runtime-claim', 'runtime-evidence-id', 'runtime-command', 'runtime-pid', 'runtime-port', 'runtime-exit-code', 'runtime-started-at', 'runtime-stopped-at', 'runtime-cleanup-method', 'cleanup-observed', 'left-running-intentionally', 'cleanup-checked', 'runtime-note', 'runtime-backend-evidence', 'visual-provenance', 'mission-envelope', 'mission-receipt', 'mission-completion', 'rollback-hint', 'media-proof', 'design-completion', 'cleanup', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified', 'from', 'format', 'out', 'file', 'json', 'require-runtime', 'require-real-runtime', 'require-tmux', 'require-visual']);
|
|
3457
3616
|
for (let index = 0; index < args.length; index += 1) {
|
|
3458
3617
|
const arg = args[index];
|
|
3459
3618
|
if (arg.startsWith('--')) {
|
|
@@ -3467,7 +3626,7 @@ function parseProofArgs(args = []) {
|
|
|
3467
3626
|
}
|
|
3468
3627
|
const value = inlineValue ?? args[index + 1] ?? '';
|
|
3469
3628
|
if (inlineValue === undefined) index += 1;
|
|
3470
|
-
if (['command', 'evidence', 'visual', 'runtime', 'runtime-backend-evidence', 'visual-provenance', 'mission-envelope', 'rollback-hint', 'media-proof', 'design-completion', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified'].includes(key)) {
|
|
3629
|
+
if (['command', 'evidence', 'visual', 'runtime', 'runtime-backend-evidence', 'visual-provenance', 'mission-envelope', 'mission-receipt', 'mission-completion', 'rollback-hint', 'media-proof', 'design-completion', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified'].includes(key)) {
|
|
3471
3630
|
flags[normalizedKey] = [...arrayFlag(flags[normalizedKey]), value];
|
|
3472
3631
|
} else {
|
|
3473
3632
|
flags[normalizedKey] = value;
|
|
@@ -3508,6 +3667,8 @@ function emptyProofArtifact() {
|
|
|
3508
3667
|
runtimeBackendEvidence: [],
|
|
3509
3668
|
visualProvenance: [],
|
|
3510
3669
|
missionEnvelope: [],
|
|
3670
|
+
missionReceipt: [],
|
|
3671
|
+
missionCompletion: [],
|
|
3511
3672
|
rollbackHint: [],
|
|
3512
3673
|
mediaProof: [],
|
|
3513
3674
|
designCompletion: [],
|
|
@@ -3536,6 +3697,8 @@ async function parseProofJson(file, empty) {
|
|
|
3536
3697
|
runtimeBackendEvidence: structuredEvidenceList(data.runtimeBackendEvidence || data.runtime_backend_evidence),
|
|
3537
3698
|
visualProvenance: structuredEvidenceList(data.visualProvenance || data.visual_provenance),
|
|
3538
3699
|
missionEnvelope: structuredEvidenceList(data.missionEnvelope || data.mission_envelope),
|
|
3700
|
+
missionReceipt: structuredEvidenceList(data.missionReceipt || data.mission_receipt),
|
|
3701
|
+
missionCompletion: structuredEvidenceList(data.missionCompletion || data.mission_completion),
|
|
3539
3702
|
rollbackHint: structuredEvidenceList(data.rollbackHint || data.rollback_hint),
|
|
3540
3703
|
mediaProof: structuredEvidenceList(data.mediaProof || data.media_proof),
|
|
3541
3704
|
designCompletion: structuredEvidenceList(data.designCompletion || data.design_completion),
|
|
@@ -3567,6 +3730,8 @@ async function parseProofMarkdown(file, empty) {
|
|
|
3567
3730
|
runtimeBackendEvidence: readProofList(text, 'Runtime backend evidence'),
|
|
3568
3731
|
visualProvenance: readProofList(text, 'Visual provenance'),
|
|
3569
3732
|
missionEnvelope: readProofList(text, 'Mission patch envelope'),
|
|
3733
|
+
missionReceipt: readProofList(text, 'Mission subagent receipt'),
|
|
3734
|
+
missionCompletion: readProofList(text, 'Mission completion gate'),
|
|
3570
3735
|
rollbackHint: readProofList(text, 'Rollback hint'),
|
|
3571
3736
|
mediaProof: readProofList(text, 'Media proof'),
|
|
3572
3737
|
designCompletion: readProofList(text, 'Design completion'),
|
|
@@ -3651,6 +3816,12 @@ function formatStructuredEvidence(value) {
|
|
|
3651
3816
|
if (data.schema === 'yam.ueye-visual-provenance.v1' || data.source_kind || data.reference_id || data.screenshot_id) {
|
|
3652
3817
|
return JSON.stringify(buildUeyeVisualProvenance(data));
|
|
3653
3818
|
}
|
|
3819
|
+
if (data.schema === 'yam.mission-subagent-receipt.v1' || data.thread_id || data.lifecycle_status || data.outcome) {
|
|
3820
|
+
return JSON.stringify(buildMissionSubagentReceipt(data));
|
|
3821
|
+
}
|
|
3822
|
+
if (data.schema === 'yam.mission-completion-gate.v1' || data.expected_thread_ids || data.ready_to_claim_complete !== undefined) {
|
|
3823
|
+
return JSON.stringify(buildMissionCompletionGate(data));
|
|
3824
|
+
}
|
|
3654
3825
|
if (data.schema === 'yam.mission-patch-envelope.v1' || data.agent_id || data.assigned_scope) {
|
|
3655
3826
|
return JSON.stringify(buildMissionPatchEnvelope(data));
|
|
3656
3827
|
}
|
|
@@ -3674,7 +3845,7 @@ function formatStructuredEvidence(value) {
|
|
|
3674
3845
|
|
|
3675
3846
|
function hasProofEvidence(parsed) {
|
|
3676
3847
|
const flags = parsed.flags || {};
|
|
3677
|
-
return ['command', 'evidence', 'visual', 'runtime', 'runtime_backend_evidence', 'visual_provenance', 'mission_envelope', 'rollback_hint', 'media_proof', 'design_completion', 'changed', 'skipped', 'blocked'].some((key) => arrayFlag(flags[key]).length > 0) || Boolean(flags.cleanup || flags.runtime_backend || flags.runtime_claim);
|
|
3848
|
+
return ['command', 'evidence', 'visual', 'runtime', 'runtime_backend_evidence', 'visual_provenance', 'mission_envelope', 'mission_receipt', 'mission_completion', 'rollback_hint', 'media_proof', 'design_completion', 'changed', 'skipped', 'blocked'].some((key) => arrayFlag(flags[key]).length > 0) || Boolean(flags.cleanup || flags.runtime_backend || flags.runtime_claim);
|
|
3678
3849
|
}
|
|
3679
3850
|
|
|
3680
3851
|
function printProofList(label, values) {
|