arkgate 4.4.0 → 4.5.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/CHANGELOG.md CHANGED
@@ -5,13 +5,58 @@ in the immutable pre-2.0 archive linked below.
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ## 4.5.0 — 2026-08-10
9
+
10
+ **Minor** over 4.4.0. Session control-plane honesty (status/MCP improvement compass with explicit
11
+ modes), domain module fitness (split oversized pure modules), selective pure verification ratchet,
12
+ self-service upgrade activation honesty, and a short session recipe in product docs. **No required
13
+ config migration.** Codex remains advisory at write time. Skills and AGENTS projection never
14
+ enforce. **Status: prepared in tree — not published on npm until OIDC publish + `npm view` succeed**
15
+ (see `docs/releases/4.5.0.md`).
16
+
17
+ ### Added
18
+
19
+ - **Status / MCP improvement compass honesty:** `ark status --json` and MCP `ark_status` always
20
+ project `improvementCompass` with explicit mode `full` \| `subset` \| `unavailable`, residual
21
+ lens ids, and `notAScore: true`. Incomplete or missing session facts never invent green residual.
22
+ When mode is `full`, residual ids are a subset of doctor residual for the same facts (report
23
+ snapshots store the thin slice). Residual never flips `valid` / strict-merge / `goal.met`.
24
+ Root helpers: `projectStatusImprovementCompass`, `STATUS_COMPASS_MODES`.
25
+ - **Session recipe (product docs):** identity bind → `ark status` → act on residual / `findingRef`;
26
+ run doctor when status compass mode is not `full`. Documented in use / agent-guide / develop —
27
+ no new skill names.
28
+ - **Managed upgrade self-service honesty:** `ark upgrade [--json]` surfaces `selfService` with
29
+ per-host write-path activation labels (`hard` \| `advisory` \| `unavailable`) and customized
30
+ content-identity preserve (`customizedPaths` / `customizedContentPreserved`). Soft hosts never
31
+ hard; upgrade never invents `hardWriteActive` from disk alone. Always `notAScore: true`. Human
32
+ output: “Self-service honesty” lines.
33
+
34
+ ### Changed
35
+
36
+ - **Domain module fitness:** pure Domain `improvementCompass` monolith split into
37
+ `improvementCompassTypes` + `improvementCompassMap` + facade `improvementCompass` (public
38
+ re-exports unchanged). All three tracked in `check:module-budgets`; over-ceiling domain modules
39
+ require a behavior-preserving split — raising max alone is not done. CLI pure mirrors:
40
+ `improvement-compass-types.mjs`, `improvement-compass-map.mjs`, `improvement-compass.mjs`.
41
+ - **Critical pure verification ratchet:** selective verification islands on
42
+ `layerMatch` peerIsolation fail-closed (missing paths/slices),
43
+ `policyDeltaAcknowledgementMatches` (hash + finding-id set), and
44
+ `canPromoteInvariant` promote honesty. Property suites under `tests/property/` plus unit fixtures;
45
+ Stryker mutate ranges + critical groups `peer-isolation-fail-closed`,
46
+ `policy-delta-ack-match`, `invariant-promote-honesty`. Mutation remains cost-gated to full-matrix
47
+ CI / publish (`npm run test:mutation` via `test:confidence`) — **not** a whole-repo mutation claim.
48
+ Strict semantics unchanged.
49
+
50
+ **Freezes held:** no principle scores/ranks; no new skill names; no new ArkRules sensors; no LLM
51
+ verdict; projection never enforces; no false hard-write for soft hosts.
52
+
8
53
  ## 4.4.0 — 2026-08-09
9
54
 
10
55
  **Minor** over 4.3.0. Improvement compass (architecture lenses as a `notAScore` projection of
11
56
  existing sensors), doctor/HTML residual coaching, deeper skills + compact router for vibe-coder
12
57
  and full-AI workflows, and product-only public docs. **No required config migration.** Codex remains
