@githolon/dsl 0.105.6 → 0.105.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dart/CHANGELOG.md +4 -0
- package/dart/lib/src/runner/node-entry.mjs +5 -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 +4 -1
- package/src/aggregate.ts +2 -1
- package/src/birth_meter_ensure.ts +38 -1
- package/src/codegen_ts.ts +20 -1
- package/src/compile_package_main.ts +2 -2
- package/src/framework/owned_custody.ts +78 -22
- package/src/law_graph.ts +21 -12
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();
|
|
@@ -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();
|
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_ts.ts
CHANGED
|
@@ -2090,6 +2090,25 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
2090
2090
|
` return read(keys);`,
|
|
2091
2091
|
`};`,
|
|
2092
2092
|
``,
|
|
2093
|
+
`/** Owned bounded reads perform the same record-key-aware second pass without erasing`,
|
|
2094
|
+
` * the page envelope. Keeping this distinct from the array helper makes cursor semantics explicit. */`,
|
|
2095
|
+
`const _readPageWithOwnedKeys = async <T>(`,
|
|
2096
|
+
` holon: NomosHolon,`,
|
|
2097
|
+
` keyStore: NomosKeyStore | undefined,`,
|
|
2098
|
+
` scopes: readonly { scope: string; owned?: true }[],`,
|
|
2099
|
+
` read: (keys: readonly NomosScopeKey[] | undefined) => Promise<{ rows: T[]; cursor: string | null; done: boolean }>,`,
|
|
2100
|
+
`): Promise<{ rows: T[]; cursor: string | null; done: boolean }> => {`,
|
|
2101
|
+
` const firstKeys = await _readKeys(holon, keyStore, scopes);`,
|
|
2102
|
+
` const first = await read(firstKeys);`,
|
|
2103
|
+
` if (!scopes.some((scope) => scope.owned === true) || first.rows.length === 0) return first;`,
|
|
2104
|
+
` const records = first.rows.map((row) => {`,
|
|
2105
|
+
` const raw = row as T & { type: string; id: string };`,
|
|
2106
|
+
` return { type: raw.type, id: raw.id };`,
|
|
2107
|
+
` });`,
|
|
2108
|
+
` const keys = await _readKeys(holon, keyStore, scopes, records);`,
|
|
2109
|
+
` return read(keys);`,
|
|
2110
|
+
`};`,
|
|
2111
|
+
``,
|
|
2093
2112
|
`/** Reactive owned reads use the exact same two-pass, record-key-aware read as one-shot reads.`,
|
|
2094
2113
|
` * The raw watch is only a local change signal; ciphertext rows never reach application callbacks. */`,
|
|
2095
2114
|
`const _watchWithOwnedKeys = <T>(`,
|
|
@@ -3375,7 +3394,7 @@ export function generateTsClient(modules: readonly DomainModule[], opts: TsClien
|
|
|
3375
3394
|
);
|
|
3376
3395
|
const queryPageCall = qScopes.length
|
|
3377
3396
|
? qOwned
|
|
3378
|
-
? `
|
|
3397
|
+
? `_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
3398
|
: `(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
3399
|
: `(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
3400
|
methods.push(
|
|
@@ -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.45";
|
|
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).
|
|
@@ -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) {
|
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 };
|