software-defence-factory 0.15.0 → 0.15.1

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/docs/npm.md CHANGED
@@ -94,6 +94,18 @@ jobs and history are preserved. Existing processes keep their original code.
94
94
  Set `SDF_AUTO_UPDATE=0` to skip automatic network checks for a command.
95
95
  Source checkouts remain managed by Git and do not update themselves.
96
96
 
97
+ The exact selected release is downloaded with npm's `--prefer-online` metadata
98
+ refresh and `--prefer-offline=false`; this does not change account settings.
99
+ An `ETARGET` visibility failure
100
+ gets at most three attempts, separated by one second, within the same total
101
+ 120-second download budget (including waits). Offline, authentication and package
102
+ identity errors are not retried by Factory. A successful download must match the
103
+ requested package name, version and CLI entry before immutable adoption. Exhaustion
104
+ cleans only that download's staging directory and retains the selected runtime,
105
+ configuration and job history. Managed updates release their idle reservations on
106
+ download failure; normal reservation/recovery rules in [services](services.md)
107
+ still apply. The retries add no extra download maintenance budget.
108
+
97
109
  CLI updates do not rebuild or update Docker images automatically. Stop the
98
110
  installation and run `install --state PATH` when intentionally adopting a new
99
111
  runtime image; repeat the relevant qualification before resuming jobs. Install retains the old and new
@@ -104,8 +116,8 @@ installation or retained attempt needs them.
104
116
 
105
117
  ## Protected evidence compatibility
106
118
 
107
- Factory 0.15.0 recognizes version-1 execution profiles emitted by native
108
- **0.8.0, 0.9.0, 0.9.1, 0.10.0, 0.11.0, 0.11.1, 0.11.2, 0.12.0, 0.13.0, 0.13.1, 0.14.0 and 0.15.0**. This is an exact allowlist in
119
+ Factory 0.15.1 recognizes version-1 execution profiles emitted by native
120
+ **0.8.0, 0.9.0, 0.9.1, 0.10.0, 0.11.0, 0.11.1, 0.11.2, 0.12.0, 0.13.0, 0.13.1, 0.14.0, 0.15.0 and 0.15.1**. This is an exact allowlist in
109
121
  `factory/execution-profile.mjs`, independent of the installed package version;
110
122
  it is not a semver range or an automatic promise for later releases. Unknown
111
123
  runtime strings, unknown profile formats and incomplete legacy acceptance
@@ -172,7 +184,7 @@ Verify evidence and cannot authorize acceptance or publication.
172
184
 
173
185
  Version 0.14.0 preserves the v1 writer only for unchanged inherited installations:
174
186
  policy bytes, candidate/check/review/acceptance bindings, mounts and credential
175
- rules stay compatible. Adopted role overrides use **v2 from 0.14.0 and 0.15.0**, recording
187
+ rules stay compatible. Adopted role overrides use **v2 from 0.14.0, 0.15.0 and 0.15.1**, recording
176
188
  role/provider/effort and an exact selection digest. The executor verifies the
177
189
  frozen common configuration before phase selection; continuation and both delivery
178
190
  validation paths require matching protected v2 evidence and private frozen config.
@@ -199,6 +211,15 @@ publishes an increased `package.json` version. Existing versions are skipped;
199
211
  registry errors fail the release instead of masquerading as a missing version.
200
212
  The workflow can also be started manually from `main`.
201
213
 
214
+ An accepted npm publication may still be processing. It is not yet a verified,
215
+ downloadable release: exact-version and `latest` metadata can appear at different
216
+ times, and an npm install can briefly report `ETARGET` even after both appear.
217
+ Verify an exact package download and compare its contents with the reviewed
218
+ artifact before reporting release availability. If the bounded updater reports
219
+ that the release is not yet downloadable, retain the prior runtime and retry the
220
+ update later. This does not establish whether a particular delay came from a
221
+ local cache or registry propagation.
222
+
202
223
  For a release, update both manifests with `npm version patch --no-git-tag-version`,
203
224
  review the change, and push through the project's normal review flow. A code
204
225
  push without a version bump is tested but does not overwrite a published package.
@@ -233,7 +254,7 @@ The 0.15.0 compatibility audit preserves unchanged v1/v2 policy construction,
233
254
  protected checks, candidate reconstruction, read-only review and acceptance guards.
