githolon 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +890 -522
- 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,606 @@ 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) {
|
|
836
|
+
const src = readFileSync3(proofPath, "utf8");
|
|
837
|
+
const legs = parseOfflineLegs(src);
|
|
838
|
+
const packageName = basename(proofPath).replace(/\.proof\.mts$/, "");
|
|
839
|
+
const deployFile = join6(dirname3(proofPath), `${packageName}.deploy.json`);
|
|
840
|
+
if (!existsSync5(deployFile)) {
|
|
841
|
+
throw new Error(`missing ${deployFile} \u2014 run \`githolon compile\` (the proof and the deploy body are built together)`);
|
|
842
|
+
}
|
|
843
|
+
const deployBody = JSON.parse(readFileSync3(deployFile, "utf8"));
|
|
844
|
+
const lawHash = await sha256hex(deployBody.packageUsda);
|
|
845
|
+
console.log(`githolon proof \u2014 OFFLINE legs on a local holon (the cloud loop: githolon proof --live)`);
|
|
846
|
+
const { eng } = await holonEngine(cloud, deployBody, "githolon-proof");
|
|
847
|
+
const ws = "proof-offline";
|
|
848
|
+
await mountFresh(eng, ws);
|
|
849
|
+
author(eng, ws, "bootstrap", "installDomain", installPayload(eng.hashes.nomos, eng.nomosPkg, "githolon-proof"), "");
|
|
850
|
+
author(eng, ws, "nomos", "installDomain", installPayload(lawHash, deployBody.packageUsda, "githolon-proof"), eng.hashes.nomos);
|
|
851
|
+
const v = runOfflineLegs(eng, ws, lawHash, legs);
|
|
852
|
+
for (const l of v.legs) console.log(l);
|
|
853
|
+
if (!v.ok) {
|
|
854
|
+
console.error(`\u2717 ${v.error}`);
|
|
855
|
+
return 1;
|
|
856
|
+
}
|
|
857
|
+
console.log(`
|
|
858
|
+
OFFLINE PROOF GREEN (${Math.round(v.ms)}ms) \u2014 the same legs rerun on every save in \`githolon dev\`.`);
|
|
859
|
+
console.log(`Deploy with confidence when ready: githolon ws create <ws> && githolon deploy <ws> (or the full cloud proof: githolon proof --live)`);
|
|
860
|
+
return 0;
|
|
861
|
+
}
|
|
862
|
+
var init_proof_offline = __esm({
|
|
863
|
+
"src/proof_offline.ts"() {
|
|
864
|
+
"use strict";
|
|
865
|
+
init_engine();
|
|
866
|
+
init_local_holon();
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
// src/cli.ts
|
|
871
|
+
import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
|
|
872
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
873
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
874
|
+
import { dirname as dirname5, join as join10, resolve as resolve2 } from "node:path";
|
|
875
|
+
import { pathToFileURL as pathToFileURL4 } from "node:url";
|
|
876
|
+
|
|
877
|
+
// src/generate.ts
|
|
878
|
+
import { mkdirSync, existsSync, writeFileSync } from "node:fs";
|
|
879
|
+
import { join } from "node:path";
|
|
880
|
+
|
|
881
|
+
// src/naming.ts
|
|
882
|
+
function words(raw) {
|
|
883
|
+
return raw.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_\-\s]+/g, " ").trim().toLowerCase().split(" ").filter((w) => w.length > 0);
|
|
884
|
+
}
|
|
885
|
+
var cap = (w) => w.length === 0 ? w : w[0].toUpperCase() + w.slice(1);
|
|
886
|
+
function pascalCase(raw) {
|
|
887
|
+
return words(raw).map(cap).join("");
|
|
888
|
+
}
|
|
889
|
+
function camelCase(raw) {
|
|
890
|
+
return words(raw).map((w, i) => i === 0 ? w : cap(w)).join("");
|
|
891
|
+
}
|
|
892
|
+
function snakeCase(raw) {
|
|
893
|
+
return words(raw).join("_");
|
|
894
|
+
}
|
|
895
|
+
function screamingSnakeCase(raw) {
|
|
896
|
+
return words(raw).join("_").toUpperCase();
|
|
897
|
+
}
|
|
898
|
+
function assertValidName(raw) {
|
|
899
|
+
const ws = words(raw);
|
|
900
|
+
if (ws.length === 0) {
|
|
901
|
+
throw new Error(`Invalid name: '${raw}' \u2014 give at least one word, e.g. 'work_order'.`);
|
|
902
|
+
}
|
|
903
|
+
for (const w of ws) {
|
|
904
|
+
if (!/^[a-z][a-z0-9]*$/.test(w)) {
|
|
905
|
+
throw new Error(
|
|
906
|
+
`Invalid name token '${w}' in '${raw}' \u2014 words must be alphanumeric and start with a letter (got after normalisation: ${ws.join(", ")}).`
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
return ws;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// src/templates.ts
|
|
914
|
+
function namesFor(raw) {
|
|
915
|
+
return {
|
|
916
|
+
raw,
|
|
917
|
+
pascal: pascalCase(raw),
|
|
918
|
+
camel: camelCase(raw),
|
|
919
|
+
statusesConst: `${screamingSnakeCase(raw)}_STATUSES`
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
var HEADER = (raw, kind) => `/**
|
|
923
|
+
* The \`${raw}\` ${kind} \u2014 scaffolded by \`githolon generate\`.
|
|
924
|
+
*
|
|
925
|
+
* Authoring rules (see @githolon/dsl): write ONLY aggregates (typed fields, each
|
|
926
|
+
* tagged with a merge Driver) and directives (Zod payload + declarative plan).
|
|
927
|
+
* You never write apply/fold/merge \u2014 the kernel owns the merge algebra.
|
|
928
|
+
*
|
|
929
|
+
* Convention: the fail-closed \`Conflict\` driver is the default for scalars.
|
|
930
|
+
* Relax only what differs: \`.merge(Lww)\`, \`.merge(AddWins)\`, \`.merge(MapOf(Lww))\`.
|
|
931
|
+
*/`;
|
|
932
|
+
function aggregateBlock(n) {
|
|
933
|
+
return `export const ${n.statusesConst} = ["active", "retired"] as const;
|
|
934
|
+
|
|
935
|
+
export const ${n.pascal} = aggregate("${n.pascal}", {
|
|
936
|
+
// Default driver is the fail-closed \`Conflict\`: concurrent edits surface
|
|
937
|
+
// as a conflict rather than silently coalescing. Declare only what differs.
|
|
938
|
+
label: t.string().merge(Conflict),
|
|
939
|
+
// Overrides \u2014 last-write-wins / set-union / per-key map:
|
|
940
|
+
pos: t.int().merge(Lww),
|
|
941
|
+
status: t.enum(${n.statusesConst}).merge(Lww),
|
|
942
|
+
tags: t.set(t.string()).merge(AddWins),
|
|
943
|
+
customFields: t.map(t.string()).merge(MapOf(Lww)),
|
|
944
|
+
});`;
|
|
945
|
+
}
|
|
946
|
+
function domainTemplate(raw) {
|
|
947
|
+
const n = namesFor(raw);
|
|
948
|
+
return `${HEADER(raw, "domain")}
|
|
949
|
+
import {
|
|
950
|
+
z,
|
|
951
|
+
aggregate,
|
|
952
|
+
directive,
|
|
953
|
+
t,
|
|
954
|
+
Conflict,
|
|
955
|
+
Lww,
|
|
956
|
+
AddWins,
|
|
957
|
+
MapOf,
|
|
958
|
+
set,
|
|
959
|
+
addToSet,
|
|
960
|
+
setEntry,
|
|
961
|
+
} from "@githolon/dsl";
|
|
962
|
+
|
|
963
|
+
${aggregateBlock(n)}
|
|
964
|
+
|
|
965
|
+
/** create${n.pascal} \u2014 .creates the aggregate; seeds label/pos/status. */
|
|
966
|
+
export const create${n.pascal} = directive("create${n.pascal}")
|
|
967
|
+
.creates(${n.pascal})
|
|
968
|
+
.payload(
|
|
969
|
+
z.object({
|
|
970
|
+
label: z.string(),
|
|
971
|
+
pos: z.number().int(),
|
|
972
|
+
status: z.enum(${n.statusesConst}),
|
|
973
|
+
}),
|
|
974
|
+
)
|
|
975
|
+
.plan((p) => [
|
|
976
|
+
set(${n.pascal}, "label", p.label),
|
|
977
|
+
set(${n.pascal}, "pos", p.pos),
|
|
978
|
+
set(${n.pascal}, "status", p.status),
|
|
979
|
+
]);
|
|
980
|
+
|
|
981
|
+
/** move${n.pascal} \u2014 .mutates pos. */
|
|
982
|
+
export const move${n.pascal} = directive("move${n.pascal}")
|
|
983
|
+
.mutates(${n.pascal})
|
|
984
|
+
.payload(z.object({ pos: z.number().int() }))
|
|
985
|
+
.plan((p) => [set(${n.pascal}, "pos", p.pos)]);
|
|
986
|
+
|
|
987
|
+
/** tag${n.pascal} \u2014 .mutates tags (add-wins union). */
|
|
988
|
+
export const tag${n.pascal} = directive("tag${n.pascal}")
|
|
989
|
+
.mutates(${n.pascal})
|
|
990
|
+
.payload(z.object({ tags: z.array(z.string()) }))
|
|
991
|
+
.plan((p) => [addToSet(${n.pascal}, "tags", p.tags)]);
|
|
992
|
+
|
|
993
|
+
/** setCustomFieldOn${n.pascal} \u2014 .mutates customFields (a map entry). */
|
|
994
|
+
export const setCustomFieldOn${n.pascal} = directive("setCustomFieldOn${n.pascal}")
|
|
995
|
+
.mutates(${n.pascal})
|
|
996
|
+
.payload(z.object({ key: z.string(), value: z.string() }))
|
|
997
|
+
.plan((p) => [setEntry(${n.pascal}, "customFields", p.key, p.value)]);
|
|
998
|
+
`;
|
|
999
|
+
}
|
|
1000
|
+
function aggregateTemplate(raw) {
|
|
1001
|
+
const n = namesFor(raw);
|
|
1002
|
+
return `${HEADER(raw, "aggregate")}
|
|
1003
|
+
import { aggregate, t, Conflict, Lww, AddWins, MapOf } from "@githolon/dsl";
|
|
1004
|
+
|
|
1005
|
+
${aggregateBlock(n)}
|
|
1006
|
+
`;
|
|
1007
|
+
}
|
|
1008
|
+
function intentTemplate(raw) {
|
|
1009
|
+
const n = namesFor(raw);
|
|
1010
|
+
return `${HEADER(raw, "intent (directive)")}
|
|
1011
|
+
import { z, aggregate, directive, t, Conflict, set } from "@githolon/dsl";
|
|
1012
|
+
|
|
1013
|
+
// Stub target \u2014 in a real domain, import the existing aggregate handle instead.
|
|
1014
|
+
export const ${n.pascal} = aggregate("${n.pascal}", {
|
|
1015
|
+
label: t.string().merge(Conflict),
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
/** ${n.camel} \u2014 .mutates the target aggregate. */
|
|
1019
|
+
export const ${n.camel} = directive("${n.camel}")
|
|
1020
|
+
.mutates(${n.pascal})
|
|
1021
|
+
.payload(z.object({ label: z.string() }))
|
|
1022
|
+
.plan((p) => [set(${n.pascal}, "label", p.label)]);
|
|
1023
|
+
`;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// src/generate.ts
|
|
1027
|
+
var RENDERERS = {
|
|
1028
|
+
domain: domainTemplate,
|
|
1029
|
+
aggregate: aggregateTemplate,
|
|
1030
|
+
intent: intentTemplate
|
|
1031
|
+
};
|
|
1032
|
+
function renderScaffold(kind, name) {
|
|
1033
|
+
assertValidName(name);
|
|
1034
|
+
const render = RENDERERS[kind];
|
|
1035
|
+
if (!render) {
|
|
1036
|
+
throw new Error(`Unknown generator kind '${kind}'. Use: domain | aggregate | intent.`);
|
|
1037
|
+
}
|
|
1038
|
+
return render(name);
|
|
1039
|
+
}
|
|
1040
|
+
function generate(opts) {
|
|
1041
|
+
const contents = renderScaffold(opts.kind, opts.name);
|
|
1042
|
+
const fileName = `${snakeCase(opts.name)}.ts`;
|
|
1043
|
+
const path = join(opts.outDir, fileName);
|
|
1044
|
+
if (existsSync(path) && !opts.force) {
|
|
1045
|
+
throw new Error(
|
|
1046
|
+
`Refusing to overwrite existing file: ${path}
|
|
1047
|
+
Re-run with --force to replace it.`
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
mkdirSync(opts.outDir, { recursive: true });
|
|
1051
|
+
writeFileSync(path, contents, "utf8");
|
|
1052
|
+
return { path, contents };
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// src/capability.ts
|
|
1056
|
+
init_cloud();
|
|
1057
|
+
import { spawnSync } from "node:child_process";
|
|
1058
|
+
var out2 = (s) => void process.stdout.write(s + "\n");
|
|
1059
|
+
var err2 = (s) => void process.stderr.write("error: " + s + "\n");
|
|
1060
|
+
var CAP_NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
1061
|
+
function readStdin() {
|
|
1062
|
+
if (process.stdin.isTTY === true) {
|
|
1063
|
+
process.stderr.write(
|
|
1064
|
+
"paste the credential value, then Ctrl-D (it never rides argv \u2014 ps/history are hosts too):\n"
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
return new Promise((resolveValue) => {
|
|
1068
|
+
let buf = "";
|
|
1069
|
+
process.stdin.setEncoding("utf8");
|
|
1070
|
+
process.stdin.on("data", (chunk) => {
|
|
1071
|
+
buf += chunk;
|
|
1072
|
+
});
|
|
1073
|
+
process.stdin.on("end", () => resolveValue(buf.trim()));
|
|
1074
|
+
process.stdin.resume();
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
function readGcloud(name) {
|
|
1078
|
+
const r = spawnSync("gcloud", ["secrets", "versions", "access", "latest", `--secret=${name}`], {
|
|
1079
|
+
encoding: "utf8",
|
|
1080
|
+
maxBuffer: 1024 * 1024
|
|
1081
|
+
});
|
|
1082
|
+
if (r.error !== void 0 || r.status !== 0) {
|
|
1083
|
+
throw new Error(
|
|
1084
|
+
`gcloud secret read failed (${r.error?.message ?? r.stderr?.trim() ?? `exit ${r.status}`}) \u2014 is gcloud installed + authed, and does secret '${name}' exist?`
|
|
1085
|
+
);
|
|
1086
|
+
}
|
|
1087
|
+
const v = r.stdout.trim();
|
|
1088
|
+
if (v === "") throw new Error(`gcloud secret '${name}' is empty`);
|
|
1089
|
+
return v;
|
|
1090
|
+
}
|
|
1091
|
+
function parseExpiresAt(s) {
|
|
1092
|
+
const n = /^\d+$/.test(s) ? Number(s) : Date.parse(s);
|
|
1093
|
+
if (!Number.isInteger(n) || n <= 0) throw new Error(`--expires-at '${s}' is neither epoch-ms nor an ISO date`);
|
|
1094
|
+
return n;
|
|
1095
|
+
}
|
|
1096
|
+
async function bindLane(lane, ws, capability, opts) {
|
|
1097
|
+
const cloud = cloudBase(opts.cloud);
|
|
1098
|
+
if (!CAP_NAME.test(capability)) {
|
|
1099
|
+
err2(`capability must be a lowercase law name matching [a-z0-9][a-z0-9._-]{0,63} \u2014 e.g. cf-analytics, openai`);
|
|
1100
|
+
return 1;
|
|
1101
|
+
}
|
|
1102
|
+
const secret = getSecret(cloud, ws);
|
|
1103
|
+
if (secret === void 0) {
|
|
1104
|
+
err2(
|
|
1105
|
+
`no stored secret for ${ws} on ${cloud} \u2014 capability ${lane} is owner-gated
|
|
1106
|
+
githolon secret set ${ws} <secret> (import the workspaceSecret you hold)`
|
|
1107
|
+
);
|
|
1108
|
+
return 1;
|
|
1109
|
+
}
|
|
1110
|
+
let value;
|
|
1111
|
+
try {
|
|
1112
|
+
value = opts.fromGcloud !== void 0 ? readGcloud(opts.fromGcloud) : await readStdin();
|
|
1113
|
+
} catch (e) {
|
|
1114
|
+
err2(e.message);
|
|
1115
|
+
return 1;
|
|
1116
|
+
}
|
|
1117
|
+
if (value === "") {
|
|
1118
|
+
err2("no credential value arrived \u2014 pipe it on stdin, or pass --from-gcloud <secret-name>");
|
|
1119
|
+
return 1;
|
|
1120
|
+
}
|
|
1121
|
+
let expiresAt;
|
|
1122
|
+
try {
|
|
1123
|
+
expiresAt = opts.expiresAt !== void 0 ? parseExpiresAt(opts.expiresAt) : void 0;
|
|
1124
|
+
} catch (e) {
|
|
1125
|
+
err2(e.message);
|
|
1126
|
+
return 1;
|
|
1127
|
+
}
|
|
1128
|
+
const r = await fetch(`${cloud}/v1/workspaces/${ws}/capabilities/${lane}`, {
|
|
1129
|
+
method: "POST",
|
|
1130
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${secret}` },
|
|
1131
|
+
body: JSON.stringify({
|
|
1132
|
+
capability,
|
|
1133
|
+
secret: value,
|
|
1134
|
+
...opts.scope !== void 0 ? { scope: opts.scope } : {},
|
|
1135
|
+
...expiresAt !== void 0 ? { expiresAt } : {}
|
|
1136
|
+
})
|
|
1137
|
+
});
|
|
1138
|
+
const d = await r.json().catch(() => void 0);
|
|
1139
|
+
if (r.status === 401) {
|
|
1140
|
+
err2(`${lane} refused (401) \u2014 the stored secret for ${ws} is not the workspace's. githolon secret set ${ws} <secret>`);
|
|
1141
|
+
return 1;
|
|
1142
|
+
}
|
|
1143
|
+
if (!r.ok || d?.ok !== true) {
|
|
1144
|
+
err2(`capability ${lane} '${capability}' on ${ws} failed (${r.status}): ${d ? JSON.stringify(d) : "no body"}`);
|
|
1145
|
+
return 1;
|
|
867
1146
|
}
|
|
1147
|
+
out2(`\u2713 ${capability} ${lane === "rotate" ? "rotated" : "bound"} on ${ws} (epoch ${d.epoch})`);
|
|
1148
|
+
out2(` the VALUE lives host-side only (the workspace's holon) \u2014 it never enters the ledger`);
|
|
1149
|
+
out2(` the law fact: keyHash ${(d.keyHash ?? "").slice(0, 16)}\u2026 status ${d.status}`);
|
|
1150
|
+
return 0;
|
|
868
1151
|
}
|
|
869
|
-
function
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1152
|
+
async function capabilityBind(ws, capability, opts) {
|
|
1153
|
+
return bindLane("bind", ws, capability, opts);
|
|
1154
|
+
}
|
|
1155
|
+
async function capabilityRotate(ws, capability, opts) {
|
|
1156
|
+
return bindLane("rotate", ws, capability, opts);
|
|
1157
|
+
}
|
|
1158
|
+
async function capabilityList(ws, opts) {
|
|
1159
|
+
const cloud = cloudBase(opts.cloud);
|
|
1160
|
+
const secret = getSecret(cloud, ws);
|
|
1161
|
+
if (secret === void 0) {
|
|
1162
|
+
err2(`no stored secret for ${ws} on ${cloud} \u2014 the binding facts are owner-gated
|
|
1163
|
+
githolon secret set ${ws} <secret>`);
|
|
1164
|
+
return 1;
|
|
875
1165
|
}
|
|
876
|
-
|
|
1166
|
+
const r = await fetch(`${cloud}/v1/workspaces/${ws}/capabilities`, {
|
|
1167
|
+
headers: { authorization: `Bearer ${secret}` }
|
|
1168
|
+
});
|
|
1169
|
+
const d = await r.json().catch(() => void 0);
|
|
1170
|
+
if (!r.ok || d?.ok !== true) {
|
|
1171
|
+
err2(`capability list '${ws}' failed (${r.status}): ${d ? JSON.stringify(d) : "no body"}`);
|
|
1172
|
+
return 1;
|
|
1173
|
+
}
|
|
1174
|
+
const bindings = d.bindings ?? [];
|
|
1175
|
+
if (bindings.length === 0) {
|
|
1176
|
+
out2(`no capability bindings on ${ws} \u2014 bind one: githolon capability bind ${ws} <capability>`);
|
|
1177
|
+
return 0;
|
|
1178
|
+
}
|
|
1179
|
+
for (const b of bindings) {
|
|
1180
|
+
const bits = [
|
|
1181
|
+
`epoch ${b.epoch}`,
|
|
1182
|
+
`${b.status}`,
|
|
1183
|
+
`keyHash ${(b.keyHash ?? "").slice(0, 16)}\u2026`,
|
|
1184
|
+
...b.scope !== void 0 ? [`scope ${b.scope}`] : [],
|
|
1185
|
+
...b.expiresAt !== void 0 ? [`expires ${new Date(b.expiresAt).toISOString()}`] : [],
|
|
1186
|
+
...b.held !== void 0 ? [b.held ? "value held" : "VALUE MISSING (rotate to restore)"] : []
|
|
1187
|
+
];
|
|
1188
|
+
out2(`${b.capability} ${bits.join(" \xB7 ")}`);
|
|
1189
|
+
}
|
|
1190
|
+
out2(`(facts about authority \u2014 the values live host-side and are never listed)`);
|
|
1191
|
+
return 0;
|
|
877
1192
|
}
|
|
878
|
-
async function
|
|
879
|
-
const
|
|
880
|
-
const
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1193
|
+
async function capabilityUnbind(ws, capability, opts) {
|
|
1194
|
+
const cloud = cloudBase(opts.cloud);
|
|
1195
|
+
const secret = getSecret(cloud, ws);
|
|
1196
|
+
if (secret === void 0) {
|
|
1197
|
+
err2(`no stored secret for ${ws} on ${cloud} \u2014 unbind is owner-gated
|
|
1198
|
+
githolon secret set ${ws} <secret>`);
|
|
1199
|
+
return 1;
|
|
1200
|
+
}
|
|
1201
|
+
const r = await fetch(`${cloud}/v1/workspaces/${ws}/capabilities/unbind`, {
|
|
1202
|
+
method: "POST",
|
|
1203
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${secret}` },
|
|
1204
|
+
body: JSON.stringify({ capability })
|
|
889
1205
|
});
|
|
890
|
-
|
|
1206
|
+
const d = await r.json().catch(() => void 0);
|
|
1207
|
+
if (!r.ok || d?.ok !== true) {
|
|
1208
|
+
err2(`capability unbind '${capability}' on ${ws} failed (${r.status}): ${d ? JSON.stringify(d) : "no body"}`);
|
|
1209
|
+
return 1;
|
|
1210
|
+
}
|
|
1211
|
+
out2(`\u2713 ${capability} unbound on ${ws} \u2014 the law fact is revoked (the record stays) and the host forgot the value`);
|
|
1212
|
+
return 0;
|
|
891
1213
|
}
|
|
892
|
-
|
|
893
|
-
|
|
1214
|
+
|
|
1215
|
+
// src/cli.ts
|
|
1216
|
+
init_cloud();
|
|
1217
|
+
|
|
1218
|
+
// src/compile.ts
|
|
1219
|
+
import { spawn, spawnSync as spawnSync2 } from "node:child_process";
|
|
1220
|
+
import { createRequire } from "node:module";
|
|
1221
|
+
import { dirname, join as join3 } from "node:path";
|
|
1222
|
+
import { pathToFileURL } from "node:url";
|
|
1223
|
+
function resolveCompileLauncher(cwd) {
|
|
1224
|
+
const resolveDslPkg = (fromDir) => {
|
|
1225
|
+
try {
|
|
1226
|
+
const req = fromDir === void 0 ? createRequire(import.meta.url) : createRequire(pathToFileURL(join3(fromDir, "noop.js")));
|
|
1227
|
+
return req.resolve("@githolon/dsl/package.json");
|
|
1228
|
+
} catch {
|
|
1229
|
+
return void 0;
|
|
1230
|
+
}
|
|
1231
|
+
};
|
|
1232
|
+
const dslPkg = resolveDslPkg(cwd) ?? resolveDslPkg(void 0);
|
|
1233
|
+
return dslPkg === void 0 ? void 0 : join3(dirname(dslPkg), "compile_package.mjs");
|
|
894
1234
|
}
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
const
|
|
898
|
-
if (
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
return void 0;
|
|
1235
|
+
var NO_DSL_REMEDY = "@githolon/dsl not found \u2014 add it to your project's dependencies";
|
|
1236
|
+
function runCompile(args) {
|
|
1237
|
+
const launcher = resolveCompileLauncher(process.cwd());
|
|
1238
|
+
if (launcher === void 0) {
|
|
1239
|
+
process.stderr.write(`error: ${NO_DSL_REMEDY}
|
|
1240
|
+
`);
|
|
1241
|
+
return 1;
|
|
903
1242
|
}
|
|
1243
|
+
const r = spawnSync2(process.execPath, [launcher, ...args], { stdio: "inherit", cwd: process.cwd() });
|
|
1244
|
+
return r.status ?? 1;
|
|
904
1245
|
}
|
|
905
|
-
function
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
return
|
|
1246
|
+
function compileAsync(cwd, args = []) {
|
|
1247
|
+
const launcher = resolveCompileLauncher(cwd);
|
|
1248
|
+
const t0 = performance.now();
|
|
1249
|
+
if (launcher === void 0) {
|
|
1250
|
+
return Promise.resolve({ ok: false, ms: 0, output: NO_DSL_REMEDY });
|
|
910
1251
|
}
|
|
911
|
-
|
|
912
|
-
|
|
1252
|
+
return new Promise((resolveRun) => {
|
|
1253
|
+
const chunks = [];
|
|
1254
|
+
const dec3 = new TextDecoder();
|
|
1255
|
+
const child = spawn(process.execPath, [launcher, ...args], { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
1256
|
+
child.stdout?.on("data", (c) => chunks.push(dec3.decode(c)));
|
|
1257
|
+
child.stderr?.on("data", (c) => chunks.push(dec3.decode(c)));
|
|
1258
|
+
child.on("error", (e) => resolveRun({ ok: false, ms: performance.now() - t0, output: String(e) }));
|
|
1259
|
+
child.on(
|
|
1260
|
+
"close",
|
|
1261
|
+
(code) => resolveRun({ ok: code === 0, ms: performance.now() - t0, output: chunks.join("") })
|
|
1262
|
+
);
|
|
1263
|
+
});
|
|
913
1264
|
}
|
|
914
1265
|
|
|
1266
|
+
// src/dev.ts
|
|
1267
|
+
init_engine();
|
|
1268
|
+
init_cloud();
|
|
1269
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, watch } from "node:fs";
|
|
1270
|
+
import { createServer } from "node:http";
|
|
1271
|
+
import { dirname as dirname4, join as join7, resolve } from "node:path";
|
|
1272
|
+
import { pathToFileURL as pathToFileURL3 } from "node:url";
|
|
1273
|
+
init_local_holon();
|
|
1274
|
+
|
|
915
1275
|
// src/project.ts
|
|
916
1276
|
import { existsSync as existsSync4, statSync as statSync2 } from "node:fs";
|
|
917
1277
|
import { join as join5 } from "node:path";
|
|
@@ -997,89 +1357,10 @@ async function resolveWorkspace(explicit, cwd, target, usageHint) {
|
|
|
997
1357
|
return resolveWorkspaceDecl(project.workspace, target, project.configPath);
|
|
998
1358
|
}
|
|
999
1359
|
|
|
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
1360
|
// src/dev.ts
|
|
1081
|
-
|
|
1082
|
-
var
|
|
1361
|
+
init_proof_offline();
|
|
1362
|
+
var out4 = (s) => void process.stdout.write(s + "\n");
|
|
1363
|
+
var err4 = (s) => void process.stderr.write("error: " + s + "\n");
|
|
1083
1364
|
var WS = "dev";
|
|
1084
1365
|
var DEFAULT_PORT = 7700;
|
|
1085
1366
|
var DEBOUNCE_MS = 200;
|
|
@@ -1163,11 +1444,11 @@ function startServer(s) {
|
|
|
1163
1444
|
});
|
|
1164
1445
|
}
|
|
1165
1446
|
function readDeployBody(cwd, packageName) {
|
|
1166
|
-
const file =
|
|
1167
|
-
if (!
|
|
1447
|
+
const file = join7(cwd, "build", `${packageName}.deploy.json`);
|
|
1448
|
+
if (!existsSync6(file)) {
|
|
1168
1449
|
throw new Error(`compile produced no ${file} \u2014 check the compile output above`);
|
|
1169
1450
|
}
|
|
1170
|
-
return JSON.parse(
|
|
1451
|
+
return JSON.parse(readFileSync4(file, "utf8"));
|
|
1171
1452
|
}
|
|
1172
1453
|
async function installLaw(s, deployBody, installedBy) {
|
|
1173
1454
|
const newHash = await sha256hex(deployBody.packageUsda);
|
|
@@ -1186,11 +1467,11 @@ async function installLaw(s, deployBody, installedBy) {
|
|
|
1186
1467
|
return { ok: true };
|
|
1187
1468
|
}
|
|
1188
1469
|
async function runProofCycle(s, cwd, deployBody, installedBy) {
|
|
1189
|
-
const proofPath =
|
|
1190
|
-
if (!
|
|
1470
|
+
const proofPath = join7(cwd, "build", `${s.packageName}.proof.mts`);
|
|
1471
|
+
if (!existsSync6(proofPath)) return void 0;
|
|
1191
1472
|
const scratch = `proof-${s.cycle}`;
|
|
1192
1473
|
try {
|
|
1193
|
-
const legs = parseOfflineLegs(
|
|
1474
|
+
const legs = parseOfflineLegs(readFileSync4(proofPath, "utf8"));
|
|
1194
1475
|
await mountFresh(s.eng, scratch);
|
|
1195
1476
|
author(s.eng, scratch, "bootstrap", "installDomain", installPayload(s.eng.hashes.nomos, s.eng.nomosPkg, installedBy), "");
|
|
1196
1477
|
author(s.eng, scratch, "nomos", "installDomain", installPayload(s.lawHash, deployBody.packageUsda, installedBy), s.eng.hashes.nomos);
|
|
@@ -1205,21 +1486,21 @@ async function dev(opts) {
|
|
|
1205
1486
|
const cwd = process.cwd();
|
|
1206
1487
|
const cloud = cloudBase(opts.cloud);
|
|
1207
1488
|
const project = await loadProject(cwd).catch((e) => {
|
|
1208
|
-
|
|
1489
|
+
err4(e.message);
|
|
1209
1490
|
return void 0;
|
|
1210
1491
|
});
|
|
1211
1492
|
if (project === void 0) {
|
|
1212
|
-
if (!
|
|
1213
|
-
|
|
1493
|
+
if (!existsSync6(join7(cwd, CONFIG_FILE))) {
|
|
1494
|
+
err4(`no ${CONFIG_FILE} here (${cwd}) \u2014 \`githolon dev\` runs inside a project; scaffold one from examples/guestbook or \`githolon generate domain <name>\``);
|
|
1214
1495
|
}
|
|
1215
1496
|
return 1;
|
|
1216
1497
|
}
|
|
1217
1498
|
const creds = loadCreds();
|
|
1218
1499
|
const installedBy = creds.session?.uid ?? creds.principal ?? "githolon-dev";
|
|
1219
|
-
|
|
1500
|
+
out4(`githolon dev \u2014 ${project.name}: compiling\u2026`);
|
|
1220
1501
|
const firstCompile = await compileAsync(cwd);
|
|
1221
1502
|
if (!firstCompile.ok) {
|
|
1222
|
-
|
|
1503
|
+
err4(`compile failed \u2014 the dev loop needs one green compile to mint the holon:
|
|
1223
1504
|
${firstCompile.output}`);
|
|
1224
1505
|
return 1;
|
|
1225
1506
|
}
|
|
@@ -1227,14 +1508,14 @@ ${firstCompile.output}`);
|
|
|
1227
1508
|
try {
|
|
1228
1509
|
deployBody = readDeployBody(cwd, project.name);
|
|
1229
1510
|
} catch (e) {
|
|
1230
|
-
|
|
1511
|
+
err4(e.message);
|
|
1231
1512
|
return 1;
|
|
1232
1513
|
}
|
|
1233
1514
|
let engBoot;
|
|
1234
1515
|
try {
|
|
1235
1516
|
engBoot = await holonEngine(cloud, deployBody, installedBy);
|
|
1236
1517
|
} catch (e) {
|
|
1237
|
-
|
|
1518
|
+
err4(`cannot boot the local holon: ${String(e.message ?? e)}
|
|
1238
1519
|
the runtime rides ${cloud}/v1/runtime/* and is cached in ~/.holon/runtime after one fetch \u2014 connect once, then dev runs offline`);
|
|
1239
1520
|
return 1;
|
|
1240
1521
|
}
|
|
@@ -1257,31 +1538,31 @@ ${firstCompile.output}`);
|
|
|
1257
1538
|
s.intents++;
|
|
1258
1539
|
const installed = await installLaw(s, deployBody, installedBy);
|
|
1259
1540
|
if (!installed.ok) {
|
|
1260
|
-
|
|
1541
|
+
err4(installed.error ?? "law install failed");
|
|
1261
1542
|
return 1;
|
|
1262
1543
|
}
|
|
1263
1544
|
s.proof = await runProofCycle(s, cwd, deployBody, installedBy);
|
|
1264
1545
|
if (opts.once === true) {
|
|
1265
|
-
|
|
1546
|
+
out4(statusScreen({ ...s, watchRoots: ["(--once: no watch)"] }));
|
|
1266
1547
|
return s.proof === void 0 || s.proof.ok ? 0 : 1;
|
|
1267
1548
|
}
|
|
1268
1549
|
let server;
|
|
1269
1550
|
try {
|
|
1270
1551
|
server = await startServer(s);
|
|
1271
1552
|
} catch (e) {
|
|
1272
|
-
|
|
1553
|
+
err4(e.message);
|
|
1273
1554
|
return 1;
|
|
1274
1555
|
}
|
|
1275
1556
|
const moduleDirs = /* @__PURE__ */ new Set();
|
|
1276
1557
|
try {
|
|
1277
1558
|
const cfgModule = await import(pathToFileURL3(project.configPath).href);
|
|
1278
1559
|
for (const d of cfgModule.default?.domains ?? []) {
|
|
1279
|
-
for (const m of d.modules ?? []) moduleDirs.add(
|
|
1560
|
+
for (const m of d.modules ?? []) moduleDirs.add(dirname4(resolve(cwd, m)));
|
|
1280
1561
|
}
|
|
1281
1562
|
} catch {
|
|
1282
|
-
moduleDirs.add(
|
|
1563
|
+
moduleDirs.add(join7(cwd, "domains"));
|
|
1283
1564
|
}
|
|
1284
|
-
if (moduleDirs.size === 0) moduleDirs.add(
|
|
1565
|
+
if (moduleDirs.size === 0) moduleDirs.add(join7(cwd, "domains"));
|
|
1285
1566
|
let timer;
|
|
1286
1567
|
let cycling = false;
|
|
1287
1568
|
let queued = false;
|
|
@@ -1297,7 +1578,7 @@ ${firstCompile.output}`);
|
|
|
1297
1578
|
s.compileMs = run.ms;
|
|
1298
1579
|
if (!run.ok) {
|
|
1299
1580
|
s.lastError = run.output.trim() || "compile failed";
|
|
1300
|
-
|
|
1581
|
+
out4(`\u2717 cycle ${s.cycle}: compile failed \u2014 the previous law keeps serving
|
|
1301
1582
|
${run.output}`);
|
|
1302
1583
|
} else {
|
|
1303
1584
|
s.lastError = void 0;
|
|
@@ -1306,16 +1587,16 @@ ${run.output}`);
|
|
|
1306
1587
|
const inst = await installLaw(s, body, installedBy);
|
|
1307
1588
|
if (!inst.ok) {
|
|
1308
1589
|
s.lastError = inst.error ?? "install failed";
|
|
1309
|
-
|
|
1590
|
+
out4(`\u2717 cycle ${s.cycle}: ${s.lastError}`);
|
|
1310
1591
|
} else {
|
|
1311
1592
|
s.proof = await runProofCycle(s, cwd, body, installedBy);
|
|
1312
1593
|
const total = performance.now() - t0;
|
|
1313
|
-
|
|
1314
|
-
|
|
1594
|
+
out4(statusScreen(s));
|
|
1595
|
+
out4(` cycle edit \u2192 law-live \u2192 proof in ${total.toFixed(0)} ms total`);
|
|
1315
1596
|
}
|
|
1316
1597
|
} catch (e) {
|
|
1317
1598
|
s.lastError = String(e.message ?? e);
|
|
1318
|
-
|
|
1599
|
+
out4(`\u2717 cycle ${s.cycle}: ${s.lastError}`);
|
|
1319
1600
|
}
|
|
1320
1601
|
}
|
|
1321
1602
|
cycling = false;
|
|
@@ -1331,7 +1612,7 @@ ${run.output}`);
|
|
|
1331
1612
|
};
|
|
1332
1613
|
const watchers = [];
|
|
1333
1614
|
for (const d of moduleDirs) {
|
|
1334
|
-
if (!
|
|
1615
|
+
if (!existsSync6(d)) continue;
|
|
1335
1616
|
watchers.push(watch(d, { recursive: true }, (_evt, f) => onChange(f)));
|
|
1336
1617
|
s.watchRoots.push(d.startsWith(cwd) ? d.slice(cwd.length + 1) + "/" : d);
|
|
1337
1618
|
}
|
|
@@ -1341,8 +1622,8 @@ ${run.output}`);
|
|
|
1341
1622
|
})
|
|
1342
1623
|
);
|
|
1343
1624
|
s.watchRoots.push(CONFIG_FILE);
|
|
1344
|
-
|
|
1345
|
-
|
|
1625
|
+
out4(statusScreen(s));
|
|
1626
|
+
out4(" (ctrl-c stops; edits in the watched paths recompile + re-prove automatically)");
|
|
1346
1627
|
return new Promise((resolveExit) => {
|
|
1347
1628
|
const stop = () => {
|
|
1348
1629
|
for (const w of watchers) w.close();
|
|
@@ -1355,13 +1636,14 @@ ${run.output}`);
|
|
|
1355
1636
|
}
|
|
1356
1637
|
|
|
1357
1638
|
// src/git.ts
|
|
1358
|
-
|
|
1639
|
+
init_cloud();
|
|
1640
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
1359
1641
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
1360
|
-
import { readFileSync as
|
|
1361
|
-
var
|
|
1362
|
-
var
|
|
1642
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
1643
|
+
var out5 = (s) => void process.stdout.write(s + "\n");
|
|
1644
|
+
var err5 = (s) => void process.stderr.write("error: " + s + "\n");
|
|
1363
1645
|
function git2(args, opts = {}) {
|
|
1364
|
-
const r =
|
|
1646
|
+
const r = spawnSync3("git", args, { stdio: opts.quiet === true ? "ignore" : "inherit" });
|
|
1365
1647
|
return r.status ?? 1;
|
|
1366
1648
|
}
|
|
1367
1649
|
function ensureSecret(cloud, ws) {
|
|
@@ -1386,36 +1668,36 @@ function workspaceFromPath(path) {
|
|
|
1386
1668
|
}
|
|
1387
1669
|
async function gitCredential(action) {
|
|
1388
1670
|
if (action !== "get") return 0;
|
|
1389
|
-
const kv = parseCredentialInput(
|
|
1671
|
+
const kv = parseCredentialInput(readFileSync5(0, "utf8"));
|
|
1390
1672
|
const ws = workspaceFromPath(kv["path"]);
|
|
1391
1673
|
if (kv["protocol"] !== "https" || ws === void 0) return 0;
|
|
1392
1674
|
const cloud = `https://${kv["host"]}`;
|
|
1393
1675
|
const token = await sessionToken().catch(() => void 0);
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1676
|
+
out5(`username=${token ?? "nomos"}`);
|
|
1677
|
+
out5(`password=${ensureSecret(cloud, ws)}`);
|
|
1678
|
+
out5("");
|
|
1397
1679
|
return 0;
|
|
1398
1680
|
}
|
|
1399
1681
|
async function gitSetup(opts) {
|
|
1400
1682
|
const cloud = cloudBase(opts.cloud);
|
|
1401
1683
|
const self = process.argv[1];
|
|
1402
1684
|
if (self === void 0) {
|
|
1403
|
-
|
|
1685
|
+
err5("cannot locate the githolon CLI entry to install as a credential helper");
|
|
1404
1686
|
return 1;
|
|
1405
1687
|
}
|
|
1406
1688
|
const helper = `!"${process.execPath}" "${self}" git-credential`;
|
|
1407
1689
|
if (git2(["config", "--global", `credential.${cloud}.helper`, helper]) !== 0 || git2(["config", "--global", `credential.${cloud}.useHttpPath`, "true"]) !== 0) {
|
|
1408
|
-
|
|
1690
|
+
err5("git config failed \u2014 is git installed?");
|
|
1409
1691
|
return 1;
|
|
1410
1692
|
}
|
|
1411
|
-
|
|
1412
|
-
|
|
1693
|
+
out5(`\u2713 git credential helper installed for ${cloud} (global gitconfig)`);
|
|
1694
|
+
out5(` pushes/clones of ${cloud}/v1/workspaces/<ws>/git now authenticate from ~/.holon`);
|
|
1413
1695
|
return 0;
|
|
1414
1696
|
}
|
|
1415
1697
|
async function gitRemote(ws, name, opts) {
|
|
1416
1698
|
const cloud = cloudBase(opts.cloud);
|
|
1417
1699
|
if (git2(["rev-parse", "--git-dir"], { quiet: true }) !== 0) {
|
|
1418
|
-
|
|
1700
|
+
err5("not a git repository \u2014 run inside your repo (git init first)");
|
|
1419
1701
|
return 1;
|
|
1420
1702
|
}
|
|
1421
1703
|
const setupRc = await gitSetup(opts);
|
|
@@ -1424,7 +1706,7 @@ async function gitRemote(ws, name, opts) {
|
|
|
1424
1706
|
const url = `${cloud}/v1/workspaces/${ws}/git`;
|
|
1425
1707
|
if (git2(["remote", "add", name, url], { quiet: true }) !== 0) {
|
|
1426
1708
|
if (git2(["remote", "set-url", name, url], { quiet: true }) !== 0) {
|
|
1427
|
-
|
|
1709
|
+
err5(`could not add or update remote '${name}'`);
|
|
1428
1710
|
return 1;
|
|
1429
1711
|
}
|
|
1430
1712
|
}
|
|
@@ -1432,7 +1714,7 @@ async function gitRemote(ws, name, opts) {
|
|
|
1432
1714
|
if (token === void 0) {
|
|
1433
1715
|
const principal = loadCreds().principal;
|
|
1434
1716
|
if (principal === void 0) {
|
|
1435
|
-
|
|
1717
|
+
err5(
|
|
1436
1718
|
`remote '${name}' \u2192 ${url} is wired, but a BIRTH push needs a principal:
|
|
1437
1719
|
githolon login --agent (then just push)
|
|
1438
1720
|
\u2014 or re-run after githolon ws create/login so a principal is on file`
|
|
@@ -1440,13 +1722,13 @@ async function gitRemote(ws, name, opts) {
|
|
|
1440
1722
|
return 1;
|
|
1441
1723
|
}
|
|
1442
1724
|
if (git2(["config", `http.${url}.extraHeader`, `x-nomos-principal: ${principal}`]) !== 0) {
|
|
1443
|
-
|
|
1725
|
+
err5("git config failed setting the principal header");
|
|
1444
1726
|
return 1;
|
|
1445
1727
|
}
|
|
1446
1728
|
}
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1729
|
+
out5(`\u2713 remote '${name}' \u2192 ${url}`);
|
|
1730
|
+
out5(` workspace secret on file; ${token !== void 0 ? "births ride your session (verified)" : "births ride x-nomos-principal (bare uid)"}`);
|
|
1731
|
+
out5(` birth/deploy by push: git push ${name} main`);
|
|
1450
1732
|
return 0;
|
|
1451
1733
|
}
|
|
1452
1734
|
|
|
@@ -1462,9 +1744,9 @@ var HELP = {
|
|
|
1462
1744
|
examples: ["githolon compile", "githolon compile nomos.package.mjs --layered"]
|
|
1463
1745
|
},
|
|
1464
1746
|
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
|
|
1747
|
+
usage: "githolon proof [build/<name>.proof.mts] [--live] [--keep]",
|
|
1748
|
+
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). --live runs the full cloud loop (throwaway workspace \u2192\ndeploy \u2192 offline write \u2192 admission \u2192 cloud reads \u2192 convergence), RETIRED on every exit;\n--keep keeps it and prints the secret once. ALL GREEN or it names the jam. The offline legs\nalso rerun on every save in `githolon dev`.",
|
|
1749
|
+
examples: ["githolon proof", "githolon proof --live", "githolon proof --live --keep"]
|
|
1468
1750
|
},
|
|
1469
1751
|
dev: {
|
|
1470
1752
|
usage: "githolon dev [--port <n>] [--cloud <url>] [--once]",
|
|
@@ -1550,6 +1832,22 @@ var HELP = {
|
|
|
1550
1832
|
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
1833
|
examples: ["githolon secret set my-ws nws_v1_\u2026", "githolon secret rotate my-ws"]
|
|
1552
1834
|
},
|
|
1835
|
+
capability: {
|
|
1836
|
+
usage: "githolon capability <bind|rotate|unbind> <ws> <capability> | list <ws> [--from-gcloud <name>] [--scope <s>] [--expires-at <iso|ms>] [--cloud <url>]",
|
|
1837
|
+
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.",
|
|
1838
|
+
flags: [
|
|
1839
|
+
["--from-gcloud <name>", "read the value from `gcloud secrets versions access latest --secret=<name>`"],
|
|
1840
|
+
["--scope <s>", "a narrowing label recorded on the law fact (e.g. read-only)"],
|
|
1841
|
+
["--expires-at <t>", "expiry recorded on the law fact (ISO date/datetime or epoch-ms; fail-closed at the host)"],
|
|
1842
|
+
["--cloud <url>", "target cloud (default: $NOMOS_CLOUD or the public cloud)"]
|
|
1843
|
+
],
|
|
1844
|
+
examples: [
|
|
1845
|
+
"cat token.txt | githolon capability bind nomos-usage cf-analytics",
|
|
1846
|
+
"githolon capability bind my-ws openai --from-gcloud openai-api-key --scope read-only",
|
|
1847
|
+
"githolon capability list nomos-usage",
|
|
1848
|
+
"githolon capability rotate nomos-usage cf-analytics --from-gcloud cf-analytics-token"
|
|
1849
|
+
]
|
|
1850
|
+
},
|
|
1553
1851
|
git: {
|
|
1554
1852
|
usage: "githolon git <setup | remote [<ws>] [<remoteName>]> [--cloud <url>] [--target <name>]",
|
|
1555
1853
|
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 +1876,37 @@ function commandHelp(cmd) {
|
|
|
1578
1876
|
}
|
|
1579
1877
|
|
|
1580
1878
|
// src/ledger.ts
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1879
|
+
init_engine();
|
|
1880
|
+
init_cloud();
|
|
1881
|
+
init_local_holon();
|
|
1882
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
1883
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1884
|
+
import { join as join8 } from "node:path";
|
|
1885
|
+
var out6 = (s) => void process.stdout.write(s + "\n");
|
|
1886
|
+
var err6 = (s) => void process.stderr.write("error: " + s + "\n");
|
|
1586
1887
|
var WS2 = "local";
|
|
1587
1888
|
async function ledgerInit(dir, opts) {
|
|
1588
1889
|
const cloud = cloudBase(opts.cloud);
|
|
1589
|
-
if (
|
|
1590
|
-
|
|
1890
|
+
if (existsSync7(dir) && readdirSync3(dir).length > 0) {
|
|
1891
|
+
err6(`refusing to mint into non-empty directory: ${dir}`);
|
|
1591
1892
|
return 1;
|
|
1592
1893
|
}
|
|
1593
1894
|
let deployFile;
|
|
1594
1895
|
try {
|
|
1595
1896
|
deployFile = discoverDeployJson(process.cwd(), opts.file);
|
|
1596
1897
|
} catch (e) {
|
|
1597
|
-
|
|
1898
|
+
err6(e.message);
|
|
1598
1899
|
return 1;
|
|
1599
1900
|
}
|
|
1600
|
-
const deployBody = JSON.parse(
|
|
1901
|
+
const deployBody = JSON.parse(readFileSync6(deployFile, "utf8"));
|
|
1601
1902
|
const domainHash = await sha256hex(deployBody.packageUsda);
|
|
1602
1903
|
const c = loadCreds();
|
|
1603
1904
|
const principal = await sessionToken().catch(() => void 0) !== void 0 ? c.session.uid : c.principal;
|
|
1604
1905
|
if (principal === void 0) {
|
|
1605
|
-
|
|
1906
|
+
err6("a holon is born OF someone \u2014 githolon login --agent first (or githolon ws create --principal <uid> once)");
|
|
1606
1907
|
return 1;
|
|
1607
1908
|
}
|
|
1608
|
-
|
|
1909
|
+
out6(`minting a holon locally (law ${domainHash.slice(0, 12)}\u2026, installedBy ${principal})`);
|
|
1609
1910
|
const { eng } = await holonEngine(cloud, deployBody, principal);
|
|
1610
1911
|
await mountFresh(eng, WS2);
|
|
1611
1912
|
author(eng, WS2, "bootstrap", "installDomain", installPayload(eng.hashes.nomos, eng.nomosPkg, principal), "");
|
|
@@ -1613,21 +1914,21 @@ async function ledgerInit(dir, opts) {
|
|
|
1613
1914
|
const verdict = verifyChainLocal(eng, WS2);
|
|
1614
1915
|
if (!printVerdict(verdict)) return 1;
|
|
1615
1916
|
const gitTree = eng.preopen.dir.contents.get("ws").contents.get(WS2).contents.get("nomos.git");
|
|
1616
|
-
const gitDir =
|
|
1917
|
+
const gitDir = join8(dir, ".git");
|
|
1617
1918
|
writeTreeToDisk(gitTree, gitDir);
|
|
1618
|
-
const cfgPath =
|
|
1619
|
-
writeFileSync4(cfgPath,
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1919
|
+
const cfgPath = join8(gitDir, "config");
|
|
1920
|
+
writeFileSync4(cfgPath, readFileSync6(cfgPath, "utf8").replace(/bare = true/, "bare = false"), "utf8");
|
|
1921
|
+
spawnSync4("git", ["-C", dir, "reset", "--hard", "main"], { stdio: "ignore" });
|
|
1922
|
+
out6(`\u2713 holon written \u2192 ${dir} (a normal git repo; git log IS the audit trail)`);
|
|
1923
|
+
out6(`
|
|
1623
1924
|
Birth it on Nomos Cloud (the cloud re-derives this exact verdict):`);
|
|
1624
|
-
|
|
1925
|
+
out6(` cd ${dir} && npx githolon git remote <ws> && git push nomos main`);
|
|
1625
1926
|
return 0;
|
|
1626
1927
|
}
|
|
1627
1928
|
async function ledgerVerify(dir, opts) {
|
|
1628
|
-
const gitDir =
|
|
1629
|
-
if (!
|
|
1630
|
-
|
|
1929
|
+
const gitDir = existsSync7(join8(dir, ".git")) ? join8(dir, ".git") : dir;
|
|
1930
|
+
if (!existsSync7(join8(gitDir, "HEAD"))) {
|
|
1931
|
+
err6(`${dir} is not a git repo (no HEAD)`);
|
|
1631
1932
|
return 1;
|
|
1632
1933
|
}
|
|
1633
1934
|
const { eng } = await holonEngine(cloudBase(opts.cloud), void 0, "verify");
|
|
@@ -1638,11 +1939,15 @@ async function ledgerVerify(dir, opts) {
|
|
|
1638
1939
|
}
|
|
1639
1940
|
|
|
1640
1941
|
// src/replay.ts
|
|
1641
|
-
|
|
1642
|
-
|
|
1942
|
+
init_engine();
|
|
1943
|
+
init_engine();
|
|
1944
|
+
init_cloud();
|
|
1945
|
+
init_local_holon();
|
|
1946
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
|
|
1947
|
+
import { join as join9 } from "node:path";
|
|
1643
1948
|
import git3 from "isomorphic-git";
|
|
1644
|
-
var
|
|
1645
|
-
var
|
|
1949
|
+
var out7 = (s) => void process.stdout.write(s + "\n");
|
|
1950
|
+
var err7 = (s) => void process.stderr.write("error: " + s + "\n");
|
|
1646
1951
|
var SOURCE = "source";
|
|
1647
1952
|
var REPLAY = "replay";
|
|
1648
1953
|
function summarizePayload(payload, max = 100) {
|
|
@@ -1678,9 +1983,9 @@ function capJsonStrings(v, max = 2048) {
|
|
|
1678
1983
|
if (typeof v === "string") return v.length > max ? `${v.slice(0, max)}\u2026(${v.length} chars)` : v;
|
|
1679
1984
|
if (Array.isArray(v)) return v.map((x) => capJsonStrings(x, max));
|
|
1680
1985
|
if (typeof v === "object" && v !== null) {
|
|
1681
|
-
const
|
|
1682
|
-
for (const [k, x] of Object.entries(v))
|
|
1683
|
-
return
|
|
1986
|
+
const out9 = {};
|
|
1987
|
+
for (const [k, x] of Object.entries(v)) out9[k] = capJsonStrings(x, max);
|
|
1988
|
+
return out9;
|
|
1684
1989
|
}
|
|
1685
1990
|
return v;
|
|
1686
1991
|
}
|
|
@@ -1727,10 +2032,10 @@ function readKey() {
|
|
|
1727
2032
|
async function replay(target, opts) {
|
|
1728
2033
|
const cloud = cloudBase(opts.cloud);
|
|
1729
2034
|
const json = opts.json === true;
|
|
1730
|
-
const emit = (o) =>
|
|
2035
|
+
const emit = (o) => out7(JSON.stringify(o));
|
|
1731
2036
|
let overlay;
|
|
1732
2037
|
try {
|
|
1733
|
-
overlay = JSON.parse(
|
|
2038
|
+
overlay = JSON.parse(readFileSync7(discoverDeployJson(process.cwd(), opts.file), "utf8"));
|
|
1734
2039
|
} catch {
|
|
1735
2040
|
overlay = void 0;
|
|
1736
2041
|
}
|
|
@@ -1738,15 +2043,15 @@ async function replay(target, opts) {
|
|
|
1738
2043
|
try {
|
|
1739
2044
|
({ eng } = await holonEngine(cloud, overlay, "githolon-replay"));
|
|
1740
2045
|
} catch (e) {
|
|
1741
|
-
|
|
2046
|
+
err7(`cannot boot the local holon: ${String(e.message ?? e)}
|
|
1742
2047
|
the runtime rides ${cloud}/v1/runtime/* (cached in ~/.holon/runtime after one fetch)`);
|
|
1743
2048
|
return 1;
|
|
1744
2049
|
}
|
|
1745
|
-
const isDir =
|
|
2050
|
+
const isDir = existsSync8(target) || target.includes("/") || target.startsWith(".");
|
|
1746
2051
|
if (isDir) {
|
|
1747
|
-
const gitDir =
|
|
1748
|
-
if (!
|
|
1749
|
-
|
|
2052
|
+
const gitDir = existsSync8(join9(target, ".git")) ? join9(target, ".git") : target;
|
|
2053
|
+
if (!existsSync8(join9(gitDir, "HEAD"))) {
|
|
2054
|
+
err7(`${target} is not a git repo (no HEAD) \u2014 pass a holon directory (githolon ledger init <dir>) or a workspace name`);
|
|
1750
2055
|
return 1;
|
|
1751
2056
|
}
|
|
1752
2057
|
await mountFresh(eng, SOURCE);
|
|
@@ -1754,7 +2059,7 @@ async function replay(target, opts) {
|
|
|
1754
2059
|
} else {
|
|
1755
2060
|
const m = await mountWorkspace(eng, SOURCE, { remote: `${cloud}/v1/workspaces/${target}/git`, headers: {} });
|
|
1756
2061
|
if (m.restored !== true) {
|
|
1757
|
-
|
|
2062
|
+
err7(
|
|
1758
2063
|
`workspace '${target}' has no refs/heads/main on ${cloud} \u2014 nothing to replay` + (m.restoreError ? ` (${String(m.restoreError).split(" | ")[0]})` : "") + `
|
|
1759
2064
|
check the name (githolon ws status ${target}), or pass a local holon directory instead`
|
|
1760
2065
|
);
|
|
@@ -1763,7 +2068,7 @@ async function replay(target, opts) {
|
|
|
1763
2068
|
}
|
|
1764
2069
|
const chain = await walkChain(eng, SOURCE);
|
|
1765
2070
|
if (chain.length === 0) {
|
|
1766
|
-
|
|
2071
|
+
err7(`the chain on '${target}' carries no intents \u2014 nothing to replay`);
|
|
1767
2072
|
return 1;
|
|
1768
2073
|
}
|
|
1769
2074
|
await mountFresh(eng, REPLAY);
|
|
@@ -1772,8 +2077,8 @@ async function replay(target, opts) {
|
|
|
1772
2077
|
const tallies = /* @__PURE__ */ new Map();
|
|
1773
2078
|
let autoRun = !interactive;
|
|
1774
2079
|
if (!json) {
|
|
1775
|
-
|
|
1776
|
-
if (interactive)
|
|
2080
|
+
out7(`replaying ${chain.length} intent(s) from ${isDir ? target : `${cloud} :: ${target}`} \u2014 every step re-admitted through the wasm gate`);
|
|
2081
|
+
if (interactive) out7(" keys: enter = next intent \xB7 a = run to the end \xB7 q = quit\n");
|
|
1777
2082
|
}
|
|
1778
2083
|
for (let i = 0; i < chain.length; i++) {
|
|
1779
2084
|
const { oid, bytes, doc } = chain[i];
|
|
@@ -1787,8 +2092,8 @@ async function replay(target, opts) {
|
|
|
1787
2092
|
if (res.ok === false) {
|
|
1788
2093
|
const msg = `replay REFUSED at intent ${i} (${domain}/${directive}, ${oid.slice(0, 10)}): ${res.error ?? "unknown"}`;
|
|
1789
2094
|
if (json) emit({ step: i, oid, domain, directive, refused: true, error: res.error ?? "unknown" });
|
|
1790
|
-
else
|
|
1791
|
-
|
|
2095
|
+
else err7(msg);
|
|
2096
|
+
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
2097
|
printVerdict(verifyChainLocal(eng, SOURCE));
|
|
1793
2098
|
return 1;
|
|
1794
2099
|
}
|
|
@@ -1809,31 +2114,31 @@ async function replay(target, opts) {
|
|
|
1809
2114
|
tallies: Object.fromEntries(tallies)
|
|
1810
2115
|
});
|
|
1811
2116
|
} else {
|
|
1812
|
-
|
|
1813
|
-
|
|
2117
|
+
out7(`[${String(i + 1).padStart(String(chain.length).length)}/${chain.length}] ${domain}/${directive} (${oid.slice(0, 10)}, ${ms.toFixed(0)} ms)`);
|
|
2118
|
+
out7(` payload ${summarizePayload(doc.payload?.payload)}`);
|
|
1814
2119
|
for (const ev of doc.events ?? []) {
|
|
1815
2120
|
const id = ev.aggregate ?? "?";
|
|
1816
2121
|
const was = before.get(id);
|
|
1817
2122
|
const verb = ev.marker === "Create" ? "+ created" : ev.marker === "Delete" ? "- deleted" : was === void 0 ? "~ touched" : "~ updated";
|
|
1818
|
-
|
|
2123
|
+
out7(` ${verb} ${id}`);
|
|
1819
2124
|
const ops = summarizeOps(ev);
|
|
1820
|
-
if (ops.length > 0)
|
|
2125
|
+
if (ops.length > 0) out7(` ${ops}`);
|
|
1821
2126
|
}
|
|
1822
2127
|
const tallyLine = [...tallies.entries()].map(([t, n]) => `${t}:${n}`).join(" ");
|
|
1823
2128
|
const showTally = stepEvery > 0 ? (i + 1) % stepEvery === 0 || i === chain.length - 1 : true;
|
|
1824
|
-
if (showTally && tallyLine.length > 0)
|
|
2129
|
+
if (showTally && tallyLine.length > 0) out7(` rows ${tallyLine}`);
|
|
1825
2130
|
}
|
|
1826
2131
|
if (interactive && !autoRun && i < chain.length - 1) {
|
|
1827
2132
|
const key = await readKey();
|
|
1828
2133
|
if (key === "quit") {
|
|
1829
|
-
|
|
2134
|
+
out7(`
|
|
1830
2135
|
stopped at intent ${i + 1}/${chain.length} \u2014 the verify verdict below covers the WHOLE chain as delivered`);
|
|
1831
2136
|
break;
|
|
1832
2137
|
}
|
|
1833
2138
|
if (key === "all") autoRun = true;
|
|
1834
2139
|
}
|
|
1835
2140
|
}
|
|
1836
|
-
if (!json)
|
|
2141
|
+
if (!json) out7("");
|
|
1837
2142
|
const verdict = verifyChainLocal(eng, SOURCE);
|
|
1838
2143
|
if (json) {
|
|
1839
2144
|
emit({ finale: true, verdict });
|
|
@@ -1843,9 +2148,11 @@ stopped at intent ${i + 1}/${chain.length} \u2014 the verify verdict below cover
|
|
|
1843
2148
|
}
|
|
1844
2149
|
|
|
1845
2150
|
// src/status.ts
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
2151
|
+
init_engine();
|
|
2152
|
+
init_cloud();
|
|
2153
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
2154
|
+
var out8 = (s) => void process.stdout.write(s + "\n");
|
|
2155
|
+
var err8 = (s) => void process.stderr.write("error: " + s + "\n");
|
|
1849
2156
|
function lawDiffVerdict(localHash, installed, ws) {
|
|
1850
2157
|
const active = installed.filter((d) => d.phase === "Active" && typeof d.domainHash === "string");
|
|
1851
2158
|
const match = active.find((d) => d.domainHash === localHash);
|
|
@@ -1868,40 +2175,40 @@ async function status(wsArg, opts) {
|
|
|
1868
2175
|
try {
|
|
1869
2176
|
ws = await resolveWorkspace(wsArg, process.cwd(), opts.target, "githolon status <ws>");
|
|
1870
2177
|
} catch (e) {
|
|
1871
|
-
|
|
2178
|
+
err8(e.message);
|
|
1872
2179
|
return 1;
|
|
1873
2180
|
}
|
|
1874
2181
|
let localHash;
|
|
1875
2182
|
try {
|
|
1876
|
-
const body = JSON.parse(
|
|
2183
|
+
const body = JSON.parse(readFileSync8(discoverDeployJson(process.cwd(), opts.file), "utf8"));
|
|
1877
2184
|
if (typeof body.packageUsda === "string") localHash = await sha256hex(body.packageUsda);
|
|
1878
2185
|
} catch {
|
|
1879
2186
|
localHash = void 0;
|
|
1880
2187
|
}
|
|
1881
2188
|
const r = await fetch(`${cloud}/v1/workspaces/${ws}/domains`).catch(() => void 0);
|
|
1882
2189
|
if (r === void 0) {
|
|
1883
|
-
|
|
2190
|
+
err8(`cannot reach ${cloud} \u2014 githolon status compares against the DEPLOYED law and needs the cloud (local-only: githolon dev)`);
|
|
1884
2191
|
return 1;
|
|
1885
2192
|
}
|
|
1886
2193
|
const d = await r.json().catch(() => void 0);
|
|
1887
2194
|
if (!r.ok || d?.ok !== true) {
|
|
1888
|
-
|
|
2195
|
+
err8(`status '${ws}' failed (${r.status}): ${d ? JSON.stringify(d) : "no body"}
|
|
1889
2196
|
is the workspace born? githolon ws create ${ws}`);
|
|
1890
2197
|
return 1;
|
|
1891
2198
|
}
|
|
1892
|
-
|
|
2199
|
+
out8(`workspace ${ws} on ${cloud}`);
|
|
1893
2200
|
const domains = d.domains ?? [];
|
|
1894
|
-
if (domains.length === 0)
|
|
2201
|
+
if (domains.length === 0) out8(`installed (none \u2014 not even the controller; the workspace may be unborn)`);
|
|
1895
2202
|
for (const dom of domains) {
|
|
1896
|
-
|
|
2203
|
+
out8(`installed ${(dom.domainHash ?? "?").slice(0, 16)}\u2026 ${dom.phase ?? "?"} by ${dom.installedBy ?? "?"}`);
|
|
1897
2204
|
}
|
|
1898
2205
|
if (localHash === void 0) {
|
|
1899
|
-
|
|
2206
|
+
out8(`local (no build/*.deploy.json here \u2014 run \`githolon compile\` to build the law this project authors)`);
|
|
1900
2207
|
return 0;
|
|
1901
2208
|
}
|
|
1902
|
-
|
|
2209
|
+
out8(`local ${localHash.slice(0, 16)}\u2026 (build/*.deploy.json)`);
|
|
1903
2210
|
const verdict = lawDiffVerdict(localHash, domains, ws);
|
|
1904
|
-
for (const line of verdict.lines)
|
|
2211
|
+
for (const line of verdict.lines) out8(line);
|
|
1905
2212
|
return verdict.inSync ? 0 : 2;
|
|
1906
2213
|
}
|
|
1907
2214
|
|
|
@@ -1943,6 +2250,15 @@ a bare <ws> resolves from nomos.package.mjs's optional \`workspace\` binding):
|
|
|
1943
2250
|
githolon secret set <ws> <secret> import a secret you already hold
|
|
1944
2251
|
githolon secret rotate <ws> rotate (and re-save) the workspace secret
|
|
1945
2252
|
|
|
2253
|
+
Capability credentials (facts on the ledger, values host-side \u2014 the value NEVER rides argv;
|
|
2254
|
+
stdin or --from-gcloud only; architecture/capability_credentials.md):
|
|
2255
|
+
githolon capability bind <ws> <capability> bind a credential: value from stdin (or
|
|
2256
|
+
--from-gcloud <name>) \u2192 the workspace's holon;
|
|
2257
|
+
sha256(value) \u2192 the law-state binding fact
|
|
2258
|
+
githolon capability list <ws> the binding FACTS (never the values)
|
|
2259
|
+
githolon capability rotate <ws> <capability> revoke + re-bind at epoch+1 (new value)
|
|
2260
|
+
githolon capability unbind <ws> <capability> revoke the fact; the host forgets the value
|
|
2261
|
+
|
|
1946
2262
|
Git lane (stock git push as deploy/birth \u2014 see also: push-to-create):
|
|
1947
2263
|
githolon git setup install the credential helper for the cloud host
|
|
1948
2264
|
githolon git remote [<ws>] [<remoteName>] wire this repo to a workspace (default name: nomos)
|
|
@@ -2021,23 +2337,58 @@ ${USAGE}`);
|
|
|
2021
2337
|
if (sub === "rotate" && ws !== void 0) return secretRotate(ws, opts);
|
|
2022
2338
|
return usageFail("expected: githolon secret <set <ws> <secret> | rotate <ws>>");
|
|
2023
2339
|
}
|
|
2340
|
+
async function runCapability(argv) {
|
|
2341
|
+
const usageFail = (m) => {
|
|
2342
|
+
process.stderr.write(`error: ${m}
|
|
2343
|
+
|
|
2344
|
+
${commandHelp("capability")}`);
|
|
2345
|
+
return 1;
|
|
2346
|
+
};
|
|
2347
|
+
const pos = [];
|
|
2348
|
+
const opts = {};
|
|
2349
|
+
const rest = argv.slice(1);
|
|
2350
|
+
for (let i = 0; i < rest.length; i++) {
|
|
2351
|
+
const a = rest[i];
|
|
2352
|
+
if (a === "--cloud" || a === "--from-gcloud" || a === "--scope" || a === "--expires-at") {
|
|
2353
|
+
const v = rest[++i];
|
|
2354
|
+
if (v === void 0) return usageFail(`${a} requires a value`);
|
|
2355
|
+
if (a === "--cloud") opts.cloud = v;
|
|
2356
|
+
else if (a === "--from-gcloud") opts.fromGcloud = v;
|
|
2357
|
+
else if (a === "--scope") opts.scope = v;
|
|
2358
|
+
else opts.expiresAt = v;
|
|
2359
|
+
} else if (a.startsWith("--")) {
|
|
2360
|
+
return usageFail(`unknown flag '${a}'`);
|
|
2361
|
+
} else {
|
|
2362
|
+
pos.push(a);
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
const [sub, ws, capability] = pos;
|
|
2366
|
+
if (sub === "bind" && ws !== void 0 && capability !== void 0) return capabilityBind(ws, capability, opts);
|
|
2367
|
+
if (sub === "rotate" && ws !== void 0 && capability !== void 0) return capabilityRotate(ws, capability, opts);
|
|
2368
|
+
if (sub === "unbind" && ws !== void 0 && capability !== void 0) return capabilityUnbind(ws, capability, opts);
|
|
2369
|
+
if (sub === "list" && ws !== void 0) return capabilityList(ws, opts);
|
|
2370
|
+
return usageFail("expected: githolon capability <bind|rotate|unbind> <ws> <capability> | list <ws>");
|
|
2371
|
+
}
|
|
2024
2372
|
function isKind(s) {
|
|
2025
2373
|
return s !== void 0 && KINDS.includes(s);
|
|
2026
2374
|
}
|
|
2027
|
-
function runProof(args) {
|
|
2375
|
+
async function runProof(args) {
|
|
2028
2376
|
const refuse = (m) => {
|
|
2029
2377
|
process.stderr.write(`error: ${m}
|
|
2030
2378
|
`);
|
|
2031
2379
|
return 1;
|
|
2032
2380
|
};
|
|
2381
|
+
const live = args.includes("--live");
|
|
2382
|
+
const keep = args.includes("--keep");
|
|
2383
|
+
if (keep && !live) return refuse("--keep only applies to --live (the offline proof creates no cloud workspace to keep)");
|
|
2033
2384
|
const explicit = args.find((a) => !a.startsWith("--"));
|
|
2034
2385
|
let proofPath;
|
|
2035
2386
|
if (explicit !== void 0) {
|
|
2036
2387
|
proofPath = resolve2(process.cwd(), explicit);
|
|
2037
|
-
if (!
|
|
2388
|
+
if (!existsSync9(proofPath)) return refuse(`proof not found: ${proofPath} \u2014 run \`githolon compile\` to (re)generate it`);
|
|
2038
2389
|
} else {
|
|
2039
|
-
const buildDir =
|
|
2040
|
-
if (!
|
|
2390
|
+
const buildDir = join10(process.cwd(), "build");
|
|
2391
|
+
if (!existsSync9(buildDir)) {
|
|
2041
2392
|
return refuse("no build/ directory here \u2014 run `githolon compile` first (every compile generates build/<name>.proof.mts from your law)");
|
|
2042
2393
|
}
|
|
2043
2394
|
const proofs = readdirSync4(buildDir).filter((f) => f.endsWith(".proof.mts"));
|
|
@@ -2047,21 +2398,30 @@ function runProof(args) {
|
|
|
2047
2398
|
if (proofs.length > 1) {
|
|
2048
2399
|
return refuse(`found ${proofs.length} proofs (${proofs.join(", ")}) \u2014 name one: githolon proof build/<name>.proof.mts`);
|
|
2049
2400
|
}
|
|
2050
|
-
proofPath =
|
|
2401
|
+
proofPath = join10(buildDir, proofs[0]);
|
|
2402
|
+
}
|
|
2403
|
+
if (!live) {
|
|
2404
|
+
try {
|
|
2405
|
+
const { runOfflineProofStandalone: runOfflineProofStandalone2 } = await Promise.resolve().then(() => (init_proof_offline(), proof_offline_exports));
|
|
2406
|
+
const { cloudBase: cloudBase2 } = await Promise.resolve().then(() => (init_cloud(), cloud_exports));
|
|
2407
|
+
return await runOfflineProofStandalone2(proofPath, cloudBase2());
|
|
2408
|
+
} catch (e) {
|
|
2409
|
+
return refuse(`${e.message ?? e}`);
|
|
2410
|
+
}
|
|
2051
2411
|
}
|
|
2052
2412
|
const resolvePkgDir = (name, fromDir) => {
|
|
2053
2413
|
try {
|
|
2054
|
-
return
|
|
2414
|
+
return dirname5(createRequire2(pathToFileURL4(join10(fromDir, "noop.js"))).resolve(`${name}/package.json`));
|
|
2055
2415
|
} catch {
|
|
2056
2416
|
return void 0;
|
|
2057
2417
|
}
|
|
2058
2418
|
};
|
|
2059
2419
|
const dslDir = (() => {
|
|
2060
2420
|
try {
|
|
2061
|
-
return
|
|
2421
|
+
return dirname5(createRequire2(pathToFileURL4(join10(process.cwd(), "noop.js"))).resolve("@githolon/dsl/package.json"));
|
|
2062
2422
|
} catch {
|
|
2063
2423
|
try {
|
|
2064
|
-
return
|
|
2424
|
+
return dirname5(createRequire2(import.meta.url).resolve("@githolon/dsl/package.json"));
|
|
2065
2425
|
} catch {
|
|
2066
2426
|
return void 0;
|
|
2067
2427
|
}
|
|
@@ -2071,17 +2431,22 @@ function runProof(args) {
|
|
|
2071
2431
|
if (tsxDir === void 0) {
|
|
2072
2432
|
return refuse("tsx not found \u2014 add @githolon/dsl to your project's dependencies (tsx rides along) and npm install");
|
|
2073
2433
|
}
|
|
2074
|
-
const tsxPkg = JSON.parse(
|
|
2434
|
+
const tsxPkg = JSON.parse(readFileSync9(join10(tsxDir, "package.json"), "utf8"));
|
|
2075
2435
|
const tsxBinRel = typeof tsxPkg.bin === "string" ? tsxPkg.bin : tsxPkg.bin?.["tsx"];
|
|
2076
|
-
const tsxCli =
|
|
2077
|
-
|
|
2436
|
+
const tsxCli = join10(tsxDir, tsxBinRel ?? "dist/cli.mjs");
|
|
2437
|
+
console.log("githolon proof --live \u2014 the full cloud loop (throwaway workspace, retired on exit" + (keep ? "; --keep: kept, secret printed once" : "") + ")");
|
|
2438
|
+
const r = spawnSync5(process.execPath, [tsxCli, proofPath], {
|
|
2439
|
+
stdio: "inherit",
|
|
2440
|
+
cwd: process.cwd(),
|
|
2441
|
+
env: { ...process.env, ...keep ? { PROOF_KEEP: "1" } : {} }
|
|
2442
|
+
});
|
|
2078
2443
|
return r.status ?? 1;
|
|
2079
2444
|
}
|
|
2080
2445
|
function parseArgs(argv) {
|
|
2081
2446
|
const [command, ...rest] = argv;
|
|
2082
2447
|
if (command !== "generate" && command !== "g") {
|
|
2083
2448
|
throw new Error(
|
|
2084
|
-
`Unknown command '${command ?? "(none)"}'. Expected dev | compile | proof | replay | status | generate | ws | deploy | secret.`
|
|
2449
|
+
`Unknown command '${command ?? "(none)"}'. Expected dev | compile | proof | replay | status | generate | ws | deploy | secret | capability.`
|
|
2085
2450
|
);
|
|
2086
2451
|
}
|
|
2087
2452
|
let outDir = DEFAULT_OUT;
|
|
@@ -2209,6 +2574,9 @@ ${commandHelp("status")}`);
|
|
|
2209
2574
|
if (argv[0] === "ws" || argv[0] === "deploy" || argv[0] === "secret") {
|
|
2210
2575
|
return runCloud(argv);
|
|
2211
2576
|
}
|
|
2577
|
+
if (argv[0] === "capability") {
|
|
2578
|
+
return runCapability(argv);
|
|
2579
|
+
}
|
|
2212
2580
|
if (argv[0] === "login") {
|
|
2213
2581
|
const agent = argv.includes("--agent");
|
|
2214
2582
|
const tokenAt = argv.indexOf("--token");
|
|
@@ -2265,8 +2633,8 @@ ${USAGE}`);
|
|
|
2265
2633
|
let parsed;
|
|
2266
2634
|
try {
|
|
2267
2635
|
parsed = parseArgs(argv);
|
|
2268
|
-
} catch (
|
|
2269
|
-
process.stderr.write(`error: ${
|
|
2636
|
+
} catch (err9) {
|
|
2637
|
+
process.stderr.write(`error: ${err9.message}
|
|
2270
2638
|
|
|
2271
2639
|
${USAGE}`);
|
|
2272
2640
|
return 1;
|
|
@@ -2285,8 +2653,8 @@ ${USAGE}`);
|
|
|
2285
2653
|
process.stdout.write(`created ${path}
|
|
2286
2654
|
`);
|
|
2287
2655
|
return 0;
|
|
2288
|
-
} catch (
|
|
2289
|
-
process.stderr.write(`error: ${
|
|
2656
|
+
} catch (err9) {
|
|
2657
|
+
process.stderr.write(`error: ${err9.message}
|
|
2290
2658
|
`);
|
|
2291
2659
|
return 1;
|
|
2292
2660
|
}
|