githolon 0.45.0 → 0.47.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.
Files changed (2) hide show
  1. package/dist/cli.mjs +175 -110
  2. package/package.json +3 -3
package/dist/cli.mjs CHANGED
@@ -351,8 +351,10 @@ var init_governance = __esm({
351
351
  // src/lifecycle.ts
352
352
  var lifecycle_exports = {};
353
353
  __export(lifecycle_exports, {
354
+ DOMAIN_LIFECYCLE_DX: () => DOMAIN_LIFECYCLE_DX,
354
355
  describeDeployPlan: () => describeDeployPlan,
355
356
  domainKeysOf: () => domainKeysOf,
357
+ domainLifecycleState: () => domainLifecycleState,
356
358
  domainsRetire: () => domainsRetire,
357
359
  domainsStatus: () => domainsStatus,
358
360
  fetchInstalledLaw: () => fetchInstalledLaw,
@@ -440,7 +442,7 @@ function describeDeployPlan(plan, targetHash) {
440
442
  case "keep-beside":
441
443
  return ` ${d.key}: ${t} kept beside ${short(d.currentHash)}`;
442
444
  case "idempotent":
443
- return ` ${d.key}: unchanged \u2014 ${t} is already the current served law`;
445
+ return ` ${d.key}: unchanged \u2014 ${t} is already active`;
444
446
  }
445
447
  });
446
448
  }
@@ -450,7 +452,7 @@ function resolveInstallHash(arg, currentLaw) {
450
452
  if (hash !== void 0) return { hash };
451
453
  const keys = Object.keys(currentLaw);
452
454
  return {
453
- error: `'${arg}' is neither a 64-hex install hash nor a served domain key` + (keys.length > 0 ? ` (served keys: ${keys.join(", ")})` : " (this workspace serves no law)")
455
+ error: `'${arg}' is neither a 64-hex install hash nor an active domain key` + (keys.length > 0 ? ` (active keys: ${keys.join(", ")})` : " (this workspace has no active law)")
454
456
  };
455
457
  }
456
458
  function keysServedBy(hash, currentLaw) {
@@ -550,14 +552,37 @@ async function domainsRetire(ws, target, opts) {
550
552
  const afterLaw = after.currentLaw ?? {};
551
553
  for (const k of retiredKeys) {
552
554
  const now = afterLaw[k];
553
- out2(` key '${k}' now serves: ${now !== void 0 ? short(now) : "NO served law"}`);
555
+ out2(` key '${k}' now serves: ${now !== void 0 ? short(now) : "NO active law"}`);
554
556
  }
555
557
  if (stranded.length > 0 && retiredKeys.some((k) => afterLaw[k] === void 0)) {
556
- err2(`\u26A0 retiring left these keys with NO served law: ${stranded.join(", ")} \u2014 nothing dispatches/reads them now`);
558
+ err2(`\u26A0 retiring left these keys with NO active law: ${stranded.join(", ")} \u2014 nothing dispatches/reads them now`);
557
559
  err2(` (deploy law covering them, or this is intentional \u2014 the kernel allows an unserved key)`);
558
560
  }
559
561
  return 0;
560
562
  }
563
+ function domainLifecycleState(row, currentHashes) {
564
+ if (row.lifecycle === "active" || row.lifecycle === "superseded" || row.lifecycle === "retired") return row.lifecycle;
565
+ if (row.lifecycle === "current") return "active";
566
+ const hash = row.domainHash;
567
+ switch (row.phase) {
568
+ case "Active":
569
+ return row.current === true || hash !== void 0 && currentHashes.has(hash) ? "active" : "superseded";
570
+ case "Superseded":
571
+ return "superseded";
572
+ case "Retired":
573
+ return "retired";
574
+ case "Pending":
575
+ return "pending";
576
+ case "Retiring":
577
+ return "retiring";
578
+ case "Disabled":
579
+ return "disabled";
580
+ case "Failed":
581
+ return "failed";
582
+ default:
583
+ return "unknown";
584
+ }
585
+ }
561
586
  async function domainsStatus(ws, opts) {
562
587
  const cloud = cloudBase(opts.cloud);
563
588
  const law = await fetchInstalledLaw(cloud, ws);
@@ -567,43 +592,49 @@ async function domainsStatus(ws, opts) {
567
592
  }
568
593
  const currentLaw = law.currentLaw ?? {};
569
594
  const rows = law.domains ?? [];
570
- const phaseOf = (hash) => rows.find((r) => r.domainHash === hash)?.phase ?? "(unknown)";
595
+ const servedHashes = new Set(Object.values(currentLaw));
596
+ const lifecycleOf = (row) => domainLifecycleState(row, servedHashes);
597
+ const lifecycleOfHash = (hash) => {
598
+ const row = rows.find((r) => r.domainHash === hash);
599
+ return row !== void 0 ? lifecycleOf(row) : "unknown";
600
+ };
571
601
  const keys = Object.keys(currentLaw).sort();
572
602
  out2(`installed law on ${ws} (${cloud})`);
573
603
  if (keys.length === 0) {
574
- out2(` (no domain key resolves a served law \u2014 nothing is dispatched/read here)`);
604
+ out2(` (no domain key has active law \u2014 nothing is dispatched/read here)`);
575
605
  } else {
576
- out2(`current interface \u2014 the served law per domain key (the frontier):`);
606
+ out2(`active law per domain key:`);
577
607
  for (const key of keys) {
578
608
  const hash = currentLaw[key];
579
- out2(` ${key} \u2192 ${short(hash)} [${phaseOf(hash)}]`);
609
+ out2(` ${key} \u2192 ${short(hash)} [${lifecycleOfHash(hash)}]`);
580
610
  }
581
611
  }
582
- const servedHashes = new Set(Object.values(currentLaw));
583
612
  const behind = rows.filter((r) => typeof r.domainHash === "string" && !servedHashes.has(r.domainHash));
584
613
  if (behind.length > 0) {
585
- out2(`superseded / retired lineage (replayable, NEVER served):`);
614
+ out2(`superseded/retired lineage (replayable, not served):`);
586
615
  for (const r of behind) {
587
- out2(` ${short(r.domainHash)} [${r.phase ?? "(unknown)"}]${r.generation !== void 0 ? ` gen ${r.generation}` : ""}`);
616
+ out2(` ${short(r.domainHash)} [${lifecycleOf(r)}]${r.generation !== void 0 ? ` gen ${r.generation}` : ""}`);
588
617
  }
589
618
  } else if (rows.length > 0) {
590
- out2(`no superseded/retired installs behind the frontier \u2014 every install is current`);
619
+ out2(`no superseded or retired installs \u2014 every install is active`);
591
620
  }
592
621
  if (opts.explain === true) {
593
622
  out2(``);
594
- out2(`why this is the current interface (the frontier resolution):`);
595
- out2(` the holon resolves, per domain key, the NEWEST '${SERVED_PHASE}' install that is not superseded/`);
596
- out2(` retired (refresh_key_map / rpc_current_law). Every live surface \u2014 the interface identity, reads,`);
597
- out2(` and dispatch \u2014 reads from THIS map, so a superseded/retired install can never pollute it.`);
623
+ out2(`domain install lifecycle:`);
624
+ for (const line of DOMAIN_LIFECYCLE_DX) out2(` - ${line}`);
625
+ out2(``);
626
+ out2(`why this is the active interface:`);
627
+ out2(` the holon resolves active law per domain key and every live surface \u2014 interface identity, reads,`);
628
+ out2(` and dispatch \u2014 reads from that map, so superseded/retired installs cannot pollute it.`);
598
629
  for (const key of keys) {
599
630
  const hash = currentLaw[key];
600
- out2(` \u2022 '${key}' serves ${short(hash)} \u2014 it is the newest ${SERVED_PHASE} install for this key.`);
631
+ out2(` \u2022 '${key}' serves ${short(hash)} \u2014 state: active.`);
601
632
  }
602
633
  if (behind.length > 0) {
603
- const excluded = behind.filter((r) => EXCLUDED_PHASES.has(r.phase ?? "") || !servedHashes.has(r.domainHash)).map((r) => `${short(r.domainHash)} [${r.phase ?? "?"}]`);
634
+ const excluded = behind.filter((r) => EXCLUDED_PHASES.has(r.phase ?? "") || !servedHashes.has(r.domainHash)).map((r) => `${short(r.domainHash)} [${lifecycleOf(r)}]`);
604
635
  out2(` \u2022 excluded from the interface (replayable, not served): ${excluded.join(", ")}`);
605
636
  }
606
- out2(` interface identity per key = the current served hash above; older installs are lineage, not surface.`);
637
+ out2(` interface identity per key = the active hash above; older installs are lineage, not surface.`);
607
638
  }
608
639
  return 0;
609
640
  }
@@ -611,7 +642,7 @@ function signingAvailable(opts) {
611
642
  const principal = opts.principal ?? activePrincipal();
612
643
  return opts.principal !== void 0 || principal !== void 0 && getDeviceKey(principal) !== void 0;
613
644
  }
614
- var out2, err2, short, SERVED_PHASE, EXCLUDED_PHASES;
645
+ var out2, err2, short, EXCLUDED_PHASES, DOMAIN_LIFECYCLE_DX;
615
646
  var init_lifecycle = __esm({
616
647
  "src/lifecycle.ts"() {
617
648
  "use strict";
@@ -619,8 +650,12 @@ var init_lifecycle = __esm({
619
650
  out2 = (s) => void process.stdout.write(s + "\n");
620
651
  err2 = (s) => void process.stderr.write("error: " + s + "\n");
621
652
  short = (h) => h.length > 16 ? h.slice(0, 16) + "\u2026" : h;
622
- SERVED_PHASE = "Active";
623
653
  EXCLUDED_PHASES = /* @__PURE__ */ new Set(["Superseded", "Retired", "Disabled", "Failed", "Pending", "Retiring"]);
654
+ DOMAIN_LIFECYCLE_DX = [
655
+ "active: served now.",
656
+ "superseded: replaced by a newer install for the same domain key; replayable, not served.",
657
+ "retired: explicitly withdrawn; replayable, not served."
658
+ ];
624
659
  }
625
660
  });
626
661
 
@@ -1088,7 +1123,7 @@ async function deploy(ws, opts) {
1088
1123
  return 1;
1089
1124
  }
1090
1125
  const phase = d.installation?.[0]?.data?.["status.phase"];
1091
- phaseSuffix = typeof phase === "string" ? ` (${phase})` : "";
1126
+ phaseSuffix = phase === "Active" ? " (active)" : typeof phase === "string" ? ` (${phase.toLowerCase()})` : "";
1092
1127
  deployedHash = typeof d.domainHash === "string" ? d.domainHash : void 0;
1093
1128
  }
1094
1129
  const after = await fetch(`${cloud}/v2/workspaces/${ws}/domains`).then((ar) => ar.json()).then((ad) => ad.ok === true ? ad : {}).catch(() => ({}));
@@ -1099,31 +1134,31 @@ async function deploy(ws, opts) {
1099
1134
  if (plan === void 0 || targetHash === void 0 || plan.decisions.length === 0) return;
1100
1135
  for (const line of describeDeployPlan2(plan, targetHash)) out3(line);
1101
1136
  if (plan.replaceCandidates.length > 1) {
1102
- out3(` \u26A0 the package's keys were served by ${plan.replaceCandidates.length} different installs \u2014 installDomain supersedes one (${plan.replaces.slice(0, 16)}\u2026); the newest-Active frontier still serves this deploy for every key.`);
1137
+ out3(` \u26A0 the package's keys were active on ${plan.replaceCandidates.length} different installs \u2014 installDomain supersedes one (${plan.replaces.slice(0, 16)}\u2026); this deploy is active for every key.`);
1103
1138
  }
1104
1139
  if (!signed && plan.replaces !== void 0 && !opts.keepBeside) {
1105
- out3(` note: the OPEN (unsigned) lane installs BESIDE \u2014 the new law is current (newest Active) but the prior install is not marked Superseded. Sign with --as <uid> to record the supersession.`);
1140
+ out3(` note: the OPEN (unsigned) lane installs beside \u2014 the new law is active, and the prior install is effectively superseded but not marked Superseded. Sign with --as <uid> to record the supersession.`);
1106
1141
  }
1107
1142
  };
1108
1143
  if (deployedHash !== void 0 && isCurrent) {
1109
1144
  out3(`\u2713 deployed ${fileName} \u2192 ${ws}${phaseSuffix}`);
1110
1145
  out3(` law ${describeLawMove(before, deployedHash)}`);
1111
1146
  emitPlan();
1112
- out3(` \u2713 current \u2014 dispatch + reads now resolve ${deployedHash.slice(0, 16)}\u2026`);
1147
+ out3(` \u2713 active \u2014 dispatch + reads now resolve ${deployedHash.slice(0, 16)}\u2026`);
1113
1148
  return 0;
1114
1149
  }
1115
1150
  if (deployedHash === void 0 || !hasCurrencyData) {
1116
1151
  out3(`\u2713 deployed ${fileName} \u2192 ${ws}${phaseSuffix}`);
1117
1152
  if (deployedHash !== void 0) out3(` law ${describeLawMove(before, deployedHash)}`);
1118
1153
  emitPlan();
1119
- out3(` \u26A0 couldn't confirm it is the current serving law (this workspace did not report current-law resolution).`);
1154
+ out3(` \u26A0 couldn't confirm it is active (this workspace did not report active-law resolution).`);
1120
1155
  return 0;
1121
1156
  }
1122
- err3(`deployed ${fileName} \u2192 ${ws}${phaseSuffix}, but it is NOT the current serving law \u2014 the upgrade did not take effect`);
1123
- out3(` law ${describeLawMove(before, deployedHash)} (committed as an install, but the holon does not resolve it as current)`);
1157
+ err3(`deployed ${fileName} \u2192 ${ws}${phaseSuffix}, but it is NOT active \u2014 the upgrade did not take effect`);
1158
+ out3(` law ${describeLawMove(before, deployedHash)} (committed as an install, but the holon does not resolve it as active)`);
1124
1159
  const leads = (after.domains ?? []).filter((x) => x.current === true && typeof x.domainHash === "string").map((x) => x.domainHash);
1125
- if (leads.length > 0) err3(` current serving: ${leads.map((h) => h.slice(0, 16) + "\u2026").join(", ")}`);
1126
- else err3(` the workspace resolves NO current law for this domain (materialisation wedged \u2014 the evolve gate did not advance it)`);
1160
+ if (leads.length > 0) err3(` active: ${leads.map((h) => h.slice(0, 16) + "\u2026").join(", ")}`);
1161
+ else err3(` the workspace resolves NO active law for this domain (materialisation wedged \u2014 the evolve gate did not advance it)`);
1127
1162
  err3(` re-run \`githolon deploy ${ws}\`; if it persists, the deploy committed but the workspace kept the old law (report it).`);
1128
1163
  return 2;
1129
1164
  }
@@ -1478,8 +1513,12 @@ async function createEngine({ wasmModule, bootstrapPkg, nomosPkg, replica, insta
1478
1513
  root.set("ws", new Directory(/* @__PURE__ */ new Map()));
1479
1514
  const preopen = new PreopenDirectory("/work", root);
1480
1515
  const STDERR = [];
1516
+ const pushStderr = (l) => {
1517
+ if (STDERR.length >= 500) STDERR.splice(0, STDERR.length - 499);
1518
+ STDERR.push(l);
1519
+ };
1481
1520
  const fds = [new OpenFile(new File([])), ConsoleStdout.lineBuffered(() => {
1482
- }), ConsoleStdout.lineBuffered((l) => STDERR.push(l)), preopen];
1521
+ }), ConsoleStdout.lineBuffered(pushStderr), preopen];
1483
1522
  const wasi = new WASI(["wasm_git_holon", "reactor"], [], fds, { debug: false });
1484
1523
  const inst = await WebAssembly.instantiate(wasmModule, { wasi_snapshot_preview1: wasi.wasiImport });
1485
1524
  const code = wasi.start(inst);
@@ -1624,24 +1663,10 @@ function parseAttestedReadNeed(error) {
1624
1663
  }
1625
1664
  return null;
1626
1665
  }
