@githolon/dsl 0.18.0 → 0.19.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/package.json +1 -1
- package/src/births.ts +24 -0
- package/src/codegen_proof.ts +553 -84
- package/src/compile_package_main.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@githolon/dsl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.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/births.ts
CHANGED
|
@@ -50,6 +50,30 @@ export function __drainBirths(): BirthRecipe[] {
|
|
|
50
50
|
* kernel executes the birth ONLY for a directive its resolved law names there (the era rule: pre-feature
|
|
51
51
|
* chains carry no key, so they replay byte-green and the kernel does nothing). A plan may birth at most one
|
|
52
52
|
* child per intent (one offer, one defined consequence — the unix model).
|
|
53
|
+
*
|
|
54
|
+
* TO BIRTH A LEAF CHILD RUNNING ITS OWN LAW (a home, an estate child) — copy `framework/home.ts`'s
|
|
55
|
+
* `birthHome`. The recipe is just the two-layer law install + your own seed step; there is NO helper to
|
|
56
|
+
* fabricate it and you do NOT need a new primitive (recursion is the law — a home births children exactly
|
|
57
|
+
* as root births platforms). `buildGenesisChain` is the ONE builder for the PLATFORM shape (it adds the
|
|
58
|
+
* `seedGenesis → grantAdmin → birthCloud` tail); a leaf just omits that tail:
|
|
59
|
+
*
|
|
60
|
+
* ```ts
|
|
61
|
+
* export const birthEstateChild = directive("birthEstateChild")
|
|
62
|
+
* .creates(EstateChild)
|
|
63
|
+
* .payload(z.object({ child: z.string(), owner: z.string(),
|
|
64
|
+
* frameworkHash: z.string(), lawHash: z.string(), bornAt: z.string() }))
|
|
65
|
+
* .plan((p) => {
|
|
66
|
+
* const actor = "user:" + p.owner;
|
|
67
|
+
* birth({ workspace: p.child, genesisChain: [
|
|
68
|
+
* { domain: "bootstrap", directiveId: "installDomain", actor, payload: { domainHash: p.frameworkHash }, domainHash: p.frameworkHash, domainPackageB64: "" },
|
|
69
|
+
* { domain: "nomos", directiveId: "installDomain", actor, payload: { domainHash: p.lawHash }, domainHash: p.lawHash, domainPackageB64: "" },
|
|
70
|
+
* { domain: "estate", directiveId: "seedEstateChild", actor, payload: { owner: p.owner, bornAt: p.bornAt } },
|
|
71
|
+
* ] });
|
|
72
|
+
* return [];
|
|
73
|
+
* })
|
|
74
|
+
* .births() // the kernel's run_birth_effect folds the recipe through the CHILD's own gate
|
|
75
|
+
* .requires("creator"); // grant the parent's owner #creator (self-bound) — the birth authority
|
|
76
|
+
* ```
|
|
53
77
|
*/
|
|
54
78
|
export function birth(recipe: BirthRecipe): void {
|
|
55
79
|
footprint.push(recipe);
|
package/src/codegen_proof.ts
CHANGED
|
@@ -42,6 +42,10 @@ import type { DomainModule } from "./codegen_dart.js";
|
|
|
42
42
|
import type { QueryDecl } from "./query.js";
|
|
43
43
|
import { finishCount } from "./count.js";
|
|
44
44
|
import { mintedCreateField } from "./workspace_routing.js";
|
|
45
|
+
import { deterministicPorts } from "./ctx.js";
|
|
46
|
+
import { __resetBirths, __drainBirths } from "./births.js";
|
|
47
|
+
import { __resetAuthoring, __drainAuthoring } from "./authoring.js";
|
|
48
|
+
import { __resetReads, __drainReads } from "./read.js";
|
|
45
49
|
import {
|
|
46
50
|
autoStampFields,
|
|
47
51
|
tsClientFactoryName,
|
|
@@ -179,6 +183,223 @@ function findConcurrencyLeg(
|
|
|
179
183
|
return undefined;
|
|
180
184
|
}
|
|
181
185
|
|
|
186
|
+
// ── child-birth discovery (the G3 `.births()` lane — prove a parent birth end-to-end into the child) ──────
|
|
187
|
+
|
|
188
|
+
/** ONE step of a discovered birth recipe's genesis chain (the subset codegen reads). */
|
|
189
|
+
interface DiscoveredGenesisStep {
|
|
190
|
+
readonly domain: string;
|
|
191
|
+
readonly directiveId: string;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** A discovered CHILD-BIRTH leg: a parent `.births()` directive proven end-to-end into the child it births
|
|
195
|
+
* (install the framework + child law in-chain → run the child genesis seed → mount the born child →
|
|
196
|
+
* verify it). The runtime obtains the actual born WORKSPACE NAME from the offer's `born` result (the recipe
|
|
197
|
+
* workspace is plan-derived — often `…-<sha256(owner)>` — so it is not statically known), and injects the
|
|
198
|
+
* REAL `frameworkHash`/`lawHash` (the local wasm's controller hash + the package domainHash) onto the
|
|
199
|
+
* synthesized payload (the synthesizer only produces `sample-*` placeholders, which the child install
|
|
200
|
+
* would refuse). */
|
|
201
|
+
export interface ChildBirthLeg {
|
|
202
|
+
/** The domain the `.births()` directive lives in. */
|
|
203
|
+
readonly parentDomain: string;
|
|
204
|
+
/** The `.births()` directive id the parent drives to birth the child. */
|
|
205
|
+
readonly birthDirectiveId: string;
|
|
206
|
+
/** The synthesized parent payload (auto-stamped `…At` omitted; `frameworkHash`/`lawHash` placeholders
|
|
207
|
+
* REMOVED — the runtime injects the real hashes onto these fields by name). */
|
|
208
|
+
readonly birthPayload: Record<string, unknown>;
|
|
209
|
+
/** Required `…At` fields the runtime stamps with ISO now() at dispatch (mirrors the create leg). */
|
|
210
|
+
readonly birthAutoStamped: readonly string[];
|
|
211
|
+
/** The payload field that carries the bootstrap framework-controller hash (runtime: the local wasm's own
|
|
212
|
+
* controller hash) — discovered from the recipe's `bootstrap/installDomain` step's payload, by matching
|
|
213
|
+
* the field whose synthesized value the recipe routed there. `undefined` ⇒ the recipe hard-codes it. */
|
|
214
|
+
readonly frameworkHashField?: string;
|
|
215
|
+
/** The payload field that carries the child law hash (runtime: the deployed package domainHash). */
|
|
216
|
+
readonly lawHashField?: string;
|
|
217
|
+
/** The CHILD domain key the recipe's seed step targets (e.g. `estate`) — the child-only law installed
|
|
218
|
+
* by this birth. This is what makes a child-only domain proof-addressable (gaps 1+3). */
|
|
219
|
+
readonly childDomain: string;
|
|
220
|
+
/** The child genesis SEED directive the recipe runs (e.g. `seedEstateWorkspace`). */
|
|
221
|
+
readonly childSeedDirectiveId: string;
|
|
222
|
+
/** The aggregate the child seed `.ensures`/`.creates` (e.g. `EstateOwner`) — read back to prove the seed ran. */
|
|
223
|
+
readonly childSeedAggregateId?: string;
|
|
224
|
+
/** The instance id the child seed writes, when it is a fixed singleton (`<Agg>:self` / a constant id) —
|
|
225
|
+
* lets the proof read the seeded record by id with no query. `undefined` ⇒ fall back to the child query. */
|
|
226
|
+
readonly childSeedInstanceId?: string;
|
|
227
|
+
/** A child declared query that reads the seed back (returns the seed aggregate). `undefined` ⇒ by-id only. */
|
|
228
|
+
readonly childQuery?: { readonly id: string; readonly key: readonly string[] };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** A stub `nomos` global (mint + sha256) so a `.births()` plan that mints / hashes a child name runs to
|
|
232
|
+
* completion at codegen. The hash is a deterministic STUB (the codegen never needs the real digest — the
|
|
233
|
+
* recipe's workspace is read at RUNTIME from the offer's `born`); mint mirrors `harvestPrincipalFlows`. */
|
|
234
|
+
function withBirthProbeGlobals<T>(run: () => T): T {
|
|
235
|
+
const g = globalThis as unknown as { nomos?: { mint?: (t: string) => string; sha256?: (s: string) => string; read?: unknown } };
|
|
236
|
+
const had = "nomos" in g;
|
|
237
|
+
const prior = g.nomos;
|
|
238
|
+
g.nomos = {
|
|
239
|
+
...(prior ?? {}),
|
|
240
|
+
mint: (typeTag: string) => `${typeTag}_0192f00d-0000-7000-8000-000000000000`,
|
|
241
|
+
// A deterministic 64-hex STUB digest (length-stable so a `.slice(0, n)` name template still works).
|
|
242
|
+
sha256: (s: string) => {
|
|
243
|
+
let h = 2166136261 >>> 0;
|
|
244
|
+
for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 16777619) >>> 0; }
|
|
245
|
+
return h.toString(16).padStart(8, "0").repeat(8);
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
try {
|
|
249
|
+
return run();
|
|
250
|
+
} finally {
|
|
251
|
+
if (had && prior !== undefined) g.nomos = prior; else delete g.nomos;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Discover the CHILD-BIRTH leg a `.births()` directive declares: run its plan with a synthesized payload +
|
|
257
|
+
* the birth-probe globals, drain the recorded birth recipe (`birth(recipe)` → `__drainBirths`), and read the
|
|
258
|
+
* recipe's genesis chain to learn the CHILD domain + seed directive + the framework/law install fields.
|
|
259
|
+
*
|
|
260
|
+
* This is the SAME compile-time plan-execution technique `harvestPrincipalFlows` (the endorse discovery) and
|
|
261
|
+
* `directiveRoutes`/`codegen_dart` already use — the plan is a pure function of (payload, ports). Best-effort:
|
|
262
|
+
* a plan that can't be probed (or doesn't record a 3-step `installDomain×2 + seed` leaf recipe) returns
|
|
263
|
+
* `undefined` and the directive proves only `compile-valid`. Never throws (drains the sinks on any path).
|
|
264
|
+
*/
|
|
265
|
+
export function discoverChildBirthLeg(
|
|
266
|
+
birthDir: Directive<unknown>,
|
|
267
|
+
parentDomain: string,
|
|
268
|
+
modules: readonly DomainModule[],
|
|
269
|
+
): ChildBirthLeg | undefined {
|
|
270
|
+
if (birthDir.birthsChild !== true) return undefined;
|
|
271
|
+
let recipe: { workspace?: unknown; genesisChain?: unknown } | undefined;
|
|
272
|
+
let synth: SynthesizedPayload;
|
|
273
|
+
try {
|
|
274
|
+
synth = synthesizePayload(birthDir.payloadSchema as z.ZodTypeAny, `${parentDomain}/${birthDir.id}`);
|
|
275
|
+
} catch {
|
|
276
|
+
return undefined;
|
|
277
|
+
}
|
|
278
|
+
// The plan needs concrete payload VALUES for every field it reads to build the recipe (frameworkHash /
|
|
279
|
+
// lawHash / owner …). The synthesizer omits `…At` (auto-stamped) + optional fields; fill the auto-stamped
|
|
280
|
+
// ones with a probe ISO string so the plan runs (the values are irrelevant to discovery — only the recipe
|
|
281
|
+
// SHAPE is read).
|
|
282
|
+
const probePayload: Record<string, unknown> = { ...synth.value };
|
|
283
|
+
for (const f of synth.autoStamped) probePayload[f] = "2026-01-01T00:00:00Z";
|
|
284
|
+
try {
|
|
285
|
+
withBirthProbeGlobals(() => {
|
|
286
|
+
__resetAuthoring();
|
|
287
|
+
__resetReads();
|
|
288
|
+
__resetBirths();
|
|
289
|
+
try {
|
|
290
|
+
birthDir.plan(probePayload as never, deterministicPorts({ physical: 1, replica: 1, principal: "principal:probe" }));
|
|
291
|
+
} finally {
|
|
292
|
+
try { __drainAuthoring(); __drainReads(); } catch { /* sinks may be drained */ }
|
|
293
|
+
}
|
|
294
|
+
const drained = __drainBirths();
|
|
295
|
+
recipe = drained[0] as typeof recipe;
|
|
296
|
+
});
|
|
297
|
+
} catch {
|
|
298
|
+
return undefined;
|
|
299
|
+
}
|
|
300
|
+
if (recipe === undefined || !Array.isArray(recipe.genesisChain)) return undefined;
|
|
301
|
+
const chain = recipe.genesisChain as DiscoveredGenesisStep[];
|
|
302
|
+
// The child seed = the FIRST genesis step that is neither bootstrap nor nomos installDomain (the leaf's
|
|
303
|
+
// own seed). A platform-shaped recipe (seedGenesis → grantAdmin → birthCloud tail) is out of scope here —
|
|
304
|
+
// we prove the LEAF birth shape (install×2 + one seed), the home/estate composable case.
|
|
305
|
+
const seedStep = chain.find(
|
|
306
|
+
(s) => s !== null && typeof s === "object" && s.directiveId !== "installDomain"
|
|
307
|
+
&& s.domain !== "bootstrap" && s.domain !== "nomos",
|
|
308
|
+
);
|
|
309
|
+
if (seedStep === undefined) return undefined;
|
|
310
|
+
const childDomain = seedStep.domain;
|
|
311
|
+
const childSeedDirectiveId = seedStep.directiveId;
|
|
312
|
+
|
|
313
|
+
// Resolve the child seed's aggregate + singleton id + read query from the CHILD domain module (so the
|
|
314
|
+
// child read-model leg reads the right thing). The child law is in THIS package (the one-package pattern
|
|
315
|
+
// — the parent folds it so the kernel can route its bytes by hash).
|
|
316
|
+
const childMod = modules.find((m) => (m.domain ?? m.name) === childDomain);
|
|
317
|
+
let childSeedAggregateId: string | undefined;
|
|
318
|
+
let childSeedInstanceId: string | undefined;
|
|
319
|
+
let childQuery: ChildBirthLeg["childQuery"];
|
|
320
|
+
if (childMod !== undefined) {
|
|
321
|
+
const seedDir = (childMod.directives as Directive<unknown>[]).find((d) => d?.id === childSeedDirectiveId);
|
|
322
|
+
if (seedDir !== undefined) {
|
|
323
|
+
childSeedAggregateId = seedDir.aggregateId;
|
|
324
|
+
childSeedInstanceId = discoverSingletonInstanceId(seedDir);
|
|
325
|
+
const q = ((childMod.queries ?? []) as QueryDecl[]).find((qq) => qq.returns === seedDir.aggregateId && qq.key.length > 0);
|
|
326
|
+
if (q !== undefined) childQuery = { id: q.id, key: q.key };
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// The framework/law install FIELDS: which parent-payload field the recipe routed into each install step's
|
|
331
|
+
// `domainHash`. The recipe step's payload value === the parent payload value for that field — match by value.
|
|
332
|
+
const installSteps = chain.filter((s) => s !== null && typeof s === "object" && s.directiveId === "installDomain") as Array<DiscoveredGenesisStep & { payload?: { domainHash?: unknown } }>;
|
|
333
|
+
const fieldForValue = (v: unknown): string | undefined =>
|
|
334
|
+
Object.entries(synth.value).find(([, sv]) => sv === v && typeof v === "string" && v.length > 0)?.[0];
|
|
335
|
+
const bootstrapStep = installSteps.find((s) => s.domain === "bootstrap");
|
|
336
|
+
const nomosStep = installSteps.find((s) => s.domain === "nomos");
|
|
337
|
+
const frameworkHashField = bootstrapStep ? fieldForValue(bootstrapStep.payload?.domainHash) : undefined;
|
|
338
|
+
const lawHashField = nomosStep ? fieldForValue(nomosStep.payload?.domainHash) : undefined;
|
|
339
|
+
|
|
340
|
+
// The parent payload the leg dispatches: the synthesized values MINUS the framework/law-hash placeholders
|
|
341
|
+
// (the runtime injects the REAL hashes onto those fields by name — a `sample-*` hash would fail the child
|
|
342
|
+
// install) MINUS the auto-stamped fields (stamped at dispatch).
|
|
343
|
+
const birthPayload: Record<string, unknown> = { ...synth.value };
|
|
344
|
+
if (frameworkHashField !== undefined) delete birthPayload[frameworkHashField];
|
|
345
|
+
if (lawHashField !== undefined) delete birthPayload[lawHashField];
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
parentDomain,
|
|
349
|
+
birthDirectiveId: birthDir.id,
|
|
350
|
+
birthPayload,
|
|
351
|
+
birthAutoStamped: synth.autoStamped,
|
|
352
|
+
...(frameworkHashField !== undefined ? { frameworkHashField } : {}),
|
|
353
|
+
...(lawHashField !== undefined ? { lawHashField } : {}),
|
|
354
|
+
childDomain,
|
|
355
|
+
childSeedDirectiveId,
|
|
356
|
+
...(childSeedAggregateId !== undefined ? { childSeedAggregateId } : {}),
|
|
357
|
+
...(childSeedInstanceId !== undefined ? { childSeedInstanceId } : {}),
|
|
358
|
+
...(childQuery !== undefined ? { childQuery } : {}),
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** Discover the SINGLETON instance id a seed directive writes (a fixed `<Agg>:self`-shaped id), by running
|
|
363
|
+
* its plan with a probe payload + birth-probe globals and reading the first written op's instance id when
|
|
364
|
+
* it is a constant (the same id across two distinct probe payloads). `undefined` ⇒ not a fixed singleton. */
|
|
365
|
+
function discoverSingletonInstanceId(seedDir: Directive<unknown>): string | undefined {
|
|
366
|
+
const idsFor = (seed: number): string | undefined => {
|
|
367
|
+
let synth: SynthesizedPayload;
|
|
368
|
+
try {
|
|
369
|
+
synth = synthesizePayload(seedDir.payloadSchema as z.ZodTypeAny, `seed/${seedDir.id}`);
|
|
370
|
+
} catch {
|
|
371
|
+
return undefined;
|
|
372
|
+
}
|
|
373
|
+
const payload: Record<string, unknown> = { ...synth.value };
|
|
374
|
+
for (const f of synth.autoStamped) payload[f] = `2026-0${seed}-01T00:00:00Z`;
|
|
375
|
+
// Make string fields distinct per-seed so a payload-derived id would DIFFER across runs (a true
|
|
376
|
+
// singleton id is constant regardless).
|
|
377
|
+
for (const [k, v] of Object.entries(payload)) if (typeof v === "string" && !synth.autoStamped.includes(k)) payload[k] = `${v}-${seed}`;
|
|
378
|
+
let id: string | undefined;
|
|
379
|
+
withBirthProbeGlobals(() => {
|
|
380
|
+
__resetAuthoring();
|
|
381
|
+
__resetReads();
|
|
382
|
+
__resetBirths();
|
|
383
|
+
try {
|
|
384
|
+
const returned = seedDir.plan(payload as never, deterministicPorts({ physical: seed, replica: 1 }));
|
|
385
|
+
const ops = [...__drainAuthoring(), ...(Array.isArray(returned) ? returned : [])];
|
|
386
|
+
const first = ops.find((o) => (o as { aggregateId?: string }).aggregateId !== undefined) as { aggregateId?: string } | undefined;
|
|
387
|
+
id = first?.aggregateId;
|
|
388
|
+
} catch {
|
|
389
|
+
id = undefined;
|
|
390
|
+
} finally {
|
|
391
|
+
try { __drainReads(); __drainBirths(); } catch { /* drained */ }
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
return id;
|
|
395
|
+
};
|
|
396
|
+
const a = idsFor(1);
|
|
397
|
+
const b = idsFor(2);
|
|
398
|
+
// A fixed singleton: a non-empty id that does NOT vary with the payload AND isn't a per-instance type tag
|
|
399
|
+
// (a TYPE id equals the aggregate type — that's the legacy one-per-workspace shape, also fine to read by id).
|
|
400
|
+
return a !== undefined && a === b ? a : undefined;
|
|
401
|
+
}
|
|
402
|
+
|
|
182
403
|
// ── the generator ─────────────────────────────────────────────────────────────────
|
|
183
404
|
|
|
184
405
|
export interface TsProofOptions {
|
|
@@ -188,28 +409,51 @@ export interface TsProofOptions {
|
|
|
188
409
|
readonly domainHash: string;
|
|
189
410
|
}
|
|
190
411
|
|
|
412
|
+
/** THE PROOF TAXONOMY (co2 feedback): which legs a domain carries.
|
|
413
|
+
* · `compile-valid` — the law compiled (always — every compiled domain is at least this).
|
|
414
|
+
* · `standalone-provable` — a `.creates` write proven offline (dispatch → declared query/count locally).
|
|
415
|
+
* · `child-birth-provable` — a `.births()` directive proven END-TO-END into the child it births.
|
|
416
|
+
* · `live-cloud-provable` — the full cloud loop (the `.mts` `--live` lane) completes (the primary domain). */
|
|
417
|
+
export type ProofKind = "compile-valid" | "standalone-provable" | "child-birth-provable" | "live-cloud-provable";
|
|
418
|
+
|
|
191
419
|
/** The machine-readable OFFLINE legs for ONE domain (the `@nomos-proof-legs`
|
|
192
420
|
* marker shape; the `proof-legs.json` index is a map of these by domain key). */
|
|
193
421
|
export interface ProofLegs {
|
|
194
422
|
readonly domain: string;
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
readonly
|
|
423
|
+
/** THE TAXONOMY (co2 feedback) — the legs this domain has, in proof order. EVERY domain has at least
|
|
424
|
+
* `compile-valid`. OPTIONAL + ADDITIVE: an older-compiler legs file without it is treated as
|
|
425
|
+
* `["standalone-provable"]` (it only emitted standalone-provable domains). */
|
|
426
|
+
readonly legs?: readonly ProofKind[];
|
|
427
|
+
/** The standalone-create directive id — present iff this domain is `standalone-provable`. */
|
|
428
|
+
readonly directiveId?: string;
|
|
429
|
+
readonly aggregateId?: string;
|
|
430
|
+
readonly payload?: Record<string, unknown>;
|
|
431
|
+
readonly autoStamped?: readonly string[];
|
|
199
432
|
readonly mintField?: string;
|
|
200
433
|
readonly query?: { readonly id: string; readonly params: Record<string, unknown> };
|
|
201
434
|
readonly count?: { readonly id: string; readonly group: string };
|
|
435
|
+
/** THE CHILD-BIRTH LEG — present iff this domain has a `.births()` directive (it is `child-birth-provable`).
|
|
436
|
+
* When this domain is itself the BORN child's law (no standalone create), the index also registers the
|
|
437
|
+
* child domain key pointing at the SAME leg, so `proof --domain <child>` resolves through the parent birth. */
|
|
438
|
+
readonly childBirth?: ChildBirthLeg;
|
|
202
439
|
}
|
|
203
440
|
|
|
204
|
-
/** Synthesize the offline legs for one domain module, or `null` when it has no
|
|
205
|
-
* creating write (nothing to prove
|
|
206
|
-
*
|
|
207
|
-
* per-domain index) both call this, so the offline lane proves any domain the
|
|
441
|
+
/** Synthesize the standalone-create offline legs for one domain module, or `null` when it has no
|
|
442
|
+
* creating write (nothing to prove standalone — it may still be child-birth-provable / compile-valid).
|
|
443
|
+
* The SINGLE source of the create-leg selection — `generateTsProof` (the .mts marker) and
|
|
444
|
+
* `proofLegsIndex` (the per-domain index) both call this, so the offline lane proves any domain the
|
|
208
445
|
* same way the generated proof proves its primary one. */
|
|
446
|
+
/** A `.creates` directive the proof can DRIVE from a synthesized sample offline: not a `.births()` (a birth
|
|
447
|
+
* is the child-birth lane), not ATTESTATION-AUTHORIZED (needs a signed §3 quote no synthesizer can produce —
|
|
448
|
+
* e.g. the home law's `recordShare`). Those are NOT standalone-provable offline; the domain proves via its
|
|
449
|
+
* child-birth leg / compile-valid instead. */
|
|
450
|
+
function isStandaloneCreate(d: Directive<unknown> | undefined): boolean {
|
|
451
|
+
return d?.marker === "creates" && typeof d.payloadSchema === "object"
|
|
452
|
+
&& d.birthsChild !== true && d.attestationAuthorizedGate === undefined;
|
|
453
|
+
}
|
|
454
|
+
|
|
209
455
|
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
|
-
);
|
|
456
|
+
const createDir = (mod.directives as Directive<unknown>[]).find((d) => isStandaloneCreate(d));
|
|
213
457
|
if (createDir === undefined) return null;
|
|
214
458
|
const domain = mod.domain ?? mod.name;
|
|
215
459
|
const agg = (mod.aggregates as AggregateHandle[]).find((a) => a.id === createDir.aggregateId);
|
|
@@ -226,6 +470,7 @@ export function proofLegsForModule(mod: DomainModule): ProofLegs | null {
|
|
|
226
470
|
.find((c) => c.of === agg.id && (c as { where?: unknown }).where == null && (c.by == null || baseValue[c.by] !== undefined));
|
|
227
471
|
return {
|
|
228
472
|
domain,
|
|
473
|
+
legs: ["compile-valid", "standalone-provable"],
|
|
229
474
|
directiveId: createDir.id,
|
|
230
475
|
aggregateId: agg.id,
|
|
231
476
|
payload: baseValue,
|
|
@@ -236,49 +481,309 @@ export function proofLegsForModule(mod: DomainModule): ProofLegs | null {
|
|
|
236
481
|
};
|
|
237
482
|
}
|
|
238
483
|
|
|
239
|
-
/** The
|
|
240
|
-
|
|
241
|
-
|
|
484
|
+
/** The first `.births()` directive in a module (with a synthesizable payload), or undefined. */
|
|
485
|
+
function birthDirectiveOf(mod: DomainModule): Directive<unknown> | undefined {
|
|
486
|
+
return (mod.directives as Directive<unknown>[]).find((d) => d?.birthsChild === true && typeof d.payloadSchema === "object");
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/** Build the legs for ONE domain module — every leg it carries (compile-valid always; standalone-provable
|
|
490
|
+
* when it has a `.creates`; child-birth-provable when it has a `.births()`). NEVER null: every compiled
|
|
491
|
+
* domain is at least `compile-valid` (gaps 1+3 — every domain is proof-addressable). */
|
|
492
|
+
export function legsForDomain(mod: DomainModule, modules: readonly DomainModule[]): ProofLegs {
|
|
493
|
+
const domain = mod.domain ?? mod.name;
|
|
494
|
+
// The standalone-create leg (when the domain has a non-birth `.creates`). FAIL SOFT: an exotic payload
|
|
495
|
+
// shape the synthesizer can't sample just drops the standalone leg (the domain stays compile-valid /
|
|
496
|
+
// child-birth-provable), never loses the index.
|
|
497
|
+
let standalone: ProofLegs | null = null;
|
|
498
|
+
try {
|
|
499
|
+
standalone = proofLegsForModule(mod);
|
|
500
|
+
} catch {
|
|
501
|
+
standalone = null;
|
|
502
|
+
}
|
|
503
|
+
// The child-birth leg (when the domain has a `.births()` directive) — discovered by running its plan.
|
|
504
|
+
const birthDir = birthDirectiveOf(mod);
|
|
505
|
+
const childBirth = birthDir !== undefined ? discoverChildBirthLeg(birthDir, domain, modules) : undefined;
|
|
506
|
+
|
|
507
|
+
const legs: ProofKind[] = ["compile-valid"];
|
|
508
|
+
if (standalone !== null) legs.push("standalone-provable");
|
|
509
|
+
if (childBirth !== undefined) legs.push("child-birth-provable");
|
|
510
|
+
|
|
511
|
+
return {
|
|
512
|
+
domain,
|
|
513
|
+
legs,
|
|
514
|
+
...(standalone !== null
|
|
515
|
+
? {
|
|
516
|
+
directiveId: standalone.directiveId,
|
|
517
|
+
aggregateId: standalone.aggregateId,
|
|
518
|
+
payload: standalone.payload,
|
|
519
|
+
autoStamped: standalone.autoStamped,
|
|
520
|
+
...(standalone.mintField !== undefined ? { mintField: standalone.mintField } : {}),
|
|
521
|
+
...(standalone.query !== undefined ? { query: standalone.query } : {}),
|
|
522
|
+
...(standalone.count !== undefined ? { count: standalone.count } : {}),
|
|
523
|
+
}
|
|
524
|
+
: {}),
|
|
525
|
+
...(childBirth !== undefined ? { childBirth } : {}),
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/** The per-domain legs index for `build/<name>.proof-legs.json` — EVERY compiled domain is addressable
|
|
530
|
+
* (gaps 1+3, co2 feedback), with its taxonomy (compile-valid | standalone-provable | child-birth-provable).
|
|
531
|
+
* A CHILD-ONLY law (no standalone create, installed only via a parent birth) is registered under its OWN
|
|
532
|
+
* domain key pointing at the parent birth that proves it. `githolon proof [--domain <key>]` resolves for
|
|
533
|
+
* every domain. The default is the first STANDALONE-provable domain (the live `.mts` lane needs one), else
|
|
534
|
+
* the first child-birth-provable, else the first compiled domain. */
|
|
242
535
|
export function proofLegsIndex(modules: readonly DomainModule[]): { default: string; domains: Record<string, ProofLegs> } | null {
|
|
243
536
|
const domains: Record<string, ProofLegs> = {};
|
|
244
|
-
|
|
537
|
+
const order: string[] = [];
|
|
245
538
|
for (const mod of modules) {
|
|
246
|
-
|
|
247
|
-
|
|
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;
|
|
539
|
+
const legs = legsForDomain(mod, modules);
|
|
540
|
+
if (domains[legs.domain] === undefined) order.push(legs.domain);
|
|
256
541
|
domains[legs.domain] = legs;
|
|
257
|
-
if (first === undefined) first = legs.domain;
|
|
258
542
|
}
|
|
259
|
-
|
|
543
|
+
// CHILD-DOMAIN ADDRESSABILITY: a `.births()` directive names a CHILD domain (e.g. `estate`) that may have
|
|
544
|
+
// no standalone leg of its own. Register that child domain key pointing at the PARENT's child-birth leg
|
|
545
|
+
// (so `proof --domain estate` proves it THROUGH the birth that installs it — the only way it comes to be).
|
|
546
|
+
for (const legs of [...Object.values(domains)]) {
|
|
547
|
+
const cb = legs.childBirth;
|
|
548
|
+
if (cb === undefined) continue;
|
|
549
|
+
const existing = domains[cb.childDomain];
|
|
550
|
+
if (existing === undefined) {
|
|
551
|
+
// A pure child-only domain whose ONLY proof is the parent birth: register it as child-birth-provable.
|
|
552
|
+
domains[cb.childDomain] = { domain: cb.childDomain, legs: ["compile-valid", "child-birth-provable"], childBirth: cb };
|
|
553
|
+
if (!order.includes(cb.childDomain)) order.push(cb.childDomain);
|
|
554
|
+
} else if (existing.childBirth === undefined) {
|
|
555
|
+
// The child domain ALSO compiled standalone (e.g. it has its own create) — add the child-birth leg to it.
|
|
556
|
+
const mergedLegs = [...new Set([...(existing.legs ?? ["compile-valid"]), "child-birth-provable" as const])];
|
|
557
|
+
domains[cb.childDomain] = { ...existing, legs: mergedLegs, childBirth: cb };
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
if (order.length === 0) return null;
|
|
561
|
+
const standaloneFirst = order.find((d) => (domains[d]!.legs ?? []).includes("standalone-provable"));
|
|
562
|
+
const childBirthFirst = order.find((d) => (domains[d]!.legs ?? []).includes("child-birth-provable"));
|
|
563
|
+
const def = standaloneFirst ?? childBirthFirst ?? order[0]!;
|
|
564
|
+
return { default: def, domains };
|
|
260
565
|
}
|
|
261
566
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
567
|
+
// ── shared `.mts` fragments (the v2 contract — G1: no host secret; deploy is OPEN + kernel-gated) ─────────
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* THE V2 PREAMBLE (G1, gap 4): create a throwaway workspace (NO secret comes back — ownership is a ledger
|
|
571
|
+
* fact), then deploy YOUR compiled law via the OPEN lane (`POST /domains`, no Bearer; write-authority is
|
|
572
|
+
* kernel-judged at the offer gate). Identity rides `x-nomos-principal` (transitional bare-uid lane), the
|
|
573
|
+
* same header `cli/src/cloud.ts deploy()` + the 0.18 host-strip lane present. Declares `fail`/`ok`/`deploy`/
|
|
574
|
+
* `r`/`d`/`runError`/`PRINCIPAL` and opens the `try {` (closed by {@link v2CatchFinally}).
|
|
575
|
+
*/
|
|
576
|
+
function v2IdentityAndCleanupPreamble(opts: TsProofOptions, hashConst: string, n: () => number): string[] {
|
|
577
|
+
return [
|
|
578
|
+
`// IDENTITY NOTE: this proof uses the bare x-nomos-principal lane so it is SELF-CONTAINED (a throwaway`,
|
|
579
|
+
`// workspace, no stored credentials). Real apps use \`githolon login --agent\` (a VERIFIED identity)`,
|
|
580
|
+
`// + \`githolon ws create/deploy\`. (G1: a create returns NO workspaceSecret — ownership is a ledger`,
|
|
581
|
+
`// fact; deploy is OPEN + kernel-gated, authenticated by the principal, not a host secret.)`,
|
|
582
|
+
`const fail: (m: string) => never = (m) => { throw new Error(m); };`,
|
|
583
|
+
`const ok = (m: string) => console.log("✓ " + m);`,
|
|
584
|
+
`const PRINCIPAL = process.env.NOMOS_PRINCIPAL || "githolon-proof";`,
|
|
585
|
+
``,
|
|
586
|
+
`const deploy = JSON.parse(readFileSync(new URL(${JSON.stringify(`./${opts.packageName}.deploy.json`)}, import.meta.url), "utf8"));`,
|
|
587
|
+
``,
|
|
588
|
+
`// CLEANUP CONTRACT: the throwaway workspace is RETIRED on every exit (green or jammed) — a proof never`,
|
|
589
|
+
`// leaves orphans. PROOF_KEEP=1 (githolon proof --live --keep) keeps it and prints the persist command.`,
|
|
590
|
+
`let runError: unknown = null;`,
|
|
591
|
+
`let born_ws = false;`,
|
|
592
|
+
`try {`,
|
|
593
|
+
``,
|
|
594
|
+
`// ${n()}. a throwaway workspace — born by the create (G1: NO secret is returned; ownership is a ledger fact)`,
|
|
595
|
+
`let r = await fetch(\`\${CLOUD}/v2/workspaces/\${WS}\`, { method: "POST", headers: { "x-nomos-principal": PRINCIPAL } });`,
|
|
596
|
+
`let d = await r.json();`,
|
|
597
|
+
`if (!d.ok) fail(\`create workspace: \${JSON.stringify(d)}\`);`,
|
|
598
|
+
`born_ws = true;`,
|
|
599
|
+
`ok(\`workspace \${WS} created (no host secret — ownership is a ledger fact; PROOF_KEEP=1 keeps it)\`);`,
|
|
600
|
+
``,
|
|
601
|
+
`// ${n()}. deploy YOUR compiled law via the OPEN lane (POST /domains, no Bearer — kernel-judged authority,`,
|
|
602
|
+
`// authenticated as PRINCIPAL via x-nomos-principal; a refusal answers as a TYPED kernel verdict 422)`,
|
|
603
|
+
`r = await fetch(\`\${CLOUD}/v2/workspaces/\${WS}/domains\`, { method: "POST", headers: { "content-type": "application/json", "x-nomos-principal": PRINCIPAL }, body: JSON.stringify(deploy) });`,
|
|
604
|
+
`d = await r.json();`,
|
|
605
|
+
`if (!d.ok || d.installation?.[0]?.data?.["status.phase"] !== "Active") fail(\`deploy: \${JSON.stringify(d)}\`);`,
|
|
606
|
+
`if (d.domainHash !== ${hashConst}) fail(\`deploy hash \${d.domainHash} != the generated client's \${${hashConst}}\`);`,
|
|
607
|
+
`ok(\`law deployed Active — \${${hashConst}.slice(0, 12)}… (the content hash baked into your typed client)\`);`,
|
|
608
|
+
];
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* THE V2 CATCH/FINALLY (G1, gap 4): retire the throwaway workspace on every exit via the OPEN lane (no
|
|
613
|
+
* Bearer — there is no host secret to present; a retire is a SIGNED governance offer in the real lane, but
|
|
614
|
+
* the open self-retire suffices to clean up a throwaway). PROOF_KEEP=1 keeps it.
|
|
615
|
+
*/
|
|
616
|
+
function v2CatchFinally(): string[] {
|
|
617
|
+
return [
|
|
618
|
+
`} catch (e) {`,
|
|
619
|
+
` runError = e;`,
|
|
620
|
+
` console.error("✗ " + ((e as Error)?.message ?? e));`,
|
|
621
|
+
`} finally {`,
|
|
622
|
+
` // THE CLEANUP CONTRACT: never an orphaned cloud workspace. Retired on every exit path (open lane — no`,
|
|
623
|
+
` // host secret exists post-G1); PROOF_KEEP=1 keeps it and prints the persist command.`,
|
|
624
|
+
` if (born_ws && process.env.PROOF_KEEP === "1") {`,
|
|
625
|
+
` console.log(\`workspace \${WS} KEPT — retire it when done: githolon ws retire \${WS}\`);`,
|
|
626
|
+
` } else if (born_ws) {`,
|
|
627
|
+
` const rr = await fetch(\`\${CLOUD}/v2/workspaces/\${WS}/retire\`, { method: "POST", headers: { "x-nomos-principal": PRINCIPAL } }).then((x) => x.json()).catch(() => ({}));`,
|
|
628
|
+
` console.log(\` retired \${WS}: \${(rr as { ok?: boolean }).ok === true}\`);`,
|
|
629
|
+
` }`,
|
|
630
|
+
` process.exit(runError ? 1 : 0);`,
|
|
631
|
+
`}`,
|
|
632
|
+
];
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* THE LIVE CHILD-BIRTH LEG (G3, gap 2): drive the parent `.births()` directive against the cloud workspace,
|
|
637
|
+
* then read the BORN CHILD workspace it spawned (the kernel's offer-effect installs the child law in-chain +
|
|
638
|
+
* folds the child genesis through the child's OWN gate). Verifies the child came up BORN (its read model has
|
|
639
|
+
* the seeded record) — the cloud's offer response surfaces `born: [{workspace, head}]`. The frameworkHash +
|
|
640
|
+
* lawHash are injected from the deployed law (the child install resolves bytes by hash from the parent's
|
|
641
|
+
* custody; a `sample-*` hash would fail). The dispatch rides the gateway offer lane (authenticated principal).
|
|
642
|
+
*/
|
|
643
|
+
function liveChildBirthLegLines(cb: ChildBirthLeg, opts: TsProofOptions, n: () => number): string[] {
|
|
644
|
+
const hashConst = tsHashConstName(opts.packageName);
|
|
645
|
+
// The birth payload literal: the synthesized values + the REAL hashes onto the discovered fields + the
|
|
646
|
+
// auto-stamped `…At` fields stamped now.
|
|
647
|
+
const lines: string[] = [];
|
|
648
|
+
lines.push(
|
|
649
|
+
`// ${n()}. CHILD BIRTH (G3 — birth is the defined consequence of an offer): drive \`${cb.parentDomain}/${cb.birthDirectiveId}\``,
|
|
650
|
+
`// against the cloud; the kernel's offer-effect installs the child law in-chain + folds the child's`,
|
|
651
|
+
`// genesis (estate/${cb.childSeedDirectiveId}) through the CHILD's own gate, then BORNS the child workspace.`,
|
|
652
|
+
`{`,
|
|
653
|
+
` const birthPayload: Record<string, unknown> = { ...${JSON.stringify(cb.birthPayload)} };`,
|
|
654
|
+
...(cb.frameworkHashField !== undefined
|
|
655
|
+
? [
|
|
656
|
+
` // The framework CONTROLLER hash (the bootstrap install-target the child runs as intent 0) — read it`,
|
|
657
|
+
` // from the workspace's own status (the kernel's controllerHash); the child install resolves the bytes`,
|
|
658
|
+
` // by hash from the parent's custody, so this MUST be the real framework hash (a guess would fail).`,
|
|
659
|
+
` const st = await (await fetch(\`\${CLOUD}/v2/workspaces/\${WS}\`)).json().catch(() => ({}));`,
|
|
660
|
+
` const frameworkHash = st.controller?.domainHash ?? st.controllerHash ?? "";`,
|
|
661
|
+
` if (!frameworkHash) fail("could not resolve the framework controller hash from the workspace status — needed for the child's in-chain installDomain");`,
|
|
662
|
+
` birthPayload[${JSON.stringify(cb.frameworkHashField)}] = frameworkHash;`,
|
|
663
|
+
]
|
|
664
|
+
: []),
|
|
665
|
+
` birthPayload[${JSON.stringify(cb.lawHashField ?? "lawHash")}] = ${hashConst};`,
|
|
666
|
+
...cb.birthAutoStamped.map((f) => ` birthPayload[${JSON.stringify(f)}] = new Date().toISOString();`),
|
|
667
|
+
` const br = await fetch(\`\${CLOUD}/v2/workspaces/\${WS}/${cb.birthDirectiveId}\`, { method: "POST", headers: { "content-type": "application/json", "x-nomos-principal": PRINCIPAL }, body: JSON.stringify(birthPayload) });`,
|
|
668
|
+
` const bd = await br.json();`,
|
|
669
|
+
` if (!bd.ok) fail(\`child birth offer ${cb.parentDomain}/${cb.birthDirectiveId} refused: \${JSON.stringify(bd)}\`);`,
|
|
670
|
+
` const born: Array<{ workspace?: string }> = bd.born ?? [];`,
|
|
671
|
+
` const childWs = born[0]?.workspace;`,
|
|
672
|
+
` if (!childWs) fail(\`birth admitted but the offer-effect spawned no child (born=\${JSON.stringify(born)})\`);`,
|
|
673
|
+
` ok(\`child birth — \${childWs} born by ${cb.parentDomain}/${cb.birthDirectiveId} (the offer-effect installed the child law + folded its genesis)\`);`,
|
|
674
|
+
` // The BORN child runs its OWN law: read the seeded ${cb.childSeedAggregateId ?? "record"} back from the child workspace.`,
|
|
675
|
+
...(cb.childSeedInstanceId !== undefined
|
|
676
|
+
? [
|
|
677
|
+
` const cr = await fetch(\`\${CLOUD}/v2/workspaces/\${childWs}/aggregates/\${encodeURIComponent(${JSON.stringify(cb.childSeedInstanceId)})}\`).then((x) => x.json()).catch(() => ({}));`,
|
|
678
|
+
` if (!(cr.rows?.length >= 1)) fail(\`the born child did not seed ${cb.childSeedAggregateId ?? "its record"} (${cb.childSeedInstanceId}): \${JSON.stringify(cr)}\`);`,
|
|
679
|
+
` ok(\`the born child's read model has the genesis seed — ${cb.childSeedDirectiveId} ran inside \${childWs}\`);`,
|
|
680
|
+
]
|
|
681
|
+
: cb.childQuery !== undefined
|
|
682
|
+
? [
|
|
683
|
+
` const cr = await fetch(\`\${CLOUD}/v2/workspaces/\${childWs}/${cb.childQuery.id}?${cb.childQuery.key.map((k) => `${encodeURIComponent(k)}=\${encodeURIComponent(String(birthPayload[${JSON.stringify(k)}] ?? ""))}`).join("&")}\`).then((x) => x.json()).catch(() => ({}));`,
|
|
684
|
+
` if (!(cr.ok && cr.rows?.length >= 1)) fail(\`the born child's declared query ${cb.childQuery.id} returned nothing: \${JSON.stringify(cr)}\`);`,
|
|
685
|
+
` ok(\`the born child's declared query ${cb.childQuery.id} returns the genesis seed — ${cb.childSeedDirectiveId} ran inside \${childWs}\`);`,
|
|
686
|
+
]
|
|
687
|
+
: [
|
|
688
|
+
` ok(\`the child \${childWs} is born (no declared read on its seed aggregate — declare a query to read it back)\`);`,
|
|
689
|
+
]),
|
|
690
|
+
`}`,
|
|
691
|
+
``,
|
|
267
692
|
);
|
|
693
|
+
return lines;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* THE BIRTH-ONLY PROOF (gap 2): a package whose primary write is a `.births()` directive (no standalone
|
|
698
|
+
* create — the home/estate composable shape). Deploy the parent law, drive the birth, verify the born child.
|
|
699
|
+
* (The offline runner proves the same legs LOCALLY on the vendored wasm — that is the default `githolon
|
|
700
|
+
* proof` lane; this `.mts` is the live `--live` lane.)
|
|
701
|
+
*/
|
|
702
|
+
function generateBirthOnlyProof(
|
|
703
|
+
mod: DomainModule,
|
|
704
|
+
modules: readonly DomainModule[],
|
|
705
|
+
opts: TsProofOptions,
|
|
706
|
+
primaryLegs: ProofLegs,
|
|
707
|
+
): string {
|
|
708
|
+
void modules;
|
|
709
|
+
const domain = mod.domain ?? mod.name;
|
|
710
|
+
const factory = tsClientFactoryName(domain);
|
|
711
|
+
const hashConst = tsHashConstName(opts.packageName);
|
|
712
|
+
const cb = primaryLegs.childBirth;
|
|
713
|
+
if (cb === undefined) {
|
|
714
|
+
throw new Error(
|
|
715
|
+
`domain '${domain}' has a .births() directive but its child-birth leg could not be discovered (the plan did not record a leaf genesis recipe: bootstrap/installDomain → nomos/installDomain → <child>/<seed>) — prove it by hand in test/e2e.mts`,
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
const slug =
|
|
719
|
+
opts.packageName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 24) || "domain";
|
|
720
|
+
const legsMarker = JSON.stringify(primaryLegs);
|
|
721
|
+
let step = 0;
|
|
722
|
+
const n = () => ++step;
|
|
723
|
+
const out: string[] = [];
|
|
724
|
+
out.push(
|
|
725
|
+
`// AUTO-GENERATED by nomos-compile — the RUNNABLE PROOF for package "${opts.packageName}",`,
|
|
726
|
+
`// synthesized from the domain's OWN law. This package's primary write is a BIRTH (a .births()`,
|
|
727
|
+
`// directive), so the proof drives the birth end-to-end into the BORN child. Reshape the law,`,
|
|
728
|
+
`// recompile, and this proof reshapes itself — no test rewriting. Run it:`,
|
|
729
|
+
`//`,
|
|
730
|
+
`// npx githolon proof (offline legs on a local holon)`,
|
|
731
|
+
`// npx githolon proof --live (or: npx tsx build/${opts.packageName}.proof.mts — the full cloud loop)`,
|
|
732
|
+
`//`,
|
|
733
|
+
`// Regenerated on every compile; do not edit.`,
|
|
734
|
+
`// @nomos-proof-legs ${legsMarker}`,
|
|
735
|
+
`import { readFileSync } from "node:fs";`,
|
|
736
|
+
`import { connect } from "@githolon/client";`,
|
|
737
|
+
`import { ${factory}, ${hashConst} } from "./${opts.packageName}.client.ts";`,
|
|
738
|
+
`void connect; void ${factory};`,
|
|
739
|
+
``,
|
|
740
|
+
`const CLOUD = (process.env.NOMOS_CLOUD || "https://nomos.captainapp.co.uk").replace(/\\/+$/, "");`,
|
|
741
|
+
`const WS = process.env.NOMOS_WS || ${JSON.stringify(slug + "-proof-")} + Math.random().toString(36).slice(2, 8);`,
|
|
742
|
+
``,
|
|
743
|
+
...v2IdentityAndCleanupPreamble(opts, hashConst, n),
|
|
744
|
+
``,
|
|
745
|
+
...liveChildBirthLegLines(cb, opts, n),
|
|
746
|
+
`console.log(\`\\nALL GREEN — generated proof for "${opts.packageName}": deploy → child birth (\${WS} → the born child) → verified\`);`,
|
|
747
|
+
``,
|
|
748
|
+
...v2CatchFinally(),
|
|
749
|
+
``,
|
|
750
|
+
);
|
|
751
|
+
return out.join("\n");
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
export function generateTsProof(modules: readonly DomainModule[], opts: TsProofOptions): string {
|
|
755
|
+
// PRIMARY-DOMAIN SELECTION: the live `.mts` drives a STANDALONE create when the package has one (the full
|
|
756
|
+
// deploy → write → query → sync → cloud loop). A BIRTH-ONLY package (every `.creates` is also a `.births()`
|
|
757
|
+
// — the home/estate composable shape) has no standalone create to drive, so the primary becomes the first
|
|
758
|
+
// `.births()` domain and the live `.mts` drives the birth as its primary write. Either way, the marker =
|
|
759
|
+
// the primary domain's FULL legs (so the offline runner + the index never describe different directives).
|
|
760
|
+
const hasStandaloneCreate = (m: DomainModule): boolean =>
|
|
761
|
+
(m.directives as Directive<unknown>[]).some((d) => isStandaloneCreate(d));
|
|
762
|
+
const hasBirth = (m: DomainModule): boolean => birthDirectiveOf(m) !== undefined;
|
|
763
|
+
const mod = modules.find(hasStandaloneCreate) ?? modules.find(hasBirth);
|
|
268
764
|
if (mod === undefined) {
|
|
269
765
|
throw new Error(
|
|
270
|
-
`no .creates directive in any domain — a generated proof needs one creating write; add a directive(...).creates(...) (test/e2e.mts remains the hand-authored lane)`,
|
|
766
|
+
`no .creates directive in any domain — a generated proof needs one creating write (or a .births() birth directive); add a directive(...).creates(...) (test/e2e.mts remains the hand-authored lane)`,
|
|
271
767
|
);
|
|
272
768
|
}
|
|
273
769
|
const domain = mod.domain ?? mod.name;
|
|
274
770
|
const factory = tsClientFactoryName(domain);
|
|
275
771
|
const hashConst = tsHashConstName(opts.packageName);
|
|
772
|
+
// THE MARKER (since 0.5.0) + the per-domain index are ONE synthesis (`legsForDomain`) — the proof body, its
|
|
773
|
+
// marker, and the index can never describe different directives. The primary domain's legs carry whichever
|
|
774
|
+
// of standalone / child-birth it has.
|
|
775
|
+
const primaryLegs = legsForDomain(mod, modules);
|
|
776
|
+
const childBirth = primaryLegs.childBirth;
|
|
276
777
|
|
|
277
|
-
//
|
|
278
|
-
//
|
|
778
|
+
// The standalone-create lane (when the primary has one). A birth-only primary skips this — its primary
|
|
779
|
+
// write is the child-birth leg below.
|
|
279
780
|
const createDir = (mod.directives as Directive<unknown>[]).find(
|
|
280
|
-
(d) => d?.marker === "creates" && typeof d.payloadSchema === "object",
|
|
281
|
-
)
|
|
781
|
+
(d) => d?.marker === "creates" && typeof d.payloadSchema === "object" && d.birthsChild !== true,
|
|
782
|
+
);
|
|
783
|
+
if (createDir === undefined) {
|
|
784
|
+
// BIRTH-ONLY PRIMARY: emit a birth-shaped proof (no standalone create/query/count/concurrency legs).
|
|
785
|
+
return generateBirthOnlyProof(mod, modules, opts, primaryLegs);
|
|
786
|
+
}
|
|
282
787
|
const agg = (mod.aggregates as AggregateHandle[]).find((a) => a.id === createDir.aggregateId);
|
|
283
788
|
if (agg === undefined) {
|
|
284
789
|
throw new Error(
|
|
@@ -341,9 +846,9 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
|
|
|
341
846
|
const listAccessor = `list${agg.id}s`;
|
|
342
847
|
|
|
343
848
|
// THE MACHINE-READABLE LEGS (parsed by `githolon`'s offline proof runner —
|
|
344
|
-
// cli/src/proof_offline.ts). The ONE leg-synthesis (`
|
|
849
|
+
// cli/src/proof_offline.ts). The ONE leg-synthesis (`legsForDomain`) feeds
|
|
345
850
|
// both this marker and the per-domain `proof-legs.json` index, so they never drift.
|
|
346
|
-
const legsMarker = JSON.stringify(
|
|
851
|
+
const legsMarker = JSON.stringify(primaryLegs);
|
|
347
852
|
|
|
348
853
|
let step = 0;
|
|
349
854
|
const n = () => ++step;
|
|
@@ -366,35 +871,7 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
|
|
|
366
871
|
`const CLOUD = (process.env.NOMOS_CLOUD || "https://nomos.captainapp.co.uk").replace(/\\/+$/, "");`,
|
|
367
872
|
`const WS = process.env.NOMOS_WS || ${JSON.stringify(slug + "-proof-")} + Math.random().toString(36).slice(2, 8);`,
|
|
368
873
|
``,
|
|
369
|
-
|
|
370
|
-
`// SELF-CONTAINED (a throwaway workspace, no stored credentials). Real apps use`,
|
|
371
|
-
`// \`githolon login --agent\` (a VERIFIED identity) + \`githolon ws create/deploy\`.`,
|
|
372
|
-
`const fail: (m: string) => never = (m) => { throw new Error(m); };`,
|
|
373
|
-
`const ok = (m: string) => console.log("✓ " + m);`,
|
|
374
|
-
``,
|
|
375
|
-
`const deploy = JSON.parse(readFileSync(new URL(${JSON.stringify(`./${opts.packageName}.deploy.json`)}, import.meta.url), "utf8"));`,
|
|
376
|
-
``,
|
|
377
|
-
`// CLEANUP CONTRACT: the throwaway workspace is RETIRED on every exit (green or`,
|
|
378
|
-
`// jammed) — a proof never leaves orphans. PROOF_KEEP=1 (githolon proof --live --keep)`,
|
|
379
|
-
`// keeps it and prints the FULL secret once, with the persist command.`,
|
|
380
|
-
`let SECRET = "";`,
|
|
381
|
-
`let runError: unknown = null;`,
|
|
382
|
-
`try {`,
|
|
383
|
-
``,
|
|
384
|
-
`// ${n()}. a throwaway workspace (the ONE-TIME secret comes back on create)`,
|
|
385
|
-
`let r = await fetch(\`\${CLOUD}/v2/workspaces/\${WS}\`, { method: "POST", headers: { "x-nomos-principal": "githolon-proof" } });`,
|
|
386
|
-
`let d = await r.json();`,
|
|
387
|
-
`if (!d.ok) fail(\`create workspace: \${JSON.stringify(d)}\`);`,
|
|
388
|
-
`SECRET = d.workspaceSecret;`,
|
|
389
|
-
`if (!SECRET?.startsWith("nws_v1_")) fail(\`no workspaceSecret returned: \${JSON.stringify(d)}\`);`,
|
|
390
|
-
`ok(\`workspace \${WS} created (one-time secret held for cleanup — PROOF_KEEP=1 keeps the workspace and prints it)\`);`,
|
|
391
|
-
``,
|
|
392
|
-
`// ${n()}. deploy YOUR compiled law (build/${opts.packageName}.deploy.json) WITH the secret`,
|
|
393
|
-
`r = await fetch(\`\${CLOUD}/v2/workspaces/\${WS}/domains\`, { method: "POST", headers: { "content-type": "application/json", authorization: \`Bearer \${SECRET}\` }, body: JSON.stringify(deploy) });`,
|
|
394
|
-
`d = await r.json();`,
|
|
395
|
-
`if (!d.ok || d.installation?.[0]?.data?.["status.phase"] !== "Active") fail(\`deploy: \${JSON.stringify(d)}\`);`,
|
|
396
|
-
`if (d.domainHash !== ${hashConst}) fail(\`deploy hash \${d.domainHash} != the generated client's \${${hashConst}}\`);`,
|
|
397
|
-
`ok(\`law deployed Active — \${${hashConst}.slice(0, 12)}… (the content hash baked into your typed client)\`);`,
|
|
874
|
+
...v2IdentityAndCleanupPreamble(opts, hashConst, n),
|
|
398
875
|
``,
|
|
399
876
|
`// ${n()}. connect + bind the GENERATED typed client`,
|
|
400
877
|
`const holon = await connect({ cloud: CLOUD, workspace: WS, clientId: "proof-1" });`,
|
|
@@ -532,6 +1009,12 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
|
|
|
532
1009
|
);
|
|
533
1010
|
}
|
|
534
1011
|
|
|
1012
|
+
// THE CHILD-BIRTH LEG (G3, gap 2): when the primary domain has a `.births()` directive, prove the birth
|
|
1013
|
+
// END-TO-END against the cloud — drive the parent birth offer, then read the BORN child workspace it spawns.
|
|
1014
|
+
if (childBirth !== undefined) {
|
|
1015
|
+
out.push(...liveChildBirthLegLines(childBirth, opts, n));
|
|
1016
|
+
}
|
|
1017
|
+
|
|
535
1018
|
out.push(
|
|
536
1019
|
`// ${n()}. CONVERGENCE: the original instance adopts canonical main in place — no reconnect`,
|
|
537
1020
|
`await holon.pull();`,
|
|
@@ -541,23 +1024,9 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
|
|
|
541
1024
|
`if (head !== remoteMain) fail(\`not converged in place: local \${head?.slice(0, 10)} vs main \${remoteMain?.slice(0, 10)}\`);`,
|
|
542
1025
|
`ok("converged in place — local head == canonical main");`,
|
|
543
1026
|
``,
|
|
544
|
-
`console.log(\`\\nALL GREEN — generated proof for "${opts.packageName}": deploy → offline write → local reads → admission → cloud reads${concurrency ? ` → AddWins merge` : ``} → convergence (\${WS})\`);`,
|
|
1027
|
+
`console.log(\`\\nALL GREEN — generated proof for "${opts.packageName}": deploy → offline write → local reads → admission → cloud reads${concurrency ? ` → AddWins merge` : ``}${childBirth !== undefined ? ` → child birth` : ``} → convergence (\${WS})\`);`,
|
|
545
1028
|
``,
|
|
546
|
-
|
|
547
|
-
` runError = e;`,
|
|
548
|
-
` console.error("✗ " + ((e as Error)?.message ?? e));`,
|
|
549
|
-
`} finally {`,
|
|
550
|
-
` // THE CLEANUP CONTRACT: never an orphaned cloud workspace. Retired on every`,
|
|
551
|
-
` // exit path; PROOF_KEEP=1 keeps it and prints the FULL secret exactly once.`,
|
|
552
|
-
` if (SECRET && process.env.PROOF_KEEP === "1") {`,
|
|
553
|
-
` console.log(\`workspace \${WS} KEPT — secret \${SECRET}\`);`,
|
|
554
|
-
` console.log(\` persist it: githolon secret set \${WS} <the secret above> (then: githolon ws retire \${WS} when done)\`);`,
|
|
555
|
-
` } else if (SECRET) {`,
|
|
556
|
-
` const rr = await fetch(\`\${CLOUD}/v2/workspaces/\${WS}/retire\`, { method: "POST", headers: { authorization: \`Bearer \${SECRET}\` } }).then((x) => x.json()).catch(() => ({}));`,
|
|
557
|
-
` console.log(\` retired \${WS}: \${(rr as { ok?: boolean }).ok === true}\`);`,
|
|
558
|
-
` }`,
|
|
559
|
-
` process.exit(runError ? 1 : 0);`,
|
|
560
|
-
`}`,
|
|
1029
|
+
...v2CatchFinally(),
|
|
561
1030
|
``,
|
|
562
1031
|
);
|
|
563
1032
|
|
|
@@ -1452,8 +1452,8 @@ async function main(): Promise<void> {
|
|
|
1452
1452
|
console.log(` deploy ${rel(deployPath)}`);
|
|
1453
1453
|
console.log(``);
|
|
1454
1454
|
console.log(`deploy (Nomos Cloud):`);
|
|
1455
|
-
console.log(` githolon ws create <ws> && githolon deploy <ws> #
|
|
1456
|
-
console.log(` # raw lane: curl -X POST -H 'content-type: application/json' -H '
|
|
1455
|
+
console.log(` githolon login --agent && githolon ws create <ws> && githolon deploy <ws> # no secret — deploy is OPEN, kernel-judged at the offer gate`);
|
|
1456
|
+
console.log(` # raw lane: curl -X POST -H 'content-type: application/json' -H 'x-nomos-principal: <your-auth-uid>' \\`);
|
|
1457
1457
|
console.log(` # --data-binary @${rel(deployPath)} https://nomos.captainapp.co.uk/v2/workspaces/<ws>/domains`);
|
|
1458
1458
|
if (proofPath !== undefined) {
|
|
1459
1459
|
console.log(`prove it OFFLINE first: npx githolon proof # the generated proof's offline legs on a LOCAL holon (no cloud workspace)`);
|