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/dist/bin/yam.js CHANGED
@@ -10,8 +10,13 @@ import process from 'node:process';
10
10
  import { fileURLToPath } from 'node:url';
11
11
  import { TRUTH_STATUSES, buildLoopReport, buildMediaGenerationProof, buildMissionCompletionGate, buildMissionPatchEnvelope, buildMissionSubagentReceipt, buildRollbackHint, buildRuntimeBackendEvidence, buildStudyNote, buildUeyeDesignCompletionGate, buildUeyeVisualProvenance, buildUeyeRunReport, buildUeyeSurfaceContext, buildYamCompletionProof, detectDbSafetyText as detectTrustDbSafetyText, isTruthStatus } from '../lib/trust-kernel.js';
12
12
  import { archiveUeyeRevision, inspectImageFile, upsertUeyeAsset, verifyUeyeAssetManifest, verifyUeyeRevisionHistory } from '../lib/ueye-artifacts.js';
13
+ import { addPlanReviewComment, closePlanReviewCanvas, createDesignRevisionState, createPlanReviewCanvas, finalizeDesignProductionPhase, readCanonicalDesignRevisionState, readPlanReviewSession, recordDesignRevisionRound, verifyFinalGalleryManifest, verifyDesignProductionPhaseReceipt, writeFinalGalleryManifest } from '../lib/design-production.js';
13
14
  import { inspectSkillInstallation, installSkillSetTransactional, uninstallSkillSetSafely } from '../lib/skill-installation.js';
14
15
  import { EXTERNAL_UPDATE_COMPONENTS, applyExternalUpdates, checkExternalUpdates } from '../lib/external-updates.js';
16
+ import { buildBoundedPromotionReport, parsePromotionSample } from '../lib/benchmark-promotion.js';
17
+ import { buildStrictGateResult, verifyStrictGateResult } from '../lib/gate-result.js';
18
+ import { createVerificationClosureReceipt, verifyVerificationClosureReceiptFile } from '../lib/verification-closure.js';
19
+ import { createMediaProviderReceipt, verifyMediaProviderReceipt } from '../lib/media-provider-receipt.js';
15
20
  import { BoundedInputError, GENERAL_STDIN_MAX_BYTES, HOOK_STDIN_MAX_BYTES, readBoundedStdinJson, readBoundedStdinText } from '../lib/bounded-input.js';
16
21
  function errorMessage(error) {
17
22
  return error instanceof Error ? error.message : String(error);
@@ -143,15 +148,18 @@ Usage:
143
148
  yam ueye report [--reference ref.png] [--actual screenshot.png] [--provider-context local] [--execution-surface in-app-browser] [--json]
144
149
  yam ueye asset <add|verify> [options]
145
150
  yam ueye revision <archive|verify> [options]
151
+ yam ueye production <canvas|revision|gallery|finalize|phase> [options]
146
152
  yam media proof [--requested] [--attempted] [--output file] [--json]
147
153
  yam runtime evidence [--backend terminal|in-app-browser|playwright|tmux|zellij] [--claim observed|started|stopped|cleanup-verified] [--json]
148
154
  yam mission queue [--agent-id id] [--scope text] [--changed file] [--verification-hint text] [--json]
149
155
  yam mission receipt [--thread-id id] [--role reviewer] [--lifecycle stopped] [--outcome passed] [--evidence text] [--json]
150
156
  yam mission gate [--expected-thread id] [--receipt file] [--json]
157
+ yam verification closure <create|verify> [options]
151
158
  yam update check [--json]
152
159
  yam update apply --component yam|scrapling|insane-search [--json]
153
160
  yam update apply --all [--json]
154
161
  yam benchmark report [--baseline n] [--current n] [--unit ms] [--target lower|higher] [--json]
162
+ 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]
155
163
  yam release report [--json]
156
164
  yam safety [text...]
157
165
  yam memory <init|add|list|summary|resolve> [dir] [options]
@@ -1306,6 +1314,7 @@ async function buildToolsDoctorReport(targetDir = process.cwd()) {
1306
1314
  readinessRow('Supabase plugin', await pluginCacheHas('openai-curated/supabase') ? 'ready' : 'unknown', 'plugin cache only; no DB query performed'),
1307
1315
  readinessRow('Vercel plugin', await pluginCacheHas('openai-curated/vercel') ? 'ready' : 'unknown', 'plugin cache only; no deployment/API call performed')
1308
1316
  ];
1317
+ const capabilityMatrix = buildCapabilityMatrix(rows, globalLiteHookHealth, globalStudyNoteHookHealth);
1309
1318
  const riskNotes = [
1310
1319
  ...instructionSurfaces.issues.map((message) => ({ level: 'issue', reason: message })),
1311
1320
  ...instructionSurfaces.warnings.map((message) => ({ level: 'warning', reason: message })),
@@ -1332,6 +1341,7 @@ async function buildToolsDoctorReport(targetDir = process.cwd()) {
1332
1341
  frameworkChecklist,
1333
1342
  contextPressure,
1334
1343
  realProbe,
1344
+ capabilityMatrix,
1335
1345
  sqlScan,
1336
1346
  riskNotes,
1337
1347
  nextActions: nextActionDetails.map((action) => action.next_action),
@@ -1344,6 +1354,43 @@ async function buildToolsDoctorReport(targetDir = process.cwd()) {
1344
1354
  }
1345
1355
  };
1346
1356
  }
