software-defence-factory 0.4.6 → 0.4.7
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/.github/ISSUE_TEMPLATE/bug-report.yml +32 -0
- package/.github/ISSUE_TEMPLATE/feature-request.yml +24 -0
- package/README.md +1 -1
- package/THIRD_PARTY_NOTICES.md +28 -0
- package/bin/software-defence-factory.mjs +5 -6
- package/docs/workflows.md +70 -0
- package/factory/issue-intake.mjs +28 -0
- package/factory/machine.mjs +15 -0
- package/factory/project-links.mjs +1 -1
- package/factory/queue.mjs +1 -1
- package/factory/server.mjs +11 -12
- package/factory/ui/assets/index-BqY8avus.js +11 -0
- package/factory/ui/assets/index-DxK01fwc.css +1 -0
- package/factory/ui/index.html +2 -2
- package/factory/workflows.mjs +42 -0
- package/kit/repository.md +2 -0
- package/package.json +4 -1
- package/scripts/export-kit.mjs +3 -1
- package/factory/ui/assets/index-BmRkDIpU.css +0 -1
- package/factory/ui/assets/index-DHpjJrPf.js +0 -11
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: Bug report
|
|
2
|
+
description: Report an observable failure in the CLI, dashboard or runtime.
|
|
3
|
+
title: "[Bug] "
|
|
4
|
+
labels: ["bug", "factory:triage"]
|
|
5
|
+
body:
|
|
6
|
+
- type: markdown
|
|
7
|
+
attributes:
|
|
8
|
+
value: Do not include credentials, private incident evidence or security exploit details. Use the security reporting link for vulnerabilities.
|
|
9
|
+
- type: textarea
|
|
10
|
+
id: problem
|
|
11
|
+
attributes:
|
|
12
|
+
label: What happened, and what should happen?
|
|
13
|
+
validations:
|
|
14
|
+
required: true
|
|
15
|
+
- type: textarea
|
|
16
|
+
id: reproduce
|
|
17
|
+
attributes:
|
|
18
|
+
label: Steps to reproduce
|
|
19
|
+
description: Include a minimal example and sanitized error output.
|
|
20
|
+
validations:
|
|
21
|
+
required: true
|
|
22
|
+
- type: textarea
|
|
23
|
+
id: environment
|
|
24
|
+
attributes:
|
|
25
|
+
label: Environment
|
|
26
|
+
description: Factory version, OS, agent and whether this affects the CLI or dashboard. Do not paste factory.json or model.env.
|
|
27
|
+
validations:
|
|
28
|
+
required: true
|
|
29
|
+
- type: textarea
|
|
30
|
+
id: acceptance
|
|
31
|
+
attributes:
|
|
32
|
+
label: How can we verify the fix?
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Feature request
|
|
2
|
+
description: Propose a concrete improvement to Software & Defence Factory.
|
|
3
|
+
title: "[Feature] "
|
|
4
|
+
labels: ["enhancement", "factory:triage"]
|
|
5
|
+
body:
|
|
6
|
+
- type: textarea
|
|
7
|
+
id: problem
|
|
8
|
+
attributes:
|
|
9
|
+
label: Who needs what to improve?
|
|
10
|
+
description: Describe the problem and when it occurs.
|
|
11
|
+
validations:
|
|
12
|
+
required: true
|
|
13
|
+
- type: textarea
|
|
14
|
+
id: outcome
|
|
15
|
+
attributes:
|
|
16
|
+
label: Desired behavior and acceptance criteria
|
|
17
|
+
description: Include what should be possible through both CLI and dashboard, when relevant.
|
|
18
|
+
validations:
|
|
19
|
+
required: true
|
|
20
|
+
- type: textarea
|
|
21
|
+
id: boundaries
|
|
22
|
+
attributes:
|
|
23
|
+
label: Boundaries and alternatives
|
|
24
|
+
description: What should remain outside this change?
|
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ flowchart LR
|
|
|
42
42
|
|
|
43
43
|
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.
|
|
44
44
|
|
|
45
|
-
The per-project dashboard keeps Software and Defence in one searchable task list, with workflow/model/status filters, a board, task details, files and history. Analytics separates workflows and shows recorded duration and token usage with explicit coverage; missing billing amounts stay unknown. View repo and New issue use the configured GitHub origin. Workers and
|
|
45
|
+
The per-project dashboard keeps Software and Defence in one searchable task list, with workflow/model/status filters, a board, task details, files and history. Analytics separates workflows and shows recorded duration and token usage with explicit coverage; missing billing amounts stay unknown. View repo and New issue use the configured GitHub origin. Start work opens a modal to import/review an issue or write a scoped brief; it never automatically starts work from a new issue. See [workflows and skills](docs/workflows.md). Workers show detected host identity and capacity. Workflows show the actual phases, six packaged skills and selected configuration, shared with the `workflows` CLI command. It binds to localhost and can be reached remotely through SSH. One controller executes one job phase at a time; each job has its own checkout and bounded Docker containers.
|
|
46
46
|
|
|
47
47
|
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).
|
|
48
48
|
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -546,3 +546,31 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
546
546
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
547
547
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
548
548
|
SOFTWARE.
|
|
549
|
+
|
|
550
|
+
## GitHub mark (Octicons)
|
|
551
|
+
|
|
552
|
+
`dashboard/src/github-icon.jsx` uses the mark-github-16 path from
|
|
553
|
+
[primer/octicons](https://github.com/primer/octicons/blob/main/icons/mark-github-16.svg).
|
|
554
|
+
The mark identifies the configured GitHub repository; it is not Factory branding.
|
|
555
|
+
|
|
556
|
+
MIT License
|
|
557
|
+
|
|
558
|
+
Copyright (c) 2026 GitHub Inc.
|
|
559
|
+
|
|
560
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
561
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
562
|
+
in the Software without restriction, including without limitation the rights
|
|
563
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
564
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
565
|
+
furnished to do so, subject to the following conditions:
|
|
566
|
+
|
|
567
|
+
The above copyright notice and this permission notice shall be included in all
|
|
568
|
+
copies or substantial portions of the Software.
|
|
569
|
+
|
|
570
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
571
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
572
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
573
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
574
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
575
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
576
|
+
SOFTWARE.
|
|
@@ -6,6 +6,8 @@ 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 } from '../factory/lib.mjs';
|
|
8
8
|
import { assertInstalledJobImage, installCustomJobImage, installStandardJobImage, inspectImageInstallation } from '../factory/image-install.mjs';
|
|
9
|
+
import { readIssue } from '../factory/issue-intake.mjs';
|
|
10
|
+
import { workflowDefinitions } from '../factory/workflows.mjs';
|
|
9
11
|
import { admitIncident } from '../factory/incident.mjs';
|
|
10
12
|
import { DEFAULT_DEMO_STATE } from '../factory/paths.mjs';
|
|
11
13
|
import { bootstrap, registerInstallation, VERSION } from '../factory/updates.mjs';
|
|
@@ -140,6 +142,7 @@ try {
|
|
|
140
142
|
else await manageService('controller',positional[0],state,flags);
|
|
141
143
|
}
|
|
142
144
|
else if(command==='tunnel')await manageService('tunnel',positional[0],state,flags);
|
|
145
|
+
else if(command==='workflows')console.log(JSON.stringify(workflowDefinitions(configAt(state)),null,2));
|
|
143
146
|
else if(command==='status') { const snapshot=await api(state,'/api/v1/status');delete snapshot.csrf_token;console.log(JSON.stringify(snapshot,null,2)); }
|
|
144
147
|
else if(command==='doctor') {
|
|
145
148
|
const config=configAt(state),dockerVersion=run('docker',['info','--format','{{.ServerVersion}}']),imageStatus=inspectImageInstallation(state,config);
|
|
@@ -148,12 +151,7 @@ try {
|
|
|
148
151
|
} else if(command==='run') {
|
|
149
152
|
let spec;
|
|
150
153
|
if(flags.issue) {
|
|
151
|
-
|
|
152
|
-
const origin=run('git',['-C',configAt(state).repo,'remote','get-url','origin']);
|
|
153
|
-
const match=origin.match(/^(?:https:\/\/github\.com\/|git@github\.com:)([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
154
|
-
if(!match||!flags.issue.toLowerCase().startsWith(`https://github.com/${match[1].toLowerCase()}/issues/`))throw new Error('Issue does not belong to the configured app origin; use a scoped task file for other input');
|
|
155
|
-
const issue=JSON.parse(run('gh',['issue','view',flags.issue,'--json','title,body,url']));
|
|
156
|
-
spec=`Issue: ${issue.url}\n${issue.title}\n\n${issue.body}`;
|
|
154
|
+
spec=(await readIssue(configAt(state).repo,flags.issue)).spec;
|
|
157
155
|
} else if(flags.file)spec=readFileSync(resolve(flags.file),'utf8');
|
|
158
156
|
else throw new Error('Use --file task.md or --issue https://github.com/owner/repo/issues/123');
|
|
159
157
|
if(!configAt(state).check?.trim())throw new Error('Configure an app check before submitting software work');
|
|
@@ -188,6 +186,7 @@ try {
|
|
|
188
186
|
init --repo PATH --agent codex|pi|custom --check "npm ci && npm test"
|
|
189
187
|
install [--image LOCAL_REF] Build the standard image, or select an existing local image
|
|
190
188
|
doctor | up | status | stop Inspect / operate your private installation
|
|
189
|
+
workflows Inspect actual workflow phases, skills and configuration
|
|
191
190
|
serve Foreground supervisor
|
|
192
191
|
service [print] Print a systemd user-service definition
|
|
193
192
|
service install|start|stop|restart Manage a Linux user service (--state PATH)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Workflows, skills and work intake
|
|
2
|
+
|
|
3
|
+
An **issue** defines the problem, boundaries and acceptance criteria. A
|
|
4
|
+
**workflow** defines the runtime's ordered phases and gates. A **task** is one
|
|
5
|
+
execution of that workflow; retries and revisions preserve its prior attempts.
|
|
6
|
+
A **skill** gives an agent instructions for doing part of the work. Six skills
|
|
7
|
+
do not mean six agents, and a skill does not schedule a job.
|
|
8
|
+
|
|
9
|
+
The queue, dashboard catalog and `software-defence-factory workflows --state PATH`
|
|
10
|
+
use the same installed definition. The CLI command works while the installation
|
|
11
|
+
is stopped; the dashboard reflects its controller's configuration at startup.
|
|
12
|
+
|
|
13
|
+
## Prepare, execute, evaluate
|
|
14
|
+
|
|
15
|
+
Triage and specification use `factory-triage` and `factory-spec` before admission.
|
|
16
|
+
They are method activities, not hidden automatically executed phases.
|
|
17
|
+
|
|
18
|
+
Software execution proceeds through Build → Check → Review → operator approval
|
|
19
|
+
→ Handoff. Build and review use the configured agent (Codex, Pi or a custom
|
|
20
|
+
executor), with `factory-implement`, `factory-review` and, when scoped,
|
|
21
|
+
`factory-security`. Checks run the application's configured command. Handoff
|
|
22
|
+
confirms the accepted candidate and evidence; it does not publish or deploy.
|
|
23
|
+
|
|
24
|
+
Defence executes a scoped investigation and produces a private draft. It is not
|
|
25
|
+
a production recovery agent. Validated incident intake still uses
|
|
26
|
+
`incident --file incident.json`; the dashboard's investigation brief is not an
|
|
27
|
+
equivalent typed incident adapter.
|
|
28
|
+
|
|
29
|
+
`factory-evaluate` supports a separately scoped comparison. All six packaged
|
|
30
|
+
skills are mounted read-only for agent phases and visible in the dashboard's
|
|
31
|
+
Skills tab, including their exact installed instructions and file hashes.
|
|
32
|
+
Availability does not prove an agent followed every instruction.
|
|
33
|
+
|
|
34
|
+
## Start work
|
|
35
|
+
|
|
36
|
+
**New issue** opens the configured GitHub repository's issue chooser. It creates
|
|
37
|
+
backlog only. **Start work** opens a modal for explicit execution:
|
|
38
|
+
|
|
39
|
+
- From GitHub issue: load a URL from this project's origin, review the imported
|
|
40
|
+
title/body and acceptance criteria, then start. GitHub CLI access is required
|
|
41
|
+
on the controller host. Imports are bounded and cannot select another repo.
|
|
42
|
+
- Write instructions: supply the accepted scope directly. The project and model
|
|
43
|
+
default to the installation; optional title/reference/model settings are
|
|
44
|
+
secondary. A reference link does not fetch instructions.
|
|
45
|
+
|
|
46
|
+
The CLI uses the same issue reader for `run --issue URL`. It deliberately submits
|
|
47
|
+
when invoked; the dashboard lets the operator inspect/edit the imported scope
|
|
48
|
+
before submitting. Issue text is untrusted input, not authority to change policy.
|
|
49
|
+
No issue, label or import alone starts work. Automatic polling/triggers require a
|
|
50
|
+
separate opt-in admission policy and are not implemented by these forms.
|
|
51
|
+
|
|
52
|
+
## Customize deliberately
|
|
53
|
+
|
|
54
|
+
Stop the installation before editing its private `factory.json`. Select the
|
|
55
|
+
agent, model, command, check and resource limits according to the setup guide,
|
|
56
|
+
then restart. Never put credentials in task text; model credentials belong in
|
|
57
|
+
private `model.env`. The Configuration tab exposes selected non-secret settings,
|
|
58
|
+
not raw environment or command arguments.
|
|
59
|
+
|
|
60
|
+
This release does not provide an editable workflow engine. Phase order,
|
|
61
|
+
approval gates and bundled skills change through a reviewed Factory release.
|
|
62
|
+
Editing an exported kit does not change the runtime's mounted skills. A future
|
|
63
|
+
editor must change the same CLI/API contract and acceptance policy, not merely
|
|
64
|
+
editable text in the dashboard. See issue #37.
|
|
65
|
+
|
|
66
|
+
Worker identity uses the host OS APIs for hostname, OS, architecture, CPU count
|
|
67
|
+
and memory. Hardware model is best-effort when the OS exposes it (Linux DMI),
|
|
68
|
+
with hostname as the fallback. It does not infer a particular machine from the
|
|
69
|
+
project path or expose serials, network addresses or credentials. Host resources
|
|
70
|
+
are distinct from each job's container limits.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { readProjectLinks } from './project-links.mjs';
|
|
4
|
+
const exec = promisify(execFile);
|
|
5
|
+
|
|
6
|
+
export function validateIssueURL(repoURL, value) {
|
|
7
|
+
if (typeof value !== 'string' || value.length > 2048 || !/^https:\/\/github\.com\/[A-Za-z0-9-]+\/[A-Za-z0-9_.-]+\/issues\/[1-9][0-9]*$/.test(value)) throw new Error('Enter a GitHub issue URL without query parameters.');
|
|
8
|
+
if (!repoURL || !value.toLowerCase().startsWith(`${repoURL.toLowerCase()}/issues/`)) throw new Error('Issue does not belong to this project’s configured GitHub origin.');
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
export async function readIssue(repo, url, read = async url => {
|
|
12
|
+
try {
|
|
13
|
+
const { stdout } = await exec('gh', ['issue', 'view', url, '--json', 'title,body,url'], {
|
|
14
|
+
encoding: 'utf8', timeout: 10000, maxBuffer: 300000,
|
|
15
|
+
env: { ...process.env, GH_PROMPT_DISABLED: '1', GH_PAGER: 'cat' },
|
|
16
|
+
});
|
|
17
|
+
return JSON.parse(stdout);
|
|
18
|
+
} catch { throw new Error('Could not read the issue. Check the URL and GitHub CLI access on the controller host.'); }
|
|
19
|
+
}) {
|
|
20
|
+
const repoURL = readProjectLinks(repo)?.repository;
|
|
21
|
+
validateIssueURL(repoURL, url);
|
|
22
|
+
const issue = await read(url);
|
|
23
|
+
validateIssueURL(repoURL, issue?.url);
|
|
24
|
+
if (issue.url.toLowerCase() !== url.toLowerCase() || typeof issue.title !== 'string' || !issue.title.trim() || typeof issue.body !== 'string') throw new Error('GitHub returned an unexpected issue.');
|
|
25
|
+
const spec = `Issue: ${issue.url}\n${issue.title}\n\n${issue.body}`;
|
|
26
|
+
if (Buffer.byteLength(spec) > 240000) throw new Error('Issue exceeds the 240 KB task limit. Use a bounded task file instead.');
|
|
27
|
+
return { title: issue.title, url: issue.url, body: issue.body, spec };
|
|
28
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { hostname, platform, arch, cpus, totalmem, release } from 'node:os';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
// No subprocess, environment, serial number, network identity or credential data.
|
|
5
|
+
function hardwareModel() {
|
|
6
|
+
if (platform() !== 'linux') return null;
|
|
7
|
+
try {
|
|
8
|
+
const value = readFileSync('/sys/devices/virtual/dmi/id/product_name', 'utf8').trim();
|
|
9
|
+
return value && value.length <= 160 && !/[\x00-\x1f]/.test(value) && !/default string|to be filled|system product name/i.test(value) ? value : null;
|
|
10
|
+
} catch { return null; }
|
|
11
|
+
}
|
|
12
|
+
export function machineInfo() {
|
|
13
|
+
return { hostname: hostname(), hardware: hardwareModel(), platform: platform(), architecture: arch(), osRelease: release(),
|
|
14
|
+
logicalCpus: cpus().length, memoryMiB: Math.round(totalmem() / 1024 / 1024) };
|
|
15
|
+
}
|
|
@@ -7,7 +7,7 @@ export function githubProjectLinks(remote) {
|
|
|
7
7
|
const match = remote.trim().match(/^(?:https:\/\/github\.com\/|git@github\.com:|ssh:\/\/git@github\.com\/)([A-Za-z0-9-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?\/?$/);
|
|
8
8
|
if (!match || ['.', '..'].includes(match[2])) return undefined;
|
|
9
9
|
const repository = `https://github.com/${match[1]}/${match[2]}`;
|
|
10
|
-
return { repository, new_issue: `${repository}/issues/new`, source: 'configured_git_origin' };
|
|
10
|
+
return { repository, new_issue: `${repository}/issues/new/choose`, source: 'configured_git_origin' };
|
|
11
11
|
}
|
|
12
12
|
export function readProjectLinks(repo) {
|
|
13
13
|
try {
|
package/factory/queue.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { existsSync, writeFileSync, rmSync } from 'node:fs';
|
|
5
5
|
import { usageFields } from './usage.mjs';
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
import { WORKFLOWS as workflows } from './workflows.mjs';
|
|
8
8
|
const id = prefix => prefix + '_' + randomBytes(12).toString('hex');
|
|
9
9
|
const now = () => new Date().toISOString();
|
|
10
10
|
export class QueueError extends Error { constructor(message, status = 409) { super(message); this.status = status; } }
|
package/factory/server.mjs
CHANGED
|
@@ -2,7 +2,9 @@ import http from 'node:http';
|
|
|
2
2
|
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
3
3
|
import { existsSync, readFileSync, readdirSync, lstatSync, realpathSync } from 'node:fs';
|
|
4
4
|
import { join, resolve, sep } from 'node:path';
|
|
5
|
-
import {
|
|
5
|
+
import { readIssue } from './issue-intake.mjs';
|
|
6
|
+
import { machineInfo } from './machine.mjs';
|
|
7
|
+
import { workflowDefinitions } from './workflows.mjs';
|
|
6
8
|
import { JobQueue, QueueError } from './queue.mjs';
|
|
7
9
|
import { executors } from './processes.mjs';
|
|
8
10
|
import { configAt, ROOT } from './lib.mjs';
|
|
@@ -30,6 +32,7 @@ export function createController(state, adapter = executors(state)) {
|
|
|
30
32
|
const token = readFileSync(join(state, 'worker.token'), 'utf8').trim();
|
|
31
33
|
const queue = new JobQueue(state, adapter);
|
|
32
34
|
const projectLinks = readProjectLinks(config.repo);
|
|
35
|
+
const definitions = workflowDefinitions(config), machine = machineInfo();
|
|
33
36
|
const server = http.createServer(async (request, response) => {
|
|
34
37
|
const send = (status, value, type = 'application/json; charset=utf-8') => { response.writeHead(status, { 'Content-Type': type }); response.end(type.startsWith('application/json') ? JSON.stringify(value) : value); };
|
|
35
38
|
response.setHeader('Cache-Control', 'no-store'); response.setHeader('X-Content-Type-Options', 'nosniff'); response.setHeader('Referrer-Policy', 'no-referrer');
|
|
@@ -44,20 +47,12 @@ export function createController(state, adapter = executors(state)) {
|
|
|
44
47
|
if (request.method === 'GET' && url.pathname === '/api/v1/status') {
|
|
45
48
|
const jobs = queue.all().map(job => ({ ...job, can_request_changes: queue.canRequestChanges(job), runs: job.runs.map(attempt => attemptPresentation({ ...attempt,
|
|
46
49
|
outcome: attempt.outcome || (attempt.state === 'succeeded' ? 'complete' : undefined) }, adapter.usage?.(job, attempt))) }));
|
|
47
|
-
return send(200, { version: 1, runtime_version: VERSION, maintenance: queue.maintenance, workflows:
|
|
48
|
-
workers: [{ name: hostname
|
|
50
|
+
return send(200, { version: 1, runtime_version: VERSION, maintenance: queue.maintenance, workflows: Object.keys(definitions.workflows), commands: [], triggers: [], jobs, csrf_token: csrf,
|
|
51
|
+
workers: [{ name: machine.hardware || machine.hostname, machine, instance_id: 'local-executor', repositories: ['app'], connected: !queue.closing, last_seen_at: new Date().toISOString() }],
|
|
49
52
|
repositories: ['app'], repo: config.repo, project_links: projectLinks, agent: config.agent });
|
|
50
53
|
}
|
|
51
54
|
if (request.method === 'GET' && url.pathname === '/api/v1/definitions') {
|
|
52
|
-
|
|
53
|
-
build: 'Implement the accepted task in an isolated checkout, using the bundled policy and skills. Produce a candidate commit and implementation evidence.',
|
|
54
|
-
verify: `Run the configured application check on the candidate commit: ${config.check}`,
|
|
55
|
-
review: 'Independently review the candidate diff and checks. Record pass, changes or blocked with concrete findings.',
|
|
56
|
-
handoff: 'After operator approval, verify the candidate and policy still match the checks and review. Record acceptance without pushing, merging or deploying.',
|
|
57
|
-
defence: 'Read-only triage of an admitted, scoped incident. Produce a private draft, preserve unknowns and make no production change or recovery claim.',
|
|
58
|
-
};
|
|
59
|
-
return send(200, { workflows: { software: ['build','verify','review','handoff'].map(name => ({ name, approval: name === 'handoff' })), defence: [{ name: 'defence' }] },
|
|
60
|
-
commands: Object.entries(descriptions).map(([name,prompt]) => ({ name, prompt, executor: ['verify','handoff'].includes(name) ? 'deterministic' : config.agent, timeout: `${config.timeoutSeconds}s` })) });
|
|
55
|
+
return send(200, definitions);
|
|
61
56
|
}
|
|
62
57
|
const content = url.pathname.match(/^\/api\/v1\/artifacts\/(job_[a-f0-9]+)~(run_[a-f0-9]+)~([\w.-]+)\/content$/);
|
|
63
58
|
const artifactList = url.pathname.match(/^\/api\/v1\/jobs\/(job_[a-f0-9]+)\/artifacts$/);
|
|
@@ -79,6 +74,10 @@ export function createController(state, adapter = executors(state)) {
|
|
|
79
74
|
if (!equal(request.headers.authorization, `Bearer ${token}`)) throw new QueueError('Operator token required for maintenance', 403);
|
|
80
75
|
return send(200, queue.setMaintenance(input.enabled));
|
|
81
76
|
}
|
|
77
|
+
if (url.pathname === '/api/v1/issues/preview') {
|
|
78
|
+
try { return send(200, await readIssue(config.repo, input.url)); }
|
|
79
|
+
catch (error) { throw new QueueError(error.message, 400); }
|
|
80
|
+
}
|
|
82
81
|
if (url.pathname === '/api/v1/jobs') {
|
|
83
82
|
if (input.model && input.model !== config.model && !['codex','pi'].includes(config.agent)) throw new QueueError('Model overrides require a codex or pi executor', 400);
|
|
84
83
|
return send(201, queue.submit(input));
|