@githolon/dsl 0.102.5 → 0.103.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/compile_package.mjs +43 -2
- package/compile_proof.d.mts +47 -0
- package/compile_proof.mjs +343 -0
- package/dart/CHANGELOG.md +7 -0
- package/dart/lib/src/dev_cloud.dart +8 -8
- package/dart/lib/src/nms_generated_runtime.dart +2 -0
- package/dart/lib/src/runner/node-entry.mjs +5504 -1236
- package/dart/lib/src/runner/offline-install.mjs +4578 -528
- package/dart/lib/src/test_harness.dart +206 -152
- package/dart/lib/src/webview_bridge.dart +196 -89
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/acquire_session_credentials_test.dart +4 -3
- package/dart/test/adv_realm_interference_test.dart +7 -6
- package/dart/test/cloud_bound_topology_test.dart +3 -3
- package/dart/test/cluster_births_test.dart +4 -3
- package/dart/test/cluster_device_keyed_acceptance_test.dart +10 -16
- package/dart/test/cluster_local_autosync_test.dart +3 -2
- package/dart/test/cluster_scale_test.dart +3 -2
- package/dart/test/dlq_session_surface_test.dart +7 -6
- package/dart/test/first_app_gauntlet_test.dart +14 -21
- package/dart/test/fixtures/wedge/fake-install.mjs +1 -1
- package/dart/test/four_layer_nested_topology_test.dart +4 -4
- package/dart/test/harness_phase_timeout_test.dart +2 -2
- package/dart/test/local_sync_inert_test.dart +1 -1
- package/dart/test/parent_signer_recovery_test.dart +2 -2
- package/dart/test/sealing_client_test.dart +4 -3
- package/dart/test/session_bound_test.dart +8 -7
- package/dart/test/test_harness_test.dart +13 -12
- package/dart/test/vo_read_typing_test.dart +4 -3
- package/dart/test/watch_after_offer_stall_test.dart +4 -3
- package/dart/test/webview_bridge_test.dart +102 -5
- package/dart_types/CHANGELOG.md +5 -0
- package/dart_types/lib/nomos_types.dart +9 -0
- package/dart_types/lib/src/nomos_ref.dart +20 -0
- package/dart_types/lib/src/optional_ref.dart +85 -0
- package/dart_types/lib/src/sha256_digest.dart +41 -0
- package/dart_types/lib/src/versioned_nomos_ref.dart +81 -0
- package/dart_types/pubspec.yaml +1 -1
- package/dart_types/test/optional_ref_test.dart +44 -0
- package/dart_types/test/sha256_digest_test.dart +18 -0
- package/package.json +9 -5
- package/runtime/generated-application-host.js +5365 -1105
- package/src/aggregate.ts +18 -0
- package/src/application_composition.ts +6 -32
- package/src/attested_core.ts +6 -3
- package/src/birth_meter.ts +1 -0
- package/src/birth_meter_ensure.ts +28 -2
- package/src/births.ts +1 -1
- package/src/build_package.ts +162 -538
- package/src/business_birth.ts +19 -3
- package/src/canonical_json.ts +21 -0
- package/src/check_package_main.ts +73 -33
- package/src/clear_directive.ts +19 -8
- package/src/codegen_dart.ts +305 -22
- package/src/codegen_dart_application.ts +844 -292
- package/src/codegen_dart_client.ts +1084 -407
- package/src/codegen_dart_scenario.ts +12 -12
- package/src/codegen_proof.ts +169 -39
- package/src/codegen_provider_dart.ts +17 -1
- package/src/codegen_ts.ts +298 -106
- package/src/compat_manifest.ts +2 -2
- package/src/compat_package_main.ts +8 -12
- package/src/compile_package_main.ts +1858 -590
- package/src/compose.ts +1 -1
- package/src/directive.ts +208 -15
- package/src/engine_entry.ts +165 -111
- package/src/evolve_preview.ts +29 -69
- package/src/extremum.ts +1 -1
- package/src/fields.ts +183 -4
- package/src/flow_gate.ts +75 -2
- package/src/framework/application_home.ts +2 -0
- package/src/framework/auth_provider_law.ts +2 -2
- package/src/framework/birth_meter.ts +7 -19
- package/src/framework/bootstrap.ts +0 -2
- package/src/framework/business_corrections.ts +9 -5
- package/src/framework/domain_lifecycle.ts +15 -29
- package/src/framework/encryption_scope.ts +2 -1
- package/src/framework/framework_aggregates.ts +2 -0
- package/src/framework/genesis.ts +16 -14
- package/src/framework/home.ts +20 -14
- package/src/framework/owned_custody.ts +9 -3
- package/src/framework/platform_admin.ts +3 -3
- package/src/framework/relations.ts +21 -13
- package/src/framework/signing_identity.ts +45 -40
- package/src/framework/workspace_invariant.ts +245 -0
- package/src/framework/workspaces.ts +23 -29
- package/src/index.ts +36 -16
- package/src/intent.ts +178 -6
- package/src/law_check.ts +32 -11
- package/src/law_graph.ts +2902 -0
- package/src/law_graph_usda.ts +1799 -0
- package/src/law_identity.ts +435 -0
- package/src/law_value_type.ts +230 -0
- package/src/lsp_server.ts +58 -29
- package/src/ops.ts +7 -0
- package/src/package_config.ts +141 -0
- package/src/package_dependencies.ts +108 -0
- package/src/package_dependency_usage.ts +150 -0
- package/src/package_source_imports.ts +71 -0
- package/src/plan_source_slice.ts +335 -0
- package/src/point_guard.ts +192 -0
- package/src/precondition.ts +44 -9
- package/src/read_projection_namespace.ts +1 -1
- package/src/runtime_zod.ts +44 -0
- package/src/secondary_ensure.ts +67 -14
- package/src/shape_compat.ts +1 -1
- package/src/stable_id_identity_bridge.ts +149 -0
- package/src/stable_id_lockfile.ts +140 -6
- package/src/stable_ids.ts +1 -1
- package/src/stable_ids_for_module.ts +348 -0
- package/src/stable_ids_types.ts +1 -1
- package/src/sum.ts +1 -1
- package/src/usd_state.ts +1 -1
- package/src/usda_relationship_integrity.ts +66 -0
- package/src/usda_text.ts +18 -0
- package/src/usdz.ts +78 -0
- package/src/value_object.ts +95 -0
- package/src/wire.ts +19 -0
- package/src/wire_encode.ts +22 -0
- package/src/workspace_sharding.ts +52 -36
- package/src/workspace_sharing.ts +28 -4
- package/src/zod_introspection.ts +1 -1
- package/dart/test/cluster_attested_test.dart +0 -201
- package/src/compile_engine.ts +0 -595
- package/src/manifest.ts +0 -1476
- package/src/read_projection_composition.ts +0 -117
- package/src/read_projection_manifest.ts +0 -115
- package/src/relation_composition.ts +0 -286
- package/src/usd.ts +0 -1967
- package/src/usd_layers.ts +0 -1776
package/src/wire.ts
CHANGED
|
@@ -36,9 +36,28 @@ export interface WireHlc {
|
|
|
36
36
|
* unit-variant bare string `"Git" | "External"`. */
|
|
37
37
|
export type WireValue =
|
|
38
38
|
| { Str: string }
|
|
39
|
+
| { NomosRef: { workspace: string; type: string; id: string } }
|
|
40
|
+
| {
|
|
41
|
+
VersionedNomosRef: {
|
|
42
|
+
pinned: { workspace: string; type: string; id: string };
|
|
43
|
+
series: { workspace: string; type: string; id: string };
|
|
44
|
+
};
|
|
45
|
+
}
|
|
39
46
|
| { Int: number }
|
|
40
47
|
| { Set: string[] }
|
|
41
48
|
| { Map: Record<string, WireField> }
|
|
49
|
+
| {
|
|
50
|
+
RelationshipSubject: {
|
|
51
|
+
status: "active" | "revoked";
|
|
52
|
+
grantedBy: string;
|
|
53
|
+
grantedAt: string;
|
|
54
|
+
revokedBy?: string;
|
|
55
|
+
revokedAt?: string;
|
|
56
|
+
validFromEpoch?: number;
|
|
57
|
+
verifiedVia?: string;
|
|
58
|
+
ownedCustody?: string;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
42
61
|
| { Evidence: WireEvidenceRef }
|
|
43
62
|
// First-class canonical JSON (RFC 8785 / JCS) — the committed form of a `t.json()` leaf. The
|
|
44
63
|
// value rides INLINE (`{Json: <any json>}`), not as a string-of-JSON; the kernel canonicalises
|
package/src/wire_encode.ts
CHANGED
|
@@ -114,6 +114,17 @@ function describeShape(value: unknown): string {
|
|
|
114
114
|
* `op` is passed purely for a NAMED diagnostic (aggregate + field + expected VO type) when a value
|
|
115
115
|
* can't be lowered — never `field '?'`. */
|
|
116
116
|
function encodeKernelValue(field: Field | undefined, value: unknown, op?: FieldOp): WireValue {
|
|
117
|
+
if (op?.kernelValue !== undefined) {
|
|
118
|
+
if (
|
|
119
|
+
"RelationshipSubject" in op.kernelValue &&
|
|
120
|
+
(op.aggregateType !== "RelationTuple" || op.field !== "subjects" || op.kind !== "setEntry")
|
|
121
|
+
) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
"RelationshipSubject is a framework kernel value restricted to RelationTuple.subjects setEntry operations",
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return op.kernelValue;
|
|
127
|
+
}
|
|
117
128
|
if (value === undefined) {
|
|
118
129
|
const aggName = op?.aggregateType ?? op?.aggregateId ?? "?";
|
|
119
130
|
const fieldName = op?.field ?? field?.kind ?? "?";
|
|
@@ -141,6 +152,17 @@ function encodeKernelValue(field: Field | undefined, value: unknown, op?: FieldO
|
|
|
141
152
|
`t.json({ null: "explicit-empty", reason }) for a real empty value.`,
|
|
142
153
|
);
|
|
143
154
|
}
|
|
155
|
+
if (field?.nomosRef !== undefined) {
|
|
156
|
+
const parsed = field.zod.parse(value) as { workspace: string; type: string; id: string };
|
|
157
|
+
return { NomosRef: parsed };
|
|
158
|
+
}
|
|
159
|
+
if (field?.versionedForeignRef !== undefined) {
|
|
160
|
+
const parsed = field.zod.parse(value) as {
|
|
161
|
+
pinned: { workspace: string; type: string; id: string };
|
|
162
|
+
series: { workspace: string; type: string; id: string };
|
|
163
|
+
};
|
|
164
|
+
return { VersionedNomosRef: parsed };
|
|
165
|
+
}
|
|
144
166
|
if (field?.kind === "evidence") {
|
|
145
167
|
const v = value as EvidenceRefValue;
|
|
146
168
|
return {
|
|
@@ -79,7 +79,15 @@ import { query } from "./query.js";
|
|
|
79
79
|
import { read } from "./read.js";
|
|
80
80
|
import { sum } from "./sum.js";
|
|
81
81
|
import { min, max } from "./extremum.js";
|
|
82
|
-
import {
|
|
82
|
+
import {
|
|
83
|
+
idItem,
|
|
84
|
+
idPayload,
|
|
85
|
+
idTemplate,
|
|
86
|
+
readRef,
|
|
87
|
+
readSet,
|
|
88
|
+
refAs,
|
|
89
|
+
workspaceInvariant,
|
|
90
|
+
} from "./framework/workspace_invariant.js";
|
|
83
91
|
|
|
84
92
|
/** One packed axis the factory derives a placement lane for. */
|
|
85
93
|
export interface ShardingAxisSpec {
|
|
@@ -1152,16 +1160,20 @@ export function shardingLawModule(spec: ShardingLawSpec): Record<string, unknown
|
|
|
1152
1160
|
// (`summary-mismatch:…`): held + carried events ⇒ claimed, byte-compared.
|
|
1153
1161
|
mod["nomosSummaryGate_nomosPropagateSummary"] = workspaceInvariant("nomosSummaryGate:nomosPropagateSummary")
|
|
1154
1162
|
.on("nomosPropagateSummary")
|
|
1155
|
-
.reads((
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
),
|
|
1163
|
-
|
|
1164
|
-
|
|
1163
|
+
.reads(readSet(
|
|
1164
|
+
readRef("frontier", NomosSummaryFrontier).pre().fromTemplate(idTemplate(
|
|
1165
|
+
"nomos-summary-frontier:",
|
|
1166
|
+
idPayload("shard"),
|
|
1167
|
+
)),
|
|
1168
|
+
readRef("prev", NomosSummarySubtotal).pre().forEach("subtotals", idTemplate(
|
|
1169
|
+
"nomos-subtotal:",
|
|
1170
|
+
idPayload("shard"),
|
|
1171
|
+
"|",
|
|
1172
|
+
idItem("readId", "row-key"),
|
|
1173
|
+
"|",
|
|
1174
|
+
idItem("group", "row-key"),
|
|
1175
|
+
)),
|
|
1176
|
+
))
|
|
1165
1177
|
.assert((snapshots, ctx) => {
|
|
1166
1178
|
const intent = ctx?.intent ?? {};
|
|
1167
1179
|
const held = snapshots["frontier"]?.["frontier"];
|
|
@@ -1212,17 +1224,17 @@ export function shardingLawModule(spec: ShardingLawSpec): Record<string, unknown
|
|
|
1212
1224
|
// no skipped or replayed sequence points. ──
|
|
1213
1225
|
mod["nomosCheckpointGate_nomosCheckpointSeal"] = workspaceInvariant("nomosCheckpointGate:nomosCheckpointSeal")
|
|
1214
1226
|
.on("nomosCheckpointSeal")
|
|
1215
|
-
.reads((
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
),
|
|
1224
|
-
|
|
1225
|
-
|
|
1227
|
+
.reads(readSet(
|
|
1228
|
+
readRef("head", NomosCheckpointSeal).pre().at(NOMOS_CHECKPOINT_HEAD_ID),
|
|
1229
|
+
readRef("own", NomosCheckpointSeal).pre().fromTemplate(idTemplate(
|
|
1230
|
+
"nomos-checkpoint-seal:",
|
|
1231
|
+
idPayload("seq"),
|
|
1232
|
+
)),
|
|
1233
|
+
readRef("front", NomosSummaryFrontier).pre().forEach("frontiers", idTemplate(
|
|
1234
|
+
"nomos-summary-frontier:",
|
|
1235
|
+
idItem("shard"),
|
|
1236
|
+
)),
|
|
1237
|
+
))
|
|
1226
1238
|
.assert((snapshots, ctx) => {
|
|
1227
1239
|
const intent = ctx?.intent ?? {};
|
|
1228
1240
|
const claimedSeq = typeof intent["seq"] === "number" ? (intent["seq"] as number) : NaN;
|
|
@@ -1259,12 +1271,12 @@ export function shardingLawModule(spec: ShardingLawSpec): Record<string, unknown
|
|
|
1259
1271
|
// homes it actually holds on the named source; a handoff seal may only land on
|
|
1260
1272
|
// rows mid-move to exactly its target, with the map version bumped exactly once.
|
|
1261
1273
|
// Every refusal is typed with the held state — the named remedy. ──
|
|
1262
|
-
const moveReads = (
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
)
|
|
1267
|
-
|
|
1274
|
+
const moveReads = readSet(
|
|
1275
|
+
readRef("held", NomosShardAssignment).pre().forEach("homeKeys", idTemplate(
|
|
1276
|
+
"nomos-shard-assignment:",
|
|
1277
|
+
idItem([], "row-key"),
|
|
1278
|
+
)),
|
|
1279
|
+
);
|
|
1268
1280
|
mod["nomosMoveGate_nomosSplitShard"] = workspaceInvariant("nomosMoveGate:nomosSplitShard")
|
|
1269
1281
|
.on("nomosSplitShard")
|
|
1270
1282
|
.reads(moveReads)
|
|
@@ -1326,10 +1338,13 @@ export function shardingLawModule(spec: ShardingLawSpec): Record<string, unknown
|
|
|
1326
1338
|
const keyField = route.key;
|
|
1327
1339
|
mod[`nomosWrongHome_${route.directive}`] = workspaceInvariant(`nomosWrongHome:${route.directive}`)
|
|
1328
1340
|
.on(route.directive)
|
|
1329
|
-
.reads((
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1341
|
+
.reads(readSet(
|
|
1342
|
+
readRef("shardIdentity", NomosShardIdentity).at(NOMOS_SHARD_IDENTITY_ID),
|
|
1343
|
+
readRef("receipt", NomosHomeReceipt).fromTemplate(idTemplate(
|
|
1344
|
+
"nomos-home-receipt:",
|
|
1345
|
+
idPayload(keyField, "row-key"),
|
|
1346
|
+
)),
|
|
1347
|
+
))
|
|
1333
1348
|
.assert((snapshots, ctx) => {
|
|
1334
1349
|
const identity = snapshots["shardIdentity"] ?? {};
|
|
1335
1350
|
const label = identity["label"];
|
|
@@ -1359,7 +1374,7 @@ export function shardingLawModule(spec: ShardingLawSpec): Record<string, unknown
|
|
|
1359
1374
|
// ── THE RECEIPT'S OWN HOMING (a receipt claiming a foreign shard never folds) ──
|
|
1360
1375
|
mod["nomosReceiptHome_nomosReceiveAssignment"] = workspaceInvariant("nomosReceiptHome:nomosReceiveAssignment")
|
|
1361
1376
|
.on("nomosReceiveAssignment")
|
|
1362
|
-
.reads((
|
|
1377
|
+
.reads(readSet(readRef("shardIdentity", NomosShardIdentity).at(NOMOS_SHARD_IDENTITY_ID)))
|
|
1363
1378
|
.assert((snapshots, ctx) => {
|
|
1364
1379
|
const label = snapshots["shardIdentity"]?.["label"];
|
|
1365
1380
|
if (typeof label !== "string" || label.length === 0) return { accept: true };
|
|
@@ -1437,9 +1452,10 @@ export function shardingLawModule(spec: ShardingLawSpec): Record<string, unknown
|
|
|
1437
1452
|
// row WITHOUT authoring).
|
|
1438
1453
|
mod[`nomosPlacementUnique_${dirId}`] = workspaceInvariant(`nomosPlacementUnique:${dirId}`)
|
|
1439
1454
|
.on(dirId)
|
|
1440
|
-
.reads((
|
|
1441
|
-
|
|
1442
|
-
|
|
1455
|
+
.reads(readSet(readRef("held", NomosShardAssignment).pre().fromTemplate(idTemplate(
|
|
1456
|
+
"nomos-shard-assignment:",
|
|
1457
|
+
idPayload(keyField, "row-key"),
|
|
1458
|
+
))))
|
|
1443
1459
|
.assert((snapshots) => {
|
|
1444
1460
|
const held = snapshots["held"] ?? {};
|
|
1445
1461
|
const heldShard = held["shard"];
|
package/src/workspace_sharing.ts
CHANGED
|
@@ -16,6 +16,8 @@ import { Lww } from "./drivers.js";
|
|
|
16
16
|
import { endorsementOf } from "./principal_evidence.js";
|
|
17
17
|
import { RelationTuple, writeTuple } from "./framework/relations.js";
|
|
18
18
|
import {
|
|
19
|
+
readRef,
|
|
20
|
+
readSet,
|
|
19
21
|
refAs,
|
|
20
22
|
workspaceInvariant,
|
|
21
23
|
type WorkspaceInvariantDecl,
|
|
@@ -58,6 +60,28 @@ export interface WorkspaceSharingInvitationLaw {
|
|
|
58
60
|
readonly pendingByRecipient: QueryDecl;
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Fold the generated invitation law into the same export bag consumed by both
|
|
65
|
+
* package composition and the executable engine registry. Keeping one seam is
|
|
66
|
+
* essential: if the compiler alone injects the redeem directive, custody can
|
|
67
|
+
* advertise a route which the selected runtime plan can never register.
|
|
68
|
+
*/
|
|
69
|
+
export function withWorkspaceSharingInvitationExports(
|
|
70
|
+
moduleExports: Record<string, unknown>,
|
|
71
|
+
policies: readonly WorkspaceSharingPolicy[],
|
|
72
|
+
): Record<string, unknown> {
|
|
73
|
+
if (policies.length === 0) return moduleExports;
|
|
74
|
+
const out = { ...moduleExports };
|
|
75
|
+
for (const policy of policies) {
|
|
76
|
+
const law = policy.invitationLaw;
|
|
77
|
+
out[`__nomosWorkspaceSharingAggregate:${law.claim.id}`] = law.claim;
|
|
78
|
+
out[`__nomosWorkspaceSharingDirective:${law.redeem.id}`] = law.redeem;
|
|
79
|
+
out[`__nomosWorkspaceSharingInvariant:${law.gate.id}`] = law.gate;
|
|
80
|
+
out[`__nomosWorkspaceSharingQuery:${law.pendingByRecipient.id}`] = law.pendingByRecipient;
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
|
|
61
85
|
/** One workspace-wide consumption record makes a bearer invitation single-use across every policy. */
|
|
62
86
|
const NomosInvitationClaim = {
|
|
63
87
|
...aggregate("NomosInternalInvitationClaim7c1f5e", {
|
|
@@ -204,10 +228,10 @@ export function workspaceSharing(options: WorkspaceSharingOptions): WorkspaceSha
|
|
|
204
228
|
});
|
|
205
229
|
const gate = workspaceInvariant(`NomosInvitationGate:${options.orders.id}`)
|
|
206
230
|
.on(redeemId)
|
|
207
|
-
.reads((
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
231
|
+
.reads(readSet(
|
|
232
|
+
readRef("order", options.orders).pre().fromPayload("orderId"),
|
|
233
|
+
readRef("claim", NomosInvitationClaim).pre().fromPayload("invitationId"),
|
|
234
|
+
))
|
|
211
235
|
.assert((snapshots, ctx) => {
|
|
212
236
|
const intent = ctx?.intent ?? {};
|
|
213
237
|
const order = snapshots.order ?? {};
|
package/src/zod_introspection.ts
CHANGED
|
@@ -63,7 +63,7 @@ const SUBJECT_SAMPLES: readonly string[] = [
|
|
|
63
63
|
"0".repeat(64),
|
|
64
64
|
];
|
|
65
65
|
const STRING_SAMPLES: readonly string[] = [
|
|
66
|
-
"sample", "{}", "en", "en-GB", "AA", "A1", "a", "1", "2026-01-01",
|
|
66
|
+
"sample", "{}", "en", "en-GB", "AA", "A1", "a", "1", "2026-01", "2026-01-01",
|
|
67
67
|
"000001", "2026-01-01T00:00:00Z", "a@example.com", "https://example.com", "#000000",
|
|
68
68
|
"00000000-0000-0000-0000-000000000000", "example.com", "0".repeat(40), "0".repeat(64),
|
|
69
69
|
`nomos-invitation-${"0".repeat(64)}`,
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
// THE CLUSTER / ATTESTED-READ ACCEPTANCE — DART LEG (co2's test-cluster ask). Prove the exact
|
|
2
|
-
// dynamic-source attested-read scenario (architecture/dynamic_source_attested_reads.md) end-to-end from
|
|
3
|
-
// Dart, ONE engine plane, TWO colocated workspaces, ZERO network:
|
|
4
|
-
//
|
|
5
|
-
// • NomosTestHarness.cluster boots ONE runner holding a `catalogue` session (the source) and an `estate`
|
|
6
|
-
// session (the reader), each with its own law installed offline.
|
|
7
|
-
// • the catalogue session seeds a listing; the estate session serves + SIGNS it via the in-process
|
|
8
|
-
// `attestedRead` op (the colocated source lane) and reads the source's GENESIS off the envelope.
|
|
9
|
-
// • NEGATIVE arm: offering `importListing` with that envelope BEFORE enrolling the catalogue refuses
|
|
10
|
-
// `AttestedReadUntrustedSource` — surfaced to Dart as a NomosBridgeError carrying that name.
|
|
11
|
-
// • HAPPY arm: enrol the source genesis + attestor key, offer `importListing` carrying the signed
|
|
12
|
-
// envelope, and read the imported row back through the generated EstateApplication bound to
|
|
13
|
-
// `harness.session('estate')`.
|
|
14
|
-
@TestOn('vm')
|
|
15
|
-
library;
|
|
16
|
-
|
|
17
|
-
import 'dart:convert';
|
|
18
|
-
import 'dart:io';
|
|
19
|
-
|
|
20
|
-
import 'package:crypto/crypto.dart';
|
|
21
|
-
import 'package:nomos_client/src/nms_conformance_runtime.dart';
|
|
22
|
-
import 'package:nomos_client/src/nms_generated_runtime.dart';
|
|
23
|
-
import 'package:test/test.dart';
|
|
24
|
-
|
|
25
|
-
// Framework acceptance tests may bind a generated application to an already-mounted harness session. The
|
|
26
|
-
// raw client is a private `part` and is deliberately not importable, even here.
|
|
27
|
-
import '../../../bench/scale/fixtures/catalogue-reader/build/dart/src/generated/estate.application.dart';
|
|
28
|
-
|
|
29
|
-
String get _root => '${Directory.current.path}/../..';
|
|
30
|
-
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
31
|
-
String get _catDeploy =>
|
|
32
|
-
'$_root/bench/scale/fixtures/catalogue-src/build/cat.deploy.json';
|
|
33
|
-
String get _estateDeploy =>
|
|
34
|
-
'$_root/bench/scale/fixtures/catalogue-reader/build/estate.deploy.json';
|
|
35
|
-
String get _estateClientDart =>
|
|
36
|
-
'$_root/bench/scale/fixtures/catalogue-reader/build/dart/src/generated/estate.application.dart';
|
|
37
|
-
String get _runtimeDir =>
|
|
38
|
-
Platform.environment['NOMOS_DART_RUNTIME'] ??
|
|
39
|
-
Platform.environment['NOMOS_OFFLINE_RUNTIME'] ??
|
|
40
|
-
'${Platform.environment['HOME'] ?? ''}/.holon/runtime';
|
|
41
|
-
|
|
42
|
-
String? _missingPrereq() {
|
|
43
|
-
if (!File(_repoEntry).existsSync())
|
|
44
|
-
return 'in-repo runner not found at $_repoEntry';
|
|
45
|
-
for (final p in [_catDeploy, _estateDeploy, _estateClientDart]) {
|
|
46
|
-
if (!File(p).existsSync()) {
|
|
47
|
-
return 'compile the catalogue fixtures with dart output: '
|
|
48
|
-
'cd bench/scale/fixtures/<catalogue-src|catalogue-reader> && node ../../../../dsl/compile_package.mjs '
|
|
49
|
-
'(needs `dart: { support: "package" }` in nomos.package.mjs) — missing $p';
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
if (!File('$_runtimeDir/holon-testmint.wasm').existsSync() ||
|
|
53
|
-
!File('$_runtimeDir/packages.json').existsSync()) {
|
|
54
|
-
return 'warm ~/.holon/runtime first (run any `githolon` command once)';
|
|
55
|
-
}
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/// sha256hex of the attestor's raw public bytes — the `keyHash` the reader enrolls (parity with the
|
|
60
|
-
/// smoke's `sha256hexBytes(base64decode(public))`).
|
|
61
|
-
String _keyHashOf(String base64Public) =>
|
|
62
|
-
sha256.convert(base64Decode(base64Public)).toString();
|
|
63
|
-
|
|
64
|
-
void main() {
|
|
65
|
-
test(
|
|
66
|
-
'cluster attested reads: colocated source served + signed → enrolled → typed import read back',
|
|
67
|
-
() async {
|
|
68
|
-
final skip = _missingPrereq();
|
|
69
|
-
if (skip != null) {
|
|
70
|
-
markTestSkipped(skip);
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
final harness = await NomosTestHarness.cluster(
|
|
75
|
-
workspaces: {
|
|
76
|
-
'catalogue': [_catDeploy],
|
|
77
|
-
'estate': [_estateDeploy],
|
|
78
|
-
},
|
|
79
|
-
// The catalogue SOURCE signs its attested reads with its device key — the colocation resolver uses it,
|
|
80
|
-
// and the reader enrols sha256(that public key). (The reader needs no device key: it authors as an actor.)
|
|
81
|
-
deviceKeyed: {'catalogue'},
|
|
82
|
-
nodeEntry: _repoEntry,
|
|
83
|
-
runtimeDir: _runtimeDir,
|
|
84
|
-
);
|
|
85
|
-
try {
|
|
86
|
-
expect(harness.sessions.toSet(), {'catalogue', 'estate'});
|
|
87
|
-
|
|
88
|
-
// THE PRODUCTION MULTI-WORKSPACE SURFACE: one bridge = this device's local plane; `bridge.session(ws)`
|
|
89
|
-
// is the first-class handle. Enumerate the plane — both workspaces are LOCAL-ONLY (no cloud posture).
|
|
90
|
-
final plane = await harness.bridge.listWorkspaces();
|
|
91
|
-
expect(plane.map((w) => w['workspace']).toSet(), {'catalogue', 'estate'});
|
|
92
|
-
expect(plane.every((w) => w['syncPosture'] == 'local-only'), isTrue,
|
|
93
|
-
reason: 'the cluster opens local githolons, not cloud connections');
|
|
94
|
-
|
|
95
|
-
// Get the session handles through the production entry (the harness delegates to bridge.session).
|
|
96
|
-
final catalogue = harness.bridge.session('catalogue');
|
|
97
|
-
final estate = harness.bridge.session('estate');
|
|
98
|
-
expect(identical(estate, harness.session('estate')), isTrue,
|
|
99
|
-
reason: 'session handles are stable + cached on the plane');
|
|
100
|
-
|
|
101
|
-
// 1. seed a listing in the CATALOGUE source (raw offer — the source's own law).
|
|
102
|
-
final seeded = await catalogue.offerCreates(
|
|
103
|
-
domain: 'cat',
|
|
104
|
-
directiveId: 'listCatalogue',
|
|
105
|
-
payload: {
|
|
106
|
-
'listingId': 'L-100',
|
|
107
|
-
'title': 'Oak dresser',
|
|
108
|
-
'priceGbp': '420',
|
|
109
|
-
'listedAt': '2026-07-03T00:00:00Z',
|
|
110
|
-
},
|
|
111
|
-
actor: 'user:seller',
|
|
112
|
-
);
|
|
113
|
-
expect(seeded.head, isNotEmpty);
|
|
114
|
-
|
|
115
|
-
// 2. Read the source's GENESIS (its true identity) + its signing key off ONE attested read. No attestor
|
|
116
|
-
// secret: the source session signs with its DEVICE key (its connect authorSecret), which the
|
|
117
|
-
// colocation resolver also uses — so the enrolled keyHash matches what the zero-injection lane signs.
|
|
118
|
-
final served = await estate.attestedRead(
|
|
119
|
-
from: 'catalogue',
|
|
120
|
-
queryId: 'listingById',
|
|
121
|
-
args: {'listingId': 'L-100'},
|
|
122
|
-
);
|
|
123
|
-
final entry = (served['entry'] as Map).cast<String, Object?>();
|
|
124
|
-
final attestation = (entry['attestation'] as Map).cast<String, Object?>();
|
|
125
|
-
final sourceGenesis = (attestation['source_genesis'] ?? '').toString();
|
|
126
|
-
final signingKey = (attestation['public_key'] ?? '').toString();
|
|
127
|
-
final keyHash = _keyHashOf(signingKey);
|
|
128
|
-
expect(sourceGenesis.length, greaterThanOrEqualTo(40),
|
|
129
|
-
reason: 'the source genesis oid resolves off the signed envelope');
|
|
130
|
-
expect(signingKey, harness.devicePublicKey('catalogue'),
|
|
131
|
-
reason:
|
|
132
|
-
'the read is signed by the source session device key the resolver will use');
|
|
133
|
-
|
|
134
|
-
// The one importListing offer, offered TWICE (unenrolled then enrolled). ZERO injection: the payload
|
|
135
|
-
// names `catalogueWs`, and the runner's colocation resolver serves+signs the foreign read IN-PROCESS —
|
|
136
|
-
// NO `attestations` param. The gate judges the served envelope's genesis against the reader's allow-list.
|
|
137
|
-
Future<NomosOfferOutcome> importRawForFrameworkTest(String importedAt) =>
|
|
138
|
-
estate.offerCreates(
|
|
139
|
-
domain: 'estate',
|
|
140
|
-
directiveId: 'importListing',
|
|
141
|
-
payload: {
|
|
142
|
-
'listingId': 'L-100',
|
|
143
|
-
'catalogueWs': 'catalogue',
|
|
144
|
-
'importedAt': importedAt,
|
|
145
|
-
},
|
|
146
|
-
actor: 'user:estate-owner',
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
// 3. NEGATIVE — BEFORE enrolment the source genesis is not in the allow-list → UntrustedSource
|
|
150
|
-
// (the resolver still serves+signs; the gate refuses on the untrusted genesis).
|
|
151
|
-
Object? negError;
|
|
152
|
-
try {
|
|
153
|
-
// Deliberately raw: this framework test needs the refused kernel outcome itself. This is framework
|
|
154
|
-
// test instrumentation, not public API; tenant code gets only the generated application.
|
|
155
|
-
await importRawForFrameworkTest('2026-07-03T12:00:00Z');
|
|
156
|
-
} catch (e) {
|
|
157
|
-
negError = e;
|
|
158
|
-
}
|
|
159
|
-
expect(negError, isA<NomosBridgeError>(),
|
|
160
|
-
reason: 'an unenrolled source must refuse the import');
|
|
161
|
-
expect(negError.toString(), contains('AttestedReadUntrustedSource'),
|
|
162
|
-
reason: 'the typed refusal name must surface to Dart');
|
|
163
|
-
|
|
164
|
-
// 4. ENROL the catalogue: its genesis + the source device keyHash become folded law-state in the
|
|
165
|
-
// reader's own chain (typed write — the reader's own directive, no attestation).
|
|
166
|
-
final app =
|
|
167
|
-
await nomosInternalBindEstateApplication(estate, 'user:estate-owner');
|
|
168
|
-
final enrolled =
|
|
169
|
-
await app.trustedCatalogues.enrollCatalogue(EnrollCataloguePayload(
|
|
170
|
-
sourceGenesis: sourceGenesis,
|
|
171
|
-
keyHashes: [keyHash],
|
|
172
|
-
label: 'Acme Catalogue',
|
|
173
|
-
enrolledAt: '2026-07-03T10:00:00Z',
|
|
174
|
-
));
|
|
175
|
-
expect(NmsInternalNomosRefs.workspace(enrolled.trustedCatalogue), 'estate');
|
|
176
|
-
|
|
177
|
-
// 5. HAPPY — the SAME zero-injection offer now admits: the resolver serves+signs, the gate judges the
|
|
178
|
-
// genesis against the folded allow-list, and the catalogue title lands on EstateListing.
|
|
179
|
-
final imported = await app.estateListings.importListing(
|
|
180
|
-
const ImportListingPayload(
|
|
181
|
-
listingId: 'L-100',
|
|
182
|
-
catalogueWs: 'catalogue',
|
|
183
|
-
importedAt: '2026-07-03T12:05:00Z',
|
|
184
|
-
),
|
|
185
|
-
);
|
|
186
|
-
expect(NmsInternalNomosRefs.workspace(imported.estateListing), 'estate');
|
|
187
|
-
|
|
188
|
-
// 6. TYPED read-back through the generated application bound to the estate session.
|
|
189
|
-
final row = await app.estateListings.find(imported.estateListing);
|
|
190
|
-
expect(row, isNotNull,
|
|
191
|
-
reason:
|
|
192
|
-
'the imported EstateListing reads back on the estate session');
|
|
193
|
-
expect(row!.title, 'Oak dresser',
|
|
194
|
-
reason: 'the imported title was read through the policy edge, typed');
|
|
195
|
-
expect(row.catalogue, 'catalogue');
|
|
196
|
-
expect(row.listingRef, 'L-100');
|
|
197
|
-
} finally {
|
|
198
|
-
await harness.dispose();
|
|
199
|
-
}
|
|
200
|
-
}, timeout: const Timeout(Duration(minutes: 3)));
|
|
201
|
-
}
|