@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/codegen_proof.ts
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* GENERATED PROOF CODEGEN — `build/<name>.proof.mts`, the runnable e2e SYNTHESIZED
|
|
10
|
-
* FROM THE LAW ITSELF (the
|
|
11
|
-
*
|
|
10
|
+
* FROM THE LAW ITSELF (reshaping the domain used to mean repairing a second,
|
|
11
|
+
* handwritten client story; now the proof reshapes itself on every compile).
|
|
12
12
|
*
|
|
13
13
|
* `generateTsProof(modules, opts)` reads the domain's ACTUAL directives / queries /
|
|
14
14
|
* counts (the same composed modules every other emitter sees) and emits a
|
|
@@ -35,17 +35,17 @@
|
|
|
35
35
|
import type { z } from "zod";
|
|
36
36
|
|
|
37
37
|
import { BUSINESS_BIRTH_SEED_PAYLOAD_FIELD, type AggregateHandle } from "./aggregate.js";
|
|
38
|
-
import type {
|
|
39
|
-
import type { Directive } from "./directive.js";
|
|
38
|
+
import type { Directive, ReferentialMarker } from "./directive.js";
|
|
40
39
|
import type { DomainModule } from "./codegen_dart.js";
|
|
41
40
|
import type { QueryDecl } from "./query.js";
|
|
42
41
|
import type { FieldOp } from "./ops.js";
|
|
42
|
+
import type { Field } from "./fields.js";
|
|
43
43
|
import type { RelationTypeDecl, Rewrite } from "./relation_schema.js";
|
|
44
44
|
import type { WorkspaceInvariantDecl } from "./framework/workspace_invariant.js";
|
|
45
45
|
import { finishCount } from "./count.js";
|
|
46
46
|
import { clearDirectiveId } from "./clear_directive.js";
|
|
47
47
|
import type { DomainInterface } from "./shape_compat.js";
|
|
48
|
-
import { voMetaOf, voSample } from "./value_object.js";
|
|
48
|
+
import { idOfMetaOf, idOfTargetKindOf, voMetaOf, voSample } from "./value_object.js";
|
|
49
49
|
import { mintedCreateField } from "./workspace_routing.js";
|
|
50
50
|
import { deterministicPorts } from "./ctx.js";
|
|
51
51
|
import { __setDispatchDomain, __setDispatchContext } from "./framework/relations.js";
|
|
@@ -54,8 +54,8 @@ import { __resetAuthoring, __drainAuthoring } from "./authoring.js";
|
|
|
54
54
|
import { __resetReads, __drainReads } from "./read.js";
|
|
55
55
|
import {
|
|
56
56
|
autoStampFields,
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
domainEncryptionMap,
|
|
58
|
+
tsApplicationName,
|
|
59
59
|
zodArrayElement,
|
|
60
60
|
zodDef,
|
|
61
61
|
zodEnumValues,
|
|
@@ -63,6 +63,10 @@ import {
|
|
|
63
63
|
zodObjectShape,
|
|
64
64
|
zodUnwrapLazy,
|
|
65
65
|
} from "./codegen_ts.js";
|
|
66
|
+
import { isNamespacedAggregate, qualifyReadProjectionType } from "./read_projection_namespace.js";
|
|
67
|
+
import { isBusinessIntent } from "./intent.js";
|
|
68
|
+
import { zodSampleNumber, zodSampleString } from "./zod_introspection.js";
|
|
69
|
+
import { generatedAggregateCollectionName } from "./generated_client_names.js";
|
|
66
70
|
|
|
67
71
|
/** Shared empty visited-set for the lazy-recursion guard (no allocation on the common path). */
|
|
68
72
|
const EMPTY_LAZY_VISITED: ReadonlySet<z.ZodTypeAny> = new Set();
|
|
@@ -161,37 +165,23 @@ function uniquifyBirthPayload(
|
|
|
161
165
|
return suffixed;
|
|
162
166
|
}
|
|
163
167
|
|
|
164
|
-
// Constrained-string candidates probed against a field's OWN schema when the readable `sample-<field>`
|
|
165
|
-
// fails it (a `.regex()/.length()/.email()/.url()` bound — co2: a locale-pattern `defaultLocale` was
|
|
166
|
-
// handed `sample-defaultLocale`, violating its own schema and killing the whole leg downstream). Ordered
|
|
167
|
-
// common-first; the READABLE sample stays the first try so unconstrained fields are byte-stable.
|
|
168
|
-
const STRING_CANDIDATES: readonly string[] = [
|
|
169
|
-
"sample", "en", "en-GB", "AA", "A1", "a", "1", "2026-01-01", "2026-01-01T00:00:00Z",
|
|
170
|
-
"a@example.com", "https://example.com", "#000000", "00000000-0000-0000-0000-000000000000",
|
|
171
|
-
"0000000000000000000000000000000000000000000000000000000000000000", // a 64-hex digest
|
|
172
|
-
];
|
|
173
168
|
function sampleString(zt: z.ZodTypeAny, field: string): string {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
`codegen-proof: field '${field}': no sample satisfies its own string schema (a .regex()/.length() ` +
|
|
183
|
-
`bound none of the ${1 + SUBJECT_CANDIDATES.length + STRING_CANDIDATES.length} candidates fit) — ` +
|
|
184
|
-
`loosen the pattern, or prove that directive by hand in test/e2e.mts`,
|
|
185
|
-
);
|
|
169
|
+
try {
|
|
170
|
+
return zodSampleString(zt, field);
|
|
171
|
+
} catch {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`codegen-proof: field '${field}': no sample satisfies its own string schema — ` +
|
|
174
|
+
"loosen the pattern or provide a generator-readable lawful example",
|
|
175
|
+
);
|
|
176
|
+
}
|
|
186
177
|
}
|
|
187
178
|
|
|
188
179
|
// Candidate numbers probed against a field's own schema (in a sensible order) so a `.min()/.max()/
|
|
189
180
|
// .int()/.positive()` constraint is respected — the number sibling of SUBJECT_CANDIDATES. `1` first
|
|
190
181
|
// keeps the guestbook golden output stable; the rest cover common bounds (lat/lng, non-negative, etc.).
|
|
191
|
-
const NUMBER_CANDIDATES: readonly number[] = [1, 0, -1, 2, 10, 100, 0.5, -90, 90, 180, -180, 1000000];
|
|
192
182
|
function sampleNumber(zt: z.ZodTypeAny): number {
|
|
193
|
-
|
|
194
|
-
return 1; //
|
|
183
|
+
try { return zodSampleNumber(zt); }
|
|
184
|
+
catch { return 1; } // whole-payload validation names an exotic bound
|
|
195
185
|
}
|
|
196
186
|
|
|
197
187
|
/** The leaf identifier of a full path (`a.b[0].c` → `c`) — the readable `sample-<leaf>` string base. */
|
|
@@ -227,7 +217,7 @@ function sampleOfZod(
|
|
|
227
217
|
if (zodKind(zt) === "lazy") {
|
|
228
218
|
if (visited.has(zt)) {
|
|
229
219
|
throw new Error(
|
|
230
|
-
`codegen-proof: ${where} — field '${path}': recursive z.lazy schema — a REQUIRED self-reference makes a finite sample impossible; make the recursive field \`.optional()\` (proof omits it) or wrap it in an array (proof emits \`[]\`)
|
|
220
|
+
`codegen-proof: ${where} — field '${path}': recursive z.lazy schema — a REQUIRED self-reference makes a finite sample impossible; make the recursive field \`.optional()\` (proof omits it) or wrap it in an array (proof emits \`[]\`)`,
|
|
231
221
|
);
|
|
232
222
|
}
|
|
233
223
|
const next = new Set(visited);
|
|
@@ -308,7 +298,7 @@ function sampleOfZod(
|
|
|
308
298
|
}
|
|
309
299
|
default:
|
|
310
300
|
throw new Error(
|
|
311
|
-
`codegen-proof: ${where} — field '${path}': cannot synthesize a sample for zod shape '${zodKind(zt)}' — reshape it to a sampleable type (string/number/boolean/enum/array/object/nested valueObject)
|
|
301
|
+
`codegen-proof: ${where} — field '${path}': cannot synthesize a sample for zod shape '${zodKind(zt)}' — reshape it to a sampleable type (string/number/boolean/enum/array/object/nested valueObject) or teach the generator that value shape`,
|
|
312
302
|
);
|
|
313
303
|
}
|
|
314
304
|
}
|
|
@@ -318,12 +308,27 @@ function sampleOfZod(
|
|
|
318
308
|
* optional/defaulted fields omitted, required `…At` strings omitted (auto-stamped
|
|
319
309
|
* by the generated client), everything else sampled by {@link sampleOfZod}.
|
|
320
310
|
*/
|
|
321
|
-
export function synthesizePayload(
|
|
311
|
+
export function synthesizePayload(
|
|
312
|
+
schema: z.ZodTypeAny,
|
|
313
|
+
where: string,
|
|
314
|
+
opts?: { voIncludeOptional?: boolean; example?: unknown },
|
|
315
|
+
): SynthesizedPayload {
|
|
322
316
|
if (zodKind(schema) !== "object") {
|
|
323
317
|
throw new Error(`codegen-proof: ${where} payload is not a zod object — the proof can only synthesize object payloads`);
|
|
324
318
|
}
|
|
325
319
|
const autoStamped = autoStampFields(schema);
|
|
326
320
|
const stamped = new Set(autoStamped);
|
|
321
|
+
if (opts?.example !== undefined) {
|
|
322
|
+
const parsed = schema.safeParse(opts.example);
|
|
323
|
+
if (!parsed.success) {
|
|
324
|
+
throw new Error(
|
|
325
|
+
`codegen-proof: ${where} directive example no longer satisfies its payload schema: ${parsed.error.message}`,
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
const value = { ...(parsed.data as Record<string, unknown>) };
|
|
329
|
+
for (const field of stamped) delete value[field];
|
|
330
|
+
return { value, autoStamped };
|
|
331
|
+
}
|
|
327
332
|
const value: Record<string, unknown> = {};
|
|
328
333
|
for (const [name, raw] of Object.entries(zodObjectShape(schema))) {
|
|
329
334
|
const f = raw as z.ZodTypeAny;
|
|
@@ -335,50 +340,72 @@ export function synthesizePayload(schema: z.ZodTypeAny, where: string, opts?: {
|
|
|
335
340
|
return { value, autoStamped };
|
|
336
341
|
}
|
|
337
342
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
343
|
+
/** Prefer a directive's validated cross-field example, then fall back to the
|
|
344
|
+
* ordinary per-field/VO synthesizer. Keeping this in one helper prevents proof,
|
|
345
|
+
* check and precondition legs from silently choosing different samples. */
|
|
346
|
+
export function synthesizeDirectivePayload(
|
|
347
|
+
directive: Directive<any>,
|
|
348
|
+
where: string,
|
|
349
|
+
opts?: { voIncludeOptional?: boolean },
|
|
350
|
+
): SynthesizedPayload {
|
|
351
|
+
return synthesizePayload(directive.payloadSchema as z.ZodTypeAny, where, {
|
|
352
|
+
...opts,
|
|
353
|
+
...(directive.payloadExample === undefined
|
|
354
|
+
? {}
|
|
355
|
+
: { example: directive.payloadExample }),
|
|
356
|
+
});
|
|
346
357
|
}
|
|
347
358
|
|
|
348
|
-
/**
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
agg: AggregateHandle,
|
|
358
|
-
): ConcurrencyLeg | undefined {
|
|
359
|
-
const addWinsSets = Object.entries(agg.fields)
|
|
360
|
-
.filter(([, f]) => (f as Field).kind === "set" && ((f as Field).driver as { kind?: string }).kind === "AddWins")
|
|
361
|
-
.map(([name]) => name);
|
|
362
|
-
if (addWinsSets.length === 0) return undefined;
|
|
363
|
-
for (const d of directives) {
|
|
364
|
-
if (d.marker !== "mutates" || d.aggregateId !== agg.id) continue;
|
|
365
|
-
let shape: Record<string, z.ZodTypeAny>;
|
|
366
|
-
try {
|
|
367
|
-
shape = zodObjectShape(d.payloadSchema as z.ZodTypeAny);
|
|
368
|
-
} catch {
|
|
369
|
-
continue;
|
|
370
|
-
}
|
|
371
|
-
const idField = Object.entries(shape).find(
|
|
372
|
-
([name, f]) => name.length > 2 && name.endsWith("Id") && zodKind(f) === "string",
|
|
373
|
-
)?.[0];
|
|
374
|
-
if (idField === undefined) continue;
|
|
375
|
-
const setField = addWinsSets.find((s) => {
|
|
376
|
-
const f = shape[s];
|
|
377
|
-
return f !== undefined && zodKind(f) === "array" && zodKind(zodArrayElement(f)) === "string";
|
|
378
|
-
});
|
|
379
|
-
if (setField !== undefined) return { directive: d, idField, setField };
|
|
359
|
+
/** Concrete aggregate references required by a payload. A reference is not sample data: the generated
|
|
360
|
+
* application proof must create/read the target and carry the returned `NomosRef<T>` forward. Shared
|
|
361
|
+
* contracts deliberately stay out of this map because they require a concrete implementing aggregate. */
|
|
362
|
+
function aggregateReferenceFields(schema: z.ZodTypeAny): Readonly<Record<string, string>> {
|
|
363
|
+
if (zodKind(schema) !== "object") return {};
|
|
364
|
+
const out: Record<string, string> = {};
|
|
365
|
+
for (const [field, raw] of Object.entries(zodObjectShape(schema))) {
|
|
366
|
+
const target = idOfMetaOf(raw as z.ZodTypeAny);
|
|
367
|
+
if (target !== undefined && idOfTargetKindOf(raw as z.ZodTypeAny) !== "contract") out[field] = target;
|
|
380
368
|
}
|
|
381
|
-
return
|
|
369
|
+
return out;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** Reference fields the business payload may legitimately omit. A validated `.example()` may include an
|
|
373
|
+
* optional predecessor even when a fresh journey has not created one yet; the runner drops that one field
|
|
374
|
+
* instead of inventing a string id or blocking the rest of the business action. */
|
|
375
|
+
function optionalAggregateReferenceFields(schema: z.ZodTypeAny): readonly string[] {
|
|
376
|
+
if (zodKind(schema) !== "object") return [];
|
|
377
|
+
return Object.entries(zodObjectShape(schema))
|
|
378
|
+
.filter(([, raw]) => {
|
|
379
|
+
const field = raw as z.ZodTypeAny;
|
|
380
|
+
const kind = zodKind(field);
|
|
381
|
+
return idOfMetaOf(field) !== undefined && (kind === "optional" || kind === "default");
|
|
382
|
+
})
|
|
383
|
+
.map(([field]) => field)
|
|
384
|
+
.sort();
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const proofTypeIdent = (type: string): string => type.replace(/[^A-Za-z0-9_$]/g, "_");
|
|
388
|
+
const proofRefSlot = (type: string): string => `proof${proofTypeIdent(type)}Ref`;
|
|
389
|
+
const proofRefGetter = (type: string): string => `requireProof${proofTypeIdent(type)}Ref`;
|
|
390
|
+
const proofResultMember = (type: string): string => {
|
|
391
|
+
const bare = type.split(".").filter(Boolean).at(-1) ?? type;
|
|
392
|
+
return lcFirst(camel(bare.replace(/[^A-Za-z0-9_$]+/g, " ")));
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
/** Emit a typed application payload. Ordinary facts remain literals; every `idOf(Aggregate)` field is an
|
|
396
|
+
* expression returning a real `NomosRef<Aggregate>` created earlier in this generated journey. */
|
|
397
|
+
function typedPayloadLiteral(
|
|
398
|
+
payload: Readonly<Record<string, unknown>>,
|
|
399
|
+
referenceFields: Readonly<Record<string, string>>,
|
|
400
|
+
overrides: Readonly<Record<string, string>> = {},
|
|
401
|
+
): string {
|
|
402
|
+
const keys = new Set([...Object.keys(payload), ...Object.keys(overrides)]);
|
|
403
|
+
const entries = [...keys].map((field) => {
|
|
404
|
+
const expression = overrides[field]
|
|
405
|
+
?? (referenceFields[field] !== undefined ? `${proofRefGetter(referenceFields[field]!)}()` : JSON.stringify(payload[field]));
|
|
406
|
+
return `${JSON.stringify(field)}: ${expression}`;
|
|
407
|
+
});
|
|
408
|
+
return `{ ${entries.join(", ")} }`;
|
|
382
409
|
}
|
|
383
410
|
|
|
384
411
|
// ── authority-conformance discovery (the ONE-ENCODER + gate proof — actorless gated offer is impossible) ──
|
|
@@ -396,6 +423,8 @@ export interface AuthorityConformanceLeg {
|
|
|
396
423
|
readonly relation: string;
|
|
397
424
|
/** The synthesized payload the proof drives the gated directive with (auto-stamped `…At` omitted). */
|
|
398
425
|
readonly payload: Record<string, unknown>;
|
|
426
|
+
/** Payload fields which must receive a previously-created typed aggregate reference. */
|
|
427
|
+
readonly referenceFields?: Readonly<Record<string, string>>;
|
|
399
428
|
/** Required `…At` fields the runtime stamps at dispatch (mirrors the create leg). */
|
|
400
429
|
readonly autoStamped: readonly string[];
|
|
401
430
|
/** When the gated directive is a `.creates` whose own id is kernel-minted, the field to mint at runtime. */
|
|
@@ -418,7 +447,7 @@ export function discoverAuthorityConformanceLeg(mod: DomainModule): AuthorityCon
|
|
|
418
447
|
if (gated === undefined) return undefined;
|
|
419
448
|
let synth: SynthesizedPayload;
|
|
420
449
|
try {
|
|
421
|
-
synth =
|
|
450
|
+
synth = synthesizeDirectivePayload(gated, `${domain}/${gated.id}`);
|
|
422
451
|
} catch {
|
|
423
452
|
return undefined; // an exotic payload the synthesizer can't sample → no conformance leg (still gated at the kernel)
|
|
424
453
|
}
|
|
@@ -431,270 +460,12 @@ export function discoverAuthorityConformanceLeg(mod: DomainModule): AuthorityCon
|
|
|
431
460
|
directiveId: gated.id,
|
|
432
461
|
relation: gated.requiresRelationGate!.relation,
|
|
433
462
|
payload,
|
|
463
|
+
...(() => { const refs = aggregateReferenceFields(gated.payloadSchema as z.ZodTypeAny); return Object.keys(refs).length > 0 ? { referenceFields: refs } : {}; })(),
|
|
434
464
|
autoStamped: synth.autoStamped,
|
|
435
465
|
...(mintField !== undefined ? { mintField } : {}),
|
|
436
466
|
};
|
|
437
467
|
}
|
|
438
468
|
|
|
439
|
-
/**
|
|
440
|
-
* THE AUTHORITY-CONFORMANCE LEG (the one-encoder + gate proof). On the real LOCAL holon, with fetch trapped:
|
|
441
|
-
* (a) bind the generated client WITH an actor + drive the gated directive. SPY on `holon.offerDirective` to
|
|
442
|
-
* capture the (domain, directiveId, payload, opts) the client passes, RE-ENCODE that through the v2
|
|
443
|
-
* envelope shape (the SAME `{ v:2, intent, security:{actor} }` the one encoder emits) and DECODE it back
|
|
444
|
-
* to assert `security.actor` IS the bound actor — the produced offer envelope carries the principal.
|
|
445
|
-
* (b) bind a SECOND client WITHOUT an actor + call the gated directive — assert it THROWS
|
|
446
|
-
* MissingOfferAuthority LOCALLY (before any offerDirective/submit fires; the spy stays untouched).
|
|
447
|
-
* Emits nothing when the package has no gated directive (a documented no-op note).
|
|
448
|
-
*/
|
|
449
|
-
function authorityConformanceLegLines(
|
|
450
|
-
leg: AuthorityConformanceLeg | undefined,
|
|
451
|
-
factory: string,
|
|
452
|
-
n: () => number,
|
|
453
|
-
): string[] {
|
|
454
|
-
if (leg === undefined) {
|
|
455
|
-
return [
|
|
456
|
-
`// ${n()}. authority-conformance leg: this law has no gated (\`.requires(...)\`) directive, so there is`,
|
|
457
|
-
`// nothing to prove about actorless-gated-offer refusal (an un-gated domain authors openly).`,
|
|
458
|
-
`console.log("– authority-conformance leg SKIPPED — no .requires(...) directive (add one to prove the one-encoder threads security.actor + an actorless gated offer can't be generated)");`,
|
|
459
|
-
``,
|
|
460
|
-
];
|
|
461
|
-
}
|
|
462
|
-
const payloadEntries = leg.mintField !== undefined
|
|
463
|
-
? `{ ...${JSON.stringify(leg.payload)}, ${JSON.stringify(leg.mintField)}: await holon.mint("conformance") }`
|
|
464
|
-
: JSON.stringify(leg.payload);
|
|
465
|
-
return [
|
|
466
|
-
`// ${n()}. AUTHORITY CONFORMANCE (THE ONE ENCODER + the gate): \`${leg.domain}/${leg.directiveId}\` is GATED`,
|
|
467
|
-
`// (\`.requires("${leg.relation}")\`). (a) WITH an actor the produced offer envelope must carry`,
|
|
468
|
-
`// security.actor; (b) WITHOUT one the generated client THROWS MissingOfferAuthority before any submit.`,
|
|
469
|
-
`{`,
|
|
470
|
-
` const realFetch2 = globalThis.fetch;`,
|
|
471
|
-
` globalThis.fetch = (() => { throw new Error("OFFLINE PROOF VIOLATED: authority-conformance touched the network"); }) as typeof fetch;`,
|
|
472
|
-
` try {`,
|
|
473
|
-
` // (a) SPY on offerDirective: capture what the generated client passes WITHOUT admitting a synthetic`,
|
|
474
|
-
` // business write, re-encode the v2 envelope, and assert security.actor IS the actor.`,
|
|
475
|
-
` const ACTOR = "user:proof-conformance";`,
|
|
476
|
-
` let captured: { domain: string; directiveId: string; payload: unknown; opts?: { domainHash?: string; actor?: string } } | undefined;`,
|
|
477
|
-
` holon.offerDirective = (domain, directiveId, payload, opts) => {`,
|
|
478
|
-
` captured = { domain, directiveId, payload, opts };`,
|
|
479
|
-
` return Promise.resolve("0000000000000000000000000000000000000000");`,
|
|
480
|
-
` };`,
|
|
481
|
-
` try {`,
|
|
482
|
-
` await ${factory}(holon, { actor: ACTOR }).${leg.directiveId}(${payloadEntries});`,
|
|
483
|
-
` } finally {`,
|
|
484
|
-
` holon.offerDirective = realOffer;`,
|
|
485
|
-
` }`,
|
|
486
|
-
` if (!captured) fail("authority-conformance: the gated directive did not reach holon.offerDirective");`,
|
|
487
|
-
` const actorOnOffer = captured!.opts?.actor;`,
|
|
488
|
-
` if (actorOnOffer !== ACTOR) fail(\`authority-conformance: the offer did not carry the actor (got \${JSON.stringify(actorOnOffer)})\`);`,
|
|
489
|
-
` // Decode the bytes THE ONE ENCODER actually emits from these args (NOT a hand-rolled copy) — so this`,
|
|
490
|
-
` // leg catches encoder DRIFT (a dropped/renamed/whitespace-normalized security.actor), not merely that`,
|
|
491
|
-
` // the actor reached offerDirective. The v2 wire format lives in exactly one place: encodeIntentOfferBytes.`,
|
|
492
|
-
` const decoded = JSON.parse(new TextDecoder().decode(encodeIntentOfferBytes({ domain: captured!.domain, directiveId: captured!.directiveId, payload: captured!.payload, domainHash: captured!.opts?.domainHash, actor: actorOnOffer })));`,
|
|
493
|
-
` if (decoded?.security?.actor !== ACTOR) fail(\`authority-conformance: THE ONE ENCODER dropped security.actor (\${JSON.stringify(decoded?.security)})\`);`,
|
|
494
|
-
` ok(\`authority conformance (a) — \${${JSON.stringify(`${leg.domain}/${leg.directiveId}`)}} authored WITH an actor carries security.actor through THE ONE ENCODER\`);`,
|
|
495
|
-
``,
|
|
496
|
-
` // (b) the SAME directive with NO actor: the generated client refuses to BUILD the offer — it throws`,
|
|
497
|
-
` // MissingOfferAuthority locally, BEFORE any submit (an actorless gated offer is impossible to make).`,
|
|
498
|
-
` let threw: Error | undefined;`,
|
|
499
|
-
` try {`,
|
|
500
|
-
` await ${factory}(holon).${leg.directiveId}(${payloadEntries});`,
|
|
501
|
-
` } catch (e) { threw = e as Error; }`,
|
|
502
|
-
` if (threw === undefined) fail("authority-conformance: the gated directive with NO actor did NOT throw — an actorless gated offer must be impossible to generate");`,
|
|
503
|
-
` if (threw.name !== "MissingOfferAuthority") fail(\`authority-conformance: expected MissingOfferAuthority, got \${threw.name}: \${threw.message}\`);`,
|
|
504
|
-
` ok(\`authority conformance (b) — \${${JSON.stringify(`${leg.domain}/${leg.directiveId}`)}} with NO actor throws MissingOfferAuthority before any submit (gated offer impossible to generate)\`);`,
|
|
505
|
-
` } finally {`,
|
|
506
|
-
` globalThis.fetch = realFetch2;`,
|
|
507
|
-
` }`,
|
|
508
|
-
`}`,
|
|
509
|
-
``,
|
|
510
|
-
];
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
/**
|
|
514
|
-
* THE RELATION-BOOTSTRAP LEG (the seed-then-prove POSITIVE — complements the negative conformance leg). On the
|
|
515
|
-
* LOCAL holon with fetch trapped: (1) dispatch the GRANT directive under the proof actor (its subject fields
|
|
516
|
-
* overwritten to the actor so the relation seats ON it), then (2) dispatch the GATED directive under the SAME
|
|
517
|
-
* actor and assert it SUCCEEDS — proving that once relation R is granted, an actor holding R authors the gated
|
|
518
|
-
* write (the exact loop the negative leg can't cover). Emits nothing when no granter is discoverable.
|
|
519
|
-
*/
|
|
520
|
-
/** The canonical set of relation-bootstrap legs for a domain (co2 #2 multi-target): the `relationBootstraps[]`
|
|
521
|
-
* array when present, else the singular `relationBootstrap` as a one-element list, else empty. */
|
|
522
|
-
function allRelationBootstrapLegs(pl: ProofLegs): readonly RelationBootstrapLeg[] {
|
|
523
|
-
return pl.relationBootstraps ?? (pl.relationBootstrap !== undefined ? [pl.relationBootstrap] : []);
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
/** Emit the seed-then-prove lines for EVERY discovered gated-create authority leg (co2 #2), or — when the
|
|
527
|
-
* domain has a gated create but NO seedable path — the single loud gap diagnostic. */
|
|
528
|
-
function relationSeedLegsLines(pl: ProofLegs, factory: string, n: () => number, gap: string | undefined): string[] {
|
|
529
|
-
const legs = allRelationBootstrapLegs(pl);
|
|
530
|
-
if (legs.length === 0) return relationSeedLegLines(undefined, factory, n, gap);
|
|
531
|
-
return legs.flatMap((leg) => relationSeedLegLines(leg, factory, n, undefined));
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
function relationSeedLegLines(
|
|
535
|
-
leg: RelationBootstrapLeg | undefined,
|
|
536
|
-
factory: string,
|
|
537
|
-
n: () => number,
|
|
538
|
-
diagnostic?: string,
|
|
539
|
-
): string[] {
|
|
540
|
-
if (leg === undefined) {
|
|
541
|
-
if (diagnostic === undefined) return [];
|
|
542
|
-
// A gated directive exists but no authority could be seeded — say EXACTLY why (a useful failure, not a
|
|
543
|
-
// silent skip). The gate is still proven negatively by the conformance leg above.
|
|
544
|
-
return [
|
|
545
|
-
`// ${n()}. relation-bootstrap leg: this law has a gated directive the proof COULD NOT seed authority for.`,
|
|
546
|
-
`console.log(${JSON.stringify(`– relation-bootstrap leg SKIPPED — ${diagnostic}`)});`,
|
|
547
|
-
``,
|
|
548
|
-
];
|
|
549
|
-
}
|
|
550
|
-
const ACTOR = `"user:proof-conformance"`;
|
|
551
|
-
const BARE_ACTOR = `"proof-conformance"`; // the uid without the `user:` prefix — for WRAPPED subject fields
|
|
552
|
-
const speculative = leg.seedSpeculative === true;
|
|
553
|
-
// A payload literal with subject/founder-conventional fields (+ the routed set) overwritten to the actor —
|
|
554
|
-
// so a seat lands ON the actor AND every write-once/author-binding/founder-binding invariant holds. A WRAPPED
|
|
555
|
-
// subject field (the plan lifts a bare uid into `user:${field}`) gets the BARE uid — else the re-wrap yields
|
|
556
|
-
// a double-prefixed `user:user:<uid>` the gate never stamped.
|
|
557
|
-
const actorSeatLit = (payload: Record<string, unknown>, routed: readonly string[], wrapped: readonly string[] = []): string => {
|
|
558
|
-
const overwrite = new Set<string>(routed);
|
|
559
|
-
const wrappedSet = new Set<string>(wrapped);
|
|
560
|
-
const entries = Object.entries(payload).map(([k, v]) =>
|
|
561
|
-
wrappedSet.has(k)
|
|
562
|
-
? `${JSON.stringify(k)}: ${BARE_ACTOR}`
|
|
563
|
-
: overwrite.has(k) || /^(subject|principal|grantedBy|grantee|member|user|owner|founder)$/i.test(k)
|
|
564
|
-
? `${JSON.stringify(k)}: ${ACTOR}`
|
|
565
|
-
: `${JSON.stringify(k)}: ${JSON.stringify(v)}`,
|
|
566
|
-
);
|
|
567
|
-
return `{ ${entries.join(", ")} }`;
|
|
568
|
-
};
|
|
569
|
-
const grantPayloadLit = actorSeatLit(leg.grantPayload, [...leg.grantSubjectFields, ...leg.grantGrantedByFields], leg.grantSubjectWrappedFields ?? []);
|
|
570
|
-
// The PREREQUISITE-SEED CHAIN dispatches (each as the actor = founder) BEFORE the grant.
|
|
571
|
-
const prereqLines = (leg.prerequisiteSeeds ?? []).flatMap((seed) => [
|
|
572
|
-
` // prerequisite: ${leg.domain}/${seed.directiveId} establishes the state the grant's invariant reads (as founder = ACTOR).`,
|
|
573
|
-
` await ${factory}(holon, { actor: ACTOR }).${seed.directiveId}(${actorSeatLit(seed.payload, [])});`,
|
|
574
|
-
` ok(\`relation bootstrap (prerequisite) — ${leg.domain}/${seed.directiveId} established (as founder \${ACTOR}) before the grant\`);`,
|
|
575
|
-
]);
|
|
576
|
-
const gatedPayloadLit = leg.gatedMintField !== undefined && leg.gatedAggregateId !== undefined
|
|
577
|
-
? `{ ...${JSON.stringify(leg.gatedPayload)}, ${JSON.stringify(leg.gatedMintField)}: await holon.mint(${JSON.stringify(leg.gatedAggregateId)}) }`
|
|
578
|
-
: JSON.stringify(leg.gatedPayload);
|
|
579
|
-
const computedNote = leg.grantedRelation !== leg.relation && !speculative
|
|
580
|
-
? ` — a COMPUTED gate: granting "${leg.grantedRelation}" makes the actor satisfy "${leg.relation}" via the rewrite`
|
|
581
|
-
: ``;
|
|
582
|
-
|
|
583
|
-
if (leg.bornContext !== undefined) {
|
|
584
|
-
// LAYER 4 — THE BORN-DOMAIN GENESIS-AUTHORITY PATH (co2 home). R is established ONLY by the birth genesis
|
|
585
|
-
// (e.g. birthHome → seedHomeCreator seats creator), and the seed carries an owner-binding PREREQUISITE a
|
|
586
|
-
// standalone re-seed can't satisfy. This leg is proven by `githolon proof` (offline, on the local wasm):
|
|
587
|
-
// the runner BIRTHS the workspace as the proof actor (= owner), then dispatches the gated directive INSIDE
|
|
588
|
-
// the born workspace as that owner, asserting ADMITTED at the real kernel gate (a refusal degrades to a
|
|
589
|
-
// diagnostic — never a false green). The LIVE `--live` lane proves the underlying birth via the child-birth
|
|
590
|
-
// leg; the gated-directive-in-born-context proof is the offline runner's (birthing a throwaway home per
|
|
591
|
-
// gated directive live would leave orphan workspaces).
|
|
592
|
-
const bc = leg.bornContext;
|
|
593
|
-
return [
|
|
594
|
-
`// ${n()}. RELATION BOOTSTRAP — LAYER 4 (born-domain genesis authority): \`${leg.domain}/${leg.gatedDirectiveId}\``,
|
|
595
|
-
`// is GATED (\`.requires("${leg.relation}")\`), established ONLY by the birth genesis (\`${bc.birthDirectiveId}\` →`,
|
|
596
|
-
`// \`${bc.seatsViaDirectiveId}\` seats "${leg.relation}") — NOT a standalone-replayable seed (its owner-binding`,
|
|
597
|
-
`// prerequisite would false-fail). Proven by \`githolon proof\` (offline): birth the workspace as the owner,`,
|
|
598
|
-
`// then dispatch \`${leg.gatedDirectiveId}\` INSIDE the born workspace as that owner (real-gate admitted).`,
|
|
599
|
-
`console.log("– relation-bootstrap leg (born-context) — \`${leg.domain}/${leg.gatedDirectiveId}\` .requires(\\"${leg.relation}\\") is proven in the BORN workspace by \`githolon proof\` (offline); the birth is proven live by the child-birth leg");`,
|
|
600
|
-
``,
|
|
601
|
-
];
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
if (speculative) {
|
|
605
|
-
// LAYER 3 (seed-path replay). The gate relation is reachable only through ttu/intersection/exclusion — no
|
|
606
|
-
// directly-grantable base. We REPLAY the domain's declared seed and let the REAL GATE arbitrate: if the
|
|
607
|
-
// gated write is ADMITTED, it is a green positive leg; if REFUSED, it is a DIAGNOSTIC (never a false
|
|
608
|
-
// green) — the discovered seed was not the authority path. Step (2) is wrapped so a refusal degrades.
|
|
609
|
-
const seats = (leg.seedWritesRelations ?? [leg.grantedRelation]).join(", ");
|
|
610
|
-
return [
|
|
611
|
-
`// ${n()}. RELATION BOOTSTRAP — LAYER 3 (seed-path replay): \`${leg.domain}/${leg.gatedDirectiveId}\` is GATED`,
|
|
612
|
-
`// (\`.requires("${leg.relation}")\`), reachable only via ttu/intersection (no directly-grantable base).`,
|
|
613
|
-
`// REPLAY the declared seed \`${leg.domain}/${leg.grantDirectiveId}\` (seats [${seats}]) on the actor, then let`,
|
|
614
|
-
`// the REAL gate decide: admitted ⇒ proven; refused ⇒ diagnostic (a speculative seed never false-greens).`,
|
|
615
|
-
`{`,
|
|
616
|
-
` const realFetch3 = globalThis.fetch;`,
|
|
617
|
-
` globalThis.fetch = (() => { throw new Error("OFFLINE PROOF VIOLATED: relation-bootstrap touched the network"); }) as typeof fetch;`,
|
|
618
|
-
` try {`,
|
|
619
|
-
` const ACTOR = ${ACTOR};`,
|
|
620
|
-
` let seedReplayed = false;`,
|
|
621
|
-
` try {`,
|
|
622
|
-
` await ${factory}(holon, { actor: ACTOR }).${leg.grantDirectiveId}(${grantPayloadLit});`,
|
|
623
|
-
` seedReplayed = true;`,
|
|
624
|
-
` } catch (e) {`,
|
|
625
|
-
` console.log(\`– relation-bootstrap leg SKIPPED (layer-3 seed-path) — the declared seed ${leg.domain}/${leg.grantDirectiveId} could not be replayed (\${(e as Error)?.message ?? e}); gate ${leg.gatedDirectiveId} stays proven negatively\`);`,
|
|
626
|
-
` }`,
|
|
627
|
-
` if (seedReplayed) {`,
|
|
628
|
-
` try {`,
|
|
629
|
-
` await ${factory}(holon, { actor: ACTOR }).${leg.gatedDirectiveId}(${gatedPayloadLit});`,
|
|
630
|
-
` ok(\`relation bootstrap (seed-path) — ${leg.domain}/${leg.gatedDirectiveId} authored after replaying the declared seed ${leg.grantDirectiveId} (the REAL gate confirmed it establishes authority for "${leg.relation}")\`);`,
|
|
631
|
-
` } catch (e) {`,
|
|
632
|
-
` // ANTI-FALSE-GREEN: the real gate REFUSED — the discovered seed was not the authority path. Do NOT`,
|
|
633
|
-
` // assert a passing leg; emit a diagnostic (the conformance leg still proves the gate has teeth).`,
|
|
634
|
-
` console.log(\`– relation-bootstrap leg SKIPPED (layer-3 seed-path) — replaying ${leg.grantDirectiveId} did NOT authorize ${leg.gatedDirectiveId} .requires("${leg.relation}") on the real gate (\${(e as Error)?.message ?? e}); no seed-path is provable\`);`,
|
|
635
|
-
` }`,
|
|
636
|
-
` }`,
|
|
637
|
-
` } finally {`,
|
|
638
|
-
` globalThis.fetch = realFetch3;`,
|
|
639
|
-
` }`,
|
|
640
|
-
`}`,
|
|
641
|
-
``,
|
|
642
|
-
];
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
const prereqNote = (leg.prerequisiteSeeds ?? []).length > 0
|
|
646
|
-
? ` The grant needs prerequisite seed(s) [${(leg.prerequisiteSeeds ?? []).map((s) => s.directiveId).join(", ")}] run first (as the actor = founder).`
|
|
647
|
-
: ``;
|
|
648
|
-
// TARGET PRECONDITION SETUPS (co2 #3): the gated establisher(s) for the target's `.requiresExisting`/`.where`
|
|
649
|
-
// parent — dispatched AFTER the grant (the actor now holds the relation), BEFORE the target, so its declared
|
|
650
|
-
// predicate (e.g. `lifecycle == "active"`) holds against the REAL parent, not a shape-only shell.
|
|
651
|
-
const precondLines = (leg.preconditionSetups ?? []).flatMap((s) => {
|
|
652
|
-
const lit = s.autoStamped.length > 0
|
|
653
|
-
? `(() => { const p: Record<string, unknown> = { ...${JSON.stringify(s.payload)} }; ${s.autoStamped.map((f) => `p[${JSON.stringify(f)}] = new Date().toISOString();`).join(" ")} return p; })()`
|
|
654
|
-
: JSON.stringify(s.payload);
|
|
655
|
-
return [
|
|
656
|
-
` // precondition: dispatch the REAL ${leg.domain}/${s.directiveId} (gated by "${s.requiresRelation ?? leg.relation}", already seated) so the target's declared predicate holds against real state.`,
|
|
657
|
-
` await ${factory}(holon, { actor: ACTOR }).${s.directiveId}(${lit});`,
|
|
658
|
-
` ok(\`relation bootstrap (precondition) — ${leg.domain}/${s.directiveId} established before ${leg.gatedDirectiveId} (real parent state, not a shell)\`);`,
|
|
659
|
-
];
|
|
660
|
-
});
|
|
661
|
-
return [
|
|
662
|
-
`// ${n()}. RELATION BOOTSTRAP (seed-then-prove the POSITIVE): \`${leg.domain}/${leg.gatedDirectiveId}\` is GATED`,
|
|
663
|
-
`// (\`.requires("${leg.relation}")\`). \`${leg.domain}/${leg.grantDirectiveId}\` GRANTS "${leg.grantedRelation}" (${leg.via})${computedNote}.${prereqNote}`,
|
|
664
|
-
`// Grant it to the proof actor, THEN prove the gated write SUCCEEDS under that actor (what the negative`,
|
|
665
|
-
`// conformance leg above can't cover — a seeded holder authoring a gated write).`,
|
|
666
|
-
`{`,
|
|
667
|
-
` const realFetch3 = globalThis.fetch;`,
|
|
668
|
-
` globalThis.fetch = (() => { throw new Error("OFFLINE PROOF VIOLATED: relation-bootstrap touched the network"); }) as typeof fetch;`,
|
|
669
|
-
` try {`,
|
|
670
|
-
` const ACTOR = ${ACTOR};`,
|
|
671
|
-
...prereqLines,
|
|
672
|
-
` // (1) grant "${leg.grantedRelation}" to ACTOR (the grant folds a writeTuple; subject seated on ACTOR).`,
|
|
673
|
-
` await ${factory}(holon, { actor: ACTOR }).${leg.grantDirectiveId}(${grantPayloadLit});`,
|
|
674
|
-
` ok(\`relation bootstrap (grant) — ${leg.domain}/${leg.grantDirectiveId} granted "${leg.grantedRelation}" to \${ACTOR}${leg.grantedRelation !== leg.relation ? ` (⇒ satisfies "${leg.relation}" via rewrite)` : ``}\`);`,
|
|
675
|
-
...precondLines,
|
|
676
|
-
` // (2) the GATED write now SUCCEEDS under the seeded actor (holds "${leg.relation}"${leg.grantedRelation !== leg.relation ? ` via "${leg.grantedRelation}"` : ``}).`,
|
|
677
|
-
` await ${factory}(holon, { actor: ACTOR }).${leg.gatedDirectiveId}(${gatedPayloadLit});`,
|
|
678
|
-
` ok(\`relation bootstrap (prove) — ${leg.domain}/${leg.gatedDirectiveId} authored under the seeded holder of "${leg.relation}" (gated write SUCCEEDS once granted)\`);`,
|
|
679
|
-
` } finally {`,
|
|
680
|
-
` globalThis.fetch = realFetch3;`,
|
|
681
|
-
` }`,
|
|
682
|
-
`}`,
|
|
683
|
-
``,
|
|
684
|
-
];
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
// ── relation-bootstrap discovery (seed the gate: grant relation R, then prove a .requires(R) write SUCCEEDS) ──
|
|
688
|
-
//
|
|
689
|
-
// The NEGATIVE conformance leg above proves an ACTORLESS gated offer can't be generated. This POSITIVE leg
|
|
690
|
-
// proves the other half: with the relation GRANTED, an actor that holds it authors the gated write SUCCESS-
|
|
691
|
-
// fully. It needs a sibling directive that ESTABLISHES the relation (folds a granting `writeTuple`). We find
|
|
692
|
-
// it two ways: FIRST a `.grants(R)` producer marker (the static, unambiguous lane — symmetric to `.requires`);
|
|
693
|
-
// ELSE plan-probe each sibling for a `writeTuple`/relation-tuple op whose `relation === R` (inference).
|
|
694
|
-
|
|
695
|
-
/** A discovered RELATION-BOOTSTRAP leg: a gated (`.requires(R)`) directive plus the sibling directive that
|
|
696
|
-
* GRANTS R. The proof dispatches the grant FIRST (its subject/grantedBy set to the seeded proof actor), then
|
|
697
|
-
* dispatches the gated directive under that actor and asserts it SUCCEEDS — the seed-then-prove positive. */
|
|
698
469
|
export interface RelationBootstrapLeg {
|
|
699
470
|
/** The gated directive's domain. */
|
|
700
471
|
readonly domain: string;
|
|
@@ -709,16 +480,31 @@ export interface RelationBootstrapLeg {
|
|
|
709
480
|
readonly grantedRelation: string;
|
|
710
481
|
/** The synthesized gated-directive payload (auto-stamped `…At` omitted; mintField removed). */
|
|
711
482
|
readonly gatedPayload: Record<string, unknown>;
|
|
483
|
+
readonly gatedReferenceFields?: Readonly<Record<string, string>>;
|
|
484
|
+
readonly gatedOptionalReferenceFields?: readonly string[];
|
|
712
485
|
readonly gatedAutoStamped: readonly string[];
|
|
486
|
+
/** Referential marker of the business consequence; distinguishes a fresh create from an idempotent ensure. */
|
|
487
|
+
readonly gatedMarker?: ReferentialMarker;
|
|
488
|
+
/** Application-owned identity field. The generated runner removes it from tenant input, binds it to the
|
|
489
|
+
* connected participant, and authors this action as that same participant. */
|
|
490
|
+
readonly gatedPrincipalField?: string;
|
|
713
491
|
/** When the gated directive's own id is kernel-minted, the field to mint at runtime. */
|
|
714
492
|
readonly gatedMintField?: string;
|
|
715
|
-
/** The gated create's aggregate id
|
|
493
|
+
/** The gated create's aggregate id. Present for every `.creates` target so its returned typed reference
|
|
494
|
+
* can feed later generated business actions; also supplies the mint type tag when applicable. */
|
|
716
495
|
readonly gatedAggregateId?: string;
|
|
496
|
+
/** Every aggregate type this business action writes, including fan-out/secondary mutations. Used only by
|
|
497
|
+
* the generated proof scheduler to replay the target's dependency slice instead of the whole workspace. */
|
|
498
|
+
readonly gatedWrittenAggregates?: readonly string[];
|
|
499
|
+
/** Aggregate types read by invariants guarding this action. These are state dependencies even when no
|
|
500
|
+
* payload field is a direct ref (for example a composite-key catalogue snapshot). */
|
|
501
|
+
readonly gatedReadAggregates?: readonly string[];
|
|
717
502
|
/** The granting directive id (the sibling that folds the granting `writeTuple`). */
|
|
718
503
|
readonly grantDirectiveId: string;
|
|
719
504
|
/** The synthesized grant payload (auto-stamped `…At` omitted). The SUBJECT fields (below) are overwritten
|
|
720
505
|
* at runtime with the seeded proof actor so the grant seats authority ON that actor. */
|
|
721
506
|
readonly grantPayload: Record<string, unknown>;
|
|
507
|
+
readonly grantReferenceFields?: Readonly<Record<string, string>>;
|
|
722
508
|
readonly grantAutoStamped: readonly string[];
|
|
723
509
|
/** The grant-payload field(s) whose value the plan routed into the tuple SUBJECT — set to the proof actor
|
|
724
510
|
* at dispatch so the granted relation lands on the actor the gated write runs as. */
|
|
@@ -736,7 +522,7 @@ export interface RelationBootstrapLeg {
|
|
|
736
522
|
* the REAL KERNEL GATE is the arbiter of whether it authorizes the write), or `"born-context"` (LAYER 4:
|
|
737
523
|
* R is established by the domain's BIRTH GENESIS — the gated directive is proven INSIDE the born workspace
|
|
738
524
|
* as the owner; see {@link BornContextAuthority}). Audit + runner semantics. */
|
|
739
|
-
readonly via: "marker" | "inferred" | "computed-base" | "seed-path" | "born-context";
|
|
525
|
+
readonly via: "marker" | "inferred" | "computed-base" | "seed-path" | "born-context" | "unrestricted";
|
|
740
526
|
/** LAYER-3 ANTI-FALSE-GREEN FLAG. `true` ⇒ this is a SPECULATIVE seed-path leg: codegen could NOT prove
|
|
741
527
|
* (via rewrite resolution) that replaying `grantDirectiveId` authorizes the gated write — it discovered a
|
|
742
528
|
* seed that writes relation tuples and is DEFERRING to the real kernel gate. The runner replays the seed,
|
|
@@ -766,15 +552,33 @@ export interface RelationBootstrapLeg {
|
|
|
766
552
|
* (each as the proof actor = founder, so their own write-once/author-binding invariants hold) BEFORE the
|
|
767
553
|
* grant — then the grant + the gated write succeed. A non-birth analog of {@link BornContextAuthority}. */
|
|
768
554
|
readonly prerequisiteSeeds?: readonly PrerequisiteSeed[];
|
|
769
|
-
/** THE TARGET'S OWN PRECONDITION SETUPS (co2 #3). The gated
|
|
555
|
+
/** THE TARGET'S OWN PRECONDITION SETUPS (co2 #3). The gated action carries a `.requiresExisting`/`.where`
|
|
770
556
|
* precondition reading ANOTHER aggregate by a payload-id field with a predicate (e.g. `createClassification
|
|
771
557
|
* Draft` needs a `ClassificationScheme` at `p.schemeId` with `lifecycle == "active"`). The establishing
|
|
772
|
-
* create (`createClassificationScheme`) is itself GATED by the SAME relation this leg already seats
|
|
773
|
-
*
|
|
774
|
-
*
|
|
775
|
-
* the grant (the actor holds the relation) and BEFORE the target, satisfying its declared predicate — not a
|
|
776
|
-
* shape-only shell. Dependency-ordered. */
|
|
558
|
+
* create (`createClassificationScheme`) is itself GATED by the SAME relation this leg already seats. The
|
|
559
|
+
* runner dispatches it AFTER the grant and BEFORE the target, then replaces the target's typed-reference
|
|
560
|
+
* field with the real record id it established. Source and target payload field names need not coincide. */
|
|
777
561
|
readonly preconditionSetups?: readonly PreconditionSetup[];
|
|
562
|
+
/** A dynamic cross-workspace business premise. The generated fixture privately builds the source's own
|
|
563
|
+
* business history through its published actions, serves the declared query, seats the target law's
|
|
564
|
+
* hidden trust record and carries the signed answer. Tenant examples still contain only the target
|
|
565
|
+
* business action. */
|
|
566
|
+
readonly sourceTrust?: SourceTrustProofSetup;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
export interface SourceTrustProofSetup {
|
|
570
|
+
readonly viaDirectiveId: string;
|
|
571
|
+
readonly fromPayload: string;
|
|
572
|
+
readonly queryId: string;
|
|
573
|
+
readonly queryArgs: Readonly<Record<string, string>>;
|
|
574
|
+
readonly copy: Readonly<Record<string, string>>;
|
|
575
|
+
readonly genesisField: string;
|
|
576
|
+
readonly keyHashesField: string;
|
|
577
|
+
readonly sourceDomain: string;
|
|
578
|
+
readonly sourceAggregateId: string;
|
|
579
|
+
readonly sourceQueryKey: readonly string[];
|
|
580
|
+
/** Smallest generated business prefix that establishes a row returned by the source query. */
|
|
581
|
+
readonly sourceJourney: readonly RelationBootstrapLeg[];
|
|
778
582
|
}
|
|
779
583
|
|
|
780
584
|
/** One target-precondition setup (co2 #3): a REAL create of a parent aggregate the gated target's precondition
|
|
@@ -782,9 +586,14 @@ export interface RelationBootstrapLeg {
|
|
|
782
586
|
export interface PreconditionSetup {
|
|
783
587
|
/** The establishing create directive id (e.g. `createClassificationScheme`). */
|
|
784
588
|
readonly directiveId: string;
|
|
785
|
-
/** Its synthesized payload
|
|
786
|
-
*
|
|
589
|
+
/** Its synthesized payload. The runner observes the real record it establishes and binds the target's
|
|
590
|
+
* typed-reference field to that id; no shared synthesized SQL-style key is required. */
|
|
787
591
|
readonly payload: Record<string, unknown>;
|
|
592
|
+
/** Aggregate this setup establishes. A capped singleton may already exist because birth genesis created it;
|
|
593
|
+
* the runner then reuses that state instead of replaying one-time ceremony. */
|
|
594
|
+
readonly aggregateType: string;
|
|
595
|
+
readonly reuseIfPresent?: boolean;
|
|
596
|
+
readonly referenceFields?: Readonly<Record<string, string>>;
|
|
788
597
|
readonly autoStamped: readonly string[];
|
|
789
598
|
/** The relation the establisher is gated by (informational; the runner dispatches it as the actor already
|
|
790
599
|
* holding this leg's seated relation — the common case is the SAME relation as the target). */
|
|
@@ -799,6 +608,7 @@ export interface PrerequisiteSeed {
|
|
|
799
608
|
/** The synthesized payload; founder/subject-conventional fields are set to the proof actor at dispatch so
|
|
800
609
|
* the seed's own write-once/author-binding invariants pass (founder == the seeding actor). */
|
|
801
610
|
readonly payload: Record<string, unknown>;
|
|
611
|
+
readonly referenceFields?: Readonly<Record<string, string>>;
|
|
802
612
|
readonly autoStamped: readonly string[];
|
|
803
613
|
}
|
|
804
614
|
|
|
@@ -865,7 +675,7 @@ export function probePlanOps(
|
|
|
865
675
|
): { ops: FieldOp[]; synth: SynthesizedPayload; probePayload: Record<string, unknown> } | undefined {
|
|
866
676
|
let synth: SynthesizedPayload;
|
|
867
677
|
try {
|
|
868
|
-
synth =
|
|
678
|
+
synth = synthesizeDirectivePayload(dir, where);
|
|
869
679
|
} catch {
|
|
870
680
|
return undefined;
|
|
871
681
|
}
|
|
@@ -1130,7 +940,7 @@ function probeBirthGenesis(
|
|
|
1130
940
|
): { chain: Array<{ domain?: string; directiveId?: string; actor?: unknown }>; synth: SynthesizedPayload } | undefined {
|
|
1131
941
|
let synth: SynthesizedPayload;
|
|
1132
942
|
try {
|
|
1133
|
-
synth =
|
|
943
|
+
synth = synthesizeDirectivePayload(birthDir, where);
|
|
1134
944
|
} catch {
|
|
1135
945
|
return undefined;
|
|
1136
946
|
}
|
|
@@ -1175,8 +985,23 @@ export function discoverBornContextAuthority(
|
|
|
1175
985
|
if (probed === undefined) continue;
|
|
1176
986
|
const { chain, synth } = probed;
|
|
1177
987
|
// Find the genesis step (in THIS domain) whose directive writeTuples `relation` — that step seats R.
|
|
988
|
+
// Business birth carries the declared founder policy as private metadata, so proof discovery does not
|
|
989
|
+
// have to reverse-engineer a role from synthesized tuple payloads (which can be deliberately opaque).
|
|
1178
990
|
let seatsVia: string | undefined;
|
|
991
|
+
const businessPolicy = (bd as unknown as {
|
|
992
|
+
businessBirth?: { founderRoles?: readonly string[] };
|
|
993
|
+
}).businessBirth;
|
|
994
|
+
if (businessPolicy?.founderRoles?.includes(relation) === true) {
|
|
995
|
+
seatsVia = chain
|
|
996
|
+
.filter((step) => step?.domain === domain && typeof step.directiveId === "string")
|
|
997
|
+
.map((step) => step.directiveId as string)
|
|
998
|
+
.find((directiveId) =>
|
|
999
|
+
(byId.get(directiveId) as { businessBirthInternal?: unknown } | undefined)
|
|
1000
|
+
?.businessBirthInternal === true,
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1179
1003
|
for (const step of chain) {
|
|
1004
|
+
if (seatsVia !== undefined) break;
|
|
1180
1005
|
if (step === null || typeof step !== "object" || step.domain !== domain || typeof step.directiveId !== "string") continue;
|
|
1181
1006
|
const stepDir = byId.get(step.directiveId);
|
|
1182
1007
|
if (stepDir === undefined) continue;
|
|
@@ -1243,16 +1068,36 @@ function directiveHasPrerequisiteInvariant(mod: DomainModule, directiveId: strin
|
|
|
1243
1068
|
/** The AGGREGATE TYPES a directive's plan writes (set/ensure/create ops) — used to find the sibling seed that
|
|
1244
1069
|
* establishes the state a prerequisite invariant reads. `[]` ⇒ not probeable / writes nothing. */
|
|
1245
1070
|
function probeWritesAggregateTypes(dir: Directive<unknown>, where: string): Set<string> {
|
|
1071
|
+
const out = new Set<string>([dir.aggregateId]);
|
|
1072
|
+
for (const output of dir.secondaryOutputs ?? []) out.add(output.aggregateId);
|
|
1246
1073
|
const probed = probePlanOps(dir, where);
|
|
1074
|
+
for (const o of probed?.ops ?? []) if (typeof o.aggregateType === "string" && o.aggregateType.length > 0) out.add(o.aggregateType);
|
|
1075
|
+
return out;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
/** Aggregate types this directive can make exist when they were absent. A mutation may write every field of
|
|
1079
|
+
* its target but cannot establish it; confusing those ideas made proof generation omit the prerequisite create
|
|
1080
|
+
* for typed `requiresExisting` mutations. Declared and probed secondary outputs keep intent fan-out visible. */
|
|
1081
|
+
function probeEstablishesAggregateTypes(dir: Directive<unknown>, where: string): Set<string> {
|
|
1247
1082
|
const out = new Set<string>();
|
|
1248
|
-
if (
|
|
1249
|
-
for (const
|
|
1083
|
+
if (dir.marker === "creates" || dir.marker === "ensures") out.add(dir.aggregateId);
|
|
1084
|
+
for (const output of dir.secondaryOutputs ?? []) {
|
|
1085
|
+
if (output.marker === "creates" || output.marker === "ensures") out.add(output.aggregateId);
|
|
1086
|
+
}
|
|
1087
|
+
const probed = probePlanOps(dir, where);
|
|
1088
|
+
for (const op of probed?.ops ?? []) {
|
|
1089
|
+
const marker = (op as { marker?: string }).marker;
|
|
1090
|
+
if ((marker === "creates" || marker === "ensures") && typeof op.aggregateType === "string" && op.aggregateType.length > 0) {
|
|
1091
|
+
out.add(op.aggregateType);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1250
1094
|
return out;
|
|
1251
1095
|
}
|
|
1252
1096
|
|
|
1253
1097
|
/** The aggregate TYPES a workspaceInvariant's declared reads depend on — the state its predicate needs to be
|
|
1254
|
-
* established first.
|
|
1255
|
-
*
|
|
1098
|
+
* established first. `pre:` says which SNAPSHOT the gate reads, not whether the dependency is self- or
|
|
1099
|
+
* cross-aggregate; callers compare these types with the directive's own writes to distinguish those cases.
|
|
1100
|
+
* Best-effort: an un-callable reads fn yields none. */
|
|
1256
1101
|
function invariantReadAggregateTypes(inv: { reads: (ctx: { intent: Record<string, unknown>; principal?: string }) => Array<{ aggregate: string; id: string }> }): Set<string> {
|
|
1257
1102
|
const out = new Set<string>();
|
|
1258
1103
|
let refs: Array<{ aggregate: string; id: string }>;
|
|
@@ -1263,7 +1108,6 @@ function invariantReadAggregateTypes(inv: { reads: (ctx: { intent: Record<string
|
|
|
1263
1108
|
}
|
|
1264
1109
|
for (const r of refs) {
|
|
1265
1110
|
if (r === null || typeof r !== "object") continue;
|
|
1266
|
-
if (typeof r.id === "string" && r.id.startsWith("pre:")) continue; // write-once self-check, not a dependency
|
|
1267
1111
|
if (typeof r.aggregate === "string" && r.aggregate.length > 0) out.add(r.aggregate);
|
|
1268
1112
|
}
|
|
1269
1113
|
return out;
|
|
@@ -1289,7 +1133,7 @@ export function discoverPrerequisiteChain(mod: DomainModule, granter: Directive<
|
|
|
1289
1133
|
const synthSeed = (dir: Directive<unknown>): PrerequisiteSeed | undefined => {
|
|
1290
1134
|
let synth: SynthesizedPayload;
|
|
1291
1135
|
try {
|
|
1292
|
-
synth =
|
|
1136
|
+
synth = synthesizeDirectivePayload(dir, `${domain}/${dir.id}`);
|
|
1293
1137
|
} catch {
|
|
1294
1138
|
return undefined;
|
|
1295
1139
|
}
|
|
@@ -1297,7 +1141,13 @@ export function discoverPrerequisiteChain(mod: DomainModule, granter: Directive<
|
|
|
1297
1141
|
const mintField = agg !== undefined ? mintedCreateField(dir, agg) : undefined;
|
|
1298
1142
|
const payload: Record<string, unknown> = { ...synth.value };
|
|
1299
1143
|
if (mintField !== undefined) delete payload[mintField];
|
|
1300
|
-
|
|
1144
|
+
const referenceFields = aggregateReferenceFields(dir.payloadSchema as z.ZodTypeAny);
|
|
1145
|
+
return {
|
|
1146
|
+
directiveId: dir.id,
|
|
1147
|
+
payload,
|
|
1148
|
+
...(Object.keys(referenceFields).length > 0 ? { referenceFields } : {}),
|
|
1149
|
+
autoStamped: synth.autoStamped,
|
|
1150
|
+
};
|
|
1301
1151
|
};
|
|
1302
1152
|
// Resolve `dir`'s prerequisites (a sibling seed per cross-step aggregate dependency), recursively. Returns
|
|
1303
1153
|
// false when ANY prerequisite has no establishing sibling seed (the whole chain is then unsatisfiable).
|
|
@@ -1330,54 +1180,87 @@ export function discoverPrerequisiteChain(mod: DomainModule, granter: Directive<
|
|
|
1330
1180
|
}
|
|
1331
1181
|
|
|
1332
1182
|
/**
|
|
1333
|
-
* TARGET-PRECONDITION SETUPS (co2 #3). A gated
|
|
1183
|
+
* TARGET-PRECONDITION SETUPS (co2 #3). A gated action may carry a `.requiresExisting`/`.where` precondition
|
|
1334
1184
|
* reading a PARENT aggregate by a payload-id field with a predicate (e.g. `createClassificationDraft` needs a
|
|
1335
1185
|
* `ClassificationScheme` at `p.schemeId` with `lifecycle == "active"`). The proof must ESTABLISH that parent
|
|
1336
1186
|
* with REAL state — a shape-only shell fails the predicate (`classification-scheme-retired`). For each such
|
|
1337
1187
|
* precondition we find the parent's own create directive (`createClassificationScheme`, itself gated) and
|
|
1338
|
-
* record it as a setup dispatched AFTER the grant / BEFORE the target
|
|
1339
|
-
*
|
|
1340
|
-
*
|
|
1341
|
-
* with no discoverable create (the predicate then arbitrates at the real gate — never a false green).
|
|
1188
|
+
* record it as a setup dispatched AFTER the grant / BEFORE the target. The runner binds the target's typed
|
|
1189
|
+
* reference to the record this setup really established, so differently named or derived keys are fine. Skip
|
|
1190
|
+
* only a self-precondition the target actually establishes; a mutation still needs a prior create/ensure.
|
|
1342
1191
|
*/
|
|
1343
1192
|
function discoverPreconditionSetups(mod: DomainModule, gated: Directive<unknown>): PreconditionSetup[] {
|
|
1344
1193
|
const domain = mod.domain ?? mod.name;
|
|
1345
1194
|
const invariants = (mod.workspaceInvariants ?? []) as Array<WorkspaceInvariantDecl>;
|
|
1346
1195
|
const directives = mod.directives as Directive<unknown>[];
|
|
1347
|
-
|
|
1196
|
+
let gatedSample: Record<string, unknown> = {};
|
|
1197
|
+
try {
|
|
1198
|
+
gatedSample = { ...synthesizeDirectivePayload(gated, `${domain}/${gated.id}`).value };
|
|
1199
|
+
} catch {
|
|
1200
|
+
// The caller will already diagnose an unsampleable public action. Keep prerequisite discovery best-effort.
|
|
1201
|
+
}
|
|
1348
1202
|
const setups: PreconditionSetup[] = [];
|
|
1349
1203
|
const seen = new Set<string>();
|
|
1350
1204
|
for (const inv of invariants) {
|
|
1351
1205
|
if (inv.on !== gated.id) continue; // preconditions on THIS target only
|
|
1206
|
+
// An absence/write-once invariant deliberately accepts an empty pre-state; creating a sibling record
|
|
1207
|
+
// would make that action fail. Only seek an establisher when the invariant itself rejects the empty
|
|
1208
|
+
// snapshot, which is the semantic definition of an existing-state prerequisite.
|
|
1209
|
+
let refs: Array<{ name?: string; aggregate: string }> = [];
|
|
1210
|
+
try {
|
|
1211
|
+
refs = inv.reads({ intent: gatedSample, principal: "user:proof-participant", principalVerified: true });
|
|
1212
|
+
const empty = Object.fromEntries(refs.map((ref) => [ref.name ?? ref.aggregate, undefined]));
|
|
1213
|
+
const verdict = inv.assert(empty as never, {
|
|
1214
|
+
intent: gatedSample,
|
|
1215
|
+
principal: "user:proof-participant",
|
|
1216
|
+
principalVerified: true,
|
|
1217
|
+
reads: [],
|
|
1218
|
+
});
|
|
1219
|
+
if ("accept" in verdict && verdict.accept === true) continue;
|
|
1220
|
+
} catch {
|
|
1221
|
+
// A predicate which cannot evaluate an absent snapshot is conservatively an existing-state dependency.
|
|
1222
|
+
}
|
|
1352
1223
|
for (const aggType of invariantReadAggregateTypes(inv)) {
|
|
1353
|
-
if (gatedWrites.has(aggType)) continue; // self-precondition — the target establishes it in its own apply
|
|
1354
1224
|
// The parent's own ESTABLISHER — a directive that WRITES `aggType` at a DETERMINISTIC (dev-chosen) key:
|
|
1355
1225
|
// an `.ensures(<aggType>)` keyed upsert (the kernel's sanctioned non-minted-id lane), or a create-once
|
|
1356
|
-
// (`.withId`).
|
|
1357
|
-
//
|
|
1358
|
-
//
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1226
|
+
// (`.withId`). The runner observes its real output and supplies that id to the target. A truly MINTED
|
|
1227
|
+
// `.creates` needs a mint recipe this setup shape does not yet carry, so deterministic ensures/creates
|
|
1228
|
+
// remain the supported establishers and the real gate arbitrates anything else.
|
|
1229
|
+
const creator = directives
|
|
1230
|
+
.map((directive, index) => ({ directive, index }))
|
|
1231
|
+
.filter(({ directive: d }) => {
|
|
1232
|
+
if (d === gated || typeof d?.payloadSchema !== "object" || d.birthsChild === true) return false;
|
|
1233
|
+
if (!probeWritesAggregateTypes(d, `${domain}/${d.id}`).has(aggType)) return false;
|
|
1234
|
+
const isEnsures = d.marker === "ensures";
|
|
1235
|
+
const isCreateOnce = d.marker === "creates" && (d as { createOnceIdField?: unknown }).createOnceIdField !== undefined;
|
|
1236
|
+
return isEnsures || isCreateOnce;
|
|
1237
|
+
})
|
|
1238
|
+
// Prerequisites are business history. Prefer the published intent which establishes it; a private
|
|
1239
|
+
// completion/recovery effect can write the same aggregate but is not a tenant-reachable producer.
|
|
1240
|
+
.sort((left, right) => {
|
|
1241
|
+
const publicRank = (directive: Directive<unknown>): number =>
|
|
1242
|
+
isBusinessIntent(directive) && !directive.id.startsWith("_nms") ? 0 : 1;
|
|
1243
|
+
return publicRank(left.directive) - publicRank(right.directive) || left.index - right.index;
|
|
1244
|
+
})[0]?.directive;
|
|
1368
1245
|
if (creator === undefined) continue;
|
|
1369
1246
|
if (seen.has(creator.id)) continue;
|
|
1370
1247
|
let synth: SynthesizedPayload;
|
|
1371
1248
|
try {
|
|
1372
|
-
synth =
|
|
1249
|
+
synth = synthesizeDirectivePayload(creator, `${domain}/${creator.id}`);
|
|
1373
1250
|
} catch {
|
|
1374
1251
|
continue;
|
|
1375
1252
|
}
|
|
1376
1253
|
const payload: Record<string, unknown> = { ...synth.value };
|
|
1254
|
+
const referenceFields = aggregateReferenceFields(creator.payloadSchema as z.ZodTypeAny);
|
|
1377
1255
|
seen.add(creator.id);
|
|
1378
1256
|
setups.push({
|
|
1379
1257
|
directiveId: creator.id,
|
|
1380
1258
|
payload,
|
|
1259
|
+
aggregateType: aggType,
|
|
1260
|
+
...(((mod.aggregates as AggregateHandle[]).find((aggregate) => aggregate.id === aggType)?.cap === 1)
|
|
1261
|
+
? { reuseIfPresent: true }
|
|
1262
|
+
: {}),
|
|
1263
|
+
...(Object.keys(referenceFields).length > 0 ? { referenceFields } : {}),
|
|
1381
1264
|
autoStamped: synth.autoStamped,
|
|
1382
1265
|
...(creator.requiresRelationGate !== undefined ? { requiresRelation: creator.requiresRelationGate.relation } : {}),
|
|
1383
1266
|
});
|
|
@@ -1398,6 +1281,8 @@ export function discoverRelationBootstrapLeg(mod: DomainModule, gatedOverride?:
|
|
|
1398
1281
|
const directives = mod.directives as Directive<unknown>[];
|
|
1399
1282
|
const isGatedProvable = (d: Directive<unknown> | undefined): boolean =>
|
|
1400
1283
|
d?.requiresRelationGate !== undefined &&
|
|
1284
|
+
!d.id.startsWith("_nms") &&
|
|
1285
|
+
(d as { businessBirthInternal?: unknown }).businessBirthInternal !== true &&
|
|
1401
1286
|
typeof d.payloadSchema === "object" &&
|
|
1402
1287
|
d.birthsChild !== true &&
|
|
1403
1288
|
d.attestationAuthorizedGate === undefined;
|
|
@@ -1420,7 +1305,7 @@ export function discoverRelationBootstrapLeg(mod: DomainModule, gatedOverride?:
|
|
|
1420
1305
|
// Synthesize the gated payload (mirror of the conformance leg — mintField removed, auto-stamped omitted).
|
|
1421
1306
|
let gatedSynth: SynthesizedPayload;
|
|
1422
1307
|
try {
|
|
1423
|
-
gatedSynth =
|
|
1308
|
+
gatedSynth = synthesizeDirectivePayload(gated, `${domain}/${gated.id}`);
|
|
1424
1309
|
} catch {
|
|
1425
1310
|
return undefined;
|
|
1426
1311
|
}
|
|
@@ -1428,6 +1313,67 @@ export function discoverRelationBootstrapLeg(mod: DomainModule, gatedOverride?:
|
|
|
1428
1313
|
const gatedMintField = gatedAgg !== undefined ? mintedCreateField(gated, gatedAgg) : undefined;
|
|
1429
1314
|
const gatedPayload: Record<string, unknown> = { ...gatedSynth.value };
|
|
1430
1315
|
if (gatedMintField !== undefined) delete gatedPayload[gatedMintField];
|
|
1316
|
+
const gatedReferenceFields = aggregateReferenceFields(gated.payloadSchema as z.ZodTypeAny);
|
|
1317
|
+
const gatedOptionalReferenceFields = optionalAggregateReferenceFields(gated.payloadSchema as z.ZodTypeAny);
|
|
1318
|
+
const gatedCreatesAggregate = gated.marker === "creates" || gated.marker === "ensures"
|
|
1319
|
+
? gated.aggregateId
|
|
1320
|
+
: undefined;
|
|
1321
|
+
const gatedWrittenAggregates = [...probeWritesAggregateTypes(gated, `${domain}/${gated.id}`)].sort();
|
|
1322
|
+
const gatedReadAggregates = [...new Set(
|
|
1323
|
+
(mod.workspaceInvariants ?? [])
|
|
1324
|
+
.filter((invariant) => invariant.on === gated.id)
|
|
1325
|
+
.flatMap((invariant) => [...invariantReadAggregateTypes(invariant)]),
|
|
1326
|
+
)].sort();
|
|
1327
|
+
|
|
1328
|
+
// A declared business birth is the product's authority path, not merely one possible seed amongst many.
|
|
1329
|
+
// Prove the gated verb in the workspace that `app.birth(...)` actually creates. Otherwise an unrelated
|
|
1330
|
+
// legacy/bootstrap directive can look like a cleaner standalone granter and make the proof test a lane
|
|
1331
|
+
// tenant application code can no longer call (the exact false negative the one-surface work removes).
|
|
1332
|
+
const businessAuthorityRelations = [
|
|
1333
|
+
relation,
|
|
1334
|
+
...resolveGrantableBaseRelations(
|
|
1335
|
+
mod.relationTypes,
|
|
1336
|
+
gated.requiresRelationGate!.object,
|
|
1337
|
+
relation,
|
|
1338
|
+
).filter((candidate) => candidate !== relation),
|
|
1339
|
+
];
|
|
1340
|
+
let businessAuthorityRelation: string | undefined;
|
|
1341
|
+
let businessBornContext: BornContextAuthority | undefined;
|
|
1342
|
+
for (const candidate of businessAuthorityRelations) {
|
|
1343
|
+
const discovered = discoverBornContextAuthority(mod, candidate);
|
|
1344
|
+
if (discovered === undefined) continue;
|
|
1345
|
+
businessAuthorityRelation = candidate;
|
|
1346
|
+
businessBornContext = discovered;
|
|
1347
|
+
break;
|
|
1348
|
+
}
|
|
1349
|
+
if (businessBornContext !== undefined) {
|
|
1350
|
+
const birthDirective = directives.find((d) => d.id === businessBornContext.birthDirectiveId);
|
|
1351
|
+
if ((birthDirective as { businessBirth?: unknown } | undefined)?.businessBirth !== undefined) {
|
|
1352
|
+
return {
|
|
1353
|
+
domain,
|
|
1354
|
+
gatedDirectiveId: gated.id,
|
|
1355
|
+
relation,
|
|
1356
|
+
grantedRelation: businessAuthorityRelation ?? relation,
|
|
1357
|
+
gatedPayload,
|
|
1358
|
+
...(Object.keys(gatedReferenceFields).length > 0 ? { gatedReferenceFields } : {}),
|
|
1359
|
+
...(gatedOptionalReferenceFields.length > 0 ? { gatedOptionalReferenceFields } : {}),
|
|
1360
|
+
gatedAutoStamped: gatedSynth.autoStamped,
|
|
1361
|
+
gatedMarker: gated.marker,
|
|
1362
|
+
...(gatedWrittenAggregates.length > 0 ? { gatedWrittenAggregates } : {}),
|
|
1363
|
+
...(gatedReadAggregates.length > 0 ? { gatedReadAggregates } : {}),
|
|
1364
|
+
...(gated.businessPrincipalField !== undefined ? { gatedPrincipalField: gated.businessPrincipalField } : {}),
|
|
1365
|
+
...(gatedMintField !== undefined ? { gatedMintField } : {}),
|
|
1366
|
+
...(gatedCreatesAggregate !== undefined ? { gatedAggregateId: gatedCreatesAggregate } : {}),
|
|
1367
|
+
grantDirectiveId: businessBornContext.seatsViaDirectiveId,
|
|
1368
|
+
grantPayload: {},
|
|
1369
|
+
grantAutoStamped: [],
|
|
1370
|
+
grantSubjectFields: [],
|
|
1371
|
+
grantGrantedByFields: [],
|
|
1372
|
+
via: "born-context",
|
|
1373
|
+
bornContext: businessBornContext,
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1431
1377
|
|
|
1432
1378
|
// Find a granter that establishes the actor's authority for the gate relation. Never the gated directive.
|
|
1433
1379
|
// (1) DIRECT: a `.grants(R)` marker (unambiguous), ELSE a plan-probed `writeTuple(R)`.
|
|
@@ -1520,16 +1466,33 @@ export function discoverRelationBootstrapLeg(mod: DomainModule, gatedOverride?:
|
|
|
1520
1466
|
const standaloneClean =
|
|
1521
1467
|
found !== undefined && !seedSpeculative && !directiveHasPrerequisiteInvariant(mod, found.granter.id);
|
|
1522
1468
|
if (!standaloneClean) {
|
|
1523
|
-
|
|
1469
|
+
// The birth may seat a BASE relation which satisfies this computed gate (homeCreator -> homeAdmin), not
|
|
1470
|
+
// the gate's display name itself. Search the same rewrite-aware candidate set used above; the real born
|
|
1471
|
+
// workspace gate remains the final arbiter, so this cannot manufacture authority.
|
|
1472
|
+
let bornAuthorityRelation: string | undefined;
|
|
1473
|
+
let bornContext: BornContextAuthority | undefined;
|
|
1474
|
+
for (const candidate of businessAuthorityRelations) {
|
|
1475
|
+
const discovered = discoverBornContextAuthority(mod, candidate);
|
|
1476
|
+
if (discovered === undefined) continue;
|
|
1477
|
+
bornAuthorityRelation = candidate;
|
|
1478
|
+
bornContext = discovered;
|
|
1479
|
+
break;
|
|
1480
|
+
}
|
|
1524
1481
|
if (bornContext !== undefined) {
|
|
1525
1482
|
return {
|
|
1526
1483
|
domain,
|
|
1527
1484
|
gatedDirectiveId: gated.id,
|
|
1528
1485
|
relation,
|
|
1529
|
-
grantedRelation: relation,
|
|
1486
|
+
grantedRelation: bornAuthorityRelation ?? relation,
|
|
1530
1487
|
gatedPayload,
|
|
1488
|
+
...(Object.keys(gatedReferenceFields).length > 0 ? { gatedReferenceFields } : {}),
|
|
1489
|
+
...(gatedOptionalReferenceFields.length > 0 ? { gatedOptionalReferenceFields } : {}),
|
|
1531
1490
|
gatedAutoStamped: gatedSynth.autoStamped,
|
|
1532
|
-
|
|
1491
|
+
gatedMarker: gated.marker,
|
|
1492
|
+
...(gatedWrittenAggregates.length > 0 ? { gatedWrittenAggregates } : {}),
|
|
1493
|
+
...(gatedReadAggregates.length > 0 ? { gatedReadAggregates } : {}),
|
|
1494
|
+
...(gatedMintField !== undefined ? { gatedMintField } : {}),
|
|
1495
|
+
...(gatedCreatesAggregate !== undefined ? { gatedAggregateId: gatedCreatesAggregate } : {}),
|
|
1533
1496
|
// grant fields are UNUSED for a born-context leg (the runner births + dispatches inside); populate them
|
|
1534
1497
|
// with the genesis seed's id / empty payload so the shape stays uniform (never dispatched standalone).
|
|
1535
1498
|
grantDirectiveId: bornContext.seatsViaDirectiveId,
|
|
@@ -1573,11 +1536,12 @@ export function discoverRelationBootstrapLeg(mod: DomainModule, gatedOverride?:
|
|
|
1573
1536
|
// any field whose name looks like a subject/grantedBy anyway).
|
|
1574
1537
|
let grantSynth: SynthesizedPayload;
|
|
1575
1538
|
try {
|
|
1576
|
-
grantSynth =
|
|
1539
|
+
grantSynth = synthesizeDirectivePayload(granter, `${domain}/${granter.id}`);
|
|
1577
1540
|
} catch {
|
|
1578
1541
|
return undefined;
|
|
1579
1542
|
}
|
|
1580
1543
|
const grantPayload: Record<string, unknown> = { ...grantSynth.value };
|
|
1544
|
+
const grantReferenceFields = aggregateReferenceFields(granter.payloadSchema as z.ZodTypeAny);
|
|
1581
1545
|
const probed = probePlanOps(granter, `${domain}/${granter.id}`);
|
|
1582
1546
|
const routed = probed !== undefined ? resolveGrantSubjectFields(probed) : { subjectFields: [], grantedByFields: [], wrappedSubjectFields: [] };
|
|
1583
1547
|
|
|
@@ -1587,10 +1551,18 @@ export function discoverRelationBootstrapLeg(mod: DomainModule, gatedOverride?:
|
|
|
1587
1551
|
relation,
|
|
1588
1552
|
grantedRelation,
|
|
1589
1553
|
gatedPayload,
|
|
1554
|
+
...(Object.keys(gatedReferenceFields).length > 0 ? { gatedReferenceFields } : {}),
|
|
1555
|
+
...(gatedOptionalReferenceFields.length > 0 ? { gatedOptionalReferenceFields } : {}),
|
|
1590
1556
|
gatedAutoStamped: gatedSynth.autoStamped,
|
|
1591
|
-
|
|
1557
|
+
gatedMarker: gated.marker,
|
|
1558
|
+
...(gatedWrittenAggregates.length > 0 ? { gatedWrittenAggregates } : {}),
|
|
1559
|
+
...(gatedReadAggregates.length > 0 ? { gatedReadAggregates } : {}),
|
|
1560
|
+
...(gated.businessPrincipalField !== undefined ? { gatedPrincipalField: gated.businessPrincipalField } : {}),
|
|
1561
|
+
...(gatedMintField !== undefined ? { gatedMintField } : {}),
|
|
1562
|
+
...(gatedCreatesAggregate !== undefined ? { gatedAggregateId: gatedCreatesAggregate } : {}),
|
|
1592
1563
|
grantDirectiveId: granter.id,
|
|
1593
1564
|
grantPayload,
|
|
1565
|
+
...(Object.keys(grantReferenceFields).length > 0 ? { grantReferenceFields } : {}),
|
|
1594
1566
|
grantAutoStamped: grantSynth.autoStamped,
|
|
1595
1567
|
grantSubjectFields: routed.subjectFields,
|
|
1596
1568
|
grantGrantedByFields: routed.grantedByFields,
|
|
@@ -1604,6 +1576,137 @@ export function discoverRelationBootstrapLeg(mod: DomainModule, gatedOverride?:
|
|
|
1604
1576
|
};
|
|
1605
1577
|
}
|
|
1606
1578
|
|
|
1579
|
+
/** A principal-bound action has no grant to bootstrap: its authority is the verified application identity
|
|
1580
|
+
* itself. It still needs a real born business workspace and any declared prerequisite records, so represent
|
|
1581
|
+
* it in the same stateful journey shape while marking the identity field explicitly for the runner. */
|
|
1582
|
+
function discoverPrincipalBoundLeg(
|
|
1583
|
+
mod: DomainModule,
|
|
1584
|
+
target: Directive<unknown>,
|
|
1585
|
+
): RelationBootstrapLeg | undefined {
|
|
1586
|
+
const principalField = target.businessPrincipalField;
|
|
1587
|
+
if (principalField === undefined || target.requiresRelationGate !== undefined) return undefined;
|
|
1588
|
+
const domain = mod.domain ?? mod.name;
|
|
1589
|
+
let synth: SynthesizedPayload;
|
|
1590
|
+
try {
|
|
1591
|
+
synth = synthesizeDirectivePayload(target, `${domain}/${target.id}`);
|
|
1592
|
+
} catch {
|
|
1593
|
+
return undefined;
|
|
1594
|
+
}
|
|
1595
|
+
const birthDirectives = (mod.directives as Directive<unknown>[]).filter(
|
|
1596
|
+
(directive) => directive.birthsChild === true &&
|
|
1597
|
+
(directive as { businessBirth?: { founderRoles?: readonly string[] } }).businessBirth !== undefined,
|
|
1598
|
+
);
|
|
1599
|
+
let bornContext: BornContextAuthority | undefined;
|
|
1600
|
+
for (const birth of birthDirectives) {
|
|
1601
|
+
const roles = (birth as { businessBirth?: { founderRoles?: readonly string[] } }).businessBirth?.founderRoles ?? [];
|
|
1602
|
+
for (const role of roles) {
|
|
1603
|
+
bornContext = discoverBornContextAuthority(mod, role);
|
|
1604
|
+
if (bornContext !== undefined && bornContext.birthDirectiveId === birth.id) break;
|
|
1605
|
+
}
|
|
1606
|
+
if (bornContext !== undefined) break;
|
|
1607
|
+
}
|
|
1608
|
+
if (bornContext === undefined) return undefined;
|
|
1609
|
+
|
|
1610
|
+
const aggregate = (mod.aggregates as AggregateHandle[]).find((candidate) => candidate.id === target.aggregateId);
|
|
1611
|
+
const mintField = aggregate === undefined ? undefined : mintedCreateField(target, aggregate);
|
|
1612
|
+
const payload: Record<string, unknown> = { ...synth.value };
|
|
1613
|
+
if (mintField !== undefined) delete payload[mintField];
|
|
1614
|
+
const referenceFields = aggregateReferenceFields(target.payloadSchema as z.ZodTypeAny);
|
|
1615
|
+
const optionalReferenceFields = optionalAggregateReferenceFields(target.payloadSchema as z.ZodTypeAny);
|
|
1616
|
+
const establishesAggregate = target.marker === "creates" || target.marker === "ensures"
|
|
1617
|
+
? target.aggregateId
|
|
1618
|
+
: undefined;
|
|
1619
|
+
const writtenAggregates = [...probeWritesAggregateTypes(target, `${domain}/${target.id}`)].sort();
|
|
1620
|
+
const readAggregates = [...new Set(
|
|
1621
|
+
(mod.workspaceInvariants ?? [])
|
|
1622
|
+
.filter((invariant) => invariant.on === target.id)
|
|
1623
|
+
.flatMap((invariant) => [...invariantReadAggregateTypes(invariant)]),
|
|
1624
|
+
)].sort();
|
|
1625
|
+
const preconditionSetups = discoverPreconditionSetups(mod, target);
|
|
1626
|
+
return {
|
|
1627
|
+
domain,
|
|
1628
|
+
gatedDirectiveId: target.id,
|
|
1629
|
+
relation: "currentPrincipal",
|
|
1630
|
+
grantedRelation: "currentPrincipal",
|
|
1631
|
+
gatedPayload: payload,
|
|
1632
|
+
...(Object.keys(referenceFields).length > 0 ? { gatedReferenceFields: referenceFields } : {}),
|
|
1633
|
+
...(optionalReferenceFields.length > 0 ? { gatedOptionalReferenceFields: optionalReferenceFields } : {}),
|
|
1634
|
+
gatedAutoStamped: synth.autoStamped,
|
|
1635
|
+
gatedMarker: target.marker,
|
|
1636
|
+
...(writtenAggregates.length > 0 ? { gatedWrittenAggregates: writtenAggregates } : {}),
|
|
1637
|
+
...(readAggregates.length > 0 ? { gatedReadAggregates: readAggregates } : {}),
|
|
1638
|
+
gatedPrincipalField: principalField,
|
|
1639
|
+
...(mintField !== undefined ? { gatedMintField: mintField } : {}),
|
|
1640
|
+
...(establishesAggregate !== undefined ? { gatedAggregateId: establishesAggregate } : {}),
|
|
1641
|
+
grantDirectiveId: bornContext.seatsViaDirectiveId,
|
|
1642
|
+
grantPayload: {},
|
|
1643
|
+
grantAutoStamped: [],
|
|
1644
|
+
grantSubjectFields: [],
|
|
1645
|
+
grantGrantedByFields: [],
|
|
1646
|
+
via: "born-context",
|
|
1647
|
+
bornContext,
|
|
1648
|
+
...(preconditionSetups.length > 0 ? { preconditionSetups } : {}),
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
/** An ordinary business action may intentionally need no role gate. It is still part of the same generated
|
|
1653
|
+
* stateful application journey: its inputs are typed refs produced by earlier intents, and a business-birth
|
|
1654
|
+
* domain must run it inside that born workspace. Treating it as "not proofable" merely because no authority
|
|
1655
|
+
* ceremony is required left conflict merge policies public but unexecuted. */
|
|
1656
|
+
function discoverUnrestrictedBusinessLeg(
|
|
1657
|
+
mod: DomainModule,
|
|
1658
|
+
target: Directive<unknown>,
|
|
1659
|
+
bornContext?: BornContextAuthority,
|
|
1660
|
+
): RelationBootstrapLeg | undefined {
|
|
1661
|
+
if (target.requiresRelationGate !== undefined || target.businessPrincipalField !== undefined) return undefined;
|
|
1662
|
+
const domain = mod.domain ?? mod.name;
|
|
1663
|
+
let synth: SynthesizedPayload;
|
|
1664
|
+
try {
|
|
1665
|
+
synth = synthesizeDirectivePayload(target, `${domain}/${target.id}`);
|
|
1666
|
+
} catch {
|
|
1667
|
+
return undefined;
|
|
1668
|
+
}
|
|
1669
|
+
const aggregate = (mod.aggregates as AggregateHandle[]).find((candidate) => candidate.id === target.aggregateId);
|
|
1670
|
+
const mintField = aggregate === undefined ? undefined : mintedCreateField(target, aggregate);
|
|
1671
|
+
const payload: Record<string, unknown> = { ...synth.value };
|
|
1672
|
+
if (mintField !== undefined) delete payload[mintField];
|
|
1673
|
+
const referenceFields = aggregateReferenceFields(target.payloadSchema as z.ZodTypeAny);
|
|
1674
|
+
const optionalReferenceFields = optionalAggregateReferenceFields(target.payloadSchema as z.ZodTypeAny);
|
|
1675
|
+
const establishesAggregate = target.marker === "creates" || target.marker === "ensures"
|
|
1676
|
+
? target.aggregateId
|
|
1677
|
+
: undefined;
|
|
1678
|
+
const writtenAggregates = [...probeWritesAggregateTypes(target, `${domain}/${target.id}`)].sort();
|
|
1679
|
+
const readAggregates = [...new Set(
|
|
1680
|
+
(mod.workspaceInvariants ?? [])
|
|
1681
|
+
.filter((invariant) => invariant.on === target.id)
|
|
1682
|
+
.flatMap((invariant) => [...invariantReadAggregateTypes(invariant)]),
|
|
1683
|
+
)].sort();
|
|
1684
|
+
const preconditionSetups = discoverPreconditionSetups(mod, target);
|
|
1685
|
+
return {
|
|
1686
|
+
domain,
|
|
1687
|
+
gatedDirectiveId: target.id,
|
|
1688
|
+
relation: "unrestricted",
|
|
1689
|
+
grantedRelation: "unrestricted",
|
|
1690
|
+
gatedPayload: payload,
|
|
1691
|
+
...(Object.keys(referenceFields).length > 0 ? { gatedReferenceFields: referenceFields } : {}),
|
|
1692
|
+
...(optionalReferenceFields.length > 0 ? { gatedOptionalReferenceFields: optionalReferenceFields } : {}),
|
|
1693
|
+
gatedAutoStamped: synth.autoStamped,
|
|
1694
|
+
gatedMarker: target.marker,
|
|
1695
|
+
...(writtenAggregates.length > 0 ? { gatedWrittenAggregates: writtenAggregates } : {}),
|
|
1696
|
+
...(readAggregates.length > 0 ? { gatedReadAggregates: readAggregates } : {}),
|
|
1697
|
+
...(mintField !== undefined ? { gatedMintField: mintField } : {}),
|
|
1698
|
+
...(establishesAggregate !== undefined ? { gatedAggregateId: establishesAggregate } : {}),
|
|
1699
|
+
grantDirectiveId: "",
|
|
1700
|
+
grantPayload: {},
|
|
1701
|
+
grantAutoStamped: [],
|
|
1702
|
+
grantSubjectFields: [],
|
|
1703
|
+
grantGrantedByFields: [],
|
|
1704
|
+
via: bornContext === undefined ? "unrestricted" : "born-context",
|
|
1705
|
+
...(bornContext === undefined ? {} : { bornContext }),
|
|
1706
|
+
...(preconditionSetups.length > 0 ? { preconditionSetups } : {}),
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1607
1710
|
/**
|
|
1608
1711
|
* MULTI-TARGET relation-bootstrap discovery (co2 ask #2). The singular {@link discoverRelationBootstrapLeg}
|
|
1609
1712
|
* proves ONE gated create per domain; this runs TARGET-SPECIFIC discovery for EVERY gated create/write, so a
|
|
@@ -1613,20 +1716,48 @@ export function discoverRelationBootstrapLeg(mod: DomainModule, gatedOverride?:
|
|
|
1613
1716
|
* discoverable path is simply absent from the result (the taxonomy audit then names ONLY the real gaps).
|
|
1614
1717
|
* De-duplicated by `gatedDirectiveId` (a target proven once is not re-emitted).
|
|
1615
1718
|
*/
|
|
1616
|
-
export function discoverRelationBootstrapLegs(
|
|
1719
|
+
export function discoverRelationBootstrapLegs(
|
|
1720
|
+
mod: DomainModule,
|
|
1721
|
+
unrestrictedBornContext?: BornContextAuthority,
|
|
1722
|
+
): RelationBootstrapLeg[] {
|
|
1617
1723
|
const directives = mod.directives as Directive<unknown>[];
|
|
1618
|
-
const
|
|
1619
|
-
d?.requiresRelationGate !== undefined &&
|
|
1724
|
+
const isBusinessJourneyProvable = (d: Directive<unknown> | undefined): boolean =>
|
|
1725
|
+
(d?.requiresRelationGate !== undefined || d?.businessPrincipalField !== undefined || isBusinessIntent(d)) &&
|
|
1726
|
+
!d.id.startsWith("_nms") &&
|
|
1727
|
+
(d as { businessBirthInternal?: unknown }).businessBirthInternal !== true &&
|
|
1620
1728
|
typeof d.payloadSchema === "object" &&
|
|
1621
1729
|
d.birthsChild !== true &&
|
|
1622
1730
|
d.attestationAuthorizedGate === undefined;
|
|
1623
1731
|
const legs: RelationBootstrapLeg[] = [];
|
|
1624
1732
|
const seen = new Set<string>();
|
|
1625
|
-
|
|
1733
|
+
// This is a generated STATEFUL business journey, not a bag of CRUD probes. Establish root records before
|
|
1734
|
+
// actions that consume typed references, and keep framework-generated clear utilities behind authored
|
|
1735
|
+
// business intents. Stable source order is the final tie-breaker.
|
|
1736
|
+
const targets = directives
|
|
1737
|
+
.map((directive, index) => ({ directive, index }))
|
|
1738
|
+
.filter(({ directive }) => isBusinessJourneyProvable(directive))
|
|
1739
|
+
.sort((a, b) => {
|
|
1740
|
+
const authoredA = isBusinessIntent(a.directive) ? 0 : 1;
|
|
1741
|
+
const authoredB = isBusinessIntent(b.directive) ? 0 : 1;
|
|
1742
|
+
// A declared application language is already the tenant's readable business sequence. Preserve that
|
|
1743
|
+
// order among authored intents; only place compiler-generated utilities behind it.
|
|
1744
|
+
if (authoredA === 0 && authoredB === 0) return a.index - b.index;
|
|
1745
|
+
const markerRank = (directive: Directive<unknown>): number =>
|
|
1746
|
+
directive.marker === "creates" ? 0 : directive.marker === "ensures" ? 1 : 2;
|
|
1747
|
+
const refsA = Object.keys(aggregateReferenceFields(a.directive.payloadSchema as z.ZodTypeAny)).length;
|
|
1748
|
+
const refsB = Object.keys(aggregateReferenceFields(b.directive.payloadSchema as z.ZodTypeAny)).length;
|
|
1749
|
+
return authoredA - authoredB || markerRank(a.directive) - markerRank(b.directive) || refsA - refsB || a.index - b.index;
|
|
1750
|
+
})
|
|
1751
|
+
.map(({ directive }) => directive);
|
|
1752
|
+
for (const target of targets) {
|
|
1626
1753
|
if (seen.has(target.id)) continue;
|
|
1627
1754
|
let leg: RelationBootstrapLeg | undefined;
|
|
1628
1755
|
try {
|
|
1629
|
-
leg =
|
|
1756
|
+
leg = target.requiresRelationGate !== undefined
|
|
1757
|
+
? discoverRelationBootstrapLeg(mod, target)
|
|
1758
|
+
: target.businessPrincipalField !== undefined
|
|
1759
|
+
? discoverPrincipalBoundLeg(mod, target)
|
|
1760
|
+
: discoverUnrestrictedBusinessLeg(mod, target, unrestrictedBornContext);
|
|
1630
1761
|
} catch {
|
|
1631
1762
|
leg = undefined;
|
|
1632
1763
|
}
|
|
@@ -1635,7 +1766,97 @@ export function discoverRelationBootstrapLegs(mod: DomainModule): RelationBootst
|
|
|
1635
1766
|
seen.add(leg.gatedDirectiveId);
|
|
1636
1767
|
}
|
|
1637
1768
|
}
|
|
1638
|
-
|
|
1769
|
+
|
|
1770
|
+
// Source order is the readable default, but it cannot override the type system: an action accepting a
|
|
1771
|
+
// NomosRef<T> must run after some generated action has actually established T. Build the smallest stable
|
|
1772
|
+
// dependency graph over the discovered business legs. This is not CRUD ordering; an intent may establish
|
|
1773
|
+
// several aggregates, and every declared/probed output participates as a possible producer.
|
|
1774
|
+
const byId = new Map(directives.map((directive, index) => [directive.id, { directive, index }]));
|
|
1775
|
+
const businessOrder = new Map(legs.map((leg, index) => [leg.gatedDirectiveId, index]));
|
|
1776
|
+
const establishes = new Map<string, Set<string>>();
|
|
1777
|
+
const requiredReferences = new Map<string, Set<string>>();
|
|
1778
|
+
for (const leg of legs) {
|
|
1779
|
+
const directive = byId.get(leg.gatedDirectiveId)?.directive;
|
|
1780
|
+
if (directive === undefined) continue;
|
|
1781
|
+
establishes.set(
|
|
1782
|
+
leg.gatedDirectiveId,
|
|
1783
|
+
probeEstablishesAggregateTypes(directive, `${mod.domain ?? mod.name}/${directive.id}`),
|
|
1784
|
+
);
|
|
1785
|
+
const optional = new Set(leg.gatedOptionalReferenceFields ?? []);
|
|
1786
|
+
requiredReferences.set(
|
|
1787
|
+
leg.gatedDirectiveId,
|
|
1788
|
+
new Set(Object.entries(leg.gatedReferenceFields ?? {})
|
|
1789
|
+
.filter(([field]) => !optional.has(field))
|
|
1790
|
+
.map(([, aggregate]) => aggregate)),
|
|
1791
|
+
);
|
|
1792
|
+
}
|
|
1793
|
+
const dependencies = new Map(legs.map((leg) => [leg.gatedDirectiveId, new Set<string>()]));
|
|
1794
|
+
for (const consumer of legs) {
|
|
1795
|
+
for (const aggregate of requiredReferences.get(consumer.gatedDirectiveId) ?? []) {
|
|
1796
|
+
const candidates = legs
|
|
1797
|
+
.filter((producer) =>
|
|
1798
|
+
producer.gatedDirectiveId !== consumer.gatedDirectiveId &&
|
|
1799
|
+
establishes.get(producer.gatedDirectiveId)?.has(aggregate) === true)
|
|
1800
|
+
.sort((a, b) => {
|
|
1801
|
+
const da = byId.get(a.gatedDirectiveId)!;
|
|
1802
|
+
const db = byId.get(b.gatedDirectiveId)!;
|
|
1803
|
+
const markerRank = (directive: Directive<unknown>): number =>
|
|
1804
|
+
directive.marker === "creates" ? 0 : directive.marker === "ensures" ? 1 : 2;
|
|
1805
|
+
const selfReferenceRank = (leg: RelationBootstrapLeg): number =>
|
|
1806
|
+
requiredReferences.get(leg.gatedDirectiveId)?.has(aggregate) === true ? 1 : 0;
|
|
1807
|
+
return selfReferenceRank(a) - selfReferenceRank(b) ||
|
|
1808
|
+
markerRank(da.directive) - markerRank(db.directive) ||
|
|
1809
|
+
(requiredReferences.get(a.gatedDirectiveId)?.size ?? 0) - (requiredReferences.get(b.gatedDirectiveId)?.size ?? 0) ||
|
|
1810
|
+
(businessOrder.get(a.gatedDirectiveId) ?? da.index) - (businessOrder.get(b.gatedDirectiveId) ?? db.index);
|
|
1811
|
+
});
|
|
1812
|
+
const producer = candidates[0];
|
|
1813
|
+
if (producer !== undefined) dependencies.get(consumer.gatedDirectiveId)!.add(producer.gatedDirectiveId);
|
|
1814
|
+
}
|
|
1815
|
+
const consumerDirective = byId.get(consumer.gatedDirectiveId)?.directive;
|
|
1816
|
+
if (consumerDirective?.marker === "mutates" || consumerDirective?.marker === "archives") {
|
|
1817
|
+
// A deterministic/composite-id mutation often has no explicit NomosRef<T> field: the plan derives its
|
|
1818
|
+
// instance id from ordinary business coordinates (site + feature key, listing + version). It still
|
|
1819
|
+
// cannot precede the action that establishes T. Treat the directive's declared target type as a
|
|
1820
|
+
// dependency whenever the business language exposes a positive creator/ensurer for that type.
|
|
1821
|
+
const targetProducer = legs
|
|
1822
|
+
.filter((candidate) => candidate.gatedDirectiveId !== consumer.gatedDirectiveId)
|
|
1823
|
+
.filter((candidate) => {
|
|
1824
|
+
const candidateDirective = byId.get(candidate.gatedDirectiveId)?.directive;
|
|
1825
|
+
return candidateDirective !== undefined &&
|
|
1826
|
+
(candidateDirective.marker === "creates" || candidateDirective.marker === "ensures") &&
|
|
1827
|
+
establishes.get(candidate.gatedDirectiveId)?.has(consumerDirective.aggregateId) === true;
|
|
1828
|
+
})
|
|
1829
|
+
.sort((left, right) => {
|
|
1830
|
+
const leftDirective = byId.get(left.gatedDirectiveId)!.directive;
|
|
1831
|
+
const rightDirective = byId.get(right.gatedDirectiveId)!.directive;
|
|
1832
|
+
const rank = (directive: Directive<unknown>): number => directive.marker === "creates" ? 0 : 1;
|
|
1833
|
+
return rank(leftDirective) - rank(rightDirective) ||
|
|
1834
|
+
(businessOrder.get(left.gatedDirectiveId) ?? 0) - (businessOrder.get(right.gatedDirectiveId) ?? 0);
|
|
1835
|
+
})[0];
|
|
1836
|
+
if (targetProducer !== undefined) {
|
|
1837
|
+
dependencies.get(consumer.gatedDirectiveId)!.add(targetProducer.gatedDirectiveId);
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
const remaining = new Map(legs.map((leg) => [leg.gatedDirectiveId, leg]));
|
|
1842
|
+
const ordered: RelationBootstrapLeg[] = [];
|
|
1843
|
+
while (remaining.size > 0) {
|
|
1844
|
+
const ready = [...remaining.values()]
|
|
1845
|
+
.filter((leg) => [...(dependencies.get(leg.gatedDirectiveId) ?? [])].every((id) => !remaining.has(id)))
|
|
1846
|
+
.sort((a, b) => (businessOrder.get(a.gatedDirectiveId) ?? 0) - (businessOrder.get(b.gatedDirectiveId) ?? 0));
|
|
1847
|
+
if (ready.length === 0) {
|
|
1848
|
+
// A genuine required-reference cycle cannot be linearized. Preserve the deterministic source suffix;
|
|
1849
|
+
// the proof will name the missing real reference rather than silently dropping business actions.
|
|
1850
|
+
ordered.push(...[...remaining.values()].sort((a, b) =>
|
|
1851
|
+
(businessOrder.get(a.gatedDirectiveId) ?? 0) - (businessOrder.get(b.gatedDirectiveId) ?? 0)));
|
|
1852
|
+
break;
|
|
1853
|
+
}
|
|
1854
|
+
for (const leg of ready) {
|
|
1855
|
+
ordered.push(leg);
|
|
1856
|
+
remaining.delete(leg.gatedDirectiveId);
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
return ordered;
|
|
1639
1860
|
}
|
|
1640
1861
|
|
|
1641
1862
|
/** A discovered layer-3 SEED directive: a sibling (non-gated, non-birth) whose plan writes relation tuples,
|
|
@@ -1687,7 +1908,7 @@ export function diagnoseRelationBootstrapGap(mod: DomainModule): string | undefi
|
|
|
1687
1908
|
const inv = (mod.workspaceInvariants ?? []).find((i) => i.on === prereqGranter.id);
|
|
1688
1909
|
return `${domain}/${gated.id} is gated by .requires("${relation}") but the proof cannot seed authority: the only granter of "${relation}" is `
|
|
1689
1910
|
+ `${domain}/${prereqGranter.id}, which carries the prerequisite invariant '${inv?.id ?? "?"}' (it reads state an EARLIER step must establish), so a standalone re-seed FALSE-fails — and no \`.births()\` genesis seats "${relation}" to prove it in a born workspace. `
|
|
1690
|
-
+ `Establish "${relation}" via a birth genesis (a \`.births()\` whose chain runs ${prereqGranter.id} after seeding its prerequisite)
|
|
1911
|
+
+ `Establish "${relation}" via a birth genesis (a \`.births()\` whose chain runs ${prereqGranter.id} after seeding its prerequisite) so the generator can produce the positive journey. (The negative conformance leg still proves the gate refuses an actorless offer.)`;
|
|
1691
1912
|
}
|
|
1692
1913
|
const bases = resolveGrantableBaseRelations(mod.relationTypes, object, relation).filter((b) => b !== relation);
|
|
1693
1914
|
const tried = bases.length > 0
|
|
@@ -1937,7 +2158,7 @@ export function discoverChildBirthLegOrReason(
|
|
|
1937
2158
|
let recipe: { workspace?: unknown; genesisChain?: unknown } | undefined;
|
|
1938
2159
|
let synth: SynthesizedPayload;
|
|
1939
2160
|
try {
|
|
1940
|
-
synth =
|
|
2161
|
+
synth = synthesizeDirectivePayload(birthDir, `${parentDomain}/${birthDir.id}`);
|
|
1941
2162
|
} catch (e) {
|
|
1942
2163
|
return { reason: `${who}: the payload could not be synthesized for the compile probe (${(e as Error)?.message ?? e}) — the offline proof needs a sampleable payload (strings/numbers/enums; exotic shapes drop the leg)` };
|
|
1943
2164
|
}
|
|
@@ -2119,7 +2340,7 @@ function discoverSingletonInstanceId(seedDir: Directive<unknown>, ownerMod?: Dom
|
|
|
2119
2340
|
const idsFor = (seed: number): string | undefined => {
|
|
2120
2341
|
let synth: SynthesizedPayload;
|
|
2121
2342
|
try {
|
|
2122
|
-
synth =
|
|
2343
|
+
synth = synthesizeDirectivePayload(seedDir, `seed/${seedDir.id}`);
|
|
2123
2344
|
} catch {
|
|
2124
2345
|
return undefined;
|
|
2125
2346
|
}
|
|
@@ -2190,6 +2411,8 @@ export interface ProofLegs {
|
|
|
2190
2411
|
readonly payload?: Record<string, unknown>;
|
|
2191
2412
|
readonly autoStamped?: readonly string[];
|
|
2192
2413
|
readonly mintField?: string;
|
|
2414
|
+
/** Hidden owned-record sealing coordinates used by the local product proof. Tenant tests never see them. */
|
|
2415
|
+
readonly ownedEncryption?: ProofOwnedEncryption;
|
|
2193
2416
|
readonly query?: { readonly id: string; readonly params: Record<string, unknown> };
|
|
2194
2417
|
readonly count?: { readonly id: string; readonly group: string };
|
|
2195
2418
|
/** BIRTHS DX DIAGNOSTICS: present when this domain declares ≥1 `.births()` directive whose child-birth
|
|
@@ -2215,6 +2438,7 @@ export interface ProofLegs {
|
|
|
2215
2438
|
readonly payload: Record<string, unknown>;
|
|
2216
2439
|
readonly autoStamped: readonly string[];
|
|
2217
2440
|
readonly mintField?: string;
|
|
2441
|
+
readonly ownedEncryption?: ProofOwnedEncryption;
|
|
2218
2442
|
readonly query?: { readonly id: string; readonly params: Record<string, unknown> };
|
|
2219
2443
|
readonly count?: { readonly id: string; readonly group: string };
|
|
2220
2444
|
readonly spatial?: { readonly id: string };
|
|
@@ -2237,12 +2461,20 @@ export interface ProofLegs {
|
|
|
2237
2461
|
readonly relationBootstraps?: readonly RelationBootstrapLeg[];
|
|
2238
2462
|
}
|
|
2239
2463
|
|
|
2464
|
+
export interface ProofOwnedEncryption {
|
|
2465
|
+
readonly scope: string;
|
|
2466
|
+
readonly recordType: string;
|
|
2467
|
+
readonly fields: Readonly<Record<string, { readonly aggSid: string; readonly fieldSid: string }>>;
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2240
2470
|
/** Synthesize the standalone-create offline legs for one domain module, or `null` when it has no
|
|
2241
2471
|
* creating write (nothing to prove standalone — it may still be child-birth-provable / compile-valid).
|
|
2242
2472
|
* The SINGLE source of the create-leg selection — `generateTsProof` (the .mts marker) and
|
|
2243
2473
|
* `proofLegsIndex` (the per-domain index) both call this, so the offline lane proves any domain the
|
|
2244
2474
|
* same way the generated proof proves its primary one. */
|
|
2245
|
-
/**
|
|
2475
|
+
/** An ungated `.creates` or `.ensures` directive the proof can DRIVE from a synthesized sample offline WITH
|
|
2476
|
+
* NO SEEDED AUTHORITY. Ensures matter for singleton/upsert business laws: they still return a typed affected
|
|
2477
|
+
* reference and can be reopened through the same generated application journey. Excludes
|
|
2246
2478
|
* a `.births()` (a birth is the child-birth lane), not ATTESTATION-AUTHORIZED (needs a signed §3 quote no
|
|
2247
2479
|
* synthesizer can produce — e.g. the home law's `recordShare`), and NOT AUTHZ-GATED (`.requires(R)` — a
|
|
2248
2480
|
* gated create dispatched with no seeded authority is REFUSED at the gate; its metadata already says it
|
|
@@ -2250,7 +2482,9 @@ export interface ProofLegs {
|
|
|
2250
2482
|
* relation-bootstrap leg / the born-child genesis instead). Those are NOT standalone-provable offline; the
|
|
2251
2483
|
* domain proves via its bootstrap / child-birth / compile-valid legs instead. */
|
|
2252
2484
|
function isStandaloneCreate(d: Directive<unknown> | undefined): boolean {
|
|
2253
|
-
return d?.marker === "creates"
|
|
2485
|
+
return (d?.marker === "creates" ||
|
|
2486
|
+
(d?.marker === "ensures" && isBusinessIntent(d))) &&
|
|
2487
|
+
typeof d.payloadSchema === "object"
|
|
2254
2488
|
// Framework lifecycle directives are installed in the certified law so the generated client can
|
|
2255
2489
|
// drive them behind its managed methods, but they are not tenant vocabulary. Selecting one here
|
|
2256
2490
|
// makes the generated proof call a method that the public generated client correctly does not expose.
|
|
@@ -2265,11 +2499,89 @@ function isStandaloneCreate(d: Directive<unknown> | undefined): boolean {
|
|
|
2265
2499
|
&& (((d as { declaredForeignReads?: readonly unknown[] }).declaredForeignReads?.length ?? 0) === 0);
|
|
2266
2500
|
}
|
|
2267
2501
|
|
|
2268
|
-
/**
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2502
|
+
/**
|
|
2503
|
+
* A shape-level Create/Ensure is only a standalone business journey when no declared invariant fires on it.
|
|
2504
|
+
* Invariants are arbitrary policy: even a same-aggregate Ensure may require an earlier state (for example,
|
|
2505
|
+
* accepting an invitation requires that invitation to be pending and the current principal to be its
|
|
2506
|
+
* invitee). Treating the marker as CRUD and dispatching it into an empty workspace is a false proof.
|
|
2507
|
+
* Stateful actions belong in a generated prerequisite journey; until one is synthesized they remain named,
|
|
2508
|
+
* not misclassified as independently provable.
|
|
2509
|
+
*/
|
|
2510
|
+
function isStandaloneProofSafe(mod: DomainModule, directive: Directive<unknown> | undefined): boolean {
|
|
2511
|
+
return isStandaloneCreate(directive) &&
|
|
2512
|
+
!(mod.workspaceInvariants ?? []).some((invariant) => invariant.on === directive!.id);
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
/** Prefer the business create which demonstrates the richest typed relationship journey. Its prerequisites
|
|
2516
|
+
* are generated automatically, so a join/create that consumes two NomosRefs is a better canonical example
|
|
2517
|
+
* than an isolated leaf create. Stable source order breaks ties. */
|
|
2518
|
+
function orderedStandaloneCreates(mod: DomainModule): Directive<unknown>[] {
|
|
2519
|
+
return (mod.directives as Directive<unknown>[])
|
|
2520
|
+
.map((directive, index) => ({ directive, index }))
|
|
2521
|
+
.filter(({ directive }) => isStandaloneProofSafe(mod, directive))
|
|
2522
|
+
.sort((a, b) => {
|
|
2523
|
+
const refsA = Object.keys(aggregateReferenceFields(a.directive.payloadSchema as z.ZodTypeAny)).length;
|
|
2524
|
+
const refsB = Object.keys(aggregateReferenceFields(b.directive.payloadSchema as z.ZodTypeAny)).length;
|
|
2525
|
+
return refsB - refsA || a.index - b.index;
|
|
2526
|
+
})
|
|
2527
|
+
.map(({ directive }) => directive);
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
interface ProofReferenceSeed {
|
|
2531
|
+
readonly directive: Directive<unknown>;
|
|
2532
|
+
readonly aggregate: AggregateHandle;
|
|
2533
|
+
readonly payload: Record<string, unknown>;
|
|
2534
|
+
readonly referenceFields: Readonly<Record<string, string>>;
|
|
2535
|
+
readonly mintField?: string;
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
/** Build the prerequisite-create graph for one generated business action. SQL-style fake foreign keys are
|
|
2539
|
+
* forbidden: if an action needs `NomosRef<A>`, the generated journey first runs an ungated create for A,
|
|
2540
|
+
* retains the returned reference, and only then invokes the action. Cycles and missing creators fail compile
|
|
2541
|
+
* with the business types named instead of leaking a stringly proof. */
|
|
2542
|
+
function discoverProofReferenceSeeds(mod: DomainModule, target: Directive<unknown>): ProofReferenceSeed[] {
|
|
2543
|
+
const domain = mod.domain ?? mod.name;
|
|
2544
|
+
const directives = mod.directives as Directive<unknown>[];
|
|
2545
|
+
const aggregates = mod.aggregates as AggregateHandle[];
|
|
2546
|
+
const emitted = new Set<string>();
|
|
2547
|
+
const visiting = new Set<string>();
|
|
2548
|
+
const out: ProofReferenceSeed[] = [];
|
|
2549
|
+
|
|
2550
|
+
const visitType = (aggregateId: string): void => {
|
|
2551
|
+
if (emitted.has(aggregateId)) return;
|
|
2552
|
+
if (visiting.has(aggregateId)) {
|
|
2553
|
+
throw new Error(`codegen-proof: ${domain}/${target.id} has a cyclic typed-reference prerequisite through '${aggregateId}'`);
|
|
2554
|
+
}
|
|
2555
|
+
visiting.add(aggregateId);
|
|
2556
|
+
const directive = directives.find((candidate) =>
|
|
2557
|
+
candidate !== target && candidate.aggregateId === aggregateId && isStandaloneProofSafe(mod, candidate));
|
|
2558
|
+
const aggregate = aggregates.find((candidate) => candidate.id === aggregateId);
|
|
2559
|
+
if (directive === undefined || aggregate === undefined) {
|
|
2560
|
+
throw new Error(
|
|
2561
|
+
`codegen-proof: ${domain}/${target.id} needs NomosRef<${aggregateId}> but the law has no ungated ` +
|
|
2562
|
+
`.creates(${aggregateId}) action from which the generated journey can obtain one`,
|
|
2563
|
+
);
|
|
2564
|
+
}
|
|
2565
|
+
const mintField = mintedCreateField(directive, aggregate);
|
|
2566
|
+
const referenceFields: Record<string, string> = { ...aggregateReferenceFields(directive.payloadSchema as z.ZodTypeAny) };
|
|
2567
|
+
if (mintField !== undefined) delete referenceFields[mintField];
|
|
2568
|
+
for (const prerequisite of Object.values(referenceFields)) visitType(prerequisite);
|
|
2569
|
+
const synth = synthesizeDirectivePayload(directive, `${domain}/${directive.id}`);
|
|
2570
|
+
const payload: Record<string, unknown> = { ...synth.value };
|
|
2571
|
+
if (mintField !== undefined) delete payload[mintField];
|
|
2572
|
+
out.push({ directive, aggregate, payload, referenceFields, ...(mintField !== undefined ? { mintField } : {}) });
|
|
2573
|
+
visiting.delete(aggregateId);
|
|
2574
|
+
emitted.add(aggregateId);
|
|
2575
|
+
};
|
|
2576
|
+
|
|
2577
|
+
const targetReferences = { ...aggregateReferenceFields(target.payloadSchema as z.ZodTypeAny) };
|
|
2578
|
+
const targetAggregate = aggregates.find((aggregate) => aggregate.id === target.aggregateId);
|
|
2579
|
+
const targetMintField = targetAggregate === undefined ? undefined : mintedCreateField(target, targetAggregate);
|
|
2580
|
+
if (targetMintField !== undefined) delete targetReferences[targetMintField];
|
|
2581
|
+
for (const aggregateId of Object.values(targetReferences)) {
|
|
2582
|
+
visitType(aggregateId);
|
|
2583
|
+
}
|
|
2584
|
+
return out;
|
|
2273
2585
|
}
|
|
2274
2586
|
|
|
2275
2587
|
/** Does `v` carry an RFC 7946 geometry object at `segs` (walked into the composite)? */
|
|
@@ -2293,9 +2605,11 @@ function createLegFor(
|
|
|
2293
2605
|
mod: DomainModule,
|
|
2294
2606
|
createDir: Directive<unknown>,
|
|
2295
2607
|
domain: string,
|
|
2608
|
+
domainInterface?: DomainInterface,
|
|
2296
2609
|
): {
|
|
2297
2610
|
directiveId: string; aggregateId: string; payload: Record<string, unknown>;
|
|
2298
2611
|
autoStamped: readonly string[]; mintField?: string;
|
|
2612
|
+
ownedEncryption?: ProofOwnedEncryption;
|
|
2299
2613
|
query?: { id: string; params: Record<string, unknown> };
|
|
2300
2614
|
count?: { id: string; group: string };
|
|
2301
2615
|
spatial?: { id: string };
|
|
@@ -2303,7 +2617,7 @@ function createLegFor(
|
|
|
2303
2617
|
const agg = (mod.aggregates as AggregateHandle[]).find((a) => a.id === createDir.aggregateId);
|
|
2304
2618
|
if (agg === undefined) return null;
|
|
2305
2619
|
const where = `${domain}/${createDir.id}`;
|
|
2306
|
-
let synth =
|
|
2620
|
+
let synth = synthesizeDirectivePayload(createDir, where);
|
|
2307
2621
|
const mintField = mintedCreateField(createDir, agg);
|
|
2308
2622
|
|
|
2309
2623
|
// SPATIAL MEMBERSHIP: probe the plan — does the create set the indexed head field with a geometry
|
|
@@ -2327,7 +2641,7 @@ function createLegFor(
|
|
|
2327
2641
|
let ops = probeWith(synth);
|
|
2328
2642
|
let matched = spatials.find((sp) => hits(ops, sp));
|
|
2329
2643
|
if (matched === undefined) {
|
|
2330
|
-
const richer =
|
|
2644
|
+
const richer = synthesizeDirectivePayload(createDir, where, { voIncludeOptional: true });
|
|
2331
2645
|
ops = probeWith(richer);
|
|
2332
2646
|
matched = spatials.find((sp) => hits(ops, sp));
|
|
2333
2647
|
if (matched !== undefined) synth = richer; // the geometry-bearing sample becomes the leg's payload
|
|
@@ -2343,23 +2657,36 @@ function createLegFor(
|
|
|
2343
2657
|
const count = (mod.counts ?? [])
|
|
2344
2658
|
.map((raw) => finishCount(raw))
|
|
2345
2659
|
.find((c) => c.of === agg.id && (c as { where?: unknown }).where == null && (c.by == null || baseValue[c.by] !== undefined));
|
|
2660
|
+
const encryption = domainInterface === undefined ? null : domainEncryptionMap(mod, domainInterface);
|
|
2661
|
+
const ownedFields = encryption?.directives[createDir.id];
|
|
2662
|
+
const ownedEncryption = agg.audience?.kind === "owned" && ownedFields !== undefined
|
|
2663
|
+
? {
|
|
2664
|
+
scope: Object.values(ownedFields)[0]!.scope,
|
|
2665
|
+
recordType: isNamespacedAggregate(agg) ? qualifyReadProjectionType(domain, agg.id) : agg.id,
|
|
2666
|
+
fields: Object.fromEntries(Object.entries(ownedFields).map(([field, slot]) => [
|
|
2667
|
+
field,
|
|
2668
|
+
{ aggSid: slot.aggSid, fieldSid: slot.fieldSid },
|
|
2669
|
+
])),
|
|
2670
|
+
}
|
|
2671
|
+
: undefined;
|
|
2346
2672
|
return {
|
|
2347
2673
|
directiveId: createDir.id,
|
|
2348
2674
|
aggregateId: agg.id,
|
|
2349
2675
|
payload: baseValue,
|
|
2350
2676
|
autoStamped: synth.autoStamped,
|
|
2351
2677
|
...(mintField !== undefined ? { mintField } : {}),
|
|
2678
|
+
...(ownedEncryption !== undefined ? { ownedEncryption } : {}),
|
|
2352
2679
|
...(query ? { query: { id: query.id, params: Object.fromEntries(query.key.map((k) => [k, baseValue[k]])) } } : {}),
|
|
2353
2680
|
...(count ? { count: { id: count.id, group: count.by != null ? String(baseValue[count.by]) : "" } } : {}),
|
|
2354
2681
|
...(spatial !== undefined ? { spatial } : {}),
|
|
2355
2682
|
};
|
|
2356
2683
|
}
|
|
2357
2684
|
|
|
2358
|
-
export function proofLegsForModule(mod: DomainModule): ProofLegs | null {
|
|
2685
|
+
export function proofLegsForModule(mod: DomainModule, domainInterface?: DomainInterface): ProofLegs | null {
|
|
2359
2686
|
const domain = mod.domain ?? mod.name;
|
|
2360
|
-
const creates = (mod
|
|
2687
|
+
const creates = orderedStandaloneCreates(mod);
|
|
2361
2688
|
const legsBuilt = creates
|
|
2362
|
-
.map((d) => { try { return createLegFor(mod, d, domain); } catch { return null; } })
|
|
2689
|
+
.map((d) => { try { return createLegFor(mod, d, domain, domainInterface); } catch { return null; } })
|
|
2363
2690
|
.filter((l): l is NonNullable<typeof l> => l !== null);
|
|
2364
2691
|
if (legsBuilt.length === 0) return null;
|
|
2365
2692
|
const [primary, ...extras] = legsBuilt;
|
|
@@ -2422,6 +2749,133 @@ function attachBusinessEncryption(
|
|
|
2422
2749
|
};
|
|
2423
2750
|
}
|
|
2424
2751
|
|
|
2752
|
+
/** Resolve `.trustsSource(...)` against the package's actual source query and published business language.
|
|
2753
|
+
* The proof recipe contains no raw row seeding: it replays the smallest prefix ending at an action that
|
|
2754
|
+
* establishes the query's returned aggregate. If that business path cannot be found, compilation names the
|
|
2755
|
+
* missing feature instead of generating a target action that can only fail with an opaque attestation error. */
|
|
2756
|
+
function attachSourceTrustProofSetup(
|
|
2757
|
+
leg: RelationBootstrapLeg,
|
|
2758
|
+
target: Directive<unknown>,
|
|
2759
|
+
modules: readonly DomainModule[],
|
|
2760
|
+
): RelationBootstrapLeg {
|
|
2761
|
+
const trust = target.businessSourceTrust;
|
|
2762
|
+
if (trust === undefined) return leg;
|
|
2763
|
+
const candidates = modules.flatMap((sourceModule) =>
|
|
2764
|
+
((sourceModule.queries ?? []) as QueryDecl[])
|
|
2765
|
+
.filter((query) => query.id === trust.queryId)
|
|
2766
|
+
.map((query) => ({ sourceModule, query })),
|
|
2767
|
+
);
|
|
2768
|
+
if (candidates.length !== 1) {
|
|
2769
|
+
throw new Error(
|
|
2770
|
+
`${leg.domain}/${leg.gatedDirectiveId}: dynamic source query '${trust.queryId}' resolved to ${candidates.length} package queries; ` +
|
|
2771
|
+
`the generated application needs exactly one source business domain`,
|
|
2772
|
+
);
|
|
2773
|
+
}
|
|
2774
|
+
const { sourceModule, query } = candidates[0]!;
|
|
2775
|
+
const sourceLegs = discoverRelationBootstrapLegs(sourceModule);
|
|
2776
|
+
const sourceDirectives = new Map(
|
|
2777
|
+
(sourceModule.directives as Directive<unknown>[]).map((directive) => [directive.id, directive] as const),
|
|
2778
|
+
);
|
|
2779
|
+
const producerIndex = sourceLegs.findIndex((sourceLeg) => {
|
|
2780
|
+
const directive = sourceDirectives.get(sourceLeg.gatedDirectiveId);
|
|
2781
|
+
return directive !== undefined &&
|
|
2782
|
+
probeEstablishesAggregateTypes(
|
|
2783
|
+
directive,
|
|
2784
|
+
`${sourceModule.domain ?? sourceModule.name}/${directive.id}`,
|
|
2785
|
+
).has(query.returns);
|
|
2786
|
+
});
|
|
2787
|
+
if (producerIndex < 0) {
|
|
2788
|
+
throw new Error(
|
|
2789
|
+
`${leg.domain}/${leg.gatedDirectiveId}: source query '${trust.queryId}' returns ${query.returns}, but ` +
|
|
2790
|
+
`${sourceModule.domain ?? sourceModule.name} exposes no generated business action that establishes it`,
|
|
2791
|
+
);
|
|
2792
|
+
}
|
|
2793
|
+
const producer = sourceLegs[producerIndex]!;
|
|
2794
|
+
const sourceQueryReturns = new Map(
|
|
2795
|
+
((sourceModule.queries ?? []) as QueryDecl[]).map((declared) => [declared.id, declared.returns] as const),
|
|
2796
|
+
);
|
|
2797
|
+
// Build the dependency closure, not a source-order prefix. A prefix contains perfectly valid but mutually
|
|
2798
|
+
// exclusive actions (archive before publish, revoke before use) and would turn a business language into one
|
|
2799
|
+
// impossible CRUD script. Dependencies are positive record producers referenced by the action or returned
|
|
2800
|
+
// by its declared reads; born-context roots are already established by the first selected action's birth.
|
|
2801
|
+
const selected = new Set<string>([producer.gatedDirectiveId]);
|
|
2802
|
+
const queue = [producer];
|
|
2803
|
+
while (queue.length > 0) {
|
|
2804
|
+
const consumer = queue.shift()!;
|
|
2805
|
+
const directive = sourceDirectives.get(consumer.gatedDirectiveId);
|
|
2806
|
+
if (directive === undefined) continue;
|
|
2807
|
+
const optionalReferences = new Set(consumer.gatedOptionalReferenceFields ?? []);
|
|
2808
|
+
const neededTypes = new Set([
|
|
2809
|
+
...Object.entries(consumer.gatedReferenceFields ?? {})
|
|
2810
|
+
.filter(([field]) => !optionalReferences.has(field))
|
|
2811
|
+
.map(([, aggregate]) => aggregate),
|
|
2812
|
+
...(consumer.gatedDirectiveId === producer.gatedDirectiveId ? (directive.declaredQueryReads ?? []) : []).flatMap((read) => {
|
|
2813
|
+
const returns = sourceQueryReturns.get(read.id);
|
|
2814
|
+
return returns === undefined ? [] : [returns];
|
|
2815
|
+
}),
|
|
2816
|
+
]);
|
|
2817
|
+
for (const aggregateType of neededTypes) {
|
|
2818
|
+
const alreadySelected = [...selected].some((directiveId) => {
|
|
2819
|
+
if (directiveId === consumer.gatedDirectiveId) return false;
|
|
2820
|
+
const selectedLeg = sourceLegs.find((candidate) => candidate.gatedDirectiveId === directiveId);
|
|
2821
|
+
const selectedDirective = sourceDirectives.get(directiveId);
|
|
2822
|
+
return selectedLeg !== undefined && selectedDirective !== undefined &&
|
|
2823
|
+
(selectedLeg.gatedAggregateId === aggregateType ||
|
|
2824
|
+
probeEstablishesAggregateTypes(
|
|
2825
|
+
selectedDirective,
|
|
2826
|
+
`${sourceModule.domain ?? sourceModule.name}/${selectedDirective.id}`,
|
|
2827
|
+
).has(aggregateType));
|
|
2828
|
+
});
|
|
2829
|
+
if (alreadySelected) continue;
|
|
2830
|
+
const candidate = sourceLegs
|
|
2831
|
+
.filter((sourceLeg) => sourceLeg.gatedDirectiveId !== consumer.gatedDirectiveId)
|
|
2832
|
+
.map((sourceLeg, index) => ({
|
|
2833
|
+
sourceLeg,
|
|
2834
|
+
index,
|
|
2835
|
+
directive: sourceDirectives.get(sourceLeg.gatedDirectiveId),
|
|
2836
|
+
}))
|
|
2837
|
+
.filter(({ sourceLeg, directive: sourceDirective }) =>
|
|
2838
|
+
sourceDirective !== undefined &&
|
|
2839
|
+
(sourceDirective.marker === "creates" || sourceDirective.marker === "ensures") &&
|
|
2840
|
+
(sourceLeg.gatedAggregateId === aggregateType ||
|
|
2841
|
+
probeEstablishesAggregateTypes(
|
|
2842
|
+
sourceDirective,
|
|
2843
|
+
`${sourceModule.domain ?? sourceModule.name}/${sourceDirective.id}`,
|
|
2844
|
+
).has(aggregateType)))
|
|
2845
|
+
.sort((left, right) => {
|
|
2846
|
+
const rank = (candidateDirective: Directive<unknown>): number =>
|
|
2847
|
+
candidateDirective.marker === "creates" ? 0 : 1;
|
|
2848
|
+
const primaryRank = (candidate: { sourceLeg: RelationBootstrapLeg }): number =>
|
|
2849
|
+
candidate.sourceLeg.gatedAggregateId === aggregateType ? 0 : 1;
|
|
2850
|
+
return primaryRank(left) - primaryRank(right) ||
|
|
2851
|
+
rank(left.directive!) - rank(right.directive!) ||
|
|
2852
|
+
left.index - right.index;
|
|
2853
|
+
})[0]?.sourceLeg;
|
|
2854
|
+
if (candidate !== undefined && !selected.has(candidate.gatedDirectiveId)) {
|
|
2855
|
+
selected.add(candidate.gatedDirectiveId);
|
|
2856
|
+
queue.push(candidate);
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
const sourceJourney = sourceLegs
|
|
2861
|
+
.filter((sourceLeg) => selected.has(sourceLeg.gatedDirectiveId))
|
|
2862
|
+
.sort((left, right) => {
|
|
2863
|
+
if (left.gatedDirectiveId === producer.gatedDirectiveId) return 1;
|
|
2864
|
+
if (right.gatedDirectiveId === producer.gatedDirectiveId) return -1;
|
|
2865
|
+
return sourceLegs.indexOf(left) - sourceLegs.indexOf(right);
|
|
2866
|
+
});
|
|
2867
|
+
return {
|
|
2868
|
+
...leg,
|
|
2869
|
+
sourceTrust: {
|
|
2870
|
+
...trust,
|
|
2871
|
+
sourceDomain: sourceModule.domain ?? sourceModule.name,
|
|
2872
|
+
sourceAggregateId: query.returns,
|
|
2873
|
+
sourceQueryKey: [...query.key],
|
|
2874
|
+
sourceJourney,
|
|
2875
|
+
},
|
|
2876
|
+
};
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2425
2879
|
/** Build the legs for ONE domain module — every leg it carries (compile-valid always; standalone-provable
|
|
2426
2880
|
* when it has a `.creates`; child-birth-provable when it has a `.births()`). NEVER null: every compiled
|
|
2427
2881
|
* domain is at least `compile-valid` (gaps 1+3 — every domain is proof-addressable). */
|
|
@@ -2430,6 +2884,7 @@ export function legsForDomain(
|
|
|
2430
2884
|
modules: readonly DomainModule[],
|
|
2431
2885
|
packageInterface?: Readonly<Record<string, DomainInterface>>,
|
|
2432
2886
|
): ProofLegs {
|
|
2887
|
+
const executableLaw = mod;
|
|
2433
2888
|
// Business-birth control declarations are proofed as part of the birth's child genesis, not as
|
|
2434
2889
|
// tenant-authored standalone/gated actions. Keep the full `modules` below for recipe resolution while
|
|
2435
2890
|
// removing the presentation-only internals from leg selection and diagnostics.
|
|
@@ -2446,7 +2901,7 @@ export function legsForDomain(
|
|
|
2446
2901
|
// child-birth-provable), never loses the index.
|
|
2447
2902
|
let standalone: ProofLegs | null = null;
|
|
2448
2903
|
try {
|
|
2449
|
-
standalone = proofLegsForModule(mod);
|
|
2904
|
+
standalone = proofLegsForModule(mod, packageInterface?.[domain]);
|
|
2450
2905
|
} catch {
|
|
2451
2906
|
standalone = null;
|
|
2452
2907
|
}
|
|
@@ -2487,7 +2942,29 @@ export function legsForDomain(
|
|
|
2487
2942
|
// MULTI-TARGET (co2 #2): discover one authority leg per gated create/write, not just the first.
|
|
2488
2943
|
let relationBootstraps: RelationBootstrapLeg[] = [];
|
|
2489
2944
|
try {
|
|
2490
|
-
|
|
2945
|
+
const publicDirectiveIds = new Set(
|
|
2946
|
+
(mod.directives as Directive<unknown>[]).map((directive) => directive.id),
|
|
2947
|
+
);
|
|
2948
|
+
const executableById = new Map(
|
|
2949
|
+
(executableLaw.directives as Directive<unknown>[]).map((directive) => [directive.id, directive] as const),
|
|
2950
|
+
);
|
|
2951
|
+
const unrestrictedBornContext: BornContextAuthority | undefined = childBirthWithSetup === undefined
|
|
2952
|
+
? undefined
|
|
2953
|
+
: {
|
|
2954
|
+
birthDirectiveId: childBirthWithSetup.birthDirectiveId,
|
|
2955
|
+
birthPayload: childBirthWithSetup.birthPayload,
|
|
2956
|
+
birthAutoStamped: childBirthWithSetup.birthAutoStamped,
|
|
2957
|
+
...(childBirthWithSetup.birthActorField === undefined ? {} : { ownerField: childBirthWithSetup.birthActorField }),
|
|
2958
|
+
...(childBirthWithSetup.frameworkHashField === undefined ? {} : { frameworkHashField: childBirthWithSetup.frameworkHashField }),
|
|
2959
|
+
...(childBirthWithSetup.lawHashField === undefined ? {} : { lawHashField: childBirthWithSetup.lawHashField }),
|
|
2960
|
+
seatsViaDirectiveId: childBirthWithSetup.childSeedDirectiveId ?? "business birth",
|
|
2961
|
+
};
|
|
2962
|
+
relationBootstraps = discoverRelationBootstrapLegs(executableLaw, unrestrictedBornContext)
|
|
2963
|
+
.filter((leg) => publicDirectiveIds.has(leg.gatedDirectiveId))
|
|
2964
|
+
.map((leg) => {
|
|
2965
|
+
const target = executableById.get(leg.gatedDirectiveId);
|
|
2966
|
+
return target === undefined ? leg : attachSourceTrustProofSetup(leg, target, modules);
|
|
2967
|
+
});
|
|
2491
2968
|
} catch {
|
|
2492
2969
|
relationBootstraps = [];
|
|
2493
2970
|
}
|
|
@@ -2508,19 +2985,42 @@ export function legsForDomain(
|
|
|
2508
2985
|
for (const issue of r.leg?.recipeIssues ?? []) birthDiagnostics.push(`${domain}/${d.id}: ${issue}`);
|
|
2509
2986
|
}
|
|
2510
2987
|
|
|
2511
|
-
// PROOF TAXONOMY AUDIT
|
|
2512
|
-
//
|
|
2513
|
-
//
|
|
2988
|
+
// PROOF TAXONOMY AUDIT. Every PUBLIC business action is executable proof, not merely emitted TypeScript.
|
|
2989
|
+
// This deliberately includes `.ensures` and mutations: silently dropping an unsampleable ensure leaves a
|
|
2990
|
+
// later typed-ref consumer with no producer and turns the framework's omission into an opaque tenant error.
|
|
2991
|
+
// Internal effects remain implementation detail; their published intent is the unit we promise and prove.
|
|
2514
2992
|
const proofDiagnostics: string[] = [];
|
|
2515
|
-
const
|
|
2993
|
+
const provenDirectiveIds = new Set<string>();
|
|
2516
2994
|
if (standalone !== null) {
|
|
2517
|
-
if (standalone.directiveId !== undefined)
|
|
2518
|
-
for (const ec of standalone.extraCreates ?? [])
|
|
2995
|
+
if (standalone.directiveId !== undefined) provenDirectiveIds.add(standalone.directiveId);
|
|
2996
|
+
for (const ec of standalone.extraCreates ?? []) provenDirectiveIds.add(ec.directiveId);
|
|
2997
|
+
}
|
|
2998
|
+
for (const rb of relationBootstraps) provenDirectiveIds.add(rb.gatedDirectiveId);
|
|
2999
|
+
for (const birth of childBirths) provenDirectiveIds.add(birth.birthDirectiveId);
|
|
3000
|
+
|
|
3001
|
+
const publishedIntentIds = new Set((mod.applicationIntents ?? []).map((intent) => intent.id));
|
|
3002
|
+
const diagnosedDirectiveIds = new Set<string>();
|
|
3003
|
+
for (const d of mod.directives as Directive<unknown>[]) {
|
|
3004
|
+
if (!publishedIntentIds.has(d.id) || provenDirectiveIds.has(d.id)) continue;
|
|
3005
|
+
const who = `${domain}/${d.id}`;
|
|
3006
|
+
try {
|
|
3007
|
+
synthesizeDirectivePayload(d, who);
|
|
3008
|
+
proofDiagnostics.push(
|
|
3009
|
+
`${who}: public business action has no generated executable proof — fix its authority, prerequisite, or journey dependency; Nomos will not publish an unexercised tenant method`,
|
|
3010
|
+
);
|
|
3011
|
+
} catch (e) {
|
|
3012
|
+
proofDiagnostics.push(
|
|
3013
|
+
`${who}: public business action could not be generated as an executable example — ${(e as Error)?.message ?? e}`,
|
|
3014
|
+
);
|
|
3015
|
+
}
|
|
3016
|
+
diagnosedDirectiveIds.add(d.id);
|
|
2519
3017
|
}
|
|
2520
|
-
|
|
3018
|
+
|
|
3019
|
+
// Retain the lower-level law audit for raw `.creates` effects that are not part of an application language.
|
|
3020
|
+
// Those are framework/law diagnostics; public actions above are a hard executable-surface promise.
|
|
2521
3021
|
for (const d of mod.directives as Directive<unknown>[]) {
|
|
2522
3022
|
if (d?.marker !== "creates" || typeof d.payloadSchema !== "object" || d.birthsChild === true) continue;
|
|
2523
|
-
if (
|
|
3023
|
+
if (provenDirectiveIds.has(d.id) || diagnosedDirectiveIds.has(d.id)) continue;
|
|
2524
3024
|
const who = `${domain}/${d.id}`;
|
|
2525
3025
|
if (d.attestationAuthorizedGate !== undefined) {
|
|
2526
3026
|
proofDiagnostics.push(
|
|
@@ -2532,15 +3032,22 @@ export function legsForDomain(
|
|
|
2532
3032
|
// A gated create with no discoverable authority path (else it would be in provenCreateIds via the
|
|
2533
3033
|
// relation-bootstrap leg) — name the exact seeding gap (the negative conformance leg still proves the gate).
|
|
2534
3034
|
const gap = diagnoseRelationBootstrapGap(mod);
|
|
2535
|
-
proofDiagnostics.push(gap ?? `${who}: creates ${d.aggregateId} gated by .requires("${d.requiresRelationGate.relation}") — no seedable authority path found;
|
|
3035
|
+
proofDiagnostics.push(gap ?? `${who}: creates ${d.aggregateId} gated by .requires("${d.requiresRelationGate.relation}") — no seedable authority path found; expose the business granter/seed so the generated journey can prove it`);
|
|
3036
|
+
continue;
|
|
3037
|
+
}
|
|
3038
|
+
const governingInvariants = (mod.workspaceInvariants ?? []).filter((invariant) => invariant.on === d.id);
|
|
3039
|
+
if (governingInvariants.length > 0) {
|
|
3040
|
+
proofDiagnostics.push(
|
|
3041
|
+
`${who}: creates ${d.aggregateId} under declared policy ${governingInvariants.map((invariant) => `'${invariant.id}'`).join(", ")} — it is state-dependent, so the generator will not pretend it is a standalone CRUD write; add or generate the prerequisite business journey`,
|
|
3042
|
+
);
|
|
2536
3043
|
continue;
|
|
2537
3044
|
}
|
|
2538
3045
|
// An UNGATED standalone create that still isn't proven ⇒ its payload could not be synthesized. Re-run the
|
|
2539
3046
|
// synthesis to capture the exact-path reason (createLegFor swallowed it to keep the index resilient).
|
|
2540
3047
|
try {
|
|
2541
|
-
|
|
3048
|
+
synthesizeDirectivePayload(d, who);
|
|
2542
3049
|
// Synthesis succeeded but the leg was still dropped (e.g. the aggregate isn't composed) — name that.
|
|
2543
|
-
proofDiagnostics.push(`${who}: creates ${d.aggregateId} but no
|
|
3050
|
+
proofDiagnostics.push(`${who}: creates ${d.aggregateId} but no generated leg was built (its aggregate '${d.aggregateId}' is not composed in this package, or its create-leg probe failed) — fix the law/generator mismatch; there is no handwritten client-proof lane`);
|
|
2544
3051
|
} catch (e) {
|
|
2545
3052
|
proofDiagnostics.push(`${who}: creates ${d.aggregateId} but its payload could not be synthesized for the offline proof — ${(e as Error)?.message ?? e}`);
|
|
2546
3053
|
}
|
|
@@ -2558,6 +3065,7 @@ export function legsForDomain(
|
|
|
2558
3065
|
payload: standalone.payload,
|
|
2559
3066
|
autoStamped: standalone.autoStamped,
|
|
2560
3067
|
...(standalone.mintField !== undefined ? { mintField: standalone.mintField } : {}),
|
|
3068
|
+
...(standalone.ownedEncryption !== undefined ? { ownedEncryption: standalone.ownedEncryption } : {}),
|
|
2561
3069
|
...(standalone.query !== undefined ? { query: standalone.query } : {}),
|
|
2562
3070
|
...(standalone.count !== undefined ? { count: standalone.count } : {}),
|
|
2563
3071
|
...(standalone.spatial !== undefined ? { spatial: standalone.spatial } : {}),
|
|
@@ -2615,378 +3123,210 @@ export function proofLegsIndex(
|
|
|
2615
3123
|
return { default: def, domains };
|
|
2616
3124
|
}
|
|
2617
3125
|
|
|
2618
|
-
// ── shared `.mts` fragments (the v2 contract — G1: no host secret; deploy is OPEN + kernel-gated) ─────────
|
|
2619
|
-
|
|
2620
|
-
/**
|
|
2621
|
-
* THE LIVE PREAMBLE: `githolon proof --live` has already established the signed session home and deployed
|
|
2622
|
-
* this package into it. Generated proof code receives only that ready session and drives the application verb.
|
|
2623
|
-
* The old raw create/deploy lane was a second product model and could only create registry/custody orphans.
|
|
2624
|
-
*/
|
|
2625
|
-
function v2IdentityAndCleanupPreamble(opts: TsProofOptions, hashConst: string, n: () => number): string[] {
|
|
2626
|
-
void opts;
|
|
2627
|
-
void hashConst;
|
|
2628
|
-
return [
|
|
2629
|
-
`// Nomos prepared an isolated signed application workspace and installed the package before entering generated application code.`,
|
|
2630
|
-
`// The application proof supplies business facts only; identity/custody/law selection stay underneath.`,
|
|
2631
|
-
`const fail: (m: string) => never = (m) => { throw new Error(m); };`,
|
|
2632
|
-
`const ok = (m: string) => console.log("✓ " + m);`,
|
|
2633
|
-
`const PRINCIPAL = process.env.NOMOS_PRINCIPAL || "githolon-proof";`,
|
|
2634
|
-
`const AUTHOR_SECRET = process.env.NOMOS_AUTHOR_SECRET || "";`,
|
|
2635
|
-
``,
|
|
2636
|
-
`let d: any;`,
|
|
2637
|
-
`let runError: unknown = null;`,
|
|
2638
|
-
`try {`,
|
|
2639
|
-
``,
|
|
2640
|
-
`// ${n()}. the one managed entry: an isolated application beneath the logged-in user's durable Nomos home`,
|
|
2641
|
-
`if (process.env.NOMOS_PROOF_MANAGED !== "1" || !AUTHOR_SECRET) fail("run this cloud proof through: githolon proof --live");`,
|
|
2642
|
-
`ok(\`Nomos application ready — \${WS}\`);`,
|
|
2643
|
-
];
|
|
2644
|
-
}
|
|
2645
|
-
|
|
2646
|
-
/**
|
|
2647
|
-
* The generated cloud proof only owns its application call. Its durable signed home is framework state,
|
|
2648
|
-
* so there is no parallel throwaway-provisioning or cleanup protocol to expose here.
|
|
2649
|
-
*/
|
|
2650
|
-
function v2CatchFinally(): string[] {
|
|
2651
|
-
return [
|
|
2652
|
-
`} catch (e) {`,
|
|
2653
|
-
` runError = e;`,
|
|
2654
|
-
` console.error("✗ " + ((e as Error)?.message ?? e));`,
|
|
2655
|
-
`} finally {`,
|
|
2656
|
-
` process.exit(runError ? 1 : 0);`,
|
|
2657
|
-
`}`,
|
|
2658
|
-
];
|
|
2659
|
-
}
|
|
2660
|
-
|
|
2661
|
-
/**
|
|
2662
|
-
* THE LIVE CHILD-BIRTH LEG (G3, gap 2): drive the parent `.births()` directive against the cloud workspace,
|
|
2663
|
-
* then read the BORN CHILD workspace it spawned (the kernel's offer-effect installs the child law in-chain +
|
|
2664
|
-
* folds the child genesis through the child's OWN gate). Verifies the child came up BORN (its read model has
|
|
2665
|
-
* the seeded record) — the cloud's offer response surfaces `born: [{workspace, head}]`. The frameworkHash +
|
|
2666
|
-
* lawHash are injected from the deployed law (the child install resolves bytes by hash from the parent's
|
|
2667
|
-
* custody; a `sample-*` hash would fail). The dispatch rides the gateway offer lane (authenticated principal).
|
|
2668
|
-
*/
|
|
2669
|
-
/**
|
|
2670
|
-
* THE LIVE AUTHORITY-CHAIN SEATING for a GATED `.births()` leg (the co2 case). A birth gated by
|
|
2671
|
-
* `.requires(R)` needs its actor to HOLD R before the birth admits — and R may itself be reachable only
|
|
2672
|
-
* through a RECURSIVE chain of grants (co2: seedPlatformFounder → seedPlatformAdmin [grants co2Admin] →
|
|
2673
|
-
* grantCo2EstateCreator [grants co2EstateCreator] → birthEstateWorkspace). This walks the discovered
|
|
2674
|
-
* `relationSetup` chain and emits each step as a cloud dispatch under the SAME `birthActor`, seating every
|
|
2675
|
-
* relation ON that actor (subject/founder/grantedBy fields overwritten to it) — so one actor plays founder,
|
|
2676
|
-
* admin, and creator, ending holding R. Recurses granter-authority-first (deepest prerequisite runs first).
|
|
2677
|
-
* `actorVar`/`wsVar` are the runtime var names the birth leg already binds.
|
|
2678
|
-
*/
|
|
2679
|
-
function liveAuthorityChainLines(setup: RelationBootstrapLeg, actorVar: string, wsVar: string): string[] {
|
|
2680
|
-
const lines: string[] = [];
|
|
2681
|
-
// 1. the granter's OWN authority first (recursive — the deepest prerequisite is seated before its dependents).
|
|
2682
|
-
if (setup.granterAuthority !== undefined) {
|
|
2683
|
-
lines.push(...liveAuthorityChainLines(setup.granterAuthority, actorVar, wsVar));
|
|
2684
|
-
}
|
|
2685
|
-
// A runtime dispatch: build the payload literal, overwrite subject/founder/grantedBy/routed fields to the
|
|
2686
|
-
// actor (seat the relation ON it), stamp the auto-stamped fields, POST to the parent ws as the actor.
|
|
2687
|
-
const dispatch = (
|
|
2688
|
-
directiveId: string,
|
|
2689
|
-
payload: Record<string, unknown>,
|
|
2690
|
-
routed: readonly string[],
|
|
2691
|
-
autoStamped: readonly string[],
|
|
2692
|
-
label: string,
|
|
2693
|
-
): string[] => {
|
|
2694
|
-
const overwrite = new Set<string>(routed);
|
|
2695
|
-
const entries = Object.entries(payload).map(([k, v]) =>
|
|
2696
|
-
overwrite.has(k) || /^(subject|principal|grantedBy|grantee|member|user|owner|founder)$/i.test(k)
|
|
2697
|
-
? `${JSON.stringify(k)}: ${actorVar}`
|
|
2698
|
-
: `${JSON.stringify(k)}: ${JSON.stringify(v)}`,
|
|
2699
|
-
);
|
|
2700
|
-
return [
|
|
2701
|
-
` {`,
|
|
2702
|
-
` const cp: Record<string, unknown> = { ${entries.join(", ")} };`,
|
|
2703
|
-
...autoStamped.map((f) => ` cp[${JSON.stringify(f)}] = new Date().toISOString();`),
|
|
2704
|
-
` const cr = await fetch(\`\${CLOUD}/v2/workspaces/\${${wsVar}}/${directiveId}\`, { method: "POST", headers: { "content-type": "application/json", "x-nomos-principal": ${actorVar} }, body: JSON.stringify(cp) });`,
|
|
2705
|
-
` const cd = await cr.json();`,
|
|
2706
|
-
` if (!cd.ok) fail(\`authority chain (${label}) ${directiveId} refused: \${JSON.stringify(cd)}\`);`,
|
|
2707
|
-
` ok(\`authority chain — ${directiveId} seated (as \${${actorVar}})\`);`,
|
|
2708
|
-
` }`,
|
|
2709
|
-
];
|
|
2710
|
-
};
|
|
2711
|
-
// 2. this leg's prerequisite seeds (as the actor = founder).
|
|
2712
|
-
for (const seed of setup.prerequisiteSeeds ?? []) {
|
|
2713
|
-
lines.push(...dispatch(seed.directiveId, seed.payload, [], seed.autoStamped, "prerequisite"));
|
|
2714
|
-
}
|
|
2715
|
-
// 3. the grant that seats this leg's relation ON the actor (subject = actor).
|
|
2716
|
-
lines.push(
|
|
2717
|
-
...dispatch(
|
|
2718
|
-
setup.grantDirectiveId,
|
|
2719
|
-
setup.grantPayload,
|
|
2720
|
-
[...setup.grantSubjectFields, ...setup.grantGrantedByFields],
|
|
2721
|
-
setup.grantAutoStamped,
|
|
2722
|
-
"grant",
|
|
2723
|
-
),
|
|
2724
|
-
);
|
|
2725
|
-
return lines;
|
|
2726
|
-
}
|
|
2727
|
-
|
|
2728
|
-
function liveBusinessBirthLegLines(cb: ChildBirthLeg, factory: string, n: () => number): string[] {
|
|
2729
|
-
const businessPayload = Object.fromEntries(
|
|
2730
|
-
Object.entries(cb.birthPayload).filter(([field]) => !["principal", "bornAt", "ownerKeyHash", "birthCert"].includes(field)),
|
|
2731
|
-
);
|
|
2732
|
-
return [
|
|
2733
|
-
`// ${n()}. THE APPLICATION SURFACE: one generated business birth`,
|
|
2734
|
-
`{`,
|
|
2735
|
-
` const holon = await connect({ cloud: CLOUD, workspace: WS, clientId: "business-birth-proof", principal: PRINCIPAL, authorSecret: AUTHOR_SECRET });`,
|
|
2736
|
-
` const app = ${factory}(holon, { actor: PRINCIPAL });`,
|
|
2737
|
-
` const facts: Record<string, unknown> = ${JSON.stringify(businessPayload)};`,
|
|
2738
|
-
` const run = Date.now().toString(36) + Math.random().toString(36).slice(2, 8);`,
|
|
2739
|
-
` for (const [field, value] of Object.entries(facts)) {`,
|
|
2740
|
-
` if (typeof value === "string" && value.startsWith("sample-") && value.length > 7) {`,
|
|
2741
|
-
` facts[field] = ("sample-" + run).slice(0, value.length);`,
|
|
2742
|
-
` }`,
|
|
2743
|
-
` }`,
|
|
2744
|
-
` const born = await app.birth(facts as never);`,
|
|
2745
|
-
` const childWs = born.workspace;`,
|
|
2746
|
-
` if (!childWs) fail("app.birth committed without returning the born workspace");`,
|
|
2747
|
-
` ok(\`app.birth(${cb.businessAggregateId}) — business facts became \${childWs}\`);`,
|
|
2748
|
-
` ok("identity, provenance, persistence and recovery verified before app.birth resolved");`,
|
|
2749
|
-
...(cb.childSeedInstanceId !== undefined
|
|
2750
|
-
? [
|
|
2751
|
-
` const restored = await fetch(\`\${CLOUD}/v2/workspaces/\${childWs}/aggregates/\${encodeURIComponent(${JSON.stringify(cb.childSeedInstanceId)})}\`).then((r) => r.json());`,
|
|
2752
|
-
` if (!(restored.rows?.length >= 1)) fail(\`recovered ${cb.businessAggregateId} has no business root: \${JSON.stringify(restored)}\`);`,
|
|
2753
|
-
` ok("business root read back from durable custody");`,
|
|
2754
|
-
]
|
|
2755
|
-
: []),
|
|
2756
|
-
`}`,
|
|
2757
|
-
``,
|
|
2758
|
-
];
|
|
2759
|
-
}
|
|
2760
|
-
|
|
2761
|
-
function liveChildBirthLegLines(cb: ChildBirthLeg, opts: TsProofOptions, n: () => number): string[] {
|
|
2762
|
-
const hashConst = tsHashConstName(opts.packageName);
|
|
2763
|
-
// The birth payload literal: the synthesized values + the REAL hashes onto the discovered fields + the
|
|
2764
|
-
// auto-stamped `…At` fields stamped now.
|
|
2765
|
-
const lines: string[] = [];
|
|
2766
|
-
const setup = cb.parentSetup;
|
|
2767
|
-
const actorExpr = (field: string | undefined, payloadVar: string): string | undefined =>
|
|
2768
|
-
field !== undefined ? `String(${payloadVar}[${JSON.stringify(field)}] ?? "")` : undefined;
|
|
2769
|
-
const birthActorExpr = actorExpr(cb.birthActorField, "birthPayload");
|
|
2770
|
-
lines.push(
|
|
2771
|
-
`// ${n()}. CHILD BIRTH (G3 — birth is the defined consequence of an offer): drive \`${cb.parentDomain}/${cb.birthDirectiveId}\``,
|
|
2772
|
-
`// against the cloud; the kernel's offer-effect installs the child law in-chain + folds the child's`,
|
|
2773
|
-
`// genesis (estate/${cb.childSeedDirectiveId}) through the CHILD's own gate, then BORNS the child workspace.`,
|
|
2774
|
-
`{`,
|
|
2775
|
-
` const frameworkHashFor = async (workspace: string) => {`,
|
|
2776
|
-
` const st = await (await fetch(\`\${CLOUD}/v2/workspaces/\${workspace}\`)).json().catch(() => ({}));`,
|
|
2777
|
-
` const frameworkHash = st.controller?.domainHash ?? st.controllerHash ?? "";`,
|
|
2778
|
-
` if (!frameworkHash) fail(\`could not resolve the framework controller hash from workspace \${workspace} — needed for the child's in-chain installDomain\`);`,
|
|
2779
|
-
` return frameworkHash;`,
|
|
2780
|
-
` };`,
|
|
2781
|
-
` const birthPayload: Record<string, unknown> = { ...${JSON.stringify(cb.birthPayload)} };`,
|
|
2782
|
-
...(
|
|
2783
|
-
birthActorExpr !== undefined && cb.birthRequiresRelation !== undefined
|
|
2784
|
-
? [
|
|
2785
|
-
` const birthActor = ${birthActorExpr};`,
|
|
2786
|
-
` if (!birthActor) fail(${JSON.stringify(`child birth ${cb.parentDomain}/${cb.birthDirectiveId} is gated by .requires("${cb.birthRequiresRelation}") but the proof could not derive an actor from ${cb.birthActorField}`)});`,
|
|
2787
|
-
]
|
|
2788
|
-
: [
|
|
2789
|
-
` const birthActor = PRINCIPAL;`,
|
|
2790
|
-
]
|
|
2791
|
-
),
|
|
2792
|
-
...(setup !== undefined
|
|
2793
|
-
? [
|
|
2794
|
-
` // Parent setup: this child birth is gated by authority seeded when the parent workspace is born.`,
|
|
2795
|
-
` const setupPayload: Record<string, unknown> = { ...${JSON.stringify(setup.birthPayload)} };`,
|
|
2796
|
-
...(setup.birthActorField !== undefined && cb.birthActorField !== undefined
|
|
2797
|
-
? [` setupPayload[${JSON.stringify(setup.birthActorField)}] = birthPayload[${JSON.stringify(cb.birthActorField)}];`]
|
|
2798
|
-
: []),
|
|
2799
|
-
...(setup.frameworkHashField !== undefined ? [` setupPayload[${JSON.stringify(setup.frameworkHashField)}] = await frameworkHashFor(WS);`] : []),
|
|
2800
|
-
...(setup.lawHashField !== undefined ? [` setupPayload[${JSON.stringify(setup.lawHashField)}] = ${hashConst};`] : []),
|
|
2801
|
-
...setup.birthAutoStamped.map((f) => ` setupPayload[${JSON.stringify(f)}] = new Date().toISOString();`),
|
|
2802
|
-
` const setupActor = ${actorExpr(setup.birthActorField, "setupPayload") ?? "birthActor"} || birthActor;`,
|
|
2803
|
-
` const sr = await fetch(\`\${CLOUD}/v2/workspaces/\${WS}/${setup.birthDirectiveId}\`, { method: "POST", headers: { "content-type": "application/json", "x-nomos-principal": setupActor }, body: JSON.stringify(setupPayload) });`,
|
|
2804
|
-
` const sd = await sr.json();`,
|
|
2805
|
-
` if (!sd.ok) fail(\`parent setup ${setup.parentDomain}/${setup.birthDirectiveId} refused: \${JSON.stringify(sd)}\`);`,
|
|
2806
|
-
` const setupBorn: Array<{ workspace?: string }> = sd.born ?? [];`,
|
|
2807
|
-
` const birthParentWs = setupBorn[0]?.workspace;`,
|
|
2808
|
-
` if (!birthParentWs) fail(\`parent setup admitted but spawned no workspace (born=\${JSON.stringify(setupBorn)})\`);`,
|
|
2809
|
-
` ok(\`parent setup — \${birthParentWs} born by ${setup.parentDomain}/${setup.birthDirectiveId} before gated child birth\`);`,
|
|
2810
|
-
]
|
|
2811
|
-
: [
|
|
2812
|
-
` const birthParentWs = WS;`,
|
|
2813
|
-
]),
|
|
2814
|
-
...(cb.frameworkHashField !== undefined
|
|
2815
|
-
? [
|
|
2816
|
-
` // The framework CONTROLLER hash (the bootstrap install-target the child runs as intent 0) — read it`,
|
|
2817
|
-
` // from the workspace's own status (the kernel's controllerHash); the child install resolves the bytes`,
|
|
2818
|
-
` // by hash from the parent's custody, so this MUST be the real framework hash (a guess would fail).`,
|
|
2819
|
-
` birthPayload[${JSON.stringify(cb.frameworkHashField)}] = await frameworkHashFor(birthParentWs);`,
|
|
2820
|
-
]
|
|
2821
|
-
: []),
|
|
2822
|
-
` birthPayload[${JSON.stringify(cb.lawHashField ?? "lawHash")}] = ${hashConst};`,
|
|
2823
|
-
...cb.birthAutoStamped.map((f) => ` birthPayload[${JSON.stringify(f)}] = new Date().toISOString();`),
|
|
2824
|
-
// GATED BIRTH: seat the FULL authority chain on birthActor before the birth (co2: seedPlatformFounder →
|
|
2825
|
-
// seedPlatformAdmin → grantCo2EstateCreator → the birth). Without this the .requires(R) birth is refused.
|
|
2826
|
-
...(cb.relationSetup !== undefined
|
|
2827
|
-
? [
|
|
2828
|
-
` // seat the .requires("${cb.birthRequiresRelation}") authority chain on birthActor before the gated birth:`,
|
|
2829
|
-
...liveAuthorityChainLines(cb.relationSetup, "birthActor", "birthParentWs"),
|
|
2830
|
-
]
|
|
2831
|
-
: []),
|
|
2832
|
-
` const br = await fetch(\`\${CLOUD}/v2/workspaces/\${birthParentWs}/${cb.birthDirectiveId}\`, { method: "POST", headers: { "content-type": "application/json", "x-nomos-principal": birthActor }, body: JSON.stringify(birthPayload) });`,
|
|
2833
|
-
` const bd = await br.json();`,
|
|
2834
|
-
` if (!bd.ok) fail(\`child birth offer ${cb.parentDomain}/${cb.birthDirectiveId} refused: \${JSON.stringify(bd)}\`);`,
|
|
2835
|
-
` const born: Array<{ workspace?: string }> = bd.born ?? [];`,
|
|
2836
|
-
` const childWs = born[0]?.workspace;`,
|
|
2837
|
-
` if (!childWs) fail(\`birth admitted but the offer-effect spawned no child (born=\${JSON.stringify(born)})\`);`,
|
|
2838
|
-
` ok(\`child birth — \${childWs} born by ${cb.parentDomain}/${cb.birthDirectiveId} (the offer-effect installed the child law + folded its genesis)\`);`,
|
|
2839
|
-
` // The BORN child runs its OWN law: read the seeded ${cb.childSeedAggregateId ?? "record"} back from the child workspace.`,
|
|
2840
|
-
...(cb.childSeedInstanceId !== undefined
|
|
2841
|
-
? [
|
|
2842
|
-
` const cr = await fetch(\`\${CLOUD}/v2/workspaces/\${childWs}/aggregates/\${encodeURIComponent(${JSON.stringify(cb.childSeedInstanceId)})}\`).then((x) => x.json()).catch(() => ({}));`,
|
|
2843
|
-
` if (!(cr.rows?.length >= 1)) fail(\`the born child did not seed ${cb.childSeedAggregateId ?? "its record"} (${cb.childSeedInstanceId}): \${JSON.stringify(cr)}\`);`,
|
|
2844
|
-
` ok(\`the born child's read model has the genesis seed — ${cb.childSeedDirectiveId} ran inside \${childWs}\`);`,
|
|
2845
|
-
]
|
|
2846
|
-
: cb.childQuery !== undefined
|
|
2847
|
-
? [
|
|
2848
|
-
` const cr = await fetch(\`\${CLOUD}/v2/workspaces/\${childWs}/${cb.childQuery.id}?${cb.childQuery.key.map((k) => `${encodeURIComponent(k)}=\${encodeURIComponent(String(birthPayload[${JSON.stringify(k)}] ?? ""))}`).join("&")}\`).then((x) => x.json()).catch(() => ({}));`,
|
|
2849
|
-
` if (!(cr.ok && cr.rows?.length >= 1)) fail(\`the born child's declared query ${cb.childQuery.id} returned nothing: \${JSON.stringify(cr)}\`);`,
|
|
2850
|
-
` ok(\`the born child's declared query ${cb.childQuery.id} returns the genesis seed — ${cb.childSeedDirectiveId} ran inside \${childWs}\`);`,
|
|
2851
|
-
]
|
|
2852
|
-
: [
|
|
2853
|
-
` ok(\`the child \${childWs} is born (no declared read on its seed aggregate — declare a query to read it back)\`);`,
|
|
2854
|
-
]),
|
|
2855
|
-
`}`,
|
|
2856
|
-
``,
|
|
2857
|
-
);
|
|
2858
|
-
return lines;
|
|
2859
|
-
}
|
|
2860
|
-
|
|
2861
3126
|
/**
|
|
2862
|
-
*
|
|
2863
|
-
*
|
|
2864
|
-
*
|
|
2865
|
-
* proof` lane; this `.mts` is the live `--live` lane.)
|
|
3127
|
+
* The generated file is application documentation, not a kernel conformance harness. Deep authority,
|
|
3128
|
+
* relation, birth-recipe and concurrency legs remain in the metadata-driven local proof runner; the live
|
|
3129
|
+
* TypeScript story uses only the same generated application object tenant code receives.
|
|
2866
3130
|
*/
|
|
2867
|
-
function
|
|
3131
|
+
function generateSealedApplicationProof(
|
|
2868
3132
|
mod: DomainModule,
|
|
2869
|
-
modules: readonly DomainModule[],
|
|
2870
3133
|
opts: TsProofOptions,
|
|
2871
3134
|
primaryLegs: ProofLegs,
|
|
2872
3135
|
): string {
|
|
2873
|
-
void modules;
|
|
2874
3136
|
const domain = mod.domain ?? mod.name;
|
|
2875
|
-
const
|
|
2876
|
-
const
|
|
2877
|
-
const
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
...
|
|
2917
|
-
|
|
3137
|
+
const applicationName = tsApplicationName(opts.packageName);
|
|
3138
|
+
const opener = `open${applicationName}`;
|
|
3139
|
+
const marker = JSON.stringify(primaryLegs);
|
|
3140
|
+
const standalone = orderedStandaloneCreates(mod)[0];
|
|
3141
|
+
const businessBirth = primaryLegs.childBirth?.businessAggregateId !== undefined
|
|
3142
|
+
? primaryLegs.childBirth
|
|
3143
|
+
: undefined;
|
|
3144
|
+
const businessBirthFollowup = businessBirth === undefined
|
|
3145
|
+
? undefined
|
|
3146
|
+
: (mod.applicationIntents ?? mod.directives).flatMap((candidate) => {
|
|
3147
|
+
const directive = candidate as Directive<unknown>;
|
|
3148
|
+
if (
|
|
3149
|
+
(directive as { businessBirthInternal?: unknown }).businessBirthInternal === true ||
|
|
3150
|
+
(directive as { businessBirth?: unknown }).businessBirth !== undefined ||
|
|
3151
|
+
(directive as { birthsChild?: unknown }).birthsChild === true ||
|
|
3152
|
+
typeof directive.id !== "string" ||
|
|
3153
|
+
typeof directive.aggregateId !== "string" ||
|
|
3154
|
+
Object.keys(aggregateReferenceFields(directive.payloadSchema as z.ZodTypeAny)).length > 0
|
|
3155
|
+
) return [];
|
|
3156
|
+
const target = (mod.aggregates as AggregateHandle[]).find((aggregate) => aggregate.id === directive.aggregateId);
|
|
3157
|
+
if (target === undefined) return [];
|
|
3158
|
+
try {
|
|
3159
|
+
const synth = synthesizeDirectivePayload(directive, `${domain}/${directive.id}`);
|
|
3160
|
+
const payload = { ...synth.value };
|
|
3161
|
+
const mintField = mintedCreateField(directive, target);
|
|
3162
|
+
if (mintField !== undefined) delete payload[mintField];
|
|
3163
|
+
return [{ directive, target, payload }];
|
|
3164
|
+
} catch {
|
|
3165
|
+
return [];
|
|
3166
|
+
}
|
|
3167
|
+
})[0];
|
|
3168
|
+
const gated = standalone === undefined && businessBirth === undefined && primaryLegs.relationBootstrap !== undefined
|
|
3169
|
+
? (mod.directives as Directive<unknown>[]).find((directive) => directive.id === primaryLegs.relationBootstrap!.gatedDirectiveId)
|
|
3170
|
+
: undefined;
|
|
3171
|
+
const action = standalone ?? gated;
|
|
3172
|
+
const aggregate = action === undefined
|
|
3173
|
+
? undefined
|
|
3174
|
+
: (mod.aggregates as AggregateHandle[]).find((candidate) => candidate.id === action.aggregateId);
|
|
3175
|
+
const referenceSeeds = action === undefined ? [] : discoverProofReferenceSeeds(mod, action);
|
|
3176
|
+
const referenceTypes = [...new Set([
|
|
3177
|
+
...(aggregate === undefined ? [] : [aggregate.id]),
|
|
3178
|
+
...referenceSeeds.map((seed) => seed.aggregate.id),
|
|
3179
|
+
...referenceSeeds.flatMap((seed) => Object.values(seed.referenceFields)),
|
|
3180
|
+
...(action === undefined ? [] : Object.values(aggregateReferenceFields(action.payloadSchema as z.ZodTypeAny))),
|
|
3181
|
+
])].filter((type) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(type)).sort();
|
|
3182
|
+
const typeImports = referenceTypes.length > 0
|
|
3183
|
+
? `, type NomosRef, ${referenceTypes.map((type) => `type ${type}`).join(", ")}`
|
|
3184
|
+
: "";
|
|
3185
|
+
const refDeclarations = referenceTypes.flatMap((type) => [
|
|
3186
|
+
`let ${proofRefSlot(type)}: NomosRef<${type}> | undefined;`,
|
|
3187
|
+
`const ${proofRefGetter(type)} = (): NomosRef<${type}> => ${proofRefSlot(type)} ?? fail(${JSON.stringify(`generated proof ordering error: NomosRef<${type}> is not available yet`)});`,
|
|
3188
|
+
]);
|
|
3189
|
+
const out = [
|
|
3190
|
+
`// AUTO-GENERATED by nomos-compile — the canonical application journey for "${opts.packageName}".`,
|
|
3191
|
+
`// Regenerated from the business law; do not edit or maintain a second client example.`,
|
|
3192
|
+
`// The local proof runner executes every deep framework leg without exposing it here.`,
|
|
3193
|
+
`// @nomos-proof-legs ${marker}`,
|
|
3194
|
+
`import { ${opener}, sameRecord${typeImports} } from "./${opts.packageName}.client.mjs";`,
|
|
2918
3195
|
``,
|
|
2919
|
-
|
|
3196
|
+
`const fail: (message: string) => never = (message) => { throw new Error(message); };`,
|
|
3197
|
+
`const ok = (message: string) => console.log("✓ " + message);`,
|
|
3198
|
+
`if (process.env.NOMOS_PROOF_MANAGED !== "1") fail("run the live journey through: githolon proof --live");`,
|
|
3199
|
+
`const cloud = (process.env.NOMOS_CLOUD || "https://nomos.captainapp.co.uk").replace(/\\/+$/, "");`,
|
|
3200
|
+
`const applicationName = process.env.NOMOS_APPLICATION || ${JSON.stringify(opts.packageName)};`,
|
|
3201
|
+
`const signIn = async (request: { readonly deviceChallenge?: string } = {}) => {`,
|
|
3202
|
+
` if (!request.deviceChallenge) return process.env.NOMOS_AUTH_TOKEN || "";`,
|
|
3203
|
+
` const subject = process.env.NOMOS_PROOF_SUBJECT || "";`,
|
|
3204
|
+
` if (!subject) fail("the generated blank-user proof has no authority subject");`,
|
|
3205
|
+
` const response = await fetch(cloud + "/dev/mint-token", {`,
|
|
3206
|
+
` method: "POST", headers: { "content-type": "application/json" },`,
|
|
3207
|
+
` body: JSON.stringify({ sub: subject, nonce: request.deviceChallenge }),`,
|
|
3208
|
+
` });`,
|
|
3209
|
+
` const body = await response.json() as { token?: string; error?: string };`,
|
|
3210
|
+
` if (!response.ok || !body.token) fail("the generated application could not attest its private device: " + (body.error || response.status));`,
|
|
3211
|
+
` return body.token;`,
|
|
3212
|
+
`};`,
|
|
3213
|
+
`const application = await ${opener}({ cloud, application: applicationName, signIn });`,
|
|
3214
|
+
`ok("opened the generated ${applicationName}; identity, custody, recovery and sync stay inside Nomos");`,
|
|
3215
|
+
`const waitUntilSaved = async (): Promise<void> => {`,
|
|
3216
|
+
` const current = await application.currentStatus();`,
|
|
3217
|
+
` if (current.phase === "attention") fail(current.message ?? "the application needs attention");`,
|
|
3218
|
+
` if (current.phase === "current") return;`,
|
|
3219
|
+
` await new Promise<void>((resolve, reject) => {`,
|
|
3220
|
+
` const timer = setTimeout(() => { stop(); reject(new Error("the saved application did not become current")); }, 30_000);`,
|
|
3221
|
+
` const stop = application.watchStatus((status) => {`,
|
|
3222
|
+
` if (status.phase === "attention") { clearTimeout(timer); stop(); reject(new Error(status.message ?? "the application needs attention")); }`,
|
|
3223
|
+
` if (status.phase === "current") { clearTimeout(timer); stop(); resolve(); }`,
|
|
3224
|
+
` });`,
|
|
3225
|
+
` });`,
|
|
3226
|
+
`};`,
|
|
3227
|
+
...refDeclarations,
|
|
2920
3228
|
``,
|
|
2921
|
-
|
|
2922
|
-
return out.join("\n");
|
|
2923
|
-
}
|
|
3229
|
+
];
|
|
2924
3230
|
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
3231
|
+
if (businessBirth !== undefined) {
|
|
3232
|
+
const businessPayload = Object.fromEntries(
|
|
3233
|
+
Object.entries(businessBirth.birthPayload).filter(([field]) => !["principal", "bornAt", "ownerKeyHash", "birthCert"].includes(field)),
|
|
3234
|
+
);
|
|
3235
|
+
out.push(
|
|
3236
|
+
`const facts = ${JSON.stringify(businessPayload)};`,
|
|
3237
|
+
`const born = await application.birth(facts);`,
|
|
3238
|
+
`if (born === undefined) fail("business birth returned no child application");`,
|
|
3239
|
+
`await waitUntilSaved();`,
|
|
3240
|
+
`ok(${JSON.stringify(`birth(${businessBirth.businessAggregateId}) saved through the generated application`)});`,
|
|
3241
|
+
);
|
|
3242
|
+
if (businessBirthFollowup !== undefined) {
|
|
3243
|
+
const { directive, target, payload } = businessBirthFollowup;
|
|
3244
|
+
const resultMember = proofResultMember(target.id);
|
|
3245
|
+
const collection = generatedAggregateCollectionName(target.id);
|
|
3246
|
+
out.push(
|
|
3247
|
+
`const childResult = await born.${directive.id}(${JSON.stringify(payload)});`,
|
|
3248
|
+
`const childReference = childResult.${resultMember};`,
|
|
3249
|
+
`const childRecord = await born.${collection}.find(childReference);`,
|
|
3250
|
+
`if (childRecord === undefined) fail(${JSON.stringify(`the born application could not read its ${target.id} consequence`)});`,
|
|
3251
|
+
`ok(${JSON.stringify(`${directive.id} changed the born ${businessBirth.businessAggregateId} application through generated business code`)});`,
|
|
3252
|
+
);
|
|
3253
|
+
}
|
|
3254
|
+
} else if (action !== undefined && aggregate !== undefined) {
|
|
3255
|
+
for (const seed of referenceSeeds) {
|
|
3256
|
+
const payload = typedPayloadLiteral(seed.payload, seed.referenceFields);
|
|
3257
|
+
const variable = `seed${proofTypeIdent(seed.aggregate.id)}`;
|
|
3258
|
+
out.push(
|
|
3259
|
+
`const ${variable}Result = await application.${seed.directive.id}(${payload});`,
|
|
3260
|
+
`const ${variable}Ref = ${variable}Result.${proofResultMember(seed.aggregate.id)};`,
|
|
3261
|
+
`${proofRefSlot(seed.aggregate.id)} = ${variable}Ref;`,
|
|
3262
|
+
);
|
|
3263
|
+
}
|
|
3264
|
+
const synth = synthesizeDirectivePayload(action, `${domain}/${action.id}`);
|
|
3265
|
+
const refs = aggregateReferenceFields(action.payloadSchema as z.ZodTypeAny);
|
|
3266
|
+
const mintField = mintedCreateField(action, aggregate);
|
|
3267
|
+
const payload = { ...synth.value };
|
|
3268
|
+
if (mintField !== undefined) delete payload[mintField];
|
|
3269
|
+
const payloadLiteral = typedPayloadLiteral(payload, refs);
|
|
3270
|
+
const nullArmLines = nullablePayloadFields(action.payloadSchema as z.ZodTypeAny).flatMap((field) => {
|
|
3271
|
+
const nullPayload = { ...payload, [field]: null };
|
|
3272
|
+
return [
|
|
3273
|
+
`await application.${action.id}(${typedPayloadLiteral(nullPayload, refs)});`,
|
|
3274
|
+
`ok(${JSON.stringify(`NULL-ARM PROOF — ${domain}/${action.id} accepted ${field}=null`)});`,
|
|
3275
|
+
];
|
|
3276
|
+
});
|
|
3277
|
+
const clearLegs = Object.entries(aggregate.fields as Record<string, Field>)
|
|
3278
|
+
.filter(([, field]) => field.isClearable === true)
|
|
3279
|
+
.map(([field]) => ({
|
|
3280
|
+
field,
|
|
3281
|
+
directiveId: clearDirectiveId(aggregate.id, field),
|
|
3282
|
+
}))
|
|
3283
|
+
.filter(({ directiveId }) => {
|
|
3284
|
+
const directive = (mod.directives as Directive<unknown>[]).find((candidate) => candidate.id === directiveId);
|
|
3285
|
+
return directive !== undefined
|
|
3286
|
+
&& (directive as { requiresRelationGate?: unknown }).requiresRelationGate === undefined;
|
|
3287
|
+
});
|
|
3288
|
+
const aggregateCollection = generatedAggregateCollectionName(aggregate.id);
|
|
3289
|
+
const clearWriteLines = clearLegs.flatMap(({ field, directiveId }) => [
|
|
3290
|
+
`await application.${directiveId}({ id: created });`,
|
|
3291
|
+
`ok(${JSON.stringify(`CLEAR-LANE PROOF — ${domain}/${directiveId} accepted the typed ${aggregate.id} reference`)});`,
|
|
3292
|
+
]);
|
|
3293
|
+
const clearReadLines = clearLegs.flatMap(({ field }) => [
|
|
3294
|
+
`const cleared${proofTypeIdent(field)} = await application.${aggregateCollection}.find(created);`,
|
|
3295
|
+
`if (cleared${proofTypeIdent(field)}?.[${JSON.stringify(field)}] !== null) fail(${JSON.stringify(`CLEAR-LANE PROOF — ${aggregate.id}.${field} did not read back as explicit null`)});`,
|
|
3296
|
+
]);
|
|
3297
|
+
out.push(
|
|
3298
|
+
`const result = await application.${action.id}(${payloadLiteral});`,
|
|
3299
|
+
`const created = result.${proofResultMember(aggregate.id)};`,
|
|
3300
|
+
`${proofRefSlot(aggregate.id)} = created;`,
|
|
3301
|
+
`ok(${JSON.stringify(`${domain}/${action.id} accepted business facts and returned a typed reference`)});`,
|
|
3302
|
+
...nullArmLines,
|
|
3303
|
+
...clearWriteLines,
|
|
3304
|
+
`await waitUntilSaved();`,
|
|
3305
|
+
...clearReadLines,
|
|
3306
|
+
`const reopened = await ${opener}({ cloud, application: applicationName, signIn });`,
|
|
3307
|
+
`const rows = await reopened.${generatedAggregateCollectionName(aggregate.id)}.all();`,
|
|
3308
|
+
`if (!rows.some((row) => sameRecord(row.ref, created))) fail("a fresh generated application could not read the saved record");`,
|
|
3309
|
+
`ok("a fresh generated application read the saved record after synchronization");`,
|
|
3310
|
+
);
|
|
3311
|
+
} else {
|
|
3312
|
+
out.push(
|
|
3313
|
+
`const status = await application.currentStatus();`,
|
|
3314
|
+
`if (status.phase === "attention") fail(status.message || "the generated application needs attention");`,
|
|
3315
|
+
`ok("the generated application opened and verified its durable local state");`,
|
|
3316
|
+
);
|
|
3317
|
+
}
|
|
2953
3318
|
out.push(
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
`//`,
|
|
2960
|
-
`// npx githolon proof (offline legs on a local holon)`,
|
|
2961
|
-
`// npx githolon proof --live (the same application proof in durable Nomos custody)`,
|
|
2962
|
-
`//`,
|
|
2963
|
-
`// Regenerated on every compile; do not edit.`,
|
|
2964
|
-
`// @nomos-proof-legs ${legsMarker}`,
|
|
2965
|
-
`import { readFileSync } from "node:fs";`,
|
|
2966
|
-
`import { connect, encodeIntentOfferBytes } from "@githolon/client";`,
|
|
2967
|
-
`import { ${factory}, ${hashConst} } from "./${opts.packageName}.client.ts";`,
|
|
2968
|
-
`void connect; void encodeIntentOfferBytes; void ${factory};`,
|
|
2969
|
-
``,
|
|
2970
|
-
`const CLOUD = (process.env.NOMOS_CLOUD || "https://nomos.captainapp.co.uk").replace(/\\/+$/, "");`,
|
|
2971
|
-
`const WS = process.env.NOMOS_WS || ${JSON.stringify(slug + "-proof-")} + Math.random().toString(36).slice(2, 8);`,
|
|
2972
|
-
``,
|
|
2973
|
-
...v2IdentityAndCleanupPreamble(opts, hashConst, n),
|
|
2974
|
-
``,
|
|
2975
|
-
`// ${n()}. connect + bind the GENERATED typed client (the relation-bootstrap leg drives it with an actor)`,
|
|
2976
|
-
`const holon = await connect({ cloud: CLOUD, workspace: WS, clientId: "proof-1", principal: PRINCIPAL, authorSecret: AUTHOR_SECRET });`,
|
|
2977
|
-
`ok(\`web client connected — ${factory} bound (replica \${holon.replica.slice(0, 6)}…)\`);`,
|
|
2978
|
-
``,
|
|
2979
|
-
// The SAME relation-bootstrap emitter the standalone proof uses — seed authority, then prove the GATED
|
|
2980
|
-
// CREATE is admitted (layers 1-3; a speculative seed-path degrades to a diagnostic, never a false green).
|
|
2981
|
-
...relationSeedLegLines(rb, factory, n),
|
|
2982
|
-
`console.log(\`\\nALL GREEN — generated proof for "${opts.packageName}": deploy → seed authority → gated create ${rb.gatedDirectiveId} admitted (\${WS})\`);`,
|
|
2983
|
-
``,
|
|
2984
|
-
...v2CatchFinally(),
|
|
3319
|
+
`console.log(${JSON.stringify(`\nALL GREEN — ${opts.packageName} used only its generated application surface`)});`,
|
|
3320
|
+
`const _proofDisposable = application as unknown as { [Symbol.asyncDispose]?: () => Promise<void> };`,
|
|
3321
|
+
`const _proofDispose = _proofDisposable[Symbol.asyncDispose];`,
|
|
3322
|
+
`if (_proofDispose) await Promise.race([_proofDispose(), new Promise<void>((resolve) => setTimeout(resolve, 1_000))]);`,
|
|
3323
|
+
`process.exit(0);`,
|
|
2985
3324
|
``,
|
|
2986
3325
|
);
|
|
2987
3326
|
return out.join("\n");
|
|
2988
3327
|
}
|
|
2989
3328
|
|
|
3329
|
+
|
|
2990
3330
|
export function generateTsProof(modules: readonly DomainModule[], opts: TsProofOptions): string {
|
|
2991
3331
|
// PRIMARY-DOMAIN SELECTION: a declared BUSINESS birth is always first because `proof --live` promises the
|
|
2992
3332
|
// product's one `app.birth(facts)` surface, not a convenient auxiliary create from the same law. Without
|
|
@@ -2997,7 +3337,7 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
|
|
|
2997
3337
|
(d) => (d as { businessBirth?: unknown }).businessBirth !== undefined,
|
|
2998
3338
|
);
|
|
2999
3339
|
const hasStandaloneCreate = (m: DomainModule): boolean =>
|
|
3000
|
-
(m.
|
|
3340
|
+
orderedStandaloneCreates(m).length > 0;
|
|
3001
3341
|
const hasBirth = (m: DomainModule): boolean => hasBirthDirective(m);
|
|
3002
3342
|
const isBootstrapProvable = (m: DomainModule): boolean => {
|
|
3003
3343
|
try {
|
|
@@ -3018,376 +3358,12 @@ export function generateTsProof(modules: readonly DomainModule[], opts: TsProofO
|
|
|
3018
3358
|
throw new Error(
|
|
3019
3359
|
gap !== undefined
|
|
3020
3360
|
? `no proof could be synthesized: ${gap}`
|
|
3021
|
-
: `no .creates directive in any domain — a generated proof needs one
|
|
3361
|
+
: `no .creates/.ensures directive in any domain — a generated proof needs one business write (or a .births() birth directive); add a business action that creates or ensures a record so the canonical generated journey can exist`,
|
|
3022
3362
|
);
|
|
3023
3363
|
}
|
|
3024
|
-
const domain = mod.domain ?? mod.name;
|
|
3025
|
-
const factory = tsClientFactoryName(domain);
|
|
3026
|
-
const hashConst = tsHashConstName(opts.packageName);
|
|
3027
3364
|
// THE MARKER (since 0.5.0) + the per-domain index are ONE synthesis (`legsForDomain`) — the proof body, its
|
|
3028
3365
|
// marker, and the index can never describe different directives. The primary domain's legs carry whichever
|
|
3029
3366
|
// of standalone / child-birth / relation-bootstrap it has.
|
|
3030
3367
|
const primaryLegs = legsForDomain(mod, modules, opts.interface);
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
// A business-birth domain may also contain `addSite`-style standalone creates. Those are useful secondary
|
|
3034
|
-
// verbs, but they must never steal the live acceptance proof from the one public application birth.
|
|
3035
|
-
if (childBirth?.businessAggregateId !== undefined) {
|
|
3036
|
-
return generateBirthOnlyProof(mod, modules, opts, primaryLegs);
|
|
3037
|
-
}
|
|
3038
|
-
|
|
3039
|
-
// The standalone-create lane needs an UNGATED create (isStandaloneCreate — never a gated/birth/attestation
|
|
3040
|
-
// create, which the gate would refuse dispatched with no seeded authority). Absent one, the primary write
|
|
3041
|
-
// is the birth leg (a `.births()` domain) or the relation-bootstrap leg (a gated-create domain).
|
|
3042
|
-
const createDir = (mod.directives as Directive<unknown>[]).find((d) => isStandaloneCreate(d));
|
|
3043
|
-
if (createDir === undefined) {
|
|
3044
|
-
// BIRTH-ONLY PRIMARY: emit a birth-shaped proof (the birth replays genesis, seating authority, then the
|
|
3045
|
-
// born child self-validates — a gated child-domain create is proven INSIDE the born child context).
|
|
3046
|
-
if (primaryLegs.childBirth !== undefined) return generateBirthOnlyProof(mod, modules, opts, primaryLegs);
|
|
3047
|
-
// BOOTSTRAP-ONLY PRIMARY: the only creating write is GATED — deploy, then seed authority + prove the
|
|
3048
|
-
// gated create is admitted (the relation-bootstrap leg is its ONLY positive proof).
|
|
3049
|
-
if (primaryLegs.relationBootstrap !== undefined) return generateBootstrapOnlyProof(mod, opts, primaryLegs);
|
|
3050
|
-
// A gated create with no seedable authority: loud diagnostic (compile skips the proof, law still ships).
|
|
3051
|
-
const gap = diagnoseRelationBootstrapGap(mod);
|
|
3052
|
-
throw new Error(gap !== undefined ? `no proof could be synthesized for '${domain}': ${gap}` : `domain '${domain}' has no provable creating write`);
|
|
3053
|
-
}
|
|
3054
|
-
const agg = (mod.aggregates as AggregateHandle[]).find((a) => a.id === createDir.aggregateId);
|
|
3055
|
-
if (agg === undefined) {
|
|
3056
|
-
throw new Error(
|
|
3057
|
-
`directive '${createDir.id}' creates '${createDir.aggregateId}' but no such aggregate is composed — export the aggregate from a listed module`,
|
|
3058
|
-
);
|
|
3059
|
-
}
|
|
3060
|
-
const synth = synthesizePayload(createDir.payloadSchema as z.ZodTypeAny, `${domain}/${createDir.id}`);
|
|
3061
|
-
|
|
3062
|
-
// THE MINTED-ID RULE (the gate's `check_create_ids`): a `.creates` payload field
|
|
3063
|
-
// that IS the target's own id must be KERNEL-MINTED — a hand-written sample id
|
|
3064
|
-
// refuses typed at admission (`NotMinted`). The marker-driven trace names the
|
|
3065
|
-
// field (never name-guessed); the generated proof MINTS it at runtime
|
|
3066
|
-
// (`holon.mint`) instead of synthesizing a sample, so the proof admits
|
|
3067
|
-
// everywhere the law does.
|
|
3068
|
-
const mintField = mintedCreateField(createDir, agg);
|
|
3069
|
-
const baseValue: Record<string, unknown> = { ...synth.value };
|
|
3070
|
-
if (mintField !== undefined) delete baseValue[mintField];
|
|
3071
|
-
|
|
3072
|
-
// The first declared query that can READ BACK the create: returns the created
|
|
3073
|
-
// aggregate and every key field has a synthesized payload value to probe with
|
|
3074
|
-
// (a query keyed on the MINTED field is skipped — its value exists only at runtime).
|
|
3075
|
-
const query = ((mod.queries ?? []) as QueryDecl[]).find(
|
|
3076
|
-
(q) => q.returns === agg.id && q.key.length > 0 && q.key.every((k) => !k.includes(".") && baseValue[k] !== undefined),
|
|
3077
|
-
);
|
|
3078
|
-
|
|
3079
|
-
// The first declared count the create moves: counts the created aggregate,
|
|
3080
|
-
// unpredicated (a .where can't be evaluated at codegen time), and — when
|
|
3081
|
-
// grouped — grouped by a field the synthesized payload carries.
|
|
3082
|
-
const count = (mod.counts ?? [])
|
|
3083
|
-
.map((raw) => finishCount(raw))
|
|
3084
|
-
.find(
|
|
3085
|
-
(c) =>
|
|
3086
|
-
c.of === agg.id &&
|
|
3087
|
-
(c as { where?: unknown }).where == null &&
|
|
3088
|
-
(c.by == null || baseValue[c.by] !== undefined),
|
|
3089
|
-
);
|
|
3090
|
-
|
|
3091
|
-
const concurrency = findConcurrencyLeg(mod.directives as Directive<unknown>[], agg);
|
|
3092
|
-
|
|
3093
|
-
// THE AUTHORITY-CONFORMANCE LEG (the one-encoder + gate proof): the first gated (`.requires(...)`) directive
|
|
3094
|
-
// in the primary domain, proven locally — actor threads through the one encoder; an actorless gated offer
|
|
3095
|
-
// throws MissingOfferAuthority. `undefined` ⇒ a documented no-op (the domain authors openly).
|
|
3096
|
-
const conformance = discoverAuthorityConformanceLeg({
|
|
3097
|
-
...mod,
|
|
3098
|
-
directives: (mod.directives as Directive<unknown>[]).filter(
|
|
3099
|
-
(d) => (d as { businessBirthInternal?: unknown }).businessBirthInternal !== true,
|
|
3100
|
-
),
|
|
3101
|
-
});
|
|
3102
|
-
|
|
3103
|
-
// THE RELATION-BOOTSTRAP LEG (the seed-then-prove positive): grant relation R to the proof actor, then prove
|
|
3104
|
-
// a `.requires(R)`-gated write SUCCEEDS under that actor. `undefined` ⇒ no gated directive or no discoverable
|
|
3105
|
-
// granter (the negative conformance leg still runs). Comes from the SAME `legsForDomain` synthesis (marker).
|
|
3106
|
-
// Workspace-name slug: any package name → a safe, recognizable ws prefix.
|
|
3107
|
-
const slug =
|
|
3108
|
-
opts.packageName
|
|
3109
|
-
.toLowerCase()
|
|
3110
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
3111
|
-
.replace(/^-+|-+$/g, "")
|
|
3112
|
-
.slice(0, 24) || "domain";
|
|
3113
|
-
|
|
3114
|
-
const payloadLit =
|
|
3115
|
-
mintField !== undefined
|
|
3116
|
-
? `{ ...${JSON.stringify(baseValue)}, ${JSON.stringify(mintField)}: __minted }`
|
|
3117
|
-
: JSON.stringify(baseValue);
|
|
3118
|
-
const queryParamsObj = query ? Object.fromEntries(query.key.map((k) => [k, baseValue[k]])) : undefined;
|
|
3119
|
-
const queryParams = queryParamsObj ? JSON.stringify(queryParamsObj) : "";
|
|
3120
|
-
// The gateway routes a declared query by its id: GET /v2/workspaces/<ws>/<queryId>?<key>=<val>
|
|
3121
|
-
// (the host reads each declared key from the querystring — never a generic /query?params= lane).
|
|
3122
|
-
const queryQs =
|
|
3123
|
-
query && queryParamsObj
|
|
3124
|
-
? query.key.map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(String(queryParamsObj[k]))}`).join("&")
|
|
3125
|
-
: "";
|
|
3126
|
-
const listAccessor = `list${agg.id}s`;
|
|
3127
|
-
|
|
3128
|
-
// THE MACHINE-READABLE LEGS (parsed by `githolon`'s offline proof runner —
|
|
3129
|
-
// cli/src/proof_offline.ts). The ONE leg-synthesis (`legsForDomain`) feeds
|
|
3130
|
-
// both this marker and the per-domain `proof-legs.json` index, so they never drift.
|
|
3131
|
-
const legsMarker = JSON.stringify(primaryLegs);
|
|
3132
|
-
|
|
3133
|
-
let step = 0;
|
|
3134
|
-
const n = () => ++step;
|
|
3135
|
-
const out: string[] = [];
|
|
3136
|
-
|
|
3137
|
-
out.push(
|
|
3138
|
-
`// AUTO-GENERATED by nomos-compile — the RUNNABLE PROOF for package "${opts.packageName}",`,
|
|
3139
|
-
`// synthesized from the domain's OWN directives/queries/counts. Reshape the law,`,
|
|
3140
|
-
`// recompile, and this proof reshapes itself — no test rewriting. Run it:`,
|
|
3141
|
-
`//`,
|
|
3142
|
-
`// npx githolon proof (or: npx tsx build/${opts.packageName}.proof.mts)`,
|
|
3143
|
-
`//`,
|
|
3144
|
-
`// The flow mirrors the scaffold's test/e2e.mts (docs/ in a scaffolded app explains`,
|
|
3145
|
-
`// every call — start at docs/01-mental-model.md). Regenerated on every compile; do not edit.`,
|
|
3146
|
-
`// @nomos-proof-legs ${legsMarker}`,
|
|
3147
|
-
`import { connect, encodeIntentOfferBytes } from "@githolon/client";`,
|
|
3148
|
-
`import { ${factory}, ${hashConst} } from "./${opts.packageName}.client.ts";`,
|
|
3149
|
-
``,
|
|
3150
|
-
`const CLOUD = (process.env.NOMOS_CLOUD || "https://nomos.captainapp.co.uk").replace(/\\/+$/, "");`,
|
|
3151
|
-
`const WS = process.env.NOMOS_WS || ${JSON.stringify(slug + "-proof-")} + Math.random().toString(36).slice(2, 8);`,
|
|
3152
|
-
``,
|
|
3153
|
-
...v2IdentityAndCleanupPreamble(opts, hashConst, n),
|
|
3154
|
-
``,
|
|
3155
|
-
`// ${n()}. connect + bind the GENERATED typed client`,
|
|
3156
|
-
`const holon = await connect({ cloud: CLOUD, workspace: WS, clientId: "proof-1", principal: PRINCIPAL, authorSecret: AUTHOR_SECRET });`,
|
|
3157
|
-
`const app = ${factory}(holon);`,
|
|
3158
|
-
`ok(\`web client connected — ${factory} bound (replica \${holon.replica.slice(0, 6)}…)\`);`,
|
|
3159
|
-
``,
|
|
3160
|
-
`// ${n()}. OFFLINE write — the proof is ENFORCED, not narrated: while we author and`,
|
|
3161
|
-
`// read LOCALLY, fetch THROWS. The payload below was SYNTHESIZED from the zod`,
|
|
3162
|
-
`// schema of \`${domain}/${createDir.id}\`${synth.autoStamped.length ? ` (${synth.autoStamped.map((s) => `\`${s}\``).join("/")} omitted — the client auto-stamps ISO now())` : ``}.`,
|
|
3163
|
-
`const realFetch = globalThis.fetch;`,
|
|
3164
|
-
`globalThis.fetch = (() => { throw new Error("OFFLINE PROOF VIOLATED: local authoring touched the network"); }) as typeof fetch;`,
|
|
3165
|
-
``,
|
|
3166
|
-
...(mintField !== undefined
|
|
3167
|
-
? [
|
|
3168
|
-
`// the target's own id is KERNEL-MINTED (never hand-written — the gate's`,
|
|
3169
|
-
`// check_create_ids refuses sample ids typed): the local wasm mints, the id`,
|
|
3170
|
-
`// rides the payload, replay reads the capture.`,
|
|
3171
|
-
`const __minted = await holon.mint(${JSON.stringify(agg.id)});`,
|
|
3172
|
-
]
|
|
3173
|
-
: []),
|
|
3174
|
-
`await app.${createDir.id}(${payloadLit});`,
|
|
3175
|
-
`ok("dispatch ${domain}/${createDir.id} — offline write under the pulled law (payload synthesized from YOUR schema${mintField !== undefined ? `; \`${mintField}\` kernel-minted` : ``})");`,
|
|
3176
|
-
``,
|
|
3177
|
-
);
|
|
3178
|
-
|
|
3179
|
-
if (query) {
|
|
3180
|
-
out.push(
|
|
3181
|
-
`// ${n()}. the first declared query routes LOCALLY (params from the synthesized payload)`,
|
|
3182
|
-
`const rows = await app.${lcFirst(camel(query.id))}(${queryParams});`,
|
|
3183
|
-
`if (rows.length !== 1) fail(\`local declared query ${query.id}: \${JSON.stringify(rows)}\`);`,
|
|
3184
|
-
`const createdId = rows[0]!.id;`,
|
|
3185
|
-
`ok(\`declared query ${query.id} answers locally — ${agg.id} \${createdId}\`);`,
|
|
3186
|
-
``,
|
|
3187
|
-
);
|
|
3188
|
-
} else {
|
|
3189
|
-
out.push(
|
|
3190
|
-
`// ${n()}. no declared query reads this create back (declare a query(...) keyed on a`,
|
|
3191
|
-
`// payload field to see one proved here) — the type-index probe captures the id.`,
|
|
3192
|
-
`const rows = await app.${listAccessor}();`,
|
|
3193
|
-
`if (rows.length !== 1) fail(\`local ${listAccessor}: \${JSON.stringify(rows)}\`);`,
|
|
3194
|
-
`const createdId = rows[0]!.id;`,
|
|
3195
|
-
`console.log("– declared-query leg SKIPPED — no query(...) returns ${agg.id} keyed on a synthesized payload field");`,
|
|
3196
|
-
`ok(\`local read answers — ${agg.id} \${createdId}\`);`,
|
|
3197
|
-
``,
|
|
3198
|
-
);
|
|
3199
|
-
}
|
|
3200
|
-
|
|
3201
|
-
if (count) {
|
|
3202
|
-
const accessor = lcFirst(camel(count.id));
|
|
3203
|
-
const callArg = count.by != null ? JSON.stringify(String(baseValue[count.by])) : "";
|
|
3204
|
-
out.push(
|
|
3205
|
-
`// ${n()}. the first declared count — the maintained O(1) tally, locally`,
|
|
3206
|
-
`const localCount = await app.${accessor}(${callArg});`,
|
|
3207
|
-
`if (localCount !== 1) fail(\`local declared count ${count.id}: \${localCount}\`);`,
|
|
3208
|
-
`ok("declared count ${count.id}${count.by != null ? `(${callArg.replace(/"/g, "'")})` : `()`} = 1 locally");`,
|
|
3209
|
-
``,
|
|
3210
|
-
);
|
|
3211
|
-
}
|
|
3212
|
-
|
|
3213
|
-
// NULL SEMANTICS BOTH-ARM PROOF (null_semantics.md): the primary dispatch above already proved the
|
|
3214
|
-
// VALUE arm (a nullable field synthesizes to a sampled value); here we prove the NULL arm — the law
|
|
3215
|
-
// ADMITS an explicit `null` for each nullable field, so it is provably total over null. Each create
|
|
3216
|
-
// arm mints a fresh id. Empty for a null-free payload (byte-identical proof).
|
|
3217
|
-
const nullArms = nullablePayloadFields(createDir.payloadSchema as z.ZodTypeAny);
|
|
3218
|
-
for (const field of nullArms) {
|
|
3219
|
-
const armMint = mintField !== undefined ? `, ${JSON.stringify(mintField)}: await holon.mint(${JSON.stringify(agg.id)})` : "";
|
|
3220
|
-
out.push(
|
|
3221
|
-
`// ${n()}. NULL-ARM PROOF — \`${field}\` is nullable: the law must ADMIT an explicit null (both arms proven).`,
|
|
3222
|
-
`try {`,
|
|
3223
|
-
` await app.${createDir.id}({ ...${JSON.stringify(baseValue)}${armMint}, ${JSON.stringify(field)}: null });`,
|
|
3224
|
-
` ok(${JSON.stringify(`null-arm — ${domain}/${createDir.id} admitted with \`${field}\`=null (the law handles the explicit-null arm)`)});`,
|
|
3225
|
-
`} catch (e) {`,
|
|
3226
|
-
` fail(\`null-arm ${field}=null was REFUSED — a nullable field's null arm must be admitted: \${(e as Error).message}\`);`,
|
|
3227
|
-
`}`,
|
|
3228
|
-
``,
|
|
3229
|
-
);
|
|
3230
|
-
}
|
|
3231
|
-
|
|
3232
|
-
// CLEAR-LANE PROOF (null_semantics.md): a `t.clearable(...)` field's compose-injected clear directive
|
|
3233
|
-
// (`clearDirectiveId(agg, field)`) LOWERS to a Set of json null — dispatch it on the created instance
|
|
3234
|
-
// and read the field back as null (ExplicitNull). Only UN-GATED clear directives (the proof binds no
|
|
3235
|
-
// actor); a gated clear needs authority the negative/positive gate legs already cover. Empty for a
|
|
3236
|
-
// clearable-free aggregate (byte-identical proof).
|
|
3237
|
-
const clearLegs = Object.entries(agg.fields as Record<string, Field>)
|
|
3238
|
-
.filter(([, f]) => f.isClearable === true)
|
|
3239
|
-
.map(([field]) => ({ field, dirId: clearDirectiveId(agg.id, field) }))
|
|
3240
|
-
.filter(({ dirId }) => {
|
|
3241
|
-
const cd = (mod.directives as Directive<unknown>[]).find((d) => d.id === dirId);
|
|
3242
|
-
return cd !== undefined && (cd as { requiresRelationGate?: unknown }).requiresRelationGate === undefined;
|
|
3243
|
-
});
|
|
3244
|
-
for (const { field, dirId } of clearLegs) {
|
|
3245
|
-
out.push(
|
|
3246
|
-
`// ${n()}. CLEAR-LANE PROOF — \`${field}\` is clearable: \`${dirId}\` lowers to Set(json null); after it,`,
|
|
3247
|
-
`// the field reads back null (ExplicitNull), never omitted.`,
|
|
3248
|
-
`try {`,
|
|
3249
|
-
` await app.${dirId}({ id: createdId });`,
|
|
3250
|
-
`} catch (e) {`,
|
|
3251
|
-
` fail(\`clear-lane ${dirId} was REFUSED — an un-gated clear of a clearable field must be admitted: \${(e as Error).message}\`);`,
|
|
3252
|
-
`}`,
|
|
3253
|
-
`{`,
|
|
3254
|
-
` const clearedRows = await holon.queryById(createdId) as { data?: Record<string, unknown> }[];`,
|
|
3255
|
-
` const clearedRow = clearedRows[0];`,
|
|
3256
|
-
` if (clearedRow !== undefined && clearedRow.data !== undefined && clearedRow.data[${JSON.stringify(field)}] != null) {`,
|
|
3257
|
-
` fail(\`clear-lane ${field}: expected null after clear, got \${JSON.stringify(clearedRow.data[${JSON.stringify(field)}])}\`);`,
|
|
3258
|
-
` }`,
|
|
3259
|
-
` ok(${JSON.stringify(`clear-lane — ${domain}/${dirId} admitted; \`${field}\` reads back null (ExplicitNull)`)});`,
|
|
3260
|
-
`}`,
|
|
3261
|
-
``,
|
|
3262
|
-
);
|
|
3263
|
-
}
|
|
3264
|
-
|
|
3265
|
-
out.push(
|
|
3266
|
-
`globalThis.fetch = realFetch;`,
|
|
3267
|
-
`ok("offline proof ENFORCED — fetch was trapped: local write/read touched no network");`,
|
|
3268
|
-
``,
|
|
3269
|
-
`// ${n()}. sync: push the session branch; edge admission re-verifies and merges to main`,
|
|
3270
|
-
`const s = await holon.sync();`,
|
|
3271
|
-
`if (!s.admission?.ok) fail(\`sync/admit: \${JSON.stringify(s)}\`);`,
|
|
3272
|
-
`const admitted = (s.admission.sessions || []).flatMap((x: { admitted?: unknown[] }) => x.admitted || []);`,
|
|
3273
|
-
`if (admitted.length < 1) fail(\`edge admitted \${admitted.length} intent(s): \${JSON.stringify(s.admission)}\`);`,
|
|
3274
|
-
`ok(\`synced — edge admission merged \${admitted.length} intent(s) to main\`);`,
|
|
3275
|
-
``,
|
|
3276
|
-
);
|
|
3277
|
-
|
|
3278
|
-
if (query) {
|
|
3279
|
-
out.push(
|
|
3280
|
-
`// ${n()}. the CLOUD answers the SAME declared query — routed by the workspace's OWN OpenAPI`,
|
|
3281
|
-
`// (GET /v2/workspaces/<ws>/${query.id}?${queryQs}); the host learns the route from the law's spec.`,
|
|
3282
|
-
`d = await (await fetch(\`\${CLOUD}/v2/workspaces/\${WS}/${query.id}${queryQs ? `?${queryQs}` : ``}\`)).json();`,
|
|
3283
|
-
`if (!(d.ok && d.rows.length === 1 && d.rows[0].id === createdId)) fail(\`cloud declared query: \${JSON.stringify(d)}\`);`,
|
|
3284
|
-
`ok("cloud declared query ${query.id} returns the admitted row — full loop closed");`,
|
|
3285
|
-
``,
|
|
3286
|
-
);
|
|
3287
|
-
} else {
|
|
3288
|
-
out.push(
|
|
3289
|
-
`// ${n()}. the CLOUD answers the by-id read (edge projection of the admitted write)`,
|
|
3290
|
-
`d = await (await fetch(\`\${CLOUD}/v2/workspaces/\${WS}/aggregates/\${encodeURIComponent(createdId)}\`)).json();`,
|
|
3291
|
-
`if (!(d.rows?.length === 1 && d.rows[0].id === createdId)) fail(\`cloud by-id read: \${JSON.stringify(d)}\`);`,
|
|
3292
|
-
`ok("cloud read returns the admitted row — full loop closed");`,
|
|
3293
|
-
``,
|
|
3294
|
-
);
|
|
3295
|
-
}
|
|
3296
|
-
|
|
3297
|
-
if (count) {
|
|
3298
|
-
out.push(
|
|
3299
|
-
`// ${n()}. the EDGE maintains the same count (same wasm, same law)`,
|
|
3300
|
-
`d = await (await fetch(\`\${CLOUD}/v2/workspaces/\${WS}/${count.id}${count.by != null ? `?group=\${encodeURIComponent(${JSON.stringify(String(baseValue[count.by]))})}` : ``}\`)).json();`,
|
|
3301
|
-
`if (!(d.ok && d.count === 1)) fail(\`cloud count ${count.id}: \${JSON.stringify(d)}\`);`,
|
|
3302
|
-
`ok("cloud count ${count.id} = 1 — the O(1) maintained read, at the edge too");`,
|
|
3303
|
-
``,
|
|
3304
|
-
);
|
|
3305
|
-
}
|
|
3306
|
-
|
|
3307
|
-
if (concurrency) {
|
|
3308
|
-
const { directive: cd, idField, setField } = concurrency;
|
|
3309
|
-
const cSynth = synthesizePayload(cd.payloadSchema as z.ZodTypeAny, `${domain}/${cd.id}`);
|
|
3310
|
-
const concPayload = (writer: string): string => {
|
|
3311
|
-
const entries = Object.entries({ ...cSynth.value, [idField]: 0, [setField]: 0 }).map(([k]) => {
|
|
3312
|
-
if (k === idField) return `${JSON.stringify(k)}: createdId`;
|
|
3313
|
-
if (k === setField) return `${JSON.stringify(k)}: ${JSON.stringify([`proof-${writer}`])}`;
|
|
3314
|
-
return `${JSON.stringify(k)}: ${JSON.stringify(cSynth.value[k])}`;
|
|
3315
|
-
});
|
|
3316
|
-
return `{ ${entries.join(", ")} }`;
|
|
3317
|
-
};
|
|
3318
|
-
out.push(
|
|
3319
|
-
`// ${n()}. CONCURRENCY — two writers, two clientIds, BLIND to each other: each adds to`,
|
|
3320
|
-
`// the AddWins set \`${agg.id}.${setField}\` via \`${domain}/${cd.id}\` offline; admission`,
|
|
3321
|
-
`// merges both and the union keeps every add.`,
|
|
3322
|
-
`const writerA = await connect({ cloud: CLOUD, workspace: WS, clientId: "proof-a", principal: PRINCIPAL, authorSecret: AUTHOR_SECRET });`,
|
|
3323
|
-
`const writerB = await connect({ cloud: CLOUD, workspace: WS, clientId: "proof-b", principal: PRINCIPAL, authorSecret: AUTHOR_SECRET });`,
|
|
3324
|
-
`await ${factory}(writerA).${cd.id}(${concPayload("a")});`,
|
|
3325
|
-
`await ${factory}(writerB).${cd.id}(${concPayload("b")});`,
|
|
3326
|
-
`await writerA.sync();`,
|
|
3327
|
-
`await writerB.sync();`,
|
|
3328
|
-
`d = await (await fetch(\`\${CLOUD}/v2/workspaces/\${WS}/aggregates/\${encodeURIComponent(createdId)}\`)).json();`,
|
|
3329
|
-
`const merged: string[] = d.rows?.[0]?.data?.[${JSON.stringify(setField)}] ?? [];`,
|
|
3330
|
-
`if (!(merged.includes("proof-a") && merged.includes("proof-b"))) fail(\`AddWins union lost an add: \${JSON.stringify(merged)}\`);`,
|
|
3331
|
-
`ok(\`two blind writers' concurrent adds BOTH survive the merge — ${setField} [\${[...merged].sort().join(", ")}]\`);`,
|
|
3332
|
-
``,
|
|
3333
|
-
);
|
|
3334
|
-
} else {
|
|
3335
|
-
out.push(
|
|
3336
|
-
`// ${n()}. concurrency leg: this law has no addToSet-capable .mutates directive on an`,
|
|
3337
|
-
`// AddWins set field, so there is nothing honest to race.`,
|
|
3338
|
-
`console.log("– concurrency leg SKIPPED — add a .mutates directive carrying an array field named like an AddWins set (the addToSet shape) to see two blind writers merge");`,
|
|
3339
|
-
``,
|
|
3340
|
-
);
|
|
3341
|
-
}
|
|
3342
|
-
|
|
3343
|
-
// THE ATTESTED / DYNAMIC-POLICY FOREIGN-READ LEG — named, honestly SKIPPED here. A `.readsFrom(...)`
|
|
3344
|
-
// directive's plan reads a SIGNED cross-workspace attestation; proving it needs a SECOND source
|
|
3345
|
-
// workspace, its read-attestor key, and a served+signed envelope — none of which the single-workspace,
|
|
3346
|
-
// client-driven offline proof plane can mint (the client has no `attestedRead` serving op). The full
|
|
3347
|
-
// offline two-workspace proof (birth a scratch source → seed a row → enrol its genesis → attested read
|
|
3348
|
-
// through the policy edge → admitted, plus the wrong-genesis/unenrolled negative + revocation +
|
|
3349
|
-
// verify_chain) lives in the bench harness against the real wasm.
|
|
3350
|
-
if (hasForeignReadDirective(mod)) {
|
|
3351
|
-
out.push(
|
|
3352
|
-
`// ${n()}. attested foreign-read leg: a .readsFrom(...) directive reads a SIGNED cross-workspace`,
|
|
3353
|
-
`// attestation — proving it needs a second SOURCE workspace + its attestor key, which this`,
|
|
3354
|
-
`// single-workspace client proof cannot mint. Proven in the two-workspace bench smoke.`,
|
|
3355
|
-
`console.log("– attested foreign-read leg SKIPPED — a .readsFrom(...) attested/policy read is proven in the two-workspace bench harness (bench/scale/{attested_reads.test,dynamic_attested_reads.smoke}.mjs), not this single-workspace offline proof");`,
|
|
3356
|
-
``,
|
|
3357
|
-
);
|
|
3358
|
-
}
|
|
3359
|
-
|
|
3360
|
-
// THE AUTHORITY-CONFORMANCE LEG (the one-encoder + gate proof): runs on the LOCAL holon (fetch re-trapped),
|
|
3361
|
-
// proving the generated client threads security.actor through THE ONE ENCODER and that an actorless gated
|
|
3362
|
-
// offer is impossible to GENERATE (throws MissingOfferAuthority before any submit). No-op when un-gated.
|
|
3363
|
-
out.push(...authorityConformanceLegLines(conformance, factory, n));
|
|
3364
|
-
|
|
3365
|
-
// THE RELATION-BOOTSTRAP LEG (seed-then-prove the POSITIVE): grant relation R (or a rewrite base of it) to
|
|
3366
|
-
// the proof actor, then prove the gated write SUCCEEDS under that actor. Runs on the LOCAL holon (fetch
|
|
3367
|
-
// re-trapped). When no granter is discoverable but a gated directive exists, emits a CLEAR diagnostic naming
|
|
3368
|
-
// exactly what's missing (the negative conformance leg still covers the gate).
|
|
3369
|
-
out.push(...relationSeedLegsLines(primaryLegs, factory, n, diagnoseRelationBootstrapGap(mod)));
|
|
3370
|
-
|
|
3371
|
-
// THE CHILD-BIRTH LEG (G3, gap 2): when the primary domain has a `.births()` directive, prove the birth
|
|
3372
|
-
// END-TO-END against the cloud — drive the parent birth offer, then read the BORN child workspace it spawns.
|
|
3373
|
-
if (childBirth !== undefined) {
|
|
3374
|
-
out.push(...liveChildBirthLegLines(childBirth, opts, n));
|
|
3375
|
-
}
|
|
3376
|
-
|
|
3377
|
-
out.push(
|
|
3378
|
-
`// ${n()}. CONVERGENCE: the original instance adopts canonical main in place — no reconnect`,
|
|
3379
|
-
`await holon.pull();`,
|
|
3380
|
-
`const head = await holon.head();`,
|
|
3381
|
-
`const refsAdv = await (await fetch(\`\${CLOUD}/v2/workspaces/\${WS}/git/info/refs?service=git-upload-pack\`)).text();`,
|
|
3382
|
-
`const remoteMain = (refsAdv.match(/([0-9a-f]{40}) refs\\/heads\\/main/) || [])[1];`,
|
|
3383
|
-
`if (head !== remoteMain) fail(\`not converged in place: local \${head?.slice(0, 10)} vs main \${remoteMain?.slice(0, 10)}\`);`,
|
|
3384
|
-
`ok("converged in place — local head == canonical main");`,
|
|
3385
|
-
``,
|
|
3386
|
-
`console.log(\`\\nALL GREEN — generated proof for "${opts.packageName}": deploy → offline write → local reads → admission → cloud reads${concurrency ? ` → AddWins merge` : ``}${childBirth !== undefined ? ` → child birth` : ``} → convergence (\${WS})\`);`,
|
|
3387
|
-
``,
|
|
3388
|
-
...v2CatchFinally(),
|
|
3389
|
-
``,
|
|
3390
|
-
);
|
|
3391
|
-
|
|
3392
|
-
return out.join("\n");
|
|
3368
|
+
return generateSealedApplicationProof(mod, opts, primaryLegs);
|
|
3393
3369
|
}
|