@volter/twin-catalog 0.2.33 → 0.2.34
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/catalog.json +1 -1
- package/docs/process.md +5 -1
- package/lib/browse.d.mts +6 -0
- package/lib/browse.mjs +1 -0
- package/lib/runner.mjs +14 -8
- package/package.json +2 -2
- package/policy.json +4 -4
- package/runner/prepare.mjs +11 -9
package/catalog.json
CHANGED
package/docs/process.md
CHANGED
|
@@ -47,7 +47,7 @@ does not change that pin. Removing a recommendation is not uninstalling a runnin
|
|
|
47
47
|
Each assessed release exposes its artifact's description, license, Node requirements, kernel peer requirements,
|
|
48
48
|
source directory and issue tracker beside its integrity, provenance and measured behavior. README and license links
|
|
49
49
|
point to the source commit verified by npm provenance. They describe that version, not the publisher's current branch.
|
|
50
|
-
The JSON-only reader also projects installed customer scope, pinned SDK dependencies, actual CLI/kernel/runtime versions, clock conditions, retention and teardown when the bound report measured them. An older report exposes `consumer: null`;
|
|
50
|
+
The JSON-only reader also projects installed customer scope, pinned SDK dependencies, actual CLI/kernel/runtime versions, app-local dependency identity, clock conditions, retention and teardown when the bound report measured them. An older report without a customer workflow exposes `consumer: null`; one without app-local dependency evidence exposes `application: null`. Neither acquires newer installation evidence from publisher trust or a later test.
|
|
51
51
|
|
|
52
52
|
Historical assessments without these retained fields show them as unavailable; current registry metadata does not
|
|
53
53
|
fill missing historical evidence. A provenance link establishes source and build identity, not vendor fidelity.
|
|
@@ -120,6 +120,10 @@ never the contributor's head. Source-registration and maintenance PRs do not exe
|
|
|
120
120
|
extraction, dependency installation and evaluation occur in disposable containers with no repository, npm, cloud,
|
|
121
121
|
moderation or publication credentials, no Docker socket and no host project mounts. Install scripts are disabled.
|
|
122
122
|
Dependencies are resolved once into a recorded lock; evaluation reuses that installation with network disabled.
|
|
123
|
+
The customer app has its own candidate, CLI-matching kernel and SDK installation in a separate owned volume,
|
|
124
|
+
outside the evaluator's dependency tree. Both volumes survive preparation and offline evaluation; temporary
|
|
125
|
+
container files do not hold prepared inputs. The controller retains both dependency locks and verifies cleanup
|
|
126
|
+
of every volume it created. This proves an app-local installation rather than ancestor dependency resolution.
|
|
123
127
|
Before dependency installation, preparation confirms the candidate's exact version and integrity in npm's
|
|
124
128
|
installation metadata as well as its exact-version metadata. These registry views may propagate separately.
|
|
125
129
|
Tarball downloads also confirm availability and integrity before extraction. Only HTTP 404 or an otherwise valid package document without that version is retried, using the existing measured
|
package/lib/browse.d.mts
CHANGED
|
@@ -14,6 +14,12 @@ export type Measurements = {
|
|
|
14
14
|
fixture: string | null;
|
|
15
15
|
sdkDependencies: Record<string, string> | null;
|
|
16
16
|
tools: Record<string, string> | null;
|
|
17
|
+
application: null | {
|
|
18
|
+
appLocal: boolean;
|
|
19
|
+
candidate: { package: string; version: string; integrity: string };
|
|
20
|
+
kernel: { package: string; version: string };
|
|
21
|
+
dependencyLockSha256: string;
|
|
22
|
+
};
|
|
17
23
|
clock: Record<string, unknown> | null;
|
|
18
24
|
retainedReadback: boolean | null;
|
|
19
25
|
retentionNotApplicable: string | null;
|
package/lib/browse.mjs
CHANGED
|
@@ -135,6 +135,7 @@ export function browse(root, { vendor, package: packageName, integrity = null }
|
|
|
135
135
|
scope: bound.report.consumer.scope ?? null, fixture: bound.report.consumer.fixture ?? null,
|
|
136
136
|
sdkDependencies: bound.envelope.prepared?.consumerFixture?.dependencies ?? null,
|
|
137
137
|
tools: bound.report.consumer.tools ?? null, clock: bound.report.consumer.clock ?? null,
|
|
138
|
+
application: bound.report.consumer.application ?? null,
|
|
138
139
|
retainedReadback: bound.report.consumer.retainedReadback ?? null,
|
|
139
140
|
retentionNotApplicable: bound.report.consumer.retentionNotApplicable ?? null,
|
|
140
141
|
teardownVerified: bound.report.consumer.teardownVerified ?? null,
|
package/lib/runner.mjs
CHANGED
|
@@ -22,23 +22,28 @@ export function withContainer(root, input, output, execute, { spawn = spawnSync
|
|
|
22
22
|
if (!allowFailure) requireThat(r.status === 0, `container ${args[0]} failed: ${r.error?.message ?? r.stderr}`);
|
|
23
23
|
return r;
|
|
24
24
|
};
|
|
25
|
-
|
|
25
|
+
const volumes = [];
|
|
26
|
+
const customerVolume = `${id}-customer`;
|
|
26
27
|
try {
|
|
27
28
|
const tools = input.policy.tools;
|
|
28
29
|
requireThat(tools['@volter/world'], 'policy must pin the product CLI for installed customer workflows');
|
|
29
30
|
run(['build', '--iidfile', imageFile, '--build-arg', `NODE_IMAGE=${input.policy.nodeImage}`, '--build-arg', `BUN_VERSION=${input.policy.bun}`, '--build-arg', `STANDARD_VERSION=${tools['@volter/twin-standard']}`, '--build-arg', `CORE_VERSION=${tools['@volter/world-core']}`, '--build-arg', `RUNTIME_VERSION=${tools['@volter/world-runtime']}`, '--build-arg', `CLI_VERSION=${tools['@volter/world']}`, '--build-arg', `PLAYWRIGHT_VERSION=${tools.playwright}`, '-f', join(root, 'runner/Dockerfile'), root]);
|
|
30
31
|
const image = readFileSync(imageFile, 'utf8').trim();
|
|
31
32
|
requireThat(/^sha256:[a-f0-9]{64}$/.test(image), 'container image has no immutable identity');
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
for (const volume of [id, customerVolume]) {
|
|
34
|
+
run(['volume', 'create', '--label', `twin-catalog.owner=${id}`, '--label', `twin-catalog.run=${runOwner}`, volume]);
|
|
35
|
+
volumes.push(volume);
|
|
36
|
+
}
|
|
37
|
+
// Only these task-owned new volumes are changed; both persist across container phases.
|
|
38
|
+
run(['run', '--rm', '--label', `twin-catalog.owner=${id}`, '--label', `twin-catalog.run=${runOwner}`, '--network', 'none', '--user', '0', '--entrypoint', 'chown', '-v', `${id}:/work`, '-v', `${customerVolume}:/customer`, image, '1000:1000', '/work', '/customer']);
|
|
39
|
+
const common = ['run', '--rm', '--label', `twin-catalog.owner=${id}`, '--label', `twin-catalog.run=${runOwner}`, '--init', '--shm-size', '256m', '--read-only', '--cap-drop', 'ALL', '--security-opt', 'no-new-privileges', '--tmpfs', '/tmp:rw,nosuid,nodev', '-e', 'HOME=/tmp/home', '-e', 'npm_config_cache=/tmp/npm-cache', '-v', `${id}:/work`, '-v', `${customerVolume}:/customer`, '-v', `${work}:/input:ro`];
|
|
36
40
|
return execute({ image, common, run, owner: id });
|
|
37
41
|
} finally {
|
|
38
|
-
const cleanup = volume
|
|
42
|
+
const cleanup = volumes.map(volume => ({ volume, result: run(['volume', 'rm', volume], true) }));
|
|
39
43
|
write(`${output}.logs.json`, logs);
|
|
40
44
|
// Retain input identity and diagnostics on failure; no other actor's resources are touched.
|
|
41
|
-
|
|
45
|
+
const failed = cleanup.filter(({ result }) => result.status !== 0);
|
|
46
|
+
requireThat(failed.length === 0, `owned volume teardown could not be verified: ${failed.map(({ volume }) => volume).join(', ')}`);
|
|
42
47
|
}
|
|
43
48
|
}
|
|
44
49
|
|
|
@@ -57,7 +62,8 @@ export function evaluate(root, input, output, options = {}) {
|
|
|
57
62
|
const lock = run([...common, '--network', 'none', image, '-e', 'process.stdout.write(require("fs").readFileSync("/work/package-lock.json","utf8"))']).stdout;
|
|
58
63
|
requireThat(digest(lock) === prepared.dependencyLockSha256, 'dependency lock identity changed');
|
|
59
64
|
write(`${output}.lock.json`, JSON.parse(lock));
|
|
60
|
-
|
|
65
|
+
requireThat(prepared.consumer?.appDir === '/customer/app', 'prepared customer app is outside its owned volume');
|
|
66
|
+
const consumerLock = run([...common, '--network', 'none', image, '-e', 'process.stdout.write(require("fs").readFileSync("/customer/app/package-lock.json","utf8"))']).stdout;
|
|
61
67
|
requireThat(digest(consumerLock) === prepared.consumerDependencyLockSha256, 'customer SDK lock identity changed');
|
|
62
68
|
write(`${output}.consumer-lock.json`, JSON.parse(consumerLock));
|
|
63
69
|
phase = 'offline-assessment';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/twin-catalog",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.34",
|
|
4
4
|
"description": "The moderated twin distribution: approved versions and submission tooling for independent publishers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"evidence"
|
|
39
39
|
],
|
|
40
40
|
"catalog": {
|
|
41
|
-
"sourceCommit": "
|
|
41
|
+
"sourceCommit": "6974ffe917b9d91212225eed921b735428cf07ec",
|
|
42
42
|
"digest": "b21e251b1ff51975a212e56872423830967cd3a1b6a258af4eca0db7287574fe"
|
|
43
43
|
}
|
|
44
44
|
}
|
package/policy.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"registry": "https://registry.npmjs.org",
|
|
4
4
|
"tools": {
|
|
5
|
-
"@volter/world-core": "3.0.
|
|
6
|
-
"@volter/world-runtime": "3.0.
|
|
7
|
-
"@volter/twin-standard": "1.0.
|
|
5
|
+
"@volter/world-core": "3.0.113",
|
|
6
|
+
"@volter/world-runtime": "3.0.113",
|
|
7
|
+
"@volter/twin-standard": "1.0.105",
|
|
8
8
|
"playwright": "1.63.0",
|
|
9
|
-
"@volter/world": "3.0.
|
|
9
|
+
"@volter/world": "3.0.113"
|
|
10
10
|
},
|
|
11
11
|
"bun": "1.4.2",
|
|
12
12
|
"nodeImage": "node:24-bookworm-slim",
|
package/runner/prepare.mjs
CHANGED
|
@@ -82,23 +82,25 @@ const provenance = await verifiedProvenance(await response.json(), source, bytes
|
|
|
82
82
|
mkdirSync('/work/packs', { recursive: true });
|
|
83
83
|
cpSync(join('/work/node_modules', s.package), join('/work/packs', s.vendor), { recursive: true });
|
|
84
84
|
const { prepareConsumerApp } = await import(evaluatorRequire.resolve('@volter/twin-standard'));
|
|
85
|
-
const
|
|
86
|
-
const
|
|
85
|
+
const productRoot = '/work/node_modules/@volter/world';
|
|
86
|
+
const product = read(join(productRoot, 'package.json'));
|
|
87
|
+
requireThat(product.name === '@volter/world' && product.version === policy.tools['@volter/world'], 'customer CLI differs from the product pin');
|
|
88
|
+
requireThat(typeof product.bin?.volter === 'string', 'product has no declared volter entry');
|
|
89
|
+
const cli = realpathSync(join(productRoot, product.bin.volter));
|
|
90
|
+
requireThat(!relative(realpathSync(productRoot), cli).startsWith('..'), 'CLI entry is outside the installed product');
|
|
91
|
+
// Keep the app outside /work's evaluator dependency tree.
|
|
92
|
+
const appDir = '/customer/app';
|
|
93
|
+
const consumerFixture = prepareConsumerApp(join('/work/packs', s.vendor), appDir, { cli });
|
|
87
94
|
const appInstall = spawnSync('npm', ['install', '--ignore-scripts', '--no-audit', '--no-fund', '--registry', policy.registry], { cwd: appDir, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 });
|
|
88
95
|
requireThat(appInstall.status === 0, `customer SDK preparation failed: ${appInstall.stderr}`);
|
|
89
96
|
const consumerLock = read(join(appDir, 'package-lock.json'));
|
|
97
|
+
requireThat(consumerLock.packages[`node_modules/${s.package}`]?.integrity === s.integrity, 'customer candidate differs from the admitted artifact');
|
|
98
|
+
requireThat(consumerLock.packages['node_modules/@volter/world-core']?.version === policy.tools['@volter/world-core'], 'customer kernel differs from the CLI pin');
|
|
90
99
|
for (const [path, entry] of Object.entries(consumerLock.packages ?? {})) {
|
|
91
100
|
if (!path) continue;
|
|
92
101
|
requireThat(!entry.link && entry.resolved && entry.integrity, `${path}: customer dependency is not a locked registry artifact`);
|
|
93
102
|
registryURL(entry.resolved, policy.registry);
|
|
94
103
|
}
|
|
95
|
-
// Other dependencies can own the flattened volter shortcut; use the exact product's declared entry.
|
|
96
|
-
const productRoot = '/work/node_modules/@volter/world';
|
|
97
|
-
const product = read(join(productRoot, 'package.json'));
|
|
98
|
-
requireThat(product.name === '@volter/world' && product.version === policy.tools['@volter/world'], 'customer CLI differs from the product pin');
|
|
99
|
-
requireThat(typeof product.bin?.volter === 'string', 'product has no declared volter entry');
|
|
100
|
-
const cli = realpathSync(join(productRoot, product.bin.volter));
|
|
101
|
-
requireThat(!relative(realpathSync(productRoot), cli).startsWith('..'), 'CLI entry is outside the installed product');
|
|
102
104
|
const consumer = { appDir, cli, artifactIntegrity: s.integrity };
|
|
103
105
|
// Retain listing facts from these exact bytes, never the registry's moving latest metadata.
|
|
104
106
|
const packageMetadata = {
|