@githolon/dsl 0.108.4 → 0.108.7
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 +5 -0
- package/dart/lib/src/application_framework_calls.dart +21 -0
- package/dart/lib/src/dev_cloud.dart +67 -0
- package/dart/lib/src/nms_test_environment.dart +1 -1
- package/dart/lib/src/runner/node-entry.mjs +354 -104
- package/dart/lib/src/runner/offline-install.mjs +64 -8
- package/dart/lib/src/session_bound.dart +58 -30
- package/dart/lib/src/test_harness.dart +60 -98
- package/dart/lib/src/webview_bridge.dart +212 -32
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/node_runner_transport_test.dart +5 -5
- package/dart/test/webview_bridge_test.dart +6 -0
- package/dart_types/CHANGELOG.md +4 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +428 -2
- package/dart_types/pubspec.yaml +1 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +293 -79
- package/src/application_topology.ts +120 -28
- package/src/births.ts +27 -0
- package/src/codegen_call_usda.ts +9 -4
- package/src/codegen_dart_application.ts +146 -37
- package/src/codegen_dart_client.ts +225 -160
- package/src/codegen_dart_framework_calls.ts +89 -4
- package/src/codegen_ts.ts +135 -60
- package/src/compile_package_main.ts +3598 -1484
- package/src/directive.ts +320 -118
- package/src/fields.ts +2 -3
- package/src/framework/application_home.ts +3 -0
- package/src/framework/auth_provider_law.ts +20 -28
- package/src/framework/genesis.ts +32 -1
- package/src/framework/home.ts +45 -8
- package/src/framework/signing_identity.ts +78 -0
- package/src/framework/workspaces.ts +9 -1
- package/src/framework_call_dart_enums.ts +33 -0
- package/src/framework_call_definitions.ts +11 -2
- package/src/index.ts +12 -0
- package/src/intent.ts +525 -194
- package/src/law_graph.ts +32 -4
- package/src/law_graph_usda.ts +3 -0
- package/src/package_config.ts +16 -1
- package/src/read.ts +33 -11
- package/src/wire.ts +1 -0
- package/src/wire_encode.ts +3 -3
|
@@ -4464,6 +4464,7 @@ __export(engine_exports, {
|
|
|
4464
4464
|
lawParity: () => lawParity,
|
|
4465
4465
|
ledgerStats: () => ledgerStats,
|
|
4466
4466
|
listRefusedRefs: () => listRefusedRefs,
|
|
4467
|
+
localCustodySnapshot: () => localCustodySnapshot,
|
|
4467
4468
|
materializeRefused: () => materializeRefused,
|
|
4468
4469
|
memoryDropProbe: () => memoryDropProbe,
|
|
4469
4470
|
migrateHomes: () => migrateHomes,
|
|
@@ -6276,6 +6277,16 @@ function prepareManagedPromotion(report, candidateHash, domain, opts = {}) {
|
|
|
6276
6277
|
error: `the package-wide report does not cover the generated business area '${domain}'`
|
|
6277
6278
|
};
|
|
6278
6279
|
}
|
|
6280
|
+
const dispositions = report.dispositions;
|
|
6281
|
+
const changesStableIdentity = dispositions !== void 0 && (dispositions.retired.length > 0 || Object.keys(dispositions.retyped).length > 0 || Object.keys(dispositions.rebinds).length > 0);
|
|
6282
|
+
if (changesStableIdentity && !acceptanceReason) {
|
|
6283
|
+
return {
|
|
6284
|
+
ok: false,
|
|
6285
|
+
stage: "acceptance",
|
|
6286
|
+
report,
|
|
6287
|
+
error: "the current-frontier transition changes stable business identities and needs an attributed acceptance reason"
|
|
6288
|
+
};
|
|
6289
|
+
}
|
|
6279
6290
|
const preflight = {
|
|
6280
6291
|
candidateHash,
|
|
6281
6292
|
// A package may be reached through any one of its generated domain clients. The
|
|
@@ -11045,6 +11056,10 @@ function presentOfferAdmitted(admitted) {
|
|
|
11045
11056
|
};
|
|
11046
11057
|
}
|
|
11047
11058
|
|
|
11059
|
+
// src/_adapter/proto/nomos/custody/v1/snapshot_pb.js
|
|
11060
|
+
var file_nomos_custody_v1_snapshot = /* @__PURE__ */ fileDesc("Ch9ub21vcy9jdXN0b2R5L3YxL3NuYXBzaG90LnByb3RvEhBub21vcy5jdXN0b2R5LnYxIqACChRMb2NhbEN1c3RvZHlTbmFwc2hvdBIWCg5mb3JtYXRfdmVyc2lvbhgBIAEoDRIWCgl3b3Jrc3BhY2UYAiABKAlIAIgBARIMCgR0cmVlGAMgASgMEhcKCmNoZWNrcG9pbnQYBCABKAxIAYgBARIVCghmcm9udGllchgFIAEoDEgCiAEBEhMKBmN1cnNvchgGIAEoCUgDiAEBEhcKCnN0YXRlX3Jvb3QYByABKAlIBIgBARIYCgtzeW5jZWRfYmFzZRgIIAEoCUgFiAEBQgwKCl93b3Jrc3BhY2VCDQoLX2NoZWNrcG9pbnRCCwoJX2Zyb250aWVyQgkKB19jdXJzb3JCDQoLX3N0YXRlX3Jvb3RCDgoMX3N5bmNlZF9iYXNlYgZwcm90bzM");
|
|
11061
|
+
var LocalCustodySnapshotSchema = /* @__PURE__ */ messageDesc(file_nomos_custody_v1_snapshot, 0);
|
|
11062
|
+
|
|
11048
11063
|
// src/_adapter/engine.ts
|
|
11049
11064
|
var enc3 = new TextEncoder();
|
|
11050
11065
|
var dec3 = new TextDecoder();
|
|
@@ -12807,8 +12822,11 @@ async function reconcileRequiredCustodyHead(eng2, ws, ledger, requiredCustodyHea
|
|
|
12807
12822
|
if (!/^[0-9a-f]{40}$/.test(requiredCustodyHead)) {
|
|
12808
12823
|
throw new Error("required custody head must be a lowercase 40-hex git object id");
|
|
12809
12824
|
}
|
|
12810
|
-
const
|
|
12811
|
-
|
|
12825
|
+
const resident = eng2.mounted.get(ws);
|
|
12826
|
+
const current = resident ? kgit.resolveRef(eng2, gitdirOf(ws), BRANCH) : "";
|
|
12827
|
+
if (current === requiredCustodyHead && resident?.remoteMain === requiredCustodyHead && !resident.restoreError) {
|
|
12828
|
+
return resident;
|
|
12829
|
+
}
|
|
12812
12830
|
if (current && isDirty(eng2, ws)) {
|
|
12813
12831
|
throw new Error(
|
|
12814
12832
|
`workspace '${ws}' has verified but unsealed resident work; refusing to discard it while reconciling custody`
|
|
@@ -12821,6 +12839,12 @@ async function reconcileRequiredCustodyHead(eng2, ws, ledger, requiredCustodyHea
|
|
|
12821
12839
|
`workspace '${ws}' could not reconcile required custody ${requiredCustodyHead}: ${mounted.restoreError || "custody has no verified main"}`
|
|
12822
12840
|
);
|
|
12823
12841
|
}
|
|
12842
|
+
const reconciled = kgit.resolveRef(eng2, gitdirOf(ws), BRANCH);
|
|
12843
|
+
if (reconciled !== requiredCustodyHead || mounted.remoteMain !== requiredCustodyHead) {
|
|
12844
|
+
throw new Error(
|
|
12845
|
+
`workspace '${ws}' restored custody ${mounted.remoteMain || reconciled || "none"}, not required custody ${requiredCustodyHead}`
|
|
12846
|
+
);
|
|
12847
|
+
}
|
|
12824
12848
|
return mounted;
|
|
12825
12849
|
}
|
|
12826
12850
|
function assertAuthored(res, what) {
|
|
@@ -12958,7 +12982,10 @@ async function applyWorkspaceEffect(eng2, ws, ledger, requestBytes, admissionChe
|
|
|
12958
12982
|
const result = response.outcome.value.result;
|
|
12959
12983
|
if (!result || result.outcome.case !== "admitted") {
|
|
12960
12984
|
const outcome = result?.outcome.case || "empty";
|
|
12961
|
-
|
|
12985
|
+
const detail = result?.outcome.case === "refused" ? result.outcome.value.refusal?.message : void 0;
|
|
12986
|
+
throw new Error(
|
|
12987
|
+
`target kernel ${outcome} the workspace effect${detail ? `: ${detail}` : ""}`
|
|
12988
|
+
);
|
|
12962
12989
|
}
|
|
12963
12990
|
await custodyPush(eng2, ws, ledger, { remoteRef: "refs/heads/main", ffOnly: true });
|
|
12964
12991
|
return toBinary(ApplyWorkspaceEffectResponseSchema, response.outcome.value);
|
|
@@ -13069,8 +13096,8 @@ function prepareRemoteOffer(eng2, ws, requestBytes) {
|
|
|
13069
13096
|
if (authoring.authorSecret.byteLength !== 0) {
|
|
13070
13097
|
throw new Error("remote offer preparation refuses client signing secrets");
|
|
13071
13098
|
}
|
|
13072
|
-
if (request.intent.case !== "call") {
|
|
13073
|
-
throw new Error("remote offer preparation requires a typed directive call");
|
|
13099
|
+
if (request.intent.case !== "call" && request.intent.case !== "modelLifecycle") {
|
|
13100
|
+
throw new Error("remote offer preparation requires a typed directive or model-lifecycle call");
|
|
13074
13101
|
}
|
|
13075
13102
|
const response = callProto(eng2.ex, {
|
|
13076
13103
|
case: "prepareRemoteOffer",
|
|
@@ -13877,7 +13904,13 @@ function packDelta(eng2, ws, base, head) {
|
|
|
13877
13904
|
case: "packDelta",
|
|
13878
13905
|
value: create(PackDeltaRequestSchema, { repo: repoArgOf(ws), base: base || void 0, head })
|
|
13879
13906
|
}, "packDelta").pack;
|
|
13880
|
-
} catch {
|
|
13907
|
+
} catch (error) {
|
|
13908
|
+
console.error("[nomos-custody-pack] packDelta refused", {
|
|
13909
|
+
workspace: ws,
|
|
13910
|
+
base: base || "<full>",
|
|
13911
|
+
head,
|
|
13912
|
+
error: String(error)
|
|
13913
|
+
});
|
|
13881
13914
|
return null;
|
|
13882
13915
|
}
|
|
13883
13916
|
}
|
|
@@ -15056,7 +15089,10 @@ async function emitOutbox(eng2, ws, ledger, workspaceEffectBase = "", admissionC
|
|
|
15056
15089
|
const targetResult = response.result;
|
|
15057
15090
|
if (!targetResult || targetResult.outcome.case !== "admitted") {
|
|
15058
15091
|
const outcome = targetResult?.outcome.case || "empty";
|
|
15059
|
-
|
|
15092
|
+
const detail = targetResult?.outcome.case === "refused" ? targetResult.outcome.value.refusal?.message : void 0;
|
|
15093
|
+
throw new Error(
|
|
15094
|
+
`target kernel ${outcome} the workspace effect${detail ? `: ${detail}` : ""}`
|
|
15095
|
+
);
|
|
15060
15096
|
}
|
|
15061
15097
|
break;
|
|
15062
15098
|
}
|
|
@@ -15088,7 +15124,7 @@ async function emitOutbox(eng2, ws, ledger, workspaceEffectBase = "", admissionC
|
|
|
15088
15124
|
} catch (e) {
|
|
15089
15125
|
errors++;
|
|
15090
15126
|
const destination = offer.route.case === "httpsRoute" ? offer.route.value.url : offer.route.case === "workspaceRoute" ? offer.route.value.workspace : offer.destination;
|
|
15091
|
-
log("outbox-deliver-failed", { ws, offerId: offer.id, destination, error: String(e).slice(0,
|
|
15127
|
+
log("outbox-deliver-failed", { ws, offerId: offer.id, destination, error: String(e).slice(0, 2e3) });
|
|
15092
15128
|
}
|
|
15093
15129
|
}
|
|
15094
15130
|
if (delivered.length || errors) log("outbox", { ws, delivered: delivered.length, errors });
|
|
@@ -15121,6 +15157,26 @@ function snapshot(eng2, ws) {
|
|
|
15121
15157
|
const wsDir = dirOf(eng2.preopen.dir.contents.get("ws")).contents.get(ws);
|
|
15122
15158
|
return serializeTree(wsDir);
|
|
15123
15159
|
}
|
|
15160
|
+
function localCustodySnapshot(eng2, ws) {
|
|
15161
|
+
const checkpoint2 = exportCheckpoint(eng2, ws);
|
|
15162
|
+
const tree = snapshot(eng2, ws);
|
|
15163
|
+
const head = headOf(eng2, ws);
|
|
15164
|
+
const body = toBinary(LocalCustodySnapshotSchema, create(LocalCustodySnapshotSchema, {
|
|
15165
|
+
formatVersion: 3,
|
|
15166
|
+
workspace: ws,
|
|
15167
|
+
tree,
|
|
15168
|
+
checkpoint: checkpoint2.blob ? bytesFromB64(checkpoint2.blob) : void 0,
|
|
15169
|
+
frontier: checkpoint2.frontier ? bytesFromB64(checkpoint2.frontier) : void 0,
|
|
15170
|
+
cursor: checkpoint2.cursor === void 0 ? void 0 : String(checkpoint2.cursor),
|
|
15171
|
+
stateRoot: checkpoint2.stateRoot,
|
|
15172
|
+
syncedBase: head || void 0
|
|
15173
|
+
}));
|
|
15174
|
+
const prefix = enc3.encode("NOMOS-CUSTODY-PB3\n");
|
|
15175
|
+
const framed = new Uint8Array(prefix.byteLength + body.byteLength);
|
|
15176
|
+
framed.set(prefix);
|
|
15177
|
+
framed.set(body, prefix.byteLength);
|
|
15178
|
+
return framed;
|
|
15179
|
+
}
|
|
15124
15180
|
function stats(eng2) {
|
|
15125
15181
|
return {
|
|
15126
15182
|
mounted: eng2.mounted.size,
|
|
@@ -284,6 +284,10 @@ Future<T> nmsInternalConvergeGeneratedModel<T>({
|
|
|
284
284
|
try {
|
|
285
285
|
return await attempt();
|
|
286
286
|
} on DomainClientModelAdoptionRequired catch (change) {
|
|
287
|
+
// A lifecycle response with no lawful next action is a terminal refusal,
|
|
288
|
+
// not a convergence step. Preserve the kernel's exact diagnostic instead
|
|
289
|
+
// of retrying until a generic stall obscures it.
|
|
290
|
+
if (!change.canStage && !change.canAccept) throw change;
|
|
287
291
|
final step = '${change.expectedDomainKey}\u0000'
|
|
288
292
|
'${change.expectedDomainHash}\u0000'
|
|
289
293
|
'${change.status}\u0000'
|
|
@@ -300,10 +304,11 @@ Future<T> nmsInternalConvergeGeneratedModel<T>({
|
|
|
300
304
|
}
|
|
301
305
|
try {
|
|
302
306
|
await handle(change);
|
|
303
|
-
} on DomainClientModelAdoptionRequired {
|
|
307
|
+
} on DomainClientModelAdoptionRequired catch (nextChange) {
|
|
304
308
|
// A transient preflight/acceptance race re-enters the same typed step.
|
|
305
309
|
// The per-step budget above prevents a genuine loop without limiting
|
|
306
310
|
// how many distinct historical generations may converge.
|
|
311
|
+
if (!nextChange.canStage && !nextChange.canAccept) throw nextChange;
|
|
307
312
|
}
|
|
308
313
|
}
|
|
309
314
|
}
|
|
@@ -379,35 +384,56 @@ class DomainNotInstalledForSession implements Exception {
|
|
|
379
384
|
/// cause classified; returns normally when the session serves the client's law compatibly.
|
|
380
385
|
extension NomosSessionCompat on NomosSession {
|
|
381
386
|
Future<void> assertCompatible(NomosClientInterface iface) async {
|
|
382
|
-
//
|
|
387
|
+
// The exact generated package identity is stronger than a structural comparison. The kernel's
|
|
388
|
+
// point-readable `law/current.usda` answers this bounded happy path without materialising the
|
|
389
|
+
// complete interface offer. Structural discovery is reserved for genuine version skew.
|
|
383
390
|
List<String> activeKeys = const [];
|
|
391
|
+
List<String> activeHashes = const [];
|
|
384
392
|
try {
|
|
385
|
-
final
|
|
386
|
-
|
|
393
|
+
final law = await lawStatus(iface.expectedDomainHash);
|
|
394
|
+
activeHashes = ((law['current'] as List?) ?? const [])
|
|
395
|
+
.map((value) => value.toString())
|
|
396
|
+
.toList(growable: false);
|
|
397
|
+
if (activeHashes.contains(iface.expectedDomainHash)) return;
|
|
398
|
+
activeKeys = ((law['currentDomains'] as List?) ?? const [])
|
|
399
|
+
.map((value) => value.toString())
|
|
400
|
+
.toList(growable: false);
|
|
401
|
+
// Compatibility is member-scoped, not one enormous application-wide ceremony. Once the
|
|
402
|
+
// authenticated current-law index proves this domain is served, generated reads decode their
|
|
403
|
+
// exact USDA result types and generated offers are judged by the current kernel route. A new
|
|
404
|
+
// member therefore fails closed when used without blocking every older compatible feature.
|
|
405
|
+
if (activeKeys.contains(iface.domain)) return;
|
|
406
|
+
} catch (_) {
|
|
407
|
+
// Preserve the structural fallback for older kernels which do not expose the typed current
|
|
408
|
+
// frontier. Its error classification below remains the authoritative diagnostic path.
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// 1. THE SHAPE AXIS (#72) — the one compatibility notion. The kernel compares the
|
|
412
|
+
// compact generated requirement directly with its authenticated current-law index. Do not
|
|
413
|
+
// export/materialise the complete interface merely to prove the successful path.
|
|
414
|
+
final requirement = iface.requirement;
|
|
415
|
+
if (requirement == null) return;
|
|
416
|
+
Map<String, Object?> verdict;
|
|
417
|
+
try {
|
|
418
|
+
final raw = await checkCompatibility(requirement);
|
|
419
|
+
verdict = (raw['verdict'] is Map)
|
|
420
|
+
? (raw['verdict'] as Map).cast<String, Object?>()
|
|
421
|
+
: raw;
|
|
387
422
|
} catch (e) {
|
|
388
|
-
// co2 blocker #2: a PROJECTION READ FAILURE (an unmountable/failing query shape — the runner surfaces
|
|
389
|
-
// `nomos.projection.read_failed` carrying the failed query) is NOT "not reachable" and MUST NOT be
|
|
390
|
-
// reclassified as trulyNotDeployed downstream — surface it as its own cause with the real fault text.
|
|
391
|
-
final isProjectionFault =
|
|
392
|
-
e.toString().contains('nomos.projection.read_failed') ||
|
|
393
|
-
e.toString().contains('projection read failed');
|
|
394
423
|
throw DomainClientSessionMismatch(
|
|
395
424
|
expectedDomainKey: iface.domain,
|
|
396
425
|
expectedDomainHash: iface.expectedDomainHash,
|
|
397
426
|
expectedInterfaceHash: iface.expectedInterfaceHash,
|
|
398
427
|
sessionId: workspace,
|
|
399
428
|
sessionWorkspace: workspace,
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
hint: isProjectionFault
|
|
404
|
-
? 'the workspace law could NOT be READ — a projection/mount failure (NOT undeployed law): $e'
|
|
405
|
-
: 'the session workspace is not reachable/mounted on this plane: $e',
|
|
429
|
+
activeDomainKeys: activeKeys,
|
|
430
|
+
activeDomainHashes: activeHashes,
|
|
431
|
+
hint: 'the shape-compatibility check itself failed: $e',
|
|
406
432
|
);
|
|
407
433
|
}
|
|
434
|
+
if (verdict['ok'] == true) return;
|
|
408
435
|
|
|
409
|
-
// 2. HASH
|
|
410
|
-
List<String> activeHashes = const [];
|
|
436
|
+
// 2. HASH DIAGNOSIS — only a failed verdict needs retained installation inventory.
|
|
411
437
|
if (iface.compatJson != null && iface.compatJson!.isNotEmpty) {
|
|
412
438
|
try {
|
|
413
439
|
final c = await checkCompat(
|
|
@@ -415,32 +441,34 @@ extension NomosSessionCompat on NomosSession {
|
|
|
415
441
|
iface.requirement);
|
|
416
442
|
if (c != null) activeHashes = c.workspaceRuns;
|
|
417
443
|
} catch (_) {
|
|
418
|
-
/* diagnosis only — never
|
|
444
|
+
/* diagnosis only — never replace the shape verdict */
|
|
419
445
|
}
|
|
420
446
|
}
|
|
421
447
|
|
|
422
|
-
// 3.
|
|
423
|
-
|
|
424
|
-
if (requirement == null) return;
|
|
425
|
-
Map<String, Object?> verdict;
|
|
448
|
+
// 3. DIAGNOSTIC OFFER — only a failed verdict needs the complete active-domain names to
|
|
449
|
+
// distinguish a newer client from the wrong package or a genuinely absent deployment.
|
|
426
450
|
try {
|
|
427
|
-
final
|
|
428
|
-
|
|
429
|
-
? (raw['verdict'] as Map).cast<String, Object?>()
|
|
430
|
-
: raw;
|
|
451
|
+
final offer = await interfaceOffer();
|
|
452
|
+
if (offer != null) activeKeys = offer.keys.toList()..sort();
|
|
431
453
|
} catch (e) {
|
|
454
|
+
final isProjectionFault =
|
|
455
|
+
e.toString().contains('nomos.projection.read_failed') ||
|
|
456
|
+
e.toString().contains('projection read failed');
|
|
432
457
|
throw DomainClientSessionMismatch(
|
|
433
458
|
expectedDomainKey: iface.domain,
|
|
434
459
|
expectedDomainHash: iface.expectedDomainHash,
|
|
435
460
|
expectedInterfaceHash: iface.expectedInterfaceHash,
|
|
436
461
|
sessionId: workspace,
|
|
437
462
|
sessionWorkspace: workspace,
|
|
438
|
-
activeDomainKeys: activeKeys,
|
|
439
463
|
activeDomainHashes: activeHashes,
|
|
440
|
-
|
|
464
|
+
cause: isProjectionFault
|
|
465
|
+
? SessionMismatchCause.projectionReadFailed
|
|
466
|
+
: null,
|
|
467
|
+
hint: isProjectionFault
|
|
468
|
+
? 'the workspace law could NOT be READ — a projection/mount failure (NOT undeployed law): $e'
|
|
469
|
+
: 'the session workspace is not reachable/mounted on this plane: $e',
|
|
441
470
|
);
|
|
442
471
|
}
|
|
443
|
-
if (verdict['ok'] == true) return;
|
|
444
472
|
|
|
445
473
|
final missing =
|
|
446
474
|
((verdict['missing'] as List?) ?? const []).map((e) => '$e').toList();
|
|
@@ -43,7 +43,7 @@ import 'dart:io';
|
|
|
43
43
|
import 'package:crypto/crypto.dart' show sha256;
|
|
44
44
|
import 'package:fixnum/fixnum.dart' show Int64;
|
|
45
45
|
import 'package:nomos_types/nomos_kernel_protocol.dart'
|
|
46
|
-
show BirthCertificate,
|
|
46
|
+
show BirthCertificate, SyncWorkspaceResponse;
|
|
47
47
|
import 'package:nomos_types/nomos_types.dart' show Sha256Digest;
|
|
48
48
|
import 'package:package_config/package_config.dart' show loadPackageConfigUri;
|
|
49
49
|
|
|
@@ -494,6 +494,14 @@ class NomosTestHarness {
|
|
|
494
494
|
/// `sha256(base64decode(...))` is the attestor keyHash a reader enrols to trust this source's signed reads.
|
|
495
495
|
String? devicePublicKey(String workspace) => _devicePublics[workspace];
|
|
496
496
|
|
|
497
|
+
/// The enrolled device identity used by provider nonce binding. Tests which
|
|
498
|
+
/// rotate an external IdP can mint the next proof without handling key bytes.
|
|
499
|
+
String? deviceKeyHash(String workspace) {
|
|
500
|
+
final publicKey = _devicePublics[workspace];
|
|
501
|
+
if (publicKey == null || publicKey.isEmpty) return null;
|
|
502
|
+
return sha256.convert(base64Decode(publicKey)).toString();
|
|
503
|
+
}
|
|
504
|
+
|
|
497
505
|
/// The base64 device SECRET a session was opened with (TEST HARNESS ONLY — the harness IS the device;
|
|
498
506
|
/// a real app keeps this in secure storage). Sign writes to locally-born children with it, e.g.
|
|
499
507
|
/// `bridge.acquireSession(childWs, authorSecret: harness.deviceSecret(home))`.
|
|
@@ -1480,6 +1488,7 @@ class NomosTestHarness {
|
|
|
1480
1488
|
static Future<NomosTestHarness> cloudBound({
|
|
1481
1489
|
required NomosTestCloud cloud,
|
|
1482
1490
|
required List<String> lawUsdzPaths,
|
|
1491
|
+
NmsApplicationFrameworkCalls? applicationFrameworkCalls,
|
|
1483
1492
|
String platformWorkspace = 'test-platform',
|
|
1484
1493
|
required String subject,
|
|
1485
1494
|
String? authToken,
|
|
@@ -1500,6 +1509,13 @@ class NomosTestHarness {
|
|
|
1500
1509
|
'no lawUsdzPaths — pass the build/<name>.law.usdz `npx githolon compile` emits',
|
|
1501
1510
|
);
|
|
1502
1511
|
}
|
|
1512
|
+
final frameworkCalls = applicationFrameworkCalls;
|
|
1513
|
+
if (frameworkCalls == null) {
|
|
1514
|
+
throw NomosTestHarnessException(
|
|
1515
|
+
'cloudBound requires this application\'s compiler-generated '
|
|
1516
|
+
'NmsApplicationFrameworkCalls; raw framework directive transport is not supported',
|
|
1517
|
+
);
|
|
1518
|
+
}
|
|
1503
1519
|
final entry = nodeEntry ??
|
|
1504
1520
|
await _resolveEntry(node, resolveFrom ?? Directory.current.path);
|
|
1505
1521
|
final bareUid =
|
|
@@ -1853,56 +1869,18 @@ class NomosTestHarness {
|
|
|
1853
1869
|
'capturedClock': DateTime.now().toUtc().millisecondsSinceEpoch,
|
|
1854
1870
|
'epoch': 0,
|
|
1855
1871
|
}, secret);
|
|
1856
|
-
// Mount the target workspace in the SAME kernel plane and author through its own
|
|
1857
|
-
// installed law. The HTTP host is neither given raw business fields nor a signing key;
|
|
1858
|
-
// sync carries the already-sealed offer and the receiving kernel replays the one gate.
|
|
1859
|
-
// The enroll route was still resolved above to prove the generated public surface
|
|
1860
|
-
// names this semantic route exactly once, but route strings grant no authority here.
|
|
1861
|
-
final platformAuthorSession = '$clientId-platform-author';
|
|
1862
|
-
await plane.connect(
|
|
1863
|
-
cloud: cloud.origin,
|
|
1864
|
-
workspace: platformWorkspace,
|
|
1865
|
-
session: platformAuthorSession,
|
|
1866
|
-
clientId: '$clientId-platform-author',
|
|
1867
|
-
authToken: idpToken,
|
|
1868
|
-
authorSecret: secret,
|
|
1869
|
-
);
|
|
1870
|
-
await plane.offerDirective(
|
|
1871
|
-
domain: applicationHomeDomain,
|
|
1872
|
-
directiveId: 'enrollFirstDevice',
|
|
1873
|
-
domainHash: controlHomeLawHash,
|
|
1874
|
-
actor: subject,
|
|
1875
|
-
authorSecret: secret,
|
|
1876
|
-
session: platformAuthorSession,
|
|
1877
|
-
payload: {
|
|
1878
|
-
'keyHash': keyHash,
|
|
1879
|
-
'verifiedVia': issuer,
|
|
1880
|
-
'validFromEpoch': 0,
|
|
1881
|
-
'grantedAt': DateTime.now().toUtc().toIso8601String(),
|
|
1882
|
-
'idpToken': idpToken,
|
|
1883
|
-
// The framework law owns this USDA object shape. Keep the
|
|
1884
|
-
// compiler-generated protobuf certificate structured while
|
|
1885
|
-
// lowering it into that domain value; never resurrect the
|
|
1886
|
-
// retired JSON string envelope (`delegationCert`).
|
|
1887
|
-
'delegationCertificate':
|
|
1888
|
-
_birthCertificateLawValue(delegationCert),
|
|
1889
|
-
},
|
|
1890
|
-
);
|
|
1891
|
-
await plane.sync(
|
|
1892
|
-
session: platformAuthorSession,
|
|
1893
|
-
);
|
|
1894
1872
|
return (
|
|
1895
1873
|
publicB64: publicB64,
|
|
1896
1874
|
secret: secret,
|
|
1897
1875
|
keyHash: keyHash,
|
|
1898
1876
|
idpToken: idpToken,
|
|
1877
|
+
issuer: issuer,
|
|
1878
|
+
delegationCert: delegationCert,
|
|
1899
1879
|
);
|
|
1900
1880
|
},
|
|
1901
1881
|
);
|
|
1902
|
-
// 5/6 — THE HOME CEREMONY.
|
|
1903
|
-
//
|
|
1904
|
-
// business fields or a signing secret and has no birth-authoring lane. The offer carries the
|
|
1905
|
-
// typed device-signed certificate. The kernel derives and enrolls the founding signer from it.
|
|
1882
|
+
// 5/6 — THE PRODUCTION HOME CEREMONY. Platform remains remote: Runtime v2 prepares both generated
|
|
1883
|
+
// calls against its live frontier, signs with this device, and opens the resulting Home custody.
|
|
1906
1884
|
String? home;
|
|
1907
1885
|
await _phased(
|
|
1908
1886
|
phase: '5/6: remote home-birth ceremony (attested)',
|
|
@@ -1912,65 +1890,49 @@ class NomosTestHarness {
|
|
|
1912
1890
|
onDiagnostic: onDiagnostic,
|
|
1913
1891
|
run: () async {
|
|
1914
1892
|
final homeName = _receivingGroupNameOf(subject);
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
);
|
|
1952
|
-
}
|
|
1953
|
-
final birthSync = await plane.sync(
|
|
1954
|
-
session: '$clientId-platform-author',
|
|
1955
|
-
);
|
|
1956
|
-
if (birthSync.pushFailed ||
|
|
1957
|
-
birthSync.diverged ||
|
|
1958
|
-
birthSync.stillAhead) {
|
|
1893
|
+
final bornAt = DateTime.now().toUtc().toIso8601String();
|
|
1894
|
+
final result = await plane
|
|
1895
|
+
.session('$clientId-onboard-scratch')
|
|
1896
|
+
.nomosInternalBirthReceivingGroup(
|
|
1897
|
+
parent: platformWorkspace,
|
|
1898
|
+
principal: bareUid,
|
|
1899
|
+
subject: subject,
|
|
1900
|
+
device: {
|
|
1901
|
+
'public': device.publicB64,
|
|
1902
|
+
'secret': device.secret,
|
|
1903
|
+
},
|
|
1904
|
+
authToken: device.idpToken,
|
|
1905
|
+
idToken: device.idpToken,
|
|
1906
|
+
verifiedVia: device.issuer,
|
|
1907
|
+
bornAt: bornAt,
|
|
1908
|
+
enrollFirstDeviceCall: frameworkCalls.enrollFirstDevice(
|
|
1909
|
+
domain: applicationHomeDomain,
|
|
1910
|
+
workspace: platformWorkspace,
|
|
1911
|
+
keyHash: device.keyHash,
|
|
1912
|
+
verifiedVia: device.issuer,
|
|
1913
|
+
validFromEpoch: 0,
|
|
1914
|
+
grantedAt: bornAt,
|
|
1915
|
+
idpToken: device.idpToken,
|
|
1916
|
+
delegationCertificate: device.delegationCert,
|
|
1917
|
+
),
|
|
1918
|
+
birthReceivingGroupCall: frameworkCalls.birthReceivingGroup(
|
|
1919
|
+
domain: applicationHomeDomain,
|
|
1920
|
+
workspace: platformWorkspace,
|
|
1921
|
+
owner: subject,
|
|
1922
|
+
frameworkHash: Sha256Digest(frameworkHash!),
|
|
1923
|
+
lawHash: Sha256Digest(applicationHomeLawHash),
|
|
1924
|
+
bornAt: bornAt,
|
|
1925
|
+
),
|
|
1926
|
+
confirm: true,
|
|
1927
|
+
);
|
|
1928
|
+
if (result.home != homeName) {
|
|
1959
1929
|
throw NomosTestHarnessException(
|
|
1960
|
-
'cloudBound:
|
|
1930
|
+
'cloudBound: production custody ceremony returned ${result.home} instead of "$homeName"',
|
|
1961
1931
|
);
|
|
1962
1932
|
}
|
|
1963
|
-
|
|
1964
|
-
final failedRemoteBirth = remoteOutcomes.where(
|
|
1965
|
-
(entry) =>
|
|
1966
|
-
entry.hasWorkspace() &&
|
|
1967
|
-
entry.workspace == homeName &&
|
|
1968
|
-
entry.status != BirthOutcomeStatus.BIRTH_OUTCOME_STATUS_BORN,
|
|
1969
|
-
);
|
|
1970
|
-
if (failedRemoteBirth.isNotEmpty) {
|
|
1933
|
+
if (!result.verified) {
|
|
1971
1934
|
throw NomosTestHarnessException(
|
|
1972
|
-
'cloudBound:
|
|
1973
|
-
'${failedRemoteBirth.toList()}',
|
|
1935
|
+
'cloudBound: born Home failed kernel verification: ${result.verificationError}',
|
|
1974
1936
|
);
|
|
1975
1937
|
}
|
|
1976
1938
|
home = homeName;
|