software-defence-factory 0.5.1 → 0.7.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/README.md +1 -1
- package/bin/software-defence-factory.mjs +52 -33
- package/docs/concepts.md +13 -8
- package/docs/integrations.md +67 -0
- package/docs/interfaces.md +10 -7
- package/docs/quickstart.md +7 -6
- package/docs/recovery.md +31 -4
- package/docs/setup.md +2 -2
- package/docs/workflows.md +52 -26
- package/factory/definition.mjs +1 -1
- package/factory/demo-fixture.mjs +11 -0
- package/factory/executor.mjs +18 -8
- package/factory/git-environment.mjs +26 -0
- package/factory/issue-provider.mjs +26 -0
- package/factory/issue-submissions.mjs +65 -0
- package/factory/lib.mjs +2 -2
- package/factory/processes.mjs +2 -1
- package/factory/project-links.mjs +3 -4
- package/factory/providers/github.mjs +77 -0
- package/factory/queue.mjs +56 -13
- package/factory/server.mjs +25 -12
- package/factory/source-admission.mjs +210 -0
- package/factory/terminology.json +3 -2
- package/factory/ui/assets/{index-qHVJlLfq.css → index-Bz6ECdy_.css} +1 -1
- package/factory/ui/assets/index-DqeOnrGK.js +13 -0
- package/factory/ui/index.html +2 -2
- package/kit/repository.md +14 -7
- package/operator-skills/factory-foundation/SKILL.md +11 -5
- package/package.json +4 -2
- package/scripts/probe-platform.mjs +58 -1
- package/scripts/probe-review.mjs +3 -2
- package/scripts/retained-source-fixture.mjs +55 -0
- package/factory/ui/assets/index-Ci1BkkmE.js +0 -13
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ The runtime supplies policy and six focused skills to its isolated jobs. `init`
|
|
|
37
37
|
|
|
38
38
|
Each result belongs to a specific candidate commit and policy. A failed check blocks delivery. Changing the candidate or check policy invalidates earlier evidence. Approval records a handoff; publishing, merging and deployment follow the application's separate authority.
|
|
39
39
|
|
|
40
|
-
The project dashboard has an **Inbox**, measured **Analytics**, **Agents**, **Skills**, **Automations**, **Definition** and **Infrastructure**. New issue offers the repository’s issue templates, a blank local form or a selectable GitHub issue.
|
|
40
|
+
The project dashboard has an **Inbox**, measured **Analytics**, **Agents**, **Skills**, **Automations**, **Definition** and **Infrastructure**. New issue offers the repository’s issue templates, a blank local form or a selectable GitHub issue. Create an issue on the supported repository provider, then choose Start work separately; local brief execution remains available. CLI `issue` exposes the same intake. Definition lives with settings above the theme control. The CLI reads the same definition and controller state. Agent roles use a selected harness such as Codex or Pi; a worker executes their isolated jobs on a host. See [concepts](docs/concepts.md) and [supported interfaces](docs/interfaces.md). Optional automations belong to the selected harness, which calls Factory CLI/API. Factory runs no cron scheduler. See [provider boundaries](docs/integrations.md).
|
|
41
41
|
|
|
42
42
|
The optional **defence** workflow accepts scoped incident evidence and produces a private, read-only draft. It does not monitor production or claim verified recovery. See [defence integration](docs/defence-integration.md).
|
|
43
43
|
|
|
@@ -7,7 +7,6 @@ import { createServer } from 'node:net';
|
|
|
7
7
|
import { ROOT, PINS, DEFAULT_STATE, configAt, save, json, run, stream, digest, api, sleep, stopContainers } 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
|
-
import { readTemplates, draftFromTemplate } from '../factory/issue-templates.mjs';
|
|
11
10
|
import { recommendWork } from '../factory/intake.mjs';
|
|
12
11
|
import { factoryDefinition, foundationSkill } from '../factory/definition.mjs';
|
|
13
12
|
import { harnessOf } from '../factory/lib.mjs';
|
|
@@ -15,6 +14,8 @@ import { admitIncident } from '../factory/incident.mjs';
|
|
|
15
14
|
import { DEFAULT_DEMO_STATE } from '../factory/paths.mjs';
|
|
16
15
|
import { bootstrap, registerInstallation, VERSION } from '../factory/updates.mjs';
|
|
17
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';
|
|
18
19
|
|
|
19
20
|
try {
|
|
20
21
|
const handled = await bootstrap(process.argv.slice(2));
|
|
@@ -33,18 +34,18 @@ let state = resolve(flags.state || DEFAULT_STATE);
|
|
|
33
34
|
if(existsSync(state))state=realpathSync(state);
|
|
34
35
|
const alive = pid => { try { process.kill(pid,0); return true; } catch(error) { if(error.code === 'ESRCH')return false; throw error; } };
|
|
35
36
|
|
|
36
|
-
function init(repo, harness='codex', check='', port=7331) {
|
|
37
|
+
function init(repo, harness='codex', check='', port=7331, sourceRef='HEAD') {
|
|
37
38
|
repo=realpathSync(resolve(repo));
|
|
38
39
|
if (existsSync(join(state,'factory.json'))) throw new Error('Already configured; edit the private factory.json explicitly or choose another --state');
|
|
39
40
|
if ([repo,state,ROOT].some(p=>/[,\n\r]/.test(p))) throw new Error('Paths cannot contain commas or line breaks');
|
|
40
|
-
if (
|
|
41
|
-
|
|
41
|
+
if (runCandidateGit(repo,'rev-parse','--show-toplevel') !== repo) throw new Error('--repo must be the Git root');
|
|
42
|
+
runCandidateGit(repo,'rev-parse','HEAD');
|
|
42
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']};
|
|
43
44
|
const argv=harness==='custom'?JSON.parse(flags['command-json'] || 'null'):presets[harness];
|
|
44
45
|
if (!argv) throw new Error('Select codex, pi, mock or custom with --command-json');
|
|
45
46
|
if (flags.model && ['codex','pi'].includes(harness)) argv.splice(harness==='codex'?argv.length-1:argv.length,0,'--model',flags.model);
|
|
46
47
|
mkdirSync(state,{recursive:true,mode:0o700});state=realpathSync(state);chmodSync(state,0o700);
|
|
47
|
-
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,
|
|
48
49
|
scope:{project:'pilot',service:'app',environment:'test',owner:'operator'}});
|
|
49
50
|
configAt(state);
|
|
50
51
|
writeFileSync(join(state,'worker.token'),randomBytes(32).toString('hex')+'\n',{mode:0o600});
|
|
@@ -99,10 +100,10 @@ async function stop() {
|
|
|
99
100
|
}
|
|
100
101
|
stopContainers(state);console.log('Controller and its labelled containers stopped.');
|
|
101
102
|
}
|
|
102
|
-
async function submit(workflow,spec) {
|
|
103
|
+
async function submit(workflow,spec,sourceRef=flags['source-ref']) {
|
|
103
104
|
if(Buffer.byteLength(spec)>240000)throw new Error('Task exceeds 240 KB');
|
|
104
105
|
const title=workflow==='defence'?'Private incident triage':spec.split('\n').find(s=>s.trim())?.replace(/^#+\s*/, '').slice(0,100)||'Software task';
|
|
105
|
-
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})});
|
|
106
107
|
}
|
|
107
108
|
async function jobAction(action) {
|
|
108
109
|
const id=positional[0];if(!/^job_[a-z0-9]+$/.test(id || ''))throw new Error('A job ID is required');
|
|
@@ -119,12 +120,12 @@ async function jobAction(action) {
|
|
|
119
120
|
}
|
|
120
121
|
// The controller validates the current run and owns reconciliation atomically.
|
|
121
122
|
// A CLI-side stop after a stale snapshot could terminate a newer attempt.
|
|
122
|
-
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']})});
|
|
123
124
|
console.log(`${action}: ${id}`);
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
try {
|
|
127
|
-
if(command==='init') { if(!flags.repo)throw new Error('init requires --repo /path/to/existing/git/repo');if(flags.harness && flags.agent && flags.harness !== flags.agent)throw new Error('--harness conflicts with legacy --agent');init(flags.repo,flags.harness || flags.agent,flags.check,flags.port); }
|
|
128
|
+
if(command==='init') { if(!flags.repo)throw new Error('init requires --repo /path/to/existing/git/repo');if(flags.harness && flags.agent && flags.harness !== flags.agent)throw new Error('--harness conflicts with legacy --agent');init(flags.repo,flags.harness || flags.agent,flags.check,flags.port,flags['source-ref'] || 'HEAD'); }
|
|
128
129
|
else if(command==='install')await withServiceOperation('install',install);
|
|
129
130
|
else if(command==='up') { if(hasService(state))await manageService('controller','start',state);else await withServiceOperation('up',up); }
|
|
130
131
|
else if(command==='stop') { if(hasService(state))await manageService('controller','stop',state);else await withServiceOperation('stop',stop); }
|
|
@@ -153,7 +154,7 @@ try {
|
|
|
153
154
|
}
|
|
154
155
|
else if(['infrastructure','automations','inbox'].includes(command)) {
|
|
155
156
|
const snapshot=await api(state,'/api/v1/status');
|
|
156
|
-
console.log(JSON.stringify(command==='inbox'?snapshot.jobs:snapshot[command],null,2));
|
|
157
|
+
console.log(JSON.stringify(command==='inbox'?snapshot.jobs:command==='automations'?snapshot.automation_control:snapshot[command],null,2));
|
|
157
158
|
}
|
|
158
159
|
else if(command==='status') { const snapshot=await api(state,'/api/v1/status');delete snapshot.csrf_token;console.log(JSON.stringify(snapshot,null,2)); }
|
|
159
160
|
else if(command==='doctor') {
|
|
@@ -161,32 +162,46 @@ try {
|
|
|
161
162
|
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}`},null,2));
|
|
162
163
|
if(!imageStatus.installed)process.exitCode=1;
|
|
163
164
|
} else if(command==='issue') {
|
|
164
|
-
const action=positional[0];
|
|
165
|
+
const action=positional[0], sourceURL=flags.url || flags.github;
|
|
165
166
|
if(action==='list') {
|
|
166
|
-
if(flags.source && !['factory','github'].includes(flags.source))throw new Error('Choose --source factory or
|
|
167
|
-
console.log(JSON.stringify(flags.source
|
|
168
|
-
} else if(action==='templates') console.log(JSON.stringify(await
|
|
167
|
+
if(flags.source && !['factory','remote','github'].includes(flags.source))throw new Error('Choose --source factory or remote');
|
|
168
|
+
console.log(JSON.stringify(['github','remote'].includes(flags.source) ? await api(state,`/api/v1/issues?page=${encodeURIComponent(flags.page || 1)}`) : (await api(state,'/api/v1/status')).jobs,null,2));
|
|
169
|
+
} else if(action==='templates') console.log(JSON.stringify(await api(state,'/api/v1/issue-templates'),null,2));
|
|
170
|
+
else if(action==='connection') console.log(JSON.stringify(await api(state,'/api/v1/issue-connection'),null,2));
|
|
171
|
+
else if(action==='submissions') console.log(JSON.stringify(await api(state,'/api/v1/issue-submissions'),null,2));
|
|
172
|
+
else if(action==='recover') {
|
|
173
|
+
if(!/^[A-Za-z0-9_-]{16,100}$/.test(flags.key || ''))throw new Error('Use --key with the saved request ID');
|
|
174
|
+
console.log(JSON.stringify(await api(state,`/api/v1/issue-submissions/${flags.key}/recover`,{}),null,2));
|
|
175
|
+
}
|
|
169
176
|
else if(action==='preview') {
|
|
170
|
-
if(!
|
|
171
|
-
console.log(JSON.stringify(await
|
|
177
|
+
if(!sourceURL)throw new Error('Use --url ISSUE_URL');
|
|
178
|
+
console.log(JSON.stringify(await api(state,'/api/v1/issues/preview',{url:sourceURL}),null,2));
|
|
172
179
|
} else if(action==='recommend') {
|
|
173
|
-
if(Boolean(
|
|
174
|
-
console.log(JSON.stringify(
|
|
180
|
+
if(Boolean(sourceURL)===Boolean(flags.file))throw new Error('Choose --file brief.md or --url ISSUE_URL');
|
|
181
|
+
console.log(JSON.stringify(sourceURL ? (await api(state,'/api/v1/issues/preview',{url:sourceURL})).recommendation : recommendWork({spec:readFileSync(resolve(flags.file),'utf8')}),null,2));
|
|
175
182
|
} else if(action==='draft') {
|
|
176
183
|
if(!flags.file||!flags.template||!flags.sha)throw new Error('Use --template NAME --sha SHA --file answers.json with {title, answers}');
|
|
177
|
-
console.log(JSON.stringify(await
|
|
184
|
+
console.log(JSON.stringify(await api(state,'/api/v1/issue-templates/draft',{...json(resolve(flags.file)),template:flags.template,sha:flags.sha}),null,2));
|
|
178
185
|
} else if(action==='create') {
|
|
186
|
+
if(flags.workflow||sourceURL)throw new Error('issue create publishes a new repository issue. Use issue start for execution.');
|
|
187
|
+
if(!flags.key)throw new Error('Provide a stable --key for safe retry and recovery.');
|
|
188
|
+
if(Boolean(flags.draft)===Boolean(flags.file))throw new Error('Choose --draft draft.json or --file brief.md --title TITLE');
|
|
189
|
+
const draft=flags.draft ? json(resolve(flags.draft)) : {title:flags.title,spec:readFileSync(resolve(flags.file),'utf8'),labels:[]};
|
|
190
|
+
const connection=await api(state,'/api/v1/issue-connection');
|
|
191
|
+
if(!connection.supported)throw new Error('No issue provider is available. Use issue start for a local brief.');
|
|
192
|
+
console.log(JSON.stringify(await api(state,'/api/v1/issues',{title:flags.title || draft.title,spec:draft.spec,labels:draft.labels || [],request_id:flags.key,repository:connection.repository,actor:connection.actor}),null,2));
|
|
193
|
+
} else if(action==='start') {
|
|
179
194
|
if(!['software','defence'].includes(flags.workflow))throw new Error('Review the issue and choose --workflow software or defence');
|
|
180
|
-
if([flags.file,
|
|
195
|
+
if([flags.file,sourceURL,flags.draft].filter(Boolean).length!==1)throw new Error('Choose --file brief.md, --draft draft.json or --url ISSUE_URL');
|
|
181
196
|
let input;
|
|
182
|
-
if(
|
|
197
|
+
if(sourceURL) { const issue=await api(state,'/api/v1/issues/preview',{url:sourceURL});input={title:issue.title,spec:issue.spec,source_url:issue.url}; }
|
|
183
198
|
else if(flags.draft) { const draft=json(resolve(flags.draft));input={title:draft.title,spec:draft.spec}; }
|
|
184
199
|
else input={title:flags.title,spec:readFileSync(resolve(flags.file),'utf8')};
|
|
185
200
|
input.title=flags.title || input.title;
|
|
186
201
|
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)');
|
|
187
202
|
if(flags.workflow==='software'&&!configAt(state).check?.trim())throw new Error('Configure an app check before submitting software work');
|
|
188
|
-
console.log(JSON.stringify(await api(state,'/api/v1/jobs',{...input,workflow:flags.workflow,repository:'app',model:flags.model || ''}),null,2));
|
|
189
|
-
} else throw new Error('Use issue list|templates|preview|recommend|draft|create; see help');
|
|
203
|
+
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));
|
|
204
|
+
} else throw new Error('Use issue list|connection|templates|preview|recommend|draft|create|start|submissions|recover; see help');
|
|
190
205
|
} else if(command==='issues') {
|
|
191
206
|
console.log(JSON.stringify(await listIssues(configAt(state).repo,Number(flags.page || 1)),null,2));
|
|
192
207
|
} else if(command==='recommend') {
|
|
@@ -212,9 +227,7 @@ try {
|
|
|
212
227
|
state=resolve(flags.state || DEFAULT_DEMO_STATE);
|
|
213
228
|
const repo=join(state,'sample-app');
|
|
214
229
|
if(!existsSync(join(state,'factory.json'))) {
|
|
215
|
-
|
|
216
|
-
writeFileSync(join(repo,'value.txt'),'broken\n');run('git',['-C',repo,'add','value.txt']);
|
|
217
|
-
run('git',['-C',repo,'-c','user.name=Factory demo','-c','user.email=demo@localhost','commit','-m','Synthetic fixture']);
|
|
230
|
+
initializeDemoRepository(repo);
|
|
218
231
|
init(repo,'mock',"test \"$(cat value.txt)\" = fixed",Number(flags.port || 7332));
|
|
219
232
|
} else if(harnessOf(configAt(state))!=='mock')throw new Error('Demo requires a mock configuration');
|
|
220
233
|
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.')));
|
|
@@ -230,7 +243,7 @@ try {
|
|
|
230
243
|
kit --output NEW_DIRECTORY Export the portable method without a runtime
|
|
231
244
|
demo Install and run a synthetic sample (no model key)
|
|
232
245
|
qualify --state PATH Exercise recovery and isolation with a stopped demo job
|
|
233
|
-
init --repo PATH --harness codex|pi|custom --check "npm ci && npm test"
|
|
246
|
+
init --repo PATH --harness codex|pi|custom --check "npm ci && npm test" [--source-ref REF]
|
|
234
247
|
install [--image LOCAL_REF] Build the standard image, or select an existing local image
|
|
235
248
|
doctor | up | status | stop Inspect / operate your private installation
|
|
236
249
|
foundation Read the operator setup skill; no installation required
|
|
@@ -248,22 +261,28 @@ try {
|
|
|
248
261
|
service resume Release a reconciled maintenance reservation
|
|
249
262
|
tunnel install|start|stop|status|logs|uninstall --host SSH_ALIAS --port PORT
|
|
250
263
|
Persistent loopback SSH tunnel (macOS/Linux)
|
|
251
|
-
issue list [--source
|
|
264
|
+
issue list [--source remote] [--page N] List local executions or open repository issues
|
|
252
265
|
issue templates Read this repository's issue forms and contact links
|
|
253
|
-
issue preview --
|
|
254
|
-
issue recommend --file brief.md | --
|
|
266
|
+
issue preview --url URL Preview one repository issue without starting work
|
|
267
|
+
issue recommend --file brief.md | --url URL
|
|
255
268
|
issue draft --template NAME --sha SHA --file answers.json
|
|
256
269
|
Validate {title,answers}; output a local draft JSON
|
|
257
|
-
issue create --draft draft.json | --
|
|
258
|
-
|
|
270
|
+
issue create --draft draft.json | --file brief.md --title TITLE --key REQUEST_ID
|
|
271
|
+
Create a repository issue; does not execute work
|
|
272
|
+
issue connection | submissions Inspect provider identity or durable creation receipts
|
|
273
|
+
issue recover --key REQUEST_ID Reconcile an uncertain creation without another write
|
|
274
|
+
issue start --draft draft.json | --url URL | --file brief.md --title TITLE
|
|
275
|
+
--workflow software|defence [--source-ref REF] [--model MODEL]
|
|
259
276
|
Create a local issue and start work; no GitHub write
|
|
260
277
|
issues [--page N] Browse open project issues, with next_page for more
|
|
261
278
|
recommend --file task.md | --issue URL Suggest a work type without starting work
|
|
262
279
|
run --file task.md | --issue URL Submit software (default), or --workflow defence
|
|
280
|
+
[--source-ref REF] Pin a configured-repository ref before admission
|
|
263
281
|
incident --file incident.json Submit a private, read-only incident draft
|
|
264
282
|
approve JOB_ID | cancel JOB_ID Review gate / stop this attempt
|
|
265
283
|
retry JOB_ID Prove stop; retain old checkout and retry
|
|
266
|
-
revise JOB_ID --file feedback.md
|
|
284
|
+
revise JOB_ID --file feedback.md [--source-ref REF]
|
|
285
|
+
New build/check/review; source stays pinned unless a new ref is explicit
|
|
267
286
|
version Show the active CLI version
|
|
268
287
|
update | update --check Update the npm CLI / inspect the latest release
|
|
269
288
|
update --auto on|off Control automatic daily CLI updates
|
package/docs/concepts.md
CHANGED
|
@@ -12,14 +12,16 @@ The CLI `definition` command and dashboard Definition page read that same catalo
|
|
|
12
12
|
| Agent | Responsibility and instructions | Implement, Review, Investigate; one shared harness/model profile |
|
|
13
13
|
| Skill | Reusable instructions | Six job skills; separate operator Factory Foundation |
|
|
14
14
|
| Workflow | Ordered steps and gates | Software: Implement → Check → Review → Accept; Defence: Investigate |
|
|
15
|
-
|
|
|
16
|
-
|
|
|
15
|
+
| Issue | Bounded work request | Remote issue lives in its provider; a local brief needs no remote issue |
|
|
16
|
+
| Execution | Admitted work and its attempts | Stored in the private SQLite queue with source link and evidence |
|
|
17
|
+
| Provider | Repository issue integration | GitHub adapter first; unknown remotes retain local execution |
|
|
18
|
+
| Automation | External schedule and agent context | Owned by the selected harness; calls Factory CLI/API, no Factory cron |
|
|
17
19
|
| Definition | Effective roles, workflows, skills and settings | Installed method plus private factory.json; read-only catalog |
|
|
18
20
|
|
|
19
|
-
Inbox contains
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
Inbox contains execution history, not a copied remote issue backlog. New issue
|
|
22
|
+
can create a repository issue without execution; Start work admits execution
|
|
23
|
+
separately. The provider owns issue content/state; SQLite owns queue/attempts and
|
|
24
|
+
creation receipts for recovery. An unfinished local form is not a saved backlog.
|
|
23
25
|
An agent role is neither a machine nor a skill. Check is deterministic, and
|
|
24
26
|
Accept is an operator gate. Triage/specification precede admission; evaluation
|
|
25
27
|
is separately scoped work, not an automatic hidden agent phase.
|
|
@@ -51,7 +53,10 @@ Execution IDs (`build`, `verify`, `handoff`, `job_*`, `run_*`) are stable wire a
|
|
|
51
53
|
evidence identifiers. Human labels explain them without rewriting stored jobs.
|
|
52
54
|
Compatibility is handled at these boundaries; there is one active implementation.
|
|
53
55
|
|
|
54
|
-
CLI `issue` groups list, templates, preview, draft, recommend
|
|
56
|
+
CLI `issue` groups list, connection, templates, preview, draft, recommend, create,
|
|
57
|
+
start, submissions and recover. Since 0.6, `issue create` only publishes; migrate
|
|
58
|
+
0.5.1 execution callers to `issue start`. The
|
|
55
59
|
legacy `run`, `issues` (GitHub list) and `recommend` commands remain compatible.
|
|
56
60
|
Task/job field names and `/api/v1/jobs` are stable wire/storage identifiers for
|
|
57
|
-
these same local issues;
|
|
61
|
+
these same local issues; the extra SQLite receipt table is external-write bookkeeping, not an issue mirror
|
|
62
|
+
or another scheduler.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Repository and issue providers
|
|
2
|
+
|
|
3
|
+
Git source, issue tracking, execution and scheduling have different owners.
|
|
4
|
+
A Git remote does not guarantee an issue API, GitHub-compatible forms or a
|
|
5
|
+
particular CI system. Factory clones local Git source independently of the
|
|
6
|
+
optional issue integration.
|
|
7
|
+
|
|
8
|
+
| State | Owner |
|
|
9
|
+
| --- | --- |
|
|
10
|
+
| Source, branches, remote issues and their metadata | Selected repository/issue service |
|
|
11
|
+
| Queue, attempts, checks, approvals and evidence | Factory controller's private SQLite/files |
|
|
12
|
+
| External creation intent, correlation and receipt | SQLite; recovery bookkeeping, not an issue-backlog mirror |
|
|
13
|
+
| Schedules and scheduled-agent context | Selected harness, where it supports automations |
|
|
14
|
+
| Forms and actions | Dashboard and CLI over the same controller API |
|
|
15
|
+
|
|
16
|
+
## Current selection
|
|
17
|
+
|
|
18
|
+
`factory/issue-provider.mjs` selects an adapter from the configured Git origin.
|
|
19
|
+
Only public github.com origins select the implemented GitHub adapter. Its own
|
|
20
|
+
module owns GitHub API paths, host authentication, issue formats and recovery.
|
|
21
|
+
Unknown/self-hosted remotes, GitHub Enterprise, Forgejo/Gitea, GitLab and Cursor
|
|
22
|
+
Origin are **not implemented adapters**. They expose no remote-issue capability;
|
|
23
|
+
local brief execution and the method remain usable. Factory never sends GitHub
|
|
24
|
+
credentials to a guessed host or assumes a mirrored repo's original host.
|
|
25
|
+
|
|
26
|
+
The adapter contract supplies provider identity and explicit capabilities,
|
|
27
|
+
connection/acting identity, paged list, preview, templates/draft, publish and
|
|
28
|
+
read-only recovery. The generic submission store binds a request key to provider,
|
|
29
|
+
repository, identity and content before publication. Another provider can supply
|
|
30
|
+
these operations without changing the queue, controller routes or creation form.
|
|
31
|
+
Unsupported capabilities must fail visibly, not invent a compatible endpoint.
|
|
32
|
+
A non-GitHub test adapter exercises this boundary; it is not a shipped integration.
|
|
33
|
+
|
|
34
|
+
A future adapter must qualify its actual authentication, template/label model,
|
|
35
|
+
error behavior and ambiguous-write recovery. Add it at this boundary, with
|
|
36
|
+
matching CLI/API/UI support. Do not build a plugin loader, generalized OAuth
|
|
37
|
+
service or second database merely to reserve future extension points. Selecting
|
|
38
|
+
an issue tracker independently of the Git host remains a deliberate future
|
|
39
|
+
configuration extension, not an inferred mapping.
|
|
40
|
+
|
|
41
|
+
## GitHub adapter
|
|
42
|
+
|
|
43
|
+
The controller uses its existing `gh` identity. `issue connection` and the creation
|
|
44
|
+
form show the destination and acting user. GitHub enforces Issues write access;
|
|
45
|
+
label application needs suitable repository access. Browser GitHub login is not
|
|
46
|
+
required. Missing/revoked access produces a visible failure without execution.
|
|
47
|
+
No access token, browser cookie or host credential is passed to a job.
|
|
48
|
+
|
|
49
|
+
Creation sends the selected title/body/labels plus an opaque hidden correlation
|
|
50
|
+
marker. A lost response is recovered by bounded authenticated reads of issues
|
|
51
|
+
created by the original actor; no automatic second POST occurs. If the marker
|
|
52
|
+
was removed, the provider is unavailable or the bounded search cannot find it,
|
|
53
|
+
manual inspection is required. A receipt never claims current issue status;
|
|
54
|
+
read the provider again for current metadata. The published issue is the backlog.
|
|
55
|
+
|
|
56
|
+
Creation is separate from `issue start`. A remote issue never grants execution,
|
|
57
|
+
merge, deployment or production access. External harness automations use the same
|
|
58
|
+
explicit admission API and remain responsible for their own schedule/deduplication.
|
|
59
|
+
|
|
60
|
+
The managed updater’s OS timer maintains installed software; it is not a work-admission scheduler.
|
|
61
|
+
|
|
62
|
+
The dashboard derives its request key from the reviewed destination, identity,
|
|
63
|
+
title, body and labels. Reopening or reloading the same submission therefore
|
|
64
|
+
reuses its receipt, including a successful result whose browser response was
|
|
65
|
+
lost. No unsent draft is persisted by this mechanism. Identical browser content
|
|
66
|
+
returns the existing issue in that controller state; an intentional separate
|
|
67
|
+
copy requires a distinct CLI request key.
|
package/docs/interfaces.md
CHANGED
|
@@ -10,16 +10,17 @@ shell endpoint or a second scheduler.
|
|
|
10
10
|
| Capability | CLI | Shared API | Dashboard | Remaining work |
|
|
11
11
|
| --- | --- | --- | --- | --- |
|
|
12
12
|
| Project/queue/attempt state | `status`, `inbox` JSON | `GET /api/v1/status` | Project, tasks, details/history | Stable versioned agent result/error contract |
|
|
13
|
-
|
|
|
14
|
-
| Browse/import
|
|
13
|
+
| Start local work | `issue start --file --title`, `--draft` or `--url`, explicit `--workflow`, optional `--model` | `POST /api/v1/jobs` | Local execution only → review → Create & start locally | Persistent unstarted drafts and typed incident intake remain separate |
|
|
14
|
+
| Browse/import repository issues | `issue list --source remote [--page N]`, `issue preview --url URL` via controller provider | Authenticated `GET /api/v1/issues`, `POST /api/v1/issues/preview` using shared readers | Paged open-issue list, search loaded results, preview and explicit start for either type | Issue → execution links retained; no implicit polling |
|
|
15
|
+
| Create repository issue / recovery | `issue connection`, `create --key`, `submissions`, `recover --key` | Authenticated connection, `POST /issues`, receipts and recovery | Display destination/actor, create without execution, recover uncertain result | GitHub adapter first; assignees/projects and other providers unimplemented |
|
|
15
16
|
| Repository issue templates | `issue templates`, `issue draft --template --sha --file` | Authenticated template list and draft compilation | Chooser, fields/defaults/validation, review | Supports Markdown and YAML markdown/input/textarea/dropdown/checkboxes; unsupported templates link to GitHub |
|
|
16
|
-
| Suggest task type | `issue recommend --file` or `--
|
|
17
|
+
| Suggest task type | `issue recommend --file` or `--url` | Authenticated `POST /api/v1/intake/recommend`; issue preview includes suggestion | Editable recommendation after source selection | Deterministic label/brief rules; no model judgment or execution authority |
|
|
17
18
|
| Cancel/retry/approve | Commands | Job action endpoints with current run ID | Task controls | JSON action results and consistent needs-attention outcomes |
|
|
18
19
|
| Request changes | `revise --file` | `request_changes` action | Feedback form | JSON action result; retain shared stale-action guards |
|
|
19
20
|
| Remove a stopped task | No command | `DELETE /api/v1/jobs/:id` | Remove action | Add CLI; keep existing recoverability/history semantics |
|
|
20
21
|
| Evidence list/read/download | No command | Authenticated artifact routes | Files/preview/download | Add CLI with matching access and size/path rules |
|
|
21
22
|
| Roles, workflows and packaged skills | `definition`, `agents`, `skills` JSON (also while stopped) | `GET /api/v1/definitions` | Agents, Skills and Definition | Shared read-only catalog; future editing must preserve common policy/gates |
|
|
22
|
-
| Project repository links | Validated links in `status` | `project_links` from configured Git origin | View repo / optional GitHub issue link |
|
|
23
|
+
| Project repository links | Validated links in `status` | `project_links` from configured Git origin | View repo / optional GitHub issue link | Provider creates/issues reads are separate from Git source links |
|
|
23
24
|
| Recorded token usage | Per-attempt `usage` and `token_usage` in `status` | Same status records | Analytics, task rows, metadata/history | No billing estimate; partial/unknown coverage stays explicit |
|
|
24
25
|
| Analytics/filtering | Raw status available | Source queue records | Derived views | Expose equivalent queries/summaries without inventing usage data |
|
|
25
26
|
| Scoped incident admission | `incident --file` validates/deduplicates private evidence | No equivalent typed intake endpoint | Generic Defence form is not equivalent admission | Common typed intake, gaps and deduplication before execution |
|
|
@@ -31,7 +32,7 @@ shell endpoint or a second scheduler.
|
|
|
31
32
|
| SSH tunnels | `tunnel` | No tunnel endpoint | None | Client-host ownership; distinguish operator machine from worker |
|
|
32
33
|
| Method export | `kit --output` | No export endpoint | None | Equivalent download/export preserving staging-only adoption |
|
|
33
34
|
| Synthetic qualification | `demo`, `qualify` | No qualification endpoint | Synthetic disclosure only | Explicit separate state; never target an application accidentally |
|
|
34
|
-
| Immutable source admission |
|
|
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 |
|
|
35
36
|
| Trusted PR handoff | Operator applies accepted patch | Not implemented | Not implemented | #29; credentials remain outside jobs |
|
|
36
37
|
|
|
37
38
|
The current generic task form can name the Defence workflow; that is not a
|
|
@@ -46,8 +47,10 @@ authority. Headless use and GUI use must ultimately reach the same outcomes;
|
|
|
46
47
|
intermediate releases must explicitly retain their unimplemented rows here.
|
|
47
48
|
|
|
48
49
|
Infrastructure exposes detected host capacity and the local worker through
|
|
49
|
-
`infrastructure` and the same status API. `automations` returns the
|
|
50
|
-
|
|
50
|
+
`infrastructure` and the same status API. `automations` returns the harness
|
|
51
|
+
ownership contract; the UI explains that external schedules are not discovered.
|
|
52
|
+
Factory has no cron module. The v1 `automations: []` field remains a compatibility
|
|
53
|
+
view; `automation_control` owns the current semantics.
|
|
51
54
|
`foundation` prints the packaged operator skill without configuring anything;
|
|
52
55
|
the Skills page reads that same file. Full safe setup controls remain #37.
|
|
53
56
|
The roadmap is split into Defence #50, quality measurement #51, GitHub intake
|
package/docs/quickstart.md
CHANGED
|
@@ -23,12 +23,14 @@ 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. The CLI and dashboard show the requested ref and resolved SHA. Task text and reference links do not select a repository or ref.
|
|
32
|
+
|
|
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.
|
|
32
34
|
|
|
33
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.
|
|
34
36
|
|
|
@@ -38,14 +40,14 @@ A local model endpoint must be reachable from inside the job container. Host loo
|
|
|
38
40
|
|
|
39
41
|
```sh
|
|
40
42
|
software-defence-factory up --state /private/state/my-app
|
|
41
|
-
software-defence-factory run --file task.md --state /private/state/my-app
|
|
43
|
+
software-defence-factory run --file task.md --source-ref main --state /private/state/my-app
|
|
42
44
|
```
|
|
43
45
|
|
|
44
46
|
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
47
|
|
|
46
48
|
## Review and handoff
|
|
47
49
|
|
|
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
|
|
50
|
+
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
51
|
|
|
50
52
|
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
53
|
|
|
@@ -98,8 +100,7 @@ Defence form is not that typed intake path.
|
|
|
98
100
|
|
|
99
101
|
The header names the configured project. View repo opens a validated GitHub
|
|
100
102
|
origin. New issue opens Factory’s local chooser: repository templates, a blank
|
|
101
|
-
form or existing GitHub issues.
|
|
102
|
-
start queues local work. See [intake and CLI examples](workflows.md). The task detail provides previous/next within the filtered list, copy
|
|
103
|
+
form or existing GitHub issues. Create issue saves to the supported repository provider without execution. Start work queues local work. See [intake and CLI examples](workflows.md). The task detail provides previous/next within the filtered list, copy
|
|
103
104
|
link and close (Escape). Closing preserves the list's filters and position.
|
|
104
105
|
|
|
105
106
|
If the interface looks unexpectedly small, check the browser zoom. The design
|
package/docs/recovery.md
CHANGED
|
@@ -4,9 +4,10 @@ Use `status --state PATH` and the private supervisor.log to identify the active
|
|
|
4
4
|
|
|
5
5
|
- A normal `stop` signals the controller, waits for the executor process group, removes its labelled containers and retains the database and artifacts.
|
|
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
|
+
- `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-*.
|
|
8
9
|
- A failed verification can retry the same unchanged candidate after the check environment is repaired. A changed candidate needs a fresh verification/review sequence.
|
|
9
|
-
- A requested revision retains previous evidence and starts a new build
|
|
10
|
+
- 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.
|
|
10
11
|
- 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.
|
|
11
12
|
|
|
12
13
|
Do not remove active.json merely to unblock a job. Establish that its PID, process group and labelled containers are stopped. PID reuse or missing process identity requires operator investigation. Preserve logs and work before cleanup.
|
|
@@ -34,11 +35,14 @@ software-defence-factory revise JOB_ID --file /private/revision.md --state /priv
|
|
|
34
35
|
|
|
35
36
|
Feedback must be nonempty and at most 4,000 characters. It is accumulated in the
|
|
36
37
|
bounded job prompt. The controller checks the latest attempt and reconciles its
|
|
37
|
-
processes before starting a new build from the
|
|
38
|
+
processes before starting a new build from the retained source commit with that
|
|
38
39
|
feedback. The old checkout moves to `previous-checkout-*`; the old failed review
|
|
39
40
|
and reports remain intact. Verification, review and operator approval are all
|
|
40
41
|
required again. A stale or duplicate request cannot approve or restart a newer
|
|
41
|
-
attempt.
|
|
42
|
+
attempt. To deliberately change the base, pass `--source-ref REF`; the controller
|
|
43
|
+
resolves and retains it from the same configured repository before changing the
|
|
44
|
+
job record. If the request cannot reconcile, its unlinked source copy is removed.
|
|
45
|
+
**Retry** only repeats the stopped phase and is suitable for a repaired
|
|
42
46
|
execution environment; retrying review cannot change the candidate.
|
|
43
47
|
|
|
44
48
|
A crashed review without a validated verdict cannot request implementation
|
|
@@ -91,6 +95,22 @@ facts in a separate private incident note with evidence paths and unknowns; leav
|
|
|
91
95
|
the original queue/evidence unchanged. Without that evidence, model/image facts
|
|
92
96
|
cannot be reconstructed reliably.
|
|
93
97
|
|
|
98
|
+
### Legacy source records
|
|
99
|
+
|
|
100
|
+
Jobs admitted before source retention keep their historical candidate and review
|
|
101
|
+
evidence without an invented admission-time SHA. A complete existing candidate
|
|
102
|
+
can still pass the normal current candidate, policy, review and approval guards;
|
|
103
|
+
the handoff labels its source **Not recorded (legacy/unknown)**. An unpinned
|
|
104
|
+
queued job is blocked on controller restart, and an unpinned job cannot retry or
|
|
105
|
+
request implementation changes. Submit a replacement job to capture the
|
|
106
|
+
configured or explicit source ref before execution. Do not reconstruct proof
|
|
107
|
+
from the current checkout, issue text or a later ref value.
|
|
108
|
+
|
|
109
|
+
If a retained store is missing or corrupt, retry and revision stop with an
|
|
110
|
+
explicit source error. Restore the private state backup containing that job's
|
|
111
|
+
retained Git objects, or submit a replacement from a source ref that still
|
|
112
|
+
resolves. The controller never substitutes the mutable configured checkout.
|
|
113
|
+
|
|
94
114
|
### Interrupted image selection or controller startup
|
|
95
115
|
|
|
96
116
|
`installation.lock` serializes image changes with controller startup, including
|
|
@@ -99,3 +119,10 @@ can run. If an operation is interrupted, preserve the lock and inspect its PID
|
|
|
99
119
|
and action. Remove it only after confirming that process and its image build or
|
|
100
120
|
startup have stopped, then run `doctor` and reconcile image metadata before
|
|
101
121
|
starting again. The CLI does not automatically clear an unknown lock.
|
|
122
|
+
|
|
123
|
+
## Unconfirmed repository issue creation
|
|
124
|
+
|
|
125
|
+
Use `issue submissions` and `issue recover --key REQUEST_ID` against the same
|
|
126
|
+
controller state. Recovery reads the original provider using the original
|
|
127
|
+
identity; it does not publish again. Do not use a new request key to retry an
|
|
128
|
+
uncertain write. See [provider ownership and recovery limits](integrations.md).
|
package/docs/setup.md
CHANGED
|
@@ -103,7 +103,7 @@ qualification record; stop this controller unless it is the selected dashboard.
|
|
|
103
103
|
## 4. Prepare each application and inference profile
|
|
104
104
|
|
|
105
105
|
Preserve existing commits, branches, uncommitted work and licenses before moving
|
|
106
|
-
anything. Verify the canonical
|
|
106
|
+
anything. Verify the canonical Git origin and main branch's tracking target;
|
|
107
107
|
a fork may still track its upstream product. Move application sources into the
|
|
108
108
|
chosen workspace, not into the npm package or private runtime state.
|
|
109
109
|
|
|
@@ -115,7 +115,7 @@ Before admitting development work, establish:
|
|
|
115
115
|
- Reproducible toolchain/dependency pins and an actual build/check command.
|
|
116
116
|
- A compatible job image for native libraries, browser/mobile tools or custom
|
|
117
117
|
model adapters; the standard image does not supply every application stack.
|
|
118
|
-
- Meaningful checks in
|
|
118
|
+
- Meaningful checks in the selected CI on the selected revision, plus the intended
|
|
119
119
|
review/branch policy. Record plan/access limits if enforcement is unavailable.
|
|
120
120
|
- Applicable application instructions, design/scope, resource limits, secret
|
|
121
121
|
references and a clear delivery destination. Keep unfinished WIP separate
|