@githolon/dsl 0.70.2 → 0.72.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/dart/CHANGELOG.md +13 -0
- package/dart/lib/nomos_client.dart +4 -0
- package/dart/lib/src/session_bound.dart +309 -0
- package/dart/lib/src/test_harness.dart +136 -4
- package/dart/lib/src/webview_bridge.dart +173 -27
- package/dart/pubspec.yaml +1 -1
- package/dart/test/cluster_attested_test.dart +0 -1
- package/dart/test/session_bound_test.dart +255 -0
- package/dart_types/CHANGELOG.md +8 -0
- package/dart_types/lib/nomos_types.dart +6 -0
- package/dart_types/lib/src/principal.dart +56 -0
- package/dart_types/lib/src/surfaced.dart +208 -0
- package/dart_types/pubspec.yaml +1 -1
- package/dart_types/test/surfaced_test.dart +69 -0
- package/package.json +1 -1
- package/src/aggregate.ts +64 -7
- package/src/build_package.ts +22 -0
- package/src/codegen_dart.ts +155 -31
- package/src/codegen_dart_client.ts +274 -15
- package/src/codegen_openapi.ts +20 -0
- package/src/codegen_proof.ts +5 -1
- package/src/codegen_ts.ts +166 -1
- package/src/compile_package_main.ts +126 -9
- package/src/directive.ts +40 -0
- package/src/drivers.ts +63 -2
- package/src/idof_lint.ts +144 -0
- package/src/index.ts +1 -0
- package/src/surfaced_lint.ts +0 -0
- package/src/usd_layers.ts +5 -0
- package/src/wire.ts +4 -1
package/src/idof_lint.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
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 PAYLOAD-REF idOf LINT (`architecture/dart_codegen_parity.md` P2 — ref ergonomics). A
|
|
10
|
+
* `t.ref(Target)` / `t.foreignRef(Target)` field on the AGGREGATE/read-model side already
|
|
11
|
+
* generates a TYPED id class (`SiteId`, `codegen_dart.ts`). Nothing enforced the payload SIDE
|
|
12
|
+
* carried the matching brand — a directive author can write `siteId: z.string()` in the
|
|
13
|
+
* payload and never discover `idOf(Site)` exists, so the generated Dart payload class carries a
|
|
14
|
+
* raw `String` while the read model carries `SiteId`: an asymmetry that forces manual
|
|
15
|
+
* `.value` unwrap/rewrap at the UI boundary and lets a mistyped id compile silently.
|
|
16
|
+
*
|
|
17
|
+
* THIS PASS closes that gap at COMPILE time: for every directive, plan-PROBE it (reusing the
|
|
18
|
+
* exact primitive the proof generator's own discovery legs use — `probePlanOps` /
|
|
19
|
+
* `synthesizePayload`, `codegen_proof.ts` — NOT a new AST interpreter) and, for every emitted
|
|
20
|
+
* field op that targets a ref-kind field, check whether the op's value is traceable to a
|
|
21
|
+
* TOP-LEVEL payload field whose zod schema is a bare (unbranded) string. If so: REFUSE, naming
|
|
22
|
+
* the directive, the payload field, the ref field, and its target aggregate, with the fix.
|
|
23
|
+
*
|
|
24
|
+
* SCOPE (deliberately narrow — a narrower lint with zero false positives beats a broad one with
|
|
25
|
+
* false refusals, per the authoring guidance):
|
|
26
|
+
* - Only DIRECT, first-order plans: `create(Agg).set("ref", payload.path)` /
|
|
27
|
+
* `setEntry(instance(Agg,...), "ref", key, payload.path)` — i.e. shapes the plan-probe
|
|
28
|
+
* mechanism already observes as ONE emitted op whose `value` is byte-identical to a
|
|
29
|
+
* TOP-LEVEL synthesized payload field's sample value. A value that is transformed,
|
|
30
|
+
* concatenated, or nested more than one level deep inside the payload does NOT match
|
|
31
|
+
* (no false positive — it simply isn't flagged; this is intentionally NOT full dataflow
|
|
32
|
+
* analysis over the plan body).
|
|
33
|
+
* - Only fires when the payload field's own sampled value round-trips EXACTLY into the op
|
|
34
|
+
* (relies on `synthesizePayload`'s per-field-distinguishable string samples,
|
|
35
|
+
* `"sample-<field>"` — the same discriminator the proof generator's own subject/grant-field
|
|
36
|
+
* resolution already trusts, see `resolveGrantSubjectFields`).
|
|
37
|
+
* - A directive whose plan can't be probed (unsynthesizable payload, or the plan throws under
|
|
38
|
+
* the synthesized sample) is SKIPPED — best-effort, matching every other plan-probe
|
|
39
|
+
* consumer in `codegen_proof.ts` (never a false refusal from an exotic shape).
|
|
40
|
+
* - `idOf(Agg)` AND `brandedId(Name)` both satisfy the lint (either brand types the Dart/TS
|
|
41
|
+
* side); only a truly BARE `z.string()` (through `.optional()`/`.default()`/`.nullable()`)
|
|
42
|
+
* refuses.
|
|
43
|
+
*
|
|
44
|
+
* NOT era-gated: this is a pure Dart/TS-codegen build-time guard (like the domain-contracts
|
|
45
|
+
* circular-import guard in `compile_package_main.ts`) — it has no wire/manifest/kernel effect,
|
|
46
|
+
* changes no hash, and never touches already-deployed law. It only judges whether `nomos-compile`
|
|
47
|
+
* accepts a directive's SOURCE shape, exactly the same posture as that circular-dependency guard.
|
|
48
|
+
* A domain already using `idOf`/`brandedId` (or one with no ref-fed payload field at all) compiles
|
|
49
|
+
* exactly as before; only a directive whose payload feeds a ref field with a provably-bare string
|
|
50
|
+
* newly refuses — which is the intended fail-closed behaviour, not an accidental regression.
|
|
51
|
+
*/
|
|
52
|
+
import type { z } from "zod";
|
|
53
|
+
|
|
54
|
+
import type { Directive } from "./directive.js";
|
|
55
|
+
import type { FieldOp } from "./ops.js";
|
|
56
|
+
import { probePlanOps } from "./codegen_proof.js";
|
|
57
|
+
import { zodKind, zodDef, zodObjectShape } from "./codegen_ts.js";
|
|
58
|
+
import { idOfMetaOf, brandedIdMetaOf } from "./value_object.js";
|
|
59
|
+
|
|
60
|
+
/** A typed idOf-lint violation — the message names the directive, field, ref, and target. */
|
|
61
|
+
export class IdOfLintError extends Error {
|
|
62
|
+
constructor(message: string) {
|
|
63
|
+
super(message);
|
|
64
|
+
this.name = "IdOfLintError";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Short, lower-camel/PascalCase-preserving stem of an aggregate id (`inventory.EstateSite` → `EstateSite`),
|
|
69
|
+
* matching the same "identity is the tail segment" convention `directive.ts`'s `shortName` uses. */
|
|
70
|
+
function shortAggName(aggregateId: string): string {
|
|
71
|
+
return aggregateId.includes(".") ? aggregateId.slice(aggregateId.lastIndexOf(".") + 1) : aggregateId;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Peel `.optional()`/`.default()`/`.nullable()` to the innermost schema (mirrors `idOfMetaOf`/
|
|
75
|
+
* `brandedIdMetaOf`'s own peeling, so a wrapped bare string is still caught). */
|
|
76
|
+
function isBareString(zt: z.ZodTypeAny): boolean {
|
|
77
|
+
let cur: z.ZodTypeAny | undefined = zt;
|
|
78
|
+
for (let hops = 0; cur !== undefined && hops < 16; hops++) {
|
|
79
|
+
if (idOfMetaOf(cur) !== undefined || brandedIdMetaOf(cur) !== undefined) return false;
|
|
80
|
+
const kind = zodKind(cur);
|
|
81
|
+
if (kind === "string") return true;
|
|
82
|
+
if (kind !== "optional" && kind !== "default" && kind !== "nullable") return false;
|
|
83
|
+
cur = zodDef(cur).innerType;
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Resolve a ref-kind field op's TARGET aggregate id (`t.ref` → `refAggregateId`/thunk; `t.foreignRef` →
|
|
89
|
+
* `foreignRef.targetAggregateId`). `undefined` when neither meta is present (an exotic/hand-built field). */
|
|
90
|
+
function refTargetOf(op: FieldOp): string | undefined {
|
|
91
|
+
const f = op.targetField as
|
|
92
|
+
| { refAggregateId?: string; foreignRef?: { targetAggregateId?: string } }
|
|
93
|
+
| undefined;
|
|
94
|
+
if (f === undefined) return undefined;
|
|
95
|
+
return f.refAggregateId ?? f.foreignRef?.targetAggregateId;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Run the idOf lint over one directive: plan-probe it and refuse on the first payload-fed
|
|
100
|
+
* ref-field mismatch. Throws {@link IdOfLintError}; a directive the prober can't run through
|
|
101
|
+
* (unsynthesizable payload / plan throws) is silently skipped — best-effort, no false refusal.
|
|
102
|
+
*/
|
|
103
|
+
export function assertIdOfLintForDirective(dir: Directive<unknown>, where: string): void {
|
|
104
|
+
const probed = probePlanOps(dir, where);
|
|
105
|
+
if (probed === undefined) return;
|
|
106
|
+
let shape: Record<string, z.ZodTypeAny>;
|
|
107
|
+
try {
|
|
108
|
+
shape = zodObjectShape(dir.payloadSchema as z.ZodTypeAny);
|
|
109
|
+
} catch {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// Reverse index: sampled STRING value → the top-level payload field name(s) that produced it.
|
|
113
|
+
// `synthesizePayload` gives every string-kind field a DISTINCT `"sample-<field>"` value (or a
|
|
114
|
+
// schema-constrained fallback), so an exact match is unambiguous in practice.
|
|
115
|
+
const byValue = new Map<string, string>();
|
|
116
|
+
for (const [name, value] of Object.entries(probed.synth.value)) {
|
|
117
|
+
if (typeof value === "string") byValue.set(value, name);
|
|
118
|
+
}
|
|
119
|
+
for (const op of probed.ops) {
|
|
120
|
+
if (op.kind !== "set" && op.kind !== "setEntry") continue;
|
|
121
|
+
if (typeof op.value !== "string") continue;
|
|
122
|
+
const targetAgg = refTargetOf(op);
|
|
123
|
+
if (targetAgg === undefined) continue; // not a ref-kind field (or one the DSL can't name a target for)
|
|
124
|
+
const payloadField = byValue.get(op.value);
|
|
125
|
+
if (payloadField === undefined) continue; // not traceable to a top-level payload field — no verdict
|
|
126
|
+
const fieldSchema = shape[payloadField];
|
|
127
|
+
if (fieldSchema === undefined || !isBareString(fieldSchema)) continue; // already idOf/brandedId — fine
|
|
128
|
+
const onAgg = shortAggName(op.aggregateType ?? op.aggregateId);
|
|
129
|
+
const targetName = shortAggName(targetAgg);
|
|
130
|
+
throw new IdOfLintError(
|
|
131
|
+
`directive '${dir.id}': payload field '${payloadField}' feeds ref field '${onAgg}.${op.field}' ` +
|
|
132
|
+
`(-> ${targetName}) but is a plain z.string() — wrap it as idOf(${targetName}) so the generated ` +
|
|
133
|
+
`Dart/TS carry the typed id, matching the read model. (dart_codegen_parity.md P2)`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Run the idOf lint over every directive in a domain. `domain` names the module for the probe's
|
|
139
|
+
* error-context strings (`synthesizePayload`'s `where`). Throws on the FIRST violation. */
|
|
140
|
+
export function assertIdOfLint(directives: readonly Directive<unknown>[], domain: string): void {
|
|
141
|
+
for (const dir of directives) {
|
|
142
|
+
assertIdOfLintForDirective(dir, `${domain}/${dir.id}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
package/src/index.ts
CHANGED
|
Binary file
|
package/src/usd_layers.ts
CHANGED
|
@@ -109,6 +109,8 @@ export function usdaStringArray(items: readonly string[]): string {
|
|
|
109
109
|
export function driverToken(driver: WireDriver): string {
|
|
110
110
|
if (typeof driver === "string") return driver;
|
|
111
111
|
if ("LwwBy" in driver) return `LwwBy(${driver.LwwBy})`;
|
|
112
|
+
// `Surfaced(<inner>)` — the user-resolvable posture stays legible in the tree too.
|
|
113
|
+
if ("Surfaced" in driver) return `Surfaced(${driverToken(driver.Surfaced)})`;
|
|
112
114
|
return `MapOf(${driverToken(driver.MapOf)})`;
|
|
113
115
|
}
|
|
114
116
|
|
|
@@ -117,6 +119,9 @@ export function parseDriverToken(token: string): WireDriver {
|
|
|
117
119
|
if (token.startsWith("MapOf(") && token.endsWith(")")) {
|
|
118
120
|
return { MapOf: parseDriverToken(token.slice("MapOf(".length, -1)) };
|
|
119
121
|
}
|
|
122
|
+
if (token.startsWith("Surfaced(") && token.endsWith(")")) {
|
|
123
|
+
return { Surfaced: parseDriverToken(token.slice("Surfaced(".length, -1)) };
|
|
124
|
+
}
|
|
120
125
|
// `LwwBy(<principal>)` — the principal is an opaque non-empty string (uids carry
|
|
121
126
|
// hyphens, so it is NOT the bare-identifier shape the unit-driver branch validates).
|
|
122
127
|
if (token.startsWith("LwwBy(") && token.endsWith(")")) {
|
package/src/wire.ts
CHANGED
|
@@ -219,7 +219,10 @@ export type WireDriver =
|
|
|
219
219
|
| "Counter"
|
|
220
220
|
| "LastPosition"
|
|
221
221
|
| { LwwBy: string }
|
|
222
|
-
| { MapOf: WireDriver }
|
|
222
|
+
| { MapOf: WireDriver }
|
|
223
|
+
// `Surfaced(Box<Driver>)` — the user-resolvable third merge posture
|
|
224
|
+
// (architecture/surfaced_merge_conflict.md): `{ "Surfaced": <inner> }`.
|
|
225
|
+
| { Surfaced: WireDriver };
|
|
223
226
|
|
|
224
227
|
/** `Schema = BTreeMap<FieldId, Driver>` — a plain object, field -> Driver. */
|
|
225
228
|
export type WireSchema = Record<string, WireDriver>;
|