githolon 0.17.0 → 0.19.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 +597 -233
  2. package/package.json +3 -2
package/dist/cli.mjs CHANGED
@@ -9,14 +9,172 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
+ // src/governance.ts
13
+ var governance_exports = {};
14
+ __export(governance_exports, {
15
+ grantOrRevoke: () => grantOrRevoke,
16
+ keyEnroll: () => keyEnroll,
17
+ keyShow: () => keyShow,
18
+ resolveGovernancePrincipal: () => resolveGovernancePrincipal,
19
+ signAndPostGovernance: () => signAndPostGovernance
20
+ });
21
+ import { connect } from "@githolon/client";
22
+ async function connectGovernance(cloud, parent) {
23
+ const authToken = await sessionToken().catch(() => void 0);
24
+ const clientId = `githolon-cli-${Date.now().toString(16)}`;
25
+ return await connect({
26
+ cloud,
27
+ workspace: parent,
28
+ clientId,
29
+ ...authToken !== void 0 ? { authToken } : {}
30
+ });
31
+ }
32
+ async function ensureDeviceKey(holon, principal) {
33
+ const existing = getDeviceKey(principal);
34
+ if (existing !== void 0) return existing;
35
+ const minted = await holon.authorKeygen();
36
+ if (typeof minted.secret !== "string" || typeof minted.public !== "string") {
37
+ throw new Error("authorKeygen did not return a {secret, public} device key");
38
+ }
39
+ const payload = await holon.enrollSignerPayload({ principal, publicKey: minted.public });
40
+ const key = { secret: minted.secret, public: minted.public, keyHash: payload.keyHash };
41
+ setDeviceKey(principal, key);
42
+ return key;
43
+ }
44
+ function resolveGovernancePrincipal(opts) {
45
+ const principal = opts.principal ?? currentPrincipal();
46
+ if (principal === void 0) {
47
+ throw new Error(
48
+ "signed governance needs a principal \u2014 githolon login --agent (self-onboarding), or pass --principal <your-auth-uid>"
49
+ );
50
+ }
51
+ return principal;
52
+ }
53
+ async function signAndPostGovernance(parent, directiveId, payload, opts) {
54
+ const cloud = cloudBase(opts.cloud);
55
+ const principal = resolveGovernancePrincipal(opts);
56
+ const holon = await connectGovernance(cloud, parent);
57
+ const device = await ensureDeviceKey(holon, principal);
58
+ const sealed = await holon.signGovernanceOffer({
59
+ directiveId,
60
+ payload,
61
+ authorSecret: device.secret,
62
+ actor: `user:${principal}`
63
+ });
64
+ const verdict = await holon.postGovernanceOffer({ directiveId, intentBytes: sealed.intentBytes, ws: parent });
65
+ return { ...verdict, principal };
66
+ }
67
+ async function grantOrRevoke(verb, kind, subject, opts) {
68
+ const parent = opts.parent ?? "root";
69
+ const now = Date.now();
70
+ let directiveId;
71
+ let payload;
72
+ const granter = (() => {
73
+ try {
74
+ return resolveGovernancePrincipal(opts);
75
+ } catch (e) {
76
+ err(e.message);
77
+ return void 0;
78
+ }
79
+ })();
80
+ if (granter === void 0) return 1;
81
+ const attribution = verb === "grant" ? { grantedBy: granter, grantedAt: now } : { revokedBy: granter, revokedAt: now };
82
+ if (kind === "admin" || kind === "creation") {
83
+ directiveId = (verb === "grant" ? "grant" : "revoke") + (kind === "admin" ? "Admin" : "Creation");
84
+ payload = { principal: subject, ...verb === "grant" ? { label: opts.label ?? `${kind} via githolon` } : {}, ...attribution };
85
+ } else if (kind === "quota" && verb === "grant") {
86
+ if (typeof opts.max !== "number" || !Number.isInteger(opts.max) || opts.max <= 0) {
87
+ err("grant quota needs --max <positive int> (the principal's workspace allowance)");
88
+ return 1;
89
+ }
90
+ directiveId = "grantQuota";
91
+ payload = { principal: subject, maxWorkspaces: opts.max, ...attribution };
92
+ } else {
93
+ err(`unsupported: githolon ${verb} ${kind} \u2014 expected ${verb} <admin|creation${verb === "grant" ? "|quota" : ""}> <principal>`);
94
+ return 1;
95
+ }
96
+ let v;
97
+ try {
98
+ v = await signAndPostGovernance(parent, directiveId, payload, opts);
99
+ } catch (e) {
100
+ err(e.message);
101
+ return 1;
102
+ }
103
+ if (!v.ok) {
104
+ err(`${directiveId} refused (${v.status}): ${typeof v.body === "string" ? v.body : JSON.stringify(v.body)}`);
105
+ return 1;
106
+ }
107
+ out(`\u2713 ${directiveId} ${subject} via signed governance offer to ${parent}`);
108
+ return 0;
109
+ }
110
+ async function keyEnroll(opts) {
111
+ const cloud = cloudBase(opts.cloud);
112
+ const principal = opts.principal ?? currentPrincipal();
113
+ if (principal === void 0) {
114
+ err("enroll needs a principal \u2014 githolon login --agent first (or --principal <uid>)");
115
+ return 1;
116
+ }
117
+ const parent = opts.parent ?? "root";
118
+ let holon;
119
+ try {
120
+ holon = await connectGovernance(cloud, parent);
121
+ } catch (e) {
122
+ err(`could not connect to ${parent} on ${cloud} to enroll: ${e.message}`);
123
+ return 1;
124
+ }
125
+ const device = await ensureDeviceKey(holon, principal);
126
+ const enrollPayload = await holon.enrollSignerPayload({ principal, publicKey: device.public });
127
+ const sealed = await holon.signGovernanceOffer({
128
+ directiveId: "enrollSigner",
129
+ payload: enrollPayload,
130
+ authorSecret: device.secret,
131
+ actor: `user:${principal}`
132
+ });
133
+ const v = await holon.postGovernanceOffer({ directiveId: "enrollSigner", intentBytes: sealed.intentBytes, ws: parent });
134
+ if (!v.ok) {
135
+ err(`enrollSigner refused (${v.status}): ${typeof v.body === "string" ? v.body : JSON.stringify(v.body)}`);
136
+ return 1;
137
+ }
138
+ out(`\u2713 device key enrolled for ${principal} at ${parent} on ${cloud}`);
139
+ out(` keyHash ${device.keyHash.slice(0, 16)}\u2026 (secret stays local; signed governance is now warranted)`);
140
+ return 0;
141
+ }
142
+ async function keyShow(opts) {
143
+ const principal = opts.principal ?? currentPrincipal();
144
+ if (principal === void 0) {
145
+ out("no principal on file \u2014 githolon login --agent, or pass --principal <uid>");
146
+ return 0;
147
+ }
148
+ const device = getDeviceKey(principal);
149
+ if (device === void 0) {
150
+ out(`no signing device key for ${principal} yet \u2014 githolon key enroll mints + enrolls one`);
151
+ return 0;
152
+ }
153
+ out(`device key for ${principal}:`);
154
+ out(` public ${device.public}`);
155
+ out(` keyHash ${device.keyHash} (the on-chain key:<hash> leaf; secret stays local)`);
156
+ return 0;
157
+ }
158
+ var out, err;
159
+ var init_governance = __esm({
160
+ "src/governance.ts"() {
161
+ "use strict";
162
+ init_cloud();
163
+ out = (s) => void process.stdout.write(s + "\n");
164
+ err = (s) => void process.stderr.write("error: " + s + "\n");
165
+ }
166
+ });
167
+
12
168
  // src/cloud.ts
13
169
  var cloud_exports = {};
