@githolon/dsl 0.100.3 → 0.100.4

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 CHANGED
@@ -1,3 +1,9 @@
1
+ ## 0.62.1
2
+
3
+ - Align the public Dart documentation with the one Flutter application entry point: identity comes from the
4
+ token passed to `NomosScope.home(application:, authToken:)`; application code no longer supplies a subject
5
+ or device-token callback.
6
+
1
7
  ## 0.62.0
2
8
 
3
9
  - Generated business clients expose one `birth(payload)` method. The bound session supplies its verified
@@ -10,7 +10,7 @@ import 'package:crypto/crypto.dart';
10
10
  /// every peer and on the cloud. Use it directly as the `workspace`:
11
11
  ///
12
12
  /// ```dart
13
- /// NomosScope(workspace: homeNameOf('user:$firebaseUid'), ...)
13
+ /// NomosScope.home(application: 'my-app', authToken: idToken, ...)
14
14
  /// ```
15
15
  ///
16
16
  /// Formula: `home-` + the first 40 hex chars of `sha256(utf8(subject))`.
@@ -1895,8 +1895,7 @@ class NomosBridge {
1895
1895
 
1896
1896
  /// Derive the device key's base64 PUBLIC half from its base64 SECRET seed, inside the LOCAL kernel
1897
1897
  /// (the secret never leaves the device). The recovery lanes use this when only the secret was
1898
- /// persisted (the device-key store keeps just the seed) — e.g. to mint a device-BOUND IdP token
1899
- /// (`authTokenForDevice(publicKey)`) for the keyless re-enroll lane.
1898
+ /// persisted (the device-key store keeps just the seed), including low-level device-bound enrollment.
1900
1899
  Future<String> devicePublicOf(String secret) async =>
1901
1900
  (_asMap(await _call('devicePublicOf', {'secret': secret}))['public'] ?? '')
1902
1901
  .toString();
package/dart/pubspec.yaml CHANGED
@@ -11,7 +11,7 @@ description: >-
11
11
  Nomos 2 Dart frontend types — the hand-written kernel wire types plus the
12
12
  code-generated per-domain aggregate + directive-payload types. (De)serializes
13
13
  the EXACT serde JSON the Rust kernel + projection produce/consume.
14
- version: 0.62.0
14
+ version: 0.62.1
15
15
 
16
16
  environment:
17
17
  sdk: ^3.5.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@githolon/dsl",
3
- "version": "0.100.3",
3
+ "version": "0.100.4",
4
4
  "type": "module",
5
5
  "description": "Nomos 2 domain-authoring DSL: aggregates + directives in TS, executed and encoded to the Rust kernel's wire shapes.",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -50,11 +50,18 @@ export interface OpenApiDoc {
50
50
  openapi: "3.1.0";
51
51
  info: Record<string, unknown>;
52
52
  paths: Record<string, Record<string, unknown>>;
53
+ /**
54
+ * Law-derived routes needed by Nomos itself (device seating, provenance, recovery, ...).
55
+ * The host consumes this only on its internal describe path and strips it from public OpenAPI.
56
+ */
57
+ "x-nomos-framework-routes"?: { paths: Record<string, Record<string, unknown>> };
53
58
  }
54
59
 
55
60
  export interface OpenApiFragmentOptions {
56
61
  /** The content hash of the installed law (the spec's version + ingest cache key). */
57
62
  lawHash?: string;
63
+ /** Include the private framework router manifest used by the Nomos host. Public codegen omits it. */
64
+ includeFrameworkRoutes?: boolean;
58
65
  }
59
66
 
60
67
  /** Lower a directive's payload zod to JSON Schema; a non-representable schema falls back to a
@@ -95,22 +102,10 @@ function domainKey(module: DomainModule): string {
95
102
  * the merge namespaces any cross-domain collision. The offer/query distinction rides on the HTTP
96
103
  * method (POST/GET) AND the authoritative `x-nomos.kind` — the host reads the latter, never the name.
97
104
  */
98
- export function generateOpenApiFragment(
105
+ function generateOpenApiSurface(
99
106
  module: DomainModule,
100
107
  opts: OpenApiFragmentOptions = {},
101
108
  ): OpenApiDoc {
102
- module = {
103
- ...module,
104
- aggregates: module.aggregates.filter(
105
- (a) => (a as { businessBirthInternal?: unknown }).businessBirthInternal !== true,
106
- ),
107
- directives: module.directives.filter(
108
- (d) => (d as { businessBirthInternal?: unknown }).businessBirthInternal !== true,
109
- ),
110
- ...(module.queries === undefined
111
- ? {}
112
- : { queries: module.queries.filter((q) => (q as { businessBirthInternal?: unknown }).businessBirthInternal !== true) }),
113
- };
114
109
  const domain = domainKey(module);
115
110
  const paths: Record<string, Record<string, unknown>> = {};
116
111
 
@@ -123,7 +118,6 @@ export function generateOpenApiFragment(
123
118
 
124
119
  // OFFERABLE INTENTS → POST offers. The payload JSON Schema is the intent's own zod, lowered.
125
120
  for (const dir of module.directives as AnyDirective[]) {
126
- if ((dir as { businessBirthInternal?: unknown }).businessBirthInternal === true) continue;
127
121
  const businessBirth = (dir as {
128
122
  businessBirth?: { aggregateId: string; publicPayloadSchema: unknown };
129
123
  }).businessBirth;
@@ -247,11 +241,79 @@ export function generateOpenApiFragment(
247
241
  };
248
242
  }
249
243
 
244
+ /**
245
+ * Generate the developer-facing OpenAPI plus, only when requested by the runtime, a separate
246
+ * framework routing manifest. The split is deliberate: filtering framework declarations out of
247
+ * `paths` is presentation; it must never make Nomos's own law declarations unroutable.
248
+ */
249
+ export function generateOpenApiFragment(
250
+ module: DomainModule,
251
+ opts: OpenApiFragmentOptions = {},
252
+ ): OpenApiDoc {
253
+ const isInternal = (value: unknown) =>
254
+ !!value && typeof value === "object"
255
+ && (value as { businessBirthInternal?: unknown }).businessBirthInternal === true;
256
+ const publicModule: DomainModule = {
257
+ ...module,
258
+ aggregates: module.aggregates.filter((a) => !isInternal(a)),
259
+ directives: module.directives.filter((d) => !isInternal(d)),
260
+ ...(module.queries === undefined ? {} : { queries: module.queries.filter((q) => !isInternal(q)) }),
261
+ };
262
+ const publicDoc = generateOpenApiSurface(publicModule, opts);
263
+ if (opts.includeFrameworkRoutes !== true) return publicDoc;
264
+
265
+ const frameworkModule: DomainModule = {
266
+ ...module,
267
+ aggregates: module.aggregates.filter(isInternal),
268
+ directives: module.directives.filter(isInternal),
269
+ ...(module.queries === undefined ? {} : { queries: module.queries.filter(isInternal) }),
270
+ // Framework routing is for declared internal offers/queries. Business maintained reads remain
271
+ // on the public module; do not duplicate them into the private table.
272
+ counts: [],
273
+ sums: [],
274
+ mins: [],
275
+ maxes: [],
276
+ spatials: [],
277
+ };
278
+ const frameworkDoc = generateOpenApiSurface(frameworkModule, opts);
279
+ if (Object.keys(frameworkDoc.paths).length === 0) return publicDoc;
280
+ return {
281
+ ...publicDoc,
282
+ "x-nomos-framework-routes": { paths: frameworkDoc.paths },
283
+ };
284
+ }
285
+
250
286
  /** Stable JSON of an operation, to detect a genuine duplicate vs a real collision in the merge. */
251
287
  function operationKey(op: unknown): string {
252
288
  return JSON.stringify(op);
253
289
  }
254
290
 
291
+ function mergePathsInto(
292
+ paths: Record<string, Record<string, unknown>>,
293
+ incoming: Record<string, Record<string, unknown>>,
294
+ ): void {
295
+ for (const [path, methods] of Object.entries(incoming)) {
296
+ for (const [method, op] of Object.entries(methods)) {
297
+ const existing = paths[path]?.[method];
298
+ if (existing === undefined) {
299
+ (paths[path] ??= {})[method] = op;
300
+ } else if (operationKey(existing) === operationKey(op)) {
301
+ // identical (a framework operation shared by many domains) — keep the one entry.
302
+ } else {
303
+ // genuine collision: two domains, same path+method, different operations. Namespace BOTH
304
+ // under their domain so neither wins silently.
305
+ const domOf = (o: unknown): string =>
306
+ String((o as { "x-nomos"?: { domain?: string } })?.["x-nomos"]?.domain ?? "domain");
307
+ const bucket = paths[path]!;
308
+ (paths[`/${domOf(existing)}${path}`] ??= {})[method] = existing;
309
+ (paths[`/${domOf(op)}${path}`] ??= {})[method] = op;
310
+ delete bucket[method];
311
+ if (Object.keys(bucket).length === 0) delete paths[path];
312
+ }
313
+ }
314
+ }
315
+ }
316
+
255
317
  /**
256
318
  * mergeOpenApiFragments — the WORKSPACE spec = the union of its installed domains' fragments. The
257
319
  * runtime step (driven by `currentLaw`), kept here as a pure, law-blind function:
@@ -266,27 +328,10 @@ export function mergeOpenApiFragments(
266
328
  opts: { title?: string; lawHash?: string } = {},
267
329
  ): OpenApiDoc {
268
330
  const paths: Record<string, Record<string, unknown>> = {};
331
+ const frameworkPaths: Record<string, Record<string, unknown>> = {};
269
332
  for (const frag of fragments) {
270
- for (const [path, methods] of Object.entries(frag.paths)) {
271
- for (const [method, op] of Object.entries(methods)) {
272
- const existing = paths[path]?.[method];
273
- if (existing === undefined) {
274
- (paths[path] ??= {})[method] = op;
275
- } else if (operationKey(existing) === operationKey(op)) {
276
- // identical (a framework operation shared by many domains) — keep the one entry.
277
- } else {
278
- // genuine collision: two domains, same path+method, different operations. Namespace BOTH
279
- // under their domain so neither wins silently.
280
- const domOf = (o: unknown): string =>
281
- String((o as { "x-nomos"?: { domain?: string } })?.["x-nomos"]?.domain ?? "domain");
282
- const bucket = paths[path]!;
283
- (paths[`/${domOf(existing)}${path}`] ??= {})[method] = existing;
284
- (paths[`/${domOf(op)}${path}`] ??= {})[method] = op;
285
- delete bucket[method];
286
- if (Object.keys(bucket).length === 0) delete paths[path];
287
- }
288
- }
289
- }
333
+ mergePathsInto(paths, frag.paths);
334
+ mergePathsInto(frameworkPaths, frag["x-nomos-framework-routes"]?.paths ?? {});
290
335
  }
291
336
  return {
292
337
  openapi: "3.1.0",
@@ -296,5 +341,8 @@ export function mergeOpenApiFragments(
296
341
  ...(opts.lawHash ? { "x-nomos-law-hash": opts.lawHash } : {}),
297
342
  },
298
343
  paths,
344
+ ...(Object.keys(frameworkPaths).length > 0
345
+ ? { "x-nomos-framework-routes": { paths: frameworkPaths } }
346
+ : {}),
299
347
  };
300
348
  }
@@ -931,11 +931,9 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
931
931
  * THE SELF-DESCRIBE VERB (G1) — the lump's OWN OpenAPI, derived from the SAME modules it
932
932
  * dispatches. The kernel runs this over the FOLDED law (the lump IS `nomos:executable:
933
933
  * javascriptHex` in the package USDA in custody), so the workspace's API surface is a pure,
934
- * deterministic function of installed law — NOT a host-staged blob. It reproduces, byte for
935
- * byte, the `<name>.openapi.json` the compiler wrote: per domain (declaration order) build the
936
- * openapi-relevant DomainModule (directives + aggregates from the live exports; queries/counts/
937
- * sums/mins/maxes/spatials/workspaceInvariants from `config.openapiDecls`), `generateOpenApi
938
- * Fragment`, then `mergeOpenApiFragments` — the EXACT calls compile_package_main makes.
934
+ * deterministic function of installed law — NOT a host-staged blob. Its public `paths` reproduce
935
+ * the `<name>.openapi.json` the compiler wrote; describe also carries a private, law-derived
936
+ * framework routing manifest which the host consumes internally and never publishes.
939
937
  * `opts.lawHash`/`opts.title` are injected by the kernel at describe-time (the version stamp +
940
938
  * package title), matching the compiler's `domainHash`/`"<name> (nomos package)"`.
941
939
  */
@@ -969,7 +967,10 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
969
967
  ? { workspaceInvariants: [...decls.workspaceInvariants] }
970
968
  : {}),
971
969
  } as unknown as DomainModule;
972
- fragments.push(generateOpenApiFragment(module, lawHash !== undefined ? { lawHash } : {}));
970
+ fragments.push(generateOpenApiFragment(module, {
971
+ ...(lawHash !== undefined ? { lawHash } : {}),
972
+ includeFrameworkRoutes: true,
973
+ }));
973
974
  }
974
975
  const title = opts?.title ?? config.packageTitle;
975
976
  return mergeOpenApiFragments(fragments, {