software-defence-factory 0.4.5 → 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/quickstart.md +19 -0
- package/docs/usage.md +48 -0
- package/docs/workflows.md +70 -0
- package/factory/execution-profile.mjs +6 -1
- package/factory/executor.mjs +9 -5
- package/factory/issue-intake.mjs +28 -0
- package/factory/machine.mjs +15 -0
- package/factory/processes.mjs +74 -5
- package/factory/project-links.mjs +20 -0
- package/factory/queue.mjs +9 -3
- package/factory/server.mjs +15 -14
- 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/usage.mjs +173 -0
- package/factory/workflows.mjs +42 -0
- package/kit/repository.md +2 -0
- package/package.json +6 -2
- package/scripts/export-kit.mjs +3 -1
- package/factory/ui/assets/index-BWfaUr91.css +0 -1
- package/factory/ui/assets/index-CnV0vWSl.js +0 -11
package/factory/server.mjs
CHANGED
|
@@ -2,11 +2,14 @@ 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';
|
|
9
11
|
import { VERSION } from './updates.mjs';
|
|
12
|
+
import { readProjectLinks } from './project-links.mjs';
|
|
10
13
|
import { attemptPresentation } from './execution-profile.mjs';
|
|
11
14
|
|
|
12
15
|
function equal(a, b) { return typeof a === 'string' && Buffer.byteLength(a) === Buffer.byteLength(b) && timingSafeEqual(Buffer.from(a), Buffer.from(b)); }
|
|
@@ -28,6 +31,8 @@ export function createController(state, adapter = executors(state)) {
|
|
|
28
31
|
const config = configAt(state), csrf = randomBytes(32).toString('hex');
|
|
29
32
|
const token = readFileSync(join(state, 'worker.token'), 'utf8').trim();
|
|
30
33
|
const queue = new JobQueue(state, adapter);
|
|
34
|
+
const projectLinks = readProjectLinks(config.repo);
|
|
35
|
+
const definitions = workflowDefinitions(config), machine = machineInfo();
|
|
31
36
|
const server = http.createServer(async (request, response) => {
|
|
32
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); };
|
|
33
38
|
response.setHeader('Cache-Control', 'no-store'); response.setHeader('X-Content-Type-Options', 'nosniff'); response.setHeader('Referrer-Policy', 'no-referrer');
|
|
@@ -41,21 +46,13 @@ export function createController(state, adapter = executors(state)) {
|
|
|
41
46
|
const authenticated = equal(request.headers.authorization, `Bearer ${token}`) || equal(request.headers['x-factory-session'], csrf);
|
|
42
47
|
if (request.method === 'GET' && url.pathname === '/api/v1/status') {
|
|
43
48
|
const jobs = queue.all().map(job => ({ ...job, can_request_changes: queue.canRequestChanges(job), runs: job.runs.map(attempt => attemptPresentation({ ...attempt,
|
|
44
|
-
outcome: attempt.outcome || (attempt.state === 'succeeded' ? 'complete' : undefined) })) }));
|
|
45
|
-
return send(200, { version: 1, runtime_version: VERSION, maintenance: queue.maintenance, workflows:
|
|
46
|
-
workers: [{ name: hostname
|
|
47
|
-
repositories: ['app'], repo: config.repo, agent: config.agent });
|
|
49
|
+
outcome: attempt.outcome || (attempt.state === 'succeeded' ? 'complete' : undefined) }, adapter.usage?.(job, attempt))) }));
|
|
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() }],
|
|
52
|
+
repositories: ['app'], repo: config.repo, project_links: projectLinks, agent: config.agent });
|
|
48
53
|
}
|
|
49
54
|
if (request.method === 'GET' && url.pathname === '/api/v1/definitions') {
|
|
50
|
-
|
|
51
|
-
build: 'Implement the accepted task in an isolated checkout, using the bundled policy and skills. Produce a candidate commit and implementation evidence.',
|
|
52
|
-
verify: `Run the configured application check on the candidate commit: ${config.check}`,
|
|
53
|
-
review: 'Independently review the candidate diff and checks. Record pass, changes or blocked with concrete findings.',
|
|
54
|
-
handoff: 'After operator approval, verify the candidate and policy still match the checks and review. Record acceptance without pushing, merging or deploying.',
|
|
55
|
-
defence: 'Read-only triage of an admitted, scoped incident. Produce a private draft, preserve unknowns and make no production change or recovery claim.',
|
|
56
|
-
};
|
|
57
|
-
return send(200, { workflows: { software: ['build','verify','review','handoff'].map(name => ({ name, approval: name === 'handoff' })), defence: [{ name: 'defence' }] },
|
|
58
|
-
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);
|
|
59
56
|
}
|
|
60
57
|
const content = url.pathname.match(/^\/api\/v1\/artifacts\/(job_[a-f0-9]+)~(run_[a-f0-9]+)~([\w.-]+)\/content$/);
|
|
61
58
|
const artifactList = url.pathname.match(/^\/api\/v1\/jobs\/(job_[a-f0-9]+)\/artifacts$/);
|
|
@@ -77,6 +74,10 @@ export function createController(state, adapter = executors(state)) {
|
|
|
77
74
|
if (!equal(request.headers.authorization, `Bearer ${token}`)) throw new QueueError('Operator token required for maintenance', 403);
|
|
78
75
|
return send(200, queue.setMaintenance(input.enabled));
|
|
79
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
|
+
}
|
|
80
81
|
if (url.pathname === '/api/v1/jobs') {
|
|
81
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);
|
|
82
83
|
return send(201, queue.submit(input));
|