13
- advisory at write time. Skills and AGENTS projection never enforce. **Status: prepared in tree —
14
- not published on npm until OIDC publish + `npm view` succeed** (see `docs/releases/4.4.0.md`).
58
+ advisory at write time. Skills and AGENTS projection never enforce. **Status: published**
59
+ (`arkgate@4.4.0` on npm `latest`; see `docs/releases/4.4.0.md`).
15
60
 
16
61
  ### Added
17
62
 
package/README.md CHANGED
@@ -16,8 +16,8 @@ and makes sure a “green” check means something real.
16
16
 
17
17
  </div>
18
18
 
19
- > **ArkGate 4.3.0** is on npm `latest`. Tree is preparing **4.4.0** (improvement compass) — see [4.4.0 notes](docs/releases/4.4.0.md) (Status: prepared until publish).
20
- > [4.4.0 notes](docs/releases/4.4.0.md) · [4.3.0](docs/releases/4.3.0.md) · [4.2.1](docs/releases/4.2.1.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
19
+ > **ArkGate 4.4.0** is on npm `latest`. Tree is preparing **4.5.0** (session honesty + upgrade self-service) — see [4.5.0 notes](docs/releases/4.5.0.md) (Status: prepared until publish).
20
+ > [4.5.0 notes](docs/releases/4.5.0.md) · [4.4.0](docs/releases/4.4.0.md) · [4.3.0](docs/releases/4.3.0.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
21
21
 
22
22
  ---
23
23
 
@@ -208,8 +208,9 @@ for real systems. Details: [docs/production-hardening.md](docs/production-harden
208
208
  | Config · package surface · TS | [configuration](docs/configuration.md) · [package-surface](docs/package-surface.md) · [typescript-support](docs/typescript-support.md) |
209
209
  | Brownfield | [docs/brownfield-adoption.md](docs/brownfield-adoption.md) |
210
210
  | Security | [SECURITY.md](SECURITY.md) |
211
- | Current release (4.3.0 on npm `latest`) | [docs/releases/4.3.0.md](docs/releases/4.3.0.md) · [CHANGELOG](CHANGELOG.md) |
212
- | Next prepare (4.4.0) | [docs/releases/4.4.0.md](docs/releases/4.4.0.md) (prepared — not published until npm verify) |
211
+ | Current release (4.4.0 on npm `latest`) | [docs/releases/4.4.0.md](docs/releases/4.4.0.md) · [CHANGELOG](CHANGELOG.md) |
212
+ | Next prepare (4.5.0) | [docs/releases/4.5.0.md](docs/releases/4.5.0.md) (prepared — not published until npm verify) |
213
+ | Prior (4.3.0) | [docs/releases/4.3.0.md](docs/releases/4.3.0.md) |
213
214
  | Prior (4.2.1) | [docs/releases/4.2.1.md](docs/releases/4.2.1.md) |
214
215
  | Previous (4.2.0) | [docs/releases/4.2.0.md](docs/releases/4.2.0.md) |
215
216
  | Previous (4.1.1) | [docs/releases/4.1.1.md](docs/releases/4.1.1.md) |
@@ -1557,19 +1557,6 @@ async function main() {
1557
1557
  enforcement: enforcementForReport,
1558
1558
  config,
1559
1559
  });
1560
- const currentSnapshot = buildReportSnapshot({
1561
- root,
1562
- config,
1563
- coverage,
1564
- violations: activeViolations,
1565
- ok,
1566
- suppressed: suppressed.length,
1567
- version: arkPackageVersion(),
1568
- fileCountByLayer,
1569
- enforcement: enforcementForReport,
1570
- score: fitness.score,
1571
- mode: fitness.mode,
1572
- });
1573
1560
  // Origin is read before archive so the HTML can show "just created" vs deltas.
1574
1561
  const existingOrigin = args.resetOrigin
1575
1562
  ? null
@@ -1599,6 +1586,40 @@ async function main() {
1599
1586
  baselineStale: analysisComplete ? reportBaselineStale : null,
1600
1587
  }
1601
1588
  );
1589
+ // DF02 — thin status compass on report snapshot so `ark status` residual ⊆ doctor.
1590
+ const reportCompass =
1591
+ designDepth?.improvementCompass && designDepth.improvementCompass.notAScore === true
1592
+ ? {
1593
+ schemaVersion: '1.0',
1594
+ notAScore: true,
1595
+ mode: analysisComplete ? 'full' : 'subset',
1596
+ topResidual: Array.isArray(designDepth.improvementCompass.topResidual)
1597
+ ? designDepth.improvementCompass.topResidual
1598
+ : [],
1599
+ factsSource: 'report-snapshot',
1600
+ ...(analysisComplete
1601
+ ? {}
1602
+ : {
1603
+ reasonCode: 'FACTS_PARTIAL',
1604
+ reason:
1605
+ 'Report analysis was incomplete — status compass is a subset; re-run doctor/report for full residual.',
1606
+ }),
1607
+ }
1608
+ : null;
1609
+ const currentSnapshot = buildReportSnapshot({
1610
+ root,
1611
+ config,
1612
+ coverage,
1613
+ violations: activeViolations,
1614
+ ok,
1615
+ suppressed: suppressed.length,
1616
+ version: arkPackageVersion(),
1617
+ fileCountByLayer,
1618
+ enforcement: enforcementForReport,
1619
+ score: fitness.score,
1620
+ mode: fitness.mode,
1621
+ improvementCompass: reportCompass,
1622
+ });
1602
1623
  const reportPayload = {
1603
1624
  root,
1604
1625
  config,
@@ -258,6 +258,21 @@ function resolveSliceFolders(rule, layerName, layers) {
258
258
  const layer = (layers ?? []).find((l) => l.name === layerName);
259
259
  return inferSliceFoldersFromPatterns(layer?.patterns);
260
260
  }
261
+ /**
262
+ * PeerIsolation deny decision given resolved path/slice evidence (DF04 pure core).
263
+ *
264
+ * Fail-closed: missing path, no classifiable folders, or unclassifiable either
265
+ * side → deny. Same-slice → allow (return false). Cross-slice → deny.
266
+ */
267
+ export function peerIsolationMustDeny(input) {
268
+ if (!input.fromPath || !input.toPath)
269
+ return true;
270
+ if (input.folderCount <= 0)
271
+ return true;
272
+ if (!input.fromSlice || !input.toSlice)
273
+ return true;
274
+ return input.fromSlice !== input.toSlice;
275
+ }
261
276
  /**
262
277
  * Find the first denying rule for a layer edge.
263
278
  *
@@ -279,20 +294,18 @@ export function findDeniedEdgeRule(rules, from, to, options) {
279
294
  if (rule.peerIsolation) {
280
295
  const fromPath = options?.fromPath;
281
296
  const toPath = options?.toPath;
282
- // Isolation is active: without both paths we cannot prove same-slice.
283
- if (!fromPath || !toPath)
284
- return rule;
285
297
  const folders = resolveSliceFolders(rule, from, options?.layers);
286
- // Configured isolation without classifiable folders cannot allow.
287
- if (folders.length === 0)
288
- return rule;
289
- const fromSlice = sliceIdForPath(fromPath, folders);
290
- const toSlice = sliceIdForPath(toPath, folders);
291
- // Unclassifiable either side: cannot prove same-slice → deny.
292
- if (!fromSlice || !toSlice)
293
- return rule;
294
- if (fromSlice !== toSlice)
298
+ const fromSlice = fromPath && toPath ? sliceIdForPath(fromPath, folders) : undefined;
299
+ const toSlice = fromPath && toPath ? sliceIdForPath(toPath, folders) : undefined;
300
+ if (peerIsolationMustDeny({
301
+ fromPath,
302
+ toPath,
303
+ folderCount: folders.length,
304
+ fromSlice,
305
+ toSlice,
306
+ })) {
295
307
  return rule;
308
+ }
296
309
  continue; // same slice: this peerIsolation rule does not deny
297
310
  }
298
311
  // Classic deny — same-layer always allowed without peerIsolation