yam-flow 0.1.0 → 0.1.2
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/COMMANDS.md +18 -0
- package/README.md +20 -0
- package/ROADMAP.md +110 -0
- package/dist/bin/yam.js +422 -4
- package/dist/lib/trust-kernel.d.ts +38 -0
- package/dist/lib/trust-kernel.js +69 -0
- package/package.json +2 -1
- package/references/mission.md +24 -0
- package/references/tool-trust-layer.md +13 -0
- package/references/ueye-proof.md +39 -0
- package/skills/mission/SKILL.md +10 -6
- package/skills/ueye/SKILL.md +12 -3
- package/src/bin/yam.ts +408 -3
- package/src/lib/trust-kernel.ts +111 -0
- package/templates/mission-plan.md +13 -0
- package/templates/ueye-comparison.md +15 -0
- package/templates/ueye-review.md +15 -0
- package/yam.manifest.json +2 -2
package/dist/bin/yam.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
2
4
|
import fs from 'node:fs';
|
|
3
5
|
import fsp from 'node:fs/promises';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
4
7
|
import os from 'node:os';
|
|
5
8
|
import path from 'node:path';
|
|
6
9
|
import process from 'node:process';
|
|
7
10
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
import { TRUTH_STATUSES, buildYamCompletionProof, detectDbSafetyText as detectTrustDbSafetyText, isTruthStatus } from '../lib/trust-kernel.js';
|
|
11
|
+
import { TRUTH_STATUSES, buildMissionPatchEnvelope, buildRollbackHint, buildUeyeVisualProvenance, buildYamCompletionProof, detectDbSafetyText as detectTrustDbSafetyText, isTruthStatus } from '../lib/trust-kernel.js';
|
|
9
12
|
function errorMessage(error) {
|
|
10
13
|
return error instanceof Error ? error.message : String(error);
|
|
11
14
|
}
|
|
@@ -117,6 +120,9 @@ Usage:
|
|
|
117
120
|
yam tools doctor [dir]
|
|
118
121
|
yam proof [dir|--from file] [--route route] [--truth status] [--command text] [--evidence text]
|
|
119
122
|
yam proof write [dir] [--format json|md] [--out file] [--route route] [--truth status] [--command text]
|
|
123
|
+
yam ueye capture --url URL --out screenshot.png [--viewport 1440x900] [--full-page] [--json]
|
|
124
|
+
yam ueye compare --reference ref.png --actual screenshot.png [--json]
|
|
125
|
+
yam release report [--json]
|
|
120
126
|
yam safety [text...]
|
|
121
127
|
yam memory <init|add|list|summary|resolve> [dir] [options]
|
|
122
128
|
yam hook <status|enable|disable|run> [lite] [--global|--project dir]
|
|
@@ -927,6 +933,86 @@ async function safety(args = []) {
|
|
|
927
933
|
const result = detectDbSafetyText(text);
|
|
928
934
|
printSafetyResult(result);
|
|
929
935
|
}
|
|
936
|
+
async function release(args = []) {
|
|
937
|
+
const subcommand = args[0] || 'help';
|
|
938
|
+
if (subcommand !== 'report') {
|
|
939
|
+
console.error('usage: yam release report [--json]');
|
|
940
|
+
process.exitCode = 1;
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
const asJson = args.includes('--json');
|
|
944
|
+
const report = runReleaseReport();
|
|
945
|
+
if (asJson) {
|
|
946
|
+
console.log(JSON.stringify(report, null, 2));
|
|
947
|
+
if (!report.ok)
|
|
948
|
+
process.exitCode = 1;
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
console.log('yam release report');
|
|
952
|
+
console.log(`- Package: ${report.packageName}@${report.version}`);
|
|
953
|
+
console.log(`- Truth status: ${report.truth_status}`);
|
|
954
|
+
for (const check of report.checks) {
|
|
955
|
+
console.log(`- ${check.id}: ${check.status} (${check.duration_ms}ms)`);
|
|
956
|
+
if (check.note)
|
|
957
|
+
console.log(` ${check.note}`);
|
|
958
|
+
}
|
|
959
|
+
if (report.failed.length) {
|
|
960
|
+
console.log('- Failed:');
|
|
961
|
+
for (const id of report.failed)
|
|
962
|
+
console.log(` - ${id}`);
|
|
963
|
+
}
|
|
964
|
+
if (!report.ok)
|
|
965
|
+
process.exitCode = 1;
|
|
966
|
+
}
|
|
967
|
+
function runReleaseReport() {
|
|
968
|
+
const startedAt = new Date().toISOString();
|
|
969
|
+
const checks = [
|
|
970
|
+
['typecheck', ['npm', ['run', 'typecheck']]],
|
|
971
|
+
['forbidden_names', ['npm', ['run', 'forbidden-names:check']]],
|
|
972
|
+
['package_boundary', ['npm', ['run', 'package-boundary:check']]],
|
|
973
|
+
['registry_status', ['npm', ['run', 'registry:check']]],
|
|
974
|
+
['cli_smoke', ['npm', ['run', 'cli-smoke']]],
|
|
975
|
+
['dist_freshness', ['npm', ['run', 'dist:freshness']]]
|
|
976
|
+
].map(([id, command]) => runReleaseCheck(id, command));
|
|
977
|
+
const failed = checks.filter((check) => check.status !== 'passed').map((check) => check.id);
|
|
978
|
+
return {
|
|
979
|
+
schema: 'yam.release-report.v1',
|
|
980
|
+
generated_at: startedAt,
|
|
981
|
+
packageName: PACKAGE_JSON.name,
|
|
982
|
+
version: VERSION,
|
|
983
|
+
ok: failed.length === 0,
|
|
984
|
+
truth_status: failed.length === 0 ? 'verified' : 'blocked',
|
|
985
|
+
checks,
|
|
986
|
+
failed
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
function runReleaseCheck(id, commandSpec) {
|
|
990
|
+
const [command, args] = commandSpec;
|
|
991
|
+
const start = Date.now();
|
|
992
|
+
const result = spawnSync(command, args, {
|
|
993
|
+
cwd: ROOT,
|
|
994
|
+
encoding: 'utf8',
|
|
995
|
+
env: { ...process.env, npm_config_cache: path.join(os.tmpdir(), 'yam-npm-cache') },
|
|
996
|
+
timeout: 120000
|
|
997
|
+
});
|
|
998
|
+
const output = [result.stdout, result.stderr].filter(Boolean).join('\n').trim();
|
|
999
|
+
const timedOut = result.error?.message?.includes('ETIMEDOUT') || result.signal === 'SIGTERM';
|
|
1000
|
+
return {
|
|
1001
|
+
id,
|
|
1002
|
+
command: [command, ...args].join(' '),
|
|
1003
|
+
status: result.status === 0 ? 'passed' : timedOut ? 'blocked' : 'failed',
|
|
1004
|
+
exit_code: typeof result.status === 'number' ? result.status : null,
|
|
1005
|
+
duration_ms: Date.now() - start,
|
|
1006
|
+
note: summarizeCheckOutput(output || result.error?.message || '')
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
function summarizeCheckOutput(output = '') {
|
|
1010
|
+
const lines = String(output || '').split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
1011
|
+
if (!lines.length)
|
|
1012
|
+
return '';
|
|
1013
|
+
const highSignal = lines.filter((line) => !line.startsWith('>')).slice(-4);
|
|
1014
|
+
return highSignal.join(' | ').slice(0, 600);
|
|
1015
|
+
}
|
|
930
1016
|
async function readStdinTextIfAvailable() {
|
|
931
1017
|
if (process.stdin.isTTY)
|
|
932
1018
|
return '';
|
|
@@ -935,6 +1021,271 @@ async function readStdinTextIfAvailable() {
|
|
|
935
1021
|
chunks.push(Buffer.from(chunk));
|
|
936
1022
|
return Buffer.concat(chunks).toString('utf8').trim();
|
|
937
1023
|
}
|
|
1024
|
+
async function ueye(args = []) {
|
|
1025
|
+
const subcommand = args[0] || 'help';
|
|
1026
|
+
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h')
|
|
1027
|
+
return ueyeUsage();
|
|
1028
|
+
if (subcommand === 'capture')
|
|
1029
|
+
return ueyeCapture(args.slice(1));
|
|
1030
|
+
if (subcommand === 'compare')
|
|
1031
|
+
return ueyeCompare(args.slice(1));
|
|
1032
|
+
console.error(`unknown ueye command: ${subcommand}`);
|
|
1033
|
+
return ueyeUsage();
|
|
1034
|
+
}
|
|
1035
|
+
function ueyeUsage() {
|
|
1036
|
+
console.log(`yam ueye
|
|
1037
|
+
|
|
1038
|
+
Opt-in visual evidence helpers. Ueye stays one skill: fast by default, capture/compare only when verified visual claims need real evidence.
|
|
1039
|
+
|
|
1040
|
+
Usage:
|
|
1041
|
+
yam ueye capture --url URL --out screenshot.png [--viewport 1440x900] [--full-page] [--json]
|
|
1042
|
+
yam ueye compare --reference ref.png --actual screenshot.png [--json]
|
|
1043
|
+
|
|
1044
|
+
Notes:
|
|
1045
|
+
capture uses a locally available Playwright install when present. It does not download browsers or install dependencies.
|
|
1046
|
+
compare uses local files only and reports sha256, dimensions, comparison_result, and proof-ready visual provenance.
|
|
1047
|
+
`);
|
|
1048
|
+
}
|
|
1049
|
+
async function ueyeCapture(args = []) {
|
|
1050
|
+
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
|
|
1051
|
+
return ueyeUsage();
|
|
1052
|
+
const flags = parseSimpleFlags(args, new Set(['url', 'out', 'viewport', 'wait-until', 'timeout', 'full-page', 'json']));
|
|
1053
|
+
const url = String(flags.url || '');
|
|
1054
|
+
const out = String(flags.out || '');
|
|
1055
|
+
if (!url || !out) {
|
|
1056
|
+
console.error('usage: yam ueye capture --url URL --out screenshot.png [--viewport 1440x900] [--full-page] [--json]');
|
|
1057
|
+
process.exitCode = 1;
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
const target = path.resolve(expandHome(out));
|
|
1061
|
+
const viewport = parseViewport(String(flags.viewport || '1440x900'));
|
|
1062
|
+
const timeout = Number(flags.timeout || 30000);
|
|
1063
|
+
const waitUntil = String(flags.wait_until || 'networkidle');
|
|
1064
|
+
let browser;
|
|
1065
|
+
try {
|
|
1066
|
+
const playwright = await loadOptionalPlaywright();
|
|
1067
|
+
const chromium = playwright?.chromium;
|
|
1068
|
+
if (!chromium)
|
|
1069
|
+
throw new Error('Playwright chromium launcher was not found');
|
|
1070
|
+
await fsp.mkdir(path.dirname(target), { recursive: true });
|
|
1071
|
+
browser = await chromium.launch({ headless: true });
|
|
1072
|
+
const page = await browser.newPage({ viewport });
|
|
1073
|
+
await page.goto(url, { waitUntil, timeout });
|
|
1074
|
+
await page.screenshot({ path: target, fullPage: Boolean(flags.full_page) });
|
|
1075
|
+
const info = await imageFileInfo(target);
|
|
1076
|
+
const result = {
|
|
1077
|
+
schema: 'yam.ueye-capture.v1',
|
|
1078
|
+
status: 'verified',
|
|
1079
|
+
url,
|
|
1080
|
+
out: target,
|
|
1081
|
+
viewport: `${viewport.width}x${viewport.height}`,
|
|
1082
|
+
full_page: Boolean(flags.full_page),
|
|
1083
|
+
sha256: info.sha256,
|
|
1084
|
+
dimensions: info.dimensions,
|
|
1085
|
+
visual_evidence: `browser screenshot captured: ${target} (${info.dimensions}, sha256:${info.sha256})`
|
|
1086
|
+
};
|
|
1087
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Ueye capture');
|
|
1088
|
+
}
|
|
1089
|
+
catch (error) {
|
|
1090
|
+
const result = {
|
|
1091
|
+
schema: 'yam.ueye-capture.v1',
|
|
1092
|
+
status: 'blocked',
|
|
1093
|
+
url,
|
|
1094
|
+
out: target,
|
|
1095
|
+
reason: errorMessage(error),
|
|
1096
|
+
next_action: 'Install Playwright in the current project or provide a user/browser screenshot, then use `yam ueye compare`.',
|
|
1097
|
+
visual_cap: 'blocked'
|
|
1098
|
+
};
|
|
1099
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Ueye capture');
|
|
1100
|
+
process.exitCode = 1;
|
|
1101
|
+
}
|
|
1102
|
+
finally {
|
|
1103
|
+
if (browser)
|
|
1104
|
+
await browser.close().catch(() => { });
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
async function ueyeCompare(args = []) {
|
|
1108
|
+
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
|
|
1109
|
+
return ueyeUsage();
|
|
1110
|
+
const flags = parseSimpleFlags(args, new Set(['reference', 'ref', 'actual', 'screenshot', 'json', 'reference-id', 'screenshot-id']));
|
|
1111
|
+
const reference = String(flags.reference || flags.ref || '');
|
|
1112
|
+
const actual = String(flags.actual || flags.screenshot || '');
|
|
1113
|
+
if (!reference || !actual) {
|
|
1114
|
+
console.error('usage: yam ueye compare --reference ref.png --actual screenshot.png [--json]');
|
|
1115
|
+
process.exitCode = 1;
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
const referencePath = path.resolve(expandHome(reference));
|
|
1119
|
+
const actualPath = path.resolve(expandHome(actual));
|
|
1120
|
+
try {
|
|
1121
|
+
const referenceInfo = await imageFileInfo(referencePath);
|
|
1122
|
+
const actualInfo = await imageFileInfo(actualPath);
|
|
1123
|
+
if (referenceInfo.dimensions === 'unknown' || actualInfo.dimensions === 'unknown') {
|
|
1124
|
+
throw new Error('compare requires image files with readable PNG, JPEG, or GIF dimensions');
|
|
1125
|
+
}
|
|
1126
|
+
const exactMatch = referenceInfo.sha256 === actualInfo.sha256;
|
|
1127
|
+
const sameDimensions = referenceInfo.dimensions === actualInfo.dimensions;
|
|
1128
|
+
const comparisonResult = exactMatch ? 'matched' : 'different';
|
|
1129
|
+
const truthStatus = exactMatch ? 'verified' : 'partial';
|
|
1130
|
+
const provenance = buildUeyeVisualProvenance({
|
|
1131
|
+
source_kind: 'implementation_screenshot',
|
|
1132
|
+
source_path: actualPath,
|
|
1133
|
+
source_hash: actualInfo.sha256,
|
|
1134
|
+
reference_id: String(flags.reference_id || path.basename(referencePath)),
|
|
1135
|
+
screenshot_id: String(flags.screenshot_id || path.basename(actualPath)),
|
|
1136
|
+
local_only: true,
|
|
1137
|
+
redacted: false,
|
|
1138
|
+
operator_provided: false,
|
|
1139
|
+
comparison_result: comparisonResult,
|
|
1140
|
+
truth_status: truthStatus
|
|
1141
|
+
});
|
|
1142
|
+
const result = {
|
|
1143
|
+
schema: 'yam.ueye-compare.v1',
|
|
1144
|
+
status: truthStatus,
|
|
1145
|
+
comparison_result: comparisonResult,
|
|
1146
|
+
exact_match: exactMatch,
|
|
1147
|
+
same_dimensions: sameDimensions,
|
|
1148
|
+
reference: referenceInfo,
|
|
1149
|
+
actual: actualInfo,
|
|
1150
|
+
visual_evidence: `browser/local screenshot comparison executed: reference=${referencePath}, actual=${actualPath}, result=${comparisonResult}`,
|
|
1151
|
+
visual_provenance: provenance,
|
|
1152
|
+
proof_hint: `yam proof --route ueye --truth ${truthStatus} --visual "browser/local screenshot comparison executed: ${comparisonResult}" --visual-provenance '${JSON.stringify(provenance)}' --require-visual`
|
|
1153
|
+
};
|
|
1154
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Ueye compare');
|
|
1155
|
+
}
|
|
1156
|
+
catch (error) {
|
|
1157
|
+
const result = {
|
|
1158
|
+
schema: 'yam.ueye-compare.v1',
|
|
1159
|
+
status: 'blocked',
|
|
1160
|
+
comparison_result: 'not-verified',
|
|
1161
|
+
reason: errorMessage(error),
|
|
1162
|
+
visual_cap: 'blocked'
|
|
1163
|
+
};
|
|
1164
|
+
printJsonOrHuman(result, Boolean(flags.json), 'Ueye compare');
|
|
1165
|
+
process.exitCode = 1;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
function parseSimpleFlags(args = [], allowed = new Set()) {
|
|
1169
|
+
const flags = {};
|
|
1170
|
+
const positionals = [];
|
|
1171
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1172
|
+
const arg = String(args[index] || '');
|
|
1173
|
+
if (!arg.startsWith('--')) {
|
|
1174
|
+
positionals.push(arg);
|
|
1175
|
+
continue;
|
|
1176
|
+
}
|
|
1177
|
+
const [rawKey, inlineValue] = arg.includes('=') ? arg.split(/=(.*)/s, 2) : [arg, undefined];
|
|
1178
|
+
const key = rawKey.slice(2);
|
|
1179
|
+
if (allowed.size && !allowed.has(key))
|
|
1180
|
+
continue;
|
|
1181
|
+
const normalizedKey = key.replace(/-/g, '_');
|
|
1182
|
+
if (['json', 'full-page'].includes(key)) {
|
|
1183
|
+
flags[normalizedKey] = true;
|
|
1184
|
+
continue;
|
|
1185
|
+
}
|
|
1186
|
+
const value = inlineValue ?? args[index + 1] ?? '';
|
|
1187
|
+
if (inlineValue === undefined)
|
|
1188
|
+
index += 1;
|
|
1189
|
+
flags[normalizedKey] = value;
|
|
1190
|
+
}
|
|
1191
|
+
flags._ = positionals;
|
|
1192
|
+
return flags;
|
|
1193
|
+
}
|
|
1194
|
+
function parseViewport(value = '1440x900') {
|
|
1195
|
+
const match = String(value || '').match(/^(\d{2,5})x(\d{2,5})$/i);
|
|
1196
|
+
if (!match)
|
|
1197
|
+
return { width: 1440, height: 900 };
|
|
1198
|
+
return { width: Number(match[1]), height: Number(match[2]) };
|
|
1199
|
+
}
|
|
1200
|
+
async function loadOptionalPlaywright() {
|
|
1201
|
+
let projectError = '';
|
|
1202
|
+
try {
|
|
1203
|
+
const projectRequire = createRequire(path.join(process.cwd(), 'package.json'));
|
|
1204
|
+
return projectRequire('playwright');
|
|
1205
|
+
}
|
|
1206
|
+
catch (error) {
|
|
1207
|
+
projectError = errorMessage(error);
|
|
1208
|
+
// Fall through to the yam package context for local development or bundled installs.
|
|
1209
|
+
}
|
|
1210
|
+
const dynamicImport = new Function('specifier', 'return import(specifier)');
|
|
1211
|
+
try {
|
|
1212
|
+
return await dynamicImport('playwright');
|
|
1213
|
+
}
|
|
1214
|
+
catch (error) {
|
|
1215
|
+
throw new Error(`Playwright not found from current project or yam package context. Current project: ${projectError}; package context: ${errorMessage(error)}`);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
async function imageFileInfo(file) {
|
|
1219
|
+
const absolute = path.resolve(expandHome(file));
|
|
1220
|
+
const buffer = await fsp.readFile(absolute);
|
|
1221
|
+
const stat = await fsp.stat(absolute);
|
|
1222
|
+
return {
|
|
1223
|
+
path: absolute,
|
|
1224
|
+
sha256: crypto.createHash('sha256').update(buffer).digest('hex'),
|
|
1225
|
+
bytes: stat.size,
|
|
1226
|
+
dimensions: imageDimensions(buffer)
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
function imageDimensions(buffer) {
|
|
1230
|
+
const png = pngDimensions(buffer);
|
|
1231
|
+
if (png)
|
|
1232
|
+
return png;
|
|
1233
|
+
const jpeg = jpegDimensions(buffer);
|
|
1234
|
+
if (jpeg)
|
|
1235
|
+
return jpeg;
|
|
1236
|
+
const gif = gifDimensions(buffer);
|
|
1237
|
+
if (gif)
|
|
1238
|
+
return gif;
|
|
1239
|
+
return 'unknown';
|
|
1240
|
+
}
|
|
1241
|
+
function pngDimensions(buffer) {
|
|
1242
|
+
if (buffer.length < 24)
|
|
1243
|
+
return '';
|
|
1244
|
+
if (buffer.readUInt32BE(0) !== 0x89504e47 || buffer.readUInt32BE(4) !== 0x0d0a1a0a)
|
|
1245
|
+
return '';
|
|
1246
|
+
return `${buffer.readUInt32BE(16)}x${buffer.readUInt32BE(20)}`;
|
|
1247
|
+
}
|
|
1248
|
+
function gifDimensions(buffer) {
|
|
1249
|
+
if (buffer.length < 10)
|
|
1250
|
+
return '';
|
|
1251
|
+
const signature = buffer.toString('ascii', 0, 6);
|
|
1252
|
+
if (signature !== 'GIF87a' && signature !== 'GIF89a')
|
|
1253
|
+
return '';
|
|
1254
|
+
return `${buffer.readUInt16LE(6)}x${buffer.readUInt16LE(8)}`;
|
|
1255
|
+
}
|
|
1256
|
+
function jpegDimensions(buffer) {
|
|
1257
|
+
if (buffer.length < 4 || buffer[0] !== 0xff || buffer[1] !== 0xd8)
|
|
1258
|
+
return '';
|
|
1259
|
+
let offset = 2;
|
|
1260
|
+
while (offset < buffer.length) {
|
|
1261
|
+
if (buffer[offset] !== 0xff)
|
|
1262
|
+
return '';
|
|
1263
|
+
const marker = buffer[offset + 1];
|
|
1264
|
+
const length = buffer.readUInt16BE(offset + 2);
|
|
1265
|
+
if (marker >= 0xc0 && marker <= 0xc3 && offset + 8 < buffer.length) {
|
|
1266
|
+
return `${buffer.readUInt16BE(offset + 7)}x${buffer.readUInt16BE(offset + 5)}`;
|
|
1267
|
+
}
|
|
1268
|
+
offset += 2 + length;
|
|
1269
|
+
}
|
|
1270
|
+
return '';
|
|
1271
|
+
}
|
|
1272
|
+
function printJsonOrHuman(result, json = false, label = 'yam') {
|
|
1273
|
+
if (json) {
|
|
1274
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
console.log(label);
|
|
1278
|
+
for (const [key, value] of Object.entries(result)) {
|
|
1279
|
+
if (key === 'schema')
|
|
1280
|
+
continue;
|
|
1281
|
+
if (typeof value === 'object' && value !== null) {
|
|
1282
|
+
console.log(`- ${key}: ${JSON.stringify(value)}`);
|
|
1283
|
+
}
|
|
1284
|
+
else {
|
|
1285
|
+
console.log(`- ${key}: ${value}`);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
938
1289
|
function detectDbSafetyText(text = '') {
|
|
939
1290
|
return detectTrustDbSafetyText(text);
|
|
940
1291
|
}
|
|
@@ -975,6 +1326,9 @@ async function proof(args = []) {
|
|
|
975
1326
|
printProofList('Evidence', summary.evidence);
|
|
976
1327
|
printProofList('Visual evidence', summary.visual);
|
|
977
1328
|
printProofList('Runtime evidence', summary.runtime);
|
|
1329
|
+
printProofList('Visual provenance', summary.visualProvenance || []);
|
|
1330
|
+
printProofList('Mission patch envelope', summary.missionEnvelope || []);
|
|
1331
|
+
printProofList('Rollback hint', summary.rollbackHint || []);
|
|
978
1332
|
printProofList('Changed surfaces', summary.changed);
|
|
979
1333
|
printProofList('Skipped', summary.skipped);
|
|
980
1334
|
printProofList('Blocked', summary.blocked);
|
|
@@ -1050,6 +1404,18 @@ function buildProofSummary(parsed, artifact) {
|
|
|
1050
1404
|
evidence: [...artifact.evidence, ...arrayFlag(parsed.flags.evidence)],
|
|
1051
1405
|
visual: [...artifact.visual, ...arrayFlag(parsed.flags.visual)],
|
|
1052
1406
|
runtime: [...artifact.runtime, ...arrayFlag(parsed.flags.runtime)],
|
|
1407
|
+
visualProvenance: [
|
|
1408
|
+
...structuredEvidenceList(artifact.visualProvenance),
|
|
1409
|
+
...structuredEvidenceList(parsed.flags.visual_provenance)
|
|
1410
|
+
],
|
|
1411
|
+
missionEnvelope: [
|
|
1412
|
+
...structuredEvidenceList(artifact.missionEnvelope),
|
|
1413
|
+
...structuredEvidenceList(parsed.flags.mission_envelope)
|
|
1414
|
+
],
|
|
1415
|
+
rollbackHint: [
|
|
1416
|
+
...structuredEvidenceList(artifact.rollbackHint),
|
|
1417
|
+
...structuredEvidenceList(parsed.flags.rollback_hint)
|
|
1418
|
+
],
|
|
1053
1419
|
cleanup: parsed.flags.cleanup || artifact.cleanup || '',
|
|
1054
1420
|
changed: [...artifact.changed, ...arrayFlag(parsed.flags.changed)],
|
|
1055
1421
|
skipped: [...artifact.skipped, ...arrayFlag(parsed.flags.skipped)],
|
|
@@ -1099,6 +1465,15 @@ function renderProofMarkdown(summary) {
|
|
|
1099
1465
|
'## Runtime evidence',
|
|
1100
1466
|
...renderProofMarkdownList(summary.runtime),
|
|
1101
1467
|
'',
|
|
1468
|
+
'## Visual provenance',
|
|
1469
|
+
...renderProofMarkdownList(summary.visualProvenance || []),
|
|
1470
|
+
'',
|
|
1471
|
+
'## Mission patch envelope',
|
|
1472
|
+
...renderProofMarkdownList(summary.missionEnvelope || []),
|
|
1473
|
+
'',
|
|
1474
|
+
'## Rollback hint',
|
|
1475
|
+
...renderProofMarkdownList(summary.rollbackHint || []),
|
|
1476
|
+
'',
|
|
1102
1477
|
'## Changed surfaces',
|
|
1103
1478
|
...renderProofMarkdownList(summary.changed),
|
|
1104
1479
|
'',
|
|
@@ -1129,7 +1504,7 @@ function renderProofMarkdownList(values) {
|
|
|
1129
1504
|
function parseProofArgs(args = []) {
|
|
1130
1505
|
const flags = {};
|
|
1131
1506
|
const positionals = [];
|
|
1132
|
-
const aliases = new Set(['goal', 'route', 'truth', 'command', 'evidence', 'visual', 'runtime', 'cleanup', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified', 'from', 'format', 'out', 'file', 'json', 'require-runtime', 'require-real-runtime', 'require-tmux', 'require-visual']);
|
|
1507
|
+
const aliases = new Set(['goal', 'route', 'truth', 'command', 'evidence', 'visual', 'runtime', 'visual-provenance', 'mission-envelope', 'rollback-hint', 'cleanup', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified', 'from', 'format', 'out', 'file', 'json', 'require-runtime', 'require-real-runtime', 'require-tmux', 'require-visual']);
|
|
1133
1508
|
for (let index = 0; index < args.length; index += 1) {
|
|
1134
1509
|
const arg = args[index];
|
|
1135
1510
|
if (arg.startsWith('--')) {
|
|
@@ -1145,7 +1520,7 @@ function parseProofArgs(args = []) {
|
|
|
1145
1520
|
const value = inlineValue ?? args[index + 1] ?? '';
|
|
1146
1521
|
if (inlineValue === undefined)
|
|
1147
1522
|
index += 1;
|
|
1148
|
-
if (['command', 'evidence', 'visual', 'runtime', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified'].includes(key)) {
|
|
1523
|
+
if (['command', 'evidence', 'visual', 'runtime', 'visual-provenance', 'mission-envelope', 'rollback-hint', 'changed', 'skipped', 'blocked', 'assumed', 'assumption', 'unverified'].includes(key)) {
|
|
1149
1524
|
flags[normalizedKey] = [...arrayFlag(flags[normalizedKey]), value];
|
|
1150
1525
|
}
|
|
1151
1526
|
else {
|
|
@@ -1184,6 +1559,9 @@ function emptyProofArtifact() {
|
|
|
1184
1559
|
evidence: [],
|
|
1185
1560
|
visual: [],
|
|
1186
1561
|
runtime: [],
|
|
1562
|
+
visualProvenance: [],
|
|
1563
|
+
missionEnvelope: [],
|
|
1564
|
+
rollbackHint: [],
|
|
1187
1565
|
cleanup: '',
|
|
1188
1566
|
changed: [],
|
|
1189
1567
|
skipped: [],
|
|
@@ -1205,6 +1583,9 @@ async function parseProofJson(file, empty) {
|
|
|
1205
1583
|
evidence: arrayFlag(data.evidence),
|
|
1206
1584
|
visual: arrayFlag(data.visual || data.visualEvidence),
|
|
1207
1585
|
runtime: arrayFlag(data.runtime || data.runtimeEvidence),
|
|
1586
|
+
visualProvenance: structuredEvidenceList(data.visualProvenance || data.visual_provenance),
|
|
1587
|
+
missionEnvelope: structuredEvidenceList(data.missionEnvelope || data.mission_envelope),
|
|
1588
|
+
rollbackHint: structuredEvidenceList(data.rollbackHint || data.rollback_hint),
|
|
1208
1589
|
cleanup: String(data.cleanup || data.cleanupStatus || ''),
|
|
1209
1590
|
changed: arrayFlag(data.changed || data.files),
|
|
1210
1591
|
skipped: arrayFlag(data.skipped),
|
|
@@ -1230,6 +1611,9 @@ async function parseProofMarkdown(file, empty) {
|
|
|
1230
1611
|
evidence: readProofList(text, 'Evidence'),
|
|
1231
1612
|
visual: readProofList(text, 'Visual evidence'),
|
|
1232
1613
|
runtime: firstProofList(text, ['Runtime evidence', 'Processes', 'tmux']),
|
|
1614
|
+
visualProvenance: readProofList(text, 'Visual provenance'),
|
|
1615
|
+
missionEnvelope: readProofList(text, 'Mission patch envelope'),
|
|
1616
|
+
rollbackHint: readProofList(text, 'Rollback hint'),
|
|
1233
1617
|
cleanup: readProofField(text, 'Cleanup') || readProofField(text, 'Cleanup status'),
|
|
1234
1618
|
changed: firstProofList(text, ['Changed surfaces', 'Files']),
|
|
1235
1619
|
skipped: readProofList(text, 'Skipped'),
|
|
@@ -1274,9 +1658,39 @@ function arrayFlag(value) {
|
|
|
1274
1658
|
return [];
|
|
1275
1659
|
return Array.isArray(value) ? value.filter(Boolean) : [value].filter(Boolean);
|
|
1276
1660
|
}
|
|
1661
|
+
function structuredEvidenceList(value) {
|
|
1662
|
+
return arrayFlag(value).map((item) => formatStructuredEvidence(item)).filter(Boolean);
|
|
1663
|
+
}
|
|
1664
|
+
function formatStructuredEvidence(value) {
|
|
1665
|
+
if (!value)
|
|
1666
|
+
return '';
|
|
1667
|
+
if (typeof value === 'object')
|
|
1668
|
+
return JSON.stringify(value);
|
|
1669
|
+
const text = String(value).trim();
|
|
1670
|
+
if (!text)
|
|
1671
|
+
return '';
|
|
1672
|
+
if (!text.startsWith('{'))
|
|
1673
|
+
return text;
|
|
1674
|
+
try {
|
|
1675
|
+
const data = JSON.parse(text);
|
|
1676
|
+
if (data.schema === 'yam.ueye-visual-provenance.v1' || data.source_kind || data.reference_id || data.screenshot_id) {
|
|
1677
|
+
return JSON.stringify(buildUeyeVisualProvenance(data));
|
|
1678
|
+
}
|
|
1679
|
+
if (data.schema === 'yam.mission-patch-envelope.v1' || data.agent_id || data.assigned_scope) {
|
|
1680
|
+
return JSON.stringify(buildMissionPatchEnvelope(data));
|
|
1681
|
+
}
|
|
1682
|
+
if (data.schema === 'yam.rollback-hint.v1' || data.touched_files || data.safe_revert_note) {
|
|
1683
|
+
return JSON.stringify(buildRollbackHint(data));
|
|
1684
|
+
}
|
|
1685
|
+
return JSON.stringify(data);
|
|
1686
|
+
}
|
|
1687
|
+
catch {
|
|
1688
|
+
return text;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1277
1691
|
function hasProofEvidence(parsed) {
|
|
1278
1692
|
const flags = parsed.flags || {};
|
|
1279
|
-
return ['command', 'evidence', 'visual', 'runtime', 'changed', 'skipped', 'blocked'].some((key) => arrayFlag(flags[key]).length > 0) || Boolean(flags.cleanup);
|
|
1693
|
+
return ['command', 'evidence', 'visual', 'runtime', 'visual_provenance', 'mission_envelope', 'rollback_hint', 'changed', 'skipped', 'blocked'].some((key) => arrayFlag(flags[key]).length > 0) || Boolean(flags.cleanup);
|
|
1280
1694
|
}
|
|
1281
1695
|
function printProofList(label, values) {
|
|
1282
1696
|
console.log(`- ${label}:`);
|
|
@@ -1967,6 +2381,10 @@ async function main() {
|
|
|
1967
2381
|
return tools(process.argv.slice(3));
|
|
1968
2382
|
if (command === 'proof')
|
|
1969
2383
|
return proof(process.argv.slice(3));
|
|
2384
|
+
if (command === 'ueye')
|
|
2385
|
+
return ueye(process.argv.slice(3));
|
|
2386
|
+
if (command === 'release')
|
|
2387
|
+
return release(process.argv.slice(3));
|
|
1970
2388
|
if (command === 'safety')
|
|
1971
2389
|
return safety(process.argv.slice(3));
|
|
1972
2390
|
if (command === 'memory')
|
|
@@ -29,6 +29,35 @@ export interface RuntimeTruthMatrix {
|
|
|
29
29
|
rows: RuntimeTruthRow[];
|
|
30
30
|
blockers: string[];
|
|
31
31
|
}
|
|
32
|
+
export interface UeyeVisualProvenance {
|
|
33
|
+
schema: 'yam.ueye-visual-provenance.v1';
|
|
34
|
+
source_kind: string;
|
|
35
|
+
source_path: string;
|
|
36
|
+
source_hash: string;
|
|
37
|
+
reference_id: string;
|
|
38
|
+
screenshot_id: string;
|
|
39
|
+
local_only: boolean;
|
|
40
|
+
redacted: boolean;
|
|
41
|
+
operator_provided: boolean;
|
|
42
|
+
comparison_result: string;
|
|
43
|
+
truth_status: TruthStatus;
|
|
44
|
+
}
|
|
45
|
+
export interface RollbackHint {
|
|
46
|
+
schema: 'yam.rollback-hint.v1';
|
|
47
|
+
touched_files: string[];
|
|
48
|
+
generated_files: string[];
|
|
49
|
+
before_check: string;
|
|
50
|
+
safe_revert_note: string;
|
|
51
|
+
}
|
|
52
|
+
export interface MissionPatchEnvelope {
|
|
53
|
+
schema: 'yam.mission-patch-envelope.v1';
|
|
54
|
+
agent_id: string;
|
|
55
|
+
assigned_scope: string;
|
|
56
|
+
changed_files: string[];
|
|
57
|
+
verification_hint: string;
|
|
58
|
+
rollback_hint: RollbackHint;
|
|
59
|
+
truth_status: TruthStatus;
|
|
60
|
+
}
|
|
32
61
|
export interface FakeRealPolicyResult {
|
|
33
62
|
schema: 'yam.fake-real-policy.v1';
|
|
34
63
|
ok: boolean;
|
|
@@ -52,6 +81,9 @@ export interface ProofSummary {
|
|
|
52
81
|
blocked?: unknown;
|
|
53
82
|
assumptions?: unknown;
|
|
54
83
|
unverified?: unknown;
|
|
84
|
+
visualProvenance?: unknown;
|
|
85
|
+
missionEnvelope?: unknown;
|
|
86
|
+
rollbackHint?: unknown;
|
|
55
87
|
}
|
|
56
88
|
export interface ProofOptions {
|
|
57
89
|
requireRealRuntime?: boolean;
|
|
@@ -79,6 +111,9 @@ export interface YamCompletionProof {
|
|
|
79
111
|
evidence: string[];
|
|
80
112
|
visual: string[];
|
|
81
113
|
runtime: string[];
|
|
114
|
+
visualProvenance: string[];
|
|
115
|
+
missionEnvelope: string[];
|
|
116
|
+
rollbackHint: string[];
|
|
82
117
|
cleanup: unknown;
|
|
83
118
|
changed: string[];
|
|
84
119
|
skipped: string[];
|
|
@@ -90,6 +125,9 @@ export interface YamCompletionProof {
|
|
|
90
125
|
fakeReal: FakeRealPolicyResult;
|
|
91
126
|
runtimeTruth: RuntimeTruthMatrix;
|
|
92
127
|
}
|
|
128
|
+
export declare function buildUeyeVisualProvenance(input?: Partial<UeyeVisualProvenance>): UeyeVisualProvenance;
|
|
129
|
+
export declare function buildRollbackHint(input?: Partial<RollbackHint>): RollbackHint;
|
|
130
|
+
export declare function buildMissionPatchEnvelope(input?: Partial<MissionPatchEnvelope>): MissionPatchEnvelope;
|
|
93
131
|
export declare function isTruthStatus(value?: unknown): value is TruthStatus;
|
|
94
132
|
export declare function weakestTruth(...statuses: unknown[]): TruthStatus;
|
|
95
133
|
export declare function detectDbSafetyText(text?: unknown): DbSafetyScanResult;
|
package/dist/lib/trust-kernel.js
CHANGED
|
@@ -10,6 +10,45 @@ export const TRUTH_STATUSES = Object.freeze([
|
|
|
10
10
|
'blocked',
|
|
11
11
|
'assumed'
|
|
12
12
|
]);
|
|
13
|
+
export function buildUeyeVisualProvenance(input = {}) {
|
|
14
|
+
const truth = isTruthStatus(input.truth_status) ? input.truth_status : 'partial';
|
|
15
|
+
return {
|
|
16
|
+
schema: 'yam.ueye-visual-provenance.v1',
|
|
17
|
+
source_kind: String(input.source_kind || ''),
|
|
18
|
+
source_path: String(input.source_path || ''),
|
|
19
|
+
source_hash: String(input.source_hash || 'unknown'),
|
|
20
|
+
reference_id: String(input.reference_id || ''),
|
|
21
|
+
screenshot_id: String(input.screenshot_id || ''),
|
|
22
|
+
local_only: Boolean(input.local_only),
|
|
23
|
+
redacted: Boolean(input.redacted),
|
|
24
|
+
operator_provided: Boolean(input.operator_provided),
|
|
25
|
+
comparison_result: String(input.comparison_result || 'not-verified'),
|
|
26
|
+
truth_status: truth
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function buildRollbackHint(input = {}) {
|
|
30
|
+
return {
|
|
31
|
+
schema: 'yam.rollback-hint.v1',
|
|
32
|
+
touched_files: asList(input.touched_files),
|
|
33
|
+
generated_files: asList(input.generated_files),
|
|
34
|
+
before_check: String(input.before_check || ''),
|
|
35
|
+
safe_revert_note: String(input.safe_revert_note || '')
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function buildMissionPatchEnvelope(input = {}) {
|
|
39
|
+
const rollback = input.rollback_hint && typeof input.rollback_hint === 'object'
|
|
40
|
+
? buildRollbackHint(input.rollback_hint)
|
|
41
|
+
: buildRollbackHint();
|
|
42
|
+
return {
|
|
43
|
+
schema: 'yam.mission-patch-envelope.v1',
|
|
44
|
+
agent_id: String(input.agent_id || ''),
|
|
45
|
+
assigned_scope: String(input.assigned_scope || ''),
|
|
46
|
+
changed_files: asList(input.changed_files),
|
|
47
|
+
verification_hint: String(input.verification_hint || ''),
|
|
48
|
+
rollback_hint: rollback,
|
|
49
|
+
truth_status: isTruthStatus(input.truth_status) ? input.truth_status : 'partial'
|
|
50
|
+
};
|
|
51
|
+
}
|
|
13
52
|
const TRUTH_RANK = Object.freeze({
|
|
14
53
|
blocked: 0,
|
|
15
54
|
real_required_missing: 0,
|
|
@@ -81,6 +120,15 @@ export function classifyEvidenceTruth(summary = {}, options = {}) {
|
|
|
81
120
|
for (const visual of asList(summary.visual)) {
|
|
82
121
|
rows.push(evidenceRow('visual', visual, classifyVisualEvidence(visual)));
|
|
83
122
|
}
|
|
123
|
+
for (const provenance of asList(summary.visualProvenance)) {
|
|
124
|
+
rows.push(evidenceRow('visual', provenance, metadataTruth(provenance, 'partial')));
|
|
125
|
+
}
|
|
126
|
+
for (const envelope of asList(summary.missionEnvelope)) {
|
|
127
|
+
rows.push(evidenceRow('evidence', envelope, metadataTruth(envelope, 'partial')));
|
|
128
|
+
}
|
|
129
|
+
for (const rollback of asList(summary.rollbackHint)) {
|
|
130
|
+
rows.push(evidenceRow('evidence', rollback, metadataTruth(rollback, 'partial')));
|
|
131
|
+
}
|
|
84
132
|
for (const runtime of asList(summary.runtime)) {
|
|
85
133
|
rows.push(evidenceRow('runtime', runtime, classifyRuntimeEvidence(runtime, options)));
|
|
86
134
|
}
|
|
@@ -166,6 +214,9 @@ export function buildYamCompletionProof(summary = {}, options = {}) {
|
|
|
166
214
|
evidence: asList(summary.evidence),
|
|
167
215
|
visual: asList(summary.visual),
|
|
168
216
|
runtime: asList(summary.runtime),
|
|
217
|
+
visualProvenance: asList(summary.visualProvenance),
|
|
218
|
+
missionEnvelope: asList(summary.missionEnvelope),
|
|
219
|
+
rollbackHint: asList(summary.rollbackHint),
|
|
169
220
|
cleanup: summary.cleanup || '',
|
|
170
221
|
changed: asList(summary.changed),
|
|
171
222
|
skipped: asList(summary.skipped),
|
|
@@ -223,6 +274,24 @@ function classifyCleanupEvidence(value = '') {
|
|
|
223
274
|
function commandPassed(value = '') {
|
|
224
275
|
return /pass|passed|ok|success|exit\s*0|green/i.test(String(value || '')) && !/fail|failed|error|blocked/i.test(String(value || ''));
|
|
225
276
|
}
|
|
277
|
+
function metadataTruth(value = '', fallback = 'partial') {
|
|
278
|
+
return weakestTruth(readStructuredTruth(value, fallback), 'partial');
|
|
279
|
+
}
|
|
280
|
+
function readStructuredTruth(value = '', fallback = 'partial') {
|
|
281
|
+
if (typeof value === 'object' && value) {
|
|
282
|
+
const candidate = value.truth_status || value.truth;
|
|
283
|
+
return isTruthStatus(candidate) ? candidate : fallback;
|
|
284
|
+
}
|
|
285
|
+
const text = String(value || '');
|
|
286
|
+
try {
|
|
287
|
+
const data = JSON.parse(text);
|
|
288
|
+
const candidate = data.truth_status || data.truth;
|
|
289
|
+
return isTruthStatus(candidate) ? candidate : fallback;
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
return fallback;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
226
295
|
function strongestSupportedTruth(rows = []) {
|
|
227
296
|
if (rows.some((row) => row.truth === 'blocked'))
|
|
228
297
|
return 'blocked';
|