software-defence-factory 0.6.0 → 0.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/bin/software-defence-factory.mjs +57 -18
- package/docs/interfaces.md +2 -2
- package/docs/quickstart.md +126 -9
- package/docs/recovery.md +208 -8
- package/docs/setup.md +70 -0
- package/docs/workflows.md +11 -1
- package/factory/delivery-provider.mjs +20 -0
- package/factory/delivery.mjs +881 -0
- package/factory/demo-fixture.mjs +11 -0
- package/factory/execution-evidence.mjs +7 -0
- package/factory/execution-profile.mjs +11 -0
- package/factory/executor.mjs +114 -48
- package/factory/git-environment.mjs +26 -0
- package/factory/inference-redaction.mjs +123 -0
- package/factory/issue-provider.mjs +4 -2
- package/factory/lib.mjs +37 -8
- package/factory/model-environment.mjs +166 -0
- package/factory/processes.mjs +49 -15
- package/factory/project-links.mjs +3 -4
- package/factory/providers/github-delivery.mjs +161 -0
- package/factory/queue.mjs +68 -14
- package/factory/scratch.mjs +15 -0
- package/factory/server.mjs +22 -6
- package/factory/source-admission.mjs +216 -0
- package/factory/ui/assets/index-B13yiAWU.css +1 -0
- package/factory/ui/assets/index-BuZQnEyv.js +13 -0
- package/factory/ui/index.html +2 -2
- package/factory/workflow-qualification.mjs +541 -0
- package/operator-skills/factory-foundation/SKILL.md +5 -1
- package/package.json +2 -1
- package/scripts/probe-platform.mjs +92 -6
- package/scripts/probe-review.mjs +3 -2
- package/scripts/retained-source-fixture.mjs +55 -0
- package/factory/ui/assets/index-CdazRQSz.js +0 -13
- package/factory/ui/assets/index-Whh9_Cb0.css +0 -1
|
@@ -4,7 +4,7 @@ import { resolve, join } from 'node:path';
|
|
|
4
4
|
import { randomBytes } from 'node:crypto';
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
6
|
import { createServer } from 'node:net';
|
|
7
|
-
import { ROOT, PINS, DEFAULT_STATE, configAt, save, json, run, stream, digest, api, sleep, stopContainers } from '../factory/lib.mjs';
|
|
7
|
+
import { ROOT, PINS, DEFAULT_STATE, configAt, save, json, run, stream, digest, api, sleep, stopContainers, PUBLICATION_API_TIMEOUT_MS } from '../factory/lib.mjs';
|
|
8
8
|
import { assertInstalledJobImage, installCustomJobImage, installStandardJobImage, inspectImageInstallation } from '../factory/image-install.mjs';
|
|
9
9
|
import { listIssues, readIssue } from '../factory/issue-intake.mjs';
|
|
10
10
|
import { recommendWork } from '../factory/intake.mjs';
|
|
@@ -14,6 +14,8 @@ import { admitIncident } from '../factory/incident.mjs';
|
|
|
14
14
|
import { DEFAULT_DEMO_STATE } from '../factory/paths.mjs';
|
|
15
15
|
import { bootstrap, registerInstallation, VERSION } from '../factory/updates.mjs';
|
|
16
16
|
import { hasService, manageService, serviceDefinition, withServiceOperation, isManagedLaunch } from '../factory/services.mjs';
|
|
17
|
+
import { runCandidateGit } from '../factory/git-environment.mjs';
|
|
18
|
+
import { initializeDemoRepository } from '../factory/demo-fixture.mjs';
|
|
17
19
|
|
|
18
20
|
try {
|
|
19
21
|
const handled = await bootstrap(process.argv.slice(2));
|
|
@@ -32,22 +34,22 @@ let state = resolve(flags.state || DEFAULT_STATE);
|
|
|
32
34
|
if(existsSync(state))state=realpathSync(state);
|
|
33
35
|
const alive = pid => { try { process.kill(pid,0); return true; } catch(error) { if(error.code === 'ESRCH')return false; throw error; } };
|
|
34
36
|
|
|
35
|
-
function init(repo, harness='codex', check='', port=7331) {
|
|
37
|
+
function init(repo, harness='codex', check='', port=7331, sourceRef='HEAD', delivery, inferenceProvider) {
|
|
36
38
|
repo=realpathSync(resolve(repo));
|
|
37
39
|
if (existsSync(join(state,'factory.json'))) throw new Error('Already configured; edit the private factory.json explicitly or choose another --state');
|
|
38
40
|
if ([repo,state,ROOT].some(p=>/[,\n\r]/.test(p))) throw new Error('Paths cannot contain commas or line breaks');
|
|
39
|
-
if (
|
|
40
|
-
|
|
41
|
+
if (runCandidateGit(repo,'rev-parse','--show-toplevel') !== repo) throw new Error('--repo must be the Git root');
|
|
42
|
+
runCandidateGit(repo,'rev-parse','HEAD');
|
|
41
43
|
const presets={codex:['codex','exec','--json','--ephemeral','--sandbox','danger-full-access','-'],pi:['pi','--mode','json','--print','--no-session','--no-extensions','--skill','/factory-skills'],mock:['node','/opt/factory/mock.mjs']};
|
|
42
44
|
const argv=harness==='custom'?JSON.parse(flags['command-json'] || 'null'):presets[harness];
|
|
43
45
|
if (!argv) throw new Error('Select codex, pi, mock or custom with --command-json');
|
|
44
46
|
if (flags.model && ['codex','pi'].includes(harness)) argv.splice(harness==='codex'?argv.length-1:argv.length,0,'--model',flags.model);
|
|
45
47
|
mkdirSync(state,{recursive:true,mode:0o700});state=realpathSync(state);chmodSync(state,0o700);
|
|
46
|
-
save(join(state,'factory.json'),{version:1,repo,harness,command:argv,check,port:Number(port),image:PINS.jobImage,network:harness==='mock'?'none':'bridge',timeoutSeconds:1800,memoryMiB:2048,model:flags.model || null,
|
|
48
|
+
save(join(state,'factory.json'),{version:1,repo,sourceRef,harness,command:argv,check,port:Number(port),image:PINS.jobImage,network:harness==='mock'?'none':'bridge',timeoutSeconds:1800,memoryMiB:2048,model:flags.model || null,...(inferenceProvider?{inferenceProvider}:{}),...(delivery?{delivery}:{}),
|
|
47
49
|
scope:{project:'pilot',service:'app',environment:'test',owner:'operator'}});
|
|
48
50
|
configAt(state);
|
|
49
51
|
writeFileSync(join(state,'worker.token'),randomBytes(32).toString('hex')+'\n',{mode:0o600});
|
|
50
|
-
writeFileSync(join(state,'model.env'),'#
|
|
52
|
+
writeFileSync(join(state,'model.env'),'# Supported inference settings only. Never add host, forge, deploy, Docker or cloud identity credentials.\n',{mode:0o600});
|
|
51
53
|
registerInstallation(state);
|
|
52
54
|
console.log(`Configured ${state}\nApp files were not changed. Only committed code is cloned into jobs.`);
|
|
53
55
|
}
|
|
@@ -98,10 +100,10 @@ async function stop() {
|
|
|
98
100
|
}
|
|
99
101
|
stopContainers(state);console.log('Controller and its labelled containers stopped.');
|
|
100
102
|
}
|
|
101
|
-
async function submit(workflow,spec) {
|
|
103
|
+
async function submit(workflow,spec,sourceRef=flags['source-ref']) {
|
|
102
104
|
if(Buffer.byteLength(spec)>240000)throw new Error('Task exceeds 240 KB');
|
|
103
105
|
const title=workflow==='defence'?'Private incident triage':spec.split('\n').find(s=>s.trim())?.replace(/^#+\s*/, '').slice(0,100)||'Software task';
|
|
104
|
-
return api(state,'/api/v1/jobs',{workflow,repository:'app',spec,title});
|
|
106
|
+
return api(state,'/api/v1/jobs',{workflow,repository:'app',spec,title,...(sourceRef===undefined?{}:{source_ref:sourceRef})});
|
|
105
107
|
}
|
|
106
108
|
async function jobAction(action) {
|
|
107
109
|
const id=positional[0];if(!/^job_[a-z0-9]+$/.test(id || ''))throw new Error('A job ID is required');
|
|
@@ -118,12 +120,43 @@ async function jobAction(action) {
|
|
|
118
120
|
}
|
|
119
121
|
// The controller validates the current run and owns reconciliation atomically.
|
|
120
122
|
// A CLI-side stop after a stale snapshot could terminate a newer attempt.
|
|
121
|
-
await api(state,`/api/v1/jobs/${id}/${action}`,{run_id:current?.id,...(feedback===undefined?{}:{feedback})});
|
|
123
|
+
await api(state,`/api/v1/jobs/${id}/${action}`,{run_id:current?.id,...(feedback===undefined?{}:{feedback}),...(flags['source-ref']===undefined?{}:{source_ref:flags['source-ref']})});
|
|
122
124
|
console.log(`${action}: ${id}`);
|
|
123
125
|
}
|
|
124
126
|
|
|
127
|
+
async function publishJob(jobId) {
|
|
128
|
+
if (!/^job_[a-f0-9]{24}$/.test(jobId || '')) throw new Error('publish requires a Factory JOB_ID');
|
|
129
|
+
const snapshot=await api(state,'/api/v1/status'),job=snapshot.jobs.find(item=>item.id===jobId);
|
|
130
|
+
if(!job)throw new Error('Job not found');
|
|
131
|
+
const current=job.runs.at(-1);
|
|
132
|
+
const receipt=await api(state,`/api/v1/jobs/${jobId}/publish`,{run_id:current?.id},undefined,{timeoutMs:PUBLICATION_API_TIMEOUT_MS});
|
|
133
|
+
console.log(JSON.stringify(receipt,null,2));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function abandonDeliveryJob(jobId) {
|
|
137
|
+
if (!/^job_[a-f0-9]{24}$/.test(jobId || '')) throw new Error('abandon-delivery requires a Factory JOB_ID');
|
|
138
|
+
const branchSha = flags['branch-sha'];
|
|
139
|
+
if (!/^[a-f0-9]{40}$/.test(branchSha || '')) throw new Error('abandon-delivery requires --branch-sha with the inspected remote SHA');
|
|
140
|
+
const snapshot=await api(state,'/api/v1/status'),job=snapshot.jobs.find(item=>item.id===jobId);
|
|
141
|
+
if(!job)throw new Error('Job not found');
|
|
142
|
+
const delivery=job.delivery_status;
|
|
143
|
+
if(!delivery?.can_abandon)throw new Error('This job has no resolvable pre-write branch collision; inspect status and reconcile unresolved provider effects.');
|
|
144
|
+
if(branchSha!==delivery.remote_collision?.sha)throw new Error('The supplied branch SHA differs from current status; inspect the current remote branch before resolving.');
|
|
145
|
+
const result=await api(state,`/api/v1/jobs/${jobId}/abandon-delivery`,{
|
|
146
|
+
run_id:job.runs.at(-1)?.id,delivery_identity:delivery.identity,branch_sha:branchSha,
|
|
147
|
+
},undefined,{timeoutMs:PUBLICATION_API_TIMEOUT_MS});
|
|
148
|
+
console.log(JSON.stringify(result,null,2));
|
|
149
|
+
}
|
|
150
|
+
|
|
125
151
|
try {
|
|
126
|
-
if(command==='init') {
|
|
152
|
+
if(command==='init') {
|
|
153
|
+
if(!flags.repo)throw new Error('init requires --repo /path/to/existing/git/repo');
|
|
154
|
+
if(flags.harness && flags.agent && flags.harness !== flags.agent)throw new Error('--harness conflicts with legacy --agent');
|
|
155
|
+
const deliveryFlags=[flags['delivery-provider'],flags['delivery-repository'],flags['delivery-target']];
|
|
156
|
+
if(deliveryFlags.some(Boolean)&&deliveryFlags.some(value=>!value))throw new Error('Trusted PR delivery requires --delivery-provider github --delivery-repository URL --delivery-target main|dev');
|
|
157
|
+
const delivery=deliveryFlags.every(Boolean)?{provider:flags['delivery-provider'],repository:flags['delivery-repository'],target:flags['delivery-target']}:undefined;
|
|
158
|
+
init(flags.repo,flags.harness || flags.agent,flags.check,flags.port,flags['source-ref'] || 'HEAD',delivery,flags['inference-provider']);
|
|
159
|
+
}
|
|
127
160
|
else if(command==='install')await withServiceOperation('install',install);
|
|
128
161
|
else if(command==='up') { if(hasService(state))await manageService('controller','start',state);else await withServiceOperation('up',up); }
|
|
129
162
|
else if(command==='stop') { if(hasService(state))await manageService('controller','stop',state);else await withServiceOperation('stop',stop); }
|
|
@@ -198,7 +231,7 @@ try {
|
|
|
198
231
|
input.title=flags.title || input.title;
|
|
199
232
|
if(typeof input.title!=='string'||!input.title.trim()||input.title.length>160)throw new Error('Provide a title of 1–160 characters (use --title for a blank issue)');
|
|
200
233
|
if(flags.workflow==='software'&&!configAt(state).check?.trim())throw new Error('Configure an app check before submitting software work');
|
|
201
|
-
console.log(JSON.stringify(await api(state,'/api/v1/jobs',{...input,workflow:flags.workflow,repository:'app',model:flags.model || ''}),null,2));
|
|
234
|
+
console.log(JSON.stringify(await api(state,'/api/v1/jobs',{...input,workflow:flags.workflow,repository:'app',model:flags.model || '',...(flags['source-ref']===undefined?{}:{source_ref:flags['source-ref']})}),null,2));
|
|
202
235
|
} else throw new Error('Use issue list|connection|templates|preview|recommend|draft|create|start|submissions|recover; see help');
|
|
203
236
|
} else if(command==='issues') {
|
|
204
237
|
console.log(JSON.stringify(await listIssues(configAt(state).repo,Number(flags.page || 1)),null,2));
|
|
@@ -220,14 +253,14 @@ try {
|
|
|
220
253
|
if(!flags.file)throw new Error('Use --file incident.json; see factory/examples/incident.json');
|
|
221
254
|
console.log(JSON.stringify(await admitIncident(state,json(resolve(flags.file)),submit)));
|
|
222
255
|
} else if(['approve','cancel','retry'].includes(command))await jobAction(command);
|
|
256
|
+
else if(command==='publish')await publishJob(positional[0]);
|
|
257
|
+
else if(command==='abandon-delivery')await abandonDeliveryJob(positional[0]);
|
|
223
258
|
else if(command==='revise')await jobAction('request_changes');
|
|
224
259
|
else if(command==='demo') {
|
|
225
260
|
state=resolve(flags.state || DEFAULT_DEMO_STATE);
|
|
226
261
|
const repo=join(state,'sample-app');
|
|
227
262
|
if(!existsSync(join(state,'factory.json'))) {
|
|
228
|
-
|
|
229
|
-
writeFileSync(join(repo,'value.txt'),'broken\n');run('git',['-C',repo,'add','value.txt']);
|
|
230
|
-
run('git',['-C',repo,'-c','user.name=Factory demo','-c','user.email=demo@localhost','commit','-m','Synthetic fixture']);
|
|
263
|
+
initializeDemoRepository(repo);
|
|
231
264
|
init(repo,'mock',"test \"$(cat value.txt)\" = fixed",Number(flags.port || 7332));
|
|
232
265
|
} else if(harnessOf(configAt(state))!=='mock')throw new Error('Demo requires a mock configuration');
|
|
233
266
|
await withServiceOperation('demo startup',async()=>{await install();await up();});console.log(JSON.stringify(await submit('software','Synthetic installation qualification: fix value.txt. No inference is used.')));
|
|
@@ -243,7 +276,9 @@ try {
|
|
|
243
276
|
kit --output NEW_DIRECTORY Export the portable method without a runtime
|
|
244
277
|
demo Install and run a synthetic sample (no model key)
|
|
245
278
|
qualify --state PATH Exercise recovery and isolation with a stopped demo job
|
|
246
|
-
init --repo PATH --harness codex|pi|custom --check "npm ci && npm test"
|
|
279
|
+
init --repo PATH --harness codex|pi|custom --check "npm ci && npm test" [--source-ref REF]
|
|
280
|
+
[--inference-provider PROVIDER]
|
|
281
|
+
[--delivery-provider github --delivery-repository https://github.com/OWNER/REPO --delivery-target main|dev]
|
|
247
282
|
install [--image LOCAL_REF] Build the standard image, or select an existing local image
|
|
248
283
|
doctor | up | status | stop Inspect / operate your private installation
|
|
249
284
|
foundation Read the operator setup skill; no installation required
|
|
@@ -272,15 +307,19 @@ try {
|
|
|
272
307
|
issue connection | submissions Inspect provider identity or durable creation receipts
|
|
273
308
|
issue recover --key REQUEST_ID Reconcile an uncertain creation without another write
|
|
274
309
|
issue start --draft draft.json | --url URL | --file brief.md --title TITLE
|
|
275
|
-
--workflow software|defence [--model MODEL]
|
|
310
|
+
--workflow software|defence [--source-ref REF] [--model MODEL]
|
|
276
311
|
Create a local issue and start work; no GitHub write
|
|
277
312
|
issues [--page N] Browse open project issues, with next_page for more
|
|
278
313
|
recommend --file task.md | --issue URL Suggest a work type without starting work
|
|
279
314
|
run --file task.md | --issue URL Submit software (default), or --workflow defence
|
|
315
|
+
[--source-ref REF] Pin a configured-repository ref before admission
|
|
280
316
|
incident --file incident.json Submit a private, read-only incident draft
|
|
281
317
|
approve JOB_ID | cancel JOB_ID Review gate / stop this attempt
|
|
318
|
+
publish JOB_ID Publish/reconcile the accepted candidate as one draft PR
|
|
319
|
+
abandon-delivery JOB_ID --branch-sha SHA Resolve an inspected pre-write collision; keep the remote branch
|
|
282
320
|
retry JOB_ID Prove stop; retain old checkout and retry
|
|
283
|
-
revise JOB_ID --file feedback.md
|
|
321
|
+
revise JOB_ID --file feedback.md [--source-ref REF]
|
|
322
|
+
New build/check/review; source stays pinned unless a new ref is explicit
|
|
284
323
|
version Show the active CLI version
|
|
285
324
|
update | update --check Update the npm CLI / inspect the latest release
|
|
286
325
|
update --auto on|off Control automatic daily CLI updates
|
|
@@ -289,7 +328,7 @@ Runtime commands accept --state PATH. Default: ${DEFAULT_STATE}
|
|
|
289
328
|
Demo default: ${DEFAULT_DEMO_STATE}
|
|
290
329
|
The npm CLI keeps state outside the package; updates wait for stopped installations.
|
|
291
330
|
Dashboard binds only to loopback; use SSH for remote access.
|
|
292
|
-
Setup plan: ${join(ROOT, 'docs/setup.md')}
|
|
331
|
+
Setup plan: ${join(ROOT, 'docs/setup.md')}
|
|
293
332
|
See docs/quickstart.md for task execution, evidence and recovery.`);
|
|
294
333
|
else throw new Error(`Unknown command: ${command}`);
|
|
295
334
|
} catch(error) {console.error(`Factory: ${error.message}`);process.exitCode=1;}
|
package/docs/interfaces.md
CHANGED
|
@@ -32,8 +32,8 @@ shell endpoint or a second scheduler.
|
|
|
32
32
|
| SSH tunnels | `tunnel` | No tunnel endpoint | None | Client-host ownership; distinguish operator machine from worker |
|
|
33
33
|
| Method export | `kit --output` | No export endpoint | None | Equivalent download/export preserving staging-only adoption |
|
|
34
34
|
| Synthetic qualification | `demo`, `qualify` | No qualification endpoint | Synthetic disclosure only | Explicit separate state; never target an application accidentally |
|
|
35
|
-
| Immutable source admission |
|
|
36
|
-
| Trusted PR handoff |
|
|
35
|
+
| Immutable source admission | `init --source-ref`, `run --source-ref`, `issue start --source-ref`; status and build evidence carry the resolved SHA | `POST /api/v1/jobs` resolves/retains before acknowledgement; shared source metadata in status | New issue and revision forms accept a ref; task detail shows requested ref, resolved SHA and prior source commits | Build/retry use retained objects; revisions keep the recorded source unless a new ref is explicit; legacy source remains unknown |
|
|
36
|
+
| Trusted PR handoff | `publish JOB_ID` publishes/reconciles; `abandon-delivery JOB_ID --branch-sha SHA` records a checked local resolution for a pre-write branch collision | Authenticated `POST /api/v1/jobs/:id/publish` and `/abandon-delivery`; shared receipt, conflict identity and removal policy | Publish/reconcile and explicit “Abandon local delivery; keep remote branch” actions share controller state; errors/results and inspected branch identity are visible | New writes require matching protected Codex/Pi build/review provenance, deterministic verify/handoff provenance, non-synthetic bound artifacts and a qualified GitHub Actions tree. Shared delivery status exposes `workflow_qualification` and the same reason blocks CLI/API/dashboard capability and publication/retry. Candidate workflow changes, unsupported triggers/syntax, or active generated-push, selected-ref-dispatch and PR jobs with write/secrets/environment/OIDC/deploy access, self-hosted runners or ambiguous privileged guards refuse trusted writes. Supported ASCII guard comparisons follow GitHub's case-insensitive string semantics; unknown PR refs, non-ASCII mismatches, and glob/escaped branch filters cannot prove a privileged job inactive. The shared summary's `action_mode` distinguishes new/resumable publication from read-only reconciliation and drives idle and pending task button wording. Branch-only collisions and unknown/abandoned states offer neither; known PR receipts and pending PR-creation checkpoints retain read-only reconciliation. Abandonment checks the current run, saved intent, exact branch head and absence of an associated PR; it writes no provider data, preserves the remote branch/evidence, disables republishing and permits local removal. Uncertain effects and incompatible evidence stay blocked. Destination remains private operator config; patch-only remains default |
|
|
37
37
|
|
|
38
38
|
The current generic task form can name the Defence workflow; that is not a
|
|
39
39
|
substitute for the CLI's validated incident admission. Treat the typed intake
|
package/docs/quickstart.md
CHANGED
|
@@ -23,32 +23,146 @@ The qualification intentionally creates failed, cancelled and interrupted tasks.
|
|
|
23
23
|
Commit an intentional, reviewed starting point in the application first. Jobs clone committed code only; uncommitted work stays in the source checkout.
|
|
24
24
|
|
|
25
25
|
```sh
|
|
26
|
-
software-defence-factory init --repo /absolute/path/to/app --harness codex --check "npm ci && npm test" --state /private/state/my-app --port 7331
|
|
26
|
+
software-defence-factory init --repo /absolute/path/to/app --harness codex --check "npm ci && npm test" --source-ref main --state /private/state/my-app --port 7331
|
|
27
27
|
software-defence-factory install --state /private/state/my-app
|
|
28
28
|
software-defence-factory doctor --state /private/state/my-app
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
`init --source-ref` selects the configured default ref (`HEAD` when omitted). Each job resolves that ref, or an explicit `--source-ref` on `run`/`issue start`, in the configured repository and durably retains its commit before acknowledging admission. It records the canonical GitHub origin identity when available; the CLI and dashboard show the requested ref and resolved SHA. Task text and reference links do not select a repository, source ref or PR target.
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
Verification commands receive `FACTORY_BASE_REVISION`, the resolved admission 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.
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
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. `--harness pi` selects Pi; `--harness 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.
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
Configure supported inference settings in the private `model.env` file. It
|
|
38
|
+
rejects unrelated names such as `DEPLOY_TOKEN`; do not copy the operator's
|
|
39
|
+
account environment or authentication folders. Codex receives only its OpenAI
|
|
40
|
+
settings. Pi receives only the selected provider's settings. Set
|
|
41
|
+
`--inference-provider PROVIDER` during `init`, or pin an operator-selected
|
|
42
|
+
`provider/model` with `--model`; if multiple provider credential groups are in
|
|
43
|
+
`model.env`, an explicit provider is required. Task-level model overrides never
|
|
44
|
+
select a credential group. See the `inferenceProvider` entry in private
|
|
45
|
+
`factory.json` when editing trusted installation configuration directly.
|
|
46
|
+
|
|
47
|
+
Installations using the existing Codex account-auth command may also put its
|
|
48
|
+
single-line JSON object in `FACTORY_CODEX_AUTH_JSON` in private `model.env`.
|
|
49
|
+
Factory validates that value as JSON and gives it only to the selected Codex
|
|
50
|
+
build/review/defence worker through a temporary private env file; the configured
|
|
51
|
+
operator command that consumes this setting must materialize its temporary
|
|
52
|
+
native `auth.json` before invoking Codex; the bundled stock `codex exec` does
|
|
53
|
+
not do this by itself. Pi never receives this setting, including with the OpenAI provider, and
|
|
54
|
+
deterministic checks and output artifacts do not receive it. Keep `model.env`
|
|
55
|
+
private with mode `0600`; do not copy account folders or place the value in a
|
|
56
|
+
task, source file or report. This setting does not replace provider API-key or
|
|
57
|
+
local OpenAI-compatible endpoint configuration.
|
|
58
|
+
|
|
59
|
+
Factory's Pi provider identifiers are `anthropic`, `azure-openai-responses`,
|
|
60
|
+
`cerebras`, `cloudflare-ai-gateway`, `cloudflare-workers-ai`, `deepseek`,
|
|
61
|
+
`google`, `groq`, `huggingface`, `kimi-coding`, `minimax`, `minimax-cn`,
|
|
62
|
+
`mistral`, `openai`, `opencode`, `opencode-go`, `openrouter`,
|
|
63
|
+
`vercel-ai-gateway`, `xiaomi`, `xiaomi-token-plan-ams`,
|
|
64
|
+
`xiaomi-token-plan-cn`, `xiaomi-token-plan-sgp`, `xai` and `zai`. Factory
|
|
65
|
+
forwards provider API-key and endpoint settings from this map; it does not
|
|
66
|
+
forward OAuth files or ambient cloud identity credentials.
|
|
67
|
+
|
|
68
|
+
`OPENAI_BASE_URL` remains available for an OpenAI-compatible local endpoint
|
|
69
|
+
selected for Codex; the endpoint must be reachable from inside the job
|
|
70
|
+
container. Host loopback addresses do not automatically refer to the host from
|
|
71
|
+
Docker. The package does not automatically expose Ollama or import models.
|
|
38
72
|
|
|
39
73
|
```sh
|
|
40
74
|
software-defence-factory up --state /private/state/my-app
|
|
41
|
-
software-defence-factory run --file task.md --state /private/state/my-app
|
|
75
|
+
software-defence-factory run --file task.md --source-ref main --state /private/state/my-app
|
|
42
76
|
```
|
|
43
77
|
|
|
44
78
|
A task should describe the accepted outcome, allowed scope and observable checks. The CLI also accepts `--issue https://github.com/owner/repo/issues/123` for an issue belonging to the configured origin; it uses the operator's existing gh access outside the job. The dashboard supports the same task workflow. Source text and links do not grant additional authority.
|
|
45
79
|
|
|
46
80
|
## Review and handoff
|
|
47
81
|
|
|
48
|
-
Inspect the task's Result, Files and History tabs. Build evidence includes candidate.json, change.patch and the implementation report. Checks and review identify their exact commit and policy hash. Approval revalidates both before writing accepted.json. Request changes
|
|
82
|
+
Inspect the task's Result, Files and History tabs. Task details show the requested source ref, resolved admission SHA and previous source commits when a new base was selected. Build evidence includes candidate.json, change.patch and the implementation report; handoff records its source SHA. Checks and review identify their exact candidate commit and policy hash. Approval revalidates both before writing accepted.json. Request changes preserves the recorded source by default and starts fresh checks/review; an explicit new source ref is retained as a deliberate base change.
|
|
49
83
|
|
|
50
84
|
The source application is not changed and no branch, PR, merge or deployment is published automatically. A reviewed change.patch can be checked and applied with `git apply --check` and `git apply` on an appropriate branch at its recorded base revision; then follow the application's normal integrated checks and delivery policy.
|
|
51
85
|
|
|
86
|
+
### Optional trusted PR delivery
|
|
87
|
+
|
|
88
|
+
Patch-only remains the default. To enable the first delivery provider, select a
|
|
89
|
+
canonical GitHub origin and an explicit target while initializing the private
|
|
90
|
+
installation:
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
software-defence-factory init --repo /absolute/path/to/app --harness codex \
|
|
94
|
+
--check "npm ci && npm test" --source-ref main \
|
|
95
|
+
--delivery-provider github \
|
|
96
|
+
--delivery-repository https://github.com/OWNER/REPO \
|
|
97
|
+
--delivery-target main --state /private/state/my-app
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Use `dev` only when it is the intended target. The source ref (`main` above)
|
|
101
|
+
and PR target are separate settings. The configured GitHub repository must
|
|
102
|
+
match the canonical origin captured at admission; a later repository rename or
|
|
103
|
+
remote change blocks delivery. Configure this before admitting work because a
|
|
104
|
+
configuration change invalidates earlier check/review/approval policy evidence.
|
|
105
|
+
Unknown providers and installations without `delivery` configuration keep the
|
|
106
|
+
patch-only flow.
|
|
107
|
+
|
|
108
|
+
After the ordinary check, independent review and operator approval complete,
|
|
109
|
+
use **Publish accepted candidate as draft PR** in task details or run:
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
software-defence-factory publish JOB_ID --state /private/state/my-app
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Trusted publication requires protected per-run execution records for native
|
|
116
|
+
Codex/Pi build and review plus deterministic verification and handoff, bound to
|
|
117
|
+
non-synthetic candidate, check and review artifacts. Mock qualification remains
|
|
118
|
+
local exploration and cannot be published; missing or inconsistent provenance
|
|
119
|
+
keeps the action unavailable in status, CLI, API and dashboard. A saved intent
|
|
120
|
+
is checked again before new provider writes. Known PR receipts and PR-creation
|
|
121
|
+
checkpoints still allow read-only reconciliation.
|
|
122
|
+
|
|
123
|
+
New writes also require the shared GitHub Actions qualification described in
|
|
124
|
+
[setup](setup.md#optional-trusted-pr-delivery) and [recovery](recovery.md#trusted-pr-delivery).
|
|
125
|
+
Unsupported or candidate-changed workflows keep publication unavailable in
|
|
126
|
+
status, CLI, API and dashboard; the accepted patch remains available for normal
|
|
127
|
+
manual delivery.
|
|
128
|
+
|
|
129
|
+
For `push`, the qualifier evaluates branch filters when either `branches` or
|
|
130
|
+
`branches-ignore` is declared, even alongside tag filters; tag-only filters do
|
|
131
|
+
not activate a generated branch push. Docker phase logs redact literal selected
|
|
132
|
+
inference credential values before retention. Mounted worker reports are
|
|
133
|
+
redacted after the container stops and before promotion; if shutdown is
|
|
134
|
+
uncertain, recovery handles them after confirming the stop. This is a bounded
|
|
135
|
+
output filter, not general protection against encoded or transformed values.
|
|
136
|
+
|
|
137
|
+
The controller uses its existing `gh` identity. GitHub credentials are never
|
|
138
|
+
copied to `model.env` or mounted into jobs. The intent, generated branch, PR
|
|
139
|
+
identity, actual base/head/tree and triggered PR check results appear in the
|
|
140
|
+
same job. Unknown and pending checks remain visible and are not reported as
|
|
141
|
+
success. GitHub's raw `success`, `skipped` and `neutral` conclusions are
|
|
142
|
+
non-blocking in the Factory check summary; skipped and neutral remain visibly
|
|
143
|
+
distinct from an executed passing check. Unknown conclusions and incomplete
|
|
144
|
+
pagination keep the aggregate unknown. This summary does not determine branch
|
|
145
|
+
protection requirements or grant merge authorization. See [GitHub's required
|
|
146
|
+
status check guidance](https://docs.github.com/en/pull-requests/how-tos/merge-and-close-pull-requests/troubleshooting-required-status-checks).
|
|
147
|
+
Repeating `publish` refreshes/reconciles the same branch and PR; it does not
|
|
148
|
+
create a second PR or overwrite a changed branch. The CLI gives this bounded
|
|
149
|
+
multi-request action ten minutes; branch resolution uses the same bound and
|
|
150
|
+
other API calls keep their five-second deadline. If a client deadline expires,
|
|
151
|
+
inspect status and repeat `publish` to reconcile or recheck the reported branch
|
|
152
|
+
identity before `abandon-delivery`. Delete issue stays disabled while delivery
|
|
153
|
+
is unresolved, and the controller enforces the same guard on its API.
|
|
154
|
+
Publication does not merge, integrate, release or deploy. See [delivery
|
|
155
|
+
recovery](recovery.md#trusted-pr-delivery).
|
|
156
|
+
|
|
157
|
+
If status reports a reserved branch collision at the untouched `intent` stage,
|
|
158
|
+
inspect that branch in GitHub first. The task detail action **Abandon local
|
|
159
|
+
delivery; keep remote branch** or `abandon-delivery JOB_ID --branch-sha SHA`
|
|
160
|
+
records only a local resolution after the controller confirms the same branch
|
|
161
|
+
head and no associated PR. It preserves the remote branch and accepted evidence,
|
|
162
|
+
permits local issue removal, and permanently disables publication for that
|
|
163
|
+
delivery record. Changed identities, PRs and uncertain provider effects remain
|
|
164
|
+
blocked; see [recovery](recovery.md#trusted-pr-delivery).
|
|
165
|
+
|
|
52
166
|
## Remote access and operation
|
|
53
167
|
|
|
54
168
|
```sh
|
|
@@ -107,8 +221,11 @@ is tested at 100%; changing browser zoom is separate from a project theme.
|
|
|
107
221
|
## Environment
|
|
108
222
|
|
|
109
223
|
Factory does not load a repository `.env` file. Configure the private
|
|
110
|
-
`factory.json` through `init`; put inference
|
|
111
|
-
`model.env`. A
|
|
224
|
+
`factory.json` through `init`; put supported inference settings only in its
|
|
225
|
+
private `model.env`. A provider allowlist selects the configured Codex/Pi
|
|
226
|
+
settings before a worker starts; unrelated host, forge, deployment, cloud
|
|
227
|
+
identity and application variables are rejected. A repository `.env.example`
|
|
228
|
+
is unnecessary for this CLI. Optional
|
|
112
229
|
process settings are `SDF_AUTO_UPDATE=0` (skip automatic CLI update checks),
|
|
113
230
|
`XDG_STATE_HOME`, `XDG_DATA_HOME` and `XDG_CONFIG_HOME` (user-owned state, release
|
|
114
231
|
and service locations). They must be exported in the process environment.
|