software-defence-factory 0.3.3 → 0.3.4
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/bin/software-defence-factory.mjs +13 -1
- package/docs/npm.md +5 -1
- package/docs/quickstart.md +2 -0
- package/factory/executor.mjs +1 -0
- package/package.json +1 -1
- package/scripts/probe-platform.mjs +12 -2
|
@@ -46,13 +46,25 @@ function init(repo, agent='codex', check='', port=7331) {
|
|
|
46
46
|
console.log(`Configured ${state}\nApp files were not changed. Only committed code is cloned into jobs.`);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
function retainImage(reference) {
|
|
50
|
+
let image;
|
|
51
|
+
try { image=run('docker',['image','inspect','--format','{{.Id}}',reference]); }
|
|
52
|
+
catch(error) { if(/No such image|No such object/.test(error.message))return null;throw error; }
|
|
53
|
+
if(!/^sha256:[a-f0-9]{64}$/.test(image))throw new Error('Docker returned an unexpected image identity');
|
|
54
|
+
// Containerd can drop an untagged manifest when the shared build tag moves.
|
|
55
|
+
// Keep the exact object reachable; existing installations retain their pins.
|
|
56
|
+
run('docker',['tag',image,`software-defence-factory-retained:${image.slice(7)}`]);
|
|
57
|
+
return image;
|
|
58
|
+
}
|
|
49
59
|
async function install() {
|
|
50
60
|
const config=configAt(state);
|
|
51
61
|
if(existsSync(join(state,'supervisor.json'))&&alive(json(join(state,'supervisor.json')).pid))throw new Error('Stop the factory before installing or updating its runtime');
|
|
52
62
|
if (!['darwin','linux'].includes(process.platform)||!['arm64','x64'].includes(process.arch)) throw new Error('Use macOS/Linux arm64/amd64, or WSL2');
|
|
53
63
|
run('docker',['info','--format','{{.ServerVersion}}']);
|
|
64
|
+
retainImage(config.image);retainImage(PINS.jobImage);
|
|
54
65
|
await stream('docker',['build','-t',PINS.jobImage,join(ROOT,'factory/image')]);
|
|
55
|
-
config.image=
|
|
66
|
+
config.image=retainImage(PINS.jobImage);
|
|
67
|
+
if(!config.image)throw new Error('Built job image is unavailable');
|
|
56
68
|
save(join(state,'factory.json'),config);save(join(state,'engine.json'),{runtime:'native-node',version:VERSION,image:config.image});
|
|
57
69
|
console.log('Installed. Job image is pinned to its local image ID. No model call made.');
|
|
58
70
|
}
|
package/docs/npm.md
CHANGED
|
@@ -60,7 +60,11 @@ Source checkouts remain managed by Git and do not update themselves.
|
|
|
60
60
|
|
|
61
61
|
CLI updates do not rebuild or update Docker images automatically. Stop the
|
|
62
62
|
installation and run `install --state PATH` when intentionally adopting a new
|
|
63
|
-
runtime image; repeat the relevant qualification before resuming jobs.
|
|
63
|
+
runtime image; repeat the relevant qualification before resuming jobs. Install retains the old and new
|
|
64
|
+
image IDs under `software-defence-factory-retained` tags, so another installation
|
|
65
|
+
rebuilding the shared tag cannot remove an existing installation's pinned image.
|
|
66
|
+
These retained images are recovery data; remove them only after confirming no
|
|
67
|
+
installation or retained attempt needs them.
|
|
64
68
|
|
|
65
69
|
## Release flow
|
|
66
70
|
|
package/docs/quickstart.md
CHANGED
|
@@ -26,6 +26,8 @@ software-defence-factory install --state /private/state/my-app
|
|
|
26
26
|
software-defence-factory doctor --state /private/state/my-app
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
Verification commands receive `FACTORY_BASE_REVISION`, the resolved commit recorded as the candidate base. Diff-based checks should compare against this revision; the isolated checkout has no origin remote. The value comes from protected controller metadata, not the task text.
|
|
30
|
+
|
|
29
31
|
Replace the check with the application's actual verification command. `init` does not edit the app, copy global skills or start work. It creates factory.json, worker.token and model.env with private permissions. Each installation has one repository and a distinct state path/port. `--agent pi` selects Pi; `--agent custom --command-json '["executable","argument"]'` selects an available command in the job image. The bundled image provides Node, Git, Codex and Pi. Other toolchains require an intentionally built compatible image; do not claim Rust/mobile/browser capabilities from this image alone.
|
|
30
32
|
|
|
31
33
|
Configure inference credentials in the private model.env file. Do not copy the operator's entire account environment or authentication folders. Codex uses its supported API credential environment; Pi uses the selected provider's configuration. Use `--model` with init for a specific model. Task-level model overrides are supported only for Codex/Pi and do not prove that the provider serves that model.
|
package/factory/executor.mjs
CHANGED
|
@@ -73,6 +73,7 @@ async function container(mode, input, command, writable = false, credentials = f
|
|
|
73
73
|
'--mount',`type=bind,source=${join(ROOT,'kit')},target=/factory-policy,readonly`,
|
|
74
74
|
'--mount',`type=bind,source=${join(ROOT,'.agents/skills')},target=/factory-skills,readonly`];
|
|
75
75
|
if (scratch) args.push('--mount',`type=bind,source=${scratch},target=/scratch`);
|
|
76
|
+
if (mode === 'verify') args.push('--env',`FACTORY_BASE_REVISION=${git('rev-parse',`${metadata().base}^{commit}`)}`);
|
|
76
77
|
if (credentials) args.push('--env-file', join(state,'model.env'));
|
|
77
78
|
args.push('-i',config.image,'timeout','--signal=KILL',`${config.timeoutSeconds}s`,'sh','-c','mkdir -p "$HOME" && exec "$@"','factory',...command);
|
|
78
79
|
console.log(JSON.stringify({ phase: mode, event: 'started', synthetic: config.agent === 'mock' }));
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
4
4
|
import { resolve, join } from 'node:path';
|
|
5
|
-
import { api, configAt, save, json, containers, sleep, stream, ROOT } from '../factory/lib.mjs';
|
|
5
|
+
import { api, configAt, save, json, containers, sleep, stream, run, ROOT } from '../factory/lib.mjs';
|
|
6
6
|
import { admitIncident } from '../factory/incident.mjs';
|
|
7
7
|
|
|
8
8
|
const state=resolve(process.argv[2] || '.factory/demo-platform'),original=configAt(state);
|
|
@@ -36,6 +36,9 @@ try {
|
|
|
36
36
|
const native=await work('Synthetic disk-backed build verification');
|
|
37
37
|
const checking=await until(()=>containers(state).find(c=>c.Config.Labels['sdf.job']===native.id&&c.State.Running&&c.Config.Env.includes('FACTORY_PHASE=verify')));
|
|
38
38
|
assert.equal(checking.HostConfig.NanoCpus,4000000000);assert.equal(checking.HostConfig.PidsLimit,1024);
|
|
39
|
+
const nativeCandidate=json(join(state,'jobs',native.id,'candidate.json'));
|
|
40
|
+
assert.notEqual(nativeCandidate.base,nativeCandidate.head);
|
|
41
|
+
assert(checking.Config.Env.includes(`FACTORY_BASE_REVISION=${nativeCandidate.base}`));
|
|
39
42
|
assert(checking.Mounts.some(m=>m.Destination==='/scratch'&&m.RW));
|
|
40
43
|
assert(checking.Mounts.some(m=>m.Destination==='/workspace'&&!m.RW));
|
|
41
44
|
await waitState(native.id,'awaiting_approval');
|
|
@@ -43,7 +46,7 @@ try {
|
|
|
43
46
|
assert(!existsSync(join(state,'jobs',native.id,verification.id,'check-workspace')));
|
|
44
47
|
assert(!existsSync(join(state,'jobs',native.id,'checkout/large-build-fixture')));
|
|
45
48
|
await cli('approve',native.id);await waitState(native.id,'succeeded');setConfig({});
|
|
46
|
-
record('native build scratch exceeds 1 GiB
|
|
49
|
+
record('native build scratch exceeds 1 GiB; immutable base, read-only candidate, limits and cleanup verified');
|
|
47
50
|
|
|
48
51
|
for (const exitCode of [0, 17]) {
|
|
49
52
|
setConfig({check:`mkdir -p cache/nested && echo cache > cache/nested/value && ln -s /workspace cache/candidate && chmod 000 cache/nested cache && exit ${exitCode}`});
|
|
@@ -98,6 +101,13 @@ try {
|
|
|
98
101
|
await cli('stop');assert.equal(containers(state).length,0);await cli('up');await waitState(interrupted.id,'interrupted');
|
|
99
102
|
setConfig({timeoutSeconds:2});await cli('retry',interrupted.id);await waitState(interrupted.id,'failed');
|
|
100
103
|
assert.equal((await snapshot(interrupted.id)).runs.length,2);assert.equal(containers(state).length,0);record('stop/restart retains interrupted state; retry proves previous writer stopped');
|
|
104
|
+
const other=join(state,'second-installation');
|
|
105
|
+
await stream(process.execPath,[join(ROOT,'bin/software-defence-factory.mjs'),'init','--state',other,'--repo',original.repo,'--agent','mock','--check',original.check,'--port','7350']);
|
|
106
|
+
await stream(process.execPath,[join(ROOT,'bin/software-defence-factory.mjs'),'install','--state',other]);
|
|
107
|
+
assert.equal(run('docker',['image','inspect','--format','{{.Id}}',original.image]),original.image);
|
|
108
|
+
run('docker',['run','--rm','--init','--network','none',original.image,'node','-e','process.exit(0)']);
|
|
109
|
+
await cli('stop');setConfig({});await cli('up');
|
|
110
|
+
record('a second installation rebuild preserves the first pinned image and controller restart');
|
|
101
111
|
save(join(state,'qualification.json'),{synthetic:true,platform:process.platform,arch:process.arch,engine:json(join(state,'engine.json')).version,results});
|
|
102
112
|
console.log(`Qualified ${results.length} paths. Model quality, cost and production connectors were not measured.`);
|
|
103
113
|
} finally {save(join(state,'factory.json'),original);}
|