@githolon/dsl 0.93.0 → 0.94.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dart/lib/src/dev_cloud.dart +1 -1
- package/dart/lib/src/node_runner_transport.dart +5 -5
- package/dart/lib/src/test_harness.dart +16 -16
- package/dart/lib/src/webview_bridge.dart +15 -1
- package/dart/pubspec.lock +1 -1
- package/dart/test/acquire_session_credentials_test.dart +1 -1
- package/dart/test/adv_realm_interference_test.dart +2 -2
- package/dart/test/cloud_bound_topology_test.dart +2 -2
- 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/co2_acceptance_test.dart +1 -1
- package/dart/test/dlq_session_surface_test.dart +1 -1
- package/dart/test/first_app_gauntlet_test.dart +1 -1
- package/dart/test/local_sync_inert_test.dart +1 -1
- package/dart/test/node_runner_transport_test.dart +3 -3
- 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 +1 -1
- package/dart/test/vo_read_typing_test.dart +1 -1
- package/dart/test/watch_after_offer_stall_test.dart +1 -1
- package/dart/test/watch_surfaces_test.dart +1 -1
- package/dart_types/lib/src/errors.dart +27 -0
- package/package.json +1 -1
- package/src/build_package.ts +13 -1
- package/src/codegen_dart_client.ts +10 -0
- package/src/codegen_ts.ts +17 -4
- package/src/compile_package_main.ts +25 -0
- package/src/framework/birth_meter.ts +1 -1
- package/src/framework/home.ts +1 -1
- package/src/framework/workspaces.ts +1 -1
|
@@ -84,7 +84,7 @@ class NomosTestCloud {
|
|
|
84
84
|
|
|
85
85
|
/// Start the test cloud: spawn `node <serverEntry> --port <n> [--ephemeral] [--data <dir>]`, wait until its
|
|
86
86
|
/// `/health` answers, then bring up the partition proxy in front of it. [serverEntry] is the dev-server
|
|
87
|
-
/// entry (`cloud/dev-server/server.
|
|
87
|
+
/// entry (`cloud/dev-server/server.ts` in-repo, or `dist/dev-server.mjs` from a published `githolon`) — the
|
|
88
88
|
/// caller resolves it (the same seam the offline harness uses for its install helper). [node] defaults to
|
|
89
89
|
/// `node` on PATH.
|
|
90
90
|
static Future<NomosTestCloud> start({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// THE HEADLESS (NODE) TRANSPORT — a [BridgeTransport] over the `@githolon/client` node runner
|
|
2
|
-
// (`runner/node-entry.
|
|
2
|
+
// (`runner/node-entry.ts`), spawned as a plain child process speaking the ONE bridge protocol as
|
|
3
3
|
// newline-delimited JSON on stdio. This is what lets `dart test` / `flutter test` / CI drive the REAL
|
|
4
4
|
// githolon (the byte-identical wasm, the real ledger, the real gate) through [NomosBridge] and the
|
|
5
5
|
// generated typed clients — no Flutter plugins, no WebView, no assets. The only environment requirement
|
|
@@ -62,8 +62,8 @@ class NodeRunnerTransport implements BridgeTransport {
|
|
|
62
62
|
/// [NomosBridge] and `await bridge.ready` (the runner posts `{event:'ready'}`
|
|
63
63
|
/// on boot), then `connect(...)` as any other transport.
|
|
64
64
|
///
|
|
65
|
-
/// [entry] — path to `node-entry.
|
|
66
|
-
/// `require.resolve('@githolon/client/runner/node-entry.
|
|
65
|
+
/// [entry] — path to `node-entry.ts`. When omitted it is resolved with
|
|
66
|
+
/// `require.resolve('@githolon/client/runner/node-entry.ts')` from
|
|
67
67
|
/// [resolveFrom] (default: the current directory), i.e. the consuming app's
|
|
68
68
|
/// own `node_modules`.
|
|
69
69
|
static Future<NodeRunnerTransport> start({
|
|
@@ -87,13 +87,13 @@ class NodeRunnerTransport implements BridgeTransport {
|
|
|
87
87
|
static Future<String> _resolveEntry(String node, String from) async {
|
|
88
88
|
final r = await Process.run(
|
|
89
89
|
node,
|
|
90
|
-
['-p', "require.resolve('@githolon/client/runner/node-entry.
|
|
90
|
+
['-p', "require.resolve('@githolon/client/runner/node-entry.ts')"],
|
|
91
91
|
workingDirectory: from,
|
|
92
92
|
);
|
|
93
93
|
final out = (r.stdout as String).trim();
|
|
94
94
|
if (r.exitCode != 0 || out.isEmpty) {
|
|
95
95
|
throw StateError(
|
|
96
|
-
'cannot resolve @githolon/client/runner/node-entry.
|
|
96
|
+
'cannot resolve @githolon/client/runner/node-entry.ts from $from — '
|
|
97
97
|
'is @githolon/client installed (npm install)? stderr: ${r.stderr}');
|
|
98
98
|
}
|
|
99
99
|
return out;
|
|
@@ -16,18 +16,18 @@
|
|
|
16
16
|
// ```
|
|
17
17
|
//
|
|
18
18
|
// HOW IT STAYS OFFLINE. Two node children, both talking only to the local disk:
|
|
19
|
-
// 1. `offline-install.
|
|
19
|
+
// 1. `offline-install.ts` runs the PROVEN offline install lane (`githolon proof`'s adapter engine) —
|
|
20
20
|
// genesis → framework controller → each tenant package's `installDomain`, folded through the ONE gate —
|
|
21
21
|
// and serializes the workspace tree to a snapshot in a fresh temp dir.
|
|
22
|
-
// 2. the node runner (`node-entry.
|
|
22
|
+
// 2. the node runner (`node-entry.ts`) boots in `NOMOS_OFFLINE=1` mode: it serves the runtime wasm from the
|
|
23
23
|
// local cache (`~/.holon/runtime`, filled by any warm `githolon` run) and boots the holon from that
|
|
24
24
|
// snapshot (`restoreFrom`). Every other URL is REFUSED — a stray fetch fails loudly, never reaches the net.
|
|
25
25
|
//
|
|
26
26
|
// PREREQUISITES (documented so a failure is legible): `node` on PATH; a resolvable
|
|
27
|
-
// `@githolon/client/runner/node-entry.
|
|
27
|
+
// `@githolon/client/runner/node-entry.ts`; and a warm `~/.holon/runtime` cache (holon.wasm + packages.json —
|
|
28
28
|
// one `githolon` run fills it). All local; no network at test time.
|
|
29
29
|
//
|
|
30
|
-
// PACKAGING NOTE: `offline-install.
|
|
30
|
+
// PACKAGING NOTE: `offline-install.ts` imports the adapter engine, which today lives beside the in-repo
|
|
31
31
|
// runner. For a PUBLISHED harness this install belongs behind a `githolon` subcommand (the CLI already
|
|
32
32
|
// bundles the engine + this exact sequence in `proof_offline`); [installHelper] is the one seam to repoint.
|
|
33
33
|
import 'dart:convert';
|
|
@@ -246,10 +246,10 @@ class NomosTestHarness {
|
|
|
246
246
|
/// framework/platform law before a domain that depends on it).
|
|
247
247
|
/// [workspace] — the local workspace name (default `test-ws`; law-only, never leaves the machine).
|
|
248
248
|
/// [runtimeDir] — the runtime cache (default `~/.holon/runtime`); must hold `holon.wasm` + `packages.json`.
|
|
249
|
-
/// [nodeEntry] — path to `node-entry.
|
|
250
|
-
/// `require.resolve('@githolon/client/runner/node-entry.
|
|
249
|
+
/// [nodeEntry] — path to `node-entry.ts`; when omitted it is resolved from [resolveFrom] via
|
|
250
|
+
/// `require.resolve('@githolon/client/runner/node-entry.ts')`.
|
|
251
251
|
/// [installHelper] — the offline law-install lane. An EXISTING `.mjs` file is run as a node script (the
|
|
252
|
-
/// in-repo `offline-install.
|
|
252
|
+
/// in-repo `offline-install.ts`, the default when it exists beside [nodeEntry]); anything else is a
|
|
253
253
|
/// githolon COMMAND LINE whose `harness-install` subcommand is driven (the default `npx githolon` when the
|
|
254
254
|
/// in-repo helper is absent — a published tenant needs nothing but `githolon`). E.g. force the subcommand
|
|
255
255
|
/// with `installHelper: 'node /abs/cli/dist/cli.mjs'`.
|
|
@@ -280,14 +280,14 @@ class NomosTestHarness {
|
|
|
280
280
|
final entry = nodeEntry ??
|
|
281
281
|
await _resolveEntry(node, resolveFrom ?? Directory.current.path);
|
|
282
282
|
// THE INSTALL LANE. [installHelper] is the one seam (PACKAGING NOTE above): either the in-repo
|
|
283
|
-
// `offline-install.
|
|
283
|
+
// `offline-install.ts` node script, OR the published `githolon harness-install` subcommand (the CLI
|
|
284
284
|
// bundles the engine + this exact install sequence, so a published tenant needs nothing but `githolon`).
|
|
285
285
|
// • an EXISTING `.mjs` file ⇒ run it as a node script (the in-repo default when present);
|
|
286
286
|
// • anything else ⇒ treat it as a githolon COMMAND LINE and drive `harness-install`
|
|
287
287
|
// (default `npx githolon` when the in-repo helper is absent; the test forces this lane by passing
|
|
288
288
|
// e.g. `installHelper: 'node /abs/cli/dist/cli.mjs'`).
|
|
289
289
|
final resolvedHelper =
|
|
290
|
-
installHelper ?? '${File(entry).parent.path}/offline-install.
|
|
290
|
+
installHelper ?? '${File(entry).parent.path}/offline-install.ts';
|
|
291
291
|
final useInRepoScript =
|
|
292
292
|
resolvedHelper.endsWith('.mjs') && File(resolvedHelper).existsSync();
|
|
293
293
|
final githolonCmd = installHelper == null ? 'npx githolon' : resolvedHelper;
|
|
@@ -303,7 +303,7 @@ class NomosTestHarness {
|
|
|
303
303
|
final info = await _phased(
|
|
304
304
|
phase: '1/4: offline law install (${deployJsonPaths.length} package(s))',
|
|
305
305
|
workspace: workspace,
|
|
306
|
-
awaiting: useInRepoScript ? 'offline-install.
|
|
306
|
+
awaiting: useInRepoScript ? 'offline-install.ts' : 'githolon harness-install',
|
|
307
307
|
timeout: phaseTimeout ?? _kInstallDeadline,
|
|
308
308
|
onDiagnostic: onDiagnostic,
|
|
309
309
|
likelyCauses: const [
|
|
@@ -465,7 +465,7 @@ class NomosTestHarness {
|
|
|
465
465
|
final entry = nodeEntry ??
|
|
466
466
|
await _resolveEntry(node, resolveFrom ?? Directory.current.path);
|
|
467
467
|
final resolvedHelper =
|
|
468
|
-
installHelper ?? '${File(entry).parent.path}/offline-install.
|
|
468
|
+
installHelper ?? '${File(entry).parent.path}/offline-install.ts';
|
|
469
469
|
final useInRepoScript =
|
|
470
470
|
resolvedHelper.endsWith('.mjs') && File(resolvedHelper).existsSync();
|
|
471
471
|
final githolonCmd = installHelper == null ? 'npx githolon' : resolvedHelper;
|
|
@@ -483,7 +483,7 @@ class NomosTestHarness {
|
|
|
483
483
|
final info = await _phased(
|
|
484
484
|
phase: '1: offline law install ${++_i}/${workspaces.length}',
|
|
485
485
|
workspace: e.key,
|
|
486
|
-
awaiting: useInRepoScript ? 'offline-install.
|
|
486
|
+
awaiting: useInRepoScript ? 'offline-install.ts' : 'githolon harness-install',
|
|
487
487
|
timeout: phaseTimeout ?? _kInstallDeadline,
|
|
488
488
|
onDiagnostic: onDiagnostic,
|
|
489
489
|
likelyCauses: const [
|
|
@@ -911,7 +911,7 @@ class NomosTestHarness {
|
|
|
911
911
|
final info = await _phased(
|
|
912
912
|
phase: 'seed: offline law install',
|
|
913
913
|
workspace: workspace,
|
|
914
|
-
awaiting: lane.useInRepoScript ? 'offline-install.
|
|
914
|
+
awaiting: lane.useInRepoScript ? 'offline-install.ts' : 'githolon harness-install',
|
|
915
915
|
timeout: _phaseTimeout ?? _kInstallDeadline,
|
|
916
916
|
onDiagnostic: _onDiagnostic,
|
|
917
917
|
run: () => _runInstall(
|
|
@@ -1106,7 +1106,7 @@ class NomosTestHarness {
|
|
|
1106
1106
|
githolonCmd.trim().split(RegExp(r'\s+')).where((t) => t.isNotEmpty).toList();
|
|
1107
1107
|
if (tokens.isEmpty) {
|
|
1108
1108
|
throw NomosTestHarnessException(
|
|
1109
|
-
'no install helper resolved — pass installHelper: (an offline-install.
|
|
1109
|
+
'no install helper resolved — pass installHelper: (an offline-install.ts path, or a githolon command)');
|
|
1110
1110
|
}
|
|
1111
1111
|
executable = tokens.first;
|
|
1112
1112
|
args = [...tokens.skip(1), 'harness-install', ...common];
|
|
@@ -1143,13 +1143,13 @@ class NomosTestHarness {
|
|
|
1143
1143
|
static Future<String> _resolveEntry(String node, String from) async {
|
|
1144
1144
|
final r = await Process.run(
|
|
1145
1145
|
node,
|
|
1146
|
-
['-p', "require.resolve('@githolon/client/runner/node-entry.
|
|
1146
|
+
['-p', "require.resolve('@githolon/client/runner/node-entry.ts')"],
|
|
1147
1147
|
workingDirectory: from,
|
|
1148
1148
|
);
|
|
1149
1149
|
final out = (r.stdout as String).trim();
|
|
1150
1150
|
if (r.exitCode != 0 || out.isEmpty) {
|
|
1151
1151
|
throw NomosTestHarnessException(
|
|
1152
|
-
'cannot resolve @githolon/client/runner/node-entry.
|
|
1152
|
+
'cannot resolve @githolon/client/runner/node-entry.ts from $from — '
|
|
1153
1153
|
'is @githolon/client installed (npm install)? Pass nodeEntry: to override. stderr: ${r.stderr}');
|
|
1154
1154
|
}
|
|
1155
1155
|
return out;
|
|
@@ -48,7 +48,7 @@ const int kWebviewBridgeVersion = 1;
|
|
|
48
48
|
/// pre-stamp bundle, which reports nothing) is a stale baked asset and fails loudly at boot.
|
|
49
49
|
/// MUST move with the paired @githolon/client release — a release-hygiene test pairs it with the
|
|
50
50
|
/// runner stamp (the pubspec-pins pattern).
|
|
51
|
-
const String kExpectedRunnerBundleVersion = '0.
|
|
51
|
+
const String kExpectedRunnerBundleVersion = '0.93.0';
|
|
52
52
|
|
|
53
53
|
/// A STALE BAKED RUNNER BUNDLE: the app's built assets carry an older nomos-runner.js than this
|
|
54
54
|
/// nomos_client release ships — Flutter's build cache kept it across a pub upgrade. Thrown from
|
|
@@ -1592,6 +1592,20 @@ class NomosBridge {
|
|
|
1592
1592
|
/// [attestorSecret] is the source's base64 read-attestor secret (from [authorKeygen]); when omitted the
|
|
1593
1593
|
/// source session's connect-time `authorSecret` signs. [nowMs] stamps the freshness clock (defaults to the
|
|
1594
1594
|
/// source's own clock).
|
|
1595
|
+
/// RELEASE COHERENCE — assert the connected cloud's kernel supports every capability this law needs (the
|
|
1596
|
+
/// generated client passes its baked `requiresKernel`, e.g. `['compoundKeys']` for a compound-key query).
|
|
1597
|
+
/// Throws a typed [KernelCoherenceError] on a skew (a stale/mismatched cloud kernel) with the named reason,
|
|
1598
|
+
/// instead of an opaque deep-fold refusal. A best-effort no-op when the cloud advertises no capabilities
|
|
1599
|
+
/// (older worker / offline). Called automatically by the generated client at bind; safe to call directly.
|
|
1600
|
+
Future<void> assertKernelCoherence(List<String> requiresKernel,
|
|
1601
|
+
{String? lawHash, String? session}) async {
|
|
1602
|
+
await _call('assertKernelCoherence', {
|
|
1603
|
+
'requiresKernel': requiresKernel,
|
|
1604
|
+
if (lawHash != null) 'lawHash': lawHash,
|
|
1605
|
+
if (session != null) 'session': session,
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1595
1609
|
Future<Map<String, Object?>> attestedRead({
|
|
1596
1610
|
required String from,
|
|
1597
1611
|
required String queryId,
|
package/dart/pubspec.lock
CHANGED
|
@@ -17,7 +17,7 @@ import 'package:nomos_client/nomos_headless.dart';
|
|
|
17
17
|
import 'package:test/test.dart';
|
|
18
18
|
|
|
19
19
|
String get _root => '${Directory.current.path}/../..';
|
|
20
|
-
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.
|
|
20
|
+
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
21
21
|
String get _docsDeploy => '$_root/bench/scale/fixtures/surfaced/build/surfaced.deploy.json';
|
|
22
22
|
String get _runtimeDir =>
|
|
23
23
|
Platform.environment['NOMOS_DART_RUNTIME'] ??
|
|
@@ -25,8 +25,8 @@ import 'package:nomos_types/nomos_types.dart' show NomosRef;
|
|
|
25
25
|
import 'package:test/test.dart';
|
|
26
26
|
|
|
27
27
|
String get _root => '${Directory.current.path}/../..';
|
|
28
|
-
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.
|
|
29
|
-
String get _installer => '$_root/cloud/web-client/runner/offline-install.
|
|
28
|
+
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
29
|
+
String get _installer => '$_root/cloud/web-client/runner/offline-install.ts';
|
|
30
30
|
String get _guestbook => '$_root/examples/guestbook/build/guestbook.deploy.json';
|
|
31
31
|
String get _surfaced => '$_root/bench/scale/fixtures/surfaced/build/surfaced.deploy.json';
|
|
32
32
|
String get _attributed => '$_root/bench/scale/fixtures/attributed/build/attributed.deploy.json';
|
|
@@ -21,8 +21,8 @@ import 'package:nomos_client/nomos_headless.dart';
|
|
|
21
21
|
import 'package:test/test.dart';
|
|
22
22
|
|
|
23
23
|
String get _root => '${Directory.current.path}/../..';
|
|
24
|
-
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.
|
|
25
|
-
String get _devServer => '$_root/cloud/dev-server/server.
|
|
24
|
+
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
25
|
+
String get _devServer => '$_root/cloud/dev-server/server.ts';
|
|
26
26
|
String get _deploy =>
|
|
27
27
|
'$_root/bench/scale/fixtures/cloud-topology/build/cloud-topology.deploy.json';
|
|
28
28
|
const _subject = 'user:topology-test';
|
|
@@ -26,7 +26,7 @@ import 'package:test/test.dart';
|
|
|
26
26
|
import '../../../bench/scale/fixtures/catalogue-reader/build/dart/src/generated/estate.client.dart';
|
|
27
27
|
|
|
28
28
|
String get _root => '${Directory.current.path}/../..';
|
|
29
|
-
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.
|
|
29
|
+
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
30
30
|
String get _catDeploy => '$_root/bench/scale/fixtures/catalogue-src/build/cat.deploy.json';
|
|
31
31
|
String get _estateDeploy => '$_root/bench/scale/fixtures/catalogue-reader/build/estate.deploy.json';
|
|
32
32
|
String get _estateClientDart =>
|
|
@@ -27,7 +27,7 @@ import 'package:nomos_client/nomos_headless.dart';
|
|
|
27
27
|
import 'package:test/test.dart';
|
|
28
28
|
|
|
29
29
|
String get _root => '${Directory.current.path}/../..';
|
|
30
|
-
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.
|
|
30
|
+
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
31
31
|
String get _birthDeploy =>
|
|
32
32
|
'$_root/bench/scale/fixtures/estate-birth/build/estate-birth.deploy.json';
|
|
33
33
|
String get _runtimeDir => '${Platform.environment['HOME'] ?? ''}/.holon/runtime';
|
|
@@ -17,7 +17,7 @@ import 'package:nomos_client/nomos_headless.dart';
|
|
|
17
17
|
import 'package:test/test.dart';
|
|
18
18
|
|
|
19
19
|
String get _root => '${Directory.current.path}/../..';
|
|
20
|
-
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.
|
|
20
|
+
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
21
21
|
String get _catDeploy => '$_root/bench/scale/fixtures/catalogue-src/build/cat.deploy.json';
|
|
22
22
|
String get _estateDeploy => '$_root/bench/scale/fixtures/catalogue-reader/build/estate.deploy.json';
|
|
23
23
|
String get _runtimeDir => '${Platform.environment['HOME'] ?? ''}/.holon/runtime';
|
|
@@ -15,7 +15,7 @@ import 'package:nomos_client/nomos_headless.dart';
|
|
|
15
15
|
import 'package:test/test.dart';
|
|
16
16
|
|
|
17
17
|
String get _root => '${Directory.current.path}/../..';
|
|
18
|
-
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.
|
|
18
|
+
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
19
19
|
String get _guestbookDeploy => '$_root/examples/guestbook/build/guestbook.deploy.json';
|
|
20
20
|
String get _runtimeDir => '${Platform.environment['HOME'] ?? ''}/.holon/runtime';
|
|
21
21
|
|
|
@@ -15,7 +15,7 @@ import 'package:nomos_client/nomos_headless.dart';
|
|
|
15
15
|
import 'package:test/test.dart';
|
|
16
16
|
|
|
17
17
|
String get _root => '${Directory.current.path}/../..';
|
|
18
|
-
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.
|
|
18
|
+
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
19
19
|
String get _guestbookDeploy => '$_root/examples/guestbook/build/guestbook.deploy.json';
|
|
20
20
|
String get _runtimeDir => '${Platform.environment['HOME'] ?? ''}/.holon/runtime';
|
|
21
21
|
|
|
@@ -13,7 +13,7 @@ import 'package:test/test.dart';
|
|
|
13
13
|
import '../../../bench/scale/fixtures/co2-acceptance/build/dart/src/generated/estate.client.dart';
|
|
14
14
|
|
|
15
15
|
String get _root => '${Directory.current.path}/../..';
|
|
16
|
-
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.
|
|
16
|
+
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
17
17
|
String get _deployJson =>
|
|
18
18
|
'$_root/bench/scale/fixtures/co2-acceptance/build/co2-acceptance.deploy.json';
|
|
19
19
|
String get _runtimeDir => '${Platform.environment['HOME'] ?? ''}/.holon/runtime';
|
|
@@ -19,7 +19,7 @@ import 'package:nomos_client/nomos_headless.dart';
|
|
|
19
19
|
import 'package:test/test.dart';
|
|
20
20
|
|
|
21
21
|
String get _root => '${Directory.current.path}/../..';
|
|
22
|
-
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.
|
|
22
|
+
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
23
23
|
String get _geoDeploy => '$_root/bench/scale/fixtures/estate-geo/build/estate-geo.deploy.json';
|
|
24
24
|
String get _docsDeploy => '$_root/bench/scale/fixtures/surfaced/build/surfaced.deploy.json';
|
|
25
25
|
String get _runtimeDir =>
|
|
@@ -24,7 +24,7 @@ import '../../../bench/scale/fixtures/estate-birth/build/dart/src/generated/esta
|
|
|
24
24
|
show EstateClient, SeedEstateWorkspacePayload;
|
|
25
25
|
|
|
26
26
|
String get _root => '${Directory.current.path}/../..';
|
|
27
|
-
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.
|
|
27
|
+
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
28
28
|
String get _homeDeploy =>
|
|
29
29
|
'$_root/bench/scale/fixtures/estate-birth/build/estate-birth.deploy.json';
|
|
30
30
|
String get _docsDeploy => '$_root/bench/scale/fixtures/surfaced/build/surfaced.deploy.json';
|
|
@@ -13,7 +13,7 @@ import 'package:test/test.dart';
|
|
|
13
13
|
import '../../../bench/scale/fixtures/estate-birth/build/dart/src/generated/home.client.dart';
|
|
14
14
|
|
|
15
15
|
String get _root => '${Directory.current.path}/../..';
|
|
16
|
-
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.
|
|
16
|
+
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
17
17
|
String get _deployJson =>
|
|
18
18
|
'$_root/bench/scale/fixtures/estate-birth/build/estate-birth.deploy.json';
|
|
19
19
|
String get _runtimeDir =>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The headless transport against the REAL node runner (`cloud/web-client/runner/node-entry.
|
|
1
|
+
// The headless transport against the REAL node runner (`cloud/web-client/runner/node-entry.ts`):
|
|
2
2
|
// protocol round-trip through NomosBridge without touching the cloud — ready handshake, typed refusal
|
|
3
3
|
// on a pre-connect op, unknown-op error. The live connect path is covered by the env-gated smoke below.
|
|
4
4
|
@TestOn('vm')
|
|
@@ -13,9 +13,9 @@ import 'package:test/test.dart';
|
|
|
13
13
|
String _repoEntry() {
|
|
14
14
|
// In-repo the runner lives beside us; consumers resolve it from node_modules instead.
|
|
15
15
|
final here = Directory.current.path;
|
|
16
|
-
final p = '$here/../../cloud/web-client/runner/node-entry.
|
|
16
|
+
final p = '$here/../../cloud/web-client/runner/node-entry.ts';
|
|
17
17
|
if (!File(p).existsSync()) {
|
|
18
|
-
throw StateError('node-entry.
|
|
18
|
+
throw StateError('node-entry.ts not found at $p');
|
|
19
19
|
}
|
|
20
20
|
return p;
|
|
21
21
|
}
|
|
@@ -22,7 +22,7 @@ import 'package:nomos_types/nomos_types.dart'
|
|
|
22
22
|
import 'package:test/test.dart';
|
|
23
23
|
|
|
24
24
|
String get _root => '${Directory.current.path}/../..';
|
|
25
|
-
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.
|
|
25
|
+
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
26
26
|
String get _deploy =>
|
|
27
27
|
'$_root/bench/scale/fixtures/estate-birth-signed/build/estate-birth-signed.deploy.json';
|
|
28
28
|
String get _runtimeDir => '${Platform.environment['HOME'] ?? ''}/.holon/runtime';
|
|
@@ -16,7 +16,7 @@ import 'package:nomos_client/nomos_headless.dart';
|
|
|
16
16
|
import 'package:test/test.dart';
|
|
17
17
|
|
|
18
18
|
String get _root => '${Directory.current.path}/../..';
|
|
19
|
-
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.
|
|
19
|
+
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
20
20
|
String get _fixture => '$_root/cloud/web-client/test/fixtures/encrypted-notes';
|
|
21
21
|
String get _deploy => '$_fixture/build/encrypted-notes.deploy.json';
|
|
22
22
|
String get _lockfile => '$_fixture/nomos.stable-ids.json';
|
|
@@ -27,7 +27,7 @@ import '../../../bench/scale/fixtures/estate-birth/build/dart/src/generated/esta
|
|
|
27
27
|
show EstateClient, SeedEstateWorkspacePayload;
|
|
28
28
|
|
|
29
29
|
String get _root => '${Directory.current.path}/../..';
|
|
30
|
-
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.
|
|
30
|
+
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
31
31
|
String get _deployJson =>
|
|
32
32
|
'$_root/bench/scale/fixtures/estate-birth/build/estate-birth.deploy.json';
|
|
33
33
|
String get _otherDeployJson =>
|
|
@@ -15,7 +15,7 @@ import 'package:nomos_client/nomos_headless.dart';
|
|
|
15
15
|
import 'package:test/test.dart';
|
|
16
16
|
|
|
17
17
|
String get _repoEntry =>
|
|
18
|
-
'${Directory.current.path}/../../cloud/web-client/runner/node-entry.
|
|
18
|
+
'${Directory.current.path}/../../cloud/web-client/runner/node-entry.ts';
|
|
19
19
|
String get _guestbookDeployJson =>
|
|
20
20
|
'${Directory.current.path}/../../examples/guestbook/build/guestbook.deploy.json';
|
|
21
21
|
String get _runtimeDir =>
|
|
@@ -19,7 +19,7 @@ import 'package:test/test.dart';
|
|
|
19
19
|
import '../../../bench/scale/fixtures/vo-read-typing/build/dart/src/generated/estate.dart';
|
|
20
20
|
|
|
21
21
|
String get _root => '${Directory.current.path}/../..';
|
|
22
|
-
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.
|
|
22
|
+
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
23
23
|
String get _deployJson =>
|
|
24
24
|
'$_root/bench/scale/fixtures/vo-read-typing/build/vo-read-typing.deploy.json';
|
|
25
25
|
String get _runtimeDir => '${Platform.environment['HOME'] ?? ''}/.holon/runtime';
|
|
@@ -12,7 +12,7 @@ import 'package:nomos_client/nomos_headless.dart';
|
|
|
12
12
|
import 'package:test/test.dart';
|
|
13
13
|
|
|
14
14
|
String get _root => '${Directory.current.path}/../..';
|
|
15
|
-
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.
|
|
15
|
+
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
16
16
|
String get _gbDeploy => '$_root/examples/guestbook/build/guestbook.deploy.json';
|
|
17
17
|
String get _runtimeDir =>
|
|
18
18
|
Platform.environment['NOMOS_DART_RUNTIME'] ??
|
|
@@ -20,7 +20,7 @@ import 'package:test/test.dart';
|
|
|
20
20
|
import '../../../bench/scale/fixtures/co2-acceptance/build/dart/src/generated/estate.client.dart';
|
|
21
21
|
|
|
22
22
|
String get _root => '${Directory.current.path}/../..';
|
|
23
|
-
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.
|
|
23
|
+
String get _repoEntry => '$_root/cloud/web-client/runner/node-entry.ts';
|
|
24
24
|
String get _geoDeploy =>
|
|
25
25
|
'$_root/bench/scale/fixtures/co2-acceptance/build/co2-acceptance.deploy.json';
|
|
26
26
|
String get _actsDeploy =>
|
|
@@ -29,6 +29,9 @@ NomosBridgeError nomosErrorFromInfo(NomosErrorInfo info) {
|
|
|
29
29
|
if (info.code == 'nomos.attested_read.source_not_mounted') {
|
|
30
30
|
return SourceSessionNotMounted.fromInfo(info);
|
|
31
31
|
}
|
|
32
|
+
if (info.code == 'kernel-capability-skew') {
|
|
33
|
+
return KernelCoherenceError.fromInfo(info);
|
|
34
|
+
}
|
|
32
35
|
return NomosBridgeError(info.message, info: info);
|
|
33
36
|
}
|
|
34
37
|
|
|
@@ -480,6 +483,30 @@ class SourceSessionNotMounted extends NomosBridgeError {
|
|
|
480
483
|
'${queryId != null ? ', query: $queryId' : ''}, recommendedAction: $recommendedAction): $message';
|
|
481
484
|
}
|
|
482
485
|
|
|
486
|
+
/// KERNEL COHERENCE SKEW (`kernel-capability-skew`) — the cloud this client connected to runs a kernel that
|
|
487
|
+
/// does NOT support a capability this law requires (e.g. a compound-key query `.key(a, b)` needs
|
|
488
|
+
/// `compoundKeys`, and the cloud's kernel is a STALE build that predates the compound-key read router). The
|
|
489
|
+
/// generated client asserts this at bind (from its baked `requiresKernel`) against the cloud's advertised
|
|
490
|
+
/// `/v1/runtime/capabilities`, so a stale/mismatched kernel is caught UP FRONT with a named reason — instead
|
|
491
|
+
/// of the opaque "the read router supports exactly one" refusal deep in a law fold. Remedy: point at a cloud
|
|
492
|
+
/// whose kernel is coherent with this release, or upgrade the cloud's kernel.
|
|
493
|
+
class KernelCoherenceError extends NomosBridgeError {
|
|
494
|
+
/// The stable machine code — always `kernel-capability-skew`.
|
|
495
|
+
final String code;
|
|
496
|
+
|
|
497
|
+
KernelCoherenceError({
|
|
498
|
+
this.code = 'kernel-capability-skew',
|
|
499
|
+
required String message,
|
|
500
|
+
NomosErrorInfo? info,
|
|
501
|
+
}) : super(message, info: info);
|
|
502
|
+
|
|
503
|
+
factory KernelCoherenceError.fromInfo(NomosErrorInfo info) =>
|
|
504
|
+
KernelCoherenceError(code: info.code, message: info.message, info: info);
|
|
505
|
+
|
|
506
|
+
@override
|
|
507
|
+
String toString() => 'KernelCoherenceError: $message';
|
|
508
|
+
}
|
|
509
|
+
|
|
483
510
|
/// The custody phase a [CustodyError] was raised in.
|
|
484
511
|
enum CustodyPhase { fold, author, fetch, unknown }
|
|
485
512
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@githolon/dsl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.94.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Nomos 2 domain-authoring DSL: aggregates + directives in TS, executed and encoded to the Rust kernel's wire shapes.",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE.md",
|
package/src/build_package.ts
CHANGED
|
@@ -237,7 +237,19 @@ export function composeDomainModule(spec: DomainModuleSpec): DomainModule {
|
|
|
237
237
|
// byte-identical; a domain that DOES could not have deployed before without the author hand-writing this.
|
|
238
238
|
const autoDeclaredEnsures: string[] = [];
|
|
239
239
|
for (const ty of discoverUnsanctionedEnsures(directives, spec.domain ?? spec.name)) {
|
|
240
|
-
|
|
240
|
+
let agg = byId.get(ty);
|
|
241
|
+
// SELF-CONTAINED FRAMEWORK RELATIONS: `seedHomeOwner`/`writeTuple` (and any `relateSubject`-style seat)
|
|
242
|
+
// upsert the canonical framework `RelationTuple` aggregate at a DETERMINISTIC id. A tenant that re-exports
|
|
243
|
+
// ONLY the seating directive (e.g. `seedHomeOwner`) — without also re-exporting `RelationTuple` + a
|
|
244
|
+
// `.ensures(RelationTuple)` directive — carries no RelationTuple handle, so the id-mint gate would refuse
|
|
245
|
+
// its reltuple id. RelationTuple is a UNIVERSAL framework aggregate (one canonical shape), so supply its
|
|
246
|
+
// handle here and carry it, making the seat self-contained. Narrow + regression-free: this fires ONLY when
|
|
247
|
+
// a domain writes RelationTuple WITHOUT carrying it — which fails today — so no already-compiling domain
|
|
248
|
+
// (which must already carry it) changes. Any OTHER uncarried type stays deferred to the runtime gate.
|
|
249
|
+
if (agg === undefined && ty === RelationTuple.id) {
|
|
250
|
+
agg = RelationTuple;
|
|
251
|
+
byId.set(RelationTuple.id, RelationTuple);
|
|
252
|
+
}
|
|
241
253
|
// Only sanction a type whose handle this domain actually carries (its schema must be present for the
|
|
242
254
|
// upsert to fold); a probe pointing at a foreign/unknown type is left to the runtime gate (no schema).
|
|
243
255
|
if (agg === undefined) continue;
|
|
@@ -33,6 +33,7 @@ import type { Field } from "./fields.js";
|
|
|
33
33
|
import type { Directive } from "./directive.js";
|
|
34
34
|
import type { QueryDecl } from "./query.js";
|
|
35
35
|
import type { DomainModule } from "./codegen_dart.js";
|
|
36
|
+
import { requiredKernelFeatures } from "./kernel_capabilities.js";
|
|
36
37
|
import {
|
|
37
38
|
cap,
|
|
38
39
|
readModelClassName,
|
|
@@ -170,6 +171,7 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
170
171
|
const domain = mod.domain ?? mod.name;
|
|
171
172
|
const className = dartClientClassName(domain);
|
|
172
173
|
const hashConst = dartHashConstName(mod.name);
|
|
174
|
+
const requiresKernelFeats = requiredKernelFeatures([mod]);
|
|
173
175
|
const supportImport =
|
|
174
176
|
opts.supportImport === "package"
|
|
175
177
|
? "package:nomos_client/nomos_client.dart"
|
|
@@ -824,6 +826,10 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
824
826
|
` /// THE STATIC INTERFACE DESCRIPTOR (session_bound_clients.md asks 5/9): what this client EXPECTS`,
|
|
825
827
|
` /// of the workspace it binds to — the domain key, the law hash it was generated against, and the`,
|
|
826
828
|
` /// structural interface (#72). Consumed by [bind] and by \`session.assertCompatible(${className}.interface)\`.`,
|
|
829
|
+
` /// The kernel capabilities this law REQUIRES (release coherence): a compound-key query needs`,
|
|
830
|
+
` /// 'compoundKeys', etc. [bind] asserts the connected cloud's kernel advertises these.`,
|
|
831
|
+
` static const List<String> requiresKernel = [${requiresKernelFeats.map((f) => dartStr(f)).join(", ")}];`,
|
|
832
|
+
``,
|
|
827
833
|
` static const NomosClientInterface interface = NomosClientInterface(`,
|
|
828
834
|
` domain: ${dartStr(domain)},`,
|
|
829
835
|
` expectedDomainHash: ${hashConst},`,
|
|
@@ -841,6 +847,10 @@ export function generateDartBridgeClient(mod: DomainModule, opts: DartBridgeClie
|
|
|
841
847
|
` /// first dispatch.`,
|
|
842
848
|
` static Future<${className}> bind(NomosSession session, {String? actor${encPresent ? ", NomosKeyStore? keyStore" : ""}}) async {`,
|
|
843
849
|
` await session.assertCompatible(interface);`,
|
|
850
|
+
` // RELEASE COHERENCE: the cloud's kernel must support what this law needs (e.g. a compound-key query`,
|
|
851
|
+
` // needs 'compoundKeys'). Throws a typed KernelCoherenceError on a stale/mismatched cloud kernel,`,
|
|
852
|
+
` // up front — not an opaque deep-fold refusal. Best-effort (no-op if the cloud advertises no caps).`,
|
|
853
|
+
` await session.assertKernelCoherence(requiresKernel, lawHash: ${hashConst});`,
|
|
844
854
|
` return ${className}(session, actor: actor${encPresent ? ", keyStore: keyStore" : ""}).._preflighted = true;`,
|
|
845
855
|
` }`,
|
|
846
856
|
``,
|
package/src/codegen_ts.ts
CHANGED
|
@@ -66,6 +66,19 @@ const tsGroupParam = (by: string) => {
|
|
|
66
66
|
return parts.length <= 1 ? by : parts[0]! + parts.slice(1).map((p) => p[0]!.toUpperCase() + p.slice(1)).join("");
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
+
/** An object-literal / interface member KEY. Field LABELS may be dotted structural paths
|
|
70
|
+
* (`spec.domainHash`, `status.phase`) or otherwise non-identifier — emitted bare they'd
|
|
71
|
+
* parse as member access and break the file. A valid JS identifier is emitted verbatim;
|
|
72
|
+
* anything else is quoted as a string-literal key (same runtime property, valid TS). */
|
|
73
|
+
const tsKey = (name: string): string =>
|
|
74
|
+
/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
75
|
+
|
|
76
|
+
/** OPTIONAL-CHAIN member access onto a value keyed by a possibly-dotted field label,
|
|
77
|
+
* meant to follow a `?` (e.g. `x?${tsOptAccess(f)}`): `.foo` for an identifier,
|
|
78
|
+
* `.["foo.bar"]` otherwise (bracket access preserves the single property). */
|
|
79
|
+
const tsOptAccess = (name: string): string =>
|
|
80
|
+
/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? `.${name}` : `.[${JSON.stringify(name)}]`;
|
|
81
|
+
|
|
69
82
|
// ── NAME NORMALIZATION (no naming convention to learn) ────────────────────────────
|
|
70
83
|
// `nomos.package.mjs` names and domain keys may be ANY string — kebab-case,
|
|
71
84
|
// snake_case, space-separated, PascalCase. Artifact FILENAMES keep the raw name
|
|
@@ -1486,7 +1499,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
1486
1499
|
for (const [, c] of [...contractByIdTs].sort(([a], [b]) => a.localeCompare(b))) {
|
|
1487
1500
|
out.push(`export interface ${c.id}Contract {`);
|
|
1488
1501
|
for (const [name, field] of Object.entries(c.fields)) {
|
|
1489
|
-
out.push(` ${name}?: ${tsTypeOfField(field as Field)};`);
|
|
1502
|
+
out.push(` ${tsKey(name)}?: ${tsTypeOfField(field as Field)};`);
|
|
1490
1503
|
}
|
|
1491
1504
|
out.push(`}`, ``);
|
|
1492
1505
|
}
|
|
@@ -1525,14 +1538,14 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
1525
1538
|
const required = (field as Field).isFromBirth === true;
|
|
1526
1539
|
const doc = nullDocLine(field);
|
|
1527
1540
|
if (doc !== undefined) out.push(` /** ${doc} */`);
|
|
1528
|
-
out.push(` ${name}${required ? "" : "?"}: ${tsTypeOfField(field as Field)};`);
|
|
1541
|
+
out.push(` ${tsKey(name)}${required ? "" : "?"}: ${tsTypeOfField(field as Field)};`);
|
|
1529
1542
|
const fr = (field as { foreignRef?: { workspaceField: string; targetAggregateId: string } }).foreignRef;
|
|
1530
1543
|
if (fr !== undefined) foreignRefFields.push({ agg: typeId, field: name, workspaceField: fr.workspaceField, target: fr.targetAggregateId });
|
|
1531
1544
|
}
|
|
1532
1545
|
if (attributed.length > 0) {
|
|
1533
1546
|
out.push(
|
|
1534
1547
|
` /** attributed-read provenance (the reserved per-row map; prefer the generated accessors) */`,
|
|
1535
|
-
` "__prov"?: { ${attributed.map((f) => `${f}?: FieldProvenance`).join("; ")} };`,
|
|
1548
|
+
` "__prov"?: { ${attributed.map((f) => `${tsKey(f)}?: FieldProvenance`).join("; ")} };`,
|
|
1536
1549
|
);
|
|
1537
1550
|
}
|
|
1538
1551
|
for (const extra of extrasByType.get(typeId) ?? []) out.push(extra);
|
|
@@ -1545,7 +1558,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
1545
1558
|
` * unknown-author fields omitted). */`,
|
|
1546
1559
|
`export const ${ns}Provenance = {`,
|
|
1547
1560
|
...attributed.map(
|
|
1548
|
-
(f) => ` ${f}: (data: ${typeId}Data): FieldProvenance | undefined => data.__prov
|
|
1561
|
+
(f) => ` ${tsKey(f)}: (data: ${typeId}Data): FieldProvenance | undefined => data.__prov?${tsOptAccess(f)},`,
|
|
1549
1562
|
),
|
|
1550
1563
|
` lastAuthors: (data: ${typeId}Data): Record<string, string> => {`,
|
|
1551
1564
|
` const out: Record<string, string> = {};`,
|
|
@@ -2781,9 +2781,34 @@ async function main(): Promise<void> {
|
|
|
2781
2781
|
// files would false-fail every vendored fixture. Analyze the in-package set; note the loose remainder.
|
|
2782
2782
|
const inPackage = emittedDart.filter((f) => pkgDirOf(f) !== undefined);
|
|
2783
2783
|
const pkgDirs = [...new Set(inPackage.map(pkgDirOf).filter((d): d is string => d !== undefined))];
|
|
2784
|
+
// DEV DOGFOOD LINK: when this compiler is running FROM SOURCE (a nomos2 checkout, NOT an installed
|
|
2785
|
+
// node_modules copy), the emitted layered packages pin the PUBLISHED nomos_client/nomos_types — which
|
|
2786
|
+
// necessarily LAG the local source the compiler was built alongside (tooling+client move in lockstep,
|
|
2787
|
+
// CLAUDE.md). So the analyze gate must resolve against the LOCAL Dart source, else it judges freshly-
|
|
2788
|
+
// emitted client code against a stale hosted runtime (e.g. a method the current codegen calls that the
|
|
2789
|
+
// last publish predates). We write a `pubspec_overrides.yaml` pointing nomos_client/nomos_types at the
|
|
2790
|
+
// repo's own `dsl/dart` / `dsl/dart_types`. An INSTALLED tenant (`npx githolon`, compiler under
|
|
2791
|
+
// node_modules) gets NO override — it verifies against exactly the hosted runtime it will ship against.
|
|
2792
|
+
const localDart = (() => {
|
|
2793
|
+
const selfDir = path.dirname(fileURLToPath(import.meta.url));
|
|
2794
|
+
if (selfDir.includes(`${path.sep}node_modules${path.sep}`)) return undefined;
|
|
2795
|
+
const client = path.resolve(selfDir, "..", "dart");
|
|
2796
|
+
const types = path.resolve(selfDir, "..", "dart_types");
|
|
2797
|
+
if (existsSync(path.join(client, "pubspec.yaml")) && existsSync(path.join(types, "pubspec.yaml"))) {
|
|
2798
|
+
return { client, types };
|
|
2799
|
+
}
|
|
2800
|
+
return undefined;
|
|
2801
|
+
})();
|
|
2784
2802
|
let dartMissing = false;
|
|
2785
2803
|
let unresolved: string | undefined;
|
|
2786
2804
|
for (const pkg of pkgDirs) {
|
|
2805
|
+
if (localDart) {
|
|
2806
|
+
const spec = readFileSync(path.join(pkg, "pubspec.yaml"), "utf8");
|
|
2807
|
+
const lines = ["dependency_overrides:"];
|
|
2808
|
+
if (/(^|\n)\s+nomos_client\s*:/.test(spec)) lines.push(` nomos_client:`, ` path: ${localDart.client}`);
|
|
2809
|
+
if (/(^|\n)\s+nomos_types\s*:/.test(spec)) lines.push(` nomos_types:`, ` path: ${localDart.types}`);
|
|
2810
|
+
if (lines.length > 1) writeFileSync(path.join(pkg, "pubspec_overrides.yaml"), lines.join("\n") + "\n", "utf8");
|
|
2811
|
+
}
|
|
2787
2812
|
try {
|
|
2788
2813
|
execFileSync("dart", ["pub", "get"], { cwd: pkg, stdio: "pipe", encoding: "utf8" });
|
|
2789
2814
|
} catch (e) {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* - {@link seedBirthPolicy} — THE GENESIS SEED (A3, ungated like `seedGenesis`): the step a
|
|
16
16
|
* parent's birth recipe threads (`buildGenesisChain`'s `descendantPool`) so the child is BORN
|
|
17
17
|
* BOUNDED — the pool chains to root by induction (root bounds platforms, platforms bound
|
|
18
|
-
* homes, homes bound
|
|
18
|
+
* homes, homes bound projects). Control-plane protected exactly like `seedGenesis`: the open
|
|
19
19
|
* session lane structurally refuses control-plane intents, and post-genesis writes on a
|
|
20
20
|
* warranted workspace require signed authorship.
|
|
21
21
|
*
|
package/src/framework/home.ts
CHANGED
|
@@ -84,7 +84,7 @@ export const HomeOwner = aggregate("HomeOwner", {
|
|
|
84
84
|
* ownership RECORD; this relation is the AUTHORIZATION spine a tenant can compute from. `seedHomeOwner` seats
|
|
85
85
|
* `(home:self, homeOwner, user:<owner>)` ATOMICALLY inside the same child genesis (before the warrant closes
|
|
86
86
|
* bootstrap), so a canonical `birthHome` yields a home whose owner passes role gates from intent 0 — a tenant
|
|
87
|
-
* declares `
|
|
87
|
+
* declares `projectCreator = computed(homeOwner)` WITHOUT copying birth mechanics. Self-bound (object `home:
|
|
88
88
|
* self`, subject the owner) — the {@link homeNameBindsOwner} squat invariant already proves the owner is the
|
|
89
89
|
* rightful one, so no separate grant is needed.
|
|
90
90
|
*/
|
|
@@ -582,7 +582,7 @@ export function buildGenesisChain(p: {
|
|
|
582
582
|
// child is BORN BOUNDED — a `seedBirthPolicy` genesis step writes the child's own BirthPolicy singleton
|
|
583
583
|
// (its default birth allowance) BEFORE the warrant lands, exactly like every other genesis seed (the
|
|
584
584
|
// still-warrant-free prefix runs unsigned). Root bounds platforms (this pool), platforms bound homes,
|
|
585
|
-
// homes bound
|
|
585
|
+
// homes bound projects — the tree meters by induction, with zero cross-workspace reads on any gate.
|
|
586
586
|
// OPT-IN (deliberate): the step folds through the CHILD'S OWN gate under the law hash the order names,
|
|
587
587
|
// so it may only be threaded when that law carries `seedBirthPolicy` (a law compiled under this DSL) —
|
|
588
588
|
// an unconditional default would refuse the genesis of every birth pinning an OLDER law hash. V1
|