githolon 0.92.0 → 0.93.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -4480,6 +4480,108 @@ var init_governance = __esm({
4480
4480
  }
4481
4481
  });
4482
4482
 
4483
+ // src/capabilities.ts
4484
+ var capabilities_exports = {};
4485
+ __export(capabilities_exports, {
4486
+ INLINE_OFFER_CAP_CHARS: () => INLINE_OFFER_CAP_CHARS,
4487
+ negotiateDeployRoute: () => negotiateDeployRoute,
4488
+ openapiRoutesDirective: () => openapiRoutesDirective,
4489
+ probeCloudCapabilities: () => probeCloudCapabilities,
4490
+ probeKernelCapabilities: () => probeKernelCapabilities,
4491
+ probeOfferLane: () => probeOfferLane,
4492
+ probeStageLane: () => probeStageLane
4493
+ });
4494
+ async function probeStageLane(cloud, fetchImpl = fetch) {
4495
+ try {
4496
+ const r = await fetchImpl(`${cloud}/v2/stage/${ZERO_SHA}`, { method: "GET" });
4497
+ const body = await r.json().catch(() => void 0);
4498
+ return typeof body?.staged === "boolean";
4499
+ } catch {
4500
+ return void 0;
4501
+ }
4502
+ }
4503
+ async function probeKernelCapabilities(cloud, fetchImpl = fetch) {
4504
+ try {
4505
+ const r = await fetchImpl(`${cloud.replace(/\/+$/, "")}/v1/runtime/capabilities`, { method: "GET" });
4506
+ if (!r.ok) return void 0;
4507
+ const body = await r.json().catch(() => void 0);
4508
+ return body && typeof body === "object" ? body : void 0;
4509
+ } catch {
4510
+ return void 0;
4511
+ }
4512
+ }
4513
+ function openapiRoutesDirective(spec, domain, directiveId) {
4514
+ const paths = spec?.paths ?? {};
4515
+ const wantPath = `/${domain}/${directiveId}`;
4516
+ const wantBare = `/${directiveId}`;
4517
+ const wantOp = `${domain}__${directiveId}`;
4518
+ for (const [p, ops] of Object.entries(paths)) {
4519
+ if (p === wantPath || p === wantBare) return true;
4520
+ for (const method of Object.values(ops ?? {})) {
4521
+ if (method?.operationId === wantOp) return true;
4522
+ }
4523
+ }
4524
+ return false;
4525
+ }
4526
+ async function probeOfferLane(cloud, ws, route, fetchImpl = fetch) {
4527
+ try {
4528
+ const r = await fetchImpl(`${cloud}/v2/workspaces/${ws}/openapi.json`, { method: "GET" });
4529
+ if (!r.ok) return { served: false, routesTarget: void 0 };
4530
+ const spec = await r.json().catch(() => void 0);
4531
+ if (spec === void 0 || typeof spec !== "object") return { served: false, routesTarget: void 0 };
4532
+ const served = true;
4533
+ const routesTarget = route === void 0 ? void 0 : openapiRoutesDirective(spec, route.domain, route.directiveId);
4534
+ return { served, routesTarget };
4535
+ } catch {
4536
+ return { served: void 0, routesTarget: void 0 };
4537
+ }
4538
+ }
4539
+ async function probeCloudCapabilities(cloud, ws, route, fetchImpl = fetch) {
4540
+ const [stage, offer] = await Promise.all([
4541
+ probeStageLane(cloud, fetchImpl),
4542
+ probeOfferLane(cloud, ws, route, fetchImpl)
4543
+ ]);
4544
+ return {
4545
+ cloud,
4546
+ stage,
4547
+ offerLane: offer.served,
4548
+ routesTarget: offer.routesTarget,
4549
+ openDeploy: void 0
4550
+ // the open /domains lane is universal in every worker/dev-server; not separately probed
4551
+ };
4552
+ }
4553
+ function negotiateDeployRoute(caps, opts) {
4554
+ if (opts.signed) {
4555
+ if (caps.offerLane === false) {
4556
+ return {
4557
+ fail: `${caps.cloud} does not serve the SIGNED OFFER lane \u2014 POST /v2/workspaces/${opts.ws}/${opts.route.domain}/${opts.route.directiveId} is not routed (the workspace serves no self-describing OpenAPI, so this cloud has no gateway to route a signed offer). This is often a lightweight cloud (e.g. \`githolon cloud dev\`) without the offer gateway.
4558
+ \u2192 deploy the OPEN (unsigned) lane instead (drop --as/--principal) if this workspace is unwarranted, or target a cloud that serves the offer gateway.`
4559
+ };
4560
+ }
4561
+ if (caps.offerLane === true && caps.routesTarget === false) {
4562
+ return {
4563
+ fail: `${caps.cloud} serves the offer gateway for ${opts.ws}, but its OpenAPI does not name the route ${opts.route.domain}/${opts.route.directiveId} \u2014 the ${opts.route.domain} controller is not installed here, so a signed ${opts.route.directiveId} would 404.
4564
+ \u2192 confirm the workspace is born and the controller is deployed, or deploy the OPEN lane (drop --as).`
4565
+ };
4566
+ }
4567
+ if (opts.sealedSizeChars > INLINE_OFFER_CAP_CHARS && caps.stage === false) {
4568
+ return {
4569
+ fail: `this signed package is ~${Math.round(opts.sealedSizeChars / (1024 * 1024))} MiB sealed \u2014 over the ${INLINE_OFFER_CAP_CHARS / (1024 * 1024)} MiB inline offer cap \u2014 and ${caps.cloud} has no /v2/stage content-addressed lane to carry it.
4570
+ \u2192 target a cloud that serves /v2/stage, or shrink the package (githolon compile --compact).`
4571
+ };
4572
+ }
4573
+ }
4574
+ return void 0;
4575
+ }
4576
+ var INLINE_OFFER_CAP_CHARS, ZERO_SHA;
4577
+ var init_capabilities = __esm({
4578
+ "src/capabilities.ts"() {
4579
+ "use strict";
4580
+ INLINE_OFFER_CAP_CHARS = 8 * 1024 * 1024;
4581
+ ZERO_SHA = "0".repeat(64);
4582
+ }
4583
+ });
4584
+
4483
4585
  // src/lifecycle.ts
