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.
- package/dist/cli.mjs +575 -211
- 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
|
|
383
|
-
import { createHash as
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
453
|
-
for (const p of keyed)
|
|
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
|
-
|
|
734
|
+
out3(`\u2713 now acting as ${principal} (signed deploy/gov/grant/key author as this principal)`);
|
|
459
735
|
if (getDeviceKey(principal) === void 0) {
|
|
460
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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
|
-
|
|
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
|
-
|
|
831
|
+
out3(`bare principal (transitional, unverified): ${c.principal}`);
|
|
556
832
|
} else {
|
|
557
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
868
|
+
err3(e.message);
|
|
593
869
|
return 1;
|
|
594
870
|
}
|
|
595
871
|
if (!v.ok) {
|
|
596
|
-
|
|
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
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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) => (
|
|
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
|
-
|
|
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
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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
|
-
|
|
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
|
-
|
|
633
|
-
|
|
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
|
-
|
|
917
|
+
err3(`status '${name}' (${r.status}): ${text}`);
|
|
642
918
|
return 1;
|
|
643
919
|
}
|
|
644
|
-
|
|
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
|
-
|
|
936
|
+
err3(e.message);
|
|
661
937
|
return 1;
|
|
662
938
|
}
|
|
663
939
|
if (!v.ok) {
|
|
664
|
-
|
|
940
|
+
err3(`retire '${ws}' refused (${v.status}): ${typeof v.body === "string" ? v.body : JSON.stringify(v.body)}`);
|
|
665
941
|
return 1;
|
|
666
942
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
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
|
-
|
|
962
|
+
err3(e.message);
|
|
687
963
|
return 1;
|
|
688
964
|
}
|
|
689
965
|
if (!v.ok) {
|
|
690
|
-
|
|
966
|
+
err3(`reclaim '${ws}' refused (${v.status}): ${typeof v.body === "string" ? v.body : JSON.stringify(v.body)}`);
|
|
691
967
|
return 1;
|
|
692
968
|
}
|
|
693
|
-
|
|
694
|
-
|
|
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
|
-
|
|
979
|
+
err3(e.message);
|
|
704
980
|
return 1;
|
|
705
981
|
}
|
|
706
982
|
const fileName = file.split("/").pop();
|
|
707
|
-
const
|
|
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(
|
|
1007
|
+
body = JSON.parse(readFileSync3(file, "utf8"));
|
|
716
1008
|
} catch (e) {
|
|
717
|
-
|
|
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
|
-
|
|
1014
|
+
err3(`deploy body ${file} is missing packageUsda \u2014 run \`githolon compile\` again`);
|
|
723
1015
|
return 1;
|
|
724
1016
|
}
|
|
725
|
-
const
|
|
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:
|
|
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
|
-
|
|
1034
|
+
err3(e.message);
|
|
741
1035
|
return 1;
|
|
742
1036
|
}
|
|
743
1037
|
if (!v.ok) {
|
|
744
|
-
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
760
|
-
|
|
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
|
-
|
|
774
|
-
|
|
775
|
-
|
|
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
|
-
|
|
780
|
-
if (deployedHash !== void 0)
|
|
781
|
-
|
|
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
|
-
|
|
785
|
-
|
|
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)
|
|
788
|
-
else
|
|
789
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
821
|
-
|
|
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
|
|
1248
|
+
const out11 = new Uint8Array(n);
|
|
943
1249
|
let o = 0;
|
|
944
1250
|
for (const c of chunks) {
|
|
945
|
-
|
|
1251
|
+
out11.set(c, o);
|
|
946
1252
|
o += c.length;
|
|
947
1253
|
}
|
|
948
|
-
return
|
|
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
|
|
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
|
-
|
|
1411
|
+
out11[i] = Number(z >> 11n) / T;
|
|
1106
1412
|
}
|
|
1107
|
-
return
|
|
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
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
1415
|
-
|
|
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
|
-
|
|
1724
|
+
err4(`chain INVALID at ${v["check"]}${v["atIndex"] !== void 0 ? ` (intent ${v["atIndex"]})` : ""}: ${v["error"]}`);
|
|
1419
1725
|
return false;
|
|
1420
1726
|
}
|
|
1421
|
-
var
|
|
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
|
-
|
|
1428
|
-
|
|
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
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
2142
|
-
var
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2582
|
+
out5(`githolon dev \u2014 ${project.name}: compiling\u2026`);
|
|
2277
2583
|
const firstCompile = await compileAsync(cwd);
|
|
2278
2584
|
if (!firstCompile.ok) {
|
|
2279
|
-
|
|
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 =
|
|
2591
|
+
deployBody = readDeployBody2(cwd, project.name);
|
|
2286
2592
|
} catch (e) {
|
|
2287
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
2371
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2402
|
-
|
|
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
|
|
2419
|
-
var
|
|
2420
|
-
var
|
|
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(
|
|
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
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2772
|
+
err6("git config failed \u2014 is git installed?");
|
|
2467
2773
|
return 1;
|
|
2468
2774
|
}
|
|
2469
|
-
|
|
2470
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2807
|
+
err6("git config failed setting the principal header");
|
|
2502
2808
|
return 1;
|
|
2503
2809
|
}
|
|
2504
2810
|
}
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
2715
|
-
var
|
|
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
|
-
|
|
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
|
-
|
|
3046
|
+
err7(e.message);
|
|
2728
3047
|
return 1;
|
|
2729
3048
|
}
|
|
2730
|
-
const deployBody = JSON.parse(
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
3068
|
+
writeFileSync5(cfgPath, readFileSync8(cfgPath, "utf8").replace(/bare = true/, "bare = false"), "utf8");
|
|
2750
3069
|
spawnSync3("git", ["-C", dir, "reset", "--hard", "main"], { stdio: "ignore" });
|
|
2751
|
-
|
|
2752
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2779
|
-
var
|
|
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
|
|
2816
|
-
for (const [k, x] of Object.entries(v))
|
|
2817
|
-
return
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2904
|
-
if (interactive)
|
|
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
|
|
2919
|
-
|
|
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
|
-
|
|
2941
|
-
|
|
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
|
-
|
|
3265
|
+
out8(` ${verb} ${id}`);
|
|
2947
3266
|
const ops = summarizeOps(ev);
|
|
2948
|
-
if (ops.length > 0)
|
|
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)
|
|
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
|
-
|
|
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)
|
|
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
|
|
3297
|
+
var out9 = (s) => {
|
|
2979
3298
|
process.stdout.write(s + "\n");
|
|
2980
3299
|
};
|
|
2981
|
-
var
|
|
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
|
-
|
|
3307
|
+
err9("--as <principal> is required (who to decrypt as)");
|
|
2989
3308
|
return 1;
|
|
2990
3309
|
}
|
|
2991
3310
|
if (!opts.secret) {
|
|
2992
|
-
|
|
3311
|
+
err9("--secret <base64 X25519 device secret> is required");
|
|
2993
3312
|
return 1;
|
|
2994
3313
|
}
|
|
2995
3314
|
if (!opts.query) {
|
|
2996
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
3041
|
-
var
|
|
3042
|
-
var
|
|
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
|
-
|
|
3409
|
+
err10(e.message);
|
|
3091
3410
|
return 1;
|
|
3092
3411
|
}
|
|
3093
3412
|
let localHash;
|
|
3094
3413
|
try {
|
|
3095
|
-
const body = JSON.parse(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
3438
|
+
out10(`installed ${(dom.domainHash ?? "?").slice(0, 16)}\u2026 ${dom.phase ?? "?"} by ${dom.installedBy ?? "?"}${mark}`);
|
|
3120
3439
|
}
|
|
3121
|
-
if (controllerCount > 0)
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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>]
|
|
3192
|
-
|
|
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(
|
|
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 (
|
|
3683
|
-
process.stderr.write(`error: ${
|
|
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 (
|
|
3703
|
-
process.stderr.write(`error: ${
|
|
4066
|
+
} catch (err11) {
|
|
4067
|
+
process.stderr.write(`error: ${err11.message}
|
|
3704
4068
|
`);
|
|
3705
4069
|
return 1;
|
|
3706
4070
|
}
|