yam-flow 2.5.0 → 2.6.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.
@@ -0,0 +1,219 @@
1
+ import { createHash } from 'node:crypto';
2
+ const GATE_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{0,95}$/;
3
+ const BOUNDARIES = new Set(['release', 'update', 'mission', 'generic']);
4
+ const CHECK_STATUSES = new Set(['passed', 'failed', 'skipped']);
5
+ export function buildStrictGateResult(input = {}) {
6
+ const gateId = boundedText(input.gate_id || '', 96);
7
+ const boundary = BOUNDARIES.has(input.boundary) ? input.boundary : 'generic';
8
+ const contractErrors = [];
9
+ if (!GATE_ID_PATTERN.test(gateId))
10
+ contractErrors.push('gate_id_invalid');
11
+ if (!BOUNDARIES.has(input.boundary))
12
+ contractErrors.push('boundary_invalid');
13
+ if (Array.isArray(input.checks) && input.checks.length > 128)
14
+ contractErrors.push('check_limit_exceeded');
15
+ if (Array.isArray(input.blockers) && input.blockers.length > 64)
16
+ contractErrors.push('blocker_limit_exceeded');
17
+ if (Array.isArray(input.evidence) && input.evidence.length > 128)
18
+ contractErrors.push('evidence_limit_exceeded');
19
+ const checks = Array.isArray(input.checks) ? input.checks.slice(0, 128).map((check, index) => {
20
+ const id = boundedText(check?.id || '', 96);
21
+ const status = CHECK_STATUSES.has(check?.status) ? check?.status : 'failed';
22
+ if (!GATE_ID_PATTERN.test(id))
23
+ contractErrors.push(`check_id_invalid:${index}`);
24
+ if (!CHECK_STATUSES.has(check?.status))
25
+ contractErrors.push(`check_status_invalid:${id || index}`);
26
+ return {
27
+ id,
28
+ status,
29
+ note: boundedText(check?.note || '', 512),
30
+ required: check?.required !== false
31
+ };
32
+ }) : [];
33
+ if (!checks.length)
34
+ contractErrors.push('checks_missing');
35
+ const checkIds = checks.map((check) => check.id).filter(Boolean);
36
+ if (new Set(checkIds).size !== checkIds.length)
37
+ contractErrors.push('duplicate_check_id');
38
+ const blockers = uniqueBounded(input.blockers, 64, 512);
39
+ const evidence = uniqueBounded(input.evidence, 128, 512);
40
+ if (!evidence.length)
41
+ contractErrors.push('evidence_missing');
42
+ for (const check of checks) {
43
+ if (check.required && check.status !== 'passed')
44
+ blockers.push(`${check.id || 'unnamed_check'}: ${check.note || 'required gate check did not pass'}`);
45
+ }
46
+ for (const error of contractErrors)
47
+ blockers.push(`gate_contract_invalid: ${error}`);
48
+ const finalBlockers = [...new Set(blockers)];
49
+ const contractValid = contractErrors.length === 0;
50
+ const passed = contractValid && finalBlockers.length === 0;
51
+ const generatedAt = new Date().toISOString();
52
+ const nextAction = boundedText(input.next_action || finalBlockers[0] || 'gate passed; retain the evidence digest with the dependent completion claim', 512);
53
+ const uniqueContractErrors = [...new Set(contractErrors)];
54
+ const truthStatus = passed ? 'verified' : 'blocked';
55
+ const canonical = {
56
+ schema: 'yam.gate-result.v1',
57
+ generated_at: generatedAt,
58
+ gate_id: gateId,
59
+ boundary,
60
+ status: passed ? 'passed' : 'failed',
61
+ checks,
62
+ blockers: finalBlockers,
63
+ evidence,
64
+ next_action: nextAction,
65
+ contract_valid: contractValid,
66
+ contract_errors: uniqueContractErrors,
67
+ truth_status: truthStatus
68
+ };
69
+ return {
70
+ schema: 'yam.gate-result.v1',
71
+ generated_at: generatedAt,
72
+ gate_id: gateId,
73
+ boundary,
74
+ status: passed ? 'passed' : 'failed',
75
+ checks,
76
+ blockers: finalBlockers,
77
+ evidence,
78
+ next_action: nextAction,
79
+ contract_valid: contractValid,
80
+ contract_errors: uniqueContractErrors,
81
+ digest: `sha256:${createHash('sha256').update(stableJson(canonical)).digest('hex')}`,
82
+ truth_status: truthStatus
83
+ };
84
+ }
85
+ export function verifyStrictGateResult(value) {
86
+ const errors = [];
87
+ const record = value && typeof value === 'object' ? value : {};
88
+ if (record.schema !== 'yam.gate-result.v1')
89
+ errors.push('schema_invalid');
90
+ if (typeof record.generated_at !== 'string' || !isIsoDate(record.generated_at))
91
+ errors.push('generated_at_invalid');
92
+ if (!GATE_ID_PATTERN.test(String(record.gate_id || '')))
93
+ errors.push('gate_id_invalid');
94
+ if (!BOUNDARIES.has(record.boundary))
95
+ errors.push('boundary_invalid');
96
+ if (!['passed', 'failed'].includes(String(record.status || '')))
97
+ errors.push('status_invalid');
98
+ const checks = Array.isArray(record.checks) ? record.checks : [];
99
+ const blockers = Array.isArray(record.blockers) ? record.blockers : [];
100
+ const evidence = Array.isArray(record.evidence) ? record.evidence : [];
101
+ const contractErrors = Array.isArray(record.contract_errors) ? record.contract_errors : [];
102
+ if (!checks.length)
103
+ errors.push('checks_missing');
104
+ if (checks.length > 128)
105
+ errors.push('check_limit_exceeded');
106
+ if (blockers.length > 64)
107
+ errors.push('blocker_limit_exceeded');
108
+ if (evidence.length > 128)
109
+ errors.push('evidence_limit_exceeded');
110
+ if (!Array.isArray(record.blockers) || blockers.some((item) => typeof item !== 'string'))
111
+ errors.push('blockers_invalid');
112
+ if (!Array.isArray(record.evidence) || !evidence.length || evidence.some((item) => typeof item !== 'string' || !item.trim()))
113
+ errors.push('evidence_missing');
114
+ if (typeof record.contract_valid !== 'boolean')
115
+ errors.push('contract_valid_missing');
116
+ if (!Array.isArray(record.contract_errors) || contractErrors.some((item) => typeof item !== 'string'))
117
+ errors.push('contract_errors_invalid');
118
+ if (record.contract_valid === true && contractErrors.length)
119
+ errors.push('contract_valid_with_errors');
120
+ if (record.contract_valid === false && !contractErrors.length)
121
+ errors.push('contract_invalid_without_errors');
122
+ if (record.status === 'passed' && record.contract_valid !== true)
123
+ errors.push('passed_with_invalid_contract');
124
+ if (typeof record.next_action !== 'string' || !record.next_action.trim())
125
+ errors.push('next_action_missing');
126
+ if (!['verified', 'blocked'].includes(String(record.truth_status || '')))
127
+ errors.push('truth_status_invalid');
128
+ if (record.status === 'passed' && record.truth_status !== 'verified')
129
+ errors.push('passed_truth_mismatch');
130
+ if (record.status === 'failed' && record.truth_status !== 'blocked')
131
+ errors.push('failed_truth_mismatch');
132
+ for (const [index, check] of checks.entries()) {
133
+ if (!check || typeof check !== 'object') {
134
+ errors.push(`check_invalid:${index}`);
135
+ continue;
136
+ }
137
+ const row = check;
138
+ if (!GATE_ID_PATTERN.test(String(row.id || '')))
139
+ errors.push(`check_id_invalid:${index}`);
140
+ if (!CHECK_STATUSES.has(row.status))
141
+ errors.push(`check_status_invalid:${index}`);
142
+ if (typeof row.note !== 'string')
143
+ errors.push(`check_note_invalid:${index}`);
144
+ if (typeof row.required !== 'boolean')
145
+ errors.push(`check_required_invalid:${index}`);
146
+ }
147
+ const checkIds = checks
148
+ .filter((check) => check && typeof check === 'object')
149
+ .map((check) => String(check.id || ''))
150
+ .filter(Boolean);
151
+ if (new Set(checkIds).size !== checkIds.length)
152
+ errors.push('duplicate_check_id');
153
+ if (record.status === 'passed' && Array.isArray(record.blockers) && record.blockers.length > 0)
154
+ errors.push('passed_with_blockers');
155
+ if (record.status === 'passed' && checks.length) {
156
+ const failedRequired = checks.some((check) => {
157
+ if (!check || typeof check !== 'object')
158
+ return true;
159
+ const row = check;
160
+ return row.required !== false && row.status !== 'passed';
161
+ });
162
+ if (failedRequired)
163
+ errors.push('passed_with_failed_required_check');
164
+ }
165
+ if (record.status === 'failed') {
166
+ const hasFailedRequired = checks.some((check) => {
167
+ if (!check || typeof check !== 'object')
168
+ return false;
169
+ const row = check;
170
+ return row.required !== false && row.status !== 'passed';
171
+ });
172
+ if (record.contract_valid === true && blockers.length === 0 && !hasFailedRequired)
173
+ errors.push('failed_without_blocker_or_failed_check');
174
+ }
175
+ const canonical = {
176
+ schema: record.schema,
177
+ generated_at: record.generated_at,
178
+ gate_id: String(record.gate_id || ''),
179
+ boundary: record.boundary,
180
+ status: record.status,
181
+ checks,
182
+ blockers,
183
+ evidence,
184
+ next_action: record.next_action,
185
+ contract_valid: record.contract_valid,
186
+ contract_errors: contractErrors,
187
+ truth_status: record.truth_status
188
+ };
189
+ const expectedDigest = `sha256:${createHash('sha256').update(stableJson(canonical)).digest('hex')}`;
190
+ if (typeof record.digest !== 'string' || record.digest !== expectedDigest)
191
+ errors.push('digest_invalid');
192
+ return {
193
+ schema: 'yam.gate-result-verification.v1',
194
+ valid: errors.length === 0,
195
+ errors: [...new Set(errors)],
196
+ truth_status: errors.length ? 'blocked' : 'verified',
197
+ next_action: errors.length ? `reject the gate result and repair: ${errors[0]}` : 'gate result contract is structurally valid'
198
+ };
199
+ }
200
+ function isIsoDate(value) {
201
+ return !Number.isNaN(Date.parse(value)) && new Date(value).toISOString() === value;
202
+ }
203
+ function uniqueBounded(value, maxItems, maxChars) {
204
+ if (!Array.isArray(value))
205
+ return [];
206
+ return [...new Set(value.map((item) => boundedText(item, maxChars)).filter(Boolean))].slice(0, maxItems);
207
+ }
208
+ function boundedText(value, max) {
209
+ return String(value || '').replace(/[\r\n\u0000-\u001f\u007f]+/g, ' ').trim().slice(0, max);
210
+ }
211
+ function stableJson(value) {
212
+ if (Array.isArray(value))
213
+ return `[${value.map((item) => stableJson(item)).join(',')}]`;
214
+ if (value && typeof value === 'object') {
215
+ const record = value;
216
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`).join(',')}}`;
217
+ }
218
+ return JSON.stringify(value);
219
+ }
@@ -1,6 +1,7 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
2
  import fsp from 'node:fs/promises';
