@githolon/dsl 0.55.1 → 0.57.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/lib/nomos_headless.dart +16 -1
- package/dart/lib/src/test_harness.dart +184 -6
- package/dart/lib/src/webview_bridge.dart +255 -29
- package/dart/pubspec.yaml +1 -1
- package/dart/test/cluster_attested_test.dart +185 -0
- package/dart/test/co2_acceptance_test.dart +0 -1
- package/dart/test/vo_read_typing_test.dart +0 -1
- package/dart/test/webview_bridge_test.dart +13 -0
- package/package.json +1 -1
- package/src/attested.ts +9 -0
- package/src/attested_core.ts +199 -2
- package/src/build_package.ts +17 -1
- package/src/codegen_dart.ts +7 -0
- package/src/codegen_proof.ts +31 -1
- package/src/directive.ts +55 -11
- package/src/index.ts +6 -0
- package/src/law_check.ts +107 -0
- package/src/read.ts +5 -2
- package/src/shape_compat.ts +10 -6
- package/src/usd.ts +145 -26
- package/src/usd_layers.ts +16 -4
package/src/shape_compat.ts
CHANGED
|
@@ -54,10 +54,13 @@ export interface DomainInterface {
|
|
|
54
54
|
readonly directives: Record<string, DirectivePayloadShape>;
|
|
55
55
|
/** keyed by AGGREGATE SID. */
|
|
56
56
|
readonly aggregates: Record<string, AggregateReadShape>;
|
|
57
|
-
/** ATTESTED READS (slice B): directive id → its declared
|
|
58
|
-
*
|
|
59
|
-
* law
|
|
60
|
-
readonly foreignReads?: Record<
|
|
57
|
+
/** ATTESTED READS (slice B + dynamic policies): directive id → its declared foreign-read edges —
|
|
58
|
+
* a STATIC `{queryId, from}` edge or a dynamic `{queryId, policy, fromPayload}` edge — the
|
|
59
|
+
* cross-workspace dependencies this law consumes on its write path. OMITTED when none (era rule). */
|
|
60
|
+
readonly foreignReads?: Record<
|
|
61
|
+
string,
|
|
62
|
+
{ queryId: string; from?: string; policy?: string; fromPayload?: string }[]
|
|
63
|
+
>;
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
/** The verdict: compatible iff nothing is missing and nothing is type-incompatible.
|
|
@@ -161,8 +164,9 @@ export function domainInterface(mod: DomainModule, stableIds?: StableIds): Domai
|
|
|
161
164
|
Object.assign(aggregates, sortedRec(aggEntries));
|
|
162
165
|
// ATTESTED READS (slice B): the declared trust edges — so shape-compat + client codegen can
|
|
163
166
|
// SEE the cross-workspace dependency (typed clients don't call it; it's write-path-internal).
|
|
164
|
-
|
|
165
|
-
|
|
167
|
+
type ForeignEdge = { queryId: string; from?: string; policy?: string; fromPayload?: string };
|
|
168
|
+
const foreignReadEntries: Array<[string, ForeignEdge[]]> = [];
|
|
169
|
+
for (const d of (mod.directives ?? []) as Array<{ id?: unknown; declaredForeignReads?: ForeignEdge[] }>) {
|
|
166
170
|
if (!d || typeof d.id !== "string") continue;
|
|
167
171
|
if (d.declaredForeignReads !== undefined && d.declaredForeignReads.length > 0) {
|
|
168
172
|
foreignReadEntries.push([d.id, d.declaredForeignReads.map((e) => ({ ...e }))]);
|
package/src/usd.ts
CHANGED
|
@@ -44,6 +44,7 @@ import type { DomainModule } from "./codegen_dart.js";
|
|
|
44
44
|
import { domainInterface } from "./shape_compat.js";
|
|
45
45
|
import { relationType, user, key, anyUser, qualifiedTypeId, qualifyRelationType } from "./relation_schema.js";
|
|
46
46
|
import { harvestPrincipalFlows } from "./wire_encode.js";
|
|
47
|
+
import { zodKind, zodObjectShape } from "./codegen_ts.js";
|
|
47
48
|
import { composeSharedRelationTypes } from "./relation_composition.js";
|
|
48
49
|
import { collectValueObjectUsages } from "./value_object.js";
|
|
49
50
|
import {
|
|
@@ -459,7 +460,29 @@ export interface UsdLayer {
|
|
|
459
460
|
onDishonor: "refuse" | "record";
|
|
460
461
|
}
|
|
461
462
|
>;
|
|
462
|
-
|
|
463
|
+
/**
|
|
464
|
+
* DYNAMIC-SOURCE trust POLICIES (dynamic_source_attested_reads.md): the acceptable-source SET is
|
|
465
|
+
* law-state — a reader-side allow-list aggregate. A directive edge may route through a policy
|
|
466
|
+
* (`{queryId, policy}`) instead of a fixed workspace; the gate resolves the envelope's genesis
|
|
467
|
+
* against the folded `allowList` rows (genesis field) → the row's `keysField` accepted keys.
|
|
468
|
+
* OMITTED when no policy is declared (byte-identical to a policy-free bundle — the era rule).
|
|
469
|
+
*/
|
|
470
|
+
readonly policies?: Record<
|
|
471
|
+
string,
|
|
472
|
+
{
|
|
473
|
+
allowList: string;
|
|
474
|
+
genesisField: string;
|
|
475
|
+
keysField: string;
|
|
476
|
+
maxAgeMs?: number;
|
|
477
|
+
minSourceEpoch?: "birth" | number;
|
|
478
|
+
monotone?: boolean;
|
|
479
|
+
onDishonor?: "refuse" | "record";
|
|
480
|
+
}
|
|
481
|
+
>;
|
|
482
|
+
readonly directives: Record<
|
|
483
|
+
string,
|
|
484
|
+
({ queryId: string; from: string } | { queryId: string; policy: string })[]
|
|
485
|
+
>;
|
|
463
486
|
};
|
|
464
487
|
/**
|
|
465
488
|
* THE READ-VISIBILITY GATE DECLARATION (the native-relations read gate).
|
|
@@ -566,6 +589,27 @@ export interface UsdDocument {
|
|
|
566
589
|
readonly layers: UsdLayer[];
|
|
567
590
|
}
|
|
568
591
|
|
|
592
|
+
/** The payload field kinds of a directive — `"string-required"` for a required `z.string()`, else the
|
|
593
|
+
* zod kind (`"optional"`, `"number"`, …). Used to validate a `fromPayload` routing field. Empty when
|
|
594
|
+
* the payload isn't a plain object (a shape the check can't reason about — the runtime gate arbitrates). */
|
|
595
|
+
function payloadFieldKinds(dir: { payloadSchema?: unknown } | undefined): Map<string, string> {
|
|
596
|
+
const m = new Map<string, string>();
|
|
597
|
+
const schema = dir?.payloadSchema as Parameters<typeof zodKind>[0] | undefined;
|
|
598
|
+
if (schema === undefined) return m;
|
|
599
|
+
let shape: Record<string, Parameters<typeof zodKind>[0]>;
|
|
600
|
+
try {
|
|
601
|
+
if (zodKind(schema) !== "object") return m;
|
|
602
|
+
shape = zodObjectShape(schema);
|
|
603
|
+
} catch {
|
|
604
|
+
return m;
|
|
605
|
+
}
|
|
606
|
+
for (const [name, f] of Object.entries(shape)) {
|
|
607
|
+
const k = zodKind(f);
|
|
608
|
+
m.set(name, k === "string" ? "string-required" : k);
|
|
609
|
+
}
|
|
610
|
+
return m;
|
|
611
|
+
}
|
|
612
|
+
|
|
569
613
|
/** Sort prims by their `path` (canonical structure). */
|
|
570
614
|
function sortPrims(prims: UsdPrim[]): UsdPrim[] {
|
|
571
615
|
return [...prims].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
@@ -984,47 +1028,122 @@ export function emitUsd(
|
|
|
984
1028
|
// trustSource declarations. Emitted iff ≥1 directive declares a foreign read (the era
|
|
985
1029
|
// rule). A declared `from` with NO matching trustSource is a NAMED COMPILE ERROR — the
|
|
986
1030
|
// kernel would fail it closed anyway; refuse loudly at build instead.
|
|
987
|
-
|
|
1031
|
+
type ForeignEdgeInternal =
|
|
1032
|
+
| { queryId: string; from: string }
|
|
1033
|
+
| { queryId: string; policy: string; fromPayload: string };
|
|
1034
|
+
type ForeignEdgeEmit = { queryId: string; from: string } | { queryId: string; policy: string };
|
|
1035
|
+
const foreignDirectives: Record<string, ForeignEdgeInternal[]> = {};
|
|
988
1036
|
for (const d of [...l.module.directives].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))) {
|
|
989
|
-
const fr = (d as { declaredForeignReads?:
|
|
1037
|
+
const fr = (d as { declaredForeignReads?: ForeignEdgeInternal[] }).declaredForeignReads;
|
|
990
1038
|
if (fr !== undefined && fr.length > 0) foreignDirectives[d.id] = fr.map((e) => ({ ...e }));
|
|
991
1039
|
}
|
|
992
1040
|
let nomosAttestedReadGate: UsdLayer["nomosAttestedReadGate"];
|
|
993
1041
|
if (Object.keys(foreignDirectives).length > 0) {
|
|
994
1042
|
const trust = (l.module as { trustSources?: readonly import("./attested.js").TrustSourceDecl[] }).trustSources ?? [];
|
|
995
1043
|
const trustByWs = new Map(trust.map((tsrc) => [tsrc.workspace, tsrc]));
|
|
1044
|
+
const policies = (l.module as { trustPolicies?: readonly import("./attested.js").TrustPolicyDecl[] }).trustPolicies ?? [];
|
|
1045
|
+
const policyById = new Map(policies.map((p) => [p.policyId, p]));
|
|
1046
|
+
const aggIds = new Set(l.module.aggregates.map((a) => a.id));
|
|
1047
|
+
// A policy-referenced allow-list must be a real aggregate of THIS module (the fold happens in
|
|
1048
|
+
// the reader's own state) — else the gate could never resolve it (fail-closed at build).
|
|
1049
|
+
for (const p of policies) {
|
|
1050
|
+
if (!aggIds.has(p.allowListType)) {
|
|
1051
|
+
throw new Error(
|
|
1052
|
+
`trustPolicy('${p.policyId}'): its allowList aggregate '${p.allowListType}' is not an ` +
|
|
1053
|
+
`aggregate of this domain — the acceptable-source set is the reader's OWN folded state, ` +
|
|
1054
|
+
`so the allow-list must be declared (and written by a gated enrolment directive) here.`,
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
996
1058
|
const sourcesRec: NonNullable<UsdLayer["nomosAttestedReadGate"]>["sources"] = {};
|
|
1059
|
+
const policiesRec: NonNullable<NonNullable<UsdLayer["nomosAttestedReadGate"]>["policies"]> = {};
|
|
1060
|
+
const emitDirectives: Record<string, ForeignEdgeEmit[]> = {};
|
|
997
1061
|
for (const [dirId, edges] of Object.entries(foreignDirectives)) {
|
|
1062
|
+
const dir = l.module.directives.find((d) => d.id === dirId);
|
|
1063
|
+
const payloadShape = payloadFieldKinds(dir);
|
|
1064
|
+
const emitted: ForeignEdgeEmit[] = [];
|
|
998
1065
|
for (const e of edges) {
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
`
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
sourcesRec[e.from]
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1066
|
+
if ("from" in e) {
|
|
1067
|
+
const tsrc = trustByWs.get(e.from);
|
|
1068
|
+
if (tsrc === undefined) {
|
|
1069
|
+
throw new Error(
|
|
1070
|
+
`directive '${dirId}' declares .readsFrom('${e.from}', '${e.queryId}') but the ` +
|
|
1071
|
+
`module exports no trustSource('${e.from}', …) — the trust edge must be declared ` +
|
|
1072
|
+
`in law (attested_reads_design.md §3.1)`,
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
if (sourcesRec[e.from] === undefined) {
|
|
1076
|
+
sourcesRec[e.from] = {
|
|
1077
|
+
genesis: tsrc.genesis,
|
|
1078
|
+
...(tsrc.keyHashes !== undefined && tsrc.keyHashes.length > 0
|
|
1079
|
+
? { keyHashes: [...tsrc.keyHashes].sort() }
|
|
1080
|
+
: {}),
|
|
1081
|
+
...(tsrc.maxAgeMs !== undefined ? { maxAgeMs: tsrc.maxAgeMs } : {}),
|
|
1082
|
+
...(tsrc.minSourceEpoch !== undefined ? { minSourceEpoch: tsrc.minSourceEpoch } : {}),
|
|
1083
|
+
...(tsrc.monotone === true ? { monotone: true } : {}),
|
|
1084
|
+
roster: tsrc.roster ?? "pinned",
|
|
1085
|
+
onDishonor: tsrc.onDishonor ?? "refuse",
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
emitted.push({ queryId: e.queryId, from: e.from });
|
|
1089
|
+
} else {
|
|
1090
|
+
const pol = policyById.get(e.policy);
|
|
1091
|
+
if (pol === undefined) {
|
|
1092
|
+
throw new Error(
|
|
1093
|
+
`directive '${dirId}' declares .readsFrom(fromPayload('${e.fromPayload}'), '${e.queryId}', ` +
|
|
1094
|
+
`{ policy: '${e.policy}' }) but the module exports no trustPolicy('${e.policy}', …) — the ` +
|
|
1095
|
+
`policy must be declared in law (dynamic_source_attested_reads.md).`,
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
1098
|
+
// The routing field must be a REQUIRED string in the directive's payload (the workspace
|
|
1099
|
+
// name is chosen at runtime; a missing/optional/non-string field could never route).
|
|
1100
|
+
const kind = payloadShape.get(e.fromPayload);
|
|
1101
|
+
if (kind === undefined) {
|
|
1102
|
+
throw new Error(
|
|
1103
|
+
`directive '${dirId}': .readsFrom(fromPayload('${e.fromPayload}'), …) names a payload ` +
|
|
1104
|
+
`field that is not declared in the directive's payload — add a required z.string() ` +
|
|
1105
|
+
`'${e.fromPayload}' to the payload (it carries the routed source workspace name).`,
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
if (kind !== "string-required") {
|
|
1109
|
+
throw new Error(
|
|
1110
|
+
`directive '${dirId}': the fromPayload routing field '${e.fromPayload}' must be a ` +
|
|
1111
|
+
`REQUIRED z.string() in the payload (it names the routed source workspace) — got '${kind}'.`,
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
if (policiesRec[e.policy] === undefined) {
|
|
1115
|
+
policiesRec[e.policy] = {
|
|
1116
|
+
// Qualify the allow-list type by the owning domain iff it is namespaced — the
|
|
1117
|
+
// gate scans rows by their stored `__type`, which the engine stamps qualified
|
|
1118
|
+
// (`qaType`, the SAME read-projection namespacing rows/schema keys use). A bare
|
|
1119
|
+
// (unqualified) allowList would match no folded row → every dynamic read refuses.
|
|
1120
|
+
allowList: qaType(pol.allowListType),
|
|
1121
|
+
genesisField: pol.genesisField,
|
|
1122
|
+
keysField: pol.keysField,
|
|
1123
|
+
...(pol.maxAgeMs !== undefined ? { maxAgeMs: pol.maxAgeMs } : {}),
|
|
1124
|
+
...(pol.minSourceEpoch !== undefined ? { minSourceEpoch: pol.minSourceEpoch } : {}),
|
|
1125
|
+
...(pol.monotone === true ? { monotone: true } : {}),
|
|
1126
|
+
...(pol.onDishonor !== undefined ? { onDishonor: pol.onDishonor } : {}),
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
emitted.push({ queryId: e.queryId, policy: e.policy });
|
|
1019
1130
|
}
|
|
1020
1131
|
}
|
|
1132
|
+
emitDirectives[dirId] = emitted;
|
|
1021
1133
|
}
|
|
1022
1134
|
// SORTED-KEY records (byte-determinism — matching the layered flatten).
|
|
1023
1135
|
const sortedSources: typeof sourcesRec = {};
|
|
1024
1136
|
for (const k of Object.keys(sourcesRec).sort()) sortedSources[k] = sourcesRec[k]!;
|
|
1025
|
-
const
|
|
1026
|
-
for (const k of Object.keys(
|
|
1027
|
-
|
|
1137
|
+
const sortedPolicies: typeof policiesRec = {};
|
|
1138
|
+
for (const k of Object.keys(policiesRec).sort()) sortedPolicies[k] = policiesRec[k]!;
|
|
1139
|
+
const sortedDirectives: Record<string, ForeignEdgeEmit[]> = {};
|
|
1140
|
+
for (const k of Object.keys(emitDirectives).sort()) sortedDirectives[k] = emitDirectives[k]!;
|
|
1141
|
+
nomosAttestedReadGate = {
|
|
1142
|
+
v: 1 as const,
|
|
1143
|
+
sources: sortedSources,
|
|
1144
|
+
...(Object.keys(sortedPolicies).length > 0 ? { policies: sortedPolicies } : {}),
|
|
1145
|
+
directives: sortedDirectives,
|
|
1146
|
+
};
|
|
1028
1147
|
}
|
|
1029
1148
|
// THE BIRTH GATE (G3 — birth is a declared kernel primitive): the SORTED set of directive ids
|
|
1030
1149
|
// declaring `.births()`. The kernel reads this off the manifest (never a domain aggregate) to know an
|
package/src/usd_layers.ts
CHANGED
|
@@ -835,20 +835,32 @@ export function flattenLayeredUsd(layers: readonly UsdLayer[]): UsdDocument {
|
|
|
835
835
|
// layers, SORTED keys (matching the composed emission). Omit-when-empty (era rule).
|
|
836
836
|
let attestedReadGate: UsdLayer["nomosAttestedReadGate"];
|
|
837
837
|
{
|
|
838
|
-
type
|
|
838
|
+
type Gate = NonNullable<UsdLayer["nomosAttestedReadGate"]>;
|
|
839
|
+
type SrcSpec = Gate["sources"][string];
|
|
840
|
+
type PolSpec = NonNullable<Gate["policies"]>[string];
|
|
841
|
+
type EdgeList = Gate["directives"][string];
|
|
839
842
|
const srcs = new Map<string, SrcSpec>();
|
|
840
|
-
const
|
|
843
|
+
const pols = new Map<string, PolSpec>();
|
|
844
|
+
const dirs = new Map<string, EdgeList>();
|
|
841
845
|
for (const l of stack) {
|
|
842
846
|
if (l.nomosAttestedReadGate === undefined) continue;
|
|
843
847
|
for (const [ws, spec] of Object.entries(l.nomosAttestedReadGate.sources)) srcs.set(ws, spec);
|
|
848
|
+
for (const [id, spec] of Object.entries(l.nomosAttestedReadGate.policies ?? {})) pols.set(id, spec);
|
|
844
849
|
for (const [id, edges] of Object.entries(l.nomosAttestedReadGate.directives)) dirs.set(id, edges);
|
|
845
850
|
}
|
|
846
851
|
if (dirs.size > 0) {
|
|
847
852
|
const sourcesRec: Record<string, SrcSpec> = {};
|
|
848
853
|
for (const k of [...srcs.keys()].sort()) sourcesRec[k] = srcs.get(k)!;
|
|
849
|
-
const
|
|
854
|
+
const policiesRec: Record<string, PolSpec> = {};
|
|
855
|
+
for (const k of [...pols.keys()].sort()) policiesRec[k] = pols.get(k)!;
|
|
856
|
+
const directivesRec: Record<string, EdgeList> = {};
|
|
850
857
|
for (const k of [...dirs.keys()].sort()) directivesRec[k] = dirs.get(k)!;
|
|
851
|
-
attestedReadGate = {
|
|
858
|
+
attestedReadGate = {
|
|
859
|
+
v: 1,
|
|
860
|
+
sources: sourcesRec,
|
|
861
|
+
...(Object.keys(policiesRec).length > 0 ? { policies: policiesRec } : {}),
|
|
862
|
+
directives: directivesRec,
|
|
863
|
+
};
|
|
852
864
|
}
|
|
853
865
|
}
|
|
854
866
|
// THE BIRTH GATE (G3): the SORTED union of every layer's `.births()` directive ids. Omit-when-empty
|