software-defence-factory 0.7.0 → 0.9.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/THIRD_PARTY_NOTICES.md +10 -0
- package/bin/software-defence-factory.mjs +58 -8
- package/docs/interfaces.md +2 -1
- package/docs/quickstart.md +121 -6
- package/docs/recovery.md +199 -4
- package/docs/setup.md +78 -0
- package/docs/web-verification.md +167 -0
- package/factory/definition.mjs +9 -2
- package/factory/delivery-provider.mjs +20 -0
- package/factory/delivery.mjs +901 -0
- package/factory/execution-evidence.mjs +11 -0
- package/factory/execution-profile.mjs +19 -1
- package/factory/executor.mjs +275 -57
- package/factory/inference-redaction.mjs +123 -0
- package/factory/lib.mjs +37 -6
- package/factory/model-environment.mjs +166 -0
- package/factory/processes.mjs +55 -16
- package/factory/providers/github-delivery.mjs +161 -0
- package/factory/queue.mjs +14 -1
- package/factory/scratch.mjs +15 -0
- package/factory/server.mjs +50 -12
- package/factory/source-admission.mjs +6 -0
- package/factory/ui/assets/index-CLrogux1.css +1 -0
- package/factory/ui/assets/index-Cjk_XtEU.js +13 -0
- package/factory/ui/index.html +2 -2
- package/factory/updates.mjs +1 -1
- package/factory/web/Dockerfile +6 -0
- package/factory/web/containers.mjs +189 -0
- package/factory/web/package-lock.json +42 -0
- package/factory/web/package.json +9 -0
- package/factory/web/qualification-fixture.mjs +97 -0
- package/factory/web/readiness.mjs +26 -0
- package/factory/web/runner.mjs +236 -0
- package/factory/web-readiness.mjs +94 -0
- package/factory/web-verification.mjs +250 -0
- package/factory/workflow-qualification.mjs +541 -0
- package/operator-skills/factory-foundation/SKILL.md +5 -1
- package/package.json +3 -1
- package/scripts/probe-platform.mjs +34 -5
- package/scripts/probe-web.mjs +104 -0
- package/factory/ui/assets/index-Bz6ECdy_.css +0 -1
- package/factory/ui/assets/index-DqeOnrGK.js +0 -13
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -152,6 +152,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
152
152
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
153
153
|
SOFTWARE.
|
|
154
154
|
|
|
155
|
+
|
|
155
156
|
## @radix-ui/react-compose-refs 1.1.5
|
|
156
157
|
|
|
157
158
|
MIT License
|
|
@@ -574,3 +575,12 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
574
575
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
575
576
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
576
577
|
SOFTWARE.
|
|
578
|
+
|
|
579
|
+
## Optional browser verification image
|
|
580
|
+
|
|
581
|
+
The operator-built Playwright runner image installs `playwright` and
|
|
582
|
+
`playwright-core` 1.63.0 from the exact lockfile in `factory/web/` under the
|
|
583
|
+
Apache License 2.0. The original license files are retained in the built image
|
|
584
|
+
under `/opt/factory-web/node_modules/playwright/LICENSE` and
|
|
585
|
+
`/opt/factory-web/node_modules/playwright-core/LICENSE`. Factory does not bundle
|
|
586
|
+
the built browser image in its npm package.
|
|
@@ -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';
|
|
@@ -16,6 +16,7 @@ import { bootstrap, registerInstallation, VERSION } from '../factory/updates.mjs
|
|
|
16
16
|
import { hasService, manageService, serviceDefinition, withServiceOperation, isManagedLaunch } from '../factory/services.mjs';
|
|
17
17
|
import { runCandidateGit } from '../factory/git-environment.mjs';
|
|
18
18
|
import { initializeDemoRepository } from '../factory/demo-fixture.mjs';
|
|
19
|
+
import { probeWebBrowser } from '../factory/web-readiness.mjs';
|
|
19
20
|
|
|
20
21
|
try {
|
|
21
22
|
const handled = await bootstrap(process.argv.slice(2));
|
|
@@ -34,7 +35,7 @@ let state = resolve(flags.state || DEFAULT_STATE);
|
|
|
34
35
|
if(existsSync(state))state=realpathSync(state);
|
|
35
36
|
const alive = pid => { try { process.kill(pid,0); return true; } catch(error) { if(error.code === 'ESRCH')return false; throw error; } };
|
|
36
37
|
|
|
37
|
-
function init(repo, harness='codex', check='', port=7331, sourceRef='HEAD') {
|
|
38
|
+
function init(repo, harness='codex', check='', port=7331, sourceRef='HEAD', delivery, inferenceProvider) {
|
|
38
39
|
repo=realpathSync(resolve(repo));
|
|
39
40
|
if (existsSync(join(state,'factory.json'))) throw new Error('Already configured; edit the private factory.json explicitly or choose another --state');
|
|
40
41
|
if ([repo,state,ROOT].some(p=>/[,\n\r]/.test(p))) throw new Error('Paths cannot contain commas or line breaks');
|
|
@@ -45,11 +46,11 @@ function init(repo, harness='codex', check='', port=7331, sourceRef='HEAD') {
|
|
|
45
46
|
if (!argv) throw new Error('Select codex, pi, mock or custom with --command-json');
|
|
46
47
|
if (flags.model && ['codex','pi'].includes(harness)) argv.splice(harness==='codex'?argv.length-1:argv.length,0,'--model',flags.model);
|
|
47
48
|
mkdirSync(state,{recursive:true,mode:0o700});state=realpathSync(state);chmodSync(state,0o700);
|
|
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,
|
|
49
|
+
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}:{}),
|
|
49
50
|
scope:{project:'pilot',service:'app',environment:'test',owner:'operator'}});
|
|
50
51
|
configAt(state);
|
|
51
52
|
writeFileSync(join(state,'worker.token'),randomBytes(32).toString('hex')+'\n',{mode:0o600});
|
|
52
|
-
writeFileSync(join(state,'model.env'),'#
|
|
53
|
+
writeFileSync(join(state,'model.env'),'# Supported inference settings only. Never add host, forge, deploy, Docker or cloud identity credentials.\n',{mode:0o600});
|
|
53
54
|
registerInstallation(state);
|
|
54
55
|
console.log(`Configured ${state}\nApp files were not changed. Only committed code is cloned into jobs.`);
|
|
55
56
|
}
|
|
@@ -124,8 +125,39 @@ async function jobAction(action) {
|
|
|
124
125
|
console.log(`${action}: ${id}`);
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
async function publishJob(jobId) {
|
|
129
|
+
if (!/^job_[a-f0-9]{24}$/.test(jobId || '')) throw new Error('publish requires a Factory JOB_ID');
|
|
130
|
+
const snapshot=await api(state,'/api/v1/status'),job=snapshot.jobs.find(item=>item.id===jobId);
|
|
131
|
+
if(!job)throw new Error('Job not found');
|
|
132
|
+
const current=job.runs.at(-1);
|
|
133
|
+
const receipt=await api(state,`/api/v1/jobs/${jobId}/publish`,{run_id:current?.id},undefined,{timeoutMs:PUBLICATION_API_TIMEOUT_MS});
|
|
134
|
+
console.log(JSON.stringify(receipt,null,2));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function abandonDeliveryJob(jobId) {
|
|
138
|
+
if (!/^job_[a-f0-9]{24}$/.test(jobId || '')) throw new Error('abandon-delivery requires a Factory JOB_ID');
|
|
139
|
+
const branchSha = flags['branch-sha'];
|
|
140
|
+
if (!/^[a-f0-9]{40}$/.test(branchSha || '')) throw new Error('abandon-delivery requires --branch-sha with the inspected remote SHA');
|
|
141
|
+
const snapshot=await api(state,'/api/v1/status'),job=snapshot.jobs.find(item=>item.id===jobId);
|
|
142
|
+
if(!job)throw new Error('Job not found');
|
|
143
|
+
const delivery=job.delivery_status;
|
|
144
|
+
if(!delivery?.can_abandon)throw new Error('This job has no resolvable pre-write branch collision; inspect status and reconcile unresolved provider effects.');
|
|
145
|
+
if(branchSha!==delivery.remote_collision?.sha)throw new Error('The supplied branch SHA differs from current status; inspect the current remote branch before resolving.');
|
|
146
|
+
const result=await api(state,`/api/v1/jobs/${jobId}/abandon-delivery`,{
|
|
147
|
+
run_id:job.runs.at(-1)?.id,delivery_identity:delivery.identity,branch_sha:branchSha,
|
|
148
|
+
},undefined,{timeoutMs:PUBLICATION_API_TIMEOUT_MS});
|
|
149
|
+
console.log(JSON.stringify(result,null,2));
|
|
150
|
+
}
|
|
151
|
+
|
|
127
152
|
try {
|
|
128
|
-
if(command==='init') {
|
|
153
|
+
if(command==='init') {
|
|
154
|
+
if(!flags.repo)throw new Error('init requires --repo /path/to/existing/git/repo');
|
|
155
|
+
if(flags.harness && flags.agent && flags.harness !== flags.agent)throw new Error('--harness conflicts with legacy --agent');
|
|
156
|
+
const deliveryFlags=[flags['delivery-provider'],flags['delivery-repository'],flags['delivery-target']];
|
|
157
|
+
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');
|
|
158
|
+
const delivery=deliveryFlags.every(Boolean)?{provider:flags['delivery-provider'],repository:flags['delivery-repository'],target:flags['delivery-target']}:undefined;
|
|
159
|
+
init(flags.repo,flags.harness || flags.agent,flags.check,flags.port,flags['source-ref'] || 'HEAD',delivery,flags['inference-provider']);
|
|
160
|
+
}
|
|
129
161
|
else if(command==='install')await withServiceOperation('install',install);
|
|
130
162
|
else if(command==='up') { if(hasService(state))await manageService('controller','start',state);else await withServiceOperation('up',up); }
|
|
131
163
|
else if(command==='stop') { if(hasService(state))await manageService('controller','stop',state);else await withServiceOperation('stop',stop); }
|
|
@@ -159,8 +191,9 @@ try {
|
|
|
159
191
|
else if(command==='status') { const snapshot=await api(state,'/api/v1/status');delete snapshot.csrf_token;console.log(JSON.stringify(snapshot,null,2)); }
|
|
160
192
|
else if(command==='doctor') {
|
|
161
193
|
const config=configAt(state),dockerVersion=run('docker',['info','--format','{{.ServerVersion}}']),imageStatus=inspectImageInstallation(state,config);
|
|
162
|
-
|
|
163
|
-
|
|
194
|
+
const webVerification=config.webVerification?.enabled?probeWebBrowser(config,state):null;
|
|
195
|
+
console.log(JSON.stringify({node:process.version,docker:dockerVersion,engineInstalled:imageStatus.installed,image:imageStatus.image,repo:config.repo,harness:harnessOf(config),agent:harnessOf(config),checksConfigured:!!config.check?.trim(),inference:'Not called or verified',qualification:{model:'not assessed',toolchain:'not assessed'},dashboard:`http://127.0.0.1:${config.port}`,...(webVerification?{web_verification:webVerification}:{})},null,2));
|
|
196
|
+
if(!imageStatus.installed||webVerification&&!webVerification.ready)process.exitCode=1;
|
|
164
197
|
} else if(command==='issue') {
|
|
165
198
|
const action=positional[0], sourceURL=flags.url || flags.github;
|
|
166
199
|
if(action==='list') {
|
|
@@ -222,6 +255,8 @@ try {
|
|
|
222
255
|
if(!flags.file)throw new Error('Use --file incident.json; see factory/examples/incident.json');
|
|
223
256
|
console.log(JSON.stringify(await admitIncident(state,json(resolve(flags.file)),submit)));
|
|
224
257
|
} else if(['approve','cancel','retry'].includes(command))await jobAction(command);
|
|
258
|
+
else if(command==='publish')await publishJob(positional[0]);
|
|
259
|
+
else if(command==='abandon-delivery')await abandonDeliveryJob(positional[0]);
|
|
225
260
|
else if(command==='revise')await jobAction('request_changes');
|
|
226
261
|
else if(command==='demo') {
|
|
227
262
|
state=resolve(flags.state || DEFAULT_DEMO_STATE);
|
|
@@ -230,11 +265,20 @@ try {
|
|
|
230
265
|
initializeDemoRepository(repo);
|
|
231
266
|
init(repo,'mock',"test \"$(cat value.txt)\" = fixed",Number(flags.port || 7332));
|
|
232
267
|
} else if(harnessOf(configAt(state))!=='mock')throw new Error('Demo requires a mock configuration');
|
|
268
|
+
save(join(state,'synthetic-demo.json'),{version:1,createdAt:new Date().toISOString(),purpose:'Disposable Factory runtime qualification only'});
|
|
233
269
|
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.')));
|
|
234
270
|
console.log('Review the synthetic change in the dashboard and approve its handoff.');
|
|
235
271
|
} else if(['version','--version','-v'].includes(command))console.log(VERSION);
|
|
236
272
|
else if(command==='qualify') {
|
|
237
273
|
await stream(process.execPath,[join(ROOT,'scripts/probe-platform.mjs'),state]);
|
|
274
|
+
} else if(command==='qualify-web') {
|
|
275
|
+
if(!flags.image)throw new Error('qualify-web requires --image sha256:<local browser image ID>');
|
|
276
|
+
await stream(process.execPath,[join(ROOT,'scripts/probe-web.mjs'),state,flags.image]);
|
|
277
|
+
} else if(command==='web') {
|
|
278
|
+
if(positional[0]!=='probe'||positional.length!==1)throw new Error('Use web probe to execute the configured browser readiness check');
|
|
279
|
+
const readiness=probeWebBrowser(configAt(state),state);
|
|
280
|
+
console.log(JSON.stringify(readiness,null,2));
|
|
281
|
+
if(readiness.enabled&&!readiness.ready)process.exitCode=1;
|
|
238
282
|
} else if(command==='kit') {
|
|
239
283
|
if(!flags.output)throw new Error('kit requires --output NEW_DIRECTORY');
|
|
240
284
|
await stream(process.execPath,[join(ROOT,'scripts/export-kit.mjs'),resolve(flags.output)]);
|
|
@@ -243,9 +287,13 @@ try {
|
|
|
243
287
|
kit --output NEW_DIRECTORY Export the portable method without a runtime
|
|
244
288
|
demo Install and run a synthetic sample (no model key)
|
|
245
289
|
qualify --state PATH Exercise recovery and isolation with a stopped demo job
|
|
290
|
+
qualify-web --state PATH --image ID Exercise Playwright Verify with a synthetic delayed-action fixture
|
|
246
291
|
init --repo PATH --harness codex|pi|custom --check "npm ci && npm test" [--source-ref REF]
|
|
292
|
+
[--inference-provider PROVIDER]
|
|
293
|
+
[--delivery-provider github --delivery-repository https://github.com/OWNER/REPO --delivery-target main|dev]
|
|
247
294
|
install [--image LOCAL_REF] Build the standard image, or select an existing local image
|
|
248
295
|
doctor | up | status | stop Inspect / operate your private installation
|
|
296
|
+
web probe --state PATH Execute the pinned local Chromium readiness probe
|
|
249
297
|
foundation Read the operator setup skill; no installation required
|
|
250
298
|
definition | agents | skills Inspect roles, instructions and installation settings
|
|
251
299
|
inbox | infrastructure | automations Inspect live tasks, host/worker and automation state
|
|
@@ -280,6 +328,8 @@ try {
|
|
|
280
328
|
[--source-ref REF] Pin a configured-repository ref before admission
|
|
281
329
|
incident --file incident.json Submit a private, read-only incident draft
|
|
282
330
|
approve JOB_ID | cancel JOB_ID Review gate / stop this attempt
|
|
331
|
+
publish JOB_ID Publish/reconcile the accepted candidate as one draft PR
|
|
332
|
+
abandon-delivery JOB_ID --branch-sha SHA Resolve an inspected pre-write collision; keep the remote branch
|
|
283
333
|
retry JOB_ID Prove stop; retain old checkout and retry
|
|
284
334
|
revise JOB_ID --file feedback.md [--source-ref REF]
|
|
285
335
|
New build/check/review; source stays pinned unless a new ref is explicit
|
|
@@ -291,7 +341,7 @@ Runtime commands accept --state PATH. Default: ${DEFAULT_STATE}
|
|
|
291
341
|
Demo default: ${DEFAULT_DEMO_STATE}
|
|
292
342
|
The npm CLI keeps state outside the package; updates wait for stopped installations.
|
|
293
343
|
Dashboard binds only to loopback; use SSH for remote access.
|
|
294
|
-
Setup plan: ${join(ROOT, 'docs/setup.md')}
|
|
344
|
+
Setup plan: ${join(ROOT, 'docs/setup.md')}
|
|
295
345
|
See docs/quickstart.md for task execution, evidence and recovery.`);
|
|
296
346
|
else throw new Error(`Unknown command: ${command}`);
|
|
297
347
|
} catch(error) {console.error(`Factory: ${error.message}`);process.exitCode=1;}
|
package/docs/interfaces.md
CHANGED
|
@@ -33,7 +33,8 @@ shell endpoint or a second scheduler.
|
|
|
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
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 |
|
|
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
|
+
| Optional trusted web verification | `web probe` performs a real local Chromium interaction; `doctor` reports readiness | Verify stores a shared story summary and protected JSON artifact in the run | Task history shows passed/failed/unavailable/inconclusive plus tool, candidate, policy and story hashes | Disabled by default. Required operator stories and Playwright/Chromium image ID are frozen in attempt policy. Linux Chromium proof cannot qualify native/mobile OS behavior; see [the browser contract](web-verification.md) |
|
|
37
38
|
|
|
38
39
|
The current generic task form can name the Defence workflow; that is not a
|
|
39
40
|
substitute for the CLI's validated incident admission. Treat the typed intake
|
package/docs/quickstart.md
CHANGED
|
@@ -28,15 +28,47 @@ 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
|
-
`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.
|
|
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
|
-
Configure inference
|
|
38
|
-
|
|
39
|
-
|
|
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.
|
|
40
72
|
|
|
41
73
|
```sh
|
|
42
74
|
software-defence-factory up --state /private/state/my-app
|
|
@@ -51,6 +83,86 @@ Inspect the task's Result, Files and History tabs. Task details show the request
|
|
|
51
83
|
|
|
52
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.
|
|
53
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
|
+
|
|
54
166
|
## Remote access and operation
|
|
55
167
|
|
|
56
168
|
```sh
|
|
@@ -109,8 +221,11 @@ is tested at 100%; changing browser zoom is separate from a project theme.
|
|
|
109
221
|
## Environment
|
|
110
222
|
|
|
111
223
|
Factory does not load a repository `.env` file. Configure the private
|
|
112
|
-
`factory.json` through `init`; put inference
|
|
113
|
-
`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
|
|
114
229
|
process settings are `SDF_AUTO_UPDATE=0` (skip automatic CLI update checks),
|
|
115
230
|
`XDG_STATE_HOME`, `XDG_DATA_HOME` and `XDG_CONFIG_HOME` (user-owned state, release
|
|
116
231
|
and service locations). They must be exported in the process environment.
|
package/docs/recovery.md
CHANGED
|
@@ -6,7 +6,8 @@ Use `status --state PATH` and the private supervisor.log to identify the active
|
|
|
6
6
|
- An unconfirmed running attempt becomes `interrupted` on controller restart. It is never silently considered successful.
|
|
7
7
|
- Admission resolves the configured source ref or an explicit `--source-ref` in the configured repository, records its identity/ref/SHA in private job metadata and retains its Git objects under that job. Build and retry restore from this retained revision; moving or deleting the source ref does not select a new commit.
|
|
8
8
|
- `retry JOB_ID` verifies retained objects and reconciles the previous process group and containers. A missing/corrupt retained source, live writer or unknown process blocks retry. For a new build/defence attempt, the prior checkout is retained as previous-checkout-*.
|
|
9
|
-
- A
|
|
9
|
+
- A forced host/controller stop can leave the selected inference env file in the private attempt directory. Normal executor cleanup removes it only after a Docker listing confirms the named container is absent. Retry/reconciliation removes a leftover copy only after the prior process group is confirmed stopped and a Docker listing confirms the labelled containers are absent; a `docker rm` or `docker stop` exit alone does not prove shutdown. If identity, a Docker probe or shutdown is uncertain, the recovery fence and file remain; do not clean it manually while a worker may still be active. After shutdown is confirmed, recovery also redacts the fixed phase reports and log using the selected environment values before removing the env file.
|
|
10
|
+
- Retry repeats the stopped phase. A failed verification may retry the same candidate when its policy is unchanged and the check can now pass. If the check or execution policy changed after build, retrying verify/review cannot reuse the earlier build for handoff; handoff remains blocked. Preserve that failed attempt, then use an eligible requested revision or submit a replacement task to build under the current policy. Do not edit SQLite or acceptance evidence to bypass the guard.
|
|
10
11
|
- A requested revision keeps the recorded source by default, retains previous evidence and starts a new build with accumulated feedback. Supplying a deliberate new `--source-ref` resolves and retains that base before the action; prior source metadata stays in history, and the new build gets fresh checks and review.
|
|
11
12
|
- Removing a stopped task from the dashboard hides its queue record. Private artifacts and its deleted_at record remain on disk; this is not secure erasure.
|
|
12
13
|
|
|
@@ -17,12 +18,32 @@ For backup, stop the installation and copy the complete private state directory,
|
|
|
17
18
|
Earlier experimental engines use a different journal. Start a new state directory for the native 0.3 runtime; preserve old journals separately. There is no automatic import of their jobs or approval state.
|
|
18
19
|
|
|
19
20
|
Verification cleanup makes owned scratch directories traversable before removing
|
|
20
|
-
them and never follows their symlinks.
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
them and never follows their symlinks. The executor and recovery paths remove
|
|
22
|
+
`check-workspace` only after Docker confirms the named container is absent; a
|
|
23
|
+
client exit, failed remove or failed/unknown probe does not establish shutdown.
|
|
24
|
+
While shutdown is uncertain, the scratch directory and active recovery fence
|
|
25
|
+
remain. Ordinary reconciliation removes scratch after the process and labelled
|
|
26
|
+
containers are confirmed stopped. If a filesystem error still prevents cleanup,
|
|
27
|
+
the attempt fails and retains the original check exit and private log path
|
|
28
|
+
alongside the cleanup error.
|
|
23
29
|
Inspect that retained attempt before manual removal; never substitute the source
|
|
24
30
|
candidate path for the scratch path.
|
|
25
31
|
|
|
32
|
+
When trusted web verification is enabled, Verify keeps the copied check output
|
|
33
|
+
as disposable preview scratch and creates separate preview and browser
|
|
34
|
+
containers. The preview uses `network none`; the browser shares only that exact
|
|
35
|
+
preview's isolated network namespace for loopback. Their mounts and PID
|
|
36
|
+
namespaces remain separate, and only the browser mounts its private result and
|
|
37
|
+
screenshot directory. The `web-policy.json`, preview scratch, browser output
|
|
38
|
+
and active fence remain until both exact labelled containers are confirmed
|
|
39
|
+
removed. Normal stop/retry recovery reconciles all Factory-labelled containers
|
|
40
|
+
before removing those files. An unknown stop, deadline or cancellation keeps
|
|
41
|
+
the state and fence for recovery. A missing browser, unsupported Linux
|
|
42
|
+
capability, interrupted story or malformed evidence blocks acceptance; never
|
|
43
|
+
retry around a retained writer or reuse earlier story evidence for a changed
|
|
44
|
+
candidate or policy. Browser traces and screenshots are private evidence and
|
|
45
|
+
are presented through the shared interfaces. See [the web contract](web-verification.md).
|
|
46
|
+
|
|
26
47
|
## Review feedback or phase retry
|
|
27
48
|
|
|
28
49
|
Use **Request changes** on a failed software review only when its validated
|
|
@@ -67,6 +88,24 @@ agent reports, passing checks or review. Nonzero exits and missing/malformed
|
|
|
67
88
|
reports still fail closed. Process termination during a host crash can leave
|
|
68
89
|
incomplete logs and an interrupted attempt; apply process reconciliation above.
|
|
69
90
|
|
|
91
|
+
Before retention, Docker stdout/stderr logs have exact selected inference
|
|
92
|
+
credential values replaced. Mounted reports (`agent-report.md`, `review.json`,
|
|
93
|
+
`incident-report.json`) are sanitized after the phase container is confirmed
|
|
94
|
+
absent; if shutdown is uncertain, they remain in the private attempt directory
|
|
95
|
+
and ordinary recovery sanitizes them after confirming the stop. Filtering
|
|
96
|
+
includes selected API-key, token, secret, password and credential settings plus
|
|
97
|
+
credential fields inside selected Codex auth JSON. Reports are opened without
|
|
98
|
+
following symlinks and with nonblocking access before descriptor type checks, so
|
|
99
|
+
special files such as FIFOs are refused promptly. An unsafe or oversized report
|
|
100
|
+
cannot be promoted. If filtering cannot complete, recovery retains the selected
|
|
101
|
+
inference file and active fence; a repeated recovery remains blocked until the
|
|
102
|
+
fixed owned report can be safely filtered. Once process and container shutdown
|
|
103
|
+
are confirmed, repair or replace that report and retry ordinary recovery so it
|
|
104
|
+
can sanitize the output before removing the selected file and fence. This
|
|
105
|
+
bounded filter does not detect encoded or transformed values and does not
|
|
106
|
+
rewrite candidate files or patches; it is not a general data loss prevention
|
|
107
|
+
control.
|
|
108
|
+
|
|
70
109
|
## Recorded execution profiles
|
|
71
110
|
|
|
72
111
|
Before each attempt executes, the controller freezes its effective configuration
|
|
@@ -126,3 +165,159 @@ Use `issue submissions` and `issue recover --key REQUEST_ID` against the same
|
|
|
126
165
|
controller state. Recovery reads the original provider using the original
|
|
127
166
|
identity; it does not publish again. Do not use a new request key to retry an
|
|
128
167
|
uncertain write. See [provider ownership and recovery limits](integrations.md).
|
|
168
|
+
|
|
169
|
+
## Trusted PR delivery
|
|
170
|
+
|
|
171
|
+
Delivery is available only for an accepted software job with current source,
|
|
172
|
+
candidate, check, independent review and approval records. Trusted publication
|
|
173
|
+
also requires matching private per-run execution records: native Codex/Pi for
|
|
174
|
+
build and review, deterministic verification and handoff, and explicitly
|
|
175
|
+
non-synthetic bound candidate/check/review artifacts. Missing, unknown,
|
|
176
|
+
synthetic or inconsistent evidence is not publication proof. Status, CLI, API
|
|
177
|
+
and dashboard use the same guard. The private operator configuration pins the
|
|
178
|
+
GitHub repository and target; task text and worker reports cannot provide
|
|
179
|
+
either. The controller stores the intent and remote branch/PR identifiers in
|
|
180
|
+
the job row before it writes. Status and task details show the receipt
|
|
181
|
+
separately from integration or deployment.
|
|
182
|
+
|
|
183
|
+
Inspect the same installation before recovery:
|
|
184
|
+
|
|
185
|
+
```sh
|
|
186
|
+
software-defence-factory status --state /private/state/project
|
|
187
|
+
software-defence-factory publish JOB_ID --state /private/state/project
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
After a lost response or controller restart, repeat `publish`. The controller
|
|
191
|
+
reads the exact configured target, generated branch and PR before attempting a
|
|
192
|
+
missing stage. A matching branch/PR is reused; a different remote head, target,
|
|
193
|
+
repository or collision is preserved and blocks recovery. It never force-pushes,
|
|
194
|
+
rebases, creates a second PR to avoid an uncertain response, or merges. Restore
|
|
195
|
+
the original trusted repository/target configuration if it changed; do not
|
|
196
|
+
redirect a saved intent.
|
|
197
|
+
|
|
198
|
+
Before any new content, branch or PR write, a saved intent is rechecked against
|
|
199
|
+
the protected run profiles and linked artifacts. A mock/synthetic record cannot
|
|
200
|
+
authorize a new write on retry. A known receipt or PR-creation checkpoint can
|
|
201
|
+
still use read-only provider reconciliation; if that cannot establish the
|
|
202
|
+
existing effect, the uncertain record remains blocked from further writes.
|
|
203
|
+
|
|
204
|
+
Before advertising or attempting a new write, status and the shared publisher
|
|
205
|
+
qualify the exact accepted Git trees again. Factory reads the immutable admitted
|
|
206
|
+
base commit and reconstructs the accepted candidate tree from the protected,
|
|
207
|
+
digest-bound patch. Added, changed, deleted or symlinked
|
|
208
|
+
`.github/workflows/*.yml` and `*.yaml` definitions block trusted publication.
|
|
209
|
+
Every base workflow must parse with the bundled YAML parser and use the
|
|
210
|
+
supported literal `push`, `pull_request`, `pull_request_target` or
|
|
211
|
+
`workflow_dispatch` trigger subset; dispatch inputs and other events such as
|
|
212
|
+
`workflow_run` or `workflow_call` are unsupported. A manual dispatch is
|
|
213
|
+
evaluated with the generated branch as its selected ref, as GitHub permits
|
|
214
|
+
dispatching a workflow against a selected branch ([manual workflow runs](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/manually-run-a-workflow)).
|
|
215
|
+
Any declared PR activity type is treated as potentially active for that
|
|
216
|
+
candidate PR. Branch filters are interpreted only when their patterns contain
|
|
217
|
+
ASCII letters, digits, `.`, `_`, `-` or `/`; exact literals are checked against
|
|
218
|
+
the generated branch and configured target. Other patterns are treated as
|
|
219
|
+
possible matches. This includes `*`, `**`, `?`, `+`, `[]`, `!` and escaped
|
|
220
|
+
characters from GitHub's [filter syntax](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#filter-pattern-cheat-sheet).
|
|
221
|
+
Path filters are also treated as possible matches.
|
|
222
|
+
|
|
223
|
+
Conditions support only `&&`-joined `==`/`!=` comparisons against quoted
|
|
224
|
+
strings using `github.ref`, `github.event_name`, `github.head_ref`,
|
|
225
|
+
`github.base_ref` or `vars.NAME`; `||`, functions and other contexts are
|
|
226
|
+
refused. Supported ASCII string comparisons follow GitHub's case-insensitive
|
|
227
|
+
[expression semantics](https://docs.github.com/en/actions/reference/workflows-and-actions/expressions).
|
|
228
|
+
Non-ASCII mismatches stay unknown rather than guessing Unicode case folding.
|
|
229
|
+
For `pull_request`, `github.ref` stays unknown because the future merge ref
|
|
230
|
+
cannot be established from a guessed PR number. Unknown comparisons cannot
|
|
231
|
+
prove a privileged job inactive. Dynamic permissions, malformed YAML and
|
|
232
|
+
ambiguous guards block publication with a reason while leaving patch delivery
|
|
233
|
+
available.
|
|
234
|
+
|
|
235
|
+
Each job that could run for the generated branch push, a branch-selected
|
|
236
|
+
manual dispatch or a PR event must have explicit effective permissions limited
|
|
237
|
+
to `contents: read` or `none`, a known GitHub-hosted runner, and no secret
|
|
238
|
+
context, protected environment, reusable workflow call, OIDC permission or
|
|
239
|
+
deployment permission. A privileged release job can qualify only when a simple
|
|
240
|
+
literal conjunction proves it cannot run for each matching event. The current
|
|
241
|
+
`ci.yml` main-only release guard is supported. Before any content, branch or PR
|
|
242
|
+
write, the existing provider readback still confirms that the configured
|
|
243
|
+
target commit and tree are the exact accepted base. Active jobs must use one of
|
|
244
|
+
the supported literal runner labels (`ubuntu-latest`,
|
|
245
|
+
`ubuntu-22.04`, `ubuntu-24.04`, `ubuntu-26.04`, `windows-latest`,
|
|
246
|
+
`windows-2022`, `windows-2025`, `macos-latest`, `macos-14`, `macos-15` or
|
|
247
|
+
`macos-26`). External action steps must be local or pinned to an immutable
|
|
248
|
+
commit SHA or container digest.
|
|
249
|
+
|
|
250
|
+
This bounded check does not establish that all CI is safe. It does not inspect
|
|
251
|
+
repository/organization rules, webhooks, external CI or other organization
|
|
252
|
+
automation, nor does it audit the behavior of referenced third-party actions.
|
|
253
|
+
Unsupported cases refuse trusted publication and leave normal local checks,
|
|
254
|
+
review, approval and manual patch delivery available.
|
|
255
|
+
|
|
256
|
+
After publication is confirmed, readback follows the saved PR identity and
|
|
257
|
+
records its current open draft/ready, closed or merged state and checks. This
|
|
258
|
+
read-only refresh does not require the source branch to remain after closure or
|
|
259
|
+
the PR base commit to equal today's target commit. It still checks the saved
|
|
260
|
+
repository, target and branch names, PR head, and accepted delivery commit's
|
|
261
|
+
tree and parent. A changed identity or candidate stays a visible conflict; a
|
|
262
|
+
provider outage retains the last known publication receipt. First publication
|
|
263
|
+
continues to require the accepted current base and an open draft PR.
|
|
264
|
+
|
|
265
|
+
The dashboard offers reconciliation for saved `intent`, `publishing`,
|
|
266
|
+
`uncertain` and safely retryable `blocked` checkpoints. Legacy accepted jobs
|
|
267
|
+
without candidate-bound check, review and approval evidence are labelled
|
|
268
|
+
unverified and cannot be published. Deleting an issue with any unconfirmed
|
|
269
|
+
delivery record is blocked in the dashboard and controller API; inspect or
|
|
270
|
+
reconcile the saved remote effect before removing the job. A confirmed
|
|
271
|
+
`published` receipt is no longer unresolved.
|
|
272
|
+
|
|
273
|
+
The shared capability advertises new publication only with ready evidence and
|
|
274
|
+
no saved delivery or a known resumable write state. A branch-only `conflict`
|
|
275
|
+
has no publish action; inspect it and use explicit local abandonment only
|
|
276
|
+
after the exact branch head and absence of an associated PR are confirmed.
|
|
277
|
+
Conflicts with a saved PR receipt and pending PR-creation checkpoints retain a
|
|
278
|
+
read-only reconciliation action. Unknown and abandoned delivery states cannot
|
|
279
|
+
advertise or start publication.
|
|
280
|
+
|
|
281
|
+
The shared delivery status marks each available PR action as `publish` or
|
|
282
|
+
`reconcile`; task details use that mode for idle and pending button wording.
|
|
283
|
+
While a request is pending, publication shows “Publishing…” and read-only
|
|
284
|
+
recovery shows “Reconciling…”. A conflicted record with a saved PR receipt
|
|
285
|
+
therefore stays visibly a readback action.
|
|
286
|
+
|
|
287
|
+
A collision found while the record is still at `intent` is known to precede
|
|
288
|
+
provider writes. Inspect the exact branch in GitHub, then use the task action
|
|
289
|
+
**Abandon local delivery; keep remote branch**, or confirm its current head with
|
|
290
|
+
the CLI:
|
|
291
|
+
|
|
292
|
+
```sh
|
|
293
|
+
software-defence-factory status --state /private/state/project
|
|
294
|
+
software-defence-factory abandon-delivery JOB_ID --branch-sha INSPECTED_SHA --state /private/state/project
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
The controller checks the latest run, saved delivery identity, current branch
|
|
298
|
+
head and exact repository/target, then confirms that no PR is attached. It
|
|
299
|
+
records the inspected identity and local resolution in the job row. It does not
|
|
300
|
+
write, update or delete the remote branch or create/accept a PR. The `abandoned`
|
|
301
|
+
state disables later publication and permits local issue removal while retaining
|
|
302
|
+
the delivery record, run history and evidence. A stale or changed branch, any
|
|
303
|
+
associated/unknown PR, changed destination, later delivery stage or uncertain
|
|
304
|
+
provider effect remains blocked; reconcile or inspect it before taking another
|
|
305
|
+
action.
|
|
306
|
+
|
|
307
|
+
CLI publication and branch-resolution requests have a ten-minute deadline
|
|
308
|
+
because they can require multiple provider requests. Other CLI API requests
|
|
309
|
+
keep their five-second deadline. If the client reaches its deadline, inspect
|
|
310
|
+
`status` after the controller action finishes. Repeat `publish` to reconcile a
|
|
311
|
+
publication; repeat `abandon-delivery` only after inspecting the currently
|
|
312
|
+
reported branch identity.
|
|
313
|
+
|
|
314
|
+
The receipt reads the exact PR head's check runs and commit statuses. Raw
|
|
315
|
+
conclusions are retained. `success`, `skipped` and `neutral` do not make the
|
|
316
|
+
aggregate fail, while skipped/neutral are distinct from an executed passing
|
|
317
|
+
check. `pending`, `unknown`, failed and unavailable remain distinct; unknown
|
|
318
|
+
conclusions and incomplete pagination stay unknown. This aggregate does not
|
|
319
|
+
establish required-check completeness or merge authorization. A target/base,
|
|
320
|
+
candidate or policy change requires fresh applicable checks, review and
|
|
321
|
+
approval; do not edit SQLite or acceptance evidence to bypass the guard. A stale
|
|
322
|
+
target after branch creation leaves that unique branch for inspection and does
|
|
323
|
+
not open a PR. A PR does not imply integration or deployment.
|