arkgate 4.8.23 → 4.8.25

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 (201) hide show
  1. package/CHANGELOG.md +153 -0
  2. package/README.md +7 -5
  3. package/bin/ark-check-runtime.mjs +41 -91
  4. package/bin/ark-dashboard.mjs +56 -53
  5. package/bin/ark-layer-match.mjs +707 -121
  6. package/bin/ark-mcp-runtime.mjs +448 -169
  7. package/bin/ark-mcp.mjs +68 -4
  8. package/bin/ark-shared.mjs +42 -22
  9. package/bin/ark.mjs +65 -97
  10. package/bin/lib/adapter-contract-types.mjs +5 -0
  11. package/bin/lib/adapter-contract.mjs +15 -0
  12. package/bin/lib/adoption-stance.mjs +2 -2
  13. package/bin/lib/adr-path.mjs +3 -3
  14. package/bin/lib/agent-gates.mjs +0 -61
  15. package/bin/lib/agent-homes.mjs +12 -21
  16. package/bin/lib/agent-projection-command.mjs +9 -5
  17. package/bin/lib/ambient-state.mjs +9 -30
  18. package/bin/lib/analysis-engine.mjs +15 -11
  19. package/bin/lib/architecture-scan.mjs +61 -15
  20. package/bin/lib/ark-order-doctor.mjs +4 -29
  21. package/bin/lib/ark-order-facts.mjs +319 -17
  22. package/bin/lib/ark-order-sensors.mjs +28 -101
  23. package/bin/lib/ark-order-types.mjs +3 -2
  24. package/bin/lib/ark-run-doctor.mjs +17 -28
  25. package/bin/lib/ark-run-facts.mjs +461 -48
  26. package/bin/lib/ark-run-sensors.mjs +6 -4
  27. package/bin/lib/arkgate-pins.mjs +91 -0
  28. package/bin/lib/arkrule-file-hints.mjs +2 -47
  29. package/bin/lib/arkrules-contract.mjs +124 -133
  30. package/bin/lib/arkrules-sensors.mjs +55 -252
  31. package/bin/lib/check-args.mjs +85 -1
  32. package/bin/lib/ci-and-commands.mjs +24 -78
  33. package/bin/lib/ci-merge-boundary.mjs +1 -1
  34. package/bin/lib/class-source-scan.mjs +479 -0
  35. package/bin/lib/clone-detection.mjs +474 -0
  36. package/bin/lib/codex-home.mjs +5 -3
  37. package/bin/lib/config-contract-slices.mjs +739 -0
  38. package/bin/lib/config-contract.mjs +42 -607
  39. package/bin/lib/config-extras.mjs +16 -22
  40. package/bin/lib/config-warnings.mjs +32 -0
  41. package/bin/lib/contract-smells.mjs +33 -15
  42. package/bin/lib/core-ratchet.mjs +0 -2
  43. package/bin/lib/dashboard-cli.mjs +62 -0
  44. package/bin/lib/deep-module-coach.mjs +2 -4
  45. package/bin/lib/deploy-path.mjs +13 -38
  46. package/bin/lib/design-delta.mjs +1 -1
  47. package/bin/lib/design-smells.mjs +2 -2
  48. package/bin/lib/diagnostic-catalog.mjs +23 -9
  49. package/bin/lib/doctor-advisories.mjs +78 -8
  50. package/bin/lib/doctor-green-cite.mjs +1 -1
  51. package/bin/lib/doctor-human.mjs +1 -1
  52. package/bin/lib/doctor-next-actions.mjs +19 -4
  53. package/bin/lib/doctor-plan.mjs +9 -13
  54. package/bin/lib/duplication-io.mjs +572 -0
  55. package/bin/lib/effective-contract-load.mjs +282 -83
  56. package/bin/lib/enforcement-profiles.mjs +40 -4
  57. package/bin/lib/entry-points-io.mjs +424 -0
  58. package/bin/lib/extra-merge-teeth.mjs +36 -0
  59. package/bin/lib/field-install.mjs +38 -14
  60. package/bin/lib/first-run-help.mjs +10 -5
  61. package/bin/lib/flat-parent-importers.mjs +139 -0
  62. package/bin/lib/flat-parent-pilot.mjs +40 -100
  63. package/bin/lib/gate-files.mjs +58 -90
  64. package/bin/lib/git-change-scope.mjs +109 -0
  65. package/bin/lib/github-enforcement.mjs +166 -29
  66. package/bin/lib/golden-pattern.mjs +1 -1
  67. package/bin/lib/hook-templates.mjs +17 -15
  68. package/bin/lib/host-support-matrix.mjs +3 -1
  69. package/bin/lib/html-report-advisories.mjs +4 -0
  70. package/bin/lib/html-report-depth.mjs +2 -2
  71. package/bin/lib/html-report.mjs +2 -2
  72. package/bin/lib/import-graph-projection.mjs +112 -0
  73. package/bin/lib/import-resolve.mjs +13 -1
  74. package/bin/lib/improvement-compass-doctor.mjs +1 -24
  75. package/bin/lib/install-migrate.mjs +50 -73
  76. package/bin/lib/invariant-coverage-io.mjs +4 -3
  77. package/bin/lib/invariant-coverage.mjs +85 -11
  78. package/bin/lib/invariant-probe-cli.mjs +347 -0
  79. package/bin/lib/invariant-probe-io.mjs +165 -0
  80. package/bin/lib/invariant-probe-runner.mjs +268 -0
  81. package/bin/lib/invariant-probe-sites.mjs +174 -0
  82. package/bin/lib/invariant-probe-workspace.mjs +183 -0
  83. package/bin/lib/invariant-probe.mjs +461 -0
  84. package/bin/lib/invariant-tests-path.mjs +16 -22
  85. package/bin/lib/literal-path-drift.mjs +4 -2
  86. package/bin/lib/managed-upgrade.mjs +2 -8
  87. package/bin/lib/mcp-adoption.mjs +13 -6
  88. package/bin/lib/mcp-hook-payload.mjs +403 -63
  89. package/bin/lib/orphan-modules-io.mjs +458 -0
  90. package/bin/lib/orphan-modules.mjs +251 -0
  91. package/bin/lib/outside-importers.mjs +197 -0
  92. package/bin/lib/package-manager.mjs +60 -2
  93. package/bin/lib/parse-health.mjs +1 -1
  94. package/bin/lib/passthrough-commands.mjs +83 -0
  95. package/bin/lib/persistence-write-hint.mjs +106 -0
  96. package/bin/lib/pin-dependent-gates.mjs +117 -0
  97. package/bin/lib/policy-delta-io.mjs +219 -14
  98. package/bin/lib/post-green-path.mjs +1 -1
  99. package/bin/lib/prepare-change.mjs +115 -7
  100. package/bin/lib/prepare-write.mjs +2 -2
  101. package/bin/lib/presets.mjs +59 -73
  102. package/bin/lib/product-copy.mjs +1 -6
  103. package/bin/lib/project-root.mjs +31 -62
  104. package/bin/lib/remediation.mjs +23 -4
  105. package/bin/lib/resolved-candidate-facts.mjs +405 -117
  106. package/bin/lib/rules-inventory.mjs +22 -3
  107. package/bin/lib/rules-under-contract.mjs +247 -9
  108. package/bin/lib/scan-files.mjs +36 -4
  109. package/bin/lib/schema-validation.mjs +130 -0
  110. package/bin/lib/sensor-promote-cli.mjs +2 -11
  111. package/bin/lib/sensor-promote-io.mjs +33 -22
  112. package/bin/lib/sensor-promotion.mjs +1 -1
  113. package/bin/lib/skill-catalog-apply.mjs +1 -1
  114. package/bin/lib/skill-install.mjs +42 -38
  115. package/bin/lib/snippet-analysis.mjs +140 -6
  116. package/bin/lib/source-policy.mjs +34 -0
  117. package/bin/lib/start-install-recovery.mjs +72 -2
  118. package/bin/lib/start-preview.mjs +31 -1
  119. package/bin/lib/states-transitions-presence.mjs +3 -3
  120. package/bin/lib/status-command.mjs +35 -22
  121. package/bin/lib/status-manifest.mjs +50 -30
  122. package/bin/lib/status-transition-catalog.mjs +0 -6
  123. package/bin/lib/suggestions.mjs +3 -3
  124. package/bin/lib/team-parliament-io.mjs +76 -88
  125. package/bin/lib/team-parliament.mjs +21 -10
  126. package/bin/lib/upgrade-behavior-changes.mjs +2 -2
  127. package/bin/lib/upgrade-command.mjs +2 -24
  128. package/bin/lib/upgrade-outcome.mjs +1 -1
  129. package/bin/lib/upstream-report.mjs +21 -10
  130. package/bin/lib/violations.mjs +10 -9
  131. package/bin/lib/weakest-link.mjs +13 -0
  132. package/bin/lib/write-path-capabilities.mjs +6 -4
  133. package/dist/chunk-A5EIY3HI.cjs +5 -0
  134. package/dist/chunk-AHACEKSV.js +16 -0
  135. package/dist/chunk-SVSQVGFJ.js +5 -0
  136. package/dist/chunk-UN5EH4QV.cjs +16 -0
  137. package/dist/{configTypes-Bbf_MFGe.d.ts → configTypes-GPvvdleM.d.cts} +30 -4
  138. package/dist/configTypes-GPvvdleM.d.ts +257 -0
  139. package/dist/eslint/index.cjs +18 -5
  140. package/dist/eslint/index.d.cts +368 -0
  141. package/dist/eslint/index.d.ts +139 -156
  142. package/dist/eslint/index.js +11 -5
  143. package/dist/index.cjs +36 -35
  144. package/dist/index.d.cts +5547 -0
  145. package/dist/index.d.ts +3283 -198
  146. package/dist/index.js +36 -35
  147. package/dist/inspectorListen-6DQVJ4DC.js +1 -0
  148. package/dist/inspectorListen-KGMIQ2TF.cjs +1 -0
  149. package/dist/nestjs/index.cjs +1 -18
  150. package/dist/nestjs/index.d.cts +22 -0
  151. package/dist/nestjs/index.d.ts +1 -3
  152. package/dist/nestjs/index.js +1 -18
  153. package/dist/order/index.cjs +1 -1
  154. package/dist/order/index.d.cts +155 -0
  155. package/dist/order/index.d.ts +15 -3
  156. package/dist/order/index.js +1 -1
  157. package/dist/runtime/index.cjs +13 -30
  158. package/dist/{diagnosticCatalog-35mrwAHe.d.ts → runtime/index.d.cts} +828 -276
  159. package/dist/runtime/index.d.ts +2741 -133
  160. package/dist/runtime/index.js +13 -30
  161. package/dist/{types-Bdj32QwA.d.ts → types-CnauMQQL.d.cts} +607 -163
  162. package/dist/types-CnauMQQL.d.ts +1800 -0
  163. package/docs/README.md +3 -1
  164. package/docs/agent-guide.md +217 -16
  165. package/docs/ai-gates.md +174 -26
  166. package/docs/arkorder.md +32 -15
  167. package/docs/brownfield-adoption.md +5 -1
  168. package/docs/configuration.md +230 -31
  169. package/docs/develop.md +2 -1
  170. package/docs/diagnostics.md +212 -19
  171. package/docs/package-surface.md +35 -9
  172. package/docs/product-voice.md +7 -1
  173. package/package.json +69 -19
  174. package/schemas/ark.analysis-result.schema.json +15 -0
  175. package/schemas/ark.config.schema.json +77 -12
  176. package/schemas/ark.invariant-probe.schema.json +368 -0
  177. package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
  178. package/schemas/ark.status-manifest.schema.json +22 -0
  179. package/server.json +3 -9
  180. package/templates/agent-skills/README.md +1 -1
  181. package/templates/agent-skills/ark-adopt/SKILL.md +5 -1
  182. package/templates/agent-skills/ark-contract/SKILL.md +17 -0
  183. package/templates/agent-skills/ark-coverage/SKILL.md +2 -0
  184. package/templates/agent-skills/ark-explore/SKILL.md +7 -0
  185. package/templates/agent-skills/ark-fix/SKILL.md +20 -0
  186. package/templates/agent-skills/ark-order/SKILL.md +4 -2
  187. package/templates/agent-skills/ark-place/SKILL.md +8 -0
  188. package/templates/agent-skills/ark-runtime/SKILL.md +6 -1
  189. package/templates/arkrules/PersistenceAdapters.json +1 -1
  190. package/templates/arkrules/PresentationAdapters.json +1 -1
  191. package/templates/skills/ark-adopt.md +5 -1
  192. package/templates/skills/ark-contract.md +17 -0
  193. package/templates/skills/ark-coverage.md +2 -0
  194. package/templates/skills/ark-explore.md +7 -0
  195. package/templates/skills/ark-fix.md +20 -0
  196. package/templates/skills/ark-order.md +4 -2
  197. package/templates/skills/ark-place.md +8 -0
  198. package/templates/skills/ark-runtime.md +6 -1
  199. package/bin/lib/ark-order-error.mjs +0 -18
  200. package/bin/lib/ark-order-invariants.mjs +0 -346
  201. package/dist/types-Ik_OakcI.d.ts +0 -183
