@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@githolon/dsl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
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",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"exports": {
|
|
18
18
|
".": "./src/index.ts",
|
|
19
19
|
"./manifest": "./src/manifest.ts",
|
|
20
|
+
"./stable-ids": "./src/stable_ids.ts",
|
|
20
21
|
"./compose": "./src/compose.ts",
|
|
21
22
|
"./usd": "./src/usd.ts",
|
|
22
23
|
"./usd-layers": "./src/usd_layers.ts",
|
package/src/build_package.ts
CHANGED
|
@@ -54,6 +54,7 @@ import type { WorkspaceTypeDecl } from "./workspace_type.js";
|
|
|
54
54
|
import type { WorkspaceInvariantDecl } from "./framework/workspace_invariant.js";
|
|
55
55
|
import { domainHash, emitManifestBytes } from "./manifest.js";
|
|
56
56
|
import { emitUsd } from "./usd.js";
|
|
57
|
+
import { expandCapabilityExports } from "./capability_exports.js";
|
|
57
58
|
|
|
58
59
|
// ─────────────────────────────────────────────────────────────────────────────────
|
|
59
60
|
// composeDomainModule — the named-keys `module(...)`
|
|
@@ -134,6 +135,9 @@ export interface DomainModuleSpec {
|
|
|
134
135
|
export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
|
|
135
136
|
let merged: Mod = {};
|
|
136
137
|
for (const m of spec.modules) merged = { ...merged, ...m };
|
|
138
|
+
// `capability()` bundles expand into discoverable entries (the package plane's
|
|
139
|
+
// merge point) — the quartet validation below then passes without re-exports.
|
|
140
|
+
merged = expandCapabilityExports(merged);
|
|
137
141
|
|
|
138
142
|
const byId = new Map<string, AggregateHandle>();
|
|
139
143
|
for (const a of [...aggregatesOf(merged), ...(spec.extraAggregates ?? [])]) byId.set(a.id, a);
|
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
* capability_exports.ts — the ONE-EXPORT expansion for `capability()` bundles
|
|
10
|
+
* (`architecture/capability_marketplace.md` §4, ruling M2 — "the domain dev
|
|
11
|
+
* declares ONE thing").
|
|
12
|
+
*
|
|
13
|
+
* Every module-exports walk in the house scans TOP-LEVEL exports by shape
|
|
14
|
+
* (aggregates, directives, queries…). A `capability()` declaration is one
|
|
15
|
+
* export holding a task aggregate + the five lifecycle directives as
|
|
16
|
+
* properties — invisible to those walks. This helper, applied at each plane's
|
|
17
|
+
* merge point (the engine lump's `mergeModules`, the package composer's merge,
|
|
18
|
+
* the compile CLI's merge), ADDS the bundle's pieces to the exports bag under
|
|
19
|
+
* synthetic names (`<export>$aggregate`, `<export>$order`, …) so every
|
|
20
|
+
* existing shape-walk discovers them with ZERO new walk semantics. The
|
|
21
|
+
* original bundle export stays (config-named lanes still resolve it); the
|
|
22
|
+
* walks ignore its unmatched shape exactly as before.
|
|
23
|
+
*
|
|
24
|
+
* ZERO imports on purpose: this file is bundled into the sealed engine lump
|
|
25
|
+
* (engine_entry.ts) — it must drag nothing with it.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** Duck-type guard: a `capability()` return (the tag is set by the factory). */
|
|
29
|
+
export function isCapabilityDecl(v: unknown): v is Record<string, unknown> {
|
|
30
|
+
return (
|
|
31
|
+
typeof v === "object" &&
|
|
32
|
+
v !== null &&
|
|
33
|
+
(v as { __isCapabilityDecl?: boolean }).__isCapabilityDecl === true
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const PIECES = ["aggregate", "order", "complete", "fail", "block", "deadLetter", "tasks"] as const;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Expand every `capability()` bundle in an exports bag into discoverable
|
|
41
|
+
* top-level entries. Pure; returns the SAME object when nothing expands (the
|
|
42
|
+
* common case stays allocation-free and referentially stable).
|
|
43
|
+
*/
|
|
44
|
+
export function expandCapabilityExports<M extends Record<string, unknown>>(mod: M): M {
|
|
45
|
+
let out: Record<string, unknown> | null = null;
|
|
46
|
+
for (const [k, v] of Object.entries(mod)) {
|
|
47
|
+
if (!isCapabilityDecl(v)) continue;
|
|
48
|
+
out ??= { ...mod };
|
|
49
|
+
for (const piece of PIECES) {
|
|
50
|
+
const member = (v as Record<string, unknown>)[piece];
|
|
51
|
+
if (member !== undefined) out[`${k}$${piece}`] = member;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return (out ?? mod) as M;
|
|
55
|
+
}
|
package/src/codegen_dart.ts
CHANGED
|
@@ -2322,6 +2322,15 @@ export interface DomainModule {
|
|
|
2322
2322
|
domain?: string;
|
|
2323
2323
|
aggregates: AggregateHandle[];
|
|
2324
2324
|
directives: AnyDirective[];
|
|
2325
|
+
/**
|
|
2326
|
+
* STABLE-ID CONTINUITY (#58 — names are labels, identity is minted). When present
|
|
2327
|
+
* (attached by `nomos-compile` from the PRIOR build's identity manifests), the
|
|
2328
|
+
* canonical manifest's `stableIds` block carries these sids/lineages forward instead
|
|
2329
|
+
* of minting at first appearance — a renamed entity keeps its identity. Keyed by
|
|
2330
|
+
* CURRENT names (the compiler resolves renames BEFORE attaching). Absent ⇒ every
|
|
2331
|
+
* sid mints deterministically at first appearance.
|
|
2332
|
+
*/
|
|
2333
|
+
stableIdContinuity?: import("./stable_ids_types.js").StableIds;
|
|
2325
2334
|
/**
|
|
2326
2335
|
* The domain's NAMED, INDEXED read declarations (read-side closure step 1).
|
|
2327
2336
|
* ADDITIVE + OPTIONAL: a domain that declares no query omits this entirely and is
|
package/src/codegen_proof.ts
CHANGED
|
@@ -41,6 +41,7 @@ import type { Directive } from "./directive.js";
|
|
|
41
41
|
import type { DomainModule } from "./codegen_dart.js";
|
|
42
42
|
import type { QueryDecl } from "./query.js";
|
|
43
43
|
import { finishCount } from "./count.js";
|
|
44
|
+
import { mintedCreateField } from "./workspace_routing.js";
|
|
44
45
|
import {
|
|
45
46
|
autoStampFields,
|
|
46
47
|
tsClientFactoryName,
|
|
@@ -187,6 +188,77 @@ export interface TsProofOptions {
|
|
|
187
188
|
readonly domainHash: string;
|
|
188
189
|
}
|
|
189
190
|
|
|
191
|
+
/** The machine-readable OFFLINE legs for ONE domain (the `@nomos-proof-legs`
|
|
192
|
+
* marker shape; the `proof-legs.json` index is a map of these by domain key). */
|
|
193
|
+
export interface ProofLegs {
|
|
194
|
+
readonly domain: string;
|
|
195
|
+
readonly directiveId: string;
|
|
196
|
+
readonly aggregateId: string;
|
|
197
|
+
readonly payload: Record<string, unknown>;
|
|
198
|
+
readonly autoStamped: readonly string[];
|
|
199
|
+
readonly mintField?: string;
|
|
200
|
+
readonly query?: { readonly id: string; readonly params: Record<string, unknown> };
|
|
201
|
+
readonly count?: { readonly id: string; readonly group: string };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Synthesize the offline legs for one domain module, or `null` when it has no
|
|
205
|
+
* creating write (nothing to prove). The SINGLE source of the leg-selection
|
|
206
|
+
* logic — `generateTsProof` (the .mts marker) and `proofLegsIndex` (the
|
|
207
|
+
* per-domain index) both call this, so the offline lane proves any domain the
|
|
208
|
+
* same way the generated proof proves its primary one. */
|
|
209
|
+
export function proofLegsForModule(mod: DomainModule): ProofLegs | null {
|
|
210
|
+
const createDir = (mod.directives as Directive<unknown>[]).find(
|
|
211
|
+
(d) => d?.marker === "creates" && typeof d.payloadSchema === "object",
|
|
212
|
+
);
|
|
213
|
+
if (createDir === undefined) return null;
|
|
214
|
+
const domain = mod.domain ?? mod.name;
|
|
215
|
+
const agg = (mod.aggregates as AggregateHandle[]).find((a) => a.id === createDir.aggregateId);
|
|
216
|
+
if (agg === undefined) return null;
|
|
217
|
+
const synth = synthesizePayload(createDir.payloadSchema as z.ZodTypeAny, `${domain}/${createDir.id}`);
|
|
218
|
+
const mintField = mintedCreateField(createDir, agg);
|
|
219
|
+
const baseValue: Record<string, unknown> = { ...synth.value };
|
|
220
|
+
if (mintField !== undefined) delete baseValue[mintField];
|
|
221
|
+
const query = ((mod.queries ?? []) as QueryDecl[]).find(
|
|
222
|
+
(q) => q.returns === agg.id && q.key.length > 0 && q.key.every((k) => !k.includes(".") && baseValue[k] !== undefined),
|
|
223
|
+
);
|
|
224
|
+
const count = (mod.counts ?? [])
|
|
225
|
+
.map((raw) => finishCount(raw))
|
|
226
|
+
.find((c) => c.of === agg.id && (c as { where?: unknown }).where == null && (c.by == null || baseValue[c.by] !== undefined));
|
|
227
|
+
return {
|
|
228
|
+
domain,
|
|
229
|
+
directiveId: createDir.id,
|
|
230
|
+
aggregateId: agg.id,
|
|
231
|
+
payload: baseValue,
|
|
232
|
+
autoStamped: synth.autoStamped,
|
|
233
|
+
...(mintField !== undefined ? { mintField } : {}),
|
|
234
|
+
...(query ? { query: { id: query.id, params: Object.fromEntries(query.key.map((k) => [k, baseValue[k]])) } } : {}),
|
|
235
|
+
...(count ? { count: { id: count.id, group: count.by != null ? String(baseValue[count.by]) : "" } } : {}),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** The per-domain legs index for `build/<name>.proof-legs.json` — every domain
|
|
240
|
+
* with a creating write, plus the default (the first, which the .mts proves).
|
|
241
|
+
* `githolon proof [--domain <key>]` runs any of these offline. */
|
|
242
|
+
export function proofLegsIndex(modules: readonly DomainModule[]): { default: string; domains: Record<string, ProofLegs> } | null {
|
|
243
|
+
const domains: Record<string, ProofLegs> = {};
|
|
244
|
+
let first: string | undefined;
|
|
245
|
+
for (const mod of modules) {
|
|
246
|
+
// FAIL SOFT per-module: a sibling domain whose payload can't be synthesized
|
|
247
|
+
// (an exotic zod shape synthesizePayload throws on) must not lose the index
|
|
248
|
+
// for the domains that DO synthesize — skip it, keep the rest.
|
|
249
|
+
let legs: ProofLegs | null;
|
|
250
|
+
try {
|
|
251
|
+
legs = proofLegsForModule(mod);
|
|
252
|
+
} catch {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
if (legs === null) continue;
|
|
256
|
+
domains[legs.domain] = legs;
|
|
257
|
+
if (first === undefined) first = legs.domain;
|
|
258
|
+
}
|
|
259
|
+
return first === undefined ? null : { default: first, domains };
|
|
260
|
+
}
|
|
261
|
+
|
|
190
262
|
export function generateTsProof(modules: readonly DomainModule[], opts: TsProofOptions): string {
|
|
191
263
|
// The proof drives the FIRST domain that has a creating write — without one
|
|
192
264
|
// there is nothing to author, so there is nothing to prove.
|
|
@@ -202,7 +274,11 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
|
|
|
202
274
|
const factory = tsClientFactoryName(domain);
|
|
203
275
|
const hashConst = tsHashConstName(opts.packageName);
|
|
204
276
|
|
|
205
|
-
|
|
277
|
+
// SAME predicate as the mod selection above AND proofLegsForModule — so the
|
|
278
|
+
// proof body, its marker, and the index can never describe different directives.
|
|
279
|
+
const createDir = (mod.directives as Directive<unknown>[]).find(
|
|
280
|
+
(d) => d?.marker === "creates" && typeof d.payloadSchema === "object",
|
|
281
|
+
)!;
|
|
206
282
|
const agg = (mod.aggregates as AggregateHandle[]).find((a) => a.id === createDir.aggregateId);
|
|
207
283
|
if (agg === undefined) {
|
|
208
284
|
throw new Error(
|
|
@@ -211,10 +287,21 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
|
|
|
211
287
|
}
|
|
212
288
|
const synth = synthesizePayload(createDir.payloadSchema as z.ZodTypeAny, `${domain}/${createDir.id}`);
|
|
213
289
|
|
|
290
|
+
// THE MINTED-ID RULE (the gate's `check_create_ids`): a `.creates` payload field
|
|
291
|
+
// that IS the target's own id must be KERNEL-MINTED — a hand-written sample id
|
|
292
|
+
// refuses typed at admission (`NotMinted`). The marker-driven trace names the
|
|
293
|
+
// field (never name-guessed); the generated proof MINTS it at runtime
|
|
294
|
+
// (`holon.mint`) instead of synthesizing a sample, so the proof admits
|
|
295
|
+
// everywhere the law does.
|
|
296
|
+
const mintField = mintedCreateField(createDir, agg);
|
|
297
|
+
const baseValue: Record<string, unknown> = { ...synth.value };
|
|
298
|
+
if (mintField !== undefined) delete baseValue[mintField];
|
|
299
|
+
|
|
214
300
|
// The first declared query that can READ BACK the create: returns the created
|
|
215
|
-
// aggregate and every key field has a synthesized payload value to probe with
|
|
301
|
+
// aggregate and every key field has a synthesized payload value to probe with
|
|
302
|
+
// (a query keyed on the MINTED field is skipped — its value exists only at runtime).
|
|
216
303
|
const query = ((mod.queries ?? []) as QueryDecl[]).find(
|
|
217
|
-
(q) => q.returns === agg.id && q.key.length > 0 && q.key.every((k) => !k.includes(".") &&
|
|
304
|
+
(q) => q.returns === agg.id && q.key.length > 0 && q.key.every((k) => !k.includes(".") && baseValue[k] !== undefined),
|
|
218
305
|
);
|
|
219
306
|
|
|
220
307
|
// The first declared count the create moves: counts the created aggregate,
|
|
@@ -226,7 +313,7 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
|
|
|
226
313
|
(c) =>
|
|
227
314
|
c.of === agg.id &&
|
|
228
315
|
(c as { where?: unknown }).where == null &&
|
|
229
|
-
(c.by == null ||
|
|
316
|
+
(c.by == null || baseValue[c.by] !== undefined),
|
|
230
317
|
);
|
|
231
318
|
|
|
232
319
|
const concurrency = findConcurrencyLeg(mod.directives as Directive<unknown>[], agg);
|
|
@@ -239,10 +326,19 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
|
|
|
239
326
|
.replace(/^-+|-+$/g, "")
|
|
240
327
|
.slice(0, 24) || "domain";
|
|
241
328
|
|
|
242
|
-
const payloadLit =
|
|
243
|
-
|
|
329
|
+
const payloadLit =
|
|
330
|
+
mintField !== undefined
|
|
331
|
+
? `{ ...${JSON.stringify(baseValue)}, ${JSON.stringify(mintField)}: __minted }`
|
|
332
|
+
: JSON.stringify(baseValue);
|
|
333
|
+
const queryParamsObj = query ? Object.fromEntries(query.key.map((k) => [k, baseValue[k]])) : undefined;
|
|
334
|
+
const queryParams = queryParamsObj ? JSON.stringify(queryParamsObj) : "";
|
|
244
335
|
const listAccessor = `list${agg.id}s`;
|
|
245
336
|
|
|
337
|
+
// THE MACHINE-READABLE LEGS (parsed by `githolon`'s offline proof runner —
|
|
338
|
+
// cli/src/proof_offline.ts). The ONE leg-synthesis (`proofLegsForModule`) feeds
|
|
339
|
+
// both this marker and the per-domain `proof-legs.json` index, so they never drift.
|
|
340
|
+
const legsMarker = JSON.stringify(proofLegsForModule(mod));
|
|
341
|
+
|
|
246
342
|
let step = 0;
|
|
247
343
|
const n = () => ++step;
|
|
248
344
|
const out: string[] = [];
|
|
@@ -256,6 +352,7 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
|
|
|
256
352
|
`//`,
|
|
257
353
|
`// The flow mirrors the scaffold's test/e2e.mts (docs/ in a scaffolded app explains`,
|
|
258
354
|
`// every call — start at docs/01-mental-model.md). Regenerated on every compile; do not edit.`,
|
|
355
|
+
`// @nomos-proof-legs ${legsMarker}`,
|
|
259
356
|
`import { readFileSync } from "node:fs";`,
|
|
260
357
|
`import { connect } from "@githolon/client";`,
|
|
261
358
|
`import { ${factory}, ${hashConst} } from "./${opts.packageName}.client.ts";`,
|
|
@@ -266,18 +363,25 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
|
|
|
266
363
|
`// IDENTITY NOTE: this proof uses the bare x-nomos-principal lane so it is`,
|
|
267
364
|
`// SELF-CONTAINED (a throwaway workspace, no stored credentials). Real apps use`,
|
|
268
365
|
`// \`githolon login --agent\` (a VERIFIED identity) + \`githolon ws create/deploy\`.`,
|
|
269
|
-
`const fail: (m: string) => never = (m) => {
|
|
366
|
+
`const fail: (m: string) => never = (m) => { throw new Error(m); };`,
|
|
270
367
|
`const ok = (m: string) => console.log("✓ " + m);`,
|
|
271
368
|
``,
|
|
272
369
|
`const deploy = JSON.parse(readFileSync(new URL(${JSON.stringify(`./${opts.packageName}.deploy.json`)}, import.meta.url), "utf8"));`,
|
|
273
370
|
``,
|
|
371
|
+
`// CLEANUP CONTRACT: the throwaway workspace is RETIRED on every exit (green or`,
|
|
372
|
+
`// jammed) — a proof never leaves orphans. PROOF_KEEP=1 (githolon proof --live --keep)`,
|
|
373
|
+
`// keeps it and prints the FULL secret once, with the persist command.`,
|
|
374
|
+
`let SECRET = "";`,
|
|
375
|
+
`let runError: unknown = null;`,
|
|
376
|
+
`try {`,
|
|
377
|
+
``,
|
|
274
378
|
`// ${n()}. a throwaway workspace (the ONE-TIME secret comes back on create)`,
|
|
275
379
|
`let r = await fetch(\`\${CLOUD}/v1/workspaces/\${WS}\`, { method: "POST", headers: { "x-nomos-principal": "githolon-proof" } });`,
|
|
276
380
|
`let d = await r.json();`,
|
|
277
381
|
`if (!d.ok) fail(\`create workspace: \${JSON.stringify(d)}\`);`,
|
|
278
|
-
`
|
|
382
|
+
`SECRET = d.workspaceSecret;`,
|
|
279
383
|
`if (!SECRET?.startsWith("nws_v1_")) fail(\`no workspaceSecret returned: \${JSON.stringify(d)}\`);`,
|
|
280
|
-
`ok(\`workspace \${WS} created
|
|
384
|
+
`ok(\`workspace \${WS} created (one-time secret held for cleanup — PROOF_KEEP=1 keeps the workspace and prints it)\`);`,
|
|
281
385
|
``,
|
|
282
386
|
`// ${n()}. deploy YOUR compiled law (build/${opts.packageName}.deploy.json) WITH the secret`,
|
|
283
387
|
`r = await fetch(\`\${CLOUD}/v1/workspaces/\${WS}/domains\`, { method: "POST", headers: { "content-type": "application/json", authorization: \`Bearer \${SECRET}\` }, body: JSON.stringify(deploy) });`,
|
|
@@ -297,8 +401,16 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
|
|
|
297
401
|
`const realFetch = globalThis.fetch;`,
|
|
298
402
|
`globalThis.fetch = (() => { throw new Error("OFFLINE PROOF VIOLATED: local authoring touched the network"); }) as typeof fetch;`,
|
|
299
403
|
``,
|
|
404
|
+
...(mintField !== undefined
|
|
405
|
+
? [
|
|
406
|
+
`// the target's own id is KERNEL-MINTED (never hand-written — the gate's`,
|
|
407
|
+
`// check_create_ids refuses sample ids typed): the local wasm mints, the id`,
|
|
408
|
+
`// rides the payload, replay reads the capture.`,
|
|
409
|
+
`const __minted = await holon.mint(${JSON.stringify(agg.id)});`,
|
|
410
|
+
]
|
|
411
|
+
: []),
|
|
300
412
|
`await app.${createDir.id}(${payloadLit});`,
|
|
301
|
-
`ok("dispatch ${domain}/${createDir.id} — offline write under the pulled law (payload synthesized from YOUR schema)");`,
|
|
413
|
+
`ok("dispatch ${domain}/${createDir.id} — offline write under the pulled law (payload synthesized from YOUR schema${mintField !== undefined ? `; \`${mintField}\` kernel-minted` : ``})");`,
|
|
302
414
|
``,
|
|
303
415
|
);
|
|
304
416
|
|
|
@@ -326,7 +438,7 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
|
|
|
326
438
|
|
|
327
439
|
if (count) {
|
|
328
440
|
const accessor = lcFirst(camel(count.id));
|
|
329
|
-
const callArg = count.by != null ? JSON.stringify(String(
|
|
441
|
+
const callArg = count.by != null ? JSON.stringify(String(baseValue[count.by])) : "";
|
|
330
442
|
out.push(
|
|
331
443
|
`// ${n()}. the first declared count — the maintained O(1) tally, locally`,
|
|
332
444
|
`const localCount = await app.${accessor}(${callArg});`,
|
|
@@ -370,7 +482,7 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
|
|
|
370
482
|
if (count) {
|
|
371
483
|
out.push(
|
|
372
484
|
`// ${n()}. the EDGE maintains the same count (same wasm, same law)`,
|
|
373
|
-
`d = await (await fetch(\`\${CLOUD}/v1/workspaces/\${WS}/counts/${count.id}${count.by != null ? `?group=\${encodeURIComponent(${JSON.stringify(String(
|
|
485
|
+
`d = await (await fetch(\`\${CLOUD}/v1/workspaces/\${WS}/counts/${count.id}${count.by != null ? `?group=\${encodeURIComponent(${JSON.stringify(String(baseValue[count.by]))})}` : ``}\`)).json();`,
|
|
374
486
|
`if (!(d.ok && d.count === 1)) fail(\`cloud count ${count.id}: \${JSON.stringify(d)}\`);`,
|
|
375
487
|
`ok("cloud count ${count.id} = 1 — the O(1) maintained read, at the edge too");`,
|
|
376
488
|
``,
|
|
@@ -424,6 +536,22 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
|
|
|
424
536
|
``,
|
|
425
537
|
`console.log(\`\\nALL GREEN — generated proof for "${opts.packageName}": deploy → offline write → local reads → admission → cloud reads${concurrency ? ` → AddWins merge` : ``} → convergence (\${WS})\`);`,
|
|
426
538
|
``,
|
|
539
|
+
`} catch (e) {`,
|
|
540
|
+
` runError = e;`,
|
|
541
|
+
` console.error("✗ " + ((e as Error)?.message ?? e));`,
|
|
542
|
+
`} finally {`,
|
|
543
|
+
` // THE CLEANUP CONTRACT: never an orphaned cloud workspace. Retired on every`,
|
|
544
|
+
` // exit path; PROOF_KEEP=1 keeps it and prints the FULL secret exactly once.`,
|
|
545
|
+
` if (SECRET && process.env.PROOF_KEEP === "1") {`,
|
|
546
|
+
` console.log(\`workspace \${WS} KEPT — secret \${SECRET}\`);`,
|
|
547
|
+
` console.log(\` persist it: githolon secret set \${WS} <the secret above> (then: githolon ws retire \${WS} when done)\`);`,
|
|
548
|
+
` } else if (SECRET) {`,
|
|
549
|
+
` const rr = await fetch(\`\${CLOUD}/v1/workspaces/\${WS}/retire\`, { method: "POST", headers: { authorization: \`Bearer \${SECRET}\` } }).then((x) => x.json()).catch(() => ({}));`,
|
|
550
|
+
` console.log(\` retired \${WS}: \${(rr as { ok?: boolean }).ok === true}\`);`,
|
|
551
|
+
` }`,
|
|
552
|
+
` process.exit(runError ? 1 : 0);`,
|
|
553
|
+
`}`,
|
|
554
|
+
``,
|
|
427
555
|
);
|
|
428
556
|
|
|
429
557
|
return out.join("\n");
|