234
255
  Historical 0.14.0 v2 validation compares its actual writer version rather than
235
256
  relabeling it as the installed version. Local registry selection is a new execution
236
- contract: every phase of a local/hybrid attempt requires **v3 from 0.15.0** and its
257
+ contract: every phase of a local/hybrid attempt requires **v3 from 0.15.0 or 0.15.1** and its
237
258
  exact protected frozen configuration. v1/v2 cannot attest local binding fields,
238
259
  providers or a common policy containing a selected local role. A selected endpoint,
239
260
  model, limit or compatibility change invalidates the current policy; explicit
@@ -241,3 +262,9 @@ rollback can restore it without editing historical evidence. Controlled regressi
241
262
  exercise legacy v2 readback and local v3 rejection/tamper cases. Installed-package
242
263
  Docker and independent Review remain delivery qualification, not inferred from this
243
264
  compatibility audit.
265
+
266
+ The 0.15.1 patch changes release downloads and preserves the 0.15.0 v1/v2/v3
267
+ writers, frozen configuration and policy bytes. The explicit allowlists include
268
+ both patch versions; supported v3 readback retains the original writer version
269
+ while still comparing every execution selection, phase and policy field. It does
270
+ not rewrite historical evidence or allow v1/v2 to attest local bindings.
@@ -21,16 +21,17 @@ import { expectedWebStories, webPolicyHash } from './web-verification.mjs';
21
21
  // selection digest. V1 can never attest an adopted role override.
22
22
  // 0.15.0 keeps unchanged v1/v2 policies and writers; local selections require
23
23
  // v3 for every phase. Legacy evidence cannot attest the new registry/launcher.
24
+ // 0.15.1 changes release downloads only; v1/v2/v3 writers and policy are unchanged.
24
25
  // Older writers still require exact patch/tree reconstruction before publication.
25
26
  // Deliberately independent of VERSION: a release bump is not
26
27
  // evidence compatibility. Re-audit this list for every trust-relevant writer,
27
28
  // isolation or validation change; remove versions whose guarantees no longer
28
29
  // satisfy current policy. See docs/npm.md. This predicate alone grants no trust.
29
- const SUPPORTED_EXECUTION_RUNTIMES_V1 = new Set(['0.8.0', '0.9.0', '0.9.1', '0.10.0', '0.11.0', '0.11.1', '0.11.2', '0.12.0', '0.13.0', '0.13.1', '0.14.0', '0.15.0']);
30
+ const SUPPORTED_EXECUTION_RUNTIMES_V1 = new Set(['0.8.0', '0.9.0', '0.9.1', '0.10.0', '0.11.0', '0.11.1', '0.11.2', '0.12.0', '0.13.0', '0.13.1', '0.14.0', '0.15.0', '0.15.1']);
30
31
 
