@githolon/dsl 0.4.0 → 0.5.1
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/package.json +2 -1
- package/src/build_package.ts +4 -0
- package/src/capability_exports.ts +55 -0
- package/src/codegen_dart.ts +9 -0
- package/src/codegen_proof.ts +140 -12
- package/src/compile_package_main.ts +255 -14
- package/src/directive.ts +35 -10
- package/src/engine_entry.ts +5 -1
- package/src/framework/capability.ts +215 -0
- package/src/framework/impure_capability.ts +25 -3
- package/src/framework/workspaces.ts +129 -0
- package/src/index.ts +9 -0
- package/src/manifest.ts +103 -0
- package/src/read.ts +29 -0
- package/src/stable_ids.ts +226 -0
- package/src/stable_ids_types.ts +40 -0
- package/src/usd.ts +54 -0
- package/src/usd_layers.ts +65 -1
- package/src/wire_encode.ts +18 -0
- package/dart/.dart_tool/package_config.json +0 -328
- package/dart/.dart_tool/package_graph.json +0 -485
- package/dart/.dart_tool/pub/bin/test/test.dart-3.11.5.snapshot +0 -0
- package/dart/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjU= +0 -0
- package/dart/.dart_tool/version +0 -1
- package/dart/build/native_assets/macos/native_assets.json +0 -1
- package/dart/build/test_cache/build/89a6598c8854ed031dfc25d83c80860e.cache.dill.track.dill +0 -0
- package/dart/build/unit_test_assets/AssetManifest.bin +0 -0
- package/dart/build/unit_test_assets/FontManifest.json +0 -1
- package/dart/build/unit_test_assets/NOTICES.Z +0 -0
- package/dart/build/unit_test_assets/NativeAssetsManifest.json +0 -1
- package/dart/build/unit_test_assets/shaders/ink_sparkle.frag +0 -0
- package/dart/build/unit_test_assets/shaders/stretch_effect.frag +0 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
|
|
2
|
+
// remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
|
|
3
|
+
// wasm32-wasip1 artifact {kernel · projection · embedded
|
|
4
|
+
// QuickJS engine} on V8 + WASI-shim, byte-identical everywhere. V8 = portability; the one
|
|
5
|
+
// wasm = determinism. No native, no wasmtime, no 2nd artifact, no domain-JS on bare V8.
|
|
6
|
+
// If a file isn't this / hosting this / authoring for this / proving this — it's gone.
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* framework/capability.ts — `capability()`: THE MARKETPLACE DECLARATION
|
|
10
|
+
* (`architecture/capability_marketplace.md` §4, ruling M2 — the framework-hidden
|
|
11
|
+
* lane). ONE call declares a typed Order→Receipt capability interface; the dev
|
|
12
|
+
* sees a typed order call and a typed task row, and the framework supplies
|
|
13
|
+
* everything else through the SHIPPED `impureCapability()` quartet — so the gate
|
|
14
|
+
* law (`check_order_receipt`), the kernel recognizer, codegen, and the stable-id
|
|
15
|
+
* lockfile all see a perfectly ordinary impure capability. Hidden means *not
|
|
16
|
+
* hand-rolled*, never *not auditable*: the Order and Receipt facts ride the
|
|
17
|
+
* ledger verbatim.
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* export const llm = capability("llm.complete", {
|
|
21
|
+
* request: { prompt: t.string(), model: t.string(), maxTokens: t.int() },
|
|
22
|
+
* receipt: { tokensIn: t.int(), tokensOut: t.int() },
|
|
23
|
+
* });
|
|
24
|
+
* // derived, deterministically from the name:
|
|
25
|
+
* // aggregate LlmCompleteTask order orderLlmComplete
|
|
26
|
+
* // complete completeLlmComplete fail/block/deadLetter likewise
|
|
27
|
+
* // capability "llm.complete" (the CapabilityBinding the executor must hold)
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* THE RECEIPT-FIELD MECHANISM (stated honestly — the sheet's §4 comment): the
|
|
31
|
+
* complete Receipt's payload is the framework-FIXED `{requestId, resultRef,
|
|
32
|
+
* completedAt}`; typed receipt VALUES ride INSIDE `resultRef` as a JSON object,
|
|
33
|
+
* and the generated `completeResult` ops decode them into the declared typed
|
|
34
|
+
* fields (per-field zod-validated IN the plan — a malformed executor result is a
|
|
35
|
+
* typed plan-halt refusal, never a silent partial fold). Receipt fields are
|
|
36
|
+
* auto-`.optional()` on the aggregate (absent while `requested` — the
|
|
37
|
+
* read-decode discipline). Large bodies belong in content-addressed
|
|
38
|
+
* `resultRef`/`resultEvidence` refs, not inline values.
|
|
39
|
+
*
|
|
40
|
+
* Naming: the capability name uses the shipped binding grammar
|
|
41
|
+
* (`^[a-z0-9][a-z0-9._-]{0,63}$` — dots legal; marketplace interfaces are
|
|
42
|
+
* dot-namespaced by convention) and is carried as the quartet's `capability`
|
|
43
|
+
* declaration — the `CapabilityBinding` the fulfilling executor must hold
|
|
44
|
+
* (`architecture/capability_credentials.md`).
|
|
45
|
+
*/
|
|
46
|
+
import { z } from "zod";
|
|
47
|
+
import { instance } from "../aggregate.js";
|
|
48
|
+
import type { Field } from "../fields.js";
|
|
49
|
+
import { set, type PlannedOp } from "../ops.js";
|
|
50
|
+
import { query, type QueryDecl } from "../query.js";
|
|
51
|
+
import {
|
|
52
|
+
IMPURE_CAPABILITY_ENVELOPE,
|
|
53
|
+
impureCapability,
|
|
54
|
+
impureCapabilityAggregate,
|
|
55
|
+
impureCapabilityInstanceId,
|
|
56
|
+
type ImpureCapability,
|
|
57
|
+
} from "./impure_capability.js";
|
|
58
|
+
|
|
59
|
+
/** The shipped CapabilityBinding name grammar (worker + governance law agree). */
|
|
60
|
+
export const CAPABILITY_NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
61
|
+
|
|
62
|
+
/** "llm.complete" → "LlmComplete" — the deterministic id stem. */
|
|
63
|
+
export function capabilityPascal(name: string): string {
|
|
64
|
+
return name
|
|
65
|
+
.split(/[._-]+/)
|
|
66
|
+
.filter(Boolean)
|
|
67
|
+
.map((part) => part[0]!.toUpperCase() + part.slice(1))
|
|
68
|
+
.join("");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The zod RAW SHAPE derived from request fields (each Field carries its own zod). */
|
|
72
|
+
type RequestShape<Req extends Record<string, Field>> = { [K in keyof Req]: Req[K]["zod"] };
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* What `capability()` returns: the full shipped quartet (so every existing
|
|
76
|
+
* consumer — codegen, lowering, the reactor helpers — works unchanged) plus the
|
|
77
|
+
* marketplace surface: the capability NAME and the request/receipt zod schemas
|
|
78
|
+
* (the executor side validates provider results against `receiptSchema` BEFORE
|
|
79
|
+
* building `resultRef`, so a refusal happens host-side first, gate-side always).
|
|
80
|
+
*/
|
|
81
|
+
export interface CapabilityDecl<
|
|
82
|
+
Req extends Record<string, Field>,
|
|
83
|
+
Rec extends Record<string, Field>,
|
|
84
|
+
> extends ImpureCapability<string, RequestShape<Req>, Req & Rec> {
|
|
85
|
+
/** The capability name — the `CapabilityBinding` slot the executor must hold. */
|
|
86
|
+
readonly name: string;
|
|
87
|
+
/** The request fields' zod object (what `order<Pascal>`'s params validate as). */
|
|
88
|
+
readonly requestSchema: z.ZodObject<RequestShape<Req>>;
|
|
89
|
+
/**
|
|
90
|
+
* THE EXECUTOR'S SCAN LANE — a declared, indexed query over the task
|
|
91
|
+
* aggregate keyed on `status` (`<camelPascal>TasksByStatus`). The executor
|
|
92
|
+
* polls `{status: "requested"}` through the ordinary RPC query lane — no
|
|
93
|
+
* special scan op, no wasm arm: the projection index IS the work queue.
|
|
94
|
+
*/
|
|
95
|
+
readonly tasks: QueryDecl;
|
|
96
|
+
/** The receipt fields' zod object — `{}` when the capability declares none. */
|
|
97
|
+
readonly receiptSchema: z.ZodObject<{ [K in keyof Rec]: Rec[K]["zod"] }>;
|
|
98
|
+
/** Build the `resultRef` JSON for a complete Receipt from typed receipt values. */
|
|
99
|
+
readonly encodeResult: (values: { [K in keyof Rec]: z.infer<Rec[K]["zod"]> }) => string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Declare a typed marketplace capability. Sugar over {@link impureCapability} —
|
|
104
|
+
* nothing new on the wire; the one piece of REAL work it adds is the
|
|
105
|
+
* resultRef-decoding `completeResult` (the receipt-field mechanism above).
|
|
106
|
+
*
|
|
107
|
+
* Refusals are COMPILE-TIME loud (thrown at module load, so `nomos-compile`
|
|
108
|
+
* names them): a malformed name, a request/receipt field shadowing the reserved
|
|
109
|
+
* envelope, or a field declared on both sides.
|
|
110
|
+
*/
|
|
111
|
+
export function capability<
|
|
112
|
+
const Name extends string,
|
|
113
|
+
Req extends Record<string, Field>,
|
|
114
|
+
Rec extends Record<string, Field> = Record<never, Field>,
|
|
115
|
+
>(name: Name, spec: { request: Req; receipt?: Rec }): CapabilityDecl<Req, Rec> {
|
|
116
|
+
if (!CAPABILITY_NAME_RE.test(name)) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`capability('${name}'): the name must match ${CAPABILITY_NAME_RE} (lowercase, dot-namespaced — e.g. 'llm.complete'); it doubles as the CapabilityBinding slot the executor must hold`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
const reserved = new Set(Object.keys(IMPURE_CAPABILITY_ENVELOPE));
|
|
122
|
+
const receipt = (spec.receipt ?? {}) as Rec;
|
|
123
|
+
for (const [side, fields] of [
|
|
124
|
+
["request", spec.request],
|
|
125
|
+
["receipt", receipt],
|
|
126
|
+
] as const) {
|
|
127
|
+
for (const k of Object.keys(fields)) {
|
|
128
|
+
if (reserved.has(k)) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`capability('${name}'): ${side} field '${k}' shadows the reserved Order→Receipt envelope — rename it (the envelope is framework law: ${[...reserved].join(", ")})`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
for (const k of Object.keys(receipt)) {
|
|
136
|
+
if (k in spec.request) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`capability('${name}'): '${k}' is declared on BOTH request and receipt — a field has one writer (the order writes requests, the complete Receipt writes receipts); split or rename it`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const pascal = capabilityPascal(name);
|
|
144
|
+
const aggregateId = `${pascal}Task`;
|
|
145
|
+
|
|
146
|
+
// Receipt fields fold only when the complete Receipt lands — auto-`.optional()`
|
|
147
|
+
// so a freshly-ordered task decodes on the generated read type (the same
|
|
148
|
+
// read-decode discipline the envelope's own optionals follow).
|
|
149
|
+
const receiptFields = Object.fromEntries(
|
|
150
|
+
Object.entries(receipt).map(([k, f]) => [k, f.optional()]),
|
|
151
|
+
) as Rec;
|
|
152
|
+
const params = { ...spec.request, ...receiptFields } as Req & Rec;
|
|
153
|
+
const taskAggregate = impureCapabilityAggregate(aggregateId, params);
|
|
154
|
+
|
|
155
|
+
const requestShape = Object.fromEntries(
|
|
156
|
+
Object.entries(spec.request).map(([k, f]) => [k, f.zod]),
|
|
157
|
+
) as RequestShape<Req>;
|
|
158
|
+
const receiptShape = Object.fromEntries(
|
|
159
|
+
Object.entries(receipt).map(([k, f]) => [k, f.zod]),
|
|
160
|
+
) as { [K in keyof Rec]: Rec[K]["zod"] };
|
|
161
|
+
const receiptEntries = Object.entries(receipt) as [string, Field][];
|
|
162
|
+
|
|
163
|
+
const quartet = impureCapability({
|
|
164
|
+
aggregateId,
|
|
165
|
+
orderDirectiveId: `order${pascal}`,
|
|
166
|
+
completeDirectiveId: `complete${pascal}`,
|
|
167
|
+
failDirectiveId: `fail${pascal}`,
|
|
168
|
+
blockDirectiveId: `block${pascal}`,
|
|
169
|
+
deadLetterDirectiveId: `deadLetter${pascal}`,
|
|
170
|
+
params,
|
|
171
|
+
paramsSchema: requestShape,
|
|
172
|
+
// The loop is dynamic (declared field names), so the per-field `set()` typing
|
|
173
|
+
// is asserted — the PUBLIC payload types stay fully inferred from the zod shape.
|
|
174
|
+
writeParams: (taskId, p) =>
|
|
175
|
+
Object.keys(spec.request).map((k) =>
|
|
176
|
+
set(instance(taskAggregate, taskId), k as never, (p as Record<string, unknown>)[k] as never),
|
|
177
|
+
),
|
|
178
|
+
// The receipt-field mechanism: typed values ride INSIDE resultRef as JSON;
|
|
179
|
+
// decode + per-field zod-validate IN the plan (pure function of payload —
|
|
180
|
+
// deterministic; a malformed executor result is a typed plan-halt refusal).
|
|
181
|
+
// Absent keys are skipped (partial results are legal — read-decode
|
|
182
|
+
// discipline), present keys must validate.
|
|
183
|
+
...(receiptEntries.length > 0
|
|
184
|
+
? {
|
|
185
|
+
completeResult: (taskId: string, resultRef: string): PlannedOp[] => {
|
|
186
|
+
const decoded = JSON.parse(resultRef) as Record<string, unknown>;
|
|
187
|
+
const ops: PlannedOp[] = [];
|
|
188
|
+
for (const [k, f] of receiptEntries) {
|
|
189
|
+
if (decoded[k] === undefined) continue;
|
|
190
|
+
ops.push(set(instance(taskAggregate, taskId), k as never, f.zod.parse(decoded[k]) as never));
|
|
191
|
+
}
|
|
192
|
+
return ops;
|
|
193
|
+
},
|
|
194
|
+
}
|
|
195
|
+
: {}),
|
|
196
|
+
capability: name,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const camel = pascal[0]!.toLowerCase() + pascal.slice(1);
|
|
200
|
+
const tasks = query(`${camel}TasksByStatus`).key("status").returns(taskAggregate);
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
...quartet,
|
|
204
|
+
aggregate: taskAggregate,
|
|
205
|
+
instanceId: impureCapabilityInstanceId,
|
|
206
|
+
name,
|
|
207
|
+
requestSchema: z.object(requestShape),
|
|
208
|
+
receiptSchema: z.object(receiptShape),
|
|
209
|
+
encodeResult: (values) => JSON.stringify(z.object(receiptShape).parse(values)),
|
|
210
|
+
tasks,
|
|
211
|
+
// The one-export expansion tag (capability_exports.ts): every exports walk
|
|
212
|
+
// discovers this bundle's aggregate + quartet + scan query without re-exports.
|
|
213
|
+
__isCapabilityDecl: true,
|
|
214
|
+
} as CapabilityDecl<Req, Rec>;
|
|
215
|
+
}
|
|
@@ -136,9 +136,10 @@ export const IMPURE_CAPABILITY_ENVELOPE = {
|
|
|
136
136
|
// GENERIC, written by every impureCapability Receipt (complete OR fail), so the read
|
|
137
137
|
// projection answers "is Order O closed, by which Receipt, with what result" in O(1)
|
|
138
138
|
// off the `(closesOrder, order_id)` index — never a ledger scan. Absent while
|
|
139
|
-
// `requested` (a fresh order has no closing receipt).
|
|
140
|
-
//
|
|
141
|
-
// `
|
|
139
|
+
// `requested` (a fresh order has no closing receipt). The kernel supplies its
|
|
140
|
+
// framework-fixed Lww driver (`kernel::manifest_view::IMPURE_CAPABILITY_RECEIPT_FIELDS`)
|
|
141
|
+
// and the gate enforces the lifecycle around it (`nomos_executor::check_order_receipt` —
|
|
142
|
+
// admit step 5.5: OrphanReceipt / ReceiptOnTerminalOrder, terminal-once as law).
|
|
142
143
|
closesOrder: t.string().merge(Lww).optional(),
|
|
143
144
|
} as const;
|
|
144
145
|
|
|
@@ -348,6 +349,18 @@ export interface ImpureCapability<
|
|
|
348
349
|
readonly block: RequirableDirective<z.infer<typeof blockSchema>>;
|
|
349
350
|
readonly deadLetter: RequirableDirective<z.infer<typeof deadLetterSchema>>;
|
|
350
351
|
readonly instanceId: (requestId: string) => string;
|
|
352
|
+
/**
|
|
353
|
+
* The CAPABILITY CREDENTIAL this task's provider must hold to fulfil Orders
|
|
354
|
+
* (`architecture/capability_credentials.md`): the law name of a
|
|
355
|
+
* `CapabilityBinding` — e.g. `"openai"`, `"cf-analytics"` — bound per
|
|
356
|
+
* workspace via the bind lane (`githolon capability bind <ws> <name>`). The
|
|
357
|
+
* provider HOST resolves `(workspace, capability)` → the credential VALUE
|
|
358
|
+
* from its own store, fail-closed against the law fact (active, unexpired);
|
|
359
|
+
* the value never rides an intent and never appears here. DECLARATION
|
|
360
|
+
* METADATA only — absent (the default) changes nothing on the wire, so
|
|
361
|
+
* credential-free domains stay byte-identical.
|
|
362
|
+
*/
|
|
363
|
+
readonly capability?: string;
|
|
351
364
|
}
|
|
352
365
|
|
|
353
366
|
/**
|
|
@@ -429,6 +442,12 @@ export function impureCapability<
|
|
|
429
442
|
* the Rust reactor's `result_fields` (`HandlerSuccess::with_result_field`).
|
|
430
443
|
*/
|
|
431
444
|
completeResult?: (taskInstanceId: string, resultRef: string) => PlannedOp[];
|
|
445
|
+
/**
|
|
446
|
+
* OPTIONAL: the capability-credential name the provider host must hold a
|
|
447
|
+
* law-state `CapabilityBinding` for (see {@link ImpureCapability.capability}).
|
|
448
|
+
* Omit-when-absent — declaring nothing changes nothing on the wire.
|
|
449
|
+
*/
|
|
450
|
+
capability?: string;
|
|
432
451
|
}): ImpureCapability<Id, ParamsShape, F> {
|
|
433
452
|
const taskAggregate = impureCapabilityAggregate(spec.aggregateId, spec.params);
|
|
434
453
|
|
|
@@ -489,6 +508,9 @@ export function impureCapability<
|
|
|
489
508
|
block,
|
|
490
509
|
deadLetter,
|
|
491
510
|
instanceId: impureCapabilityInstanceId,
|
|
511
|
+
// Omit-when-absent (hash discipline): a capability-free declaration carries
|
|
512
|
+
// NO key at all, so its bundled source — and the package hash — is unchanged.
|
|
513
|
+
...(spec.capability !== undefined ? { capability: spec.capability } : {}),
|
|
492
514
|
};
|
|
493
515
|
}
|
|
494
516
|
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// governance-law revision: capability bindings 2026-06-12 (facts about authority on the
|
|
2
|
+
// ledger, authority itself host-side — architecture/capability_credentials.md). Carries
|
|
3
|
+
// the founding-key-rotation re-entry forward (genesis re-seeds idempotently by hash).
|
|
4
|
+
export const GOVERNANCE_REVISION = "2026-06-12-capability-bindings"; // hash-bearing: re-enters root genesis idempotently
|
|
1
5
|
// NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
|
|
2
6
|
// remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
|
|
3
7
|
// wasm32-wasip1 artifact {kernel · projection · embedded
|
|
@@ -203,6 +207,15 @@ export const CloudPolicy = aggregate("CloudPolicy", {
|
|
|
203
207
|
maxWorkspacesPerPrincipal: t.int().merge(Lww),
|
|
204
208
|
/** Cumulative pushed-bytes cap per workspace ledger (bytes). */
|
|
205
209
|
workspaceByteCap: t.int().merge(Lww),
|
|
210
|
+
/**
|
|
211
|
+
* The workspace-CREATION posture dial (cloud_sovereignty.md S6) —
|
|
212
|
+
* `"open"` (anyone creates), `"invite"` (an active CreationGrant key
|
|
213
|
+
* required), or `"closed"` (admins only). OPTIONAL: absent keeps the
|
|
214
|
+
* implicit posture the cloud has always had (zero active CreationGrants ⇒
|
|
215
|
+
* open, any ⇒ invite-governed) — pre-dial chains and policies behave
|
|
216
|
+
* exactly as before.
|
|
217
|
+
*/
|
|
218
|
+
creationPosture: t.string().merge(Lww).optional(),
|
|
206
219
|
/** Who last set the policy (attribution). */
|
|
207
220
|
setBy: t.string().merge(Lww),
|
|
208
221
|
/** Epoch-ms the policy was last set (caller-stamped). */
|
|
@@ -245,6 +258,47 @@ export const PlatformGrant = aggregate("PlatformGrant", {
|
|
|
245
258
|
status: t.enum(GRANT_STATUSES).merge(Lww),
|
|
246
259
|
});
|
|
247
260
|
|
|
261
|
+
/**
|
|
262
|
+
* CapabilityBinding — A FACT ABOUT AUTHORITY, never authority itself (the
|
|
263
|
+
* doctrine: `architecture/capability_credentials.md`). A workspace's impure
|
|
264
|
+
* capabilities (analytics tokens, provider API keys, upload credentials) need
|
|
265
|
+
* credentials — and credentials are constitutionally incompatible with a
|
|
266
|
+
* replicated chain (every peer would hold them forever; git never forgets;
|
|
267
|
+
* revocation cannot recall bytes). So the LEDGER records only the binding fact:
|
|
268
|
+
* WHICH workspace bound WHICH capability under WHICH credential digest
|
|
269
|
+
* (`keyHash` — the AdminGrant discipline, again), with what scope/expiry, by
|
|
270
|
+
* whom, at which `epoch`. The credential VALUE lives at exactly ONE
|
|
271
|
+
* credential-holding host (the workspace DO's storage, keyed
|
|
272
|
+
* `(workspace, capability)`), is never passed host-to-host, and is unusable
|
|
273
|
+
* the moment the law flips this record — the executing host checks the law
|
|
274
|
+
* fail-closed before every use.
|
|
275
|
+
*
|
|
276
|
+
* EPOCHS ARE PROVENANCE: a rotation revokes the active record and creates the
|
|
277
|
+
* next one (`epoch + 1`, fresh `keyHash`) — so every Receipt fact a provider
|
|
278
|
+
* stamps can name the binding epoch that produced it, and "which credential
|
|
279
|
+
* produced which facts" is answerable forever from law-state alone.
|
|
280
|
+
*/
|
|
281
|
+
export const CapabilityBinding = aggregate("CapabilityBinding", {
|
|
282
|
+
/** The bound workspace's CloudWorkspace AGGREGATE id (root-custody lineage). */
|
|
283
|
+
workspaceId: t.string().merge(Lww),
|
|
284
|
+
/** The capability's law name (e.g. "cf-analytics", "openai", "gcs-upload"). */
|
|
285
|
+
capability: t.string().merge(Lww),
|
|
286
|
+
/** sha256 hex of the credential VALUE — the value itself NEVER lands here. */
|
|
287
|
+
keyHash: t.string().merge(Lww),
|
|
288
|
+
/** Monotone per (workspace, capability): 1, 2, … — rotation provenance. */
|
|
289
|
+
epoch: t.int().merge(Lww),
|
|
290
|
+
/** OPTIONAL narrowing label ("read-only", an account tag, …); absent = whole capability. */
|
|
291
|
+
scope: t.string().merge(Lww).optional(),
|
|
292
|
+
/** OPTIONAL epoch-ms expiry; absent = until revoked. Expired = fail-closed at the host. */
|
|
293
|
+
expiresAt: t.int().merge(Lww).optional(),
|
|
294
|
+
/** Who bound it (attribution, on the ledger forever). */
|
|
295
|
+
boundBy: t.string().merge(Lww),
|
|
296
|
+
/** Epoch-ms the binding was authored (caller-stamped). */
|
|
297
|
+
boundAt: t.int().merge(Lww),
|
|
298
|
+
/** `active` | `revoked` — the law's kill switch for the credential's USE. */
|
|
299
|
+
status: t.enum(GRANT_STATUSES).merge(Lww),
|
|
300
|
+
});
|
|
301
|
+
|
|
248
302
|
// --- workspace directives ---------------------------------------------------------
|
|
249
303
|
|
|
250
304
|
/**
|
|
@@ -607,6 +661,66 @@ export const releaseHostname = directive("releaseHostname")
|
|
|
607
661
|
return [set(c, "status", "released" as const)];
|
|
608
662
|
});
|
|
609
663
|
|
|
664
|
+
/**
|
|
665
|
+
* bindCapability — record the FACT that a workspace holds a capability
|
|
666
|
+
* credential (worker-authored after the value is stored host-side; the value
|
|
667
|
+
* itself takes the other road — DO storage — and NEVER this one). The payload's
|
|
668
|
+
* only credential-shaped field is `keyHash`, regex-pinned to sha256 hex: a raw
|
|
669
|
+
* secret value structurally cannot enter this directive. `scope`/`expiresAt`
|
|
670
|
+
* are omit-when-absent — a scope-free, expiry-free binding writes neither field.
|
|
671
|
+
*/
|
|
672
|
+
export const bindCapability = directive("bindCapability")
|
|
673
|
+
.creates(CapabilityBinding)
|
|
674
|
+
.payload(
|
|
675
|
+
z.object({
|
|
676
|
+
workspaceId: z.string().min(1),
|
|
677
|
+
/** Lowercase capability name — a law identifier, not free text. */
|
|
678
|
+
capability: z.string().regex(/^[a-z0-9][a-z0-9._-]{0,63}$/, "expected a lowercase capability name ([a-z0-9._-], max 64)"),
|
|
679
|
+
keyHash: sha256Hex,
|
|
680
|
+
epoch: z.number().int().positive(),
|
|
681
|
+
scope: z.string().min(1).optional(),
|
|
682
|
+
expiresAt: epochMs.optional(),
|
|
683
|
+
boundBy: z.string().min(1),
|
|
684
|
+
boundAt: epochMs,
|
|
685
|
+
}),
|
|
686
|
+
)
|
|
687
|
+
.plan((p) => {
|
|
688
|
+
const b = create(CapabilityBinding)
|
|
689
|
+
.set("workspaceId", p.workspaceId)
|
|
690
|
+
.set("capability", p.capability)
|
|
691
|
+
.set("keyHash", p.keyHash)
|
|
692
|
+
.set("epoch", p.epoch)
|
|
693
|
+
.set("boundBy", p.boundBy)
|
|
694
|
+
.set("boundAt", p.boundAt)
|
|
695
|
+
.set("status", "active" as const);
|
|
696
|
+
// Omit-when-absent: optional facts fold only when the payload carries them.
|
|
697
|
+
if (p.scope !== undefined) b.set("scope", p.scope);
|
|
698
|
+
if (p.expiresAt !== undefined) b.set("expiresAt", p.expiresAt);
|
|
699
|
+
return [];
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* revokeCapability — flip a CapabilityBinding to `revoked`. The credential
|
|
704
|
+
* stops being USABLE at fold time (the executing host checks this record,
|
|
705
|
+
* fail-closed, before resolving the value); the record (who bound it, when,
|
|
706
|
+
* who killed it — the revoker rides the intent) stays on the ledger — audit is
|
|
707
|
+
* structural. Rotation = revoke + bind at `epoch + 1`; unbind = revoke + the
|
|
708
|
+
* host deletes the stored value.
|
|
709
|
+
*/
|
|
710
|
+
export const revokeCapability = directive("revokeCapability")
|
|
711
|
+
.mutates(CapabilityBinding)
|
|
712
|
+
.payload(
|
|
713
|
+
z.object({
|
|
714
|
+
bindingId: z.string().min(1),
|
|
715
|
+
revokedBy: z.string().min(1),
|
|
716
|
+
revokedAt: epochMs,
|
|
717
|
+
}),
|
|
718
|
+
)
|
|
719
|
+
.plan((p) => {
|
|
720
|
+
const b = instance(CapabilityBinding, p.bindingId);
|
|
721
|
+
return [set(b, "status", "revoked" as const)];
|
|
722
|
+
});
|
|
723
|
+
|
|
610
724
|
/**
|
|
611
725
|
* setCloudPolicy — set (or amend) THE QUOTA LAW. An Ensure on the law-state
|
|
612
726
|
* singleton: the first author births it, later authors amend it in place —
|
|
@@ -618,6 +732,10 @@ export const setCloudPolicy = directive("setCloudPolicy")
|
|
|
618
732
|
z.object({
|
|
619
733
|
maxWorkspacesPerPrincipal: z.number().int().positive(),
|
|
620
734
|
workspaceByteCap: z.number().int().positive(),
|
|
735
|
+
// The creation-posture dial (cloud_sovereignty.md S6). Optional:
|
|
736
|
+
// omitted leaves the field untouched (a partial amend never silently
|
|
737
|
+
// resets the door).
|
|
738
|
+
creationPosture: z.enum(["open", "invite", "closed"]).optional(),
|
|
621
739
|
setBy: z.string().min(1),
|
|
622
740
|
setAt: epochMs,
|
|
623
741
|
}),
|
|
@@ -628,6 +746,7 @@ export const setCloudPolicy = directive("setCloudPolicy")
|
|
|
628
746
|
withMarker(set(pol, "scope", "cloud"), "ensures"),
|
|
629
747
|
set(pol, "maxWorkspacesPerPrincipal", p.maxWorkspacesPerPrincipal),
|
|
630
748
|
set(pol, "workspaceByteCap", p.workspaceByteCap),
|
|
749
|
+
...(p.creationPosture !== undefined ? [set(pol, "creationPosture", p.creationPosture)] : []),
|
|
631
750
|
set(pol, "setBy", p.setBy),
|
|
632
751
|
set(pol, "setAt", p.setAt),
|
|
633
752
|
];
|
|
@@ -815,3 +934,13 @@ export const hostnameClaimByHostname = query("hostnameClaimByHostname")
|
|
|
815
934
|
export const hostnameClaimsByWorkspace = query("hostnameClaimsByWorkspace")
|
|
816
935
|
.key("workspaceId")
|
|
817
936
|
.returns(HostnameClaim);
|
|
937
|
+
|
|
938
|
+
/** A workspace's capability bindings — the executor's fail-closed law probe AND the audit list. */
|
|
939
|
+
export const capabilityBindingsByWorkspace = query("capabilityBindingsByWorkspace")
|
|
940
|
+
.key("workspaceId")
|
|
941
|
+
.returns(CapabilityBinding);
|
|
942
|
+
|
|
943
|
+
/** Provenance: which binding (workspace, capability, epoch) a credential digest belongs to. */
|
|
944
|
+
export const capabilityBindingByKeyHash = query("capabilityBindingByKeyHash")
|
|
945
|
+
.key("keyHash")
|
|
946
|
+
.returns(CapabilityBinding);
|
package/src/index.ts
CHANGED
|
@@ -66,6 +66,9 @@ export { create, existing, type AggregateRef } from "./authoring.js";
|
|
|
66
66
|
// THE CAPTURED READ — a plan reads O(1) DSL queries via `read(query, args)`; the result is committed as
|
|
67
67
|
// the intent's read footprint (deterministic replay + stale-premise detection). The dev calls typed DSL
|
|
68
68
|
// queries; the library routes them here. The host capability is `nomos.read` (twin of `nomos.rng`).
|
|
69
|
+
// NOT YET DEPLOYABLE (#57): the sealed wasm engine does not provide `nomos.read` yet, so
|
|
70
|
+
// `nomos-compile` REFUSES law that references read() (a named compile error, never a runtime halt).
|
|
71
|
+
// The captured-reads wave (task #58) wires the capability in and lifts the refusal.
|
|
69
72
|
export { read } from "./read.js";
|
|
70
73
|
export {
|
|
71
74
|
query,
|
|
@@ -216,6 +219,12 @@ export {
|
|
|
216
219
|
type TaskOutcome,
|
|
217
220
|
type TaskReceipt,
|
|
218
221
|
} from "./framework/impure_capability.js";
|
|
222
|
+
export {
|
|
223
|
+
capability,
|
|
224
|
+
capabilityPascal,
|
|
225
|
+
CAPABILITY_NAME_RE,
|
|
226
|
+
type CapabilityDecl,
|
|
227
|
+
} from "./framework/capability.js";
|
|
219
228
|
export {
|
|
220
229
|
DOMAIN_INSTALLATION_PHASES,
|
|
221
230
|
PolicyBundle,
|
package/src/manifest.ts
CHANGED
|
@@ -48,6 +48,14 @@ import {
|
|
|
48
48
|
canonicalTaxonomyFragment,
|
|
49
49
|
type CanonicalDirectiveRoute,
|
|
50
50
|
} from "./workspace_routing.js";
|
|
51
|
+
import {
|
|
52
|
+
mintAggregateSid,
|
|
53
|
+
mintDomainSid,
|
|
54
|
+
mintFieldSid,
|
|
55
|
+
type StableAggregateId,
|
|
56
|
+
type StableFieldId,
|
|
57
|
+
type StableIds,
|
|
58
|
+
} from "./stable_ids.js";
|
|
51
59
|
|
|
52
60
|
/** One captured `[fieldName, zodKind]` payload field, with optionality. */
|
|
53
61
|
export interface CanonicalPayloadField {
|
|
@@ -126,6 +134,15 @@ export interface CanonicalDirective {
|
|
|
126
134
|
* before this key existed (pinned golden hashes stay UNCHANGED).
|
|
127
135
|
*/
|
|
128
136
|
readonly certifiedReads?: CanonicalCertifiedRead[];
|
|
137
|
+
/**
|
|
138
|
+
* The directive's DECLARED CAPTURED-READ queries (#58 — the captured-read lane):
|
|
139
|
+
* the O(1) DSL queries its `plan` may `read()` on the write path, each carried as
|
|
140
|
+
* `{queryId, key, returns}` (the read RECIPE is the contract — a different key or
|
|
141
|
+
* returns-type is a different read, so it moves the hash). SORTED by queryId.
|
|
142
|
+
* OMITTED ENTIRELY when the directive declares none — a captured-read-free
|
|
143
|
+
* directive is byte-identical in the manifest to before this key existed.
|
|
144
|
+
*/
|
|
145
|
+
readonly capturedReads?: CanonicalCapturedRead[];
|
|
129
146
|
/**
|
|
130
147
|
* The directive's DECLARED required HOST CAPABILITY ports (`TARGET_deps.dot` cluster_ports;
|
|
131
148
|
* the HOST/PORT axis, invariant 3): the capability ports the host must PROVIDE for the
|
|
@@ -178,6 +195,15 @@ export interface CanonicalRelationEvidence {
|
|
|
178
195
|
readonly kind: string;
|
|
179
196
|
}
|
|
180
197
|
|
|
198
|
+
/** One DECLARED captured-read query (#58) — the write-path `read()` recipe in the
|
|
199
|
+
* manifest: the query id, its index key (DECLARED order — index column order), and the
|
|
200
|
+
* aggregate TYPE it returns. */
|
|
201
|
+
export interface CanonicalCapturedRead {
|
|
202
|
+
readonly queryId: string;
|
|
203
|
+
readonly key: string[];
|
|
204
|
+
readonly returns: string;
|
|
205
|
+
}
|
|
206
|
+
|
|
181
207
|
/** One captured DECLARED CertifiedRead — the profiled write-path read identity in the manifest.
|
|
182
208
|
* `queryId` is the gate's required-read key; `sql`/`multiRow`/`uniqueTieBreakers` are the recipe
|
|
183
209
|
* + shape the admission re-run + the profile pin (a recipe change moves the domain hash). */
|
|
@@ -378,6 +404,18 @@ export interface CanonicalManifest {
|
|
|
378
404
|
readonly domain: string;
|
|
379
405
|
readonly aggregates: CanonicalAggregate[];
|
|
380
406
|
readonly directives: CanonicalDirective[];
|
|
407
|
+
/**
|
|
408
|
+
* STABLE IDENTIFIERS (#58 — the foundation: names are LABELS, identity is MINTED by
|
|
409
|
+
* the compiler at first appearance, never dev-assigned). Carries `{stableId, name}`
|
|
410
|
+
* for the domain, every aggregate, and every field — keyed by CURRENT name, each
|
|
411
|
+
* entry holding its `sid` plus the label lineage `was` (prior names, oldest first;
|
|
412
|
+
* compiler-derived from continuity, never dev-written). ALWAYS EMITTED by the post-
|
|
413
|
+
* #58 compiler — this key MOVES every enforcement-era identity hash (named and
|
|
414
|
+
* expected; inert-era artifacts already built stay byte-identical untouched). The
|
|
415
|
+
* EVOLVE GATE diffs old vs new law BY SID; the projection folds via the sid lineage
|
|
416
|
+
* so a renamed entity's old-era data coheres under the new label.
|
|
417
|
+
*/
|
|
418
|
+
readonly stableIds?: StableIds;
|
|
381
419
|
/**
|
|
382
420
|
* The domain's WORKSPACE INVARIANTS (#266), SORTED by id, PRESENCE ONLY (id + `on`).
|
|
383
421
|
* OMITTED ENTIRELY when the domain declares none — so an invariant-free domain is
|
|
@@ -636,6 +674,23 @@ function canonicalEmits(
|
|
|
636
674
|
* NAME (the `reads:{}` key) is incidental to identity (it is `decide`'s ergonomics), so it is
|
|
637
675
|
* NOT captured — only the `query_id` + recipe.
|
|
638
676
|
*/
|
|
677
|
+
/**
|
|
678
|
+
* Canonicalize a directive's DECLARED captured-read queries (#58) into a manifest
|
|
679
|
+
* fragment. OMIT-WHEN-EMPTY: returns `{}` (no `capturedReads` key) when the directive
|
|
680
|
+
* declares none — the omission keeps read-free hashes unchanged. When non-empty,
|
|
681
|
+
* returns a `capturedReads` array SORTED by queryId, each carrying the read recipe
|
|
682
|
+
* `{queryId, key, returns}` (key in DECLARED order — index column order).
|
|
683
|
+
*/
|
|
684
|
+
function canonicalCapturedReads(
|
|
685
|
+
declared: QueryDecl[],
|
|
686
|
+
): { capturedReads?: CanonicalCapturedRead[] } {
|
|
687
|
+
if (declared.length === 0) return {};
|
|
688
|
+
const capturedReads: CanonicalCapturedRead[] = declared
|
|
689
|
+
.map((q) => ({ queryId: q.id, key: [...q.key], returns: q.returns }))
|
|
690
|
+
.sort((a, b) => (a.queryId < b.queryId ? -1 : a.queryId > b.queryId ? 1 : 0));
|
|
691
|
+
return { capturedReads };
|
|
692
|
+
}
|
|
693
|
+
|
|
639
694
|
function canonicalCertifiedReads(
|
|
640
695
|
declared: Record<string, CertifiedReadDecl>,
|
|
641
696
|
): { certifiedReads?: CanonicalCertifiedRead[] } {
|
|
@@ -888,6 +943,50 @@ function canonicalOrderedReads(
|
|
|
888
943
|
return { orderedReads: items };
|
|
889
944
|
}
|
|
890
945
|
|
|
946
|
+
/**
|
|
947
|
+
* The domain's STABLE-ID surface (#58): continuity-carried when the compiler attached
|
|
948
|
+
* `mod.stableIdContinuity` (renames resolved, sids + lineage carried), deterministic
|
|
949
|
+
* first-appearance minting otherwise — so the manifest stays a pure function of the
|
|
950
|
+
* module (+ its attached continuity). Any aggregate/field the continuity does not
|
|
951
|
+
* cover (a genuinely new appearance) mints fresh.
|
|
952
|
+
*/
|
|
953
|
+
function stableIdsForModule(
|
|
954
|
+
mod: DomainModule,
|
|
955
|
+
aggregates: CanonicalAggregate[],
|
|
956
|
+
): StableIds {
|
|
957
|
+
const continuity = mod.stableIdContinuity;
|
|
958
|
+
const domainSid = continuity?.domain ?? mintDomainSid(mod.name);
|
|
959
|
+
// The CURRENT field KINDS off the live handles (the retype arm's identity input —
|
|
960
|
+
// a `t.string()` → `t.int()` retype moves `kind` while the driver stays Lww).
|
|
961
|
+
const handlesById = new Map(mod.aggregates.map((a) => [a.id, a]));
|
|
962
|
+
const out: Record<string, StableAggregateId> = {};
|
|
963
|
+
for (const agg of aggregates) {
|
|
964
|
+
const prior = continuity?.aggregates[agg.id];
|
|
965
|
+
const aggSid = prior?.sid ?? mintAggregateSid(domainSid, agg.id);
|
|
966
|
+
const handleFields = (handlesById.get(agg.id)?.fields ?? {}) as Record<
|
|
967
|
+
string,
|
|
968
|
+
{ kind?: string } | undefined
|
|
969
|
+
>;
|
|
970
|
+
const fields: Record<string, StableFieldId> = {};
|
|
971
|
+
for (const fieldName of Object.keys(agg.schema).sort()) {
|
|
972
|
+
const pf = prior?.fields[fieldName];
|
|
973
|
+
const kind = handleFields[fieldName]?.kind;
|
|
974
|
+
fields[fieldName] = {
|
|
975
|
+
sid: pf?.sid ?? mintFieldSid(aggSid, fieldName),
|
|
976
|
+
...(pf?.was !== undefined && pf.was.length > 0 ? { was: pf.was } : {}),
|
|
977
|
+
// The CURRENT kind, always from the live module (continuity never supplies it).
|
|
978
|
+
...(kind !== undefined ? { kind } : {}),
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
out[agg.id] = {
|
|
982
|
+
sid: aggSid,
|
|
983
|
+
...(prior?.was !== undefined && prior.was.length > 0 ? { was: prior.was } : {}),
|
|
984
|
+
fields,
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
return { v: 1, domain: domainSid, aggregates: out };
|
|
988
|
+
}
|
|
989
|
+
|
|
891
990
|
/**
|
|
892
991
|
* Build the canonical semantic manifest for a domain module — purely from the
|
|
893
992
|
* in-memory DSL objects. No file reads, no bundle, no esbuild: the manifest is a
|
|
@@ -919,6 +1018,7 @@ export function domainManifest(mod: DomainModule): CanonicalManifest {
|
|
|
919
1018
|
// Omit-when-empty: a directive declaring NO boundary contributes neither key,
|
|
920
1019
|
// so its canonical manifest is byte-identical to before the boundary existed.
|
|
921
1020
|
...canonicalReads(d.declaredReads),
|
|
1021
|
+
...canonicalCapturedReads(d.declaredQueryReads ?? []),
|
|
922
1022
|
...canonicalEmits(d.declaredEmits),
|
|
923
1023
|
...canonicalCertifiedReads(d.declaredCertifiedReads),
|
|
924
1024
|
...canonicalRelations(d.declaredRelations),
|
|
@@ -939,6 +1039,9 @@ export function domainManifest(mod: DomainModule): CanonicalManifest {
|
|
|
939
1039
|
domain: mod.name,
|
|
940
1040
|
aggregates,
|
|
941
1041
|
directives,
|
|
1042
|
+
// STABLE IDS (#58): always emitted by this compiler — the one deliberate
|
|
1043
|
+
// hash-mover (enforcement-era identity hashes move; named + expected).
|
|
1044
|
+
stableIds: stableIdsForModule(mod, aggregates),
|
|
942
1045
|
...canonicalWorkspaceInvariants(mod.workspaceInvariants),
|
|
943
1046
|
...canonicalQueries(mod.queries),
|
|
944
1047
|
...canonicalCounts(allCounts.length > 0 ? allCounts : undefined),
|
package/src/read.ts
CHANGED
|
@@ -46,9 +46,38 @@ function host(): NomosRead {
|
|
|
46
46
|
* result}` into the intent's read footprint — so the read is replayable and the write's premise is
|
|
47
47
|
* committed. `query` is a declared, indexed {@link QueryDecl} (e.g. a `t.hasMany` inverse from
|
|
48
48
|
* `hasManyIndexes`); `args` are its index-key values.
|
|
49
|
+
*
|
|
50
|
+
* DEPLOYABLE SINCE #58 — WHEN DECLARED: the sealed wasm engine provides `nomos.read` for law that
|
|
51
|
+
* DECLARES the captured-read lane — the directive whose plan reads must declare each query with
|
|
52
|
+
* `.reads(theQuery)` (which lands the `nomosReadGate` key in the law's USD-IR). The engine then
|
|
53
|
+
* serves the read LIVE from the PRE-APPLY committed state at author, captures `{queryId, args,
|
|
54
|
+
* result}` into the intent's `captured_ports.reads`, replays the capture at verify, and the one
|
|
55
|
+
* gate RE-DERIVES each captured read at the pre-apply position and byte-compares — a typed
|
|
56
|
+
* read-conflict refusal on drift (CAS as law). Undeclared `read()` in law with NO declared
|
|
57
|
+
* captured-read query still REFUSES TO PACKAGE (`nomos-compile` greps the bundled lump for
|
|
58
|
+
* {@link CAPTURED_READ_LUMP_MARKER}, which lives ONLY in this function's body and is tree-shaken
|
|
59
|
+
* away with it) — and an inert host (no `nomos.read`) still halts with the named error below.
|
|
49
60
|
*/
|
|
50
61
|
export function read<T = unknown>(query: QueryDecl, args: Record<string, string>): T {
|
|
62
|
+
const h = host() as { read?: unknown } | undefined;
|
|
63
|
+
if (typeof h?.read !== "function") {
|
|
64
|
+
// The string below IS the compile gate's marker — keep it byte-identical to
|
|
65
|
+
// CAPTURED_READ_LUMP_MARKER (compile_package_main.ts greps the bundled lump for it).
|
|
66
|
+
throw new Error(
|
|
67
|
+
`read('${query.id}'): nomos.read is not provided by this host — the engine serves ` +
|
|
68
|
+
`captured reads only for law that DECLARES them: add .reads(${query.id}'s QueryDecl) ` +
|
|
69
|
+
`to the directive and recompile (#58).`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
51
72
|
const result = host().read(query.id, args) as T;
|
|
52
73
|
footprint.push({ queryId: query.id, args, result });
|
|
53
74
|
return result;
|
|
54
75
|
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The compile gate's lump sentinel — the EXACT substring of the refusal `read()` throws above.
|
|
79
|
+
* It exists in a bundled engine lump iff `read` itself survived tree-shaking, i.e. iff some
|
|
80
|
+
* domain module actually references the captured-read lane. `nomos-compile` greps the lump for
|
|
81
|
+
* it and refuses to package (fail-closed: better a named compile refusal than a runtime halt).
|
|
82
|
+
*/
|
|
83
|
+
export const CAPTURED_READ_LUMP_MARKER = "nomos.read is not provided by this host";
|