@@ -125,7 +125,8 @@ the same files or weaken the gate.
125
125
  - Keep Domain-role layers plane-free (`ARKORDER_KERNEL_IN_DOMAIN`).
126
126
  - First freeze: `release()`. Later change of a named choice is `proposeRelease` then `apply`
127
127
  — not a second `release()` (`ARKORDER_UNVALVED_RELEASE`), not `update` / `patch` / `set`
128
- (`ARKORDER_GENERIC_UPDATE`).
128
+ (`ARKORDER_GENERIC_UPDATE`). Apply the proposal you reviewed against the current Release;
129
+ a stale one, or one whose blast is not the transition that commits, fails `ARKORDER_STALE_PROPOSAL` — propose again.
129
130
  - Name **`xiKeys`** (3–5 slow product decisions). Membership ids and recomputable statuses
130
131
  are not keys: derive a status on read or fold it from ingest. A use-case that persists
131
132
  those keys is `ARKORDER_XI_FIELD_WRITE`. Invoices and seats still flow through `ingest`.
@@ -149,7 +150,8 @@ the same files or weaken the gate.
149
150
  - Extra absent and the user wants it: **STOP — do not continue this skill as complete.**
150
151
  **`/ark-adopt`** writes advisory `arkOrder`.
151
152
  - Skip cluster (`ARKORDER_MISSING_PLANE` / `ARKORDER_KERNEL_IN_DOMAIN` / `ARKORDER_GENERIC_UPDATE`
152
- / `ARKORDER_TOO_MANY_PARAMS` / `ARKORDER_INGEST_WRITES_XI` / `ARKORDER_XI_FIELD_WRITE`) after
153
+ / `ARKORDER_TOO_MANY_PARAMS` / `ARKORDER_INGEST_WRITES_XI` / `ARKORDER_XI_FIELD_WRITE`
154
+ / `ARKORDER_XI_TTL` / `ARKORDER_INFORMATION_BUDGET`) after
153
155
  the extra is on: **`/ark-autopilot`** — this skill still wires one candidate.