31
32
  export function isSupportedExecutionProfile(profile) {
32
- if ([2, 3].includes(profile?.version)) return (profile.version === 3 ? profile.runtimeVersion === '0.15.0' && Object.hasOwn(profile, 'localBinding')
33
- : ['0.14.0', '0.15.0'].includes(profile.runtimeVersion) && !Object.hasOwn(profile, 'localBinding') && profile.inferenceProvider !== 'factory-local')
33
+ if ([2, 3].includes(profile?.version)) return (profile.version === 3 ? ['0.15.0', '0.15.1'].includes(profile.runtimeVersion) && Object.hasOwn(profile, 'localBinding')
34
+ : ['0.14.0', '0.15.0', '0.15.1'].includes(profile.runtimeVersion) && !Object.hasOwn(profile, 'localBinding') && profile.inferenceProvider !== 'factory-local')
34
35
  && profile.role === (Object.keys(ROLE_PHASES).find(role => ROLE_PHASES[role] === profile.phase) || null)
35
36
  && (profile.reasoningEffort === null || ['low', 'medium', 'high'].includes(profile.reasoningEffort))
36
37
  && /^[a-f0-9]{64}$/.test(profile.selectionHash || '');
@@ -132,7 +133,7 @@ export function assertFrozenExecution(config, execution, phase) {
132
133
  delete expected.hostName;
133
134
  // Audited legacy writers retain their real version. Never let v1/v2 attest
134
135
  // a local binding, including a deterministic phase of a hybrid attempt.
135
- if (expected.version < 3 && isSupportedExecutionProfile(execution) && execution.version === expected.version)
136
+ if (isSupportedExecutionProfile(execution) && execution.version === expected.version)
136
137
  expected.runtimeVersion = execution.runtimeVersion;
137
138
  const actual = { ...execution }; delete actual.hostName; delete actual.workerName;
138
139
  if (!isDeepStrictEqual(expected, actual)) throw new Error('Admitted execution profile does not match this attempt');
@@ -65,17 +65,33 @@ function installedEntry(dataHome, version) {
65
65
  return pkg.name === PACKAGE && pkg.version === version && existsSync(entry) ? entry : null;
66
66
  } catch { return null; }
67
67
  }
68
- export function installRelease(version, dataHome = DATA_HOME, runner = spawnSync) {
68
+ export function installRelease(version, dataHome = DATA_HOME, runner = spawnSync, {
69
+ now = () => performance.now(),
70
+ sleep = ms => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms),
71
+ } = {}) {
69
72
  if (!/^\d+\.\d+\.\d+$/.test(version)) throw new Error('Invalid release version');
70
73
  const existing = installedEntry(dataHome, version);
71
74
  if (existing) return existing;
72
75
  const releases = join(dataHome, 'releases'); privateDir(releases);
73
76
  const staging = mkdtempSync(join(releases, '.download-'));
74
77
  try {
75
- const result = runner('npm', ['install', '--prefix', staging, '--no-save', '--package-lock=false',
76
- '--ignore-scripts', '--omit=dev', '--no-audit', '--no-fund', '--registry', REGISTRY, `${PACKAGE}@${version}`],
77
- { encoding: 'utf8', timeout: 120000, maxBuffer: 1024 * 1024 });
78
- if (result.error || result.status !== 0) throw new Error(`npm update download failed: ${result.error?.message || result.stderr || result.status}`);
78
+ // Metadata refresh and visibility retries share the original download budget.
79
+ // Only ETARGET is retried; offline/auth/identity failures keep their own errors.
80
+ const deadline = now() + 120000;
81
+ for (let attempt = 1; attempt <= 3; attempt++) {
82
+ const remaining = Math.floor(deadline - now());
83
+ if (remaining <= 0) throw new Error(`npm update download failed for ${PACKAGE}@${version}: 120-second download budget exhausted`);
84
+ const result = runner('npm', ['install', '--prefix', staging, '--no-save', '--package-lock=false',
85
+ '--ignore-scripts', '--omit=dev', '--no-audit', '--no-fund', '--prefer-online', '--prefer-offline=false', '--registry', REGISTRY, `${PACKAGE}@${version}`],
86
+ { encoding: 'utf8', timeout: remaining, killSignal: 'SIGKILL', maxBuffer: 1024 * 1024 });
87
+ if (!result.error && result.status === 0) break;
88
+ const detail = result.error?.message || result.stderr || result.status;
89
+ const visibilityDelay = !result.error && /^npm (?:ERR!|error) code ETARGET\s*$/m.test(result.stderr || '');
90
+ if (!visibilityDelay) throw new Error(`npm update download failed for ${PACKAGE}@${version}: ${detail}`);
91
+ if (attempt === 3 || deadline - now() <= 1000)
92
+ throw new Error(`npm release ${PACKAGE}@${version} is not yet downloadable; publication may still be processing. Retry the update later. ${detail}`);
93
+ sleep(1000);
94
+ }
79
95
  const pkg = read(join(staging, 'node_modules', PACKAGE, 'package.json'));
80
96
  if (pkg.name !== PACKAGE || pkg.version !== version || !existsSync(join(staging, 'node_modules', PACKAGE, 'bin/software-defence-factory.mjs')))
81
97
  throw new Error('Downloaded package identity does not match the requested release');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "software-defence-factory",
3
- "version": "0.15.0",
3
+ "version": "0.15.1",
4
4
  "description": "Factory: scoped software delivery and defence investigations with isolated jobs, evidence and review",
5
5
  "type": "module",
6
6
  "license": "MIT",