githolon 0.46.0 → 0.48.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 +137 -64
  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);
@@ -1579,7 +1618,7 @@ function author(eng, ws, domain, directiveId, payload, controllerHash, opts = {}
1579
1618
  writeWork(eng, `envelope-${seq}.json`, enc3.encode(stringifyBig(envelope)));
1580
1619
  v = offerOnce();
1581
1620
  }
1582
- const res = v.outcome === "admitted" ? { ok: true, head: v.head, intentOut: v.intentOut, ...v.born ? { born: v.born } : {} } : v.outcome === "refused" ? { ok: false, error: v.verdict?.reason ?? v.error } : v;
1621
+ const res = v.outcome === "admitted" ? { ok: true, head: v.head, intentOut: v.intentOut, ...v.born ? { born: v.born } : {}, ...v.birthOutcomes ? { birthOutcomes: v.birthOutcomes } : {} } : v.outcome === "refused" ? { ok: false, error: v.verdict?.reason ?? v.error } : v;
1583
1622
  if (defer && res.ok) (eng.pendingProjection ??= /* @__PURE__ */ new Set()).add(ws);
1584
1623
  return res;
1585
1624
  }
@@ -1950,6 +1989,13 @@ function runChildBirthLeg(eng, ws, lawHash, cb, frameworkHash) {
1950
1989
  const t0 = performance.now();
1951
1990
  const lines = [];
1952
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
+ }
1953
1999
  const fw = frameworkHash ?? eng.hashes?.nomos;
1954
2000
  const payload = { ...cb.birthPayload };
1955
2001
  if (cb.frameworkHashField !== void 0) payload[cb.frameworkHashField] = fw;
@@ -1998,7 +2044,29 @@ function runChildBirthLeg(eng, ws, lawHash, cb, frameworkHash) {
1998
2044
  }
1999
2045
  const childWs = (res.born ?? []).map((b) => b?.workspace).find((w) => typeof w === "string" && w.length > 0);
2000
2046
  if (childWs === void 0) {
2001
- 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 planReturnHint = "\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.";
2048
+ const recipeHint = "\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.";
2049
+ const failing = (res.birthOutcomes ?? []).find((o) => o?.status === "failed" || o?.status === "deferred");
2050
+ if (failing !== void 0) {
2051
+ const reason = String(failing.reason ?? "unknown");
2052
+ const what = failing.status === "deferred" ? `the birth of '${failing.workspace}' was DEFERRED to the edge (no local child custody):
2053
+ ${reason}
2054
+ (expected on a host-less/native peer \u2014 the edge materializes the child on next sync; not a law bug)` : `the child genesis of '${failing.workspace}' FAILED inside the kernel's birth offer-effect:
2055
+ ${reason}${/not iterable/.test(reason) ? planReturnHint : recipeHint}`;
2056
+ return done(false, `child birth ${cb.parentDomain}/${cb.birthDirectiveId} admitted, but ${what}`);
2057
+ }
2058
+ const kernelSays = eng.STDERR?.filter((l) => l.includes("birth-effect-failed")).at(-1);
2059
+ if (kernelSays !== void 0) {
2060
+ const hint = /not iterable/.test(kernelSays) ? planReturnHint : recipeHint;
2061
+ return done(false, `child birth ${cb.parentDomain}/${cb.birthDirectiveId} admitted, but the child genesis FAILED inside the kernel's birth offer-effect:
2062
+ ${kernelSays.replace(/^nomos:\s*/, "")}${hint}`);
2063
+ }
2064
+ 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)`);
2065
+ }
2066
+ const downgraded = (res.birthOutcomes ?? []).find((o) => o?.workspace === childWs && o?.status === "unwarranted-cert");
2067
+ if (downgraded !== void 0) {
2068
+ const checks = downgraded.certChecks !== void 0 ? ` certChecks=${JSON.stringify(downgraded.certChecks)}` : "";
2069
+ lines.push(`\u26A0 child ${childWs} was born UNWARRANTED \u2014 ${String(downgraded.reason ?? "cert not seeded")}${checks}`);
2002
2070
  }
2003
2071
  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)`);
