software-defence-factory 0.9.1 → 0.11.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 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. 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).
40
+ The project dashboard has an **Inbox**, measured **Analytics**, **Agents**, **Skills**, **Automations**, **Definition** and **Infrastructure**. Inbox lists repository issues with readiness and linked execution attempts. New issue offers repository templates or a blank creation form. 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
 
@@ -6,7 +6,7 @@ import { spawn } from 'node:child_process';
6
6
  import { createServer } from 'node:net';
7
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
- import { listIssues, readIssue } from '../factory/issue-intake.mjs';
9
+ import { readIssue } from '../factory/issue-intake.mjs';
10
10
  import { recommendWork } from '../factory/intake.mjs';
11
11
  import { factoryDefinition, foundationSkill } from '../factory/definition.mjs';
12
12
  import { harnessOf } from '../factory/lib.mjs';
@@ -55,6 +55,16 @@ function init(repo, harness='codex', check='', port=7331, sourceRef='HEAD', deli
55
55
  console.log(`Configured ${state}\nApp files were not changed. Only committed code is cloned into jobs.`);
56
56
  }
57
57
 
58
+ async function listInbox(defaultSource) {
59
+ const source=flags.source || defaultSource;
60
+ if(!['factory','inbox','remote','github'].includes(source))throw new Error('Choose --source factory, inbox or remote');
61
+ if(source==='factory') {
62
+ if(flags.page !== undefined || flags['issue-state'] !== undefined)throw new Error('Repository paging/state filters require --source inbox or remote.');
63
+ return (await api(state,'/api/v1/status')).jobs;
64
+ }
65
+ return api(state,`/api/v1/issues?page=${encodeURIComponent(flags.page || 1)}&state=${encodeURIComponent(flags['issue-state'] || 'open')}`);
66
+ }
67
+
58
68
  async function install() {
59
69
  const config=configAt(state);
60
70
  if (!['darwin','linux'].includes(process.platform)||!['arm64','x64'].includes(process.arch)) throw new Error('Use macOS/Linux arm64/amd64, or WSL2');
@@ -101,10 +111,10 @@ async function stop() {
101
111
  }
102
112
  stopContainers(state);console.log('Controller and its labelled containers stopped.');
103
113
  }
