@githolon/dsl 0.102.5 → 0.103.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/compile_package.mjs +43 -2
- package/compile_proof.d.mts +47 -0
- package/compile_proof.mjs +343 -0
- package/dart/CHANGELOG.md +14 -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 +5539 -1228
- 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 +239 -102
- 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/acquire_session_test.dart +9 -0
- 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 +862 -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 +1860 -592
- 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
|
@@ -1,117 +0,0 @@
|
|
|
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 · SQLite projection · embedded QuickJS engine}.
|
|
4
|
-
//
|
|
5
|
-
// COMPILE-TIME DIVERGENCE GUARD for SHARED read-projection keys — the read-layer
|
|
6
|
-
// sibling of `relation_composition.ts` (the scalpel).
|
|
7
|
-
//
|
|
8
|
-
// THE MODEL. The kernel's installed-read-manifest accumulator
|
|
9
|
-
// (`wasm_git_holon.rs::ReadProjectionAccumulator`) UNIONS the CURRENT installed
|
|
10
|
-
// `nomosReadProjection` fragments BY KEY — `aggregateFieldKinds` by aggregate TYPE,
|
|
11
|
-
// `queries`/`counts`/`spatials`/`sums`/`mins`/`maxes`/`orderedReads` by declared `id`,
|
|
12
|
-
// `deriveds`/`combineds` by `"<of>\0<id>"` — and (correctly, fail-closed) REFUSES a
|
|
13
|
-
// CURRENT key declared twice with DIVERGENT shapes: "divergent read projection
|
|
14
|
-
// declaration for key <k>". Superseded eras never feed the current read manifest.
|
|
15
|
-
//
|
|
16
|
-
// Read-projection NAMESPACING (`read_projection_namespace.ts`) makes a NON-GLOBAL
|
|
17
|
-
// aggregate's keys domain-qualified, so tenant domains never share a key. What CAN
|
|
18
|
-
// still share a key inside one package: GLOBAL framework aggregates declared by ≥2
|
|
19
|
-
// domains (byte-identical by construction — same framework version — but a version
|
|
20
|
-
// skew diverges), and same-named declared reads across domains. This guard turns the
|
|
21
|
-
// current-runtime conflict into a precise COMPILE refusal: identical shared declarations
|
|
22
|
-
// PASS (the kernel dedupes them), divergent ones REFUSE with the key and each domain's
|
|
23
|
-
// shape. Validation only — a conflict-free package is returned untouched (era rule).
|
|
24
|
-
|
|
25
|
-
import type { UsdLayer } from "./usd.js";
|
|
26
|
-
import { domainOfLayerPath } from "./relation_composition.js";
|
|
27
|
-
|
|
28
|
-
/** One shared-key divergence, gathered for the scalpel message. */
|
|
29
|
-
interface ReadProjectionConflict {
|
|
30
|
-
/** The accumulator section (`aggregateFieldKinds`, `queries`, …). */
|
|
31
|
-
readonly section: string;
|
|
32
|
-
/** The colliding accumulator key (the kernel's exact key). */
|
|
33
|
-
readonly key: string;
|
|
34
|
-
/** Each contributing domain's declaration, in package (weak→strong) order. */
|
|
35
|
-
readonly opinions: { readonly domain: string; readonly shape: string }[];
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function scalpelMessage(conflicts: readonly ReadProjectionConflict[]): string {
|
|
39
|
-
const lines: string[] = [
|
|
40
|
-
`Refusing to compose the package: shared read-projection key(s) carry DIVERGENT declarations ` +
|
|
41
|
-
`across domains — the current installed-read-manifest accumulator refuses exactly this at ` +
|
|
42
|
-
`SERVE time ("divergent read projection declaration for key …"). Superseded eras are ignored, ` +
|
|
43
|
-
`but genuinely current law must have one coherent read shape. Fail-closed compile refusal.`,
|
|
44
|
-
``,
|
|
45
|
-
];
|
|
46
|
-
for (const c of conflicts) {
|
|
47
|
-
lines.push(
|
|
48
|
-
` read projection "${c.section}" key "${c.key.replace("\0", " · ")}" — ${c.opinions.length} divergent declarations:`,
|
|
49
|
-
);
|
|
50
|
-
const w = Math.max(...c.opinions.map((o) => o.domain.length));
|
|
51
|
-
for (const o of c.opinions) lines.push(` · ${o.domain.padEnd(w)} ${o.shape}`);
|
|
52
|
-
}
|
|
53
|
-
lines.push(
|
|
54
|
-
``,
|
|
55
|
-
`Fix: give the shared key ONE canonical declaration (the domains must agree, byte-for-byte — ` +
|
|
56
|
-
`for a framework aggregate that means the SAME framework version in every domain), or make ` +
|
|
57
|
-
`the declarations DISTINCT (a non-global aggregate auto-qualifies by its owning domain; a ` +
|
|
58
|
-
`declared read can simply take a different id).`,
|
|
59
|
-
);
|
|
60
|
-
return lines.join("\n");
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
type Fragment = NonNullable<UsdLayer["nomosReadProjection"]>;
|
|
64
|
-
|
|
65
|
-
/** The accumulator sections keyed by declared `id` (mirrors `merge_projection_array_by_id`). */
|
|
66
|
-
const BY_ID_SECTIONS = ["queries", "counts", "spatials", "sums", "mins", "maxes", "orderedReads"] as const;
|
|
67
|
-
/** The sections keyed by `"<of>\0<id>"` (mirrors `merge_projection_array_by_type_and_id`). */
|
|
68
|
-
const BY_TYPE_AND_ID_SECTIONS = ["deriveds", "combineds"] as const;
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Guard the package's layers against a shared-read-projection-key divergence — the EXACT key
|
|
72
|
-
* semantics the kernel accumulator merges by. Identical shared declarations pass (dedupe);
|
|
73
|
-
* divergent ones throw the scalpel. Deterministic (sorted keys/sections). Returns the layers
|
|
74
|
-
* UNTOUCHED (validation only).
|
|
75
|
-
*
|
|
76
|
-
* @throws a scalpel `Error` naming the section, key, and each domain's declaration on divergence.
|
|
77
|
-
*/
|
|
78
|
-
export function guardSharedReadProjectionKeys(layers: readonly UsdLayer[]): readonly UsdLayer[] {
|
|
79
|
-
// section → key → the contributing {domain, canonical JSON} opinions, in package order.
|
|
80
|
-
const seen = new Map<string, Map<string, { domain: string; shape: string }[]>>();
|
|
81
|
-
const note = (section: string, key: string, domain: string, value: unknown): void => {
|
|
82
|
-
const bySection = seen.get(section) ?? new Map<string, { domain: string; shape: string }[]>();
|
|
83
|
-
seen.set(section, bySection);
|
|
84
|
-
const arr = bySection.get(key) ?? [];
|
|
85
|
-
arr.push({ domain, shape: JSON.stringify(value) });
|
|
86
|
-
bySection.set(key, arr);
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
for (const layer of layers) {
|
|
90
|
-
const frag: Fragment | undefined = layer.nomosReadProjection;
|
|
91
|
-
if (frag === undefined) continue;
|
|
92
|
-
const domain = domainOfLayerPath(layer.path);
|
|
93
|
-
for (const [ty, schema] of Object.entries(frag.aggregateFieldKinds ?? {})) {
|
|
94
|
-
note("aggregateFieldKinds", ty, domain, schema);
|
|
95
|
-
}
|
|
96
|
-
const bag = frag as unknown as Record<string, readonly { id: string; of?: string }[] | undefined>;
|
|
97
|
-
for (const section of BY_ID_SECTIONS) {
|
|
98
|
-
for (const item of bag[section] ?? []) note(section, item.id, domain, item);
|
|
99
|
-
}
|
|
100
|
-
for (const section of BY_TYPE_AND_ID_SECTIONS) {
|
|
101
|
-
for (const item of bag[section] ?? []) note(section, `${item.of}\0${item.id}`, domain, item);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const conflicts: ReadProjectionConflict[] = [];
|
|
106
|
-
for (const section of [...seen.keys()].sort()) {
|
|
107
|
-
const bySection = seen.get(section)!;
|
|
108
|
-
for (const key of [...bySection.keys()].sort()) {
|
|
109
|
-
const opinions = bySection.get(key)!;
|
|
110
|
-
if (opinions.length < 2) continue;
|
|
111
|
-
const distinct = new Set(opinions.map((o) => o.shape));
|
|
112
|
-
if (distinct.size > 1) conflicts.push({ section, key, opinions });
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
if (conflicts.length > 0) throw new Error(scalpelMessage(conflicts));
|
|
116
|
-
return layers;
|
|
117
|
-
}
|
|
@@ -1,115 +0,0 @@
|
|
|
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 · SQLite projection · embedded QuickJS engine}.
|
|
4
|
-
//
|
|
5
|
-
// READ-PROJECTION NAMESPACING — the CANONICAL-MANIFEST label transform (the compile-side
|
|
6
|
-
// rename). BUILD-TIME ONLY, split out of `read_projection_namespace.ts` deliberately: this
|
|
7
|
-
// file types against `manifest.ts` (which imports `node:crypto`/`Buffer` for the domain
|
|
8
|
-
// hash), and the classification/qualifier half is imported by PLATFORM-NEUTRAL engine
|
|
9
|
-
// files (`engine_entry.ts`, `wire_encode.ts`, `framework/relations.ts`) that tenant
|
|
10
|
-
// scaffolds typecheck WITHOUT `@types/node`. Keeping the manifest edge here (reached only
|
|
11
|
-
// via `usd.ts`, the build lane) keeps the neutral type graph node-free — the same
|
|
12
|
-
// build/runtime split `stable_ids.ts` vs `stable_ids_types.ts` ratified.
|
|
13
|
-
|
|
14
|
-
import type { CanonicalManifest } from "./manifest.js";
|
|
15
|
-
import type { StableIds, StableAggregateId } from "./stable_ids_types.js";
|
|
16
|
-
import { qualifyReadProjectionType } from "./read_projection_namespace.js";
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Qualify EVERY aggregate-type label a domain's canonical manifest carries — exactly as
|
|
20
|
-
* if the author had renamed `Entry` → `<domainKey>__Entry` in source, which is the #58
|
|
21
|
-
* rename the whole system already supports (sid identity unmoved; the projection folds
|
|
22
|
-
* old-label rows onto the new label via the `was` lineage). Touches: aggregate ids,
|
|
23
|
-
* directive targets, `queries[].returns`, every maintained read's `of` (counts / sums /
|
|
24
|
-
* mins / maxes / spatials / deriveds / combineds / orderedReads), `combineds[].reads`
|
|
25
|
-
* (a related aggregate TYPE — the cross-aggregate invalidation key), and the stable-ids
|
|
26
|
-
* labels (the BARE name is APPENDED to the sid's `was` lineage so pre-namespacing rows
|
|
27
|
-
* normalize onto the qualified label). Fields keep their bare labels (field names never
|
|
28
|
-
* collide across types — the accumulator keys them under the type). Pure + deterministic;
|
|
29
|
-
* with an empty `namespaced` set the manifest is returned UNTOUCHED (the era rule).
|
|
30
|
-
*/
|
|
31
|
-
export function qualifyManifestAggregateLabels(
|
|
32
|
-
manifest: CanonicalManifest,
|
|
33
|
-
domainKey: string,
|
|
34
|
-
namespaced: ReadonlySet<string>,
|
|
35
|
-
): CanonicalManifest {
|
|
36
|
-
if (namespaced.size === 0) return manifest;
|
|
37
|
-
const qa = (n: string): string => (namespaced.has(n) ? qualifyReadProjectionType(domainKey, n) : n);
|
|
38
|
-
|
|
39
|
-
const stableIds: StableIds | undefined =
|
|
40
|
-
manifest.stableIds === undefined
|
|
41
|
-
? undefined
|
|
42
|
-
: {
|
|
43
|
-
...manifest.stableIds,
|
|
44
|
-
aggregates: Object.fromEntries(
|
|
45
|
-
Object.entries(manifest.stableIds.aggregates).map(([name, rec]): [string, StableAggregateId] => {
|
|
46
|
-
if (!namespaced.has(name)) return [name, rec];
|
|
47
|
-
// The bare labels this sid's rows may carry: every prior bare label + the current
|
|
48
|
-
// bare name (pre-namespacing rows; the runtime stamp is qualified going forward).
|
|
49
|
-
const was = [...new Set([...(rec.was ?? []), name])];
|
|
50
|
-
return [qa(name), { ...rec, was }];
|
|
51
|
-
}),
|
|
52
|
-
),
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
...manifest,
|
|
57
|
-
aggregates: manifest.aggregates.map((a) => (namespaced.has(a.id) ? { ...a, id: qa(a.id) } : a)),
|
|
58
|
-
// NB: `capturedReads[].returns` (the #58 read-gate spec the kernel derives against) MUST be
|
|
59
|
-
// qualified too — the projection stamps the QUALIFIED `__type` on every row, so a bare
|
|
60
|
-
// `returns` derives 0 rows at the one gate (the bug that broke captured_reads.test.mjs).
|
|
61
|
-
directives: manifest.directives.map((d) => ({
|
|
62
|
-
...d,
|
|
63
|
-
...(namespaced.has(d.target) ? { target: qa(d.target) } : {}),
|
|
64
|
-
...(d.conflictResolution !== undefined && namespaced.has(d.conflictResolution.aggregate)
|
|
65
|
-
? {
|
|
66
|
-
conflictResolution: {
|
|
67
|
-
...d.conflictResolution,
|
|
68
|
-
aggregate: qa(d.conflictResolution.aggregate),
|
|
69
|
-
},
|
|
70
|
-
}
|
|
71
|
-
: {}),
|
|
72
|
-
...(d.capturedReads !== undefined
|
|
73
|
-
? {
|
|
74
|
-
capturedReads: d.capturedReads.map((r) =>
|
|
75
|
-
namespaced.has(r.returns) ? { ...r, returns: qa(r.returns) } : r,
|
|
76
|
-
),
|
|
77
|
-
}
|
|
78
|
-
: {}),
|
|
79
|
-
})),
|
|
80
|
-
...(stableIds !== undefined ? { stableIds } : {}),
|
|
81
|
-
...(manifest.queries !== undefined
|
|
82
|
-
? { queries: manifest.queries.map((q) => (namespaced.has(q.returns) ? { ...q, returns: qa(q.returns) } : q)) }
|
|
83
|
-
: {}),
|
|
84
|
-
...(manifest.counts !== undefined
|
|
85
|
-
? { counts: manifest.counts.map((c) => (namespaced.has(c.of) ? { ...c, of: qa(c.of) } : c)) }
|
|
86
|
-
: {}),
|
|
87
|
-
...(manifest.sums !== undefined
|
|
88
|
-
? { sums: manifest.sums.map((s) => (namespaced.has(s.of) ? { ...s, of: qa(s.of) } : s)) }
|
|
89
|
-
: {}),
|
|
90
|
-
...(manifest.mins !== undefined
|
|
91
|
-
? { mins: manifest.mins.map((e) => (namespaced.has(e.of) ? { ...e, of: qa(e.of) } : e)) }
|
|
92
|
-
: {}),
|
|
93
|
-
...(manifest.maxes !== undefined
|
|
94
|
-
? { maxes: manifest.maxes.map((e) => (namespaced.has(e.of) ? { ...e, of: qa(e.of) } : e)) }
|
|
95
|
-
: {}),
|
|
96
|
-
...(manifest.spatials !== undefined
|
|
97
|
-
? { spatials: manifest.spatials.map((s) => (namespaced.has(s.of) ? { ...s, of: qa(s.of) } : s)) }
|
|
98
|
-
: {}),
|
|
99
|
-
...(manifest.deriveds !== undefined
|
|
100
|
-
? { deriveds: manifest.deriveds.map((d) => (namespaced.has(d.of) ? { ...d, of: qa(d.of) } : d)) }
|
|
101
|
-
: {}),
|
|
102
|
-
...(manifest.combineds !== undefined
|
|
103
|
-
? {
|
|
104
|
-
combineds: manifest.combineds.map((c) => ({
|
|
105
|
-
...c,
|
|
106
|
-
...(namespaced.has(c.of) ? { of: qa(c.of) } : {}),
|
|
107
|
-
...(namespaced.has(c.reads) ? { reads: qa(c.reads) } : {}),
|
|
108
|
-
})),
|
|
109
|
-
}
|
|
110
|
-
: {}),
|
|
111
|
-
...(manifest.orderedReads !== undefined
|
|
112
|
-
? { orderedReads: manifest.orderedReads.map((r) => (namespaced.has(r.of) ? { ...r, of: qa(r.of) } : r)) }
|
|
113
|
-
: {}),
|
|
114
|
-
};
|
|
115
|
-
}
|
|
@@ -1,286 +0,0 @@
|
|
|
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 QuickJS engine}.
|
|
4
|
-
//
|
|
5
|
-
// COMPILE-TIME USD-LAYER COMPOSITION for SHARED (global) relation types + the
|
|
6
|
-
// fail-closed DIVERGENCE GUARD.
|
|
7
|
-
//
|
|
8
|
-
// THE MODEL. When a package bundles multiple domains, each domain lowers its own
|
|
9
|
-
// `relationType` decls into its own `UsdLayer.nomosRelations`. A NON-global type is
|
|
10
|
-
// auto-qualified by its owning domain (`estate` → `estate__workspace`), so two
|
|
11
|
-
// domains that each declare the same bare type NEVER collide — their qualified ids
|
|
12
|
-
// differ, and they stay separate layers. A `.asGlobal()` type (a kernel-coupled
|
|
13
|
-
// framework type, e.g. governance/read-visibility `workspace`) stays a BARE shared
|
|
14
|
-
// name, so MULTIPLE domains that each contribute to one global type land on the SAME
|
|
15
|
-
// canonical id. That cross-domain merge USED to happen only at the KERNEL (runtime
|
|
16
|
-
// `merge_layers`, a blunt per-type last-wins that SILENTLY DROPS the losing layer's
|
|
17
|
-
// relations — the bug that broke co2's `createSite`). We move it to COMPILE time, in
|
|
18
|
-
// TS, using USD's composition MODEL (LIVRPS sublayer strength):
|
|
19
|
-
//
|
|
20
|
-
// * COMPOSE (deliverable #1): for each SHARED global type, UNION the relations
|
|
21
|
-
// across the contributing domains — a prim-set union, so EVERY domain's relations
|
|
22
|
-
// survive (nothing is dropped). The composed union is written back into every
|
|
23
|
-
// contributing layer so the downstream kernel merge (union OR last-wins) yields
|
|
24
|
-
// the full union regardless of its policy. This is idempotent and byte-stable.
|
|
25
|
-
//
|
|
26
|
-
// * GUARD (deliverable #2): when two or more domains declare the SAME relation on a
|
|
27
|
-
// SHARED global type with DIVERGENT rewrites (co2's `workspace.admin`: `self()` in
|
|
28
|
-
// estate/platform vs `union(self, computed(creator))` in home), REFUSE AT COMPILE
|
|
29
|
-
// with a scalpel message naming the type, the relation, and each domain's rewrite.
|
|
30
|
-
// Single-owner relations and byte-identical agreeing opinions PASS (union). This
|
|
31
|
-
// turns the kernel's blunt `None` / "no-relation-schema" into a precise compile
|
|
32
|
-
// refusal — the era rule ("the law is the era") is preserved because a package with
|
|
33
|
-
// no shared global type composes to itself, byte-identical.
|
|
34
|
-
//
|
|
35
|
-
// SUBLAYER STRENGTH ORDER (documented, deliverable #1): the package's domain order is
|
|
36
|
-
// WEAK → STRONG (Nomos module-fold notation — later wins), which maps onto USD
|
|
37
|
-
// `subLayers` STRONGEST-FIRST by reversal (the same left-biased monoid, opposite list
|
|
38
|
-
// notation — the convention `usd_layers.ts::layeredRootUsda` already records). For a
|
|
39
|
-
// NON-divergent shared type the union is order-INDEPENDENT (every domain's opinion on a
|
|
40
|
-
// given relation is identical, so which layer "wins" is immaterial); order matters ONLY
|
|
41
|
-
// to the visualization flatten, never to the composed IR.
|
|
42
|
-
|
|
43
|
-
import type { UsdLayer } from "./usd.js";
|
|
44
|
-
|
|
45
|
-
/** A single relation's definition as it rides in `nomosRelations` (`{rewrite, allowed}`). */
|
|
46
|
-
type RelDef = NonNullable<UsdLayer["nomosRelations"]>["types"][string]["relations"][string];
|
|
47
|
-
/** One relation TYPE's spec (`{relations, caps?}`). */
|
|
48
|
-
type RelSpec = NonNullable<UsdLayer["nomosRelations"]>["types"][string];
|
|
49
|
-
|
|
50
|
-
/** Strip the `/Nomos/` prim-scope prefix to recover the owning domain key from a layer path. */
|
|
51
|
-
export function domainOfLayerPath(path: string): string {
|
|
52
|
-
return path.startsWith("/Nomos/") ? path.slice("/Nomos/".length) : path;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Render a rewrite AST to the author-facing surface syntax (`self()` / `computed(x)` /
|
|
57
|
-
* `union(...)` / `via(t, c)` / …) — the readable form the scalpel message + the layered
|
|
58
|
-
* `.usda` provenance attr carry. A `null`/absent rewrite is a pure-direct relation (`this`).
|
|
59
|
-
*/
|
|
60
|
-
export function renderRewrite(rw: unknown): string {
|
|
61
|
-
if (rw === null || rw === undefined) return "this";
|
|
62
|
-
const r = rw as { kind?: string; [k: string]: unknown };
|
|
63
|
-
switch (r.kind) {
|
|
64
|
-
case "this":
|
|
65
|
-
return "self()";
|
|
66
|
-
case "computed":
|
|
67
|
-
return `computed(${String(r.relation)})`;
|
|
68
|
-
case "ttu":
|
|
69
|
-
return `${r.all ? "viaAll" : "via"}(${String(r.tupleset)}, ${String(r.computed)})`;
|
|
70
|
-
case "union":
|
|
71
|
-
return `union(${(r.children as unknown[]).map(renderRewrite).join(", ")})`;
|
|
72
|
-
case "intersection":
|
|
73
|
-
return `intersection(${(r.children as unknown[]).map(renderRewrite).join(", ")})`;
|
|
74
|
-
case "exclusion":
|
|
75
|
-
return `exclusion(${renderRewrite(r.base)}, ${renderRewrite(r.subtract)})`;
|
|
76
|
-
default:
|
|
77
|
-
return JSON.stringify(rw);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** A per-relation divergence, gathered for the scalpel message. */
|
|
82
|
-
interface RelationConflict {
|
|
83
|
-
readonly type: string;
|
|
84
|
-
readonly relation: string;
|
|
85
|
-
/** Each contributing domain's opinion, in package (weak→strong) order. */
|
|
86
|
-
readonly opinions: { readonly domain: string; readonly rewrite: string }[];
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** Format the accumulated divergences into the fail-closed refusal (mirrors `failclosed_check.py`). */
|
|
90
|
-
function scalpelMessage(conflicts: readonly RelationConflict[]): string {
|
|
91
|
-
const lines: string[] = [
|
|
92
|
-
`Refusing to compose the package: shared relation type(s) carry DIVERGENT rewrites across ` +
|
|
93
|
-
`domains — the composed flatten would SILENTLY take the strongest layer's opinion (LIVRPS ` +
|
|
94
|
-
`last-wins), so this is a fail-closed compile refusal, not a silent pick.`,
|
|
95
|
-
``,
|
|
96
|
-
];
|
|
97
|
-
for (const c of conflicts) {
|
|
98
|
-
lines.push(` relation type "${c.type}" relation "${c.relation}" — ${c.opinions.length} divergent opinions:`);
|
|
99
|
-
const w = Math.max(...c.opinions.map((o) => o.domain.length));
|
|
100
|
-
for (const o of c.opinions) lines.push(` · ${o.domain.padEnd(w)} ${o.rewrite}`);
|
|
101
|
-
}
|
|
102
|
-
lines.push(
|
|
103
|
-
``,
|
|
104
|
-
`Fix: give the type ONE canonical definition of each divergent relation (the domains must ` +
|
|
105
|
-
`agree, byte-for-byte), or use DISTINCT / domain-qualified relation types (drop \`.asGlobal()\` ` +
|
|
106
|
-
`so each domain's type auto-qualifies and never shares an id).`,
|
|
107
|
-
);
|
|
108
|
-
return lines.join("\n");
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Compose the SHARED (global, bare-id) relation types across a package's layers — UNION the
|
|
113
|
-
* relations per shared type, GUARD divergent rewrites fail-closed, and write the union back into
|
|
114
|
-
* every contributing layer. Non-shared (single-owner / auto-qualified) types are untouched, so a
|
|
115
|
-
* package with no shared global type returns byte-identical layers (the era rule). Deterministic:
|
|
116
|
-
* sorted keys, sorted diagnostics.
|
|
117
|
-
*
|
|
118
|
-
* @throws a scalpel `Error` naming the type, relation, and each domain's rewrite on any divergence.
|
|
119
|
-
*/
|
|
120
|
-
export function composeSharedRelationTypes(layers: readonly UsdLayer[]): UsdLayer[] {
|
|
121
|
-
// type id → the layers (domains) that contribute it, in package (weak→strong) order.
|
|
122
|
-
const contributors = new Map<string, { domain: string; spec: RelSpec }[]>();
|
|
123
|
-
for (const layer of layers) {
|
|
124
|
-
const rels = layer.nomosRelations;
|
|
125
|
-
if (rels === undefined) continue;
|
|
126
|
-
for (const [type, spec] of Object.entries(rels.types)) {
|
|
127
|
-
const arr = contributors.get(type) ?? [];
|
|
128
|
-
arr.push({ domain: domainOfLayerPath(layer.path), spec });
|
|
129
|
-
contributors.set(type, arr);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const composed = new Map<string, RelSpec>();
|
|
134
|
-
const conflicts: RelationConflict[] = [];
|
|
135
|
-
for (const type of [...contributors.keys()].sort()) {
|
|
136
|
-
const contribs = contributors.get(type)!;
|
|
137
|
-
// A type contributed by ONE domain is single-owner — nothing to compose (untouched).
|
|
138
|
-
if (contribs.length < 2) continue;
|
|
139
|
-
|
|
140
|
-
// caps must agree across the contributing domains (a divergent bound is a hard error).
|
|
141
|
-
let caps: RelSpec["caps"];
|
|
142
|
-
for (const c of contribs) {
|
|
143
|
-
if (c.spec.caps === undefined) continue;
|
|
144
|
-
if (caps === undefined) caps = c.spec.caps;
|
|
145
|
-
else if (caps.maxDepth !== c.spec.caps.maxDepth || caps.maxFanout !== c.spec.caps.maxFanout) {
|
|
146
|
-
throw new Error(
|
|
147
|
-
`Refusing to compose the package: shared relation type "${type}" is declared with ` +
|
|
148
|
-
`CONFLICTING caps across domains (${contribs.map((x) => x.domain).join(", ")}). Give the ` +
|
|
149
|
-
`type one canonical \`caps\`.`,
|
|
150
|
-
);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Gather every domain's opinion on each relation (in package order).
|
|
155
|
-
const opinions = new Map<string, { domain: string; def: RelDef }[]>();
|
|
156
|
-
for (const c of contribs) {
|
|
157
|
-
for (const [rel, def] of Object.entries(c.spec.relations)) {
|
|
158
|
-
const arr = opinions.get(rel) ?? [];
|
|
159
|
-
arr.push({ domain: c.domain, def });
|
|
160
|
-
opinions.set(rel, arr);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const unioned: Record<string, RelDef> = {};
|
|
165
|
-
for (const rel of [...opinions.keys()].sort()) {
|
|
166
|
-
const ops = opinions.get(rel)!;
|
|
167
|
-
const distinct = new Set(ops.map((o) => JSON.stringify(o.def)));
|
|
168
|
-
if (distinct.size > 1) {
|
|
169
|
-
conflicts.push({
|
|
170
|
-
type,
|
|
171
|
-
relation: rel,
|
|
172
|
-
opinions: ops.map((o) => ({ domain: o.domain, rewrite: renderRewrite((o.def as { rewrite: unknown }).rewrite) })),
|
|
173
|
-
});
|
|
174
|
-
} else {
|
|
175
|
-
unioned[rel] = ops[0]!.def; // agreeing opinions / single-owner relation — keep it
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
// Sorted relation keys (byte-determinism against the per-layer emission).
|
|
179
|
-
const relations: Record<string, RelDef> = {};
|
|
180
|
-
for (const k of Object.keys(unioned).sort()) relations[k] = unioned[k]!;
|
|
181
|
-
composed.set(type, { relations, ...(caps !== undefined ? { caps } : {}) });
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (conflicts.length > 0) throw new Error(scalpelMessage(conflicts));
|
|
185
|
-
if (composed.size === 0) return [...layers]; // nothing shared — byte-identical (era rule)
|
|
186
|
-
|
|
187
|
-
// Write the composed union back into every contributing layer, keeping type keys sorted.
|
|
188
|
-
return layers.map((layer) => {
|
|
189
|
-
const rels = layer.nomosRelations;
|
|
190
|
-
if (rels === undefined) return layer;
|
|
191
|
-
let touched = false;
|
|
192
|
-
const merged: Record<string, RelSpec> = {};
|
|
193
|
-
for (const [type, spec] of Object.entries(rels.types)) {
|
|
194
|
-
const c = composed.get(type);
|
|
195
|
-
if (c !== undefined) {
|
|
196
|
-
merged[type] = c;
|
|
197
|
-
touched = true;
|
|
198
|
-
} else {
|
|
199
|
-
merged[type] = spec;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
if (!touched) return layer;
|
|
203
|
-
const sorted: Record<string, RelSpec> = {};
|
|
204
|
-
for (const k of Object.keys(merged).sort()) sorted[k] = merged[k]!;
|
|
205
|
-
return { ...layer, nomosRelations: { v: 1 as const, types: sorted } };
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// ── the REAL layered `.usda` emission (deliverable #3 — for usdview / usdcat --flatten) ──────────
|
|
210
|
-
|
|
211
|
-
/** One domain's contribution to the layered relation emission: its bare-id relation types. */
|
|
212
|
-
export interface RelationLayerInput {
|
|
213
|
-
readonly domain: string;
|
|
214
|
-
/** The domain's `nomosRelations.types` (UNCOMPOSED — the layers ARE the sublayers usdcat composes). */
|
|
215
|
-
readonly types: Record<string, RelSpec>;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/** `"a"` → escaped USDA string literal (minimal — relation/rewrite tokens are `[A-Za-z0-9_.,()]`). */
|
|
219
|
-
function usda(s: string): string {
|
|
220
|
-
return `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Emit ONE domain's relation opinions as a real `.usda` sublayer: `def "<Type>" { def "<relation>"
|
|
225
|
-
* { custom string nomos:rewrite = "…"; custom string nomos:from = "<domain>" } }`. Structured so
|
|
226
|
-
* `usdcat --flatten` composes the relation prims across sublayers (a stronger layer's `nomos:rewrite`
|
|
227
|
-
* wins; the relation SET is the union). Deterministic: types + relations sorted.
|
|
228
|
-
*/
|
|
229
|
-
export function emitRelationSublayerUsda(input: RelationLayerInput): string {
|
|
230
|
-
const lines: string[] = [`#usda 1.0`, `(`, ` doc = "Nomos relation layer — domain ${input.domain}"`, `)`, ``];
|
|
231
|
-
for (const type of Object.keys(input.types).sort()) {
|
|
232
|
-
lines.push(`def ${usda(type)}`, `{`);
|
|
233
|
-
const relations = input.types[type]!.relations;
|
|
234
|
-
for (const rel of Object.keys(relations).sort()) {
|
|
235
|
-
const def = relations[rel]! as { rewrite: unknown };
|
|
236
|
-
lines.push(
|
|
237
|
-
` def ${usda(rel)}`,
|
|
238
|
-
` {`,
|
|
239
|
-
` custom string nomos:rewrite = ${usda(renderRewrite(def.rewrite))}`,
|
|
240
|
-
` custom string nomos:from = ${usda(input.domain)}`,
|
|
241
|
-
` }`,
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
lines.push(`}`, ``);
|
|
245
|
-
}
|
|
246
|
-
return lines.join("\n");
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/** The result of {@link emitLayeredRelationUsda}: a root document + one sublayer file per domain. */
|
|
250
|
-
export interface LayeredRelationEmit {
|
|
251
|
-
/** The root `.usda` (`subLayers = [...]`, STRONGEST-FIRST) that usdcat --flatten composes. */
|
|
252
|
-
readonly root: string;
|
|
253
|
-
/** Per-domain sublayer files (`name` is a bare filename; write beside the root). */
|
|
254
|
-
readonly files: { readonly name: string; readonly text: string }[];
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Emit a REAL layered relation `.usda` set: one sublayer per domain (its relation opinions as prims)
|
|
259
|
-
* + a root with `subLayers` STRONGEST-FIRST, so `usdcat --flatten <root>` composes the package's
|
|
260
|
-
* shared relation types. `inputs` are in package order (WEAK → STRONG); the root reverses them to
|
|
261
|
-
* strongest-first (the documented convention). Deterministic (sorted within each layer, stable file
|
|
262
|
-
* names). Additive build artifact — never feeds the package hash.
|
|
263
|
-
*/
|
|
264
|
-
export function emitLayeredRelationUsda(inputs: readonly RelationLayerInput[], packageName: string): LayeredRelationEmit {
|
|
265
|
-
const safe = (s: string) => s.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
266
|
-
const files = inputs.map((input) => ({
|
|
267
|
-
name: `${safe(input.domain)}.usda`,
|
|
268
|
-
text: emitRelationSublayerUsda(input),
|
|
269
|
-
}));
|
|
270
|
-
// subLayers is STRONGEST-FIRST → reverse the weak→strong package order.
|
|
271
|
-
const strongestFirst = [...files].reverse();
|
|
272
|
-
const root = [
|
|
273
|
-
`#usda 1.0`,
|
|
274
|
-
`(`,
|
|
275
|
-
` doc = """Nomos relation-composition layers for package ${packageName}. subLayers are`,
|
|
276
|
-
`STRONGEST-FIRST (USD layer-stack order); the Nomos domain list composes weak→strong (later`,
|
|
277
|
-
`wins). \`usdcat --flatten\` composes the shared relation types; the compiler's TS composition`,
|
|
278
|
-
`(composeSharedRelationTypes) is the fail-closed guard layered on top."""`,
|
|
279
|
-
` subLayers = [`,
|
|
280
|
-
...strongestFirst.map((f, i) => ` @./${f.name}@${i < strongestFirst.length - 1 ? "," : ""}`),
|
|
281
|
-
` ]`,
|
|
282
|
-
`)`,
|
|
283
|
-
``,
|
|
284
|
-
].join("\n");
|
|
285
|
-
return { root, files };
|
|
286
|
-
}
|