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.
- package/README.md +5 -5
- package/dist/src/core/charter.d.ts +91 -0
- package/dist/src/core/charter.d.ts.map +1 -0
- package/dist/src/core/charter.js +536 -0
- package/dist/src/core/charter.js.map +1 -0
- package/dist/src/core/federation.d.ts +39 -0
- package/dist/src/core/federation.d.ts.map +1 -0
- package/dist/src/core/federation.js +85 -0
- package/dist/src/core/federation.js.map +1 -0
- package/dist/src/core/gateway.d.ts +55 -1
- package/dist/src/core/gateway.d.ts.map +1 -1
- package/dist/src/core/gateway.js +309 -0
- package/dist/src/core/gateway.js.map +1 -1
- package/dist/src/core/reserve.d.ts +28 -0
- package/dist/src/core/reserve.d.ts.map +1 -0
- package/dist/src/core/reserve.js +69 -0
- package/dist/src/core/reserve.js.map +1 -0
- package/dist/src/core/time.d.ts +20 -0
- package/dist/src/core/time.d.ts.map +1 -0
- package/dist/src/core/time.js +100 -0
- package/dist/src/core/time.js.map +1 -0
- package/dist/src/core/transactional.d.ts +58 -0
- package/dist/src/core/transactional.d.ts.map +1 -0
- package/dist/src/core/transactional.js +171 -0
- package/dist/src/core/transactional.js.map +1 -0
- package/dist/src/index.d.ts +22 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +9 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/types/approval.d.ts +113 -0
- package/dist/src/types/approval.d.ts.map +1 -0
- package/dist/src/types/approval.js +16 -0
- package/dist/src/types/approval.js.map +1 -0
- package/dist/src/types/charter.d.ts +294 -0
- package/dist/src/types/charter.d.ts.map +1 -0
- package/dist/src/types/charter.js +24 -0
- package/dist/src/types/charter.js.map +1 -0
- package/dist/src/types/dispute.d.ts +80 -0
- package/dist/src/types/dispute.d.ts.map +1 -0
- package/dist/src/types/dispute.js +14 -0
- package/dist/src/types/dispute.js.map +1 -0
- package/dist/src/types/escrow.d.ts +84 -0
- package/dist/src/types/escrow.d.ts.map +1 -0
- package/dist/src/types/escrow.js +10 -0
- package/dist/src/types/escrow.js.map +1 -0
- package/dist/src/types/evidence.d.ts +14 -0
- package/dist/src/types/evidence.d.ts.map +1 -0
- package/dist/src/types/evidence.js +9 -0
- package/dist/src/types/evidence.js.map +1 -0
- package/dist/src/types/federation.d.ts +44 -0
- package/dist/src/types/federation.d.ts.map +1 -0
- package/dist/src/types/federation.js +16 -0
- package/dist/src/types/federation.js.map +1 -0
- package/dist/src/types/finality.d.ts +18 -0
- package/dist/src/types/finality.d.ts.map +1 -0
- package/dist/src/types/finality.js +10 -0
- package/dist/src/types/finality.js.map +1 -0
- package/dist/src/types/foreign.d.ts +63 -0
- package/dist/src/types/foreign.d.ts.map +1 -0
- package/dist/src/types/foreign.js +16 -0
- package/dist/src/types/foreign.js.map +1 -0
- package/dist/src/types/gateway.d.ts +221 -1
- package/dist/src/types/gateway.d.ts.map +1 -1
- package/dist/src/types/passport.d.ts +7 -0
- package/dist/src/types/passport.d.ts.map +1 -1
- package/dist/src/types/passport.js.map +1 -1
- package/dist/src/types/reserve.d.ts +52 -0
- package/dist/src/types/reserve.d.ts.map +1 -0
- package/dist/src/types/reserve.js +15 -0
- package/dist/src/types/reserve.js.map +1 -0
- package/dist/src/types/time.d.ts +70 -0
- package/dist/src/types/time.d.ts.map +1 -0
- package/dist/src/types/time.js +19 -0
- package/dist/src/types/time.js.map +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/agent-passport-system)
|
|
4
4
|
[](https://github.com/aeoess/agent-passport-system/blob/main/LICENSE)
|
|
5
|
-
[](https://github.com/aeoess/agent-passport-system)
|
|
6
6
|
[](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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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 |
|
|
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
|