githolon 0.31.1 → 0.32.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 +575 -211
  2. package/package.json +3 -3
package/dist/cli.mjs CHANGED
@@ -348,6 +348,282 @@ var init_governance = __esm({
348
348
  }
349
349
  });
350
350
 
351
+ // src/lifecycle.ts
352
+ var lifecycle_exports = {};
353
+ __export(lifecycle_exports, {
354
+ describeDeployPlan: () => describeDeployPlan,
355
+ domainKeysOf: () => domainKeysOf,
356
+ domainsRetire: () => domainsRetire,
357
+ domainsStatus: () => domainsStatus,
358
+ fetchInstalledLaw: () => fetchInstalledLaw,
359
+ fetchInstalledPackage: () => fetchInstalledPackage,
360
+ keysServedBy: () => keysServedBy,
361
+ packageHashOf: () => packageHashOf,
362
+ readDeployBody: () => readDeployBody,
363
+ resolveDeployPlan: () => resolveDeployPlan,
364
+ resolveInstallHash: () => resolveInstallHash,
365
+ signingAvailable: () => signingAvailable
366
+ });
367
+ import { createHash as createHash2 } from "node:crypto";
368
+ import { readFileSync as readFileSync2 } from "node:fs";
369
+ async function fetchInstalledLaw(cloud, ws) {
370
+ try {
371
+ const r = await fetch(`${cloud}/v2/workspaces/${ws}/domains`);
372
+ const d = await r.json().catch(() => void 0);
373
+ return d?.ok === true ? d : {};
374
+ } catch {
375
+ return {};
376
+ }
377
+ }
378
+ async function fetchInstalledPackage(cloud, ws, hash) {
379
+ try {
380
+ const r = await fetch(`${cloud}/v2/workspaces/${ws}/domains/${hash}/package.usda`);
381
+ if (!r.ok) return void 0;
382
+ const text = await r.text();
383
+ return text.length > 0 ? text : void 0;
384
+ } catch {
385
+ return void 0;
386
+ }
387
+ }
388
+ function readDeployBody(file) {
389
+ let body;
390
+ try {
391
+ body = JSON.parse(readFileSync2(file, "utf8"));
392
+ } catch (e) {
393
+ throw new Error(`deploy body ${file} is not valid JSON \u2014 recompile (githolon compile): ${e.message}`);
394
+ }
395
+ if (typeof body.packageUsda !== "string" || body.packageUsda === "") {
396
+ throw new Error(`deploy body ${file} is missing packageUsda \u2014 run \`githolon compile\` again`);
397
+ }
398
+ return body;
399
+ }
400
+ function domainKeysOf(body) {
401
+ return (body.summary?.domains ?? []).map((d) => d.domain).filter((k) => typeof k === "string" && k !== "");
402
+ }
403
+ function packageHashOf(packageUsda) {
404
+ return createHash2("sha256").update(Buffer.from(packageUsda, "utf8")).digest("hex");
405
+ }
406
+ function resolveDeployPlan(domainKeys, currentLaw, targetHash, opts = {}) {
407
+ const decisions = [];
408
+ const priors = /* @__PURE__ */ new Set();
409
+ for (const key of domainKeys) {
410
+ const currentHash = currentLaw[key];
411
+ if (currentHash === void 0) {
412
+ decisions.push({ key, action: "first" });
413
+ } else if (currentHash === targetHash) {
414
+ decisions.push({ key, currentHash, action: "idempotent" });
415
+ } else if (opts.keepBeside === true) {
416
+ decisions.push({ key, currentHash, action: "keep-beside" });
417
+ } else {
418
+ decisions.push({ key, currentHash, action: "supersedes" });
419
+ priors.add(currentHash);
420
+ }
421
+ }
422
+ const replaceCandidates = [...priors];
423
+ const idempotent = domainKeys.length > 0 && decisions.every((d) => d.action === "idempotent");
424
+ return {
425
+ decisions,
426
+ ...replaceCandidates.length > 0 ? { replaces: replaceCandidates[0] } : {},
427
+ retireReplaced: opts.retireReplaced === true,
428
+ idempotent,
429
+ replaceCandidates
430
+ };
431
+ }
432
+ function describeDeployPlan(plan, targetHash) {
433
+ const t = short(targetHash);
434
+ return plan.decisions.map((d) => {
435
+ switch (d.action) {
436
+ case "first":
437
+ return ` ${d.key}: ${t} installed (first)`;
438
+ case "supersedes":
439
+ return ` ${d.key}: ${t} supersedes ${short(d.currentHash)}${plan.retireReplaced ? " (retired)" : ""}`;
440
+ case "keep-beside":
441
+ return ` ${d.key}: ${t} kept beside ${short(d.currentHash)}`;
442
+ case "idempotent":
443
+ return ` ${d.key}: unchanged \u2014 ${t} is already the current served law`;
444
+ }
445
+ });
446
+ }
447
+ function resolveInstallHash(arg, currentLaw) {
448
+ if (/^[0-9a-f]{64}$/.test(arg)) return { hash: arg };
449
+ const hash = currentLaw[arg];
450
+ if (hash !== void 0) return { hash };
451
+ const keys = Object.keys(currentLaw);
452
+ 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)")
454
+ };
455
+ }
456
+ function keysServedBy(hash, currentLaw) {
457
+ return Object.keys(currentLaw).filter((k) => currentLaw[k] === hash);
458
+ }
459
+ async function domainsRetire(ws, target, opts) {
460
+ const cloud = cloudBase(opts.cloud);
461
+ const law = await fetchInstalledLaw(cloud, ws);
462
+ const currentLaw = law.currentLaw ?? {};
463
+ const resolved = resolveInstallHash(target, currentLaw);
464
+ if ("error" in resolved) {
465
+ err2(resolved.error);
466
+ return 1;
467
+ }
468
+ const retireHash = resolved.hash;
469
+ const installed = (law.domains ?? []).find((r) => r.domainHash === retireHash);
470
+ if (installed === void 0) {
471
+ err2(`no install '${short(retireHash)}' on ${ws} \u2014 GET /v2/workspaces/${ws}/domains lists the installed hashes`);
472
+ return 1;
473
+ }
474
+ let successorHash;
475
+ let successorUsda;
476
+ let successorSource;
477
+ if (opts.with !== void 0) {
478
+ const wResolved = resolveInstallHash(opts.with, currentLaw);
479
+ if ("error" in wResolved) {
480
+ err2(`--with ${wResolved.error}`);
481
+ return 1;
482
+ }
483
+ successorHash = wResolved.hash;
484
+ const bytes = await fetchInstalledPackage(cloud, ws, successorHash);
485
+ if (bytes === void 0) {
486
+ err2(`--with package '${short(successorHash)}' is not installed on ${ws} (its bytes could not be fetched)`);
487
+ return 1;
488
+ }
489
+ successorUsda = bytes;
490
+ successorSource = `installed package ${short(successorHash)}`;
491
+ } else {
492
+ let file;
493
+ try {
494
+ file = discoverDeployJson(process.cwd(), opts.file);
495
+ } catch (e) {
496
+ err2(`${e.message}
497
+ (or name an installed successor: githolon domains retire ${ws} ${target} --with <hashOrKey>)`);
498
+ return 1;
499
+ }
500
+ let body;
501
+ try {
502
+ body = readDeployBody(file);
503
+ } catch (e) {
504
+ err2(e.message);
505
+ return 1;
506
+ }
507
+ successorUsda = body.packageUsda;
508
+ successorHash = packageHashOf(successorUsda);
509
+ successorSource = `local package ${file.split("/").pop()} (${short(successorHash)})`;
510
+ }
511
+ if (successorHash === retireHash) {
512
+ err2(
513
+ `refusing to retire '${short(retireHash)}' with ITSELF as the successor \u2014 the framework has no standalone
514
+ retire-to-empty directive (retirement is the consequence of installing a different successor).
515
+ Name a different successor with --with <hashOrKey>, or deploy new law that supersedes it.`
516
+ );
517
+ return 1;
518
+ }
519
+ const retiredKeys = keysServedBy(retireHash, currentLaw);
520
+ const successorKeys = new Set(keysServedBy(successorHash, currentLaw));
521
+ const stranded = retiredKeys.filter((k) => !successorKeys.has(k));
522
+ const { signAndPostOffer: signAndPostOffer2, resolveGovernancePrincipal: resolveGovernancePrincipal2 } = await Promise.resolve().then(() => (init_governance(), governance_exports));
523
+ let v;
524
+ try {
525
+ const who = resolveGovernancePrincipal2(opts);
526
+ const installPayload2 = {
527
+ domainHash: successorHash,
528
+ packageUsda: successorUsda,
529
+ installedBy: `user:${who}`,
530
+ dependencies: [],
531
+ finalizers: [],
532
+ replaces: retireHash,
533
+ retireReplaced: true
534
+ };
535
+ v = await signAndPostOffer2(ws, "nomos", "installDomain", installPayload2, opts);
536
+ } catch (e) {
537
+ err2(e.message);
538
+ return 1;
539
+ }
540
+ if (!v.ok) {
541
+ err2(`retire '${short(retireHash)}' refused (${v.status}): ${typeof v.body === "string" ? v.body : JSON.stringify(v.body)}`);
542
+ if (v.status === 401 || v.status === 403 || v.status === 422) {
543
+ err2(` installing law is kernel-judged \u2014 retire as an authorized principal: --as <uid> (import its key: githolon key import)`);
544
+ }
545
+ return 1;
546
+ }
547
+ out2(`\u2713 retired ${short(retireHash)} on ${ws} (marked Retired \u2014 replayable, no longer served)`);
548
+ out2(` successor: ${successorSource}`);
549
+ const after = await fetchInstalledLaw(cloud, ws);
550
+ const afterLaw = after.currentLaw ?? {};
551
+ for (const k of retiredKeys) {
552
+ const now = afterLaw[k];
553
+ out2(` key '${k}' now serves: ${now !== void 0 ? short(now) : "NO served law"}`);
554
+ }
555
+ 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`);
557
+ err2(` (deploy law covering them, or this is intentional \u2014 the kernel allows an unserved key)`);
558
+ }
559
+ return 0;
560
+ }
561
+ async function domainsStatus(ws, opts) {
562
+ const cloud = cloudBase(opts.cloud);
563
+ const law = await fetchInstalledLaw(cloud, ws);
564
+ if (law.ok === false || law.domains === void 0 && law.currentLaw === void 0) {
565
+ err2(`no installed-law report for ${ws} on ${cloud} (unborn, or the workspace did not answer)`);
566
+ return 1;
567
+ }
568
+ const currentLaw = law.currentLaw ?? {};
569
+ const rows = law.domains ?? [];
570
+ const phaseOf = (hash) => rows.find((r) => r.domainHash === hash)?.phase ?? "(unknown)";
571
+ const keys = Object.keys(currentLaw).sort();
572
+ out2(`installed law on ${ws} (${cloud})`);
573
+ if (keys.length === 0) {
574
+ out2(` (no domain key resolves a served law \u2014 nothing is dispatched/read here)`);
575
+ } else {
576
+ out2(`current interface \u2014 the served law per domain key (the frontier):`);
577
+ for (const key of keys) {
578
+ const hash = currentLaw[key];
579
+ out2(` ${key} \u2192 ${short(hash)} [${phaseOf(hash)}]`);
580
+ }
581
+ }
582
+ const servedHashes = new Set(Object.values(currentLaw));
583
+ const behind = rows.filter((r) => typeof r.domainHash === "string" && !servedHashes.has(r.domainHash));
584
+ if (behind.length > 0) {
585
+ out2(`superseded / retired lineage (replayable, NEVER served):`);
586
+ for (const r of behind) {
587
+ out2(` ${short(r.domainHash)} [${r.phase ?? "(unknown)"}]${r.generation !== void 0 ? ` gen ${r.generation}` : ""}`);
588
+ }
589
+ } else if (rows.length > 0) {
590
+ out2(`no superseded/retired installs behind the frontier \u2014 every install is current`);
591
+ }
592
+ if (opts.explain === true) {
593
+ 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.`);
598
+ for (const key of keys) {
599
+ const hash = currentLaw[key];
600
+ out2(` \u2022 '${key}' serves ${short(hash)} \u2014 it is the newest ${SERVED_PHASE} install for this key.`);
601
+ }
602
+ 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 ?? "?"}]`);
604
+ out2(` \u2022 excluded from the interface (replayable, not served): ${excluded.join(", ")}`);
605
+ }
606
+ out2(` interface identity per key = the current served hash above; older installs are lineage, not surface.`);
607
+ }
608
+ return 0;
609
+ }
610
+ function signingAvailable(opts) {
611
+ const principal = opts.principal ?? activePrincipal();
612
+ return opts.principal !== void 0 || principal !== void 0 && getDeviceKey(principal) !== void 0;
613
+ }
614
+ var out2, err2, short, SERVED_PHASE, EXCLUDED_PHASES;
615
+ var init_lifecycle = __esm({
616
+ "src/lifecycle.ts"() {
617
+ "use strict";
618
+ init_cloud();
619
+ out2 = (s) => void process.stdout.write(s + "\n");
620
+ err2 = (s) => void process.stderr.write("error: " + s + "\n");
621
+ short = (h) => h.length > 16 ? h.slice(0, 16) + "\u2026" : h;
622
+ SERVED_PHASE = "Active";
623
+ EXCLUDED_PHASES = /* @__PURE__ */ new Set(["Superseded", "Retired", "Disabled", "Failed", "Pending", "Retiring"]);
624
+ }
625
+ });
626
+
351
627
  // src/cloud.ts
352
628
  var cloud_exports = {};
353
629
  __export(cloud_exports, {
@@ -379,8 +655,8 @@ __export(cloud_exports, {
379
655
  wsRetire: () => wsRetire,
380
656
  wsStatus: () => wsStatus
381
657
  });
382
- import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "node:fs";
383
- import { createHash as createHash2 } from "node:crypto";
658
+ import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
659
+ import { createHash as createHash3 } from "node:crypto";
384
660
  import { join as join2 } from "node:path";
385
661
  import { homedir } from "node:os";
386
662
  function cloudBase(flag) {
@@ -400,7 +676,7 @@ function credsPath() {
400
676
  }
401
677
  function loadCreds() {
402
678
  if (!existsSync2(credsPath())) return { version: 1, secrets: {} };
403
- return JSON.parse(readFileSync2(credsPath(), "utf8"));
679
+ return JSON.parse(readFileSync3(credsPath(), "utf8"));
404
680
  }
405
681
  function saveCreds(c) {
406
682
  mkdirSync2(configDir(), { recursive: true });
@@ -440,24 +716,24 @@ function clearActAs() {
440
716
  async function authUse(principal, opts = {}) {
441
717
  if (opts.clear === true) {
442
718
  clearActAs();
443
- out2("\u2713 cleared the active signing principal \u2014 signed lanes fall back to the session / bare default");
719
+ out3("\u2713 cleared the active signing principal \u2014 signed lanes fall back to the session / bare default");
444
720
  return 0;
445
721
  }
446
722
  if (principal === void 0) {
447
723
  const c = loadCreds();
448
724
  const active = c.actAs;
449
- out2(active !== void 0 ? `acting as ${active}${getDeviceKey(active) ? " (key on file \u2713)" : " \u26A0 no signing key \u2014 githolon key import --principal " + active}` : "no active signing principal set (githolon use <uid>) \u2014 signed lanes use the session / bare default");
725
+ out3(active !== void 0 ? `acting as ${active}${getDeviceKey(active) ? " (key on file \u2713)" : " \u26A0 no signing key \u2014 githolon key import --principal " + active}` : "no active signing principal set (githolon use <uid>) \u2014 signed lanes use the session / bare default");
450
726
  const keyed = Object.keys(c.devices ?? {});
451
727
  if (keyed.length > 0) {
452
- out2(`keys on file:`);
453
- for (const p of keyed) out2(` ${p}${p === active ? " \u2190 active" : ""}`);
728
+ out3(`keys on file:`);
729
+ for (const p of keyed) out3(` ${p}${p === active ? " \u2190 active" : ""}`);
454
730
  }
455
731
  return 0;
456
732
  }
457
733
  setActAs(principal);
458
- out2(`\u2713 now acting as ${principal} (signed deploy/gov/grant/key author as this principal)`);
734
+ out3(`\u2713 now acting as ${principal} (signed deploy/gov/grant/key author as this principal)`);
459
735
  if (getDeviceKey(principal) === void 0) {
460
- out2(` \u26A0 no signing key on file for ${principal} yet \u2014 import it: githolon key import --principal ${principal} --secret <key>`);
736
+ out3(` \u26A0 no signing key on file for ${principal} yet \u2014 import it: githolon key import --principal ${principal} --secret <key>`);
461
737
  }
462
738
  return 0;
463
739
  }
@@ -530,7 +806,7 @@ function discoverDeployJson(cwd, explicit) {
530
806
  }
531
807
  async function login(opts) {
532
808
  if (opts.agent !== true && opts.token === void 0) {
533
- err2(
809
+ err3(
534
810
  "browser login is not wired up yet \u2014 use one of:\n githolon login --agent anonymous sign-in (verified identity, linkable later)\n githolon login --token <refresh> adopt an existing captain app session"
535
811
  );
536
812
  return 1;
@@ -538,33 +814,33 @@ async function login(opts) {
538
814
  const d = opts.agent === true ? await gotrue("/auth/v1/signup", "{}") : await gotrue("/auth/v1/token?grant_type=refresh_token", JSON.stringify({ refresh_token: opts.token }));
539
815
  const s = sessionFromGotrue(d);
540
816
  if (s === void 0) {
541
- err2(`login failed: ${d.error_description ?? d.error ?? d.msg ?? JSON.stringify(d)}`);
817
+ err3(`login failed: ${d.error_description ?? d.error ?? d.msg ?? JSON.stringify(d)}`);
542
818
  return 1;
543
819
  }
544
820
  saveSession(s);
545
- out2(`\u2713 logged in${s.anonymous ? " (anonymous agent identity \u2014 linkable to a full account later)" : ""}`);
546
- out2(` principal uid ${s.uid}`);
547
- out2(` session saved \u2192 ${credsPath()} (auto-refreshed; births now ride x-nomos-auth)`);
821
+ out3(`\u2713 logged in${s.anonymous ? " (anonymous agent identity \u2014 linkable to a full account later)" : ""}`);
822
+ out3(` principal uid ${s.uid}`);
823
+ out3(` session saved \u2192 ${credsPath()} (auto-refreshed; births now ride x-nomos-auth)`);
548
824
  return 0;
549
825
  }
550
826
  async function whoami() {
551
827
  const c = loadCreds();
552
828
  if (c.session !== void 0) {
553
- out2(`session: ${c.session.anonymous ? "anonymous agent" : "account"} uid ${c.session.uid} (auth ${c.session.url})`);
829
+ out3(`session: ${c.session.anonymous ? "anonymous agent" : "account"} uid ${c.session.uid} (auth ${c.session.url})`);
554
830
  } else if (c.principal !== void 0) {
555
- out2(`bare principal (transitional, unverified): ${c.principal}`);
831
+ out3(`bare principal (transitional, unverified): ${c.principal}`);
556
832
  } else {
557
- out2("not logged in \u2014 githolon login --agent, or pass --principal <uid> per birth");
833
+ out3("not logged in \u2014 githolon login --agent, or pass --principal <uid> per birth");
558
834
  }
559
835
  const active = activePrincipal();
560
836
  if (c.actAs !== void 0) {
561
- out2(`acting as: ${c.actAs}${getDeviceKey(c.actAs) ? " (key on file \u2713)" : " \u26A0 no signing key"} [githolon use <uid> to switch]`);
837
+ out3(`acting as: ${c.actAs}${getDeviceKey(c.actAs) ? " (key on file \u2713)" : " \u26A0 no signing key"} [githolon use <uid> to switch]`);
562
838
  } else if (active !== void 0) {
563
- out2(`signed lanes act as: ${active} (the session/bare default \u2014 githolon use <uid> to switch)`);
839
+ out3(`signed lanes act as: ${active} (the session/bare default \u2014 githolon use <uid> to switch)`);
564
840
  }
565
841
  const keyed = Object.keys(c.devices ?? {});
566
842
  if (keyed.length > 0) {
567
- out2(`signing keys on file: ${keyed.map((p) => p === active ? `${p} \u2190active` : p).join(", ")}`);
843
+ out3(`signing keys on file: ${keyed.map((p) => p === active ? `${p} \u2190active` : p).join(", ")}`);
568
844
  }
569
845
  return 0;
570
846
  }
@@ -572,7 +848,7 @@ async function logout() {
572
848
  const c = loadCreds();
573
849
  delete c.session;
574
850
  saveCreds(c);
575
- out2("\u2713 session cleared (stored workspace secrets kept)");
851
+ out3("\u2713 session cleared (stored workspace secrets kept)");
576
852
  return 0;
577
853
  }
578
854
  async function wsCreate(name, opts) {
@@ -589,24 +865,24 @@ async function wsCreate(name, opts) {
589
865
  opts
590
866
  );
591
867
  } catch (e) {
592
- err2(e.message);
868
+ err3(e.message);
593
869
  return 1;
594
870
  }
595
871
  if (!v.ok) {
596
- err2(`createWorkspace '${name}' refused (${v.status}) at ${opts.via}: ${typeof v.body === "string" ? v.body : JSON.stringify(v.body)}`);
872
+ err3(`createWorkspace '${name}' refused (${v.status}) at ${opts.via}: ${typeof v.body === "string" ? v.body : JSON.stringify(v.body)}`);
597
873
  return 1;
598
874
  }
599
- out2(`\u2713 createWorkspace '${name}' requested via signed governance offer to ${opts.via} on ${cloud}`);
600
- out2(` the worker provisions it on first touch; deploy is open (kernel-gated)`);
601
- out2(` (want the direct birth instead? drop --via: githolon ws create ${name})`);
875
+ out3(`\u2713 createWorkspace '${name}' requested via signed governance offer to ${opts.via} on ${cloud}`);
876
+ out3(` the worker provisions it on first touch; deploy is open (kernel-gated)`);
877
+ out3(` (want the direct birth instead? drop --via: githolon ws create ${name})`);
602
878
  return 0;
603
879
  }
604
- let principal = opts.principal ?? await sessionToken().catch((e) => (err2(e.message), void 0)) ?? loadCreds().principal;
880
+ let principal = opts.principal ?? await sessionToken().catch((e) => (err3(e.message), void 0)) ?? loadCreds().principal;
605
881
  if (principal === void 0) {
606
882
  const d2 = await gotrue("/auth/v1/signup", "{}");
607
883
  const s = sessionFromGotrue(d2);
608
884
  if (s === void 0) {
609
- err2(
885
+ err3(
610
886
  `a birth needs a principal, and automatic agent login failed: ${d2.error_description ?? d2.error ?? d2.msg ?? JSON.stringify(d2)}
