@githolon/dsl 0.32.0 → 0.34.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/src/errors.dart +105 -1
- package/dart/lib/src/webview_bridge.dart +6 -4
- package/dart/test/webview_bridge_test.dart +65 -0
- package/package.json +1 -1
- package/src/aggregate.ts +12 -1
- package/src/authoring.ts +4 -1
- package/src/birth_meter.ts +300 -0
- package/src/birth_meter_ensure.ts +67 -0
- package/src/build_package.ts +59 -0
- package/src/codegen_dart.ts +347 -3
- package/src/codegen_ts.ts +74 -1
- package/src/compile_engine.ts +13 -2
- package/src/compile_package_main.ts +42 -2
- package/src/directive.ts +10 -1
- package/src/engine_entry.ts +56 -9
- package/src/fields.ts +84 -0
- package/src/framework/birth_meter.ts +125 -0
- package/src/framework/impure_capability.ts +5 -2
- package/src/framework/platform_admin.ts +4 -1
- package/src/framework/relations.ts +19 -0
- package/src/framework/workspaces.ts +42 -1
- package/src/index.ts +53 -0
- package/src/read.ts +22 -0
- package/src/read_projection_composition.ts +117 -0
- package/src/read_projection_manifest.ts +94 -0
- package/src/read_projection_namespace.ts +152 -0
- package/src/usd.ts +141 -10
- package/src/usd_layers.ts +82 -0
- package/src/value_object.ts +456 -0
- package/src/wire_encode.ts +34 -3
|
@@ -482,6 +482,13 @@ export function buildGenesisChain(p: {
|
|
|
482
482
|
* chains arm the signature requirement.
|
|
483
483
|
*/
|
|
484
484
|
requiresWarrant?: boolean;
|
|
485
|
+
/**
|
|
486
|
+
* METERED BIRTH (slice A3 — the descendant budget). When present, the child's genesis includes a
|
|
487
|
+
* `seedBirthPolicy` step so it is BORN BOUNDED (its own default birth allowance per subject).
|
|
488
|
+
* OPT-IN: thread it only when the `lawHash` the order names carries `seedBirthPolicy` (a governance
|
|
489
|
+
* package compiled under the metered-birth DSL) — see the poolSteps note below.
|
|
490
|
+
*/
|
|
491
|
+
descendantPool?: number;
|
|
485
492
|
}): string {
|
|
486
493
|
const founder = "user:" + p.principal;
|
|
487
494
|
// THE ORDER-DECLARED LAW INSTALL (G3): the child installs the EXACT law the order names — bytes are NOT in
|
|
@@ -508,8 +515,29 @@ export function buildGenesisChain(p: {
|
|
|
508
515
|
authorizationHash: "",
|
|
509
516
|
parentHead: "",
|
|
510
517
|
});
|
|
518
|
+
// METERED BIRTH (slice A3 — THE POOL CHAINS TO ROOT): when the order carries a `descendantPool`, the
|
|
519
|
+
// child is BORN BOUNDED — a `seedBirthPolicy` genesis step writes the child's own BirthPolicy singleton
|
|
520
|
+
// (its default birth allowance) BEFORE the warrant lands, exactly like every other genesis seed (the
|
|
521
|
+
// still-warrant-free prefix runs unsigned). Root bounds platforms (this pool), platforms bound homes,
|
|
522
|
+
// homes bound estates — the tree meters by induction, with zero cross-workspace reads on any gate.
|
|
523
|
+
// OPT-IN (deliberate): the step folds through the CHILD'S OWN gate under the law hash the order names,
|
|
524
|
+
// so it may only be threaded when that law carries `seedBirthPolicy` (a law compiled under this DSL) —
|
|
525
|
+
// an unconditional default would refuse the genesis of every birth pinning an OLDER law hash. V1
|
|
526
|
+
// BOUNDARY: each node enforces its own count-quota; cross-level pool CONSERVATION arithmetic is v2.
|
|
527
|
+
const poolSteps =
|
|
528
|
+
p.descendantPool !== undefined
|
|
529
|
+
? [
|
|
530
|
+
{
|
|
531
|
+
domain: "workspaces",
|
|
532
|
+
directiveId: "seedBirthPolicy",
|
|
533
|
+
actor: founder,
|
|
534
|
+
payload: { maxBirthsPerPrincipal: p.descendantPool, setBy: p.principal, setAt: p.bornAt },
|
|
535
|
+
},
|
|
536
|
+
]
|
|
537
|
+
: [];
|
|
511
538
|
return JSON.stringify([
|
|
512
539
|
...installSteps,
|
|
540
|
+
...poolSteps,
|
|
513
541
|
// THE WARRANT-FLIP (THE GOVERNANCE DEFAULT): a governance birth is born WARRANTED by default — a REAL
|
|
514
542
|
// self-id (the workspace name) so signed control-plane authorship is enforced on every lane. The ONLY
|
|
515
543
|
// way to stay on the legacy host-stamped lane ("" self-id, byte-identical to a pre-VA chain) is an
|
|
@@ -926,6 +954,11 @@ export const birthChild = directive("birthChild")
|
|
|
926
954
|
* is born WARRANTED (signed authorship enforced on every lane). Absent/false ⇒ the legacy
|
|
927
955
|
* host-stamped lane (byte-identical to a pre-VA birth). Only meaningful with a genesis. */
|
|
928
956
|
requiresWarrant: z.boolean().optional(),
|
|
957
|
+
/** METERED BIRTH (slice A3): the child's own descendant budget — when present, the genesis
|
|
958
|
+
* recipe seeds the child's BirthPolicy (`seedBirthPolicy`) so it is BORN BOUNDED. OPT-IN:
|
|
959
|
+
* only pass it when the `lawHash` the order names carries `seedBirthPolicy` (a governance
|
|
960
|
+
* package compiled under the metered-birth DSL). Only meaningful with a genesis. */
|
|
961
|
+
descendantPool: z.number().int().positive().optional(),
|
|
929
962
|
})
|
|
930
963
|
// A genesis birth (a platform) must carry a principal AND a bornAt — it owns a subtree, a lineage
|
|
931
964
|
// anchor, and a law-authored genesis chain whose warrant is stamped at bornAt.
|
|
@@ -946,7 +979,7 @@ export const birthChild = directive("birthChild")
|
|
|
946
979
|
// EXPLICITLY opts out (`requiresWarrant === false`). Thread the flag through ONLY when the request set
|
|
947
980
|
// it — undefined falls to buildGenesisChain's warranted default; an explicit `false` rides the compat
|
|
948
981
|
// hatch (an era-safe legacy re-build).
|
|
949
|
-
const genesisChain = buildGenesisChain({ name: p.name, principal: p.principal, parent: p.parent, tier: p.tier, foundingAdminHash: p.foundingAdminHash, bornAt: p.bornAt as string, requestedAt: p.requestedAt, ...(p.frameworkHash ? { frameworkHash: p.frameworkHash } : {}), ...(p.lawHash ? { lawHash: p.lawHash } : {}), ...(p.requiresWarrant !== undefined ? { requiresWarrant: p.requiresWarrant } : {}) });
|
|
982
|
+
const genesisChain = buildGenesisChain({ name: p.name, principal: p.principal, parent: p.parent, tier: p.tier, foundingAdminHash: p.foundingAdminHash, bornAt: p.bornAt as string, requestedAt: p.requestedAt, ...(p.frameworkHash ? { frameworkHash: p.frameworkHash } : {}), ...(p.lawHash ? { lawHash: p.lawHash } : {}), ...(p.requiresWarrant !== undefined ? { requiresWarrant: p.requiresWarrant } : {}), ...(p.descendantPool !== undefined ? { descendantPool: p.descendantPool } : {}) });
|
|
950
983
|
// THE GENESIS RECIPE IS ALSO A LINEAGE/AUDIT FACT on the parent's chain (the parent records what it
|
|
951
984
|
// ORDERED — law naming law, legitimate). The kernel does NOT read this field to detect the birth.
|
|
952
985
|
rec.set("genesisChain", genesisChain);
|
|
@@ -1645,3 +1678,11 @@ export const workspaceQuotaBirth = workspaceInvariant("WorkspaceQuotaBirth")
|
|
|
1645
1678
|
.assert((_snapshots, ctx) =>
|
|
1646
1679
|
quotaVerdict(ctx?.reads ?? [], String((ctx?.intent ?? {}).principal ?? "")),
|
|
1647
1680
|
);
|
|
1681
|
+
|
|
1682
|
+
// ── METERED BIRTH (slice A — the allowance governance in the workspaces domain) ─────────────────────
|
|
1683
|
+
// Re-exported here so any governance package composed from THIS module carries the birth-allowance
|
|
1684
|
+
// surface: `setBirthPolicy`/`grantBirthAllowance` (admin-gated) and `seedBirthPolicy` — the A3 genesis
|
|
1685
|
+
// seed `buildGenesisChain`'s `descendantPool` step dispatches as `workspaces/seedBirthPolicy` (it must
|
|
1686
|
+
// live in the SAME domain key the genesis recipe names). The meter's structural half (record + quota)
|
|
1687
|
+
// is auto-ensured by `.births()` — see `../birth_meter.ts`.
|
|
1688
|
+
export { setBirthPolicy, seedBirthPolicy, grantBirthAllowance, BirthPolicy, BirthGrant, NomosBirthRecord, birthRecordsByPrincipal, birthPolicy, birthGrantByPrincipal } from "./birth_meter.js";
|
package/src/index.ts
CHANGED
|
@@ -40,6 +40,26 @@ export {
|
|
|
40
40
|
secretField,
|
|
41
41
|
publicField,
|
|
42
42
|
} from "./fields.js";
|
|
43
|
+
// FIRST-CLASS COMPOSABLE VALUE OBJECTS (`value_object.ts`): named, reusable, nestable
|
|
44
|
+
// structured values — ONE definition yields the zod (payload validation), the TS type,
|
|
45
|
+
// the generated Dart class, and the `nomosValueObjects` IR descriptor. A VO lowers to a
|
|
46
|
+
// single Lww-class json-object leaf (wholesale-replace merge — no per-subfield merge).
|
|
47
|
+
// `geo.*` are the RFC 7946 geometry VOs (pair `geo.point()` with `spatial(...)`).
|
|
48
|
+
export {
|
|
49
|
+
valueObject,
|
|
50
|
+
geo,
|
|
51
|
+
type ValueObject,
|
|
52
|
+
type ValueObjectValue,
|
|
53
|
+
type OptionalValueObject,
|
|
54
|
+
type ValueObjectShapeEntry,
|
|
55
|
+
type ValueObjectDescriptor,
|
|
56
|
+
type ValueObjectFieldDescriptor,
|
|
57
|
+
type GeometryKind,
|
|
58
|
+
type GeoPointValue,
|
|
59
|
+
type GeoLineStringValue,
|
|
60
|
+
type GeoPolygonValue,
|
|
61
|
+
type ValueObjectUsage,
|
|
62
|
+
} from "./value_object.js";
|
|
43
63
|
export {
|
|
44
64
|
type PrincipalEvidence,
|
|
45
65
|
type AttributionPrincipal,
|
|
@@ -312,6 +332,28 @@ export {
|
|
|
312
332
|
birthAttestation,
|
|
313
333
|
parentAttestedBirth,
|
|
314
334
|
} from "./framework/home.js";
|
|
335
|
+
// METERED BIRTH (slice A — architecture/metered_birth_and_attested_reads.md): the structural birth
|
|
336
|
+
// record + the parent-local birth quota + the allowance governance surface. The record/quota STRUCTURE
|
|
337
|
+
// is auto-ensured on every `.births()` law (zero tenant code); these exports are the GOVERNANCE half a
|
|
338
|
+
// tenant composes in to set/override allowances, plus the constants/predicates tests and tools read.
|
|
339
|
+
export {
|
|
340
|
+
NomosBirthRecord,
|
|
341
|
+
BirthPolicy,
|
|
342
|
+
BirthGrant,
|
|
343
|
+
BIRTH_POLICY_ID,
|
|
344
|
+
NOMOS_BIRTH_RECORD_PREFIX,
|
|
345
|
+
birthRecordId,
|
|
346
|
+
birthGrantId,
|
|
347
|
+
birthRecordsByPrincipal,
|
|
348
|
+
birthPolicy,
|
|
349
|
+
birthGrantByPrincipal,
|
|
350
|
+
DEFAULT_MAX_BIRTHS_PER_PRINCIPAL,
|
|
351
|
+
birthQuotaVerdict,
|
|
352
|
+
birthQuotaInvariantId,
|
|
353
|
+
setBirthPolicy,
|
|
354
|
+
seedBirthPolicy,
|
|
355
|
+
grantBirthAllowance,
|
|
356
|
+
} from "./framework/birth_meter.js";
|
|
315
357
|
// THE PLATFORM FOUNDING-ADMIN BOOTSTRAP (the platform analog of seedHomeCreator) — a tenant-importable
|
|
316
358
|
// factory that seats a tenant-named governance relation for a write-once, author-bound founder at genesis.
|
|
317
359
|
export {
|
|
@@ -438,6 +480,17 @@ export {
|
|
|
438
480
|
type AllowedSubject,
|
|
439
481
|
type RelationCaps,
|
|
440
482
|
} from "./relation_schema.js";
|
|
483
|
+
|
|
484
|
+
// READ-PROJECTION (aggregate-type) NAMESPACING — the read-layer mirror of the relation namespacing:
|
|
485
|
+
// the qualifier, the deliberate framework GLOBAL allowlist, and the classification the compiler +
|
|
486
|
+
// sealed engine share (`aggregate(id, fields, { global: true })` is the dynamic opt-out).
|
|
487
|
+
export {
|
|
488
|
+
READ_PROJECTION_DELIMITER,
|
|
489
|
+
GLOBAL_READ_PROJECTION_AGGREGATE_IDS,
|
|
490
|
+
qualifyReadProjectionType,
|
|
491
|
+
isNamespacedAggregate,
|
|
492
|
+
namespacedAggregateIds,
|
|
493
|
+
} from "./read_projection_namespace.js";
|
|
441
494
|
// SECURE-BY-DEFAULT SUGAR over the relation schema — plain-English combinators, audiences, presets.
|
|
442
495
|
// Pure sugar: every export here BUILDS the existing Rewrite/RelationTypeDecl AST (no new wire shape).
|
|
443
496
|
export {
|
package/src/read.ts
CHANGED
|
@@ -74,6 +74,28 @@ export function read<T = unknown>(query: QueryDecl, args: Record<string, string>
|
|
|
74
74
|
return result;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/**
|
|
78
|
+
* THE BIRTH-METER CAPTURE (metered birth, slice A2 — library-internal). Identical semantics to
|
|
79
|
+
* {@link read} — serve the declared query live off `nomos.read` + record `{queryId, args, result}`
|
|
80
|
+
* into the SAME per-dispatch footprint — but with a DISTINCT refusal string, deliberately NOT the
|
|
81
|
+
* {@link CAPTURED_READ_LUMP_MARKER}: the marker greps for TENANT-authored `read()` calls that lack a
|
|
82
|
+
* declaration, whereas this door is only ever invoked by the `.births()` lane (which auto-DECLARES
|
|
83
|
+
* its three queries on the directive), so a lump that merely bundles it must not trip the read-free
|
|
84
|
+
* compile gate. A `.births()` dispatch on a host with no `nomos.read` still halts NAMED (fail-closed).
|
|
85
|
+
*/
|
|
86
|
+
export function __captureBirthMeterRead(query: QueryDecl, args: Record<string, string>): void {
|
|
87
|
+
const h = host() as { read?: unknown } | undefined;
|
|
88
|
+
if (typeof h?.read !== "function") {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`birth-meter read('${query.id}'): this host provides no nomos.read port — a .births() ` +
|
|
91
|
+
`dispatch captures its quota reads through the engine/client runtime (tests: stub ` +
|
|
92
|
+
`globalThis.nomos.read).`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
const result = host().read(query.id, args);
|
|
96
|
+
footprint.push({ queryId: query.id, args, result });
|
|
97
|
+
}
|
|
98
|
+
|
|
77
99
|
/**
|
|
78
100
|
* The compile gate's lump sentinel — the EXACT substring of the refusal `read()` throws above.
|
|
79
101
|
* It exists in a bundled engine lump iff `read` itself survived tree-shaking, i.e. iff some
|
|
@@ -0,0 +1,117 @@
|
|
|
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 every active install's
|
|
10
|
+
// `nomosReadProjection` fragment 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
|
+
// key declared twice with DIVERGENT shapes: "divergent read projection declaration
|
|
14
|
+
// for key <k>". At RUNTIME that refusal WEDGES the workspace's read plane.
|
|
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
|
+
// runtime wedge into a precise COMPILE refusal: identical shared declarations PASS
|
|
22
|
+
// (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 kernel's installed-read-manifest accumulator refuses exactly this at ` +
|
|
42
|
+
`SERVE time ("divergent read projection declaration for key …"), wedging the workspace's ` +
|
|
43
|
+
`read plane. Fail-closed compile refusal, not a runtime wedge.`,
|
|
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
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
directives: manifest.directives.map((d) => (namespaced.has(d.target) ? { ...d, target: qa(d.target) } : d)),
|
|
59
|
+
...(stableIds !== undefined ? { stableIds } : {}),
|
|
60
|
+
...(manifest.queries !== undefined
|
|
61
|
+
? { queries: manifest.queries.map((q) => (namespaced.has(q.returns) ? { ...q, returns: qa(q.returns) } : q)) }
|
|
62
|
+
: {}),
|
|
63
|
+
...(manifest.counts !== undefined
|
|
64
|
+
? { counts: manifest.counts.map((c) => (namespaced.has(c.of) ? { ...c, of: qa(c.of) } : c)) }
|
|
65
|
+
: {}),
|
|
66
|
+
...(manifest.sums !== undefined
|
|
67
|
+
? { sums: manifest.sums.map((s) => (namespaced.has(s.of) ? { ...s, of: qa(s.of) } : s)) }
|
|
68
|
+
: {}),
|
|
69
|
+
...(manifest.mins !== undefined
|
|
70
|
+
? { mins: manifest.mins.map((e) => (namespaced.has(e.of) ? { ...e, of: qa(e.of) } : e)) }
|
|
71
|
+
: {}),
|
|
72
|
+
...(manifest.maxes !== undefined
|
|
73
|
+
? { maxes: manifest.maxes.map((e) => (namespaced.has(e.of) ? { ...e, of: qa(e.of) } : e)) }
|
|
74
|
+
: {}),
|
|
75
|
+
...(manifest.spatials !== undefined
|
|
76
|
+
? { spatials: manifest.spatials.map((s) => (namespaced.has(s.of) ? { ...s, of: qa(s.of) } : s)) }
|
|
77
|
+
: {}),
|
|
78
|
+
...(manifest.deriveds !== undefined
|
|
79
|
+
? { deriveds: manifest.deriveds.map((d) => (namespaced.has(d.of) ? { ...d, of: qa(d.of) } : d)) }
|
|
80
|
+
: {}),
|
|
81
|
+
...(manifest.combineds !== undefined
|
|
82
|
+
? {
|
|
83
|
+
combineds: manifest.combineds.map((c) => ({
|
|
84
|
+
...c,
|
|
85
|
+
...(namespaced.has(c.of) ? { of: qa(c.of) } : {}),
|
|
86
|
+
...(namespaced.has(c.reads) ? { reads: qa(c.reads) } : {}),
|
|
87
|
+
})),
|
|
88
|
+
}
|
|
89
|
+
: {}),
|
|
90
|
+
...(manifest.orderedReads !== undefined
|
|
91
|
+
? { orderedReads: manifest.orderedReads.map((r) => (namespaced.has(r.of) ? { ...r, of: qa(r.of) } : r)) }
|
|
92
|
+
: {}),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
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 (aggregate-type) NAMESPACING — the read-layer mirror of the shipped
|
|
6
|
+
// relation-type namespacing (`relation_schema.ts::qualifyRelationType`).
|
|
7
|
+
//
|
|
8
|
+
// THE DISEASE. The kernel's installed-read-manifest accumulator
|
|
9
|
+
// (`wasm_git_holon.rs::insert_read_projection_value`) unions every ACTIVE install's
|
|
10
|
+
// `nomosReadProjection` fragment and — correctly, fail-closed — REFUSES when two
|
|
11
|
+
// installs declare the SAME key with DIVERGENT shapes ("divergent read projection
|
|
12
|
+
// declaration for key AuthProvider"). Two different domain keys that each declare a
|
|
13
|
+
// same-named aggregate therefore WEDGE the workspace's read plane at serve time.
|
|
14
|
+
//
|
|
15
|
+
// THE CURE (the maintainer's rule: "Namespaces are a good idea. Use them."): a
|
|
16
|
+
// NON-GLOBAL aggregate's TYPE NAME is QUALIFIED by its owning domain key at compile —
|
|
17
|
+
// `Entry` in domain `guestbook` becomes `guestbook.Entry` — so two domains that each
|
|
18
|
+
// declare the same bare name NEVER share an accumulator key. The delimiter is `.`
|
|
19
|
+
// (NOT relations' `__`: the id-mint gate splits a minted id's type tag on the first
|
|
20
|
+
// `_`, and the type must also stay colon-free — see READ_PROJECTION_DELIMITER). The engine stamps the SAME qualified name onto every event's
|
|
21
|
+
// `__type` at plan runtime (see `wire_encode.ts`), so the projected row's type and the
|
|
22
|
+
// installed schema key agree by construction; the BARE name rides the aggregate's
|
|
23
|
+
// stable-id `was` lineage so pre-namespacing rows normalize onto the qualified label
|
|
24
|
+
// (the #58 rename machinery — a qualification IS a rename).
|
|
25
|
+
//
|
|
26
|
+
// GLOBAL (un-namespaced) aggregates — the opt-out. Framework aggregates the KERNEL
|
|
27
|
+
// reads by bare name / bare instance-id convention (AuthProvider for in-kernel JWT
|
|
28
|
+
// verification, RelationTuple for the native authz fold, the domain-lifecycle law
|
|
29
|
+
// records, …) MUST keep their bare identity. They are enumerated DELIBERATELY in the
|
|
30
|
+
// explicit allowlist below (never a heuristic); a drift test pins the list to the
|
|
31
|
+
// real framework exports. A dynamically-named framework aggregate (a factory product,
|
|
32
|
+
// e.g. `platformAdmin()`'s founder aggregate) opts out via `aggregate(id, fields,
|
|
33
|
+
// { global: true })` instead — the flag rides the handle into BOTH the compiler and
|
|
34
|
+
// the sealed engine, so the two classifications can never disagree.
|
|
35
|
+
//
|
|
36
|
+
// ERA RULE (the law is the era): qualification happens at COMPILE — already-deployed
|
|
37
|
+
// packages are byte-identical untouched, and a qualified key is just a NEW name to the
|
|
38
|
+
// kernel accumulator (no kernel change). The `nomosReadProjectionNamespace` era key is
|
|
39
|
+
// emitted IFF a layer qualified ≥1 aggregate — the marker that this bundle's read keys
|
|
40
|
+
// are domain-qualified (mirrors `nomosRelationNamespace`).
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The read-projection (aggregate-type) qualifier delimiter. NOT the relation namespacing's
|
|
44
|
+
* `__`, deliberately: an aggregate type is also the kernel id-mint TYPE TAG
|
|
45
|
+
* (`id-mint/src/lib.rs::type_tag_of` splits a minted id on the FIRST `_`), so an
|
|
46
|
+
* underscore-bearing type can never mint — `guestbook__Entry_uuid` parses as tag
|
|
47
|
+
* `guestbook` and refuses at the id-mint gate. And it must stay colon-free (`ObjectRef::
|
|
48
|
+
* parse` splits addresses on the first `:`). `.` satisfies both parsers. The ONE surface
|
|
49
|
+
* where `.` is illegal — a real-USD PRIM NAME (identifiers only) — is already covered by the
|
|
50
|
+
* `.usda` text lane's `primName()` sanitizer (`usd_layers.ts`), which round-trips the raw id
|
|
51
|
+
* via `nomos:id`; the TYPE STRING everywhere (prim `type` attr, directive `target`,
|
|
52
|
+
* read-projection keys, the runtime `__type` stamp, stable-id labels, the mint tag) is dotted.
|
|
53
|
+
*/
|
|
54
|
+
export const READ_PROJECTION_DELIMITER = ".";
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* THE EXPLICIT GLOBAL ALLOWLIST — every STATICALLY-NAMED framework aggregate. These
|
|
59
|
+
* stay BARE on every surface (read projection, prims, stable-ids, runtime `__type`)
|
|
60
|
+
* because the kernel and the framework machinery address them by bare name /
|
|
61
|
+
* bare-name-derived instance ids (`auth-provider:platform`, the relation fold, the
|
|
62
|
+
* policy/lifecycle law records, the signer spine, the key-fanout gate, …).
|
|
63
|
+
*
|
|
64
|
+
* DELIBERATE ENUMERATION, not a heuristic: `read_projection_namespace.test.ts` pins
|
|
65
|
+
* this set to the real `FRAMEWORK_AGGREGATE_IDS` registry so it can never drift.
|
|
66
|
+
* (A tenant aggregate that merely REUSES one of these names also stays bare — that
|
|
67
|
+
* matches the kernel-couples-by-name reality and is exactly the shared-key case the
|
|
68
|
+
* compile-time divergence guard `read_projection_composition.ts` polices.)
|
|
69
|
+
*/
|
|
70
|
+
export const GLOBAL_READ_PROJECTION_AGGREGATE_IDS: ReadonlySet<string> = new Set([
|
|
71
|
+
// disclosure
|
|
72
|
+
"Disclosure",
|
|
73
|
+
// domain lifecycle (the law records themselves)
|
|
74
|
+
"PolicyBundle",
|
|
75
|
+
"DomainInstallation",
|
|
76
|
+
// encryption identity + scope (the operator-blind key machinery)
|
|
77
|
+
"EncKey",
|
|
78
|
+
"KeyScope",
|
|
79
|
+
"WrappedKey",
|
|
80
|
+
// genesis / births
|
|
81
|
+
"GenesisWarrant",
|
|
82
|
+
// metered birth (slice A) — NomosBirthRecord is kernel-read by the birth-record
|
|
83
|
+
// gate (the `nomos-birth-record:` prefix); the policy/grant singletons ride with it
|
|
84
|
+
"NomosBirthRecord",
|
|
85
|
+
"BirthPolicy",
|
|
86
|
+
"BirthGrant",
|
|
87
|
+
// home holons
|
|
88
|
+
"HomeOwner",
|
|
89
|
+
"SharedWithMe",
|
|
90
|
+
"BirthAttestation",
|
|
91
|
+
// identity
|
|
92
|
+
"Actor",
|
|
93
|
+
"User",
|
|
94
|
+
"DeviceKey",
|
|
95
|
+
"Organization",
|
|
96
|
+
"PublicUser",
|
|
97
|
+
// native relations (the one authz spine)
|
|
98
|
+
"RelationTuple",
|
|
99
|
+
// repair
|
|
100
|
+
"RepairRecord",
|
|
101
|
+
// signer spine
|
|
102
|
+
"SignerEpoch",
|
|
103
|
+
"SignerDelegation",
|
|
104
|
+
// governance / workspaces
|
|
105
|
+
"WorkspaceSelf",
|
|
106
|
+
"CloudWorkspace",
|
|
107
|
+
"CloudPolicy",
|
|
108
|
+
"QuotaGrant",
|
|
109
|
+
"PlatformGrant",
|
|
110
|
+
"CapabilityBinding",
|
|
111
|
+
"AuthProvider",
|
|
112
|
+
"HostnameClaim",
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
/** The minimal handle shape the classification reads (compiler AND engine — one function). */
|
|
116
|
+
export interface NamespaceClassifiable {
|
|
117
|
+
readonly id: string;
|
|
118
|
+
/** The `aggregate(id, fields, { global: true })` opt-out — kernel-coupled dynamic framework aggregates. */
|
|
119
|
+
readonly global?: boolean;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* TRUE iff this aggregate's type name is NAMESPACED (qualified by its owning domain).
|
|
124
|
+
* FALSE for the explicit framework allowlist and for `{ global: true }` handles.
|
|
125
|
+
* ONE classification, consumed by the compiler (USD lowering) and the sealed engine
|
|
126
|
+
* (the runtime `__type` stamp) — they can never disagree.
|
|
127
|
+
*/
|
|
128
|
+
export function isNamespacedAggregate(agg: NamespaceClassifiable): boolean {
|
|
129
|
+
return agg.global !== true && !GLOBAL_READ_PROJECTION_AGGREGATE_IDS.has(agg.id);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Qualify a bare aggregate type by its owning domain key: `("guestbook","Entry") → "guestbook.Entry"`.
|
|
134
|
+
* The domain-key half is MINT-TAG-SAFE-encoded (`_` → `-`): the qualified type is also the kernel
|
|
135
|
+
* id-mint TYPE TAG, and `type_tag_of` splits a minted id on the FIRST `_` — so NO underscore may
|
|
136
|
+
* appear anywhere in the tag (`co2_platform.X` would parse as tag `co2`). Deterministic; the era
|
|
137
|
+
* key witnesses the encoded form.
|
|
138
|
+
*/
|
|
139
|
+
export function qualifyReadProjectionType(domainKey: string, type: string): string {
|
|
140
|
+
return `${domainKey.replace(/_/g, "-")}${READ_PROJECTION_DELIMITER}${type}`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* The NAMESPACED (non-global) aggregate ids a module declares — the set whose type
|
|
145
|
+
* names qualify. Derived from the live handles (the `global` flag rides the handle),
|
|
146
|
+
* shared by `usd.ts` (compile) and `engine_entry.ts` (the dispatch context).
|
|
147
|
+
*/
|
|
148
|
+
export function namespacedAggregateIds(aggregates: readonly NamespaceClassifiable[]): Set<string> {
|
|
149
|
+
const out = new Set<string>();
|
|
150
|
+
for (const a of aggregates) if (isNamespacedAggregate(a)) out.add(a.id);
|
|
151
|
+
return out;
|
|
152
|
+
}
|