@githolon/dsl 0.111.1 → 0.112.1
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/runner/node-entry.mjs +30 -18
- package/dart/lib/src/runtime_v2_transport.dart +31 -20
- package/dart/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/compile_package_main.ts +7 -2
- package/src/inspect_release_main.ts +17 -0
- package/src/law_units.ts +1 -1
- package/src/release_inspection.ts +147 -0
package/dart/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
## 0.66.10
|
|
2
|
+
|
|
3
|
+
- Share the existing typed platform-effect handler with browser background work, so console enrolment, device identity and law acquisition can complete without a foreground invocation.
|
|
4
|
+
- Restage the peer runtime with residency-aware live correspondence and full Git-ref retention checks.
|
|
5
|
+
|
|
1
6
|
## 0.66.9
|
|
2
7
|
|
|
3
8
|
- Prepare delayed workspace enrollment inside Peer Office and retry a proven non-admitted write once. Generated applications can observe preparation while retaining their draft.
|
|
@@ -7290,7 +7290,7 @@ function runtimeKernelPort(engine) {
|
|
|
7290
7290
|
case: "resolveRef",
|
|
7291
7291
|
value: create(ResolveRefRequestSchema, {
|
|
7292
7292
|
repo: workspaceRepositoryPath(workspace),
|
|
7293
|
-
reference: `refs/heads/${branch}`
|
|
7293
|
+
reference: branch.startsWith("refs/") ? branch : `refs/heads/${branch}`
|
|
7294
7294
|
})
|
|
7295
7295
|
}
|
|
7296
7296
|
})
|
|
@@ -8272,7 +8272,6 @@ function gitCustodyDesk(kernel, fetchImpl = fetch, observe, phaseStarted) {
|
|
|
8272
8272
|
return acquired;
|
|
8273
8273
|
},
|
|
8274
8274
|
deepenMain(workspace, ledger) {
|
|
8275
|
-
if (ledger.operatorCustody !== true) return Promise.resolve({ changed: false, complete: true });
|
|
8276
8275
|
return acquiring(workspace, ledger, "deepen-main", async () => {
|
|
8277
8276
|
if (resolveRef(workspace, branchRef("main")) === void 0) {
|
|
8278
8277
|
const acquired = await acquireMain(workspace, ledger);
|
|
@@ -13601,6 +13600,7 @@ var PeerPlatformShell = class {
|
|
|
13601
13600
|
// one constitutional genesis offer. WorkspaceAccessDesk decides whether
|
|
13602
13601
|
// that exact request may proceed against the unborn kernel mount.
|
|
13603
13602
|
async openCustody(workspace) {
|
|
13603
|
+
if (physical.openLocalCustody) return physical.openLocalCustody(workspace);
|
|
13604
13604
|
await custody.acquireMain(workspace, await ledgerFor(workspace));
|
|
13605
13605
|
return {};
|
|
13606
13606
|
},
|
|
@@ -13616,7 +13616,12 @@ var PeerPlatformShell = class {
|
|
|
13616
13616
|
},
|
|
13617
13617
|
persistBornCustody: (effect) => physical.persistBornCustody(effect),
|
|
13618
13618
|
retainFrontier: (effect) => physical.retainFrontier(effect),
|
|
13619
|
-
...physical.
|
|
13619
|
+
...physical.advanceHistory ? {
|
|
13620
|
+
advanceHistory: (workspace, capability) => {
|
|
13621
|
+
const credential = capability ? this.#credentials.authToken(capability) : authToken;
|
|
13622
|
+
return physical.advanceHistory(workspace, credential && new TextDecoder().decode(credential));
|
|
13623
|
+
}
|
|
13624
|
+
} : physical.completeHistoryInLocalCustody ? {
|
|
13620
13625
|
// The kernel mounts a shallow working copy; the peer's own Git custody is deep.
|
|
13621
13626
|
// One bounded slice per call; the Peer Office decides whether and when to take the next.
|
|
13622
13627
|
async advanceHistory(workspace) {
|
|
@@ -22969,10 +22974,11 @@ var LiveCorrespondenceDesk = class {
|
|
|
22969
22974
|
failures: 0,
|
|
22970
22975
|
origin: origin2,
|
|
22971
22976
|
publisher: !binding.cloudBound,
|
|
22972
|
-
published: !binding.cloudBound ? head : void 0
|
|
22977
|
+
published: !binding.cloudBound ? head : void 0,
|
|
22978
|
+
wasResident: this.kernel.isResident(binding.workspace) !== false
|
|
22973
22979
|
};
|
|
22974
22980
|
this.#links.set(binding.workspace, link);
|
|
22975
|
-
this.#open(link);
|
|
22981
|
+
if (link.wasResident) this.#open(link);
|
|
22976
22982
|
}
|
|
22977
22983
|
changed(workspace) {
|
|
22978
22984
|
const link = this.#links.get(workspace);
|
|
@@ -23129,9 +23135,11 @@ var LiveCorrespondenceDesk = class {
|
|
|
23129
23135
|
let wake;
|
|
23130
23136
|
for (const link of this.#links.values()) {
|
|
23131
23137
|
if (this.kernel.isResident(link.binding.workspace) === false) {
|
|
23132
|
-
this.disconnect(link.binding.workspace);
|
|
23138
|
+
if (link.wasResident) this.disconnect(link.binding.workspace);
|
|
23139
|
+
else wake = wake === void 0 || wake > 250n ? 250n : wake;
|
|
23133
23140
|
continue;
|
|
23134
23141
|
}
|
|
23142
|
+
link.wasResident = true;
|
|
23135
23143
|
if (link.state === "opening" && now >= link.due) {
|
|
23136
23144
|
this.#effect(link, { case: "close", value: true });
|
|
23137
23145
|
link.state = "closed";
|
|
@@ -30812,7 +30820,7 @@ var executeGitPushEffect = async (effect, fetchImpl) => {
|
|
|
30812
30820
|
});
|
|
30813
30821
|
}
|
|
30814
30822
|
};
|
|
30815
|
-
async function drivePeerFrame(runner, initialFrame, fetchImpl = globalThis.fetch, custody, bornCustody, credentialRefresh, yieldWakeToCaller = false, deviceIdentity, lawPackage, yieldLocalReadToCaller = false, yieldHistoryToCaller = false) {
|
|
30823
|
+
async function drivePeerFrame(runner, initialFrame, fetchImpl = globalThis.fetch, custody, bornCustody, credentialRefresh, yieldWakeToCaller = false, deviceIdentity, lawPackage, yieldLocalReadToCaller = false, yieldHistoryToCaller = false, performExternalEffect) {
|
|
30816
30824
|
const persistBornCustody = bornCustody ?? (custody?.persistBornCustody ? (effect) => custody.persistBornCustody(effect) : void 0);
|
|
30817
30825
|
let responseFrame = runner.handleFrame(initialFrame);
|
|
30818
30826
|
const satisfied = /* @__PURE__ */ new Set();
|
|
@@ -30948,12 +30956,10 @@ async function drivePeerFrame(runner, initialFrame, fetchImpl = globalThis.fetch
|
|
|
30948
30956
|
if (pending.effect.case === "historyAcquisition" && (yieldHistoryToCaller || !custody?.advanceHistory)) {
|
|
30949
30957
|
return responseFrame;
|
|
30950
30958
|
}
|
|
30951
|
-
if (pending.effect.case === "refreshCredential" && credentialRefresh === externalPeerEffect) return responseFrame;
|
|
30952
|
-
if (pending.effect.case === "deviceIdentity" && deviceIdentity === externalPeerEffect) return responseFrame;
|
|
30953
|
-
if (pending.effect.case === "lawPackage" && lawPackage === externalPeerEffect) return responseFrame;
|
|
30954
30959
|
if (pending.effect.case === "wake" && yieldWakeToCaller) return responseFrame;
|
|
30955
|
-
|
|
30956
|
-
|
|
30960
|
+
const external = pending.effect.case === "refreshCredential" && credentialRefresh === externalPeerEffect || pending.effect.case === "deviceIdentity" && deviceIdentity === externalPeerEffect || pending.effect.case === "lawPackage" && lawPackage === externalPeerEffect || pending.effect.case === "consoleEnrolment";
|
|
30961
|
+
if (external && !performExternalEffect) return responseFrame;
|
|
30962
|
+
const receipt = external ? await performExternalEffect(pending) : pending.effect.case === "http" ? await executeHttpEffect(pending.effect.value, fetchImpl) : pending.effect.case === "gitFetch" ? await executeGitFetchWithAlongside(pending.effect.value, fetchImpl) : pending.effect.case === "gitPush" ? await executeGitPushEffect(pending.effect.value, fetchImpl) : pending.effect.case === "persistBornCustody" ? create(PeerEffectReceiptSchema, {
|
|
30957
30963
|
effectId: pending.effect.value.effectId,
|
|
30958
30964
|
outcome: {
|
|
30959
30965
|
case: "bornCustodyPersisted",
|
|
@@ -30981,7 +30987,7 @@ async function drivePeerFrame(runner, initialFrame, fetchImpl = globalThis.fetch
|
|
|
30981
30987
|
async function executeHistoryAcquisitionEffect(effect, custody) {
|
|
30982
30988
|
try {
|
|
30983
30989
|
if (!custody.advanceHistory) throw new Error("platform shell has no history acquisition capability");
|
|
30984
|
-
const progress = await custody.advanceHistory(effect.workspace);
|
|
30990
|
+
const progress = await custody.advanceHistory(effect.workspace, effect.credentialCapability);
|
|
30985
30991
|
return create(PeerEffectReceiptSchema, {
|
|
30986
30992
|
effectId: effect.effectId,
|
|
30987
30993
|
outcome: {
|
|
@@ -31212,7 +31218,7 @@ var targetOf = (frame) => {
|
|
|
31212
31218
|
};
|
|
31213
31219
|
};
|
|
31214
31220
|
function openPeerOfficeHost(kernel, shell, fetchImpl = globalThis.fetch, maintenanceSettled, postEvent2 = () => {
|
|
31215
|
-
}, credentialRefresh, yieldWakeToCaller = false, deviceIdentity, lawPackage) {
|
|
31221
|
+
}, credentialRefresh, yieldWakeToCaller = false, deviceIdentity, lawPackage, performExternalEffect) {
|
|
31216
31222
|
let runner;
|
|
31217
31223
|
const configuredOrigins = new Set((shell.providerOrigins?.() ?? []).map((provider) => originOf(provider)).filter((origin2) => origin2 !== void 0));
|
|
31218
31224
|
const permittedSocket = (url) => {
|
|
@@ -31221,10 +31227,10 @@ function openPeerOfficeHost(kernel, shell, fetchImpl = globalThis.fetch, mainten
|
|
|
31221
31227
|
return origin2 !== void 0 && configuredOrigins.has(origin2);
|
|
31222
31228
|
};
|
|
31223
31229
|
const sockets = socketEffects(
|
|
31224
|
-
(frame) => drivePeerFrame(runner, frame, fetchImpl, shell.custodyPort(), void 0, void 0, false, deviceIdentity, lawPackage),
|
|
31230
|
+
(frame) => drivePeerFrame(runner, frame, fetchImpl, shell.custodyPort(), void 0, void 0, false, deviceIdentity, lawPackage, false, false, performExternalEffect),
|
|
31225
31231
|
permittedSocket
|
|
31226
31232
|
);
|
|
31227
|
-
const credentials = credentialEffects((frame) => drivePeerFrame(runner, frame, fetchImpl, shell.custodyPort(), void 0, credentialRefresh, false, deviceIdentity, lawPackage), credentialRefresh);
|
|
31233
|
+
const credentials = credentialEffects((frame) => drivePeerFrame(runner, frame, fetchImpl, shell.custodyPort(), void 0, credentialRefresh, false, deviceIdentity, lawPackage, false, false, performExternalEffect), credentialRefresh);
|
|
31228
31234
|
runner = createPeerOfficeFrameRunner(
|
|
31229
31235
|
kernel,
|
|
31230
31236
|
shell.credentials(),
|
|
@@ -31249,7 +31255,10 @@ function openPeerOfficeHost(kernel, shell, fetchImpl = globalThis.fetch, mainten
|
|
|
31249
31255
|
credentialRefresh,
|
|
31250
31256
|
false,
|
|
31251
31257
|
deviceIdentity,
|
|
31252
|
-
lawPackage
|
|
31258
|
+
lawPackage,
|
|
31259
|
+
false,
|
|
31260
|
+
false,
|
|
31261
|
+
performExternalEffect
|
|
31253
31262
|
);
|
|
31254
31263
|
},
|
|
31255
31264
|
shell.peerDeskStore()
|
|
@@ -31301,7 +31310,10 @@ function openPeerOfficeHost(kernel, shell, fetchImpl = globalThis.fetch, mainten
|
|
|
31301
31310
|
credentialRefresh,
|
|
31302
31311
|
yieldWakeToCaller,
|
|
31303
31312
|
deviceIdentity,
|
|
31304
|
-
lawPackage
|
|
31313
|
+
lawPackage,
|
|
31314
|
+
false,
|
|
31315
|
+
false,
|
|
31316
|
+
performExternalEffect
|
|
31305
31317
|
);
|
|
31306
31318
|
},
|
|
31307
31319
|
settleRetention: (withinMillis) => runner.settleRetention(withinMillis),
|
|
@@ -477,6 +477,36 @@ mixin NmsPlatformCredentialProviderHost
|
|
|
477
477
|
}
|
|
478
478
|
}
|
|
479
479
|
|
|
480
|
+
/// Execute one typed effect carried by either a foreground or background paper.
|
|
481
|
+
Future<nomos_protocol.PeerEffectReceipt> performExternalEffect(
|
|
482
|
+
nomos_protocol.CorrespondencePending pending,
|
|
483
|
+
) async {
|
|
484
|
+
final nomos_protocol.PeerEffectReceipt receipt;
|
|
485
|
+
if (pending.hasDeviceIdentity()) {
|
|
486
|
+
receipt = await performDeviceIdentityEffect(pending.deviceIdentity);
|
|
487
|
+
} else if (pending.hasLawPackage()) {
|
|
488
|
+
receipt = await performLawPackageEffect(pending.lawPackage);
|
|
489
|
+
} else if (pending.hasConsoleEnrolment()) {
|
|
490
|
+
receipt = await performConsoleEnrolment(pending.consoleEnrolment);
|
|
491
|
+
} else if (pending.hasWake()) {
|
|
492
|
+
// The shared runtime chooses the delay and checks its own clock.
|
|
493
|
+
// Waiting outside the serial Node frame lane keeps it available for
|
|
494
|
+
// credential-provider calls while authentication is pending.
|
|
495
|
+
await Future<void>.delayed(
|
|
496
|
+
Duration(milliseconds: pending.wake.delayMillis),
|
|
497
|
+
);
|
|
498
|
+
receipt = nomos_protocol.PeerEffectReceipt(
|
|
499
|
+
effectId: pending.wake.effectId,
|
|
500
|
+
woken: nomos_protocol.PeerWoken(),
|
|
501
|
+
);
|
|
502
|
+
} else if (pending.hasRefreshCredential()) {
|
|
503
|
+
receipt = await performCredentialRefresh(pending.refreshCredential);
|
|
504
|
+
} else {
|
|
505
|
+
throw StateError('Unsupported enclosing-shell effect');
|
|
506
|
+
}
|
|
507
|
+
return receipt;
|
|
508
|
+
}
|
|
509
|
+
|
|
480
510
|
Future<nomos_protocol.RuntimeResponse> driveCredentialEffects(
|
|
481
511
|
nomos_protocol.RuntimeRequest request,
|
|
482
512
|
Future<nomos_protocol.RuntimeResponse> Function(
|
|
@@ -498,26 +528,7 @@ mixin NmsPlatformCredentialProviderHost
|
|
|
498
528
|
!response.correspondencePending.hasConsoleEnrolment() &&
|
|
499
529
|
!response.correspondencePending.hasWake())) return response;
|
|
500
530
|
final pending = response.correspondencePending;
|
|
501
|
-
final
|
|
502
|
-
if (pending.hasDeviceIdentity()) {
|
|
503
|
-
receipt = await performDeviceIdentityEffect(pending.deviceIdentity);
|
|
504
|
-
} else if (pending.hasLawPackage()) {
|
|
505
|
-
receipt = await performLawPackageEffect(pending.lawPackage);
|
|
506
|
-
} else if (pending.hasConsoleEnrolment()) {
|
|
507
|
-
receipt = await performConsoleEnrolment(pending.consoleEnrolment);
|
|
508
|
-
} else if (pending.hasWake()) {
|
|
509
|
-
// The shared runtime chooses the delay and checks its own clock.
|
|
510
|
-
// Waiting outside the serial Node frame lane keeps it available for
|
|
511
|
-
// credential-provider calls while authentication is pending.
|
|
512
|
-
await Future<void>.delayed(
|
|
513
|
-
Duration(milliseconds: pending.wake.delayMillis));
|
|
514
|
-
receipt = nomos_protocol.PeerEffectReceipt(
|
|
515
|
-
effectId: pending.wake.effectId,
|
|
516
|
-
woken: nomos_protocol.PeerWoken(),
|
|
517
|
-
);
|
|
518
|
-
} else {
|
|
519
|
-
receipt = await performCredentialRefresh(pending.refreshCredential);
|
|
520
|
-
}
|
|
531
|
+
final receipt = await performExternalEffect(pending);
|
|
521
532
|
next = nomos_protocol.RuntimeRequest(
|
|
522
533
|
protocolVersion: 2,
|
|
523
534
|
requestId: request.requestId,
|
package/dart/pubspec.yaml
CHANGED
|
@@ -7,7 +7,7 @@ repository: https://github.com/Captain-App/nomos2
|
|
|
7
7
|
description: >-
|
|
8
8
|
Nomos 2 Dart client — generated domain values, intent payloads and application
|
|
9
9
|
lifecycle types over the typed Nomos kernel protocol and USDA custody model.
|
|
10
|
-
version: 0.66.
|
|
10
|
+
version: 0.66.10
|
|
11
11
|
|
|
12
12
|
environment:
|
|
13
13
|
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.1",
|
|
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.1",
|
|
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
|
+
}
|
|
@@ -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(
|
|
@@ -2544,8 +2545,8 @@ 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.10";
|
|
2549
|
+
const NOMOS_FLUTTER_DEP = "^0.65.15";
|
|
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,6 +3856,7 @@ 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;`,
|
|
3857
3862
|
`export 'package:nomos_client/src/write_progress.dart' show NomosWrite, NomosWriteProgress, NomosWritePhase;`,
|
|
@@ -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
|
+
}
|