@githolon/dsl 0.105.5 → 0.105.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 +27 -0
- package/dart/lib/src/runner/node-entry.mjs +155 -31
- package/dart/lib/src/runner/offline-install.mjs +82 -27
- package/dart/lib/src/webview_bridge.dart +115 -14
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/acquire_session_test.dart +37 -0
- package/dart/test/residency_refusal_test.dart +49 -0
- package/dart/test/sealing_client_test.dart +1 -1
- package/dart/test/webview_bridge_test.dart +19 -0
- package/dart_types/CHANGELOG.md +5 -0
- package/dart_types/lib/src/subscriptions.dart +5 -1
- package/dart_types/pubspec.yaml +1 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +138 -30
- package/src/check_package_main.ts +25 -54
- package/src/codegen_ts.ts +7 -0
- package/src/compat_package_main.ts +36 -30
- package/src/compile_package_main.ts +40 -39
- package/src/decl_shapes.ts +1 -1
- package/src/engine_entry.ts +2 -2
- package/src/evolve_preview.ts +7 -7
- package/src/identity_model.ts +123 -0
- package/src/identity_usda.ts +548 -0
- package/src/law_identity.ts +15 -0
- package/src/lsp_server.ts +14 -37
- package/src/stable_ids.ts +1 -1
- package/src/stable_id_lockfile.ts +0 -278
package/src/lsp_server.ts
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* Multi-root: each document resolves to its NEAREST `nomos.project.ts` (walk up), so a repo with
|
|
21
21
|
* several packages diagnoses each against its own config.
|
|
22
22
|
*/
|
|
23
|
-
import { existsSync,
|
|
23
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
24
24
|
import path from "node:path";
|
|
25
25
|
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
26
26
|
import { bindEnumTypeExports } from "./enum_type.js";
|
|
@@ -45,12 +45,14 @@ import { LawSourceMap } from "./law_source_map.js";
|
|
|
45
45
|
import {
|
|
46
46
|
currentEvolveLaw,
|
|
47
47
|
evolveDiff,
|
|
48
|
-
evolveLawFromInterface,
|
|
49
48
|
evolveLawFromLockfile,
|
|
50
49
|
type EvolveLaw,
|
|
51
|
-
type
|
|
50
|
+
type CompilerIdentityModel,
|
|
52
51
|
} from "./evolve_preview.js";
|
|
53
|
-
import {
|
|
52
|
+
import {
|
|
53
|
+
NOMOS_IDENTITY_FILENAME,
|
|
54
|
+
parseIdentityUsda,
|
|
55
|
+
} from "./identity_usda.js";
|
|
54
56
|
import {
|
|
55
57
|
validateNomosPackageConfig,
|
|
56
58
|
type NormalizedNomosPackageConfig,
|
|
@@ -194,54 +196,29 @@ async function composePackage(
|
|
|
194
196
|
return { modules: composed, domainFiles, dependencyFindings };
|
|
195
197
|
}
|
|
196
198
|
|
|
197
|
-
/** Load prior evolve diagnostics from
|
|
199
|
+
/** Load prior evolve diagnostics from the one committed typed identity layer. */
|
|
198
200
|
function loadPriorEvolveLaw(arg: string, cfgDir: string): EvolveLaw | undefined {
|
|
199
201
|
const abs = path.isAbsolute(arg) ? arg : path.resolve(cfgDir, arg);
|
|
200
202
|
if (!existsSync(abs)) return undefined;
|
|
201
203
|
let file = abs;
|
|
202
204
|
if (statSync(abs).isDirectory()) {
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
if (!existsSync(d)) return undefined;
|
|
206
|
-
const hit = readdirSync(d).find((f) => f.endsWith(".interface.json"));
|
|
207
|
-
return hit !== undefined ? path.join(d, hit) : undefined;
|
|
208
|
-
};
|
|
209
|
-
const iface = scan(abs) ?? scan(buildDir);
|
|
210
|
-
const lock = path.join(abs, "nomos.stable-ids.json");
|
|
211
|
-
if (iface !== undefined) file = iface;
|
|
212
|
-
else if (existsSync(lock)) file = lock;
|
|
205
|
+
const identity = path.join(abs, NOMOS_IDENTITY_FILENAME);
|
|
206
|
+
if (existsSync(identity)) file = identity;
|
|
213
207
|
else return undefined;
|
|
214
208
|
}
|
|
215
|
-
let parsed: unknown;
|
|
216
209
|
try {
|
|
217
|
-
|
|
210
|
+
return evolveLawFromLockfile(parseIdentityUsda(readFileSync(file, "utf8")));
|
|
218
211
|
} catch {
|
|
219
212
|
return undefined;
|
|
220
213
|
}
|
|
221
|
-
if (path.basename(file) === "nomos.stable-ids.json") {
|
|
222
|
-
return evolveLawFromLockfile(parseStableIdLockfile(parsed));
|
|
223
|
-
}
|
|
224
|
-
const lockFile = [
|
|
225
|
-
path.join(path.dirname(file), "nomos.stable-ids.json"),
|
|
226
|
-
path.join(path.dirname(path.dirname(file)), "nomos.stable-ids.json"),
|
|
227
|
-
].find((candidate) => existsSync(candidate));
|
|
228
|
-
let lock: StableIdLockfile | undefined;
|
|
229
|
-
if (lockFile !== undefined) {
|
|
230
|
-
try {
|
|
231
|
-
lock = parseStableIdLockfile(JSON.parse(readFileSync(lockFile, "utf8")));
|
|
232
|
-
} catch {
|
|
233
|
-
return undefined;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
return evolveLawFromInterface(parsed as Parameters<typeof evolveLawFromInterface>[0], lock);
|
|
237
214
|
}
|
|
238
215
|
|
|
239
|
-
/** Read the committed
|
|
240
|
-
function loadCwdLockfile(cfgDir: string):
|
|
241
|
-
const lockPath = path.join(cfgDir,
|
|
216
|
+
/** Read the committed typed identity layer beside the config (the CURRENT identity). */
|
|
217
|
+
function loadCwdLockfile(cfgDir: string): CompilerIdentityModel | undefined {
|
|
218
|
+
const lockPath = path.join(cfgDir, NOMOS_IDENTITY_FILENAME);
|
|
242
219
|
if (!existsSync(lockPath)) return undefined;
|
|
243
220
|
try {
|
|
244
|
-
return
|
|
221
|
+
return parseIdentityUsda(readFileSync(lockPath, "utf8"));
|
|
245
222
|
} catch {
|
|
246
223
|
return undefined;
|
|
247
224
|
}
|
package/src/stable_ids.ts
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* package therefore publishes one identical field-contract identity for that shared row.
|
|
34
34
|
*
|
|
35
35
|
* CONTINUITY (across compiles): `nomos-compile` reads the PRIOR build's identity
|
|
36
|
-
* manifests / the committed `nomos.
|
|
36
|
+
* manifests / the committed `nomos.identity.usda` lockfile and derives a continuity
|
|
37
37
|
* table per domain — match by name first; then the UNAMBIGUOUS-PAIR rule: exactly ONE
|
|
38
38
|
* disappeared name + exactly ONE same-shaped appeared name is an inferred RENAME (the
|
|
39
39
|
* sid is carried, the old label recorded in `was`, and the compile summary prints the
|
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
import type { StableAggregateId, StableFieldId, StableIds } from "./stable_ids_types.js";
|
|
2
|
-
import type { WireSchema } from "./wire.js";
|
|
3
|
-
import { mintGlobalAggregateSid } from "./stable_ids.js";
|
|
4
|
-
import {
|
|
5
|
-
LAW_FRAMEWORK_PARENT,
|
|
6
|
-
emptyLawIdentityRegistry,
|
|
7
|
-
lawEntityKey,
|
|
8
|
-
validateLawIdentityRegistry,
|
|
9
|
-
type LawIdentityEntity,
|
|
10
|
-
type LawIdentityRegistry,
|
|
11
|
-
} from "./law_identity.js";
|
|
12
|
-
|
|
13
|
-
export interface StableIdLockDomain {
|
|
14
|
-
stableIds: StableIds;
|
|
15
|
-
schemas?: Record<string, WireSchema>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** The materialized lock shape consumed inside the compiler and authoring tools. */
|
|
19
|
-
export interface StableIdLockfile {
|
|
20
|
-
v: 1;
|
|
21
|
-
domains: Record<string, StableIdLockDomain>;
|
|
22
|
-
/** W4 compiler identity custody. Semantics remain exclusively in USDA. */
|
|
23
|
-
identity?: LawIdentityRegistry;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
type CompactStableFieldId = Omit<StableFieldId, "contract" | "shape"> & {
|
|
27
|
-
contract?: string | number;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
interface CompactStableAggregateId extends Omit<StableAggregateId, "fields"> {
|
|
31
|
-
fields: Record<string, CompactStableFieldId>;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
interface CompactStableIds extends Omit<StableIds, "aggregates"> {
|
|
35
|
-
aggregates: Record<string, CompactStableAggregateId>;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface CompactStableIdLockfile {
|
|
39
|
-
v: 2;
|
|
40
|
-
/** [contract hash, canonical contract descriptor], addressed by field.contract. */
|
|
41
|
-
contracts: readonly (readonly [string, unknown])[];
|
|
42
|
-
domains: Record<string, { stableIds: CompactStableIds }>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface IdentityStableIdLockfile {
|
|
46
|
-
v: 3;
|
|
47
|
-
identity: LawIdentityRegistry;
|
|
48
|
-
/** Temporary W4 compatibility evidence; deleted once evolution traverses prior USDA. */
|
|
49
|
-
contracts: readonly (readonly [string, unknown])[];
|
|
50
|
-
/** Temporary aggregate/field compatibility view consumed by the v2 compiler path. */
|
|
51
|
-
domains: Record<string, { stableIds: CompactStableIds }>;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
55
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function sortedJson(value: unknown): unknown {
|
|
59
|
-
if (Array.isArray(value)) return value.map(sortedJson);
|
|
60
|
-
if (!isRecord(value)) return value;
|
|
61
|
-
return Object.fromEntries(
|
|
62
|
-
Object.keys(value)
|
|
63
|
-
.sort()
|
|
64
|
-
.map((key) => [key, sortedJson(value[key])]),
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Read both the original verbose lock and the compact v2 wire shape. Callers always
|
|
70
|
-
* receive the same fully materialized v1 object, so continuity and evolution logic
|
|
71
|
-
* never needs to know how the durable file was encoded.
|
|
72
|
-
*/
|
|
73
|
-
export function parseStableIdLockfile(value: unknown): StableIdLockfile {
|
|
74
|
-
if (!isRecord(value)) throw new Error("stable-id lock must be a JSON object");
|
|
75
|
-
if (value.v === 1) return value as unknown as StableIdLockfile;
|
|
76
|
-
if (
|
|
77
|
-
(value.v !== 2 && value.v !== 3) ||
|
|
78
|
-
!Array.isArray(value.contracts) ||
|
|
79
|
-
!isRecord(value.domains)
|
|
80
|
-
) {
|
|
81
|
-
throw new Error("unsupported stable-id lock encoding");
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
let identity: LawIdentityRegistry | undefined;
|
|
85
|
-
if (value.v === 3) {
|
|
86
|
-
if (!isRecord(value.identity)) throw new Error("stable-id lock v3 identity is malformed");
|
|
87
|
-
identity = value.identity as unknown as LawIdentityRegistry;
|
|
88
|
-
validateLawIdentityRegistry(identity);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const compact = value as unknown as CompactStableIdLockfile | IdentityStableIdLockfile;
|
|
92
|
-
const contracts = compact.contracts.map((entry, index) => {
|
|
93
|
-
if (!Array.isArray(entry) || entry.length !== 2 || typeof entry[0] !== "string") {
|
|
94
|
-
throw new Error(`stable-id lock contract ${index} is malformed`);
|
|
95
|
-
}
|
|
96
|
-
return entry;
|
|
97
|
-
});
|
|
98
|
-
const domains: StableIdLockfile["domains"] = {};
|
|
99
|
-
|
|
100
|
-
for (const [domainName, domain] of Object.entries(compact.domains)) {
|
|
101
|
-
const aggregates: StableIds["aggregates"] = {};
|
|
102
|
-
for (const [aggregateName, aggregate] of Object.entries(domain.stableIds.aggregates ?? {})) {
|
|
103
|
-
const fields: StableAggregateId["fields"] = {};
|
|
104
|
-
for (const [fieldName, field] of Object.entries(aggregate.fields ?? {})) {
|
|
105
|
-
const contractRef = field.contract;
|
|
106
|
-
const { contract: _contractRef, ...fieldWithoutContract } = field;
|
|
107
|
-
if (typeof contractRef === "number") {
|
|
108
|
-
const contract = contracts[contractRef];
|
|
109
|
-
if (contract === undefined) {
|
|
110
|
-
throw new Error(
|
|
111
|
-
`stable-id lock field ${domainName}.${aggregateName}.${fieldName} references missing contract ${contractRef}`,
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
fields[fieldName] = {
|
|
115
|
-
...fieldWithoutContract,
|
|
116
|
-
contract: contract[0],
|
|
117
|
-
shape: contract[1],
|
|
118
|
-
};
|
|
119
|
-
} else if (typeof contractRef === "string") {
|
|
120
|
-
fields[fieldName] = { ...fieldWithoutContract, contract: contractRef };
|
|
121
|
-
} else {
|
|
122
|
-
fields[fieldName] = fieldWithoutContract;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
aggregates[aggregateName] = { ...aggregate, fields };
|
|
126
|
-
}
|
|
127
|
-
domains[domainName] = {
|
|
128
|
-
stableIds: { ...domain.stableIds, aggregates },
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return { v: 1, domains, ...(identity === undefined ? {} : { identity }) };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Encode the committed lock without repeating identical contract descriptors or
|
|
137
|
-
* aggregate driver schemas. Fields already retain their canonical merge driver;
|
|
138
|
-
* the contract table retains the full descriptor needed to prove safe widening.
|
|
139
|
-
*/
|
|
140
|
-
export function serializeStableIdLockfile(lock: StableIdLockfile): string {
|
|
141
|
-
if (lock.identity !== undefined) validateLawIdentityRegistry(lock.identity);
|
|
142
|
-
const contracts: [string, unknown][] = [];
|
|
143
|
-
const contractIndexes = new Map<string, number>();
|
|
144
|
-
const domains: CompactStableIdLockfile["domains"] = {};
|
|
145
|
-
|
|
146
|
-
for (const [domainName, domain] of Object.entries(lock.domains)) {
|
|
147
|
-
const aggregates: CompactStableIds["aggregates"] = {};
|
|
148
|
-
for (const [aggregateName, aggregate] of Object.entries(domain.stableIds.aggregates)) {
|
|
149
|
-
const fields: CompactStableAggregateId["fields"] = {};
|
|
150
|
-
for (const [fieldName, field] of Object.entries(aggregate.fields)) {
|
|
151
|
-
const { shape, ...withoutShape } = field;
|
|
152
|
-
if (field.contract !== undefined && shape !== undefined) {
|
|
153
|
-
let index = contractIndexes.get(field.contract);
|
|
154
|
-
if (index === undefined) {
|
|
155
|
-
index = contracts.length;
|
|
156
|
-
contracts.push([field.contract, shape]);
|
|
157
|
-
contractIndexes.set(field.contract, index);
|
|
158
|
-
}
|
|
159
|
-
fields[fieldName] = { ...withoutShape, contract: index };
|
|
160
|
-
} else {
|
|
161
|
-
fields[fieldName] = withoutShape;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
aggregates[aggregateName] = { ...aggregate, fields };
|
|
165
|
-
}
|
|
166
|
-
domains[domainName] = {
|
|
167
|
-
stableIds: { ...domain.stableIds, aggregates },
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// This is a machine-owned lockfile. Compact JSON is intentional: unlike generated
|
|
172
|
-
// law and clients, it must be committed so identity survives a clean clone.
|
|
173
|
-
const encoded =
|
|
174
|
-
lock.identity === undefined
|
|
175
|
-
? ({ v: 2, contracts, domains } satisfies CompactStableIdLockfile)
|
|
176
|
-
: ({ v: 3, identity: lock.identity, contracts, domains } satisfies IdentityStableIdLockfile);
|
|
177
|
-
return JSON.stringify(sortedJson(encoded)) + "\n";
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* One-time W4 import of the existing domain/aggregate/field SIDs. The legacy view has
|
|
182
|
-
* already forgotten removals, so only currently present entities can be imported;
|
|
183
|
-
* from this point onward the returned registry retains every retirement.
|
|
184
|
-
*/
|
|
185
|
-
export function identityRegistryFromStableIdLockfile(
|
|
186
|
-
lock: StableIdLockfile | undefined,
|
|
187
|
-
): LawIdentityRegistry {
|
|
188
|
-
if (lock?.identity !== undefined) {
|
|
189
|
-
validateLawIdentityRegistry(lock.identity);
|
|
190
|
-
return lock.identity;
|
|
191
|
-
}
|
|
192
|
-
if (lock === undefined) return emptyLawIdentityRegistry();
|
|
193
|
-
|
|
194
|
-
const entities: Record<string, LawIdentityEntity> = {};
|
|
195
|
-
const aggregateOccurrences = new Map<string, number>();
|
|
196
|
-
for (const domain of Object.values(lock.domains)) {
|
|
197
|
-
for (const aggregate of Object.values(domain.stableIds.aggregates)) {
|
|
198
|
-
aggregateOccurrences.set(aggregate.sid, (aggregateOccurrences.get(aggregate.sid) ?? 0) + 1);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
for (const [domainName, domain] of Object.entries(lock.domains).sort(([a], [b]) =>
|
|
203
|
-
compareText(a, b),
|
|
204
|
-
)) {
|
|
205
|
-
const domainKey = importIdentityEntity(entities, {
|
|
206
|
-
kind: "domain",
|
|
207
|
-
sid: domain.stableIds.domain,
|
|
208
|
-
label: domainName,
|
|
209
|
-
generation: 0,
|
|
210
|
-
status: "active",
|
|
211
|
-
});
|
|
212
|
-
for (const [aggregateName, aggregate] of Object.entries(domain.stableIds.aggregates).sort(
|
|
213
|
-
([a], [b]) => compareText(a, b),
|
|
214
|
-
)) {
|
|
215
|
-
const parent =
|
|
216
|
-
(aggregateOccurrences.get(aggregate.sid) ?? 0) > 1 ||
|
|
217
|
-
aggregate.sid === mintGlobalAggregateSid(aggregateName)
|
|
218
|
-
? LAW_FRAMEWORK_PARENT
|
|
219
|
-
: domainKey;
|
|
220
|
-
const aggregateKey = importIdentityEntity(entities, {
|
|
221
|
-
kind: "aggregate",
|
|
222
|
-
sid: aggregate.sid,
|
|
223
|
-
parent,
|
|
224
|
-
label: aggregateName,
|
|
225
|
-
...(aggregate.was === undefined ? {} : { priorLabels: aggregate.was }),
|
|
226
|
-
generation: 0,
|
|
227
|
-
status: "active",
|
|
228
|
-
});
|
|
229
|
-
for (const [fieldName, field] of Object.entries(aggregate.fields).sort(([a], [b]) =>
|
|
230
|
-
compareText(a, b),
|
|
231
|
-
)) {
|
|
232
|
-
importIdentityEntity(entities, {
|
|
233
|
-
kind: "field",
|
|
234
|
-
sid: field.sid,
|
|
235
|
-
parent: aggregateKey,
|
|
236
|
-
label: fieldName,
|
|
237
|
-
...(field.was === undefined ? {} : { priorLabels: field.was }),
|
|
238
|
-
generation: 0,
|
|
239
|
-
status: "active",
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const registry: LawIdentityRegistry = {
|
|
246
|
-
v: 1,
|
|
247
|
-
entities: Object.fromEntries(Object.entries(entities).sort(([a], [b]) => compareText(a, b))),
|
|
248
|
-
};
|
|
249
|
-
validateLawIdentityRegistry(registry);
|
|
250
|
-
return registry;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function importIdentityEntity(
|
|
254
|
-
entities: Record<string, LawIdentityEntity>,
|
|
255
|
-
entity: LawIdentityEntity,
|
|
256
|
-
): string {
|
|
257
|
-
const key = lawEntityKey(entity.kind, entity.sid);
|
|
258
|
-
const prior = entities[key];
|
|
259
|
-
if (prior === undefined) {
|
|
260
|
-
entities[key] = entity;
|
|
261
|
-
return key;
|
|
262
|
-
}
|
|
263
|
-
if (
|
|
264
|
-
prior.kind !== entity.kind ||
|
|
265
|
-
prior.parent !== entity.parent ||
|
|
266
|
-
prior.label !== entity.label ||
|
|
267
|
-
JSON.stringify(prior.priorLabels ?? []) !== JSON.stringify(entity.priorLabels ?? [])
|
|
268
|
-
) {
|
|
269
|
-
throw new Error(
|
|
270
|
-
`NMS-ID-002 legacy SID ${key} aliases incompatible ${prior.kind} '${prior.label}' and ${entity.kind} '${entity.label}'`,
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
return key;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function compareText(a: string, b: string): number {
|
|
277
|
-
return a < b ? -1 : a > b ? 1 : 0;
|
|
278
|
-
}
|