@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.
- package/compile_package.mjs +43 -2
- package/compile_proof.d.mts +47 -0
- package/compile_proof.mjs +343 -0
- package/dart/CHANGELOG.md +7 -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 +5504 -1236
- 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 +196 -89
- 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/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 +76 -35
- package/src/clear_directive.ts +19 -8
- package/src/codegen_dart.ts +305 -22
- package/src/codegen_dart_application.ts +844 -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 +1866 -597
- package/src/compose.ts +1 -1
- package/src/directive.ts +208 -15
- package/src/engine_entry.ts +165 -111
- package/src/evolve_preview.ts +43 -81
- 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 +61 -31
- 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 +278 -0
- package/src/stable_ids.ts +7 -2
- 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/compose.ts
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* `CompositionViolation`, never silently coerced.
|
|
25
25
|
*/
|
|
26
26
|
import { createHash } from "node:crypto";
|
|
27
|
-
import { canonicalJson } from "./
|
|
27
|
+
import { canonicalJson } from "./canonical_json.js";
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* A composable law unit (modelled on a directive/role). The typed-rule fields:
|
package/src/directive.ts
CHANGED
|
@@ -29,11 +29,24 @@ import { zodDef, zodKind, zodObjectShape } from "./zod_introspection.js";
|
|
|
29
29
|
import {
|
|
30
30
|
isFromPayloadRef,
|
|
31
31
|
isTrustPolicyDecl,
|
|
32
|
+
NomosSourceFrontier,
|
|
32
33
|
__registerMonotoneForeignQuery,
|
|
33
34
|
type FromPayloadRef,
|
|
34
35
|
type TrustPolicyDecl,
|
|
35
36
|
type ForeignReadEdge,
|
|
36
37
|
} from "./attested_core.js";
|
|
38
|
+
import {
|
|
39
|
+
buildPointGuard,
|
|
40
|
+
evaluatePointGuard,
|
|
41
|
+
type PointGuardContext,
|
|
42
|
+
type PointGuardDeclaration,
|
|
43
|
+
type PointGuardSpec,
|
|
44
|
+
} from "./point_guard.js";
|
|
45
|
+
import {
|
|
46
|
+
resolveInvariantReadId,
|
|
47
|
+
type InvariantReadId,
|
|
48
|
+
type InvariantReadIdPart,
|
|
49
|
+
} from "./framework/workspace_invariant.js";
|
|
37
50
|
|
|
38
51
|
export type ReferentialMarker = "creates" | "mutates" | "ensures" | "archives";
|
|
39
52
|
|
|
@@ -62,12 +75,20 @@ export interface PreconditionDescriptor {
|
|
|
62
75
|
readonly fieldNames: readonly string[];
|
|
63
76
|
/** Pure payload → target instance id (no clock, no IO — re-derived identically on every peer). */
|
|
64
77
|
readonly idField: (payload: unknown) => string;
|
|
78
|
+
/** Typed payload path for native USDA read selection. Legacy callback selectors omit this and
|
|
79
|
+
* remain compiler-visible migration debt; new code should pass a payload key or `.precondition`. */
|
|
80
|
+
readonly idPayloadPath?: readonly string[];
|
|
81
|
+
/** Complete typed USDA id expression. Present for keys, literals and templates; absent only
|
|
82
|
+
* for legacy arbitrary callbacks that still require migration. */
|
|
83
|
+
readonly readId?: InvariantReadId;
|
|
65
84
|
/** The fixed-predicate specialization, or `predicate` for the general `.where` form. */
|
|
66
85
|
readonly kind: "exists" | "absent" | "predicate";
|
|
67
86
|
/** The row predicate — `row` is the resolved snapshot or `undefined` when the instance is absent. */
|
|
68
87
|
readonly predicate: (row: Record<string, unknown> | undefined) => boolean;
|
|
69
88
|
/** The typed refusal code emitted when the predicate is false. */
|
|
70
89
|
readonly rejectCode: string;
|
|
90
|
+
/** Generic point-state algebra lowered from typed USDA directly into the Rust gate. */
|
|
91
|
+
readonly pointGuard?: PointGuardSpec;
|
|
71
92
|
/**
|
|
72
93
|
* Why this precondition may intentionally make a fact about private state observable through its
|
|
73
94
|
* public refusal. Set by chaining `.declassify(reason)` directly after the precondition marker;
|
|
@@ -76,6 +97,56 @@ export interface PreconditionDescriptor {
|
|
|
76
97
|
readonly declassify?: string;
|
|
77
98
|
}
|
|
78
99
|
|
|
100
|
+
type PayloadIdSelector<P> =
|
|
101
|
+
| Extract<keyof P, string>
|
|
102
|
+
| ((payload: P) => string)
|
|
103
|
+
| InvariantReadIdPart
|
|
104
|
+
| readonly InvariantReadIdPart[];
|
|
105
|
+
|
|
106
|
+
function payloadIdSelector<P>(selector: PayloadIdSelector<P>): {
|
|
107
|
+
readonly field: (payload: unknown) => string;
|
|
108
|
+
readonly path?: readonly string[];
|
|
109
|
+
readonly readId?: InvariantReadId;
|
|
110
|
+
} {
|
|
111
|
+
if (typeof selector === "string") {
|
|
112
|
+
const readId: InvariantReadId = {
|
|
113
|
+
source: "payload",
|
|
114
|
+
path: [selector],
|
|
115
|
+
prefix: "",
|
|
116
|
+
suffix: "",
|
|
117
|
+
optional: false,
|
|
118
|
+
};
|
|
119
|
+
return {
|
|
120
|
+
field: (payload) => resolveInvariantReadId(readId, payload as Record<string, unknown>) ?? "",
|
|
121
|
+
path: [selector],
|
|
122
|
+
readId,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (Array.isArray(selector)) {
|
|
126
|
+
const readId: InvariantReadId = { source: "template", parts: [...selector], optional: false };
|
|
127
|
+
return {
|
|
128
|
+
field: (payload) => resolveInvariantReadId(readId, payload as Record<string, unknown>) ?? "",
|
|
129
|
+
readId,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (typeof selector === "object" && selector !== null && "source" in selector) {
|
|
133
|
+
if (selector.source === "item") {
|
|
134
|
+
throw new Error("directive precondition ids cannot use item-relative read parts");
|
|
135
|
+
}
|
|
136
|
+
const readId: InvariantReadId = selector.source === "literal"
|
|
137
|
+
? selector
|
|
138
|
+
: selector.transform === "none"
|
|
139
|
+
? { source: "payload", path: selector.path, prefix: "", suffix: "", optional: false }
|
|
140
|
+
: { source: "template", parts: [selector], optional: false };
|
|
141
|
+
return {
|
|
142
|
+
field: (payload) => resolveInvariantReadId(readId, payload as Record<string, unknown>) ?? "",
|
|
143
|
+
...(readId.source === "payload" ? { path: readId.path } : {}),
|
|
144
|
+
readId,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
return { field: selector as (payload: unknown) => string };
|
|
148
|
+
}
|
|
149
|
+
|
|
79
150
|
/**
|
|
80
151
|
* A pure, replay-stable derivation of the authz SCOPE an authored intent targets,
|
|
81
152
|
* from its payload — e.g. `(p) => ["site", p.siteId]`. It MUST be a pure function
|
|
@@ -90,8 +161,12 @@ export interface PreconditionDescriptor {
|
|
|
90
161
|
export interface SecondaryOutput {
|
|
91
162
|
readonly marker: ReferentialMarker;
|
|
92
163
|
readonly aggregateId: string;
|
|
164
|
+
/** Process-local compiler link; canonical law emits the referenced identity, never this object. */
|
|
165
|
+
readonly aggregate?: AggregateHandle;
|
|
93
166
|
/** This action may affect more than one record of this aggregate type. */
|
|
94
167
|
readonly many?: true;
|
|
168
|
+
/** This action affects zero or one record of this aggregate type. */
|
|
169
|
+
readonly optional?: true;
|
|
95
170
|
}
|
|
96
171
|
|
|
97
172
|
/**
|
|
@@ -133,6 +208,21 @@ export interface Directive<P = unknown> {
|
|
|
133
208
|
readonly id: string;
|
|
134
209
|
readonly marker: ReferentialMarker;
|
|
135
210
|
readonly aggregateId: string;
|
|
211
|
+
/**
|
|
212
|
+
* This declaration contributes certified law metadata but is not an
|
|
213
|
+
* executable offer. The compiler must never publish a plan route for it.
|
|
214
|
+
* Used for structural sanctions such as the generated secondary-Ensure
|
|
215
|
+
* declaration; ordinary tenant and framework actions leave it unset.
|
|
216
|
+
*/
|
|
217
|
+
readonly manifestOnly?: true;
|
|
218
|
+
/** Typed declaration target retained for compiler composition; omitted by every wire/law serializer. */
|
|
219
|
+
readonly aggregate?: AggregateHandle;
|
|
220
|
+
/**
|
|
221
|
+
* Business-result cardinality for the primary marker target. The default is
|
|
222
|
+
* exactly one; `.affectsMany()` and `.affectsOptional()` opt into the other
|
|
223
|
+
* two generated client shapes without changing the directive's replay law.
|
|
224
|
+
*/
|
|
225
|
+
readonly outputCardinality?: "many" | "optional";
|
|
136
226
|
/**
|
|
137
227
|
* MULTI-OUTPUT DIRECTIVES: additional aggregates this directive declares it writes, beyond the primary
|
|
138
228
|
* marker target. Present iff the directive chained `.alsoEnsures/.alsoCreates/.alsoMutates/.alsoArchives`
|
|
@@ -162,6 +252,13 @@ export interface Directive<P = unknown> {
|
|
|
162
252
|
* application APIs. Set only through `nomosImplementationDetail(...)`.
|
|
163
253
|
*/
|
|
164
254
|
readonly businessBirthInternal?: true;
|
|
255
|
+
/**
|
|
256
|
+
* Compiler-generated child seed produced from `birth(..., { initialise })`.
|
|
257
|
+
* Its effect signature is owned by the lowering pass: tenant authors never
|
|
258
|
+
* receive this directive and therefore cannot honestly call `.alsoWrites()`
|
|
259
|
+
* on it themselves. Omitted from canonical law output.
|
|
260
|
+
*/
|
|
261
|
+
readonly businessBirthSeed?: true;
|
|
165
262
|
/**
|
|
166
263
|
* FRAMEWORK-SHARED brand (Dart-codegen hygiene). Set (by `framework/framework_aggregates.ts`) ONLY on
|
|
167
264
|
* the framework-owned directive singletons declared in `dsl/src/framework/` (the relate/signer/enroll
|
|
@@ -445,21 +542,33 @@ export interface RequirableDirective<P> extends Directive<P> {
|
|
|
445
542
|
genesisField?: string;
|
|
446
543
|
keyHashesField?: string;
|
|
447
544
|
}): RequirableDirective<P>;
|
|
545
|
+
/** Declare that this action can affect several records of its primary aggregate. */
|
|
546
|
+
affectsMany(): RequirableDirective<P>;
|
|
547
|
+
/** Declare that this action can affect zero or one record of its primary aggregate. */
|
|
548
|
+
affectsOptional(): RequirableDirective<P>;
|
|
448
549
|
/**
|
|
449
550
|
* MULTI-OUTPUT: declare an ADDITIONAL aggregate this directive writes (beyond its primary marker target),
|
|
450
551
|
* so generated docs/proof/client metadata name every output ("writes A + B") instead of one by convention.
|
|
451
552
|
* `.alsoEnsures/.alsoCreates/.alsoMutates/.alsoArchives` are the marker-specific sugar; `.alsoWrites` takes
|
|
452
553
|
* an explicit marker. Chainable + additive (a directive that never calls these is byte-identical). The
|
|
453
|
-
*
|
|
554
|
+
* The compiler checks observable marked plan outputs against this certified effect signature; an
|
|
555
|
+
* undeclared secondary output is a compile error rather than a runtime-only projection surprise.
|
|
454
556
|
*/
|
|
455
557
|
alsoWrites(aggregate: AggregateHandle, marker: ReferentialMarker): RequirableDirective<P>;
|
|
456
558
|
alsoWritesMany(aggregate: AggregateHandle, marker: ReferentialMarker): RequirableDirective<P>;
|
|
559
|
+
alsoWritesOptional(aggregate: AggregateHandle, marker: ReferentialMarker): RequirableDirective<P>;
|
|
457
560
|
alsoEnsures(aggregate: AggregateHandle): RequirableDirective<P>;
|
|
458
561
|
alsoCreates(aggregate: AggregateHandle): RequirableDirective<P>;
|
|
459
562
|
alsoEnsuresMany(aggregate: AggregateHandle): RequirableDirective<P>;
|
|
460
563
|
alsoCreatesMany(aggregate: AggregateHandle): RequirableDirective<P>;
|
|
564
|
+
alsoEnsuresOptional(aggregate: AggregateHandle): RequirableDirective<P>;
|
|
565
|
+
alsoCreatesOptional(aggregate: AggregateHandle): RequirableDirective<P>;
|
|
461
566
|
alsoMutates(aggregate: AggregateHandle): RequirableDirective<P>;
|
|
462
567
|
alsoArchives(aggregate: AggregateHandle): RequirableDirective<P>;
|
|
568
|
+
alsoMutatesMany(aggregate: AggregateHandle): RequirableDirective<P>;
|
|
569
|
+
alsoArchivesMany(aggregate: AggregateHandle): RequirableDirective<P>;
|
|
570
|
+
alsoMutatesOptional(aggregate: AggregateHandle): RequirableDirective<P>;
|
|
571
|
+
alsoArchivesOptional(aggregate: AggregateHandle): RequirableDirective<P>;
|
|
463
572
|
/**
|
|
464
573
|
* Declare the authz pre-condition: the `capability` the actor must hold (defaults
|
|
465
574
|
* to the directive id) at the scope `scopeFrom(payload)` derives (defaults to root).
|
|
@@ -702,19 +811,22 @@ class DirectivePlanStep<Id extends string, P> {
|
|
|
702
811
|
* function of the inferred payload. ONLY valid on a `.creates` directive (it defines the created id).
|
|
703
812
|
* Additive; `.creates(Agg)` with no `.withId` is the minted path (byte-identical).
|
|
704
813
|
*/
|
|
705
|
-
withId(
|
|
814
|
+
withId(idSelector: PayloadIdSelector<P>): DirectivePlanStep<Id, P> {
|
|
706
815
|
if (this.marker !== "creates") {
|
|
707
816
|
throw new Error(
|
|
708
817
|
`directive('${this.id}').withId(...) is only valid on a .creates(...) directive — it fixes the ` +
|
|
709
818
|
`CREATED aggregate's caller-supplied id (a .${this.marker} directive targets an existing id).`,
|
|
710
819
|
);
|
|
711
820
|
}
|
|
712
|
-
const
|
|
821
|
+
const selected = payloadIdSelector(idSelector);
|
|
822
|
+
const idField = selected.field;
|
|
713
823
|
return this.with(
|
|
714
824
|
{
|
|
715
825
|
aggregateId: this.aggregate.id,
|
|
716
826
|
fieldNames: Object.keys(this.aggregate.fields).sort(),
|
|
717
827
|
idField,
|
|
828
|
+
...(selected.path === undefined ? {} : { idPayloadPath: selected.path }),
|
|
829
|
+
...(selected.readId === undefined ? {} : { readId: selected.readId }),
|
|
718
830
|
kind: "absent",
|
|
719
831
|
predicate: (row) => row === undefined,
|
|
720
832
|
rejectCode: `${shortName(this.aggregate.id)}-exists`,
|
|
@@ -730,13 +842,16 @@ class DirectivePlanStep<Id extends string, P> {
|
|
|
730
842
|
*/
|
|
731
843
|
requiresExisting<F extends Record<string, Field>>(
|
|
732
844
|
aggregate: AggregateHandle<string, F>,
|
|
733
|
-
|
|
845
|
+
idSelector: PayloadIdSelector<P>,
|
|
734
846
|
rejectCode?: string,
|
|
735
847
|
): DirectivePlanStep<Id, P> {
|
|
848
|
+
const selected = payloadIdSelector(idSelector);
|
|
736
849
|
return this.with({
|
|
737
850
|
aggregateId: aggregate.id,
|
|
738
851
|
fieldNames: Object.keys(aggregate.fields).sort(),
|
|
739
|
-
idField:
|
|
852
|
+
idField: selected.field,
|
|
853
|
+
...(selected.path === undefined ? {} : { idPayloadPath: selected.path }),
|
|
854
|
+
...(selected.readId === undefined ? {} : { readId: selected.readId }),
|
|
740
855
|
kind: "exists",
|
|
741
856
|
predicate: (row) => row !== undefined,
|
|
742
857
|
rejectCode: rejectCode ?? `${shortName(aggregate.id)}-not-found`,
|
|
@@ -752,20 +867,65 @@ class DirectivePlanStep<Id extends string, P> {
|
|
|
752
867
|
*/
|
|
753
868
|
where<F extends Record<string, Field>>(
|
|
754
869
|
aggregate: AggregateHandle<string, F>,
|
|
755
|
-
|
|
870
|
+
idSelector: PayloadIdSelector<P>,
|
|
756
871
|
predicate: (row: ReadModelRow<F> | undefined) => boolean,
|
|
757
872
|
rejectCode: string,
|
|
758
873
|
): DirectivePlanStep<Id, P> {
|
|
874
|
+
const selected = payloadIdSelector(idSelector);
|
|
759
875
|
return this.with({
|
|
760
876
|
aggregateId: aggregate.id,
|
|
761
877
|
fieldNames: Object.keys(aggregate.fields).sort(),
|
|
762
|
-
idField:
|
|
878
|
+
idField: selected.field,
|
|
879
|
+
...(selected.path === undefined ? {} : { idPayloadPath: selected.path }),
|
|
880
|
+
...(selected.readId === undefined ? {} : { readId: selected.readId }),
|
|
763
881
|
kind: "predicate",
|
|
764
882
|
predicate: predicate as (row: Record<string, unknown> | undefined) => boolean,
|
|
765
883
|
rejectCode,
|
|
766
884
|
});
|
|
767
885
|
}
|
|
768
886
|
|
|
887
|
+
/**
|
|
888
|
+
* A kernel-lowerable point precondition. Unlike `.where(...)`, this callback builds a small typed
|
|
889
|
+
* algebraic value; it is not tenant code executed at admission. The resulting aggregate lookup,
|
|
890
|
+
* predicate tree, and refusal code are preserved as typed USDA and evaluated directly by Rust.
|
|
891
|
+
*
|
|
892
|
+
* .precondition(Site, ({ row, intent, all }) => ({
|
|
893
|
+
* id: intent.field("siteId"),
|
|
894
|
+
* holds: all(row.exists(), row.field("status").ne("deleted")),
|
|
895
|
+
* }), "site-not-found-or-deleted")
|
|
896
|
+
*/
|
|
897
|
+
precondition<F extends Record<string, Field>>(
|
|
898
|
+
aggregate: AggregateHandle<string, F>,
|
|
899
|
+
build: (ctx: PointGuardContext<P, F>) => PointGuardDeclaration,
|
|
900
|
+
rejectCode: string,
|
|
901
|
+
): DirectivePlanStep<Id, P> {
|
|
902
|
+
const pointGuard = buildPointGuard(aggregate, build, rejectCode);
|
|
903
|
+
return this.with({
|
|
904
|
+
aggregateId: aggregate.id,
|
|
905
|
+
fieldNames: Object.keys(aggregate.fields).sort(),
|
|
906
|
+
idField: (payload) => {
|
|
907
|
+
let current: unknown = payload;
|
|
908
|
+
for (const part of pointGuard.idPayloadPath) {
|
|
909
|
+
if (current === null || typeof current !== "object") return "";
|
|
910
|
+
current = (current as Record<string, unknown>)[part];
|
|
911
|
+
}
|
|
912
|
+
return typeof current === "string" ? current : "";
|
|
913
|
+
},
|
|
914
|
+
idPayloadPath: pointGuard.idPayloadPath,
|
|
915
|
+
readId: {
|
|
916
|
+
source: "payload",
|
|
917
|
+
path: pointGuard.idPayloadPath,
|
|
918
|
+
prefix: "",
|
|
919
|
+
suffix: "",
|
|
920
|
+
optional: false,
|
|
921
|
+
},
|
|
922
|
+
kind: "predicate",
|
|
923
|
+
predicate: (row) => evaluatePointGuard(pointGuard.predicate, row, undefined),
|
|
924
|
+
rejectCode,
|
|
925
|
+
pointGuard,
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
|
|
769
929
|
/**
|
|
770
930
|
* Acknowledge that the MOST RECENT write-path precondition may intentionally reveal one fact about
|
|
771
931
|
* private state through its typed refusal. This is business policy, so it stays beside the condition
|
|
@@ -808,6 +968,7 @@ class DirectivePlanStep<Id extends string, P> {
|
|
|
808
968
|
id: this.id,
|
|
809
969
|
marker: this.marker,
|
|
810
970
|
aggregateId: this.aggregate.id,
|
|
971
|
+
aggregate: this.aggregate,
|
|
811
972
|
payloadSchema: this.payloadSchema,
|
|
812
973
|
plan: fn,
|
|
813
974
|
declaredReads: [],
|
|
@@ -859,20 +1020,32 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
859
1020
|
const alsoWrites = (
|
|
860
1021
|
aggregate: AggregateHandle,
|
|
861
1022
|
marker: ReferentialMarker,
|
|
862
|
-
many =
|
|
1023
|
+
cardinality: "one" | "many" | "optional" = "one",
|
|
863
1024
|
): RequirableDirective<P> => {
|
|
864
1025
|
// Dedup by (marker, aggregateId) + the primary target — declaring the same output twice is a no-op, and
|
|
865
1026
|
// re-declaring the primary aggregate is ignored (it is already the marker target). Sorted at emit time.
|
|
866
1027
|
const next: SecondaryOutput = {
|
|
867
1028
|
marker,
|
|
868
1029
|
aggregateId: aggregate.id,
|
|
869
|
-
|
|
1030
|
+
aggregate,
|
|
1031
|
+
...(cardinality === "many" ? { many: true as const } : {}),
|
|
1032
|
+
...(cardinality === "optional" ? { optional: true as const } : {}),
|
|
870
1033
|
};
|
|
871
|
-
|
|
1034
|
+
// Only the exact primary EFFECT is already declared. The same aggregate under a
|
|
1035
|
+
// different marker is a distinct lawful consequence: a `.mutates(A)` directive
|
|
1036
|
+
// may deliberately emit an Archive for A and must retain `.alsoArchives(A)` in
|
|
1037
|
+
// its typed effect signature.
|
|
1038
|
+
const isPrimary = aggregate.id === base.aggregateId && marker === base.marker;
|
|
872
1039
|
const existing = (base.secondaryOutputs ?? []).find(
|
|
873
1040
|
(s) => s.marker === marker && s.aggregateId === aggregate.id,
|
|
874
1041
|
);
|
|
875
|
-
if (
|
|
1042
|
+
if (
|
|
1043
|
+
isPrimary ||
|
|
1044
|
+
(existing !== undefined &&
|
|
1045
|
+
(cardinality === "one" ||
|
|
1046
|
+
(cardinality === "many" && existing.many === true) ||
|
|
1047
|
+
(cardinality === "optional" && existing.optional === true)))
|
|
1048
|
+
) {
|
|
876
1049
|
return makeRequirable(base);
|
|
877
1050
|
}
|
|
878
1051
|
if (existing !== undefined) {
|
|
@@ -887,6 +1060,8 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
887
1060
|
};
|
|
888
1061
|
return {
|
|
889
1062
|
...base,
|
|
1063
|
+
affectsMany: () => makeRequirable({ ...base, outputCardinality: "many" }),
|
|
1064
|
+
affectsOptional: () => makeRequirable({ ...base, outputCardinality: "optional" }),
|
|
890
1065
|
example(payload: P): RequirableDirective<P> {
|
|
891
1066
|
if (isRuntimeLaw()) {
|
|
892
1067
|
// The Node authoring pass already proved this example. Executable law retains it as proof
|
|
@@ -991,15 +1166,29 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
991
1166
|
},
|
|
992
1167
|
alsoWrites,
|
|
993
1168
|
alsoWritesMany: (aggregate: AggregateHandle, marker: ReferentialMarker) =>
|
|
994
|
-
alsoWrites(aggregate, marker,
|
|
1169
|
+
alsoWrites(aggregate, marker, "many"),
|
|
1170
|
+
alsoWritesOptional: (aggregate: AggregateHandle, marker: ReferentialMarker) =>
|
|
1171
|
+
alsoWrites(aggregate, marker, "optional"),
|
|
995
1172
|
alsoEnsures: (aggregate: AggregateHandle) => alsoWrites(aggregate, "ensures"),
|
|
996
1173
|
alsoCreates: (aggregate: AggregateHandle) => alsoWrites(aggregate, "creates"),
|
|
997
1174
|
alsoEnsuresMany: (aggregate: AggregateHandle) =>
|
|
998
|
-
alsoWrites(aggregate, "ensures",
|
|
1175
|
+
alsoWrites(aggregate, "ensures", "many"),
|
|
999
1176
|
alsoCreatesMany: (aggregate: AggregateHandle) =>
|
|
1000
|
-
alsoWrites(aggregate, "creates",
|
|
1177
|
+
alsoWrites(aggregate, "creates", "many"),
|
|
1178
|
+
alsoEnsuresOptional: (aggregate: AggregateHandle) =>
|
|
1179
|
+
alsoWrites(aggregate, "ensures", "optional"),
|
|
1180
|
+
alsoCreatesOptional: (aggregate: AggregateHandle) =>
|
|
1181
|
+
alsoWrites(aggregate, "creates", "optional"),
|
|
1001
1182
|
alsoMutates: (aggregate: AggregateHandle) => alsoWrites(aggregate, "mutates"),
|
|
1002
1183
|
alsoArchives: (aggregate: AggregateHandle) => alsoWrites(aggregate, "archives"),
|
|
1184
|
+
alsoMutatesMany: (aggregate: AggregateHandle) =>
|
|
1185
|
+
alsoWrites(aggregate, "mutates", "many"),
|
|
1186
|
+
alsoArchivesMany: (aggregate: AggregateHandle) =>
|
|
1187
|
+
alsoWrites(aggregate, "archives", "many"),
|
|
1188
|
+
alsoMutatesOptional: (aggregate: AggregateHandle) =>
|
|
1189
|
+
alsoWrites(aggregate, "mutates", "optional"),
|
|
1190
|
+
alsoArchivesOptional: (aggregate: AggregateHandle) =>
|
|
1191
|
+
alsoWrites(aggregate, "archives", "optional"),
|
|
1003
1192
|
requires(
|
|
1004
1193
|
relation: string,
|
|
1005
1194
|
opts?: { object?: string; objectFrom?: { recordType: string; idField: string } },
|
|
@@ -1158,7 +1347,11 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
1158
1347
|
const byKey = new Map<string, ForeignReadEdge>(prior.map((e) => [canon(e), e]));
|
|
1159
1348
|
byKey.set(canon(entry), entry);
|
|
1160
1349
|
const merged = [...byKey.values()].sort((a, b) => (canon(a) < canon(b) ? -1 : canon(a) > canon(b) ? 1 : 0));
|
|
1161
|
-
|
|
1350
|
+
// Every admitted foreign read advances the reader-local source frontier in-band.
|
|
1351
|
+
// This is a framework consequence of `.readsFrom`, so make it part of the typed
|
|
1352
|
+
// effect signature here; tenant authors must not know the bookkeeping aggregate.
|
|
1353
|
+
return makeRequirable({ ...base, declaredForeignReads: merged })
|
|
1354
|
+
.alsoEnsures(NomosSourceFrontier);
|
|
1162
1355
|
},
|
|
1163
1356
|
emits(eventType: string, opts?: { max?: number }): RequirableDirective<P> {
|
|
1164
1357
|
// Accumulate one event type onto any prior emits; a `max` bound is recorded only
|