3
3
  import path from 'node:path';
4
+ import { compareStableText } from './stable-order.js';
4
5
  export const INSTALL_RECEIPT_NAME = '.yam-flow-install-receipt.json';
5
6
  export const INSTALL_LOCK_NAME = '.yam-flow-install.lock';
6
7
  export const INSTALL_RECEIPT_SCHEMA = 'yam.install-receipt.v1';
@@ -70,7 +71,7 @@ async function collectTreeManifest(root, prefix = '') {
70
71
  const files = [];
71
72
  async function walk(current, relative) {
72
73
  const entries = await fsp.readdir(current, { withFileTypes: true });
73
- entries.sort((left, right) => left.name.localeCompare(right.name));
74
+ entries.sort((left, right) => compareStableText(left.name, right.name));
74
75
  for (const entry of entries) {
75
76
  const absolute = path.join(current, entry.name);
76
77
  const nextRelative = relative ? path.join(relative, entry.name) : entry.name;
@@ -86,7 +87,7 @@ async function collectTreeManifest(root, prefix = '') {
86
87
  }
87
88
  }
88
89
  await walk(root, '');
89
- return files.sort((left, right) => left.path.localeCompare(right.path));
90
+ return files.sort((left, right) => compareStableText(left.path, right.path));
90
91
  }
91
92
  async function expectedInstallManifest(sourceRoot, skills) {
92
93
  const referencesRoot = path.join(sourceRoot, 'references');
@@ -112,7 +113,7 @@ async function expectedInstallManifest(sourceRoot, skills) {
112
113
  });
113
114
  }
