@githolon/dsl 0.111.0 → 0.112.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/CHANGELOG.md +11 -3
- package/dart/lib/src/reception/dart_reception.dart +48 -30
- package/dart/lib/src/runner/node-entry.mjs +134 -19
- package/dart/lib/src/runtime_v2_gateway.dart +1 -22
- package/dart/lib/src/runtime_v2_transport.dart +5 -1
- package/dart/lib/src/write_progress.dart +48 -0
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/child_authentication_repair_test.dart +46 -0
- package/dart/test/runtime_v2_gateway_test.dart +29 -0
- package/dart/test/write_progress_test.dart +56 -0
- package/dart_types/CHANGELOG.md +4 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/observability_desk.pb.dart +90 -1
- package/dart_types/lib/src/runtime_outcome.dart +46 -31
- package/dart_types/pubspec.yaml +1 -1
- package/inspect_release.mjs +42 -0
- package/package.json +4 -3
- package/src/client_inventory_metadata.ts +9 -0
- package/src/codegen_dart_facade.ts +1 -0
- package/src/compile_package_main.ts +9 -3
- package/src/inspect_release_main.ts +17 -0
- package/src/law_units.ts +1 -1
- package/src/release_inspection.ts +147 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import 'dart:async';
|
|
2
|
+
import 'package:nomos_client/src/write_progress.dart';
|
|
3
|
+
import 'package:test/test.dart';
|
|
4
|
+
|
|
5
|
+
void main() {
|
|
6
|
+
test('observation spans prerequisites and preserves the result', () async {
|
|
7
|
+
final phases = <NomosWritePhase>[];
|
|
8
|
+
final result = await NomosWrite.observe(() async {
|
|
9
|
+
NomosWrite.captureObserver()!(
|
|
10
|
+
const NomosWriteProgress(NomosWritePhase.preparing));
|
|
11
|
+
await Future<void>.delayed(Duration.zero);
|
|
12
|
+
return 42;
|
|
13
|
+
}, onProgress: (p) => phases.add(p.phase));
|
|
14
|
+
expect(result, 42);
|
|
15
|
+
expect(phases, [
|
|
16
|
+
NomosWritePhase.running,
|
|
17
|
+
NomosWritePhase.preparing,
|
|
18
|
+
NomosWritePhase.completed
|
|
19
|
+
]);
|
|
20
|
+
expect(NomosWrite.captureObserver(), isNull);
|
|
21
|
+
});
|
|
22
|
+
test('concurrent observers do not receive each other\'s progress', () async {
|
|
23
|
+
final first = <NomosWritePhase>[], second = <NomosWritePhase>[];
|
|
24
|
+
final gate = Completer<void>();
|
|
25
|
+
final one = NomosWrite.observe(() async {
|
|
26
|
+
final observer = NomosWrite.captureObserver()!;
|
|
27
|
+
await gate.future;
|
|
28
|
+
observer(const NomosWriteProgress(NomosWritePhase.preparing));
|
|
29
|
+
}, onProgress: (p) => first.add(p.phase));
|
|
30
|
+
await NomosWrite.observe(() async {},
|
|
31
|
+
onProgress: (p) => second.add(p.phase));
|
|
32
|
+
gate.complete();
|
|
33
|
+
await one;
|
|
34
|
+
expect(first, contains(NomosWritePhase.preparing));
|
|
35
|
+
expect(second, [NomosWritePhase.running, NomosWritePhase.completed]);
|
|
36
|
+
});
|
|
37
|
+
test('observer exceptions cannot reject or retry an action', () async {
|
|
38
|
+
var calls = 0;
|
|
39
|
+
expect(
|
|
40
|
+
await NomosWrite.observe(() async {
|
|
41
|
+
calls++;
|
|
42
|
+
return 7;
|
|
43
|
+
}, onProgress: (_) => throw StateError('widget gone')),
|
|
44
|
+
7);
|
|
45
|
+
expect(calls, 1);
|
|
46
|
+
});
|
|
47
|
+
test('typed failure is preserved without retry', () async {
|
|
48
|
+
final failure = StateError('refused');
|
|
49
|
+
final phases = <NomosWritePhase>[];
|
|
50
|
+
await expectLater(
|
|
51
|
+
NomosWrite.observe(() async => throw failure,
|
|
52
|
+
onProgress: (p) => phases.add(p.phase)),
|
|
53
|
+
throwsA(same(failure)));
|
|
54
|
+
expect(phases, [NomosWritePhase.running, NomosWritePhase.failed]);
|
|
55
|
+
});
|
|
56
|
+
}
|
package/dart_types/CHANGELOG.md
CHANGED
|
@@ -617,6 +617,7 @@ enum RuntimeEvent_Event {
|
|
|
617
617
|
localCopyPreserved,
|
|
618
618
|
foreignPlacementIgnored,
|
|
619
619
|
bindingQuarantined,
|
|
620
|
+
writePreparation,
|
|
620
621
|
notSet
|
|
621
622
|
}
|
|
622
623
|
|
|
@@ -644,6 +645,7 @@ class RuntimeEvent extends $pb.GeneratedMessage {
|
|
|
644
645
|
$5.PreservedLocalCopy? localCopyPreserved,
|
|
645
646
|
$6.ForeignPlacementIgnored? foreignPlacementIgnored,
|
|
646
647
|
$6.QuarantinedBinding? bindingQuarantined,
|
|
648
|
+
WritePreparation? writePreparation,
|
|
647
649
|
}) {
|
|
648
650
|
final result = create();
|
|
649
651
|
if (sequence != null) result.sequence = sequence;
|
|
@@ -675,6 +677,7 @@ class RuntimeEvent extends $pb.GeneratedMessage {
|
|
|
675
677
|
result.foreignPlacementIgnored = foreignPlacementIgnored;
|
|
676
678
|
if (bindingQuarantined != null)
|
|
677
679
|
result.bindingQuarantined = bindingQuarantined;
|
|
680
|
+
if (writePreparation != null) result.writePreparation = writePreparation;
|
|
678
681
|
return result;
|
|
679
682
|
}
|
|
680
683
|
|
|
@@ -709,6 +712,7 @@ class RuntimeEvent extends $pb.GeneratedMessage {
|
|
|
709
712
|
27: RuntimeEvent_Event.localCopyPreserved,
|
|
710
713
|
28: RuntimeEvent_Event.foreignPlacementIgnored,
|
|
711
714
|
29: RuntimeEvent_Event.bindingQuarantined,
|
|
715
|
+
30: RuntimeEvent_Event.writePreparation,
|
|
712
716
|
0: RuntimeEvent_Event.notSet
|
|
713
717
|
};
|
|
714
718
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
|
@@ -736,7 +740,8 @@ class RuntimeEvent extends $pb.GeneratedMessage {
|
|
|
736
740
|
26,
|
|
737
741
|
27,
|
|
738
742
|
28,
|
|
739
|
-
29
|
|
743
|
+
29,
|
|
744
|
+
30
|
|
740
745
|
])
|
|
741
746
|
..a<$fixnum.Int64>(
|
|
742
747
|
1, _omitFieldNames ? '' : 'sequence', $pb.PbFieldType.OU6,
|
|
@@ -789,6 +794,8 @@ class RuntimeEvent extends $pb.GeneratedMessage {
|
|
|
789
794
|
..aOM<$6.QuarantinedBinding>(
|
|
790
795
|
29, _omitFieldNames ? '' : 'bindingQuarantined',
|
|
791
796
|
subBuilder: $6.QuarantinedBinding.create)
|
|
797
|
+
..aOM<WritePreparation>(30, _omitFieldNames ? '' : 'writePreparation',
|
|
798
|
+
subBuilder: WritePreparation.create)
|
|
792
799
|
..hasRequiredFields = false;
|
|
793
800
|
|
|
794
801
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
@@ -830,6 +837,7 @@ class RuntimeEvent extends $pb.GeneratedMessage {
|
|
|
830
837
|
@$pb.TagNumber(27)
|
|
831
838
|
@$pb.TagNumber(28)
|
|
832
839
|
@$pb.TagNumber(29)
|
|
840
|
+
@$pb.TagNumber(30)
|
|
833
841
|
RuntimeEvent_Event whichEvent() => _RuntimeEvent_EventByTag[$_whichOneof(0)]!;
|
|
834
842
|
@$pb.TagNumber(10)
|
|
835
843
|
@$pb.TagNumber(11)
|
|
@@ -851,6 +859,7 @@ class RuntimeEvent extends $pb.GeneratedMessage {
|
|
|
851
859
|
@$pb.TagNumber(27)
|
|
852
860
|
@$pb.TagNumber(28)
|
|
853
861
|
@$pb.TagNumber(29)
|
|
862
|
+
@$pb.TagNumber(30)
|
|
854
863
|
void clearEvent() => $_clearField($_whichOneof(0));
|
|
855
864
|
|
|
856
865
|
@$pb.TagNumber(1)
|
|
@@ -1099,6 +1108,17 @@ class RuntimeEvent extends $pb.GeneratedMessage {
|
|
|
1099
1108
|
void clearBindingQuarantined() => $_clearField(29);
|
|
1100
1109
|
@$pb.TagNumber(29)
|
|
1101
1110
|
$6.QuarantinedBinding ensureBindingQuarantined() => $_ensure(21);
|
|
1111
|
+
|
|
1112
|
+
@$pb.TagNumber(30)
|
|
1113
|
+
WritePreparation get writePreparation => $_getN(22);
|
|
1114
|
+
@$pb.TagNumber(30)
|
|
1115
|
+
set writePreparation(WritePreparation value) => $_setField(30, value);
|
|
1116
|
+
@$pb.TagNumber(30)
|
|
1117
|
+
$core.bool hasWritePreparation() => $_has(22);
|
|
1118
|
+
@$pb.TagNumber(30)
|
|
1119
|
+
void clearWritePreparation() => $_clearField(30);
|
|
1120
|
+
@$pb.TagNumber(30)
|
|
1121
|
+
WritePreparation ensureWritePreparation() => $_ensure(22);
|
|
1102
1122
|
}
|
|
1103
1123
|
|
|
1104
1124
|
/// Application-wide (no session): the replica follows this peer keeps up with in the background.
|
|
@@ -2225,6 +2245,75 @@ class TransferProgress extends $pb.GeneratedMessage {
|
|
|
2225
2245
|
void clearKind() => $_clearField(3);
|
|
2226
2246
|
}
|
|
2227
2247
|
|
|
2248
|
+
/// Transient observation for one refused, not-yet-admitted generated call. The
|
|
2249
|
+
/// office owns recovery; observing this event is never required for correctness.
|
|
2250
|
+
class WritePreparation extends $pb.GeneratedMessage {
|
|
2251
|
+
factory WritePreparation({
|
|
2252
|
+
$core.String? requestId,
|
|
2253
|
+
$core.String? workspace,
|
|
2254
|
+
}) {
|
|
2255
|
+
final result = create();
|
|
2256
|
+
if (requestId != null) result.requestId = requestId;
|
|
2257
|
+
if (workspace != null) result.workspace = workspace;
|
|
2258
|
+
return result;
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
WritePreparation._();
|
|
2262
|
+
|
|
2263
|
+
factory WritePreparation.fromBuffer($core.List<$core.int> data,
|
|
2264
|
+
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
2265
|
+
create()..mergeFromBuffer(data, registry);
|
|
2266
|
+
factory WritePreparation.fromJson($core.String json,
|
|
2267
|
+
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
2268
|
+
create()..mergeFromJson(json, registry);
|
|
2269
|
+
|
|
2270
|
+
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
|
2271
|
+
_omitMessageNames ? '' : 'WritePreparation',
|
|
2272
|
+
package:
|
|
2273
|
+
const $pb.PackageName(_omitMessageNames ? '' : 'nomos.runtime.v2'),
|
|
2274
|
+
createEmptyInstance: create)
|
|
2275
|
+
..aOS(1, _omitFieldNames ? '' : 'requestId')
|
|
2276
|
+
..aOS(2, _omitFieldNames ? '' : 'workspace')
|
|
2277
|
+
..hasRequiredFields = false;
|
|
2278
|
+
|
|
2279
|
+
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
2280
|
+
WritePreparation clone() => deepCopy();
|
|
2281
|
+
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
2282
|
+
WritePreparation copyWith(void Function(WritePreparation) updates) =>
|
|
2283
|
+
super.copyWith((message) => updates(message as WritePreparation))
|
|
2284
|
+
as WritePreparation;
|
|
2285
|
+
|
|
2286
|
+
@$core.override
|
|
2287
|
+
$pb.BuilderInfo get info_ => _i;
|
|
2288
|
+
|
|
2289
|
+
@$core.pragma('dart2js:noInline')
|
|
2290
|
+
static WritePreparation create() => WritePreparation._();
|
|
2291
|
+
@$core.override
|
|
2292
|
+
WritePreparation createEmptyInstance() => create();
|
|
2293
|
+
@$core.pragma('dart2js:noInline')
|
|
2294
|
+
static WritePreparation getDefault() => _defaultInstance ??=
|
|
2295
|
+
$pb.GeneratedMessage.$_defaultFor<WritePreparation>(create);
|
|
2296
|
+
static WritePreparation? _defaultInstance;
|
|
2297
|
+
|
|
2298
|
+
@$pb.TagNumber(1)
|
|
2299
|
+
$core.String get requestId => $_getSZ(0);
|
|
2300
|
+
@$pb.TagNumber(1)
|
|
2301
|
+
set requestId($core.String value) => $_setString(0, value);
|
|
2302
|
+
@$pb.TagNumber(1)
|
|
2303
|
+
$core.bool hasRequestId() => $_has(0);
|
|
2304
|
+
@$pb.TagNumber(1)
|
|
2305
|
+
void clearRequestId() => $_clearField(1);
|
|
2306
|
+
|
|
2307
|
+
@$pb.TagNumber(2)
|
|
2308
|
+
$core.String get workspace => $_getSZ(1);
|
|
2309
|
+
@$pb.TagNumber(2)
|
|
2310
|
+
set workspace($core.String value) => $_setString(1, value);
|
|
2311
|
+
@$pb.TagNumber(2)
|
|
2312
|
+
$core.bool hasWorkspace() => $_has(1);
|
|
2313
|
+
@$pb.TagNumber(2)
|
|
2314
|
+
void clearWorkspace() => $_clearField(2);
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2228
2317
|
const $core.bool _omitFieldNames =
|
|
2229
2318
|
$core.bool.fromEnvironment('protobuf.omit_field_names');
|
|
2230
2319
|
const $core.bool _omitMessageNames =
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
library;
|
|
7
7
|
|
|
8
8
|
import 'proto/nomos/runtime/v2/observability_desk.pb.dart'
|
|
9
|
-
show RuntimeUnavailable;
|
|
9
|
+
show RuntimeUnavailable, RuntimeRefusal;
|
|
10
10
|
import 'proto/nomos/runtime/v2/observability_desk.pbenum.dart'
|
|
11
|
-
show RuntimeUnavailableReason;
|
|
11
|
+
show RuntimeUnavailableReason, RuntimeRefusalReason;
|
|
12
12
|
|
|
13
13
|
enum NomosUnavailableReason {
|
|
14
14
|
remoteOnlyOffline,
|
|
@@ -39,35 +39,33 @@ final class NomosUnavailable implements Exception {
|
|
|
39
39
|
final String detail;
|
|
40
40
|
|
|
41
41
|
/// The one translation of the generated `unavailable` arm, for every transport.
|
|
42
|
-
factory NomosUnavailable.fromRuntime(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
detail: unavailable.detail,
|
|
70
|
-
);
|
|
42
|
+
factory NomosUnavailable.fromRuntime(
|
|
43
|
+
RuntimeUnavailable unavailable,
|
|
44
|
+
) => NomosUnavailable(
|
|
45
|
+
reason: switch (unavailable.reason) {
|
|
46
|
+
RuntimeUnavailableReason.RUNTIME_UNAVAILABLE_REASON_REMOTE_ONLY_OFFLINE =>
|
|
47
|
+
NomosUnavailableReason.remoteOnlyOffline,
|
|
48
|
+
RuntimeUnavailableReason.RUNTIME_UNAVAILABLE_REASON_REMOTE_UNREACHABLE =>
|
|
49
|
+
NomosUnavailableReason.remoteUnreachable,
|
|
50
|
+
RuntimeUnavailableReason.RUNTIME_UNAVAILABLE_REASON_REMOTE_TIMEOUT =>
|
|
51
|
+
NomosUnavailableReason.remoteTimeout,
|
|
52
|
+
RuntimeUnavailableReason
|
|
53
|
+
.RUNTIME_UNAVAILABLE_REASON_LOCAL_REPLICA_NOT_READY =>
|
|
54
|
+
NomosUnavailableReason.localReplicaNotReady,
|
|
55
|
+
RuntimeUnavailableReason
|
|
56
|
+
.RUNTIME_UNAVAILABLE_REASON_CREDENTIAL_UNAVAILABLE =>
|
|
57
|
+
NomosUnavailableReason.credentialUnavailable,
|
|
58
|
+
RuntimeUnavailableReason.RUNTIME_UNAVAILABLE_REASON_WORKSPACE_OPENING =>
|
|
59
|
+
NomosUnavailableReason.workspaceOpening,
|
|
60
|
+
_ => NomosUnavailableReason.unknown,
|
|
61
|
+
},
|
|
62
|
+
workspace: unavailable.workspace,
|
|
63
|
+
retryable: unavailable.retryable,
|
|
64
|
+
retryAfter: unavailable.hasRetryAfterMillis()
|
|
65
|
+
? Duration(milliseconds: unavailable.retryAfterMillis)
|
|
66
|
+
: null,
|
|
67
|
+
detail: unavailable.detail,
|
|
68
|
+
);
|
|
71
69
|
|
|
72
70
|
@override
|
|
73
71
|
String toString() =>
|
|
@@ -91,6 +89,23 @@ final class NomosRefused implements Exception {
|
|
|
91
89
|
this.detail = '',
|
|
92
90
|
});
|
|
93
91
|
|
|
92
|
+
factory NomosRefused.fromRuntime(RuntimeRefusal refused) => NomosRefused(
|
|
93
|
+
reason: switch (refused.reason) {
|
|
94
|
+
RuntimeRefusalReason.RUNTIME_REFUSAL_REASON_INVALID_CREDENTIAL =>
|
|
95
|
+
NomosRefusalReason.invalidCredential,
|
|
96
|
+
RuntimeRefusalReason.RUNTIME_REFUSAL_REASON_ACCESS_DENIED =>
|
|
97
|
+
NomosRefusalReason.accessDenied,
|
|
98
|
+
RuntimeRefusalReason.RUNTIME_REFUSAL_REASON_PLACEMENT_POLICY =>
|
|
99
|
+
NomosRefusalReason.placementPolicy,
|
|
100
|
+
RuntimeRefusalReason.RUNTIME_REFUSAL_REASON_SESSION_UNBOUND =>
|
|
101
|
+
NomosRefusalReason.sessionUnbound,
|
|
102
|
+
_ => NomosRefusalReason.unknown,
|
|
103
|
+
},
|
|
104
|
+
workspace: refused.workspace,
|
|
105
|
+
retryable: refused.retryable,
|
|
106
|
+
detail: refused.detail,
|
|
107
|
+
);
|
|
108
|
+
|
|
94
109
|
final NomosRefusalReason reason;
|
|
95
110
|
final String workspace;
|
|
96
111
|
final bool retryable;
|
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. Typed protobuf and USDA boundary values plus the offer codec,
|
|
11
11
|
reactive read engine, schema validation, and data-only error/trace types.
|
|
12
|
-
version: 0.11.
|
|
12
|
+
version: 0.11.3
|
|
13
13
|
|
|
14
14
|
environment:
|
|
15
15
|
sdk: ^3.5.0
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
|
|
3
|
+
// remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
|
|
4
|
+
// wasm32-wasip1 artifact {kernel · projection · embedded QuickJS engine}.
|
|
5
|
+
// If a file isn't this / hosting this / authoring for this / proving this — it's gone.
|
|
6
|
+
|
|
7
|
+
// `githolon inspect-release` launcher — the twin of `compile_package.mjs`: resolve `tsx`
|
|
8
|
+
// (the caller's, else the DSL's own dependency) and run `src/inspect_release_main.ts`
|
|
9
|
+
// through its ESM loader in this process. Resolution goes through Node's
|
|
10
|
+
// resolver (createRequire), NOT node_modules/.bin paths — hoisted/workspace installs
|
|
11
|
+
// put the bin shims elsewhere.
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
13
|
+
import { createRequire } from "node:module";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
16
|
+
|
|
17
|
+
const DSL_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const MAIN = path.join(DSL_DIR, "src", "inspect_release_main.ts");
|
|
19
|
+
|
|
20
|
+
function resolvePkgDir(name, fromDir) {
|
|
21
|
+
try {
|
|
22
|
+
const req = createRequire(pathToFileURL(path.join(fromDir, "noop.js")));
|
|
23
|
+
return path.dirname(req.resolve(`${name}/package.json`));
|
|
24
|
+
} catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const tsxDir = resolvePkgDir("tsx", process.cwd()) ?? resolvePkgDir("tsx", DSL_DIR);
|
|
30
|
+
if (!tsxDir) {
|
|
31
|
+
console.error("githolon inspect-release: tsx not found — reinstall @githolon/dsl (tsx is one of its dependencies)");
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
const tsxApi = JSON.parse(readFileSync(path.join(tsxDir, "package.json"), "utf8")).exports?.["./esm/api"];
|
|
35
|
+
const tsxApiEntry = path.join(
|
|
36
|
+
tsxDir,
|
|
37
|
+
(typeof tsxApi === "string" ? tsxApi : tsxApi?.import?.default ?? tsxApi?.import ?? tsxApi?.default) ?? "dist/esm/api/index.mjs"
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const { register } = await import(pathToFileURL(tsxApiEntry).href);
|
|
41
|
+
register();
|
|
42
|
+
await import(pathToFileURL(MAIN).href);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@githolon/dsl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.112.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "Nomos 2 domain-authoring DSL: aggregates + directives in TS, executed and encoded to the Rust kernel's wire shapes.",
|
|
@@ -60,7 +60,8 @@
|
|
|
60
60
|
"compile_proof.d.mts",
|
|
61
61
|
"compile_package.mjs",
|
|
62
62
|
"check_package.mjs",
|
|
63
|
-
"lsp_server.mjs"
|
|
63
|
+
"lsp_server.mjs",
|
|
64
|
+
"inspect_release.mjs"
|
|
64
65
|
],
|
|
65
66
|
"publishConfig": {
|
|
66
67
|
"access": "public"
|
|
@@ -72,7 +73,7 @@
|
|
|
72
73
|
"test": "vitest run"
|
|
73
74
|
},
|
|
74
75
|
"dependencies": {
|
|
75
|
-
"@githolon/runtime": "^0.
|
|
76
|
+
"@githolon/runtime": "^0.112.0",
|
|
76
77
|
"@bufbuild/protobuf": "^2.13.0",
|
|
77
78
|
"@bufbuild/protoc-gen-es": "^2.13.0",
|
|
78
79
|
"esbuild": "0.24.2",
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
/** Hash the exact generated consumer requirement bytes, independently of installed law. */
|
|
4
|
+
export function dartClientInventoryMetadata(requirementsUsda: string): string {
|
|
5
|
+
const digest = createHash('sha256').update(requirementsUsda, 'utf8').digest('hex');
|
|
6
|
+
return `// GENERATED by nomos-compile. Observation identity only; not rollout approval.\n` +
|
|
7
|
+
`/// SHA-256 of this generated package's complete reception compatibility requirements.\n` +
|
|
8
|
+
`const String nomosReceptionRequirementsSha256 = '${digest}';\n`;
|
|
9
|
+
}
|
|
@@ -1906,6 +1906,7 @@ export function generateDartApplication(
|
|
|
1906
1906
|
return [
|
|
1907
1907
|
`// AUTO-GENERATED by nomos-compile — the tenant application surface for "${domain}".`,
|
|
1908
1908
|
`// Business facts in, business records out. Runtime lifecycle is deliberately absent.`,
|
|
1909
|
+
`export 'package:nomos_client/src/write_progress.dart' show NomosWrite, NomosWriteProgress, NomosWritePhase;`,
|
|
1909
1910
|
...((mod.workspaceInvariants ?? []).some(rule => rule.refusal) ? [
|
|
1910
1911
|
`export 'package:nomos_types/nomos_types.dart' show AdmissionDecision, Admitted, DomainRefused, FrameworkRefused, UnsupportedRefusalContract, NomosDomainRefusalEvidence, KernelRefusalCode;`,
|
|
1911
1912
|
] : []),
|
|
@@ -337,6 +337,7 @@ import {
|
|
|
337
337
|
encodeOpenUsdUsdc,
|
|
338
338
|
flattenOpenUsdToUsda,
|
|
339
339
|
} from "./openusd_compiled_layer.js";
|
|
340
|
+
import { dartClientInventoryMetadata } from "./client_inventory_metadata.js";
|
|
340
341
|
import { emitApplicationCompatibilityUsda } from "./compatibility_usda.js";
|
|
341
342
|
|
|
342
343
|
const DSL_DIR = path.resolve(
|
|
@@ -2540,12 +2541,12 @@ const dartTypesImportFor = (generatedSource: string): string => {
|
|
|
2540
2541
|
* just the floor generated code needs — a lagging pin forces tenants into dependency_overrides. */
|
|
2541
2542
|
// 0.3.0: SurfacedValue<T>/SurfacedObservation/PendingConflictRow — generated Surfaced-field typing +
|
|
2542
2543
|
// the pendingConflicts read rows.
|
|
2543
|
-
const NOMOS_TYPES_DEP = "^0.11.
|
|
2544
|
+
const NOMOS_TYPES_DEP = "^0.11.3";
|
|
2544
2545
|
// 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
|
|
2545
2546
|
// DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
|
|
2546
2547
|
// REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
|
|
2547
|
-
const NOMOS_CLIENT_DEP = "^0.66.
|
|
2548
|
-
const NOMOS_FLUTTER_DEP = "^0.65.
|
|
2548
|
+
const NOMOS_CLIENT_DEP = "^0.66.9";
|
|
2549
|
+
const NOMOS_FLUTTER_DEP = "^0.65.14";
|
|
2549
2550
|
// DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in the Dart and Flutter
|
|
2550
2551
|
// package pubspecs — a release without this bump fails the suite, so tenants never
|
|
2551
2552
|
// land back in dependency_overrides (the exact co2 report this closes, third occurrence).
|
|
@@ -3836,6 +3837,9 @@ function emitDartPackages(
|
|
|
3836
3837
|
);
|
|
3837
3838
|
written.push(packageApplicationPath);
|
|
3838
3839
|
}
|
|
3840
|
+
const inventoryMetadata = path.join(clientGen, "client_inventory_metadata.dart");
|
|
3841
|
+
writeFileSync(inventoryMetadata, dartClientInventoryMetadata(emitApplicationCompatibilityUsda(packageInterface)), "utf8");
|
|
3842
|
+
written.push(inventoryMetadata);
|
|
3839
3843
|
const clientBarrel = [
|
|
3840
3844
|
"// GENERATED by nomos-compile — the CLIENT package's public entrypoint. DO NOT EDIT.",
|
|
3841
3845
|
`/// The generated business application for ${describePackageName}.`,
|
|
@@ -3852,8 +3856,10 @@ function emitDartPackages(
|
|
|
3852
3856
|
`/// are deliberately not exported here.`,
|
|
3853
3857
|
"library;",
|
|
3854
3858
|
"",
|
|
3859
|
+
`export '${genRel}/client_inventory_metadata.dart' show nomosReceptionRequirementsSha256;`,
|
|
3855
3860
|
`export 'package:${packages.types}/${genRel}/${businessSharedFile}';`,
|
|
3856
3861
|
`export 'package:nomos_client/src/workspace_open_progress.dart' show ApplicationWorkspaceOpenProgress;`,
|
|
3862
|
+
`export 'package:nomos_client/src/write_progress.dart' show NomosWrite, NomosWriteProgress, NomosWritePhase;`,
|
|
3857
3863
|
`export 'package:nomos_types/nomos_types.dart' show CustodyError, CustodyPhase, CustodySource, NomosRecommendedAction;`,
|
|
3858
3864
|
...tenantTypesExportKeys
|
|
3859
3865
|
.map((file) => {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { inspectReleaseBaseline } from './release_inspection.js';
|
|
2
|
+
const args = process.argv.slice(2);
|
|
3
|
+
if (args.includes('--help')) {
|
|
4
|
+
console.log('githolon inspect-release --identity nomos.identity.usda --units build/app.units [--against prior.identity.usda] [--installed workspace.json]\nRead-only comparison. JSON to stdout; no admission or publication approval.');
|
|
5
|
+
} else {
|
|
6
|
+
try {
|
|
7
|
+
const options: Record<string, string> = {};
|
|
8
|
+
for (let i = 0; i < args.length; i += 2) {
|
|
9
|
+
const key = args[i]?.replace(/^--/, '');
|
|
10
|
+
if (!key || !['identity', 'units', 'against', 'installed'].includes(key) || !args[i + 1] || options[key]) throw new Error('Invalid arguments; use --help');
|
|
11
|
+
options[key] = args[i + 1]!;
|
|
12
|
+
}
|
|
13
|
+
if (!options.identity || !options.units) throw new Error('--identity and --units are required');
|
|
14
|
+
console.log(JSON.stringify(inspectReleaseBaseline({ identity: options.identity, units: options.units,
|
|
15
|
+
...(options.against ? { against: options.against } : {}), ...(options.installed ? { installed: options.installed } : {}) }), null, 2));
|
|
16
|
+
} catch (error) { console.error(`inspect-release: ${(error as Error).message}`); process.exitCode = 2; }
|
|
17
|
+
}
|
package/src/law_units.ts
CHANGED
|
@@ -255,7 +255,7 @@ export function lawUnitHash(
|
|
|
255
255
|
return hash.digest("hex");
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
function unitManifestUsda(unit: Omit<LawUnit, "manifest">): string {
|
|
258
|
+
export function unitManifestUsda(unit: Omit<LawUnit, "manifest">): string {
|
|
259
259
|
return [
|
|
260
260
|
"#usda 1.0",
|
|
261
261
|
"(",
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/** Read-only evidence, not an evolution/admission judge. No compile, mint or network calls. */
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { lstatSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { parseIdentityUsda } from './identity_usda.js';
|
|
6
|
+
import { identityRegistryFromModel } from './identity_model.js';
|
|
7
|
+
import { canonicalJson } from './canonical_json.js';
|
|
8
|
+
import { lawUnitHash, unitManifestUsda, type LawUnit } from './law_units.js';
|
|
9
|
+
|
|
10
|
+
const sha = (bytes: string | Uint8Array) => createHash('sha256').update(bytes).digest('hex');
|
|
11
|
+
const literal = '("(?:[^"\\\\]|\\\\.)*")';
|
|
12
|
+
function attribute(source: string, name: string): string {
|
|
13
|
+
const values = [...source.matchAll(new RegExp(`nomos:${name} = ${literal}`, 'g'))];
|
|
14
|
+
if (values.length !== 1) throw new Error(`Expected one nomos:${name}`);
|
|
15
|
+
return JSON.parse(values[0]![1]!);
|
|
16
|
+
}
|
|
17
|
+
function files(directory: string, root = directory): string[] {
|
|
18
|
+
return readdirSync(directory).sort().flatMap(name => {
|
|
19
|
+
const file = path.join(directory, name);
|
|
20
|
+
const stat = lstatSync(file);
|
|
21
|
+
if (stat.isSymbolicLink()) throw new Error(`Symlinks are not accepted in unit exports: ${file}`);
|
|
22
|
+
return stat.isDirectory() ? files(file, root) : [path.relative(root, file).split(path.sep).join('/')];
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/** Accept the compiler's canonical manifest dialect, verify every byte with the existing hasher,
|
|
26
|
+
* and round-trip the manifest through its existing emitter. Never interpret a general USD stage. */
|
|
27
|
+
export function readInspectionUnits(directory: string): LawUnit[] {
|
|
28
|
+
const keys = new Set<string>();
|
|
29
|
+
const units = readdirSync(directory).sort().map(name => {
|
|
30
|
+
const root = path.join(directory, name);
|
|
31
|
+
if (!lstatSync(root).isDirectory() || lstatSync(root).isSymbolicLink()) throw new Error('Expected unit directories');
|
|
32
|
+
const allFiles = files(root);
|
|
33
|
+
const source = readFileSync(path.join(root, 'unit.usda'), 'utf8');
|
|
34
|
+
const key = attribute(source, 'unit');
|
|
35
|
+
const kind = attribute(source, 'unitKind') as LawUnit['kind'];
|
|
36
|
+
const hash = attribute(source, 'unitHash');
|
|
37
|
+
if (!['domain', 'framework', 'composition'].includes(kind) || keys.has(key)) throw new Error('Invalid or duplicate unit key');
|
|
38
|
+
keys.add(key);
|
|
39
|
+
const domain = kind === 'domain' ? attribute(source, 'domain') : undefined;
|
|
40
|
+
if (key !== (domain === undefined ? kind : `domain:${domain}`)) throw new Error('Unit key/kind disagreement');
|
|
41
|
+
const assets = [...source.matchAll(/def NomosLawAsset "[^"]+"\s*\{([^}]+)\}/g)].map(match => {
|
|
42
|
+
const logicalPath = attribute(match[1]!, 'logicalPath');
|
|
43
|
+
if (logicalPath.startsWith('/') || logicalPath.split('/').some(p => p === '..' || p === '.' || !p) || logicalPath.includes('\\')) throw new Error('Unsafe unit asset path');
|
|
44
|
+
const file = path.join(root, logicalPath);
|
|
45
|
+
if (!realpathSync(file).startsWith(realpathSync(root) + path.sep)) throw new Error('Asset escaped unit');
|
|
46
|
+
const bytes = readFileSync(file);
|
|
47
|
+
const sha256 = attribute(match[1]!, 'sha256');
|
|
48
|
+
if (sha(bytes) !== sha256) throw new Error(`Asset digest mismatch: ${key}/${logicalPath}`);
|
|
49
|
+
return { logicalPath, sha256, bytes };
|
|
50
|
+
});
|
|
51
|
+
const routes = [...source.matchAll(/def NomosLawRoute "[^"]+"\s*\{([^}]+)\}/g)].map(match => ({
|
|
52
|
+
selector: attribute(match[1]!, 'selector'), planSha256: attribute(match[1]!, 'plan'),
|
|
53
|
+
}));
|
|
54
|
+
if (new Set(assets.map(a => a.logicalPath)).size !== assets.length || new Set(routes.map(r => r.selector)).size !== routes.length) throw new Error('Duplicate manifest entry');
|
|
55
|
+
if (JSON.stringify(allFiles) !== JSON.stringify([...assets.map(a => a.logicalPath), 'unit.usda'].sort())) throw new Error('Manifest file inventory mismatch');
|
|
56
|
+
const unit = { key, kind, ...(domain === undefined ? {} : { domain }), hash, assets, routes };
|
|
57
|
+
if (lawUnitHash(key, assets, routes) !== hash) throw new Error(`Unit digest mismatch: ${key}`);
|
|
58
|
+
if (Buffer.from(unitManifestUsda(unit)).toString('utf8') !== source) throw new Error('Unsupported noncanonical unit manifest');
|
|
59
|
+
return { ...unit, manifest: Buffer.from(source) };
|
|
60
|
+
});
|
|
61
|
+
if (!units.length) throw new Error('No compiled units');
|
|
62
|
+
return units;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function inspectReleaseBaseline(input: { identity: string; units: string; against?: string; installed?: string }) {
|
|
66
|
+
const identityBytes = readFileSync(input.identity, 'utf8');
|
|
67
|
+
const model = parseIdentityUsda(identityBytes);
|
|
68
|
+
const registry = identityRegistryFromModel(model);
|
|
69
|
+
const units = readInspectionUnits(input.units);
|
|
70
|
+
const changes: { entity: string; change: string }[] = [];
|
|
71
|
+
let baseline: { sha256: string; hasIdentityRegistry: boolean } | null = null;
|
|
72
|
+
if (input.against) {
|
|
73
|
+
const bytes = readFileSync(input.against, 'utf8');
|
|
74
|
+
const priorModel = parseIdentityUsda(bytes);
|
|
75
|
+
baseline = { sha256: sha(bytes), hasIdentityRegistry: priorModel.identity !== undefined };
|
|
76
|
+
const prior = identityRegistryFromModel(priorModel);
|
|
77
|
+
for (const key of [...new Set([...Object.keys(prior.entities), ...Object.keys(registry.entities)])].sort()) {
|
|
78
|
+
const before = prior.entities[key], after = registry.entities[key];
|
|
79
|
+
if (!before) changes.push({ entity: key, change: 'added' });
|
|
80
|
+
else if (!after) changes.push({ entity: key, change: 'missing-continuity' });
|
|
81
|
+
else if (canonicalJson(before) !== canonicalJson(after)) changes.push({ entity: key, change: 'changed-identity-metadata' });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Evidence from the compiler's emitted declarations only. Structural anonymous fields may have
|
|
85
|
+
// different prim prefixes from their registry kind, so operation fields match by SID + wire key.
|
|
86
|
+
const declarations = new Map<string, { label?: string; wire?: string; aliases?: string[] }[]>();
|
|
87
|
+
for (const unit of units) for (const asset of unit.assets) {
|
|
88
|
+
if (!/^domains\/.*\.usda$/.test(asset.logicalPath) && asset.logicalPath !== 'framework.usda') continue;
|
|
89
|
+
const source = Buffer.from(asset.bytes).toString('utf8');
|
|
90
|
+
for (const match of source.matchAll(/def \w+ "([a-z]_([a-f0-9]{16}))"\s*\{([\s\S]*?)(?=\n\s*(?:def |over |\}))/g)) {
|
|
91
|
+
const attrs = match[3]!;
|
|
92
|
+
const label = /nomos:label = ("(?:[^"\\]|\\.)*")/.exec(attrs);
|
|
93
|
+
const wire = /nomos:wireKey = ("(?:[^"\\]|\\.)*")/.exec(attrs);
|
|
94
|
+
const aliases = /nomos:wireAliases = (\[[^\n]*\])/.exec(attrs);
|
|
95
|
+
const item = { ...(label ? { label: JSON.parse(label[1]!) as string } : {}),
|
|
96
|
+
...(wire ? { wire: JSON.parse(wire[1]!) as string } : {}),
|
|
97
|
+
...(aliases ? { aliases: JSON.parse(aliases[1]!) as string[] } : {}) };
|
|
98
|
+
for (const key of [match[1]!, ...(wire ? [`u_${match[2]!}`] : [])]) {
|
|
99
|
+
declarations.set(key, [...(declarations.get(key) ?? []), item]);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const compiledIdentity = { observed: 0, unobserved: 0, differences: [] as { entity: string; reason: string }[] };
|
|
104
|
+
for (const [key, entity] of Object.entries(registry.entities)) {
|
|
105
|
+
const seen = declarations.get(key);
|
|
106
|
+
if (!seen) { compiledIdentity.unobserved++; continue; }
|
|
107
|
+
compiledIdentity.observed++;
|
|
108
|
+
if (!seen.some(row => row.label === entity.label || entity.priorLabels?.includes(row.label ?? ''))) {
|
|
109
|
+
compiledIdentity.differences.push({ entity: key, reason: 'label-disagreement' });
|
|
110
|
+
}
|
|
111
|
+
const wire = entity.operationFieldWire?.protobuf;
|
|
112
|
+
if (wire && !seen.some(row => row.wire === wire.canonical && canonicalJson([...(row.aliases ?? [])].sort()) === canonicalJson([...(wire.aliases ?? [])].sort()))) {
|
|
113
|
+
compiledIdentity.differences.push({ entity: key, reason: 'wire-allocation-disagreement' });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
let installed: null | { sha256: string; workspace: string; head: string; declarationCoverage: 'unknown' | 'observed'; comparisons: { closure: string; units: { key: string; status: string; compiled: string; installed: string | null }[] }[] | null } = null;
|
|
117
|
+
if (input.installed) {
|
|
118
|
+
const bytes = readFileSync(input.installed, 'utf8');
|
|
119
|
+
const value = JSON.parse(bytes);
|
|
120
|
+
// A console /v1/universe/workspaces/:name export. Identity is an observation of that response,
|
|
121
|
+
// not an attestation. Never confuse a client assertion with verified kernel custody.
|
|
122
|
+
const view = value.workspace ?? value;
|
|
123
|
+
const closures = view.law?.closures;
|
|
124
|
+
if (typeof view.name !== 'string' || typeof view.head !== 'string' || !view.head || (closures !== null && !Array.isArray(closures))) throw new Error('Expected a console workspace export with head and law.closures');
|
|
125
|
+
installed = { sha256: sha(bytes), workspace: view.name, head: view.head,
|
|
126
|
+
declarationCoverage: closures === null ? 'unknown' : 'observed',
|
|
127
|
+
comparisons: closures === null ? null : closures.map((closure: { closureIdentity: string; unitSet?: { units: { key: string; hash: string }[] } | null }) => {
|
|
128
|
+
if (typeof closure.closureIdentity !== 'string') throw new Error('Invalid closure');
|
|
129
|
+
const known = new Map((closure.unitSet?.units ?? []).map(row => {
|
|
130
|
+
if (typeof row.key !== 'string' || !/^[a-f0-9]{64}$/.test(row.hash)) throw new Error('Invalid installed unit');
|
|
131
|
+
return [row.key, row.hash];
|
|
132
|
+
}));
|
|
133
|
+
return { closure: closure.closureIdentity, units: units.map(unit => ({ key: unit.key, compiled: unit.hash,
|
|
134
|
+
installed: known.get(unit.key) ?? null,
|
|
135
|
+
status: !known.has(unit.key) ? 'unknown' : known.get(unit.key) === unit.hash ? 'identical' : 'different' })) };
|
|
136
|
+
}) };
|
|
137
|
+
}
|
|
138
|
+
return { schema: 'nomos.release-inspection.v1', admission: 'not-evaluated',
|
|
139
|
+
identity: { sha256: sha(identityBytes), hasIdentityRegistry: model.identity !== undefined,
|
|
140
|
+
active: Object.values(registry.entities).filter(e => e.status === 'active').length,
|
|
141
|
+
retired: Object.values(registry.entities).filter(e => e.status === 'retired').length },
|
|
142
|
+
baseline, changes, compiledIdentity,
|
|
143
|
+
units: units.map(({ key, hash }) => ({ key, hash })), installed,
|
|
144
|
+
unknown: ['Installed law does not establish complete authoring lineage (prior names, generations and all retirements).',
|
|
145
|
+
'Unobserved declarations may be out of package scope or structurally interned; absence is not a compatibility refusal.',
|
|
146
|
+
'Consumer compatibility, state evolution and publication provenance are not certified by this report.'] };
|
|
147
|
+
}
|