1627
- function offerIntent(eng, ws, bytes, opts) {
1628
- const name = `offer-in-${eng.seq++}.json`;
1629
- writeWork(eng, name, bytes);
1630
- const deferProjection = !!(opts && opts.defer);
1631
- const branch = opts && opts.branch || BRANCH;
1632
- return JSON.parse(call(eng.ex, "offer", { repoArg: repoArgOf(ws), workspace: ws, intentFile: `/work/${name}`, branch, deferProjection }, eng.STDERR));
1633
- }
1634
- function offerAdmit(eng, ws, bytes, opts) {
1635
- const v = offerIntent(eng, ws, bytes, opts);
1636
- return v.outcome === "admitted" ? { ok: true, head: v.head, ...v.born ? { born: v.born } : {} } : { ok: false, error: v.verdict?.reason ?? v.error ?? "the offer was refused" };
1637
- }
1638
- function applyIntentBytes(eng, ws, bytes, opts) {
1639
- return offerAdmit(eng, ws, bytes, opts);
1640
- }
1641
1666
  function verifyChainLocal(eng, ws) {
1642
1667
  return JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "verifyChain" }), branch: BRANCH }, eng.STDERR));
1643
1668
  }
1644
- var enc3, dec3, BRANCH, RESULT_PACK_INLINE_MAX, repoArgOf, gitdirOf, unpack, repoArgOfGitdir, bytesFromB64, kgit, installPayload, fullRef, applyPackInto, custodyLsRefs, qById, query, attestedRead, cryptoUnwrapKey, count, sum;
1669
+ var enc3, dec3, BRANCH, RESULT_PACK_INLINE_MAX, repoArgOf, gitdirOf, unpack, repoArgOfGitdir, bytesFromB64, kgit, installPayload, fullRef, applyPackInto, custodyLsRefs, qById, query, attestedRead, cryptoUnwrapKey, count, sum, spatialWithin;
1645
1670
  var init_engine = __esm({
1646
1671
  "vendor/engine/engine.mjs"() {
1647
1672
  "use strict";
@@ -1707,6 +1732,7 @@ var init_engine = __esm({
1707
1732
  cryptoUnwrapKey = (eng, { secret, hpkeEpk, ct }) => JSON.parse(call(eng.ex, "query", { queryBytes: b64Json({ op: "cryptoUnwrapKey", secret, hpkeEpk, ct }) }, eng.STDERR)).scopeKey;
1708
1733
  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));
1709
1734
  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));
1735
+ spatialWithin = (eng, ws, spatialId, bbox, principal = "") => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryBytes: b64Json({ op: "spatial", spatialId, minLng: bbox.minLng, minLat: bbox.minLat, maxLng: bbox.maxLng, maxLat: bbox.maxLat }), principal, branch: BRANCH }, eng.STDERR));
1710
1736
  }