4484
4586
  var lifecycle_exports = {};
4485
4587
  __export(lifecycle_exports, {
@@ -4791,97 +4893,6 @@ var init_lifecycle = __esm({
4791
4893
  }
4792
4894
  });
4793
4895
 
4794
- // src/capabilities.ts
4795
- var capabilities_exports = {};
4796
- __export(capabilities_exports, {
4797
- INLINE_OFFER_CAP_CHARS: () => INLINE_OFFER_CAP_CHARS,
4798
- negotiateDeployRoute: () => negotiateDeployRoute,
4799
- openapiRoutesDirective: () => openapiRoutesDirective,
4800
- probeCloudCapabilities: () => probeCloudCapabilities,
4801
- probeOfferLane: () => probeOfferLane,
4802
- probeStageLane: () => probeStageLane
4803
- });
4804
- async function probeStageLane(cloud, fetchImpl = fetch) {
4805
- try {
4806
- const r = await fetchImpl(`${cloud}/v2/stage/${ZERO_SHA}`, { method: "GET" });
4807
- const body = await r.json().catch(() => void 0);
4808
- return typeof body?.staged === "boolean";
4809
- } catch {
4810
- return void 0;
4811
- }
4812
- }
4813
- function openapiRoutesDirective(spec, domain, directiveId) {
4814
- const paths = spec?.paths ?? {};
4815
- const wantPath = `/${domain}/${directiveId}`;
4816
- const wantBare = `/${directiveId}`;
4817
- const wantOp = `${domain}__${directiveId}`;
4818
- for (const [p, ops] of Object.entries(paths)) {
4819
- if (p === wantPath || p === wantBare) return true;
4820
- for (const method of Object.values(ops ?? {})) {
4821
- if (method?.operationId === wantOp) return true;
4822
- }
4823
- }
4824
- return false;
4825
- }
4826
- async function probeOfferLane(cloud, ws, route, fetchImpl = fetch) {
4827
- try {
4828
- const r = await fetchImpl(`${cloud}/v2/workspaces/${ws}/openapi.json`, { method: "GET" });
4829
- if (!r.ok) return { served: false, routesTarget: void 0 };
4830
- const spec = await r.json().catch(() => void 0);
4831
- if (spec === void 0 || typeof spec !== "object") return { served: false, routesTarget: void 0 };
4832
- const served = true;
4833
- const routesTarget = route === void 0 ? void 0 : openapiRoutesDirective(spec, route.domain, route.directiveId);
4834
- return { served, routesTarget };
4835
- } catch {
4836
- return { served: void 0, routesTarget: void 0 };
4837
- }
4838
- }
4839
- async function probeCloudCapabilities(cloud, ws, route, fetchImpl = fetch) {
4840
- const [stage, offer] = await Promise.all([
4841
- probeStageLane(cloud, fetchImpl),
4842
- probeOfferLane(cloud, ws, route, fetchImpl)
4843
- ]);
4844
- return {
4845
- cloud,
4846
- stage,
4847
- offerLane: offer.served,
4848
- routesTarget: offer.routesTarget,
4849
- openDeploy: void 0
4850
- // the open /domains lane is universal in every worker/dev-server; not separately probed
4851
- };
4852
- }
4853
- function negotiateDeployRoute(caps, opts) {
4854
- if (opts.signed) {
4855
- if (caps.offerLane === false) {
4856
- return {
4857
- fail: `${caps.cloud} does not serve the SIGNED OFFER lane \u2014 POST /v2/workspaces/${opts.ws}/${opts.route.domain}/${opts.route.directiveId} is not routed (the workspace serves no self-describing OpenAPI, so this cloud has no gateway to route a signed offer). This is often a lightweight cloud (e.g. \`githolon cloud dev\`) without the offer gateway.
4858
- \u2192 deploy the OPEN (unsigned) lane instead (drop --as/--principal) if this workspace is unwarranted, or target a cloud that serves the offer gateway.`
4859
- };
4860
- }
4861
- if (caps.offerLane === true && caps.routesTarget === false) {
4862
- return {
4863
- fail: `${caps.cloud} serves the offer gateway for ${opts.ws}, but its OpenAPI does not name the route ${opts.route.domain}/${opts.route.directiveId} \u2014 the ${opts.route.domain} controller is not installed here, so a signed ${opts.route.directiveId} would 404.
4864
- \u2192 confirm the workspace is born and the controller is deployed, or deploy the OPEN lane (drop --as).`
4865
- };
4866
- }
4867
- if (opts.sealedSizeChars > INLINE_OFFER_CAP_CHARS && caps.stage === false) {
4868
- return {
4869
- fail: `this signed package is ~${Math.round(opts.sealedSizeChars / (1024 * 1024))} MiB sealed \u2014 over the ${INLINE_OFFER_CAP_CHARS / (1024 * 1024)} MiB inline offer cap \u2014 and ${caps.cloud} has no /v2/stage content-addressed lane to carry it.
4870
- \u2192 target a cloud that serves /v2/stage, or shrink the package (githolon compile --compact).`
4871
- };
4872
- }
4873
- }
4874
- return void 0;
4875
- }
4876
- var INLINE_OFFER_CAP_CHARS, ZERO_SHA;
4877
- var init_capabilities = __esm({
4878
- "src/capabilities.ts"() {
4879
- "use strict";
4880
- INLINE_OFFER_CAP_CHARS = 8 * 1024 * 1024;
4881
- ZERO_SHA = "0".repeat(64);
4882
- }
4883
- });
4884
-
4885
4896
  // src/cloud.ts
4886
4897
  var cloud_exports = {};
4887
4898
  __export(cloud_exports, {
@@ -5273,6 +5284,29 @@ async function deploy2(ws, opts) {
5273
5284
  const beforeLaw = await fetch(`${cloud}/v2/workspaces/${ws}/domains`).then((br) => br.json()).then((bd) => bd.ok === true ? bd : {}).catch(() => ({}));
5274
5285
  const before = beforeLaw.domains ?? [];
5275
5286
  const currentLaw2 = beforeLaw.currentLaw ?? {};
5287
+ try {
5288
+ const compat = JSON.parse(readFileSync5(file, "utf8")).nomosCompat;
5289
+ const requires = compat?.requiresKernel ?? [];
5290
+ if (requires.length > 0) {
5291
+ const { probeKernelCapabilities: probeKernelCapabilities2 } = await Promise.resolve().then(() => (init_capabilities(), capabilities_exports));
5292
+ const report = await probeKernelCapabilities2(cloud);
5293
+ const kernel = report?.prod;
5294
+ if (kernel?.features) {
5295
+ const missing = requires.filter((f) => !kernel.features.includes(f));
5296
+ if (missing.length > 0) {
5297
+ const lawH = String(compat?.lawHash ?? "").slice(0, 8);
5298
+ const kernH = String(kernel.hash ?? "").slice(0, 8);
5299
+ err4(
5300
+ `deploy '${ws}' REFUSED \u2014 release coherence: kernel ${kernH} lacks capability '${missing.join("', '")}' that law ${lawH} requires (the cloud runs a STALE kernel: ${kernel.note ?? kernel.version}).` + (missing.includes("compoundKeys") ? `
5301
+ A compound-key query (\`.key(a, b, \u2026)\`) would be refused deep in a law fold ("query declares N index keys; the read router supports exactly one").` : "") + `
5302
+ Point at a cloud whose kernel is coherent with this release, or upgrade the cloud's kernel.`
5303
+ );
5304
+ return 1;
5305
+ }
5306
+ }
5307
+ }
5308
+ } catch {
5309
+ }
5276
5310
  const { readDeployBody: readDeployBody3, domainKeysOf: domainKeysOf2, packageHashOf: packageHashOf2, resolveDeployPlan: resolveDeployPlan2, describeDeployPlan: describeDeployPlan2 } = await Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports));
5277
5311
  let plan;
5278
5312
  let targetHash;
@@ -5645,9 +5679,9 @@ function parseOfflineLegs(proofSource) {
5645
5679
  }
5646
5680
  const domain = dom[1];
5647
5681
  const autoStamped = [];
5648
- const stampLine = proofSource.split("\n").find((l) => l.includes("omitted \u2014 the client auto-stamps ISO now()"));
5649
- if (stampLine !== void 0) {
5650
- for (const m of stampLine.matchAll(/`(\w+)`/g)) autoStamped.push(m[1]);
5682
+ const stampLine2 = proofSource.split("\n").find((l) => l.includes("omitted \u2014 the client auto-stamps ISO now()"));
5683
+ if (stampLine2 !== void 0) {
5684
+ for (const m of stampLine2.matchAll(/`(\w+)`/g)) autoStamped.push(m[1]);
5651
5685
  }
5652
5686
  let queryLeg;
5653
5687
  const q = proofSource.match(/local declared query (\w+):/);
@@ -6553,6 +6587,84 @@ var init_harness = __esm({
6553
6587
  }
6554
6588
  });
6555
6589
 
6590
+ // src/doctor.ts
6591
+ var doctor_exports = {};
6592
+ __export(doctor_exports, {
6593
+ doctor: () => doctor
6594
+ });
6595
+ import { existsSync as existsSync14, readFileSync as readFileSync15 } from "node:fs";
6596
+ function readLocalCompat(cwd, file) {
6597
+ try {
6598
+ const path = discoverDeployJson(cwd, file);
6599
+ if (!existsSync14(path)) return void 0;
6600
+ const body = JSON.parse(readFileSync15(path, "utf8"));
6601
+ return body.nomosCompat;
6602
+ } catch {
6603
+ return void 0;
6604
+ }
6605
+ }
6606
+ function stampLine(label, stamp) {
6607
+ if (!stamp) return ` ${label.padEnd(12)}: (not advertised)`;
6608
+ const h = String(stamp.hash || "").slice(0, 12);
6609
+ const caps = stamp.features?.length ? `[${stamp.features.join(", ")}]` : "[]";
6610
+ const known = stamp.known ? "" : " \u26A0 UNKNOWN to registry";
6611
+ return ` ${label.padEnd(12)}: ${h}\u2026 ${stamp.version ?? ""}${known}
6612
+ caps ${caps}`;
6613
+ }
6614
+ async function doctor(opts = {}) {
6615
+ const cloud = cloudBase(opts.cloud);
6616
+ const compat = readLocalCompat(process.cwd(), opts.file);
6617
+ const report = await probeKernelCapabilities(cloud);
6618
+ const out13 = [];
6619
+ out13.push(`githolon doctor \u2014 release coherence`);
6620
+ out13.push(` cloud : ${cloud}`);
6621
+ out13.push(` compiler : @githolon/dsl ${compat?.generator?.dsl ?? "(no local build \u2014 run `githolon compile`)"}`);
6622
+ out13.push(` runner : cloud expects bundle version ${report?.expectedBundleVersion ?? "(not advertised)"}`);
6623
+ out13.push(stampLine("prod kernel", report?.prod));
6624
+ out13.push(stampLine("test kernel", report?.testmint));
6625
+ const requires = compat?.requiresKernel ?? [];
6626
+ if (!compat) {
6627
+ out13.push(``);
6628
+ out13.push(` \u26A0 no local build found \u2014 cannot check the law\u2194kernel fit. Run \`githolon compile\` first.`);
6629
+ process.stdout.write(out13.join("\n") + "\n");
6630
+ return report ? 0 : 1;
6631
+ }
6632
+ if (!report?.prod) {
6633
+ out13.push(``);
6634
+ out13.push(` \u26A0 cloud advertises no kernel capabilities (older worker?) \u2014 coherence UNVERIFIABLE.`);
6635
+ out13.push(` law ${String(compat.lawHash).slice(0, 8)} requires: [${requires.join(", ") || "none"}]`);
6636
+ process.stdout.write(out13.join("\n") + "\n");
6637
+ return 0;
6638
+ }
6639
+ const feats = report.prod.features ?? [];
6640
+ const missing = requires.filter((f) => !feats.includes(f));
6641
+ out13.push(``);
6642
+ const lawH = String(compat.lawHash || "").slice(0, 8);
6643
+ const kernH = String(report.prod.hash || "").slice(0, 8);
6644
+ if (requires.length === 0) {
6645
+ out13.push(` \u2713 COHERENT \u2014 law ${lawH} requires no special kernel capabilities.`);
6646
+ } else if (missing.length === 0) {
6647
+ out13.push(` \u2713 COHERENT \u2014 kernel ${kernH} supports all capabilities law ${lawH} requires: [${requires.join(", ")}].`);
6648
+ } else {
6649
+ out13.push(` \u2717 SKEW \u2014 kernel ${kernH} lacks capability '${missing.join("', '")}' that law ${lawH} requires.`);
6650
+ out13.push(` The cloud is running a STALE kernel (${report.prod.note ?? report.prod.version ?? "unknown build"}).`);
6651
+ if (missing.includes("compoundKeys")) {
6652
+ out13.push(` A compound-key query (\`.key(a, b, \u2026)\`) will be refused deep in a law fold with`);
6653
+ out13.push(` "query declares N index keys; the read router supports exactly one".`);
6654
+ }
6655
+ out13.push(` Point at a cloud whose kernel is coherent with this release, or upgrade the cloud's kernel.`);
6656
+ }
6657
+ process.stdout.write(out13.join("\n") + "\n");
6658
+ return missing.length > 0 ? 1 : 0;
6659
+ }
6660
+ var init_doctor = __esm({
6661
+ "src/doctor.ts"() {
6662
+ "use strict";
6663
+ init_cloud();
6664
+ init_capabilities();
6665
+ }
6666
+ });
6667
+
6556
6668
  // src/scene_projector.ts
6557
6669
  import { createHash as createHash4 } from "node:crypto";
6558
6670
  function fnv1a32(s) {
@@ -7609,7 +7721,7 @@ var scene_exports = {};
7609
7721
  __export(scene_exports, {
7610
7722
  scene: () => scene
7611
7723
  });
7612
- import { existsSync as existsSync14, mkdirSync as mkdirSync6, writeFileSync as writeFileSync9 } from "node:fs";
7724
+ import { existsSync as existsSync15, mkdirSync as mkdirSync6, writeFileSync as writeFileSync9 } from "node:fs";
7613
7725
  import { join as join16, resolve as resolve4 } from "node:path";
7614
7726
  import { createHash as createHash5 } from "node:crypto";
7615
7727
  import git3 from "isomorphic-git";
@@ -7631,8 +7743,8 @@ async function projectOnce(cloud, target, isDir, name) {
7631
7743
  const { eng } = await holonEngine(cloud, void 0, "githolon-scene");
7632
7744
  const SOURCE3 = "source";
7633
7745
  if (isDir) {
7634
- const gitDir = existsSync14(join16(target, ".git")) ? join16(target, ".git") : target;
7635
- if (!existsSync14(join16(gitDir, "HEAD"))) throw new Error(`${target} is not a git repo (no HEAD)`);
7746
+ const gitDir = existsSync15(join16(target, ".git")) ? join16(target, ".git") : target;
7747
+ if (!existsSync15(join16(gitDir, "HEAD"))) throw new Error(`${target} is not a git repo (no HEAD)`);
7636
7748
  await mountFresh(eng, SOURCE3);
7637
7749
  eng.preopen.dir.contents.get("ws").contents.get(SOURCE3).contents.set("nomos.git", readTreeFromDisk(gitDir));
7638
7750
  } else {
@@ -7691,7 +7803,7 @@ async function projectOnce(cloud, target, isDir, name) {
7691
7803
  }
7692
7804
  async function scene(target, opts) {
7693
7805
  const cloud = cloudBase(opts.cloud);
7694
- const isDir = existsSync14(target) || target.includes("/") || target.startsWith(".");
7806
+ const isDir = existsSync15(target) || target.includes("/") || target.startsWith(".");
7695
7807
  const name = isDir ? (resolve4(target).split("/").filter(Boolean).pop() ?? "scene").replace(/\.git$/, "") : target;
7696
7808
  out11(`scene \u2014 ${name} (${isDir ? resolve4(target) : `${cloud} :: ${target}`})`);
7697
7809
  let first, second;
@@ -7802,7 +7914,7 @@ var init_dlq = __esm({
7802
7914
  });
7803
7915
 
7804
7916
  // src/cli.ts
7805
- import { existsSync as existsSync15, readdirSync as readdirSync6, readFileSync as readFileSync15 } from "node:fs";
7917
+ import { existsSync as existsSync16, readdirSync as readdirSync6, readFileSync as readFileSync16 } from "node:fs";
7806
7918
  import { spawnSync as spawnSync6 } from "node:child_process";
7807
7919
  import { createRequire as createRequire3 } from "node:module";
7808
7920
  import { dirname as dirname9, join as join17, resolve as resolve5 } from "node:path";
@@ -9436,10 +9548,10 @@ async function runProof(args) {
9436
9548
  let proofPath;
9437
9549
  if (explicit !== void 0) {
9438
9550
  proofPath = resolve5(process.cwd(), explicit);
9439
- if (!existsSync15(proofPath)) return refuse(`proof not found: ${proofPath} \u2014 run \`githolon compile\` to (re)generate it`);
9551
+ if (!existsSync16(proofPath)) return refuse(`proof not found: ${proofPath} \u2014 run \`githolon compile\` to (re)generate it`);
9440
9552
  } else {
9441
9553
  const buildDir = join17(process.cwd(), "build");
9442
- if (!existsSync15(buildDir)) {
9554
+ if (!existsSync16(buildDir)) {
9443
9555
  return refuse("no build/ directory here \u2014 run `githolon compile` first (every compile generates build/<name>.proof.mts from your law)");
9444
9556
  }
9445
9557
  const proofs = readdirSync6(buildDir).filter((f) => f.endsWith(".proof.mts"));
@@ -9482,7 +9594,7 @@ async function runProof(args) {
9482
9594
  if (tsxDir === void 0) {
9483
9595
  return refuse("tsx not found \u2014 add @githolon/dsl to your project's dependencies (tsx rides along) and npm install");
9484
9596
  }
9485
- const tsxPkg = JSON.parse(readFileSync15(join17(tsxDir, "package.json"), "utf8"));
9597
+ const tsxPkg = JSON.parse(readFileSync16(join17(tsxDir, "package.json"), "utf8"));
9486
9598
  const tsxBinRel = typeof tsxPkg.bin === "string" ? tsxPkg.bin : tsxPkg.bin?.["tsx"];
9487
9599
  const tsxCli = join17(tsxDir, tsxBinRel ?? "dist/cli.mjs");
9488
9600
  console.log("githolon proof --live \u2014 the full cloud loop (throwaway workspace, retired on exit" + (keep ? "; --keep: kept" : "") + ")");
@@ -9687,6 +9799,17 @@ ${commandHelp("dev")}`);
9687
9799
  ...once ? { once: true } : {}
9688
9800
  });
9689
9801
  }
9802
+ if (argv[0] === "doctor") {
9803
+ const rest = argv.slice(1);
9804
+ let cloudFlag;
9805
+ let fileFlag;
9806
+ for (let i = 0; i < rest.length; i++) {
9807
+ if (rest[i] === "--cloud") cloudFlag = rest[++i];
9808
+ else if (rest[i] === "--file") fileFlag = rest[++i];
9809
+ }
9810
+ const { doctor: doctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
9811
+ return doctor2({ ...cloudFlag !== void 0 ? { cloud: cloudFlag } : {}, ...fileFlag !== void 0 ? { file: fileFlag } : {} });
9812
+ }
9690
9813
  if (argv[0] === "cloud") {
9691
9814
  const sub2 = argv[1];
9692
9815
  if (sub2 !== "dev") {
@@ -9707,7 +9830,7 @@ usage: githolon cloud dev [--port <n>] [--data <dir>] [--ephemeral]
9707
9830
  resolve5(here, "../../../cloud/dev-server/server.mjs"),
9708
9831
  resolve5(process.cwd(), "cloud/dev-server/server.mjs")
9709
9832
  ];
9710
- const serverPath = candidates.find((p) => existsSync15(p));
9833
+ const serverPath = candidates.find((p) => existsSync16(p));
9711
9834
  if (!serverPath) {
9712
9835
  process.stderr.write("error: dev-server not found \u2014 reinstall `githolon` or run `npm run build` in the cli package\n");
9713
9836
  return 1;
@@ -1411,7 +1411,7 @@ async function ledgerStats(eng, ws, ledger = null) {
1411
1411
  if (mounted) mounted.ledgerStats = out;
1412
1412
  return out;
1413
1413
  }
1414
- var enc3, dec3, BRANCH, RESULT_PACK_INLINE_MAX, repoArgOf, gitdirOf, unpack, log, repoArgOfGitdir, bytesFromB64, kgit, installPayload, routeTagHexOfHomeKey, SUMMARY_SEP, summaryKey, splitSummaryKey, NOMOS_SHARD_ROWS_READ, groupOf, tallyOf, isExtremumRead, extremumMonotone, pushVia, fullRef, applyPackInto, custodyLsRefs, qById, query, authorKeygen, attestedRead, count, sum, pendingConflicts, spatialBboxParams, spatialWithin, extremum, nomosActive, refusedRefName;
1414
+ var enc3, dec3, BRANCH, RESULT_PACK_INLINE_MAX, repoArgOf, gitdirOf, unpack, log, repoArgOfGitdir, bytesFromB64, kgit, installPayload, routeTagHexOfHomeKey, SUMMARY_SEP, summaryKey, splitSummaryKey, NOMOS_SHARD_ROWS_READ, groupOf, tallyOf, isExtremumRead, extremumMonotone, pushVia, fullRef, applyPackInto, custodyLsRefs, qById, query, authorKeygen, attestedRead, certSign, count, sum, pendingConflicts, spatialBboxParams, spatialWithin, extremum, nomosActive, refusedRefName;
1415
1415
  var init_engine = __esm({
1416
1416
  "../adapter/engine.mjs"() {
1417
1417
  init_git_fs();
@@ -1494,6 +1494,7 @@ var init_engine = __esm({
1494
1494
  branch: BRANCH,
1495
1495
  queryBytes: b64Json({ op: "attestedRead", queryId, paramsJson: paramsJson || "{}", attestorSecret, nowMs: nowMs ?? Date.now(), sourceWorkspace: ws, ...includeRosterProof ? { includeRosterProof: true } : {} })
1496
1496
  }, eng.STDERR));
1497
+ certSign = (eng, { child, founder, verified, parentKey, parentId, epoch, secret }) => JSON.parse(call(eng.ex, "query", { queryBytes: b64Json({ op: "certSign", child, founder, verified, parentKey, parentId, epoch, secret }) }, eng.STDERR)).cert;
1497
1498
  count = (eng, ws, countId, groupKey, principal = "") => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "count", countId, groupKey }), principal, branch: BRANCH }, eng.STDERR));
1498
1499
  sum = (eng, ws, sumId, groupKey, principal = "") => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "sum", sumId, groupKey }), principal, branch: BRANCH }, eng.STDERR));
1499
1500
  pendingConflicts = (eng, ws, principal = "") => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "pendingConflicts" }), principal, branch: BRANCH }, eng.STDERR));
@@ -1512,6 +1513,7 @@ var init_engine = __esm({
1512
1513
  var bootstrap_exports = {};
1513
1514
  __export(bootstrap_exports, {
1514
1515
  bootstrapPlatform: () => bootstrapPlatform,
1516
+ homeDomainOf: () => homeDomainOf,
1515
1517
  isTestMint: () => isTestMint
1516
1518
  });
1517
1519
  import { readFileSync } from "node:fs";
@@ -1523,6 +1525,17 @@ function isTestMint(eng) {
1523
1525
  return !/poisoned|unknown|adapter-visible API/.test(String(e && e.message || e));
1524
1526
  }
1525
1527
  }
1528
+ function homeDomainOf(opts) {
1529
+ if (!opts || !opts.deploy) return null;
1530
+ try {
1531
+ const body = JSON.parse(readFileSync(opts.deploy, "utf8"));
1532
+ const domains = body.summary && body.summary.domains || [];
1533
+ const hit = domains.find((d) => Array.isArray(d.directives) && d.directives.includes("seedBirthAttestation"));
1534
+ return hit ? hit.domain : null;
1535
+ } catch {
1536
+ return null;
1537
+ }
1538
+ }
1526
1539
  async function bootstrapPlatform(dev, opts) {
1527
1540
  const { eng, custody, ledgerFor, serialize, origin } = dev;
1528
1541
  const platform = opts.platform || "co2-platform-local";
@@ -1535,20 +1548,23 @@ async function bootstrapPlatform(dev, opts) {
1535
1548
  if (opts.deploy) {
1536
1549
  const body = JSON.parse(readFileSync(opts.deploy, "utf8"));
1537
1550
  const usda = body.packageUsda || body.usda;
1538
- if (!usda) {
1539
- log2(`WARN --deploy ${opts.deploy} has no packageUsda; skipping law deploy`);
1540
- } else {
1541
- await serialize(async () => {
1542
- const ledger = ledgerFor(platform);
1543
- const domainHash = await sha256hex(usda);
1544
- try {
1545
- const out = await deploy(eng, platform, ledger, usda, domainHash, body.dispositions || null, { actor: "user:dev-admin" });
1546
- log2(`platform law deployed: ${domainHash.slice(0, 12)} (head ${String(out.head).slice(0, 12)})`);
1547
- } catch (e) {
1548
- log2(`WARN platform law deploy refused: ${String(e && e.message || e).slice(0, 200)}`);
1551
+ if (!usda) throw new Error(`--deploy ${opts.deploy} has no packageUsda (nothing to install)`);
1552
+ let deployErr = null;
1553
+ await serialize(async () => {
1554
+ const ledger = ledgerFor(platform);
1555
+ const domainHash = await sha256hex(usda);
1556
+ try {
1557
+ const out = await deploy(eng, platform, ledger, usda, domainHash, body.dispositions || null, { actor: "user:dev-admin" });
1558
+ if (out && out.ok === false) {
1559
+ deployErr = String(out.error || "the law refused the platform package");
1560
+ return;
1549
1561
  }
1550
- });
1551
- }
1562
+ log2(`platform law deployed: ${domainHash.slice(0, 12)} (head ${String(out.head).slice(0, 12)})`);
1563
+ } catch (e) {
1564
+ deployErr = String(e && e.message || e);
1565
+ }
1566
+ });
1567
+ if (deployErr) throw new Error(`platform law deploy REFUSED: ${deployErr.slice(0, 400)}`);
1552
1568
  } else {
1553
1569
  log2("no --deploy given; platform runs framework law only (deploy your platform package to enable births)");
1554
1570
  }
@@ -1559,18 +1575,71 @@ async function bootstrapPlatform(dev, opts) {
1559
1575
  log2(" \u2192 re-run: NOMOS_LOCAL_WASM=<that.wasm> githolon cloud dev --platform " + platform + " --bootstrap");
1560
1576
  log2(" Without it the platform cannot roots-at-K_root-attest home births (LEG rejected by design).");
1561
1577
  } else {
1578
+ const OPERATOR = opts.operator || "user:dev-operator";
1562
1579
  const kRoot = authorKeygen(eng);
1563
1580
  setTestKRoot(eng, kRoot.public);
1564
1581
  log2(`K_root ceremony: pinned LOCAL test K_root ${kRoot.public.slice(0, 16)}\u2026`);
1565
- log2(" (deliver signed DelegationCerts via the platform's grant directives; see home_delegated_estate.smoke.mjs)");
1566
- dev.testKRoot = kRoot;
1582
+ const sP = authorKeygen(eng);
1583
+ const platformCert = certSign(eng, {
1584
+ child: platform,
1585
+ founder: OPERATOR,
1586
+ verified: true,
1587
+ parentKey: sP.public,
1588
+ // P_P — the onboarding signer the platform vouches for
1589
+ parentId: "root",
1590
+ epoch: 0,
1591
+ secret: kRoot.secret
1592
+ // signed by S_root
1593
+ });
1594
+ const certChain = JSON.stringify({ links: [platformCert] });
1595
+ const homeDomain = homeDomainOf(opts) || "home";
1596
+ const lawKeys = await serialize(async () => {
1597
+ await mountWorkspace(eng, platform, ledgerFor(platform));
1598
+ return currentLaw(eng, platform);
1599
+ });
1600
+ const lawHash = (lawKeys || {})[homeDomain] || "";
1601
+ if (!lawHash) {
1602
+ log2(`ATTESTED BIRTHS: the deployed platform law declares no '${homeDomain}' framework domain`);
1603
+ log2(" (seedBirthAttestation/birthHome absent) \u2014 attested home births are not available on this platform.");
1604
+ dev.testKRoot = kRoot;
1605
+ dev.onboardingSigner = sP;
1606
+ } else {
1607
+ const seed = await serialize(async () => authorOn(
1608
+ eng,
1609
+ platform,
1610
+ ledgerFor(platform),
1611
+ homeDomain,
1612
+ "seedBirthAttestation",
1613
+ {
1614
+ child: platform,
1615
+ founder: OPERATOR,
1616
+ verified: "true",
1617
+ parentId: "root",
1618
+ parentKey: sP.public,
1619
+ epoch: 0,
1620
+ certChain
1621
+ },
1622
+ lawHash,
1623
+ { actor: OPERATOR, principalVerified: true }
1624
+ ));
1625
+ if (!seed.ok) throw new Error(`platform BirthAttestation seed REFUSED: ${String(seed.error).slice(0, 400)}`);
1626
+ log2(`root-attested platform: seeded BirthAttestation certChain (platform_cert signed by K_root, P_P=${sP.public.slice(0, 12)}\u2026)`);
1627
+ log2(" a birthHome under this platform now assembles [home_cert, u_delegation(S_P), platform_cert(K_root)] \u2014 no unwarranted-cert.");
1628
+ log2(" ENROLL each user at your app's enroll gate: home/enrollSigner(device key) + home/enrollDelegation(S_P-signed cert).");
1629
+ dev.testKRoot = kRoot;
1630
+ dev.onboardingSigner = sP;
1631
+ dev.platformCert = platformCert;
1632
+ dev.platformBirthAttestation = { child: platform, parentKey: sP.public, certChain };
1633
+ dev.homeDomain = homeDomain;
1634
+ }
1567
1635
  }
1568
1636
  if (opts.authProvider || opts.firebaseProject) {
1569
1637
  log2(`AuthProvider: ${opts.authProvider || `firebase:${opts.firebaseProject}`} \u2014 register it via the platform's setAuthProvider governance offer;`);
1570
1638
  log2(" the kernel verifies x-nomos-auth JWTs against the law-declared key (host stays blind).");
1571
1639
  }
1572
- log2("bootstrap complete (verifiable local legs done; attested-birth + Firebase legs gated as noted above).");
1573
- return { platform, testMint };
1640
+ const attested = testMint && !!dev.platformBirthAttestation;
1641
+ log2(attested ? "bootstrap complete \u2014 platform law deployed + ROOT-ATTESTED (BirthAttestation certChain seeded; birthHome assembles a valid travelling cert)." : "bootstrap complete (platform law deployed; attested-birth legs gated as noted above \u2014 need the test-mint wasm).");
1642
+ return { platform, testMint, attested };
1574
1643
  }
1575
1644
  var log2;
1576
1645
  var init_bootstrap = __esm({
@@ -1839,6 +1908,31 @@ function firstExisting(...paths) {
1839
1908
  }
1840
1909
  var TEST_MINT_MARKER = Buffer.from("set_test_k_root");
1841
1910
  var isTestMintBytes = (bytes) => bytes.includes(TEST_MINT_MARKER);
1911
+ var KERNEL_CAPABILITY_REGISTRY = [
1912
+ {
1913
+ prefix: "846c6bf8",
1914
+ version: "kernel-846c6bf8",
1915
+ note: "production (accepts compound-key law)",
1916
+ features: ["compoundKeys", "capturedReads", "invariantGate", "surfacedMerge", "fieldProvenance", "nullSemantics", "attestedReads"]
1917
+ },
1918
+ {
1919
+ prefix: "f6e44b3a",
1920
+ version: "kernel-f6e44b3a",
1921
+ note: "test-mint (DEV) \u2014 current, feature-equivalent to production",
1922
+ features: ["compoundKeys", "capturedReads", "invariantGate", "surfacedMerge", "fieldProvenance", "nullSemantics", "attestedReads"]
1923
+ },
1924
+ {
1925
+ prefix: "f81cb952",
1926
+ version: "kernel-f81cb952",
1927
+ note: "test-mint (DEV ONLY \u2014 STALE: predates compound-key read router; superseded by f6e44b3a)",
1928
+ features: ["capturedReads", "invariantGate", "surfacedMerge", "fieldProvenance", "nullSemantics", "attestedReads"]
1929
+ }
1930
+ ];
1931
+ function capabilitiesForKernelHash(hash) {
1932
+ const h = String(hash || "").toLowerCase();
1933
+ const hit = KERNEL_CAPABILITY_REGISTRY.find((e) => h.startsWith(e.prefix) || e.prefix.startsWith(h.slice(0, 8)));
1934
+ return hit ? { hash: h, version: hit.version, note: hit.note, features: hit.features, known: true } : { hash: h, version: null, note: "UNKNOWN kernel \u2014 not in the capability registry", features: [], known: false };
1935
+ }
1842
1936
  var DEFAULT_KERNEL_SOURCE = "https://nomos.captainapp.co.uk";
1843
1937
  async function resolveTestMintWasm(kernelSource) {
1844
1938
  const runtimeCache = path.join(configDir(), "runtime");
@@ -2049,6 +2143,18 @@ async function startDevCloud(opts = {}) {
2049
2143
  res.writeHead(200, { ...CORS, "content-type": "application/wasm", "cache-control": "no-store", "x-nomos-kernel": kernelKind, "x-nomos-kernel-sha": kernelSha });
2050
2144
  return res.end(Buffer.from(wasmBytes));
2051
2145
  }
2146
+ if (url.pathname === "/v1/runtime/capabilities") {
2147
+ const stamp = capabilitiesForKernelHash(kernelSha);
2148
+ return sendJson(res, {
2149
+ ok: true,
2150
+ prod: stamp,
2151
+ // the dev cloud serves ONE kernel; it is what holon.wasm returns
2152
+ testmint: kernelKind === "test-mint" ? stamp : null,
2153
+ kernelKind,
2154
+ expectedBundleVersion: 1,
2155
+ vocabulary: KERNEL_CAPABILITY_REGISTRY.flatMap((e) => e.features).filter((f, i, a) => a.indexOf(f) === i).sort()
2156
+ });
2157
+ }
2052
2158
  if (url.pathname === "/v1/runtime/packages") {
2053
2159
  return sendJson(res, { bootstrap: bootstrapPkg, nomos: nomosPkg });
2054
2160
  }
@@ -2469,10 +2575,30 @@ if (import.meta.url === `file://${process.argv[1]}`) {
2469
2575
  console.log(`[dev-cloud] Nomos dev cloud listening on ${dev.origin}`);
2470
2576
  console.log(`[dev-cloud] data dir : ${dev.dataDir}`);
2471
2577
  console.log(`[dev-cloud] kernel : ${dev.kernelSha.slice(0, 16)}\u2026`);
2578
+ {
2579
+ const stamp = capabilitiesForKernelHash(dev.kernelSha);
2580
+ if (stamp.known) {
2581
+ console.log(`[dev-cloud] caps : ${stamp.version} \u2014 [${stamp.features.join(", ")}]`);
2582
+ if (/STALE/.test(stamp.note || "")) {
2583
+ console.log(`[dev-cloud] \u26A0 STALE KERNEL: ${stamp.note}. Law using compound-key queries will be REFUSED here.`);
2584
+ console.log(`[dev-cloud] Update the dev kernel (holon-testmint.wasm) to a build coherent with your compiler/release.`);
2585
+ }
2586
+ } else {
2587
+ console.log(`[dev-cloud] \u26A0 caps : kernel ${dev.kernelSha.slice(0, 8)} is UNKNOWN to the capability registry \u2014 coherence cannot be verified.`);
2588
+ }
2589
+ console.log(`[dev-cloud] coherence: GET ${dev.origin}/v1/runtime/capabilities`);
2590
+ }
2472
2591
  console.log(`[dev-cloud] point your Flutter app's cloud: base URL at ${dev.origin}`);
2473
2592
  if (opts.bootstrap || opts.platform) {
2474
2593
  const { bootstrapPlatform: bootstrapPlatform2 } = await Promise.resolve().then(() => (init_bootstrap(), bootstrap_exports));
2475
- await bootstrapPlatform2(dev, opts).catch((e) => console.error("[dev-cloud] bootstrap failed:", e));
2594
+ try {
2595
+ await bootstrapPlatform2(dev, opts);
2596
+ } catch (e) {
2597
+ console.error("[dev-cloud] bootstrap FAILED:", String(e && e.message || e));
2598
+ await dev.close().catch(() => {
2599
+ });
2600
+ process.exit(1);
2601
+ }
2476
2602
  }
2477
2603
  process.on("SIGINT", async () => {
2478
2604
  await dev.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "githolon",
3
- "version": "0.92.0",
3
+ "version": "0.93.0",
4
4
  "type": "module",
5
5
  "description": "githolon — the Nomos developer CLI: Rails-style generators for @githolon/dsl domains + the package compiler. Kernel-independent.",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -29,8 +29,8 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@bjorn3/browser_wasi_shim": "0.4.2",
32
- "@githolon/client": "^0.92.0",
33
- "@githolon/dsl": "^0.92.0",
32
+ "@githolon/client": "^0.93.0",
33
+ "@githolon/dsl": "^0.93.0",
34
34
  "isomorphic-git": "^1.38.4"
35
35
  },
36
36
  "devDependencies": {