@fides-anima/fpp-protocol-core 1.0.2
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/LICENSE +95 -0
- package/README.md +41 -0
- package/SKILL.md +348 -0
- package/constitution.json +102 -0
- package/constitution.yaml +106 -0
- package/dist/adoption-disclosure.d.ts +35 -0
- package/dist/adoption-disclosure.d.ts.map +1 -0
- package/dist/adoption-disclosure.js +67 -0
- package/dist/adoption-disclosure.js.map +1 -0
- package/dist/adoption.d.ts +56 -0
- package/dist/adoption.d.ts.map +1 -0
- package/dist/adoption.js +97 -0
- package/dist/adoption.js.map +1 -0
- package/dist/canonical-json.d.ts +23 -0
- package/dist/canonical-json.d.ts.map +1 -0
- package/dist/canonical-json.js +60 -0
- package/dist/canonical-json.js.map +1 -0
- package/dist/capsules.d.ts +63 -0
- package/dist/capsules.d.ts.map +1 -0
- package/dist/capsules.js +94 -0
- package/dist/capsules.js.map +1 -0
- package/dist/claims.d.ts +62 -0
- package/dist/claims.d.ts.map +1 -0
- package/dist/claims.js +99 -0
- package/dist/claims.js.map +1 -0
- package/dist/digest.d.ts +38 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +52 -0
- package/dist/digest.js.map +1 -0
- package/dist/disposition.d.ts +34 -0
- package/dist/disposition.d.ts.map +1 -0
- package/dist/disposition.js +51 -0
- package/dist/disposition.js.map +1 -0
- package/dist/emergency-override.d.ts +73 -0
- package/dist/emergency-override.d.ts.map +1 -0
- package/dist/emergency-override.js +102 -0
- package/dist/emergency-override.js.map +1 -0
- package/dist/evidence.d.ts +25 -0
- package/dist/evidence.d.ts.map +1 -0
- package/dist/evidence.js +30 -0
- package/dist/evidence.js.map +1 -0
- package/dist/freshness.d.ts +41 -0
- package/dist/freshness.d.ts.map +1 -0
- package/dist/freshness.js +66 -0
- package/dist/freshness.js.map +1 -0
- package/dist/governance.d.ts +87 -0
- package/dist/governance.d.ts.map +1 -0
- package/dist/governance.js +94 -0
- package/dist/governance.js.map +1 -0
- package/dist/identity.d.ts +43 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +93 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/mandates.d.ts +80 -0
- package/dist/mandates.d.ts.map +1 -0
- package/dist/mandates.js +127 -0
- package/dist/mandates.js.map +1 -0
- package/dist/merkle.d.ts +31 -0
- package/dist/merkle.d.ts.map +1 -0
- package/dist/merkle.js +95 -0
- package/dist/merkle.js.map +1 -0
- package/dist/quorum.d.ts +133 -0
- package/dist/quorum.d.ts.map +1 -0
- package/dist/quorum.js +128 -0
- package/dist/quorum.js.map +1 -0
- package/dist/receipts.d.ts +77 -0
- package/dist/receipts.d.ts.map +1 -0
- package/dist/receipts.js +104 -0
- package/dist/receipts.js.map +1 -0
- package/dist/steward-authorization.d.ts +200 -0
- package/dist/steward-authorization.d.ts.map +1 -0
- package/dist/steward-authorization.js +483 -0
- package/dist/steward-authorization.js.map +1 -0
- package/dist/workspace-profile.d.ts +43 -0
- package/dist/workspace-profile.d.ts.map +1 -0
- package/dist/workspace-profile.js +91 -0
- package/dist/workspace-profile.js.map +1 -0
- package/package.json +56 -0
- package/src/adoption-disclosure.ts +120 -0
- package/src/adoption.ts +152 -0
- package/src/canonical-json.ts +70 -0
- package/src/capsules.ts +161 -0
- package/src/claims.ts +147 -0
- package/src/digest.ts +63 -0
- package/src/disposition.ts +74 -0
- package/src/emergency-override.ts +168 -0
- package/src/evidence.ts +48 -0
- package/src/freshness.ts +103 -0
- package/src/governance.ts +152 -0
- package/src/identity.ts +119 -0
- package/src/index.ts +270 -0
- package/src/mandates.ts +201 -0
- package/src/merkle.ts +138 -0
- package/src/quorum.ts +193 -0
- package/src/receipts.ts +150 -0
- package/src/steward-authorization.ts +657 -0
- package/src/workspace-profile.ts +134 -0
package/src/identity.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key-bound agent identity primitives.
|
|
3
|
+
*
|
|
4
|
+
* V2 agent IDs are full key fingerprints: `fpp:ed25519:<sha256(pubkey)>`.
|
|
5
|
+
* Legacy truncated aliases (`fpp-<16 hex>`) remain parseable for display and
|
|
6
|
+
* migration but are never independent proof of identity.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { sha256 } from "@noble/hashes/sha256";
|
|
10
|
+
import { bytesToHex } from "@noble/hashes/utils";
|
|
11
|
+
import * as ed from "@noble/ed25519";
|
|
12
|
+
import { sha512 } from "@noble/hashes/sha512";
|
|
13
|
+
|
|
14
|
+
ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));
|
|
15
|
+
|
|
16
|
+
export const KEY_ALGORITHM = "ed25519" as const;
|
|
17
|
+
|
|
18
|
+
export type ParsedAgentId =
|
|
19
|
+
| { kind: "v2"; algorithm: typeof KEY_ALGORITHM; fingerprint: string; raw: string }
|
|
20
|
+
| { kind: "legacy-alias"; fingerprintPrefix: string; raw: string }
|
|
21
|
+
| { kind: "unknown"; raw: string };
|
|
22
|
+
|
|
23
|
+
function decodePublicKeyHex(publicKeyHex: string): Uint8Array {
|
|
24
|
+
if (!/^[0-9a-fA-F]+$/.test(publicKeyHex)) {
|
|
25
|
+
throw new Error("public key must be hex");
|
|
26
|
+
}
|
|
27
|
+
if (publicKeyHex.length !== 64) {
|
|
28
|
+
throw new Error("public key must be 32 bytes (64 hex chars)");
|
|
29
|
+
}
|
|
30
|
+
const bytes = new Uint8Array(32);
|
|
31
|
+
for (let i = 0; i < 32; i++) {
|
|
32
|
+
bytes[i] = Number.parseInt(publicKeyHex.slice(i * 2, i * 2 + 2), 16);
|
|
33
|
+
}
|
|
34
|
+
return bytes;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** SHA-256 fingerprint of the raw public key bytes (64 hex chars). */
|
|
38
|
+
export function fingerprintPublicKey(publicKeyHex: string): string {
|
|
39
|
+
const bytes = decodePublicKeyHex(publicKeyHex);
|
|
40
|
+
return bytesToHex(sha256(bytes));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** V2 agent identifier bound to the full public-key fingerprint. */
|
|
44
|
+
export function deriveAgentIdV2(publicKeyHex: string): string {
|
|
45
|
+
return `fpp:${KEY_ALGORITHM}:${fingerprintPublicKey(publicKeyHex)}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Historical truncated alias: `fpp-` + first 16 hex chars of SHA-256(pubkey).
|
|
50
|
+
* Labeled for migration/display only — not independent identity proof.
|
|
51
|
+
*/
|
|
52
|
+
export function deriveLegacyAlias(publicKeyHex: string): string {
|
|
53
|
+
return "fpp-" + fingerprintPublicKey(publicKeyHex).slice(0, 16);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function isLegacyAgentAlias(agentId: string): boolean {
|
|
57
|
+
return /^fpp-[0-9a-f]{16}$/i.test(agentId);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function parseAgentId(agentId: string): ParsedAgentId {
|
|
61
|
+
const v2 = /^fpp:(ed25519):([0-9a-f]{64})$/i.exec(agentId);
|
|
62
|
+
if (v2) {
|
|
63
|
+
return {
|
|
64
|
+
kind: "v2",
|
|
65
|
+
algorithm: KEY_ALGORITHM,
|
|
66
|
+
fingerprint: v2[2]!.toLowerCase(),
|
|
67
|
+
raw: agentId,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const legacy = /^fpp-([0-9a-f]{16})$/i.exec(agentId);
|
|
71
|
+
if (legacy) {
|
|
72
|
+
return {
|
|
73
|
+
kind: "legacy-alias",
|
|
74
|
+
fingerprintPrefix: legacy[1]!.toLowerCase(),
|
|
75
|
+
raw: agentId,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return { kind: "unknown", raw: agentId };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Recompute the expected identifier from the public key and compare.
|
|
83
|
+
* Legacy aliases match only when `allowLegacyAlias` is explicitly set.
|
|
84
|
+
*/
|
|
85
|
+
export function publicKeyMatchesAgentId(
|
|
86
|
+
agentId: string,
|
|
87
|
+
publicKeyHex: string,
|
|
88
|
+
options: { allowLegacyAlias?: boolean } = {},
|
|
89
|
+
): boolean {
|
|
90
|
+
const expectedV2 = deriveAgentIdV2(publicKeyHex);
|
|
91
|
+
if (agentId === expectedV2) return true;
|
|
92
|
+
if (options.allowLegacyAlias === true) {
|
|
93
|
+
return agentId === deriveLegacyAlias(publicKeyHex);
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function verifySignature(
|
|
99
|
+
message: Uint8Array,
|
|
100
|
+
signature: Uint8Array,
|
|
101
|
+
publicKey: Uint8Array,
|
|
102
|
+
): boolean {
|
|
103
|
+
try {
|
|
104
|
+
return ed.verify(signature, message, publicKey);
|
|
105
|
+
} catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function signMessage(
|
|
111
|
+
message: Uint8Array,
|
|
112
|
+
privateKey: Uint8Array,
|
|
113
|
+
): Uint8Array {
|
|
114
|
+
return ed.sign(message, privateKey);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function publicKeyFromSeed(seed: Uint8Array): Uint8Array {
|
|
118
|
+
return ed.getPublicKey(seed);
|
|
119
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fides-anima/fpp-protocol-core — shared schemas and cryptographic contracts
|
|
3
|
+
* for the Freedom Preserving Protocol.
|
|
4
|
+
*
|
|
5
|
+
* Package version and protocol schema version are independent:
|
|
6
|
+
* this package release carries schema version 2.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const PACKAGE_NAME = "@fides-anima/fpp-protocol-core" as const;
|
|
10
|
+
export const PACKAGE_VERSION = "1.0.2" as const;
|
|
11
|
+
/** Protocol schema version carried by this package release. */
|
|
12
|
+
export const SCHEMA_VERSION = 2 as const;
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
canonicalize,
|
|
16
|
+
canonicalizeV1,
|
|
17
|
+
canonicalizeV2,
|
|
18
|
+
} from "./canonical-json.js";
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
DIGEST_DOMAINS,
|
|
22
|
+
digest,
|
|
23
|
+
hashEntryV1,
|
|
24
|
+
type DigestDomain,
|
|
25
|
+
type DigestInput,
|
|
26
|
+
} from "./digest.js";
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
computeMerkleRoot,
|
|
30
|
+
computeMerkleRootV1,
|
|
31
|
+
computeMerkleRootV2,
|
|
32
|
+
createMerkleProof,
|
|
33
|
+
createMerkleProofV1,
|
|
34
|
+
createMerkleProofV2,
|
|
35
|
+
hashPairV1,
|
|
36
|
+
hashPairV2,
|
|
37
|
+
verifyMerkleProof,
|
|
38
|
+
verifyMerkleProofV1,
|
|
39
|
+
verifyMerkleProofV2,
|
|
40
|
+
type MerkleProof,
|
|
41
|
+
type MerkleProofStep,
|
|
42
|
+
} from "./merkle.js";
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
KEY_ALGORITHM,
|
|
46
|
+
deriveAgentIdV2,
|
|
47
|
+
deriveLegacyAlias,
|
|
48
|
+
fingerprintPublicKey,
|
|
49
|
+
isLegacyAgentAlias,
|
|
50
|
+
parseAgentId,
|
|
51
|
+
publicKeyFromSeed,
|
|
52
|
+
publicKeyMatchesAgentId,
|
|
53
|
+
signMessage,
|
|
54
|
+
verifySignature,
|
|
55
|
+
type ParsedAgentId,
|
|
56
|
+
} from "./identity.js";
|
|
57
|
+
|
|
58
|
+
export {
|
|
59
|
+
STEWARD_DIGEST_DOMAINS,
|
|
60
|
+
KeyRefSchema,
|
|
61
|
+
OperatorAuthorizationRevocationV1Schema,
|
|
62
|
+
OperatorAuthorizationV1Schema,
|
|
63
|
+
StewardBootstrapPolicyCapsV1Schema,
|
|
64
|
+
StewardBootstrapV1Schema,
|
|
65
|
+
StewardKeyAttestationV1Schema,
|
|
66
|
+
attestationSigningFields,
|
|
67
|
+
authorizationRevocationSigningFields,
|
|
68
|
+
authorizationSigningFields,
|
|
69
|
+
bootstrapSigningFields,
|
|
70
|
+
buildStewardEvidenceDigest,
|
|
71
|
+
buildStewardReplayDigest,
|
|
72
|
+
containsOpenPgpSecretKeyArmor,
|
|
73
|
+
isStewardIdV1,
|
|
74
|
+
mintStewardIdV1,
|
|
75
|
+
parseKeyRef,
|
|
76
|
+
parseOperatorAuthorization,
|
|
77
|
+
parseOperatorAuthorizationRevocation,
|
|
78
|
+
parseStewardBootstrap,
|
|
79
|
+
parseStewardIdV1,
|
|
80
|
+
parseStewardKeyAttestation,
|
|
81
|
+
validateOperatorAuthorizationBounds,
|
|
82
|
+
type BoundsResult,
|
|
83
|
+
type KeyRefParseResult,
|
|
84
|
+
type OperatorAuthorizationParseResult,
|
|
85
|
+
type OperatorAuthorizationRevocationParseResult,
|
|
86
|
+
type OperatorAuthorizationRevocationV1,
|
|
87
|
+
type OperatorAuthorizationV1,
|
|
88
|
+
type ParsedKeyRef,
|
|
89
|
+
type ParsedStewardIdV1,
|
|
90
|
+
type StewardBootstrapParseResult,
|
|
91
|
+
type StewardBootstrapPolicyCapsV1,
|
|
92
|
+
type StewardBootstrapV1,
|
|
93
|
+
type StewardIdParseResult,
|
|
94
|
+
type StewardIdV1,
|
|
95
|
+
type StewardKeyAttestationParseResult,
|
|
96
|
+
type StewardKeyAttestationV1,
|
|
97
|
+
} from "./steward-authorization.js";
|
|
98
|
+
|
|
99
|
+
export {
|
|
100
|
+
CLAIM_CLASSES,
|
|
101
|
+
ConstitutionalClaimV2Schema,
|
|
102
|
+
LegacyConstitutionalClaimV1Schema,
|
|
103
|
+
parseClaim,
|
|
104
|
+
type ClaimClass,
|
|
105
|
+
type ClaimParseResult,
|
|
106
|
+
type ConstitutionalClaimV2,
|
|
107
|
+
type LegacyConstitutionalClaimV1,
|
|
108
|
+
} from "./claims.js";
|
|
109
|
+
|
|
110
|
+
export {
|
|
111
|
+
FreshnessEnvelopeSchema,
|
|
112
|
+
buildReplayKey,
|
|
113
|
+
parseFreshnessEnvelope,
|
|
114
|
+
validateFreshness,
|
|
115
|
+
type FreshnessEnvelope,
|
|
116
|
+
type FreshnessParseResult,
|
|
117
|
+
type FreshnessPolicy,
|
|
118
|
+
type FreshnessValidation,
|
|
119
|
+
} from "./freshness.js";
|
|
120
|
+
|
|
121
|
+
export {
|
|
122
|
+
ConformanceReceiptV1Schema,
|
|
123
|
+
ReceiptInclusionEvidenceV1Schema,
|
|
124
|
+
parseConformanceReceipt,
|
|
125
|
+
parseReceiptInclusionEvidence,
|
|
126
|
+
type ConformanceReceiptV1,
|
|
127
|
+
type ReceiptInclusionEvidenceParseResult,
|
|
128
|
+
type ReceiptInclusionEvidenceV1,
|
|
129
|
+
type ReceiptParseResult,
|
|
130
|
+
} from "./receipts.js";
|
|
131
|
+
|
|
132
|
+
export {
|
|
133
|
+
GOVERNANCE_EVENT_KINDS,
|
|
134
|
+
GOVERNANCE_MODES,
|
|
135
|
+
GovernanceActorSchema,
|
|
136
|
+
GovernanceEpochSchema,
|
|
137
|
+
GovernanceEventSignatureSchema,
|
|
138
|
+
GovernanceEventV1Schema,
|
|
139
|
+
GovernanceModeSchema,
|
|
140
|
+
GovernanceStateV1Schema,
|
|
141
|
+
isGovernanceMode,
|
|
142
|
+
parseGovernanceEpoch,
|
|
143
|
+
parseGovernanceEvent,
|
|
144
|
+
parseGovernanceState,
|
|
145
|
+
type GovernanceEpochParseResult,
|
|
146
|
+
type GovernanceEventKind,
|
|
147
|
+
type GovernanceEventParseResult,
|
|
148
|
+
type GovernanceEventV1,
|
|
149
|
+
type GovernanceMode,
|
|
150
|
+
type GovernanceStateParseResult,
|
|
151
|
+
type GovernanceStateV1,
|
|
152
|
+
} from "./governance.js";
|
|
153
|
+
|
|
154
|
+
export {
|
|
155
|
+
MANDATE_ISSUER_CLASSES,
|
|
156
|
+
StandingMandateV1Schema,
|
|
157
|
+
mandateSigningFields,
|
|
158
|
+
parseStandingMandate,
|
|
159
|
+
validateMandateValidity,
|
|
160
|
+
verifyMandateSignature,
|
|
161
|
+
type MandateIssuerClass,
|
|
162
|
+
type MandateLedgerEntry,
|
|
163
|
+
type MandateParseResult,
|
|
164
|
+
type MandateStoreFile,
|
|
165
|
+
type MandateValidity,
|
|
166
|
+
type MandateValidityPolicy,
|
|
167
|
+
type StandingMandateV1,
|
|
168
|
+
} from "./mandates.js";
|
|
169
|
+
|
|
170
|
+
export {
|
|
171
|
+
SignedEmergencyOverrideV1Schema,
|
|
172
|
+
emergencyOverrideSigningFields,
|
|
173
|
+
parseSignedEmergencyOverride,
|
|
174
|
+
validateEmergencyOverrideValidity,
|
|
175
|
+
verifyEmergencyOverrideSignature,
|
|
176
|
+
type EmergencyOverrideLedgerEntry,
|
|
177
|
+
type EmergencyOverrideParseResult,
|
|
178
|
+
type EmergencyOverrideStoreFile,
|
|
179
|
+
type EmergencyOverrideValidity,
|
|
180
|
+
type EmergencyOverrideValidityPolicy,
|
|
181
|
+
type SignedEmergencyOverrideV1,
|
|
182
|
+
} from "./emergency-override.js";
|
|
183
|
+
|
|
184
|
+
export {
|
|
185
|
+
AUTHORIZATION_CLASSES,
|
|
186
|
+
AUTHZ,
|
|
187
|
+
DISPOSITION_DECISIONS,
|
|
188
|
+
isAuthorizationClass,
|
|
189
|
+
isDispositionDecision,
|
|
190
|
+
parseDispositionDecision,
|
|
191
|
+
type AuthorizationClass,
|
|
192
|
+
type DispositionDecision,
|
|
193
|
+
type DispositionParseResult,
|
|
194
|
+
} from "./disposition.js";
|
|
195
|
+
|
|
196
|
+
export {
|
|
197
|
+
CapsuleAdoptionDisclosureSummarySchema,
|
|
198
|
+
TrustStateCapsuleV2Schema,
|
|
199
|
+
parseTrustStateCapsule,
|
|
200
|
+
validateCapsuleAdoptionConsistency,
|
|
201
|
+
type CapsuleAdoptionConsistency,
|
|
202
|
+
type CapsuleAdoptionDisclosureSummary,
|
|
203
|
+
type CapsuleParseResult,
|
|
204
|
+
type TrustStateCapsuleV2,
|
|
205
|
+
} from "./capsules.js";
|
|
206
|
+
|
|
207
|
+
export {
|
|
208
|
+
ADOPTION_OVERLAY_FLAGS,
|
|
209
|
+
ADOPTION_STATES,
|
|
210
|
+
AdoptionStateRecordV1Schema,
|
|
211
|
+
AdoptionStateRecordV2Schema,
|
|
212
|
+
ENFORCEMENT_GRADES,
|
|
213
|
+
parseAdoptionStateRecord,
|
|
214
|
+
type AdoptionOverlayFlag,
|
|
215
|
+
type AdoptionParseResult,
|
|
216
|
+
type AdoptionState,
|
|
217
|
+
type AdoptionStateRecord,
|
|
218
|
+
type AdoptionStateRecordV1,
|
|
219
|
+
type AdoptionStateRecordV2,
|
|
220
|
+
type EnforcementGrade,
|
|
221
|
+
} from "./adoption.js";
|
|
222
|
+
|
|
223
|
+
export {
|
|
224
|
+
ADOPTION_ASSURANCE_CLASSES,
|
|
225
|
+
AdoptionDisclosureV1Schema,
|
|
226
|
+
maxJustifiedConclusion,
|
|
227
|
+
parseAdoptionDisclosure,
|
|
228
|
+
type AdoptionAssurance,
|
|
229
|
+
type AdoptionDisclosureParseResult,
|
|
230
|
+
type AdoptionDisclosureV1,
|
|
231
|
+
} from "./adoption-disclosure.js";
|
|
232
|
+
|
|
233
|
+
export {
|
|
234
|
+
EvidenceEnvelopeV1Schema,
|
|
235
|
+
parseEvidenceEnvelope,
|
|
236
|
+
type EvidenceEnvelopeV1,
|
|
237
|
+
type EvidenceParseResult,
|
|
238
|
+
} from "./evidence.js";
|
|
239
|
+
|
|
240
|
+
export {
|
|
241
|
+
QUORUM_CLASSES,
|
|
242
|
+
QUORUM_VOTES,
|
|
243
|
+
QuorumBallotV1Schema,
|
|
244
|
+
QuorumEvidencePackageV1Schema,
|
|
245
|
+
QuorumProposalV1Schema,
|
|
246
|
+
computeQuorumEvidenceDigest,
|
|
247
|
+
parseQuorumBallot,
|
|
248
|
+
parseQuorumEvidencePackage,
|
|
249
|
+
parseQuorumProposal,
|
|
250
|
+
validateBallotAgainstProposal,
|
|
251
|
+
type BallotMatchResult,
|
|
252
|
+
type QuorumBallotParseResult,
|
|
253
|
+
type QuorumBallotV1,
|
|
254
|
+
type QuorumClass,
|
|
255
|
+
type QuorumEvidencePackageV1,
|
|
256
|
+
type QuorumEvidenceParseResult,
|
|
257
|
+
type QuorumProposalParseResult,
|
|
258
|
+
type QuorumProposalV1,
|
|
259
|
+
type QuorumVote,
|
|
260
|
+
} from "./quorum.js";
|
|
261
|
+
|
|
262
|
+
export {
|
|
263
|
+
DEFAULT_WORKSPACE_PROFILE,
|
|
264
|
+
resolveWorkspaceRoot,
|
|
265
|
+
workspaceFile,
|
|
266
|
+
absolutizeWorkspacePath,
|
|
267
|
+
type ResolveWorkspaceOptions,
|
|
268
|
+
type AbsolutizeWorkspaceOptions,
|
|
269
|
+
type WorkspaceProfileId,
|
|
270
|
+
} from "./workspace-profile.js";
|
package/src/mandates.ts
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standing mandate schema — signed or allowlist-backed authorization
|
|
3
|
+
* consumed by the unattended disposition engine.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Type, type Static } from "@sinclair/typebox";
|
|
7
|
+
import { Value } from "@sinclair/typebox/value";
|
|
8
|
+
import { canonicalizeV2 } from "./canonical-json.js";
|
|
9
|
+
import { verifySignature } from "./identity.js";
|
|
10
|
+
|
|
11
|
+
export const MANDATE_ISSUER_CLASSES = [
|
|
12
|
+
"operator",
|
|
13
|
+
"peer-quorum",
|
|
14
|
+
"steward-quorum",
|
|
15
|
+
"standing-allowlist",
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
export type MandateIssuerClass = (typeof MANDATE_ISSUER_CLASSES)[number];
|
|
19
|
+
|
|
20
|
+
export const StandingMandateV1Schema = Type.Object(
|
|
21
|
+
{
|
|
22
|
+
schemaVersion: Type.Literal(1),
|
|
23
|
+
mandateId: Type.String({ minLength: 1 }),
|
|
24
|
+
issuerClass: Type.Union(
|
|
25
|
+
MANDATE_ISSUER_CLASSES.map((c) => Type.Literal(c)) as [
|
|
26
|
+
ReturnType<typeof Type.Literal<(typeof MANDATE_ISSUER_CLASSES)[number]>>,
|
|
27
|
+
...ReturnType<
|
|
28
|
+
typeof Type.Literal<(typeof MANDATE_ISSUER_CLASSES)[number]>
|
|
29
|
+
>[],
|
|
30
|
+
],
|
|
31
|
+
),
|
|
32
|
+
issuerId: Type.String({ minLength: 1 }),
|
|
33
|
+
scope: Type.Object(
|
|
34
|
+
{
|
|
35
|
+
classifications: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
36
|
+
capabilities: Type.Optional(Type.Array(Type.String({ minLength: 1 }))),
|
|
37
|
+
},
|
|
38
|
+
{ additionalProperties: false },
|
|
39
|
+
),
|
|
40
|
+
budgets: Type.Object(
|
|
41
|
+
{
|
|
42
|
+
maxActions: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
43
|
+
remainingActions: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
44
|
+
},
|
|
45
|
+
{ additionalProperties: false },
|
|
46
|
+
),
|
|
47
|
+
validFrom: Type.String({ minLength: 1 }),
|
|
48
|
+
validTo: Type.String({ minLength: 1 }),
|
|
49
|
+
revocable: Type.Boolean(),
|
|
50
|
+
revoked: Type.Optional(Type.Boolean()),
|
|
51
|
+
evidenceRef: Type.String({ minLength: 1 }),
|
|
52
|
+
quorumRef: Type.Optional(Type.String({ minLength: 1 })),
|
|
53
|
+
publicKey: Type.Optional(Type.String({ minLength: 1 })),
|
|
54
|
+
signature: Type.Optional(Type.String({ minLength: 1 })),
|
|
55
|
+
},
|
|
56
|
+
{ additionalProperties: false },
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
export type StandingMandateV1 = Static<typeof StandingMandateV1Schema>;
|
|
60
|
+
|
|
61
|
+
/** Mutable runtime counters — never part of the signed grant payload. */
|
|
62
|
+
export type MandateLedgerEntry = {
|
|
63
|
+
remainingActions?: number;
|
|
64
|
+
revoked?: boolean;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Shared on-disk mandate store shape (enforcement-core + trust-core).
|
|
69
|
+
* `ledgers` is additive; absent keys mean unlimited budget / not revoked.
|
|
70
|
+
*/
|
|
71
|
+
export type MandateStoreFile = {
|
|
72
|
+
schemaVersion: 1;
|
|
73
|
+
mandates: StandingMandateV1[];
|
|
74
|
+
ledgers?: Record<string, MandateLedgerEntry>;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export type MandateParseResult =
|
|
78
|
+
| { ok: true; mandate: StandingMandateV1 }
|
|
79
|
+
| { ok: false; error: string };
|
|
80
|
+
|
|
81
|
+
export type MandateValidityPolicy = {
|
|
82
|
+
/** Verifier clock — not signer-controlled. */
|
|
83
|
+
nowMs: number;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export type MandateValidity = {
|
|
87
|
+
valid: boolean;
|
|
88
|
+
reason: string;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export function parseStandingMandate(input: unknown): MandateParseResult {
|
|
92
|
+
if (!Value.Check(StandingMandateV1Schema, input)) {
|
|
93
|
+
const errors = [...Value.Errors(StandingMandateV1Schema, input)];
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
error:
|
|
97
|
+
errors[0] !== undefined
|
|
98
|
+
? `${errors[0].path}: ${errors[0].message}`
|
|
99
|
+
: "invalid StandingMandateV1",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return { ok: true, mandate: input };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Temporal / revocation check for a schema-valid mandate.
|
|
107
|
+
* Budget debit and signature verification live in the plugin mandate store.
|
|
108
|
+
*/
|
|
109
|
+
export function validateMandateValidity(
|
|
110
|
+
mandate: StandingMandateV1,
|
|
111
|
+
policy: MandateValidityPolicy,
|
|
112
|
+
): MandateValidity {
|
|
113
|
+
if (mandate.revoked === true) {
|
|
114
|
+
return { valid: false, reason: "mandate revoked" };
|
|
115
|
+
}
|
|
116
|
+
const from = Date.parse(mandate.validFrom);
|
|
117
|
+
const to = Date.parse(mandate.validTo);
|
|
118
|
+
if (Number.isNaN(from) || Number.isNaN(to)) {
|
|
119
|
+
return { valid: false, reason: "validFrom/validTo must be ISO-8601" };
|
|
120
|
+
}
|
|
121
|
+
if (to <= from) {
|
|
122
|
+
return { valid: false, reason: "validTo must be after validFrom" };
|
|
123
|
+
}
|
|
124
|
+
if (policy.nowMs < from) {
|
|
125
|
+
return { valid: false, reason: "mandate not yet valid (validFrom)" };
|
|
126
|
+
}
|
|
127
|
+
if (policy.nowMs > to) {
|
|
128
|
+
return { valid: false, reason: "mandate expired" };
|
|
129
|
+
}
|
|
130
|
+
return { valid: true, reason: "ok" };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Canonical unsigned payload for new mandate signatures.
|
|
135
|
+
* Excludes `signature`, `revoked`, and `budgets.remainingActions` so debit/revoke
|
|
136
|
+
* can mutate an unsigned ledger without invalidating the grant.
|
|
137
|
+
*/
|
|
138
|
+
export function mandateSigningFields(
|
|
139
|
+
mandate: StandingMandateV1,
|
|
140
|
+
): Record<string, unknown> {
|
|
141
|
+
const {
|
|
142
|
+
signature: _signature,
|
|
143
|
+
revoked: _revoked,
|
|
144
|
+
budgets,
|
|
145
|
+
...rest
|
|
146
|
+
} = mandate;
|
|
147
|
+
void _signature;
|
|
148
|
+
void _revoked;
|
|
149
|
+
const signingBudgets: Record<string, unknown> = {};
|
|
150
|
+
if (budgets.maxActions !== undefined) {
|
|
151
|
+
signingBudgets.maxActions = budgets.maxActions;
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
...rest,
|
|
155
|
+
budgets: signingBudgets,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function legacySigningFields(
|
|
160
|
+
mandate: StandingMandateV1,
|
|
161
|
+
): Record<string, unknown> {
|
|
162
|
+
const { signature: _signature, ...unsigned } = mandate;
|
|
163
|
+
void _signature;
|
|
164
|
+
return unsigned as Record<string, unknown>;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function tryVerifyMandatePayload(
|
|
168
|
+
mandate: StandingMandateV1,
|
|
169
|
+
payload: Record<string, unknown>,
|
|
170
|
+
): boolean {
|
|
171
|
+
if (!mandate.publicKey || !mandate.signature) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
const message = Buffer.from(canonicalizeV2(payload), "utf8");
|
|
175
|
+
const sigBytes = Buffer.from(mandate.signature, "hex");
|
|
176
|
+
const pubBytes = Buffer.from(mandate.publicKey, "hex");
|
|
177
|
+
if (sigBytes.length !== 64 || pubBytes.length !== 32) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
return verifySignature(message, sigBytes, pubBytes);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Dual-verify mandate signatures:
|
|
185
|
+
* 1. New payload (`mandateSigningFields`) — preferred for newly issued grants.
|
|
186
|
+
* 2. Legacy payload (full object minus `signature`) — undebited historical files.
|
|
187
|
+
*
|
|
188
|
+
* Standing-allowlist mandates are unsigned by design.
|
|
189
|
+
*/
|
|
190
|
+
export function verifyMandateSignature(mandate: StandingMandateV1): boolean {
|
|
191
|
+
if (mandate.issuerClass === "standing-allowlist") {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
if (!mandate.publicKey || !mandate.signature) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
if (tryVerifyMandatePayload(mandate, mandateSigningFields(mandate))) {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
return tryVerifyMandatePayload(mandate, legacySigningFields(mandate));
|
|
201
|
+
}
|