arkgate 4.8.23 → 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 +68 -0
- package/README.md +7 -5
- package/bin/ark-check-runtime.mjs +24 -77
- package/bin/ark-dashboard.mjs +56 -53
- package/bin/ark-layer-match.mjs +596 -112
- 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 -493
- 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 +16 -9
- package/bin/lib/doctor-advisories.mjs +25 -1
- package/bin/lib/doctor-next-actions.mjs +17 -2
- package/bin/lib/doctor-plan.mjs +6 -8
- package/bin/lib/effective-contract-load.mjs +121 -59
- 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 +40 -100
- 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/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/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 +77 -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-Bbf_MFGe.d.ts → configTypes-BJbaeoj4.d.cts} +18 -3
- 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 +130 -156
- package/dist/eslint/index.js +10 -4
- package/dist/index.cjs +35 -34
- package/dist/index.d.cts +5475 -0
- package/dist/index.d.ts +3312 -299
- package/dist/index.js +35 -34
- 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} +804 -290
- package/dist/runtime/index.d.ts +2678 -108
- package/dist/runtime/index.js +13 -30
- package/dist/{types-Bdj32QwA.d.ts → types-DRinG-vT.d.cts} +576 -140
- package/dist/types-DRinG-vT.d.ts +1792 -0
- package/docs/README.md +2 -1
- package/docs/agent-guide.md +84 -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 +196 -26
- package/docs/develop.md +2 -1
- package/docs/diagnostics.md +96 -19
- 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 +47 -10
- 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-Ik_OakcI.d.ts +0 -183
package/docs/README.md
CHANGED
|
@@ -73,7 +73,8 @@ These are **not** the day-to-day product path. They stay in the repo for evidenc
|
|
|
73
73
|
| Field adoption kit (scaffolding, not closed) | [field/](field/) |
|
|
74
74
|
| Runtime hardening (experimental) | [production-hardening.md](production-hardening.md) |
|
|
75
75
|
|
|
76
|
-
Current release: [CHANGELOG](../CHANGELOG.md) (`arkgate@4.8.
|
|
76
|
+
Current release: [CHANGELOG](../CHANGELOG.md) (`arkgate@4.8.24`; confirm npm with `npm view arkgate@4.8.24 version`; does not close `K01`).
|
|
77
|
+
Prior published: [CHANGELOG](../CHANGELOG.md) (`arkgate@4.8.23`).
|
|
77
78
|
Prior published: [CHANGELOG](../CHANGELOG.md) (`arkgate@4.8.22`).
|
|
78
79
|
Prior published: [CHANGELOG](../CHANGELOG.md) (`arkgate@4.8.21`).
|
|
79
80
|
Prior published: [CHANGELOG](../CHANGELOG.md) (`arkgate@4.8.20`). Plain-English history: [arkgate.online/changelog](https://www.arkgate.online/changelog/).
|
package/docs/agent-guide.md
CHANGED
|
@@ -207,6 +207,16 @@ npx ark status --json
|
|
|
207
207
|
npx ark status --json --expected-root /abs/project/root
|
|
208
208
|
```
|
|
209
209
|
|
|
210
|
+
`--config <file>` reads a non-default contract (same for `ark agents-md --config <file>`).
|
|
211
|
+
When the resolved config fails the shared contract validator, status adds
|
|
212
|
+
`contract: { valid: false, errors: [...] }` and the next action is `fix-config` — the same
|
|
213
|
+
config every enforcing surface refuses is never reported as ready. `ark-check --json` (and
|
|
214
|
+
`--doctor --json`) print `{ "ok": false, "error": "CONFIG_INVALID", "configPath", "messages" }`
|
|
215
|
+
on stdout for that case (exit 2), so a JSON consumer always gets the reason. Any other fatal
|
|
216
|
+
error under `--json` prints `{ "ok": false, "error": "POLICY_BASE_UNREADABLE" | "CHECK_ERROR", "message" }`
|
|
217
|
+
(also exit 2); `POLICY_BASE_UNREADABLE` means the policy base ref (for example `GITHUB_BASE_REF`)
|
|
218
|
+
could not be read in this checkout — fetch it or pass `--base <ref>`.
|
|
219
|
+
|
|
210
220
|
MCP parity tool: **`ark_status`** (same envelope; pass `project.expectedRoot` after `ark_identity`).
|
|
211
221
|
Schema: `arkgate/schema/status-manifest`. Never prompts; under `CI=1` JSON is forced. **Not a
|
|
212
222
|
score** — counts, honesty modes, and residual ids only. Write-path interpretation of activation vs
|
|
@@ -312,14 +322,35 @@ npx ark-check --ratchet-cores # require populated core layers
|
|
|
312
322
|
npx ark-check --watch # debounced re-check when governed files change
|
|
313
323
|
```
|
|
314
324
|
|
|
325
|
+
**Package pin:** `ark start` and `ark init` both pin `arkgate` in `package.json`
|
|
326
|
+
`devDependencies` (and install it unless `--skip-package-manager`) before writing the CI workflow
|
|
327
|
+
and host hooks, because those call the local `ark-check` / `arkgate-mcp` bins. `--no-install`
|
|
328
|
+
skips the pin; without a `package.json` setup prints a warning instead. When arkgate is not a
|
|
329
|
+
local dependency (no pin, no `node_modules/arkgate`), every generated command runs
|
|
330
|
+
`npx -y -p arkgate@<exact version> <bin>` instead of a bare `npx ark-check`, which npm would
|
|
331
|
+
resolve as a nonexistent `ark-check` package (404) or a stale global. `--doctor` leads with
|
|
332
|
+
`PACKAGE_PIN_ABSENT` when an existing generated file still uses the local runner without the pin.
|
|
333
|
+
`start --remove-host`
|
|
334
|
+
never runs the package manager. An explicit `--archetype` / `--preset` on `ark start` is the
|
|
335
|
+
contract that is written (not only a way past the shape-confidence gate), and re-running
|
|
336
|
+
`ark start --apply` on a project that already has `ark.config.json` keeps that contract and is not
|
|
337
|
+
re-gated on detected shape confidence (so the `--remove-host` restore command works).
|
|
338
|
+
The generated `typecheck` script and CI step are added only when `typescript` is a dependency
|
|
339
|
+
(or resolves from the project root); otherwise setup prints a note.
|
|
340
|
+
|
|
315
341
|
**Day-zero origin (2.12+):** `ark init` freezes `.ark/reports/origin.*` before writing agent
|
|
316
|
-
docs or CI templates. Compact `ark start` previews first and keeps the applied setup small
|
|
342
|
+
docs or CI templates (the day-zero HTML is written under `.ark/reports/`, not the project root). Compact `ark start` previews first and keeps the applied setup small
|
|
317
343
|
(budget: 8 files / 32 KB including project `.mcp.json`);
|
|
318
344
|
run `ark-check --report ark-report.html` explicitly when you want to establish an origin/evolution
|
|
319
345
|
baseline. Do not `--reset-origin` unless the user explicitly wants a new baseline.
|
|
320
346
|
`ark-check --report --no-archive` still creates `origin.*` on the first report (or on an explicit
|
|
321
347
|
reset) and refreshes `latest.*`; it skips only the timestamped JSON file under
|
|
322
348
|
`.ark/reports/history/`.
|
|
349
|
+
`--report` applies the committed `.ark-baseline.json` by default (the same freeze `--doctor`
|
|
350
|
+
reads), so `latest.json` and `ark status` count frozen debt as frozen, not active. Under
|
|
351
|
+
`--strict` / `--strict-merge` / `--contract-diff` the implicit freeze is not applied (pass
|
|
352
|
+
`--baseline` explicitly), so `--report` never changes a merge verdict. Only
|
|
353
|
+
`--report` refreshes that snapshot; a plain check or `--doctor` does not.
|
|
323
354
|
Snapshots record best-effort, shell-free Git provenance (`HEAD`, attached branch, and dirty
|
|
324
355
|
worktree state). Evolution keeps raw metrics visible across ArkGate upgrades, but an Ark score
|
|
325
356
|
delta is comparable—and therefore rendered—only when origin and current snapshots use the same
|
|
@@ -625,7 +656,9 @@ Plan by default — there is no `--dry-run` anywhere in `bin/`. `--apply` needs
|
|
|
625
656
|
one named rule id: `--promote <ruleId> --apply`, or `--promote=<ruleId>` when
|
|
626
657
|
the id starts with `-`. It refuses a bare `--promote --apply` rather than
|
|
627
658
|
rewriting the contract in bulk behind a single flag, refuses an id declared in
|
|
628
|
-
two documents rather than silently writing the first
|
|
659
|
+
two documents rather than silently writing the first (starter ids are unique:
|
|
660
|
+
`thin-persistence-adapter` / `thin-presentation-adapter`, and a layer that clones
|
|
661
|
+
another layer's archetype gets its ids prefixed with its kebab-cased name), and refuses to make a
|
|
629
662
|
contract change on a cost this run did not measure. `--promote` cannot be
|
|
630
663
|
combined with a mode that answers first (`--sensors`, `--coverage`, `--plan`,
|
|
631
664
|
`--doctor`, …) — that printed the report and exited 0 having written nothing —
|
|
@@ -1005,6 +1038,13 @@ a `Domain.*` event — the publish throws `ObservedLayerFlowViolationError` befo
|
|
|
1005
1038
|
reaches history, outbox, or subscribers. Use `'soft'` to record `layer.observedViolation`
|
|
1006
1039
|
trace/audit records without blocking, or `'off'` to disable. Agents should name the event's
|
|
1007
1040
|
`source` honestly: it is checked against the layer matrix, not just the intent name.
|
|
1041
|
+
Kernels built from `ark.config.json` map intents to layers through `layers[].intentPrefixes`;
|
|
1042
|
+
canonical layer names (`DomainModel`, `ApplicationOrchestration`, … as `ark init` writes them)
|
|
1043
|
+
without prefixes get the built-in ones. A custom-named deny-rule layer with no prefixes is
|
|
1044
|
+
recorded once as a `layer.observedFlowUnresolvable` audit record
|
|
1045
|
+
(`ARKRUN_LAYER_FLOW_UNRESOLVABLE`) and listed by `ark doctor`; pass
|
|
1046
|
+
`enforceObservedLayerFlow: 'hard'` explicitly to make it throw `ArkKernelConfigError`.
|
|
1047
|
+
`peerIsolation` slice walls are not evaluated at runtime (names cannot place a slice).
|
|
1008
1048
|
|
|
1009
1049
|
Strict kernels also require published events to have a registered source intent
|
|
1010
1050
|
and a matching event contract:
|
|
@@ -1150,9 +1190,9 @@ edges are not denied by that rule.
|
|
|
1150
1190
|
- **Denied:** `src/features/auth/**` → `src/features/payments/**` (different slice id).
|
|
1151
1191
|
- **Allowed:** same-slice imports when both paths classify; classic non-peerIsolation denies still apply across layers.
|
|
1152
1192
|
- **`sliceFolders`:** optional parent segments (default: inferred from layer globs). A bare name stays an unanchored one-segment match (`features` on `src/features/auth` is `features/auth`). A starred anchored pattern with `sliceIdentity` `path` (the default) includes the literal prefix plus the star bindings (`lib/features/*/*` → `lib/features/projects/rfi`), so parallel trees get different ids.
|
|
1153
|
-
- **`sliceIdentity`:** `path` (default, also when the key is absent) or `stars`. `path` keeps today's slice ids. `stars` names a starred prefix as the last literal plus
|
|
1193
|
+
- **`sliceIdentity`:** `path` (default, also when the key is absent) or `stars`. `path` keeps today's slice ids. `stars` names a starred prefix as the last literal plus every star binding (`lib/features/*/*` and `lib/repositories/features/*/*` both yield `features/projects/rfi`; `modules/*/api/*` keeps the module: `orders/api/v1`). Doctor names both prefixes when they collapse onto one id. Additive — no migration.
|
|
1154
1194
|
- **Fail-closed:** missing paths, empty/unresolvable slice folders, or unclassifiable either side → **deny** via peerIsolation (cannot prove same-slice).
|
|
1155
|
-
- **`childSlices`** (optional): a second wall under the same rule. The universe wall runs first and is unchanged when the block is absent. With it present, a cross-universe deny is `reasonId` `CROSS_PARENT_SLICE` (never advisory). Inside one universe, `commonFolders`
|
|
1195
|
+
- **`childSlices`** (optional): a second wall under the same rule. The universe wall runs first and is unchanged when the block is absent. With it present, a cross-universe deny is `reasonId` `CROSS_PARENT_SLICE` (never advisory). Needs `peerIsolation: true` and `allowed: false` on the rule. Inside one universe, flat files and a `commonFolders` directory directly under the universe are common (the same folder name inside a child belongs to the child); a child may import them; common may import a child only when `parentMayImportChild` is true. Sibling crossings are `CROSS_SIBLING_SLICE` — error when `siblings` is `deny` (the default), warning when `advisory`. With a baseline, a new advisory crossing fails only when the baseline already records an advisory crossing of that rule, or with `siblings.ratchet: true`; `ratchet: false` measures only. `childSlices.message` is the inner-wall text; the rule `message` stays the universe-wall text. `ruleId` stays `LAYER_IMPORT_VIOLATION`; adapter diagnostics carry `evidence.reasonId`. arkgate 4.8.22 and older reject the key; `CONFIG_CHILD_SLICES_VERSION` fires only when the repo pins such a version (package.json, hook, CI), at that file.
|
|
1156
1196
|
- **`sharedRoots`** (4.8.4): roots the repo declares shared on purpose (`["ui", "hooks", "lib/permissions"]`). A file under a declared shared root is evidence, not an unclassifiable path, so fail-closed stops firing on every shared file. **Anchored** — the root starts the path, optionally after one `src/` or `app/`; write deeper or monorepo roots out (`packages/web/src/ui`) or glob them, and a bare `*` / `**` is refused. A path that still resolves to a slice keeps its slice.
|
|
1157
1197
|
- **`allowedCrossSlice`** (4.8.4): `[{ "from": "features/checkout", "to": "features/catalog" }]` — one directed slice→slice edge the repo declares on purpose. The reverse still denies.
|
|
1158
1198
|
- **The denial names its reason:** `cross-slice edge a → b` (a fact about the code) vs `unclassifiable path (…)`, `no slice folders`, `no path evidence` (facts about the evidence ArkGate had).
|
|
@@ -1241,8 +1281,14 @@ imports/exports; resolved CLI/preflight is authoritative outside that envelope),
|
|
|
1241
1281
|
`ark/no-denied-capabilities` (per-layer capability deny sets),
|
|
1242
1282
|
`ark/no-arkrun-kernel-in-domain` / `ark/no-arkrun-direct-new` /
|
|
1243
1283
|
`ark/no-arkrun-transport-bypass` (ArkRun extra; silent when absent; import / `new`
|
|
1244
|
-
envelope only), `ark/no-
|
|
1245
|
-
`ark/
|
|
1284
|
+
envelope only), `ark/no-arkorder-kernel-in-domain` / `ark/no-arkorder-generic-update`
|
|
1285
|
+
(ArkOrder extra), `ark/arkrules-structure` (ArkRules structure sensors, file-local),
|
|
1286
|
+
`ark/no-raw-event-publish`, and `ark/require-publish-source`. Findings ark-check reports as
|
|
1287
|
+
warnings (type-only placement debt, advisory slice walls, advisory extras) report on the
|
|
1288
|
+
warn-level `ark/architecture-advisory`, so ESLint errors only where ark-check fails (a
|
|
1289
|
+
config without that rule gets them on the blocking rule id, tagged advisory). An
|
|
1290
|
+
invalid contract is one `configInvalid` error per file, not a crash. See
|
|
1291
|
+
[ai-gates.md](ai-gates.md#eslint-editor-feedback--bounded-parity-envelope).
|
|
1246
1292
|
|
|
1247
1293
|
## Runtime Observability
|
|
1248
1294
|
|
|
@@ -1324,7 +1370,10 @@ not replace your web framework, HTTP clients, or job scheduler.
|
|
|
1324
1370
|
The strongest place to constrain an AI agent is the moment it writes a file, not after.
|
|
1325
1371
|
`arkgate-mcp` / `ark-mcp` exposes ArkGate over MCP (JSON-RPC over stdio; it prefers a usable
|
|
1326
1372
|
project TypeScript API, then exact `typescript-ark-host@6.0.3`) so a host can gate
|
|
1327
|
-
the write path
|
|
1373
|
+
the write path. `arkgate mcp` / `ark mcp` starts the same server; that is the form
|
|
1374
|
+
`npx arkgate@<version> mcp --root .` and the MCP Registry use, since npx runs the bin named
|
|
1375
|
+
after the package. Without `--config` the server reads `ark.config.json` when it exists and still
|
|
1376
|
+
starts in a fresh project; an explicit `--config` that points at a missing file exits 1:
|
|
1328
1377
|
|
|
1329
1378
|
For a complete multi-file candidate, use `ark preflight --changes changes.json --json` or MCP
|
|
1330
1379
|
`ark_prepare_change`. Add `--change-map map.json` (or MCP `changeMap`) only for an explicit schema
|
|
@@ -1370,13 +1419,13 @@ The server exposes these thirteen tools. Every tool accepts the additive
|
|
|
1370
1419
|
|------|---------------------------|
|
|
1371
1420
|
| `ark_identity` | `{ project: { expectedRoot, expectedProjectId? } }`: return the canonical root/config, stable project id, contract identity, and live runtime identity; use it before every other project-bound surface. |
|
|
1372
1421
|
| `ark_manifest` | No non-project args: return the machine-readable architecture contract with an authoritative binding after the identity handshake. |
|
|
1373
|
-
| `validate_code` | `{ source, layer?, filePath? }`:
|
|
1422
|
+
| `validate_code` | `{ source, layer?, filePath? }`: single-file lexical check (layer plane plus the enforced ArkRun / ArkRules structure / ArkOrder sensors CI runs on that file); infer the layer from `filePath` when possible. Always partial: `valid:false` / `isError:true` until complete-candidate preflight — read `lexicalValid` for the one-file verdict. Not a hook; hard blocking is `arkgate-mcp --hook`. |
|
|
1374
1423
|
| `ark_check` | `{ strict?, baseline? }`: run the full project architecture check. `verdict` separates `identity`, `completeness`, `graph`, `coverage`, `gates`, and `overallOk`; no individual green fact substitutes for the combined verdict. |
|
|
1375
|
-
| `ark_policy_delta` | `{ baseConfig, candidateConfig?, acknowledgement? }`: classify a complete contract transition; never edits the contract. Weakening / new layer / new allow edge needs `adrPath` on the acknowledgement. |
|
|
1424
|
+
| `ark_policy_delta` | `{ baseConfig, candidateConfig?, baseArkRuleFiles?, candidateArkRuleFiles?, acknowledgement? }`: classify a complete contract transition, ArkRules included; never edits the contract. `baseArkRuleFiles` (`{ "<path from baseConfig.arkRules>": <file JSON> }`) is required when `baseConfig` maps `arkRules`. Weakening / new layer / new allow edge needs `adrPath` on the acknowledgement. |
|
|
1376
1425
|
| `ark_coverage` | No args: report per-layer counts, every unclassified file, unmatched layers, and missing rule edges. |
|
|
1377
|
-
| `ark_place` | `{ filePath
|
|
1378
|
-
| `ark_prepare_write` | `{ source, filePath
|
|
1379
|
-
| `ark_prepare_change` | `{ changes, changeMap? }`: preflight one complete create/update/delete batch in memory; never writes files. |
|
|
1426
|
+
| `ark_place` | `{ filePath, description? }`: resolve the governed home for `filePath` (fail-closed without it; never invents a path) and return its import/global constraints. |
|
|
1427
|
+
| `ark_prepare_write` | `{ source, filePath, description?, layer? }`: compose placement and snippet validation, with hashes and a mechanical-safe patch when available. `filePath` is required (fail-closed). Partial like `validate_code`: read `lexicalValid`. |
|
|
1428
|
+
| `ark_prepare_change` | `{ changes, changeMap? }`: preflight one complete create/update/delete batch in memory against the same Effective Contract as `ark-check` (ArkRules included: same `policyHash`, structure/invariant findings; a missing referenced ArkRules file fails closed); ArkRules-plane findings the base tree already has are returned as `preExisting: true` warnings and do not block, so baselined brownfield debt does not reject an unrelated batch; never writes files. |
|
|
1380
1429
|
| `ark_recommend` | No args: return the deterministic application-shape plan used by `ark-check --recommend --json`. |
|
|
1381
1430
|
| `ark_suggest_include` | No args: propose TypeScript/JavaScript include roots from workspaces and nested packages. |
|
|
1382
1431
|
| `ark_rules_inventory` | No args: inventory possible intra-layer rules using configured layer evidence when available; test/fixture/seed/migration surfaces and narrow technical constants are excluded from extraction pilots. Counts are not a score. |
|
|
@@ -1406,7 +1455,13 @@ Every project-bound tool success, tool error, and JSON-RPC error data carries:
|
|
|
1406
1455
|
```
|
|
1407
1456
|
|
|
1408
1457
|
`projectId` stays stable across process restarts and contract edits; `runtimeId` and
|
|
1409
|
-
`processStartedAt` identify this live process.
|
|
1458
|
+
`processStartedAt` identify this live process. The contract (`ark.config.json`, the project
|
|
1459
|
+
manifest, referenced ArkRules files) is loaded once at startup: after any of them changes on
|
|
1460
|
+
disk, every project tool except `ark_identity` returns `CONTRACT_STALE` (`isError`,
|
|
1461
|
+
`authoritative: false`, `contractStale: true`) until the MCP server restarts — the process never
|
|
1462
|
+
answers authoritatively from an old contract. `ark_identity` keeps answering with
|
|
1463
|
+
`contractStale: true` so the host can diagnose it; the project-local CLI reads the current
|
|
1464
|
+
contract meanwhile. Binding states are:
|
|
1410
1465
|
|
|
1411
1466
|
- `matched` — canonical `expectedRoot` is the exact project root, or it is a contained
|
|
1412
1467
|
descendant and the caller also supplied the matching project id; `authoritative` is `true`;
|
|
@@ -1513,8 +1568,22 @@ doctor → compact router (and `/ark-autopilot` only after the skill pack).
|
|
|
1513
1568
|
| `GET /workflows` | Workflows monitor: counts + `workflows[]` summaries (`id`, `name`, `status`, optional `currentStep` / `error`) |
|
|
1514
1569
|
|
|
1515
1570
|
Dual package bins **`ark-dashboard`** and **`arkgate-dashboard`**
|
|
1516
|
-
(`bin/ark-dashboard.mjs`) poll `--url`
|
|
1517
|
-
|
|
1571
|
+
(`bin/ark-dashboard.mjs`) poll `--url` and sibling `/outbox` + `/workflows` on an
|
|
1572
|
+
interval (`--interval` / `--timeout`, clamped to 200–60000 ms). `--url` accepts the
|
|
1573
|
+
inspector root (`handle.url`) or `handle.snapshotUrl`. `startInspector()` binds a
|
|
1574
|
+
**random port** unless you pass `{ port }`, so pass the URL your app printed (or set
|
|
1575
|
+
`ARK_DASHBOARD_URL`); the fallback `http://127.0.0.1:3000/snapshot` only matches
|
|
1576
|
+
`startInspector({ port: 3000 })`, and the dashboard prints that hint when it cannot
|
|
1577
|
+
connect. `--once` renders one frame and exits (non-zero when unreachable);
|
|
1578
|
+
`--help` / `--version` exit 0; unknown flags exit 2 with one line.
|
|
1579
|
+
|
|
1580
|
+
The default in-memory event buffer (`InMemoryEventBuffer`) is capped at
|
|
1581
|
+
`maxHistorySize` (default 1000) and its records are marked `dispatched` once the kernel
|
|
1582
|
+
finishes local delivery, so `/outbox` shows real backlog only. Broker sends are marked
|
|
1583
|
+
`dispatched` / `failed` from the handoff outcome, and a failed handoff (awaited or
|
|
1584
|
+
fire-and-forget) records an `event.handoffFailed` trace + audit entry. An injected
|
|
1585
|
+
`eventBuffer` stays relay-owned: records remain `pending` for your relay's `claim` /
|
|
1586
|
+
`markDispatched` (except broker handoffs the kernel performs itself).
|
|
1518
1587
|
ANSI escape sequences + polling only — no React, Ink, or Blessed. Use
|
|
1519
1588
|
`ark dashboard` / `arkgate dashboard` (passthrough to `bin/ark-dashboard.mjs`) or the
|
|
1520
1589
|
dual bins `ark-dashboard` / `arkgate-dashboard`.
|
package/docs/ai-gates.md
CHANGED
|
@@ -50,7 +50,8 @@ fail-open. OpenCode is MCP-only. Required CI is always the merge line.
|
|
|
50
50
|
|
|
51
51
|
Everything below uses the same `ark.config.json` as `arkgate-check` / `ark-check` (CI) — one
|
|
52
52
|
rules file shared by every surface. From **4.0**, optional **ArkRules** (`arkRules` map +
|
|
53
|
-
`arkrules/*.json`) ride the **same** write path, doctor, and CI adapter
|
|
53
|
+
`arkrules/*.json`) ride the **same** write path, doctor, and CI adapter (ESLint runs the
|
|
54
|
+
file-local structure sensors only — see [ESLint](#eslint-editor-feedback--bounded-parity-envelope)); absence of ArkRules
|
|
54
55
|
does not change inter-layer verdicts. Optional ArkRun / ArkOrder extras ride that envelope
|
|
55
56
|
when on (`proposeRelease` then `apply`). Absence is silent. Label residual **`[Layer]`** vs
|
|
56
57
|
**`[ArkRules]`** vs **`[ArkRun]`** vs **`[ArkOrder]`**. See
|
|
@@ -128,7 +129,11 @@ project id before using project evidence.
|
|
|
128
129
|
Like `ark-check --baseline`, the hook ratchets: an edit is blocked only when it **adds**
|
|
129
130
|
violations relative to the file's current on-disk state, so files with pre-existing
|
|
130
131
|
(baselined) violations stay editable — they just can't get worse. New files block on
|
|
131
|
-
every violation.
|
|
132
|
+
every violation. A contract failure (`WRITE_GATE_UNAVAILABLE`, e.g. a referenced ArkRules
|
|
133
|
+
file missing) is never ratcheted: it blocks edits to existing files as well as new ones.
|
|
134
|
+
The Codex `apply_patch` atomic preflight ratchets ArkRules-plane findings (`ARKRULE_*`,
|
|
135
|
+
`INVARIANT_*`) the same way against the base tree: debt the repository already has is
|
|
136
|
+
reported as a warning (`preExisting: true`), and only findings the patch adds block.
|
|
132
137
|
|
|
133
138
|
The hook classifies import targets with the same `layerForRelativePath` specificity as
|
|
134
139
|
`ark-check` (an explicit file pattern beats a broader glob such as `src/lib/**`).
|
|
@@ -250,7 +255,11 @@ After edits run: npx ark-check --root . --config ark.config.json --strict
|
|
|
250
255
|
```
|
|
251
256
|
|
|
252
257
|
The hook belongs in the **project's** `.claude/settings.json` (that's what
|
|
253
|
-
`--install-agent-gates` generates).
|
|
258
|
+
`--install-agent-gates` generates). An existing `.claude/settings.json` (or `.codex/hooks.json`)
|
|
259
|
+
is **merged**, not skipped or replaced: Ark upserts only its own `SessionStart` / `PreToolUse`
|
|
260
|
+
`arkgate-mcp` entries and keeps `permissions`, `env`, other hook events, and your own hook
|
|
261
|
+
entries; re-running is a no-op. Only a file that is not a JSON object is left untouched (and
|
|
262
|
+
`--require-write-hook` then refuses until you fix it or pass `--force`). It is also safe by construction if you prefer it in
|
|
254
263
|
your global settings: without an `ark.config.json` in the project, `--session-context`
|
|
255
264
|
prints nothing and exits 0, so non-Ark projects are untouched.
|
|
256
265
|
|
|
@@ -721,8 +730,25 @@ If your runtime can run a shell command before file writes and pass the tool pay
|
|
|
721
730
|
- Grok: `{ "decision": "deny", "reason": "…" }` on stdout when blocked
|
|
722
731
|
- Antigravity: **stdout `decision` is required** — allow → `{ "decision": "allow" }`; deny →
|
|
723
732
|
`{ "decision": "deny", "reason": "…" }` (exit 2 still set on deny)
|
|
724
|
-
- plumbing problems (no stdin, non-source files, files outside
|
|
725
|
-
still emits `{ "decision": "allow" }` on those fail-open paths
|
|
733
|
+
- plumbing problems (no stdin, malformed payload, non-file tools, non-source files, files outside
|
|
734
|
+
`--root`) never block; Antigravity still emits `{ "decision": "allow" }` on those fail-open paths.
|
|
735
|
+
"Outside `--root`" is judged after realpath: `/tmp` vs `/private/tmp` or a symlinked checkout is
|
|
736
|
+
the same workspace and is gated
|
|
737
|
+
- a gate that **cannot run** blocks every governed source write (exit `2`, `WRITE_GATE_UNAVAILABLE`,
|
|
738
|
+
plus the host deny JSON): `ark.config.json` missing / unparsable / invalid, a referenced ArkRules
|
|
739
|
+
file missing or invalid, or `dist/` missing or broken. No checker, no write. This applies to
|
|
740
|
+
existing files too: the same-file ratchet never cancels a contract failure. Writes to the law
|
|
741
|
+
files (`ark.config.json` and every ArkRules file it references under `arkRules`) are still
|
|
742
|
+
allowed so the contract can be repaired, but a write or `apply_patch` delete that would leave
|
|
743
|
+
one unloadable is denied (`WRITE_GATE_UNAVAILABLE`)
|
|
744
|
+
- Codex `apply_patch`: the full grammar is reconstructed (`*** End of File`, a first hunk without
|
|
745
|
+
`@@`, `*** Move to:` judged at the destination). A patch that truly cannot be reconstructed is
|
|
746
|
+
allowed with a stderr notice and stays CI-backed
|
|
747
|
+
- the hook runs the same file-local extra planes CI runs on that file: enforced ArkRun editor
|
|
748
|
+
sensors, enforced ArkRules structure sensors (`ARKRULE_STRUCTURE`), and enforced ArkOrder editor
|
|
749
|
+
sensors (`ARKORDER_KERNEL_IN_DOMAIN`, `ARKORDER_GENERIC_UPDATE`, …), under the same
|
|
750
|
+
classification floor. Cross-file evidence (invariant coverage, missing plane roots) stays
|
|
751
|
+
CI-authoritative
|
|
726
752
|
|
|
727
753
|
## ESLint (editor feedback) — bounded parity envelope
|
|
728
754
|
|
|
@@ -731,31 +757,108 @@ Layer imports and purity globals are driven by **`ark.config.json`** (walk-up fr
|
|
|
731
757
|
linted file).
|
|
732
758
|
|
|
733
759
|
```js
|
|
734
|
-
// eslint.config.js (flat config)
|
|
760
|
+
// eslint.config.js (flat config, ESM)
|
|
735
761
|
import ark from 'arkgate/eslint';
|
|
736
762
|
|
|
737
763
|
export default [
|
|
738
764
|
ark.configs.recommended,
|
|
739
|
-
// no-domain-infra-imports
|
|
740
|
-
// no-forbidden-globals
|
|
741
|
-
//
|
|
742
|
-
//
|
|
743
|
-
//
|
|
744
|
-
// no-
|
|
765
|
+
// error: no-domain-infra-imports → config-driven layer edges that fail ark-check
|
|
766
|
+
// error: no-forbidden-globals → layer.forbiddenGlobals from ark.config.json
|
|
767
|
+
// error: no-denied-capabilities → layer.capabilities.deny / layer.pure
|
|
768
|
+
// error: no-arkrun-kernel-in-domain + no-arkrun-direct-new + no-arkrun-transport-bypass
|
|
769
|
+
// → arkRun extra (silent when absent; import / `new` envelope only)
|
|
770
|
+
// error: no-arkorder-kernel-in-domain + no-arkorder-generic-update → arkOrder extra
|
|
771
|
+
// error: arkrules-structure → ArkRules structure sensors (file-local)
|
|
772
|
+
// error: no-raw-event-publish + require-publish-source → runtime event hygiene
|
|
773
|
+
// warn: architecture-advisory → the findings ark-check reports as warnings
|
|
745
774
|
];
|
|
746
775
|
```
|
|
747
776
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
777
|
+
```js
|
|
778
|
+
// eslint.config.cjs (CommonJS works the same way)
|
|
779
|
+
const ark = require('arkgate/eslint');
|
|
780
|
+
module.exports = [ark.configs.recommended];
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
`require('arkgate/eslint')` returns the plugin itself (`configs`, `rules`, `meta`), the same
|
|
784
|
+
object as the ESM default export.
|
|
785
|
+
|
|
786
|
+
**Severity follows ark-check, per finding.** ESLint takes severity from the rule's configured
|
|
787
|
+
level; one rule cannot report some findings as errors and others as warnings. So the plugin
|
|
788
|
+
splits them by rule id. The blocking rules above report only findings that fail ark-check
|
|
789
|
+
(`severity: "error"`). Every finding ark-check reports as a warning (`failsStrict: false`)
|
|
790
|
+
reports on **`ark/architecture-advisory`** instead, which `recommended` sets to `warn`:
|
|
791
|
+
|
|
792
|
+
- type-only placement debt (a non-peer `import type` across a denied edge);
|
|
793
|
+
- a `CROSS_SIBLING_SLICE` crossing under `childSlices.siblings: "advisory"` (or not listed in
|
|
794
|
+
`enforce`);
|
|
795
|
+
- `arkRun.mode`, `arkOrder.mode`, or an ArkRules rule set to `advisory`.
|
|
796
|
+
|
|
797
|
+
So `eslint` exits `0` wherever `arkgate-check` exits `0` for these findings, and a Next.js
|
|
798
|
+
build that runs ESLint does not fail on them. The warning text names the ark-check rule id.
|
|
799
|
+
The blocking rule and `architecture-advisory` share one evaluation per file, so enabling both
|
|
800
|
+
does not resolve a file's imports twice.
|
|
801
|
+
|
|
802
|
+
A hand-written config that turns on single rules without `architecture-advisory` does not
|
|
803
|
+
lose these findings. They fall back to the blocking rule id, at that rule's level, with the
|
|
804
|
+
text `[<ark-check rule id>; advisory — does not fail ark-check. Enable
|
|
805
|
+
ark/architecture-advisory …]`. Add `'ark/architecture-advisory': 'warn'` to see them as
|
|
806
|
+
warnings instead.
|
|
807
|
+
There is no baseline in the editor: a new advisory crossing past the recorded baseline, which
|
|
808
|
+
fails in CI, is still only a warning here.
|
|
809
|
+
|
|
810
|
+
**Messages are the ark-check text.** A layer denial reads
|
|
811
|
+
`Architecture: <ark-check message> Specifier: <import>`. That message carries the rule-level
|
|
812
|
+
`message` and the slice reason (cross-slice, cross-sibling, cross-parent, unclassifiable) plus
|
|
813
|
+
the type-only suffix. ArkOrder and ArkRules findings print the sensor's own message.
|
|
814
|
+
|
|
815
|
+
**Invalid contract.** When `ark.config.json` does not parse or validate, or a referenced
|
|
816
|
+
ArkRules file is missing or invalid, ESLint does not crash. The file gets one
|
|
817
|
+
`configInvalid` error carrying the validator text (fail closed, same as `ark-check`, which
|
|
818
|
+
exits `2`), and other rules and files keep linting.
|
|
819
|
+
|
|
820
|
+
**`eslint --cache`.** The plugin has `meta` (`arkgate@<version>`), so an upgrade invalidates
|
|
821
|
+
cached results. `configs.recommended` also carries `settings.ark.contractHash`, a fingerprint of
|
|
822
|
+
the `ark.config.json` (and referenced ArkRules files) found by walking up from the **current
|
|
823
|
+
working directory** of the process that reads `configs.recommended` — not from the config
|
|
824
|
+
file's directory. Editing that contract therefore invalidates the cache too. When ESLint runs
|
|
825
|
+
from another directory (`eslint -c some/dir/eslint.config.js` from elsewhere, or an editor
|
|
826
|
+
whose cwd is a workspace root above the project), the fingerprint covers a different contract
|
|
827
|
+
or none. Pin it to the config file's directory yourself:
|
|
828
|
+
|
|
829
|
+
```js
|
|
830
|
+
// eslint.config.js
|
|
831
|
+
import ark, { contractFingerprint } from 'arkgate/eslint';
|
|
832
|
+
const contractHash = contractFingerprint(import.meta.dirname);
|
|
833
|
+
export default [
|
|
834
|
+
ark.configs.recommended,
|
|
835
|
+
...(contractHash ? [{ settings: { ark: { contractHash } } }] : []),
|
|
836
|
+
];
|
|
837
|
+
```
|
|
754
838
|
|
|
755
|
-
|
|
756
|
-
`
|
|
757
|
-
|
|
758
|
-
|
|
839
|
+
Not covered by the fingerprint: a second `ark.config.json` deeper in a monorepo,
|
|
840
|
+
`tsconfig.json` edits, and a cross-file target that appears or disappears. Use
|
|
841
|
+
`--cache-strategy content`, clear the cache, or rely on `ark-check` / CI, which stay the
|
|
842
|
+
source of truth.
|
|
843
|
+
|
|
844
|
+
**Exact layer-edge parity envelope:** the linted production source is inside `include`,
|
|
845
|
+
outside configured/generated exclusions, parse-clean, and uses a static `import`/`export` with
|
|
846
|
+
a **relative** or **tsconfig `paths` / `baseUrl` alias** (e.g. `@/*`) literal whose target is
|
|
847
|
+
on disk. Inside that envelope, ESLint uses the same layer glob specificity, rule decision,
|
|
848
|
+
severity, rule id, message, and evidence as the resolved CLI. It reloads `ark.config.json`
|
|
849
|
+
when its content changes and never invents a not-yet-created target. File-local facts
|
|
850
|
+
(`new`, ArkOrder, ArkRules structure) come from the editor buffer, so unsaved edits and
|
|
851
|
+
`--stdin --stdin-filename` are linted as written.
|
|
852
|
+
|
|
853
|
+
**Path-alias resolution:** like the CLI (`ts.findConfigFile`), the tsconfig is the one nearest
|
|
854
|
+
to the linted file, so a nested `apps/web/tsconfig.json` is used under a single root
|
|
855
|
+
`ark.config.json`. It is read as JSONC (comments, trailing commas, and `"@/*"` / `"**/*.ts"`
|
|
856
|
+
strings are safe). `extends` may be relative, absolute, a package (`@tsconfig/next/...`), or an
|
|
857
|
+
array. Inheritance follows TypeScript: a child's `paths` replaces the parent's, and `paths`
|
|
858
|
+
resolve against `baseUrl`, else against the tsconfig that declares them. Each target in a path
|
|
859
|
+
array is tried in order, and a `*` may sit mid-pattern. Parsed tsconfigs are cached and re-read
|
|
860
|
+
when the file changes. Not claimed: TypeScript project references, catch-all `*` mappings,
|
|
861
|
+
package/workspace bare imports, or symlink hops. Those stay CI / preflight / write-gate truth.
|
|
759
862
|
|
|
760
863
|
Outside that envelope—packages/workspaces, symlinks, CommonJS, `import = require`, dynamic
|
|
761
864
|
imports, virtual creates/deletes, unresolved targets, or complete cross-file candidates—ESLint
|
|
@@ -765,8 +868,9 @@ ApplyPatch hook, or final strict CI; those paths consume the canonical resolved
|
|
|
765
868
|
Additional rule notes:
|
|
766
869
|
|
|
767
870
|
- Relative and tsconfig-aliased imports resolve only to existing on-disk TS/JS targets; package bare imports are left to CI/TS.
|
|
768
|
-
- Value forbidden edges error (same pass/fail as `arkgate-check`). Type-only forbidden edges are **placement debt
|
|
769
|
-
- `no-forbidden-globals` applies from the file layer’s `forbiddenGlobals`; the `globals` option is only a standalone fallback when no project config applies, never an override that weakens the project contract. Layers without either surface are not inventively restricted. `process` also owns exact value imports of `process` / `node:process`; type-only forms, subpaths, and `child_process` stay excluded. If the same layer also denies the `process` capability, this rule is the single `FORBIDDEN_GLOBAL` voice.
|
|
871
|
+
- Value forbidden edges error (same pass/fail as `arkgate-check`). Type-only forbidden edges that are not `peerIsolation` walls are **placement debt**: a warning on `ark/architecture-advisory`, never an error — align with doctor `typeEdgePolicy`.
|
|
872
|
+
- `no-forbidden-globals` applies from the file layer’s `forbiddenGlobals`; the `globals` option is only a standalone fallback when no project config applies, never an override that weakens the project contract. Layers without either surface are not inventively restricted. It sees dotted access (`Date.now`), static bracket access (`Date['now']`, ``Date[`now`]``, `globalThis['fetch']`), and destructuring from a global (`const { now } = Date`, `const { now: n } = globalThis.Date`); a dynamic key (`Date[k]`) is not a verdict. `process` also owns exact value imports of `process` / `node:process`; type-only forms, subpaths, and `child_process` stay excluded. If the same layer also denies the `process` capability, this rule is the single `FORBIDDEN_GLOBAL` voice.
|
|
873
|
+
- `require-publish-source` uses the ark-check candidate test: the first argument is an Ark intent string (`'Domain.Order.Placed'`), an `{ intent }` object, or an intent-creator reference (`OrderPlaced`, `Events.OrderPlaced`). `source` counts in the first argument's `metadata`, or in the second or third argument. A generic `pubsub.publish('USER_CREATED', …)` or `client.publish({ topic })` is not flagged. The rule is silent without an `ark.config.json` and outside `include`, like ark-check.
|
|
770
874
|
- Without `ark.config.json`, `no-domain-infra-imports` emits no contract verdict.
|
|
771
875
|
- **ArkRun (RN06):** when `arkRun` is present, `no-arkrun-kernel-in-domain`,
|
|
772
876
|
`no-arkrun-direct-new`, and `no-arkrun-transport-bypass` reuse the same
|
|
@@ -779,6 +883,21 @@ Additional rule notes:
|
|
|
779
883
|
kernel imports (including type-only). Type-only broker imports do not flag
|
|
780
884
|
transport-bypass. Missing-root and undeclared emit/handle/depend are **not**
|
|
781
885
|
in this adapter — use CLI / preflight / CI.
|
|
886
|
+
- **ArkOrder:** when `arkOrder` is present, `no-arkorder-kernel-in-domain` flags an
|
|
887
|
+
`arkgate/order` import, export-from, `require`, or dynamic import in a Domain-role layer — a
|
|
888
|
+
layer named `Domain` / `DomainModel` / `Domain…`, or one whose `intentPrefixes` include
|
|
889
|
+
`Domain.` — with the same sensor as ark-check. `no-arkorder-generic-update` runs the
|
|
890
|
+
file-local ξ sensors: generic `update()`, ξ field writes, `ingest()` into a Release, and
|
|
891
|
+
oversized `release()`. Missing-plane stays CLI / preflight / CI.
|
|
892
|
+
- **ArkRules:** `arkrules-structure` resolves the same Effective Contract as ark-check (a
|
|
893
|
+
missing or invalid referenced file is the `configInvalid` error above) and runs the
|
|
894
|
+
**structure** sensors on the linted file: `aggregate-private-state`,
|
|
895
|
+
`always-valid-factory`, `domain-event-on-mutation`, `no-anemic-model` (always advisory),
|
|
896
|
+
and the text heuristics for `orchestration-only`, `thin-adapter`, and
|
|
897
|
+
`writes-via-aggregate`. Not in the ESLint adapter — use preflight, `ark_prepare_change`, or
|
|
898
|
+
CI: invariant coverage and `INVARIANT_*` findings (they need test globs across files),
|
|
899
|
+
empty `appliesTo` and empty invariant catalogs, the structural-hint budget, and persistence
|
|
900
|
+
hints that need resolved import layers.
|
|
782
901
|
|
|
783
902
|
Rule ids are `ark/<kebab-name>`. Individual rules are also on `ark.rules` if you wire them by hand.
|
|
784
903
|
Prefer keeping editor + CI on the same `ark.config.json`. Use the rule-local `globals` list only
|
|
@@ -822,10 +941,13 @@ but stays advisory.
|
|
|
822
941
|
**command**. The hard merge boundary is making that job a **required GitHub status context** —
|
|
823
942
|
not “workflow file present.”
|
|
824
943
|
|
|
825
|
-
Or use the repository's composite Action at a pinned release or commit
|
|
944
|
+
Or use the repository's composite Action at a pinned release or commit. Pin the tag (or commit
|
|
945
|
+
SHA) that matches the `arkgate` version in your `package.json` and write hook. The Action runs the
|
|
946
|
+
checker from the ref you pin, so an older ref runs an older checker, and that checker rejects newer
|
|
947
|
+
config keys (for example `schemaVersion` 1.3, `childSlices`, `sliceIdentity`) with exit 2:
|
|
826
948
|
|
|
827
949
|
```yaml
|
|
828
|
-
- uses: pedroknigge/arkgate@
|
|
950
|
+
- uses: pedroknigge/arkgate@v4.8.24 # same version as the arkgate devDependency
|
|
829
951
|
with:
|
|
830
952
|
root: .
|
|
831
953
|
config: ark.config.json
|
|
@@ -912,6 +1034,18 @@ npx ark-check --update-baseline # writes .ark-baseline.json — commit it
|
|
|
912
1034
|
npx ark-check --baseline # only NEW violations fail
|
|
913
1035
|
```
|
|
914
1036
|
|
|
1037
|
+
`--update-baseline` always measures the whole governed tree. It refuses `--changed` /
|
|
1038
|
+
`--local` and ignores `ARK_CHECK_LOCAL=1`, because a changed-files scan would overwrite the
|
|
1039
|
+
freeze with only that subset.
|
|
1040
|
+
|
|
1041
|
+
`ark-check --report` applies the committed `.ark-baseline.json` by default (the same file
|
|
1042
|
+
`--doctor` reads), so the report, `.ark/reports/latest.json`, and `ark status` agree with
|
|
1043
|
+
doctor on what is active versus frozen. A plain `ark-check` without `--baseline` is
|
|
1044
|
+
unchanged, and so is a merge verdict: with `--strict` / `--strict-merge` / `--contract-diff`,
|
|
1045
|
+
`--report` does **not** apply the implicit freeze (pass `--baseline` explicitly), so adding a
|
|
1046
|
+
reporting flag can never turn a failing merge run green. Only `--report` refreshes the `ark status` last-check snapshot; a plain check or
|
|
1047
|
+
`--doctor` does not write it.
|
|
1048
|
+
|
|
915
1049
|
`--update-baseline` also patches existing check **invocations** so CI keeps using the
|
|
916
1050
|
new freeze file: `package.json` scripts and GitHub workflow `run:` lines that already
|
|
917
1051
|
call `ark-check` / `arkgate-check` via `npx`, `pnpm`, `yarn`, `npm`, or `node`. Those
|
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 /
|