yam-flow 2.6.0 → 2.8.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/AGENTS.md +1 -0
- package/CHANGELOG.md +51 -0
- package/COMMANDS.md +72 -5
- package/README.md +49 -9
- package/ROADMAP.md +20 -7
- package/dist/bin/yam.js +616 -68
- package/dist/lib/external-updates.d.ts +9 -0
- package/dist/lib/external-updates.js +232 -4
- package/dist/lib/media-provider-receipt.d.ts +79 -0
- package/dist/lib/media-provider-receipt.js +498 -0
- package/dist/lib/next-step.d.ts +61 -0
- package/dist/lib/next-step.js +340 -0
- package/dist/lib/release-auth-readiness.d.ts +81 -0
- package/dist/lib/release-auth-readiness.js +106 -0
- package/dist/lib/release-registry-status.d.ts +18 -0
- package/dist/lib/release-registry-status.js +37 -0
- package/dist/lib/scout-receipt.d.ts +89 -0
- package/dist/lib/scout-receipt.js +542 -0
- package/dist/lib/skill-installation.d.ts +30 -0
- package/dist/lib/skill-installation.js +738 -110
- package/dist/lib/trust-kernel.d.ts +2 -0
- package/dist/lib/trust-kernel.js +21 -2
- package/dist/lib/verification-closure.d.ts +97 -0
- package/dist/lib/verification-closure.js +728 -0
- package/package.json +23 -3
- package/references/final-report.md +15 -1
- package/references/next-step.md +67 -0
- package/references/scout-wide-scan.md +16 -0
- package/references/study-note.md +4 -2
- package/references/tool-trust-layer.md +2 -0
- package/skills/deep/SKILL.md +2 -0
- package/skills/mission/SKILL.md +2 -0
- package/skills/quick/SKILL.md +1 -0
- package/skills/scout/SKILL.md +19 -9
- package/skills/ueye/SKILL.md +1 -0
- package/src/bin/yam.ts +615 -64
- package/src/lib/external-updates.ts +262 -4
- package/src/lib/media-provider-receipt.ts +548 -0
- package/src/lib/next-step.ts +397 -0
- package/src/lib/release-auth-readiness.ts +148 -0
- package/src/lib/release-registry-status.ts +55 -0
- package/src/lib/scout-receipt.ts +593 -0
- package/src/lib/skill-installation.ts +930 -108
- package/src/lib/trust-kernel.ts +25 -2
- package/src/lib/verification-closure.ts +738 -0
- package/yam.manifest.json +4 -3
|
@@ -41,6 +41,14 @@ export interface ExternalComponentCheck {
|
|
|
41
41
|
drift: boolean;
|
|
42
42
|
note: string;
|
|
43
43
|
};
|
|
44
|
+
source_receipt?: {
|
|
45
|
+
canonical_url: string;
|
|
46
|
+
retrieved_at: string;
|
|
47
|
+
access_path: string;
|
|
48
|
+
version: string;
|
|
49
|
+
revision: string;
|
|
50
|
+
integrity: string[];
|
|
51
|
+
};
|
|
44
52
|
executable?: string;
|
|
45
53
|
error?: string;
|
|
46
54
|
}
|
|
@@ -54,6 +62,7 @@ export interface ExternalComponentReceipt {
|
|
|
54
62
|
installed_version: string;
|
|
55
63
|
source: string;
|
|
56
64
|
source_revision: JsonRecord | null;
|
|
65
|
+
source_receipt: JsonRecord | null;
|
|
57
66
|
checks: Array<{
|
|
58
67
|
id: string;
|
|
59
68
|
status: 'passed' | 'failed' | 'skipped';
|
|
@@ -6,7 +6,9 @@ import path from 'node:path';
|
|
|
6
6
|
import { buildStrictGateResult, verifyStrictGateResult } from './gate-result.js';
|
|
7
7
|
export const EXTERNAL_UPDATE_COMPONENTS = ['yam', 'scrapling', 'insane-search'];
|
|
8
8
|
const YAM_REGISTRY_URL = 'https://registry.npmjs.org/yam-flow/latest';
|
|
9
|
+
const NPM_REGISTRY = 'https://registry.npmjs.org/';
|
|
9
10
|
const SCRAPLING_PYPI_URL = 'https://pypi.org/pypi/scrapling/json';
|
|
11
|
+
const SCRAPLING_PYPI_INDEX = 'https://pypi.org/simple';
|
|
10
12
|
const INSANE_MARKETPLACE = 'gptaku-codex';
|
|
11
13
|
const INSANE_PLUGIN_ID = 'insane-search-codex@gptaku-codex';
|
|
12
14
|
const INSANE_MARKETPLACE_SOURCE = 'https://github.com/fivetaku/gptaku-plugins-codex.git';
|
|
@@ -269,7 +271,19 @@ async function checkYam(currentYamVersion, deps) {
|
|
|
269
271
|
const data = await deps.fetchJson(YAM_REGISTRY_URL);
|
|
270
272
|
const latest = requireStableVersion(data?.version, 'yam registry version');
|
|
271
273
|
const installed = requireStableVersion(currentYamVersion, 'installed yam version');
|
|
272
|
-
|
|
274
|
+
const integrity = requireIntegrityValue(data?.dist?.integrity, 'yam registry integrity');
|
|
275
|
+
const revision = requireGitRevision(data?.gitHead, 'yam registry gitHead');
|
|
276
|
+
return {
|
|
277
|
+
...componentCheck('yam', installed, latest, 'npm:yam-flow'),
|
|
278
|
+
source_receipt: {
|
|
279
|
+
canonical_url: YAM_REGISTRY_URL,
|
|
280
|
+
retrieved_at: deps.now().toISOString(),
|
|
281
|
+
access_path: 'npm registry latest endpoint',
|
|
282
|
+
version: latest,
|
|
283
|
+
revision,
|
|
284
|
+
integrity: [integrity]
|
|
285
|
+
}
|
|
286
|
+
};
|
|
273
287
|
}
|
|
274
288
|
catch (error) {
|
|
275
289
|
return failedCheck('yam', currentYamVersion, 'npm:yam-flow', error);
|
|
@@ -279,6 +293,7 @@ async function checkScrapling(deps) {
|
|
|
279
293
|
try {
|
|
280
294
|
const data = await deps.fetchJson(SCRAPLING_PYPI_URL);
|
|
281
295
|
const latest = requireStableVersion(data?.info?.version, 'Scrapling PyPI version');
|
|
296
|
+
const releaseIntegrity = pypiReleaseIntegrity(data?.urls, latest);
|
|
282
297
|
const executable = deps.paths.scraplingBin;
|
|
283
298
|
const versionResult = await deps.run(executable, ['--version'], { env: deps.env, timeout: 30000 });
|
|
284
299
|
if (!versionResult.ok) {
|
|
@@ -289,12 +304,28 @@ async function checkScrapling(deps) {
|
|
|
289
304
|
update_available: true,
|
|
290
305
|
status: 'not_installed',
|
|
291
306
|
source: 'pypi:scrapling',
|
|
307
|
+
source_receipt: {
|
|
308
|
+
canonical_url: SCRAPLING_PYPI_URL,
|
|
309
|
+
retrieved_at: deps.now().toISOString(),
|
|
310
|
+
access_path: 'PyPI project JSON endpoint',
|
|
311
|
+
version: latest,
|
|
312
|
+
revision: '',
|
|
313
|
+
integrity: releaseIntegrity
|
|
314
|
+
},
|
|
292
315
|
executable
|
|
293
316
|
};
|
|
294
317
|
}
|
|
295
318
|
const installed = requireStableVersion(extractVersion(versionResult.stdout), 'installed Scrapling version');
|
|
296
319
|
return {
|
|
297
320
|
...componentCheck('scrapling', installed, latest, 'pypi:scrapling'),
|
|
321
|
+
source_receipt: {
|
|
322
|
+
canonical_url: SCRAPLING_PYPI_URL,
|
|
323
|
+
retrieved_at: deps.now().toISOString(),
|
|
324
|
+
access_path: 'PyPI project JSON endpoint',
|
|
325
|
+
version: latest,
|
|
326
|
+
revision: '',
|
|
327
|
+
integrity: releaseIntegrity
|
|
328
|
+
},
|
|
298
329
|
executable
|
|
299
330
|
};
|
|
300
331
|
}
|
|
@@ -376,6 +407,14 @@ async function checkInsaneSearch(deps) {
|
|
|
376
407
|
note: installed && compareVersions(installed, latest) === 0
|
|
377
408
|
? 'marketplace revision drift alone is not treated as an Insane Search update'
|
|
378
409
|
: 'plugin update availability is based on the official plugin manifest version'
|
|
410
|
+
},
|
|
411
|
+
source_receipt: {
|
|
412
|
+
canonical_url: manifestUrl,
|
|
413
|
+
retrieved_at: deps.now().toISOString(),
|
|
414
|
+
access_path: 'Git-pinned raw marketplace manifest',
|
|
415
|
+
version: latest,
|
|
416
|
+
revision: remoteRevision,
|
|
417
|
+
integrity: [`git_commit:${remoteRevision.toLowerCase()}`]
|
|
379
418
|
}
|
|
380
419
|
};
|
|
381
420
|
}
|
|
@@ -419,7 +458,13 @@ async function applyYam(currentYamVersion, deps) {
|
|
|
419
458
|
const checks = [];
|
|
420
459
|
const sideEffects = [];
|
|
421
460
|
const exactPackage = `yam-flow@${check.latest_version}`;
|
|
422
|
-
const install = await deps.run('npm', [
|
|
461
|
+
const install = await deps.run('npm', [
|
|
462
|
+
'install',
|
|
463
|
+
'-g',
|
|
464
|
+
exactPackage,
|
|
465
|
+
'--registry',
|
|
466
|
+
NPM_REGISTRY
|
|
467
|
+
], {
|
|
423
468
|
env: deps.env,
|
|
424
469
|
timeout: 600000
|
|
425
470
|
});
|
|
@@ -495,7 +540,13 @@ async function applyYam(currentYamVersion, deps) {
|
|
|
495
540
|
async function rollbackYam(previousVersion, deps, checks) {
|
|
496
541
|
if (!previousVersion)
|
|
497
542
|
return { ok: false };
|
|
498
|
-
const result = await deps.run('npm', [
|
|
543
|
+
const result = await deps.run('npm', [
|
|
544
|
+
'install',
|
|
545
|
+
'-g',
|
|
546
|
+
`yam-flow@${previousVersion}`,
|
|
547
|
+
'--registry',
|
|
548
|
+
NPM_REGISTRY
|
|
549
|
+
], {
|
|
499
550
|
env: deps.env,
|
|
500
551
|
timeout: 600000
|
|
501
552
|
});
|
|
@@ -661,7 +712,15 @@ async function applyScrapling(deps) {
|
|
|
661
712
|
const candidatePython = path.join(candidate, 'bin', 'python');
|
|
662
713
|
const candidateScrapling = path.join(candidate, 'bin', 'scrapling');
|
|
663
714
|
const packageSpec = `scrapling[fetchers]==${check.latest_version}`;
|
|
664
|
-
const install = await deps.run(candidatePython, [
|
|
715
|
+
const install = await deps.run(candidatePython, [
|
|
716
|
+
'-m',
|
|
717
|
+
'pip',
|
|
718
|
+
'install',
|
|
719
|
+
'--isolated',
|
|
720
|
+
'--index-url',
|
|
721
|
+
SCRAPLING_PYPI_INDEX,
|
|
722
|
+
packageSpec
|
|
723
|
+
], {
|
|
665
724
|
env: deps.env,
|
|
666
725
|
timeout: 600000
|
|
667
726
|
});
|
|
@@ -797,6 +856,15 @@ async function applyInsaneSearch(deps) {
|
|
|
797
856
|
error: `marketplace upgrade failed: ${commandNote(upgrade)}`
|
|
798
857
|
}), deps);
|
|
799
858
|
}
|
|
859
|
+
const pinnedSource = await verifyUpgradedInsaneSource(check, deps, 'pre_add_source_revision');
|
|
860
|
+
checks.push(pinnedSource.check);
|
|
861
|
+
if (!pinnedSource.ok) {
|
|
862
|
+
return persistReceipt(baseReceipt(check, 'failed', checks, insaneRollback(check.installed_version), {
|
|
863
|
+
installed_version: check.installed_version,
|
|
864
|
+
side_effects: ['The official Codex marketplace snapshot was upgraded; plugin add was blocked because the post-upgrade source did not match the checked revision.'],
|
|
865
|
+
error: pinnedSource.error
|
|
866
|
+
}), deps);
|
|
867
|
+
}
|
|
800
868
|
let observation = await installedInsaneVersion(deps, checks, 'plugin_list_after_marketplace_upgrade');
|
|
801
869
|
if (!observation.ok) {
|
|
802
870
|
return persistReceipt(baseReceipt(check, 'failed', checks, insaneRollback(check.installed_version), {
|
|
@@ -836,6 +904,20 @@ async function applyInsaneSearch(deps) {
|
|
|
836
904
|
error: `official CLI finished without installing expected version ${check.latest_version}`
|
|
837
905
|
}), deps);
|
|
838
906
|
}
|
|
907
|
+
const finalPinnedSource = await verifyUpgradedInsaneSource(check, deps, 'final_source_revision');
|
|
908
|
+
checks.push(finalPinnedSource.check);
|
|
909
|
+
if (!finalPinnedSource.ok) {
|
|
910
|
+
return persistReceipt(baseReceipt(check, 'failed', checks, insaneRollback(check.installed_version), {
|
|
911
|
+
installed_version: installedAfter,
|
|
912
|
+
side_effects: [
|
|
913
|
+
`upgraded official marketplace ${INSANE_MARKETPLACE}`,
|
|
914
|
+
installedAfter === check.latest_version
|
|
915
|
+
? `the plugin reached ${installedAfter}, but final source provenance changed and completion was blocked`
|
|
916
|
+
: 'plugin state did not reach the checked version'
|
|
917
|
+
],
|
|
918
|
+
error: finalPinnedSource.error
|
|
919
|
+
}), deps);
|
|
920
|
+
}
|
|
839
921
|
return persistReceipt(baseReceipt(check, 'updated', checks, insaneRollback(check.installed_version), {
|
|
840
922
|
installed_version: installedAfter,
|
|
841
923
|
side_effects: [
|
|
@@ -845,6 +927,114 @@ async function applyInsaneSearch(deps) {
|
|
|
845
927
|
truth_status: 'verified'
|
|
846
928
|
}), deps);
|
|
847
929
|
}
|
|
930
|
+
async function verifyUpgradedInsaneSource(check, deps, id) {
|
|
931
|
+
try {
|
|
932
|
+
const expectedRevision = requireGitRevision(check.source_receipt?.revision, 'checked Insane Search source revision');
|
|
933
|
+
const marketplaceList = await deps.run('codex', ['plugin', 'marketplace', 'list', '--json'], {
|
|
934
|
+
env: deps.env,
|
|
935
|
+
timeout: 30000
|
|
936
|
+
});
|
|
937
|
+
if (!marketplaceList.ok) {
|
|
938
|
+
throw new Error(`post-upgrade marketplace state could not be observed: ${commandNote(marketplaceList)}`);
|
|
939
|
+
}
|
|
940
|
+
const parsedMarketplaces = parseJsonOutput(marketplaceList.stdout, 'post-upgrade codex plugin marketplace list');
|
|
941
|
+
const marketplaceEntry = arrayValue(parsedMarketplaces?.marketplaces).find((item) => item?.name === INSANE_MARKETPLACE);
|
|
942
|
+
if (!marketplaceEntry)
|
|
943
|
+
throw new Error(`post-upgrade marketplace is missing: ${INSANE_MARKETPLACE}`);
|
|
944
|
+
const configuredSource = String(marketplaceEntry?.marketplaceSource?.source || '');
|
|
945
|
+
if (marketplaceEntry?.marketplaceSource?.sourceType !== 'git'
|
|
946
|
+
|| configuredSource !== INSANE_MARKETPLACE_SOURCE) {
|
|
947
|
+
throw new Error(`refusing unexpected post-upgrade ${INSANE_MARKETPLACE} marketplace source`);
|
|
948
|
+
}
|
|
949
|
+
const marketplaceRoot = String(marketplaceEntry.root || '');
|
|
950
|
+
if (!marketplaceRoot || !isSafeMarketplaceRoot(marketplaceRoot, deps.homeDir)) {
|
|
951
|
+
throw new Error(`refusing unsafe post-upgrade ${INSANE_MARKETPLACE} marketplace root`);
|
|
952
|
+
}
|
|
953
|
+
const localRevisionResult = await deps.run('git', ['-C', marketplaceRoot, 'rev-parse', 'HEAD'], {
|
|
954
|
+
env: deps.env,
|
|
955
|
+
timeout: 30000
|
|
956
|
+
});
|
|
957
|
+
if (!localRevisionResult.ok) {
|
|
958
|
+
throw new Error(`post-upgrade marketplace revision could not be read: ${commandNote(localRevisionResult)}`);
|
|
959
|
+
}
|
|
960
|
+
const actualRevision = requireGitRevision(localRevisionResult.stdout.trim(), 'post-upgrade Insane Search marketplace revision');
|
|
961
|
+
const statusResult = await deps.run('git', [
|
|
962
|
+
'-C',
|
|
963
|
+
marketplaceRoot,
|
|
964
|
+
'status',
|
|
965
|
+
'--porcelain=v1',
|
|
966
|
+
'--untracked-files=all',
|
|
967
|
+
'--ignored=matching'
|
|
968
|
+
], { env: deps.env, timeout: 30000 });
|
|
969
|
+
if (!statusResult.ok) {
|
|
970
|
+
throw new Error(`post-upgrade marketplace cleanliness could not be observed: ${commandNote(statusResult)}`);
|
|
971
|
+
}
|
|
972
|
+
if (statusResult.stdout.trim()) {
|
|
973
|
+
throw new Error('post-upgrade Insane Search marketplace working tree is dirty; refusing to bind plugin install to a clean commit receipt');
|
|
974
|
+
}
|
|
975
|
+
const trackedManifest = await deps.run('git', [
|
|
976
|
+
'-C',
|
|
977
|
+
marketplaceRoot,
|
|
978
|
+
'ls-files',
|
|
979
|
+
'--error-unmatch',
|
|
980
|
+
'--',
|
|
981
|
+
INSANE_MANIFEST_PATH
|
|
982
|
+
], { env: deps.env, timeout: 30000 });
|
|
983
|
+
if (!trackedManifest.ok) {
|
|
984
|
+
throw new Error(`local Insane Search manifest is not tracked at the checked commit: ${commandNote(trackedManifest)}`);
|
|
985
|
+
}
|
|
986
|
+
const localManifestPath = path.join(marketplaceRoot, INSANE_MANIFEST_PATH);
|
|
987
|
+
assertChildPath(marketplaceRoot, localManifestPath, 'local Insane Search manifest');
|
|
988
|
+
const localManifestStat = await fsp.lstat(localManifestPath);
|
|
989
|
+
if (!localManifestStat.isFile() || localManifestStat.isSymbolicLink() || localManifestStat.size > 1024 * 1024) {
|
|
990
|
+
throw new Error('local Insane Search manifest must be a regular tracked file no larger than 1 MiB');
|
|
991
|
+
}
|
|
992
|
+
const localManifestBytes = await fsp.readFile(localManifestPath, 'utf8');
|
|
993
|
+
const committedManifest = await deps.run('git', [
|
|
994
|
+
'-C',
|
|
995
|
+
marketplaceRoot,
|
|
996
|
+
'show',
|
|
997
|
+
`${actualRevision}:${INSANE_MANIFEST_PATH}`
|
|
998
|
+
], { env: deps.env, timeout: 30000 });
|
|
999
|
+
if (!committedManifest.ok) {
|
|
1000
|
+
throw new Error(`checked Insane Search manifest bytes could not be read from Git: ${commandNote(committedManifest)}`);
|
|
1001
|
+
}
|
|
1002
|
+
if (localManifestBytes !== committedManifest.stdout) {
|
|
1003
|
+
throw new Error('local Insane Search manifest bytes do not match the checked Git commit');
|
|
1004
|
+
}
|
|
1005
|
+
const localManifest = parseJsonOutput(localManifestBytes, 'local Insane Search manifest');
|
|
1006
|
+
const localVersion = requireStableVersion(localManifest?.version, 'local Insane Search manifest version');
|
|
1007
|
+
const manifestUrl = `${INSANE_MANIFEST_ROOT}/${actualRevision}/${INSANE_MANIFEST_PATH}`;
|
|
1008
|
+
const manifest = await deps.fetchJson(manifestUrl);
|
|
1009
|
+
const actualVersion = requireStableVersion(manifest?.version, 'post-upgrade Insane Search manifest version');
|
|
1010
|
+
if (actualRevision !== expectedRevision) {
|
|
1011
|
+
throw new Error(`Insane Search source changed between check and upgrade: checked ${expectedRevision}, observed ${actualRevision}; rerun the read-only check before retrying`);
|
|
1012
|
+
}
|
|
1013
|
+
if (actualVersion !== check.latest_version) {
|
|
1014
|
+
throw new Error(`post-upgrade Insane Search manifest version ${actualVersion} does not match checked ${check.latest_version}`);
|
|
1015
|
+
}
|
|
1016
|
+
if (localVersion !== actualVersion) {
|
|
1017
|
+
throw new Error(`local Insane Search manifest version ${localVersion} does not match pinned source version ${actualVersion}`);
|
|
1018
|
+
}
|
|
1019
|
+
return {
|
|
1020
|
+
ok: true,
|
|
1021
|
+
error: '',
|
|
1022
|
+
check: {
|
|
1023
|
+
id,
|
|
1024
|
+
status: 'passed',
|
|
1025
|
+
note: `clean tracked marketplace remained pinned to ${actualRevision} with local and remote manifest version ${actualVersion}`
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
catch (error) {
|
|
1030
|
+
const note = boundedNote(errorMessage(error));
|
|
1031
|
+
return {
|
|
1032
|
+
ok: false,
|
|
1033
|
+
error: note,
|
|
1034
|
+
check: { id, status: 'failed', note }
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
848
1038
|
async function installedInsaneVersion(deps, checks, id) {
|
|
849
1039
|
const result = await deps.run('codex', ['plugin', 'list', '--json'], { env: deps.env, timeout: 30000 });
|
|
850
1040
|
checks.push(commandCheck(id, result));
|
|
@@ -883,6 +1073,7 @@ function baseReceipt(check, outcome, checks, rollbackHint, overrides = {}) {
|
|
|
883
1073
|
installed_version: check.installed_version,
|
|
884
1074
|
source: check.source,
|
|
885
1075
|
source_revision: receiptSourceRevision(check),
|
|
1076
|
+
source_receipt: check.source_receipt || null,
|
|
886
1077
|
checks,
|
|
887
1078
|
rollback_hint: rollbackHint,
|
|
888
1079
|
side_effects: [],
|
|
@@ -1204,6 +1395,43 @@ function requireStableVersion(value, label) {
|
|
|
1204
1395
|
}
|
|
1205
1396
|
return version;
|
|
1206
1397
|
}
|
|
1398
|
+
function requireIntegrityValue(value, label) {
|
|
1399
|
+
const integrity = String(value || '').trim();
|
|
1400
|
+
if (!/^sha(?:256|384|512)-[A-Za-z0-9+/=]+$/.test(integrity)) {
|
|
1401
|
+
throw new Error(`${label} is missing or malformed`);
|
|
1402
|
+
}
|
|
1403
|
+
return integrity;
|
|
1404
|
+
}
|
|
1405
|
+
function requireGitRevision(value, label) {
|
|
1406
|
+
const revision = String(value || '').trim().toLowerCase();
|
|
1407
|
+
if (!/^[a-f0-9]{40}$/.test(revision))
|
|
1408
|
+
throw new Error(`${label} is not a 40-character Git revision`);
|
|
1409
|
+
return revision;
|
|
1410
|
+
}
|
|
1411
|
+
function pypiReleaseIntegrity(value, version) {
|
|
1412
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
1413
|
+
throw new Error(`Scrapling PyPI ${version} has no SHA-256 release artifact inventory`);
|
|
1414
|
+
}
|
|
1415
|
+
if (value.length > 64) {
|
|
1416
|
+
throw new Error(`Scrapling PyPI ${version} release artifact inventory exceeds the 64-item provenance limit`);
|
|
1417
|
+
}
|
|
1418
|
+
const artifacts = value.map((item, index) => {
|
|
1419
|
+
const filename = typeof item?.filename === 'string' ? item.filename.trim() : '';
|
|
1420
|
+
const rawSha256 = item?.digests?.sha256;
|
|
1421
|
+
const sha256 = typeof rawSha256 === 'string' ? rawSha256.trim().toLowerCase() : '';
|
|
1422
|
+
if (!filename || filename.length > 512 || /[\r\n\u0000-\u001f\u007f]/.test(filename)) {
|
|
1423
|
+
throw new Error(`Scrapling PyPI ${version} release artifact ${index} has a malformed filename`);
|
|
1424
|
+
}
|
|
1425
|
+
if (!/^[a-f0-9]{64}$/.test(sha256)) {
|
|
1426
|
+
throw new Error(`Scrapling PyPI ${version} release artifact ${index} has a missing or malformed SHA-256 digest`);
|
|
1427
|
+
}
|
|
1428
|
+
return `sha256:${sha256} filename:${filename}`;
|
|
1429
|
+
}).sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
|
|
1430
|
+
if (new Set(artifacts).size !== artifacts.length) {
|
|
1431
|
+
throw new Error(`Scrapling PyPI ${version} release artifact inventory contains duplicates`);
|
|
1432
|
+
}
|
|
1433
|
+
return artifacts;
|
|
1434
|
+
}
|
|
1207
1435
|
function extractVersion(text) {
|
|
1208
1436
|
return String(text || '').match(/\b\d+(?:\.\d+){1,3}\b/)?.[0] || '';
|
|
1209
1437
|
}
|
|
@@ -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
|
+
}>;
|