1357
+ function buildCapabilityMatrix(readinessRows = [], liteHook = {}, studyNoteHook = {}) {
1358
+ const readiness = new Map(readinessRows.map((row) => [row.name, row.status]));
1359
+ const capabilities = [
1360
+ 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'),
1361
+ capabilityRow('external_update_check', 'stable', 'unknown', 'packaged `yam update check` command', 'this inventory does not contact registries or prove Codex/plugin/network availability'),
1362
+ 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'),
1363
+ capabilityRow('mission_receipt_gate', 'stable', 'ready', 'packaged mission receipt and aggregate gate commands', 'validates supplied real-thread evidence; it does not spawn agents'),
1364
+ 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'),
1365
+ capabilityRow('ueye_asset_revision_integrity', 'stable', 'ready', 'hash-verified Ueye asset and revision manifests', 'integrity does not prove design quality, licensing, or browser state'),
1366
+ capabilityRow('plan_review_canvas_lite', 'beta', 'ready', 'local static review session artifacts', 'no remote hosting, background service, or automatic implementation start'),
1367
+ capabilityRow('bounded_design_revisions', 'beta', 'ready', 'maximum two reviewer-finding-backed revision rounds', 'round state does not replace Ueye artifact hash verification'),
1368
+ capabilityRow('final_gallery_manifest', 'beta', 'ready', 'path-confined hash-verified packaging manifest', 'gallery completeness does not prove visual or license correctness'),
1369
+ 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'),
1370
+ 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'),
1371
+ 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'),
1372
+ capabilityRow('yam_lite_hook', 'beta', String(liteHook.state || readiness.get('yam-lite hook') || 'unknown'), 'optional hook health inspection', 'advisory only; disabled unless explicitly enabled'),
1373
+ 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')
1374
+ ];
1375
+ return {
1376
+ schema: 'yam.capability-matrix.v1',
1377
+ generated_at: new Date().toISOString(),
1378
+ capabilities,
1379
+ maturity_vocabulary: ['stable', 'beta', 'experimental', 'instruction-only'],
1380
+ truth_status: 'partial',
1381
+ note: 'support maturity and current runtime state are separate; this read-only inventory does not execute capabilities'
1382
+ };
1383
+ }
1384
+ function capabilityRow(id, maturity, runtimeState, evidence, limit) {
1385
+ return {
1386
+ id,
1387
+ maturity,
1388
+ runtime_state: runtimeState,
1389
+ evidence,
1390
+ limit,
1391
+ truth_status: runtimeState === 'ready' ? 'partial' : 'assumed'
1392
+ };
1393
+ }
1347
1394
  function printToolsDoctorReport(data) {
1348
1395
  console.log('yam tools doctor');
1349
1396
  console.log(`Project: ${data.project}`);
@@ -1359,6 +1406,13 @@ function printToolsDoctorReport(data) {
1359
1406
  console.log(`- ${key}: ${value || '(not found)'}`);
1360
1407
  }
1361
1408
  }
