@githolon/dsl 0.105.13 → 0.105.15
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 +13 -0
- package/dart/lib/src/runner/node-entry.mjs +19 -13
- package/dart/lib/src/runner/offline-install.mjs +25 -11
- package/dart/lib/src/session_bound.dart +7 -7
- package/dart/lib/src/webview_bridge.dart +4 -5
- package/dart/pubspec.yaml +1 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +93 -34
- package/src/build_package.ts +37 -3
- package/src/check_package_main.ts +12 -10
- package/src/codegen_dart.ts +7 -4
- package/src/codegen_dart_client.ts +5 -16
- package/src/codegen_ts.ts +32 -4
- package/src/compile_package_main.ts +100 -9
- package/src/evolution.ts +313 -0
- package/src/evolution_files.ts +31 -0
- package/src/evolve_preview.ts +64 -55
- package/src/index.ts +10 -0
- package/src/law_graph.ts +58 -1
- package/src/law_graph_usda.ts +26 -0
- package/src/stable_ids_for_module.ts +1 -1
package/src/build_package.ts
CHANGED
|
@@ -27,6 +27,7 @@ import { createHash } from "node:crypto";
|
|
|
27
27
|
import { z } from "zod";
|
|
28
28
|
|
|
29
29
|
import type { AggregateHandle } from "./aggregate.js";
|
|
30
|
+
import type { Field } from "./fields.js";
|
|
30
31
|
import type { Directive } from "./directive.js";
|
|
31
32
|
import type { DomainModule, PermissionVocabulary, DartImport } from "./codegen_dart.js";
|
|
32
33
|
import type { AnyCount } from "./count.js";
|
|
@@ -83,6 +84,11 @@ import {
|
|
|
83
84
|
applyConflictPolicyResolvers,
|
|
84
85
|
conflictPoliciesOf,
|
|
85
86
|
} from "./conflicts.js";
|
|
87
|
+
import {
|
|
88
|
+
applyFieldEvolutionsToAggregates,
|
|
89
|
+
fieldEvolutionsOf,
|
|
90
|
+
retirementsOf,
|
|
91
|
+
} from "./evolution.js";
|
|
86
92
|
import {
|
|
87
93
|
expandBusinessBirthExports,
|
|
88
94
|
isBusinessBirthDirective,
|
|
@@ -200,6 +206,8 @@ export interface DomainModuleSpec {
|
|
|
200
206
|
export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
|
|
201
207
|
const domain = spec.domain ?? spec.name;
|
|
202
208
|
const conflictPolicies = conflictPoliciesOf(spec.modules, domain);
|
|
209
|
+
const fieldEvolutions = fieldEvolutionsOf(spec.modules);
|
|
210
|
+
const retirements = retirementsOf(spec.modules, domain);
|
|
203
211
|
const applicationLanguage = applicationLanguageOf(spec.modules, domain);
|
|
204
212
|
let merged: Mod = {};
|
|
205
213
|
for (const m of spec.modules) merged = { ...merged, ...m };
|
|
@@ -520,11 +528,34 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
|
|
|
520
528
|
directives.push(dir);
|
|
521
529
|
autoDeclaredEnsures.push(ty);
|
|
522
530
|
}
|
|
523
|
-
const aggregates =
|
|
524
|
-
|
|
525
|
-
|
|
531
|
+
const aggregates = applyFieldEvolutionsToAggregates(
|
|
532
|
+
applyConflictPoliciesToAggregates(
|
|
533
|
+
[...byId.values()],
|
|
534
|
+
conflictPolicies,
|
|
535
|
+
domain,
|
|
536
|
+
),
|
|
537
|
+
fieldEvolutions,
|
|
526
538
|
domain,
|
|
527
539
|
);
|
|
540
|
+
const currentAggregates = new Map(aggregates.map((aggregate) => [aggregate.id, aggregate]));
|
|
541
|
+
for (const retired of retirements) {
|
|
542
|
+
const current = currentAggregates.get(retired.aggregate);
|
|
543
|
+
if (retired.kind === "aggregate" && current !== undefined) {
|
|
544
|
+
throw new Error(
|
|
545
|
+
`domain '${domain}' declares '${retired.aggregate}' both current and retired`,
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
if (
|
|
549
|
+
retired.kind === "field" &&
|
|
550
|
+
current !== undefined &&
|
|
551
|
+
retired.field !== undefined &&
|
|
552
|
+
(current.fields as Record<string, Field>)[retired.field] !== undefined
|
|
553
|
+
) {
|
|
554
|
+
throw new Error(
|
|
555
|
+
`domain '${domain}' declares '${retired.aggregate}.${retired.field}' both current and retired`,
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
528
559
|
const directiveIds = new Set(directives.map((d) => d.id));
|
|
529
560
|
for (const task of declaredCapabilities) {
|
|
530
561
|
for (const directive of [task.obligation, task.complete, task.fail, task.block, task.deadLetter]) {
|
|
@@ -855,6 +886,9 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
|
|
|
855
886
|
// hash-bearing manifest; payload-field tracing also drives generated typed references.
|
|
856
887
|
// Omit-when-empty because ordinary domains need no resolver metadata.
|
|
857
888
|
...(surfacedResolvers.length > 0 ? { surfacedResolvers } : {}),
|
|
889
|
+
// Explicit authored tombstones are law. The compiler identity registry may remember
|
|
890
|
+
// every disappearance, but only these declarations permit an activation to retire it.
|
|
891
|
+
...(retirements.length > 0 ? { retirements } : {}),
|
|
858
892
|
...(queries.length > 0 ? { queries } : {}),
|
|
859
893
|
...(counts.length > 0 ? { counts } : {}),
|
|
860
894
|
...(spatials.length > 0 ? { spatials } : {}),
|
|
@@ -17,6 +17,8 @@ import { pathToFileURL } from "node:url";
|
|
|
17
17
|
import { composeDomainModule, type Mod } from "./build_package.js";
|
|
18
18
|
import type { DomainModule } from "./codegen_dart.js";
|
|
19
19
|
import { bindEnumTypeExports } from "./enum_type.js";
|
|
20
|
+
import { discoverEvolutionModulePaths, isEvolutionModulePath } from "./evolution_files.js";
|
|
21
|
+
import { assertEvolutionModuleExports } from "./evolution.js";
|
|
20
22
|
import { checkDomainModules, formatFindings, hasRefusal, type Finding } from "./law_check.js";
|
|
21
23
|
import { LawSourceMap } from "./law_source_map.js";
|
|
22
24
|
import {
|
|
@@ -105,9 +107,18 @@ export async function loadComposedModulesWithSources(
|
|
|
105
107
|
const files: string[] = [];
|
|
106
108
|
for (const rel of d.modules) {
|
|
107
109
|
const abs = path.resolve(cfgDir, rel);
|
|
110
|
+
if (isEvolutionModulePath(abs)) {
|
|
111
|
+
fail(
|
|
112
|
+
`domain '${d.key}': ${rel} is an evolution concern; move it under ` +
|
|
113
|
+
`evolutions/${d.key}/ and remove it from feature modules so it cannot enter QuickJS`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
108
116
|
if (!existsSync(abs)) fail(`domain '${d.key}': module not found: ${abs}`);
|
|
109
117
|
files.push(abs);
|
|
110
118
|
}
|
|
119
|
+
for (const abs of discoverEvolutionModulePaths(cfgDir, d.key)) {
|
|
120
|
+
if (!files.includes(abs)) files.push(abs);
|
|
121
|
+
}
|
|
111
122
|
try {
|
|
112
123
|
assertPublicPackageImports(files);
|
|
113
124
|
} catch (error) {
|
|
@@ -116,6 +127,7 @@ export async function loadComposedModulesWithSources(
|
|
|
116
127
|
for (const abs of files) {
|
|
117
128
|
// Cache-bust so a re-import (the LSP re-check after an edit) re-evaluates the edited module.
|
|
118
129
|
const mod = (await import(`${pathToFileURL(abs).href}?t=${Date.now()}`)) as Mod;
|
|
130
|
+
if (isEvolutionModulePath(abs)) assertEvolutionModuleExports(mod, abs);
|
|
119
131
|
bindEnumTypeExports(mod, abs);
|
|
120
132
|
mods.push(mod);
|
|
121
133
|
}
|
|
@@ -201,7 +213,6 @@ async function main(): Promise<void> {
|
|
|
201
213
|
const findings: Finding[] = [...dependencyFindings, ...checkDomainModules(modules)];
|
|
202
214
|
|
|
203
215
|
// ── EVOLVE PREVIEW (--against): diff the current build against a prior law + pre-fire the gate. ──
|
|
204
|
-
let evolveDispositions: Record<string, unknown> | undefined;
|
|
205
216
|
if (againstIdx >= 0 || againstArg !== undefined) {
|
|
206
217
|
if (againstArg === undefined || againstArg.startsWith("--")) {
|
|
207
218
|
fail(`--against needs a path: --against <${NOMOS_IDENTITY_FILENAME} | package dir>`);
|
|
@@ -218,7 +229,6 @@ async function main(): Promise<void> {
|
|
|
218
229
|
const current = currentEvolveLaw(modules, lock);
|
|
219
230
|
const diff = evolveDiff(prior.law, current);
|
|
220
231
|
findings.push(...diff.findings);
|
|
221
|
-
if (Object.keys(diff.dispositions).length > 0) evolveDispositions = diff.dispositions;
|
|
222
232
|
if (!asJson) console.log(`githolon check: evolve preview against ${prior.source}`);
|
|
223
233
|
}
|
|
224
234
|
|
|
@@ -255,14 +265,6 @@ async function main(): Promise<void> {
|
|
|
255
265
|
}
|
|
256
266
|
if (findings.length === 0) console.log(" all clear — no static twin flagged anything");
|
|
257
267
|
|
|
258
|
-
// The consolidated, pasteable dispositions the destructive evolves demand (per domain).
|
|
259
|
-
if (evolveDispositions !== undefined) {
|
|
260
|
-
console.log(
|
|
261
|
-
"\ngitholon check: to admit the destructive evolve, include `dispositions` in the installation offer:",
|
|
262
|
-
);
|
|
263
|
-
console.log(JSON.stringify(evolveDispositions, null, 2));
|
|
264
|
-
}
|
|
265
|
-
|
|
266
268
|
process.exit(hasRefusal(findings) ? 1 : 0);
|
|
267
269
|
}
|
|
268
270
|
|
package/src/codegen_dart.ts
CHANGED
|
@@ -2742,14 +2742,14 @@ export function generateDartActivityRegistry(entries: readonly PackageActivityEn
|
|
|
2742
2742
|
` final String summary;`,
|
|
2743
2743
|
` final NomosModelReview review;`,
|
|
2744
2744
|
` final Future<NomosModelAdoption> Function()? _stage;`,
|
|
2745
|
-
` final Future<NomosModelAdoption> Function(String
|
|
2745
|
+
` final Future<NomosModelAdoption> Function(String)? _accept;`,
|
|
2746
2746
|
` const NomosModelAdoption.adopted({required this.summary, required this.review})`,
|
|
2747
2747
|
` : status = NomosModelAdoptionStatus.adopted, _stage = null, _accept = null;`,
|
|
2748
2748
|
` const NomosModelAdoption.blocked({required this.summary, required this.review})`,
|
|
2749
2749
|
` : status = NomosModelAdoptionStatus.blocked, _stage = null, _accept = null;`,
|
|
2750
2750
|
` NomosModelAdoption.needsStaging({required this.summary, required this.review, required Future<NomosModelAdoption> Function() stage})`,
|
|
2751
2751
|
` : status = NomosModelAdoptionStatus.needsStaging, _stage = stage, _accept = null;`,
|
|
2752
|
-
` NomosModelAdoption.needsDecision({required this.summary, required this.review, required Future<NomosModelAdoption> Function(String
|
|
2752
|
+
` NomosModelAdoption.needsDecision({required this.summary, required this.review, required Future<NomosModelAdoption> Function(String) accept})`,
|
|
2753
2753
|
` : status = NomosModelAdoptionStatus.needsDecision, _stage = null, _accept = accept;`,
|
|
2754
2754
|
` Future<NomosModelAdoption> stage() {`,
|
|
2755
2755
|
` final stageCandidate = _stage;`,
|
|
@@ -2758,14 +2758,14 @@ export function generateDartActivityRegistry(entries: readonly PackageActivityEn
|
|
|
2758
2758
|
` }`,
|
|
2759
2759
|
` return stageCandidate();`,
|
|
2760
2760
|
` }`,
|
|
2761
|
-
` Future<NomosModelAdoption> accept(String because
|
|
2761
|
+
` Future<NomosModelAdoption> accept(String because) {`,
|
|
2762
2762
|
` final reason = because.trim();`,
|
|
2763
2763
|
` final acceptDecision = _accept;`,
|
|
2764
2764
|
` if (status != NomosModelAdoptionStatus.needsDecision || acceptDecision == null) {`,
|
|
2765
2765
|
` throw StateError('This model change is not waiting for a business decision.');`,
|
|
2766
2766
|
` }`,
|
|
2767
2767
|
` if (reason.isEmpty) throw ArgumentError('accept(reason) needs the business reason for changing prior outcomes');`,
|
|
2768
|
-
` return acceptDecision(reason
|
|
2768
|
+
` return acceptDecision(reason);`,
|
|
2769
2769
|
` }`,
|
|
2770
2770
|
`}`,
|
|
2771
2771
|
``,
|
|
@@ -4788,6 +4788,9 @@ export interface DomainModule {
|
|
|
4788
4788
|
* Absent when the domain declares no Surfaced field (byte-identical — the era rule).
|
|
4789
4789
|
*/
|
|
4790
4790
|
surfacedResolvers?: import("./surfaced_lint.js").SurfacedResolverBinding[];
|
|
4791
|
+
/** Explicit tombstones authored in separate evolution files. These compile into
|
|
4792
|
+
* candidate USDA and are never exposed through generated tenant clients. */
|
|
4793
|
+
retirements?: import("./evolution.js").Retirement[];
|
|
4791
4794
|
/**
|
|
4792
4795
|
* STABLE-ID CONTINUITY (#58 — names are labels, identity is minted). When present
|
|
4793
4796
|
* (attached by `nomos-compile` from the PRIOR build's identity manifests), the
|
|
@@ -1673,11 +1673,11 @@ export function generateDartBridgeClient(
|
|
|
1673
1673
|
` sameOutcome: projection?['equal'] as bool?, differences: differences);`,
|
|
1674
1674
|
` }`,
|
|
1675
1675
|
``,
|
|
1676
|
-
` Future<NomosModelAdoption> _adoptGeneratedModel([String? because
|
|
1676
|
+
` Future<NomosModelAdoption> _adoptGeneratedModel([String? because]) async {`,
|
|
1677
1677
|
` final raw = await bridge.nomosFrameworkAdoptGeneratedModel(`,
|
|
1678
1678
|
` domain: ${dartStr(
|
|
1679
1679
|
domain
|
|
1680
|
-
)}, candidateHash: domainHash, because: because
|
|
1680
|
+
)}, candidateHash: domainHash, because: because);`,
|
|
1681
1681
|
` final reportRaw = raw['report'];`,
|
|
1682
1682
|
` final report = reportRaw is Map ? reportRaw.cast<String, Object?>() : null;`,
|
|
1683
1683
|
` final review = _nomosModelReview(report);`,
|
|
@@ -1703,20 +1703,9 @@ export function generateDartBridgeClient(
|
|
|
1703
1703
|
` if (stage == 'acceptance') {`,
|
|
1704
1704
|
` return NomosModelAdoption.needsDecision(`,
|
|
1705
1705
|
` summary: 'This model would change prior business outcomes. Review the differences and record why that is intended.',`,
|
|
1706
|
-
` review: review, accept: (reason
|
|
1706
|
+
` review: review, accept: (reason) => _adoptGeneratedModel(reason));`,
|
|
1707
1707
|
` }`,
|
|
1708
1708
|
` final evolve = (report?['evolve'] as Map?)?.cast<String, Object?>();`,
|
|
1709
|
-
` // A field/aggregate the candidate genuinely removed is a #58 disposition decision, not a`,
|
|
1710
|
-
` // dead end: retry with the SAME accept/dispositions seam so the tenant's own policy can`,
|
|
1711
|
-
` // supply dispositions.retired. Every prior stage (promoted/staging-required/acceptance) is`,
|
|
1712
|
-
` // already handled above; 'identity' (needs sign-in) is the one remaining stage this must NOT`,
|
|
1713
|
-
` // paper over, so it is the sole exclusion rather than requiring an exact 'preflight' match or`,
|
|
1714
|
-
` // exact boolean report fields, which may not round-trip identically across every bridge.`,
|
|
1715
|
-
` if (stage != 'identity') {`,
|
|
1716
|
-
` return NomosModelAdoption.needsDecision(`,
|
|
1717
|
-
` summary: 'This model cannot yet interpret every existing value without a declared disposition. Review the differences and record why that is intended.',`,
|
|
1718
|
-
` review: review, accept: (reason, {dispositions}) => _adoptGeneratedModel(reason, dispositions));`,
|
|
1719
|
-
` }`,
|
|
1720
1709
|
` final rawError = raw['error']?.toString().trim();`,
|
|
1721
1710
|
` final summary = rawError != null && rawError.isNotEmpty`,
|
|
1722
1711
|
` ? rawError`,
|
|
@@ -2436,8 +2425,8 @@ export function generateDartBridgeClient(
|
|
|
2436
2425
|
` : null,`,
|
|
2437
2426
|
]),
|
|
2438
2427
|
` accept: adoption.status == NomosModelAdoptionStatus.needsDecision`,
|
|
2439
|
-
` ? (reason
|
|
2440
|
-
` final accepted = await adoption.accept(reason
|
|
2428
|
+
` ? (reason) async {`,
|
|
2429
|
+
` final accepted = await adoption.accept(reason);`,
|
|
2441
2430
|
` // A retry that still isn't adopted (a transient business-outcome/history race,`,
|
|
2442
2431
|
` // e.g. concurrent writes landing between the bare preflight and this accept) must`,
|
|
2443
2432
|
` // NOT be swallowed as a terminal StateError: re-throw the SAME typed exception so`,
|
package/src/codegen_ts.ts
CHANGED
|
@@ -751,6 +751,9 @@ export interface TsClientOptions {
|
|
|
751
751
|
readonly packageName: string;
|
|
752
752
|
/** Content identity of the built typed USDA closure (`.law.usdz`). */
|
|
753
753
|
readonly domainHash: string;
|
|
754
|
+
/** Sibling lossless USDZ transport carried by the generated client when its signed-in workspace
|
|
755
|
+
* still serves the predecessor model. The archive is never canonical state and is not embedded. */
|
|
756
|
+
readonly lawAssetName?: string;
|
|
754
757
|
/** The package's STRUCTURAL INTERFACE (#72) — directives (payload shapes) + aggregates
|
|
755
758
|
* (read shapes, sid-keyed), keyed by domain. Baked in so `connect()` can negotiate
|
|
756
759
|
* shape-compatibility against the workspace's installed offer — NOT by hash. */
|
|
@@ -819,6 +822,7 @@ function emitTsActivityBlock(entries: readonly PackageActivityEntry[], describeM
|
|
|
819
822
|
`}`,
|
|
820
823
|
`type NomosModelAdoption =`,
|
|
821
824
|
` | { status: "adopted"; summary: string; review: NomosModelReview }`,
|
|
825
|
+
` | { status: "needs-staging"; summary: string; review: NomosModelReview; stage(): Promise<NomosModelAdoption> }`,
|
|
822
826
|
` | { status: "needs-decision"; summary: string; review: NomosModelReview; accept(because: string): Promise<NomosModelAdoption> }`,
|
|
823
827
|
` | { status: "blocked"; summary: string; error: string; review: NomosModelReview };`,
|
|
824
828
|
`type NomosModelRestoration =`,
|
|
@@ -868,7 +872,7 @@ function emitTsActivityBlock(entries: readonly PackageActivityEntry[], describeM
|
|
|
868
872
|
` };`,
|
|
869
873
|
`}`,
|
|
870
874
|
`async function _nomosAdoptModel(`,
|
|
871
|
-
` holon: NomosHolon, domain: string, candidateHash: string, because?: string,`,
|
|
875
|
+
` holon: NomosHolon, domain: string, candidateHash: string, lawAsset?: URL, because?: string,`,
|
|
872
876
|
`): Promise<NomosModelAdoption> {`,
|
|
873
877
|
` if (!holon.__nomosAdoptModel) {`,
|
|
874
878
|
` const error = "This device needs the current Nomos client before it can adopt a model.";`,
|
|
@@ -879,6 +883,26 @@ function emitTsActivityBlock(entries: readonly PackageActivityEntry[], describeM
|
|
|
879
883
|
` if (raw.stage === "promoted" && raw.ok) {`,
|
|
880
884
|
` return { status: "adopted", summary: "Nomos proved this model against current history and made it active.", review };`,
|
|
881
885
|
` }`,
|
|
886
|
+
` if (raw.stage === "staging-required" && lawAsset !== undefined && holon.__nomosStageGeneratedModel !== undefined) {`,
|
|
887
|
+
` return {`,
|
|
888
|
+
` status: "needs-staging",`,
|
|
889
|
+
` summary: "This workspace is running the previous model. Nomos can stage this generated USDA candidate through its signed author gate.",`,
|
|
890
|
+
` review,`,
|
|
891
|
+
` stage: async () => {`,
|
|
892
|
+
` // Keep Node's file loader behind a runtime-only boundary: browser-generated clients`,
|
|
893
|
+
` // must typecheck without @types/node, while the local CLI can still stage its USDA asset.`,
|
|
894
|
+
` const dynamicImport = Function("specifier", "return import(specifier)") as (specifier: string) => Promise<{ readFile(source: URL): Promise<Uint8Array> }>;`,
|
|
895
|
+
` const { readFile } = await dynamicImport("node:fs/promises");`,
|
|
896
|
+
` const lawUsdz = new Uint8Array(await readFile(lawAsset));`,
|
|
897
|
+
` const staged = await holon.__nomosStageGeneratedModel!(domain, candidateHash, lawUsdz);`,
|
|
898
|
+
` if (staged.ok !== true) {`,
|
|
899
|
+
` const error = staged.error ?? "The workspace refused the generated USDA candidate.";`,
|
|
900
|
+
` return { status: "blocked", summary: error, error, review };`,
|
|
901
|
+
` }`,
|
|
902
|
+
` return _nomosAdoptModel(holon, domain, candidateHash, lawAsset);`,
|
|
903
|
+
` },`,
|
|
904
|
+
` };`,
|
|
905
|
+
` }`,
|
|
882
906
|
` if (raw.stage === "acceptance") {`,
|
|
883
907
|
` return {`,
|
|
884
908
|
` status: "needs-decision",`,
|
|
@@ -887,7 +911,7 @@ function emitTsActivityBlock(entries: readonly PackageActivityEntry[], describeM
|
|
|
887
911
|
` accept: async (reason: string) => {`,
|
|
888
912
|
` const stated = reason.trim();`,
|
|
889
913
|
` if (!stated) throw new Error("accept(reason) needs the business reason for changing prior outcomes");`,
|
|
890
|
-
` return _nomosAdoptModel(holon, domain, candidateHash, stated);`,
|
|
914
|
+
` return _nomosAdoptModel(holon, domain, candidateHash, lawAsset, stated);`,
|
|
891
915
|
` },`,
|
|
892
916
|
` };`,
|
|
893
917
|
` }`,
|
|
@@ -1807,6 +1831,8 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
1807
1831
|
` /** FRAMEWORK-INTERNAL exact-history adoption seam. Generated clients expose only`,
|
|
1808
1832
|
` * \`model.adopt()\` and a chained \`decision.accept(reason)\`. */`,
|
|
1809
1833
|
` __nomosAdoptModel?(domain: string, candidateHash: string, opts?: { because?: string }): Promise<__NomosModelAdoptionWire>;`,
|
|
1834
|
+
` /** FRAMEWORK-INTERNAL signed lifecycle offer used by generated model staging. */`,
|
|
1835
|
+
` __nomosStageGeneratedModel?(domain: string, candidateHash: string, lawUsdz: Uint8Array): Promise<{ ok?: boolean; error?: string }>;`,
|
|
1810
1836
|
` /** FRAMEWORK-INTERNAL exact-history restoration seam. Generated clients expose only`,
|
|
1811
1837
|
` * \`model.restorePrevious()\` and a chained \`decision.accept(reason)\`. */`,
|
|
1812
1838
|
` __nomosRestorePreviousModel?(domain: string, currentHash: string, opts?: { because?: string }): Promise<__NomosModelRestorationWire>;`,
|
|
@@ -3844,7 +3870,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
3844
3870
|
` /** Adopt THIS generated model. Nomos proves it against current history; a deliberate`,
|
|
3845
3871
|
` * difference returns a decision whose accept(reason) re-proves and records the reason. */`,
|
|
3846
3872
|
` model: {`,
|
|
3847
|
-
` adopt: (): Promise<NomosModelAdoption> => _nomosAdoptModel(holon, ${JSON.stringify(domain)}, domainHash),`,
|
|
3873
|
+
` adopt: (): Promise<NomosModelAdoption> => _nomosAdoptModel(holon, ${JSON.stringify(domain)}, domainHash${opts.lawAssetName === undefined ? "" : `, new URL(${JSON.stringify(`./${opts.lawAssetName}`)}, import.meta.url)`}),`,
|
|
3848
3874
|
` restorePrevious: (): Promise<NomosModelRestoration> => _nomosRestorePreviousModel(holon, ${JSON.stringify(domain)}, domainHash),`,
|
|
3849
3875
|
` },`,
|
|
3850
3876
|
...(clientInterface !== undefined
|
|
@@ -3971,6 +3997,8 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
3971
3997
|
` readonly signIn: (request?: { readonly deviceChallenge?: string }) => Promise<string>;`,
|
|
3972
3998
|
` /** Typed timings for developer tooling; Nomos routing, credentials and Git custody remain private. */`,
|
|
3973
3999
|
` readonly onPerformance?: (sample: ${applicationName}PerformanceSample) => void;`,
|
|
4000
|
+
` /** Supply the accountable business decision when this client intentionally changes the deployed model. */`,
|
|
4001
|
+
` readonly approveModel?: (decision: { readonly summary: string; readonly review: NomosModelReview }) => Promise<{ readonly because: string }>;`,
|
|
3974
4002
|
`}`,
|
|
3975
4003
|
`function _nomosPublicDomain<T extends Record<string, unknown>>(domain: T): _NomosPublicDomain<T> {`,
|
|
3976
4004
|
` const application: Record<string, unknown> = {};`,
|
|
@@ -4106,7 +4134,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
4106
4134
|
` * or parallel cache around it. Nomos owns offline custody and synchronization. */`,
|
|
4107
4135
|
`export function ${applicationOpener}(options: ${applicationName}OpenOptions): Promise<${applicationName}> {`,
|
|
4108
4136
|
` return nomosInternalOpenGeneratedApplication(`,
|
|
4109
|
-
` { cloud: options.cloud, application: options.application, signIn: options.signIn, onPerformance: options.onPerformance, lawHash: ${hashConst}, readyCommands: [${rootBirths.map((birth) => `{ domain: ${JSON.stringify(birth.sourceDomain)}, packageHash: ${hashConst}, directiveId: ${JSON.stringify(birth.directiveId)} }`).join(", ")}] },`,
|
|
4137
|
+
` { cloud: options.cloud, application: options.application, signIn: options.signIn, onPerformance: options.onPerformance, approveModel: options.approveModel, lawHash: ${hashConst}, readyCommands: [${rootBirths.map((birth) => `{ domain: ${JSON.stringify(birth.sourceDomain)}, packageHash: ${hashConst}, directiveId: ${JSON.stringify(birth.directiveId)} }`).join(", ")}] },`,
|
|
4110
4138
|
` (holon, user) => _nomosBind${applicationName}(holon as NomosHolon, user),`,
|
|
4111
4139
|
` );`,
|
|
4112
4140
|
`}`,
|
|
@@ -64,6 +64,8 @@ import {
|
|
|
64
64
|
} from "./codegen_dart.js";
|
|
65
65
|
import type { ContractHandle } from "./contract.js";
|
|
66
66
|
import { bindEnumTypeExports } from "./enum_type.js";
|
|
67
|
+
import { discoverEvolutionModulePaths, isEvolutionModulePath } from "./evolution_files.js";
|
|
68
|
+
import { assertEvolutionModuleExports } from "./evolution.js";
|
|
67
69
|
import { generateDartBridgeClient } from "./codegen_dart_client.js";
|
|
68
70
|
import {
|
|
69
71
|
dartApplicationClassName,
|
|
@@ -154,6 +156,7 @@ import {
|
|
|
154
156
|
serializeIdentityUsda,
|
|
155
157
|
} from "./identity_usda.js";
|
|
156
158
|
import { reconcileStableIdsWithIdentity } from "./stable_id_identity_bridge.js";
|
|
159
|
+
import { LAW_FRAMEWORK_PARENT } from "./law_identity.js";
|
|
157
160
|
import { discoverLawGraph, type LawGraph } from "./law_graph.js";
|
|
158
161
|
import { emitFrameworkLawUsda, emitSparseLawGraphUsda } from "./law_graph_usda.js";
|
|
159
162
|
import { assertNoDartBarrelCollisions, type BarrelExportedFile } from "./dart_barrel_guard.js";
|
|
@@ -163,6 +166,7 @@ import { domainInterface, type DomainInterface } from "./shape_compat.js";
|
|
|
163
166
|
import { generateTsProof, proofLegsIndex } from "./codegen_proof.js";
|
|
164
167
|
import { usdzPack } from "./usdz.js";
|
|
165
168
|
import { checkDomainModules, formatFindings, hasRefusal } from "./law_check.js";
|
|
169
|
+
import { currentEvolveLaw, evolveDiff, evolveLawFromLockfile } from "./evolve_preview.js";
|
|
166
170
|
import type { NomosCompat } from "./compat_manifest.js";
|
|
167
171
|
import { requiredKernelFeatures, type FeatureProbeModule } from "./kernel_capabilities.js";
|
|
168
172
|
import { APPLICATION_HOME_DOMAIN } from "./framework/application_home_domain.js";
|
|
@@ -1660,8 +1664,8 @@ const NOMOS_TYPES_DEP = "^0.8.7";
|
|
|
1660
1664
|
// 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
|
|
1661
1665
|
// DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
|
|
1662
1666
|
// REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
|
|
1663
|
-
const NOMOS_CLIENT_DEP = "^0.64.
|
|
1664
|
-
const NOMOS_FLUTTER_DEP = "^0.63.
|
|
1667
|
+
const NOMOS_CLIENT_DEP = "^0.64.31";
|
|
1668
|
+
const NOMOS_FLUTTER_DEP = "^0.63.54";
|
|
1665
1669
|
// DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in dsl/dart/pubspec.yaml
|
|
1666
1670
|
// and dsl/dart_types/pubspec.yaml — a Dart release without this bump fails the suite, so tenants never
|
|
1667
1671
|
// land back in dependency_overrides (the exact co2 report this closes, third occurrence).
|
|
@@ -3137,6 +3141,7 @@ async function main(): Promise<void> {
|
|
|
3137
3141
|
fail(`each domain needs { key, modules: [path, ...] }; got ${JSON.stringify(d)}`);
|
|
3138
3142
|
}
|
|
3139
3143
|
const mods: Mod[] = [];
|
|
3144
|
+
const evolutionMods: Mod[] = [];
|
|
3140
3145
|
const sourceExprs: string[] = [];
|
|
3141
3146
|
const directOfferExports = new Map<string, { modulePath: string; exportName: string; aggregateId: string }>();
|
|
3142
3147
|
const directWorkspaceInvariantExports = new Map<string, {
|
|
@@ -3145,6 +3150,12 @@ async function main(): Promise<void> {
|
|
|
3145
3150
|
}>();
|
|
3146
3151
|
for (const rel of d.modules) {
|
|
3147
3152
|
const abs = path.resolve(cfgDir, rel);
|
|
3153
|
+
if (isEvolutionModulePath(abs)) {
|
|
3154
|
+
fail(
|
|
3155
|
+
`domain '${d.key}': ${rel} is an evolution concern; move it under ` +
|
|
3156
|
+
`evolutions/${d.key}/ and remove it from feature modules so it cannot enter QuickJS`,
|
|
3157
|
+
);
|
|
3158
|
+
}
|
|
3148
3159
|
if (!existsSync(abs)) fail(`domain '${d.key}': module not found: ${abs}`);
|
|
3149
3160
|
const m = (await import(pathToFileURL(abs).href)) as Mod;
|
|
3150
3161
|
bindEnumTypeExports(m, abs);
|
|
@@ -3182,6 +3193,16 @@ async function main(): Promise<void> {
|
|
|
3182
3193
|
}
|
|
3183
3194
|
}
|
|
3184
3195
|
}
|
|
3196
|
+
const configuredPaths = new Set(d.modules.map((rel) => path.resolve(cfgDir, rel)));
|
|
3197
|
+
for (const abs of discoverEvolutionModulePaths(cfgDir, d.key)) {
|
|
3198
|
+
// Explicitly listed files remain supported during migration, but the same
|
|
3199
|
+
// module must not be composed twice when it is already in the convention.
|
|
3200
|
+
if (configuredPaths.has(abs)) continue;
|
|
3201
|
+
const evolutionModule = (await import(pathToFileURL(abs).href)) as Mod;
|
|
3202
|
+
assertEvolutionModuleExports(evolutionModule, abs);
|
|
3203
|
+
bindEnumTypeExports(evolutionModule, abs);
|
|
3204
|
+
evolutionMods.push(evolutionModule);
|
|
3205
|
+
}
|
|
3185
3206
|
if (mods.some((mod) => Object.prototype.hasOwnProperty.call(mod, BUSINESS_BIRTH_POLICY_DOMAINS_EXPORT))) {
|
|
3186
3207
|
fail(
|
|
3187
3208
|
`domain '${d.key}' exports reserved compiler binding '${BUSINESS_BIRTH_POLICY_DOMAINS_EXPORT}'; ` +
|
|
@@ -3502,7 +3523,7 @@ async function main(): Promise<void> {
|
|
|
3502
3523
|
composeDomainModule({
|
|
3503
3524
|
name: d.name ?? d.key,
|
|
3504
3525
|
domain: d.key,
|
|
3505
|
-
modules: mods,
|
|
3526
|
+
modules: [...mods, ...evolutionMods],
|
|
3506
3527
|
...(extraAggregates.length > 0 ? { extraAggregates } : {}),
|
|
3507
3528
|
...(queries.length > 0 ? { queries } : {}),
|
|
3508
3529
|
...(counts.length > 0 ? { counts } : {}),
|
|
@@ -3748,7 +3769,8 @@ async function main(): Promise<void> {
|
|
|
3748
3769
|
// exactly ONE disappeared + ONE appeared name of the same kind/shape is an inferred
|
|
3749
3770
|
// RENAME (sid carried, lineage recorded, printed below); anything else mints fresh
|
|
3750
3771
|
// at first appearance (a leftover disappearance is THE EVOLVE GATE's typed question
|
|
3751
|
-
// at
|
|
3772
|
+
// at compile — answered by a typed declaration in evolutions/<domain>/*.evolution.ts).
|
|
3773
|
+
// The resolved continuity
|
|
3752
3774
|
// is attached to the module so the canonical manifest + USD-IR carry it.
|
|
3753
3775
|
const lockPath = path.join(cfgDir, NOMOS_IDENTITY_FILENAME);
|
|
3754
3776
|
const priorLock: CompilerIdentityModel | undefined = existsSync(lockPath)
|
|
@@ -3891,6 +3913,63 @@ async function main(): Promise<void> {
|
|
|
3891
3913
|
schemas: Object.fromEntries(currentAggs.map((a) => [a.id, a.schema])),
|
|
3892
3914
|
};
|
|
3893
3915
|
}
|
|
3916
|
+
|
|
3917
|
+
// ── ORDINARY-COMPILE EVOLUTION GATE ──────────────────────────────────────────────
|
|
3918
|
+
// Agents reliably run `compile`; they do not reliably discover an optional migration command.
|
|
3919
|
+
// Compare the candidate against the committed identity frontier here, after stable-id
|
|
3920
|
+
// reconciliation (so inferred renames have their true identity) but before writing either the
|
|
3921
|
+
// lock or the custody package. The kernel independently repeats the same admission decision at
|
|
3922
|
+
// deployment; this is its fast, local static twin, not a replacement for runtime authority.
|
|
3923
|
+
//
|
|
3924
|
+
// The identity file is project-wide while a package may own only some of its domains. Scope the
|
|
3925
|
+
// prior side to the modules in this compile so compiling one sibling never reads another sibling's
|
|
3926
|
+
// absence as a domain retirement.
|
|
3927
|
+
if (priorLock !== undefined) {
|
|
3928
|
+
const priorDomainsForPackage: CompilerIdentityModel["domains"] = {};
|
|
3929
|
+
const priorIdentity = identityRegistryFromModel(priorLock);
|
|
3930
|
+
const frameworkAggregateSids = new Set(
|
|
3931
|
+
Object.values(priorIdentity.entities)
|
|
3932
|
+
.filter((entity) => entity.kind === "aggregate" && entity.parent === LAW_FRAMEWORK_PARENT)
|
|
3933
|
+
.map((entity) => entity.sid),
|
|
3934
|
+
);
|
|
3935
|
+
for (const mod of domainModules) {
|
|
3936
|
+
const domainKey = mod.domain ?? mod.name;
|
|
3937
|
+
const locked = priorLock.domains[mod.name] ?? priorLock.domains[domainKey];
|
|
3938
|
+
if (locked !== undefined) {
|
|
3939
|
+
// 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.
|
|
3942
|
+
const localAggregates = Object.fromEntries(
|
|
3943
|
+
Object.entries(locked.stableIds.aggregates)
|
|
3944
|
+
.filter(([, aggregate]) => !frameworkAggregateSids.has(aggregate.sid)),
|
|
3945
|
+
);
|
|
3946
|
+
priorDomainsForPackage[domainKey] = {
|
|
3947
|
+
stableIds: { ...locked.stableIds, aggregates: localAggregates },
|
|
3948
|
+
...(locked.schemas === undefined
|
|
3949
|
+
? {}
|
|
3950
|
+
: {
|
|
3951
|
+
schemas: Object.fromEntries(
|
|
3952
|
+
Object.entries(locked.schemas).filter(([label]) => label in localAggregates),
|
|
3953
|
+
),
|
|
3954
|
+
}),
|
|
3955
|
+
};
|
|
3956
|
+
}
|
|
3957
|
+
}
|
|
3958
|
+
const priorLaw = evolveLawFromLockfile({
|
|
3959
|
+
v: 1,
|
|
3960
|
+
domains: priorDomainsForPackage,
|
|
3961
|
+
...(priorLock.identity === undefined ? {} : { identity: priorLock.identity }),
|
|
3962
|
+
});
|
|
3963
|
+
const candidateLaw = currentEvolveLaw(domainModules, nextLock);
|
|
3964
|
+
const evolutionFindings = evolveDiff(priorLaw, candidateLaw).findings;
|
|
3965
|
+
const evolutionRefusals = evolutionFindings.filter((finding) => finding.level === "refuse");
|
|
3966
|
+
if (evolutionRefusals.length > 0) {
|
|
3967
|
+
console.error("nomos-compile: candidate law evolution refused before custody emission:");
|
|
3968
|
+
for (const line of formatFindings(evolutionRefusals)) console.error(` ✗ ${line}`);
|
|
3969
|
+
process.exitCode = 1;
|
|
3970
|
+
return;
|
|
3971
|
+
}
|
|
3972
|
+
}
|
|
3894
3973
|
// Persist the typed identity layer (commit it with the law: it is how a rename keeps its
|
|
3895
3974
|
// identity across clean clones). Existing project-domain order is retained, so once sibling
|
|
3896
3975
|
// packages are registered, recompiling them in any order rewrites the same bytes without a
|
|
@@ -5214,8 +5293,11 @@ async function main(): Promise<void> {
|
|
|
5214
5293
|
: `package asset: NOT EMITTED — declare rootPart: { name, kind: "component" | "assembly" }`,
|
|
5215
5294
|
`typed application: ${cfg.name}.client.mjs — open${cfg.name.split(/[^A-Za-z0-9]+/).filter(Boolean).map((part) => part[0]!.toUpperCase() + part.slice(1)).join("")}Application({ cloud, signIn })`,
|
|
5216
5295
|
proofPath !== undefined
|
|
5217
|
-
? `generated proof: ${cfg.name}.proof.mts —
|
|
5296
|
+
? `generated proof: ${cfg.name}.proof.mts — fresh: githolon proof; existing custody: githolon proof --upgrade`
|
|
5218
5297
|
: `generated proof: SKIPPED — ${proofSkip}`,
|
|
5298
|
+
...(priorLock === undefined
|
|
5299
|
+
? []
|
|
5300
|
+
: [`upgrade proof: githolon proof --upgrade — exact existing history, disposable promotion, cold restart`]),
|
|
5219
5301
|
``,
|
|
5220
5302
|
].join("\n");
|
|
5221
5303
|
writeFileSync(path.join(outDir, `${cfg.name}.summary.txt`), summaryTxt, "utf8");
|
|
@@ -5258,6 +5340,7 @@ async function main(): Promise<void> {
|
|
|
5258
5340
|
const clientOptions = {
|
|
5259
5341
|
packageName: cfg.name,
|
|
5260
5342
|
domainHash,
|
|
5343
|
+
lawAssetName: `${cfg.name}.law.usdz`,
|
|
5261
5344
|
interface: packageInterface,
|
|
5262
5345
|
compat: nomosCompat,
|
|
5263
5346
|
homeDomain: resolveApplicationTopology(presentationDomainModules).homeDomain,
|
|
@@ -5271,6 +5354,11 @@ async function main(): Promise<void> {
|
|
|
5271
5354
|
if (!existsSync(runtimePath)) {
|
|
5272
5355
|
fail("the compiler-private generated application runtime is missing — reinstall @githolon/dsl");
|
|
5273
5356
|
}
|
|
5357
|
+
// Validate the generated ESM clients as ESM even when the tenant project has no package.json yet.
|
|
5358
|
+
// NodeNext classifies a plain `.ts` file from the nearest package boundary, so a freshly scaffolded
|
|
5359
|
+
// or fixture-only project would otherwise be treated as CommonJS and reject the intentional
|
|
5360
|
+
// `import.meta.url` used to locate its adjacent law asset. The compiler options below describe the
|
|
5361
|
+
// generated `.mjs` runtime contract directly instead of inheriting unrelated tenant metadata.
|
|
5274
5362
|
const temporaryClientPath = path.join(outDir, `.${cfg.name}.client.source.ts`);
|
|
5275
5363
|
const temporaryHeadlessClientPath = path.join(outDir, `.${cfg.name}.headless.source.ts`);
|
|
5276
5364
|
const temporaryRuntimeTypesPath = path.join(outDir, `.${cfg.name}.application-runtime.d.ts`);
|
|
@@ -5278,7 +5366,7 @@ async function main(): Promise<void> {
|
|
|
5278
5366
|
const temporaryHeadlessDeclarationPath = temporaryHeadlessClientPath.replace(/\.ts$/, ".d.ts");
|
|
5279
5367
|
const runtimeTypesSource = [
|
|
5280
5368
|
`export declare function nomosInternalOpenGeneratedApplication<T extends object>(`,
|
|
5281
|
-
` options: { cloud: string; application: string; signIn: (request?: { readonly deviceChallenge?: string }) => Promise<string>; onPerformance?: (sample: { readonly operation: "open" | "offer" | "birth" | "read" | "sync" | "maintenance"; readonly name: string; readonly durationMs: number; readonly domain?: string; readonly directive?: string }) => void; lawHash: string; readyCommands?: readonly { readonly domain: string; readonly packageHash: string; readonly directiveId: string }[] },`,
|
|
5369
|
+
` options: { cloud: string; application: string; signIn: (request?: { readonly deviceChallenge?: string }) => Promise<string>; onPerformance?: (sample: { readonly operation: "open" | "offer" | "birth" | "read" | "sync" | "maintenance"; readonly name: string; readonly durationMs: number; readonly domain?: string; readonly directive?: string }) => void; approveModel?: (decision: { readonly summary: string; readonly review: { checkedActions: number; candidateActions: number; sameOutcome: boolean | null; differences: { kind: "rules" | "outcome" | "evidence"; area?: string; action?: string; summary: string }[] } }) => Promise<{ readonly because: string }>; lawHash: string; readyCommands?: readonly { readonly domain: string; readonly packageHash: string; readonly directiveId: string }[] },`,
|
|
5282
5370
|
` bind: (holon: unknown, user: { readonly identityId: string; readonly identityAddress: string }) => T,`,
|
|
5283
5371
|
`): Promise<T & {`,
|
|
5284
5372
|
` currentStatus(): Promise<any>;`,
|
|
@@ -5357,8 +5445,8 @@ async function main(): Promise<void> {
|
|
|
5357
5445
|
[temporaryClientPath, temporaryHeadlessClientPath, temporaryRuntimeTypesPath],
|
|
5358
5446
|
{
|
|
5359
5447
|
target: ts.ScriptTarget.ES2022,
|
|
5360
|
-
module: ts.ModuleKind.
|
|
5361
|
-
moduleResolution: ts.ModuleResolutionKind.
|
|
5448
|
+
module: ts.ModuleKind.ESNext,
|
|
5449
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
5362
5450
|
declaration: true,
|
|
5363
5451
|
emitDeclarationOnly: true,
|
|
5364
5452
|
noEmitOnError: true,
|
|
@@ -6042,12 +6130,15 @@ async function main(): Promise<void> {
|
|
|
6042
6130
|
console.log(` deploy githolon deploy <workspace> --file ${rel(lawUsdzPath)}`);
|
|
6043
6131
|
console.log(``);
|
|
6044
6132
|
console.log(`your business path:`);
|
|
6133
|
+
if (priorLock !== undefined) {
|
|
6134
|
+
console.log(` npx githolon proof --upgrade # candidate -> exact existing history -> disposable promotion -> cold restart`);
|
|
6135
|
+
}
|
|
6045
6136
|
if (proofPath !== undefined) {
|
|
6046
6137
|
console.log(` npx githolon proof # generated birth -> verify -> durable restore -> business read-back`);
|
|
6047
6138
|
console.log(` npx githolon dev # rerun that proof on every save`);
|
|
6048
6139
|
console.log(` npx githolon proof --live # the same business proof against Nomos Cloud`);
|
|
6049
6140
|
if (proofDomains.length > 1) {
|
|
6050
|
-
console.log(` this package proves ${proofDomains.length}
|
|
6141
|
+
console.log(` this package proves all ${proofDomains.length} business areas together (${proofDomains.join(", ")})`);
|
|
6051
6142
|
}
|
|
6052
6143
|
} else {
|
|
6053
6144
|
console.log(` add a business birth declaration to generate an end-to-end proof`);
|