arkgate 4.6.7 → 4.7.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 (56) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/README.md +20 -9
  3. package/SECURITY.md +1 -1
  4. package/bin/ark-check-runtime.mjs +13 -1
  5. package/bin/ark-mcp-runtime.mjs +65 -3
  6. package/bin/lib/adapter-contract.mjs +17 -36
  7. package/bin/lib/analysis-engine.mjs +6 -6
  8. package/bin/lib/ark-run-doctor.mjs +144 -0
  9. package/bin/lib/ark-run-facts.mjs +472 -0
  10. package/bin/lib/ark-run-report.mjs +57 -0
  11. package/bin/lib/ark-run-sensors.mjs +309 -0
  12. package/bin/lib/config-contract.mjs +86 -11
  13. package/bin/lib/diagnostic-catalog.mjs +8 -0
  14. package/bin/lib/doctor-advisories.mjs +45 -8
  15. package/bin/lib/doctor-human.mjs +10 -0
  16. package/bin/lib/doctor-plan.mjs +20 -16
  17. package/bin/lib/extra-merge-teeth.mjs +187 -0
  18. package/bin/lib/html-report-advisories.mjs +2 -0
  19. package/bin/lib/html-report-depth.mjs +22 -2
  20. package/bin/lib/html-report.mjs +16 -0
  21. package/bin/lib/remediation.mjs +132 -0
  22. package/bin/lib/resolved-candidate-facts.mjs +67 -2
  23. package/bin/lib/rules-under-contract.mjs +37 -89
  24. package/bin/lib/snippet-analysis.mjs +43 -2
  25. package/bin/lib/status-command.mjs +28 -0
  26. package/bin/lib/status-manifest.mjs +23 -0
  27. package/dist/{configTypes-l6XiwiC1.d.ts → configTypes-CgJimx9o.d.ts} +17 -3
  28. package/dist/eslint/index.cjs +6 -2
  29. package/dist/eslint/index.d.ts +70 -2
  30. package/dist/eslint/index.js +6 -2
  31. package/dist/index.cjs +35 -35
  32. package/dist/index.d.ts +787 -272
  33. package/dist/index.js +35 -35
  34. package/docs/README.md +2 -1
  35. package/docs/agent-guide.md +21 -15
  36. package/docs/ai-gates.md +13 -0
  37. package/docs/configuration.md +24 -11
  38. package/docs/develop.md +12 -3
  39. package/docs/diagnostics.md +75 -0
  40. package/docs/enthusiast/README.md +4 -3
  41. package/docs/package-surface.md +16 -13
  42. package/docs/product-voice.md +6 -3
  43. package/docs/threat-model.md +1 -1
  44. package/docs/use.md +5 -4
  45. package/package.json +1 -1
  46. package/schemas/ark.config.schema.json +41 -2
  47. package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
  48. package/schemas/ark.status-manifest.schema.json +47 -0
  49. package/server.json +2 -2
  50. package/templates/agent-skills/README.md +1 -1
  51. package/templates/agent-skills/ark-adopt/SKILL.md +23 -2
  52. package/templates/agent-skills/ark-place/SKILL.md +26 -2
  53. package/templates/agent-skills/ark-runtime/SKILL.md +66 -24
  54. package/templates/skills/ark-adopt.md +23 -2
  55. package/templates/skills/ark-place.md +26 -2
  56. package/templates/skills/ark-runtime.md +66 -24
