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