@githolon/dsl 0.92.0 → 0.93.0
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/dart_types/CHANGELOG.md +4 -0
- package/package.json +1 -1
- package/src/codegen_ts.ts +6 -0
- package/src/compat_manifest.ts +7 -0
- package/src/compile_package_main.ts +15 -0
- package/src/index.ts +14 -0
- package/src/kernel_capabilities.ts +245 -0
package/dart_types/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
## 0.5.0
|
|
2
|
+
|
|
3
|
+
- Add typed `SourceSessionNotMounted` error (code `nomos.attested_read.source_not_mounted`) for the colocated attested-read fix — a foreign `read(q, {from})` whose source workspace isn't mounted now fails fast with a typed error + `mountSourceSession` remedy.
|
|
4
|
+
|
|
1
5
|
## 0.4.1
|
|
2
6
|
|
|
3
7
|
- `canonicalPrincipal` / `signerObjectOf` — THE one principal normalizer (kernel truth:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@githolon/dsl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.93.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Nomos 2 domain-authoring DSL: aggregates + directives in TS, executed and encoded to the Rust kernel's wire shapes.",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE.md",
|
package/src/codegen_ts.ts
CHANGED
|
@@ -1086,6 +1086,9 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
1086
1086
|
` * and the holon computes the LIVE per-member verdict on \`holon.compat\`. A KNOWN-incompatible directive`,
|
|
1087
1087
|
` * then refuses CLIENT-SIDE at write time (LawMismatchError), never as a server 422. */`,
|
|
1088
1088
|
` checkCompat?(compat: { lawHash: string; interfaceHash: string; packageName: string }, requirement?: unknown): NomosCompatVerdict | null;`,
|
|
1089
|
+
` /** RELEASE COHERENCE: throws a typed KernelCoherenceError at bind if the cloud's kernel lacks a`,
|
|
1090
|
+
` * capability this law requires (e.g. \`compoundKeys\`) — instead of an opaque deep fold refusal. */`,
|
|
1091
|
+
` assertKernelCoherence?(requiresKernel: readonly string[], opts?: { lawHash?: string }): unknown;`,
|
|
1089
1092
|
` /** The live compat verdict (set by checkCompat). \`has(memberId)\` is the dark-launch feature flag;`,
|
|
1090
1093
|
` * \`refresh()\` re-derives against the current projection. Absent until the factory binds. */`,
|
|
1091
1094
|
` compat?: NomosCompatVerdict;`,
|
|
@@ -2109,6 +2112,9 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
2109
2112
|
opts.interface !== undefined
|
|
2110
2113
|
? ` holon.checkCompat?.(${compatConstName(opts.packageName)}, ${interfaceConstName(opts.packageName)});`
|
|
2111
2114
|
: ` holon.checkCompat?.(${compatConstName(opts.packageName)});`,
|
|
2115
|
+
// RELEASE COHERENCE: throw a typed skew error AT BIND if the cloud's kernel lacks a capability
|
|
2116
|
+
// this law requires (e.g. compoundKeys) — instead of the opaque deep fold refusal later.
|
|
2117
|
+
` holon.assertKernelCoherence?.(${compatConstName(opts.packageName)}.requiresKernel ?? [], { lawHash: ${compatConstName(opts.packageName)}.lawHash });`,
|
|
2112
2118
|
]
|
|
2113
2119
|
: []),
|
|
2114
2120
|
` return {`,
|
package/src/compat_manifest.ts
CHANGED
|
@@ -17,4 +17,11 @@ export interface NomosCompat {
|
|
|
17
17
|
readonly domains: Record<string, { readonly identityHash?: string }>;
|
|
18
18
|
/** The generator provenance — the `@githolon/dsl` version + this manifest's schema. */
|
|
19
19
|
readonly generator: { readonly dsl: string; readonly schema: number };
|
|
20
|
+
/** RELEASE COHERENCE (kernel_capabilities.ts): the named kernel capabilities this law
|
|
21
|
+
* REQUIRES to fold (derived from its shape — e.g. `compoundKeys` for a `.key(a, b)` query).
|
|
22
|
+
* connect() / deploy compares this against the target kernel's advertised capabilities and
|
|
23
|
+
* fails with a TYPED message naming the mismatch, INSTEAD of the opaque deep-fold refusal.
|
|
24
|
+
* Empty ⇒ no special capability needed (byte-identical to before this field for such a law
|
|
25
|
+
* — but note: this manifest is frontend-only, never in the USD IR / domainHash). */
|
|
26
|
+
readonly requiresKernel: readonly string[];
|
|
20
27
|
}
|
|
@@ -132,6 +132,7 @@ import { domainInterface, type DomainInterface } from "./shape_compat.js";
|
|
|
132
132
|
import { generateTsProof, proofLegsIndex } from "./codegen_proof.js";
|
|
133
133
|
import { checkDomainModules, formatFindings, hasRefusal } from "./law_check.js";
|
|
134
134
|
import type { NomosCompat } from "./compat_manifest.js";
|
|
135
|
+
import { requiredKernelFeatures, type FeatureProbeModule } from "./kernel_capabilities.js";
|
|
135
136
|
|
|
136
137
|
const DSL_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
137
138
|
|
|
@@ -2552,12 +2553,26 @@ async function main(): Promise<void> {
|
|
|
2552
2553
|
const identityHash = identity.hashes[key];
|
|
2553
2554
|
compatDomains[key] = identityHash === undefined ? {} : { identityHash };
|
|
2554
2555
|
}
|
|
2556
|
+
// RELEASE COHERENCE — the kernel capabilities this law REQUIRES (compound-key queries +
|
|
2557
|
+
// era-key features). Baked into the client so connect() can check it against the target
|
|
2558
|
+
// kernel's advertised capabilities BEFORE the law folds (kernel_capabilities.ts).
|
|
2559
|
+
const requiresKernel = ((): string[] => {
|
|
2560
|
+
const probes: FeatureProbeModule[] = domainModules.map((m) => (m.queries ? { queries: m.queries } : {}));
|
|
2561
|
+
try {
|
|
2562
|
+
const parsed = JSON.parse(usdJson) as { layers?: Record<string, unknown>[] };
|
|
2563
|
+
for (const layer of parsed.layers ?? []) probes.push({ layer });
|
|
2564
|
+
} catch {
|
|
2565
|
+
/* shape-derived features (compoundKeys) still apply from the queries above */
|
|
2566
|
+
}
|
|
2567
|
+
return requiredKernelFeatures(probes);
|
|
2568
|
+
})();
|
|
2555
2569
|
const nomosCompat: NomosCompat = {
|
|
2556
2570
|
packageName: cfg.name,
|
|
2557
2571
|
lawHash: domainHash,
|
|
2558
2572
|
interfaceHash: sha256HexUtf8(canonicalJson(packageInterface)),
|
|
2559
2573
|
domains: compatDomains,
|
|
2560
2574
|
generator: { dsl: dslVersion, schema: 1 },
|
|
2575
|
+
requiresKernel,
|
|
2561
2576
|
};
|
|
2562
2577
|
|
|
2563
2578
|
// ── 3b. the OpenAPI spec — THE PACKAGE'S SELF-DESCRIPTION (the host's runtime routing table;
|
package/src/index.ts
CHANGED
|
@@ -148,6 +148,20 @@ export { repair, repairsOf, isRepairDecl, type RepairDecl, type RepairMatcher, t
|
|
|
148
148
|
// `nomos-compile` REFUSES law that references read() (a named compile error, never a runtime halt).
|
|
149
149
|
// The captured-reads wave (task #58) wires the capability in and lifts the refusal.
|
|
150
150
|
export { read } from "./read.js";
|
|
151
|
+
// RELEASE COHERENCE — the kernel capability stamp + law↔kernel fit check (kernel_capabilities.ts).
|
|
152
|
+
export {
|
|
153
|
+
requiredKernelFeatures,
|
|
154
|
+
capabilitiesForKernelHash,
|
|
155
|
+
assessKernelCoherence,
|
|
156
|
+
KernelCoherenceError,
|
|
157
|
+
KERNEL_CAPABILITY_REGISTRY,
|
|
158
|
+
ERA_KEY_TO_FEATURE,
|
|
159
|
+
type KernelFeature,
|
|
160
|
+
type KernelStamp,
|
|
161
|
+
type KernelCoherenceVerdict,
|
|
162
|
+
type KernelRegistryEntry,
|
|
163
|
+
type FeatureProbeModule,
|
|
164
|
+
} from "./kernel_capabilities.js";
|
|
151
165
|
// ATTESTED CROSS-WORKSPACE READS (slice B — attested_reads_design.md): the declared trust
|
|
152
166
|
// surface (`trustSource`), the foreign query reference, and the reader-side SourceFrontier.
|
|
153
167
|
export {
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// RELEASE COHERENCE — the kernel capability stamp + the law↔kernel fit check.
|
|
2
|
+
//
|
|
3
|
+
// THE PROBLEM THIS SOLVES (the co2 skew, 2026-07): a published release can be
|
|
4
|
+
// internally inconsistent. The production kernel accepts law that a compiler emits;
|
|
5
|
+
// the DEV/test-mint kernel a `githolon cloud dev` serves can be STALE and REFUSE the
|
|
6
|
+
// same law — but only at runtime, deep in a law fold ("query declares 2 index keys;
|
|
7
|
+
// the read router supports exactly one"). Four artifacts drift independently:
|
|
8
|
+
// · the compiler (@githolon/dsl) that EMITS the law,
|
|
9
|
+
// · the runner bundle (@githolon/client) that RUNS it,
|
|
10
|
+
// · the production kernel wasm the cloud serves,
|
|
11
|
+
// · the test-mint kernel wasm the dev cloud serves.
|
|
12
|
+
// There is NO version/hash check that catches the skew before the opaque refusal.
|
|
13
|
+
//
|
|
14
|
+
// THE FIX: a kernel advertises a VERSION + a set of named CAPABILITIES (feature flags
|
|
15
|
+
// keyed to the era keys / read-router lanes it implements). A package DECLARES the
|
|
16
|
+
// kernel capabilities its law REQUIRES (derived from its shape — e.g. a compound-key
|
|
17
|
+
// query needs `compoundKeys`). Before the law ever folds, we compare the two and fail
|
|
18
|
+
// with a TYPED, human-legible message NAMING the mismatch — not the deep refusal.
|
|
19
|
+
//
|
|
20
|
+
// This module is the SINGLE SOURCE OF TRUTH for: the capability vocabulary, deriving a
|
|
21
|
+
// package's requirements from its shape, the known-kernel registry (keyed by wasm
|
|
22
|
+
// sha256), and the coherence assessment. The CLI (`githolon doctor`, deploy), the
|
|
23
|
+
// generated client (baked `requiresKernel`), and the cloud worker's
|
|
24
|
+
// `/v1/runtime/capabilities` endpoint all draw on it.
|
|
25
|
+
|
|
26
|
+
import type { QueryDecl } from "./query.js";
|
|
27
|
+
|
|
28
|
+
/** A named kernel capability — a feature/lane the wasm kernel must implement for a law
|
|
29
|
+
* that uses it to fold. Each maps to a read-router lane or an era key. Additive: a new
|
|
30
|
+
* capability name is minted here the same day the kernel arm ships. */
|
|
31
|
+
export type KernelFeature =
|
|
32
|
+
/** The read router accepts a query with MORE THAN ONE index key (`.key(a, b, …)`),
|
|
33
|
+
* routing it as a compound (AND) intersection. Pre-this kernels refuse >1 index key
|
|
34
|
+
* ("the read router supports exactly one"). THE CONCRETE co2 SKEW. */
|
|
35
|
+
| "compoundKeys"
|
|
36
|
+
/** The #58 captured-read lane (`read()` in deployable law + `.reads(query)`). */
|
|
37
|
+
| "capturedReads"
|
|
38
|
+
/** The #41 declared-invariant gate (`invariant` / `workspaceInvariant`). */
|
|
39
|
+
| "invariantGate"
|
|
40
|
+
/** Surfaced(inner) — retained concurrent competing values as a pending set. */
|
|
41
|
+
| "surfacedMerge"
|
|
42
|
+
/** `.attributed()` / aggregate `{attributed:true}` per-field provenance. */
|
|
43
|
+
| "fieldProvenance"
|
|
44
|
+
/** Null as an opt-in named capability (`admitsNull()`). */
|
|
45
|
+
| "nullSemantics"
|
|
46
|
+
/** Dynamic attested reads under a trustPolicy allowList. */
|
|
47
|
+
| "attestedReads";
|
|
48
|
+
|
|
49
|
+
/** The USD-IR era key each derived-from-law capability rides on, for the requirement
|
|
50
|
+
* derivation below (a package that emits the era key needs the capability). */
|
|
51
|
+
export const ERA_KEY_TO_FEATURE: Readonly<Record<string, KernelFeature>> = {
|
|
52
|
+
nomosReadGate: "capturedReads",
|
|
53
|
+
nomosInvariantGate: "invariantGate",
|
|
54
|
+
nomosSurfaced: "surfacedMerge",
|
|
55
|
+
nomosAttributed: "fieldProvenance",
|
|
56
|
+
nomosNullable: "nullSemantics",
|
|
57
|
+
nomosAttestedReads: "attestedReads",
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** A minimal shape of a compiled domain module — enough to derive its kernel needs. */
|
|
61
|
+
export interface FeatureProbeModule {
|
|
62
|
+
readonly queries?: readonly QueryDecl[];
|
|
63
|
+
/** The domain's USD-IR layer (the era keys ride here). Optional — shape-derived
|
|
64
|
+
* features (compoundKeys) work without it. */
|
|
65
|
+
readonly layer?: Record<string, unknown>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** DERIVE the kernel capabilities a package's law REQUIRES, from its shape + era keys.
|
|
69
|
+
* The result is a sorted, de-duped list baked into the client compat manifest and
|
|
70
|
+
* checked against a target kernel's advertised capabilities before the law folds.
|
|
71
|
+
*
|
|
72
|
+
* The load-bearing case: ANY query with a compound (`.key(a, b)`) index needs
|
|
73
|
+
* `compoundKeys` — exactly the lane the stale test-mint kernel lacked. */
|
|
74
|
+
export function requiredKernelFeatures(modules: readonly FeatureProbeModule[]): KernelFeature[] {
|
|
75
|
+
const needs = new Set<KernelFeature>();
|
|
76
|
+
for (const m of modules) {
|
|
77
|
+
for (const q of m.queries ?? []) {
|
|
78
|
+
if (Array.isArray(q.key) && q.key.length > 1) needs.add("compoundKeys");
|
|
79
|
+
}
|
|
80
|
+
if (m.layer) {
|
|
81
|
+
for (const [eraKey, feature] of Object.entries(ERA_KEY_TO_FEATURE)) {
|
|
82
|
+
if (m.layer[eraKey] !== undefined) needs.add(feature);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return [...needs].sort();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** A kernel's self-description: its version tag + the capabilities it implements. This is
|
|
90
|
+
* the shape the kernel stamps (see `kernelStamp` op in wasm_git_holon.rs) AND the shape
|
|
91
|
+
* the host `/v1/runtime/capabilities` endpoint synthesizes for the deployed wasm it can't
|
|
92
|
+
* yet ask (keyed by the wasm sha256 in the registry below). */
|
|
93
|
+
export interface KernelStamp {
|
|
94
|
+
/** The kernel semver-ish version tag baked into the wasm at build. */
|
|
95
|
+
readonly version: string;
|
|
96
|
+
/** The wasm artifact sha256 (64-hex), when known. */
|
|
97
|
+
readonly hash?: string;
|
|
98
|
+
/** The named capabilities this kernel implements. */
|
|
99
|
+
readonly features: readonly KernelFeature[];
|
|
100
|
+
/** Human note (e.g. "production", "test-mint (DEV ONLY)") — surfaced in the skew message. */
|
|
101
|
+
readonly note?: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** THE KNOWN-KERNEL REGISTRY — maps a kernel wasm sha256 PREFIX to its capability stamp.
|
|
105
|
+
* Keyed by prefix so a short hash (the co2 report cites `846c6bf8` / `f81cb952`) resolves.
|
|
106
|
+
*
|
|
107
|
+
* WHY host-side and not only asked of the kernel: the CURRENTLY-DEPLOYED stale kernel
|
|
108
|
+
* predates the stamp op — it cannot answer "what are your capabilities?". Until every
|
|
109
|
+
* serving kernel carries the in-wasm stamp, this registry lets the host + client resolve
|
|
110
|
+
* a deployed wasm's capabilities from its BYTES, which is always available. A kernel that
|
|
111
|
+
* DOES carry the stamp op is preferred; this is the fallback that works today.
|
|
112
|
+
*
|
|
113
|
+
* Add an entry the moment a new kernel wasm is cut (its sha256 + the features it gained). */
|
|
114
|
+
export interface KernelRegistryEntry extends KernelStamp {
|
|
115
|
+
readonly prefix: string;
|
|
116
|
+
/** Human note (e.g. "production", "test-mint (DEV ONLY)"). */
|
|
117
|
+
readonly note?: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const KERNEL_CAPABILITY_REGISTRY: readonly KernelRegistryEntry[] = [
|
|
121
|
+
{
|
|
122
|
+
prefix: "846c6bf8",
|
|
123
|
+
version: "kernel-846c6bf8",
|
|
124
|
+
note: "production (accepts compound-key law)",
|
|
125
|
+
features: [
|
|
126
|
+
"compoundKeys",
|
|
127
|
+
"capturedReads",
|
|
128
|
+
"invariantGate",
|
|
129
|
+
"surfacedMerge",
|
|
130
|
+
"fieldProvenance",
|
|
131
|
+
"nullSemantics",
|
|
132
|
+
"attestedReads",
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
// CURRENT test-mint kernel (rebuilt from the SAME source as prod 846c6bf8, 2026-07-14; staged to R2 +
|
|
137
|
+
// built/staged by deploy.sh every release). Feature-equivalent to production — the dev cloud is coherent.
|
|
138
|
+
prefix: "f6e44b3a",
|
|
139
|
+
version: "kernel-f6e44b3a",
|
|
140
|
+
note: "test-mint (DEV) — current, feature-equivalent to production",
|
|
141
|
+
features: [
|
|
142
|
+
"compoundKeys",
|
|
143
|
+
"capturedReads",
|
|
144
|
+
"invariantGate",
|
|
145
|
+
"surfacedMerge",
|
|
146
|
+
"fieldProvenance",
|
|
147
|
+
"nullSemantics",
|
|
148
|
+
"attestedReads",
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
prefix: "f81cb952",
|
|
153
|
+
version: "kernel-f81cb952",
|
|
154
|
+
note: "test-mint (DEV ONLY — STALE: predates compound-key read router; superseded by f6e44b3a)",
|
|
155
|
+
features: [
|
|
156
|
+
"capturedReads",
|
|
157
|
+
"invariantGate",
|
|
158
|
+
"surfacedMerge",
|
|
159
|
+
"fieldProvenance",
|
|
160
|
+
"nullSemantics",
|
|
161
|
+
"attestedReads",
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
/** Resolve a kernel wasm hash (full or short) to its known capability stamp, or `null`
|
|
167
|
+
* when the hash is unknown to the registry (a kernel we've never catalogued). */
|
|
168
|
+
export function capabilitiesForKernelHash(hash: string | undefined | null): KernelStamp | null {
|
|
169
|
+
if (typeof hash !== "string" || hash.length < 6) return null;
|
|
170
|
+
const h = hash.toLowerCase().replace(/^sha256:|^policy:/, "");
|
|
171
|
+
const hit = KERNEL_CAPABILITY_REGISTRY.find((e) => h.startsWith(e.prefix) || e.prefix.startsWith(h));
|
|
172
|
+
return hit
|
|
173
|
+
? { version: hit.version, hash: h, features: hit.features, ...(hit.note ? { note: hit.note } : {}) }
|
|
174
|
+
: null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** The verdict of a law↔kernel coherence assessment. */
|
|
178
|
+
export interface KernelCoherenceVerdict {
|
|
179
|
+
readonly ok: boolean;
|
|
180
|
+
/** The required features the target kernel does NOT advertise. Empty ⇒ coherent. */
|
|
181
|
+
readonly missing: KernelFeature[];
|
|
182
|
+
/** A human-legible one-line message — the thing surfaced INSTEAD of the deep fold refusal. */
|
|
183
|
+
readonly message: string;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** ASSESS whether a target kernel can fold a package's law. `requires` is the package's
|
|
187
|
+
* derived requirement ({@link requiredKernelFeatures}); `kernel` is the target's stamp
|
|
188
|
+
* (from its stamp op, or {@link capabilitiesForKernelHash}). A `null` kernel stamp means
|
|
189
|
+
* the target's capabilities are UNKNOWN — reported distinctly (can't prove coherence).
|
|
190
|
+
*
|
|
191
|
+
* The message names BOTH hashes and the offending capability — the co2 skew, legibly. */
|
|
192
|
+
export function assessKernelCoherence(params: {
|
|
193
|
+
requires: readonly KernelFeature[];
|
|
194
|
+
kernel: KernelStamp | null;
|
|
195
|
+
/** The target kernel's hash, for the message even when the stamp is unknown. */
|
|
196
|
+
kernelHash?: string;
|
|
197
|
+
/** The law's hash, for the message. */
|
|
198
|
+
lawHash?: string;
|
|
199
|
+
}): KernelCoherenceVerdict {
|
|
200
|
+
const { requires, kernel } = params;
|
|
201
|
+
const short = (h?: string) => (h ? `${String(h).replace(/^sha256:|^policy:/, "").slice(0, 8)}` : "<unknown>");
|
|
202
|
+
const lawH = short(params.lawHash);
|
|
203
|
+
const kernH = short(params.kernelHash ?? kernel?.hash);
|
|
204
|
+
if (requires.length === 0) {
|
|
205
|
+
return { ok: true, missing: [], message: `law ${lawH} requires no special kernel capabilities` };
|
|
206
|
+
}
|
|
207
|
+
if (kernel === null) {
|
|
208
|
+
return {
|
|
209
|
+
ok: false,
|
|
210
|
+
missing: [...requires],
|
|
211
|
+
message:
|
|
212
|
+
`cannot verify release coherence — this cloud's kernel ${kernH} is UNKNOWN to the ` +
|
|
213
|
+
`capability registry, so it cannot be confirmed to support [${requires.join(", ")}] that ` +
|
|
214
|
+
`law ${lawH} requires. Update the kernel registry or upgrade the cloud's kernel.`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const missing = requires.filter((f) => !kernel.features.includes(f)) as KernelFeature[];
|
|
218
|
+
if (missing.length === 0) {
|
|
219
|
+
return { ok: true, missing: [], message: `kernel ${kernH} supports all capabilities law ${lawH} requires` };
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
ok: false,
|
|
223
|
+
missing,
|
|
224
|
+
message:
|
|
225
|
+
`this cloud's kernel ${kernH} lacks capability '${missing.join("', '")}' that law ${lawH} requires — ` +
|
|
226
|
+
`the cloud is running a STALE kernel (${kernel.note ?? kernel.version}). ` +
|
|
227
|
+
(missing.includes("compoundKeys")
|
|
228
|
+
? `A compound-key query (\`.key(a, b, …)\`) will be refused deep in a law fold with ` +
|
|
229
|
+
`"query declares N index keys; the read router supports exactly one". `
|
|
230
|
+
: ``) +
|
|
231
|
+
`Upgrade the cloud's kernel (or point at a cloud whose kernel is coherent with this release).`,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** A typed error thrown when a coherence assertion fails — carries the structured verdict so
|
|
236
|
+
* callers can render it or branch, instead of parsing a message. */
|
|
237
|
+
export class KernelCoherenceError extends Error {
|
|
238
|
+
readonly verdict: KernelCoherenceVerdict;
|
|
239
|
+
readonly code = "kernel-capability-skew";
|
|
240
|
+
constructor(verdict: KernelCoherenceVerdict) {
|
|
241
|
+
super(verdict.message);
|
|
242
|
+
this.name = "KernelCoherenceError";
|
|
243
|
+
this.verdict = verdict;
|
|
244
|
+
}
|
|
245
|
+
}
|