software-defence-factory 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/THIRD_PARTY_NOTICES.md +10 -0
  2. package/bin/software-defence-factory.mjs +58 -8
  3. package/docs/interfaces.md +2 -1
  4. package/docs/quickstart.md +121 -6
  5. package/docs/recovery.md +199 -4
  6. package/docs/setup.md +78 -0
  7. package/docs/web-verification.md +167 -0
  8. package/factory/definition.mjs +9 -2
  9. package/factory/delivery-provider.mjs +20 -0
  10. package/factory/delivery.mjs +901 -0
  11. package/factory/execution-evidence.mjs +11 -0
  12. package/factory/execution-profile.mjs +19 -1
  13. package/factory/executor.mjs +275 -57
  14. package/factory/inference-redaction.mjs +123 -0
  15. package/factory/lib.mjs +37 -6
  16. package/factory/model-environment.mjs +166 -0
  17. package/factory/processes.mjs +55 -16
  18. package/factory/providers/github-delivery.mjs +161 -0
  19. package/factory/queue.mjs +14 -1
  20. package/factory/scratch.mjs +15 -0
  21. package/factory/server.mjs +50 -12
  22. package/factory/source-admission.mjs +6 -0
  23. package/factory/ui/assets/index-CLrogux1.css +1 -0
  24. package/factory/ui/assets/index-Cjk_XtEU.js +13 -0
  25. package/factory/ui/index.html +2 -2
  26. package/factory/updates.mjs +1 -1
  27. package/factory/web/Dockerfile +6 -0
  28. package/factory/web/containers.mjs +189 -0
  29. package/factory/web/package-lock.json +42 -0
  30. package/factory/web/package.json +9 -0
  31. package/factory/web/qualification-fixture.mjs +97 -0
  32. package/factory/web/readiness.mjs +26 -0
  33. package/factory/web/runner.mjs +236 -0
  34. package/factory/web-readiness.mjs +94 -0
  35. package/factory/web-verification.mjs +250 -0
  36. package/factory/workflow-qualification.mjs +541 -0
  37. package/operator-skills/factory-foundation/SKILL.md +5 -1
  38. package/package.json +3 -1
  39. package/scripts/probe-platform.mjs +34 -5
  40. package/scripts/probe-web.mjs +104 -0
  41. package/factory/ui/assets/index-Bz6ECdy_.css +0 -1
  42. package/factory/ui/assets/index-DqeOnrGK.js +0 -13
package/docs/setup.md CHANGED
@@ -155,11 +155,87 @@ model servers, public listeners, Docker socket mounts or whole account folders.
155
155
  Cloud inference likewise needs a real provider/model connectivity check without
156
156
  printing credentials. A model-list/health response is connectivity evidence;
157
157
  qualifying model output requires a separately accepted bounded task.
