@githolon/dsl 0.103.4 → 0.103.6
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 +8 -0
- package/dart/lib/src/runner/node-entry.mjs +333 -117
- package/dart/lib/src/runner/offline-install.mjs +173 -67
- package/dart/lib/src/session_bound.dart +111 -2
- package/dart/lib/src/test_harness.dart +51 -41
- package/dart/lib/src/webview_bridge.dart +714 -60
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/acquire_session_test.dart +62 -5
- package/dart/test/cloud_bound_topology_test.dart +59 -38
- package/dart/test/session_bound_test.dart +25 -10
- package/dart/test/webview_bridge_test.dart +326 -7
- package/dart_types/CHANGELOG.md +5 -0
- package/dart_types/lib/nomos_types.dart +1 -0
- package/dart_types/lib/src/git_object_id.dart +37 -0
- package/dart_types/pubspec.yaml +1 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +347 -113
- package/src/births.ts +16 -1
- package/src/business_birth.ts +55 -34
- package/src/codegen_dart.ts +68 -12
- package/src/codegen_dart_application.ts +114 -19
- package/src/codegen_dart_client.ts +149 -6
- package/src/codegen_proof.ts +16 -9
- package/src/codegen_provider_dart.ts +5 -0
- package/src/codegen_ts.ts +115 -19
- package/src/compile_package_main.ts +159 -43
- package/src/fields.ts +43 -0
- package/src/framework/domain_lifecycle.ts +108 -88
- package/src/framework/genesis.ts +2 -1
- package/src/framework/home.ts +6 -43
- package/src/framework/workspaces.ts +42 -25
- package/src/idof_lint.ts +65 -51
- package/src/index.ts +21 -1
- package/src/intent.ts +11 -1
- package/src/law_graph.ts +81 -11
- package/src/relation_schema.ts +18 -0
- package/src/shape_compat.ts +3 -0
- package/src/stable_ids_for_module.ts +7 -1
- package/src/value_object.ts +40 -0
|
@@ -798,6 +798,9 @@ const DART_SUPPORT_CORE_SRC = [
|
|
|
798
798
|
"driver.dart",
|
|
799
799
|
"offer.dart",
|
|
800
800
|
"subscriptions.dart",
|
|
801
|
+
// Nominal custody/content identities used by generated payloads and read models.
|
|
802
|
+
"sha256_digest.dart",
|
|
803
|
+
"git_object_id.dart",
|
|
801
804
|
// The tri-state slot (NomosSlot<T>) for `nullable`/`clearable` fields (null_semantics.md) — the
|
|
802
805
|
// barrel re-exports it and generated read-model/payload classes reference it, so it must vendor.
|
|
803
806
|
"null_slot.dart",
|
|
@@ -845,13 +848,14 @@ const DART_SUPPORT_SRC = [...DART_SUPPORT_CORE_SRC, ...DART_SUPPORT_RUNTIME_SRC]
|
|
|
845
848
|
/** The local shim replacing `package:nomos_types/nomos_types.dart` inside vendored runtime files. */
|
|
846
849
|
const VENDORED_TYPES_SHIM = "nomos_types.dart";
|
|
847
850
|
const VENDORED_META_DEP = "^1.12.0";
|
|
851
|
+
const VENDORED_DART_SDK_FLOOR = "3.3.0";
|
|
848
852
|
|
|
849
853
|
/**
|
|
850
854
|
* Vendored runtime files carry package:meta's @internal boundary. Keep the
|
|
851
855
|
* containing Dart package honest by declaring that implementation dependency
|
|
852
856
|
* instead of stripping the annotation from generated copies.
|
|
853
857
|
*/
|
|
854
|
-
function
|
|
858
|
+
function ensureVendoredPackageRequirements(dartOut: string): void {
|
|
855
859
|
let dir = path.resolve(dartOut);
|
|
856
860
|
let pubspec: string | undefined;
|
|
857
861
|
for (let hops = 0; hops < 8; hops++) {
|
|
@@ -866,13 +870,37 @@ function ensureVendoredMetaDependency(dartOut: string): void {
|
|
|
866
870
|
}
|
|
867
871
|
if (pubspec === undefined) return;
|
|
868
872
|
const source = readFileSync(pubspec, "utf8");
|
|
869
|
-
|
|
870
|
-
|
|
873
|
+
let next = source;
|
|
874
|
+
const sdkLine = /^(\s+sdk:\s*)([^#\r\n]+)(\s*(?:#.*)?)$/m.exec(next);
|
|
875
|
+
if (sdkLine === null) {
|
|
876
|
+
fail("generated Dart semantic types need an environment.sdk constraint in " + pubspec);
|
|
877
|
+
}
|
|
878
|
+
const declared = sdkLine[2]!.trim().replace(/^['"]|['"]$/g, "");
|
|
879
|
+
const firstVersion = /(\d+)\.(\d+)(?:\.\d+)?/.exec(declared);
|
|
880
|
+
if (firstVersion === null) {
|
|
881
|
+
fail(
|
|
882
|
+
`generated Dart semantic types need SDK >=${VENDORED_DART_SDK_FLOOR}; ` +
|
|
883
|
+
`cannot interpret environment.sdk '${declared}' in ${pubspec}`,
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
const major = Number(firstVersion[1]);
|
|
887
|
+
const minor = Number(firstVersion[2]);
|
|
888
|
+
if (major < 3 || (major === 3 && minor < 3)) {
|
|
889
|
+
next = next.replace(
|
|
890
|
+
sdkLine[0],
|
|
891
|
+
`${sdkLine[1]}\">=${VENDORED_DART_SDK_FLOOR} <4.0.0\"${sdkLine[3] ?? ""}`,
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
if (/^ meta:\s*/m.test(next)) {
|
|
895
|
+
if (next !== source) writeFileSync(pubspec, next, "utf8");
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
if (!/^dependencies:\s*$/m.test(next)) {
|
|
871
899
|
fail("generated Dart vendored support needs a dependencies: block in " + pubspec);
|
|
872
900
|
}
|
|
873
901
|
writeFileSync(
|
|
874
902
|
pubspec,
|
|
875
|
-
|
|
903
|
+
next.replace(
|
|
876
904
|
/^dependencies:\s*$/m,
|
|
877
905
|
"dependencies:\n meta: " + VENDORED_META_DEP,
|
|
878
906
|
),
|
|
@@ -930,7 +958,7 @@ function buildPackageAggregateDomains(
|
|
|
930
958
|
}
|
|
931
959
|
|
|
932
960
|
/**
|
|
933
|
-
* CIRCULAR-DEPENDENCY GUARD (domain_contracts.md). A
|
|
961
|
+
* CIRCULAR-DEPENDENCY GUARD (domain_contracts.md). A typed cross-workspace reference targeting a CONCRETE aggregate makes
|
|
934
962
|
* the referencing domain's generated file import the target domain's file. Two domains that concretely
|
|
935
963
|
* reference each other (A→B and B→A) form an import cycle — the exact anti-pattern domain contracts exist
|
|
936
964
|
* to break. Build the concrete domain→domain edge set and FAIL on any 2-cycle, pointing at contracts as the
|
|
@@ -943,8 +971,11 @@ function guardNoConcreteImportCycle(
|
|
|
943
971
|
const edges = new Map<string, Set<string>>(); // fromDomainFile → { toDomainFile }
|
|
944
972
|
for (const mod of modules) {
|
|
945
973
|
for (const agg of mod.aggregates) {
|
|
946
|
-
for (const f of Object.values(agg.fields as Record<string, {
|
|
947
|
-
|
|
974
|
+
for (const f of Object.values(agg.fields as Record<string, {
|
|
975
|
+
foreignRef?: { targetAggregateId: string; targetIsContract?: boolean };
|
|
976
|
+
nomosRef?: { targetAggregateId: string; targetIsContract?: boolean };
|
|
977
|
+
}>)) {
|
|
978
|
+
const fr = f.foreignRef ?? f.nomosRef;
|
|
948
979
|
if (fr === undefined || fr.targetIsContract === true) continue; // contract edges route via contracts.dart
|
|
949
980
|
const to = aggIdToDartFile.get(fr.targetAggregateId);
|
|
950
981
|
if (to === undefined || to === mod.name) continue;
|
|
@@ -957,9 +988,9 @@ function guardNoConcreteImportCycle(
|
|
|
957
988
|
if (edges.get(to)?.has(from) && from < to) {
|
|
958
989
|
fail(
|
|
959
990
|
`domain contracts: concrete cross-domain import cycle between '${from}' and '${to}' — their ` +
|
|
960
|
-
`generated Dart files would import each other (a
|
|
991
|
+
`generated Dart files would import each other (a typed custody reference in each targets a concrete aggregate ` +
|
|
961
992
|
`owned by the other). Break it with a record contract: declare recordContract(...) for the shared shape, ` +
|
|
962
|
-
`.implements(...) it on the owning aggregate, and point the
|
|
993
|
+
`.implements(...) it on the owning aggregate, and point the reference at the CONTRACT (it resolves ` +
|
|
963
994
|
`through the shared contracts.dart, keeping the domain graph acyclic).`,
|
|
964
995
|
);
|
|
965
996
|
}
|
|
@@ -1291,7 +1322,7 @@ function emitDart(
|
|
|
1291
1322
|
// 1. optional vendored framework support (barrel + src/), copied byte-for-byte except
|
|
1292
1323
|
// the barrel's schema_validation export (see DART_SUPPORT_SRC).
|
|
1293
1324
|
if (supportImport === "vendored") {
|
|
1294
|
-
|
|
1325
|
+
ensureVendoredPackageRequirements(dartOut);
|
|
1295
1326
|
mkdirSync(path.join(genDir, "src"), { recursive: true });
|
|
1296
1327
|
// Core files come from the pure `nomos_types` package, runtime files from `nomos_client`
|
|
1297
1328
|
// (the layered split). Runtime files import the core via `package:nomos_types/...` — the
|
|
@@ -1427,14 +1458,17 @@ function emitDart(
|
|
|
1427
1458
|
// framework import. Only pass the import when this domain actually folds a framework symbol.
|
|
1428
1459
|
const { modForDart, skips, foldsFramework } = domainDedupOpts(mod, shared);
|
|
1429
1460
|
const domainFrameworkImport = foldsFramework ? frameworkImportUri : undefined;
|
|
1430
|
-
// AUTO cross-domain
|
|
1431
|
-
//
|
|
1461
|
+
// AUTO cross-domain typed-reference imports: foreignRef and atomic nomosRef targets in ANOTHER tenant
|
|
1462
|
+
// domain need that domain's generated type so their typed Dart locator can compile.
|
|
1432
1463
|
// A target that is framework-shared resolves via the frameworkImportUri (not in the tenant map), so it is
|
|
1433
1464
|
// skipped here. Merged with any manual `dartImports`; deduped by uri.
|
|
1434
1465
|
const foreignRefImportUris = new Set<string>();
|
|
1435
|
-
for (const agg of (modForDart.aggregates as { fields: Record<string, {
|
|
1466
|
+
for (const agg of (modForDart.aggregates as { fields: Record<string, {
|
|
1467
|
+
foreignRef?: { targetAggregateId: string; targetIsContract?: boolean };
|
|
1468
|
+
nomosRef?: { targetAggregateId: string; targetIsContract?: boolean };
|
|
1469
|
+
}> }[])) {
|
|
1436
1470
|
for (const f of Object.values(agg.fields)) {
|
|
1437
|
-
const fr = f.foreignRef;
|
|
1471
|
+
const fr = f.foreignRef ?? f.nomosRef;
|
|
1438
1472
|
if (fr === undefined) continue;
|
|
1439
1473
|
// A CONTRACT target resolves through the shared `contracts.dart` (the acyclic edge), NEVER the
|
|
1440
1474
|
// concrete target domain — so estate never imports catalogue. (The contract id may EQUAL an
|
|
@@ -1519,6 +1553,9 @@ function emitDart(
|
|
|
1519
1553
|
const sha256DigestExport = [...sourceByFile.values()].some((source) => source.includes("Sha256Digest"))
|
|
1520
1554
|
? ", Sha256Digest"
|
|
1521
1555
|
: "";
|
|
1556
|
+
const gitObjectIdExport = [...sourceByFile.values()].some((source) => source.includes("GitObjectId"))
|
|
1557
|
+
? ", GitObjectId"
|
|
1558
|
+
: "";
|
|
1522
1559
|
const barrelLines = [
|
|
1523
1560
|
"// GENERATED by nomos-compile — the package's public entrypoint. DO NOT EDIT.",
|
|
1524
1561
|
"// Regenerated on every compile; generated implementation lives under src/generated/.",
|
|
@@ -1542,8 +1579,8 @@ function emitDart(
|
|
|
1542
1579
|
// The generated clients import their runtime support directly. Keeping it out of the public barrel is
|
|
1543
1580
|
// the surface firewall: a tenant import receives business types and application APIs, never raw runtime.
|
|
1544
1581
|
supportImport === "vendored"
|
|
1545
|
-
? `export '${genRel}/src/${VENDORED_TYPES_SHIM}' show NomosRef, NomosOptionalRef, VersionedNomosRef, ReferenceFreshness, ReferenceCurrent, ReferenceStale, ReferenceFreshnessUnavailable${sha256DigestExport};`
|
|
1546
|
-
: `export 'package:nomos_types/nomos_types.dart' show NomosRef, NomosOptionalRef, VersionedNomosRef, ReferenceFreshness, ReferenceCurrent, ReferenceStale, ReferenceFreshnessUnavailable${sha256DigestExport};`,
|
|
1582
|
+
? `export '${genRel}/src/${VENDORED_TYPES_SHIM}' show NomosRef, NomosOptionalRef, VersionedNomosRef, ReferenceFreshness, ReferenceCurrent, ReferenceStale, ReferenceFreshnessUnavailable${sha256DigestExport}${gitObjectIdExport};`
|
|
1583
|
+
: `export 'package:nomos_types/nomos_types.dart' show NomosRef, NomosOptionalRef, VersionedNomosRef, ReferenceFreshness, ReferenceCurrent, ReferenceStale, ReferenceFreshnessUnavailable${sha256DigestExport}${gitObjectIdExport};`,
|
|
1547
1584
|
...(contractsImportUri !== undefined
|
|
1548
1585
|
? tenantDartExport(`${genRel}/${CONTRACTS_FILE}.dart`, sourceByFile.get(CONTRACTS_FILE) ?? "")
|
|
1549
1586
|
: []),
|
|
@@ -1605,12 +1642,12 @@ const dartTypesImportFor = (generatedSource: string): string => {
|
|
|
1605
1642
|
* just the floor generated code needs — a lagging pin forces tenants into dependency_overrides. */
|
|
1606
1643
|
// 0.3.0: SurfacedValue<T>/SurfacedObservation/PendingConflictRow — generated Surfaced-field typing +
|
|
1607
1644
|
// the pendingConflicts read rows.
|
|
1608
|
-
const NOMOS_TYPES_DEP = "^0.8.
|
|
1645
|
+
const NOMOS_TYPES_DEP = "^0.8.3";
|
|
1609
1646
|
// 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
|
|
1610
1647
|
// DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
|
|
1611
1648
|
// REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
|
|
1612
|
-
const NOMOS_CLIENT_DEP = "^0.64.
|
|
1613
|
-
const NOMOS_FLUTTER_DEP = "^0.63.
|
|
1649
|
+
const NOMOS_CLIENT_DEP = "^0.64.6";
|
|
1650
|
+
const NOMOS_FLUTTER_DEP = "^0.63.8";
|
|
1614
1651
|
// DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in dsl/dart/pubspec.yaml
|
|
1615
1652
|
// and dsl/dart_types/pubspec.yaml — a Dart release without this bump fails the suite, so tenants never
|
|
1616
1653
|
// land back in dependency_overrides (the exact co2 report this closes, third occurrence).
|
|
@@ -1847,7 +1884,7 @@ function generatedPackageApplication(
|
|
|
1847
1884
|
`import 'package:flutter/widgets.dart';`,
|
|
1848
1885
|
`import 'package:nomos_flutter/src/application_status.dart' show ApplicationSignIn, ApplicationStatus;`,
|
|
1849
1886
|
`import 'package:nomos_client/src/nms_generated_runtime.dart' show NmsInternalUsers, NomosSession, User${hasInvitations ? ", Invitation, Recipient" : ""};`,
|
|
1850
|
-
`import 'package:nomos_flutter/src/generated_application_host.dart' show NomosApplicationStartupState, NmsInternalApplicationHost, NmsInternalApplicationUserBinding${hasInvitations ? ", NmsInternalInvitationPolicy" : ""};`,
|
|
1887
|
+
`import 'package:nomos_flutter/src/generated_application_host.dart' show ApplicationModelChange, NomosApplicationStartupState, NmsInternalApplicationHost, NmsInternalApplicationUserBinding${hasInvitations ? ", NmsInternalInvitationPolicy" : ""};`,
|
|
1851
1888
|
...members.map(({ file, prefix }) =>
|
|
1852
1889
|
`import '${file}.application.dart' as ${prefix};`),
|
|
1853
1890
|
``,
|
|
@@ -1881,10 +1918,14 @@ function generatedPackageApplication(
|
|
|
1881
1918
|
` ${rootMember.prefix}.NomosModel get model => ${rootMember.member}.model;`,
|
|
1882
1919
|
``,
|
|
1883
1920
|
` /// Current durability and convergence across this business application.`,
|
|
1884
|
-
` ApplicationStatus get currentStatus => NmsInternalApplicationHost.
|
|
1921
|
+
` ApplicationStatus get currentStatus => NmsInternalApplicationHost.currentApplicationStatus(_nmsSession);`,
|
|
1885
1922
|
``,
|
|
1886
1923
|
` /// React whenever this application saves, receives work, goes offline or needs attention.`,
|
|
1887
|
-
` Stream<ApplicationStatus> watchStatus() => NmsInternalApplicationHost.
|
|
1924
|
+
` Stream<ApplicationStatus> watchStatus() => NmsInternalApplicationHost.watchApplicationStatus(_nmsSession);`,
|
|
1925
|
+
``,
|
|
1926
|
+
` /// Wait until accepted local work is in shared custody and this application has caught up.`,
|
|
1927
|
+
` Future<void> whenCurrent({Duration timeout = const Duration(minutes: 2)}) =>`,
|
|
1928
|
+
` NmsInternalApplicationHost.whenApplicationCurrent(_nmsSession, timeout: timeout);`,
|
|
1888
1929
|
...(hasInvitations
|
|
1889
1930
|
? [
|
|
1890
1931
|
``,
|
|
@@ -1930,12 +1971,16 @@ function generatedPackageApplication(
|
|
|
1930
1971
|
` required Widget Function(BuildContext context, ${application} application) builder,`,
|
|
1931
1972
|
` Widget Function(BuildContext context)? loadingBuilder,`,
|
|
1932
1973
|
` Widget Function(BuildContext context, Object error, VoidCallback retry)? errorBuilder,`,
|
|
1974
|
+
` ApplicationModelChange? onModelChange,`,
|
|
1933
1975
|
` ValueChanged<NomosApplicationStartupState>? onStartupStateChanged,`,
|
|
1976
|
+
` Duration startupTimeout = const Duration(seconds: 45),`,
|
|
1934
1977
|
` }) => NmsInternalApplicationHost.home<${application}>(`,
|
|
1935
1978
|
` key: key, cloud: cloud, application: application, signIn: signIn,`,
|
|
1936
1979
|
` bind: nomosInternalBind${application}, builder: builder,`,
|
|
1937
1980
|
` loadingBuilder: loadingBuilder, errorBuilder: errorBuilder,`,
|
|
1981
|
+
` onModelChange: onModelChange,`,
|
|
1938
1982
|
` onStartupStateChanged: onStartupStateChanged,`,
|
|
1983
|
+
` startupTimeout: startupTimeout,`,
|
|
1939
1984
|
` );`,
|
|
1940
1985
|
`}`,
|
|
1941
1986
|
``,
|
|
@@ -2059,22 +2104,34 @@ function reconcileGeneratedLawAsset(pkgDir: string, generatedAsset: string): boo
|
|
|
2059
2104
|
const p = path.join(pkgDir, "pubspec.yaml");
|
|
2060
2105
|
if (!existsSync(p)) return false;
|
|
2061
2106
|
const source = readFileSync(p, "utf8");
|
|
2107
|
+
const trailingNewline = source.endsWith("\n");
|
|
2062
2108
|
const lines = source.split(/\r?\n/);
|
|
2109
|
+
if (trailingNewline) lines.pop();
|
|
2063
2110
|
const compilerAsset = /^\s+-\s+lib\/src\/generated\/[^\s]+\.(?:deploy\.json|law\.usdz)\s*$/;
|
|
2064
2111
|
const replacement = ` - ${generatedAsset}`;
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2112
|
+
const nextLines = lines.filter((line) => !compilerAsset.test(line));
|
|
2113
|
+
|
|
2114
|
+
let flutterStart = nextLines.findIndex((line) => /^flutter:\s*(?:#.*)?$/.test(line));
|
|
2115
|
+
if (flutterStart < 0) {
|
|
2116
|
+
if (nextLines.length > 0 && nextLines.at(-1) !== "") nextLines.push("");
|
|
2117
|
+
flutterStart = nextLines.length;
|
|
2118
|
+
nextLines.push("flutter:");
|
|
2119
|
+
}
|
|
2120
|
+
let flutterEnd = flutterStart + 1;
|
|
2121
|
+
while (flutterEnd < nextLines.length) {
|
|
2122
|
+
const line = nextLines[flutterEnd]!;
|
|
2123
|
+
if (line.trim() !== "" && !/^\s/.test(line) && !/^\s*#/.test(line)) break;
|
|
2124
|
+
flutterEnd += 1;
|
|
2125
|
+
}
|
|
2126
|
+
const assetHeader = nextLines.findIndex(
|
|
2127
|
+
(line, index) => index > flutterStart && index < flutterEnd && /^\s{2}assets:\s*(?:#.*)?$/.test(line),
|
|
2128
|
+
);
|
|
2129
|
+
if (assetHeader < 0) {
|
|
2130
|
+
nextLines.splice(flutterEnd, 0, " assets:", replacement);
|
|
2131
|
+
} else {
|
|
2075
2132
|
nextLines.splice(assetHeader + 1, 0, replacement);
|
|
2076
2133
|
}
|
|
2077
|
-
const next = nextLines.join("\n")
|
|
2134
|
+
const next = `${nextLines.join("\n")}${trailingNewline ? "\n" : ""}`;
|
|
2078
2135
|
if (next === source) return false;
|
|
2079
2136
|
writeFileSync(p, next, "utf8");
|
|
2080
2137
|
return true;
|
|
@@ -2196,9 +2253,12 @@ function emitDartPackages(
|
|
|
2196
2253
|
const perDomain = modules.map((mod) => ({ mod, dd: domainDedupOpts(mod, shared) }));
|
|
2197
2254
|
for (const { mod, dd } of perDomain) {
|
|
2198
2255
|
const frImports = new Set<string>();
|
|
2199
|
-
for (const agg of (dd.modForDart.aggregates as { fields: Record<string, {
|
|
2256
|
+
for (const agg of (dd.modForDart.aggregates as { fields: Record<string, {
|
|
2257
|
+
foreignRef?: { targetAggregateId: string; targetIsContract?: boolean };
|
|
2258
|
+
nomosRef?: { targetAggregateId: string; targetIsContract?: boolean };
|
|
2259
|
+
}> }[])) {
|
|
2200
2260
|
for (const f of Object.values(agg.fields)) {
|
|
2201
|
-
const fr = f.foreignRef;
|
|
2261
|
+
const fr = f.foreignRef ?? f.nomosRef;
|
|
2202
2262
|
if (fr === undefined) continue;
|
|
2203
2263
|
// A contract target resolves via contracts.dart (the acyclic edge), not the concrete domain file.
|
|
2204
2264
|
if (fr.targetIsContract === true) {
|
|
@@ -2290,11 +2350,14 @@ function emitDartPackages(
|
|
|
2290
2350
|
const sha256DigestExport = [...typesSources.values()].some((source) => source.includes("Sha256Digest"))
|
|
2291
2351
|
? ", Sha256Digest"
|
|
2292
2352
|
: "";
|
|
2353
|
+
const gitObjectIdExport = [...typesSources.values()].some((source) => source.includes("GitObjectId"))
|
|
2354
|
+
? ", GitObjectId"
|
|
2355
|
+
: "";
|
|
2293
2356
|
writeFileSync(
|
|
2294
2357
|
businessSharedPath,
|
|
2295
2358
|
[
|
|
2296
2359
|
"// GENERATED by nomos-compile — shared business values and activity; no runtime machinery.",
|
|
2297
|
-
`export 'package:nomos_types/nomos_types.dart' show NomosRef, NomosOptionalRef, VersionedNomosRef, ReferenceFreshness, ReferenceCurrent, ReferenceStale, ReferenceFreshnessUnavailable${sha256DigestExport}, Identity, User, Recipient, Invitation, ActivityEvent, ActivityRef, ActivitySeverity, SurfacedObservation, SurfacedValue, SurfacedResolved, SurfacedPending, SurfacedEvolutionRequired;`,
|
|
2360
|
+
`export 'package:nomos_types/nomos_types.dart' show NomosRef, NomosOptionalRef, VersionedNomosRef, ReferenceFreshness, ReferenceCurrent, ReferenceStale, ReferenceFreshnessUnavailable${sha256DigestExport}${gitObjectIdExport}, Identity, User, Recipient, Invitation, ActivityEvent, ActivityRef, ActivitySeverity, SurfacedObservation, SurfacedValue, SurfacedResolved, SurfacedPending, SurfacedEvolutionRequired;`,
|
|
2298
2361
|
...(sharedBusinessTypeNames.length === 0
|
|
2299
2362
|
? []
|
|
2300
2363
|
: [`export '${FRAMEWORK_FILE}.dart' show ${sharedBusinessTypeNames.join(", ")};`]),
|
|
@@ -2358,6 +2421,17 @@ function emitDartPackages(
|
|
|
2358
2421
|
|
|
2359
2422
|
// ── the CLIENT package — effectful runtime, depends on types + nomos_client ──
|
|
2360
2423
|
const clientGen = freshGenDir(packages.client);
|
|
2424
|
+
const embeddedLawName = `${describePackageName}.law.usdz`;
|
|
2425
|
+
const applicationLawPath = lawUsdzPath === undefined
|
|
2426
|
+
? undefined
|
|
2427
|
+
: path.join(clientGen, embeddedLawName);
|
|
2428
|
+
if (applicationLawPath !== undefined) {
|
|
2429
|
+
copyFileSync(lawUsdzPath!, applicationLawPath);
|
|
2430
|
+
written.push(applicationLawPath);
|
|
2431
|
+
}
|
|
2432
|
+
const applicationLawAssetKey = applicationLawPath === undefined
|
|
2433
|
+
? undefined
|
|
2434
|
+
: `packages/${packages.client}/lib/${genRel}/${embeddedLawName}`;
|
|
2361
2435
|
const typesImportUri = `package:${packages.types}/${packages.types}.dart`;
|
|
2362
2436
|
const implementationTypeImports = explicitBarrel
|
|
2363
2437
|
? modules.map((m) => `package:${packages.types}/${genRel}/${m.name}.dart`)
|
|
@@ -2371,6 +2445,9 @@ function emitDartPackages(
|
|
|
2371
2445
|
for (const { mod, dd } of perDomain) {
|
|
2372
2446
|
const makeClient = (imports: readonly string[]) => generateDartBridgeClient(dd.modForDart, {
|
|
2373
2447
|
domainHash,
|
|
2448
|
+
...(applicationLawAssetKey !== undefined
|
|
2449
|
+
? { lawAssetKey: applicationLawAssetKey }
|
|
2450
|
+
: {}),
|
|
2374
2451
|
interface: packageInterface[dd.modForDart.domain ?? dd.modForDart.name],
|
|
2375
2452
|
...(compat !== undefined ? { compat } : {}),
|
|
2376
2453
|
supportImport: "package",
|
|
@@ -2467,6 +2544,7 @@ function emitDartPackages(
|
|
|
2467
2544
|
"library;",
|
|
2468
2545
|
"",
|
|
2469
2546
|
`export 'package:${packages.types}/${genRel}/${businessSharedFile}';`,
|
|
2547
|
+
`export 'package:nomos_flutter/src/generated_application_host.dart' show ApplicationModelDecision, ApplicationModelReview;`,
|
|
2470
2548
|
...tenantTypesExportKeys
|
|
2471
2549
|
.map((file) => {
|
|
2472
2550
|
const names = tenantDartExportNames(typesSources.get(file) ?? "");
|
|
@@ -2514,16 +2592,28 @@ function emitDartPackages(
|
|
|
2514
2592
|
` nomos_flutter: ${NOMOS_FLUTTER_DEP}`,
|
|
2515
2593
|
` ${packages.types}:`,
|
|
2516
2594
|
` path: ../${packages.types} # co-emitted sibling; replace with a hosted constraint when publishing`,
|
|
2595
|
+
...(applicationLawPath === undefined
|
|
2596
|
+
? []
|
|
2597
|
+
: [
|
|
2598
|
+
`flutter:`,
|
|
2599
|
+
` assets:`,
|
|
2600
|
+
` - lib/${genRel}/${embeddedLawName}`,
|
|
2601
|
+
]),
|
|
2517
2602
|
``,
|
|
2518
2603
|
].join("\n"),
|
|
2519
2604
|
{ nomos_client: NOMOS_CLIENT_DEP, nomos_flutter: NOMOS_FLUTTER_DEP },
|
|
2520
2605
|
);
|
|
2606
|
+
if (applicationLawPath !== undefined) {
|
|
2607
|
+
reconcileGeneratedLawAsset(
|
|
2608
|
+
pkgDir(packages.client),
|
|
2609
|
+
`lib/${genRel}/${embeddedLawName}`,
|
|
2610
|
+
);
|
|
2611
|
+
}
|
|
2521
2612
|
|
|
2522
2613
|
// ── the TEST package (optional) — generated business scenarios, test-only dependency ──
|
|
2523
2614
|
if (packages.test !== undefined) {
|
|
2524
2615
|
const testGen = freshGenDir(packages.test);
|
|
2525
2616
|
if (lawUsdzPath === undefined) fail("dart.packages.test requires the compiled canonical law archive");
|
|
2526
|
-
const embeddedLawName = `${describePackageName}.law.usdz`;
|
|
2527
2617
|
const embeddedLawPath = path.join(testGen, embeddedLawName);
|
|
2528
2618
|
copyFileSync(lawUsdzPath, embeddedLawPath);
|
|
2529
2619
|
written.push(embeddedLawPath);
|
|
@@ -2791,6 +2881,12 @@ async function main(): Promise<void> {
|
|
|
2791
2881
|
"Nomos will not compile a birth-capable home with an implicit quota",
|
|
2792
2882
|
);
|
|
2793
2883
|
}
|
|
2884
|
+
if (cfg.application !== undefined && cfg.rootPart === undefined) {
|
|
2885
|
+
fail(
|
|
2886
|
+
"application policy requires rootPart: { name, kind }; " +
|
|
2887
|
+
"Nomos will not silently ignore private application-home law on a loose law fragment",
|
|
2888
|
+
);
|
|
2889
|
+
}
|
|
2794
2890
|
// A born application home may carry several co-resident law namespaces. BirthPolicy is namespaced
|
|
2795
2891
|
// law-state, so genesis must seed every domain that can itself birth; seeding only the private home
|
|
2796
2892
|
// domain would leave (for example) `home/birthEstate` correctly refusing its first offer. Discover the
|
|
@@ -3653,6 +3749,13 @@ async function main(): Promise<void> {
|
|
|
3653
3749
|
nextLock.identity = nextIdentity;
|
|
3654
3750
|
const continuity = reconciled.continuity;
|
|
3655
3751
|
const inferred = proposed.inferred;
|
|
3752
|
+
// Resolve the complete CURRENT stable-id surface exactly once. `continuity`
|
|
3753
|
+
// deliberately starts from the prior release, whereas this value also contains
|
|
3754
|
+
// contracts and explicit evolution routes authored by the candidate. Building
|
|
3755
|
+
// USDA from `continuity` made the first compile update only the lockfile and the
|
|
3756
|
+
// second compile finally publish those semantics into custody.
|
|
3757
|
+
mod.stableIdContinuity = continuity;
|
|
3758
|
+
const emittedStableIds = stableIdsForModule(mod);
|
|
3656
3759
|
// W4 typed compiler graph: reconcile independently-lived intent/read identities,
|
|
3657
3760
|
// link every current target as a kind-safe ref, and refuse unresolved structure
|
|
3658
3761
|
// before any USDA string is rendered. Later W4 stages move emitters/codegen onto
|
|
@@ -3661,7 +3764,7 @@ async function main(): Promise<void> {
|
|
|
3661
3764
|
// independently of domain declaration order. Build custody from that same closed view used by generated
|
|
3662
3765
|
// clients/interfaces; using the pre-closure executable module here published a USDA-only `birth` route.
|
|
3663
3766
|
const graphModule = closedDomainModulesByName.get(mod.name) ?? mod;
|
|
3664
|
-
const discoveredGraph = discoverLawGraph(graphModule,
|
|
3767
|
+
const discoveredGraph = discoverLawGraph(graphModule, emittedStableIds, nextIdentity);
|
|
3665
3768
|
for (const node of discoveredGraph.graph.nodes) {
|
|
3666
3769
|
if (node.kind === "intent" && node.births && node.birthPolicySeed === undefined) {
|
|
3667
3770
|
fail(
|
|
@@ -3674,7 +3777,6 @@ async function main(): Promise<void> {
|
|
|
3674
3777
|
nextIdentity = discoveredGraph.registry;
|
|
3675
3778
|
nextLock.identity = nextIdentity;
|
|
3676
3779
|
typedLawGraphs.set(identityName, discoveredGraph.graph);
|
|
3677
|
-
mod.stableIdContinuity = continuity;
|
|
3678
3780
|
graphModule.stableIdContinuity = continuity;
|
|
3679
3781
|
if (inferred.length > 0) inferredRenames.push({ domain: identityName, renames: inferred });
|
|
3680
3782
|
// ── THE fromBirth EVOLVE GUARD (dart_codegen_parity.md): birth-presence may be declared at a
|
|
@@ -3709,7 +3811,7 @@ async function main(): Promise<void> {
|
|
|
3709
3811
|
// The lockfile records the EMITTED surface (sids + lineage + kinds — the next
|
|
3710
3812
|
// compile's pair rule reads the kinds), not the bare continuity.
|
|
3711
3813
|
nextLock.domains[identityName] = {
|
|
3712
|
-
stableIds:
|
|
3814
|
+
stableIds: emittedStableIds,
|
|
3713
3815
|
schemas: Object.fromEntries(currentAggs.map((a) => [a.id, a.schema])),
|
|
3714
3816
|
};
|
|
3715
3817
|
}
|
|
@@ -5459,6 +5561,13 @@ async function main(): Promise<void> {
|
|
|
5459
5561
|
writeFileSync(file, text, "utf8");
|
|
5460
5562
|
};
|
|
5461
5563
|
for (const pkg of pkgDirs) {
|
|
5564
|
+
// Layered Dart output is generated-owned, including the compiler's temporary source-dogfood
|
|
5565
|
+
// override. A package may have been generated previously by a source checkout and then rebuilt
|
|
5566
|
+
// by the published compiler. In that transition, doing nothing here leaves the old absolute
|
|
5567
|
+
// checkout paths live and silently defeats the hosted-package release boundary. Clear the
|
|
5568
|
+
// compiler-owned file on every compile, then recreate it only for an actual source checkout.
|
|
5569
|
+
const overrideFile = path.join(pkg, "pubspec_overrides.yaml");
|
|
5570
|
+
rmSync(overrideFile, { force: true });
|
|
5462
5571
|
if (localDart) {
|
|
5463
5572
|
const spec = readFileSync(path.join(pkg, "pubspec.yaml"), "utf8");
|
|
5464
5573
|
const lines = ["dependency_overrides:"];
|
|
@@ -5479,7 +5588,7 @@ async function main(): Promise<void> {
|
|
|
5479
5588
|
// when its own pubspec does not name nomos_flutter directly.
|
|
5480
5589
|
if ((usesFlutter || usesClient) && localDart.flutter) lines.push(` nomos_flutter:`, ` path: ${localDart.flutter}`);
|
|
5481
5590
|
if (lines.length > 1) {
|
|
5482
|
-
writeTextIfChanged(
|
|
5591
|
+
writeTextIfChanged(overrideFile, lines.join("\n") + "\n");
|
|
5483
5592
|
}
|
|
5484
5593
|
}
|
|
5485
5594
|
}
|
|
@@ -5503,9 +5612,16 @@ async function main(): Promise<void> {
|
|
|
5503
5612
|
"pubspec_overrides.yaml",
|
|
5504
5613
|
path.join(".dart_tool", "package_config.json"),
|
|
5505
5614
|
].map((name) => ({ name, file: path.join(pkg, name) }));
|
|
5506
|
-
|
|
5615
|
+
// `pubspec_overrides.yaml` is intentionally absent for installed compilers. Its absence is an
|
|
5616
|
+
// input to the proof, not proof that dependency resolution can never be current.
|
|
5617
|
+
if (files.some(({ name, file }) => name !== "pubspec_overrides.yaml" && !existsSync(file))) return undefined;
|
|
5507
5618
|
const hash = createHash("sha256").update(`${dartResolutionFormat}\0`);
|
|
5508
|
-
for (const { name, file } of files)
|
|
5619
|
+
for (const { name, file } of files) {
|
|
5620
|
+
hash.update(name).update("\0");
|
|
5621
|
+
if (existsSync(file)) hash.update("present\0").update(readFileSync(file));
|
|
5622
|
+
else hash.update("absent");
|
|
5623
|
+
hash.update("\0");
|
|
5624
|
+
}
|
|
5509
5625
|
return hash.digest("hex");
|
|
5510
5626
|
};
|
|
5511
5627
|
const dartPackageResolutionCurrent = (pkg: string): boolean => {
|
package/src/fields.ts
CHANGED
|
@@ -162,12 +162,21 @@ export interface VersionedNomosRefValue {
|
|
|
162
162
|
*/
|
|
163
163
|
export type Sha256Digest = string & { readonly __nomosSha256Digest: unique symbol };
|
|
164
164
|
|
|
165
|
+
/** A Git object identity in Nomos' current SHA-1 custody repositories.
|
|
166
|
+
*
|
|
167
|
+
* This is not a content-law digest. Keeping it nominal prevents a commit/tree address from being
|
|
168
|
+
* accepted by a field that promises a 256-bit semantic identity merely because both travel as text.
|
|
169
|
+
*/
|
|
170
|
+
export type GitObjectId = string & { readonly __nomosGitObjectId: unique symbol };
|
|
171
|
+
|
|
165
172
|
/** Codegen marker for the semantic SHA-256 string type. The marker is deliberately
|
|
166
173
|
* non-enumerable: Zod remains the runtime/admission schema, while emitters recover
|
|
167
174
|
* the stronger client type without adding a parallel manifest representation. */
|
|
168
175
|
export const NOMOS_SHA256_DIGEST_BRAND: unique symbol = Symbol.for("nomos.sha256Digest");
|
|
176
|
+
export const NOMOS_GIT_OBJECT_ID_BRAND: unique symbol = Symbol.for("nomos.gitObjectId");
|
|
169
177
|
|
|
170
178
|
const SHA256_DIGEST_RE = /^[0-9a-f]{64}$/;
|
|
179
|
+
const GIT_OBJECT_ID_RE = /^[0-9a-f]{40}$/;
|
|
171
180
|
|
|
172
181
|
/** Declare and validate a lowercase hexadecimal SHA-256 digest payload value. */
|
|
173
182
|
export function sha256Digest(): z.ZodType<Sha256Digest> {
|
|
@@ -176,6 +185,13 @@ export function sha256Digest(): z.ZodType<Sha256Digest> {
|
|
|
176
185
|
return schema as unknown as z.ZodType<Sha256Digest>;
|
|
177
186
|
}
|
|
178
187
|
|
|
188
|
+
/** Declare and validate the object identity used by the current gix custody format. */
|
|
189
|
+
export function gitObjectId(): z.ZodType<GitObjectId> {
|
|
190
|
+
const schema = z.string().regex(GIT_OBJECT_ID_RE, "expected a lowercase hexadecimal Git object id");
|
|
191
|
+
Object.defineProperty(schema, NOMOS_GIT_OBJECT_ID_BRAND, { value: true, enumerable: false });
|
|
192
|
+
return schema as unknown as z.ZodType<GitObjectId>;
|
|
193
|
+
}
|
|
194
|
+
|
|
179
195
|
/** Recover the SHA-256 semantic marker through omission/null wrappers. */
|
|
180
196
|
export function isSha256DigestSchema(schema: z.ZodTypeAny): boolean {
|
|
181
197
|
let current: z.ZodTypeAny | undefined = schema;
|
|
@@ -199,6 +215,29 @@ export function isSha256DigestSchema(schema: z.ZodTypeAny): boolean {
|
|
|
199
215
|
return false;
|
|
200
216
|
}
|
|
201
217
|
|
|
218
|
+
/** Recover the Git object-id semantic marker through omission/null wrappers. */
|
|
219
|
+
export function isGitObjectIdSchema(schema: z.ZodTypeAny): boolean {
|
|
220
|
+
let current: z.ZodTypeAny | undefined = schema;
|
|
221
|
+
for (let hops = 0; current !== undefined && hops < 16; hops++) {
|
|
222
|
+
if ((current as unknown as Record<symbol, unknown>)[NOMOS_GIT_OBJECT_ID_BRAND] === true) {
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
const raw = current as unknown as {
|
|
226
|
+
_def?: { type?: unknown; innerType?: z.ZodTypeAny };
|
|
227
|
+
def?: { type?: unknown; innerType?: z.ZodTypeAny };
|
|
228
|
+
};
|
|
229
|
+
const def = raw._def ?? raw.def;
|
|
230
|
+
const kind = def?.type;
|
|
231
|
+
const name = (current as unknown as { _def?: { typeName?: string } })._def?.typeName;
|
|
232
|
+
if (kind !== "optional" && kind !== "default" && kind !== "nullable" &&
|
|
233
|
+
name !== "ZodOptional" && name !== "ZodDefault" && name !== "ZodNullable") {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
current = def?.innerType;
|
|
237
|
+
}
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
|
|
202
241
|
export const nomosRefValueSchema: z.ZodType<NomosRefValue> = z.object({
|
|
203
242
|
workspace: z.string().min(1),
|
|
204
243
|
type: z.string().min(1),
|
|
@@ -895,6 +934,10 @@ export const t = {
|
|
|
895
934
|
sha256Digest(): Field<Sha256Digest, "string"> {
|
|
896
935
|
return makeField<Sha256Digest, "string">("string", sha256Digest(), FirstWriterWins);
|
|
897
936
|
},
|
|
937
|
+
/** A gix custody object identity. Generated clients surface [GitObjectId], not String. */
|
|
938
|
+
gitObjectId(): Field<GitObjectId, "string"> {
|
|
939
|
+
return makeField<GitObjectId, "string">("string", gitObjectId(), FirstWriterWins);
|
|
940
|
+
},
|
|
898
941
|
int(rules: IntRules = {}): Field<number, "int"> {
|
|
899
942
|
for (const [name, value] of [["minimum", rules.minimum], ["maximum", rules.maximum]] as const) {
|
|
900
943
|
if (value !== undefined && !Number.isSafeInteger(value)) {
|