arkgate 4.8.22 → 4.8.24
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 +91 -0
- package/README.md +7 -5
- package/bin/ark-check-runtime.mjs +27 -78
- package/bin/ark-dashboard.mjs +56 -53
- package/bin/ark-layer-match.mjs +1117 -38
- package/bin/ark-mcp-runtime.mjs +412 -138
- package/bin/ark-mcp.mjs +68 -4
- package/bin/ark-shared.mjs +39 -0
- 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/agent-projection-command.mjs +5 -1
- package/bin/lib/analysis-engine.mjs +15 -11
- package/bin/lib/architecture-scan.mjs +50 -6
- package/bin/lib/ark-order-doctor.mjs +1 -1
- package/bin/lib/ark-order-facts.mjs +410 -15
- package/bin/lib/ark-order-invariants.mjs +51 -9
- package/bin/lib/ark-order-sensors.mjs +28 -101
- package/bin/lib/ark-order-types.mjs +2 -0
- package/bin/lib/ark-run-doctor.mjs +14 -0
- package/bin/lib/ark-run-facts.mjs +434 -11
- package/bin/lib/ark-run-sensors.mjs +5 -3
- package/bin/lib/arkgate-pins.mjs +91 -0
- package/bin/lib/arkrules-sensors.mjs +50 -249
- package/bin/lib/check-args.mjs +23 -1
- package/bin/lib/ci-and-commands.mjs +16 -6
- package/bin/lib/class-source-scan.mjs +479 -0
- package/bin/lib/codex-home.mjs +4 -2
- package/bin/lib/config-contract-slices.mjs +701 -0
- package/bin/lib/config-contract.mjs +5 -2
- package/bin/lib/config-warnings.mjs +32 -0
- package/bin/lib/dashboard-cli.mjs +62 -0
- package/bin/lib/design-delta.mjs +1 -1
- package/bin/lib/diagnostic-catalog.mjs +45 -6
- package/bin/lib/doctor-advisories.mjs +84 -1
- package/bin/lib/doctor-next-actions.mjs +17 -2
- package/bin/lib/doctor-plan.mjs +7 -9
- package/bin/lib/effective-contract-load.mjs +121 -59
- package/bin/lib/enforcement-honesty.mjs +22 -0
- package/bin/lib/enforcement-profiles.mjs +40 -4
- package/bin/lib/first-run-help.mjs +3 -2
- package/bin/lib/flat-parent-importers.mjs +137 -0
- package/bin/lib/flat-parent-pilot.mjs +192 -0
- package/bin/lib/gate-files.mjs +29 -2
- package/bin/lib/git-change-scope.mjs +109 -0
- package/bin/lib/github-enforcement.mjs +1 -1
- package/bin/lib/hook-templates.mjs +18 -16
- package/bin/lib/host-support-matrix.mjs +3 -1
- package/bin/lib/html-report-advisories.mjs +2 -0
- package/bin/lib/html-report-depth.mjs +3 -1
- package/bin/lib/import-resolve.mjs +13 -1
- package/bin/lib/install-migrate.mjs +50 -73
- package/bin/lib/invariant-coverage.mjs +59 -11
- package/bin/lib/mcp-adoption.mjs +9 -1
- package/bin/lib/mcp-hook-payload.mjs +401 -61
- package/bin/lib/package-manager.mjs +59 -1
- package/bin/lib/passthrough-commands.mjs +83 -0
- package/bin/lib/persistence-write-hint.mjs +81 -0
- package/bin/lib/physical-cohesion.mjs +16 -18
- package/bin/lib/pilot-loop.mjs +21 -8
- package/bin/lib/pin-dependent-gates.mjs +117 -0
- package/bin/lib/policy-delta-io.mjs +168 -10
- package/bin/lib/prepare-change.mjs +120 -6
- package/bin/lib/prepare-write.mjs +1 -0
- package/bin/lib/presets.mjs +3 -5
- package/bin/lib/remediation.mjs +23 -4
- package/bin/lib/resolved-candidate-facts.mjs +315 -55
- package/bin/lib/rules-inventory.mjs +22 -3
- package/bin/lib/rules-under-contract.mjs +179 -5
- package/bin/lib/scan-files.mjs +32 -0
- package/bin/lib/snippet-analysis.mjs +140 -6
- package/bin/lib/source-policy.mjs +34 -0
- package/bin/lib/stable-hash.mjs +90 -0
- package/bin/lib/start-install-recovery.mjs +71 -1
- package/bin/lib/start-preview.mjs +30 -0
- package/bin/lib/status-command.mjs +33 -4
- package/bin/lib/status-manifest.mjs +32 -4
- package/bin/lib/team-parliament-io.mjs +79 -75
- package/bin/lib/team-parliament.mjs +6 -1
- package/bin/lib/write-path-capabilities.mjs +4 -2
- package/dist/chunk-A5EIY3HI.cjs +5 -0
- package/dist/chunk-MBXCFLO4.js +16 -0
- package/dist/chunk-RNQHRDCQ.cjs +16 -0
- package/dist/chunk-SVSQVGFJ.js +5 -0
- package/dist/{configTypes-CTELo87-.d.ts → configTypes-BJbaeoj4.d.cts} +53 -4
- package/dist/configTypes-BJbaeoj4.d.ts +246 -0
- package/dist/eslint/index.cjs +17 -4
- package/dist/eslint/index.d.cts +359 -0
- package/dist/eslint/index.d.ts +178 -158
- package/dist/eslint/index.js +10 -4
- package/dist/index.cjs +38 -37
- package/dist/index.d.cts +5475 -0
- package/dist/index.d.ts +3408 -296
- package/dist/index.js +38 -37
- 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-WD2TPSwZ.d.ts → runtime/index.d.cts} +814 -290
- package/dist/runtime/index.d.ts +2678 -108
- package/dist/runtime/index.js +13 -30
- package/dist/{types-yDNEQ603.d.ts → types-DRinG-vT.d.cts} +576 -140
- package/dist/types-DRinG-vT.d.ts +1792 -0
- package/docs/README.md +3 -1
- package/docs/agent-guide.md +92 -15
- package/docs/ai-gates.md +160 -26
- package/docs/arkorder.md +32 -15
- package/docs/brownfield-adoption.md +5 -1
- package/docs/configuration.md +273 -12
- package/docs/develop.md +2 -1
- package/docs/diagnostics.md +122 -13
- package/docs/package-surface.md +31 -8
- package/docs/product-voice.md +1 -1
- package/package.json +59 -18
- package/schemas/ark.analysis-result.schema.json +15 -0
- package/schemas/ark.config.schema.json +163 -4
- 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 +4 -1
- package/templates/agent-skills/ark-order/SKILL.md +4 -2
- 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 +4 -1
- package/templates/skills/ark-order.md +4 -2
- package/templates/skills/ark-runtime.md +6 -1
- package/dist/types-fQ9-KrBj.d.ts +0 -183
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
|
|
|
@@ -132,7 +134,10 @@ Top-level fields:
|
|
|
132
134
|
certify a test the project never declared a runner root for.
|
|
133
135
|
- **`arkRules`** (optional, schema `1.1+`) — map of layer name → project-relative path to an
|
|
134
136
|
ArkRules file (e.g. `"DomainModel": "arkrules/DomainModel.json"`). Keys must match a declared
|
|
135
|
-
layer. Missing/invalid referenced files **fail closed**.
|
|
137
|
+
layer. Missing/invalid referenced files **fail closed**. A `arkrules/*.json` file the map
|
|
138
|
+
does not reference (or any such file when the map is absent) is advisory drift:
|
|
139
|
+
`ARKRULE_FILE_UNREFERENCED` in check `warnings`, `rulesUnderContract.unreferencedFiles` in
|
|
140
|
+
doctor, and `arkRulesCatalog.unreferencedFiles` in MCP `ark_manifest`. It never fails the check.
|
|
136
141
|
- **`arkRun`** (optional, schema `1.2+`) — inline ArkRun extra (`mode`, `kernelRoots`
|
|
137
142
|
(`compositionRoots` alias), `managedLayers`, `requireDeclarations`). Absence is silent. Unknown keys fail closed.
|
|
138
143
|
`managedLayers` must name existing `layers[].name` values. Empty `compositionRoots` in
|
|
@@ -142,6 +147,19 @@ Top-level fields:
|
|
|
142
147
|
is a policy-delta **weakening**. Enforced extra teeth share the CLI / MCP / hook /
|
|
143
148
|
preflight / CI verdict and arm only when the layer plane is classified (same ≥50%
|
|
144
149
|
governed and ≥1 populated-layer floor as ArkRules).
|
|
150
|
+
Kernel factories in a root: `createArkKernel` / `createStrictArkKernel` /
|
|
151
|
+
`createLenientArkKernel`, their `*FromConfig` variants, or `ArkModule.forRoot()` /
|
|
152
|
+
`forRootAsync()` imported from `arkgate/nestjs`. Undeclared-* sensors count only calls
|
|
153
|
+
whose receiver is traced to the kernel (factory-bound local, import from a root module —
|
|
154
|
+
resolved through tsconfig `paths` and barrels that re-export it — a plain alias
|
|
155
|
+
`const kernel = ark`, a `typeof ark` binding, a binding typed `ArkKernel` / `EventBus` /
|
|
156
|
+
publisher from `arkgate/runtime` or `arkgate/nestjs`, its `.eventBus`, a `publisher(..)`
|
|
157
|
+
result), or whose receiver cannot be traced but whose literal name is a kernel-valid
|
|
158
|
+
intent (`Domain.…`, `Application.…`) — `res.send('ok')` or `require.resolve('pkg')` never
|
|
159
|
+
count. Call names are a string literal or a same-file
|
|
160
|
+
`define(..)` / `defineIntent(..)` creator (or string constant); anything else in
|
|
161
|
+
`enforced` mode reports `ARKRUN_INTERACTION_NAME_INCOMPLETE` (partial). See
|
|
162
|
+
[diagnostics](diagnostics.md#ARKRUN_MISSING_ROOT).
|
|
145
163
|
- **`arkOrder`** (optional, schema `1.3+`) — inline ArkOrder extra (`mode`, `planeRoots`,
|
|
146
164
|
`managedLayers`, `maxXiKeys`, **`xiKeys`**, optional **`appliesTo`**). Absence is silent.
|
|
147
165
|
Unknown keys fail closed. Import `createOrderPlane` from `arkgate/order` (same package).
|
|
@@ -157,7 +175,11 @@ Top-level fields:
|
|
|
157
175
|
at least one glob. Membership ids and recomputable statuses such as `paid` /
|
|
158
176
|
`overdue` are not keys. Factory options `informationBudget`, `sigmaMaxAgeMs`,
|
|
159
177
|
`store` (`ReleaseStore`), and capacity packs belong on `createOrderPlane`, not this
|
|
160
|
-
extra object.
|
|
178
|
+
extra object. Literal evidence of either still reports statically: a ttl/freshUntil/maxAge
|
|
179
|
+
key in a plane `release` / `proposeRelease` ξ literal (`ARKORDER_XI_TTL`), and a literal
|
|
180
|
+
`allowedKinds` entry denied by a literal `cannotObserve` (`ARKORDER_INFORMATION_BUDGET`).
|
|
181
|
+
Later ξ is `proposeRelease` then `apply` (a stale proposal fails
|
|
182
|
+
`ARKORDER_STALE_PROPOSAL`); `refreshSigma`; ingest
|
|
161
183
|
residual `absorb | escalate_up | hold`. Demotion or deletion is a policy-delta
|
|
162
184
|
**weakening**. Field ingest never mints a pattern.
|
|
163
185
|
|
|
@@ -230,6 +252,18 @@ That sentence is product copy. Not “Rich domain model, business rules, and dom
|
|
|
230
252
|
```
|
|
231
253
|
|
|
232
254
|
- `intentPrefixes`, `forbiddenGlobals`, `mayImportInfrastructure`, `optional`
|
|
255
|
+
— `intentPrefixes` is how the ArkRun kernel maps an intent name to a layer at runtime.
|
|
256
|
+
A layer with a canonical 11-layer name (`DomainModel`, `ApplicationOrchestration`,
|
|
257
|
+
`PersistenceAdapters`, … — what `ark init` writes) and no `intentPrefixes` gets the
|
|
258
|
+
built-in prefixes (`Domain.`, `Application.`, `Adapter.Persistence.`, …), so default configs
|
|
259
|
+
enforce hard observed-layer-flow as-is. A custom-named layer in an `allowed: false` rule
|
|
260
|
+
with no `intentPrefixes` cannot map any intent: `createStrictArkKernelFromConfig` /
|
|
261
|
+
`createArkKernelFromConfig` still build (strict default) and record one
|
|
262
|
+
`layer.observedFlowUnresolvable` audit record (`ARKRUN_LAYER_FLOW_UNRESOLVABLE`), and
|
|
263
|
+
`ark doctor` lists those layers when the `arkRun` extra is on. Passing
|
|
264
|
+
`enforceObservedLayerFlow: 'hard'` explicitly throws `ArkKernelConfigError` instead.
|
|
265
|
+
`peerIsolation` rules are file-path slice walls; runtime flow (producer/intent names only)
|
|
266
|
+
does not evaluate them.
|
|
233
267
|
- `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
268
|
- `capabilities: { deny: [...] }` — opt-in effect walls over the seven capability ids
|
|
235
269
|
(`network`, `filesystem`, `clock`, `randomness`, `environment`, `process`, `persistence`);
|
|
@@ -251,7 +285,7 @@ process module-capability family must be denied.
|
|
|
251
285
|
Rule fields:
|
|
252
286
|
|
|
253
287
|
- `from`, `to`, `allowed`, `message`, `peerIsolation`, `sliceFolders`, `sliceIdentity`,
|
|
254
|
-
`sharedRoots`, `allowedCrossSlice`, `sharedImportsSlice`
|
|
288
|
+
`sharedRoots`, `allowedCrossSlice`, `sharedImportsSlice`, `childSlices`
|
|
255
289
|
- `peerIsolation: true` + `allowed: false`: deny only when slice ids differ; same-slice allows
|
|
256
290
|
when both paths classify. Applies to **any** declared `from`→`to` pair, not only self-edges.
|
|
257
291
|
Missing paths, empty slice folders, or unclassifiable slices **fail closed** (deny — cannot
|
|
@@ -269,13 +303,162 @@ Rule fields:
|
|
|
269
303
|
off that prefix (`src/app/api/projects/...`) is not that slice.
|
|
270
304
|
- `sliceIdentity` is `path` or `stars`. Absent and `path` are the same: today's ids,
|
|
271
305
|
byte for byte, so baselines and `allowedCrossSlice` keep working with no migration.
|
|
272
|
-
`stars` names a starred prefix as the last literal plus
|
|
306
|
+
`stars` names a starred prefix as the last literal plus every star binding. It
|
|
307
|
+
drops only the literals before the last literal; a star before it is kept, so
|
|
308
|
+
`modules/*/api/*` binds `orders/api/v1` and `users/api/v1`, never one shared `api/v1`.
|
|
309
|
+
arkgate 4.8.23 dropped that star too (`api/v1`). For one release a `sliceAliases`
|
|
310
|
+
`to`, an `allowedCrossSlice` entry, or a `childSlices.allowedCrossSlice` entry written
|
|
311
|
+
against the 4.8.23 id still loads and keeps its 4.8.23 verdict: a legacy alias target
|
|
312
|
+
(`api/v1/x`) joins the universe whose 4.8.23 id it names on each edge, and a legacy
|
|
313
|
+
allowance still clears the edges it cleared. ark-check warns `CONFIG_SLICE_LEGACY_STARS_ID`
|
|
314
|
+
(`failsStrict: false`) and names the new id to write (`orders/api/v1/x`, or one entry
|
|
315
|
+
per module when several modules share that old id). A legacy alias target is rejected at
|
|
316
|
+
config load only when it maps to more than one universe shape of the rule
|
|
317
|
+
(`maps to more than one universe shape`). Rewrite these entries before the next minor
|
|
318
|
+
release, which drops the legacy form.
|
|
273
319
|
`lib/features/*/*` and `lib/repositories/features/*/*` both yield
|
|
274
320
|
`features/projects/rfi`, so one feature has one id across parallel trees. Bare names
|
|
275
321
|
(`features`) are unchanged. Doctor warns when two different prefixes bind as the
|
|
276
322
|
same id, and the warning names both paths (`admin/features/*` and `public/features/*`
|
|
277
323
|
both bind as `features/*`). That warning is advisory. Unrelated trees that share a
|
|
278
324
|
last folder name should stay on `path`.
|
|
325
|
+
- `childSlices` is an optional inner wall on a `peerIsolation` rule. It runs only with
|
|
326
|
+
`peerIsolation: true` and `allowed: false` on the same rule. Elsewhere it is inert: config
|
|
327
|
+
load still accepts it (4.8.23 did), and ark-check warns `CONFIG_CHILD_SLICES_INERT`
|
|
328
|
+
(`failsStrict: false`) with the fix — add both flags to the rule, or remove `childSlices`.
|
|
329
|
+
Absent means today's
|
|
330
|
+
universe wall, byte for byte: no `reasonId`, same messages, same doctor output.
|
|
331
|
+
`sliceFolders` names the children. `sliceIdentity` is the same `path` | `stars` choice, and
|
|
332
|
+
the child id must extend the universe id. With `"sliceIdentity": "stars"`, `lib/features/*/*`
|
|
333
|
+
yields child ids like `features/projects/rfi` under the universe id `features/projects`. Under
|
|
334
|
+
the default `path` identity the child id keeps the full prefix (`lib/features/projects/rfi`),
|
|
335
|
+
which does not extend a bare-name universe id such as `features/projects`: the check warns
|
|
336
|
+
`CONFIG_CHILD_SLICE_EXTENDS` and the child wall stays off. Pair a bare-name universe with
|
|
337
|
+
`"sliceIdentity": "stars"` on `childSlices`. A flat file whose child id does not grow
|
|
338
|
+
past the universe id is universe common. So is a `commonFolders` directory (a name such as
|
|
339
|
+
`domain`) **directly under the universe**: `lib/features/projects/domain/**`. A folder of
|
|
340
|
+
that name inside a child (`lib/features/projects/rfi/domain/**`) belongs to that child, so a
|
|
341
|
+
sibling that imports it is `CROSS_SIBLING_SLICE` and the child's own `domain/` may import
|
|
342
|
+
its own feature. A child may import universe common code. Common code may import a child only when
|
|
343
|
+
`parentMayImportChild` is true (default false). `siblings` is `deny` (default, also when the
|
|
344
|
+
key is omitted), `advisory`, or `{ "default": "deny" | "advisory", "enforce": [...] }`.
|
|
345
|
+
The universe wall runs first. A denied cross-universe edge is
|
|
346
|
+
`reasonId` `CROSS_PARENT_SLICE` and is never advisory. A denied sibling edge is
|
|
347
|
+
`CROSS_SIBLING_SLICE`: an error when `siblings` is `deny` or `{ "default": "deny" }`,
|
|
348
|
+
a warning (`failsStrict: false`) when `advisory`. With `{ "default": "advisory", "enforce" }`,
|
|
349
|
+
a crossing whose importer is listed is an error. Every other sibling crossing stays a warning.
|
|
350
|
+
`enforce` entries are child slice ids (`features/projects/rfi`) or subtree paths
|
|
351
|
+
(`lib/features/projects/rfi` or `src/lib/features/projects/rfi`). A child id matches that
|
|
352
|
+
importer's child id. A path matches when the importer file sits in that directory.
|
|
353
|
+
Bare names do not match. `*` is not a wildcard on this list. `default: "deny"` denies every
|
|
354
|
+
sibling crossing; the list cannot loosen it. Advisory is advisory on day 1: with
|
|
355
|
+
`--baseline` (or an `--against` base), the anti-growth ratchet is judged **per rule** and
|
|
356
|
+
only over advisory crossings. `siblings.ratchet` on the object form picks it:
|
|
357
|
+
absent — on only when the baseline in use already records at least one advisory
|
|
358
|
+
crossing of that rule, and then only a crossing past that recorded count fails (an empty
|
|
359
|
+
baseline, or one frozen before the child wall, promotes nothing). The recorded count is
|
|
360
|
+
what the baseline holds for that rule: a key whose crossing was removed still counts when
|
|
361
|
+
the rule still classifies that edge as an advisory sibling crossing, so swapping one
|
|
362
|
+
recorded crossing for a new one keeps the count and stays a warning; `true` — any unrecorded
|
|
363
|
+
advisory crossing fails whenever a baseline is in use (the durable lock); `false` — measure
|
|
364
|
+
only, never fails (doctor `slices.crossSibling` still counts). A recorded enforced crossing
|
|
365
|
+
never switches the advisory ratchet on. A promoted finding says why and names
|
|
366
|
+
`ratchet: false`. `enforce` importers and `default: "deny"` stay errors either way. Doctor
|
|
367
|
+
`productHonesty` applies the same ratchet as `ark-check --baseline`. `ratchet: false` is a
|
|
368
|
+
weakening policy delta and `ratchet: true` a strengthening one. arkgate 4.8.23 and older
|
|
369
|
+
reject `ratchet` (`unknown field` at `$.rules[n].childSlices.siblings.ratchet`).
|
|
370
|
+
`childSlices.message` is the text for inner-wall findings (`CROSS_SIBLING_SLICE` and
|
|
371
|
+
universe common importing a child). The rule `message` stays the universe-wall text
|
|
372
|
+
(`CROSS_PARENT_SLICE` and the fail-closed denies). Without `childSlices.message`, an
|
|
373
|
+
inner-wall finding gets ArkGate's own default text; it never reuses the rule message.
|
|
374
|
+
Every surface uses the same choice: ark-check / CI, the write hook, MCP `validate_code`,
|
|
375
|
+
ESLint, and `analyzeProject` / prepare-change. Editing it is not a policy delta. arkgate
|
|
376
|
+
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
|
|
377
|
+
not include `reasonId`. `productHonesty.finished` stays false while any child wall is
|
|
378
|
+
advisory, including `{ "default": "advisory", "enforce": [...] }`. The list does not finish
|
|
379
|
+
the house. Doctor `slices` is `{ crossParent, crossSibling, pairs }` for the directed universe
|
|
380
|
+
pairs that occur. A child id that does not extend its universe id warns
|
|
381
|
+
`CONFIG_CHILD_SLICE_EXTENDS` and is treated as universe common. arkgate 4.8.22 and older
|
|
382
|
+
reject `childSlices`. Ark warns `CONFIG_CHILD_SLICES_VERSION` only when the repo pins such
|
|
383
|
+
an older arkgate: an exact `package.json` dependency, the installed `node_modules/arkgate`,
|
|
384
|
+
`package-lock.json`, a `scripts/*hook*` / `.husky` / host hook command, or a CI workflow
|
|
385
|
+
(`pedroknigge/arkgate@vX.Y.Z` or its `version:` input). The finding points at that file and
|
|
386
|
+
line and says which version to bump to. Without such evidence it is silent. Ranges
|
|
387
|
+
(`^4.8.0`) are not evidence; pnpm/yarn lockfiles are read through the installed copy. The
|
|
388
|
+
same check covers `sliceIdentity` (4.8.21), `sharedImportsSlice` (4.8.20),
|
|
389
|
+
`"deny-cross-parent"` (4.8.23), and the keys added after 4.8.23 (`stopAt`,
|
|
390
|
+
`childSlices.message`, `siblings.ratchet`). A build that still types `siblings` as
|
|
391
|
+
the string enum `deny | advisory` rejects the object at config load
|
|
392
|
+
(`must be one of deny, advisory` at `$.rules[n].childSlices.siblings`). It does not ignore
|
|
393
|
+
the object. No published release through 4.8.22 accepts `{ default, enforce }`. Ship that
|
|
394
|
+
form only on a release that includes it.
|
|
395
|
+
`childSlices.allowedCrossSlice` is a directed list on the child block only:
|
|
396
|
+
`{ "from": "features/projects/*", "to": "features/projects/d2d-item" }`.
|
|
397
|
+
`*` matches one whole path segment. `**` and a partial segment (`feat*`) are
|
|
398
|
+
rejected at config load. A bare name (no `/`) is rejected, because it is
|
|
399
|
+
ambiguous across universes. The list clears only a `CROSS_SIBLING_SLICE`,
|
|
400
|
+
and only after the universe wall has allowed the edge. It never clears
|
|
401
|
+
`CROSS_PARENT_SLICE` or `CROSS_PARENT_VIA_SHARED`. A pattern that can name
|
|
402
|
+
two universes (`features/*/*` to `features/management/eos`) leaves the
|
|
403
|
+
cross-parent finding in place and warns `CONFIG_CHILD_SLICE_CROSS_UNIVERSE`
|
|
404
|
+
(`failsStrict: false`). The universe `allowedCrossSlice` does not read this
|
|
405
|
+
list and still treats `*` as a literal slice id, byte for byte with 4.8.22.
|
|
406
|
+
Adding an entry is a weakening policy delta. Removing one is strengthening.
|
|
407
|
+
Additions and removals together need a judgment. A wildcard that widens a
|
|
408
|
+
literal entry is weakening and needs the same acknowledgement. A build that
|
|
409
|
+
accepts `childSlices` and still rejects unknown fields fails at config load
|
|
410
|
+
(`unknown field` at `$.rules[n].childSlices.allowedCrossSlice`). It does not
|
|
411
|
+
ignore the key. No published release through 4.8.22 accepts the wildcard.
|
|
412
|
+
Ship it only on a release that includes this field.
|
|
413
|
+
`childSlices.sliceAliases` maps files that sit outside every slice folder onto
|
|
414
|
+
a child that already exists: `{ "from": "lib/compliance/**", "to": "features/projects/compliance" }`.
|
|
415
|
+
A `from` without a wildcard in its last segment is a folder form and also covers
|
|
416
|
+
everything under it, as `sharedRoots` does (`lib/compliance` equals `lib/compliance/**`).
|
|
417
|
+
`to` must match a universe `sliceFolders` shape of the rule, plus one child segment.
|
|
418
|
+
A bare name, a universe id alone, a target outside every universe shape, and a
|
|
419
|
+
wildcard in `to` are rejected at config load (`child of a universe shape`); under
|
|
420
|
+
`"sliceIdentity": "stars"` a target written against the 4.8.23 id loads with a
|
|
421
|
+
`CONFIG_SLICE_LEGACY_STARS_ID` warning (see `sliceIdentity` above). Config
|
|
422
|
+
load cannot see the tree, so it checks only the shape: doctor marks an alias move
|
|
423
|
+
whose target universe no scanned file belongs to (`unknownUniverse`, "check for a
|
|
424
|
+
typo"). The source glob may cover only files the slice folders do not already classify. Overlap with a
|
|
425
|
+
universe `sliceFolders` entry or a child `sliceFolders` entry is rejected
|
|
426
|
+
(`overlaps a slice folder`). Two aliases that can match one file are rejected
|
|
427
|
+
(`two slice aliases match the same file`). An aliased file takes the target
|
|
428
|
+
universe id and the target child id for both walls, so an import into another
|
|
429
|
+
universe is `CROSS_PARENT_SLICE` and an import into another child of the same
|
|
430
|
+
universe is `CROSS_SIBLING_SLICE`. Same-child imports and imports into that
|
|
431
|
+
universe's common folders follow the existing child wall. Doctor lists every
|
|
432
|
+
alias as an owed move: the source glob, the target slice, the destination
|
|
433
|
+
folder, and the files. The section says the files are not finished. Aliases
|
|
434
|
+
are debt. `productHonesty.finished` stays false while any alias is set.
|
|
435
|
+
Adding aliases is a strengthening policy delta (unclassified files come under
|
|
436
|
+
the walls). Removing them is weakening (those files leave the walls) and needs
|
|
437
|
+
the same hash-bound acknowledgement as any other weakening. Additions and
|
|
438
|
+
removals in one change, including a retarget, are judgment-required. A folder
|
|
439
|
+
edit already asks for a judgment and covers the alias list. The baseline key
|
|
440
|
+
stays `ruleId|file|fromLayer|toLayer|target` and does not include `reasonId`.
|
|
441
|
+
The write hook, ESLint, snippet analysis, ark-check, and CI all see the alias
|
|
442
|
+
through one resolver. There is no skip flag. arkgate 4.8.22 and older reject
|
|
443
|
+
`childSlices`. A build that accepts `childSlices` and still rejects unknown
|
|
444
|
+
fields fails at config load (`unknown field` at `$.rules[n].childSlices.sliceAliases`).
|
|
445
|
+
It does not ignore the key. No published release through 4.8.22 accepts
|
|
446
|
+
`sliceAliases`. Ship a config that sets it only on a release that includes
|
|
447
|
+
this field. The write hook, ESLint, snippet analysis, ark-check, and CI all
|
|
448
|
+
see the per-edge decision. In ESLint an error-level crossing reports on
|
|
449
|
+
`ark/no-domain-infra-imports` and an advisory (warning) crossing reports on the
|
|
450
|
+
warn-level `ark/architecture-advisory`, because ESLint severity is per rule id
|
|
451
|
+
([ai-gates — ESLint](ai-gates.md#eslint-editor-feedback--bounded-parity-envelope)).
|
|
452
|
+
|
|
453
|
+
Doctor may suggest moving a flat file that sits at universe level when
|
|
454
|
+
exactly one child slice imports it. The card names the file, that child, and
|
|
455
|
+
a destination folder inside the child that keeps the file's layer. It is a
|
|
456
|
+
suggestion. It is not a config field, not a finding, and not an ark-check
|
|
457
|
+
result. The importers come from the same resolved graph ark-check uses (tsconfig
|
|
458
|
+
path aliases included). A file two children import stays where it is, and so
|
|
459
|
+
does a file that anything outside that one child imports (universe common, a
|
|
460
|
+
shared root, another universe, another layer). A move that the walls would deny
|
|
461
|
+
for any recorded importer is never suggested.
|
|
279
462
|
|
|
280
463
|
```jsonc
|
|
281
464
|
{
|
|
@@ -346,6 +529,37 @@ is evidence:
|
|
|
346
529
|
to `"deny"`. That hop is direct only — `allowedCrossSlice` does not excuse it. A green
|
|
347
530
|
`ark-check` prints one line per layer edge (the count and three examples) and says the
|
|
348
531
|
wall is direct-only. Every edge stays in `ark-check --json` and `ark-check --doctor`.
|
|
532
|
+
`"deny-cross-parent"` does not change that direct hop: it stays a `SHARED_IMPORTS_SLICE`
|
|
533
|
+
warning. It adds a whole-graph finding when a slice reaches a **different** universe
|
|
534
|
+
only through a shared root (`reasonId` `CROSS_PARENT_VIA_SHARED` on
|
|
535
|
+
`LAYER_IMPORT_VIOLATION`). A shared hop that stays inside one universe is not a finding.
|
|
536
|
+
That pass runs in `ark-check` and CI. The write hook and ESLint see one edge at a time
|
|
537
|
+
and do not block it. arkgate 4.8.22 and older reject the value at config load (`must be
|
|
538
|
+
one of deny`). They do not ignore it and they do not treat it as `"deny"`.
|
|
539
|
+
A universe edge the importer's rule declares in `allowedCrossSlice` (directed) is not
|
|
540
|
+
reported through a shared root either: it is the same dependency the direct wall
|
|
541
|
+
already allows. `childSlices.allowedCrossSlice` still never clears it.
|
|
542
|
+
In a DI app the composition root (bootstrap, registrations) has to sit under
|
|
543
|
+
`sharedRoots`, and every slice that asks the root for a service then "reaches" every
|
|
544
|
+
universe the root registers. Name those files in the object form:
|
|
545
|
+
`"sharedImportsSlice": { "mode": "deny-cross-parent", "stopAt": ["kernel/bootstrap.ts",
|
|
546
|
+
"kernel/registrations/**", "lib/feature-registrations.ts"] }`. The walk never starts at
|
|
547
|
+
or passes through a stop file, so a path through it is not a crossing. A `stopAt` entry
|
|
548
|
+
matches like a shared root (anchored, optional leading `src/` or `app/`, a plain folder
|
|
549
|
+
covers its subtree) or like an alias glob (`kernel/registrations/**` against
|
|
550
|
+
`src/kernel/registrations/x.ts`). Config load rejects an entry that covers the whole
|
|
551
|
+
tree (`*`, `**`, `src`, `src/**`, `app/**`, `*/**`) or a whole layer root of that rule
|
|
552
|
+
(layer `src/lib/**` with `lib/**`), since it would stop every shared node and silence
|
|
553
|
+
the walk: `must not cover the whole tree` / `must not cover a whole layer root of this
|
|
554
|
+
rule`. A stop file must still
|
|
555
|
+
sit under `sharedRoots`; `stopAt` does not classify files and does not silence the direct
|
|
556
|
+
`SHARED_IMPORTS_SLICE` warning. A slice destination that matches a stop is still reported.
|
|
557
|
+
Each finding carries `via` (the shared hops). Doctor `sharedWalkHubs` names a shared file
|
|
558
|
+
that sits on at least half of ten or more such findings — a likely composition root. That
|
|
559
|
+
is advisory, not a finding. Adding stops is a weakening policy delta
|
|
560
|
+
(`shared-walk-stop-added`), removing them strengthens, both together need a judgment. The
|
|
561
|
+
string forms are unchanged. arkgate 4.8.23 and older reject the object at config load
|
|
562
|
+
(`must be one of deny, deny-cross-parent`).
|
|
349
563
|
- `allowedCrossSlice` entries match a full slice id (`features/catalog`) or a bare slice name
|
|
350
564
|
(`catalog`), and only in the direction written. The reverse edge still denies. A bare name
|
|
351
565
|
matches that name under **any** slice folder, so in a repo with several slice parents
|
|
@@ -357,7 +571,10 @@ is evidence:
|
|
|
357
571
|
about your code) versus `unclassifiable path (src/widgets/x.tsx)` (a fact about our evidence).
|
|
358
572
|
`no slice folders` and `no path evidence` are the two remaining evidence reasons. A rule-level
|
|
359
573
|
`message` override no longer hides it: the reason is appended to your text, not replaced by it.
|
|
360
|
-
|
|
574
|
+
The write hook and MCP `validate_code` say the same, and for an evidence reason their fix
|
|
575
|
+
asks you to move the file into a slice or declare its root in `sharedRoots`.
|
|
576
|
+
- Both declarations are **weakening** changes in the policy delta (`arkgate-check --strict-merge`
|
|
577
|
+
with `--policy-base <file>` / `--policy-base-ref <ref>`, or MCP `ark_policy_delta`)
|
|
361
578
|
(`shared-roots-added`, `cross-slice-allowance-added`), so a policy review sees them. Both are
|
|
362
579
|
inert on a rule without `peerIsolation: true`, and policy-delta stays silent about them until
|
|
363
580
|
the wall exists.
|
|
@@ -373,7 +590,8 @@ other choice deliberately, not so slices can drift into a mesh.
|
|
|
373
590
|
runtime coupling. They still appear on the **violations** list with `typeOnly: true`,
|
|
374
591
|
`failsStrict: false`, and adapter diagnostic **severity: warning** so doctor/HTML keep
|
|
375
592
|
`violations.typeOnly` / `typeEdgePolicy` honest — but they **do not** fail merge/exit, library
|
|
376
|
-
`valid`, or preflight the way **value** edges do.
|
|
593
|
+
`valid`, or preflight the way **value** edges do. ESLint reports them as warnings on
|
|
594
|
+
`ark/architecture-advisory`, not as errors. **Exception:** `peerIsolation` slice
|
|
377
595
|
boundaries stay hard even for type-only. A value import of a pure-type barrel is still a value
|
|
378
596
|
edge (not soft-skipped). Prefer placing shared types in a **SharedTypes** (or owning) layer
|
|
379
597
|
both sides may import. Optional starter: [`templates/layers/shared-types.starter.json`](../templates/layers/shared-types.starter.json)
|
|
@@ -435,8 +653,8 @@ Each `arkrules/<Layer>.json` may declare:
|
|
|
435
653
|
|
|
436
654
|
| Section | Purpose | Modes | What it really enforces |
|
|
437
655
|
|---------|---------|--------|-------------------------|
|
|
438
|
-
| `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-import + write-token in the declaring layer (ADR 0032). Tier-2 sensors (`no-anemic-model`) stay advisory-only (cannot promote to enforced). |
|
|
439
|
-
| `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
|
|
656
|
+
| `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-import + write-token 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. |
|
|
657
|
+
| `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.” |
|
|
440
658
|
|
|
441
659
|
**Reporting:** diagnostics carry `evidence.arkruleId` + `evidence.arkruleSource`. Label residual
|
|
442
660
|
**`[Layer]`** vs **`[ArkRules]`** in agent output. Doctor / HTML: `rulesUnderContract` (catalog +
|
|
@@ -480,6 +698,14 @@ npx ark-check --strict-config --policy-base ./before.ark.config.json --json
|
|
|
480
698
|
npx ark-check --strict-merge --policy-base-ref origin/main
|
|
481
699
|
```
|
|
482
700
|
|
|
701
|
+
ArkRules are part of the policy. With a Git ref, the base `arkrules/*.json` files are read from
|
|
702
|
+
that same ref (a file the base config references but the ref does not contain yet counts as an
|
|
703
|
+
empty layer). With `--policy-base <file>`, the base ArkRules paths resolve next to that file (keep
|
|
704
|
+
a copy of the base `arkrules/` beside it; the error names that directory and the
|
|
705
|
+
`--policy-base-ref` alternative). A base ArkRules file that cannot be read or parsed fails
|
|
706
|
+
closed, because comparing the candidate catalog with itself would read every demotion or
|
|
707
|
+
deletion as neutral.
|
|
708
|
+
|
|
483
709
|
The additive JSON result includes `policyDelta`: both policy hashes, the overall classification,
|
|
484
710
|
stable findings, and `blockingFindingIds`. Supported comparisons cover governed include/exclude
|
|
485
711
|
roots, layer patterns/exclusions/forbidden globals, deny rules, same-layer peer isolation,
|
|
@@ -536,14 +762,40 @@ type than product source:
|
|
|
536
762
|
|
|
537
763
|
| Check | What it does |
|
|
538
764
|
|-------|----------------|
|
|
539
|
-
| `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
|
|
765
|
+
| `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. |
|
|
540
766
|
| `ark-check --changed --base origin/dev` | Layer check on touched sources only. A CSS/i18n PR pays almost nothing. |
|
|
541
767
|
| `ark-check --against origin/dev` | New violation keys vs **that ref's** baseline (not only HEAD). |
|
|
542
768
|
| `ark-check --contract-diff --base origin/dev` | Classifies tighten / loosen / reclassify / baseline-grow. |
|
|
543
|
-
| `--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. |
|
|
769
|
+
| `--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. |
|
|
544
770
|
| `--persona touch\|contributor\|agent\|steward` | Budget presets for the same teeth. |
|
|
545
771
|
| `ark status --vs origin/dev` | One line: pin / contract / baseline drift vs that ref. |
|
|
546
772
|
|
|
773
|
+
How the changed-file set is computed (`--changed`, `--local`, `--against`, `--persona`):
|
|
774
|
+
|
|
775
|
+
- Paths are relative to `--root`, even when that root is a package inside a monorepo
|
|
776
|
+
(`git diff --relative`; base-ref files are read as `<ref>:./<path>`). Changes outside the
|
|
777
|
+
root are not part of that package's diff.
|
|
778
|
+
- Committed changes since the merge base, staged, unstaged, and untracked files all count.
|
|
779
|
+
File names with spaces, quotes, or non-ASCII characters are kept exactly.
|
|
780
|
+
- Without `--base` / `--against`, the base is the first of `origin/dev`, `origin/main`,
|
|
781
|
+
`origin/master`, `dev`, `main`, `master` that exists. When none exists (or the root is
|
|
782
|
+
not a git repository), `--changed` and a non-steward `--persona` stop with **exit 2**
|
|
783
|
+
(`reasonId: changed-needs-base`; `--local` uses `local-needs-base`). `--contract-diff`
|
|
784
|
+
and `--persona steward` (which implies it) also stop with **exit 2**
|
|
785
|
+
(`reasonId: contract-diff-needs-base`): the contract and baseline diff has nothing to
|
|
786
|
+
compare against. Pass `--base <ref>` or run a plain full check. A diff that could not be
|
|
787
|
+
computed never counts as "no changes".
|
|
788
|
+
- Any failed git listing (bad ref, timeout, output too large) is exit 2
|
|
789
|
+
(`reasonId: changed-paths-unavailable`), never a pass.
|
|
790
|
+
- `--report` applies the committed `.ark-baseline.json` implicitly, except under
|
|
791
|
+
`--strict` / `--strict-merge` / `--contract-diff` / `--against`: a merge verdict uses a
|
|
792
|
+
baseline only when `--baseline` is passed.
|
|
793
|
+
- `--update-baseline` always freezes the whole tree: it refuses `--changed` / `--local`
|
|
794
|
+
(exit 2) and ignores `ARK_CHECK_LOCAL=1`, so a partial scan can never truncate
|
|
795
|
+
`.ark-baseline.json`.
|
|
796
|
+
- Layer-pattern typo warnings (`CONFIG_LAYER_PATTERN_NO_MATCHES`) under `--changed` are
|
|
797
|
+
judged against the full governed tree, so an untouched layer is not reported as a typo.
|
|
798
|
+
|
|
547
799
|
Write-gate ApplyPatch denies a batch that mixes law files with product source. Humans who
|
|
548
800
|
never hit PreToolUse are unchanged. Local `pnpm` gates should call `--local --base` or
|
|
549
801
|
`--changed --base`, not only full-tree `--strict-merge`. Write hooks stay on the lexical
|
|
@@ -554,11 +806,20 @@ worktree has its own root); there is no machine-wide analysis lock.
|
|
|
554
806
|
include tree. File-local ArkRules sensors (class shape, orchestration-only, thin-adapter,
|
|
555
807
|
writes-via-aggregate) and hint preload stay on the touched set. Layer and cycle sensors
|
|
556
808
|
see that closure, so a new illegal import or a cycle the change can complete still
|
|
557
|
-
fails.
|
|
809
|
+
fails. When a touched file sits under the `sharedRoots` of a `"deny-cross-parent"` rule,
|
|
810
|
+
the graph runs full-tree: the finding belongs to the slice that imports the shared root,
|
|
811
|
+
which may be outside the closure. A `CROSS_PARENT_VIA_SHARED` path counts as part of the
|
|
812
|
+
change when any file on it changed. Untouched files outside the closure are left to the full-tree CI check. Same
|
|
558
813
|
engine; not a second analysis path. A `--changed` pass is not a full-tree structural
|
|
559
814
|
verdict.
|
|
560
815
|
|
|
561
816
|
MCP clients can call `ark_policy_delta` with the previous `baseConfig`, an optional candidate
|
|
562
817
|
contract (the current project contract is the default), and the same optional acknowledgement.
|
|
818
|
+
When `baseConfig` maps `arkRules`, pass the base catalog as data in `baseArkRuleFiles`
|
|
819
|
+
(`{ "<path from baseConfig.arkRules>": <ArkRules file JSON> }`); without it the call is refused
|
|
820
|
+
instead of returning a config-only verdict. The candidate catalog is read from disk when the
|
|
821
|
+
candidate is omitted or maps the same `arkRules` files as the project contract (after the loader's
|
|
822
|
+
normalisation, so the project `ark.config.json` passed verbatim counts); otherwise pass
|
|
823
|
+
`candidateArkRuleFiles` in the same shape.
|
|
563
824
|
It invokes the public classifier directly, is read-only, and marks a blocking result as an MCP
|
|
564
825
|
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
|
|