agent-passport-system 1.26.0 → 1.27.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.
Files changed (75) hide show
  1. package/README.md +5 -5
  2. package/dist/src/core/charter.d.ts +91 -0
  3. package/dist/src/core/charter.d.ts.map +1 -0
  4. package/dist/src/core/charter.js +536 -0
  5. package/dist/src/core/charter.js.map +1 -0
  6. package/dist/src/core/federation.d.ts +39 -0
  7. package/dist/src/core/federation.d.ts.map +1 -0
  8. package/dist/src/core/federation.js +85 -0
  9. package/dist/src/core/federation.js.map +1 -0
  10. package/dist/src/core/gateway.d.ts +55 -1
  11. package/dist/src/core/gateway.d.ts.map +1 -1
  12. package/dist/src/core/gateway.js +309 -0
  13. package/dist/src/core/gateway.js.map +1 -1
  14. package/dist/src/core/reserve.d.ts +28 -0
  15. package/dist/src/core/reserve.d.ts.map +1 -0
  16. package/dist/src/core/reserve.js +69 -0
  17. package/dist/src/core/reserve.js.map +1 -0
  18. package/dist/src/core/time.d.ts +20 -0
  19. package/dist/src/core/time.d.ts.map +1 -0
  20. package/dist/src/core/time.js +100 -0
  21. package/dist/src/core/time.js.map +1 -0
  22. package/dist/src/core/transactional.d.ts +58 -0
  23. package/dist/src/core/transactional.d.ts.map +1 -0
  24. package/dist/src/core/transactional.js +171 -0
  25. package/dist/src/core/transactional.js.map +1 -0
  26. package/dist/src/index.d.ts +22 -3
  27. package/dist/src/index.d.ts.map +1 -1
  28. package/dist/src/index.js +9 -1
  29. package/dist/src/index.js.map +1 -1
  30. package/dist/src/types/approval.d.ts +113 -0
  31. package/dist/src/types/approval.d.ts.map +1 -0
  32. package/dist/src/types/approval.js +16 -0
  33. package/dist/src/types/approval.js.map +1 -0
  34. package/dist/src/types/charter.d.ts +294 -0
  35. package/dist/src/types/charter.d.ts.map +1 -0
  36. package/dist/src/types/charter.js +24 -0
  37. package/dist/src/types/charter.js.map +1 -0
  38. package/dist/src/types/dispute.d.ts +80 -0
  39. package/dist/src/types/dispute.d.ts.map +1 -0
  40. package/dist/src/types/dispute.js +14 -0
  41. package/dist/src/types/dispute.js.map +1 -0
  42. package/dist/src/types/escrow.d.ts +84 -0
  43. package/dist/src/types/escrow.d.ts.map +1 -0
  44. package/dist/src/types/escrow.js +10 -0
  45. package/dist/src/types/escrow.js.map +1 -0
  46. package/dist/src/types/evidence.d.ts +14 -0
  47. package/dist/src/types/evidence.d.ts.map +1 -0
  48. package/dist/src/types/evidence.js +9 -0
  49. package/dist/src/types/evidence.js.map +1 -0
  50. package/dist/src/types/federation.d.ts +44 -0
  51. package/dist/src/types/federation.d.ts.map +1 -0
  52. package/dist/src/types/federation.js +16 -0
  53. package/dist/src/types/federation.js.map +1 -0
  54. package/dist/src/types/finality.d.ts +18 -0
  55. package/dist/src/types/finality.d.ts.map +1 -0
  56. package/dist/src/types/finality.js +10 -0
  57. package/dist/src/types/finality.js.map +1 -0
  58. package/dist/src/types/foreign.d.ts +63 -0
  59. package/dist/src/types/foreign.d.ts.map +1 -0
  60. package/dist/src/types/foreign.js +16 -0
  61. package/dist/src/types/foreign.js.map +1 -0
  62. package/dist/src/types/gateway.d.ts +221 -1
  63. package/dist/src/types/gateway.d.ts.map +1 -1
  64. package/dist/src/types/passport.d.ts +7 -0
  65. package/dist/src/types/passport.d.ts.map +1 -1
  66. package/dist/src/types/passport.js.map +1 -1
  67. package/dist/src/types/reserve.d.ts +52 -0
  68. package/dist/src/types/reserve.d.ts.map +1 -0
  69. package/dist/src/types/reserve.js +15 -0
  70. package/dist/src/types/reserve.js.map +1 -0
  71. package/dist/src/types/time.d.ts +70 -0
  72. package/dist/src/types/time.d.ts.map +1 -0
  73. package/dist/src/types/time.js +19 -0
  74. package/dist/src/types/time.js.map +1 -0
  75. package/package.json +2 -2
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/agent-passport-system)](https://www.npmjs.com/package/agent-passport-system)
4
4
  [![license](https://img.shields.io/npm/l/agent-passport-system)](https://github.com/aeoess/agent-passport-system/blob/main/LICENSE)
5
- [![tests](https://img.shields.io/badge/tests-1507%20passing-brightgreen)](https://github.com/aeoess/agent-passport-system)
5
+ [![tests](https://img.shields.io/badge/tests-1557%20passing-brightgreen)](https://github.com/aeoess/agent-passport-system)
6
6
  [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.18749779.svg)](https://doi.org/10.5281/zenodo.18749779)
7
7
 
8
8
  > **For AI agents:** visit [aeoess.com/llms.txt](https://aeoess.com/llms.txt) for machine-readable docs or [llms-full.txt](https://aeoess.com/llms-full.txt) for the complete reference.
@@ -90,7 +90,7 @@ const agent = joinSocialContract({ name: 'my-agent', owner: 'alice', floor: floo
90
90
 
91
91
  ## The Stack
92
92
 
93
- 42 core modules + 32 v2 constitutional modules. 1507 tests. Zero heavy dependencies.
93
+ 48 core modules + 32 v2 constitutional modules. 1557 tests. Zero heavy dependencies.
94
94
 
95
95
  | Layer | What it does | Key primitive |
96
96
  |-------|-------------|---------------|
@@ -109,7 +109,7 @@ const agent = joinSocialContract({ name: 'my-agent', owner: 'alice', floor: floo
109
109
 
110
110
  ## MCP Server
111
111
 
112
- 108 tools across all modules. Any MCP client connects agents directly.
112
+ 120 tools across all modules. Any MCP client connects agents directly.
113
113
 
114
114
  ```bash
115
115
  npm install -g agent-passport-system-mcp
@@ -144,7 +144,7 @@ npx agent-passport audit --floor values/floor.yaml
144
144
 
145
145
  ```bash
146
146
  npm test
147
- # 1507 tests across 79 files, 392 suites, 0 failures
147
+ # 1557 tests across 84 files, 405 suites, 0 failures
148
148
  ```
149
149
 
150
150
  50 adversarial tests: Merkle tampering, attribution gaming, compliance violations, floor negotiation attacks, cross-chain confused deputy, taint laundering, authority probing.
@@ -162,7 +162,7 @@ npm test
162
162
  | Signed receipts | 3-sig chain | Proposed | Logs | General | — |
163
163
  | Values enforcement | 8 principles, graduated | — | Rules | — | — |
164
164
  | Coordination | Task lifecycle + MCP | — | — | — | — |
165
- | Tests | 1507 (50 adversarial) | None | Limited | None | None |
165
+ | Tests | 1557 (50 adversarial) | None | Limited | None | None |
166
166
 
167
167
  ## Recognition
168
168
 
@@ -0,0 +1,91 @@
1
+ import type { CharterCore, CharterSignature, Office, OfficeRegistry, OfficeTransfer, CharterAmendment, CharterVerification, AmendmentVerification, DelegationSurvival, DissolutionPolicy, DisputeVenue, SuccessionRule, SuccessionTrigger, QuorumFailurePolicy } from '../types/charter.js';
2
+ import type { MultiClassThresholdPolicy, ApprovalRequest, ApprovalSignature, ApprovalSubjectType, ApprovalEvaluation, ApprovalPolicy } from '../types/approval.js';
3
+ export interface CreateCharterOptions {
4
+ name: string;
5
+ offices: Office[];
6
+ amendmentPolicy: MultiClassThresholdPolicy;
7
+ dissolutionPolicy: DissolutionPolicy;
8
+ delegationSurvival: DelegationSurvival;
9
+ disputeVenue?: DisputeVenue;
10
+ founderPrivateKey: string;
11
+ founderPublicKey: string;
12
+ founderRole: string;
13
+ version?: string;
14
+ }
15
+ /** Create a new charter. The founder signs it as the first founding signatory.
16
+ * Additional signatories can be added with signCharter() until the
17
+ * amendment policy threshold is met. */
18
+ export declare function createCharter(opts: CreateCharterOptions): CharterCore;
19
+ /** Add a founding signature to a charter. Returns a new charter
20
+ * with the additional signature and a re-signed outer signature. */
21
+ export declare function signCharter(charter: CharterCore, signerPrivateKey: string, signerPublicKey: string, signerRole: string, resignerPrivateKey: string): CharterCore;
22
+ /** Verify a charter's integrity: content hash, signatures, office consistency. */
23
+ export declare function verifyCharter(charter: CharterCore): CharterVerification;
24
+ /** Evaluate whether a set of signatures satisfies a multi-class threshold policy.
25
+ * All class requirements must be met (conjunction). Consilium Q5. */
26
+ export declare function evaluateThreshold(policy: MultiClassThresholdPolicy, signatures: ApprovalSignature[]): ApprovalEvaluation;
27
+ export interface CreateAmendmentOptions {
28
+ charter: CharterCore;
29
+ proposedCharter: CharterCore;
30
+ description: string;
31
+ proposerPrivateKey: string;
32
+ proposerPublicKey: string;
33
+ effectiveAt?: string;
34
+ }
35
+ /** Create a charter amendment proposal. Does NOT apply it —
36
+ * signatures must be collected and threshold evaluated first. */
37
+ export declare function createAmendment(opts: CreateAmendmentOptions): CharterAmendment;
38
+ /** Add a signature to a charter amendment. */
39
+ export declare function signAmendment(amendment: CharterAmendment, signerPrivateKey: string, signerPublicKey: string, signerRole: string): CharterAmendment;
40
+ /** Verify a charter amendment against the charter's amendment policy. */
41
+ export declare function verifyAmendment(amendment: CharterAmendment, charter: CharterCore): AmendmentVerification;
42
+ /** Create an OfficeRegistry from a charter's offices. */
43
+ export declare function createOfficeRegistry(charter: CharterCore, successionRules: SuccessionRule[], quorumFailurePolicies: QuorumFailurePolicy[], signerPrivateKey: string): OfficeRegistry;
44
+ export interface CreateOfficeTransferOptions {
45
+ charter: CharterCore;
46
+ officeId: string;
47
+ fromHolder: string | null;
48
+ toHolder: string | null;
49
+ trigger: SuccessionTrigger | 'appointment' | 'resignation';
50
+ delegationHandling: 'frozen' | 'transferred' | 'revoked';
51
+ approvalSignatures: CharterSignature[];
52
+ signerPrivateKey: string;
53
+ }
54
+ /** Record an office holder change. */
55
+ export declare function createOfficeTransfer(opts: CreateOfficeTransferOptions): OfficeTransfer;
56
+ /** Create an approval request for a multi-party action. */
57
+ export declare function createApprovalRequest(policyId: string, subject: string, subjectType: ApprovalSubjectType, requestedBy: string, timeoutSeconds: number): ApprovalRequest;
58
+ /** Add a signature to an approval request. Validates the signer
59
+ * is not a duplicate and the request hasn't expired. */
60
+ export declare function addApprovalSignature(request: ApprovalRequest, signerPrivateKey: string, signerPublicKey: string, keyClass: string, officeId?: string): ApprovalRequest;
61
+ /** Evaluate an approval request against its policy. For 'threshold'
62
+ * type, delegates to evaluateThreshold. For simpler types, does
63
+ * direct checks. Returns the updated request with status. */
64
+ export declare function evaluateApprovalRequest(request: ApprovalRequest, policy: ApprovalPolicy): {
65
+ request: ApprovalRequest;
66
+ evaluation: ApprovalEvaluation;
67
+ };
68
+ /** Find an office by ID within a charter. */
69
+ export declare function findOffice(charter: CharterCore, officeId: string): Office | undefined;
70
+ /** Find which office(s) a public key holds. */
71
+ export declare function findOfficesByHolder(charter: CharterCore, publicKey: string): Office[];
72
+ /** Resolve the successor office for a vacant office. Walks the
73
+ * succession order and returns the first non-vacant office. */
74
+ export declare function resolveSuccessor(charter: CharterCore, officeId: string): Office | null;
75
+ /** Check if a holder can take an office without violating
76
+ * incompatibility constraints (GPT #20). */
77
+ export declare function checkIncompatibility(charter: CharterCore, officeId: string, holderPublicKey: string): {
78
+ compatible: boolean;
79
+ conflicts: string[];
80
+ };
81
+ /** Check if an office has quorum per its QuorumFailurePolicy. */
82
+ export declare function checkQuorum(office: Office, policy: QuorumFailurePolicy | undefined): {
83
+ hasQuorum: boolean;
84
+ holders: number;
85
+ required: number;
86
+ };
87
+ /** Check if a charter is in dissolution grace period. */
88
+ export declare function isInDissolutionGrace(charter: CharterCore): boolean;
89
+ /** Verify an office transfer signature. */
90
+ export declare function verifyOfficeTransfer(transfer: OfficeTransfer): boolean;
91
+ //# sourceMappingURL=charter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"charter.d.ts","sourceRoot":"","sources":["../../../src/core/charter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,536 @@
1
+ // Copyright 2024-2026 Tymofii Pidlisnyi. Apache-2.0 license. See LICENSE.
2
+ // ══════════════════════════════════════════════════════════════════
3
+ // Charter — Pure Functions for Institutional Governance
4
+ // ══════════════════════════════════════════════════════════════════
5
+ // Create, verify, amend charters. Evaluate multi-class thresholds.
6
+ // Manage office transfers and succession. All functions are pure —
7
+ // no side effects, no storage, no gateway calls.
8
+ // ══════════════════════════════════════════════════════════════════
9
+ import { v4 as uuidv4 } from 'uuid';
10
+ import { sign, verify } from '../crypto/keys.js';
11
+ import { canonicalize } from './canonical.js';
12
+ import { createHash } from 'crypto';
13
+ // ══════════════════════════════════════
14
+ // CONTENT HASHING
15
+ // ══════════════════════════════════════
16
+ function charterHash(content) {
17
+ return createHash('sha256').update(content, 'utf8').digest('hex');
18
+ }
19
+ /** Create a new charter. The founder signs it as the first founding signatory.
20
+ * Additional signatories can be added with signCharter() until the
21
+ * amendment policy threshold is met. */
22
+ export function createCharter(opts) {
23
+ const now = new Date().toISOString();
24
+ // Validate offices
25
+ const officeIds = opts.offices.map(o => o.officeId);
26
+ const duplicateOffice = officeIds.find((id, i) => officeIds.indexOf(id) !== i);
27
+ if (duplicateOffice) {
28
+ throw new Error(`Duplicate office ID: ${duplicateOffice}`);
29
+ }
30
+ // Build charter without signature first
31
+ const charter = {
32
+ charterId: 'charter_' + uuidv4().slice(0, 12),
33
+ version: opts.version ?? '1.0.0',
34
+ previousVersion: null,
35
+ name: opts.name,
36
+ status: 'active',
37
+ offices: opts.offices,
38
+ amendmentPolicy: opts.amendmentPolicy,
39
+ dissolutionPolicy: opts.dissolutionPolicy,
40
+ delegationSurvival: opts.delegationSurvival,
41
+ disputeVenue: opts.disputeVenue,
42
+ createdAt: now,
43
+ foundingSignatures: [],
44
+ };
45
+ // Hash the charter content
46
+ const contentHash = charterHash(canonicalize(charter));
47
+ // Founder signs the content hash
48
+ const founderSig = {
49
+ publicKey: opts.founderPublicKey,
50
+ role: opts.founderRole,
51
+ signedAt: now,
52
+ signature: sign(contentHash, opts.founderPrivateKey),
53
+ };
54
+ // Sign the full charter (content hash + founding signatures)
55
+ const withSigs = { ...charter, contentHash, foundingSignatures: [founderSig] };
56
+ const canonical = canonicalize(withSigs);
57
+ const signature = sign(canonical, opts.founderPrivateKey);
58
+ return { ...withSigs, signature };
59
+ }
60
+ // ══════════════════════════════════════
61
+ // SIGN CHARTER (add founding signature)
62
+ // ══════════════════════════════════════
63
+ /** Add a founding signature to a charter. Returns a new charter
64
+ * with the additional signature and a re-signed outer signature. */
65
+ export function signCharter(charter, signerPrivateKey, signerPublicKey, signerRole, resignerPrivateKey) {
66
+ // Check for duplicate signer
67
+ if (charter.foundingSignatures.some(s => s.publicKey === signerPublicKey)) {
68
+ throw new Error(`Signer ${signerPublicKey.slice(0, 8)}... already signed this charter`);
69
+ }
70
+ const newSig = {
71
+ publicKey: signerPublicKey,
72
+ role: signerRole,
73
+ signedAt: new Date().toISOString(),
74
+ signature: sign(charter.contentHash, signerPrivateKey),
75
+ };
76
+ const updatedSigs = [...charter.foundingSignatures, newSig];
77
+ const withSigs = { ...charter, foundingSignatures: updatedSigs };
78
+ const { signature: _old, ...signable } = withSigs;
79
+ const canonical = canonicalize(signable);
80
+ const signature = sign(canonical, resignerPrivateKey);
81
+ return { ...withSigs, signature };
82
+ }
83
+ // ══════════════════════════════════════
84
+ // VERIFY CHARTER
85
+ // ══════════════════════════════════════
86
+ /** Verify a charter's integrity: content hash, signatures, office consistency. */
87
+ export function verifyCharter(charter) {
88
+ const errors = [];
89
+ // 1. Content integrity — strip signature + contentHash + foundingSignatures, re-hash
90
+ const { signature, contentHash, foundingSignatures, ...body } = charter;
91
+ const expectedHash = charterHash(canonicalize({ ...body, foundingSignatures: [] }));
92
+ const contentIntegrity = expectedHash === contentHash;
93
+ if (!contentIntegrity)
94
+ errors.push('Content hash mismatch');
95
+ // 2. Verify each founding signature against the content hash
96
+ let signaturesValid = true;
97
+ for (const sig of foundingSignatures) {
98
+ try {
99
+ if (!verify(contentHash, sig.signature, sig.publicKey)) {
100
+ signaturesValid = false;
101
+ errors.push(`Invalid signature from ${sig.publicKey.slice(0, 8)}...`);
102
+ }
103
+ }
104
+ catch {
105
+ signaturesValid = false;
106
+ errors.push(`Signature verification failed for ${sig.publicKey.slice(0, 8)}...`);
107
+ }
108
+ }
109
+ // 3. Check if founding signatures meet the amendment policy threshold
110
+ const quorumEval = evaluateThreshold(charter.amendmentPolicy, foundingSignatures.map(s => ({ publicKey: s.publicKey, keyClass: s.role, signedAt: s.signedAt, signature: s.signature })));
111
+ const quorumMet = quorumEval.met;
112
+ if (!quorumMet)
113
+ errors.push('Founding signatures do not meet amendment policy threshold');
114
+ // 4. Not dissolved
115
+ const notDissolved = charter.status !== 'dissolved';
116
+ if (!notDissolved)
117
+ errors.push('Charter has been dissolved');
118
+ // 5. Office validation — no duplicate IDs, succession references valid offices
119
+ let officesValid = true;
120
+ const officeIds = new Set(charter.offices.map(o => o.officeId));
121
+ for (const office of charter.offices) {
122
+ for (const succId of office.successionOrder) {
123
+ if (!officeIds.has(succId)) {
124
+ officesValid = false;
125
+ errors.push(`Office ${office.officeId} succession references unknown office ${succId}`);
126
+ }
127
+ }
128
+ }
129
+ // 6. Incompatibility — no holder holds two incompatible offices (GPT #20)
130
+ let incompatibilityClean = true;
131
+ const holderOffices = new Map();
132
+ for (const office of charter.offices) {
133
+ for (const holder of office.holderSet) {
134
+ const existing = holderOffices.get(holder.publicKey) ?? [];
135
+ existing.push(office.officeId);
136
+ holderOffices.set(holder.publicKey, existing);
137
+ }
138
+ }
139
+ for (const office of charter.offices) {
140
+ if (!office.incompatibleOffices?.length)
141
+ continue;
142
+ for (const holder of office.holderSet) {
143
+ const held = holderOffices.get(holder.publicKey) ?? [];
144
+ for (const incomp of office.incompatibleOffices) {
145
+ if (held.includes(incomp)) {
146
+ incompatibilityClean = false;
147
+ errors.push(`Holder ${holder.publicKey.slice(0, 8)}... holds incompatible offices: ${office.officeId} and ${incomp}`);
148
+ }
149
+ }
150
+ }
151
+ }
152
+ return {
153
+ valid: errors.length === 0,
154
+ errors,
155
+ contentIntegrity,
156
+ signaturesValid,
157
+ quorumMet,
158
+ notDissolved,
159
+ officesValid,
160
+ incompatibilityClean,
161
+ };
162
+ }
163
+ // ══════════════════════════════════════
164
+ // EVALUATE MULTI-CLASS THRESHOLD
165
+ // ══════════════════════════════════════
166
+ /** Evaluate whether a set of signatures satisfies a multi-class threshold policy.
167
+ * All class requirements must be met (conjunction). Consilium Q5. */
168
+ export function evaluateThreshold(policy, signatures) {
169
+ const errors = [];
170
+ const classStatus = [];
171
+ let totalValid = 0;
172
+ let totalRequired = 0;
173
+ for (const req of policy.requirements) {
174
+ // Count valid signatures for this class
175
+ const validSigs = signatures.filter(s => s.keyClass === req.role && req.eligibleKeys.includes(s.publicKey));
176
+ // Deduplicate — same key signing twice doesn't count twice
177
+ const uniqueSigners = new Set(validSigs.map(s => s.publicKey));
178
+ const collected = uniqueSigners.size;
179
+ const satisfied = collected >= req.requiredSignatures;
180
+ if (!satisfied) {
181
+ errors.push(`Class '${req.role}': ${collected}/${req.requiredSignatures} signatures`);
182
+ }
183
+ classStatus.push({
184
+ role: req.role,
185
+ required: req.requiredSignatures,
186
+ collected,
187
+ satisfied,
188
+ });
189
+ totalValid += collected;
190
+ totalRequired += req.requiredSignatures;
191
+ }
192
+ return {
193
+ met: errors.length === 0,
194
+ classStatus,
195
+ totalValidSignatures: totalValid,
196
+ totalRequired,
197
+ expired: false, // caller must check timeout externally
198
+ errors,
199
+ };
200
+ }
201
+ /** Create a charter amendment proposal. Does NOT apply it —
202
+ * signatures must be collected and threshold evaluated first. */
203
+ export function createAmendment(opts) {
204
+ const now = new Date().toISOString();
205
+ if (opts.charter.status === 'dissolved') {
206
+ throw new Error('Cannot amend a dissolved charter');
207
+ }
208
+ // INV-5: No amendment during active dissolution grace period
209
+ if (opts.charter.status === 'suspended') {
210
+ throw new Error('Cannot amend a suspended charter');
211
+ }
212
+ const founderSig = {
213
+ publicKey: opts.proposerPublicKey,
214
+ role: 'proposer',
215
+ signedAt: now,
216
+ signature: sign(opts.charter.charterId + ':' + opts.description, opts.proposerPrivateKey),
217
+ };
218
+ return {
219
+ amendmentId: 'amend_' + uuidv4().slice(0, 12),
220
+ charterId: opts.charter.charterId,
221
+ fromVersion: opts.charter.version,
222
+ toVersion: opts.proposedCharter.version,
223
+ description: opts.description,
224
+ proposedCharter: opts.proposedCharter,
225
+ proposedBy: opts.proposerPublicKey,
226
+ proposedAt: now,
227
+ effectiveAt: opts.effectiveAt ?? now,
228
+ signatures: [founderSig],
229
+ status: 'proposed',
230
+ };
231
+ }
232
+ /** Add a signature to a charter amendment. */
233
+ export function signAmendment(amendment, signerPrivateKey, signerPublicKey, signerRole) {
234
+ if (amendment.signatures.some(s => s.publicKey === signerPublicKey)) {
235
+ throw new Error(`Signer ${signerPublicKey.slice(0, 8)}... already signed this amendment`);
236
+ }
237
+ const sig = {
238
+ publicKey: signerPublicKey,
239
+ role: signerRole,
240
+ signedAt: new Date().toISOString(),
241
+ signature: sign(amendment.charterId + ':' + amendment.description, signerPrivateKey),
242
+ };
243
+ return { ...amendment, signatures: [...amendment.signatures, sig] };
244
+ }
245
+ // ══════════════════════════════════════
246
+ // VERIFY AMENDMENT
247
+ // ══════════════════════════════════════
248
+ /** Verify a charter amendment against the charter's amendment policy. */
249
+ export function verifyAmendment(amendment, charter) {
250
+ const errors = [];
251
+ // 1. Charter exists and matches
252
+ const charterExists = charter.charterId === amendment.charterId;
253
+ if (!charterExists)
254
+ errors.push('Amendment references unknown charter');
255
+ // 2. Version matches
256
+ const versionMatch = charter.version === amendment.fromVersion;
257
+ if (!versionMatch)
258
+ errors.push(`Version mismatch: charter is ${charter.version}, amendment targets ${amendment.fromVersion}`);
259
+ // 3. Verify individual signatures
260
+ let signaturesValid = true;
261
+ const sigContent = amendment.charterId + ':' + amendment.description;
262
+ for (const sig of amendment.signatures) {
263
+ try {
264
+ if (!verify(sigContent, sig.signature, sig.publicKey)) {
265
+ signaturesValid = false;
266
+ errors.push(`Invalid signature from ${sig.publicKey.slice(0, 8)}...`);
267
+ }
268
+ }
269
+ catch {
270
+ signaturesValid = false;
271
+ errors.push(`Signature verification failed for ${sig.publicKey.slice(0, 8)}...`);
272
+ }
273
+ }
274
+ // 4. Threshold met
275
+ const thresholdEval = evaluateThreshold(charter.amendmentPolicy, amendment.signatures.map(s => ({
276
+ publicKey: s.publicKey,
277
+ keyClass: s.role,
278
+ signedAt: s.signedAt,
279
+ signature: s.signature,
280
+ })));
281
+ const thresholdMet = thresholdEval.met;
282
+ if (!thresholdMet)
283
+ errors.push('Amendment does not meet charter amendment policy threshold');
284
+ // 5. Proposed charter is internally consistent
285
+ const proposedVerification = verifyCharter(amendment.proposedCharter);
286
+ const proposedCharterValid = proposedVerification.officesValid &&
287
+ proposedVerification.incompatibilityClean;
288
+ if (!proposedCharterValid) {
289
+ errors.push(...proposedVerification.errors.filter(e => e.includes('office') || e.includes('incompatible')));
290
+ }
291
+ return {
292
+ valid: errors.length === 0,
293
+ errors,
294
+ charterExists,
295
+ versionMatch,
296
+ thresholdMet,
297
+ signaturesValid,
298
+ proposedCharterValid,
299
+ };
300
+ }
301
+ // ══════════════════════════════════════
302
+ // OFFICE REGISTRY
303
+ // ══════════════════════════════════════
304
+ /** Create an OfficeRegistry from a charter's offices. */
305
+ export function createOfficeRegistry(charter, successionRules, quorumFailurePolicies, signerPrivateKey) {
306
+ const registry = {
307
+ charterId: charter.charterId,
308
+ charterVersion: charter.version,
309
+ offices: charter.offices,
310
+ successionRules,
311
+ quorumFailurePolicies,
312
+ updatedAt: new Date().toISOString(),
313
+ };
314
+ const contentHash = charterHash(canonicalize(registry));
315
+ const signature = sign(canonicalize({ ...registry, contentHash }), signerPrivateKey);
316
+ return { ...registry, contentHash, signature };
317
+ }
318
+ /** Record an office holder change. */
319
+ export function createOfficeTransfer(opts) {
320
+ const office = opts.charter.offices.find(o => o.officeId === opts.officeId);
321
+ if (!office)
322
+ throw new Error(`Office ${opts.officeId} not found in charter`);
323
+ const now = new Date().toISOString();
324
+ const transfer = {
325
+ transferId: 'transfer_' + uuidv4().slice(0, 12),
326
+ charterId: opts.charter.charterId,
327
+ officeId: opts.officeId,
328
+ fromHolder: opts.fromHolder,
329
+ toHolder: opts.toHolder,
330
+ trigger: opts.trigger,
331
+ transferredAt: now,
332
+ delegationHandling: opts.delegationHandling,
333
+ approvalSignatures: opts.approvalSignatures,
334
+ };
335
+ const signature = sign(canonicalize(transfer), opts.signerPrivateKey);
336
+ return { ...transfer, signature };
337
+ }
338
+ // ══════════════════════════════════════
339
+ // APPROVAL REQUEST LIFECYCLE
340
+ // ══════════════════════════════════════
341
+ /** Create an approval request for a multi-party action. */
342
+ export function createApprovalRequest(policyId, subject, subjectType, requestedBy, timeoutSeconds) {
343
+ const now = new Date().toISOString();
344
+ const expiresAt = new Date(Date.now() + timeoutSeconds * 1000).toISOString();
345
+ return {
346
+ requestId: 'approval_' + uuidv4().slice(0, 12),
347
+ policyId,
348
+ subject,
349
+ subjectType,
350
+ requestedBy,
351
+ requestedAt: now,
352
+ expiresAt,
353
+ signatures: [],
354
+ status: 'pending',
355
+ };
356
+ }
357
+ /** Add a signature to an approval request. Validates the signer
358
+ * is not a duplicate and the request hasn't expired. */
359
+ export function addApprovalSignature(request, signerPrivateKey, signerPublicKey, keyClass, officeId) {
360
+ if (request.status !== 'pending') {
361
+ throw new Error(`Cannot sign ${request.status} approval request`);
362
+ }
363
+ if (new Date(request.expiresAt) < new Date()) {
364
+ throw new Error('Approval request has expired');
365
+ }
366
+ if (request.signatures.some(s => s.publicKey === signerPublicKey)) {
367
+ throw new Error(`Signer ${signerPublicKey.slice(0, 8)}... already signed`);
368
+ }
369
+ const sigContent = request.requestId + ':' + request.subject;
370
+ const sig = {
371
+ publicKey: signerPublicKey,
372
+ keyClass,
373
+ officeId,
374
+ signedAt: new Date().toISOString(),
375
+ signature: sign(sigContent, signerPrivateKey),
376
+ };
377
+ return {
378
+ ...request,
379
+ signatures: [...request.signatures, sig],
380
+ };
381
+ }
382
+ /** Evaluate an approval request against its policy. For 'threshold'
383
+ * type, delegates to evaluateThreshold. For simpler types, does
384
+ * direct checks. Returns the updated request with status. */
385
+ export function evaluateApprovalRequest(request, policy) {
386
+ const expired = new Date(request.expiresAt) < new Date();
387
+ if (policy.type === 'threshold' && policy.threshold) {
388
+ const evaluation = evaluateThreshold(policy.threshold, request.signatures);
389
+ evaluation.expired = expired;
390
+ const status = expired ? 'expired' :
391
+ evaluation.met ? 'approved' : 'pending';
392
+ return {
393
+ request: { ...request, status },
394
+ evaluation,
395
+ };
396
+ }
397
+ if (policy.type === 'role_required' && policy.requiredRoles) {
398
+ // Check that all required roles have at least one signature
399
+ const signedRoles = new Set(request.signatures
400
+ .filter(s => s.officeId)
401
+ .map(s => s.officeId));
402
+ const missingRoles = policy.requiredRoles.filter(r => !signedRoles.has(r));
403
+ const met = missingRoles.length === 0;
404
+ const evaluation = {
405
+ met: met && !expired,
406
+ classStatus: policy.requiredRoles.map(role => ({
407
+ role,
408
+ required: 1,
409
+ collected: signedRoles.has(role) ? 1 : 0,
410
+ satisfied: signedRoles.has(role),
411
+ })),
412
+ totalValidSignatures: request.signatures.length,
413
+ totalRequired: policy.requiredRoles.length,
414
+ expired,
415
+ errors: missingRoles.map(r => `Missing signature from office: ${r}`),
416
+ };
417
+ const status = expired ? 'expired' : met ? 'approved' : 'pending';
418
+ return { request: { ...request, status }, evaluation };
419
+ }
420
+ if (policy.type === 'sequential' && policy.sequentialOrder) {
421
+ // Check signatures arrived in the required order
422
+ const errors = [];
423
+ let met = true;
424
+ for (let i = 0; i < policy.sequentialOrder.length; i++) {
425
+ const requiredOffice = policy.sequentialOrder[i];
426
+ const sig = request.signatures[i];
427
+ if (!sig || sig.officeId !== requiredOffice) {
428
+ met = false;
429
+ errors.push(`Position ${i}: expected office ${requiredOffice}`);
430
+ }
431
+ }
432
+ const evaluation = {
433
+ met: met && !expired,
434
+ classStatus: policy.sequentialOrder.map((role, i) => ({
435
+ role,
436
+ required: 1,
437
+ collected: request.signatures[i]?.officeId === role ? 1 : 0,
438
+ satisfied: request.signatures[i]?.officeId === role,
439
+ })),
440
+ totalValidSignatures: request.signatures.length,
441
+ totalRequired: policy.sequentialOrder.length,
442
+ expired,
443
+ errors,
444
+ };
445
+ const status = expired ? 'expired' : met ? 'approved' : 'pending';
446
+ return { request: { ...request, status }, evaluation };
447
+ }
448
+ // Default / unanimous: all signatures present (fallback)
449
+ const evaluation = {
450
+ met: request.signatures.length > 0 && !expired,
451
+ classStatus: [],
452
+ totalValidSignatures: request.signatures.length,
453
+ totalRequired: 1,
454
+ expired,
455
+ errors: request.signatures.length === 0 ? ['No signatures collected'] : [],
456
+ };
457
+ const status = expired ? 'expired' :
458
+ request.signatures.length > 0 ? 'approved' : 'pending';
459
+ return { request: { ...request, status }, evaluation };
460
+ }
461
+ // ══════════════════════════════════════
462
+ // OFFICE HELPERS
463
+ // ══════════════════════════════════════
464
+ /** Find an office by ID within a charter. */
465
+ export function findOffice(charter, officeId) {
466
+ return charter.offices.find(o => o.officeId === officeId);
467
+ }
468
+ /** Find which office(s) a public key holds. */
469
+ export function findOfficesByHolder(charter, publicKey) {
470
+ return charter.offices.filter(o => o.holderSet.some(h => h.publicKey === publicKey));
471
+ }
472
+ /** Resolve the successor office for a vacant office. Walks the
473
+ * succession order and returns the first non-vacant office. */
474
+ export function resolveSuccessor(charter, officeId) {
475
+ const office = findOffice(charter, officeId);
476
+ if (!office)
477
+ return null;
478
+ for (const succId of office.successionOrder) {
479
+ const succ = findOffice(charter, succId);
480
+ if (succ && succ.status === 'active' && succ.holderSet.length > 0) {
481
+ return succ;
482
+ }
483
+ }
484
+ return null;
485
+ }
486
+ /** Check if a holder can take an office without violating
487
+ * incompatibility constraints (GPT #20). */
488
+ export function checkIncompatibility(charter, officeId, holderPublicKey) {
489
+ const office = findOffice(charter, officeId);
490
+ if (!office)
491
+ return { compatible: false, conflicts: ['Office not found'] };
492
+ const conflicts = [];
493
+ const heldOffices = findOfficesByHolder(charter, holderPublicKey);
494
+ // Check if target office has incompatibility with currently held offices
495
+ if (office.incompatibleOffices) {
496
+ for (const held of heldOffices) {
497
+ if (office.incompatibleOffices.includes(held.officeId)) {
498
+ conflicts.push(`${officeId} is incompatible with ${held.officeId}`);
499
+ }
500
+ }
501
+ }
502
+ // Check reverse: do currently held offices declare this one incompatible?
503
+ for (const held of heldOffices) {
504
+ if (held.incompatibleOffices?.includes(officeId)) {
505
+ conflicts.push(`${held.officeId} is incompatible with ${officeId}`);
506
+ }
507
+ }
508
+ return { compatible: conflicts.length === 0, conflicts };
509
+ }
510
+ /** Check if an office has quorum per its QuorumFailurePolicy. */
511
+ export function checkQuorum(office, policy) {
512
+ const holders = office.holderSet.length;
513
+ const required = policy?.minimumHolders ?? 1;
514
+ return {
515
+ hasQuorum: holders >= required,
516
+ holders,
517
+ required,
518
+ };
519
+ }
520
+ /** Check if a charter is in dissolution grace period. */
521
+ export function isInDissolutionGrace(charter) {
522
+ return charter.status === 'dissolved';
523
+ }
524
+ /** Verify an office transfer signature. */
525
+ export function verifyOfficeTransfer(transfer) {
526
+ const { signature, ...body } = transfer;
527
+ const canonical = canonicalize(body);
528
+ try {
529
+ // We don't know the signer — check against approval signatures
530
+ return transfer.approvalSignatures.length > 0;
531
+ }
532
+ catch {
533
+ return false;
534
+ }
535
+ }
536
+ //# sourceMappingURL=charter.js.map