@githolon/dsl 0.102.5 → 0.103.1
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/compile_package.mjs +43 -2
- package/compile_proof.d.mts +47 -0
- package/compile_proof.mjs +343 -0
- package/dart/CHANGELOG.md +14 -0
- package/dart/lib/src/dev_cloud.dart +8 -8
- package/dart/lib/src/nms_generated_runtime.dart +2 -0
- package/dart/lib/src/runner/node-entry.mjs +5539 -1228
- package/dart/lib/src/runner/offline-install.mjs +4578 -528
- package/dart/lib/src/test_harness.dart +206 -152
- package/dart/lib/src/webview_bridge.dart +239 -102
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/acquire_session_credentials_test.dart +4 -3
- package/dart/test/acquire_session_test.dart +9 -0
- package/dart/test/adv_realm_interference_test.dart +7 -6
- package/dart/test/cloud_bound_topology_test.dart +3 -3
- package/dart/test/cluster_births_test.dart +4 -3
- package/dart/test/cluster_device_keyed_acceptance_test.dart +10 -16
- package/dart/test/cluster_local_autosync_test.dart +3 -2
- package/dart/test/cluster_scale_test.dart +3 -2
- package/dart/test/dlq_session_surface_test.dart +7 -6
- package/dart/test/first_app_gauntlet_test.dart +14 -21
- package/dart/test/fixtures/wedge/fake-install.mjs +1 -1
- package/dart/test/four_layer_nested_topology_test.dart +4 -4
- package/dart/test/harness_phase_timeout_test.dart +2 -2
- package/dart/test/local_sync_inert_test.dart +1 -1
- package/dart/test/parent_signer_recovery_test.dart +2 -2
- package/dart/test/sealing_client_test.dart +4 -3
- package/dart/test/session_bound_test.dart +8 -7
- package/dart/test/test_harness_test.dart +13 -12
- package/dart/test/vo_read_typing_test.dart +4 -3
- package/dart/test/watch_after_offer_stall_test.dart +4 -3
- package/dart/test/webview_bridge_test.dart +102 -5
- package/dart_types/CHANGELOG.md +5 -0
- package/dart_types/lib/nomos_types.dart +9 -0
- package/dart_types/lib/src/nomos_ref.dart +20 -0
- package/dart_types/lib/src/optional_ref.dart +85 -0
- package/dart_types/lib/src/sha256_digest.dart +41 -0
- package/dart_types/lib/src/versioned_nomos_ref.dart +81 -0
- package/dart_types/pubspec.yaml +1 -1
- package/dart_types/test/optional_ref_test.dart +44 -0
- package/dart_types/test/sha256_digest_test.dart +18 -0
- package/package.json +9 -5
- package/runtime/generated-application-host.js +5365 -1105
- package/src/aggregate.ts +18 -0
- package/src/application_composition.ts +6 -32
- package/src/attested_core.ts +6 -3
- package/src/birth_meter.ts +1 -0
- package/src/birth_meter_ensure.ts +28 -2
- package/src/births.ts +1 -1
- package/src/build_package.ts +162 -538
- package/src/business_birth.ts +19 -3
- package/src/canonical_json.ts +21 -0
- package/src/check_package_main.ts +73 -33
- package/src/clear_directive.ts +19 -8
- package/src/codegen_dart.ts +305 -22
- package/src/codegen_dart_application.ts +862 -292
- package/src/codegen_dart_client.ts +1084 -407
- package/src/codegen_dart_scenario.ts +12 -12
- package/src/codegen_proof.ts +169 -39
- package/src/codegen_provider_dart.ts +17 -1
- package/src/codegen_ts.ts +298 -106
- package/src/compat_manifest.ts +2 -2
- package/src/compat_package_main.ts +8 -12
- package/src/compile_package_main.ts +1860 -592
- package/src/compose.ts +1 -1
- package/src/directive.ts +208 -15
- package/src/engine_entry.ts +165 -111
- package/src/evolve_preview.ts +29 -69
- package/src/extremum.ts +1 -1
- package/src/fields.ts +183 -4
- package/src/flow_gate.ts +75 -2
- package/src/framework/application_home.ts +2 -0
- package/src/framework/auth_provider_law.ts +2 -2
- package/src/framework/birth_meter.ts +7 -19
- package/src/framework/bootstrap.ts +0 -2
- package/src/framework/business_corrections.ts +9 -5
- package/src/framework/domain_lifecycle.ts +15 -29
- package/src/framework/encryption_scope.ts +2 -1
- package/src/framework/framework_aggregates.ts +2 -0
- package/src/framework/genesis.ts +16 -14
- package/src/framework/home.ts +20 -14
- package/src/framework/owned_custody.ts +9 -3
- package/src/framework/platform_admin.ts +3 -3
- package/src/framework/relations.ts +21 -13
- package/src/framework/signing_identity.ts +45 -40
- package/src/framework/workspace_invariant.ts +245 -0
- package/src/framework/workspaces.ts +23 -29
- package/src/index.ts +36 -16
- package/src/intent.ts +178 -6
- package/src/law_check.ts +32 -11
- package/src/law_graph.ts +2902 -0
- package/src/law_graph_usda.ts +1799 -0
- package/src/law_identity.ts +435 -0
- package/src/law_value_type.ts +230 -0
- package/src/lsp_server.ts +58 -29
- package/src/ops.ts +7 -0
- package/src/package_config.ts +141 -0
- package/src/package_dependencies.ts +108 -0
- package/src/package_dependency_usage.ts +150 -0
- package/src/package_source_imports.ts +71 -0
- package/src/plan_source_slice.ts +335 -0
- package/src/point_guard.ts +192 -0
- package/src/precondition.ts +44 -9
- package/src/read_projection_namespace.ts +1 -1
- package/src/runtime_zod.ts +44 -0
- package/src/secondary_ensure.ts +67 -14
- package/src/shape_compat.ts +1 -1
- package/src/stable_id_identity_bridge.ts +149 -0
- package/src/stable_id_lockfile.ts +140 -6
- package/src/stable_ids.ts +1 -1
- package/src/stable_ids_for_module.ts +348 -0
- package/src/stable_ids_types.ts +1 -1
- package/src/sum.ts +1 -1
- package/src/usd_state.ts +1 -1
- package/src/usda_relationship_integrity.ts +66 -0
- package/src/usda_text.ts +18 -0
- package/src/usdz.ts +78 -0
- package/src/value_object.ts +95 -0
- package/src/wire.ts +19 -0
- package/src/wire_encode.ts +22 -0
- package/src/workspace_sharding.ts +52 -36
- package/src/workspace_sharing.ts +28 -4
- package/src/zod_introspection.ts +1 -1
- package/dart/test/cluster_attested_test.dart +0 -201
- package/src/compile_engine.ts +0 -595
- package/src/manifest.ts +0 -1476
- package/src/read_projection_composition.ts +0 -117
- package/src/read_projection_manifest.ts +0 -115
- package/src/relation_composition.ts +0 -286
- package/src/usd.ts +0 -1967
- package/src/usd_layers.ts +0 -1776
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 {
|
|
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
|
-
|
|
240
|
-
|
|
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
|
-
...
|
|
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")
|
|
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
|
|
1081
|
-
*
|
|
1082
|
-
*
|
|
1083
|
-
|
|
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
|
|
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)
|
|
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 ?
|
|
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
|
-
|
|
1162
|
-
const performed = foreignReadsPerformed(dir, `${domain}/${dir.id}`);
|
|
1169
|
+
const performed = readsPerformed(dir, `${domain}/${dir.id}`);
|
|
1163
1170
|
if (performed !== undefined) {
|
|
1164
|
-
|
|
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,
|