@githolon/dsl 0.103.12 → 0.103.14
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 +5 -0
- package/dart/lib/src/runner/node-entry.mjs +244 -173
- package/dart/lib/src/runner/offline-install.mjs +87 -112
- package/dart/lib/src/session_bound.dart +7 -4
- package/dart/lib/src/webview_bridge.dart +13 -11
- package/dart/pubspec.yaml +1 -1
- package/dart/test/webview_bridge_test.dart +10 -7
- package/dart_types/CHANGELOG.md +2 -2
- package/dart_types/lib/nomos_types.dart +4 -4
- package/dart_types/lib/src/versioned_nomos_ref.dart +13 -11
- package/dart_types/pubspec.yaml +1 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +433 -239
- package/src/aggregate.ts +3 -1
- package/src/build_package.ts +77 -6
- package/src/check_package_main.ts +39 -16
- package/src/codegen_dart.ts +189 -52
- package/src/codegen_dart_application.ts +82 -16
- package/src/codegen_dart_client.ts +42 -14
- package/src/codegen_dot.ts +3 -3
- package/src/codegen_proof.ts +24 -2
- package/src/codegen_ts.ts +32 -19
- package/src/compile_package_main.ts +68 -11
- package/src/derived.ts +17 -0
- package/src/directive.ts +65 -0
- package/src/engine_entry.ts +22 -2
- package/src/enum_type.ts +71 -0
- package/src/fields.ts +135 -5
- package/src/flow_gate.ts +2 -1
- package/src/framework/workspaces.ts +73 -0
- package/src/idof_lint.ts +5 -1
- package/src/index.ts +20 -0
- package/src/kernel_capabilities.ts +12 -1
- package/src/law_check.ts +107 -28
- package/src/law_graph.ts +151 -20
- package/src/law_graph_usda.ts +23 -0
- package/src/law_value_type.ts +10 -3
- package/src/lsp_server.ts +26 -16
- package/src/package_config.ts +112 -9
- package/src/package_feature_topology.ts +174 -0
- package/src/plan_source_slice.ts +6 -0
- package/src/report.ts +6 -0
- package/src/stable_ids_for_module.ts +9 -4
- package/src/value_object.ts +9 -2
- package/src/workspace_sharding.ts +4 -4
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
|
|
5
|
+
import type { Finding } from "./law_check.js";
|
|
6
|
+
import {
|
|
7
|
+
featureDependenciesOf,
|
|
8
|
+
type NormalizedNomosPackageDomainConfig,
|
|
9
|
+
type NomosPackageFeatureConfig,
|
|
10
|
+
} from "./package_config.js";
|
|
11
|
+
|
|
12
|
+
export const MAX_PUBLISHED_FEATURE_MODULE_LINES = 800;
|
|
13
|
+
|
|
14
|
+
const GENERIC_LAW_BUCKET_BASENAMES = new Set([
|
|
15
|
+
"common.ts",
|
|
16
|
+
"helpers.ts",
|
|
17
|
+
"models.ts",
|
|
18
|
+
"types.ts",
|
|
19
|
+
"utils.ts",
|
|
20
|
+
"values.ts",
|
|
21
|
+
"workflow_values.ts",
|
|
22
|
+
"processing_values.ts",
|
|
23
|
+
"relation_values.ts",
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
export interface PackageFeatureTopologyOptions {
|
|
27
|
+
/** Internal fixture escape hatch. Tenant-facing compile/check/LSP never disable source guidance. */
|
|
28
|
+
readonly checkSourceShape?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface PackageFeatureTopologyDiagnostic {
|
|
32
|
+
readonly rule: "allow-large-file" | "allow-generic-module";
|
|
33
|
+
readonly domain: string;
|
|
34
|
+
readonly feature: string;
|
|
35
|
+
readonly source: string;
|
|
36
|
+
readonly message: string;
|
|
37
|
+
readonly fix: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function hasSourceAcknowledgement(
|
|
41
|
+
source: string,
|
|
42
|
+
rule: PackageFeatureTopologyDiagnostic["rule"],
|
|
43
|
+
): boolean {
|
|
44
|
+
const escapedRule = rule.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
45
|
+
return new RegExp(
|
|
46
|
+
`^\\s*//\\s*nomos-lint:\\s*${escapedRule}\\s*--\\s*\\S.{7,}$`,
|
|
47
|
+
"mu",
|
|
48
|
+
).test(source);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveConfiguredImport(
|
|
52
|
+
importer: string,
|
|
53
|
+
specifier: string,
|
|
54
|
+
configured: ReadonlySet<string>,
|
|
55
|
+
): string | undefined {
|
|
56
|
+
if (!specifier.startsWith(".")) return undefined;
|
|
57
|
+
const raw = path.resolve(path.dirname(importer), specifier);
|
|
58
|
+
const ext = path.extname(raw);
|
|
59
|
+
const candidates = [
|
|
60
|
+
raw,
|
|
61
|
+
...(ext === ".js" || ext === ".mjs" || ext === ".cjs"
|
|
62
|
+
? [raw.slice(0, -ext.length) + ".ts", raw.slice(0, -ext.length) + ".tsx"]
|
|
63
|
+
: []),
|
|
64
|
+
...(ext === "" ? [`${raw}.ts`, `${raw}.tsx`, path.join(raw, "index.ts")] : []),
|
|
65
|
+
];
|
|
66
|
+
return candidates.find((candidate) => configured.has(candidate));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Enforce the authored feature graph against the real TypeScript source graph. Merely putting files
|
|
71
|
+
* into directories is cosmetic; this check makes an undeclared sibling dependency a compiler error.
|
|
72
|
+
*/
|
|
73
|
+
export function assertPackageFeatureTopology(
|
|
74
|
+
domains: readonly NormalizedNomosPackageDomainConfig[],
|
|
75
|
+
configDirectory: string,
|
|
76
|
+
options: PackageFeatureTopologyOptions = {},
|
|
77
|
+
): readonly PackageFeatureTopologyDiagnostic[] {
|
|
78
|
+
const diagnostics: PackageFeatureTopologyDiagnostic[] = [];
|
|
79
|
+
const checkSourceShape = options.checkSourceShape !== false;
|
|
80
|
+
for (const domain of domains) {
|
|
81
|
+
const ownerByModule = new Map<string, NomosPackageFeatureConfig>();
|
|
82
|
+
for (const feature of domain.features) {
|
|
83
|
+
for (const relative of feature.modules) {
|
|
84
|
+
const absolute = path.resolve(configDirectory, relative);
|
|
85
|
+
const prior = ownerByModule.get(absolute);
|
|
86
|
+
if (prior !== undefined) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`domain '${domain.key}' source '${absolute}' resolves into both '${prior.key}' and ` +
|
|
89
|
+
`'${feature.key}'; every law source must have exactly one feature owner`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
ownerByModule.set(absolute, feature);
|
|
93
|
+
if (existsSync(absolute)) {
|
|
94
|
+
const source = readFileSync(absolute, "utf8");
|
|
95
|
+
const basename = path.basename(absolute).toLowerCase();
|
|
96
|
+
if (
|
|
97
|
+
checkSourceShape &&
|
|
98
|
+
GENERIC_LAW_BUCKET_BASENAMES.has(basename) &&
|
|
99
|
+
!hasSourceAcknowledgement(source, "allow-generic-module")
|
|
100
|
+
) {
|
|
101
|
+
diagnostics.push({
|
|
102
|
+
rule: "allow-generic-module",
|
|
103
|
+
domain: domain.key,
|
|
104
|
+
feature: feature.key,
|
|
105
|
+
source: relative,
|
|
106
|
+
message: `uses the generic bucket name '${basename}'`,
|
|
107
|
+
fix:
|
|
108
|
+
"give each domain value or workflow its own purpose-named source file, or acknowledge " +
|
|
109
|
+
"a cohesive exception in this file with " +
|
|
110
|
+
"`// nomos-lint: allow-generic-module -- <reason>`",
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
const lines = source.split(/\r?\n/u).length;
|
|
114
|
+
if (
|
|
115
|
+
checkSourceShape &&
|
|
116
|
+
lines > MAX_PUBLISHED_FEATURE_MODULE_LINES &&
|
|
117
|
+
!hasSourceAcknowledgement(source, "allow-large-file")
|
|
118
|
+
) {
|
|
119
|
+
diagnostics.push({
|
|
120
|
+
rule: "allow-large-file",
|
|
121
|
+
domain: domain.key,
|
|
122
|
+
feature: feature.key,
|
|
123
|
+
source: relative,
|
|
124
|
+
message: `is ${lines} lines`,
|
|
125
|
+
fix:
|
|
126
|
+
"split it around cohesive declarations so agents can load and change one purpose at " +
|
|
127
|
+
"a time, or acknowledge that the complexity belongs together in this file with " +
|
|
128
|
+
"`// nomos-lint: allow-large-file -- <reason>`",
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const configured = new Set(ownerByModule.keys());
|
|
135
|
+
for (const [modulePath, owner] of ownerByModule) {
|
|
136
|
+
if (!existsSync(modulePath)) continue;
|
|
137
|
+
const imports = ts.preProcessFile(readFileSync(modulePath, "utf8"), true, true).importedFiles;
|
|
138
|
+
for (const { fileName } of imports) {
|
|
139
|
+
const dependencyPath = resolveConfiguredImport(modulePath, fileName, configured);
|
|
140
|
+
if (dependencyPath === undefined) continue;
|
|
141
|
+
const dependencyOwner = ownerByModule.get(dependencyPath);
|
|
142
|
+
if (dependencyOwner === undefined || dependencyOwner === owner) continue;
|
|
143
|
+
if (featureDependenciesOf(owner).includes(dependencyOwner)) continue;
|
|
144
|
+
throw new Error(
|
|
145
|
+
`domain '${domain.key}' feature '${owner.key}' imports feature '${dependencyOwner.key}' via ` +
|
|
146
|
+
`'${path.relative(configDirectory, modulePath)}' -> ` +
|
|
147
|
+
`'${path.relative(configDirectory, dependencyPath)}' without declaring dependsOn: [` +
|
|
148
|
+
`${dependencyOwner.key}]. Import topology is law topology; declare the dependency or move ` +
|
|
149
|
+
"cross-feature behaviour into an explicit workflow feature.",
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return diagnostics;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Convert source topology diagnostics into the common check/LSP vocabulary without losing the file. */
|
|
158
|
+
export function packageFeatureTopologyFindings(
|
|
159
|
+
diagnostics: readonly PackageFeatureTopologyDiagnostic[],
|
|
160
|
+
configDirectory: string,
|
|
161
|
+
): readonly Finding[] {
|
|
162
|
+
return diagnostics.map((diagnostic) => ({
|
|
163
|
+
domain: diagnostic.domain,
|
|
164
|
+
directiveId: diagnostic.feature,
|
|
165
|
+
level: "warn" as const,
|
|
166
|
+
what: `feature '${diagnostic.feature}' source '${diagnostic.source}' ${diagnostic.message}`,
|
|
167
|
+
fix: diagnostic.fix,
|
|
168
|
+
source: {
|
|
169
|
+
file: path.resolve(configDirectory, diagnostic.source),
|
|
170
|
+
line: 1,
|
|
171
|
+
column: 1,
|
|
172
|
+
},
|
|
173
|
+
}));
|
|
174
|
+
}
|
package/src/plan_source_slice.ts
CHANGED
|
@@ -174,6 +174,12 @@ export class PlanSourceSliceProgram {
|
|
|
174
174
|
if (ts.isExportSpecifier(node)) {
|
|
175
175
|
markSymbol(this.#checker.getExportSpecifierLocalTargetSymbol(node));
|
|
176
176
|
}
|
|
177
|
+
// In `{ epochMs }`, `getSymbolAtLocation(epochMs)` is the synthetic property symbol,
|
|
178
|
+
// not necessarily the imported value binding. Follow the value side explicitly or the
|
|
179
|
+
// sliced module retains the shorthand use while pruning the import that initializes it.
|
|
180
|
+
if (ts.isShorthandPropertyAssignment(node)) {
|
|
181
|
+
markSymbol(this.#checker.getShorthandAssignmentValueSymbol(node));
|
|
182
|
+
}
|
|
177
183
|
if (ts.isIdentifier(node)) markSymbol(this.#checker.getSymbolAtLocation(node));
|
|
178
184
|
ts.forEachChild(node, visit);
|
|
179
185
|
};
|
package/src/report.ts
CHANGED
|
@@ -37,6 +37,12 @@ export function days(n: number): number {
|
|
|
37
37
|
export function hours(n: number): number {
|
|
38
38
|
return n * 60 * 60 * 1000;
|
|
39
39
|
}
|
|
40
|
+
export function minutes(n: number): number {
|
|
41
|
+
return n * 60 * 1000;
|
|
42
|
+
}
|
|
43
|
+
export function seconds(n: number): number {
|
|
44
|
+
return n * 1000;
|
|
45
|
+
}
|
|
40
46
|
|
|
41
47
|
import type { AggregateHandle } from "./aggregate.js";
|
|
42
48
|
import type { Field, FieldValue } from "./fields.js";
|
|
@@ -106,6 +106,7 @@ function fieldValueContractDetails(field: Field): { contract: string; shape: unk
|
|
|
106
106
|
...(field.nullSemantic !== undefined ? { nullSemantic: field.nullSemantic } : {}),
|
|
107
107
|
...(field.isClearable === true ? { clearable: true } : {}),
|
|
108
108
|
...(field.isEncrypted === true ? { encrypted: true } : {}),
|
|
109
|
+
...(field.isTimestamp === true ? { timestamp: "epoch-milliseconds" } : {}),
|
|
109
110
|
});
|
|
110
111
|
const contract = createHash("sha256")
|
|
111
112
|
.update(
|
|
@@ -343,11 +344,15 @@ function normalizeFieldContract(value: unknown, parentKey = ""): unknown {
|
|
|
343
344
|
return items;
|
|
344
345
|
}
|
|
345
346
|
if (value !== null && typeof value === "object") {
|
|
347
|
+
const record = value as Record<string, unknown>;
|
|
348
|
+
// `enumType(...)`'s exported-binding name (`bindEnumTypeExport`) rides the JSON Schema node
|
|
349
|
+
// as a cosmetic `name` purely for codegen/LSP labeling — it carries no business value and
|
|
350
|
+
// must never manufacture a false retype when a shared enum export is bound/renamed/moved.
|
|
351
|
+
const entries = record.type === "enum"
|
|
352
|
+
? Object.entries(record).filter(([key]) => key !== "name")
|
|
353
|
+
: Object.entries(record);
|
|
346
354
|
return Object.fromEntries(
|
|
347
|
-
|
|
348
|
-
key,
|
|
349
|
-
normalizeFieldContract(child, key),
|
|
350
|
-
]),
|
|
355
|
+
entries.map(([key, child]) => [key, normalizeFieldContract(child, key)]),
|
|
351
356
|
);
|
|
352
357
|
}
|
|
353
358
|
return value;
|
package/src/value_object.ts
CHANGED
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
*/
|
|
55
55
|
|
|
56
56
|
import { z } from "zod";
|
|
57
|
+
import { enumTypeNameOf } from "./enum_type.js";
|
|
57
58
|
|
|
58
59
|
import { isRuntimeLaw } from "./authoring_context.js";
|
|
59
60
|
import type { Driver } from "./drivers.js";
|
|
@@ -565,8 +566,14 @@ function describeZod(
|
|
|
565
566
|
}
|
|
566
567
|
case "boolean":
|
|
567
568
|
return { type: "boolean" };
|
|
568
|
-
case "enum":
|
|
569
|
-
|
|
569
|
+
case "enum": {
|
|
570
|
+
const name = enumTypeNameOf(schema);
|
|
571
|
+
return {
|
|
572
|
+
type: "enum",
|
|
573
|
+
values: zodEnumValues(schema),
|
|
574
|
+
...(name === undefined ? {} : { name }),
|
|
575
|
+
};
|
|
576
|
+
}
|
|
570
577
|
case "literal": {
|
|
571
578
|
const def = zodDef(schema) as { value?: unknown; values?: readonly unknown[] };
|
|
572
579
|
const v = def.value ?? def.values?.[0];
|
|
@@ -431,7 +431,7 @@ export const nomosReceiveAssignment = directive("nomosReceiveAssignment")
|
|
|
431
431
|
/**
|
|
432
432
|
* THE MOVE OBLIGATION (P6 — sharding orchestration as outbound offers, slice S1): one
|
|
433
433
|
* impure-capability-shaped row per (move, leg), driven through the obligation lifecycle the
|
|
434
|
-
* KERNEL already enforces (
|
|
434
|
+
* KERNEL already enforces (admission seals it into the typed outbox; `OrphanReceipt`/terminal-once
|
|
435
435
|
* gate its receipt). The coordinator EMITS the move as data; the bailiff delivers each leg + relays
|
|
436
436
|
* the shard's gate verdict as a receipt; each receipt mints the next leg (S2). It carries the
|
|
437
437
|
* `requested`-status lifecycle envelope (`scheduledAt`/`deadlineAt` are required ints — the reactor
|
|
@@ -440,7 +440,7 @@ export const nomosReceiveAssignment = directive("nomosReceiveAssignment")
|
|
|
440
440
|
* the move (the obligation is observable on the outbox, not yet consumed). `architecture/sharding_as_outbound_offers.md`.
|
|
441
441
|
*/
|
|
442
442
|
export const NomosMoveObligation = impureCapabilityAggregate("NomosMoveObligation", {
|
|
443
|
-
// the move-leg params (
|
|
443
|
+
// the move-leg params (sealed as typed USDA values in the outbound offer):
|
|
444
444
|
fromShard: t.string().merge(Lww),
|
|
445
445
|
toShard: t.string().merge(Lww),
|
|
446
446
|
homeKeys: t.json().merge(Lww), // the moving home keys (the leg's work list)
|
|
@@ -498,8 +498,8 @@ export const nomosSplitShard = directive("nomosSplitShard")
|
|
|
498
498
|
const newMapVersion = maxHeld + 1;
|
|
499
499
|
// P6 S1/S2 — mint the receipt-leg obligation (the standard obligation API: `create` mints the id; its
|
|
500
500
|
// ops are ambient-harvested + merged with these returned ops). status:'requested' + the lifecycle
|
|
501
|
-
// envelope ⇒
|
|
502
|
-
//
|
|
501
|
+
// envelope ⇒ admission seals a typed outbound offer; the move-leg params ride inside that offer.
|
|
502
|
+
// Re-entry is idempotent via the MOVE GATE (a double `nomosSplitShard` refuses
|
|
503
503
|
// `not-active:moving:*` before this runs). The host orchestration is UNCHANGED — observable, not consumed.
|
|
504
504
|
create(NomosMoveObligation)
|
|
505
505
|
.set("requestedBy", "coordinator")
|