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/CHANGELOG.md
CHANGED
|
@@ -96,6 +96,97 @@ in the immutable pre-2.0 archive linked below.
|
|
|
96
96
|
Existing configs keep whatever URL they already have. Editor completion
|
|
97
97
|
matches the 4.x line you just installed.
|
|
98
98
|
|
|
99
|
+
## 4.8.24 — 2026-09-29
|
|
100
|
+
|
|
101
|
+
**Patch** over **4.8.23**. Follow-ups to the hierarchical slices shipped in 4.8.23
|
|
102
|
+
(issues #335–#338), every defect a full audit of the gate and its add-ons
|
|
103
|
+
confirmed, and much lower memory per validation (#339). **Write. Check. Ship.**
|
|
104
|
+
**No required config migration:** a config that loads in 4.8.23 still loads;
|
|
105
|
+
ids that changed meaning warn (`CONFIG_CHILD_SLICES_INERT`,
|
|
106
|
+
`CONFIG_SLICE_LEGACY_STARS_ID`) and keep their 4.8.23 verdict for one release.
|
|
107
|
+
No `schemaVersion` bump. Does not close `K01` / `Z09`. This mother
|
|
108
|
+
`ark.config.json` still does **not** turn `arkOrder` on.
|
|
109
|
+
|
|
110
|
+
**Minimum version 4.8.24 for the new config fields** (`sharedImportsSlice`
|
|
111
|
+
object form with `stopAt`, `childSlices.message`, `childSlices.siblings.ratchet`).
|
|
112
|
+
arkgate 4.8.23 and older reject them at config load. Pin 4.8.24 in the write
|
|
113
|
+
hook and CI first.
|
|
114
|
+
|
|
115
|
+
**Status: released** (tag `v4.8.24`). This file does not assert registry
|
|
116
|
+
state; run `npm view arkgate@4.8.24 version` to confirm (#314).
|
|
117
|
+
|
|
118
|
+
### Added
|
|
119
|
+
- `sharedImportsSlice` accepts `{ "mode": "deny-cross-parent", "stopAt": [...] }`. The whole-graph `CROSS_PARENT_VIA_SHARED` walk never starts at or passes through a stop file, so a declared composition root (`kernel/bootstrap.ts`, `kernel/registrations/**`) is no longer reported as a crossing. `stopAt` matches like a shared root or an alias glob; a stop that covers the whole tree or a whole layer root is rejected. Findings carry `via`, and doctor `sharedWalkHubs` names a likely undeclared composition root. Adding stops is a weakening policy delta. The string forms are unchanged. arkgate 4.8.23 and older reject the object (issue [#335](https://github.com/pedroknigge/arkgate/issues/335)).
|
|
120
|
+
- Optional `childSlices.message` sets the text for inner-wall findings (`CROSS_SIBLING_SLICE`, universe common code importing a child). Without it ArkGate uses its own text; the rule `message` stays the universe-wall text and is never reused on inner-wall findings. Same text in ark-check, CI, the write hook, MCP, and ESLint (issue [#337](https://github.com/pedroknigge/arkgate/issues/337)).
|
|
121
|
+
- Optional `childSlices.siblings.ratchet`: `true` always fails a new advisory crossing, `false` only measures (issue [#336](https://github.com/pedroknigge/arkgate/issues/336)).
|
|
122
|
+
- `arkgate mcp` / `ark mcp` start the stdio MCP server (same as `arkgate-mcp`).
|
|
123
|
+
- ESLint: warn-level `ark/architecture-advisory` rule (findings ark-check reports as warnings) and `ark/arkrules-structure` (ArkRules structure sensors on the same Effective Contract).
|
|
124
|
+
- New advisory `ARKRULE_FILE_UNREFERENCED` for an `arkrules/*.json` the config does not reference, and `doctor.rulesMigration`.
|
|
125
|
+
- `ark-dashboard --help`, `--version`, `--once`, `ARK_DASHBOARD_URL`.
|
|
126
|
+
- `npm run bench:memory` regression guard (budgets in `eval/performance/memory-budgets.v1.json`).
|
|
127
|
+
|
|
128
|
+
### Changed
|
|
129
|
+
- **Memory and speed.** ark-check, `--doctor`, the write hook, ApplyPatch preflight, the MCP server and the ESLint plugin use much less memory; verdicts are byte-identical. On a 20k-file repo: ark-check peak 422 → 303 MB (3.7 → 2.3 s), ApplyPatch hook 649 → 414 MB (9.5 → 3.6 s), Write hook 163 → 146 MB (0.68 → 0.24 s), MCP server after 50 calls 725 → 442 MB. Sources are read one at a time and released, the facts hash streams, module resolution is memoized per run, the Write/Edit hook no longer walks the whole `include` tree for tsconfig aliases, and preflight reuses base parses.
|
|
130
|
+
- Advisory sibling crossings are advisory on day 1. The anti-growth ratchet is judged per rule and turns on only when the baseline in use already records an advisory sibling crossing of that rule (issue [#336](https://github.com/pedroknigge/arkgate/issues/336)).
|
|
131
|
+
- `CONFIG_CHILD_SLICES_VERSION` (and the other new-key version warnings) fire only with evidence that the repo pins an older arkgate (exact `package.json` dependency, installed copy, lockfile, hook script, or CI workflow). The finding points at that file and line and names the version to bump to (issue [#338](https://github.com/pedroknigge/arkgate/issues/338)).
|
|
132
|
+
- `childSlices` on a rule without `peerIsolation: true` and `allowed: false` still loads (as in 4.8.23) but is inert; ark-check now warns `CONFIG_CHILD_SLICES_INERT` and says how to fix it (add both flags to the rule, or remove `childSlices`). No config that loads in 4.8.23 fails to load.
|
|
133
|
+
- `sliceIdentity: "stars"` keeps every star binding (`modules/*/api/*` binds `orders/api/v1`, not `api/v1`). **Migration:** a `sliceAliases` `to` or an `allowedCrossSlice` / `childSlices.allowedCrossSlice` entry written against the 4.8.23 id (`api/v1/x`, `api/v1`) keeps loading and keeps its 4.8.23 verdict for this release: the alias joins the universe whose old id it names, and the allowance still clears the edge it cleared. ark-check warns `CONFIG_SLICE_LEGACY_STARS_ID` and names the new id to write (`orders/api/v1/x`). A legacy alias target is rejected at config load only when it maps to more than one universe shape of the rule. The legacy form is removed in the next minor release.
|
|
134
|
+
- `childSlices.commonFolders` counts only a folder directly under the universe; the same name inside a child belongs to that child.
|
|
135
|
+
- The write hook fails closed: if it cannot run inside an Ark project (invalid config, missing ArkRules file, missing `dist/`), governed writes are denied with exit 2 (`WRITE_GATE_UNAVAILABLE`) instead of exit 1, which hosts let through. The hook finds its project root like ark-check: a set `--root-env` variable, else the root that holds `ark.config.json`, else it walks up from the payload (written file, `cwd`, `workspace_roots`) to the nearest `ark.config.json`. A write outside any Ark project (no `ark.config.json` found) is allowed, as in 4.8.23.
|
|
136
|
+
- `ark start --apply --remove-host <host>` removes only the Ark hook entries from a merged `.claude/settings.json`, `.codex/hooks.json`, `.cursor/hooks.json` or `.agents/hooks.json` (same identity as the merge) and keeps every user key and hook; the file is deleted only when nothing else remains. "customized" is reported only when the Ark entry itself was edited.
|
|
137
|
+
- An enforced invariant with `coverage: { test: false }` and no `coverage.symbol` is uncovered; `coverage.symbol: "Class.member"` needs the member declared in that class.
|
|
138
|
+
- ArkOrder `apply()` only accepts a proposal bound to the current Release (`ARKORDER_STALE_PROPOSAL`); `restore()` never changes ξ or rolls the version back; `ReleaseStore.load()` is validated; the plane advances only after `save()`; `informationBudget` is enforced when a Release is created.
|
|
139
|
+
- ArkRun: the default event buffer is capped and settles after local delivery; broker handoff failures are recorded (`event.handoffFailed`); config kernels with canonical layer names enforce hard observed-layer-flow.
|
|
140
|
+
- `ark-check --update-baseline` always freezes the whole tree and refuses `--changed` / `--local`.
|
|
141
|
+
|
|
142
|
+
### Fixed
|
|
143
|
+
- `--changed`, `--local`, `--against` and `--persona` no longer give a false green: paths resolve relative to `--root` (monorepo packages), non-ASCII names are kept, large or failed git listings exit 2, and a missing base ref exits 2 (`changed-needs-base`) instead of "nothing changed".
|
|
144
|
+
- `--changed` catches a new `CROSS_PARENT_VIA_SHARED` created by editing only a shared-root file, and no longer warns `CONFIG_LAYER_PATTERN_NO_MATCHES` for untouched layers.
|
|
145
|
+
- `ark-check --report`, `latest.json`, `--doctor` and `ark status` agree on frozen versus active findings.
|
|
146
|
+
- Write path parity with CI: the hook, `validate_code`, `ark_prepare_write`, `ark_prepare_change` and ApplyPatch preflight enforce ArkRules structure sensors and ArkOrder editor sensors, load ArkRules like ark-check, handle realpath aliases, and accept the full Codex `apply_patch` format.
|
|
147
|
+
- MCP: `validate_code` / `ark_check` output schemas pass MCP SDK validation (tools no longer disappear); a stale contract returns `CONTRACT_STALE`; `ark_status` honors `--config`; `arkgate-mcp --help` / `--version` exit.
|
|
148
|
+
- MCP Registry: `server.json` runs `npx arkgate@<version> mcp --root .`; the old descriptor never started the server.
|
|
149
|
+
- CommonJS TypeScript consumers: `.d.cts` declarations ship and are mapped for every entry.
|
|
150
|
+
- ESLint: warnings in ark-check are warnings in ESLint; messages carry the rule message and slice reason; ArkOrder rules show real text at 1-based columns; tsconfig JSONC / nearest tsconfig / `extends` are read correctly; an invalid config reports one diagnostic instead of crashing; `require('arkgate/eslint')` returns the plugin (it also carries a non-enumerable `__esModule`, so Babel `_interopRequireWildcard` and TypeScript `__importStar` still see every named export); `--cache` invalidates on contract change; forbidden-globals catches destructuring and bracket access.
|
|
151
|
+
- Slices: adapter diagnostics carry `reasonId` and the universe pair; `CROSS_PARENT_VIA_SHARED` honors the universe `allowedCrossSlice`; fail-closed reasons are named in the hook and MCP; folder-form `sliceAliases` cover their subtree; the flat-parent move card counts every resolved importer; `SHARED_IMPORTS_SLICE` is catalogued.
|
|
152
|
+
- Setup: `ark start --archetype/--preset` writes that shape; re-running `ark start --apply` works; `--remove-host` does not touch packages; existing `.claude/settings.json` / `.codex/hooks.json` are merged instead of skipped or wiped; `typecheck` is added only when TypeScript is present; the steward lock holds under `--strict-merge --contract-session`; `ark status --config` / `ark agents-md --config` work; starter ArkRules ids are unique.
|
|
153
|
+
- ArkRules: policy delta reads base ArkRules from the base ref (demoting an enforced rule is weakening); class-shape sensors see generic classes, ignore braces in strings and regexes, and skip a `static { … }` initialization block (it is not a public member); `ArkRulesValidationError` is exported from the root.
|
|
154
|
+
- ArkRun: undeclared emit/handle/depend count only kernel-traced receivers (`res.send`, `require.resolve`, rxjs `subscribe` are ignored); typed `send(Creator)` resolves names; `ArkModule.forRoot()` is a composition root; `arkgate/runtime` and `arkgate/nestjs` share one kernel chunk (`instanceof` works). A namespace import of a composition root (`import * as main from '../main'; main.ark.publisher(..)`) is traced as a kernel receiver, and an untraced receiver counts when its name carries one of the project's effective intent prefixes (configured `layers[].intentPrefixes` plus the canonical ones), not only `Domain.`/`Application.`….
|
|
155
|
+
- ArkOrder: `ARKORDER_XI_FIELD_WRITE` recognizes Prisma/ORM clients by construction, `GENERIC_UPDATE` / `INGEST_WRITES_XI` false positives removed, `xi-ttl` and `information-budget` sensors emit, NaN/Infinity rejected, documented types exported, and the shipped `bin/lib/ark-order-invariants.mjs` loads.
|
|
156
|
+
- Docs: the composite Action pin tracks the current release, and configuration.md names the real policy-delta entry points.
|
|
157
|
+
- Generated CI workflows, host hooks and MCP entries in a project that does not
|
|
158
|
+
have arkgate as a local dependency run `npx -y -p arkgate@<exact version> <bin>`
|
|
159
|
+
instead of a bare `npx ark-check` / `npx arkgate-mcp` (npm resolved those as a
|
|
160
|
+
nonexistent package, or ran a stale global). Without a `package.json` the workflow
|
|
161
|
+
skips the install step and the npm cache. `--migrate-commands` rewrites bare runners
|
|
162
|
+
into the pinned form, and `--doctor` leads with `PACKAGE_PIN_ABSENT` when a generated
|
|
163
|
+
file still uses the local runner without the pin.
|
|
164
|
+
- `ark-check --json` answers every fatal error with a JSON envelope (`POLICY_BASE_UNREADABLE` when a CI base ref such as `GITHUB_BASE_REF` cannot be read in the checkout, otherwise `CHECK_ERROR`), not only `CONFIG_INVALID`; exit code stays 2.
|
|
165
|
+
- Security: CodeQL `js/polynomial-redos` in the steward email check and two incomplete-sanitization alerts are closed.
|
|
166
|
+
|
|
167
|
+
## 4.8.23 — 2026-09-28
|
|
168
|
+
|
|
169
|
+
**Patch** over **4.8.22**. Hierarchical slices: the issue #326 series
|
|
170
|
+
(#327–#333). **Write. Check. Ship.** **No required config migration.** Every
|
|
171
|
+
new field is optional; absent keeps today's output. No `schemaVersion` bump.
|
|
172
|
+
Does not close `K01` / `Z09`. This mother `ark.config.json` still
|
|
173
|
+
does **not** turn `arkOrder` on.
|
|
174
|
+
|
|
175
|
+
**Minimum version 4.8.23 for the new config fields.** arkgate 4.8.22 and older
|
|
176
|
+
reject `childSlices` and `sharedImportsSlice: "deny-cross-parent"` at config
|
|
177
|
+
load. Pin 4.8.23 in the write hook and CI first.
|
|
178
|
+
|
|
179
|
+
**Status: released** (tag `v4.8.23`). This file does not assert registry
|
|
180
|
+
state; run `npm view arkgate@4.8.23 version` to confirm (#314).
|
|
181
|
+
|
|
182
|
+
### Added
|
|
183
|
+
- Optional `childSlices` on a `peerIsolation` rule: an inner wall under today's universe wall. Absent, behavior and output stay the same. Present, a cross-universe deny is `reasonId` `CROSS_PARENT_SLICE` (never advisory) and a sibling crossing is `CROSS_SIBLING_SLICE` (`siblings`: `deny` or `advisory`). `commonFolders` and flat files are universe common. `parentMayImportChild` defaults to false. The baseline key does not include `reasonId`. Advisory siblings still fail when they grow past the recorded baseline, and `productHonesty.finished` stays false while any child wall is advisory. Doctor reports `{ crossParent, crossSibling, pairs }`. arkgate 4.8.22 and older reject the key. Wildcards and aliases are not part of that introduction (#328).
|
|
184
|
+
- `sharedImportsSlice` may be `"deny-cross-parent"`. The direct shared-root hop stays a `SHARED_IMPORTS_SLICE` warning. ark-check and CI add `reasonId` `CROSS_PARENT_VIA_SHARED` when a slice reaches another universe only through a shared root. A shared hop inside one universe is not a finding. `"deny"` is unchanged. The write hook and ESLint see one edge at a time and do not block the path. The baseline key does not include `reasonId`. arkgate 4.8.22 and older reject the value at config load. Does not close `K01` (#329).
|
|
185
|
+
- `childSlices.siblings` may be `{ "default": "advisory" | "deny", "enforce": [...] }`. A `CROSS_SIBLING_SLICE` whose importer is an enforced child id or subtree path is an error. Other sibling crossings stay warnings, and a new one past the recorded baseline still fails. `default: "deny"` denies every sibling crossing. The string forms `"deny"` and `"advisory"` stay the same. `ruleId` stays `LAYER_IMPORT_VIOLATION`. The baseline key does not include `reasonId`. `productHonesty.finished` stays false while `default` is advisory. An enforce list does not finish the house. The write hook, ESLint, ark-check, and CI all see the per-edge severity. A build that still types `siblings` as a string enum rejects the object at config load. Does not close `K01` (#330).
|
|
186
|
+
- `childSlices.allowedCrossSlice` accepts a whole-segment `*` (`features/projects/*`). It clears only a `CROSS_SIBLING_SLICE` inside one universe. It does not clear `CROSS_PARENT_SLICE` or `CROSS_PARENT_VIA_SHARED`. A pattern that can name two universes warns `CONFIG_CHILD_SLICE_CROSS_UNIVERSE` and the cross-parent finding still fires. A bare name is rejected at config load. `**` and a partial segment are rejected. The universe `allowedCrossSlice` still treats `*` as a literal. Adding an entry is a weakening policy delta; a wildcard that widens a literal entry is weakening too. `ruleId` stays `LAYER_IMPORT_VIOLATION`. The baseline key does not include `reasonId`. The write hook, ESLint, ark-check, and CI all see the per-edge decision. A build that still rejects unknown `childSlices` fields rejects the key at config load. arkgate 4.8.22 and older reject `childSlices`. Does not close `K01` (#331).
|
|
187
|
+
- `childSlices.sliceAliases` maps a folder outside the slice trees onto a child slice (`{ "from": "lib/compliance/**", "to": "features/projects/compliance" }`). The file takes that universe id and that child id for both walls, so an import into another universe is `CROSS_PARENT_SLICE`. A bare name, a universe id alone, an unknown universe, and a wildcard in `to` are rejected at config load. A glob that overlaps a slice folder is rejected. Two aliases that match the same file are rejected. Doctor lists each alias as an owed move (files, target slice, destination folder) and does not count the files as finished. Adding aliases is a strengthening policy delta. Removing them is weakening. Additions and removals together, including a retarget, need a judgment. `ruleId` stays `LAYER_IMPORT_VIOLATION`. The baseline key does not include `reasonId`. The write hook, ESLint, snippet analysis, ark-check, and CI all see the alias. A build that still rejects unknown `childSlices` fields rejects the key at config load. arkgate 4.8.22 and older reject `childSlices`. Does not close `K01` (#332).
|
|
188
|
+
- Doctor suggests a move for a flat file that sits at universe level when exactly one child slice imports it (`doctor.flatParentPilot`). The card names the file, that child, and a destination folder inside the child that keeps the file's layer. Two importers, or none, produce no card. The sentence is a suggestion. It does not change ark-check, `ruleId`, `reasonId`, or the baseline key. The reshape destination check uses the same slice identity as the walls, so `sliceIdentity: "stars"` does not treat two children as one slice. No new config field. Does not close `K01` (#333).
|
|
189
|
+
|
|
99
190
|
## 4.8.22 — 2026-09-27
|
|
100
191
|
|
|
101
192
|
**Patch** over **4.8.21**. Daily accumulate (2026-09-27 ART) of the ships
|
package/README.md
CHANGED
|
@@ -25,11 +25,11 @@ Design the boundary before you implement. Put a small interface in front of the
|
|
|
25
25
|
|
|
26
26
|
## Try it
|
|
27
27
|
|
|
28
|
-
One command. Preview only — no files change. Current release is **4.8.
|
|
28
|
+
One command. Preview only — no files change. Current release is **4.8.24**
|
|
29
29
|
(check npm `latest` with `npm view arkgate version`):
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
npx arkgate@4.8.
|
|
32
|
+
npx arkgate@4.8.24 start
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
## What a red result looks like
|
|
@@ -320,9 +320,11 @@ queue monitors:
|
|
|
320
320
|
|
|
321
321
|
Poll those facts from the dual bins **`ark-dashboard`** / **`arkgate-dashboard`**
|
|
322
322
|
(`bin/ark-dashboard.mjs`). ANSI + polling only (no React/Ink/Blessed). Point
|
|
323
|
-
`--url` / `-u` at the inspector
|
|
324
|
-
|
|
325
|
-
|
|
323
|
+
`--url` / `-u` at the inspector (`handle.url` or `handle.snapshotUrl`); the dashboard
|
|
324
|
+
also fetches sibling `/outbox` and `/workflows`. `startInspector()` binds a random port
|
|
325
|
+
unless you pass `{ port }`, so the fallback `http://127.0.0.1:3000/snapshot` (or
|
|
326
|
+
`ARK_DASHBOARD_URL`) only matches `startInspector({ port: 3000 })`. `--interval` / `-i`
|
|
327
|
+
is clamped to 200–60000 ms (default 2000); `--once`, `--help`, `--version` are supported.
|
|
326
328
|
Also available as `ark dashboard` / `arkgate dashboard` (passthrough to the same bin). Kernel stays
|
|
327
329
|
JSON-only; presentation stays in `bin/`.
|
|
328
330
|
|
|
@@ -66,6 +66,7 @@ import {
|
|
|
66
66
|
printAdrPresenceHint,
|
|
67
67
|
} from './lib/doctor-plan.mjs';
|
|
68
68
|
import { runRatchetCores } from './lib/core-ratchet.mjs';
|
|
69
|
+
import { applyAdvisorySiblingRatchet } from './ark-layer-match.mjs';
|
|
69
70
|
import {
|
|
70
71
|
baselineKey,
|
|
71
72
|
baselineOccurrenceKeys,
|
|
@@ -105,7 +106,7 @@ import {
|
|
|
105
106
|
normalize,
|
|
106
107
|
walk,
|
|
107
108
|
} from './lib/scan-files.mjs';
|
|
108
|
-
import { configWarning } from './lib/config-warnings.mjs';
|
|
109
|
+
import { configInvalidJsonEnvelope, configWarning } from './lib/config-warnings.mjs';
|
|
109
110
|
import { runArchitectureScan } from './lib/architecture-scan.mjs';
|
|
110
111
|
import {
|
|
111
112
|
ANALYSIS_COMPLETENESS,
|
|
@@ -123,6 +124,7 @@ import {
|
|
|
123
124
|
applyAgainstRatchet,
|
|
124
125
|
bindTeamBaseRefs,
|
|
125
126
|
contractSessionFrom,
|
|
127
|
+
pruneScopedPatternWarnings,
|
|
126
128
|
runTeamPreflight,
|
|
127
129
|
ungovernedDumpMessage,
|
|
128
130
|
} from './lib/team-parliament-io.mjs';
|
|
@@ -134,7 +136,9 @@ import {
|
|
|
134
136
|
resolveEffectiveProjectRoot,
|
|
135
137
|
} from './lib/project-root.mjs';
|
|
136
138
|
import { demoteArkRuleTeethUnderClassificationFloor } from './lib/rules-under-contract.mjs';
|
|
137
|
-
import {
|
|
139
|
+
import {
|
|
140
|
+
effectiveBaselineName, localCheckEnvelope, parseArgs, resolveDesignDeltaBaseRef, UPDATE_BASELINE_SCOPE_MESSAGE,
|
|
141
|
+
} from './lib/check-args.mjs';
|
|
138
142
|
import { detectConfig, proposeForUncovered } from './lib/check-config-detect.mjs';
|
|
139
143
|
import { runWatchMode } from './lib/check-watch.mjs';
|
|
140
144
|
|
|
@@ -1332,6 +1336,7 @@ async function main() {
|
|
|
1332
1336
|
strictMerge: args.strictMerge || args.contractDiff,
|
|
1333
1337
|
basePath: args.policyBase,
|
|
1334
1338
|
baseRef: args.policyBaseRef,
|
|
1339
|
+
adoptMissingBase: args.policyBaseFromTeam === true,
|
|
1335
1340
|
acknowledgementPath: args.policyAck,
|
|
1336
1341
|
});
|
|
1337
1342
|
const preflight = runTeamPreflight({
|
|
@@ -1446,6 +1451,7 @@ async function main() {
|
|
|
1446
1451
|
args,
|
|
1447
1452
|
});
|
|
1448
1453
|
|
|
1454
|
+
if (args.changed) pruneScopedPatternWarnings(warnings, root, loadGovernedFiles);
|
|
1449
1455
|
// Align merge with extraMergeTeeth stamp: demote enforced ArkRules under classification floor.
|
|
1450
1456
|
const preCov = computeCoverage(root, config, files, rules);
|
|
1451
1457
|
const populatedLayerCount = Array.isArray(preCov.layers)
|
|
@@ -1514,6 +1520,8 @@ async function main() {
|
|
|
1514
1520
|
}
|
|
1515
1521
|
|
|
1516
1522
|
if (args.updateBaseline) {
|
|
1523
|
+
// A persona can turn on --changed after parseArgs; a subset scan must never rewrite the baseline.
|
|
1524
|
+
if (args.changed) { console.error(UPDATE_BASELINE_SCOPE_MESSAGE); process.exitCode = 2; return; }
|
|
1517
1525
|
if (!contractSessionFrom(args)) {
|
|
1518
1526
|
console.error('Growing the baseline requires --contract-session. Freeze in a law-only PR.');
|
|
1519
1527
|
process.exitCode = 1;
|
|
@@ -1586,15 +1594,18 @@ async function main() {
|
|
|
1586
1594
|
changed: args.changed,
|
|
1587
1595
|
changedPaths,
|
|
1588
1596
|
occurrenceKeys: baselineOccurrenceKeys(violations),
|
|
1597
|
+
rules,
|
|
1598
|
+
layers: config.layers,
|
|
1589
1599
|
});
|
|
1590
1600
|
activeViolations = ratcheted.activeViolations;
|
|
1591
1601
|
suppressed = ratcheted.suppressed;
|
|
1592
|
-
} else if (args
|
|
1593
|
-
const baseline = readBaseline(root, args
|
|
1602
|
+
} else if (effectiveBaselineName(args, root)) {
|
|
1603
|
+
const baseline = readBaseline(root, effectiveBaselineName(args, root));
|
|
1594
1604
|
if (baseline.exists) {
|
|
1595
1605
|
const occurrenceKeys = baselineOccurrenceKeys(violations);
|
|
1596
|
-
|
|
1597
|
-
|
|
1606
|
+
const judged = applyAdvisorySiblingRatchet(violations, occurrenceKeys, baseline.keys, { rules, layers: config.layers });
|
|
1607
|
+
suppressed = judged.filter((_, index) => baseline.keys.has(occurrenceKeys[index]));
|
|
1608
|
+
activeViolations = judged.filter((_, index) => !baseline.keys.has(occurrenceKeys[index]));
|
|
1598
1609
|
const currentKeys = new Set(occurrenceKeys);
|
|
1599
1610
|
staleBaselineKeys = [...baseline.keys].filter((key) => !currentKeys.has(key)).length;
|
|
1600
1611
|
} else {
|
|
@@ -1637,81 +1648,16 @@ async function main() {
|
|
|
1637
1648
|
}
|
|
1638
1649
|
|
|
1639
1650
|
if (args.rulesInventory) {
|
|
1640
|
-
|
|
1641
|
-
const
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
fileContents[rel] = fs.readFileSync(file, 'utf8');
|
|
1647
|
-
const layer = layerForFile(root, file, config.layers);
|
|
1648
|
-
if (layer) fileLayers[rel] = layer;
|
|
1649
|
-
} catch {
|
|
1650
|
-
/* skip unreadable */
|
|
1651
|
-
}
|
|
1652
|
-
}
|
|
1653
|
-
const contracted = [];
|
|
1654
|
-
if (config.arkRules) {
|
|
1655
|
-
try {
|
|
1656
|
-
const { loadEffectiveArkRulesFromDisk } = await import('./lib/effective-contract-load.mjs');
|
|
1657
|
-
const loaded = loadEffectiveArkRulesFromDisk(root, config);
|
|
1658
|
-
for (const rule of loaded.arkRules.structure ?? []) contracted.push(rule.id);
|
|
1659
|
-
for (const inv of loaded.arkRules.invariants ?? []) contracted.push(inv.id);
|
|
1660
|
-
} catch {
|
|
1661
|
-
/* advisory inventory still useful */
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
const inventory = buildRulesInventory({
|
|
1665
|
-
fileContents,
|
|
1666
|
-
fileLayers,
|
|
1667
|
-
layerContexts: (config.layers ?? []).map((layer) => ({
|
|
1668
|
-
name: layer.name,
|
|
1669
|
-
intentPrefixes: layer.intentPrefixes ?? [],
|
|
1670
|
-
})),
|
|
1671
|
-
contractedRuleIds: contracted,
|
|
1672
|
-
});
|
|
1673
|
-
const nextPilot =
|
|
1674
|
-
inventory.candidates[0] != null
|
|
1675
|
-
? inventoryToExtractionCard(inventory.candidates[0])
|
|
1676
|
-
: null;
|
|
1677
|
-
let evidenceLines = [];
|
|
1678
|
-
let coverageEvidence = null;
|
|
1679
|
-
if (config.arkRules && Object.keys(config.arkRules).length > 0) {
|
|
1680
|
-
try {
|
|
1681
|
-
const { summarizeRulesUnderContract, formatArkRulesEvidenceLines } = await import(
|
|
1682
|
-
'./lib/rules-under-contract.mjs'
|
|
1683
|
-
);
|
|
1684
|
-
const section = summarizeRulesUnderContract(root, config, {
|
|
1685
|
-
files: files.map((file) => ({ path: normalize(path.relative(root, file)) })),
|
|
1686
|
-
});
|
|
1687
|
-
evidenceLines = formatArkRulesEvidenceLines(section);
|
|
1688
|
-
if (section.active === true) {
|
|
1689
|
-
coverageEvidence = {
|
|
1690
|
-
symbolEvidence: section.symbolEvidence ?? [],
|
|
1691
|
-
discarded: section.coverageStats?.discarded ?? null,
|
|
1692
|
-
};
|
|
1693
|
-
}
|
|
1694
|
-
} catch {
|
|
1695
|
-
/* inventory still useful when coverage cannot be read */
|
|
1696
|
-
}
|
|
1697
|
-
}
|
|
1698
|
-
const payload = {
|
|
1699
|
-
rulesInventory: inventory,
|
|
1700
|
-
rulesMigration: {
|
|
1701
|
-
inventoried: inventory.inventoried,
|
|
1702
|
-
underContract: inventory.underContract,
|
|
1703
|
-
frozen: inventory.frozen,
|
|
1704
|
-
notAScore: true,
|
|
1705
|
-
},
|
|
1706
|
-
nextPilot: nextPilot,
|
|
1707
|
-
...(coverageEvidence ? { coverageEvidence } : {}),
|
|
1708
|
-
};
|
|
1651
|
+
// Same helper as MCP ark_rules_inventory and doctor.rulesMigration (AR15).
|
|
1652
|
+
const { buildRulesInventoryPayload, formatRulesMigrationCounts } = await import(
|
|
1653
|
+
'./lib/rules-under-contract.mjs'
|
|
1654
|
+
);
|
|
1655
|
+
const { payload, evidenceLines } = buildRulesInventoryPayload(root, config, files);
|
|
1656
|
+
const { rulesInventory: inventory, nextPilot } = payload;
|
|
1709
1657
|
if (args.json) {
|
|
1710
1658
|
console.log(JSON.stringify(payload, null, 2));
|
|
1711
1659
|
} else {
|
|
1712
|
-
console.log(
|
|
1713
|
-
`Rules inventory: ${inventory.inventoried} inventoried, ${inventory.underContract} under contract, ${inventory.frozen} frozen (not a score).`
|
|
1714
|
-
);
|
|
1660
|
+
console.log(`Rules inventory: ${formatRulesMigrationCounts(payload.rulesMigration)}.`);
|
|
1715
1661
|
for (const c of inventory.candidates.slice(0, 12)) {
|
|
1716
1662
|
console.log(` - [${c.confidence}] ${c.kind} @ ${c.file}:${c.line} — ${c.message}`);
|
|
1717
1663
|
}
|
|
@@ -1863,6 +1809,7 @@ async function main() {
|
|
|
1863
1809
|
? renderBeginnerHtmlReport(reportPayload)
|
|
1864
1810
|
: renderHtmlReport(reportPayload);
|
|
1865
1811
|
const reportPath = path.isAbsolute(args.report) ? args.report : path.join(root, args.report);
|
|
1812
|
+
fs.mkdirSync(path.dirname(reportPath), { recursive: true });
|
|
1866
1813
|
fs.writeFileSync(reportPath, html);
|
|
1867
1814
|
|
|
1868
1815
|
const archive = archiveReportSnapshots(root, {
|
|
@@ -2085,6 +2032,8 @@ async function main() {
|
|
|
2085
2032
|
}
|
|
2086
2033
|
|
|
2087
2034
|
main().catch((error) => {
|
|
2035
|
+
const envelope = configInvalidJsonEnvelope(error, process.argv);
|
|
2036
|
+
if (envelope) console.log(envelope);
|
|
2088
2037
|
console.error(
|
|
2089
2038
|
process.env.ARK_DEBUG_STACK === '1' && error instanceof Error
|
|
2090
2039
|
? error.stack
|
package/bin/ark-dashboard.mjs
CHANGED
|
@@ -1,43 +1,55 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
2
3
|
import { parseArgs } from 'node:util';
|
|
4
|
+
import {
|
|
5
|
+
DASHBOARD_DEFAULT_INTERVAL_MS,
|
|
6
|
+
DASHBOARD_DEFAULT_TIMEOUT_MS,
|
|
7
|
+
DASHBOARD_DEFAULT_URL,
|
|
8
|
+
DASHBOARD_EPHEMERAL_PORT_HINT,
|
|
9
|
+
DASHBOARD_HELP,
|
|
10
|
+
clampDashboardMs,
|
|
11
|
+
dashboardSiblingUrl,
|
|
12
|
+
} from './lib/dashboard-cli.mjs';
|
|
3
13
|
|
|
4
|
-
const DEFAULT_INTERVAL_MS = '2000';
|
|
5
|
-
const DEFAULT_SNAPSHOT_URL = 'http://127.0.0.1:3000/snapshot';
|
|
6
|
-
const DEFAULT_FETCH_TIMEOUT_MS = 5_000;
|
|
7
14
|
const MAX_FIELD_LEN = 120;
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
16
|
+
function parseDashboardArgs() {
|
|
17
|
+
try {
|
|
18
|
+
return parseArgs({
|
|
19
|
+
options: {
|
|
20
|
+
interval: { type: 'string', short: 'i' },
|
|
21
|
+
url: { type: 'string', short: 'u' },
|
|
22
|
+
timeout: { type: 'string', short: 't' },
|
|
23
|
+
once: { type: 'boolean' },
|
|
24
|
+
help: { type: 'boolean', short: 'h' },
|
|
25
|
+
version: { type: 'boolean', short: 'v' },
|
|
26
|
+
},
|
|
27
|
+
allowPositionals: true,
|
|
28
|
+
});
|
|
29
|
+
} catch (err) {
|
|
30
|
+
const message = err instanceof Error ? err.message.split('. ')[0] : String(err);
|
|
31
|
+
process.stderr.write(`ark-dashboard: ${message}. Run ark-dashboard --help.\n`);
|
|
32
|
+
process.exit(2);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const args = parseDashboardArgs();
|
|
37
|
+
if (args.values.help || args.positionals[0] === 'help') {
|
|
38
|
+
console.log(DASHBOARD_HELP);
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
if (args.values.version) {
|
|
42
|
+
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
43
|
+
console.log(pkg.version);
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
29
46
|
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
const parsedTimeout = parseInt(args.values.timeout, 10);
|
|
37
|
-
const fetchTimeoutMs =
|
|
38
|
-
Number.isFinite(parsedTimeout) && parsedTimeout >= 200 && parsedTimeout <= 60_000
|
|
39
|
-
? parsedTimeout
|
|
40
|
-
: DEFAULT_FETCH_TIMEOUT_MS;
|
|
47
|
+
const envUrl = process.env.ARK_DASHBOARD_URL?.trim();
|
|
48
|
+
const urlExplicit = Boolean(args.values.url || envUrl);
|
|
49
|
+
const targetUrl = args.values.url || envUrl || DASHBOARD_DEFAULT_URL;
|
|
50
|
+
const interval = clampDashboardMs(args.values.interval, DASHBOARD_DEFAULT_INTERVAL_MS);
|
|
51
|
+
const fetchTimeoutMs = clampDashboardMs(args.values.timeout, DASHBOARD_DEFAULT_TIMEOUT_MS);
|
|
52
|
+
const once = Boolean(args.values.once);
|
|
41
53
|
|
|
42
54
|
const RESET = '\x1b[0m';
|
|
43
55
|
const RED = '\x1b[31m';
|
|
@@ -73,22 +85,6 @@ function sanitizeField(value, maxLen = MAX_FIELD_LEN) {
|
|
|
73
85
|
return text;
|
|
74
86
|
}
|
|
75
87
|
|
|
76
|
-
function siblingUrl(snapshotUrl, suffix) {
|
|
77
|
-
try {
|
|
78
|
-
const u = new URL(snapshotUrl);
|
|
79
|
-
let basePath = u.pathname;
|
|
80
|
-
if (basePath.endsWith('/snapshot')) {
|
|
81
|
-
basePath = basePath.slice(0, -'/snapshot'.length);
|
|
82
|
-
} else if (basePath.endsWith('/snapshot/')) {
|
|
83
|
-
basePath = basePath.slice(0, -'/snapshot/'.length);
|
|
84
|
-
}
|
|
85
|
-
u.pathname = `${basePath}${suffix}`;
|
|
86
|
-
return u.toString();
|
|
87
|
-
} catch {
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
88
|
/**
|
|
93
89
|
* @returns {{ ok: true, data: unknown } | { ok: false, kind: 'waiting' | 'failure', error?: string }}
|
|
94
90
|
*/
|
|
@@ -340,6 +336,7 @@ function renderHardening(snapshotResult) {
|
|
|
340
336
|
} else {
|
|
341
337
|
const detail = snapshotResult.error ? ` (${snapshotResult.error})` : '';
|
|
342
338
|
console.log(`${RED}Failure contacting kernel${detail}${RESET}`);
|
|
339
|
+
if (!urlExplicit) console.log(`${MUTED}${DASHBOARD_EPHEMERAL_PORT_HINT}${RESET}`);
|
|
343
340
|
}
|
|
344
341
|
return;
|
|
345
342
|
}
|
|
@@ -381,13 +378,13 @@ function renderHardening(snapshotResult) {
|
|
|
381
378
|
}
|
|
382
379
|
|
|
383
380
|
async function render() {
|
|
384
|
-
process.stdout.write('\x1b[2J\x1b[H');
|
|
381
|
+
if (!once) process.stdout.write('\x1b[2J\x1b[H');
|
|
385
382
|
console.log(`ArkGate Observability Dashboard`);
|
|
386
383
|
console.log(`Time: ${sanitizeField(new Date().toISOString())}`);
|
|
387
384
|
console.log(`Endpoint: ${sanitizeField(targetUrl)}\n`);
|
|
388
385
|
|
|
389
|
-
const outboxUrl =
|
|
390
|
-
const workflowsUrl =
|
|
386
|
+
const outboxUrl = dashboardSiblingUrl(targetUrl, '/outbox');
|
|
387
|
+
const workflowsUrl = dashboardSiblingUrl(targetUrl, '/workflows');
|
|
391
388
|
const [snapshotResult, outboxResult, workflowsResult] = await Promise.all([
|
|
392
389
|
fetchJsonResult(targetUrl),
|
|
393
390
|
fetchJsonResult(outboxUrl),
|
|
@@ -404,9 +401,15 @@ async function render() {
|
|
|
404
401
|
snapshot?.outbox,
|
|
405
402
|
snapshot?.workflows,
|
|
406
403
|
);
|
|
404
|
+
return snapshotResult.ok;
|
|
407
405
|
}
|
|
408
406
|
|
|
409
407
|
async function startDashboard() {
|
|
408
|
+
if (once) {
|
|
409
|
+
const ok = await render();
|
|
410
|
+
process.exitCode = ok ? 0 : 1;
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
410
413
|
console.log(
|
|
411
414
|
`Starting ArkGate Observability Dashboard (polling every ${interval}ms, fetch timeout ${fetchTimeoutMs}ms)`,
|
|
412
415
|
);
|