githolon 0.77.1 → 0.79.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 +111 -16
  2. package/package.json +3 -3
package/dist/cli.mjs CHANGED
@@ -3010,8 +3010,10 @@ __export(governance_exports, {
3010
3010
  STAGE_THRESHOLD_CHARS: () => STAGE_THRESHOLD_CHARS,
3011
3011
  buildCertificate: () => buildCertificate,
3012
3012
  buildRebirthPlan: () => buildRebirthPlan,
3013
+ canonicalPrincipal: () => canonicalPrincipal,
3013
3014
  certifyEra: () => certifyEra,
3014
3015
  consentRebirth: () => consentRebirth,
3016
+ discoverSigningDomain: () => discoverSigningDomain,
3015
3017
  executeRepairRebirth: () => executeRepairRebirth,
3016
3018
  governanceOffer: () => governanceOffer,
3017
3019
  governanceRelay: () => governanceRelay,
@@ -3044,6 +3046,33 @@ async function clientConnect() {
3044
3046
  const mod = await import("@githolon/client");
3045
3047
  return mod.connect;
3046
3048
  }
3049
+ async function discoverSigningDomain(holon, parent, directiveId, explicit) {
3050
+ if (explicit !== void 0 && explicit !== "") return explicit;
3051
+ try {
3052
+ const offer = typeof holon.interfaceOffer === "function" ? await holon.interfaceOffer() : null;
3053
+ const declaring = [];
3054
+ for (const [domain, iface] of Object.entries(offer ?? {})) {
3055
+ if (iface?.directives !== void 0 && Object.prototype.hasOwnProperty.call(iface.directives, directiveId)) declaring.push(domain);
3056
+ }
3057
+ if (declaring.length === 1) return declaring[0];
3058
+ if (declaring.length > 1) {
3059
+ throw new Error(
3060
+ `'${directiveId}' is declared by ${declaring.length} installed domains on ${parent} (${declaring.join(", ")}) \u2014 pass --domain <one of them>`
3061
+ );
3062
+ }
3063
+ } catch (e) {
3064
+ if (/pass --domain/.test(e.message)) throw e;
3065
+ }
3066
+ if (typeof holon.discoverDirectiveDomain === "function") {
3067
+ try {
3068
+ const r = await holon.discoverDirectiveDomain({ ws: parent, directiveId });
3069
+ return r.domain;
3070
+ } catch (e) {
3071
+ throw new Error(`${e.message} (override with --domain <d> [--directive <id>])`);
3072
+ }
3073
+ }
3074
+ return "workspaces";
3075
+ }
3047
3076
  async function connectGovernance(cloud, parent) {
3048
3077
  const authToken = await sessionToken().catch(() => void 0);
3049
3078
  const clientId = `githolon-cli-${Date.now().toString(16)}`;
@@ -3076,17 +3105,23 @@ function resolveGovernancePrincipal(opts) {
3076
3105
  }
3077
3106
  return principal;
3078
3107
  }
3108
+ function canonicalPrincipal(principal) {
3109
+ const s = String(principal ?? "").trim();
3110
+ if (s === "") return "";
3111
+ return s.includes(":") ? s : `user:${s}`;
3112
+ }
3079
3113
  async function signAndPostOffer(ws, domain, directiveId, payload, opts) {
3080
3114
  const cloud = cloudBase(opts.cloud);
3081
3115
  const principal = resolveGovernancePrincipal(opts);
3082
3116
  const holon = await connectGovernance(cloud, ws);
3117
+ const resolvedDomain = domain ?? await discoverSigningDomain(holon, ws, directiveId, void 0);
3083
3118
  const device = await ensureDeviceKey(holon, principal);
3084
3119
  const sealed = await holon.signGovernanceOffer({
3085
- domain,
3120
+ domain: resolvedDomain,
3086
3121
  directiveId,
3087
3122
  payload,
3088
3123
  authorSecret: device.secret,
3089
- actor: `user:${principal}`
3124
+ actor: canonicalPrincipal(principal)
3090
3125
  });
3091
3126
  if (sealed.intentBytes.length > STAGE_THRESHOLD_CHARS) {
3092
3127
  const staged = await relaySealedOfferViaStage(cloud, ws, directiveId, sealed.intentBytes, fetch);
@@ -3124,7 +3159,7 @@ async function relaySealedOfferViaStage(cloud, ws, directiveId, intentBytesB64,
3124
3159
  return { sha256: sha2562, size: raw.length, verdict: { ok: r.ok, status: r.status, body } };
3125
3160
  }
3126
3161
  async function signAndPostGovernance(parent, directiveId, payload, opts) {
3127
- return signAndPostOffer(parent, "workspaces", directiveId, payload, opts);
3162
+ return signAndPostOffer(parent, opts.domain, directiveId, payload, opts);
3128
3163
  }
3129
3164
  async function grantOrRevoke(verb, kind, subject, opts) {
3130
3165
  const parent = opts.parent ?? "root";
@@ -3307,20 +3342,29 @@ async function keyEnroll(opts) {
3307
3342
  err2(`could not connect to ${parent} on ${cloud} to enroll: ${e.message}`);
3308
3343
  return 1;
3309
3344
  }
3345
+ const directiveId = opts.directive ?? "enrollSigner";
3346
+ let domain;
3347
+ try {
3348
+ domain = await discoverSigningDomain(holon, parent, directiveId, opts.domain);
3349
+ } catch (e) {
3350
+ err2(`could not resolve the signing directive on ${parent}: ${e.message}`);
3351
+ return 1;
3352
+ }
3310
3353
  const device = await ensureDeviceKey(holon, principal);
3311
3354
  const enrollPayload = await holon.enrollSignerPayload({ principal, publicKey: device.public });
3312
3355
  const sealed = await holon.signGovernanceOffer({
3313
- directiveId: "enrollSigner",
3356
+ domain,
3357
+ directiveId,
3314
3358
  payload: enrollPayload,
3315
3359
  authorSecret: device.secret,
3316
- actor: `user:${principal}`
3360
+ actor: canonicalPrincipal(principal)
3317
3361
  });
3318
- const v = await holon.postGovernanceOffer({ directiveId: "enrollSigner", intentBytes: sealed.intentBytes, ws: parent });
3362
+ const v = await holon.postGovernanceOffer({ directiveId, intentBytes: sealed.intentBytes, ws: parent });
3319
3363
  if (!v.ok) {
3320
- err2(`enrollSigner refused (${v.status}): ${typeof v.body === "string" ? v.body : JSON.stringify(v.body)}`);
3364
+ err2(`${domain}/${directiveId} refused (${v.status}): ${typeof v.body === "string" ? v.body : JSON.stringify(v.body)}`);
3321
3365
  return 1;
3322
3366
  }
3323
- out2(`\u2713 device key enrolled for ${principal} at ${parent} on ${cloud}`);
3367
+ out2(`\u2713 device key enrolled for ${principal} at ${parent} on ${cloud} (via ${domain}/${directiveId})`);
3324
3368
  out2(` keyHash ${device.keyHash.slice(0, 16)}\u2026 (secret stays local; signed governance is now warranted)`);
3325
3369
  return 0;
3326
3370
  }
@@ -3444,8 +3488,9 @@ async function governanceOffer(ws, directiveId, opts) {
3444
3488
  if (opts.save !== void 0) {
3445
3489
  try {
3446
3490
  const holon = await connectGovernance(cloudBase(opts.cloud), ws);
3491
+ const domain = await discoverSigningDomain(holon, ws, directiveId, opts.domain);
3447
3492
  const device = await ensureDeviceKey(holon, principal);
3448
- const sealed = await holon.signGovernanceOffer({ directiveId, payload, authorSecret: device.secret, actor: `user:${principal}` });
3493
+ const sealed = await holon.signGovernanceOffer({ domain, directiveId, payload, authorSecret: device.secret, actor: canonicalPrincipal(principal) });
3449
3494
  writeFileSync4(opts.save, JSON.stringify({ ws, directiveId, principal, intentBytes: sealed.intentBytes }, null, 2) + "\n");
3450
3495
  } catch (e) {
3451
3496
  err2(e.message);
@@ -3727,6 +3772,28 @@ async function buildRebirthPlan(cloud, ws, reason) {
3727
3772
  }
3728
3773
  } catch {
3729
3774
  }
3775
+ const signerTuplesRaw = [];
3776
+ try {
3777
+ const rows2 = query2(eng, ws, "aggregatesByType", JSON.stringify({ __type: "RelationTuple" }));
3778
+ for (const r of rows2) {
3779
+ const data = r?.data ?? {};
3780
+ if (data.relation !== "signer" || typeof data.object !== "string" || !data.object.startsWith("identity:")) continue;
3781
+ const principal = data.object.slice("identity:".length);
3782
+ for (const [subject, leafRaw] of Object.entries(data.subjects ?? {})) {
3783
+ if (!subject.startsWith("key:")) continue;
3784
+ let status2 = leafRaw;
3785
+ if (typeof leafRaw === "string") {
3786
+ try {
3787
+ status2 = JSON.parse(leafRaw).status;
3788
+ } catch {
3789
+ status2 = leafRaw;
3790
+ }
3791
+ } else if (leafRaw !== null && typeof leafRaw === "object") status2 = leafRaw.status;
3792
+ signerTuplesRaw.push({ principal, keyHash: subject.slice(4), status: status2 === "revoked" ? "revoked" : "active" });
3793
+ }
3794
+ }
3795
+ } catch {
3796
+ }
3730
3797
  let compaction = { carried: null, parked: null, scarShape: "unknown" };
3731
3798
  let parkedAllLawInstalls = false;
3732
3799
  try {
@@ -3793,6 +3860,12 @@ async function buildRebirthPlan(cloud, ws, reason) {
3793
3860
  compaction,
3794
3861
  repairs,
3795
3862
  posture: { posture, consent: consentState },
3863
+ ...signerTuplesRaw.length > 0 ? {
3864
+ signerEnrollments: signerTuplesRaw.map((t) => ({
3865
+ ...t,
3866
+ recovery: mode === "state-seed" ? "re-enroll-required" : "carried-by-replay"
3867
+ }))
3868
+ } : {},
3796
3869
  clientImpact: CLIENT_IMPACT,
3797
3870
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
3798
3871
  };
@@ -3849,6 +3922,23 @@ function renderRebirthImpact(plan, ledgerCommits) {
3849
3922
  L.push(" \u2192 preserved by lineage continuity; their birth-cert chains stay valid (same founding key + self-id).");
3850
3923
  }
3851
3924
  L.push("");
3925
+ const se = plan.signerEnrollments ?? [];
3926
+ L.push(`SIGNER ENROLLMENTS (${se.length})`);
3927
+ if (se.length === 0) L.push(" (none on this chain \u2014 no device\u2192principal bindings to recover)");
3928
+ else {
3929
+ for (const s of se) {
3930
+ L.push(` \u2022 identity:${s.principal}#signer @ key:${s.keyHash.slice(0, 16)}\u2026 [${s.status}] \u2192 ${s.recovery}`);
3931
+ }
3932
+ if (se.some((s) => s.recovery === "re-enroll-required")) {
3933
+ L.push(" \u2192 a state-seed re-genesis does NOT carry these tuples: every device that authors against this");
3934
+ L.push(" workspace (e.g. for sibling births) must be lawfully re-enrolled AFTER the rebirth \u2014 client");
3935
+ L.push(" ensureParentSignerEnrollment / NomosScope.home's reopen repair / `githolon key enroll --parent <ws>`.");
3936
+ } else {
3937
+ L.push(" \u2192 replay-compact re-folds the enrolling intents; a PARKED enrolling intent would still strand its");
3938
+ L.push(" binding until re-admitted \u2014 verify with the parked list, or re-enroll idempotently (a no-op when live).");
3939
+ }
3940
+ }
3941
+ L.push("");
3852
3942
  L.push(`REPAIRS (${plan.repairs.length})`);
3853
3943
  if (plan.repairs.length === 0) L.push(" (none matched \u2014 a plain compaction; no corrective verb declared for this refusal)");
3854
3944
  else {
@@ -4051,7 +4141,7 @@ gov rebirth ${ws}:`);
4051
4141
  return 1;
4052
4142
  }
4053
4143
  signSecret = dk.secret;
4054
- actor = `user:${actorUid}`;
4144
+ actor = canonicalPrincipal(actorUid);
4055
4145
  }
4056
4146
  const res = author2(eng, "root", "workspaces", "rebirthWorkspace", { workspaceId, reason, rebornAt }, controllerHash, { authorSecret: signSecret, actor });
4057
4147
  if (res.ok === false) {
@@ -8283,6 +8373,8 @@ var HELP = {
8283
8373
  ["--sha256", "import: derive the seed as sha256(--secret) (a passphrase/preimage)"],
8284
8374
  ["--format <f>", "import: force hex|base64 interpretation (default: auto-detect)"],
8285
8375
  ["--parent <ws>", "enroll: the governance workspace to enroll at (default root)"],
8376
+ ["--domain <d>", "enroll: the domain declaring the signing directive (default: DISCOVERED from the parent's installed law \u2014 never assume 'workspaces')"],
8377
+ ["--directive <id>", "enroll: the signing directive id (default enrollSigner)"],
8286
8378
  ["--yes", "export: confirm printing the secret to stdout"]
8287
8379
  ],
8288
8380
  examples: [
@@ -8294,8 +8386,8 @@ var HELP = {
8294
8386
  ]
8295
8387
  },
8296
8388
  gov: {
8297
- usage: "githolon gov <ws> <directiveId> [--payload <json> | --payload-file <f>] [--as <uid>] [--stamp] [--save <f>] [--cloud <url>]\n githolon gov relay <ws> <offer-file> [--cloud <url>]",
8298
- what: "Author ANY `workspaces` governance directive as a SIGNED offer to <ws> and relay it \u2014 the generic\nlane the grant/ws/key verbs are sugar over. The directive is planned against <ws>'s OWN folded law\n(we connect + sign locally with your enrolled key; the host relays opaque {intentBytes} and the\nkernel re-verifies the signature). SIGNING IS LOCAL: with --save it writes the sealed offer WITHOUT\nrelaying \u2014 sign now, relay later/elsewhere with `gov relay` (the only step that touches the cloud).\nThe acting principal needs an enrolled signing key (githolon key enroll | key import).",
8389
+ usage: "githolon gov <ws> <directiveId> [--domain <d>] [--payload <json> | --payload-file <f>] [--as <uid>] [--stamp] [--save <f>] [--cloud <url>]\n githolon gov relay <ws> <offer-file> [--cloud <url>]",
8390
+ what: "Author ANY governance directive as a SIGNED offer to <ws> and relay it \u2014 the generic lane. The\ndirective's DOMAIN is discovered from <ws>'s installed law (a tenant-composed domain like\n`co2_platform` routes correctly; --domain overrides; ambiguity names the candidates) \u2014 the generic\nlane the grant/ws/key verbs are sugar over. The directive is planned against <ws>'s OWN folded law\n(we connect + sign locally with your enrolled key; the host relays opaque {intentBytes} and the\nkernel re-verifies the signature). SIGNING IS LOCAL: with --save it writes the sealed offer WITHOUT\nrelaying \u2014 sign now, relay later/elsewhere with `gov relay` (the only step that touches the cloud).\nThe acting principal needs an enrolled signing key (githolon key enroll | key import).",
8299
8391
  flags: [
8300
8392
  ["--payload <json>", `the directive payload as a JSON object string ('{"name":"\u2026"}')`],
8301
8393
  ["--payload-file <f>", "read the payload JSON from a file instead"],
@@ -8930,14 +9022,15 @@ Identity (~/.holon/credentials.json \u2014 sessions auto-refresh):
8930
9022
  githolon use [<uid>] [--clear] set/show/clear the ACTIVE signing principal: the one
8931
9023
  deploy/gov/grant/key author as (--as overrides per-cmd)
8932
9024
  githolon logout clear the session (workspace secrets kept)
8933
- githolon key enroll [--principal <uid>] [--parent <ws>] mint a signing device key + enroll it on-chain
9025
+ githolon key enroll [--principal <uid>] [--parent <ws>] [--domain <d>] [--directive <id>] mint a signing device key + enroll it on-chain (the signing directive is DISCOVERED from the parent's installed law; the flags override)
8934
9026
  (secret stays local; warrants SIGNED governance)
8935
9027
  githolon key import --principal <uid> --secret <key> bring an EXISTING admin key into ~/.holon (accepts
8936
9028
  hex / base64 seed, or --sha256 <passphrase>)
8937
9029
  githolon key show | export [--principal <uid>] the device key's public identity (export --yes: secret)
8938
9030
 
8939
9031
  Governance (SIGNED client-side, relayed as {intentBytes} \u2014 never a host-author endpoint):
8940
- githolon gov <ws> <directive> [--payload <json>] author ANY workspaces directive as a signed offer
9032
+ githolon gov <ws> <directive> [--domain <d>] author ANY governance directive (domain discovered
9033
+ [--payload <json>] from installed law; --domain overrides) as a signed offer
8941
9034
  [--as <uid>] [--stamp] [--save <file>] to <ws> (--save signs without relaying; relay later)
8942
9035
  githolon gov relay <ws> <offer-file> relay a saved offer (the only step that hits the cloud)
8943
9036
  githolon gov rebirth <ws> --reason <text> LAWFUL SUPERSESSION (recovery verb 3): supersede a
@@ -8997,7 +9090,7 @@ Options:
8997
9090
  function cloudArgs(rest) {
8998
9091
  const pos = [];
8999
9092
  const opts = {};
9000
- const VALUE_FLAGS = ["--cloud", "--principal", "--as", "--file", "--target", "--parent", "--via", "--max", "--label", "--secret", "--payload", "--payload-file", "--save", "--format", "--with", "--out", "--reason", "--oid", "--seed-secret", "--gcp-secret", "--controller-hash", "--plan", "--depth"];
9093
+ const VALUE_FLAGS = ["--cloud", "--principal", "--as", "--file", "--target", "--parent", "--via", "--max", "--label", "--secret", "--payload", "--payload-file", "--save", "--format", "--with", "--out", "--reason", "--oid", "--seed-secret", "--gcp-secret", "--controller-hash", "--plan", "--depth", "--domain", "--directive"];
9001
9094
  const BOOL_FLAGS = ["--sha256", "--stamp", "--yes", "--clear", "--keep-beside", "--retire-replaced", "--explain-current-interface", "--dry-run", "--json", "--full"];
9002
9095
  for (let i = 0; i < rest.length; i++) {
9003
9096
  const a = rest[i];
@@ -9027,6 +9120,8 @@ function cloudArgs(rest) {
9027
9120
  else if (a === "--save") opts.save = v;
9028
9121
  else if (a === "--format") opts.format = v;
9029
9122
  else if (a === "--with") opts.with = v;
9123
+ else if (a === "--domain") opts.domain = v;
9124
+ else if (a === "--directive") opts.directive = v;
9030
9125
  else if (a === "--out") opts.out = v;
9031
9126
  else if (a === "--max") {
9032
9127
  const n = Number(v);
@@ -9696,7 +9791,7 @@ ${commandHelp("gov") ?? USAGE}`);
9696
9791
  }
9697
9792
  const [ws, directiveId] = pos;
9698
9793
  if (ws === void 0 || directiveId === void 0) {
9699
- process.stderr.write(`error: expected: githolon gov <ws> <directiveId> [--payload <json>] [--as <uid>]
9794
+ process.stderr.write(`error: expected: githolon gov <ws> <directiveId> [--domain <d>] [--payload <json>] [--as <uid>]
9700
9795
 
9701
9796
  ${commandHelp("gov") ?? USAGE}`);
9702
9797
  return 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "githolon",
3
- "version": "0.77.1",
3
+ "version": "0.79.0",
4
4
  "type": "module",
5
5
  "description": "githolon — the Nomos developer CLI: Rails-style generators for @githolon/dsl domains + the package compiler. Kernel-independent.",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -29,8 +29,8 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@bjorn3/browser_wasi_shim": "0.4.2",
32
- "@githolon/client": "^0.77.1",
33
- "@githolon/dsl": "^0.77.1",
32
+ "@githolon/client": "^0.79.0",
33
+ "@githolon/dsl": "^0.79.0",
34
34
  "isomorphic-git": "^1.38.4"
35
35
  },
36
36
  "devDependencies": {