@githolon/dsl 0.103.1 → 0.103.3
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 +7 -0
- package/dart/lib/src/dev_cloud.dart +5 -0
- package/dart/lib/src/runner/node-entry.mjs +1 -1
- package/dart/lib/src/runner/offline-install.mjs +1 -1
- package/dart/lib/src/test_harness.dart +33 -1
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/first_app_gauntlet_test.dart +16 -1
- package/dart_types/CHANGELOG.md +5 -0
- package/dart_types/pubspec.yaml +1 -1
- package/package.json +1 -1
- package/src/application_home_genesis.ts +73 -0
- package/src/birth_meter.ts +7 -15
- package/src/birth_meter_ensure.ts +9 -0
- package/src/births.ts +18 -0
- package/src/business_birth.ts +44 -1
- package/src/codegen_dart_application.ts +9 -1
- package/src/codegen_dart_scenario.ts +7 -0
- package/src/codegen_proof.ts +11 -1
- package/src/compile_package_main.ts +124 -8
- package/src/directive.ts +8 -0
- package/src/framework/application_home.ts +19 -2
- package/src/framework/birth_meter.ts +4 -5
- package/src/framework/home.ts +71 -5
- package/src/framework/workspaces.ts +44 -57
- package/src/index.ts +7 -1
- package/src/law_graph.ts +23 -0
- package/src/law_graph_usda.ts +11 -1
- package/src/package_config.ts +30 -0
package/dart/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## 0.64.4
|
|
2
|
+
|
|
3
|
+
- Require generated applications to seed explicit child-birth and workspace policies before first use;
|
|
4
|
+
missing law remains a typed kernel refusal instead of a compiled allowance or creation fallback.
|
|
5
|
+
- Keep application startup single-flight and bounded through authentication, custody restore, law install
|
|
6
|
+
and client binding so concurrent UI consumers share one lifecycle and one actionable result.
|
|
7
|
+
|
|
1
8
|
## 0.64.3
|
|
2
9
|
|
|
3
10
|
- Single-flight generated child-workspace acquisition and continuous sync so concurrent references share one
|
|
@@ -126,6 +126,7 @@ class NomosTestCloud {
|
|
|
126
126
|
/// goes last and is retained as the explicit law used to birth a receiving group. A payload package is
|
|
127
127
|
/// not made current on the parent merely to make it available to a child.
|
|
128
128
|
List<String> lawUsdzPaths = const [],
|
|
129
|
+
int? applicationMaxChildWorkspacesPerUser,
|
|
129
130
|
Duration readyTimeout = const Duration(seconds: 30),
|
|
130
131
|
void Function(String line)? onDiagnostic,
|
|
131
132
|
}) async {
|
|
@@ -137,6 +138,10 @@ class NomosTestCloud {
|
|
|
137
138
|
if (ephemeral) '--ephemeral',
|
|
138
139
|
if (dataDir != null) ...['--data', dataDir],
|
|
139
140
|
if (platform != null) ...['--platform', platform],
|
|
141
|
+
if (applicationMaxChildWorkspacesPerUser != null) ...[
|
|
142
|
+
'--application-max-child-workspaces-per-user',
|
|
143
|
+
'$applicationMaxChildWorkspacesPerUser',
|
|
144
|
+
],
|
|
140
145
|
for (final deploy in [
|
|
141
146
|
...lawUsdzPaths,
|
|
142
147
|
if (lawUsdzPath != null && !lawUsdzPaths.contains(lawUsdzPath))
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// <define:__NOMOS_KERNEL_RELEASE__>
|
|
4
|
-
var define_NOMOS_KERNEL_RELEASE_default = { production: { hash: "
|
|
4
|
+
var define_NOMOS_KERNEL_RELEASE_default = { production: { hash: "4bf8f2709135d729475c4dfbabb2014f8d1e10ff506506da1ee0299a808c415e", version: "kernel-4bf8f270", features: ["compoundKeys", "capturedReads", "invariantGate", "surfacedMerge", "fieldProvenance", "nullSemantics", "attestedReads", "conflictSnapshots", "ownedAudience"], protocol: 1 }, testMint: { hash: "c83316d63542760b76df0588e42c1ec34367536790bb73775694c6d167a8a87e", version: "kernel-c83316d6", features: ["compoundKeys", "capturedReads", "invariantGate", "surfacedMerge", "fieldProvenance", "nullSemantics", "attestedReads", "conflictSnapshots", "ownedAudience"], protocol: 1 } };
|
|
5
5
|
|
|
6
6
|
// runner/node-entry.ts
|
|
7
7
|
import { createInterface } from "node:readline";
|
|
@@ -6,7 +6,7 @@ var __export = (target, all) => {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
// <define:__NOMOS_KERNEL_RELEASE__>
|
|
9
|
-
var define_NOMOS_KERNEL_RELEASE_default = { production: { hash: "
|
|
9
|
+
var define_NOMOS_KERNEL_RELEASE_default = { production: { hash: "4bf8f2709135d729475c4dfbabb2014f8d1e10ff506506da1ee0299a808c415e", version: "kernel-4bf8f270", features: ["compoundKeys", "capturedReads", "invariantGate", "surfacedMerge", "fieldProvenance", "nullSemantics", "attestedReads", "conflictSnapshots", "ownedAudience"], protocol: 1 }, testMint: { hash: "c83316d63542760b76df0588e42c1ec34367536790bb73775694c6d167a8a87e", version: "kernel-c83316d6", features: ["compoundKeys", "capturedReads", "invariantGate", "surfacedMerge", "fieldProvenance", "nullSemantics", "attestedReads", "conflictSnapshots", "ownedAudience"], protocol: 1 } };
|
|
10
10
|
|
|
11
11
|
// runner/offline-install.ts
|
|
12
12
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync } from "node:fs";
|
|
@@ -519,6 +519,7 @@ class NomosTestHarness {
|
|
|
519
519
|
/// [autoTearDown] — when true (default) and [registerTearDown] is given, registers [dispose] as a teardown.
|
|
520
520
|
static Future<NomosTestHarness> ephemeral({
|
|
521
521
|
required List<String> lawUsdzPaths,
|
|
522
|
+
int? applicationMaxChildWorkspacesPerUser,
|
|
522
523
|
String? ownerSubject,
|
|
523
524
|
String workspace = 'test-ws',
|
|
524
525
|
String clientId = 'test-runner',
|
|
@@ -670,6 +671,14 @@ class NomosTestHarness {
|
|
|
670
671
|
),
|
|
671
672
|
);
|
|
672
673
|
if (birthsApplicationHome) {
|
|
674
|
+
final maxChildWorkspaces = applicationMaxChildWorkspacesPerUser;
|
|
675
|
+
if (maxChildWorkspaces == null || maxChildWorkspaces < 0) {
|
|
676
|
+
throw NomosTestHarnessException(
|
|
677
|
+
'a generated application scenario must carry its law-declared '
|
|
678
|
+
'application.maxChildWorkspacesPerUser into the test ceremony; '
|
|
679
|
+
'Nomos has no test-only birth-policy fallback',
|
|
680
|
+
);
|
|
681
|
+
}
|
|
673
682
|
final devicePublic = info['devicePublic']?.toString();
|
|
674
683
|
final deviceSecret = info['deviceSecret']?.toString();
|
|
675
684
|
final framework = info['frameworkHash']?.toString();
|
|
@@ -689,6 +698,29 @@ class NomosTestHarness {
|
|
|
689
698
|
);
|
|
690
699
|
}
|
|
691
700
|
final keyHash = sha256.convert(base64Decode(devicePublic)).toString();
|
|
701
|
+
final bornAt = DateTime.now().toUtc().toIso8601String();
|
|
702
|
+
await _phased(
|
|
703
|
+
phase: 'application: establish the parent birth policy',
|
|
704
|
+
workspace: installWorkspace,
|
|
705
|
+
awaiting: 'law-declared seedBirthPolicy offer',
|
|
706
|
+
timeout: phaseTimeout ?? _kOtherDeadline,
|
|
707
|
+
onDiagnostic: onDiagnostic,
|
|
708
|
+
// This throwaway parent is still on its warrant-free founding prefix. The generated
|
|
709
|
+
// scenario carries the value from the same package declaration that compiled the
|
|
710
|
+
// receiving-group plan; the harness does not choose or default it. The ordinary
|
|
711
|
+
// seed offer crosses the kernel author gate before the first birth is attempted.
|
|
712
|
+
run: () => plane.session(installWorkspace).offerOutcome(
|
|
713
|
+
domain: '__nomos_application_home_v1',
|
|
714
|
+
directiveId: 'seedBirthPolicy',
|
|
715
|
+
actor: owner,
|
|
716
|
+
authorSecret: deviceSecret,
|
|
717
|
+
payload: <String, Object?>{
|
|
718
|
+
'maxBirthsPerPrincipal': maxChildWorkspaces,
|
|
719
|
+
'setBy': owner,
|
|
720
|
+
'setAt': bornAt,
|
|
721
|
+
},
|
|
722
|
+
),
|
|
723
|
+
);
|
|
692
724
|
final outcome = await _phased(
|
|
693
725
|
phase: 'application: birth the signed-in application',
|
|
694
726
|
workspace: workspace,
|
|
@@ -707,7 +739,7 @@ class NomosTestHarness {
|
|
|
707
739
|
// Ordered application deployment is reference packages first, deferred child payload last.
|
|
708
740
|
// The parent serves the references; the receiving group is born with only its payload law.
|
|
709
741
|
'lawHash': laws.last,
|
|
710
|
-
'bornAt':
|
|
742
|
+
'bornAt': bornAt,
|
|
711
743
|
},
|
|
712
744
|
),
|
|
713
745
|
);
|
package/dart/pubspec.lock
CHANGED
package/dart/pubspec.yaml
CHANGED
|
@@ -8,7 +8,7 @@ description: >-
|
|
|
8
8
|
Nomos 2 Dart frontend types — the hand-written kernel wire types plus the
|
|
9
9
|
code-generated per-domain aggregate + directive-payload types. (De)serializes
|
|
10
10
|
the EXACT serde JSON the Rust kernel + projection produce/consume.
|
|
11
|
-
version: 0.64.
|
|
11
|
+
version: 0.64.4
|
|
12
12
|
|
|
13
13
|
environment:
|
|
14
14
|
sdk: ^3.5.0
|
|
@@ -17,7 +17,7 @@ dependencies:
|
|
|
17
17
|
# The pure-data core (wire types, offer codec, NomosReads, schema validation, error
|
|
18
18
|
# info). nomos_client depends on and re-exports it, so existing imports are unchanged.
|
|
19
19
|
# For local monorepo dev, pubspec_overrides.yaml points this at ../dart_types.
|
|
20
|
-
nomos_types: ^0.8.
|
|
20
|
+
nomos_types: ^0.8.2
|
|
21
21
|
json_schema: ^5.2.2
|
|
22
22
|
crypto: ^3.0.0
|
|
23
23
|
package_config: ^2.1.0
|
|
@@ -148,6 +148,21 @@ void main() {
|
|
|
148
148
|
final home = harness.session('home');
|
|
149
149
|
final bornEstate = await _leg(
|
|
150
150
|
'leg 2: internal CO2 law setup (Estate → Site → Asset)', () async {
|
|
151
|
+
// This is an internal framework-conformance setup, so declare its parent allowance explicitly
|
|
152
|
+
// through the same offer a compiled application ceremony uses. There is deliberately no harness
|
|
153
|
+
// or kernel fallback for a birth-capable workspace with missing policy.
|
|
154
|
+
final bornAt = DateTime.now().toUtc().toIso8601String();
|
|
155
|
+
await home.offerOutcome(
|
|
156
|
+
domain: 'co2',
|
|
157
|
+
directiveId: 'seedBirthPolicy',
|
|
158
|
+
payload: {
|
|
159
|
+
'maxBirthsPerPrincipal': 1,
|
|
160
|
+
'setBy': _owner,
|
|
161
|
+
'setAt': bornAt,
|
|
162
|
+
},
|
|
163
|
+
actor: _owner,
|
|
164
|
+
authorSecret: harness.deviceSecret(home.workspace),
|
|
165
|
+
);
|
|
151
166
|
final birth = await home.offerOutcome(
|
|
152
167
|
domain: 'co2',
|
|
153
168
|
directiveId: 'nomosBirthEstate',
|
|
@@ -157,7 +172,7 @@ void main() {
|
|
|
157
172
|
'country': 'GB',
|
|
158
173
|
'baselineTonnesCo2e': 4200,
|
|
159
174
|
'principal': _owner,
|
|
160
|
-
'bornAt':
|
|
175
|
+
'bornAt': bornAt,
|
|
161
176
|
},
|
|
162
177
|
actor: _owner,
|
|
163
178
|
);
|
package/dart_types/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
## 0.8.2
|
|
2
|
+
|
|
3
|
+
- Carry the typed bounded-startup, missing-law and governed child-policy errors used by the USDA-native
|
|
4
|
+
application lifecycle, preserving kernel refusals as actionable frontend states.
|
|
5
|
+
|
|
1
6
|
## 0.8.1
|
|
2
7
|
|
|
3
8
|
- Carry the typed USDA-native kernel protocol, plan diagnostics, reference freshness and application
|
package/dart_types/pubspec.yaml
CHANGED
|
@@ -9,7 +9,7 @@ description: >-
|
|
|
9
9
|
Pure kernel wire/data types for Nomos Dart frontends — no transport, no bridge,
|
|
10
10
|
no runtime. The hand-written serde-JSON-compatible types plus the offer codec,
|
|
11
11
|
reactive read engine, schema validation, and data-only error/trace types.
|
|
12
|
-
version: 0.8.
|
|
12
|
+
version: 0.8.2
|
|
13
13
|
|
|
14
14
|
environment:
|
|
15
15
|
sdk: ^3.5.0
|
package/package.json
CHANGED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// NOMOS — explicit application-home founding ceremony.
|
|
2
|
+
|
|
3
|
+
import type { Directive } from "./directive.js";
|
|
4
|
+
|
|
5
|
+
const APPLICATION_HOME_GENESIS_BRAND = "__nomos_application_home_genesis_v1" as const;
|
|
6
|
+
|
|
7
|
+
/** Facts fixed by the canonical receiving-group birth. Tenant law may derive founding offers
|
|
8
|
+
* from these facts; the host supplies none of them and cannot append an initializer. */
|
|
9
|
+
export interface ApplicationHomeGenesisFacts {
|
|
10
|
+
readonly owner: string;
|
|
11
|
+
readonly workspaceName: string;
|
|
12
|
+
readonly bornAt: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ApplicationHomeGenesisStep<
|
|
16
|
+
P extends Record<string, unknown> = Record<string, unknown>,
|
|
17
|
+
> {
|
|
18
|
+
readonly domain: string;
|
|
19
|
+
readonly directive: Directive<P>;
|
|
20
|
+
readonly payload: (facts: ApplicationHomeGenesisFacts) => P;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ApplicationHomeGenesis {
|
|
24
|
+
readonly [APPLICATION_HOME_GENESIS_BRAND]: true;
|
|
25
|
+
readonly steps: readonly ApplicationHomeGenesisStep<any>[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Declare one ordinary offer which must run during application-home genesis. The directive's
|
|
29
|
+
* own schema and invariants remain the authority over its payload; this helper only binds the
|
|
30
|
+
* canonical founder facts to that offer and names the law namespace which owns it. */
|
|
31
|
+
export function offerAtHomeGenesis<P extends Record<string, unknown>>(
|
|
32
|
+
domain: string,
|
|
33
|
+
directive: Directive<P>,
|
|
34
|
+
payload: (facts: ApplicationHomeGenesisFacts) => P,
|
|
35
|
+
): ApplicationHomeGenesisStep<P> {
|
|
36
|
+
const namespace = domain.trim();
|
|
37
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(namespace)) {
|
|
38
|
+
throw new Error(`offerAtHomeGenesis: '${domain}' is not a law domain name`);
|
|
39
|
+
}
|
|
40
|
+
if (directive === null || typeof directive !== "object" || typeof directive.id !== "string") {
|
|
41
|
+
throw new Error("offerAtHomeGenesis expects a finished intent or directive");
|
|
42
|
+
}
|
|
43
|
+
return Object.freeze({ domain: namespace, directive, payload });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The complete tenant-declared extension to the canonical application-home ceremony.
|
|
47
|
+
* An empty declaration is deliberate; omission means there are no tenant founding offers. */
|
|
48
|
+
export function applicationHomeGenesis<
|
|
49
|
+
const Steps extends readonly ApplicationHomeGenesisStep<any>[],
|
|
50
|
+
>(
|
|
51
|
+
...steps: Steps
|
|
52
|
+
): ApplicationHomeGenesis {
|
|
53
|
+
const seen = new Set<string>();
|
|
54
|
+
for (const step of steps) {
|
|
55
|
+
const key = `${step.domain}\u001f${step.directive.id}`;
|
|
56
|
+
if (seen.has(key)) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`applicationHomeGenesis repeats '${step.domain}/${step.directive.id}'; ` +
|
|
59
|
+
"one founding offer must have one accountable position",
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
seen.add(key);
|
|
63
|
+
}
|
|
64
|
+
return Object.freeze({
|
|
65
|
+
[APPLICATION_HOME_GENESIS_BRAND]: true,
|
|
66
|
+
steps: Object.freeze([...steps]),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function isApplicationHomeGenesis(value: unknown): value is ApplicationHomeGenesis {
|
|
71
|
+
return value !== null && typeof value === "object" &&
|
|
72
|
+
(value as Record<string, unknown>)[APPLICATION_HOME_GENESIS_BRAND] === true;
|
|
73
|
+
}
|
package/src/birth_meter.ts
CHANGED
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
* CAS-re-derived on every lane), and a synthesized framework `workspaceInvariant` (the exact
|
|
29
29
|
* WorkspaceQuota shape) refuses `birth-quota-exceeded` once the subject already holds its
|
|
30
30
|
* allowance. Allowance resolution: the latest {@link BirthGrant} for the subject if any, else
|
|
31
|
-
* the workspace's {@link BirthPolicy} singleton
|
|
31
|
+
* the workspace's REQUIRED {@link BirthPolicy} singleton. There is no compiled allowance:
|
|
32
|
+
* missing policy is invalid state and fails closed as `birth-policy-not-configured`.
|
|
32
33
|
*
|
|
33
34
|
* A3 — THE POOL CHAINS TO ROOT. A parent births a child WITH the child's own descendant budget
|
|
34
35
|
* (`buildGenesisChain`'s `descendantPool` threads a `seedBirthPolicy` genesis step), so every
|
|
@@ -164,15 +165,6 @@ export const BIRTH_FRAMEWORK_QUERIES: readonly QueryDecl[] = [
|
|
|
164
165
|
childLifecyclesByStatus,
|
|
165
166
|
];
|
|
166
167
|
|
|
167
|
-
/**
|
|
168
|
-
* THE DEFAULT BIRTH ALLOWANCE when a workspace carries neither a BirthPolicy singleton nor a grant
|
|
169
|
-
* for the subject: a CONSERVATIVE floor for non-root workspaces (a home births a handful of estate
|
|
170
|
-
* children; a platform that needs more seeds a BirthPolicy at its own genesis — the A3 induction —
|
|
171
|
-
* or grants per-subject allowances). Root/platform governance already carries the 50-workspace
|
|
172
|
-
* `WorkspaceQuota`; THIS default only ever binds law that never set its own policy.
|
|
173
|
-
*/
|
|
174
|
-
export const DEFAULT_MAX_BIRTHS_PER_PRINCIPAL = 5;
|
|
175
|
-
|
|
176
168
|
// ── the meter subject + the auto-record/auto-capture halves the `.births()` lane calls ─────────────
|
|
177
169
|
|
|
178
170
|
/** The payload-carried birth SUBJECT the meter counts under: `principal`, else `owner`, else `""`
|
|
@@ -257,9 +249,9 @@ type MeterRow = { id?: string; data?: Record<string, unknown> };
|
|
|
257
249
|
* THE BIRTH-QUOTA VERDICT — the PURE predicate over the COMMITTED captured reads (the same
|
|
258
250
|
* `{queryId,args,result}` the plan recorded, CAS-re-derived on every lane). Counts the subject's
|
|
259
251
|
* birth records and compares against its allowance (the latest BirthGrant.maxBirths if any, else
|
|
260
|
-
* BirthPolicy.maxBirthsPerPrincipal
|
|
261
|
-
*
|
|
262
|
-
*
|
|
252
|
+
* BirthPolicy.maxBirthsPerPrincipal). The NEW birth's record is NOT yet in the pre-apply state, so
|
|
253
|
+
* `count < allowance` (strict) is the correct test. A subject-less birth and a workspace with no
|
|
254
|
+
* configured policy both fail CLOSED. Identical on author, edge admission, and verify_chain.
|
|
263
255
|
*/
|
|
264
256
|
export function birthQuotaVerdict(
|
|
265
257
|
reads: readonly { queryId: string; args: Record<string, string>; result: unknown }[],
|
|
@@ -277,7 +269,7 @@ export function birthQuotaVerdict(
|
|
|
277
269
|
.filter((r) => r.data?.status === "retired" || r.data?.status === "reclaimed")
|
|
278
270
|
.length;
|
|
279
271
|
const count = Math.max(0, born - terminal);
|
|
280
|
-
// ALLOWANCE: the LATEST BirthGrant (max grantedAt) wins; else the BirthPolicy singleton
|
|
272
|
+
// ALLOWANCE: the LATEST BirthGrant (max grantedAt) wins; else the REQUIRED BirthPolicy singleton.
|
|
281
273
|
let allowance: number | undefined;
|
|
282
274
|
let latestGrantedAt = -Infinity;
|
|
283
275
|
for (const g of rowsOf(birthGrantByPrincipal.id, "principal", subject)) {
|
|
@@ -293,7 +285,7 @@ export function birthQuotaVerdict(
|
|
|
293
285
|
const max = Number(policyRows[0]?.data?.maxBirthsPerPrincipal ?? NaN);
|
|
294
286
|
if (Number.isFinite(max)) allowance = max;
|
|
295
287
|
}
|
|
296
|
-
if (allowance === undefined)
|
|
288
|
+
if (allowance === undefined) return { reject: "birth-policy-not-configured" };
|
|
297
289
|
return count < allowance ? { accept: true } : { reject: "birth-quota-exceeded" };
|
|
298
290
|
}
|
|
299
291
|
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
BirthGrant,
|
|
32
32
|
} from "./birth_meter.js";
|
|
33
33
|
import {
|
|
34
|
+
grantBirthAllowance,
|
|
34
35
|
reclaimChild,
|
|
35
36
|
reclaimChildAsAdmin,
|
|
36
37
|
reclaimChildAsAdminRequiresRetired,
|
|
@@ -41,6 +42,8 @@ import {
|
|
|
41
42
|
retireChildAsAdminRequiresBirth,
|
|
42
43
|
retireChildRequiresBirth,
|
|
43
44
|
retireChildNotReclaimed,
|
|
45
|
+
seedBirthPolicy,
|
|
46
|
+
setBirthPolicy,
|
|
44
47
|
} from "./framework/birth_meter.js";
|
|
45
48
|
import { RelationTuple, relationTupleInstanceId, relationTuplesByObject } from "./framework/relations.js";
|
|
46
49
|
import { GENESIS_WARRANT_ID, GenesisWarrant, seedGenesis } from "./framework/genesis.js";
|
|
@@ -236,6 +239,12 @@ export function withBirthMeterExports<M extends Record<string, unknown>>(mod: M)
|
|
|
236
239
|
internalize("__nomos_NomosChildLifecycle", NomosChildLifecycle);
|
|
237
240
|
internalize("__nomos_BirthPolicy", BirthPolicy);
|
|
238
241
|
internalize("__nomos_BirthGrant", BirthGrant);
|
|
242
|
+
// A birth-capable workspace is never allowed to depend on a compiled allowance. Its law must
|
|
243
|
+
// carry both the genesis seed and the governed amendment verbs; presentation codegen keeps these
|
|
244
|
+
// framework controls private while the kernel can still route and authorize them normally.
|
|
245
|
+
internalize("__nomos_seedBirthPolicy", seedBirthPolicy);
|
|
246
|
+
internalize("__nomos_setBirthPolicy", setBirthPolicy);
|
|
247
|
+
internalize("__nomos_grantBirthAllowance", grantBirthAllowance);
|
|
239
248
|
internalize("__nomos_retireChild", retireChild);
|
|
240
249
|
internalize("__nomos_reclaimChild", reclaimChild);
|
|
241
250
|
internalize("__nomos_retireChildAsAdmin", retireChildAsAdmin);
|
package/src/births.ts
CHANGED
|
@@ -84,6 +84,11 @@ export interface BusinessBirthDecl<
|
|
|
84
84
|
readonly founderRoles: readonly string[];
|
|
85
85
|
/** The business noun those founder roles govern. */
|
|
86
86
|
readonly founderScope: string;
|
|
87
|
+
/** Explicit policy inherited by the BORN workspace. Zero declares a leaf workspace. */
|
|
88
|
+
readonly maxChildWorkspacesPerUser: number;
|
|
89
|
+
/** @internal Compiler-derived namespaces installed into the born workspace. The declaration owns
|
|
90
|
+
* the value; this list only repeats that value wherever the composed law requires its own state. */
|
|
91
|
+
__nomosBirthPolicyDomains?: readonly string[];
|
|
87
92
|
/** Optional business noun used only to make the durable address readable. Defaults to the aggregate id. */
|
|
88
93
|
readonly namespace?: string;
|
|
89
94
|
/** Owning business domain, needed only when another domain composes this birth into a wider intent. */
|
|
@@ -114,6 +119,11 @@ export interface BusinessBirthSpec<F extends Record<string, Field>> {
|
|
|
114
119
|
/** Business noun governed by those roles. Defaults to the born aggregate noun. */
|
|
115
120
|
readonly scope?: string;
|
|
116
121
|
};
|
|
122
|
+
/** Law for the workspace being born. This is mandatory because silence must not become a
|
|
123
|
+
* compiler-chosen quota; zero explicitly declares that the born workspace is a leaf. */
|
|
124
|
+
readonly children: {
|
|
125
|
+
readonly maxWorkspacesPerUser: number;
|
|
126
|
+
};
|
|
117
127
|
/** Additional atomic business initialization which belongs to the same birth. */
|
|
118
128
|
readonly initialise?: (
|
|
119
129
|
root: AggregateRef<F>,
|
|
@@ -174,6 +184,13 @@ export function birth<F extends Record<string, Field>>(
|
|
|
174
184
|
`birth(${aggregate.id}): founder scope '${founderScope}' is not a business noun`,
|
|
175
185
|
);
|
|
176
186
|
}
|
|
187
|
+
const maxChildWorkspacesPerUser = spec.children?.maxWorkspacesPerUser;
|
|
188
|
+
if (!Number.isInteger(maxChildWorkspacesPerUser) || maxChildWorkspacesPerUser < 0) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
`birth(${aggregate.id}): children.maxWorkspacesPerUser must be an explicit non-negative integer; ` +
|
|
191
|
+
"zero declares a leaf workspace and Nomos has no compiled fallback",
|
|
192
|
+
);
|
|
193
|
+
}
|
|
177
194
|
type Payload = BusinessBirthPayload<F>;
|
|
178
195
|
const declaration = {
|
|
179
196
|
__isBusinessBirth: true,
|
|
@@ -188,6 +205,7 @@ export function birth<F extends Record<string, Field>>(
|
|
|
188
205
|
},
|
|
189
206
|
founderRoles,
|
|
190
207
|
founderScope,
|
|
208
|
+
maxChildWorkspacesPerUser,
|
|
191
209
|
...(spec.namespace !== undefined ? { namespace: spec.namespace } : {}),
|
|
192
210
|
};
|
|
193
211
|
const inDomain = (domain: string): BusinessBirthDecl<Payload, F> => {
|
package/src/business_birth.ts
CHANGED
|
@@ -51,6 +51,8 @@ import {
|
|
|
51
51
|
|
|
52
52
|
/** Private recipe selectors. The kernel resolves these against the parent's current installed law. */
|
|
53
53
|
export const CURRENT_LAW_PREFIX = "nomos:current-law:";
|
|
54
|
+
/** @internal Compiler-only domain enumeration. It carries no allowance or authority. */
|
|
55
|
+
export const BUSINESS_BIRTH_POLICY_DOMAINS_EXPORT = "__nomos_birth_policy_domains_c9f14a";
|
|
54
56
|
|
|
55
57
|
export interface BusinessBirthMetadata {
|
|
56
58
|
readonly aggregateId: string;
|
|
@@ -124,6 +126,7 @@ const applicationIntentOnlyBirths = new WeakSet<object>();
|
|
|
124
126
|
function lowerOne<P extends Record<string, unknown>, F extends Record<string, Field>>(
|
|
125
127
|
decl: BusinessBirthDecl<P, F>,
|
|
126
128
|
domain: string,
|
|
129
|
+
initialBirthPolicyDomains: readonly string[] = [domain],
|
|
127
130
|
): Record<string, unknown> {
|
|
128
131
|
const fields = (decl.payloadSchema as z.ZodObject<z.ZodRawShape>).shape;
|
|
129
132
|
for (const field of INTERNAL_FIELDS) {
|
|
@@ -245,6 +248,16 @@ function lowerOne<P extends Record<string, unknown>, F extends Record<string, Fi
|
|
|
245
248
|
payload: { domainHash: `${CURRENT_LAW_PREFIX}${domain}` },
|
|
246
249
|
domainHash: `${CURRENT_LAW_PREFIX}${domain}`,
|
|
247
250
|
},
|
|
251
|
+
...(decl.__nomosBirthPolicyDomains ?? initialBirthPolicyDomains).map((policyDomain) => ({
|
|
252
|
+
domain: policyDomain,
|
|
253
|
+
directiveId: "seedBirthPolicy",
|
|
254
|
+
actor: founder,
|
|
255
|
+
payload: {
|
|
256
|
+
maxBirthsPerPrincipal: decl.maxChildWorkspacesPerUser,
|
|
257
|
+
setBy: founder,
|
|
258
|
+
setAt: p.bornAt,
|
|
259
|
+
},
|
|
260
|
+
})),
|
|
248
261
|
{
|
|
249
262
|
domain,
|
|
250
263
|
directiveId: seedId,
|
|
@@ -396,10 +409,29 @@ export function businessBirthConsequence<
|
|
|
396
409
|
return lowered;
|
|
397
410
|
}
|
|
398
411
|
|
|
412
|
+
/** @internal Bind the compiler-derived composed-law namespaces to declarations already captured by
|
|
413
|
+
* wider business intents. This chooses no policy value; it only repeats the tenant-declared value
|
|
414
|
+
* across the namespaces whose USDA says they can birth. */
|
|
415
|
+
export function bindBusinessBirthPolicyDomains(
|
|
416
|
+
mod: Record<string, unknown>,
|
|
417
|
+
domains: readonly string[],
|
|
418
|
+
): void {
|
|
419
|
+
const declarations = businessBirthsOf(mod);
|
|
420
|
+
if (declarations.length === 0) return;
|
|
421
|
+
const unique = [...new Set(domains)];
|
|
422
|
+
if (unique.length === 0 || unique.some((value) => value.trim().length === 0)) {
|
|
423
|
+
throw new Error("birth policy namespaces must be a non-empty list of domain names");
|
|
424
|
+
}
|
|
425
|
+
for (const declaration of declarations) {
|
|
426
|
+
declaration.__nomosBirthPolicyDomains = unique;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
399
430
|
/** Lower the one tenant-facing birth declaration into ordinary, gate-visible framework directives. */
|
|
400
431
|
export function expandBusinessBirthExports<M extends Record<string, unknown>>(
|
|
401
432
|
mod: M,
|
|
402
433
|
domain: string,
|
|
434
|
+
birthPolicyDomains?: readonly string[],
|
|
403
435
|
): Record<string, unknown> {
|
|
404
436
|
const declarations = businessBirthsOf(mod);
|
|
405
437
|
if (declarations.length === 0) return mod;
|
|
@@ -409,7 +441,18 @@ export function expandBusinessBirthExports<M extends Record<string, unknown>>(
|
|
|
409
441
|
"ambiguous — model one born aggregate root per domain",
|
|
410
442
|
);
|
|
411
443
|
}
|
|
412
|
-
const
|
|
444
|
+
const compiledDomains = mod[BUSINESS_BIRTH_POLICY_DOMAINS_EXPORT];
|
|
445
|
+
const selectedDomains = birthPolicyDomains ?? (
|
|
446
|
+
Array.isArray(compiledDomains) && compiledDomains.every((value) => typeof value === "string")
|
|
447
|
+
? compiledDomains as string[]
|
|
448
|
+
: [domain]
|
|
449
|
+
);
|
|
450
|
+
const policyDomains = [...new Set(selectedDomains)];
|
|
451
|
+
if (policyDomains.length === 0 || policyDomains.some((value) => value.trim().length === 0)) {
|
|
452
|
+
throw new Error(`birth(${declarations[0]!.aggregate.id}): compiled law declares no policy namespaces`);
|
|
453
|
+
}
|
|
454
|
+
bindBusinessBirthPolicyDomains(mod, policyDomains);
|
|
455
|
+
const lowered = lowerOne(declarations[0]!, domain, policyDomains);
|
|
413
456
|
for (const [key, generated] of Object.entries(lowered)) {
|
|
414
457
|
if (!Object.prototype.hasOwnProperty.call(mod, key)) continue;
|
|
415
458
|
const existing = mod[key];
|
|
@@ -405,6 +405,7 @@ export function generateDartApplication(
|
|
|
405
405
|
applicationMembers.set(name, owner);
|
|
406
406
|
};
|
|
407
407
|
claimApplicationMember("activity", "the generated business history");
|
|
408
|
+
claimApplicationMember("model", "the generated model lifecycle");
|
|
408
409
|
const collectionLines = new Map<string, string[]>();
|
|
409
410
|
const collectionMembers = new Map<string, Set<string>>();
|
|
410
411
|
const siblingImports = new Map<string, Set<string>>();
|
|
@@ -1922,7 +1923,9 @@ export function generateDartApplication(
|
|
|
1922
1923
|
`import 'package:nomos_flutter/src/application_status.dart' show ApplicationSignIn, ApplicationStatus;`,
|
|
1923
1924
|
]
|
|
1924
1925
|
: []),
|
|
1925
|
-
`import 'package:nomos_flutter/src/generated_application_host.dart' show
|
|
1926
|
+
`import 'package:nomos_flutter/src/generated_application_host.dart' show ${
|
|
1927
|
+
tenantRoot ? "NomosApplicationStartupState, " : ""
|
|
1928
|
+
}NmsInternalApplicationUserBinding${
|
|
1926
1929
|
tenantRoot || sharingPolicies.length > 0
|
|
1927
1930
|
? ", NmsInternalApplicationHost"
|
|
1928
1931
|
: ""
|
|
@@ -1972,6 +1975,9 @@ export function generateDartApplication(
|
|
|
1972
1975
|
` ${application}._(${client} client${
|
|
1973
1976
|
tenantRoot ? ", User currentUser" : ""
|
|
1974
1977
|
}) : ${applicationInitializers.join(", ")};`,
|
|
1978
|
+
``,
|
|
1979
|
+
` /// Prove, adopt or restore the law this application was generated against.`,
|
|
1980
|
+
` NomosModel get model => _client.model;`,
|
|
1975
1981
|
...(opts.flutterHost === true && tenantRoot
|
|
1976
1982
|
? [
|
|
1977
1983
|
``,
|
|
@@ -1995,10 +2001,12 @@ export function generateDartApplication(
|
|
|
1995
2001
|
` required Widget Function(BuildContext context, ${application} application) builder,`,
|
|
1996
2002
|
` Widget Function(BuildContext context)? loadingBuilder,`,
|
|
1997
2003
|
` Widget Function(BuildContext context, Object error, VoidCallback retry)? errorBuilder,`,
|
|
2004
|
+
` ValueChanged<NomosApplicationStartupState>? onStartupStateChanged,`,
|
|
1998
2005
|
` }) => NmsInternalApplicationHost.home<${application}>(`,
|
|
1999
2006
|
` key: key, cloud: cloud, application: application, signIn: signIn,`,
|
|
2000
2007
|
` bind: nomosInternalBind${application}, builder: builder,`,
|
|
2001
2008
|
` loadingBuilder: loadingBuilder, errorBuilder: errorBuilder,`,
|
|
2009
|
+
` onStartupStateChanged: onStartupStateChanged,`,
|
|
2002
2010
|
` );`,
|
|
2003
2011
|
]
|
|
2004
2012
|
: []),
|
|
@@ -90,6 +90,7 @@ export function generateDartScenario(
|
|
|
90
90
|
readonly surfaceName?: string;
|
|
91
91
|
readonly applicationFile?: string;
|
|
92
92
|
readonly applicationModules?: readonly DomainModule[];
|
|
93
|
+
readonly applicationMaxChildWorkspacesPerUser?: number;
|
|
93
94
|
} = {},
|
|
94
95
|
): string {
|
|
95
96
|
const domain = options.surfaceName ?? mod.domain ?? mod.name;
|
|
@@ -195,6 +196,9 @@ export function generateDartScenario(
|
|
|
195
196
|
` final packageFiles = await _applicationPackages();`,
|
|
196
197
|
` final environment = await NomosTestHarness.ephemeral(`,
|
|
197
198
|
` lawUsdzPaths: packageFiles.map((file) => file.path).toList(growable: false),`,
|
|
199
|
+
...(options.applicationMaxChildWorkspacesPerUser === undefined
|
|
200
|
+
? []
|
|
201
|
+
: [` applicationMaxChildWorkspacesPerUser: ${options.applicationMaxChildWorkspacesPerUser},`]),
|
|
198
202
|
` ownerSubject: person._principal,`,
|
|
199
203
|
` workspace: nmsInternalReceivingGroupNameOf(person._principal),`,
|
|
200
204
|
` nodeEntry: Platform.environment['NOMOS_NODE_ENTRY'],`,
|
|
@@ -224,6 +228,9 @@ export function generateDartScenario(
|
|
|
224
228
|
` final cloud = await NomosTestCloud.start(`,
|
|
225
229
|
` serverEntry: serverEntry, platform: applicationName,`,
|
|
226
230
|
` lawUsdzPaths: packageFiles.map((file) => file.path).toList(growable: false),`,
|
|
231
|
+
...(options.applicationMaxChildWorkspacesPerUser === undefined
|
|
232
|
+
? []
|
|
233
|
+
: [` applicationMaxChildWorkspacesPerUser: ${options.applicationMaxChildWorkspacesPerUser},`]),
|
|
227
234
|
` onDiagnostic: Platform.environment['NOMOS_TEST_DIAGNOSTICS'] == '1' ? stderr.writeln : null,`,
|
|
228
235
|
` );`,
|
|
229
236
|
` late final NomosTestHarness environment;`,
|
package/src/codegen_proof.ts
CHANGED
|
@@ -2441,6 +2441,9 @@ export interface TsProofOptions {
|
|
|
2441
2441
|
readonly domainHash: string;
|
|
2442
2442
|
/** Stable-id structural interface used to teach the offline harness the hidden owned-birth seal slots. */
|
|
2443
2443
|
readonly interface?: Readonly<Record<string, DomainInterface>>;
|
|
2444
|
+
/** Compiled application-home child allowance. The local proof cloud seeds this exact law fact before
|
|
2445
|
+
* warranting its throwaway platform; absent means this is a domain package, not an application root. */
|
|
2446
|
+
readonly applicationMaxChildWorkspacesPerUser?: number;
|
|
2444
2447
|
}
|
|
2445
2448
|
|
|
2446
2449
|
/** THE PROOF TAXONOMY (co2 feedback): which legs a domain carries.
|
|
@@ -2455,6 +2458,8 @@ export type ProofKind = "compile-valid" | "standalone-provable" | "child-birth-p
|
|
|
2455
2458
|
* marker shape; the `proof-legs.json` index is a map of these by domain key). */
|
|
2456
2459
|
export interface ProofLegs {
|
|
2457
2460
|
readonly domain: string;
|
|
2461
|
+
/** Application policy carried to the local proof ceremony; never a runner default. */
|
|
2462
|
+
readonly applicationMaxChildWorkspacesPerUser?: number;
|
|
2458
2463
|
/** THE TAXONOMY (co2 feedback) — the legs this domain has, in proof order. EVERY domain has at least
|
|
2459
2464
|
* `compile-valid`. OPTIONAL + ADDITIVE: an older-compiler legs file without it is treated as
|
|
2460
2465
|
* `["standalone-provable"]` (it only emitted standalone-provable domains). */
|
|
@@ -3223,7 +3228,12 @@ function generateSealedApplicationProof(
|
|
|
3223
3228
|
const domain = mod.domain ?? mod.name;
|
|
3224
3229
|
const applicationName = tsApplicationName(opts.packageName);
|
|
3225
3230
|
const opener = `open${applicationName}`;
|
|
3226
|
-
const marker = JSON.stringify(
|
|
3231
|
+
const marker = JSON.stringify({
|
|
3232
|
+
...primaryLegs,
|
|
3233
|
+
...(opts.applicationMaxChildWorkspacesPerUser === undefined
|
|
3234
|
+
? {}
|
|
3235
|
+
: { applicationMaxChildWorkspacesPerUser: opts.applicationMaxChildWorkspacesPerUser }),
|
|
3236
|
+
});
|
|
3227
3237
|
const standalone = orderedStandaloneCreates(mod)[0];
|
|
3228
3238
|
const businessBirth = primaryLegs.childBirth?.businessAggregateId !== undefined
|
|
3229
3239
|
? primaryLegs.childBirth
|