githolon 0.4.0 → 0.5.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 +947 -530
- package/package.json +2 -2
package/dist/cli.mjs
CHANGED
|
@@ -1,197 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
// src/generate.ts
|
|
11
|
-
import { mkdirSync, existsSync, writeFileSync } from "node:fs";
|
|
12
|
-
import { join } from "node:path";
|
|
13
|
-
|
|
14
|
-
// src/naming.ts
|
|
15
|
-
function words(raw) {
|
|
16
|
-
return raw.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_\-\s]+/g, " ").trim().toLowerCase().split(" ").filter((w) => w.length > 0);
|
|
17
|
-
}
|
|
18
|
-
var cap = (w) => w.length === 0 ? w : w[0].toUpperCase() + w.slice(1);
|
|
19
|
-
function pascalCase(raw) {
|
|
20
|
-
return words(raw).map(cap).join("");
|
|
21
|
-
}
|
|
22
|
-
function camelCase(raw) {
|
|
23
|
-
return words(raw).map((w, i) => i === 0 ? w : cap(w)).join("");
|
|
24
|
-
}
|
|
25
|
-
function snakeCase(raw) {
|
|
26
|
-
return words(raw).join("_");
|
|
27
|
-
}
|
|
28
|
-
function screamingSnakeCase(raw) {
|
|
29
|
-
return words(raw).join("_").toUpperCase();
|
|
30
|
-
}
|
|
31
|
-
function assertValidName(raw) {
|
|
32
|
-
const ws = words(raw);
|
|
33
|
-
if (ws.length === 0) {
|
|
34
|
-
throw new Error(`Invalid name: '${raw}' \u2014 give at least one word, e.g. 'work_order'.`);
|
|
35
|
-
}
|
|
36
|
-
for (const w of ws) {
|
|
37
|
-
if (!/^[a-z][a-z0-9]*$/.test(w)) {
|
|
38
|
-
throw new Error(
|
|
39
|
-
`Invalid name token '${w}' in '${raw}' \u2014 words must be alphanumeric and start with a letter (got after normalisation: ${ws.join(", ")}).`
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return ws;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// src/templates.ts
|
|
47
|
-
function namesFor(raw) {
|
|
48
|
-
return {
|
|
49
|
-
raw,
|
|
50
|
-
pascal: pascalCase(raw),
|
|
51
|
-
camel: camelCase(raw),
|
|
52
|
-
statusesConst: `${screamingSnakeCase(raw)}_STATUSES`
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
var HEADER = (raw, kind) => `/**
|
|
56
|
-
* The \`${raw}\` ${kind} \u2014 scaffolded by \`githolon generate\`.
|
|
57
|
-
*
|
|
58
|
-
* Authoring rules (see @githolon/dsl): write ONLY aggregates (typed fields, each
|
|
59
|
-
* tagged with a merge Driver) and directives (Zod payload + declarative plan).
|
|
60
|
-
* You never write apply/fold/merge \u2014 the kernel owns the merge algebra.
|
|
61
|
-
*
|
|
62
|
-
* Convention: the fail-closed \`Conflict\` driver is the default for scalars.
|
|
63
|
-
* Relax only what differs: \`.merge(Lww)\`, \`.merge(AddWins)\`, \`.merge(MapOf(Lww))\`.
|
|
64
|
-
*/`;
|
|
65
|
-
function aggregateBlock(n) {
|
|
66
|
-
return `export const ${n.statusesConst} = ["active", "retired"] as const;
|
|
67
|
-
|
|
68
|
-
export const ${n.pascal} = aggregate("${n.pascal}", {
|
|
69
|
-
// Default driver is the fail-closed \`Conflict\`: concurrent edits surface
|
|
70
|
-
// as a conflict rather than silently coalescing. Declare only what differs.
|
|
71
|
-
label: t.string().merge(Conflict),
|
|
72
|
-
// Overrides \u2014 last-write-wins / set-union / per-key map:
|
|
73
|
-
pos: t.int().merge(Lww),
|
|
74
|
-
status: t.enum(${n.statusesConst}).merge(Lww),
|
|
75
|
-
tags: t.set(t.string()).merge(AddWins),
|
|
76
|
-
customFields: t.map(t.string()).merge(MapOf(Lww)),
|
|
77
|
-
});`;
|
|
78
|
-
}
|
|
79
|
-
function domainTemplate(raw) {
|
|
80
|
-
const n = namesFor(raw);
|
|
81
|
-
return `${HEADER(raw, "domain")}
|
|
82
|
-
import {
|
|
83
|
-
z,
|
|
84
|
-
aggregate,
|
|
85
|
-
directive,
|
|
86
|
-
t,
|
|
87
|
-
Conflict,
|
|
88
|
-
Lww,
|
|
89
|
-
AddWins,
|
|
90
|
-
MapOf,
|
|
91
|
-
set,
|
|
92
|
-
addToSet,
|
|
93
|
-
setEntry,
|
|
94
|
-
} from "@githolon/dsl";
|
|
95
|
-
|
|
96
|
-
${aggregateBlock(n)}
|
|
97
|
-
|
|
98
|
-
/** create${n.pascal} \u2014 .creates the aggregate; seeds label/pos/status. */
|
|
99
|
-
export const create${n.pascal} = directive("create${n.pascal}")
|
|
100
|
-
.creates(${n.pascal})
|
|
101
|
-
.payload(
|
|
102
|
-
z.object({
|
|
103
|
-
label: z.string(),
|
|
104
|
-
pos: z.number().int(),
|
|
105
|
-
status: z.enum(${n.statusesConst}),
|
|
106
|
-
}),
|
|
107
|
-
)
|
|
108
|
-
.plan((p) => [
|
|
109
|
-
set(${n.pascal}, "label", p.label),
|
|
110
|
-
set(${n.pascal}, "pos", p.pos),
|
|
111
|
-
set(${n.pascal}, "status", p.status),
|
|
112
|
-
]);
|
|
113
|
-
|
|
114
|
-
/** move${n.pascal} \u2014 .mutates pos. */
|
|
115
|
-
export const move${n.pascal} = directive("move${n.pascal}")
|
|
116
|
-
.mutates(${n.pascal})
|
|
117
|
-
.payload(z.object({ pos: z.number().int() }))
|
|
118
|
-
.plan((p) => [set(${n.pascal}, "pos", p.pos)]);
|
|
119
|
-
|
|
120
|
-
/** tag${n.pascal} \u2014 .mutates tags (add-wins union). */
|
|
121
|
-
export const tag${n.pascal} = directive("tag${n.pascal}")
|
|
122
|
-
.mutates(${n.pascal})
|
|
123
|
-
.payload(z.object({ tags: z.array(z.string()) }))
|
|
124
|
-
.plan((p) => [addToSet(${n.pascal}, "tags", p.tags)]);
|
|
125
|
-
|
|
126
|
-
/** setCustomFieldOn${n.pascal} \u2014 .mutates customFields (a map entry). */
|
|
127
|
-
export const setCustomFieldOn${n.pascal} = directive("setCustomFieldOn${n.pascal}")
|
|
128
|
-
.mutates(${n.pascal})
|
|
129
|
-
.payload(z.object({ key: z.string(), value: z.string() }))
|
|
130
|
-
.plan((p) => [setEntry(${n.pascal}, "customFields", p.key, p.value)]);
|
|
131
|
-
`;
|
|
132
|
-
}
|
|
133
|
-
function aggregateTemplate(raw) {
|
|
134
|
-
const n = namesFor(raw);
|
|
135
|
-
return `${HEADER(raw, "aggregate")}
|
|
136
|
-
import { aggregate, t, Conflict, Lww, AddWins, MapOf } from "@githolon/dsl";
|
|
137
|
-
|
|
138
|
-
${aggregateBlock(n)}
|
|
139
|
-
`;
|
|
140
|
-
}
|
|
141
|
-
function intentTemplate(raw) {
|
|
142
|
-
const n = namesFor(raw);
|
|
143
|
-
return `${HEADER(raw, "intent (directive)")}
|
|
144
|
-
import { z, aggregate, directive, t, Conflict, set } from "@githolon/dsl";
|
|
145
|
-
|
|
146
|
-
// Stub target \u2014 in a real domain, import the existing aggregate handle instead.
|
|
147
|
-
export const ${n.pascal} = aggregate("${n.pascal}", {
|
|
148
|
-
label: t.string().merge(Conflict),
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
/** ${n.camel} \u2014 .mutates the target aggregate. */
|
|
152
|
-
export const ${n.camel} = directive("${n.camel}")
|
|
153
|
-
.mutates(${n.pascal})
|
|
154
|
-
.payload(z.object({ label: z.string() }))
|
|
155
|
-
.plan((p) => [set(${n.pascal}, "label", p.label)]);
|
|
156
|
-
`;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// src/generate.ts
|
|
160
|
-
var RENDERERS = {
|
|
161
|
-
domain: domainTemplate,
|
|
162
|
-
aggregate: aggregateTemplate,
|
|
163
|
-
intent: intentTemplate
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
164
10
|
};
|
|
165
|
-
function renderScaffold(kind, name) {
|
|
166
|
-
assertValidName(name);
|
|
167
|
-
const render = RENDERERS[kind];
|
|
168
|
-
if (!render) {
|
|
169
|
-
throw new Error(`Unknown generator kind '${kind}'. Use: domain | aggregate | intent.`);
|
|
170
|
-
}
|
|
171
|
-
return render(name);
|
|
172
|
-
}
|
|
173
|
-
function generate(opts) {
|
|
174
|
-
const contents = renderScaffold(opts.kind, opts.name);
|
|
175
|
-
const fileName = `${snakeCase(opts.name)}.ts`;
|
|
176
|
-
const path = join(opts.outDir, fileName);
|
|
177
|
-
if (existsSync(path) && !opts.force) {
|
|
178
|
-
throw new Error(
|
|
179
|
-
`Refusing to overwrite existing file: ${path}
|
|
180
|
-
Re-run with --force to replace it.`
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
mkdirSync(opts.outDir, { recursive: true });
|
|
184
|
-
writeFileSync(path, contents, "utf8");
|
|
185
|
-
return { path, contents };
|
|
186
|
-
}
|
|
187
11
|
|
|
188
12
|
// src/cloud.ts
|
|
13
|
+
var cloud_exports = {};
|
|
14
|
+
__export(cloud_exports, {
|
|
15
|
+
cloudBase: () => cloudBase,
|
|
16
|
+
configDir: () => configDir,
|
|
17
|
+
deploy: () => deploy,
|
|
18
|
+
describeLawMove: () => describeLawMove,
|
|
19
|
+
discoverDeployJson: () => discoverDeployJson,
|
|
20
|
+
getSecret: () => getSecret,
|
|
21
|
+
loadCreds: () => loadCreds,
|
|
22
|
+
login: () => login,
|
|
23
|
+
logout: () => logout,
|
|
24
|
+
principalHeaders: () => principalHeaders,
|
|
25
|
+
secretRotate: () => secretRotate,
|
|
26
|
+
secretSet: () => secretSet,
|
|
27
|
+
sessionToken: () => sessionToken,
|
|
28
|
+
setPrincipal: () => setPrincipal,
|
|
29
|
+
setSecret: () => setSecret,
|
|
30
|
+
whoami: () => whoami,
|
|
31
|
+
wsCreate: () => wsCreate,
|
|
32
|
+
wsRetire: () => wsRetire,
|
|
33
|
+
wsStatus: () => wsStatus
|
|
34
|
+
});
|
|
189
35
|
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
190
36
|
import { join as join2 } from "node:path";
|
|
191
37
|
import { homedir } from "node:os";
|
|
192
|
-
var DEFAULT_CLOUD = "https://nomos.captainapp.co.uk";
|
|
193
|
-
var DEFAULT_AUTH_URL = "https://kjbcjkihxskuwwfdqklt.supabase.co";
|
|
194
|
-
var DEFAULT_AUTH_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtqYmNqa2loeHNrdXd3ZmRxa2x0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTA3MDU2OTAsImV4cCI6MjA2NjI4MTY5MH0.V9e7XsuTlTOLqefOIedTqlBiTxUSn4O5FZSPWwAxiSI";
|
|
195
38
|
function cloudBase(flag) {
|
|
196
39
|
return (flag ?? process.env["NOMOS_CLOUD"] ?? DEFAULT_CLOUD).replace(/\/+$/, "");
|
|
197
40
|
}
|
|
@@ -216,7 +59,6 @@ function saveCreds(c) {
|
|
|
216
59
|
writeFileSync2(credsPath(), JSON.stringify(c, null, 2) + "\n", "utf8");
|
|
217
60
|
chmodSync(credsPath(), 384);
|
|
218
61
|
}
|
|
219
|
-
var secretKey = (cloud, ws) => `${cloud} ${ws}`;
|
|
220
62
|
function getSecret(cloud, ws) {
|
|
221
63
|
return loadCreds().secrets[secretKey(cloud, ws)];
|
|
222
64
|
}
|
|
@@ -289,8 +131,6 @@ function discoverDeployJson(cwd, explicit) {
|
|
|
289
131
|
}
|
|
290
132
|
return join2(buildDir, bodies[0]);
|
|
291
133
|
}
|
|
292
|
-
var out = (s) => void process.stdout.write(s + "\n");
|
|
293
|
-
var err = (s) => void process.stderr.write("error: " + s + "\n");
|
|
294
134
|
async function login(opts) {
|
|
295
135
|
if (opts.agent !== true && opts.token === void 0) {
|
|
296
136
|
err(
|
|
@@ -483,65 +323,18 @@ async function secretRotate(ws, opts) {
|
|
|
483
323
|
out(`\u2713 secret for ${ws} rotated and saved \u2192 ${credsPath()}`);
|
|
484
324
|
return 0;
|
|
485
325
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
return req.resolve("@githolon/dsl/package.json");
|
|
497
|
-
} catch {
|
|
498
|
-
return void 0;
|
|
499
|
-
}
|
|
500
|
-
};
|
|
501
|
-
const dslPkg = resolveDslPkg(cwd) ?? resolveDslPkg(void 0);
|
|
502
|
-
return dslPkg === void 0 ? void 0 : join3(dirname(dslPkg), "compile_package.mjs");
|
|
503
|
-
}
|
|
504
|
-
var NO_DSL_REMEDY = "@githolon/dsl not found \u2014 add it to your project's dependencies";
|
|
505
|
-
function runCompile(args) {
|
|
506
|
-
const launcher = resolveCompileLauncher(process.cwd());
|
|
507
|
-
if (launcher === void 0) {
|
|
508
|
-
process.stderr.write(`error: ${NO_DSL_REMEDY}
|
|
509
|
-
`);
|
|
510
|
-
return 1;
|
|
511
|
-
}
|
|
512
|
-
const r = spawnSync(process.execPath, [launcher, ...args], { stdio: "inherit", cwd: process.cwd() });
|
|
513
|
-
return r.status ?? 1;
|
|
514
|
-
}
|
|
515
|
-
function compileAsync(cwd, args = []) {
|
|
516
|
-
const launcher = resolveCompileLauncher(cwd);
|
|
517
|
-
const t0 = performance.now();
|
|
518
|
-
if (launcher === void 0) {
|
|
519
|
-
return Promise.resolve({ ok: false, ms: 0, output: NO_DSL_REMEDY });
|
|
326
|
+
var DEFAULT_CLOUD, DEFAULT_AUTH_URL, DEFAULT_AUTH_ANON_KEY, secretKey, out, err;
|
|
327
|
+
var init_cloud = __esm({
|
|
328
|
+
"src/cloud.ts"() {
|
|
329
|
+
"use strict";
|
|
330
|
+
DEFAULT_CLOUD = "https://nomos.captainapp.co.uk";
|
|
331
|
+
DEFAULT_AUTH_URL = "https://kjbcjkihxskuwwfdqklt.supabase.co";
|
|
332
|
+
DEFAULT_AUTH_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImtqYmNqa2loeHNrdXd3ZmRxa2x0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTA3MDU2OTAsImV4cCI6MjA2NjI4MTY5MH0.V9e7XsuTlTOLqefOIedTqlBiTxUSn4O5FZSPWwAxiSI";
|
|
333
|
+
secretKey = (cloud, ws) => `${cloud} ${ws}`;
|
|
334
|
+
out = (s) => void process.stdout.write(s + "\n");
|
|
335
|
+
err = (s) => void process.stderr.write("error: " + s + "\n");
|
|
520
336
|
}
|
|
521
|
-
|
|
522
|
-
const chunks = [];
|
|
523
|
-
const dec3 = new TextDecoder();
|
|
524
|
-
const child = spawn(process.execPath, [launcher, ...args], { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
525
|
-
child.stdout?.on("data", (c) => chunks.push(dec3.decode(c)));
|
|
526
|
-
child.stderr?.on("data", (c) => chunks.push(dec3.decode(c)));
|
|
527
|
-
child.on("error", (e) => resolveRun({ ok: false, ms: performance.now() - t0, output: String(e) }));
|
|
528
|
-
child.on(
|
|
529
|
-
"close",
|
|
530
|
-
(code) => resolveRun({ ok: code === 0, ms: performance.now() - t0, output: chunks.join("") })
|
|
531
|
-
);
|
|
532
|
-
});
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// src/dev.ts
|
|
536
|
-
import { existsSync as existsSync5, readFileSync as readFileSync3, watch } from "node:fs";
|
|
537
|
-
import { createServer } from "node:http";
|
|
538
|
-
import { dirname as dirname3, join as join6, resolve } from "node:path";
|
|
539
|
-
import { pathToFileURL as pathToFileURL3 } from "node:url";
|
|
540
|
-
|
|
541
|
-
// vendor/engine/engine.mjs
|
|
542
|
-
import { WASI, File, Directory, OpenFile, ConsoleStdout, PreopenDirectory } from "@bjorn3/browser_wasi_shim";
|
|
543
|
-
import git from "isomorphic-git";
|
|
544
|
-
import http from "isomorphic-git/http/web";
|
|
337
|
+
});
|
|
545
338
|
|
|
546
339
|
// vendor/engine/git-fs.mjs
|
|
547
340
|
function fsErr(code, p) {
|
|
@@ -640,21 +433,26 @@ function makeGitFs(preopen, mount, { File: File3, Directory: Directory3 }) {
|
|
|
640
433
|
};
|
|
641
434
|
return { promises };
|
|
642
435
|
}
|
|
436
|
+
var init_git_fs = __esm({
|
|
437
|
+
"vendor/engine/git-fs.mjs"() {
|
|
438
|
+
"use strict";
|
|
439
|
+
}
|
|
440
|
+
});
|
|
643
441
|
|
|
644
442
|
// vendor/engine/tree.mjs
|
|
645
|
-
var enc
|
|
646
|
-
var
|
|
443
|
+
var enc, dec;
|
|
444
|
+
var init_tree = __esm({
|
|
445
|
+
"vendor/engine/tree.mjs"() {
|
|
446
|
+
"use strict";
|
|
447
|
+
enc = new TextEncoder();
|
|
448
|
+
dec = new TextDecoder();
|
|
449
|
+
}
|
|
450
|
+
});
|
|
647
451
|
|
|
648
452
|
// vendor/engine/engine.mjs
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
var repoArgOf = (ws) => `/work/ws/${ws}`;
|
|
653
|
-
var gitdirOf = (ws) => `/work/ws/${ws}/nomos.git`;
|
|
654
|
-
var unpack = (p) => {
|
|
655
|
-
const v = typeof p === "bigint" ? p : BigInt(p);
|
|
656
|
-
return { ptr: Number(v >> 32n), len: Number(v & 0xffffffffn) };
|
|
657
|
-
};
|
|
453
|
+
import { WASI, File, Directory, OpenFile, ConsoleStdout, PreopenDirectory } from "@bjorn3/browser_wasi_shim";
|
|
454
|
+
import git from "isomorphic-git";
|
|
455
|
+
import http from "isomorphic-git/http/web";
|
|
658
456
|
async function sha256hex(t) {
|
|
659
457
|
const b = await crypto.subtle.digest("SHA-256", enc2.encode(t));
|
|
660
458
|
return [...new Uint8Array(b)].map((x) => x.toString(16).padStart(2, "0")).join("");
|
|
@@ -662,13 +460,13 @@ async function sha256hex(t) {
|
|
|
662
460
|
function osEntropyBuffer(n) {
|
|
663
461
|
const bytes = new Uint8Array(n * 8);
|
|
664
462
|
for (let o = 0; o < bytes.length; o += 65536) crypto.getRandomValues(bytes.subarray(o, Math.min(o + 65536, bytes.length)));
|
|
665
|
-
const
|
|
463
|
+
const out9 = new Array(n), T = 2 ** 53;
|
|
666
464
|
for (let i = 0; i < n; i++) {
|
|
667
465
|
let z = 0n;
|
|
668
466
|
for (let b = 7; b >= 0; b--) z = z << 8n | BigInt(bytes[i * 8 + b]);
|
|
669
|
-
|
|
467
|
+
out9[i] = Number(z >> 11n) / T;
|
|
670
468
|
}
|
|
671
|
-
return
|
|
469
|
+
return out9;
|
|
672
470
|
}
|
|
673
471
|
function stringifyBig(o) {
|
|
674
472
|
return JSON.stringify(o, (_k, v) => typeof v === "bigint" ? `@@B:${v}@@` : v).replace(/"@@B:(\d+)@@"/g, "$1");
|
|
@@ -694,7 +492,6 @@ function seedManifests(m, strings) {
|
|
|
694
492
|
m.set("domain_manifests.json", new File(enc2.encode(strings.read)));
|
|
695
493
|
m.set("identity-manifests.json", new File(enc2.encode(strings.identity)));
|
|
696
494
|
}
|
|
697
|
-
var installPayload = (hash, usda, installedBy) => ({ domainHash: hash, packageUsda: usda, installedBy, authorityScope: "workspace/root", dependencies: [], finalizers: [] });
|
|
698
495
|
async function createEngine({ wasmModule, bootstrapPkg, nomosPkg, manifestStrings, replica, installedBy = "nomos-cloud" }) {
|
|
699
496
|
const hashes = { bootstrap: await sha256hex(bootstrapPkg), nomos: await sha256hex(nomosPkg) };
|
|
700
497
|
const root = /* @__PURE__ */ new Map();
|
|
@@ -780,6 +577,9 @@ function unmount(eng, ws) {
|
|
|
780
577
|
function writeWork(eng, name, bytes) {
|
|
781
578
|
eng.preopen.dir.contents.set(name, new File(bytes));
|
|
782
579
|
}
|
|
580
|
+
function mintId(eng, typeTag) {
|
|
581
|
+
return JSON.parse(call(eng.ex, "mint", { typeTag, nowMillis: Date.now() }, eng.STDERR)).id;
|
|
582
|
+
}
|
|
783
583
|
function author(eng, ws, domain, directiveId, payload, controllerHash, opts = {}) {
|
|
784
584
|
const seq = eng.seq++;
|
|
785
585
|
const envelope = { payload: { domain, directiveId, payload }, captured_ports: { clock: { physical: Date.now(), logical: 0, replica: eng.replica }, rng: osEntropyBuffer(opts.rngDraws ?? 64) }, policy_version: 1, policy_domain: "Nomos", policy_gas: 0, policy_memory: 0 };
|
|
@@ -791,10 +591,6 @@ function author(eng, ws, domain, directiveId, payload, controllerHash, opts = {}
|
|
|
791
591
|
if (defer && res.ok) (eng.pendingProjection ??= /* @__PURE__ */ new Set()).add(ws);
|
|
792
592
|
return res;
|
|
793
593
|
}
|
|
794
|
-
var qById = (eng, ws, id) => JSON.parse(call(eng.ex, "query_by_id", { repoArg: repoArgOf(ws), workspace: ws, aggregateId: id, branch: BRANCH }, eng.STDERR));
|
|
795
|
-
var query = (eng, ws, queryId, paramsJson) => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryId, paramsJson, branch: BRANCH }, eng.STDERR));
|
|
796
|
-
var count = (eng, ws, countId, groupKey) => JSON.parse(call(eng.ex, "count", { repoArg: repoArgOf(ws), workspace: ws, countId, groupKey, branch: BRANCH }, eng.STDERR));
|
|
797
|
-
var sum = (eng, ws, sumId, groupKey) => JSON.parse(call(eng.ex, "sum", { repoArg: repoArgOf(ws), workspace: ws, sumId, groupKey, branch: BRANCH }, eng.STDERR));
|
|
798
594
|
function applyIntentBytes(eng, ws, bytes) {
|
|
799
595
|
const name = `intent-in-${eng.seq++}.json`;
|
|
800
596
|
writeWork(eng, name, bytes);
|
|
@@ -803,14 +599,34 @@ function applyIntentBytes(eng, ws, bytes) {
|
|
|
803
599
|
function verifyChainLocal(eng, ws) {
|
|
804
600
|
return JSON.parse(call(eng.ex, "verify_chain", { repoArg: repoArgOf(ws), workspace: ws, branch: BRANCH }, eng.STDERR));
|
|
805
601
|
}
|
|
602
|
+
var enc2, dec2, BRANCH, repoArgOf, gitdirOf, unpack, installPayload, qById, query, count, sum;
|
|
603
|
+
var init_engine = __esm({
|
|
604
|
+
"vendor/engine/engine.mjs"() {
|
|
605
|
+
"use strict";
|
|
606
|
+
init_git_fs();
|
|
607
|
+
init_tree();
|
|
608
|
+
enc2 = new TextEncoder();
|
|
609
|
+
dec2 = new TextDecoder();
|
|
610
|
+
BRANCH = "main";
|
|
611
|
+
repoArgOf = (ws) => `/work/ws/${ws}`;
|
|
612
|
+
gitdirOf = (ws) => `/work/ws/${ws}/nomos.git`;
|
|
613
|
+
unpack = (p) => {
|
|
614
|
+
const v = typeof p === "bigint" ? p : BigInt(p);
|
|
615
|
+
return { ptr: Number(v >> 32n), len: Number(v & 0xffffffffn) };
|
|
616
|
+
};
|
|
617
|
+
installPayload = (hash, usda, installedBy, dispositions) => ({ domainHash: hash, packageUsda: usda, installedBy, authorityScope: "workspace/root", dependencies: [], finalizers: [], ...dispositions ? { dispositions } : {} });
|
|
618
|
+
qById = (eng, ws, id) => JSON.parse(call(eng.ex, "query_by_id", { repoArg: repoArgOf(ws), workspace: ws, aggregateId: id, branch: BRANCH }, eng.STDERR));
|
|
619
|
+
query = (eng, ws, queryId, paramsJson) => JSON.parse(call(eng.ex, "query", { repoArg: repoArgOf(ws), workspace: ws, queryId, paramsJson, branch: BRANCH }, eng.STDERR));
|
|
620
|
+
count = (eng, ws, countId, groupKey) => JSON.parse(call(eng.ex, "count", { repoArg: repoArgOf(ws), workspace: ws, countId, groupKey, branch: BRANCH }, eng.STDERR));
|
|
621
|
+
sum = (eng, ws, sumId, groupKey) => JSON.parse(call(eng.ex, "sum", { repoArg: repoArgOf(ws), workspace: ws, sumId, groupKey, branch: BRANCH }, eng.STDERR));
|
|
622
|
+
}
|
|
623
|
+
});
|
|
806
624
|
|
|
807
625
|
// src/local_holon.ts
|
|
808
626
|
import { randomBytes } from "node:crypto";
|
|
809
627
|
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
810
628
|
import { dirname as dirname2, join as join4 } from "node:path";
|
|
811
629
|
import { File as File2, Directory as Directory2 } from "@bjorn3/browser_wasi_shim";
|
|
812
|
-
var out2 = (s) => void process.stdout.write(s + "\n");
|
|
813
|
-
var err2 = (s) => void process.stderr.write("error: " + s + "\n");
|
|
814
630
|
async function fetchJsonCached(url, cacheFile) {
|
|
815
631
|
try {
|
|
816
632
|
const r = await fetch(url);
|
|
@@ -856,62 +672,620 @@ function mergeManifests(baked, overlay) {
|
|
|
856
672
|
}
|
|
857
673
|
}
|
|
858
674
|
}
|
|
859
|
-
return { read: JSON.stringify(read), identity: JSON.stringify(identity) };
|
|
860
|
-
}
|
|
861
|
-
function writeTreeToDisk(dir, diskPath) {
|
|
862
|
-
mkdirSync3(diskPath, { recursive: true });
|
|
863
|
-
for (const [name, inode] of dir.contents) {
|
|
864
|
-
const p = join4(diskPath, name);
|
|
865
|
-
if (inode.contents instanceof Map) writeTreeToDisk(inode, p);
|
|
866
|
-
else writeFileSync3(p, inode.data ?? new Uint8Array(0));
|
|
675
|
+
return { read: JSON.stringify(read), identity: JSON.stringify(identity) };
|
|
676
|
+
}
|
|
677
|
+
function writeTreeToDisk(dir, diskPath) {
|
|
678
|
+
mkdirSync3(diskPath, { recursive: true });
|
|
679
|
+
for (const [name, inode] of dir.contents) {
|
|
680
|
+
const p = join4(diskPath, name);
|
|
681
|
+
if (inode.contents instanceof Map) writeTreeToDisk(inode, p);
|
|
682
|
+
else writeFileSync3(p, inode.data ?? new Uint8Array(0));
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
function readTreeFromDisk(diskPath) {
|
|
686
|
+
const contents = /* @__PURE__ */ new Map();
|
|
687
|
+
for (const name of readdirSync2(diskPath)) {
|
|
688
|
+
const p = join4(diskPath, name);
|
|
689
|
+
if (statSync(p).isDirectory()) contents.set(name, readTreeFromDisk(p));
|
|
690
|
+
else contents.set(name, new File2(readFileSync2(p)));
|
|
691
|
+
}
|
|
692
|
+
return new Directory2(contents);
|
|
693
|
+
}
|
|
694
|
+
async function holonEngine(cloud, overlay, installedBy) {
|
|
695
|
+
const runtime = await fetchRuntime(cloud);
|
|
696
|
+
const manifestStrings = overlay === void 0 ? { read: runtime.manifests.domainManifests, identity: runtime.manifests.identityManifests } : mergeManifests(runtime.manifests, overlay);
|
|
697
|
+
const replica = BigInt(`0x${randomBytes(8).toString("hex")}`) & (1n << 63n) - 1n;
|
|
698
|
+
const eng = await createEngine({
|
|
699
|
+
wasmModule: await WebAssembly.compile(runtime.wasmBytes),
|
|
700
|
+
bootstrapPkg: runtime.packages.bootstrap,
|
|
701
|
+
nomosPkg: runtime.packages.nomos,
|
|
702
|
+
manifestStrings,
|
|
703
|
+
replica,
|
|
704
|
+
installedBy
|
|
705
|
+
});
|
|
706
|
+
return { eng, runtime };
|
|
707
|
+
}
|
|
708
|
+
async function mountFresh(eng, ws) {
|
|
709
|
+
await mountWorkspace(eng, ws, { remote: "https://unborn.invalid/", headers: {} });
|
|
710
|
+
}
|
|
711
|
+
function sealedIntentOf(eng, ws, res) {
|
|
712
|
+
if (typeof res.intentOut !== "string") return void 0;
|
|
713
|
+
const f = eng.preopen.dir.contents.get("ws").contents.get(ws)?.contents.get(res.intentOut);
|
|
714
|
+
if (f === void 0 || !(f.data instanceof Uint8Array)) return void 0;
|
|
715
|
+
try {
|
|
716
|
+
return JSON.parse(new TextDecoder().decode(f.data));
|
|
717
|
+
} catch {
|
|
718
|
+
return void 0;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
function printVerdict(v) {
|
|
722
|
+
if (v["valid"] === true) {
|
|
723
|
+
out3(`\u2713 chain VALID \u2014 head ${String(v["head"]).slice(0, 12)}\u2026, ${v["intents"]} intent(s), ${v["plansRerun"]} plan(s) re-run`);
|
|
724
|
+
out3(` controller ${String(v["controllerHash"]).slice(0, 12)}\u2026 | installed: ${v["installedDomains"]?.map((h) => h.slice(0, 12) + "\u2026").join(", ") ?? "\u2014"}`);
|
|
725
|
+
return true;
|
|
726
|
+
}
|
|
727
|
+
err3(`chain INVALID at ${v["check"]}${v["atIndex"] !== void 0 ? ` (intent ${v["atIndex"]})` : ""}: ${v["error"]}`);
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
730
|
+
var out3, err3;
|
|
731
|
+
var init_local_holon = __esm({
|
|
732
|
+
"src/local_holon.ts"() {
|
|
733
|
+
"use strict";
|
|
734
|
+
init_engine();
|
|
735
|
+
init_cloud();
|
|
736
|
+
out3 = (s) => void process.stdout.write(s + "\n");
|
|
737
|
+
err3 = (s) => void process.stderr.write("error: " + s + "\n");
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
// src/proof_offline.ts
|
|
742
|
+
var proof_offline_exports = {};
|
|
743
|
+
__export(proof_offline_exports, {
|
|
744
|
+
parseOfflineLegs: () => parseOfflineLegs,
|
|
745
|
+
runOfflineLegs: () => runOfflineLegs,
|
|
746
|
+
runOfflineProofStandalone: () => runOfflineProofStandalone
|
|
747
|
+
});
|
|
748
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
|
|
749
|
+
import { basename, dirname as dirname3, join as join6 } from "node:path";
|
|
750
|
+
function parseOfflineLegs(proofSource) {
|
|
751
|
+
if (!proofSource.includes("AUTO-GENERATED by nomos-compile")) {
|
|
752
|
+
throw new Error("not a generated proof (missing the AUTO-GENERATED header) \u2014 run `githolon compile` to (re)generate build/<name>.proof.mts");
|
|
753
|
+
}
|
|
754
|
+
const marker = proofSource.match(/^\/\/ @nomos-proof-legs (\{.*\})$/m);
|
|
755
|
+
if (marker !== null) {
|
|
756
|
+
return JSON.parse(marker[1]);
|
|
757
|
+
}
|
|
758
|
+
const dispatch = proofSource.match(/^await app\.(\w+)\((\{.*\})\);$/m);
|
|
759
|
+
if (dispatch === null) {
|
|
760
|
+
throw new Error("generated proof has no `await app.<directive>({...})` dispatch \u2014 regenerate with `githolon compile` (a creating directive is required)");
|
|
761
|
+
}
|
|
762
|
+
const directiveId = dispatch[1];
|
|
763
|
+
const payload = JSON.parse(dispatch[2]);
|
|
764
|
+
const dom = proofSource.match(/dispatch (\w+)\/(\w+) — offline write/);
|
|
765
|
+
if (dom === null || dom[2] !== directiveId) {
|
|
766
|
+
throw new Error("generated proof names no `dispatch <domain>/<directive> \u2014 offline write` \u2014 regenerate with `githolon compile`");
|
|
767
|
+
}
|
|
768
|
+
const domain = dom[1];
|
|
769
|
+
const autoStamped = [];
|
|
770
|
+
const stampLine = proofSource.split("\n").find((l) => l.includes("omitted \u2014 the client auto-stamps ISO now()"));
|
|
771
|
+
if (stampLine !== void 0) {
|
|
772
|
+
for (const m of stampLine.matchAll(/`(\w+)`/g)) autoStamped.push(m[1]);
|
|
773
|
+
}
|
|
774
|
+
let queryLeg;
|
|
775
|
+
const q = proofSource.match(/local declared query (\w+):/);
|
|
776
|
+
if (q !== null) {
|
|
777
|
+
const call2 = proofSource.match(/^const rows = await app\.\w+\((\{.*\})\);$/m);
|
|
778
|
+
if (call2 === null) throw new Error(`generated proof names query '${q[1]}' but its accessor call is missing \u2014 regenerate with \`githolon compile\``);
|
|
779
|
+
queryLeg = { id: q[1], params: JSON.parse(call2[1]) };
|
|
780
|
+
}
|
|
781
|
+
let countLeg;
|
|
782
|
+
const c = proofSource.match(/local declared count (\w+):/);
|
|
783
|
+
if (c !== null) {
|
|
784
|
+
const call2 = proofSource.match(/^const localCount = await app\.\w+\((.*?)\);$/m);
|
|
785
|
+
if (call2 === null) throw new Error(`generated proof names count '${c[1]}' but its accessor call is missing \u2014 regenerate with \`githolon compile\``);
|
|
786
|
+
const arg = call2[1].trim();
|
|
787
|
+
countLeg = { id: c[1], group: arg.length === 0 ? "" : JSON.parse(arg) };
|
|
788
|
+
}
|
|
789
|
+
return {
|
|
790
|
+
domain,
|
|
791
|
+
directiveId,
|
|
792
|
+
payload,
|
|
793
|
+
autoStamped,
|
|
794
|
+
...queryLeg !== void 0 ? { query: queryLeg } : {},
|
|
795
|
+
...countLeg !== void 0 ? { count: countLeg } : {}
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
function runOfflineLegs(eng, ws, lawHash, legs) {
|
|
799
|
+
const t0 = performance.now();
|
|
800
|
+
const done = (ok, lines2, error) => ({
|
|
801
|
+
ok,
|
|
802
|
+
ms: performance.now() - t0,
|
|
803
|
+
legs: lines2,
|
|
804
|
+
...error !== void 0 ? { error } : {}
|
|
805
|
+
});
|
|
806
|
+
const lines = [];
|
|
807
|
+
const payload = { ...legs.payload };
|
|
808
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
809
|
+
for (const f of legs.autoStamped) payload[f] = stamp;
|
|
810
|
+
if (legs.mintField !== void 0 && legs.aggregateId !== void 0) {
|
|
811
|
+
payload[legs.mintField] = mintId(eng, legs.aggregateId);
|
|
812
|
+
}
|
|
813
|
+
const res = author(eng, ws, legs.domain, legs.directiveId, payload, lawHash);
|
|
814
|
+
if (res.ok === false) {
|
|
815
|
+
return done(false, lines, `dispatch ${legs.domain}/${legs.directiveId} refused: ${res.error ?? "unknown"} \u2014 the law itself rejected its own synthesized sample; check the directive's plan/payload schema`);
|
|
816
|
+
}
|
|
817
|
+
const createdId = (sealedIntentOf(eng, ws, res)?.events ?? []).find((e) => e.marker === "Create")?.aggregate;
|
|
818
|
+
lines.push(`\u2713 dispatch ${legs.domain}/${legs.directiveId} \u2014 offline write under the new law${createdId !== void 0 ? ` (${createdId.slice(0, 28)}\u2026)` : ""}`);
|
|
819
|
+
if (legs.query !== void 0) {
|
|
820
|
+
const rows = query(eng, ws, legs.query.id, JSON.stringify(legs.query.params));
|
|
821
|
+
if (rows.length !== 1 || createdId !== void 0 && rows[0].id !== createdId) {
|
|
822
|
+
return done(false, lines, `declared query ${legs.query.id} expected the 1 created row, got ${JSON.stringify(rows.map((r) => r.id))} \u2014 check the query's key fields against the directive's payload`);
|
|
823
|
+
}
|
|
824
|
+
lines.push(`\u2713 declared query ${legs.query.id} answers locally \u2014 1 row`);
|
|
825
|
+
}
|
|
826
|
+
if (legs.count !== void 0) {
|
|
827
|
+
const c = count(eng, ws, legs.count.id, legs.count.group).count;
|
|
828
|
+
if (c !== 1) {
|
|
829
|
+
return done(false, lines, `declared count ${legs.count.id}(${JSON.stringify(legs.count.group)}) expected 1, got ${c} \u2014 check the count's grouping against the created row`);
|
|
830
|
+
}
|
|
831
|
+
lines.push(`\u2713 declared count ${legs.count.id}(${JSON.stringify(legs.count.group)}) = 1 locally`);
|
|
832
|
+
}
|
|
833
|
+
return done(true, lines);
|
|
834
|
+
}
|
|
835
|
+
async function runOfflineProofStandalone(proofPath, cloud, domain) {
|
|
836
|
+
const src = readFileSync3(proofPath, "utf8");
|
|
837
|
+
const packageName = basename(proofPath).replace(/\.proof\.mts$/, "");
|
|
838
|
+
const deployFile = join6(dirname3(proofPath), `${packageName}.deploy.json`);
|
|
839
|
+
if (!existsSync5(deployFile)) {
|
|
840
|
+
throw new Error(`missing ${deployFile} \u2014 run \`githolon compile\` (the proof and the deploy body are built together)`);
|
|
841
|
+
}
|
|
842
|
+
let legs;
|
|
843
|
+
const legsFile = join6(dirname3(proofPath), `${packageName}.proof-legs.json`);
|
|
844
|
+
if (existsSync5(legsFile)) {
|
|
845
|
+
const index = JSON.parse(readFileSync3(legsFile, "utf8"));
|
|
846
|
+
const keys = Object.keys(index.domains);
|
|
847
|
+
const chosen = domain ?? index.default;
|
|
848
|
+
if (index.domains[chosen] === void 0) {
|
|
849
|
+
throw new Error(`no provable domain '${chosen}' in this package \u2014 it proves: ${keys.join(", ")} (pick one: githolon proof --domain <key>)`);
|
|
850
|
+
}
|
|
851
|
+
legs = index.domains[chosen];
|
|
852
|
+
if (keys.length > 1) console.log(`githolon proof \u2014 domain '${chosen}' (of ${keys.length}: ${keys.join(", ")}; --domain <key> to switch)`);
|
|
853
|
+
} else {
|
|
854
|
+
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`);
|
|
855
|
+
legs = parseOfflineLegs(src);
|
|
856
|
+
}
|
|
857
|
+
const deployBody = JSON.parse(readFileSync3(deployFile, "utf8"));
|
|
858
|
+
const lawHash = await sha256hex(deployBody.packageUsda);
|
|
859
|
+
console.log(`githolon proof \u2014 OFFLINE legs on a local holon (the cloud loop: githolon proof --live)`);
|
|
860
|
+
const { eng } = await holonEngine(cloud, deployBody, "githolon-proof");
|
|
861
|
+
const ws = "proof-offline";
|
|
862
|
+
await mountFresh(eng, ws);
|
|
863
|
+
author(eng, ws, "bootstrap", "installDomain", installPayload(eng.hashes.nomos, eng.nomosPkg, "githolon-proof"), "");
|
|
864
|
+
author(eng, ws, "nomos", "installDomain", installPayload(lawHash, deployBody.packageUsda, "githolon-proof"), eng.hashes.nomos);
|
|
865
|
+
const v = runOfflineLegs(eng, ws, lawHash, legs);
|
|
866
|
+
for (const l of v.legs) console.log(l);
|
|
867
|
+
if (!v.ok) {
|
|
868
|
+
console.error(`\u2717 ${v.error}`);
|
|
869
|
+
return 1;
|
|
870
|
+
}
|
|
871
|
+
console.log(`
|
|
872
|
+
OFFLINE PROOF GREEN (${Math.round(v.ms)}ms) \u2014 the same legs rerun on every save in \`githolon dev\`.`);
|
|
873
|
+
console.log(`Deploy with confidence when ready: githolon ws create <ws> && githolon deploy <ws> (or the full cloud proof: githolon proof --live)`);
|
|
874
|
+
return 0;
|
|
875
|
+
}
|
|
876
|
+
var init_proof_offline = __esm({
|
|
877
|
+
"src/proof_offline.ts"() {
|
|
878
|
+
"use strict";
|
|
879
|
+
init_engine();
|
|
880
|
+
init_local_holon();
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
// src/cli.ts
|
|
885
|
+
import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
|
|
886
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
887
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
888
|
+
import { dirname as dirname5, join as join10, resolve as resolve2 } from "node:path";
|
|
889
|
+
import { pathToFileURL as pathToFileURL4 } from "node:url";
|
|
890
|
+
|
|
891
|
+
// src/generate.ts
|
|
892
|
+
import { mkdirSync, existsSync, writeFileSync } from "node:fs";
|
|
893
|
+
import { join } from "node:path";
|
|
894
|
+
|
|
895
|
+
// src/naming.ts
|
|
896
|
+
function words(raw) {
|
|
897
|
+
return raw.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_\-\s]+/g, " ").trim().toLowerCase().split(" ").filter((w) => w.length > 0);
|
|
898
|
+
}
|
|
899
|
+
var cap = (w) => w.length === 0 ? w : w[0].toUpperCase() + w.slice(1);
|
|
900
|
+
function pascalCase(raw) {
|
|
901
|
+
return words(raw).map(cap).join("");
|
|
902
|
+
}
|
|
903
|
+
function camelCase(raw) {
|
|
904
|
+
return words(raw).map((w, i) => i === 0 ? w : cap(w)).join("");
|
|
905
|
+
}
|
|
906
|
+
function snakeCase(raw) {
|
|
907
|
+
return words(raw).join("_");
|
|
908
|
+
}
|
|
909
|
+
function screamingSnakeCase(raw) {
|
|
910
|
+
return words(raw).join("_").toUpperCase();
|
|
911
|
+
}
|
|
912
|
+
function assertValidName(raw) {
|
|
913
|
+
const ws = words(raw);
|
|
914
|
+
if (ws.length === 0) {
|
|
915
|
+
throw new Error(`Invalid name: '${raw}' \u2014 give at least one word, e.g. 'work_order'.`);
|
|
916
|
+
}
|
|
917
|
+
for (const w of ws) {
|
|
918
|
+
if (!/^[a-z][a-z0-9]*$/.test(w)) {
|
|
919
|
+
throw new Error(
|
|
920
|
+
`Invalid name token '${w}' in '${raw}' \u2014 words must be alphanumeric and start with a letter (got after normalisation: ${ws.join(", ")}).`
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
return ws;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// src/templates.ts
|
|
928
|
+
function namesFor(raw) {
|
|
929
|
+
return {
|
|
930
|
+
raw,
|
|
931
|
+
pascal: pascalCase(raw),
|
|
932
|
+
camel: camelCase(raw),
|
|
933
|
+
statusesConst: `${screamingSnakeCase(raw)}_STATUSES`
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
var HEADER = (raw, kind) => `/**
|
|
937
|
+
* The \`${raw}\` ${kind} \u2014 scaffolded by \`githolon generate\`.
|
|
938
|
+
*
|
|
939
|
+
* Authoring rules (see @githolon/dsl): write ONLY aggregates (typed fields, each
|
|
940
|
+
* tagged with a merge Driver) and directives (Zod payload + declarative plan).
|
|
941
|
+
* You never write apply/fold/merge \u2014 the kernel owns the merge algebra.
|
|
942
|
+
*
|
|
943
|
+
* Convention: the fail-closed \`Conflict\` driver is the default for scalars.
|
|
944
|
+
* Relax only what differs: \`.merge(Lww)\`, \`.merge(AddWins)\`, \`.merge(MapOf(Lww))\`.
|
|
945
|
+
*/`;
|
|
946
|
+
function aggregateBlock(n) {
|
|
947
|
+
return `export const ${n.statusesConst} = ["active", "retired"] as const;
|
|
948
|
+
|
|
949
|
+
export const ${n.pascal} = aggregate("${n.pascal}", {
|
|
950
|
+
// Default driver is the fail-closed \`Conflict\`: concurrent edits surface
|
|
951
|
+
// as a conflict rather than silently coalescing. Declare only what differs.
|
|
952
|
+
label: t.string().merge(Conflict),
|
|
953
|
+
// Overrides \u2014 last-write-wins / set-union / per-key map:
|
|
954
|
+
pos: t.int().merge(Lww),
|
|
955
|
+
status: t.enum(${n.statusesConst}).merge(Lww),
|
|
956
|
+
tags: t.set(t.string()).merge(AddWins),
|
|
957
|
+
customFields: t.map(t.string()).merge(MapOf(Lww)),
|
|
958
|
+
});`;
|
|
959
|
+
}
|
|
960
|
+
function domainTemplate(raw) {
|
|
961
|
+
const n = namesFor(raw);
|
|
962
|
+
return `${HEADER(raw, "domain")}
|
|
963
|
+
import {
|
|
964
|
+
z,
|
|
965
|
+
aggregate,
|
|
966
|
+
directive,
|
|
967
|
+
t,
|
|
968
|
+
Conflict,
|
|
969
|
+
Lww,
|
|
970
|
+
AddWins,
|
|
971
|
+
MapOf,
|
|
972
|
+
set,
|
|
973
|
+
addToSet,
|
|
974
|
+
setEntry,
|
|
975
|
+
} from "@githolon/dsl";
|
|
976
|
+
|
|
977
|
+
${aggregateBlock(n)}
|
|
978
|
+
|
|
979
|
+
/** create${n.pascal} \u2014 .creates the aggregate; seeds label/pos/status. */
|
|
980
|
+
export const create${n.pascal} = directive("create${n.pascal}")
|
|
981
|
+
.creates(${n.pascal})
|
|
982
|
+
.payload(
|
|
983
|
+
z.object({
|
|
984
|
+
label: z.string(),
|
|
985
|
+
pos: z.number().int(),
|
|
986
|
+
status: z.enum(${n.statusesConst}),
|
|
987
|
+
}),
|
|
988
|
+
)
|
|
989
|
+
.plan((p) => [
|
|
990
|
+
set(${n.pascal}, "label", p.label),
|
|
991
|
+
set(${n.pascal}, "pos", p.pos),
|
|
992
|
+
set(${n.pascal}, "status", p.status),
|
|
993
|
+
]);
|
|
994
|
+
|
|
995
|
+
/** move${n.pascal} \u2014 .mutates pos. */
|
|
996
|
+
export const move${n.pascal} = directive("move${n.pascal}")
|
|
997
|
+
.mutates(${n.pascal})
|
|
998
|
+
.payload(z.object({ pos: z.number().int() }))
|
|
999
|
+
.plan((p) => [set(${n.pascal}, "pos", p.pos)]);
|
|
1000
|
+
|
|
1001
|
+
/** tag${n.pascal} \u2014 .mutates tags (add-wins union). */
|
|
1002
|
+
export const tag${n.pascal} = directive("tag${n.pascal}")
|
|
1003
|
+
.mutates(${n.pascal})
|
|
1004
|
+
.payload(z.object({ tags: z.array(z.string()) }))
|
|
1005
|
+
.plan((p) => [addToSet(${n.pascal}, "tags", p.tags)]);
|
|
1006
|
+
|
|
1007
|
+
/** setCustomFieldOn${n.pascal} \u2014 .mutates customFields (a map entry). */
|
|
1008
|
+
export const setCustomFieldOn${n.pascal} = directive("setCustomFieldOn${n.pascal}")
|
|
1009
|
+
.mutates(${n.pascal})
|
|
1010
|
+
.payload(z.object({ key: z.string(), value: z.string() }))
|
|
1011
|
+
.plan((p) => [setEntry(${n.pascal}, "customFields", p.key, p.value)]);
|
|
1012
|
+
`;
|
|
1013
|
+
}
|
|
1014
|
+
function aggregateTemplate(raw) {
|
|
1015
|
+
const n = namesFor(raw);
|
|
1016
|
+
return `${HEADER(raw, "aggregate")}
|
|
1017
|
+
import { aggregate, t, Conflict, Lww, AddWins, MapOf } from "@githolon/dsl";
|
|
1018
|
+
|
|
1019
|
+
${aggregateBlock(n)}
|
|
1020
|
+
`;
|
|
1021
|
+
}
|
|
1022
|
+
function intentTemplate(raw) {
|
|
1023
|
+
const n = namesFor(raw);
|
|
1024
|
+
return `${HEADER(raw, "intent (directive)")}
|
|
1025
|
+
import { z, aggregate, directive, t, Conflict, set } from "@githolon/dsl";
|
|
1026
|
+
|
|
1027
|
+
// Stub target \u2014 in a real domain, import the existing aggregate handle instead.
|
|
1028
|
+
export const ${n.pascal} = aggregate("${n.pascal}", {
|
|
1029
|
+
label: t.string().merge(Conflict),
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
/** ${n.camel} \u2014 .mutates the target aggregate. */
|
|
1033
|
+
export const ${n.camel} = directive("${n.camel}")
|
|
1034
|
+
.mutates(${n.pascal})
|
|
1035
|
+
.payload(z.object({ label: z.string() }))
|
|
1036
|
+
.plan((p) => [set(${n.pascal}, "label", p.label)]);
|
|
1037
|
+
`;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// src/generate.ts
|
|
1041
|
+
var RENDERERS = {
|
|
1042
|
+
domain: domainTemplate,
|
|
1043
|
+
aggregate: aggregateTemplate,
|
|
1044
|
+
intent: intentTemplate
|
|
1045
|
+
};
|
|
1046
|
+
function renderScaffold(kind, name) {
|
|
1047
|
+
assertValidName(name);
|
|
1048
|
+
const render = RENDERERS[kind];
|
|
1049
|
+
if (!render) {
|
|
1050
|
+
throw new Error(`Unknown generator kind '${kind}'. Use: domain | aggregate | intent.`);
|
|
1051
|
+
}
|
|
1052
|
+
return render(name);
|
|
1053
|
+
}
|
|
1054
|
+
function generate(opts) {
|
|
1055
|
+
const contents = renderScaffold(opts.kind, opts.name);
|
|
1056
|
+
const fileName = `${snakeCase(opts.name)}.ts`;
|
|
1057
|
+
const path = join(opts.outDir, fileName);
|
|
1058
|
+
if (existsSync(path) && !opts.force) {
|
|
1059
|
+
throw new Error(
|
|
1060
|
+
`Refusing to overwrite existing file: ${path}
|
|
1061
|
+
Re-run with --force to replace it.`
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
mkdirSync(opts.outDir, { recursive: true });
|
|
1065
|
+
writeFileSync(path, contents, "utf8");
|
|
1066
|
+
return { path, contents };
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// src/capability.ts
|
|
1070
|
+
init_cloud();
|
|
1071
|
+
import { spawnSync } from "node:child_process";
|
|
1072
|
+
var out2 = (s) => void process.stdout.write(s + "\n");
|
|
1073
|
+
var err2 = (s) => void process.stderr.write("error: " + s + "\n");
|
|
1074
|
+
var CAP_NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
1075
|
+
function readStdin() {
|
|
1076
|
+
if (process.stdin.isTTY === true) {
|
|
1077
|
+
process.stderr.write(
|
|
1078
|
+
"paste the credential value, then Ctrl-D (it never rides argv \u2014 ps/history are hosts too):\n"
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
return new Promise((resolveValue) => {
|
|
1082
|
+
let buf = "";
|
|
1083
|
+
process.stdin.setEncoding("utf8");
|
|
1084
|
+
process.stdin.on("data", (chunk) => {
|
|
1085
|
+
buf += chunk;
|
|
1086
|
+
});
|
|
1087
|
+
process.stdin.on("end", () => resolveValue(buf.trim()));
|
|
1088
|
+
process.stdin.resume();
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
function readGcloud(name) {
|
|
1092
|
+
const r = spawnSync("gcloud", ["secrets", "versions", "access", "latest", `--secret=${name}`], {
|
|
1093
|
+
encoding: "utf8",
|
|
1094
|
+
maxBuffer: 1024 * 1024
|
|
1095
|
+
});
|
|
1096
|
+
if (r.error !== void 0 || r.status !== 0) {
|
|
1097
|
+
throw new Error(
|
|
1098
|
+
`gcloud secret read failed (${r.error?.message ?? r.stderr?.trim() ?? `exit ${r.status}`}) \u2014 is gcloud installed + authed, and does secret '${name}' exist?`
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
const v = r.stdout.trim();
|
|
1102
|
+
if (v === "") throw new Error(`gcloud secret '${name}' is empty`);
|
|
1103
|
+
return v;
|
|
1104
|
+
}
|
|
1105
|
+
function parseExpiresAt(s) {
|
|
1106
|
+
const n = /^\d+$/.test(s) ? Number(s) : Date.parse(s);
|
|
1107
|
+
if (!Number.isInteger(n) || n <= 0) throw new Error(`--expires-at '${s}' is neither epoch-ms nor an ISO date`);
|
|
1108
|
+
return n;
|
|
1109
|
+
}
|
|
1110
|
+
async function bindLane(lane, ws, capability, opts) {
|
|
1111
|
+
const cloud = cloudBase(opts.cloud);
|
|
1112
|
+
if (!CAP_NAME.test(capability)) {
|
|
1113
|
+
err2(`capability must be a lowercase law name matching [a-z0-9][a-z0-9._-]{0,63} \u2014 e.g. cf-analytics, openai`);
|
|
1114
|
+
return 1;
|
|
1115
|
+
}
|
|
1116
|
+
const secret = getSecret(cloud, ws);
|
|
1117
|
+
if (secret === void 0) {
|
|
1118
|
+
err2(
|
|
1119
|
+
`no stored secret for ${ws} on ${cloud} \u2014 capability ${lane} is owner-gated
|
|
1120
|
+
githolon secret set ${ws} <secret> (import the workspaceSecret you hold)`
|
|
1121
|
+
);
|
|
1122
|
+
return 1;
|
|
1123
|
+
}
|
|
1124
|
+
let value;
|
|
1125
|
+
try {
|
|
1126
|
+
value = opts.fromGcloud !== void 0 ? readGcloud(opts.fromGcloud) : await readStdin();
|
|
1127
|
+
} catch (e) {
|
|
1128
|
+
err2(e.message);
|
|
1129
|
+
return 1;
|
|
1130
|
+
}
|
|
1131
|
+
if (value === "") {
|
|
1132
|
+
err2("no credential value arrived \u2014 pipe it on stdin, or pass --from-gcloud <secret-name>");
|
|
1133
|
+
return 1;
|
|
1134
|
+
}
|
|
1135
|
+
let expiresAt;
|
|
1136
|
+
try {
|
|
1137
|
+
expiresAt = opts.expiresAt !== void 0 ? parseExpiresAt(opts.expiresAt) : void 0;
|
|
1138
|
+
} catch (e) {
|
|
1139
|
+
err2(e.message);
|
|
1140
|
+
return 1;
|
|
1141
|
+
}
|
|
1142
|
+
const r = await fetch(`${cloud}/v1/workspaces/${ws}/capabilities/${lane}`, {
|
|
1143
|
+
method: "POST",
|
|
1144
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${secret}` },
|
|
1145
|
+
body: JSON.stringify({
|
|
1146
|
+
capability,
|
|
1147
|
+
secret: value,
|
|
1148
|
+
...opts.scope !== void 0 ? { scope: opts.scope } : {},
|
|
1149
|
+
...expiresAt !== void 0 ? { expiresAt } : {}
|
|
1150
|
+
})
|
|
1151
|
+
});
|
|
1152
|
+
const d = await r.json().catch(() => void 0);
|
|
1153
|
+
if (r.status === 401) {
|
|
1154
|
+
err2(`${lane} refused (401) \u2014 the stored secret for ${ws} is not the workspace's. githolon secret set ${ws} <secret>`);
|
|
1155
|
+
return 1;
|
|
1156
|
+
}
|
|
1157
|
+
if (!r.ok || d?.ok !== true) {
|
|
1158
|
+
err2(`capability ${lane} '${capability}' on ${ws} failed (${r.status}): ${d ? JSON.stringify(d) : "no body"}`);
|
|
1159
|
+
return 1;
|
|
867
1160
|
}
|
|
1161
|
+
out2(`\u2713 ${capability} ${lane === "rotate" ? "rotated" : "bound"} on ${ws} (epoch ${d.epoch})`);
|
|
1162
|
+
out2(` the VALUE lives host-side only (the workspace's holon) \u2014 it never enters the ledger`);
|
|
1163
|
+
out2(` the law fact: keyHash ${(d.keyHash ?? "").slice(0, 16)}\u2026 status ${d.status}`);
|
|
1164
|
+
return 0;
|
|
868
1165
|
}
|
|
869
|
-
function
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1166
|
+
async function capabilityBind(ws, capability, opts) {
|
|
1167
|
+
return bindLane("bind", ws, capability, opts);
|
|
1168
|
+
}
|
|
1169
|
+
async function capabilityRotate(ws, capability, opts) {
|
|
1170
|
+
return bindLane("rotate", ws, capability, opts);
|
|
1171
|
+
}
|
|
1172
|
+
async function capabilityList(ws, opts) {
|
|
1173
|
+
const cloud = cloudBase(opts.cloud);
|
|
1174
|
+
const secret = getSecret(cloud, ws);
|
|
1175
|
+
if (secret === void 0) {
|
|
1176
|
+
err2(`no stored secret for ${ws} on ${cloud} \u2014 the binding facts are owner-gated
|
|
1177
|
+
githolon secret set ${ws} <secret>`);
|
|
1178
|
+
return 1;
|
|
875
1179
|
}
|
|
876
|
-
|
|
1180
|
+
const r = await fetch(`${cloud}/v1/workspaces/${ws}/capabilities`, {
|
|
1181
|
+
headers: { authorization: `Bearer ${secret}` }
|
|
1182
|
+
});
|
|
1183
|
+
const d = await r.json().catch(() => void 0);
|
|
1184
|
+
if (!r.ok || d?.ok !== true) {
|
|
1185
|
+
err2(`capability list '${ws}' failed (${r.status}): ${d ? JSON.stringify(d) : "no body"}`);
|
|
1186
|
+
return 1;
|
|
1187
|
+
}
|
|
1188
|
+
const bindings = d.bindings ?? [];
|
|
1189
|
+
if (bindings.length === 0) {
|
|
1190
|
+
out2(`no capability bindings on ${ws} \u2014 bind one: githolon capability bind ${ws} <capability>`);
|
|
1191
|
+
return 0;
|
|
1192
|
+
}
|
|
1193
|
+
for (const b of bindings) {
|
|
1194
|
+
const bits = [
|
|
1195
|
+
`epoch ${b.epoch}`,
|
|
1196
|
+
`${b.status}`,
|
|
1197
|
+
`keyHash ${(b.keyHash ?? "").slice(0, 16)}\u2026`,
|
|
1198
|
+
...b.scope !== void 0 ? [`scope ${b.scope}`] : [],
|
|
1199
|
+
...b.expiresAt !== void 0 ? [`expires ${new Date(b.expiresAt).toISOString()}`] : [],
|
|
1200
|
+
...b.held !== void 0 ? [b.held ? "value held" : "VALUE MISSING (rotate to restore)"] : []
|
|
1201
|
+
];
|
|
1202
|
+
out2(`${b.capability} ${bits.join(" \xB7 ")}`);
|
|
1203
|
+
}
|
|
1204
|
+
out2(`(facts about authority \u2014 the values live host-side and are never listed)`);
|
|
1205
|
+
return 0;
|
|
877
1206
|
}
|
|
878
|
-
async function
|
|
879
|
-
const
|
|
880
|
-
const
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1207
|
+
async function capabilityUnbind(ws, capability, opts) {
|
|
1208
|
+
const cloud = cloudBase(opts.cloud);
|
|
1209
|
+
const secret = getSecret(cloud, ws);
|
|
1210
|
+
if (secret === void 0) {
|
|
1211
|
+
err2(`no stored secret for ${ws} on ${cloud} \u2014 unbind is owner-gated
|
|
1212
|
+
githolon secret set ${ws} <secret>`);
|
|
1213
|
+
return 1;
|
|
1214
|
+
}
|
|
1215
|
+
const r = await fetch(`${cloud}/v1/workspaces/${ws}/capabilities/unbind`, {
|
|
1216
|
+
method: "POST",
|
|
1217
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${secret}` },
|
|
1218
|
+
body: JSON.stringify({ capability })
|
|
889
1219
|
});
|
|
890
|
-
|
|
1220
|
+
const d = await r.json().catch(() => void 0);
|
|
1221
|
+
if (!r.ok || d?.ok !== true) {
|
|
1222
|
+
err2(`capability unbind '${capability}' on ${ws} failed (${r.status}): ${d ? JSON.stringify(d) : "no body"}`);
|
|
1223
|
+
return 1;
|
|
1224
|
+
}
|
|
1225
|
+
out2(`\u2713 ${capability} unbound on ${ws} \u2014 the law fact is revoked (the record stays) and the host forgot the value`);
|
|
1226
|
+
return 0;
|
|
891
1227
|
}
|
|
892
|
-
|
|
893
|
-
|
|
1228
|
+
|
|
1229
|
+
// src/cli.ts
|
|
1230
|
+
init_cloud();
|
|
1231
|
+
|
|
1232
|
+
// src/compile.ts
|
|
1233
|
+
import { spawn, spawnSync as spawnSync2 } from "node:child_process";
|
|
1234
|
+
import { createRequire } from "node:module";
|
|
1235
|
+
import { dirname, join as join3 } from "node:path";
|
|
1236
|
+
import { pathToFileURL } from "node:url";
|
|
1237
|
+
function resolveCompileLauncher(cwd) {
|
|
1238
|
+
const resolveDslPkg = (fromDir) => {
|
|
1239
|
+
try {
|
|
1240
|
+
const req = fromDir === void 0 ? createRequire(import.meta.url) : createRequire(pathToFileURL(join3(fromDir, "noop.js")));
|
|
1241
|
+
return req.resolve("@githolon/dsl/package.json");
|
|
1242
|
+
} catch {
|
|
1243
|
+
return void 0;
|
|
1244
|
+
}
|
|
1245
|
+
};
|
|
1246
|
+
const dslPkg = resolveDslPkg(cwd) ?? resolveDslPkg(void 0);
|
|
1247
|
+
return dslPkg === void 0 ? void 0 : join3(dirname(dslPkg), "compile_package.mjs");
|
|
894
1248
|
}
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
const
|
|
898
|
-
if (
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
return void 0;
|
|
1249
|
+
var NO_DSL_REMEDY = "@githolon/dsl not found \u2014 add it to your project's dependencies";
|
|
1250
|
+
function runCompile(args) {
|
|
1251
|
+
const launcher = resolveCompileLauncher(process.cwd());
|
|
1252
|
+
if (launcher === void 0) {
|
|
1253
|
+
process.stderr.write(`error: ${NO_DSL_REMEDY}
|
|
1254
|
+
`);
|
|
1255
|
+
return 1;
|
|
903
1256
|
}
|
|
1257
|
+
const r = spawnSync2(process.execPath, [launcher, ...args], { stdio: "inherit", cwd: process.cwd() });
|
|
1258
|
+
return r.status ?? 1;
|
|
904
1259
|
}
|
|
905
|
-
function
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
return
|
|
1260
|
+
function compileAsync(cwd, args = []) {
|
|
1261
|
+
const launcher = resolveCompileLauncher(cwd);
|
|
1262
|
+
const t0 = performance.now();
|
|
1263
|
+
if (launcher === void 0) {
|
|
1264
|
+
return Promise.resolve({ ok: false, ms: 0, output: NO_DSL_REMEDY });
|
|
910
1265
|
}
|
|
911
|
-
|
|
912
|
-
|
|
1266
|
+
return new Promise((resolveRun) => {
|
|
1267
|
+
const chunks = [];
|
|
1268
|
+
const dec3 = new TextDecoder();
|
|
1269
|
+
const child = spawn(process.execPath, [launcher, ...args], { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
1270
|
+
child.stdout?.on("data", (c) => chunks.push(dec3.decode(c)));
|
|
1271
|
+
child.stderr?.on("data", (c) => chunks.push(dec3.decode(c)));
|
|
1272
|
+
child.on("error", (e) => resolveRun({ ok: false, ms: performance.now() - t0, output: String(e) }));
|
|
1273
|
+
child.on(
|
|
1274
|
+
"close",
|
|
1275
|
+
(code) => resolveRun({ ok: code === 0, ms: performance.now() - t0, output: chunks.join("") })
|
|
1276
|
+
);
|
|
1277
|
+
});
|
|
913
1278
|
}
|
|
914
1279
|
|
|
1280
|
+
// src/dev.ts
|
|
1281
|
+
init_engine();
|
|
1282
|
+
init_cloud();
|
|
1283
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, watch } from "node:fs";
|
|
1284
|
+
import { createServer } from "node:http";
|
|
1285
|
+
import { dirname as dirname4, join as join7, resolve } from "node:path";
|
|
1286
|
+
import { pathToFileURL as pathToFileURL3 } from "node:url";
|
|
1287
|
+
init_local_holon();
|
|
1288
|
+
|
|
915
1289
|
// src/project.ts
|
|
916
1290
|
import { existsSync as existsSync4, statSync as statSync2 } from "node:fs";
|
|
917
1291
|
import { join as join5 } from "node:path";
|
|
@@ -997,89 +1371,10 @@ async function resolveWorkspace(explicit, cwd, target, usageHint) {
|
|
|
997
1371
|
return resolveWorkspaceDecl(project.workspace, target, project.configPath);
|
|
998
1372
|
}
|
|
999
1373
|
|
|
1000
|
-
// src/proof_offline.ts
|
|
1001
|
-
function parseOfflineLegs(proofSource) {
|
|
1002
|
-
if (!proofSource.includes("AUTO-GENERATED by nomos-compile")) {
|
|
1003
|
-
throw new Error("not a generated proof (missing the AUTO-GENERATED header) \u2014 run `githolon compile` to (re)generate build/<name>.proof.mts");
|
|
1004
|
-
}
|
|
1005
|
-
const dispatch = proofSource.match(/^await app\.(\w+)\((\{.*\})\);$/m);
|
|
1006
|
-
if (dispatch === null) {
|
|
1007
|
-
throw new Error("generated proof has no `await app.<directive>({...})` dispatch \u2014 regenerate with `githolon compile` (a creating directive is required)");
|
|
1008
|
-
}
|
|
1009
|
-
const directiveId = dispatch[1];
|
|
1010
|
-
const payload = JSON.parse(dispatch[2]);
|
|
1011
|
-
const dom = proofSource.match(/dispatch (\w+)\/(\w+) — offline write/);
|
|
1012
|
-
if (dom === null || dom[2] !== directiveId) {
|
|
1013
|
-
throw new Error("generated proof names no `dispatch <domain>/<directive> \u2014 offline write` \u2014 regenerate with `githolon compile`");
|
|
1014
|
-
}
|
|
1015
|
-
const domain = dom[1];
|
|
1016
|
-
const autoStamped = [];
|
|
1017
|
-
const stampLine = proofSource.split("\n").find((l) => l.includes("omitted \u2014 the client auto-stamps ISO now()"));
|
|
1018
|
-
if (stampLine !== void 0) {
|
|
1019
|
-
for (const m of stampLine.matchAll(/`(\w+)`/g)) autoStamped.push(m[1]);
|
|
1020
|
-
}
|
|
1021
|
-
let queryLeg;
|
|
1022
|
-
const q = proofSource.match(/local declared query (\w+):/);
|
|
1023
|
-
if (q !== null) {
|
|
1024
|
-
const call2 = proofSource.match(/^const rows = await app\.\w+\((\{.*\})\);$/m);
|
|
1025
|
-
if (call2 === null) throw new Error(`generated proof names query '${q[1]}' but its accessor call is missing \u2014 regenerate with \`githolon compile\``);
|
|
1026
|
-
queryLeg = { id: q[1], params: JSON.parse(call2[1]) };
|
|
1027
|
-
}
|
|
1028
|
-
let countLeg;
|
|
1029
|
-
const c = proofSource.match(/local declared count (\w+):/);
|
|
1030
|
-
if (c !== null) {
|
|
1031
|
-
const call2 = proofSource.match(/^const localCount = await app\.\w+\((.*?)\);$/m);
|
|
1032
|
-
if (call2 === null) throw new Error(`generated proof names count '${c[1]}' but its accessor call is missing \u2014 regenerate with \`githolon compile\``);
|
|
1033
|
-
const arg = call2[1].trim();
|
|
1034
|
-
countLeg = { id: c[1], group: arg.length === 0 ? "" : JSON.parse(arg) };
|
|
1035
|
-
}
|
|
1036
|
-
return {
|
|
1037
|
-
domain,
|
|
1038
|
-
directiveId,
|
|
1039
|
-
payload,
|
|
1040
|
-
autoStamped,
|
|
1041
|
-
...queryLeg !== void 0 ? { query: queryLeg } : {},
|
|
1042
|
-
...countLeg !== void 0 ? { count: countLeg } : {}
|
|
1043
|
-
};
|
|
1044
|
-
}
|
|
1045
|
-
function runOfflineLegs(eng, ws, lawHash, legs) {
|
|
1046
|
-
const t0 = performance.now();
|
|
1047
|
-
const done = (ok, lines2, error) => ({
|
|
1048
|
-
ok,
|
|
1049
|
-
ms: performance.now() - t0,
|
|
1050
|
-
legs: lines2,
|
|
1051
|
-
...error !== void 0 ? { error } : {}
|
|
1052
|
-
});
|
|
1053
|
-
const lines = [];
|
|
1054
|
-
const payload = { ...legs.payload };
|
|
1055
|
-
const stamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1056
|
-
for (const f of legs.autoStamped) payload[f] = stamp;
|
|
1057
|
-
const res = author(eng, ws, legs.domain, legs.directiveId, payload, lawHash);
|
|
1058
|
-
if (res.ok === false) {
|
|
1059
|
-
return done(false, lines, `dispatch ${legs.domain}/${legs.directiveId} refused: ${res.error ?? "unknown"} \u2014 the law itself rejected its own synthesized sample; check the directive's plan/payload schema`);
|
|
1060
|
-
}
|
|
1061
|
-
const createdId = (sealedIntentOf(eng, ws, res)?.events ?? []).find((e) => e.marker === "Create")?.aggregate;
|
|
1062
|
-
lines.push(`\u2713 dispatch ${legs.domain}/${legs.directiveId} \u2014 offline write under the new law${createdId !== void 0 ? ` (${createdId.slice(0, 28)}\u2026)` : ""}`);
|
|
1063
|
-
if (legs.query !== void 0) {
|
|
1064
|
-
const rows = query(eng, ws, legs.query.id, JSON.stringify(legs.query.params));
|
|
1065
|
-
if (rows.length !== 1 || createdId !== void 0 && rows[0].id !== createdId) {
|
|
1066
|
-
return done(false, lines, `declared query ${legs.query.id} expected the 1 created row, got ${JSON.stringify(rows.map((r) => r.id))} \u2014 check the query's key fields against the directive's payload`);
|
|
1067
|
-
}
|
|
1068
|
-
lines.push(`\u2713 declared query ${legs.query.id} answers locally \u2014 1 row`);
|
|
1069
|
-
}
|
|
1070
|
-
if (legs.count !== void 0) {
|
|
1071
|
-
const c = count(eng, ws, legs.count.id, legs.count.group).count;
|
|
1072
|
-
if (c !== 1) {
|
|
1073
|
-
return done(false, lines, `declared count ${legs.count.id}(${JSON.stringify(legs.count.group)}) expected 1, got ${c} \u2014 check the count's grouping against the created row`);
|
|
1074
|
-
}
|
|
1075
|
-
lines.push(`\u2713 declared count ${legs.count.id}(${JSON.stringify(legs.count.group)}) = 1 locally`);
|
|
1076
|
-
}
|
|
1077
|
-
return done(true, lines);
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
1374
|
// src/dev.ts
|
|
1081
|
-
|
|
1082
|
-
var
|
|
1375
|
+
init_proof_offline();
|
|
1376
|
+
var out4 = (s) => void process.stdout.write(s + "\n");
|
|
1377
|
+
var err4 = (s) => void process.stderr.write("error: " + s + "\n");
|
|
1083
1378
|
var WS = "dev";
|
|
1084
1379
|
var DEFAULT_PORT = 7700;
|
|
1085
1380
|
var DEBOUNCE_MS = 200;
|
|
@@ -1163,11 +1458,11 @@ function startServer(s) {
|
|
|
1163
1458
|
});
|
|
1164
1459
|
}
|
|
1165
1460
|
function readDeployBody(cwd, packageName) {
|
|
1166
|
-
const file =
|
|
1167
|
-
if (!
|
|
1461
|
+
const file = join7(cwd, "build", `${packageName}.deploy.json`);
|
|
1462
|
+
if (!existsSync6(file)) {
|
|
1168
1463
|
throw new Error(`compile produced no ${file} \u2014 check the compile output above`);
|
|
1169
1464
|
}
|
|
1170
|
-
return JSON.parse(
|
|
1465
|
+
return JSON.parse(readFileSync4(file, "utf8"));
|
|
1171
1466
|
}
|
|
1172
1467
|
async function installLaw(s, deployBody, installedBy) {
|
|
1173
1468
|
const newHash = await sha256hex(deployBody.packageUsda);
|
|
@@ -1186,11 +1481,11 @@ async function installLaw(s, deployBody, installedBy) {
|
|
|
1186
1481
|
return { ok: true };
|
|
1187
1482
|
}
|
|
1188
1483
|
async function runProofCycle(s, cwd, deployBody, installedBy) {
|
|
1189
|
-
const proofPath =
|
|
1190
|
-
if (!
|
|
1484
|
+
const proofPath = join7(cwd, "build", `${s.packageName}.proof.mts`);
|
|
1485
|
+
if (!existsSync6(proofPath)) return void 0;
|
|
1191
1486
|
const scratch = `proof-${s.cycle}`;
|
|
1192
1487
|
try {
|
|
1193
|
-
const legs = parseOfflineLegs(
|
|
1488
|
+
const legs = parseOfflineLegs(readFileSync4(proofPath, "utf8"));
|
|
1194
1489
|
await mountFresh(s.eng, scratch);
|
|
1195
1490
|
author(s.eng, scratch, "bootstrap", "installDomain", installPayload(s.eng.hashes.nomos, s.eng.nomosPkg, installedBy), "");
|
|
1196
1491
|
author(s.eng, scratch, "nomos", "installDomain", installPayload(s.lawHash, deployBody.packageUsda, installedBy), s.eng.hashes.nomos);
|
|
@@ -1205,21 +1500,21 @@ async function dev(opts) {
|
|
|
1205
1500
|
const cwd = process.cwd();
|
|
1206
1501
|
const cloud = cloudBase(opts.cloud);
|
|
1207
1502
|
const project = await loadProject(cwd).catch((e) => {
|
|
1208
|
-
|
|
1503
|
+
err4(e.message);
|
|
1209
1504
|
return void 0;
|
|
1210
1505
|
});
|
|
1211
1506
|
if (project === void 0) {
|
|
1212
|
-
if (!
|
|
1213
|
-
|
|
1507
|
+
if (!existsSync6(join7(cwd, CONFIG_FILE))) {
|
|
1508
|
+
err4(`no ${CONFIG_FILE} here (${cwd}) \u2014 \`githolon dev\` runs inside a project; scaffold one from examples/guestbook or \`githolon generate domain <name>\``);
|
|
1214
1509
|
}
|
|
1215
1510
|
return 1;
|
|
1216
1511
|
}
|
|
1217
1512
|
const creds = loadCreds();
|
|
1218
1513
|
const installedBy = creds.session?.uid ?? creds.principal ?? "githolon-dev";
|
|
1219
|
-
|
|
1514
|
+
out4(`githolon dev \u2014 ${project.name}: compiling\u2026`);
|
|
1220
1515
|
const firstCompile = await compileAsync(cwd);
|
|
1221
1516
|
if (!firstCompile.ok) {
|
|
1222
|
-
|
|
1517
|
+
err4(`compile failed \u2014 the dev loop needs one green compile to mint the holon:
|
|
1223
1518
|
${firstCompile.output}`);
|
|
1224
1519
|
return 1;
|
|
1225
1520
|
}
|
|
@@ -1227,14 +1522,14 @@ ${firstCompile.output}`);
|
|
|
1227
1522
|
try {
|
|
1228
1523
|
deployBody = readDeployBody(cwd, project.name);
|
|
1229
1524
|
} catch (e) {
|
|
1230
|
-
|
|
1525
|
+
err4(e.message);
|
|
1231
1526
|
return 1;
|
|
1232
1527
|
}
|
|
1233
1528
|
let engBoot;
|
|
1234
1529
|
try {
|
|
1235
1530
|
engBoot = await holonEngine(cloud, deployBody, installedBy);
|
|
1236
1531
|
} catch (e) {
|
|
1237
|
-
|
|
1532
|
+
err4(`cannot boot the local holon: ${String(e.message ?? e)}
|
|
1238
1533
|
the runtime rides ${cloud}/v1/runtime/* and is cached in ~/.holon/runtime after one fetch \u2014 connect once, then dev runs offline`);
|
|
1239
1534
|
return 1;
|
|
1240
1535
|
}
|
|
@@ -1257,31 +1552,31 @@ ${firstCompile.output}`);
|
|
|
1257
1552
|
s.intents++;
|
|
1258
1553
|
const installed = await installLaw(s, deployBody, installedBy);
|
|
1259
1554
|
if (!installed.ok) {
|
|
1260
|
-
|
|
1555
|
+
err4(installed.error ?? "law install failed");
|
|
1261
1556
|
return 1;
|
|
1262
1557
|
}
|
|
1263
1558
|
s.proof = await runProofCycle(s, cwd, deployBody, installedBy);
|
|
1264
1559
|
if (opts.once === true) {
|
|
1265
|
-
|
|
1560
|
+
out4(statusScreen({ ...s, watchRoots: ["(--once: no watch)"] }));
|
|
1266
1561
|
return s.proof === void 0 || s.proof.ok ? 0 : 1;
|
|
1267
1562
|
}
|
|
1268
1563
|
let server;
|
|
1269
1564
|
try {
|
|
1270
1565
|
server = await startServer(s);
|
|
1271
1566
|
} catch (e) {
|
|
1272
|
-
|
|
1567
|
+
err4(e.message);
|
|
1273
1568
|
return 1;
|
|
1274
1569
|
}
|
|
1275
1570
|
const moduleDirs = /* @__PURE__ */ new Set();
|
|
1276
1571
|
try {
|
|
1277
1572
|
const cfgModule = await import(pathToFileURL3(project.configPath).href);
|
|
1278
1573
|
for (const d of cfgModule.default?.domains ?? []) {
|
|
1279
|
-
for (const m of d.modules ?? []) moduleDirs.add(
|
|
1574
|
+
for (const m of d.modules ?? []) moduleDirs.add(dirname4(resolve(cwd, m)));
|
|
1280
1575
|
}
|
|
1281
1576
|
} catch {
|
|
1282
|
-
moduleDirs.add(
|
|
1577
|
+
moduleDirs.add(join7(cwd, "domains"));
|
|
1283
1578
|
}
|
|
1284
|
-
if (moduleDirs.size === 0) moduleDirs.add(
|
|
1579
|
+
if (moduleDirs.size === 0) moduleDirs.add(join7(cwd, "domains"));
|
|
1285
1580
|
let timer;
|
|
1286
1581
|
let cycling = false;
|
|
1287
1582
|
let queued = false;
|
|
@@ -1297,7 +1592,7 @@ ${firstCompile.output}`);
|
|
|
1297
1592
|
s.compileMs = run.ms;
|
|
1298
1593
|
if (!run.ok) {
|
|
1299
1594
|
s.lastError = run.output.trim() || "compile failed";
|
|
1300
|
-
|
|
1595
|
+
out4(`\u2717 cycle ${s.cycle}: compile failed \u2014 the previous law keeps serving
|
|
1301
1596
|
${run.output}`);
|
|
1302
1597
|
} else {
|
|
1303
1598
|
s.lastError = void 0;
|
|
@@ -1306,16 +1601,16 @@ ${run.output}`);
|
|
|
1306
1601
|
const inst = await installLaw(s, body, installedBy);
|
|
1307
1602
|
if (!inst.ok) {
|
|
1308
1603
|
s.lastError = inst.error ?? "install failed";
|
|
1309
|
-
|
|
1604
|
+
out4(`\u2717 cycle ${s.cycle}: ${s.lastError}`);
|
|
1310
1605
|
} else {
|
|
1311
1606
|
s.proof = await runProofCycle(s, cwd, body, installedBy);
|
|
1312
1607
|
const total = performance.now() - t0;
|
|
1313
|
-
|
|
1314
|
-
|
|
1608
|
+
out4(statusScreen(s));
|
|
1609
|
+
out4(` cycle edit \u2192 law-live \u2192 proof in ${total.toFixed(0)} ms total`);
|
|
1315
1610
|
}
|
|
1316
1611
|
} catch (e) {
|
|
1317
1612
|
s.lastError = String(e.message ?? e);
|
|
1318
|
-
|
|
1613
|
+
out4(`\u2717 cycle ${s.cycle}: ${s.lastError}`);
|
|
1319
1614
|
}
|
|
1320
1615
|
}
|
|
1321
1616
|
cycling = false;
|
|
@@ -1331,7 +1626,7 @@ ${run.output}`);
|
|
|
1331
1626
|
};
|
|
1332
1627
|
const watchers = [];
|
|
1333
1628
|
for (const d of moduleDirs) {
|
|
1334
|
-
if (!
|
|
1629
|
+
if (!existsSync6(d)) continue;
|
|
1335
1630
|
watchers.push(watch(d, { recursive: true }, (_evt, f) => onChange(f)));
|
|
1336
1631
|
s.watchRoots.push(d.startsWith(cwd) ? d.slice(cwd.length + 1) + "/" : d);
|
|
1337
1632
|
}
|
|
@@ -1341,8 +1636,8 @@ ${run.output}`);
|
|
|
1341
1636
|
})
|
|
1342
1637
|
);
|
|
1343
1638
|
s.watchRoots.push(CONFIG_FILE);
|
|
1344
|
-
|
|
1345
|
-
|
|
1639
|
+
out4(statusScreen(s));
|
|
1640
|
+
out4(" (ctrl-c stops; edits in the watched paths recompile + re-prove automatically)");
|
|
1346
1641
|
return new Promise((resolveExit) => {
|
|
1347
1642
|
const stop = () => {
|
|
1348
1643
|
for (const w of watchers) w.close();
|
|
@@ -1355,13 +1650,14 @@ ${run.output}`);
|
|
|
1355
1650
|
}
|
|
1356
1651
|
|
|
1357
1652
|
// src/git.ts
|
|
1358
|
-
|
|
1653
|
+
init_cloud();
|
|
1654
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
1359
1655
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
1360
|
-
import { readFileSync as
|
|
1361
|
-
var
|
|
1362
|
-
var
|
|
1656
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
1657
|
+
var out5 = (s) => void process.stdout.write(s + "\n");
|
|
1658
|
+
var err5 = (s) => void process.stderr.write("error: " + s + "\n");
|
|
1363
1659
|
function git2(args, opts = {}) {
|
|
1364
|
-
const r =
|
|
1660
|
+
const r = spawnSync3("git", args, { stdio: opts.quiet === true ? "ignore" : "inherit" });
|
|
1365
1661
|
return r.status ?? 1;
|
|
1366
1662
|
}
|
|
1367
1663
|
function ensureSecret(cloud, ws) {
|
|
@@ -1386,36 +1682,36 @@ function workspaceFromPath(path) {
|
|
|
1386
1682
|
}
|
|
1387
1683
|
async function gitCredential(action) {
|
|
1388
1684
|
if (action !== "get") return 0;
|
|
1389
|
-
const kv = parseCredentialInput(
|
|
1685
|
+
const kv = parseCredentialInput(readFileSync5(0, "utf8"));
|
|
1390
1686
|
const ws = workspaceFromPath(kv["path"]);
|
|
1391
1687
|
if (kv["protocol"] !== "https" || ws === void 0) return 0;
|
|
1392
1688
|
const cloud = `https://${kv["host"]}`;
|
|
1393
1689
|
const token = await sessionToken().catch(() => void 0);
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1690
|
+
out5(`username=${token ?? "nomos"}`);
|
|
1691
|
+
out5(`password=${ensureSecret(cloud, ws)}`);
|
|
1692
|
+
out5("");
|
|
1397
1693
|
return 0;
|
|
1398
1694
|
}
|
|
1399
1695
|
async function gitSetup(opts) {
|
|
1400
1696
|
const cloud = cloudBase(opts.cloud);
|
|
1401
1697
|
const self = process.argv[1];
|
|
1402
1698
|
if (self === void 0) {
|
|
1403
|
-
|
|
1699
|
+
err5("cannot locate the githolon CLI entry to install as a credential helper");
|
|
1404
1700
|
return 1;
|
|
1405
1701
|
}
|
|
1406
1702
|
const helper = `!"${process.execPath}" "${self}" git-credential`;
|
|
1407
1703
|
if (git2(["config", "--global", `credential.${cloud}.helper`, helper]) !== 0 || git2(["config", "--global", `credential.${cloud}.useHttpPath`, "true"]) !== 0) {
|
|
1408
|
-
|
|
1704
|
+
err5("git config failed \u2014 is git installed?");
|
|
1409
1705
|
return 1;
|
|
1410
1706
|
}
|
|
1411
|
-
|
|
1412
|
-
|
|
1707
|
+
out5(`\u2713 git credential helper installed for ${cloud} (global gitconfig)`);
|
|
1708
|
+
out5(` pushes/clones of ${cloud}/v1/workspaces/<ws>/git now authenticate from ~/.holon`);
|
|
1413
1709
|
return 0;
|
|
1414
1710
|
}
|
|
1415
1711
|
async function gitRemote(ws, name, opts) {
|
|
1416
1712
|
const cloud = cloudBase(opts.cloud);
|
|
1417
1713
|
if (git2(["rev-parse", "--git-dir"], { quiet: true }) !== 0) {
|
|
1418
|
-
|
|
1714
|
+
err5("not a git repository \u2014 run inside your repo (git init first)");
|
|
1419
1715
|
return 1;
|
|
1420
1716
|
}
|
|
1421
1717
|
const setupRc = await gitSetup(opts);
|
|
@@ -1424,7 +1720,7 @@ async function gitRemote(ws, name, opts) {
|
|
|
1424
1720
|
const url = `${cloud}/v1/workspaces/${ws}/git`;
|
|
1425
1721
|
if (git2(["remote", "add", name, url], { quiet: true }) !== 0) {
|
|
1426
1722
|
if (git2(["remote", "set-url", name, url], { quiet: true }) !== 0) {
|
|
1427
|
-
|
|
1723
|
+
err5(`could not add or update remote '${name}'`);
|
|
1428
1724
|
return 1;
|
|
1429
1725
|
}
|
|
1430
1726
|
}
|
|
@@ -1432,7 +1728,7 @@ async function gitRemote(ws, name, opts) {
|
|
|
1432
1728
|
if (token === void 0) {
|
|
1433
1729
|
const principal = loadCreds().principal;
|
|
1434
1730
|
if (principal === void 0) {
|
|
1435
|
-
|
|
1731
|
+
err5(
|
|
1436
1732
|
`remote '${name}' \u2192 ${url} is wired, but a BIRTH push needs a principal:
|
|
1437
1733
|
githolon login --agent (then just push)
|
|
1438
1734
|
\u2014 or re-run after githolon ws create/login so a principal is on file`
|
|
@@ -1440,13 +1736,13 @@ async function gitRemote(ws, name, opts) {
|
|
|
1440
1736
|
return 1;
|
|
1441
1737
|
}
|
|
1442
1738
|
if (git2(["config", `http.${url}.extraHeader`, `x-nomos-principal: ${principal}`]) !== 0) {
|
|
1443
|
-
|
|
1739
|
+
err5("git config failed setting the principal header");
|
|
1444
1740
|
return 1;
|
|
1445
1741
|
}
|
|
1446
1742
|
}
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1743
|
+
out5(`\u2713 remote '${name}' \u2192 ${url}`);
|
|
1744
|
+
out5(` workspace secret on file; ${token !== void 0 ? "births ride your session (verified)" : "births ride x-nomos-principal (bare uid)"}`);
|
|
1745
|
+
out5(` birth/deploy by push: git push ${name} main`);
|
|
1450
1746
|
return 0;
|
|
1451
1747
|
}
|
|
1452
1748
|
|
|
@@ -1462,9 +1758,9 @@ var HELP = {
|
|
|
1462
1758
|
examples: ["githolon compile", "githolon compile nomos.package.mjs --layered"]
|
|
1463
1759
|
},
|
|
1464
1760
|
proof: {
|
|
1465
|
-
usage: "githolon proof [build/<name>.proof.mts]",
|
|
1466
|
-
what: "Run the proof GENERATED from your law, live
|
|
1467
|
-
examples: ["githolon proof", "githolon proof
|
|
1761
|
+
usage: "githolon proof [build/<name>.proof.mts] [--domain <key>] [--live] [--keep]",
|
|
1762
|
+
what: "Run the proof GENERATED from your law. DEFAULT: the OFFLINE legs on a LOCAL holon \u2014 the\nsame engine plane the cloud edge runs; no cloud workspace is touched (develop/play/understand\nhere, deploy with confidence after). --domain <key> proves a SPECIFIC domain of a multi-domain\npackage (offline; the default is the first). --live runs the full cloud loop (throwaway\nworkspace \u2192 deploy \u2192 offline write \u2192 admission \u2192 cloud reads \u2192 convergence), RETIRED on every\nexit; --keep keeps it and prints the secret once. ALL GREEN or it names the jam. The offline\nlegs also rerun on every save in `githolon dev`.",
|
|
1763
|
+
examples: ["githolon proof", "githolon proof --domain co2_platform", "githolon proof --live", "githolon proof --live --keep"]
|
|
1468
1764
|
},
|
|
1469
1765
|
dev: {
|
|
1470
1766
|
usage: "githolon dev [--port <n>] [--cloud <url>] [--once]",
|
|
@@ -1550,6 +1846,22 @@ var HELP = {
|
|
|
1550
1846
|
what: "The workspace-secret store (~/.holon/credentials.json, 0600). `set` imports one you already\nhold; `rotate` mints a fresh one (and claims a legacy workspace).",
|
|
1551
1847
|
examples: ["githolon secret set my-ws nws_v1_\u2026", "githolon secret rotate my-ws"]
|
|
1552
1848
|
},
|
|
1849
|
+
capability: {
|
|
1850
|
+
usage: "githolon capability <bind|rotate|unbind> <ws> <capability> | list <ws> [--from-gcloud <name>] [--scope <s>] [--expires-at <iso|ms>] [--cloud <url>]",
|
|
1851
|
+
what: "Capability credentials (architecture/capability_credentials.md): the ledger holds FACTS ABOUT\nAUTHORITY (the CapabilityBinding record \u2014 keyHash/scope/expiry/epoch), the credential VALUE\nlives at exactly ONE host (the workspace's holon). `bind` ships the value over the owner-gated\nlane ONCE \u2014 from stdin (pipe it) or Google Secret Manager, NEVER argv (ps/history are hosts\ntoo). `rotate` = revoke + re-bind at epoch+1; `unbind` revokes the fact and the host forgets\nthe value; `list` shows facts only.",
|
|
1852
|
+
flags: [
|
|
1853
|
+
["--from-gcloud <name>", "read the value from `gcloud secrets versions access latest --secret=<name>`"],
|
|
1854
|
+
["--scope <s>", "a narrowing label recorded on the law fact (e.g. read-only)"],
|
|
1855
|
+
["--expires-at <t>", "expiry recorded on the law fact (ISO date/datetime or epoch-ms; fail-closed at the host)"],
|
|
1856
|
+
["--cloud <url>", "target cloud (default: $NOMOS_CLOUD or the public cloud)"]
|
|
1857
|
+
],
|
|
1858
|
+
examples: [
|
|
1859
|
+
"cat token.txt | githolon capability bind nomos-usage cf-analytics",
|
|
1860
|
+
"githolon capability bind my-ws openai --from-gcloud openai-api-key --scope read-only",
|
|
1861
|
+
"githolon capability list nomos-usage",
|
|
1862
|
+
"githolon capability rotate nomos-usage cf-analytics --from-gcloud cf-analytics-token"
|
|
1863
|
+
]
|
|
1864
|
+
},
|
|
1553
1865
|
git: {
|
|
1554
1866
|
usage: "githolon git <setup | remote [<ws>] [<remoteName>]> [--cloud <url>] [--target <name>]",
|
|
1555
1867
|
what: "Stock git push as deploy/birth. `setup` installs the credential helper for the cloud host;\n`remote` wires this repo to a workspace (then: git push nomos main). With no <ws>, the\nproject's `workspace` binding resolves it.",
|
|
@@ -1578,34 +1890,37 @@ function commandHelp(cmd) {
|
|
|
1578
1890
|
}
|
|
1579
1891
|
|
|
1580
1892
|
// src/ledger.ts
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1893
|
+
init_engine();
|
|
1894
|
+
init_cloud();
|
|
1895
|
+
init_local_holon();
|
|
1896
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
1897
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1898
|
+
import { join as join8 } from "node:path";
|
|
1899
|
+
var out6 = (s) => void process.stdout.write(s + "\n");
|
|
1900
|
+
var err6 = (s) => void process.stderr.write("error: " + s + "\n");
|
|
1586
1901
|
var WS2 = "local";
|
|
1587
1902
|
async function ledgerInit(dir, opts) {
|
|
1588
1903
|
const cloud = cloudBase(opts.cloud);
|
|
1589
|
-
if (
|
|
1590
|
-
|
|
1904
|
+
if (existsSync7(dir) && readdirSync3(dir).length > 0) {
|
|
1905
|
+
err6(`refusing to mint into non-empty directory: ${dir}`);
|
|
1591
1906
|
return 1;
|
|
1592
1907
|
}
|
|
1593
1908
|
let deployFile;
|
|
1594
1909
|
try {
|
|
1595
1910
|
deployFile = discoverDeployJson(process.cwd(), opts.file);
|
|
1596
1911
|
} catch (e) {
|
|
1597
|
-
|
|
1912
|
+
err6(e.message);
|
|
1598
1913
|
return 1;
|
|
1599
1914
|
}
|
|
1600
|
-
const deployBody = JSON.parse(
|
|
1915
|
+
const deployBody = JSON.parse(readFileSync6(deployFile, "utf8"));
|
|
1601
1916
|
const domainHash = await sha256hex(deployBody.packageUsda);
|
|
1602
1917
|
const c = loadCreds();
|
|
1603
1918
|
const principal = await sessionToken().catch(() => void 0) !== void 0 ? c.session.uid : c.principal;
|
|
1604
1919
|
if (principal === void 0) {
|
|
1605
|
-
|
|
1920
|
+
err6("a holon is born OF someone \u2014 githolon login --agent first (or githolon ws create --principal <uid> once)");
|
|
1606
1921
|
return 1;
|
|
1607
1922
|
}
|
|
1608
|
-
|
|
1923
|
+
out6(`minting a holon locally (law ${domainHash.slice(0, 12)}\u2026, installedBy ${principal})`);
|
|
1609
1924
|
const { eng } = await holonEngine(cloud, deployBody, principal);
|
|
1610
1925
|
await mountFresh(eng, WS2);
|
|
1611
1926
|
author(eng, WS2, "bootstrap", "installDomain", installPayload(eng.hashes.nomos, eng.nomosPkg, principal), "");
|
|
@@ -1613,21 +1928,21 @@ async function ledgerInit(dir, opts) {
|
|
|
1613
1928
|
const verdict = verifyChainLocal(eng, WS2);
|
|
1614
1929
|
if (!printVerdict(verdict)) return 1;
|
|
1615
1930
|
const gitTree = eng.preopen.dir.contents.get("ws").contents.get(WS2).contents.get("nomos.git");
|
|
1616
|
-
const gitDir =
|
|
1931
|
+
const gitDir = join8(dir, ".git");
|
|
1617
1932
|
writeTreeToDisk(gitTree, gitDir);
|
|
1618
|
-
const cfgPath =
|
|
1619
|
-
writeFileSync4(cfgPath,
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1933
|
+
const cfgPath = join8(gitDir, "config");
|
|
1934
|
+
writeFileSync4(cfgPath, readFileSync6(cfgPath, "utf8").replace(/bare = true/, "bare = false"), "utf8");
|
|
1935
|
+
spawnSync4("git", ["-C", dir, "reset", "--hard", "main"], { stdio: "ignore" });
|
|
1936
|
+
out6(`\u2713 holon written \u2192 ${dir} (a normal git repo; git log IS the audit trail)`);
|
|
1937
|
+
out6(`
|
|
1623
1938
|
Birth it on Nomos Cloud (the cloud re-derives this exact verdict):`);
|
|
1624
|
-
|
|
1939
|
+
out6(` cd ${dir} && npx githolon git remote <ws> && git push nomos main`);
|
|
1625
1940
|
return 0;
|
|
1626
1941
|
}
|
|
1627
1942
|
async function ledgerVerify(dir, opts) {
|
|
1628
|
-
const gitDir =
|
|
1629
|
-
if (!
|
|
1630
|
-
|
|
1943
|
+
const gitDir = existsSync7(join8(dir, ".git")) ? join8(dir, ".git") : dir;
|
|
1944
|
+
if (!existsSync7(join8(gitDir, "HEAD"))) {
|
|
1945
|
+
err6(`${dir} is not a git repo (no HEAD)`);
|
|
1631
1946
|
return 1;
|
|
1632
1947
|
}
|
|
1633
1948
|
const { eng } = await holonEngine(cloudBase(opts.cloud), void 0, "verify");
|
|
@@ -1638,11 +1953,15 @@ async function ledgerVerify(dir, opts) {
|
|
|
1638
1953
|
}
|
|
1639
1954
|
|
|
1640
1955
|
// src/replay.ts
|
|
1641
|
-
|
|
1642
|
-
|
|
1956
|
+
init_engine();
|
|
1957
|
+
init_engine();
|
|
1958
|
+
init_cloud();
|
|
1959
|
+
init_local_holon();
|
|
1960
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
|
|
1961
|
+
import { join as join9 } from "node:path";
|
|
1643
1962
|
import git3 from "isomorphic-git";
|
|
1644
|
-
var
|
|
1645
|
-
var
|
|
1963
|
+
var out7 = (s) => void process.stdout.write(s + "\n");
|
|
1964
|
+
var err7 = (s) => void process.stderr.write("error: " + s + "\n");
|
|
1646
1965
|
var SOURCE = "source";
|
|
1647
1966
|
var REPLAY = "replay";
|
|
1648
1967
|
function summarizePayload(payload, max = 100) {
|
|
@@ -1678,9 +1997,9 @@ function capJsonStrings(v, max = 2048) {
|
|
|
1678
1997
|
if (typeof v === "string") return v.length > max ? `${v.slice(0, max)}\u2026(${v.length} chars)` : v;
|
|
1679
1998
|
if (Array.isArray(v)) return v.map((x) => capJsonStrings(x, max));
|
|
1680
1999
|
if (typeof v === "object" && v !== null) {
|
|
1681
|
-
const
|
|
1682
|
-
for (const [k, x] of Object.entries(v))
|
|
1683
|
-
return
|
|
2000
|
+
const out9 = {};
|
|
2001
|
+
for (const [k, x] of Object.entries(v)) out9[k] = capJsonStrings(x, max);
|
|
2002
|
+
return out9;
|
|
1684
2003
|
}
|
|
1685
2004
|
return v;
|
|
1686
2005
|
}
|
|
@@ -1727,10 +2046,10 @@ function readKey() {
|
|
|
1727
2046
|
async function replay(target, opts) {
|
|
1728
2047
|
const cloud = cloudBase(opts.cloud);
|
|
1729
2048
|
const json = opts.json === true;
|
|
1730
|
-
const emit = (o) =>
|
|
2049
|
+
const emit = (o) => out7(JSON.stringify(o));
|
|
1731
2050
|
let overlay;
|
|
1732
2051
|
try {
|
|
1733
|
-
overlay = JSON.parse(
|
|
2052
|
+
overlay = JSON.parse(readFileSync7(discoverDeployJson(process.cwd(), opts.file), "utf8"));
|
|
1734
2053
|
} catch {
|
|
1735
2054
|
overlay = void 0;
|
|
1736
2055
|
}
|
|
@@ -1738,15 +2057,15 @@ async function replay(target, opts) {
|
|
|
1738
2057
|
try {
|
|
1739
2058
|
({ eng } = await holonEngine(cloud, overlay, "githolon-replay"));
|
|
1740
2059
|
} catch (e) {
|
|
1741
|
-
|
|
2060
|
+
err7(`cannot boot the local holon: ${String(e.message ?? e)}
|
|
1742
2061
|
the runtime rides ${cloud}/v1/runtime/* (cached in ~/.holon/runtime after one fetch)`);
|
|
1743
2062
|
return 1;
|
|
1744
2063
|
}
|
|
1745
|
-
const isDir =
|
|
2064
|
+
const isDir = existsSync8(target) || target.includes("/") || target.startsWith(".");
|
|
1746
2065
|
if (isDir) {
|
|
1747
|
-
const gitDir =
|
|
1748
|
-
if (!
|
|
1749
|
-
|
|
2066
|
+
const gitDir = existsSync8(join9(target, ".git")) ? join9(target, ".git") : target;
|
|
2067
|
+
if (!existsSync8(join9(gitDir, "HEAD"))) {
|
|
2068
|
+
err7(`${target} is not a git repo (no HEAD) \u2014 pass a holon directory (githolon ledger init <dir>) or a workspace name`);
|
|
1750
2069
|
return 1;
|
|
1751
2070
|
}
|
|
1752
2071
|
await mountFresh(eng, SOURCE);
|
|
@@ -1754,7 +2073,7 @@ async function replay(target, opts) {
|
|
|
1754
2073
|
} else {
|
|
1755
2074
|
const m = await mountWorkspace(eng, SOURCE, { remote: `${cloud}/v1/workspaces/${target}/git`, headers: {} });
|
|
1756
2075
|
if (m.restored !== true) {
|
|
1757
|
-
|
|
2076
|
+
err7(
|
|
1758
2077
|
`workspace '${target}' has no refs/heads/main on ${cloud} \u2014 nothing to replay` + (m.restoreError ? ` (${String(m.restoreError).split(" | ")[0]})` : "") + `
|
|
1759
2078
|
check the name (githolon ws status ${target}), or pass a local holon directory instead`
|
|
1760
2079
|
);
|
|
@@ -1763,7 +2082,7 @@ async function replay(target, opts) {
|
|
|
1763
2082
|
}
|
|
1764
2083
|
const chain = await walkChain(eng, SOURCE);
|
|
1765
2084
|
if (chain.length === 0) {
|
|
1766
|
-
|
|
2085
|
+
err7(`the chain on '${target}' carries no intents \u2014 nothing to replay`);
|
|
1767
2086
|
return 1;
|
|
1768
2087
|
}
|
|
1769
2088
|
await mountFresh(eng, REPLAY);
|
|
@@ -1772,8 +2091,8 @@ async function replay(target, opts) {
|
|
|
1772
2091
|
const tallies = /* @__PURE__ */ new Map();
|
|
1773
2092
|
let autoRun = !interactive;
|
|
1774
2093
|
if (!json) {
|
|
1775
|
-
|
|
1776
|
-
if (interactive)
|
|
2094
|
+
out7(`replaying ${chain.length} intent(s) from ${isDir ? target : `${cloud} :: ${target}`} \u2014 every step re-admitted through the wasm gate`);
|
|
2095
|
+
if (interactive) out7(" keys: enter = next intent \xB7 a = run to the end \xB7 q = quit\n");
|
|
1777
2096
|
}
|
|
1778
2097
|
for (let i = 0; i < chain.length; i++) {
|
|
1779
2098
|
const { oid, bytes, doc } = chain[i];
|
|
@@ -1787,8 +2106,8 @@ async function replay(target, opts) {
|
|
|
1787
2106
|
if (res.ok === false) {
|
|
1788
2107
|
const msg = `replay REFUSED at intent ${i} (${domain}/${directive}, ${oid.slice(0, 10)}): ${res.error ?? "unknown"}`;
|
|
1789
2108
|
if (json) emit({ step: i, oid, domain, directive, refused: true, error: res.error ?? "unknown" });
|
|
1790
|
-
else
|
|
1791
|
-
|
|
2109
|
+
else err7(msg);
|
|
2110
|
+
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)");
|
|
1792
2111
|
printVerdict(verifyChainLocal(eng, SOURCE));
|
|
1793
2112
|
return 1;
|
|
1794
2113
|
}
|
|
@@ -1809,31 +2128,31 @@ async function replay(target, opts) {
|
|
|
1809
2128
|
tallies: Object.fromEntries(tallies)
|
|
1810
2129
|
});
|
|
1811
2130
|
} else {
|
|
1812
|
-
|
|
1813
|
-
|
|
2131
|
+
out7(`[${String(i + 1).padStart(String(chain.length).length)}/${chain.length}] ${domain}/${directive} (${oid.slice(0, 10)}, ${ms.toFixed(0)} ms)`);
|
|
2132
|
+
out7(` payload ${summarizePayload(doc.payload?.payload)}`);
|
|
1814
2133
|
for (const ev of doc.events ?? []) {
|
|
1815
2134
|
const id = ev.aggregate ?? "?";
|
|
1816
2135
|
const was = before.get(id);
|
|
1817
2136
|
const verb = ev.marker === "Create" ? "+ created" : ev.marker === "Delete" ? "- deleted" : was === void 0 ? "~ touched" : "~ updated";
|
|
1818
|
-
|
|
2137
|
+
out7(` ${verb} ${id}`);
|
|
1819
2138
|
const ops = summarizeOps(ev);
|
|
1820
|
-
if (ops.length > 0)
|
|
2139
|
+
if (ops.length > 0) out7(` ${ops}`);
|
|
1821
2140
|
}
|
|
1822
2141
|
const tallyLine = [...tallies.entries()].map(([t, n]) => `${t}:${n}`).join(" ");
|
|
1823
2142
|
const showTally = stepEvery > 0 ? (i + 1) % stepEvery === 0 || i === chain.length - 1 : true;
|
|
1824
|
-
if (showTally && tallyLine.length > 0)
|
|
2143
|
+
if (showTally && tallyLine.length > 0) out7(` rows ${tallyLine}`);
|
|
1825
2144
|
}
|
|
1826
2145
|
if (interactive && !autoRun && i < chain.length - 1) {
|
|
1827
2146
|
const key = await readKey();
|
|
1828
2147
|
if (key === "quit") {
|
|
1829
|
-
|
|
2148
|
+
out7(`
|
|
1830
2149
|
stopped at intent ${i + 1}/${chain.length} \u2014 the verify verdict below covers the WHOLE chain as delivered`);
|
|
1831
2150
|
break;
|
|
1832
2151
|
}
|
|
1833
2152
|
if (key === "all") autoRun = true;
|
|
1834
2153
|
}
|
|
1835
2154
|
}
|
|
1836
|
-
if (!json)
|
|
2155
|
+
if (!json) out7("");
|
|
1837
2156
|
const verdict = verifyChainLocal(eng, SOURCE);
|
|
1838
2157
|
if (json) {
|
|
1839
2158
|
emit({ finale: true, verdict });
|
|
@@ -1843,65 +2162,89 @@ stopped at intent ${i + 1}/${chain.length} \u2014 the verify verdict below cover
|
|
|
1843
2162
|
}
|
|
1844
2163
|
|
|
1845
2164
|
// src/status.ts
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
2165
|
+
init_engine();
|
|
2166
|
+
init_cloud();
|
|
2167
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
2168
|
+
var out8 = (s) => void process.stdout.write(s + "\n");
|
|
2169
|
+
var err8 = (s) => void process.stderr.write("error: " + s + "\n");
|
|
1849
2170
|
function lawDiffVerdict(localHash, installed, ws) {
|
|
1850
2171
|
const active = installed.filter((d) => d.phase === "Active" && typeof d.domainHash === "string");
|
|
1851
|
-
const
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
2172
|
+
const currents = active.filter((d) => d.current === true);
|
|
2173
|
+
const priors = active.filter((d) => d.current !== true);
|
|
2174
|
+
const localActive = active.find((d) => d.domainHash === localHash);
|
|
2175
|
+
const localIsCurrent = currents.some((d) => d.domainHash === localHash);
|
|
1855
2176
|
const lines = [];
|
|
2177
|
+
if (localIsCurrent || currents.length === 0 && localActive !== void 0) {
|
|
2178
|
+
lines.push(`\u2713 in sync \u2014 the deployed law IS your local build (${localHash.slice(0, 16)}\u2026)`);
|
|
2179
|
+
if (priors.length > 0) lines.push(` (${priors.length} prior deploy(s) remain Active under install-beside; yours is the current one resolution serves)`);
|
|
2180
|
+
return { inSync: true, lines };
|
|
2181
|
+
}
|
|
1856
2182
|
if (active.length === 0) {
|
|
1857
2183
|
lines.push(`\u2717 workspace '${ws}' has NO active tenant law \u2014 your local build is not deployed`);
|
|
2184
|
+
} else if (localActive !== void 0) {
|
|
2185
|
+
lines.push(`~ your build (${localHash.slice(0, 16)}\u2026) is an ACTIVE install on '${ws}', but SUPERSEDED \u2014 a newer deploy is current:`);
|
|
2186
|
+
for (const c of currents) lines.push(` current ${c.domainHash.slice(0, 16)}\u2026 (${c.installedBy ?? "?"}) \u2190 resolution serves this`);
|
|
2187
|
+
lines.push(` redeploy to make your build current: githolon deploy ${ws}`);
|
|
2188
|
+
return { inSync: false, lines };
|
|
2189
|
+
} else if (currents.length > 0) {
|
|
2190
|
+
lines.push(`\u2717 local law differs from the current install${currents.length > 1 ? "s" : ""} on '${ws}':`);
|
|
2191
|
+
for (const c of currents) lines.push(` current ${c.domainHash.slice(0, 16)}\u2026 (${c.installedBy ?? "?"}) \u2190 resolution serves this`);
|
|
2192
|
+
if (priors.length > 0) lines.push(` + ${priors.length} prior active install(s) (install-beside \u2014 historical, not served)`);
|
|
1858
2193
|
} else {
|
|
1859
|
-
lines.push(`\u2717 local law differs from every active installation on '${ws}':`);
|
|
2194
|
+
lines.push(`\u2717 local law differs from every active installation on '${ws}' (${active.length}):`);
|
|
1860
2195
|
for (const d of active) lines.push(` deployed ${d.domainHash.slice(0, 16)}\u2026 (${d.installedBy ?? "?"})`);
|
|
1861
2196
|
}
|
|
1862
|
-
lines.push(` remedy: githolon deploy ${ws} (installs ${localHash.slice(0, 16)}\u2026)`);
|
|
2197
|
+
lines.push(` remedy: githolon deploy ${ws} (installs ${localHash.slice(0, 16)}\u2026 and makes it current)`);
|
|
1863
2198
|
return { inSync: false, lines };
|
|
1864
2199
|
}
|
|
2200
|
+
var CONTROL_PLANE_KEYS = ["nomos", "bootstrap", "workspaces"];
|
|
1865
2201
|
async function status(wsArg, opts) {
|
|
1866
2202
|
const cloud = cloudBase(opts.cloud);
|
|
1867
2203
|
let ws;
|
|
1868
2204
|
try {
|
|
1869
2205
|
ws = await resolveWorkspace(wsArg, process.cwd(), opts.target, "githolon status <ws>");
|
|
1870
2206
|
} catch (e) {
|
|
1871
|
-
|
|
2207
|
+
err8(e.message);
|
|
1872
2208
|
return 1;
|
|
1873
2209
|
}
|
|
1874
2210
|
let localHash;
|
|
1875
2211
|
try {
|
|
1876
|
-
const body = JSON.parse(
|
|
2212
|
+
const body = JSON.parse(readFileSync8(discoverDeployJson(process.cwd(), opts.file), "utf8"));
|
|
1877
2213
|
if (typeof body.packageUsda === "string") localHash = await sha256hex(body.packageUsda);
|
|
1878
2214
|
} catch {
|
|
1879
2215
|
localHash = void 0;
|
|
1880
2216
|
}
|
|
1881
2217
|
const r = await fetch(`${cloud}/v1/workspaces/${ws}/domains`).catch(() => void 0);
|
|
1882
2218
|
if (r === void 0) {
|
|
1883
|
-
|
|
2219
|
+
err8(`cannot reach ${cloud} \u2014 githolon status compares against the DEPLOYED law and needs the cloud (local-only: githolon dev)`);
|
|
1884
2220
|
return 1;
|
|
1885
2221
|
}
|
|
1886
2222
|
const d = await r.json().catch(() => void 0);
|
|
1887
2223
|
if (!r.ok || d?.ok !== true) {
|
|
1888
|
-
|
|
2224
|
+
err8(`status '${ws}' failed (${r.status}): ${d ? JSON.stringify(d) : "no body"}
|
|
1889
2225
|
is the workspace born? githolon ws create ${ws}`);
|
|
1890
2226
|
return 1;
|
|
1891
2227
|
}
|
|
1892
|
-
|
|
1893
|
-
const
|
|
1894
|
-
|
|
2228
|
+
out8(`workspace ${ws} on ${cloud}`);
|
|
2229
|
+
const allDomains = d.domains ?? [];
|
|
2230
|
+
const controlPlaneHashes = new Set(CONTROL_PLANE_KEYS.map((k) => d.currentLaw?.[k]).filter(Boolean));
|
|
2231
|
+
const domains = allDomains.filter((dm) => !controlPlaneHashes.has(dm.domainHash));
|
|
2232
|
+
const controllerCount = allDomains.length - domains.length;
|
|
2233
|
+
if (allDomains.length === 0) out8(`installed (none \u2014 not even the controller; the workspace may be unborn)`);
|
|
1895
2234
|
for (const dom of domains) {
|
|
1896
|
-
|
|
2235
|
+
const mark = dom.current === true ? " \u2190 current (the holon resolves dispatch here)" : "";
|
|
2236
|
+
out8(`installed ${(dom.domainHash ?? "?").slice(0, 16)}\u2026 ${dom.phase ?? "?"} by ${dom.installedBy ?? "?"}${mark}`);
|
|
1897
2237
|
}
|
|
2238
|
+
if (controllerCount > 0) out8(` (+ ${controllerCount} control-plane install(s) \u2014 the nomos lifecycle controller; not tenant law)`);
|
|
2239
|
+
const tenantActive = domains.filter((dm) => dm.phase === "Active").length;
|
|
2240
|
+
if (tenantActive > 1) out8(` (${tenantActive} active tenant installs \u2014 install-beside keeps every prior deploy Active; the holon serves the current one per domain)`);
|
|
1898
2241
|
if (localHash === void 0) {
|
|
1899
|
-
|
|
2242
|
+
out8(`local (no build/*.deploy.json here \u2014 run \`githolon compile\` to build the law this project authors)`);
|
|
1900
2243
|
return 0;
|
|
1901
2244
|
}
|
|
1902
|
-
|
|
2245
|
+
out8(`local ${localHash.slice(0, 16)}\u2026 (build/*.deploy.json)`);
|
|
1903
2246
|
const verdict = lawDiffVerdict(localHash, domains, ws);
|
|
1904
|
-
for (const line of verdict.lines)
|
|
2247
|
+
for (const line of verdict.lines) out8(line);
|
|
1905
2248
|
return verdict.inSync ? 0 : 2;
|
|
1906
2249
|
}
|
|
1907
2250
|
|
|
@@ -1943,6 +2286,15 @@ a bare <ws> resolves from nomos.package.mjs's optional \`workspace\` binding):
|
|
|
1943
2286
|
githolon secret set <ws> <secret> import a secret you already hold
|
|
1944
2287
|
githolon secret rotate <ws> rotate (and re-save) the workspace secret
|
|
1945
2288
|
|
|
2289
|
+
Capability credentials (facts on the ledger, values host-side \u2014 the value NEVER rides argv;
|
|
2290
|
+
stdin or --from-gcloud only; architecture/capability_credentials.md):
|
|
2291
|
+
githolon capability bind <ws> <capability> bind a credential: value from stdin (or
|
|
2292
|
+
--from-gcloud <name>) \u2192 the workspace's holon;
|
|
2293
|
+
sha256(value) \u2192 the law-state binding fact
|
|
2294
|
+
githolon capability list <ws> the binding FACTS (never the values)
|
|
2295
|
+
githolon capability rotate <ws> <capability> revoke + re-bind at epoch+1 (new value)
|
|
2296
|
+
githolon capability unbind <ws> <capability> revoke the fact; the host forgets the value
|
|
2297
|
+
|
|
1946
2298
|
Git lane (stock git push as deploy/birth \u2014 see also: push-to-create):
|
|
1947
2299
|
githolon git setup install the credential helper for the cloud host
|
|
1948
2300
|
githolon git remote [<ws>] [<remoteName>] wire this repo to a workspace (default name: nomos)
|
|
@@ -2021,23 +2373,71 @@ ${USAGE}`);
|
|
|
2021
2373
|
if (sub === "rotate" && ws !== void 0) return secretRotate(ws, opts);
|
|
2022
2374
|
return usageFail("expected: githolon secret <set <ws> <secret> | rotate <ws>>");
|
|
2023
2375
|
}
|
|
2376
|
+
async function runCapability(argv) {
|
|
2377
|
+
const usageFail = (m) => {
|
|
2378
|
+
process.stderr.write(`error: ${m}
|
|
2379
|
+
|
|
2380
|
+
${commandHelp("capability")}`);
|
|
2381
|
+
return 1;
|
|
2382
|
+
};
|
|
2383
|
+
const pos = [];
|
|
2384
|
+
const opts = {};
|
|
2385
|
+
const rest = argv.slice(1);
|
|
2386
|
+
for (let i = 0; i < rest.length; i++) {
|
|
2387
|
+
const a = rest[i];
|
|
2388
|
+
if (a === "--cloud" || a === "--from-gcloud" || a === "--scope" || a === "--expires-at") {
|
|
2389
|
+
const v = rest[++i];
|
|
2390
|
+
if (v === void 0) return usageFail(`${a} requires a value`);
|
|
2391
|
+
if (a === "--cloud") opts.cloud = v;
|
|
2392
|
+
else if (a === "--from-gcloud") opts.fromGcloud = v;
|
|
2393
|
+
else if (a === "--scope") opts.scope = v;
|
|
2394
|
+
else opts.expiresAt = v;
|
|
2395
|
+
} else if (a.startsWith("--")) {
|
|
2396
|
+
return usageFail(`unknown flag '${a}'`);
|
|
2397
|
+
} else {
|
|
2398
|
+
pos.push(a);
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
const [sub, ws, capability] = pos;
|
|
2402
|
+
if (sub === "bind" && ws !== void 0 && capability !== void 0) return capabilityBind(ws, capability, opts);
|
|
2403
|
+
if (sub === "rotate" && ws !== void 0 && capability !== void 0) return capabilityRotate(ws, capability, opts);
|
|
2404
|
+
if (sub === "unbind" && ws !== void 0 && capability !== void 0) return capabilityUnbind(ws, capability, opts);
|
|
2405
|
+
if (sub === "list" && ws !== void 0) return capabilityList(ws, opts);
|
|
2406
|
+
return usageFail("expected: githolon capability <bind|rotate|unbind> <ws> <capability> | list <ws>");
|
|
2407
|
+
}
|
|
2024
2408
|
function isKind(s) {
|
|
2025
2409
|
return s !== void 0 && KINDS.includes(s);
|
|
2026
2410
|
}
|
|
2027
|
-
function runProof(args) {
|
|
2411
|
+
async function runProof(args) {
|
|
2028
2412
|
const refuse = (m) => {
|
|
2029
2413
|
process.stderr.write(`error: ${m}
|
|
2030
2414
|
`);
|
|
2031
2415
|
return 1;
|
|
2032
2416
|
};
|
|
2033
|
-
const
|
|
2417
|
+
const live = args.includes("--live");
|
|
2418
|
+
const keep = args.includes("--keep");
|
|
2419
|
+
if (keep && !live) return refuse("--keep only applies to --live (the offline proof creates no cloud workspace to keep)");
|
|
2420
|
+
let domain;
|
|
2421
|
+
let domainValueIdx = -1;
|
|
2422
|
+
const di = args.findIndex((a) => a === "--domain" || a.startsWith("--domain="));
|
|
2423
|
+
if (di >= 0) {
|
|
2424
|
+
const a = args[di];
|
|
2425
|
+
if (a.includes("=")) domain = a.slice(a.indexOf("=") + 1);
|
|
2426
|
+
else {
|
|
2427
|
+
domain = args[di + 1];
|
|
2428
|
+
domainValueIdx = di + 1;
|
|
2429
|
+
}
|
|
2430
|
+
if (domain === void 0 || domain === "" || domain.startsWith("--")) return refuse("--domain needs a domain key: githolon proof --domain <key>");
|
|
2431
|
+
}
|
|
2432
|
+
if (domain !== void 0 && live) return refuse("--domain is an OFFLINE-lane selector (the --live proof runs the package's primary domain end-to-end); drop --live or drop --domain");
|
|
2433
|
+
const explicit = args.find((a, i) => !a.startsWith("--") && i !== domainValueIdx);
|
|
2034
2434
|
let proofPath;
|
|
2035
2435
|
if (explicit !== void 0) {
|
|
2036
2436
|
proofPath = resolve2(process.cwd(), explicit);
|
|
2037
|
-
if (!
|
|
2437
|
+
if (!existsSync9(proofPath)) return refuse(`proof not found: ${proofPath} \u2014 run \`githolon compile\` to (re)generate it`);
|
|
2038
2438
|
} else {
|
|
2039
|
-
const buildDir =
|
|
2040
|
-
if (!
|
|
2439
|
+
const buildDir = join10(process.cwd(), "build");
|
|
2440
|
+
if (!existsSync9(buildDir)) {
|
|
2041
2441
|
return refuse("no build/ directory here \u2014 run `githolon compile` first (every compile generates build/<name>.proof.mts from your law)");
|
|
2042
2442
|
}
|
|
2043
2443
|
const proofs = readdirSync4(buildDir).filter((f) => f.endsWith(".proof.mts"));
|
|
@@ -2047,21 +2447,30 @@ function runProof(args) {
|
|
|
2047
2447
|
if (proofs.length > 1) {
|
|
2048
2448
|
return refuse(`found ${proofs.length} proofs (${proofs.join(", ")}) \u2014 name one: githolon proof build/<name>.proof.mts`);
|
|
2049
2449
|
}
|
|
2050
|
-
proofPath =
|
|
2450
|
+
proofPath = join10(buildDir, proofs[0]);
|
|
2451
|
+
}
|
|
2452
|
+
if (!live) {
|
|
2453
|
+
try {
|
|
2454
|
+
const { runOfflineProofStandalone: runOfflineProofStandalone2 } = await Promise.resolve().then(() => (init_proof_offline(), proof_offline_exports));
|
|
2455
|
+
const { cloudBase: cloudBase2 } = await Promise.resolve().then(() => (init_cloud(), cloud_exports));
|
|
2456
|
+
return await runOfflineProofStandalone2(proofPath, cloudBase2(), domain);
|
|
2457
|
+
} catch (e) {
|
|
2458
|
+
return refuse(`${e.message ?? e}`);
|
|
2459
|
+
}
|
|
2051
2460
|
}
|
|
2052
2461
|
const resolvePkgDir = (name, fromDir) => {
|
|
2053
2462
|
try {
|
|
2054
|
-
return
|
|
2463
|
+
return dirname5(createRequire2(pathToFileURL4(join10(fromDir, "noop.js"))).resolve(`${name}/package.json`));
|
|
2055
2464
|
} catch {
|
|
2056
2465
|
return void 0;
|
|
2057
2466
|
}
|
|
2058
2467
|
};
|
|
2059
2468
|
const dslDir = (() => {
|
|
2060
2469
|
try {
|
|
2061
|
-
return
|
|
2470
|
+
return dirname5(createRequire2(pathToFileURL4(join10(process.cwd(), "noop.js"))).resolve("@githolon/dsl/package.json"));
|
|
2062
2471
|
} catch {
|
|
2063
2472
|
try {
|
|
2064
|
-
return
|
|
2473
|
+
return dirname5(createRequire2(import.meta.url).resolve("@githolon/dsl/package.json"));
|
|
2065
2474
|
} catch {
|
|
2066
2475
|
return void 0;
|
|
2067
2476
|
}
|
|
@@ -2071,17 +2480,22 @@ function runProof(args) {
|
|
|
2071
2480
|
if (tsxDir === void 0) {
|
|
2072
2481
|
return refuse("tsx not found \u2014 add @githolon/dsl to your project's dependencies (tsx rides along) and npm install");
|
|
2073
2482
|
}
|
|
2074
|
-
const tsxPkg = JSON.parse(
|
|
2483
|
+
const tsxPkg = JSON.parse(readFileSync9(join10(tsxDir, "package.json"), "utf8"));
|
|
2075
2484
|
const tsxBinRel = typeof tsxPkg.bin === "string" ? tsxPkg.bin : tsxPkg.bin?.["tsx"];
|
|
2076
|
-
const tsxCli =
|
|
2077
|
-
|
|
2485
|
+
const tsxCli = join10(tsxDir, tsxBinRel ?? "dist/cli.mjs");
|
|
2486
|
+
console.log("githolon proof --live \u2014 the full cloud loop (throwaway workspace, retired on exit" + (keep ? "; --keep: kept, secret printed once" : "") + ")");
|
|
2487
|
+
const r = spawnSync5(process.execPath, [tsxCli, proofPath], {
|
|
2488
|
+
stdio: "inherit",
|
|
2489
|
+
cwd: process.cwd(),
|
|
2490
|
+
env: { ...process.env, ...keep ? { PROOF_KEEP: "1" } : {} }
|
|
2491
|
+
});
|
|
2078
2492
|
return r.status ?? 1;
|
|
2079
2493
|
}
|
|
2080
2494
|
function parseArgs(argv) {
|
|
2081
2495
|
const [command, ...rest] = argv;
|
|
2082
2496
|
if (command !== "generate" && command !== "g") {
|
|
2083
2497
|
throw new Error(
|
|
2084
|
-
`Unknown command '${command ?? "(none)"}'. Expected dev | compile | proof | replay | status | generate | ws | deploy | secret.`
|
|
2498
|
+
`Unknown command '${command ?? "(none)"}'. Expected dev | compile | proof | replay | status | generate | ws | deploy | secret | capability.`
|
|
2085
2499
|
);
|
|
2086
2500
|
}
|
|
2087
2501
|
let outDir = DEFAULT_OUT;
|
|
@@ -2209,6 +2623,9 @@ ${commandHelp("status")}`);
|
|
|
2209
2623
|
if (argv[0] === "ws" || argv[0] === "deploy" || argv[0] === "secret") {
|
|
2210
2624
|
return runCloud(argv);
|
|
2211
2625
|
}
|
|
2626
|
+
if (argv[0] === "capability") {
|
|
2627
|
+
return runCapability(argv);
|
|
2628
|
+
}
|
|
2212
2629
|
if (argv[0] === "login") {
|
|
2213
2630
|
const agent = argv.includes("--agent");
|
|
2214
2631
|
const tokenAt = argv.indexOf("--token");
|
|
@@ -2265,8 +2682,8 @@ ${USAGE}`);
|
|
|
2265
2682
|
let parsed;
|
|
2266
2683
|
try {
|
|
2267
2684
|
parsed = parseArgs(argv);
|
|
2268
|
-
} catch (
|
|
2269
|
-
process.stderr.write(`error: ${
|
|
2685
|
+
} catch (err9) {
|
|
2686
|
+
process.stderr.write(`error: ${err9.message}
|
|
2270
2687
|
|
|
2271
2688
|
${USAGE}`);
|
|
2272
2689
|
return 1;
|
|
@@ -2285,8 +2702,8 @@ ${USAGE}`);
|
|
|
2285
2702
|
process.stdout.write(`created ${path}
|
|
2286
2703
|
`);
|
|
2287
2704
|
return 0;
|
|
2288
|
-
} catch (
|
|
2289
|
-
process.stderr.write(`error: ${
|
|
2705
|
+
} catch (err9) {
|
|
2706
|
+
process.stderr.write(`error: ${err9.message}
|
|
2290
2707
|
`);
|
|
2291
2708
|
return 1;
|
|
2292
2709
|
}
|