1409
+ if (data.capabilityMatrix?.capabilities?.length) {
1410
+ console.log('');
1411
+ console.log('Capability matrix:');
1412
+ for (const capability of data.capabilityMatrix.capabilities) {
1413
+ console.log(`- ${capability.id}: ${capability.maturity} / ${capability.runtime_state} (${capability.limit})`);
1414
+ }
1415
+ }
1362
1416
  if (data.instructionSurfaces.found.length || data.projectSurfaces.length) {
1363
1417
  console.log('');
1364
1418
  console.log('Project surfaces:');
@@ -2258,8 +2312,35 @@ function runReleaseReport() {
2258
2312
  const publishBlockerEvidence = publishBlockerEvidenceFromRelease(checks, tarball);
2259
2313
  const publishReadiness = releasePublishReadiness(checks, provenance, tarball, publishBlockerEvidence);
2260
2314
  const nextActions = releaseNextActions(checks, provenance, tarball, publishBlockerEvidence, publishReadiness);
2261
- const ok = failed.length === 0 && publishReadiness.status === 'ready';
2262
- const readinessReceipt = releaseReadinessReceipt(provenance, freshness, tarball, publishReadiness, ok);
2315
+ const preliminaryOk = failed.length === 0 && publishReadiness.status === 'ready';
2316
+ const readinessReceipt = releaseReadinessReceipt(provenance, freshness, tarball, publishReadiness, preliminaryOk);
2317
+ const gateResult = buildStrictGateResult({
2318
+ gate_id: 'release-readiness',
2319
+ boundary: 'release',
2320
+ checks: [
2321
+ ...checks.map((check) => ({
2322
+ id: `release-${String(check.id || 'unnamed').replace(/[^a-z0-9._-]/gi, '-')}`,
2323
+ status: check.status === 'passed' ? 'passed' : 'failed',
2324
+ note: check.note || check.command || check.id,
2325
+ required: true
2326
+ })),
2327
+ {
2328
+ id: 'publish-readiness',
2329
+ status: publishReadiness.status === 'ready' ? 'passed' : 'failed',
2330
+ note: publishReadiness.next_action,
2331
+ required: true
2332
+ }
2333
+ ],
2334
+ blockers: publishReadiness.blockers?.map((blocker) => `${blocker.kind}: ${blocker.reason}`) || [],
2335
+ evidence: [
2336
+ `${readinessReceipt.schema}:${readinessReceipt.local_version}`,
2337
+ tarball.integrity ? `tarball-integrity:${tarball.integrity}` : 'tarball-integrity:not-observed',
2338
+ provenance.git_commit ? `git-commit:${provenance.git_commit}` : 'git-commit:not-observed'
2339
+ ],
2340
+ next_action: publishReadiness.next_action
2341
+ });
2342
+ const gateContract = verifyStrictGateResult(gateResult);
2343
+ const ok = preliminaryOk && gateResult.status === 'passed' && gateContract.valid;
2263
2344
  return {
2264
2345
  schema: 'yam.release-report.v1',
2265
2346
  generated_at: startedAt,
@@ -2274,6 +2355,8 @@ function runReleaseReport() {
2274
2355
  freshness,
2275
2356
  publish_readiness: publishReadiness,
2276
2357
  readiness_receipt: readinessReceipt,
2358
+ gate_result: gateResult,
2359
+ gate_contract: gateContract,
2277
2360
  study_note: releaseStudyNote(publishReadiness, publishBlockerEvidence),
2278
2361
  publishBlockerEvidence,
2279
2362
  next_actions: nextActions
@@ -2767,6 +2850,8 @@ async function ueye(args = []) {
2767
2850
  return ueyeAsset(args.slice(1));
2768
2851
  if (subcommand === 'revision')
2769
2852
  return ueyeRevision(args.slice(1));
2853
+ if (subcommand === 'production')
2854
+ return ueyeProduction(args.slice(1));
2770
2855
  console.error(`unknown ueye command: ${subcommand}`);
2771
2856
  return ueyeUsage();
2772
2857
  }
@@ -2784,6 +2869,11 @@ Usage:
2784
2869
  yam ueye asset verify [--manifest file] [--json]
2785
2870
  yam ueye revision archive --file image --round n [--artifact-id id] [--root dir] [--manifest file] [--json]
2786
2871
  yam ueye revision verify [--root dir] [--manifest file] [--json]
2872
+ yam ueye production canvas <create|comment|close|show> [options]
2873
+ yam ueye production revision <init|record|show> [options]
2874
+ yam ueye production gallery <write|verify> [options]
2875
+ yam ueye production finalize [options]
2876
+ yam ueye production phase verify [options]
2787
2877
 
2788
2878
  Notes:
2789
2879
  capture uses a locally available Playwright install when present. It does not download browsers or install dependencies.
@@ -2791,6 +2881,7 @@ Notes:
2791
2881
  report produces a proof-ready Ueye visual run report, design completion gate, and continuity/comparison record without requiring a new capture.
2792
2882
  asset records local reference provenance and protection flags; it never downloads remote content.
2793
2883
  revision copies the current local artifact into a non-overwriting round archive before the live file is edited.
2884
+ 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.
2794
2885
  `);
2795
2886
  }
2796
2887
  async function ueyeAsset(args = []) {
@@ -2870,6 +2961,252 @@ async function ueyeRevision(args = []) {
2870
2961
  process.exitCode = 1;
2871
2962
  }
2872
2963
  }
2964
+ async function ueyeProduction(args = []) {
2965
+ const surface = String(args[0] || 'help');
2966
+ const isFinalize = surface === 'finalize';
2967
+ const action = isFinalize ? 'run' : String(args[1] || 'help');
2968
+ const flagArgs = isFinalize ? args.slice(1) : args.slice(2);
2969
+ if (surface === 'help' || action === 'help' || surface === '--help' || surface === '-h' || flagArgs.includes('--help') || flagArgs.includes('-h'))
2970
+ return ueyeProductionUsage();
2971
+ const flags = parseSimpleFlags(flagArgs, new Set([
2972
+ 'root', 'session-id', 'title', 'artifact-spec', 'session-path', 'render-path',
2973
+ 'comment-id', 'artifact-id', 'anchor-kind', 'locator', 'finding', 'requested-change', 'verdict',
2974
+ 'state-path', 'finding-id', 'source-comment-id', 'finding-summary', 'finding-evidence', 'planned-change', 'outcome', 'revision-ref',
2975
+ 'manifest-path', 'completion-state', 'demand-kind', 'demand-evidence', 'canvas-session-path',
2976
+ 'revision-state-path', 'gallery-manifest-path', 'receipt-path', 'json'
2977
+ ]));
2978
+ const root = path.resolve(expandHome(flags.root || process.cwd()));
2979
+ try {
2980
+ let result;
2981
+ if (surface === 'canvas' && action === 'create') {
2982
+ requireProductionFlags(flags, ['session_id', 'title', 'demand_kind', 'demand_evidence', 'artifact_spec']);
2983
+ result = await createPlanReviewCanvas({
2984
+ root,
2985
+ session_id: String(flags.session_id),
2986
+ title: String(flags.title),
2987
+ demand_trigger: {
2988
+ kind: String(flags.demand_kind),
2989
+ evidence: String(flags.demand_evidence)
2990
+ },
2991
+ artifacts: await readProductionSpecs(flags.artifact_spec, 'canvas artifact spec'),
2992
+ ...(flags.session_path ? { session_path: String(flags.session_path) } : {}),
2993
+ ...(flags.render_path ? { render_path: String(flags.render_path) } : {})
2994
+ });
2995
+ }
2996
+ else if (surface === 'canvas' && action === 'comment') {
2997
+ requireProductionFlags(flags, ['session_path', 'render_path', 'comment_id', 'artifact_id', 'anchor_kind', 'locator', 'finding', 'requested_change']);
2998
+ result = await addPlanReviewComment({
2999
+ root,
3000
+ session_path: String(flags.session_path),
3001
+ render_path: String(flags.render_path),
3002
+ comment: {
3003
+ id: String(flags.comment_id),
3004
+ anchor: {
3005
+ artifact_id: String(flags.artifact_id),
3006
+ kind: String(flags.anchor_kind),
3007
+ locator: String(flags.locator)
3008
+ },
3009
+ finding: String(flags.finding),
3010
+ requested_change: String(flags.requested_change)
3011
+ }
3012
+ });
3013
+ }
3014
+ else if (surface === 'canvas' && action === 'close') {
3015
+ requireProductionFlags(flags, ['session_path', 'render_path', 'verdict']);
3016
+ result = await closePlanReviewCanvas({
3017
+ root,
3018
+ session_path: String(flags.session_path),
3019
+ render_path: String(flags.render_path),
3020
+ verdict: String(flags.verdict)
3021
+ });
3022
+ }
3023
+ else if (surface === 'canvas' && action === 'show') {
3024
+ requireProductionFlags(flags, ['session_path']);
3025
+ const sessionPath = await existingProductionPath(root, String(flags.session_path), 'session_path');
3026
+ result = await readPlanReviewSession(sessionPath);
3027
+ }
3028
+ else if (surface === 'revision' && action === 'init') {
3029
+ requireProductionFlags(flags, ['session_id']);
3030
+ result = await createDesignRevisionState({
3031
+ root,
3032
+ session_id: String(flags.session_id),
3033
+ ...(flags.state_path ? { state_path: String(flags.state_path) } : {})
3034
+ });
3035
+ }
3036
+ else if (surface === 'revision' && action === 'record') {
3037
+ requireProductionFlags(flags, ['state_path', 'finding_id', 'source_comment_id', 'finding_summary', 'finding_evidence', 'planned_change', 'outcome', 'revision_ref']);
3038
+ result = await recordDesignRevisionRound({
3039
+ root,
3040
+ state_path: String(flags.state_path),
3041
+ reviewer_finding: {
3042
+ id: String(flags.finding_id),
3043
+ source_comment_id: String(flags.source_comment_id),
3044
+ summary: String(flags.finding_summary),
3045
+ evidence: String(flags.finding_evidence)
3046
+ },
3047
+ planned_change: String(flags.planned_change),
3048
+ outcome: String(flags.outcome),
3049
+ revision_refs: await readProductionSpecs(flags.revision_ref, 'Ueye revision reference')
3050
+ });
3051
+ }
3052
+ else if (surface === 'revision' && action === 'show') {
3053
+ requireProductionFlags(flags, ['state_path']);
3054
+ result = await readCanonicalDesignRevisionState({ root, state_path: String(flags.state_path) });
3055
+ }
3056
+ else if (surface === 'gallery' && action === 'write') {
3057
+ requireProductionFlags(flags, ['manifest_path', 'session_id', 'completion_state', 'artifact_spec']);
3058
+ result = await writeFinalGalleryManifest({
3059
+ root,
3060
+ manifest_path: String(flags.manifest_path),
3061
+ session_id: String(flags.session_id),
3062
+ completion_state: String(flags.completion_state),
3063
+ artifacts: await readProductionSpecs(flags.artifact_spec, 'gallery artifact spec')
3064
+ });
3065
+ }
3066
+ else if (surface === 'gallery' && action === 'verify') {
3067
+ requireProductionFlags(flags, ['manifest_path']);
3068
+ result = await verifyFinalGalleryManifest({ root, manifest_path: String(flags.manifest_path) });
3069
+ }
3070
+ else if (surface === 'finalize' && action === 'run') {
3071
+ requireProductionFlags(flags, ['demand_kind', 'demand_evidence', 'canvas_session_path', 'gallery_manifest_path', 'receipt_path']);
3072
+ result = await finalizeDesignProductionPhase({
3073
+ root,
3074
+ demand_trigger: {
3075
+ kind: String(flags.demand_kind),
3076
+ evidence: String(flags.demand_evidence)
3077
+ },
3078
+ canvas_session_path: String(flags.canvas_session_path),
3079
+ revision_state_path: flags.revision_state_path ? String(flags.revision_state_path) : null,
3080
+ gallery_manifest_path: String(flags.gallery_manifest_path),
3081
+ receipt_path: String(flags.receipt_path)
3082
+ });
3083
+ }
3084
+ else if (surface === 'phase' && action === 'verify') {
3085
+ requireProductionFlags(flags, ['receipt_path']);
3086
+ result = await verifyDesignProductionPhaseReceipt({ root, receipt_path: String(flags.receipt_path) });
3087
+ }
3088
+ else {
3089
+ throw new Error(`unknown Ueye production command: ${surface} ${action}`);
3090
+ }
3091
+ printJsonOrHuman(result, Boolean(flags.json), 'Ueye design production');
3092
+ if (result.truth_status === 'blocked')
3093
+ process.exitCode = 1;
3094
+ }
3095
+ catch (error) {
3096
+ const reason = summarizeCheckOutput(errorMessage(error));
3097
+ const result = {
3098
+ schema: 'yam.design-production-error.v1',
3099
+ surface,
3100
+ action,
3101
+ status: 'blocked',
3102
+ reason,
3103
+ next_action: /two_round_limit|maximum (?:is )?2|maximum two/i.test(reason)
3104
+ ? 'stop this phase at two rounds and keep unresolved gallery work draft; start a new phase only after new explicit demand authorization'
3105
+ : /stopped with status accepted/i.test(reason)
3106
+ ? 'stop revising because the reviewer already accepted this phase'
3107
+ : 'repair the local spec, path, integrity evidence, or phase state before retrying',
3108
+ truth_status: 'blocked'
3109
+ };
3110
+ printJsonOrHuman(result, Boolean(flags.json), 'Ueye design production');
3111
+ process.exitCode = 1;
3112
+ }
3113
+ }
3114
+ function ueyeProductionUsage() {
3115
+ console.log(`yam ueye production
3116
+
3117
+ Demand-gated local design production helpers. Spec files are bounded JSON objects; repeat --artifact-spec or --revision-ref for multiple entries.
3118
+
3119
+ Usage:
3120
+ 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]
3121
+ 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]
3122
+ yam ueye production canvas close [--root dir] --session-path relative --render-path relative --verdict approve|request_changes [--json]
3123
+ yam ueye production canvas show [--root dir] --session-path relative [--json]
3124
+ yam ueye production revision init [--root dir] --session-id id [--state-path .yam/ueye/design-production/<session-id>/revision-state.json] [--json]
3125
+ 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]
3126
+ yam ueye production revision show [--root dir] --state-path relative [--json]
3127
+ yam ueye production gallery write [--root dir] --manifest-path relative --session-id id --completion-state draft|ready_for_inspection|packaged --artifact-spec file [--json]
3128
+ yam ueye production gallery verify [--root dir] --manifest-path relative [--json]
3129
+ 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]
3130
+ yam ueye production phase verify [--root dir] --receipt-path relative [--json]
3131
+
3132
+ 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.
3133
+ `);
3134
+ }
3135
+ function requireProductionFlags(flags, names) {
3136
+ const missing = names.filter((name) => flags[name] === undefined || flags[name] === '');
3137
+ if (missing.length)
3138
+ throw new Error(`missing required option(s): ${missing.map((name) => `--${name.replace(/_/g, '-')}`).join(', ')}`);
3139
+ }
3140
+ async function readProductionSpecs(value, label) {
3141
+ const files = arrayFlag(value);
3142
+ if (!files.length)
3143
+ throw new Error(`${label} requires at least one JSON spec file`);
3144
+ const specs = [];
3145
+ for (const file of files)
3146
+ specs.push(await readBoundedJsonSpec(path.resolve(expandHome(file)), label));
3147
+ return specs;
3148
+ }
3149
+ async function readOptionalSpecs(value, label) {
3150
+ const files = arrayFlag(value);
3151
+ const specs = [];
3152
+ for (const file of files)
3153
+ specs.push(await readBoundedJsonSpec(path.resolve(expandHome(file)), label));
3154
+ return specs;
3155
+ }
3156
+ async function readBoundedJsonSpec(file, label) {
3157
+ const before = await fsp.lstat(file);
3158
+ if (before.isSymbolicLink() || !before.isFile())
3159
+ throw new Error(`${label} must be a regular non-symlink file: ${file}`);
3160
+ if (before.size > 256 * 1024)
3161
+ throw new Error(`${label} exceeds the 256 KiB limit: ${file}`);
3162
+ const noFollow = process.platform !== 'win32' && typeof fs.constants.O_NOFOLLOW === 'number'
3163
+ ? fs.constants.O_NOFOLLOW
3164
+ : 0;
3165
+ const handle = await fsp.open(file, fs.constants.O_RDONLY | noFollow);
3166
+ let text = '';
3167
+ try {
3168
+ const opened = await handle.stat();
3169
+ if (!opened.isFile() || !sameSpecFileIdentity(before, opened))
3170
+ throw new Error(`${label} identity changed before open: ${file}`);
3171
+ if (opened.size > 256 * 1024)
3172
+ throw new Error(`${label} exceeds the 256 KiB limit: ${file}`);
3173
+ const bytes = Buffer.alloc(opened.size);
3174
+ let offset = 0;
3175
+ while (offset < bytes.length) {
3176
+ const { bytesRead } = await handle.read(bytes, offset, bytes.length - offset, offset);
3177
+ if (!bytesRead)
3178
+ throw new Error(`${label} became shorter while reading: ${file}`);
3179
+ offset += bytesRead;
3180
+ }
3181
+ const openedAfter = await handle.stat();
3182
+ const after = await fsp.lstat(file);
3183
+ if (!sameSpecFileIdentity(opened, openedAfter) || openedAfter.size !== opened.size
3184
+ || after.isSymbolicLink() || !after.isFile() || !sameSpecFileIdentity(opened, after)) {
3185
+ throw new Error(`${label} changed size or identity while reading: ${file}`);
3186
+ }
3187
+ text = bytes.toString('utf8');
3188
+ }
3189
+ finally {
3190
+ await handle.close();
3191
+ }
3192
+ const value = JSON.parse(text);
3193
+ if (!value || typeof value !== 'object' || Array.isArray(value))
3194
+ throw new Error(`${label} must contain one JSON object: ${file}`);
3195
+ return value;
3196
+ }
3197
+ function sameSpecFileIdentity(left, right) {
3198
+ return left.dev === right.dev && left.ino === right.ino;
3199
+ }
3200
+ async function existingProductionPath(root, value, label) {
3201
+ if (!value || path.isAbsolute(value) || value.includes('\0'))
3202
+ throw new Error(`${label} must be a non-empty project-relative path`);
3203
+ const realRoot = await fsp.realpath(root);
3204
+ const candidate = await fsp.realpath(path.resolve(realRoot, value));
3205
+ const relative = path.relative(realRoot, candidate);
3206
+ if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))
3207
+ throw new Error(`${label} escapes the project root`);
3208
+ return candidate;
3209
+ }
2873
3210
  async function ueyePreflight(args = []) {
2874
3211
  if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
2875
3212
  return ueyeUsage();
@@ -3415,12 +3752,83 @@ function normalizeUeyeComparisonResult(value = '') {
3415
3752
  return text;
3416
3753
  return 'not-verified';
3417
3754
  }
3755
+ async function verification(args = []) {
3756
+ const surface = String(args[0] || 'help');
3757
+ const action = String(args[1] || 'help');
3758
+ if (surface === 'help' || action === 'help' || surface === '--help' || surface === '-h')
3759
+ return verificationUsage();
3760
+ if (surface !== 'closure') {
3761
+ console.error(`unknown verification command: ${surface}`);
3762
+ return verificationUsage();
3763
+ }
3764
+ const flags = parseSimpleFlags(args.slice(2), new Set([
3765
+ 'root', 'receipt-path', 'planned-scope', 'changed-file', 'selected-spec',
3766
+ 'executed-spec', 'skipped-spec', 'truncated-spec', 'json'
3767
+ ]));
3768
+ const root = path.resolve(expandHome(flags.root || process.cwd()));
3769
+ try {
3770
+ let result;
3771
+ if (action === 'create') {
3772
+ requireProductionFlags(flags, ['receipt_path', 'planned_scope', 'changed_file', 'selected_spec']);
3773
+ result = await createVerificationClosureReceipt({
3774
+ root,
3775
+ receipt_path: String(flags.receipt_path),
3776
+ planned_scope: String(flags.planned_scope),
3777
+ changed_files: arrayFlag(flags.changed_file),
3778
+ selected_commands: await readProductionSpecs(flags.selected_spec, 'selected command spec'),
3779
+ executed_commands: await readOptionalSpecs(flags.executed_spec, 'executed command spec'),
3780
+ skipped: await readOptionalSpecs(flags.skipped_spec, 'skipped command spec'),
3781
+ truncated: await readOptionalSpecs(flags.truncated_spec, 'truncated output spec')
3782
+ });
3783
+ }
3784
+ else if (action === 'verify') {
3785
+ requireProductionFlags(flags, ['receipt_path']);
3786
+ result = await verifyVerificationClosureReceiptFile({ root, receipt_path: String(flags.receipt_path) });
3787
+ }
3788
+ else {
3789
+ throw new Error(`unknown verification closure command: ${action}`);
3790
+ }
3791
+ printJsonOrHuman(result, Boolean(flags.json), 'Verification closure');
3792
+ if (result.truth_status === 'blocked')
3793
+ process.exitCode = 1;
3794
+ }
3795
+ catch (error) {
3796
+ const result = {
3797
+ schema: 'yam.verification-closure-error.v1',
3798
+ action,
3799
+ status: 'blocked',
3800
+ reason: summarizeCheckOutput(errorMessage(error)),
3801
+ next_action: 'repair changed-file scope, selected/executed command evidence, skipped/truncated reasons, or receipt integrity before retrying',
3802
+ truth_status: 'blocked'
3803
+ };
3804
+ printJsonOrHuman(result, Boolean(flags.json), 'Verification closure');
3805
+ process.exitCode = 1;
3806
+ }
3807
+ }
3808
+ function verificationUsage() {
3809
+ console.log(`yam verification closure
3810
+
3811
+ Explicit, immutable completion receipts. Create records operator-supplied results; it does not execute commands or replace advisory yam detect output.
3812
+
3813
+ Usage:
3814
+ 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]
3815
+ yam verification closure verify [--root dir] --receipt-path .yam/verification/closure.json [--json]
3816
+
3817
+ Selected spec: {"id":"typecheck","command":"npm run typecheck","scope":"affected","required":true}
3818
+ Executed spec: {"id":"typecheck","command":"npm run typecheck","exit_code":0,"evidence":"exit 0 observed"}
3819
+ Skipped/truncated spec: {"check_id":"browser","reason":"no UI surface changed"}
3820
+
3821
+ 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.
3822
+ `);
3823
+ }
3418
3824
  async function media(args = []) {
3419
3825
  const subcommand = args[0] || 'help';
3420
3826
  if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h')
3421
3827
  return mediaUsage();
3422
3828
  if (subcommand === 'proof')
3423
3829
  return mediaProof(args.slice(1));
3830
+ if (subcommand === 'provider')
3831
+ return mediaProvider(args.slice(1));
3424
3832
  console.error(`unknown media command: ${subcommand}`);
3425
3833
  return mediaUsage();
3426
3834
  }
@@ -3431,8 +3839,66 @@ Opt-in media generation proof helpers. Generated media can support visual direct
3431
3839
 
3432
3840
  Usage:
3433
3841
  yam media proof [--tool name] [--requested] [--attempted] [--output file] [--wait-loop] [--blocked-reason text] [--json]
3842
+ 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]
3843
+ yam media provider verify [--root dir] --receipt-path .yam/media/run.json [--json]
3844
+
3845
+ 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.
3434
3846
  `);
