githolon 0.77.1 → 0.78.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 +92 -6
  2. package/package.json +3 -3
package/dist/cli.mjs CHANGED
@@ -3012,6 +3012,7 @@ __export(governance_exports, {
3012
3012
  buildRebirthPlan: () => buildRebirthPlan,
3013
3013
  certifyEra: () => certifyEra,
3014
3014
  consentRebirth: () => consentRebirth,
3015
+ discoverSigningDomain: () => discoverSigningDomain,
3015
3016
  executeRepairRebirth: () => executeRepairRebirth,
3016
3017
  governanceOffer: () => governanceOffer,
3017
3018
  governanceRelay: () => governanceRelay,
@@ -3044,6 +3045,33 @@ async function clientConnect() {
3044
3045
  const mod = await import("@githolon/client");
3045
3046
  return mod.connect;
3046
3047
  }
3048
+ async function discoverSigningDomain(holon, parent, directiveId, explicit) {
3049
+ if (explicit !== void 0 && explicit !== "") return explicit;
3050
+ try {
3051
+ const offer = typeof holon.interfaceOffer === "function" ? await holon.interfaceOffer() : null;
3052
+ const declaring = [];
3053
+ for (const [domain, iface] of Object.entries(offer ?? {})) {
3054
+ if (iface?.directives !== void 0 && Object.prototype.hasOwnProperty.call(iface.directives, directiveId)) declaring.push(domain);
3055
+ }
3056
+ if (declaring.length === 1) return declaring[0];
3057
+ if (declaring.length > 1) {
3058
+ throw new Error(
3059
+ `'${directiveId}' is declared by ${declaring.length} installed domains on ${parent} (${declaring.join(", ")}) \u2014 pass --domain <one of them>`
3060
+ );
3061
+ }
3062
+ } catch (e) {
3063
+ if (/pass --domain/.test(e.message)) throw e;
3064
+ }
3065
+ if (typeof holon.discoverDirectiveDomain === "function") {
3066
+ try {
3067
+ const r = await holon.discoverDirectiveDomain({ ws: parent, directiveId });
3068
+ return r.domain;
3069
+ } catch (e) {
3070
+ throw new Error(`${e.message} (override with --domain <d> [--directive <id>])`);
3071
+ }
3072
+ }
3073
+ return "workspaces";
3074
+ }
3047
3075
  async function connectGovernance(cloud, parent) {
3048
3076
  const authToken = await sessionToken().catch(() => void 0);
3049
3077
  const clientId = `githolon-cli-${Date.now().toString(16)}`;
@@ -3307,20 +3335,29 @@ async function keyEnroll(opts) {
3307
3335
  err2(`could not connect to ${parent} on ${cloud} to enroll: ${e.message}`);
3308
3336
  return 1;
3309
3337
  }
3338
+ const directiveId = opts.directive ?? "enrollSigner";
3339
+ let domain;
3340
+ try {
3341
+ domain = await discoverSigningDomain(holon, parent, directiveId, opts.domain);
3342
+ } catch (e) {
3343
+ err2(`could not resolve the signing directive on ${parent}: ${e.message}`);
3344
+ return 1;
3345
+ }
3310
3346
  const device = await ensureDeviceKey(holon, principal);
3311
3347
  const enrollPayload = await holon.enrollSignerPayload({ principal, publicKey: device.public });
3312
3348
  const sealed = await holon.signGovernanceOffer({
3313
- directiveId: "enrollSigner",
3349
+ domain,
3350
+ directiveId,
3314
3351
  payload: enrollPayload,
3315
3352
  authorSecret: device.secret,
3316
3353
  actor: `user:${principal}`
3317
3354
  });
3318
- const v = await holon.postGovernanceOffer({ directiveId: "enrollSigner", intentBytes: sealed.intentBytes, ws: parent });
3355
+ const v = await holon.postGovernanceOffer({ directiveId, intentBytes: sealed.intentBytes, ws: parent });
3319
3356
  if (!v.ok) {
3320
- err2(`enrollSigner refused (${v.status}): ${typeof v.body === "string" ? v.body : JSON.stringify(v.body)}`);
3357
+ err2(`${domain}/${directiveId} refused (${v.status}): ${typeof v.body === "string" ? v.body : JSON.stringify(v.body)}`);
3321
3358
  return 1;
3322
3359
  }
3323
- out2(`\u2713 device key enrolled for ${principal} at ${parent} on ${cloud}`);
3360
+ out2(`\u2713 device key enrolled for ${principal} at ${parent} on ${cloud} (via ${domain}/${directiveId})`);
3324
3361
  out2(` keyHash ${device.keyHash.slice(0, 16)}\u2026 (secret stays local; signed governance is now warranted)`);
3325
3362
  return 0;
3326
3363
  }
@@ -3727,6 +3764,28 @@ async function buildRebirthPlan(cloud, ws, reason) {
3727
3764
  }
3728
3765
  } catch {
3729
3766
  }
3767
+ const signerTuplesRaw = [];
3768
+ try {
3769
+ const rows2 = query2(eng, ws, "aggregatesByType", JSON.stringify({ __type: "RelationTuple" }));
3770
+ for (const r of rows2) {
3771
+ const data = r?.data ?? {};
3772
+ if (data.relation !== "signer" || typeof data.object !== "string" || !data.object.startsWith("identity:")) continue;
3773
+ const principal = data.object.slice("identity:".length);
3774
+ for (const [subject, leafRaw] of Object.entries(data.subjects ?? {})) {
3775
+ if (!subject.startsWith("key:")) continue;
3776
+ let status2 = leafRaw;
3777
+ if (typeof leafRaw === "string") {
3778
+ try {
3779
+ status2 = JSON.parse(leafRaw).status;
3780
+ } catch {
3781
+ status2 = leafRaw;
3782
+ }
3783
+ } else if (leafRaw !== null && typeof leafRaw === "object") status2 = leafRaw.status;
3784
+ signerTuplesRaw.push({ principal, keyHash: subject.slice(4), status: status2 === "revoked" ? "revoked" : "active" });
3785
+ }
3786
+ }
3787
+ } catch {
3788
+ }
3730
3789
  let compaction = { carried: null, parked: null, scarShape: "unknown" };
3731
3790
  let parkedAllLawInstalls = false;
3732
3791
  try {
@@ -3793,6 +3852,12 @@ async function buildRebirthPlan(cloud, ws, reason) {
3793
3852
  compaction,
3794
3853
  repairs,
3795
3854
  posture: { posture, consent: consentState },
3855
+ ...signerTuplesRaw.length > 0 ? {
3856
+ signerEnrollments: signerTuplesRaw.map((t) => ({
3857
+ ...t,
3858
+ recovery: mode === "state-seed" ? "re-enroll-required" : "carried-by-replay"
3859
+ }))
3860
+ } : {},
3796
3861
  clientImpact: CLIENT_IMPACT,
3797
3862
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
3798
3863
  };
@@ -3849,6 +3914,23 @@ function renderRebirthImpact(plan, ledgerCommits) {
3849
3914
  L.push(" \u2192 preserved by lineage continuity; their birth-cert chains stay valid (same founding key + self-id).");
3850
3915
  }
3851
3916
  L.push("");
3917
+ const se = plan.signerEnrollments ?? [];
3918
+ L.push(`SIGNER ENROLLMENTS (${se.length})`);
3919
+ if (se.length === 0) L.push(" (none on this chain \u2014 no device\u2192principal bindings to recover)");
3920
+ else {
3921
+ for (const s of se) {
3922
+ L.push(` \u2022 identity:${s.principal}#signer @ key:${s.keyHash.slice(0, 16)}\u2026 [${s.status}] \u2192 ${s.recovery}`);
3923
+ }
3924
+ if (se.some((s) => s.recovery === "re-enroll-required")) {
3925
+ L.push(" \u2192 a state-seed re-genesis does NOT carry these tuples: every device that authors against this");
3926
+ L.push(" workspace (e.g. for sibling births) must be lawfully re-enrolled AFTER the rebirth \u2014 client");
3927
+ L.push(" ensureParentSignerEnrollment / NomosScope.home's reopen repair / `githolon key enroll --parent <ws>`.");
3928
+ } else {
3929
+ L.push(" \u2192 replay-compact re-folds the enrolling intents; a PARKED enrolling intent would still strand its");
3930
+ L.push(" binding until re-admitted \u2014 verify with the parked list, or re-enroll idempotently (a no-op when live).");
3931
+ }
3932
+ }
3933
+ L.push("");
3852
3934
  L.push(`REPAIRS (${plan.repairs.length})`);
3853
3935
  if (plan.repairs.length === 0) L.push(" (none matched \u2014 a plain compaction; no corrective verb declared for this refusal)");
3854
3936
  else {
@@ -8283,6 +8365,8 @@ var HELP = {
8283
8365
  ["--sha256", "import: derive the seed as sha256(--secret) (a passphrase/preimage)"],
8284
8366
  ["--format <f>", "import: force hex|base64 interpretation (default: auto-detect)"],
8285
8367
  ["--parent <ws>", "enroll: the governance workspace to enroll at (default root)"],
8368
+ ["--domain <d>", "enroll: the domain declaring the signing directive (default: DISCOVERED from the parent's installed law \u2014 never assume 'workspaces')"],
8369
+ ["--directive <id>", "enroll: the signing directive id (default enrollSigner)"],
8286
8370
  ["--yes", "export: confirm printing the secret to stdout"]
8287
8371
  ],
8288
8372
  examples: [
@@ -8930,7 +9014,7 @@ Identity (~/.holon/credentials.json \u2014 sessions auto-refresh):
8930
9014
  githolon use [<uid>] [--clear] set/show/clear the ACTIVE signing principal: the one
8931
9015
  deploy/gov/grant/key author as (--as overrides per-cmd)
8932
9016
  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
9017
+ 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
9018
  (secret stays local; warrants SIGNED governance)
8935
9019
  githolon key import --principal <uid> --secret <key> bring an EXISTING admin key into ~/.holon (accepts
8936
9020
  hex / base64 seed, or --sha256 <passphrase>)
@@ -8997,7 +9081,7 @@ Options:
8997
9081
  function cloudArgs(rest) {
8998
9082
  const pos = [];
8999
9083
  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"];
9084
+ 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
9085
  const BOOL_FLAGS = ["--sha256", "--stamp", "--yes", "--clear", "--keep-beside", "--retire-replaced", "--explain-current-interface", "--dry-run", "--json", "--full"];
9002
9086
  for (let i = 0; i < rest.length; i++) {
9003
9087
  const a = rest[i];
@@ -9027,6 +9111,8 @@ function cloudArgs(rest) {
9027
9111
  else if (a === "--save") opts.save = v;
9028
9112
  else if (a === "--format") opts.format = v;
9029
9113
  else if (a === "--with") opts.with = v;
9114
+ else if (a === "--domain") opts.domain = v;
9115
+ else if (a === "--directive") opts.directive = v;
9030
9116
  else if (a === "--out") opts.out = v;
9031
9117
  else if (a === "--max") {
9032
9118
  const n = Number(v);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "githolon",
3
- "version": "0.77.1",
3
+ "version": "0.78.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.78.0",
33
+ "@githolon/dsl": "^0.78.0",
34
34
  "isomorphic-git": "^1.38.4"
35
35
  },
36
36
  "devDependencies": {