package/CHANGELOG.md CHANGED
@@ -5,6 +5,115 @@ in the immutable pre-2.0 archive linked below.
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ## 4.7.0 — 2026-08-25
9
+
10
+ **Minor** over **4.6.7**. Ships **ArkRun**: an opt-in extra on schema `1.2` for kernel
11
+ usage and complete declarations, plus companion `@arkgate/runtime` DX. Absence is
12
+ silent (Layers / ArkRules verdicts unchanged). In-memory stores remain
13
+ reference-only. **No required config migration.** Does not close Z09 / K01.
14
+
15
+ **Status: prepared** (see `docs/releases/4.7.0.md`). npm `latest` remains **4.6.7**
16
+ until publish.
17
+
18
+ ### Added
19
+
20
+ - **`arkRun` extra on `ark.config.json` schema `1.2` (RN02):** optional inline
21
+ `{ mode, compositionRoots, managedLayers, requireDeclarations }`. `1.1` and
22
+ earlier configs migrate in memory; absence is silent (Layers / ArkRules
23
+ verdicts unchanged). Unknown keys, unknown `managedLayers` names, and empty
24
+ `compositionRoots` in `enforced` mode fail closed (`ARKRUN_MISSING_ROOT`).
25
+ Advisory → enforced is a strengthening policy delta; demotion or deletion is
26
+ weakening and needs the existing hash-bound ack. ESLint envelope is RN06
27
+ (landed below); CI extra teeth landed in RN07. Does not close Z09 / K01.
28
+
29
+ - **ArkRun resolver facts on resolved-candidate-facts schema `1.2` (RN03):**
30
+ additive optional `arkRunKernelCalls`, `arkRunManagedNews`, and
31
+ `arkRunCompositionRootHits`. `1.0`/`1.1` payloads stay loadable (empty
32
+ arrays). Syntax evidence only — sensors consume these in RN04. Absence of
33
+ `arkRun` still leaves Layers / ArkRules verdicts unchanged. Does not close
34
+ Z09 / K01.
35
+
36
+ - **ArkRun tier-1 sensors (RN04):** when `arkRun` is present, closed sensors
37
+ emit `ARKRUN_MISSING_ROOT`, `ARKRUN_KERNEL_IN_DOMAIN`, `ARKRUN_DIRECT_NEW`,
38
+ `ARKRUN_UNDECLARED_EMIT`, `ARKRUN_UNDECLARED_HANDLE`, `ARKRUN_UNDECLARED_DEPEND`,
39
+ and `ARKRUN_TRANSPORT_BYPASS`. Advisory findings never flip `valid`; enforced
40
+ blocks. Absence of the extra is still silent on Layers / ArkRules verdicts.
41
+ Optional `arkRunDeclarations` facts stay additive on schema `1.2`. Dual-depth
42
+ catalog nextAction is RN05 (landed below). Does not close Z09 / K01.
43
+
44
+ - **ArkRun diagnostic catalog dual-depth (RN05):** closed `ARKRUN_*` catalog
45
+ entries have dual-depth remediation: casual `enthusiastHint` plus engineer
46
+ `nextAction` (target interpolates the call-site literal or specifier). Adding
47
+ an existing declaration-list string is `mechanical-safe` (`arkrun-declaration-list`)
48
+ only when that literal is already present; other ArkRun findings stay
49
+ `judgment`. Sensors, adapter fallback, and CLI remediation share
50
+ `deterministicNextAction`. Does not close Z09 / K01.
51
+
52
+ - **ArkRun ESLint envelope (RN06):** `arkgate/eslint` recommended config adds
53
+ `ark/no-arkrun-kernel-in-domain`, `ark/no-arkrun-direct-new`, and
54
+ `ark/no-arkrun-transport-bypass`. Same `ARKRUN_*` sensors as ark-check for
55
+ the import / `new` envelope; silent when `arkRun` is absent. Missing-root and
56
+ undeclared-* stay CLI/MCP/preflight. Does not close Z09 / K01.
57
+
58
+ - **ArkRun extra-teeth parity (RN07):** CLI `--strict-merge`, MCP `ark_check` /
59
+ snippet write, PreToolUse hook, atomic preflight, and CI share one ArkRun
60
+ verdict. Enforced extra teeth arm only when the layer plane is classified
61
+ (same ≥50% governed / ≥1 populated-layer floor as ArkRules); advisory and
62
+ absence stay silent on `valid`. Doctor/status `arkRun` section landed in
63
+ RN08 below. Does not close Z09 / K01.
64
+
65
+ - **ArkRun doctor / status / report (RN08):** `ark-check --doctor`, HTML
66
+ `--report`, and `ark status` / MCP `ark_status` expose an `arkRun` section
67
+ that is always `notAScore`. Residual is a finding-id count, never a score or
68
+ LLM verdict. `mergePlanes.arkRun` states whether the extra can fail merge;
69
+ advisory and absence never arm extra teeth. Report parity requires
70
+ `data-advisory="arkRun"`. Does not close Z09 / K01.
71
+
72
+ - **ArkRun companion branding (RN09):** `@arkgate/runtime` README and public
73
+ docs brand the kernel **ArkRun**. `createStrictArkKernel` stays the factory
74
+ (per-instance; no process-wide singleton). Kernel implementation stays out of
75
+ the `arkgate` tarball. Branding is not a production-durability claim. Does not
76
+ close Z09 / K01.
77
+
78
+ - **ArkRun interaction declarations (RN10):** `@arkgate/runtime` `register()`
79
+ accepts `uses` / `reactsTo` / `raises` / `sends` plus optional tooling-only
80
+ `extendedInfo`. `getDependencyInformationPackage()` returns a JSON-serializable
81
+ snapshot of ids, lifetime, and declarations — never factories, live instances,
82
+ or input DTOs. Companion registrations may omit declarations for local
83
+ experiments; enforced `arkRun` on the gate still requires them. Does not close
84
+ Z09 / K01.
85
+
86
+ - **ArkRun transport ports (RN11):** `@arkgate/runtime` `send()` is one call site
87
+ for `local` / `localBlocking` / `broker`. `ephemeral` defaults true (await local
88
+ recording or adapter accept — not a durability claim). Missing broker adapter
89
+ falls back to in-process local delivery, not cloud portability. No cloud SDKs
90
+ ship in the package. Does not close Z09 / K01.
91
+
92
+ - **ArkRun dev inspector (RN12):** `@arkgate/runtime` `startInspector()` /
93
+ `startArkRunInspector()` is opt-in. Default bind is `127.0.0.1`; `NODE_ENV=production`
94
+ vetoes start; public hosts (`0.0.0.0`, `::`) are rejected. HTTP is lazy-loaded.
95
+ `GET /snapshot` and `GET /events` (SSE) serve the information package plus
96
+ transport facts (no factories, no shipped cloud SDKs). Does not close Z09 / K01.
97
+
98
+ - **ArkRun graph slices (RN13):** `@arkgate/runtime` `requestGraph()` slices the
99
+ information package into `process` (raises / reactsTo / sends) or `technical`
100
+ (`uses`) graphs. Optional `nodeIds`, `degreesOfSeparation`, and include/exclude
101
+ query keep a neighborhood. `formatArkRunGraphMermaid()` / `graph.mermaid` is a
102
+ helper string, never a score. Inspector `GET /graph` serves the same slice.
103
+ Does not close Z09 / K01.
104
+
105
+ - **ArkRun skip corpus (RN14):** `tests/fixtures/arkrun-skip-corpus/` is the
106
+ executable proof: Application `new`, same-layer peer import, and homemade
107
+ `EventEmitter` stay green when `arkRun` is absent (Layers / ArkRules match
108
+ schema `1.1`) and fail write path, CLI, MCP, and `--strict-merge` when the
109
+ extra is enforced. Does not close Z09 / K01.
110
+
111
+ - **ArkRun skill-body deepen (RN15):** `/ark-runtime`, `/ark-place`, and
112
+ `/ark-adopt` teach the extra vs companion (advisory adopt, kernel-only
113
+ scaffold, composition-root wiring). Frozen **13** names — no `/ark-run`.
114
+ Skills never enforce; doctor `arkRun` stays `notAScore`. Agent Skills layout
115
+ stays 1:1 with `templates/skills`. Does not close Z09 / K01.
116
+
8
117
  ## 4.6.7 — 2026-08-24
