@githolon/dsl 0.105.15 → 0.105.17

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/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.64.32
2
+
3
+ - Prove a restored generated-application device is enrolled before exposing business APIs, including recovery from an interrupted bootstrap journal.
4
+
1
5
  ## 0.64.31
2
6
 
3
7
  - Restage the validated intent-offer byte gate and deterministic signer-posture reader into the immutable Dart runner.
package/dart/pubspec.yaml CHANGED
@@ -7,7 +7,7 @@ repository: https://github.com/Captain-App/nomos2
7
7
  description: >-
8
8
  Nomos 2 Dart client — generated domain values, intent payloads and application
9
9
  lifecycle types over the typed Nomos kernel protocol and USDA custody model.
10
- version: 0.64.31
10
+ version: 0.64.32
11
11
 
12
12
  environment:
13
13
  sdk: ^3.5.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@githolon/dsl",
3
- "version": "0.105.15",
3
+ "version": "0.105.17",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Nomos 2 domain-authoring DSL: aggregates + directives in TS, executed and encoded to the Rust kernel's wire shapes.",
@@ -13884,7 +13884,6 @@ async function nomosInternalOpenGeneratedApplication(options, bind, internals =
13884
13884
  let authorityHolon = await measureOpen("open private application custody", () => mountAuthority(secret, authToken));
13885
13885
  const storedJournal = await measureOpen("load application bootstrap state", () => store.loadText(journalState));
13886
13886
  const completed = storedJournal === "complete";
13887
- const hadDurableSecret = secret !== null;
13888
13887
  let publicKey;
13889
13888
  if (secret === null) {
13890
13889
  const device = await measureOpen("create application device identity", () => authorityHolon.authorKeygen());
@@ -13905,22 +13904,44 @@ async function nomosInternalOpenGeneratedApplication(options, bind, internals =
13905
13904
  throw applicationError("Sign in changed authority while preparing this application device.", "nomos.authority.changed_during_open");
13906
13905
  }
13907
13906
  authToken = deviceToken;
13907
+ const birthOrRepeat = async () => {
13908
+ const birth = await measureOpen("establish private application custody", () => authorityHolon.nomosInternalBirthReceivingGroup({
13909
+ parent: application,
13910
+ principal,
13911
+ subject,
13912
+ device: { secret, public: publicKey },
13913
+ authToken,
13914
+ idToken: authToken,
13915
+ verifiedVia: issuer,
13916
+ bornAt: now().toISOString(),
13917
+ confirm: true
13918
+ }));
13919
+ const verdict = birth.verify;
13920
+ if (verdict !== void 0 && verdict.valid !== true) throw new Error(`the born application failed replay verification: ${String(verdict.error ?? "unknown")}`);
13921
+ };
13922
+ const ensureSignerEnrollment = async () => {
13923
+ const enroll = () => authorityHolon.ensureParentSignerEnrollment({
13924
+ // The write gate that this device will use is the receiving-group workspace itself.
13925
+ // The application root authorises/births that home; it is not a substitute signer index.
13926
+ parent: receivingGroup,
13927
+ // The author gate speaks canonical Nomos principals. A provider subject is deliberately
13928
+ // opaque and may contain its own namespace separators; never enrol that raw value as the actor.
13929
+ principal: subject,
13930
+ publicKey,
13931
+ authorSecret: secret,
13932
+ authToken,
13933
+ idToken: authToken,
13934
+ verifiedVia: issuer
13935
+ });
13936
+ try {
13937
+ await measureOpen("confirm private-custody signer enrolment", enroll);
13938
+ } catch (error) {
13939
+ if (!String(error).includes("receiving-group-additional-device-requires-current-device-or-guardian")) throw error;
13940
+ await birthOrRepeat();
13941
+ await measureOpen("verify recovered private-custody signer enrolment", enroll);
13942
+ }
13943
+ };
13908
13944
  if (!completed) {
13909
- const birthOrRepeat = async () => {
13910
- const birth = await measureOpen("establish private application custody", () => authorityHolon.nomosInternalBirthReceivingGroup({
13911
- parent: application,
13912
- principal,
13913
- subject,
13914
- device: { secret, public: publicKey },
13915
- authToken,
13916
- idToken: authToken,
13917
- verifiedVia: issuer,
13918
- bornAt: now().toISOString(),
13919
- confirm: true
13920
- }));
13921
- const verdict = birth.verify;
13922
- if (verdict !== void 0 && verdict.valid !== true) throw new Error(`the born application failed replay verification: ${String(verdict.error ?? "unknown")}`);
13923
- };
13924
13945
  let homeAvailable = false;
13925
13946
  try {
13926
13947
  homeAvailable = Boolean(await measureOpen("check private application custody", () => authorityHolon.head()));
@@ -13928,28 +13949,6 @@ async function nomosInternalOpenGeneratedApplication(options, bind, internals =
13928
13949
  }
13929
13950
  if (!homeAvailable) {
13930
13951
  await birthOrRepeat();
13931
- } else if (!hadDurableSecret) {
13932
- try {
13933
- await measureOpen("enrol application device in its private custody", () => authorityHolon.ensureParentSignerEnrollment({
13934
- // The write gate that this device will use is the receiving-group workspace itself.
13935
- // The application root authorises/births that home; it is not a substitute signer index.
13936
- parent: receivingGroup,
13937
- // The author gate speaks canonical Nomos principals. A provider subject is deliberately
13938
- // opaque and may contain its own namespace separators; never enrol that raw value as the actor.
13939
- principal: subject,
13940
- publicKey,
13941
- authorSecret: secret,
13942
- authToken,
13943
- idToken: authToken,
13944
- verifiedVia: issuer
13945
- }));
13946
- } catch (error) {
13947
- if (String(error).includes("receiving-group-additional-device-requires-current-device-or-guardian")) {
13948
- await birthOrRepeat();
13949
- } else {
13950
- throw error;
13951
- }
13952
- }
13953
13952
  }
13954
13953
  await measureOpen("save application bootstrap progress", () => store.saveText(journalState, "home-available"));
13955
13954
  authorityHolon = await measureOpen("rebind durable application authority", () => mountAuthority(secret, authToken));
@@ -13962,23 +13961,11 @@ async function nomosInternalOpenGeneratedApplication(options, bind, internals =
13962
13961
  const verified = await measureOpen("verify new application custody", () => authorityHolon.verifyChain());
13963
13962
  if (verified.valid !== true) throw new Error(`the private authority context failed replay verification: ${String(verified.error ?? "unknown")}`);
13964
13963
  await measureOpen("save application bootstrap progress", () => store.saveText(journalState, "verified"));
13965
- try {
13966
- await measureOpen("confirm private-custody signer enrolment", () => authorityHolon.ensureParentSignerEnrollment({
13967
- parent: receivingGroup,
13968
- principal: subject,
13969
- authorSecret: secret,
13970
- authToken,
13971
- idToken: authToken,
13972
- verifiedVia: issuer
13973
- }));
13974
- } catch (error) {
13975
- if (/revoked/i.test(String(error))) throw error;
13976
- }
13977
- await measureOpen("complete application bootstrap journal", async () => {
13964
+ await measureOpen("save verified application bootstrap journal", async () => {
13978
13965
  await store.saveText(journalState, "complete");
13979
- await store.remove(journalState);
13980
13966
  });
13981
13967
  }
13968
+ await ensureSignerEnrollment();
13982
13969
  holon = authorityHolon;
13983
13970
  const authorityVerified = await measureOpen("verify application custody", () => holon.verifyChain());
13984
13971
  if (authorityVerified.valid !== true) throw new Error(`the private authority context failed replay verification: ${String(authorityVerified.error ?? "unknown")}`);
package/src/codegen_ts.ts CHANGED
@@ -1598,7 +1598,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
1598
1598
  // holon; `resolve`/`watch` use the application's captured shared-plane navigator.
1599
1599
  // The reader is captured by the generated application. Tenant code receives navigation, never a reader.
1600
1600
  `interface _NomosForeignWorkspace { queryById(id: string): Promise<unknown>; watchById?(id: string, cb: (rows: unknown) => void, opts?: { intervalMs?: number }): () => void }`,
1601
- `interface NomosForeignReader { __nomosOpenChild?(workspace: string): Promise<_NomosForeignWorkspace> }`,
1601
+ `interface NomosForeignReader { readonly workspace?: string; __nomosOpenChild?(workspace: string): Promise<_NomosForeignWorkspace> }`,
1602
1602
  `async function _openForeign(nomos: NomosForeignReader, workspace: string): Promise<_NomosForeignWorkspace> {`,
1603
1603
  ` if (nomos.__nomosOpenChild === undefined) throw new Error("Nomos cannot follow this business reference on the installed runtime");`,
1604
1604
  ` return nomos.__nomosOpenChild(workspace);`,
@@ -2329,8 +2329,16 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2329
2329
  }
2330
2330
 
2331
2331
  const aggByType = new Map<string, AggregateHandle>();
2332
+ const runtimeTypeByAggregate = new Map<string, string>();
2332
2333
  for (const mod of modules) {
2333
- for (const agg of mod.aggregates as AggregateHandle[]) aggByType.set(agg.id, agg);
2334
+ const domain = mod.domain ?? mod.name;
2335
+ for (const agg of mod.aggregates as AggregateHandle[]) {
2336
+ aggByType.set(agg.id, agg);
2337
+ runtimeTypeByAggregate.set(
2338
+ agg.id,
2339
+ isNamespacedAggregate(agg) ? qualifyReadProjectionType(domain, agg.id) : agg.id,
2340
+ );
2341
+ }
2334
2342
  }
2335
2343
 
2336
2344
  const anyAutoStamp = modules.some((mod) =>
@@ -2533,6 +2541,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2533
2541
 
2534
2542
  out.push(`// ── read models (projection \`data\` shapes; partial-fold optional, EXCEPT .fromBirth()`, `// fields — their presence is gate law, so the type is non-optional) ──`, ``);
2535
2543
  const foreignRefFields: { agg: string; field: string; workspaceField: string; target: string }[] = [];
2544
+ const directRefFields: { agg: string; field: string; target: string; workspace?: string }[] = [];
2536
2545
  const atomicRefFields: { agg: string; field: string }[] = [];
2537
2546
  const versionedRefFields: { agg: string; field: string; currentField: string }[] = [];
2538
2547
  // wrap a read accessor return expression through the foreignRef row-hydrator (identity + byte-identical
@@ -2540,9 +2549,9 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2540
2549
  // Every read accessor's rows get their canonical `.ref` stamped from `holon.workspace` (additive); a
2541
2550
  // foreignRef-declaring package ALSO hydrates its foreignRef fields into typed locators first.
2542
2551
  const wrapP = (expr: string): string =>
2543
- `_businessRecordsP(_stampRefsP(holon.workspace, ${foreignRefFields.length + atomicRefFields.length + versionedRefFields.length > 0 ? `_hydrateP(holon, ${expr})` : expr}))`;
2552
+ `_businessRecordsP(_stampRefsP(holon.workspace, ${foreignRefFields.length + directRefFields.length + atomicRefFields.length + versionedRefFields.length > 0 ? `_hydrateP(holon, ${expr})` : expr}))`;
2544
2553
  const wrapRows = (expr: string): string =>
2545
- `_businessRecords(_stampRefs(holon.workspace, ${foreignRefFields.length + atomicRefFields.length + versionedRefFields.length > 0 ? `_hydrateRows(holon, ${expr})` : expr}))`;
2554
+ `_businessRecords(_stampRefs(holon.workspace, ${foreignRefFields.length + directRefFields.length + atomicRefFields.length + versionedRefFields.length > 0 ? `_hydrateRows(holon, ${expr})` : expr}))`;
2546
2555
  // ATTRIBUTED READS (architecture/field_provenance.md): the shared provenance shape +
2547
2556
  // per-aggregate accessors — emitted ONLY when the package declares ≥1 `.attributed()`
2548
2557
  // field, so a provenance-free package's client is byte-identical (the era rule).
@@ -2571,6 +2580,19 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2571
2580
  out.push(` ${tsKey(name)}${required ? "" : "?"}: ${tsTypeOfField(field as Field)};`);
2572
2581
  const fr = (field as { foreignRef?: { workspaceField: string; targetAggregateId: string } }).foreignRef;
2573
2582
  if (fr !== undefined) foreignRefFields.push({ agg: typeId, field: name, workspaceField: fr.workspaceField, target: fr.targetAggregateId });
2583
+ if (
2584
+ (field as Field).refAggregateId !== undefined &&
2585
+ (field as Field).foreignRef === undefined &&
2586
+ (field as Field).nomosRef === undefined &&
2587
+ (field as Field).versionedForeignRef === undefined
2588
+ ) {
2589
+ directRefFields.push({
2590
+ agg: typeId,
2591
+ field: name,
2592
+ target: runtimeTypeByAggregate.get((field as Field).refAggregateId!) ?? (field as Field).refAggregateId!,
2593
+ ...((field as Field).refWorkspace === undefined ? {} : { workspace: (field as Field).refWorkspace }),
2594
+ });
2595
+ }
2574
2596
  if ((field as Field).nomosRef !== undefined) atomicRefFields.push({ agg: typeId, field: name });
2575
2597
  const vr = (field as Field).versionedForeignRef;
2576
2598
  if (vr !== undefined) versionedRefFields.push({ agg: typeId, field: name, currentField: vr.currentField });
@@ -2614,7 +2636,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2614
2636
  // row into a `ForeignRef<TargetAggregate>` — reading the foreign id (the field) + the foreign workspace id (the
2615
2637
  // declared sibling field). The read model stays honest (the field is the raw stored id string); this gives
2616
2638
  // the TYPED locator with resolve/watch on top, without cloning the other holon.
2617
- if (foreignRefFields.length + atomicRefFields.length + versionedRefFields.length > 0) {
2639
+ if (foreignRefFields.length + directRefFields.length + atomicRefFields.length + versionedRefFields.length > 0) {
2618
2640
  const byAgg = new Map<string, typeof foreignRefFields>();
2619
2641
  for (const f of foreignRefFields) { const l = byAgg.get(f.agg) ?? []; l.push(f); byAgg.set(f.agg, l); }
2620
2642
  out.push(`// ── foreign-ref locators (t.foreignRef): typed cross-workspace edges (resolve/watch on demand) ──`);
@@ -2637,23 +2659,35 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
2637
2659
  // `row.data.<field>.id` is still the raw id. Identity for any aggregate without foreignRefs.
2638
2660
  const hydrateSpecs: Record<string, { field: string; wsField: string; target: string }[]> = {};
2639
2661
  for (const f of foreignRefFields) { (hydrateSpecs[f.agg] ??= []).push({ field: f.field, wsField: f.workspaceField, target: f.target }); }
2662
+ const directSpecs: Record<string, { field: string; target: string; workspace?: string }[]> = {};
2663
+ for (const f of directRefFields) {
2664
+ (directSpecs[f.agg] ??= []).push({
2665
+ field: f.field,
2666
+ target: f.target,
2667
+ ...(f.workspace === undefined ? {} : { workspace: f.workspace }),
2668
+ });
2669
+ }
2640
2670
  const atomicSpecs: Record<string, { field: string }[]> = {};
2641
2671
  for (const f of atomicRefFields) { (atomicSpecs[f.agg] ??= []).push({ field: f.field }); }
2642
2672
  const versionedSpecs: Record<string, { field: string; currentField: string }[]> = {};
2643
2673
  for (const f of versionedRefFields) { (versionedSpecs[f.agg] ??= []).push({ field: f.field, currentField: f.currentField }); }
2644
2674
  out.push(
2645
2675
  `const _FOREIGN_REFS: Record<string, { field: string; wsField: string; target: string }[]> = ${JSON.stringify(hydrateSpecs)};`,
2676
+ `const _DIRECT_REFS: Record<string, { field: string; target: string; workspace?: string }[]> = ${JSON.stringify(directSpecs)};`,
2646
2677
  `const _ATOMIC_REFS: Record<string, { field: string }[]> = ${JSON.stringify(atomicSpecs)};`,
2647
2678
  `const _VERSIONED_REFS: Record<string, { field: string; currentField: string }[]> = ${JSON.stringify(versionedSpecs)};`,
2648
2679
  `function _hydrateRows<T>(nomos: NomosForeignReader, rows: Row<T>[]): Row<T>[] {`,
2649
2680
  ` return rows.map((row) => {`,
2650
2681
  ` const raw = row as Row<T> & { type: string };`,
2651
- ` const specs = _FOREIGN_REFS[raw.type] ?? [];`,
2652
- ` const atomic = _ATOMIC_REFS[raw.type] ?? [];`,
2653
- ` const versioned = _VERSIONED_REFS[raw.type] ?? [];`,
2654
- ` if ((specs.length + atomic.length + versioned.length) === 0 || row.data == null) return row;`,
2682
+ ` const localType = raw.type.slice(raw.type.lastIndexOf(".") + 1);`,
2683
+ ` const specs = _FOREIGN_REFS[raw.type] ?? _FOREIGN_REFS[localType] ?? [];`,
2684
+ ` const direct = _DIRECT_REFS[raw.type] ?? _DIRECT_REFS[localType] ?? [];`,
2685
+ ` const atomic = _ATOMIC_REFS[raw.type] ?? _ATOMIC_REFS[localType] ?? [];`,
2686
+ ` const versioned = _VERSIONED_REFS[raw.type] ?? _VERSIONED_REFS[localType] ?? [];`,
2687
+ ` if ((specs.length + direct.length + atomic.length + versioned.length) === 0 || row.data == null) return row;`,
2655
2688
  ` const data: Record<string, unknown> = { ...(row.data as Record<string, unknown>) };`,
2656
2689
  ` for (const s of specs) { const v = data[s.field]; if (typeof v === "string") data[s.field] = foreignRef(nomos, s.target, v, String(data[s.wsField] ?? "")); }`,
2690
+ ` for (const s of direct) { const v = data[s.field]; if (typeof v === "string") data[s.field] = _nmsRef(s.workspace ?? nomos.workspace ?? "", s.target, v); }`,
2657
2691
  ` for (const s of atomic) { const v = data[s.field] as { workspace: string; type: string; id: string } | undefined; if (v && typeof v === "object") data[s.field] = _nmsRef(v.workspace, v.type, v.id); }`,
2658
2692
  ...(hasVersionedRefs
2659
2693
  ? [` for (const s of versioned) { const v = data[s.field] as { pinned: _NomosRawAddress; series: _NomosRawAddress } | undefined; if (v && typeof v === "object") data[s.field] = _versionedNomosRef(nomos as NomosHolon, v, s.currentField); }`]
@@ -1664,8 +1664,8 @@ const NOMOS_TYPES_DEP = "^0.8.7";
1664
1664
  // 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
1665
1665
  // DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
1666
1666
  // REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
1667
- const NOMOS_CLIENT_DEP = "^0.64.31";
1668
- const NOMOS_FLUTTER_DEP = "^0.63.54";
1667
+ const NOMOS_CLIENT_DEP = "^0.64.32";
1668
+ const NOMOS_FLUTTER_DEP = "^0.63.56";
1669
1669
  // DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in dsl/dart/pubspec.yaml
1670
1670
  // and dsl/dart_types/pubspec.yaml — a Dart release without this bump fails the suite, so tenants never
1671
1671
  // land back in dependency_overrides (the exact co2 report this closes, third occurrence).
@@ -3936,12 +3936,26 @@ async function main(): Promise<void> {
3936
3936
  const domainKey = mod.domain ?? mod.name;
3937
3937
  const locked = priorLock.domains[mod.name] ?? priorLock.domains[domainKey];
3938
3938
  if (locked !== undefined) {
3939
+ const currentImplementationDetailAggregateIds = new Set(
3940
+ mod.aggregates
3941
+ .filter(
3942
+ (aggregate) =>
3943
+ (aggregate as { businessBirthInternal?: unknown }).businessBirthInternal === true,
3944
+ )
3945
+ .map((aggregate) => aggregate.id),
3946
+ );
3939
3947
  // Framework aggregates are shared implementation law, reconciled under @framework rather
3940
- // than owned by this tenant domain. A package may compose a different framework subset as
3941
- // its topology changes; never instruct a tenant to author a retirement for Nomos internals.
3948
+ // than owned by this tenant domain. Domain-scoped implementation aggregates such as
3949
+ // NomosSourceFrontier deliberately retain the tenant domain's law namespace, but are still
3950
+ // hidden from its structural interface. A package may compose a different framework subset
3951
+ // as its topology changes; never instruct a tenant to author a retirement for Nomos internals.
3942
3952
  const localAggregates = Object.fromEntries(
3943
3953
  Object.entries(locked.stableIds.aggregates)
3944
- .filter(([, aggregate]) => !frameworkAggregateSids.has(aggregate.sid)),
3954
+ .filter(
3955
+ ([aggregateId, aggregate]) =>
3956
+ !frameworkAggregateSids.has(aggregate.sid) &&
3957
+ !currentImplementationDetailAggregateIds.has(aggregateId),
3958
+ ),
3945
3959
  );
3946
3960
  priorDomainsForPackage[domainKey] = {
3947
3961
  stableIds: { ...locked.stableIds, aggregates: localAggregates },