3435
3847
  }
3848
+ async function mediaProvider(args = []) {
3849
+ const action = String(args[0] || 'help');
3850
+ if (action === 'help' || action === '--help' || action === '-h')
3851
+ return mediaUsage();
3852
+ const flags = parseSimpleFlags(args.slice(1), new Set([
3853
+ 'root', 'receipt-path', 'demand-kind', 'demand-evidence', 'provider', 'model',
3854
+ 'provider-calls', 'provider-execution', 'dry-run', 'submit', 'asset-spec', 'json'
3855
+ ]));
3856
+ const root = path.resolve(expandHome(flags.root || process.cwd()));
3857
+ try {
3858
+ let result;
3859
+ if (action === 'create') {
3860
+ requireProductionFlags(flags, [
3861
+ 'receipt_path', 'demand_kind', 'demand_evidence', 'provider', 'model', 'provider_calls', 'asset_spec'
3862
+ ]);
3863
+ result = await createMediaProviderReceipt({
3864
+ root,
3865
+ receipt_path: String(flags.receipt_path),
3866
+ demand_trigger: {
3867
+ kind: String(flags.demand_kind),
3868
+ evidence: String(flags.demand_evidence)
3869
+ },
3870
+ provider: { name: String(flags.provider), model: String(flags.model) },
3871
+ provider_calls: Number(flags.provider_calls),
3872
+ provider_execution: Boolean(flags.provider_execution),
3873
+ dry_run: Boolean(flags.dry_run),
3874
+ submit: Boolean(flags.submit),
3875
+ assets: await readProductionSpecs(flags.asset_spec, 'media asset spec')
3876
+ });
3877
+ }
3878
+ else if (action === 'verify') {
3879
+ requireProductionFlags(flags, ['receipt_path']);
3880
+ result = await verifyMediaProviderReceipt({ root, receipt_path: String(flags.receipt_path) });
3881
+ }
3882
+ else {
3883
+ throw new Error(`unknown media provider command: ${action}`);
3884
+ }
3885
+ printJsonOrHuman(result, Boolean(flags.json), 'Media provider receipt');
3886
+ if (result.truth_status === 'blocked')
3887
+ process.exitCode = 1;
3888
+ }
3889
+ catch (error) {
3890
+ const result = {
3891
+ schema: 'yam.media-provider-receipt-error.v1',
3892
+ action,
3893
+ status: 'blocked',
3894
+ reason: summarizeCheckOutput(errorMessage(error)),
3895
+ next_action: 'repair the demand, exact execution fields, confined asset provenance, or receipt integrity before retrying',
3896
+ truth_status: 'blocked'
3897
+ };
3898
+ printJsonOrHuman(result, Boolean(flags.json), 'Media provider receipt');
3899
+ process.exitCode = 1;
3900
+ }
3901
+ }
3436
3902
  async function mediaProof(args = []) {
3437
3903
  if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
3438
3904
  return mediaUsage();
@@ -3704,12 +4170,37 @@ async function missionGate(args = []) {
3704
4170
  receipts
3705
4171
  });
