@githolon/dsl 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/LICENSE.md +36 -0
  2. package/compile_package.mjs +50 -0
  3. package/package.json +59 -0
  4. package/src/aggregate.ts +167 -0
  5. package/src/authoring.ts +119 -0
  6. package/src/build_package.ts +636 -0
  7. package/src/certified_read.ts +313 -0
  8. package/src/codegen_dart.ts +2732 -0
  9. package/src/codegen_dot.ts +466 -0
  10. package/src/codegen_provider_dart.ts +358 -0
  11. package/src/codegen_ts.ts +365 -0
  12. package/src/codegen_usda.ts +388 -0
  13. package/src/combined.ts +195 -0
  14. package/src/compile_engine.ts +567 -0
  15. package/src/compile_package_main.ts +496 -0
  16. package/src/compose.ts +317 -0
  17. package/src/count.ts +218 -0
  18. package/src/ctx.ts +57 -0
  19. package/src/derived.ts +138 -0
  20. package/src/directive.ts +306 -0
  21. package/src/drivers.ts +95 -0
  22. package/src/emits_guard.ts +123 -0
  23. package/src/engine_entry.ts +449 -0
  24. package/src/exists.ts +170 -0
  25. package/src/extremum.ts +227 -0
  26. package/src/fields.ts +291 -0
  27. package/src/framework/bootstrap.ts +22 -0
  28. package/src/framework/disclosure.ts +108 -0
  29. package/src/framework/domain_lifecycle.ts +108 -0
  30. package/src/framework/identity.ts +537 -0
  31. package/src/framework/impure_capability.ts +643 -0
  32. package/src/framework/rbac.ts +418 -0
  33. package/src/framework/repair.ts +150 -0
  34. package/src/framework/sync_lifecycle.ts +125 -0
  35. package/src/framework/workspace_invariant.ts +128 -0
  36. package/src/framework/workspaces.ts +817 -0
  37. package/src/index.ts +317 -0
  38. package/src/manifest.ts +947 -0
  39. package/src/ops.ts +145 -0
  40. package/src/ordered_read.ts +228 -0
  41. package/src/predicate.ts +203 -0
  42. package/src/query/compile.ts +0 -0
  43. package/src/query/relations.ts +144 -0
  44. package/src/query.ts +151 -0
  45. package/src/read.ts +54 -0
  46. package/src/relation.ts +189 -0
  47. package/src/report/csv.ts +54 -0
  48. package/src/report.ts +401 -0
  49. package/src/spatial.ts +115 -0
  50. package/src/sum.ts +194 -0
  51. package/src/usd.ts +563 -0
  52. package/src/wire.ts +149 -0
  53. package/src/wire_encode.ts +250 -0