14
170
  __export(cloud_exports, {
15
171
  cloudBase: () => cloudBase,
16
172
  configDir: () => configDir,
173
+ currentPrincipal: () => currentPrincipal,
17
174
  deploy: () => deploy,
18
175
  describeLawMove: () => describeLawMove,
19
176
  discoverDeployJson: () => discoverDeployJson,
177
+ getDeviceKey: () => getDeviceKey,
20
178
  getSecret: () => getSecret,
21
179
  loadCreds: () => loadCreds,
22
180
  login: () => login,
@@ -25,10 +183,12 @@ __export(cloud_exports, {
25
183
  secretRotate: () => secretRotate,
26
184
  secretSet: () => secretSet,
27
185
  sessionToken: () => sessionToken,
186
+ setDeviceKey: () => setDeviceKey,
28
187
  setPrincipal: () => setPrincipal,
29
188
  setSecret: () => setSecret,
30
189
  whoami: () => whoami,
31
190
  wsCreate: () => wsCreate,
191
+ wsReclaim: () => wsReclaim,
32
192
  wsRetire: () => wsRetire,
33
193
  wsStatus: () => wsStatus
34
194
  });
@@ -72,6 +232,18 @@ function setPrincipal(principal) {
72
232
  c.principal = principal;
73
233
  saveCreds(c);
74
234
  }
235
+ function currentPrincipal() {
236
+ const c = loadCreds();
237
+ return c.session?.uid ?? c.principal;
238
+ }
239
+ function getDeviceKey(principal) {
240
+ return loadCreds().devices?.[principal];
241
+ }
242
+ function setDeviceKey(principal, key) {
243
+ const c = loadCreds();
244
+ (c.devices ??= {})[principal] = key;
245
+ saveCreds(c);
246
+ }
75
247
  function principalHeaders(principal) {
76
248
  return /^[\w-]+\.[\w-]+\.[\w-]+$/.test(principal) ? { "x-nomos-auth": principal } : { "x-nomos-principal": principal };
77
249
  }
@@ -133,7 +305,7 @@ function discoverDeployJson(cwd, explicit) {
133
305
  }
134
306
  async function login(opts) {
135
307
  if (opts.agent !== true && opts.token === void 0) {
136
- err(
308
+ err2(
137
309
  "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"
138
310
  );
139
311
  return 1;
@@ -141,43 +313,67 @@ async function login(opts) {
141
313
  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 }));
142
314
  const s = sessionFromGotrue(d);
143
315
  if (s === void 0) {
144
- err(`login failed: ${d.error_description ?? d.error ?? d.msg ?? JSON.stringify(d)}`);
316
+ err2(`login failed: ${d.error_description ?? d.error ?? d.msg ?? JSON.stringify(d)}`);
145
317
  return 1;
146
318
  }
147
319
  saveSession(s);
148
- out(`\u2713 logged in${s.anonymous ? " (anonymous agent identity \u2014 linkable to a full account later)" : ""}`);
149
- out(` principal uid ${s.uid}`);
150
- out(` session saved \u2192 ${credsPath()} (auto-refreshed; births now ride x-nomos-auth)`);
320
+ out2(`\u2713 logged in${s.anonymous ? " (anonymous agent identity \u2014 linkable to a full account later)" : ""}`);
321
+ out2(` principal uid ${s.uid}`);
322
+ out2(` session saved \u2192 ${credsPath()} (auto-refreshed; births now ride x-nomos-auth)`);
151
323
  return 0;
152
324
  }
153
325
  async function whoami() {
154
326
  const c = loadCreds();
155
327
  if (c.session !== void 0) {
156
- out(`session: ${c.session.anonymous ? "anonymous agent" : "account"} uid ${c.session.uid} (auth ${c.session.url})`);
328
+ out2(`session: ${c.session.anonymous ? "anonymous agent" : "account"} uid ${c.session.uid} (auth ${c.session.url})`);
157
329
  return 0;
158
330
  }
159
331
  if (c.principal !== void 0) {
160
- out(`bare principal (transitional, unverified): ${c.principal}`);
332
+ out2(`bare principal (transitional, unverified): ${c.principal}`);
161
333
  return 0;
162
334
  }
163
- out("not logged in \u2014 githolon login --agent, or pass --principal <uid> per birth");
335
+ out2("not logged in \u2014 githolon login --agent, or pass --principal <uid> per birth");
164
336
  return 0;
165
337
  }
166
338
  async function logout() {
167
339
  const c = loadCreds();
168
340
  delete c.session;
169
341
  saveCreds(c);
170
- out("\u2713 session cleared (stored workspace secrets kept)");
342
+ out2("\u2713 session cleared (stored workspace secrets kept)");
171
343
  return 0;
172
344
  }
173
345
  async function wsCreate(name, opts) {
174
346
  const cloud = cloudBase(opts.cloud);
175
- let principal = opts.principal ?? await sessionToken().catch((e) => (err(e.message), void 0)) ?? loadCreds().principal;
347
+ if (opts.via !== void 0) {
348
+ const { signAndPostGovernance: signAndPostGovernance2, resolveGovernancePrincipal: resolveGovernancePrincipal2 } = await Promise.resolve().then(() => (init_governance(), governance_exports));
349
+ let v;
350
+ try {
351
+ const principal2 = resolveGovernancePrincipal2(opts);
352
+ v = await signAndPostGovernance2(
353
+ opts.via,
354
+ "createWorkspace",
355
+ { name, tier: "edge", parent: opts.via === "root" ? "" : opts.via, principal: principal2, requestedBy: principal2, requestedAt: Date.now() },
356
+ opts
357
+ );
358
+ } catch (e) {
359
+ err2(e.message);
360
+ return 1;
361
+ }
362
+ if (!v.ok) {
363
+ err2(`createWorkspace '${name}' refused (${v.status}) at ${opts.via}: ${typeof v.body === "string" ? v.body : JSON.stringify(v.body)}`);
364
+ return 1;
365
+ }
366
+ out2(`\u2713 createWorkspace '${name}' requested via signed governance offer to ${opts.via} on ${cloud}`);
367
+ out2(` the worker provisions it on first touch; deploy is open (kernel-gated)`);
368
+ out2(` (want the direct birth instead? drop --via: githolon ws create ${name})`);
369
+ return 0;
370
+ }
371
+ let principal = opts.principal ?? await sessionToken().catch((e) => (err2(e.message), void 0)) ?? loadCreds().principal;
176
372
  if (principal === void 0) {
177
373
  const d2 = await gotrue("/auth/v1/signup", "{}");
178
374
  const s = sessionFromGotrue(d2);
179
375
  if (s === void 0) {
180
- err(
376
+ err2(
181
377
  `a birth needs a principal, and automatic agent login failed: ${d2.error_description ?? d2.error ?? d2.msg ?? JSON.stringify(d2)}
182
378
  githolon login --agent (self-onboarding), githolon login --token <refresh>,
183
379
  or pass --principal <your-auth-uid or access token> (saved as the default)`
@@ -185,9 +381,9 @@ async function wsCreate(name, opts) {
185
381
  return 1;
186
382
  }
187
383
  saveSession(s);
188
- out(`no principal on file \u2014 self-onboarded an anonymous agent identity (githolon login --agent)`);
189
- out(` principal uid ${s.uid} (linkable to a full account later)`);
190
- out(` session saved \u2192 ${credsPath()} (auto-refreshed; births ride x-nomos-auth)`);
384
+ out2(`no principal on file \u2014 self-onboarded an anonymous agent identity (githolon login --agent)`);
385
+ out2(` principal uid ${s.uid} (linkable to a full account later)`);
386
+ out2(` session saved \u2192 ${credsPath()} (auto-refreshed; births ride x-nomos-auth)`);
191
387
  principal = s.accessToken;
192
388
  }
193
389
  const r = await fetch(`${cloud}/v2/workspaces/${name}`, {
@@ -196,20 +392,12 @@ async function wsCreate(name, opts) {
196
392
  });
197
393
  const d = await r.json().catch(() => void 0);
198
394
  if (!r.ok || d?.ok !== true) {
199
- err(`create '${name}' refused (${r.status}): ${d ? JSON.stringify(d) : "no body"}`);
395
+ err2(`create '${name}' refused (${r.status}): ${d ? JSON.stringify(d) : "no body"}`);
200
396
  return 1;
201
397
  }
202
398
  if (opts.principal !== void 0) setPrincipal(opts.principal);
203
- if (typeof d.workspaceSecret === "string") {
204
- setSecret(cloud, name, d.workspaceSecret);
205
- out(`\u2713 workspace ${name} created on ${cloud}`);
206
- out(`\u2713 workspaceSecret saved \u2192 ${credsPath()} (githolon deploy uses it automatically)`);
207
- } else {
208
- out(`\u2713 workspace ${name} already exists on ${cloud} (no new secret issued)`);
209
- if (getSecret(cloud, name) === void 0) {
210
- out(` no stored secret for it here \u2014 if you own it: githolon secret set ${name} <secret>`);
211
- }
212
- }
399
+ out2(`\u2713 workspace ${name} created on ${cloud}`);
400
+ out2(` deploy is open (kernel-gated): githolon deploy ${name}`);
213
401
  return 0;
214
402
  }
215
403
  async function wsStatus(name, opts) {
@@ -217,39 +405,61 @@ async function wsStatus(name, opts) {
217
405
  const r = await fetch(`${cloud}/v2/workspaces/${name}`);
218
406
  const text = await r.text();
219
407
  if (!r.ok) {
220
- err(`status '${name}' (${r.status}): ${text}`);
408
+ err2(`status '${name}' (${r.status}): ${text}`);
221
409
  return 1;
222
410
  }
223
- out(text);
411
+ out2(text);
224
412
  return 0;
225
413
  }
226
414
  async function wsRetire(ws, opts) {
227
415
  const cloud = cloudBase(opts.cloud);
228
- const secret = getSecret(cloud, ws);
229
- if (secret === void 0) {
230
- err(
231
- `no stored secret for ${ws} on ${cloud} \u2014 retirement is owner-gated
232
- githolon secret set ${ws} <secret> (import the workspaceSecret you hold)`
416
+ const { signAndPostGovernance: signAndPostGovernance2, resolveGovernancePrincipal: resolveGovernancePrincipal2 } = await Promise.resolve().then(() => (init_governance(), governance_exports));
417
+ const parent = opts.parent ?? "root";
418
+ let v;
419
+ try {
420
+ const principal = resolveGovernancePrincipal2(opts);
421
+ v = await signAndPostGovernance2(
422
+ parent,
423
+ "retireWorkspace",
424
+ { workspaceId: ws, retiredBy: principal, retiredAt: Date.now() },
425
+ opts
233
426
  );
427
+ } catch (e) {
428
+ err2(e.message);
234
429
  return 1;
235
430
  }
236
- const r = await fetch(`${cloud}/v2/workspaces/${ws}/retire`, {
237
- method: "POST",
238
- headers: { authorization: `Bearer ${secret}` }
239
- });
240
- const d = await r.json().catch(() => void 0);
241
- if (r.status === 401) {
242
- err(`retire refused (401) \u2014 the stored secret for ${ws} is not the workspace's. githolon secret set ${ws} <secret>`);
431
+ if (!v.ok) {
432
+ err2(`retire '${ws}' refused (${v.status}): ${typeof v.body === "string" ? v.body : JSON.stringify(v.body)}`);
243
433
  return 1;
244
434
  }
245
- if (!r.ok || d?.ok !== true) {
246
- err(`retire '${ws}' failed (${r.status}): ${d ? JSON.stringify(d) : "no body"}`);
435
+ out2(`\u2713 workspace ${ws} retired via signed governance offer to ${parent} on ${cloud}`);
436
+ out2(" nothing was deleted \u2014 the ledger and its data are retained per the Nomos Pre-Release License");
437
+ out2(" it no longer counts toward your workspace allowance; reads keep serving, deploys/pushes refuse");
438
+ return 0;
439
+ }
440
+ async function wsReclaim(ws, opts) {
441
+ const cloud = cloudBase(opts.cloud);
442
+ const { signAndPostGovernance: signAndPostGovernance2, resolveGovernancePrincipal: resolveGovernancePrincipal2 } = await Promise.resolve().then(() => (init_governance(), governance_exports));
443
+ const parent = opts.parent ?? "root";
444
+ let v;
445
+ try {
446
+ const principal = resolveGovernancePrincipal2(opts);
447
+ v = await signAndPostGovernance2(
448
+ parent,
449
+ "reclaimWorkspace",
450
+ { workspaceId: ws, reclaimedBy: principal, reclaimedAt: Date.now() },
451
+ opts
452
+ );
453
+ } catch (e) {
454
+ err2(e.message);
247
455
  return 1;
248
456
  }
249
- out(`\u2713 workspace ${ws} retired on ${cloud}${d.alreadyRetired === true ? " (it was already retired)" : ""}`);
250
- out(" nothing was deleted \u2014 the ledger and its data are retained per the Nomos Pre-Release License");
251
- out(" it no longer counts toward your workspace allowance; reads keep serving, deploys/pushes refuse");
252
- out(" to un-retire: contact the operator (an admin restores the record via root's governance offer, POST /v2/workspaces/root/createWorkspace)");
457
+ if (!v.ok) {
458
+ err2(`reclaim '${ws}' refused (${v.status}): ${typeof v.body === "string" ? v.body : JSON.stringify(v.body)}`);
459
+ return 1;
460
+ }
461
+ out2(`\u2713 workspace ${ws} reclaimed via signed governance offer to ${parent} on ${cloud}`);
462
+ out2(" the custodian drops the ledger bytes on seeing this fact folded; the reclaimed FACT is append-only");
253
463
  return 0;
254
464
  }
255
465
  async function deploy(ws, opts) {
@@ -258,37 +468,24 @@ async function deploy(ws, opts) {
258
468
  try {
259
469
  file = discoverDeployJson(process.cwd(), opts.file);
260
470
  } catch (e) {
261
- err(e.message);
262
- return 1;
263
- }
264
- const secret = getSecret(cloud, ws);
265
- if (secret === void 0) {
266
- err(
267
- `no stored secret for ${ws} on ${cloud}
268
- githolon ws create ${ws} (a fresh workspace saves its secret here)
269
- githolon secret set ${ws} <secret> (import one you already hold)`
270
- );
471
+ err2(e.message);
271
472
  return 1;
272
473
  }
273
474
  const before = await fetch(`${cloud}/v2/workspaces/${ws}/domains`).then((br) => br.json()).then((bd) => bd.ok === true ? bd.domains ?? [] : []).catch(() => []);
274
475
  const r = await fetch(`${cloud}/v2/workspaces/${ws}/domains`, {
275
476
  method: "POST",
276
- headers: { "content-type": "application/json", authorization: `Bearer ${secret}` },
477
+ headers: { "content-type": "application/json" },
277
478
  body: readFileSync(file, "utf8")
278
479
  });
279
480
  const d = await r.json().catch(() => void 0);
280
- if (r.status === 401) {
281
- err(`deploy refused (401) \u2014 the stored secret for ${ws} is not the workspace's. githolon secret set ${ws} <secret>`);
282
- return 1;
283
- }
284
481
  if (!r.ok || d?.ok !== true) {
285
- err(`deploy '${ws}' failed (${r.status}): ${d ? JSON.stringify(d) : "no body"}`);
482
+ err2(`deploy '${ws}' failed (${r.status}): ${d ? JSON.stringify(d) : "no body"}`);
286
483
  return 1;
287
484
  }
288
485
  const phase = d.installation?.[0]?.data?.["status.phase"];
289
- out(`\u2713 deployed ${file.split("/").pop()} \u2192 ${ws}${typeof phase === "string" ? ` (${phase})` : ""}`);
486
+ out2(`\u2713 deployed ${file.split("/").pop()} \u2192 ${ws}${typeof phase === "string" ? ` (${phase})` : ""}`);
290
487
  if (typeof d.domainHash === "string") {
291
- out(` law ${describeLawMove(before, d.domainHash)}`);
488
+ out2(` law ${describeLawMove(before, d.domainHash)}`);
292
489
  }
293
490
  return 0;
294
491
  }
@@ -300,30 +497,19 @@ function describeLawMove(before, newHash) {
300
497
  }
301
498
  async function secretSet(ws, secret, opts) {
302
499
  setSecret(cloudBase(opts.cloud), ws, secret);
303
- out(`\u2713 secret for ${ws} saved \u2192 ${credsPath()}`);
500
+ out2(`\u2713 git push password for ${ws} saved \u2192 ${credsPath()} (used by the push-to-create birth lane)`);
304
501
  return 0;
305
502
  }
306
503
  async function secretRotate(ws, opts) {
307
504
  const cloud = cloudBase(opts.cloud);
308
- const current = getSecret(cloud, ws);
309
- if (current === void 0) {
310
- err(`no stored secret for ${ws} on ${cloud} \u2014 githolon secret set ${ws} <secret> first`);
311
- return 1;
312
- }
313
- const r = await fetch(`${cloud}/v2/workspaces/${ws}/rotate-secret`, {
314
- method: "POST",
315
- headers: { authorization: `Bearer ${current}` }
316
- });
317
- const d = await r.json().catch(() => void 0);
318
- if (!r.ok || d?.ok !== true || typeof d.workspaceSecret !== "string") {
319
- err(`rotate '${ws}' failed (${r.status}): ${d ? JSON.stringify(d) : "no body"}`);
320
- return 1;
321
- }
322
- setSecret(cloud, ws, d.workspaceSecret);
323
- out(`\u2713 secret for ${ws} rotated and saved \u2192 ${credsPath()}`);
324
- return 0;
505
+ err2(
506
+ `secret rotate is retired (host-authority strip) \u2014 there is no host-held workspace secret on ${cloud}
507
+ ownership is sha256(push password) bound in the chain; re-own by re-birthing via the upload lane
508
+ with a new push password (see: githolon git remote ${ws} && git push nomos main)`
509
+ );
510
+ return 1;
325
511
  }
326
- var DEFAULT_CLOUD, DEFAULT_AUTH_URL, DEFAULT_AUTH_ANON_KEY, secretKey, out, err;
512
+ var DEFAULT_CLOUD, DEFAULT_AUTH_URL, DEFAULT_AUTH_ANON_KEY, secretKey, out2, err2;
327
513
  var init_cloud = __esm({
328
514
  "src/cloud.ts"() {
329
515
  "use strict";
@@ -331,8 +517,8 @@ var init_cloud = __esm({
331
517
  DEFAULT_AUTH_URL = "https://kjbcjkihxskuwwfdqklt.supabase.co";
332
518
  DEFAULT_AUTH_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtqYmNqa2loeHNrdXd3ZmRxa2x0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTA3MDU2OTAsImV4cCI6MjA2NjI4MTY5MH0.V9e7XsuTlTOLqefOIedTqlBiTxUSn4O5FZSPWwAxiSI";
333
519
  secretKey = (cloud, ws) => `${cloud} ${ws}`;
334
- out = (s) => void process.stdout.write(s + "\n");
335
- err = (s) => void process.stderr.write("error: " + s + "\n");
520
+ out2 = (s) => void process.stdout.write(s + "\n");
521
+ err2 = (s) => void process.stderr.write("error: " + s + "\n");
336
522
  }
337
523
  });
338
524
 
@@ -453,13 +639,13 @@ var init_tree = __esm({
453
639
  function concatBytes(chunks) {
454
640
  let n = 0;
455
641
  for (const c of chunks) n += c.length;
456
- const out9 = new Uint8Array(n);
642
+ const out10 = new Uint8Array(n);
457
643
  let o = 0;
458
644
  for (const c of chunks) {
459
- out9.set(c, o);
645
+ out10.set(c, o);
460
646
  o += c.length;
461
647
  }
462
- return out9;
648
+ return out10;
463
649
  }
464
650
  function pkt(payload) {
465
651
  const body = typeof payload === "string" ? enc2.encode(payload) : payload;
@@ -612,13 +798,13 @@ async function sha256hex(t) {
612
798
  function osEntropyBuffer(n) {
613
799
  const bytes = new Uint8Array(n * 8);
614
800
  for (let o = 0; o < bytes.length; o += 65536) crypto.getRandomValues(bytes.subarray(o, Math.min(o + 65536, bytes.length)));
615
- const out9 = new Array(n), T = 2 ** 53;
801
+ const out10 = new Array(n), T = 2 ** 53;
616
802
  for (let i = 0; i < n; i++) {
617
803
  let z = 0n;
618
804
  for (let b = 7; b >= 0; b--) z = z << 8n | BigInt(bytes[i * 8 + b]);
619
- out9[i] = Number(z >> 11n) / T;
805
+ out10[i] = Number(z >> 11n) / T;
620
806
  }
621
- return out9;
807
+ return out10;
622
808
  }
623
809
  function stringifyBig(o) {
624
810
  return JSON.stringify(o, (_k, v) => typeof v === "bigint" ? `@@B:${v}@@` : v).replace(/"@@B:(\d+)@@"/g, "$1");
@@ -716,13 +902,19 @@ function mintId(eng, typeTag) {
716
902
  }
717
903
  function author(eng, ws, domain, directiveId, payload, controllerHash, opts = {}) {
718
904
  const seq = eng.seq++;
719
- const envelope = { payload: { domain, directiveId, payload }, captured_ports: { clock: { physical: Date.now(), logical: 0, replica: eng.replica }, rng: osEntropyBuffer(opts.rngDraws ?? 64) }, policy_version: 1, policy_domain: "Nomos", policy_gas: 0, policy_memory: 0 };
905
+ const capPorts = { clock: { physical: Date.now(), logical: 0, replica: eng.replica }, rng: osEntropyBuffer(opts.rngDraws ?? 64) };
906
+ if (opts.validFromEpoch !== void 0) capPorts.valid_from_epoch = opts.validFromEpoch;
907
+ if (opts.principalVerified !== void 0) {
908
+ capPorts.principal = opts.actor ?? "";
909
+ capPorts.principal_verified = !!opts.principalVerified;
910
+ }
911
+ const envelope = { payload: { domain, directiveId, payload }, captured_ports: capPorts, policy_version: 1, policy_domain: "Nomos", policy_gas: 0, policy_memory: 0 };
720
912
  writeWork(eng, `payload-${seq}.json`, enc3.encode(JSON.stringify(payload)));
721
913
  writeWork(eng, `envelope-${seq}.json`, enc3.encode(stringifyBig(envelope)));
722
914
  const genesis = !controllerHash;
723
915
  const defer = opts.deferProjection !== false;
724
- const v = JSON.parse(call(eng.ex, "offer", { repoArg: repoArgOf(ws), workspace: ws, domain, directiveId, payloadFile: `/work/payload-${seq}.json`, envelopeFile: `/work/envelope-${seq}.json`, seq, actor: opts.actor ?? "", domainFile: genesis ? "/work/domain.package.usda" : "", domainHash: genesis ? "" : controllerHash, branch: BRANCH, ...opts.authorSecret ? { authorSecret: opts.authorSecret } : {}, ...opts.dryRun ? { dryRun: true } : defer ? { deferProjection: true } : {} }, eng.STDERR));
725
- const res = v.outcome === "admitted" ? { ok: true, head: v.head, intentOut: v.intentOut } : v.outcome === "refused" ? { ok: false, error: v.verdict?.reason ?? v.error } : v;
916
+ const v = JSON.parse(call(eng.ex, "offer", { repoArg: repoArgOf(ws), workspace: ws, domain, directiveId, payloadFile: `/work/payload-${seq}.json`, envelopeFile: `/work/envelope-${seq}.json`, seq, actor: opts.actor ?? "", ...opts.authToken ? { authToken: opts.authToken } : {}, domainFile: genesis ? "/work/domain.package.usda" : "", domainHash: genesis ? "" : controllerHash, branch: BRANCH, ...opts.authorSecret ? { authorSecret: opts.authorSecret } : {}, ...opts.dryRun ? { dryRun: true } : defer ? { deferProjection: true } : {} }, eng.STDERR));
917
+ const res = v.outcome === "admitted" ? { ok: true, head: v.head, intentOut: v.intentOut, ...v.born ? { born: v.born } : {} } : v.outcome === "refused" ? { ok: false, error: v.verdict?.reason ?? v.error } : v;
726
918
  if (defer && res.ok) (eng.pendingProjection ??= /* @__PURE__ */ new Set()).add(ws);
727
919
  return res;
728
920
  }
@@ -763,7 +955,7 @@ function offerIntent(eng, ws, bytes, opts) {
763
955
  }
764
956
  function offerAdmit(eng, ws, bytes, opts) {
765
957
  const v = offerIntent(eng, ws, bytes, opts);
766
- return v.outcome === "admitted" ? { ok: true, head: v.head } : { ok: false, error: v.verdict?.reason ?? v.error ?? "the offer was refused" };
958
+ return v.outcome === "admitted" ? { ok: true, head: v.head, ...v.born ? { born: v.born } : {} } : { ok: false, error: v.verdict?.reason ?? v.error ?? "the offer was refused" };
767
959
  }
768
960
  function applyIntentBytes(eng, ws, bytes, opts) {
769
961
  return offerAdmit(eng, ws, bytes, opts);
@@ -919,21 +1111,21 @@ function sealedIntentOf(eng, ws, res) {
919
1111
  }
920
1112
  function printVerdict(v) {
921
1113
  if (v["valid"] === true) {
922
- out2(`\u2713 chain VALID \u2014 head ${String(v["head"]).slice(0, 12)}\u2026, ${v["intents"]} intent(s), ${v["plansRerun"]} plan(s) re-run`);
923
- out2(` controller ${String(v["controllerHash"]).slice(0, 12)}\u2026 | installed: ${v["installedDomains"]?.map((h) => h.slice(0, 12) + "\u2026").join(", ") ?? "\u2014"}`);
1114
+ out3(`\u2713 chain VALID \u2014 head ${String(v["head"]).slice(0, 12)}\u2026, ${v["intents"]} intent(s), ${v["plansRerun"]} plan(s) re-run`);
1115
+ out3(` controller ${String(v["controllerHash"]).slice(0, 12)}\u2026 | installed: ${v["installedDomains"]?.map((h) => h.slice(0, 12) + "\u2026").join(", ") ?? "\u2014"}`);
924
1116
  return true;
925
1117
  }
926
- err2(`chain INVALID at ${v["check"]}${v["atIndex"] !== void 0 ? ` (intent ${v["atIndex"]})` : ""}: ${v["error"]}`);
1118
+ err3(`chain INVALID at ${v["check"]}${v["atIndex"] !== void 0 ? ` (intent ${v["atIndex"]})` : ""}: ${v["error"]}`);
927
1119
  return false;
928
1120
  }
929
- var out2, err2;
1121
+ var out3, err3;
930
1122
  var init_local_holon = __esm({
931
1123
  "src/local_holon.ts"() {
932
1124
  "use strict";
933
1125
  init_engine();
934
1126
  init_cloud();
935
- out2 = (s) => void process.stdout.write(s + "\n");
936
- err2 = (s) => void process.stderr.write("error: " + s + "\n");
1127
+ out3 = (s) => void process.stdout.write(s + "\n");
1128
+ err3 = (s) => void process.stderr.write("error: " + s + "\n");
937
1129
  }
938
1130
  });
939
1131
 
@@ -941,6 +1133,7 @@ var init_local_holon = __esm({
941
1133
  var proof_offline_exports = {};
942
1134
  __export(proof_offline_exports, {
943
1135
  parseOfflineLegs: () => parseOfflineLegs,
1136
+ runChildBirthLeg: () => runChildBirthLeg,
944
1137
  runOfflineLegs: () => runOfflineLegs,
945
1138
  runOfflineProofStandalone: () => runOfflineProofStandalone
946
1139
  });
@@ -994,7 +1187,7 @@ function parseOfflineLegs(proofSource) {
994
1187
  ...countLeg !== void 0 ? { count: countLeg } : {}
995
1188
  };
996
1189
  }
997
- function runOfflineLegs(eng, ws, lawHash, legs) {
1190
+ function runOfflineLegs(eng, ws, lawHash, legs, frameworkHash) {
998
1191
  const t0 = performance.now();
999
1192
  const done = (ok, lines2, error) => ({
1000
1193
  ok,
@@ -1003,33 +1196,108 @@ function runOfflineLegs(eng, ws, lawHash, legs) {
1003
1196
  ...error !== void 0 ? { error } : {}
1004
1197
  });
1005
1198
  const lines = [];
1006
- const payload = { ...legs.payload };
1007
- const stamp = (/* @__PURE__ */ new Date()).toISOString();
1008
- for (const f of legs.autoStamped) payload[f] = stamp;
1009
- if (legs.mintField !== void 0 && legs.aggregateId !== void 0) {
1010
- payload[legs.mintField] = mintId(eng, legs.aggregateId);
1199
+ const kinds = legs.legs ?? (legs.directiveId !== void 0 ? ["standalone-provable"] : []);
1200
+ if (legs.directiveId !== void 0) {
1201
+ const payload = { ...legs.payload };
1202
+ const stamp = (/* @__PURE__ */ new Date()).toISOString();
1203
+ for (const f of legs.autoStamped ?? []) payload[f] = stamp;
1204
+ if (legs.mintField !== void 0 && legs.aggregateId !== void 0) {
1205
+ payload[legs.mintField] = mintId(eng, legs.aggregateId);
1206
+ }
1207
+ const res = author(eng, ws, legs.domain, legs.directiveId, payload, lawHash);
1208
+ if (res.ok === false) {
1209
+ return done(false, lines, `dispatch ${legs.domain}/${legs.directiveId} refused: ${res.error ?? "unknown"} \u2014 the law itself rejected its own synthesized sample; check the directive's plan/payload schema`);
1210
+ }
1211
+ const createdId = (sealedIntentOf(eng, ws, res)?.events ?? []).find((e) => e.marker === "Create")?.aggregate;
1212
+ lines.push(`\u2713 dispatch ${legs.domain}/${legs.directiveId} \u2014 offline write under the new law${createdId !== void 0 ? ` (${createdId.slice(0, 28)}\u2026)` : ""}`);
1213
+ if (legs.query !== void 0) {
1214
+ const rows = query(eng, ws, legs.query.id, JSON.stringify(legs.query.params));
1215
+ if (rows.length !== 1 || createdId !== void 0 && rows[0].id !== createdId) {
1216
+ return done(false, lines, `declared query ${legs.query.id} expected the 1 created row, got ${JSON.stringify(rows.map((r) => r.id))} \u2014 check the query's key fields against the directive's payload`);
1217
+ }
1218
+ lines.push(`\u2713 declared query ${legs.query.id} answers locally \u2014 1 row`);
1219
+ }
1220
+ if (legs.count !== void 0) {
1221
+ const c = count(eng, ws, legs.count.id, legs.count.group).count;
1222
+ if (c !== 1) {
1223
+ return done(false, lines, `declared count ${legs.count.id}(${JSON.stringify(legs.count.group)}) expected 1, got ${c} \u2014 check the count's grouping against the created row`);
1224
+ }
1225
+ lines.push(`\u2713 declared count ${legs.count.id}(${JSON.stringify(legs.count.group)}) = 1 locally`);
1226
+ }
1227
+ }
1228
+ if (legs.childBirth !== void 0) {
1229
+ const v = runChildBirthLeg(eng, ws, lawHash, legs.childBirth, frameworkHash);
1230
+ lines.push(...v.legs);
1231
+ if (!v.ok) return done(false, lines, v.error);
1232
+ }
1233
+ if (legs.directiveId === void 0 && legs.childBirth === void 0) {
1234
+ lines.push(`\u2713 ${legs.domain} \u2014 compile-valid (the law compiled; no standalone create or .births() to prove a write)${kinds.length ? ` [${kinds.join(", ")}]` : ""}`);
1011
1235
  }
1012
- const res = author(eng, ws, legs.domain, legs.directiveId, payload, lawHash);
1236
+ return done(true, lines);
1237
+ }
1238
+ function runChildBirthLeg(eng, ws, lawHash, cb, frameworkHash) {
1239
+ const t0 = performance.now();
1240
+ const lines = [];
1241
+ const done = (ok, error) => ({ ok, ms: performance.now() - t0, legs: lines, ...error !== void 0 ? { error } : {} });
1242
+ const fw = frameworkHash ?? eng.hashes?.nomos;
1243
+ const payload = { ...cb.birthPayload };
1244
+ if (cb.frameworkHashField !== void 0) payload[cb.frameworkHashField] = fw;
1245
+ if (cb.lawHashField !== void 0) payload[cb.lawHashField] = lawHash;
1246
+ const stamp = (/* @__PURE__ */ new Date()).toISOString();
1247
+ for (const f of cb.birthAutoStamped) payload[f] = stamp;
1248
+ const res = author(eng, ws, cb.parentDomain, cb.birthDirectiveId, payload, lawHash);
1013
1249
  if (res.ok === false) {
1014
- return done(false, lines, `dispatch ${legs.domain}/${legs.directiveId} refused: ${res.error ?? "unknown"} \u2014 the law itself rejected its own synthesized sample; check the directive's plan/payload schema`);
1250
+ 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)`);
1251
+ }
1252
+ const childWs = (res.born ?? []).map((b) => b?.workspace).find((w) => typeof w === "string" && w.length > 0);
1253
+ if (childWs === void 0) {
1254
+ return done(false, `child birth ${cb.parentDomain}/${cb.birthDirectiveId} admitted but the offer-effect spawned no child (born=${JSON.stringify(res.born ?? [])}) \u2014 the run_birth_effect did not fold a child (is the vendored kernel current? does the recipe carry a genesis chain?)`);
1015
1255
  }
1016
- const createdId = (sealedIntentOf(eng, ws, res)?.events ?? []).find((e) => e.marker === "Create")?.aggregate;
1017
- lines.push(`\u2713 dispatch ${legs.domain}/${legs.directiveId} \u2014 offline write under the new law${createdId !== void 0 ? ` (${createdId.slice(0, 28)}\u2026)` : ""}`);
1018
- if (legs.query !== void 0) {
1019
- const rows = query(eng, ws, legs.query.id, JSON.stringify(legs.query.params));
1020
- if (rows.length !== 1 || createdId !== void 0 && rows[0].id !== createdId) {
1021
- return done(false, lines, `declared query ${legs.query.id} expected the 1 created row, got ${JSON.stringify(rows.map((r) => r.id))} \u2014 check the query's key fields against the directive's payload`);
1256
+ lines.push(`\u2713 child birth ${cb.parentDomain}/${cb.birthDirectiveId} \u2014 ${childWs} born (the offer-effect folded the child genesis through the child's OWN gate)`);
1257
+ const installed = (hash) => {
1258
+ try {
1259
+ return qById(eng, childWs, `domain-installation:${hash}`).length > 0;
1260
+ } catch {
1261
+ return false;
1022
1262
  }
1023
- lines.push(`\u2713 declared query ${legs.query.id} answers locally \u2014 1 row`);
1263
+ };
1264
+ if (fw !== void 0 && !installed(fw)) {
1265
+ return done(false, `the born child ${childWs} did not install the framework controller in-chain (domain-installation:${String(fw).slice(0, 12)}\u2026 absent) \u2014 the genesis bootstrap/installDomain did not fold`);
1024
1266
  }
1025
- if (legs.count !== void 0) {
1026
- const c = count(eng, ws, legs.count.id, legs.count.group).count;
1027
- if (c !== 1) {
1028
- return done(false, lines, `declared count ${legs.count.id}(${JSON.stringify(legs.count.group)}) expected 1, got ${c} \u2014 check the count's grouping against the created row`);
1267
+ if (!installed(lawHash)) {
1268
+ return done(false, `the born child ${childWs} did not install the child law in-chain (domain-installation:${lawHash.slice(0, 12)}\u2026 absent) \u2014 the genesis nomos/installDomain did not fold (by-hash byte routing failed: does the parent fold this law?)`);
1269
+ }
1270
+ lines.push(`\u2713 the born child installed the framework + child law IN-CHAIN (the genesis installDomain\xD72 folded)`);
1271
+ let seedRows = [];
1272
+ if (cb.childSeedInstanceId !== void 0) {
1273
+ try {
1274
+ seedRows = qById(eng, childWs, cb.childSeedInstanceId);
1275
+ } catch (e) {
1276
+ return done(false, `reading the child seed (${cb.childSeedInstanceId}) failed: ${e.message}`);
1277
+ }
1278
+ } else if (cb.childQuery !== void 0) {
1279
+ const params = Object.fromEntries(cb.childQuery.key.map((k) => [k, payload[k] ?? ""]));
1280
+ try {
1281
+ seedRows = query(eng, childWs, cb.childQuery.id, JSON.stringify(params));
1282
+ } catch (e) {
1283
+ return done(false, `the child declared query ${cb.childQuery.id} failed: ${e.message}`);
1029
1284
  }
1030
- lines.push(`\u2713 declared count ${legs.count.id}(${JSON.stringify(legs.count.group)}) = 1 locally`);
1031
1285
  }
1032
- return done(true, lines);
1286
+ if ((cb.childSeedInstanceId !== void 0 || cb.childQuery !== void 0) && seedRows.length < 1) {
1287
+ return done(false, `the born child ${childWs} did not seed ${cb.childSeedAggregateId ?? "its genesis record"} (${cb.childSeedDirectiveId} produced no readable row) \u2014 the child genesis seed did not run`);
1288
+ }
1289
+ lines.push(`\u2713 the born child's genesis seed ran \u2014 ${cb.childSeedDirectiveId} produced ${seedRows.length} readable ${cb.childSeedAggregateId ?? "row"}(s) in ${childWs}`);
1290
+ let verdict;
1291
+ try {
1292
+ verdict = verifyChainLocal(eng, childWs);
1293
+ } catch (e) {
1294
+ return done(false, `verify_chain on the born child ${childWs} threw: ${e.message}`);
1295
+ }
1296
+ if (verdict.valid !== true) {
1297
+ return done(false, `the born child ${childWs} FAILED verify_chain at ${verdict.check ?? "?"}${verdict.atIndex !== void 0 ? ` (intent ${verdict.atIndex})` : ""}: ${verdict.error ?? "invalid"} \u2014 the child's own gate refused its genesis`);
1298
+ }
1299
+ 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)`);
1300
+ return done(true);
1033
1301
  }
1034
1302
  async function runOfflineProofStandalone(proofPath, cloud, domain) {
1035
1303
  const src = readFileSync3(proofPath, "utf8");
@@ -1048,7 +1316,13 @@ async function runOfflineProofStandalone(proofPath, cloud, domain) {
1048
1316
  throw new Error(`no provable domain '${chosen}' in this package \u2014 it proves: ${keys.join(", ")} (pick one: githolon proof --domain <key>)`);
1049
1317
  }
1050
1318
  legs = index.domains[chosen];
1051
- if (keys.length > 1) console.log(`githolon proof \u2014 domain '${chosen}' (of ${keys.length}: ${keys.join(", ")}; --domain <key> to switch)`);
1319
+ if (keys.length > 1) {
1320
+ console.log(`githolon proof \u2014 domain '${chosen}' (of ${keys.length}; --domain <key> to switch):`);
1321
+ for (const k of keys) {
1322
+ const kk = index.domains[k].legs ?? (index.domains[k].directiveId !== void 0 ? ["standalone-provable"] : ["compile-valid"]);
1323
+ console.log(` ${k === chosen ? "\u25B8" : " "} ${k.padEnd(20)} ${kk.join(", ")}`);
1324
+ }
1325
+ }
1052
1326
  } else {
1053
1327
  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`);
1054
1328
  legs = parseOfflineLegs(src);
@@ -1061,7 +1335,7 @@ async function runOfflineProofStandalone(proofPath, cloud, domain) {
1061
1335
  await mountFresh(eng, ws);
1062
1336
  author(eng, ws, "bootstrap", "installDomain", installPayload(eng.hashes.nomos, eng.nomosPkg, "githolon-proof"), "");
1063
1337
  author(eng, ws, "nomos", "installDomain", installPayload(lawHash, deployBody.packageUsda, "githolon-proof"), eng.hashes.nomos);
1064
- const v = runOfflineLegs(eng, ws, lawHash, legs);
1338
+ const v = runOfflineLegs(eng, ws, lawHash, legs, eng.hashes.nomos);
1065
1339
  for (const l of v.legs) console.log(l);
1066
1340
  if (!v.ok) {
1067
1341
  console.error(`\u2717 ${v.error}`);
@@ -1267,6 +1541,7 @@ Re-run with --force to replace it.`
1267
1541
 
1268
1542
  // src/cli.ts
1269
1543
  init_cloud();
1544
+ init_governance();
1270
1545
 
1271
1546
  // src/compile.ts
1272
1547
  import { spawn, spawnSync } from "node:child_process";
@@ -1412,8 +1687,8 @@ async function resolveWorkspace(explicit, cwd, target, usageHint) {
1412
1687
 
1413
1688
  // src/dev.ts
1414
1689
  init_proof_offline();
1415
- var out3 = (s) => void process.stdout.write(s + "\n");
1416
- var err3 = (s) => void process.stderr.write("error: " + s + "\n");
1690
+ var out4 = (s) => void process.stdout.write(s + "\n");
1691
+ var err4 = (s) => void process.stderr.write("error: " + s + "\n");
1417
1692
  var WS = "dev";
1418
1693
  var DEFAULT_PORT = 7700;
1419
1694
  var DEBOUNCE_MS = 200;
@@ -1536,21 +1811,21 @@ async function dev(opts) {
1536
1811
  const cwd = process.cwd();
1537
1812
  const cloud = cloudBase(opts.cloud);
1538
1813
  const project = await loadProject(cwd).catch((e) => {
1539
- err3(e.message);
1814
+ err4(e.message);
1540
1815
  return void 0;
1541
1816
  });
1542
1817
  if (project === void 0) {
1543
1818
  if (!existsSync6(join7(cwd, CONFIG_FILE))) {
1544
- err3(`no ${CONFIG_FILE} here (${cwd}) \u2014 \`githolon dev\` runs inside a project; scaffold one from examples/guestbook or \`githolon generate domain <name>\``);
1819
+ err4(`no ${CONFIG_FILE} here (${cwd}) \u2014 \`githolon dev\` runs inside a project; scaffold one from examples/guestbook or \`githolon generate domain <name>\``);
1545
1820
  }
1546
1821
  return 1;
1547
1822
  }
1548
1823
  const creds = loadCreds();
1549
1824
  const installedBy = creds.session?.uid ?? creds.principal ?? "githolon-dev";
1550
- out3(`githolon dev \u2014 ${project.name}: compiling\u2026`);
1825
+ out4(`githolon dev \u2014 ${project.name}: compiling\u2026`);
1551
1826
  const firstCompile = await compileAsync(cwd);
1552
1827
  if (!firstCompile.ok) {
1553
- err3(`compile failed \u2014 the dev loop needs one green compile to mint the holon:
1828
+ err4(`compile failed \u2014 the dev loop needs one green compile to mint the holon:
1554
1829
  ${firstCompile.output}`);
1555
1830
  return 1;
1556
1831
  }
@@ -1558,14 +1833,14 @@ ${firstCompile.output}`);
1558
1833
  try {
1559
1834
  deployBody = readDeployBody(cwd, project.name);
1560
1835
  } catch (e) {
1561
- err3(e.message);
1836
+ err4(e.message);
1562
1837
  return 1;
1563
1838
  }
1564
1839
  let engBoot;
1565
1840
  try {
1566
1841
  engBoot = await holonEngine(cloud, deployBody, installedBy);
1567
1842
  } catch (e) {
1568
- err3(`cannot boot the local holon: ${String(e.message ?? e)}
1843
+ err4(`cannot boot the local holon: ${String(e.message ?? e)}
1569
1844
  the runtime rides ${cloud}/v1/runtime/* and is cached in ~/.holon/runtime after one fetch \u2014 connect once, then dev runs offline`);
1570
1845
  return 1;
1571
1846
  }
@@ -1588,19 +1863,19 @@ ${firstCompile.output}`);
1588
1863
  s.intents++;
1589
1864
  const installed = await installLaw(s, deployBody, installedBy);
1590
1865
  if (!installed.ok) {
1591
- err3(installed.error ?? "law install failed");
1866
+ err4(installed.error ?? "law install failed");
1592
1867
  return 1;
1593
1868
  }
1594
1869
  s.proof = await runProofCycle(s, cwd, deployBody, installedBy);
1595
1870
  if (opts.once === true) {
1596
- out3(statusScreen({ ...s, watchRoots: ["(--once: no watch)"] }));
1871
+ out4(statusScreen({ ...s, watchRoots: ["(--once: no watch)"] }));
1597
1872
  return s.proof === void 0 || s.proof.ok ? 0 : 1;
1598
1873
  }
1599
1874
  let server;
1600
1875
  try {
1601
1876
  server = await startServer(s);
1602
1877
  } catch (e) {
1603
- err3(e.message);
1878
+ err4(e.message);
1604
1879
  return 1;
1605
1880
  }
1606
1881
  const moduleDirs = /* @__PURE__ */ new Set();
@@ -1628,7 +1903,7 @@ ${firstCompile.output}`);
1628
1903
  s.compileMs = run.ms;
1629
1904
  if (!run.ok) {
1630
1905
  s.lastError = run.output.trim() || "compile failed";
1631
- out3(`\u2717 cycle ${s.cycle}: compile failed \u2014 the previous law keeps serving
1906
+ out4(`\u2717 cycle ${s.cycle}: compile failed \u2014 the previous law keeps serving
1632
1907
  ${run.output}`);
1633
1908
  } else {
1634
1909
  s.lastError = void 0;
@@ -1637,16 +1912,16 @@ ${run.output}`);
1637
1912
  const inst = await installLaw(s, body, installedBy);
1638
1913
  if (!inst.ok) {
1639
1914
  s.lastError = inst.error ?? "install failed";
1640
- out3(`\u2717 cycle ${s.cycle}: ${s.lastError}`);
1915
+ out4(`\u2717 cycle ${s.cycle}: ${s.lastError}`);
1641
1916
  } else {
1642
1917
  s.proof = await runProofCycle(s, cwd, body, installedBy);
1643
1918
  const total = performance.now() - t0;
1644
- out3(statusScreen(s));
1645
- out3(` cycle edit \u2192 law-live \u2192 proof in ${total.toFixed(0)} ms total`);
1919
+ out4(statusScreen(s));
1920
+ out4(` cycle edit \u2192 law-live \u2192 proof in ${total.toFixed(0)} ms total`);
1646
1921
  }
1647
1922
  } catch (e) {
1648
1923
  s.lastError = String(e.message ?? e);
1649
- out3(`\u2717 cycle ${s.cycle}: ${s.lastError}`);
1924
+ out4(`\u2717 cycle ${s.cycle}: ${s.lastError}`);
1650
1925
  }
1651
1926
  }
1652
1927
  cycling = false;
@@ -1672,8 +1947,8 @@ ${run.output}`);
1672
1947
  })
1673
1948
  );
1674
1949
  s.watchRoots.push(CONFIG_FILE);
1675
- out3(statusScreen(s));
1676
- out3(" (ctrl-c stops; edits in the watched paths recompile + re-prove automatically)");
1950
+ out4(statusScreen(s));
1951
+ out4(" (ctrl-c stops; edits in the watched paths recompile + re-prove automatically)");
1677
1952
  return new Promise((resolveExit) => {
1678
1953
  const stop = () => {
1679
1954
  for (const w of watchers) w.close();
@@ -1690,8 +1965,8 @@ init_cloud();
1690
1965
  import { spawnSync as spawnSync2 } from "node:child_process";
1691
1966
  import { randomBytes as randomBytes2 } from "node:crypto";
1692
1967
  import { readFileSync as readFileSync5 } from "node:fs";
1693
- var out4 = (s) => void process.stdout.write(s + "\n");
1694
- var err4 = (s) => void process.stderr.write("error: " + s + "\n");
1968
+ var out5 = (s) => void process.stdout.write(s + "\n");
1969
+ var err5 = (s) => void process.stderr.write("error: " + s + "\n");
1695
1970
  function git(args, opts = {}) {
1696
1971
  const r = spawnSync2("git", args, { stdio: opts.quiet === true ? "ignore" : "inherit" });
1697
1972
  return r.status ?? 1;
@@ -1723,31 +1998,31 @@ async function gitCredential(action) {
1723
1998
  if (kv["protocol"] !== "https" || ws === void 0) return 0;
1724
1999
  const cloud = `https://${kv["host"]}`;
1725
2000
  const token = await sessionToken().catch(() => void 0);
1726
- out4(`username=${token ?? "nomos"}`);
1727
- out4(`password=${ensureSecret(cloud, ws)}`);
1728
- out4("");
2001
+ out5(`username=${token ?? "nomos"}`);
2002
+ out5(`password=${ensureSecret(cloud, ws)}`);
2003
+ out5("");
1729
2004
  return 0;
1730
2005
  }
1731
2006
  async function gitSetup(opts) {
1732
2007
  const cloud = cloudBase(opts.cloud);
1733
2008
  const self = process.argv[1];
1734
2009
  if (self === void 0) {
1735
- err4("cannot locate the githolon CLI entry to install as a credential helper");
2010
+ err5("cannot locate the githolon CLI entry to install as a credential helper");
1736
2011
  return 1;
1737
2012
  }
1738
2013
  const helper = `!"${process.execPath}" "${self}" git-credential`;
1739
2014
  if (git(["config", "--global", `credential.${cloud}.helper`, helper]) !== 0 || git(["config", "--global", `credential.${cloud}.useHttpPath`, "true"]) !== 0) {
1740
- err4("git config failed \u2014 is git installed?");
2015
+ err5("git config failed \u2014 is git installed?");
1741
2016
  return 1;
1742
2017
  }
1743
- out4(`\u2713 git credential helper installed for ${cloud} (global gitconfig)`);
1744
- out4(` pushes/clones of ${cloud}/v2/workspaces/<ws>/git now authenticate from ~/.holon`);
2018
+ out5(`\u2713 git credential helper installed for ${cloud} (global gitconfig)`);
2019
+ out5(` pushes/clones of ${cloud}/v2/workspaces/<ws>/git now authenticate from ~/.holon`);
1745
2020
  return 0;
1746
2021
  }
1747
2022
  async function gitRemote(ws, name, opts) {
1748
2023
  const cloud = cloudBase(opts.cloud);
1749
2024
  if (git(["rev-parse", "--git-dir"], { quiet: true }) !== 0) {
1750
- err4("not a git repository \u2014 run inside your repo (git init first)");
2025
+ err5("not a git repository \u2014 run inside your repo (git init first)");
1751
2026
  return 1;
1752
2027
  }
1753
2028
  const setupRc = await gitSetup(opts);
@@ -1756,7 +2031,7 @@ async function gitRemote(ws, name, opts) {
1756
2031
  const url = `${cloud}/v2/workspaces/${ws}/git`;
1757
2032
  if (git(["remote", "add", name, url], { quiet: true }) !== 0) {
1758
2033
  if (git(["remote", "set-url", name, url], { quiet: true }) !== 0) {
1759
- err4(`could not add or update remote '${name}'`);
2034
+ err5(`could not add or update remote '${name}'`);
1760
2035
  return 1;
1761
2036
  }
1762
2037
  }
@@ -1764,7 +2039,7 @@ async function gitRemote(ws, name, opts) {
1764
2039
  if (token === void 0) {
1765
2040
  const principal = loadCreds().principal;
1766
2041
  if (principal === void 0) {
1767
- err4(
2042
+ err5(
1768
2043
  `remote '${name}' \u2192 ${url} is wired, but a BIRTH push needs a principal:
1769
2044
  githolon login --agent (then just push)
1770
2045
  \u2014 or re-run after githolon ws create/login so a principal is on file`
@@ -1772,13 +2047,13 @@ async function gitRemote(ws, name, opts) {
1772
2047
  return 1;
1773
2048
  }
1774
2049
  if (git(["config", `http.${url}.extraHeader`, `x-nomos-principal: ${principal}`]) !== 0) {
1775
- err4("git config failed setting the principal header");
2050
+ err5("git config failed setting the principal header");
1776
2051
  return 1;
1777
2052
  }
1778
2053
  }
1779
- out4(`\u2713 remote '${name}' \u2192 ${url}`);
1780
- out4(` workspace secret on file; ${token !== void 0 ? "births ride your session (verified)" : "births ride x-nomos-principal (bare uid)"}`);
1781
- out4(` birth/deploy by push: git push ${name} main`);
2054
+ out5(`\u2713 remote '${name}' \u2192 ${url}`);
2055
+ out5(` workspace secret on file; ${token !== void 0 ? "births ride your session (verified)" : "births ride x-nomos-principal (bare uid)"}`);
2056
+ out5(` birth/deploy by push: git push ${name} main`);
1782
2057
  return 0;
1783
2058
  }
1784
2059
 
@@ -1858,18 +2133,50 @@ var HELP = {
1858
2133
  what: "Clear the session. Stored workspace secrets are KEPT."
1859
2134
  },
1860
2135
  ws: {
1861
- usage: "githolon ws <create|status|retire> [<name>] [--principal <uid|token>] [--cloud <url>] [--target <name>]",
1862
- what: "Workspace lifecycle on Nomos Cloud. `create` births one (saves its ONE-TIME secret to\n~/.holon); `status` shows lineage/phase/verdicts; `retire` frees the quota slot (owner; nothing\nis deleted). `status` with no <name> resolves the project's `workspace` binding.",
2136
+ usage: "githolon ws <create|status|retire|reclaim> [<name>] [--principal <uid|token>] [--via <parent>] [--parent <ws>] [--cloud <url>] [--target <name>]",
2137
+ what: "Workspace lifecycle on Nomos Cloud. `create` births one (NO host secret is issued \u2014\nownership is a ledger fact; deploy is open + kernel-gated) or, with --via <parent>, requests it\nas a SIGNED createWorkspace governance offer. `status` shows lineage/phase/verdicts. `retire`\nand `reclaim` are SIGNED governance offers to the parent (default root) \u2014 never host-author\nendpoints; retire withdraws the law (data retained), reclaim authorizes the bailiff to drop\ncustody (only a retired workspace; never root). `status` with no <name> resolves the project's\n`workspace` binding.",
1863
2138
  flags: [
1864
2139
  ["--principal <p>", "the birth's principal (uid or access token; remembered as the default)"],
2140
+ ["--via <parent>", "ws create: sign a createWorkspace offer at <parent> instead of the raw birth"],
2141
+ ["--parent <ws>", "retire/reclaim: the governance workspace to sign against (default root)"],
1865
2142
  ["--cloud <url>", "target cloud (default: $NOMOS_CLOUD or the public cloud)"],
1866
2143
  ["--target <name>", "ws status: pick among the project's named workspace targets"]
1867
2144
  ],
1868
- examples: ["githolon ws create my-guestbook", "githolon ws status", "githolon ws retire my-guestbook"]
2145
+ examples: ["githolon ws create my-guestbook", "githolon ws create app1 --via co2", "githolon ws status", "githolon ws retire my-guestbook"]
2146
+ },
2147
+ key: {
2148
+ usage: "githolon key <enroll|show> [--principal <uid>] [--parent <ws>] [--cloud <url>]",
2149
+ what: "The signing DEVICE KEY (ed25519) the SIGNED-governance lane authors with. `enroll` mints one\n(if needed) and enrolls its PUBLIC half on-chain (a signed enrollSigner offer at the parent,\ndefault root) so the warrant gate can prove this device authors as the principal; `show` prints\nthe public identity. The SECRET stays in ~/.holon/credentials.json (0600) \u2014 never on chain.",
2150
+ flags: [
2151
+ ["--principal <p>", "the principal the key authors as (default: the logged-in session uid)"],
2152
+ ["--parent <ws>", "the governance workspace to enroll at (default root)"],
2153
+ ["--cloud <url>", "target cloud"]
2154
+ ],
2155
+ examples: ["githolon key enroll", "githolon key show"]
2156
+ },
2157
+ grant: {
2158
+ usage: "githolon grant <admin|creation|quota> <principal> [--max <N>] [--label <L>] [--parent <ws>] [--cloud <url>]",
2159
+ what: "Grant authority as a SIGNED governance offer to the parent (default root) \u2014 never a host\nendpoint. The grant is attributed to YOUR signing principal; only an admin's signature is\nadmitted by the far-side gate. `admin`/`creation` grant the principal-centric relation; `quota`\ngrants a per-principal workspace allowance (--max). Enroll a device key first (githolon key enroll).",
2160
+ flags: [
2161
+ ["--max <N>", "grant quota: the principal's workspace allowance (positive int)"],
2162
+ ["--label <L>", "grant admin/creation: a provenance label"],
2163
+ ["--parent <ws>", "the governance workspace to sign against (default root)"],
2164
+ ["--cloud <url>", "target cloud"]
2165
+ ],
2166
+ examples: ["githolon grant creation 32c7100e-\u2026", "githolon grant admin 32c7100e-\u2026 --label lieutenant", "githolon grant quota 32c7100e-\u2026 --max 200"]
2167
+ },
2168
+ revoke: {
2169
+ usage: "githolon revoke <admin|creation> <principal> [--parent <ws>] [--cloud <url>]",
2170
+ what: "Revoke authority as a SIGNED governance offer to the parent (default root). The revoked FACT\n(who, when, by whom) rides the chain forever \u2014 audit is structural. Admin-gated, like grant.",
2171
+ flags: [
2172
+ ["--parent <ws>", "the governance workspace to sign against (default root)"],
2173
+ ["--cloud <url>", "target cloud"]
2174
+ ],
2175
+ examples: ["githolon revoke creation 32c7100e-\u2026", "githolon revoke admin 32c7100e-\u2026"]
1869
2176
  },
1870
2177
  deploy: {
1871
2178
  usage: "githolon deploy [<ws>] [--file <deploy.json>] [--cloud <url>] [--target <name>]",
1872
- what: "POST build/*.deploy.json with the stored workspace secret. Prints what MOVED (old \u2192 new law\nhash). With no <ws>, the project's `workspace` binding in nomos.package.mjs resolves it.",
2179
+ 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.",
1873
2180
  flags: [
1874
2181
  ["--file <path>", "an explicit deploy body (default: the one build/*.deploy.json)"],
1875
2182
  ["--target <name>", "pick among named targets (workspace: { dev: \u2026, prod: \u2026 })"],
@@ -1878,9 +2185,9 @@ var HELP = {
1878
2185
  examples: ["githolon deploy", "githolon deploy my-guestbook", "githolon deploy --target prod"]
1879
2186
  },
1880
2187
  secret: {
1881
- usage: "githolon secret <set <ws> <secret> | rotate <ws>> [--cloud <url>]",
1882
- what: "The workspace-secret store (~/.holon/credentials.json, 0600). `set` imports one you already\nhold; `rotate` mints a fresh one (and claims a legacy workspace).",
1883
- examples: ["githolon secret set my-ws nws_v1_\u2026", "githolon secret rotate my-ws"]
2188
+ usage: "githolon secret <set <ws> <push-password> | rotate <ws>> [--cloud <url>]",
2189
+ what: "The git push-password store (~/.holon/credentials.json, 0600) for the push-to-create birth lane\n(sha256(it) becomes the workspace owner bound IN THE CHAIN). The open deploy lane uses no secret.\n`set` imports a push password you already hold; `rotate` is RETIRED (the host holds no ownership\ncredential to rotate \u2014 410).",
2190
+ examples: ["githolon secret set my-ws holon_v1_\u2026"]
1884
2191
  },
1885
2192
  git: {
1886
2193
  usage: "githolon git <setup | remote [<ws>] [<remoteName>]> [--cloud <url>] [--target <name>]",
@@ -1916,20 +2223,20 @@ init_local_holon();
1916
2223
  import { spawnSync as spawnSync3 } from "node:child_process";
1917
2224
  import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
1918
2225
  import { join as join8 } from "node:path";
1919
- var out5 = (s) => void process.stdout.write(s + "\n");
1920
- var err5 = (s) => void process.stderr.write("error: " + s + "\n");
2226
+ var out6 = (s) => void process.stdout.write(s + "\n");
2227
+ var err6 = (s) => void process.stderr.write("error: " + s + "\n");
1921
2228
  var WS2 = "local";
1922
2229
  async function ledgerInit(dir, opts) {
1923
2230
  const cloud = cloudBase(opts.cloud);
1924
2231
  if (existsSync7(dir) && readdirSync3(dir).length > 0) {
1925
- err5(`refusing to mint into non-empty directory: ${dir}`);
2232
+ err6(`refusing to mint into non-empty directory: ${dir}`);
1926
2233
  return 1;
1927
2234
  }
1928
2235
  let deployFile;
1929
2236
  try {
1930
2237
  deployFile = discoverDeployJson(process.cwd(), opts.file);
1931
2238
  } catch (e) {
1932
- err5(e.message);
2239
+ err6(e.message);
1933
2240
  return 1;
1934
2241
  }
1935
2242
  const deployBody = JSON.parse(readFileSync6(deployFile, "utf8"));
@@ -1937,10 +2244,10 @@ async function ledgerInit(dir, opts) {
1937
2244
  const c = loadCreds();
1938
2245
  const principal = await sessionToken().catch(() => void 0) !== void 0 ? c.session.uid : c.principal;
1939
2246
  if (principal === void 0) {
1940
- err5("a holon is born OF someone \u2014 githolon login --agent first (or githolon ws create --principal <uid> once)");
2247
+ err6("a holon is born OF someone \u2014 githolon login --agent first (or githolon ws create --principal <uid> once)");
1941
2248
  return 1;
1942
2249
  }
1943
- out5(`minting a holon locally (law ${domainHash.slice(0, 12)}\u2026, installedBy ${principal})`);
2250
+ out6(`minting a holon locally (law ${domainHash.slice(0, 12)}\u2026, installedBy ${principal})`);
1944
2251
  const { eng } = await holonEngine(cloud, deployBody, principal);
1945
2252
  await mountFresh(eng, WS2);
1946
2253
  author(eng, WS2, "bootstrap", "installDomain", installPayload(eng.hashes.nomos, eng.nomosPkg, principal), "");
@@ -1953,16 +2260,16 @@ async function ledgerInit(dir, opts) {
1953
2260
  const cfgPath = join8(gitDir, "config");
1954
2261
  writeFileSync4(cfgPath, readFileSync6(cfgPath, "utf8").replace(/bare = true/, "bare = false"), "utf8");
1955
2262
  spawnSync3("git", ["-C", dir, "reset", "--hard", "main"], { stdio: "ignore" });
1956
- out5(`\u2713 holon written \u2192 ${dir} (a normal git repo; git log IS the audit trail)`);
1957
- out5(`
2263
+ out6(`\u2713 holon written \u2192 ${dir} (a normal git repo; git log IS the audit trail)`);
2264
+ out6(`
1958
2265
  Birth it on Nomos Cloud (the cloud re-derives this exact verdict):`);
1959
- out5(` cd ${dir} && npx githolon git remote <ws> && git push nomos main`);
2266
+ out6(` cd ${dir} && npx githolon git remote <ws> && git push nomos main`);
1960
2267
  return 0;
1961
2268
  }
1962
2269
  async function ledgerVerify(dir, opts) {
1963
2270
  const gitDir = existsSync7(join8(dir, ".git")) ? join8(dir, ".git") : dir;
1964
2271
  if (!existsSync7(join8(gitDir, "HEAD"))) {
1965
- err5(`${dir} is not a git repo (no HEAD)`);
2272
+ err6(`${dir} is not a git repo (no HEAD)`);
1966
2273
  return 1;
1967
2274
  }
1968
2275
  const { eng } = await holonEngine(cloudBase(opts.cloud), void 0, "verify");
@@ -1980,8 +2287,8 @@ init_local_holon();
1980
2287
  import { existsSync as existsSync8 } from "node:fs";
1981
2288
  import { join as join9 } from "node:path";
1982
2289
  import git2 from "isomorphic-git";
1983
- var out6 = (s) => void process.stdout.write(s + "\n");
1984
- var err6 = (s) => void process.stderr.write("error: " + s + "\n");
2290
+ var out7 = (s) => void process.stdout.write(s + "\n");
2291
+ var err7 = (s) => void process.stderr.write("error: " + s + "\n");
1985
2292
  var SOURCE = "source";
1986
2293
  var REPLAY = "replay";
1987
2294
  function summarizePayload(payload, max = 100) {
@@ -2017,9 +2324,9 @@ function capJsonStrings(v, max = 2048) {
2017
2324
  if (typeof v === "string") return v.length > max ? `${v.slice(0, max)}\u2026(${v.length} chars)` : v;
2018
2325
  if (Array.isArray(v)) return v.map((x) => capJsonStrings(x, max));
2019
2326
  if (typeof v === "object" && v !== null) {
2020
- const out9 = {};
2021
- for (const [k, x] of Object.entries(v)) out9[k] = capJsonStrings(x, max);
2022
- return out9;
2327
+ const out10 = {};
2328
+ for (const [k, x] of Object.entries(v)) out10[k] = capJsonStrings(x, max);
2329
+ return out10;
2023
2330
  }
2024
2331
  return v;
2025
2332
  }
@@ -2066,12 +2373,12 @@ function readKey() {
2066
2373
  async function replay(target, opts) {
2067
2374
  const cloud = cloudBase(opts.cloud);
2068
2375
  const json = opts.json === true;
2069
- const emit = (o) => out6(JSON.stringify(o));
2376
+ const emit = (o) => out7(JSON.stringify(o));
2070
2377
  let eng;
2071
2378
  try {
2072
2379
  ({ eng } = await holonEngine(cloud, void 0, "githolon-replay"));
2073
2380
  } catch (e) {
2074
- err6(`cannot boot the local holon: ${String(e.message ?? e)}
2381
+ err7(`cannot boot the local holon: ${String(e.message ?? e)}
2075
2382
  the runtime rides ${cloud}/v1/runtime/* (cached in ~/.holon/runtime after one fetch)`);
2076
2383
  return 1;
2077
2384
  }
@@ -2079,7 +2386,7 @@ async function replay(target, opts) {
2079
2386
  if (isDir) {
2080
2387
  const gitDir = existsSync8(join9(target, ".git")) ? join9(target, ".git") : target;
2081
2388
  if (!existsSync8(join9(gitDir, "HEAD"))) {
2082
- err6(`${target} is not a git repo (no HEAD) \u2014 pass a holon directory (githolon ledger init <dir>) or a workspace name`);
2389
+ err7(`${target} is not a git repo (no HEAD) \u2014 pass a holon directory (githolon ledger init <dir>) or a workspace name`);
2083
2390
  return 1;
2084
2391
  }
2085
2392
  await mountFresh(eng, SOURCE);
@@ -2087,7 +2394,7 @@ async function replay(target, opts) {
2087
2394
  } else {
2088
2395
  const m = await mountWorkspace(eng, SOURCE, { remote: `${cloud}/v2/workspaces/${target}/git`, headers: {} });
2089
2396
  if (m.restored !== true) {
2090
- err6(
2397
+ err7(
2091
2398
  `workspace '${target}' has no refs/heads/main on ${cloud} \u2014 nothing to replay` + (m.restoreError ? ` (${String(m.restoreError).split(" | ")[0]})` : "") + `
2092
2399
  check the name (githolon ws status ${target}), or pass a local holon directory instead`
2093
2400
  );
@@ -2096,7 +2403,7 @@ async function replay(target, opts) {
2096
2403
  }
2097
2404
  const chain = await walkChain(eng, SOURCE);
2098
2405
  if (chain.length === 0) {
2099
- err6(`the chain on '${target}' carries no intents \u2014 nothing to replay`);
2406
+ err7(`the chain on '${target}' carries no intents \u2014 nothing to replay`);
2100
2407
  return 1;
2101
2408
  }
2102
2409
  await mountFresh(eng, REPLAY);
@@ -2105,8 +2412,8 @@ async function replay(target, opts) {
2105
2412
  const tallies = /* @__PURE__ */ new Map();
2106
2413
  let autoRun = !interactive;
2107
2414
  if (!json) {
2108
- out6(`replaying ${chain.length} intent(s) from ${isDir ? target : `${cloud} :: ${target}`} \u2014 every step re-admitted through the wasm gate`);
2109
- if (interactive) out6(" keys: enter = next intent \xB7 a = run to the end \xB7 q = quit\n");
2415
+ out7(`replaying ${chain.length} intent(s) from ${isDir ? target : `${cloud} :: ${target}`} \u2014 every step re-admitted through the wasm gate`);
2416
+ if (interactive) out7(" keys: enter = next intent \xB7 a = run to the end \xB7 q = quit\n");
2110
2417
  }
2111
2418
  for (let i = 0; i < chain.length; i++) {
2112
2419
  const { oid, bytes, doc } = chain[i];
@@ -2120,8 +2427,8 @@ async function replay(target, opts) {
2120
2427
  if (res.ok === false) {
2121
2428
  const msg = `replay REFUSED at intent ${i} (${domain}/${directive}, ${oid.slice(0, 10)}): ${res.error ?? "unknown"}`;
2122
2429
  if (json) emit({ step: i, oid, domain, directive, refused: true, error: res.error ?? "unknown" });
2123
- else err6(msg);
2124
- err6("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)");
2430
+ else err7(msg);
2431
+ err7("the chain's own gate rejects this entry on a fresh fold \u2014 the source it came from would refuse it too (verify below names the check)");
2125
2432
  printVerdict(verifyChainLocal(eng, SOURCE));
2126
2433
  return 1;
2127
2434
  }
@@ -2142,31 +2449,31 @@ async function replay(target, opts) {
2142
2449
  tallies: Object.fromEntries(tallies)
2143
2450
  });
2144
2451
  } else {
2145
- out6(`[${String(i + 1).padStart(String(chain.length).length)}/${chain.length}] ${domain}/${directive} (${oid.slice(0, 10)}, ${ms.toFixed(0)} ms)`);
2146
- out6(` payload ${summarizePayload(doc.payload?.payload)}`);
2452
+ out7(`[${String(i + 1).padStart(String(chain.length).length)}/${chain.length}] ${domain}/${directive} (${oid.slice(0, 10)}, ${ms.toFixed(0)} ms)`);
2453
+ out7(` payload ${summarizePayload(doc.payload?.payload)}`);
2147
2454
  for (const ev of doc.events ?? []) {
2148
2455
  const id = ev.aggregate ?? "?";
2149
2456
  const was = before.get(id);
2150
2457
  const verb = ev.marker === "Create" ? "+ created" : ev.marker === "Delete" ? "- deleted" : was === void 0 ? "~ touched" : "~ updated";
2151
- out6(` ${verb} ${id}`);
2458
+ out7(` ${verb} ${id}`);
2152
2459
  const ops = summarizeOps(ev);
2153
- if (ops.length > 0) out6(` ${ops}`);
2460
+ if (ops.length > 0) out7(` ${ops}`);
2154
2461
  }
2155
2462
  const tallyLine = [...tallies.entries()].map(([t, n]) => `${t}:${n}`).join(" ");
2156
2463
  const showTally = stepEvery > 0 ? (i + 1) % stepEvery === 0 || i === chain.length - 1 : true;
2157
- if (showTally && tallyLine.length > 0) out6(` rows ${tallyLine}`);
2464
+ if (showTally && tallyLine.length > 0) out7(` rows ${tallyLine}`);
2158
2465
  }
2159
2466
  if (interactive && !autoRun && i < chain.length - 1) {
2160
2467
  const key = await readKey();
2161
2468
  if (key === "quit") {
2162
- out6(`
2469
+ out7(`
2163
2470
  stopped at intent ${i + 1}/${chain.length} \u2014 the verify verdict below covers the WHOLE chain as delivered`);
2164
2471
  break;
2165
2472
  }
2166
2473
  if (key === "all") autoRun = true;
2167
2474
  }
2168
2475
  }
2169
- if (!json) out6("");
2476
+ if (!json) out7("");
2170
2477
  const verdict = verifyChainLocal(eng, SOURCE);
2171
2478
  if (json) {
2172
2479
  emit({ finale: true, verdict });
@@ -2180,32 +2487,32 @@ init_local_holon();
2180
2487
  init_cloud();
2181
2488
  init_engine();
2182
2489
  var SOURCE2 = "source";
2183
- var out7 = (s) => {
2490
+ var out8 = (s) => {
2184
2491
  process.stdout.write(s + "\n");
2185
2492
  };
2186
- var err7 = (s) => {
2493
+ var err8 = (s) => {
2187
2494
  process.stderr.write(`error: ${s}
2188
2495
  `);
2189
2496
  };
2190
2497
  async function decrypt(ws, opts) {
2191
2498
  const cloud = cloudBase(opts.cloud);
2192
2499
  if (!opts.as) {
2193
- err7("--as <principal> is required (who to decrypt as)");
2500
+ err8("--as <principal> is required (who to decrypt as)");
2194
2501
  return 1;
2195
2502
  }
2196
2503
  if (!opts.secret) {
2197
- err7("--secret <base64 X25519 device secret> is required");
2504
+ err8("--secret <base64 X25519 device secret> is required");
2198
2505
  return 1;
2199
2506
  }
2200
2507
  if (!opts.query) {
2201
- err7("--query <queryId> is required (the read to decrypt)");
2508
+ err8("--query <queryId> is required (the read to decrypt)");
2202
2509
  return 1;
2203
2510
  }
2204
2511
  let eng;
2205
2512
  try {
2206
2513
  ({ eng } = await holonEngine(cloud, opts.overlay, "githolon-decrypt"));
2207
2514
  } catch (e) {
2208
- err7(`cannot boot the local holon: ${String(e.message ?? e)}`);
2515
+ err8(`cannot boot the local holon: ${String(e.message ?? e)}`);
2209
2516
  return 1;
2210
2517
  }
2211
2518
  const m = await mountWorkspace(eng, SOURCE2, {
@@ -2213,7 +2520,7 @@ async function decrypt(ws, opts) {
2213
2520
  headers: { "x-nomos-principal": opts.as }
2214
2521
  });
2215
2522
  if (m.restored !== true) {
2216
- err7(`workspace '${ws}' has no main on ${cloud}, or its clone was refused for '${opts.as}'` + (m.restoreError ? ` (${String(m.restoreError).split(" | ")[0]})` : ""));
2523
+ err8(`workspace '${ws}' has no main on ${cloud}, or its clone was refused for '${opts.as}'` + (m.restoreError ? ` (${String(m.restoreError).split(" | ")[0]})` : ""));
2217
2524
  return 1;
2218
2525
  }
2219
2526
  const wraps = query(eng, SOURCE2, "wrappedKeysByRecipient", JSON.stringify({ recipientPrincipal: opts.as }), opts.as);
@@ -2228,14 +2535,14 @@ async function decrypt(ws, opts) {
2228
2535
  }
2229
2536
  }
2230
2537
  }
2231
- if (opts.json !== true) out7(`# resolved ${keys.length} scope key(s) for '${opts.as}': ${keys.map((k) => `${k.scope}@${k.epoch}`).join(", ") || "(none)"}`);
2538
+ if (opts.json !== true) out8(`# resolved ${keys.length} scope key(s) for '${opts.as}': ${keys.map((k) => `${k.scope}@${k.epoch}`).join(", ") || "(none)"}`);
2232
2539
  const rows = query(eng, SOURCE2, opts.query, JSON.stringify(opts.params ?? {}), opts.as, keys);
2233
2540
  if (rows && !Array.isArray(rows) && rows.ok === false) {
2234
- err7(`the read was refused: ${rows.error}` + (String(rows.error).startsWith("read-forbidden") ? `
2541
+ err8(`the read was refused: ${rows.error}` + (String(rows.error).startsWith("read-forbidden") ? `
2235
2542
  '${opts.as}' is not granted this scope's read capability` : ""));
2236
2543
  return 1;
2237
2544
  }
2238
- out7(JSON.stringify(rows, null, opts.json === true ? 0 : 2));
2545
+ out8(JSON.stringify(rows, null, opts.json === true ? 0 : 2));
2239
2546
  return 0;
2240
2547
  }
2241
2548
 
@@ -2243,8 +2550,8 @@ async function decrypt(ws, opts) {
2243
2550
  init_engine();
2244
2551
  init_cloud();
2245
2552
  import { readFileSync as readFileSync8 } from "node:fs";
2246
- var out8 = (s) => void process.stdout.write(s + "\n");
2247
- var err8 = (s) => void process.stderr.write("error: " + s + "\n");
2553
+ var out9 = (s) => void process.stdout.write(s + "\n");
2554
+ var err9 = (s) => void process.stderr.write("error: " + s + "\n");
2248
2555
  function lawDiffVerdict(localHash, installed, ws) {
2249
2556
  const active = installed.filter((d) => d.phase === "Active" && typeof d.domainHash === "string");
2250
2557
  const currents = active.filter((d) => d.current === true);
@@ -2282,7 +2589,7 @@ async function status(wsArg, opts) {
2282
2589
  try {
2283
2590
  ws = await resolveWorkspace(wsArg, process.cwd(), opts.target, "githolon status <ws>");
2284
2591
  } catch (e) {
2285
- err8(e.message);
2592
+ err9(e.message);
2286
2593
  return 1;
2287
2594
  }
2288
2595
  let localHash;
@@ -2294,35 +2601,35 @@ async function status(wsArg, opts) {
2294
2601
  }
2295
2602
  const r = await fetch(`${cloud}/v2/workspaces/${ws}/domains`).catch(() => void 0);
2296
2603
  if (r === void 0) {
2297
- err8(`cannot reach ${cloud} \u2014 githolon status compares against the DEPLOYED law and needs the cloud (local-only: githolon dev)`);
2604
+ err9(`cannot reach ${cloud} \u2014 githolon status compares against the DEPLOYED law and needs the cloud (local-only: githolon dev)`);
2298
2605
  return 1;
2299
2606
  }
2300
2607
  const d = await r.json().catch(() => void 0);
2301
2608
  if (!r.ok || d?.ok !== true) {
2302
- err8(`status '${ws}' failed (${r.status}): ${d ? JSON.stringify(d) : "no body"}
2609
+ err9(`status '${ws}' failed (${r.status}): ${d ? JSON.stringify(d) : "no body"}
2303
2610
  is the workspace born? githolon ws create ${ws}`);
2304
2611
  return 1;
2305
2612
  }
2306
- out8(`workspace ${ws} on ${cloud}`);
2613
+ out9(`workspace ${ws} on ${cloud}`);
2307
2614
  const allDomains = d.domains ?? [];
2308
2615
  const controlPlaneHashes = new Set(CONTROL_PLANE_KEYS.map((k) => d.currentLaw?.[k]).filter(Boolean));
2309
2616
  const domains = allDomains.filter((dm) => !controlPlaneHashes.has(dm.domainHash));
2310
2617
  const controllerCount = allDomains.length - domains.length;
2311
- if (allDomains.length === 0) out8(`installed (none \u2014 not even the controller; the workspace may be unborn)`);
2618
+ if (allDomains.length === 0) out9(`installed (none \u2014 not even the controller; the workspace may be unborn)`);
2312
2619
  for (const dom of domains) {
2313
2620
  const mark = dom.current === true ? " \u2190 current (the holon resolves dispatch here)" : "";
2314
- out8(`installed ${(dom.domainHash ?? "?").slice(0, 16)}\u2026 ${dom.phase ?? "?"} by ${dom.installedBy ?? "?"}${mark}`);
2621
+ out9(`installed ${(dom.domainHash ?? "?").slice(0, 16)}\u2026 ${dom.phase ?? "?"} by ${dom.installedBy ?? "?"}${mark}`);
2315
2622
  }
2316
- if (controllerCount > 0) out8(` (+ ${controllerCount} control-plane install(s) \u2014 the nomos lifecycle controller; not tenant law)`);
2623
+ if (controllerCount > 0) out9(` (+ ${controllerCount} control-plane install(s) \u2014 the nomos lifecycle controller; not tenant law)`);
2317
2624
  const tenantActive = domains.filter((dm) => dm.phase === "Active").length;
2318
- if (tenantActive > 1) out8(` (${tenantActive} active tenant installs \u2014 install-beside keeps every prior deploy Active; the holon serves the current one per domain)`);
2625
+ if (tenantActive > 1) out9(` (${tenantActive} active tenant installs \u2014 install-beside keeps every prior deploy Active; the holon serves the current one per domain)`);
2319
2626
  if (localHash === void 0) {
2320
- out8(`local (no build/*.deploy.json here \u2014 run \`githolon compile\` to build the law this project authors)`);
2627
+ out9(`local (no build/*.deploy.json here \u2014 run \`githolon compile\` to build the law this project authors)`);
2321
2628
  return 0;
2322
2629
  }
2323
- out8(`local ${localHash.slice(0, 16)}\u2026 (build/*.deploy.json)`);
2630
+ out9(`local ${localHash.slice(0, 16)}\u2026 (build/*.deploy.json)`);
2324
2631
  const verdict = lawDiffVerdict(localHash, domains, ws);
2325
- for (const line of verdict.lines) out8(line);
2632
+ for (const line of verdict.lines) out9(line);
2326
2633
  return verdict.inSync ? 0 : 2;
2327
2634
  }
2328
2635
 
@@ -2353,18 +2660,32 @@ Identity (~/.holon/credentials.json \u2014 sessions auto-refresh):
2353
2660
  githolon login --token <refresh_token> adopt an existing captain app session
2354
2661
  githolon whoami show the principal births will ride
2355
2662
  githolon logout clear the session (workspace secrets kept)
2663
+ githolon key enroll [--principal <uid>] [--parent <ws>] mint a signing device key + enroll it on-chain
2664
+ (secret stays local; warrants SIGNED governance)
2665
+ githolon key show [--principal <uid>] the device key's public identity (never the secret)
2666
+
2667
+ Governance (SIGNED client-side, relayed as {intentBytes} \u2014 never a host-author endpoint):
2668
+ githolon grant <admin|creation> <principal> [--label L] grant authority at the parent (default root)
2669
+ githolon grant quota <principal> --max <N> grant a per-principal workspace allowance
2670
+ githolon revoke <admin|creation> <principal> revoke it (the revoked fact rides the chain)
2671
+ (all gated by the parent's law: only an admin's
2672
+ signature is admitted; --parent <p> targets a platform)
2356
2673
 
2357
- Nomos Cloud (secrets live in ~/.holon/credentials.json \u2014 never copy one by hand;
2358
- a bare <ws> resolves from nomos.package.mjs's optional \`workspace\` binding):
2359
- githolon ws create <name> [--principal <uid|token>] birth a workspace; SAVES its one-time secret
2674
+ Nomos Cloud (a bare <ws> resolves from nomos.package.mjs's optional \`workspace\` binding;
2675
+ deploy is OPEN + kernel-gated \u2014 births return no host secret, ownership is a ledger fact):
2676
+ githolon ws create <name> [--principal <uid|token>] birth a workspace (no host secret is issued)
2360
2677
  (no identity on file? it self-onboards an agent login)
2678
+ githolon ws create <name> --via <parent> request it as a SIGNED createWorkspace governance
2679
+ offer at the parent
2361
2680
  githolon ws status [<name>] workspace status (lineage, phase, verdicts)
2362
- githolon ws retire <name> retire a workspace (owner) \u2014 quota freed; data retained
2363
- per the license; reads keep serving
2364
- githolon deploy [<ws>] [--file <deploy.json>] POST build/*.deploy.json with the stored secret;
2681
+ githolon ws retire <name> [--parent <ws>] retire a workspace via a SIGNED governance offer;
2682
+ quota freed; data retained; reads keep serving
2683
+ githolon ws reclaim <name> [--parent <ws>] lawful custody discard via a SIGNED governance offer
2684
+ (only a retired workspace; never root)
2685
+ githolon deploy [<ws>] [--file <deploy.json>] POST build/*.deploy.json (open deploy lane);
2365
2686
  prints what moved (old \u2192 new law hash)
2366
- githolon secret set <ws> <secret> import a secret you already hold
2367
- githolon secret rotate <ws> rotate (and re-save) the workspace secret
2687
+ githolon secret set <ws> <push-password> store a git push password (push-to-create birth lane)
2688
+ githolon secret rotate <ws> RETIRED \u2014 host holds no ownership credential (410)
2368
2689
 
2369
2690
  Git lane (stock git push as deploy/birth \u2014 see also: push-to-create):
2370
2691
  githolon git setup install the credential helper for the cloud host
@@ -2387,15 +2708,23 @@ Options:
2387
2708
  function cloudArgs(rest) {
2388
2709
  const pos = [];
2389
2710
  const opts = {};
2711
+ const VALUE_FLAGS = ["--cloud", "--principal", "--file", "--target", "--parent", "--via", "--max", "--label"];
2390
2712
  for (let i = 0; i < rest.length; i++) {
2391
2713
  const a = rest[i];
2392
- if (a === "--cloud" || a === "--principal" || a === "--file" || a === "--target") {
2714
+ if (VALUE_FLAGS.includes(a)) {
2393
2715
  const v = rest[++i];
2394
2716
  if (v === void 0) return { pos, opts, bad: `${a} requires a value` };
2395
2717
  if (a === "--cloud") opts.cloud = v;
2396
2718
  else if (a === "--principal") opts.principal = v;
2397
2719
  else if (a === "--target") opts.target = v;
2398
- else opts.file = v;
2720
+ else if (a === "--parent") opts.parent = v;
2721
+ else if (a === "--via") opts.via = v;
2722
+ else if (a === "--label") opts.label = v;
2723
+ else if (a === "--max") {
2724
+ const n = Number(v);
2725
+ if (!Number.isInteger(n) || n <= 0) return { pos, opts, bad: `--max requires a positive integer` };
2726
+ opts.max = n;
2727
+ } else opts.file = v;
2399
2728
  } else if (a.startsWith("--")) {
2400
2729
  return { pos, opts, bad: `unknown flag '${a}'` };
2401
2730
  } else {
@@ -2428,7 +2757,8 @@ ${USAGE}`);
2428
2757
  }
2429
2758
  }
2430
2759
  if (sub2 === "retire" && name !== void 0) return wsRetire(name, opts);
2431
- return usageFail("expected: githolon ws <create|status|retire> <name>");
2760
+ if (sub2 === "reclaim" && name !== void 0) return wsReclaim(name, opts);
2761
+ return usageFail("expected: githolon ws <create|status|retire|reclaim> <name>");
2432
2762
  }
2433
2763
  if (command === "deploy") {
2434
2764
  try {
@@ -2700,6 +3030,40 @@ ${commandHelp("status")}`);
2700
3030
  if (argv[0] === "ws" || argv[0] === "deploy" || argv[0] === "secret") {
2701
3031
  return runCloud(argv);
2702
3032
  }
3033
+ if (argv[0] === "key") {
3034
+ const { pos, opts, bad } = cloudArgs(argv.slice(1));
3035
+ if (bad !== void 0) {
3036
+ process.stderr.write(`error: ${bad}
3037
+
3038
+ ${USAGE}`);
3039
+ return 1;
3040
+ }
3041
+ const [sub] = pos;
3042
+ if (sub === "enroll") return keyEnroll(opts);
3043
+ if (sub === "show") return keyShow(opts);
3044
+ process.stderr.write(`error: expected: githolon key <enroll|show> [--principal <uid>] [--parent <ws>]
3045
+
3046
+ ${USAGE}`);
3047
+ return 1;
3048
+ }
3049
+ if (argv[0] === "grant" || argv[0] === "revoke") {
3050
+ const verb = argv[0];
3051
+ const { pos, opts, bad } = cloudArgs(argv.slice(1));
3052
+ if (bad !== void 0) {
3053
+ process.stderr.write(`error: ${bad}
3054
+
3055
+ ${USAGE}`);
3056
+ return 1;
3057
+ }
3058
+ const [kind, subject] = pos;
3059
+ if (kind === void 0 || subject === void 0) {
3060
+ process.stderr.write(`error: expected: githolon ${verb} <admin|creation${verb === "grant" ? "|quota" : ""}> <principal>
3061
+
3062
+ ${USAGE}`);
3063
+ return 1;
3064
+ }
3065
+ return grantOrRevoke(verb, kind, subject, opts);
3066
+ }
2703
3067
  if (argv[0] === "login") {
2704
3068
  const agent = argv.includes("--agent");
2705
3069
  const tokenAt = argv.indexOf("--token");
@@ -2756,8 +3120,8 @@ ${USAGE}`);
2756
3120
  let parsed;
2757
3121
  try {
2758
3122
  parsed = parseArgs(argv);
2759
- } catch (err9) {
2760
- process.stderr.write(`error: ${err9.message}
3123
+ } catch (err10) {
3124
+ process.stderr.write(`error: ${err10.message}
2761
3125
 
2762
3126
  ${USAGE}`);
2763
3127
  return 1;
@@ -2776,8 +3140,8 @@ ${USAGE}`);
2776
3140
  process.stdout.write(`created ${path}
2777
3141
  `);
2778
3142
  return 0;
2779
- } catch (err9) {
2780
- process.stderr.write(`error: ${err9.message}
3143
+ } catch (err10) {
3144
+ process.stderr.write(`error: ${err10.message}
2781
3145
  `);
2782
3146
  return 1;
2783
3147
  }