3706
4172
  const blockers = [...gate.blockers, ...loadErrors.map((error) => `receipt_load_failed: ${error}`)];
4173
+ const gateResult = buildStrictGateResult({
4174
+ gate_id: 'mission-completion',
4175
+ boundary: 'mission',
4176
+ checks: [
4177
+ {
4178
+ id: 'receipt-inventory',
4179
+ status: gate.ready_to_claim_complete && loadErrors.length === 0 ? 'passed' : 'failed',
4180
+ note: blockers[0] || 'all expected Mission receipts are present and eligible',
4181
+ required: true
4182
+ },
4183
+ {
4184
+ id: 'read-only-boundary',
4185
+ status: gate.read_only_violations.length === 0 ? 'passed' : 'failed',
4186
+ note: gate.read_only_violations.length ? `violations: ${gate.read_only_violations.join(', ')}` : 'reviewer and doctor receipts preserve read-only access',
4187
+ required: true
4188
+ }
4189
+ ],
4190
+ blockers,
4191
+ evidence: gate.receipts.map((receipt) => `${receipt.thread_id}:${receipt.receipt_id}:${receipt.outcome}`),
4192
+ next_action: blockers[0] || gate.next_action
4193
+ });
4194
+ const gateContract = verifyStrictGateResult(gateResult);
4195
+ const strictReady = gate.ready_to_claim_complete && loadErrors.length === 0 && gateResult.status === 'passed' && gateContract.valid;
3707
4196
  const result = {
3708
4197
  ...gate,
3709
4198
  blockers,
3710
- ready_to_claim_complete: gate.ready_to_claim_complete && loadErrors.length === 0,
4199
+ ready_to_claim_complete: strictReady,
3711
4200
  next_action: blockers[0] || gate.next_action,
3712
- truth_status: blockers.length ? 'blocked' : gate.truth_status,
4201
+ truth_status: strictReady ? gate.truth_status : 'blocked',
4202
+ gate_result: gateResult,
4203
+ gate_contract: gateContract,
3713
4204
  persistence: flags.out ? 'written' : 'not-persisted',
3714
4205
  out: flags.out ? path.resolve(expandHome(flags.out)) : ''
3715
4206
  };
