@githolon/dsl 0.103.11 → 0.103.13

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.
Files changed (43) hide show
  1. package/dart/CHANGELOG.md +10 -0
  2. package/dart/lib/src/runner/node-entry.mjs +234 -170
  3. package/dart/lib/src/runner/offline-install.mjs +111 -123
  4. package/dart/lib/src/webview_bridge.dart +11 -11
  5. package/dart/pubspec.yaml +1 -1
  6. package/dart/test/webview_bridge_test.dart +10 -7
  7. package/dart_types/CHANGELOG.md +1 -1
  8. package/dart_types/lib/nomos_types.dart +4 -4
  9. package/dart_types/lib/src/versioned_nomos_ref.dart +13 -11
  10. package/package.json +1 -1
  11. package/runtime/generated-application-host.js +424 -237
  12. package/src/aggregate.ts +3 -1
  13. package/src/build_package.ts +77 -6
  14. package/src/check_package_main.ts +39 -16
  15. package/src/codegen_dart.ts +185 -48
  16. package/src/codegen_dart_application.ts +82 -16
  17. package/src/codegen_dart_client.ts +38 -10
  18. package/src/codegen_dot.ts +3 -3
  19. package/src/codegen_proof.ts +24 -2
  20. package/src/codegen_ts.ts +32 -19
  21. package/src/compile_package_main.ts +68 -11
  22. package/src/derived.ts +17 -0
  23. package/src/directive.ts +65 -0
  24. package/src/engine_entry.ts +22 -2
  25. package/src/enum_type.ts +71 -0
  26. package/src/fields.ts +135 -5
  27. package/src/flow_gate.ts +2 -1
  28. package/src/framework/workspaces.ts +73 -0
  29. package/src/idof_lint.ts +5 -1
  30. package/src/index.ts +20 -0
  31. package/src/kernel_capabilities.ts +12 -1
  32. package/src/law_check.ts +107 -28
  33. package/src/law_graph.ts +151 -20
  34. package/src/law_graph_usda.ts +23 -0
  35. package/src/law_value_type.ts +10 -3
  36. package/src/lsp_server.ts +26 -16
  37. package/src/package_config.ts +112 -9
  38. package/src/package_feature_topology.ts +174 -0
  39. package/src/plan_source_slice.ts +6 -0
  40. package/src/report.ts +6 -0
  41. package/src/stable_ids_for_module.ts +1 -0
  42. package/src/value_object.ts +9 -2
  43. 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
+ }
@@ -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(
@@ -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
- return { type: "enum", values: zodEnumValues(schema) };
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 (`scan_requested` emits it on the outbox; `OrphanReceipt`/terminal-once
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 (the non-envelope fields scan_requested returns as the order's `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 ⇒ the outbox (`scan_requested`) surfaces it as an outbound offer; the move-leg params ride
502
- // as the order's params. Re-entry is idempotent via the MOVE GATE (a double `nomosSplitShard` refuses
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")