@githolon/dsl 0.5.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@githolon/dsl",
3
- "version": "0.5.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",
@@ -188,6 +188,77 @@ export interface TsProofOptions {
188
188
  readonly domainHash: string;
189
189
  }
190
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
+
191
262
  export function generateTsProof(modules: readonly DomainModule[], opts: TsProofOptions): string {
192
263
  // The proof drives the FIRST domain that has a creating write — without one
193
264
  // there is nothing to author, so there is nothing to prove.
@@ -203,7 +274,11 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
203
274
  const factory = tsClientFactoryName(domain);
204
275
  const hashConst = tsHashConstName(opts.packageName);
205
276
 
206
- const createDir = (mod.directives as Directive<unknown>[]).find((d) => d?.marker === "creates")!;
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
+ )!;
207
282
  const agg = (mod.aggregates as AggregateHandle[]).find((a) => a.id === createDir.aggregateId);
208
283
  if (agg === undefined) {
209
284
  throw new Error(
@@ -260,17 +335,9 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
260
335
  const listAccessor = `list${agg.id}s`;
261
336
 
262
337
  // THE MACHINE-READABLE LEGS (parsed by `githolon`'s offline proof runner —
263
- // cli/src/proof_offline.ts instead of regex-scraping the generated source).
264
- const legsMarker = JSON.stringify({
265
- domain,
266
- directiveId: createDir.id,
267
- aggregateId: agg.id,
268
- payload: baseValue,
269
- autoStamped: synth.autoStamped,
270
- ...(mintField !== undefined ? { mintField } : {}),
271
- ...(query && queryParamsObj ? { query: { id: query.id, params: queryParamsObj } } : {}),
272
- ...(count ? { count: { id: count.id, group: count.by != null ? String(baseValue[count.by]) : "" } } : {}),
273
- });
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));
274
341
 
275
342
  let step = 0;
276
343
  const n = () => ++step;
@@ -104,7 +104,7 @@ import {
104
104
  } from "./stable_ids.js";
105
105
  import type { UsdLayer } from "./usd.js";
106
106
  import { generateTsClient, tsClientFactoryName } from "./codegen_ts.js";
107
- import { generateTsProof } from "./codegen_proof.js";
107
+ import { generateTsProof, proofLegsIndex } from "./codegen_proof.js";
108
108
 
109
109
  const DSL_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
110
110
 
@@ -1287,10 +1287,20 @@ async function main(): Promise<void> {
1287
1287
  // synthesizer can't sample yet still compiles — the skip names its remedy.
1288
1288
  let proofPath: string | undefined;
1289
1289
  let proofSkip: string | undefined;
1290
+ let proofDomains: string[] = [];
1290
1291
  try {
1291
1292
  const proofSrc = generateTsProof(domainModules, { packageName: cfg.name, domainHash });
1292
1293
  proofPath = path.join(outDir, `${cfg.name}.proof.mts`);
1293
1294
  writeFileSync(proofPath, proofSrc, "utf8");
1295
+ // THE PER-DOMAIN LEGS INDEX: every domain with a creating write, so
1296
+ // `githolon proof --domain <key>` proves ANY of them offline (the .mts
1297
+ // proves the primary one live). A multi-domain package no longer locks the
1298
+ // proof to whichever domain happens to be first.
1299
+ const legsIndex = proofLegsIndex(domainModules);
1300
+ if (legsIndex !== null) {
1301
+ writeFileSync(path.join(outDir, `${cfg.name}.proof-legs.json`), JSON.stringify(legsIndex, null, 2) + "\n", "utf8");
1302
+ proofDomains = Object.keys(legsIndex.domains);
1303
+ }
1294
1304
  } catch (e) {
1295
1305
  proofSkip = (e as Error).message;
1296
1306
  }
@@ -1385,6 +1395,9 @@ async function main(): Promise<void> {
1385
1395
  console.log(`prove it OFFLINE first: npx githolon proof # the generated proof's offline legs on a LOCAL holon (no cloud workspace)`);
1386
1396
  console.log(` inner loop: npx githolon dev # watch -> recompile -> law live locally -> offline proof, on every save`);
1387
1397
  console.log(` then the cloud loop: npx githolon proof --live # throwaway workspace, retired on exit (--keep keeps it)`);
1398
+ if (proofDomains.length > 1) {
1399
+ console.log(` this package proves ${proofDomains.length} domains (${proofDomains.join(", ")}) — default '${proofDomains[0]}'; pick another: npx githolon proof --domain <key>`);
1400
+ }
1388
1401
  } else {
1389
1402
  console.log(`prove it: npm run e2e # offline write -> sync -> admission -> cloud query, live`);
1390
1403
  }
@@ -139,9 +139,12 @@ function encodeKernelFieldOp(op: FieldOp, field: Field | undefined): WireFieldOp
139
139
  * to test domain LIFECYCLE.
140
140
  *
141
141
  * For a KERNEL-BACKED local harness — admission through the real gate, folds,
142
- * reads, the whole lifecycle on the same engine plane the cloud edge runs — use
143
- * the CLI: `githolon proof` (the generated proof's offline legs on a local
144
- * holon) and `githolon dev` (the watchrecompile→law-live→proof inner loop).
142
+ * reads, minted ids, the whole lifecycle on the same engine plane the cloud
143
+ * edge runs use the CLI, NOT this function:
144
+ * `githolon dev --once` ONE compile law-live-locally proof cycle, then
145
+ * exit (the CI/smoke lane for lifecycle + id-mint tests — a non-zero exit
146
+ * names the jam). `githolon dev` (no flag) is the same on a watch loop.
147
+ * • `githolon proof` — the generated proof's offline legs on a local holon.
145
148
  * Those mint ids, run the gate, and fold — exactly what this function does NOT.
146
149
  */
147
150
  export function executeDirectiveToIntent<P>(