@githolon/dsl 0.102.4 → 0.103.0

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 (131) hide show
  1. package/compile_package.mjs +43 -2
  2. package/compile_proof.d.mts +47 -0
  3. package/compile_proof.mjs +343 -0
  4. package/dart/CHANGELOG.md +7 -0
  5. package/dart/lib/src/dev_cloud.dart +8 -8
  6. package/dart/lib/src/nms_generated_runtime.dart +2 -0
  7. package/dart/lib/src/runner/node-entry.mjs +5504 -1236
  8. package/dart/lib/src/runner/offline-install.mjs +4578 -528
  9. package/dart/lib/src/test_harness.dart +206 -152
  10. package/dart/lib/src/webview_bridge.dart +196 -89
  11. package/dart/pubspec.lock +1 -1
  12. package/dart/pubspec.yaml +2 -2
  13. package/dart/test/acquire_session_credentials_test.dart +4 -3
  14. package/dart/test/adv_realm_interference_test.dart +7 -6
  15. package/dart/test/cloud_bound_topology_test.dart +3 -3
  16. package/dart/test/cluster_births_test.dart +4 -3
  17. package/dart/test/cluster_device_keyed_acceptance_test.dart +10 -16
  18. package/dart/test/cluster_local_autosync_test.dart +3 -2
  19. package/dart/test/cluster_scale_test.dart +3 -2
  20. package/dart/test/dlq_session_surface_test.dart +7 -6
  21. package/dart/test/first_app_gauntlet_test.dart +14 -21
  22. package/dart/test/fixtures/wedge/fake-install.mjs +1 -1
  23. package/dart/test/four_layer_nested_topology_test.dart +4 -4
  24. package/dart/test/harness_phase_timeout_test.dart +2 -2
  25. package/dart/test/local_sync_inert_test.dart +1 -1
  26. package/dart/test/parent_signer_recovery_test.dart +2 -2
  27. package/dart/test/sealing_client_test.dart +4 -3
  28. package/dart/test/session_bound_test.dart +8 -7
  29. package/dart/test/test_harness_test.dart +13 -12
  30. package/dart/test/vo_read_typing_test.dart +4 -3
  31. package/dart/test/watch_after_offer_stall_test.dart +4 -3
  32. package/dart/test/webview_bridge_test.dart +102 -5
  33. package/dart_types/CHANGELOG.md +5 -0
  34. package/dart_types/lib/nomos_types.dart +9 -0
  35. package/dart_types/lib/src/nomos_ref.dart +20 -0
  36. package/dart_types/lib/src/optional_ref.dart +85 -0
  37. package/dart_types/lib/src/sha256_digest.dart +41 -0
  38. package/dart_types/lib/src/versioned_nomos_ref.dart +81 -0
  39. package/dart_types/pubspec.yaml +1 -1
  40. package/dart_types/test/optional_ref_test.dart +44 -0
  41. package/dart_types/test/sha256_digest_test.dart +18 -0
  42. package/package.json +9 -5
  43. package/runtime/generated-application-host.js +5365 -1105
  44. package/src/aggregate.ts +18 -0
  45. package/src/application_composition.ts +6 -32
  46. package/src/attested_core.ts +6 -3
  47. package/src/birth_meter.ts +1 -0
  48. package/src/birth_meter_ensure.ts +28 -2
  49. package/src/births.ts +1 -1
  50. package/src/build_package.ts +162 -538
  51. package/src/business_birth.ts +19 -3
  52. package/src/canonical_json.ts +21 -0
  53. package/src/check_package_main.ts +76 -35
  54. package/src/clear_directive.ts +19 -8
  55. package/src/codegen_dart.ts +305 -22
  56. package/src/codegen_dart_application.ts +844 -292
  57. package/src/codegen_dart_client.ts +1084 -407
  58. package/src/codegen_dart_scenario.ts +12 -12
  59. package/src/codegen_proof.ts +169 -39
  60. package/src/codegen_provider_dart.ts +17 -1
  61. package/src/codegen_ts.ts +298 -106
  62. package/src/compat_manifest.ts +2 -2
  63. package/src/compat_package_main.ts +8 -12
  64. package/src/compile_package_main.ts +1866 -597
  65. package/src/compose.ts +1 -1
  66. package/src/directive.ts +208 -15
  67. package/src/engine_entry.ts +165 -111
  68. package/src/evolve_preview.ts +43 -81
  69. package/src/extremum.ts +1 -1
  70. package/src/fields.ts +183 -4
  71. package/src/flow_gate.ts +75 -2
  72. package/src/framework/application_home.ts +2 -0
  73. package/src/framework/auth_provider_law.ts +2 -2
  74. package/src/framework/birth_meter.ts +7 -19
  75. package/src/framework/bootstrap.ts +0 -2
  76. package/src/framework/business_corrections.ts +9 -5
  77. package/src/framework/domain_lifecycle.ts +15 -29
  78. package/src/framework/encryption_scope.ts +2 -1
  79. package/src/framework/framework_aggregates.ts +2 -0
  80. package/src/framework/genesis.ts +16 -14
  81. package/src/framework/home.ts +20 -14
  82. package/src/framework/owned_custody.ts +9 -3
  83. package/src/framework/platform_admin.ts +3 -3
  84. package/src/framework/relations.ts +21 -13
  85. package/src/framework/signing_identity.ts +45 -40
  86. package/src/framework/workspace_invariant.ts +245 -0
  87. package/src/framework/workspaces.ts +23 -29
  88. package/src/index.ts +36 -16
  89. package/src/intent.ts +178 -6
  90. package/src/law_check.ts +32 -11
  91. package/src/law_graph.ts +2902 -0
  92. package/src/law_graph_usda.ts +1799 -0
  93. package/src/law_identity.ts +435 -0
  94. package/src/law_value_type.ts +230 -0
  95. package/src/lsp_server.ts +61 -31
  96. package/src/ops.ts +7 -0
  97. package/src/package_config.ts +141 -0
  98. package/src/package_dependencies.ts +108 -0
  99. package/src/package_dependency_usage.ts +150 -0
  100. package/src/package_source_imports.ts +71 -0
  101. package/src/plan_source_slice.ts +335 -0
  102. package/src/point_guard.ts +192 -0
  103. package/src/precondition.ts +44 -9
  104. package/src/read_projection_namespace.ts +1 -1
  105. package/src/runtime_zod.ts +44 -0
  106. package/src/secondary_ensure.ts +67 -14
  107. package/src/shape_compat.ts +1 -1
  108. package/src/stable_id_identity_bridge.ts +149 -0
  109. package/src/stable_id_lockfile.ts +278 -0
  110. package/src/stable_ids.ts +7 -2
  111. package/src/stable_ids_for_module.ts +348 -0
  112. package/src/stable_ids_types.ts +1 -1
  113. package/src/sum.ts +1 -1
  114. package/src/usd_state.ts +1 -1
  115. package/src/usda_relationship_integrity.ts +66 -0
  116. package/src/usda_text.ts +18 -0
  117. package/src/usdz.ts +78 -0
  118. package/src/value_object.ts +95 -0
  119. package/src/wire.ts +19 -0
  120. package/src/wire_encode.ts +22 -0
  121. package/src/workspace_sharding.ts +52 -36
  122. package/src/workspace_sharing.ts +28 -4
  123. package/src/zod_introspection.ts +1 -1
  124. package/dart/test/cluster_attested_test.dart +0 -201
  125. package/src/compile_engine.ts +0 -595
  126. package/src/manifest.ts +0 -1476
  127. package/src/read_projection_composition.ts +0 -117
  128. package/src/read_projection_manifest.ts +0 -115
  129. package/src/relation_composition.ts +0 -286
  130. package/src/usd.ts +0 -1967
  131. package/src/usd_layers.ts +0 -1776
