@githolon/dsl 0.102.5 → 0.103.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/compile_package.mjs +43 -2
- package/compile_proof.d.mts +47 -0
- package/compile_proof.mjs +343 -0
- package/dart/CHANGELOG.md +7 -0
- package/dart/lib/src/dev_cloud.dart +8 -8
- package/dart/lib/src/nms_generated_runtime.dart +2 -0
- package/dart/lib/src/runner/node-entry.mjs +5504 -1236
- package/dart/lib/src/runner/offline-install.mjs +4578 -528
- package/dart/lib/src/test_harness.dart +206 -152
- package/dart/lib/src/webview_bridge.dart +196 -89
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/acquire_session_credentials_test.dart +4 -3
- package/dart/test/adv_realm_interference_test.dart +7 -6
- package/dart/test/cloud_bound_topology_test.dart +3 -3
- package/dart/test/cluster_births_test.dart +4 -3
- package/dart/test/cluster_device_keyed_acceptance_test.dart +10 -16
- package/dart/test/cluster_local_autosync_test.dart +3 -2
- package/dart/test/cluster_scale_test.dart +3 -2
- package/dart/test/dlq_session_surface_test.dart +7 -6
- package/dart/test/first_app_gauntlet_test.dart +14 -21
- package/dart/test/fixtures/wedge/fake-install.mjs +1 -1
- package/dart/test/four_layer_nested_topology_test.dart +4 -4
- package/dart/test/harness_phase_timeout_test.dart +2 -2
- package/dart/test/local_sync_inert_test.dart +1 -1
- package/dart/test/parent_signer_recovery_test.dart +2 -2
- package/dart/test/sealing_client_test.dart +4 -3
- package/dart/test/session_bound_test.dart +8 -7
- package/dart/test/test_harness_test.dart +13 -12
- package/dart/test/vo_read_typing_test.dart +4 -3
- package/dart/test/watch_after_offer_stall_test.dart +4 -3
- package/dart/test/webview_bridge_test.dart +102 -5
- package/dart_types/CHANGELOG.md +5 -0
- package/dart_types/lib/nomos_types.dart +9 -0
- package/dart_types/lib/src/nomos_ref.dart +20 -0
- package/dart_types/lib/src/optional_ref.dart +85 -0
- package/dart_types/lib/src/sha256_digest.dart +41 -0
- package/dart_types/lib/src/versioned_nomos_ref.dart +81 -0
- package/dart_types/pubspec.yaml +1 -1
- package/dart_types/test/optional_ref_test.dart +44 -0
- package/dart_types/test/sha256_digest_test.dart +18 -0
- package/package.json +9 -5
- package/runtime/generated-application-host.js +5365 -1105
- package/src/aggregate.ts +18 -0
- package/src/application_composition.ts +6 -32
- package/src/attested_core.ts +6 -3
- package/src/birth_meter.ts +1 -0
- package/src/birth_meter_ensure.ts +28 -2
- package/src/births.ts +1 -1
- package/src/build_package.ts +162 -538
- package/src/business_birth.ts +19 -3
- package/src/canonical_json.ts +21 -0
- package/src/check_package_main.ts +73 -33
- package/src/clear_directive.ts +19 -8
- package/src/codegen_dart.ts +305 -22
- package/src/codegen_dart_application.ts +844 -292
- package/src/codegen_dart_client.ts +1084 -407
- package/src/codegen_dart_scenario.ts +12 -12
- package/src/codegen_proof.ts +169 -39
- package/src/codegen_provider_dart.ts +17 -1
- package/src/codegen_ts.ts +298 -106
- package/src/compat_manifest.ts +2 -2
- package/src/compat_package_main.ts +8 -12
- package/src/compile_package_main.ts +1858 -590
- package/src/compose.ts +1 -1
- package/src/directive.ts +208 -15
- package/src/engine_entry.ts +165 -111
- package/src/evolve_preview.ts +29 -69
- package/src/extremum.ts +1 -1
- package/src/fields.ts +183 -4
- package/src/flow_gate.ts +75 -2
- package/src/framework/application_home.ts +2 -0
- package/src/framework/auth_provider_law.ts +2 -2
- package/src/framework/birth_meter.ts +7 -19
- package/src/framework/bootstrap.ts +0 -2
- package/src/framework/business_corrections.ts +9 -5
- package/src/framework/domain_lifecycle.ts +15 -29
- package/src/framework/encryption_scope.ts +2 -1
- package/src/framework/framework_aggregates.ts +2 -0
- package/src/framework/genesis.ts +16 -14
- package/src/framework/home.ts +20 -14
- package/src/framework/owned_custody.ts +9 -3
- package/src/framework/platform_admin.ts +3 -3
- package/src/framework/relations.ts +21 -13
- package/src/framework/signing_identity.ts +45 -40
- package/src/framework/workspace_invariant.ts +245 -0
- package/src/framework/workspaces.ts +23 -29
- package/src/index.ts +36 -16
- package/src/intent.ts +178 -6
- package/src/law_check.ts +32 -11
- package/src/law_graph.ts +2902 -0
- package/src/law_graph_usda.ts +1799 -0
- package/src/law_identity.ts +435 -0
- package/src/law_value_type.ts +230 -0
- package/src/lsp_server.ts +58 -29
- package/src/ops.ts +7 -0
- package/src/package_config.ts +141 -0
- package/src/package_dependencies.ts +108 -0
- package/src/package_dependency_usage.ts +150 -0
- package/src/package_source_imports.ts +71 -0
- package/src/plan_source_slice.ts +335 -0
- package/src/point_guard.ts +192 -0
- package/src/precondition.ts +44 -9
- package/src/read_projection_namespace.ts +1 -1
- package/src/runtime_zod.ts +44 -0
- package/src/secondary_ensure.ts +67 -14
- package/src/shape_compat.ts +1 -1
- package/src/stable_id_identity_bridge.ts +149 -0
- package/src/stable_id_lockfile.ts +140 -6
- package/src/stable_ids.ts +1 -1
- package/src/stable_ids_for_module.ts +348 -0
- package/src/stable_ids_types.ts +1 -1
- package/src/sum.ts +1 -1
- package/src/usd_state.ts +1 -1
- package/src/usda_relationship_integrity.ts +66 -0
- package/src/usda_text.ts +18 -0
- package/src/usdz.ts +78 -0
- package/src/value_object.ts +95 -0
- package/src/wire.ts +19 -0
- package/src/wire_encode.ts +22 -0
- package/src/workspace_sharding.ts +52 -36
- package/src/workspace_sharing.ts +28 -4
- package/src/zod_introspection.ts +1 -1
- package/dart/test/cluster_attested_test.dart +0 -201
- package/src/compile_engine.ts +0 -595
- package/src/manifest.ts +0 -1476
- package/src/read_projection_composition.ts +0 -117
- package/src/read_projection_manifest.ts +0 -115
- package/src/relation_composition.ts +0 -286
- package/src/usd.ts +0 -1967
- package/src/usd_layers.ts +0 -1776
package/src/fields.ts
CHANGED
|
@@ -142,6 +142,74 @@ export interface EvidenceRefValue {
|
|
|
142
142
|
storageClass: "git" | "external";
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
/** One portable address inside Nomos custody. This is identity and routing, never authority. */
|
|
146
|
+
export interface NomosRefValue {
|
|
147
|
+
readonly workspace: string;
|
|
148
|
+
readonly type: string;
|
|
149
|
+
readonly id: string;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** An immutable version pin plus the stable series whose law names the current version. */
|
|
153
|
+
export interface VersionedNomosRefValue {
|
|
154
|
+
readonly pinned: NomosRefValue;
|
|
155
|
+
readonly series: NomosRefValue;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** A SHA-256 digest encoded as exactly 64 lowercase hexadecimal characters.
|
|
159
|
+
*
|
|
160
|
+
* This is nominal at author/client compile time and remains a plain string on the
|
|
161
|
+
* wire. Construct values through {@link sha256Digest}; never cast a string into it.
|
|
162
|
+
*/
|
|
163
|
+
export type Sha256Digest = string & { readonly __nomosSha256Digest: unique symbol };
|
|
164
|
+
|
|
165
|
+
/** Codegen marker for the semantic SHA-256 string type. The marker is deliberately
|
|
166
|
+
* non-enumerable: Zod remains the runtime/admission schema, while emitters recover
|
|
167
|
+
* the stronger client type without adding a parallel manifest representation. */
|
|
168
|
+
export const NOMOS_SHA256_DIGEST_BRAND: unique symbol = Symbol.for("nomos.sha256Digest");
|
|
169
|
+
|
|
170
|
+
const SHA256_DIGEST_RE = /^[0-9a-f]{64}$/;
|
|
171
|
+
|
|
172
|
+
/** Declare and validate a lowercase hexadecimal SHA-256 digest payload value. */
|
|
173
|
+
export function sha256Digest(): z.ZodType<Sha256Digest> {
|
|
174
|
+
const schema = z.string().regex(SHA256_DIGEST_RE, "expected a lowercase hexadecimal SHA-256 digest");
|
|
175
|
+
Object.defineProperty(schema, NOMOS_SHA256_DIGEST_BRAND, { value: true, enumerable: false });
|
|
176
|
+
return schema as unknown as z.ZodType<Sha256Digest>;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Recover the SHA-256 semantic marker through omission/null wrappers. */
|
|
180
|
+
export function isSha256DigestSchema(schema: z.ZodTypeAny): boolean {
|
|
181
|
+
let current: z.ZodTypeAny | undefined = schema;
|
|
182
|
+
for (let hops = 0; current !== undefined && hops < 16; hops++) {
|
|
183
|
+
if ((current as unknown as Record<symbol, unknown>)[NOMOS_SHA256_DIGEST_BRAND] === true) {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
const raw = current as unknown as {
|
|
187
|
+
_def?: { type?: unknown; innerType?: z.ZodTypeAny };
|
|
188
|
+
def?: { type?: unknown; innerType?: z.ZodTypeAny };
|
|
189
|
+
};
|
|
190
|
+
const def = raw._def ?? raw.def;
|
|
191
|
+
const kind = def?.type;
|
|
192
|
+
const name = (current as unknown as { _def?: { typeName?: string } })._def?.typeName;
|
|
193
|
+
if (kind !== "optional" && kind !== "default" && kind !== "nullable" &&
|
|
194
|
+
name !== "ZodOptional" && name !== "ZodDefault" && name !== "ZodNullable") {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
current = def?.innerType;
|
|
198
|
+
}
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export const nomosRefValueSchema: z.ZodType<NomosRefValue> = z.object({
|
|
203
|
+
workspace: z.string().min(1),
|
|
204
|
+
type: z.string().min(1),
|
|
205
|
+
id: z.string().min(1),
|
|
206
|
+
}).strict();
|
|
207
|
+
|
|
208
|
+
export const versionedNomosRefValueSchema: z.ZodType<VersionedNomosRefValue> = z.object({
|
|
209
|
+
pinned: nomosRefValueSchema,
|
|
210
|
+
series: nomosRefValueSchema,
|
|
211
|
+
}).strict();
|
|
212
|
+
|
|
145
213
|
/** The Zod schema for an authored `EvidenceRefValue` — the single source of truth. */
|
|
146
214
|
export const evidenceRefSchema: z.ZodType<EvidenceRefValue> = z.object({
|
|
147
215
|
hash: z.string(),
|
|
@@ -174,6 +242,8 @@ export type ValueObjectFieldDescriptor =
|
|
|
174
242
|
| {
|
|
175
243
|
readonly type: "string";
|
|
176
244
|
readonly brand?: string;
|
|
245
|
+
/** A framework semantic string whose generated client type validates its canonical spelling. */
|
|
246
|
+
readonly digest?: "sha256";
|
|
177
247
|
readonly idOf?: string;
|
|
178
248
|
/** Present only for an `idOf(recordContract(...))` target. Aggregate is the default. */
|
|
179
249
|
readonly idOfContract?: true;
|
|
@@ -285,6 +355,9 @@ export interface Field<T = unknown, K extends FieldKind = FieldKind> {
|
|
|
285
355
|
* absent the clear lane is ungated (and `law_check` WARNs if sibling writes ARE gated). Compose/codegen
|
|
286
356
|
* metadata — the REAL law is the generated directive's `requiresRelationGate` (hashed there, not here). */
|
|
287
357
|
readonly clearRequires?: string;
|
|
358
|
+
/** Static Zanzibar object paired with `clearRequires`. Non-workspace clear lanes must state this so
|
|
359
|
+
* generated directives cannot silently fall back to the wrong authorization namespace. */
|
|
360
|
+
readonly clearRequiresObject?: string;
|
|
288
361
|
/** For `t.ref` cross-workspace edges: the foreign workspace id, if any. */
|
|
289
362
|
readonly refWorkspace?: string;
|
|
290
363
|
/**
|
|
@@ -306,6 +379,21 @@ export interface Field<T = unknown, K extends FieldKind = FieldKind> {
|
|
|
306
379
|
* the target's concrete implementation. */
|
|
307
380
|
readonly targetIsContract?: boolean;
|
|
308
381
|
};
|
|
382
|
+
/** First-class atomic custody address. Kept in the reference kind so query/read routing remains a ref. */
|
|
383
|
+
readonly nomosRef?: {
|
|
384
|
+
readonly targetAggregateId: string;
|
|
385
|
+
readonly targetIsContract?: boolean;
|
|
386
|
+
};
|
|
387
|
+
/** Immutable publication pin + stable series, with the current pointer selected by compiled law. */
|
|
388
|
+
readonly versionedForeignRef?: {
|
|
389
|
+
readonly targetAggregateId: string;
|
|
390
|
+
readonly targetIsContract?: boolean;
|
|
391
|
+
readonly seriesAggregateId: string;
|
|
392
|
+
readonly seriesIsContract?: boolean;
|
|
393
|
+
readonly currentField: string;
|
|
394
|
+
/** Owning domain for cross-domain target, series and current-field law references. */
|
|
395
|
+
readonly targetDomain?: string;
|
|
396
|
+
};
|
|
309
397
|
/** For `t.ref` (the target) AND `t.hasMany` (the child type): the related aggregate's wire id. */
|
|
310
398
|
readonly refAggregateId?: string;
|
|
311
399
|
/** For `t.hasMany(Child).via(backref)`: the back-reference field NAME on the child. Virtual. */
|
|
@@ -399,6 +487,8 @@ export interface Field<T = unknown, K extends FieldKind = FieldKind> {
|
|
|
399
487
|
* Flag named `isMeta` — the `meta()` METHOD sets it, mirroring `encrypted()`/`optional()`.
|
|
400
488
|
*/
|
|
401
489
|
readonly isMeta?: true;
|
|
490
|
+
/** ZD6 — this encrypted field carries a blind-index equality token (the `blindIndexed()` METHOD sets it). */
|
|
491
|
+
readonly isBlindIndexed?: true;
|
|
402
492
|
/**
|
|
403
493
|
* THE INTEGRITY/CONFIDENTIALITY FACET (security_model.md — the endorse model). Set by the
|
|
404
494
|
* `authorityField`/`attributionField`/`standingField`/`secretField`/`publicField` wrappers (or the
|
|
@@ -418,6 +508,15 @@ export interface Field<T = unknown, K extends FieldKind = FieldKind> {
|
|
|
418
508
|
withFacet<Fa extends FieldFacet>(facet: Fa): Field<T, K> & { readonly facet: Fa };
|
|
419
509
|
/** Mark this field's value ENCRYPTED (sets {@link Field.isEncrypted}). Chainable + non-destructive. */
|
|
420
510
|
encrypted(): Field<T, K>;
|
|
511
|
+
/**
|
|
512
|
+
* ZD6 BLIND INDEX (encrypted_merge_model.md — the CipherSweet pattern): opt THIS encrypted field
|
|
513
|
+
* into EDGE-SERVED EQUALITY lookups. The sealing client attaches a scope-key-derived HMAC token
|
|
514
|
+
* (`bidx`) beside the ciphertext; key holders compute the same token query-side; the operator can
|
|
515
|
+
* observe equality/frequency among the opted-in rows but can never invert a token. Requires
|
|
516
|
+
* `.encrypted()` (aggregate() refuses otherwise) — a DELIBERATE, DECLARED equality leak, rotated
|
|
517
|
+
* with the scope epoch. Sets {@link Field.isBlindIndexed}.
|
|
518
|
+
*/
|
|
519
|
+
blindIndexed(): Field<T, K>;
|
|
421
520
|
/** Keep AUTHOR PROVENANCE beside this field's folded value (sets {@link Field.isAttributed}) —
|
|
422
521
|
* `<field>Provenance` accessors are generated on the read models. Lww-class scalars only. */
|
|
423
522
|
attributed(): Field<T, K>;
|
|
@@ -441,7 +540,7 @@ export interface Field<T = unknown, K extends FieldKind = FieldKind> {
|
|
|
441
540
|
withNull(semantic: NullSemantic, reason?: string): Field<T, K>;
|
|
442
541
|
/** INTERNAL (`t.clearable` calls this): mark this field EXPLICIT-CLEARABLE (sets {@link Field.isClearable}
|
|
443
542
|
* + optional {@link Field.clearRequires}). Threaded through merge/optional like every other flag. */
|
|
444
|
-
clearable(requires?: string): Field<T, K> & { readonly isClearable: true };
|
|
543
|
+
clearable(requires?: string, object?: string): Field<T, K> & { readonly isClearable: true };
|
|
445
544
|
/** Declare how values authored by an earlier field contract become this field's current
|
|
446
545
|
* business value. With no function, the compiler proves identity widening. With a function,
|
|
447
546
|
* the source must be finite (enum/boolean/literal union), so Nomos can exhaustively compile
|
|
@@ -458,6 +557,14 @@ function makeField<T, K extends FieldKind = FieldKind>(
|
|
|
458
557
|
extra: {
|
|
459
558
|
refWorkspace?: string;
|
|
460
559
|
foreignRef?: { workspaceField: string; targetAggregateId: string; targetDomain?: string; targetIsContract?: boolean };
|
|
560
|
+
nomosRef?: { targetAggregateId: string; targetIsContract?: boolean };
|
|
561
|
+
versionedForeignRef?: {
|
|
562
|
+
targetAggregateId: string;
|
|
563
|
+
targetIsContract?: boolean;
|
|
564
|
+
seriesAggregateId: string;
|
|
565
|
+
seriesIsContract?: boolean;
|
|
566
|
+
currentField: string;
|
|
567
|
+
};
|
|
461
568
|
refAggregateId?: string;
|
|
462
569
|
/** LAZY target resolution — `t.ref(() => Agg)` / `t.hasMany(() => Agg)`. Breaks a BIDIRECTIONAL
|
|
463
570
|
* declaration cycle (a Parent's `hasMany(Child)` ⇄ a Child's `ref(Parent)`): the thunk is stored and
|
|
@@ -473,6 +580,7 @@ function makeField<T, K extends FieldKind = FieldKind>(
|
|
|
473
580
|
valueObject?: ValueObjectFieldMeta;
|
|
474
581
|
mapValueVo?: ValueObjectFieldMeta;
|
|
475
582
|
encrypted?: true;
|
|
583
|
+
blindIndexed?: true;
|
|
476
584
|
attributed?: true;
|
|
477
585
|
meta?: true;
|
|
478
586
|
facet?: FieldFacet;
|
|
@@ -480,6 +588,7 @@ function makeField<T, K extends FieldKind = FieldKind>(
|
|
|
480
588
|
nullReason?: string;
|
|
481
589
|
isClearable?: true;
|
|
482
590
|
clearRequires?: string;
|
|
591
|
+
clearRequiresObject?: string;
|
|
483
592
|
evolutions?: readonly FieldEvolutionDeclaration[];
|
|
484
593
|
} = {},
|
|
485
594
|
): Field<T, K> {
|
|
@@ -493,12 +602,15 @@ function makeField<T, K extends FieldKind = FieldKind>(
|
|
|
493
602
|
...(extra.refWorkspace !== undefined ? { refWorkspace: extra.refWorkspace } : {}),
|
|
494
603
|
...(extra.refAggregateId !== undefined ? { refAggregateId: extra.refAggregateId } : {}),
|
|
495
604
|
...(extra.foreignRef !== undefined ? { foreignRef: extra.foreignRef } : {}),
|
|
605
|
+
...(extra.nomosRef !== undefined ? { nomosRef: extra.nomosRef } : {}),
|
|
606
|
+
...(extra.versionedForeignRef !== undefined ? { versionedForeignRef: extra.versionedForeignRef } : {}),
|
|
496
607
|
...(extra.viaField !== undefined ? { viaField: extra.viaField } : {}),
|
|
497
608
|
...(extra.mapValueKind !== undefined ? { mapValueKind: extra.mapValueKind } : {}),
|
|
498
609
|
...(extra.jsonShape !== undefined ? { jsonShape: extra.jsonShape } : {}),
|
|
499
610
|
...(extra.valueObject !== undefined ? { valueObject: extra.valueObject } : {}),
|
|
500
611
|
...(extra.mapValueVo !== undefined ? { mapValueVo: extra.mapValueVo } : {}),
|
|
501
612
|
...(extra.encrypted ? { isEncrypted: true as const } : {}),
|
|
613
|
+
...(extra.blindIndexed ? { isBlindIndexed: true as const } : {}),
|
|
502
614
|
...(extra.attributed ? { isAttributed: true as const } : {}),
|
|
503
615
|
...(extra.meta ? { isMeta: true as const } : {}),
|
|
504
616
|
...(extra.fromBirth ? { isFromBirth: true as const } : {}),
|
|
@@ -506,6 +618,7 @@ function makeField<T, K extends FieldKind = FieldKind>(
|
|
|
506
618
|
...(extra.nullReason !== undefined ? { nullReason: extra.nullReason } : {}),
|
|
507
619
|
...(extra.isClearable ? { isClearable: true as const } : {}),
|
|
508
620
|
...(extra.clearRequires !== undefined ? { clearRequires: extra.clearRequires } : {}),
|
|
621
|
+
...(extra.clearRequiresObject !== undefined ? { clearRequiresObject: extra.clearRequiresObject } : {}),
|
|
509
622
|
...(extra.evolutions !== undefined && extra.evolutions.length > 0
|
|
510
623
|
? { evolutions: extra.evolutions }
|
|
511
624
|
: {}),
|
|
@@ -549,11 +662,12 @@ function makeField<T, K extends FieldKind = FieldKind>(
|
|
|
549
662
|
...(reason !== undefined ? { nullReason: reason } : {}),
|
|
550
663
|
});
|
|
551
664
|
},
|
|
552
|
-
clearable(requires?: string): Field<T, K> & { readonly isClearable: true } {
|
|
665
|
+
clearable(requires?: string, object?: string): Field<T, K> & { readonly isClearable: true } {
|
|
553
666
|
return makeField<T, K>(kind, zod, driver, {
|
|
554
667
|
...extra,
|
|
555
668
|
isClearable: true,
|
|
556
669
|
...(requires !== undefined ? { clearRequires: requires } : {}),
|
|
670
|
+
...(object !== undefined ? { clearRequiresObject: object } : {}),
|
|
557
671
|
}) as Field<T, K> & { readonly isClearable: true };
|
|
558
672
|
},
|
|
559
673
|
evolvesFrom<From>(source: Field<From>, project?: (value: From) => T): Field<T, K> {
|
|
@@ -600,6 +714,11 @@ function makeField<T, K extends FieldKind = FieldKind>(
|
|
|
600
714
|
// `nomosEncryption` era key, built by aggregate()/manifest, is what carries the fact.
|
|
601
715
|
return makeField<T, K>(kind, zod, driver, { ...extra, encrypted: true });
|
|
602
716
|
},
|
|
717
|
+
blindIndexed(): Field<T, K> {
|
|
718
|
+
// ZD6 — carried through merge/optional like encrypted(); aggregate() enforces the
|
|
719
|
+
// encrypted-only rule (a plaintext field needs no blind index — index it directly).
|
|
720
|
+
return makeField<T, K>(kind, zod, driver, { ...extra, blindIndexed: true });
|
|
721
|
+
},
|
|
603
722
|
attributed(): Field<T, K> {
|
|
604
723
|
// Carry the flag through merge/optional (extra is threaded), so `.merge(Lww).attributed()`
|
|
605
724
|
// and `.attributed().merge(Lww)` agree. aggregate() validates Lww-class eligibility and
|
|
@@ -771,6 +890,11 @@ export const t = {
|
|
|
771
890
|
}
|
|
772
891
|
return makeField("string", schema, FirstWriterWins);
|
|
773
892
|
},
|
|
893
|
+
/** A SHA-256 digest with one canonical wire spelling: 64 lowercase hex characters.
|
|
894
|
+
* Generated clients surface [Sha256Digest], not a bare string. */
|
|
895
|
+
sha256Digest(): Field<Sha256Digest, "string"> {
|
|
896
|
+
return makeField<Sha256Digest, "string">("string", sha256Digest(), FirstWriterWins);
|
|
897
|
+
},
|
|
774
898
|
int(rules: IntRules = {}): Field<number, "int"> {
|
|
775
899
|
for (const [name, value] of [["minimum", rules.minimum], ["maximum", rules.maximum]] as const) {
|
|
776
900
|
if (value !== undefined && !Number.isSafeInteger(value)) {
|
|
@@ -904,10 +1028,13 @@ export const t = {
|
|
|
904
1028
|
*/
|
|
905
1029
|
clearable<T, K extends FieldKind>(
|
|
906
1030
|
field: Field<T, K>,
|
|
907
|
-
opts?: { requires?: string },
|
|
1031
|
+
opts?: { requires?: string; object?: string },
|
|
908
1032
|
): Field<T, K> & { readonly isClearable: true } {
|
|
909
1033
|
assertNullEligible(field, "clearable");
|
|
910
|
-
|
|
1034
|
+
if (opts?.object !== undefined && opts.requires === undefined) {
|
|
1035
|
+
throw new Error("t.clearable(..., { object }) also requires an authorization relation");
|
|
1036
|
+
}
|
|
1037
|
+
return field.clearable(opts?.requires, opts?.object);
|
|
911
1038
|
},
|
|
912
1039
|
/**
|
|
913
1040
|
* `t.nullable(field, { reason })` — null IS a real domain value on this field (`architecture/
|
|
@@ -977,6 +1104,58 @@ export const t = {
|
|
|
977
1104
|
},
|
|
978
1105
|
});
|
|
979
1106
|
},
|
|
1107
|
+
/** A first-class, atomic portable custody address. Unlike legacy `foreignRef`, there is no sibling
|
|
1108
|
+
* workspace field which can tear under merge; workspace, aggregate type and id are one Lww value. */
|
|
1109
|
+
nomosRef(
|
|
1110
|
+
target: AggregateHandle<string, Record<string, Field>> | ContractHandle,
|
|
1111
|
+
): Field<NomosRefValue, "ref"> {
|
|
1112
|
+
const isContract = (target as { __isContract?: unknown }).__isContract === true;
|
|
1113
|
+
return makeField<NomosRefValue, "ref">("ref", nomosRefValueSchema, FirstWriterWins, {
|
|
1114
|
+
refAggregateId: target.id,
|
|
1115
|
+
nomosRef: {
|
|
1116
|
+
targetAggregateId: target.id,
|
|
1117
|
+
...(isContract ? { targetIsContract: true } : {}),
|
|
1118
|
+
},
|
|
1119
|
+
});
|
|
1120
|
+
},
|
|
1121
|
+
/** A version pin whose freshness is checked against one law-declared current pointer on a stable series.
|
|
1122
|
+
* The declaration fails immediately if `series.current` is not an atomic ref to `target`. */
|
|
1123
|
+
versionedForeignRef(opts: {
|
|
1124
|
+
target: AggregateHandle<string, Record<string, Field>> | ContractHandle;
|
|
1125
|
+
series: AggregateHandle<string, Record<string, Field>> | ContractHandle;
|
|
1126
|
+
current: string;
|
|
1127
|
+
domain?: string;
|
|
1128
|
+
}): Field<VersionedNomosRefValue, "ref"> {
|
|
1129
|
+
const current = (opts.series.fields as Record<string, Field>)[opts.current];
|
|
1130
|
+
if (current === undefined) {
|
|
1131
|
+
throw new Error(
|
|
1132
|
+
`t.versionedForeignRef: series '${opts.series.id}' has no current field '${opts.current}'`,
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
if (current.nomosRef?.targetAggregateId !== opts.target.id) {
|
|
1136
|
+
throw new Error(
|
|
1137
|
+
`t.versionedForeignRef: '${opts.series.id}.${opts.current}' must be ` +
|
|
1138
|
+
`t.nomosRef(${opts.target.id}); it is the O(1) current pointer used for freshness`,
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
const targetIsContract = (opts.target as { __isContract?: unknown }).__isContract === true;
|
|
1142
|
+
const seriesIsContract = (opts.series as { __isContract?: unknown }).__isContract === true;
|
|
1143
|
+
return makeField<VersionedNomosRefValue, "ref">(
|
|
1144
|
+
"ref",
|
|
1145
|
+
versionedNomosRefValueSchema,
|
|
1146
|
+
FirstWriterWins,
|
|
1147
|
+
{
|
|
1148
|
+
versionedForeignRef: {
|
|
1149
|
+
targetAggregateId: opts.target.id,
|
|
1150
|
+
...(targetIsContract ? { targetIsContract: true } : {}),
|
|
1151
|
+
seriesAggregateId: opts.series.id,
|
|
1152
|
+
...(seriesIsContract ? { seriesIsContract: true } : {}),
|
|
1153
|
+
currentField: opts.current,
|
|
1154
|
+
...(opts.domain === undefined ? {} : { targetDomain: opts.domain }),
|
|
1155
|
+
},
|
|
1156
|
+
},
|
|
1157
|
+
);
|
|
1158
|
+
},
|
|
980
1159
|
/**
|
|
981
1160
|
* A 1:N INVERSE relation — the read side of a `t.ref` on the child (`aggregate_lifecycle_and_relations.md`).
|
|
982
1161
|
* `t.hasMany(Child).via("parent")` on a Parent declares "the Children whose `parent` ref points here".
|
package/src/flow_gate.ts
CHANGED
|
@@ -15,7 +15,11 @@
|
|
|
15
15
|
// Integrity (who may WRITE) is gated natively at admission by `.requires(relation)`;
|
|
16
16
|
// the integrity-flow dual + rule 4 (secret-correlated refusal) + `declassify` + the
|
|
17
17
|
// `nomosFlowGate` era key are the next slices.
|
|
18
|
-
import type {
|
|
18
|
+
import type { DomainModule } from "./codegen_dart.js";
|
|
19
|
+
import { finishCount } from "./count.js";
|
|
20
|
+
import { existsAsCount } from "./exists.js";
|
|
21
|
+
import { finishExtremum } from "./extremum.js";
|
|
22
|
+
import { finishSum } from "./sum.js";
|
|
19
23
|
import type { CanonicalPred } from "./predicate.js";
|
|
20
24
|
|
|
21
25
|
/** A read projection, read structurally — every read kind (query / count / sum / extremum /
|
|
@@ -32,6 +36,26 @@ type Projection = {
|
|
|
32
36
|
readonly where?: CanonicalPred;
|
|
33
37
|
};
|
|
34
38
|
|
|
39
|
+
/** The finite facts the information-flow proof consumes. A canonical manifest satisfies this
|
|
40
|
+
* structurally for legacy callers; the production compiler derives it straight from typed declarations. */
|
|
41
|
+
export interface FlowLaw {
|
|
42
|
+
readonly aggregates: readonly {
|
|
43
|
+
readonly id: string;
|
|
44
|
+
readonly encryptedFields?: readonly string[];
|
|
45
|
+
readonly requiresRead?: string;
|
|
46
|
+
}[];
|
|
47
|
+
readonly directives: readonly {
|
|
48
|
+
readonly id: string;
|
|
49
|
+
readonly capturedReads?: readonly { readonly queryId: string; readonly returns: string }[];
|
|
50
|
+
}[];
|
|
51
|
+
readonly queries?: readonly Projection[];
|
|
52
|
+
readonly counts?: readonly Projection[];
|
|
53
|
+
readonly sums?: readonly Projection[];
|
|
54
|
+
readonly mins?: readonly Projection[];
|
|
55
|
+
readonly maxes?: readonly Projection[];
|
|
56
|
+
readonly orderedReads?: readonly Projection[];
|
|
57
|
+
}
|
|
58
|
+
|
|
35
59
|
/** The raw module bits rule 4 needs that the canonical manifest strips (the executable
|
|
36
60
|
* invariant `.reads` + the declassify waivers). Read structurally — no coupling to DomainModule. */
|
|
37
61
|
type FlowModule = {
|
|
@@ -82,7 +106,7 @@ function predFields(p: CanonicalPred | undefined): string[] {
|
|
|
82
106
|
* {@link FlowGateError} on the first violation (a typed compile error). Pure over
|
|
83
107
|
* manifest IR — no side effects, no AST.
|
|
84
108
|
*/
|
|
85
|
-
export function assertFlowGate(m:
|
|
109
|
+
export function assertFlowGate(m: FlowLaw, mod?: FlowModule): void {
|
|
86
110
|
// ── RULE 1 — NO ENCRYPT-THEN-INDEX ──────────────────────────────────────────
|
|
87
111
|
// A field that is .encrypted() (operator-blind ciphertext) MUST NOT appear in any
|
|
88
112
|
// read's group-by / key / value / where over its own type: grouping/indexing/
|
|
@@ -233,3 +257,52 @@ export function assertFlowGate(m: CanonicalManifest, mod?: FlowModule): void {
|
|
|
233
257
|
}
|
|
234
258
|
}
|
|
235
259
|
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Production compiler entry: prove information flow directly from the live typed declarations.
|
|
263
|
+
* This deliberately does not build, serialize or hash the retired canonical JSON manifest.
|
|
264
|
+
*/
|
|
265
|
+
export function assertFlowGateModule(mod: DomainModule): void {
|
|
266
|
+
const counts = [
|
|
267
|
+
...(mod.counts ?? []).map(finishCount),
|
|
268
|
+
...(mod.exists_ ?? []).map(existsAsCount),
|
|
269
|
+
];
|
|
270
|
+
const facts: FlowLaw = {
|
|
271
|
+
aggregates: mod.aggregates.map((aggregate) => ({
|
|
272
|
+
id: aggregate.id,
|
|
273
|
+
...(aggregate.encryptedFields === undefined ? {} : { encryptedFields: aggregate.encryptedFields }),
|
|
274
|
+
...(aggregate.visibility === undefined ? {} : { requiresRead: aggregate.visibility.requires }),
|
|
275
|
+
})),
|
|
276
|
+
directives: mod.directives.map((directive) => ({
|
|
277
|
+
id: directive.id,
|
|
278
|
+
...((directive.declaredQueryReads ?? []).length === 0
|
|
279
|
+
? {}
|
|
280
|
+
: {
|
|
281
|
+
capturedReads: (directive.declaredQueryReads ?? []).map((read) => ({
|
|
282
|
+
queryId: read.id,
|
|
283
|
+
returns: read.returns,
|
|
284
|
+
})),
|
|
285
|
+
}),
|
|
286
|
+
})),
|
|
287
|
+
...(mod.queries === undefined ? {} : { queries: mod.queries }),
|
|
288
|
+
...(counts.length === 0 ? {} : { counts }),
|
|
289
|
+
...(mod.sums === undefined
|
|
290
|
+
? {}
|
|
291
|
+
: {
|
|
292
|
+
sums: mod.sums.map((raw) => {
|
|
293
|
+
const sum = finishSum(raw);
|
|
294
|
+
return {
|
|
295
|
+
id: sum.id,
|
|
296
|
+
of: sum.of,
|
|
297
|
+
valueField: sum.sumField,
|
|
298
|
+
...(sum.by === undefined ? {} : { by: sum.by }),
|
|
299
|
+
...(sum.where === undefined ? {} : { where: sum.where }),
|
|
300
|
+
};
|
|
301
|
+
}),
|
|
302
|
+
}),
|
|
303
|
+
...(mod.mins === undefined ? {} : { mins: mod.mins.map(finishExtremum) }),
|
|
304
|
+
...(mod.maxes === undefined ? {} : { maxes: mod.maxes.map(finishExtremum) }),
|
|
305
|
+
...(mod.orderedReads === undefined ? {} : { orderedReads: mod.orderedReads }),
|
|
306
|
+
};
|
|
307
|
+
assertFlowGate(facts, mod);
|
|
308
|
+
}
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
seedBirthAttestation,
|
|
39
39
|
seedReceivingGroup,
|
|
40
40
|
seedReceivingGroupPolicy,
|
|
41
|
+
sharedByObject,
|
|
41
42
|
sharedByWorkspace,
|
|
42
43
|
} from "./home.js";
|
|
43
44
|
import { RelationTuple, relationTuplesByObject } from "./relations.js";
|
|
@@ -86,6 +87,7 @@ export const __nomos_SharedWithMe = aggregate(SharedWithMe);
|
|
|
86
87
|
export const __nomos_recordShare = directive(recordShare);
|
|
87
88
|
export const __nomos_pruneShare = directive(pruneShare);
|
|
88
89
|
export const __nomos_sharedByWorkspace = query(sharedByWorkspace);
|
|
90
|
+
export const __nomos_sharedByObject = query(sharedByObject);
|
|
89
91
|
|
|
90
92
|
// Warrant, provenance, signer seating, recovery and verifier state.
|
|
91
93
|
export const __nomos_GenesisWarrant = aggregate(GenesisWarrant);
|
|
@@ -13,7 +13,7 @@ import { Lww } from "../drivers.js";
|
|
|
13
13
|
import { set, withMarker } from "../ops.js";
|
|
14
14
|
import { query } from "../query.js";
|
|
15
15
|
import { GENESIS_WARRANT_ID } from "./genesis.js";
|
|
16
|
-
import { refAs, workspaceInvariant } from "./workspace_invariant.js";
|
|
16
|
+
import { readRef, readSet, refAs, workspaceInvariant } from "./workspace_invariant.js";
|
|
17
17
|
|
|
18
18
|
const epochMs = z.number().int().nonnegative();
|
|
19
19
|
|
|
@@ -135,7 +135,7 @@ export const seedAuthProvider = directive("seedAuthProvider")
|
|
|
135
135
|
|
|
136
136
|
export const authProviderSeedBeforeWarrant = workspaceInvariant("AuthProviderSeedBeforeWarrant")
|
|
137
137
|
.on("seedAuthProvider")
|
|
138
|
-
.reads((
|
|
138
|
+
.reads(readSet(readRef("warrant", "GenesisWarrant").at(GENESIS_WARRANT_ID)))
|
|
139
139
|
.assert(({ warrant }) => {
|
|
140
140
|
// Missing invariant refs are represented as an empty snapshot by the executor, not necessarily null.
|
|
141
141
|
const principal = (warrant as { principal?: unknown } | null | undefined)?.principal;
|
|
@@ -27,7 +27,7 @@ import { z } from "zod";
|
|
|
27
27
|
import { instance } from "../aggregate.js";
|
|
28
28
|
import { directive } from "../directive.js";
|
|
29
29
|
import { set, type PlannedOp } from "../ops.js";
|
|
30
|
-
import { refAs, workspaceInvariant } from "./workspace_invariant.js";
|
|
30
|
+
import { readRef, readSet, refAs, workspaceInvariant } from "./workspace_invariant.js";
|
|
31
31
|
import {
|
|
32
32
|
BirthPolicy,
|
|
33
33
|
BirthGrant,
|
|
@@ -149,9 +149,7 @@ export const reclaimChildAsAdmin = directive("reclaimChildAsAdmin")
|
|
|
149
149
|
/** Reclaim is two-phase; a child must first be explicitly withdrawn. */
|
|
150
150
|
export const reclaimChildRequiresRetired = workspaceInvariant("ReclaimChildRequiresRetired")
|
|
151
151
|
.on("reclaimChild")
|
|
152
|
-
.reads((
|
|
153
|
-
refAs("record", "NomosChildLifecycle", "pre:" + childLifecycleId(String(ctx.intent.child ?? ""))),
|
|
154
|
-
])
|
|
152
|
+
.reads(readSet(readRef("record", NomosChildLifecycle).pre().fromPayload("child", { prefix: "nomos-child-lifecycle:" })))
|
|
155
153
|
.assert(({ record }, ctx) =>
|
|
156
154
|
(record as { status?: string; principal?: string } | undefined)?.status === "retired"
|
|
157
155
|
&& sameLifecyclePrincipal((record as { principal?: string }).principal, ctx?.principal)
|
|
@@ -162,9 +160,7 @@ export const reclaimChildRequiresRetired = workspaceInvariant("ReclaimChildRequi
|
|
|
162
160
|
/** Operator recovery has the same two-phase law, without depending on the broken creator credential. */
|
|
163
161
|
export const reclaimChildAsAdminRequiresRetired = workspaceInvariant("ReclaimChildAsAdminRequiresRetired")
|
|
164
162
|
.on("reclaimChildAsAdmin")
|
|
165
|
-
.reads((
|
|
166
|
-
refAs("record", "NomosChildLifecycle", "pre:" + childLifecycleId(String(ctx.intent.child ?? ""))),
|
|
167
|
-
])
|
|
163
|
+
.reads(readSet(readRef("record", NomosChildLifecycle).pre().fromPayload("child", { prefix: "nomos-child-lifecycle:" })))
|
|
168
164
|
.assert(({ record }) =>
|
|
169
165
|
(record as { status?: string } | undefined)?.status === "retired"
|
|
170
166
|
? { accept: true }
|
|
@@ -174,9 +170,7 @@ export const reclaimChildAsAdminRequiresRetired = workspaceInvariant("ReclaimChi
|
|
|
174
170
|
/** Reclaimed is permanent: retirement cannot move a child back out of the terminal state. */
|
|
175
171
|
export const retireChildNotReclaimed = workspaceInvariant("RetireChildNotReclaimed")
|
|
176
172
|
.on("retireChild")
|
|
177
|
-
.reads((
|
|
178
|
-
refAs("record", "NomosChildLifecycle", "pre:" + childLifecycleId(String(ctx.intent.child ?? ""))),
|
|
179
|
-
])
|
|
173
|
+
.reads(readSet(readRef("record", NomosChildLifecycle).pre().fromPayload("child", { prefix: "nomos-child-lifecycle:" })))
|
|
180
174
|
.assert(({ record }) =>
|
|
181
175
|
(record as { status?: string } | undefined)?.status === "reclaimed"
|
|
182
176
|
? { reject: "reclaimed-child-is-terminal" }
|
|
@@ -186,9 +180,7 @@ export const retireChildNotReclaimed = workspaceInvariant("RetireChildNotReclaim
|
|
|
186
180
|
/** Reclaimed is terminal for operator recovery too. */
|
|
187
181
|
export const retireChildAsAdminNotReclaimed = workspaceInvariant("RetireChildAsAdminNotReclaimed")
|
|
188
182
|
.on("retireChildAsAdmin")
|
|
189
|
-
.reads((
|
|
190
|
-
refAs("record", "NomosChildLifecycle", "pre:" + childLifecycleId(String(ctx.intent.child ?? ""))),
|
|
191
|
-
])
|
|
183
|
+
.reads(readSet(readRef("record", NomosChildLifecycle).pre().fromPayload("child", { prefix: "nomos-child-lifecycle:" })))
|
|
192
184
|
.assert(({ record }) =>
|
|
193
185
|
(record as { status?: string } | undefined)?.status === "reclaimed"
|
|
194
186
|
? { reject: "reclaimed-child-is-terminal" }
|
|
@@ -198,9 +190,7 @@ export const retireChildAsAdminNotReclaimed = workspaceInvariant("RetireChildAsA
|
|
|
198
190
|
/** A lifecycle cannot mint authority for arbitrary custody: retirement must name its exact birth witness. */
|
|
199
191
|
export const retireChildRequiresBirth = workspaceInvariant("RetireChildRequiresBirth")
|
|
200
192
|
.on("retireChild")
|
|
201
|
-
.reads((
|
|
202
|
-
refAs("birth", "NomosBirthRecord", "pre:" + birthRecordId(String(ctx.intent.child ?? ""))),
|
|
203
|
-
])
|
|
193
|
+
.reads(readSet(readRef("birth", NomosBirthRecord).pre().fromPayload("child", { prefix: "nomos-birth-record:" })))
|
|
204
194
|
.assert(({ birth }, ctx) => {
|
|
205
195
|
const record = birth as { child?: string; principal?: string } | undefined;
|
|
206
196
|
const intent = ctx?.intent as { child?: string; principal?: string } | undefined;
|
|
@@ -214,9 +204,7 @@ export const retireChildRequiresBirth = workspaceInvariant("RetireChildRequiresB
|
|
|
214
204
|
/** Admin recovery may act for the recorded creator, but still cannot invent or retarget custody. */
|
|
215
205
|
export const retireChildAsAdminRequiresBirth = workspaceInvariant("RetireChildAsAdminRequiresBirth")
|
|
216
206
|
.on("retireChildAsAdmin")
|
|
217
|
-
.reads((
|
|
218
|
-
refAs("birth", "NomosBirthRecord", "pre:" + birthRecordId(String(ctx.intent.child ?? ""))),
|
|
219
|
-
])
|
|
207
|
+
.reads(readSet(readRef("birth", NomosBirthRecord).pre().fromPayload("child", { prefix: "nomos-birth-record:" })))
|
|
220
208
|
.assert(({ birth }, ctx) => {
|
|
221
209
|
const record = birth as { child?: string; principal?: string } | undefined;
|
|
222
210
|
const intent = ctx?.intent as { child?: string; principal?: string } | undefined;
|
|
@@ -22,8 +22,16 @@ const correctionRecord = aggregate(BUSINESS_CORRECTION_AGGREGATE, {
|
|
|
22
22
|
correctedAt: t.string().merge(Lww),
|
|
23
23
|
}).public();
|
|
24
24
|
|
|
25
|
+
export const businessCorrectionRecord = {
|
|
26
|
+
...correctionRecord,
|
|
27
|
+
businessBirthInternal: true as const,
|
|
28
|
+
};
|
|
29
|
+
|
|
25
30
|
const correction = directive(BUSINESS_CORRECTION_DIRECTIVE)
|
|
26
|
-
.
|
|
31
|
+
// The directive's typed aggregate handle is followed by composeDomainModule. It must be the same
|
|
32
|
+
// framework-private singleton that is exported below; pointing at the unbranded construction
|
|
33
|
+
// intermediate lets that handle overwrite the branded one during typed target closure.
|
|
34
|
+
.ensures(businessCorrectionRecord)
|
|
27
35
|
.payload(z.object({
|
|
28
36
|
recordId: z.string().min(1),
|
|
29
37
|
target: z.string().min(1),
|
|
@@ -45,10 +53,6 @@ const correction = directive(BUSINESS_CORRECTION_DIRECTIVE)
|
|
|
45
53
|
];
|
|
46
54
|
});
|
|
47
55
|
|
|
48
|
-
export const businessCorrectionRecord = {
|
|
49
|
-
...correctionRecord,
|
|
50
|
-
businessBirthInternal: true as const,
|
|
51
|
-
};
|
|
52
56
|
export const recordBusinessCorrection = {
|
|
53
57
|
...correction,
|
|
54
58
|
businessBirthInternal: true as const,
|