@@ -0,0 +1,313 @@
1
+ // NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
2
+ // remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
3
+ // wasm32-wasip1 artifact {kernel · projection · embedded
4
+ // QuickJS engine} on V8 + WASI-shim, byte-identical everywhere. V8 = portability; the one
5
+ // wasm = determinism. No native, no wasmtime, no 2nd artifact, no domain-JS on bare V8.
6
+ // If a file isn't this / hosting this / authoring for this / proving this — it's gone.
7
+
8
+ /**
9
+ * **CertifiedRead — the DECLARED write-path read surface (survival Constraint 5;
10
+ * `certified_read.md`).** A tenant directive's `decide()` may READ the current world to
11
+ * decide whether a write is valid ("is this thing assigned? what's the balance? enough
12
+ * stock?"). That read RESULT is a DECISION INPUT, so it must be a deterministic, captured,
13
+ * byte-identical-across-peers fact ([[determinism-or-death]]). This module is the AUTHORING
14
+ * surface a tenant uses to DECLARE such reads — the `reads:{ name: q.someProfiledRead(args) }`
15
+ * shape from `certified_read.md` §"Tenant authoring".
16
+ *
17
+ * ## The two halves (mirroring `readmodel/src/certified_read.rs`)
18
+ * 1. {@link sqlProfileV0Check} — the TS port of the Rust `SqlProfileV0` enforcer. The villain
19
+ * is UNDER-SPECIFICATION, not SQLite: a declared write-path read MUST obey the restricted
20
+ * profile so EVERY valid execution plan produces the SAME canonical result (explicit
21
+ * `ORDER BY` ending in a UNIQUE tie-breaker on multi-row, NO clock/rng functions, BINARY
22
+ * collation only, NO floats, explicit columns, no implicit rowid, a single pure SELECT). An
23
+ * under-specified declared read is REFUSED at COMPILE (`certifiedRead(...).sql(...)` throws),
24
+ * the SAME refusal the Rust `CertifiedReadDef::compile` raises — so a tenant cannot author a
25
+ * non-deterministic write-path read.
26
+ * 2. {@link certifiedRead} — the declaration builder. `certifiedRead("stockAvailable")
27
+ * .sql("SELECT value_json FROM rows WHERE …", { multiRow:false })` yields a
28
+ * {@link CertifiedReadDecl} the directive's `.readsCertified({ available: decl })` attaches.
29
+ * The declared `query_id`s land in the canonical manifest (`manifest.ts`), so the
30
+ * ONE write-path gate (`executor::admit` step 5.6) can DERIVE the expected declared-read set
31
+ * and REFUSE any admit where a declared read produced no witness (the vacuous-pass close).
32
+ *
33
+ * THE BOUNDARY: a tenant declares the SQL recipe + the shape (single/multi-row + tie-breakers).
34
+ * Nomos owns the determinism (the profile, the canonical encoding, the witness, the admission
35
+ * re-run). The DSL author NEVER mints a witness or touches SQLite at decide time — the HOST
36
+ * resolves each declared read against the pinned projection snapshot and records the witness
37
+ * (the capture wire; the Rust `CertifiedReadPort::resolve_and_witness`).
38
+ */
39
+
40
+ /** The SQL-profile-v0 version pin — frozen alongside the Rust `SQL_PROFILE_VERSION`. */
41
+ export const SQL_PROFILE_VERSION = "v0";
42
+
43
+ /** The NON-DETERMINISTIC SQL functions BANNED on the write/decide path — the clock family +
44
+ * the entropy family. Lower-cased (SQLite function names are case-insensitive). Byte-identical
45
+ * to the Rust `BANNED_FUNCTIONS` so the TS compile refusal and the Rust runtime guard agree. */
46
+ export const BANNED_FUNCTIONS = [
47
+ "random",
48
+ "randomblob",
49
+ "datetime",
50
+ "date",
51
+ "time",
52
+ "julianday",
53
+ "unixepoch",
54
+ "strftime",
55
+ "current_timestamp",
56
+ "current_time",
57
+ "current_date",
58
+ "localtime",
59
+ "localtimestamp",
60
+ ] as const;
61
+
62
+ /** Why a declared write-path read was REFUSED by the SQL-profile-v0 enforcer — the same typed
63
+ * cause set as the Rust `ProfileViolation`. Carried in the thrown {@link ProfileError}. */
64
+ export type ProfileViolation =
65
+ | { kind: "SelectStar" }
66
+ | { kind: "MissingOrderBy" }
67
+ | { kind: "NoUniqueTieBreaker" }
68
+ | { kind: "BannedFunction"; name: string }
69
+ | { kind: "NonBinaryCollation"; collation: string }
70
+ | { kind: "FloatInDecision"; literal: string }
71
+ | { kind: "ImplicitRowid" }
72
+ | { kind: "NotASingleSelect" };
73
+
74
+ /** A compile-time refusal of an under-specified declared read (the villain is
75
+ * UNDER-SPECIFICATION). Thrown by {@link certifiedRead}'s `.sql(...)`. */
76
+ export class ProfileError extends Error {
77
+ constructor(public readonly violation: ProfileViolation) {
78
+ super(`SQL-profile-v0 violation: ${describe(violation)}`);
79
+ this.name = "ProfileError";
80
+ }
81
+ }
82
+
83
+ function describe(v: ProfileViolation): string {
84
+ switch (v.kind) {
85
+ case "SelectStar":
86
+ return "SELECT * is banned on the write path — list explicit columns";
87
+ case "MissingOrderBy":
88
+ return "a multi-row query MUST have an explicit ORDER BY (row order is UNDEFINED without one)";
89
+ case "NoUniqueTieBreaker":
90
+ return "ORDER BY MUST end in a UNIQUE deterministic tie-breaker (e.g. `…, aggregate_id`)";
91
+ case "BannedFunction":
92
+ return `non-deterministic function \`${v.name}\` is banned (the clock/rng family)`;
93
+ case "NonBinaryCollation":
94
+ return `COLLATE \`${v.collation}\` is banned — only BINARY/canonical collation is byte-stable`;
95
+ case "FloatInDecision":
96
+ return `float literal \`${v.literal}\` in a decision is banned — use integers / decimals-as-strings`;
97
+ case "ImplicitRowid":
98
+ return "implicit rowid is banned unless modeled";
99
+ case "NotASingleSelect":
100
+ return "the write-path read must be a SINGLE pure SELECT (no chained statements / write verb)";
101
+ }
102
+ }
103
+
104
+ // ── the profile parser (lower-cased input only; a faithful TS port of the Rust enforcer) ──
105
+
106
+ function collapseWs(s: string): string {
107
+ return ` ${s.replace(/\s+/g, " ").trim()} `;
108
+ }
109
+
110
+ function containsWord(hay: string, word: string): boolean {
111
+ if (word.length === 0) return false;
112
+ const re = new RegExp(`(?<![A-Za-z0-9_])${escapeRe(word)}(?![A-Za-z0-9_])`);
113
+ return re.test(hay);
114
+ }
115
+
116
+ function containsFunctionCall(hay: string, name: string): boolean {
117
+ // `name` followed (with optional spaces) by `(`, and NOT preceded by a word char.
118
+ const re = new RegExp(`(?<![A-Za-z0-9_])${escapeRe(name)}\\s*\\(`);
119
+ return re.test(hay);
120
+ }
121
+
122
+ function escapeRe(s: string): string {
123
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
124
+ }
125
+
126
+ function selectListOf(trimmed: string): string {
127
+ const afterSelect = trimmed.startsWith("select ") ? trimmed.slice("select ".length) : trimmed;
128
+ const idx = afterSelect.indexOf(" from ");
129
+ return idx >= 0 ? afterSelect.slice(0, idx) : afterSelect;
130
+ }
131
+
132
+ function orderByClause(normalized: string): string | undefined {
133
+ const idx = normalized.lastIndexOf(" order by ");
134
+ if (idx < 0) return undefined;
135
+ const after = normalized.slice(idx + " order by ".length);
136
+ const lim = after.indexOf(" limit ");
137
+ return (lim >= 0 ? after.slice(0, lim) : after).trim();
138
+ }
139
+
140
+ function collateOtherThanBinary(normalized: string): string | undefined {
141
+ const re = /collate\s+([a-z0-9_]+)/g;
142
+ let m: RegExpExecArray | null;
143
+ while ((m = re.exec(normalized)) !== null) {
144
+ const name = m[1]!;
145
+ if (name !== "binary") return name;
146
+ }
147
+ return undefined;
148
+ }
149
+
150
+ function floatLiteral(normalized: string): string | undefined {
151
+ // A numeric literal that is NOT a pure integer: a decimal point and/or scientific exponent,
152
+ // at a token boundary, outside single-quoted strings. Mirrors the Rust `float_literal`.
153
+ let inStr = false;
154
+ const isIdent = (c: string) => /[A-Za-z0-9_]/.test(c);
155
+ const n = normalized.length;
156
+ let i = 0;
157
+ while (i < n) {
158
+ const ch = normalized[i]!;
159
+ if (ch === "'") {
160
+ inStr = !inStr;
161
+ i += 1;
162
+ continue;
163
+ }
164
+ if (inStr) {
165
+ i += 1;
166
+ continue;
167
+ }
168
+ const prev = i > 0 ? normalized[i - 1]! : "";
169
+ const startsDigit = /[0-9]/.test(ch);
170
+ const startsDot = ch === "." && i + 1 < n && /[0-9]/.test(normalized[i + 1]!);
171
+ const boundary = i === 0 || (!isIdent(prev) && prev !== ".");
172
+ if (boundary && (startsDigit || startsDot)) {
173
+ const start = i;
174
+ let isFloat = startsDot;
175
+ while (i < n && /[0-9.]/.test(normalized[i]!)) {
176
+ if (normalized[i] === ".") isFloat = true;
177
+ i += 1;
178
+ }
179
+ if (i < n && (normalized[i] === "e" || normalized[i] === "E")) {
180
+ let j = i + 1;
181
+ if (j < n && (normalized[j] === "+" || normalized[j] === "-")) j += 1;
182
+ if (j < n && /[0-9]/.test(normalized[j]!)) {
183
+ isFloat = true;
184
+ i = j;
185
+ while (i < n && /[0-9]/.test(normalized[i]!)) i += 1;
186
+ }
187
+ }
188
+ if (isFloat) return normalized.slice(start, i);
189
+ continue;
190
+ }
191
+ i += 1;
192
+ }
193
+ return undefined;
194
+ }
195
+
196
+ /**
197
+ * Check `sql` against SQL-profile-v0 — the TS port of `SqlProfileV0::check`. `multiRow` is
198
+ * whether the query can return more than one row (a scalar COUNT/MAX is single-row, exempt from
199
+ * the ORDER BY rule). `uniqueTieBreakers` are the declared unique deterministic key columns the
200
+ * multi-row `ORDER BY` must end in. Returns the FIRST {@link ProfileViolation}, or `undefined`
201
+ * when the query obeys EVERY rule.
202
+ */
203
+ export function sqlProfileV0Check(
204
+ sql: string,
205
+ multiRow: boolean,
206
+ uniqueTieBreakers: string[],
207
+ ): ProfileViolation | undefined {
208
+ const lower = sql.toLowerCase();
209
+ const normalized = collapseWs(lower);
210
+
211
+ // single pure SELECT (no chained statements / write verbs)
212
+ const trimmed = normalized.replace(/;+$/, "").trim();
213
+ if (trimmed.includes(";")) return { kind: "NotASingleSelect" };
214
+ if (!trimmed.startsWith("select ") && trimmed !== "select") return { kind: "NotASingleSelect" };
215
+ for (const verb of [
216
+ " insert ", " update ", " delete ", " drop ", " create ", " alter ", " attach ",
217
+ " pragma ", " replace ", " reindex ", " vacuum ",
218
+ ]) {
219
+ if (normalized.includes(verb)) return { kind: "NotASingleSelect" };
220
+ }
221
+
222
+ // explicit columns (no SELECT *), allowing the one `count(*)`
223
+ const selectList = selectListOf(trimmed).replaceAll("count(*)", "count_star");
224
+ if (selectList.includes("*")) return { kind: "SelectStar" };
225
+
226
+ // banned non-deterministic functions (clock/rng family), anywhere
227
+ for (const name of BANNED_FUNCTIONS) {
228
+ if (containsFunctionCall(normalized, name)) return { kind: "BannedFunction", name };
229
+ if (name.startsWith("current_") && containsWord(normalized, name)) {
230
+ return { kind: "BannedFunction", name };
231
+ }
232
+ }
233
+
234
+ // BINARY/canonical collation only
235
+ const coll = collateOtherThanBinary(normalized);
236
+ if (coll !== undefined) return { kind: "NonBinaryCollation", collation: coll };
237
+
238
+ // no implicit rowid (unless modeled as a column)
239
+ for (const kw of ["rowid", "_rowid_", "oid"]) {
240
+ if (containsWord(normalized, kw)) return { kind: "ImplicitRowid" };
241
+ }
242
+
243
+ // no floats in decision-critical results
244
+ const lit = floatLiteral(normalized);
245
+ if (lit !== undefined) return { kind: "FloatInDecision", literal: lit };
246
+
247
+ // ORDER BY with a unique tie-breaker (multi-row only)
248
+ if (multiRow) {
249
+ const orderBy = orderByClause(normalized);
250
+ if (orderBy === undefined) return { kind: "MissingOrderBy" };
251
+ let lastTerm = orderBy.split(",").pop()!.trim();
252
+ const collIdx = lastTerm.indexOf(" collate ");
253
+ if (collIdx >= 0) lastTerm = lastTerm.slice(0, collIdx).trim();
254
+ lastTerm = lastTerm.replace(/ asc$/, "").replace(/ desc$/, "").trim();
255
+ const bare = lastTerm.includes(".") ? lastTerm.split(".").pop()! : lastTerm;
256
+ const ok = uniqueTieBreakers.some((t) => t === lastTerm || t === bare);
257
+ if (!ok) return { kind: "NoUniqueTieBreaker" };
258
+ }
259
+
260
+ return undefined;
261
+ }
262
+
263
+ /**
264
+ * A FINISHED, profile-conforming CertifiedRead declaration — the write-path read API surface
265
+ * (the DSL analogue of the Rust `CertifiedReadDef`). Construction (`certifiedRead(id).sql(...)`)
266
+ * is the COMPILE-TIME refusal: a profile-violating SQL throws {@link ProfileError}, so a
267
+ * `CertifiedReadDecl` value is a profile-conformance WITNESS by construction. The `queryId` is
268
+ * which question; the `sql` is the validated recipe; `multiRow` + `uniqueTieBreakers` are the
269
+ * shape the profile + the admission re-run pin.
270
+ */
271
+ export interface CertifiedReadDecl {
272
+ readonly queryId: string;
273
+ readonly sql: string;
274
+ readonly multiRow: boolean;
275
+ readonly uniqueTieBreakers: string[];
276
+ }
277
+
278
+ /** Options for {@link CertifiedReadBuilder.sql}. */
279
+ export interface CertifiedReadOpts {
280
+ /** Whether the query can return more than one row (drives the ORDER BY requirement).
281
+ * Defaults to `false` (a single-row scalar read, e.g. a COUNT/MAX — exempt from ORDER BY). */
282
+ readonly multiRow?: boolean;
283
+ /** The declared unique deterministic tie-breaker column(s) the multi-row ORDER BY must end in
284
+ * (e.g. `["aggregate_id"]`). Required for a `multiRow` query; ignored for single-row. */
285
+ readonly uniqueTieBreakers?: string[];
286
+ }
287
+
288
+ /** Stage 2: attach the profiled SQL recipe, producing the finished {@link CertifiedReadDecl}. */
289
+ class CertifiedReadBuilder<Id extends string> {
290
+ constructor(private readonly id: Id) {}
291
+ /**
292
+ * Declare the read's SQL recipe. COMPILE-TIME refusal: the SQL is validated against
293
+ * SQL-profile-v0 here ({@link sqlProfileV0Check}) and THROWS {@link ProfileError} on any
294
+ * under-specification — the SAME refusal the Rust `CertifiedReadDef::compile` raises. So a
295
+ * non-conforming declared read can never become a {@link CertifiedReadDecl}.
296
+ */
297
+ sql(text: string, opts: CertifiedReadOpts = {}): CertifiedReadDecl {
298
+ const multiRow = opts.multiRow ?? false;
299
+ const uniqueTieBreakers = [...(opts.uniqueTieBreakers ?? [])];
300
+ const violation = sqlProfileV0Check(text, multiRow, uniqueTieBreakers);
301
+ if (violation !== undefined) throw new ProfileError(violation);
302
+ return { queryId: this.id, sql: text, multiRow, uniqueTieBreakers };
303
+ }
304
+ }
305
+
306
+ /**
307
+ * Begin a CertifiedRead declaration: `certifiedRead("stockAvailable").sql("SELECT … FROM …",
308
+ * { multiRow:false })`. The id is the `query_id` frozen into the witness + the manifest's
309
+ * declared-read set; `.sql(...)` validates the recipe and produces the finished decl.
310
+ */
311
+ export function certifiedRead<const Id extends string>(id: Id): CertifiedReadBuilder<Id> {
312
+ return new CertifiedReadBuilder(id);
313
+ }