githolon 0.21.0 → 0.22.1
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 +282 -37
- package/package.json +3 -3
package/dist/cli.mjs
CHANGED
|
@@ -12,12 +12,18 @@ var __export = (target, all) => {
|
|
|
12
12
|
// src/governance.ts
|
|
13
13
|
var governance_exports = {};
|
|
14
14
|
__export(governance_exports, {
|
|
15
|
+
governanceOffer: () => governanceOffer,
|
|
16
|
+
governanceRelay: () => governanceRelay,
|
|
15
17
|
grantOrRevoke: () => grantOrRevoke,
|
|
16
18
|
keyEnroll: () => keyEnroll,
|
|
19
|
+
keyExport: () => keyExport,
|
|
20
|
+
keyImport: () => keyImport,
|
|
17
21
|
keyShow: () => keyShow,
|
|
18
22
|
resolveGovernancePrincipal: () => resolveGovernancePrincipal,
|
|
19
23
|
signAndPostGovernance: () => signAndPostGovernance
|
|
20
24
|
});
|
|
25
|
+
import { createHash, createPrivateKey, createPublicKey } from "node:crypto";
|
|
26
|
+
import { readFileSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
21
27
|
import { connect } from "@githolon/client";
|
|
22
28
|
async function connectGovernance(cloud, parent) {
|
|
23
29
|
const authToken = await sessionToken().catch(() => void 0);
|
|
@@ -155,6 +161,174 @@ async function keyShow(opts) {
|
|
|
155
161
|
out(` keyHash ${device.keyHash} (the on-chain key:<hash> leaf; secret stays local)`);
|
|
156
162
|
return 0;
|
|
157
163
|
}
|
|
164
|
+
function deviceKeyFromSeed(seed) {
|
|
165
|
+
if (seed.length !== 32) throw new Error(`an ed25519 seed is 32 bytes; got ${seed.length}`);
|
|
166
|
+
const pkcs8 = Buffer.concat([Buffer.from("302e020100300506032b657004220420", "hex"), seed]);
|
|
167
|
+
const pub = Buffer.from(
|
|
168
|
+
createPublicKey(createPrivateKey({ key: pkcs8, format: "der", type: "pkcs8" })).export({ format: "der", type: "spki" }).subarray(-32)
|
|
169
|
+
);
|
|
170
|
+
return { secret: seed.toString("base64"), public: pub.toString("base64"), keyHash: createHash("sha256").update(pub).digest("hex") };
|
|
171
|
+
}
|
|
172
|
+
function seedFromInput(input, opts) {
|
|
173
|
+
const v = input.trim();
|
|
174
|
+
if (opts.sha256 === true) return createHash("sha256").update(v).digest();
|
|
175
|
+
if (opts.format === "hex" || opts.format === void 0 && /^[0-9a-fA-F]{64}$/.test(v)) return Buffer.from(v, "hex");
|
|
176
|
+
if (opts.format === "base64" || opts.format === void 0) {
|
|
177
|
+
const b = Buffer.from(v, "base64");
|
|
178
|
+
if (b.length === 32) return b;
|
|
179
|
+
if (opts.format === "base64") throw new Error(`--secret base64 decoded to ${b.length} bytes, not a 32-byte seed`);
|
|
180
|
+
}
|
|
181
|
+
throw new Error(
|
|
182
|
+
"--secret is not a 32-byte ed25519 seed \u2014 pass a base64 seed, a 64-hex seed (--format hex is auto-detected), or --sha256 to derive the seed from a passphrase/preimage"
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
async function keyImport(opts) {
|
|
186
|
+
const principal = opts.principal ?? currentPrincipal();
|
|
187
|
+
if (principal === void 0) {
|
|
188
|
+
err("key import needs --principal <uid> (the auth-uid this key authors as)");
|
|
189
|
+
return 1;
|
|
190
|
+
}
|
|
191
|
+
if (opts.secret === void 0 || opts.secret === "") {
|
|
192
|
+
err("key import needs --secret <hex|base64 ed25519 seed> (or --sha256 to derive the seed from a passphrase)");
|
|
193
|
+
return 1;
|
|
194
|
+
}
|
|
195
|
+
let key;
|
|
196
|
+
try {
|
|
197
|
+
key = deviceKeyFromSeed(seedFromInput(opts.secret, opts));
|
|
198
|
+
} catch (e) {
|
|
199
|
+
err(e.message);
|
|
200
|
+
return 1;
|
|
201
|
+
}
|
|
202
|
+
const existing = getDeviceKey(principal);
|
|
203
|
+
if (existing !== void 0 && existing.keyHash !== key.keyHash && opts.yes !== true) {
|
|
204
|
+
err(`a DIFFERENT key is already stored for ${principal} (keyHash ${existing.keyHash.slice(0, 12)}\u2026) \u2014 re-run with --yes to replace it`);
|
|
205
|
+
return 1;
|
|
206
|
+
}
|
|
207
|
+
setDeviceKey(principal, key);
|
|
208
|
+
out(`\u2713 imported signing key for ${principal}`);
|
|
209
|
+
out(` keyHash ${key.keyHash}`);
|
|
210
|
+
out(` public ${key.public} (secret stored 0600 in ~/.holon)`);
|
|
211
|
+
out(` now: githolon gov <ws> <directive> --as ${principal} (if not yet enrolled there: githolon key enroll --principal ${principal} --parent <ws>)`);
|
|
212
|
+
return 0;
|
|
213
|
+
}
|
|
214
|
+
async function keyExport(opts) {
|
|
215
|
+
const principal = opts.principal ?? currentPrincipal();
|
|
216
|
+
if (principal === void 0) {
|
|
217
|
+
err("key export needs --principal <uid>");
|
|
218
|
+
return 1;
|
|
219
|
+
}
|
|
220
|
+
const device = getDeviceKey(principal);
|
|
221
|
+
if (device === void 0) {
|
|
222
|
+
out(`no signing key on file for ${principal}`);
|
|
223
|
+
return 0;
|
|
224
|
+
}
|
|
225
|
+
if (opts.yes !== true) {
|
|
226
|
+
err(`key export prints the SECRET seed for ${principal} to stdout \u2014 re-run with --yes to confirm`);
|
|
227
|
+
return 1;
|
|
228
|
+
}
|
|
229
|
+
out(`signing key for ${principal} (KEEP SECRET):`);
|
|
230
|
+
out(` secret ${device.secret} (base64 ed25519 seed \u2014 = githolon key import --secret <this>)`);
|
|
231
|
+
out(` public ${device.public}`);
|
|
232
|
+
out(` keyHash ${device.keyHash}`);
|
|
233
|
+
return 0;
|
|
234
|
+
}
|
|
235
|
+
function buildOfferPayload(opts) {
|
|
236
|
+
const raw = opts.payloadFile !== void 0 ? readFileSync(opts.payloadFile, "utf8") : opts.payload;
|
|
237
|
+
let payload = {};
|
|
238
|
+
if (raw !== void 0 && raw.trim() !== "") payload = JSON.parse(raw);
|
|
239
|
+
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
|
|
240
|
+
throw new Error("payload must be a JSON object");
|
|
241
|
+
}
|
|
242
|
+
const p = payload;
|
|
243
|
+
if (opts.stamp === true) {
|
|
244
|
+
const now = Date.now();
|
|
245
|
+
for (const f of ["requestedAt", "grantedAt", "revokedAt", "setAt"]) if (!(f in p)) p[f] = now;
|
|
246
|
+
if (!("bornAt" in p)) p["bornAt"] = new Date(now).toISOString();
|
|
247
|
+
}
|
|
248
|
+
return p;
|
|
249
|
+
}
|
|
250
|
+
async function governanceOffer(ws, directiveId, opts) {
|
|
251
|
+
let payload;
|
|
252
|
+
try {
|
|
253
|
+
payload = buildOfferPayload(opts);
|
|
254
|
+
} catch (e) {
|
|
255
|
+
err(`--payload: ${e.message}`);
|
|
256
|
+
return 1;
|
|
257
|
+
}
|
|
258
|
+
let principal;
|
|
259
|
+
try {
|
|
260
|
+
principal = resolveGovernancePrincipal(opts);
|
|
261
|
+
} catch (e) {
|
|
262
|
+
err(e.message);
|
|
263
|
+
return 1;
|
|
264
|
+
}
|
|
265
|
+
if (opts.save !== void 0) {
|
|
266
|
+
try {
|
|
267
|
+
const holon = await connectGovernance(cloudBase(opts.cloud), ws);
|
|
268
|
+
const device = await ensureDeviceKey(holon, principal);
|
|
269
|
+
const sealed = await holon.signGovernanceOffer({ directiveId, payload, authorSecret: device.secret, actor: `user:${principal}` });
|
|
270
|
+
writeFileSync2(opts.save, JSON.stringify({ ws, directiveId, principal, intentBytes: sealed.intentBytes }, null, 2) + "\n");
|
|
271
|
+
} catch (e) {
|
|
272
|
+
err(e.message);
|
|
273
|
+
return 1;
|
|
274
|
+
}
|
|
275
|
+
out(`\u2713 authored + signed ${directiveId} for ${ws} \u2192 ${opts.save} (not relayed)`);
|
|
276
|
+
out(` relay it (the only step that needs the cloud): githolon gov relay ${ws} ${opts.save}`);
|
|
277
|
+
return 0;
|
|
278
|
+
}
|
|
279
|
+
let v;
|
|
280
|
+
try {
|
|
281
|
+
v = await signAndPostGovernance(ws, directiveId, payload, opts);
|
|
282
|
+
} catch (e) {
|
|
283
|
+
err(e.message);
|
|
284
|
+
return 1;
|
|
285
|
+
}
|
|
286
|
+
if (!v.ok) {
|
|
287
|
+
err(`${directiveId} refused (${v.status}): ${typeof v.body === "string" ? v.body : JSON.stringify(v.body)}`);
|
|
288
|
+
return 1;
|
|
289
|
+
}
|
|
290
|
+
const head = v.body?.head;
|
|
291
|
+
out(`\u2713 ${directiveId} admitted at ${ws} via signed offer (as ${principal})${head !== void 0 ? ` \u2014 head ${head.slice(0, 12)}\u2026` : ""}`);
|
|
292
|
+
return 0;
|
|
293
|
+
}
|
|
294
|
+
async function governanceRelay(ws, file, opts) {
|
|
295
|
+
let saved;
|
|
296
|
+
try {
|
|
297
|
+
saved = JSON.parse(readFileSync(file, "utf8"));
|
|
298
|
+
} catch (e) {
|
|
299
|
+
err(`cannot read offer file ${file}: ${e.message}`);
|
|
300
|
+
return 1;
|
|
301
|
+
}
|
|
302
|
+
const { directiveId, intentBytes } = saved;
|
|
303
|
+
if (typeof directiveId !== "string" || typeof intentBytes !== "string") {
|
|
304
|
+
err(`${file} is not a saved offer (expected { directiveId, intentBytes })`);
|
|
305
|
+
return 1;
|
|
306
|
+
}
|
|
307
|
+
const cloud = cloudBase(opts.cloud);
|
|
308
|
+
let r;
|
|
309
|
+
try {
|
|
310
|
+
r = await fetch(`${cloud}/v2/workspaces/${ws}/${directiveId}`, {
|
|
311
|
+
method: "POST",
|
|
312
|
+
headers: { "content-type": "application/json" },
|
|
313
|
+
body: JSON.stringify({ intentBytes })
|
|
314
|
+
});
|
|
315
|
+
} catch (e) {
|
|
316
|
+
err(`relay to ${ws} failed: ${e.message}`);
|
|
317
|
+
return 1;
|
|
318
|
+
}
|
|
319
|
+
const text = await r.text();
|
|
320
|
+
if (!r.ok) {
|
|
321
|
+
err(`${directiveId} refused (${r.status}): ${text}`);
|
|
322
|
+
return 1;
|
|
323
|
+
}
|
|
324
|
+
let head;
|
|
325
|
+
try {
|
|
326
|
+
head = JSON.parse(text).head;
|
|
327
|
+
} catch {
|
|
328
|
+
}
|
|
329
|
+
out(`\u2713 relayed ${directiveId} to ${ws}${head !== void 0 ? ` \u2014 head ${head.slice(0, 12)}\u2026` : ""}`);
|
|
330
|
+
return 0;
|
|
331
|
+
}
|
|
158
332
|
var out, err;
|
|
159
333
|
var init_governance = __esm({
|
|
160
334
|
"src/governance.ts"() {
|
|
@@ -192,7 +366,7 @@ __export(cloud_exports, {
|
|
|
192
366
|
wsRetire: () => wsRetire,
|
|
193
367
|
wsStatus: () => wsStatus
|
|
194
368
|
});
|
|
195
|
-
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync, writeFileSync as
|
|
369
|
+
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
196
370
|
import { join as join2 } from "node:path";
|
|
197
371
|
import { homedir } from "node:os";
|
|
198
372
|
function cloudBase(flag) {
|
|
@@ -212,11 +386,11 @@ function credsPath() {
|
|
|
212
386
|
}
|
|
213
387
|
function loadCreds() {
|
|
214
388
|
if (!existsSync2(credsPath())) return { version: 1, secrets: {} };
|
|
215
|
-
return JSON.parse(
|
|
389
|
+
return JSON.parse(readFileSync2(credsPath(), "utf8"));
|
|
216
390
|
}
|
|
217
391
|
function saveCreds(c) {
|
|
218
392
|
mkdirSync2(configDir(), { recursive: true });
|
|
219
|
-
|
|
393
|
+
writeFileSync3(credsPath(), JSON.stringify(c, null, 2) + "\n", "utf8");
|
|
220
394
|
chmodSync(credsPath(), 384);
|
|
221
395
|
}
|
|
222
396
|
function getSecret(cloud, ws) {
|
|
@@ -475,7 +649,7 @@ async function deploy(ws, opts) {
|
|
|
475
649
|
const r = await fetch(`${cloud}/v2/workspaces/${ws}/domains`, {
|
|
476
650
|
method: "POST",
|
|
477
651
|
headers: { "content-type": "application/json" },
|
|
478
|
-
body:
|
|
652
|
+
body: readFileSync2(file, "utf8")
|
|
479
653
|
});
|
|
480
654
|
const d = await r.json().catch(() => void 0);
|
|
481
655
|
if (!r.ok || d?.ok !== true) {
|
|
@@ -1028,7 +1202,7 @@ var init_engine = __esm({
|
|
|
1028
1202
|
|
|
1029
1203
|
// src/local_holon.ts
|
|
1030
1204
|
import { randomBytes } from "node:crypto";
|
|
1031
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as
|
|
1205
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1032
1206
|
import { dirname as dirname2, join as join4 } from "node:path";
|
|
1033
1207
|
import { File as File2, Directory as Directory2 } from "@bjorn3/browser_wasi_shim";
|
|
1034
1208
|
async function fetchJsonCached(url, cacheFile) {
|
|
@@ -1037,10 +1211,10 @@ async function fetchJsonCached(url, cacheFile) {
|
|
|
1037
1211
|
if (!r.ok) throw new Error(`${url} \u2192 ${r.status}`);
|
|
1038
1212
|
const text = await r.text();
|
|
1039
1213
|
mkdirSync3(dirname2(cacheFile), { recursive: true });
|
|
1040
|
-
|
|
1214
|
+
writeFileSync4(cacheFile, text, "utf8");
|
|
1041
1215
|
return JSON.parse(text);
|
|
1042
1216
|
} catch (e) {
|
|
1043
|
-
if (existsSync3(cacheFile)) return JSON.parse(
|
|
1217
|
+
if (existsSync3(cacheFile)) return JSON.parse(readFileSync3(cacheFile, "utf8"));
|
|
1044
1218
|
throw e;
|
|
1045
1219
|
}
|
|
1046
1220
|
}
|
|
@@ -1050,7 +1224,7 @@ async function fetchRuntime(cloud) {
|
|
|
1050
1224
|
const wasmCache = join4(cache, "holon.wasm");
|
|
1051
1225
|
const localWasm = process.env["NOMOS_LOCAL_WASM"];
|
|
1052
1226
|
if (localWasm) {
|
|
1053
|
-
const bytes =
|
|
1227
|
+
const bytes = readFileSync3(localWasm);
|
|
1054
1228
|
const packages2 = await fetchJsonCached(`${cloud}/v1/runtime/packages`, join4(cache, "packages.json"));
|
|
1055
1229
|
return { wasmBytes: bytes, packages: packages2 };
|
|
1056
1230
|
}
|
|
@@ -1059,10 +1233,10 @@ async function fetchRuntime(cloud) {
|
|
|
1059
1233
|
if (!r.ok) throw new Error(`runtime wasm \u2192 ${r.status}`);
|
|
1060
1234
|
wasmBytes = new Uint8Array(await r.arrayBuffer());
|
|
1061
1235
|
mkdirSync3(cache, { recursive: true });
|
|
1062
|
-
|
|
1236
|
+
writeFileSync4(wasmCache, wasmBytes);
|
|
1063
1237
|
} catch (e) {
|
|
1064
1238
|
if (!existsSync3(wasmCache)) throw e;
|
|
1065
|
-
wasmBytes =
|
|
1239
|
+
wasmBytes = readFileSync3(wasmCache);
|
|
1066
1240
|
}
|
|
1067
1241
|
const packages = await fetchJsonCached(`${cloud}/v1/runtime/packages`, join4(cache, "packages.json"));
|
|
1068
1242
|
return { wasmBytes, packages };
|
|
@@ -1072,7 +1246,7 @@ function writeTreeToDisk(dir, diskPath) {
|
|
|
1072
1246
|
for (const [name, inode] of dir.contents) {
|
|
1073
1247
|
const p = join4(diskPath, name);
|
|
1074
1248
|
if (inode.contents instanceof Map) writeTreeToDisk(inode, p);
|
|
1075
|
-
else
|
|
1249
|
+
else writeFileSync4(p, inode.data ?? new Uint8Array(0));
|
|
1076
1250
|
}
|
|
1077
1251
|
}
|
|
1078
1252
|
function readTreeFromDisk(diskPath) {
|
|
@@ -1080,7 +1254,7 @@ function readTreeFromDisk(diskPath) {
|
|
|
1080
1254
|
for (const name of readdirSync2(diskPath)) {
|
|
1081
1255
|
const p = join4(diskPath, name);
|
|
1082
1256
|
if (statSync(p).isDirectory()) contents.set(name, readTreeFromDisk(p));
|
|
1083
|
-
else contents.set(name, new File2(
|
|
1257
|
+
else contents.set(name, new File2(readFileSync3(p)));
|
|
1084
1258
|
}
|
|
1085
1259
|
return new Directory2(contents);
|
|
1086
1260
|
}
|
|
@@ -1137,7 +1311,7 @@ __export(proof_offline_exports, {
|
|
|
1137
1311
|
runOfflineLegs: () => runOfflineLegs,
|
|
1138
1312
|
runOfflineProofStandalone: () => runOfflineProofStandalone
|
|
1139
1313
|
});
|
|
1140
|
-
import { existsSync as existsSync5, readFileSync as
|
|
1314
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
|
|
1141
1315
|
import { basename, dirname as dirname3, join as join6 } from "node:path";
|
|
1142
1316
|
function parseOfflineLegs(proofSource) {
|
|
1143
1317
|
if (!proofSource.includes("AUTO-GENERATED by nomos-compile")) {
|
|
@@ -1300,7 +1474,7 @@ function runChildBirthLeg(eng, ws, lawHash, cb, frameworkHash) {
|
|
|
1300
1474
|
return done(true);
|
|
1301
1475
|
}
|
|
1302
1476
|
async function runOfflineProofStandalone(proofPath, cloud, domain) {
|
|
1303
|
-
const src =
|
|
1477
|
+
const src = readFileSync4(proofPath, "utf8");
|
|
1304
1478
|
const packageName = basename(proofPath).replace(/\.proof\.mts$/, "");
|
|
1305
1479
|
const deployFile = join6(dirname3(proofPath), `${packageName}.deploy.json`);
|
|
1306
1480
|
if (!existsSync5(deployFile)) {
|
|
@@ -1309,7 +1483,7 @@ async function runOfflineProofStandalone(proofPath, cloud, domain) {
|
|
|
1309
1483
|
let legs;
|
|
1310
1484
|
const legsFile = join6(dirname3(proofPath), `${packageName}.proof-legs.json`);
|
|
1311
1485
|
if (existsSync5(legsFile)) {
|
|
1312
|
-
const index = JSON.parse(
|
|
1486
|
+
const index = JSON.parse(readFileSync4(legsFile, "utf8"));
|
|
1313
1487
|
const keys = Object.keys(index.domains);
|
|
1314
1488
|
const chosen = domain ?? index.default;
|
|
1315
1489
|
if (index.domains[chosen] === void 0) {
|
|
@@ -1327,7 +1501,7 @@ async function runOfflineProofStandalone(proofPath, cloud, domain) {
|
|
|
1327
1501
|
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`);
|
|
1328
1502
|
legs = parseOfflineLegs(src);
|
|
1329
1503
|
}
|
|
1330
|
-
const deployBody = JSON.parse(
|
|
1504
|
+
const deployBody = JSON.parse(readFileSync4(deployFile, "utf8"));
|
|
1331
1505
|
const lawHash = await sha256hex(deployBody.packageUsda);
|
|
1332
1506
|
console.log(`githolon proof \u2014 OFFLINE legs on a local holon (the cloud loop: githolon proof --live)`);
|
|
1333
1507
|
const { eng } = await holonEngine(cloud, deployBody, "githolon-proof");
|
|
@@ -1355,7 +1529,7 @@ var init_proof_offline = __esm({
|
|
|
1355
1529
|
});
|
|
1356
1530
|
|
|
1357
1531
|
// src/cli.ts
|
|
1358
|
-
import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as
|
|
1532
|
+
import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync10 } from "node:fs";
|
|
1359
1533
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
1360
1534
|
import { createRequire as createRequire2 } from "node:module";
|
|
1361
1535
|
import { dirname as dirname5, join as join10, resolve as resolve2 } from "node:path";
|
|
@@ -1594,7 +1768,7 @@ function compileAsync(cwd, args = []) {
|
|
|
1594
1768
|
// src/dev.ts
|
|
1595
1769
|
init_engine();
|
|
1596
1770
|
init_cloud();
|
|
1597
|
-
import { existsSync as existsSync6, readFileSync as
|
|
1771
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, watch } from "node:fs";
|
|
1598
1772
|
import { createServer } from "node:http";
|
|
1599
1773
|
import { dirname as dirname4, join as join7, resolve } from "node:path";
|
|
1600
1774
|
import { pathToFileURL as pathToFileURL3 } from "node:url";
|
|
@@ -1776,7 +1950,7 @@ function readDeployBody(cwd, packageName) {
|
|
|
1776
1950
|
if (!existsSync6(file)) {
|
|
1777
1951
|
throw new Error(`compile produced no ${file} \u2014 check the compile output above`);
|
|
1778
1952
|
}
|
|
1779
|
-
return JSON.parse(
|
|
1953
|
+
return JSON.parse(readFileSync5(file, "utf8"));
|
|
1780
1954
|
}
|
|
1781
1955
|
async function installLaw(s, deployBody, installedBy) {
|
|
1782
1956
|
const newHash = await sha256hex(deployBody.packageUsda);
|
|
@@ -1796,7 +1970,7 @@ async function runProofCycle(s, cwd, deployBody, installedBy) {
|
|
|
1796
1970
|
if (!existsSync6(proofPath)) return void 0;
|
|
1797
1971
|
const scratch = `proof-${s.cycle}`;
|
|
1798
1972
|
try {
|
|
1799
|
-
const legs = parseOfflineLegs(
|
|
1973
|
+
const legs = parseOfflineLegs(readFileSync5(proofPath, "utf8"));
|
|
1800
1974
|
await mountFresh(s.eng, scratch);
|
|
1801
1975
|
author(s.eng, scratch, "bootstrap", "installDomain", installPayload(s.eng.hashes.nomos, s.eng.nomosPkg, installedBy), "");
|
|
1802
1976
|
author(s.eng, scratch, "nomos", "installDomain", installPayload(s.lawHash, deployBody.packageUsda, installedBy), s.eng.hashes.nomos);
|
|
@@ -1964,7 +2138,7 @@ ${run.output}`);
|
|
|
1964
2138
|
init_cloud();
|
|
1965
2139
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1966
2140
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
1967
|
-
import { readFileSync as
|
|
2141
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
1968
2142
|
var out5 = (s) => void process.stdout.write(s + "\n");
|
|
1969
2143
|
var err5 = (s) => void process.stderr.write("error: " + s + "\n");
|
|
1970
2144
|
function git(args, opts = {}) {
|
|
@@ -1993,7 +2167,7 @@ function workspaceFromPath(path) {
|
|
|
1993
2167
|
}
|
|
1994
2168
|
async function gitCredential(action) {
|
|
1995
2169
|
if (action !== "get") return 0;
|
|
1996
|
-
const kv = parseCredentialInput(
|
|
2170
|
+
const kv = parseCredentialInput(readFileSync6(0, "utf8"));
|
|
1997
2171
|
const ws = workspaceFromPath(kv["path"]);
|
|
1998
2172
|
if (kv["protocol"] !== "https" || ws === void 0) return 0;
|
|
1999
2173
|
const cloud = `https://${kv["host"]}`;
|
|
@@ -2145,14 +2319,41 @@ var HELP = {
|
|
|
2145
2319
|
examples: ["githolon ws create my-guestbook", "githolon ws create app1 --via co2", "githolon ws status", "githolon ws retire my-guestbook"]
|
|
2146
2320
|
},
|
|
2147
2321
|
key: {
|
|
2148
|
-
usage: "githolon key <enroll|show> [--principal <uid>] [--
|
|
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
|
|
2322
|
+
usage: "githolon key <enroll|show|import|export> [--principal <uid>] [--secret <key>] [--sha256] [--format hex|base64] [--parent <ws>] [--yes]",
|
|
2323
|
+
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. `import` brings\nan EXISTING admin key you already hold into ~/.holon \u2014 pass a base64 seed, a 64-hex seed\n(auto-detected; or --format hex), or --sha256 to derive the seed from a passphrase/preimage.\n`show` prints the public identity; `export --yes` prints the SECRET seed (backup/move). The\nsecret stays in ~/.holon/credentials.json (0600) \u2014 never on chain.",
|
|
2150
2324
|
flags: [
|
|
2151
2325
|
["--principal <p>", "the principal the key authors as (default: the logged-in session uid)"],
|
|
2152
|
-
["--
|
|
2326
|
+
["--secret <key>", "import: the ed25519 seed (base64 or 64-hex), or a passphrase with --sha256"],
|
|
2327
|
+
["--sha256", "import: derive the seed as sha256(--secret) (a passphrase/preimage)"],
|
|
2328
|
+
["--format <f>", "import: force hex|base64 interpretation (default: auto-detect)"],
|
|
2329
|
+
["--parent <ws>", "enroll: the governance workspace to enroll at (default root)"],
|
|
2330
|
+
["--yes", "export: confirm printing the secret to stdout"]
|
|
2331
|
+
],
|
|
2332
|
+
examples: [
|
|
2333
|
+
"githolon key enroll",
|
|
2334
|
+
"githolon key import --principal 32c7100e-\u2026 --secret 21c23da3\u2026 # a 64-hex admin seed",
|
|
2335
|
+
"githolon key import --principal d205ca94-\u2026 --secret 'copak_\u2026' --sha256",
|
|
2336
|
+
"githolon key show",
|
|
2337
|
+
"githolon key export --principal 32c7100e-\u2026 --yes"
|
|
2338
|
+
]
|
|
2339
|
+
},
|
|
2340
|
+
gov: {
|
|
2341
|
+
usage: "githolon gov <ws> <directiveId> [--payload <json> | --payload-file <f>] [--as <uid>] [--stamp] [--save <f>] [--cloud <url>]\n githolon gov relay <ws> <offer-file> [--cloud <url>]",
|
|
2342
|
+
what: "Author ANY `workspaces` governance directive as a SIGNED offer to <ws> and relay it \u2014 the generic\nlane the grant/ws/key verbs are sugar over. The directive is planned against <ws>'s OWN folded law\n(we connect + sign locally with your enrolled key; the host relays opaque {intentBytes} and the\nkernel re-verifies the signature). SIGNING IS LOCAL: with --save it writes the sealed offer WITHOUT\nrelaying \u2014 sign now, relay later/elsewhere with `gov relay` (the only step that touches the cloud).\nThe acting principal needs an enrolled signing key (githolon key enroll | key import).",
|
|
2343
|
+
flags: [
|
|
2344
|
+
["--payload <json>", `the directive payload as a JSON object string ('{"name":"\u2026"}')`],
|
|
2345
|
+
["--payload-file <f>", "read the payload JSON from a file instead"],
|
|
2346
|
+
["--as <uid>", "the principal to author as (alias of --principal; default: session uid)"],
|
|
2347
|
+
["--stamp", "fill any missing requestedAt/grantedAt/revokedAt/setAt (epoch ms) + bornAt (ISO) with now"],
|
|
2348
|
+
["--save <f>", "author + sign, write the sealed offer to <f>, do NOT relay"],
|
|
2153
2349
|
["--cloud <url>", "target cloud"]
|
|
2154
2350
|
],
|
|
2155
|
-
examples: [
|
|
2351
|
+
examples: [
|
|
2352
|
+
`githolon gov root grantCreation --payload '{"principal":"d205ca94-\u2026"}' --as 32c7100e-\u2026 --stamp`,
|
|
2353
|
+
"githolon gov co2-platform birthHome --payload-file home.json --as d205ca94-\u2026",
|
|
2354
|
+
"githolon gov root birthChild --payload-file co2.json --as 32c7100e-\u2026 --save co2.offer.json",
|
|
2355
|
+
"githolon gov relay root co2.offer.json"
|
|
2356
|
+
]
|
|
2156
2357
|
},
|
|
2157
2358
|
grant: {
|
|
2158
2359
|
usage: "githolon grant <admin|creation|quota> <principal> [--max <N>] [--label <L>] [--parent <ws>] [--cloud <url>]",
|
|
@@ -2221,7 +2422,7 @@ init_engine();
|
|
|
2221
2422
|
init_cloud();
|
|
2222
2423
|
init_local_holon();
|
|
2223
2424
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
2224
|
-
import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as
|
|
2425
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2225
2426
|
import { join as join8 } from "node:path";
|
|
2226
2427
|
var out6 = (s) => void process.stdout.write(s + "\n");
|
|
2227
2428
|
var err6 = (s) => void process.stderr.write("error: " + s + "\n");
|
|
@@ -2239,7 +2440,7 @@ async function ledgerInit(dir, opts) {
|
|
|
2239
2440
|
err6(e.message);
|
|
2240
2441
|
return 1;
|
|
2241
2442
|
}
|
|
2242
|
-
const deployBody = JSON.parse(
|
|
2443
|
+
const deployBody = JSON.parse(readFileSync7(deployFile, "utf8"));
|
|
2243
2444
|
const domainHash = await sha256hex(deployBody.packageUsda);
|
|
2244
2445
|
const c = loadCreds();
|
|
2245
2446
|
const principal = await sessionToken().catch(() => void 0) !== void 0 ? c.session.uid : c.principal;
|
|
@@ -2258,7 +2459,7 @@ async function ledgerInit(dir, opts) {
|
|
|
2258
2459
|
const gitDir = join8(dir, ".git");
|
|
2259
2460
|
writeTreeToDisk(gitTree, gitDir);
|
|
2260
2461
|
const cfgPath = join8(gitDir, "config");
|
|
2261
|
-
|
|
2462
|
+
writeFileSync5(cfgPath, readFileSync7(cfgPath, "utf8").replace(/bare = true/, "bare = false"), "utf8");
|
|
2262
2463
|
spawnSync3("git", ["-C", dir, "reset", "--hard", "main"], { stdio: "ignore" });
|
|
2263
2464
|
out6(`\u2713 holon written \u2192 ${dir} (a normal git repo; git log IS the audit trail)`);
|
|
2264
2465
|
out6(`
|
|
@@ -2549,7 +2750,7 @@ async function decrypt(ws, opts) {
|
|
|
2549
2750
|
// src/status.ts
|
|
2550
2751
|
init_engine();
|
|
2551
2752
|
init_cloud();
|
|
2552
|
-
import { readFileSync as
|
|
2753
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
2553
2754
|
var out9 = (s) => void process.stdout.write(s + "\n");
|
|
2554
2755
|
var err9 = (s) => void process.stderr.write("error: " + s + "\n");
|
|
2555
2756
|
function lawDiffVerdict(localHash, installed, ws) {
|
|
@@ -2594,7 +2795,7 @@ async function status(wsArg, opts) {
|
|
|
2594
2795
|
}
|
|
2595
2796
|
let localHash;
|
|
2596
2797
|
try {
|
|
2597
|
-
const body = JSON.parse(
|
|
2798
|
+
const body = JSON.parse(readFileSync9(discoverDeployJson(process.cwd(), opts.file), "utf8"));
|
|
2598
2799
|
if (typeof body.packageUsda === "string") localHash = await sha256hex(body.packageUsda);
|
|
2599
2800
|
} catch {
|
|
2600
2801
|
localHash = void 0;
|
|
@@ -2662,9 +2863,14 @@ Identity (~/.holon/credentials.json \u2014 sessions auto-refresh):
|
|
|
2662
2863
|
githolon logout clear the session (workspace secrets kept)
|
|
2663
2864
|
githolon key enroll [--principal <uid>] [--parent <ws>] mint a signing device key + enroll it on-chain
|
|
2664
2865
|
(secret stays local; warrants SIGNED governance)
|
|
2665
|
-
githolon key
|
|
2866
|
+
githolon key import --principal <uid> --secret <key> bring an EXISTING admin key into ~/.holon (accepts
|
|
2867
|
+
hex / base64 seed, or --sha256 <passphrase>)
|
|
2868
|
+
githolon key show | export [--principal <uid>] the device key's public identity (export --yes: secret)
|
|
2666
2869
|
|
|
2667
2870
|
Governance (SIGNED client-side, relayed as {intentBytes} \u2014 never a host-author endpoint):
|
|
2871
|
+
githolon gov <ws> <directive> [--payload <json>] author ANY workspaces directive as a signed offer
|
|
2872
|
+
[--as <uid>] [--stamp] [--save <file>] to <ws> (--save signs without relaying; relay later)
|
|
2873
|
+
githolon gov relay <ws> <offer-file> relay a saved offer (the only step that hits the cloud)
|
|
2668
2874
|
githolon grant <admin|creation> <principal> [--label L] grant authority at the parent (default root)
|
|
2669
2875
|
githolon grant quota <principal> --max <N> grant a per-principal workspace allowance
|
|
2670
2876
|
githolon revoke <admin|creation> <principal> revoke it (the revoked fact rides the chain)
|
|
@@ -2708,18 +2914,28 @@ Options:
|
|
|
2708
2914
|
function cloudArgs(rest) {
|
|
2709
2915
|
const pos = [];
|
|
2710
2916
|
const opts = {};
|
|
2711
|
-
const VALUE_FLAGS = ["--cloud", "--principal", "--file", "--target", "--parent", "--via", "--max", "--label"];
|
|
2917
|
+
const VALUE_FLAGS = ["--cloud", "--principal", "--as", "--file", "--target", "--parent", "--via", "--max", "--label", "--secret", "--payload", "--payload-file", "--save", "--format"];
|
|
2918
|
+
const BOOL_FLAGS = ["--sha256", "--stamp", "--yes"];
|
|
2712
2919
|
for (let i = 0; i < rest.length; i++) {
|
|
2713
2920
|
const a = rest[i];
|
|
2714
|
-
if (
|
|
2921
|
+
if (BOOL_FLAGS.includes(a)) {
|
|
2922
|
+
if (a === "--sha256") opts.sha256 = true;
|
|
2923
|
+
else if (a === "--stamp") opts.stamp = true;
|
|
2924
|
+
else opts.yes = true;
|
|
2925
|
+
} else if (VALUE_FLAGS.includes(a)) {
|
|
2715
2926
|
const v = rest[++i];
|
|
2716
2927
|
if (v === void 0) return { pos, opts, bad: `${a} requires a value` };
|
|
2717
2928
|
if (a === "--cloud") opts.cloud = v;
|
|
2718
|
-
else if (a === "--principal") opts.principal = v;
|
|
2929
|
+
else if (a === "--principal" || a === "--as") opts.principal = v;
|
|
2719
2930
|
else if (a === "--target") opts.target = v;
|
|
2720
2931
|
else if (a === "--parent") opts.parent = v;
|
|
2721
2932
|
else if (a === "--via") opts.via = v;
|
|
2722
2933
|
else if (a === "--label") opts.label = v;
|
|
2934
|
+
else if (a === "--secret") opts.secret = v;
|
|
2935
|
+
else if (a === "--payload") opts.payload = v;
|
|
2936
|
+
else if (a === "--payload-file") opts.payloadFile = v;
|
|
2937
|
+
else if (a === "--save") opts.save = v;
|
|
2938
|
+
else if (a === "--format") opts.format = v;
|
|
2723
2939
|
else if (a === "--max") {
|
|
2724
2940
|
const n = Number(v);
|
|
2725
2941
|
if (!Number.isInteger(n) || n <= 0) return { pos, opts, bad: `--max requires a positive integer` };
|
|
@@ -2849,7 +3065,7 @@ async function runProof(args) {
|
|
|
2849
3065
|
if (tsxDir === void 0) {
|
|
2850
3066
|
return refuse("tsx not found \u2014 add @githolon/dsl to your project's dependencies (tsx rides along) and npm install");
|
|
2851
3067
|
}
|
|
2852
|
-
const tsxPkg = JSON.parse(
|
|
3068
|
+
const tsxPkg = JSON.parse(readFileSync10(join10(tsxDir, "package.json"), "utf8"));
|
|
2853
3069
|
const tsxBinRel = typeof tsxPkg.bin === "string" ? tsxPkg.bin : tsxPkg.bin?.["tsx"];
|
|
2854
3070
|
const tsxCli = join10(tsxDir, tsxBinRel ?? "dist/cli.mjs");
|
|
2855
3071
|
console.log("githolon proof --live \u2014 the full cloud loop (throwaway workspace, retired on exit" + (keep ? "; --keep: kept, secret printed once" : "") + ")");
|
|
@@ -3041,11 +3257,40 @@ ${USAGE}`);
|
|
|
3041
3257
|
const [sub] = pos;
|
|
3042
3258
|
if (sub === "enroll") return keyEnroll(opts);
|
|
3043
3259
|
if (sub === "show") return keyShow(opts);
|
|
3044
|
-
|
|
3260
|
+
if (sub === "import") return keyImport(opts);
|
|
3261
|
+
if (sub === "export") return keyExport(opts);
|
|
3262
|
+
process.stderr.write(`error: expected: githolon key <enroll|show|import|export> [--principal <uid>]
|
|
3045
3263
|
|
|
3046
3264
|
${USAGE}`);
|
|
3047
3265
|
return 1;
|
|
3048
3266
|
}
|
|
3267
|
+
if (argv[0] === "gov") {
|
|
3268
|
+
const { pos, opts, bad } = cloudArgs(argv.slice(1));
|
|
3269
|
+
if (bad !== void 0) {
|
|
3270
|
+
process.stderr.write(`error: ${bad}
|
|
3271
|
+
|
|
3272
|
+
${commandHelp("gov") ?? USAGE}`);
|
|
3273
|
+
return 1;
|
|
3274
|
+
}
|
|
3275
|
+
if (pos[0] === "relay") {
|
|
3276
|
+
const [, ws2, file] = pos;
|
|
3277
|
+
if (ws2 === void 0 || file === void 0) {
|
|
3278
|
+
process.stderr.write(`error: expected: githolon gov relay <ws> <offer-file>
|
|
3279
|
+
|
|
3280
|
+
${commandHelp("gov") ?? USAGE}`);
|
|
3281
|
+
return 1;
|
|
3282
|
+
}
|
|
3283
|
+
return governanceRelay(ws2, file, opts);
|
|
3284
|
+
}
|
|
3285
|
+
const [ws, directiveId] = pos;
|
|
3286
|
+
if (ws === void 0 || directiveId === void 0) {
|
|
3287
|
+
process.stderr.write(`error: expected: githolon gov <ws> <directiveId> [--payload <json>] [--as <uid>]
|
|
3288
|
+
|
|
3289
|
+
${commandHelp("gov") ?? USAGE}`);
|
|
3290
|
+
return 1;
|
|
3291
|
+
}
|
|
3292
|
+
return governanceOffer(ws, directiveId, opts);
|
|
3293
|
+
}
|
|
3049
3294
|
if (argv[0] === "grant" || argv[0] === "revoke") {
|
|
3050
3295
|
const verb = argv[0];
|
|
3051
3296
|
const { pos, opts, bad } = cloudArgs(argv.slice(1));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "githolon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "githolon — the Nomos developer CLI: Rails-style generators for @githolon/dsl domains + the package compiler. Kernel-independent.",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@bjorn3/browser_wasi_shim": "0.4.2",
|
|
30
|
-
"@githolon/client": "^0.
|
|
31
|
-
"@githolon/dsl": "^0.
|
|
30
|
+
"@githolon/client": "^0.22.1",
|
|
31
|
+
"@githolon/dsl": "^0.22.1",
|
|
32
32
|
"isomorphic-git": "^1.38.4"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|