104
- async function submit(workflow,spec,sourceRef=flags['source-ref']) {
114
+ async function submit(workflow,spec,sourceRef=flags['source-ref'],sourceURL) {
105
115
  if(Buffer.byteLength(spec)>240000)throw new Error('Task exceeds 240 KB');
106
116
  const title=workflow==='defence'?'Private incident triage':spec.split('\n').find(s=>s.trim())?.replace(/^#+\s*/, '').slice(0,100)||'Software task';
107
- return api(state,'/api/v1/jobs',{workflow,repository:'app',spec,title,...(sourceRef===undefined?{}:{source_ref:sourceRef})});
117
+ return api(state,'/api/v1/jobs',{workflow,repository:'app',spec,title,...(sourceURL?{source_url:sourceURL}:{}),...(sourceRef===undefined?{}:{source_ref:sourceRef})});
108
118
  }
109
119
  async function jobAction(action) {
110
120
  const id=positional[0];if(!/^job_[a-z0-9]+$/.test(id || ''))throw new Error('A job ID is required');
@@ -113,15 +123,22 @@ async function jobAction(action) {
113
123
  const current=job.runs.at(-1);
114
124
  if(action==='approve'&&job.state!=='awaiting_approval')throw new Error('Job is not awaiting approval');
115
125
  if(action==='retry'&&!['interrupted','failed','blocked','cancelled'].includes(job.state))throw new Error('Only a stopped attempt can be retried');
116
- let feedback;
126
+ let feedback, revision = {};
117
127
  if(action==='request_changes') {
118
128
  if(!flags.file)throw new Error('revise requires --file feedback.md');
129
+ if (flags.from !== undefined && !['admitted-source', 'reviewed-candidate'].includes(flags.from)) throw new Error('revise --from must be admitted-source or reviewed-candidate');
130
+ if (flags.from !== undefined && flags['source-ref'] !== undefined) throw new Error('Choose --from or --source-ref, not both');
131
+ if (flags.from === 'reviewed-candidate') {
132
+ const checkpoint = job.continuation_status;
133
+ if (!checkpoint?.available) throw new Error(checkpoint?.reason || 'No current reviewed checkpoint is available; inspect status');
134
+ revision = { revision_mode: 'continue_candidate', candidate_head: checkpoint.head, candidate_tree: checkpoint.tree };
135
+ } else revision = { revision_mode: flags['source-ref'] === undefined ? 'fresh_source' : 'replace_source' };
119
136
  feedback=readFileSync(resolve(flags.file),'utf8');
120
137
  if(!feedback.trim()||feedback.length>4000)throw new Error('Provide revision feedback under 4000 characters');
121
138
  }
122
139
  // The controller validates the current run and owns reconciliation atomically.
123
140
  // A CLI-side stop after a stale snapshot could terminate a newer attempt.
124
- await api(state,`/api/v1/jobs/${id}/${action}`,{run_id:current?.id,...(feedback===undefined?{}:{feedback}),...(flags['source-ref']===undefined?{}:{source_ref:flags['source-ref']})});
141
+ await api(state,`/api/v1/jobs/${id}/${action}`,{run_id:current?.id,...revision,...(feedback===undefined?{}:{feedback}),...(flags['source-ref']===undefined?{}:{source_ref:flags['source-ref']})});
125
142
  console.log(`${action}: ${id}`);
126
143
  }
127
144
 
@@ -150,6 +167,7 @@ async function abandonDeliveryJob(jobId) {
150
167
  }
151
168
 
152
169
  try {
170
+ if(flags['brief-file'] !== undefined && (command !== 'issue' || positional[0] !== 'start' || !(flags.url || flags.github) || flags.file || flags.draft))throw new Error('Use --brief-file only with issue start --url (or --github); local --file/--draft already supplies the scope.');
153
171
  if(command==='init') {
154
172
  if(!flags.repo)throw new Error('init requires --repo /path/to/existing/git/repo');
155
173
  if(flags.harness && flags.agent && flags.harness !== flags.agent)throw new Error('--harness conflicts with legacy --agent');
@@ -184,9 +202,10 @@ try {
184
202
  const value=command==='agents'?definition.agents:command==='skills'?{agents:definition.skills,operators:definition.operator_skills}:definition;
185
203
  console.log(JSON.stringify(value,null,2));
186
204
  }
187
- else if(['infrastructure','automations','inbox'].includes(command)) {
205
+ else if(command==='inbox')console.log(JSON.stringify(await listInbox('inbox'),null,2));
206
+ else if(['infrastructure','automations'].includes(command)) {
188
207
  const snapshot=await api(state,'/api/v1/status');
189
- console.log(JSON.stringify(command==='inbox'?snapshot.jobs:command==='automations'?snapshot.automation_control:snapshot[command],null,2));
208
+ console.log(JSON.stringify(command==='automations'?snapshot.automation_control:snapshot[command],null,2));
190
209
  }
191
210
  else if(command==='status') { const snapshot=await api(state,'/api/v1/status');delete snapshot.csrf_token;console.log(JSON.stringify(snapshot,null,2)); }
192
211
  else if(command==='doctor') {
@@ -197,8 +216,7 @@ try {
197
216
  } else if(command==='issue') {
198
217
  const action=positional[0], sourceURL=flags.url || flags.github;
199
218
  if(action==='list') {
200
- if(flags.source && !['factory','remote','github'].includes(flags.source))throw new Error('Choose --source factory or remote');
201
- 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));
219
+ console.log(JSON.stringify(await listInbox('factory'),null,2));
202
220
  } else if(action==='templates') console.log(JSON.stringify(await api(state,'/api/v1/issue-templates'),null,2));
203
221
  else if(action==='connection') console.log(JSON.stringify(await api(state,'/api/v1/issue-connection'),null,2));
204
222
  else if(action==='submissions') console.log(JSON.stringify(await api(state,'/api/v1/issue-submissions'),null,2));
@@ -226,17 +244,21 @@ try {
226
244
  } else if(action==='start') {
227
245
  if(!['software','defence'].includes(flags.workflow))throw new Error('Review the issue and choose --workflow software or defence');
228
246
  if([flags.file,sourceURL,flags.draft].filter(Boolean).length!==1)throw new Error('Choose --file brief.md, --draft draft.json or --url ISSUE_URL');
229
- let input;
230
- if(sourceURL) { const issue=await api(state,'/api/v1/issues/preview',{url:sourceURL});input={title:issue.title,spec:issue.spec,source_url:issue.url}; }
247
+ let input, brief;
248
+ if(flags['brief-file'] !== undefined) {
249
+ brief=readFileSync(resolve(flags['brief-file']),'utf8');
250
+ if(brief.length>16000)throw new Error('Operator brief must be at most 16000 characters.');
251
+ }
252
+ if(sourceURL) { const issue=await api(state,'/api/v1/issues/preview',{url:sourceURL});input={title:issue.title,url:issue.url,expected_spec:issue.spec,...(brief===undefined?{}:{brief})}; }
231
253
  else if(flags.draft) { const draft=json(resolve(flags.draft));input={title:draft.title,spec:draft.spec}; }
232
254
  else input={title:flags.title,spec:readFileSync(resolve(flags.file),'utf8')};
233
255
  input.title=flags.title || input.title;
234
- 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)');
256
+ if(!sourceURL && (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)');
235
257
  if(flags.workflow==='software'&&!configAt(state).check?.trim())throw new Error('Configure an app check before submitting software work');
236
- 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));
258
+ console.log(JSON.stringify(await api(state,sourceURL ? '/api/v1/issues/start' : '/api/v1/jobs',{...input,workflow:flags.workflow,repository:'app',model:flags.model || '',...(flags['source-ref']===undefined?{}:{source_ref:flags['source-ref']})}),null,2));
237
259
  } else throw new Error('Use issue list|connection|templates|preview|recommend|draft|create|start|submissions|recover; see help');
238
260
  } else if(command==='issues') {
239
- console.log(JSON.stringify(await listIssues(configAt(state).repo,Number(flags.page || 1)),null,2));
261
+ console.log(JSON.stringify(await listInbox('inbox'),null,2));
240
262
  } else if(command==='recommend') {
241
263
  if(Boolean(flags.issue) === Boolean(flags.file))throw new Error('Choose --file task.md or --issue URL');
242
264
  const recommendation=flags.issue ? (await readIssue(configAt(state).repo,flags.issue)).recommendation : recommendWork({spec:readFileSync(resolve(flags.file),'utf8')});
@@ -244,13 +266,13 @@ try {
244
266
  } else if(command==='run') {
245
267
  const workflow=flags.workflow || 'software';
246
268
  if(!['software','defence'].includes(workflow))throw new Error('Choose --workflow software or defence');
247
- let spec;
269
+ let spec,sourceURL;
248
270
  if(flags.issue) {
249
- spec=(await readIssue(configAt(state).repo,flags.issue)).spec;
271
+ const issue=await readIssue(configAt(state).repo,flags.issue);spec=issue.spec;sourceURL=issue.url;
250
272
  } else if(flags.file)spec=readFileSync(resolve(flags.file),'utf8');
251
273
  else throw new Error('Use --file task.md or --issue https://github.com/owner/repo/issues/123');
252
274
  if(workflow==='software'&&!configAt(state).check?.trim())throw new Error('Configure an app check before submitting software work');
253
- console.log(JSON.stringify(await submit(workflow,spec)));
275
+ console.log(JSON.stringify(await submit(workflow,spec,flags['source-ref'],sourceURL)));
254
276
  } else if(command==='incident') {
255
277
  if(!flags.file)throw new Error('Use --file incident.json; see factory/examples/incident.json');
256
278
  console.log(JSON.stringify(await admitIncident(state,json(resolve(flags.file)),submit)));
@@ -296,7 +318,9 @@ try {
296
318
  web probe --state PATH Execute the pinned local Chromium readiness probe
297
319
  foundation Read the operator setup skill; no installation required
298
320
  definition | agents | skills Inspect roles, instructions and installation settings
299
- inbox | infrastructure | automations Inspect live tasks, host/worker and automation state
321
+ inbox [--page N] [--issue-state open|closed|all] [--source inbox|factory]
322
+ Repository backlog (default); factory: execution-only array
323
+ infrastructure | automations Inspect host/worker and automation state
300
324
  workflows Compatibility alias for definition
301
325
  --agent Legacy alias for init --harness
302
326
  serve Foreground supervisor
@@ -309,7 +333,8 @@ try {
309
333
  service resume Release a reconciled maintenance reservation
310
334
  tunnel install|start|stop|status|logs|uninstall --host SSH_ALIAS --port PORT
311
335
  Persistent loopback SSH tunnel (macOS/Linux)
312
- issue list [--source remote] [--page N] List local executions or open repository issues
336
+ issue list [--source inbox|remote|factory] [--page N] [--issue-state open|closed|all]
337
+ List linked repository issues or local executions (default)
313
338
  issue templates Read this repository's issue forms and contact links
314
339
  issue preview --url URL Preview one repository issue without starting work
315
340
  issue recommend --file brief.md | --url URL
@@ -321,8 +346,9 @@ try {
321
346
  issue recover --key REQUEST_ID Reconcile an uncertain creation without another write
322
347
  issue start --draft draft.json | --url URL | --file brief.md --title TITLE
323
348
  --workflow software|defence [--source-ref REF] [--model MODEL]
324
- Create a local issue and start work; no GitHub write
325
- issues [--page N] Browse open project issues, with next_page for more
349
+ [--brief-file operator.md] Only with --url; at most 16000 characters
350
+ Explicitly start execution; no GitHub write
351
+ issues [--page N] Browse linked project issues; supports --issue-state
326
352
  recommend --file task.md | --issue URL Suggest a work type without starting work
327
353
  run --file task.md | --issue URL Submit software (default), or --workflow defence
328
354
  [--source-ref REF] Pin a configured-repository ref before admission
@@ -331,7 +357,7 @@ try {
331
357
  publish JOB_ID Publish/reconcile the accepted candidate as one draft PR
332
358
  abandon-delivery JOB_ID --branch-sha SHA Resolve an inspected pre-write collision; keep the remote branch
333
359
  retry JOB_ID Prove stop; retain old checkout and retry
334
- revise JOB_ID --file feedback.md [--source-ref REF]
360
+ revise JOB_ID --file feedback.md [--from admitted-source|reviewed-candidate | --source-ref REF]
335
361
  New build/check/review; source stays pinned unless a new ref is explicit
336
362
  version Show the active CLI version
337
363
  update | update --check Update the npm CLI / inspect the latest release
@@ -9,14 +9,15 @@ shell endpoint or a second scheduler.
9
9
 
10
10
  | Capability | CLI | Shared API | Dashboard | Remaining work |
11
11
  | --- | --- | --- | --- | --- |
12
- | Project/queue/attempt state | `status`, `inbox` JSON | `GET /api/v1/status` | Project, tasks, details/history | Stable versioned agent result/error contract |
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 |
12
+ | Project/queue/attempt state | `status`, `inbox --source factory` JSON | `GET /api/v1/status` | Project, tasks, details/history | Stable versioned agent result/error contract |
13
+ | Start local work | `issue start --file --title` or `--draft`, 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 repository Inbox | `inbox [--page N] [--issue-state open/closed/all]` (also `issue list --source inbox`), `issue preview --url URL` via controller provider | Authenticated `GET /api/v1/issues`, `POST /api/v1/issues/preview` using shared readers | Inbox with provider/state/readiness, loaded-page counts, linked attempts, local/off-page history; explicit Start work for either type | Issue → execution links retained; no implicit polling |
15
+ | Start repository work | `issue start --url URL --workflow software/defence [--brief-file operator.md]` | `POST /api/v1/issues/start` | Issue context → explicit Start work with operator brief | Rechecks current content and active admission |
15
16
  | 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 |
16
17
  | 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 |
17
18
  | 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 |
18
19
  | Cancel/retry/approve | Commands | Job action endpoints with current run ID | Task controls | JSON action results and consistent needs-attention outcomes |
19
- | Request changes | `revise --file` | `request_changes` action | Feedback form | JSON action result; retain shared stale-action guards |
20
+ | Request changes | `revise --file`, optional `--from admitted-source\|reviewed-candidate` or `--source-ref REF` | `request_changes` with `revision_mode`; continuation binds current run/head/tree from shared status | Explicit starting point, baseline and reviewed checkpoint; inline errors | JSON action result; unfinished Build checkpoints remain #42 |
20
21
  | Remove a stopped task | No command | `DELETE /api/v1/jobs/:id` | Remove action | Add CLI; keep existing recoverability/history semantics |
21
22
  | Evidence list/read/download | No command | Authenticated artifact routes | Files/preview/download | Add CLI with matching access and size/path rules |
22
23
  | 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 |
@@ -32,7 +33,7 @@ shell endpoint or a second scheduler.
32
33
  | SSH tunnels | `tunnel` | No tunnel endpoint | None | Client-host ownership; distinguish operator machine from worker |
33
34
  | Method export | `kit --output` | No export endpoint | None | Equivalent download/export preserving staging-only adoption |
34
35
  | Synthetic qualification | `demo`, `qualify` | No qualification endpoint | Synthetic disclosure only | Explicit separate state; never target an application accidentally |
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
+ | 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 | Issue Start work, local request and revision forms accept a ref; task detail shows requested ref, resolved SHA and prior source commits | Build/retry use retained objects; revisions start fresh by default; explicit continuation keeps the reviewed tree and recorded source; a new ref replaces the base; legacy source remains unknown |
36
37
  | 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
38
  | 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) |
38
39
 
@@ -57,3 +58,51 @@ the Skills page reads that same file. Full safe setup controls remain #37.
57
58
  The roadmap is split into Defence #50, quality measurement #51, GitHub intake
58
59
  #52, editable definitions #53 and scoped MCP #54. Existing REST endpoints are
59
60
  local single-operator interfaces, not a public multi-user API.
61
+
62
+
63
+ ## Issue lifecycle API in 0.11.0
64
+
65
+ `GET /api/v1/issues?page=1&state=open` retains repository/issues/next_page and adds
66
+ provider, page, state, loaded_count, total (null when unknown), and history.
67
+ Each issue adds canonical identity, provider state, readiness, executions,
68
+ latest_execution, active_execution(s), and start_block_reason. History groups
69
+ local jobs and canonical sources outside the page; not_loaded does not claim
70
+ that an issue is missing or closed. `status.issue_history` refreshes these local
71
+ associations without polling the provider. No issue content database was added.
72
+
73
+ `POST /api/v1/issues/preview` retains content/labels/recommendation and adds the
74
+ same identity/readiness/history contract. `POST /api/v1/issues/start` accepts
75
+ url, expected_spec (the preview's spec), workflow, optional brief, source_ref
76
+ and model. It re-reads current provider context, rejects changed scope, closed
77
+ sources, blocked/conflicting readiness and active/unresolved work, then uses
78
+ existing source admission. HTTP 409 describes stale or duplicate admission.
79
+ `POST /api/v1/jobs` remains compatible for local/direct callers; its queue-level
80
+ canonical reservation also rejects concurrent duplicate active work and retries.
81
+ Terminal succeeded/failed/cancelled executions release the reservation unless
82
+ provider delivery is unresolved. Other/unknown states retain it conservatively.
83
+
84
+ `inbox` now defaults to the same repository page object as the dashboard (open,
85
+ page 1), an intentional 0.11.0 JSON change from its former jobs array. Use
86
+ `inbox --source factory` for the explicit legacy execution-only array.
87
+ `issue list` still defaults to the original local jobs array (`--source factory`).
88
+ `--source inbox`, `remote` and compatibility `github` return the shared enriched
89
+ page. `issues` now uses this same authenticated controller endpoint and enriched
90
+ JSON, rather than bypassing the controller; scripts need a running controller.
91
+ Use `--issue-state` for provider state; `--state` continues to mean installation
92
+ path. Repository paging/state options are rejected in execution-only mode rather
93
+ than ignored. Unsupported providers return their capability state and local/history
94
+ records; provider/auth failures exit nonzero, never an empty-success backlog.
95
+ `issue start --url URL --brief-file operator.md` reads a UTF-8 operator brief of at
96
+ most 16000 characters (the API's string-length limit), forwarded unchanged to the
97
+ shared preview/start contract. The API appends nonblank, trimmed text under
98
+ `Operator brief:` in the admitted spec, while preserving provider identity and
99
+ rechecking current content. `--brief-file` is optional, only valid with remote
100
+ `issue start --url` (or its `--github` alias); it cannot accompany local `--file`
101
+ or `--draft`, creation, browsing or other commands. Missing/unreadable files and
102
+ oversize text fail without admission. Local file/draft execution is unchanged. `run` retains
103
+ its compatible direct-job path and the controller's duplicate identity guard.
104
+
105
+ Definition exposes `configuration.issueReadinessLabels`. The optional private
106
+ config field has exactly triage/spec/ready/blocked keys with four distinct label
107
+ names; defaults are factory:triage/spec/ready/blocked. Configuration is validated,
108
+ not inferred from issue content. No browsing path changes labels or comments.
package/docs/npm.md CHANGED
@@ -73,8 +73,8 @@ installation or retained attempt needs them.
73
73
 
74
74
  ## Protected evidence compatibility
75
75
 
76
- 0.9.1 recognizes version-1 execution profiles emitted by native **0.8.0,
77
- 0.9.0 and 0.9.1**. This is an exact allowlist in
76
+ 0.10.0 recognizes version-1 execution profiles emitted by native **0.8.0,
77
+ 0.9.0, 0.9.1 and 0.10.0**. This is an exact allowlist in
78
78
  `factory/execution-profile.mjs`, independent of the installed package version;
79
79
  it is not a semver range or an automatic promise for later releases. Unknown
80
80
  runtime strings, unknown profile formats and incomplete legacy acceptance
@@ -101,12 +101,25 @@ neither a reason to discard evidence nor proof of compatibility. Retain
101
101
  unsupported records unchanged and obtain fresh applicable evidence through the
102
102
  normal workflow; never repair them by editing private records or hashes.
103
103
 
104
- After native Verify/Review, the lead must qualify the installed patch read-only
105
- against actual retained production evidence. This source change and its
106
- controlled fixtures do not supply that proof. An already delivered PR keeps
107
- read-only receipt reconciliation. New publication still requires the current
108
- remote target to equal the original accepted base; checkpoint continuation and
109
- target mismatch remain the separate #42 limit.
104
+ The 0.10.0 writer retains the same profile format and original-base, aggregate
105
+ single-parent candidate/check/review/approval guarantees. Continuation adds
106
+ separate provenance; it is not acceptance evidence. The audited 0.8.0, 0.9.0 and
107
+ 0.9.1 writers remain supported only when their old records meet all current
108
+ checks. For continuation specifically, the current completed review, successful
109
+ Build/Verify, protected per-attempt artifacts, current policy and clean candidate
110
+ objects must also be present and agree. An old runtime string alone never makes
111
+ a checkpoint usable. Browser-disabled 0.8.0 records cannot satisfy a newly
112
+ enabled browser policy. Missing or unsupported records stay unchanged and
113
+ unavailable; no schema migration, profile relabeling or policy repair occurs.
114
+
115
+ #71 shipped 0.9.0 through PR #81 and #84 shipped 0.9.1 through PR #85; both
116
+ passed installed qualification, as reported by the lead. For this 0.10.0 slice,
117
+ native Verify/Review remain required. The lead owns installed continuation,
118
+ disposable protected PR/CI and desktop/narrow light/dark browser qualification
119
+ on Z13. Controlled source fixtures do not supply that proof. New publication
120
+ still requires the current remote target to equal the original accepted base;
121
+ reviewed-candidate continuation preserves that baseline, while target refresh
122
+ remains #72.
110
123
 
111
124
  ## Release flow
112
125
 
@@ -149,7 +149,7 @@ create a second PR or overwrite a changed branch. The CLI gives this bounded
149
149
  multi-request action ten minutes; branch resolution uses the same bound and
150
150
  other API calls keep their five-second deadline. If a client deadline expires,
151
151
  inspect status and repeat `publish` to reconcile or recheck the reported branch
152
- identity before `abandon-delivery`. Delete issue stays disabled while delivery
152
+ identity before `abandon-delivery`. Remove local execution history stays disabled while delivery
153
153
  is unresolved, and the controller enforces the same guard on its API.
154
154
  Publication does not merge, integrate, release or deploy. See [delivery
155
155
  recovery](recovery.md#trusted-pr-delivery).
@@ -211,8 +211,7 @@ use the [Defence integration](defence-integration.md) recipe; the generic
211
211
  Defence form is not that typed intake path.
212
212
 
213
213
  The header names the configured project. View repo opens a validated GitHub
214
- origin. New issue opens Factory’s local chooser: repository templates, a blank
215
- 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
214
+ origin. Inbox lists repository issues directly with open/closed/all filters and paging. New issue opens repository templates or a blank creation form. Create issue saves to the supported repository provider without execution. Open an issue from Inbox and choose Start work to admit execution explicitly. The controller rejects duplicate active work; linked attempts remain accessible. See [intake and CLI examples](workflows.md). The task detail provides previous/next within the filtered list, copy
216
215
  link and close (Escape). Closing preserves the list's filters and position.
217
216
 
218
217
  If the interface looks unexpectedly small, check the browser zoom. The design
package/docs/recovery.md CHANGED
@@ -8,7 +8,7 @@ Use `status --state PATH` and the private supervisor.log to identify the active
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
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
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.
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
+ - A requested revision starts fresh from the recorded source by default, retains previous evidence and starts a new build with accumulated feedback. It never silently reuses candidate code. 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. See the explicit continuation choice below.
12
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.
13
13
 
14
14
  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.
@@ -331,3 +331,69 @@ candidate or policy change requires fresh applicable checks, review and
331
331
  approval; do not edit SQLite or acceptance evidence to bypass the guard. A stale
332
332
  target after branch creation leaves that unique branch for inspection and does
333
333
  not open a PR. A PR does not imply integration or deployment.
334
+
335
+ ## Continue a reviewed candidate (first #42 slice)
336
+
337
+ Use one explicit starting point for a current software review with a returned
338
+ `changes`/`blocked` verdict, or a passed review awaiting approval:
339
+
340
+ ```sh
341
+ # Existing default: restore admitted source, discarding candidate code from the next Build.
342
+ software-defence-factory revise JOB_ID --file feedback.md --state PATH
343
+ # Keep this job’s current immutable reviewed tree; keep its source/delivery baseline.
344
+ software-defence-factory revise JOB_ID --file feedback.md --from reviewed-candidate --state PATH
345
+ # Existing source replacement: resolve and retain a deliberately different baseline.
346
+ software-defence-factory revise JOB_ID --file feedback.md --source-ref REF --state PATH
347
+ ```
348
+
349
+ `--from admitted-source` names the default explicitly. `--from` and
350
+ `--source-ref` cannot be combined. In the dashboard, Request changes exposes
351
+ these same three starting points, the original source SHA, selected checkpoint
352
+ head/tree and review attempt. Opening the form defaults to fresh source; a
353
+ background refresh does not silently replace a checkpoint already selected.
354
+ Errors stay next to the revision action and preserve feedback.
355
+
356
+ The shared `request_changes` action accepts `run_id`, `feedback`, and
357
+ `revision_mode`: `fresh_source`, `continue_candidate` or `replace_source`.
358
+ Continuation additionally requires `candidate_head` and `candidate_tree` from
359
+ this job’s `continuation_status` in `GET /api/v1/status`. They are stale-action
360
+ guards, not selectors for arbitrary refs, host paths or other jobs. Replacement
361
+ requires `source_ref`. Omitting mode preserves the existing default and
362
+ `source_ref` behavior. Status includes `revision_mode`, public `continuation`
363
+ provenance, and `continuation_status.available` or an actionable refusal reason.
364
+ Availability describes protected evidence; the action also probes live workers
365
+ and validates the Git objects before changing the checkout.
366
+
367
+ Continuation confirms the executor/process group and job containers are idle;
368
+ it refuses active or unknown work instead of stopping it implicitly. Under the
369
+ existing queue action lock it validates the current review cycle, exact
370
+ protected profiles and artifacts, original retained source, effective policy
371
+ and any required browser evidence. It copies the clean reviewed Git objects
372
+ into a private, job-bound checkpoint store with a pinned ref and manifest,
373
+ checks head/tree/single parent and object integrity, and only then archives the
374
+ writable checkout through ordinary reconciliation. Missing, dirty, tampered,
375
+ foreign or incompatible candidates are refused without deleting prior work.
376
+
377
+ The next Build validates the retained checkpoint and current policy again,
378
+ restores the retained source as HEAD, and stages the checkpoint tree on that
379
+ base. The ordinary Build commit is a single-parent aggregate: Review sees the
380
+ entire original-base-to-new-candidate diff, including earlier changes. The
381
+ original source admission is unchanged; continuation provenance has separate
382
+ names and records on the revision, job, candidate and acceptance. Prior failed
383
+ reviews stay failed, with their usage and artifacts retained. A stopped Build
384
+ can retry the same selected immutable checkpoint after validation; it cannot
385
+ reuse unfinished Build output. A restart never grants acceptance or triggers
386
+ an automatic retry.
387
+
388
+ Every revision requires new full Verify, independent Review and operator
389
+ approval. Browser and profile gates still apply. Delivery requires the original
390
+ accepted base to equal the configured, unchanged remote target. Continuation
391
+ does not refresh that target (#72), rewrite history, merge or publish anything.
392
+ Changed execution policy requires an explicit fresh start or source replacement,
393
+ not reuse of an incompatible checkpoint. Restore missing private retention from
394
+ backup before retrying; never edit checkpoint manifests or acceptance records.
395
+
396
+ Unfinished/uncommitted Build checkpoints, exit-cause classification, live pending
397
+ feedback and harness deadline hints remain deferred within #42. Opt-in desktop/VM
398
+ observation and browser recordings belong to later #82. This slice does not close
399
+ #42 or qualify those capabilities.
package/docs/setup.md CHANGED
@@ -227,7 +227,7 @@ controller action; other CLI API requests retain their five-second deadline.
227
227
  If the client deadline expires, inspect status and repeat `publish JOB_ID` so
228
228
  the controller can reconcile its saved intent. Restart the installed controller
229
229
  and repeat `publish JOB_ID`; verify the same branch and PR head are read back
230
- and no second PR appears. Delete issue is disabled while the delivery is
230
+ and no second PR appears. Remove local execution history is disabled while the delivery is
231
231
  unresolved, and the controller rejects the same removal through its API. Leave
232
232
  pending/unknown checks labelled as such. After inspection, close the proof PR
233
233
  without merging its fixture change. Do not publish a worker candidate from
@@ -326,3 +326,36 @@ installed file. Keep host identities, credentials, raw logs and customer details
326
326
  out of public issues and package contents. A ready worker is only the foundation:
327
327
  follow [the method](../kit/README.md#first-real-task) for the first explicitly
328
328
  accepted application task and revision-bound checks/review/handoff.
329
+
330
+
331
+ ## Repository Inbox and explicit execution
332
+
333
+ The controller's configured repository selects its issue adapter. On GitHub,
334
+ use the controller host's existing read access; browser login does not supply
335
+ credentials. Open Inbox, verify the provider/repository, refresh and page through
336
+ Open/Closed/All states. Provider failures remain visible; local execution and
337
+ retained history remain available on unsupported hosts. Creating through New
338
+ issue and browsing must leave the execution queue unchanged. Open the issue and
339
+ choose Start work only after reviewing its scope and work type.
340
+
341
+ Readiness is separate from execution state. To use different repository labels,
342
+ set `issueReadinessLabels` in private `factory.json` while stopped and restart:
343
+ `{"triage":"factory:triage","spec":"factory:spec","ready":"factory:ready","blocked":"factory:blocked"}`.
344
+ All four values must be distinct label names. Definition displays the effective
345
+ mapping. This only interprets read metadata; it installs no labels or automations.
346
+ Keep private security reports on their configured private route.
347
+
348
+ Before adopting 0.11.0, qualify the exact installed package and real provider
349
+ lifecycle, including creation without execution, explicit start, duplicate
350
+ rejection and linked subsequent attempts. Inspect affected flows at desktop,
351
+ 390px and 320px in both themes, including failures. Component/provider-fixture
352
+ tests do not qualify those native interactions. Existing source retention,
353
+ continuation, review and trusted delivery acceptance remain required.
354
+
355
+ CLI `inbox --state PATH` opens the same repository page (open issues, page 1).
356
+ Use `--page N` and `--issue-state closed|all` for additional issues/history;
357
+ `inbox --source factory` explicitly selects the legacy execution-only array.
358
+ To add operator scope at admission, use `issue start --url URL --workflow software
359
+ --brief-file operator.md --state PATH` with an optional UTF-8 brief of at most
360
+ 16000 characters. This keeps the remote identity and current-content check;
361
+ local requests still use `issue start --file` or `--draft` without `--brief-file`.
package/docs/workflows.md CHANGED
@@ -24,12 +24,27 @@ kept outside those execution mounts.
24
24
 
25
25
  ## Start work
26
26
 
27
- Open **New issue** in Inbox, choose a repository template (or **Blank issue**),
28
- complete the title and fields, then **Continue**. Or choose
29
- **From GitHub issues** and select an open issue from the configured repository.
30
- The list excludes pull requests, loads 50 GitHub records per page and offers
31
- **Load more**; search filters the loaded issues by title, number or label. GitHub
32
- reads use the controller's existing access, with retry on failure.
27
+ Inbox lists repository issues directly. Choose Open, Closed or All states and use
28
+ Previous/Next page or Refresh issues. Search covers the loaded page and visible
29
+ history, not the whole repository. GitHub returns up to 50 records per page;
30
+ pull requests are excluded, so even a page with zero issues can have a next page.
31
+ Counts name the loaded page and total remains unknown. Authentication/provider
32
+ failures are visible, with any retained page explicitly stale.
33
+
34
+ Open an issue for its context, readiness, and linked execution attempts. Start
35
+ work is deliberate; browsing never starts an agent. Active or unresolved work
36
+ blocks another admission for the same canonical identity, including concurrent
37
+ requests and retries. Completion/cancellation permits a subsequent explicit
38
+ attempt. Failed work stays in history; blocked/interrupted work must first be
39
+ reconciled or cancelled, and unresolved provider delivery continues to block.
40
+ Closed issues and blocked/conflicting readiness labels cannot start new work.
41
+ No readiness labels means unknown, not ready or running. Readiness is planning
42
+ metadata; Needs triage never means an agent is Triaging.
43
+
44
+ **New issue** only composes/publishes: choose a repository template (or **Blank
45
+ issue**), complete the title and fields, then Continue and Create issue. Done
46
+ returns to Inbox, where the new issue appears without a job. Refresh also finds
47
+ issues created directly on the forge. Existing issue selection is in Inbox.
33
48
 
34
49
  Review the instructions and suggested work type before explicitly starting work. The shared,
35
50
  deterministic suggestion prioritizes `track:software` and `track:security` (also
@@ -57,8 +72,8 @@ shown as literal text, never executed or rendered as raw HTML.
57
72
  On a supported repository, **Create issue on GitHub** writes the title, description
58
73
  and template labels to that repository using the displayed host identity. It
59
74
  returns the real issue number/link and **does not start execution**. Select
60
- **Start work** separately, or choose the issue later from the repository list.
61
- Choose **Local execution only** to submit a brief without publishing it. A local
75
+ **Done**, then open the issue in Inbox and choose **Start work**.
76
+ Choose **Local execution request** in Inbox to submit a brief without publishing it. On unsupported hosts, New issue also offers this explicitly local route. A local
62
77
  brief is an execution request; an unfinished form is not a persistent backlog.
63
78
  Use the private security contact route for sensitive reports, never a public issue.
64
79
 
@@ -73,17 +88,24 @@ CLI equivalents (the selected controller must be running):
73
88
 
74
89
  ```sh
75
90
  software-defence-factory issue connection --state PATH
76
- software-defence-factory issue list --source remote --state PATH --page 1
91
+ software-defence-factory inbox --state PATH --page 1 --issue-state open
92
+ # Explicit legacy execution-only JSON array:
93
+ software-defence-factory inbox --state PATH --source factory
77
94
  software-defence-factory issue templates --state PATH
78
95
  software-defence-factory issue draft --state PATH --template bug-report.yml --sha TEMPLATE_SHA --file answers.json > draft.json
79
96
  software-defence-factory issue create --state PATH --draft draft.json --key release-board-fix-01
80
97
  software-defence-factory issue submissions --state PATH
81
98
  software-defence-factory issue recover --state PATH --key release-board-fix-01
82
99
  # Explicit execution, independent of creation:
83
- software-defence-factory issue start --state PATH --url URL --workflow software
100
+ software-defence-factory issue start --state PATH --url URL --workflow software --brief-file operator.md
84
101
  software-defence-factory issue start --state PATH --file brief.md --title "Investigate supplied evidence" --workflow defence --source-ref main
85
102
  ```
86
103
 
104
+ `--brief-file` is optional, UTF-8, at most 16000 characters and valid only with
105
+ a remote issue start. Use `--file` or `--draft` alone for local scope. Inbox
106
+ defaults to a repository page with linked history; `--issue-state closed` or
107
+ `all` and `--page` browse further without starting work.
108
+
87
109
  `answers.json` contains `{"title":"Fix the board","answers":{"problem":"..."}}`;
88
110
  keys match `fields[].id` in `issue templates`. Multi-select/checkbox answers are
89
111
  arrays of exact option labels. `issue create` now publishes only; migrate 0.5.1
@@ -116,3 +138,12 @@ while the installation is stopped, then restart. Workflow order and packaged
116
138
  skills change through reviewed Factory releases. This release does not support
117
139
  per-role profiles or arbitrary editable workflow graphs. Versioned editable
118
140
  definitions are tracked in [#53](https://github.com/arcitai/software-and-defence-factory/issues/53).
141
+
142
+
143
+ Inbox groups URL case, HTTP/HTTPS, trailing-slash, query and fragment aliases by GitHub
144
+ repository and issue number. Credential-bearing URLs, queries, foreign hosts and
145
+ non-issue paths are not admitted by the provider preview. Local-only records and
146
+ executions whose source is missing, closed or outside the loaded page remain in
147
+ Local and other execution history. Removing local execution history preserves
148
+ private evidence and never deletes a provider issue; unresolved delivery guards
149
+ still apply. The separate Execution history tab keeps the execution list/board.
@@ -1,3 +1,4 @@
1
+ import { readinessMapping } from './issue-lifecycle.mjs';
1
2
  import { readFileSync } from 'node:fs';
2
3
  import { join } from 'node:path';
3
4
  import { ROOT, digest, harnessOf } from './lib.mjs';
@@ -39,7 +40,7 @@ export function factoryDefinition(config) {
39
40
  commands: Object.entries(phaseInfo).map(([name, info]) => ({ name, ...info, prompt: info.description,
40
41
  executor: info.owner === 'agent' ? harnessOf(config) : 'factory', timeout: `${config.timeoutSeconds}s` })),
41
42
  skills,
42
- configuration: { harness, agent: harness, // agent is a v1 compatibility alias
43
+ configuration: { issueReadinessLabels: readinessMapping(config.issueReadinessLabels), harness, agent: harness, // agent is a v1 compatibility alias
43
44
  model: config.model || null, check: config.check, timeoutSeconds: config.timeoutSeconds,
44
45
  memoryMiB: config.memoryMiB, cpus: config.cpus || 2,
45
46
  web_verification: config.webVerification?.enabled ? {