@githolon/dsl 0.108.3 → 0.108.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.
Files changed (44) hide show
  1. package/dart/CHANGELOG.md +10 -0
  2. package/dart/lib/src/application_framework_calls.dart +21 -0
  3. package/dart/lib/src/dev_cloud.dart +67 -0
  4. package/dart/lib/src/nms_test_environment.dart +1 -1
  5. package/dart/lib/src/runner/node-entry.mjs +372 -106
  6. package/dart/lib/src/runner/offline-install.mjs +171 -11
  7. package/dart/lib/src/session_bound.dart +58 -30
  8. package/dart/lib/src/test_harness.dart +60 -98
  9. package/dart/lib/src/webview_bridge.dart +212 -32
  10. package/dart/pubspec.lock +1 -1
  11. package/dart/pubspec.yaml +2 -2
  12. package/dart/test/node_runner_transport_test.dart +5 -5
  13. package/dart/test/webview_bridge_test.dart +6 -0
  14. package/dart_types/CHANGELOG.md +4 -0
  15. package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +428 -2
  16. package/dart_types/pubspec.yaml +1 -1
  17. package/package.json +1 -1
  18. package/runtime/generated-application-host.js +314 -82
  19. package/src/application_topology.ts +120 -28
  20. package/src/births.ts +27 -0
  21. package/src/codegen_call_usda.ts +9 -4
  22. package/src/codegen_dart_application.ts +146 -37
  23. package/src/codegen_dart_client.ts +225 -160
  24. package/src/codegen_dart_framework_calls.ts +89 -4
  25. package/src/codegen_ts.ts +135 -60
  26. package/src/compile_package_main.ts +3544 -1460
  27. package/src/directive.ts +320 -118
  28. package/src/fields.ts +2 -3
  29. package/src/framework/application_home.ts +3 -0
  30. package/src/framework/auth_provider_law.ts +20 -16
  31. package/src/framework/genesis.ts +32 -1
  32. package/src/framework/home.ts +45 -8
  33. package/src/framework/signing_identity.ts +78 -0
  34. package/src/framework/workspaces.ts +9 -1
  35. package/src/framework_call_dart_enums.ts +33 -0
  36. package/src/framework_call_definitions.ts +11 -2
  37. package/src/index.ts +12 -0
  38. package/src/intent.ts +525 -194
  39. package/src/law_graph.ts +32 -4
  40. package/src/law_graph_usda.ts +3 -0
  41. package/src/package_config.ts +16 -1
  42. package/src/read.ts +33 -11
  43. package/src/wire.ts +1 -0
  44. package/src/wire_encode.ts +3 -3
package/src/law_graph.ts CHANGED
@@ -595,6 +595,8 @@ export interface LawIntentNode {
595
595
  readonly workspaceRelations: readonly LawStructuralRef<"workspace-relation">[];
596
596
  /** Exact signed parent-workspace effects may propose this call; authority is still `.requires`. */
597
597
  readonly acceptsWorkspaceEffect: boolean;
598
+ /** Recovery-only admission: the verified source workspace must be the target's exact birth parent. */
599
+ readonly acceptsVerifiedBirthParentRecovery: boolean;
598
600
  /** Client proof selected by law for this exact public offer. Absence retains the ordinary
599
601
  * session-credential posture. */
600
602
  readonly offerAuthenticationProof?: "enrolledDevice";
@@ -1484,6 +1486,8 @@ export function discoverLawGraph(
1484
1486
  foreignReads: [],
1485
1487
  workspaceRelations,
1486
1488
  acceptsWorkspaceEffect: intent.acceptsWorkspaceEffectGate === true,
1489
+ acceptsVerifiedBirthParentRecovery:
1490
+ intent.acceptsVerifiedBirthParentRecoveryGate === true,
1487
1491
  ...(isBusinessIntent(intent) && intent.offerAuthenticationProof !== undefined
1488
1492
  ? { offerAuthenticationProof: intent.offerAuthenticationProof }
1489
1493
  : {}),
@@ -1672,6 +1676,8 @@ export function discoverLawGraph(
1672
1676
  relation.label,
1673
1677
  namespacedRelationTypes,
1674
1678
  domainKey,
1679
+ relation.global,
1680
+ relation.authoredObjectType,
1675
1681
  ),
1676
1682
  ] as const));
