@githolon/dsl 0.102.4 → 0.103.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/compile_package.mjs +43 -2
- package/compile_proof.d.mts +47 -0
- package/compile_proof.mjs +343 -0
- package/dart/CHANGELOG.md +7 -0
- package/dart/lib/src/dev_cloud.dart +8 -8
- package/dart/lib/src/nms_generated_runtime.dart +2 -0
- package/dart/lib/src/runner/node-entry.mjs +5504 -1236
- package/dart/lib/src/runner/offline-install.mjs +4578 -528
- package/dart/lib/src/test_harness.dart +206 -152
- package/dart/lib/src/webview_bridge.dart +196 -89
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/acquire_session_credentials_test.dart +4 -3
- package/dart/test/adv_realm_interference_test.dart +7 -6
- package/dart/test/cloud_bound_topology_test.dart +3 -3
- package/dart/test/cluster_births_test.dart +4 -3
- package/dart/test/cluster_device_keyed_acceptance_test.dart +10 -16
- package/dart/test/cluster_local_autosync_test.dart +3 -2
- package/dart/test/cluster_scale_test.dart +3 -2
- package/dart/test/dlq_session_surface_test.dart +7 -6
- package/dart/test/first_app_gauntlet_test.dart +14 -21
- package/dart/test/fixtures/wedge/fake-install.mjs +1 -1
- package/dart/test/four_layer_nested_topology_test.dart +4 -4
- package/dart/test/harness_phase_timeout_test.dart +2 -2
- package/dart/test/local_sync_inert_test.dart +1 -1
- package/dart/test/parent_signer_recovery_test.dart +2 -2
- package/dart/test/sealing_client_test.dart +4 -3
- package/dart/test/session_bound_test.dart +8 -7
- package/dart/test/test_harness_test.dart +13 -12
- package/dart/test/vo_read_typing_test.dart +4 -3
- package/dart/test/watch_after_offer_stall_test.dart +4 -3
- package/dart/test/webview_bridge_test.dart +102 -5
- package/dart_types/CHANGELOG.md +5 -0
- package/dart_types/lib/nomos_types.dart +9 -0
- package/dart_types/lib/src/nomos_ref.dart +20 -0
- package/dart_types/lib/src/optional_ref.dart +85 -0
- package/dart_types/lib/src/sha256_digest.dart +41 -0
- package/dart_types/lib/src/versioned_nomos_ref.dart +81 -0
- package/dart_types/pubspec.yaml +1 -1
- package/dart_types/test/optional_ref_test.dart +44 -0
- package/dart_types/test/sha256_digest_test.dart +18 -0
- package/package.json +9 -5
- package/runtime/generated-application-host.js +5365 -1105
- package/src/aggregate.ts +18 -0
- package/src/application_composition.ts +6 -32
- package/src/attested_core.ts +6 -3
- package/src/birth_meter.ts +1 -0
- package/src/birth_meter_ensure.ts +28 -2
- package/src/births.ts +1 -1
- package/src/build_package.ts +162 -538
- package/src/business_birth.ts +19 -3
- package/src/canonical_json.ts +21 -0
- package/src/check_package_main.ts +76 -35
- package/src/clear_directive.ts +19 -8
- package/src/codegen_dart.ts +305 -22
- package/src/codegen_dart_application.ts +844 -292
- package/src/codegen_dart_client.ts +1084 -407
- package/src/codegen_dart_scenario.ts +12 -12
- package/src/codegen_proof.ts +169 -39
- package/src/codegen_provider_dart.ts +17 -1
- package/src/codegen_ts.ts +298 -106
- package/src/compat_manifest.ts +2 -2
- package/src/compat_package_main.ts +8 -12
- package/src/compile_package_main.ts +1866 -597
- package/src/compose.ts +1 -1
- package/src/directive.ts +208 -15
- package/src/engine_entry.ts +165 -111
- package/src/evolve_preview.ts +43 -81
- package/src/extremum.ts +1 -1
- package/src/fields.ts +183 -4
- package/src/flow_gate.ts +75 -2
- package/src/framework/application_home.ts +2 -0
- package/src/framework/auth_provider_law.ts +2 -2
- package/src/framework/birth_meter.ts +7 -19
- package/src/framework/bootstrap.ts +0 -2
- package/src/framework/business_corrections.ts +9 -5
- package/src/framework/domain_lifecycle.ts +15 -29
- package/src/framework/encryption_scope.ts +2 -1
- package/src/framework/framework_aggregates.ts +2 -0
- package/src/framework/genesis.ts +16 -14
- package/src/framework/home.ts +20 -14
- package/src/framework/owned_custody.ts +9 -3
- package/src/framework/platform_admin.ts +3 -3
- package/src/framework/relations.ts +21 -13
- package/src/framework/signing_identity.ts +45 -40
- package/src/framework/workspace_invariant.ts +245 -0
- package/src/framework/workspaces.ts +23 -29
- package/src/index.ts +36 -16
- package/src/intent.ts +178 -6
- package/src/law_check.ts +32 -11
- package/src/law_graph.ts +2902 -0
- package/src/law_graph_usda.ts +1799 -0
- package/src/law_identity.ts +435 -0
- package/src/law_value_type.ts +230 -0
- package/src/lsp_server.ts +61 -31
- package/src/ops.ts +7 -0
- package/src/package_config.ts +141 -0
- package/src/package_dependencies.ts +108 -0
- package/src/package_dependency_usage.ts +150 -0
- package/src/package_source_imports.ts +71 -0
- package/src/plan_source_slice.ts +335 -0
- package/src/point_guard.ts +192 -0
- package/src/precondition.ts +44 -9
- package/src/read_projection_namespace.ts +1 -1
- package/src/runtime_zod.ts +44 -0
- package/src/secondary_ensure.ts +67 -14
- package/src/shape_compat.ts +1 -1
- package/src/stable_id_identity_bridge.ts +149 -0
- package/src/stable_id_lockfile.ts +278 -0
- package/src/stable_ids.ts +7 -2
- package/src/stable_ids_for_module.ts +348 -0
- package/src/stable_ids_types.ts +1 -1
- package/src/sum.ts +1 -1
- package/src/usd_state.ts +1 -1
- package/src/usda_relationship_integrity.ts +66 -0
- package/src/usda_text.ts +18 -0
- package/src/usdz.ts +78 -0
- package/src/value_object.ts +95 -0
- package/src/wire.ts +19 -0
- package/src/wire_encode.ts +22 -0
- package/src/workspace_sharding.ts +52 -36
- package/src/workspace_sharing.ts +28 -4
- package/src/zod_introspection.ts +1 -1
- package/dart/test/cluster_attested_test.dart +0 -201
- package/src/compile_engine.ts +0 -595
- package/src/manifest.ts +0 -1476
- package/src/read_projection_composition.ts +0 -117
- package/src/read_projection_manifest.ts +0 -115
- package/src/relation_composition.ts +0 -286
- package/src/usd.ts +0 -1967
- package/src/usd_layers.ts +0 -1776
package/src/evolve_preview.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* THE EVOLVE PREVIEW — `githolon check --against <prior>` (architecture/author_time_gate.md,
|
|
8
8
|
* the `--against` row of check slice 3). The evolve gate is the kernel's #58 diff-by-stable-id
|
|
9
9
|
* (executor/src/law58.rs::evolve_violation): a RENAME (same sid, new label) admits silently; a
|
|
10
|
-
* true REMOVAL / merge-driver change refuses TYPED unless the
|
|
10
|
+
* true REMOVAL / merge-driver change refuses TYPED unless the installation offer carries a
|
|
11
11
|
* `dispositions` acknowledgement. A changed BUSINESS VALUE CONTRACT is stricter: it needs
|
|
12
12
|
* deterministic `.evolvesFrom(...)` coverage and cannot be waived by an acknowledgement.
|
|
13
13
|
*
|
|
@@ -34,9 +34,10 @@ import type { Finding } from "./law_check.js";
|
|
|
34
34
|
import type { DirectivePayloadShape, DomainInterface } from "./shape_compat.js";
|
|
35
35
|
import { domainInterface } from "./shape_compat.js";
|
|
36
36
|
import type { StableIds } from "./stable_ids_types.js";
|
|
37
|
+
import type { StableIdLockfile } from "./stable_id_lockfile.js";
|
|
38
|
+
export type { StableIdLockfile } from "./stable_id_lockfile.js";
|
|
37
39
|
import type { DomainModule } from "./codegen_dart.js";
|
|
38
40
|
import type { WireSchema } from "./wire.js";
|
|
39
|
-
import { parseUsdaDocument } from "./usd_layers.js";
|
|
40
41
|
|
|
41
42
|
// ── the normalized law shape both sides of the diff speak ──────────────────────────
|
|
42
43
|
|
|
@@ -70,7 +71,7 @@ export interface EvolveDomain {
|
|
|
70
71
|
/** A whole package's evolve surface, keyed by DOMAIN KEY. */
|
|
71
72
|
export type EvolveLaw = Record<string, EvolveDomain>;
|
|
72
73
|
|
|
73
|
-
/** The dispositions fragment the
|
|
74
|
+
/** The dispositions fragment the installDomain offer carries to admit destructive evolves (the exact
|
|
74
75
|
* shape executor/src/law58.rs::dispositions_from_payload parses). */
|
|
75
76
|
export interface EvolveDispositionsJson {
|
|
76
77
|
retired?: string[];
|
|
@@ -92,7 +93,7 @@ function aggregatesFromStableIds(
|
|
|
92
93
|
for (const [aggLabel, a] of Object.entries(stableIds.aggregates)) {
|
|
93
94
|
const fields: EvolveAggregate["fields"] = {};
|
|
94
95
|
for (const [fLabel, f] of Object.entries(a.fields ?? {})) {
|
|
95
|
-
const driver = schemas?.[aggLabel]?.[fLabel];
|
|
96
|
+
const driver = f.driver ?? schemas?.[aggLabel]?.[fLabel];
|
|
96
97
|
fields[f.sid] = {
|
|
97
98
|
sid: f.sid,
|
|
98
99
|
label: fLabel,
|
|
@@ -110,7 +111,7 @@ function aggregatesFromStableIds(
|
|
|
110
111
|
return out;
|
|
111
112
|
}
|
|
112
113
|
|
|
113
|
-
/** Build an EvolveDomain from a DomainInterface
|
|
114
|
+
/** Build an EvolveDomain from a diagnostic DomainInterface — its aggregates are
|
|
114
115
|
* already sid-keyed with kinds+labels, its directives carry payload shapes. Drivers are absent
|
|
115
116
|
* from an interface (it exposes only read kinds), so a driver-only retype is not seen from an
|
|
116
117
|
* interface alone — the lockfile lane (aggregatesFromStableIds + schemas) carries them. */
|
|
@@ -130,78 +131,29 @@ function evolveDomainFromInterface(iface: DomainInterface): EvolveDomain {
|
|
|
130
131
|
};
|
|
131
132
|
}
|
|
132
133
|
|
|
133
|
-
/**
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
export interface DeployJson {
|
|
142
|
-
interface?: Record<string, DomainInterface>;
|
|
143
|
-
packageUsda?: string;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/** Reconstruct the effective law directly from a native `.package.usda` stage. */
|
|
147
|
-
export function usdJsonFromPackage(bundle: string): unknown | undefined {
|
|
148
|
-
try {
|
|
149
|
-
return parseUsdaDocument(bundle);
|
|
150
|
-
} catch {
|
|
151
|
-
return undefined;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/** Per-domain-key { stableIds, schemas } parsed out of a package's embedded USD-IR (mirrors
|
|
156
|
-
* executor/src/law58.rs::stable_ids_from_usd + the aggregate-prim schema harvest). */
|
|
157
|
-
export function stableIdsFromUsd(
|
|
158
|
-
usd: unknown,
|
|
159
|
-
): Record<string, { stableIds: StableIds; schemas: Record<string, WireSchema> }> {
|
|
160
|
-
const out: Record<string, { stableIds: StableIds; schemas: Record<string, WireSchema> }> = {};
|
|
161
|
-
const layers = (usd as { layers?: unknown[] })?.layers;
|
|
162
|
-
if (!Array.isArray(layers)) return out;
|
|
163
|
-
for (const layer of layers) {
|
|
164
|
-
const l = layer as {
|
|
165
|
-
path?: string;
|
|
166
|
-
nomosStableIds?: StableIds;
|
|
167
|
-
prims?: Array<{ kind?: string; type?: string; schema?: Record<string, unknown> }>;
|
|
168
|
-
};
|
|
169
|
-
const path = typeof l.path === "string" ? l.path : "";
|
|
170
|
-
const key = path.startsWith("/Nomos/") ? path.slice("/Nomos/".length) : "";
|
|
171
|
-
if (key === "" || l.nomosStableIds === undefined) continue;
|
|
172
|
-
const schemas: Record<string, WireSchema> = {};
|
|
173
|
-
for (const prim of l.prims ?? []) {
|
|
174
|
-
if (prim.kind === "aggregate" && typeof prim.type === "string" && prim.schema) {
|
|
175
|
-
schemas[prim.type] = prim.schema as WireSchema;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
out[key] = { stableIds: l.nomosStableIds, schemas };
|
|
179
|
-
}
|
|
180
|
-
return out;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/** Build an EvolveLaw from a deploy.json body: interface for directive payloads + sid-keyed read
|
|
184
|
-
* shapes, and the embedded package USD-IR for the driver schemas (so a driver-only retype is
|
|
185
|
-
* seen from a deploy.json alone). */
|
|
186
|
-
export function evolveLawFromDeployJson(deploy: DeployJson): EvolveLaw {
|
|
134
|
+
/** Build an EvolveLaw from compiler diagnostic sidecars. The interface supplies directive and
|
|
135
|
+
* read shapes; the stable-id lock supplies identity, drivers and value-contract evolution. Neither
|
|
136
|
+
* is deployable law: canonical law remains the composed, logically named USDA closure interpreted
|
|
137
|
+
* by the kernel; `.law.usdz` is only its lossless one-file transport envelope. */
|
|
138
|
+
export function evolveLawFromInterface(
|
|
139
|
+
ifaces: Record<string, DomainInterface>,
|
|
140
|
+
lock?: StableIdLockfile,
|
|
141
|
+
): EvolveLaw {
|
|
187
142
|
const law: EvolveLaw = {};
|
|
188
|
-
const ifaces = deploy.interface ?? {};
|
|
189
|
-
const usd = deploy.packageUsda !== undefined ? usdJsonFromPackage(deploy.packageUsda) : undefined;
|
|
190
|
-
const fromUsd = usd !== undefined ? stableIdsFromUsd(usd) : {};
|
|
191
143
|
for (const [key, iface] of Object.entries(ifaces)) {
|
|
192
144
|
const dom = evolveDomainFromInterface(iface);
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
if (
|
|
145
|
+
const locked = lock?.domains[key] ?? lock?.domains[iface.domain];
|
|
146
|
+
const stableIds = locked?.stableIds;
|
|
147
|
+
if (stableIds !== undefined) {
|
|
196
148
|
for (const agg of Object.values(dom.aggregates)) {
|
|
197
|
-
const stableAggregate = Object.values(
|
|
149
|
+
const stableAggregate = Object.values(stableIds.aggregates)
|
|
198
150
|
.find((candidate) => candidate.sid === agg.sid);
|
|
199
151
|
const stableFields = new Map(
|
|
200
152
|
Object.values(stableAggregate?.fields ?? {})
|
|
201
153
|
.map((field) => [field.sid, field] as const),
|
|
202
154
|
);
|
|
203
|
-
const schema =
|
|
204
|
-
?? Object.entries(
|
|
155
|
+
const schema = locked?.schemas?.[agg.label]
|
|
156
|
+
?? Object.entries(locked?.schemas ?? {}).find(([type]) => type.endsWith(`.${agg.label}`))?.[1];
|
|
205
157
|
for (const f of Object.values(agg.fields)) {
|
|
206
158
|
const driver = schema?.[f.label];
|
|
207
159
|
if (driver !== undefined) (f as { driver?: string }).driver = JSON.stringify(driver);
|
|
@@ -216,10 +168,14 @@ export function evolveLawFromDeployJson(deploy: DeployJson): EvolveLaw {
|
|
|
216
168
|
}
|
|
217
169
|
law[key] = dom;
|
|
218
170
|
}
|
|
219
|
-
// A
|
|
220
|
-
for (const [key,
|
|
171
|
+
// A lockfile without a sibling interface still supports aggregate/field identity checks.
|
|
172
|
+
for (const [key, locked] of Object.entries(lock?.domains ?? {})) {
|
|
221
173
|
if (law[key] !== undefined) continue;
|
|
222
|
-
law[key] = {
|
|
174
|
+
law[key] = {
|
|
175
|
+
domainSid: locked.stableIds?.domain,
|
|
176
|
+
aggregates: aggregatesFromStableIds(locked.stableIds, locked.schemas),
|
|
177
|
+
directives: {},
|
|
178
|
+
};
|
|
223
179
|
}
|
|
224
180
|
return law;
|
|
225
181
|
}
|
|
@@ -254,13 +210,19 @@ export function currentEvolveLaw(
|
|
|
254
210
|
const stableIds = locked?.stableIds;
|
|
255
211
|
const iface = domainInterface(mod, stableIds);
|
|
256
212
|
const dom = evolveDomainFromInterface(iface);
|
|
257
|
-
// Fold drivers from the
|
|
258
|
-
|
|
213
|
+
// Fold drivers from the stable fields. Verbose v1 locks may also carry the
|
|
214
|
+
// older aggregate-schema copy; compact v2 locks deliberately omit it.
|
|
215
|
+
if (stableIds !== undefined) {
|
|
259
216
|
for (const agg of Object.values(dom.aggregates)) {
|
|
260
|
-
const
|
|
261
|
-
|
|
217
|
+
const stableAggregate = Object.values(stableIds.aggregates).find(
|
|
218
|
+
(candidate) => candidate.sid === agg.sid,
|
|
219
|
+
);
|
|
220
|
+
const schema = locked?.schemas?.[agg.label];
|
|
262
221
|
for (const f of Object.values(agg.fields)) {
|
|
263
|
-
const
|
|
222
|
+
const stableField = Object.values(stableAggregate?.fields ?? {}).find(
|
|
223
|
+
(candidate) => candidate.sid === f.sid,
|
|
224
|
+
);
|
|
225
|
+
const driver = stableField?.driver ?? schema?.[f.label];
|
|
264
226
|
if (driver !== undefined) (f as { driver?: string }).driver = JSON.stringify(driver);
|
|
265
227
|
}
|
|
266
228
|
}
|
|
@@ -273,7 +235,7 @@ export function currentEvolveLaw(
|
|
|
273
235
|
// ── the diff ────────────────────────────────────────────────────────────────────────
|
|
274
236
|
|
|
275
237
|
/** The full preview result: the findings AND the merged dispositions the destructive findings
|
|
276
|
-
* demand, keyed by DOMAIN KEY (
|
|
238
|
+
* demand, keyed by DOMAIN KEY (the object carried by an installDomain offer). */
|
|
277
239
|
export interface EvolveDiffResult {
|
|
278
240
|
readonly findings: Finding[];
|
|
279
241
|
/** domain key → the dispositions fragment that makes THIS domain's destructive evolves admit. */
|
|
@@ -335,7 +297,7 @@ function diffDomain(
|
|
|
335
297
|
domain,
|
|
336
298
|
level: "refuse",
|
|
337
299
|
what: `aggregate '${oldAgg.label}' (stable id ${sid}) is REMOVED — the evolve gate will refuse without a disposition`,
|
|
338
|
-
fix: `
|
|
300
|
+
fix: `include in the installation offer: "dispositions": { "retired": ["${sid}"] }`,
|
|
339
301
|
});
|
|
340
302
|
continue;
|
|
341
303
|
}
|
|
@@ -357,7 +319,7 @@ function diffDomain(
|
|
|
357
319
|
domain,
|
|
358
320
|
level: "refuse",
|
|
359
321
|
what: `field '${oldAgg.label}.${oldF.label}' (stable id ${fSid}) is REMOVED — the evolve gate will refuse without a disposition`,
|
|
360
|
-
fix: `
|
|
322
|
+
fix: `include in the installation offer: "dispositions": { "retired": ["${fSid}"] }`,
|
|
361
323
|
});
|
|
362
324
|
continue;
|
|
363
325
|
}
|
|
@@ -394,7 +356,7 @@ function diffDomain(
|
|
|
394
356
|
domain,
|
|
395
357
|
level: "refuse",
|
|
396
358
|
what: `field '${oldAgg.label}.${oldF.label}' (stable id ${fSid}) is RETYPED in place (${how}) — the evolve gate will refuse without a disposition`,
|
|
397
|
-
fix: `
|
|
359
|
+
fix: `include in the installation offer: "dispositions": { "retyped": { "${fSid}": "<why goes here>" } } — or add a NEW field instead`,
|
|
398
360
|
});
|
|
399
361
|
}
|
|
400
362
|
}
|
|
@@ -490,7 +452,7 @@ export function evolveDiff(prior: EvolveLaw, current: EvolveLaw): EvolveDiffResu
|
|
|
490
452
|
domain,
|
|
491
453
|
level: "refuse",
|
|
492
454
|
what: `domain '${domain}' aggregate '${agg.label}' (stable id ${sid}) disappears — the current build no longer carries domain '${domain}'`,
|
|
493
|
-
fix: `restore the domain, or
|
|
455
|
+
fix: `restore the domain, or include "dispositions": { "retired": ["${sid}"] } in the replacement installation offer`,
|
|
494
456
|
});
|
|
495
457
|
}
|
|
496
458
|
continue;
|
package/src/extremum.ts
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* VALIDATION RULE (manifest-load, not type-level): the `valueField` parameter MUST be
|
|
29
29
|
* an `int`-kind field of the `of`-aggregate. The DSL type system cannot enforce this
|
|
30
30
|
* at the `min/max(id, valueField)` call site (the aggregate is not yet known), but
|
|
31
|
-
*
|
|
31
|
+
* typed USDA projection-law admission performs a no-fallback reject
|
|
32
32
|
* if the field is absent or non-int on the declared aggregate.
|
|
33
33
|
*
|
|
34
34
|
* ORDER-SENSITIVE GUARDRAIL: min/max expose NO `.first`/`.take`/`.orderBy`. min/max
|
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);
|