1711
1737
  });
1712
1738
 
@@ -1889,32 +1915,55 @@ async function runOfflineLegs(eng, ws, lawHash, legs, frameworkHash, deployUsda)
1889
1915
  });
1890
1916
  const lines = [];
1891
1917
  const kinds = legs.legs ?? (legs.directiveId !== void 0 ? ["standalone-provable"] : []);
1918
+ const createLegs = [];
1892
1919
  if (legs.directiveId !== void 0) {
1893
- const payload = { ...legs.payload };
1920
+ createLegs.push({
1921
+ directiveId: legs.directiveId,
1922
+ ...legs.aggregateId !== void 0 ? { aggregateId: legs.aggregateId } : {},
1923
+ ...legs.payload !== void 0 ? { payload: legs.payload } : {},
1924
+ ...legs.autoStamped !== void 0 ? { autoStamped: legs.autoStamped } : {},
1925
+ ...legs.mintField !== void 0 ? { mintField: legs.mintField } : {},
1926
+ ...legs.query !== void 0 ? { query: legs.query } : {},
1927
+ ...legs.count !== void 0 ? { count: legs.count } : {},
1928
+ ...legs.spatial !== void 0 ? { spatial: legs.spatial } : {}
1929
+ });
1930
+ for (const e of legs.extraCreates ?? []) createLegs.push({ ...e });
1931
+ }
1932
+ for (const leg of createLegs) {
1933
+ const payload = { ...leg.payload };
1894
1934
  const stamp = (/* @__PURE__ */ new Date()).toISOString();
1895
- for (const f of legs.autoStamped ?? []) payload[f] = stamp;
1896
- if (legs.mintField !== void 0 && legs.aggregateId !== void 0) {
1897
- payload[legs.mintField] = mintId(eng, legs.aggregateId);
1935
+ for (const f of leg.autoStamped ?? []) payload[f] = stamp;
1936
+ if (leg.mintField !== void 0 && leg.aggregateId !== void 0) {
1937
+ payload[leg.mintField] = mintId(eng, leg.aggregateId);
1898
1938
  }
1899
- const res = author(eng, ws, legs.domain, legs.directiveId, payload, lawHash);
1939
+ const res = author(eng, ws, legs.domain, leg.directiveId, payload, lawHash);
1900
1940
  if (res.ok === false) {
1901
- return done(false, lines, `dispatch ${legs.domain}/${legs.directiveId} refused: ${res.error ?? "unknown"} \u2014 the law itself rejected its own synthesized sample; check the directive's plan/payload schema`);
1941
+ return done(false, lines, `dispatch ${legs.domain}/${leg.directiveId} refused: ${res.error ?? "unknown"} \u2014 the law itself rejected its own synthesized sample; check the directive's plan/payload schema`);
1902
1942
  }
1903
1943
  const createdId = (sealedIntentOf(eng, ws, res)?.events ?? []).find((e) => e.marker === "Create")?.aggregate;
1904
- lines.push(`\u2713 dispatch ${legs.domain}/${legs.directiveId} \u2014 offline write under the new law${createdId !== void 0 ? ` (${createdId.slice(0, 28)}\u2026)` : ""}`);
1905
- if (legs.query !== void 0) {
1906
- const rows = query(eng, ws, legs.query.id, JSON.stringify(legs.query.params));
1944
+ lines.push(`\u2713 dispatch ${legs.domain}/${leg.directiveId} \u2014 offline write under the new law${createdId !== void 0 ? ` (${createdId.slice(0, 28)}\u2026)` : ""}`);
1945
+ if (leg.query !== void 0) {
1946
+ const rows = query(eng, ws, leg.query.id, JSON.stringify(leg.query.params));
1907
1947
  if (rows.length !== 1 || createdId !== void 0 && rows[0].id !== createdId) {
1908
- return done(false, lines, `declared query ${legs.query.id} expected the 1 created row, got ${JSON.stringify(rows.map((r) => r.id))} \u2014 check the query's key fields against the directive's payload`);
1948
+ return done(false, lines, `declared query ${leg.query.id} expected the 1 created row, got ${JSON.stringify(rows.map((r) => r.id))} \u2014 check the query's key fields against the directive's payload`);
1909
1949
  }
1910
- lines.push(`\u2713 declared query ${legs.query.id} answers locally \u2014 1 row`);
1950
+ lines.push(`\u2713 declared query ${leg.query.id} answers locally \u2014 1 row`);
1911
1951
  }
1912
- if (legs.count !== void 0) {
1913
- const c = count(eng, ws, legs.count.id, legs.count.group).count;
1952
+ if (leg.count !== void 0) {
1953
+ const c = count(eng, ws, leg.count.id, leg.count.group).count;
1914
1954
  if (c !== 1) {
1915
- return done(false, lines, `declared count ${legs.count.id}(${JSON.stringify(legs.count.group)}) expected 1, got ${c} \u2014 check the count's grouping against the created row`);
1955
+ return done(false, lines, `declared count ${leg.count.id}(${JSON.stringify(leg.count.group)}) expected 1, got ${c} \u2014 check the count's grouping against the created row`);
1956
+ }
1957
+ lines.push(`\u2713 declared count ${leg.count.id}(${JSON.stringify(leg.count.group)}) = 1 locally`);
1958
+ }
1959
+ if (leg.spatial !== void 0) {
1960
+ const world = { minLng: -180, minLat: -90, maxLng: 180, maxLat: 90 };
1961
+ const sr = spatialWithin(eng, ws, leg.spatial.id, world);
1962
+ const ids = (sr.rows ?? []).map((r) => r.id);
1963
+ if (sr.ok === false || createdId === void 0 || !ids.includes(createdId)) {
1964
+ return done(false, lines, `spatial ${leg.spatial.id} membership: expected the created row ${createdId ?? "?"} in the world-bbox probe, got ${JSON.stringify(ids)}${sr.error ? ` (${sr.error})` : ""} \u2014 check the indexed geometry path against the directive's payload`);
1916
1965
  }
1917
- lines.push(`\u2713 declared count ${legs.count.id}(${JSON.stringify(legs.count.group)}) = 1 locally`);
1966
+ lines.push(`\u2713 spatial ${leg.spatial.id} \u2014 the created row answers the R*Tree probe (index membership proven)`);
1918
1967
  }
1919
1968
  }
1920
1969
  if (legs.childBirth !== void 0) {
@@ -1940,6 +1989,13 @@ function runChildBirthLeg(eng, ws, lawHash, cb, frameworkHash) {
1940
1989
  const t0 = performance.now();
1941
1990
  const lines = [];
1942
1991
  const done = (ok, error) => ({ ok, ms: performance.now() - t0, legs: lines, ...error !== void 0 ? { error } : {} });
1992
+ if (cb.recipeIssues !== void 0 && cb.recipeIssues.length > 0) {
1993
+ return done(
1994
+ false,
1995
+ `child birth ${cb.parentDomain}/${cb.birthDirectiveId} \u2014 the genesis recipe has ${cb.recipeIssues.length} compile-diagnosed issue(s); fix these and recompile:
1996
+ - ${cb.recipeIssues.join("\n - ")}`
1997
+ );
1998
+ }
1943
1999
  const fw = frameworkHash ?? eng.hashes?.nomos;
1944
2000
  const payload = { ...cb.birthPayload };
1945
2001
  if (cb.frameworkHashField !== void 0) payload[cb.frameworkHashField] = fw;
@@ -1988,7 +2044,13 @@ function runChildBirthLeg(eng, ws, lawHash, cb, frameworkHash) {
1988
2044
  }
1989
2045
  const childWs = (res.born ?? []).map((b) => b?.workspace).find((w) => typeof w === "string" && w.length > 0);
1990
2046
  if (childWs === void 0) {
1991
- return done(false, `child birth ${cb.parentDomain}/${cb.birthDirectiveId} admitted but the offer-effect spawned no child (born=${JSON.stringify(res.born ?? [])}) \u2014 the run_birth_effect did not fold a child (is the vendored kernel current? does the recipe carry a genesis chain?)`);
2047
+ const kernelSays = eng.STDERR?.filter((l) => l.includes("birth-effect-failed")).at(-1);
2048
+ if (kernelSays !== void 0) {
2049
+ const hint = /not iterable/.test(kernelSays) ? "\n fix: the named step's plan must RETURN AN ARRAY of ops \u2014 fluent builders (ensure()/create().set(...)) record themselves, so end the plan with `return [];`, or wrap standalone ops in `[ ... ]`. Then recompile and rerun." : "\n fix: the named genesis step is what the child's own gate refused \u2014 correct that directive/payload in the recipe and recompile. A working template: bench/scale/fixtures/estate-birth.";
2050
+ return done(false, `child birth ${cb.parentDomain}/${cb.birthDirectiveId} admitted, but the child genesis FAILED inside the kernel's birth offer-effect:
2051
+ ${kernelSays.replace(/^nomos:\s*/, "")}${hint}`);
2052
+ }
2053
+ return done(false, `child birth ${cb.parentDomain}/${cb.birthDirectiveId} admitted but the offer-effect spawned no child (born=${JSON.stringify(res.born ?? [])}) and the kernel logged no birth-effect failure \u2014 check the recipe carries a genesis chain (birth({ workspace, genesisChain })), and that the runtime wasm is current (~/.holon/runtime)`);
1992
2054
  }
1993
2055
  lines.push(`\u2713 child birth ${cb.parentDomain}/${cb.birthDirectiveId} \u2014 ${childWs} born (the offer-effect folded the child genesis through the child's OWN gate)`);
1994
2056
  const installed = (hash) => {
@@ -4112,7 +4174,7 @@ var HELP = {
4112
4174
  },
4113
4175
  replay: {
4114
4176
  usage: "githolon replay <ws|dir> [--step N] [--json] [--cloud <url>]",
4115
- what: "The ledger as film: walk a chain (cloud workspace or local holon directory) and re-apply it\nintent by intent through the SAME wasm gate edge admission runs \u2014 per step: directive, payload,\nthe rows it changed, running per-type tallies. Finale: the chain's verify_chain verdict.\nOn a TTY it steps interactively (enter = next, a = run to end, q = quit).",
4177
+ what: "The ledger as film: walk a chain (cloud workspace or local holon directory), require the\nkernel's from-genesis verify_chain verdict to be green, then render the verified intent stream \u2014\nper step: directive, payload, the rows it changed, running per-type tallies. On a TTY it steps\ninteractively (enter = next, a = run to end, q = quit).",
4116
4178
  flags: [
4117
4179
  ["<ws|dir>", "a workspace name on the cloud, or a local holon directory (githolon ledger init)"],
4118
4180
  ["--step N", "non-interactive; print the running tallies every N intents"],
@@ -4244,11 +4306,11 @@ var HELP = {
4244
4306
  },
4245
4307
  deploy: {
4246
4308
  usage: "githolon deploy [<ws>] [--as <uid>] [--keep-beside] [--retire-replaced] [--file <deploy.json>] [--cloud <url>] [--target <name>]",
4247
- what: "Deploy build/*.deploy.json \u2014 kernel-gated, no host secret. DEFAULT = REPLACE-CURRENT: for each domain\nkey in the package it resolves the workspace's CURRENT served install (the holon's `currentLaw` frontier\nmap) and passes it as installDomain's `replaces`, so the new law SUPERSEDES the old per key (the prior\ndrops out of the served frontier). Re-deploying the SAME hash stays idempotent (never self-superseded).\nTWO lanes, picked automatically:\n\u2022 SIGNED (a WARRANTED ws, or when --as / the active `githolon use` principal has a signing key on\n file): signs a `nomos/installDomain` offer (carrying `replaces`) with that key and relays it; the\n kernel re-verifies the signature + judges the installDomain relation. Required for warranted platforms.\n\u2022 OPEN (unwarranted ws / no key): the open POST /domains \u2014 installs BESIDE (the host-authored lane does\n not thread `replaces`; the newest-Active frontier still serves the deploy). Sign with --as to supersede.\nAfter the POST it CONFIRMS the deployed hash is the CURRENT serving law (exit 2 if committed-but-not-\ncurrent) and prints what MOVED, per key. With no <ws>, the project's `workspace` binding resolves it.",
4309
+ what: "Deploy build/*.deploy.json \u2014 kernel-gated, no host secret. DEFAULT = REPLACE-CURRENT: for each domain\nkey in the package it resolves the workspace's active install (the holon's existing `currentLaw` map)\nand passes it as installDomain's `replaces`, so the old install becomes superseded for that key.\nRe-deploying the SAME hash stays idempotent (never self-superseded).\nTWO lanes, picked automatically:\n\u2022 SIGNED (a WARRANTED ws, or when --as / the active `githolon use` principal has a signing key on\n file): signs a `nomos/installDomain` offer (carrying `replaces`) with that key and relays it; the\n kernel re-verifies the signature + judges the installDomain relation. Required for warranted platforms.\n\u2022 OPEN (unwarranted ws / no key): the open POST /domains installs beside. The new law becomes active,\n but the prior install is only effectively superseded unless you sign with --as to record Superseded.\nAfter the POST it CONFIRMS the deployed hash is active (exit 2 if committed-but-not-active) and prints\nwhat moved, per key. With no <ws>, the project's `workspace` binding resolves it.\nLifecycle vocabulary: active = served now; superseded = replayable, not served; retired = explicitly withdrawn, replayable, not served.",
4248
4310
  flags: [
4249
4311
  ["--as <uid>", "deploy as this principal via the SIGNED lane (needs its key \u2014 githolon key import; else githolon use)"],
4250
- ["--keep-beside", "install BESIDE the prior law (the old multi-version default; no supersession)"],
4251
- ["--retire-replaced", "supersede via Retired (not just Superseded) \u2014 the prior install is explicitly retired"],
4312
+ ["--keep-beside", "install beside the prior law (the old multi-version default; prior becomes effectively superseded by recency)"],
4313
+ ["--retire-replaced", "supersede via Retired \u2014 the prior install is explicitly withdrawn"],
4252
4314
  ["--file <path>", "an explicit deploy body (default: the one build/*.deploy.json)"],
4253
4315
  ["--target <name>", "pick among named targets (workspace: { dev: \u2026, prod: \u2026 })"],
4254
4316
  ["--cloud <url>", "target cloud"]
@@ -4257,7 +4319,7 @@ var HELP = {
4257
4319
  },
4258
4320
  domains: {
4259
4321
  usage: "githolon domains <status <ws> [--explain-current-interface] | retire <ws> <hashOrKey> [--with <hashOrKey>]>",
4260
- what: "The INSTALL-LIFECYCLE lane over a workspace's law.\n\u2022 status: per domain KEY, the current SERVED hash + phase, then the superseded/retired lineage behind it\n (replayable installs the frontier excludes). --explain-current-interface spells out WHY the current\n interface is what it is \u2014 the frontier resolves the newest Active-not-superseded/-retired install per\n key, so old installs never pollute the current interface.\n\u2022 retire: mark an install Retired (replayable, no longer served) via a signed installDomain offer. The\n framework retires only as the consequence of installing a SUCCESSOR \u2014 by default the local compiled\n package (build/*.deploy.json), or --with <hashOrKey> names an already-installed successor. A key the\n successor does not cover is left with NO served law \u2014 WARNED loudly, never blocked. A key resolves to\n its current served hash; a 64-hex arg is taken as the install hash.",
4322
+ what: "The INSTALL-LIFECYCLE lane over a workspace's law.\n\u2022 status: per domain KEY, the active hash, then the superseded/retired lineage behind it.\n Lifecycle vocabulary: active = served now; superseded = replaced by a newer install for the same domain\n key, replayable, not served; retired = explicitly withdrawn, replayable, not served.\n --explain-current-interface spells out why active reads/dispatch use only that active map.\n\u2022 retire: mark an install Retired via a signed installDomain offer. The framework retires only as the\n consequence of installing a SUCCESSOR \u2014 by default the local compiled package (build/*.deploy.json),\n or --with <hashOrKey> names an already-installed successor. A key the successor does not cover is left\n with NO active law \u2014 WARNED loudly, never blocked. A key resolves to its active hash; a 64-hex arg is\n taken as the install hash.",
4261
4323
  flags: [
4262
4324
  ["--explain-current-interface", "status: also explain the frontier resolution (why the interface is what it is)"],
4263
4325
  ["--with <hashOrKey>", "retire: the retiring successor package \u2014 an already-installed hash or served key"],
@@ -4372,7 +4434,6 @@ import git2 from "isomorphic-git";
4372
4434
  var out8 = (s) => void process.stdout.write(s + "\n");
4373
4435
  var err8 = (s) => void process.stderr.write("error: " + s + "\n");
4374
4436
  var SOURCE = "source";
4375
- var REPLAY = "replay";
4376
4437
  function summarizePayload(payload, max = 100) {
4377
4438
  if (payload === void 0) return "\u2014";
4378
4439
  const parts = [];
@@ -4488,32 +4549,26 @@ async function replay(target, opts) {
4488
4549
  err8(`the chain on '${target}' carries no intents \u2014 nothing to replay`);
4489
4550
  return 1;
4490
4551
  }
4491
- await mountFresh(eng, REPLAY);
4552
+ const verdict = verifyChainLocal(eng, SOURCE);
4553
+ if (verdict["valid"] !== true) {
4554
+ if (json) emit({ finale: true, verdict });
4555
+ else printVerdict(verdict);
4556
+ return 1;
4557
+ }
4492
4558
  const interactive = !json && opts.step === void 0 && process.stdout.isTTY === true && process.stdin.isTTY === true;
4493
4559
  const stepEvery = opts.step ?? (json ? 0 : 0);
4494
4560
  const tallies = /* @__PURE__ */ new Map();
4561
+ const seenRows = /* @__PURE__ */ new Set();
4495
4562
  let autoRun = !interactive;
4496
4563
  if (!json) {
4497
- out8(`replaying ${chain.length} intent(s) from ${isDir ? target : `${cloud} :: ${target}`} \u2014 every step re-admitted through the wasm gate`);
4564
+ out8(`replaying ${chain.length} intent(s) from ${isDir ? target : `${cloud} :: ${target}`} \u2014 verify_chain green, rendering the sealed event film`);
4498
4565
  if (interactive) out8(" keys: enter = next intent \xB7 a = run to the end \xB7 q = quit\n");
4499
4566
  }
4500
4567
  for (let i = 0; i < chain.length; i++) {
4501
- const { oid, bytes, doc } = chain[i];
4568
+ const { oid, doc } = chain[i];
4502
4569
  const domain = doc.payload?.domain ?? "?";
4503
4570
  const directive = doc.payload?.directiveId ?? "?";
4504
- const touched = (doc.events ?? []).map((e) => e.aggregate).filter((a) => typeof a === "string");
4505
- const before = new Map(touched.map((id) => [id, qById(eng, REPLAY, id)[0]?.data]));
4506
- const t0 = performance.now();
4507
- const res = applyIntentBytes(eng, REPLAY, bytes);
4508
- const ms = performance.now() - t0;
4509
- if (res.ok === false) {
4510
- const msg = `replay REFUSED at intent ${i} (${domain}/${directive}, ${oid.slice(0, 10)}): ${res.error ?? "unknown"}`;
4511
- if (json) emit({ step: i, oid, domain, directive, refused: true, error: res.error ?? "unknown" });
4512
- else err8(msg);
4513
- err8("the chain's own gate rejects this entry on a fresh fold \u2014 the source it came from would refuse it too (verify below names the check)");
4514
- printVerdict(verifyChainLocal(eng, SOURCE));
4515
- return 1;
4516
- }
4571
+ const seenBeforeStep = new Set(seenRows);
4517
4572
  for (const ev of doc.events ?? []) {
4518
4573
  if (ev.marker === "Create") tallies.set(typeOf(ev), (tallies.get(typeOf(ev)) ?? 0) + 1);
4519
4574
  if (ev.marker === "Delete") tallies.set(typeOf(ev), (tallies.get(typeOf(ev)) ?? 1) - 1);
@@ -4527,16 +4582,16 @@ async function replay(target, opts) {
4527
4582
  directive,
4528
4583
  payload: capJsonStrings(doc.payload?.payload ?? null),
4529
4584
  events: (doc.events ?? []).map((e) => ({ aggregate: e.aggregate, marker: e.marker, ops: summarizeOps(e) })),
4530
- applyMs: +ms.toFixed(1),
4585
+ verified: true,
4531
4586
  tallies: Object.fromEntries(tallies)
4532
4587
  });
4533
4588
  } else {
4534
- out8(`[${String(i + 1).padStart(String(chain.length).length)}/${chain.length}] ${domain}/${directive} (${oid.slice(0, 10)}, ${ms.toFixed(0)} ms)`);
4589
+ out8(`[${String(i + 1).padStart(String(chain.length).length)}/${chain.length}] ${domain}/${directive} (${oid.slice(0, 10)})`);
4535
4590
  out8(` payload ${summarizePayload(doc.payload?.payload)}`);
4536
4591
  for (const ev of doc.events ?? []) {
4537
4592
  const id = ev.aggregate ?? "?";
4538
- const was = before.get(id);
4539
- const verb = ev.marker === "Create" ? "+ created" : ev.marker === "Delete" ? "- deleted" : was === void 0 ? "~ touched" : "~ updated";
4593
+ const hadRow = typeof ev.aggregate === "string" && seenBeforeStep.has(ev.aggregate);
4594
+ const verb = ev.marker === "Create" ? "+ created" : ev.marker === "Delete" ? "- deleted" : hadRow ? "~ updated" : "~ touched";
4540
4595
  out8(` ${verb} ${id}`);
4541
4596
  const ops = summarizeOps(ev);
4542
4597
  if (ops.length > 0) out8(` ${ops}`);
@@ -4545,6 +4600,12 @@ async function replay(target, opts) {
4545
4600
  const showTally = stepEvery > 0 ? (i + 1) % stepEvery === 0 || i === chain.length - 1 : true;
4546
4601
  if (showTally && tallyLine.length > 0) out8(` rows ${tallyLine}`);
4547
4602
  }
4603
+ for (const ev of doc.events ?? []) {
4604
+ if (typeof ev.aggregate === "string") {
4605
+ if (ev.marker === "Delete") seenRows.delete(ev.aggregate);
4606
+ else seenRows.add(ev.aggregate);
4607
+ }
4608
+ }
4548
4609
  if (interactive && !autoRun && i < chain.length - 1) {
4549
4610
  const key = await readKey();
4550
4611
  if (key === "quit") {
@@ -4556,7 +4617,6 @@ stopped at intent ${i + 1}/${chain.length} \u2014 the verify verdict below cover
4556
4617
  }
4557
4618
  }
4558
4619
  if (!json) out8("");
4559
- const verdict = verifyChainLocal(eng, SOURCE);
4560
4620
  if (json) {
4561
4621
  emit({ finale: true, verdict });
4562
4622
  return verdict["valid"] === true ? 0 : 1;
@@ -4631,6 +4691,7 @@ async function decrypt(ws, opts) {
4631
4691
  // src/status.ts
4632
4692
  init_engine();
4633
4693
  init_cloud();
4694
+ init_lifecycle();
4634
4695
  import { readFileSync as readFileSync10 } from "node:fs";
4635
4696
  var out10 = (s) => void process.stdout.write(s + "\n");
4636
4697
  var err10 = (s) => void process.stderr.write("error: " + s + "\n");
@@ -4642,39 +4703,39 @@ function lawDiffVerdict(localHash, installed, ws, hasCurrencyData) {
4642
4703
  const localIsCurrent = currents.some((d) => d.domainHash === localHash);
4643
4704
  const lines = [];
4644
4705
  if (localIsCurrent) {
4645
- lines.push(`\u2713 in sync \u2014 your local build IS the current serving law (${localHash.slice(0, 16)}\u2026)`);
4646
- if (priors.length > 0) lines.push(` (${priors.length} prior deploy(s) remain Active under install-beside; yours is the one dispatch resolves)`);
4706
+ lines.push(`\u2713 in sync \u2014 your local build is active (${localHash.slice(0, 16)}\u2026, served now)`);
4707
+ if (priors.length > 0) lines.push(` (${priors.length} superseded prior deploy(s) are replayable, not served)`);
4647
4708
  return { inSync: true, lines };
4648
4709
  }
4649
4710
  if (!hasCurrencyData && localActive !== void 0) {
4650
- lines.push(`~ your local build (${localHash.slice(0, 16)}\u2026) is an ACTIVE install on '${ws}'`);
4651
- lines.push(` \u26A0 this workspace did not report current-law resolution \u2014 matched by active presence only; can't confirm it is serving`);
4711
+ lines.push(`~ your local build (${localHash.slice(0, 16)}\u2026) is installed on '${ws}'`);
4712
+ lines.push(` \u26A0 this workspace did not report active-law resolution \u2014 matched by installed presence only; can't confirm it is served now`);
4652
4713
  return { inSync: true, lines };
4653
4714
  }
4654
4715
  if (active.length === 0) {
4655
4716
  lines.push(`\u2717 workspace '${ws}' has NO active tenant law \u2014 your local build is not deployed`);
4656
4717
  } else if (localActive !== void 0) {
4657
4718
  if (currents.length > 0) {
4658
- lines.push(`~ your build (${localHash.slice(0, 16)}\u2026) is an ACTIVE install on '${ws}', but SUPERSEDED \u2014 a newer deploy is current:`);
4659
- for (const c of currents) lines.push(` current ${c.domainHash.slice(0, 16)}\u2026 (${c.installedBy ?? "?"}) \u2190 resolution serves this`);
4719
+ lines.push(`~ your build (${localHash.slice(0, 16)}\u2026) is superseded on '${ws}' \u2014 a newer deploy is active:`);
4720
+ for (const c of currents) lines.push(` active ${c.domainHash.slice(0, 16)}\u2026 (${c.installedBy ?? "?"}) \u2190 served now`);
4660
4721
  } else {
4661
- lines.push(`\u2717 your build (${localHash.slice(0, 16)}\u2026) is ACTIVE on '${ws}' but is NOT the current serving law \u2014 the deploy did not take effect:`);
4662
- lines.push(` the workspace resolves NO current law for this domain (materialisation wedged \u2014 the evolve gate did not advance it)`);
4722
+ lines.push(`\u2717 your build (${localHash.slice(0, 16)}\u2026) is installed on '${ws}' but is NOT active \u2014 the deploy did not take effect:`);
4723
+ lines.push(` the workspace resolves NO active law for this domain (materialisation wedged \u2014 the evolve gate did not advance it)`);
4663
4724
  }
4664
- lines.push(` redeploy to make your build current: githolon deploy ${ws}`);
4725
+ lines.push(` redeploy to make your build active: githolon deploy ${ws}`);
4665
4726
  return { inSync: false, lines };
4666
4727
  } else if (currents.length > 0) {
4667
- lines.push(`\u2717 local law differs from the current install${currents.length > 1 ? "s" : ""} on '${ws}':`);
4668
- for (const c of currents) lines.push(` current ${c.domainHash.slice(0, 16)}\u2026 (${c.installedBy ?? "?"}) \u2190 resolution serves this`);
4669
- if (priors.length > 0) lines.push(` + ${priors.length} prior active install(s) (install-beside \u2014 historical, not served)`);
4728
+ lines.push(`\u2717 local law differs from the active install${currents.length > 1 ? "s" : ""} on '${ws}':`);
4729
+ for (const c of currents) lines.push(` active ${c.domainHash.slice(0, 16)}\u2026 (${c.installedBy ?? "?"}) \u2190 served now`);
4730
+ if (priors.length > 0) lines.push(` + ${priors.length} superseded prior install(s) (replayable, not served)`);
4670
4731
  } else {
4671
- lines.push(`\u2717 local law differs from every active installation on '${ws}' (${active.length}):`);
4732
+ lines.push(`\u2717 local law differs from every installed tenant law on '${ws}' (${active.length}):`);
4672
4733
  for (const d of active) lines.push(` deployed ${d.domainHash.slice(0, 16)}\u2026 (${d.installedBy ?? "?"})`);
4673
4734
  }
4674
- lines.push(` remedy: githolon deploy ${ws} (installs ${localHash.slice(0, 16)}\u2026 and makes it current)`);
4735
+ lines.push(` remedy: githolon deploy ${ws} (installs ${localHash.slice(0, 16)}\u2026 and makes it active)`);
4675
4736
  return { inSync: false, lines };
4676
4737
  }
4677
- var CONTROL_PLANE_KEYS = ["nomos", "bootstrap", "workspaces"];
4738
+ var FRAMEWORK_DOMAIN_KEYS = ["nomos", "bootstrap", "workspaces"];
4678
4739
  async function status(wsArg, opts) {
4679
4740
  const cloud = cloudBase(opts.cloud);
4680
4741
  let ws;
@@ -4704,17 +4765,21 @@ async function status(wsArg, opts) {
4704
4765
  }
4705
4766
  out10(`workspace ${ws} on ${cloud}`);
4706
4767
  const allDomains = d.domains ?? [];
4707
- const controlPlaneHashes = new Set(CONTROL_PLANE_KEYS.map((k) => d.currentLaw?.[k]).filter(Boolean));
4708
- const domains = allDomains.filter((dm) => !controlPlaneHashes.has(dm.domainHash));
4768
+ const frameworkHashes = new Set(FRAMEWORK_DOMAIN_KEYS.map((k) => d.currentLaw?.[k]).filter(Boolean));
4769
+ const domains = allDomains.filter((dm) => !frameworkHashes.has(dm.domainHash));
4709
4770
  const controllerCount = allDomains.length - domains.length;
4710
4771
  if (allDomains.length === 0) out10(`installed (none \u2014 not even the controller; the workspace may be unborn)`);
4772
+ const currentHashes = new Set(Object.values(d.currentLaw ?? {}));
4711
4773
  for (const dom of domains) {
4712
- const mark = dom.current === true ? " \u2190 current (the holon resolves dispatch here)" : "";
4713
- out10(`installed ${(dom.domainHash ?? "?").slice(0, 16)}\u2026 ${dom.phase ?? "?"} by ${dom.installedBy ?? "?"}${mark}`);
4774
+ const state = domainLifecycleState(dom, currentHashes);
4775
+ const mark = state === "active" ? " \u2190 served now" : "";
4776
+ out10(`law ${(dom.domainHash ?? "?").slice(0, 16)}\u2026 ${state.padEnd(10)} by ${dom.installedBy ?? "?"}${mark}`);
4714
4777
  }
4715
- if (controllerCount > 0) out10(` (+ ${controllerCount} control-plane install(s) \u2014 the nomos lifecycle controller; not tenant law)`);
4778
+ if (controllerCount > 0) out10(` (+ ${controllerCount} framework install(s) \u2014 nomos lifecycle/governance; not tenant law)`);
4716
4779
  const tenantActive = domains.filter((dm) => dm.phase === "Active").length;
4717
- if (tenantActive > 1) out10(` (${tenantActive} active tenant installs \u2014 install-beside keeps every prior deploy Active; the holon serves the current one per domain)`);
4780
+ const tenantServed = domains.filter((dm) => domainLifecycleState(dm, currentHashes) === "active").length;
4781
+ const tenantSuperseded = domains.filter((dm) => domainLifecycleState(dm, currentHashes) === "superseded").length;
4782
+ if (tenantActive > tenantServed || tenantSuperseded > 0) out10(` (${tenantServed} active, ${tenantSuperseded} superseded tenant install(s); superseded installs are replayable, not served)`);
4718
4783
  if (localHash === void 0) {
4719
4784
  out10(`local (no build/*.deploy.json here \u2014 run \`githolon compile\` to build the law this project authors)`);
4720
4785
  return 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "githolon",
3
- "version": "0.45.0",
3
+ "version": "0.47.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.45.0",
33
- "@githolon/dsl": "^0.45.0",
32
+ "@githolon/client": "^0.47.0",
33
+ "@githolon/dsl": "^0.47.0",
34
34
  "isomorphic-git": "^1.38.4"
35
35
  },
36
36
  "devDependencies": {