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.
- package/CHANGELOG.md +51 -0
- package/COMMANDS.md +27 -1
- package/README.md +24 -2
- package/ROADMAP.md +42 -7
- package/dist/bin/yam.js +406 -23
- package/dist/lib/benchmark-promotion.d.ts +53 -0
- package/dist/lib/benchmark-promotion.js +204 -0
- package/dist/lib/bounded-input.d.ts +12 -0
- package/dist/lib/bounded-input.js +48 -0
- package/dist/lib/design-production.d.ts +306 -0
- package/dist/lib/design-production.js +1341 -0
- package/dist/lib/external-updates.d.ts +23 -0
- package/dist/lib/external-updates.js +344 -23
- package/dist/lib/gate-result.d.ts +44 -0
- package/dist/lib/gate-result.js +219 -0
- package/dist/lib/skill-installation.js +23 -5
- package/dist/lib/stable-order.d.ts +1 -0
- package/dist/lib/stable-order.js +5 -0
- package/dist/lib/trust-kernel.js +17 -1
- package/dist/lib/ueye-artifacts.js +3 -2
- package/package.json +6 -2
- package/references/ueye.md +14 -0
- package/skills/ueye/SKILL.md +11 -0
- package/src/bin/yam.ts +415 -19
- package/src/lib/benchmark-promotion.ts +249 -0
- package/src/lib/bounded-input.ts +59 -0
- package/src/lib/design-production.ts +1441 -0
- package/src/lib/external-updates.ts +397 -24
- package/src/lib/gate-result.ts +223 -0
- package/src/lib/skill-installation.ts +24 -5
- package/src/lib/stable-order.ts +4 -0
- package/src/lib/trust-kernel.ts +20 -1
- package/src/lib/ueye-artifacts.ts +3 -2
- package/yam.manifest.json +2 -2
|
@@ -70,6 +70,20 @@ export interface ExternalComponentReceipt {
|
|
|
70
70
|
persistence?: 'written' | 'failed';
|
|
71
71
|
receipt_path?: string;
|
|
72
72
|
receipt_error?: string;
|
|
73
|
+
install_identity?: YamInstallIdentity;
|
|
74
|
+
rollback_identity?: YamInstallIdentity;
|
|
75
|
+
}
|
|
76
|
+
export interface YamInstallIdentity {
|
|
77
|
+
expected_version: string;
|
|
78
|
+
observed_version: string;
|
|
79
|
+
first_on_path: string;
|
|
80
|
+
canonical_executable: string;
|
|
81
|
+
npm_global_root: string;
|
|
82
|
+
canonical_npm_global_root: string;
|
|
83
|
+
package_root: string;
|
|
84
|
+
package_manifest: string;
|
|
85
|
+
package_entrypoint: string;
|
|
86
|
+
canonical_entrypoint: string;
|
|
73
87
|
}
|
|
74
88
|
export declare function checkExternalUpdates(currentYamVersion: string, dependencies?: ExternalUpdateDependencies): Promise<{
|
|
75
89
|
schema: string;
|
|
@@ -94,8 +108,17 @@ export declare function applyExternalUpdates(currentYamVersion: string, selectio
|
|
|
94
108
|
stopped_early: boolean;
|
|
95
109
|
lock: {
|
|
96
110
|
path: string;
|
|
111
|
+
acquired: boolean;
|
|
97
112
|
released: boolean;
|
|
98
113
|
};
|
|
114
|
+
gate_result: import("./gate-result.js").StrictGateResult;
|
|
115
|
+
gate_contract: {
|
|
116
|
+
schema: "yam.gate-result-verification.v1";
|
|
117
|
+
valid: boolean;
|
|
118
|
+
errors: string[];
|
|
119
|
+
truth_status: "verified" | "blocked";
|
|
120
|
+
next_action: string;
|
|
121
|
+
};
|
|
99
122
|
success: boolean;
|
|
100
123
|
truth_status: string;
|
|
101
124
|
next_action: string;
|
|
@@ -3,6 +3,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
import fsp from 'node:fs/promises';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
+
import { buildStrictGateResult, verifyStrictGateResult } from './gate-result.js';
|
|
6
7
|
export const EXTERNAL_UPDATE_COMPONENTS = ['yam', 'scrapling', 'insane-search'];
|
|
7
8
|
const YAM_REGISTRY_URL = 'https://registry.npmjs.org/yam-flow/latest';
|
|
8
9
|
const SCRAPLING_PYPI_URL = 'https://pypi.org/pypi/scrapling/json';
|
|
@@ -50,6 +51,45 @@ export async function applyExternalUpdates(currentYamVersion, selection, depende
|
|
|
50
51
|
const receipts = [];
|
|
51
52
|
try {
|
|
52
53
|
lockHandle = await acquireApplyLock(deps);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
const lockError = boundedNote(errorMessage(error));
|
|
57
|
+
const nextAction = `confirm no updater is running, inspect the existing lock, then remove it manually only if stale: ${deps.paths.lockFile}`;
|
|
58
|
+
const gateResult = buildStrictGateResult({
|
|
59
|
+
gate_id: 'external-update-apply',
|
|
60
|
+
boundary: 'update',
|
|
61
|
+
checks: [{
|
|
62
|
+
id: 'update-lock-acquisition',
|
|
63
|
+
status: 'failed',
|
|
64
|
+
note: lockError,
|
|
65
|
+
required: true
|
|
66
|
+
}],
|
|
67
|
+
blockers: [`update_lock_not_acquired: ${lockError}`],
|
|
68
|
+
evidence: [`requested-components:${components.join(',')}`, `lock-path:${deps.paths.lockFile}`],
|
|
69
|
+
next_action: nextAction
|
|
70
|
+
});
|
|
71
|
+
const gateContract = verifyStrictGateResult(gateResult);
|
|
72
|
+
return {
|
|
73
|
+
schema: 'yam.external-update-apply.v1',
|
|
74
|
+
generated_at: deps.now().toISOString(),
|
|
75
|
+
mutation_authorized: true,
|
|
76
|
+
requested_components: components,
|
|
77
|
+
applied_components: [],
|
|
78
|
+
receipts: [],
|
|
79
|
+
stopped_early: true,
|
|
80
|
+
lock: {
|
|
81
|
+
path: deps.paths.lockFile,
|
|
82
|
+
acquired: false,
|
|
83
|
+
released: false
|
|
84
|
+
},
|
|
85
|
+
gate_result: gateResult,
|
|
86
|
+
gate_contract: gateContract,
|
|
87
|
+
success: false,
|
|
88
|
+
truth_status: 'blocked',
|
|
89
|
+
next_action: nextAction
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
53
93
|
for (const component of components) {
|
|
54
94
|
const receipt = component === 'yam'
|
|
55
95
|
? await applyYam(currentYamVersion, deps)
|
|
@@ -84,6 +124,53 @@ export async function applyExternalUpdates(currentYamVersion, selection, depende
|
|
|
84
124
|
|| receipt.outcome === 'manual_plugin_update_required'
|
|
85
125
|
|| receipt.persistence === 'failed'));
|
|
86
126
|
const incomplete = receipts.length !== components.length;
|
|
127
|
+
const operationSuccess = !failed && !incomplete && lockReleased;
|
|
128
|
+
const gateBlockers = receipts.flatMap((receipt) => receipt.checks
|
|
129
|
+
.filter((check) => check.status === 'failed')
|
|
130
|
+
.map((check) => `${receipt.component}.${check.id}: ${check.note}`));
|
|
131
|
+
for (const receipt of receipts) {
|
|
132
|
+
if (receipt.persistence === 'failed')
|
|
133
|
+
gateBlockers.push(`${receipt.component}.receipt_persistence: ${receipt.receipt_error || 'receipt was not persisted'}`);
|
|
134
|
+
if (receipt.outcome === 'manual_plugin_update_required')
|
|
135
|
+
gateBlockers.push(`${receipt.component}.manual_update_required: ${receipt.error || 'safe in-place plugin update unavailable'}`);
|
|
136
|
+
}
|
|
137
|
+
if (incomplete)
|
|
138
|
+
gateBlockers.push(`component_inventory_incomplete: applied ${receipts.length} of ${components.length}`);
|
|
139
|
+
if (!lockReleased)
|
|
140
|
+
gateBlockers.push(`update_lock_not_released: ${deps.paths.lockFile}`);
|
|
141
|
+
const gateResult = buildStrictGateResult({
|
|
142
|
+
gate_id: 'external-update-apply',
|
|
143
|
+
boundary: 'update',
|
|
144
|
+
checks: [
|
|
145
|
+
{
|
|
146
|
+
id: 'component-inventory',
|
|
147
|
+
status: incomplete ? 'failed' : 'passed',
|
|
148
|
+
note: incomplete ? `applied ${receipts.length} of ${components.length} requested components` : 'every requested component produced one outcome receipt',
|
|
149
|
+
required: true
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: 'component-outcomes',
|
|
153
|
+
status: failed ? 'failed' : 'passed',
|
|
154
|
+
note: failed ? firstFailureNextAction(receipts) : 'all component outcomes passed',
|
|
155
|
+
required: true
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: 'update-lock-release',
|
|
159
|
+
status: lockReleased ? 'passed' : 'failed',
|
|
160
|
+
note: lockReleased ? 'external update lock was removed' : `inspect the stale lock: ${deps.paths.lockFile}`,
|
|
161
|
+
required: true
|
|
162
|
+
}
|
|
163
|
+
],
|
|
164
|
+
blockers: gateBlockers,
|
|
165
|
+
evidence: receipts.map((receipt) => `${receipt.component}:${receipt.outcome}:${receipt.persistence || 'not-persisted'}:${receipt.receipt_path || 'no-path'}`),
|
|
166
|
+
next_action: failed
|
|
167
|
+
? firstFailureNextAction(receipts)
|
|
168
|
+
: !lockReleased
|
|
169
|
+
? `confirm no updater is running, inspect the stale lock, then remove it manually: ${deps.paths.lockFile}`
|
|
170
|
+
: 'review the component receipts and restart Codex only if yam skills or a Codex plugin changed'
|
|
171
|
+
});
|
|
172
|
+
const gateContract = verifyStrictGateResult(gateResult);
|
|
173
|
+
const success = operationSuccess && gateResult.status === 'passed' && gateContract.valid;
|
|
87
174
|
return {
|
|
88
175
|
schema: 'yam.external-update-apply.v1',
|
|
89
176
|
generated_at: deps.now().toISOString(),
|
|
@@ -94,10 +181,13 @@ export async function applyExternalUpdates(currentYamVersion, selection, depende
|
|
|
94
181
|
stopped_early: incomplete,
|
|
95
182
|
lock: {
|
|
96
183
|
path: deps.paths.lockFile,
|
|
184
|
+
acquired: true,
|
|
97
185
|
released: lockReleased
|
|
98
186
|
},
|
|
99
|
-
|
|
100
|
-
|
|
187
|
+
gate_result: gateResult,
|
|
188
|
+
gate_contract: gateContract,
|
|
189
|
+
success,
|
|
190
|
+
truth_status: success ? 'verified' : 'blocked',
|
|
101
191
|
next_action: failed
|
|
102
192
|
? firstFailureNextAction(receipts)
|
|
103
193
|
: !lockReleased
|
|
@@ -340,62 +430,203 @@ async function applyYam(currentYamVersion, deps) {
|
|
|
340
430
|
}), deps);
|
|
341
431
|
}
|
|
342
432
|
sideEffects.push(`globally installed ${exactPackage}`);
|
|
433
|
+
let installIdentity;
|
|
434
|
+
let rollbackIdentity;
|
|
435
|
+
const identityValidation = await validateEffectiveYamIdentity(check.latest_version, deps, 'yam_effective_identity');
|
|
436
|
+
checks.push(identityValidation.check);
|
|
437
|
+
if (identityValidation.passed)
|
|
438
|
+
installIdentity = identityValidation.identity;
|
|
343
439
|
const postCommands = [
|
|
344
440
|
['yam_install_skills', ['install']],
|
|
345
441
|
['yam_version', ['version']],
|
|
346
442
|
['yam_status', ['status']]
|
|
347
443
|
];
|
|
348
|
-
let postFailure =
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
444
|
+
let postFailure = identityValidation.passed
|
|
445
|
+
? ''
|
|
446
|
+
: `yam_effective_identity failed: ${identityValidation.error}`;
|
|
447
|
+
if (!postFailure && installIdentity) {
|
|
448
|
+
for (const [id, args] of postCommands) {
|
|
449
|
+
const result = await deps.run(installIdentity.first_on_path, args, { env: deps.env, timeout: 180000 });
|
|
450
|
+
checks.push(commandCheck(id, result));
|
|
451
|
+
if (!result.ok || (id === 'yam_version' && extractVersion(result.stdout) !== check.latest_version)) {
|
|
452
|
+
postFailure = `${id} failed: ${commandNote(result)}`;
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
355
455
|
}
|
|
356
456
|
}
|
|
457
|
+
if (!postFailure && installIdentity) {
|
|
458
|
+
const doctor = await deps.run(installIdentity.first_on_path, ['doctor', '--json'], { env: deps.env, timeout: 180000 });
|
|
459
|
+
const validation = doctorValidation('yam_finalize_doctor', doctor);
|
|
460
|
+
checks.push(validation.check);
|
|
461
|
+
if (!validation.passed)
|
|
462
|
+
postFailure = `yam_finalize_doctor failed: ${validation.error}`;
|
|
463
|
+
}
|
|
357
464
|
if (postFailure) {
|
|
358
465
|
const rollback = await rollbackYam(check.installed_version, deps, checks);
|
|
466
|
+
rollbackIdentity = rollback.identity;
|
|
359
467
|
return persistReceipt(baseReceipt(check, 'failed', checks, {
|
|
360
468
|
...yamRollback(check.installed_version),
|
|
361
|
-
automatic: rollback
|
|
469
|
+
automatic: rollback.ok
|
|
362
470
|
}, {
|
|
471
|
+
...(installIdentity ? { install_identity: installIdentity } : {}),
|
|
472
|
+
...(rollbackIdentity ? { rollback_identity: rollbackIdentity } : {}),
|
|
363
473
|
side_effects: sideEffects,
|
|
364
474
|
error: postFailure
|
|
365
475
|
}), deps);
|
|
366
476
|
}
|
|
367
477
|
const receipt = baseReceipt(check, 'updated', checks, yamRollback(check.installed_version), {
|
|
368
478
|
installed_version: check.latest_version,
|
|
479
|
+
...(installIdentity ? { install_identity: installIdentity } : {}),
|
|
369
480
|
side_effects: sideEffects,
|
|
370
481
|
truth_status: 'verified'
|
|
371
482
|
});
|
|
372
483
|
const persisted = await persistReceipt(receipt, deps);
|
|
373
484
|
if (persisted.persistence === 'failed') {
|
|
374
485
|
const rollback = await rollbackYam(check.installed_version, deps, persisted.checks);
|
|
375
|
-
persisted.rollback_hint.automatic = rollback;
|
|
486
|
+
persisted.rollback_hint.automatic = rollback.ok;
|
|
487
|
+
if (rollback.identity)
|
|
488
|
+
persisted.rollback_identity = rollback.identity;
|
|
376
489
|
persisted.outcome = 'failed';
|
|
377
490
|
persisted.truth_status = 'blocked';
|
|
378
|
-
persisted.error = `update completed but receipt persistence failed; automatic rollback ${rollback ? 'passed' : 'failed'}`;
|
|
491
|
+
persisted.error = `update completed but receipt persistence failed; automatic rollback ${rollback.ok ? 'passed' : 'failed'}`;
|
|
379
492
|
}
|
|
380
493
|
return persisted;
|
|
381
494
|
}
|
|
382
495
|
async function rollbackYam(previousVersion, deps, checks) {
|
|
383
496
|
if (!previousVersion)
|
|
384
|
-
return false;
|
|
497
|
+
return { ok: false };
|
|
385
498
|
const result = await deps.run('npm', ['install', '-g', `yam-flow@${previousVersion}`], {
|
|
386
499
|
env: deps.env,
|
|
387
500
|
timeout: 600000
|
|
388
501
|
});
|
|
389
502
|
checks.push(commandCheck('automatic_yam_rollback', result));
|
|
390
503
|
if (!result.ok)
|
|
391
|
-
return false;
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
504
|
+
return { ok: false };
|
|
505
|
+
const identityValidation = await validateEffectiveYamIdentity(previousVersion, deps, 'automatic_yam_rollback_identity');
|
|
506
|
+
checks.push(identityValidation.check);
|
|
507
|
+
if (!identityValidation.passed || !identityValidation.identity)
|
|
508
|
+
return { ok: false };
|
|
509
|
+
const executable = identityValidation.identity.first_on_path;
|
|
510
|
+
const install = await deps.run(executable, ['install'], { env: deps.env, timeout: 180000 });
|
|
511
|
+
const version = await deps.run(executable, ['version'], { env: deps.env, timeout: 30000 });
|
|
512
|
+
const status = await deps.run(executable, ['status'], { env: deps.env, timeout: 120000 });
|
|
513
|
+
const doctor = await deps.run(executable, ['doctor', '--json'], { env: deps.env, timeout: 180000 });
|
|
514
|
+
const doctorResult = doctorValidation('automatic_yam_rollback_doctor', doctor);
|
|
395
515
|
checks.push(commandCheck('automatic_yam_rollback_skills', install));
|
|
396
516
|
checks.push(commandCheck('automatic_yam_rollback_version', version));
|
|
397
517
|
checks.push(commandCheck('automatic_yam_rollback_status', status));
|
|
398
|
-
|
|
518
|
+
checks.push(doctorResult.check);
|
|
519
|
+
return {
|
|
520
|
+
ok: (install.ok
|
|
521
|
+
&& version.ok
|
|
522
|
+
&& extractVersion(version.stdout) === previousVersion
|
|
523
|
+
&& status.ok
|
|
524
|
+
&& doctorResult.passed),
|
|
525
|
+
identity: identityValidation.identity
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
async function validateEffectiveYamIdentity(expectedVersion, deps, id) {
|
|
529
|
+
try {
|
|
530
|
+
const npmRootResult = await deps.run('npm', ['root', '-g'], { env: deps.env, timeout: 30000 });
|
|
531
|
+
if (!npmRootResult.ok) {
|
|
532
|
+
throw new Error(`npm global root lookup failed: ${commandNote(npmRootResult)}`);
|
|
533
|
+
}
|
|
534
|
+
const npmGlobalRoot = firstOutputLine(npmRootResult.stdout);
|
|
535
|
+
if (!npmGlobalRoot || !path.isAbsolute(npmGlobalRoot)) {
|
|
536
|
+
throw new Error('npm global root lookup did not return one absolute path');
|
|
537
|
+
}
|
|
538
|
+
const packageRoot = path.join(path.resolve(npmGlobalRoot), 'yam-flow');
|
|
539
|
+
const packageManifest = path.join(packageRoot, 'package.json');
|
|
540
|
+
let manifest;
|
|
541
|
+
try {
|
|
542
|
+
const parsed = JSON.parse(await fsp.readFile(packageManifest, 'utf8'));
|
|
543
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
544
|
+
throw new Error('not an object');
|
|
545
|
+
manifest = parsed;
|
|
546
|
+
}
|
|
547
|
+
catch {
|
|
548
|
+
throw new Error(`yam-flow package manifest is missing or malformed: ${packageManifest}`);
|
|
549
|
+
}
|
|
550
|
+
if (manifest.name !== 'yam-flow') {
|
|
551
|
+
throw new Error(`npm global package manifest has unexpected name at ${packageManifest}`);
|
|
552
|
+
}
|
|
553
|
+
const manifestVersion = requireStableVersion(manifest.version, 'yam-flow package manifest version');
|
|
554
|
+
if (manifestVersion !== expectedVersion) {
|
|
555
|
+
throw new Error(`yam-flow package manifest version ${manifestVersion} does not match expected ${expectedVersion}`);
|
|
556
|
+
}
|
|
557
|
+
const binValue = typeof manifest.bin === 'string' ? manifest.bin : manifest.bin?.yam;
|
|
558
|
+
if (typeof binValue !== 'string' || !binValue.trim()) {
|
|
559
|
+
throw new Error(`yam-flow package manifest has no usable yam bin entry: ${packageManifest}`);
|
|
560
|
+
}
|
|
561
|
+
const packageEntrypoint = path.resolve(packageRoot, binValue);
|
|
562
|
+
assertChildPath(packageRoot, packageEntrypoint, 'yam-flow package entrypoint');
|
|
563
|
+
const whichResult = await deps.run('which', ['yam'], { env: deps.env, timeout: 10000 });
|
|
564
|
+
if (!whichResult.ok) {
|
|
565
|
+
throw new Error(`first yam on PATH could not be resolved: ${commandNote(whichResult)}`);
|
|
566
|
+
}
|
|
567
|
+
const firstOnPath = firstOutputLine(whichResult.stdout);
|
|
568
|
+
if (!firstOnPath || !path.isAbsolute(firstOnPath)) {
|
|
569
|
+
throw new Error('first yam on PATH was not reported as one absolute path');
|
|
570
|
+
}
|
|
571
|
+
let canonicalNpmGlobalRoot;
|
|
572
|
+
let canonicalPackageRoot;
|
|
573
|
+
let canonicalEntrypoint;
|
|
574
|
+
let canonicalExecutable;
|
|
575
|
+
try {
|
|
576
|
+
[canonicalNpmGlobalRoot, canonicalPackageRoot, canonicalEntrypoint, canonicalExecutable] = await Promise.all([
|
|
577
|
+
fsp.realpath(npmGlobalRoot),
|
|
578
|
+
fsp.realpath(packageRoot),
|
|
579
|
+
fsp.realpath(packageEntrypoint),
|
|
580
|
+
fsp.realpath(firstOnPath)
|
|
581
|
+
]);
|
|
582
|
+
}
|
|
583
|
+
catch {
|
|
584
|
+
throw new Error('yam install identity contains an unreadable package path, entrypoint, or PATH executable');
|
|
585
|
+
}
|
|
586
|
+
assertChildPath(canonicalNpmGlobalRoot, canonicalPackageRoot, 'canonical yam-flow package root');
|
|
587
|
+
assertChildPath(canonicalPackageRoot, canonicalEntrypoint, 'canonical yam-flow package entrypoint');
|
|
588
|
+
if (canonicalExecutable !== canonicalEntrypoint) {
|
|
589
|
+
throw new Error(`first yam on PATH is shadowed: ${firstOnPath} resolves to ${canonicalExecutable}, expected ${canonicalEntrypoint}; reorder PATH or remove the stale shim before retrying`);
|
|
590
|
+
}
|
|
591
|
+
const versionResult = await deps.run(firstOnPath, ['version'], { env: deps.env, timeout: 30000 });
|
|
592
|
+
if (!versionResult.ok) {
|
|
593
|
+
throw new Error(`effective yam version check failed: ${commandNote(versionResult)}`);
|
|
594
|
+
}
|
|
595
|
+
const observedVersion = requireStableVersion(extractVersion(versionResult.stdout), 'effective yam version');
|
|
596
|
+
if (observedVersion !== expectedVersion) {
|
|
597
|
+
throw new Error(`effective yam version ${observedVersion} does not match expected ${expectedVersion}`);
|
|
598
|
+
}
|
|
599
|
+
const identity = {
|
|
600
|
+
expected_version: expectedVersion,
|
|
601
|
+
observed_version: observedVersion,
|
|
602
|
+
first_on_path: firstOnPath,
|
|
603
|
+
canonical_executable: canonicalExecutable,
|
|
604
|
+
npm_global_root: path.resolve(npmGlobalRoot),
|
|
605
|
+
canonical_npm_global_root: canonicalNpmGlobalRoot,
|
|
606
|
+
package_root: packageRoot,
|
|
607
|
+
package_manifest: packageManifest,
|
|
608
|
+
package_entrypoint: packageEntrypoint,
|
|
609
|
+
canonical_entrypoint: canonicalEntrypoint
|
|
610
|
+
};
|
|
611
|
+
return {
|
|
612
|
+
passed: true,
|
|
613
|
+
error: '',
|
|
614
|
+
check: {
|
|
615
|
+
id,
|
|
616
|
+
status: 'passed',
|
|
617
|
+
note: boundedNote(`verified yam install identity: ${JSON.stringify(identity)}`)
|
|
618
|
+
},
|
|
619
|
+
identity
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
catch (error) {
|
|
623
|
+
const note = boundedNote(errorMessage(error));
|
|
624
|
+
return {
|
|
625
|
+
passed: false,
|
|
626
|
+
error: note,
|
|
627
|
+
check: { id, status: 'failed', note }
|
|
628
|
+
};
|
|
629
|
+
}
|
|
399
630
|
}
|
|
400
631
|
async function applyScrapling(deps) {
|
|
401
632
|
const check = await checkScrapling(deps);
|
|
@@ -820,7 +1051,7 @@ function yamRollback(previousVersion) {
|
|
|
820
1051
|
automatic: false,
|
|
821
1052
|
previous_target: previousVersion,
|
|
822
1053
|
guidance: previousVersion
|
|
823
|
-
? `Reinstall the exact prior package with \`npm install -g yam-flow@${previousVersion}\`, then run \`yam install\`, \`yam version\`, and \`yam
|
|
1054
|
+
? `Reinstall the exact prior package with \`npm install -g yam-flow@${previousVersion}\`, then run \`yam install\`, \`yam version\`, \`yam status\`, and \`yam doctor --json\`.`
|
|
824
1055
|
: 'Inspect the global npm installation before attempting a manual rollback.'
|
|
825
1056
|
};
|
|
826
1057
|
}
|
|
@@ -858,6 +1089,81 @@ function commandCheck(id, result) {
|
|
|
858
1089
|
note: commandNote(result)
|
|
859
1090
|
};
|
|
860
1091
|
}
|
|
1092
|
+
function doctorValidation(id, result) {
|
|
1093
|
+
if (!result.ok) {
|
|
1094
|
+
const note = boundedNote(`command failed: ${commandNote(result)}`);
|
|
1095
|
+
return {
|
|
1096
|
+
passed: false,
|
|
1097
|
+
error: note,
|
|
1098
|
+
check: { id, status: 'failed', note }
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
let report;
|
|
1102
|
+
try {
|
|
1103
|
+
const parsed = JSON.parse(String(result.stdout || ''));
|
|
1104
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
1105
|
+
throw new Error('not an object');
|
|
1106
|
+
}
|
|
1107
|
+
report = parsed;
|
|
1108
|
+
}
|
|
1109
|
+
catch {
|
|
1110
|
+
const note = 'yam doctor returned invalid JSON';
|
|
1111
|
+
return {
|
|
1112
|
+
passed: false,
|
|
1113
|
+
error: note,
|
|
1114
|
+
check: { id, status: 'failed', note }
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
if (report.schema !== 'yam.doctor.v1') {
|
|
1118
|
+
const note = 'yam doctor returned an unexpected schema';
|
|
1119
|
+
return {
|
|
1120
|
+
passed: false,
|
|
1121
|
+
error: note,
|
|
1122
|
+
check: { id, status: 'failed', note }
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
if (report.ok !== true) {
|
|
1126
|
+
const note = doctorFailureDiagnostic(report);
|
|
1127
|
+
return {
|
|
1128
|
+
passed: false,
|
|
1129
|
+
error: note,
|
|
1130
|
+
check: { id, status: 'failed', note }
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
const note = 'yam.doctor.v1 reported ok=true';
|
|
1134
|
+
return {
|
|
1135
|
+
passed: true,
|
|
1136
|
+
error: '',
|
|
1137
|
+
check: { id, status: 'passed', note }
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
function doctorFailureDiagnostic(report) {
|
|
1141
|
+
const detail = arrayValue(report.nextActionDetails).find((item) => (item
|
|
1142
|
+
&& typeof item === 'object'
|
|
1143
|
+
&& !Array.isArray(item)
|
|
1144
|
+
&& [item.reason, item.next_action, item.nextAction, item.command].some((value) => cleanDiagnosticValue(value))));
|
|
1145
|
+
const issue = cleanDiagnosticValue(detail?.reason) || firstDiagnosticValue(report.issues);
|
|
1146
|
+
const nextAction = cleanDiagnosticValue(detail?.next_action || detail?.nextAction)
|
|
1147
|
+
|| firstDiagnosticValue(report.nextActions);
|
|
1148
|
+
const command = cleanDiagnosticValue(detail?.command);
|
|
1149
|
+
const fragments = [
|
|
1150
|
+
issue ? `issue: ${issue}` : '',
|
|
1151
|
+
nextAction ? `next: ${nextAction}` : '',
|
|
1152
|
+
command ? `command: ${command}` : ''
|
|
1153
|
+
].filter(Boolean);
|
|
1154
|
+
return boundedNote(fragments.length
|
|
1155
|
+
? `yam.doctor.v1 reported ok=false; ${fragments.join('; ')}`
|
|
1156
|
+
: 'yam.doctor.v1 reported ok=false');
|
|
1157
|
+
}
|
|
1158
|
+
function firstDiagnosticValue(value) {
|
|
1159
|
+
return arrayValue(value).map(cleanDiagnosticValue).find(Boolean) || '';
|
|
1160
|
+
}
|
|
1161
|
+
function cleanDiagnosticValue(value) {
|
|
1162
|
+
return typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '';
|
|
1163
|
+
}
|
|
1164
|
+
function boundedNote(text) {
|
|
1165
|
+
return redactSensitiveText(String(text || '')).slice(0, 800);
|
|
1166
|
+
}
|
|
861
1167
|
function commandNote(result) {
|
|
862
1168
|
const text = redactSensitiveText([result.stdout, result.stderr].filter(Boolean).join('\n'));
|
|
863
1169
|
const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
@@ -901,6 +1207,9 @@ function requireStableVersion(value, label) {
|
|
|
901
1207
|
function extractVersion(text) {
|
|
902
1208
|
return String(text || '').match(/\b\d+(?:\.\d+){1,3}\b/)?.[0] || '';
|
|
903
1209
|
}
|
|
1210
|
+
function firstOutputLine(text) {
|
|
1211
|
+
return String(text || '').split(/\r?\n/).map((line) => line.trim()).find(Boolean) || '';
|
|
1212
|
+
}
|
|
904
1213
|
function compareVersions(left, right) {
|
|
905
1214
|
const a = left.split('.').map(Number);
|
|
906
1215
|
const b = right.split('.').map(Number);
|
|
@@ -972,6 +1281,7 @@ async function unlinkIfExists(file) {
|
|
|
972
1281
|
function redactSensitiveText(text) {
|
|
973
1282
|
return String(text || '')
|
|
974
1283
|
.replace(/(_authToken\s*=\s*)[^\s]+/gi, '$1[redacted]')
|
|
1284
|
+
.replace(/\b([A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|PASSWD|API_KEY))\s*[:=]\s*\S+/gi, '$1=[redacted]')
|
|
975
1285
|
.replace(/\b(password|passwd|api[_-]?key|secret|token)\s*[:=]\s*\S+/gi, '$1=[redacted]')
|
|
976
1286
|
.replace(/\/\/([^:\s]+):([^@\s]+)@/g, '//[redacted]@')
|
|
977
1287
|
.replace(/\b(npm_[A-Za-z0-9]{20,}|gh[pousr]_[A-Za-z0-9_]{20,}|sk-[A-Za-z0-9_-]{20,})\b/g, '[redacted-token]');
|
|
@@ -982,8 +1292,19 @@ function firstFailureNextAction(receipts) {
|
|
|
982
1292
|
|| item.persistence === 'failed'));
|
|
983
1293
|
if (!receipt)
|
|
984
1294
|
return 'inspect the incomplete component inventory before retrying';
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1295
|
+
const deepestFailedCheck = [...receipt.checks].reverse().find((check) => check.status === 'failed');
|
|
1296
|
+
const deepestCause = deepestFailedCheck
|
|
1297
|
+
? `${deepestFailedCheck.id} failed: ${deepestFailedCheck.note}`
|
|
1298
|
+
: receipt.persistence === 'failed' && receipt.receipt_error
|
|
1299
|
+
? `receipt persistence failed: ${receipt.receipt_error}`
|
|
1300
|
+
: receipt.error || `${receipt.component} update failed`;
|
|
1301
|
+
const persistenceContext = deepestFailedCheck && receipt.persistence === 'failed' && receipt.receipt_error
|
|
1302
|
+
? `; secondary receipt persistence failure: ${receipt.receipt_error}`
|
|
1303
|
+
: '';
|
|
1304
|
+
const cause = boundedNote(`${deepestCause}${persistenceContext}`).slice(0, 420);
|
|
1305
|
+
const recoveryGuidance = receipt.outcome === 'manual_plugin_update_required'
|
|
1306
|
+
? 'Update Insane Search through a reviewed official Codex plugin workflow; do not remove-first or edit the plugin cache directly.'
|
|
1307
|
+
: receipt.rollback_hint.guidance || `resolve the ${receipt.component} failure before retrying`;
|
|
1308
|
+
const recovery = boundedNote(recoveryGuidance).slice(0, 340);
|
|
1309
|
+
return boundedNote(`resolve ${receipt.component} failure: ${cause}. ${recovery}`);
|
|
989
1310
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type GateBoundary = 'release' | 'update' | 'mission' | 'generic';
|
|
2
|
+
export type GateCheckStatus = 'passed' | 'failed' | 'skipped';
|
|
3
|
+
export interface GateCheckInput {
|
|
4
|
+
id?: string;
|
|
5
|
+
status?: GateCheckStatus;
|
|
6
|
+
note?: string;
|
|
7
|
+
required?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface StrictGateInput {
|
|
10
|
+
gate_id?: string;
|
|
11
|
+
boundary?: GateBoundary;
|
|
12
|
+
checks?: GateCheckInput[];
|
|
13
|
+
blockers?: string[];
|
|
14
|
+
evidence?: string[];
|
|
15
|
+
next_action?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface StrictGateResult {
|
|
18
|
+
schema: 'yam.gate-result.v1';
|
|
19
|
+
generated_at: string;
|
|
20
|
+
gate_id: string;
|
|
21
|
+
boundary: GateBoundary;
|
|
22
|
+
status: 'passed' | 'failed';
|
|
23
|
+
checks: Array<{
|
|
24
|
+
id: string;
|
|
25
|
+
status: GateCheckStatus;
|
|
26
|
+
note: string;
|
|
27
|
+
required: boolean;
|
|
28
|
+
}>;
|
|
29
|
+
blockers: string[];
|
|
30
|
+
evidence: string[];
|
|
31
|
+
next_action: string;
|
|
32
|
+
contract_valid: boolean;
|
|
33
|
+
contract_errors: string[];
|
|
34
|
+
digest: string;
|
|
35
|
+
truth_status: 'verified' | 'blocked';
|
|
36
|
+
}
|
|
37
|
+
export declare function buildStrictGateResult(input?: StrictGateInput): StrictGateResult;
|
|
38
|
+
export declare function verifyStrictGateResult(value: unknown): {
|
|
39
|
+
schema: "yam.gate-result-verification.v1";
|
|
40
|
+
valid: boolean;
|
|
41
|
+
errors: string[];
|
|
42
|
+
truth_status: "verified" | "blocked";
|
|
43
|
+
next_action: string;
|
|
44
|
+
};
|