@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.
- package/dart/CHANGELOG.md +10 -0
- package/dart/lib/src/application_framework_calls.dart +21 -0
- package/dart/lib/src/dev_cloud.dart +67 -0
- package/dart/lib/src/nms_test_environment.dart +1 -1
- package/dart/lib/src/runner/node-entry.mjs +372 -106
- package/dart/lib/src/runner/offline-install.mjs +171 -11
- package/dart/lib/src/session_bound.dart +58 -30
- package/dart/lib/src/test_harness.dart +60 -98
- package/dart/lib/src/webview_bridge.dart +212 -32
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/node_runner_transport_test.dart +5 -5
- package/dart/test/webview_bridge_test.dart +6 -0
- package/dart_types/CHANGELOG.md +4 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +428 -2
- package/dart_types/pubspec.yaml +1 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +314 -82
- package/src/application_topology.ts +120 -28
- package/src/births.ts +27 -0
- package/src/codegen_call_usda.ts +9 -4
- package/src/codegen_dart_application.ts +146 -37
- package/src/codegen_dart_client.ts +225 -160
- package/src/codegen_dart_framework_calls.ts +89 -4
- package/src/codegen_ts.ts +135 -60
- package/src/compile_package_main.ts +3544 -1460
- package/src/directive.ts +320 -118
- package/src/fields.ts +2 -3
- package/src/framework/application_home.ts +3 -0
- package/src/framework/auth_provider_law.ts +20 -16
- package/src/framework/genesis.ts +32 -1
- package/src/framework/home.ts +45 -8
- package/src/framework/signing_identity.ts +78 -0
- package/src/framework/workspaces.ts +9 -1
- package/src/framework_call_dart_enums.ts +33 -0
- package/src/framework_call_definitions.ts +11 -2
- package/src/index.ts +12 -0
- package/src/intent.ts +525 -194
- package/src/law_graph.ts +32 -4
- package/src/law_graph_usda.ts +3 -0
- package/src/package_config.ts +16 -1
- package/src/read.ts +33 -11
- package/src/wire.ts +1 -0
- package/src/wire_encode.ts +3 -3
package/src/directive.ts
CHANGED
|
@@ -23,7 +23,11 @@ import type { PlannedOp } from "./ops.js";
|
|
|
23
23
|
import type { Ports } from "./ctx.js";
|
|
24
24
|
import type { RelationDecl } from "./relation.js";
|
|
25
25
|
import type { QueryDecl } from "./query.js";
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
normalizeActivity,
|
|
28
|
+
type ActivityInput,
|
|
29
|
+
type ActivityDescriptor,
|
|
30
|
+
} from "./activity.js";
|
|
27
31
|
import { idOfMetaOf } from "./value_object.js";
|
|
28
32
|
import { BIRTH_METER_QUERIES } from "./birth_meter.js";
|
|
29
33
|
import { zodDef, zodKind, zodObjectShape } from "./zod_introspection.js";
|
|
@@ -128,29 +132,52 @@ function payloadIdSelector<P>(selector: PayloadIdSelector<P>): {
|
|
|
128
132
|
optional: false,
|
|
129
133
|
};
|
|
130
134
|
return {
|
|
131
|
-
field: (payload) =>
|
|
135
|
+
field: (payload) =>
|
|
136
|
+
resolveInvariantReadId(readId, payload as Record<string, unknown>) ??
|
|
137
|
+
"",
|
|
132
138
|
path: [selector],
|
|
133
139
|
readId,
|
|
134
140
|
};
|
|
135
141
|
}
|
|
136
142
|
if (Array.isArray(selector)) {
|
|
137
|
-
const readId: InvariantReadId = {
|
|
143
|
+
const readId: InvariantReadId = {
|
|
144
|
+
source: "template",
|
|
145
|
+
parts: [...selector],
|
|
146
|
+
optional: false,
|
|
147
|
+
};
|
|
138
148
|
return {
|
|
139
|
-
field: (payload) =>
|
|
149
|
+
field: (payload) =>
|
|
150
|
+
resolveInvariantReadId(readId, payload as Record<string, unknown>) ??
|
|
151
|
+
"",
|
|
140
152
|
readId,
|
|
141
153
|
};
|
|
142
154
|
}
|
|
143
|
-
if (
|
|
155
|
+
if (
|
|
156
|
+
typeof selector === "object" &&
|
|
157
|
+
selector !== null &&
|
|
158
|
+
"source" in selector
|
|
159
|
+
) {
|
|
144
160
|
if (selector.source === "item") {
|
|
145
|
-
throw new Error(
|
|
161
|
+
throw new Error(
|
|
162
|
+
"directive precondition ids cannot use item-relative read parts"
|
|
163
|
+
);
|
|
146
164
|
}
|
|
147
|
-
const readId: InvariantReadId =
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
165
|
+
const readId: InvariantReadId =
|
|
166
|
+
selector.source === "literal"
|
|
167
|
+
? selector
|
|
168
|
+
: selector.transform === "none"
|
|
169
|
+
? {
|
|
170
|
+
source: "payload",
|
|
171
|
+
path: selector.path,
|
|
172
|
+
prefix: "",
|
|
173
|
+
suffix: "",
|
|
174
|
+
optional: false,
|
|
175
|
+
}
|
|
151
176
|
: { source: "template", parts: [selector], optional: false };
|
|
152
177
|
return {
|
|
153
|
-
field: (payload) =>
|
|
178
|
+
field: (payload) =>
|
|
179
|
+
resolveInvariantReadId(readId, payload as Record<string, unknown>) ??
|
|
180
|
+
"",
|
|
154
181
|
...(readId.source === "payload" ? { path: readId.path } : {}),
|
|
155
182
|
readId,
|
|
156
183
|
};
|
|
@@ -215,6 +242,15 @@ export interface BusinessSourceTrust {
|
|
|
215
242
|
/** Hidden trust payload fields populated from custody identity; v2 leaves the signing-key set empty. */
|
|
216
243
|
readonly genesisField: string;
|
|
217
244
|
readonly keyHashesField: string;
|
|
245
|
+
/**
|
|
246
|
+
* Optional indexed business query proving that this source is already seated.
|
|
247
|
+
* Generated clients use the declared query before authoring the hidden trust
|
|
248
|
+
* directive, so an idempotent prerequisite does not become a new commit on
|
|
249
|
+
* every business action.
|
|
250
|
+
*/
|
|
251
|
+
readonly satisfiedByQueryId?: string;
|
|
252
|
+
/** Satisfaction-query key -> business payload field. */
|
|
253
|
+
readonly satisfiedByQueryArgs?: Readonly<Record<string, string>>;
|
|
218
254
|
}
|
|
219
255
|
|
|
220
256
|
export interface Directive<P = unknown> {
|
|
@@ -320,7 +356,10 @@ export interface Directive<P = unknown> {
|
|
|
320
356
|
readonly object: string;
|
|
321
357
|
/** PER-RECORD (§6): when set, the gate object is `<recordType>:<payload[idField]>` (the static
|
|
322
358
|
* `object` is the fallback). `undefined` ⇒ the static object — era-ruled, byte-identical replay. */
|
|
323
|
-
readonly objectFrom?: {
|
|
359
|
+
readonly objectFrom?: {
|
|
360
|
+
readonly recordType: string;
|
|
361
|
+
readonly idField: string;
|
|
362
|
+
};
|
|
324
363
|
};
|
|
325
364
|
/**
|
|
326
365
|
* Admit this directive when it is the exact target call carried by a verified,
|
|
@@ -330,6 +369,9 @@ export interface Directive<P = unknown> {
|
|
|
330
369
|
* check remains the authorization decision.
|
|
331
370
|
*/
|
|
332
371
|
readonly acceptsWorkspaceEffectGate?: true;
|
|
372
|
+
/** Recovery-only workspace-effect door. The carried source must prove it is this workspace's
|
|
373
|
+
* exact K_root-rooted birth parent; no Zanzibar relation or host assertion substitutes for it. */
|
|
374
|
+
readonly acceptsVerifiedBirthParentRecoveryGate?: true;
|
|
333
375
|
/**
|
|
334
376
|
* CROSS-CUSTODY ATTESTATION-AUTHORIZED (home_holons §3/§5). When `true`, this directive's authorization
|
|
335
377
|
* is a carried §3 SIGNED QUOTE (the asserter's pubkey + a signature over the edge), NOT an author block —
|
|
@@ -572,6 +614,8 @@ export interface RequirableDirective<P> extends Directive<P> {
|
|
|
572
614
|
copy?: Readonly<Record<string, string>>;
|
|
573
615
|
genesisField?: string;
|
|
574
616
|
keyHashesField?: string;
|
|
617
|
+
satisfiedBy?: QueryDecl;
|
|
618
|
+
satisfiedByArgs?: Readonly<Record<string, string>>;
|
|
575
619
|
}): RequirableDirective<P>;
|
|
576
620
|
/** Declare that this action can affect several records of its primary aggregate. */
|
|
577
621
|
affectsMany(): RequirableDirective<P>;
|
|
@@ -585,9 +629,18 @@ export interface RequirableDirective<P> extends Directive<P> {
|
|
|
585
629
|
* The compiler checks observable marked plan outputs against this certified effect signature; an
|
|
586
630
|
* undeclared secondary output is a compile error rather than a runtime-only projection surprise.
|
|
587
631
|
*/
|
|
588
|
-
alsoWrites(
|
|
589
|
-
|
|
590
|
-
|
|
632
|
+
alsoWrites(
|
|
633
|
+
aggregate: AggregateHandle,
|
|
634
|
+
marker: ReferentialMarker
|
|
635
|
+
): RequirableDirective<P>;
|
|
636
|
+
alsoWritesMany(
|
|
637
|
+
aggregate: AggregateHandle,
|
|
638
|
+
marker: ReferentialMarker
|
|
639
|
+
): RequirableDirective<P>;
|
|
640
|
+
alsoWritesOptional(
|
|
641
|
+
aggregate: AggregateHandle,
|
|
642
|
+
marker: ReferentialMarker
|
|
643
|
+
): RequirableDirective<P>;
|
|
591
644
|
alsoEnsures(aggregate: AggregateHandle): RequirableDirective<P>;
|
|
592
645
|
alsoCreates(aggregate: AggregateHandle): RequirableDirective<P>;
|
|
593
646
|
alsoEnsuresMany(aggregate: AggregateHandle): RequirableDirective<P>;
|
|
@@ -607,7 +660,10 @@ export interface RequirableDirective<P> extends Directive<P> {
|
|
|
607
660
|
*/
|
|
608
661
|
requires(
|
|
609
662
|
relation: string,
|
|
610
|
-
opts?: {
|
|
663
|
+
opts?: {
|
|
664
|
+
object?: string;
|
|
665
|
+
objectFrom?: { recordType: string; idField: string };
|
|
666
|
+
}
|
|
611
667
|
): RequirableDirective<P>;
|
|
612
668
|
/** Declare this exact call shape as an admissible signed parent-workspace effect. */
|
|
613
669
|
acceptsWorkspaceEffect(): RequirableDirective<P>;
|
|
@@ -617,7 +673,10 @@ export interface RequirableDirective<P> extends Directive<P> {
|
|
|
617
673
|
* gate verifies the quote + pins the principal to the attestation key, and the bailiff delivers it
|
|
618
674
|
* host-free. Framework-only (the home law's `recordShare`). Returns a NEW requirable directive.
|
|
619
675
|
*/
|
|
620
|
-
attestationAuthorized(owner: {
|
|
676
|
+
attestationAuthorized(owner: {
|
|
677
|
+
ownerObject: string;
|
|
678
|
+
ownerField: string;
|
|
679
|
+
}): RequirableDirective<P>;
|
|
621
680
|
/**
|
|
622
681
|
* Declare this directive a DELEGATED-AUTHORITY delivery (`delegated_authority_births.md`): its write folds
|
|
623
682
|
* the delegated `relation` tuple ONLY behind a carried `DelegationCert` P signed — lowered into the
|
|
@@ -625,14 +684,20 @@ export interface RequirableDirective<P> extends Directive<P> {
|
|
|
625
684
|
* `subjectField`/`certField` default to `"subject"`/`"delegationCert"`. Chain `.delegatedFrom(parent)` to
|
|
626
685
|
* bind the grantor to this workspace's parent key. Returns a NEW requirable directive.
|
|
627
686
|
*/
|
|
628
|
-
delegatedRole(
|
|
687
|
+
delegatedRole(
|
|
688
|
+
relation: string,
|
|
689
|
+
opts?: { subjectField?: string; certField?: string }
|
|
690
|
+
): RequirableDirective<P>;
|
|
629
691
|
/**
|
|
630
692
|
* `.delegatedFrom(parent)` — bind the delegating grantor to THIS workspace's parent key (the platform P
|
|
631
693
|
* that bore this home): the `DelegationCert`'s grantor key MUST equal the base64 key at
|
|
632
694
|
* `parentKeyObject.parentKeyField` (e.g. the birth-cert `birth-attestation:self`.`parentKey`, or a seeded
|
|
633
695
|
* `platform-key:self`.`grantorKey`). Refines the `.delegatedRole()` gate. Returns a NEW requirable directive.
|
|
634
696
|
*/
|
|
635
|
-
delegatedFrom(parent: {
|
|
697
|
+
delegatedFrom(parent: {
|
|
698
|
+
parentKeyObject: string;
|
|
699
|
+
parentKeyField: string;
|
|
700
|
+
}): RequirableDirective<P>;
|
|
636
701
|
/**
|
|
637
702
|
* Declare this directive a KEYLESS VERIFIED-FIRST-DEVICE bootstrap (keyless_verified_onboard.md): its
|
|
638
703
|
* authorization is a carried, device-bound IdP `idpToken` re-verified against the workspace AuthProvider —
|
|
@@ -675,7 +740,10 @@ export interface RequirableDirective<P> extends Directive<P> {
|
|
|
675
740
|
* …)` declaration (signature / accepted key + genesis / bounds / freshness). Callable multiple
|
|
676
741
|
* times; deduped by `(queryId, from)`. Purely additive (the era rule).
|
|
677
742
|
*/
|
|
678
|
-
readsFrom(
|
|
743
|
+
readsFrom(
|
|
744
|
+
sourceWorkspace: string,
|
|
745
|
+
query: { id: string }
|
|
746
|
+
): RequirableDirective<P>;
|
|
679
747
|
/**
|
|
680
748
|
* Declare ONE DYNAMIC (payload-routed) foreign read (dynamic_source_attested_reads.md): the plan may
|
|
681
749
|
* `read(theQuery, args, { from: payload.<field> })` — the routed workspace is chosen at runtime by the
|
|
@@ -686,7 +754,7 @@ export interface RequirableDirective<P> extends Directive<P> {
|
|
|
686
754
|
readsFrom(
|
|
687
755
|
source: FromPayloadRef,
|
|
688
756
|
query: { id: string },
|
|
689
|
-
opts: { policy: TrustPolicyDecl }
|
|
757
|
+
opts: { policy: TrustPolicyDecl }
|
|
690
758
|
): RequirableDirective<P>;
|
|
691
759
|
/**
|
|
692
760
|
* Declare ONE entry of the directive's EMIT boundary: an `eventType` its `plan` may
|
|
@@ -792,14 +860,16 @@ class DirectivePayloadStep<Id extends string> {
|
|
|
792
860
|
constructor(
|
|
793
861
|
private readonly id: Id,
|
|
794
862
|
private readonly marker: ReferentialMarker,
|
|
795
|
-
private readonly aggregate: AggregateHandle
|
|
863
|
+
private readonly aggregate: AggregateHandle
|
|
796
864
|
) {}
|
|
797
|
-
payload<S extends z.ZodTypeAny>(
|
|
865
|
+
payload<S extends z.ZodTypeAny>(
|
|
866
|
+
schema: S
|
|
867
|
+
): DirectivePlanStep<Id, z.infer<S>> {
|
|
798
868
|
return new DirectivePlanStep(
|
|
799
869
|
this.id,
|
|
800
870
|
this.marker,
|
|
801
871
|
this.aggregate,
|
|
802
|
-
schema as z.ZodType<z.infer<S
|
|
872
|
+
schema as z.ZodType<z.infer<S>>
|
|
803
873
|
);
|
|
804
874
|
}
|
|
805
875
|
}
|
|
@@ -820,13 +890,13 @@ class DirectivePlanStep<Id extends string, P> {
|
|
|
820
890
|
private readonly payloadSchema: z.ZodType<P>,
|
|
821
891
|
private readonly createOnceIdField?: (payload: unknown) => string,
|
|
822
892
|
private readonly preconditions: PreconditionDescriptor[] = [],
|
|
823
|
-
private readonly targetField?: string
|
|
893
|
+
private readonly targetField?: string
|
|
824
894
|
) {}
|
|
825
895
|
|
|
826
896
|
/** Non-destructive copy carrying one more precondition (and/or the create-once id field). */
|
|
827
897
|
private with(
|
|
828
898
|
desc?: PreconditionDescriptor,
|
|
829
|
-
createOnceIdField?: (payload: unknown) => string
|
|
899
|
+
createOnceIdField?: (payload: unknown) => string
|
|
830
900
|
): DirectivePlanStep<Id, P> {
|
|
831
901
|
return new DirectivePlanStep(
|
|
832
902
|
this.id,
|
|
@@ -835,7 +905,7 @@ class DirectivePlanStep<Id extends string, P> {
|
|
|
835
905
|
this.payloadSchema,
|
|
836
906
|
createOnceIdField ?? this.createOnceIdField,
|
|
837
907
|
desc !== undefined ? [...this.preconditions, desc] : this.preconditions,
|
|
838
|
-
this.targetField
|
|
908
|
+
this.targetField
|
|
839
909
|
);
|
|
840
910
|
}
|
|
841
911
|
|
|
@@ -845,10 +915,12 @@ class DirectivePlanStep<Id extends string, P> {
|
|
|
845
915
|
* Use this only when the payload carries more than one reference to the same
|
|
846
916
|
* aggregate and the compiler cannot choose without domain knowledge.
|
|
847
917
|
*/
|
|
848
|
-
target<K extends Extract<keyof P, string>>(
|
|
918
|
+
target<K extends Extract<keyof P, string>>(
|
|
919
|
+
field: K
|
|
920
|
+
): DirectivePlanStep<Id, P> {
|
|
849
921
|
if (this.marker !== "mutates" && this.marker !== "archives") {
|
|
850
922
|
throw new Error(
|
|
851
|
-
`directive('${this.id}').target(...) is only valid on .mutates(...) or .archives(...)
|
|
923
|
+
`directive('${this.id}').target(...) is only valid on .mutates(...) or .archives(...)`
|
|
852
924
|
);
|
|
853
925
|
}
|
|
854
926
|
const shape = zodObjectShape(this.payloadSchema);
|
|
@@ -856,7 +928,11 @@ class DirectivePlanStep<Id extends string, P> {
|
|
|
856
928
|
const actual = schema === undefined ? undefined : idOfMetaOf(schema);
|
|
857
929
|
if (actual !== this.aggregate.id) {
|
|
858
930
|
throw new Error(
|
|
859
|
-
`directive('${
|
|
931
|
+
`directive('${
|
|
932
|
+
this.id
|
|
933
|
+
}').target('${field}') must name an idOf(${shortName(
|
|
934
|
+
this.aggregate.id
|
|
935
|
+
)}) payload field`
|
|
860
936
|
);
|
|
861
937
|
}
|
|
862
938
|
return new DirectivePlanStep(
|
|
@@ -866,7 +942,7 @@ class DirectivePlanStep<Id extends string, P> {
|
|
|
866
942
|
this.payloadSchema,
|
|
867
943
|
this.createOnceIdField,
|
|
868
944
|
this.preconditions,
|
|
869
|
-
field
|
|
945
|
+
field
|
|
870
946
|
);
|
|
871
947
|
}
|
|
872
948
|
|
|
@@ -881,7 +957,7 @@ class DirectivePlanStep<Id extends string, P> {
|
|
|
881
957
|
if (this.marker !== "creates") {
|
|
882
958
|
throw new Error(
|
|
883
959
|
`directive('${this.id}').withId(...) is only valid on a .creates(...) directive — it fixes the ` +
|
|
884
|
-
`CREATED aggregate's caller-supplied id (a .${this.marker} directive targets an existing id)
|
|
960
|
+
`CREATED aggregate's caller-supplied id (a .${this.marker} directive targets an existing id).`
|
|
885
961
|
);
|
|
886
962
|
}
|
|
887
963
|
const selected = payloadIdSelector(idSelector);
|
|
@@ -891,13 +967,15 @@ class DirectivePlanStep<Id extends string, P> {
|
|
|
891
967
|
aggregateId: this.aggregate.id,
|
|
892
968
|
fieldNames: Object.keys(this.aggregate.fields).sort(),
|
|
893
969
|
idField,
|
|
894
|
-
...(selected.path === undefined
|
|
970
|
+
...(selected.path === undefined
|
|
971
|
+
? {}
|
|
972
|
+
: { idPayloadPath: selected.path }),
|
|
895
973
|
...(selected.readId === undefined ? {} : { readId: selected.readId }),
|
|
896
974
|
kind: "absent",
|
|
897
975
|
predicate: (row) => row === undefined,
|
|
898
976
|
rejectCode: `${shortName(this.aggregate.id)}-exists`,
|
|
899
977
|
},
|
|
900
|
-
idField
|
|
978
|
+
idField
|
|
901
979
|
);
|
|
902
980
|
}
|
|
903
981
|
|
|
@@ -909,7 +987,7 @@ class DirectivePlanStep<Id extends string, P> {
|
|
|
909
987
|
requiresExisting<F extends Record<string, Field>>(
|
|
910
988
|
aggregate: AggregateHandle<string, F>,
|
|
911
989
|
idSelector: PayloadIdSelector<P>,
|
|
912
|
-
rejectCode?: string
|
|
990
|
+
rejectCode?: string
|
|
913
991
|
): DirectivePlanStep<Id, P> {
|
|
914
992
|
const selected = payloadIdSelector(idSelector);
|
|
915
993
|
return this.with({
|
|
@@ -935,7 +1013,7 @@ class DirectivePlanStep<Id extends string, P> {
|
|
|
935
1013
|
aggregate: AggregateHandle<string, F>,
|
|
936
1014
|
idSelector: PayloadIdSelector<P>,
|
|
937
1015
|
predicate: (row: ReadModelRow<F> | undefined) => boolean,
|
|
938
|
-
rejectCode: string
|
|
1016
|
+
rejectCode: string
|
|
939
1017
|
): DirectivePlanStep<Id, P> {
|
|
940
1018
|
const selected = payloadIdSelector(idSelector);
|
|
941
1019
|
return this.with({
|
|
@@ -945,7 +1023,9 @@ class DirectivePlanStep<Id extends string, P> {
|
|
|
945
1023
|
...(selected.path === undefined ? {} : { idPayloadPath: selected.path }),
|
|
946
1024
|
...(selected.readId === undefined ? {} : { readId: selected.readId }),
|
|
947
1025
|
kind: "predicate",
|
|
948
|
-
predicate: predicate as (
|
|
1026
|
+
predicate: predicate as (
|
|
1027
|
+
row: Record<string, unknown> | undefined
|
|
1028
|
+
) => boolean,
|
|
949
1029
|
rejectCode,
|
|
950
1030
|
});
|
|
951
1031
|
}
|
|
@@ -963,7 +1043,7 @@ class DirectivePlanStep<Id extends string, P> {
|
|
|
963
1043
|
precondition<F extends Record<string, Field>>(
|
|
964
1044
|
aggregate: AggregateHandle<string, F>,
|
|
965
1045
|
build: (ctx: PointGuardContext<P, F>) => PointGuardDeclaration,
|
|
966
|
-
rejectCode: string
|
|
1046
|
+
rejectCode: string
|
|
967
1047
|
): DirectivePlanStep<Id, P> {
|
|
968
1048
|
const pointGuard = buildPointGuard(aggregate, build, rejectCode);
|
|
969
1049
|
return this.with({
|
|
@@ -986,7 +1066,8 @@ class DirectivePlanStep<Id extends string, P> {
|
|
|
986
1066
|
optional: false,
|
|
987
1067
|
},
|
|
988
1068
|
kind: "predicate",
|
|
989
|
-
predicate: (row) =>
|
|
1069
|
+
predicate: (row) =>
|
|
1070
|
+
evaluatePointGuard(pointGuard.predicate, row, undefined),
|
|
990
1071
|
rejectCode,
|
|
991
1072
|
pointGuard,
|
|
992
1073
|
});
|
|
@@ -1002,18 +1083,18 @@ class DirectivePlanStep<Id extends string, P> {
|
|
|
1002
1083
|
const trimmed = reason.trim();
|
|
1003
1084
|
if (trimmed.length === 0) {
|
|
1004
1085
|
throw new Error(
|
|
1005
|
-
`directive('${this.id}').declassify(reason) needs a non-empty human reason — it is the audit record for an intended private-state refusal
|
|
1086
|
+
`directive('${this.id}').declassify(reason) needs a non-empty human reason — it is the audit record for an intended private-state refusal.`
|
|
1006
1087
|
);
|
|
1007
1088
|
}
|
|
1008
1089
|
if (this.preconditions.length === 0) {
|
|
1009
1090
|
throw new Error(
|
|
1010
|
-
`directive('${this.id}').declassify(reason) must follow .requiresExisting(...), .where(...), or .withId(...)
|
|
1091
|
+
`directive('${this.id}').declassify(reason) must follow .requiresExisting(...), .where(...), or .withId(...).`
|
|
1011
1092
|
);
|
|
1012
1093
|
}
|
|
1013
1094
|
const last = this.preconditions[this.preconditions.length - 1]!;
|
|
1014
1095
|
if (last.declassify !== undefined) {
|
|
1015
1096
|
throw new Error(
|
|
1016
|
-
`directive('${this.id}') already declassifies its most recent precondition — declare each policy once, beside its condition
|
|
1097
|
+
`directive('${this.id}') already declassifies its most recent precondition — declare each policy once, beside its condition.`
|
|
1017
1098
|
);
|
|
1018
1099
|
}
|
|
1019
1100
|
return new DirectivePlanStep(
|
|
@@ -1022,11 +1103,8 @@ class DirectivePlanStep<Id extends string, P> {
|
|
|
1022
1103
|
this.aggregate,
|
|
1023
1104
|
this.payloadSchema,
|
|
1024
1105
|
this.createOnceIdField,
|
|
1025
|
-
[
|
|
1026
|
-
|
|
1027
|
-
{ ...last, declassify: trimmed },
|
|
1028
|
-
],
|
|
1029
|
-
this.targetField,
|
|
1106
|
+
[...this.preconditions.slice(0, -1), { ...last, declassify: trimmed }],
|
|
1107
|
+
this.targetField
|
|
1030
1108
|
);
|
|
1031
1109
|
}
|
|
1032
1110
|
|
|
@@ -1037,11 +1115,15 @@ class DirectivePlanStep<Id extends string, P> {
|
|
|
1037
1115
|
const candidates = Object.entries(shape)
|
|
1038
1116
|
.filter(([, schema]) => idOfMetaOf(schema) === this.aggregate.id)
|
|
1039
1117
|
.map(([field]) => field);
|
|
1040
|
-
const field =
|
|
1118
|
+
const field =
|
|
1119
|
+
this.targetField ??
|
|
1120
|
+
(candidates.length === 1 ? candidates[0] : undefined);
|
|
1041
1121
|
if (this.targetField === undefined && candidates.length > 1) {
|
|
1042
1122
|
throw new Error(
|
|
1043
|
-
`directive('${this.id}') has several idOf(${shortName(
|
|
1044
|
-
|
|
1123
|
+
`directive('${this.id}') has several idOf(${shortName(
|
|
1124
|
+
this.aggregate.id
|
|
1125
|
+
)}) payload fields; ` +
|
|
1126
|
+
`select the mutation target with .target('field') before .plan(...)`
|
|
1045
1127
|
);
|
|
1046
1128
|
}
|
|
1047
1129
|
if (field !== undefined) {
|
|
@@ -1063,9 +1145,13 @@ class DirectivePlanStep<Id extends string, P> {
|
|
|
1063
1145
|
declaredHostCapabilities: [],
|
|
1064
1146
|
// `.withId(...)` — record the caller-supplied-id extractor (codegen/authoring hint). The `absent`
|
|
1065
1147
|
// create-once guard is already in `preconditions` (seeded by `.withId`). Omit-when-absent.
|
|
1066
|
-
...(this.createOnceIdField !== undefined
|
|
1148
|
+
...(this.createOnceIdField !== undefined
|
|
1149
|
+
? { createOnceIdField: this.createOnceIdField }
|
|
1150
|
+
: {}),
|
|
1067
1151
|
// Omit-when-empty so a directive with no precondition is byte-identical (the era rule).
|
|
1068
|
-
...(this.preconditions.length > 0
|
|
1152
|
+
...(this.preconditions.length > 0
|
|
1153
|
+
? { preconditions: this.preconditions }
|
|
1154
|
+
: {}),
|
|
1069
1155
|
});
|
|
1070
1156
|
}
|
|
1071
1157
|
}
|
|
@@ -1090,7 +1176,7 @@ function shortName(aggregateId: string): string {
|
|
|
1090
1176
|
*/
|
|
1091
1177
|
export function recordObject(
|
|
1092
1178
|
agg: AggregateHandle,
|
|
1093
|
-
idField: string
|
|
1179
|
+
idField: string
|
|
1094
1180
|
): { recordType: string; idField: string } {
|
|
1095
1181
|
return { recordType: agg.id, idField };
|
|
1096
1182
|
}
|
|
@@ -1105,7 +1191,7 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
1105
1191
|
const alsoWrites = (
|
|
1106
1192
|
aggregate: AggregateHandle,
|
|
1107
1193
|
marker: ReferentialMarker,
|
|
1108
|
-
cardinality: "one" | "many" | "optional" = "one"
|
|
1194
|
+
cardinality: "one" | "many" | "optional" = "one"
|
|
1109
1195
|
): RequirableDirective<P> => {
|
|
1110
1196
|
// Dedup by (marker, aggregateId) + the primary target — declaring the same output twice is a no-op, and
|
|
1111
1197
|
// re-declaring the primary aggregate is ignored (it is already the marker target). Sorted at emit time.
|
|
@@ -1120,9 +1206,10 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
1120
1206
|
// different marker is a distinct lawful consequence: a `.mutates(A)` directive
|
|
1121
1207
|
// may deliberately emit an Archive for A and must retain `.alsoArchives(A)` in
|
|
1122
1208
|
// its typed effect signature.
|
|
1123
|
-
const isPrimary =
|
|
1209
|
+
const isPrimary =
|
|
1210
|
+
aggregate.id === base.aggregateId && marker === base.marker;
|
|
1124
1211
|
const existing = (base.secondaryOutputs ?? []).find(
|
|
1125
|
-
(s) => s.marker === marker && s.aggregateId === aggregate.id
|
|
1212
|
+
(s) => s.marker === marker && s.aggregateId === aggregate.id
|
|
1126
1213
|
);
|
|
1127
1214
|
if (
|
|
1128
1215
|
isPrimary ||
|
|
@@ -1137,16 +1224,20 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
1137
1224
|
return makeRequirable({
|
|
1138
1225
|
...base,
|
|
1139
1226
|
secondaryOutputs: (base.secondaryOutputs ?? []).map((output) =>
|
|
1140
|
-
output === existing ? next : output
|
|
1227
|
+
output === existing ? next : output
|
|
1141
1228
|
),
|
|
1142
1229
|
});
|
|
1143
1230
|
}
|
|
1144
|
-
return makeRequirable({
|
|
1231
|
+
return makeRequirable({
|
|
1232
|
+
...base,
|
|
1233
|
+
secondaryOutputs: [...(base.secondaryOutputs ?? []), next],
|
|
1234
|
+
});
|
|
1145
1235
|
};
|
|
1146
1236
|
return {
|
|
1147
1237
|
...base,
|
|
1148
1238
|
affectsMany: () => makeRequirable({ ...base, outputCardinality: "many" }),
|
|
1149
|
-
affectsOptional: () =>
|
|
1239
|
+
affectsOptional: () =>
|
|
1240
|
+
makeRequirable({ ...base, outputCardinality: "optional" }),
|
|
1150
1241
|
example(payload: P): RequirableDirective<P> {
|
|
1151
1242
|
if (isRuntimeLaw()) {
|
|
1152
1243
|
// The Node authoring pass already proved this example. Executable law retains it as proof
|
|
@@ -1154,46 +1245,57 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
1154
1245
|
return makeRequirable({ ...base, payloadExample: payload });
|
|
1155
1246
|
}
|
|
1156
1247
|
const parsed = base.payloadSchema.safeParse(payload);
|
|
1157
|
-
if (!parsed.success)
|
|
1158
|
-
|
|
1159
|
-
|
|
1248
|
+
if (!parsed.success)
|
|
1249
|
+
throw new Error(
|
|
1250
|
+
`directive('${base.id}').example(...) must satisfy its payload schema: ${parsed.error.message}`
|
|
1251
|
+
);
|
|
1160
1252
|
return makeRequirable({ ...base, payloadExample: parsed.data });
|
|
1161
1253
|
},
|
|
1162
1254
|
fromCurrentPrincipal(field: string): RequirableDirective<P> {
|
|
1163
1255
|
const name = field.trim();
|
|
1164
1256
|
if (name.length === 0) {
|
|
1165
|
-
throw new Error(
|
|
1257
|
+
throw new Error(
|
|
1258
|
+
"directive.fromCurrentPrincipal(field): field must be a non-empty payload field name"
|
|
1259
|
+
);
|
|
1166
1260
|
}
|
|
1167
1261
|
if (zodKind(base.payloadSchema) !== "object") {
|
|
1168
1262
|
throw new Error(
|
|
1169
|
-
`directive('${base.id}').fromCurrentPrincipal('${name}') needs an object payload schema
|
|
1263
|
+
`directive('${base.id}').fromCurrentPrincipal('${name}') needs an object payload schema`
|
|
1170
1264
|
);
|
|
1171
1265
|
}
|
|
1172
1266
|
const principalSchema = zodObjectShape(base.payloadSchema)[name];
|
|
1173
1267
|
if (principalSchema === undefined) {
|
|
1174
1268
|
throw new Error(
|
|
1175
|
-
`directive('${base.id}').fromCurrentPrincipal('${name}') names no payload field
|
|
1269
|
+
`directive('${base.id}').fromCurrentPrincipal('${name}') names no payload field`
|
|
1176
1270
|
);
|
|
1177
1271
|
}
|
|
1178
1272
|
let principalValue = principalSchema;
|
|
1179
|
-
while (
|
|
1273
|
+
while (
|
|
1274
|
+
zodKind(principalValue) === "optional" ||
|
|
1275
|
+
zodKind(principalValue) === "default"
|
|
1276
|
+
) {
|
|
1180
1277
|
principalValue = zodDef(principalValue).innerType!;
|
|
1181
1278
|
}
|
|
1182
1279
|
if (zodKind(principalValue) !== "string") {
|
|
1183
1280
|
throw new Error(
|
|
1184
|
-
`directive('${base.id}').fromCurrentPrincipal('${name}') must bind a string payload field
|
|
1281
|
+
`directive('${base.id}').fromCurrentPrincipal('${name}') must bind a string payload field`
|
|
1185
1282
|
);
|
|
1186
1283
|
}
|
|
1187
|
-
if (
|
|
1284
|
+
if (
|
|
1285
|
+
base.businessPrincipalField !== undefined &&
|
|
1286
|
+
base.businessPrincipalField !== name
|
|
1287
|
+
) {
|
|
1188
1288
|
throw new Error(
|
|
1189
|
-
`directive('${base.id}').fromCurrentPrincipal(...) may bind only one identity field
|
|
1289
|
+
`directive('${base.id}').fromCurrentPrincipal(...) may bind only one identity field`
|
|
1190
1290
|
);
|
|
1191
1291
|
}
|
|
1192
1292
|
return makeRequirable({ ...base, businessPrincipalField: name });
|
|
1193
1293
|
},
|
|
1194
1294
|
fromRecord(field: string, opts?: { as?: string }): RequirableDirective<P> {
|
|
1195
1295
|
if (typeof field !== "string" || field.length === 0) {
|
|
1196
|
-
throw new Error(
|
|
1296
|
+
throw new Error(
|
|
1297
|
+
"directive.fromRecord(field): field must be a non-empty payload field name"
|
|
1298
|
+
);
|
|
1197
1299
|
}
|
|
1198
1300
|
const prior = base.businessRecordInputs ?? [];
|
|
1199
1301
|
const next: BusinessRecordInput = {
|
|
@@ -1202,30 +1304,33 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
1202
1304
|
};
|
|
1203
1305
|
return makeRequirable({
|
|
1204
1306
|
...base,
|
|
1205
|
-
businessRecordInputs: [
|
|
1307
|
+
businessRecordInputs: [
|
|
1308
|
+
...prior.filter((candidate) => candidate.field !== field),
|
|
1309
|
+
next,
|
|
1310
|
+
],
|
|
1206
1311
|
});
|
|
1207
1312
|
},
|
|
1208
1313
|
trustsSource(opts): RequirableDirective<P> {
|
|
1209
1314
|
if (base.businessSourceTrust !== undefined) {
|
|
1210
1315
|
throw new Error(
|
|
1211
|
-
`directive('${base.id}').trustsSource(...) may be declared only once
|
|
1316
|
+
`directive('${base.id}').trustsSource(...) may be declared only once`
|
|
1212
1317
|
);
|
|
1213
1318
|
}
|
|
1214
1319
|
const fromPayload = opts.fromPayload.trim();
|
|
1215
1320
|
if (fromPayload.length === 0 || opts.query.id.trim().length === 0) {
|
|
1216
1321
|
throw new Error(
|
|
1217
|
-
`directive('${base.id}').trustsSource(...) needs a source payload field and query
|
|
1322
|
+
`directive('${base.id}').trustsSource(...) needs a source payload field and query`
|
|
1218
1323
|
);
|
|
1219
1324
|
}
|
|
1220
1325
|
const matchingRead = (base.declaredForeignReads ?? []).some(
|
|
1221
1326
|
(edge) =>
|
|
1222
1327
|
!("from" in edge) &&
|
|
1223
1328
|
edge.fromPayload === fromPayload &&
|
|
1224
|
-
edge.queryId === opts.query.id
|
|
1329
|
+
edge.queryId === opts.query.id
|
|
1225
1330
|
);
|
|
1226
1331
|
if (!matchingRead) {
|
|
1227
1332
|
throw new Error(
|
|
1228
|
-
`directive('${base.id}').trustsSource(...) must follow its matching dynamic .readsFrom(...) declaration
|
|
1333
|
+
`directive('${base.id}').trustsSource(...) must follow its matching dynamic .readsFrom(...) declaration`
|
|
1229
1334
|
);
|
|
1230
1335
|
}
|
|
1231
1336
|
const via = opts.via as Directive<unknown> & {
|
|
@@ -1233,7 +1338,15 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
1233
1338
|
};
|
|
1234
1339
|
if (via.businessBirthInternal !== true) {
|
|
1235
1340
|
throw new Error(
|
|
1236
|
-
`directive('${base.id}').trustsSource(...): the trust directive must be wrapped in nomosImplementationDetail(...) so tenant code cannot call it
|
|
1341
|
+
`directive('${base.id}').trustsSource(...): the trust directive must be wrapped in nomosImplementationDetail(...) so tenant code cannot call it`
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
if (
|
|
1345
|
+
(opts.satisfiedBy === undefined) !==
|
|
1346
|
+
(opts.satisfiedByArgs === undefined)
|
|
1347
|
+
) {
|
|
1348
|
+
throw new Error(
|
|
1349
|
+
`directive('${base.id}').trustsSource(...): satisfiedBy and satisfiedByArgs must be declared together`
|
|
1237
1350
|
);
|
|
1238
1351
|
}
|
|
1239
1352
|
return makeRequirable({
|
|
@@ -1246,16 +1359,26 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
1246
1359
|
copy: { ...(opts.copy ?? {}) },
|
|
1247
1360
|
genesisField: opts.genesisField ?? "sourceGenesis",
|
|
1248
1361
|
keyHashesField: opts.keyHashesField ?? "sourceKeyHashes",
|
|
1362
|
+
...(opts.satisfiedBy === undefined
|
|
1363
|
+
? {}
|
|
1364
|
+
: {
|
|
1365
|
+
satisfiedByQueryId: opts.satisfiedBy.id,
|
|
1366
|
+
satisfiedByQueryArgs: { ...(opts.satisfiedByArgs ?? {}) },
|
|
1367
|
+
}),
|
|
1249
1368
|
},
|
|
1250
1369
|
});
|
|
1251
1370
|
},
|
|
1252
1371
|
alsoWrites,
|
|
1253
1372
|
alsoWritesMany: (aggregate: AggregateHandle, marker: ReferentialMarker) =>
|
|
1254
1373
|
alsoWrites(aggregate, marker, "many"),
|
|
1255
|
-
alsoWritesOptional: (
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1374
|
+
alsoWritesOptional: (
|
|
1375
|
+
aggregate: AggregateHandle,
|
|
1376
|
+
marker: ReferentialMarker
|
|
1377
|
+
) => alsoWrites(aggregate, marker, "optional"),
|
|
1378
|
+
alsoEnsures: (aggregate: AggregateHandle) =>
|
|
1379
|
+
alsoWrites(aggregate, "ensures"),
|
|
1380
|
+
alsoCreates: (aggregate: AggregateHandle) =>
|
|
1381
|
+
alsoWrites(aggregate, "creates"),
|
|
1259
1382
|
alsoEnsuresMany: (aggregate: AggregateHandle) =>
|
|
1260
1383
|
alsoWrites(aggregate, "ensures", "many"),
|
|
1261
1384
|
alsoCreatesMany: (aggregate: AggregateHandle) =>
|
|
@@ -1264,8 +1387,10 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
1264
1387
|
alsoWrites(aggregate, "ensures", "optional"),
|
|
1265
1388
|
alsoCreatesOptional: (aggregate: AggregateHandle) =>
|
|
1266
1389
|
alsoWrites(aggregate, "creates", "optional"),
|
|
1267
|
-
alsoMutates: (aggregate: AggregateHandle) =>
|
|
1268
|
-
|
|
1390
|
+
alsoMutates: (aggregate: AggregateHandle) =>
|
|
1391
|
+
alsoWrites(aggregate, "mutates"),
|
|
1392
|
+
alsoArchives: (aggregate: AggregateHandle) =>
|
|
1393
|
+
alsoWrites(aggregate, "archives"),
|
|
1269
1394
|
alsoMutatesMany: (aggregate: AggregateHandle) =>
|
|
1270
1395
|
alsoWrites(aggregate, "mutates", "many"),
|
|
1271
1396
|
alsoArchivesMany: (aggregate: AggregateHandle) =>
|
|
@@ -1276,7 +1401,10 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
1276
1401
|
alsoWrites(aggregate, "archives", "optional"),
|
|
1277
1402
|
requires(
|
|
1278
1403
|
relation: string,
|
|
1279
|
-
opts?: {
|
|
1404
|
+
opts?: {
|
|
1405
|
+
object?: string;
|
|
1406
|
+
objectFrom?: { recordType: string; idField: string };
|
|
1407
|
+
}
|
|
1280
1408
|
): RequirableDirective<P> {
|
|
1281
1409
|
// THE ONE write-authz declaration (full-native Zanzibar, no mixed systems): the captured author
|
|
1282
1410
|
// must HOLD `relation` on the object for the one gate to admit. Default object is self-relative
|
|
@@ -1289,14 +1417,19 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
1289
1417
|
requiresRelationGate: {
|
|
1290
1418
|
relation,
|
|
1291
1419
|
object: opts?.object ?? "workspace:self",
|
|
1292
|
-
...(opts?.objectFrom !== undefined
|
|
1420
|
+
...(opts?.objectFrom !== undefined
|
|
1421
|
+
? { objectFrom: opts.objectFrom }
|
|
1422
|
+
: {}),
|
|
1293
1423
|
},
|
|
1294
1424
|
});
|
|
1295
1425
|
},
|
|
1296
1426
|
acceptsWorkspaceEffect(): RequirableDirective<P> {
|
|
1297
1427
|
return makeRequirable({ ...base, acceptsWorkspaceEffectGate: true });
|
|
1298
1428
|
},
|
|
1299
|
-
attestationAuthorized(owner: {
|
|
1429
|
+
attestationAuthorized(owner: {
|
|
1430
|
+
ownerObject: string;
|
|
1431
|
+
ownerField: string;
|
|
1432
|
+
}): RequirableDirective<P> {
|
|
1300
1433
|
// CROSS-CUSTODY ATTESTATION-AUTHORIZED (home_holons §3/§5): this directive's authorization is a
|
|
1301
1434
|
// carried §3 signed quote, not an author block — lowered into the `nomosAttestationGate` era key, so
|
|
1302
1435
|
// the one gate verifies the quote (binding its subject to the LAW-DECLARED `owner`), pins the principal
|
|
@@ -1319,13 +1452,22 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
1319
1452
|
},
|
|
1320
1453
|
});
|
|
1321
1454
|
},
|
|
1322
|
-
delegatedRole(
|
|
1455
|
+
delegatedRole(
|
|
1456
|
+
relation: string,
|
|
1457
|
+
opts?: { subjectField?: string; certField?: string }
|
|
1458
|
+
): RequirableDirective<P> {
|
|
1323
1459
|
// DELEGATED-AUTHORITY DELIVERY (delegated_authority_births.md): this directive's write folds the
|
|
1324
1460
|
// delegated `relation` tuple ONLY behind a carried DelegationCert — lowered into the `nomosDelegationGate`
|
|
1325
1461
|
// era key, so the one gate re-verifies P's grant to K_root OFFLINE on every peer. Field names default to
|
|
1326
1462
|
// the framework convention here (the kernel reads them from the law's declaration, hardcoding nothing).
|
|
1327
|
-
const prior = (
|
|
1328
|
-
|
|
1463
|
+
const prior = (
|
|
1464
|
+
base as {
|
|
1465
|
+
delegatedRoleGate?: {
|
|
1466
|
+
parentKeyObject?: string;
|
|
1467
|
+
parentKeyField?: string;
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
).delegatedRoleGate;
|
|
1329
1471
|
return makeRequirable({
|
|
1330
1472
|
...base,
|
|
1331
1473
|
delegatedRoleGate: {
|
|
@@ -1337,14 +1479,25 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
1337
1479
|
},
|
|
1338
1480
|
});
|
|
1339
1481
|
},
|
|
1340
|
-
delegatedFrom(parent: {
|
|
1482
|
+
delegatedFrom(parent: {
|
|
1483
|
+
parentKeyObject: string;
|
|
1484
|
+
parentKeyField: string;
|
|
1485
|
+
}): RequirableDirective<P> {
|
|
1341
1486
|
// .delegatedFrom(parent): bind the grantor to this workspace's parent key (the platform P). Refines the
|
|
1342
1487
|
// .delegatedRole() gate (a preceding .delegatedRole() supplies relation/fields; this adds the binding).
|
|
1343
|
-
const g = (
|
|
1344
|
-
|
|
1345
|
-
|
|
1488
|
+
const g = (
|
|
1489
|
+
base as {
|
|
1490
|
+
delegatedRoleGate?: {
|
|
1491
|
+
relation: string;
|
|
1492
|
+
subjectField: string;
|
|
1493
|
+
certField: string;
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
).delegatedRoleGate;
|
|
1346
1497
|
if (g === undefined) {
|
|
1347
|
-
throw new Error(
|
|
1498
|
+
throw new Error(
|
|
1499
|
+
"directive.delegatedFrom(): call .delegatedRole(relation) before .delegatedFrom(parent)"
|
|
1500
|
+
);
|
|
1348
1501
|
}
|
|
1349
1502
|
return makeRequirable({
|
|
1350
1503
|
...base,
|
|
@@ -1391,17 +1544,27 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
1391
1544
|
// captured-read lane (#58). Both accumulate, dedup (ref types by value, queries
|
|
1392
1545
|
// by id — later decl wins), sort — only the SET is the contract.
|
|
1393
1546
|
const refTypes = reads.filter((r): r is string => typeof r === "string");
|
|
1394
|
-
const queryDecls = reads.filter(
|
|
1547
|
+
const queryDecls = reads.filter(
|
|
1548
|
+
(r): r is QueryDecl => typeof r !== "string"
|
|
1549
|
+
);
|
|
1395
1550
|
const merged = [...new Set([...base.declaredReads, ...refTypes])].sort();
|
|
1396
|
-
const byId = new Map<string, QueryDecl>(
|
|
1551
|
+
const byId = new Map<string, QueryDecl>(
|
|
1552
|
+
(base.declaredQueryReads ?? []).map((q) => [q.id, q])
|
|
1553
|
+
);
|
|
1397
1554
|
for (const q of queryDecls) byId.set(q.id, q);
|
|
1398
|
-
const mergedQueries = [...byId.values()].sort((a, b) =>
|
|
1399
|
-
|
|
1555
|
+
const mergedQueries = [...byId.values()].sort((a, b) =>
|
|
1556
|
+
a.id < b.id ? -1 : a.id > b.id ? 1 : 0
|
|
1557
|
+
);
|
|
1558
|
+
return makeRequirable({
|
|
1559
|
+
...base,
|
|
1560
|
+
declaredReads: merged,
|
|
1561
|
+
declaredQueryReads: mergedQueries,
|
|
1562
|
+
});
|
|
1400
1563
|
},
|
|
1401
1564
|
readsFrom(
|
|
1402
1565
|
source: string | FromPayloadRef,
|
|
1403
1566
|
query: { id: string },
|
|
1404
|
-
opts?: { policy: TrustPolicyDecl }
|
|
1567
|
+
opts?: { policy: TrustPolicyDecl }
|
|
1405
1568
|
): RequirableDirective<P> {
|
|
1406
1569
|
// ATTESTED CROSS-WORKSPACE READ (slice B + dynamic policies): accumulate a STATIC (queryId, from)
|
|
1407
1570
|
// edge OR a DYNAMIC (queryId, policy, fromPayload) edge, dedup by canonical key, sort (only the
|
|
@@ -1413,16 +1576,20 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
1413
1576
|
throw new Error(
|
|
1414
1577
|
`readsFrom(fromPayload('${source.payloadField}'), '${query.id}'): a payload-routed foreign ` +
|
|
1415
1578
|
`read REQUIRES a trust policy — pass { policy: <trustPolicy(...) decl> } (the acceptable ` +
|
|
1416
|
-
`sources are LAW-STATE, so the runtime-named workspace is judged against a folded allow-list)
|
|
1579
|
+
`sources are LAW-STATE, so the runtime-named workspace is judged against a folded allow-list).`
|
|
1417
1580
|
);
|
|
1418
1581
|
}
|
|
1419
1582
|
if (!isTrustPolicyDecl(policy)) {
|
|
1420
1583
|
throw new Error(
|
|
1421
1584
|
`readsFrom(fromPayload('${source.payloadField}'), '${query.id}'): { policy } must be a ` +
|
|
1422
|
-
`trustPolicy(...) declaration
|
|
1585
|
+
`trustPolicy(...) declaration.`
|
|
1423
1586
|
);
|
|
1424
1587
|
}
|
|
1425
|
-
entry = {
|
|
1588
|
+
entry = {
|
|
1589
|
+
queryId: query.id,
|
|
1590
|
+
policy: policy.policyId,
|
|
1591
|
+
fromPayload: source.payloadField,
|
|
1592
|
+
};
|
|
1426
1593
|
if (policy.monotone === true) __registerMonotoneForeignQuery(query.id);
|
|
1427
1594
|
} else {
|
|
1428
1595
|
entry = { queryId: query.id, from: source };
|
|
@@ -1432,19 +1599,27 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
1432
1599
|
? `0\\0${e.from}\\0${e.queryId}`
|
|
1433
1600
|
: `1\\0${e.policy}\\0${e.fromPayload}\\0${e.queryId}`;
|
|
1434
1601
|
const prior = base.declaredForeignReads ?? [];
|
|
1435
|
-
const byKey = new Map<string, ForeignReadEdge>(
|
|
1602
|
+
const byKey = new Map<string, ForeignReadEdge>(
|
|
1603
|
+
prior.map((e) => [canon(e), e])
|
|
1604
|
+
);
|
|
1436
1605
|
byKey.set(canon(entry), entry);
|
|
1437
|
-
const merged = [...byKey.values()].sort((a, b) =>
|
|
1606
|
+
const merged = [...byKey.values()].sort((a, b) =>
|
|
1607
|
+
canon(a) < canon(b) ? -1 : canon(a) > canon(b) ? 1 : 0
|
|
1608
|
+
);
|
|
1438
1609
|
// Every admitted foreign read advances the reader-local source frontier in-band.
|
|
1439
1610
|
// This is a framework consequence of `.readsFrom`, so make it part of the typed
|
|
1440
1611
|
// effect signature here; tenant authors must not know the bookkeeping aggregate.
|
|
1441
|
-
return makeRequirable({
|
|
1442
|
-
|
|
1612
|
+
return makeRequirable({
|
|
1613
|
+
...base,
|
|
1614
|
+
declaredForeignReads: merged,
|
|
1615
|
+
}).alsoEnsures(NomosSourceFrontier);
|
|
1443
1616
|
},
|
|
1444
1617
|
emits(eventType: string, opts?: { max?: number }): RequirableDirective<P> {
|
|
1445
1618
|
// Accumulate one event type onto any prior emits; a `max` bound is recorded only
|
|
1446
1619
|
// when supplied (so `{}` vs `{max}` is a real contract difference).
|
|
1447
|
-
const merged: Record<string, { max?: number }> = {
|
|
1620
|
+
const merged: Record<string, { max?: number }> = {
|
|
1621
|
+
...base.declaredEmits,
|
|
1622
|
+
};
|
|
1448
1623
|
merged[eventType] = opts?.max !== undefined ? { max: opts.max } : {};
|
|
1449
1624
|
return makeRequirable({ ...base, declaredEmits: merged });
|
|
1450
1625
|
},
|
|
@@ -1457,20 +1632,35 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
1457
1632
|
// .creates target + any .alsoCreates). Non-law, additive — stored like `description`.
|
|
1458
1633
|
const createdIds = [
|
|
1459
1634
|
...(base.marker === "creates" ? [base.aggregateId] : []),
|
|
1460
|
-
...(base.secondaryOutputs ?? [])
|
|
1635
|
+
...(base.secondaryOutputs ?? [])
|
|
1636
|
+
.filter((o) => o.marker === "creates")
|
|
1637
|
+
.map((o) => o.aggregateId),
|
|
1461
1638
|
];
|
|
1462
|
-
return makeRequirable({
|
|
1639
|
+
return makeRequirable({
|
|
1640
|
+
...base,
|
|
1641
|
+
activityDescriptor: normalizeActivity(
|
|
1642
|
+
base.id,
|
|
1643
|
+
input,
|
|
1644
|
+
base.payloadSchema,
|
|
1645
|
+
createdIds
|
|
1646
|
+
),
|
|
1647
|
+
});
|
|
1463
1648
|
},
|
|
1464
1649
|
declaresRelation(rel: RelationDecl): RequirableDirective<P> {
|
|
1465
1650
|
// Accumulate onto any prior relations — additive, non-destructive. A relation lands in
|
|
1466
1651
|
// the manifest keyed by its `proposes` directive (cross_workspace.md §3.2.1).
|
|
1467
|
-
return makeRequirable({
|
|
1652
|
+
return makeRequirable({
|
|
1653
|
+
...base,
|
|
1654
|
+
declaredRelations: [...base.declaredRelations, rel],
|
|
1655
|
+
});
|
|
1468
1656
|
},
|
|
1469
1657
|
requiresHostCapability(...portIds: string[]): RequirableDirective<P> {
|
|
1470
1658
|
// Accumulate onto any prior host-capability ports, dedup, sort — order is incidental to
|
|
1471
1659
|
// the HOST/PORT contract; only the SET of required port ids matters. DISTINCT symbol from
|
|
1472
1660
|
// the AUTHZ `requiresCapability` (the two axes never conflate).
|
|
1473
|
-
const merged = [
|
|
1661
|
+
const merged = [
|
|
1662
|
+
...new Set([...base.declaredHostCapabilities, ...portIds]),
|
|
1663
|
+
].sort();
|
|
1474
1664
|
return makeRequirable({ ...base, declaredHostCapabilities: merged });
|
|
1475
1665
|
},
|
|
1476
1666
|
births(): RequirableDirective<P> {
|
|
@@ -1482,10 +1672,18 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
1482
1672
|
// `nomosReadGate` era key carries them by construction and the meter reads the `.births()` dispatch
|
|
1483
1673
|
// auto-captures (see `executeDirectiveToIntent`) CAS-verify on every lane. The era is the bundle:
|
|
1484
1674
|
// recompiling under this DSL is what arms the meter (every pre-era bundle replays byte-green).
|
|
1485
|
-
const byId = new Map<string, QueryDecl>(
|
|
1675
|
+
const byId = new Map<string, QueryDecl>(
|
|
1676
|
+
(base.declaredQueryReads ?? []).map((q) => [q.id, q])
|
|
1677
|
+
);
|
|
1486
1678
|
for (const q of BIRTH_METER_QUERIES) byId.set(q.id, q);
|
|
1487
|
-
const mergedQueries = [...byId.values()].sort((a, b) =>
|
|
1488
|
-
|
|
1679
|
+
const mergedQueries = [...byId.values()].sort((a, b) =>
|
|
1680
|
+
a.id < b.id ? -1 : a.id > b.id ? 1 : 0
|
|
1681
|
+
);
|
|
1682
|
+
return makeRequirable({
|
|
1683
|
+
...base,
|
|
1684
|
+
birthsChild: true,
|
|
1685
|
+
declaredQueryReads: mergedQueries,
|
|
1686
|
+
});
|
|
1489
1687
|
},
|
|
1490
1688
|
resolves(field: string): RequirableDirective<P> {
|
|
1491
1689
|
// THE DESIGNATED SURFACED RESOLVER: record which Surfaced field this directive resolves. Field
|
|
@@ -1494,13 +1692,13 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
1494
1692
|
if (typeof field !== "string" || field.length === 0) {
|
|
1495
1693
|
throw new Error(
|
|
1496
1694
|
`directive('${base.id}').resolves(field) needs a non-empty field name — the Surfaced field ` +
|
|
1497
|
-
`this directive is the designated resolver of (e.g. .resolves("title"))
|
|
1695
|
+
`this directive is the designated resolver of (e.g. .resolves("title")).`
|
|
1498
1696
|
);
|
|
1499
1697
|
}
|
|
1500
1698
|
if (base.resolvesField !== undefined && base.resolvesField !== field) {
|
|
1501
1699
|
throw new Error(
|
|
1502
1700
|
`directive('${base.id}').resolves("${field}"): already .resolves("${base.resolvesField}") — ` +
|
|
1503
|
-
`a directive designates AT MOST ONE Surfaced field (write a second directive for the other)
|
|
1701
|
+
`a directive designates AT MOST ONE Surfaced field (write a second directive for the other).`
|
|
1504
1702
|
);
|
|
1505
1703
|
}
|
|
1506
1704
|
return makeRequirable({ ...base, resolvesField: field });
|
|
@@ -1509,13 +1707,17 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
1509
1707
|
// PROOF-TOOLING PRODUCER MARKER (symmetric to `.requires`): record that this directive establishes
|
|
1510
1708
|
// `relation` (its plan folds a granting `writeTuple`). NO kernel/manifest effect — a pure static hint
|
|
1511
1709
|
// the proof generator's relation-bootstrap leg reads. Accumulate + dedup; additive + non-destructive.
|
|
1512
|
-
const merged = [
|
|
1710
|
+
const merged = [
|
|
1711
|
+
...new Set([...(base.grantsRelation ?? []), relation]),
|
|
1712
|
+
].sort();
|
|
1513
1713
|
return makeRequirable({ ...base, grantsRelation: merged });
|
|
1514
1714
|
},
|
|
1515
1715
|
};
|
|
1516
1716
|
}
|
|
1517
1717
|
|
|
1518
|
-
export function directive<const Id extends string>(
|
|
1718
|
+
export function directive<const Id extends string>(
|
|
1719
|
+
id: Id
|
|
1720
|
+
): DirectiveMarkerStep<Id> {
|
|
1519
1721
|
return new DirectiveMarkerStep(id);
|
|
1520
1722
|
}
|
|
1521
1723
|
|
|
@@ -1525,7 +1727,7 @@ export function directive<const Id extends string>(id: Id): DirectiveMarkerStep<
|
|
|
1525
1727
|
* framework lowering and temporary migration law, never a second application lane.
|
|
1526
1728
|
*/
|
|
1527
1729
|
export function nomosImplementationDetail<T extends object>(
|
|
1528
|
-
declaration: T
|
|
1730
|
+
declaration: T
|
|
1529
1731
|
): T & { readonly businessBirthInternal: true } {
|
|
1530
1732
|
return { ...declaration, businessBirthInternal: true };
|
|
1531
1733
|
}
|