package/src/index.ts CHANGED
@@ -9,6 +9,14 @@
9
9
  // ONE import for a domain file: `z` (payload schemas) rides along with the DSL, so
10
10
  // authors never juggle a separate zod dependency (or risk a second zod instance).
11
11
  export { z } from "zod";
12
+ export {
13
+ definePackage,
14
+ validateNomosPackageConfig,
15
+ type NomosPackageConfig,
16
+ type NomosPackageDefinition,
17
+ type NomosPackageDomainConfig,
18
+ } from "./package_config.js";
19
+ export type { RuntimePackageDependency } from "./package_dependencies.js";
12
20
  export {
13
21
  aggregate,
14
22
  instance,
@@ -41,6 +49,10 @@ export {
41
49
  jsonWithNullSchema,
42
50
  type EvidenceRefValue,
43
51
  evidenceRefSchema,
52
+ type Sha256Digest,
53
+ sha256Digest,
54
+ isSha256DigestSchema,
55
+ NOMOS_SHA256_DIGEST_BRAND,
44
56
  // FIELD FACETS (the endorse model — security_model.md): authority/standing/attribution gate
45
57
  // set() acceptance of PrincipalEvidence; secret/public are the confidentiality lattice.
46
58
  authorityField,
@@ -61,11 +73,15 @@ export {
61
73
  idOf,
62
74
  idOfMetaOf,
63
75
  idOfTargetKindOf,
76
+ versionedRefOf,
77
+ versionedRefOfMetaOf,
64
78
  brandedId,
65
79
  brandedIdMetaOf,
66
80
  voMetaOf,
67
81
  voSample,
68
82
  NOMOS_VO_BRAND,
83
+ NOMOS_VERSIONED_REF_BRAND,
84
+ type VersionedRefOfMeta,
69
85
  geo,
70
86
  type ValueObject,
71
87
  type ValueObjectValue,
@@ -478,6 +494,7 @@ export {
478
494
  recordShare,
479
495
  pruneShare,
480
496
  sharedByWorkspace,
497
+ sharedByObject,
481
498
  ReceivingGroup,
482
499
  ReceivingGroupPolicy,
483
500
  RECEIVING_GROUP_ID,
@@ -594,9 +611,7 @@ export {
594
611
  } from "./framework/capability.js";
595
612
  export {
596
613
  DOMAIN_INSTALLATION_PHASES,
597
- PolicyBundle,
598
614
  DomainInstallation,
599
- policyAggregateId,
600
615
  domainInstallationAggregateId,
601
616
  installDomain,
602
617
  stageDomain,
@@ -623,8 +638,18 @@ export {
623
638
  } from "./framework/repair.js";
624
639
  export {
625
640
  workspaceInvariant,
641
+ readRef,
642
+ readSet,
643
+ idItem,
644
+ idLiteral,
645
+ idPayload,
646
+ idTemplate,
626
647
  ref,
627
648
  refAs,
649
+ type InvariantReadBinding,
650
+ type InvariantReadId,
651
+ type InvariantReadIdPart,
652
+ type InvariantReadTransform,
628
653
  type WorkspaceInvariantDecl,
629
654
  type WorkspaceInvariantVerdict,
630
655
  type WorkspaceInvariantReads,
@@ -729,7 +754,10 @@ export {
729
754
  type InvariantEvidence,
730
755
  type InvariantVerdict,
731
756
  } from "./relation.js";
732
- export { generateDartDomain, type DomainModule } from "./codegen_dart.js";
757
+ // Build-time generators live on explicit package subpaths. Keep the structural type available to
758
+ // authoring tools, but never pull generator implementations into the runtime namespace evaluated by
759
+ // every kernel sandbox.
760
+ export type { DomainModule } from "./codegen_dart.js";
733
761
  // SHAPE COMPATIBILITY (#72) — the structural contract a client negotiates with a workspace
734
762
  // by, instead of the law hash. Pure + deterministic; the editor/CI mirror imports these.
735
763
  export {
@@ -744,28 +772,20 @@ export {
744
772
  // turned on by the `--emit-dot` compiler flag). Pure `DomainModule -> string`, deterministic;
745
773
  // NOT part of the domain identity. Barrel-safe (no node:* imports), so the engine bundle can
746
774
  // import this barrel unaffected.
747
- export { emitDomainDot, type DotOptions } from "./codegen_dot.js";
775
+ export type { DotOptions } from "./codegen_dot.js";
748
776
  // The OPTIONAL OpenUSD `.usda` emitter — a COMPILED DOMAIN as a navigable USD stage (aggregates →
749
777
  // Sphere prims, directives → Cube prims carrying their `plan` lump, the creates/mutates/ref flow →
750
778
  // BasisCurves). The abstract LAW structure as a maths object (holon-maths projection), NOT the org-data
751
779
  // twin. Sibling of `emitDomainDot`: pure `DomainModule -> string`, deterministic, NOT part of the
752
780
  // domain identity; barrel-safe (no node:* imports). Open the output in usdview / any USD tool.
753
- export { emitDomainUsda, type UsdaOptions } from "./codegen_usda.js";
781
+ export type { UsdaOptions } from "./codegen_usda.js";
754
782
  // The PROVIDER SDK codegen (symmetric twin of the peer payload classes). `emitProviderSdk`
755
783
  // is invoked INSIDE `generateDartDomain` for a module's `impureCapabilities`; the type is exported
756
784
  // so the emit script can type its `impureCapabilities` list. A `impureCapability(...)` factory return value
757
785
  // satisfies `ImpureCapabilityDecl` structurally.
758
- export {
759
- emitProviderSdk,
760
- emitProviderTask,
761
- taskBaseName,
762
- type ImpureCapabilityDecl,
763
- } from "./codegen_provider_dart.js";
764
- // NOTE: the canonical-manifest / domain-hash API (`./manifest.js`) is a BUILD-TIME
765
- // concern that imports `node:crypto`. It is DELIBERATELY NOT re-exported from this
766
- // barrel: the deterministic engine bundle (`golden/domain_nomos.js`) imports this
767
- // barrel, and esbuild `--platform=neutral` cannot resolve `node:crypto`. Build-time
768
- // consumers import it via the `@githolon/dsl/manifest` subpath instead.
786
+ export type { ImpureCapabilityDecl } from "./codegen_provider_dart.js";
787
+ // Canonical law is emitted as typed USDA. The retired canonical-JSON manifest helper has
788
+ // been removed from both this barrel and the package exports.
769
789
  export { encodeKernelSchema, executeDirectiveToIntent, encodeRefMode, intentIdFromHlc } from "./wire_encode.js";
770
790
  export type {
771
791
  WireEvent,
package/src/intent.ts CHANGED
@@ -18,7 +18,12 @@ import type { Ports } from "./ctx.js";
18
18
  import type { PlannedOp } from "./ops.js";
19
19
  import type { Field } from "./fields.js";
20
20
  import type { AggregateHandle } from "./aggregate.js";
21
- import type { WorkspaceInvariantDecl } from "./framework/workspace_invariant.js";
21
+ import type {
22
+ InvariantReadBinding,
23
+ InvariantReadId,
24
+ InvariantReadIdPart,
25
+ WorkspaceInvariantDecl,
26
+ } from "./framework/workspace_invariant.js";
22
27
  import { businessBirthConsequence, type BusinessBirthDirective } from "./business_birth.js";
23
28
  import type { BusinessBirthDecl } from "./births.js";
24
29
  import type { WorkspaceSharingPolicy } from "./workspace_sharing.js";
@@ -31,6 +36,132 @@ export interface IntentStep<I = unknown, P = unknown> {
31
36
  readonly payload: (facts: I) => P;
32
37
  }
33
38
 
39
+ const PAYLOAD_TRACE_PATH = Symbol("nomos.intent.payload-trace-path");
40
+
41
+ type PayloadTrace = {
42
+ readonly [PAYLOAD_TRACE_PATH]: readonly string[];
43
+ };
44
+
45
+ /**
46
+ * An inert symbolic business payload used only while compiling application-language
47
+ * composition. A private payload mapping may carry a public field through unchanged;
48
+ * in that case the trace proves the exact public path without parsing JavaScript source.
49
+ * Any computation/coercion loses the trace and therefore cannot be miscompiled as a
50
+ * native read recipe.
51
+ */
52
+ function payloadTrace(path: readonly string[] = []): PayloadTrace {
53
+ const target = Object.create(null) as PayloadTrace;
54
+ return new Proxy(target, {
55
+ get(_target, property) {
56
+ if (property === PAYLOAD_TRACE_PATH) return path;
57
+ if (property === "then") return undefined;
58
+ if (property === Symbol.toPrimitive || property === "toString" || property === "valueOf") {
59
+ return () => {
60
+ throw new Error("computed application-language payload field is not a direct path");
61
+ };
62
+ }
63
+ return payloadTrace([...path, String(property)]);
64
+ },
65
+ });
66
+ }
67
+
68
+ function tracedPath(value: unknown): readonly string[] | undefined {
69
+ if (value === null || typeof value !== "object") return undefined;
70
+ const path = (value as Partial<PayloadTrace>)[PAYLOAD_TRACE_PATH];
71
+ return Array.isArray(path) ? path : undefined;
72
+ }
73
+
74
+ function valueAtPath(value: unknown, path: readonly string[]): unknown {
75
+ let current = value;
76
+ for (const part of path) {
77
+ if (current === null || (typeof current !== "object" && typeof current !== "function")) {
78
+ return undefined;
79
+ }
80
+ current = (current as Record<string, unknown>)[part];
81
+ }
82
+ return current;
83
+ }
84
+
85
+ function remapPayloadPath(
86
+ mapped: unknown,
87
+ privatePath: readonly string[],
88
+ ): readonly string[] | undefined {
89
+ return tracedPath(valueAtPath(mapped, privatePath));
90
+ }
91
+
92
+ function remapStepPayloadPath(
93
+ step: IntentStep<any, any>,
94
+ privatePath: readonly string[],
95
+ ): readonly string[] | undefined {
96
+ try {
97
+ return remapPayloadPath(step.payload(payloadTrace()), privatePath);
98
+ } catch {
99
+ return undefined;
100
+ }
101
+ }
102
+
103
+ function remapReadPart(
104
+ part: InvariantReadIdPart,
105
+ mapped: unknown,
106
+ ): InvariantReadIdPart | undefined {
107
+ if (part.source !== "payload") return part;
108
+ const path = remapPayloadPath(mapped, part.path);
109
+ return path === undefined ? undefined : { ...part, path };
110
+ }
111
+
112
+ function remapReadId(id: InvariantReadId, mapped: unknown): InvariantReadId | undefined {
113
+ if (id.source === "literal") return id;
114
+ if (id.source === "payload") {
115
+ const path = remapPayloadPath(mapped, id.path);
116
+ return path === undefined ? undefined : { ...id, path };
117
+ }
118
+ const parts: InvariantReadIdPart[] = [];
119
+ for (const part of id.parts) {
120
+ const remapped = remapReadPart(part, mapped);
121
+ if (remapped === undefined) return undefined;
122
+ parts.push(remapped);
123
+ }
124
+ return { ...id, parts };
125
+ }
126
+
127
+ /** Rewrite a private effect's typed point-read recipe onto its public business facts.
128
+ * Returns undefined unless every payload dependency is structurally proven. */
129
+ function remapNativeReads(
130
+ reads: readonly InvariantReadBinding[],
131
+ step: IntentStep<any, any>,
132
+ ): readonly InvariantReadBinding[] | undefined {
133
+ const needsPayload = reads.some((read) =>
134
+ read.eachPayloadPath !== undefined ||
135
+ read.id.source === "payload" ||
136
+ (read.id.source === "template" && read.id.parts.some((part) => part.source === "payload"))
137
+ );
138
+ if (!needsPayload) return reads;
139
+
140
+ let mapped: unknown;
141
+ try {
142
+ mapped = step.payload(payloadTrace());
143
+ } catch {
144
+ return undefined;
145
+ }
146
+
147
+ const out: InvariantReadBinding[] = [];
148
+ for (const read of reads) {
149
+ const eachPayloadPath = read.eachPayloadPath === undefined
150
+ ? undefined
151
+ : remapPayloadPath(mapped, read.eachPayloadPath);
152
+ if (read.eachPayloadPath !== undefined && eachPayloadPath === undefined) return undefined;
153
+
154
+ const id = remapReadId(read.id, mapped);
155
+ if (id === undefined) return undefined;
156
+ out.push({
157
+ ...read,
158
+ ...(eachPayloadPath === undefined ? {} : { eachPayloadPath }),
159
+ id,
160
+ });
161
+ }
162
+ return out;
163
+ }
164
+
34
165
  export interface BusinessIntent<I = unknown> extends Directive<I> {
35
166
  readonly [INTENT_BRAND]: true;
36
167
  readonly steps: readonly IntentStep<I, any>[];
@@ -235,10 +366,33 @@ class IntentBuilder<I> {
235
366
  }
236
367
 
237
368
  const preconditions: PreconditionDescriptor[] = this.steps.flatMap((step) =>
238
- (step.directive.preconditions ?? []).map((condition) => ({
239
- ...condition,
240
- idField: (facts: unknown) => condition.idField(step.payload(facts as I)),
241
- })),
369
+ (step.directive.preconditions ?? []).map((condition) => {
370
+ const {
371
+ idPayloadPath: privateIdPayloadPath,
372
+ readId: privateReadId,
373
+ ...rest
374
+ } = condition;
375
+ const publicIdPayloadPath = privateIdPayloadPath === undefined
376
+ ? undefined
377
+ : remapStepPayloadPath(step, privateIdPayloadPath);
378
+ let publicReadId: InvariantReadId | undefined;
379
+ if (privateReadId !== undefined) {
380
+ try {
381
+ publicReadId = remapReadId(
382
+ privateReadId,
383
+ step.payload(payloadTrace() as unknown as I),
384
+ );
385
+ } catch {
386
+ publicReadId = undefined;
387
+ }
388
+ }
389
+ return {
390
+ ...rest,
391
+ ...(publicIdPayloadPath === undefined ? {} : { idPayloadPath: publicIdPayloadPath }),
392
+ ...(publicReadId === undefined ? {} : { readId: publicReadId }),
393
+ idField: (facts: unknown) => condition.idField(step.payload(facts as I)),
394
+ };
395
+ }),
242
396
  );
243
397
  const queryReads = dedupeBy(
244
398
  this.steps.flatMap((step) => step.directive.declaredQueryReads ?? []),
@@ -289,6 +443,14 @@ class IntentBuilder<I> {
289
443
  },
290
444
  marker: primary.marker as ReferentialMarker,
291
445
  aggregateId: primary.aggregateId,
446
+ // Keep the typed local target on the lowered public intent. A narrow
447
+ // runtime plan may retain only this intent export; without the handle the
448
+ // engine cannot rebuild its aggregate registry entry and silently drops
449
+ // compound intents that also birth a child.
450
+ ...(primary.aggregate === undefined ? {} : { aggregate: primary.aggregate }),
451
+ ...(primary.outputCardinality !== undefined
452
+ ? { outputCardinality: primary.outputCardinality }
453
+ : {}),
292
454
  ...(secondaryOutputs.length > 0 ? { secondaryOutputs } : {}),
293
455
  payloadSchema: (birthSteps[0]?.directive.payloadSchema ?? this.contractDeclaration.accepts) as unknown as z.ZodType<I>,
294
456
  plan,
@@ -571,10 +733,20 @@ export function remapWorkspaceInvariantsForLanguage(
571
733
  // still validates every mapped consequence before it can commit.
572
734
  return (parsed.success ? parsed.data : mapped) as Record<string, unknown>;
573
735
  };
736
+ // Never carry a private payload path blindly onto a public intent. Prove the
737
+ // mapping structurally with inert path values; otherwise leave the selector
738
+ // compiler-visible so the native-only gate can name the exact unmapped law.
739
+ const remappedNativeReads = invariant.__nomosReadBindings === undefined
740
+ ? undefined
741
+ : remapNativeReads(invariant.__nomosReadBindings, use.step);
742
+ const { __nomosReadBindings: _privateReads, ...withoutPrivateReads } = invariant;
574
743
  out.push({
575
- ...invariant,
744
+ ...withoutPrivateReads,
576
745
  id: `${use.intent.id}__${invariant.id}`,
577
746
  on: use.intent.id,
747
+ ...(remappedNativeReads === undefined
748
+ ? {}
749
+ : { __nomosReadBindings: remappedNativeReads }),
578
750
  reads: (ctx) => invariant.reads({ ...ctx, intent: mapFacts(ctx.intent) }),
579
751
  assert: (snapshots, ctx) => invariant.assert(
580
752
  snapshots,
package/src/law_check.ts CHANGED
@@ -443,7 +443,9 @@ function unreadPayloadFindings(domain: string, dir: Directive<unknown>, probe: P
443
443
  // ROUTING lane consumes (the plan may pass it straight to read(…, {from}) without a tracked property
444
444
  // read on the sampled path) — a declared trust-edge field is never a dead field.
445
445
  for (const e of (dir as { declaredForeignReads?: ForeignReadEdge[] }).declaredForeignReads ?? []) {
446
- if ("fromPayload" in e && typeof e.fromPayload === "string") gateConsumed.add(e.fromPayload);
446
+ if ("fromPayload" in e && typeof e.fromPayload === "string") {
447
+ gateConsumed.add(e.fromPayload.split(".")[0]!);
448
+ }
447
449
  }
448
450
  const out: Finding[] = [];
449
451
  for (const f of requiredPayloadFields(dir.payloadSchema as z.ZodTypeAny)) {
@@ -1077,10 +1079,14 @@ function topLevelPortReadFindings(mod: DomainModule, portReadingDomains: Readonl
1077
1079
  // (b) a trustPolicy whose allow-list aggregate has no directive writing it → WARN (unpopulatable);
1078
1080
  // (c) a query declared BOTH as a local .reads() premise AND a foreign policy read → REFUSE (laundering).
1079
1081
 
1080
- /** The FOREIGN query ids a directive's plan actually reads (`read(q, …, { from })`) on the one probe
1081
- * sample — `undefined` when the plan can't be probed (unsynthesizable payload / it threw). A clean re-run
1082
- * under a read port that answers both lanes; every side-effect sink is drained (never leaks). */
1083
- function foreignReadsPerformed(dir: Directive<unknown>, where: string): Set<string> | undefined {
1082
+ /** The local and FOREIGN query ids a directive's plan actually reads on the one probe sample —
1083
+ * `undefined` when the plan can't be probed (unsynthesizable payload / it threw). This semantic
1084
+ * check is deliberately independent of bundle layout: moving the DSL runtime into an authenticated
1085
+ * shared USDA base must never make an undeclared read disappear from author-time diagnostics. */
1086
+ function readsPerformed(
1087
+ dir: Directive<unknown>,
1088
+ where: string,
1089
+ ): { readonly local: Set<string>; readonly foreign: Set<string> } | undefined {
1084
1090
  let synth: SynthesizedPayload;
1085
1091
  try {
1086
1092
  synth = synthesizeDirectivePayload(dir, where);
@@ -1089,7 +1095,8 @@ function foreignReadsPerformed(dir: Directive<unknown>, where: string): Set<stri
1089
1095
  }
1090
1096
  const payload: Record<string, unknown> = { ...synth.value };
1091
1097
  for (const f of synth.autoStamped) payload[f] = "2026-01-01T00:00:00Z";
1092
- const performed = new Set<string>();
1098
+ const local = new Set<string>();
1099
+ const foreign = new Set<string>();
1093
1100
  let ok = true;
1094
1101
  withBirthProbeGlobals(() => {
1095
1102
  const g = globalThis as unknown as { nomos: Record<string, unknown> };
@@ -1107,7 +1114,8 @@ function foreignReadsPerformed(dir: Directive<unknown>, where: string): Set<stri
1107
1114
  try {
1108
1115
  dir.plan(payload as never, deterministicPorts({ physical: 1, replica: 1, principal: "principal:probe" }));
1109
1116
  for (const r of __drainReads()) {
1110
- if ((r as { attestation?: unknown }).attestation !== undefined) performed.add(r.queryId);
1117
+ if ((r as { attestation?: unknown }).attestation !== undefined) foreign.add(r.queryId);
1118
+ else local.add(r.queryId);
1111
1119
  }
1112
1120
  } catch {
1113
1121
  ok = false;
@@ -1117,7 +1125,7 @@ function foreignReadsPerformed(dir: Directive<unknown>, where: string): Set<stri
1117
1125
  g.nomos.read = prevRead;
1118
1126
  }
1119
1127
  });
1120
- return ok ? performed : undefined;
1128
+ return ok ? { local, foreign } : undefined;
1121
1129
  }
1122
1130
 
1123
1131
  function attestedReadFindings(mod: DomainModule): Finding[] {
@@ -1158,10 +1166,23 @@ function attestedReadFindings(mod: DomainModule): Finding[] {
1158
1166
  }
1159
1167
  }
1160
1168
 
1161
- // (a) an undeclared foreign read — the plan routes read(q, …, { from }) with no trust edge for q.
1162
- const performed = foreignReadsPerformed(dir, `${domain}/${dir.id}`);
1169
+ const performed = readsPerformed(dir, `${domain}/${dir.id}`);
1163
1170
  if (performed !== undefined) {
1164
- for (const qid of performed) {
1171
+ // An ordinary captured read is a declared premise, not an incidental function call. Catch it
1172
+ // against the directive itself so package slicing/runtime-base changes cannot hide the error.
1173
+ for (const qid of performed.local) {
1174
+ if (!localQueryIds.has(qid)) {
1175
+ findings.push({
1176
+ domain,
1177
+ directiveId: dir.id,
1178
+ level: "refuse",
1179
+ what: `directive '${dir.id}' references read() for query '${qid}', but NO directive declares a captured-read query for that premise`,
1180
+ fix: `declare the read on this directive: .reads(theQueryDecl) (the same QueryDecl passed to read())`,
1181
+ });
1182
+ }
1183
+ }
1184
+ // (a) an undeclared foreign read — the plan routes read(q, …, { from }) with no trust edge for q.
1185
+ for (const qid of performed.foreign) {
1165
1186
  if (!declaredQueryIds.has(qid)) {
1166
1187
  findings.push({
1167
1188
  domain,