githolon 0.24.0 → 0.25.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 +146 -27
  2. package/package.json +5 -3
package/dist/cli.mjs CHANGED
@@ -25,10 +25,14 @@ __export(governance_exports, {
25
25
  });
26
26
  import { createHash, createPrivateKey, createPublicKey } from "node:crypto";
27
27
  import { readFileSync, writeFileSync as writeFileSync2 } from "node:fs";
28
- import { connect } from "@githolon/client";
28
+ async function clientConnect() {
29
+ const mod = await import("@githolon/client");
30
+ return mod.connect;
31
+ }
29
32
  async function connectGovernance(cloud, parent) {
30
33
  const authToken = await sessionToken().catch(() => void 0);
31
34
  const clientId = `githolon-cli-${Date.now().toString(16)}`;
35
+ const connect = await clientConnect();
32
36
  return await connect({
33
37
  cloud,
34
38
  workspace: parent,
@@ -49,7 +53,7 @@ async function ensureDeviceKey(holon, principal) {
49
53
  return key;
50
54
  }
51
55
  function resolveGovernancePrincipal(opts) {
52
- const principal = opts.principal ?? currentPrincipal();
56
+ const principal = opts.principal ?? activePrincipal();
53
57
  if (principal === void 0) {
54
58
  throw new Error(
55
59
  "signed governance needs a principal \u2014 githolon login --agent (self-onboarding), or pass --principal <your-auth-uid>"
@@ -120,7 +124,7 @@ async function grantOrRevoke(verb, kind, subject, opts) {
120
124
  }
121
125
  async function keyEnroll(opts) {
122
126
  const cloud = cloudBase(opts.cloud);
123
- const principal = opts.principal ?? currentPrincipal();
127
+ const principal = opts.principal ?? activePrincipal();
124
128
  if (principal === void 0) {
125
129
  err("enroll needs a principal \u2014 githolon login --agent first (or --principal <uid>)");
126
130
  return 1;
@@ -151,7 +155,7 @@ async function keyEnroll(opts) {
151
155
  return 0;
152
156
  }
153
157
  async function keyShow(opts) {
154
- const principal = opts.principal ?? currentPrincipal();
158
+ const principal = opts.principal ?? activePrincipal();
155
159
  if (principal === void 0) {
156
160
  out("no principal on file \u2014 githolon login --agent, or pass --principal <uid>");
157
161
  return 0;
@@ -188,7 +192,7 @@ function seedFromInput(input, opts) {
188
192
  );
189
193
  }
190
194
  async function keyImport(opts) {
191
- const principal = opts.principal ?? currentPrincipal();
195
+ const principal = opts.principal ?? activePrincipal();
192
196
  if (principal === void 0) {
193
197
  err("key import needs --principal <uid> (the auth-uid this key authors as)");
194
198
  return 1;
@@ -217,7 +221,7 @@ async function keyImport(opts) {
217
221
  return 0;
218
222
  }
219
223
  async function keyExport(opts) {
220
- const principal = opts.principal ?? currentPrincipal();
224
+ const principal = opts.principal ?? activePrincipal();
221
225
  if (principal === void 0) {
222
226
  err("key export needs --principal <uid>");
223
227
  return 1;
@@ -347,6 +351,9 @@ var init_governance = __esm({
347
351
  // src/cloud.ts
348
352
  var cloud_exports = {};
349
353
  __export(cloud_exports, {
354
+ activePrincipal: () => activePrincipal,
355
+ authUse: () => authUse,
356
+ clearActAs: () => clearActAs,
350
357
  cloudBase: () => cloudBase,
351
358
  configDir: () => configDir,
352
359
  currentPrincipal: () => currentPrincipal,
@@ -362,6 +369,7 @@ __export(cloud_exports, {
362
369
  secretRotate: () => secretRotate,
363
370
  secretSet: () => secretSet,
364
371
  sessionToken: () => sessionToken,
372
+ setActAs: () => setActAs,
365
373
  setDeviceKey: () => setDeviceKey,
366
374
  setPrincipal: () => setPrincipal,
367
375
  setSecret: () => setSecret,
@@ -416,6 +424,43 @@ function currentPrincipal() {
416
424
  const c = loadCreds();
417
425
  return c.session?.uid ?? c.principal;
418
426
  }
427
+ function activePrincipal() {
428
+ return loadCreds().actAs ?? currentPrincipal();
429
+ }
430
+ function setActAs(principal) {
431
+ const c = loadCreds();
432
+ c.actAs = principal;
433
+ saveCreds(c);
434
+ }
435
+ function clearActAs() {
436
+ const c = loadCreds();
437
+ delete c.actAs;
438
+ saveCreds(c);
439
+ }
440
+ async function authUse(principal, opts = {}) {
441
+ if (opts.clear === true) {
442
+ clearActAs();
443
+ out2("\u2713 cleared the active signing principal \u2014 signed lanes fall back to the session / bare default");
444
+ return 0;
445
+ }
446
+ if (principal === void 0) {
447
+ const c = loadCreds();
448
+ 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");
450
+ const keyed = Object.keys(c.devices ?? {});
451
+ if (keyed.length > 0) {
452
+ out2(`keys on file:`);
453
+ for (const p of keyed) out2(` ${p}${p === active ? " \u2190 active" : ""}`);
454
+ }
455
+ return 0;
456
+ }
457
+ setActAs(principal);
458
+ out2(`\u2713 now acting as ${principal} (signed deploy/gov/grant/key author as this principal)`);
459
+ 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>`);
461
+ }
462
+ return 0;
463
+ }
419
464
  function getDeviceKey(principal) {
420
465
  return loadCreds().devices?.[principal];
421
466
  }
@@ -506,13 +551,21 @@ async function whoami() {
506
551
  const c = loadCreds();
507
552
  if (c.session !== void 0) {
508
553
  out2(`session: ${c.session.anonymous ? "anonymous agent" : "account"} uid ${c.session.uid} (auth ${c.session.url})`);
509
- return 0;
510
- }
511
- if (c.principal !== void 0) {
554
+ } else if (c.principal !== void 0) {
512
555
  out2(`bare principal (transitional, unverified): ${c.principal}`);
513
- return 0;
556
+ } else {
557
+ out2("not logged in \u2014 githolon login --agent, or pass --principal <uid> per birth");
558
+ }
559
+ const active = activePrincipal();
560
+ 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]`);
562
+ } else if (active !== void 0) {
563
+ out2(`signed lanes act as: ${active} (the session/bare default \u2014 githolon use <uid> to switch)`);
564
+ }
565
+ const keyed = Object.keys(c.devices ?? {});
566
+ if (keyed.length > 0) {
567
+ out2(`signing keys on file: ${keyed.map((p) => p === active ? `${p} \u2190active` : p).join(", ")}`);
514
568
  }
515
- out2("not logged in \u2014 githolon login --agent, or pass --principal <uid> per birth");
516
569
  return 0;
517
570
  }
518
571
  async function logout() {
@@ -653,7 +706,7 @@ async function deploy(ws, opts) {
653
706
  }
654
707
  const fileName = file.split("/").pop();
655
708
  const before = await fetch(`${cloud}/v2/workspaces/${ws}/domains`).then((br) => br.json()).then((bd) => bd.ok === true ? bd.domains ?? [] : []).catch(() => []);
656
- const principal = opts.principal ?? currentPrincipal();
709
+ const principal = opts.principal ?? activePrincipal();
657
710
  const signed = opts.principal !== void 0 || principal !== void 0 && getDeviceKey(principal) !== void 0;
658
711
  let phaseSuffix = "";
659
712
  let deployedHash;
@@ -1493,7 +1546,43 @@ function runChildBirthLeg(eng, ws, lawHash, cb, frameworkHash) {
1493
1546
  if (cb.lawHashField !== void 0) payload[cb.lawHashField] = lawHash;
1494
1547
  const stamp = (/* @__PURE__ */ new Date()).toISOString();
1495
1548
  for (const f of cb.birthAutoStamped) payload[f] = stamp;
1496
- const res = author(eng, ws, cb.parentDomain, cb.birthDirectiveId, payload, lawHash);
1549
+ const actor = actorFromPayload(payload, cb.birthActorField);
1550
+ if (cb.birthRequiresRelation !== void 0 && actor === void 0) {
1551
+ return done(false, `child birth ${cb.parentDomain}/${cb.birthDirectiveId} is gated by .requires("${cb.birthRequiresRelation}") but the proof could not discover an actor field from the birth recipe \u2014 route the genesis step actor from a payload subject field`);
1552
+ }
1553
+ let parentWs = ws;
1554
+ if (cb.parentSetup !== void 0) {
1555
+ const setup = cb.parentSetup;
1556
+ const setupPayload = { ...setup.birthPayload };
1557
+ if (setup.frameworkHashField !== void 0) setupPayload[setup.frameworkHashField] = fw;
1558
+ if (setup.lawHashField !== void 0) setupPayload[setup.lawHashField] = lawHash;
1559
+ for (const f of setup.birthAutoStamped) setupPayload[f] = stamp;
1560
+ if (setup.birthActorField !== void 0 && cb.birthActorField !== void 0 && payload[cb.birthActorField] !== void 0) {
1561
+ setupPayload[setup.birthActorField] = payload[cb.birthActorField];
1562
+ }
1563
+ const setupActor = actorFromPayload(setupPayload, setup.birthActorField) ?? actor;
1564
+ const setupOpts = setupActor !== void 0 ? { actor: setupActor } : {};
1565
+ const setupRes = author(
1566
+ eng,
1567
+ ws,
1568
+ setup.parentDomain,
1569
+ setup.birthDirectiveId,
1570
+ setupPayload,
1571
+ lawHash,
1572
+ setupOpts
1573
+ );
1574
+ if (setupRes.ok === false) {
1575
+ return done(false, `parent setup ${setup.parentDomain}/${setup.birthDirectiveId} refused: ${setupRes.error ?? "unknown"} \u2014 the proof could not birth the parent workspace needed before gated child birth`);
1576
+ }
1577
+ const bornParent = (setupRes.born ?? []).map((b) => b?.workspace).find((w) => typeof w === "string" && w.length > 0);
1578
+ if (bornParent === void 0) {
1579
+ return done(false, `parent setup ${setup.parentDomain}/${setup.birthDirectiveId} admitted but spawned no parent workspace (born=${JSON.stringify(setupRes.born ?? [])})`);
1580
+ }
1581
+ parentWs = bornParent;
1582
+ lines.push(`\u2713 parent setup ${setup.parentDomain}/${setup.birthDirectiveId} \u2014 ${parentWs} born before gated child birth`);
1583
+ }
1584
+ const opts = actor !== void 0 ? { actor } : {};
1585
+ const res = author(eng, parentWs, cb.parentDomain, cb.birthDirectiveId, payload, lawHash, opts);
1497
1586
  if (res.ok === false) {
1498
1587
  return done(false, `child birth ${cb.parentDomain}/${cb.birthDirectiveId} refused: ${res.error ?? "unknown"} \u2014 the parent's own gate rejected the birth offer (check the .births() plan + payload)`);
1499
1588
  }
@@ -1547,6 +1636,11 @@ function runChildBirthLeg(eng, ws, lawHash, cb, frameworkHash) {
1547
1636
  lines.push(`\u2713 verify_chain GREEN on the born child ${childWs} (${verdict.plansRerun ?? "?"} plans re-run \u2014 it self-validates from intent 0 through its OWN gate)`);
1548
1637
  return done(true);
1549
1638
  }
1639
+ function actorFromPayload(payload, field) {
1640
+ if (field === void 0) return void 0;
1641
+ const value = payload[field];
1642
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
1643
+ }
1550
1644
  async function runOfflineProofStandalone(proofPath, cloud, domain) {
1551
1645
  const src = readFileSync4(proofPath, "utf8");
1552
1646
  const packageName = basename(proofPath).replace(/\.proof\.mts$/, "");
@@ -1789,7 +1883,6 @@ Re-run with --force to replace it.`
1789
1883
 
1790
1884
  // src/cli.ts
1791
1885
  init_cloud();
1792
- init_governance();
1793
1886
 
1794
1887
  // src/compile.ts
1795
1888
  import { spawn, spawnSync } from "node:child_process";
@@ -2374,7 +2467,16 @@ var HELP = {
2374
2467
  },
2375
2468
  whoami: {
2376
2469
  usage: "githolon whoami",
2377
- what: "Show the principal births will ride (session uid, or the transitional bare uid)."
2470
+ what: "Show the principal births will ride (session uid, or the transitional bare uid), the ACTIVE\nsigning principal (githolon use), and the signing keys on file."
2471
+ },
2472
+ use: {
2473
+ usage: "githolon use [<uid>] [--clear]",
2474
+ what: "Set, show, or clear the ACTIVE signing principal \u2014 the one the signed lanes (deploy, gov, grant,\nkey) author as by DEFAULT, without an explicit --as. Pair with `githolon key import` to hold an\nenrolled key per principal (a platform admin key, your own uid, \u2026) and switch which one acts with\none command. `--as`/`--principal` still overrides per-command. No argument shows the active\nprincipal + keys on file; `--clear` falls back to the session/bare default. Stored in ~/.holon.",
2475
+ flags: [
2476
+ ["<uid>", "the auth-uid to act as (must have a signing key \u2014 githolon key import)"],
2477
+ ["--clear", "clear the active principal (signed lanes use the session/bare default)"]
2478
+ ],
2479
+ examples: ["githolon use d205ca94-...", "githolon use", "githolon use --clear"]
2378
2480
  },
2379
2481
  logout: {
2380
2482
  usage: "githolon logout",
@@ -2450,14 +2552,15 @@ var HELP = {
2450
2552
  examples: ["githolon revoke creation 32c7100e-\u2026", "githolon revoke admin 32c7100e-\u2026"]
2451
2553
  },
2452
2554
  deploy: {
2453
- usage: "githolon deploy [<ws>] [--file <deploy.json>] [--cloud <url>] [--target <name>]",
2454
- what: "POST build/*.deploy.json on the OPEN deploy lane (kernel-gated \u2014 no host secret). Prints what\nMOVED (old \u2192 new law hash). With no <ws>, the project's `workspace` binding in nomos.package.mjs\nresolves it.",
2555
+ usage: "githolon deploy [<ws>] [--as <uid>] [--file <deploy.json>] [--cloud <url>] [--target <name>]",
2556
+ 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.",
2455
2557
  flags: [
2558
+ ["--as <uid>", "deploy as this principal via the SIGNED lane (needs its key \u2014 githolon key import; else githolon use)"],
2456
2559
  ["--file <path>", "an explicit deploy body (default: the one build/*.deploy.json)"],
2457
2560
  ["--target <name>", "pick among named targets (workspace: { dev: \u2026, prod: \u2026 })"],
2458
2561
  ["--cloud <url>", "target cloud"]
2459
2562
  ],
2460
- examples: ["githolon deploy", "githolon deploy my-guestbook", "githolon deploy --target prod"]
2563
+ examples: ["githolon deploy", "githolon deploy my-guestbook", "githolon use <admin-uid> && githolon deploy co2-platform", "githolon deploy tbx --as <admin-uid>"]
2461
2564
  },
2462
2565
  secret: {
2463
2566
  usage: "githolon secret <set <ws> <push-password> | rotate <ws>> [--cloud <url>]",
@@ -2944,7 +3047,9 @@ Authoring:
2944
3047
  Identity (~/.holon/credentials.json \u2014 sessions auto-refresh):
2945
3048
  githolon login --agent anonymous sign-in: a verified self-onboarded identity
2946
3049
  githolon login --token <refresh_token> adopt an existing captain app session
2947
- githolon whoami show the principal births will ride
3050
+ githolon whoami show the principal births will ride + keys on file
3051
+ githolon use [<uid>] [--clear] set/show/clear the ACTIVE signing principal: the one
3052
+ deploy/gov/grant/key author as (--as overrides per-cmd)
2948
3053
  githolon logout clear the session (workspace secrets kept)
2949
3054
  githolon key enroll [--principal <uid>] [--parent <ws>] mint a signing device key + enroll it on-chain
2950
3055
  (secret stays local; warrants SIGNED governance)
@@ -3000,12 +3105,13 @@ function cloudArgs(rest) {
3000
3105
  const pos = [];
3001
3106
  const opts = {};
3002
3107
  const VALUE_FLAGS = ["--cloud", "--principal", "--as", "--file", "--target", "--parent", "--via", "--max", "--label", "--secret", "--payload", "--payload-file", "--save", "--format"];
3003
- const BOOL_FLAGS = ["--sha256", "--stamp", "--yes"];
3108
+ const BOOL_FLAGS = ["--sha256", "--stamp", "--yes", "--clear"];
3004
3109
  for (let i = 0; i < rest.length; i++) {
3005
3110
  const a = rest[i];
3006
3111
  if (BOOL_FLAGS.includes(a)) {
3007
3112
  if (a === "--sha256") opts.sha256 = true;
3008
3113
  else if (a === "--stamp") opts.stamp = true;
3114
+ else if (a === "--clear") opts.clear = true;
3009
3115
  else opts.yes = true;
3010
3116
  } else if (VALUE_FLAGS.includes(a)) {
3011
3117
  const v = rest[++i];
@@ -3332,6 +3438,7 @@ ${commandHelp("status")}`);
3332
3438
  return runCloud(argv);
3333
3439
  }
3334
3440
  if (argv[0] === "key") {
3441
+ const { keyEnroll: keyEnroll2, keyExport: keyExport2, keyImport: keyImport2, keyShow: keyShow2 } = await Promise.resolve().then(() => (init_governance(), governance_exports));
3335
3442
  const { pos, opts, bad } = cloudArgs(argv.slice(1));
3336
3443
  if (bad !== void 0) {
3337
3444
  process.stderr.write(`error: ${bad}
@@ -3340,16 +3447,17 @@ ${USAGE}`);
3340
3447
  return 1;
3341
3448
  }
3342
3449
  const [sub] = pos;
3343
- if (sub === "enroll") return keyEnroll(opts);
3344
- if (sub === "show") return keyShow(opts);
3345
- if (sub === "import") return keyImport(opts);
3346
- if (sub === "export") return keyExport(opts);
3450
+ if (sub === "enroll") return keyEnroll2(opts);
3451
+ if (sub === "show") return keyShow2(opts);
3452
+ if (sub === "import") return keyImport2(opts);
3453
+ if (sub === "export") return keyExport2(opts);
3347
3454
  process.stderr.write(`error: expected: githolon key <enroll|show|import|export> [--principal <uid>]
3348
3455
 
3349
3456
  ${USAGE}`);
3350
3457
  return 1;
3351
3458
  }
3352
3459
  if (argv[0] === "gov") {
3460
+ const { governanceOffer: governanceOffer2, governanceRelay: governanceRelay2 } = await Promise.resolve().then(() => (init_governance(), governance_exports));
3353
3461
  const { pos, opts, bad } = cloudArgs(argv.slice(1));
3354
3462
  if (bad !== void 0) {
3355
3463
  process.stderr.write(`error: ${bad}
@@ -3365,7 +3473,7 @@ ${commandHelp("gov") ?? USAGE}`);
3365
3473
  ${commandHelp("gov") ?? USAGE}`);
3366
3474
  return 1;
3367
3475
  }
3368
- return governanceRelay(ws2, file, opts);
3476
+ return governanceRelay2(ws2, file, opts);
3369
3477
  }
3370
3478
  const [ws, directiveId] = pos;
3371
3479
  if (ws === void 0 || directiveId === void 0) {
@@ -3374,9 +3482,10 @@ ${commandHelp("gov") ?? USAGE}`);
3374
3482
  ${commandHelp("gov") ?? USAGE}`);
3375
3483
  return 1;
3376
3484
  }
3377
- return governanceOffer(ws, directiveId, opts);
3485
+ return governanceOffer2(ws, directiveId, opts);
3378
3486
  }
3379
3487
  if (argv[0] === "grant" || argv[0] === "revoke") {
3488
+ const { grantOrRevoke: grantOrRevoke2 } = await Promise.resolve().then(() => (init_governance(), governance_exports));
3380
3489
  const verb = argv[0];
3381
3490
  const { pos, opts, bad } = cloudArgs(argv.slice(1));
3382
3491
  if (bad !== void 0) {
@@ -3392,7 +3501,7 @@ ${USAGE}`);
3392
3501
  ${USAGE}`);
3393
3502
  return 1;
3394
3503
  }
3395
- return grantOrRevoke(verb, kind, subject, opts);
3504
+ return grantOrRevoke2(verb, kind, subject, opts);
3396
3505
  }
3397
3506
  if (argv[0] === "login") {
3398
3507
  const agent = argv.includes("--agent");
@@ -3406,6 +3515,16 @@ ${USAGE}`);
3406
3515
  }
3407
3516
  if (argv[0] === "whoami") return whoami();
3408
3517
  if (argv[0] === "logout") return logout();
3518
+ if (argv[0] === "use") {
3519
+ const { pos, opts, bad } = cloudArgs(argv.slice(1));
3520
+ if (bad !== void 0) {
3521
+ process.stderr.write(`error: ${bad}
3522
+
3523
+ ${USAGE}`);
3524
+ return 1;
3525
+ }
3526
+ return authUse(pos[0], { clear: opts.clear === true });
3527
+ }
3409
3528
  if (argv[0] === "ledger") {
3410
3529
  const { pos, opts, bad } = cloudArgs(argv.slice(1));
3411
3530
  if (bad !== void 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "githolon",
3
- "version": "0.24.0",
3
+ "version": "0.25.0",
4
4
  "type": "module",
5
5
  "description": "githolon — the Nomos developer CLI: Rails-style generators for @githolon/dsl domains + the package compiler. Kernel-independent.",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -21,14 +21,16 @@
21
21
  "scripts": {
22
22
  "build": "node build.mjs",
23
23
  "prepare": "npm run build",
24
+ "pretest": "npm run build",
24
25
  "holon": "tsx src/cli.ts",
25
26
  "test": "vitest run",
27
+ "pretypecheck": "npm run build",
26
28
  "typecheck": "tsc --noEmit"
27
29
  },
28
30
  "dependencies": {
29
31
  "@bjorn3/browser_wasi_shim": "0.4.2",
30
- "@githolon/client": "^0.24.0",
31
- "@githolon/dsl": "^0.24.0",
32
+ "@githolon/client": "^0.25.0",
33
+ "@githolon/dsl": "^0.25.0",
32
34
  "isomorphic-git": "^1.38.4"
33
35
  },
34
36
  "devDependencies": {