@githolon/dsl 0.100.5 → 0.100.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dart/CHANGELOG.md +6 -0
- package/dart/lib/src/test_harness.dart +146 -59
- package/dart/pubspec.yaml +1 -1
- package/dart/test/acquire_session_credentials_test.dart +1 -1
- package/dart/test/adv_realm_interference_test.dart +1 -1
- package/dart/test/cluster_attested_test.dart +1 -1
- package/dart/test/cluster_births_test.dart +1 -1
- package/dart/test/cluster_device_keyed_acceptance_test.dart +1 -1
- package/dart/test/cluster_local_autosync_test.dart +1 -1
- package/dart/test/cluster_scale_test.dart +1 -1
- package/dart/test/dlq_session_surface_test.dart +1 -1
- package/dart/test/first_app_gauntlet_test.dart +106 -87
- package/dart/test/local_sync_inert_test.dart +10 -5
- package/dart/test/parent_signer_recovery_test.dart +1 -1
- package/dart/test/sealing_client_test.dart +1 -1
- package/dart/test/session_bound_test.dart +1 -1
- package/dart/test/test_harness_test.dart +2 -2
- package/dart/test/vo_read_typing_test.dart +1 -1
- package/dart/test/watch_after_offer_stall_test.dart +1 -1
- package/package.json +1 -1
- package/src/birth_meter_ensure.ts +157 -29
- package/src/build_package.ts +41 -6
- package/src/business_birth.ts +31 -9
- package/src/codegen_dart_client.ts +11 -7
- package/src/compile_package_main.ts +17 -8
- package/src/shape_compat.ts +14 -3
|
@@ -42,6 +42,44 @@ import {
|
|
|
42
42
|
retireChildRequiresBirth,
|
|
43
43
|
retireChildNotReclaimed,
|
|
44
44
|
} from "./framework/birth_meter.js";
|
|
45
|
+
import { RelationTuple, relationTupleInstanceId, relationTuplesByObject } from "./framework/relations.js";
|
|
46
|
+
import { GENESIS_WARRANT_ID, GenesisWarrant, seedGenesis } from "./framework/genesis.js";
|
|
47
|
+
import {
|
|
48
|
+
BIRTH_ATTESTATION_ID,
|
|
49
|
+
BirthAttestation,
|
|
50
|
+
birthAttestation,
|
|
51
|
+
seedBirthAttestation,
|
|
52
|
+
} from "./framework/home.js";
|
|
53
|
+
import { requireParentAttestedBirth } from "./births.js";
|
|
54
|
+
import {
|
|
55
|
+
SignerDelegation,
|
|
56
|
+
enrollDelegation,
|
|
57
|
+
enrollSigner,
|
|
58
|
+
identityRelations,
|
|
59
|
+
signerDelegationByUser,
|
|
60
|
+
signerObject,
|
|
61
|
+
} from "./framework/signing_identity.js";
|
|
62
|
+
import { AuthProvider, authProvider, setAuthProvider } from "./framework/auth_provider_law.js";
|
|
63
|
+
import { refAs, workspaceInvariant } from "./framework/workspace_invariant.js";
|
|
64
|
+
|
|
65
|
+
/** Lightweight copy of the framework's founding-signer guard, kept here to avoid importing the whole
|
|
66
|
+
* governance module (whose unrelated captured-read laws would otherwise survive into every child lump). */
|
|
67
|
+
const bornFounderSignerWriteOnce = workspaceInvariant("FounderSignerWriteOnce")
|
|
68
|
+
.on("enrollSigner")
|
|
69
|
+
.reads((ctx) => [
|
|
70
|
+
refAs(
|
|
71
|
+
"tuple",
|
|
72
|
+
"RelationTuple",
|
|
73
|
+
"pre:" + relationTupleInstanceId(signerObject(String((ctx.intent ?? {}).principal ?? "")), "signer"),
|
|
74
|
+
),
|
|
75
|
+
])
|
|
76
|
+
.assert(({ tuple }, ctx) => {
|
|
77
|
+
const subjects = (tuple as { subjects?: Record<string, unknown> })?.subjects ?? {};
|
|
78
|
+
const want = "key:" + String((ctx?.intent ?? {}).keyHash ?? "");
|
|
79
|
+
return Object.keys(subjects).every((key) => key === want)
|
|
80
|
+
? { accept: true }
|
|
81
|
+
: { reject: "founding-signer-already-enrolled" };
|
|
82
|
+
});
|
|
45
83
|
|
|
46
84
|
/**
|
|
47
85
|
* The sanctioning declaration — an `ensures(NomosBirthRecord)` directive whose plan emits ZERO ops.
|
|
@@ -70,36 +108,126 @@ export function withBirthMeterExports<M extends Record<string, unknown>>(mod: M)
|
|
|
70
108
|
(v as { birthsChild?: boolean }).birthsChild === true &&
|
|
71
109
|
typeof (v as Directive<unknown>).plan === "function",
|
|
72
110
|
);
|
|
73
|
-
if
|
|
111
|
+
// A law that accepts the kernel-injected birth attestation is itself a possible CHILD even if it never
|
|
112
|
+
// births anything. Such a law must also accept the framework-owned founding signer step, otherwise the
|
|
113
|
+
// child can replay but can never sign its first write or birth a descendant without tenant recipe work.
|
|
114
|
+
const acceptsBirth = Object.values(mod).some(
|
|
115
|
+
(v) =>
|
|
116
|
+
v !== null &&
|
|
117
|
+
typeof v === "object" &&
|
|
118
|
+
(v as { id?: unknown }).id === "seedBirthAttestation" &&
|
|
119
|
+
typeof (v as Directive<unknown>).plan === "function",
|
|
120
|
+
);
|
|
121
|
+
if (!declaresBirth && !acceptsBirth) return mod;
|
|
74
122
|
const out: Record<string, unknown> = { ...mod };
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
123
|
+
const internal = <T extends object>(v: T): T => ({ ...v, businessBirthInternal: true }) as T;
|
|
124
|
+
const hasOwn = (key: string): boolean => Object.prototype.hasOwnProperty.call(out, key);
|
|
125
|
+
const isGeneratedInternal = (value: unknown): boolean =>
|
|
126
|
+
value !== null &&
|
|
127
|
+
typeof value === "object" &&
|
|
128
|
+
(value as { businessBirthInternal?: unknown }).businessBirthInternal === true;
|
|
129
|
+
const reservedNameError = (key: string): Error => new Error(
|
|
130
|
+
`Nomos internal export name '${key}' is reserved inside a birth-capable law, but the tenant module ` +
|
|
131
|
+
"already exports a different concept there. Nomos will not silently overwrite or reroute it; rename " +
|
|
132
|
+
"the tenant export.",
|
|
80
133
|
);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
134
|
+
/** Reserve a framework-only JS export slot as well as its stable law id. The keys are deliberately obscure,
|
|
135
|
+
* but obscurity is collision avoidance, not collision handling: an occupied slot is always a loud refusal. */
|
|
136
|
+
const reserve = <T>(key: string, canonical: T): void => {
|
|
137
|
+
if (!hasOwn(key)) {
|
|
138
|
+
out[key] = canonical;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const existing = out[key];
|
|
142
|
+
if (existing !== canonical && !isGeneratedInternal(existing)) throw reservedNameError(key);
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* Framework ownership follows the stable law id, not the tenant's export key. Older laws commonly
|
|
146
|
+
* re-exported `enrollSigner`/`seedGenesis` under their public names; merely adding a second hidden
|
|
147
|
+
* `__nomos_*` binding leaves BOTH declarations discoverable and lets codegen expose the old one. Mark
|
|
148
|
+
* every matching binding internal, and add the canonical declaration only when the law carried none.
|
|
149
|
+
*/
|
|
150
|
+
const internalize = <T extends { readonly id: string }>(key: string, canonical: T): void => {
|
|
151
|
+
if (hasOwn(key)) {
|
|
152
|
+
const existing = out[key];
|
|
153
|
+
const sameGeneratedId =
|
|
154
|
+
isGeneratedInternal(existing) &&
|
|
155
|
+
(existing as { id?: unknown }).id === canonical.id;
|
|
156
|
+
if (existing !== canonical && !sameGeneratedId) throw reservedNameError(key);
|
|
157
|
+
}
|
|
158
|
+
let found = false;
|
|
159
|
+
for (const [exportName, value] of Object.entries(out)) {
|
|
160
|
+
if (
|
|
161
|
+
value !== null &&
|
|
162
|
+
typeof value === "object" &&
|
|
163
|
+
(value as { id?: unknown }).id === canonical.id
|
|
164
|
+
) {
|
|
165
|
+
const isCanonicalSingleton = value === canonical;
|
|
166
|
+
const isNomosGeneratedBinding =
|
|
167
|
+
(value as { businessBirthInternal?: unknown }).businessBirthInternal === true;
|
|
168
|
+
if (!isCanonicalSingleton && !isNomosGeneratedBinding) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Nomos framework id '${canonical.id}' is reserved inside a birth-capable law, but export ` +
|
|
171
|
+
`'${exportName}' declares a different tenant-owned concept with that id. Nomos will not ` +
|
|
172
|
+
`silently reinterpret it as framework machinery; rename the tenant concept.`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
out[exportName] = internal(value);
|
|
176
|
+
found = true;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (!found) out[key] = internal(canonical);
|
|
180
|
+
};
|
|
181
|
+
// THE BORN-WORKSPACE CORE: the kernel appends enrollSigner -> attestation -> warrant to every valid birth.
|
|
182
|
+
// Carry the law needed to judge that internal step in every possible child, plus the write-once guard that
|
|
183
|
+
// keeps the founding lane a genesis primitive. These declarations are certified but never tenant-facing.
|
|
184
|
+
internalize("__nomos_RelationTuple", RelationTuple);
|
|
185
|
+
reserve("__nomos_identityRelations", identityRelations);
|
|
186
|
+
internalize("__nomos_enrollSigner", enrollSigner);
|
|
187
|
+
internalize("__nomos_relationTuplesByObject", relationTuplesByObject);
|
|
188
|
+
internalize("__nomos_GenesisWarrant", GenesisWarrant);
|
|
189
|
+
internalize("__nomos_seedGenesis", seedGenesis);
|
|
190
|
+
internalize("__nomos_BirthAttestation", BirthAttestation);
|
|
191
|
+
internalize("__nomos_seedBirthAttestation", seedBirthAttestation);
|
|
192
|
+
internalize("__nomos_birthAttestation", birthAttestation);
|
|
193
|
+
internalize("__nomos_AuthProvider", AuthProvider);
|
|
194
|
+
internalize("__nomos_setAuthProvider", setAuthProvider);
|
|
195
|
+
internalize("__nomos_authProvider", authProvider);
|
|
196
|
+
reserve("__nomos_founderSignerWriteOnce", bornFounderSignerWriteOnce);
|
|
197
|
+
if (!declaresBirth) return out as M;
|
|
198
|
+
// Birth lifecycle is Nomos machinery for every birth-capable law. It remains certified/routable law,
|
|
199
|
+
// but no generated tenant API gets a second maintenance lane beside business `birth(...)`.
|
|
200
|
+
internalize("__nomos_nomosBirthRecordEnsure", nomosBirthRecordEnsure);
|
|
201
|
+
internalize("__nomos_NomosBirthRecord", NomosBirthRecord);
|
|
202
|
+
internalize("__nomos_NomosChildLifecycle", NomosChildLifecycle);
|
|
203
|
+
internalize("__nomos_BirthPolicy", BirthPolicy);
|
|
204
|
+
internalize("__nomos_BirthGrant", BirthGrant);
|
|
205
|
+
internalize("__nomos_retireChild", retireChild);
|
|
206
|
+
internalize("__nomos_reclaimChild", reclaimChild);
|
|
207
|
+
internalize("__nomos_retireChildAsAdmin", retireChildAsAdmin);
|
|
208
|
+
internalize("__nomos_reclaimChildAsAdmin", reclaimChildAsAdmin);
|
|
209
|
+
reserve("__nomos_reclaimChildRequiresRetired", reclaimChildRequiresRetired);
|
|
210
|
+
reserve("__nomos_reclaimChildAsAdminRequiresRetired", reclaimChildAsAdminRequiresRetired);
|
|
211
|
+
reserve("__nomos_retireChildRequiresBirth", retireChildRequiresBirth);
|
|
212
|
+
reserve("__nomos_retireChildNotReclaimed", retireChildNotReclaimed);
|
|
213
|
+
reserve("__nomos_retireChildAsAdminRequiresBirth", retireChildAsAdminRequiresBirth);
|
|
214
|
+
reserve("__nomos_retireChildAsAdminNotReclaimed", retireChildAsAdminNotReclaimed);
|
|
215
|
+
// ONE BIRTH LANE ALSO MEANS ONE PRIVATE SIGNING LANE. The client completes a `.births()` offer's signer
|
|
216
|
+
// enrollment, delegation and cert retry internally; therefore every birth-declaring parent law must carry
|
|
217
|
+
// the declarations that make those framework writes lawful. Previously only the newer `businessBirth`
|
|
218
|
+
// helper composed them, while an ordinary `.births()` directive forced the harness/app to understand
|
|
219
|
+
// `enrollDelegation` (or failed as an unverifiable black box). Auto-compose the exact same framework
|
|
220
|
+
// declarations for ALL births and mark them presentation-internal: they remain certified/routable law,
|
|
221
|
+
// but never appear in TS/Dart/OpenAPI tenant surfaces.
|
|
222
|
+
internalize("__nomos_SignerDelegation", SignerDelegation);
|
|
223
|
+
internalize("__nomos_enrollDelegation", enrollDelegation);
|
|
224
|
+
internalize("__nomos_signerDelegationByUser", signerDelegationByUser);
|
|
225
|
+
reserve("__nomos_parentAttestedBirth", internal({
|
|
226
|
+
...requireParentAttestedBirth,
|
|
227
|
+
ownerObject: GENESIS_WARRANT_ID,
|
|
228
|
+
ownerField: "principal",
|
|
229
|
+
certObject: BIRTH_ATTESTATION_ID,
|
|
230
|
+
certField: "certChain",
|
|
231
|
+
}));
|
|
104
232
|
return out as M;
|
|
105
233
|
}
|
package/src/build_package.ts
CHANGED
|
@@ -75,6 +75,7 @@ import { collectValueObjectDescriptors } from "./value_object.js";
|
|
|
75
75
|
import { emitUsd } from "./usd.js";
|
|
76
76
|
import { expandCapabilityExports } from "./capability_exports.js";
|
|
77
77
|
import { RelationTuple, relationTuplesByObject } from "./framework/relations.js";
|
|
78
|
+
import { signerDelegationByUser } from "./framework/signing_identity.js";
|
|
78
79
|
import { AuthProvider, setAuthProvider, authProvider } from "./framework/workspaces.js";
|
|
79
80
|
import { parentAttestedBirthMarker } from "./births.js";
|
|
80
81
|
import { discoverUnsanctionedEnsures, sanctioningEnsureDirective } from "./secondary_ensure.js";
|
|
@@ -125,7 +126,10 @@ export function contractsOf(mod: Mod): import("./contract.js").ContractHandle[]
|
|
|
125
126
|
|
|
126
127
|
/** Collect every exported `Directive` ({id, plan, aggregateId} duck-type). */
|
|
127
128
|
export function directivesOf(mod: Mod): Directive<any>[] {
|
|
128
|
-
|
|
129
|
+
// One wire directive id is one law declaration. Merge seams may retain both an old tenant re-export and
|
|
130
|
+
// Nomos's presentation-internal wrapper under different JS keys; later wins so the framework wrapper
|
|
131
|
+
// controls codegen without duplicating the hashed/routable law.
|
|
132
|
+
const byId = new Map<string, Directive<any>>();
|
|
129
133
|
for (const v of Object.values(mod)) {
|
|
130
134
|
if (
|
|
131
135
|
v &&
|
|
@@ -134,10 +138,10 @@ export function directivesOf(mod: Mod): Directive<any>[] {
|
|
|
134
138
|
typeof (v as { plan?: unknown }).plan === "function" &&
|
|
135
139
|
typeof (v as { aggregateId?: unknown }).aggregateId === "string"
|
|
136
140
|
) {
|
|
137
|
-
|
|
141
|
+
byId.set((v as { id: string }).id, v as Directive<any>);
|
|
138
142
|
}
|
|
139
143
|
}
|
|
140
|
-
return
|
|
144
|
+
return [...byId.values()];
|
|
141
145
|
}
|
|
142
146
|
|
|
143
147
|
/** The NAMED-KEYS spec for one engine domain (the serialization of `module(...)`). */
|
|
@@ -306,7 +310,11 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
|
|
|
306
310
|
}
|
|
307
311
|
const readModels = spec.readModelAggregates ?? aggregates.map((a) => a.id);
|
|
308
312
|
|
|
309
|
-
|
|
313
|
+
// As with directives, one stable query id is one declaration. This also makes an internal framework
|
|
314
|
+
// wrapper win over an older public re-export discovered earlier in the module bag.
|
|
315
|
+
const queryById = new Map<string, QueryDecl>();
|
|
316
|
+
for (const query of spec.queries ?? []) queryById.set(query.id, query);
|
|
317
|
+
const queries = [...queryById.values()];
|
|
310
318
|
const counts = [...(spec.counts ?? [])];
|
|
311
319
|
const spatials = [...(spec.spatials ?? [])];
|
|
312
320
|
const deriveds = [...(spec.deriveds ?? [])];
|
|
@@ -316,7 +324,23 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
|
|
|
316
324
|
const mins = [...(spec.mins ?? [])];
|
|
317
325
|
const maxes = [...(spec.maxes ?? [])];
|
|
318
326
|
const workspaceTypes = [...(spec.workspaceTypes ?? [])];
|
|
319
|
-
|
|
327
|
+
// Invariants exported by framework auto-composition must enter the manifest at this same seam; callers of
|
|
328
|
+
// composeDomainModule (tests and package builders alike) should not need to rediscover/rethread them.
|
|
329
|
+
const workspaceInvariantById = new Map<string, WorkspaceInvariantDecl>();
|
|
330
|
+
for (const value of Object.values(merged)) {
|
|
331
|
+
if (
|
|
332
|
+
value !== null &&
|
|
333
|
+
typeof value === "object" &&
|
|
334
|
+
(value as { __isWorkspaceInvariant?: unknown }).__isWorkspaceInvariant === true
|
|
335
|
+
) {
|
|
336
|
+
const invariant = value as WorkspaceInvariantDecl;
|
|
337
|
+
workspaceInvariantById.set(invariant.id, invariant);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
for (const invariant of spec.workspaceInvariants ?? []) {
|
|
341
|
+
workspaceInvariantById.set(invariant.id, invariant);
|
|
342
|
+
}
|
|
343
|
+
const workspaceInvariants = [...workspaceInvariantById.values()];
|
|
320
344
|
if (declaresBirth) {
|
|
321
345
|
const haveInvariant = new Set(workspaceInvariants.map((w) => w.id));
|
|
322
346
|
for (const lifecycle of [
|
|
@@ -343,10 +367,21 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
|
|
|
343
367
|
if (declaresBirth) {
|
|
344
368
|
const haveQuery = new Set(queries.map((q) => q.id));
|
|
345
369
|
for (const q of BIRTH_FRAMEWORK_QUERIES) {
|
|
346
|
-
if (!haveQuery.has(q.id))
|
|
370
|
+
if (!haveQuery.has(q.id)) {
|
|
371
|
+
queries.push({ ...q, businessBirthInternal: true } as QueryDecl);
|
|
372
|
+
}
|
|
347
373
|
}
|
|
348
374
|
// The inheritance read (`.reads("authProvider")` on birthHome) routes like any declared query.
|
|
349
375
|
if (!haveQuery.has(authProvider.id)) queries.push(authProvider);
|
|
376
|
+
// The client's private birth ceremony checks the actor's delegation before creating it. Carry the
|
|
377
|
+
// structural read in certified routing law for every `.births()` domain, but keep it off tenant codegen.
|
|
378
|
+
if (!haveQuery.has(signerDelegationByUser.id)) {
|
|
379
|
+
const internalDelegationQuery: QueryDecl & { readonly businessBirthInternal: true } = {
|
|
380
|
+
...signerDelegationByUser,
|
|
381
|
+
businessBirthInternal: true,
|
|
382
|
+
};
|
|
383
|
+
queries.push(internalDelegationQuery);
|
|
384
|
+
}
|
|
350
385
|
}
|
|
351
386
|
const relationTypes = [...(spec.relationTypes ?? [])];
|
|
352
387
|
// RELATION-USING LAW AUTO-COMPOSES ITS AUDIT READ (co2 recovery DX — same idiom as the meter
|
package/src/business_birth.ts
CHANGED
|
@@ -229,7 +229,7 @@ function lowerOne(decl: BusinessBirthDecl, domain: string): Record<string, unkno
|
|
|
229
229
|
__nomos_birthAttestation: internalSurface(birthAttestation),
|
|
230
230
|
__nomos_RelationTuple: internalSurface(RelationTuple),
|
|
231
231
|
__nomos_relationTuplesByObject: internalSurface(relationTuplesByObject),
|
|
232
|
-
__nomos_identityRelations: identityRelations,
|
|
232
|
+
__nomos_identityRelations: internalSurface(identityRelations),
|
|
233
233
|
__nomos_enrollSigner: internalDirective(enrollSigner),
|
|
234
234
|
__nomos_enrollFirstDevice: internalDirective(enrollFirstDevice),
|
|
235
235
|
__nomos_SignerDelegation: internalSurface(SignerDelegation),
|
|
@@ -238,19 +238,19 @@ function lowerOne(decl: BusinessBirthDecl, domain: string): Record<string, unkno
|
|
|
238
238
|
__nomos_AuthProvider: internalSurface(AuthProvider),
|
|
239
239
|
__nomos_setAuthProvider: internalDirective(setAuthProvider),
|
|
240
240
|
__nomos_authProvider: internalSurface(authProvider),
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
241
|
+
// Obscure framework export keys minimize accidental tenant collisions. Stable law ids remain the real
|
|
242
|
+
// identity; the generic composition seam rejects a tenant-owned declaration with any of those ids.
|
|
243
|
+
__nomos_NomosBirthRecord: internalSurface(NomosBirthRecord),
|
|
244
|
+
__nomos_NomosChildLifecycle: internalSurface(NomosChildLifecycle),
|
|
245
|
+
__nomos_BirthPolicy: internalSurface(BirthPolicy),
|
|
246
|
+
__nomos_BirthGrant: internalSurface(BirthGrant),
|
|
247
|
+
__nomos_nomosBirthRecordEnsure: internalDirective(nomosBirthRecordEnsure),
|
|
248
248
|
__nomos_birthRecordsByPrincipal: internalSurface(birthRecordsByPrincipal),
|
|
249
249
|
__nomos_childLifecyclesByPrincipal: internalSurface(childLifecyclesByPrincipal),
|
|
250
250
|
__nomos_childLifecyclesByStatus: internalSurface(childLifecyclesByStatus),
|
|
251
251
|
__nomos_birthPolicy: internalSurface(birthPolicy),
|
|
252
252
|
__nomos_birthGrantByPrincipal: internalSurface(birthGrantByPrincipal),
|
|
253
|
-
__nomos_parentAttestedBirth: businessParentAttestedBirth,
|
|
253
|
+
__nomos_parentAttestedBirth: internalSurface(businessParentAttestedBirth),
|
|
254
254
|
};
|
|
255
255
|
}
|
|
256
256
|
|
|
@@ -268,6 +268,28 @@ export function expandBusinessBirthExports<M extends Record<string, unknown>>(
|
|
|
268
268
|
);
|
|
269
269
|
}
|
|
270
270
|
const lowered = lowerOne(declarations[0]!, domain);
|
|
271
|
+
for (const [key, generated] of Object.entries(lowered)) {
|
|
272
|
+
if (!Object.prototype.hasOwnProperty.call(mod, key)) continue;
|
|
273
|
+
const existing = mod[key];
|
|
274
|
+
const sameGeneratedDeclaration =
|
|
275
|
+
existing !== null &&
|
|
276
|
+
typeof existing === "object" &&
|
|
277
|
+
generated !== null &&
|
|
278
|
+
typeof generated === "object" &&
|
|
279
|
+
(((existing as { businessBirthInternal?: unknown }).businessBirthInternal === true &&
|
|
280
|
+
(generated as { businessBirthInternal?: unknown }).businessBirthInternal === true &&
|
|
281
|
+
(existing as { id?: unknown }).id === (generated as { id?: unknown }).id) ||
|
|
282
|
+
((existing as { businessBirth?: unknown }).businessBirth !== undefined &&
|
|
283
|
+
(generated as { businessBirth?: unknown }).businessBirth !== undefined &&
|
|
284
|
+
(existing as { id?: unknown }).id === (generated as { id?: unknown }).id));
|
|
285
|
+
if (!sameGeneratedDeclaration) {
|
|
286
|
+
throw new Error(
|
|
287
|
+
`Nomos internal export name '${key}' is reserved by birth(${declarations[0]!.aggregate.id}), but ` +
|
|
288
|
+
"the tenant module already exports a different concept there. Nomos will not silently overwrite " +
|
|
289
|
+
"or reroute it; rename the tenant export.",
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
271
293
|
return { ...mod, ...lowered };
|
|
272
294
|
}
|
|
273
295
|
|
|
@@ -939,13 +939,17 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
939
939
|
` if (b is NomosSession) await b.assertCompatible(interface);`,
|
|
940
940
|
` }`,
|
|
941
941
|
``,
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
942
|
+
...(dirs.length > 0 || birthDirs.some((d) => (d as { businessBirth?: unknown }).businessBirth !== undefined)
|
|
943
|
+
? [
|
|
944
|
+
` /// THE ONE DISPATCH LANE every generated directive method routes through: lazy preflight, then`,
|
|
945
|
+
` /// the session's offer (the generated-internal use of the engine-level lane).`,
|
|
946
|
+
` Future<NomosOfferOutcome> _offer(String directiveId, Object? payload, {List<Map<String, Object?>>? attestations}) async {`,
|
|
947
|
+
` await _preflightOnce();`,
|
|
948
|
+
` return bridge.offerOutcome(domain: ${dartStr(domain)}, directiveId: directiveId, payload: payload, actor: actor, domainHash: domainHash, attestations: attestations);`,
|
|
949
|
+
` }`,
|
|
950
|
+
``,
|
|
951
|
+
]
|
|
952
|
+
: []),
|
|
949
953
|
...(encPresent ? dartEncClassMembers(encMap!) : []),
|
|
950
954
|
// THE OWNER CEREMONY (slice 2): grantAndWrap / revokeAndRotate / rotate — the operator-blind key-epoch
|
|
951
955
|
// handshake as one call. Bind `actor` (the owner) to author the fan-out. Only for a domain with a scope.
|
|
@@ -598,10 +598,13 @@ function collectDartSharedSymbols(
|
|
|
598
598
|
const frameworkAggById = new Map<string, AggregateHandle>();
|
|
599
599
|
const frameworkDirById = new Map<string, ModDirective>();
|
|
600
600
|
const frameworkQueryById = new Map<string, ModQuery>();
|
|
601
|
+
const isInternal = (value: unknown): boolean =>
|
|
602
|
+
!!value && typeof value === "object"
|
|
603
|
+
&& (value as { businessBirthInternal?: unknown }).businessBirthInternal === true;
|
|
601
604
|
for (const mod of modules) {
|
|
602
|
-
for (const a of mod.aggregates) if (isFrameworkAggregate(a)) frameworkAggById.set(a.id, a);
|
|
603
|
-
for (const d of mod.directives) if (isFrameworkDirective(d)) frameworkDirById.set(d.id, d);
|
|
604
|
-
for (const q of mod.queries ?? []) if (isFrameworkQuery(q)) frameworkQueryById.set(q.id, q);
|
|
605
|
+
for (const a of mod.aggregates) if (!isInternal(a) && isFrameworkAggregate(a)) frameworkAggById.set(a.id, a);
|
|
606
|
+
for (const d of mod.directives) if (!isInternal(d) && isFrameworkDirective(d)) frameworkDirById.set(d.id, d);
|
|
607
|
+
for (const q of mod.queries ?? []) if (!isInternal(q) && isFrameworkQuery(q)) frameworkQueryById.set(q.id, q);
|
|
605
608
|
}
|
|
606
609
|
const frameworkAggs = [...frameworkAggById.values()];
|
|
607
610
|
const frameworkDirs = [...frameworkDirById.values()];
|
|
@@ -1773,7 +1776,9 @@ async function main(): Promise<void> {
|
|
|
1773
1776
|
if (directivesOf(merged).some((dir) => (dir as { birthsChild?: boolean }).birthsChild === true)) {
|
|
1774
1777
|
const haveQuery = new Set(queries.map((q) => q.id));
|
|
1775
1778
|
for (const q of BIRTH_FRAMEWORK_QUERIES) {
|
|
1776
|
-
if (!haveQuery.has(q.id))
|
|
1779
|
+
if (!haveQuery.has(q.id)) {
|
|
1780
|
+
queries.push({ ...q, businessBirthInternal: true } as QueryDecl);
|
|
1781
|
+
}
|
|
1777
1782
|
}
|
|
1778
1783
|
}
|
|
1779
1784
|
// RELATION-USING LAW AUTO-COMPOSES ITS AUDIT READ (co2 recovery DX, same idiom as the birth-meter
|
|
@@ -2583,12 +2588,16 @@ async function main(): Promise<void> {
|
|
|
2583
2588
|
// the read overlay; the workspace's full spec is the runtime merge of installed packages' specs
|
|
2584
2589
|
// (currentLaw); the host ingests it and builds its router from it — no domain/directive name in host
|
|
2585
2590
|
// source. Self-describing for the whole web ecosystem (Swagger/codegen/Hono/agent tool-calling).
|
|
2586
|
-
const
|
|
2591
|
+
const publicOpenapi = mergeOpenApiFragments(
|
|
2587
2592
|
domainModules.map((m) => generateOpenApiFragment(m, { lawHash: domainHash })),
|
|
2588
2593
|
{ title: `${cfg.name} (nomos package)`, lawHash: domainHash },
|
|
2589
2594
|
);
|
|
2595
|
+
const runtimeOpenapi = mergeOpenApiFragments(
|
|
2596
|
+
domainModules.map((m) => generateOpenApiFragment(m, { lawHash: domainHash, includeFrameworkRoutes: true })),
|
|
2597
|
+
{ title: `${cfg.name} (nomos package)`, lawHash: domainHash },
|
|
2598
|
+
);
|
|
2590
2599
|
const openapiPath = path.join(outDir, `${cfg.name}.openapi.json`);
|
|
2591
|
-
writeFileSync(openapiPath, JSON.stringify(
|
|
2600
|
+
writeFileSync(openapiPath, JSON.stringify(publicOpenapi, null, 2) + "\n", "utf8");
|
|
2592
2601
|
|
|
2593
2602
|
// ── 4. the one-call deploy body (package only) ──
|
|
2594
2603
|
// SELF-DESCRIBING: `summary` states what the (otherwise hex-opaque) package
|
|
@@ -2620,7 +2629,7 @@ async function main(): Promise<void> {
|
|
|
2620
2629
|
interface: packageInterface,
|
|
2621
2630
|
// THE SELF-DESCRIBING API: this package's OpenAPI spec (law-derived). The worker stages it on the
|
|
2622
2631
|
// manifest overlay; a read serves the workspace's merged spec; the host builds its router from it.
|
|
2623
|
-
openapi,
|
|
2632
|
+
openapi: runtimeOpenapi,
|
|
2624
2633
|
// THE CLIENT/LAW COMPAT MANIFEST (§5): a DIAGNOSIS surface (law hash + interface hash + generator
|
|
2625
2634
|
// provenance) the connect-time classifier reads. Frontend-only — the package bytes above ignore it.
|
|
2626
2635
|
nomosCompat,
|
|
@@ -2895,7 +2904,7 @@ async function main(): Promise<void> {
|
|
|
2895
2904
|
}
|
|
2896
2905
|
console.log(` client ${rel(clientPath)} (typed TS client — payloads, read models, query accessors)`);
|
|
2897
2906
|
for (const note of recursiveNotes) console.log(` note ${note}`);
|
|
2898
|
-
console.log(` openapi ${rel(openapiPath)} (${Object.keys(
|
|
2907
|
+
console.log(` openapi ${rel(openapiPath)} (${Object.keys(publicOpenapi.paths).length} operation(s) — the public business API; private framework routes ride only in the deploy manifest)`);
|
|
2899
2908
|
if (proofPath !== undefined) {
|
|
2900
2909
|
console.log(` proof ${rel(proofPath)} (a runnable e2e GENERATED from your law — run: githolon proof)`);
|
|
2901
2910
|
} else {
|
package/src/shape_compat.ts
CHANGED
|
@@ -123,6 +123,10 @@ function aggFieldReadKind(f: Field): string {
|
|
|
123
123
|
* stable-id surface. Without stable ids (pre-#58) aggregate fields key by NAME and the
|
|
124
124
|
* kind comes from the field declaration — a graceful, rename-unaware fallback. */
|
|
125
125
|
export function domainInterface(mod: DomainModule, stableIds?: StableIds): DomainInterface {
|
|
126
|
+
const isInternal = (value: unknown): boolean =>
|
|
127
|
+
!!value &&
|
|
128
|
+
typeof value === "object" &&
|
|
129
|
+
(value as { businessBirthInternal?: unknown }).businessBirthInternal === true;
|
|
126
130
|
const enumTypes = new Map<string, string>();
|
|
127
131
|
// DETERMINISTIC KEY ORDER — directive ids, aggregate sids, and field keys are SORTED, so the
|
|
128
132
|
// emitted interface is byte-stable and the layered-USD fold (which merges + sorts) reproduces it
|
|
@@ -134,21 +138,27 @@ export function domainInterface(mod: DomainModule, stableIds?: StableIds): Domai
|
|
|
134
138
|
};
|
|
135
139
|
const directives: Record<string, DirectivePayloadShape> = {};
|
|
136
140
|
const dirEntries: Array<[string, DirectivePayloadShape]> = [];
|
|
137
|
-
for (const d of (mod.directives ?? []) as Array<{
|
|
141
|
+
for (const d of (mod.directives ?? []) as Array<{
|
|
142
|
+
id?: unknown;
|
|
143
|
+
payloadSchema?: unknown;
|
|
144
|
+
businessBirth?: { publicPayloadSchema?: unknown };
|
|
145
|
+
}>) {
|
|
138
146
|
if (!d || typeof d.id !== "string") continue;
|
|
147
|
+
if (isInternal(d)) continue;
|
|
139
148
|
const fieldEntries: Array<[string, PayloadFieldShape]> = [];
|
|
140
|
-
const schema = d.payloadSchema;
|
|
149
|
+
const schema = d.businessBirth?.publicPayloadSchema ?? d.payloadSchema;
|
|
141
150
|
if (schema && typeof schema === "object") {
|
|
142
151
|
for (const spec of introspectPayload(schema as z.ZodTypeAny, enumTypes)) {
|
|
143
152
|
fieldEntries.push([spec.name, { kind: payloadFieldKind(spec), optional: !!spec.optional }]);
|
|
144
153
|
}
|
|
145
154
|
}
|
|
146
|
-
dirEntries.push([d.id, { fields: sortedRec(fieldEntries) }]);
|
|
155
|
+
dirEntries.push([d.businessBirth === undefined ? d.id : "birth", { fields: sortedRec(fieldEntries) }]);
|
|
147
156
|
}
|
|
148
157
|
Object.assign(directives, sortedRec(dirEntries));
|
|
149
158
|
const aggregates: Record<string, AggregateReadShape> = {};
|
|
150
159
|
const aggEntries: Array<[string, AggregateReadShape]> = [];
|
|
151
160
|
for (const agg of (mod.aggregates ?? []) as Array<{ id: string; fields: Record<string, Field> }>) {
|
|
161
|
+
if (isInternal(agg)) continue;
|
|
152
162
|
const label = agg.id;
|
|
153
163
|
const sa = stableIds?.aggregates?.[label];
|
|
154
164
|
const aggSid = sa?.sid ?? label;
|
|
@@ -168,6 +178,7 @@ export function domainInterface(mod: DomainModule, stableIds?: StableIds): Domai
|
|
|
168
178
|
const foreignReadEntries: Array<[string, ForeignEdge[]]> = [];
|
|
169
179
|
for (const d of (mod.directives ?? []) as Array<{ id?: unknown; declaredForeignReads?: ForeignEdge[] }>) {
|
|
170
180
|
if (!d || typeof d.id !== "string") continue;
|
|
181
|
+
if (isInternal(d)) continue;
|
|
171
182
|
if (d.declaredForeignReads !== undefined && d.declaredForeignReads.length > 0) {
|
|
172
183
|
foreignReadEntries.push([d.id, d.declaredForeignReads.map((e) => ({ ...e }))]);
|
|
173
184
|
}
|