@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
|
@@ -84,6 +84,8 @@ import {
|
|
|
84
84
|
type Mod,
|
|
85
85
|
} from "./build_package.js";
|
|
86
86
|
import { synthesizePreconditionInvariants } from "./precondition.js";
|
|
87
|
+
import { BIRTH_METER_QUERIES, synthesizeBirthQuotaInvariants } from "./birth_meter.js";
|
|
88
|
+
import { withBirthMeterExports } from "./birth_meter_ensure.js";
|
|
87
89
|
import { collectEngineRouting } from "./engine_entry.js";
|
|
88
90
|
import {
|
|
89
91
|
discover,
|
|
@@ -422,8 +424,25 @@ function emitDart(
|
|
|
422
424
|
const frameworkAggIds: ReadonlySet<string> = new Set(frameworkAggById.keys());
|
|
423
425
|
const frameworkDirIds: ReadonlySet<string> = new Set(frameworkDirById.keys());
|
|
424
426
|
const frameworkQueryIds: ReadonlySet<string> = new Set(frameworkQueryById.keys());
|
|
427
|
+
// VALUE OBJECTS shared by ≥2 domains: their generated Dart class emits ONCE in the shared
|
|
428
|
+
// `nomos_framework.dart` (the same collision rule as framework symbols — a barrel re-exporting
|
|
429
|
+
// two domains must not define one VO class twice). A single-domain VO stays in its domain file.
|
|
430
|
+
const voDomainsByName = new Map<string, number>();
|
|
431
|
+
const voDescriptorByName = new Map<string, NonNullable<DomainModule["valueObjects"]>[number]>();
|
|
432
|
+
for (const mod of modules) {
|
|
433
|
+
for (const d of mod.valueObjects ?? []) {
|
|
434
|
+
voDomainsByName.set(d.name, (voDomainsByName.get(d.name) ?? 0) + 1);
|
|
435
|
+
voDescriptorByName.set(d.name, d);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
const sharedVoNames: ReadonlySet<string> = new Set(
|
|
439
|
+
[...voDomainsByName.entries()].filter(([, n]) => n > 1).map(([name]) => name),
|
|
440
|
+
);
|
|
441
|
+
const sharedVos = [...sharedVoNames]
|
|
442
|
+
.sort()
|
|
443
|
+
.map((name) => voDescriptorByName.get(name)!);
|
|
425
444
|
const hasFrameworkSymbols =
|
|
426
|
-
frameworkAggs.length > 0 || frameworkDirs.length > 0 || frameworkQueries.length > 0;
|
|
445
|
+
frameworkAggs.length > 0 || frameworkDirs.length > 0 || frameworkQueries.length > 0 || sharedVos.length > 0;
|
|
427
446
|
const FRAMEWORK_FILE = "nomos_framework";
|
|
428
447
|
const frameworkImportUri = hasFrameworkSymbols ? `${FRAMEWORK_FILE}.dart` : undefined;
|
|
429
448
|
|
|
@@ -455,6 +474,8 @@ function emitDart(
|
|
|
455
474
|
directives: frameworkDirs,
|
|
456
475
|
readModelAggregates: frameworkAggs.map((a) => a.id),
|
|
457
476
|
...(frameworkQueries.length > 0 ? { queries: frameworkQueries } : {}),
|
|
477
|
+
// Package-SHARED value objects (used by ≥2 domains) — their class emits here, once.
|
|
478
|
+
...(sharedVos.length > 0 ? { valueObjects: sharedVos } : {}),
|
|
458
479
|
};
|
|
459
480
|
writeGenerated(FRAMEWORK_FILE, generateDartDomain(frameworkMod));
|
|
460
481
|
}
|
|
@@ -480,12 +501,17 @@ function emitDart(
|
|
|
480
501
|
const domainFwQueryIds = new Set(
|
|
481
502
|
(modForDart.queries ?? []).filter((q) => frameworkQueryIds.has(q.id)).map((q) => q.id),
|
|
482
503
|
);
|
|
483
|
-
const
|
|
504
|
+
const domainSharedVoNames = new Set(
|
|
505
|
+
(modForDart.valueObjects ?? []).filter((d) => sharedVoNames.has(d.name)).map((d) => d.name),
|
|
506
|
+
);
|
|
507
|
+
const foldsFramework =
|
|
508
|
+
domainFwAggIds.size > 0 || domainFwDirIds.size > 0 || domainFwQueryIds.size > 0 || domainSharedVoNames.size > 0;
|
|
484
509
|
const domainFrameworkImport = foldsFramework ? frameworkImportUri : undefined;
|
|
485
510
|
const src = generateDartDomain(modForDart, {
|
|
486
511
|
skipAggregateIds: domainFwAggIds,
|
|
487
512
|
skipDirectiveIds: domainFwDirIds,
|
|
488
513
|
skipQueryIds: domainFwQueryIds,
|
|
514
|
+
skipValueObjectNames: domainSharedVoNames,
|
|
489
515
|
...(domainFrameworkImport !== undefined ? { frameworkImportUri: domainFrameworkImport } : {}),
|
|
490
516
|
});
|
|
491
517
|
writeGenerated(mod.name, src);
|
|
@@ -596,6 +622,8 @@ async function main(): Promise<void> {
|
|
|
596
622
|
// `capability()` bundles expand into discoverable entries (the CLI plane's
|
|
597
623
|
// merge point) — queries/sums/sid-minting/manifests all see the pieces.
|
|
598
624
|
merged = expandCapabilityExports(merged);
|
|
625
|
+
// METERED BIRTH: the same injection composeDomainModule/mergeModules apply — one seam, no drift.
|
|
626
|
+
merged = withBirthMeterExports(merged);
|
|
599
627
|
let shardingSums: AnySum[] = [];
|
|
600
628
|
let shardingMins: AnyExtremum[] = [];
|
|
601
629
|
let shardingMaxes: AnyExtremum[] = [];
|
|
@@ -736,6 +764,15 @@ async function main(): Promise<void> {
|
|
|
736
764
|
...discover<QueryDecl>(merged, isQueryDecl),
|
|
737
765
|
...resolveNamed<QueryDecl>(merged, d.queries, "queries"),
|
|
738
766
|
];
|
|
767
|
+
// METERED BIRTH (slice A): a domain declaring ≥1 `.births()` directive gets the meter's declared
|
|
768
|
+
// queries appended (dedup by id) so the openapi self-describe decls + read routing carry them —
|
|
769
|
+
// the same auto-ensure `composeDomainModule` applies to the manifest half.
|
|
770
|
+
if (directivesOf(merged).some((dir) => (dir as { birthsChild?: boolean }).birthsChild === true)) {
|
|
771
|
+
const haveQuery = new Set(queries.map((q) => q.id));
|
|
772
|
+
for (const q of BIRTH_METER_QUERIES) {
|
|
773
|
+
if (!haveQuery.has(q.id)) queries.push(q);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
739
776
|
const counts = [
|
|
740
777
|
...discover<AnyCount>(merged, isCountDecl),
|
|
741
778
|
...resolveNamed<AnyCount>(merged, d.counts, "counts"),
|
|
@@ -763,6 +800,9 @@ async function main(): Promise<void> {
|
|
|
763
800
|
const workspaceInvariantsForDecls = [
|
|
764
801
|
...workspaceInvariants,
|
|
765
802
|
...directivesOf(merged).flatMap((dir) => synthesizePreconditionInvariants(dir)),
|
|
803
|
+
// METERED BIRTH (slice A2): the synthesized birth-quota invariants (also synthesized inside
|
|
804
|
+
// `composeDomainModule`) — recomposed here purely for the openapi self-describe decls.
|
|
805
|
+
...directivesOf(merged).flatMap((dir) => synthesizeBirthQuotaInvariants(dir)),
|
|
766
806
|
];
|
|
767
807
|
// Native Zanzibar relation schema (P1) — auto-discovered by brand, like workspaceInvariants.
|
|
768
808
|
const relationTypes = [
|
package/src/directive.ts
CHANGED
|
@@ -23,6 +23,7 @@ import type { Ports } from "./ctx.js";
|
|
|
23
23
|
import type { CertifiedReadDecl } from "./certified_read.js";
|
|
24
24
|
import type { RelationDecl } from "./relation.js";
|
|
25
25
|
import type { QueryDecl } from "./query.js";
|
|
26
|
+
import { BIRTH_METER_QUERIES } from "./birth_meter.js";
|
|
26
27
|
|
|
27
28
|
export type ReferentialMarker = "creates" | "mutates" | "ensures" | "archives";
|
|
28
29
|
|
|
@@ -685,7 +686,15 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
685
686
|
// BIRTH AS A DECLARED KERNEL PRIMITIVE (G3): mark this directive a birth — its plan's `birth(recipe)`
|
|
686
687
|
// rides the intent, and the kernel (reading the `nomosBirthGate` era key, not domain state) executes
|
|
687
688
|
// the birth after admit. Era-ruled: a non-birthing directive omits the flag and is byte-identical.
|
|
688
|
-
|
|
689
|
+
// METERED BIRTH (slice A): `.births()` ALSO auto-declares the three birth-meter captured reads
|
|
690
|
+
// (`birthRecordsByPrincipal`/`birthGrantByPrincipal`/`birthPolicy` — the #58 lane), so the
|
|
691
|
+
// `nomosReadGate` era key carries them by construction and the meter reads the `.births()` dispatch
|
|
692
|
+
// auto-captures (see `executeDirectiveToIntent`) CAS-verify on every lane. The era is the bundle:
|
|
693
|
+
// recompiling under this DSL is what arms the meter (every pre-era bundle replays byte-green).
|
|
694
|
+
const byId = new Map<string, QueryDecl>((base.declaredQueryReads ?? []).map((q) => [q.id, q]));
|
|
695
|
+
for (const q of BIRTH_METER_QUERIES) byId.set(q.id, q);
|
|
696
|
+
const mergedQueries = [...byId.values()].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
697
|
+
return makeRequirable({ ...base, birthsChild: true, declaredQueryReads: mergedQueries });
|
|
689
698
|
},
|
|
690
699
|
grants(relation: string): RequirableDirective<P> {
|
|
691
700
|
// PROOF-TOOLING PRODUCER MARKER (symmetric to `.requires`): record that this directive establishes
|
package/src/engine_entry.ts
CHANGED
|
@@ -54,8 +54,11 @@
|
|
|
54
54
|
import { executeDirectiveToIntent } from "./wire_encode.js";
|
|
55
55
|
import { expandCapabilityExports } from "./capability_exports.js";
|
|
56
56
|
import { RelationTuple, __setDispatchContext } from "./framework/relations.js";
|
|
57
|
+
import { namespacedAggregateIds, qualifyReadProjectionType, READ_PROJECTION_DELIMITER } from "./read_projection_namespace.js";
|
|
57
58
|
import { isRelationTypeDecl } from "./relation_schema.js";
|
|
58
59
|
import { synthesizePreconditionInvariants } from "./precondition.js";
|
|
60
|
+
import { synthesizeBirthQuotaInvariants } from "./birth_meter.js";
|
|
61
|
+
import { withBirthMeterExports } from "./birth_meter_ensure.js";
|
|
59
62
|
import { generateOpenApiFragment, mergeOpenApiFragments, type OpenApiDoc } from "./codegen_openapi.js";
|
|
60
63
|
import type { QueryDecl } from "./query.js";
|
|
61
64
|
import type { AnyCount } from "./count.js";
|
|
@@ -243,7 +246,10 @@ function mergeModules(mods: readonly DomainModuleExports[]): DomainModuleExports
|
|
|
243
246
|
// `capability()` bundles expand into discoverable entries here — the engine
|
|
244
247
|
// plane's ONE merge point, so the registry sees the task aggregate + quartet
|
|
245
248
|
// without per-piece re-exports (capability_exports.ts).
|
|
246
|
-
|
|
249
|
+
// METERED BIRTH: a `.births()`-declaring domain gains the birth-meter structural exports
|
|
250
|
+
// (the sanctioning `nomosBirthRecordEnsure` + the meter aggregates) at this same seam, so
|
|
251
|
+
// the registry + `describe()` see the exact surface the composed manifest carries.
|
|
252
|
+
return withBirthMeterExports(expandCapabilityExports(merged));
|
|
247
253
|
}
|
|
248
254
|
|
|
249
255
|
/**
|
|
@@ -270,6 +276,13 @@ interface DomainSlice {
|
|
|
270
276
|
* type's relations (governance/signer/read-visibility) are EXCLUDED so their bare objects stay bare.
|
|
271
277
|
*/
|
|
272
278
|
qualifiableRelations: Set<string>;
|
|
279
|
+
/**
|
|
280
|
+
* READ-PROJECTION NAMESPACING classification: the NON-GLOBAL aggregate ids this domain declares —
|
|
281
|
+
* the set whose runtime `__type` stamp qualifies to `<domain>__<type>` (matching the qualified
|
|
282
|
+
* keys the compiler wrote into the read projection / prims / stable-ids). GLOBAL aggregates (the
|
|
283
|
+
* explicit framework allowlist / `{global:true}`) are excluded so their bare identity survives.
|
|
284
|
+
*/
|
|
285
|
+
qualifiableAggregates: Set<string>;
|
|
273
286
|
}
|
|
274
287
|
|
|
275
288
|
/** Build one domain's slice from its merged module exports (the original scans). */
|
|
@@ -376,6 +389,13 @@ function buildDomainSlice(mod: DomainModuleExports): DomainSlice {
|
|
|
376
389
|
for (const directive of dirs.values()) {
|
|
377
390
|
for (const w of synthesizePreconditionInvariants(directive)) wsInvariants.set(w.id, w);
|
|
378
391
|
}
|
|
392
|
+
// METERED BIRTH (slice A2 — mirror of composeDomainModule): synthesize THE BIRTH-QUOTA invariant's
|
|
393
|
+
// executable body for every `.births()` directive, so the SEALED ENGINE registers the same
|
|
394
|
+
// `reads`/`assert` the manifest's `nomosInvariantGate` entry names. Byte-identical for a domain
|
|
395
|
+
// with no birth directive (synthesizes none — the era rule).
|
|
396
|
+
for (const directive of dirs.values()) {
|
|
397
|
+
for (const w of synthesizeBirthQuotaInvariants(directive)) wsInvariants.set(w.id, w);
|
|
398
|
+
}
|
|
379
399
|
|
|
380
400
|
// ── RELATION-NAMESPACING classification — the LOCAL non-global `(type,relation)` set (by shape) ──
|
|
381
401
|
// A bare `writeTuple({object:"<type>:<id>", relation})` auto-qualifies iff `<type>#<relation>` is here.
|
|
@@ -388,7 +408,11 @@ function buildDomainSlice(mod: DomainModuleExports): DomainSlice {
|
|
|
388
408
|
for (const rel of v.relations.keys()) qualifiableRelations.add(`${v.type}#${rel}`);
|
|
389
409
|
}
|
|
390
410
|
|
|
391
|
-
|
|
411
|
+
// ── READ-PROJECTION NAMESPACING classification — the LOCAL non-global aggregate ids (one shared
|
|
412
|
+
// classification with the compiler: `read_projection_namespace.ts`). ──
|
|
413
|
+
const qualifiableAggregates = namespacedAggregateIds([...aggs.values()]);
|
|
414
|
+
|
|
415
|
+
return { registry, deriveds, combineds, invariants, aggInvariants, wsInvariants, qualifiableRelations, qualifiableAggregates };
|
|
392
416
|
}
|
|
393
417
|
|
|
394
418
|
/**
|
|
@@ -413,9 +437,14 @@ export function collectEngineRouting(
|
|
|
413
437
|
};
|
|
414
438
|
for (const [domainName, mods] of Object.entries(domains)) {
|
|
415
439
|
const slice = buildDomainSlice(mergeModules(mods));
|
|
416
|
-
|
|
417
|
-
for
|
|
418
|
-
|
|
440
|
+
// Type-keyed dispatches arrive with the ROW's type — QUALIFIED for a namespaced aggregate (the
|
|
441
|
+
// runtime `__type` stamp), bare for a global one / a pre-namespacing row. Route BOTH keys so
|
|
442
|
+
// either era resolves to the declaring domain (the slice lookup strips the qualifier back off).
|
|
443
|
+
const routeKeys = (type: string): string[] =>
|
|
444
|
+
slice.qualifiableAggregates.has(type) ? [type, qualifyReadProjectionType(domainName, type)] : [type];
|
|
445
|
+
for (const type of slice.deriveds.keys()) for (const k of routeKeys(type)) add(derivedOf, k, domainName);
|
|
446
|
+
for (const type of slice.combineds.keys()) for (const k of routeKeys(type)) add(combinedOf, k, domainName);
|
|
447
|
+
for (const type of slice.aggInvariants.keys()) for (const k of routeKeys(type)) add(aggregateInvariantOf, k, domainName);
|
|
419
448
|
for (const relationId of slice.invariants.keys()) add(relationOf, relationId, domainName);
|
|
420
449
|
for (const wsInvariantId of slice.wsInvariants.keys()) add(workspaceInvariantOf, wsInvariantId, domainName);
|
|
421
450
|
}
|
|
@@ -509,6 +538,24 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
|
|
|
509
538
|
return domainNames;
|
|
510
539
|
}
|
|
511
540
|
|
|
541
|
+
/**
|
|
542
|
+
* Resolve a TYPE-keyed registry entry for a dispatch that may carry a QUALIFIED type name
|
|
543
|
+
* (read-projection namespacing — the runtime `__type` stamp is `<domain>__<type>`): try the
|
|
544
|
+
* presented key, then the bare remainder after this domain's own qualifier. Registries stay
|
|
545
|
+
* keyed by the module's bare handle ids (the source of truth); this is the one un-qualify seam.
|
|
546
|
+
*/
|
|
547
|
+
function typeKeyed<T>(map: Map<string, T> | undefined, domainName: string, type: string): T | undefined {
|
|
548
|
+
if (map === undefined) return undefined;
|
|
549
|
+
const direct = map.get(type);
|
|
550
|
+
if (direct !== undefined) return direct;
|
|
551
|
+
// Strip the domain qualifier at the LAST delimiter — bare type names never contain `.`,
|
|
552
|
+
// and the qualifier's domain half is mint-tag-safe-ENCODED (`_`→`-`), so a raw-domainName
|
|
553
|
+
// prefix compare would miss (`co2_platform` qualifies as `co2-platform.`).
|
|
554
|
+
void domainName;
|
|
555
|
+
const i = type.lastIndexOf(READ_PROJECTION_DELIMITER);
|
|
556
|
+
return i > 0 ? map.get(type.slice(i + READ_PROJECTION_DELIMITER.length)) : undefined;
|
|
557
|
+
}
|
|
558
|
+
|
|
512
559
|
const REPORTS: Record<string, (actor: string) => EngineReport> = config.reports ?? {};
|
|
513
560
|
|
|
514
561
|
// ── the five planReport dispatch branches (wire shapes are the host oracle's contract) ──
|
|
@@ -555,7 +602,7 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
|
|
|
555
602
|
// Later domain wins (the Map.set fold order of the one flat registry).
|
|
556
603
|
let body: AggregateInvariantFn | undefined;
|
|
557
604
|
for (const domainName of domainsFor(routing.aggregateInvariantOf, aggregateType)) {
|
|
558
|
-
const found = sliceOf(domainName)?.aggInvariants
|
|
605
|
+
const found = typeKeyed(sliceOf(domainName)?.aggInvariants, domainName, aggregateType);
|
|
559
606
|
if (found !== undefined) body = found;
|
|
560
607
|
}
|
|
561
608
|
// VACUOUS HOLDS: a type with NO declared invariant trivially holds — `{accept:true}`,
|
|
@@ -680,7 +727,7 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
|
|
|
680
727
|
// Domain declaration order, then module export order — the eager fold order.
|
|
681
728
|
const fields: DerivedDecl[] = [];
|
|
682
729
|
for (const domainName of domainsFor(routing.derivedOf, ofType)) {
|
|
683
|
-
fields.push(...(sliceOf(domainName)?.deriveds
|
|
730
|
+
fields.push(...(typeKeyed(sliceOf(domainName)?.deriveds, domainName, ofType) ?? []));
|
|
684
731
|
}
|
|
685
732
|
const prior = (job.priorState ?? {}) as Record<string, unknown>;
|
|
686
733
|
const out: Record<string, unknown> = {};
|
|
@@ -705,7 +752,7 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
|
|
|
705
752
|
// Domain declaration order, then module export order — the eager fold order.
|
|
706
753
|
const fields: CombinedDecl[] = [];
|
|
707
754
|
for (const domainName of domainsFor(routing.combinedOf, ofType)) {
|
|
708
|
-
fields.push(...(sliceOf(domainName)?.combineds
|
|
755
|
+
fields.push(...(typeKeyed(sliceOf(domainName)?.combineds, domainName, ofType) ?? []));
|
|
709
756
|
}
|
|
710
757
|
const owner = (job.priorState ?? {}) as Record<string, unknown>;
|
|
711
758
|
// Preferred: related rows keyed by combined field id (iteration-order independent);
|
|
@@ -822,7 +869,7 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
|
|
|
822
869
|
// token qualifies explicitly. (`module.domain` === `intent.domain`.) Synchronous plan execution ⇒
|
|
823
870
|
// cleared in the `finally`.
|
|
824
871
|
const slice = sliceOf(domain)!;
|
|
825
|
-
__setDispatchContext({ domain, qualifiable: slice.qualifiableRelations });
|
|
872
|
+
__setDispatchContext({ domain, qualifiable: slice.qualifiableRelations, qualifiableAggregates: slice.qualifiableAggregates });
|
|
826
873
|
let wire: ReturnType<typeof executeDirectiveToIntent>;
|
|
827
874
|
try {
|
|
828
875
|
wire = executeDirectiveToIntent(entry.directive, entry.agg, intent.payload as never, ctx);
|
package/src/fields.ts
CHANGED
|
@@ -72,6 +72,62 @@ export const evidenceRefSchema: z.ZodType<EvidenceRefValue> = z.object({
|
|
|
72
72
|
storageClass: z.enum(["git", "external"]),
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
+
// ── VALUE OBJECTS (composable value objects — `value_object.ts`) ─────────────────────
|
|
76
|
+
// The descriptor types live HERE (dependency-free) so `Field` can carry the metadata
|
|
77
|
+
// without a `fields.ts → value_object.ts` cycle; `value_object.ts` re-exports them.
|
|
78
|
+
|
|
79
|
+
/** The RFC 7946 geometry kinds a `geo.*` value object can declare (Point is the
|
|
80
|
+
* spatial-index-blessed one; see `spatial.ts`). */
|
|
81
|
+
export type GeometryKind = "Point" | "LineString" | "Polygon";
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* One field of a value-object DESCRIPTOR — the canonical, JSON-schema-ish shape that
|
|
85
|
+
* rides the package IR (`nomosValueObjects`) and drives TS/Dart codegen. `optional`
|
|
86
|
+
* is PRESENT only when true (omit-when-false — descriptor bytes are deterministic).
|
|
87
|
+
* `array.items` is a nested descriptor WITHOUT `optional` (an array of optionals is
|
|
88
|
+
* not expressible — the v1 constrained set).
|
|
89
|
+
*/
|
|
90
|
+
export type ValueObjectFieldDescriptor =
|
|
91
|
+
| { readonly type: "string"; readonly optional?: true }
|
|
92
|
+
| { readonly type: "number"; readonly optional?: true }
|
|
93
|
+
| { readonly type: "boolean"; readonly optional?: true }
|
|
94
|
+
| { readonly type: "enum"; readonly values: readonly string[]; readonly optional?: true }
|
|
95
|
+
| { readonly type: "array"; readonly items: ValueObjectFieldDescriptor; readonly optional?: true }
|
|
96
|
+
/** A nested value object, referenced BY NAME (its own descriptor rides alongside). */
|
|
97
|
+
| { readonly type: "object"; readonly ref: string; readonly optional?: true }
|
|
98
|
+
/** A nested GEO value object (RFC 7946 geometry), referenced by name. */
|
|
99
|
+
| { readonly type: "geometry"; readonly ref: string; readonly geometry: GeometryKind; readonly optional?: true };
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The canonical descriptor of ONE named value object. A structural VO carries
|
|
103
|
+
* `fields` (keys SORTED — byte-determinism); a `geo.*` VO carries `geometry`
|
|
104
|
+
* instead (its shape is fixed by RFC 7946, not authored).
|
|
105
|
+
*/
|
|
106
|
+
export interface ValueObjectDescriptor {
|
|
107
|
+
readonly name: string;
|
|
108
|
+
readonly fields?: Readonly<Record<string, ValueObjectFieldDescriptor>>;
|
|
109
|
+
readonly geometry?: GeometryKind;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The value-object metadata a VO-typed `Field` carries (`ValueObject.field()` sets it).
|
|
114
|
+
* FRONTEND/IR-ONLY: it never enters the kernel wire `Schema` (driver-only — see
|
|
115
|
+
* `encodeKernelSchema`) nor the canonical manifest, so a VO field's domainHash is
|
|
116
|
+
* IDENTICAL to a hand-rolled `t.jsonObject()` field. The additive `nomosValueObjects`
|
|
117
|
+
* USD key (built by `composeDomainModule`/`usd.ts` FROM this metadata) is what makes
|
|
118
|
+
* the structure visible to tooling.
|
|
119
|
+
*/
|
|
120
|
+
export interface ValueObjectFieldMeta {
|
|
121
|
+
/** The VO's declared name (the generated TS interface / Dart class name). */
|
|
122
|
+
readonly name: string;
|
|
123
|
+
/** This VO's own canonical descriptor. */
|
|
124
|
+
readonly descriptor: ValueObjectDescriptor;
|
|
125
|
+
/** Every TRANSITIVELY nested VO's descriptor (self excluded), sorted by name. */
|
|
126
|
+
readonly dependencies: readonly ValueObjectDescriptor[];
|
|
127
|
+
/** Present iff this VO is a `geo.*` geometry (the spatial-index pairing check). */
|
|
128
|
+
readonly geometry?: GeometryKind;
|
|
129
|
+
}
|
|
130
|
+
|
|
75
131
|
export interface Field<T = unknown, K extends FieldKind = FieldKind> {
|
|
76
132
|
/** The kind tag. `K` is the LITERAL kind so the query DSL can constrain `.ref`
|
|
77
133
|
* to ONLY `kind:'ref'` fields at compile time (`t.ref` → `Field<string,'ref'>`).
|
|
@@ -113,6 +169,15 @@ export interface Field<T = unknown, K extends FieldKind = FieldKind> {
|
|
|
113
169
|
* `encodeKernelSchema`) — so it changes NO hash (identical to `mapValueKind`/`isOptional`).
|
|
114
170
|
*/
|
|
115
171
|
readonly jsonShape?: "object";
|
|
172
|
+
/**
|
|
173
|
+
* For a `json`-kind field lowered from a NAMED VALUE OBJECT (`ValueObject.field()`):
|
|
174
|
+
* the VO metadata (name + canonical descriptor + nested-VO dependencies). Drives the
|
|
175
|
+
* generated TS interface / Dart class and the additive `nomosValueObjects` USD key.
|
|
176
|
+
* FRONTEND/IR-ONLY — never in the kernel wire `Schema` (driver-only), so the field's
|
|
177
|
+
* canonical-manifest bytes are identical to a plain `t.jsonObject()` (see
|
|
178
|
+
* {@link ValueObjectFieldMeta}). `undefined` for every non-VO field.
|
|
179
|
+
*/
|
|
180
|
+
readonly valueObject?: ValueObjectFieldMeta;
|
|
116
181
|
/**
|
|
117
182
|
* Whether a folded aggregate may LACK this field. A SCALAR field
|
|
118
183
|
* (`string`/`int`/`enum`/`ref`/`json`) that a `.creates` directive does not
|
|
@@ -184,6 +249,7 @@ function makeField<T, K extends FieldKind = FieldKind>(
|
|
|
184
249
|
isOptional?: boolean;
|
|
185
250
|
mapValueKind?: FieldKind;
|
|
186
251
|
jsonShape?: "object";
|
|
252
|
+
valueObject?: ValueObjectFieldMeta;
|
|
187
253
|
encrypted?: true;
|
|
188
254
|
meta?: true;
|
|
189
255
|
facet?: FieldFacet;
|
|
@@ -201,6 +267,7 @@ function makeField<T, K extends FieldKind = FieldKind>(
|
|
|
201
267
|
...(extra.viaField !== undefined ? { viaField: extra.viaField } : {}),
|
|
202
268
|
...(extra.mapValueKind !== undefined ? { mapValueKind: extra.mapValueKind } : {}),
|
|
203
269
|
...(extra.jsonShape !== undefined ? { jsonShape: extra.jsonShape } : {}),
|
|
270
|
+
...(extra.valueObject !== undefined ? { valueObject: extra.valueObject } : {}),
|
|
204
271
|
...(extra.encrypted ? { isEncrypted: true as const } : {}),
|
|
205
272
|
...(extra.meta ? { isMeta: true as const } : {}),
|
|
206
273
|
merge(d: Driver) {
|
|
@@ -401,3 +468,20 @@ function refImpl(
|
|
|
401
468
|
}
|
|
402
469
|
return makeField<string, "ref">("ref", z.string(), Conflict, { refAggregateId: a.id });
|
|
403
470
|
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* INTERNAL (`value_object.ts` — `ValueObject.field()`): construct the JSON-OBJECT value
|
|
474
|
+
* leaf a named value object lowers to. Storage/merge-identical to `t.jsonObject()`
|
|
475
|
+
* (kind `"json"`, `jsonShape:"object"`, driver Lww — the whole VO replaces WHOLESALE;
|
|
476
|
+
* per-subfield merge is what aggregates/maps are for), PLUS the {@link
|
|
477
|
+
* ValueObjectFieldMeta} that names the VO for codegen + the `nomosValueObjects` IR key.
|
|
478
|
+
* The zod is the VO's TYPED object schema, so `set(handle, field, value)` and payload
|
|
479
|
+
* embedding validate the real structure. Not part of `t.*` — reach it via
|
|
480
|
+
* `valueObject(...)`/`geo.*`'s `.field()`.
|
|
481
|
+
*/
|
|
482
|
+
export function valueObjectField<T>(
|
|
483
|
+
zod: z.ZodType<T>,
|
|
484
|
+
valueObject: ValueObjectFieldMeta,
|
|
485
|
+
): Field<T, "json"> {
|
|
486
|
+
return makeField<T, "json">("json", zod, Lww, { jsonShape: "object", valueObject });
|
|
487
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
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
|
|
4
|
+
// QuickJS engine}. If a file isn't this / hosting this / authoring for this / proving this — it's gone.
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* framework/birth_meter — THE BIRTH-ALLOWANCE GOVERNANCE SURFACE (metered birth, slice A2/A3).
|
|
8
|
+
*
|
|
9
|
+
* The meter's STRUCTURE (record + queries + quota invariant + auto-capture) is auto-ensured on every
|
|
10
|
+
* `.births()` law — see `../birth_meter.ts`. THIS module is the part a tenant COMPOSES IN when they
|
|
11
|
+
* want to GOVERN the allowance instead of living on the conservative default:
|
|
12
|
+
*
|
|
13
|
+
* - {@link setBirthPolicy} — set/amend the workspace's default allowance (admin-gated).
|
|
14
|
+
* - {@link grantBirthAllowance} — a per-subject override (admin-gated; latest grant wins).
|
|
15
|
+
* - {@link seedBirthPolicy} — THE GENESIS SEED (A3, ungated like `seedGenesis`): the step a
|
|
16
|
+
* parent's birth recipe threads (`buildGenesisChain`'s `descendantPool`) so the child is BORN
|
|
17
|
+
* BOUNDED — the pool chains to root by induction (root bounds platforms, platforms bound
|
|
18
|
+
* homes, homes bound estates). Control-plane protected exactly like `seedGenesis`: the open
|
|
19
|
+
* session lane structurally refuses control-plane intents, and post-genesis writes on a
|
|
20
|
+
* warranted workspace require signed authorship.
|
|
21
|
+
*
|
|
22
|
+
* V1 BOUNDARY (deliberate, stated in `architecture/metered_birth_and_attested_reads.md`): each node
|
|
23
|
+
* enforces ITS OWN count-quota; cross-level pool CONSERVATION arithmetic (a node may not grant its
|
|
24
|
+
* children more than it holds) is v2.
|
|
25
|
+
*/
|
|
26
|
+
import { z } from "zod";
|
|
27
|
+
import { instance } from "../aggregate.js";
|
|
28
|
+
import { directive } from "../directive.js";
|
|
29
|
+
import { set, type PlannedOp } from "../ops.js";
|
|
30
|
+
import {
|
|
31
|
+
BirthPolicy,
|
|
32
|
+
BirthGrant,
|
|
33
|
+
BIRTH_POLICY_ID,
|
|
34
|
+
birthGrantId,
|
|
35
|
+
} from "../birth_meter.js";
|
|
36
|
+
|
|
37
|
+
// Re-export the whole meter core so `@githolon/dsl` framework consumers get one import site.
|
|
38
|
+
export {
|
|
39
|
+
NomosBirthRecord,
|
|
40
|
+
BirthPolicy,
|
|
41
|
+
BirthGrant,
|
|
42
|
+
BIRTH_POLICY_ID,
|
|
43
|
+
NOMOS_BIRTH_RECORD_PREFIX,
|
|
44
|
+
birthRecordId,
|
|
45
|
+
birthGrantId,
|
|
46
|
+
birthRecordsByPrincipal,
|
|
47
|
+
birthPolicy,
|
|
48
|
+
birthGrantByPrincipal,
|
|
49
|
+
DEFAULT_MAX_BIRTHS_PER_PRINCIPAL,
|
|
50
|
+
birthQuotaVerdict,
|
|
51
|
+
birthQuotaInvariantId,
|
|
52
|
+
} from "../birth_meter.js";
|
|
53
|
+
|
|
54
|
+
/** The BirthPolicy singleton upsert ops (shared by the governed set and the genesis seed). */
|
|
55
|
+
function birthPolicyOps(p: { maxBirthsPerPrincipal: number; setBy: string; setAt: string }): PlannedOp[] {
|
|
56
|
+
const pol = instance(BirthPolicy, BIRTH_POLICY_ID);
|
|
57
|
+
return [
|
|
58
|
+
set(pol, "scope", "workspace"),
|
|
59
|
+
set(pol, "maxBirthsPerPrincipal", p.maxBirthsPerPrincipal),
|
|
60
|
+
set(pol, "setBy", p.setBy),
|
|
61
|
+
set(pol, "setAt", p.setAt),
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* setBirthPolicy — set (or amend) THE WORKSPACE'S BIRTH ALLOWANCE DEFAULT. An Ensure on the
|
|
67
|
+
* law-state singleton (deterministic id — the `setCloudPolicy` shape), gated on the workspace's
|
|
68
|
+
* admin relation (the existing relation-gate idiom).
|
|
69
|
+
*/
|
|
70
|
+
export const setBirthPolicy = directive("setBirthPolicy")
|
|
71
|
+
.ensures(BirthPolicy)
|
|
72
|
+
.payload(
|
|
73
|
+
z.object({
|
|
74
|
+
maxBirthsPerPrincipal: z.number().int().positive(),
|
|
75
|
+
setBy: z.string().min(1),
|
|
76
|
+
/** Caller-stamped (the timestamp doctrine). */
|
|
77
|
+
setAt: z.string().min(1),
|
|
78
|
+
}),
|
|
79
|
+
)
|
|
80
|
+
.plan((p) => birthPolicyOps(p))
|
|
81
|
+
.requires("admin");
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* seedBirthPolicy — THE GENESIS SEED (A3). Identical ops to {@link setBirthPolicy} but UNGATED,
|
|
85
|
+
* because it runs INSIDE a child's genesis recipe (before any admin relation exists) — exactly the
|
|
86
|
+
* `seedGenesis` idiom: a birth-door control-plane write the open session lane structurally refuses,
|
|
87
|
+
* and which post-genesis signed-authorship protects on every warranted workspace.
|
|
88
|
+
*/
|
|
89
|
+
export const seedBirthPolicy = directive("seedBirthPolicy")
|
|
90
|
+
.ensures(BirthPolicy)
|
|
91
|
+
.payload(
|
|
92
|
+
z.object({
|
|
93
|
+
maxBirthsPerPrincipal: z.number().int().positive(),
|
|
94
|
+
setBy: z.string().min(1),
|
|
95
|
+
setAt: z.string().min(1),
|
|
96
|
+
}),
|
|
97
|
+
)
|
|
98
|
+
.plan((p) => birthPolicyOps(p));
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* grantBirthAllowance — a PER-SUBJECT allowance override (the `grantQuota` shape): the LATEST grant
|
|
102
|
+
* (max `grantedAt`) for a subject is the allowance in force, overriding the BirthPolicy default.
|
|
103
|
+
* Admin-gated via the existing relation-gate idiom.
|
|
104
|
+
*/
|
|
105
|
+
export const grantBirthAllowance = directive("grantBirthAllowance")
|
|
106
|
+
.ensures(BirthGrant)
|
|
107
|
+
.payload(
|
|
108
|
+
z.object({
|
|
109
|
+
principal: z.string().min(1),
|
|
110
|
+
maxBirths: z.number().int().positive(),
|
|
111
|
+
grantedBy: z.string().min(1),
|
|
112
|
+
/** epoch-ms — the recency tiebreak (latest grant wins), caller-stamped. */
|
|
113
|
+
grantedAt: z.number().int().nonnegative(),
|
|
114
|
+
}),
|
|
115
|
+
)
|
|
116
|
+
.plan((p) => {
|
|
117
|
+
const g = instance(BirthGrant, birthGrantId(p.principal));
|
|
118
|
+
return [
|
|
119
|
+
set(g, "principal", p.principal),
|
|
120
|
+
set(g, "maxBirths", p.maxBirths),
|
|
121
|
+
set(g, "grantedBy", p.grantedBy),
|
|
122
|
+
set(g, "grantedAt", p.grantedAt),
|
|
123
|
+
];
|
|
124
|
+
})
|
|
125
|
+
.requires("admin");
|
|
@@ -165,7 +165,10 @@ export function impureCapabilityAggregate<
|
|
|
165
165
|
const Id extends string,
|
|
166
166
|
P extends Record<string, Field>,
|
|
167
167
|
>(id: Id, params: P): AggregateHandle<Id, typeof IMPURE_CAPABILITY_ENVELOPE & P> {
|
|
168
|
-
|
|
168
|
+
// GLOBAL (read-projection namespacing opt-out): the capability lane addresses the envelope
|
|
169
|
+
// by its BARE declared id (`nomosCapabilities.aggregateId` → the obligation rows the holon
|
|
170
|
+
// surfaces), so the envelope's type name must not be domain-qualified.
|
|
171
|
+
return aggregate(id, { ...IMPURE_CAPABILITY_ENVELOPE, ...params }, { global: true });
|
|
169
172
|
}
|
|
170
173
|
|
|
171
174
|
/**
|
|
@@ -235,7 +238,7 @@ const deadLetterSchema = z.object({
|
|
|
235
238
|
* handle; the envelope handle just types the envelope subset for `set()`).
|
|
236
239
|
*/
|
|
237
240
|
function envelopeInstance(aggregateId: string, requestId: string) {
|
|
238
|
-
const envelopeHandle = aggregate(aggregateId, IMPURE_CAPABILITY_ENVELOPE);
|
|
241
|
+
const envelopeHandle = aggregate(aggregateId, IMPURE_CAPABILITY_ENVELOPE, { global: true });
|
|
239
242
|
return instance(envelopeHandle, impureCapabilityInstanceId(requestId));
|
|
240
243
|
}
|
|
241
244
|
|
|
@@ -188,7 +188,10 @@ export function seedPlatformAdmin(opts: SeedPlatformAdminOptions): PlatformAdmin
|
|
|
188
188
|
relation: t.string().merge(Lww),
|
|
189
189
|
/** ISO-8601, caller-stamped (determinism — no clock in the plan). */
|
|
190
190
|
seededAt: t.string().merge(Lww),
|
|
191
|
-
|
|
191
|
+
// GLOBAL (read-projection namespacing opt-out): a dynamically-named framework aggregate —
|
|
192
|
+
// platform governance law deploys under differing domain keys (root vs each platform), and the
|
|
193
|
+
// founder singleton's bare identity must stay stable across them.
|
|
194
|
+
}, { global: true });
|
|
192
195
|
|
|
193
196
|
/**
|
|
194
197
|
* GENESIS SEED 1 — stamp the founder singleton at the fixed id. Authored by the platform's birth/genesis
|
|
@@ -48,6 +48,7 @@ import { set, setEntry, withMarker, type PlannedOp } from "../ops.js";
|
|
|
48
48
|
import { query } from "../query.js";
|
|
49
49
|
import { endorsementOf, type AttributionPrincipal, type EndorsedAuthority } from "../principal_evidence.js";
|
|
50
50
|
import { isRelationObjectToken, qualifyRelationType, type RelationObjectToken } from "../relation_schema.js";
|
|
51
|
+
import { qualifyReadProjectionType } from "../read_projection_namespace.js";
|
|
51
52
|
|
|
52
53
|
// ─────────────────────────────── the dispatch context (relation-namespacing) ───────────────────────────────
|
|
53
54
|
//
|
|
@@ -71,6 +72,12 @@ interface DispatchContext {
|
|
|
71
72
|
readonly domain: string;
|
|
72
73
|
/** `<type>#<relation>` for every LOCAL non-global authored relation of this domain — the qualify set. */
|
|
73
74
|
readonly qualifiable: ReadonlySet<string>;
|
|
75
|
+
/**
|
|
76
|
+
* READ-PROJECTION NAMESPACING: the NON-GLOBAL aggregate ids of the dispatching domain — the set
|
|
77
|
+
* whose event `__type` stamp qualifies to `<domain>__<type>` (matching the compiler's qualified
|
|
78
|
+
* read-projection / prim / stable-id keys). Absent (probe lanes) ⇒ no aggregate qualification.
|
|
79
|
+
*/
|
|
80
|
+
readonly qualifiableAggregates?: ReadonlySet<string>;
|
|
74
81
|
}
|
|
75
82
|
|
|
76
83
|
const EMPTY_QUALIFIABLE: ReadonlySet<string> = new Set<string>();
|
|
@@ -90,6 +97,18 @@ export function __setDispatchDomain(domain: string | undefined): void {
|
|
|
90
97
|
_ctx = domain === undefined ? undefined : { domain, qualifiable: EMPTY_QUALIFIABLE };
|
|
91
98
|
}
|
|
92
99
|
|
|
100
|
+
/**
|
|
101
|
+
* READ-PROJECTION NAMESPACING (the aggregate-type mirror of `resolveTupleObject`): qualify an event's
|
|
102
|
+
* aggregate TYPE by the dispatching domain IFF the domain declares it as a NON-GLOBAL aggregate — so the
|
|
103
|
+
* `__type` a plan's ops stamp equals the qualified schema key the compiler wrote (`usd.ts`). A global /
|
|
104
|
+
* foreign / unknown type, or a dispatch outside a plan (codegen probes), passes through BARE. Called by
|
|
105
|
+
* `wire_encode.ts::executeDirectiveToIntent` at the one event-emission point.
|
|
106
|
+
*/
|
|
107
|
+
export function __qualifyAggregateType(type: string): string {
|
|
108
|
+
if (_ctx === undefined || _ctx.qualifiableAggregates === undefined) return type;
|
|
109
|
+
return _ctx.qualifiableAggregates.has(type) ? qualifyReadProjectionType(_ctx.domain, type) : type;
|
|
110
|
+
}
|
|
111
|
+
|
|
93
112
|
/** Resolve a `writeTuple`/`removeTuple` object for a given relation (token → qualify; string → classify). */
|
|
94
113
|
function resolveTupleObject(object: string | RelationObjectToken, relation: string): string {
|
|
95
114
|
// A TOKEN is explicit sugar — always qualify its type by the dispatching domain.
|