@githolon/dsl 0.113.4 → 0.115.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/assets/operation-contract.wasm +0 -0
- package/compile_package.mjs +23 -3
- package/compile_proof.d.mts +7 -0
- package/compile_proof.mjs +38 -3
- package/dart/CHANGELOG.md +60 -0
- package/dart/lib/src/application_framework_calls.dart +16 -6
- package/dart/lib/src/console_enrolment.dart +7 -0
- package/dart/lib/src/console_http_io.dart +22 -15
- package/dart/lib/src/dev_cloud.dart +62 -0
- package/dart/lib/src/generated_call.dart +14 -3
- package/dart/lib/src/nms_generated_runtime.dart +7 -0
- package/dart/lib/src/node_runner_transport.dart +77 -8
- package/dart/lib/src/nomos_trace.dart +107 -0
- package/dart/lib/src/operation_receipts.dart +74 -7
- package/dart/lib/src/reception/dart_reception.dart +174 -17
- package/dart/lib/src/rows_watch.dart +390 -0
- package/dart/lib/src/runner/node-entry.mjs +7678 -2860
- package/dart/lib/src/runtime_v2_transport.dart +3 -0
- package/dart/lib/src/sync_status_projection.dart +94 -1
- package/dart/lib/src/sync_status_reducer.dart +5 -2
- package/dart/lib/src/test_harness.dart +107 -41
- package/dart/lib/src/testing/test_peer.dart +107 -99
- package/dart/lib/src/testing/test_peer_host.dart +305 -0
- package/dart/lib/src/typed_watch_registry.dart +26 -3
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/background_model_adoption_test.dart +1 -0
- package/dart/test/console_enrolment_test.dart +47 -0
- package/dart/test/custodian_warming_test.dart +105 -0
- package/dart/test/fixtures/offer_watch_order_peer.dart +74 -0
- package/dart/test/kernel_author_after_read_test.dart +6 -3
- package/dart/test/kernel_fence_test.dart +4 -1
- package/dart/test/model_adoption_test.dart +66 -0
- package/dart/test/nomos_trace_test.dart +91 -0
- package/dart/test/own_offer_watch_order_test.dart +90 -0
- package/dart/test/receiving_group_open_test.dart +4 -8
- package/dart/test/replica_readiness_test.dart +14 -4
- package/dart/test/rows_watch_test.dart +331 -0
- package/dart/test/runtime_projections_test.dart +41 -0
- package/dart/test/test_peer_host_selection_test.dart +49 -0
- package/dart_types/CHANGELOG.md +39 -0
- package/dart_types/lib/src/approved_model.dart +100 -6
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +1063 -3
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +89 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/application_desk.pb.dart +14 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pb.dart +2272 -130
- package/dart_types/lib/src/proto/nomos/runtime/v2/correspondence_desk.pbenum.dart +65 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/model_adoption_desk.pb.dart +118 -2
- package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pb.dart +278 -9
- package/dart_types/lib/src/proto/nomos/runtime/v2/receiving_group_desk.pb.dart +7 -85
- package/dart_types/lib/src/proto/nomos/runtime/v2/records_desk.pb.dart +16 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/residency_desk.pb.dart +35 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +43 -2
- package/dart_types/pubspec.yaml +1 -1
- package/package.json +6 -2
- package/src/aggregate.ts +93 -0
- package/src/anonymous_value_interning.ts +205 -0
- package/src/baseline.ts +27 -0
- package/src/baseline_export.ts +432 -0
- package/src/baseline_import.ts +312 -0
- package/src/baseline_usda.ts +186 -0
- package/src/birth_key_rule.ts +472 -0
- package/src/births.ts +207 -24
- package/src/build_provenance.ts +17 -2
- package/src/business_birth.ts +50 -4
- package/src/check_package_main.ts +50 -6
- package/src/codegen_call_proto.ts +5 -18
- package/src/codegen_dart.ts +95 -3
- package/src/codegen_dart_client.ts +73 -15
- package/src/codegen_dart_facade.ts +58 -35
- package/src/codegen_dart_framework_calls.ts +32 -10
- package/src/codegen_dart_scenario.ts +7 -0
- package/src/codegen_proof.ts +5 -2
- package/src/codegen_result_proto.ts +8 -6
- package/src/codegen_result_usda.ts +4 -0
- package/src/codegen_ts.ts +135 -34
- package/src/compatibility_graph.ts +41 -4
- package/src/compatibility_usda.ts +11 -1
- package/src/compile_package_main.ts +272 -105
- package/src/composed_wire_identity.ts +24 -0
- package/src/directive.ts +6 -0
- package/src/domain_requirements.ts +75 -0
- package/src/engine_entry.ts +4 -48
- package/src/engine_javascript.ts +12 -6
- package/src/fields.ts +109 -0
- package/src/identity_usda.ts +77 -51
- package/src/index.ts +25 -1
- package/src/intent.ts +5 -0
- package/src/law_graph.ts +142 -1
- package/src/law_graph_usda.ts +25 -141
- package/src/law_layer_prims.ts +199 -0
- package/src/law_unit_selection.ts +45 -0
- package/src/law_unit_set_identity.ts +44 -0
- package/src/law_version.ts +107 -0
- package/src/package_config.ts +44 -0
- package/src/package_dependencies.ts +8 -0
- package/src/query/compile.ts +0 -0
- package/src/release_inspection.ts +24 -0
- package/src/release_publication.ts +298 -0
- package/src/value_object.ts +6 -3
- package/src/wire.ts +2 -0
- package/src/wire_paths.ts +17 -0
- package/src/workspace_routing.ts +1 -1
|
Binary file
|
package/compile_package.mjs
CHANGED
|
@@ -19,9 +19,11 @@ import { createRequire } from "node:module";
|
|
|
19
19
|
import path from "node:path";
|
|
20
20
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
21
21
|
import {
|
|
22
|
+
authoredImport,
|
|
22
23
|
compileInputs,
|
|
23
24
|
compileProofPath,
|
|
24
25
|
readCompileProof,
|
|
26
|
+
recordedImportedSources,
|
|
25
27
|
} from "./compile_proof.mjs";
|
|
26
28
|
|
|
27
29
|
const DSL_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -49,12 +51,21 @@ if (cacheable) {
|
|
|
49
51
|
dslDir: DSL_DIR,
|
|
50
52
|
configPath,
|
|
51
53
|
argumentIdentity,
|
|
54
|
+
importedSources: recordedImportedSources(proofPath),
|
|
52
55
|
});
|
|
53
56
|
const proof = readCompileProof({ proofPath, inputs });
|
|
54
57
|
if (proof !== undefined) {
|
|
55
58
|
console.log(`nomos-compile: ${proof.name} unchanged`);
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
// The law identity is the unit set (CAP-773): the compile wrote it beside the package it proves.
|
|
60
|
+
const unitSet = (() => {
|
|
61
|
+
try {
|
|
62
|
+
return readFileSync(proof.law.replace(/\.law\.usdz$/u, ".units.sha256"), "utf8").trim();
|
|
63
|
+
} catch {
|
|
64
|
+
return "(unreadable — recompile)";
|
|
65
|
+
}
|
|
66
|
+
})();
|
|
67
|
+
console.log(` law unit set ${unitSet}`);
|
|
68
|
+
console.log(` package ${proof.domainHash} — package — not what targets serve (${path.relative(process.cwd(), proof.law)})`);
|
|
58
69
|
console.log(` verify ✓ typed USDA content proof covers compiler, authored inputs and ${proof.outputCount} generated artifacts`);
|
|
59
70
|
process.exit(0);
|
|
60
71
|
}
|
|
@@ -85,6 +96,15 @@ const tsxApiEntry = path.join(
|
|
|
85
96
|
|
|
86
97
|
// Global hooks (not a namespaced `tsImport`): the compiler's `import.meta.url` and every
|
|
87
98
|
// `createRequire` must stay plain file URLs, exactly as under the tsx CLI.
|
|
99
|
+
// The compile's authored import closure (including modules outside the project directory) is
|
|
100
|
+
// collected here and recorded in the compile proof, so editing any of them invalidates it.
|
|
101
|
+
const importedSources = new Set();
|
|
102
|
+
globalThis[Symbol.for("nomos.compile.importedSources")] = importedSources;
|
|
88
103
|
const { register } = await import(pathToFileURL(tsxApiEntry).href);
|
|
89
|
-
register(
|
|
104
|
+
register({
|
|
105
|
+
onImport(url) {
|
|
106
|
+
const file = authoredImport(url, DSL_DIR);
|
|
107
|
+
if (file !== undefined) importedSources.add(file);
|
|
108
|
+
},
|
|
109
|
+
});
|
|
90
110
|
await import(pathToFileURL(MAIN).href);
|
package/compile_proof.d.mts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export interface CompileProofInputs {
|
|
2
2
|
readonly authoring: readonly { readonly path: string; readonly sha256: string }[];
|
|
3
3
|
readonly compiler: readonly { readonly path: string; readonly sha256: string }[];
|
|
4
|
+
/** The authored modules the compile imported, including any outside the project directory. */
|
|
5
|
+
readonly importedSources: readonly string[];
|
|
4
6
|
readonly authoringIdentity: string;
|
|
5
7
|
readonly compilerIdentity: string;
|
|
6
8
|
readonly argumentIdentity: string;
|
|
@@ -11,7 +13,12 @@ export function compileInputs(options: {
|
|
|
11
13
|
dslDir: string;
|
|
12
14
|
configPath: string;
|
|
13
15
|
argumentIdentity: string;
|
|
16
|
+
importedSources?: readonly string[];
|
|
14
17
|
}): CompileProofInputs;
|
|
18
|
+
/** The authored file a compile imported from `url`, or undefined for compiler and dependency code. */
|
|
19
|
+
export function authoredImport(url: string, dslDir: string): string | undefined;
|
|
20
|
+
/** The authored import closure a previous compile recorded in its proof. */
|
|
21
|
+
export function recordedImportedSources(proofPath: string): string[];
|
|
15
22
|
export function compileProofPath(cwd: string, configPath: string): string;
|
|
16
23
|
export function generatedOutputFiles(
|
|
17
24
|
outDir: string,
|
package/compile_proof.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "node:fs";
|
|
14
14
|
import { createRequire } from "node:module";
|
|
15
15
|
import path from "node:path";
|
|
16
|
-
import { pathToFileURL } from "node:url";
|
|
16
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
17
17
|
|
|
18
18
|
const INPUT_EXTENSIONS = new Set([
|
|
19
19
|
".ts", ".mts", ".cts", ".js", ".mjs", ".cjs", ".json", ".yaml", ".yml",
|
|
@@ -94,8 +94,38 @@ function dependencyPackageFiles(dslDir, cwd) {
|
|
|
94
94
|
return files;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
/**
|
|
98
|
+
* The authored module a running compile imported, or undefined for compiler and dependency code.
|
|
99
|
+
* A project may name modules outside its own directory (`../../domains/x.ts`); walking the project
|
|
100
|
+
* root never sees those, so the proof records the closure the compile actually loaded.
|
|
101
|
+
*/
|
|
102
|
+
export function authoredImport(url, dslDir) {
|
|
103
|
+
if (!url.startsWith("file:")) return undefined;
|
|
104
|
+
const file = path.resolve(fileURLToPath(url));
|
|
105
|
+
if (!INPUT_EXTENSIONS.has(path.extname(file))) return undefined;
|
|
106
|
+
if (file.split(path.sep).includes("node_modules")) return undefined;
|
|
107
|
+
const fromCompiler = path.relative(path.resolve(dslDir), file);
|
|
108
|
+
if (!fromCompiler.startsWith("..") && !path.isAbsolute(fromCompiler)) return undefined;
|
|
109
|
+
return file;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The authored import closure a previous compile recorded, so an unchanged check re-hashes it. */
|
|
113
|
+
export function recordedImportedSources(proofPath) {
|
|
114
|
+
let text;
|
|
115
|
+
try {
|
|
116
|
+
text = readFileSync(proofPath, "utf8");
|
|
117
|
+
} catch {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
const list = /custom uniform asset\[\] nomos:importedSources = \[([^\]]*)\]/u.exec(text)?.[1];
|
|
121
|
+
if (list === undefined) return [];
|
|
122
|
+
return [...list.matchAll(/@([^@]+)@/gu)].map((match) => path.resolve(path.dirname(proofPath), match[1]));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function compileInputs({ cwd, dslDir, configPath, argumentIdentity, importedSources = [] }) {
|
|
126
|
+
const imported = [...new Set(importedSources.map((file) => path.resolve(file)))].sort(compareText);
|
|
98
127
|
const authoring = inventory([
|
|
128
|
+
...imported,
|
|
99
129
|
configPath,
|
|
100
130
|
// An operator may compile an absolute project path from a different repo.
|
|
101
131
|
// Its sibling sources are inputs too; cwd alone can falsely certify stale law.
|
|
@@ -115,7 +145,9 @@ export function compileInputs({ cwd, dslDir, configPath, argumentIdentity }) {
|
|
|
115
145
|
return {
|
|
116
146
|
authoring,
|
|
117
147
|
compiler,
|
|
118
|
-
|
|
148
|
+
importedSources: imported,
|
|
149
|
+
// v2 of the authoring identity covers the imported closure; a v1 proof never certifies it.
|
|
150
|
+
authoringIdentity: inventoryIdentity("authoring-closure", authoring),
|
|
119
151
|
compilerIdentity: inventoryIdentity("implementation", compiler),
|
|
120
152
|
argumentIdentity: sha256(`nomos-compile-arguments-v1\0${argumentIdentity}`),
|
|
121
153
|
};
|
|
@@ -163,6 +195,9 @@ function emitCompileProof({ proofPath, inputs, outputs, name, domainHash, law })
|
|
|
163
195
|
` custom uniform string nomos:authoringIdentity = "${inputs.authoringIdentity}"`,
|
|
164
196
|
` custom uniform string nomos:compilerIdentity = "${inputs.compilerIdentity}"`,
|
|
165
197
|
` custom uniform string nomos:argumentIdentity = "${inputs.argumentIdentity}"`,
|
|
198
|
+
` custom uniform asset[] nomos:importedSources = [${(inputs.importedSources ?? [])
|
|
199
|
+
.map((file) => `@${path.relative(proofDir, file).replaceAll(path.sep, "/")}@`)
|
|
200
|
+
.join(", ")}]`,
|
|
166
201
|
" custom rel nomos:inputs = [",
|
|
167
202
|
...relationships("Inputs", inputPrims).map((line, index) => `${line}${index + 1 === inputPrims.length ? "" : ","}`),
|
|
168
203
|
" ]",
|
package/dart/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,63 @@
|
|
|
1
|
+
## 0.69.0
|
|
2
|
+
|
|
3
|
+
- The test cloud's partition proxy (`NomosTestCloud`) carries live sockets: each workspace's catch-up
|
|
4
|
+
stream and live offers pass through it while ONLINE, and going OFFLINE severs them too (CAP-826).
|
|
5
|
+
|
|
6
|
+
Uses nomos_types 0.14.0; regenerate generated clients with this compiler.
|
|
7
|
+
|
|
8
|
+
- Generated framework calls expose `domainRequirements` (CAP-666): for each application domain, the law unit and
|
|
9
|
+
the generated requirement identity this client was compiled against. `NomosApprovedModel` carries them to the
|
|
10
|
+
model-adoption desk, and the client inventory records them as the build's contract, so an application pinned
|
|
11
|
+
to one domain no longer goes stale when a rollout moves a domain it never touches.
|
|
12
|
+
- One parent per node (CAP-788): the kernel refuses, with `[birth-child-already-parented]`, a business birth whose
|
|
13
|
+
child already records a different parent. Names are fixed at birth (CAP-803): `keyedBirthWorkspace` and the
|
|
14
|
+
generated `open` / `fetch` / `watchOpenProgress(key)` address a business child by
|
|
15
|
+
`workspaceNameForKey(namespace, key)`, exactly as 0.68.0 did, and send no parent. The parent-scoped naming
|
|
16
|
+
proposed with CAP-788 is withdrawn before release.
|
|
17
|
+
- Birth keys are minted by default (CAP-804). A business birth with no `key` names its child by an id Nomos
|
|
18
|
+
mints inside the birth; generated applications open it with `openReference` / `fetchReference` / `mine()`
|
|
19
|
+
instead of `open(key)`. An encrypted `.owned()` birth reserves its minted key first (`session.mintId`). An
|
|
20
|
+
explicit key, `key: "<field>"`, must name a typed-id field and names children exactly as before. When a duplicate birth is refused on the losing peer
|
|
21
|
+
(`[birth-child-already-parented]`), the child's creation receipt completes with a `NomosOperationRefused`
|
|
22
|
+
error (new export; `NomosOperationStatus.refusal` / `isRefused`).
|
|
23
|
+
- Birth keys are typed ids (CAP-804 follow-up): a generated client's branded-id fields are extension types in read
|
|
24
|
+
models and query keys, and `open(key)` / `fetch(key)` / `watchOpenProgress(key)` take the key's branded type. A
|
|
25
|
+
write that changes a birth key is refused (`birth-key-write-once`).
|
|
26
|
+
- One console enrolment exchange is bounded at 20 s (`nomosConsoleEnrolmentTimeout`); a timeout is a retryable
|
|
27
|
+
transport failure, so a slow console no longer holds a person's write (CAP-807).
|
|
28
|
+
- Nested record members, map values, list elements and union variants carry their protobuf wire allocation in the
|
|
29
|
+
generated compatibility requirement, and the kernel judges them recursively (CAP-681). A client whose nested
|
|
30
|
+
allocation differs from the installed law is refused with the member named, never decoded wrongly.
|
|
31
|
+
- A reloaded app no longer sees an empty child list (CAP-824): `nomosFrameworkChildWorkspaces` (behind the
|
|
32
|
+
generated `mine()` / `available()`) waits out a retryable not-ready answer while the device is still preparing
|
|
33
|
+
its copy, as every other one-shot read does, so the caller gets the children or a real error. A write that meets
|
|
34
|
+
unfinished preparation is held and reported as `NomosWritePhase.preparing`, then completes; no client change.
|
|
35
|
+
- Bundles the 0.115.0 runtime: the kernel-side release qualification (CAP-789), the per-domain adoption record
|
|
36
|
+
(CAP-666), one parent per node with names fixed at birth (CAP-788, CAP-803), minted birth keys and the losing
|
|
37
|
+
peer's typed birth refusal (CAP-804), the pack indexed in place (CAP-802), pack imports in bounded steps
|
|
38
|
+
that never hold up a person's work (CAP-806), one law version per identity named by its unit set (CAP-809), the
|
|
39
|
+
v2 command frontier (CAP-810) and claimed law versions (CAP-811).
|
|
40
|
+
|
|
41
|
+
## 0.68.0
|
|
42
|
+
|
|
43
|
+
Breaking: regenerate generated Dart clients with this compiler.
|
|
44
|
+
|
|
45
|
+
- Generated clients name the law by its unit set (CAP-773). Generated framework calls expose `lawUnitSet` and
|
|
46
|
+
`lawPackageSha256` in place of `applicationBirthContract`, and no generated offer carries a law expectation;
|
|
47
|
+
the kernel selects current law from the workspace's own state. `OpenReceivingGroupRequest.contract` is retired:
|
|
48
|
+
a Home is born under the law its platform serves. Pass `unitSet` to `NomosApprovedModel` (nomos_types 0.13.0).
|
|
49
|
+
- A local offer's call returns after the watches it changed have delivered the post-offer state, and it re-reads
|
|
50
|
+
only the watches it can change (CAP-755, CAP-760). Rows watches merge the rows that changed instead of
|
|
51
|
+
re-reading the whole result (`NmsRowsMerge`, `NomosRows`), and `NomosRowProjection` redoes only those rows
|
|
52
|
+
(CAP-731, CAP-751).
|
|
53
|
+
- The sync status reports a custodian warming up as `CustodianWarmingPosture`, not as an error (CAP-769).
|
|
54
|
+
- `--dart-define=NOMOS_TRACE=true` writes an opt-in `[nomos-trace]` timeline across the save path (CAP-741).
|
|
55
|
+
- Testing: `NmsTestPeer` stands on an explicit `NmsTestPeerHost`; `NOMOS_TEST_PEER_HOST=native` runs AppSim on the
|
|
56
|
+
real Apple native host (CAP-717). `NOMOS_PEER_CLASSES` selects the Node peer's classes, e.g.
|
|
57
|
+
`personal-device` (CAP-722).
|
|
58
|
+
- Bundles the runtime with instant local writes, the live link changes and the fixes listed in
|
|
59
|
+
nomos_flutter 0.67.0.
|
|
60
|
+
|
|
1
61
|
## 0.67.2
|
|
2
62
|
|
|
3
63
|
- Bundles the 0.113.4 runtime: owed watch re-reads never starve the Peer Office's maintenance.
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
import 'dart:convert' show utf8;
|
|
4
4
|
import 'dart:typed_data' show Uint8List;
|
|
5
5
|
import 'package:meta/meta.dart' show internal;
|
|
6
|
-
import 'package:nomos_types/nomos_types.dart' show Sha256Digest;
|
|
7
|
-
import 'package:nomos_types/nomos_kernel_protocol.dart' show
|
|
6
|
+
import 'package:nomos_types/nomos_types.dart' show NomosDomainRequirement, Sha256Digest;
|
|
7
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' show BirthCertificate;
|
|
8
8
|
|
|
9
9
|
import 'generated_call.dart';
|
|
10
10
|
import 'result_usda.dart' show NmsUsdaLayer, NmsUsdaPrim;
|
|
@@ -235,10 +235,20 @@ abstract interface class NmsApplicationFrameworkCalls {
|
|
|
235
235
|
String get parentModelAuthoritySeatAggregate;
|
|
236
236
|
String get parentModelAuthoritySeatId;
|
|
237
237
|
|
|
238
|
-
///
|
|
239
|
-
///
|
|
240
|
-
///
|
|
241
|
-
|
|
238
|
+
/// THE LAW this application was compiled as (CAP-773): the unit set identity of its `build/<name>.units`.
|
|
239
|
+
/// An application approves law by it; each workspace serves a closure its own kernel composes from those
|
|
240
|
+
/// units, so no compiled value names what a workspace serves.
|
|
241
|
+
String get lawUnitSet;
|
|
242
|
+
|
|
243
|
+
/// The compiled package closure (`build/<name>.law.usdz`): the bytes a device stages through the
|
|
244
|
+
/// model-adoption desk on a package-lane workspace. Package — not what a unit-lane workspace serves.
|
|
245
|
+
String get lawPackageSha256;
|
|
246
|
+
|
|
247
|
+
/// THE PER-DOMAIN REQUIREMENTS (CAP-666), by application domain key: the exact law unit
|
|
248
|
+
/// (`domain:<key>` of the unit set) and the generated consumer requirement identity this
|
|
249
|
+
/// client was compiled against. An application pins its release by these beside [lawUnitSet];
|
|
250
|
+
/// the model-adoption desk and the client inventory carry them as this build's contract.
|
|
251
|
+
Map<String, NomosDomainRequirement> get domainRequirements;
|
|
242
252
|
|
|
243
253
|
NmsGeneratedCall enrollFirstDevice({
|
|
244
254
|
required String domain,
|
|
@@ -192,6 +192,13 @@ final class NomosConsoleEnrolmentRefused implements Exception {
|
|
|
192
192
|
String toString() => 'console enrolment refused (HTTP $status): $message';
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
/// How long one console enrolment exchange may take before the shell reports
|
|
196
|
+
/// it as a retryable transport failure. The console answers once the
|
|
197
|
+
/// workspaces the device asked for admitted it (its fan-out follows in the
|
|
198
|
+
/// background), so a healthy answer takes a few seconds; Peer Office asks
|
|
199
|
+
/// again with a fresh token rather than holding a person's write.
|
|
200
|
+
const Duration nomosConsoleEnrolmentTimeout = Duration(seconds: 20);
|
|
201
|
+
|
|
195
202
|
/// One console exchange: status and raw body.
|
|
196
203
|
final class NomosConsoleResponse {
|
|
197
204
|
const NomosConsoleResponse({required this.status, required this.body});
|
|
@@ -1,25 +1,32 @@
|
|
|
1
|
+
import 'dart:async';
|
|
1
2
|
import 'dart:convert';
|
|
2
3
|
import 'dart:io';
|
|
3
4
|
|
|
4
|
-
import 'console_enrolment.dart'
|
|
5
|
+
import 'console_enrolment.dart'
|
|
6
|
+
show NomosConsoleResponse, nomosConsoleEnrolmentTimeout;
|
|
5
7
|
|
|
6
8
|
/// The native shells' console transport: one JSON POST, the answer's status
|
|
7
9
|
/// and body. No retry and no interpretation: Peer Office owns both.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
///
|
|
11
|
+
/// The whole exchange is bounded by [timeout] ([nomosConsoleEnrolmentTimeout]) (CAP-807:
|
|
12
|
+
/// a slow console held a person's write for 53 s before this failed). A
|
|
13
|
+
/// [TimeoutException] is a retryable transport failure: the console may still
|
|
14
|
+
/// finish the enrolment, and the next attempt (a fresh token) finds it done.
|
|
15
|
+
Future<NomosConsoleResponse> nomosDefaultConsolePost(Uri url, String body,
|
|
16
|
+
{Duration timeout = nomosConsoleEnrolmentTimeout}) async {
|
|
17
|
+
final client = HttpClient()..connectionTimeout = timeout;
|
|
11
18
|
try {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
return await () async {
|
|
20
|
+
final request = await client.postUrl(url);
|
|
21
|
+
request.headers
|
|
22
|
+
..contentType = ContentType.json
|
|
23
|
+
..set(HttpHeaders.acceptHeader, 'application/json');
|
|
24
|
+
request.add(utf8.encode(body));
|
|
25
|
+
final response = await request.close();
|
|
26
|
+
final text = await response.transform(utf8.decoder).join();
|
|
27
|
+
return NomosConsoleResponse(status: response.statusCode, body: text);
|
|
28
|
+
}()
|
|
29
|
+
.timeout(timeout);
|
|
23
30
|
} finally {
|
|
24
31
|
client.close(force: true);
|
|
25
32
|
}
|
|
@@ -79,6 +79,10 @@ class NomosTestCloud {
|
|
|
79
79
|
bool _online = true;
|
|
80
80
|
bool _stopped = false;
|
|
81
81
|
|
|
82
|
+
/// Live sockets the proxy carries (each workspace's catch-up stream and live offers,
|
|
83
|
+
/// architecture/peer_catchup.md). Going OFFLINE severs them too, as a real link loss would.
|
|
84
|
+
final Set<WebSocket> _liveSockets = {};
|
|
85
|
+
|
|
82
86
|
NomosTestCloud._(
|
|
83
87
|
this._server,
|
|
84
88
|
this._proxy,
|
|
@@ -98,6 +102,12 @@ class NomosTestCloud {
|
|
|
98
102
|
/// cloud and peers converge. Deterministic: no timing races, the test decides exactly when the link exists.
|
|
99
103
|
void setOnline(bool online) {
|
|
100
104
|
_online = online;
|
|
105
|
+
if (!online) {
|
|
106
|
+
for (final socket in _liveSockets.toList()) {
|
|
107
|
+
socket.close(WebSocketStatus.goingAway, 'partitioned');
|
|
108
|
+
}
|
|
109
|
+
_liveSockets.clear();
|
|
110
|
+
}
|
|
101
111
|
_log(
|
|
102
112
|
'partition: ${online ? "ONLINE (link up)" : "OFFLINE (link severed)"}',
|
|
103
113
|
);
|
|
@@ -350,6 +360,10 @@ class NomosTestCloud {
|
|
|
350
360
|
} catch (_) {}
|
|
351
361
|
return;
|
|
352
362
|
}
|
|
363
|
+
if (WebSocketTransformer.isUpgradeRequest(req)) {
|
|
364
|
+
await _proxyLiveSocket(req);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
353
367
|
try {
|
|
354
368
|
final target = _upstream.replace(
|
|
355
369
|
path: req.uri.path,
|
|
@@ -375,6 +389,50 @@ class NomosTestCloud {
|
|
|
375
389
|
}
|
|
376
390
|
}
|
|
377
391
|
|
|
392
|
+
/// A WebSocket through the partition: both ends piped frame for frame, both closed together.
|
|
393
|
+
Future<void> _proxyLiveSocket(HttpRequest req) async {
|
|
394
|
+
final WebSocket upstream;
|
|
395
|
+
try {
|
|
396
|
+
upstream = await WebSocket.connect(_upstream
|
|
397
|
+
.replace(
|
|
398
|
+
scheme: _upstream.scheme == 'https' ? 'wss' : 'ws',
|
|
399
|
+
path: req.uri.path,
|
|
400
|
+
query: req.uri.query.isEmpty ? null : req.uri.query,
|
|
401
|
+
)
|
|
402
|
+
.toString());
|
|
403
|
+
} catch (_) {
|
|
404
|
+
try {
|
|
405
|
+
req.response.statusCode = 502;
|
|
406
|
+
await req.response.close();
|
|
407
|
+
} catch (_) {}
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
final WebSocket client;
|
|
411
|
+
try {
|
|
412
|
+
client = await WebSocketTransformer.upgrade(req);
|
|
413
|
+
} catch (_) {
|
|
414
|
+
await upstream.close();
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
_liveSockets
|
|
418
|
+
..add(client)
|
|
419
|
+
..add(upstream);
|
|
420
|
+
void closeBoth() {
|
|
421
|
+
_liveSockets
|
|
422
|
+
..remove(client)
|
|
423
|
+
..remove(upstream);
|
|
424
|
+
client.close();
|
|
425
|
+
upstream.close();
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
upstream.listen((frame) {
|
|
429
|
+
if (_online) client.add(frame);
|
|
430
|
+
}, onDone: closeBoth, onError: (_) => closeBoth());
|
|
431
|
+
client.listen((frame) {
|
|
432
|
+
if (_online) upstream.add(frame);
|
|
433
|
+
}, onDone: closeBoth, onError: (_) => closeBoth());
|
|
434
|
+
}
|
|
435
|
+
|
|
378
436
|
Future<Map<String, Object?>> _getJson(Uri url) async {
|
|
379
437
|
final req = await _client.getUrl(url);
|
|
380
438
|
final resp = await req.close();
|
|
@@ -394,6 +452,10 @@ class NomosTestCloud {
|
|
|
394
452
|
if (_stopped) return;
|
|
395
453
|
_stopped = true;
|
|
396
454
|
_log('stopping');
|
|
455
|
+
for (final socket in _liveSockets.toList()) {
|
|
456
|
+
socket.close();
|
|
457
|
+
}
|
|
458
|
+
_liveSockets.clear();
|
|
397
459
|
try {
|
|
398
460
|
await _proxy.close(force: true);
|
|
399
461
|
} catch (_) {}
|
|
@@ -2,6 +2,8 @@ import 'dart:typed_data';
|
|
|
2
2
|
|
|
3
3
|
import 'package:meta/meta.dart' show internal;
|
|
4
4
|
|
|
5
|
+
import 'nomos_trace.dart' show NomosTrace;
|
|
6
|
+
|
|
5
7
|
/// One compiler-authored, nominal domain call.
|
|
6
8
|
///
|
|
7
9
|
/// Generated applications construct this directly from their typed input. The
|
|
@@ -63,10 +65,17 @@ abstract final class NmsInternalGeneratedCalls {
|
|
|
63
65
|
throw ArgumentError(
|
|
64
66
|
'A generated Nomos call needs one exact typed Protobuf payload.');
|
|
65
67
|
}
|
|
66
|
-
return _GeneratedCall(domain, directive, protobufType, protobufPayload
|
|
68
|
+
return _GeneratedCall(domain, directive, protobufType, protobufPayload,
|
|
69
|
+
NomosTrace.enabled ? NomosTrace.now() : 0);
|
|
67
70
|
}
|
|
68
71
|
}
|
|
69
72
|
|
|
73
|
+
/// When generated code finished encoding [call] (Timeline microseconds), for the opt-in
|
|
74
|
+
/// timeline trace; 0 when tracing was off or the call was not compiler-authored.
|
|
75
|
+
@internal
|
|
76
|
+
int nmsGeneratedCallCreatedAt(NmsGeneratedCall call) =>
|
|
77
|
+
call is _GeneratedCall ? call.createdAtMicros : 0;
|
|
78
|
+
|
|
70
79
|
/// Constructor namespace reserved for generated query code.
|
|
71
80
|
@internal
|
|
72
81
|
abstract final class NmsInternalGeneratedQueries {
|
|
@@ -112,8 +121,10 @@ abstract final class NmsInternalGeneratedResults {
|
|
|
112
121
|
}
|
|
113
122
|
|
|
114
123
|
final class _GeneratedCall implements NmsGeneratedCall {
|
|
115
|
-
const _GeneratedCall(
|
|
116
|
-
this.
|
|
124
|
+
const _GeneratedCall(this.domain, this.directive, this.protobufType,
|
|
125
|
+
this.protobufPayload, this.createdAtMicros);
|
|
126
|
+
|
|
127
|
+
final int createdAtMicros;
|
|
117
128
|
|
|
118
129
|
@override
|
|
119
130
|
final String domain;
|
|
@@ -39,6 +39,12 @@ export 'package:nomos_types/src/foreign_ref.dart'
|
|
|
39
39
|
ForeignDenied,
|
|
40
40
|
ForeignUnreachable;
|
|
41
41
|
export 'reception/dart_reception.dart';
|
|
42
|
+
export 'rows_watch.dart'
|
|
43
|
+
show
|
|
44
|
+
NomosRowProjection,
|
|
45
|
+
NomosRows,
|
|
46
|
+
NomosRowsProjectionStream,
|
|
47
|
+
NomosWatchPositionMismatch;
|
|
42
48
|
export 'session_bound.dart';
|
|
43
49
|
export 'application_model_adoption.dart'
|
|
44
50
|
show
|
|
@@ -49,6 +55,7 @@ export 'application_model_adoption.dart'
|
|
|
49
55
|
NomosModelAdoptionPhase,
|
|
50
56
|
NomosModelAdoptionProgress;
|
|
51
57
|
export 'provider.dart';
|
|
58
|
+
export 'nomos_trace.dart' show NomosTrace;
|
|
52
59
|
export 'record_origin.dart';
|
|
53
60
|
export 'generated_call.dart'
|
|
54
61
|
show
|
|
@@ -20,6 +20,8 @@ import 'package:package_config/package_config.dart' show loadPackageConfigUri;
|
|
|
20
20
|
|
|
21
21
|
import 'package:nomos_types/nomos_kernel_protocol.dart' as nomos_protocol;
|
|
22
22
|
|
|
23
|
+
import 'nomos_trace.dart' show NomosTrace;
|
|
24
|
+
|
|
23
25
|
import 'runtime_v2_transport.dart'
|
|
24
26
|
show
|
|
25
27
|
NmsBufferedRuntimeEvents,
|
|
@@ -40,8 +42,15 @@ class NodeRunnerTransport
|
|
|
40
42
|
final void Function(String line)? _onDiagnostic;
|
|
41
43
|
final Map<int, Completer<nomos_protocol.RuntimeResponse>> _runtimeReplies =
|
|
42
44
|
{};
|
|
45
|
+
|
|
46
|
+
/// Opt-in timeline only: when each request was written, and when its answer was decoded.
|
|
47
|
+
final Map<int, int> _traceSentAt = {};
|
|
48
|
+
final Map<int, int> _traceArrivedAt = {};
|
|
43
49
|
bool _disposed = false;
|
|
44
50
|
|
|
51
|
+
/// A watch change was delivered in this event-loop turn and its listeners have not run yet.
|
|
52
|
+
bool _eventsAhead = false;
|
|
53
|
+
|
|
45
54
|
NodeRunnerTransport._(this._proc, this._onDiagnostic) {
|
|
46
55
|
_proc.stdout.transform(utf8.decoder).transform(const LineSplitter()).listen(
|
|
47
56
|
(line) {
|
|
@@ -49,12 +58,34 @@ class NodeRunnerTransport
|
|
|
49
58
|
if (s.isEmpty) return;
|
|
50
59
|
if (s.startsWith('runtime-v2:')) {
|
|
51
60
|
try {
|
|
61
|
+
final traceArrived = NomosTrace.enabled ? NomosTrace.now() : 0;
|
|
52
62
|
final response = nomos_protocol.RuntimeResponse.fromBuffer(
|
|
53
63
|
base64Decode(s.substring('runtime-v2:'.length)),
|
|
54
64
|
);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
65
|
+
if (NomosTrace.enabled) {
|
|
66
|
+
final id = response.requestId.toInt();
|
|
67
|
+
final now = NomosTrace.now();
|
|
68
|
+
_traceArrivedAt[id] = now;
|
|
69
|
+
final sent = _traceSentAt[id];
|
|
70
|
+
NomosTrace.emit(
|
|
71
|
+
'main',
|
|
72
|
+
'rt.recv',
|
|
73
|
+
'rr=$id outcome=${response.whichOutcome().name} '
|
|
74
|
+
'decUs=${now - traceArrived} '
|
|
75
|
+
'us=${sent == null ? -1 : now - sent} bytes=${s.length}');
|
|
76
|
+
}
|
|
77
|
+
final reply = _runtimeReplies.remove(response.requestId.toInt());
|
|
78
|
+
if (reply != null) {
|
|
79
|
+
if (_eventsAhead) {
|
|
80
|
+
// Watch changes the peer queued while serving this request were written ahead of
|
|
81
|
+
// its reply and may share this stdout chunk. Their listeners run as microtasks;
|
|
82
|
+
// complete the reply in the next event-loop turn so they all run first (CAP-755: a
|
|
83
|
+
// local offer's watches have delivered before its call returns).
|
|
84
|
+
Timer.run(() => reply.complete(response));
|
|
85
|
+
} else {
|
|
86
|
+
reply.complete(response);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
58
89
|
} catch (error, stackTrace) {
|
|
59
90
|
_diag('[nomos-node-runner runtime-v2] $error');
|
|
60
91
|
for (final reply in _runtimeReplies.values) {
|
|
@@ -72,6 +103,11 @@ class NodeRunnerTransport
|
|
|
72
103
|
if (!consumeCredentialRefreshEvent(
|
|
73
104
|
event, this, _runtimeEvents.addError)) {
|
|
74
105
|
_runtimeEvents.add(event);
|
|
106
|
+
if (event.hasWatchChanged() && !_eventsAhead) {
|
|
107
|
+
_eventsAhead = true;
|
|
108
|
+
// Cleared once this chunk's microtasks (the events' listeners) have all run.
|
|
109
|
+
Timer.run(() => _eventsAhead = false);
|
|
110
|
+
}
|
|
75
111
|
}
|
|
76
112
|
} catch (error, stackTrace) {
|
|
77
113
|
_runtimeEvents.addError(error, stackTrace);
|
|
@@ -89,7 +125,15 @@ class NodeRunnerTransport
|
|
|
89
125
|
_proc.stderr
|
|
90
126
|
.transform(utf8.decoder)
|
|
91
127
|
.transform(const LineSplitter())
|
|
92
|
-
.listen((line)
|
|
128
|
+
.listen((line) {
|
|
129
|
+
// The peer's own timeline lines join this process's log as they are (nomos_trace.dart).
|
|
130
|
+
if (line.startsWith('[nomos-trace] ')) {
|
|
131
|
+
// ignore: avoid_print
|
|
132
|
+
print(line);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
_diag('[nomos-node-runner] $line');
|
|
136
|
+
});
|
|
93
137
|
}
|
|
94
138
|
|
|
95
139
|
void _diag(String line) {
|
|
@@ -199,15 +243,40 @@ class NodeRunnerTransport
|
|
|
199
243
|
}
|
|
200
244
|
final reply = Completer<nomos_protocol.RuntimeResponse>();
|
|
201
245
|
_runtimeReplies[id] = reply;
|
|
246
|
+
final traceStarted = NomosTrace.enabled ? NomosTrace.now() : 0;
|
|
247
|
+
if (NomosTrace.enabled && !request.hasTrace()) {
|
|
248
|
+
// Sampled: the peer writes its own timeline lines for this frame.
|
|
249
|
+
request.trace = NomosTrace.sampledContext();
|
|
250
|
+
}
|
|
202
251
|
try {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
)
|
|
252
|
+
final line = 'runtime-v2:${base64Encode(request.writeToBuffer())}';
|
|
253
|
+
_proc.stdin.writeln(line);
|
|
254
|
+
if (NomosTrace.enabled) {
|
|
255
|
+
_traceSentAt[id] = NomosTrace.now();
|
|
256
|
+
NomosTrace.emit(
|
|
257
|
+
'main',
|
|
258
|
+
'rt.send',
|
|
259
|
+
'rr=$id op=${NomosTrace.describeRequest(request)} '
|
|
260
|
+
'lr=${request.hasKernelCall() ? request.kernelCall.request.requestId : '-'} '
|
|
261
|
+
'encUs=${NomosTrace.now() - traceStarted} bytes=${line.length}');
|
|
262
|
+
}
|
|
206
263
|
} catch (error, stackTrace) {
|
|
207
264
|
_runtimeReplies.remove(id);
|
|
208
265
|
reply.completeError(error, stackTrace);
|
|
209
266
|
}
|
|
210
|
-
return reply.future;
|
|
267
|
+
if (!NomosTrace.enabled) return reply.future;
|
|
268
|
+
return reply.future.then((response) {
|
|
269
|
+
// From the decoded answer to the awaiting code running again.
|
|
270
|
+
final arrived = _traceArrivedAt.remove(id);
|
|
271
|
+
final sent = _traceSentAt.remove(id);
|
|
272
|
+
final now = NomosTrace.now();
|
|
273
|
+
NomosTrace.emit(
|
|
274
|
+
'main',
|
|
275
|
+
'rt.resume',
|
|
276
|
+
'rr=$id resumeUs=${arrived == null ? -1 : now - arrived} '
|
|
277
|
+
'us=${sent == null ? -1 : now - sent}');
|
|
278
|
+
return response;
|
|
279
|
+
});
|
|
211
280
|
}
|
|
212
281
|
|
|
213
282
|
/// Tear down: stop the runner process and close the message stream.
|