yam-flow 2.6.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/COMMANDS.md +30 -0
- package/README.md +9 -1
- package/ROADMAP.md +3 -1
- package/dist/bin/yam.js +183 -6
- package/dist/lib/media-provider-receipt.d.ts +79 -0
- package/dist/lib/media-provider-receipt.js +498 -0
- package/dist/lib/skill-installation.js +337 -97
- package/dist/lib/verification-closure.d.ts +97 -0
- package/dist/lib/verification-closure.js +728 -0
- package/package.json +3 -2
- package/src/bin/yam.ts +179 -5
- package/src/lib/media-provider-receipt.ts +548 -0
- package/src/lib/skill-installation.ts +462 -95
- package/src/lib/verification-closure.ts +738 -0
- package/yam.manifest.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to yam-flow are documented here.
|
|
4
4
|
|
|
5
|
+
## v2.7.0 - 2026-08-24
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added immutable final-scope verification closure receipts that bind declared work to the current Git dirty, staged, and untracked path set, promote release-sensitive changes, and separate selected checks from operator-declared execution evidence.
|
|
10
|
+
- Added optional demand-gated media provider receipts with exact call/execution/dry-run/submit state plus confined, identity-bound local asset provenance.
|
|
11
|
+
|
|
12
|
+
### Improved
|
|
13
|
+
|
|
14
|
+
- Skill installation and removal now use no-follow regular-file access where available, verify descriptor and path identity, and revalidate parent-directory identity around read, copy, move, rollback, and mutation boundaries.
|
|
15
|
+
- Verification closure and media receipt reads and immutable writes apply confined path, non-symlink parent, descriptor identity, size, and digest checks; media asset hashing is sequential and bounded.
|
|
16
|
+
|
|
17
|
+
### Security
|
|
18
|
+
|
|
19
|
+
- Symlinked install source/destination segments, raced path identities, rollback or cleanup targets whose recorded type/device/inode no longer match, unplanned final Git paths, unavailable Git scope, receipt path escapes, local asset symlinks, and incoherent provider-execution claims fail closed.
|
|
20
|
+
- Node does not expose descriptor-relative `openat` traversal, and arbitrary Windows reparse-point coverage remains unverified; these limits are documented instead of overstated.
|
|
21
|
+
|
|
22
|
+
### Verification
|
|
23
|
+
|
|
24
|
+
- Focused install-transaction and phase-adoption smokes cover source/destination race handling, rollback preservation, Git-scope drift, immutable/tampered receipts, bounded media assets, and packaged CLI compatibility.
|
|
25
|
+
- Release checks include typecheck, clean build/self-verification, distribution freshness, package boundary/secrets, registry version availability, and CLI smoke.
|
|
26
|
+
|
|
5
27
|
## v2.6.0 - 2026-08-07
|
|
6
28
|
|
|
7
29
|
### Added
|
package/COMMANDS.md
CHANGED
|
@@ -174,6 +174,36 @@ yam ueye production phase verify --receipt-path .yam/ueye/design-production/camp
|
|
|
174
174
|
|
|
175
175
|
Canvas artifact specs use `{ "id", "role", "file_path" }`; gallery specs include `{ "id", "role", "provenance", "revision_ref", "dimensions", "sha256", "completion_state", "final_path" }`. A revision reference also carries the archive receipt's exact `archived_at`, which must match its Ueye manifest entry. All project paths are relative and confined; Canvas session/render paths and the single revision state are fixed under the session-specific `.yam/ueye/design-production/` directory. Revision init binds that state to the closed `request_changes` Canvas path and digest; record/show/finalize reject alternate state files, and record immediately checks the source Canvas comment plus the current archive-time boundary. Demand is operator-asserted but must be recorded before Canvas creation and match at finalize. Canvas source hashes and exact anchors are rechecked before comment/close/finalize. Every phase round names its source Canvas comment and supplies at least one newly archived Ueye revision created within that round's review window; gallery artifacts bind to the final round. Ueye asset/revision hashes are rechecked, a protected `do_not_replace` asset requires `intent: "preserve"`, and `edit_copy` requires `allowed_for_edit: true`. The phase receipt stores upstream and canonical receipt digests, and `phase verify` re-reads their semantics as well as detecting later byte changes. Overall phase truth remains `partial`, and gallery visual, license, and implementation claims always remain `not_verified`.
|
|
176
176
|
|
|
177
|
+
## Verification Closure And Media Provider Receipts
|
|
178
|
+
|
|
179
|
+
These are explicit, immutable artifacts. They do not run verification commands or contact media providers.
|
|
180
|
+
|
|
181
|
+
Prepare one JSON file per selected/executed/skipped/truncated command record, then repeat the corresponding flag:
|
|
182
|
+
|
|
183
|
+
```json
|
|
184
|
+
{"id":"typecheck","command":"npm run typecheck","scope":"affected","required":true}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```json
|
|
188
|
+
{"id":"typecheck","command":"npm run typecheck","exit_code":0,"evidence":"exit 0 observed in the final worktree"}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
yam verification closure create --root /path/to/project --receipt-path .yam/verification/final.json --planned-scope affected --changed-file src/example.ts --selected-spec ./selected.json --executed-spec ./executed.json --json
|
|
193
|
+
yam verification closure verify --root /path/to/project --receipt-path .yam/verification/final.json --json
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
`create` snapshots the Git top-level dirty, staged, and untracked path set. Every observed path must be declared; unplanned paths, missing declared paths, unavailable Git scope, missing required execution, and release-sensitive paths without a required release-scope check fail closed. `verify` re-reads the current Git path set as well as the receipt digest. A later path-set change returns `current_git_scope_drift`; rerun checks against the final state and create a new receipt path because receipts are non-overwriting. Content-only changes to an already-dirty path do not alter the Git path set, and command results remain operator-asserted `partial` evidence.
|
|
197
|
+
|
|
198
|
+
Media provider receipts are optional and should be used only after real execution-tracking demand exists. Each asset spec contains `{ "id", "role", "file_path", "provenance": { "kind", "source_ref", "recorded_at" } }`.
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
yam media provider create --root /path/to/project --receipt-path .yam/media/dry-run.json --demand-kind media_generation --demand-evidence "coordinated asset work needs an execution boundary" --provider example --model example-v1 --provider-calls 0 --dry-run --asset-spec ./input-asset.json --json
|
|
202
|
+
yam media provider verify --root /path/to/project --receipt-path .yam/media/dry-run.json --json
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
A dry run requires zero calls, no execution, and no submit. Explicit execution requires `--provider-execution --submit`, at least one call, and a provider-provenance output asset. Local asset and receipt integrity is verified, but provider execution, visual quality, and implementation correctness are not independently proven.
|
|
206
|
+
|
|
177
207
|
## Bounded Promotion
|
|
178
208
|
|
|
179
209
|
```bash
|
package/README.md
CHANGED
|
@@ -112,7 +112,9 @@ cd ~/Documents/Codex/tools/yam
|
|
|
112
112
|
yam install
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
`yam install` first checks ownership of every existing active skill. A skill is replaced automatically only when the previous yam receipt names it and its complete regular-file inventory and content hashes still match the receipt. The installer then stages every managed skill plus the shared `references/` directory, verifies the complete staged file manifest with SHA-256, and replaces the verified active set in `~/.agents/skills/`. If any commit or post-install verification step fails, yam restores the previous managed skill set and receipt.
|
|
115
|
+
`yam install` first checks ownership of every existing active skill. A skill is replaced automatically only when the previous yam receipt names it and its complete regular-file inventory and content hashes still match the receipt. The installer then stages every managed skill plus the shared `references/` directory, verifies the complete staged file manifest with SHA-256, and replaces the verified active set in `~/.agents/skills/`. If any commit or post-install verification step fails, yam restores the previous managed skill set and receipt. Rollback and cleanup remove newly installed skills, receipts, locks, and transaction roots only while their recorded type and device/inode identity still match; an identity-swapped path is preserved and reported instead of being treated as yam-owned.
|
|
116
|
+
|
|
117
|
+
Install source, destination, receipt, and managed-tree paths must use canonical physical directories. A symlink alias used as the package source or `YAM_SKILLS_HOME`, or a symlinked parent segment inside either boundary, fails closed; resolve the alias to its real path before retrying. Regular-file reads use descriptor identity checks and `O_NOFOLLOW` where the platform exposes it, while mutation boundaries revalidate parent directory identities. This narrows symlink-swap risk but is not `openat`-grade proof because Node does not expose descriptor-relative traversal here. Arbitrary Windows reparse-point handling is also unverified, so Windows receives the portable identity checks without a claim of complete reparse coverage.
|
|
116
118
|
|
|
117
119
|
If an existing active directory is user-owned, locally modified, or cannot be proven yam-owned, installation stops before staging and preserves it. After reviewing the files, replace only the intended conflicting name explicitly:
|
|
118
120
|
|
|
@@ -335,6 +337,8 @@ These shapes keep reports machine-readable without turning normal work into a he
|
|
|
335
337
|
- Bounded promotion receipt: `yam benchmark report` can compare paired candidate/baseline measurements under explicit sample, mean-delta, and win-rate thresholds. Arithmetic is verified, while measurement provenance remains `operator_asserted` and overall truth stays `partial`.
|
|
336
338
|
- Honest capability matrix: `yam tools doctor --json` reports feature maturity separately from observed runtime readiness; instruction-only or unprobed capabilities are not reported as executed.
|
|
337
339
|
- Strict gate result: release, update, benchmark-promotion, and Mission completion boundaries emit a digest-bound `yam.gate-result.v1` and fail closed on missing or inconsistent required evidence.
|
|
340
|
+
- Final-scope verification closure: `yam verification closure create|verify` binds the planned boundary, current Git dirty/staged/untracked paths, release-sensitive escalation, selected versus operator-declared executed commands, skipped/truncated reasons, and an immutable digest. Missing required execution, unknown results, unplanned files, unavailable Git scope, and later receipt tampering fail closed; yam does not execute the recorded commands or upgrade their truth beyond `partial`.
|
|
341
|
+
- Optional media provider receipt: `yam media provider create|verify` records an explicit demand trigger, exact `provider_calls`/`provider_execution`/`dry_run`/`submit` state, and identity-bound local asset provenance. It never calls a provider, and integrity verification never proves visual or implementation correctness.
|
|
338
342
|
|
|
339
343
|
## Ueye Capture And Compare
|
|
340
344
|
|
|
@@ -366,4 +370,8 @@ Generated media can guide visual direction, but it cannot prove the implemented
|
|
|
366
370
|
yam media proof --requested --attempted --output ./generated.png --wait-loop --json
|
|
367
371
|
```
|
|
368
372
|
|
|
373
|
+
When real repeated media work needs an execution boundary, prepare bounded JSON asset specs and opt in explicitly. Dry-run receipts require zero calls/no submit; execution receipts require a submitted call plus a provider-provenance output. Both the receipt and local asset paths are confined below the project root and can be re-read with `yam media provider verify`. The existing `yam media proof` command remains the lightweight default.
|
|
374
|
+
|
|
375
|
+
For completion evidence, `yam verification closure create` consumes small selected/executed/skipped/truncated JSON specs. It snapshots actual Git status before writing under `.yam/verification/`; declare every expected changed path with repeated `--changed-file`, then use `verify` to detect semantic, digest, or current Git path-set drift. `current_git_scope_drift` means checks must be rerun against the final state and a new immutable receipt must be created. This remains an explicit artifact, not an always-on hook or command runner.
|
|
376
|
+
|
|
369
377
|
For agent-driven visual QA, prefer the Codex in-app Browser plugin. Use the user's Chrome browser only when explicitly requested or when Chrome-only session/profile state is required.
|
package/ROADMAP.md
CHANGED
|
@@ -53,6 +53,7 @@ Tasks:
|
|
|
53
53
|
- Detect common commands: typecheck, lint, test, build. Done.
|
|
54
54
|
- Map changed files to likely checks. Done: dirty, staged, and untracked paths now map to focused rules with confidence, reason, fallback, and available commands.
|
|
55
55
|
- Keep this advisory, not mandatory. Done.
|
|
56
|
+
- Add an explicit final-scope closure artifact without turning the detector into an auto-runner. Done: immutable receipts bind current Git scope, release escalation, selected/executed command separation, skipped/truncated reasons, and partial operator-asserted truth.
|
|
56
57
|
|
|
57
58
|
### 4. Token Budget Reporter
|
|
58
59
|
|
|
@@ -76,6 +77,7 @@ Tasks:
|
|
|
76
77
|
- Add local reference asset provenance/protection manifest and non-overwriting revision history. Done.
|
|
77
78
|
- Add optional browser/screenshot capture notes.
|
|
78
79
|
- Keep image generation optional, never a gate.
|
|
80
|
+
- Add an optional provider-execution boundary only when real media demand exists. Done: exact fail-closed execution fields, confined hash-bound local assets/provenance, and verify/tamper detection without provider calls or correctness claims.
|
|
79
81
|
|
|
80
82
|
### 6. Deep Runtime Workflow
|
|
81
83
|
|
|
@@ -94,7 +96,7 @@ Goal: make install/update simpler.
|
|
|
94
96
|
|
|
95
97
|
Tasks:
|
|
96
98
|
|
|
97
|
-
-
|
|
99
|
+
- Reconsider symlink-friendly local install only if Node exposes a safe descriptor-relative traversal boundary or an equivalent proven design. Deferred: current package source, `YAM_SKILLS_HOME`, and managed-tree paths intentionally reject symlink aliases and symlinked parent segments.
|
|
98
100
|
- Add version command. Done.
|
|
99
101
|
- Add staged transactional install, rollback, receipt, and drift-aware status. Done.
|
|
100
102
|
- Add checked component updates for yam, Scrapling, and Insane Search with explicit authorization, receipts, and rollback guidance. Done.
|
package/dist/bin/yam.js
CHANGED
|
@@ -15,6 +15,8 @@ import { inspectSkillInstallation, installSkillSetTransactional, uninstallSkillS
|
|
|
15
15
|
import { EXTERNAL_UPDATE_COMPONENTS, applyExternalUpdates, checkExternalUpdates } from '../lib/external-updates.js';
|
|
16
16
|
import { buildBoundedPromotionReport, parsePromotionSample } from '../lib/benchmark-promotion.js';
|
|
17
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';
|
|
18
20
|
import { BoundedInputError, GENERAL_STDIN_MAX_BYTES, HOOK_STDIN_MAX_BYTES, readBoundedStdinJson, readBoundedStdinText } from '../lib/bounded-input.js';
|
|
19
21
|
function errorMessage(error) {
|
|
20
22
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -152,6 +154,7 @@ Usage:
|
|
|
152
154
|
yam mission queue [--agent-id id] [--scope text] [--changed file] [--verification-hint text] [--json]
|
|
153
155
|
yam mission receipt [--thread-id id] [--role reviewer] [--lifecycle stopped] [--outcome passed] [--evidence text] [--json]
|
|
154
156
|
yam mission gate [--expected-thread id] [--receipt file] [--json]
|
|
157
|
+
yam verification closure <create|verify> [options]
|
|
155
158
|
yam update check [--json]
|
|
156
159
|
yam update apply --component yam|scrapling|insane-search [--json]
|
|
157
160
|
yam update apply --all [--json]
|
|
@@ -1364,6 +1367,8 @@ function buildCapabilityMatrix(readinessRows = [], liteHook = {}, studyNoteHook
|
|
|
1364
1367
|
capabilityRow('bounded_design_revisions', 'beta', 'ready', 'maximum two reviewer-finding-backed revision rounds', 'round state does not replace Ueye artifact hash verification'),
|
|
1365
1368
|
capabilityRow('final_gallery_manifest', 'beta', 'ready', 'path-confined hash-verified packaging manifest', 'gallery completeness does not prove visual or license correctness'),
|
|
1366
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'),
|
|
1367
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'),
|
|
1368
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')
|
|
1369
1374
|
];
|
|
@@ -3141,17 +3146,57 @@ async function readProductionSpecs(value, label) {
|
|
|
3141
3146
|
specs.push(await readBoundedJsonSpec(path.resolve(expandHome(file)), label));
|
|
3142
3147
|
return specs;
|
|
3143
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
|
+
}
|
|
3144
3156
|
async function readBoundedJsonSpec(file, label) {
|
|
3145
|
-
const
|
|
3146
|
-
if (!
|
|
3147
|
-
throw new Error(`${label} must be a regular file: ${file}`);
|
|
3148
|
-
if (
|
|
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)
|
|
3149
3161
|
throw new Error(`${label} exceeds the 256 KiB limit: ${file}`);
|
|
3150
|
-
const
|
|
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);
|
|
3151
3193
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
3152
3194
|
throw new Error(`${label} must contain one JSON object: ${file}`);
|
|
3153
3195
|
return value;
|
|
3154
3196
|
}
|
|
3197
|
+
function sameSpecFileIdentity(left, right) {
|
|
3198
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
3199
|
+
}
|
|
3155
3200
|
async function existingProductionPath(root, value, label) {
|
|
3156
3201
|
if (!value || path.isAbsolute(value) || value.includes('\0'))
|
|
3157
3202
|
throw new Error(`${label} must be a non-empty project-relative path`);
|
|
@@ -3707,12 +3752,83 @@ function normalizeUeyeComparisonResult(value = '') {
|
|
|
3707
3752
|
return text;
|
|
3708
3753
|
return 'not-verified';
|
|
3709
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
|
+
}
|
|
3710
3824
|
async function media(args = []) {
|
|
3711
3825
|
const subcommand = args[0] || 'help';
|
|
3712
3826
|
if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h')
|
|
3713
3827
|
return mediaUsage();
|
|
3714
3828
|
if (subcommand === 'proof')
|
|
3715
3829
|
return mediaProof(args.slice(1));
|
|
3830
|
+
if (subcommand === 'provider')
|
|
3831
|
+
return mediaProvider(args.slice(1));
|
|
3716
3832
|
console.error(`unknown media command: ${subcommand}`);
|
|
3717
3833
|
return mediaUsage();
|
|
3718
3834
|
}
|
|
@@ -3723,8 +3839,66 @@ Opt-in media generation proof helpers. Generated media can support visual direct
|
|
|
3723
3839
|
|
|
3724
3840
|
Usage:
|
|
3725
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.
|
|
3726
3846
|
`);
|
|
3727
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
|
+
}
|
|
3728
3902
|
async function mediaProof(args = []) {
|
|
3729
3903
|
if (args[0] === 'help' || args[0] === '--help' || args[0] === '-h')
|
|
3730
3904
|
return mediaUsage();
|
|
@@ -4250,7 +4424,7 @@ function parseSimpleFlags(args = [], allowed = new Set()) {
|
|
|
4250
4424
|
if (allowed.size && !allowed.has(key))
|
|
4251
4425
|
continue;
|
|
4252
4426
|
const normalizedKey = key.replace(/-/g, '_');
|
|
4253
|
-
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)) {
|
|
4254
4428
|
flags[normalizedKey] = true;
|
|
4255
4429
|
continue;
|
|
4256
4430
|
}
|
|
@@ -5778,6 +5952,7 @@ function showRequestedHelp(args) {
|
|
|
5778
5952
|
loop: loopUsage,
|
|
5779
5953
|
ueye: ueyeUsage,
|
|
5780
5954
|
media: mediaUsage,
|
|
5955
|
+
verification: verificationUsage,
|
|
5781
5956
|
runtime: runtimeUsage,
|
|
5782
5957
|
mission: missionUsage,
|
|
5783
5958
|
update: updateUsage,
|
|
@@ -5824,6 +5999,8 @@ async function main() {
|
|
|
5824
5999
|
return ueye(process.argv.slice(3));
|
|
5825
6000
|
if (command === 'media')
|
|
5826
6001
|
return media(process.argv.slice(3));
|
|
6002
|
+
if (command === 'verification')
|
|
6003
|
+
return verification(process.argv.slice(3));
|
|
5827
6004
|
if (command === 'runtime')
|
|
5828
6005
|
return runtime(process.argv.slice(3));
|
|
5829
6006
|
if (command === 'mission')
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export interface MediaProviderAssetInput {
|
|
2
|
+
id: string;
|
|
3
|
+
role: 'input' | 'output';
|
|
4
|
+
file_path: string;
|
|
5
|
+
provenance: {
|
|
6
|
+
kind: 'operator' | 'provider';
|
|
7
|
+
source_ref: string;
|
|
8
|
+
recorded_at: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export interface MediaProviderReceipt {
|
|
12
|
+
schema: 'yam.media-provider-receipt.v1';
|
|
13
|
+
created_at: string;
|
|
14
|
+
demand_trigger: {
|
|
15
|
+
kind: 'media_generation' | 'media_transformation';
|
|
16
|
+
evidence: string;
|
|
17
|
+
evidence_truth: 'operator_asserted';
|
|
18
|
+
};
|
|
19
|
+
provider: {
|
|
20
|
+
name: string;
|
|
21
|
+
model: string;
|
|
22
|
+
};
|
|
23
|
+
provider_calls: number;
|
|
24
|
+
provider_execution: boolean;
|
|
25
|
+
dry_run: boolean;
|
|
26
|
+
submit: boolean;
|
|
27
|
+
assets: Array<MediaProviderAssetInput & {
|
|
28
|
+
sha256: string;
|
|
29
|
+
bytes: number;
|
|
30
|
+
}>;
|
|
31
|
+
execution_boundary: {
|
|
32
|
+
provider_calls_by_yam: false;
|
|
33
|
+
provider_claim_source: 'operator_asserted';
|
|
34
|
+
optional: true;
|
|
35
|
+
};
|
|
36
|
+
claims: {
|
|
37
|
+
asset_integrity: 'digest_bound';
|
|
38
|
+
provider_execution: 'operator_asserted';
|
|
39
|
+
visual_correctness: 'not_verified';
|
|
40
|
+
implementation_correctness: 'not_verified';
|
|
41
|
+
};
|
|
42
|
+
truth_status: 'partial';
|
|
43
|
+
digest: string;
|
|
44
|
+
}
|
|
45
|
+
export declare function createMediaProviderReceipt(input: {
|
|
46
|
+
root: string;
|
|
47
|
+
receipt_path: string;
|
|
48
|
+
demand_trigger: {
|
|
49
|
+
kind: 'media_generation' | 'media_transformation';
|
|
50
|
+
evidence: string;
|
|
51
|
+
};
|
|
52
|
+
provider: {
|
|
53
|
+
name: string;
|
|
54
|
+
model: string;
|
|
55
|
+
};
|
|
56
|
+
provider_calls: number;
|
|
57
|
+
provider_execution: boolean;
|
|
58
|
+
dry_run: boolean;
|
|
59
|
+
submit: boolean;
|
|
60
|
+
assets: MediaProviderAssetInput[];
|
|
61
|
+
}): Promise<{
|
|
62
|
+
schema: "yam.media-provider-receipt-write.v1";
|
|
63
|
+
receipt_path: string;
|
|
64
|
+
receipt: MediaProviderReceipt;
|
|
65
|
+
truth_status: "partial";
|
|
66
|
+
next_action: string;
|
|
67
|
+
}>;
|
|
68
|
+
export declare function verifyMediaProviderReceipt(input: {
|
|
69
|
+
root: string;
|
|
70
|
+
receipt_path: string;
|
|
71
|
+
}): Promise<{
|
|
72
|
+
schema: "yam.media-provider-receipt-verification.v1";
|
|
73
|
+
receipt_path: string;
|
|
74
|
+
valid: boolean;
|
|
75
|
+
errors: string[];
|
|
76
|
+
receipt_truth_status: "partial";
|
|
77
|
+
truth_status: "verified" | "blocked";
|
|
78
|
+
next_action: string;
|
|
79
|
+
}>;
|