@@ -3802,15 +4293,72 @@ function benchmarkUsage() {
3802
4293
 
3803
4294
  Usage:
3804
4295
  yam benchmark report [--label text] [--baseline n] [--current n] [--unit ms] [--target lower|higher] [--next-action text] [--json]
4296
+ 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]
3805
4297
 
3806
4298
  Notes:
3807
4299
  Records a small optimization loop result. It does not run benchmarks by itself.
4300
+ Promotion mode verifies deterministic paired-sample arithmetic but treats supplied scores as operator assertions, not statistical significance.
3808
4301
  `);
3809
4302
  }
3810
4303
  async function benchmarkReport(args = []) {
3811
4304
  if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
3812
4305
  return benchmarkUsage();
3813
- const flags = parseSimpleFlags(args, new Set(['label', 'baseline', 'current', 'unit', 'target', 'next-action', 'rollback-hint', 'intent', 'json']));
4306
+ 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']));
4307
+ const promotionRequested = Boolean(flags.candidate_digest
4308
+ || flags.baseline_digest
4309
+ || flags.sample
4310
+ || flags.min_samples
4311
+ || flags.min_mean_delta
4312
+ || flags.min_win_rate
4313
+ || flags.evidence_source);
4314
+ if (promotionRequested) {
4315
+ try {
4316
+ const promotion = buildBoundedPromotionReport({
4317
+ label: String(flags.label || 'unnamed'),
4318
+ candidate_digest: String(flags.candidate_digest || ''),
4319
+ baseline_digest: String(flags.baseline_digest || ''),
4320
+ samples: arrayFlag(flags.sample).map((sample) => parsePromotionSample(sample)),
4321
+ min_samples: flags.min_samples === undefined ? undefined : Number(flags.min_samples),
4322
+ min_mean_delta: flags.min_mean_delta === undefined ? undefined : Number(flags.min_mean_delta),
4323
+ min_win_rate: flags.min_win_rate === undefined ? undefined : Number(flags.min_win_rate),
4324
+ target: String(flags.target || 'higher').toLowerCase(),
4325
+ unit: String(flags.unit || ''),
4326
+ evidence_source: String(flags.evidence_source || '')
4327
+ });
4328
+ const gateResult = buildStrictGateResult({
4329
+ gate_id: 'benchmark-promotion',
4330
+ boundary: 'generic',
4331
+ checks: [
4332
+ {
4333
+ id: 'promotion-decision',
4334
+ status: promotion.decision === 'keep' ? 'passed' : 'failed',
4335
+ note: promotion.next_action,
4336
+ required: true
4337
+ }
4338
+ ],
4339
+ blockers: promotion.blockers,
4340
+ evidence: [promotion.evidence_digest, promotion.evidence_source || 'operator-supplied paired measurements'],
4341
+ next_action: promotion.next_action
4342
+ });
4343
+ const result = { ...promotion, gate_result: gateResult };
4344
+ printJsonOrHuman(result, Boolean(flags.json), 'Bounded promotion report');
4345
+ if (promotion.decision !== 'keep' || gateResult.status !== 'passed')
4346
+ process.exitCode = 1;
4347
+ return;
4348
+ }
4349
+ catch (error) {
4350
+ const result = {
4351
+ schema: 'yam.bounded-promotion-error.v1',
4352
+ decision: 'blocked',
4353
+ blockers: [errorMessage(error)],
4354
+ next_action: 'repair the malformed paired-sample input before making a promotion decision',
4355
+ truth_status: 'blocked'
4356
+ };
4357
+ printJsonOrHuman(result, Boolean(flags.json), 'Bounded promotion report');
4358
+ process.exitCode = 1;
4359
+ return;
4360
+ }
4361
+ }
3814
4362
  const baseline = numberOrNull(flags.baseline);
3815
4363
  const current = numberOrNull(flags.current);
3816
4364
  const target = String(flags.target || 'lower').toLowerCase() === 'higher' ? 'higher' : 'lower';
@@ -3876,7 +4424,7 @@ function parseSimpleFlags(args = [], allowed = new Set()) {
3876
4424
  if (allowed.size && !allowed.has(key))
3877
4425
  continue;
3878
4426
  const normalizedKey = key.replace(/-/g, '_');
3879
- 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)) {
4427
+ 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)) {
3880
4428
  flags[normalizedKey] = true;
3881
4429
  continue;
3882
4430
  }
@@ -4412,7 +4960,12 @@ function formatStructuredEvidence(value) {
4412
4960
  return JSON.stringify(buildMissionSubagentReceipt(data));
4413
4961
  }
4414
4962
  if (data.schema === 'yam.mission-completion-gate.v1' || data.expected_thread_ids || data.ready_to_claim_complete !== undefined) {
4415
- return JSON.stringify(buildMissionCompletionGate(data));
4963
+ const completion = buildMissionCompletionGate(data);
4964
+ return JSON.stringify({
4965
+ ...completion,
4966
+ ...(data.gate_result && typeof data.gate_result === 'object' ? { gate_result: data.gate_result } : {}),
4967
+ ...(data.gate_contract && typeof data.gate_contract === 'object' ? { gate_contract: data.gate_contract } : {})
4968
+ });
4416
4969
  }
4417
4970
  if (data.schema === 'yam.mission-patch-envelope.v1' || data.agent_id || data.assigned_scope) {
4418
4971
  return JSON.stringify(buildMissionPatchEnvelope(data));
@@ -5386,6 +5939,10 @@ function showPath() {
5386
5939
  function showRequestedHelp(args) {
5387
5940
  if (!args.some((arg) => arg === '--help' || arg === '-h'))
5388
5941
  return false;
5942
+ if (args[0] === 'ueye' && args[1] === 'production') {
5943
+ ueyeProductionUsage();
5944
+ return true;
5945
+ }
5389
5946
  const commandUsage = {
5390
5947
  context: contextUsage,
5391
5948
  cleanup: cleanupUsage,
@@ -5395,6 +5952,7 @@ function showRequestedHelp(args) {
5395
5952
  loop: loopUsage,
5396
5953
  ueye: ueyeUsage,
5397
5954
  media: mediaUsage,
5955
+ verification: verificationUsage,
5398
5956
  runtime: runtimeUsage,
5399
5957
  mission: missionUsage,
5400
5958
  update: updateUsage,
@@ -5441,6 +5999,8 @@ async function main() {
5441
5999
  return ueye(process.argv.slice(3));
5442
6000
  if (command === 'media')
5443
6001
  return media(process.argv.slice(3));
6002
+ if (command === 'verification')
6003
+ return verification(process.argv.slice(3));
5444
6004
  if (command === 'runtime')
5445
6005
  return runtime(process.argv.slice(3));
5446
6006
  if (command === 'mission')