154
156
  - New plane-root file after the extra is on: **`/ark-place`**.
155
157
  - One kernel / bus candidate: **`/ark-runtime`** — do not mix planes.
@@ -35,6 +35,12 @@ right house. Skills never enforce — CLI / hooks / CI do.
35
35
  adapter or a Domain / auth-tag policy — not another SQLite / JSON-file store
36
36
  or admin literal in the page. Soft. Silent when those houses are absent or
37
37
  the shortcut is already in the right house. No `/ark-prototype`.
38
+ - When doctor JSON `crossWallDuplication` (status details, `--doctor --all`) lists a
39
+ family, its `destination` is where the one shared copy goes: the declared shared
40
+ root, the universe common folder, or the lower layer both sides may import. Write
41
+ it there and import it from both sides — never a third copy, never a new
42
+ `allowedCrossSlice` to reach across the wall. `ask-place` means pick a home both
43
+ sides may import. Advisory. No `/ark-dedupe`.
38
44
  - When the matched layer has `layers[].description`, print that caption next to the
39
45
  layer name and globs. Omit when absent — do not invent a caption or `/ark-describe`.
40
46
  - When the matched layer has `layers[].trustBoundary`, print `trust: <tag>` next
@@ -175,6 +181,8 @@ ArkGate has **always-on Layers** plus opt-in extras. The user chooses extras; yo
175
181
 
176
182
 
177
183
  ### Place + ArkRules
184
+ - `arkRules[<Layer>]` may be one path or a list of paths. A child wall may set `childSlices.arkRulesFile` to `arkrules.<Layer>.json`. That file lives at the slice root. Its id in findings is `<childId>#<localId>`. Doctor lists it on `rulesUnderContract.bySlice`. When `ark_place` returns `sliceRules`, those are the ids for this file's slice.
185
+ - A `sliceAliases` entry with `"pinned": true` stays put (a framework route). `reason` is a label only. Do not treat a pinned route as finished while another alias is still an owed move.
178
186
  - Choose layer from contract **and** check structure sensors for that layer (private state, factory, thin adapter, writes-via-aggregate).
179
187
  - Scaffold to satisfy **[ArkRules]** when present; state which sensors apply.
180
188
  - Persistence **writes** (insert/update/delete against a driver) go through a Domain aggregate + persistence adapter. Application/Feature files that import Prisma/pg/Supabase and call `.insert` / `.create` are **[ArkRules]** `writes-via-aggregate`. Do not invent `Externals/` or `admission.ts`.
@@ -124,7 +124,12 @@ the same files or weaken the gate.
124
124
  `appendDecisionTape`. Shadow / compare / replay that snapshot in-memory (not a bus, not
125
125
  durable). This is still **[ArkRun]** — do not turn the tape into an ArkOrder skill.
126
126
  - In-memory stores lose state on restart — **not** production durability. Note bounded history
127
- (`maxHistorySize` 1000) if the hand-rolled version retained everything.
127
+ (`maxHistorySize` 1000 — history, trace, audit, and the default event buffer) if the
128
+ hand-rolled version retained everything.
129
+ - `createStrictArkKernelFromConfig` maps intents by `layers[].intentPrefixes` (canonical
130
+ layer names get built-in ones); a custom deny-rule layer without prefixes is audited as
131
+ `layer.observedFlowUnresolvable` (explicit `'hard'` throws `ArkKernelConfigError`). Enforced declarations accept a literal or a same-file
132
+ `define(..)` creator as the call name.
128
133
  6. **Delete the hand-rolled version** once call sites are moved — the point is
129
134
  less code, not a second parallel system. Deleting code is a destructive move:
130
135
  confirm with the user before removing the old implementation, and never delete
@@ -4,7 +4,7 @@
4
4
  "layer": "PersistenceAdapters",
