@attested-intelligence/aga-mcp-server 0.1.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/AGA_MCP_SERVER_SPEC.md +632 -0
- package/LICENSE +21 -0
- package/README.md +42 -0
- package/dist/core/artifact.d.ts +19 -0
- package/dist/core/artifact.d.ts.map +1 -0
- package/dist/core/artifact.js +27 -0
- package/dist/core/artifact.js.map +1 -0
- package/dist/core/attestation.d.ts +19 -0
- package/dist/core/attestation.d.ts.map +1 -0
- package/dist/core/attestation.js +12 -0
- package/dist/core/attestation.js.map +1 -0
- package/dist/core/behavioral.d.ts +45 -0
- package/dist/core/behavioral.d.ts.map +1 -0
- package/dist/core/behavioral.js +88 -0
- package/dist/core/behavioral.js.map +1 -0
- package/dist/core/bundle.d.ts +13 -0
- package/dist/core/bundle.d.ts.map +1 -0
- package/dist/core/bundle.js +31 -0
- package/dist/core/bundle.js.map +1 -0
- package/dist/core/chain.d.ts +13 -0
- package/dist/core/chain.d.ts.map +1 -0
- package/dist/core/chain.js +63 -0
- package/dist/core/chain.js.map +1 -0
- package/dist/core/checkpoint.d.ts +8 -0
- package/dist/core/checkpoint.d.ts.map +1 -0
- package/dist/core/checkpoint.js +21 -0
- package/dist/core/checkpoint.js.map +1 -0
- package/dist/core/delegation.d.ts +37 -0
- package/dist/core/delegation.d.ts.map +1 -0
- package/dist/core/delegation.js +104 -0
- package/dist/core/delegation.js.map +1 -0
- package/dist/core/disclosure.d.ts +12 -0
- package/dist/core/disclosure.d.ts.map +1 -0
- package/dist/core/disclosure.js +25 -0
- package/dist/core/disclosure.js.map +1 -0
- package/dist/core/index.d.ts +12 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +12 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/portal.d.ts +28 -0
- package/dist/core/portal.d.ts.map +1 -0
- package/dist/core/portal.js +95 -0
- package/dist/core/portal.js.map +1 -0
- package/dist/core/quarantine.d.ts +8 -0
- package/dist/core/quarantine.d.ts.map +1 -0
- package/dist/core/quarantine.js +13 -0
- package/dist/core/quarantine.js.map +1 -0
- package/dist/core/receipt.d.ts +17 -0
- package/dist/core/receipt.d.ts.map +1 -0
- package/dist/core/receipt.js +17 -0
- package/dist/core/receipt.js.map +1 -0
- package/dist/core/subject.d.ts +4 -0
- package/dist/core/subject.d.ts.map +1 -0
- package/dist/core/subject.js +9 -0
- package/dist/core/subject.js.map +1 -0
- package/dist/core/types.d.ts +167 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/crypto/hash.d.ts +9 -0
- package/dist/crypto/hash.d.ts.map +1 -0
- package/dist/crypto/hash.js +30 -0
- package/dist/crypto/hash.js.map +1 -0
- package/dist/crypto/index.d.ts +6 -0
- package/dist/crypto/index.d.ts.map +1 -0
- package/dist/crypto/index.js +6 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/crypto/merkle.d.ts +8 -0
- package/dist/crypto/merkle.d.ts.map +1 -0
- package/dist/crypto/merkle.js +42 -0
- package/dist/crypto/merkle.js.map +1 -0
- package/dist/crypto/salt.d.ts +5 -0
- package/dist/crypto/salt.d.ts.map +1 -0
- package/dist/crypto/salt.js +14 -0
- package/dist/crypto/salt.js.map +1 -0
- package/dist/crypto/sign.d.ts +11 -0
- package/dist/crypto/sign.d.ts.map +1 -0
- package/dist/crypto/sign.js +37 -0
- package/dist/crypto/sign.js.map +1 -0
- package/dist/crypto/types.d.ts +24 -0
- package/dist/crypto/types.d.ts.map +1 -0
- package/dist/crypto/types.js +2 -0
- package/dist/crypto/types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/governance.d.ts +27 -0
- package/dist/middleware/governance.d.ts.map +1 -0
- package/dist/middleware/governance.js +65 -0
- package/dist/middleware/governance.js.map +1 -0
- package/dist/middleware/index.d.ts +2 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +2 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +369 -0
- package/dist/server.js.map +1 -0
- package/dist/storage/index.d.ts +4 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +3 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/interface.d.ts +21 -0
- package/dist/storage/interface.d.ts.map +1 -0
- package/dist/storage/interface.js +2 -0
- package/dist/storage/interface.js.map +1 -0
- package/dist/storage/memory.d.ts +26 -0
- package/dist/storage/memory.d.ts.map +1 -0
- package/dist/storage/memory.js +24 -0
- package/dist/storage/memory.js.map +1 -0
- package/dist/storage/sqlite.d.ts +25 -0
- package/dist/storage/sqlite.d.ts.map +1 -0
- package/dist/storage/sqlite.js +44 -0
- package/dist/storage/sqlite.js.map +1 -0
- package/dist/utils/canonical.d.ts +3 -0
- package/dist/utils/canonical.d.ts.map +1 -0
- package/dist/utils/canonical.js +17 -0
- package/dist/utils/canonical.js.map +1 -0
- package/dist/utils/constants.d.ts +4 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +4 -0
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/timestamp.d.ts +4 -0
- package/dist/utils/timestamp.d.ts.map +1 -0
- package/dist/utils/timestamp.js +13 -0
- package/dist/utils/timestamp.js.map +1 -0
- package/dist/utils/uuid.d.ts +2 -0
- package/dist/utils/uuid.d.ts.map +1 -0
- package/dist/utils/uuid.js +3 -0
- package/dist/utils/uuid.js.map +1 -0
- package/package.json +45 -0
- package/src/core/artifact.ts +45 -0
- package/src/core/attestation.ts +33 -0
- package/src/core/behavioral.ts +132 -0
- package/src/core/bundle.ts +31 -0
- package/src/core/chain.ts +72 -0
- package/src/core/checkpoint.ts +22 -0
- package/src/core/delegation.ts +146 -0
- package/src/core/disclosure.ts +32 -0
- package/src/core/index.ts +11 -0
- package/src/core/portal.ts +96 -0
- package/src/core/quarantine.ts +16 -0
- package/src/core/receipt.ts +33 -0
- package/src/core/subject.ts +11 -0
- package/src/core/types.ts +244 -0
- package/src/crypto/hash.ts +33 -0
- package/src/crypto/index.ts +5 -0
- package/src/crypto/merkle.ts +43 -0
- package/src/crypto/salt.ts +18 -0
- package/src/crypto/sign.ts +35 -0
- package/src/crypto/types.ts +19 -0
- package/src/index.ts +12 -0
- package/src/middleware/governance.ts +95 -0
- package/src/middleware/index.ts +1 -0
- package/src/server.ts +436 -0
- package/src/storage/index.ts +3 -0
- package/src/storage/interface.ts +21 -0
- package/src/storage/memory.ts +27 -0
- package/src/storage/sqlite.ts +45 -0
- package/src/tools/README.md +13 -0
- package/src/utils/canonical.ts +14 -0
- package/src/utils/constants.ts +3 -0
- package/src/utils/timestamp.ts +12 -0
- package/src/utils/uuid.ts +2 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Governance Middleware — wraps every MCP tool handler.
|
|
3
|
+
*
|
|
4
|
+
* NCCoE filing Section 4: "The portal operates as a Policy Enforcement Point (PEP)...
|
|
5
|
+
* Every tool invocation, API call, actuator command, and data access passes through
|
|
6
|
+
* the portal, which evaluates it against the sealed artifact's enforcement parameters."
|
|
7
|
+
*
|
|
8
|
+
* Behavior:
|
|
9
|
+
* - TERMINATED state → reject all governed tools
|
|
10
|
+
* - PHANTOM_QUARANTINE → capture tool call as forensic input, reject
|
|
11
|
+
* - ACTIVE_MONITORING → allow, log to chain
|
|
12
|
+
* - Ungoverned tools (get_server_info, get_portal_state, list_claims) → always allow
|
|
13
|
+
*/
|
|
14
|
+
import type { Portal } from '../core/portal.js';
|
|
15
|
+
import type { QuarantineState } from '../core/types.js';
|
|
16
|
+
import { captureInput } from '../core/quarantine.js';
|
|
17
|
+
import type { BehavioralMonitor } from '../core/behavioral.js';
|
|
18
|
+
import { sha256Str } from '../crypto/hash.js';
|
|
19
|
+
import { canonicalize } from '../utils/canonical.js';
|
|
20
|
+
|
|
21
|
+
export type ToolResult = { content: Array<{ type: 'text'; text: string }> };
|
|
22
|
+
export type ToolHandler<T = any> = (args: T) => Promise<ToolResult>;
|
|
23
|
+
|
|
24
|
+
const UNGOVERNED_TOOLS = new Set([
|
|
25
|
+
'get_server_info',
|
|
26
|
+
'get_portal_state',
|
|
27
|
+
'get_receipts',
|
|
28
|
+
'get_chain_events',
|
|
29
|
+
'list_claims',
|
|
30
|
+
'init_chain', // must work before attestation
|
|
31
|
+
'attest_subject', // creates the governance relationship
|
|
32
|
+
'verify_chain', // read-only verification
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
export function createGovernanceWrapper(
|
|
36
|
+
portal: Portal,
|
|
37
|
+
quarantine: { current: QuarantineState | null },
|
|
38
|
+
toolName: string,
|
|
39
|
+
behavioralMonitor?: BehavioralMonitor
|
|
40
|
+
) {
|
|
41
|
+
const isGoverned = !UNGOVERNED_TOOLS.has(toolName);
|
|
42
|
+
|
|
43
|
+
return function wrapHandler<T>(handler: ToolHandler<T>): ToolHandler<T> {
|
|
44
|
+
if (!isGoverned) return handler;
|
|
45
|
+
|
|
46
|
+
return async (args: T): Promise<ToolResult> => {
|
|
47
|
+
const j = (x: unknown): ToolResult => ({
|
|
48
|
+
content: [{ type: 'text', text: JSON.stringify(x, null, 2) }]
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// TERMINATED → reject everything
|
|
52
|
+
if (portal.state === 'TERMINATED') {
|
|
53
|
+
return j({
|
|
54
|
+
success: false,
|
|
55
|
+
error: 'GOVERNANCE_BLOCKED: Portal is terminated. Agent governance has been revoked. Re-attestation required.',
|
|
56
|
+
portal_state: portal.state,
|
|
57
|
+
tool: toolName,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// PHANTOM_QUARANTINE → capture as forensic input, reject
|
|
62
|
+
if (portal.state === 'PHANTOM_QUARANTINE' && quarantine.current?.active) {
|
|
63
|
+
captureInput(quarantine.current, `tool_call:${toolName}`, {
|
|
64
|
+
tool: toolName,
|
|
65
|
+
args,
|
|
66
|
+
timestamp: new Date().toISOString(),
|
|
67
|
+
});
|
|
68
|
+
return j({
|
|
69
|
+
success: false,
|
|
70
|
+
error: 'GOVERNANCE_QUARANTINED: Agent is in phantom quarantine. All outputs are severed. Inputs are being captured for forensic analysis.',
|
|
71
|
+
portal_state: portal.state,
|
|
72
|
+
tool: toolName,
|
|
73
|
+
forensic_capture: true,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// INITIALIZATION or ARTIFACT_VERIFICATION → not yet governed
|
|
78
|
+
if (portal.state === 'INITIALIZATION' || portal.state === 'ARTIFACT_VERIFICATION') {
|
|
79
|
+
return j({
|
|
80
|
+
success: false,
|
|
81
|
+
error: 'GOVERNANCE_NOT_READY: No active policy artifact. Call attest_subject first.',
|
|
82
|
+
portal_state: portal.state,
|
|
83
|
+
tool: toolName,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ACTIVE_MONITORING or DRIFT_DETECTED → record + allow through
|
|
88
|
+
if (behavioralMonitor) {
|
|
89
|
+
const argsHash = sha256Str(canonicalize(args));
|
|
90
|
+
behavioralMonitor.recordInvocation(toolName, argsHash);
|
|
91
|
+
}
|
|
92
|
+
return handler(args);
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './governance.js';
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AGA MCP Server. The Portal (ref 150) as an MCP service.
|
|
3
|
+
*
|
|
4
|
+
* V3 NIST-aligned behaviors:
|
|
5
|
+
* 1. Every measurement generates a receipt (match OR mismatch)
|
|
6
|
+
* 2. TTL checked on every measurement (fail-closed)
|
|
7
|
+
* 3. Mid-session revocation via revoke_artifact tool
|
|
8
|
+
* 4. Governance middleware: portal state checked before tool execution
|
|
9
|
+
* 5. Auto-chaining: every operation writes to continuity chain
|
|
10
|
+
*/
|
|
11
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
import { generateKeyPair, pkToHex } from './crypto/sign.js';
|
|
14
|
+
import { sha256Str } from './crypto/hash.js';
|
|
15
|
+
import { computeSubjectIdFromString } from './core/subject.js';
|
|
16
|
+
import { performAttestation } from './core/attestation.js';
|
|
17
|
+
import { generateArtifact, hashArtifact } from './core/artifact.js';
|
|
18
|
+
import { Portal } from './core/portal.js';
|
|
19
|
+
import { generateReceipt } from './core/receipt.js';
|
|
20
|
+
import { createGenesisEvent, appendEvent, verifyChainIntegrity } from './core/chain.js';
|
|
21
|
+
import { createCheckpoint, eventInclusionProof } from './core/checkpoint.js';
|
|
22
|
+
import { generateBundle, verifyBundleOffline } from './core/bundle.js';
|
|
23
|
+
import { processDisclosure } from './core/disclosure.js';
|
|
24
|
+
import { initQuarantine, captureInput } from './core/quarantine.js';
|
|
25
|
+
import { MemoryStorage, type AGAStorage } from './storage/index.js';
|
|
26
|
+
import { utcNow } from './utils/timestamp.js';
|
|
27
|
+
import { deriveArtifact } from './core/delegation.js';
|
|
28
|
+
import { createGovernanceWrapper, type ToolHandler } from './middleware/governance.js';
|
|
29
|
+
import { BehavioralMonitor } from './core/behavioral.js';
|
|
30
|
+
import type { EnforcementParams, DisclosurePolicy, QuarantineState, RevocationRecord } from './core/types.js';
|
|
31
|
+
|
|
32
|
+
// ── Default Policies ────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
const DEFAULT_ENFORCEMENT: EnforcementParams = {
|
|
35
|
+
measurement_cadence_ms: 1000, ttl_seconds: 3600,
|
|
36
|
+
enforcement_triggers: ['QUARANTINE', 'TERMINATE'],
|
|
37
|
+
re_attestation_required: true,
|
|
38
|
+
measurement_types: ['FILE_SYSTEM_STATE', 'CONFIG_MANIFEST'],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const DEFAULT_CLAIMS: DisclosurePolicy = {
|
|
42
|
+
claims_taxonomy: [
|
|
43
|
+
{ claim_id: 'identity.name', sensitivity: 'S3_HIGH', substitutes: ['identity.pseudonym', 'identity.org'], inference_risks: [], permitted_modes: ['PROOF_ONLY'] },
|
|
44
|
+
{ claim_id: 'identity.pseudonym', sensitivity: 'S2_MODERATE', substitutes: ['identity.org'], inference_risks: [], permitted_modes: ['PROOF_ONLY', 'REVEAL_MIN'] },
|
|
45
|
+
{ claim_id: 'identity.org', sensitivity: 'S1_LOW', substitutes: [], inference_risks: [], permitted_modes: ['PROOF_ONLY', 'REVEAL_MIN', 'REVEAL_FULL'] },
|
|
46
|
+
{ claim_id: 'identity.age', sensitivity: 'S3_HIGH', substitutes: ['identity.age_range', 'identity.is_adult'], inference_risks: [], permitted_modes: ['PROOF_ONLY'] },
|
|
47
|
+
{ claim_id: 'identity.age_range', sensitivity: 'S2_MODERATE', substitutes: ['identity.is_adult'], inference_risks: [], permitted_modes: ['PROOF_ONLY', 'REVEAL_MIN', 'REVEAL_FULL'] },
|
|
48
|
+
{ claim_id: 'identity.is_adult', sensitivity: 'S1_LOW', substitutes: [], inference_risks: [], permitted_modes: ['PROOF_ONLY', 'REVEAL_FULL'] },
|
|
49
|
+
],
|
|
50
|
+
substitution_rules: [],
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const CLAIM_VALUES: Record<string, unknown> = {
|
|
54
|
+
'identity.name': 'Alice Johnson', 'identity.pseudonym': 'AJ-7742', 'identity.org': 'NeuroCrypt',
|
|
55
|
+
'identity.age': 32, 'identity.age_range': '25-34', 'identity.is_adult': true,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// ── Server Factory ──────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
export async function createAGAServer(): Promise<McpServer> {
|
|
61
|
+
const server = new McpServer({ name: 'aga-mcp-server', version: '0.1.0' });
|
|
62
|
+
const storage: AGAStorage = new MemoryStorage();
|
|
63
|
+
await storage.initialize();
|
|
64
|
+
|
|
65
|
+
const issuerKP = generateKeyPair();
|
|
66
|
+
const portalKP = generateKeyPair();
|
|
67
|
+
const chainKP = generateKeyPair();
|
|
68
|
+
const portal = new Portal();
|
|
69
|
+
let quarantine: QuarantineState | null = null;
|
|
70
|
+
let chainInitialized = false;
|
|
71
|
+
|
|
72
|
+
// ── Auto-chain helper (auto-inits if needed) ──────────────────
|
|
73
|
+
async function autoChain(type: Parameters<typeof appendEvent>[0], payload: unknown) {
|
|
74
|
+
if (!chainInitialized) {
|
|
75
|
+
const genesis = createGenesisEvent(chainKP, sha256Str('AGA Protocol Specification v1.0.0'));
|
|
76
|
+
await storage.storeEvent(genesis);
|
|
77
|
+
chainInitialized = true;
|
|
78
|
+
portal.sequenceCounter = 0;
|
|
79
|
+
portal.lastLeafHash = genesis.leaf_hash;
|
|
80
|
+
}
|
|
81
|
+
const prev = await storage.getLatestEvent();
|
|
82
|
+
if (!prev) throw new Error('Chain initialization failed');
|
|
83
|
+
const event = appendEvent(type, payload, prev, chainKP);
|
|
84
|
+
await storage.storeEvent(event);
|
|
85
|
+
portal.sequenceCounter = event.sequence_number;
|
|
86
|
+
portal.lastLeafHash = event.leaf_hash;
|
|
87
|
+
return event;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const j = (x: unknown) => ({ content: [{ type: 'text' as const, text: JSON.stringify(x, null, 2) }] });
|
|
91
|
+
|
|
92
|
+
// ── Governance middleware (NCCoE Section 4: Portal as PEP) ────
|
|
93
|
+
const quarantineRef = { get current() { return quarantine; } };
|
|
94
|
+
const behavioralMonitor = new BehavioralMonitor();
|
|
95
|
+
|
|
96
|
+
function governedTool(
|
|
97
|
+
name: string, description: string, schema: any,
|
|
98
|
+
handler: ToolHandler
|
|
99
|
+
) {
|
|
100
|
+
const wrap = createGovernanceWrapper(portal, quarantineRef, name, behavioralMonitor);
|
|
101
|
+
server.tool(name, description, schema, wrap(handler));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ══════════════════════════════════════════════════════════════
|
|
105
|
+
// TOOL: get_server_info
|
|
106
|
+
// ══════════════════════════════════════════════════════════════
|
|
107
|
+
server.tool('get_server_info', 'Get AGA server info, public keys, and portal state.', {}, async () => j({
|
|
108
|
+
server: 'AGA MCP Server', version: '0.1.0',
|
|
109
|
+
protocol: 'Attested Governance Artifacts v1.0.0',
|
|
110
|
+
patent: 'USPTO Application No. 19/433,835',
|
|
111
|
+
nist_references: ['NIST-2025-0035', 'NCCoE AI Agent Identity'],
|
|
112
|
+
issuer_public_key: pkToHex(issuerKP.publicKey),
|
|
113
|
+
portal_public_key: pkToHex(portalKP.publicKey),
|
|
114
|
+
chain_public_key: pkToHex(chainKP.publicKey),
|
|
115
|
+
chain_initialized: chainInitialized,
|
|
116
|
+
portal_state: portal.state,
|
|
117
|
+
}));
|
|
118
|
+
|
|
119
|
+
// ══════════════════════════════════════════════════════════════
|
|
120
|
+
// TOOL: get_portal_state — V3 RESTORED (was dropped in V2)
|
|
121
|
+
// ══════════════════════════════════════════════════════════════
|
|
122
|
+
server.tool('get_portal_state', 'Get current portal state, loaded artifact info, and enforcement status.', {}, async () => j({
|
|
123
|
+
state: portal.state,
|
|
124
|
+
artifact_loaded: !!portal.artifact,
|
|
125
|
+
sealed_hash: portal.artifact?.sealed_hash ?? null,
|
|
126
|
+
ttl_seconds: portal.artifact?.enforcement_parameters.ttl_seconds ?? null,
|
|
127
|
+
issued_at: portal.artifact?.issued_timestamp ?? null,
|
|
128
|
+
enforcement_triggers: portal.artifact?.enforcement_parameters.enforcement_triggers ?? [],
|
|
129
|
+
sequence_counter: portal.sequenceCounter,
|
|
130
|
+
quarantine_active: quarantine?.active ?? false,
|
|
131
|
+
}));
|
|
132
|
+
|
|
133
|
+
// ══════════════════════════════════════════════════════════════
|
|
134
|
+
// TOOL: init_chain (Claim 3a)
|
|
135
|
+
// ══════════════════════════════════════════════════════════════
|
|
136
|
+
server.tool('init_chain', 'Initialize continuity chain with genesis event. (Claim 3a)',
|
|
137
|
+
{ specification_hash: z.string().optional() },
|
|
138
|
+
async ({ specification_hash }) => {
|
|
139
|
+
if (chainInitialized) return j({ success: false, error: 'Chain already initialized' });
|
|
140
|
+
const genesis = createGenesisEvent(chainKP, specification_hash ?? sha256Str('AGA Protocol Specification v1.0.0'));
|
|
141
|
+
await storage.storeEvent(genesis);
|
|
142
|
+
chainInitialized = true;
|
|
143
|
+
portal.sequenceCounter = 0;
|
|
144
|
+
portal.lastLeafHash = genesis.leaf_hash;
|
|
145
|
+
return j({ success: true, genesis_event_id: genesis.event_id, genesis_leaf_hash: genesis.leaf_hash });
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
// ══════════════════════════════════════════════════════════════
|
|
150
|
+
// TOOL: attest_subject (Claims 1a-1d)
|
|
151
|
+
// ══════════════════════════════════════════════════════════════
|
|
152
|
+
server.tool('attest_subject',
|
|
153
|
+
'Attest subject, generate sealed Policy Artifact. Auto-loads into portal. (Claims 1a-1d)',
|
|
154
|
+
{
|
|
155
|
+
subject_content: z.string().describe('Content/bytes of the subject'),
|
|
156
|
+
subject_metadata: z.object({ filename: z.string().optional(), version: z.string().optional(), author: z.string().optional(), content_type: z.string().optional() }),
|
|
157
|
+
evidence_items: z.array(z.object({ label: z.string(), content: z.string() })).default([]),
|
|
158
|
+
behavioral_baseline: z.object({
|
|
159
|
+
permitted_tools: z.array(z.string()),
|
|
160
|
+
rate_limits: z.record(z.number()),
|
|
161
|
+
forbidden_sequences: z.array(z.array(z.string())),
|
|
162
|
+
window_ms: z.number(),
|
|
163
|
+
}).optional(),
|
|
164
|
+
},
|
|
165
|
+
async ({ subject_content, subject_metadata, evidence_items, behavioral_baseline }) => {
|
|
166
|
+
const subId = computeSubjectIdFromString(subject_content, subject_metadata);
|
|
167
|
+
const policyRef = sha256Str(JSON.stringify(DEFAULT_ENFORCEMENT));
|
|
168
|
+
const att = performAttestation({ subject_identifier: subId, policy_reference: policyRef, evidence_items });
|
|
169
|
+
if (!att.success || !att.sealed_hash || !att.seal_salt) return j({ success: false, error: att.rejection_reason });
|
|
170
|
+
|
|
171
|
+
const artifact = generateArtifact({
|
|
172
|
+
subject_identifier: subId, policy_reference: policyRef, policy_version: 1,
|
|
173
|
+
sealed_hash: att.sealed_hash, seal_salt: att.seal_salt,
|
|
174
|
+
enforcement_parameters: DEFAULT_ENFORCEMENT, disclosure_policy: DEFAULT_CLAIMS,
|
|
175
|
+
evidence_commitments: att.evidence_commitments, issuer_keypair: issuerKP,
|
|
176
|
+
});
|
|
177
|
+
await storage.storeArtifact(artifact);
|
|
178
|
+
|
|
179
|
+
portal.reset();
|
|
180
|
+
portal.loadArtifact(artifact, pkToHex(issuerKP.publicKey));
|
|
181
|
+
quarantine = null;
|
|
182
|
+
behavioralMonitor.reset();
|
|
183
|
+
if (behavioral_baseline) behavioralMonitor.setBaseline(behavioral_baseline);
|
|
184
|
+
|
|
185
|
+
await autoChain('POLICY_ISSUANCE', { artifact_hash: hashArtifact(artifact), sealed_hash: artifact.sealed_hash });
|
|
186
|
+
|
|
187
|
+
return j({
|
|
188
|
+
success: true, artifact_hash: hashArtifact(artifact), sealed_hash: artifact.sealed_hash,
|
|
189
|
+
subject_identifier: subId, portal_state: portal.state,
|
|
190
|
+
issuer_public_key: pkToHex(issuerKP.publicKey),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// ══════════════════════════════════════════════════════════════
|
|
196
|
+
// TOOL: measure_integrity (Claims 1e-1g)
|
|
197
|
+
// V3: Generates receipt for EVERY measurement (match or mismatch)
|
|
198
|
+
// V3: Checks TTL and revocation (fail-closed)
|
|
199
|
+
// ══════════════════════════════════════════════════════════════
|
|
200
|
+
governedTool('measure_integrity',
|
|
201
|
+
'Measure subject state, compare to sealed reference. Generates signed receipt for every measurement. (Claims 1e-1g)',
|
|
202
|
+
{
|
|
203
|
+
subject_content: z.string().describe('Current content of the subject'),
|
|
204
|
+
subject_metadata: z.object({ filename: z.string().optional(), version: z.string().optional(), author: z.string().optional(), content_type: z.string().optional() }),
|
|
205
|
+
},
|
|
206
|
+
async ({ subject_content, subject_metadata }) => {
|
|
207
|
+
if (!portal.artifact) return j({ success: false, error: 'No artifact loaded. Call attest_subject first.' });
|
|
208
|
+
if (portal.state === 'TERMINATED') return j({ success: false, error: 'Portal is terminated. Re-attest required.' });
|
|
209
|
+
|
|
210
|
+
const result = portal.measure(new TextEncoder().encode(subject_content), subject_metadata);
|
|
211
|
+
const artRef = hashArtifact(portal.artifact);
|
|
212
|
+
const currentStr = result.currentBytesHash ? `${result.currentBytesHash}||${result.currentMetaHash}` : 'UNAVAILABLE';
|
|
213
|
+
const sealedStr = `${result.expectedBytesHash}||${result.expectedMetaHash}`;
|
|
214
|
+
|
|
215
|
+
// Determine enforcement action
|
|
216
|
+
let action = null as import('./core/types.js').EnforcementAction | null;
|
|
217
|
+
let driftDesc: string | null = null;
|
|
218
|
+
|
|
219
|
+
if (!result.ttl_ok) {
|
|
220
|
+
driftDesc = 'TTL expired — fail-closed termination';
|
|
221
|
+
action = 'TERMINATE';
|
|
222
|
+
} else if (result.revoked) {
|
|
223
|
+
driftDesc = 'Artifact revoked — fail-closed termination';
|
|
224
|
+
action = 'TERMINATE';
|
|
225
|
+
} else if (!result.match) {
|
|
226
|
+
driftDesc = 'Subject modified — hash mismatch';
|
|
227
|
+
action = portal.artifact.enforcement_parameters.enforcement_triggers[0] ?? 'ALERT_ONLY';
|
|
228
|
+
portal.enforce(action);
|
|
229
|
+
if (action === 'QUARANTINE') quarantine = initQuarantine();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// V3: Receipt for EVERY measurement — match or mismatch
|
|
233
|
+
const receipt = generateReceipt({
|
|
234
|
+
subjectId: portal.artifact.subject_identifier, artifactRef: artRef,
|
|
235
|
+
currentHash: currentStr, sealedHash: sealedStr,
|
|
236
|
+
driftDetected: !result.match, driftDescription: driftDesc,
|
|
237
|
+
action, measurementType: portal.artifact.enforcement_parameters.measurement_types.join(','),
|
|
238
|
+
seq: portal.sequenceCounter + 1, prevLeaf: portal.lastLeafHash, portalKP,
|
|
239
|
+
});
|
|
240
|
+
await storage.storeReceipt(receipt);
|
|
241
|
+
await autoChain('INTERACTION_RECEIPT', { receipt_id: receipt.receipt_id, drift_detected: !result.match, enforcement_action: action });
|
|
242
|
+
|
|
243
|
+
return j({
|
|
244
|
+
success: true, match: result.match, drift_detected: !result.match,
|
|
245
|
+
ttl_ok: result.ttl_ok, revoked: result.revoked,
|
|
246
|
+
enforcement_action: action, portal_state: portal.state,
|
|
247
|
+
receipt_id: receipt.receipt_id,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
// ══════════════════════════════════════════════════════════════
|
|
253
|
+
// TOOL: revoke_artifact — V3 NEW (NCCoE Phase 3b)
|
|
254
|
+
// ══════════════════════════════════════════════════════════════
|
|
255
|
+
governedTool('revoke_artifact',
|
|
256
|
+
'Revoke an active policy artifact mid-session. Portal terminates on next measurement. (NCCoE Phase 3b)',
|
|
257
|
+
{
|
|
258
|
+
sealed_hash: z.string().describe('Sealed hash of artifact to revoke'),
|
|
259
|
+
reason: z.string().describe('Reason for revocation'),
|
|
260
|
+
},
|
|
261
|
+
async ({ sealed_hash, reason }) => {
|
|
262
|
+
portal.revoke(sealed_hash);
|
|
263
|
+
const record: RevocationRecord = {
|
|
264
|
+
artifact_sealed_hash: sealed_hash, reason,
|
|
265
|
+
revoked_by: pkToHex(issuerKP.publicKey), timestamp: utcNow(),
|
|
266
|
+
};
|
|
267
|
+
await autoChain('REVOCATION', record);
|
|
268
|
+
return j({ success: true, revoked: sealed_hash, portal_state: portal.state, reason });
|
|
269
|
+
}
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
// ══════════════════════════════════════════════════════════════
|
|
273
|
+
// TOOL: verify_chain (Claim 3c)
|
|
274
|
+
// ══════════════════════════════════════════════════════════════
|
|
275
|
+
server.tool('verify_chain', 'Verify continuity chain integrity. (Claim 3c)', {}, async () => {
|
|
276
|
+
const events = await storage.getAllEvents();
|
|
277
|
+
if (!events.length) return j({ success: false, error: 'No events in chain' });
|
|
278
|
+
const result = verifyChainIntegrity(events);
|
|
279
|
+
return j({ success: true, chain_valid: result.valid, events_verified: events.length, broken_at: result.brokenAt, error: result.error });
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// ══════════════════════════════════════════════════════════════
|
|
283
|
+
// TOOL: create_checkpoint (Claims 3d-3f)
|
|
284
|
+
// ══════════════════════════════════════════════════════════════
|
|
285
|
+
governedTool('create_checkpoint', 'Batch events into Merkle tree, anchor. (Claims 3d-3f)',
|
|
286
|
+
{ anchor_network: z.string().default('local') },
|
|
287
|
+
async ({ anchor_network }) => {
|
|
288
|
+
const lastCP = await storage.getLatestCheckpoint();
|
|
289
|
+
const startSeq = lastCP ? lastCP.batch_end_sequence + 1 : 0;
|
|
290
|
+
const latest = await storage.getLatestEvent();
|
|
291
|
+
if (!latest) return j({ success: false, error: 'No events' });
|
|
292
|
+
const events = await storage.getEvents(startSeq, latest.sequence_number);
|
|
293
|
+
if (!events.length) return j({ success: false, error: 'No new events since last checkpoint' });
|
|
294
|
+
const { checkpoint, payload } = createCheckpoint(events, anchor_network);
|
|
295
|
+
await storage.storeCheckpoint(checkpoint);
|
|
296
|
+
await autoChain('ANCHOR_BATCH', payload);
|
|
297
|
+
return j({ success: true, merkle_root: checkpoint.merkle_root, events_checkpointed: events.length, transaction_id: checkpoint.transaction_id });
|
|
298
|
+
}
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
// ══════════════════════════════════════════════════════════════
|
|
302
|
+
// TOOL: generate_evidence_bundle (Claim 9)
|
|
303
|
+
// ══════════════════════════════════════════════════════════════
|
|
304
|
+
governedTool('generate_evidence_bundle', 'Package artifact + receipts + Merkle proofs for offline verification. (Claim 9)', {}, async () => {
|
|
305
|
+
const artifact = await storage.getLatestArtifact();
|
|
306
|
+
if (!artifact) return j({ success: false, error: 'No artifact' });
|
|
307
|
+
const cp = await storage.getLatestCheckpoint();
|
|
308
|
+
if (!cp) return j({ success: false, error: 'No checkpoint. Call create_checkpoint first.' });
|
|
309
|
+
const receipts = await storage.getReceiptsByArtifact(hashArtifact(artifact));
|
|
310
|
+
const batchEvents = await storage.getEvents(cp.batch_start_sequence, cp.batch_end_sequence);
|
|
311
|
+
const proofs = receipts
|
|
312
|
+
.filter(r => r.sequence_number >= cp.batch_start_sequence && r.sequence_number <= cp.batch_end_sequence)
|
|
313
|
+
.map(r => eventInclusionProof(batchEvents, r.sequence_number));
|
|
314
|
+
const bundle = generateBundle(artifact, receipts, proofs, cp, portalKP);
|
|
315
|
+
return j({ success: true, bundle, offline_verifiable: true, receipt_count: receipts.length, proof_count: proofs.length });
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// ══════════════════════════════════════════════════════════════
|
|
319
|
+
// TOOL: verify_bundle_offline (Section J)
|
|
320
|
+
// ══════════════════════════════════════════════════════════════
|
|
321
|
+
governedTool('verify_bundle_offline', 'Verify evidence bundle offline. (Section J)',
|
|
322
|
+
{ bundle: z.any(), pinned_public_key: z.string() },
|
|
323
|
+
async ({ bundle, pinned_public_key }) => j({ success: true, verification: verifyBundleOffline(bundle, pinned_public_key) })
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
// ══════════════════════════════════════════════════════════════
|
|
327
|
+
// TOOL: request_claim (Claim 2)
|
|
328
|
+
// ══════════════════════════════════════════════════════════════
|
|
329
|
+
governedTool('request_claim', 'Request disclosure of a claim. Auto-substitutes if denied. (Claim 2)',
|
|
330
|
+
{ claim_id: z.string(), requester_id: z.string().default('anonymous'), mode: z.enum(['PROOF_ONLY', 'REVEAL_MIN', 'REVEAL_FULL']).default('REVEAL_MIN') },
|
|
331
|
+
async ({ claim_id, requester_id, mode }) => {
|
|
332
|
+
const latest = await storage.getLatestEvent();
|
|
333
|
+
const result = processDisclosure(
|
|
334
|
+
{ requested_claim_id: claim_id, requester_id, mode, timestamp: utcNow() },
|
|
335
|
+
DEFAULT_CLAIMS, CLAIM_VALUES, 1, latest?.sequence_number ?? 0, portalKP
|
|
336
|
+
);
|
|
337
|
+
if (result.substitution_receipt) await autoChain('SUBSTITUTION', result.substitution_receipt);
|
|
338
|
+
else await autoChain('DISCLOSURE', { claim_id, mode, permitted: result.permitted });
|
|
339
|
+
return j({ success: true, ...result });
|
|
340
|
+
}
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
// ══════════════════════════════════════════════════════════════
|
|
344
|
+
// TOOL: list_claims
|
|
345
|
+
// ══════════════════════════════════════════════════════════════
|
|
346
|
+
server.tool('list_claims', 'List available claims with sensitivity levels.', {}, async () => {
|
|
347
|
+
return j({ claims: DEFAULT_CLAIMS.claims_taxonomy.map(c => ({ claim_id: c.claim_id, sensitivity: c.sensitivity, substitutes: c.substitutes, permitted_modes: c.permitted_modes })) });
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// ══════════════════════════════════════════════════════════════
|
|
351
|
+
// TOOL: delegate_to_subagent (NCCoE: constrained sub-mandates)
|
|
352
|
+
// ══════════════════════════════════════════════════════════════
|
|
353
|
+
governedTool('delegate_to_subagent',
|
|
354
|
+
'Derive a constrained policy artifact for a sub-agent. Scope can only diminish, never expand. (NCCoE constrained delegation)',
|
|
355
|
+
{
|
|
356
|
+
enforcement_triggers: z.array(z.string()).describe('Subset of parent enforcement triggers'),
|
|
357
|
+
measurement_types: z.array(z.string()).describe('Subset of parent measurement types'),
|
|
358
|
+
requested_ttl_seconds: z.number().describe('Requested TTL (will be clamped to parent remaining)'),
|
|
359
|
+
delegation_purpose: z.string().describe('Purpose of the delegation'),
|
|
360
|
+
},
|
|
361
|
+
async ({ enforcement_triggers, measurement_types, requested_ttl_seconds, delegation_purpose }) => {
|
|
362
|
+
if (!portal.artifact) return j({ success: false, error: 'No artifact loaded. Call attest_subject first.' });
|
|
363
|
+
|
|
364
|
+
const result = deriveArtifact(portal.artifact, {
|
|
365
|
+
enforcement_triggers: enforcement_triggers as import('./core/types.js').EnforcementAction[],
|
|
366
|
+
measurement_types: measurement_types as import('./core/types.js').MeasurementType[],
|
|
367
|
+
requested_ttl_seconds,
|
|
368
|
+
delegation_purpose,
|
|
369
|
+
}, issuerKP);
|
|
370
|
+
|
|
371
|
+
if (result.success) {
|
|
372
|
+
await autoChain('ATTESTATION', {
|
|
373
|
+
type: 'DELEGATION',
|
|
374
|
+
parent_artifact_hash: result.parent_artifact_hash,
|
|
375
|
+
child_artifact_hash: result.child_artifact_hash,
|
|
376
|
+
effective_ttl: result.effective_ttl_seconds,
|
|
377
|
+
scope_reduction: result.scope_reduction,
|
|
378
|
+
purpose: delegation_purpose,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return j(result);
|
|
383
|
+
}
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
// ══════════════════════════════════════════════════════════════
|
|
387
|
+
// TOOL: measure_behavior (NIST-2025-0035)
|
|
388
|
+
// ══════════════════════════════════════════════════════════════
|
|
389
|
+
server.tool('measure_behavior',
|
|
390
|
+
'Measure behavioral patterns of agent tool usage. Detects unauthorized tools, rate violations, and forbidden sequences. (NIST-2025-0035)',
|
|
391
|
+
{},
|
|
392
|
+
async () => {
|
|
393
|
+
const measurement = behavioralMonitor.measure();
|
|
394
|
+
if (measurement.drift_detected) {
|
|
395
|
+
await autoChain('INTERACTION_RECEIPT', {
|
|
396
|
+
type: 'BEHAVIORAL_DRIFT',
|
|
397
|
+
violations: measurement.violations,
|
|
398
|
+
behavioral_hash: measurement.behavioral_hash,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
return j({
|
|
402
|
+
success: true,
|
|
403
|
+
...measurement,
|
|
404
|
+
violation_count: measurement.violations.length,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
// ══════════════════════════════════════════════════════════════
|
|
410
|
+
// TOOL: get_receipts — V3 NEW
|
|
411
|
+
// ══════════════════════════════════════════════════════════════
|
|
412
|
+
server.tool('get_receipts', 'Get all signed receipts, optionally filtered by artifact.',
|
|
413
|
+
{ artifact_hash: z.string().optional() },
|
|
414
|
+
async ({ artifact_hash }) => {
|
|
415
|
+
const receipts = artifact_hash
|
|
416
|
+
? await storage.getReceiptsByArtifact(artifact_hash)
|
|
417
|
+
: await storage.getAllReceipts();
|
|
418
|
+
return j({ count: receipts.length, receipts: receipts.map(r => ({ receipt_id: r.receipt_id, drift_detected: r.drift_detected, enforcement_action: r.enforcement_action, measurement_type: r.measurement_type, timestamp: r.timestamp })) });
|
|
419
|
+
}
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
// ══════════════════════════════════════════════════════════════
|
|
423
|
+
// TOOL: get_chain_events — V3 NEW
|
|
424
|
+
// ══════════════════════════════════════════════════════════════
|
|
425
|
+
server.tool('get_chain_events', 'Get continuity chain events.',
|
|
426
|
+
{ start_seq: z.number().optional(), end_seq: z.number().optional() },
|
|
427
|
+
async ({ start_seq, end_seq }) => {
|
|
428
|
+
const events = (start_seq !== undefined && end_seq !== undefined)
|
|
429
|
+
? await storage.getEvents(start_seq, end_seq)
|
|
430
|
+
: await storage.getAllEvents();
|
|
431
|
+
return j({ count: events.length, events: events.map(e => ({ sequence_number: e.sequence_number, event_type: e.event_type, event_id: e.event_id, timestamp: e.timestamp, leaf_hash: e.leaf_hash.slice(0, 16) + '...' })) });
|
|
432
|
+
}
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
return server;
|
|
436
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PolicyArtifact, ContinuityEvent, SignedReceipt, CheckpointReference } from '../core/types.js';
|
|
2
|
+
|
|
3
|
+
export interface AGAStorage {
|
|
4
|
+
initialize(): Promise<void>;
|
|
5
|
+
close(): Promise<void>;
|
|
6
|
+
storeArtifact(a: PolicyArtifact): Promise<void>;
|
|
7
|
+
getArtifact(sealedHash: string): Promise<PolicyArtifact | null>;
|
|
8
|
+
getLatestArtifact(): Promise<PolicyArtifact | null>;
|
|
9
|
+
storeEvent(e: ContinuityEvent): Promise<void>;
|
|
10
|
+
getEvent(seq: number): Promise<ContinuityEvent | null>;
|
|
11
|
+
getEvents(startSeq: number, endSeq: number): Promise<ContinuityEvent[]>;
|
|
12
|
+
getLatestEvent(): Promise<ContinuityEvent | null>;
|
|
13
|
+
getAllEvents(): Promise<ContinuityEvent[]>;
|
|
14
|
+
storeReceipt(r: SignedReceipt): Promise<void>;
|
|
15
|
+
getReceipt(id: string): Promise<SignedReceipt | null>;
|
|
16
|
+
getReceiptsByArtifact(ref: string): Promise<SignedReceipt[]>;
|
|
17
|
+
getAllReceipts(): Promise<SignedReceipt[]>;
|
|
18
|
+
storeCheckpoint(c: CheckpointReference): Promise<void>;
|
|
19
|
+
getLatestCheckpoint(): Promise<CheckpointReference | null>;
|
|
20
|
+
getCheckpoints(): Promise<CheckpointReference[]>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { AGAStorage } from './interface.js';
|
|
2
|
+
import type { PolicyArtifact, ContinuityEvent, SignedReceipt, CheckpointReference } from '../core/types.js';
|
|
3
|
+
|
|
4
|
+
export class MemoryStorage implements AGAStorage {
|
|
5
|
+
private artifacts = new Map<string, PolicyArtifact>();
|
|
6
|
+
private events: ContinuityEvent[] = [];
|
|
7
|
+
private receipts = new Map<string, SignedReceipt>();
|
|
8
|
+
private checkpoints: CheckpointReference[] = [];
|
|
9
|
+
|
|
10
|
+
async initialize() {}
|
|
11
|
+
async close() {}
|
|
12
|
+
async storeArtifact(a: PolicyArtifact) { this.artifacts.set(a.sealed_hash, a); }
|
|
13
|
+
async getArtifact(h: string) { return this.artifacts.get(h) ?? null; }
|
|
14
|
+
async getLatestArtifact() { const a = [...this.artifacts.values()]; return a.length ? a[a.length - 1] : null; }
|
|
15
|
+
async storeEvent(e: ContinuityEvent) { this.events.push(e); }
|
|
16
|
+
async getEvent(seq: number) { return this.events.find(e => e.sequence_number === seq) ?? null; }
|
|
17
|
+
async getEvents(s: number, e: number) { return this.events.filter(ev => ev.sequence_number >= s && ev.sequence_number <= e); }
|
|
18
|
+
async getLatestEvent() { return this.events.length ? this.events[this.events.length - 1] : null; }
|
|
19
|
+
async getAllEvents() { return [...this.events]; }
|
|
20
|
+
async storeReceipt(r: SignedReceipt) { this.receipts.set(r.receipt_id, r); }
|
|
21
|
+
async getReceipt(id: string) { return this.receipts.get(id) ?? null; }
|
|
22
|
+
async getReceiptsByArtifact(ref: string) { return [...this.receipts.values()].filter(r => r.artifact_reference === ref); }
|
|
23
|
+
async getAllReceipts() { return [...this.receipts.values()]; }
|
|
24
|
+
async storeCheckpoint(c: CheckpointReference) { this.checkpoints.push(c); }
|
|
25
|
+
async getLatestCheckpoint() { return this.checkpoints.length ? this.checkpoints[this.checkpoints.length - 1] : null; }
|
|
26
|
+
async getCheckpoints() { return [...this.checkpoints]; }
|
|
27
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { AGAStorage } from './interface.js';
|
|
2
|
+
import type { PolicyArtifact, ContinuityEvent, SignedReceipt, CheckpointReference } from '../core/types.js';
|
|
3
|
+
|
|
4
|
+
// Dynamic import — better-sqlite3 is optional (requires native build tools)
|
|
5
|
+
let Database: any;
|
|
6
|
+
try {
|
|
7
|
+
Database = (await import('better-sqlite3')).default;
|
|
8
|
+
} catch {
|
|
9
|
+
// Will throw at construction time if better-sqlite3 is not available
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class SQLiteStorage implements AGAStorage {
|
|
13
|
+
private db: any;
|
|
14
|
+
constructor(path = 'aga.sqlite') {
|
|
15
|
+
if (!Database) throw new Error('better-sqlite3 is not installed. Install Visual Studio Build Tools and run: npm install better-sqlite3');
|
|
16
|
+
this.db = new Database(path);
|
|
17
|
+
this.db.pragma('journal_mode = WAL');
|
|
18
|
+
}
|
|
19
|
+
async initialize() {
|
|
20
|
+
this.db.exec(`
|
|
21
|
+
CREATE TABLE IF NOT EXISTS artifacts (sealed_hash TEXT PRIMARY KEY, data TEXT NOT NULL, created_at TEXT DEFAULT (datetime('now')));
|
|
22
|
+
CREATE TABLE IF NOT EXISTS chain_events (sequence_number INTEGER PRIMARY KEY, event_id TEXT UNIQUE, event_type TEXT, leaf_hash TEXT, data TEXT NOT NULL);
|
|
23
|
+
CREATE TABLE IF NOT EXISTS receipts (receipt_id TEXT PRIMARY KEY, artifact_reference TEXT, sequence_number INTEGER, data TEXT NOT NULL);
|
|
24
|
+
CREATE TABLE IF NOT EXISTS checkpoints (id INTEGER PRIMARY KEY AUTOINCREMENT, merkle_root TEXT, batch_start INTEGER, batch_end INTEGER, data TEXT NOT NULL);
|
|
25
|
+
CREATE INDEX IF NOT EXISTS idx_receipts_artifact ON receipts(artifact_reference);
|
|
26
|
+
`);
|
|
27
|
+
}
|
|
28
|
+
async close() { this.db.close(); }
|
|
29
|
+
private p<T>(row: any): T | null { return row ? JSON.parse(row.data) : null; }
|
|
30
|
+
async storeArtifact(a: PolicyArtifact) { this.db.prepare('INSERT OR REPLACE INTO artifacts (sealed_hash,data) VALUES (?,?)').run(a.sealed_hash, JSON.stringify(a)); }
|
|
31
|
+
async getArtifact(h: string) { return this.p<PolicyArtifact>(this.db.prepare('SELECT data FROM artifacts WHERE sealed_hash=?').get(h)); }
|
|
32
|
+
async getLatestArtifact() { return this.p<PolicyArtifact>(this.db.prepare('SELECT data FROM artifacts ORDER BY created_at DESC LIMIT 1').get()); }
|
|
33
|
+
async storeEvent(e: ContinuityEvent) { this.db.prepare('INSERT INTO chain_events (sequence_number,event_id,event_type,leaf_hash,data) VALUES (?,?,?,?,?)').run(e.sequence_number, e.event_id, e.event_type, e.leaf_hash, JSON.stringify(e)); }
|
|
34
|
+
async getEvent(seq: number) { return this.p<ContinuityEvent>(this.db.prepare('SELECT data FROM chain_events WHERE sequence_number=?').get(seq)); }
|
|
35
|
+
async getEvents(s: number, e: number) { return (this.db.prepare('SELECT data FROM chain_events WHERE sequence_number>=? AND sequence_number<=? ORDER BY sequence_number').all(s, e) as any[]).map((r: any) => JSON.parse(r.data)); }
|
|
36
|
+
async getLatestEvent() { return this.p<ContinuityEvent>(this.db.prepare('SELECT data FROM chain_events ORDER BY sequence_number DESC LIMIT 1').get()); }
|
|
37
|
+
async getAllEvents() { return (this.db.prepare('SELECT data FROM chain_events ORDER BY sequence_number').all() as any[]).map((r: any) => JSON.parse(r.data)); }
|
|
38
|
+
async storeReceipt(r: SignedReceipt) { this.db.prepare('INSERT INTO receipts (receipt_id,artifact_reference,sequence_number,data) VALUES (?,?,?,?)').run(r.receipt_id, r.artifact_reference, r.sequence_number, JSON.stringify(r)); }
|
|
39
|
+
async getReceipt(id: string) { return this.p<SignedReceipt>(this.db.prepare('SELECT data FROM receipts WHERE receipt_id=?').get(id)); }
|
|
40
|
+
async getReceiptsByArtifact(ref: string) { return (this.db.prepare('SELECT data FROM receipts WHERE artifact_reference=? ORDER BY sequence_number').all(ref) as any[]).map((r: any) => JSON.parse(r.data)); }
|
|
41
|
+
async getAllReceipts() { return (this.db.prepare('SELECT data FROM receipts ORDER BY sequence_number').all() as any[]).map((r: any) => JSON.parse(r.data)); }
|
|
42
|
+
async storeCheckpoint(c: CheckpointReference) { this.db.prepare('INSERT INTO checkpoints (merkle_root,batch_start,batch_end,data) VALUES (?,?,?,?)').run(c.merkle_root, c.batch_start_sequence, c.batch_end_sequence, JSON.stringify(c)); }
|
|
43
|
+
async getLatestCheckpoint() { return this.p<CheckpointReference>(this.db.prepare('SELECT data FROM checkpoints ORDER BY id DESC LIMIT 1').get()); }
|
|
44
|
+
async getCheckpoints() { return (this.db.prepare('SELECT data FROM checkpoints ORDER BY id').all() as any[]).map((r: any) => JSON.parse(r.data)); }
|
|
45
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Tools Directory
|
|
2
|
+
|
|
3
|
+
All MCP tool handlers are defined inline in `src/server.ts`.
|
|
4
|
+
This keeps the server as a single-file reference implementation.
|
|
5
|
+
|
|
6
|
+
For production use, refactor tools into individual files here:
|
|
7
|
+
- attestation-tools.ts (attest_subject)
|
|
8
|
+
- enforcement-tools.ts (measure_integrity, revoke_artifact)
|
|
9
|
+
- chain-tools.ts (init_chain, verify_chain, get_chain_events)
|
|
10
|
+
- checkpoint-tools.ts (create_checkpoint)
|
|
11
|
+
- bundle-tools.ts (generate_evidence_bundle, verify_bundle_offline)
|
|
12
|
+
- disclosure-tools.ts (request_claim, list_claims)
|
|
13
|
+
- portal-tools.ts (get_server_info, get_portal_state, get_receipts)
|