@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.
- package/LICENSE.md +36 -0
- package/compile_package.mjs +50 -0
- package/package.json +59 -0
- package/src/aggregate.ts +167 -0
- package/src/authoring.ts +119 -0
- package/src/build_package.ts +636 -0
- package/src/certified_read.ts +313 -0
- package/src/codegen_dart.ts +2732 -0
- package/src/codegen_dot.ts +466 -0
- package/src/codegen_provider_dart.ts +358 -0
- package/src/codegen_ts.ts +365 -0
- package/src/codegen_usda.ts +388 -0
- package/src/combined.ts +195 -0
- package/src/compile_engine.ts +567 -0
- package/src/compile_package_main.ts +496 -0
- package/src/compose.ts +317 -0
- package/src/count.ts +218 -0
- package/src/ctx.ts +57 -0
- package/src/derived.ts +138 -0
- package/src/directive.ts +306 -0
- package/src/drivers.ts +95 -0
- package/src/emits_guard.ts +123 -0
- package/src/engine_entry.ts +449 -0
- package/src/exists.ts +170 -0
- package/src/extremum.ts +227 -0
- package/src/fields.ts +291 -0
- package/src/framework/bootstrap.ts +22 -0
- package/src/framework/disclosure.ts +108 -0
- package/src/framework/domain_lifecycle.ts +108 -0
- package/src/framework/identity.ts +537 -0
- package/src/framework/impure_capability.ts +643 -0
- package/src/framework/rbac.ts +418 -0
- package/src/framework/repair.ts +150 -0
- package/src/framework/sync_lifecycle.ts +125 -0
- package/src/framework/workspace_invariant.ts +128 -0
- package/src/framework/workspaces.ts +817 -0
- package/src/index.ts +317 -0
- package/src/manifest.ts +947 -0
- package/src/ops.ts +145 -0
- package/src/ordered_read.ts +228 -0
- package/src/predicate.ts +203 -0
- package/src/query/compile.ts +0 -0
- package/src/query/relations.ts +144 -0
- package/src/query.ts +151 -0
- package/src/read.ts +54 -0
- package/src/relation.ts +189 -0
- package/src/report/csv.ts +54 -0
- package/src/report.ts +401 -0
- package/src/spatial.ts +115 -0
- package/src/sum.ts +194 -0
- package/src/usd.ts +563 -0
- package/src/wire.ts +149 -0
- package/src/wire_encode.ts +250 -0
package/src/extremum.ts
ADDED
|
@@ -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
|
+
});
|