158
+ Store only supported inference settings in `model.env`; jobs reject unrelated
159
+ names such as deployment credentials. Codex receives its OpenAI setting group,
160
+ and Pi receives only its operator-selected provider group. Use
161
+ `--inference-provider` with `init` when Pi's provider is not encoded in the
162
+ operator-configured model name or when multiple provider groups are stored.
163
+ An installation already using Codex account auth may set the validated
164
+ `FACTORY_CODEX_AUTH_JSON` single-line JSON value in private `model.env`; only
165
+ Codex agent phases receive it. Pi and checks do not. See the [quickstart
166
+ inference guidance](quickstart.md#connect-an-application) for constraints.
158
167
 
159
168
  Checkpoint: record the exact source revision, image ID, check command, resource
160
169
  limits, inference connectivity and CI result. Keep product controllers stopped
161
170
  until their tasks are explicitly ready to run.
162
171
 
172
+ Optional web verification is configured separately from the selected harness
173
+ and model. The execution-host owner prepares and pins the Playwright/Chromium
174
+ image, writes trusted stories in private `factory.json`, then runs `web probe`
175
+ and `doctor` on that installation. Use [the browser setup and story contract](web-verification.md)
176
+ for the recipe, dependency preparation and limits. Verify uses separate isolated
177
+ Linux preview and browser containers and cannot qualify native/mobile operating
178
+ systems.
179
+
180
+ ### Optional trusted PR delivery
181
+
182
+ Patch-only handoff is the default. When the operator intends to enable GitHub
183
+ delivery for this installation, configure the exact canonical repository and
184
+ target while initializing it, for example:
185
+
186
+ ```sh
187
+ software-defence-factory init --repo /absolute/path/to/app --harness pi \
188
+ --check "npm ci && npm test" --source-ref main \
189
+ --delivery-provider github \
190
+ --delivery-repository https://github.com/OWNER/REPO \
191
+ --delivery-target main --state /private/state/my-app
192
+ ```
193
+
194
+ The admitted source ref and PR target are independent. Only `main` and `dev`
195
+ are supported target choices in this release. The destination must match the
196
+ canonical origin retained at admission. A changed/renamed origin, moved target
197
+ base or changed Factory policy blocks delivery until fresh applicable evidence
198
+ exists. Configure this before admitting work. Unknown providers remain
199
+ patch-only.
200
+
201
+ Before enabling trusted PR delivery, confirm the repository workflows fit the
202
+ bounded qualification in [recovery](recovery.md#trusted-pr-delivery). Factory
203
+ compares the immutable admitted base and accepted candidate trees, refuses any
204
+ candidate change to a GitHub Actions workflow, and requires jobs that can run
205
+ for generated-branch pushes, PR events or selected-ref manual dispatches to use
206
+ explicit `contents: read` or `none`, a known GitHub-hosted runner, and no
207
+ secrets, protected environments, OIDC or deploy permissions. Unsupported
208
+ workflow syntax keeps the patch-only path available. Organization hooks and
209
+ other external CI automation remain operator-owned and are not audited by this
210
+ check.
211
+
212
+ The operator's authenticated `gh` identity stays on the controller. Keep GitHub
213
+ credentials out of `model.env`, project files and worker containers. Do not add
214
+ deploy credentials or a Docker socket for PR delivery. Use only the existing
215
+ authorized repository and the repository access already approved for the
216
+ operator; do not create a new fixture repository or request broader access.
217
+
218
+ The release lead owns the live provider/browser qualification after installing
219
+ the published candidate separately. Use a separate private state against the
220
+ already authorized Factory repository and `main`; ordinary issue admission,
221
+ checks, independent review and approval must produce the disposable candidate.
222
+ Give its issue a title beginning **[Factory PR handoff proof]**, then use the
223
+ normal **Publish accepted candidate as draft PR** action. Record the job,
224
+ candidate SHA/tree, generated unique branch, exact draft PR, base/head/tree and
225
+ triggered check states. The CLI allows up to ten minutes for this multi-request
226
+ controller action; other CLI API requests retain their five-second deadline.
227
+ If the client deadline expires, inspect status and repeat `publish JOB_ID` so
228
+ the controller can reconcile its saved intent. Restart the installed controller
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
231
+ unresolved, and the controller rejects the same removal through its API. Leave
232
+ pending/unknown checks labelled as such. After inspection, close the proof PR
233
+ without merging its fixture change. Do not publish a worker candidate from
234
+ inside its sandbox.
235
+
236
+ Mocks exercise controller and receipt behavior only. They are not live GitHub
237
+ publication or browser proof; record each separately in [qualification](proof.md).
238
+
163
239
  ## 5. Enable only the intended background services
164
240
 
165
241
  A newly initialized state has no jobs. Before adopting older state, establish
@@ -242,6 +318,8 @@ Copy this private completion record into the installation's handoff:
242
318
  | History and intentionally stopped products preserved | Pending | |
243
319
  | Backups, logs, stop/update/rollback owner and guide | Pending | |
244
320
  | First bounded application task | Not started | Separate task authority and proof |
321
+ | Optional trusted PR provider and target explicitly configured, or patch-only retained | Pending | |
322
+ | Release lead live disposable Factory draft PR/readback/restart/close proof | Not started | Separate from mocked provider tests |
245
323
 
246
324
  Use Pass, Fail or Not applicable with a reason; never infer success from an
247
325
  installed file. Keep host identities, credentials, raw logs and customer details
@@ -0,0 +1,167 @@
1
+ # Optional trusted web verification
2
+
3
+ Browser verification is an operator-controlled extension of the existing
4
+ Verify phase. It is disabled when `webVerification` is absent. Issue text,
5
+ candidate files and generated tests cannot enable it or change its required
6
+ stories. The selected harness and model remain independent of this capability.
7
+
8
+ ## Prepare and qualify Playwright/Chromium
9
+
10
+ The first adapter uses the Playwright 1.63.0 package and Chromium version from
11
+ the matching Microsoft Playwright image. Build it explicitly on the execution
12
+ host from the installed Factory package (or a trusted Factory source checkout):
13
+
14
+ ```sh
15
+ docker build --pull -f factory/web/Dockerfile \
16
+ -t software-defence-factory-playwright:1.63.0 factory/web
17
+ docker image inspect --format '{{.Id}}' software-defence-factory-playwright:1.63.0
18
+ ```
19
+
20
+ Copy the resulting `sha256:…` image ID into private `factory.json`. The
21
+ Dockerfile uses the versioned `mcr.microsoft.com/playwright:v1.63.0-noble`
22
+ base and `npm ci` with the committed lockfile. Factory resolves no tag, pulls
23
+ no image, installs no package and falls back to no other browser during a job.
24
+ Rebuild and deliberately select a new image ID when upgrading the base or
25
+ Playwright version.
26
+
27
+ For a global npm installation, resolve the packaged recipe directory first:
28
+
29
+ ```sh
30
+ factory_package="$(npm root -g)/software-defence-factory"
31
+ docker build --pull -f "$factory_package/factory/web/Dockerfile" \
32
+ -t software-defence-factory-playwright:1.63.0 "$factory_package/factory/web"
33
+ browser_image="$(docker image inspect --format '{{.Id}}' software-defence-factory-playwright:1.63.0)"
34
+ ```
35
+
36
+ Run the readiness check from the selected installation:
37
+
38
+ ```sh
39
+ software-defence-factory web probe --state /private/state/project
40
+ software-defence-factory doctor --state /private/state/project
41
+ ```
42
+
43
+ The probe runs a real headless Chromium click and result assertion in a bounded,
44
+ read-only, `network none` Docker container as the controller UID. Its real
45
+ wall-clock deadline stops the exact labelled probe, and readiness is reported
46
+ only after that container is confirmed removed. A missing image, unsupported
47
+ adapter/version or browser launch failure reports unavailable. The probe never
48
+ downloads or builds an image.
49
+
50
+ ## Configure trusted stories
51
+
52
+ Stop the installation before editing its private `factory.json`. Add an
53
+ operator-authored value like this and adapt names, routes, actions and result
54
+ text to the application:
55
+
56
+ ```json
57
+ {
58
+ "webVerification": {
59
+ "enabled": true,
60
+ "adapter": "playwright",
61
+ "version": "1.63.0",
62
+ "image": "sha256:REPLACE_WITH_LOCAL_IMAGE_ID",
63
+ "previewCommand": ["npm", "run", "preview", "--", "--host", "127.0.0.1", "--port", "4173"],
64
+ "port": 4173,
65
+ "timeoutSeconds": 90,
66
+ "themes": ["light", "dark"],
67
+ "stories": []
68
+ }
69
+ }
70
+ ```
71
+
72
+ `stories` is a small declarative contract; the empty array in this sketch is a
73
+ placeholder and is rejected until the complete trusted story set is supplied.
74
+ Every selected theme requires an interactive `desktop-<theme>` and
75
+ `narrow-<theme>` story. All installations
76
+ also require these named stories, using the first configured theme at desktop
77
+ size:
78
+
79
+ - `busy-disabled`: click an action, observe its disabled state, wait for it to
80
+ become enabled, and check the result text.
81
+ - `result`: exercise an action and assert its result text.
82
+ - `failure-retry`: assert a `role: "alert"` failure, click Retry, then assert a
83
+ `role: "status"` result.
84
+ - `keyboard-focus`: press a supported key and assert the expected control has
85
+ focus.
86
+
87
+ Each story names `viewport` (`desktop` or `narrow`), `theme` (`light` or
88
+ `dark`), a local `path` beginning with `/`, and ordered `steps`. Supported
89
+ steps are `click`, `expect-visible`, `expect-enabled`, `expect-disabled`,
90
+ `expect-text`, `press` and `expect-focused`. Actions and assertions use exact
91
+ accessible roles and names. Arbitrary selectors, JavaScript, source tests and
92
+ candidate-supplied story files are not accepted. `factory/web/qualification-fixture.mjs`
93
+ shows the complete eight-story contract used by the disposable installed
94
+ qualification.
95
+
96
+ `execution.json` freezes and hashes this complete operator configuration with
97
+ the attempt policy. The check evidence records the candidate head/tree, Verify
98
+ attempt, policy hash, immutable browser image ID, adapter/tool/browser versions,
99
+ story-set hash and each story's content hash. Any changed tree, tool, story or
100
+ policy makes prior evidence stale. Missing, failed, unavailable, inconclusive
101
+ or malformed required evidence blocks Review, handoff and trusted PR delivery.
102
+ The run summary and `web-verification.json` artifact are shared through CLI,
103
+ API and dashboard; the dashboard renders traces as text and retained PNGs as
104
+ images. It does not execute candidate HTML in the controller origin.
105
+
106
+ ## Package and preview guidance
107
+
108
+ Use the configured project check for dependency preparation and build output.
109
+ For Node projects, install from the committed lockfile with the project's
110
+ frozen install command (for example `npm ci --ignore-scripts`), run the build,
111
+ then configure `previewCommand` to start the already-prepared local preview
112
+ from the project root in the disposable check copy.
113
+ The preview phase reuses that check's disposable scratch copy. It does not run
114
+ another install or contact a package registry. The qualified project job image
115
+ provides the preview runtime. Do not add secrets to the preview environment or
116
+ image.
117
+
118
+ Verify starts two containers with separate mount and PID namespaces. The
119
+ preview uses the configured project job image, the candidate at `/workspace`
120
+ read-only, and the writable `/scratch` copy. It runs as the controller UID with
121
+ `network none`, dropped capabilities, a read-only root and bounded resources.
122
+ The browser uses the pinned Playwright image as the same UID, with its own
123
+ read-only root, writable temporary profile and a separate private output mount.
124
+ It receives the frozen trusted stories on stdin and does not mount the
125
+ candidate, project scratch or controller results. The browser shares only the
126
+ exact preview container's `network none` namespace to reach its loopback port;
127
+ each container retains its own PID namespace. Neither container gets host
128
+ networking, published ports, extra capabilities, a Docker socket, controller,
129
+ forge or inference credentials, or a user profile.
130
+
131
+ The controller enforces a real wall-clock deadline, stops and reconciles both
132
+ exact labelled containers, then reads the bounded result and PNG screenshots
133
+ from the browser-only mount. The private result contains candidate/tree,
134
+ attempt, policy, pinned tool/image and story hashes, bounded action traces and
135
+ screenshot hashes. CLI, API and dashboard expose the same evidence; screenshots
136
+ can be viewed as images, and no candidate HTML is served on the controller
137
+ origin. Chromium is headless and uses the outer Docker boundary; this is not a
138
+ hosted browser service.
139
+
140
+ This adapter proves only Linux Chromium interaction with a local web preview.
141
+ It does not qualify Android/iOS, desktop-native applications, Safari, Firefox,
142
+ mobile operating systems, production access or an application's overall
143
+ correctness. Deterministic checks and independent Review remain separate.
144
+
145
+ ## Installed synthetic fixture qualification
146
+
147
+ The release lead owns this Docker/browser qualification on the installed
148
+ execution host. Use a separate demo state, complete and inspect the demo's
149
+ normal sample handoff, stop that controller, then run the exact qualification
150
+ command with the already-built browser image ID:
151
+
152
+ ```sh
153
+ software-defence-factory demo --state /private/state/sdf-web-proof --port 7348
154
+ # Review and approve the synthetic demo task in its dashboard.
155
+ software-defence-factory stop --state /private/state/sdf-web-proof
156
+ browser_image="$(docker image inspect --format '{{.Id}}' software-defence-factory-playwright:1.63.0)"
157
+ software-defence-factory qualify-web --state /private/state/sdf-web-proof --image "$browser_image"
158
+ ```
159
+
160
+ The installed command probes actual Chromium interaction, then runs a
161
+ disposable delayed-save/failure-retry fixture through mock Build/Review and
162
+ real Docker Verify. Its deliberate variant removes the busy disabled state and
163
+ changes the result text; Verify must fail and no acceptance record may appear.
164
+ The command records a private `qualification.json` under the demo state's
165
+ private qualification directory. It never targets an application or calls a
166
+ model/provider. Do not report this as passed until the lead observes the
167
+ installed invocation and its result.
@@ -1,6 +1,7 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { ROOT, digest, harnessOf } from './lib.mjs';
4
+ import { expectedWebStories, webPolicyHash } from './web-verification.mjs';
4
5
 
5
6
  // Execution order is shared with the queue; presentation cannot invent phases.
6
7
  export const WORKFLOWS = Object.freeze({
@@ -9,7 +10,7 @@ export const WORKFLOWS = Object.freeze({
9
10
  });
10
11
  const phaseInfo = {
11
12
  build: { title: 'Implement', owner: 'agent', skills: ['factory-implement'], description: 'Implement the accepted scope in an isolated checkout. Produce a candidate and evidence.' },
12
- verify: { title: 'Check', owner: 'factory', skills: [], description: 'Run the project check command against the candidate. A failure stops delivery.' },
13
+ verify: { title: 'Check', owner: 'factory', skills: [], description: 'Run project checks and any optional trusted browser stories against the candidate. Missing or non-passing required proof stops delivery.' },
13
14
  review: { title: 'Review', owner: 'agent', skills: ['factory-review', 'factory-security'], description: 'Review the candidate and evidence in a separate agent invocation. Security review applies when required by the accepted scope.' },
14
15
  handoff: { title: 'Accept & hand off', owner: 'operator', skills: [], description: 'Wait for operator approval, then confirm the candidate and policy still match the checks and review. Record acceptance; do not push, merge or deploy.' },
15
16
  defence: { title: 'Investigate', owner: 'agent', skills: ['factory-security'], description: 'Investigate supplied incident evidence within the accepted scope. Produce a private draft with findings and unknowns. No production access or recovery action is granted.' },
@@ -40,7 +41,13 @@ export function factoryDefinition(config) {
40
41
  skills,
41
42
  configuration: { harness, agent: harness, // agent is a v1 compatibility alias
42
43
  model: config.model || null, check: config.check, timeoutSeconds: config.timeoutSeconds,
43
- memoryMiB: config.memoryMiB, cpus: config.cpus || 2 },
44
+ memoryMiB: config.memoryMiB, cpus: config.cpus || 2,
45
+ web_verification: config.webVerification?.enabled ? {
46
+ enabled: true, adapter: config.webVerification.adapter, version: config.webVerification.version,
47
+ browser: 'chromium', image: config.webVerification.image, policy_hash: webPolicyHash(config.webVerification),
48
+ required_stories: expectedWebStories(config.webVerification), platform: 'linux-container', coverage: 'web',
49
+ } : { enabled: false },
50
+ },
44
51
  method: {
45
52
  preparation: ['factory-triage', 'factory-spec'], evaluation: ['factory-evaluate'],
46
53
  instructions: 'All six skills are available read-only to agent phases. A skill is an instruction set, not a separate agent or an automatic workflow step.',
@@ -0,0 +1,20 @@
1
+ import { githubDeliveryProvider } from './providers/github-delivery.mjs';
2
+
3
+ // Provider selection is a trusted installation setting. Unknown providers do
4
+ // not get guessed URLs, credentials or a write path; they remain patch-only.
5
+ export function deliveryProvider(config, integrations = {}) {
6
+ const provider = config.delivery?.provider;
7
+ if (provider !== 'github') return { id: provider || null, supported: false };
8
+ return integrations.deliveryProvider || githubDeliveryProvider();
9
+ }
10
+
11
+ export function deliveryProviderInfo(config, provider) {
12
+ const enabled = provider?.supported === true && config.delivery?.provider === provider.id;
13
+ return {
14
+ enabled,
15
+ mode: enabled ? 'trusted_pr' : 'patch_only',
16
+ provider: config.delivery?.provider || null,
17
+ repository: enabled ? config.delivery.repository : null,
18
+ target: enabled ? config.delivery.target : null,
19
+ };
20
+ }