2004
2072
  const installed = (hash) => {
@@ -4254,11 +4322,11 @@ var HELP = {
4254
4322
  },
4255
4323
  deploy: {
4256
4324
  usage: "githolon deploy [<ws>] [--as <uid>] [--keep-beside] [--retire-replaced] [--file <deploy.json>] [--cloud <url>] [--target <name>]",
4257
- 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.",
4325
+ 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.",
4258
4326
  flags: [
4259
4327
  ["--as <uid>", "deploy as this principal via the SIGNED lane (needs its key \u2014 githolon key import; else githolon use)"],
4260
- ["--keep-beside", "install BESIDE the prior law (the old multi-version default; no supersession)"],
4261
- ["--retire-replaced", "supersede via Retired (not just Superseded) \u2014 the prior install is explicitly retired"],
4328
+ ["--keep-beside", "install beside the prior law (the old multi-version default; prior becomes effectively superseded by recency)"],
4329
+ ["--retire-replaced", "supersede via Retired \u2014 the prior install is explicitly withdrawn"],
4262
4330
  ["--file <path>", "an explicit deploy body (default: the one build/*.deploy.json)"],
4263
4331
  ["--target <name>", "pick among named targets (workspace: { dev: \u2026, prod: \u2026 })"],
4264
4332
  ["--cloud <url>", "target cloud"]
@@ -4267,7 +4335,7 @@ var HELP = {
4267
4335
  },
4268
4336
  domains: {
4269
4337
  usage: "githolon domains <status <ws> [--explain-current-interface] | retire <ws> <hashOrKey> [--with <hashOrKey>]>",
4270
- 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.",
4338
+ 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.",
4271
4339
  flags: [
4272
4340
  ["--explain-current-interface", "status: also explain the frontier resolution (why the interface is what it is)"],
4273
4341
  ["--with <hashOrKey>", "retire: the retiring successor package \u2014 an already-installed hash or served key"],
@@ -4639,6 +4707,7 @@ async function decrypt(ws, opts) {
4639
4707
  // src/status.ts
4640
4708
  init_engine();
4641
4709
  init_cloud();
4710
+ init_lifecycle();
4642
4711
  import { readFileSync as readFileSync10 } from "node:fs";
4643
4712
  var out10 = (s) => void process.stdout.write(s + "\n");
4644
4713
  var err10 = (s) => void process.stderr.write("error: " + s + "\n");
@@ -4650,39 +4719,39 @@ function lawDiffVerdict(localHash, installed, ws, hasCurrencyData) {
4650
4719
  const localIsCurrent = currents.some((d) => d.domainHash === localHash);
4651
4720
  const lines = [];
4652
4721
  if (localIsCurrent) {
4653
- lines.push(`\u2713 in sync \u2014 your local build IS the current serving law (${localHash.slice(0, 16)}\u2026)`);
4654
- if (priors.length > 0) lines.push(` (${priors.length} prior deploy(s) remain Active under install-beside; yours is the one dispatch resolves)`);
4722
+ lines.push(`\u2713 in sync \u2014 your local build is active (${localHash.slice(0, 16)}\u2026, served now)`);
4723
+ if (priors.length > 0) lines.push(` (${priors.length} superseded prior deploy(s) are replayable, not served)`);
4655
4724
  return { inSync: true, lines };
4656
4725
  }
4657
4726
  if (!hasCurrencyData && localActive !== void 0) {
4658
- lines.push(`~ your local build (${localHash.slice(0, 16)}\u2026) is an ACTIVE install on '${ws}'`);
4659
- lines.push(` \u26A0 this workspace did not report current-law resolution \u2014 matched by active presence only; can't confirm it is serving`);
4727
+ lines.push(`~ your local build (${localHash.slice(0, 16)}\u2026) is installed on '${ws}'`);
4728
+ lines.push(` \u26A0 this workspace did not report active-law resolution \u2014 matched by installed presence only; can't confirm it is served now`);
4660
4729
  return { inSync: true, lines };
4661
4730
  }
4662
4731
  if (active.length === 0) {
4663
4732
  lines.push(`\u2717 workspace '${ws}' has NO active tenant law \u2014 your local build is not deployed`);
4664
4733
  } else if (localActive !== void 0) {
4665
4734
  if (currents.length > 0) {
4666
- lines.push(`~ your build (${localHash.slice(0, 16)}\u2026) is an ACTIVE install on '${ws}', but SUPERSEDED \u2014 a newer deploy is current:`);
4667
- for (const c of currents) lines.push(` current ${c.domainHash.slice(0, 16)}\u2026 (${c.installedBy ?? "?"}) \u2190 resolution serves this`);
4735
+ lines.push(`~ your build (${localHash.slice(0, 16)}\u2026) is superseded on '${ws}' \u2014 a newer deploy is active:`);
4736
+ for (const c of currents) lines.push(` active ${c.domainHash.slice(0, 16)}\u2026 (${c.installedBy ?? "?"}) \u2190 served now`);
4668
4737
  } else {
4669
- 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:`);
4670
- lines.push(` the workspace resolves NO current law for this domain (materialisation wedged \u2014 the evolve gate did not advance it)`);
4738
+ 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:`);
4739
+ lines.push(` the workspace resolves NO active law for this domain (materialisation wedged \u2014 the evolve gate did not advance it)`);
4671
4740
  }
4672
- lines.push(` redeploy to make your build current: githolon deploy ${ws}`);
4741
+ lines.push(` redeploy to make your build active: githolon deploy ${ws}`);
4673
4742
  return { inSync: false, lines };
4674
4743
  } else if (currents.length > 0) {
4675
- lines.push(`\u2717 local law differs from the current install${currents.length > 1 ? "s" : ""} on '${ws}':`);
4676
- for (const c of currents) lines.push(` current ${c.domainHash.slice(0, 16)}\u2026 (${c.installedBy ?? "?"}) \u2190 resolution serves this`);
4677
- if (priors.length > 0) lines.push(` + ${priors.length} prior active install(s) (install-beside \u2014 historical, not served)`);
4744
+ lines.push(`\u2717 local law differs from the active install${currents.length > 1 ? "s" : ""} on '${ws}':`);
4745
+ for (const c of currents) lines.push(` active ${c.domainHash.slice(0, 16)}\u2026 (${c.installedBy ?? "?"}) \u2190 served now`);
4746
+ if (priors.length > 0) lines.push(` + ${priors.length} superseded prior install(s) (replayable, not served)`);
4678
4747
  } else {
4679
- lines.push(`\u2717 local law differs from every active installation on '${ws}' (${active.length}):`);
4748
+ lines.push(`\u2717 local law differs from every installed tenant law on '${ws}' (${active.length}):`);
4680
4749
  for (const d of active) lines.push(` deployed ${d.domainHash.slice(0, 16)}\u2026 (${d.installedBy ?? "?"})`);
4681
4750
  }
4682
- lines.push(` remedy: githolon deploy ${ws} (installs ${localHash.slice(0, 16)}\u2026 and makes it current)`);
4751
+ lines.push(` remedy: githolon deploy ${ws} (installs ${localHash.slice(0, 16)}\u2026 and makes it active)`);
4683
4752
  return { inSync: false, lines };
4684
4753
  }
4685
- var CONTROL_PLANE_KEYS = ["nomos", "bootstrap", "workspaces"];
4754
+ var FRAMEWORK_DOMAIN_KEYS = ["nomos", "bootstrap", "workspaces"];
4686
4755
  async function status(wsArg, opts) {
4687
4756
  const cloud = cloudBase(opts.cloud);
4688
4757
  let ws;
@@ -4712,17 +4781,21 @@ async function status(wsArg, opts) {
4712
4781
  }
4713
4782
  out10(`workspace ${ws} on ${cloud}`);
4714
4783
  const allDomains = d.domains ?? [];
4715
- const controlPlaneHashes = new Set(CONTROL_PLANE_KEYS.map((k) => d.currentLaw?.[k]).filter(Boolean));
4716
- const domains = allDomains.filter((dm) => !controlPlaneHashes.has(dm.domainHash));
4784
+ const frameworkHashes = new Set(FRAMEWORK_DOMAIN_KEYS.map((k) => d.currentLaw?.[k]).filter(Boolean));
4785
+ const domains = allDomains.filter((dm) => !frameworkHashes.has(dm.domainHash));
4717
4786
  const controllerCount = allDomains.length - domains.length;
4718
4787
  if (allDomains.length === 0) out10(`installed (none \u2014 not even the controller; the workspace may be unborn)`);
4788
+ const currentHashes = new Set(Object.values(d.currentLaw ?? {}));
4719
4789
  for (const dom of domains) {
4720
- const mark = dom.current === true ? " \u2190 current (the holon resolves dispatch here)" : "";
4721
- out10(`installed ${(dom.domainHash ?? "?").slice(0, 16)}\u2026 ${dom.phase ?? "?"} by ${dom.installedBy ?? "?"}${mark}`);
4790
+ const state = domainLifecycleState(dom, currentHashes);
4791
+ const mark = state === "active" ? " \u2190 served now" : "";
4792
+ out10(`law ${(dom.domainHash ?? "?").slice(0, 16)}\u2026 ${state.padEnd(10)} by ${dom.installedBy ?? "?"}${mark}`);
4722
4793
  }
4723
- if (controllerCount > 0) out10(` (+ ${controllerCount} control-plane install(s) \u2014 the nomos lifecycle controller; not tenant law)`);
4794
+ if (controllerCount > 0) out10(` (+ ${controllerCount} framework install(s) \u2014 nomos lifecycle/governance; not tenant law)`);
4724
4795
  const tenantActive = domains.filter((dm) => dm.phase === "Active").length;
4725
- if (tenantActive > 1) out10(` (${tenantActive} active tenant installs \u2014 install-beside keeps every prior deploy Active; the holon serves the current one per domain)`);
4796
+ const tenantServed = domains.filter((dm) => domainLifecycleState(dm, currentHashes) === "active").length;
4797
+ const tenantSuperseded = domains.filter((dm) => domainLifecycleState(dm, currentHashes) === "superseded").length;
4798
+ if (tenantActive > tenantServed || tenantSuperseded > 0) out10(` (${tenantServed} active, ${tenantSuperseded} superseded tenant install(s); superseded installs are replayable, not served)`);
4726
4799
  if (localHash === void 0) {
4727
4800
  out10(`local (no build/*.deploy.json here \u2014 run \`githolon compile\` to build the law this project authors)`);
4728
4801
  return 0;
@@ -4794,12 +4867,12 @@ deploy is OPEN + kernel-gated \u2014 births return no host secret, ownership is
4794
4867
  githolon ws reclaim <name> [--parent <ws>] lawful custody discard via a SIGNED governance offer
4795
4868
  (only a retired workspace; never root)
4796
4869
  githolon deploy [<ws>] [--file <deploy.json>] deploy build/*.deploy.json \u2014 DEFAULT replace-current:
4797
- [--keep-beside] [--retire-replaced] the new law SUPERSEDES the prior install per domain
4798
- key (prints what moved). --keep-beside installs
4799
- beside; --retire-replaced supersedes via Retired
4800
- githolon domains status <ws> per domain key: the current served law + phase +
4801
- [--explain-current-interface] superseded/retired lineage (the served frontier)
4802
- githolon domains retire <ws> <hashOrKey> retire an install (marks it Retired \u2014 replayable,
4870
+ [--keep-beside] [--retire-replaced] new law becomes active; prior install is superseded
4871
+ per domain key (prints what moved). --keep-beside
4872
+ installs beside; --retire-replaced marks retired
4873
+ githolon domains status <ws> per domain key: active law plus superseded/retired
4874
+ [--explain-current-interface] lineage (active = served now)
4875
+ githolon domains retire <ws> <hashOrKey> retire an install (marks it retired \u2014 replayable,
4803
4876
  [--with <hashOrKey>] no longer served) via a signed installDomain offer
4804
4877
  githolon secret set <ws> <push-password> store a git push password (push-to-create birth lane)
4805
4878
  githolon secret rotate <ws> RETIRED \u2014 host holds no ownership credential (410)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "githolon",
3
- "version": "0.46.0",
3
+ "version": "0.48.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.46.0",
33
- "@githolon/dsl": "^0.46.0",
32
+ "@githolon/client": "^0.48.0",
33
+ "@githolon/dsl": "^0.48.0",
34
34
  "isomorphic-git": "^1.38.4"
35
35
  },
36
36
  "devDependencies": {