githolon 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +492 -210
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
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
|
-
|
|
332
|
+
out2(`bare principal (transitional, unverified): ${c.principal}`);
|
|
161
333
|
return 0;
|
|
162
334
|
}
|
|
163
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
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
|
-
|
|
204
|
-
|
|
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
|
-
|
|
408
|
+
err2(`status '${name}' (${r.status}): ${text}`);
|
|
221
409
|
return 1;
|
|
222
410
|
}
|
|
223
|
-
|
|
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
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
237
|
-
|
|
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
|
-
|
|
246
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
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"
|
|
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
|
-
|
|
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
|
-
|
|
486
|
+
out2(`\u2713 deployed ${file.split("/").pop()} \u2192 ${ws}${typeof phase === "string" ? ` (${phase})` : ""}`);
|
|
290
487
|
if (typeof d.domainHash === "string") {
|
|
291
|
-
|
|
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
|
-
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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,
|
|
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
|
-
|
|
335
|
-
|
|
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
|
|
642
|
+
const out10 = new Uint8Array(n);
|
|
457
643
|
let o = 0;
|
|
458
644
|
for (const c of chunks) {
|
|
459
|
-
|
|
645
|
+
out10.set(c, o);
|
|
460
646
|
o += c.length;
|
|
461
647
|
}
|
|
462
|
-
return
|
|
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
|
|
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
|
-
|
|
805
|
+
out10[i] = Number(z >> 11n) / T;
|
|
620
806
|
}
|
|
621
|
-
return
|
|
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
|
|
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
|
-
|
|
923
|
-
|
|
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
|
-
|
|
1118
|
+
err3(`chain INVALID at ${v["check"]}${v["atIndex"] !== void 0 ? ` (intent ${v["atIndex"]})` : ""}: ${v["error"]}`);
|
|
927
1119
|
return false;
|
|
928
1120
|
}
|
|
929
|
-
var
|
|
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
|
-
|
|
936
|
-
|
|
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
|
|
|
@@ -1267,6 +1459,7 @@ Re-run with --force to replace it.`
|
|
|
1267
1459
|
|
|
1268
1460
|
// src/cli.ts
|
|
1269
1461
|
init_cloud();
|
|
1462
|
+
init_governance();
|
|
1270
1463
|
|
|
1271
1464
|
// src/compile.ts
|
|
1272
1465
|
import { spawn, spawnSync } from "node:child_process";
|
|
@@ -1412,8 +1605,8 @@ async function resolveWorkspace(explicit, cwd, target, usageHint) {
|
|
|
1412
1605
|
|
|
1413
1606
|
// src/dev.ts
|
|
1414
1607
|
init_proof_offline();
|
|
1415
|
-
var
|
|
1416
|
-
var
|
|
1608
|
+
var out4 = (s) => void process.stdout.write(s + "\n");
|
|
1609
|
+
var err4 = (s) => void process.stderr.write("error: " + s + "\n");
|
|
1417
1610
|
var WS = "dev";
|
|
1418
1611
|
var DEFAULT_PORT = 7700;
|
|
1419
1612
|
var DEBOUNCE_MS = 200;
|
|
@@ -1536,21 +1729,21 @@ async function dev(opts) {
|
|
|
1536
1729
|
const cwd = process.cwd();
|
|
1537
1730
|
const cloud = cloudBase(opts.cloud);
|
|
1538
1731
|
const project = await loadProject(cwd).catch((e) => {
|
|
1539
|
-
|
|
1732
|
+
err4(e.message);
|
|
1540
1733
|
return void 0;
|
|
1541
1734
|
});
|
|
1542
1735
|
if (project === void 0) {
|
|
1543
1736
|
if (!existsSync6(join7(cwd, CONFIG_FILE))) {
|
|
1544
|
-
|
|
1737
|
+
err4(`no ${CONFIG_FILE} here (${cwd}) \u2014 \`githolon dev\` runs inside a project; scaffold one from examples/guestbook or \`githolon generate domain <name>\``);
|
|
1545
1738
|
}
|
|
1546
1739
|
return 1;
|
|
1547
1740
|
}
|
|
1548
1741
|
const creds = loadCreds();
|
|
1549
1742
|
const installedBy = creds.session?.uid ?? creds.principal ?? "githolon-dev";
|
|
1550
|
-
|
|
1743
|
+
out4(`githolon dev \u2014 ${project.name}: compiling\u2026`);
|
|
1551
1744
|
const firstCompile = await compileAsync(cwd);
|
|
1552
1745
|
if (!firstCompile.ok) {
|
|
1553
|
-
|
|
1746
|
+
err4(`compile failed \u2014 the dev loop needs one green compile to mint the holon:
|
|
1554
1747
|
${firstCompile.output}`);
|
|
1555
1748
|
return 1;
|
|
1556
1749
|
}
|
|
@@ -1558,14 +1751,14 @@ ${firstCompile.output}`);
|
|
|
1558
1751
|
try {
|
|
1559
1752
|
deployBody = readDeployBody(cwd, project.name);
|
|
1560
1753
|
} catch (e) {
|
|
1561
|
-
|
|
1754
|
+
err4(e.message);
|
|
1562
1755
|
return 1;
|
|
1563
1756
|
}
|
|
1564
1757
|
let engBoot;
|
|
1565
1758
|
try {
|
|
1566
1759
|
engBoot = await holonEngine(cloud, deployBody, installedBy);
|
|
1567
1760
|
} catch (e) {
|
|
1568
|
-
|
|
1761
|
+
err4(`cannot boot the local holon: ${String(e.message ?? e)}
|
|
1569
1762
|
the runtime rides ${cloud}/v1/runtime/* and is cached in ~/.holon/runtime after one fetch \u2014 connect once, then dev runs offline`);
|
|
1570
1763
|
return 1;
|
|
1571
1764
|
}
|
|
@@ -1588,19 +1781,19 @@ ${firstCompile.output}`);
|
|
|
1588
1781
|
s.intents++;
|
|
1589
1782
|
const installed = await installLaw(s, deployBody, installedBy);
|
|
1590
1783
|
if (!installed.ok) {
|
|
1591
|
-
|
|
1784
|
+
err4(installed.error ?? "law install failed");
|
|
1592
1785
|
return 1;
|
|
1593
1786
|
}
|
|
1594
1787
|
s.proof = await runProofCycle(s, cwd, deployBody, installedBy);
|
|
1595
1788
|
if (opts.once === true) {
|
|
1596
|
-
|
|
1789
|
+
out4(statusScreen({ ...s, watchRoots: ["(--once: no watch)"] }));
|
|
1597
1790
|
return s.proof === void 0 || s.proof.ok ? 0 : 1;
|
|
1598
1791
|
}
|
|
1599
1792
|
let server;
|
|
1600
1793
|
try {
|
|
1601
1794
|
server = await startServer(s);
|
|
1602
1795
|
} catch (e) {
|
|
1603
|
-
|
|
1796
|
+
err4(e.message);
|
|
1604
1797
|
return 1;
|
|
1605
1798
|
}
|
|
1606
1799
|
const moduleDirs = /* @__PURE__ */ new Set();
|
|
@@ -1628,7 +1821,7 @@ ${firstCompile.output}`);
|
|
|
1628
1821
|
s.compileMs = run.ms;
|
|
1629
1822
|
if (!run.ok) {
|
|
1630
1823
|
s.lastError = run.output.trim() || "compile failed";
|
|
1631
|
-
|
|
1824
|
+
out4(`\u2717 cycle ${s.cycle}: compile failed \u2014 the previous law keeps serving
|
|
1632
1825
|
${run.output}`);
|
|
1633
1826
|
} else {
|
|
1634
1827
|
s.lastError = void 0;
|
|
@@ -1637,16 +1830,16 @@ ${run.output}`);
|
|
|
1637
1830
|
const inst = await installLaw(s, body, installedBy);
|
|
1638
1831
|
if (!inst.ok) {
|
|
1639
1832
|
s.lastError = inst.error ?? "install failed";
|
|
1640
|
-
|
|
1833
|
+
out4(`\u2717 cycle ${s.cycle}: ${s.lastError}`);
|
|
1641
1834
|
} else {
|
|
1642
1835
|
s.proof = await runProofCycle(s, cwd, body, installedBy);
|
|
1643
1836
|
const total = performance.now() - t0;
|
|
1644
|
-
|
|
1645
|
-
|
|
1837
|
+
out4(statusScreen(s));
|
|
1838
|
+
out4(` cycle edit \u2192 law-live \u2192 proof in ${total.toFixed(0)} ms total`);
|
|
1646
1839
|
}
|
|
1647
1840
|
} catch (e) {
|
|
1648
1841
|
s.lastError = String(e.message ?? e);
|
|
1649
|
-
|
|
1842
|
+
out4(`\u2717 cycle ${s.cycle}: ${s.lastError}`);
|
|
1650
1843
|
}
|
|
1651
1844
|
}
|
|
1652
1845
|
cycling = false;
|
|
@@ -1672,8 +1865,8 @@ ${run.output}`);
|
|
|
1672
1865
|
})
|
|
1673
1866
|
);
|
|
1674
1867
|
s.watchRoots.push(CONFIG_FILE);
|
|
1675
|
-
|
|
1676
|
-
|
|
1868
|
+
out4(statusScreen(s));
|
|
1869
|
+
out4(" (ctrl-c stops; edits in the watched paths recompile + re-prove automatically)");
|
|
1677
1870
|
return new Promise((resolveExit) => {
|
|
1678
1871
|
const stop = () => {
|
|
1679
1872
|
for (const w of watchers) w.close();
|
|
@@ -1690,8 +1883,8 @@ init_cloud();
|
|
|
1690
1883
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1691
1884
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
1692
1885
|
import { readFileSync as readFileSync5 } from "node:fs";
|
|
1693
|
-
var
|
|
1694
|
-
var
|
|
1886
|
+
var out5 = (s) => void process.stdout.write(s + "\n");
|
|
1887
|
+
var err5 = (s) => void process.stderr.write("error: " + s + "\n");
|
|
1695
1888
|
function git(args, opts = {}) {
|
|
1696
1889
|
const r = spawnSync2("git", args, { stdio: opts.quiet === true ? "ignore" : "inherit" });
|
|
1697
1890
|
return r.status ?? 1;
|
|
@@ -1723,31 +1916,31 @@ async function gitCredential(action) {
|
|
|
1723
1916
|
if (kv["protocol"] !== "https" || ws === void 0) return 0;
|
|
1724
1917
|
const cloud = `https://${kv["host"]}`;
|
|
1725
1918
|
const token = await sessionToken().catch(() => void 0);
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1919
|
+
out5(`username=${token ?? "nomos"}`);
|
|
1920
|
+
out5(`password=${ensureSecret(cloud, ws)}`);
|
|
1921
|
+
out5("");
|
|
1729
1922
|
return 0;
|
|
1730
1923
|
}
|
|
1731
1924
|
async function gitSetup(opts) {
|
|
1732
1925
|
const cloud = cloudBase(opts.cloud);
|
|
1733
1926
|
const self = process.argv[1];
|
|
1734
1927
|
if (self === void 0) {
|
|
1735
|
-
|
|
1928
|
+
err5("cannot locate the githolon CLI entry to install as a credential helper");
|
|
1736
1929
|
return 1;
|
|
1737
1930
|
}
|
|
1738
1931
|
const helper = `!"${process.execPath}" "${self}" git-credential`;
|
|
1739
1932
|
if (git(["config", "--global", `credential.${cloud}.helper`, helper]) !== 0 || git(["config", "--global", `credential.${cloud}.useHttpPath`, "true"]) !== 0) {
|
|
1740
|
-
|
|
1933
|
+
err5("git config failed \u2014 is git installed?");
|
|
1741
1934
|
return 1;
|
|
1742
1935
|
}
|
|
1743
|
-
|
|
1744
|
-
|
|
1936
|
+
out5(`\u2713 git credential helper installed for ${cloud} (global gitconfig)`);
|
|
1937
|
+
out5(` pushes/clones of ${cloud}/v2/workspaces/<ws>/git now authenticate from ~/.holon`);
|
|
1745
1938
|
return 0;
|
|
1746
1939
|
}
|
|
1747
1940
|
async function gitRemote(ws, name, opts) {
|
|
1748
1941
|
const cloud = cloudBase(opts.cloud);
|
|
1749
1942
|
if (git(["rev-parse", "--git-dir"], { quiet: true }) !== 0) {
|
|
1750
|
-
|
|
1943
|
+
err5("not a git repository \u2014 run inside your repo (git init first)");
|
|
1751
1944
|
return 1;
|
|
1752
1945
|
}
|
|
1753
1946
|
const setupRc = await gitSetup(opts);
|
|
@@ -1756,7 +1949,7 @@ async function gitRemote(ws, name, opts) {
|
|
|
1756
1949
|
const url = `${cloud}/v2/workspaces/${ws}/git`;
|
|
1757
1950
|
if (git(["remote", "add", name, url], { quiet: true }) !== 0) {
|
|
1758
1951
|
if (git(["remote", "set-url", name, url], { quiet: true }) !== 0) {
|
|
1759
|
-
|
|
1952
|
+
err5(`could not add or update remote '${name}'`);
|
|
1760
1953
|
return 1;
|
|
1761
1954
|
}
|
|
1762
1955
|
}
|
|
@@ -1764,7 +1957,7 @@ async function gitRemote(ws, name, opts) {
|
|
|
1764
1957
|
if (token === void 0) {
|
|
1765
1958
|
const principal = loadCreds().principal;
|
|
1766
1959
|
if (principal === void 0) {
|
|
1767
|
-
|
|
1960
|
+
err5(
|
|
1768
1961
|
`remote '${name}' \u2192 ${url} is wired, but a BIRTH push needs a principal:
|
|
1769
1962
|
githolon login --agent (then just push)
|
|
1770
1963
|
\u2014 or re-run after githolon ws create/login so a principal is on file`
|
|
@@ -1772,13 +1965,13 @@ async function gitRemote(ws, name, opts) {
|
|
|
1772
1965
|
return 1;
|
|
1773
1966
|
}
|
|
1774
1967
|
if (git(["config", `http.${url}.extraHeader`, `x-nomos-principal: ${principal}`]) !== 0) {
|
|
1775
|
-
|
|
1968
|
+
err5("git config failed setting the principal header");
|
|
1776
1969
|
return 1;
|
|
1777
1970
|
}
|
|
1778
1971
|
}
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1972
|
+
out5(`\u2713 remote '${name}' \u2192 ${url}`);
|
|
1973
|
+
out5(` workspace secret on file; ${token !== void 0 ? "births ride your session (verified)" : "births ride x-nomos-principal (bare uid)"}`);
|
|
1974
|
+
out5(` birth/deploy by push: git push ${name} main`);
|
|
1782
1975
|
return 0;
|
|
1783
1976
|
}
|
|
1784
1977
|
|
|
@@ -1858,18 +2051,50 @@ var HELP = {
|
|
|
1858
2051
|
what: "Clear the session. Stored workspace secrets are KEPT."
|
|
1859
2052
|
},
|
|
1860
2053
|
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 (
|
|
2054
|
+
usage: "githolon ws <create|status|retire|reclaim> [<name>] [--principal <uid|token>] [--via <parent>] [--parent <ws>] [--cloud <url>] [--target <name>]",
|
|
2055
|
+
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
2056
|
flags: [
|
|
1864
2057
|
["--principal <p>", "the birth's principal (uid or access token; remembered as the default)"],
|
|
2058
|
+
["--via <parent>", "ws create: sign a createWorkspace offer at <parent> instead of the raw birth"],
|
|
2059
|
+
["--parent <ws>", "retire/reclaim: the governance workspace to sign against (default root)"],
|
|
1865
2060
|
["--cloud <url>", "target cloud (default: $NOMOS_CLOUD or the public cloud)"],
|
|
1866
2061
|
["--target <name>", "ws status: pick among the project's named workspace targets"]
|
|
1867
2062
|
],
|
|
1868
|
-
examples: ["githolon ws create my-guestbook", "githolon ws status", "githolon ws retire my-guestbook"]
|
|
2063
|
+
examples: ["githolon ws create my-guestbook", "githolon ws create app1 --via co2", "githolon ws status", "githolon ws retire my-guestbook"]
|
|
2064
|
+
},
|
|
2065
|
+
key: {
|
|
2066
|
+
usage: "githolon key <enroll|show> [--principal <uid>] [--parent <ws>] [--cloud <url>]",
|
|
2067
|
+
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.",
|
|
2068
|
+
flags: [
|
|
2069
|
+
["--principal <p>", "the principal the key authors as (default: the logged-in session uid)"],
|
|
2070
|
+
["--parent <ws>", "the governance workspace to enroll at (default root)"],
|
|
2071
|
+
["--cloud <url>", "target cloud"]
|
|
2072
|
+
],
|
|
2073
|
+
examples: ["githolon key enroll", "githolon key show"]
|
|
2074
|
+
},
|
|
2075
|
+
grant: {
|
|
2076
|
+
usage: "githolon grant <admin|creation|quota> <principal> [--max <N>] [--label <L>] [--parent <ws>] [--cloud <url>]",
|
|
2077
|
+
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).",
|
|
2078
|
+
flags: [
|
|
2079
|
+
["--max <N>", "grant quota: the principal's workspace allowance (positive int)"],
|
|
2080
|
+
["--label <L>", "grant admin/creation: a provenance label"],
|
|
2081
|
+
["--parent <ws>", "the governance workspace to sign against (default root)"],
|
|
2082
|
+
["--cloud <url>", "target cloud"]
|
|
2083
|
+
],
|
|
2084
|
+
examples: ["githolon grant creation 32c7100e-\u2026", "githolon grant admin 32c7100e-\u2026 --label lieutenant", "githolon grant quota 32c7100e-\u2026 --max 200"]
|
|
2085
|
+
},
|
|
2086
|
+
revoke: {
|
|
2087
|
+
usage: "githolon revoke <admin|creation> <principal> [--parent <ws>] [--cloud <url>]",
|
|
2088
|
+
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.",
|
|
2089
|
+
flags: [
|
|
2090
|
+
["--parent <ws>", "the governance workspace to sign against (default root)"],
|
|
2091
|
+
["--cloud <url>", "target cloud"]
|
|
2092
|
+
],
|
|
2093
|
+
examples: ["githolon revoke creation 32c7100e-\u2026", "githolon revoke admin 32c7100e-\u2026"]
|
|
1869
2094
|
},
|
|
1870
2095
|
deploy: {
|
|
1871
2096
|
usage: "githolon deploy [<ws>] [--file <deploy.json>] [--cloud <url>] [--target <name>]",
|
|
1872
|
-
what: "POST build/*.deploy.json
|
|
2097
|
+
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
2098
|
flags: [
|
|
1874
2099
|
["--file <path>", "an explicit deploy body (default: the one build/*.deploy.json)"],
|
|
1875
2100
|
["--target <name>", "pick among named targets (workspace: { dev: \u2026, prod: \u2026 })"],
|
|
@@ -1878,9 +2103,9 @@ var HELP = {
|
|
|
1878
2103
|
examples: ["githolon deploy", "githolon deploy my-guestbook", "githolon deploy --target prod"]
|
|
1879
2104
|
},
|
|
1880
2105
|
secret: {
|
|
1881
|
-
usage: "githolon secret <set <ws> <
|
|
1882
|
-
what: "The
|
|
1883
|
-
examples: ["githolon secret set my-ws
|
|
2106
|
+
usage: "githolon secret <set <ws> <push-password> | rotate <ws>> [--cloud <url>]",
|
|
2107
|
+
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).",
|
|
2108
|
+
examples: ["githolon secret set my-ws holon_v1_\u2026"]
|
|
1884
2109
|
},
|
|
1885
2110
|
git: {
|
|
1886
2111
|
usage: "githolon git <setup | remote [<ws>] [<remoteName>]> [--cloud <url>] [--target <name>]",
|
|
@@ -1916,20 +2141,20 @@ init_local_holon();
|
|
|
1916
2141
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
1917
2142
|
import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1918
2143
|
import { join as join8 } from "node:path";
|
|
1919
|
-
var
|
|
1920
|
-
var
|
|
2144
|
+
var out6 = (s) => void process.stdout.write(s + "\n");
|
|
2145
|
+
var err6 = (s) => void process.stderr.write("error: " + s + "\n");
|
|
1921
2146
|
var WS2 = "local";
|
|
1922
2147
|
async function ledgerInit(dir, opts) {
|
|
1923
2148
|
const cloud = cloudBase(opts.cloud);
|
|
1924
2149
|
if (existsSync7(dir) && readdirSync3(dir).length > 0) {
|
|
1925
|
-
|
|
2150
|
+
err6(`refusing to mint into non-empty directory: ${dir}`);
|
|
1926
2151
|
return 1;
|
|
1927
2152
|
}
|
|
1928
2153
|
let deployFile;
|
|
1929
2154
|
try {
|
|
1930
2155
|
deployFile = discoverDeployJson(process.cwd(), opts.file);
|
|
1931
2156
|
} catch (e) {
|
|
1932
|
-
|
|
2157
|
+
err6(e.message);
|
|
1933
2158
|
return 1;
|
|
1934
2159
|
}
|
|
1935
2160
|
const deployBody = JSON.parse(readFileSync6(deployFile, "utf8"));
|
|
@@ -1937,10 +2162,10 @@ async function ledgerInit(dir, opts) {
|
|
|
1937
2162
|
const c = loadCreds();
|
|
1938
2163
|
const principal = await sessionToken().catch(() => void 0) !== void 0 ? c.session.uid : c.principal;
|
|
1939
2164
|
if (principal === void 0) {
|
|
1940
|
-
|
|
2165
|
+
err6("a holon is born OF someone \u2014 githolon login --agent first (or githolon ws create --principal <uid> once)");
|
|
1941
2166
|
return 1;
|
|
1942
2167
|
}
|
|
1943
|
-
|
|
2168
|
+
out6(`minting a holon locally (law ${domainHash.slice(0, 12)}\u2026, installedBy ${principal})`);
|
|
1944
2169
|
const { eng } = await holonEngine(cloud, deployBody, principal);
|
|
1945
2170
|
await mountFresh(eng, WS2);
|
|
1946
2171
|
author(eng, WS2, "bootstrap", "installDomain", installPayload(eng.hashes.nomos, eng.nomosPkg, principal), "");
|
|
@@ -1953,16 +2178,16 @@ async function ledgerInit(dir, opts) {
|
|
|
1953
2178
|
const cfgPath = join8(gitDir, "config");
|
|
1954
2179
|
writeFileSync4(cfgPath, readFileSync6(cfgPath, "utf8").replace(/bare = true/, "bare = false"), "utf8");
|
|
1955
2180
|
spawnSync3("git", ["-C", dir, "reset", "--hard", "main"], { stdio: "ignore" });
|
|
1956
|
-
|
|
1957
|
-
|
|
2181
|
+
out6(`\u2713 holon written \u2192 ${dir} (a normal git repo; git log IS the audit trail)`);
|
|
2182
|
+
out6(`
|
|
1958
2183
|
Birth it on Nomos Cloud (the cloud re-derives this exact verdict):`);
|
|
1959
|
-
|
|
2184
|
+
out6(` cd ${dir} && npx githolon git remote <ws> && git push nomos main`);
|
|
1960
2185
|
return 0;
|
|
1961
2186
|
}
|
|
1962
2187
|
async function ledgerVerify(dir, opts) {
|
|
1963
2188
|
const gitDir = existsSync7(join8(dir, ".git")) ? join8(dir, ".git") : dir;
|
|
1964
2189
|
if (!existsSync7(join8(gitDir, "HEAD"))) {
|
|
1965
|
-
|
|
2190
|
+
err6(`${dir} is not a git repo (no HEAD)`);
|
|
1966
2191
|
return 1;
|
|
1967
2192
|
}
|
|
1968
2193
|
const { eng } = await holonEngine(cloudBase(opts.cloud), void 0, "verify");
|
|
@@ -1980,8 +2205,8 @@ init_local_holon();
|
|
|
1980
2205
|
import { existsSync as existsSync8 } from "node:fs";
|
|
1981
2206
|
import { join as join9 } from "node:path";
|
|
1982
2207
|
import git2 from "isomorphic-git";
|
|
1983
|
-
var
|
|
1984
|
-
var
|
|
2208
|
+
var out7 = (s) => void process.stdout.write(s + "\n");
|
|
2209
|
+
var err7 = (s) => void process.stderr.write("error: " + s + "\n");
|
|
1985
2210
|
var SOURCE = "source";
|
|
1986
2211
|
var REPLAY = "replay";
|
|
1987
2212
|
function summarizePayload(payload, max = 100) {
|
|
@@ -2017,9 +2242,9 @@ function capJsonStrings(v, max = 2048) {
|
|
|
2017
2242
|
if (typeof v === "string") return v.length > max ? `${v.slice(0, max)}\u2026(${v.length} chars)` : v;
|
|
2018
2243
|
if (Array.isArray(v)) return v.map((x) => capJsonStrings(x, max));
|
|
2019
2244
|
if (typeof v === "object" && v !== null) {
|
|
2020
|
-
const
|
|
2021
|
-
for (const [k, x] of Object.entries(v))
|
|
2022
|
-
return
|
|
2245
|
+
const out10 = {};
|
|
2246
|
+
for (const [k, x] of Object.entries(v)) out10[k] = capJsonStrings(x, max);
|
|
2247
|
+
return out10;
|
|
2023
2248
|
}
|
|
2024
2249
|
return v;
|
|
2025
2250
|
}
|
|
@@ -2066,12 +2291,12 @@ function readKey() {
|
|
|
2066
2291
|
async function replay(target, opts) {
|
|
2067
2292
|
const cloud = cloudBase(opts.cloud);
|
|
2068
2293
|
const json = opts.json === true;
|
|
2069
|
-
const emit = (o) =>
|
|
2294
|
+
const emit = (o) => out7(JSON.stringify(o));
|
|
2070
2295
|
let eng;
|
|
2071
2296
|
try {
|
|
2072
2297
|
({ eng } = await holonEngine(cloud, void 0, "githolon-replay"));
|
|
2073
2298
|
} catch (e) {
|
|
2074
|
-
|
|
2299
|
+
err7(`cannot boot the local holon: ${String(e.message ?? e)}
|
|
2075
2300
|
the runtime rides ${cloud}/v1/runtime/* (cached in ~/.holon/runtime after one fetch)`);
|
|
2076
2301
|
return 1;
|
|
2077
2302
|
}
|
|
@@ -2079,7 +2304,7 @@ async function replay(target, opts) {
|
|
|
2079
2304
|
if (isDir) {
|
|
2080
2305
|
const gitDir = existsSync8(join9(target, ".git")) ? join9(target, ".git") : target;
|
|
2081
2306
|
if (!existsSync8(join9(gitDir, "HEAD"))) {
|
|
2082
|
-
|
|
2307
|
+
err7(`${target} is not a git repo (no HEAD) \u2014 pass a holon directory (githolon ledger init <dir>) or a workspace name`);
|
|
2083
2308
|
return 1;
|
|
2084
2309
|
}
|
|
2085
2310
|
await mountFresh(eng, SOURCE);
|
|
@@ -2087,7 +2312,7 @@ async function replay(target, opts) {
|
|
|
2087
2312
|
} else {
|
|
2088
2313
|
const m = await mountWorkspace(eng, SOURCE, { remote: `${cloud}/v2/workspaces/${target}/git`, headers: {} });
|
|
2089
2314
|
if (m.restored !== true) {
|
|
2090
|
-
|
|
2315
|
+
err7(
|
|
2091
2316
|
`workspace '${target}' has no refs/heads/main on ${cloud} \u2014 nothing to replay` + (m.restoreError ? ` (${String(m.restoreError).split(" | ")[0]})` : "") + `
|
|
2092
2317
|
check the name (githolon ws status ${target}), or pass a local holon directory instead`
|
|
2093
2318
|
);
|
|
@@ -2096,7 +2321,7 @@ async function replay(target, opts) {
|
|
|
2096
2321
|
}
|
|
2097
2322
|
const chain = await walkChain(eng, SOURCE);
|
|
2098
2323
|
if (chain.length === 0) {
|
|
2099
|
-
|
|
2324
|
+
err7(`the chain on '${target}' carries no intents \u2014 nothing to replay`);
|
|
2100
2325
|
return 1;
|
|
2101
2326
|
}
|
|
2102
2327
|
await mountFresh(eng, REPLAY);
|
|
@@ -2105,8 +2330,8 @@ async function replay(target, opts) {
|
|
|
2105
2330
|
const tallies = /* @__PURE__ */ new Map();
|
|
2106
2331
|
let autoRun = !interactive;
|
|
2107
2332
|
if (!json) {
|
|
2108
|
-
|
|
2109
|
-
if (interactive)
|
|
2333
|
+
out7(`replaying ${chain.length} intent(s) from ${isDir ? target : `${cloud} :: ${target}`} \u2014 every step re-admitted through the wasm gate`);
|
|
2334
|
+
if (interactive) out7(" keys: enter = next intent \xB7 a = run to the end \xB7 q = quit\n");
|
|
2110
2335
|
}
|
|
2111
2336
|
for (let i = 0; i < chain.length; i++) {
|
|
2112
2337
|
const { oid, bytes, doc } = chain[i];
|
|
@@ -2120,8 +2345,8 @@ async function replay(target, opts) {
|
|
|
2120
2345
|
if (res.ok === false) {
|
|
2121
2346
|
const msg = `replay REFUSED at intent ${i} (${domain}/${directive}, ${oid.slice(0, 10)}): ${res.error ?? "unknown"}`;
|
|
2122
2347
|
if (json) emit({ step: i, oid, domain, directive, refused: true, error: res.error ?? "unknown" });
|
|
2123
|
-
else
|
|
2124
|
-
|
|
2348
|
+
else err7(msg);
|
|
2349
|
+
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
2350
|
printVerdict(verifyChainLocal(eng, SOURCE));
|
|
2126
2351
|
return 1;
|
|
2127
2352
|
}
|
|
@@ -2142,31 +2367,31 @@ async function replay(target, opts) {
|
|
|
2142
2367
|
tallies: Object.fromEntries(tallies)
|
|
2143
2368
|
});
|
|
2144
2369
|
} else {
|
|
2145
|
-
|
|
2146
|
-
|
|
2370
|
+
out7(`[${String(i + 1).padStart(String(chain.length).length)}/${chain.length}] ${domain}/${directive} (${oid.slice(0, 10)}, ${ms.toFixed(0)} ms)`);
|
|
2371
|
+
out7(` payload ${summarizePayload(doc.payload?.payload)}`);
|
|
2147
2372
|
for (const ev of doc.events ?? []) {
|
|
2148
2373
|
const id = ev.aggregate ?? "?";
|
|
2149
2374
|
const was = before.get(id);
|
|
2150
2375
|
const verb = ev.marker === "Create" ? "+ created" : ev.marker === "Delete" ? "- deleted" : was === void 0 ? "~ touched" : "~ updated";
|
|
2151
|
-
|
|
2376
|
+
out7(` ${verb} ${id}`);
|
|
2152
2377
|
const ops = summarizeOps(ev);
|
|
2153
|
-
if (ops.length > 0)
|
|
2378
|
+
if (ops.length > 0) out7(` ${ops}`);
|
|
2154
2379
|
}
|
|
2155
2380
|
const tallyLine = [...tallies.entries()].map(([t, n]) => `${t}:${n}`).join(" ");
|
|
2156
2381
|
const showTally = stepEvery > 0 ? (i + 1) % stepEvery === 0 || i === chain.length - 1 : true;
|
|
2157
|
-
if (showTally && tallyLine.length > 0)
|
|
2382
|
+
if (showTally && tallyLine.length > 0) out7(` rows ${tallyLine}`);
|
|
2158
2383
|
}
|
|
2159
2384
|
if (interactive && !autoRun && i < chain.length - 1) {
|
|
2160
2385
|
const key = await readKey();
|
|
2161
2386
|
if (key === "quit") {
|
|
2162
|
-
|
|
2387
|
+
out7(`
|
|
2163
2388
|
stopped at intent ${i + 1}/${chain.length} \u2014 the verify verdict below covers the WHOLE chain as delivered`);
|
|
2164
2389
|
break;
|
|
2165
2390
|
}
|
|
2166
2391
|
if (key === "all") autoRun = true;
|
|
2167
2392
|
}
|
|
2168
2393
|
}
|
|
2169
|
-
if (!json)
|
|
2394
|
+
if (!json) out7("");
|
|
2170
2395
|
const verdict = verifyChainLocal(eng, SOURCE);
|
|
2171
2396
|
if (json) {
|
|
2172
2397
|
emit({ finale: true, verdict });
|
|
@@ -2180,32 +2405,32 @@ init_local_holon();
|
|
|
2180
2405
|
init_cloud();
|
|
2181
2406
|
init_engine();
|
|
2182
2407
|
var SOURCE2 = "source";
|
|
2183
|
-
var
|
|
2408
|
+
var out8 = (s) => {
|
|
2184
2409
|
process.stdout.write(s + "\n");
|
|
2185
2410
|
};
|
|
2186
|
-
var
|
|
2411
|
+
var err8 = (s) => {
|
|
2187
2412
|
process.stderr.write(`error: ${s}
|
|
2188
2413
|
`);
|
|
2189
2414
|
};
|
|
2190
2415
|
async function decrypt(ws, opts) {
|
|
2191
2416
|
const cloud = cloudBase(opts.cloud);
|
|
2192
2417
|
if (!opts.as) {
|
|
2193
|
-
|
|
2418
|
+
err8("--as <principal> is required (who to decrypt as)");
|
|
2194
2419
|
return 1;
|
|
2195
2420
|
}
|
|
2196
2421
|
if (!opts.secret) {
|
|
2197
|
-
|
|
2422
|
+
err8("--secret <base64 X25519 device secret> is required");
|
|
2198
2423
|
return 1;
|
|
2199
2424
|
}
|
|
2200
2425
|
if (!opts.query) {
|
|
2201
|
-
|
|
2426
|
+
err8("--query <queryId> is required (the read to decrypt)");
|
|
2202
2427
|
return 1;
|
|
2203
2428
|
}
|
|
2204
2429
|
let eng;
|
|
2205
2430
|
try {
|
|
2206
2431
|
({ eng } = await holonEngine(cloud, opts.overlay, "githolon-decrypt"));
|
|
2207
2432
|
} catch (e) {
|
|
2208
|
-
|
|
2433
|
+
err8(`cannot boot the local holon: ${String(e.message ?? e)}`);
|
|
2209
2434
|
return 1;
|
|
2210
2435
|
}
|
|
2211
2436
|
const m = await mountWorkspace(eng, SOURCE2, {
|
|
@@ -2213,7 +2438,7 @@ async function decrypt(ws, opts) {
|
|
|
2213
2438
|
headers: { "x-nomos-principal": opts.as }
|
|
2214
2439
|
});
|
|
2215
2440
|
if (m.restored !== true) {
|
|
2216
|
-
|
|
2441
|
+
err8(`workspace '${ws}' has no main on ${cloud}, or its clone was refused for '${opts.as}'` + (m.restoreError ? ` (${String(m.restoreError).split(" | ")[0]})` : ""));
|
|
2217
2442
|
return 1;
|
|
2218
2443
|
}
|
|
2219
2444
|
const wraps = query(eng, SOURCE2, "wrappedKeysByRecipient", JSON.stringify({ recipientPrincipal: opts.as }), opts.as);
|
|
@@ -2228,14 +2453,14 @@ async function decrypt(ws, opts) {
|
|
|
2228
2453
|
}
|
|
2229
2454
|
}
|
|
2230
2455
|
}
|
|
2231
|
-
if (opts.json !== true)
|
|
2456
|
+
if (opts.json !== true) out8(`# resolved ${keys.length} scope key(s) for '${opts.as}': ${keys.map((k) => `${k.scope}@${k.epoch}`).join(", ") || "(none)"}`);
|
|
2232
2457
|
const rows = query(eng, SOURCE2, opts.query, JSON.stringify(opts.params ?? {}), opts.as, keys);
|
|
2233
2458
|
if (rows && !Array.isArray(rows) && rows.ok === false) {
|
|
2234
|
-
|
|
2459
|
+
err8(`the read was refused: ${rows.error}` + (String(rows.error).startsWith("read-forbidden") ? `
|
|
2235
2460
|
'${opts.as}' is not granted this scope's read capability` : ""));
|
|
2236
2461
|
return 1;
|
|
2237
2462
|
}
|
|
2238
|
-
|
|
2463
|
+
out8(JSON.stringify(rows, null, opts.json === true ? 0 : 2));
|
|
2239
2464
|
return 0;
|
|
2240
2465
|
}
|
|
2241
2466
|
|
|
@@ -2243,8 +2468,8 @@ async function decrypt(ws, opts) {
|
|
|
2243
2468
|
init_engine();
|
|
2244
2469
|
init_cloud();
|
|
2245
2470
|
import { readFileSync as readFileSync8 } from "node:fs";
|
|
2246
|
-
var
|
|
2247
|
-
var
|
|
2471
|
+
var out9 = (s) => void process.stdout.write(s + "\n");
|
|
2472
|
+
var err9 = (s) => void process.stderr.write("error: " + s + "\n");
|
|
2248
2473
|
function lawDiffVerdict(localHash, installed, ws) {
|
|
2249
2474
|
const active = installed.filter((d) => d.phase === "Active" && typeof d.domainHash === "string");
|
|
2250
2475
|
const currents = active.filter((d) => d.current === true);
|
|
@@ -2282,7 +2507,7 @@ async function status(wsArg, opts) {
|
|
|
2282
2507
|
try {
|
|
2283
2508
|
ws = await resolveWorkspace(wsArg, process.cwd(), opts.target, "githolon status <ws>");
|
|
2284
2509
|
} catch (e) {
|
|
2285
|
-
|
|
2510
|
+
err9(e.message);
|
|
2286
2511
|
return 1;
|
|
2287
2512
|
}
|
|
2288
2513
|
let localHash;
|
|
@@ -2294,35 +2519,35 @@ async function status(wsArg, opts) {
|
|
|
2294
2519
|
}
|
|
2295
2520
|
const r = await fetch(`${cloud}/v2/workspaces/${ws}/domains`).catch(() => void 0);
|
|
2296
2521
|
if (r === void 0) {
|
|
2297
|
-
|
|
2522
|
+
err9(`cannot reach ${cloud} \u2014 githolon status compares against the DEPLOYED law and needs the cloud (local-only: githolon dev)`);
|
|
2298
2523
|
return 1;
|
|
2299
2524
|
}
|
|
2300
2525
|
const d = await r.json().catch(() => void 0);
|
|
2301
2526
|
if (!r.ok || d?.ok !== true) {
|
|
2302
|
-
|
|
2527
|
+
err9(`status '${ws}' failed (${r.status}): ${d ? JSON.stringify(d) : "no body"}
|
|
2303
2528
|
is the workspace born? githolon ws create ${ws}`);
|
|
2304
2529
|
return 1;
|
|
2305
2530
|
}
|
|
2306
|
-
|
|
2531
|
+
out9(`workspace ${ws} on ${cloud}`);
|
|
2307
2532
|
const allDomains = d.domains ?? [];
|
|
2308
2533
|
const controlPlaneHashes = new Set(CONTROL_PLANE_KEYS.map((k) => d.currentLaw?.[k]).filter(Boolean));
|
|
2309
2534
|
const domains = allDomains.filter((dm) => !controlPlaneHashes.has(dm.domainHash));
|
|
2310
2535
|
const controllerCount = allDomains.length - domains.length;
|
|
2311
|
-
if (allDomains.length === 0)
|
|
2536
|
+
if (allDomains.length === 0) out9(`installed (none \u2014 not even the controller; the workspace may be unborn)`);
|
|
2312
2537
|
for (const dom of domains) {
|
|
2313
2538
|
const mark = dom.current === true ? " \u2190 current (the holon resolves dispatch here)" : "";
|
|
2314
|
-
|
|
2539
|
+
out9(`installed ${(dom.domainHash ?? "?").slice(0, 16)}\u2026 ${dom.phase ?? "?"} by ${dom.installedBy ?? "?"}${mark}`);
|
|
2315
2540
|
}
|
|
2316
|
-
if (controllerCount > 0)
|
|
2541
|
+
if (controllerCount > 0) out9(` (+ ${controllerCount} control-plane install(s) \u2014 the nomos lifecycle controller; not tenant law)`);
|
|
2317
2542
|
const tenantActive = domains.filter((dm) => dm.phase === "Active").length;
|
|
2318
|
-
if (tenantActive > 1)
|
|
2543
|
+
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
2544
|
if (localHash === void 0) {
|
|
2320
|
-
|
|
2545
|
+
out9(`local (no build/*.deploy.json here \u2014 run \`githolon compile\` to build the law this project authors)`);
|
|
2321
2546
|
return 0;
|
|
2322
2547
|
}
|
|
2323
|
-
|
|
2548
|
+
out9(`local ${localHash.slice(0, 16)}\u2026 (build/*.deploy.json)`);
|
|
2324
2549
|
const verdict = lawDiffVerdict(localHash, domains, ws);
|
|
2325
|
-
for (const line of verdict.lines)
|
|
2550
|
+
for (const line of verdict.lines) out9(line);
|
|
2326
2551
|
return verdict.inSync ? 0 : 2;
|
|
2327
2552
|
}
|
|
2328
2553
|
|
|
@@ -2353,18 +2578,32 @@ Identity (~/.holon/credentials.json \u2014 sessions auto-refresh):
|
|
|
2353
2578
|
githolon login --token <refresh_token> adopt an existing captain app session
|
|
2354
2579
|
githolon whoami show the principal births will ride
|
|
2355
2580
|
githolon logout clear the session (workspace secrets kept)
|
|
2581
|
+
githolon key enroll [--principal <uid>] [--parent <ws>] mint a signing device key + enroll it on-chain
|
|
2582
|
+
(secret stays local; warrants SIGNED governance)
|
|
2583
|
+
githolon key show [--principal <uid>] the device key's public identity (never the secret)
|
|
2584
|
+
|
|
2585
|
+
Governance (SIGNED client-side, relayed as {intentBytes} \u2014 never a host-author endpoint):
|
|
2586
|
+
githolon grant <admin|creation> <principal> [--label L] grant authority at the parent (default root)
|
|
2587
|
+
githolon grant quota <principal> --max <N> grant a per-principal workspace allowance
|
|
2588
|
+
githolon revoke <admin|creation> <principal> revoke it (the revoked fact rides the chain)
|
|
2589
|
+
(all gated by the parent's law: only an admin's
|
|
2590
|
+
signature is admitted; --parent <p> targets a platform)
|
|
2356
2591
|
|
|
2357
|
-
Nomos Cloud (
|
|
2358
|
-
|
|
2359
|
-
githolon ws create <name> [--principal <uid|token>] birth a workspace
|
|
2592
|
+
Nomos Cloud (a bare <ws> resolves from nomos.package.mjs's optional \`workspace\` binding;
|
|
2593
|
+
deploy is OPEN + kernel-gated \u2014 births return no host secret, ownership is a ledger fact):
|
|
2594
|
+
githolon ws create <name> [--principal <uid|token>] birth a workspace (no host secret is issued)
|
|
2360
2595
|
(no identity on file? it self-onboards an agent login)
|
|
2596
|
+
githolon ws create <name> --via <parent> request it as a SIGNED createWorkspace governance
|
|
2597
|
+
offer at the parent
|
|
2361
2598
|
githolon ws status [<name>] workspace status (lineage, phase, verdicts)
|
|
2362
|
-
githolon ws retire <name>
|
|
2363
|
-
|
|
2364
|
-
githolon
|
|
2599
|
+
githolon ws retire <name> [--parent <ws>] retire a workspace via a SIGNED governance offer;
|
|
2600
|
+
quota freed; data retained; reads keep serving
|
|
2601
|
+
githolon ws reclaim <name> [--parent <ws>] lawful custody discard via a SIGNED governance offer
|
|
2602
|
+
(only a retired workspace; never root)
|
|
2603
|
+
githolon deploy [<ws>] [--file <deploy.json>] POST build/*.deploy.json (open deploy lane);
|
|
2365
2604
|
prints what moved (old \u2192 new law hash)
|
|
2366
|
-
githolon secret set <ws> <
|
|
2367
|
-
githolon secret rotate <ws>
|
|
2605
|
+
githolon secret set <ws> <push-password> store a git push password (push-to-create birth lane)
|
|
2606
|
+
githolon secret rotate <ws> RETIRED \u2014 host holds no ownership credential (410)
|
|
2368
2607
|
|
|
2369
2608
|
Git lane (stock git push as deploy/birth \u2014 see also: push-to-create):
|
|
2370
2609
|
githolon git setup install the credential helper for the cloud host
|
|
@@ -2387,15 +2626,23 @@ Options:
|
|
|
2387
2626
|
function cloudArgs(rest) {
|
|
2388
2627
|
const pos = [];
|
|
2389
2628
|
const opts = {};
|
|
2629
|
+
const VALUE_FLAGS = ["--cloud", "--principal", "--file", "--target", "--parent", "--via", "--max", "--label"];
|
|
2390
2630
|
for (let i = 0; i < rest.length; i++) {
|
|
2391
2631
|
const a = rest[i];
|
|
2392
|
-
if (a
|
|
2632
|
+
if (VALUE_FLAGS.includes(a)) {
|
|
2393
2633
|
const v = rest[++i];
|
|
2394
2634
|
if (v === void 0) return { pos, opts, bad: `${a} requires a value` };
|
|
2395
2635
|
if (a === "--cloud") opts.cloud = v;
|
|
2396
2636
|
else if (a === "--principal") opts.principal = v;
|
|
2397
2637
|
else if (a === "--target") opts.target = v;
|
|
2398
|
-
else opts.
|
|
2638
|
+
else if (a === "--parent") opts.parent = v;
|
|
2639
|
+
else if (a === "--via") opts.via = v;
|
|
2640
|
+
else if (a === "--label") opts.label = v;
|
|
2641
|
+
else if (a === "--max") {
|
|
2642
|
+
const n = Number(v);
|
|
2643
|
+
if (!Number.isInteger(n) || n <= 0) return { pos, opts, bad: `--max requires a positive integer` };
|
|
2644
|
+
opts.max = n;
|
|
2645
|
+
} else opts.file = v;
|
|
2399
2646
|
} else if (a.startsWith("--")) {
|
|
2400
2647
|
return { pos, opts, bad: `unknown flag '${a}'` };
|
|
2401
2648
|
} else {
|
|
@@ -2428,7 +2675,8 @@ ${USAGE}`);
|
|
|
2428
2675
|
}
|
|
2429
2676
|
}
|
|
2430
2677
|
if (sub2 === "retire" && name !== void 0) return wsRetire(name, opts);
|
|
2431
|
-
|
|
2678
|
+
if (sub2 === "reclaim" && name !== void 0) return wsReclaim(name, opts);
|
|
2679
|
+
return usageFail("expected: githolon ws <create|status|retire|reclaim> <name>");
|
|
2432
2680
|
}
|
|
2433
2681
|
if (command === "deploy") {
|
|
2434
2682
|
try {
|
|
@@ -2700,6 +2948,40 @@ ${commandHelp("status")}`);
|
|
|
2700
2948
|
if (argv[0] === "ws" || argv[0] === "deploy" || argv[0] === "secret") {
|
|
2701
2949
|
return runCloud(argv);
|
|
2702
2950
|
}
|
|
2951
|
+
if (argv[0] === "key") {
|
|
2952
|
+
const { pos, opts, bad } = cloudArgs(argv.slice(1));
|
|
2953
|
+
if (bad !== void 0) {
|
|
2954
|
+
process.stderr.write(`error: ${bad}
|
|
2955
|
+
|
|
2956
|
+
${USAGE}`);
|
|
2957
|
+
return 1;
|
|
2958
|
+
}
|
|
2959
|
+
const [sub] = pos;
|
|
2960
|
+
if (sub === "enroll") return keyEnroll(opts);
|
|
2961
|
+
if (sub === "show") return keyShow(opts);
|
|
2962
|
+
process.stderr.write(`error: expected: githolon key <enroll|show> [--principal <uid>] [--parent <ws>]
|
|
2963
|
+
|
|
2964
|
+
${USAGE}`);
|
|
2965
|
+
return 1;
|
|
2966
|
+
}
|
|
2967
|
+
if (argv[0] === "grant" || argv[0] === "revoke") {
|
|
2968
|
+
const verb = argv[0];
|
|
2969
|
+
const { pos, opts, bad } = cloudArgs(argv.slice(1));
|
|
2970
|
+
if (bad !== void 0) {
|
|
2971
|
+
process.stderr.write(`error: ${bad}
|
|
2972
|
+
|
|
2973
|
+
${USAGE}`);
|
|
2974
|
+
return 1;
|
|
2975
|
+
}
|
|
2976
|
+
const [kind, subject] = pos;
|
|
2977
|
+
if (kind === void 0 || subject === void 0) {
|
|
2978
|
+
process.stderr.write(`error: expected: githolon ${verb} <admin|creation${verb === "grant" ? "|quota" : ""}> <principal>
|
|
2979
|
+
|
|
2980
|
+
${USAGE}`);
|
|
2981
|
+
return 1;
|
|
2982
|
+
}
|
|
2983
|
+
return grantOrRevoke(verb, kind, subject, opts);
|
|
2984
|
+
}
|
|
2703
2985
|
if (argv[0] === "login") {
|
|
2704
2986
|
const agent = argv.includes("--agent");
|
|
2705
2987
|
const tokenAt = argv.indexOf("--token");
|
|
@@ -2756,8 +3038,8 @@ ${USAGE}`);
|
|
|
2756
3038
|
let parsed;
|
|
2757
3039
|
try {
|
|
2758
3040
|
parsed = parseArgs(argv);
|
|
2759
|
-
} catch (
|
|
2760
|
-
process.stderr.write(`error: ${
|
|
3041
|
+
} catch (err10) {
|
|
3042
|
+
process.stderr.write(`error: ${err10.message}
|
|
2761
3043
|
|
|
2762
3044
|
${USAGE}`);
|
|
2763
3045
|
return 1;
|
|
@@ -2776,8 +3058,8 @@ ${USAGE}`);
|
|
|
2776
3058
|
process.stdout.write(`created ${path}
|
|
2777
3059
|
`);
|
|
2778
3060
|
return 0;
|
|
2779
|
-
} catch (
|
|
2780
|
-
process.stderr.write(`error: ${
|
|
3061
|
+
} catch (err10) {
|
|
3062
|
+
process.stderr.write(`error: ${err10.message}
|
|
2781
3063
|
`);
|
|
2782
3064
|
return 1;
|
|
2783
3065
|
}
|