9
118
 
10
119
  **Patch** over **4.6.6**. Production-hardening: CODEOWNERS, eval/pack honesty, CLI extracts,
package/README.md CHANGED
@@ -16,11 +16,11 @@ and makes sure a “green” check means something real.
16
16
 
17
17
  </div>
18
18
 
19
- > **ArkGate 4.6.7** is current on npm `latest`.
19
+ > **ArkGate 4.7.0** is prepared on this tree. **4.6.7** remains npm `latest` until publish.
20
20
  > A tree is **adopted** only with a required GitHub status running `arkgate-check --strict-merge`,
21
21
  > or `.ark/adoption-stance.json` `stance: "advisory-only"`. Doctor is compact (`--doctor --all`
22
- > for Details). [4.6.7 notes](docs/releases/4.6.7.md) · [4.6.6](docs/releases/4.6.6.md) ·
23
- > [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
22
+ > for Details). [4.7.0 notes](docs/releases/4.7.0.md) · [4.6.7](docs/releases/4.6.7.md) ·
23
+ > [4.6.6](docs/releases/4.6.6.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
24
24
 
25
25
  ---
26
26
 
@@ -78,11 +78,12 @@ A machine-readable architecture file (`ark.config.json`) plus enforcement:
78
78
  |-------|----------------|--------|
79
79
  | **Layers** (always) | Who may import whom — imports, placement, purity, isolation | `ark.config.json` layers + rules |
80
80
  | **ArkRules** (opt-in; structure rules inside a layer) | Habits *inside* a layer — structure sensors + domain invariants as data | `arkRules` → `arkrules/<Layer>.json` |
81
+ | **ArkRun** (opt-in extra) | Kernel usage + complete declarations | `arkRun` on schema `1.2` |
81
82
 
82
- Absence of ArkRules changes no inter-layer verdict. Label residual **`[Layer]`** vs **`[ArkRules]`**.
83
- Details: [configuration](docs/configuration.md#arkrules-intra-layer-opt-in) · [use path](docs/use.md).
83
+ Absence of ArkRules or ArkRun changes no inter-layer verdict. Label residual **`[Layer]`** vs **`[ArkRules]`**.
84
+ Details: [configuration](docs/configuration.md) · [use path](docs/use.md).
84
85
 
85
- **Not** a web framework, ORM, or job runner. Optional experimental runtime is separate and not required for the gate.
86
+ **Not** a web framework, ORM, or job runner. Optional **ArkRun** extra and companion kernel (`@arkgate/runtime`) are separate and not required for the gate. In-memory stores are not production durability.
86
87
 
87
88
  **Name note:** npm package `arkgate` — not affiliated with the separate Archgate CLI project.
88
89
 
@@ -192,9 +193,18 @@ More: [docs/develop.md](docs/develop.md) · skills install: [docs/agent-guide.md
192
193
 
193
194
  ---
194
195
 
195
- ## Optional experimental runtime
196
+ ## Optional ArkRun kernel
196
197
 
197
- Gates need **no** app runtime. The experimental `@arkgate/runtime` companion is separate and is not a production-readiness claim.
198
+ Gates need **no** app runtime. The experimental **ArkRun** companion (`@arkgate/runtime`) is separate
199
+ and is not a production-readiness claim. `createStrictArkKernel` is the factory: each call creates
200
+ an isolated instance (no process-wide singleton). Managed components declare `uses` / `reactsTo` /
201
+ `raises` / `sends`; `getDependencyInformationPackage()` is a JSON snapshot and never leaks factories.
202
+ `requestGraph()` slices that snapshot into process or technical graphs (`nodeIds`,
203
+ `degreesOfSeparation`, include/exclude query) with a Mermaid helper. `send()` is local /
204
+ localBlocking / broker (broker falls back to in-process local; `ephemeral`
205
+ defaults true; no cloud SDKs in the package). Opt-in `startInspector()` binds `127.0.0.1`,
206
+ refuses `NODE_ENV=production`, and lazy-loads HTTP for JSON snapshots, SSE, and `/graph`. The kernel is
207
+ not bundled in the `arkgate` tarball.
198
208
 
199
209
  ### Durability stance
200
210
 
@@ -217,7 +227,8 @@ for real systems. Details: [docs/production-hardening.md](docs/production-harden
217
227
  | Config · package surface · TS | [configuration](docs/configuration.md) · [package-surface](docs/package-surface.md) · [typescript-support](docs/typescript-support.md) |
218
228
  | Brownfield | [docs/brownfield-adoption.md](docs/brownfield-adoption.md) |
219
229
  | Security | [SECURITY.md](SECURITY.md) |
220
- | Current published (4.6.7 on npm `latest`) | [docs/releases/4.6.7.md](docs/releases/4.6.7.md) · [CHANGELOG](CHANGELOG.md) |
230
+ | Current tree (4.7.0 prepared) | [docs/releases/4.7.0.md](docs/releases/4.7.0.md) · [CHANGELOG](CHANGELOG.md) |
231
+ | Current published (4.6.7 on npm `latest`) | [docs/releases/4.6.7.md](docs/releases/4.6.7.md) |
221
232
  | Prior published (4.6.6) | [docs/releases/4.6.6.md](docs/releases/4.6.6.md) |
222
233
  | Prior published (4.6.5) | [docs/releases/4.6.5.md](docs/releases/4.6.5.md) |
223
234
  | Prior published (4.6.3) | [docs/releases/4.6.3.md](docs/releases/4.6.3.md) |
package/SECURITY.md CHANGED
@@ -18,7 +18,7 @@ Preferred path: use GitHub's private vulnerability reporting for this repository
18
18
  - reproduction steps
19
19
  - expected impact
20
20
  - whether the issue affects `arkgate-check` / `ark-check`, `arkgate-mcp` / `ark-mcp`,
21
- generated agent gates, the GitHub Action, or the optional runtime kernel
21
+ generated agent gates, the GitHub Action, or the optional ArkRun kernel
22
22
 
23
23
  If private vulnerability reporting is unavailable, open a minimal public issue asking for
24
24
  a private security contact without including exploit details.
@@ -1471,6 +1471,7 @@ async function main() {
1471
1471
  score: fitness.score,
1472
1472
  mode: fitness.mode,
1473
1473
  improvementCompass: reportCompass,
1474
+ arkRun: designDepth?.arkRun ?? null,
1474
1475
  }),
1475
1476
  leftoverDesignWork: designDepth?.designFitness?.designWeak === true,
1476
1477
  };
@@ -1493,7 +1494,17 @@ async function main() {
1493
1494
  adoption: adoptionForReport,
1494
1495
  designDepth,
1495
1496
  advisories: {
1496
- ...computeDoctorAdvisories(root, config, coverage, rules, files, ts, parseHealth),
1497
+ ...computeDoctorAdvisories(
1498
+ root,
1499
+ config,
1500
+ coverage,
1501
+ rules,
1502
+ files,
1503
+ ts,
1504
+ parseHealth,
1505
+ undefined,
1506
+ activeViolations
1507
+ ),
1497
1508
  // Doctor parity: always emit improvement compass when doctor would (reportParity).
1498
1509
  ...(designDepth?.improvementCompass
1499
1510
  ? { improvementCompass: designDepth.improvementCompass }
@@ -1502,6 +1513,7 @@ async function main() {
1502
1513
  ...(designDepth?.deepModuleCoach
1503
1514
  ? { deepModuleCoach: designDepth.deepModuleCoach }
1504
1515
  : {}),
1516
+ ...(designDepth?.arkRun ? { arkRun: designDepth.arkRun } : {}),
1505
1517
  },
1506
1518
  };
1507
1519
  const html = args.beginner
@@ -308,6 +308,42 @@ function hookEnforcement(root, host, operation, completePatch = false) {
308
308
  }).enforcementLadder;
309
309
  }
310
310
 
311
+ function extraMergeTeethClassification(root, config) {
312
+ const files = collectGovernedFiles(root, config);
313
+ const layers = config.layers ?? [];
314
+ let classified = 0;
315
+ const populated = new Set();
316
+ for (const abs of files) {
317
+ const layer = layerForFile(root, abs, layers);
318
+ if (layer) {
319
+ classified += 1;
320
+ populated.add(layer);
321
+ }
322
+ }
323
+ return {
324
+ governedPercent: files.length > 0 ? Math.round((classified / files.length) * 100) : 0,
325
+ populatedLayerCount: populated.size,
326
+ };
327
+ }
328
+
329
+ function arkRunSnippetContext({ root, config, filePath, layer, relFile, classification }) {
330
+ const extra = config?.arkRun;
331
+ if (!extra) return { layer, filePath };
332
+ const relative =
333
+ relFile ||
334
+ (typeof filePath === 'string'
335
+ ? path.relative(root, path.resolve(root, filePath)).split(path.sep).join('/')
336
+ : undefined);
337
+ return {
338
+ layer,
339
+ filePath,
340
+ relFile: relative,
341
+ arkRun: extra,
342
+ layers: config.layers ?? [],
343
+ classification: classification ?? extraMergeTeethClassification(root, config),
344
+ };
345
+ }
346
+
311
347
  function designDeltaViolations(delta) {
312
348
  return (delta?.changes ?? []).map((change) => ({
313
349
  ruleId: 'DESIGN_SMELL_REGRESSION',
@@ -565,7 +601,18 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
565
601
 
566
602
  const layer = inferLayer(filePath, config, args.root);
567
603
  const validateOnce = (src) =>
568
- validateSnippetAnalysis({ gate, ts, source: src, context: { layer, filePath } });
604
+ validateSnippetAnalysis({
605
+ gate,
606
+ ts,
607
+ source: src,
608
+ context: arkRunSnippetContext({
609
+ root: args.root,
610
+ config,
611
+ filePath,
612
+ layer,
613
+ relFile: normalizedRel,
614
+ }),
615
+ });
569
616
  // W1: one validation pass (+ optional autoPatch). Original write still blocked when
570
617
  // invalid; hosts must apply autoPatch explicitly (never silent write).
571
618
  const result = ts
@@ -2136,7 +2183,17 @@ export async function runArkMcp({ hookInput } = {}) {
2136
2183
  const filePath = params.arguments.filePath;
2137
2184
  const layer = params.arguments.layer ?? inferLayer(filePath, config, args.root);
2138
2185
  const validateOnce = (src) =>
2139
- validateSnippetAnalysis({ gate, ts, source: src, context: { layer, filePath } });
2186
+ validateSnippetAnalysis({
2187
+ gate,
2188
+ ts,
2189
+ source: src,
2190
+ context: arkRunSnippetContext({
2191
+ root: args.root,
2192
+ config,
2193
+ filePath,
2194
+ layer,
2195
+ }),
2196
+ });
2140
2197
  // W1: attempt mechanical-safe single-file autoPatch (import type), re-validate or discard.
2141
2198
  const result = validateWithAutoPatch({
2142
2199
  source,
@@ -2429,7 +2486,12 @@ export async function runArkMcp({ hookInput } = {}) {
2429
2486
  gate,
2430
2487
  ts,
2431
2488
  source: src,
2432
- context: { layer, filePath: placement.filePath },
2489
+ context: arkRunSnippetContext({
2490
+ root: args.root,
2491
+ config,
2492
+ filePath: placement.filePath,
2493
+ layer,
2494
+ }),
2433
2495
  });
2434
2496
  const result = composePrepareWrite({
2435
2497
  source,
@@ -8,7 +8,7 @@
8
8
  * Pure CLI helper (bin/lib/adapter-contract.mjs). Zero Node I/O.
9
9
  */
10
10
 
11
- import { layerImportNextAction } from './remediation.mjs';
11
+ import { deterministicNextAction } from './remediation.mjs';
12
12
  /** Versioned public result contract shared by every ArkGate enforcement adapter. */
13
13
  /**
14
14
  * 1.5 adds stable finding refs on every factory-emitted diagnostic (ACS06):
@@ -77,41 +77,22 @@ export function adapterDocsCodePath(ruleId) {
77
77
  return `${ADAPTER_DIAGNOSTIC_DOCS_RELATIVE_PATH}#${ruleId}`;
78
78
  }
79
79
  function nextActionForDiagnostic(ruleId, evidence, violation) {
80
- if (ruleId === 'LAYER_IMPORT_VIOLATION') {
81
- return layerImportNextAction({
82
- ruleId,
83
- typeOnly: evidence.typeOnly === true,
84
- targetTypeOnlyExports: violation.targetTypeOnlyExports === true,
85
- namedBindingsTypeOnly: violation.namedBindingsTypeOnly === true,
86
- peerIsolation: violation.peerIsolation === true,
87
- portProofEligible: violation.portProofEligible === true,
88
- fromLayer: text(evidence.fromLayer) ?? undefined,
89
- toLayer: text(evidence.toLayer) ?? undefined,
90
- target: text(evidence.target) ?? text(violation.target) ?? undefined,
91
- });
92
- }
93
- if (ruleId === 'FORBIDDEN_GLOBAL') {
94
- return `Inject ${evidence.target ?? 'the capability'} through a port, test at the public interface, then preflight again.`;
95
- }
96
- if (ruleId === 'CAPABILITY_VIOLATION') {
97
- return `Define a ${text(violation.capability) ?? 'capability'} port in ${evidence.fromLayer ?? 'the walled layer'}, bind the implementation outside it, test at the public interface, then preflight again.`;
98
- }
99
- if (ruleId === 'CIRCULAR_DEPENDENCY') {
100
- return 'Extract the shared dependency into a third module, test at the public interface, then preflight again.';
101
- }
102
- if (ruleId === 'RAW_EVENT_PUBLISH')
103
- return 'Publish through a registered intent creator, then run Ark again.';
104
- if (ruleId === 'PUBLISH_MISSING_SOURCE')
105
- return 'Add metadata.source to the publish call, then run Ark again.';
106
- if (ruleId === 'ARKRULE_STRUCTURE' ||
107
- ruleId === 'ARKRULE_INVARIANT' ||
108
- ruleId === 'INVARIANT_UNCOVERED' ||
109
- ruleId.startsWith('ARKRULE_')) {
110
- const source = evidence.arkruleSource ?? 'arkrules/<Layer>.json';
111
- const id = evidence.arkruleId ?? 'the ArkRule';
112
- return `Fix the structure or invariant for ${id} (declared in ${source}), then preflight again. Do not demote the rule without a hash-bound policy acknowledgement.`;
113
- }
114
- return `Resolve ${ruleId} without weakening ark.config.json, then run Ark again.`;
80
+ return deterministicNextAction({
81
+ ruleId,
82
+ target: text(evidence.target) ?? text(violation.target) ?? undefined,
83
+ fromLayer: text(evidence.fromLayer) ?? undefined,
84
+ toLayer: text(evidence.toLayer) ?? undefined,
85
+ typeOnly: evidence.typeOnly === true,
86
+ targetTypeOnlyExports: evidence.targetTypeOnlyExports === true,
87
+ namedBindingsTypeOnly: evidence.namedBindingsTypeOnly === true,
88
+ portProofEligible: evidence.portProofEligible === true,
89
+ peerIsolation: evidence.peerIsolation === true,
90
+ sourcePureTypeModule: evidence.sourcePureTypeModule === true,
91
+ edgeKind: text(evidence.edgeKind) ?? undefined,
92
+ capability: text(evidence.capability) ?? text(violation.capability) ?? undefined,
93
+ arkruleId: text(evidence.arkruleId) ?? undefined,
94
+ arkruleSource: text(evidence.arkruleSource) ?? undefined,
95
+ });
115
96
  }
116
97
  export function toAdapterDiagnostic(violation, fallbackSeverity = 'error',
117
98
  /**