114
115
  }
115
- return files.sort((left, right) => left.path.localeCompare(right.path));
116
+ return files.sort((left, right) => compareStableText(left.path, right.path));
116
117
  }
117
118
  async function copyTree(source, target) {
118
119
  const stat = await fsp.lstat(source);
@@ -137,8 +138,11 @@ async function copyTree(source, target) {
137
138
  }
138
139
  }
139
140
  function manifestDigest(files) {
141
+ return digestManifestFiles([...files].sort((left, right) => compareStableText(left.path, right.path)));
142
+ }
143
+ function digestManifestFiles(files) {
140
144
  const digest = createHash('sha256');
141
- for (const file of [...files].sort((left, right) => left.path.localeCompare(right.path))) {
145
+ for (const file of files) {
142
146
  digest.update(file.path);
143
147
  digest.update('\0');
144
148
  digest.update(String(file.bytes));
@@ -148,6 +152,20 @@ function manifestDigest(files) {
148
152
  }
149
153
  return digest.digest('hex');
150
154
  }
155
+ function allowsLegacyManifestOrder(version) {
156
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(String(version || ''));
157
+ if (!match)
158
+ return false;
159
+ const major = Number(match[1]);
160
+ const minor = Number(match[2]);
161
+ const patch = Number(match[3]);
162
+ return major < 2 || (major === 2 && (minor < 5 || (minor === 5 && patch === 0)));
163
+ }
164
+ function receiptManifestDigestMatches(files, digest, version) {
165
+ if (digest === manifestDigest(files))
166
+ return true;
167
+ return allowsLegacyManifestOrder(version) && digest === digestManifestFiles(files);
168
+ }
151
169
  function manifestIssues(expected, actual, label) {
152
170
  const issues = [];
153
171
  const expectedByPath = new Map(expected.map((file) => [file.path, file]));
@@ -236,7 +254,7 @@ function ownershipReceiptIssues(receipt, packageName, destination) {
236
254
  if (value.integrity?.file_count !== files.length) {
237
255
  issues.push('install receipt file count does not match its manifest');
238
256
  }
239
- if (value.integrity?.source_digest !== manifestDigest(files)) {
257
+ if (!receiptManifestDigestMatches(files, value.integrity?.source_digest, value.package?.version)) {
240
258
  issues.push('install receipt source digest does not match its manifest');
241
259
  }
242
260
  if (Array.isArray(value.skills)) {
@@ -0,0 +1 @@
1
+ export declare function compareStableText(left: string, right: string): number;
@@ -0,0 +1,5 @@
1
+ export function compareStableText(left, right) {
2
+ if (left === right)
3
+ return 0;
4
+ return left < right ? -1 : 1;
5
+ }
@@ -1,3 +1,4 @@
1
+ import { verifyStrictGateResult } from './gate-result.js';
1
2
  export const TRUTH_STATUSES = Object.freeze([
2
3
  'verified',
3
4
  'proven',
@@ -1023,7 +1024,22 @@ function missionCompletionCap(value = '') {
1023
1024
  if (!parsed || typeof parsed !== 'object')
1024
1025
  return weakestTruth(weakest, 'blocked');
1025
1026
  const gate = buildMissionCompletionGate(parsed);
1026
- return weakestTruth(weakest, gate.truth_status);
1027
+ const record = parsed;
1028
+ const strictGate = record.gate_result && typeof record.gate_result === 'object'
1029
+ ? record.gate_result
1030
+ : null;
1031
+ const reportedContract = record.gate_contract && typeof record.gate_contract === 'object'
1032
+ ? record.gate_contract
1033
+ : null;
1034
+ const strictVerification = verifyStrictGateResult(strictGate);
1035
+ const strictReady = Boolean(strictGate
1036
+ && strictGate.boundary === 'mission'
1037
+ && strictGate.gate_id === 'mission-completion'
1038
+ && strictGate.status === 'passed'
1039
+ && strictVerification.valid
1040
+ && reportedContract?.valid === true
1041
+ && reportedContract?.truth_status === 'verified');
1042
+ return weakestTruth(weakest, strictReady ? gate.truth_status : 'blocked');
1027
1043
  }, 'proven');
1028
1044
  }
1029
1045
  function strongestSupportedTruth(rows = []) {
@@ -2,6 +2,7 @@ import { createHash } from 'node:crypto';
2
2
  import { constants as fsConstants } from 'node:fs';
3
3
  import fsp from 'node:fs/promises';
4
4
  import path from 'node:path';
5
+ import { compareStableText } from './stable-order.js';
5
6
  export async function inspectImageFile(file) {
6
7
  const absolute = path.resolve(file);
7
8
  const buffer = await fsp.readFile(absolute);
@@ -46,7 +47,7 @@ export async function upsertUeyeAsset(input) {
46
47
  manifest.assets[existingIndex] = entry;
47
48
  else
48
49
  manifest.assets.push(entry);
49
- manifest.assets.sort((left, right) => left.id.localeCompare(right.id));
50
+ manifest.assets.sort((left, right) => compareStableText(left.id, right.id));
50
51
  manifest.updated_at = new Date().toISOString();
51
52
  await writeJsonAtomic(manifestPath, manifest);
52
53
  const warnings = assetWarnings(entry);
@@ -152,7 +153,7 @@ export async function archiveUeyeRevision(input) {
152
153
  else {
153
154
  history.revisions.push(entry);
154
155
  }
155
- history.revisions.sort((left, right) => left.round - right.round || left.artifact_id.localeCompare(right.artifact_id));
156
+ history.revisions.sort((left, right) => left.round - right.round || compareStableText(left.artifact_id, right.artifact_id));
156
157
  history.updated_at = new Date().toISOString();
157
158
  await writeJsonAtomic(manifestPath, history);
158
159
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yam-flow",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "description": "Progressive proof-first agent flow: start fast, deepen deliberately, stay honest by design.",
5
5
  "type": "module",
6
6
  "author": "0kim0bos",
@@ -52,7 +52,7 @@
52
52
  "doctor": "node ./dist/bin/yam.js doctor",
53
53
  "list": "node ./dist/bin/yam.js list",
54
54
  "examples": "node ./dist/bin/yam.js examples",
55
- "verify:self": "npm run build && node --check ./dist/bin/yam.js && node --check ./dist/lib/trust-kernel.js && node --check ./dist/lib/skill-installation.js && node --check ./dist/lib/external-updates.js && npm run hook-latency-smoke && node ./scripts/external-updates-smoke.mjs && node ./scripts/install-transaction-smoke.mjs && node ./dist/bin/yam.js verify && node ./dist/bin/yam.js doctor",
55
+ "verify:self": "npm run build && node --check ./dist/bin/yam.js && node --check ./dist/lib/trust-kernel.js && node --check ./dist/lib/skill-installation.js && node --check ./dist/lib/external-updates.js && node --check ./dist/lib/gate-result.js && node --check ./dist/lib/benchmark-promotion.js && node --check ./dist/lib/design-production.js && npm run hook-latency-smoke && node ./scripts/external-updates-smoke.mjs && node ./scripts/install-transaction-smoke.mjs && node ./scripts/trust-adoptions-smoke.mjs && node ./scripts/design-production-smoke.mjs && npm run package-secrets:check && npm run package-secrets-smoke && node ./dist/bin/yam.js verify && node ./dist/bin/yam.js doctor",
56
56
  "prepare": "npm run build",
57
57
  "prepack": "npm run verify:self",
58
58
  "prepublishOnly": "npm run verify:self",
@@ -61,10 +61,14 @@
61
61
  "forbidden-names:check": "node ./scripts/check-forbidden-names.mjs",
62
62
  "study-note:check": "node ./dist/bin/yam.js study-note check --json",
63
63
  "package-boundary:check": "node ./scripts/check-package-boundary.mjs",
64
+ "package-secrets:check": "node ./scripts/check-package-secrets.mjs",
65
+ "package-secrets-smoke": "node ./scripts/package-secrets-smoke.mjs",
64
66
  "dist:freshness": "node ./scripts/check-dist-freshness.mjs",
65
67
  "registry:check": "node ./scripts/check-registry-version.mjs",
66
68
  "cli-smoke": "node ./scripts/cli-smoke.mjs",
67
69
  "external-updates-smoke": "npm run build && node ./scripts/external-updates-smoke.mjs",
70
+ "trust-adoptions-smoke": "npm run build && node ./scripts/trust-adoptions-smoke.mjs",
71
+ "design-production-smoke": "npm run build && node ./scripts/design-production-smoke.mjs",
68
72
  "hook-latency-smoke": "node ./scripts/hook-latency-smoke.mjs",
69
73
  "install-transaction-smoke": "npm run build && node ./scripts/install-transaction-smoke.mjs",
70
74
  "release:check": "npm run typecheck && npm run forbidden-names:check && npm run verify:self && npm run dist:freshness && npm run package-boundary:check && npm run registry:check && npm run cli-smoke",
@@ -70,6 +70,7 @@ Use these when the task depends on visual truth, reference matching, or design q
70
70
  7. Deep Visual Review, when Ueye needs Deep-grade verification.
71
71
  8. Asset Manifest, when local reference media affects the design.
72
72
  9. Revision History, when an iterative visual artifact is about to be replaced.
73
+ 10. Demand-Gated Design Production receipt, only for repeated plan review or large coordinated multi-asset work.
73
74
 
74
75
  Default bound:
75
76
 
@@ -89,6 +90,19 @@ Record source URL/page when known, a license or usage note, operator-provided st
89
90
 
90
91
  Use `yam ueye revision archive` before replacing an artifact during an iterative design pass. Archives are written to numbered round folders with exclusive-copy behavior, so a different payload cannot silently overwrite an existing round. `yam ueye revision verify` recomputes hashes before the history supports a done claim.
91
92
 
93
+ ### Demand-Gated Design Production
94
+
95
+ Do not start this phase for a one-off review or single-asset edit. When repeated exact-element plan feedback or a large coordinated asset set creates real review cost, use `yam ueye production` to bind:
96
+
97
+ - operator-asserted demand evidence;
98
+ - a local static Canvas on fixed session-specific evidence paths;
99
+ - exact anchored comments and an explicit approve/request-changes verdict;
100
+ - no more than two revision rounds, each linked to a Canvas comment and at least one new Ueye archive;
101
+ - a final gallery whose revision references come from the final round; and
102
+ - one immutable phase receipt.
103
+
104
+ Path/hash/reference integrity can complete the phase evidence, but overall truth remains partial. The gallery and phase receipt do not verify visual quality, licensing, or implementation correctness.
105
+
92
106
  ### Visual Evidence Inventory
93
107
 
94
108
  Record the real screens and image sources behind the review.
@@ -40,6 +40,7 @@ Do not use for:
40
40
  - For follow-up visual reviews, keep continuity explicit: previous report/source, current report/source, comparison delta, resolved findings, new findings, and next action.
41
41
  - Record surface context when it matters: provider context, provider badge, execution surface, app surface, browser surface, control mode, preserved URL/state, and evidence id.
42
42
  - Generated media can guide direction, but it cannot verify the implemented screen without implementation screenshot evidence.
43
+ - Keep the Demand-Gated Design Production Phase dormant unless repeated plan annotation or a large coordinated multi-asset set creates real review cost; ordinary and single-asset work stays on the normal Ueye path.
43
44
  - Prefer the in-app browser for interactive visual checks. Do not switch to a profile-dependent browser unless the user explicitly asks or the task requires existing cookies, sessions, extensions, or profile state.
44
45
  - Ueye owns the design completion gate. Do not rely on a separate code-risk route to catch sloppy visual completion.
45
46
  - Draft or needs-polish work may stay fast; a `done` claim requires recorded design gate evidence.
@@ -63,6 +64,8 @@ Do not use for:
63
64
  - When local reference assets materially shape the design, record them with `yam ueye asset add` before editing.
64
65
  - Use `do_not_replace` for protected identity assets and enable `allowed_for_edit` only when usage rights and task scope support editing.
65
66
  - Before overwriting an iterative visual artifact, preserve the current file with `yam ueye revision archive`.
67
+ - Only when real repeated-review or multi-asset demand exists, use `yam ueye production`: record an exact anchored Canvas review, explicitly close it, run at most two source-comment-linked rounds with a fresh Ueye archive in each round, bind the gallery to the final round, then finalize one phase receipt.
68
+ - Treat phase demand as operator-asserted and keep gallery visual, license, and implementation correctness `not_verified` until their separate checks pass.
66
69
  7. Check default, loading, error, empty, disabled, hover/focus, and mobile states when relevant.
67
70
  8. Run browser/screenshot verification when feasible.
68
71
  - Prefer the in-app browser for local pages, localhost, file URLs, and ordinary visual QA.
@@ -137,6 +140,12 @@ Use these artifacts for reference-led implementation or serious visual review. K
137
140
  - archive the current file under a numbered round before editing it.
138
141
  - never overwrite a different archived payload for the same artifact and round.
139
142
  - verify the revision manifest before using it in a visual `done` claim.
143
+ 12. Demand-Gated Design Production receipt, only when repeated plan review or large multi-asset coordination triggers it:
144
+ - keep Canvas session/render files on their fixed local `.yam/ueye/design-production/<session>/` paths.
145
+ - record exact line/unique-text/bounded-region findings and an explicit verdict.
146
+ - stop after acceptance, an integrity blocker, or two revisions; never loop a third time automatically.
147
+ - require each round to link a Canvas comment and a newly archived Ueye revision, and bind gallery artifacts to the final round.
148
+ - keep demand evidence operator-asserted and the overall phase truth `partial`; the phase receipt is not a visual/license/implementation correctness gate.
140
149
 
141
150
  ## Visual Truth Caps
142
151
 
@@ -152,6 +161,7 @@ Use these artifacts for reference-led implementation or serious visual review. K
152
161
  - Mock or invented screenshots: cap the result at `partial` and mark the source.
153
162
  - Browser unavailable but source files reviewed: report implementation verification separately from visual verification.
154
163
  - Generated callout images may improve review quality, but missing generated images must not block ordinary Ueye work.
164
+ - Missing demand or a one-off/single-asset task must not auto-start the design production phase.
155
165
 
156
166
  ## Design Checks
157
167
 
@@ -188,3 +198,4 @@ Report:
188
198
  - Truth status and visual verification cap.
189
199
  - Handoff when useful: fix-first visual issues, remaining tasks, recommended direction, why that next step matters, blocker kind, safe retry, evidence level/stamp, and owner scope when another route or person may continue.
190
200
  - Asset manifest and revision history status when reference media or iterative artifact replacement materially affected the work.
201
+ - Demand trigger, bounded revision stop, final gallery state, and phase receipt when the opt-in design production phase was actually used.