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.
- package/THIRD_PARTY_NOTICES.md +10 -0
- package/bin/software-defence-factory.mjs +58 -8
- package/docs/interfaces.md +2 -1
- package/docs/quickstart.md +121 -6
- package/docs/recovery.md +199 -4
- package/docs/setup.md +78 -0
- package/docs/web-verification.md +167 -0
- package/factory/definition.mjs +9 -2
- package/factory/delivery-provider.mjs +20 -0
- package/factory/delivery.mjs +901 -0
- package/factory/execution-evidence.mjs +11 -0
- package/factory/execution-profile.mjs +19 -1
- package/factory/executor.mjs +275 -57
- package/factory/inference-redaction.mjs +123 -0
- package/factory/lib.mjs +37 -6
- package/factory/model-environment.mjs +166 -0
- package/factory/processes.mjs +55 -16
- package/factory/providers/github-delivery.mjs +161 -0
- package/factory/queue.mjs +14 -1
- package/factory/scratch.mjs +15 -0
- package/factory/server.mjs +50 -12
- package/factory/source-admission.mjs +6 -0
- package/factory/ui/assets/index-CLrogux1.css +1 -0
- package/factory/ui/assets/index-Cjk_XtEU.js +13 -0
- package/factory/ui/index.html +2 -2
- package/factory/updates.mjs +1 -1
- package/factory/web/Dockerfile +6 -0
- package/factory/web/containers.mjs +189 -0
- package/factory/web/package-lock.json +42 -0
- package/factory/web/package.json +9 -0
- package/factory/web/qualification-fixture.mjs +97 -0
- package/factory/web/readiness.mjs +26 -0
- package/factory/web/runner.mjs +236 -0
- package/factory/web-readiness.mjs +94 -0
- package/factory/web-verification.mjs +250 -0
- package/factory/workflow-qualification.mjs +541 -0
- package/operator-skills/factory-foundation/SKILL.md +5 -1
- package/package.json +3 -1
- package/scripts/probe-platform.mjs +34 -5
- package/scripts/probe-web.mjs +104 -0
- package/factory/ui/assets/index-Bz6ECdy_.css +0 -1
- 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.
|
package/factory/definition.mjs
CHANGED
|
@@ -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
|
|
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
|
+
}
|