@githolon/dsl 0.107.3 → 0.108.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dart/CHANGELOG.md +19 -0
- package/dart/lib/src/runner/node-entry.mjs +4044 -3831
- package/dart/lib/src/runner/offline-install.mjs +2633 -2537
- package/dart/lib/src/test_harness.dart +249 -239
- package/dart/lib/src/webview_bridge.dart +38 -168
- package/dart/pubspec.lock +17 -1
- package/dart/pubspec.yaml +3 -2
- package/dart_types/CHANGELOG.md +10 -0
- package/dart_types/lib/nomos_kernel_protocol.dart +10 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +16158 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +766 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbjson.dart +6096 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbserver.dart +50 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pb.dart +245 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pbenum.dart +160 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pbjson.dart +178 -0
- package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pb.dart +828 -0
- package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbenum.dart +465 -0
- package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbjson.dart +573 -0
- package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbserver.dart +50 -0
- package/dart_types/pubspec.lock +16 -0
- package/dart_types/pubspec.yaml +3 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +298 -106
- package/src/birth_meter_ensure.ts +2 -0
- package/src/business_birth.ts +2 -0
- package/src/compile_package_main.ts +36 -18
- package/src/framework/application_home.ts +2 -0
- package/src/framework/auth_provider_law.ts +34 -0
- package/src/framework/birth_certificate_law.ts +24 -0
- package/src/framework/home.ts +53 -14
- package/src/framework/owned_custody.ts +6 -1
- package/src/framework/signing_identity.ts +66 -17
- package/src/framework/workspaces.ts +8 -1
- package/src/index.ts +2 -0
- package/src/shape_compat.ts +6 -3
|
@@ -50,6 +50,7 @@ import { GENESIS_WARRANT_ID, GenesisWarrant, seedGenesis } from "./framework/gen
|
|
|
50
50
|
import {
|
|
51
51
|
BIRTH_ATTESTATION_ID,
|
|
52
52
|
BirthAttestation,
|
|
53
|
+
BirthAttestationLink,
|
|
53
54
|
birthAttestation,
|
|
54
55
|
seedBirthAttestation,
|
|
55
56
|
} from "./framework/home.js";
|
|
@@ -253,6 +254,7 @@ export function withBirthMeterExports<M extends Record<string, unknown>>(mod: M)
|
|
|
253
254
|
internalize("__nomos_GenesisWarrant", GenesisWarrant);
|
|
254
255
|
internalize("__nomos_seedGenesis", seedGenesis);
|
|
255
256
|
internalize("__nomos_BirthAttestation", BirthAttestation);
|
|
257
|
+
internalize("__nomos_BirthAttestationLink", BirthAttestationLink);
|
|
256
258
|
internalize("__nomos_seedBirthAttestation", seedBirthAttestation);
|
|
257
259
|
internalize("__nomos_birthAttestation", birthAttestation);
|
|
258
260
|
internalize("__nomos_AuthProvider", AuthProvider);
|
package/src/business_birth.ts
CHANGED
|
@@ -36,6 +36,7 @@ import { GENESIS_WARRANT_ID, GenesisWarrant, seedGenesis } from "./framework/gen
|
|
|
36
36
|
import {
|
|
37
37
|
BIRTH_ATTESTATION_ID,
|
|
38
38
|
BirthAttestation,
|
|
39
|
+
BirthAttestationLink,
|
|
39
40
|
birthAttestation,
|
|
40
41
|
seedBirthAttestation,
|
|
41
42
|
} from "./framework/home.js";
|
|
@@ -359,6 +360,7 @@ function lowerOne<P extends Record<string, unknown>, F extends Record<string, Fi
|
|
|
359
360
|
__nomos_GenesisWarrant: internalSurface(GenesisWarrant),
|
|
360
361
|
__nomos_seedGenesis: internalDirective(seedGenesis),
|
|
361
362
|
__nomos_BirthAttestation: internalSurface(BirthAttestation),
|
|
363
|
+
__nomos_BirthAttestationLink: internalSurface(BirthAttestationLink),
|
|
362
364
|
__nomos_seedBirthAttestation: internalDirective(seedBirthAttestation),
|
|
363
365
|
__nomos_birthAttestation: internalSurface(birthAttestation),
|
|
364
366
|
__nomos_RelationTuple: internalSurface(RelationTuple),
|
|
@@ -865,7 +865,10 @@ const DART_SUPPORT_RUNTIME_SRC = [
|
|
|
865
865
|
const DART_SUPPORT_SRC = [...DART_SUPPORT_CORE_SRC, ...DART_SUPPORT_RUNTIME_SRC] as const;
|
|
866
866
|
/** The local shim replacing `package:nomos_types/nomos_types.dart` inside vendored runtime files. */
|
|
867
867
|
const VENDORED_TYPES_SHIM = "nomos_types.dart";
|
|
868
|
+
const VENDORED_KERNEL_PROTOCOL_SHIM = "nomos_kernel_protocol.dart";
|
|
868
869
|
const VENDORED_META_DEP = "^1.12.0";
|
|
870
|
+
const VENDORED_PROTOBUF_DEP = "^5.0.0";
|
|
871
|
+
const VENDORED_FIXNUM_DEP = "^1.1.1";
|
|
869
872
|
const VENDORED_DART_SDK_FLOOR = "3.3.0";
|
|
870
873
|
|
|
871
874
|
/**
|
|
@@ -909,21 +912,12 @@ function ensureVendoredPackageRequirements(dartOut: string): void {
|
|
|
909
912
|
`${sdkLine[1]}\">=${VENDORED_DART_SDK_FLOOR} <4.0.0\"${sdkLine[3] ?? ""}`,
|
|
910
913
|
);
|
|
911
914
|
}
|
|
912
|
-
if (
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
}
|
|
919
|
-
writeFileSync(
|
|
920
|
-
pubspec,
|
|
921
|
-
next.replace(
|
|
922
|
-
/^dependencies:\s*$/m,
|
|
923
|
-
"dependencies:\n meta: " + VENDORED_META_DEP,
|
|
924
|
-
),
|
|
925
|
-
"utf8",
|
|
926
|
-
);
|
|
915
|
+
if (next !== source) writeFileSync(pubspec, next, "utf8");
|
|
916
|
+
reconcilePubspec(dir, "", {
|
|
917
|
+
meta: VENDORED_META_DEP,
|
|
918
|
+
protobuf: VENDORED_PROTOBUF_DEP,
|
|
919
|
+
fixnum: VENDORED_FIXNUM_DEP,
|
|
920
|
+
});
|
|
927
921
|
}
|
|
928
922
|
|
|
929
923
|
/** The generated-owned subdirectory, relative to `dartOut` (the pub package's `lib/`). WIPED and
|
|
@@ -1357,9 +1351,33 @@ function emitDart(
|
|
|
1357
1351
|
.replace(
|
|
1358
1352
|
/import 'package:nomos_types\/src\/(?:nomos_ref|foreign_ref|versioned_nomos_ref)\.dart'/g,
|
|
1359
1353
|
`import '${VENDORED_TYPES_SHIM}'`,
|
|
1354
|
+
)
|
|
1355
|
+
.replace(
|
|
1356
|
+
/import 'package:nomos_types\/nomos_kernel_protocol\.dart'/g,
|
|
1357
|
+
`import '${VENDORED_KERNEL_PROTOCOL_SHIM}'`,
|
|
1360
1358
|
);
|
|
1361
1359
|
writeFileSync(path.join(genDir, "src", f), src, "utf8");
|
|
1362
1360
|
}
|
|
1361
|
+
// Protobuf is the sole kernel/host message schema. Flat vendored packages receive a generated
|
|
1362
|
+
// projection of that schema, never a handwritten certificate mirror. The compiler copies only the
|
|
1363
|
+
// generated kernel files this runtime uses; `generate:kernel-proto-dart` remains their source.
|
|
1364
|
+
const generatedKernelProto = path.join(DSL_DIR, "dart_types", "lib", "src", "proto", "nomos", "kernel", "v1");
|
|
1365
|
+
const vendoredKernelProto = path.join(genDir, "src", "proto", "nomos", "kernel", "v1");
|
|
1366
|
+
mkdirSync(vendoredKernelProto, { recursive: true });
|
|
1367
|
+
for (const f of ["kernel.pb.dart", "kernel.pbenum.dart"]) {
|
|
1368
|
+
copyFileSync(path.join(generatedKernelProto, f), path.join(vendoredKernelProto, f));
|
|
1369
|
+
}
|
|
1370
|
+
writeFileSync(
|
|
1371
|
+
path.join(genDir, "src", VENDORED_KERNEL_PROTOCOL_SHIM),
|
|
1372
|
+
[
|
|
1373
|
+
"// GENERATED from proto/nomos/kernel/v1/kernel.proto by nomos-compile. DO NOT EDIT.",
|
|
1374
|
+
"library;",
|
|
1375
|
+
"",
|
|
1376
|
+
"export 'proto/nomos/kernel/v1/kernel.pb.dart';",
|
|
1377
|
+
"",
|
|
1378
|
+
].join("\n"),
|
|
1379
|
+
"utf8",
|
|
1380
|
+
);
|
|
1363
1381
|
// The local shim the rewritten runtime imports resolve through (the vendored twin of the
|
|
1364
1382
|
// nomos_types barrel — core files only).
|
|
1365
1383
|
writeFileSync(
|
|
@@ -1660,12 +1678,12 @@ const dartTypesImportFor = (generatedSource: string): string => {
|
|
|
1660
1678
|
* just the floor generated code needs — a lagging pin forces tenants into dependency_overrides. */
|
|
1661
1679
|
// 0.3.0: SurfacedValue<T>/SurfacedObservation/PendingConflictRow — generated Surfaced-field typing +
|
|
1662
1680
|
// the pendingConflicts read rows.
|
|
1663
|
-
const NOMOS_TYPES_DEP = "^0.
|
|
1681
|
+
const NOMOS_TYPES_DEP = "^0.9.0";
|
|
1664
1682
|
// 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
|
|
1665
1683
|
// DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
|
|
1666
1684
|
// REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
|
|
1667
|
-
const NOMOS_CLIENT_DEP = "^0.
|
|
1668
|
-
const NOMOS_FLUTTER_DEP = "^0.
|
|
1685
|
+
const NOMOS_CLIENT_DEP = "^0.65.0";
|
|
1686
|
+
const NOMOS_FLUTTER_DEP = "^0.64.0";
|
|
1669
1687
|
// DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in dsl/dart/pubspec.yaml
|
|
1670
1688
|
// and dsl/dart_types/pubspec.yaml — a Dart release without this bump fails the suite, so tenants never
|
|
1671
1689
|
// land back in dependency_overrides (the exact co2 report this closes, third occurrence).
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
import { GenesisWarrant, seedGenesis } from "./genesis.js";
|
|
24
24
|
import {
|
|
25
25
|
BirthAttestation,
|
|
26
|
+
BirthAttestationLink,
|
|
26
27
|
ReceivingGroup,
|
|
27
28
|
ReceivingGroupPolicy,
|
|
28
29
|
SharedWithMe,
|
|
@@ -110,6 +111,7 @@ export const __nomos_sharedByObject = query(sharedByObject);
|
|
|
110
111
|
export const __nomos_GenesisWarrant = aggregate(GenesisWarrant);
|
|
111
112
|
export const __nomos_seedGenesis = directive(seedGenesis);
|
|
112
113
|
export const __nomos_BirthAttestation = aggregate(BirthAttestation);
|
|
114
|
+
export const __nomos_BirthAttestationLink = aggregate(BirthAttestationLink);
|
|
113
115
|
export const __nomos_seedBirthAttestation = directive(seedBirthAttestation);
|
|
114
116
|
export const __nomos_birthAttestation = query(birthAttestation);
|
|
115
117
|
export const __nomos_parentAttestedBirth = internal(parentAttestedBirth);
|
|
@@ -69,8 +69,29 @@ export const AuthProvider = aggregate("AuthProvider", {
|
|
|
69
69
|
setBy: t.string().merge(Lww),
|
|
70
70
|
/** Epoch-ms it was set (caller-stamped). */
|
|
71
71
|
setAt: t.int().merge(Lww),
|
|
72
|
+
/** The platform-signed provider epoch inserted above new keyless delegations. These are the nominal
|
|
73
|
+
* BirthCertificate fields, stored separately as typed USDA properties; the provider issuer, keys and
|
|
74
|
+
* audience come from this same AuthProvider row and are therefore covered by the signature without a
|
|
75
|
+
* second serialized certificate document. Historical children retain the epoch they were born with. */
|
|
76
|
+
attestationChild: t.string().merge(Lww).optional(),
|
|
77
|
+
attestationFounder: t.string().merge(Lww).optional(),
|
|
78
|
+
attestationVerified: t.bool().merge(Lww).optional(),
|
|
79
|
+
attestationParentKey: t.string().merge(Lww).optional(),
|
|
80
|
+
attestationParentId: t.string().merge(Lww).optional(),
|
|
81
|
+
attestationEpoch: t.int().merge(Lww).optional(),
|
|
82
|
+
attestationSignature: t.string().merge(Lww).optional(),
|
|
72
83
|
}).public();
|
|
73
84
|
|
|
85
|
+
const authProviderAttestation = z.object({
|
|
86
|
+
child: z.string().min(1),
|
|
87
|
+
founder: z.string().min(1),
|
|
88
|
+
verified: z.boolean(),
|
|
89
|
+
parentKey: z.string().min(1),
|
|
90
|
+
parentId: z.string().min(1),
|
|
91
|
+
epoch: z.number().int().nonnegative(),
|
|
92
|
+
signature: z.string().min(1),
|
|
93
|
+
});
|
|
94
|
+
|
|
74
95
|
/**
|
|
75
96
|
* setAuthProvider — declare (or amend) the platform's identity provider. An
|
|
76
97
|
* Ensure on the singleton; every change is an attributed, replayable intent.
|
|
@@ -95,6 +116,10 @@ const authProviderPayload = z.object({
|
|
|
95
116
|
label: z.string().min(1),
|
|
96
117
|
setBy: z.string().min(1),
|
|
97
118
|
setAt: epochMs,
|
|
119
|
+
/** A typed, already-signed BirthCertificate body for this provider version. The signer is the
|
|
120
|
+
* platform key attested by its existing lineage; the kernel verifies it while walking every new
|
|
121
|
+
* descendant chain. Optional keeps pre-epoch histories replayable. */
|
|
122
|
+
attestation: authProviderAttestation.optional(),
|
|
98
123
|
});
|
|
99
124
|
|
|
100
125
|
type AuthProviderPayload = z.infer<typeof authProviderPayload>;
|
|
@@ -113,6 +138,15 @@ function writeAuthProvider(p: AuthProviderPayload) {
|
|
|
113
138
|
set(ap, "label", p.label),
|
|
114
139
|
set(ap, "setBy", p.setBy),
|
|
115
140
|
set(ap, "setAt", p.setAt),
|
|
141
|
+
...(p.attestation ? [
|
|
142
|
+
set(ap, "attestationChild", p.attestation.child),
|
|
143
|
+
set(ap, "attestationFounder", p.attestation.founder),
|
|
144
|
+
set(ap, "attestationVerified", p.attestation.verified),
|
|
145
|
+
set(ap, "attestationParentKey", p.attestation.parentKey),
|
|
146
|
+
set(ap, "attestationParentId", p.attestation.parentId),
|
|
147
|
+
set(ap, "attestationEpoch", p.attestation.epoch),
|
|
148
|
+
set(ap, "attestationSignature", p.attestation.signature),
|
|
149
|
+
] : []),
|
|
116
150
|
];
|
|
117
151
|
}
|
|
118
152
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// NOMOS — the one typed law payload for a birth-certificate link.
|
|
2
|
+
//
|
|
3
|
+
// A BirthCertificate is nominal in Rust and Protobuf and becomes ordinary typed USDA fields in law state.
|
|
4
|
+
// It is never lowered to a JSON string. The JWKS member is the external standardized JWKS document itself;
|
|
5
|
+
// the certificate envelope and chain structure remain typed Nomos data.
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
export const birthCertificatePayload = z.object({
|
|
9
|
+
child: z.string().min(1),
|
|
10
|
+
founder: z.string().min(1),
|
|
11
|
+
verified: z.boolean(),
|
|
12
|
+
parentKey: z.string().min(1),
|
|
13
|
+
parentId: z.string(),
|
|
14
|
+
epoch: z.number().int().nonnegative(),
|
|
15
|
+
signature: z.string().min(1),
|
|
16
|
+
verifiedVia: z.string().min(1).optional(),
|
|
17
|
+
idpToken: z.string().min(1).optional(),
|
|
18
|
+
capturedClock: z.number().int().nonnegative().optional(),
|
|
19
|
+
authProviderIssuer: z.string().min(1).optional(),
|
|
20
|
+
authProviderJwks: z.string().min(1).optional(),
|
|
21
|
+
authProviderAudience: z.string().min(1).optional(),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export type BirthCertificatePayload = z.infer<typeof birthCertificatePayload>;
|
package/src/framework/home.ts
CHANGED
|
@@ -31,7 +31,7 @@ import { sha256Digest, t } from "../fields.js";
|
|
|
31
31
|
import { Lww } from "../drivers.js";
|
|
32
32
|
import { directive } from "../directive.js";
|
|
33
33
|
import { ensure } from "../authoring.js";
|
|
34
|
-
import { set, type PlannedOp } from "../ops.js";
|
|
34
|
+
import { set, withMarker, type PlannedOp } from "../ops.js";
|
|
35
35
|
import { query } from "../query.js";
|
|
36
36
|
import { birth } from "../births.js";
|
|
37
37
|
import { readFrameworkDeclared } from "../read.js";
|
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
DomainInstallExecutor,
|
|
48
48
|
domainInstallGenesisOffer,
|
|
49
49
|
} from "./domain_lifecycle.js";
|
|
50
|
+
import { birthCertificatePayload } from "./birth_certificate_law.js";
|
|
50
51
|
|
|
51
52
|
// `nomos.sha256` — the TRUSTLESS, pure hash computed IN the githolon (Rust sha2 bound in the sealed engine;
|
|
52
53
|
// see deterministic-rquickjs). Available as a global to plans + invariant predicates (like `nomos.mint`); it
|
|
@@ -581,6 +582,7 @@ export const sharedByObject = query("sharedByObject").key("object").returns(Shar
|
|
|
581
582
|
* verify doors); this aggregate is the human-readable mirror.
|
|
582
583
|
*/
|
|
583
584
|
export const BIRTH_ATTESTATION_ID = "birth-attestation:self";
|
|
585
|
+
export const BIRTH_ATTESTATION_LINK_PREFIX = "birth-attestation-link:";
|
|
584
586
|
export const BirthAttestation = aggregate("BirthAttestation", {
|
|
585
587
|
/** This home's self-id (`home-<sha256(founder)>`) — the cert's `child`. */
|
|
586
588
|
child: t.string().merge(Lww),
|
|
@@ -594,12 +596,29 @@ export const BirthAttestation = aggregate("BirthAttestation", {
|
|
|
594
596
|
parentKey: t.string().merge(Lww),
|
|
595
597
|
/** The parent's SignerEpoch at the attestation (informational; the cert is judged at birth-time validity). */
|
|
596
598
|
epoch: t.int().merge(Lww),
|
|
597
|
-
/**
|
|
598
|
-
*
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
599
|
+
/** Legacy replay slot for histories authored before `BirthAttestationLink`. New histories write the
|
|
600
|
+
* travelling chain only as ordered, typed link records below. */
|
|
601
|
+
certChain: t.string().merge(Lww).optional(),
|
|
602
|
+
}).public();
|
|
603
|
+
|
|
604
|
+
/** One exact link in the travelling certificate chain. The zero-padded index is custody order; every
|
|
605
|
+
* certificate member is a separate typed USDA property. `certChain` above remains only as a replay fallback
|
|
606
|
+
* for histories authored before this aggregate existed. */
|
|
607
|
+
export const BirthAttestationLink = aggregate("BirthAttestationLink", {
|
|
608
|
+
index: t.int().merge(Lww),
|
|
609
|
+
child: t.string().merge(Lww),
|
|
610
|
+
founder: t.string().merge(Lww),
|
|
611
|
+
verified: t.bool().merge(Lww),
|
|
612
|
+
parentKey: t.string().merge(Lww),
|
|
613
|
+
parentId: t.string().merge(Lww),
|
|
614
|
+
epoch: t.int().merge(Lww),
|
|
615
|
+
signature: t.string().merge(Lww),
|
|
616
|
+
verifiedVia: t.string().merge(Lww).optional(),
|
|
617
|
+
idpToken: t.string().merge(Lww).optional(),
|
|
618
|
+
capturedClock: t.int().merge(Lww).optional(),
|
|
619
|
+
authProviderIssuer: t.string().merge(Lww).optional(),
|
|
620
|
+
authProviderJwks: t.string().merge(Lww).optional(),
|
|
621
|
+
authProviderAudience: t.string().merge(Lww).optional(),
|
|
603
622
|
}).public();
|
|
604
623
|
|
|
605
624
|
/** Audit read: this home's birth attestation (who attested it + the verified verdict). */
|
|
@@ -611,7 +630,7 @@ export const birthAttestation = query("birthAttestation").key("child").returns(B
|
|
|
611
630
|
* the [`BirthAttestation`] singleton so the home's own state carries its travelling cert. A CONTROL-PLANE
|
|
612
631
|
* write seeded once at birth (the open session lane structurally refuses it by directive id). The values
|
|
613
632
|
* are the parent's GATE VERDICT (`founder`/`verified` lowered from the captured port — CAS-as-law, never
|
|
614
|
-
* the offer payload) + the signed
|
|
633
|
+
* the offer payload) + the typed signed links; the home's verify doors re-derive trust from the chain.
|
|
615
634
|
*/
|
|
616
635
|
export const seedBirthAttestation = directive("seedBirthAttestation")
|
|
617
636
|
.ensures(BirthAttestation)
|
|
@@ -625,21 +644,41 @@ export const seedBirthAttestation = directive("seedBirthAttestation")
|
|
|
625
644
|
parentId: z.string(),
|
|
626
645
|
parentKey: z.string().min(1), // base64 K_P
|
|
627
646
|
epoch: z.number().int().nonnegative(),
|
|
628
|
-
|
|
647
|
+
links: z.array(birthCertificatePayload).min(1),
|
|
629
648
|
}),
|
|
630
649
|
)
|
|
631
650
|
.plan((p) => {
|
|
632
651
|
const h = instance(BirthAttestation, BIRTH_ATTESTATION_ID);
|
|
633
|
-
|
|
652
|
+
const header = [
|
|
634
653
|
set(h, "child", p.child),
|
|
635
654
|
set(h, "founder", p.founder),
|
|
636
655
|
set(h, "verified", p.verified),
|
|
637
656
|
set(h, "parentId", p.parentId),
|
|
638
657
|
set(h, "parentKey", p.parentKey),
|
|
639
658
|
set(h, "epoch", p.epoch),
|
|
640
|
-
set(h, "certChain", p.certChain),
|
|
641
659
|
];
|
|
642
|
-
|
|
660
|
+
const links = p.links.flatMap((certificate, index) => {
|
|
661
|
+
const link = instance(BirthAttestationLink, `${BIRTH_ATTESTATION_LINK_PREFIX}${String(index).padStart(8, "0")}`);
|
|
662
|
+
return [
|
|
663
|
+
withMarker(set(link, "index", index), "ensures"),
|
|
664
|
+
withMarker(set(link, "child", certificate.child), "ensures"),
|
|
665
|
+
withMarker(set(link, "founder", certificate.founder), "ensures"),
|
|
666
|
+
withMarker(set(link, "verified", certificate.verified), "ensures"),
|
|
667
|
+
withMarker(set(link, "parentKey", certificate.parentKey), "ensures"),
|
|
668
|
+
withMarker(set(link, "parentId", certificate.parentId), "ensures"),
|
|
669
|
+
withMarker(set(link, "epoch", certificate.epoch), "ensures"),
|
|
670
|
+
withMarker(set(link, "signature", certificate.signature), "ensures"),
|
|
671
|
+
...(certificate.verifiedVia ? [withMarker(set(link, "verifiedVia", certificate.verifiedVia), "ensures")] : []),
|
|
672
|
+
...(certificate.idpToken ? [withMarker(set(link, "idpToken", certificate.idpToken), "ensures")] : []),
|
|
673
|
+
...(certificate.capturedClock === undefined ? [] : [withMarker(set(link, "capturedClock", certificate.capturedClock), "ensures")]),
|
|
674
|
+
...(certificate.authProviderIssuer ? [withMarker(set(link, "authProviderIssuer", certificate.authProviderIssuer), "ensures")] : []),
|
|
675
|
+
...(certificate.authProviderJwks ? [withMarker(set(link, "authProviderJwks", certificate.authProviderJwks), "ensures")] : []),
|
|
676
|
+
...(certificate.authProviderAudience ? [withMarker(set(link, "authProviderAudience", certificate.authProviderAudience), "ensures")] : []),
|
|
677
|
+
];
|
|
678
|
+
});
|
|
679
|
+
return [...header, ...links];
|
|
680
|
+
})
|
|
681
|
+
.alsoEnsures(BirthAttestationLink);
|
|
643
682
|
|
|
644
683
|
/**
|
|
645
684
|
* THE ERA DECLARATION (the `nomosParentAttestedBirth` USD key). Exporting this sentinel from the home law
|
|
@@ -652,8 +691,8 @@ export const seedBirthAttestation = directive("seedBirthAttestation")
|
|
|
652
691
|
export const parentAttestedBirth: ParentAttestedBirthMarker = {
|
|
653
692
|
...requireParentAttestedBirth,
|
|
654
693
|
// DECLARE the owner + cert locators so the KERNEL reads them (it names neither): the home owner lives at
|
|
655
|
-
// the `HomeOwner` singleton's `principal
|
|
656
|
-
//
|
|
694
|
+
// the `HomeOwner` singleton's `principal`. `certObject`/`certField` retain the legacy chain locator for
|
|
695
|
+
// old histories; current histories are discovered through the typed `BirthAttestationLink` aggregate.
|
|
657
696
|
ownerObject: RECEIVING_GROUP_ID,
|
|
658
697
|
ownerField: "principal",
|
|
659
698
|
certObject: BIRTH_ATTESTATION_ID,
|
|
@@ -19,7 +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
|
+
import { BirthAttestation, BirthAttestationLink, seedBirthAttestation } from "./home.js";
|
|
23
23
|
import { RelationTuple, __qualifyAggregateType, removeTuple, writeTuple } from "./relations.js";
|
|
24
24
|
import { SIGNER_RELATION, enrollSigner, signerObject, identityRelations } from "./signing_identity.js";
|
|
25
25
|
import { impureCapability, impureCapabilityAggregate } from "./impure_capability.js";
|
|
@@ -430,6 +430,7 @@ const CUSTODY_RELATIONS_EXPORT = "__nomos_owned_custody_relations_7c1f5e";
|
|
|
430
430
|
const WARRANT_EXPORT = "__nomos_owned_genesis_warrant_7c1f5e";
|
|
431
431
|
const SEED_EXPORT = "__nomos_owned_seed_genesis_7c1f5e";
|
|
432
432
|
const BIRTH_ATTESTATION_EXPORT = "__nomos_owned_birth_attestation_7c1f5e";
|
|
433
|
+
const BIRTH_ATTESTATION_LINK_EXPORT = "__nomos_owned_birth_attestation_link_7c1f5e";
|
|
433
434
|
const BIRTH_ATTESTATION_SEED_EXPORT = "__nomos_owned_seed_birth_attestation_7c1f5e";
|
|
434
435
|
const ENROLL_EXPORT = "__nomos_owned_enroll_signer_7c1f5e";
|
|
435
436
|
const INITIALIZE_EXPORT = "__nomos_owned_initialize_identity_7c1f5e";
|
|
@@ -452,6 +453,7 @@ const RECOVERY_CAPABILITY_EXPORT = "__nomos_owned_recovery_capability_7c1f5e";
|
|
|
452
453
|
const ownedGenesisWarrant = hidden(GenesisWarrant);
|
|
453
454
|
const ownedSeedGenesis = hidden(seedGenesis);
|
|
454
455
|
const ownedBirthAttestation = hidden(BirthAttestation);
|
|
456
|
+
const ownedBirthAttestationLink = hidden(BirthAttestationLink);
|
|
455
457
|
const ownedSeedBirthAttestation = hidden(seedBirthAttestation);
|
|
456
458
|
const ownedEnrollSigner = hidden(enrollSigner);
|
|
457
459
|
const ownedIdentityRelations = hidden(identityRelations);
|
|
@@ -543,6 +545,7 @@ export function withOwnedCustodyExports<M extends Record<string, unknown>>(mod:
|
|
|
543
545
|
[WARRANT_EXPORT, ownedGenesisWarrant],
|
|
544
546
|
[SEED_EXPORT, ownedSeedGenesis],
|
|
545
547
|
[BIRTH_ATTESTATION_EXPORT, ownedBirthAttestation],
|
|
548
|
+
[BIRTH_ATTESTATION_LINK_EXPORT, ownedBirthAttestationLink],
|
|
546
549
|
[BIRTH_ATTESTATION_SEED_EXPORT, ownedSeedBirthAttestation],
|
|
547
550
|
[ENROLL_EXPORT, ownedEnrollSigner],
|
|
548
551
|
] as const) {
|
|
@@ -574,6 +577,7 @@ export function withOwnedCustodyExports<M extends Record<string, unknown>>(mod:
|
|
|
574
577
|
[GenesisWarrant, ownedGenesisWarrant],
|
|
575
578
|
[seedGenesis, ownedSeedGenesis],
|
|
576
579
|
[BirthAttestation, ownedBirthAttestation],
|
|
580
|
+
[BirthAttestationLink, ownedBirthAttestationLink],
|
|
577
581
|
[seedBirthAttestation, ownedSeedBirthAttestation],
|
|
578
582
|
[enrollSigner, ownedEnrollSigner],
|
|
579
583
|
] as const) {
|
|
@@ -639,6 +643,7 @@ export function withOwnedCustodyExports<M extends Record<string, unknown>>(mod:
|
|
|
639
643
|
[WARRANT_EXPORT]: ownedGenesisWarrant,
|
|
640
644
|
[SEED_EXPORT]: ownedSeedGenesis,
|
|
641
645
|
[BIRTH_ATTESTATION_EXPORT]: ownedBirthAttestation,
|
|
646
|
+
[BIRTH_ATTESTATION_LINK_EXPORT]: ownedBirthAttestationLink,
|
|
642
647
|
[BIRTH_ATTESTATION_SEED_EXPORT]: ownedSeedBirthAttestation,
|
|
643
648
|
[ENROLL_EXPORT]: ownedEnrollSigner,
|
|
644
649
|
};
|
|
@@ -42,11 +42,12 @@
|
|
|
42
42
|
* only revokes keys of its OWN identity).
|
|
43
43
|
*/
|
|
44
44
|
import { z } from "zod";
|
|
45
|
-
import {
|
|
45
|
+
import { birthCertificatePayload, type BirthCertificatePayload } from "./birth_certificate_law.js";
|
|
46
|
+
import { aggregate, instance, type BoundAggregate } from "../aggregate.js";
|
|
46
47
|
import { t } from "../fields.js";
|
|
47
48
|
import { Lww } from "../drivers.js";
|
|
48
49
|
import { directive } from "../directive.js";
|
|
49
|
-
import { set, withMarker, type PlannedOp } from "../ops.js";
|
|
50
|
+
import { set, withMarker, type FieldOp, type PlannedOp } from "../ops.js";
|
|
50
51
|
import { query } from "../query.js";
|
|
51
52
|
import { relationType, self, key } from "../relation_schema.js";
|
|
52
53
|
import { readRef, readSet, workspaceInvariant, ref } from "./workspace_invariant.js";
|
|
@@ -305,8 +306,8 @@ export const enrollSignerSelfRequiresVerified = workspaceInvariant("EnrollSigner
|
|
|
305
306
|
* DELEGATION CERT binding U's device key `P_U` to the tenant's lineage: a `BirthCert {founder: U,
|
|
306
307
|
* parent_key: P_U, …}` signed by `S_P`. This singleton (keyed by U) records it in the PLATFORM's folded
|
|
307
308
|
* state, so the kernel's birth offer-effect reads U's delegation by-user (trusted, not from the payload) when
|
|
308
|
-
* it assembles U's home cert chain.
|
|
309
|
-
*
|
|
309
|
+
* it assembles U's home cert chain. Every certificate member is stored as typed USDA fields; the kernel
|
|
310
|
+
* reconstructs and verifies the nominal link signed by S_P, whose key is attested by the platform chain.
|
|
310
311
|
*
|
|
311
312
|
* SELF-BOUND / control-plane: enrolled by the tenant onboarding lane (composed into the tenant's governance
|
|
312
313
|
* law, off the open session lane), so a tenant cannot forge a delegation for a user another tenant verified.
|
|
@@ -322,9 +323,21 @@ export const SignerDelegation = aggregate("SignerDelegation", {
|
|
|
322
323
|
user: t.string().merge(Lww),
|
|
323
324
|
/** The user's device PUBLIC KEY (base64) the delegation binds — `P_U`, the home_cert's signer. */
|
|
324
325
|
deviceKey: t.string().merge(Lww),
|
|
325
|
-
/**
|
|
326
|
-
|
|
327
|
-
|
|
326
|
+
/** Legacy replay slot for histories authored before the typed certificate fields below. */
|
|
327
|
+
certJson: t.string().merge(Lww).optional(),
|
|
328
|
+
certificateChild: t.string().merge(Lww).optional(),
|
|
329
|
+
certificateFounder: t.string().merge(Lww).optional(),
|
|
330
|
+
certificateVerified: t.bool().merge(Lww).optional(),
|
|
331
|
+
certificateParentKey: t.string().merge(Lww).optional(),
|
|
332
|
+
certificateParentId: t.string().merge(Lww).optional(),
|
|
333
|
+
certificateEpoch: t.int().merge(Lww).optional(),
|
|
334
|
+
certificateSignature: t.string().merge(Lww).optional(),
|
|
335
|
+
certificateVerifiedVia: t.string().merge(Lww).optional(),
|
|
336
|
+
certificateIdpToken: t.string().merge(Lww).optional(),
|
|
337
|
+
certificateCapturedClock: t.int().merge(Lww).optional(),
|
|
338
|
+
certificateAuthProviderIssuer: t.string().merge(Lww).optional(),
|
|
339
|
+
certificateAuthProviderJwks: t.string().merge(Lww).optional(),
|
|
340
|
+
certificateAuthProviderAudience: t.string().merge(Lww).optional(),
|
|
328
341
|
/** ISO-8601, caller-stamped (determinism — no clock in the plan). */
|
|
329
342
|
enrolledAt: t.string().merge(Lww),
|
|
330
343
|
}).public();
|
|
@@ -335,6 +348,29 @@ export function signerDelegationInstanceId(user: string): string {
|
|
|
335
348
|
return SIGNER_DELEGATION_PREFIX + user;
|
|
336
349
|
}
|
|
337
350
|
|
|
351
|
+
function writeDelegationCertificate(
|
|
352
|
+
delegation: BoundAggregate<typeof SignerDelegation.id, typeof SignerDelegation.fields>,
|
|
353
|
+
certificate: BirthCertificatePayload,
|
|
354
|
+
marker?: "ensures",
|
|
355
|
+
): PlannedOp[] {
|
|
356
|
+
const marked = (operation: FieldOp) => marker ? withMarker(operation, marker) : operation;
|
|
357
|
+
return [
|
|
358
|
+
marked(set(delegation, "certificateChild", certificate.child)),
|
|
359
|
+
marked(set(delegation, "certificateFounder", certificate.founder)),
|
|
360
|
+
marked(set(delegation, "certificateVerified", certificate.verified)),
|
|
361
|
+
marked(set(delegation, "certificateParentKey", certificate.parentKey)),
|
|
362
|
+
marked(set(delegation, "certificateParentId", certificate.parentId)),
|
|
363
|
+
marked(set(delegation, "certificateEpoch", certificate.epoch)),
|
|
364
|
+
marked(set(delegation, "certificateSignature", certificate.signature)),
|
|
365
|
+
...(certificate.verifiedVia ? [marked(set(delegation, "certificateVerifiedVia", certificate.verifiedVia))] : []),
|
|
366
|
+
...(certificate.idpToken ? [marked(set(delegation, "certificateIdpToken", certificate.idpToken))] : []),
|
|
367
|
+
...(certificate.capturedClock === undefined ? [] : [marked(set(delegation, "certificateCapturedClock", certificate.capturedClock))]),
|
|
368
|
+
...(certificate.authProviderIssuer ? [marked(set(delegation, "certificateAuthProviderIssuer", certificate.authProviderIssuer))] : []),
|
|
369
|
+
...(certificate.authProviderJwks ? [marked(set(delegation, "certificateAuthProviderJwks", certificate.authProviderJwks))] : []),
|
|
370
|
+
...(certificate.authProviderAudience ? [marked(set(delegation, "certificateAuthProviderAudience", certificate.authProviderAudience))] : []),
|
|
371
|
+
];
|
|
372
|
+
}
|
|
373
|
+
|
|
338
374
|
/**
|
|
339
375
|
* `enrollFirstDevice` (architecture/keyless_verified_onboard.md) — THE KEYLESS VERIFIED FIRST-DEVICE
|
|
340
376
|
* self-onboard. A brand-new principal (an IdP-verified user with NO enrolled signer key) bootstraps its
|
|
@@ -369,8 +405,11 @@ export const enrollFirstDevice = directive("enrollFirstDevice")
|
|
|
369
405
|
/** THE CAPTURED PREMISE: the IdP's signed identity assertion (JWT), re-verified on every lane against
|
|
370
406
|
* the on-chain AuthProvider keys. */
|
|
371
407
|
idpToken: z.string().min(1),
|
|
372
|
-
/**
|
|
373
|
-
*
|
|
408
|
+
/** Legacy replay input for callers predating `delegationCertificate`. New callers must provide the
|
|
409
|
+
* typed certificate below. */
|
|
410
|
+
delegationCert: z.string().min(1).optional(),
|
|
411
|
+
/** THE KEYLESS DELEGATION CERT — the self-serve sibling of `enrollDelegation.certificate`. Built
|
|
412
|
+
* CLIENT-SIDE via the typed kernel sign operation: DEVICE-self-signed by this
|
|
374
413
|
* same device key `P_U` (parent_key == P_U), founder == `user:<uid>`, carrying `verifiedVia` +
|
|
375
414
|
* `idpToken` + `capturedClock`. There is NO tenant `S_P` in the loop — the kernel's home-birth chain
|
|
376
415
|
* walk (`verify_to_root`, keyless branch) RE-VERIFIES the token against the platform-attested
|
|
@@ -380,7 +419,7 @@ export const enrollFirstDevice = directive("enrollFirstDevice")
|
|
|
380
419
|
* OPTIONAL — an enroll-only flow (seat the signer, no imminent home birth) omits it and seeds no
|
|
381
420
|
* delegation (backward-compatible with every pre-B `enrollFirstDevice` caller). The keyless home-birth
|
|
382
421
|
* ceremony ALWAYS supplies it (that is what seeds the delegation the chain needs). */
|
|
383
|
-
|
|
422
|
+
delegationCertificate: birthCertificatePayload.optional(),
|
|
384
423
|
}),
|
|
385
424
|
)
|
|
386
425
|
.plan((p, ctx) => {
|
|
@@ -404,14 +443,24 @@ export const enrollFirstDevice = directive("enrollFirstDevice")
|
|
|
404
443
|
// The delegation is keyed by the AUTHENTICATED principal — EXACTLY the `founder` the kernel's birth
|
|
405
444
|
// offer-effect reads it back by (`read_signer_delegation(state, &founder)`, founder == captured principal
|
|
406
445
|
// == this `rawPrincipal`, already in `user:<uid>` form). Do NOT re-prefix.
|
|
407
|
-
// Absent
|
|
408
|
-
if (!p.delegationCert) return tupleOps;
|
|
446
|
+
// Absent certificate ⇒ enroll-only (no delegation seeded), byte-identical to a pre-B enroll.
|
|
447
|
+
if (!p.delegationCert && !p.delegationCertificate) return tupleOps;
|
|
409
448
|
const d = instance(SignerDelegation, signerDelegationInstanceId(rawPrincipal));
|
|
410
449
|
const delegationOps: PlannedOp[] = [
|
|
411
450
|
withMarker(set(d, "user", rawPrincipal), "ensures"),
|
|
412
451
|
withMarker(set(d, "deviceKey", "key:" + p.keyHash), "ensures"),
|
|
413
|
-
|
|
414
|
-
|
|
452
|
+
...(p.delegationCertificate
|
|
453
|
+
? [
|
|
454
|
+
withMarker(set(d, "enrolledAt", p.grantedAt), "ensures"),
|
|
455
|
+
...writeDelegationCertificate(d, p.delegationCertificate, "ensures"),
|
|
456
|
+
]
|
|
457
|
+
: [
|
|
458
|
+
// Exact legacy event order is replay identity, even though Lww folding would commute these
|
|
459
|
+
// two writes. Historical enrollFirstDevice intents wrote certJson before enrolledAt; retain
|
|
460
|
+
// that byte-level outcome when re-authoring an old payload under a candidate law.
|
|
461
|
+
withMarker(set(d, "certJson", p.delegationCert!), "ensures"),
|
|
462
|
+
withMarker(set(d, "enrolledAt", p.grantedAt), "ensures"),
|
|
463
|
+
]),
|
|
415
464
|
];
|
|
416
465
|
return [...tupleOps, ...delegationOps];
|
|
417
466
|
})
|
|
@@ -466,7 +515,7 @@ export const revokeSigner = directive("revokeSigner")
|
|
|
466
515
|
* offer-effect can read U's delegation when it assembles U's home chain. `.ensures` (a deterministic-keyed
|
|
467
516
|
* singleton — upsert-by-user). A CONTROL-PLANE write composed into the tenant's onboarding governance law
|
|
468
517
|
* (off the open session lane) — only the tenant (whose `S_P` the platform chain attests) may enroll a
|
|
469
|
-
* delegation. The
|
|
518
|
+
* delegation. The typed certificate is verified by the kernel at home birth (signed by S_P, founder == U); a forged
|
|
470
519
|
* delegation simply fails the chain walk / founder-binding at the child's gate.
|
|
471
520
|
*/
|
|
472
521
|
export const enrollDelegation = directive("enrollDelegation")
|
|
@@ -475,7 +524,7 @@ export const enrollDelegation = directive("enrollDelegation")
|
|
|
475
524
|
z.object({
|
|
476
525
|
user: z.string().min(1), // the user:<uid> the delegation enrolls
|
|
477
526
|
deviceKey: z.string().min(1), // base64 P_U — the user's device public key
|
|
478
|
-
|
|
527
|
+
certificate: birthCertificatePayload,
|
|
479
528
|
enrolledAt: z.string().min(1), // ISO-8601, caller-stamped
|
|
480
529
|
}),
|
|
481
530
|
)
|
|
@@ -484,8 +533,8 @@ export const enrollDelegation = directive("enrollDelegation")
|
|
|
484
533
|
return [
|
|
485
534
|
set(d, "user", p.user),
|
|
486
535
|
set(d, "deviceKey", p.deviceKey),
|
|
487
|
-
set(d, "certJson", p.certJson),
|
|
488
536
|
set(d, "enrolledAt", p.enrolledAt),
|
|
537
|
+
...writeDelegationCertificate(d, p.certificate),
|
|
489
538
|
];
|
|
490
539
|
});
|
|
491
540
|
|
|
@@ -106,7 +106,14 @@ export { enrollFirstDevice, revokeSigner } from "./signing_identity.js";
|
|
|
106
106
|
// `[platform_cert]` chain would fail `verify_to_root` (>=2 links), so its own verify doors must stay inert
|
|
107
107
|
// while it still carries the cert for its children. Fully additive (new aggregate + directive, no era key ⇒
|
|
108
108
|
// pre-existing chains replay byte-identical). See architecture/verified_auth_from_genesis.md.
|
|
109
|
-
export {
|
|
109
|
+
export {
|
|
110
|
+
BIRTH_ATTESTATION_ID,
|
|
111
|
+
BIRTH_ATTESTATION_LINK_PREFIX,
|
|
112
|
+
BirthAttestation,
|
|
113
|
+
BirthAttestationLink,
|
|
114
|
+
birthAttestation,
|
|
115
|
+
seedBirthAttestation,
|
|
116
|
+
} from "./home.js";
|
|
110
117
|
// THE ENROLL-ONCE DELEGATION LANE — a platform records each tenant's device-key delegation (signed by its
|
|
111
118
|
// onboarding signer S_P) so the kernel reads it at `signer-delegation:<user>` when assembling a home's chain.
|
|
112
119
|
export { SignerDelegation, enrollDelegation, signerDelegationByUser } from "./signing_identity.js";
|
package/src/index.ts
CHANGED
|
@@ -566,7 +566,9 @@ export {
|
|
|
566
566
|
birthReceivingGroup,
|
|
567
567
|
// THE PEER-SOVEREIGN ROOT-OF-TRUST (the BirthAttestation cert chain).
|
|
568
568
|
BirthAttestation,
|
|
569
|
+
BirthAttestationLink,
|
|
569
570
|
BIRTH_ATTESTATION_ID,
|
|
571
|
+
BIRTH_ATTESTATION_LINK_PREFIX,
|
|
570
572
|
seedBirthAttestation,
|
|
571
573
|
birthAttestation,
|
|
572
574
|
parentAttestedBirth,
|
package/src/shape_compat.ts
CHANGED
|
@@ -213,16 +213,19 @@ export function domainInterface(mod: DomainModule, stableIds?: StableIds): Domai
|
|
|
213
213
|
for (const agg of (mod.aggregates ?? []) as Array<{
|
|
214
214
|
id: string;
|
|
215
215
|
fields: Record<string, Field>;
|
|
216
|
-
internalFields?: readonly string[];
|
|
217
216
|
}>) {
|
|
218
217
|
if (isInternal(agg)) continue;
|
|
219
218
|
const label = agg.id;
|
|
220
219
|
const sa = stableIds?.aggregates?.[label];
|
|
221
220
|
const aggSid = sa?.sid ?? label;
|
|
222
221
|
const fieldEntries: Array<[string, { kind: string; label: string }]> = [];
|
|
223
|
-
const internal = new Set(agg.internalFields ?? []);
|
|
224
222
|
for (const [fname, f] of Object.entries(agg.fields ?? {})) {
|
|
225
|
-
|
|
223
|
+
// This is the workspace's complete structural offer and the input to law-evolution
|
|
224
|
+
// checking, so framework-managed stored fields belong here too. Only the generated
|
|
225
|
+
// client's view hides them, in publicClientInterface below. Filtering them at both
|
|
226
|
+
// seams makes an unchanged private field look deleted when the current offer is
|
|
227
|
+
// compared with the committed identity ledger.
|
|
228
|
+
if (!f || f.kind === "hasMany") continue;
|
|
226
229
|
const fsid = sa?.fields?.[fname]?.sid ?? fname;
|
|
227
230
|
const kind = sa?.fields?.[fname]?.kind ?? aggFieldReadKind(f);
|
|
228
231
|
fieldEntries.push([fsid, { kind, label: fname }]);
|