@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,227 @@
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
+ * `min(id, field)` / `max(id, field)` builders — the EXTREMUM siblings of `sum`.
10
+ *
11
+ * A `min`/`max` declares a NAMED, MAINTAINED running extremum of an `int`-kind field
12
+ * across every aggregate of the `of`-type (optionally filtered by a `.where(pred)`
13
+ * predicate and partitioned by a `.by(field)` group-by). The result is ONE value per
14
+ * group, maintained incrementally as the workspace folds — NEVER a `MIN/MAX(*)` scan
15
+ * over the `rows` table (the same O(log n) perf invariant as `sum`, via the
16
+ * `extremum_members` B-tree index).
17
+ *
18
+ * The TYPE-STATE mirrors `sum.ts` exactly:
19
+ * `min(id, field)` → `TypelessExtremum` (only `.of` available)
20
+ * `.of(agg)` → `Extremum` builder (`.where`, `.by` available; grand-total usable)
21
+ * `.where(p)` → `Extremum` with predicate baked in (`.by` still available)
22
+ * `.by(k)` → `ExtremumDecl` (finished, grouped declaration)
23
+ *
24
+ * Both a bare `Extremum` (grand-total) and a grouped `ExtremumDecl` satisfy
25
+ * `AnyExtremum` — what `DomainModule.mins` / `DomainModule.maxes` accepts.
26
+ * `finishExtremum` normalizes either to an `ExtremumDecl`.
27
+ *
28
+ * VALIDATION RULE (manifest-load, not type-level): the `valueField` parameter MUST be
29
+ * an `int`-kind field of the `of`-aggregate. The DSL type system cannot enforce this
30
+ * at the `min/max(id, valueField)` call site (the aggregate is not yet known), but
31
+ * `AggregateSchemas::parse` (readmodel/src/manifest.rs) performs a no-fallback reject
32
+ * if the field is absent or non-int on the declared aggregate.
33
+ *
34
+ * ORDER-SENSITIVE GUARDRAIL: min/max expose NO `.first`/`.take`/`.orderBy`. min/max
35
+ * are ORDER-INDEPENDENT REDUCTIONS — `MIN/MAX` over a set is deterministic regardless
36
+ * of physical row order. Same rationale as `count.ts` / `sum.ts`. Assert the absence;
37
+ * do NOT add dead methods (LAW 3).
38
+ *
39
+ * EMPTY-GROUP SEMANTICS: min/max return `Option<i64>` (Rust) / `int?` (Dart, nullable).
40
+ * An empty group has NO extremum, and `0` is a LEGITIMATE min/max value — collapsing
41
+ * empty-group to `0` would be the `sum`-of-0 ambiguity (manifest.rs:274-276). `null`
42
+ * means "no contributing member"; a non-null value is the extremum. This is HARDER
43
+ * and CORRECT (LAW 3 — harden, not loosen).
44
+ */
45
+ import type { AggregateHandle } from "./aggregate.js";
46
+ import type { Field } from "./fields.js";
47
+ import { type Predicate, type CanonicalPred, predBuilder, canonicalizePred } from "./predicate.js";
48
+
49
+ /** Which kind of extremum — the `kind` discriminant shared between min/max. */
50
+ export type ExtremumKind = "min" | "max";
51
+
52
+ /**
53
+ * A FINISHED extremum declaration (the read-engine's input shape): an id, the
54
+ * aggregate TYPE it tallies, the FIELD being extremised, the `kind` (min|max), the
55
+ * OPTIONAL predicate, and the OPTIONAL group-by field.
56
+ */
57
+ export interface ExtremumDecl {
58
+ readonly id: string;
59
+ /** `"min"` or `"max"` — the kind of extremum maintained. */
60
+ readonly kind: ExtremumKind;
61
+ /** The aggregate TYPE id, e.g. `ListingAggregate`. */
62
+ readonly of: string;
63
+ /**
64
+ * The `int`-kind field being extremised, e.g. `"itemValue"`. MUST be an
65
+ * `int`-kind field of the `of`-aggregate (validated at manifest-load).
66
+ */
67
+ readonly valueField: string;
68
+ /**
69
+ * The optional predicate: ONLY aggregates satisfying this predicate contribute.
70
+ * ABSENT ⇒ every aggregate of the `of`-type is considered.
71
+ */
72
+ readonly where?: CanonicalPred;
73
+ /**
74
+ * The group-by field name. ABSENT ⇒ a grand total across all (matching)
75
+ * aggregates of the `of`-type (one synthetic group).
76
+ */
77
+ readonly by?: string;
78
+ }
79
+
80
+ /**
81
+ * The `Extremum` BUILDER: it has named its `of`-type (and valued field), so `.where(...)`
82
+ * and `.by(...)` are available. The `F` type parameter carries the `of`-aggregate's
83
+ * field map so `.where(p => p.field("status").eq("active"))` is keyof-checked.
84
+ */
85
+ export interface Extremum<F extends Record<string, Field> = Record<string, Field>> {
86
+ readonly id: string;
87
+ readonly kind: ExtremumKind;
88
+ /** The aggregate TYPE id the extremum tallies. */
89
+ readonly of: string;
90
+ /** The `int`-kind field being extremised. */
91
+ readonly valueField: string;
92
+ /**
93
+ * Attach an optional PREDICATE: only aggregates satisfying the predicate contribute.
94
+ * Returns a new `Extremum` with the predicate baked in; `.by(...)` is still available.
95
+ */
96
+ where(fn: (p: ReturnType<typeof predBuilder<F>>) => Predicate<F>): Extremum<F>;
97
+ /**
98
+ * Partition by a GROUP-BY field. Every distinct value of `groupField` maintains its
99
+ * own running extremum. Returns a finished `ExtremumDecl`.
100
+ */
101
+ by(groupField: string): ExtremumDecl;
102
+ }
103
+
104
+ /**
105
+ * The INITIAL, un-typed extremum — its ONLY method is `.of(...)`. An `min/max(id, field)`
106
+ * without `.of(...)` cannot be used as a declaration: the aggregate type is not optional.
107
+ */
108
+ export interface TypelessExtremum {
109
+ readonly id: string;
110
+ readonly kind: ExtremumKind;
111
+ /** The `int`-kind field to extremise. */
112
+ readonly valueField: string;
113
+ /**
114
+ * Declare the aggregate TYPE. Takes a typed HANDLE (never a string id). Returns the
115
+ * `Extremum` builder (the only shape exposing `.where` and `.by`), which is already
116
+ * a usable grand-total extremum.
117
+ */
118
+ of<F extends Record<string, Field>>(aggregate: AggregateHandle<string, F>): Extremum<F>;
119
+ }
120
+
121
+ /**
122
+ * Either form a domain may declare in `DomainModule.mins` / `DomainModule.maxes`.
123
+ * `Extremum<any>` for the same reason as `AnySum = SumDecl | Sum<any>`:
124
+ * `Extremum<F>` is invariant in `F` (the `where` method), so `Extremum<SpecificFields>`
125
+ * is not assignable to `Extremum<Record<string,Field>>`. The consumer only accesses
126
+ * `id`/`kind`/`of`/`valueField`/`by` string fields — it never calls `.where(fn)` — so
127
+ * `any` is safe.
128
+ */
129
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
130
+ export type AnyExtremum = ExtremumDecl | Extremum<any>;
131
+
132
+ /** Narrow: an `Extremum` builder exposes a `by` METHOD; an `ExtremumDecl` does not. */
133
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
134
+ function isExtremumBuilder(e: AnyExtremum): e is Extremum<any> {
135
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
136
+ return typeof (e as Extremum<any>).by === "function";
137
+ }
138
+
139
+ /**
140
+ * Normalize an `AnyExtremum` to a finished `ExtremumDecl`. The grand-total builder
141
+ * (bare `.of(...)`) becomes `{id, kind, of, valueField}` (no `by`); a grouped
142
+ * `.by(...)` result is already an `ExtremumDecl` and passes through.
143
+ */
144
+ export function finishExtremum(e: AnyExtremum): ExtremumDecl {
145
+ if (isExtremumBuilder(e)) {
146
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
147
+ const b = e as any;
148
+ const w: CanonicalPred | undefined = b._where;
149
+ return {
150
+ id: b.id,
151
+ kind: b.kind,
152
+ of: b.of,
153
+ valueField: b.valueField,
154
+ ...(w !== undefined ? { where: w } : {}),
155
+ };
156
+ }
157
+ return e;
158
+ }
159
+
160
+ /** Internal factory so `.where(...)` can return a new Extremum without duplicating impl. */
161
+ function makeExtremum<F extends Record<string, Field>>(
162
+ id: string,
163
+ kind: ExtremumKind,
164
+ ofType: string,
165
+ valueField: string,
166
+ where: CanonicalPred | undefined,
167
+ ): Extremum<F> {
168
+ // Same exactOptionalPropertyTypes pattern as makeSum: spread `_where` only when
169
+ // defined so the key is absent (not `undefined`) in the predicate-free case.
170
+ const e = {
171
+ id,
172
+ kind,
173
+ of: ofType,
174
+ valueField,
175
+ ...(where !== undefined ? { _where: where } : {}),
176
+ where(fn: (p: ReturnType<typeof predBuilder<F>>) => Predicate<F>): Extremum<F> {
177
+ const pred = fn(predBuilder<F>());
178
+ const canonical = canonicalizePred(pred as Predicate<Record<string, Field>>);
179
+ return makeExtremum<F>(id, kind, ofType, valueField, canonical);
180
+ },
181
+ by(groupField: string): ExtremumDecl {
182
+ return {
183
+ id,
184
+ kind,
185
+ of: ofType,
186
+ valueField,
187
+ ...(where !== undefined ? { where } : {}),
188
+ by: groupField,
189
+ };
190
+ },
191
+ } as unknown as Extremum<F>;
192
+ return e;
193
+ }
194
+
195
+ /**
196
+ * Begin a `min` declaration. `id` is the min's canonical name (e.g.
197
+ * `"minItemValuePerSite"`); `valueField` is the `int`-kind field on the `of`-aggregate
198
+ * whose minimum is maintained. Returns a `TypelessExtremum`: until `.of(aggregate)` is
199
+ * called, the aggregate type is unknown and no usable declaration exists.
200
+ */
201
+ export function min(id: string, valueField: string): TypelessExtremum {
202
+ return {
203
+ id,
204
+ kind: "min",
205
+ valueField,
206
+ of<F extends Record<string, Field>>(aggregate: AggregateHandle<string, F>): Extremum<F> {
207
+ return makeExtremum<F>(id, "min", aggregate.id, valueField, undefined);
208
+ },
209
+ };
210
+ }
211
+
212
+ /**
213
+ * Begin a `max` declaration. `id` is the max's canonical name (e.g.
214
+ * `"maxItemValuePerSite"`); `valueField` is the `int`-kind field on the `of`-aggregate
215
+ * whose maximum is maintained. Returns a `TypelessExtremum`: until `.of(aggregate)` is
216
+ * called, the aggregate type is unknown and no usable declaration exists.
217
+ */
218
+ export function max(id: string, valueField: string): TypelessExtremum {
219
+ return {
220
+ id,
221
+ kind: "max",
222
+ valueField,
223
+ of<F extends Record<string, Field>>(aggregate: AggregateHandle<string, F>): Extremum<F> {
224
+ return makeExtremum<F>(id, "max", aggregate.id, valueField, undefined);
225
+ },
226
+ };
227
+ }
package/src/fields.ts ADDED
@@ -0,0 +1,291 @@
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
+ * Field builders (`t.*`). Each field carries:
10
+ * - a Zod schema (the single source of truth for its runtime type + TS inference),
11
+ * - a merge Driver (defaulted; overridable via `.merge(...)`),
12
+ * - a kind tag used to map authored values onto the kernel `Value` shape.
13
+ *
14
+ * `.merge(Driver)` is chainable and returns a new immutable Field so inference of
15
+ * the value type (via Zod) is preserved.
16
+ */
17
+ import { z } from "zod";
18
+ import { Conflict, Lww, type Driver } from "./drivers.js";
19
+ import type { AggregateHandle } from "./aggregate.js";
20
+
21
+ export type FieldKind =
22
+ | "string"
23
+ | "int"
24
+ | "enum"
25
+ | "set"
26
+ | "map"
27
+ | "json"
28
+ | "ref"
29
+ // `t.hasMany(Child).via(backref)` — a VIRTUAL inverse relation (the 1:N read side of a
30
+ // `t.ref` on the child). Stored on NOTHING: it is not in the kernel wire schema (encodeKernelSchema
31
+ // skips it, so it changes no hash) and is never authored directly — `.add` writes the child's
32
+ // back-reference, and the read engine serves it as the managed `Child by backref` index.
33
+ | "hasMany"
34
+ // evidence.md §6/§9.2 — a content-addressed EvidenceRef. The migration target of the
35
+ // hand-wired `blobRef` `t.jsonObject()` string (thing_structures.ts). The authored
36
+ // value is the typed EvidenceRef shape; it encodes to a kernel `Value::Evidence`.
37
+ | "evidence";
38
+
39
+ /**
40
+ * The authored shape of an `EvidenceRef` (`evidence.md` §1) — a content-addressed
41
+ * reference the dev places on an aggregate field via `t.evidence()`. The `hash` is the
42
+ * git blob oid (computed by the framework from the bytes, peer-side, pure); the dev
43
+ * supplies the bytes via the dispatch SIDECAR and the framework mints this ref. Mirrors
44
+ * the kernel `nomos_kernel::evidence::EvidenceRef` byte-for-byte (the field order the
45
+ * canonicalizer commits).
46
+ */
47
+ export interface EvidenceRefValue {
48
+ /** The content hash — git's blob oid (the identity). */
49
+ hash: string;
50
+ /** The declared content type (e.g. `application/pdf`). */
51
+ mediaType: string;
52
+ /** The byte length of the referenced content. */
53
+ byteLength: number;
54
+ /** Where the bytes live: `git` (inline-syncing) or `external` (shreddable storage). */
55
+ storageClass: "git" | "external";
56
+ }
57
+
58
+ /** The Zod schema for an authored `EvidenceRefValue` — the single source of truth. */
59
+ export const evidenceRefSchema: z.ZodType<EvidenceRefValue> = z.object({
60
+ hash: z.string(),
61
+ mediaType: z.string(),
62
+ byteLength: z.number().int().nonnegative(),
63
+ storageClass: z.enum(["git", "external"]),
64
+ });
65
+
66
+ export interface Field<T = unknown, K extends FieldKind = FieldKind> {
67
+ /** The kind tag. `K` is the LITERAL kind so the query DSL can constrain `.ref`
68
+ * to ONLY `kind:'ref'` fields at compile time (`t.ref` → `Field<string,'ref'>`).
69
+ * `.merge` preserves it. */
70
+ readonly kind: K;
71
+ readonly zod: z.ZodType<T>;
72
+ readonly driver: Driver;
73
+ /** For `t.ref` cross-workspace edges: the foreign workspace id, if any. */
74
+ readonly refWorkspace?: string;
75
+ /** For `t.ref` (the target) AND `t.hasMany` (the child type): the related aggregate's wire id. */
76
+ readonly refAggregateId?: string;
77
+ /** For `t.hasMany(Child).via(backref)`: the back-reference field NAME on the child. Virtual. */
78
+ readonly viaField?: string;
79
+ /**
80
+ * For a `t.map(inner)` field: the inner VALUE field's kind (e.g. `"string"` for
81
+ * `t.map(t.string())`, `"json"` for `t.map(t.json())`). The wire DRIVER is
82
+ * identical for both (`MapOf(Lww)` — a json leaf encodes to a `Str` value just like
83
+ * a string), so this changes NO hash; it only tells codegen how to SURFACE each
84
+ * entry value: a `string`-valued map reads `Map<String,String>` (flat strings),
85
+ * whereas a `json`-valued map reads `Map<String,Object?>` (each entry JSON-decoded
86
+ * back to its object) so a value-object map (e.g. a site's `customFields`,
87
+ * each entry the canonical `CustomField.toJson()`) round-trips losslessly. `undefined`
88
+ * for a non-map field.
89
+ */
90
+ readonly mapValueKind?: FieldKind;
91
+ /**
92
+ * For a `json`-kind field: the DECODED SHAPE of its value-object, when the domain
93
+ * dev can assert it. A `t.json()` leaf is typed `String` in the DSL (the kernel
94
+ * `Value` is Str|Int only, so a value-object — OR a double/bool — is stored as a
95
+ * JSON string), but the Rust read engine DECODES it back to whatever it is
96
+ * (object / list / scalar). The v1-FREE projection needs to know whether to surface
97
+ * it as a STRUCTURED `Map<String,Object?>` (a strict object reader, e.g. `location`)
98
+ * or as a permissive `Object?` (a value that can legitimately be a list/scalar — e.g.
99
+ * a `scaleX` double or an `estimatedCost` authored as a JSON leaf). `"object"` is the
100
+ * EXPLICIT assertion "this json leaf is always a JSON OBJECT" (set by `t.jsonObject()`);
101
+ * `undefined` (a plain `t.json()`) means the shape is unknown/non-object → the
102
+ * projection defaults it to the permissive `Object?`. This flag affects ONLY the
103
+ * generated frontend projection TYPE — NOT the kernel wire `Schema` (driver-only, see
104
+ * `encodeKernelSchema`) — so it changes NO hash (identical to `mapValueKind`/`isOptional`).
105
+ */
106
+ readonly jsonShape?: "object";
107
+ /**
108
+ * Whether a folded aggregate may LACK this field. A SCALAR field
109
+ * (`string`/`int`/`enum`/`ref`/`json`) that a `.creates` directive does not
110
+ * always fold (e.g. `description`/`siteType` on a freshly-created site) is marked
111
+ * `.optional()` so the generated Dart projection type makes it NULLABLE and decodes
112
+ * a minimal freshly-folded aggregate (DSL codegen GAP 1). COLLECTION fields
113
+ * (`set`/`map`) are ALWAYS empty-defaultable and never need this. This flag affects
114
+ * ONLY the generated frontend type's required-vs-optional — NOT the kernel wire
115
+ * `Schema` (which is driver-only, see `encodeKernelSchema`), so it changes no hash.
116
+ */
117
+ readonly isOptional: boolean;
118
+ /** Returns a copy with the chosen driver (kind literal preserved). */
119
+ merge(driver: Driver): Field<T, K>;
120
+ /**
121
+ * Mark this (scalar) field OPTIONAL: a folded aggregate may lack it, so the
122
+ * generated Dart type is nullable + tolerant. Chainable + non-destructive (kind
123
+ * literal + driver preserved). Wraps the Zod schema in `.optional()` so the field
124
+ * schema IS the single source of truth codegen derives required-vs-optional from.
125
+ */
126
+ optional(): Field<T | undefined, K>;
127
+ /** Phantom to keep T load-bearing for inference. */
128
+ readonly _t?: T;
129
+ }
130
+
131
+ function makeField<T, K extends FieldKind = FieldKind>(
132
+ kind: K,
133
+ zod: z.ZodType<T>,
134
+ driver: Driver,
135
+ extra: {
136
+ refWorkspace?: string;
137
+ refAggregateId?: string;
138
+ viaField?: string;
139
+ isOptional?: boolean;
140
+ mapValueKind?: FieldKind;
141
+ jsonShape?: "object";
142
+ } = {},
143
+ ): Field<T, K> {
144
+ const isOptional = extra.isOptional ?? false;
145
+ const f: Field<T, K> = {
146
+ kind,
147
+ zod,
148
+ driver,
149
+ isOptional,
150
+ ...(extra.refWorkspace !== undefined ? { refWorkspace: extra.refWorkspace } : {}),
151
+ ...(extra.refAggregateId !== undefined ? { refAggregateId: extra.refAggregateId } : {}),
152
+ ...(extra.viaField !== undefined ? { viaField: extra.viaField } : {}),
153
+ ...(extra.mapValueKind !== undefined ? { mapValueKind: extra.mapValueKind } : {}),
154
+ ...(extra.jsonShape !== undefined ? { jsonShape: extra.jsonShape } : {}),
155
+ merge(d: Driver) {
156
+ return makeField<T, K>(kind, zod, d, extra);
157
+ },
158
+ optional(): Field<T | undefined, K> {
159
+ // Wrap the Zod once (`.optional()` is idempotent enough — re-wrapping an
160
+ // already-optional schema stays optional). Carry the flag so codegen reads it
161
+ // directly without re-introspecting Zod.
162
+ return makeField<T | undefined, K>(kind, zod.optional(), driver, {
163
+ ...extra,
164
+ isOptional: true,
165
+ });
166
+ },
167
+ };
168
+ return f;
169
+ }
170
+
171
+ /** Infer the authored value type of a Field. */
172
+ export type FieldValue<F> = F extends Field<infer T> ? T : never;
173
+
174
+ export const t = {
175
+ string(): Field<string, "string"> {
176
+ return makeField("string", z.string(), Lww);
177
+ },
178
+ int(): Field<number, "int"> {
179
+ return makeField("int", z.number().int(), Lww);
180
+ },
181
+ /** Enum via `as const` literal union — value typos squeal at compile time. */
182
+ enum<const E extends readonly [string, ...string[]]>(values: E): Field<E[number], "enum"> {
183
+ return makeField<E[number], "enum">(
184
+ "enum",
185
+ z.enum(values) as unknown as z.ZodType<E[number]>,
186
+ Lww,
187
+ );
188
+ },
189
+ /** A set of items. Defaults to AddWins is NOT assumed — caller picks via .merge; default Lww-on-set is wrong, so we default to Conflict-free AddWins-friendly Lww only when overridden. */
190
+ set(_inner: Field<string>): Field<string[], "set"> {
191
+ // Authored as an array; encodes to a Set Value (AddToSet op). Default driver
192
+ // is AddWins since a set field that isn't add-wins is almost always a bug,
193
+ // but it remains overridable.
194
+ return makeField<string[], "set">("set", z.array(z.string()), { kind: "AddWins" });
195
+ },
196
+ /** A map of driven sub-fields. Defaults to MapOf(Lww); override via .merge(MapOf(...)).
197
+ * Records the inner VALUE field's `kind` (`mapValueKind`) so codegen can surface a
198
+ * `json`-valued map (`t.map(t.json())`) as `Map<String,Object?>` (each entry decoded
199
+ * back to its object — loss-less value-object round-trip) while a `string`-valued map
200
+ * stays `Map<String,String>`. The wire driver is identical (`MapOf(Lww)` — both encode
201
+ * a leaf to `Str`), so this changes no hash. */
202
+ map<T>(inner: Field<T>): Field<Record<string, T>, "map"> {
203
+ return makeField<Record<string, T>, "map">(
204
+ "map",
205
+ z.record(z.string(), inner.zod),
206
+ { kind: "MapOf", inner: inner.driver },
207
+ { mapValueKind: inner.kind },
208
+ );
209
+ },
210
+ /** Arbitrary JSON-string leaf (used inside maps); encodes as a Str at op time. The
211
+ * decoded shape is UNKNOWN/non-object (it may be a double/bool/list authored as a
212
+ * JSON leaf — the kernel `Value` is Str|Int only), so the v1-free projection surfaces
213
+ * it as a permissive `Object?`. Use `t.jsonObject()` when the leaf is ALWAYS an OBJECT
214
+ * value-object (e.g. a `location`) to get a strict `Map<String,Object?>` read type. */
215
+ json(): Field<string, "json"> {
216
+ return makeField<string, "json">("json", z.string(), Lww);
217
+ },
218
+ /** A JSON-OBJECT value-object leaf: an arbitrary JSON-string leaf (identical wire +
219
+ * driver to `t.json()` — a `Str` at op time, so NO hash change) whose decoded value is
220
+ * asserted to ALWAYS be a JSON OBJECT (e.g. `location`, `address`, `blobRef`,
221
+ * `structuralAssignment`). The v1-FREE projection surfaces it as a STRUCTURED
222
+ * `Map<String,Object?>` with a STRICT reader (a present-but-non-object value throws —
223
+ * the strict-decode contract), as opposed to a plain `t.json()` (decoded shape unknown
224
+ * → permissive `Object?`). Marks `jsonShape:"object"`; the kind stays `"json"`. */
225
+ jsonObject(): Field<string, "json"> {
226
+ return makeField<string, "json">("json", z.string(), Lww, { jsonShape: "object" });
227
+ },
228
+ /**
229
+ * Reference to another aggregate (stored as its id string).
230
+ * - `t.ref(Agg)` — same-workspace.
231
+ * - `t.ref('otherWsId', Agg)` — cross-workspace (PR-tier edge); just marked,
232
+ * routing is the kernel's job.
233
+ */
234
+ ref: refImpl as RefFn,
235
+ /**
236
+ * A 1:N INVERSE relation — the read side of a `t.ref` on the child (`aggregate_lifecycle_and_relations.md`).
237
+ * `t.hasMany(Child).via("parent")` on a Parent declares "the Children whose `parent` ref points here".
238
+ * It is VIRTUAL: nothing is stored on the parent (encodeKernelSchema skips it → no hash change). From this one
239
+ * line the framework derives BOTH sides — `parent.add("children", child)` writes the child's back-ref
240
+ * (one event on the CHILD, parent untouched), and the read engine serves `parent.children` as the
241
+ * managed `Child by parent` index. The dev never builds a write-amplifying parent collection.
242
+ */
243
+ hasMany(child: AggregateHandle<string, Record<string, Field>>): {
244
+ via(backref: string): Field<string[], "hasMany">;
245
+ } {
246
+ return {
247
+ via(backref: string): Field<string[], "hasMany"> {
248
+ return makeField<string[], "hasMany">("hasMany", z.array(z.string()), Conflict, {
249
+ refAggregateId: child.id,
250
+ viaField: backref,
251
+ });
252
+ },
253
+ };
254
+ },
255
+ /**
256
+ * A content-addressed EVIDENCE reference (`evidence.md` §6/§9.2). The authored value is
257
+ * the typed {@link EvidenceRefValue} ({hash, mediaType, byteLength, storageClass}); it
258
+ * encodes to a first-class kernel `Value::Evidence` (NOT a JSON string), the gate
259
+ * evidence-backs it (re-hash the dispatch sidecar + store), and codegen emits a typed
260
+ * `EvidenceRef` class (TS + Dart). This is the migration target of the hand-wired
261
+ * `blobRef` `t.jsonObject()` string — re-pointing the LIVE Attachment domain is the §7
262
+ * "use exhaustively" phase (deferred); this builder + codegen + a test-domain field
263
+ * land the type now. Default driver Lww (a content ref is replaced wholesale — a Scalar
264
+ * value, like a string), overridable via `.merge(...)`.
265
+ */
266
+ evidence(): Field<EvidenceRefValue, "evidence"> {
267
+ return makeField<EvidenceRefValue, "evidence">("evidence", evidenceRefSchema, Lww);
268
+ },
269
+ };
270
+
271
+ interface RefFn {
272
+ (target: AggregateHandle<string, Record<string, Field>>): Field<string, "ref">;
273
+ (
274
+ workspaceId: string,
275
+ target: AggregateHandle<string, Record<string, Field>>,
276
+ ): Field<string, "ref">;
277
+ }
278
+
279
+ function refImpl(
280
+ a: string | AggregateHandle<string, Record<string, Field>>,
281
+ b?: AggregateHandle<string, Record<string, Field>>,
282
+ ): Field<string, "ref"> {
283
+ if (typeof a === "string") {
284
+ const target = b!;
285
+ return makeField<string, "ref">("ref", z.string(), Conflict, {
286
+ refWorkspace: a,
287
+ refAggregateId: target.id,
288
+ });
289
+ }
290
+ return makeField<string, "ref">("ref", z.string(), Conflict, { refAggregateId: a.id });
291
+ }
@@ -0,0 +1,22 @@
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
+ * Stage-0 bootstrap domain.
10
+ *
11
+ * Genesis pins this package so an empty GitHolon has exactly one executable
12
+ * authority: install the Nomos lifecycle controller package. It deliberately
13
+ * exposes the narrow stage-zero intent `bootstrap/installDomain`; after Nomos is
14
+ * active, all future domain lifecycle authoring must use installed Nomos law.
15
+ */
16
+
17
+ export {
18
+ DomainInstallation,
19
+ PolicyBundle,
20
+ installDomain,
21
+ policyAggregateId,
22
+ } from "./domain_lifecycle.js";
@@ -0,0 +1,108 @@
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
+ * The `disclosure` domain — a recorded, re-derivable report disclosure.
10
+ *
11
+ * When a report is generated and handed to a recipient (a regulator, an auditor),
12
+ * the FACT of the disclosure is itself a first-class, append-only kernel record.
13
+ * A `Disclosure` pins everything needed to RE-DERIVE the disclosed
14
+ * artifact byte-for-byte:
15
+ *
16
+ * * `sourceCommit` — the ledger commit the report's query was resolved @ (the
17
+ * projection is rebuilt @ this commit on re-derivation),
18
+ * * `queryHash` — sha256 of the canonical query AST (which question),
19
+ * * `artifactSha` — the content-addressed blob oid of the rendered bytes (the
20
+ * answer — `put_blob(render_output)` must reproduce this exactly),
21
+ * * `recipient` / `purpose` — the disclosure's audit metadata,
22
+ * * `generatedBy` / `generatedAt` — who/when authored it.
23
+ *
24
+ * Re-derivation = verification: rebuild the projection @ `sourceCommit`, re-run the
25
+ * report feeding the captured rows, and assert `put_blob(render) == artifactSha`.
26
+ * Because the artifact is content-addressed and the query is pinned, a recorded
27
+ * disclosure is provably the bytes that were disclosed.
28
+ *
29
+ * Authored against the STABLE DSL only (typed fields + drivers + a `.creates`
30
+ * directive). All provenance fields are immutable-after-create (NOTE 1a in
31
+ * identity.ts) — a disclosure is a frozen fact — modelled as `Lww` (a second write
32
+ * merely converges; the real semantic is create-only, a kernel gap flagged there).
33
+ */
34
+ import { z } from "zod";
35
+ import { aggregate, instance } from "../aggregate.js";
36
+ import { t } from "../fields.js";
37
+ import { Lww } from "../drivers.js";
38
+ import { directive } from "../directive.js";
39
+ import { set } from "../ops.js";
40
+
41
+ /**
42
+ * The Disclosure — a frozen record of one report disclosure. Keyed by a
43
+ * `disclosureId` (instance-bound). Every field is immutable-after-create provenance.
44
+ */
45
+ export const Disclosure = aggregate("Disclosure", {
46
+ disclosureId: t.string().merge(Lww), // immutable-after-create
47
+ reportId: t.string().merge(Lww), // which report (e.g. a recently-touched report)
48
+ reportVersion: t.string().merge(Lww), // the report's declared version (REPORT axis)
49
+ // The DOMAIN-VERSION axis (#136): the set of domain identity hashes
50
+ // { domain → sha256(canonical manifest) } active for the report's domains at
51
+ // capture, JSON-encoded (sorted keys → byte-stable). This is what lets a
52
+ // why-different diff report "data identical but embodied-carbon domain v3→v4":
53
+ // two disclosures with an identical resultDigest but a different domainVersionSet
54
+ // prove the difference is a VERSION change, not a data change. Immutable-after-
55
+ // create like every other provenance field.
56
+ domainVersionSet: t.string().merge(Lww),
57
+ sourceCommit: t.string().merge(Lww), // ledger commit the query was resolved @ (DATA axis)
58
+ queryHash: t.string().merge(Lww), // sha256(canonical query AST) (QUERY axis)
59
+ resultDigest: t.string().merge(Lww), // sha256(canonical totally-ordered rows)
60
+ artifactSha: t.string().merge(Lww), // content-addressed oid of the rendered bytes
61
+ recipient: t.string().merge(Lww), // who the disclosure was made to
62
+ purpose: t.string().merge(Lww), // why (audit metadata)
63
+ generatedBy: t.string().merge(Lww), // authoring actor
64
+ generatedAt: t.string().merge(Lww), // ISO-8601
65
+ });
66
+
67
+ /**
68
+ * `recordDisclosure` — `.creates` a Disclosure (instance-bound by
69
+ * `disclosureId`). Rejects a double-create (RefMode::Create). The render bytes are
70
+ * NOT in the payload — they are `put_blob`'d separately to the content-addressed
71
+ * store and only their `artifactSha` is recorded here (the disclosure points at the
72
+ * artifact; it does not embed it).
73
+ */
74
+ export const recordDisclosure = directive("recordDisclosure")
75
+ .creates(Disclosure)
76
+ .payload(
77
+ z.object({
78
+ disclosureId: z.string(),
79
+ reportId: z.string(),
80
+ reportVersion: z.string(),
81
+ domainVersionSet: z.string(),
82
+ sourceCommit: z.string(),
83
+ queryHash: z.string(),
84
+ resultDigest: z.string(),
85
+ artifactSha: z.string(),
86
+ recipient: z.string(),
87
+ purpose: z.string(),
88
+ generatedBy: z.string(),
89
+ generatedAt: z.string(),
90
+ }),
91
+ )
92
+ .plan((p) => {
93
+ const d = instance(Disclosure, p.disclosureId);
94
+ return [
95
+ set(d, "disclosureId", p.disclosureId),
96
+ set(d, "reportId", p.reportId),
97
+ set(d, "reportVersion", p.reportVersion),
98
+ set(d, "domainVersionSet", p.domainVersionSet),
99
+ set(d, "sourceCommit", p.sourceCommit),
100
+ set(d, "queryHash", p.queryHash),
101
+ set(d, "resultDigest", p.resultDigest),
102
+ set(d, "artifactSha", p.artifactSha),
103
+ set(d, "recipient", p.recipient),
104
+ set(d, "purpose", p.purpose),
105
+ set(d, "generatedBy", p.generatedBy),
106
+ set(d, "generatedAt", p.generatedAt),
107
+ ];
108
+ });