5
5
  "structure": [
6
6
  {
7
- "id": "thin-adapter",
7
+ "id": "thin-persistence-adapter",
8
8
  "sensor": "thin-adapter",
9
9
  "mode": "advisory",
10
10
  "description": "Persistence adapters implement ports without domain branching."
@@ -4,7 +4,7 @@
4
4
  "layer": "PresentationAdapters",
5
5
  "structure": [
6
6
  {
7
- "id": "thin-adapter",
7
+ "id": "thin-presentation-adapter",
8
8
  "sensor": "thin-adapter",
9
9
  "mode": "advisory",
10
10
  "description": "Adapters stay thin: map I/O and delegate; avoid domain branching + persistence here."
@@ -241,6 +241,7 @@ ArkGate has **always-on Layers** plus opt-in extras. The user chooses extras; yo
241
241
 
242
242
 
243
243
  ### Adopt + ArkRules
244
+ - `arkRules[<Layer>]` may be one path or a list. On a child wall, `childSlices.arkRulesFile` (`arkrules.<Layer>.json`) is the slice's own file. Do not put feature rules only in the central file when that filename is set. `"pinned": true` on a slice alias keeps a framework path; `reason` does not pin.
244
245
  - After classify: emit or refresh `arkRules` for matched layers (exact names; generic mold for unknowns).
245
246
  - Mine rules → inventory + write advisory invariants/structure into `arkrules/<Layer>.json` **in this turn**.
246
247
  - Empty `invariants[]` on a populated Domain while `arkRules` is on is residual (`INVARIANT_CATALOG_EMPTY`), not done. Fill 1–2 short phrases. Advisory until a domain structure rule is `enforced`.
@@ -362,7 +363,10 @@ Ark protects the **boundary around** a framework, not its internals. Nest/DI pub
362
363
  import them, and they may import a slice. Doctor lists that shared-root → slice hop even
363
364
  when the check stays green, so the wall is direct-only. `sharedImportsSlice: "deny"` closes
364
365
  that one hop; it does not finish the wall. Promoting the shared slice to its own
365
- layer is still the preferred fix.
366
+ layer is still the preferred fix. With `"deny-cross-parent"` in a DI app, list the
367
+ composition root (bootstrap, registrations) in the object form's `stopAt` instead of
368
+ freezing dozens of `CROSS_PARENT_VIA_SHARED` findings through it; doctor
369
+ `sharedWalkHubs` names that file.
366
370
  `sliceFolders`: a bare name stays an unanchored one-segment match (`features` on
367
371
  `src/lib/features/projects/rfi/x.ts` is `features/projects`); a filename is never the
368
372
  child segment. A starred prefix (`lib/features/*/*`) is anchored like `sharedRoots`
@@ -52,6 +52,23 @@ If the host supports **parallel subagents**, fan out read-only scouts; otherwise
52
52
  2. Path honest and tightening → execute **`/ark-autopilot`**.
53
53
  3. One kernel candidate → **`/ark-runtime`**. One order-plane candidate → **`/ark-order`**.
54
54
 
55
+ ## Promoting an invariant (advisory → enforced)
56
+
57
+ Promotion goes through one judge. Run `arkgate-check --promote [<id>] --json` first and read
58
+ each invariant row's `blocker`:
59
+
60
+ - `no-coverage-evidence` — no test title or declared symbol, partial scan, outside
61
+ `coverage.coverageRoots`, or no roots declared. Write the evidence or the roots (`/ark-adopt`).
62
+ - `probe-survived` — the committed `.ark/invariant-probe.json` shows, for files unchanged
63
+ since the run, that the covering tests still pass with the rule broken (`survived`) or never
64
+ call the symbol (`not-reached`). The fix is a **stronger test**, not more evidence text: add
65
+ the case the line names, then re-run `arkgate-check --probe-invariants=<id> --write` and
66
+ commit the file. Never delete or hand-edit the artifact to get past the refusal.
67
+
68
+ The probe runs the project's own tests in a temporary copy; ask the owner before you run it.
69
+ It is opt-in and not a score: a missing, stale, `killed` or `inconclusive` result changes
70
+ nothing. Only then `--promote <id> --apply`.
71
+
55
72
  ## Completion contract (skill incomplete if missing)
56
73
 
57
74
  Skill incomplete if missing any field below.
@@ -97,11 +97,13 @@ ArkGate has **always-on Layers** plus opt-in extras. The user chooses extras; yo
97
97
  4. Editing `arkrules/*` or promoting modes is **`/ark-adopt`**; fixing code under a structure sensor is **`/ark-autopilot`** (judgment, never invent mechanical-safe).
98
98
  5. CLI helpers: `ark-check --rules-inventory --json`, doctor JSON `rulesUnderContract`, sensors emit `ARKRULE_*` / `INVARIANT_UNCOVERED` / `INVARIANT_CATALOG_EMPTY` with `evidence.arkruleId`.
99
99
  5b. Invariant coverage is a **text match, never an execution**: a file walk plus the invariant id in a `describe`/`it` title. `coverage.symbol` means a declaration of that identifier in a non-test file (`symbolEvidenceFile`); imports and test calls do not count. Doctor and `--rules-inventory` print that witness path and the scan discard counts on a green run too. Never report `INVARIANT_UNCOVERED: 0`, a witness path, or a discard line as “the tests pass” or “the tests run” — ArkGate does not run tests and reads no runner config. Declaration text is not “the tests pass.” When the project declares `coverage.coverageRoots`, a covering test found outside them raises the advisory `INVARIANT_COVERAGE_OUTSIDE_ROOTS`; without that declaration ArkGate makes no claim about where tests run. Adopted + catalogued invariants without `coverage.testGlobs` or `coverage.coverageRoots` fails closed (`INVARIANT_TESTS_PATH_MISSING`) — write the path (`/ark-adopt`). When any invariant is enforced, missing `coverage.coverageRoots` fails closed (`INVARIANT_COVERAGE_ROOTS_MISSING`) — write the runner roots (`/ark-adopt`). `testGlobs` alone is not enough.
100
+ 5c. The one exception is owner-invoked: `arkgate-check --probe-invariants` breaks each `coverage.symbol` on purpose in a temporary copy and runs only its covering tests with the project's runner (ask first — it runs their tests). When `.ark/invariant-probe.json` exists, doctor `rulesUnderContract.probe` and `--rules-inventory` `coverageEvidence.probe` show per invariant `killed` / `survived` / `not-reached` / `stale` / `not run`; `INVARIANT_PROBE_SURVIVED` means a fresh row refuses promotion. Report it as **[ArkRules]** "the test names the rule but does not pin it", name the change that went unnoticed, never as a percentage. `killed` is evidence the covering tests catch those three changes — not "the domain is correct."
100
101
  6. Extras silent when off. Doctor `arkRun` / `arkOrder` is `notAScore`. Do not force extras. Wire one kernel candidate via `/ark-runtime`; one order-plane candidate via `/ark-order`. Do not invent `/ark-run`.
101
102
 
102
103
 
103
104
  ### Coverage + ArkRules
104
105
  - Fitness report: governed% / gates / baseline = **[Layer]**; `rulesUnderContract` inventoried/under-contract/frozen = **[ArkRules]** (counts, never a score).
106
+ - Invariant probe (when the artifact exists): one row per `survived` / `not-reached` invariant with the line it names and the test to add; `stale` rows mean "re-run `--probe-invariants --write`", never "pinned".
105
107
  - Capability gap: “no arkRules map” is optional adoption opportunity, not a failed gate.
106
108
 
107
109
  ### Coverage + extras
@@ -58,6 +58,13 @@ one bounded decision needs 2–3 enforceable options.
58
58
  including when the run is green. A declaration in a non-test file is not
59
59
  “the tests pass” — ArkGate never runs tests. Depth and byte caps stay
60
60
  internal; the counts are what you read.
61
+ - When doctor JSON has `orphanModules` with files listed, map them: nothing imports
62
+ each file and no entry point covers it. Read `certainty` — `maybe-dynamic` and
63
+ `maybe-unresolved` may still be loaded (open what the evidence names). A
64
+ `partial` section is never a complete list. `--doctor --all` adds exports
65
+ nothing imports by name. Map only; deleting is `/ark-fix`, one file at a time.
66
+ A framework or loader ArkGate does not know goes in `.ark/entry-points.json`
67
+ with a reason. Never say "dead code". No new skill.
61
68
  - A doctor green / Healthy line must name a file, config key, or test.
62
69
  Uncited green is a lie — treat it as unfinished. No `/ark-cite`.
63
70
  - Distinguish **missing** skills vs **stale** catalog. Installed ≠ stale.
@@ -25,6 +25,26 @@ Write an **extraction card** (`docs/brownfield-adoption.md` §6) — never mecha
25
25
  never silent B apply. **Kill-switch** required. `multiPilotBatchForbidden` — never
26
26
  multi-pilot batch. Execute that work on **`/ark-autopilot`**.
27
27
 
28
+ ## Files nothing imports (one delete at a time)
29
+
30
+ When `doctor.orphanModules` lists a file with `certainty: no-importer`, nothing
31
+ imports it and no entry point covers it. Delete **one** file through the write
32
+ gate, run `arkgate-check` and status again, then the next. A `maybe-dynamic` or
33
+ `maybe-unresolved` item may still be loaded: open what its evidence names
34
+ first. If a framework or loader uses the file, add a glob with a reason to
35
+ `.ark/entry-points.json` instead — never edit `ark.config.json` for it. No batch
36
+ delete, no codemod.
37
+
38
+ ## Copies across a wall (one move at a time)
39
+
40
+ When `doctor.crossWallDuplication` (status details, `--doctor --all`) lists a
41
+ family, the wall stopped the import and the code was copied. Move **one** copy to
42
+ the family's `destination` through the write gate (`/ark-place` names it), point
43
+ both sides at it, delete the other copy, run `arkgate-check` and status again,
44
+ then the next family. Never open the wall (`allowedCrossSlice`, a weaker rule) to
45
+ make the copy importable. Copies inside one slice are not listed on purpose. No
46
+ batch, no codemod.
47
+
28
48
  ## Dual engine (mandatory)
29
49
 
30
50
  | Engine | Role |
@@ -125,7 +125,8 @@ the same files or weaken the gate.
125
125
  - Keep Domain-role layers plane-free (`ARKORDER_KERNEL_IN_DOMAIN`).
126
126
  - First freeze: `release()`. Later change of a named choice is `proposeRelease` then `apply`
127
127
  — not a second `release()` (`ARKORDER_UNVALVED_RELEASE`), not `update` / `patch` / `set`
128
- (`ARKORDER_GENERIC_UPDATE`).
128
+ (`ARKORDER_GENERIC_UPDATE`). Apply the proposal you reviewed against the current Release;
129
+ a stale one, or one whose blast is not the transition that commits, fails `ARKORDER_STALE_PROPOSAL` — propose again.
129
130
  - Name **`xiKeys`** (3–5 slow product decisions). Membership ids and recomputable statuses
130
131
  are not keys: derive a status on read or fold it from ingest. A use-case that persists
131
132
  those keys is `ARKORDER_XI_FIELD_WRITE`. Invoices and seats still flow through `ingest`.
@@ -149,7 +150,8 @@ the same files or weaken the gate.
149
150
  - Extra absent and the user wants it: **STOP — do not continue this skill as complete.**
150
151
  **`/ark-adopt`** writes advisory `arkOrder`.
151
152
  - Skip cluster (`ARKORDER_MISSING_PLANE` / `ARKORDER_KERNEL_IN_DOMAIN` / `ARKORDER_GENERIC_UPDATE`
152
- / `ARKORDER_TOO_MANY_PARAMS` / `ARKORDER_INGEST_WRITES_XI` / `ARKORDER_XI_FIELD_WRITE`) after
153
+ / `ARKORDER_TOO_MANY_PARAMS` / `ARKORDER_INGEST_WRITES_XI` / `ARKORDER_XI_FIELD_WRITE`
154
+ / `ARKORDER_XI_TTL` / `ARKORDER_INFORMATION_BUDGET`) after
153
155
  the extra is on: **`/ark-autopilot`** — this skill still wires one candidate.
154
156
  - New plane-root file after the extra is on: **`/ark-place`**.
155
157
  - One kernel / bus candidate: **`/ark-runtime`** — do not mix planes.
@@ -35,6 +35,12 @@ right house. Skills never enforce — CLI / hooks / CI do.
35
35
  adapter or a Domain / auth-tag policy — not another SQLite / JSON-file store
36
36
  or admin literal in the page. Soft. Silent when those houses are absent or
37
37
  the shortcut is already in the right house. No `/ark-prototype`.
38
+ - When doctor JSON `crossWallDuplication` (status details, `--doctor --all`) lists a
39
+ family, its `destination` is where the one shared copy goes: the declared shared
40
+ root, the universe common folder, or the lower layer both sides may import. Write
41
+ it there and import it from both sides — never a third copy, never a new
42
+ `allowedCrossSlice` to reach across the wall. `ask-place` means pick a home both
43
+ sides may import. Advisory. No `/ark-dedupe`.
38
44
  - When the matched layer has `layers[].description`, print that caption next to the
39
45
  layer name and globs. Omit when absent — do not invent a caption or `/ark-describe`.
40
46
  - When the matched layer has `layers[].trustBoundary`, print `trust: <tag>` next
@@ -175,6 +181,8 @@ ArkGate has **always-on Layers** plus opt-in extras. The user chooses extras; yo
175
181
 
176
182
 
177
183
  ### Place + ArkRules
184
+ - `arkRules[<Layer>]` may be one path or a list of paths. A child wall may set `childSlices.arkRulesFile` to `arkrules.<Layer>.json`. That file lives at the slice root. Its id in findings is `<childId>#<localId>`. Doctor lists it on `rulesUnderContract.bySlice`. When `ark_place` returns `sliceRules`, those are the ids for this file's slice.
185
+ - A `sliceAliases` entry with `"pinned": true` stays put (a framework route). `reason` is a label only. Do not treat a pinned route as finished while another alias is still an owed move.
178
186
  - Choose layer from contract **and** check structure sensors for that layer (private state, factory, thin adapter, writes-via-aggregate).
179
187
  - Scaffold to satisfy **[ArkRules]** when present; state which sensors apply.
180
188
  - Persistence **writes** (insert/update/delete against a driver) go through a Domain aggregate + persistence adapter. Application/Feature files that import Prisma/pg/Supabase and call `.insert` / `.create` are **[ArkRules]** `writes-via-aggregate`. Do not invent `Externals/` or `admission.ts`.
@@ -124,7 +124,12 @@ the same files or weaken the gate.
124
124
  `appendDecisionTape`. Shadow / compare / replay that snapshot in-memory (not a bus, not
125
125
  durable). This is still **[ArkRun]** — do not turn the tape into an ArkOrder skill.
126
126
  - In-memory stores lose state on restart — **not** production durability. Note bounded history
127
- (`maxHistorySize` 1000) if the hand-rolled version retained everything.
127
+ (`maxHistorySize` 1000 — history, trace, audit, and the default event buffer) if the
128
+ hand-rolled version retained everything.
129
+ - `createStrictArkKernelFromConfig` maps intents by `layers[].intentPrefixes` (canonical
130
+ layer names get built-in ones); a custom deny-rule layer without prefixes is audited as
131
+ `layer.observedFlowUnresolvable` (explicit `'hard'` throws `ArkKernelConfigError`). Enforced declarations accept a literal or a same-file
132
+ `define(..)` creator as the call name.
128
133
  6. **Delete the hand-rolled version** once call sites are moved — the point is
129
134
  less code, not a second parallel system. Deleting code is a destructive move:
130
135
  confirm with the user before removing the old implementation, and never delete
@@ -1,18 +0,0 @@
1
- /**
2
- * GENERATED FILE — do not edit by hand.
3
- *
4
- * Canonical algorithm: src/domain/arkOrderError.ts
5
- * Regenerate: node scripts/generate-cli-pure.mjs
6
- * Drift check: node scripts/generate-cli-pure.mjs --check
7
- *
8
- * Pure CLI helper (bin/lib/ark-order-error.mjs). Zero Node I/O.
9
- */
10
-
11
- export class ArkOrderError extends Error {
12
- code;
13
- constructor(code, message) {
14
- super(message);
15
- this.name = 'ArkOrderError';
16
- this.code = code;
17
- }
18
- }
@@ -1,346 +0,0 @@
1
- /**
2
- * GENERATED FILE — do not edit by hand.
3
- *
4
- * Canonical algorithm: src/domain/arkOrderInvariants.ts
5
- * Regenerate: node scripts/generate-cli-pure.mjs
6
- * Drift check: node scripts/generate-cli-pure.mjs --check
7
- *
8
- * Pure CLI helper (bin/lib/ark-order-invariants.mjs). Zero Node I/O.
9
- */
10
-
11
- import { ArkOrderError } from './ark-order-error.mjs';
12
- import { CAPACITY_OPS, DEFAULT_MAX_XI_KEYS } from './ark-order-types.mjs';
13
- import { deterministicHash, stableSerialize } from './stableHash';
14
- export { DEFAULT_MAX_XI_KEYS };
15
- /** D7: consumer still owns handlers — this only names the travel verb. */
16
- export function ingestTravelAction(residual) {
17
- if (residual.kind === 'absorb')
18
- return 'send';
19
- if (residual.kind === 'escalate_up' && residual.target === 'human')
20
- return 'raises';
21
- return 'none';
22
- }
23
- const FORBIDDEN_PLANE_METHODS = ['update', 'patch', 'set', 'mutate'];
24
- export function isForbiddenPlaneMethod(name) {
25
- return FORBIDDEN_PLANE_METHODS.includes(name);
26
- }
27
- function isPrimitive(value) {
28
- return (value === null ||
29
- typeof value === 'string' ||
30
- typeof value === 'number' ||
31
- typeof value === 'boolean');
32
- }
33
- export function freezeRecord(input, label) {
34
- const keys = Object.keys(input);
35
- if (keys.length === 0 && label === 'ξ') {
36
- throw new ArkOrderError('ARKORDER_EMPTY_XI', 'ξ must name at least one slow mode');
37
- }
38
- const out = {};
39
- for (const key of keys.sort()) {
40
- const value = input[key];
41
- if (!isPrimitive(value)) {
42
- throw new ArkOrderError('ARKORDER_NESTED_XI', `${label} key ${JSON.stringify(key)} is not a slow primitive (nested values are microstate)`);
43
- }
44
- out[key] = value;
45
- }
46
- return Object.freeze(out);
47
- }
48
- export function assertXiKeyCap(xi, maxXiKeys) {
49
- const n = Object.keys(xi).length;
50
- if (n === 0) {
51
- throw new ArkOrderError('ARKORDER_EMPTY_XI', 'ξ must name at least one slow mode');
52
- }
53
- if (n > maxXiKeys) {
54
- throw new ArkOrderError('ARKORDER_TOO_MANY_PARAMS', `ξ has ${n} keys; maxXiKeys is ${maxXiKeys} (Haken: few slow modes)`);
55
- }
56
- }
57
- export function assertXiSchema(xi, schema) {
58
- if (!schema)
59
- return;
60
- const properties = schema.properties ?? {};
61
- const additional = schema.additionalProperties !== false ? true : false;
62
- for (const key of Object.keys(xi)) {
63
- const prop = properties[key];
64
- if (!prop) {
65
- if (!additional) {
66
- throw new ArkOrderError('ARKORDER_SCHEMA', `ξ key ${JSON.stringify(key)} is not in xiSchema.properties`);
67
- }
68
- continue;
69
- }
70
- if (prop.enum && !prop.enum.some((allowed) => allowed === xi[key])) {
71
- throw new ArkOrderError('ARKORDER_SCHEMA', `ξ key ${JSON.stringify(key)} value is not in enum`);
72
- }
73
- if (prop.type === 'null' && xi[key] !== null) {
74
- throw new ArkOrderError('ARKORDER_SCHEMA', `ξ key ${JSON.stringify(key)} must be null`);
75
- }
76
- if (prop.type && prop.type !== 'null' && typeof xi[key] !== prop.type) {
77
- throw new ArkOrderError('ARKORDER_SCHEMA', `ξ key ${JSON.stringify(key)} must be ${prop.type}`);
78
- }
79
- }
80
- }
81
- function catalogDigestFor(xi, catalogDigest) {
82
- if (typeof catalogDigest !== 'string')
83
- return undefined;
84
- if (!Object.prototype.hasOwnProperty.call(xi, 'catalogReleaseId'))
85
- return undefined;
86
- return catalogDigest;
87
- }
88
- export function hashReleasePayload(xi, sigma, catalogDigest) {
89
- const digest = catalogDigestFor(xi, catalogDigest);
90
- if (digest !== undefined)
91
- return deterministicHash(stableSerialize({ xi, sigma, catalogDigest: digest }));
92
- return deterministicHash(stableSerialize({ xi, sigma }));
93
- }
94
- export function hashXiIdentity(xi, catalogDigest) {
95
- const digest = catalogDigestFor(xi, catalogDigest);
96
- if (digest !== undefined)
97
- return deterministicHash(stableSerialize({ xi, catalogDigest: digest }));
98
- return deterministicHash(stableSerialize({ xi }));
99
- }
100
- export function hashSigmaIdentity(sigma) {
101
- return deterministicHash(stableSerialize({ sigma }));
102
- }
103
- export function xiRecordsEqual(left, right) {
104
- return stableSerialize(left) === stableSerialize(right);
105
- }
106
- /** D1: after the first freeze, a later release() may not change ξ. */
107
- export function assertUnvalvedRelease(current, nextXi) {
108
- if (!current)
109
- return;
110
- if (xiRecordsEqual(current.xi, nextXi))
111
- return;
112
- throw new ArkOrderError('ARKORDER_UNVALVED_RELEASE', 'ξ is frozen; change the pattern with proposeRelease then apply(ProposeResult)');
113
- }
114
- export function createFrozenRelease(input) {
115
- assertXiKeyCap(input.xi, input.maxXiKeys);
116
- const xi = freezeRecord(input.xi, 'ξ');
117
- assertXiHasNoTtl(xi);
118
- assertXiSchema(xi, input.xiSchema);
119
- const sigma = freezeRecord(input.sigma ?? {}, 'σ');
120
- const release = Object.freeze({
121
- version: input.version,
122
- hash: hashReleasePayload(xi, sigma, input.catalogDigest),
123
- xiHash: hashXiIdentity(xi, input.catalogDigest),
124
- sigmaHash: hashSigmaIdentity(sigma),
125
- xi,
126
- sigma,
127
- releasedAt: input.now,
128
- });
129
- return release;
130
- }
131
- /** D2: refresh σ without minting a pattern. xiHash must not change. */
132
- export function refreshSigmaRecord(input) {
133
- const sigma = freezeRecord(input.sigma, 'σ');
134
- return Object.freeze({
135
- version: input.current.version,
136
- hash: hashReleasePayload(input.current.xi, sigma, input.catalogDigest),
137
- xiHash: input.current.xiHash,
138
- sigmaHash: hashSigmaIdentity(sigma),
139
- xi: input.current.xi,
140
- sigma,
141
- releasedAt: input.now,
142
- });
143
- }
144
- const XI_TTL_KEY_RE = /^(ttl|freshUntil|fresh_until|maxAge|max_age)$/i;
145
- export function assertXiHasNoTtl(xi) {
146
- for (const key of Object.keys(xi)) {
147
- if (XI_TTL_KEY_RE.test(key)) {
148
- throw new ArkOrderError('ARKORDER_XI_TTL', `ξ key ${JSON.stringify(key)} is a freshness field; TTL belongs on σ, never on ξ`);
149
- }
150
- }
151
- }
152
- export function assertInformationBudget(projection, budget) {
153
- if (!budget || budget.cannotObserve.length === 0)
154
- return;
155
- const denied = new Set(budget.cannotObserve);
156
- for (const kind of projection.allowedKinds) {
157
- if (denied.has(kind)) {
158
- throw new ArkOrderError('ARKORDER_INFORMATION_BUDGET', `projection allows ${JSON.stringify(kind)}; informationBudget.cannotObserve forbids it`);
159
- }
160
- }
161
- }
162
- export function assertSigmaFresh(input) {
163
- if (input.maxAgeMs === undefined)
164
- return;
165
- const until = input.sigma.freshUntil;
166
- if (typeof until === 'number') {
167
- if (input.now > until) {
168
- throw new ArkOrderError('ARKORDER_STALE_SIGMA', 'σ freshUntil has elapsed; ξ does not TTL');
169
- }
170
- return;
171
- }
172
- const origin = typeof input.sigma.releasedAt === 'number' ? input.sigma.releasedAt : input.releasedAt;
173
- if (typeof origin === 'number' && input.now - origin > input.maxAgeMs) {
174
- throw new ArkOrderError('ARKORDER_STALE_SIGMA', 'σ is older than sigmaMaxAgeMs; ξ does not TTL');
175
- }
176
- }
177
- export function fieldEventIdentity(event) {
178
- return deterministicHash(stableSerialize({ kind: event.kind, payload: event.payload ?? null }));
179
- }
180
- function bindResidual(event, xiHash) {
181
- return { event, xiHash, eventId: fieldEventIdentity(event) };
182
- }
183
- const CAPACITY_OP_SET = new Set(CAPACITY_OPS);
184
- function isCapacityOp(value) {
185
- return typeof value === 'string' && CAPACITY_OP_SET.has(value);
186
- }
187
- function numericLeaf(value) {
188
- return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
189
- }
190
- function compareCapacity(left, op, right) {
191
- if (op === 'lte')
192
- return left <= right;
193
- if (op === 'lt')
194
- return left < right;
195
- if (op === 'gte')
196
- return left >= right;
197
- return left > right;
198
- }
199
- function packHasFunction(value) {
200
- if (typeof value === 'function')
201
- return true;
202
- if (value === null || typeof value !== 'object')
203
- return false;
204
- if (Array.isArray(value))
205
- return value.some(packHasFunction);
206
- return Object.values(value).some(packHasFunction);
207
- }
208
- function evaluateCapacity(event, sigma, pack) {
209
- const rows = pack.capacity ?? [];
210
- for (const row of rows) {
211
- if (packHasFunction(row) || !isCapacityOp(row.op))
212
- return 'pack';
213
- if (row.kind !== event.kind)
214
- continue;
215
- const payload = event.payload && typeof event.payload === 'object' && !Array.isArray(event.payload)
216
- ? numericLeaf(event.payload[row.payloadKey])
217
- : undefined;
218
- const limit = numericLeaf(sigma[row.sigmaKey]);
219
- if (payload === undefined || limit === undefined)
220
- return 'pack';
221
- if (!compareCapacity(payload, row.op, limit))
222
- return 'capacity';
223
- }
224
- return 'ok';
225
- }
226
- export function classifyIngest(projection, event, packs = [], xiHash = '', sigma = Object.freeze({})) {
227
- const kind = event.kind;
228
- const bound = bindResidual(event, xiHash);
229
- for (const pack of packs) {
230
- if (packHasFunction(pack.capacity) || packHasFunction(pack.escalateKinds)) {
231
- return {
232
- ...bound,
233
- kind: 'hold',
234
- reasonCode: 'pack',
235
- reason: `pack ${pack.id} is not data-only; user predicates are forbidden`,
236
- };
237
- }
238
- if (pack.escalateKinds?.includes(kind)) {
239
- const target = pack.escalateTarget ?? 'human';
240
- return {
241
- ...bound,
242
- kind: 'escalate_up',
243
- reasonCode: 'pack',
244
- reason: `pack ${pack.id} slaves kind ${JSON.stringify(kind)} to a pattern change`,
245
- target,
246
- };
247
- }
248
- }
249
- if (!projection.allowedKinds.includes(kind)) {
250
- return {
251
- ...bound,
252
- kind: 'escalate_up',
253
- reasonCode: 'not-in-pattern',
254
- reason: `kind ${JSON.stringify(kind)} is not allowed by h(ξ); field cannot rewrite the pattern`,
255
- target: 'human',
256
- };
257
- }
258
- for (const pack of packs) {
259
- const cap = evaluateCapacity(event, sigma, pack);
260
- if (cap === 'ok')
261
- continue;
262
- return {
263
- ...bound,
264
- kind: 'hold',
265
- reasonCode: cap,
266
- reason: cap === 'capacity'
267
- ? `pack ${pack.id} capacity ${JSON.stringify(event.kind)} does not hold`
268
- : `pack ${pack.id} capacity is not numeric data`,
269
- };
270
- }
271
- return { ...bound, kind: 'absorb' };
272
- }
273
- export function blastRadiusOf(previous, next) {
274
- const prev = new Set(previous.allowedKinds);
275
- const nxt = new Set(next.allowedKinds);
276
- const blast = new Set();
277
- for (const kind of prev) {
278
- if (!nxt.has(kind))
279
- blast.add(kind);
280
- }
281
- for (const kind of nxt) {
282
- if (!prev.has(kind))
283
- blast.add(kind);
284
- }
285
- for (const item of next.invalidated)
286
- blast.add(item);
287
- return {
288
- blastRadius: [...blast].sort(),
289
- invalidations: [...next.invalidated].sort(),
290
- };
291
- }
292
- export function proposePatternChange(input) {
293
- const merged = { ...input.current.xi };
294
- for (const [key, value] of Object.entries(input.delta)) {
295
- if (value === undefined) {
296
- delete merged[key];
297
- continue;
298
- }
299
- merged[key] = value;
300
- }
301
- const candidate = createFrozenRelease({
302
- xi: merged,
303
- sigma: { ...input.current.sigma },
304
- version: input.current.version + 1,
305
- now: input.now,
306
- maxXiKeys: input.maxXiKeys,
307
- xiSchema: input.xiSchema,
308
- catalogDigest: input.catalogDigest,
309
- });
310
- if (candidate.hash === input.current.hash) {
311
- throw new ArkOrderError('ARKORDER_EMPTY_BLAST', 'delta does not change ξ; that is not a pattern change');
312
- }
313
- const previous = input.projector(input.current, input.current.sigma);
314
- const next = input.projector(candidate, candidate.sigma);
315
- const { blastRadius, invalidations } = blastRadiusOf(previous, next);
316
- if (blastRadius.length === 0) {
317
- throw new ArkOrderError('ARKORDER_EMPTY_BLAST', 'pattern change has empty blast radius; that key is not an order parameter');
318
- }
319
- return {
320
- nextXi: candidate.xi,
321
- blastRadius,
322
- invalidations,
323
- };
324
- }
325
- /** D1 valve: freeze ProposeResult.nextXi. Empty blast still fails. */
326
- export function applyProposedRelease(input) {
327
- const candidate = createFrozenRelease({
328
- xi: { ...input.proposal.nextXi },
329
- sigma: { ...input.current.sigma },
330
- version: input.current.version + 1,
331
- now: input.now,
332
- maxXiKeys: input.maxXiKeys,
333
- xiSchema: input.xiSchema,
334
- catalogDigest: input.catalogDigest,
335
- });
336
- if (xiRecordsEqual(candidate.xi, input.current.xi)) {
337
- throw new ArkOrderError('ARKORDER_EMPTY_BLAST', 'delta does not change ξ; that is not a pattern change');
338
- }
339
- const previous = input.projector(input.current, input.current.sigma);
340
- const next = input.projector(candidate, candidate.sigma);
341
- const { blastRadius } = blastRadiusOf(previous, next);
342
- if (blastRadius.length === 0) {
343
- throw new ArkOrderError('ARKORDER_EMPTY_BLAST', 'pattern change has empty blast radius; that key is not an order parameter');
344
- }
345
- return candidate;
346
- }