@githolon/dsl 0.103.0 → 0.103.2
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 +14 -0
- package/dart/lib/src/dev_cloud.dart +5 -0
- package/dart/lib/src/runner/node-entry.mjs +47 -4
- package/dart/lib/src/runner/offline-install.mjs +1 -1
- package/dart/lib/src/test_harness.dart +33 -1
- package/dart/lib/src/webview_bridge.dart +51 -21
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/acquire_session_test.dart +9 -0
- 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 +27 -1
- package/src/codegen_dart_scenario.ts +7 -0
- package/src/codegen_proof.ts +11 -1
- package/src/compile_package_main.ts +123 -7
- 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,17 @@
|
|
|
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
|
+
|
|
8
|
+
## 0.64.3
|
|
9
|
+
|
|
10
|
+
- Single-flight generated child-workspace acquisition and continuous sync so concurrent references share one
|
|
11
|
+
open, one lifecycle result and one error rather than cloning or registering the same workspace twice.
|
|
12
|
+
- Generate separate shared-workspace reads so applications can render denormalized owned-workspace cards
|
|
13
|
+
without eagerly opening every founded child merely to discover genuinely shared lives.
|
|
14
|
+
|
|
1
15
|
## 0.64.2
|
|
2
16
|
|
|
3
17
|
- Execute generated applications through the typed USDA-native kernel boundary, including native read
|
|
@@ -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";
|
|
@@ -16,6 +16,43 @@ var RUNNER_BUNDLE_VERSION = typeof __NOMOS_BUNDLE_VERSION__ !== "undefined" ? __
|
|
|
16
16
|
var plainObject = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
17
17
|
var errorMessage = (e, fallback = "nomos runtime error") => String(e?.message ?? e ?? fallback);
|
|
18
18
|
var compact = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0 && v !== null && v !== ""));
|
|
19
|
+
var PERF_COMPONENT_LIMIT = 12;
|
|
20
|
+
var PERF_PHASE_LIMIT = 16;
|
|
21
|
+
var PERF_NOTE_LIMIT = 12;
|
|
22
|
+
var numericEntries = (value, limit) => {
|
|
23
|
+
const entries = Object.entries(plainObject(value) || {}).filter(([, v]) => typeof v === "number" && Number.isFinite(v) && v !== 0).map(([name, n]) => [name, n]).sort((a, b) => Math.abs(b[1]) - Math.abs(a[1]));
|
|
24
|
+
return {
|
|
25
|
+
values: Object.fromEntries(entries.slice(0, limit)),
|
|
26
|
+
omitted: Math.max(0, entries.length - limit)
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
function boundedKernelSpans(value) {
|
|
30
|
+
const root = plainObject(value);
|
|
31
|
+
if (!root) return value;
|
|
32
|
+
const ranked = Object.entries(root).map(([name, raw]) => {
|
|
33
|
+
const row = plainObject(raw) || {};
|
|
34
|
+
const phases = numericEntries(row.phases, PERF_PHASE_LIMIT);
|
|
35
|
+
const notes = numericEntries(row.notes, PERF_NOTE_LIMIT);
|
|
36
|
+
const flat = numericEntries(row, PERF_PHASE_LIMIT);
|
|
37
|
+
const totalUs = typeof row.totalUs === "number" && Number.isFinite(row.totalUs) ? row.totalUs : Object.values(flat.values).reduce((sum, n) => sum + Number(n), 0);
|
|
38
|
+
const shaped = plainObject(row.phases) || plainObject(row.notes) || row.totalUs !== void 0 ? compact({
|
|
39
|
+
totalUs,
|
|
40
|
+
phases: Object.keys(phases.values).length ? phases.values : void 0,
|
|
41
|
+
notes: Object.keys(notes.values).length ? notes.values : void 0,
|
|
42
|
+
omittedPhases: phases.omitted || void 0,
|
|
43
|
+
omittedNotes: notes.omitted || void 0
|
|
44
|
+
}) : compact({
|
|
45
|
+
...flat.values,
|
|
46
|
+
omittedMetrics: flat.omitted || void 0
|
|
47
|
+
});
|
|
48
|
+
return { name, totalUs, shaped };
|
|
49
|
+
}).sort((a, b) => b.totalUs - a.totalUs);
|
|
50
|
+
const kept = ranked.slice(0, PERF_COMPONENT_LIMIT);
|
|
51
|
+
return {
|
|
52
|
+
...Object.fromEntries(kept.map(({ name, shaped }) => [name, shaped])),
|
|
53
|
+
...ranked.length > PERF_COMPONENT_LIMIT ? { _summary: { omittedComponents: ranked.length - PERF_COMPONENT_LIMIT } } : {}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
19
56
|
var errorInfo = (e, req = {}, component = "nomos.webview.runner") => {
|
|
20
57
|
const err = e;
|
|
21
58
|
const existing = plainObject(err?.errorInfo) || plainObject(err?.info);
|
|
@@ -99,6 +136,7 @@ function createWebviewRunner({ post: post2, openRealm: openRealm2 }) {
|
|
|
99
136
|
const spanStarts = /* @__PURE__ */ new Map();
|
|
100
137
|
const forwardSpan = (session, raw) => {
|
|
101
138
|
const s = plainObject(raw) ?? {};
|
|
139
|
+
const presented = s.kernelSpans === void 0 ? s : { ...s, kernelSpans: boundedKernelSpans(s.kernelSpans) };
|
|
102
140
|
const id = String(s.id ?? "");
|
|
103
141
|
if (s.phase === "begin" && id) {
|
|
104
142
|
spanStarts.set(id, s);
|
|
@@ -115,13 +153,13 @@ function createWebviewRunner({ post: post2, openRealm: openRealm2 }) {
|
|
|
115
153
|
elapsedMs,
|
|
116
154
|
domain: s.domain,
|
|
117
155
|
directiveId: s.directiveId,
|
|
118
|
-
kernelSpans:
|
|
156
|
+
kernelSpans: presented.kernelSpans,
|
|
119
157
|
born: s.born,
|
|
120
158
|
birthOutcomes: s.birthOutcomes
|
|
121
159
|
}))}`);
|
|
122
160
|
}
|
|
123
161
|
}
|
|
124
|
-
post2({ v: WEBVIEW_BRIDGE_VERSION, event: "span", session, span:
|
|
162
|
+
post2({ v: WEBVIEW_BRIDGE_VERSION, event: "span", session, span: presented });
|
|
125
163
|
};
|
|
126
164
|
const pendingCustodyAsks = /* @__PURE__ */ new Map();
|
|
127
165
|
let connectGate = Promise.resolve();
|
|
@@ -566,7 +604,12 @@ function createWebviewRunner({ post: post2, openRealm: openRealm2 }) {
|
|
|
566
604
|
return await (await need(a)).__nomosPrepareOwnedRecordAction({ action: a.action, scope: a.scope, record: a.record, peer: a.peer });
|
|
567
605
|
},
|
|
568
606
|
async cryptoOwnedBirthScopeKey(a) {
|
|
569
|
-
return await (await need(a)).__nomosOwnedBirthScopeKey({
|
|
607
|
+
return await (await need(a)).__nomosOwnedBirthScopeKey({
|
|
608
|
+
scope: a.scope,
|
|
609
|
+
namespace: a.namespace,
|
|
610
|
+
key: a.key,
|
|
611
|
+
...a.record !== void 0 ? { record: a.record } : {}
|
|
612
|
+
});
|
|
570
613
|
},
|
|
571
614
|
async cryptoOwnedDeviceRequest(a) {
|
|
572
615
|
return { request: await (await need(a)).__nomosOwnedDeviceRequest({ scope: a.scope }) };
|
|
@@ -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
|
);
|
|
@@ -1039,6 +1039,11 @@ class NomosBridge {
|
|
|
1039
1039
|
// [acquireSession]'s per-workspace single-flight: workspace → the in-flight open every concurrent
|
|
1040
1040
|
// acquirer joins (removed when it completes; a live session then hits the fast path instead).
|
|
1041
1041
|
final Map<String, Future<NomosSession>> _acquiring = {};
|
|
1042
|
+
// Every workspace opened as part of one stateful application participates in continuous custody exchange.
|
|
1043
|
+
// Starting that lifecycle is itself single-flight: generated navigation may discover the same child through
|
|
1044
|
+
// several references, but the runner gets one autoSync registration for that session.
|
|
1045
|
+
final Set<String> _autoSyncSessions = {};
|
|
1046
|
+
final Map<String, Future<void>> _autoSyncStarting = {};
|
|
1042
1047
|
int _seq = 0;
|
|
1043
1048
|
final String _traceId = _newTraceId();
|
|
1044
1049
|
late final StreamSubscription<Map<String, Object?>> _sub;
|
|
@@ -1875,14 +1880,34 @@ class NomosBridge {
|
|
|
1875
1880
|
plane._sessionPrincipals[workspace] = sourcePrincipal;
|
|
1876
1881
|
}
|
|
1877
1882
|
|
|
1883
|
+
Future<NomosSession> ready(NomosSession opened) async {
|
|
1884
|
+
inheritPrincipal();
|
|
1885
|
+
if (!plane._autoSyncSessions.contains(workspace)) {
|
|
1886
|
+
final start = plane._autoSyncStarting.putIfAbsent(workspace, () async {
|
|
1887
|
+
await opened.autoSync();
|
|
1888
|
+
plane._autoSyncSessions.add(workspace);
|
|
1889
|
+
});
|
|
1890
|
+
try {
|
|
1891
|
+
await start;
|
|
1892
|
+
} finally {
|
|
1893
|
+
if (identical(plane._autoSyncStarting[workspace], start)) {
|
|
1894
|
+
plane._autoSyncStarting.remove(workspace);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
return opened;
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1878
1901
|
if (plane._sessionNames.contains(workspace)) {
|
|
1879
1902
|
inheritPrincipal();
|
|
1880
|
-
return
|
|
1903
|
+
return ready(session(workspace));
|
|
1881
1904
|
}
|
|
1882
1905
|
// SINGLE-FLIGHT: one in-flight open per workspace per plane; concurrent callers join it.
|
|
1883
1906
|
return plane._acquiring.putIfAbsent(workspace, () async {
|
|
1884
1907
|
try {
|
|
1885
|
-
if (plane._sessionNames.contains(workspace))
|
|
1908
|
+
if (plane._sessionNames.contains(workspace)) {
|
|
1909
|
+
return ready(session(workspace));
|
|
1910
|
+
}
|
|
1886
1911
|
// Consult the plane's TRUTHFUL registry (mounted ∪ parked ∪ resident custody — the runner's
|
|
1887
1912
|
// realm.list()): anything the device already holds opens locally, no cloud, no credentials.
|
|
1888
1913
|
var resident = false;
|
|
@@ -1898,8 +1923,7 @@ class NomosBridge {
|
|
|
1898
1923
|
session: workspace,
|
|
1899
1924
|
allowUnborn: true,
|
|
1900
1925
|
authorSecret: authorSecret);
|
|
1901
|
-
|
|
1902
|
-
return session(workspace);
|
|
1926
|
+
return ready(session(workspace));
|
|
1903
1927
|
}
|
|
1904
1928
|
if (ifAbsentCredentials == null) {
|
|
1905
1929
|
throw StateError(
|
|
@@ -1916,8 +1940,7 @@ class NomosBridge {
|
|
|
1916
1940
|
authorSecret: c.authorSecret ?? authorSecret,
|
|
1917
1941
|
allowUnborn: c.allowUnborn,
|
|
1918
1942
|
);
|
|
1919
|
-
|
|
1920
|
-
return session(workspace);
|
|
1943
|
+
return ready(session(workspace));
|
|
1921
1944
|
} finally {
|
|
1922
1945
|
// completed (either way) — the next acquire re-evaluates fresh (a live session hits the fast path).
|
|
1923
1946
|
plane._acquiring.remove(workspace);
|
|
@@ -1935,11 +1958,12 @@ class NomosBridge {
|
|
|
1935
1958
|
/// @internal The immutable genesis of a sibling custody chain. This is observational metadata used to
|
|
1936
1959
|
/// compose an ordinary source-enrolment offer; the kernel still reopens and proves every foreign read.
|
|
1937
1960
|
Future<String> nomosWorkspaceGenesis(String workspace,
|
|
1938
|
-
|
|
1961
|
+
{String? session}) async =>
|
|
1939
1962
|
(_asMap(await _call('nomosWorkspaceGenesis', {
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1963
|
+
'workspace': workspace,
|
|
1964
|
+
if (session != null) 'session': session,
|
|
1965
|
+
}))['genesis'] ??
|
|
1966
|
+
'')
|
|
1943
1967
|
.toString();
|
|
1944
1968
|
|
|
1945
1969
|
/// UNDO the most-recent action via the kernel STRIKE primitive (the fold drops the struck intent by parity).
|
|
@@ -2452,7 +2476,8 @@ class NomosBridge {
|
|
|
2452
2476
|
/// Compare an immutable publication pin with the stable series' one compiled
|
|
2453
2477
|
/// current pointer. This is an O(current-state) check: it neither resolves the
|
|
2454
2478
|
/// pinned publication nor reconstructs aggregate history.
|
|
2455
|
-
Future<ReferenceFreshness<TVersion>>
|
|
2479
|
+
Future<ReferenceFreshness<TVersion>>
|
|
2480
|
+
checkReferenceFreshness<TVersion, TSeries>(
|
|
2456
2481
|
VersionedNomosRef<TVersion, TSeries> reference,
|
|
2457
2482
|
) async {
|
|
2458
2483
|
final result = _asMap(await _call('checkRefFreshness', {
|
|
@@ -2460,8 +2485,7 @@ class NomosBridge {
|
|
|
2460
2485
|
{
|
|
2461
2486
|
'pinned': NmsInternalNomosRefs.toJson(reference.pinned),
|
|
2462
2487
|
'series': NmsInternalNomosRefs.toJson(reference.series),
|
|
2463
|
-
'currentField':
|
|
2464
|
-
NmsInternalVersionedNomosRefs.currentField(reference),
|
|
2488
|
+
'currentField': NmsInternalVersionedNomosRefs.currentField(reference),
|
|
2465
2489
|
},
|
|
2466
2490
|
],
|
|
2467
2491
|
}));
|
|
@@ -2666,8 +2690,8 @@ class NomosBridge {
|
|
|
2666
2690
|
}));
|
|
2667
2691
|
|
|
2668
2692
|
/// FRAMEWORK-INTERNAL business birth: derive the unborn child's owned key from its deterministic address.
|
|
2669
|
-
Future<NomosScopeKey> nomosOwnedBirthScopeKey(
|
|
2670
|
-
|
|
2693
|
+
Future<NomosScopeKey> nomosOwnedBirthScopeKey(String scope, String namespace,
|
|
2694
|
+
Object? key, String recordType, String recordId) async {
|
|
2671
2695
|
final out = _asMap(await _call('cryptoOwnedBirthScopeKey', {
|
|
2672
2696
|
'scope': scope,
|
|
2673
2697
|
'namespace': namespace,
|
|
@@ -2799,14 +2823,20 @@ class NomosBridge {
|
|
|
2799
2823
|
/// Each round arrives on [syncEvents] as `{ event:'sync', reason, result }`; socket state as `{event:'link'}`.
|
|
2800
2824
|
/// Call once after [connect] — then the app never polls.
|
|
2801
2825
|
Future<void> autoSync(
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2826
|
+
{int? debounceMs, int? fallbackMs, String? signalId}) async {
|
|
2827
|
+
await _call('autoSync', {
|
|
2828
|
+
if (debounceMs != null) 'debounceMs': debounceMs,
|
|
2829
|
+
if (fallbackMs != null) 'fallbackMs': fallbackMs,
|
|
2830
|
+
if (signalId != null) 'signalId': signalId
|
|
2831
|
+
});
|
|
2832
|
+
final workspace = _sessionName;
|
|
2833
|
+
if (workspace != null) _eventPlane._autoSyncSessions.add(workspace);
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2808
2836
|
Future<void> stopAutoSync() async {
|
|
2809
2837
|
await _call('stopAutoSync');
|
|
2838
|
+
final workspace = _sessionName;
|
|
2839
|
+
if (workspace != null) _eventPlane._autoSyncSessions.remove(workspace);
|
|
2810
2840
|
}
|
|
2811
2841
|
|
|
2812
2842
|
/// Background-sync telemetry: each autoSync round (`event:'sync'`) and doorbell socket state (`event:'link'`).
|
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
|
|
@@ -25,6 +25,11 @@ class FakeRunner implements BridgeTransport {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
int get connectCount => sent.where((m) => m['op'] == 'connect').length;
|
|
28
|
+
int autoSyncCount(String workspace) => sent
|
|
29
|
+
.where((m) =>
|
|
30
|
+
m['op'] == 'autoSync' &&
|
|
31
|
+
((m['args'] as Map)['session']).toString() == workspace)
|
|
32
|
+
.length;
|
|
28
33
|
Map<String, Object?> connectArgs(int i) =>
|
|
29
34
|
(sent.where((m) => m['op'] == 'connect').elementAt(i)['args'] as Map)
|
|
30
35
|
.cast<String, Object?>();
|
|
@@ -89,6 +94,8 @@ void main() {
|
|
|
89
94
|
fail('credentials must not be requested for a mounted session'));
|
|
90
95
|
expect(identical(s, bridge.session('home')), isTrue);
|
|
91
96
|
expect(runner.connectCount, 1, reason: 'no re-connect for a live session');
|
|
97
|
+
expect(runner.autoSyncCount('home'), 1,
|
|
98
|
+
reason: 'an acquired application workspace continuously syncs');
|
|
92
99
|
await bridge.dispose();
|
|
93
100
|
});
|
|
94
101
|
|
|
@@ -140,6 +147,8 @@ void main() {
|
|
|
140
147
|
reason: 'ONE connect for three concurrent acquires');
|
|
141
148
|
expect(identical(results[0], results[1]), isTrue);
|
|
142
149
|
expect(identical(results[1], results[2]), isTrue);
|
|
150
|
+
expect(runner.autoSyncCount('cloudy'), 1,
|
|
151
|
+
reason: 'the workspace lifecycle is single-flight too');
|
|
143
152
|
final args = runner.connectArgs(0);
|
|
144
153
|
expect(args['cloud'], 'https://cloud.example');
|
|
145
154
|
expect(args['authToken'], 'tok');
|
|
@@ -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);
|