@githolon/dsl 0.101.6 → 0.102.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/dart/CHANGELOG.md +13 -0
- package/dart/lib/nomos_client.dart +3 -52
- package/dart/lib/src/dev_cloud.dart +5 -0
- package/dart/lib/src/home.dart +10 -15
- package/dart/lib/src/nms_conformance_runtime.dart +12 -0
- package/dart/lib/src/nms_generated_runtime.dart +29 -0
- package/dart/lib/src/nms_test_environment.dart +8 -0
- package/dart/lib/src/node_runner_transport.dart +56 -18
- package/dart/lib/src/provider.dart +5 -1
- package/dart/lib/src/record_origin.dart +55 -0
- package/dart/lib/src/runner/node-entry.mjs +9105 -0
- package/dart/lib/src/runner/offline-install.mjs +4729 -0
- package/dart/lib/src/session_bound.dart +23 -10
- package/dart/lib/src/source_trust.dart +51 -0
- package/dart/lib/src/test_harness.dart +490 -58
- package/dart/lib/src/webview_bridge.dart +167 -58
- package/dart/lib/src/workspace_name.dart +7 -1
- package/dart/pubspec.lock +3 -3
- package/dart/pubspec.yaml +4 -2
- package/dart/test/acquire_session_credentials_test.dart +34 -17
- package/dart/test/acquire_session_test.dart +109 -24
- package/dart/test/adv_realm_interference_test.dart +227 -99
- package/dart/test/bundle_version_pairing_test.dart +1 -1
- package/dart/test/cloud_bound_topology_test.dart +1 -1
- package/dart/test/cluster_attested_test.dart +52 -35
- package/dart/test/cluster_births_test.dart +5 -2
- package/dart/test/cluster_device_keyed_acceptance_test.dart +5 -2
- package/dart/test/cluster_local_autosync_test.dart +5 -2
- package/dart/test/cluster_scale_test.dart +5 -2
- package/dart/test/compat_test.dart +1 -1
- package/dart/test/dlq_session_surface_test.dart +2 -1
- package/dart/test/errors_test.dart +1 -1
- package/dart/test/first_app_gauntlet_test.dart +78 -51
- package/dart/test/four_layer_nested_topology_test.dart +1 -1
- package/dart/test/harness_phase_timeout_test.dart +1 -1
- package/dart/test/home_parity_test.dart +9 -5
- package/dart/test/local_sync_inert_test.dart +22 -14
- package/dart/test/node_runner_transport_test.dart +9 -5
- package/dart/test/null_slot_test.dart +1 -1
- package/dart/test/parent_signer_recovery_test.dart +5 -2
- package/dart/test/schema_validation_test.dart +1 -1
- package/dart/test/sealing_client_test.dart +2 -1
- package/dart/test/session_bound_test.dart +92 -119
- package/dart/test/stale_bundle_test.dart +1 -1
- package/dart/test/test_harness_test.dart +77 -22
- package/dart/test/vo_read_typing_test.dart +32 -14
- package/dart/test/watch_after_offer_stall_test.dart +2 -1
- package/dart/test/webview_bridge_test.dart +17 -4
- package/dart/test/wire_test.dart +1 -1
- package/dart_types/CHANGELOG.md +8 -0
- package/dart_types/lib/nomos_types.dart +7 -6
- package/dart_types/lib/src/activity.dart +23 -13
- package/dart_types/lib/src/foreign_ref.dart +61 -112
- package/dart_types/lib/src/invitation.dart +96 -0
- package/dart_types/lib/src/nomos_ref.dart +97 -59
- package/dart_types/pubspec.yaml +1 -1
- package/dart_types/test/surfaced_test.dart +125 -19
- package/package.json +7 -3
- package/runtime/generated-application-host.js +8851 -0
- package/src/activity.ts +2 -2
- package/src/aggregate.ts +24 -1
- package/src/application_composition.ts +282 -0
- package/src/application_topology.ts +151 -0
- package/src/attested_core.ts +2 -2
- package/src/authoring_context.ts +9 -0
- package/src/birth_meter_ensure.ts +1 -1
- package/src/births.ts +53 -5
- package/src/build_package.ts +79 -71
- package/src/business_birth.ts +93 -10
- package/src/clear_directive.ts +3 -1
- package/src/codegen_dart.ts +369 -420
- package/src/codegen_dart_application.ts +1508 -0
- package/src/codegen_dart_client.ts +156 -95
- package/src/codegen_dart_scenario.ts +299 -0
- package/src/codegen_openapi.ts +8 -3
- package/src/codegen_proof.ts +1113 -1137
- package/src/codegen_provider_dart.ts +12 -1
- package/src/codegen_ts.ts +1176 -496
- package/src/compat_package_main.ts +22 -10
- package/src/compile_package_main.ts +1536 -337
- package/src/contract.ts +10 -4
- package/src/dart_barrel_guard.ts +5 -1
- package/src/describe_model.ts +13 -5
- package/src/directive.ts +244 -5
- package/src/engine_entry.ts +56 -13
- package/src/engine_javascript.ts +31 -0
- package/src/evolve_preview.ts +3 -12
- package/src/framework/application_home.ts +24 -10
- package/src/framework/capability.ts +15 -1
- package/src/framework/domain_lifecycle.ts +8 -0
- package/src/framework/framework_aggregates.ts +2 -2
- package/src/framework/genesis.ts +11 -1
- package/src/framework/home.ts +178 -75
- package/src/framework/impure_capability.ts +24 -7
- package/src/framework/relations.ts +13 -5
- package/src/framework/workspaces.ts +13 -12
- package/src/generated_client_names.ts +42 -3
- package/src/idof_lint.ts +65 -13
- package/src/index.ts +46 -12
- package/src/intent.ts +587 -0
- package/src/law_check.ts +64 -16
- package/src/precondition.ts +16 -4
- package/src/read_projection_namespace.ts +2 -7
- package/src/recipient.ts +33 -0
- package/src/relation_schema.ts +93 -0
- package/src/secondary_ensure.ts +11 -2
- package/src/shape_compat.ts +11 -1
- package/src/usd.ts +30 -8
- package/src/usd_layers.ts +447 -246
- package/src/usd_state.ts +1 -1
- package/src/value_object.ts +49 -13
- package/src/wire_encode.ts +124 -26
- package/src/workspace_sharing.ts +269 -0
- package/src/zod_introspection.ts +84 -0
- package/dart/example/nomos_secure_key_store.dart +0 -82
- package/dart/example/nomos_webview_transport.dart +0 -63
- package/dart/lib/nomos_headless.dart +0 -50
- package/dart/lib/testing.dart +0 -14
- package/dart/test/co2_product_gauntlet_test.dart +0 -244
- package/src/framework/identity.ts +0 -537
package/src/contract.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// aggregate's domainHash moves only when its OWN fields change. Contracts ride the IR as ADDITIVE metadata
|
|
8
8
|
// (like value objects — era-safe, omit-when-empty) and are stable-id tracked for evolution.
|
|
9
9
|
//
|
|
10
|
-
// const CatalogueListingContract =
|
|
10
|
+
// const CatalogueListingContract = recordContract("CatalogueListing", { listingId: t.string(), name: t.string() });
|
|
11
11
|
// const CatalogueListing = aggregate("CatalogueListing", { listingId: t.string().merge(Lww), … })
|
|
12
12
|
// .implements(CatalogueListingContract); // ← compile-checked: the aggregate SATISFIES the shape
|
|
13
13
|
// const EstateAsset = aggregate("EstateAsset", {
|
|
@@ -39,21 +39,27 @@ export function isContractHandle(x: unknown): x is ContractHandle {
|
|
|
39
39
|
return typeof x === "object" && x !== null && (x as { __isContract?: unknown }).__isContract === true;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/** The generated public type name for a contract. Contract ids are already public business names;
|
|
43
|
+
* preserve an explicit `...Contract` suffix rather than producing `...ContractContract`. */
|
|
44
|
+
export function contractTypeName(id: string): string {
|
|
45
|
+
return id.endsWith("Contract") ? id : `${id}Contract`;
|
|
46
|
+
}
|
|
47
|
+
|
|
42
48
|
/**
|
|
43
49
|
* Declare a domain CONTRACT — a named public shape. Reuse the `t.*` field builders for the fields (the merge
|
|
44
50
|
* driver is irrelevant — a contract is a shape). Export it; a concrete aggregate `.implements(...)` it, and
|
|
45
51
|
* other domains reference it via `t.ref` / `t.foreignRef`. Purely additive: a package with no contract emits
|
|
46
52
|
* byte-identically to before.
|
|
47
53
|
*/
|
|
48
|
-
export function
|
|
54
|
+
export function recordContract<const Id extends string, const F extends Record<string, ContractField>>(
|
|
49
55
|
id: Id,
|
|
50
56
|
fields: F,
|
|
51
57
|
): ContractHandle<Id, F> {
|
|
52
58
|
if (typeof id !== "string" || id.length === 0) {
|
|
53
|
-
throw new Error("
|
|
59
|
+
throw new Error("recordContract(id, fields): the id must be a non-empty string (the stable public name).");
|
|
54
60
|
}
|
|
55
61
|
if (typeof fields !== "object" || fields === null || Object.keys(fields).length === 0) {
|
|
56
|
-
throw new Error(`
|
|
62
|
+
throw new Error(`recordContract('${id}'): declare at least one field (a record contract is a public shape).`);
|
|
57
63
|
}
|
|
58
64
|
return { id, fields, __isContract: true };
|
|
59
65
|
}
|
package/src/dart_barrel_guard.ts
CHANGED
|
@@ -27,7 +27,7 @@ export interface BarrelExportedFile {
|
|
|
27
27
|
readonly src: string;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
/** Declared PUBLIC top-level type/typedef names in a Dart source
|
|
30
|
+
/** Declared PUBLIC top-level type/typedef/named-extension names in a Dart source.
|
|
31
31
|
* Private (`_`-prefixed) names never leak through an `export`, so they cannot collide — excluded. */
|
|
32
32
|
export function publicDartDeclarations(src: string): Set<string> {
|
|
33
33
|
const names = new Set<string>();
|
|
@@ -39,6 +39,10 @@ export function publicDartDeclarations(src: string): Set<string> {
|
|
|
39
39
|
for (let m = re.exec(src); m !== null; m = re.exec(src)) {
|
|
40
40
|
if (!m[1]!.startsWith("_")) names.add(m[1]!);
|
|
41
41
|
}
|
|
42
|
+
const extension = /^extension\s+([A-Za-z][A-Za-z0-9_]*)\s+(?:<[^\n>]+>\s+)?on\s+/gm;
|
|
43
|
+
for (let m = extension.exec(src); m !== null; m = extension.exec(src)) {
|
|
44
|
+
if (!m[1]!.startsWith("_")) names.add(m[1]!);
|
|
45
|
+
}
|
|
42
46
|
return names;
|
|
43
47
|
}
|
|
44
48
|
|
package/src/describe_model.ts
CHANGED
|
@@ -145,7 +145,7 @@ const publicAggregates = (mod: DomainModule): AggregateHandle[] =>
|
|
|
145
145
|
);
|
|
146
146
|
|
|
147
147
|
const publicDirectives = (mod: DomainModule): Directive<unknown>[] =>
|
|
148
|
-
(mod.directives as Directive<unknown>[]).filter((directive) =>
|
|
148
|
+
((mod.applicationIntents ?? mod.directives) as Directive<unknown>[]).filter((directive) =>
|
|
149
149
|
directive.framework !== true
|
|
150
150
|
&& (directive as { businessBirthInternal?: unknown }).businessBirthInternal !== true
|
|
151
151
|
);
|
|
@@ -181,7 +181,11 @@ const aggregateItem = (domain: string, aggregate: AggregateHandle): NomosDescrib
|
|
|
181
181
|
};
|
|
182
182
|
};
|
|
183
183
|
|
|
184
|
-
const actionItem = (
|
|
184
|
+
const actionItem = (
|
|
185
|
+
domain: string,
|
|
186
|
+
directive: Directive<unknown>,
|
|
187
|
+
publicAggregateIds: ReadonlySet<string>,
|
|
188
|
+
): NomosDescribeItemModel => {
|
|
185
189
|
const businessBirth = businessBirthOf(directive);
|
|
186
190
|
const actionId = businessBirth === undefined ? directive.id : "birth";
|
|
187
191
|
const actionLabel = businessBirth === undefined
|
|
@@ -190,7 +194,7 @@ const actionItem = (domain: string, directive: Directive<unknown>): NomosDescrib
|
|
|
190
194
|
const outputs = [
|
|
191
195
|
{ effect: directive.marker, type: directive.aggregateId },
|
|
192
196
|
...(directive.secondaryOutputs ?? []).map((output) => ({ effect: output.marker, type: output.aggregateId })),
|
|
193
|
-
];
|
|
197
|
+
].filter((output) => publicAggregateIds.has(output.type));
|
|
194
198
|
const effectVerb = (marker: string): string => ({
|
|
195
199
|
creates: "create",
|
|
196
200
|
mutates: "change",
|
|
@@ -207,7 +211,7 @@ const actionItem = (domain: string, directive: Directive<unknown>): NomosDescrib
|
|
|
207
211
|
id: `${domain}.${actionId}`,
|
|
208
212
|
label: actionLabel,
|
|
209
213
|
summary: businessBirth === undefined
|
|
210
|
-
? directive.description ?? `${actionLabel} will ${effect}.`
|
|
214
|
+
? directive.description ?? (effect === "" ? `${actionLabel} changes the business.` : `${actionLabel} will ${effect}.`)
|
|
211
215
|
: `${actionLabel} will ${effect}.`,
|
|
212
216
|
details: {
|
|
213
217
|
domain,
|
|
@@ -407,7 +411,11 @@ export function buildNomosDescribeModel(
|
|
|
407
411
|
});
|
|
408
412
|
const questions = modules.flatMap(questionItems);
|
|
409
413
|
const nouns = aggregates.map(({ domain, aggregate }) => aggregateItem(domain, aggregate));
|
|
410
|
-
const
|
|
414
|
+
const publicByDomain = new Map(modules.map((mod) => [
|
|
415
|
+
mod.domain ?? mod.name,
|
|
416
|
+
new Set(publicAggregates(mod).map((aggregate) => aggregate.id)),
|
|
417
|
+
]));
|
|
418
|
+
const verbs = actions.map(({ domain, directive }) => actionItem(domain, directive, publicByDomain.get(domain) ?? new Set()));
|
|
411
419
|
const rules = modules.flatMap(ruleItems);
|
|
412
420
|
const domainCount = new Set(modules.map((mod) => mod.domain ?? mod.name)).size;
|
|
413
421
|
|
package/src/directive.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import type { z } from "zod";
|
|
19
19
|
import type { AggregateHandle, ReadModelRow } from "./aggregate.js";
|
|
20
|
+
import { isRuntimeLaw } from "./authoring_context.js";
|
|
20
21
|
import type { Field } from "./fields.js";
|
|
21
22
|
import type { PlannedOp } from "./ops.js";
|
|
22
23
|
import type { Ports } from "./ctx.js";
|
|
@@ -24,6 +25,7 @@ import type { RelationDecl } from "./relation.js";
|
|
|
24
25
|
import type { QueryDecl } from "./query.js";
|
|
25
26
|
import { normalizeActivity, type ActivityInput, type ActivityDescriptor } from "./activity.js";
|
|
26
27
|
import { BIRTH_METER_QUERIES } from "./birth_meter.js";
|
|
28
|
+
import { zodDef, zodKind, zodObjectShape } from "./zod_introspection.js";
|
|
27
29
|
import {
|
|
28
30
|
isFromPayloadRef,
|
|
29
31
|
isTrustPolicyDecl,
|
|
@@ -56,6 +58,8 @@ export type ReferentialMarker = "creates" | "mutates" | "ensures" | "archives";
|
|
|
56
58
|
export interface PreconditionDescriptor {
|
|
57
59
|
/** The guarded aggregate's TYPE id (the ref type the synthesized invariant resolves). */
|
|
58
60
|
readonly aggregateId: string;
|
|
61
|
+
/** Stored business fields used to distinguish an unresolved metadata shell from a real row. */
|
|
62
|
+
readonly fieldNames: readonly string[];
|
|
59
63
|
/** Pure payload → target instance id (no clock, no IO — re-derived identically on every peer). */
|
|
60
64
|
readonly idField: (payload: unknown) => string;
|
|
61
65
|
/** The fixed-predicate specialization, or `predicate` for the general `.where` form. */
|
|
@@ -86,6 +90,43 @@ export interface PreconditionDescriptor {
|
|
|
86
90
|
export interface SecondaryOutput {
|
|
87
91
|
readonly marker: ReferentialMarker;
|
|
88
92
|
readonly aggregateId: string;
|
|
93
|
+
/** This action may affect more than one record of this aggregate type. */
|
|
94
|
+
readonly many?: true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Frontend-only application shaping for a foreign reference carried by a
|
|
99
|
+
* directive payload. Tenant code supplies the referenced business record;
|
|
100
|
+
* generated client state privately supplies its workspace locator and wire id.
|
|
101
|
+
* This does not change the law or its replayed payload shape.
|
|
102
|
+
*/
|
|
103
|
+
export interface BusinessRecordInput {
|
|
104
|
+
/** Payload id field, which must also be a foreignRef field on the target aggregate. */
|
|
105
|
+
readonly field: string;
|
|
106
|
+
/** Optional business argument name; defaults to the id field without its `Id` suffix. */
|
|
107
|
+
readonly as?: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Application-only composition for a business action whose law performs a
|
|
112
|
+
* dynamic attested read. The generated stateful client first obtains the
|
|
113
|
+
* source's signed answer, privately seats its identity through an internal
|
|
114
|
+
* directive, then submits the business action carrying that attestation.
|
|
115
|
+
*/
|
|
116
|
+
export interface BusinessSourceTrust {
|
|
117
|
+
/** Hidden law directive that records the accepted source identity. */
|
|
118
|
+
readonly viaDirectiveId: string;
|
|
119
|
+
/** Payload field routing the dynamic source read. */
|
|
120
|
+
readonly fromPayload: string;
|
|
121
|
+
/** Source query to attest. */
|
|
122
|
+
readonly queryId: string;
|
|
123
|
+
/** Source-query argument -> business payload field. */
|
|
124
|
+
readonly queryArgs: Readonly<Record<string, string>>;
|
|
125
|
+
/** Hidden trust payload field -> business payload field. */
|
|
126
|
+
readonly copy: Readonly<Record<string, string>>;
|
|
127
|
+
/** Hidden trust payload fields populated from the signed envelope. */
|
|
128
|
+
readonly genesisField: string;
|
|
129
|
+
readonly keyHashesField: string;
|
|
89
130
|
}
|
|
90
131
|
|
|
91
132
|
export interface Directive<P = unknown> {
|
|
@@ -101,7 +142,26 @@ export interface Directive<P = unknown> {
|
|
|
101
142
|
*/
|
|
102
143
|
readonly secondaryOutputs?: readonly SecondaryOutput[];
|
|
103
144
|
readonly payloadSchema: z.ZodType<P>;
|
|
145
|
+
/**
|
|
146
|
+
* One lawful sample of this directive's complete payload. Proof generation
|
|
147
|
+
* and author-time checks prefer it when constraints span several fields and
|
|
148
|
+
* therefore cannot be inferred from the individual Zod leaves. Validated at
|
|
149
|
+
* declaration time; non-law tooling metadata, omitted when undeclared.
|
|
150
|
+
*/
|
|
151
|
+
readonly payloadExample?: Readonly<P>;
|
|
104
152
|
readonly plan: (payload: P, ctx: Ports) => PlannedOp[];
|
|
153
|
+
/** Application-only typed-record inputs. Omitted from canonical law output. */
|
|
154
|
+
readonly businessRecordInputs?: readonly BusinessRecordInput[];
|
|
155
|
+
/** Application-only source-trust composition. Omitted from canonical law. */
|
|
156
|
+
readonly businessSourceTrust?: BusinessSourceTrust;
|
|
157
|
+
/** Application-only identity binding. The generated application removes this
|
|
158
|
+
* field from tenant input and fills it from its verified signed-in principal. */
|
|
159
|
+
readonly businessPrincipalField?: string;
|
|
160
|
+
/**
|
|
161
|
+
* Signed framework or lowering law retained by Nomos but omitted from tenant
|
|
162
|
+
* application APIs. Set only through `nomosImplementationDetail(...)`.
|
|
163
|
+
*/
|
|
164
|
+
readonly businessBirthInternal?: true;
|
|
105
165
|
/**
|
|
106
166
|
* FRAMEWORK-SHARED brand (Dart-codegen hygiene). Set (by `framework/framework_aggregates.ts`) ONLY on
|
|
107
167
|
* the framework-owned directive singletons declared in `dsl/src/framework/` (the relate/signer/enroll
|
|
@@ -353,6 +413,38 @@ export interface Directive<P = unknown> {
|
|
|
353
413
|
* each `plan` — the kernel resolves it WITHIN the one shared `admit` gate.
|
|
354
414
|
*/
|
|
355
415
|
export interface RequirableDirective<P> extends Directive<P> {
|
|
416
|
+
/**
|
|
417
|
+
* Supply one lawful payload for proof/check synthesis when the schema has
|
|
418
|
+
* cross-field refinements. The value is validated immediately against this
|
|
419
|
+
* directive's payload schema and is never sent unless a generated proof uses
|
|
420
|
+
* it. Returns a new immutable directive.
|
|
421
|
+
*/
|
|
422
|
+
example(payload: P): RequirableDirective<P>;
|
|
423
|
+
/** Bind one payload field to the generated application's verified principal.
|
|
424
|
+
* The field remains signed law and may be checked by invariants, but disappears
|
|
425
|
+
* from tenant payload types so applications cannot claim to be another person. */
|
|
426
|
+
fromCurrentPrincipal(field: string): RequirableDirective<P>;
|
|
427
|
+
/**
|
|
428
|
+
* Replace a payload foreign-reference locator with the referenced business
|
|
429
|
+
* record on generated application surfaces. The target aggregate's
|
|
430
|
+
* `foreignRef({ workspace: ... })` declaration supplies the paired workspace
|
|
431
|
+
* field; generated client state supplies its value privately.
|
|
432
|
+
*/
|
|
433
|
+
fromRecord(field: string, opts?: { as?: string }): RequirableDirective<P>;
|
|
434
|
+
/**
|
|
435
|
+
* Keep dynamic-source identity, key hashing and attestation carriage inside
|
|
436
|
+
* the generated application action. [via] remains signed law but must be
|
|
437
|
+
* wrapped in `nomosImplementationDetail(...)` at its export site.
|
|
438
|
+
*/
|
|
439
|
+
trustsSource(opts: {
|
|
440
|
+
via: Directive<any>;
|
|
441
|
+
fromPayload: string;
|
|
442
|
+
query: { id: string };
|
|
443
|
+
queryArgs: Readonly<Record<string, string>>;
|
|
444
|
+
copy?: Readonly<Record<string, string>>;
|
|
445
|
+
genesisField?: string;
|
|
446
|
+
keyHashesField?: string;
|
|
447
|
+
}): RequirableDirective<P>;
|
|
356
448
|
/**
|
|
357
449
|
* MULTI-OUTPUT: declare an ADDITIONAL aggregate this directive writes (beyond its primary marker target),
|
|
358
450
|
* so generated docs/proof/client metadata name every output ("writes A + B") instead of one by convention.
|
|
@@ -361,8 +453,11 @@ export interface RequirableDirective<P> extends Directive<P> {
|
|
|
361
453
|
* kernel folds by the plan's ops regardless — this is descriptive law metadata, not a write-restriction.
|
|
362
454
|
*/
|
|
363
455
|
alsoWrites(aggregate: AggregateHandle, marker: ReferentialMarker): RequirableDirective<P>;
|
|
456
|
+
alsoWritesMany(aggregate: AggregateHandle, marker: ReferentialMarker): RequirableDirective<P>;
|
|
364
457
|
alsoEnsures(aggregate: AggregateHandle): RequirableDirective<P>;
|
|
365
458
|
alsoCreates(aggregate: AggregateHandle): RequirableDirective<P>;
|
|
459
|
+
alsoEnsuresMany(aggregate: AggregateHandle): RequirableDirective<P>;
|
|
460
|
+
alsoCreatesMany(aggregate: AggregateHandle): RequirableDirective<P>;
|
|
366
461
|
alsoMutates(aggregate: AggregateHandle): RequirableDirective<P>;
|
|
367
462
|
alsoArchives(aggregate: AggregateHandle): RequirableDirective<P>;
|
|
368
463
|
/**
|
|
@@ -618,6 +713,7 @@ class DirectivePlanStep<Id extends string, P> {
|
|
|
618
713
|
return this.with(
|
|
619
714
|
{
|
|
620
715
|
aggregateId: this.aggregate.id,
|
|
716
|
+
fieldNames: Object.keys(this.aggregate.fields).sort(),
|
|
621
717
|
idField,
|
|
622
718
|
kind: "absent",
|
|
623
719
|
predicate: (row) => row === undefined,
|
|
@@ -639,6 +735,7 @@ class DirectivePlanStep<Id extends string, P> {
|
|
|
639
735
|
): DirectivePlanStep<Id, P> {
|
|
640
736
|
return this.with({
|
|
641
737
|
aggregateId: aggregate.id,
|
|
738
|
+
fieldNames: Object.keys(aggregate.fields).sort(),
|
|
642
739
|
idField: idFn as (payload: unknown) => string,
|
|
643
740
|
kind: "exists",
|
|
644
741
|
predicate: (row) => row !== undefined,
|
|
@@ -661,6 +758,7 @@ class DirectivePlanStep<Id extends string, P> {
|
|
|
661
758
|
): DirectivePlanStep<Id, P> {
|
|
662
759
|
return this.with({
|
|
663
760
|
aggregateId: aggregate.id,
|
|
761
|
+
fieldNames: Object.keys(aggregate.fields).sort(),
|
|
664
762
|
idField: idFn as (payload: unknown) => string,
|
|
665
763
|
kind: "predicate",
|
|
666
764
|
predicate: predicate as (row: Record<string, unknown> | undefined) => boolean,
|
|
@@ -758,20 +856,148 @@ export function recordObject(
|
|
|
758
856
|
* declaration site reads `directive(id)…plan(fn).requires("cap", p => […])`.
|
|
759
857
|
*/
|
|
760
858
|
function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
761
|
-
const alsoWrites = (
|
|
859
|
+
const alsoWrites = (
|
|
860
|
+
aggregate: AggregateHandle,
|
|
861
|
+
marker: ReferentialMarker,
|
|
862
|
+
many = false,
|
|
863
|
+
): RequirableDirective<P> => {
|
|
762
864
|
// Dedup by (marker, aggregateId) + the primary target — declaring the same output twice is a no-op, and
|
|
763
865
|
// re-declaring the primary aggregate is ignored (it is already the marker target). Sorted at emit time.
|
|
764
|
-
const next: SecondaryOutput = {
|
|
866
|
+
const next: SecondaryOutput = {
|
|
867
|
+
marker,
|
|
868
|
+
aggregateId: aggregate.id,
|
|
869
|
+
...(many ? { many: true as const } : {}),
|
|
870
|
+
};
|
|
765
871
|
const isPrimary = aggregate.id === base.aggregateId;
|
|
766
|
-
const
|
|
767
|
-
|
|
872
|
+
const existing = (base.secondaryOutputs ?? []).find(
|
|
873
|
+
(s) => s.marker === marker && s.aggregateId === aggregate.id,
|
|
874
|
+
);
|
|
875
|
+
if (isPrimary || (existing !== undefined && (!many || existing.many === true))) {
|
|
876
|
+
return makeRequirable(base);
|
|
877
|
+
}
|
|
878
|
+
if (existing !== undefined) {
|
|
879
|
+
return makeRequirable({
|
|
880
|
+
...base,
|
|
881
|
+
secondaryOutputs: (base.secondaryOutputs ?? []).map((output) =>
|
|
882
|
+
output === existing ? next : output,
|
|
883
|
+
),
|
|
884
|
+
});
|
|
885
|
+
}
|
|
768
886
|
return makeRequirable({ ...base, secondaryOutputs: [...(base.secondaryOutputs ?? []), next] });
|
|
769
887
|
};
|
|
770
888
|
return {
|
|
771
889
|
...base,
|
|
890
|
+
example(payload: P): RequirableDirective<P> {
|
|
891
|
+
if (isRuntimeLaw()) {
|
|
892
|
+
// The Node authoring pass already proved this example. Executable law retains it as proof
|
|
893
|
+
// metadata but does not rerun host-shaped Zod checks when a lazy domain is first forced.
|
|
894
|
+
return makeRequirable({ ...base, payloadExample: payload });
|
|
895
|
+
}
|
|
896
|
+
const parsed = base.payloadSchema.safeParse(payload);
|
|
897
|
+
if (!parsed.success) throw new Error(
|
|
898
|
+
`directive('${base.id}').example(...) must satisfy its payload schema: ${parsed.error.message}`,
|
|
899
|
+
);
|
|
900
|
+
return makeRequirable({ ...base, payloadExample: parsed.data });
|
|
901
|
+
},
|
|
902
|
+
fromCurrentPrincipal(field: string): RequirableDirective<P> {
|
|
903
|
+
const name = field.trim();
|
|
904
|
+
if (name.length === 0) {
|
|
905
|
+
throw new Error("directive.fromCurrentPrincipal(field): field must be a non-empty payload field name");
|
|
906
|
+
}
|
|
907
|
+
if (zodKind(base.payloadSchema) !== "object") {
|
|
908
|
+
throw new Error(
|
|
909
|
+
`directive('${base.id}').fromCurrentPrincipal('${name}') needs an object payload schema`,
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
const principalSchema = zodObjectShape(base.payloadSchema)[name];
|
|
913
|
+
if (principalSchema === undefined) {
|
|
914
|
+
throw new Error(
|
|
915
|
+
`directive('${base.id}').fromCurrentPrincipal('${name}') names no payload field`,
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
let principalValue = principalSchema;
|
|
919
|
+
while (zodKind(principalValue) === "optional" || zodKind(principalValue) === "default") {
|
|
920
|
+
principalValue = zodDef(principalValue).innerType!;
|
|
921
|
+
}
|
|
922
|
+
if (zodKind(principalValue) !== "string") {
|
|
923
|
+
throw new Error(
|
|
924
|
+
`directive('${base.id}').fromCurrentPrincipal('${name}') must bind a string payload field`,
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
if (base.businessPrincipalField !== undefined && base.businessPrincipalField !== name) {
|
|
928
|
+
throw new Error(
|
|
929
|
+
`directive('${base.id}').fromCurrentPrincipal(...) may bind only one identity field`,
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
return makeRequirable({ ...base, businessPrincipalField: name });
|
|
933
|
+
},
|
|
934
|
+
fromRecord(field: string, opts?: { as?: string }): RequirableDirective<P> {
|
|
935
|
+
if (typeof field !== "string" || field.length === 0) {
|
|
936
|
+
throw new Error("directive.fromRecord(field): field must be a non-empty payload field name");
|
|
937
|
+
}
|
|
938
|
+
const prior = base.businessRecordInputs ?? [];
|
|
939
|
+
const next: BusinessRecordInput = {
|
|
940
|
+
field,
|
|
941
|
+
...(opts?.as === undefined ? {} : { as: opts.as }),
|
|
942
|
+
};
|
|
943
|
+
return makeRequirable({
|
|
944
|
+
...base,
|
|
945
|
+
businessRecordInputs: [...prior.filter((candidate) => candidate.field !== field), next],
|
|
946
|
+
});
|
|
947
|
+
},
|
|
948
|
+
trustsSource(opts): RequirableDirective<P> {
|
|
949
|
+
if (base.businessSourceTrust !== undefined) {
|
|
950
|
+
throw new Error(
|
|
951
|
+
`directive('${base.id}').trustsSource(...) may be declared only once`,
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
const fromPayload = opts.fromPayload.trim();
|
|
955
|
+
if (fromPayload.length === 0 || opts.query.id.trim().length === 0) {
|
|
956
|
+
throw new Error(
|
|
957
|
+
`directive('${base.id}').trustsSource(...) needs a source payload field and query`,
|
|
958
|
+
);
|
|
959
|
+
}
|
|
960
|
+
const matchingRead = (base.declaredForeignReads ?? []).some(
|
|
961
|
+
(edge) =>
|
|
962
|
+
!("from" in edge) &&
|
|
963
|
+
edge.fromPayload === fromPayload &&
|
|
964
|
+
edge.queryId === opts.query.id,
|
|
965
|
+
);
|
|
966
|
+
if (!matchingRead) {
|
|
967
|
+
throw new Error(
|
|
968
|
+
`directive('${base.id}').trustsSource(...) must follow its matching dynamic .readsFrom(...) declaration`,
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
const via = opts.via as Directive<unknown> & {
|
|
972
|
+
businessBirthInternal?: unknown;
|
|
973
|
+
};
|
|
974
|
+
if (via.businessBirthInternal !== true) {
|
|
975
|
+
throw new Error(
|
|
976
|
+
`directive('${base.id}').trustsSource(...): the trust directive must be wrapped in nomosImplementationDetail(...) so tenant code cannot call it`,
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
return makeRequirable({
|
|
980
|
+
...base,
|
|
981
|
+
businessSourceTrust: {
|
|
982
|
+
viaDirectiveId: via.id,
|
|
983
|
+
fromPayload,
|
|
984
|
+
queryId: opts.query.id,
|
|
985
|
+
queryArgs: { ...opts.queryArgs },
|
|
986
|
+
copy: { ...(opts.copy ?? {}) },
|
|
987
|
+
genesisField: opts.genesisField ?? "sourceGenesis",
|
|
988
|
+
keyHashesField: opts.keyHashesField ?? "sourceKeyHashes",
|
|
989
|
+
},
|
|
990
|
+
});
|
|
991
|
+
},
|
|
772
992
|
alsoWrites,
|
|
993
|
+
alsoWritesMany: (aggregate: AggregateHandle, marker: ReferentialMarker) =>
|
|
994
|
+
alsoWrites(aggregate, marker, true),
|
|
773
995
|
alsoEnsures: (aggregate: AggregateHandle) => alsoWrites(aggregate, "ensures"),
|
|
774
996
|
alsoCreates: (aggregate: AggregateHandle) => alsoWrites(aggregate, "creates"),
|
|
997
|
+
alsoEnsuresMany: (aggregate: AggregateHandle) =>
|
|
998
|
+
alsoWrites(aggregate, "ensures", true),
|
|
999
|
+
alsoCreatesMany: (aggregate: AggregateHandle) =>
|
|
1000
|
+
alsoWrites(aggregate, "creates", true),
|
|
775
1001
|
alsoMutates: (aggregate: AggregateHandle) => alsoWrites(aggregate, "mutates"),
|
|
776
1002
|
alsoArchives: (aggregate: AggregateHandle) => alsoWrites(aggregate, "archives"),
|
|
777
1003
|
requires(
|
|
@@ -925,7 +1151,9 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
925
1151
|
entry = { queryId: query.id, from: source };
|
|
926
1152
|
}
|
|
927
1153
|
const canon = (e: ForeignReadEdge): string =>
|
|
928
|
-
"from" in e
|
|
1154
|
+
"from" in e
|
|
1155
|
+
? `0\\0${e.from}\\0${e.queryId}`
|
|
1156
|
+
: `1\\0${e.policy}\\0${e.fromPayload}\\0${e.queryId}`;
|
|
929
1157
|
const prior = base.declaredForeignReads ?? [];
|
|
930
1158
|
const byKey = new Map<string, ForeignReadEdge>(prior.map((e) => [canon(e), e]));
|
|
931
1159
|
byKey.set(canon(entry), entry);
|
|
@@ -1009,3 +1237,14 @@ function makeRequirable<P>(base: Directive<P>): RequirableDirective<P> {
|
|
|
1009
1237
|
export function directive<const Id extends string>(id: Id): DirectiveMarkerStep<Id> {
|
|
1010
1238
|
return new DirectiveMarkerStep(id);
|
|
1011
1239
|
}
|
|
1240
|
+
|
|
1241
|
+
/**
|
|
1242
|
+
* Keep a signed-law declaration available to Nomos while omitting it from every
|
|
1243
|
+
* generated tenant API. This is deliberately namespaced and explicit: it is for
|
|
1244
|
+
* framework lowering and temporary migration law, never a second application lane.
|
|
1245
|
+
*/
|
|
1246
|
+
export function nomosImplementationDetail<T extends object>(
|
|
1247
|
+
declaration: T,
|
|
1248
|
+
): T & { readonly businessBirthInternal: true } {
|
|
1249
|
+
return { ...declaration, businessBirthInternal: true };
|
|
1250
|
+
}
|
package/src/engine_entry.ts
CHANGED
|
@@ -57,7 +57,7 @@ import { RelationTuple, __setDispatchContext } from "./framework/relations.js";
|
|
|
57
57
|
import { BIRTH_METER_AGGREGATES } from "./birth_meter.js";
|
|
58
58
|
import { AuthProvider as FrameworkAuthProvider, setAuthProvider as frameworkSetAuthProvider } from "./framework/auth_provider_law.js";
|
|
59
59
|
import { namespacedAggregateIds, qualifyReadProjectionType, READ_PROJECTION_DELIMITER } from "./read_projection_namespace.js";
|
|
60
|
-
import {
|
|
60
|
+
import { discoverRelationTypeDeclarations } from "./relation_schema.js";
|
|
61
61
|
import { synthesizePreconditionInvariants } from "./precondition.js";
|
|
62
62
|
import { synthesizeBirthQuotaInvariants } from "./birth_meter.js";
|
|
63
63
|
import { withBirthMeterExports } from "./birth_meter_ensure.js";
|
|
@@ -80,9 +80,16 @@ import type { CombinedDecl } from "./combined.js";
|
|
|
80
80
|
import type { InvariantBody, InvariantEvidence, InvariantVerdict } from "./relation.js";
|
|
81
81
|
import type { WorkspaceInvariantDecl } from "./framework/workspace_invariant.js";
|
|
82
82
|
import type { QueryRow } from "./report.js";
|
|
83
|
-
import { expandBusinessBirthExports } from "./business_birth.js";
|
|
83
|
+
import { expandBusinessBirthExports, isBusinessBirthDirective } from "./business_birth.js";
|
|
84
84
|
import { withOwnedCustodyExports } from "./framework/owned_custody.js";
|
|
85
85
|
import { withBusinessCorrectionExports } from "./framework/business_corrections.js";
|
|
86
|
+
import { withClearDirectives } from "./clear_directive.js";
|
|
87
|
+
import {
|
|
88
|
+
applicationLanguageOf,
|
|
89
|
+
assertApplicationLanguageCoversAuthoredDirectives,
|
|
90
|
+
executableDirectivesForLanguage,
|
|
91
|
+
remapWorkspaceInvariantsForLanguage,
|
|
92
|
+
} from "./intent.js";
|
|
86
93
|
|
|
87
94
|
/** One bundled domain module: a bag of named exports the entry scans by SHAPE. */
|
|
88
95
|
export type DomainModuleExports = Record<string, unknown>;
|
|
@@ -250,6 +257,14 @@ function combinedsOf(mod: DomainModuleExports): CombinedDecl[] {
|
|
|
250
257
|
function mergeModules(mods: readonly DomainModuleExports[], domain: string): DomainModuleExports {
|
|
251
258
|
let merged: DomainModuleExports = {};
|
|
252
259
|
for (const m of mods) merged = { ...merged, ...m };
|
|
260
|
+
const applicationLanguage = applicationLanguageOf([merged], domain);
|
|
261
|
+
if (applicationLanguage !== undefined) {
|
|
262
|
+
assertApplicationLanguageCoversAuthoredDirectives(
|
|
263
|
+
applicationLanguage,
|
|
264
|
+
[...directivesOf(merged).values()],
|
|
265
|
+
domain,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
253
268
|
merged = expandBusinessBirthExports(merged, domain);
|
|
254
269
|
merged = withOwnedCustodyExports(merged);
|
|
255
270
|
merged = withBusinessCorrectionExports(merged);
|
|
@@ -261,7 +276,11 @@ function mergeModules(mods: readonly DomainModuleExports[], domain: string): Dom
|
|
|
261
276
|
// the registry + `describe()` see the exact surface the composed manifest carries.
|
|
262
277
|
// ROLE SUGAR: lift every `role(...).emit()` into its grant/revoke/seed directives at the SAME
|
|
263
278
|
// engine merge point so the registry dispatches them (the compiler saw them via the same expander).
|
|
264
|
-
return expandRoleExports(
|
|
279
|
+
return expandRoleExports(
|
|
280
|
+
withClearDirectives(
|
|
281
|
+
withAttestedReadExports(withBirthMeterExports(expandCapabilityExports(merged))),
|
|
282
|
+
),
|
|
283
|
+
) as DomainModuleExports;
|
|
265
284
|
}
|
|
266
285
|
|
|
267
286
|
/**
|
|
@@ -298,11 +317,16 @@ interface DomainSlice {
|
|
|
298
317
|
}
|
|
299
318
|
|
|
300
319
|
/** Build one domain's slice from its merged module exports (the original scans). */
|
|
301
|
-
function buildDomainSlice(mod: DomainModuleExports): DomainSlice {
|
|
320
|
+
function buildDomainSlice(mod: DomainModuleExports, domain: string): DomainSlice {
|
|
302
321
|
// ── directiveId → {directive, agg} ────────────────────────────────────────────
|
|
303
322
|
const registry = new Map<string, RegistryEntry>();
|
|
304
323
|
const aggs = aggregatesOf(mod);
|
|
305
|
-
const
|
|
324
|
+
const applicationLanguage = applicationLanguageOf([mod], domain);
|
|
325
|
+
const discoveredDirectives = [...directivesOf(mod).values()];
|
|
326
|
+
const executableDirectives = applicationLanguage === undefined
|
|
327
|
+
? discoveredDirectives
|
|
328
|
+
: executableDirectivesForLanguage(applicationLanguage, discoveredDirectives, domain);
|
|
329
|
+
const dirs = new Map(executableDirectives.map((directive) => [directive.id, directive]));
|
|
306
330
|
// RELATIONS AUTO-ENSURE (mirror of composeDomainModule): a relate-family directive `.ensures(RelationTuple)`
|
|
307
331
|
// needs the framework aggregate registered for DISPATCH too — without it the directive is silently dropped
|
|
308
332
|
// below. Inject iff a directive targets it and the dev didn't already export the handle.
|
|
@@ -398,6 +422,7 @@ function buildDomainSlice(mod: DomainModuleExports): DomainSlice {
|
|
|
398
422
|
|
|
399
423
|
// ── workspace-invariant id → declaration — from the SAME exports (#266, by shape) ──
|
|
400
424
|
const wsInvariants = new Map<string, WorkspaceInvariantDecl>();
|
|
425
|
+
const declaredWorkspaceInvariants: WorkspaceInvariantDecl[] = [];
|
|
401
426
|
for (const v of Object.values(mod)) {
|
|
402
427
|
if (
|
|
403
428
|
v &&
|
|
@@ -413,9 +438,14 @@ function buildDomainSlice(mod: DomainModuleExports): DomainSlice {
|
|
|
413
438
|
`body — the gate would have nothing to evaluate (docs/workspace_invariant.md).`,
|
|
414
439
|
);
|
|
415
440
|
}
|
|
416
|
-
|
|
441
|
+
declaredWorkspaceInvariants.push(w);
|
|
417
442
|
}
|
|
418
443
|
}
|
|
444
|
+
for (const w of applicationLanguage === undefined
|
|
445
|
+
? declaredWorkspaceInvariants
|
|
446
|
+
: remapWorkspaceInvariantsForLanguage(declaredWorkspaceInvariants, applicationLanguage)) {
|
|
447
|
+
wsInvariants.set(w.id, w);
|
|
448
|
+
}
|
|
419
449
|
// ── DESUGARED write-path preconditions (`.requiresExisting`/`.creates(Agg,idFn)`/`.where`) ──
|
|
420
450
|
// The synthesized workspace invariants are NOT module exports (they are generated from the directive
|
|
421
451
|
// markers), so the export-scan above never sees them. Synthesize them here — from the SAME discovered
|
|
@@ -438,8 +468,8 @@ function buildDomainSlice(mod: DomainModuleExports): DomainSlice {
|
|
|
438
468
|
// objects stay bare; the synthesized read-visibility `workspace` caps are never authored relationTypes,
|
|
439
469
|
// so they are absent here too (their bare grants stay bare — the read gate hardcodes `workspace:self`).
|
|
440
470
|
const qualifiableRelations = new Set<string>();
|
|
441
|
-
for (const v of
|
|
442
|
-
if (
|
|
471
|
+
for (const v of discoverRelationTypeDeclarations(mod)) {
|
|
472
|
+
if (v.global === true) continue;
|
|
443
473
|
for (const rel of v.relations.keys()) qualifiableRelations.add(`${v.type}#${rel}`);
|
|
444
474
|
}
|
|
445
475
|
|
|
@@ -471,7 +501,7 @@ export function collectEngineRouting(
|
|
|
471
501
|
if (!list.includes(domainName)) list.push(domainName);
|
|
472
502
|
};
|
|
473
503
|
for (const [domainName, mods] of Object.entries(domains)) {
|
|
474
|
-
const slice = buildDomainSlice(mergeModules(mods, domainName));
|
|
504
|
+
const slice = buildDomainSlice(mergeModules(mods, domainName), domainName);
|
|
475
505
|
// Type-keyed dispatches arrive with the ROW's type — QUALIFIED for a namespaced aggregate (the
|
|
476
506
|
// runtime `__type` stamp), bare for a global one / a pre-namespacing row. Route BOTH keys so
|
|
477
507
|
// either era resolves to the declaring domain (the slice lookup strips the qualifier back off).
|
|
@@ -557,7 +587,7 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
|
|
|
557
587
|
const sources = config.domains[domainName];
|
|
558
588
|
if (sources === undefined) return undefined;
|
|
559
589
|
const forced = sources.map((m) => (typeof m === "function" ? m() : m));
|
|
560
|
-
const slice = buildDomainSlice(mergeModules(forced, domainName));
|
|
590
|
+
const slice = buildDomainSlice(mergeModules(forced, domainName), domainName);
|
|
561
591
|
sliceCache.set(domainName, slice);
|
|
562
592
|
return slice;
|
|
563
593
|
}
|
|
@@ -933,8 +963,8 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
|
|
|
933
963
|
|
|
934
964
|
/**
|
|
935
965
|
* THE SELF-DESCRIBE VERB (G1) — the lump's OWN OpenAPI, derived from the SAME modules it
|
|
936
|
-
* dispatches. The kernel
|
|
937
|
-
*
|
|
966
|
+
* dispatches. The kernel selects the content-addressed describe plan from the native USD
|
|
967
|
+
* package in custody, so the workspace's API surface is a pure,
|
|
938
968
|
* deterministic function of installed law — NOT a host-staged blob. Its public `paths` reproduce
|
|
939
969
|
* the `<name>.openapi.json` the compiler wrote; describe also carries a private, law-derived
|
|
940
970
|
* framework routing manifest which the host consumes internally and never publishes.
|
|
@@ -951,7 +981,19 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
|
|
|
951
981
|
// Aggregates dedup by wire id + RelationTuple auto-ensure (the compose contract) — openapi
|
|
952
982
|
// reads aggregates only for the hasMany inverse indexes, so this matches composeDomainModule.
|
|
953
983
|
const byId = aggregatesOf(merged);
|
|
954
|
-
const
|
|
984
|
+
const discoveredDirectives = [...directivesOf(merged).values()];
|
|
985
|
+
const applicationLanguage = applicationLanguageOf([merged], domainName);
|
|
986
|
+
const directives = applicationLanguage === undefined
|
|
987
|
+
? discoveredDirectives
|
|
988
|
+
: executableDirectivesForLanguage(applicationLanguage, discoveredDirectives, domainName);
|
|
989
|
+
const applicationIntents = applicationLanguage === undefined
|
|
990
|
+
? undefined
|
|
991
|
+
: [
|
|
992
|
+
...applicationLanguage.intents,
|
|
993
|
+
...directives.filter((directive) =>
|
|
994
|
+
isBusinessBirthDirective(directive) &&
|
|
995
|
+
!applicationLanguage.intents.some((intent) => intent.id === directive.id)),
|
|
996
|
+
];
|
|
955
997
|
if (!byId.has(RelationTuple.id) && directives.some((d) => d.aggregateId === RelationTuple.id)) {
|
|
956
998
|
byId.set(RelationTuple.id, RelationTuple);
|
|
957
999
|
}
|
|
@@ -961,6 +1003,7 @@ export function registerEngine(config: EngineEntryConfig): RegisteredEngine {
|
|
|
961
1003
|
domain: domainName,
|
|
962
1004
|
aggregates: [...byId.values()],
|
|
963
1005
|
directives,
|
|
1006
|
+
...(applicationIntents === undefined ? {} : { applicationIntents }),
|
|
964
1007
|
...(decls.queries && decls.queries.length > 0 ? { queries: [...decls.queries] } : {}),
|
|
965
1008
|
...(decls.counts && decls.counts.length > 0 ? { counts: [...decls.counts] } : {}),
|
|
966
1009
|
...(decls.sums && decls.sums.length > 0 ? { sums: [...decls.sums] } : {}),
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonicalise esbuild's non-semantic install-topology noise before the executable law is hashed.
|
|
3
|
+
*
|
|
4
|
+
* A workspace dependency is labelled `../../dsl/src/...`; the same published dependency is labelled
|
|
5
|
+
* `node_modules/@githolon/dsl/src/...`. Esbuild can also leave either one or two newlines after its
|
|
6
|
+
* `"use strict"` prologue. None of those bytes describe business law, so they must not give the same
|
|
7
|
+
* law two identities.
|
|
8
|
+
*
|
|
9
|
+
* Replacements are deliberately restricted to esbuild's generated comment/function labels. Tenant
|
|
10
|
+
* strings and executable imports remain byte-significant.
|
|
11
|
+
*/
|
|
12
|
+
export function canonicalizeEngineJavascript(source: string): string {
|
|
13
|
+
const body = source
|
|
14
|
+
.replace(/\r\n?/g, "\n")
|
|
15
|
+
.replace(/^[ \t]*"use strict";[ \t]*(?:\n|$)/gm, "")
|
|
16
|
+
.replace(/^(\s*\/\/ )(?:(?:\.\.\/)+dsl\/src\/|node_modules\/@githolon\/dsl\/src\/)/gm, "$1@githolon/dsl/src/")
|
|
17
|
+
.replace(/"(?:(?:\.\.\/)+dsl\/src\/|node_modules\/@githolon\/dsl\/src\/)([^"]+)"\(\) \{/g, '"@githolon/dsl/src/$1"() {')
|
|
18
|
+
.replace(/^(\s*\/\/ )(?:(?:\.\.\/)+node_modules\/zod\/|node_modules\/zod\/)/gm, "$1zod/")
|
|
19
|
+
.replace(/"(?:(?:\.\.\/)+node_modules\/zod\/|node_modules\/zod\/)([^"]+)"\(\) \{/g, '"zod/$1"() {')
|
|
20
|
+
.replace(/\n[ \t]*\n/g, "\n")
|
|
21
|
+
.trim();
|
|
22
|
+
return `"use strict";\n${body}\n`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Remove the same install-topology label from the sealed generated application bundle. */
|
|
26
|
+
export function canonicalizeGeneratedClientJavascript(source: string): string {
|
|
27
|
+
return source.replace(
|
|
28
|
+
/^(\s*\/\/ )(?:(?:\.\.\/)+dsl\/runtime\/|node_modules\/@githolon\/dsl\/runtime\/)/gm,
|
|
29
|
+
"$1@githolon/dsl/runtime/",
|
|
30
|
+
);
|
|
31
|
+
}
|