@githolon/dsl 0.105.6 → 0.105.8
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 +4 -0
- package/dart/lib/src/runner/node-entry.mjs +8 -2
- package/dart/lib/src/runner/offline-install.mjs +26 -17
- package/dart/lib/src/webview_bridge.dart +20 -4
- package/dart/pubspec.yaml +1 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +7 -1
- package/src/aggregate.ts +2 -1
- package/src/birth_meter_ensure.ts +38 -1
- package/src/codegen_dart.ts +1 -1
- package/src/codegen_proof.ts +1 -1
- package/src/codegen_ts.ts +41 -9
- package/src/compat_manifest.ts +1 -1
- package/src/compile_package_main.ts +81 -13
- package/src/framework/home.ts +10 -11
- package/src/framework/owned_custody.ts +78 -22
- package/src/framework/workspaces.ts +1 -1
- package/src/index.ts +1 -1
- package/src/law_check.ts +1 -1
- package/src/law_graph.ts +21 -12
- package/src/relation_schema.ts +1 -1
- package/src/relation_sugar.ts +1 -1
- package/src/shape_compat.ts +1 -1
- package/src/value_object.ts +2 -2
package/dart/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
## 0.64.24
|
|
2
|
+
|
|
3
|
+
- Restaged with the current kernel candidate: regenerated vendored assets (kernel release identity is baked into them). Published pub.dev archives are immutable, so regenerated bytes ride their own version.
|
|
4
|
+
|
|
1
5
|
## 0.64.23
|
|
2
6
|
|
|
3
7
|
- Rebuild the self-contained runner without the retired structural-interface sidecar assumption;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// <define:__NOMOS_KERNEL_RELEASE__>
|
|
4
|
-
var define_NOMOS_KERNEL_RELEASE_default = { production: { hash: "
|
|
4
|
+
var define_NOMOS_KERNEL_RELEASE_default = { production: { hash: "08719339b0ecfb39b75543bf40422e84832153cc463b00283c0e84f6e38ce359", version: "kernel-08719339", features: ["compoundKeys", "capturedReads", "invariantGate", "surfacedMerge", "fieldProvenance", "nullSemantics", "attestedReads", "conflictSnapshots", "ownedAudience", "temporalEnum"], protocol: 1 }, testMint: { hash: "ab5c45c029a57b39711a559d803ed7d23e8eb94c11bbba652df7f8b3abd3c6df", version: "kernel-ab5c45c0", features: ["compoundKeys", "capturedReads", "invariantGate", "surfacedMerge", "fieldProvenance", "nullSemantics", "attestedReads", "conflictSnapshots", "ownedAudience", "temporalEnum"], protocol: 1 } };
|
|
5
5
|
|
|
6
6
|
// runner/node-entry.ts
|
|
7
7
|
import { createInterface } from "node:readline";
|
|
@@ -7596,7 +7596,10 @@ async function pushPack(remote, headers = {}, { updates = [], pack = null } = {}
|
|
|
7596
7596
|
var RELEASE_RECEIPT_PREFIX = "nomos-release-receipt:";
|
|
7597
7597
|
function prepareManagedPromotion(report, candidateHash, domain, opts = {}) {
|
|
7598
7598
|
if (!report.sourceVerified || !report.complete || !report.evolve?.ok) {
|
|
7599
|
-
const reason = !report.sourceVerified ? "the source ledger did not verify" : !report.complete ? "some historical premises could not be re-derived" :
|
|
7599
|
+
const reason = !report.sourceVerified ? "the source ledger did not verify" : !report.complete ? "some historical premises could not be re-derived" : [
|
|
7600
|
+
"the candidate fails deterministic schema evolution",
|
|
7601
|
+
...report.evolve.violations.map((violation) => violation.detail)
|
|
7602
|
+
].join(": ");
|
|
7600
7603
|
return { ok: false, stage: "preflight", report, error: `promotion refused: ${reason}` };
|
|
7601
7604
|
}
|
|
7602
7605
|
const acceptanceReason = opts.acceptanceReason?.trim();
|
|
@@ -8496,6 +8499,9 @@ function createSerializedScheduler() {
|
|
|
8496
8499
|
return scheduler;
|
|
8497
8500
|
}
|
|
8498
8501
|
|
|
8502
|
+
// src/intent_offer_codec.ts
|
|
8503
|
+
var encoder = new TextEncoder();
|
|
8504
|
+
|
|
8499
8505
|
// src/index.ts
|
|
8500
8506
|
var enc4 = new TextEncoder();
|
|
8501
8507
|
var dec4 = new TextDecoder();
|
|
@@ -6,7 +6,7 @@ var __export = (target, all) => {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
// <define:__NOMOS_KERNEL_RELEASE__>
|
|
9
|
-
var define_NOMOS_KERNEL_RELEASE_default = { production: { hash: "
|
|
9
|
+
var define_NOMOS_KERNEL_RELEASE_default = { production: { hash: "08719339b0ecfb39b75543bf40422e84832153cc463b00283c0e84f6e38ce359", version: "kernel-08719339", features: ["compoundKeys", "capturedReads", "invariantGate", "surfacedMerge", "fieldProvenance", "nullSemantics", "attestedReads", "conflictSnapshots", "ownedAudience", "temporalEnum"], protocol: 1 }, testMint: { hash: "ab5c45c029a57b39711a559d803ed7d23e8eb94c11bbba652df7f8b3abd3c6df", version: "kernel-ab5c45c0", features: ["compoundKeys", "capturedReads", "invariantGate", "surfacedMerge", "fieldProvenance", "nullSemantics", "attestedReads", "conflictSnapshots", "ownedAudience", "temporalEnum"], protocol: 1 } };
|
|
10
10
|
|
|
11
11
|
// runner/offline-install.ts
|
|
12
12
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync } from "node:fs";
|
|
@@ -1938,7 +1938,10 @@ async function pushPack(remote, headers = {}, { updates = [], pack = null } = {}
|
|
|
1938
1938
|
var RELEASE_RECEIPT_PREFIX = "nomos-release-receipt:";
|
|
1939
1939
|
function prepareManagedPromotion(report, candidateHash, domain, opts = {}) {
|
|
1940
1940
|
if (!report.sourceVerified || !report.complete || !report.evolve?.ok) {
|
|
1941
|
-
const reason = !report.sourceVerified ? "the source ledger did not verify" : !report.complete ? "some historical premises could not be re-derived" :
|
|
1941
|
+
const reason = !report.sourceVerified ? "the source ledger did not verify" : !report.complete ? "some historical premises could not be re-derived" : [
|
|
1942
|
+
"the candidate fails deterministic schema evolution",
|
|
1943
|
+
...report.evolve.violations.map((violation) => violation.detail)
|
|
1944
|
+
].join(": ");
|
|
1942
1945
|
return { ok: false, stage: "preflight", report, error: `promotion refused: ${reason}` };
|
|
1943
1946
|
}
|
|
1944
1947
|
const acceptanceReason = opts.acceptanceReason?.trim();
|
|
@@ -5899,17 +5902,18 @@ var protoAuthority = (principal = "", keys = [], authToken = "", nowUnix = 0, au
|
|
|
5899
5902
|
}))
|
|
5900
5903
|
});
|
|
5901
5904
|
function applicationQueryProto(eng2, ws, query2, options = {}) {
|
|
5905
|
+
const hasAuthority = options.principal !== void 0 || options.keys !== void 0 || options.authToken !== void 0 || options.nowUnix !== void 0 || options.authorSecret !== void 0;
|
|
5902
5906
|
const response = callProto(eng2.ex, {
|
|
5903
5907
|
case: "query",
|
|
5904
5908
|
value: create(QueryRequestSchema, {
|
|
5905
5909
|
target: protoTarget(ws, options.branch ?? BRANCH),
|
|
5906
|
-
authority: protoAuthority(
|
|
5910
|
+
authority: hasAuthority ? protoAuthority(
|
|
5907
5911
|
options.principal,
|
|
5908
5912
|
options.keys,
|
|
5909
5913
|
options.authToken,
|
|
5910
5914
|
options.nowUnix,
|
|
5911
5915
|
options.authorSecret
|
|
5912
|
-
),
|
|
5916
|
+
) : void 0,
|
|
5913
5917
|
spans: options.spans ?? false,
|
|
5914
5918
|
query: query2
|
|
5915
5919
|
})
|
|
@@ -7843,12 +7847,10 @@ var cryptoOwnedScopeKey = (eng2, workspace2, authorSecret, scope, principal = ""
|
|
|
7843
7847
|
const recordObject = record === void 0 ? void 0 : `${record.type}:${record.id}`;
|
|
7844
7848
|
const derived = namedQueryProto(eng2, workspace2, QueryOperation.CRYPTO_OWNED_SCOPE_KEY, {
|
|
7845
7849
|
custodyWorkspace: workspace2,
|
|
7846
|
-
principal,
|
|
7847
|
-
authorSecret,
|
|
7848
7850
|
scope,
|
|
7849
7851
|
...recordObject === void 0 ? {} : { recordObject, recordEpoch },
|
|
7850
7852
|
...recordWrap === void 0 ? {} : { recordWrap }
|
|
7851
|
-
});
|
|
7853
|
+
}, { principal, authorSecret });
|
|
7852
7854
|
const ownedKeySubject = String(derived.ownedKeySubject || "");
|
|
7853
7855
|
if (!requireSeat) {
|
|
7854
7856
|
return {
|
|
@@ -7865,7 +7867,16 @@ var cryptoOwnedScopeKey = (eng2, workspace2, authorSecret, scope, principal = ""
|
|
|
7865
7867
|
let hex = "";
|
|
7866
7868
|
for (const byte of enc3.encode(principal)) hex += byte.toString(16).padStart(2, "0");
|
|
7867
7869
|
const object = `nomos-owned-principal:${hex || "00"}`;
|
|
7868
|
-
const rows = qById(
|
|
7870
|
+
const rows = qById(
|
|
7871
|
+
eng2,
|
|
7872
|
+
workspace2,
|
|
7873
|
+
`reltuple-${object}-nomos_internal_owned_device_key_7c1f5e`,
|
|
7874
|
+
principal,
|
|
7875
|
+
[],
|
|
7876
|
+
"",
|
|
7877
|
+
0,
|
|
7878
|
+
authorSecret
|
|
7879
|
+
);
|
|
7869
7880
|
const row = Array.isArray(rows) ? rows[0] : rows?.rows?.[0];
|
|
7870
7881
|
const leaves = row?.data?.subjects ?? row?.subjects ?? {};
|
|
7871
7882
|
const leaf = (raw) => {
|
|
@@ -7896,14 +7907,12 @@ var cryptoOwnedScopeKey = (eng2, workspace2, authorSecret, scope, principal = ""
|
|
|
7896
7907
|
const custody = String(leaf(leaves[ownedKeySubject]).ownedCustody || "");
|
|
7897
7908
|
if (!custody) throw new Error("the approved device has no owned-data handoff");
|
|
7898
7909
|
const opened = namedQueryProto(eng2, workspace2, QueryOperation.CRYPTO_OWNED_SCOPE_KEY, {
|
|
7899
|
-
|
|
7900
|
-
principal,
|
|
7901
|
-
authorSecret,
|
|
7910
|
+
custodyWorkspace: workspace2,
|
|
7902
7911
|
scope,
|
|
7903
7912
|
ownedCustody: custody,
|
|
7904
7913
|
...recordObject === void 0 ? {} : { recordObject, recordEpoch },
|
|
7905
7914
|
...recordWrap === void 0 ? {} : { recordWrap }
|
|
7906
|
-
});
|
|
7915
|
+
}, { principal, authorSecret });
|
|
7907
7916
|
return {
|
|
7908
7917
|
scope: String(opened.scope || scope),
|
|
7909
7918
|
epoch: Number(opened.epoch) || 0,
|
|
@@ -7924,11 +7933,8 @@ var cryptoOwnedScopeKey = (eng2, workspace2, authorSecret, scope, principal = ""
|
|
|
7924
7933
|
};
|
|
7925
7934
|
};
|
|
7926
7935
|
var cryptoOwnedDeviceRequest = (eng2, workspace2, principal, authorSecret, scope) => String(namedQueryProto(eng2, workspace2, QueryOperation.CRYPTO_OWNED_DEVICE_REQUEST, {
|
|
7927
|
-
workspace: workspace2,
|
|
7928
|
-
principal,
|
|
7929
|
-
authorSecret,
|
|
7930
7936
|
scope
|
|
7931
|
-
}).request || "");
|
|
7937
|
+
}, { principal, authorSecret }).request || "");
|
|
7932
7938
|
var cryptoOwnedPrepareApproval = (eng2, args) => namedQueryProto(eng2, args.workspace, QueryOperation.CRYPTO_OWNED_PREPARE_APPROVAL, args);
|
|
7933
7939
|
var cryptoOwnedPrepareRecovery = (eng2, args) => namedQueryProto(eng2, args.workspace, QueryOperation.CRYPTO_OWNED_PREPARE_RECOVERY, args);
|
|
7934
7940
|
var cryptoKeygen = (eng2) => namedQueryProto(eng2, "root", QueryOperation.CRYPTO_KEYGEN);
|
|
@@ -7969,7 +7975,10 @@ var certSign = (eng2, {
|
|
|
7969
7975
|
...authProviderJwks !== void 0 ? { authProviderJwks } : {},
|
|
7970
7976
|
...authProviderAudience !== void 0 ? { authProviderAudience } : {}
|
|
7971
7977
|
}).cert;
|
|
7972
|
-
var cryptoWrapKey = (eng2, { recipientPub, scopeKey }) =>
|
|
7978
|
+
var cryptoWrapKey = (eng2, { recipientPub, scopeKey }) => {
|
|
7979
|
+
const result = namedQueryProto(eng2, "root", QueryOperation.CRYPTO_WRAP_KEY, { recipientPub, scopeKey });
|
|
7980
|
+
return { hpkeEpk: String(result.hpkeEpk ?? ""), ct: String(result.ct ?? "") };
|
|
7981
|
+
};
|
|
7973
7982
|
var cryptoUnwrapKey = (eng2, { secret, hpkeEpk, ct }) => namedQueryProto(eng2, "root", QueryOperation.CRYPTO_UNWRAP_KEY, { secret, hpkeEpk, ct }).scopeKey;
|
|
7974
7983
|
var readPosture = (eng2, ws, principal = "", authToken = "", nowUnix = 0, authorSecret = "") => namedQueryProto(
|
|
7975
7984
|
eng2,
|
|
@@ -1254,6 +1254,14 @@ class NomosBridge {
|
|
|
1254
1254
|
final Map<String, SyncStatus> _statusBySession = {};
|
|
1255
1255
|
final Map<String, StreamController<SyncStatus>> _syncStatusBySession = {};
|
|
1256
1256
|
Timer? _metricsTimer;
|
|
1257
|
+
// The zone this bridge was CONSTRUCTED in. The metrics poll belongs to the
|
|
1258
|
+
// bridge (a long-lived, often file-shared object in tests), yet it used to be
|
|
1259
|
+
// created in whichever zone the first status LISTENER attached from — in
|
|
1260
|
+
// widget tests that is a per-test FakeAsync zone, so a file-shared bridge's
|
|
1261
|
+
// periodic poll became a fake timer owned by ONE test and tripped
|
|
1262
|
+
// flutter_test's '!timersPending' invariant at that test's teardown. Timers
|
|
1263
|
+
// run in their owner's zone, not their caller's.
|
|
1264
|
+
final Zone _creationZone = Zone.current;
|
|
1257
1265
|
Future<void>? _metricsPoll;
|
|
1258
1266
|
bool _metricsPollAgain = false;
|
|
1259
1267
|
final Completer<void> _ready = Completer<void>();
|
|
@@ -3579,8 +3587,12 @@ class NomosBridge {
|
|
|
3579
3587
|
// metrics snapshot.
|
|
3580
3588
|
if (!_syncStatusCtrl.isClosed) _syncStatusCtrl.add(_status);
|
|
3581
3589
|
_pollMetricsOnce();
|
|
3582
|
-
_metricsTimer ??=
|
|
3583
|
-
|
|
3590
|
+
_metricsTimer ??= _creationZone.run(
|
|
3591
|
+
() => Timer.periodic(
|
|
3592
|
+
const Duration(seconds: 4),
|
|
3593
|
+
(_) => _creationZone.run(_pollMetricsOnce),
|
|
3594
|
+
),
|
|
3595
|
+
);
|
|
3584
3596
|
}
|
|
3585
3597
|
|
|
3586
3598
|
void _startSessionStatusPoll(String session) {
|
|
@@ -3589,8 +3601,12 @@ class NomosBridge {
|
|
|
3589
3601
|
controller.add(_currentSessionStatus(session));
|
|
3590
3602
|
}
|
|
3591
3603
|
_pollMetricsOnce();
|
|
3592
|
-
_metricsTimer ??=
|
|
3593
|
-
|
|
3604
|
+
_metricsTimer ??= _creationZone.run(
|
|
3605
|
+
() => Timer.periodic(
|
|
3606
|
+
const Duration(seconds: 4),
|
|
3607
|
+
(_) => _creationZone.run(_pollMetricsOnce),
|
|
3608
|
+
),
|
|
3609
|
+
);
|
|
3594
3610
|
}
|
|
3595
3611
|
|
|
3596
3612
|
void _stopStatusPollIfUnused() {
|
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.64.
|
|
10
|
+
version: 0.64.24
|
|
11
11
|
|
|
12
12
|
environment:
|
|
13
13
|
sdk: ^3.5.0
|
package/package.json
CHANGED
|
@@ -6627,7 +6627,10 @@ async function pushPack(remote, headers = {}, { updates = [], pack = null } = {}
|
|
|
6627
6627
|
var RELEASE_RECEIPT_PREFIX = "nomos-release-receipt:";
|
|
6628
6628
|
function prepareManagedPromotion(report, candidateHash, domain, opts = {}) {
|
|
6629
6629
|
if (!report.sourceVerified || !report.complete || !report.evolve?.ok) {
|
|
6630
|
-
const reason = !report.sourceVerified ? "the source ledger did not verify" : !report.complete ? "some historical premises could not be re-derived" :
|
|
6630
|
+
const reason = !report.sourceVerified ? "the source ledger did not verify" : !report.complete ? "some historical premises could not be re-derived" : [
|
|
6631
|
+
"the candidate fails deterministic schema evolution",
|
|
6632
|
+
...report.evolve.violations.map((violation) => violation.detail)
|
|
6633
|
+
].join(": ");
|
|
6631
6634
|
return { ok: false, stage: "preflight", report, error: `promotion refused: ${reason}` };
|
|
6632
6635
|
}
|
|
6633
6636
|
const acceptanceReason = opts.acceptanceReason?.trim();
|
|
@@ -7527,6 +7530,9 @@ function createSerializedScheduler() {
|
|
|
7527
7530
|
return scheduler;
|
|
7528
7531
|
}
|
|
7529
7532
|
|
|
7533
|
+
// ../cloud/web-client/src/intent_offer_codec.ts
|
|
7534
|
+
var encoder = new TextEncoder();
|
|
7535
|
+
|
|
7530
7536
|
// ../cloud/web-client/src/index.ts
|
|
7531
7537
|
var enc4 = new TextEncoder();
|
|
7532
7538
|
var dec4 = new TextDecoder();
|
package/src/aggregate.ts
CHANGED
|
@@ -84,6 +84,7 @@ export type Audience =
|
|
|
84
84
|
export const OWNER_FIELD = "__nomos_owner_principal_7c1f5e";
|
|
85
85
|
export const OWNED_READ_RELATION = "nomos_internal_owned_reader_7c1f5e";
|
|
86
86
|
export const OWNED_KEY_RELATION = "nomos_internal_owned_device_key_7c1f5e";
|
|
87
|
+
export const OWNED_PRINCIPAL_OBJECT_TYPE = "nomos-owned-principal";
|
|
87
88
|
export const OWNED_KEY_PAYLOAD_FIELD = "__nomos_owned_device_key_subject_7c1f5e";
|
|
88
89
|
export const OWNED_CUSTODY_PAYLOAD_FIELD = "__nomos_owned_device_custody_7c1f5e";
|
|
89
90
|
/** Framework-carried record identities reserved by the client before an owned create is planned.
|
|
@@ -114,7 +115,7 @@ export function ownedKeyObject(principal: string): string {
|
|
|
114
115
|
: [0xf0 | (cp >> 18), 0x80 | ((cp >> 12) & 0x3f), 0x80 | ((cp >> 6) & 0x3f), 0x80 | (cp & 0x3f)];
|
|
115
116
|
for (const byte of bytes) hex += byte.toString(16).padStart(2, "0");
|
|
116
117
|
}
|
|
117
|
-
return
|
|
118
|
+
return `${OWNED_PRINCIPAL_OBJECT_TYPE}:${hex || "00"}`;
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
/**
|
|
@@ -58,6 +58,8 @@ import {
|
|
|
58
58
|
SignerDelegation,
|
|
59
59
|
enrollDelegation,
|
|
60
60
|
enrollSigner,
|
|
61
|
+
enrollSignerSelf,
|
|
62
|
+
enrollSignerSelfRequiresVerified,
|
|
61
63
|
identityRelations,
|
|
62
64
|
signerDelegationByUser,
|
|
63
65
|
signerObject,
|
|
@@ -70,6 +72,7 @@ import {
|
|
|
70
72
|
setAuthProvider,
|
|
71
73
|
} from "./framework/auth_provider_law.js";
|
|
72
74
|
import { refAs, workspaceInvariant } from "./framework/workspace_invariant.js";
|
|
75
|
+
import { isRelationTypeDecl, type RelationTypeDecl } from "./relation_schema.js";
|
|
73
76
|
|
|
74
77
|
/** Lightweight copy of the framework's founding-signer guard, kept here to avoid importing the whole
|
|
75
78
|
* governance module (whose unrelated captured-read laws would otherwise survive into every child lump). */
|
|
@@ -201,6 +204,34 @@ export function withBirthMeterExports<M extends Record<string, unknown>>(mod: M)
|
|
|
201
204
|
}
|
|
202
205
|
if (!found) out[key] = internal(canonical);
|
|
203
206
|
};
|
|
207
|
+
/** Relation schemas have a typed object address rather than an `id`. Treat an explicit re-export
|
|
208
|
+
* of the canonical framework singleton exactly like `internalize()` treats directives: replace
|
|
209
|
+
* every canonical binding with one branded copy, but never annex a tenant-authored fragment that
|
|
210
|
+
* merely happens to extend the same object type. */
|
|
211
|
+
const internalizeRelationType = (key: string, canonical: RelationTypeDecl): void => {
|
|
212
|
+
if (hasOwn(key)) {
|
|
213
|
+
const existing = out[key];
|
|
214
|
+
const sameGeneratedType =
|
|
215
|
+
isGeneratedInternal(existing) &&
|
|
216
|
+
isRelationTypeDecl(existing) &&
|
|
217
|
+
existing.type === canonical.type &&
|
|
218
|
+
existing.global === canonical.global;
|
|
219
|
+
if (existing !== canonical && !sameGeneratedType) throw reservedNameError(key);
|
|
220
|
+
}
|
|
221
|
+
let found = false;
|
|
222
|
+
for (const [exportName, value] of Object.entries(out)) {
|
|
223
|
+
if (value === canonical || (
|
|
224
|
+
isGeneratedInternal(value) &&
|
|
225
|
+
isRelationTypeDecl(value) &&
|
|
226
|
+
value.type === canonical.type &&
|
|
227
|
+
value.global === canonical.global
|
|
228
|
+
)) {
|
|
229
|
+
out[exportName] = internal(value as RelationTypeDecl);
|
|
230
|
+
found = true;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (!found) out[key] = internal(canonical);
|
|
234
|
+
};
|
|
204
235
|
if (governsBirth && !declaresBirth && !acceptsBirth) {
|
|
205
236
|
if (exportedDirectiveIds.has("setBirthPolicy")) internalize("__nomos_BirthPolicy", BirthPolicy);
|
|
206
237
|
if (exportedDirectiveIds.has("grantBirthAllowance")) internalize("__nomos_BirthGrant", BirthGrant);
|
|
@@ -210,8 +241,14 @@ export function withBirthMeterExports<M extends Record<string, unknown>>(mod: M)
|
|
|
210
241
|
// Carry the law needed to judge that internal step in every possible child, plus the write-once guard that
|
|
211
242
|
// keeps the founding lane a genesis primitive. These declarations are certified but never tenant-facing.
|
|
212
243
|
internalize("__nomos_RelationTuple", RelationTuple);
|
|
213
|
-
|
|
244
|
+
// Keep the framework brand when this declaration is injected under a second private export key.
|
|
245
|
+
// Owned-custody law already carries the same signer schema under its own internal key; an unbranded
|
|
246
|
+
// clone makes the relation merger correctly treat it as a tenant-authored duplicate and refuse the
|
|
247
|
+
// package. Two framework paths naming one byte-identical relation are one declaration, not two authors.
|
|
248
|
+
internalizeRelationType("__nomos_identityRelations", identityRelations);
|
|
214
249
|
internalize("__nomos_enrollSigner", enrollSigner);
|
|
250
|
+
internalize("__nomos_enrollSignerSelf", enrollSignerSelf);
|
|
251
|
+
internalize("__nomos_enrollSignerSelfRequiresVerified", enrollSignerSelfRequiresVerified);
|
|
215
252
|
internalize("__nomos_relationTuplesByObject", relationTuplesByObject);
|
|
216
253
|
internalize("__nomos_GenesisWarrant", GenesisWarrant);
|
|
217
254
|
internalize("__nomos_seedGenesis", seedGenesis);
|
package/src/codegen_dart.ts
CHANGED
|
@@ -575,7 +575,7 @@ function introspectField(
|
|
|
575
575
|
case "ZodArray": {
|
|
576
576
|
const elem = zodArrayElement(zt);
|
|
577
577
|
// A `string[]` is a typed `List<String>`. A list of DECLARED VALUE OBJECTS is typed
|
|
578
|
-
// with the generated VO class (
|
|
578
|
+
// with the generated VO class (vo_first_class.md). ANY other element
|
|
579
579
|
// type is arbitrary JSON DATA to the engine, modelled as `List<Object?>`.
|
|
580
580
|
if (zodTypeName(elem) === "ZodString") {
|
|
581
581
|
return { name, dartType: "List<String>", toJson: "strList" };
|
package/src/codegen_proof.ts
CHANGED
|
@@ -239,7 +239,7 @@ function sampleOfZod(
|
|
|
239
239
|
next.add(zt);
|
|
240
240
|
return sampleOfZod(zodUnwrapLazy(zt), path, where, voIncludeOptional, next);
|
|
241
241
|
}
|
|
242
|
-
// VALUE OBJECTS FIRST (
|
|
242
|
+
// VALUE OBJECTS FIRST (vo_first_class.md): a declared VO carries its own lawful
|
|
243
243
|
// sample (the author's validated `example`, the blessed geometry sample, or per-field descriptor
|
|
244
244
|
// synthesis, recursing through nested VOs / arrays-of-VOs / unions). The raw-zod walk below cannot
|
|
245
245
|
// see interior constraints (geo's min-2 coordinates, a `.min()` on a subfield), so after building the
|
package/src/codegen_ts.ts
CHANGED
|
@@ -333,7 +333,7 @@ function tsTypeOfZod(
|
|
|
333
333
|
const target = idOfTargetKindOf(zt) === "contract" ? contractTypeName(aggregateType) : aggregateType;
|
|
334
334
|
return `NomosRef<${target}>`;
|
|
335
335
|
}
|
|
336
|
-
// A DECLARED VALUE OBJECT keeps its NAME (
|
|
336
|
+
// A DECLARED VALUE OBJECT keeps its NAME (vo_first_class.md): the payload
|
|
337
337
|
// references the same exported interface the read model uses — one name, both directions —
|
|
338
338
|
// instead of an anonymous inline literal type. Checked on the BARE object only: optional/
|
|
339
339
|
// default wrappers must keep flowing through the switch so `| undefined` survives.
|
|
@@ -761,6 +761,9 @@ export interface TsClientOptions {
|
|
|
761
761
|
/** Explicit application home for direct/synthetic generation. The package compiler derives this from
|
|
762
762
|
* business birth topology and passes it here; ambiguous multi-domain packages fail rather than flatten. */
|
|
763
763
|
readonly homeDomain?: string;
|
|
764
|
+
/** Emit the structural holon factory for the compiler-owned headless artifact. The ordinary tenant
|
|
765
|
+
* client leaves this false so framework transport types never enter its public declaration graph. */
|
|
766
|
+
readonly exportHolonFactory?: boolean;
|
|
764
767
|
}
|
|
765
768
|
|
|
766
769
|
/** TS string literal (JSON.stringify covers escaping). */
|
|
@@ -1890,9 +1893,9 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
1890
1893
|
` /** THE ACTIVITY FEED (declared \`.activity(...)\`): subscribe to per-committed-intent RAW ROWS`,
|
|
1891
1894
|
` * (the runtime folds the intent log). \`cb\` fires with the current rows + on every new intent;`,
|
|
1892
1895
|
` * returns an unsubscribe fn. The generated client maps these through \`renderActivity\`. */`,
|
|
1893
|
-
` watchActivity(cb: (rows: ActivityRawRow[]) => void, opts?: { base?: string; intervalMs?: number }): () => void;`,
|
|
1896
|
+
` watchActivity?(cb: (rows: ActivityRawRow[]) => void, opts?: { base?: string; intervalMs?: number }): () => void;`,
|
|
1894
1897
|
` /** One-shot: the current committed activity log as RAW ROWS (folded to typed rows by the client). */`,
|
|
1895
|
-
` activityLog(opts?: { base?: string; limit?: number }): Promise<ActivityRawRow[]>;`,
|
|
1898
|
+
` activityLog?(opts?: { base?: string; limit?: number }): Promise<ActivityRawRow[]>;`,
|
|
1896
1899
|
]
|
|
1897
1900
|
: []),
|
|
1898
1901
|
` /** THE COMPAT SUPERPOWER (#72b) — OPTIONAL: a holon from @githolon/client's connect() attaches it.`,
|
|
@@ -2090,6 +2093,25 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
2090
2093
|
` return read(keys);`,
|
|
2091
2094
|
`};`,
|
|
2092
2095
|
``,
|
|
2096
|
+
`/** Owned bounded reads perform the same record-key-aware second pass without erasing`,
|
|
2097
|
+
` * the page envelope. Keeping this distinct from the array helper makes cursor semantics explicit. */`,
|
|
2098
|
+
`const _readPageWithOwnedKeys = async <T>(`,
|
|
2099
|
+
` holon: NomosHolon,`,
|
|
2100
|
+
` keyStore: NomosKeyStore | undefined,`,
|
|
2101
|
+
` scopes: readonly { scope: string; owned?: true }[],`,
|
|
2102
|
+
` read: (keys: readonly NomosScopeKey[] | undefined) => Promise<{ rows: T[]; cursor: string | null; done: boolean }>,`,
|
|
2103
|
+
`): Promise<{ rows: T[]; cursor: string | null; done: boolean }> => {`,
|
|
2104
|
+
` const firstKeys = await _readKeys(holon, keyStore, scopes);`,
|
|
2105
|
+
` const first = await read(firstKeys);`,
|
|
2106
|
+
` if (!scopes.some((scope) => scope.owned === true) || first.rows.length === 0) return first;`,
|
|
2107
|
+
` const records = first.rows.map((row) => {`,
|
|
2108
|
+
` const raw = row as T & { type: string; id: string };`,
|
|
2109
|
+
` return { type: raw.type, id: raw.id };`,
|
|
2110
|
+
` });`,
|
|
2111
|
+
` const keys = await _readKeys(holon, keyStore, scopes, records);`,
|
|
2112
|
+
` return read(keys);`,
|
|
2113
|
+
`};`,
|
|
2114
|
+
``,
|
|
2093
2115
|
`/** Reactive owned reads use the exact same two-pass, record-key-aware read as one-shot reads.`,
|
|
2094
2116
|
` * The raw watch is only a local change signal; ciphertext rows never reach application callbacks. */`,
|
|
2095
2117
|
`const _watchWithOwnedKeys = <T>(`,
|
|
@@ -3375,7 +3397,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
3375
3397
|
);
|
|
3376
3398
|
const queryPageCall = qScopes.length
|
|
3377
3399
|
? qOwned
|
|
3378
|
-
? `
|
|
3400
|
+
? `_readPageWithOwnedKeys(holon, keyStore, ${qScopeJson}, (keys) => (holon.queryPage ?? (() => { throw new Error("bounded reads need a current @githolon/client"); }))(${JSON.stringify(q.id)}, ${wireParams}, page, keys) as Promise<{ rows: Row<${q.returns}>[]; cursor: string | null; done: boolean }>)`
|
|
3379
3401
|
: `(holon.queryPage ?? (() => { throw new Error("bounded reads need a current @githolon/client"); }))(${JSON.stringify(q.id)}, ${wireParams}, page, await _readKeys(holon, keyStore, ${qScopeJson})) as Promise<{ rows: Row<${q.returns}>[]; cursor: string | null; done: boolean }>`
|
|
3380
3402
|
: `(holon.queryPage ?? (() => { throw new Error("bounded reads need a current @githolon/client"); }))(${JSON.stringify(q.id)}, ${wireParams}, page) as Promise<{ rows: Row<${q.returns}>[]; cursor: string | null; done: boolean }>`;
|
|
3381
3403
|
methods.push(
|
|
@@ -3639,15 +3661,15 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
3639
3661
|
` * Every public directive has a baseline; \`.activity(...)\` enriches it. Framework/control work`,
|
|
3640
3662
|
` * outside this generated package is dropped. Returns stop(). */`,
|
|
3641
3663
|
` watchActivity: (cb: (events: ActivityEvent[]) => void, opts?: { base?: string }) =>`,
|
|
3642
|
-
`
|
|
3664
|
+
` _nomosWatchActivity((rows) => cb(rows.map(renderActivity).filter((e): e is ActivityEvent => e !== null)), opts),`,
|
|
3643
3665
|
` /** One-shot: the current committed activity log, folded to typed [ActivityEvent]s. */`,
|
|
3644
3666
|
` activityLog: async (opts?: { base?: string; limit?: number }): Promise<ActivityEvent[]> =>`,
|
|
3645
|
-
` (await
|
|
3667
|
+
` (await _nomosActivityLog(opts)).map(renderActivity).filter((e): e is ActivityEvent => e !== null),`,
|
|
3646
3668
|
` /** THE SELF-DESCRIPTION: root links to exactly six grounded pages — model, current state, rules,`,
|
|
3647
3669
|
` * history, decisions and evidence. The old \`events\` / \`transcript\` fields remain, so this is a`,
|
|
3648
3670
|
` * strict enrichment of the activity narrative. \`section\` selects one page. */`,
|
|
3649
3671
|
` describe: async (opts: { section?: NomosDescribeSection; since?: string; performedBy?: string; category?: string; limit?: number } = {}): Promise<NomosDescription> => {`,
|
|
3650
|
-
` const raw = await
|
|
3672
|
+
` const raw = await _nomosActivityLog(opts.limit !== undefined ? { limit: opts.limit } : {});`,
|
|
3651
3673
|
` let events = raw.map(renderActivity).filter((e): e is ActivityEvent => e !== null);`,
|
|
3652
3674
|
` if (opts.performedBy !== undefined) events = events.filter((e) => e.performedBy === opts.performedBy);`,
|
|
3653
3675
|
` if (opts.category !== undefined) events = events.filter((e) => e.category === opts.category);`,
|
|
@@ -3759,7 +3781,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
3759
3781
|
` * point at it (only directives declaring a ref to the aggregate appear in its story) + a transcript. */`,
|
|
3760
3782
|
` describeHistory: async <TAggregate>(record: NomosRef<TAggregate>, opts: { limit?: number } = {}): Promise<ActivityNarrative> => {`,
|
|
3761
3783
|
` const aggregateId = _nmsReferenceId(record, "", holon.workspace, "record");`,
|
|
3762
|
-
` const raw = await
|
|
3784
|
+
` const raw = await _nomosActivityLog(opts.limit !== undefined ? { limit: opts.limit } : {});`,
|
|
3763
3785
|
` const events = raw.map(renderActivity).filter((e): e is ActivityEvent => e !== null)`,
|
|
3764
3786
|
` .filter((e) => Object.values(e.refs).some((r) => _nmsReferenceData(r).id === aggregateId));`,
|
|
3765
3787
|
` return { events, transcript: _activityTranscript(events) };`,
|
|
@@ -3770,9 +3792,19 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
3770
3792
|
out.push(
|
|
3771
3793
|
`/** Framework binding for the generated application. Law identity, author identity and key custody`,
|
|
3772
3794
|
` * are captured from the connected stateful client; callers cannot override them. */`,
|
|
3773
|
-
|
|
3795
|
+
`/** THE HOLON-LEVEL FACTORY — the headless/code-mode binding (headless-operation.md). Binds`,
|
|
3796
|
+
` * structurally to any NomosHolon: a full \`connect()\` holon, or the thin remote holon`,
|
|
3797
|
+
` * (\`connectRemote\`) for isolates that live beside the edge. The application surface`,
|
|
3798
|
+
` * (\`open<Name>Application\`) remains the batteries-included entry for devices. */`,
|
|
3799
|
+
`${opts.exportHolonFactory === true ? "export " : ""}function ${tsClientFactoryName(domain)}(holon: NomosHolon) {`,
|
|
3774
3800
|
` const domainHash = ${hashConst};`,
|
|
3775
3801
|
` const actor = holon.principal;`,
|
|
3802
|
+
...(activityPresent
|
|
3803
|
+
? [
|
|
3804
|
+
` const _nomosActivityLog = (opts?: { base?: string; limit?: number }) => { if (!holon.activityLog) throw new Error("activity reads need a custody-capable @githolon/client binding"); return holon.activityLog(opts); };`,
|
|
3805
|
+
` const _nomosWatchActivity = (cb: (rows: ActivityRawRow[]) => void, opts?: { base?: string; intervalMs?: number }) => { if (!holon.watchActivity) throw new Error("activity watches need a custody-capable @githolon/client binding"); return holon.watchActivity(cb, opts); };`,
|
|
3806
|
+
]
|
|
3807
|
+
: []),
|
|
3776
3808
|
` holon.prepareDomain?.(${JSON.stringify(domain)}, domainHash);`,
|
|
3777
3809
|
...(encPresent ? [` const keyStore: NomosKeyStore | undefined = ${domScopes.length > 0 ? `{ scopeKey: async (scope) => { if (!holon.__nomosScopeKey) throw new Error("encrypted business data needs the current @githolon/client"); return holon.__nomosScopeKey({ scope }); }, readKeys: async (scopes) => { if (!holon.__nomosScopeKeys) throw new Error("encrypted business data needs the current @githolon/client"); return holon.__nomosScopeKeys({ scopes }); } }` : "undefined"};`] : []),
|
|
3778
3810
|
...(ownedScope !== undefined && Object.keys(ownedActions).length > 0
|
package/src/compat_manifest.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The CLIENT/LAW COMPATIBILITY MANIFEST (
|
|
1
|
+
// The CLIENT/LAW COMPATIBILITY MANIFEST (architecture/shape_compatibility.md).
|
|
2
2
|
//
|
|
3
3
|
// A DIAGNOSIS surface, NEVER a gate. Clients fit workspaces by STRUCTURAL INTERFACE
|
|
4
4
|
// (stable-id subtyping, #72), not by law-hash pin — the hash pin was a bug we removed.
|
|
@@ -1660,8 +1660,8 @@ const NOMOS_TYPES_DEP = "^0.8.7";
|
|
|
1660
1660
|
// 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
|
|
1661
1661
|
// DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
|
|
1662
1662
|
// REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
|
|
1663
|
-
const NOMOS_CLIENT_DEP = "^0.64.
|
|
1664
|
-
const NOMOS_FLUTTER_DEP = "^0.63.
|
|
1663
|
+
const NOMOS_CLIENT_DEP = "^0.64.24";
|
|
1664
|
+
const NOMOS_FLUTTER_DEP = "^0.63.46";
|
|
1665
1665
|
// DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in dsl/dart/pubspec.yaml
|
|
1666
1666
|
// and dsl/dart_types/pubspec.yaml — a Dart release without this bump fails the suite, so tenants never
|
|
1667
1667
|
// land back in dependency_overrides (the exact co2 report this closes, third occurrence).
|
|
@@ -5080,7 +5080,7 @@ async function main(): Promise<void> {
|
|
|
5080
5080
|
identityHashes[module.domain ?? module.name] = hash;
|
|
5081
5081
|
}
|
|
5082
5082
|
|
|
5083
|
-
// ── 3a. the CLIENT/LAW COMPATIBILITY MANIFEST (
|
|
5083
|
+
// ── 3a. the CLIENT/LAW COMPATIBILITY MANIFEST (architecture/shape_compatibility.md).
|
|
5084
5084
|
// A DIAGNOSIS surface, NEVER a gate: the shape-compat arc (#72) established that clients
|
|
5085
5085
|
// fit workspaces by STRUCTURAL INTERFACE (stable-id subtyping), not by law-hash pin. This
|
|
5086
5086
|
// block is baked into both generated clients (TS + Dart) and mirrored into the deploy JSON
|
|
@@ -5253,20 +5253,29 @@ async function main(): Promise<void> {
|
|
|
5253
5253
|
// deployed law's content hash baked in (emitted AFTER the package is hashed) ──
|
|
5254
5254
|
const clientPath = path.join(outDir, `${cfg.name}.client.mjs`);
|
|
5255
5255
|
const clientDeclarationPath = path.join(outDir, `${cfg.name}.client.d.mts`);
|
|
5256
|
-
const
|
|
5256
|
+
const headlessClientPath = path.join(outDir, `${cfg.name}.headless.mjs`);
|
|
5257
|
+
const headlessClientDeclarationPath = path.join(outDir, `${cfg.name}.headless.d.mts`);
|
|
5258
|
+
const clientOptions = {
|
|
5257
5259
|
packageName: cfg.name,
|
|
5258
5260
|
domainHash,
|
|
5259
5261
|
interface: packageInterface,
|
|
5260
5262
|
compat: nomosCompat,
|
|
5261
5263
|
homeDomain: resolveApplicationTopology(presentationDomainModules).homeDomain,
|
|
5264
|
+
} as const;
|
|
5265
|
+
const clientSource = generateTsClient(presentationDomainModules, clientOptions);
|
|
5266
|
+
const headlessClientSource = generateTsClient(presentationDomainModules, {
|
|
5267
|
+
...clientOptions,
|
|
5268
|
+
exportHolonFactory: true,
|
|
5262
5269
|
});
|
|
5263
5270
|
const runtimePath = path.join(DSL_DIR, "runtime", "generated-application-host.js");
|
|
5264
5271
|
if (!existsSync(runtimePath)) {
|
|
5265
5272
|
fail("the compiler-private generated application runtime is missing — reinstall @githolon/dsl");
|
|
5266
5273
|
}
|
|
5267
5274
|
const temporaryClientPath = path.join(outDir, `.${cfg.name}.client.source.ts`);
|
|
5275
|
+
const temporaryHeadlessClientPath = path.join(outDir, `.${cfg.name}.headless.source.ts`);
|
|
5268
5276
|
const temporaryRuntimeTypesPath = path.join(outDir, `.${cfg.name}.application-runtime.d.ts`);
|
|
5269
5277
|
const temporaryDeclarationPath = temporaryClientPath.replace(/\.ts$/, ".d.ts");
|
|
5278
|
+
const temporaryHeadlessDeclarationPath = temporaryHeadlessClientPath.replace(/\.ts$/, ".d.ts");
|
|
5270
5279
|
const runtimeTypesSource = [
|
|
5271
5280
|
`export declare function nomosInternalOpenGeneratedApplication<T extends object>(`,
|
|
5272
5281
|
` options: { cloud: string; application: string; signIn: (request?: { readonly deviceChallenge?: string }) => Promise<string>; onPerformance?: (sample: { readonly operation: "open" | "offer" | "birth" | "read" | "sync" | "maintenance"; readonly name: string; readonly durationMs: number; readonly domain?: string; readonly directive?: string }) => void; lawHash: string; readyCommands?: readonly { readonly domain: string; readonly packageHash: string; readonly directiveId: string }[] },`,
|
|
@@ -5306,6 +5315,8 @@ async function main(): Promise<void> {
|
|
|
5306
5315
|
.update(`${declarationProofFormat}\0`)
|
|
5307
5316
|
.update(normalizedClientSource)
|
|
5308
5317
|
.update("\0")
|
|
5318
|
+
.update(headlessClientSource.replaceAll(domainHash, "<nomos-composed-law-identity>"))
|
|
5319
|
+
.update("\0")
|
|
5309
5320
|
.update(runtimeTypesSource)
|
|
5310
5321
|
.update("\0")
|
|
5311
5322
|
.update(ts.version)
|
|
@@ -5313,25 +5324,37 @@ async function main(): Promise<void> {
|
|
|
5313
5324
|
.update(readFileSync(fileURLToPath(import.meta.url)))
|
|
5314
5325
|
.digest("hex");
|
|
5315
5326
|
const declarationCacheDir = path.join(cfgDir, "node_modules", ".cache", "nomos", "typescript-declarations");
|
|
5316
|
-
const declarationCachePath = path.join(declarationCacheDir, `${declarationInputIdentity}.d.mts`);
|
|
5317
|
-
const
|
|
5318
|
-
const
|
|
5319
|
-
|
|
5327
|
+
const declarationCachePath = path.join(declarationCacheDir, `${declarationInputIdentity}.client.d.mts`);
|
|
5328
|
+
const headlessDeclarationCachePath = path.join(declarationCacheDir, `${declarationInputIdentity}.headless.d.mts`);
|
|
5329
|
+
const declarationCacheProofPath = path.join(declarationCacheDir, `${declarationInputIdentity}.client.proof.usda`);
|
|
5330
|
+
const headlessDeclarationCacheProofPath = path.join(declarationCacheDir, `${declarationInputIdentity}.headless.proof.usda`);
|
|
5331
|
+
const declarationProofKey = (artifact: "client" | "headless", bytes: Buffer): string => createHash("sha256")
|
|
5332
|
+
.update(`${declarationProofFormat}\0${declarationInputIdentity}\0${artifact}\0`)
|
|
5320
5333
|
.update(bytes)
|
|
5321
5334
|
.digest("hex");
|
|
5322
5335
|
const cachedDeclaration = existsSync(declarationCachePath) ? readFileSync(declarationCachePath) : undefined;
|
|
5336
|
+
const cachedHeadlessDeclaration = existsSync(headlessDeclarationCachePath)
|
|
5337
|
+
? readFileSync(headlessDeclarationCachePath)
|
|
5338
|
+
: undefined;
|
|
5323
5339
|
const declarationCacheHit = cachedDeclaration !== undefined &&
|
|
5340
|
+
cachedHeadlessDeclaration !== undefined &&
|
|
5324
5341
|
declarationProofCodec.readKeyProofUsda(declarationCacheProofPath, declarationProofFormat) ===
|
|
5325
|
-
declarationProofKey(cachedDeclaration)
|
|
5326
|
-
|
|
5342
|
+
declarationProofKey("client", cachedDeclaration) &&
|
|
5343
|
+
declarationProofCodec.readKeyProofUsda(headlessDeclarationCacheProofPath, declarationProofFormat) ===
|
|
5344
|
+
declarationProofKey("headless", cachedHeadlessDeclaration);
|
|
5345
|
+
if (declarationCacheHit) {
|
|
5346
|
+
copyFileSync(declarationCachePath, clientDeclarationPath);
|
|
5347
|
+
copyFileSync(headlessDeclarationCachePath, headlessClientDeclarationPath);
|
|
5348
|
+
}
|
|
5327
5349
|
try {
|
|
5328
5350
|
if (!declarationCacheHit) {
|
|
5329
5351
|
writeFileSync(temporaryClientPath, clientSource, "utf8");
|
|
5352
|
+
writeFileSync(temporaryHeadlessClientPath, headlessClientSource, "utf8");
|
|
5330
5353
|
writeFileSync(temporaryRuntimeTypesPath, runtimeTypesSource, "utf8");
|
|
5331
5354
|
const typescriptEntry = createRequire(import.meta.url).resolve("typescript/lib/typescript.js");
|
|
5332
5355
|
const ts = (await import(pathToFileURL(typescriptEntry).href)) as typeof import("typescript");
|
|
5333
5356
|
const program = ts.createProgram(
|
|
5334
|
-
[temporaryClientPath, temporaryRuntimeTypesPath],
|
|
5357
|
+
[temporaryClientPath, temporaryHeadlessClientPath, temporaryRuntimeTypesPath],
|
|
5335
5358
|
{
|
|
5336
5359
|
target: ts.ScriptTarget.ES2022,
|
|
5337
5360
|
module: ts.ModuleKind.NodeNext,
|
|
@@ -5351,7 +5374,10 @@ async function main(): Promise<void> {
|
|
|
5351
5374
|
);
|
|
5352
5375
|
const emit = program.emit();
|
|
5353
5376
|
const diagnostics = [...ts.getPreEmitDiagnostics(program), ...emit.diagnostics];
|
|
5354
|
-
if (
|
|
5377
|
+
if (
|
|
5378
|
+
diagnostics.length > 0 || emit.emitSkipped ||
|
|
5379
|
+
!existsSync(temporaryDeclarationPath) || !existsSync(temporaryHeadlessDeclarationPath)
|
|
5380
|
+
) {
|
|
5355
5381
|
const detail = diagnostics.map((diagnostic) => {
|
|
5356
5382
|
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
|
|
5357
5383
|
if (diagnostic.file === undefined || diagnostic.start === undefined) return message;
|
|
@@ -5497,14 +5523,23 @@ async function main(): Promise<void> {
|
|
|
5497
5523
|
].join("\n");
|
|
5498
5524
|
writeFileSync(temporaryDeclarationPath, declarationSource, "utf8");
|
|
5499
5525
|
renameSync(temporaryDeclarationPath, clientDeclarationPath);
|
|
5526
|
+
renameSync(temporaryHeadlessDeclarationPath, headlessClientDeclarationPath);
|
|
5500
5527
|
mkdirSync(declarationCacheDir, { recursive: true });
|
|
5501
5528
|
const declarationCacheTemporaryPath = `${declarationCachePath}.tmp-${process.pid}`;
|
|
5529
|
+
const headlessDeclarationCacheTemporaryPath = `${headlessDeclarationCachePath}.tmp-${process.pid}`;
|
|
5502
5530
|
copyFileSync(clientDeclarationPath, declarationCacheTemporaryPath);
|
|
5531
|
+
copyFileSync(headlessClientDeclarationPath, headlessDeclarationCacheTemporaryPath);
|
|
5503
5532
|
renameSync(declarationCacheTemporaryPath, declarationCachePath);
|
|
5533
|
+
renameSync(headlessDeclarationCacheTemporaryPath, headlessDeclarationCachePath);
|
|
5504
5534
|
declarationProofCodec.writeKeyProofUsda(
|
|
5505
5535
|
declarationCacheProofPath,
|
|
5506
5536
|
declarationProofFormat,
|
|
5507
|
-
declarationProofKey(readFileSync(declarationCachePath)),
|
|
5537
|
+
declarationProofKey("client", readFileSync(declarationCachePath)),
|
|
5538
|
+
);
|
|
5539
|
+
declarationProofCodec.writeKeyProofUsda(
|
|
5540
|
+
headlessDeclarationCacheProofPath,
|
|
5541
|
+
declarationProofFormat,
|
|
5542
|
+
declarationProofKey("headless", readFileSync(headlessDeclarationCachePath)),
|
|
5508
5543
|
);
|
|
5509
5544
|
}
|
|
5510
5545
|
await esbuildMod.build({
|
|
@@ -5540,12 +5575,44 @@ async function main(): Promise<void> {
|
|
|
5540
5575
|
) {
|
|
5541
5576
|
fail("generated TypeScript application did not seal its private runtime binding");
|
|
5542
5577
|
}
|
|
5578
|
+
await esbuildMod.build({
|
|
5579
|
+
stdin: {
|
|
5580
|
+
contents: headlessClientSource,
|
|
5581
|
+
loader: "ts",
|
|
5582
|
+
resolveDir: outDir,
|
|
5583
|
+
sourcefile: `${cfg.name}.headless.ts`,
|
|
5584
|
+
},
|
|
5585
|
+
outfile: headlessClientPath,
|
|
5586
|
+
bundle: true,
|
|
5587
|
+
packages: "bundle",
|
|
5588
|
+
platform: "node",
|
|
5589
|
+
format: "esm",
|
|
5590
|
+
target: "node20",
|
|
5591
|
+
legalComments: "none",
|
|
5592
|
+
logLevel: "silent",
|
|
5593
|
+
plugins: [{
|
|
5594
|
+
name: "nomos-generated-headless-runtime",
|
|
5595
|
+
setup(build) {
|
|
5596
|
+
build.onResolve(
|
|
5597
|
+
{ filter: /^@githolon\/client\/_generated_application_host_7c1f5e$/ },
|
|
5598
|
+
() => ({ path: runtimePath }),
|
|
5599
|
+
);
|
|
5600
|
+
},
|
|
5601
|
+
}],
|
|
5602
|
+
});
|
|
5603
|
+
const headlessBundled = canonicalizeGeneratedClientJavascript(readFileSync(headlessClientPath, "utf8"));
|
|
5604
|
+
writeFileSync(headlessClientPath, headlessBundled, "utf8");
|
|
5605
|
+
if (!new RegExp(`export\\s*\\{[^}]*${tsClientFactoryName(resolveApplicationTopology(presentationDomainModules).homeDomain)}`, "s").test(headlessBundled)) {
|
|
5606
|
+
fail("generated headless application omitted its structural holon factory");
|
|
5607
|
+
}
|
|
5543
5608
|
} catch (e) {
|
|
5544
5609
|
fail(`generated TypeScript application could not be sealed — this is a Nomos codegen bug: ${(e as Error).message}`);
|
|
5545
5610
|
} finally {
|
|
5546
5611
|
rmSync(temporaryClientPath, { force: true });
|
|
5612
|
+
rmSync(temporaryHeadlessClientPath, { force: true });
|
|
5547
5613
|
rmSync(temporaryRuntimeTypesPath, { force: true });
|
|
5548
5614
|
rmSync(temporaryDeclarationPath, { force: true });
|
|
5615
|
+
rmSync(temporaryHeadlessDeclarationPath, { force: true });
|
|
5549
5616
|
rmSync(path.join(outDir, `${cfg.name}.client.ts`), { force: true });
|
|
5550
5617
|
}
|
|
5551
5618
|
|
|
@@ -5903,6 +5970,7 @@ async function main(): Promise<void> {
|
|
|
5903
5970
|
}
|
|
5904
5971
|
}
|
|
5905
5972
|
console.log(` client ${rel(clientPath)} (typed TS client — payloads, read models, query accessors)`);
|
|
5973
|
+
console.log(` headless ${rel(headlessClientPath)} + ${rel(headlessClientDeclarationPath)} (typed business binding for code-mode/edge holons)`);
|
|
5906
5974
|
for (const note of recursiveNotes) console.log(` note ${note}`);
|
|
5907
5975
|
console.log(` openapi served by the holon from installed USDA (GET /v2/workspaces/<ws>/openapi.json) — no build-time copy to drift`);
|
|
5908
5976
|
if (proofPath !== undefined) {
|
package/src/framework/home.ts
CHANGED
|
@@ -96,12 +96,11 @@ export const ReceivingGroup = aggregate("NomosInternalReceivingGroup7c1f5e", {
|
|
|
96
96
|
* and secure: a live, verified device may add or revoke another device; keyless IdP enrolment is only the
|
|
97
97
|
* pre-group bootstrap lane.
|
|
98
98
|
*
|
|
99
|
-
* ALL-DEVICE-LOSS RECOVERY —
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* the policy fields that design reads.
|
|
99
|
+
* ALL-DEVICE-LOSS RECOVERY — CO2 already recovers through its platform AuthProvider, but the implementation
|
|
100
|
+
* reaches that result by repeating birth after this home's direct enrollment policy refuses. The corrected
|
|
101
|
+
* design (`architecture/guardian_device_recovery.md`, reconciled 2026-08-10) makes recovery authority an
|
|
102
|
+
* explicit tenant-selected policy: platform identity provider or guardian quorum. The missing work is to
|
|
103
|
+
* evaluate that choice at the existing child's frontier and delete the client-side birth-repeat bypass.
|
|
105
104
|
*/
|
|
106
105
|
export const ReceivingGroupPolicy = aggregate("NomosInternalReceivingGroupPolicy7c1f5e", {
|
|
107
106
|
additionalDevice: t.string().merge(Lww),
|
|
@@ -207,15 +206,15 @@ const receivingGroupPolicyRefs = readSet(
|
|
|
207
206
|
);
|
|
208
207
|
|
|
209
208
|
/** An IdP token may bootstrap the parent before the group exists; it may never bypass an existing group's
|
|
210
|
-
* device policy.
|
|
209
|
+
* device policy. An existing group chooses whether recovery is delegated to its platform AuthProvider or to
|
|
210
|
+
* guardians; the current default below says guardian, while CO2's current product behaviour is platform IdP.
|
|
211
211
|
*
|
|
212
212
|
* KNOWN GAP (`architecture/guardian_device_recovery.md`): this invariant is declared `.on("enrollFirstDevice")`
|
|
213
213
|
* and only fires when that directive targets THIS home directly. It is NOT re-applied when the birth
|
|
214
214
|
* offer-effect (`run_birth_effect`, repeating `nomosInternalBirthReceivingGroup` against an ALREADY-founded
|
|
215
|
-
* home) folds a signer seat via the SAME underlying mechanism
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
* roster); do not paper over it in the DSL only. */
|
|
215
|
+
* home) folds a signer seat via the SAME underlying mechanism. That is how CO2 recovery works today, but its
|
|
216
|
+
* home frontier still says `guardian-quorum`. The fix is not to ban platform recovery: add the explicit
|
|
217
|
+
* platform-identity-provider policy, make CO2 choose it, and have the child gate verify the chosen evidence. */
|
|
219
218
|
export const receivingGroupRejectsKeylessAdditionalDevice = workspaceInvariant("ReceivingGroupRejectsKeylessAdditionalDevice")
|
|
220
219
|
.on("enrollFirstDevice")
|
|
221
220
|
.reads(receivingGroupPolicyRefs)
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
// Tenant code sees only generated `devices.request()` / `devices.approve(request)`.
|
|
3
3
|
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { OWNED_KEY_RELATION, OWNED_READ_RELATION, aggregate, instance, ownedKeyObject, type AggregateHandle } from "../aggregate.js";
|
|
5
|
+
import { OWNED_KEY_RELATION, OWNED_PRINCIPAL_OBJECT_TYPE, OWNED_READ_RELATION, aggregate, instance, ownedKeyObject, type AggregateHandle } from "../aggregate.js";
|
|
6
6
|
import { Lww } from "../drivers.js";
|
|
7
7
|
import { directive } from "../directive.js";
|
|
8
8
|
import { endorsementOf } from "../principal_evidence.js";
|
|
9
|
-
import { isRelationTypeDecl } from "../relation_schema.js";
|
|
9
|
+
import { isRelationTypeDecl, key, relationType, self, user } from "../relation_schema.js";
|
|
10
10
|
import { t } from "../fields.js";
|
|
11
11
|
import { set, withMarker } from "../ops.js";
|
|
12
12
|
import { query } from "../query.js";
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
writeWrap,
|
|
20
20
|
} from "./encryption_scope.js";
|
|
21
21
|
import { GenesisWarrant, seedGenesis } from "./genesis.js";
|
|
22
|
+
import { BirthAttestation, seedBirthAttestation } from "./home.js";
|
|
22
23
|
import { RelationTuple, __qualifyAggregateType, removeTuple, writeTuple } from "./relations.js";
|
|
23
24
|
import { SIGNER_RELATION, enrollSigner, signerObject, identityRelations } from "./signing_identity.js";
|
|
24
25
|
import { impureCapability, impureCapabilityAggregate } from "./impure_capability.js";
|
|
@@ -27,6 +28,10 @@ import { impureCapability, impureCapabilityAggregate } from "./impure_capability
|
|
|
27
28
|
export const OWNED_DEVICE_APPROVAL_DIRECTIVE = "nomos_internal_approve_owned_device_7c1f5e";
|
|
28
29
|
export const OWNED_INITIALIZE_DIRECTIVE = "nomos_internal_initialize_owned_identity_7c1f5e";
|
|
29
30
|
export const OWNED_RECOVERY_RELATION = "nomos_internal_owned_recovery_principal_7c1f5e";
|
|
31
|
+
export const ownedCustodyRelations = relationType(OWNED_PRINCIPAL_OBJECT_TYPE)
|
|
32
|
+
.asGlobal()
|
|
33
|
+
.relation(OWNED_KEY_RELATION, { rewrite: self(), allow: [key()] })
|
|
34
|
+
.relation(OWNED_RECOVERY_RELATION, { rewrite: self(), allow: [user()] });
|
|
30
35
|
export const OWNED_RECOVERY_PROTECT_DIRECTIVE = "nomos_internal_protect_owned_identity_7c1f5e";
|
|
31
36
|
export const OWNED_RECOVERY_REQUEST_DIRECTIVE = "nomos_internal_request_owned_recovery_7c1f5e";
|
|
32
37
|
export const OWNED_RECOVERY_COMPLETE_DIRECTIVE = "nomos_internal_complete_owned_recovery_7c1f5e";
|
|
@@ -109,7 +114,33 @@ const ownedRecoveryLifecycle = impureCapability({
|
|
|
109
114
|
writeReceipt: (id, r) => [
|
|
110
115
|
set(instance(OwnedRecoveryOrder, id), "ownedCustody", r.ownedCustody as string),
|
|
111
116
|
],
|
|
117
|
+
// This is intentionally unaddressed: opening the designated principal's ordinary generated
|
|
118
|
+
// client is the provider. Declaring the capability still matters because every durable Order must
|
|
119
|
+
// have one typed provider contract; the kernel may then retain the sealed obligation locally until
|
|
120
|
+
// that principal opens the workspace, rather than inventing an untyped host work description.
|
|
121
|
+
capability: "nomos.owned-recovery",
|
|
112
122
|
});
|
|
123
|
+
const hiddenRecoveryLifecycle = {
|
|
124
|
+
aggregate: hidden(OwnedRecoveryOrder),
|
|
125
|
+
obligation: hidden(ownedRecoveryLifecycle.obligation),
|
|
126
|
+
complete: hidden(ownedRecoveryLifecycle.complete),
|
|
127
|
+
fail: hidden(ownedRecoveryLifecycle.fail),
|
|
128
|
+
block: hidden(ownedRecoveryLifecycle.block),
|
|
129
|
+
deadLetter: hidden(ownedRecoveryLifecycle.deadLetter),
|
|
130
|
+
};
|
|
131
|
+
const ownedRecoveryCapability = {
|
|
132
|
+
...ownedRecoveryLifecycle,
|
|
133
|
+
// Capability expansion deliberately re-seats the lifecycle quartet after application-language
|
|
134
|
+
// closure. Carry the private declarations there too; pointing back at the raw impure-capability
|
|
135
|
+
// declarations silently reintroduced framework verbs into proof selection and generated names.
|
|
136
|
+
aggregate: hiddenRecoveryLifecycle.aggregate,
|
|
137
|
+
obligation: hiddenRecoveryLifecycle.obligation,
|
|
138
|
+
complete: hiddenRecoveryLifecycle.complete,
|
|
139
|
+
fail: hiddenRecoveryLifecycle.fail,
|
|
140
|
+
block: hiddenRecoveryLifecycle.block,
|
|
141
|
+
deadLetter: hiddenRecoveryLifecycle.deadLetter,
|
|
142
|
+
__isCapabilityDecl: true as const,
|
|
143
|
+
};
|
|
113
144
|
|
|
114
145
|
export const ownedRecoveryOrdersByRecoveryPrincipal = query("nomosOwnedRecoveryOrdersByRecoveryPrincipal")
|
|
115
146
|
.key("recoveryPrincipal")
|
|
@@ -157,7 +188,7 @@ const protectOwnedIdentity = Object.assign(
|
|
|
157
188
|
.alsoEnsures(RelationTuple)
|
|
158
189
|
.alsoEnsures(KeyScope)
|
|
159
190
|
.alsoEnsures(WrappedKey),
|
|
160
|
-
{ businessBirthInternal: true as const },
|
|
191
|
+
{ businessBirthInternal: true as const, ownedAuthoringInternal: true as const },
|
|
161
192
|
);
|
|
162
193
|
|
|
163
194
|
const acceptOwnedRecovery = Object.assign(
|
|
@@ -181,18 +212,9 @@ const acceptOwnedRecovery = Object.assign(
|
|
|
181
212
|
ownedCustody: p.ownedCustody,
|
|
182
213
|
});
|
|
183
214
|
}),
|
|
184
|
-
{ businessBirthInternal: true as const },
|
|
215
|
+
{ businessBirthInternal: true as const, ownedAuthoringInternal: true as const },
|
|
185
216
|
);
|
|
186
217
|
|
|
187
|
-
const hiddenRecoveryLifecycle = {
|
|
188
|
-
aggregate: hidden(OwnedRecoveryOrder),
|
|
189
|
-
obligation: hidden(ownedRecoveryLifecycle.obligation),
|
|
190
|
-
complete: hidden(ownedRecoveryLifecycle.complete),
|
|
191
|
-
fail: hidden(ownedRecoveryLifecycle.fail),
|
|
192
|
-
block: hidden(ownedRecoveryLifecycle.block),
|
|
193
|
-
deadLetter: hidden(ownedRecoveryLifecycle.deadLetter),
|
|
194
|
-
};
|
|
195
|
-
|
|
196
218
|
export type OwnedRecordAction = "share" | "unshare" | "transfer";
|
|
197
219
|
|
|
198
220
|
/** One collision-resistant framework id per aggregate/action. Tenant code never sees these ids. */
|
|
@@ -256,6 +278,7 @@ const approval = directive(OWNED_DEVICE_APPROVAL_DIRECTIVE)
|
|
|
256
278
|
/** Branded internal so ordinary directive codegen never exposes its cryptographic payload. */
|
|
257
279
|
export const approveOwnedDevice = Object.assign(approval, {
|
|
258
280
|
businessBirthInternal: true as const,
|
|
281
|
+
ownedAuthoringInternal: true as const,
|
|
259
282
|
});
|
|
260
283
|
|
|
261
284
|
/** Establish this principal's shared owned-data root before another principal shares to them. */
|
|
@@ -281,7 +304,7 @@ const initializeOwnedIdentity = Object.assign(
|
|
|
281
304
|
ownedCustody: p.ownedCustody,
|
|
282
305
|
});
|
|
283
306
|
}),
|
|
284
|
-
{ businessBirthInternal: true as const },
|
|
307
|
+
{ businessBirthInternal: true as const, ownedAuthoringInternal: true as const },
|
|
285
308
|
);
|
|
286
309
|
|
|
287
310
|
function recordScope(aggregate: AggregateHandle, recordId: string): string {
|
|
@@ -295,7 +318,10 @@ function recordObject(aggregate: AggregateHandle, recordId: string): string {
|
|
|
295
318
|
function ownedRecordActionDirectives(aggregate: AggregateHandle) {
|
|
296
319
|
const share = Object.assign(
|
|
297
320
|
directive(ownedRecordActionDirectiveId("share", aggregate.id))
|
|
298
|
-
.
|
|
321
|
+
// The owned business record is the semantic target. Relation tuples and wraps are framework
|
|
322
|
+
// effects of sharing it; making a wrap the primary target causes authenticated law to describe
|
|
323
|
+
// this as a key action and the kernel correctly refuses `share(record, person)`.
|
|
324
|
+
.mutates(aggregate)
|
|
299
325
|
.payload(z.object({
|
|
300
326
|
recordId: z.string().min(1),
|
|
301
327
|
peer: principal,
|
|
@@ -313,13 +339,14 @@ function ownedRecordActionDirectives(aggregate: AggregateHandle) {
|
|
|
313
339
|
}),
|
|
314
340
|
...p.wraps.flatMap((entry) => writeWrap(recordScope(aggregate, p.recordId), entry.epoch, entry)),
|
|
315
341
|
])
|
|
316
|
-
.alsoEnsures(RelationTuple)
|
|
317
|
-
|
|
342
|
+
.alsoEnsures(RelationTuple)
|
|
343
|
+
.alsoEnsuresMany(WrappedKey),
|
|
344
|
+
{ businessBirthInternal: true as const, ownedAuthoringInternal: true as const },
|
|
318
345
|
);
|
|
319
346
|
|
|
320
347
|
const unshare = Object.assign(
|
|
321
348
|
directive(ownedRecordActionDirectiveId("unshare", aggregate.id))
|
|
322
|
-
.
|
|
349
|
+
.mutates(aggregate)
|
|
323
350
|
.payload(z.object({
|
|
324
351
|
recordId: z.string().min(1),
|
|
325
352
|
peer: principal,
|
|
@@ -340,14 +367,15 @@ function ownedRecordActionDirectives(aggregate: AggregateHandle) {
|
|
|
340
367
|
...writeKeyEpoch(recordScope(aggregate, p.recordId), p.epoch, p.changedAt, p.wraps),
|
|
341
368
|
])
|
|
342
369
|
.alsoEnsures(RelationTuple)
|
|
370
|
+
.alsoEnsures(KeyScope)
|
|
343
371
|
.alsoEnsuresMany(WrappedKey),
|
|
344
|
-
{ businessBirthInternal: true as const },
|
|
372
|
+
{ businessBirthInternal: true as const, ownedAuthoringInternal: true as const },
|
|
345
373
|
);
|
|
346
374
|
|
|
347
375
|
const ownerField = aggregate.audience?.kind === "owned" ? aggregate.audience.ownerField : "";
|
|
348
376
|
const transfer = Object.assign(
|
|
349
377
|
directive(ownedRecordActionDirectiveId("transfer", aggregate.id))
|
|
350
|
-
.
|
|
378
|
+
.mutates(aggregate)
|
|
351
379
|
.payload(z.object({
|
|
352
380
|
recordId: z.string().min(1),
|
|
353
381
|
newOwner: principal,
|
|
@@ -383,9 +411,9 @@ function ownedRecordActionDirectives(aggregate: AggregateHandle) {
|
|
|
383
411
|
return ops;
|
|
384
412
|
})
|
|
385
413
|
.alsoEnsuresMany(RelationTuple)
|
|
386
|
-
.
|
|
414
|
+
.alsoEnsures(KeyScope)
|
|
387
415
|
.alsoEnsuresMany(WrappedKey),
|
|
388
|
-
{ businessBirthInternal: true as const },
|
|
416
|
+
{ businessBirthInternal: true as const, ownedAuthoringInternal: true as const },
|
|
389
417
|
);
|
|
390
418
|
|
|
391
419
|
return { share, unshare, transfer };
|
|
@@ -393,8 +421,11 @@ function ownedRecordActionDirectives(aggregate: AggregateHandle) {
|
|
|
393
421
|
|
|
394
422
|
const APPROVAL_EXPORT = "__nomos_owned_device_approval_7c1f5e";
|
|
395
423
|
const IDENTITY_EXPORT = "__nomos_owned_identity_relations_7c1f5e";
|
|
424
|
+
const CUSTODY_RELATIONS_EXPORT = "__nomos_owned_custody_relations_7c1f5e";
|
|
396
425
|
const WARRANT_EXPORT = "__nomos_owned_genesis_warrant_7c1f5e";
|
|
397
426
|
const SEED_EXPORT = "__nomos_owned_seed_genesis_7c1f5e";
|
|
427
|
+
const BIRTH_ATTESTATION_EXPORT = "__nomos_owned_birth_attestation_7c1f5e";
|
|
428
|
+
const BIRTH_ATTESTATION_SEED_EXPORT = "__nomos_owned_seed_birth_attestation_7c1f5e";
|
|
398
429
|
const ENROLL_EXPORT = "__nomos_owned_enroll_signer_7c1f5e";
|
|
399
430
|
const INITIALIZE_EXPORT = "__nomos_owned_initialize_identity_7c1f5e";
|
|
400
431
|
const KEY_SCOPE_EXPORT = "__nomos_owned_key_scope_7c1f5e";
|
|
@@ -411,11 +442,15 @@ const RECOVERY_FAIL_EXPORT = "__nomos_owned_recovery_fail_7c1f5e";
|
|
|
411
442
|
const RECOVERY_BLOCK_EXPORT = "__nomos_owned_recovery_block_7c1f5e";
|
|
412
443
|
const RECOVERY_DEAD_LETTER_EXPORT = "__nomos_owned_recovery_dead_letter_7c1f5e";
|
|
413
444
|
const RECOVERY_QUERY_EXPORT = "__nomos_owned_recovery_query_7c1f5e";
|
|
445
|
+
const RECOVERY_CAPABILITY_EXPORT = "__nomos_owned_recovery_capability_7c1f5e";
|
|
414
446
|
|
|
415
447
|
const ownedGenesisWarrant = hidden(GenesisWarrant);
|
|
416
448
|
const ownedSeedGenesis = hidden(seedGenesis);
|
|
449
|
+
const ownedBirthAttestation = hidden(BirthAttestation);
|
|
450
|
+
const ownedSeedBirthAttestation = hidden(seedBirthAttestation);
|
|
417
451
|
const ownedEnrollSigner = hidden(enrollSigner);
|
|
418
452
|
const ownedIdentityRelations = hidden(identityRelations);
|
|
453
|
+
const ownedCustodyRelationLaw = hidden(ownedCustodyRelations);
|
|
419
454
|
|
|
420
455
|
/**
|
|
421
456
|
* The published CLI bundles its compiler while a tenant module imports declarations from the separately
|
|
@@ -476,6 +511,7 @@ export function withOwnedCustodyExports<M extends Record<string, unknown>>(mod:
|
|
|
476
511
|
[RECOVERY_BLOCK_EXPORT, hiddenRecoveryLifecycle.block],
|
|
477
512
|
[RECOVERY_DEAD_LETTER_EXPORT, hiddenRecoveryLifecycle.deadLetter],
|
|
478
513
|
[RECOVERY_QUERY_EXPORT, hidden(ownedRecoveryOrdersByRecoveryPrincipal)],
|
|
514
|
+
[RECOVERY_CAPABILITY_EXPORT, hidden(ownedRecoveryCapability)],
|
|
479
515
|
] as const;
|
|
480
516
|
|
|
481
517
|
// These names are deliberately obscure, not magical. A tenant can still reach one accidentally or
|
|
@@ -489,6 +525,10 @@ export function withOwnedCustodyExports<M extends Record<string, unknown>>(mod:
|
|
|
489
525
|
&& !sameFrameworkDeclaration(mod[IDENTITY_EXPORT], ownedIdentityRelations)) {
|
|
490
526
|
reservedCollision(IDENTITY_EXPORT);
|
|
491
527
|
}
|
|
528
|
+
if (Object.prototype.hasOwnProperty.call(mod, CUSTODY_RELATIONS_EXPORT)
|
|
529
|
+
&& !sameFrameworkDeclaration(mod[CUSTODY_RELATIONS_EXPORT], ownedCustodyRelationLaw)) {
|
|
530
|
+
reservedCollision(CUSTODY_RELATIONS_EXPORT);
|
|
531
|
+
}
|
|
492
532
|
for (const [key, generated] of recoveryInjections) {
|
|
493
533
|
if (Object.prototype.hasOwnProperty.call(mod, key) && !sameFrameworkDeclaration(mod[key], generated)) {
|
|
494
534
|
reservedCollision(key);
|
|
@@ -497,6 +537,8 @@ export function withOwnedCustodyExports<M extends Record<string, unknown>>(mod:
|
|
|
497
537
|
for (const [key, generated] of [
|
|
498
538
|
[WARRANT_EXPORT, ownedGenesisWarrant],
|
|
499
539
|
[SEED_EXPORT, ownedSeedGenesis],
|
|
540
|
+
[BIRTH_ATTESTATION_EXPORT, ownedBirthAttestation],
|
|
541
|
+
[BIRTH_ATTESTATION_SEED_EXPORT, ownedSeedBirthAttestation],
|
|
500
542
|
[ENROLL_EXPORT, ownedEnrollSigner],
|
|
501
543
|
] as const) {
|
|
502
544
|
if (Object.prototype.hasOwnProperty.call(mod, key) && !sameFrameworkDeclaration(mod[key], generated)) {
|
|
@@ -526,6 +568,8 @@ export function withOwnedCustodyExports<M extends Record<string, unknown>>(mod:
|
|
|
526
568
|
for (const [framework, generated] of [
|
|
527
569
|
[GenesisWarrant, ownedGenesisWarrant],
|
|
528
570
|
[seedGenesis, ownedSeedGenesis],
|
|
571
|
+
[BirthAttestation, ownedBirthAttestation],
|
|
572
|
+
[seedBirthAttestation, ownedSeedBirthAttestation],
|
|
529
573
|
[enrollSigner, ownedEnrollSigner],
|
|
530
574
|
] as const) {
|
|
531
575
|
if (
|
|
@@ -546,6 +590,14 @@ export function withOwnedCustodyExports<M extends Record<string, unknown>>(mod:
|
|
|
546
590
|
) {
|
|
547
591
|
reservedCollision(`global relation type '${identityRelations.type}'`);
|
|
548
592
|
}
|
|
593
|
+
if (
|
|
594
|
+
!sameFrameworkDeclaration(value, ownedCustodyRelationLaw)
|
|
595
|
+
&& isRelationTypeDecl(value)
|
|
596
|
+
&& value.global === true
|
|
597
|
+
&& value.type === ownedCustodyRelations.type
|
|
598
|
+
) {
|
|
599
|
+
reservedCollision(`global relation type '${ownedCustodyRelations.type}'`);
|
|
600
|
+
}
|
|
549
601
|
if (value !== null && typeof value === "object" && typeof (value as { id?: unknown }).id === "string") {
|
|
550
602
|
const id = (value as { id: string }).id;
|
|
551
603
|
const generated = actions.flatMap((entry) => [entry.share, entry.unshare, entry.transfer]);
|
|
@@ -560,6 +612,7 @@ export function withOwnedCustodyExports<M extends Record<string, unknown>>(mod:
|
|
|
560
612
|
[APPROVAL_EXPORT]: approveOwnedDevice,
|
|
561
613
|
[INITIALIZE_EXPORT]: initializeOwnedIdentity,
|
|
562
614
|
[IDENTITY_EXPORT]: ownedIdentityRelations,
|
|
615
|
+
[CUSTODY_RELATIONS_EXPORT]: ownedCustodyRelationLaw,
|
|
563
616
|
[KEY_SCOPE_EXPORT]: hidden(KeyScope),
|
|
564
617
|
[WRAPPED_KEY_EXPORT]: hidden(WrappedKey),
|
|
565
618
|
[KEY_SCOPE_QUERY_EXPORT]: hidden(keyScopeByScope),
|
|
@@ -574,11 +627,14 @@ export function withOwnedCustodyExports<M extends Record<string, unknown>>(mod:
|
|
|
574
627
|
[RECOVERY_BLOCK_EXPORT]: hiddenRecoveryLifecycle.block,
|
|
575
628
|
[RECOVERY_DEAD_LETTER_EXPORT]: hiddenRecoveryLifecycle.deadLetter,
|
|
576
629
|
[RECOVERY_QUERY_EXPORT]: hidden(ownedRecoveryOrdersByRecoveryPrincipal),
|
|
630
|
+
[RECOVERY_CAPABILITY_EXPORT]: hidden(ownedRecoveryCapability),
|
|
577
631
|
// A local law test starts from an empty root, while production business state is normally born into a
|
|
578
632
|
// warranted child. Keep the same lawful bootstrap machinery available in both cases, but framework-only:
|
|
579
633
|
// the harness can seat its test identity and warrant the root without adding a tenant-facing lane.
|
|
580
634
|
[WARRANT_EXPORT]: ownedGenesisWarrant,
|
|
581
635
|
[SEED_EXPORT]: ownedSeedGenesis,
|
|
636
|
+
[BIRTH_ATTESTATION_EXPORT]: ownedBirthAttestation,
|
|
637
|
+
[BIRTH_ATTESTATION_SEED_EXPORT]: ownedSeedBirthAttestation,
|
|
582
638
|
[ENROLL_EXPORT]: ownedEnrollSigner,
|
|
583
639
|
};
|
|
584
640
|
for (const { aggregate, share, unshare, transfer } of actions) {
|
|
@@ -104,7 +104,7 @@ export { enrollFirstDevice, revokeSigner } from "./signing_identity.js";
|
|
|
104
104
|
// (this trio) but DELIBERATELY does NOT export the `parentAttestedBirth` era marker — a platform's own 1-link
|
|
105
105
|
// `[platform_cert]` chain would fail `verify_to_root` (>=2 links), so its own verify doors must stay inert
|
|
106
106
|
// while it still carries the cert for its children. Fully additive (new aggregate + directive, no era key ⇒
|
|
107
|
-
// pre-existing chains replay byte-identical). architecture/
|
|
107
|
+
// pre-existing chains replay byte-identical). See architecture/verified_auth_from_genesis.md.
|
|
108
108
|
export { BIRTH_ATTESTATION_ID, BirthAttestation, birthAttestation, seedBirthAttestation } from "./home.js";
|
|
109
109
|
// THE ENROLL-ONCE DELEGATION LANE — a platform records each tenant's device-key delegation (signed by its
|
|
110
110
|
// onboarding signer S_P) so the kernel reads it at `signer-delegation:<user>` when assembling a home's chain.
|
package/src/index.ts
CHANGED
|
@@ -464,7 +464,7 @@ export {
|
|
|
464
464
|
// a `requires(cap)` aggregate's cap IS a read relation (synthesized into nomosRelations at compile),
|
|
465
465
|
// and a grant is a `relateSubject({ object:"workspace:self", relation:cap, ... })` tuple. See
|
|
466
466
|
// framework/relations.ts + relation_schema.ts below.
|
|
467
|
-
// NATIVE ZANZIBAR / ReBAC (architecture/
|
|
467
|
+
// NATIVE ZANZIBAR / ReBAC (architecture/authz_relationship_spine.md) — object-keyed
|
|
468
468
|
// relation-tuple events `object#relation@subject`, the clean-slate-native authz spine.
|
|
469
469
|
export {
|
|
470
470
|
RELATION_STATUSES,
|
package/src/law_check.ts
CHANGED
|
@@ -1351,7 +1351,7 @@ export function checkDomainModules(
|
|
|
1351
1351
|
domain,
|
|
1352
1352
|
level: "warn",
|
|
1353
1353
|
what: `birth recipe: ${diag}`,
|
|
1354
|
-
fix: `see
|
|
1354
|
+
fix: `see create-githolon/template/docs/08-births.md — the recipe must install both framework + child law and seed genesis`,
|
|
1355
1355
|
});
|
|
1356
1356
|
}
|
|
1357
1357
|
}
|
package/src/law_graph.ts
CHANGED
|
@@ -1221,6 +1221,7 @@ export function discoverLawGraph(
|
|
|
1221
1221
|
const frameworkCapabilities = new Set<"owned-authoring">();
|
|
1222
1222
|
if (
|
|
1223
1223
|
businessBirth?.owned === true ||
|
|
1224
|
+
(intent as { ownedAuthoringInternal?: unknown }).ownedAuthoringInternal === true ||
|
|
1224
1225
|
affectedAggregateIds.some((aggregateId) =>
|
|
1225
1226
|
aggregateByLabel.get(aggregateId)?.node.ownerField !== undefined)
|
|
1226
1227
|
) {
|
|
@@ -1537,16 +1538,23 @@ export function discoverLawGraph(
|
|
|
1537
1538
|
const synthesizedReadRelations = new Map<string, (typeof authoredRelationDecls)[number]>();
|
|
1538
1539
|
for (const aggregate of module.aggregates) {
|
|
1539
1540
|
if (aggregate.audience === undefined || aggregate.audience.kind === "public") continue;
|
|
1540
|
-
const
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1541
|
+
const owned = aggregate.audience.kind === "owned";
|
|
1542
|
+
const relation = owned ? OWNED_READ_RELATION : aggregate.audience.cap;
|
|
1543
|
+
// A normal capability is a workspace role. `.owned()` is deliberately different: Nomos seats the
|
|
1544
|
+
// creator on each record's own relation so sharing one record grants no sibling. The old synthesis
|
|
1545
|
+
// declared the owned relation on `workspace:self` while the framework correctly wrote
|
|
1546
|
+
// `<domain>.<Aggregate>:<id>#reader`; every lawful owner read was consequently denied.
|
|
1547
|
+
const authoredObjectType = owned ? aggregate.id : "workspace";
|
|
1548
|
+
const objectType = owned
|
|
1549
|
+
? aggregateByLabel.get(aggregate.id)!.node.wireType
|
|
1550
|
+
: "workspace";
|
|
1551
|
+
const label = `${authoredObjectType}#${relation}`;
|
|
1544
1552
|
if (authoredRelationLabels.has(label) || synthesizedReadRelations.has(label)) continue;
|
|
1545
1553
|
synthesizedReadRelations.set(label, {
|
|
1546
1554
|
label,
|
|
1547
|
-
authoredObjectType
|
|
1548
|
-
objectType
|
|
1549
|
-
global:
|
|
1555
|
+
authoredObjectType,
|
|
1556
|
+
objectType,
|
|
1557
|
+
global: !owned,
|
|
1550
1558
|
caps: DEFAULT_RELATION_CAPS,
|
|
1551
1559
|
relation,
|
|
1552
1560
|
definition: {
|
|
@@ -1697,16 +1705,17 @@ export function discoverLawGraph(
|
|
|
1697
1705
|
}
|
|
1698
1706
|
for (const aggregate of module.aggregates) {
|
|
1699
1707
|
if (aggregate.audience === undefined || aggregate.audience.kind === "public") continue;
|
|
1700
|
-
const
|
|
1701
|
-
|
|
1702
|
-
|
|
1708
|
+
const owned = aggregate.audience.kind === "owned";
|
|
1709
|
+
const relationName = owned ? OWNED_READ_RELATION : aggregate.audience.cap;
|
|
1710
|
+
const objectType = owned ? aggregate.id : "workspace";
|
|
1711
|
+
const relationLabel = `${objectType}#${relationName}`;
|
|
1703
1712
|
const candidates = relationNodes.filter((candidate) =>
|
|
1704
|
-
candidate.entity.label ===
|
|
1713
|
+
candidate.entity.label === relationLabel);
|
|
1705
1714
|
if (candidates.length > 1) {
|
|
1706
1715
|
fail(`aggregate '${aggregate.id}' read relation '${relationName}' is ambiguous across relation types`);
|
|
1707
1716
|
}
|
|
1708
1717
|
const readPolicy: LawTargetRef<"relation" | "policy"> = candidates[0] === undefined
|
|
1709
|
-
? { kind: "dependency-export", entityKind: "relation", label:
|
|
1718
|
+
? { kind: "dependency-export", entityKind: "relation", label: relationLabel }
|
|
1710
1719
|
: ref(candidates[0].entity);
|
|
1711
1720
|
const entry = aggregateByLabel.get(aggregate.id)!;
|
|
1712
1721
|
const updated = { ...entry.node, readPolicy };
|
package/src/relation_schema.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
/**
|
|
7
7
|
* relation_schema — THE NATIVE ZANZIBAR RELATION SCHEMA (the userset-rewrite law).
|
|
8
8
|
*
|
|
9
|
-
* This is layer
|
|
9
|
+
* This is the schema layer of the authorization design (see `architecture/authz_relationship_spine.md`):
|
|
10
10
|
* 1. TUPLES = ledger events (`relateSubject`/`RelationTuple`, framework/relations.ts).
|
|
11
11
|
* 2. SCHEMA = THIS FILE — per-type relation REWRITES authored as PURE DATA, lowered to the
|
|
12
12
|
* `nomosRelations` USD era key. NO JavaScript on the schema path: a `.relation(...)` produces
|
package/src/relation_sugar.ts
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* directly(user(), key()) → { rewrite: this, allow:[...] } — the this+allow PAIR so the
|
|
28
28
|
* type restriction CAN'T be forgotten (the #1 footgun).
|
|
29
29
|
*
|
|
30
|
-
* 2. SECURE-BY-DEFAULT AUDIENCES (the PRIMARY surface; `
|
|
30
|
+
* 2. SECURE-BY-DEFAULT AUDIENCES (the PRIMARY surface; `authz_relationship_spine.md`). Builder
|
|
31
31
|
* methods that lower to a relation (or a small set of relations) with the SAFE default baked in:
|
|
32
32
|
* .public(rel) → rel = directly(anyUser()) (user:* ONLY — never key:*)
|
|
33
33
|
* .owned(rel?) → owner = directly(user()); rel = directly(self) ∪ computed(owner)
|
package/src/shape_compat.ts
CHANGED
|
@@ -138,7 +138,7 @@ function payloadFieldKind(spec: PayloadFieldSpec): string {
|
|
|
138
138
|
return "string";
|
|
139
139
|
case "vo":
|
|
140
140
|
case "voList":
|
|
141
|
-
// Interface kinds stay at the base lane for now (
|
|
141
|
+
// Interface kinds stay at the base lane for now (the remaining parity work in dart_codegen_parity.md
|
|
142
142
|
// upgrades these to a first-class "vo" kind) — a VO IS a json leaf structurally.
|
|
143
143
|
return "json";
|
|
144
144
|
case "scalar":
|
package/src/value_object.ts
CHANGED
|
@@ -608,7 +608,7 @@ function describeZod(
|
|
|
608
608
|
const VO_NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
609
609
|
|
|
610
610
|
/**
|
|
611
|
-
* THE VO BRAND (
|
|
611
|
+
* THE VO BRAND (vo_first_class.md) — `valueObject()` stamps its zod schema
|
|
612
612
|
* with the VO's {@link ValueObjectFieldMeta} under this symbol. The property itself is
|
|
613
613
|
* non-enumerable, never serialized and never read by the engine's validation path. When attached
|
|
614
614
|
* to an aggregate field, its canonical descriptor deliberately enters that field's stable value contract.
|
|
@@ -1036,7 +1036,7 @@ export const geo = {
|
|
|
1036
1036
|
/**
|
|
1037
1037
|
* Collect the value objects referenced by DIRECTIVE PAYLOADS (top-level fields + array
|
|
1038
1038
|
* elements), via the {@link NOMOS_VO_BRAND} the schemas carry. CODEGEN-ONLY (slice B of
|
|
1039
|
-
*
|
|
1039
|
+
* vo_first_class.md): the result feeds the generated Dart/TS surfaces so a
|
|
1040
1040
|
* payload-only VO still gets its class/interface — it does NOT enter the `nomosValueObjects`
|
|
1041
1041
|
* IR key (that stays aggregate-derived, keeping package bytes / domainHash untouched).
|
|
1042
1042
|
*/
|