@githolon/dsl 0.107.4 → 0.108.1
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 +24 -0
- package/dart/lib/src/application_framework_calls.dart +299 -0
- package/dart/lib/src/generated_call.dart +96 -0
- package/dart/lib/src/nms_generated_runtime.dart +29 -0
- package/dart/lib/src/node_runner_transport.dart +77 -1
- package/dart/lib/src/result_usda.dart +487 -0
- package/dart/lib/src/runner/node-entry.mjs +10634 -6122
- package/dart/lib/src/runner/offline-install.mjs +4187 -2716
- package/dart/lib/src/runtime_v2_gateway.dart +54 -0
- package/dart/lib/src/runtime_v2_transport.dart +117 -0
- package/dart/lib/src/session_bound.dart +7 -2
- package/dart/lib/src/test_harness.dart +373 -282
- package/dart/lib/src/webview_bridge.dart +2146 -725
- package/dart/pubspec.lock +17 -1
- package/dart/pubspec.yaml +3 -2
- package/dart/test/acquire_session_credentials_test.dart +1 -1
- package/dart/test/acquire_session_test.dart +68 -14
- package/dart/test/adv_realm_interference_test.dart +6 -4
- package/dart/test/cluster_births_test.dart +11 -8
- package/dart/test/cluster_local_autosync_test.dart +1 -1
- package/dart/test/cluster_scale_test.dart +3 -2
- package/dart/test/dlq_session_surface_test.dart +52 -35
- package/dart/test/first_app_gauntlet_test.dart +1 -1
- package/dart/test/local_sync_inert_test.dart +23 -26
- package/dart/test/offer_authentication_proof_test.dart +193 -0
- package/dart/test/result_usda_test.dart +49 -0
- package/dart/test/session_bound_test.dart +8 -11
- package/dart/test/stale_bundle_test.dart +18 -3
- package/dart/test/test_harness_test.dart +14 -27
- package/dart/test/webview_bridge_test.dart +504 -83
- package/dart_types/CHANGELOG.md +17 -0
- package/dart_types/lib/nomos_kernel_protocol.dart +11 -0
- package/dart_types/lib/src/custody.dart +64 -19
- package/dart_types/lib/src/local_work_rejected.dart +30 -45
- package/dart_types/lib/src/proto/nomos/custody/v1/snapshot.pb.dart +169 -0
- package/dart_types/lib/src/proto/nomos/custody/v1/snapshot.pbenum.dart +11 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +17945 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +933 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pb.dart +245 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pbenum.dart +160 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +8664 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pbenum.dart +369 -0
- package/dart_types/lib/src/surfaced.dart +30 -25
- package/dart_types/pubspec.lock +16 -0
- package/dart_types/pubspec.yaml +3 -1
- package/dart_types/test/surfaced_test.dart +2 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +5760 -1879
- package/src/authoring.ts +3 -2
- package/src/birth_meter_ensure.ts +2 -0
- package/src/births.ts +21 -0
- package/src/build_package.ts +30 -0
- package/src/business_birth.ts +41 -35
- package/src/codegen_call_usda.ts +1111 -0
- package/src/codegen_dart.ts +207 -282
- package/src/codegen_dart_application.ts +195 -252
- package/src/codegen_dart_client.ts +1978 -593
- package/src/codegen_dart_framework_calls.ts +291 -0
- package/src/codegen_dart_scenario.ts +7 -0
- package/src/codegen_proof.ts +3 -4
- package/src/codegen_provider_dart.ts +21 -7
- package/src/codegen_query_usda.ts +214 -0
- package/src/codegen_result_usda.ts +685 -0
- package/src/codegen_ts.ts +3945 -1111
- package/src/compile_package_main.ts +392 -46
- package/src/directive.ts +19 -4
- package/src/engine_entry.ts +45 -4
- package/src/framework/application_home.ts +4 -0
- package/src/framework/auth_provider_law.ts +296 -20
- package/src/framework/birth_certificate_law.ts +24 -0
- package/src/framework/home.ts +83 -40
- package/src/framework/owned_custody.ts +25 -12
- package/src/framework/owned_custody_value.ts +51 -0
- package/src/framework/relations.ts +47 -21
- package/src/framework/signing_identity.ts +100 -17
- package/src/framework/workspaces.ts +39 -10
- package/src/framework_call_definitions.ts +53 -0
- package/src/framework_read_definitions.ts +45 -0
- package/src/identity_frontier.ts +58 -0
- package/src/index.ts +22 -1
- package/src/intent.ts +216 -16
- package/src/law_check.ts +19 -1
- package/src/law_graph.ts +57 -8
- package/src/law_graph_usda.ts +73 -5
- package/src/law_value_type.ts +106 -19
- package/src/read.ts +47 -0
- package/src/read_projection_namespace.ts +7 -0
- package/src/shape_compat.ts +44 -42
- package/src/stable_ids.ts +37 -2
- package/src/test_call_usda.ts +83 -0
- package/src/wire.ts +36 -2
- package/src/wire_encode.ts +45 -1
- package/src/workspace_sharing.ts +27 -10
package/src/law_value_type.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import type { z } from "zod";
|
|
2
2
|
import { enumTypeNameOf } from "./enum_type.js";
|
|
3
|
+
import { isGitObjectIdSchema, isSha256DigestSchema } from "./fields.js";
|
|
4
|
+
import { recipientMetaOf } from "./recipient.js";
|
|
5
|
+
import {
|
|
6
|
+
NOMOS_UNIT_BRAND,
|
|
7
|
+
brandedIdMetaOf,
|
|
8
|
+
idOfMetaOf,
|
|
9
|
+
idOfTargetKindOf,
|
|
10
|
+
refOfMetaOf,
|
|
11
|
+
versionedRefOfMetaOf,
|
|
12
|
+
voMetaOf,
|
|
13
|
+
} from "./value_object.js";
|
|
3
14
|
import {
|
|
4
15
|
zodArrayElement,
|
|
5
16
|
zodDef,
|
|
@@ -24,7 +35,33 @@ export interface LawValueMember {
|
|
|
24
35
|
readonly value: LawValueType;
|
|
25
36
|
}
|
|
26
37
|
|
|
27
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Nominal meaning carried by an otherwise structural value.
|
|
40
|
+
*
|
|
41
|
+
* These markers are lowered from the authored DSL once, into the transient law
|
|
42
|
+
* graph. Language generators must consume these markers instead of consulting
|
|
43
|
+
* Zod brands independently. They are semantic type facts, not host API or
|
|
44
|
+
* presentation metadata.
|
|
45
|
+
*/
|
|
46
|
+
export type LawValueSemantic =
|
|
47
|
+
| { readonly kind: "sha256-digest" | "git-object-id" }
|
|
48
|
+
| { readonly kind: "branded-id" | "unit" | "value-object"; readonly name: string }
|
|
49
|
+
| { readonly kind: "recipient"; readonly subject: "User" }
|
|
50
|
+
| {
|
|
51
|
+
readonly kind: "id-reference" | "address-reference";
|
|
52
|
+
readonly target: string;
|
|
53
|
+
readonly targetKind: "aggregate" | "contract";
|
|
54
|
+
}
|
|
55
|
+
| {
|
|
56
|
+
readonly kind: "versioned-reference";
|
|
57
|
+
readonly target: string;
|
|
58
|
+
readonly targetKind: "aggregate" | "contract";
|
|
59
|
+
readonly series: string;
|
|
60
|
+
readonly seriesKind: "aggregate" | "contract";
|
|
61
|
+
readonly currentField: string;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
type LawValueShape =
|
|
28
65
|
| { readonly kind: "string" | "number" | "integer"; readonly constraints: readonly LawValueConstraint[] }
|
|
29
66
|
| { readonly kind: "boolean" | "null" | "unknown" }
|
|
30
67
|
| { readonly kind: "enum"; readonly values: readonly string[]; readonly name?: string }
|
|
@@ -38,6 +75,8 @@ export type LawValueType =
|
|
|
38
75
|
| { readonly kind: "recursive"; readonly anchor: string; readonly value: LawValueType }
|
|
39
76
|
| { readonly kind: "recursive-ref"; readonly anchor: string };
|
|
40
77
|
|
|
78
|
+
export type LawValueType = LawValueShape & { readonly semantic?: LawValueSemantic };
|
|
79
|
+
|
|
41
80
|
interface ZodCheck {
|
|
42
81
|
readonly check?: string;
|
|
43
82
|
readonly minimum?: number;
|
|
@@ -77,65 +116,72 @@ function lower(
|
|
|
77
116
|
): LawValueType {
|
|
78
117
|
const kind = zodKind(schema);
|
|
79
118
|
const def = zodDef(schema) as ZodExtendedDef;
|
|
119
|
+
// Omission/null wrappers are structural. Their contained value owns the
|
|
120
|
+
// nominal marker, so generators can unwrap them without duplicating meaning.
|
|
121
|
+
const semantic = kind === "optional" || kind === "default" || kind === "nullable"
|
|
122
|
+
? undefined
|
|
123
|
+
: semanticOf(schema);
|
|
124
|
+
const finish = (value: LawValueShape): LawValueType =>
|
|
125
|
+
semantic === undefined ? value : { ...value, semantic };
|
|
80
126
|
switch (kind) {
|
|
81
127
|
case "string":
|
|
82
|
-
return { kind: "string", constraints: constraintsOf(def) };
|
|
128
|
+
return finish({ kind: "string", constraints: constraintsOf(def) });
|
|
83
129
|
case "number": {
|
|
84
130
|
const constraints = constraintsOf(def);
|
|
85
131
|
const integer = def.checks?.some((check) => checkDef(check).format === "safeint") === true;
|
|
86
|
-
return { kind: integer ? "integer" : "number", constraints };
|
|
132
|
+
return finish({ kind: integer ? "integer" : "number", constraints });
|
|
87
133
|
}
|
|
88
134
|
case "boolean":
|
|
89
|
-
return { kind: "boolean" };
|
|
135
|
+
return finish({ kind: "boolean" });
|
|
90
136
|
case "null":
|
|
91
|
-
return { kind: "null" };
|
|
137
|
+
return finish({ kind: "null" });
|
|
92
138
|
case "unknown":
|
|
93
139
|
case "any":
|
|
94
|
-
return { kind: "unknown" };
|
|
140
|
+
return finish({ kind: "unknown" });
|
|
95
141
|
case "enum": {
|
|
96
142
|
const name = enumTypeNameOf(schema);
|
|
97
|
-
return {
|
|
143
|
+
return finish({
|
|
98
144
|
kind: "enum",
|
|
99
145
|
values: zodEnumValues(schema).sort(compareText),
|
|
100
146
|
...(name === undefined ? {} : { name }),
|
|
101
|
-
};
|
|
147
|
+
});
|
|
102
148
|
}
|
|
103
149
|
case "literal":
|
|
104
|
-
return { kind: "literal", value: literalOf(def, path) };
|
|
150
|
+
return finish({ kind: "literal", value: literalOf(def, path) });
|
|
105
151
|
case "array":
|
|
106
|
-
return { kind: "list", element: lower(zodArrayElement(schema), `${path}[]`, activeLazy) };
|
|
152
|
+
return finish({ kind: "list", element: lower(zodArrayElement(schema), `${path}[]`, activeLazy) });
|
|
107
153
|
case "record": {
|
|
108
154
|
if (def.valueType === undefined) fail(path, "record has no value type");
|
|
109
|
-
return { kind: "map", value: lower(def.valueType, `${path}.*`, activeLazy) };
|
|
155
|
+
return finish({ kind: "map", value: lower(def.valueType, `${path}.*`, activeLazy) });
|
|
110
156
|
}
|
|
111
157
|
case "object":
|
|
112
|
-
return {
|
|
158
|
+
return finish({
|
|
113
159
|
kind: "record",
|
|
114
160
|
fields: Object.entries(zodObjectShape(schema))
|
|
115
161
|
.sort(([a], [b]) => compareText(a, b))
|
|
116
162
|
.map(([name, field]) => lowerMember(name, field, path, activeLazy)),
|
|
117
|
-
};
|
|
163
|
+
});
|
|
118
164
|
case "union":
|
|
119
|
-
return {
|
|
165
|
+
return finish({
|
|
120
166
|
kind: "sum",
|
|
121
167
|
options: (def.options ?? []).map((option, index) =>
|
|
122
168
|
lower(option, `${path}|${index}`, activeLazy)),
|
|
123
|
-
};
|
|
169
|
+
});
|
|
124
170
|
case "optional": {
|
|
125
171
|
if (def.innerType === undefined) fail(path, "optional has no inner type");
|
|
126
172
|
return lower(def.innerType, path, activeLazy);
|
|
127
173
|
}
|
|
128
174
|
case "nullable": {
|
|
129
175
|
if (def.innerType === undefined) fail(path, "nullable has no inner type");
|
|
130
|
-
return { kind: "nullable", value: lower(def.innerType, path, activeLazy) };
|
|
176
|
+
return finish({ kind: "nullable", value: lower(def.innerType, path, activeLazy) });
|
|
131
177
|
}
|
|
132
178
|
case "default": {
|
|
133
179
|
if (def.innerType === undefined) fail(path, "default has no inner type");
|
|
134
|
-
return {
|
|
180
|
+
return finish({
|
|
135
181
|
kind: "default",
|
|
136
182
|
value: lower(def.innerType, path, activeLazy),
|
|
137
183
|
defaultValue: constant(def.defaultValue, path, "default"),
|
|
138
|
-
};
|
|
184
|
+
});
|
|
139
185
|
}
|
|
140
186
|
case "lazy": {
|
|
141
187
|
const existing = activeLazy.get(schema);
|
|
@@ -144,13 +190,54 @@ function lower(
|
|
|
144
190
|
activeLazy.set(schema, path);
|
|
145
191
|
const value = lower(def.getter(), path, activeLazy);
|
|
146
192
|
activeLazy.delete(schema);
|
|
147
|
-
return { kind: "recursive", anchor: path, value };
|
|
193
|
+
return finish({ kind: "recursive", anchor: path, value });
|
|
148
194
|
}
|
|
149
195
|
default:
|
|
150
196
|
fail(path, `unsupported Zod type '${kind}'`);
|
|
151
197
|
}
|
|
152
198
|
}
|
|
153
199
|
|
|
200
|
+
function semanticOf(schema: z.ZodTypeAny): LawValueSemantic | undefined {
|
|
201
|
+
if (isSha256DigestSchema(schema)) return { kind: "sha256-digest" };
|
|
202
|
+
if (isGitObjectIdSchema(schema)) return { kind: "git-object-id" };
|
|
203
|
+
const addressed = refOfMetaOf(schema);
|
|
204
|
+
if (addressed !== undefined) {
|
|
205
|
+
return {
|
|
206
|
+
kind: "address-reference",
|
|
207
|
+
target: addressed.aggregateType,
|
|
208
|
+
targetKind: addressed.targetKind,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const versioned = versionedRefOfMetaOf(schema);
|
|
212
|
+
if (versioned !== undefined) {
|
|
213
|
+
return {
|
|
214
|
+
kind: "versioned-reference",
|
|
215
|
+
target: versioned.targetAggregateType,
|
|
216
|
+
targetKind: versioned.targetKind,
|
|
217
|
+
series: versioned.seriesAggregateType,
|
|
218
|
+
seriesKind: versioned.seriesKind,
|
|
219
|
+
currentField: versioned.currentField,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
const recipient = recipientMetaOf(schema);
|
|
223
|
+
if (recipient !== undefined) return { kind: "recipient", subject: recipient };
|
|
224
|
+
const target = idOfMetaOf(schema);
|
|
225
|
+
if (target !== undefined) {
|
|
226
|
+
return {
|
|
227
|
+
kind: "id-reference",
|
|
228
|
+
target,
|
|
229
|
+
targetKind: idOfTargetKindOf(schema) ?? "aggregate",
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const brand = brandedIdMetaOf(schema);
|
|
233
|
+
if (brand !== undefined) return { kind: "branded-id", name: brand };
|
|
234
|
+
const unit = (schema as unknown as Record<symbol, unknown>)[NOMOS_UNIT_BRAND];
|
|
235
|
+
if (typeof unit === "string") return { kind: "unit", name: unit };
|
|
236
|
+
const valueObject = voMetaOf(schema);
|
|
237
|
+
if (valueObject !== undefined) return { kind: "value-object", name: valueObject.name };
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
|
|
154
241
|
function lowerMember(
|
|
155
242
|
name: string,
|
|
156
243
|
schema: z.ZodTypeAny,
|
package/src/read.ts
CHANGED
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
* it read to justify them. The dev never sees `nomos.read` — they call typed DSL queries; the library
|
|
18
18
|
* routes them here. (See `docs/aggregate_lifecycle_and_relations.md`.)
|
|
19
19
|
*/
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
import type { AggregateHandle, ReadModelRow, StoredFields } from "./aggregate.js";
|
|
22
|
+
import type { Field } from "./fields.js";
|
|
20
23
|
import type { QueryDecl } from "./query.js";
|
|
21
24
|
import type { WireRead, WireReadAttestation } from "./wire.js";
|
|
22
25
|
import { NomosSourceFrontier, sourceFrontierId, __trustFor, __isMonotoneForeignQuery, __ensure } from "./attested_core.js";
|
|
@@ -103,6 +106,50 @@ export function readFrameworkDeclared<T = unknown>(queryId: string, args: Record
|
|
|
103
106
|
return result;
|
|
104
107
|
}
|
|
105
108
|
|
|
109
|
+
type ProjectionShape<F extends Record<string, Field>> = {
|
|
110
|
+
[K in keyof F]: z.ZodOptional<F[K]["zod"]>;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Decode one framework query's standard projected rows from the SAME aggregate field declarations
|
|
115
|
+
* that authored its law. The only type assertion is at the TypeScript `Object.fromEntries` seam:
|
|
116
|
+
* every runtime member is still obtained from `Field.zod`, so there is no second field-name/type list
|
|
117
|
+
* and no unvalidated object crosses into a framework plan.
|
|
118
|
+
*
|
|
119
|
+
* Historical captured reads may contain either the current `{ type, id, data }` projection envelope
|
|
120
|
+
* or the earlier flat data row. Both pass through the identical derived schema before being returned.
|
|
121
|
+
*/
|
|
122
|
+
export function readFrameworkDeclaredRows<
|
|
123
|
+
const Id extends string,
|
|
124
|
+
F extends Record<string, Field>,
|
|
125
|
+
>(
|
|
126
|
+
query: QueryDecl,
|
|
127
|
+
aggregate: AggregateHandle<Id, F>,
|
|
128
|
+
args: Record<string, string>,
|
|
129
|
+
): Array<ReadModelRow<F>> {
|
|
130
|
+
if (query.returns !== aggregate.id) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`framework read('${query.id}') declares '${query.returns}', not '${aggregate.id}'`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
const fields: Readonly<Record<string, Field>> = aggregate.fields;
|
|
136
|
+
const shape = Object.fromEntries(
|
|
137
|
+
Object.entries(fields).map(([name, field]) => [name, field.zod.optional()]),
|
|
138
|
+
) as ProjectionShape<StoredFields<F>>;
|
|
139
|
+
const data = z.object(shape).strict() as z.ZodType<ReadModelRow<F>>;
|
|
140
|
+
// Recognize the envelope before validating its data. If the payload is malformed, validation must
|
|
141
|
+
// report the actual aggregate field (for example a historical `keys` member), not fall through and
|
|
142
|
+
// misdiagnose the envelope's `type`/`id` metadata as flat-row fields.
|
|
143
|
+
const envelope = z.object({ data: z.unknown() }).passthrough();
|
|
144
|
+
const row = z.union([envelope, z.unknown()])
|
|
145
|
+
.transform((value) => {
|
|
146
|
+
const wrapped = envelope.safeParse(value);
|
|
147
|
+
return wrapped.success ? wrapped.data.data : value;
|
|
148
|
+
})
|
|
149
|
+
.pipe(data);
|
|
150
|
+
return z.array(row).parse(readFrameworkDeclared(query.id, args));
|
|
151
|
+
}
|
|
152
|
+
|
|
106
153
|
function readForeign<T = unknown>(query: QueryDecl | { id: string }, args: Record<string, string>, from: string): T {
|
|
107
154
|
// ── THE FOREIGN (attested cross-workspace) LANE (slice B — attested_reads_design.md §4.1).
|
|
108
155
|
// The port answers the `{rows, attestation}` wrapper: at AUTHOR the kernel verified the
|
|
@@ -108,6 +108,13 @@ export const GLOBAL_READ_PROJECTION_AGGREGATE_IDS: ReadonlySet<string> = new Set
|
|
|
108
108
|
"PlatformGrant",
|
|
109
109
|
"CapabilityBinding",
|
|
110
110
|
"AuthProvider",
|
|
111
|
+
// The kernel reconstructs the active verifier by the framework-owned aggregate
|
|
112
|
+
// ids/prefixes, and child law copies the same typed rows across domain boundaries.
|
|
113
|
+
// Namespacing these two types made identical framework queries domain-local:
|
|
114
|
+
// bootstrap wrote application-home.AuthProviderKey while a tenant Home birth
|
|
115
|
+
// read home.AuthProviderKey, silently dropping every verifier key.
|
|
116
|
+
"AuthProviderKey",
|
|
117
|
+
"AuthProviderCertificate",
|
|
111
118
|
"HostnameClaim",
|
|
112
119
|
// governance postures (managed / sovereign) — the tenant-owned posture singleton + consent facts
|
|
113
120
|
"GovernancePosture",
|
package/src/shape_compat.ts
CHANGED
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
// is reported precisely).
|
|
24
24
|
|
|
25
25
|
import { z } from "zod";
|
|
26
|
-
import {
|
|
26
|
+
import type { DomainModule } from "./codegen_dart.js";
|
|
27
|
+
import { lowerLawValueType, type LawValueType } from "./law_value_type.js";
|
|
27
28
|
import type { StableIds } from "./stable_ids_types.js";
|
|
28
29
|
import type { Field } from "./fields.js";
|
|
29
30
|
import { isBusinessIntent } from "./intent.js";
|
|
@@ -113,46 +114,40 @@ export interface CompatVerdict {
|
|
|
113
114
|
readonly incompatible: string[];
|
|
114
115
|
}
|
|
115
116
|
|
|
116
|
-
/** Normalize
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
function payloadFieldKind(
|
|
120
|
-
switch (
|
|
117
|
+
/** Normalize the canonical language-neutral DSL value graph to the compatibility
|
|
118
|
+
* vocabulary. Language generators are projections of this graph, never authorities
|
|
119
|
+
* for it. */
|
|
120
|
+
function payloadFieldKind(value: LawValueType): string {
|
|
121
|
+
switch (value.kind) {
|
|
121
122
|
case "enum":
|
|
122
123
|
return "enum";
|
|
123
|
-
case "
|
|
124
|
-
return "set";
|
|
125
|
-
case "json":
|
|
126
|
-
return "json";
|
|
127
|
-
case "wrapped":
|
|
128
|
-
// typed-id (string wire) or unit (number wire) — the interface sees the WIRE kind.
|
|
129
|
-
return spec.wrappedScalarKind === "string" ? "string" : "double";
|
|
130
|
-
case "ref":
|
|
131
|
-
// NomosRef<T> is aggregate-typed in the client and remains a string id on the wire.
|
|
132
|
-
return "string";
|
|
133
|
-
case "addressRef":
|
|
134
|
-
// A workspace-qualified NomosRef<T> preserves its complete address object.
|
|
135
|
-
return "json";
|
|
136
|
-
case "recipient":
|
|
137
|
-
// Recipient<User> is nominal in clients and an opaque delivery address on the law wire.
|
|
124
|
+
case "string":
|
|
138
125
|
return "string";
|
|
139
|
-
case "
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
126
|
+
case "integer":
|
|
127
|
+
return "int";
|
|
128
|
+
case "number":
|
|
129
|
+
return "double";
|
|
130
|
+
case "boolean":
|
|
131
|
+
return "bool";
|
|
132
|
+
case "literal":
|
|
133
|
+
return typeof value.value === "boolean"
|
|
134
|
+
? "bool"
|
|
135
|
+
: typeof value.value === "number"
|
|
136
|
+
? Number.isInteger(value.value) ? "int" : "double"
|
|
137
|
+
: typeof value.value === "string" ? "string" : "json";
|
|
138
|
+
case "list":
|
|
139
|
+
return value.element.kind === "string" ? "set" : "json";
|
|
140
|
+
case "nullable":
|
|
141
|
+
case "default":
|
|
142
|
+
case "recursive":
|
|
143
|
+
return payloadFieldKind(value.value);
|
|
144
|
+
case "map":
|
|
145
|
+
case "record":
|
|
146
|
+
case "sum":
|
|
147
|
+
case "null":
|
|
148
|
+
case "unknown":
|
|
149
|
+
case "recursive-ref":
|
|
143
150
|
return "json";
|
|
144
|
-
case "scalar":
|
|
145
|
-
default:
|
|
146
|
-
switch (spec.dartType) {
|
|
147
|
-
case "int":
|
|
148
|
-
return "int";
|
|
149
|
-
case "double":
|
|
150
|
-
return "double";
|
|
151
|
-
case "bool":
|
|
152
|
-
return "bool";
|
|
153
|
-
default:
|
|
154
|
-
return "string";
|
|
155
|
-
}
|
|
156
151
|
}
|
|
157
152
|
}
|
|
158
153
|
|
|
@@ -198,8 +193,12 @@ export function domainInterface(mod: DomainModule, stableIds?: StableIds): Domai
|
|
|
198
193
|
const fieldEntries: Array<[string, PayloadFieldShape]> = [];
|
|
199
194
|
const schema = d.businessBirth?.publicPayloadSchema ?? d.payloadSchema;
|
|
200
195
|
if (schema && typeof schema === "object") {
|
|
201
|
-
|
|
202
|
-
|
|
196
|
+
const payload = lowerLawValueType(schema as z.ZodTypeAny);
|
|
197
|
+
if (payload.kind !== "record") {
|
|
198
|
+
throw new Error(`directive '${d.id}' payload must lower to a typed record`);
|
|
199
|
+
}
|
|
200
|
+
for (const field of payload.fields) {
|
|
201
|
+
fieldEntries.push([field.name, { kind: payloadFieldKind(field.value), optional: !field.required }]);
|
|
203
202
|
}
|
|
204
203
|
}
|
|
205
204
|
dirEntries.push([
|
|
@@ -213,16 +212,19 @@ export function domainInterface(mod: DomainModule, stableIds?: StableIds): Domai
|
|
|
213
212
|
for (const agg of (mod.aggregates ?? []) as Array<{
|
|
214
213
|
id: string;
|
|
215
214
|
fields: Record<string, Field>;
|
|
216
|
-
internalFields?: readonly string[];
|
|
217
215
|
}>) {
|
|
218
216
|
if (isInternal(agg)) continue;
|
|
219
217
|
const label = agg.id;
|
|
220
218
|
const sa = stableIds?.aggregates?.[label];
|
|
221
219
|
const aggSid = sa?.sid ?? label;
|
|
222
220
|
const fieldEntries: Array<[string, { kind: string; label: string }]> = [];
|
|
223
|
-
const internal = new Set(agg.internalFields ?? []);
|
|
224
221
|
for (const [fname, f] of Object.entries(agg.fields ?? {})) {
|
|
225
|
-
|
|
222
|
+
// This is the workspace's complete structural offer and the input to law-evolution
|
|
223
|
+
// checking, so framework-managed stored fields belong here too. Only the generated
|
|
224
|
+
// client's view hides them, in publicClientInterface below. Filtering them at both
|
|
225
|
+
// seams makes an unchanged private field look deleted when the current offer is
|
|
226
|
+
// compared with the committed identity ledger.
|
|
227
|
+
if (!f || f.kind === "hasMany") continue;
|
|
226
228
|
const fsid = sa?.fields?.[fname]?.sid ?? fname;
|
|
227
229
|
const kind = sa?.fields?.[fname]?.kind ?? aggFieldReadKind(f);
|
|
228
230
|
fieldEntries.push([fsid, { kind, label: fname }]);
|
package/src/stable_ids.ts
CHANGED
|
@@ -92,6 +92,16 @@ export interface InferredRename {
|
|
|
92
92
|
readonly sid: string;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
/** One authored retirement from the immediately preceding model. Retirement is
|
|
96
|
+
* semantic evidence that a disappearance is intentional, so it must take
|
|
97
|
+
* precedence over the shape heuristic used only to infer otherwise-silent
|
|
98
|
+
* renames. */
|
|
99
|
+
export interface StableIdRetirement {
|
|
100
|
+
readonly kind: "aggregate" | "field";
|
|
101
|
+
readonly aggregate: string;
|
|
102
|
+
readonly field?: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
95
105
|
/**
|
|
96
106
|
* Derive the stable-id CONTINUITY for one domain from its PRIOR manifest's stableIds
|
|
97
107
|
* (the chain/build holds v's sids) against the CURRENT compile's names.
|
|
@@ -120,12 +130,29 @@ export function deriveStableIdContinuity(
|
|
|
120
130
|
/** The prior canonical manifest's aggregate schemas (keyed by PRIOR name) — when
|
|
121
131
|
* supplied, tightens the field-pair rule to same-driver only. */
|
|
122
132
|
priorSchemas?: Record<string, WireSchema>,
|
|
133
|
+
/** Explicit candidate-law retirements. Their exact prior SIDs are excluded from
|
|
134
|
+
* heuristic rename inference; a same-shaped replacement therefore mints its own
|
|
135
|
+
* identity while an undeclared, genuine rename still carries continuity. */
|
|
136
|
+
retirements: readonly StableIdRetirement[] = [],
|
|
123
137
|
): { continuity: StableIds; inferred: InferredRename[] } {
|
|
124
138
|
const inferred: InferredRename[] = [];
|
|
125
139
|
const domainSid = prior?.domain ?? mintDomainSid(domainName);
|
|
126
140
|
|
|
127
141
|
const priorAggs = prior?.aggregates ?? {};
|
|
128
142
|
const currentNames = new Set(currentAggregates.map((a) => a.id));
|
|
143
|
+
const explicitlyRetiredSids = new Set<string>();
|
|
144
|
+
for (const retirement of retirements) {
|
|
145
|
+
const priorAggregate = priorAggs[retirement.aggregate];
|
|
146
|
+
if (priorAggregate === undefined) continue;
|
|
147
|
+
if (retirement.kind === "aggregate") {
|
|
148
|
+
explicitlyRetiredSids.add(priorAggregate.sid);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const priorField = retirement.field === undefined
|
|
152
|
+
? undefined
|
|
153
|
+
: priorAggregate.fields[retirement.field];
|
|
154
|
+
if (priorField !== undefined) explicitlyRetiredSids.add(priorField.sid);
|
|
155
|
+
}
|
|
129
156
|
|
|
130
157
|
// Pass 1 — name matches.
|
|
131
158
|
const resolved: Record<
|
|
@@ -151,7 +178,11 @@ export function deriveStableIdContinuity(
|
|
|
151
178
|
// Pass 2 — the unambiguous aggregate pair (one disappeared, one appeared, same shape).
|
|
152
179
|
const disappeared = Object.keys(priorAggs).filter((n) => !currentNames.has(n));
|
|
153
180
|
const appeared = currentAggregates.filter((a) => resolved[a.id] === undefined);
|
|
154
|
-
if (
|
|
181
|
+
if (
|
|
182
|
+
disappeared.length === 1 &&
|
|
183
|
+
appeared.length === 1 &&
|
|
184
|
+
!explicitlyRetiredSids.has(priorAggs[disappeared[0]!]!.sid)
|
|
185
|
+
) {
|
|
155
186
|
const oldName = disappeared[0]!;
|
|
156
187
|
const oldAgg = priorAggs[oldName]!;
|
|
157
188
|
const newAgg = appeared[0]!;
|
|
@@ -192,7 +223,11 @@ export function deriveStableIdContinuity(
|
|
|
192
223
|
}
|
|
193
224
|
const fDisappeared = Object.keys(priorFields).filter((n) => !fieldNames.includes(n));
|
|
194
225
|
const fAppeared = fieldNames.filter((n) => fields[n] === undefined);
|
|
195
|
-
if (
|
|
226
|
+
if (
|
|
227
|
+
fDisappeared.length === 1 &&
|
|
228
|
+
fAppeared.length >= 1 &&
|
|
229
|
+
!explicitlyRetiredSids.has(priorFields[fDisappeared[0]!]!.sid)
|
|
230
|
+
) {
|
|
196
231
|
const oldF = fDisappeared[0]!;
|
|
197
232
|
// Shape gate for a field rename: the DRIVER (merge law) AND the KIND (read
|
|
198
233
|
// type) must be unchanged — a rename is a LABEL move. Among the appeared
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { DomainModule } from "./codegen_dart.js";
|
|
2
|
+
import { identityRegistryFromModel, type CompilerIdentityModel } from "./identity_model.js";
|
|
3
|
+
import { discoverLawGraph } from "./law_graph.js";
|
|
4
|
+
import { compileSparseLawGraphUsda } from "./law_graph_usda.js";
|
|
5
|
+
import { stableIdsForModule } from "./stable_ids_for_module.js";
|
|
6
|
+
import { collectValueObjectDescriptors } from "./value_object.js";
|
|
7
|
+
|
|
8
|
+
/** Build the same canonical graph/path input the package compiler supplies to
|
|
9
|
+
* the generated-client encoders. Tests must not resurrect a dictionary fallback
|
|
10
|
+
* merely to exercise a lower-level generator directly. */
|
|
11
|
+
function callUsdaForTestIdentity(
|
|
12
|
+
module: DomainModule,
|
|
13
|
+
identityModel?: CompilerIdentityModel,
|
|
14
|
+
closureModules: readonly DomainModule[] = [module],
|
|
15
|
+
) {
|
|
16
|
+
// A low-level generator fixture does not compose the framework birth-grant
|
|
17
|
+
// query that the package compiler supplies. Keep these tests honest about
|
|
18
|
+
// the call shape without pretending that unrelated framework reads are part
|
|
19
|
+
// of the tenant domain under test.
|
|
20
|
+
const normalize = (candidate: DomainModule): DomainModule => ({
|
|
21
|
+
...candidate,
|
|
22
|
+
aggregates: candidate.aggregates.map((aggregate) =>
|
|
23
|
+
aggregate.audience === undefined
|
|
24
|
+
? { ...aggregate, audience: { kind: "public" as const } }
|
|
25
|
+
: aggregate),
|
|
26
|
+
valueObjects: collectValueObjectDescriptors(candidate.aggregates),
|
|
27
|
+
directives: candidate.directives.map((directive) => ({
|
|
28
|
+
...directive,
|
|
29
|
+
...((directive as { birthsChild?: boolean }).birthsChild === true
|
|
30
|
+
? { declaredQueryReads: [] }
|
|
31
|
+
: {}),
|
|
32
|
+
})),
|
|
33
|
+
});
|
|
34
|
+
const byName = new Map(
|
|
35
|
+
[module, ...closureModules].map((candidate) => [candidate.name, normalize(candidate)]),
|
|
36
|
+
);
|
|
37
|
+
const graphModules = [...byName.values()];
|
|
38
|
+
const stableIdsByName = new Map(
|
|
39
|
+
graphModules.map((candidate) => [candidate.name, stableIdsForModule(candidate)]),
|
|
40
|
+
);
|
|
41
|
+
const registry = identityRegistryFromModel(identityModel ?? ({
|
|
42
|
+
v: 1,
|
|
43
|
+
domains: Object.fromEntries(
|
|
44
|
+
graphModules.map((candidate) => [
|
|
45
|
+
candidate.name,
|
|
46
|
+
{ stableIds: stableIdsByName.get(candidate.name)! },
|
|
47
|
+
]),
|
|
48
|
+
),
|
|
49
|
+
} satisfies CompilerIdentityModel));
|
|
50
|
+
const graphs = graphModules.map((candidate) => discoverLawGraph(
|
|
51
|
+
candidate,
|
|
52
|
+
stableIdsByName.get(candidate.name)!,
|
|
53
|
+
registry,
|
|
54
|
+
).graph);
|
|
55
|
+
const graph = graphs.find((candidate) => candidate.domain.entity.label === module.name);
|
|
56
|
+
if (graph === undefined) throw new Error(`test law graph '${module.name}' was not composed`);
|
|
57
|
+
const rendered = compileSparseLawGraphUsda(
|
|
58
|
+
graph,
|
|
59
|
+
`/${module.name}.law.usda`,
|
|
60
|
+
graphs,
|
|
61
|
+
);
|
|
62
|
+
return { graph, paths: rendered.paths };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function callUsdaForTest(module: DomainModule) {
|
|
66
|
+
return callUsdaForTestIdentity(module);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function callUsdaForTestWithClosure(
|
|
70
|
+
module: DomainModule,
|
|
71
|
+
closureModules: readonly DomainModule[],
|
|
72
|
+
) {
|
|
73
|
+
return callUsdaForTestIdentity(module, undefined, closureModules);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Use the compiler's committed/evolved identity ledger when a runtime regression must author
|
|
77
|
+
* against an already compiled fixture rather than a first-generation synthetic model. */
|
|
78
|
+
export function callUsdaForTestWithIdentity(
|
|
79
|
+
module: DomainModule,
|
|
80
|
+
identityModel: CompilerIdentityModel,
|
|
81
|
+
) {
|
|
82
|
+
return callUsdaForTestIdentity(module, identityModel);
|
|
83
|
+
}
|
package/src/wire.ts
CHANGED
|
@@ -55,9 +55,11 @@ export type WireValue =
|
|
|
55
55
|
revokedAt?: string;
|
|
56
56
|
validFromEpoch?: number;
|
|
57
57
|
verifiedVia?: string;
|
|
58
|
-
ownedCustody?:
|
|
58
|
+
ownedCustody?: WireOwnedCustody;
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
|
+
| { OwnedCustody: WireOwnedCustody }
|
|
62
|
+
| { OwnedDeviceRequest: WireOwnedDeviceRequest }
|
|
61
63
|
| { Evidence: WireEvidenceRef }
|
|
62
64
|
// First-class canonical JSON (RFC 8785 / JCS) — the committed form of a `t.json()` leaf. The
|
|
63
65
|
// value rides INLINE (`{Json: <any json>}`), not as a string-of-JSON; the kernel canonicalises
|
|
@@ -69,6 +71,25 @@ export type WireValue =
|
|
|
69
71
|
| { Position: string }
|
|
70
72
|
| { TwoPhaseSet: { adds: string[]; removes: string[] } };
|
|
71
73
|
|
|
74
|
+
export interface WireOwnedDeviceRequest {
|
|
75
|
+
v: number;
|
|
76
|
+
workspace: string;
|
|
77
|
+
principal: string;
|
|
78
|
+
scope: string;
|
|
79
|
+
signingPublic: string;
|
|
80
|
+
wrappingPublic: string;
|
|
81
|
+
signature: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface WireOwnedCustody {
|
|
85
|
+
v: number;
|
|
86
|
+
request: WireOwnedDeviceRequest;
|
|
87
|
+
hpkeEpk: string;
|
|
88
|
+
ct: string;
|
|
89
|
+
keyCommitment: string;
|
|
90
|
+
principalWrappingPublic: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
72
93
|
/** `EvidenceRef { hash: ContentHash, media_type, byte_length, storage_class }` — the
|
|
73
94
|
* kernel serde shape. `ContentHash(String)` serializes transparently as a bare string
|
|
74
95
|
* (a newtype struct), and `StorageClass` as a bare unit-variant string. */
|
|
@@ -166,7 +187,20 @@ export interface WireIntent {
|
|
|
166
187
|
* child's OWN gate. A verify-input WITNESS (rides the carried `captured_ports`, never the events nor the
|
|
167
188
|
* OID). OMITTED when empty so a birth-free intent is byte-identical to before this existed (the era rule).
|
|
168
189
|
*/
|
|
169
|
-
births?: {
|
|
190
|
+
births?: {
|
|
191
|
+
workspace: string;
|
|
192
|
+
genesisChain: unknown;
|
|
193
|
+
parentWorkspaceRoles?: readonly import("./births.js").WorkspaceRole[];
|
|
194
|
+
}[];
|
|
195
|
+
/** Exact target calls authored by source law. The OpenUSD bytes come from a compiler-generated
|
|
196
|
+
* encoder for the target Directive object; no public operation string or generic payload crosses. */
|
|
197
|
+
workspaceEffects?: {
|
|
198
|
+
destination:
|
|
199
|
+
| { readonly workspace: string }
|
|
200
|
+
| { readonly verifiedParent: true }
|
|
201
|
+
| { readonly verifiedSource: true };
|
|
202
|
+
targetCallUsda: Uint8Array;
|
|
203
|
+
}[];
|
|
170
204
|
}
|
|
171
205
|
|
|
172
206
|
/**
|