@githolon/dsl 0.116.4 → 0.117.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 +8 -0
- package/dart/lib/src/reception/dart_reception.dart +31 -9
- package/dart/lib/src/runner/node-entry.mjs +13 -2
- package/dart/pubspec.yaml +1 -1
- package/dart/test/realm_close_pending_test.dart +55 -0
- package/package.json +4 -2
- package/src/codegen_ts.ts +9 -2
- package/src/compile_package_main.ts +80 -14
- package/src/protocol/nomos/kernel/v1/openusd_cache_pb.d.ts +240 -0
- package/src/protocol/nomos/kernel/v1/openusd_cache_pb.js +68 -0
- package/src/reception_artifact.ts +66 -0
- package/src/reception_artifact_contract.ts +258 -0
- package/src/reception_cache.ts +45 -0
- package/src/reception_template.ts +81 -0
package/dart/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## 0.70.3
|
|
2
|
+
|
|
3
|
+
Refresh the packaged Peer Office runner from the shared client source used by the hosted headless application client. The existing text-decoder BOM behavior is explicit; the kernel and Peer Office protocols are unchanged.
|
|
4
|
+
|
|
5
|
+
- Keep the runtime event subscription alive while realm closure waits for preparation source release and acknowledged custody retention, within the existing ten-second disposal deadline.
|
|
6
|
+
|
|
7
|
+
- Refresh outdated Git custody advertisements before rejecting an acknowledged frontier, while preserving ancestry and compare-and-swap safety checks.
|
|
8
|
+
|
|
1
9
|
## 0.70.2
|
|
2
10
|
|
|
3
11
|
- Carries the restaged peer runner for the native host 0.68.1 selection (CAP-874); its runtime identity moves with the runtime's patch release.
|
|
@@ -31,6 +31,8 @@ import 'package:nomos_types/nomos_types.dart'
|
|
|
31
31
|
show
|
|
32
32
|
nomosErrorFromInfo,
|
|
33
33
|
NomosErrorInfo,
|
|
34
|
+
NomosRuntimeError,
|
|
35
|
+
NomosRuntimeFailure,
|
|
34
36
|
ParentSignerEnrollmentMissing,
|
|
35
37
|
NomosRef,
|
|
36
38
|
VersionedNomosRef,
|
|
@@ -4224,15 +4226,35 @@ class DartReception {
|
|
|
4224
4226
|
// host's transport disposal) releases the subscriptions regardless.
|
|
4225
4227
|
}
|
|
4226
4228
|
try {
|
|
4227
|
-
final
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4229
|
+
final closing = Stopwatch()..start();
|
|
4230
|
+
const deadline = Duration(seconds: 10);
|
|
4231
|
+
while (true) {
|
|
4232
|
+
final remaining = deadline - closing.elapsed;
|
|
4233
|
+
if (remaining <= Duration.zero) {
|
|
4234
|
+
throw TimeoutException('Peer Office realm closure remains pending');
|
|
4235
|
+
}
|
|
4236
|
+
try {
|
|
4237
|
+
final response = await _invokeRuntimeV2(
|
|
4238
|
+
nomos_protocol.RuntimeRequest(
|
|
4239
|
+
closeRealm: nomos_protocol.CloseRealmRequest(),
|
|
4240
|
+
),
|
|
4241
|
+
).timeout(remaining);
|
|
4242
|
+
if (response.hasRealmClosed()) break;
|
|
4243
|
+
throw StateError(
|
|
4244
|
+
'Runtime v2 returned ${response.whichOutcome().name} for closeRealm');
|
|
4245
|
+
} catch (error) {
|
|
4246
|
+
final code = switch (error) {
|
|
4247
|
+
NomosRuntimeFailure(:final code) => code,
|
|
4248
|
+
NomosRuntimeError(:final info) => info.code,
|
|
4249
|
+
_ => null,
|
|
4250
|
+
};
|
|
4251
|
+
if (code != 'nomos.runtime.preparation_io_pending' &&
|
|
4252
|
+
code != 'nomos.runtime.custody_retention_pending') rethrow;
|
|
4253
|
+
}
|
|
4254
|
+
// The office still owns physical release or acknowledged custody. Keep
|
|
4255
|
+
// the event subscription alive while its existing host pump settles it;
|
|
4256
|
+
// a refused close is not permission to dispose that owner's directory.
|
|
4257
|
+
await Future<void>.delayed(const Duration(milliseconds: 10));
|
|
4236
4258
|
}
|
|
4237
4259
|
} catch (_) {
|
|
4238
4260
|
// A dead/unstarted embedding has no reachable runner to release. The
|
|
@@ -8694,6 +8694,18 @@ function gitCustodyDesk(kernel, fetchImpl = fetch, observe, phaseStarted, import
|
|
|
8694
8694
|
}
|
|
8695
8695
|
const publish = async (allowReconcile) => {
|
|
8696
8696
|
const oldHead = remote.get(reference) ?? ZERO_OID;
|
|
8697
|
+
const expectedRemote = sealedMain.get(key) ?? resolveRef(workspace, receiptRef(ledger)) ?? ZERO_OID;
|
|
8698
|
+
if (allowReconcile && expectedRemote !== ZERO_OID && oldHead !== expectedRemote) {
|
|
8699
|
+
remote = new Map(Object.entries(await lsRefs(
|
|
8700
|
+
ledger.remote,
|
|
8701
|
+
ledger.headers ?? {},
|
|
8702
|
+
{ service: "git-receive-pack", prefix: "refs/", signal: ledger.signal },
|
|
8703
|
+
fetchImpl
|
|
8704
|
+
)));
|
|
8705
|
+
observedRemoteRefs.set(key, remote);
|
|
8706
|
+
await publish(false);
|
|
8707
|
+
return;
|
|
8708
|
+
}
|
|
8697
8709
|
const bookmarkUpdates = [SYNC_BASE_REF, PLACEMENT_SYNC_BASE_REF].filter((ref) => named.has(ref) && remote.get(ref) !== named.get(ref)).map((ref) => ({ ref, oldOid: remote.get(ref) ?? ZERO_OID, newOid: named.get(ref) }));
|
|
8698
8710
|
const outcomeUpdates = cursors.filter(([ref]) => ref.startsWith(EFFECT_OUTCOME_PREFIX) && remote.get(ref) === void 0).map(([ref, oid]) => ({ ref, oldOid: ZERO_OID, newOid: oid }));
|
|
8699
8711
|
const deliveryUpdates = cursors.filter(([ref]) => ref.startsWith(DELIVERY_PREFIX) && remote.get(ref) === void 0).map(([ref, oid]) => ({ ref, oldOid: ZERO_OID, newOid: oid }));
|
|
@@ -8703,7 +8715,6 @@ function gitCustodyDesk(kernel, fetchImpl = fetch, observe, phaseStarted, import
|
|
|
8703
8715
|
if (ledger.readOnly) {
|
|
8704
8716
|
throw new Error(`read-only custody for '${workspace}' does not hold the requested frontier`);
|
|
8705
8717
|
}
|
|
8706
|
-
const expectedRemote = sealedMain.get(key) ?? resolveRef(workspace, receiptRef(ledger)) ?? ZERO_OID;
|
|
8707
8718
|
let ancestryPack;
|
|
8708
8719
|
if (oldHead !== expectedRemote && oldHead !== head) {
|
|
8709
8720
|
if (oldHead !== ZERO_OID) {
|
|
@@ -24990,7 +25001,7 @@ function delayUntilDue(follow, nowUnixMillis) {
|
|
|
24990
25001
|
|
|
24991
25002
|
// ../src/runtime/runtime_identity.ts
|
|
24992
25003
|
init_define_NOMOS_KERNEL_RELEASE();
|
|
24993
|
-
var PEER_RUNTIME_IDENTITY = "0.
|
|
25004
|
+
var PEER_RUNTIME_IDENTITY = "0.117.0" ? `@githolon/runtime@${"0.117.0"}` : "@githolon/runtime@unbundled";
|
|
24994
25005
|
|
|
24995
25006
|
// ../src/runtime/replica_follow_desk.ts
|
|
24996
25007
|
init_define_NOMOS_KERNEL_RELEASE();
|
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.70.
|
|
10
|
+
version: 0.70.3
|
|
11
11
|
|
|
12
12
|
environment:
|
|
13
13
|
sdk: ^3.5.0
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import 'dart:async';
|
|
2
|
+
import 'package:nomos_client/src/nms_generated_runtime.dart';
|
|
3
|
+
import 'package:nomos_types/nomos_kernel_protocol.dart' as p;
|
|
4
|
+
import 'package:test/test.dart';
|
|
5
|
+
|
|
6
|
+
final class _Office implements PeerOfficeConnection {
|
|
7
|
+
final events = StreamController<p.RuntimeEvent>.broadcast(sync: true);
|
|
8
|
+
final List<String> refusals;
|
|
9
|
+
int closes = 0;
|
|
10
|
+
int sequence = 0;
|
|
11
|
+
bool listenerRetained = true;
|
|
12
|
+
_Office(this.refusals);
|
|
13
|
+
@override
|
|
14
|
+
Stream<p.RuntimeEvent> get runtimeEvents => events.stream;
|
|
15
|
+
@override
|
|
16
|
+
int nextRuntimeRequestId() => ++sequence;
|
|
17
|
+
@override
|
|
18
|
+
Future<p.RuntimeResponse> invokeRuntime(p.RuntimeRequest request) async {
|
|
19
|
+
expect(request.hasCloseRealm(), isTrue);
|
|
20
|
+
listenerRetained &= events.hasListener;
|
|
21
|
+
final index = closes++;
|
|
22
|
+
return p.RuntimeResponse(
|
|
23
|
+
protocolVersion: 2,
|
|
24
|
+
requestId: request.requestId,
|
|
25
|
+
failure: index < refusals.length
|
|
26
|
+
? p.RuntimeFailure(code: refusals[index], message: 'owner pending')
|
|
27
|
+
: null,
|
|
28
|
+
realmClosed: index >= refusals.length ? p.RealmClosed() : null);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
void main() {
|
|
33
|
+
test(
|
|
34
|
+
'realm disposal retains events through physical release and custody settlement',
|
|
35
|
+
() async {
|
|
36
|
+
final office = _Office([
|
|
37
|
+
'nomos.runtime.preparation_io_pending',
|
|
38
|
+
'nomos.runtime.custody_retention_pending',
|
|
39
|
+
]);
|
|
40
|
+
final reception = DartReception(office);
|
|
41
|
+
await reception.dispose();
|
|
42
|
+
expect(office.closes, 3);
|
|
43
|
+
expect(office.listenerRetained, isTrue);
|
|
44
|
+
expect(office.events.hasListener, isFalse);
|
|
45
|
+
await office.events.close();
|
|
46
|
+
});
|
|
47
|
+
test('an unrelated refusal is not retried as preparation', () async {
|
|
48
|
+
final office = _Office(['nomos.runtime.not_authorized']);
|
|
49
|
+
final reception = DartReception(office);
|
|
50
|
+
await reception.dispose();
|
|
51
|
+
expect(office.closes, 1);
|
|
52
|
+
expect(office.events.hasListener, isFalse);
|
|
53
|
+
await office.events.close();
|
|
54
|
+
});
|
|
55
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@githolon/dsl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.117.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.",
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
"./workspace-type": "./src/workspace_type.ts",
|
|
33
33
|
"./workspace-routing": "./src/workspace_routing.ts",
|
|
34
34
|
"./workspace-sharding": "./src/workspace_sharding.ts",
|
|
35
|
+
"./reception-artifact": "./src/reception_artifact.ts",
|
|
36
|
+
"./reception-artifact-contract": "./src/reception_artifact_contract.ts",
|
|
35
37
|
"./build-package": "./src/build_package.ts",
|
|
36
38
|
"./codegen-dart": "./src/codegen_dart.ts",
|
|
37
39
|
"./codegen-dot": "./src/codegen_dot.ts",
|
|
@@ -77,7 +79,7 @@
|
|
|
77
79
|
"test": "vitest run"
|
|
78
80
|
},
|
|
79
81
|
"dependencies": {
|
|
80
|
-
"@githolon/runtime": "^0.
|
|
82
|
+
"@githolon/runtime": "^0.117.0",
|
|
81
83
|
"@bufbuild/protobuf": "^2.13.0",
|
|
82
84
|
"@bufbuild/protoc-gen-es": "^2.13.0",
|
|
83
85
|
"esbuild": "0.24.2",
|
package/src/codegen_ts.ts
CHANGED
|
@@ -1177,6 +1177,10 @@ export interface TsClientOptions {
|
|
|
1177
1177
|
/** Export the holon-level factories beside the application surface: the one TypeScript Reception the
|
|
1178
1178
|
* compiler emits carries both (a code-mode or unattended peer binds the factories to any NomosHolon). */
|
|
1179
1179
|
readonly exportHolonFactory?: boolean;
|
|
1180
|
+
/** Compiler-only single-domain binding, with the exact composed modules retained
|
|
1181
|
+
* as ownership evidence for cross-domain births. Emits no application opener
|
|
1182
|
+
* that follows children or references sibling factories. */
|
|
1183
|
+
readonly factoryOnly?: { readonly composition: readonly DomainModule[] };
|
|
1180
1184
|
/** Emit only the fixed framework operator call set (identity, lifecycle and birth). This is the
|
|
1181
1185
|
* small server/operator companion: exact generated USDA calls, no application/read runtime. */
|
|
1182
1186
|
readonly frameworkOperatorOnly?: boolean;
|
|
@@ -1467,6 +1471,8 @@ function generateTsClientBody(
|
|
|
1467
1471
|
opts: TsClientOptions,
|
|
1468
1472
|
brandSink: Set<string>,
|
|
1469
1473
|
): string {
|
|
1474
|
+
if (opts.factoryOnly && (modules.length !== 1 || !opts.factoryOnly.composition.includes(modules[0]!) || opts.exportHolonFactory !== true))
|
|
1475
|
+
throw new Error("single-factory codegen requires one exported facet from its exact composed modules");
|
|
1470
1476
|
if (opts.callUsda === undefined) {
|
|
1471
1477
|
throw new Error(
|
|
1472
1478
|
"TypeScript client codegen requires the composed law graph; application calls and results may not fall back to generic maps"
|
|
@@ -1604,9 +1610,10 @@ function generateTsClientBody(
|
|
|
1604
1610
|
};
|
|
1605
1611
|
});
|
|
1606
1612
|
const aggregateOwners = new Map<string, string[]>();
|
|
1607
|
-
for (const module of modules) {
|
|
1613
|
+
for (const module of opts.factoryOnly?.composition ?? modules) {
|
|
1608
1614
|
const domain = module.domain ?? module.name;
|
|
1609
1615
|
for (const aggregate of module.aggregates) {
|
|
1616
|
+
if (opts.factoryOnly && ((aggregate as { businessBirthInternal?: unknown }).businessBirthInternal === true || isFrameworkAggregate(aggregate))) continue;
|
|
1610
1617
|
aggregateOwners.set(aggregate.id, [
|
|
1611
1618
|
...(aggregateOwners.get(aggregate.id) ?? []),
|
|
1612
1619
|
domain,
|
|
@@ -6786,7 +6793,7 @@ function generateTsClientBody(
|
|
|
6786
6793
|
// THE ONE APPLICATION ENTRY. Domain factories remain implementation detail; this package-level opener
|
|
6787
6794
|
// owns identity, home binding, durable custody, continuous sync, compatibility and recovery. Multi-domain
|
|
6788
6795
|
// applications are flattened into one business vocabulary and collision-check rather than silently choosing.
|
|
6789
|
-
{
|
|
6796
|
+
if (opts.factoryOnly === undefined) {
|
|
6790
6797
|
const rootBirths = applicationBirths.filter((birth) =>
|
|
6791
6798
|
clientDomains.includes(birth.sourceDomain)
|
|
6792
6799
|
);
|
|
@@ -264,6 +264,8 @@ import {
|
|
|
264
264
|
} from "./compatibility_graph.js";
|
|
265
265
|
import { proofLegsIndex } from "./codegen_proof.js";
|
|
266
266
|
import { usdzPack } from "./usdz.js";
|
|
267
|
+
import { compilerReceptionOperations, bundleCompilerReceptionFactory, compressCompilerReceptionTemplate } from "./reception_template.js";
|
|
268
|
+
import { embedReceptionTemplate, appendReceptionDomainOpinion, RECEPTION_CLOSURE_TOKEN } from "./reception_artifact_contract.js";
|
|
267
269
|
import {
|
|
268
270
|
LAW_UNIT_MANIFEST,
|
|
269
271
|
assertLawUnitsCoverPackage,
|
|
@@ -2588,8 +2590,8 @@ const NOMOS_TYPES_DEP = "^0.15.0";
|
|
|
2588
2590
|
// 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
|
|
2589
2591
|
// DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
|
|
2590
2592
|
// REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
|
|
2591
|
-
const NOMOS_CLIENT_DEP = "^0.70.
|
|
2592
|
-
const NOMOS_FLUTTER_DEP = "^0.68.
|
|
2593
|
+
const NOMOS_CLIENT_DEP = "^0.70.3";
|
|
2594
|
+
const NOMOS_FLUTTER_DEP = "^0.68.5";
|
|
2593
2595
|
// DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in the Dart and Flutter
|
|
2594
2596
|
// package pubspecs — a release without this bump fails the suite, so tenants never
|
|
2595
2597
|
// land back in dependency_overrides (the exact co2 report this closes, third occurrence).
|
|
@@ -7861,6 +7863,81 @@ async function main(): Promise<void> {
|
|
|
7861
7863
|
});
|
|
7862
7864
|
}
|
|
7863
7865
|
|
|
7866
|
+
// RELEASE COHERENCE — typed OSGi Contract Namespace requirements derived from law shape.
|
|
7867
|
+
const kernelContracts = (() => {
|
|
7868
|
+
const probes: KernelContractProbeModule[] = domainModules.map((m) => ({
|
|
7869
|
+
...(m.queries ? { queries: m.queries } : {}),
|
|
7870
|
+
...(m.deriveds ? { deriveds: m.deriveds } : {}),
|
|
7871
|
+
...(m.surfacedResolvers
|
|
7872
|
+
? { surfacedResolvers: m.surfacedResolvers }
|
|
7873
|
+
: {}),
|
|
7874
|
+
...(m.aggregates ? { aggregates: m.aggregates } : {}),
|
|
7875
|
+
}));
|
|
7876
|
+
return requiredKernelContracts(probes);
|
|
7877
|
+
})();
|
|
7878
|
+
|
|
7879
|
+
// Compiler-private receiving-group machinery is deliberately not a public facet.
|
|
7880
|
+
for (const [name, graph] of typedLawGraphs) {
|
|
7881
|
+
if (graph.domain.entity.label !== APPLICATION_HOME_DOMAIN) continue;
|
|
7882
|
+
const paths = typedLawPathsByName.get(name)!;
|
|
7883
|
+
const domainPath = paths.get(graph.domain.entity.id)!;
|
|
7884
|
+
const safeDomain = name.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
7885
|
+
const index = v3DomainAssets.findIndex(asset => asset.logicalPath === `domains/${safeDomain}.usda`);
|
|
7886
|
+
const prior = v3DomainAssets[index]!;
|
|
7887
|
+
const text = appendReceptionDomainOpinion(prior.utf8, domainPath, 'custom token nomos:receptionVisibility = "private"');
|
|
7888
|
+
const sha256 = sha256HexUtf8(text);
|
|
7889
|
+
v3DomainAssets[index] = { ...prior, utf8: text, sha256 };
|
|
7890
|
+
typedDomainIdentityByName.set(name, sha256);
|
|
7891
|
+
}
|
|
7892
|
+
|
|
7893
|
+
// Complete compiler-produced Reception metadata belongs to the domain unit.
|
|
7894
|
+
// The realised closure is not known until the target kernel composes its installed
|
|
7895
|
+
// set; its token is bound only by the server generator, outside law identity.
|
|
7896
|
+
for (const module of presentationDomainModules) {
|
|
7897
|
+
const graph = typedLawGraphs.get(module.name);
|
|
7898
|
+
const paths = typedLawPathsByName.get(module.name);
|
|
7899
|
+
if (!graph || !paths) fail(`Reception metadata has no graph for '${module.name}'`);
|
|
7900
|
+
const domain = module.domain ?? module.name;
|
|
7901
|
+
const domainPath = paths.get(graph.domain.entity.id);
|
|
7902
|
+
if (!domainPath) fail(`Reception metadata has no stable domain path for '${domain}'`);
|
|
7903
|
+
const selectedGraphs = new Map([[module.name, graph]]);
|
|
7904
|
+
const selectedPaths = new Map([[module.name, paths]]);
|
|
7905
|
+
const schema = operationProtoSchema([module], selectedGraphs, selectedPaths);
|
|
7906
|
+
const scratch = path.join(outDir, ".reception-metadata", sha256HexUtf8(domain));
|
|
7907
|
+
mkdirSync(scratch, { recursive: true });
|
|
7908
|
+
const protoPath = path.join(scratch, schema.fileName);
|
|
7909
|
+
writeFileSync(protoPath, schema.source, "utf8");
|
|
7910
|
+
try {
|
|
7911
|
+
execFileSync("protoc", tsProtocArgs(scratch, protoPath, protocGenEs()), { stdio: "pipe" });
|
|
7912
|
+
const binding = readFileSync(path.join(scratch, "operation_payloads_pb.ts"), "utf8");
|
|
7913
|
+
const source = generateTsClient([module], {
|
|
7914
|
+
packageName: module.name, domainHash: RECEPTION_CLOSURE_TOKEN,
|
|
7915
|
+
interface: { [domain]: packageInterface[domain]! }, kernelContracts,
|
|
7916
|
+
homeDomain: domain, exportHolonFactory: true, callUsda: [{ graph, paths }],
|
|
7917
|
+
factoryOnly: { composition: presentationDomainModules },
|
|
7918
|
+
});
|
|
7919
|
+
const factorySource = await bundleCompilerReceptionFactory({ factory: tsClientFactoryName(domain), clientSource: source, operationBinding: binding });
|
|
7920
|
+
const template = {
|
|
7921
|
+
factorySource, factorySha256: sha256HexUtf8(factorySource),
|
|
7922
|
+
domain, factory: tsClientFactoryName(domain), clientSource: source,
|
|
7923
|
+
operations: compilerReceptionOperations(module, graph),
|
|
7924
|
+
operationBinding: binding, operationProto: schema.source,
|
|
7925
|
+
sourceSha256: sha256HexUtf8(source), bindingSha256: sha256HexUtf8(binding), protoSha256: sha256HexUtf8(schema.source),
|
|
7926
|
+
};
|
|
7927
|
+
const safeDomain = module.name.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
7928
|
+
const index = v3DomainAssets.findIndex(asset => asset.logicalPath === `domains/${safeDomain}.usda`);
|
|
7929
|
+
if (index < 0) fail(`Reception metadata cannot locate domain layer '${module.name}'`);
|
|
7930
|
+
const prior = v3DomainAssets[index]!;
|
|
7931
|
+
const text = embedReceptionTemplate(prior.utf8, domainPath, compressCompilerReceptionTemplate(template));
|
|
7932
|
+
const sha256 = sha256HexUtf8(text);
|
|
7933
|
+
v3DomainAssets[index] = { ...prior, utf8: text, sha256 };
|
|
7934
|
+
typedDomainIdentityByName.set(module.name, sha256);
|
|
7935
|
+
} finally {
|
|
7936
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
7937
|
+
}
|
|
7938
|
+
}
|
|
7939
|
+
rmSync(path.join(outDir, ".reception-metadata"), { recursive: true, force: true });
|
|
7940
|
+
|
|
7864
7941
|
stage("boot probe (started)");
|
|
7865
7942
|
// ── 2. Canonical typed USDA closure ──
|
|
7866
7943
|
// There is no flattened package and no JSON law intermediate. Sparse typed source opinions plus
|
|
@@ -8254,18 +8331,7 @@ async function main(): Promise<void> {
|
|
|
8254
8331
|
return "unknown";
|
|
8255
8332
|
}
|
|
8256
8333
|
})();
|
|
8257
|
-
|
|
8258
|
-
const kernelContracts = (() => {
|
|
8259
|
-
const probes: KernelContractProbeModule[] = domainModules.map((m) => ({
|
|
8260
|
-
...(m.queries ? { queries: m.queries } : {}),
|
|
8261
|
-
...(m.deriveds ? { deriveds: m.deriveds } : {}),
|
|
8262
|
-
...(m.surfacedResolvers
|
|
8263
|
-
? { surfacedResolvers: m.surfacedResolvers }
|
|
8264
|
-
: {}),
|
|
8265
|
-
...(m.aggregates ? { aggregates: m.aggregates } : {}),
|
|
8266
|
-
}));
|
|
8267
|
-
return requiredKernelContracts(probes);
|
|
8268
|
-
})();
|
|
8334
|
+
|
|
8269
8335
|
|
|
8270
8336
|
// ── 3b. NO OPENAPI SIDECAR. OpenAPI remains the sanctioned standard for describing this package's
|
|
8271
8337
|
// API — but it is SERVED, not stored: the kernel derives it from the installed typed USDA IR
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// @generated by protoc-gen-es v2.13.0 with parameter "target=js+dts,import_extension=js"
|
|
2
|
+
// @generated from file nomos/kernel/v1/openusd_cache.proto (package nomos.kernel.cache.v1, syntax proto3)
|
|
3
|
+
/* eslint-disable */
|
|
4
|
+
|
|
5
|
+
import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv2";
|
|
6
|
+
import type { Message } from "@bufbuild/protobuf";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Describes the file nomos/kernel/v1/openusd_cache.proto.
|
|
10
|
+
*/
|
|
11
|
+
export declare const file_nomos_kernel_v1_openusd_cache: GenFile;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Existing parsed OpenUSD law-product frame version 1, encoded by
|
|
15
|
+
* projection/src/law_cache.rs. This exposes its generic transport layout;
|
|
16
|
+
* it adds no kernel request, authority, business schema or execution path.
|
|
17
|
+
*
|
|
18
|
+
* @generated from message nomos.kernel.cache.v1.CachedOpenUsdLayer
|
|
19
|
+
*/
|
|
20
|
+
export declare type CachedOpenUsdLayer = Message<"nomos.kernel.cache.v1.CachedOpenUsdLayer"> & {
|
|
21
|
+
/**
|
|
22
|
+
* @generated from field: uint32 format_version = 1;
|
|
23
|
+
*/
|
|
24
|
+
formatVersion: number;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @generated from field: string package_identity = 2;
|
|
28
|
+
*/
|
|
29
|
+
packageIdentity: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @generated from field: repeated nomos.kernel.cache.v1.CachedPrim prims = 3;
|
|
33
|
+
*/
|
|
34
|
+
prims: CachedPrim[];
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Describes the message nomos.kernel.cache.v1.CachedOpenUsdLayer.
|
|
39
|
+
* Use `create(CachedOpenUsdLayerSchema)` to create a new message.
|
|
40
|
+
*/
|
|
41
|
+
export declare const CachedOpenUsdLayerSchema: GenMessage<CachedOpenUsdLayer>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @generated from message nomos.kernel.cache.v1.CachedPrim
|
|
45
|
+
*/
|
|
46
|
+
export declare type CachedPrim = Message<"nomos.kernel.cache.v1.CachedPrim"> & {
|
|
47
|
+
/**
|
|
48
|
+
* @generated from field: string path = 1;
|
|
49
|
+
*/
|
|
50
|
+
path: string;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @generated from field: optional string type_name = 2;
|
|
54
|
+
*/
|
|
55
|
+
typeName?: string | undefined;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @generated from field: repeated nomos.kernel.cache.v1.CachedProperty properties = 3;
|
|
59
|
+
*/
|
|
60
|
+
properties: CachedProperty[];
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Describes the message nomos.kernel.cache.v1.CachedPrim.
|
|
65
|
+
* Use `create(CachedPrimSchema)` to create a new message.
|
|
66
|
+
*/
|
|
67
|
+
export declare const CachedPrimSchema: GenMessage<CachedPrim>;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @generated from message nomos.kernel.cache.v1.CachedProperty
|
|
71
|
+
*/
|
|
72
|
+
export declare type CachedProperty = Message<"nomos.kernel.cache.v1.CachedProperty"> & {
|
|
73
|
+
/**
|
|
74
|
+
* @generated from field: string name = 1;
|
|
75
|
+
*/
|
|
76
|
+
name: string;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @generated from field: string declared_type = 2;
|
|
80
|
+
*/
|
|
81
|
+
declaredType: string;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @generated from field: nomos.kernel.cache.v1.CachedValue value = 3;
|
|
85
|
+
*/
|
|
86
|
+
value?: CachedValue | undefined;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Describes the message nomos.kernel.cache.v1.CachedProperty.
|
|
91
|
+
* Use `create(CachedPropertySchema)` to create a new message.
|
|
92
|
+
*/
|
|
93
|
+
export declare const CachedPropertySchema: GenMessage<CachedProperty>;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @generated from message nomos.kernel.cache.v1.CachedValue
|
|
97
|
+
*/
|
|
98
|
+
export declare type CachedValue = Message<"nomos.kernel.cache.v1.CachedValue"> & {
|
|
99
|
+
/**
|
|
100
|
+
* @generated from oneof nomos.kernel.cache.v1.CachedValue.kind
|
|
101
|
+
*/
|
|
102
|
+
kind: {
|
|
103
|
+
/**
|
|
104
|
+
* @generated from field: bool bool = 1;
|
|
105
|
+
*/
|
|
106
|
+
value: boolean;
|
|
107
|
+
case: "bool";
|
|
108
|
+
} | {
|
|
109
|
+
/**
|
|
110
|
+
* @generated from field: nomos.kernel.cache.v1.CachedBoolArray bool_array = 2;
|
|
111
|
+
*/
|
|
112
|
+
value: CachedBoolArray;
|
|
113
|
+
case: "boolArray";
|
|
114
|
+
} | {
|
|
115
|
+
/**
|
|
116
|
+
* @generated from field: sint64 int = 3;
|
|
117
|
+
*/
|
|
118
|
+
value: bigint;
|
|
119
|
+
case: "int";
|
|
120
|
+
} | {
|
|
121
|
+
/**
|
|
122
|
+
* @generated from field: fixed64 uint = 4;
|
|
123
|
+
*/
|
|
124
|
+
value: bigint;
|
|
125
|
+
case: "uint";
|
|
126
|
+
} | {
|
|
127
|
+
/**
|
|
128
|
+
* @generated from field: double double = 5;
|
|
129
|
+
*/
|
|
130
|
+
value: number;
|
|
131
|
+
case: "double";
|
|
132
|
+
} | {
|
|
133
|
+
/**
|
|
134
|
+
* @generated from field: string string = 6;
|
|
135
|
+
*/
|
|
136
|
+
value: string;
|
|
137
|
+
case: "string";
|
|
138
|
+
} | {
|
|
139
|
+
/**
|
|
140
|
+
* @generated from field: string token = 7;
|
|
141
|
+
*/
|
|
142
|
+
value: string;
|
|
143
|
+
case: "token";
|
|
144
|
+
} | {
|
|
145
|
+
/**
|
|
146
|
+
* @generated from field: nomos.kernel.cache.v1.CachedStringArray string_array = 8;
|
|
147
|
+
*/
|
|
148
|
+
value: CachedStringArray;
|
|
149
|
+
case: "stringArray";
|
|
150
|
+
} | {
|
|
151
|
+
/**
|
|
152
|
+
* @generated from field: nomos.kernel.cache.v1.CachedTokenArray token_array = 9;
|
|
153
|
+
*/
|
|
154
|
+
value: CachedTokenArray;
|
|
155
|
+
case: "tokenArray";
|
|
156
|
+
} | {
|
|
157
|
+
/**
|
|
158
|
+
* @generated from field: string asset = 10;
|
|
159
|
+
*/
|
|
160
|
+
value: string;
|
|
161
|
+
case: "asset";
|
|
162
|
+
} | {
|
|
163
|
+
/**
|
|
164
|
+
* @generated from field: nomos.kernel.cache.v1.CachedRelationship relationship = 11;
|
|
165
|
+
*/
|
|
166
|
+
value: CachedRelationship;
|
|
167
|
+
case: "relationship";
|
|
168
|
+
} | { case: undefined; value?: undefined };
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Describes the message nomos.kernel.cache.v1.CachedValue.
|
|
173
|
+
* Use `create(CachedValueSchema)` to create a new message.
|
|
174
|
+
*/
|
|
175
|
+
export declare const CachedValueSchema: GenMessage<CachedValue>;
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @generated from message nomos.kernel.cache.v1.CachedBoolArray
|
|
179
|
+
*/
|
|
180
|
+
export declare type CachedBoolArray = Message<"nomos.kernel.cache.v1.CachedBoolArray"> & {
|
|
181
|
+
/**
|
|
182
|
+
* @generated from field: repeated bool values = 1 [packed = true];
|
|
183
|
+
*/
|
|
184
|
+
values: boolean[];
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Describes the message nomos.kernel.cache.v1.CachedBoolArray.
|
|
189
|
+
* Use `create(CachedBoolArraySchema)` to create a new message.
|
|
190
|
+
*/
|
|
191
|
+
export declare const CachedBoolArraySchema: GenMessage<CachedBoolArray>;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* @generated from message nomos.kernel.cache.v1.CachedStringArray
|
|
195
|
+
*/
|
|
196
|
+
export declare type CachedStringArray = Message<"nomos.kernel.cache.v1.CachedStringArray"> & {
|
|
197
|
+
/**
|
|
198
|
+
* @generated from field: repeated string values = 1;
|
|
199
|
+
*/
|
|
200
|
+
values: string[];
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Describes the message nomos.kernel.cache.v1.CachedStringArray.
|
|
205
|
+
* Use `create(CachedStringArraySchema)` to create a new message.
|
|
206
|
+
*/
|
|
207
|
+
export declare const CachedStringArraySchema: GenMessage<CachedStringArray>;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* @generated from message nomos.kernel.cache.v1.CachedTokenArray
|
|
211
|
+
*/
|
|
212
|
+
export declare type CachedTokenArray = Message<"nomos.kernel.cache.v1.CachedTokenArray"> & {
|
|
213
|
+
/**
|
|
214
|
+
* @generated from field: repeated string values = 1;
|
|
215
|
+
*/
|
|
216
|
+
values: string[];
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Describes the message nomos.kernel.cache.v1.CachedTokenArray.
|
|
221
|
+
* Use `create(CachedTokenArraySchema)` to create a new message.
|
|
222
|
+
*/
|
|
223
|
+
export declare const CachedTokenArraySchema: GenMessage<CachedTokenArray>;
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* @generated from message nomos.kernel.cache.v1.CachedRelationship
|
|
227
|
+
*/
|
|
228
|
+
export declare type CachedRelationship = Message<"nomos.kernel.cache.v1.CachedRelationship"> & {
|
|
229
|
+
/**
|
|
230
|
+
* @generated from field: repeated string targets = 1;
|
|
231
|
+
*/
|
|
232
|
+
targets: string[];
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Describes the message nomos.kernel.cache.v1.CachedRelationship.
|
|
237
|
+
* Use `create(CachedRelationshipSchema)` to create a new message.
|
|
238
|
+
*/
|
|
239
|
+
export declare const CachedRelationshipSchema: GenMessage<CachedRelationship>;
|
|
240
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// @generated by protoc-gen-es v2.13.0 with parameter "target=js+dts,import_extension=js"
|
|
2
|
+
// @generated from file nomos/kernel/v1/openusd_cache.proto (package nomos.kernel.cache.v1, syntax proto3)
|
|
3
|
+
/* eslint-disable */
|
|
4
|
+
|
|
5
|
+
import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv2";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Describes the file nomos/kernel/v1/openusd_cache.proto.
|
|
9
|
+
*/
|
|
10
|
+
export const file_nomos_kernel_v1_openusd_cache = /*@__PURE__*/
|
|
11
|
+
fileDesc("CiNub21vcy9rZXJuZWwvdjEvb3BlbnVzZF9jYWNoZS5wcm90bxIVbm9tb3Mua2VybmVsLmNhY2hlLnYxIngKEkNhY2hlZE9wZW5Vc2RMYXllchIWCg5mb3JtYXRfdmVyc2lvbhgBIAEoDRIYChBwYWNrYWdlX2lkZW50aXR5GAIgASgJEjAKBXByaW1zGAMgAygLMiEubm9tb3Mua2VybmVsLmNhY2hlLnYxLkNhY2hlZFByaW0iewoKQ2FjaGVkUHJpbRIMCgRwYXRoGAEgASgJEhYKCXR5cGVfbmFtZRgCIAEoCUgAiAEBEjkKCnByb3BlcnRpZXMYAyADKAsyJS5ub21vcy5rZXJuZWwuY2FjaGUudjEuQ2FjaGVkUHJvcGVydHlCDAoKX3R5cGVfbmFtZSJoCg5DYWNoZWRQcm9wZXJ0eRIMCgRuYW1lGAEgASgJEhUKDWRlY2xhcmVkX3R5cGUYAiABKAkSMQoFdmFsdWUYAyABKAsyIi5ub21vcy5rZXJuZWwuY2FjaGUudjEuQ2FjaGVkVmFsdWUijQMKC0NhY2hlZFZhbHVlEg4KBGJvb2wYASABKAhIABI8Cgpib29sX2FycmF5GAIgASgLMiYubm9tb3Mua2VybmVsLmNhY2hlLnYxLkNhY2hlZEJvb2xBcnJheUgAEg0KA2ludBgDIAEoEkgAEg4KBHVpbnQYBCABKAZIABIQCgZkb3VibGUYBSABKAFIABIQCgZzdHJpbmcYBiABKAlIABIPCgV0b2tlbhgHIAEoCUgAEkAKDHN0cmluZ19hcnJheRgIIAEoCzIoLm5vbW9zLmtlcm5lbC5jYWNoZS52MS5DYWNoZWRTdHJpbmdBcnJheUgAEj4KC3Rva2VuX2FycmF5GAkgASgLMicubm9tb3Mua2VybmVsLmNhY2hlLnYxLkNhY2hlZFRva2VuQXJyYXlIABIPCgVhc3NldBgKIAEoCUgAEkEKDHJlbGF0aW9uc2hpcBgLIAEoCzIpLm5vbW9zLmtlcm5lbC5jYWNoZS52MS5DYWNoZWRSZWxhdGlvbnNoaXBIAEIGCgRraW5kIiUKD0NhY2hlZEJvb2xBcnJheRISCgZ2YWx1ZXMYASADKAhCAhABIiMKEUNhY2hlZFN0cmluZ0FycmF5Eg4KBnZhbHVlcxgBIAMoCSIiChBDYWNoZWRUb2tlbkFycmF5Eg4KBnZhbHVlcxgBIAMoCSIlChJDYWNoZWRSZWxhdGlvbnNoaXASDwoHdGFyZ2V0cxgBIAMoCWIGcHJvdG8z");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Describes the message nomos.kernel.cache.v1.CachedOpenUsdLayer.
|
|
15
|
+
* Use `create(CachedOpenUsdLayerSchema)` to create a new message.
|
|
16
|
+
*/
|
|
17
|
+
export const CachedOpenUsdLayerSchema = /*@__PURE__*/
|
|
18
|
+
messageDesc(file_nomos_kernel_v1_openusd_cache, 0);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Describes the message nomos.kernel.cache.v1.CachedPrim.
|
|
22
|
+
* Use `create(CachedPrimSchema)` to create a new message.
|
|
23
|
+
*/
|
|
24
|
+
export const CachedPrimSchema = /*@__PURE__*/
|
|
25
|
+
messageDesc(file_nomos_kernel_v1_openusd_cache, 1);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Describes the message nomos.kernel.cache.v1.CachedProperty.
|
|
29
|
+
* Use `create(CachedPropertySchema)` to create a new message.
|
|
30
|
+
*/
|
|
31
|
+
export const CachedPropertySchema = /*@__PURE__*/
|
|
32
|
+
messageDesc(file_nomos_kernel_v1_openusd_cache, 2);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Describes the message nomos.kernel.cache.v1.CachedValue.
|
|
36
|
+
* Use `create(CachedValueSchema)` to create a new message.
|
|
37
|
+
*/
|
|
38
|
+
export const CachedValueSchema = /*@__PURE__*/
|
|
39
|
+
messageDesc(file_nomos_kernel_v1_openusd_cache, 3);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Describes the message nomos.kernel.cache.v1.CachedBoolArray.
|
|
43
|
+
* Use `create(CachedBoolArraySchema)` to create a new message.
|
|
44
|
+
*/
|
|
45
|
+
export const CachedBoolArraySchema = /*@__PURE__*/
|
|
46
|
+
messageDesc(file_nomos_kernel_v1_openusd_cache, 4);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Describes the message nomos.kernel.cache.v1.CachedStringArray.
|
|
50
|
+
* Use `create(CachedStringArraySchema)` to create a new message.
|
|
51
|
+
*/
|
|
52
|
+
export const CachedStringArraySchema = /*@__PURE__*/
|
|
53
|
+
messageDesc(file_nomos_kernel_v1_openusd_cache, 5);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Describes the message nomos.kernel.cache.v1.CachedTokenArray.
|
|
57
|
+
* Use `create(CachedTokenArraySchema)` to create a new message.
|
|
58
|
+
*/
|
|
59
|
+
export const CachedTokenArraySchema = /*@__PURE__*/
|
|
60
|
+
messageDesc(file_nomos_kernel_v1_openusd_cache, 6);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Describes the message nomos.kernel.cache.v1.CachedRelationship.
|
|
64
|
+
* Use `create(CachedRelationshipSchema)` to create a new message.
|
|
65
|
+
*/
|
|
66
|
+
export const CachedRelationshipSchema = /*@__PURE__*/
|
|
67
|
+
messageDesc(file_nomos_kernel_v1_openusd_cache, 7);
|
|
68
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** Pure hosted generation from authenticated compiled USD metadata. The compiler
|
|
2
|
+
* already bundled each facet. This assembler neither transforms TypeScript nor
|
|
3
|
+
* executes tenant code, business plans, network access or a kernel. */
|
|
4
|
+
import {
|
|
5
|
+
artifactIdentity, receptionSha256, validateReceptionGenerationInput, verifyReceptionArtifact,
|
|
6
|
+
RECEPTION_CLOSURE_TOKEN,
|
|
7
|
+
type ClosureReceptionArtifact, type ReceptionGenerationInput,
|
|
8
|
+
} from "./reception_artifact_contract.js";
|
|
9
|
+
export * from "./reception_artifact_contract.js";
|
|
10
|
+
|
|
11
|
+
export async function generateReceptionArtifact(input: ReceptionGenerationInput): Promise<ClosureReceptionArtifact> {
|
|
12
|
+
const templates = await validateReceptionGenerationInput(input);
|
|
13
|
+
const artifactId = await artifactIdentity(input.closure, input.generator, input.runtimeContract);
|
|
14
|
+
const inventory = templates.map(({ domain, factory, factorySha256, operations }) => ({ domain, factory, sourceSha256: factorySha256, operations }));
|
|
15
|
+
const entry = [
|
|
16
|
+
'import { WorkerEntrypoint } from "cloudflare:workers";',
|
|
17
|
+
...templates.map((template, index) => `const factory${index} = (() => { ${template.factorySource.replaceAll(JSON.stringify(RECEPTION_CLOSURE_TOKEN), JSON.stringify(input.closure))} return __NOMOS_RECEPTION_FACET__.${template.factory}; })();`),
|
|
18
|
+
`const domains = Object.freeze(Object.assign(Object.create(null), { ${templates.map((template, index) => `${JSON.stringify(template.domain)}: factory${index}`).join(", ")} }));`,
|
|
19
|
+
`const description = ${JSON.stringify({ artifactId, closure: input.closure, generator: input.generator, runtimeContract: input.runtimeContract, domains: inventory })};`,
|
|
20
|
+
`function serialiseResult(value, depth = 0, seen = new Set()) {`,
|
|
21
|
+
` const unavailable = () => { const error = new Error("Generated result cannot cross the hosted JSON contract"); error.code = "nomos.reception.result.unavailable"; throw error; };`,
|
|
22
|
+
` if (depth > 64) return unavailable();`,
|
|
23
|
+
` if (value === undefined) return { $nomos: "absent" };`,
|
|
24
|
+
` if (value === null || typeof value === "string" || typeof value === "boolean") return value;`,
|
|
25
|
+
` if (typeof value === "number") { if (!Number.isFinite(value)) return unavailable(); return value; }`,
|
|
26
|
+
` if (typeof value === "bigint") return { $nomos: "integer", decimal: value.toString() };`,
|
|
27
|
+
` if (value instanceof Uint8Array) { let binary = ""; for (const byte of value) binary += String.fromCharCode(byte); return { $nomos: "bytes", base64: btoa(binary) }; }`,
|
|
28
|
+
` if (value instanceof Date) { if (!Number.isFinite(value.getTime())) return unavailable(); return { $nomos: "timestamp", iso: value.toISOString() }; }`,
|
|
29
|
+
` if (typeof value !== "object" || seen.has(value)) return unavailable();`,
|
|
30
|
+
` seen.add(value); try {`,
|
|
31
|
+
` if (Array.isArray(value)) return value.map(item => serialiseResult(item, depth + 1, seen));`,
|
|
32
|
+
` const prototype = Object.getPrototypeOf(value); if (prototype !== Object.prototype && prototype !== null) return unavailable();`,
|
|
33
|
+
` const output = {}; for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(value))) { if (descriptor.get || descriptor.set) return unavailable(); Object.defineProperty(output, key, { value: serialiseResult(descriptor.value, depth + 1, seen), enumerable: true }); } return output;`,
|
|
34
|
+
` } finally { seen.delete(value); }`,
|
|
35
|
+
`}`,
|
|
36
|
+
`export default class Reception extends WorkerEntrypoint {`,
|
|
37
|
+
` describe() { return description; }`,
|
|
38
|
+
` async invoke(call, office) {`,
|
|
39
|
+
` if (!call || typeof call.domain !== "string" || typeof call.member !== "string" || !Array.isArray(call.args)) throw new Error("invalid Reception invocation");`,
|
|
40
|
+
` if (call.member === "constructor" || call.member.startsWith("__") || call.member.startsWith("watch")) throw new Error("unsupported hosted Reception member");`,
|
|
41
|
+
` if (!Object.hasOwn(domains, call.domain)) throw new Error("Reception domain is absent from this closure");`,
|
|
42
|
+
` if (typeof call.workspace !== "string" || !call.workspace || typeof call.principal !== "string" || !call.principal) throw new Error("Reception requires request scoped workspace and principal");`,
|
|
43
|
+
` const scoped = { workspace: call.workspace, principal: call.principal, offerCall: (...args) => office.offerCall(...args),`,
|
|
44
|
+
` __nomosQuery: (...args) => office.__nomosQuery(...args), __nomosQueryUsda: (...args) => office.__nomosQueryUsda(...args),`,
|
|
45
|
+
` __nomosQueryByIdUsda: (...args) => office.__nomosQueryByIdUsda(...args), __nomosQueryByTypeUsda: (...args) => office.__nomosQueryByTypeUsda(...args),`,
|
|
46
|
+
` __nomosQueryPage: (...args) => office.__nomosQueryPage(...args), __nomosQueryPageUsda: (...args) => office.__nomosQueryPageUsda(...args) };`,
|
|
47
|
+
` const selected = description.domains.find(domain => domain.domain === call.domain)?.operations.find(operation => operation.member === call.member);`,
|
|
48
|
+
` if (!selected) throw new Error("Reception member is not declared for stateless hosting");`,
|
|
49
|
+
` const client = domains[call.domain](scoped);`,
|
|
50
|
+
` const path = call.member.split("."); let parent = client;`,
|
|
51
|
+
` for (const part of path.slice(0, -1)) { if (!part || part.startsWith("__") || part === "constructor" || !Object.hasOwn(parent, part)) throw new Error("Reception business member is absent from this closure"); parent = parent[part]; if (!parent || typeof parent !== "object") throw new Error("Reception business member is absent from this closure"); }`,
|
|
52
|
+
` const member = path.at(-1); if (!member || member.startsWith("watch") || member.startsWith("__") || member === "constructor" || !Object.hasOwn(parent, member) || typeof parent[member] !== "function") throw new Error("Reception business member is absent from this closure");`,
|
|
53
|
+
` const args = [...call.args];`,
|
|
54
|
+
` if (selected.kind === "byId" && typeof args[0] === "string" && typeof parent.fromAddress === "function") args[0] = parent.fromAddress(args[0]);`,
|
|
55
|
+
` return serialiseResult(await parent[member](...args));`,
|
|
56
|
+
` }`,
|
|
57
|
+
`}`,
|
|
58
|
+
].join("\n");
|
|
59
|
+
const moduleSource = entry + "\n";
|
|
60
|
+
const artifact: ClosureReceptionArtifact = { manifest: {
|
|
61
|
+
format: "nomos.reception-artifact.v1", artifactId, closure: input.closure, generator: input.generator,
|
|
62
|
+
runtimeContract: input.runtimeContract, entrypoint: "reception.mjs", moduleSha256: await receptionSha256(moduleSource), domains: inventory,
|
|
63
|
+
}, moduleSource };
|
|
64
|
+
await verifyReceptionArtifact(artifact, input);
|
|
65
|
+
return artifact;
|
|
66
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/** Derived Reception identity is independent of admitted law and request authority. */
|
|
2
|
+
import { usdaString, usdaUnquote } from "./usda_text.js";
|
|
3
|
+
|
|
4
|
+
export const RECEPTION_GENERATOR = "nomos-typescript-reception-v1";
|
|
5
|
+
export const RECEPTION_RUNTIME_CONTRACT = "nomos-peer-office-reception-v1";
|
|
6
|
+
export const RECEPTION_TEMPLATE_FORMAT = "nomos.reception-template.v1";
|
|
7
|
+
export const RECEPTION_COMPRESSED_TEMPLATE_FORMAT = "nomos.reception-template.v2";
|
|
8
|
+
export const RECEPTION_SOURCE_BYTES_LIMIT = 16 * 1024 * 1024;
|
|
9
|
+
export const RECEPTION_CLOSURE_TOKEN = "__NOMOS_REALISED_CLOSURE_V1__";
|
|
10
|
+
export interface ReceptionGenerationInput {
|
|
11
|
+
readonly closure: string;
|
|
12
|
+
readonly generator: string;
|
|
13
|
+
readonly runtimeContract: string;
|
|
14
|
+
readonly semanticLayer: string | Uint8Array;
|
|
15
|
+
readonly domains?: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
export interface ReceptionOperation {
|
|
18
|
+
readonly member: string;
|
|
19
|
+
readonly kind: "offer" | "query" | "byType" | "byId";
|
|
20
|
+
readonly selector: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ReceptionTemplate {
|
|
23
|
+
/** Compiler-only deterministic envelope; decoded sources keep their exact digests. */
|
|
24
|
+
readonly sourceEnvelope?: { readonly gzipBase64: string; readonly decodedBytes: number };
|
|
25
|
+
readonly factorySource: string;
|
|
26
|
+
readonly factorySha256: string;
|
|
27
|
+
readonly operations: readonly ReceptionOperation[];
|
|
28
|
+
readonly domain: string;
|
|
29
|
+
readonly factory: string;
|
|
30
|
+
readonly clientSource: string;
|
|
31
|
+
readonly operationBinding: string;
|
|
32
|
+
readonly operationProto: string;
|
|
33
|
+
readonly sourceSha256: string;
|
|
34
|
+
readonly bindingSha256: string;
|
|
35
|
+
readonly protoSha256: string;
|
|
36
|
+
}
|
|
37
|
+
export interface ReceptionArtifactManifest {
|
|
38
|
+
readonly format: "nomos.reception-artifact.v1";
|
|
39
|
+
readonly artifactId: string;
|
|
40
|
+
readonly closure: string;
|
|
41
|
+
readonly generator: string;
|
|
42
|
+
readonly runtimeContract: string;
|
|
43
|
+
readonly entrypoint: "reception.mjs";
|
|
44
|
+
readonly moduleSha256: string;
|
|
45
|
+
readonly domains: readonly { readonly domain: string; readonly factory: string; readonly sourceSha256: string; readonly operations: readonly ReceptionOperation[] }[];
|
|
46
|
+
}
|
|
47
|
+
export interface ClosureReceptionArtifact {
|
|
48
|
+
readonly manifest: ReceptionArtifactManifest;
|
|
49
|
+
readonly moduleSource: string;
|
|
50
|
+
}
|
|
51
|
+
const HEX = /^[0-9a-f]{64}$/;
|
|
52
|
+
export async function receptionSha256(value: string): Promise<string> {
|
|
53
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
|
|
54
|
+
return Array.from(new Uint8Array(digest), x => x.toString(16).padStart(2, "0")).join("");
|
|
55
|
+
}
|
|
56
|
+
export async function artifactIdentity(closure: string, generator: string, runtimeContract: string): Promise<string> {
|
|
57
|
+
if (!HEX.test(closure) || generator !== RECEPTION_GENERATOR || runtimeContract !== RECEPTION_RUNTIME_CONTRACT)
|
|
58
|
+
throw new Error("Reception generation requires a realised closure and supported generator/runtime contract");
|
|
59
|
+
return receptionSha256(JSON.stringify(["nomos.reception-artifact.v1", closure, generator, runtimeContract]));
|
|
60
|
+
}
|
|
61
|
+
const base64 = (value: string): string => {
|
|
62
|
+
const bytes = new TextEncoder().encode(value);
|
|
63
|
+
let binary = "";
|
|
64
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
65
|
+
return btoa(binary);
|
|
66
|
+
};
|
|
67
|
+
const unbase64Bytes = (value: string): Uint8Array<ArrayBuffer> => {
|
|
68
|
+
const binary = atob(value);
|
|
69
|
+
if (btoa(binary) !== value) throw new Error("Reception metadata has noncanonical base64");
|
|
70
|
+
return Uint8Array.from(binary, c => c.charCodeAt(0));
|
|
71
|
+
};
|
|
72
|
+
const unbase64 = (value: string): string => new TextDecoder("utf-8", { fatal: true, ignoreBOM: false }).decode(unbase64Bytes(value));
|
|
73
|
+
async function receptionSources(gzipBase64: string, declaredBytes: string): Promise<readonly [string, string, string, string]> {
|
|
74
|
+
const length = Number(declaredBytes);
|
|
75
|
+
if (!/^[1-9][0-9]*$/.test(declaredBytes) || !Number.isSafeInteger(length) || length > RECEPTION_SOURCE_BYTES_LIMIT)
|
|
76
|
+
throw new Error("Reception metadata has an invalid decoded source length");
|
|
77
|
+
const reader = new Blob([unbase64Bytes(gzipBase64)]).stream().pipeThrough(new DecompressionStream("gzip")).getReader();
|
|
78
|
+
const bytes = new Uint8Array(length);
|
|
79
|
+
let offset = 0;
|
|
80
|
+
try {
|
|
81
|
+
while (true) {
|
|
82
|
+
const chunk = await reader.read();
|
|
83
|
+
if (chunk.done) break;
|
|
84
|
+
if (chunk.value.byteLength > length - offset) throw new Error("Reception metadata exceeds its decoded source length");
|
|
85
|
+
bytes.set(chunk.value, offset); offset += chunk.value.byteLength;
|
|
86
|
+
}
|
|
87
|
+
} finally { await reader.cancel().catch(() => {}); }
|
|
88
|
+
if (offset !== length) throw new Error("Reception metadata has an incomplete decoded source envelope");
|
|
89
|
+
const sources: unknown = JSON.parse(new TextDecoder("utf-8", { fatal: true, ignoreBOM: false }).decode(bytes));
|
|
90
|
+
if (!Array.isArray(sources) || sources.length !== 4 || sources.some(value => typeof value !== "string" || !value))
|
|
91
|
+
throw new Error("Reception metadata has an incomplete source envelope");
|
|
92
|
+
return sources as [string, string, string, string];
|
|
93
|
+
}
|
|
94
|
+
/** Compiler metadata is composed under the domain's stable USD path. Source is
|
|
95
|
+
* quoted as base64 so OpenUSD flattening cannot change escaping or whitespace. */
|
|
96
|
+
export function receptionTemplateUsda(domainPath: string, template: ReceptionTemplate): string {
|
|
97
|
+
if (!/^\/Nomos\/Domains\/d_[0-9a-f]{16}$/.test(domainPath)) throw new Error("invalid Reception domain path");
|
|
98
|
+
const fields = {
|
|
99
|
+
format: template.sourceEnvelope ? RECEPTION_COMPRESSED_TEMPLATE_FORMAT : RECEPTION_TEMPLATE_FORMAT,
|
|
100
|
+
domain: template.domain, factory: template.factory,
|
|
101
|
+
...(template.sourceEnvelope ? {
|
|
102
|
+
sourcesGzipBase64: template.sourceEnvelope.gzipBase64, sourcesDecodedBytes: String(template.sourceEnvelope.decodedBytes),
|
|
103
|
+
} : {
|
|
104
|
+
factorySourceBase64: base64(template.factorySource), clientSourceBase64: base64(template.clientSource),
|
|
105
|
+
operationBindingBase64: base64(template.operationBinding), operationProtoBase64: base64(template.operationProto),
|
|
106
|
+
}),
|
|
107
|
+
factorySha256: template.factorySha256, sourceSha256: template.sourceSha256,
|
|
108
|
+
bindingSha256: template.bindingSha256, protoSha256: template.protoSha256,
|
|
109
|
+
};
|
|
110
|
+
return ['over "Nomos"', '{', ' over "Domains"', ' {', ` over "${domainPath.split("/").at(-1)}"`, ' {',
|
|
111
|
+
' def NomosReceptionTemplate "Reception"', ' {',
|
|
112
|
+
...Object.entries(fields).map(([key, value]) => ` custom string nomos:${key} = ${usdaString(value)}`),
|
|
113
|
+
` custom string[] nomos:members = [${template.operations.map(x => usdaString(x.member)).join(", ")}]`,
|
|
114
|
+
` custom token[] nomos:operationKinds = [${template.operations.map(x => usdaString(x.kind)).join(", ")}]`,
|
|
115
|
+
` custom string[] nomos:selectors = [${template.operations.map(x => usdaString(x.selector)).join(", ")}]`,
|
|
116
|
+
' }', ' }', ' }', '}', ''].join("\n");
|
|
117
|
+
}
|
|
118
|
+
/** Add an opinion inside the existing prim. Repeating a top-level over in one
|
|
119
|
+
* layer replaces its earlier spec in OpenUSD; metadata must never erase law. */
|
|
120
|
+
export function appendReceptionDomainOpinion(layer: string, domainPath: string, body: string): string {
|
|
121
|
+
const frames: string[] = [];
|
|
122
|
+
let pending: string | undefined;
|
|
123
|
+
let inserted = false;
|
|
124
|
+
const output: string[] = [];
|
|
125
|
+
for (const raw of layer.split("\n")) {
|
|
126
|
+
const line = raw.trim();
|
|
127
|
+
const prim = /^(?:def|over|class)\s+(?:\w+\s+)?"([^"\\]+)"(?:\s*\()?\s*$/.exec(line);
|
|
128
|
+
if (prim) pending = prim[1]!;
|
|
129
|
+
if (line.endsWith("= {")) frames.push("#dictionary");
|
|
130
|
+
else if (line === "{") { frames.push(pending ?? "#dictionary"); pending = undefined; }
|
|
131
|
+
else if (line === "}") {
|
|
132
|
+
if (`/${frames.join("/")}` === domainPath) {
|
|
133
|
+
if (inserted) throw new Error("duplicate Reception domain prim");
|
|
134
|
+
const indentation = raw.slice(0, raw.indexOf("}")) + " ";
|
|
135
|
+
output.push(...body.trimEnd().split("\n").map(value => indentation + value));
|
|
136
|
+
inserted = true;
|
|
137
|
+
}
|
|
138
|
+
frames.pop();
|
|
139
|
+
}
|
|
140
|
+
output.push(raw);
|
|
141
|
+
}
|
|
142
|
+
if (!inserted) throw new Error(`Reception metadata cannot find domain prim ${domainPath}`);
|
|
143
|
+
return output.join("\n");
|
|
144
|
+
}
|
|
145
|
+
export function embedReceptionTemplate(layer: string, domainPath: string, template: ReceptionTemplate): string {
|
|
146
|
+
const fragment = receptionTemplateUsda(domainPath, template).split("\n").slice(6, -4).map(line => line.slice(12)).join("\n");
|
|
147
|
+
return appendReceptionDomainOpinion(layer, domainPath, fragment);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Parses only compiler metadata; never evaluates any tenant code. Every domain
|
|
151
|
+
* selected by the exact semantic product must carry one complete template. */
|
|
152
|
+
export async function validateReceptionGenerationInput(input: ReceptionGenerationInput): Promise<readonly ReceptionTemplate[]> {
|
|
153
|
+
await artifactIdentity(input.closure, input.generator, input.runtimeContract);
|
|
154
|
+
const templates: ReceptionTemplate[] = [];
|
|
155
|
+
const lawDomains = new Map<string, { label?: string; visibility?: string }>();
|
|
156
|
+
const frames: string[] = [];
|
|
157
|
+
let pending: { name: string; type: string } | undefined;
|
|
158
|
+
let fields: Record<string, string> | undefined;
|
|
159
|
+
let templatePath: string | undefined;
|
|
160
|
+
let arrays: Record<string, string[]> = {};
|
|
161
|
+
let dictionaryDepth = 0;
|
|
162
|
+
// Compiler startup precedes protocol generation during a clean Prepare install.
|
|
163
|
+
// Only consumers of the realised binary product need the generated cache decoder.
|
|
164
|
+
const semanticLayer = typeof input.semanticLayer === "string" ? input.semanticLayer
|
|
165
|
+
: (await import("./reception_cache.js")).receptionMetadataFromLawProduct(input.semanticLayer, input.closure);
|
|
166
|
+
// Preflight the entire closure before allocating any decompressed source. A
|
|
167
|
+
// small compressed input must not allow many individually bounded envelopes
|
|
168
|
+
// to exhaust the generator isolate. Legacy base64 is bounded by its encoded
|
|
169
|
+
// size as well, before it is decoded.
|
|
170
|
+
let sourceBytes = 0;
|
|
171
|
+
for (const match of semanticLayer.matchAll(/^\s*(?:custom\s+)?(?:uniform\s+)?string\s+nomos:(sourcesDecodedBytes|factorySourceBase64|clientSourceBase64|operationBindingBase64|operationProtoBase64)\s*=\s*("(?:[^"\\]|\\.)*")\s*$/gm)) {
|
|
172
|
+
const value = usdaUnquote(match[2]!);
|
|
173
|
+
const bytes = match[1] === "sourcesDecodedBytes" ? Number(value) : Math.ceil(value.length * 3 / 4);
|
|
174
|
+
if ((match[1] === "sourcesDecodedBytes" && !/^[1-9][0-9]*$/.test(value)) || !Number.isSafeInteger(bytes) || bytes > RECEPTION_SOURCE_BYTES_LIMIT - sourceBytes)
|
|
175
|
+
throw new Error("Reception metadata exceeds the closure decoded source budget");
|
|
176
|
+
sourceBytes += bytes;
|
|
177
|
+
}
|
|
178
|
+
for (const raw of semanticLayer.split("\n")) {
|
|
179
|
+
const line = raw.trim();
|
|
180
|
+
if (line.endsWith("= {")) { dictionaryDepth += 1; continue; }
|
|
181
|
+
if (dictionaryDepth > 0) { if (line === "}") dictionaryDepth -= 1; continue; }
|
|
182
|
+
const prim = /^(?:def|over)\s+(?:(\w+)\s+)?"([^"\\]+)"(?:\s*\()?\s*$/.exec(line);
|
|
183
|
+
if (prim) { pending = { name: prim[2]!, type: prim[1] ?? "" }; continue; }
|
|
184
|
+
if (line === "{") {
|
|
185
|
+
if (!pending) continue;
|
|
186
|
+
frames.push(pending.name);
|
|
187
|
+
const path = `/${frames.join("/")}`;
|
|
188
|
+
if (/^\/Nomos\/Domains\/d_[0-9a-f]{16}$/.test(path) && !lawDomains.has(path)) lawDomains.set(path, {});
|
|
189
|
+
if (pending.type === "NomosReceptionTemplate") {
|
|
190
|
+
if (!/^\/Nomos\/Domains\/d_[0-9a-f]{16}\/Reception$/.test(path)) throw new Error("Reception metadata is outside its domain");
|
|
191
|
+
fields = {}; arrays = {}; templatePath = path;
|
|
192
|
+
}
|
|
193
|
+
pending = undefined; continue;
|
|
194
|
+
}
|
|
195
|
+
if (line === "}") {
|
|
196
|
+
if (fields && `/${frames.join("/")}` === templatePath) {
|
|
197
|
+
const required = (name: string): string => { const value = fields![name]; if (!value) throw new Error(`Reception metadata missing ${name}`); return value; };
|
|
198
|
+
const format = required("format");
|
|
199
|
+
if (format !== RECEPTION_TEMPLATE_FORMAT && format !== RECEPTION_COMPRESSED_TEMPLATE_FORMAT) throw new Error("unsupported Reception metadata format");
|
|
200
|
+
if (!arrays.members || !arrays.operationKinds || !arrays.selectors || arrays.members.length !== arrays.operationKinds.length || arrays.members.length !== arrays.selectors.length) throw new Error("Reception metadata has no complete operation inventory");
|
|
201
|
+
const operations = arrays.members.map((member, index) => ({ member, kind: arrays.operationKinds![index] as ReceptionOperation["kind"], selector: arrays.selectors![index]! }));
|
|
202
|
+
if (new Set(operations.map(x => x.member)).size !== operations.length || operations.some(x => !x.member || !x.selector || !["offer", "query", "byType", "byId"].includes(x.kind))) throw new Error("invalid Reception operation inventory");
|
|
203
|
+
const [factorySource, clientSource, operationBinding, operationProto] = format === RECEPTION_COMPRESSED_TEMPLATE_FORMAT
|
|
204
|
+
? await receptionSources(required("sourcesGzipBase64"), required("sourcesDecodedBytes"))
|
|
205
|
+
: [unbase64(required("factorySourceBase64")), unbase64(required("clientSourceBase64")), unbase64(required("operationBindingBase64")), unbase64(required("operationProtoBase64"))];
|
|
206
|
+
templates.push({ operations, factorySource: factorySource!, factorySha256: required("factorySha256"), domain: required("domain"), factory: required("factory"), clientSource: clientSource!,
|
|
207
|
+
operationBinding: operationBinding!, operationProto: operationProto!,
|
|
208
|
+
sourceSha256: required("sourceSha256"), bindingSha256: required("bindingSha256"), protoSha256: required("protoSha256") });
|
|
209
|
+
fields = undefined; templatePath = undefined;
|
|
210
|
+
}
|
|
211
|
+
frames.pop(); continue;
|
|
212
|
+
}
|
|
213
|
+
const domain = lawDomains.get(`/${frames.join("/")}`);
|
|
214
|
+
if (domain) {
|
|
215
|
+
const declaration = /^(?:custom\s+)?(?:uniform\s+)?(?:string|token)\s+nomos:(label|receptionVisibility)\s*=\s*("(?:[^"\\]|\\.)*")$/.exec(line);
|
|
216
|
+
if (declaration) { const key = declaration[1] === "label" ? "label" : "visibility"; domain[key] = usdaUnquote(declaration[2]!); }
|
|
217
|
+
}
|
|
218
|
+
if (fields) {
|
|
219
|
+
const array = /^(?:custom\s+)?(?:uniform\s+)?(?:string|token)\[\]\s+nomos:(members|operationKinds|selectors)\s*=\s*\[(.*)\]$/.exec(line);
|
|
220
|
+
if (array) { arrays[array[1]!] = Array.from(array[2]!.matchAll(/"(?:[^"\\]|\\.)*"/g), x => usdaUnquote(x[0])); continue; }
|
|
221
|
+
const attribute = /^(?:custom\s+)?(?:uniform\s+)?string\s+nomos:(\w+)\s*=\s*("(?:[^"\\]|\\.)*")$/.exec(line);
|
|
222
|
+
if (attribute) { if (fields[attribute[1]!] !== undefined) throw new Error("duplicate Reception metadata field"); fields[attribute[1]!] = usdaUnquote(attribute[2]!); }
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (templates.length === 0) throw new Error("realised law carries no compiler Reception metadata; recompile with a supported compiler");
|
|
226
|
+
const domains = new Set<string>();
|
|
227
|
+
for (const template of templates) {
|
|
228
|
+
if (domains.has(template.domain)) throw new Error(`duplicate Reception domain ${template.domain}`);
|
|
229
|
+
domains.add(template.domain);
|
|
230
|
+
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(template.factory)) throw new Error("invalid Reception factory");
|
|
231
|
+
for (const [source, digest] of [[template.factorySource, template.factorySha256], [template.clientSource, template.sourceSha256], [template.operationBinding, template.bindingSha256], [template.operationProto, template.protoSha256]]) {
|
|
232
|
+
if (!HEX.test(digest!) || await receptionSha256(source!) !== digest) throw new Error("Reception compiler metadata digest mismatch");
|
|
233
|
+
}
|
|
234
|
+
if (!template.clientSource.includes(JSON.stringify(RECEPTION_CLOSURE_TOKEN)) || !template.factorySource.includes(JSON.stringify(RECEPTION_CLOSURE_TOKEN))) throw new Error("Reception source has no closure identity binding");
|
|
235
|
+
}
|
|
236
|
+
for (const domain of lawDomains.values()) {
|
|
237
|
+
if (domain.visibility === "private") continue;
|
|
238
|
+
if (!domain.label || !domains.has(domain.label)) throw new Error(`realised law domain ${domain.label ?? "without label"} carries no Reception metadata`);
|
|
239
|
+
}
|
|
240
|
+
// An optional authoritative caller inventory adds an exact domain set check.
|
|
241
|
+
const declaredPrivate = new Set([...lawDomains.values()].filter(x => x.visibility === "private").map(x => x.label));
|
|
242
|
+
const expectedDomains = input.domains?.filter(domain => !declaredPrivate.has(domain));
|
|
243
|
+
if (expectedDomains && (expectedDomains.length !== domains.size || expectedDomains.some(domain => !domains.has(domain))))
|
|
244
|
+
throw new Error("realised law Reception domain inventory is incomplete");
|
|
245
|
+
return templates.sort((a, b) => a.domain < b.domain ? -1 : a.domain > b.domain ? 1 : 0);
|
|
246
|
+
}
|
|
247
|
+
export async function verifyReceptionArtifact(artifact: ClosureReceptionArtifact, expected?: { closure: string; generator: string; runtimeContract: string }): Promise<void> {
|
|
248
|
+
const manifest = artifact.manifest;
|
|
249
|
+
if (!manifest || manifest.format !== "nomos.reception-artifact.v1" || manifest.entrypoint !== "reception.mjs" || typeof artifact.moduleSource !== "string") throw new Error("invalid Reception artifact");
|
|
250
|
+
if (expected && (manifest.closure !== expected.closure || manifest.generator !== expected.generator || manifest.runtimeContract !== expected.runtimeContract)) throw new Error("Reception artifact provenance mismatch");
|
|
251
|
+
if (manifest.artifactId !== await artifactIdentity(manifest.closure, manifest.generator, manifest.runtimeContract) || manifest.moduleSha256 !== await receptionSha256(artifact.moduleSource)) throw new Error("Reception artifact digest mismatch");
|
|
252
|
+
if (!Array.isArray(manifest.domains) || manifest.domains.length === 0 || new Set(manifest.domains.map(x => x.domain)).size !== manifest.domains.length) throw new Error("invalid Reception artifact domain inventory");
|
|
253
|
+
for (const domain of manifest.domains) {
|
|
254
|
+
if (typeof domain.domain !== "string" || !domain.domain || !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(domain.factory) || !HEX.test(domain.sourceSha256) || !Array.isArray(domain.operations)) throw new Error("invalid Reception artifact domain contract");
|
|
255
|
+
const operations: readonly ReceptionOperation[] = domain.operations;
|
|
256
|
+
if (new Set(operations.map(x => x.member)).size !== operations.length || operations.some(x => typeof x.member !== "string" || !x.member || typeof x.selector !== "string" || !x.selector || !["offer", "query", "byType", "byId"].includes(x.kind))) throw new Error("invalid Reception artifact operation inventory");
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** Decode the existing generic OpenUSD law-product frame using its published
|
|
2
|
+
* Protobuf schema. These bytes are already verified by the target; their frame
|
|
3
|
+
* identity is checked again before selecting any compiler metadata. */
|
|
4
|
+
import { fromBinary } from "@bufbuild/protobuf";
|
|
5
|
+
import { CachedOpenUsdLayerSchema } from "./protocol/nomos/kernel/v1/openusd_cache_pb.js";
|
|
6
|
+
import { usdaString } from "./usda_text.js";
|
|
7
|
+
|
|
8
|
+
export function receptionMetadataFromLawProduct(bytes: Uint8Array, closure: string): string {
|
|
9
|
+
const layer = fromBinary(CachedOpenUsdLayerSchema, bytes);
|
|
10
|
+
if (layer.formatVersion !== 1 || layer.packageIdentity !== closure)
|
|
11
|
+
throw new Error("Reception law product format or closure identity mismatch");
|
|
12
|
+
const paths = new Set<string>();
|
|
13
|
+
const output = ["#usda 1.0"];
|
|
14
|
+
const domains = layer.prims.filter(prim => /^\/Nomos\/Domains\/d_[0-9a-f]{16}$/.test(prim.path));
|
|
15
|
+
for (const domain of domains) {
|
|
16
|
+
const template = layer.prims.filter(prim => prim.path === `${domain.path}/Reception`);
|
|
17
|
+
if (template.length > 1) throw new Error("Reception law product repeats a template prim");
|
|
18
|
+
const selected = [domain, ...template];
|
|
19
|
+
output.push('def Scope "Nomos"', '{', ' def Scope "Domains"', ' {', ` def Scope ${usdaString(domain.path.split("/").at(-1)!)}`, ' {');
|
|
20
|
+
for (const prim of selected) {
|
|
21
|
+
if (paths.has(prim.path)) throw new Error("Reception law product repeats a domain prim");
|
|
22
|
+
paths.add(prim.path);
|
|
23
|
+
const names = new Set<string>();
|
|
24
|
+
if (prim !== domain) {
|
|
25
|
+
if (prim.typeName !== "NomosReceptionTemplate") throw new Error("Reception law product has an incompatible template prim type");
|
|
26
|
+
output.push(' def NomosReceptionTemplate "Reception"', ' {');
|
|
27
|
+
}
|
|
28
|
+
for (const property of prim.properties) {
|
|
29
|
+
if (prim === domain && !["nomos:label", "nomos:receptionVisibility"].includes(property.name)) continue;
|
|
30
|
+
if (names.has(property.name)) throw new Error("Reception law product repeats a property");
|
|
31
|
+
names.add(property.name);
|
|
32
|
+
const value = property.value?.kind;
|
|
33
|
+
if (value?.case === "string" || value?.case === "token") {
|
|
34
|
+
output.push(` custom ${value.case} ${property.name} = ${usdaString(value.value)}`);
|
|
35
|
+
} else if (value?.case === "stringArray" || value?.case === "tokenArray") {
|
|
36
|
+
output.push(` custom ${value.case === "stringArray" ? "string" : "token"}[] ${property.name} = [${value.value.values.map(usdaString).join(", ")}]`);
|
|
37
|
+
} else throw new Error(`Reception law product property ${property.name} has an incompatible value type`);
|
|
38
|
+
}
|
|
39
|
+
if (prim !== domain) output.push(' }');
|
|
40
|
+
}
|
|
41
|
+
output.push(' }', ' }', '}');
|
|
42
|
+
}
|
|
43
|
+
if (domains.length === 0) throw new Error("Reception law product contains no domain inventory");
|
|
44
|
+
return output.join("\n");
|
|
45
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { build, version as esbuildVersion } from "esbuild";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { gzipSync } from "node:zlib";
|
|
5
|
+
/** Compiler-only inventory of the generated stateless business methods. */
|
|
6
|
+
import type { DomainModule } from "./codegen_dart.js";
|
|
7
|
+
import type { LawGraph } from "./law_graph.js";
|
|
8
|
+
import { generatedAggregateCollectionName } from "./generated_client_names.js";
|
|
9
|
+
import { isNamespacedAggregate, qualifyReadProjectionType } from "./read_projection_namespace.js";
|
|
10
|
+
import type { ReceptionOperation } from "./reception_artifact_contract.js";
|
|
11
|
+
import type { ReceptionTemplate } from "./reception_artifact_contract.js";
|
|
12
|
+
import { isFrameworkAggregate, isFrameworkQuery } from "./framework/framework_aggregates.js";
|
|
13
|
+
|
|
14
|
+
/** Keep source completeness without multiplying opaque metadata in semantic USD.
|
|
15
|
+
* gzip has no timestamp/name, so repeated compiler runs carry the same bytes. */
|
|
16
|
+
export function compressCompilerReceptionTemplate(template: ReceptionTemplate): ReceptionTemplate {
|
|
17
|
+
const bytes = Buffer.from(JSON.stringify([template.factorySource, template.clientSource, template.operationBinding, template.operationProto]));
|
|
18
|
+
return { ...template, sourceEnvelope: { gzipBase64: gzipSync(bytes, { level: 9 }).toString("base64"), decodedBytes: bytes.byteLength } };
|
|
19
|
+
}
|
|
20
|
+
const queryMember = (value: string) => {
|
|
21
|
+
const camel = value.replace(/[_\s-]+(\w)/g, (_match, letter: string) => letter.toUpperCase());
|
|
22
|
+
return camel[0]!.toLowerCase() + camel.slice(1);
|
|
23
|
+
};
|
|
24
|
+
export function compilerReceptionOperations(module: DomainModule, graph: LawGraph): readonly ReceptionOperation[] {
|
|
25
|
+
const operations: ReceptionOperation[] = [];
|
|
26
|
+
const domain = module.domain ?? module.name;
|
|
27
|
+
for (const node of graph.nodes) {
|
|
28
|
+
if (node.kind === "intent" && node.public) {
|
|
29
|
+
const selector = node.publicRoute ?? node.entity.label;
|
|
30
|
+
operations.push({ member: selector, kind: "offer", selector });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
for (const read of [...(module.queries ?? []), ...(module.orderedReads ?? [])]) {
|
|
34
|
+
if ((read as { businessBirthInternal?: unknown }).businessBirthInternal === true || isFrameworkQuery(read as Parameters<typeof isFrameworkQuery>[0])) continue;
|
|
35
|
+
operations.push({ member: queryMember(read.id), kind: "query", selector: read.id });
|
|
36
|
+
}
|
|
37
|
+
for (const aggregate of module.aggregates) {
|
|
38
|
+
if ((aggregate as { businessBirthInternal?: boolean }).businessBirthInternal || isFrameworkAggregate(aggregate)) continue;
|
|
39
|
+
const collection = generatedAggregateCollectionName(aggregate.id);
|
|
40
|
+
const selector = isNamespacedAggregate(aggregate) ? qualifyReadProjectionType(domain, aggregate.id) : aggregate.id;
|
|
41
|
+
operations.push({ member: `${collection}.all`, kind: "byType", selector }, { member: `${collection}.find`, kind: "byId", selector });
|
|
42
|
+
}
|
|
43
|
+
const sorted = operations.sort((a, b) => a.member < b.member ? -1 : a.member > b.member ? 1 : 0);
|
|
44
|
+
if (new Set(sorted.map(x => x.member)).size !== sorted.length) throw new Error(`Reception metadata repeats business member in ${domain}`);
|
|
45
|
+
return sorted;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Compiler-only: lower the existing generated client to a closure-independent
|
|
49
|
+
* factory fragment. The hosted generator performs no TypeScript transformation. */
|
|
50
|
+
export async function bundleCompilerReceptionFactory(template: { factory: string; clientSource: string; operationBinding: string }): Promise<string> {
|
|
51
|
+
if (esbuildVersion !== "0.24.2") throw new Error(`unsupported Reception compiler transformer ${esbuildVersion}`);
|
|
52
|
+
const compilerRequire = createRequire(import.meta.url);
|
|
53
|
+
const files = new Map([
|
|
54
|
+
["entry.ts", `export { ${template.factory} } from "./client.ts";`],
|
|
55
|
+
["client.ts", template.clientSource], ["operation_payloads_pb.ts", template.operationBinding],
|
|
56
|
+
]);
|
|
57
|
+
const result = await build({ entryPoints: ["entry.ts"], bundle: true, write: false,
|
|
58
|
+
format: "iife", globalName: "__NOMOS_RECEPTION_FACET__", platform: "browser", target: "es2022",
|
|
59
|
+
minify: true, legalComments: "none", charset: "utf8", logLevel: "silent", sourcemap: false,
|
|
60
|
+
plugins: [{ name: "compiler-reception-factory", setup(builder) {
|
|
61
|
+
builder.onResolve({ filter: /.*/ }, args => {
|
|
62
|
+
if (args.path === "entry.ts") return { path: args.path, namespace: "reception" };
|
|
63
|
+
if (args.namespace === "reception") {
|
|
64
|
+
if (args.path === "./client.ts") return { path: "client.ts", namespace: "reception" };
|
|
65
|
+
if (args.path === "./operation_payloads_pb.js") return { path: "operation_payloads_pb.ts", namespace: "reception" };
|
|
66
|
+
if (args.path === "@githolon/runtime/application") return { path: join(dirname(compilerRequire.resolve("@githolon/runtime/package.json")), "src", "application_reception.ts") };
|
|
67
|
+
if (/^@bufbuild\/protobuf(?:\/[A-Za-z0-9_-]+)?$/.test(args.path)) return { path: compilerRequire.resolve(args.path) };
|
|
68
|
+
throw new Error(`Reception compiler source imports forbidden module '${args.path}'`);
|
|
69
|
+
}
|
|
70
|
+
return undefined;
|
|
71
|
+
});
|
|
72
|
+
builder.onLoad({ filter: /.*/, namespace: "reception" }, args => {
|
|
73
|
+
const contents = files.get(args.path);
|
|
74
|
+
if (contents === undefined) throw new Error(`Reception compiler missing source '${args.path}'`);
|
|
75
|
+
return { contents, loader: "ts" };
|
|
76
|
+
});
|
|
77
|
+
} }],
|
|
78
|
+
});
|
|
79
|
+
if (result.outputFiles?.length !== 1) throw new Error("Reception compiler emitted no unique factory");
|
|
80
|
+
return result.outputFiles[0]!.text;
|
|
81
|
+
}
|