1677
1683
  const relationPredicateNodes = new Map<LawStructuralSid<"relation-predicate">, LawRelationPredicateNode>();
@@ -1704,7 +1710,10 @@ export function discoverLawGraph(
1704
1710
  for (const [index, intentNode] of intentNodes.entries()) {
1705
1711
  const declaration = intents.find((intent) => intent.id === intentNode.entity.label)!;
1706
1712
  const gate = declaration.requiresRelationGate;
1707
- if (declaration.acceptsWorkspaceEffectGate === true) {
1713
+ if (
1714
+ declaration.acceptsWorkspaceEffectGate === true &&
1715
+ declaration.acceptsVerifiedBirthParentRecoveryGate !== true
1716
+ ) {
1708
1717
  if (gate === undefined || gate.object !== "workspace:self" || gate.objectFrom !== undefined) {
1709
1718
  fail(
1710
1719
  `intent '${declaration.id}' .acceptsWorkspaceEffect() requires a static ` +
@@ -1752,7 +1761,12 @@ export function discoverLawGraph(
1752
1761
  ...intentNode,
1753
1762
  authorization: {
1754
1763
  relation: candidates[0] === undefined
1755
- ? { kind: "dependency-export", entityKind: "relation", label: exactLabel }
1764
+ ? {
1765
+ kind: "dependency-export",
1766
+ entityKind: "relation",
1767
+ label: exactLabel,
1768
+ domain: module.name,
1769
+ }
1756
1770
  : ref(candidates[0].entity),
1757
1771
  object: authorizationObject,
1758
1772
  ...(objectFrom === undefined ? {} : { objectFrom }),
@@ -1771,7 +1785,12 @@ export function discoverLawGraph(
1771
1785
  fail(`aggregate '${aggregate.id}' read relation '${relationName}' is ambiguous across relation types`);
1772
1786
  }
1773
1787
  const readPolicy: LawTargetRef<"relation" | "policy"> = candidates[0] === undefined
1774
- ? { kind: "dependency-export", entityKind: "relation", label: relationLabel }
1788
+ ? {
1789
+ kind: "dependency-export",
1790
+ entityKind: "relation",
1791
+ label: relationLabel,
1792
+ domain: module.name,
1793
+ }
1775
1794
  : ref(candidates[0].entity);
1776
1795
  const entry = aggregateByLabel.get(aggregate.id)!;
1777
1796
  const updated = { ...entry.node, readPolicy };
@@ -3030,10 +3049,19 @@ function linkRelationAllowed(
3030
3049
  owner: string,
3031
3050
  namespacedTypes: ReadonlySet<string>,
3032
3051
  domainKey: string,
3052
+ ownerIsGlobal: boolean,
3053
+ ownerObjectType: string,
3033
3054
  ): LawAllowedSubject[] {
3034
3055
  return sortStructuralValues(allowed.map((subject): LawAllowedSubject => {
3035
3056
  if (subject.kind === "wildcard") return { kind: "wildcard" };
3036
- const subjectType = namespacedTypes.has(subject.type)
3057
+ // A global relation over `workspace` may coexist with application-local
3058
+ // `workspace` relations in the same domain. Its self-typed subjects are the
3059
+ // same global object class, not the coincidentally named local class.
3060
+ // Otherwise `workspace:self#child@workspace:<child>` compiles as allowing
3061
+ // only `<domain>__workspace:*`, making the correctly proven workspace actor
3062
+ // impossible to authorize.
3063
+ const subjectType = namespacedTypes.has(subject.type) &&
3064
+ !(ownerIsGlobal && subject.type === ownerObjectType)
3037
3065
  ? qualifyRelationType(domainKey, subject.type)
3038
3066
  : subject.type;
3039
3067
  if (subject.kind === "direct") return { kind: "direct", type: subjectType };
@@ -986,6 +986,9 @@ function emitIntent(
986
986
  ...(intent.acceptsWorkspaceEffect
987
987
  ? [`${pad}custom uniform bool nomos:acceptsWorkspaceEffect = true`]
988
988
  : []),
989
+ ...(intent.acceptsVerifiedBirthParentRecovery
990
+ ? [`${pad}custom uniform token nomos:workspaceEffectAdmission = "VerifiedBirthParentRecovery"`]
991
+ : []),
989
992
  ...(intent.offerAuthenticationProof === "enrolledDevice"
990
993
  ? [`${pad}custom uniform token nomos:offerAuthenticationProof = "EnrolledDevice"`]
991
994
  : []),
@@ -8,6 +8,7 @@ import {
8
8
  isApplicationHomeGenesis,
9
9
  type ApplicationHomeGenesis,
10
10
  } from "./application_home_genesis.js";
11
+ import type { WorkspaceRole } from "./births.js";
11
12
 
12
13
  /**
13
14
  * One cohesive, vertically sliced business capability inside a domain.
@@ -73,6 +74,9 @@ export interface NomosPackageConfig {
73
74
  * Required for a published application: Nomos never invents a child-workspace allowance. */
74
75
  readonly application?: {
75
76
  readonly maxChildWorkspacesPerUser: number;
77
+ /** Target-owned roles granted at genesis to the cryptographically verified platform workspace.
78
+ * These are law constants, not caller or host input. */
79
+ readonly parentWorkspaceRoles?: readonly WorkspaceRole[];
76
80
  /** Tenant law which must hold from the application's first usable home state. Every entry is
77
81
  * an ordinary offer executed through the child's gate; omission declares no tenant ceremony. */
78
82
  readonly homeGenesis?: ApplicationHomeGenesis;
@@ -221,7 +225,18 @@ export function validateNomosPackageConfig(value: unknown): {
221
225
  ) {
222
226
  throw new Error(
223
227
  "application.homeGenesis must be declared with applicationHomeGenesis(...); " +
224
- "Nomos will not reinterpret an untyped genesis recipe",
228
+ "Nomos will not reinterpret an untyped genesis recipe",
229
+ );
230
+ }
231
+ if (
232
+ config.application.parentWorkspaceRoles !== undefined &&
233
+ (!Array.isArray(config.application.parentWorkspaceRoles) ||
234
+ config.application.parentWorkspaceRoles.some(
235
+ (role) => typeof role !== "string" || !/^[A-Za-z][A-Za-z0-9_-]*$/.test(role),
236
+ ))
237
+ ) {
238
+ throw new Error(
239
+ "application.parentWorkspaceRoles must contain workspaceRole(...) declarations",
225
240
  );
226
241
  }
227
242
  }
package/src/read.ts CHANGED
@@ -112,9 +112,9 @@ type ProjectionShape<F extends Record<string, Field>> = {
112
112
 
113
113
  /**
114
114
  * Decode one framework query's standard projected rows from the SAME aggregate field declarations
115
- * that authored its law. The only type assertion is at the TypeScript `Object.fromEntries` seam:
116
- * every runtime member is still obtained from `Field.zod`, so there is no second field-name/type list
117
- * and no unvalidated object crosses into a framework plan.
115
+ * that authored its law. Every runtime member is obtained from `Field.zod`, so there is no second
116
+ * field-name/type list and no unvalidated object crosses into a framework plan. Historical bool
117
+ * strings are lifted before that one generated schema validates the current projection.
118
118
  *
119
119
  * Historical captured reads may contain either the current `{ type, id, data }` projection envelope
120
120
  * or the earlier flat data row. Both pass through the identical derived schema before being returned.
@@ -133,18 +133,40 @@ export function readFrameworkDeclaredRows<
133
133
  );
134
134
  }
135
135
  const fields: Readonly<Record<string, Field>> = aggregate.fields;
136
- const shape = Object.fromEntries(
137
- Object.entries(fields).map(([name, field]) => [name, field.zod.optional()]),
138
- ) as ProjectionShape<StoredFields<F>>;
139
- const data = z.object(shape).strict() as z.ZodType<ReadModelRow<F>>;
140
- // Recognize the envelope before validating its data. If the payload is malformed, validation must
141
- // report the actual aggregate field (for example a historical `keys` member), not fall through and
142
- // misdiagnose the envelope's `type`/`id` metadata as flat-row fields.
136
+ const shape: Record<string, z.ZodTypeAny> = {};
137
+ for (const name in fields) {
138
+ const field = fields[name]!;
139
+ const schema = field.zod;
140
+ if (typeof schema?.optional !== "function") {
141
+ throw new Error(
142
+ `framework read('${query.id}') field '${aggregate.id}.${name}' has no executable schema`,
143
+ );
144
+ }
145
+ shape[name] = schema.optional();
146
+ }
147
+ // A workspace is folded under the law carried by each historical intent, while a read is
148
+ // interpreted by the law that is current now. Retired fields therefore remain honest custody
149
+ // evidence but are not members of the current nominal projection. Strip those historical
150
+ // members at this typed boundary; validating them against today's schema would retroactively
151
+ // apply new law to old state and make ordinary evolution impossible.
152
+ const data = z.object(shape as ProjectionShape<StoredFields<F>>).strip() as z.ZodType<ReadModelRow<F>>;
153
+ // Recognize the envelope before validating its data. Known current members remain fully typed;
154
+ // historical members are deliberately outside the current projection above.
143
155
  const envelope = z.object({ data: z.unknown() }).passthrough();
144
156
  const row = z.union([envelope, z.unknown()])
145
157
  .transform((value) => {
146
158
  const wrapped = envelope.safeParse(value);
147
- return wrapped.success ? wrapped.data.data : value;
159
+ const projected = wrapped.success ? wrapped.data.data : value;
160
+ if (projected === null || typeof projected !== "object" || Array.isArray(projected)) {
161
+ return projected;
162
+ }
163
+ const lifted = { ...(projected as Record<string, unknown>) };
164
+ for (const name in fields) {
165
+ if (fields[name]?.kind !== "bool") continue;
166
+ if (lifted[name] === "true") lifted[name] = true;
167
+ else if (lifted[name] === "false") lifted[name] = false;
168
+ }
169
+ return lifted;
148
170
  })
149
171
  .pipe(data);
150
172
  return z.array(row).parse(readFrameworkDeclared(query.id, args));
package/src/wire.ts CHANGED
@@ -36,6 +36,7 @@ export interface WireHlc {
36
36
  * unit-variant bare string `"Git" | "External"`. */
37
37
  export type WireValue =
38
38
  | { Str: string }
39
+ | { Bool: boolean }
39
40
  | { NomosRef: { workspace: string; type: string; id: string } }
40
41
  | {
41
42
  VersionedNomosRef: {
@@ -229,7 +229,7 @@ function encodeKernelValue(field: Field | undefined, value: unknown, op?: FieldO
229
229
  // a map's entry value is a leaf scalar.
230
230
  if (typeof value === "number" && Number.isInteger(value)) return { Int: value };
231
231
  if (typeof value === "string") return { Str: value };
232
- if (typeof value === "boolean") return { Str: String(value) };
232
+ if (typeof value === "boolean") return { Bool: value };
233
233
  // Enum values are strings; refs are id strings — both handled above.
234
234
  // NAMED diagnostic (never `field '?'`): identify the aggregate + field + the shape we got, and — when the
235
235
  // field metadata was UNAVAILABLE (a sibling write whose target schema didn't reach the encoder) — say so,
@@ -249,8 +249,8 @@ function encodeKernelValue(field: Field | undefined, value: unknown, op?: FieldO
249
249
  const kindLabel = field.kind === "map" ? `map (${field.mapValueKind ?? "?"}-valued)` : field.kind;
250
250
  throw new Error(
251
251
  `${aggName}.${fieldName} is a '${kindLabel}' field${expected ? ` (value object ${expected})` : ""}, ` +
252
- `but got ${describeShape(value)} (${JSON.stringify(value)}) — kernel scalars are Str | Int only. ` +
253
- `A structured value needs a json/value-object field; a set/map needs its additive write.`,
252
+ `but got ${describeShape(value)} (${JSON.stringify(value)}) — kernel scalars are Str | Bool | Int. ` +
253
+ `A structured value needs a declared value-object field; a set/map needs its additive write.`,
254
254
  );
255
255
  }
256
256