yam-flow 2.5.1 → 2.7.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 +47 -0
- package/COMMANDS.md +57 -1
- package/README.md +28 -2
- package/ROADMAP.md +41 -7
- package/dist/bin/yam.js +567 -7
- package/dist/lib/benchmark-promotion.d.ts +53 -0
- package/dist/lib/benchmark-promotion.js +204 -0
- package/dist/lib/design-production.d.ts +306 -0
- package/dist/lib/design-production.js +1341 -0
- package/dist/lib/external-updates.d.ts +23 -0
- package/dist/lib/external-updates.js +286 -30
- package/dist/lib/gate-result.d.ts +44 -0
- package/dist/lib/gate-result.js +219 -0
- package/dist/lib/media-provider-receipt.d.ts +79 -0
- package/dist/lib/media-provider-receipt.js +498 -0
- package/dist/lib/skill-installation.js +337 -97
- package/dist/lib/trust-kernel.js +17 -1
- package/dist/lib/verification-closure.d.ts +97 -0
- package/dist/lib/verification-closure.js +728 -0
- package/package.json +5 -2
- package/references/ueye.md +14 -0
- package/skills/ueye/SKILL.md +11 -0
- package/src/bin/yam.ts +569 -7
- package/src/lib/benchmark-promotion.ts +249 -0
- package/src/lib/design-production.ts +1441 -0
- package/src/lib/external-updates.ts +331 -28
- package/src/lib/gate-result.ts +223 -0
- package/src/lib/media-provider-receipt.ts +548 -0
- package/src/lib/skill-installation.ts +462 -95
- package/src/lib/trust-kernel.ts +20 -1
- package/src/lib/verification-closure.ts +738 -0
- package/yam.manifest.json +2 -2
package/src/bin/yam.ts
CHANGED
|
@@ -34,6 +34,19 @@ import {
|
|
|
34
34
|
verifyUeyeAssetManifest,
|
|
35
35
|
verifyUeyeRevisionHistory
|
|
36
36
|
} from '../lib/ueye-artifacts.js';
|
|
37
|
+
import {
|
|
38
|
+
addPlanReviewComment,
|
|
39
|
+
closePlanReviewCanvas,
|
|
40
|
+
createDesignRevisionState,
|
|
41
|
+
createPlanReviewCanvas,
|
|
42
|
+
finalizeDesignProductionPhase,
|
|
43
|
+
readCanonicalDesignRevisionState,
|
|
44
|
+
readPlanReviewSession,
|
|
45
|
+
recordDesignRevisionRound,
|
|
46
|
+
verifyFinalGalleryManifest,
|
|
47
|
+
verifyDesignProductionPhaseReceipt,
|
|
48
|
+
writeFinalGalleryManifest
|
|
49
|
+
} from '../lib/design-production.js';
|
|
37
50
|
import {
|
|
38
51
|
inspectSkillInstallation,
|
|
39
52
|
installSkillSetTransactional,
|
|
@@ -45,6 +58,22 @@ import {
|
|
|
45
58
|
checkExternalUpdates
|
|
46
59
|
} from '../lib/external-updates.js';
|
|
47
60
|
import type { ExternalUpdateComponent } from '../lib/external-updates.js';
|
|
61
|
+
import {
|
|
62
|
+
buildBoundedPromotionReport,
|
|
63
|
+
parsePromotionSample
|
|
64
|
+
} from '../lib/benchmark-promotion.js';
|
|
65
|
+
import {
|
|
66
|
+
buildStrictGateResult,
|
|
67
|
+
verifyStrictGateResult
|
|
68
|
+
} from '../lib/gate-result.js';
|
|
69
|
+
import {
|
|
70
|
+
createVerificationClosureReceipt,
|
|
71
|
+
verifyVerificationClosureReceiptFile
|
|
72
|
+
} from '../lib/verification-closure.js';
|
|
73
|
+
import {
|
|
74
|
+
createMediaProviderReceipt,
|
|
75
|
+
verifyMediaProviderReceipt
|
|
76
|
+
} from '../lib/media-provider-receipt.js';
|
|
48
77
|
import {
|
|
49
78
|
BoundedInputError,
|
|
50
79
|
GENERAL_STDIN_MAX_BYTES,
|
|
@@ -188,15 +217,18 @@ Usage:
|
|
|
188
217
|
yam ueye report [--reference ref.png] [--actual screenshot.png] [--provider-context local] [--execution-surface in-app-browser] [--json]
|
|
189
218
|
yam ueye asset <add|verify> [options]
|
|
190
219
|
yam ueye revision <archive|verify> [options]
|
|
220
|
+
yam ueye production <canvas|revision|gallery|finalize|phase> [options]
|
|
191
221
|
yam media proof [--requested] [--attempted] [--output file] [--json]
|
|
192
222
|
yam runtime evidence [--backend terminal|in-app-browser|playwright|tmux|zellij] [--claim observed|started|stopped|cleanup-verified] [--json]
|
|
193
223
|
yam mission queue [--agent-id id] [--scope text] [--changed file] [--verification-hint text] [--json]
|
|
194
224
|
yam mission receipt [--thread-id id] [--role reviewer] [--lifecycle stopped] [--outcome passed] [--evidence text] [--json]
|
|
195
225
|
yam mission gate [--expected-thread id] [--receipt file] [--json]
|
|
226
|
+
yam verification closure <create|verify> [options]
|
|
196
227
|
yam update check [--json]
|
|
197
228
|
yam update apply --component yam|scrapling|insane-search [--json]
|
|
198
229
|
yam update apply --all [--json]
|
|
199
230
|
yam benchmark report [--baseline n] [--current n] [--unit ms] [--target lower|higher] [--json]
|
|
231
|
+
yam benchmark report --candidate-digest sha256 --baseline-digest sha256 --sample seed:candidate:baseline --unit score --evidence-source text [--min-samples n] [--min-mean-delta n] [--min-win-rate n] [--json]
|
|
200
232
|
yam release report [--json]
|
|
201
233
|
yam safety [text...]
|
|
202
234
|
yam memory <init|add|list|summary|resolve> [dir] [options]
|
|
@@ -1335,6 +1367,7 @@ async function buildToolsDoctorReport(targetDir = process.cwd()) {
|
|
|
1335
1367
|
readinessRow('Supabase plugin', await pluginCacheHas('openai-curated/supabase') ? 'ready' : 'unknown', 'plugin cache only; no DB query performed'),
|
|
1336
1368
|
readinessRow('Vercel plugin', await pluginCacheHas('openai-curated/vercel') ? 'ready' : 'unknown', 'plugin cache only; no deployment/API call performed')
|
|
1337
1369
|
];
|
|
1370
|
+
const capabilityMatrix = buildCapabilityMatrix(rows, globalLiteHookHealth, globalStudyNoteHookHealth);
|
|
1338
1371
|
const riskNotes = [
|
|
1339
1372
|
...instructionSurfaces.issues.map((message) => ({ level: 'issue', reason: message })),
|
|
1340
1373
|
...instructionSurfaces.warnings.map((message) => ({ level: 'warning', reason: message })),
|
|
@@ -1361,6 +1394,7 @@ async function buildToolsDoctorReport(targetDir = process.cwd()) {
|
|
|
1361
1394
|
frameworkChecklist,
|
|
1362
1395
|
contextPressure,
|
|
1363
1396
|
realProbe,
|
|
1397
|
+
capabilityMatrix,
|
|
1364
1398
|
sqlScan,
|
|
1365
1399
|
riskNotes,
|
|
1366
1400
|
nextActions: nextActionDetails.map((action) => action.next_action),
|
|
@@ -1374,6 +1408,45 @@ async function buildToolsDoctorReport(targetDir = process.cwd()) {
|
|
|
1374
1408
|
};
|
|
1375
1409
|
}
|
|
1376
1410
|
|
|
1411
|
+
function buildCapabilityMatrix(readinessRows = [], liteHook: AnyRecord = {}, studyNoteHook: AnyRecord = {}) {
|
|
1412
|
+
const readiness = new Map(readinessRows.map((row) => [row.name, row.status]));
|
|
1413
|
+
const capabilities = [
|
|
1414
|
+
capabilityRow('skill_installation', 'stable', readiness.get('Yam skills') || 'unknown', 'yam status plus the transactional installation receipt', 'proves managed skill bytes only; it does not prove a route ran'),
|
|
1415
|
+
capabilityRow('external_update_check', 'stable', 'unknown', 'packaged `yam update check` command', 'this inventory does not contact registries or prove Codex/plugin/network availability'),
|
|
1416
|
+
capabilityRow('external_update_apply', 'beta', 'unknown', 'transactional component updater with rollback receipts', 'runtime readiness is checked only during an explicit run; effective yam identity currently supports POSIX symlinks while Windows npm .cmd shims fail closed as unverified'),
|
|
1417
|
+
capabilityRow('mission_receipt_gate', 'stable', 'ready', 'packaged mission receipt and aggregate gate commands', 'validates supplied real-thread evidence; it does not spawn agents'),
|
|
1418
|
+
capabilityRow('mission_real_subagents', 'instruction-only', 'unknown', 'Mission skill delegates through environment-owned subagent tools', 'CLI cannot prove host slot availability or a real thread outcome'),
|
|
1419
|
+
capabilityRow('ueye_asset_revision_integrity', 'stable', 'ready', 'hash-verified Ueye asset and revision manifests', 'integrity does not prove design quality, licensing, or browser state'),
|
|
1420
|
+
capabilityRow('plan_review_canvas_lite', 'beta', 'ready', 'local static review session artifacts', 'no remote hosting, background service, or automatic implementation start'),
|
|
1421
|
+
capabilityRow('bounded_design_revisions', 'beta', 'ready', 'maximum two reviewer-finding-backed revision rounds', 'round state does not replace Ueye artifact hash verification'),
|
|
1422
|
+
capabilityRow('final_gallery_manifest', 'beta', 'ready', 'path-confined hash-verified packaging manifest', 'gallery completeness does not prove visual or license correctness'),
|
|
1423
|
+
capabilityRow('demand_gated_design_production', 'beta', 'ready', 'pre-recorded operator trigger, Canvas, chronological bounded revisions, gallery, and digest-verifiable phase receipt', 'demand evidence is operator-asserted and overall phase truth remains partial'),
|
|
1424
|
+
capabilityRow('final_scope_verification_closure', 'beta', 'ready', 'explicit Git-scope and selected/executed command receipt with digest verification', 'does not execute checks; unavailable or unplanned Git scope blocks and successful operator evidence remains partial'),
|
|
1425
|
+
capabilityRow('media_provider_execution_receipt', 'beta', 'ready', 'optional demand-gated exact execution boundary plus identity-bound local assets', 'never contacts providers and does not prove visual or implementation correctness'),
|
|
1426
|
+
capabilityRow('yam_lite_hook', 'beta', String(liteHook.state || readiness.get('yam-lite hook') || 'unknown'), 'optional hook health inspection', 'advisory only; disabled unless explicitly enabled'),
|
|
1427
|
+
capabilityRow('study_note_hook', 'beta', String(studyNoteHook.state || readiness.get('yam-study-note hook') || 'unknown'), 'optional prompt and bounded Stop check', 'does not generate the Study Note or run verification')
|
|
1428
|
+
];
|
|
1429
|
+
return {
|
|
1430
|
+
schema: 'yam.capability-matrix.v1',
|
|
1431
|
+
generated_at: new Date().toISOString(),
|
|
1432
|
+
capabilities,
|
|
1433
|
+
maturity_vocabulary: ['stable', 'beta', 'experimental', 'instruction-only'],
|
|
1434
|
+
truth_status: 'partial',
|
|
1435
|
+
note: 'support maturity and current runtime state are separate; this read-only inventory does not execute capabilities'
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
function capabilityRow(id, maturity, runtimeState, evidence, limit) {
|
|
1440
|
+
return {
|
|
1441
|
+
id,
|
|
1442
|
+
maturity,
|
|
1443
|
+
runtime_state: runtimeState,
|
|
1444
|
+
evidence,
|
|
1445
|
+
limit,
|
|
1446
|
+
truth_status: runtimeState === 'ready' ? 'partial' : 'assumed'
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1377
1450
|
function printToolsDoctorReport(data) {
|
|
1378
1451
|
console.log('yam tools doctor');
|
|
1379
1452
|
console.log(`Project: ${data.project}`);
|
|
@@ -1390,6 +1463,14 @@ function printToolsDoctorReport(data) {
|
|
|
1390
1463
|
}
|
|
1391
1464
|
}
|
|
1392
1465
|
|
|
1466
|
+
if (data.capabilityMatrix?.capabilities?.length) {
|
|
1467
|
+
console.log('');
|
|
1468
|
+
console.log('Capability matrix:');
|
|
1469
|
+
for (const capability of data.capabilityMatrix.capabilities) {
|
|
1470
|
+
console.log(`- ${capability.id}: ${capability.maturity} / ${capability.runtime_state} (${capability.limit})`);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1393
1474
|
if (data.instructionSurfaces.found.length || data.projectSurfaces.length) {
|
|
1394
1475
|
console.log('');
|
|
1395
1476
|
console.log('Project surfaces:');
|
|
@@ -2268,8 +2349,35 @@ function runReleaseReport() {
|
|
|
2268
2349
|
const publishBlockerEvidence = publishBlockerEvidenceFromRelease(checks, tarball);
|
|
2269
2350
|
const publishReadiness = releasePublishReadiness(checks, provenance, tarball, publishBlockerEvidence);
|
|
2270
2351
|
const nextActions = releaseNextActions(checks, provenance, tarball, publishBlockerEvidence, publishReadiness);
|
|
2271
|
-
const
|
|
2272
|
-
const readinessReceipt = releaseReadinessReceipt(provenance, freshness, tarball, publishReadiness,
|
|
2352
|
+
const preliminaryOk = failed.length === 0 && publishReadiness.status === 'ready';
|
|
2353
|
+
const readinessReceipt = releaseReadinessReceipt(provenance, freshness, tarball, publishReadiness, preliminaryOk);
|
|
2354
|
+
const gateResult = buildStrictGateResult({
|
|
2355
|
+
gate_id: 'release-readiness',
|
|
2356
|
+
boundary: 'release',
|
|
2357
|
+
checks: [
|
|
2358
|
+
...checks.map((check) => ({
|
|
2359
|
+
id: `release-${String(check.id || 'unnamed').replace(/[^a-z0-9._-]/gi, '-')}`,
|
|
2360
|
+
status: check.status === 'passed' ? 'passed' as const : 'failed' as const,
|
|
2361
|
+
note: check.note || check.command || check.id,
|
|
2362
|
+
required: true
|
|
2363
|
+
})),
|
|
2364
|
+
{
|
|
2365
|
+
id: 'publish-readiness',
|
|
2366
|
+
status: publishReadiness.status === 'ready' ? 'passed' as const : 'failed' as const,
|
|
2367
|
+
note: publishReadiness.next_action,
|
|
2368
|
+
required: true
|
|
2369
|
+
}
|
|
2370
|
+
],
|
|
2371
|
+
blockers: publishReadiness.blockers?.map((blocker) => `${blocker.kind}: ${blocker.reason}`) || [],
|
|
2372
|
+
evidence: [
|
|
2373
|
+
`${readinessReceipt.schema}:${readinessReceipt.local_version}`,
|
|
2374
|
+
tarball.integrity ? `tarball-integrity:${tarball.integrity}` : 'tarball-integrity:not-observed',
|
|
2375
|
+
provenance.git_commit ? `git-commit:${provenance.git_commit}` : 'git-commit:not-observed'
|
|
2376
|
+
],
|
|
2377
|
+
next_action: publishReadiness.next_action
|
|
2378
|
+
});
|
|
2379
|
+
const gateContract = verifyStrictGateResult(gateResult);
|
|
2380
|
+
const ok = preliminaryOk && gateResult.status === 'passed' && gateContract.valid;
|
|
2273
2381
|
return {
|
|
2274
2382
|
schema: 'yam.release-report.v1',
|
|
2275
2383
|
generated_at: startedAt,
|
|
@@ -2284,6 +2392,8 @@ function runReleaseReport() {
|
|
|
2284
2392
|
freshness,
|
|
2285
2393
|
publish_readiness: publishReadiness,
|
|
2286
2394
|
readiness_receipt: readinessReceipt,
|
|
2395
|
+
gate_result: gateResult,
|
|
2396
|
+
gate_contract: gateContract,
|
|
2287
2397
|
study_note: releaseStudyNote(publishReadiness, publishBlockerEvidence),
|
|
2288
2398
|
publishBlockerEvidence,
|
|
2289
2399
|
next_actions: nextActions
|
|
@@ -2781,6 +2891,7 @@ async function ueye(args = []) {
|
|
|
2781
2891
|
if (subcommand === 'report') return ueyeReport(args.slice(1));
|
|
2782
2892
|
if (subcommand === 'asset') return ueyeAsset(args.slice(1));
|
|
2783
2893
|
if (subcommand === 'revision') return ueyeRevision(args.slice(1));
|
|
2894
|
+
if (subcommand === 'production') return ueyeProduction(args.slice(1));
|
|
2784
2895
|
console.error(`unknown ueye command: ${subcommand}`);
|
|
2785
2896
|
return ueyeUsage();
|
|
2786
2897
|
}
|
|
@@ -2799,6 +2910,11 @@ Usage:
|
|
|
2799
2910
|
yam ueye asset verify [--manifest file] [--json]
|
|
2800
2911
|
yam ueye revision archive --file image --round n [--artifact-id id] [--root dir] [--manifest file] [--json]
|
|
2801
2912
|
yam ueye revision verify [--root dir] [--manifest file] [--json]
|
|
2913
|
+
yam ueye production canvas <create|comment|close|show> [options]
|
|
2914
|
+
yam ueye production revision <init|record|show> [options]
|
|
2915
|
+
yam ueye production gallery <write|verify> [options]
|
|
2916
|
+
yam ueye production finalize [options]
|
|
2917
|
+
yam ueye production phase verify [options]
|
|
2802
2918
|
|
|
2803
2919
|
Notes:
|
|
2804
2920
|
capture uses a locally available Playwright install when present. It does not download browsers or install dependencies.
|
|
@@ -2806,6 +2922,7 @@ Notes:
|
|
|
2806
2922
|
report produces a proof-ready Ueye visual run report, design completion gate, and continuity/comparison record without requiring a new capture.
|
|
2807
2923
|
asset records local reference provenance and protection flags; it never downloads remote content.
|
|
2808
2924
|
revision copies the current local artifact into a non-overwriting round archive before the live file is edited.
|
|
2925
|
+
production is demand-gated: it writes local-only review artifacts, permits at most two finding-backed revision rounds, and keeps gallery correctness claims explicitly unverified.
|
|
2809
2926
|
`);
|
|
2810
2927
|
}
|
|
2811
2928
|
|
|
@@ -2880,6 +2997,236 @@ async function ueyeRevision(args = []) {
|
|
|
2880
2997
|
}
|
|
2881
2998
|
}
|
|
2882
2999
|
|
|
3000
|
+
async function ueyeProduction(args = []) {
|
|
3001
|
+
const surface = String(args[0] || 'help');
|
|
3002
|
+
const isFinalize = surface === 'finalize';
|
|
3003
|
+
const action = isFinalize ? 'run' : String(args[1] || 'help');
|
|
3004
|
+
const flagArgs = isFinalize ? args.slice(1) : args.slice(2);
|
|
3005
|
+
if (surface === 'help' || action === 'help' || surface === '--help' || surface === '-h' || flagArgs.includes('--help') || flagArgs.includes('-h')) return ueyeProductionUsage();
|
|
3006
|
+
const flags = parseSimpleFlags(flagArgs, new Set([
|
|
3007
|
+
'root', 'session-id', 'title', 'artifact-spec', 'session-path', 'render-path',
|
|
3008
|
+
'comment-id', 'artifact-id', 'anchor-kind', 'locator', 'finding', 'requested-change', 'verdict',
|
|
3009
|
+
'state-path', 'finding-id', 'source-comment-id', 'finding-summary', 'finding-evidence', 'planned-change', 'outcome', 'revision-ref',
|
|
3010
|
+
'manifest-path', 'completion-state', 'demand-kind', 'demand-evidence', 'canvas-session-path',
|
|
3011
|
+
'revision-state-path', 'gallery-manifest-path', 'receipt-path', 'json'
|
|
3012
|
+
]));
|
|
3013
|
+
const root = path.resolve(expandHome(flags.root || process.cwd()));
|
|
3014
|
+
try {
|
|
3015
|
+
let result: AnyRecord;
|
|
3016
|
+
if (surface === 'canvas' && action === 'create') {
|
|
3017
|
+
requireProductionFlags(flags, ['session_id', 'title', 'demand_kind', 'demand_evidence', 'artifact_spec']);
|
|
3018
|
+
result = await createPlanReviewCanvas({
|
|
3019
|
+
root,
|
|
3020
|
+
session_id: String(flags.session_id),
|
|
3021
|
+
title: String(flags.title),
|
|
3022
|
+
demand_trigger: {
|
|
3023
|
+
kind: String(flags.demand_kind) as 'repeated_plan_review' | 'multi_asset_production',
|
|
3024
|
+
evidence: String(flags.demand_evidence)
|
|
3025
|
+
},
|
|
3026
|
+
artifacts: await readProductionSpecs(flags.artifact_spec, 'canvas artifact spec') as any,
|
|
3027
|
+
...(flags.session_path ? { session_path: String(flags.session_path) } : {}),
|
|
3028
|
+
...(flags.render_path ? { render_path: String(flags.render_path) } : {})
|
|
3029
|
+
});
|
|
3030
|
+
} else if (surface === 'canvas' && action === 'comment') {
|
|
3031
|
+
requireProductionFlags(flags, ['session_path', 'render_path', 'comment_id', 'artifact_id', 'anchor_kind', 'locator', 'finding', 'requested_change']);
|
|
3032
|
+
result = await addPlanReviewComment({
|
|
3033
|
+
root,
|
|
3034
|
+
session_path: String(flags.session_path),
|
|
3035
|
+
render_path: String(flags.render_path),
|
|
3036
|
+
comment: {
|
|
3037
|
+
id: String(flags.comment_id),
|
|
3038
|
+
anchor: {
|
|
3039
|
+
artifact_id: String(flags.artifact_id),
|
|
3040
|
+
kind: String(flags.anchor_kind) as 'line' | 'region' | 'text',
|
|
3041
|
+
locator: String(flags.locator)
|
|
3042
|
+
},
|
|
3043
|
+
finding: String(flags.finding),
|
|
3044
|
+
requested_change: String(flags.requested_change)
|
|
3045
|
+
}
|
|
3046
|
+
});
|
|
3047
|
+
} else if (surface === 'canvas' && action === 'close') {
|
|
3048
|
+
requireProductionFlags(flags, ['session_path', 'render_path', 'verdict']);
|
|
3049
|
+
result = await closePlanReviewCanvas({
|
|
3050
|
+
root,
|
|
3051
|
+
session_path: String(flags.session_path),
|
|
3052
|
+
render_path: String(flags.render_path),
|
|
3053
|
+
verdict: String(flags.verdict) as 'approve' | 'request_changes'
|
|
3054
|
+
});
|
|
3055
|
+
} else if (surface === 'canvas' && action === 'show') {
|
|
3056
|
+
requireProductionFlags(flags, ['session_path']);
|
|
3057
|
+
const sessionPath = await existingProductionPath(root, String(flags.session_path), 'session_path');
|
|
3058
|
+
result = await readPlanReviewSession(sessionPath);
|
|
3059
|
+
} else if (surface === 'revision' && action === 'init') {
|
|
3060
|
+
requireProductionFlags(flags, ['session_id']);
|
|
3061
|
+
result = await createDesignRevisionState({
|
|
3062
|
+
root,
|
|
3063
|
+
session_id: String(flags.session_id),
|
|
3064
|
+
...(flags.state_path ? { state_path: String(flags.state_path) } : {})
|
|
3065
|
+
});
|
|
3066
|
+
} else if (surface === 'revision' && action === 'record') {
|
|
3067
|
+
requireProductionFlags(flags, ['state_path', 'finding_id', 'source_comment_id', 'finding_summary', 'finding_evidence', 'planned_change', 'outcome', 'revision_ref']);
|
|
3068
|
+
result = await recordDesignRevisionRound({
|
|
3069
|
+
root,
|
|
3070
|
+
state_path: String(flags.state_path),
|
|
3071
|
+
reviewer_finding: {
|
|
3072
|
+
id: String(flags.finding_id),
|
|
3073
|
+
source_comment_id: String(flags.source_comment_id),
|
|
3074
|
+
summary: String(flags.finding_summary),
|
|
3075
|
+
evidence: String(flags.finding_evidence)
|
|
3076
|
+
},
|
|
3077
|
+
planned_change: String(flags.planned_change),
|
|
3078
|
+
outcome: String(flags.outcome) as 'accepted' | 'changes_requested',
|
|
3079
|
+
revision_refs: await readProductionSpecs(flags.revision_ref, 'Ueye revision reference') as any
|
|
3080
|
+
});
|
|
3081
|
+
} else if (surface === 'revision' && action === 'show') {
|
|
3082
|
+
requireProductionFlags(flags, ['state_path']);
|
|
3083
|
+
result = await readCanonicalDesignRevisionState({ root, state_path: String(flags.state_path) });
|
|
3084
|
+
} else if (surface === 'gallery' && action === 'write') {
|
|
3085
|
+
requireProductionFlags(flags, ['manifest_path', 'session_id', 'completion_state', 'artifact_spec']);
|
|
3086
|
+
result = await writeFinalGalleryManifest({
|
|
3087
|
+
root,
|
|
3088
|
+
manifest_path: String(flags.manifest_path),
|
|
3089
|
+
session_id: String(flags.session_id),
|
|
3090
|
+
completion_state: String(flags.completion_state) as 'draft' | 'ready_for_inspection' | 'packaged',
|
|
3091
|
+
artifacts: await readProductionSpecs(flags.artifact_spec, 'gallery artifact spec') as any
|
|
3092
|
+
});
|
|
3093
|
+
} else if (surface === 'gallery' && action === 'verify') {
|
|
3094
|
+
requireProductionFlags(flags, ['manifest_path']);
|
|
3095
|
+
result = await verifyFinalGalleryManifest({ root, manifest_path: String(flags.manifest_path) });
|
|
3096
|
+
} else if (surface === 'finalize' && action === 'run') {
|
|
3097
|
+
requireProductionFlags(flags, ['demand_kind', 'demand_evidence', 'canvas_session_path', 'gallery_manifest_path', 'receipt_path']);
|
|
3098
|
+
result = await finalizeDesignProductionPhase({
|
|
3099
|
+
root,
|
|
3100
|
+
demand_trigger: {
|
|
3101
|
+
kind: String(flags.demand_kind) as 'repeated_plan_review' | 'multi_asset_production',
|
|
3102
|
+
evidence: String(flags.demand_evidence)
|
|
3103
|
+
},
|
|
3104
|
+
canvas_session_path: String(flags.canvas_session_path),
|
|
3105
|
+
revision_state_path: flags.revision_state_path ? String(flags.revision_state_path) : null,
|
|
3106
|
+
gallery_manifest_path: String(flags.gallery_manifest_path),
|
|
3107
|
+
receipt_path: String(flags.receipt_path)
|
|
3108
|
+
});
|
|
3109
|
+
} else if (surface === 'phase' && action === 'verify') {
|
|
3110
|
+
requireProductionFlags(flags, ['receipt_path']);
|
|
3111
|
+
result = await verifyDesignProductionPhaseReceipt({ root, receipt_path: String(flags.receipt_path) });
|
|
3112
|
+
} else {
|
|
3113
|
+
throw new Error(`unknown Ueye production command: ${surface} ${action}`);
|
|
3114
|
+
}
|
|
3115
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Ueye design production');
|
|
3116
|
+
if (result.truth_status === 'blocked') process.exitCode = 1;
|
|
3117
|
+
} catch (error) {
|
|
3118
|
+
const reason = summarizeCheckOutput(errorMessage(error));
|
|
3119
|
+
const result = {
|
|
3120
|
+
schema: 'yam.design-production-error.v1',
|
|
3121
|
+
surface,
|
|
3122
|
+
action,
|
|
3123
|
+
status: 'blocked',
|
|
3124
|
+
reason,
|
|
3125
|
+
next_action: /two_round_limit|maximum (?:is )?2|maximum two/i.test(reason)
|
|
3126
|
+
? 'stop this phase at two rounds and keep unresolved gallery work draft; start a new phase only after new explicit demand authorization'
|
|
3127
|
+
: /stopped with status accepted/i.test(reason)
|
|
3128
|
+
? 'stop revising because the reviewer already accepted this phase'
|
|
3129
|
+
: 'repair the local spec, path, integrity evidence, or phase state before retrying',
|
|
3130
|
+
truth_status: 'blocked'
|
|
3131
|
+
};
|
|
3132
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Ueye design production');
|
|
3133
|
+
process.exitCode = 1;
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
function ueyeProductionUsage() {
|
|
3138
|
+
console.log(`yam ueye production
|
|
3139
|
+
|
|
3140
|
+
Demand-gated local design production helpers. Spec files are bounded JSON objects; repeat --artifact-spec or --revision-ref for multiple entries.
|
|
3141
|
+
|
|
3142
|
+
Usage:
|
|
3143
|
+
yam ueye production canvas create [--root dir] --session-id id --title text --demand-kind repeated_plan_review|multi_asset_production --demand-evidence text --artifact-spec file [--json]
|
|
3144
|
+
yam ueye production canvas comment [--root dir] --session-path relative --render-path relative --comment-id id --artifact-id id --anchor-kind line|region|text --locator text --finding text --requested-change text [--json]
|
|
3145
|
+
yam ueye production canvas close [--root dir] --session-path relative --render-path relative --verdict approve|request_changes [--json]
|
|
3146
|
+
yam ueye production canvas show [--root dir] --session-path relative [--json]
|
|
3147
|
+
yam ueye production revision init [--root dir] --session-id id [--state-path .yam/ueye/design-production/<session-id>/revision-state.json] [--json]
|
|
3148
|
+
yam ueye production revision record [--root dir] --state-path relative --finding-id id --source-comment-id id --finding-summary text --finding-evidence text --planned-change text --outcome accepted|changes_requested --revision-ref file [--json]
|
|
3149
|
+
yam ueye production revision show [--root dir] --state-path relative [--json]
|
|
3150
|
+
yam ueye production gallery write [--root dir] --manifest-path relative --session-id id --completion-state draft|ready_for_inspection|packaged --artifact-spec file [--json]
|
|
3151
|
+
yam ueye production gallery verify [--root dir] --manifest-path relative [--json]
|
|
3152
|
+
yam ueye production finalize [--root dir] --demand-kind repeated_plan_review|multi_asset_production --demand-evidence text --canvas-session-path relative [--revision-state-path relative] --gallery-manifest-path relative --receipt-path relative [--json]
|
|
3153
|
+
yam ueye production phase verify [--root dir] --receipt-path relative [--json]
|
|
3154
|
+
|
|
3155
|
+
Use this phase only when repeated plan review or large multi-asset production creates real coordination demand. Canvas artifacts stay static and local. Revision init requires the session's closed request_changes Canvas and fixes state to .yam/ueye/design-production/<session-id>/revision-state.json; --state-path is accepted only when it names that exact path. Record/show/finalize reject alternate state files. Gallery verification covers paths, hashes, dimensions, and revision references only. Finalize binds the explicit demand evidence, closed Canvas, terminal revision state when needed, and verified gallery into one immutable receipt.
|
|
3156
|
+
`);
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
function requireProductionFlags(flags: AnyRecord, names: string[]) {
|
|
3160
|
+
const missing = names.filter((name) => flags[name] === undefined || flags[name] === '');
|
|
3161
|
+
if (missing.length) throw new Error(`missing required option(s): ${missing.map((name) => `--${name.replace(/_/g, '-')}`).join(', ')}`);
|
|
3162
|
+
}
|
|
3163
|
+
|
|
3164
|
+
async function readProductionSpecs(value: unknown, label: string) {
|
|
3165
|
+
const files = arrayFlag(value);
|
|
3166
|
+
if (!files.length) throw new Error(`${label} requires at least one JSON spec file`);
|
|
3167
|
+
const specs: AnyRecord[] = [];
|
|
3168
|
+
for (const file of files) specs.push(await readBoundedJsonSpec(path.resolve(expandHome(file)), label));
|
|
3169
|
+
return specs;
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
async function readOptionalSpecs(value: unknown, label: string) {
|
|
3173
|
+
const files = arrayFlag(value);
|
|
3174
|
+
const specs: AnyRecord[] = [];
|
|
3175
|
+
for (const file of files) specs.push(await readBoundedJsonSpec(path.resolve(expandHome(file)), label));
|
|
3176
|
+
return specs;
|
|
3177
|
+
}
|
|
3178
|
+
|
|
3179
|
+
async function readBoundedJsonSpec(file: string, label: string) {
|
|
3180
|
+
const before = await fsp.lstat(file);
|
|
3181
|
+
if (before.isSymbolicLink() || !before.isFile()) throw new Error(`${label} must be a regular non-symlink file: ${file}`);
|
|
3182
|
+
if (before.size > 256 * 1024) throw new Error(`${label} exceeds the 256 KiB limit: ${file}`);
|
|
3183
|
+
const noFollow = process.platform !== 'win32' && typeof fs.constants.O_NOFOLLOW === 'number'
|
|
3184
|
+
? fs.constants.O_NOFOLLOW
|
|
3185
|
+
: 0;
|
|
3186
|
+
const handle = await fsp.open(file, fs.constants.O_RDONLY | noFollow);
|
|
3187
|
+
let text = '';
|
|
3188
|
+
try {
|
|
3189
|
+
const opened = await handle.stat();
|
|
3190
|
+
if (!opened.isFile() || !sameSpecFileIdentity(before, opened)) throw new Error(`${label} identity changed before open: ${file}`);
|
|
3191
|
+
if (opened.size > 256 * 1024) throw new Error(`${label} exceeds the 256 KiB limit: ${file}`);
|
|
3192
|
+
const bytes = Buffer.alloc(opened.size);
|
|
3193
|
+
let offset = 0;
|
|
3194
|
+
while (offset < bytes.length) {
|
|
3195
|
+
const { bytesRead } = await handle.read(bytes, offset, bytes.length - offset, offset);
|
|
3196
|
+
if (!bytesRead) throw new Error(`${label} became shorter while reading: ${file}`);
|
|
3197
|
+
offset += bytesRead;
|
|
3198
|
+
}
|
|
3199
|
+
const openedAfter = await handle.stat();
|
|
3200
|
+
const after = await fsp.lstat(file);
|
|
3201
|
+
if (!sameSpecFileIdentity(opened, openedAfter) || openedAfter.size !== opened.size
|
|
3202
|
+
|| after.isSymbolicLink() || !after.isFile() || !sameSpecFileIdentity(opened, after)) {
|
|
3203
|
+
throw new Error(`${label} changed size or identity while reading: ${file}`);
|
|
3204
|
+
}
|
|
3205
|
+
text = bytes.toString('utf8');
|
|
3206
|
+
} finally {
|
|
3207
|
+
await handle.close();
|
|
3208
|
+
}
|
|
3209
|
+
const value = JSON.parse(text);
|
|
3210
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${label} must contain one JSON object: ${file}`);
|
|
3211
|
+
return value;
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
function sameSpecFileIdentity(
|
|
3215
|
+
left: { dev: number | bigint; ino: number | bigint },
|
|
3216
|
+
right: { dev: number | bigint; ino: number | bigint }
|
|
3217
|
+
) {
|
|
3218
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3221
|
+
async function existingProductionPath(root: string, value: string, label: string) {
|
|
3222
|
+
if (!value || path.isAbsolute(value) || value.includes('\0')) throw new Error(`${label} must be a non-empty project-relative path`);
|
|
3223
|
+
const realRoot = await fsp.realpath(root);
|
|
3224
|
+
const candidate = await fsp.realpath(path.resolve(realRoot, value));
|
|
3225
|
+
const relative = path.relative(realRoot, candidate);
|
|
3226
|
+
if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) throw new Error(`${label} escapes the project root`);
|
|
3227
|
+
return candidate;
|
|
3228
|
+
}
|
|
3229
|
+
|
|
2883
3230
|
async function ueyePreflight(args = []) {
|
|
2884
3231
|
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') return ueyeUsage();
|
|
2885
3232
|
const parsed = parseDirJsonArgs(args);
|
|
@@ -3442,10 +3789,77 @@ function normalizeUeyeComparisonResult(value = '') {
|
|
|
3442
3789
|
return 'not-verified';
|
|
3443
3790
|
}
|
|
3444
3791
|
|
|
3792
|
+
async function verification(args = []) {
|
|
3793
|
+
const surface = String(args[0] || 'help');
|
|
3794
|
+
const action = String(args[1] || 'help');
|
|
3795
|
+
if (surface === 'help' || action === 'help' || surface === '--help' || surface === '-h') return verificationUsage();
|
|
3796
|
+
if (surface !== 'closure') {
|
|
3797
|
+
console.error(`unknown verification command: ${surface}`);
|
|
3798
|
+
return verificationUsage();
|
|
3799
|
+
}
|
|
3800
|
+
const flags = parseSimpleFlags(args.slice(2), new Set([
|
|
3801
|
+
'root', 'receipt-path', 'planned-scope', 'changed-file', 'selected-spec',
|
|
3802
|
+
'executed-spec', 'skipped-spec', 'truncated-spec', 'json'
|
|
3803
|
+
]));
|
|
3804
|
+
const root = path.resolve(expandHome(flags.root || process.cwd()));
|
|
3805
|
+
try {
|
|
3806
|
+
let result: AnyRecord;
|
|
3807
|
+
if (action === 'create') {
|
|
3808
|
+
requireProductionFlags(flags, ['receipt_path', 'planned_scope', 'changed_file', 'selected_spec']);
|
|
3809
|
+
result = await createVerificationClosureReceipt({
|
|
3810
|
+
root,
|
|
3811
|
+
receipt_path: String(flags.receipt_path),
|
|
3812
|
+
planned_scope: String(flags.planned_scope) as 'affected' | 'release',
|
|
3813
|
+
changed_files: arrayFlag(flags.changed_file),
|
|
3814
|
+
selected_commands: await readProductionSpecs(flags.selected_spec, 'selected command spec') as any,
|
|
3815
|
+
executed_commands: await readOptionalSpecs(flags.executed_spec, 'executed command spec') as any,
|
|
3816
|
+
skipped: await readOptionalSpecs(flags.skipped_spec, 'skipped command spec') as any,
|
|
3817
|
+
truncated: await readOptionalSpecs(flags.truncated_spec, 'truncated output spec') as any
|
|
3818
|
+
});
|
|
3819
|
+
} else if (action === 'verify') {
|
|
3820
|
+
requireProductionFlags(flags, ['receipt_path']);
|
|
3821
|
+
result = await verifyVerificationClosureReceiptFile({ root, receipt_path: String(flags.receipt_path) });
|
|
3822
|
+
} else {
|
|
3823
|
+
throw new Error(`unknown verification closure command: ${action}`);
|
|
3824
|
+
}
|
|
3825
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Verification closure');
|
|
3826
|
+
if (result.truth_status === 'blocked') process.exitCode = 1;
|
|
3827
|
+
} catch (error) {
|
|
3828
|
+
const result = {
|
|
3829
|
+
schema: 'yam.verification-closure-error.v1',
|
|
3830
|
+
action,
|
|
3831
|
+
status: 'blocked',
|
|
3832
|
+
reason: summarizeCheckOutput(errorMessage(error)),
|
|
3833
|
+
next_action: 'repair changed-file scope, selected/executed command evidence, skipped/truncated reasons, or receipt integrity before retrying',
|
|
3834
|
+
truth_status: 'blocked'
|
|
3835
|
+
};
|
|
3836
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Verification closure');
|
|
3837
|
+
process.exitCode = 1;
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3840
|
+
|
|
3841
|
+
function verificationUsage() {
|
|
3842
|
+
console.log(`yam verification closure
|
|
3843
|
+
|
|
3844
|
+
Explicit, immutable completion receipts. Create records operator-supplied results; it does not execute commands or replace advisory yam detect output.
|
|
3845
|
+
|
|
3846
|
+
Usage:
|
|
3847
|
+
yam verification closure create [--root dir] --receipt-path .yam/verification/closure.json --planned-scope affected|release --changed-file file --selected-spec file [--executed-spec file] [--skipped-spec file] [--truncated-spec file] [--json]
|
|
3848
|
+
yam verification closure verify [--root dir] --receipt-path .yam/verification/closure.json [--json]
|
|
3849
|
+
|
|
3850
|
+
Selected spec: {"id":"typecheck","command":"npm run typecheck","scope":"affected","required":true}
|
|
3851
|
+
Executed spec: {"id":"typecheck","command":"npm run typecheck","exit_code":0,"evidence":"exit 0 observed"}
|
|
3852
|
+
Skipped/truncated spec: {"check_id":"browser","reason":"no UI surface changed"}
|
|
3853
|
+
|
|
3854
|
+
Repeat spec and changed-file flags as needed. Release-sensitive paths promote affected to release and require an executed required release-scope command. Required selected commands cannot be skipped. Digest verification does not upgrade operator-declared execution results beyond partial truth.
|
|
3855
|
+
`);
|
|
3856
|
+
}
|
|
3857
|
+
|
|
3445
3858
|
async function media(args = []) {
|
|
3446
3859
|
const subcommand = args[0] || 'help';
|
|
3447
3860
|
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') return mediaUsage();
|
|
3448
3861
|
if (subcommand === 'proof') return mediaProof(args.slice(1));
|
|
3862
|
+
if (subcommand === 'provider') return mediaProvider(args.slice(1));
|
|
3449
3863
|
console.error(`unknown media command: ${subcommand}`);
|
|
3450
3864
|
return mediaUsage();
|
|
3451
3865
|
}
|
|
@@ -3457,9 +3871,63 @@ Opt-in media generation proof helpers. Generated media can support visual direct
|
|
|
3457
3871
|
|
|
3458
3872
|
Usage:
|
|
3459
3873
|
yam media proof [--tool name] [--requested] [--attempted] [--output file] [--wait-loop] [--blocked-reason text] [--json]
|
|
3874
|
+
yam media provider create [--root dir] --receipt-path .yam/media/run.json --demand-kind media_generation|media_transformation --demand-evidence text --provider name --model name --provider-calls n [--provider-execution] [--dry-run] [--submit] --asset-spec file [--json]
|
|
3875
|
+
yam media provider verify [--root dir] --receipt-path .yam/media/run.json [--json]
|
|
3876
|
+
|
|
3877
|
+
Provider receipts are optional and demand-gated. This command hashes local assets and records an operator-asserted boundary; it never contacts a provider. Dry runs require zero calls and no submit. Explicit execution requires at least one call, submit, and a provider-provenance output asset.
|
|
3460
3878
|
`);
|
|
3461
3879
|
}
|
|
3462
3880
|
|
|
3881
|
+
async function mediaProvider(args = []) {
|
|
3882
|
+
const action = String(args[0] || 'help');
|
|
3883
|
+
if (action === 'help' || action === '--help' || action === '-h') return mediaUsage();
|
|
3884
|
+
const flags = parseSimpleFlags(args.slice(1), new Set([
|
|
3885
|
+
'root', 'receipt-path', 'demand-kind', 'demand-evidence', 'provider', 'model',
|
|
3886
|
+
'provider-calls', 'provider-execution', 'dry-run', 'submit', 'asset-spec', 'json'
|
|
3887
|
+
]));
|
|
3888
|
+
const root = path.resolve(expandHome(flags.root || process.cwd()));
|
|
3889
|
+
try {
|
|
3890
|
+
let result: AnyRecord;
|
|
3891
|
+
if (action === 'create') {
|
|
3892
|
+
requireProductionFlags(flags, [
|
|
3893
|
+
'receipt_path', 'demand_kind', 'demand_evidence', 'provider', 'model', 'provider_calls', 'asset_spec'
|
|
3894
|
+
]);
|
|
3895
|
+
result = await createMediaProviderReceipt({
|
|
3896
|
+
root,
|
|
3897
|
+
receipt_path: String(flags.receipt_path),
|
|
3898
|
+
demand_trigger: {
|
|
3899
|
+
kind: String(flags.demand_kind) as 'media_generation' | 'media_transformation',
|
|
3900
|
+
evidence: String(flags.demand_evidence)
|
|
3901
|
+
},
|
|
3902
|
+
provider: { name: String(flags.provider), model: String(flags.model) },
|
|
3903
|
+
provider_calls: Number(flags.provider_calls),
|
|
3904
|
+
provider_execution: Boolean(flags.provider_execution),
|
|
3905
|
+
dry_run: Boolean(flags.dry_run),
|
|
3906
|
+
submit: Boolean(flags.submit),
|
|
3907
|
+
assets: await readProductionSpecs(flags.asset_spec, 'media asset spec') as any
|
|
3908
|
+
});
|
|
3909
|
+
} else if (action === 'verify') {
|
|
3910
|
+
requireProductionFlags(flags, ['receipt_path']);
|
|
3911
|
+
result = await verifyMediaProviderReceipt({ root, receipt_path: String(flags.receipt_path) });
|
|
3912
|
+
} else {
|
|
3913
|
+
throw new Error(`unknown media provider command: ${action}`);
|
|
3914
|
+
}
|
|
3915
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Media provider receipt');
|
|
3916
|
+
if (result.truth_status === 'blocked') process.exitCode = 1;
|
|
3917
|
+
} catch (error) {
|
|
3918
|
+
const result = {
|
|
3919
|
+
schema: 'yam.media-provider-receipt-error.v1',
|
|
3920
|
+
action,
|
|
3921
|
+
status: 'blocked',
|
|
3922
|
+
reason: summarizeCheckOutput(errorMessage(error)),
|
|
3923
|
+
next_action: 'repair the demand, exact execution fields, confined asset provenance, or receipt integrity before retrying',
|
|
3924
|
+
truth_status: 'blocked'
|
|
3925
|
+
};
|
|
3926
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Media provider receipt');
|
|
3927
|
+
process.exitCode = 1;
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3463
3931
|
async function mediaProof(args = []) {
|
|
3464
3932
|
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') return mediaUsage();
|
|
3465
3933
|
const flags = parseSimpleFlags(args, new Set(['tool', 'requested', 'attempted', 'output', 'wait-loop', 'blocked-reason', 'next-action', 'json']));
|
|
@@ -3719,12 +4187,37 @@ async function missionGate(args = []) {
|
|
|
3719
4187
|
receipts
|
|
3720
4188
|
});
|
|
3721
4189
|
const blockers = [...gate.blockers, ...loadErrors.map((error) => `receipt_load_failed: ${error}`)];
|
|
4190
|
+
const gateResult = buildStrictGateResult({
|
|
4191
|
+
gate_id: 'mission-completion',
|
|
4192
|
+
boundary: 'mission',
|
|
4193
|
+
checks: [
|
|
4194
|
+
{
|
|
4195
|
+
id: 'receipt-inventory',
|
|
4196
|
+
status: gate.ready_to_claim_complete && loadErrors.length === 0 ? 'passed' : 'failed',
|
|
4197
|
+
note: blockers[0] || 'all expected Mission receipts are present and eligible',
|
|
4198
|
+
required: true
|
|
4199
|
+
},
|
|
4200
|
+
{
|
|
4201
|
+
id: 'read-only-boundary',
|
|
4202
|
+
status: gate.read_only_violations.length === 0 ? 'passed' : 'failed',
|
|
4203
|
+
note: gate.read_only_violations.length ? `violations: ${gate.read_only_violations.join(', ')}` : 'reviewer and doctor receipts preserve read-only access',
|
|
4204
|
+
required: true
|
|
4205
|
+
}
|
|
4206
|
+
],
|
|
4207
|
+
blockers,
|
|
4208
|
+
evidence: gate.receipts.map((receipt) => `${receipt.thread_id}:${receipt.receipt_id}:${receipt.outcome}`),
|
|
4209
|
+
next_action: blockers[0] || gate.next_action
|
|
4210
|
+
});
|
|
4211
|
+
const gateContract = verifyStrictGateResult(gateResult);
|
|
4212
|
+
const strictReady = gate.ready_to_claim_complete && loadErrors.length === 0 && gateResult.status === 'passed' && gateContract.valid;
|
|
3722
4213
|
const result = {
|
|
3723
4214
|
...gate,
|
|
3724
4215
|
blockers,
|
|
3725
|
-
ready_to_claim_complete:
|
|
4216
|
+
ready_to_claim_complete: strictReady,
|
|
3726
4217
|
next_action: blockers[0] || gate.next_action,
|
|
3727
|
-
truth_status:
|
|
4218
|
+
truth_status: strictReady ? gate.truth_status : 'blocked',
|
|
4219
|
+
gate_result: gateResult,
|
|
4220
|
+
gate_contract: gateContract,
|
|
3728
4221
|
persistence: flags.out ? 'written' : 'not-persisted',
|
|
3729
4222
|
out: flags.out ? path.resolve(expandHome(flags.out)) : ''
|
|
3730
4223
|
};
|
|
@@ -3810,15 +4303,72 @@ function benchmarkUsage() {
|
|
|
3810
4303
|
|
|
3811
4304
|
Usage:
|
|
3812
4305
|
yam benchmark report [--label text] [--baseline n] [--current n] [--unit ms] [--target lower|higher] [--next-action text] [--json]
|
|
4306
|
+
yam benchmark report --candidate-digest sha256 --baseline-digest sha256 --sample seed:candidate:baseline --unit score --evidence-source text [--sample ...] [--min-samples n] [--min-mean-delta n] [--min-win-rate n] [--json]
|
|
3813
4307
|
|
|
3814
4308
|
Notes:
|
|
3815
4309
|
Records a small optimization loop result. It does not run benchmarks by itself.
|
|
4310
|
+
Promotion mode verifies deterministic paired-sample arithmetic but treats supplied scores as operator assertions, not statistical significance.
|
|
3816
4311
|
`);
|
|
3817
4312
|
}
|
|
3818
4313
|
|
|
3819
4314
|
async function benchmarkReport(args = []) {
|
|
3820
4315
|
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h') return benchmarkUsage();
|
|
3821
|
-
const flags = parseSimpleFlags(args, new Set(['label', 'baseline', 'current', 'unit', 'target', 'next-action', 'rollback-hint', 'intent', 'json']));
|
|
4316
|
+
const flags = parseSimpleFlags(args, new Set(['label', 'baseline', 'current', 'unit', 'target', 'next-action', 'rollback-hint', 'intent', 'candidate-digest', 'baseline-digest', 'sample', 'min-samples', 'min-mean-delta', 'min-win-rate', 'evidence-source', 'json']));
|
|
4317
|
+
const promotionRequested = Boolean(
|
|
4318
|
+
flags.candidate_digest
|
|
4319
|
+
|| flags.baseline_digest
|
|
4320
|
+
|| flags.sample
|
|
4321
|
+
|| flags.min_samples
|
|
4322
|
+
|| flags.min_mean_delta
|
|
4323
|
+
|| flags.min_win_rate
|
|
4324
|
+
|| flags.evidence_source
|
|
4325
|
+
);
|
|
4326
|
+
if (promotionRequested) {
|
|
4327
|
+
try {
|
|
4328
|
+
const promotion = buildBoundedPromotionReport({
|
|
4329
|
+
label: String(flags.label || 'unnamed'),
|
|
4330
|
+
candidate_digest: String(flags.candidate_digest || ''),
|
|
4331
|
+
baseline_digest: String(flags.baseline_digest || ''),
|
|
4332
|
+
samples: arrayFlag(flags.sample).map((sample) => parsePromotionSample(sample)),
|
|
4333
|
+
min_samples: flags.min_samples === undefined ? undefined : Number(flags.min_samples),
|
|
4334
|
+
min_mean_delta: flags.min_mean_delta === undefined ? undefined : Number(flags.min_mean_delta),
|
|
4335
|
+
min_win_rate: flags.min_win_rate === undefined ? undefined : Number(flags.min_win_rate),
|
|
4336
|
+
target: String(flags.target || 'higher').toLowerCase() as 'lower' | 'higher',
|
|
4337
|
+
unit: String(flags.unit || ''),
|
|
4338
|
+
evidence_source: String(flags.evidence_source || '')
|
|
4339
|
+
});
|
|
4340
|
+
const gateResult = buildStrictGateResult({
|
|
4341
|
+
gate_id: 'benchmark-promotion',
|
|
4342
|
+
boundary: 'generic',
|
|
4343
|
+
checks: [
|
|
4344
|
+
{
|
|
4345
|
+
id: 'promotion-decision',
|
|
4346
|
+
status: promotion.decision === 'keep' ? 'passed' : 'failed',
|
|
4347
|
+
note: promotion.next_action,
|
|
4348
|
+
required: true
|
|
4349
|
+
}
|
|
4350
|
+
],
|
|
4351
|
+
blockers: promotion.blockers,
|
|
4352
|
+
evidence: [promotion.evidence_digest, promotion.evidence_source || 'operator-supplied paired measurements'],
|
|
4353
|
+
next_action: promotion.next_action
|
|
4354
|
+
});
|
|
4355
|
+
const result = { ...promotion, gate_result: gateResult };
|
|
4356
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Bounded promotion report');
|
|
4357
|
+
if (promotion.decision !== 'keep' || gateResult.status !== 'passed') process.exitCode = 1;
|
|
4358
|
+
return;
|
|
4359
|
+
} catch (error) {
|
|
4360
|
+
const result = {
|
|
4361
|
+
schema: 'yam.bounded-promotion-error.v1',
|
|
4362
|
+
decision: 'blocked',
|
|
4363
|
+
blockers: [errorMessage(error)],
|
|
4364
|
+
next_action: 'repair the malformed paired-sample input before making a promotion decision',
|
|
4365
|
+
truth_status: 'blocked'
|
|
4366
|
+
};
|
|
4367
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Bounded promotion report');
|
|
4368
|
+
process.exitCode = 1;
|
|
4369
|
+
return;
|
|
4370
|
+
}
|
|
4371
|
+
}
|
|
3822
4372
|
const baseline = numberOrNull(flags.baseline);
|
|
3823
4373
|
const current = numberOrNull(flags.current);
|
|
3824
4374
|
const target = String(flags.target || 'lower').toLowerCase() === 'higher' ? 'higher' : 'lower';
|
|
@@ -3879,7 +4429,7 @@ function parseSimpleFlags(args = [], allowed = new Set<string>()) {
|
|
|
3879
4429
|
const key = rawKey.slice(2);
|
|
3880
4430
|
if (allowed.size && !allowed.has(key)) continue;
|
|
3881
4431
|
const normalizedKey = key.replace(/-/g, '_');
|
|
3882
|
-
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)) {
|
|
4432
|
+
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', 'provider-execution', 'dry-run', 'submit'].includes(key)) {
|
|
3883
4433
|
flags[normalizedKey] = true;
|
|
3884
4434
|
continue;
|
|
3885
4435
|
}
|
|
@@ -4413,7 +4963,12 @@ function formatStructuredEvidence(value) {
|
|
|
4413
4963
|
return JSON.stringify(buildMissionSubagentReceipt(data));
|
|
4414
4964
|
}
|
|
4415
4965
|
if (data.schema === 'yam.mission-completion-gate.v1' || data.expected_thread_ids || data.ready_to_claim_complete !== undefined) {
|
|
4416
|
-
|
|
4966
|
+
const completion = buildMissionCompletionGate(data);
|
|
4967
|
+
return JSON.stringify({
|
|
4968
|
+
...completion,
|
|
4969
|
+
...(data.gate_result && typeof data.gate_result === 'object' ? { gate_result: data.gate_result } : {}),
|
|
4970
|
+
...(data.gate_contract && typeof data.gate_contract === 'object' ? { gate_contract: data.gate_contract } : {})
|
|
4971
|
+
});
|
|
4417
4972
|
}
|
|
4418
4973
|
if (data.schema === 'yam.mission-patch-envelope.v1' || data.agent_id || data.assigned_scope) {
|
|
4419
4974
|
return JSON.stringify(buildMissionPatchEnvelope(data));
|
|
@@ -5415,6 +5970,11 @@ function showPath() {
|
|
|
5415
5970
|
function showRequestedHelp(args: string[]) {
|
|
5416
5971
|
if (!args.some((arg) => arg === '--help' || arg === '-h')) return false;
|
|
5417
5972
|
|
|
5973
|
+
if (args[0] === 'ueye' && args[1] === 'production') {
|
|
5974
|
+
ueyeProductionUsage();
|
|
5975
|
+
return true;
|
|
5976
|
+
}
|
|
5977
|
+
|
|
5418
5978
|
const commandUsage: Record<string, () => void> = {
|
|
5419
5979
|
context: contextUsage,
|
|
5420
5980
|
cleanup: cleanupUsage,
|
|
@@ -5424,6 +5984,7 @@ function showRequestedHelp(args: string[]) {
|
|
|
5424
5984
|
loop: loopUsage,
|
|
5425
5985
|
ueye: ueyeUsage,
|
|
5426
5986
|
media: mediaUsage,
|
|
5987
|
+
verification: verificationUsage,
|
|
5427
5988
|
runtime: runtimeUsage,
|
|
5428
5989
|
mission: missionUsage,
|
|
5429
5990
|
update: updateUsage,
|
|
@@ -5454,6 +6015,7 @@ async function main() {
|
|
|
5454
6015
|
if (command === 'loop') return loop(process.argv.slice(3));
|
|
5455
6016
|
if (command === 'ueye') return ueye(process.argv.slice(3));
|
|
5456
6017
|
if (command === 'media') return media(process.argv.slice(3));
|
|
6018
|
+
if (command === 'verification') return verification(process.argv.slice(3));
|
|
5457
6019
|
if (command === 'runtime') return runtime(process.argv.slice(3));
|
|
5458
6020
|
if (command === 'mission') return mission(process.argv.slice(3));
|
|
5459
6021
|
if (command === 'update') return updateCommand(process.argv.slice(3));
|