611
887
  githolon login --agent (self-onboarding), githolon login --token <refresh>,
612
888
  or pass --principal <your-auth-uid or access token> (saved as the default)`
@@ -614,9 +890,9 @@ async function wsCreate(name, opts) {
614
890
  return 1;
615
891
  }
616
892
  saveSession(s);
617
- out2(`no principal on file \u2014 self-onboarded an anonymous agent identity (githolon login --agent)`);
618
- out2(` principal uid ${s.uid} (linkable to a full account later)`);
619
- out2(` session saved \u2192 ${credsPath()} (auto-refreshed; births ride x-nomos-auth)`);
893
+ out3(`no principal on file \u2014 self-onboarded an anonymous agent identity (githolon login --agent)`);
894
+ out3(` principal uid ${s.uid} (linkable to a full account later)`);
895
+ out3(` session saved \u2192 ${credsPath()} (auto-refreshed; births ride x-nomos-auth)`);
620
896
  principal = s.accessToken;
621
897
  }
622
898
  const r = await fetch(`${cloud}/v2/workspaces/${name}`, {
@@ -625,12 +901,12 @@ async function wsCreate(name, opts) {
625
901
  });
626
902
  const d = await r.json().catch(() => void 0);
627
903
  if (!r.ok || d?.ok !== true) {
628
- err2(`create '${name}' refused (${r.status}): ${d ? JSON.stringify(d) : "no body"}`);
904
+ err3(`create '${name}' refused (${r.status}): ${d ? JSON.stringify(d) : "no body"}`);
629
905
  return 1;
630
906
  }
631
907
  if (opts.principal !== void 0) setPrincipal(opts.principal);
632
- out2(`\u2713 workspace ${name} created on ${cloud}`);
633
- out2(` deploy is open (kernel-gated): githolon deploy ${name}`);
908
+ out3(`\u2713 workspace ${name} created on ${cloud}`);
909
+ out3(` deploy is open (kernel-gated): githolon deploy ${name}`);
634
910
  return 0;
635
911
  }
636
912
  async function wsStatus(name, opts) {
@@ -638,10 +914,10 @@ async function wsStatus(name, opts) {
638
914
  const r = await fetch(`${cloud}/v2/workspaces/${name}`);
639
915
  const text = await r.text();
640
916
  if (!r.ok) {
641
- err2(`status '${name}' (${r.status}): ${text}`);
917
+ err3(`status '${name}' (${r.status}): ${text}`);
642
918
  return 1;
643
919
  }
644
- out2(text);
920
+ out3(text);
645
921
  return 0;
646
922
  }
647
923
  async function wsRetire(ws, opts) {
@@ -657,16 +933,16 @@ async function wsRetire(ws, opts) {
657
933
  v = await signAndPostGovernance2(parent, "retireWorkspace", payload, opts);
658
934
  }
659
935
  } catch (e) {
660
- err2(e.message);
936
+ err3(e.message);
661
937
  return 1;
662
938
  }
663
939
  if (!v.ok) {
664
- err2(`retire '${ws}' refused (${v.status}): ${typeof v.body === "string" ? v.body : JSON.stringify(v.body)}`);
940
+ err3(`retire '${ws}' refused (${v.status}): ${typeof v.body === "string" ? v.body : JSON.stringify(v.body)}`);
665
941
  return 1;
666
942
  }
667
- out2(`\u2713 workspace ${ws} retired via signed governance offer to ${parent} on ${cloud}`);
668
- out2(" nothing was deleted \u2014 the ledger and its data are retained per the Nomos Pre-Release License");
669
- out2(" it no longer counts toward your workspace allowance; reads keep serving, deploys/pushes refuse");
943
+ out3(`\u2713 workspace ${ws} retired via signed governance offer to ${parent} on ${cloud}`);
944
+ out3(" nothing was deleted \u2014 the ledger and its data are retained per the Nomos Pre-Release License");
945
+ out3(" it no longer counts toward your workspace allowance; reads keep serving, deploys/pushes refuse");
670
946
  return 0;
671
947
  }
672
948
  async function wsReclaim(ws, opts) {
@@ -683,15 +959,15 @@ async function wsReclaim(ws, opts) {
683
959
  opts
684
960
  );
685
961
  } catch (e) {
686
- err2(e.message);
962
+ err3(e.message);
687
963
  return 1;
688
964
  }
689
965
  if (!v.ok) {
690
- err2(`reclaim '${ws}' refused (${v.status}): ${typeof v.body === "string" ? v.body : JSON.stringify(v.body)}`);
966
+ err3(`reclaim '${ws}' refused (${v.status}): ${typeof v.body === "string" ? v.body : JSON.stringify(v.body)}`);
691
967
  return 1;
692
968
  }
693
- out2(`\u2713 workspace ${ws} reclaimed via signed governance offer to ${parent} on ${cloud}`);
694
- out2(" the custodian drops the ledger bytes on seeing this fact folded; the reclaimed FACT is append-only");
969
+ out3(`\u2713 workspace ${ws} reclaimed via signed governance offer to ${parent} on ${cloud}`);
970
+ out3(" the custodian drops the ledger bytes on seeing this fact folded; the reclaimed FACT is append-only");
695
971
  return 0;
696
972
  }
697
973
  async function deploy(ws, opts) {
@@ -700,11 +976,27 @@ async function deploy(ws, opts) {
700
976
  try {
701
977
  file = discoverDeployJson(process.cwd(), opts.file);
702
978
  } catch (e) {
703
- err2(e.message);
979
+ err3(e.message);
704
980
  return 1;
705
981
  }
706
982
  const fileName = file.split("/").pop();
707
- const before = await fetch(`${cloud}/v2/workspaces/${ws}/domains`).then((br) => br.json()).then((bd) => bd.ok === true ? bd.domains ?? [] : []).catch(() => []);
983
+ const beforeLaw = await fetch(`${cloud}/v2/workspaces/${ws}/domains`).then((br) => br.json()).then((bd) => bd.ok === true ? bd : {}).catch(() => ({}));
984
+ const before = beforeLaw.domains ?? [];
985
+ const currentLaw = beforeLaw.currentLaw ?? {};
986
+ const { readDeployBody: readDeployBody3, domainKeysOf: domainKeysOf2, packageHashOf: packageHashOf2, resolveDeployPlan: resolveDeployPlan2, describeDeployPlan: describeDeployPlan2 } = await Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports));
987
+ let plan;
988
+ let targetHash;
989
+ try {
990
+ const body = readDeployBody3(file);
991
+ targetHash = packageHashOf2(body.packageUsda);
992
+ plan = resolveDeployPlan2(domainKeysOf2(body), currentLaw, targetHash, {
993
+ keepBeside: opts.keepBeside === true,
994
+ retireReplaced: opts.retireReplaced === true
995
+ });
996
+ } catch {
997
+ plan = void 0;
998
+ targetHash = void 0;
999
+ }
708
1000
  const principal = opts.principal ?? activePrincipal();
709
1001
  const signed = opts.principal !== void 0 || principal !== void 0 && getDeviceKey(principal) !== void 0;
710
1002
  let phaseSuffix = "";
@@ -712,52 +1004,54 @@ async function deploy(ws, opts) {
712
1004
  if (signed) {
713
1005
  let body;
714
1006
  try {
715
- body = JSON.parse(readFileSync2(file, "utf8"));
1007
+ body = JSON.parse(readFileSync3(file, "utf8"));
716
1008
  } catch (e) {
717
- err2(`deploy body ${file} is not valid JSON \u2014 recompile: ${e.message}`);
1009
+ err3(`deploy body ${file} is not valid JSON \u2014 recompile: ${e.message}`);
718
1010
  return 1;
719
1011
  }
720
1012
  const packageUsda = body.packageUsda;
721
1013
  if (typeof packageUsda !== "string" || packageUsda === "") {
722
- err2(`deploy body ${file} is missing packageUsda \u2014 run \`githolon compile\` again`);
1014
+ err3(`deploy body ${file} is missing packageUsda \u2014 run \`githolon compile\` again`);
723
1015
  return 1;
