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.
- package/CHANGELOG.md +153 -0
- package/README.md +7 -5
- package/bin/ark-check-runtime.mjs +41 -91
- package/bin/ark-dashboard.mjs +56 -53
- package/bin/ark-layer-match.mjs +707 -121
- package/bin/ark-mcp-runtime.mjs +448 -169
- package/bin/ark-mcp.mjs +68 -4
- package/bin/ark-shared.mjs +42 -22
- package/bin/ark.mjs +65 -97
- package/bin/lib/adapter-contract-types.mjs +5 -0
- package/bin/lib/adapter-contract.mjs +15 -0
- package/bin/lib/adoption-stance.mjs +2 -2
- package/bin/lib/adr-path.mjs +3 -3
- package/bin/lib/agent-gates.mjs +0 -61
- package/bin/lib/agent-homes.mjs +12 -21
- package/bin/lib/agent-projection-command.mjs +9 -5
- package/bin/lib/ambient-state.mjs +9 -30
- package/bin/lib/analysis-engine.mjs +15 -11
- package/bin/lib/architecture-scan.mjs +61 -15
- package/bin/lib/ark-order-doctor.mjs +4 -29
- package/bin/lib/ark-order-facts.mjs +319 -17
- package/bin/lib/ark-order-sensors.mjs +28 -101
- package/bin/lib/ark-order-types.mjs +3 -2
- package/bin/lib/ark-run-doctor.mjs +17 -28
- package/bin/lib/ark-run-facts.mjs +461 -48
- package/bin/lib/ark-run-sensors.mjs +6 -4
- package/bin/lib/arkgate-pins.mjs +91 -0
- package/bin/lib/arkrule-file-hints.mjs +2 -47
- package/bin/lib/arkrules-contract.mjs +124 -133
- package/bin/lib/arkrules-sensors.mjs +55 -252
- package/bin/lib/check-args.mjs +85 -1
- package/bin/lib/ci-and-commands.mjs +24 -78
- package/bin/lib/ci-merge-boundary.mjs +1 -1
- package/bin/lib/class-source-scan.mjs +479 -0
- package/bin/lib/clone-detection.mjs +474 -0
- package/bin/lib/codex-home.mjs +5 -3
- package/bin/lib/config-contract-slices.mjs +739 -0
- package/bin/lib/config-contract.mjs +42 -607
- package/bin/lib/config-extras.mjs +16 -22
- package/bin/lib/config-warnings.mjs +32 -0
- package/bin/lib/contract-smells.mjs +33 -15
- package/bin/lib/core-ratchet.mjs +0 -2
- package/bin/lib/dashboard-cli.mjs +62 -0
- package/bin/lib/deep-module-coach.mjs +2 -4
- package/bin/lib/deploy-path.mjs +13 -38
- package/bin/lib/design-delta.mjs +1 -1
- package/bin/lib/design-smells.mjs +2 -2
- package/bin/lib/diagnostic-catalog.mjs +23 -9
- package/bin/lib/doctor-advisories.mjs +78 -8
- package/bin/lib/doctor-green-cite.mjs +1 -1
- package/bin/lib/doctor-human.mjs +1 -1
- package/bin/lib/doctor-next-actions.mjs +19 -4
- package/bin/lib/doctor-plan.mjs +9 -13
- package/bin/lib/duplication-io.mjs +572 -0
- package/bin/lib/effective-contract-load.mjs +282 -83
- package/bin/lib/enforcement-profiles.mjs +40 -4
- package/bin/lib/entry-points-io.mjs +424 -0
- package/bin/lib/extra-merge-teeth.mjs +36 -0
- package/bin/lib/field-install.mjs +38 -14
- package/bin/lib/first-run-help.mjs +10 -5
- package/bin/lib/flat-parent-importers.mjs +139 -0
- package/bin/lib/flat-parent-pilot.mjs +40 -100
- package/bin/lib/gate-files.mjs +58 -90
- package/bin/lib/git-change-scope.mjs +109 -0
- package/bin/lib/github-enforcement.mjs +166 -29
- package/bin/lib/golden-pattern.mjs +1 -1
- package/bin/lib/hook-templates.mjs +17 -15
- package/bin/lib/host-support-matrix.mjs +3 -1
- package/bin/lib/html-report-advisories.mjs +4 -0
- package/bin/lib/html-report-depth.mjs +2 -2
- package/bin/lib/html-report.mjs +2 -2
- package/bin/lib/import-graph-projection.mjs +112 -0
- package/bin/lib/import-resolve.mjs +13 -1
- package/bin/lib/improvement-compass-doctor.mjs +1 -24
- package/bin/lib/install-migrate.mjs +50 -73
- package/bin/lib/invariant-coverage-io.mjs +4 -3
- package/bin/lib/invariant-coverage.mjs +85 -11
- package/bin/lib/invariant-probe-cli.mjs +347 -0
- package/bin/lib/invariant-probe-io.mjs +165 -0
- package/bin/lib/invariant-probe-runner.mjs +268 -0
- package/bin/lib/invariant-probe-sites.mjs +174 -0
- package/bin/lib/invariant-probe-workspace.mjs +183 -0
- package/bin/lib/invariant-probe.mjs +461 -0
- package/bin/lib/invariant-tests-path.mjs +16 -22
- package/bin/lib/literal-path-drift.mjs +4 -2
- package/bin/lib/managed-upgrade.mjs +2 -8
- package/bin/lib/mcp-adoption.mjs +13 -6
- package/bin/lib/mcp-hook-payload.mjs +403 -63
- package/bin/lib/orphan-modules-io.mjs +458 -0
- package/bin/lib/orphan-modules.mjs +251 -0
- package/bin/lib/outside-importers.mjs +197 -0
- package/bin/lib/package-manager.mjs +60 -2
- package/bin/lib/parse-health.mjs +1 -1
- package/bin/lib/passthrough-commands.mjs +83 -0
- package/bin/lib/persistence-write-hint.mjs +106 -0
- package/bin/lib/pin-dependent-gates.mjs +117 -0
- package/bin/lib/policy-delta-io.mjs +219 -14
- package/bin/lib/post-green-path.mjs +1 -1
- package/bin/lib/prepare-change.mjs +115 -7
- package/bin/lib/prepare-write.mjs +2 -2
- package/bin/lib/presets.mjs +59 -73
- package/bin/lib/product-copy.mjs +1 -6
- package/bin/lib/project-root.mjs +31 -62
- package/bin/lib/remediation.mjs +23 -4
- package/bin/lib/resolved-candidate-facts.mjs +405 -117
- package/bin/lib/rules-inventory.mjs +22 -3
- package/bin/lib/rules-under-contract.mjs +247 -9
- package/bin/lib/scan-files.mjs +36 -4
- package/bin/lib/schema-validation.mjs +130 -0
- package/bin/lib/sensor-promote-cli.mjs +2 -11
- package/bin/lib/sensor-promote-io.mjs +33 -22
- package/bin/lib/sensor-promotion.mjs +1 -1
- package/bin/lib/skill-catalog-apply.mjs +1 -1
- package/bin/lib/skill-install.mjs +42 -38
- package/bin/lib/snippet-analysis.mjs +140 -6
- package/bin/lib/source-policy.mjs +34 -0
- package/bin/lib/start-install-recovery.mjs +72 -2
- package/bin/lib/start-preview.mjs +31 -1
- package/bin/lib/states-transitions-presence.mjs +3 -3
- package/bin/lib/status-command.mjs +35 -22
- package/bin/lib/status-manifest.mjs +50 -30
- package/bin/lib/status-transition-catalog.mjs +0 -6
- package/bin/lib/suggestions.mjs +3 -3
- package/bin/lib/team-parliament-io.mjs +76 -88
- package/bin/lib/team-parliament.mjs +21 -10
- package/bin/lib/upgrade-behavior-changes.mjs +2 -2
- package/bin/lib/upgrade-command.mjs +2 -24
- package/bin/lib/upgrade-outcome.mjs +1 -1
- package/bin/lib/upstream-report.mjs +21 -10
- package/bin/lib/violations.mjs +10 -9
- package/bin/lib/weakest-link.mjs +13 -0
- package/bin/lib/write-path-capabilities.mjs +6 -4
- package/dist/chunk-A5EIY3HI.cjs +5 -0
- package/dist/chunk-AHACEKSV.js +16 -0
- package/dist/chunk-SVSQVGFJ.js +5 -0
- package/dist/chunk-UN5EH4QV.cjs +16 -0
- package/dist/{configTypes-Bbf_MFGe.d.ts → configTypes-GPvvdleM.d.cts} +30 -4
- package/dist/configTypes-GPvvdleM.d.ts +257 -0
- package/dist/eslint/index.cjs +18 -5
- package/dist/eslint/index.d.cts +368 -0
- package/dist/eslint/index.d.ts +139 -156
- package/dist/eslint/index.js +11 -5
- package/dist/index.cjs +36 -35
- package/dist/index.d.cts +5547 -0
- package/dist/index.d.ts +3283 -198
- package/dist/index.js +36 -35
- package/dist/inspectorListen-6DQVJ4DC.js +1 -0
- package/dist/inspectorListen-KGMIQ2TF.cjs +1 -0
- package/dist/nestjs/index.cjs +1 -18
- package/dist/nestjs/index.d.cts +22 -0
- package/dist/nestjs/index.d.ts +1 -3
- package/dist/nestjs/index.js +1 -18
- package/dist/order/index.cjs +1 -1
- package/dist/order/index.d.cts +155 -0
- package/dist/order/index.d.ts +15 -3
- package/dist/order/index.js +1 -1
- package/dist/runtime/index.cjs +13 -30
- package/dist/{diagnosticCatalog-35mrwAHe.d.ts → runtime/index.d.cts} +828 -276
- package/dist/runtime/index.d.ts +2741 -133
- package/dist/runtime/index.js +13 -30
- package/dist/{types-Bdj32QwA.d.ts → types-CnauMQQL.d.cts} +607 -163
- package/dist/types-CnauMQQL.d.ts +1800 -0
- package/docs/README.md +3 -1
- package/docs/agent-guide.md +217 -16
- package/docs/ai-gates.md +174 -26
- package/docs/arkorder.md +32 -15
- package/docs/brownfield-adoption.md +5 -1
- package/docs/configuration.md +230 -31
- package/docs/develop.md +2 -1
- package/docs/diagnostics.md +212 -19
- package/docs/package-surface.md +35 -9
- package/docs/product-voice.md +7 -1
- package/package.json +69 -19
- package/schemas/ark.analysis-result.schema.json +15 -0
- package/schemas/ark.config.schema.json +77 -12
- package/schemas/ark.invariant-probe.schema.json +368 -0
- package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
- package/schemas/ark.status-manifest.schema.json +22 -0
- package/server.json +3 -9
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +5 -1
- package/templates/agent-skills/ark-contract/SKILL.md +17 -0
- package/templates/agent-skills/ark-coverage/SKILL.md +2 -0
- package/templates/agent-skills/ark-explore/SKILL.md +7 -0
- package/templates/agent-skills/ark-fix/SKILL.md +20 -0
- package/templates/agent-skills/ark-order/SKILL.md +4 -2
- package/templates/agent-skills/ark-place/SKILL.md +8 -0
- package/templates/agent-skills/ark-runtime/SKILL.md +6 -1
- package/templates/arkrules/PersistenceAdapters.json +1 -1
- package/templates/arkrules/PresentationAdapters.json +1 -1
- package/templates/skills/ark-adopt.md +5 -1
- package/templates/skills/ark-contract.md +17 -0
- package/templates/skills/ark-coverage.md +2 -0
- package/templates/skills/ark-explore.md +7 -0
- package/templates/skills/ark-fix.md +20 -0
- package/templates/skills/ark-order.md +4 -2
- package/templates/skills/ark-place.md +8 -0
- package/templates/skills/ark-runtime.md +6 -1
- package/bin/lib/ark-order-error.mjs +0 -18
- package/bin/lib/ark-order-invariants.mjs +0 -346
- package/dist/types-Ik_OakcI.d.ts +0 -183
package/docs/arkorder.md
CHANGED
|
@@ -58,8 +58,8 @@ They already exist:
|
|
|
58
58
|
| Escalation as a first-class concept | `IngestResult` residual `absorb \| escalate_up \| hold` (`reasonCode`, `IngestEscalate.target`) | 4.8.0 / 4.8.6 |
|
|
59
59
|
| Projection at the boundary | `Projector = (release, sigma) => Projection` | 4.8.0 |
|
|
60
60
|
| Cap on badly designed slow parameters | `DEFAULT_MAX_XI_KEYS = 7`, `maxXiKeys`, `ARKORDER_TOO_MANY_PARAMS`, `ARKORDER_EMPTY_XI`, `ARKORDER_NESTED_XI` | 4.8.0 |
|
|
61
|
-
| Valved proposals, never direct mutation | `ProposeResult` then `apply` (`ARKORDER_UNVALVED_RELEASE`) | 4.8.0 / 4.8.6 |
|
|
62
|
-
| Typed cell schema | `XiSchema` / `XiPropertySchema` | 4.8.0 |
|
|
61
|
+
| Valved proposals, never direct mutation | `ProposeResult` then `apply` (`ARKORDER_UNVALVED_RELEASE`); a proposal carries `baseXiHash` / `baseVersion`; a stale one, one without that binding, or one whose blast radius is not the transition that would commit fails `ARKORDER_STALE_PROPOSAL`. The binding is data, not a capability: an object that states the current base and the exact transition applies whoever built it | 4.8.0 / 4.8.6 |
|
|
62
|
+
| Typed cell schema | `XiSchema` / `XiPropertySchema` / `XiPrimitive` (finite numbers only; NaN / ±Infinity fail `ARKORDER_NESTED_XI`) | 4.8.0 |
|
|
63
63
|
| Named slow keys on the write path | `arkOrder.xiKeys`; `ARKORDER_XI_FIELD_WRITE` | 4.8.3 |
|
|
64
64
|
| Factory isolation | `createOrderPlane` from `arkgate/order` only | 4.8.0 |
|
|
65
65
|
| Information budget | `informationBudget.cannotObserve`; `ARKORDER_INFORMATION_BUDGET` | 4.8.5 |
|
|
@@ -69,10 +69,10 @@ They already exist:
|
|
|
69
69
|
| Decision tape | ArkRun information package `decisionTape` `{ xiHash, event, residual }` | 4.8.6 |
|
|
70
70
|
| σ vs ξ identity | `xiHash` / `sigmaHash` / `refreshSigma` | 4.8.6 |
|
|
71
71
|
| Capacity as data | `ConstraintPack.capacity` (`kind` / `sigmaKey` / `payloadKey` / `op`) | 4.8.6 |
|
|
72
|
-
| Store port | `ReleaseStore` / `createMemoryReleaseStore` in-memory default — not durable, not K01 | 4.8.6 |
|
|
72
|
+
| Store port | `ReleaseStore` / `createMemoryReleaseStore` in-memory default — not durable, not K01. `load()` output is validated like `restore()`: JSON copies are accepted; hashes are recomputed from ξ / σ and an inconsistent one fails closed; a `releasedAt` (or σ.`releasedAt`) after the plane clock fails closed, so a rewritten timestamp cannot switch off σ staleness. Hashes are a consistency check, not a signature — authenticity stays the store's job. The plane advances only after `save()` returns | 4.8.6 |
|
|
73
73
|
| Thin travel helper | `ingestTravelAction` absorb→`send` / escalate_up human→`raises` | 4.8.6 |
|
|
74
74
|
| Verify a stored `Release.hash` | `hashOf(ξ, σ)` alias of `hashReleasePayload` — no `release()` side effect | 4.8.9 |
|
|
75
|
-
| Reinstall a
|
|
75
|
+
| Reinstall a Release | `restore(release)` — frozen or JSON-deserialized; hash is identity; onto an empty plane, or the same ξ at the current version or the next one. Never changes ξ, never rolls the version back, never jumps it (`ARKORDER_UNVALVED_RELEASE`); a freeze time after the plane clock fails closed. Process-local; not durable; does not close K01 | 4.8.9 |
|
|
76
76
|
| Default clock | omitted `clocks` is Kernel `Date.now()`; Domain must not call `Date.now` | 4.8.9 |
|
|
77
77
|
|
|
78
78
|
Nothing here is a hosted runtime. Nothing here can be “down”. A degraded-mode
|
|
@@ -102,10 +102,10 @@ const plane = createOrderPlane({
|
|
|
102
102
|
plane.release(xi, sigma); // first freeze only
|
|
103
103
|
plane.project(); // derive allowed s + invalidations
|
|
104
104
|
plane.ingest(event); // residual absorb | escalate_up | hold. Never a Release
|
|
105
|
-
plane.proposeRelease(delta); // blast radius. Empty blast = domain error
|
|
106
|
-
plane.apply(proposal); // valve: later ξ change
|
|
105
|
+
plane.proposeRelease(delta); // blast radius, bound to the current Release. Empty blast = domain error
|
|
106
|
+
plane.apply(proposal); // valve: later ξ change; stale proposal = ARKORDER_STALE_PROPOSAL
|
|
107
107
|
plane.refreshSigma(sigma); // saldo / clocks; xiHash unchanged
|
|
108
|
-
plane.restore(release); //
|
|
108
|
+
plane.restore(release); // reinstall (same ξ, current or next version); not durable; not K01
|
|
109
109
|
hashOf(xi, sigma); // same bytes as hashReleasePayload; no freeze side effect
|
|
110
110
|
```
|
|
111
111
|
|
|
@@ -114,6 +114,12 @@ There is no `update()`. Calling `update` / `patch` / `set` on the plane throws
|
|
|
114
114
|
`ARKORDER_GENERIC_UPDATE`. A second `release()` whose ξ differs fails
|
|
115
115
|
`ARKORDER_UNVALVED_RELEASE`.
|
|
116
116
|
|
|
117
|
+
`release`, `proposeRelease`, `apply`, `refreshSigma`, and `restore` check the
|
|
118
|
+
candidate projection against `informationBudget` before the Release is offered
|
|
119
|
+
or persisted (`ARKORDER_INFORMATION_BUDGET`), so a denied pattern never lands.
|
|
120
|
+
σ goes stale after a numeric `σ.freshUntil` on its own, or after
|
|
121
|
+
`sigmaMaxAgeMs` when σ has no `freshUntil`.
|
|
122
|
+
|
|
117
123
|
| Field write | Verb |
|
|
118
124
|
|-------------|------|
|
|
119
125
|
| Invoice, seat within cap, timesheet, daily log | `ingest` → absorb (`ingestTravelAction` may `send`) |
|
|
@@ -209,13 +215,22 @@ except withdrawn heuristics.
|
|
|
209
215
|
|------------|------|
|
|
210
216
|
| `ARKORDER_MISSING_PLANE` | Extra on, no `createOrderPlane` in `planeRoots` |
|
|
211
217
|
| `ARKORDER_KERNEL_IN_DOMAIN` | Domain-role layer imports `arkgate/order` |
|
|
212
|
-
| `ARKORDER_GENERIC_UPDATE` | `update` / `patch` / `set` on the plane |
|
|
218
|
+
| `ARKORDER_GENERIC_UPDATE` | `update` / `patch` / `set` on a plane receiver: bound to `createOrderPlane(...)` in the file, annotated `: OrderPlane`, a `plane` / `*Plane` import from a `planeRoots` module, or a `plane` / `*Plane` name in a file that imports `arkgate/order`. A name alone (`clipPlane`, `controlPlane`) is not evidence |
|
|
213
219
|
| `ARKORDER_TOO_MANY_PARAMS` | ξ keys > `maxXiKeys` |
|
|
214
|
-
| `ARKORDER_INGEST_WRITES_XI` | `ingest` result assigned into a Release / ξ store |
|
|
215
|
-
| `ARKORDER_XI_FIELD_WRITE` | Managed-layer driver import **and** write
|
|
216
|
-
| `ARKORDER_INFORMATION_BUDGET` | `
|
|
217
|
-
| `ARKORDER_XI_TTL` |
|
|
218
|
-
|
|
220
|
+
| `ARKORDER_INGEST_WRITES_XI` | `ingest` result assigned into a Release / ξ holder (`release`, `xi`, `xiNext`, `nextXi`, `currentRelease`, `store.xi`, …); a prefix alone (`releaseState`, `currentResidual`) is not evidence |
|
|
221
|
+
| `ARKORDER_XI_FIELD_WRITE` | Managed-layer driver import **and** a write on a persistence client (`db` / `prisma` / …, `new PrismaClient()`, a name bound to a driver constructor or annotated with a client type, or imported from a local `db` / `orm` / … module) **and** a declared `xiKeys` name |
|
|
222
|
+
| `ARKORDER_INFORMATION_BUDGET` | A literal `allowedKinds` entry that a literal `informationBudget.cannotObserve` denies |
|
|
223
|
+
| `ARKORDER_XI_TTL` | ttl/freshUntil/maxAge (shorthand included) in the ξ literal of `plane.release` / `plane.proposeRelease` on a plane receiver — freshness is σ |
|
|
224
|
+
|
|
225
|
+
Runtime `ArkOrderError` codes (thrown by the plane; not static sensors, not
|
|
226
|
+
promoted by `arkOrder.mode`):
|
|
227
|
+
|
|
228
|
+
| Code | Thrown when |
|
|
229
|
+
|------|-------------|
|
|
230
|
+
| `ARKORDER_STALE_SIGMA` | ingest after σ.freshUntil, or after `release.releasedAt` + `sigmaMaxAgeMs` (ingest returns `hold`, reason `stale-sigma`) |
|
|
231
|
+
| `ARKORDER_STALE_PROPOSAL` | `apply` gets a proposal not computed against the current Release |
|
|
232
|
+
| `ARKORDER_UNVALVED_RELEASE` | a second `release` with a different ξ, or a `restore` that changes ξ / rolls the version back |
|
|
233
|
+
| `ARKORDER_INFORMATION_BUDGET` / `ARKORDER_XI_TTL` | also thrown at runtime, whatever the static sensor saw |
|
|
219
234
|
|
|
220
235
|
Why / fix: [diagnostics.md](diagnostics.md#ARKORDER_MISSING_PLANE).
|
|
221
236
|
`ark-check --sensors` lists every extra sensor with plane, tier, and whether
|
|
@@ -230,8 +245,10 @@ package, inspector, in-memory compare). ArkOrder does not grow a bus, outbox,
|
|
|
230
245
|
or hosted replay. [ADR 0033](adr/0033-arkorder-runtime-half-is-arkrun.md).
|
|
231
246
|
|
|
232
247
|
Durability (`K01`) stays parked. In-memory is the honesty line.
|
|
233
|
-
`restore(release)` reinstalls a
|
|
234
|
-
|
|
248
|
+
`restore(release)` reinstalls a validated Release in this process: onto an
|
|
249
|
+
empty plane, or the same ξ at the current version or the next one. It is
|
|
250
|
+
not a store and not a rollback (that is `proposeRelease` then `apply`). It does
|
|
251
|
+
not close `K01`.
|
|
235
252
|
|
|
236
253
|
---
|
|
237
254
|
|
|
@@ -101,7 +101,11 @@ ark-check --rules-inventory --json
|
|
|
101
101
|
# MCP: ark_rules_inventory
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
Output is **honest counts** (inventoried / under-contract / frozen) — never a score.
|
|
104
|
+
Output is **honest counts** (inventoried / under-contract / frozen) — never a score. A
|
|
105
|
+
structure candidate is under contract when its layer declares a rule on the suggested
|
|
106
|
+
**sensor** (the rule id is yours to name); an invariant candidate by its id. Frozen counts the
|
|
107
|
+
ArkRules-plane keys (`ARKRULE_*` / `INVARIANT_*`) in `.ark-baseline.json`. `--doctor` shows the
|
|
108
|
+
same counts as `rulesMigration` (JSON and human line), and the MCP payload matches the CLI. Route
|
|
105
109
|
extraction through `/ark-fix` or `/ark-loop` (one pilot card at a time) and declaration
|
|
106
110
|
through `/ark-contract` editing `arkrules/<Layer>.json` (ADR 0015 — no new skill names).
|
|
107
111
|
Report residual as **`[Layer]`** (import edges / baseline) vs **`[ArkRules]`** (inventory /
|
package/docs/configuration.md
CHANGED
|
@@ -74,7 +74,9 @@ Invalid ArkGate config (/repo/ark.config.json):
|
|
|
74
74
|
- $.layers[0].forbiddenGlobal: unknown field
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
The same input cannot pass CI while being silently ignored by MCP or ESLint.
|
|
77
|
+
The same input cannot pass CI while being silently ignored by MCP or ESLint. In ESLint an
|
|
78
|
+
invalid contract (root config or a referenced ArkRules file) is one `configInvalid` error per
|
|
79
|
+
linted file with the same validator text; it does not crash the lint run. Invalid JSON, wrong
|
|
78
80
|
types, empty required strings, duplicate string-array entries, negative safety thresholds, and
|
|
79
81
|
unsupported schema versions also fail before scanning begins.
|
|
80
82
|
|
|
@@ -130,9 +132,24 @@ Top-level fields:
|
|
|
130
132
|
`enforced`. Declaring nothing stays silent unless any invariant is already enforced — then
|
|
131
133
|
missing roots fail closed (`INVARIANT_COVERAGE_ROOTS_MISSING`), because otherwise coverage can
|
|
132
134
|
certify a test the project never declared a runner root for.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
|
|
136
|
+
ArkGate never executes tests — except the opt-in `arkgate-check --probe-invariants`, which
|
|
137
|
+
you run yourself: it breaks an invariant's `coverage.symbol` on purpose in a temporary copy
|
|
138
|
+
and runs only its covering tests with your own runner. Its committed result
|
|
139
|
+
(`.ark/invariant-probe.json`) can only **refuse** a promotion, never grant one, and it is
|
|
140
|
+
never part of a check or the merge gate. No config key. See
|
|
141
|
+
[Probe an invariant](agent-guide.md#probe-an-invariant).
|
|
142
|
+
- **`arkRules`** (optional, schema `1.1+`) — map of layer name → one project-relative path, or a
|
|
143
|
+
list of paths merged into that layer's catalog (`"DomainModel": "arkrules/DomainModel.json"`
|
|
144
|
+
or `"DomainModel": ["arkrules/DomainModel.json", "arkrules/DomainModel.shared.json"]`). A
|
|
145
|
+
string keeps today's behaviour. Keys must match a declared layer. The same effective id in
|
|
146
|
+
two files is `ARKRULE_DUPLICATE_ID` and config load fails closed. Missing or invalid
|
|
147
|
+
referenced files **fail closed**. A `arkrules/*.json` file the map does not reference, and a
|
|
148
|
+
slice file named `arkrules.<Layer>.json` beside governed code that discovery did not read,
|
|
149
|
+
is advisory drift: `ARKRULE_FILE_UNREFERENCED` in check `warnings`,
|
|
150
|
+
`rulesUnderContract.unreferencedFiles` in doctor, and `arkRulesCatalog.unreferencedFiles` in
|
|
151
|
+
MCP `ark_manifest`. It never fails the check. Doctor `rulesUnderContract.bySlice` lists the
|
|
152
|
+
rules a child slice contributed. `ark_place` shows those ids as `sliceRules` next to the layer.
|
|
136
153
|
- **`arkRun`** (optional, schema `1.2+`) — inline ArkRun extra (`mode`, `kernelRoots`
|
|
137
154
|
(`compositionRoots` alias), `managedLayers`, `requireDeclarations`). Absence is silent. Unknown keys fail closed.
|
|
138
155
|
`managedLayers` must name existing `layers[].name` values. Empty `compositionRoots` in
|
|
@@ -142,6 +159,19 @@ Top-level fields:
|
|
|
142
159
|
is a policy-delta **weakening**. Enforced extra teeth share the CLI / MCP / hook /
|
|
143
160
|
preflight / CI verdict and arm only when the layer plane is classified (same ≥50%
|
|
144
161
|
governed and ≥1 populated-layer floor as ArkRules).
|
|
162
|
+
Kernel factories in a root: `createArkKernel` / `createStrictArkKernel` /
|
|
163
|
+
`createLenientArkKernel`, their `*FromConfig` variants, or `ArkModule.forRoot()` /
|
|
164
|
+
`forRootAsync()` imported from `arkgate/nestjs`. Undeclared-* sensors count only calls
|
|
165
|
+
whose receiver is traced to the kernel (factory-bound local, import from a root module —
|
|
166
|
+
resolved through tsconfig `paths` and barrels that re-export it — a plain alias
|
|
167
|
+
`const kernel = ark`, a `typeof ark` binding, a binding typed `ArkKernel` / `EventBus` /
|
|
168
|
+
publisher from `arkgate/runtime` or `arkgate/nestjs`, its `.eventBus`, a `publisher(..)`
|
|
169
|
+
result), or whose receiver cannot be traced but whose literal name is a kernel-valid
|
|
170
|
+
intent (`Domain.…`, `Application.…`) — `res.send('ok')` or `require.resolve('pkg')` never
|
|
171
|
+
count. Call names are a string literal or a same-file
|
|
172
|
+
`define(..)` / `defineIntent(..)` creator (or string constant); anything else in
|
|
173
|
+
`enforced` mode reports `ARKRUN_INTERACTION_NAME_INCOMPLETE` (partial). See
|
|
174
|
+
[diagnostics](diagnostics.md#ARKRUN_MISSING_ROOT).
|
|
145
175
|
- **`arkOrder`** (optional, schema `1.3+`) — inline ArkOrder extra (`mode`, `planeRoots`,
|
|
146
176
|
`managedLayers`, `maxXiKeys`, **`xiKeys`**, optional **`appliesTo`**). Absence is silent.
|
|
147
177
|
Unknown keys fail closed. Import `createOrderPlane` from `arkgate/order` (same package).
|
|
@@ -157,7 +187,11 @@ Top-level fields:
|
|
|
157
187
|
at least one glob. Membership ids and recomputable statuses such as `paid` /
|
|
158
188
|
`overdue` are not keys. Factory options `informationBudget`, `sigmaMaxAgeMs`,
|
|
159
189
|
`store` (`ReleaseStore`), and capacity packs belong on `createOrderPlane`, not this
|
|
160
|
-
extra object.
|
|
190
|
+
extra object. Literal evidence of either still reports statically: a ttl/freshUntil/maxAge
|
|
191
|
+
key in a plane `release` / `proposeRelease` ξ literal (`ARKORDER_XI_TTL`), and a literal
|
|
192
|
+
`allowedKinds` entry denied by a literal `cannotObserve` (`ARKORDER_INFORMATION_BUDGET`).
|
|
193
|
+
Later ξ is `proposeRelease` then `apply` (a stale proposal fails
|
|
194
|
+
`ARKORDER_STALE_PROPOSAL`); `refreshSigma`; ingest
|
|
161
195
|
residual `absorb | escalate_up | hold`. Demotion or deletion is a policy-delta
|
|
162
196
|
**weakening**. Field ingest never mints a pattern.
|
|
163
197
|
|
|
@@ -230,6 +264,18 @@ That sentence is product copy. Not “Rich domain model, business rules, and dom
|
|
|
230
264
|
```
|
|
231
265
|
|
|
232
266
|
- `intentPrefixes`, `forbiddenGlobals`, `mayImportInfrastructure`, `optional`
|
|
267
|
+
— `intentPrefixes` is how the ArkRun kernel maps an intent name to a layer at runtime.
|
|
268
|
+
A layer with a canonical 11-layer name (`DomainModel`, `ApplicationOrchestration`,
|
|
269
|
+
`PersistenceAdapters`, … — what `ark init` writes) and no `intentPrefixes` gets the
|
|
270
|
+
built-in prefixes (`Domain.`, `Application.`, `Adapter.Persistence.`, …), so default configs
|
|
271
|
+
enforce hard observed-layer-flow as-is. A custom-named layer in an `allowed: false` rule
|
|
272
|
+
with no `intentPrefixes` cannot map any intent: `createStrictArkKernelFromConfig` /
|
|
273
|
+
`createArkKernelFromConfig` still build (strict default) and record one
|
|
274
|
+
`layer.observedFlowUnresolvable` audit record (`ARKRUN_LAYER_FLOW_UNRESOLVABLE`), and
|
|
275
|
+
`ark doctor` lists those layers when the `arkRun` extra is on. Passing
|
|
276
|
+
`enforceObservedLayerFlow: 'hard'` explicitly throws `ArkKernelConfigError` instead.
|
|
277
|
+
`peerIsolation` rules are file-path slice walls; runtime flow (producer/intent names only)
|
|
278
|
+
does not evaluate them.
|
|
233
279
|
- `reserved` / `allowEmpty` — future houses whose globs match nothing yet. `--strict-config` does not fail; `CONFIG_LAYER_PATTERN_NO_MATCHES` (typo warning) is skipped. A typo warning fires only when the glob is not reserved.
|
|
234
280
|
- `capabilities: { deny: [...] }` — opt-in effect walls over the seven capability ids
|
|
235
281
|
(`network`, `filesystem`, `clock`, `randomness`, `environment`, `process`, `persistence`);
|
|
@@ -256,6 +302,8 @@ Rule fields:
|
|
|
256
302
|
when both paths classify. Applies to **any** declared `from`→`to` pair, not only self-edges.
|
|
257
303
|
Missing paths, empty slice folders, or unclassifiable slices **fail closed** (deny — cannot
|
|
258
304
|
prove same-slice).
|
|
305
|
+
The wall denies imports; status details (`--doctor --all`) list code copied across it
|
|
306
|
+
(`doctor.crossWallDuplication`, advisory, no config key).
|
|
259
307
|
- `sliceFolders` is a bare name or a starred prefix. A bare name (`features`) stays an
|
|
260
308
|
unanchored one-segment match: `src/lib/features/projects/rfi/x.ts` with `["features"]` is
|
|
261
309
|
`features/projects`. The next segment is never a filename, so
|
|
@@ -269,19 +317,43 @@ Rule fields:
|
|
|
269
317
|
off that prefix (`src/app/api/projects/...`) is not that slice.
|
|
270
318
|
- `sliceIdentity` is `path` or `stars`. Absent and `path` are the same: today's ids,
|
|
271
319
|
byte for byte, so baselines and `allowedCrossSlice` keep working with no migration.
|
|
272
|
-
`stars` names a starred prefix as the last literal plus
|
|
320
|
+
`stars` names a starred prefix as the last literal plus every star binding. It
|
|
321
|
+
drops only the literals before the last literal; a star before it is kept, so
|
|
322
|
+
`modules/*/api/*` binds `orders/api/v1` and `users/api/v1`, never one shared `api/v1`.
|
|
323
|
+
arkgate 4.8.23 dropped that star too (`api/v1`). For one release a `sliceAliases`
|
|
324
|
+
`to`, an `allowedCrossSlice` entry, or a `childSlices.allowedCrossSlice` entry written
|
|
325
|
+
against the 4.8.23 id still loads and keeps its 4.8.23 verdict: a legacy alias target
|
|
326
|
+
(`api/v1/x`) joins the universe whose 4.8.23 id it names on each edge, and a legacy
|
|
327
|
+
allowance still clears the edges it cleared. ark-check warns `CONFIG_SLICE_LEGACY_STARS_ID`
|
|
328
|
+
(`failsStrict: false`) and names the new id to write (`orders/api/v1/x`, or one entry
|
|
329
|
+
per module when several modules share that old id). A legacy alias target is rejected at
|
|
330
|
+
config load only when it maps to more than one universe shape of the rule
|
|
331
|
+
(`maps to more than one universe shape`). Rewrite these entries before the next minor
|
|
332
|
+
release, which drops the legacy form.
|
|
273
333
|
`lib/features/*/*` and `lib/repositories/features/*/*` both yield
|
|
274
334
|
`features/projects/rfi`, so one feature has one id across parallel trees. Bare names
|
|
275
335
|
(`features`) are unchanged. Doctor warns when two different prefixes bind as the
|
|
276
336
|
same id, and the warning names both paths (`admin/features/*` and `public/features/*`
|
|
277
337
|
both bind as `features/*`). That warning is advisory. Unrelated trees that share a
|
|
278
338
|
last folder name should stay on `path`.
|
|
279
|
-
- `childSlices` is an optional inner wall on a `peerIsolation` rule.
|
|
339
|
+
- `childSlices` is an optional inner wall on a `peerIsolation` rule. It runs only with
|
|
340
|
+
`peerIsolation: true` and `allowed: false` on the same rule. Elsewhere it is inert: config
|
|
341
|
+
load still accepts it (4.8.23 did), and ark-check warns `CONFIG_CHILD_SLICES_INERT`
|
|
342
|
+
(`failsStrict: false`) with the fix — add both flags to the rule, or remove `childSlices`.
|
|
343
|
+
Absent means today's
|
|
280
344
|
universe wall, byte for byte: no `reasonId`, same messages, same doctor output.
|
|
281
|
-
`sliceFolders` names the children
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
345
|
+
`sliceFolders` names the children. `sliceIdentity` is the same `path` | `stars` choice, and
|
|
346
|
+
the child id must extend the universe id. With `"sliceIdentity": "stars"`, `lib/features/*/*`
|
|
347
|
+
yields child ids like `features/projects/rfi` under the universe id `features/projects`. Under
|
|
348
|
+
the default `path` identity the child id keeps the full prefix (`lib/features/projects/rfi`),
|
|
349
|
+
which does not extend a bare-name universe id such as `features/projects`: the check warns
|
|
350
|
+
`CONFIG_CHILD_SLICE_EXTENDS` and the child wall stays off. Pair a bare-name universe with
|
|
351
|
+
`"sliceIdentity": "stars"` on `childSlices`. A flat file whose child id does not grow
|
|
352
|
+
past the universe id is universe common. So is a `commonFolders` directory (a name such as
|
|
353
|
+
`domain`) **directly under the universe**: `lib/features/projects/domain/**`. A folder of
|
|
354
|
+
that name inside a child (`lib/features/projects/rfi/domain/**`) belongs to that child, so a
|
|
355
|
+
sibling that imports it is `CROSS_SIBLING_SLICE` and the child's own `domain/` may import
|
|
356
|
+
its own feature. A child may import universe common code. Common code may import a child only when
|
|
285
357
|
`parentMayImportChild` is true (default false). `siblings` is `deny` (default, also when the
|
|
286
358
|
key is omitted), `advisory`, or `{ "default": "deny" | "advisory", "enforce": [...] }`.
|
|
287
359
|
The universe wall runs first. A denied cross-universe edge is
|
|
@@ -293,15 +365,43 @@ Rule fields:
|
|
|
293
365
|
(`lib/features/projects/rfi` or `src/lib/features/projects/rfi`). A child id matches that
|
|
294
366
|
importer's child id. A path matches when the importer file sits in that directory.
|
|
295
367
|
Bare names do not match. `*` is not a wildcard on this list. `default: "deny"` denies every
|
|
296
|
-
sibling crossing; the list cannot loosen it.
|
|
297
|
-
baseline
|
|
298
|
-
|
|
368
|
+
sibling crossing; the list cannot loosen it. Advisory is advisory on day 1: with
|
|
369
|
+
`--baseline` (or an `--against` base), the anti-growth ratchet is judged **per rule** and
|
|
370
|
+
only over advisory crossings. `siblings.ratchet` on the object form picks it:
|
|
371
|
+
absent — on only when the baseline in use already records at least one advisory
|
|
372
|
+
crossing of that rule, and then only a crossing past that recorded count fails (an empty
|
|
373
|
+
baseline, or one frozen before the child wall, promotes nothing). The recorded count is
|
|
374
|
+
what the baseline holds for that rule: a key whose crossing was removed still counts when
|
|
375
|
+
the rule still classifies that edge as an advisory sibling crossing, so swapping one
|
|
376
|
+
recorded crossing for a new one keeps the count and stays a warning; `true` — any unrecorded
|
|
377
|
+
advisory crossing fails whenever a baseline is in use (the durable lock); `false` — measure
|
|
378
|
+
only, never fails (doctor `slices.crossSibling` still counts). A recorded enforced crossing
|
|
379
|
+
never switches the advisory ratchet on. A promoted finding says why and names
|
|
380
|
+
`ratchet: false`. `enforce` importers and `default: "deny"` stay errors either way. Doctor
|
|
381
|
+
`productHonesty` applies the same ratchet as `ark-check --baseline`. `ratchet: false` is a
|
|
382
|
+
weakening policy delta and `ratchet: true` a strengthening one. arkgate 4.8.23 and older
|
|
383
|
+
reject `ratchet` (`unknown field` at `$.rules[n].childSlices.siblings.ratchet`).
|
|
384
|
+
`childSlices.message` is the text for inner-wall findings (`CROSS_SIBLING_SLICE` and
|
|
385
|
+
universe common importing a child). The rule `message` stays the universe-wall text
|
|
386
|
+
(`CROSS_PARENT_SLICE` and the fail-closed denies). Without `childSlices.message`, an
|
|
387
|
+
inner-wall finding gets ArkGate's own default text; it never reuses the rule message.
|
|
388
|
+
Every surface uses the same choice: ark-check / CI, the write hook, MCP `validate_code`,
|
|
389
|
+
ESLint, and `analyzeProject` / prepare-change. Editing it is not a policy delta. arkgate
|
|
390
|
+
4.8.23 and older reject it (`unknown field` at `$.rules[n].childSlices.message`). The baseline key stays `ruleId|file|fromLayer|toLayer|target` and does
|
|
299
391
|
not include `reasonId`. `productHonesty.finished` stays false while any child wall is
|
|
300
392
|
advisory, including `{ "default": "advisory", "enforce": [...] }`. The list does not finish
|
|
301
393
|
the house. Doctor `slices` is `{ crossParent, crossSibling, pairs }` for the directed universe
|
|
302
394
|
pairs that occur. A child id that does not extend its universe id warns
|
|
303
395
|
`CONFIG_CHILD_SLICE_EXTENDS` and is treated as universe common. arkgate 4.8.22 and older
|
|
304
|
-
reject `childSlices
|
|
396
|
+
reject `childSlices`. Ark warns `CONFIG_CHILD_SLICES_VERSION` only when the repo pins such
|
|
397
|
+
an older arkgate: an exact `package.json` dependency, the installed `node_modules/arkgate`,
|
|
398
|
+
`package-lock.json`, a `scripts/*hook*` / `.husky` / host hook command, or a CI workflow
|
|
399
|
+
(`pedroknigge/arkgate@vX.Y.Z` or its `version:` input). The finding points at that file and
|
|
400
|
+
line and says which version to bump to. Without such evidence it is silent. Ranges
|
|
401
|
+
(`^4.8.0`) are not evidence; pnpm/yarn lockfiles are read through the installed copy. The
|
|
402
|
+
same check covers `sliceIdentity` (4.8.21), `sharedImportsSlice` (4.8.20),
|
|
403
|
+
`"deny-cross-parent"` (4.8.23), and the keys added after 4.8.23 (`stopAt`,
|
|
404
|
+
`childSlices.message`, `siblings.ratchet`). A build that still types `siblings` as
|
|
305
405
|
the string enum `deny | advisory` rejects the object at config load
|
|
306
406
|
(`must be one of deny, advisory` at `$.rules[n].childSlices.siblings`). It does not ignore
|
|
307
407
|
the object. No published release through 4.8.22 accepts `{ default, enforce }`. Ship that
|
|
@@ -326,10 +426,16 @@ Rule fields:
|
|
|
326
426
|
Ship it only on a release that includes this field.
|
|
327
427
|
`childSlices.sliceAliases` maps files that sit outside every slice folder onto
|
|
328
428
|
a child that already exists: `{ "from": "lib/compliance/**", "to": "features/projects/compliance" }`.
|
|
329
|
-
`
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
429
|
+
A `from` without a wildcard in its last segment is a folder form and also covers
|
|
430
|
+
everything under it, as `sharedRoots` does (`lib/compliance` equals `lib/compliance/**`).
|
|
431
|
+
`to` must match a universe `sliceFolders` shape of the rule, plus one child segment.
|
|
432
|
+
A bare name, a universe id alone, a target outside every universe shape, and a
|
|
433
|
+
wildcard in `to` are rejected at config load (`child of a universe shape`); under
|
|
434
|
+
`"sliceIdentity": "stars"` a target written against the 4.8.23 id loads with a
|
|
435
|
+
`CONFIG_SLICE_LEGACY_STARS_ID` warning (see `sliceIdentity` above). Config
|
|
436
|
+
load cannot see the tree, so it checks only the shape: doctor marks an alias move
|
|
437
|
+
whose target universe no scanned file belongs to (`unknownUniverse`, "check for a
|
|
438
|
+
typo"). The source glob may cover only files the slice folders do not already classify. Overlap with a
|
|
333
439
|
universe `sliceFolders` entry or a child `sliceFolders` entry is rejected
|
|
334
440
|
(`overlaps a slice folder`). Two aliases that can match one file are rejected
|
|
335
441
|
(`two slice aliases match the same file`). An aliased file takes the target
|
|
@@ -337,9 +443,23 @@ Rule fields:
|
|
|
337
443
|
universe is `CROSS_PARENT_SLICE` and an import into another child of the same
|
|
338
444
|
universe is `CROSS_SIBLING_SLICE`. Same-child imports and imports into that
|
|
339
445
|
universe's common folders follow the existing child wall. Doctor lists every
|
|
340
|
-
alias as an owed move: the source glob, the target slice, the destination
|
|
341
|
-
folder, and the files. The section says
|
|
342
|
-
|
|
446
|
+
unpinned alias as an owed move: the source glob, the target slice, the destination
|
|
447
|
+
folder, and the files. The section says those files are not finished.
|
|
448
|
+
`"pinned": true` lists the alias under pinned instead. `reason` (for example
|
|
449
|
+
`"framework-route"`) is the label doctor prints. A reason alone does not pin.
|
|
450
|
+
A pinned route is not `slice-alias-debt` and does not clear any other honesty
|
|
451
|
+
reason, so `productHonesty.finished` stays false while real debt remains.
|
|
452
|
+
`childSlices.arkRulesFile` is a filename at each child root the wall already
|
|
453
|
+
resolved (`"arkrules.<Layer>.json"`). `<Layer>` is the only token. The file is
|
|
454
|
+
law: it enters `policyHash`, and a path added or removed is one
|
|
455
|
+
`arkrules-ref-added` or `arkrules-ref-removed`. The effective id is
|
|
456
|
+
`<childId>#<localId>`. Omitting `appliesTo` scopes the rule to the slice
|
|
457
|
+
directory. An explicit `appliesTo` outside that directory is
|
|
458
|
+
`ARKRULE_SCOPE_ESCAPES_SLICE` and config load fails closed. A filename
|
|
459
|
+
`arkrules.<Layer>.json` is a law path for mixed-law classification. An older
|
|
460
|
+
build rejects the array form, `arkRulesFile`, `pinned`, and `reason` at config
|
|
461
|
+
load. Aliases
|
|
462
|
+
are debt while any unpinned alias remains. `productHonesty.finished` stays false while that debt, or any other honesty reason, remains.
|
|
343
463
|
Adding aliases is a strengthening policy delta (unclassified files come under
|
|
344
464
|
the walls). Removing them is weakening (those files leave the walls) and needs
|
|
345
465
|
the same hash-bound acknowledgement as any other weakening. Additions and
|
|
@@ -353,13 +473,20 @@ Rule fields:
|
|
|
353
473
|
It does not ignore the key. No published release through 4.8.22 accepts
|
|
354
474
|
`sliceAliases`. Ship a config that sets it only on a release that includes
|
|
355
475
|
this field. The write hook, ESLint, snippet analysis, ark-check, and CI all
|
|
356
|
-
see the per-edge decision.
|
|
476
|
+
see the per-edge decision. In ESLint an error-level crossing reports on
|
|
477
|
+
`ark/no-domain-infra-imports` and an advisory (warning) crossing reports on the
|
|
478
|
+
warn-level `ark/architecture-advisory`, because ESLint severity is per rule id
|
|
479
|
+
([ai-gates — ESLint](ai-gates.md#eslint-editor-feedback--bounded-parity-envelope)).
|
|
357
480
|
|
|
358
481
|
Doctor may suggest moving a flat file that sits at universe level when
|
|
359
482
|
exactly one child slice imports it. The card names the file, that child, and
|
|
360
483
|
a destination folder inside the child that keeps the file's layer. It is a
|
|
361
484
|
suggestion. It is not a config field, not a finding, and not an ark-check
|
|
362
|
-
result.
|
|
485
|
+
result. The importers come from the same resolved graph ark-check uses (tsconfig
|
|
486
|
+
path aliases included). A file two children import stays where it is, and so
|
|
487
|
+
does a file that anything outside that one child imports (universe common, a
|
|
488
|
+
shared root, another universe, another layer). A move that the walls would deny
|
|
489
|
+
for any recorded importer is never suggested.
|
|
363
490
|
|
|
364
491
|
```jsonc
|
|
365
492
|
{
|
|
@@ -437,6 +564,30 @@ is evidence:
|
|
|
437
564
|
That pass runs in `ark-check` and CI. The write hook and ESLint see one edge at a time
|
|
438
565
|
and do not block it. arkgate 4.8.22 and older reject the value at config load (`must be
|
|
439
566
|
one of deny`). They do not ignore it and they do not treat it as `"deny"`.
|
|
567
|
+
A universe edge the importer's rule declares in `allowedCrossSlice` (directed) is not
|
|
568
|
+
reported through a shared root either: it is the same dependency the direct wall
|
|
569
|
+
already allows. `childSlices.allowedCrossSlice` still never clears it.
|
|
570
|
+
In a DI app the composition root (bootstrap, registrations) has to sit under
|
|
571
|
+
`sharedRoots`, and every slice that asks the root for a service then "reaches" every
|
|
572
|
+
universe the root registers. Name those files in the object form:
|
|
573
|
+
`"sharedImportsSlice": { "mode": "deny-cross-parent", "stopAt": ["kernel/bootstrap.ts",
|
|
574
|
+
"kernel/registrations/**", "lib/feature-registrations.ts"] }`. The walk never starts at
|
|
575
|
+
or passes through a stop file, so a path through it is not a crossing. A `stopAt` entry
|
|
576
|
+
matches like a shared root (anchored, optional leading `src/` or `app/`, a plain folder
|
|
577
|
+
covers its subtree) or like an alias glob (`kernel/registrations/**` against
|
|
578
|
+
`src/kernel/registrations/x.ts`). Config load rejects an entry that covers the whole
|
|
579
|
+
tree (`*`, `**`, `src`, `src/**`, `app/**`, `*/**`) or a whole layer root of that rule
|
|
580
|
+
(layer `src/lib/**` with `lib/**`), since it would stop every shared node and silence
|
|
581
|
+
the walk: `must not cover the whole tree` / `must not cover a whole layer root of this
|
|
582
|
+
rule`. A stop file must still
|
|
583
|
+
sit under `sharedRoots`; `stopAt` does not classify files and does not silence the direct
|
|
584
|
+
`SHARED_IMPORTS_SLICE` warning. A slice destination that matches a stop is still reported.
|
|
585
|
+
Each finding carries `via` (the shared hops). Doctor `sharedWalkHubs` names a shared file
|
|
586
|
+
that sits on at least half of ten or more such findings — a likely composition root. That
|
|
587
|
+
is advisory, not a finding. Adding stops is a weakening policy delta
|
|
588
|
+
(`shared-walk-stop-added`), removing them strengthens, both together need a judgment. The
|
|
589
|
+
string forms are unchanged. arkgate 4.8.23 and older reject the object at config load
|
|
590
|
+
(`must be one of deny, deny-cross-parent`).
|
|
440
591
|
- `allowedCrossSlice` entries match a full slice id (`features/catalog`) or a bare slice name
|
|
441
592
|
(`catalog`), and only in the direction written. The reverse edge still denies. A bare name
|
|
442
593
|
matches that name under **any** slice folder, so in a repo with several slice parents
|
|
@@ -448,7 +599,10 @@ is evidence:
|
|
|
448
599
|
about your code) versus `unclassifiable path (src/widgets/x.tsx)` (a fact about our evidence).
|
|
449
600
|
`no slice folders` and `no path evidence` are the two remaining evidence reasons. A rule-level
|
|
450
601
|
`message` override no longer hides it: the reason is appended to your text, not replaced by it.
|
|
451
|
-
|
|
602
|
+
The write hook and MCP `validate_code` say the same, and for an evidence reason their fix
|
|
603
|
+
asks you to move the file into a slice or declare its root in `sharedRoots`.
|
|
604
|
+
- Both declarations are **weakening** changes in the policy delta (`arkgate-check --strict-merge`
|
|
605
|
+
with `--policy-base <file>` / `--policy-base-ref <ref>`, or MCP `ark_policy_delta`)
|
|
452
606
|
(`shared-roots-added`, `cross-slice-allowance-added`), so a policy review sees them. Both are
|
|
453
607
|
inert on a rule without `peerIsolation: true`, and policy-delta stays silent about them until
|
|
454
608
|
the wall exists.
|
|
@@ -464,7 +618,8 @@ other choice deliberately, not so slices can drift into a mesh.
|
|
|
464
618
|
runtime coupling. They still appear on the **violations** list with `typeOnly: true`,
|
|
465
619
|
`failsStrict: false`, and adapter diagnostic **severity: warning** so doctor/HTML keep
|
|
466
620
|
`violations.typeOnly` / `typeEdgePolicy` honest — but they **do not** fail merge/exit, library
|
|
467
|
-
`valid`, or preflight the way **value** edges do.
|
|
621
|
+
`valid`, or preflight the way **value** edges do. ESLint reports them as warnings on
|
|
622
|
+
`ark/architecture-advisory`, not as errors. **Exception:** `peerIsolation` slice
|
|
468
623
|
boundaries stay hard even for type-only. A value import of a pure-type barrel is still a value
|
|
469
624
|
edge (not soft-skipped). Prefer placing shared types in a **SharedTypes** (or owning) layer
|
|
470
625
|
both sides may import. Optional starter: [`templates/layers/shared-types.starter.json`](../templates/layers/shared-types.starter.json)
|
|
@@ -526,8 +681,8 @@ Each `arkrules/<Layer>.json` may declare:
|
|
|
526
681
|
|
|
527
682
|
| Section | Purpose | Modes | What it really enforces |
|
|
528
683
|
|---------|---------|--------|-------------------------|
|
|
529
|
-
| `structure[]` | Closed sensor ids (e.g. `orchestration-only`, `thin-adapter`, `writes-via-aggregate`, `aggregate-private-state`, `always-valid-factory`, `domain-event-on-mutation`, `no-anemic-model`) | `advisory` (default) or `enforced` | **Heuristics of module shape** — not proof that logic was extracted to Domain. `writes-via-aggregate` is driver
|
|
530
|
-
| `invariants[]` | Stable ids + description + `coverage` (`test` / `symbol`) + optional `appliesTo` globs | `advisory` or `enforced` | **Named policy + evidence.** `coverage.symbol` means a declaration of that identifier in a non-test file (witness path `symbolEvidenceFile
|
|
684
|
+
| `structure[]` | Closed sensor ids (e.g. `orchestration-only`, `thin-adapter`, `writes-via-aggregate`, `aggregate-private-state`, `always-valid-factory`, `domain-event-on-mutation`, `no-anemic-model`) | `advisory` (default) or `enforced` | **Heuristics of module shape** — not proof that logic was extracted to Domain. `writes-via-aggregate` is a driver import plus a write token, or a SQL write head inside a tagged template, in the declaring layer (ADR 0032). Tier-2 sensors (`no-anemic-model`) stay advisory-only (cannot promote to enforced). Class shapes come from a tokenizer that skips strings, comments, template literals and regex literals; a class it cannot walk to its end is reported as `shape analysed until character N` (an error under an enforced sensor), never passed in silence. |
|
|
685
|
+
| `invariants[]` | Stable ids + description + `coverage` (`test` / `symbol`) + optional `appliesTo` globs | `advisory` or `enforced` | **Named policy + evidence.** `coverage.symbol` means a declaration of that identifier in a non-test file (witness path `symbolEvidenceFile`, project-relative; additive `shape`: `function`, `class`, `const`, `method`, `type`, `interface`, or `enum`). A `Class.member` symbol (e.g. `Order.ensureInvariants`) requires a member with that name inside the body of `class Class`, a `const Class = class { … }` expression, a `namespace Class { … }` (function or const), or a top-level key of a `const Class = { … }` object literal; a member of another container, a nested object key, a free function, or a file that only mentions the class name does not count. The search is not restricted to the invariant's layer. `coverage.test: false` without a `coverage.symbol` declares no evidence: it is advisory-only (an enforced invariant with that shape reports `INVARIANT_UNCOVERED` and fails strict; the rest of the catalog still evaluates) and cannot be promoted. Imports and test calls do not count. A `describe` / `it` / `test` / `context` title may also name the invariant id. A comment, string, or other bare mention does not. Does **not** execute tests or business logic and does **not** replace behavior/property tests. Doctor and `--rules-inventory` print the witness path and discard counts on green runs too. A declaration match is not “the tests pass.” |
|
|
531
686
|
|
|
532
687
|
**Reporting:** diagnostics carry `evidence.arkruleId` + `evidence.arkruleSource`. Label residual
|
|
533
688
|
**`[Layer]`** vs **`[ArkRules]`** in agent output. Doctor / HTML: `rulesUnderContract` (catalog +
|
|
@@ -571,6 +726,14 @@ npx ark-check --strict-config --policy-base ./before.ark.config.json --json
|
|
|
571
726
|
npx ark-check --strict-merge --policy-base-ref origin/main
|
|
572
727
|
```
|
|
573
728
|
|
|
729
|
+
ArkRules are part of the policy. With a Git ref, the base `arkrules/*.json` files are read from
|
|
730
|
+
that same ref (a file the base config references but the ref does not contain yet counts as an
|
|
731
|
+
empty layer). With `--policy-base <file>`, the base ArkRules paths resolve next to that file (keep
|
|
732
|
+
a copy of the base `arkrules/` beside it; the error names that directory and the
|
|
733
|
+
`--policy-base-ref` alternative). A base ArkRules file that cannot be read or parsed fails
|
|
734
|
+
closed, because comparing the candidate catalog with itself would read every demotion or
|
|
735
|
+
deletion as neutral.
|
|
736
|
+
|
|
574
737
|
The additive JSON result includes `policyDelta`: both policy hashes, the overall classification,
|
|
575
738
|
stable findings, and `blockingFindingIds`. Supported comparisons cover governed include/exclude
|
|
576
739
|
roots, layer patterns/exclusions/forbidden globals, deny rules, same-layer peer isolation,
|
|
@@ -627,14 +790,40 @@ type than product source:
|
|
|
627
790
|
|
|
628
791
|
| Check | What it does |
|
|
629
792
|
|-------|----------------|
|
|
630
|
-
| `ark-check --local --base origin/dev` | Opt-in local / multi-worktree cheap check. Same engine as `--changed`. Refused with `--strict-merge`. `ARK_CHECK_LOCAL=1` is the same unless CI
|
|
793
|
+
| `ark-check --local --base origin/dev` | Opt-in local / multi-worktree cheap check. Same engine as `--changed`. Refused with `--strict-merge` and with `--update-baseline`. `ARK_CHECK_LOCAL=1` is the same unless CI, a full-tree report mode, or `--update-baseline` is on. |
|
|
631
794
|
| `ark-check --changed --base origin/dev` | Layer check on touched sources only. A CSS/i18n PR pays almost nothing. |
|
|
632
795
|
| `ark-check --against origin/dev` | New violation keys vs **that ref's** baseline (not only HEAD). |
|
|
633
796
|
| `ark-check --contract-diff --base origin/dev` | Classifies tighten / loosen / reclassify / baseline-grow. |
|
|
634
|
-
| `--contract-session --author <id>` | Law-only PR. Mixed law+product still fails. Loosen/grow need a session even with an empty `stewards[]`; a non-empty list also needs a matching listed author. |
|
|
797
|
+
| `--contract-session --author <id>` | Law-only PR. Mixed law+product still fails. Loosen/grow need a session even with an empty `stewards[]`; a non-empty list also needs a matching listed author. A session always classifies the law change against the team base (`--base`, or the discovered `main`/`dev`), including under `--strict-merge`. If that base has no contract yet (the adoption PR that adds `ark.config.json`), there is nothing to loosen and the session proceeds; an explicit `--policy-base-ref` without a contract at that ref still exits 2. |
|
|
635
798
|
| `--persona touch\|contributor\|agent\|steward` | Budget presets for the same teeth. |
|
|
636
799
|
| `ark status --vs origin/dev` | One line: pin / contract / baseline drift vs that ref. |
|
|
637
800
|
|
|
801
|
+
How the changed-file set is computed (`--changed`, `--local`, `--against`, `--persona`):
|
|
802
|
+
|
|
803
|
+
- Paths are relative to `--root`, even when that root is a package inside a monorepo
|
|
804
|
+
(`git diff --relative`; base-ref files are read as `<ref>:./<path>`). Changes outside the
|
|
805
|
+
root are not part of that package's diff.
|
|
806
|
+
- Committed changes since the merge base, staged, unstaged, and untracked files all count.
|
|
807
|
+
File names with spaces, quotes, or non-ASCII characters are kept exactly.
|
|
808
|
+
- Without `--base` / `--against`, the base is the first of `origin/dev`, `origin/main`,
|
|
809
|
+
`origin/master`, `dev`, `main`, `master` that exists. When none exists (or the root is
|
|
810
|
+
not a git repository), `--changed` and a non-steward `--persona` stop with **exit 2**
|
|
811
|
+
(`reasonId: changed-needs-base`; `--local` uses `local-needs-base`). `--contract-diff`
|
|
812
|
+
and `--persona steward` (which implies it) also stop with **exit 2**
|
|
813
|
+
(`reasonId: contract-diff-needs-base`): the contract and baseline diff has nothing to
|
|
814
|
+
compare against. Pass `--base <ref>` or run a plain full check. A diff that could not be
|
|
815
|
+
computed never counts as "no changes".
|
|
816
|
+
- Any failed git listing (bad ref, timeout, output too large) is exit 2
|
|
817
|
+
(`reasonId: changed-paths-unavailable`), never a pass.
|
|
818
|
+
- `--report` applies the committed `.ark-baseline.json` implicitly, except under
|
|
819
|
+
`--strict` / `--strict-merge` / `--contract-diff` / `--against`: a merge verdict uses a
|
|
820
|
+
baseline only when `--baseline` is passed.
|
|
821
|
+
- `--update-baseline` always freezes the whole tree: it refuses `--changed` / `--local`
|
|
822
|
+
(exit 2) and ignores `ARK_CHECK_LOCAL=1`, so a partial scan can never truncate
|
|
823
|
+
`.ark-baseline.json`.
|
|
824
|
+
- Layer-pattern typo warnings (`CONFIG_LAYER_PATTERN_NO_MATCHES`) under `--changed` are
|
|
825
|
+
judged against the full governed tree, so an untouched layer is not reported as a typo.
|
|
826
|
+
|
|
638
827
|
Write-gate ApplyPatch denies a batch that mixes law files with product source. Humans who
|
|
639
828
|
never hit PreToolUse are unchanged. Local `pnpm` gates should call `--local --base` or
|
|
640
829
|
`--changed --base`, not only full-tree `--strict-merge`. Write hooks stay on the lexical
|
|
@@ -645,11 +834,21 @@ worktree has its own root); there is no machine-wide analysis lock.
|
|
|
645
834
|
include tree. File-local ArkRules sensors (class shape, orchestration-only, thin-adapter,
|
|
646
835
|
writes-via-aggregate) and hint preload stay on the touched set. Layer and cycle sensors
|
|
647
836
|
see that closure, so a new illegal import or a cycle the change can complete still
|
|
648
|
-
fails.
|
|
837
|
+
fails. When a touched file sits under the `sharedRoots` of a `"deny-cross-parent"` rule,
|
|
838
|
+
the graph runs full-tree: the finding belongs to the slice that imports the shared root,
|
|
839
|
+
which may be outside the closure. A `CROSS_PARENT_VIA_SHARED` path counts as part of the
|
|
840
|
+
change when any file on it changed. Untouched files outside the closure are left to the full-tree CI check. Same
|
|
649
841
|
engine; not a second analysis path. A `--changed` pass is not a full-tree structural
|
|
650
842
|
verdict.
|
|
651
843
|
|
|
652
844
|
MCP clients can call `ark_policy_delta` with the previous `baseConfig`, an optional candidate
|
|
653
845
|
contract (the current project contract is the default), and the same optional acknowledgement.
|
|
846
|
+
When `baseConfig` maps `arkRules`, pass the base catalog as data in `baseArkRuleFiles`
|
|
847
|
+
(`{ "<path from baseConfig.arkRules>": <ArkRules file JSON> }`); without it the call is refused
|
|
848
|
+
instead of returning a config-only verdict. When `childSlices.arkRulesFile` is set, include each
|
|
849
|
+
discovered slice file in that same map, keyed by its project-relative path. The candidate catalog is read from disk when the
|
|
850
|
+
candidate is omitted or maps the same `arkRules` files as the project contract (after the loader's
|
|
851
|
+
normalisation, so the project `ark.config.json` passed verbatim counts); otherwise pass
|
|
852
|
+
`candidateArkRuleFiles` in the same shape.
|
|
654
853
|
It invokes the public classifier directly, is read-only, and marks a blocking result as an MCP
|
|
655
854
|
error without maintaining separate adapter policy.
|
package/docs/develop.md
CHANGED
|
@@ -214,7 +214,8 @@ stays plane-free. Empty `planeRoots` in `enforced` mode fails closed
|
|
|
214
214
|
(`ARKORDER_MISSING_PLANE`). In-memory. Not durable. Does not replace ArkRun.
|
|
215
215
|
|
|
216
216
|
First freeze is `release()`. Later pattern change is `proposeRelease` then
|
|
217
|
-
`apply` (`ARKORDER_UNVALVED_RELEASE`)
|
|
217
|
+
`apply` (`ARKORDER_UNVALVED_RELEASE`); `apply` only takes a proposal computed against the
|
|
218
|
+
current Release (`ARKORDER_STALE_PROPOSAL`). `refreshSigma`; ingest residual
|
|
218
219
|
`absorb | escalate_up | hold` + `reasonCode`; capacity pack as data;
|
|
219
220
|
`createMemoryReleaseStore`; `ingestTravelAction`; ArkRun `decisionTape`.
|
|
220
221
|
|