724
1016
  }
725
- const targetHash = createHash2("sha256").update(Buffer.from(packageUsda, "utf8")).digest("hex");
1017
+ const targetHash2 = createHash3("sha256").update(Buffer.from(packageUsda, "utf8")).digest("hex");
726
1018
  const { signAndPostOffer: signAndPostOffer2, resolveGovernancePrincipal: resolveGovernancePrincipal2 } = await Promise.resolve().then(() => (init_governance(), governance_exports));
727
1019
  let v;
728
1020
  try {
729
1021
  const who = resolveGovernancePrincipal2(opts);
730
1022
  const installPayload2 = {
731
- domainHash: targetHash,
1023
+ domainHash: targetHash2,
732
1024
  packageUsda,
733
1025
  installedBy: `user:${who}`,
734
1026
  dependencies: [],
735
1027
  finalizers: [],
1028
+ // REPLACE-CURRENT: supersede the prior install per key (unless --keep-beside / first-install / idempotent).
1029
+ ...plan?.replaces !== void 0 ? { replaces: plan.replaces, retireReplaced: plan.retireReplaced } : {},
736
1030
  ...body.dispositions !== void 0 && body.dispositions !== null ? { dispositions: body.dispositions } : {}
737
1031
  };
738
1032
  v = await signAndPostOffer2(ws, "nomos", "installDomain", installPayload2, opts);
739
1033
  } catch (e) {
740
- err2(e.message);
1034
+ err3(e.message);
741
1035
  return 1;
742
1036
  }
743
1037
  if (!v.ok) {
744
- err2(`signed deploy '${ws}' refused (${v.status}): ${typeof v.body === "string" ? v.body : JSON.stringify(v.body)}`);
1038
+ err3(`signed deploy '${ws}' refused (${v.status}): ${typeof v.body === "string" ? v.body : JSON.stringify(v.body)}`);
745
1039
  return 1;
746
1040
  }
747
- deployedHash = targetHash;
1041
+ deployedHash = targetHash2;
748
1042
  } else {
749
1043
  const r = await fetch(`${cloud}/v2/workspaces/${ws}/domains`, {
750
1044
  method: "POST",
751
1045
  headers: { "content-type": "application/json" },
752
- body: readFileSync2(file, "utf8")
1046
+ body: readFileSync3(file, "utf8")
753
1047
  });
754
1048
  const d = await r.json().catch(() => void 0);
755
1049
  if (!r.ok || d?.ok !== true) {
756
1050
  const txt = d ? JSON.stringify(d) : "no body";
757
- err2(`deploy '${ws}' failed (${r.status}): ${txt}`);
1051
+ err3(`deploy '${ws}' failed (${r.status}): ${txt}`);
758
1052
  if (r.status === 401 || r.status === 403 || r.status === 422 || /warrant|signed|signer|unsigned|authorship|forged|relation|installDomain/i.test(txt)) {
759
- err2(` this workspace looks WARRANTED \u2014 re-run with --as <principal> to author a SIGNED install offer`);
760
- err2(` (import the admin signing key first if needed: githolon key import --principal <uid> --secret <key>)`);
1053
+ err3(` this workspace looks WARRANTED \u2014 re-run with --as <principal> to author a SIGNED install offer`);
1054
+ err3(` (import the admin signing key first if needed: githolon key import --principal <uid> --secret <key>)`);
761
1055
  }
762
1056
  return 1;
763
1057
  }
@@ -769,24 +1063,36 @@ async function deploy(ws, opts) {
769
1063
  const currentHashes = new Set(Object.values(after.currentLaw ?? {}));
770
1064
  const hasCurrencyData = Object.keys(after.currentLaw ?? {}).length > 0;
771
1065
  const isCurrent = deployedHash !== void 0 && (currentHashes.has(deployedHash) || (after.domains ?? []).some((x) => x.domainHash === deployedHash && x.current === true));
1066
+ const emitPlan = () => {
1067
+ if (plan === void 0 || targetHash === void 0 || plan.decisions.length === 0) return;
1068
+ for (const line of describeDeployPlan2(plan, targetHash)) out3(line);
1069
+ if (plan.replaceCandidates.length > 1) {
1070
+ 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.`);
1071
+ }
1072
+ if (!signed && plan.replaces !== void 0 && !opts.keepBeside) {
1073
+ 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.`);
1074
+ }
1075
+ };
772
1076
  if (deployedHash !== void 0 && isCurrent) {
773
- out2(`\u2713 deployed ${fileName} \u2192 ${ws}${phaseSuffix}`);
774
- out2(` law ${describeLawMove(before, deployedHash)}`);
775
- out2(` \u2713 current \u2014 dispatch + reads now resolve ${deployedHash.slice(0, 16)}\u2026`);
1077
+ out3(`\u2713 deployed ${fileName} \u2192 ${ws}${phaseSuffix}`);
1078
+ out3(` law ${describeLawMove(before, deployedHash)}`);
1079
+ emitPlan();
1080
+ out3(` \u2713 current \u2014 dispatch + reads now resolve ${deployedHash.slice(0, 16)}\u2026`);
776
1081
  return 0;
777
1082
  }
778
1083
  if (deployedHash === void 0 || !hasCurrencyData) {
779
- out2(`\u2713 deployed ${fileName} \u2192 ${ws}${phaseSuffix}`);
780
- if (deployedHash !== void 0) out2(` law ${describeLawMove(before, deployedHash)}`);
781
- out2(` \u26A0 couldn't confirm it is the current serving law (this workspace did not report current-law resolution).`);
1084
+ out3(`\u2713 deployed ${fileName} \u2192 ${ws}${phaseSuffix}`);
1085
+ if (deployedHash !== void 0) out3(` law ${describeLawMove(before, deployedHash)}`);
1086
+ emitPlan();
1087
+ out3(` \u26A0 couldn't confirm it is the current serving law (this workspace did not report current-law resolution).`);
782
1088
  return 0;
783
1089
  }
784
- err2(`deployed ${fileName} \u2192 ${ws}${phaseSuffix}, but it is NOT the current serving law \u2014 the upgrade did not take effect`);
785
- out2(` law ${describeLawMove(before, deployedHash)} (committed as an install, but the holon does not resolve it as current)`);
1090
+ err3(`deployed ${fileName} \u2192 ${ws}${phaseSuffix}, but it is NOT the current serving law \u2014 the upgrade did not take effect`);
1091
+ out3(` law ${describeLawMove(before, deployedHash)} (committed as an install, but the holon does not resolve it as current)`);
786
1092
  const leads = (after.domains ?? []).filter((x) => x.current === true && typeof x.domainHash === "string").map((x) => x.domainHash);
787
- if (leads.length > 0) err2(` current serving: ${leads.map((h) => h.slice(0, 16) + "\u2026").join(", ")}`);
788
- else err2(` the workspace resolves NO current law for this domain (materialisation wedged \u2014 the evolve gate did not advance it)`);
789
- err2(` re-run \`githolon deploy ${ws}\`; if it persists, the deploy committed but the workspace kept the old law (report it).`);
1093
+ if (leads.length > 0) err3(` current serving: ${leads.map((h) => h.slice(0, 16) + "\u2026").join(", ")}`);
1094
+ else err3(` the workspace resolves NO current law for this domain (materialisation wedged \u2014 the evolve gate did not advance it)`);
1095
+ err3(` re-run \`githolon deploy ${ws}\`; if it persists, the deploy committed but the workspace kept the old law (report it).`);
790
1096
  return 2;
791
1097
  }
792
1098
  function describeLawMove(before, newHash) {
@@ -797,19 +1103,19 @@ function describeLawMove(before, newHash) {
797
1103
  }
798
1104
  async function secretSet(ws, secret, opts) {
799
1105
  setSecret(cloudBase(opts.cloud), ws, secret);
800
- out2(`\u2713 git push password for ${ws} saved \u2192 ${credsPath()} (used by the push-to-create birth lane)`);
1106
+ out3(`\u2713 git push password for ${ws} saved \u2192 ${credsPath()} (used by the push-to-create birth lane)`);
801
1107
  return 0;
802
1108
  }
803
1109
  async function secretRotate(ws, opts) {
804
1110
  const cloud = cloudBase(opts.cloud);
805
- err2(
1111
+ err3(
806
1112
  `secret rotate is retired (host-authority strip) \u2014 there is no host-held workspace secret on ${cloud}
807
1113
  ownership is sha256(push password) bound in the chain; re-own by re-birthing via the upload lane
808
1114
  with a new push password (see: githolon git remote ${ws} && git push nomos main)`
809
1115
  );
810
1116
  return 1;
811
1117
  }
812
- var DEFAULT_CLOUD, DEFAULT_AUTH_URL, DEFAULT_AUTH_ANON_KEY, secretKey, out2, err2;
1118
+ var DEFAULT_CLOUD, DEFAULT_AUTH_URL, DEFAULT_AUTH_ANON_KEY, secretKey, out3, err3;
813
1119
  var init_cloud = __esm({
814
1120
  "src/cloud.ts"() {
815
1121
  "use strict";
@@ -817,8 +1123,8 @@ var init_cloud = __esm({
817
1123
  DEFAULT_AUTH_URL = "https://kjbcjkihxskuwwfdqklt.supabase.co";
818
1124
  DEFAULT_AUTH_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtqYmNqa2loeHNrdXd3ZmRxa2x0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTA3MDU2OTAsImV4cCI6MjA2NjI4MTY5MH0.V9e7XsuTlTOLqefOIedTqlBiTxUSn4O5FZSPWwAxiSI";
819
1125
  secretKey = (cloud, ws) => `${cloud} ${ws}`;
820
- out2 = (s) => void process.stdout.write(s + "\n");
821
- err2 = (s) => void process.stderr.write("error: " + s + "\n");
1126
+ out3 = (s) => void process.stdout.write(s + "\n");
1127
+ err3 = (s) => void process.stderr.write("error: " + s + "\n");
822
1128
  }
823
1129
  });
824
1130
 
@@ -939,13 +1245,13 @@ var init_tree = __esm({
939
1245
  function concatBytes(chunks) {
940
1246
  let n = 0;
941
1247
  for (const c of chunks) n += c.length;
942
- const out10 = new Uint8Array(n);
1248
+ const out11 = new Uint8Array(n);
943
1249
  let o = 0;
944
1250
  for (const c of chunks) {
945
- out10.set(c, o);
1251
+ out11.set(c, o);
946
1252
  o += c.length;
947
1253
  }
948
- return out10;
1254
+ return out11;
949
1255
  }
950
1256
  function pkt(payload) {
951
1257
  const body = typeof payload === "string" ? enc2.encode(payload) : payload;
@@ -1098,13 +1404,13 @@ async function sha256hex(t) {
1098
1404
  function osEntropyBuffer(n) {
1099
1405
  const bytes = new Uint8Array(n * 8);
1100
1406
  for (let o = 0; o < bytes.length; o += 65536) crypto.getRandomValues(bytes.subarray(o, Math.min(o + 65536, bytes.length)));
1101
- const out10 = new Array(n), T = 2 ** 53;
1407
+ const out11 = new Array(n), T = 2 ** 53;
1102
1408
  for (let i = 0; i < n; i++) {
1103
1409
  let z = 0n;
1104
1410
  for (let b = 7; b >= 0; b--) z = z << 8n | BigInt(bytes[i * 8 + b]);
1105
- out10[i] = Number(z >> 11n) / T;
1411
+ out11[i] = Number(z >> 11n) / T;
1106
1412
  }
1107
- return out10;
1413
+ return out11;
1108
1414
  }
1109
1415
  function stringifyBig(o) {
1110
1416
  return JSON.stringify(o, (_k, v) => typeof v === "bigint" ? `@@B:${v}@@` : v).replace(/"@@B:(\d+)@@"/g, "$1");
@@ -1328,7 +1634,7 @@ var init_engine = __esm({
1328
1634
 
1329
1635
  // src/local_holon.ts
1330
1636
  import { randomBytes } from "node:crypto";
1331
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync4 } from "node:fs";
1637
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync, writeFileSync as writeFileSync4 } from "node:fs";
1332
1638
  import { dirname as dirname2, join as join4 } from "node:path";
1333
1639
  import { File as File2, Directory as Directory2 } from "@bjorn3/browser_wasi_shim";
1334
1640
  async function fetchJsonCached(url, cacheFile) {
@@ -1340,7 +1646,7 @@ async function fetchJsonCached(url, cacheFile) {
1340
1646
  writeFileSync4(cacheFile, text, "utf8");
1341
1647
  return JSON.parse(text);
1342
1648
  } catch (e) {
1343
- if (existsSync3(cacheFile)) return JSON.parse(readFileSync3(cacheFile, "utf8"));
1649
+ if (existsSync3(cacheFile)) return JSON.parse(readFileSync4(cacheFile, "utf8"));
1344
1650
  throw e;
1345
1651
  }
1346
1652
  }
@@ -1350,7 +1656,7 @@ async function fetchRuntime(cloud) {
1350
1656
  const wasmCache = join4(cache, "holon.wasm");
1351
1657
  const localWasm = process.env["NOMOS_LOCAL_WASM"];
1352
1658
  if (localWasm) {
1353
- const bytes = readFileSync3(localWasm);
1659
+ const bytes = readFileSync4(localWasm);
1354
1660
  const packages2 = await fetchJsonCached(`${cloud}/v1/runtime/packages`, join4(cache, "packages.json"));
1355
1661
  return { wasmBytes: bytes, packages: packages2 };
1356
1662
  }
@@ -1362,7 +1668,7 @@ async function fetchRuntime(cloud) {
1362
1668
  writeFileSync4(wasmCache, wasmBytes);
1363
1669
  } catch (e) {
1364
1670
  if (!existsSync3(wasmCache)) throw e;
1365
- wasmBytes = readFileSync3(wasmCache);
1671
+ wasmBytes = readFileSync4(wasmCache);
1366
1672
  }
1367
1673
  const packages = await fetchJsonCached(`${cloud}/v1/runtime/packages`, join4(cache, "packages.json"));
1368
1674
  return { wasmBytes, packages };
@@ -1380,7 +1686,7 @@ function readTreeFromDisk(diskPath) {
1380
1686
  for (const name of readdirSync2(diskPath)) {
1381
1687
  const p = join4(diskPath, name);
1382
1688
  if (statSync(p).isDirectory()) contents.set(name, readTreeFromDisk(p));
1383
- else contents.set(name, new File2(readFileSync3(p)));
1689
+ else contents.set(name, new File2(readFileSync4(p)));
1384
1690
  }
1385
1691
  return new Directory2(contents);
1386
1692
  }
@@ -1411,21 +1717,21 @@ function sealedIntentOf(eng, ws, res) {
1411
1717
  }
1412
1718
  function printVerdict(v) {
1413
1719
  if (v["valid"] === true) {
1414
- out3(`\u2713 chain VALID \u2014 head ${String(v["head"]).slice(0, 12)}\u2026, ${v["intents"]} intent(s), ${v["plansRerun"]} plan(s) re-run`);
1415
- out3(` controller ${String(v["controllerHash"]).slice(0, 12)}\u2026 | installed: ${v["installedDomains"]?.map((h) => h.slice(0, 12) + "\u2026").join(", ") ?? "\u2014"}`);
1720
+ out4(`\u2713 chain VALID \u2014 head ${String(v["head"]).slice(0, 12)}\u2026, ${v["intents"]} intent(s), ${v["plansRerun"]} plan(s) re-run`);
1721
+ out4(` controller ${String(v["controllerHash"]).slice(0, 12)}\u2026 | installed: ${v["installedDomains"]?.map((h) => h.slice(0, 12) + "\u2026").join(", ") ?? "\u2014"}`);
1416
1722
  return true;
1417
1723
  }
1418
- err3(`chain INVALID at ${v["check"]}${v["atIndex"] !== void 0 ? ` (intent ${v["atIndex"]})` : ""}: ${v["error"]}`);
1724
+ err4(`chain INVALID at ${v["check"]}${v["atIndex"] !== void 0 ? ` (intent ${v["atIndex"]})` : ""}: ${v["error"]}`);
1419
1725
  return false;
1420
1726
  }
1421
- var out3, err3;
1727
+ var out4, err4;
1422
1728
  var init_local_holon = __esm({
1423
1729
  "src/local_holon.ts"() {
1424
1730
  "use strict";
1425
1731
  init_engine();
1426
1732
  init_cloud();
1427
- out3 = (s) => void process.stdout.write(s + "\n");
1428
- err3 = (s) => void process.stderr.write("error: " + s + "\n");
1733
+ out4 = (s) => void process.stdout.write(s + "\n");
1734
+ err4 = (s) => void process.stderr.write("error: " + s + "\n");
1429
1735
  }
1430
1736
  });
1431
1737
 
@@ -1438,7 +1744,7 @@ __export(proof_offline_exports, {
1438
1744
  runOfflineProofStandalone: () => runOfflineProofStandalone,
1439
1745
  runRelationBootstrapLeg: () => runRelationBootstrapLeg
1440
1746
  });
1441
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
1747
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
1442
1748
  import { basename, dirname as dirname3, join as join6 } from "node:path";
1443
1749
  function parseOfflineLegs(proofSource) {
1444
1750
  if (!proofSource.includes("AUTO-GENERATED by nomos-compile")) {
@@ -1752,7 +2058,7 @@ function actorFromPayload(payload, field) {
1752
2058
  return typeof value === "string" && value.trim().length > 0 ? value : void 0;
1753
2059
  }
1754
2060
  async function runOfflineProofStandalone(proofPath, cloud, domain) {
1755
- const src = readFileSync4(proofPath, "utf8");
2061
+ const src = readFileSync5(proofPath, "utf8");
1756
2062
  const packageName = basename(proofPath).replace(/\.proof\.mts$/, "");
1757
2063
  const deployFile = join6(dirname3(proofPath), `${packageName}.deploy.json`);
1758
2064
  if (!existsSync5(deployFile)) {
@@ -1761,7 +2067,7 @@ async function runOfflineProofStandalone(proofPath, cloud, domain) {
1761
2067
  let legs;
1762
2068
  const legsFile = join6(dirname3(proofPath), `${packageName}.proof-legs.json`);
1763
2069
  if (existsSync5(legsFile)) {
1764
- const index = JSON.parse(readFileSync4(legsFile, "utf8"));
2070
+ const index = JSON.parse(readFileSync5(legsFile, "utf8"));
1765
2071
  const keys = Object.keys(index.domains);
1766
2072
  const chosen = domain ?? index.default;
1767
2073
  if (index.domains[chosen] === void 0) {
@@ -1779,7 +2085,7 @@ async function runOfflineProofStandalone(proofPath, cloud, domain) {
1779
2085
  if (domain !== void 0) throw new Error(`--domain needs a per-domain legs index (build/${packageName}.proof-legs.json) \u2014 recompile with \`githolon compile\` to emit it`);
1780
2086
  legs = parseOfflineLegs(src);
1781
2087
  }
1782
- const deployBody = JSON.parse(readFileSync4(deployFile, "utf8"));
2088
+ const deployBody = JSON.parse(readFileSync5(deployFile, "utf8"));
1783
2089
  const lawHash = await sha256hex(deployBody.packageUsda);
1784
2090
  console.log(`githolon proof \u2014 OFFLINE legs on a local holon (the cloud loop: githolon proof --live)`);
1785
2091
  const { eng } = await holonEngine(cloud, deployBody, "githolon-proof");
@@ -1807,7 +2113,7 @@ var init_proof_offline = __esm({
1807
2113
  });
1808
2114
 
1809
2115
  // src/cli.ts
1810
- import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync10 } from "node:fs";
2116
+ import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync11 } from "node:fs";
1811
2117
  import { spawnSync as spawnSync4 } from "node:child_process";
1812
2118
  import { createRequire as createRequire2 } from "node:module";
1813
2119
  import { dirname as dirname5, join as join10, resolve as resolve2 } from "node:path";
@@ -2045,7 +2351,7 @@ function compileAsync(cwd, args = []) {
2045
2351
  // src/dev.ts
2046
2352
  init_engine();
2047
2353
  init_cloud();
2048
- import { existsSync as existsSync6, readFileSync as readFileSync5, watch } from "node:fs";
2354
+ import { existsSync as existsSync6, readFileSync as readFileSync6, watch } from "node:fs";
2049
2355
  import { createServer } from "node:http";
2050
2356
  import { dirname as dirname4, join as join7, resolve } from "node:path";
2051
2357
  import { pathToFileURL as pathToFileURL3 } from "node:url";
@@ -2138,8 +2444,8 @@ async function resolveWorkspace(explicit, cwd, target, usageHint) {
2138
2444
 
2139
2445
  // src/dev.ts
2140
2446
  init_proof_offline();
2141
- var out4 = (s) => void process.stdout.write(s + "\n");
2142
- var err4 = (s) => void process.stderr.write("error: " + s + "\n");
2447
+ var out5 = (s) => void process.stdout.write(s + "\n");
2448
+ var err5 = (s) => void process.stderr.write("error: " + s + "\n");
2143
2449
  var WS = "dev";
2144
2450
  var DEFAULT_PORT = 7700;
2145
2451
  var DEBOUNCE_MS = 200;
@@ -2222,12 +2528,12 @@ function startServer(s) {
2222
2528
  server.listen(s.port, "127.0.0.1", () => resolveSrv({ close: () => server.close() }));
2223
2529
  });
2224
2530
  }
2225
- function readDeployBody(cwd, packageName) {
2531
+ function readDeployBody2(cwd, packageName) {
2226
2532
  const file = join7(cwd, "build", `${packageName}.deploy.json`);
2227
2533
  if (!existsSync6(file)) {
2228
2534
  throw new Error(`compile produced no ${file} \u2014 check the compile output above`);
2229
2535
  }
2230
- return JSON.parse(readFileSync5(file, "utf8"));
2536
+ return JSON.parse(readFileSync6(file, "utf8"));
2231
2537
  }
2232
2538
  async function installLaw(s, deployBody, installedBy) {
2233
2539
  const newHash = await sha256hex(deployBody.packageUsda);
@@ -2247,7 +2553,7 @@ async function runProofCycle(s, cwd, deployBody, installedBy) {
2247
2553
  if (!existsSync6(proofPath)) return void 0;
2248
2554
  const scratch = `proof-${s.cycle}`;
2249
2555
  try {
2250
- const legs = parseOfflineLegs(readFileSync5(proofPath, "utf8"));
2556
+ const legs = parseOfflineLegs(readFileSync6(proofPath, "utf8"));
2251
2557
  await mountFresh(s.eng, scratch);
2252
2558
  author(s.eng, scratch, "bootstrap", "installDomain", installPayload(s.eng.hashes.nomos, s.eng.nomosPkg, installedBy), "");
2253
2559
  author(s.eng, scratch, "nomos", "installDomain", installPayload(s.lawHash, deployBody.packageUsda, installedBy), s.eng.hashes.nomos);
@@ -2262,36 +2568,36 @@ async function dev(opts) {
2262
2568
  const cwd = process.cwd();
2263
2569
  const cloud = cloudBase(opts.cloud);
2264
2570
  const project = await loadProject(cwd).catch((e) => {
2265
- err4(e.message);
2571
+ err5(e.message);
2266
2572
  return void 0;
2267
2573
  });
2268
2574
  if (project === void 0) {
2269
2575
  if (!existsSync6(join7(cwd, CONFIG_FILE))) {
2270
- err4(`no ${CONFIG_FILE} here (${cwd}) \u2014 \`githolon dev\` runs inside a project; scaffold one from examples/guestbook or \`githolon generate domain <name>\``);
2576
+ err5(`no ${CONFIG_FILE} here (${cwd}) \u2014 \`githolon dev\` runs inside a project; scaffold one from examples/guestbook or \`githolon generate domain <name>\``);
2271
2577
  }
2272
2578
  return 1;
2273
2579
  }
2274
2580
  const creds = loadCreds();
2275
2581
  const installedBy = creds.session?.uid ?? creds.principal ?? "githolon-dev";
2276
- out4(`githolon dev \u2014 ${project.name}: compiling\u2026`);
2582
+ out5(`githolon dev \u2014 ${project.name}: compiling\u2026`);
2277
2583
  const firstCompile = await compileAsync(cwd);
2278
2584
  if (!firstCompile.ok) {
2279
- err4(`compile failed \u2014 the dev loop needs one green compile to mint the holon:
2585
+ err5(`compile failed \u2014 the dev loop needs one green compile to mint the holon:
2280
2586
  ${firstCompile.output}`);
2281
2587
  return 1;
2282
2588
  }
2283
2589
  let deployBody;
2284
2590
  try {
2285
- deployBody = readDeployBody(cwd, project.name);
2591
+ deployBody = readDeployBody2(cwd, project.name);
2286
2592
  } catch (e) {
2287
- err4(e.message);
2593
+ err5(e.message);
2288
2594
  return 1;
2289
2595
  }
2290
2596
  let engBoot;
2291
2597
  try {
2292
2598
  engBoot = await holonEngine(cloud, deployBody, installedBy);
2293
2599
  } catch (e) {
2294
- err4(`cannot boot the local holon: ${String(e.message ?? e)}
2600
+ err5(`cannot boot the local holon: ${String(e.message ?? e)}
2295
2601
  the runtime rides ${cloud}/v1/runtime/* and is cached in ~/.holon/runtime after one fetch \u2014 connect once, then dev runs offline`);
2296
2602
  return 1;
2297
2603
  }
@@ -2314,19 +2620,19 @@ ${firstCompile.output}`);
2314
2620
  s.intents++;
2315
2621
  const installed = await installLaw(s, deployBody, installedBy);
2316
2622
  if (!installed.ok) {
2317
- err4(installed.error ?? "law install failed");
2623
+ err5(installed.error ?? "law install failed");
2318
2624
  return 1;
2319
2625
  }
2320
2626
  s.proof = await runProofCycle(s, cwd, deployBody, installedBy);
2321
2627
  if (opts.once === true) {
2322
- out4(statusScreen({ ...s, watchRoots: ["(--once: no watch)"] }));
2628
+ out5(statusScreen({ ...s, watchRoots: ["(--once: no watch)"] }));
2323
2629
  return s.proof === void 0 || s.proof.ok ? 0 : 1;
2324
2630
  }
2325
2631
  let server;
2326
2632
  try {
2327
2633
  server = await startServer(s);
2328
2634
  } catch (e) {
2329
- err4(e.message);
2635
+ err5(e.message);
2330
2636
  return 1;
2331
2637
  }
2332
2638
  const moduleDirs = /* @__PURE__ */ new Set();
@@ -2354,25 +2660,25 @@ ${firstCompile.output}`);
2354
2660
  s.compileMs = run.ms;
2355
2661
  if (!run.ok) {
2356
2662
  s.lastError = run.output.trim() || "compile failed";
2357
- out4(`\u2717 cycle ${s.cycle}: compile failed \u2014 the previous law keeps serving
2663
+ out5(`\u2717 cycle ${s.cycle}: compile failed \u2014 the previous law keeps serving
2358
2664
  ${run.output}`);
2359
2665
  } else {
2360
2666
  s.lastError = void 0;
2361
2667
  try {
2362
- const body = readDeployBody(cwd, s.packageName);
2668
+ const body = readDeployBody2(cwd, s.packageName);
2363
2669
  const inst = await installLaw(s, body, installedBy);
2364
2670
  if (!inst.ok) {
2365
2671
  s.lastError = inst.error ?? "install failed";
2366
- out4(`\u2717 cycle ${s.cycle}: ${s.lastError}`);
2672
+ out5(`\u2717 cycle ${s.cycle}: ${s.lastError}`);
2367
2673
  } else {
2368
2674
  s.proof = await runProofCycle(s, cwd, body, installedBy);
2369
2675
  const total = performance.now() - t0;
2370
- out4(statusScreen(s));
2371
- out4(` cycle edit \u2192 law-live \u2192 proof in ${total.toFixed(0)} ms total`);
2676
+ out5(statusScreen(s));
2677
+ out5(` cycle edit \u2192 law-live \u2192 proof in ${total.toFixed(0)} ms total`);
2372
2678
  }
2373
2679
  } catch (e) {
2374
2680
  s.lastError = String(e.message ?? e);
2375
- out4(`\u2717 cycle ${s.cycle}: ${s.lastError}`);
2681
+ out5(`\u2717 cycle ${s.cycle}: ${s.lastError}`);
2376
2682
  }
2377
2683
  }
2378
2684
  cycling = false;
@@ -2398,8 +2704,8 @@ ${run.output}`);
2398
2704
  })
2399
2705
  );
2400
2706
  s.watchRoots.push(CONFIG_FILE);
2401
- out4(statusScreen(s));
2402
- out4(" (ctrl-c stops; edits in the watched paths recompile + re-prove automatically)");
2707
+ out5(statusScreen(s));
2708
+ out5(" (ctrl-c stops; edits in the watched paths recompile + re-prove automatically)");
2403
2709
  return new Promise((resolveExit) => {
2404
2710
  const stop = () => {
2405
2711
  for (const w of watchers) w.close();
@@ -2415,9 +2721,9 @@ ${run.output}`);
2415
2721
  init_cloud();
2416
2722
  import { spawnSync as spawnSync2 } from "node:child_process";
2417
2723
  import { randomBytes as randomBytes2 } from "node:crypto";
2418
- import { readFileSync as readFileSync6 } from "node:fs";
2419
- var out5 = (s) => void process.stdout.write(s + "\n");
2420
- var err5 = (s) => void process.stderr.write("error: " + s + "\n");
2724
+ import { readFileSync as readFileSync7 } from "node:fs";
2725
+ var out6 = (s) => void process.stdout.write(s + "\n");
2726
+ var err6 = (s) => void process.stderr.write("error: " + s + "\n");
2421
2727
  function git(args, opts = {}) {
2422
2728
  const r = spawnSync2("git", args, { stdio: opts.quiet === true ? "ignore" : "inherit" });
2423
2729
  return r.status ?? 1;
@@ -2444,36 +2750,36 @@ function workspaceFromPath(path) {
2444
2750
  }
2445
2751
  async function gitCredential(action) {
2446
2752
  if (action !== "get") return 0;
2447
- const kv = parseCredentialInput(readFileSync6(0, "utf8"));
2753
+ const kv = parseCredentialInput(readFileSync7(0, "utf8"));
2448
2754
  const ws = workspaceFromPath(kv["path"]);
2449
2755
  if (kv["protocol"] !== "https" || ws === void 0) return 0;
2450
2756
  const cloud = `https://${kv["host"]}`;
2451
2757
  const token = await sessionToken().catch(() => void 0);
2452
- out5(`username=${token ?? "nomos"}`);
2453
- out5(`password=${ensureSecret(cloud, ws)}`);
2454
- out5("");
2758
+ out6(`username=${token ?? "nomos"}`);
2759
+ out6(`password=${ensureSecret(cloud, ws)}`);
2760
+ out6("");
2455
2761
  return 0;
2456
2762
  }
2457
2763
  async function gitSetup(opts) {
2458
2764
  const cloud = cloudBase(opts.cloud);
2459
2765
  const self = process.argv[1];
2460
2766
  if (self === void 0) {
2461
- err5("cannot locate the githolon CLI entry to install as a credential helper");
2767
+ err6("cannot locate the githolon CLI entry to install as a credential helper");
2462
2768
  return 1;
2463
2769
  }
2464
2770
  const helper = `!"${process.execPath}" "${self}" git-credential`;
2465
2771
  if (git(["config", "--global", `credential.${cloud}.helper`, helper]) !== 0 || git(["config", "--global", `credential.${cloud}.useHttpPath`, "true"]) !== 0) {
2466
- err5("git config failed \u2014 is git installed?");
2772
+ err6("git config failed \u2014 is git installed?");
2467
2773
  return 1;
2468
2774
  }
2469
- out5(`\u2713 git credential helper installed for ${cloud} (global gitconfig)`);
2470
- out5(` pushes/clones of ${cloud}/v2/workspaces/<ws>/git now authenticate from ~/.holon`);
2775
+ out6(`\u2713 git credential helper installed for ${cloud} (global gitconfig)`);
2776
+ out6(` pushes/clones of ${cloud}/v2/workspaces/<ws>/git now authenticate from ~/.holon`);
2471
2777
  return 0;
2472
2778
  }
2473
2779
  async function gitRemote(ws, name, opts) {
2474
2780
  const cloud = cloudBase(opts.cloud);
2475
2781
  if (git(["rev-parse", "--git-dir"], { quiet: true }) !== 0) {
2476
- err5("not a git repository \u2014 run inside your repo (git init first)");
2782
+ err6("not a git repository \u2014 run inside your repo (git init first)");
2477
2783
  return 1;
2478
2784
  }
2479
2785
  const setupRc = await gitSetup(opts);
@@ -2482,7 +2788,7 @@ async function gitRemote(ws, name, opts) {
2482
2788
  const url = `${cloud}/v2/workspaces/${ws}/git`;
2483
2789
  if (git(["remote", "add", name, url], { quiet: true }) !== 0) {
2484
2790
  if (git(["remote", "set-url", name, url], { quiet: true }) !== 0) {
2485
- err5(`could not add or update remote '${name}'`);
2791
+ err6(`could not add or update remote '${name}'`);
2486
2792
  return 1;
2487
2793
  }
2488
2794
  }
@@ -2490,7 +2796,7 @@ async function gitRemote(ws, name, opts) {
2490
2796
  if (token === void 0) {
2491
2797
  const principal = loadCreds().principal;
2492
2798
  if (principal === void 0) {
2493
- err5(
2799
+ err6(
2494
2800
  `remote '${name}' \u2192 ${url} is wired, but a BIRTH push needs a principal:
2495
2801
  githolon login --agent (then just push)
2496
2802
  \u2014 or re-run after githolon ws create/login so a principal is on file`
@@ -2498,13 +2804,13 @@ async function gitRemote(ws, name, opts) {
2498
2804
  return 1;
2499
2805
  }
2500
2806
  if (git(["config", `http.${url}.extraHeader`, `x-nomos-principal: ${principal}`]) !== 0) {
2501
- err5("git config failed setting the principal header");
2807
+ err6("git config failed setting the principal header");
2502
2808
  return 1;
2503
2809
  }
2504
2810
  }
2505
- out5(`\u2713 remote '${name}' \u2192 ${url}`);
2506
- out5(` workspace secret on file; ${token !== void 0 ? "births ride your session (verified)" : "births ride x-nomos-principal (bare uid)"}`);
2507
- out5(` birth/deploy by push: git push ${name} main`);
2811
+ out6(`\u2713 remote '${name}' \u2192 ${url}`);
2812
+ out6(` workspace secret on file; ${token !== void 0 ? "births ride your session (verified)" : "births ride x-nomos-principal (bare uid)"}`);
2813
+ out6(` birth/deploy by push: git push ${name} main`);
2508
2814
  return 0;
2509
2815
  }
2510
2816
 
@@ -2662,15 +2968,28 @@ var HELP = {
2662
2968
  examples: ["githolon revoke creation 32c7100e-\u2026", "githolon revoke admin 32c7100e-\u2026"]
2663
2969
  },
2664
2970
  deploy: {
2665
- usage: "githolon deploy [<ws>] [--as <uid>] [--file <deploy.json>] [--cloud <url>] [--target <name>]",
2666
- what: "Deploy build/*.deploy.json \u2014 kernel-gated, no host secret. TWO 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 with that key and relays it; the kernel re-verifies the\n signature + judges the installDomain relation. Required for warranted platforms (co2, tbx) \u2014 an\n unsigned install is refused there.\n\u2022 OPEN (unwarranted ws / no key): the open POST /domains, still kernel-judged.\nAfter the POST it re-reads the workspace and CONFIRMS the deployed hash is the CURRENT serving law\n(exit 2 if committed-but-not-current). Prints what MOVED (old \u2192 new law hash). With no <ws>, the\nproject's `workspace` binding in nomos.package.mjs resolves it.",
2971
+ usage: "githolon deploy [<ws>] [--as <uid>] [--keep-beside] [--retire-replaced] [--file <deploy.json>] [--cloud <url>] [--target <name>]",
2972
+ 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.",
2667
2973
  flags: [
2668
2974
  ["--as <uid>", "deploy as this principal via the SIGNED lane (needs its key \u2014 githolon key import; else githolon use)"],
2975
+ ["--keep-beside", "install BESIDE the prior law (the old multi-version default; no supersession)"],
2976
+ ["--retire-replaced", "supersede via Retired (not just Superseded) \u2014 the prior install is explicitly retired"],
2669
2977
  ["--file <path>", "an explicit deploy body (default: the one build/*.deploy.json)"],
2670
2978
  ["--target <name>", "pick among named targets (workspace: { dev: \u2026, prod: \u2026 })"],
2671
2979
  ["--cloud <url>", "target cloud"]
2672
2980
  ],
2673
- examples: ["githolon deploy", "githolon deploy my-guestbook", "githolon use <admin-uid> && githolon deploy co2-platform", "githolon deploy tbx --as <admin-uid>"]
2981
+ examples: ["githolon deploy", "githolon deploy my-guestbook", "githolon deploy co2-platform --as <admin-uid>", "githolon deploy my-ws --keep-beside", "githolon deploy my-ws --retire-replaced"]
2982
+ },
2983
+ domains: {
2984
+ usage: "githolon domains <status <ws> [--explain-current-interface] | retire <ws> <hashOrKey> [--with <hashOrKey>]>",
2985
+ 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.",
2986
+ flags: [
2987
+ ["--explain-current-interface", "status: also explain the frontier resolution (why the interface is what it is)"],
2988
+ ["--with <hashOrKey>", "retire: the retiring successor package \u2014 an already-installed hash or served key"],
2989
+ ["--as <uid>", "retire as this principal (installing law is kernel-judged; needs a signing key)"],
2990
+ ["--cloud <url>", "target cloud"]
2991
+ ],
2992
+ examples: ["githolon domains status co2-platform", "githolon domains status co2-platform --explain-current-interface", "githolon domains retire my-ws <oldHash> --as <admin-uid>"]
2674
2993
  },
2675
2994
  secret: {
2676
2995
  usage: "githolon secret <set <ws> <push-password> | rotate <ws>> [--cloud <url>]",
@@ -2709,33 +3028,33 @@ init_engine();
2709
3028
  init_cloud();
2710
3029
  init_local_holon();
2711
3030
  import { spawnSync as spawnSync3 } from "node:child_process";
2712
- import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
3031
+ import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
2713
3032
  import { join as join8 } from "node:path";
2714
- var out6 = (s) => void process.stdout.write(s + "\n");
2715
- var err6 = (s) => void process.stderr.write("error: " + s + "\n");
3033
+ var out7 = (s) => void process.stdout.write(s + "\n");
3034
+ var err7 = (s) => void process.stderr.write("error: " + s + "\n");
2716
3035
  var WS2 = "local";
2717
3036
  async function ledgerInit(dir, opts) {
2718
3037
  const cloud = cloudBase(opts.cloud);
2719
3038
  if (existsSync7(dir) && readdirSync3(dir).length > 0) {
2720
- err6(`refusing to mint into non-empty directory: ${dir}`);
3039
+ err7(`refusing to mint into non-empty directory: ${dir}`);
2721
3040
  return 1;
2722
3041
  }
2723
3042
  let deployFile;
2724
3043
  try {
2725
3044
  deployFile = discoverDeployJson(process.cwd(), opts.file);
2726
3045
  } catch (e) {
2727
- err6(e.message);
3046
+ err7(e.message);
2728
3047
  return 1;
2729
3048
  }
2730
- const deployBody = JSON.parse(readFileSync7(deployFile, "utf8"));
3049
+ const deployBody = JSON.parse(readFileSync8(deployFile, "utf8"));
2731
3050
  const domainHash = await sha256hex(deployBody.packageUsda);
2732
3051
  const c = loadCreds();
2733
3052
  const principal = await sessionToken().catch(() => void 0) !== void 0 ? c.session.uid : c.principal;
2734
3053
  if (principal === void 0) {
2735
- err6("a holon is born OF someone \u2014 githolon login --agent first (or githolon ws create --principal <uid> once)");
3054
+ err7("a holon is born OF someone \u2014 githolon login --agent first (or githolon ws create --principal <uid> once)");
2736
3055
  return 1;
2737
3056
  }
2738
- out6(`minting a holon locally (law ${domainHash.slice(0, 12)}\u2026, installedBy ${principal})`);
3057
+ out7(`minting a holon locally (law ${domainHash.slice(0, 12)}\u2026, installedBy ${principal})`);
2739
3058
  const { eng } = await holonEngine(cloud, deployBody, principal);
2740
3059
  await mountFresh(eng, WS2);
2741
3060
  author(eng, WS2, "bootstrap", "installDomain", installPayload(eng.hashes.nomos, eng.nomosPkg, principal), "");
@@ -2746,18 +3065,18 @@ async function ledgerInit(dir, opts) {
2746
3065
  const gitDir = join8(dir, ".git");
2747
3066
  writeTreeToDisk(gitTree, gitDir);
2748
3067
  const cfgPath = join8(gitDir, "config");
2749
- writeFileSync5(cfgPath, readFileSync7(cfgPath, "utf8").replace(/bare = true/, "bare = false"), "utf8");
3068
+ writeFileSync5(cfgPath, readFileSync8(cfgPath, "utf8").replace(/bare = true/, "bare = false"), "utf8");
2750
3069
  spawnSync3("git", ["-C", dir, "reset", "--hard", "main"], { stdio: "ignore" });
2751
- out6(`\u2713 holon written \u2192 ${dir} (a normal git repo; git log IS the audit trail)`);
2752
- out6(`
3070
+ out7(`\u2713 holon written \u2192 ${dir} (a normal git repo; git log IS the audit trail)`);
3071
+ out7(`
2753
3072
  Birth it on Nomos Cloud (the cloud re-derives this exact verdict):`);
2754
- out6(` cd ${dir} && npx githolon git remote <ws> && git push nomos main`);
3073
+ out7(` cd ${dir} && npx githolon git remote <ws> && git push nomos main`);
2755
3074
  return 0;
2756
3075
  }
2757
3076
  async function ledgerVerify(dir, opts) {
2758
3077
  const gitDir = existsSync7(join8(dir, ".git")) ? join8(dir, ".git") : dir;
2759
3078
  if (!existsSync7(join8(gitDir, "HEAD"))) {
2760
- err6(`${dir} is not a git repo (no HEAD)`);
3079
+ err7(`${dir} is not a git repo (no HEAD)`);
2761
3080
  return 1;
2762
3081
  }
2763
3082
  const { eng } = await holonEngine(cloudBase(opts.cloud), void 0, "verify");
@@ -2775,8 +3094,8 @@ init_local_holon();
2775
3094
  import { existsSync as existsSync8 } from "node:fs";
2776
3095
  import { join as join9 } from "node:path";
2777
3096
  import git2 from "isomorphic-git";
2778
- var out7 = (s) => void process.stdout.write(s + "\n");
2779
- var err7 = (s) => void process.stderr.write("error: " + s + "\n");
3097
+ var out8 = (s) => void process.stdout.write(s + "\n");
3098
+ var err8 = (s) => void process.stderr.write("error: " + s + "\n");
2780
3099
  var SOURCE = "source";
2781
3100
  var REPLAY = "replay";
2782
3101
  function summarizePayload(payload, max = 100) {
@@ -2812,9 +3131,9 @@ function capJsonStrings(v, max = 2048) {
2812
3131
  if (typeof v === "string") return v.length > max ? `${v.slice(0, max)}\u2026(${v.length} chars)` : v;
2813
3132
  if (Array.isArray(v)) return v.map((x) => capJsonStrings(x, max));
2814
3133
  if (typeof v === "object" && v !== null) {
2815
- const out10 = {};
2816
- for (const [k, x] of Object.entries(v)) out10[k] = capJsonStrings(x, max);
2817
- return out10;
3134
+ const out11 = {};
3135
+ for (const [k, x] of Object.entries(v)) out11[k] = capJsonStrings(x, max);
3136
+ return out11;
2818
3137
  }
2819
3138
  return v;
2820
3139
  }
@@ -2861,12 +3180,12 @@ function readKey() {
2861
3180
  async function replay(target, opts) {
2862
3181
  const cloud = cloudBase(opts.cloud);
2863
3182
  const json = opts.json === true;
2864
- const emit = (o) => out7(JSON.stringify(o));
3183
+ const emit = (o) => out8(JSON.stringify(o));
2865
3184
  let eng;
2866
3185
  try {
2867
3186
  ({ eng } = await holonEngine(cloud, void 0, "githolon-replay"));
2868
3187
  } catch (e) {
2869
- err7(`cannot boot the local holon: ${String(e.message ?? e)}
3188
+ err8(`cannot boot the local holon: ${String(e.message ?? e)}
2870
3189
  the runtime rides ${cloud}/v1/runtime/* (cached in ~/.holon/runtime after one fetch)`);
2871
3190
  return 1;
2872
3191
  }
@@ -2874,7 +3193,7 @@ async function replay(target, opts) {
2874
3193
  if (isDir) {
2875
3194
  const gitDir = existsSync8(join9(target, ".git")) ? join9(target, ".git") : target;
2876
3195
  if (!existsSync8(join9(gitDir, "HEAD"))) {
2877
- err7(`${target} is not a git repo (no HEAD) \u2014 pass a holon directory (githolon ledger init <dir>) or a workspace name`);
3196
+ err8(`${target} is not a git repo (no HEAD) \u2014 pass a holon directory (githolon ledger init <dir>) or a workspace name`);
2878
3197
  return 1;
2879
3198
  }
2880
3199
  await mountFresh(eng, SOURCE);
@@ -2882,7 +3201,7 @@ async function replay(target, opts) {
2882
3201
  } else {
2883
3202
  const m = await mountWorkspace(eng, SOURCE, { remote: `${cloud}/v2/workspaces/${target}/git`, headers: {} });
2884
3203
  if (m.restored !== true) {
2885
- err7(
3204
+ err8(
2886
3205
  `workspace '${target}' has no refs/heads/main on ${cloud} \u2014 nothing to replay` + (m.restoreError ? ` (${String(m.restoreError).split(" | ")[0]})` : "") + `
2887
3206
  check the name (githolon ws status ${target}), or pass a local holon directory instead`
2888
3207
  );
@@ -2891,7 +3210,7 @@ async function replay(target, opts) {
2891
3210
  }
2892
3211
  const chain = await walkChain(eng, SOURCE);
2893
3212
  if (chain.length === 0) {
2894
- err7(`the chain on '${target}' carries no intents \u2014 nothing to replay`);
3213
+ err8(`the chain on '${target}' carries no intents \u2014 nothing to replay`);
2895
3214
  return 1;
2896
3215
  }
2897
3216
  await mountFresh(eng, REPLAY);
@@ -2900,8 +3219,8 @@ async function replay(target, opts) {
2900
3219
  const tallies = /* @__PURE__ */ new Map();
2901
3220
  let autoRun = !interactive;
2902
3221
  if (!json) {
2903
- out7(`replaying ${chain.length} intent(s) from ${isDir ? target : `${cloud} :: ${target}`} \u2014 every step re-admitted through the wasm gate`);
2904
- if (interactive) out7(" keys: enter = next intent \xB7 a = run to the end \xB7 q = quit\n");
3222
+ out8(`replaying ${chain.length} intent(s) from ${isDir ? target : `${cloud} :: ${target}`} \u2014 every step re-admitted through the wasm gate`);
3223
+ if (interactive) out8(" keys: enter = next intent \xB7 a = run to the end \xB7 q = quit\n");
2905
3224
  }
2906
3225
  for (let i = 0; i < chain.length; i++) {
2907
3226
  const { oid, bytes, doc } = chain[i];
@@ -2915,8 +3234,8 @@ async function replay(target, opts) {
2915
3234
  if (res.ok === false) {
2916
3235
  const msg = `replay REFUSED at intent ${i} (${domain}/${directive}, ${oid.slice(0, 10)}): ${res.error ?? "unknown"}`;
2917
3236
  if (json) emit({ step: i, oid, domain, directive, refused: true, error: res.error ?? "unknown" });
2918
- else err7(msg);
2919
- err7("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)");
3237
+ else err8(msg);
3238
+ 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)");
2920
3239
  printVerdict(verifyChainLocal(eng, SOURCE));
2921
3240
  return 1;
2922
3241
  }
@@ -2937,31 +3256,31 @@ async function replay(target, opts) {
2937
3256
  tallies: Object.fromEntries(tallies)
2938
3257
  });
2939
3258
  } else {
2940
- out7(`[${String(i + 1).padStart(String(chain.length).length)}/${chain.length}] ${domain}/${directive} (${oid.slice(0, 10)}, ${ms.toFixed(0)} ms)`);
2941
- out7(` payload ${summarizePayload(doc.payload?.payload)}`);
3259
+ out8(`[${String(i + 1).padStart(String(chain.length).length)}/${chain.length}] ${domain}/${directive} (${oid.slice(0, 10)}, ${ms.toFixed(0)} ms)`);
3260
+ out8(` payload ${summarizePayload(doc.payload?.payload)}`);
2942
3261
  for (const ev of doc.events ?? []) {
2943
3262
  const id = ev.aggregate ?? "?";
2944
3263
  const was = before.get(id);
2945
3264
  const verb = ev.marker === "Create" ? "+ created" : ev.marker === "Delete" ? "- deleted" : was === void 0 ? "~ touched" : "~ updated";
2946
- out7(` ${verb} ${id}`);
3265
+ out8(` ${verb} ${id}`);
2947
3266
  const ops = summarizeOps(ev);
2948
- if (ops.length > 0) out7(` ${ops}`);
3267
+ if (ops.length > 0) out8(` ${ops}`);
2949
3268
  }
2950
3269
  const tallyLine = [...tallies.entries()].map(([t, n]) => `${t}:${n}`).join(" ");
2951
3270
  const showTally = stepEvery > 0 ? (i + 1) % stepEvery === 0 || i === chain.length - 1 : true;
2952
- if (showTally && tallyLine.length > 0) out7(` rows ${tallyLine}`);
3271
+ if (showTally && tallyLine.length > 0) out8(` rows ${tallyLine}`);
2953
3272
  }
2954
3273
  if (interactive && !autoRun && i < chain.length - 1) {
2955
3274
  const key = await readKey();
2956
3275
  if (key === "quit") {
2957
- out7(`
3276
+ out8(`
2958
3277
  stopped at intent ${i + 1}/${chain.length} \u2014 the verify verdict below covers the WHOLE chain as delivered`);
2959
3278
  break;
2960
3279
  }
2961
3280
  if (key === "all") autoRun = true;
2962
3281
  }
2963
3282
  }
2964
- if (!json) out7("");
3283
+ if (!json) out8("");
2965
3284
  const verdict = verifyChainLocal(eng, SOURCE);
2966
3285
  if (json) {
2967
3286
  emit({ finale: true, verdict });
@@ -2975,32 +3294,32 @@ init_local_holon();
2975
3294
  init_cloud();
2976
3295
  init_engine();
2977
3296
  var SOURCE2 = "source";
2978
- var out8 = (s) => {
3297
+ var out9 = (s) => {
2979
3298
  process.stdout.write(s + "\n");
2980
3299
  };
2981
- var err8 = (s) => {
3300
+ var err9 = (s) => {
2982
3301
  process.stderr.write(`error: ${s}
2983
3302
  `);
2984
3303
  };
2985
3304
  async function decrypt(ws, opts) {
2986
3305
  const cloud = cloudBase(opts.cloud);
2987
3306
  if (!opts.as) {
2988
- err8("--as <principal> is required (who to decrypt as)");
3307
+ err9("--as <principal> is required (who to decrypt as)");
2989
3308
  return 1;
2990
3309
  }
2991
3310
  if (!opts.secret) {
2992
- err8("--secret <base64 X25519 device secret> is required");
3311
+ err9("--secret <base64 X25519 device secret> is required");
2993
3312
  return 1;
2994
3313
  }
2995
3314
  if (!opts.query) {
2996
- err8("--query <queryId> is required (the read to decrypt)");
3315
+ err9("--query <queryId> is required (the read to decrypt)");
2997
3316
  return 1;
2998
3317
  }
2999
3318
  let eng;
3000
3319
  try {
3001
3320
  ({ eng } = await holonEngine(cloud, opts.overlay, "githolon-decrypt"));
3002
3321
  } catch (e) {
3003
- err8(`cannot boot the local holon: ${String(e.message ?? e)}`);
3322
+ err9(`cannot boot the local holon: ${String(e.message ?? e)}`);
3004
3323
  return 1;
3005
3324
  }
3006
3325
  const m = await mountWorkspace(eng, SOURCE2, {
@@ -3008,7 +3327,7 @@ async function decrypt(ws, opts) {
3008
3327
  headers: { "x-nomos-principal": opts.as }
3009
3328
  });
3010
3329
  if (m.restored !== true) {
3011
- err8(`workspace '${ws}' has no main on ${cloud}, or its clone was refused for '${opts.as}'` + (m.restoreError ? ` (${String(m.restoreError).split(" | ")[0]})` : ""));
3330
+ err9(`workspace '${ws}' has no main on ${cloud}, or its clone was refused for '${opts.as}'` + (m.restoreError ? ` (${String(m.restoreError).split(" | ")[0]})` : ""));
3012
3331
  return 1;
3013
3332
  }
3014
3333
  const wraps = query(eng, SOURCE2, "wrappedKeysByRecipient", JSON.stringify({ recipientPrincipal: opts.as }), opts.as);
@@ -3023,23 +3342,23 @@ async function decrypt(ws, opts) {
3023
3342
  }
3024
3343
  }
3025
3344
  }
3026
- if (opts.json !== true) out8(`# resolved ${keys.length} scope key(s) for '${opts.as}': ${keys.map((k) => `${k.scope}@${k.epoch}`).join(", ") || "(none)"}`);
3345
+ if (opts.json !== true) out9(`# resolved ${keys.length} scope key(s) for '${opts.as}': ${keys.map((k) => `${k.scope}@${k.epoch}`).join(", ") || "(none)"}`);
3027
3346
  const rows = query(eng, SOURCE2, opts.query, JSON.stringify(opts.params ?? {}), opts.as, keys);
3028
3347
  if (rows && !Array.isArray(rows) && rows.ok === false) {
3029
- err8(`the read was refused: ${rows.error}` + (String(rows.error).startsWith("read-forbidden") ? `
3348
+ err9(`the read was refused: ${rows.error}` + (String(rows.error).startsWith("read-forbidden") ? `
3030
3349
  '${opts.as}' is not granted this scope's read capability` : ""));
3031
3350
  return 1;
3032
3351
  }
3033
- out8(JSON.stringify(rows, null, opts.json === true ? 0 : 2));
3352
+ out9(JSON.stringify(rows, null, opts.json === true ? 0 : 2));
3034
3353
  return 0;
3035
3354
  }
3036
3355
 
3037
3356
  // src/status.ts
3038
3357
  init_engine();
3039
3358
  init_cloud();
3040
- import { readFileSync as readFileSync9 } from "node:fs";
3041
- var out9 = (s) => void process.stdout.write(s + "\n");
3042
- var err9 = (s) => void process.stderr.write("error: " + s + "\n");
3359
+ import { readFileSync as readFileSync10 } from "node:fs";
3360
+ var out10 = (s) => void process.stdout.write(s + "\n");
3361
+ var err10 = (s) => void process.stderr.write("error: " + s + "\n");
3043
3362
  function lawDiffVerdict(localHash, installed, ws, hasCurrencyData) {
3044
3363
  const active = installed.filter((d) => d.phase === "Active" && typeof d.domainHash === "string");
3045
3364
  const currents = active.filter((d) => d.current === true);
@@ -3087,48 +3406,48 @@ async function status(wsArg, opts) {
3087
3406
  try {
3088
3407
  ws = await resolveWorkspace(wsArg, process.cwd(), opts.target, "githolon status <ws>");
3089
3408
  } catch (e) {
3090
- err9(e.message);
3409
+ err10(e.message);
3091
3410
  return 1;
3092
3411
  }
3093
3412
  let localHash;
3094
3413
  try {
3095
- const body = JSON.parse(readFileSync9(discoverDeployJson(process.cwd(), opts.file), "utf8"));
3414
+ const body = JSON.parse(readFileSync10(discoverDeployJson(process.cwd(), opts.file), "utf8"));
3096
3415
  if (typeof body.packageUsda === "string") localHash = await sha256hex(body.packageUsda);
3097
3416
  } catch {
3098
3417
  localHash = void 0;
3099
3418
  }
3100
3419
  const r = await fetch(`${cloud}/v2/workspaces/${ws}/domains`).catch(() => void 0);
3101
3420
  if (r === void 0) {
3102
- err9(`cannot reach ${cloud} \u2014 githolon status compares against the DEPLOYED law and needs the cloud (local-only: githolon dev)`);
3421
+ err10(`cannot reach ${cloud} \u2014 githolon status compares against the DEPLOYED law and needs the cloud (local-only: githolon dev)`);
3103
3422
  return 1;
3104
3423
  }
3105
3424
  const d = await r.json().catch(() => void 0);
3106
3425
  if (!r.ok || d?.ok !== true) {
3107
- err9(`status '${ws}' failed (${r.status}): ${d ? JSON.stringify(d) : "no body"}
3426
+ err10(`status '${ws}' failed (${r.status}): ${d ? JSON.stringify(d) : "no body"}
3108
3427
  is the workspace born? githolon ws create ${ws}`);
3109
3428
  return 1;
3110
3429
  }
3111
- out9(`workspace ${ws} on ${cloud}`);
3430
+ out10(`workspace ${ws} on ${cloud}`);
3112
3431
  const allDomains = d.domains ?? [];
3113
3432
  const controlPlaneHashes = new Set(CONTROL_PLANE_KEYS.map((k) => d.currentLaw?.[k]).filter(Boolean));
3114
3433
  const domains = allDomains.filter((dm) => !controlPlaneHashes.has(dm.domainHash));
3115
3434
  const controllerCount = allDomains.length - domains.length;
3116
- if (allDomains.length === 0) out9(`installed (none \u2014 not even the controller; the workspace may be unborn)`);
3435
+ if (allDomains.length === 0) out10(`installed (none \u2014 not even the controller; the workspace may be unborn)`);
3117
3436
  for (const dom of domains) {
3118
3437
  const mark = dom.current === true ? " \u2190 current (the holon resolves dispatch here)" : "";
3119
- out9(`installed ${(dom.domainHash ?? "?").slice(0, 16)}\u2026 ${dom.phase ?? "?"} by ${dom.installedBy ?? "?"}${mark}`);
3438
+ out10(`installed ${(dom.domainHash ?? "?").slice(0, 16)}\u2026 ${dom.phase ?? "?"} by ${dom.installedBy ?? "?"}${mark}`);
3120
3439
  }
3121
- if (controllerCount > 0) out9(` (+ ${controllerCount} control-plane install(s) \u2014 the nomos lifecycle controller; not tenant law)`);
3440
+ if (controllerCount > 0) out10(` (+ ${controllerCount} control-plane install(s) \u2014 the nomos lifecycle controller; not tenant law)`);
3122
3441
  const tenantActive = domains.filter((dm) => dm.phase === "Active").length;
3123
- if (tenantActive > 1) out9(` (${tenantActive} active tenant installs \u2014 install-beside keeps every prior deploy Active; the holon serves the current one per domain)`);
3442
+ if (tenantActive > 1) out10(` (${tenantActive} active tenant installs \u2014 install-beside keeps every prior deploy Active; the holon serves the current one per domain)`);
3124
3443
  if (localHash === void 0) {
3125
- out9(`local (no build/*.deploy.json here \u2014 run \`githolon compile\` to build the law this project authors)`);
3444
+ out10(`local (no build/*.deploy.json here \u2014 run \`githolon compile\` to build the law this project authors)`);
3126
3445
  return 0;
3127
3446
  }
3128
- out9(`local ${localHash.slice(0, 16)}\u2026 (build/*.deploy.json)`);
3447
+ out10(`local ${localHash.slice(0, 16)}\u2026 (build/*.deploy.json)`);
3129
3448
  const hasCurrencyData = Object.keys(d.currentLaw ?? {}).length > 0;
3130
3449
  const verdict = lawDiffVerdict(localHash, domains, ws, hasCurrencyData);
3131
- for (const line of verdict.lines) out9(line);
3450
+ for (const line of verdict.lines) out10(line);
3132
3451
  return verdict.inSync ? 0 : 2;
3133
3452
  }
3134
3453
 
@@ -3188,8 +3507,14 @@ deploy is OPEN + kernel-gated \u2014 births return no host secret, ownership is
3188
3507
  quota freed; data retained; reads keep serving
3189
3508
  githolon ws reclaim <name> [--parent <ws>] lawful custody discard via a SIGNED governance offer
3190
3509
  (only a retired workspace; never root)
3191
- githolon deploy [<ws>] [--file <deploy.json>] POST build/*.deploy.json (open deploy lane);
3192
- prints what moved (old \u2192 new law hash)
3510
+ githolon deploy [<ws>] [--file <deploy.json>] deploy build/*.deploy.json \u2014 DEFAULT replace-current:
3511
+ [--keep-beside] [--retire-replaced] the new law SUPERSEDES the prior install per domain
3512
+ key (prints what moved). --keep-beside installs
3513
+ beside; --retire-replaced supersedes via Retired
3514
+ githolon domains status <ws> per domain key: the current served law + phase +
3515
+ [--explain-current-interface] superseded/retired lineage (the served frontier)
3516
+ githolon domains retire <ws> <hashOrKey> retire an install (marks it Retired \u2014 replayable,
3517
+ [--with <hashOrKey>] no longer served) via a signed installDomain offer
3193
3518
  githolon secret set <ws> <push-password> store a git push password (push-to-create birth lane)
3194
3519
  githolon secret rotate <ws> RETIRED \u2014 host holds no ownership credential (410)
3195
3520
 
@@ -3214,14 +3539,17 @@ Options:
3214
3539
  function cloudArgs(rest) {
3215
3540
  const pos = [];
3216
3541
  const opts = {};
3217
- const VALUE_FLAGS = ["--cloud", "--principal", "--as", "--file", "--target", "--parent", "--via", "--max", "--label", "--secret", "--payload", "--payload-file", "--save", "--format"];
3218
- const BOOL_FLAGS = ["--sha256", "--stamp", "--yes", "--clear"];
3542
+ const VALUE_FLAGS = ["--cloud", "--principal", "--as", "--file", "--target", "--parent", "--via", "--max", "--label", "--secret", "--payload", "--payload-file", "--save", "--format", "--with"];
3543
+ const BOOL_FLAGS = ["--sha256", "--stamp", "--yes", "--clear", "--keep-beside", "--retire-replaced", "--explain-current-interface"];
3219
3544
  for (let i = 0; i < rest.length; i++) {
3220
3545
  const a = rest[i];
3221
3546
  if (BOOL_FLAGS.includes(a)) {
3222
3547
  if (a === "--sha256") opts.sha256 = true;
3223
3548
  else if (a === "--stamp") opts.stamp = true;
3224
3549
  else if (a === "--clear") opts.clear = true;
3550
+ else if (a === "--keep-beside") opts.keepBeside = true;
3551
+ else if (a === "--retire-replaced") opts.retireReplaced = true;
3552
+ else if (a === "--explain-current-interface") opts.explain = true;
3225
3553
  else opts.yes = true;
3226
3554
  } else if (VALUE_FLAGS.includes(a)) {
3227
3555
  const v = rest[++i];
@@ -3237,6 +3565,7 @@ function cloudArgs(rest) {
3237
3565
  else if (a === "--payload-file") opts.payloadFile = v;
3238
3566
  else if (a === "--save") opts.save = v;
3239
3567
  else if (a === "--format") opts.format = v;
3568
+ else if (a === "--with") opts.with = v;
3240
3569
  else if (a === "--max") {
3241
3570
  const n = Number(v);
3242
3571
  if (!Number.isInteger(n) || n <= 0) return { pos, opts, bad: `--max requires a positive integer` };
@@ -3366,7 +3695,7 @@ async function runProof(args) {
3366
3695
  if (tsxDir === void 0) {
3367
3696
  return refuse("tsx not found \u2014 add @githolon/dsl to your project's dependencies (tsx rides along) and npm install");
3368
3697
  }
3369
- const tsxPkg = JSON.parse(readFileSync10(join10(tsxDir, "package.json"), "utf8"));
3698
+ const tsxPkg = JSON.parse(readFileSync11(join10(tsxDir, "package.json"), "utf8"));
3370
3699
  const tsxBinRel = typeof tsxPkg.bin === "string" ? tsxPkg.bin : tsxPkg.bin?.["tsx"];
3371
3700
  const tsxCli = join10(tsxDir, tsxBinRel ?? "dist/cli.mjs");
3372
3701
  console.log("githolon proof --live \u2014 the full cloud loop (throwaway workspace, retired on exit" + (keep ? "; --keep: kept, secret printed once" : "") + ")");
@@ -3381,7 +3710,7 @@ function parseArgs(argv) {
3381
3710
  const [command, ...rest] = argv;
3382
3711
  if (command !== "generate" && command !== "g") {
3383
3712
  throw new Error(
3384
- `Unknown command '${command ?? "(none)"}'. Expected dev | compile | proof | replay | status | generate | ws | deploy | secret.`
3713
+ `Unknown command '${command ?? "(none)"}'. Expected dev | compile | proof | replay | status | generate | ws | deploy | domains | secret.`
3385
3714
  );
3386
3715
  }
3387
3716
  let outDir = DEFAULT_OUT;
@@ -3547,6 +3876,41 @@ ${commandHelp("status")}`);
3547
3876
  if (argv[0] === "ws" || argv[0] === "deploy" || argv[0] === "secret") {
3548
3877
  return runCloud(argv);
3549
3878
  }
3879
+ if (argv[0] === "domains") {
3880
+ const { domainsRetire: domainsRetire2, domainsStatus: domainsStatus2 } = await Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports));
3881
+ const { pos, opts, bad } = cloudArgs(argv.slice(1));
3882
+ if (bad !== void 0) {
3883
+ process.stderr.write(`error: ${bad}
3884
+
3885
+ ${commandHelp("domains") ?? USAGE}`);
3886
+ return 1;
3887
+ }
3888
+ const [sub, ...tail] = pos;
3889
+ const boundWs = async (explicit, hint) => resolveWorkspace(explicit, process.cwd(), opts.target, hint);
3890
+ try {
3891
+ if (sub === "retire") {
3892
+ const [ws, target] = tail;
3893
+ if (ws === void 0 || target === void 0) {
3894
+ process.stderr.write(`error: expected: githolon domains retire <ws> <domainHashOrKey> [--with <hashOrKey>]
3895
+
3896
+ ${commandHelp("domains") ?? USAGE}`);
3897
+ return 1;
3898
+ }
3899
+ return await domainsRetire2(await boundWs(ws, "githolon domains retire <ws> <hashOrKey>"), target, opts);
3900
+ }
3901
+ if (sub === "status") {
3902
+ return await domainsStatus2(await boundWs(tail[0], "githolon domains status <ws>"), opts);
3903
+ }
3904
+ } catch (e) {
3905
+ process.stderr.write(`error: ${e.message}
3906
+ `);
3907
+ return 1;
3908
+ }
3909
+ process.stderr.write(`error: expected: githolon domains <status <ws> [--explain-current-interface] | retire <ws> <hashOrKey>>
3910
+
3911
+ ${commandHelp("domains") ?? USAGE}`);
3912
+ return 1;
3913
+ }
3550
3914
  if (argv[0] === "key") {
3551
3915
  const { keyEnroll: keyEnroll2, keyExport: keyExport2, keyImport: keyImport2, keyShow: keyShow2 } = await Promise.resolve().then(() => (init_governance(), governance_exports));
3552
3916
  const { pos, opts, bad } = cloudArgs(argv.slice(1));
@@ -3679,8 +4043,8 @@ ${USAGE}`);
3679
4043
  let parsed;
3680
4044
  try {
3681
4045
  parsed = parseArgs(argv);
3682
- } catch (err10) {
3683
- process.stderr.write(`error: ${err10.message}
4046
+ } catch (err11) {
4047
+ process.stderr.write(`error: ${err11.message}
3684
4048
 
3685
4049
  ${USAGE}`);
3686
4050
  return 1;
@@ -3699,8 +4063,8 @@ ${USAGE}`);
3699
4063
  process.stdout.write(`created ${path}
3700
4064
  `);
3701
4065
  return 0;
3702
- } catch (err10) {
3703
- process.stderr.write(`error: ${err10.message}
4066
+ } catch (err11) {
4067
+ process.stderr.write(`error: ${err11.message}
3704
4068
  `);
3705
4069
  return 1;
3706
4070
  }