@attested-intelligence/aga-mcp-server 2.0.1 → 2.2.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 (241) hide show
  1. package/README.md +197 -124
  2. package/SECURITY.md +59 -0
  3. package/dist/adapters/openclaw.d.ts +43 -0
  4. package/dist/adapters/openclaw.d.ts.map +1 -0
  5. package/dist/adapters/openclaw.js +86 -0
  6. package/dist/adapters/openclaw.js.map +1 -0
  7. package/dist/core/bundle.d.ts +9 -2
  8. package/dist/core/bundle.d.ts.map +1 -1
  9. package/dist/core/bundle.js +16 -2
  10. package/dist/core/bundle.js.map +1 -1
  11. package/dist/core/identity.d.ts +19 -10
  12. package/dist/core/identity.d.ts.map +1 -1
  13. package/dist/core/identity.js +45 -11
  14. package/dist/core/identity.js.map +1 -1
  15. package/dist/core/portal.d.ts +10 -1
  16. package/dist/core/portal.d.ts.map +1 -1
  17. package/dist/core/portal.js +16 -12
  18. package/dist/core/portal.js.map +1 -1
  19. package/dist/core/types.d.ts +29 -2
  20. package/dist/core/types.d.ts.map +1 -1
  21. package/dist/crypto/index.d.ts +5 -6
  22. package/dist/crypto/index.d.ts.map +1 -1
  23. package/dist/crypto/index.js +5 -6
  24. package/dist/crypto/index.js.map +1 -1
  25. package/dist/crypto/sign.d.ts +2 -0
  26. package/dist/crypto/sign.d.ts.map +1 -1
  27. package/dist/crypto/sign.js +6 -0
  28. package/dist/crypto/sign.js.map +1 -1
  29. package/dist/index.js +1 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/middleware/governance.d.ts +7 -1
  32. package/dist/middleware/governance.d.ts.map +1 -1
  33. package/dist/middleware/governance.js +18 -11
  34. package/dist/middleware/governance.js.map +1 -1
  35. package/dist/proxy/evaluator.d.ts +14 -0
  36. package/dist/proxy/evaluator.d.ts.map +1 -0
  37. package/dist/proxy/evaluator.js +141 -0
  38. package/dist/proxy/evaluator.js.map +1 -0
  39. package/dist/proxy/index.d.ts +22 -0
  40. package/dist/proxy/index.d.ts.map +1 -0
  41. package/dist/proxy/index.js +230 -0
  42. package/dist/proxy/index.js.map +1 -0
  43. package/dist/proxy/profiles.d.ts +16 -0
  44. package/dist/proxy/profiles.d.ts.map +1 -0
  45. package/dist/proxy/profiles.js +43 -0
  46. package/dist/proxy/profiles.js.map +1 -0
  47. package/dist/proxy/server.d.ts +106 -0
  48. package/dist/proxy/server.d.ts.map +1 -0
  49. package/dist/proxy/server.js +389 -0
  50. package/dist/proxy/server.js.map +1 -0
  51. package/dist/proxy/stdio-bridge.d.ts +42 -0
  52. package/dist/proxy/stdio-bridge.d.ts.map +1 -0
  53. package/dist/proxy/stdio-bridge.js +142 -0
  54. package/dist/proxy/stdio-bridge.js.map +1 -0
  55. package/dist/proxy/types.d.ts +36 -0
  56. package/dist/proxy/types.d.ts.map +1 -0
  57. package/dist/proxy/types.js +11 -0
  58. package/dist/proxy/types.js.map +1 -0
  59. package/dist/proxy/verify.d.ts +29 -0
  60. package/dist/proxy/verify.d.ts.map +1 -0
  61. package/dist/proxy/verify.js +183 -0
  62. package/dist/proxy/verify.js.map +1 -0
  63. package/dist/server.d.ts +7 -3
  64. package/dist/server.d.ts.map +1 -1
  65. package/dist/server.js +342 -214
  66. package/dist/server.js.map +1 -1
  67. package/dist/storage/sqlite.js +6 -6
  68. package/independent-verifier/README.md +31 -0
  69. package/independent-verifier/package.json +18 -0
  70. package/independent-verifier/verify.ts +211 -0
  71. package/package.json +97 -71
  72. package/src/adapters/openclaw.ts +125 -0
  73. package/src/core/artifact.ts +45 -0
  74. package/src/core/attestation.ts +33 -0
  75. package/src/core/behavioral.ts +132 -0
  76. package/src/core/bundle.ts +45 -0
  77. package/src/core/chain.ts +72 -0
  78. package/src/core/checkpoint.ts +22 -0
  79. package/src/core/delegation.ts +146 -0
  80. package/src/core/disclosure.ts +32 -0
  81. package/src/core/identity.ts +62 -0
  82. package/src/core/index.ts +14 -0
  83. package/src/core/portal.ts +117 -0
  84. package/src/core/quarantine.ts +16 -0
  85. package/src/core/receipt.ts +33 -0
  86. package/src/core/subject.ts +11 -0
  87. package/src/core/types.ts +285 -0
  88. package/src/crypto/hash.ts +33 -0
  89. package/src/crypto/index.ts +5 -0
  90. package/src/crypto/merkle.ts +43 -0
  91. package/src/crypto/salt.ts +18 -0
  92. package/src/crypto/sign.ts +42 -0
  93. package/src/crypto/types.ts +19 -0
  94. package/src/index.ts +12 -0
  95. package/src/middleware/governance.ts +95 -0
  96. package/src/middleware/index.ts +1 -0
  97. package/src/proxy/evaluator.ts +176 -0
  98. package/src/proxy/index.ts +259 -0
  99. package/src/proxy/profiles.ts +48 -0
  100. package/src/proxy/server.ts +499 -0
  101. package/src/proxy/stdio-bridge.ts +171 -0
  102. package/src/proxy/types.ts +40 -0
  103. package/src/proxy/verify.ts +202 -0
  104. package/src/server.ts +435 -0
  105. package/src/storage/index.ts +3 -0
  106. package/src/storage/interface.ts +21 -0
  107. package/src/storage/memory.ts +27 -0
  108. package/src/storage/sqlite.ts +45 -0
  109. package/src/tools/README.md +13 -0
  110. package/src/utils/canonical.ts +14 -0
  111. package/src/utils/constants.ts +3 -0
  112. package/src/utils/timestamp.ts +12 -0
  113. package/src/utils/uuid.ts +2 -0
  114. package/dist/context.d.ts +0 -39
  115. package/dist/context.d.ts.map +0 -1
  116. package/dist/context.js +0 -113
  117. package/dist/context.js.map +0 -1
  118. package/dist/core/measurement.d.ts +0 -16
  119. package/dist/core/measurement.d.ts.map +0 -1
  120. package/dist/core/measurement.js +0 -18
  121. package/dist/core/measurement.js.map +0 -1
  122. package/dist/crypto/canonicalize.d.ts +0 -7
  123. package/dist/crypto/canonicalize.d.ts.map +0 -1
  124. package/dist/crypto/canonicalize.js +0 -21
  125. package/dist/crypto/canonicalize.js.map +0 -1
  126. package/dist/crypto/keys.d.ts +0 -10
  127. package/dist/crypto/keys.d.ts.map +0 -1
  128. package/dist/crypto/keys.js +0 -19
  129. package/dist/crypto/keys.js.map +0 -1
  130. package/dist/prompts/drift-analysis.d.ts +0 -13
  131. package/dist/prompts/drift-analysis.d.ts.map +0 -1
  132. package/dist/prompts/drift-analysis.js +0 -43
  133. package/dist/prompts/drift-analysis.js.map +0 -1
  134. package/dist/prompts/governance-report.d.ts +0 -7
  135. package/dist/prompts/governance-report.d.ts.map +0 -1
  136. package/dist/prompts/governance-report.js +0 -26
  137. package/dist/prompts/governance-report.js.map +0 -1
  138. package/dist/prompts/nccoe-demo.d.ts +0 -14
  139. package/dist/prompts/nccoe-demo.d.ts.map +0 -1
  140. package/dist/prompts/nccoe-demo.js +0 -47
  141. package/dist/prompts/nccoe-demo.js.map +0 -1
  142. package/dist/resources/cosai-mapping.d.ts +0 -24
  143. package/dist/resources/cosai-mapping.d.ts.map +0 -1
  144. package/dist/resources/cosai-mapping.js +0 -127
  145. package/dist/resources/cosai-mapping.js.map +0 -1
  146. package/dist/resources/crypto-primitives.d.ts +0 -3
  147. package/dist/resources/crypto-primitives.d.ts.map +0 -1
  148. package/dist/resources/crypto-primitives.js +0 -52
  149. package/dist/resources/crypto-primitives.js.map +0 -1
  150. package/dist/resources/sample-bundle.d.ts +0 -6
  151. package/dist/resources/sample-bundle.d.ts.map +0 -1
  152. package/dist/resources/sample-bundle.js +0 -58
  153. package/dist/resources/sample-bundle.js.map +0 -1
  154. package/dist/resources/specification.d.ts +0 -3
  155. package/dist/resources/specification.d.ts.map +0 -1
  156. package/dist/resources/specification.js +0 -161
  157. package/dist/resources/specification.js.map +0 -1
  158. package/dist/tools/create-artifact.d.ts +0 -25
  159. package/dist/tools/create-artifact.d.ts.map +0 -1
  160. package/dist/tools/create-artifact.js +0 -85
  161. package/dist/tools/create-artifact.js.map +0 -1
  162. package/dist/tools/delegate-subagent.d.ts +0 -18
  163. package/dist/tools/delegate-subagent.d.ts.map +0 -1
  164. package/dist/tools/delegate-subagent.js +0 -50
  165. package/dist/tools/delegate-subagent.js.map +0 -1
  166. package/dist/tools/disclose-claim.d.ts +0 -14
  167. package/dist/tools/disclose-claim.d.ts.map +0 -1
  168. package/dist/tools/disclose-claim.js +0 -23
  169. package/dist/tools/disclose-claim.js.map +0 -1
  170. package/dist/tools/export-bundle.d.ts +0 -8
  171. package/dist/tools/export-bundle.d.ts.map +0 -1
  172. package/dist/tools/export-bundle.js +0 -25
  173. package/dist/tools/export-bundle.js.map +0 -1
  174. package/dist/tools/full-lifecycle.d.ts +0 -16
  175. package/dist/tools/full-lifecycle.d.ts.map +0 -1
  176. package/dist/tools/full-lifecycle.js +0 -121
  177. package/dist/tools/full-lifecycle.js.map +0 -1
  178. package/dist/tools/generate-receipt.d.ts +0 -16
  179. package/dist/tools/generate-receipt.d.ts.map +0 -1
  180. package/dist/tools/generate-receipt.js +0 -31
  181. package/dist/tools/generate-receipt.js.map +0 -1
  182. package/dist/tools/get-chain.d.ts +0 -14
  183. package/dist/tools/get-chain.d.ts.map +0 -1
  184. package/dist/tools/get-chain.js +0 -45
  185. package/dist/tools/get-chain.js.map +0 -1
  186. package/dist/tools/get-portal-state.d.ts +0 -8
  187. package/dist/tools/get-portal-state.d.ts.map +0 -1
  188. package/dist/tools/get-portal-state.js +0 -15
  189. package/dist/tools/get-portal-state.js.map +0 -1
  190. package/dist/tools/init-chain.d.ts +0 -10
  191. package/dist/tools/init-chain.d.ts.map +0 -1
  192. package/dist/tools/init-chain.js +0 -13
  193. package/dist/tools/init-chain.js.map +0 -1
  194. package/dist/tools/measure-behavior.d.ts +0 -12
  195. package/dist/tools/measure-behavior.d.ts.map +0 -1
  196. package/dist/tools/measure-behavior.js +0 -29
  197. package/dist/tools/measure-behavior.js.map +0 -1
  198. package/dist/tools/measure-subject.d.ts +0 -15
  199. package/dist/tools/measure-subject.d.ts.map +0 -1
  200. package/dist/tools/measure-subject.js +0 -106
  201. package/dist/tools/measure-subject.js.map +0 -1
  202. package/dist/tools/quarantine-status.d.ts +0 -8
  203. package/dist/tools/quarantine-status.d.ts.map +0 -1
  204. package/dist/tools/quarantine-status.js +0 -16
  205. package/dist/tools/quarantine-status.js.map +0 -1
  206. package/dist/tools/revoke-artifact.d.ts +0 -13
  207. package/dist/tools/revoke-artifact.d.ts.map +0 -1
  208. package/dist/tools/revoke-artifact.js +0 -24
  209. package/dist/tools/revoke-artifact.js.map +0 -1
  210. package/dist/tools/rotate-keys.d.ts +0 -13
  211. package/dist/tools/rotate-keys.d.ts.map +0 -1
  212. package/dist/tools/rotate-keys.js +0 -39
  213. package/dist/tools/rotate-keys.js.map +0 -1
  214. package/dist/tools/server-info.d.ts +0 -8
  215. package/dist/tools/server-info.d.ts.map +0 -1
  216. package/dist/tools/server-info.js +0 -23
  217. package/dist/tools/server-info.js.map +0 -1
  218. package/dist/tools/set-verification-tier.d.ts +0 -11
  219. package/dist/tools/set-verification-tier.d.ts.map +0 -1
  220. package/dist/tools/set-verification-tier.js +0 -31
  221. package/dist/tools/set-verification-tier.js.map +0 -1
  222. package/dist/tools/start-monitoring.d.ts +0 -12
  223. package/dist/tools/start-monitoring.d.ts.map +0 -1
  224. package/dist/tools/start-monitoring.js +0 -17
  225. package/dist/tools/start-monitoring.js.map +0 -1
  226. package/dist/tools/trigger-measurement.d.ts +0 -15
  227. package/dist/tools/trigger-measurement.d.ts.map +0 -1
  228. package/dist/tools/trigger-measurement.js +0 -86
  229. package/dist/tools/trigger-measurement.js.map +0 -1
  230. package/dist/tools/verify-artifact.d.ts +0 -13
  231. package/dist/tools/verify-artifact.d.ts.map +0 -1
  232. package/dist/tools/verify-artifact.js +0 -6
  233. package/dist/tools/verify-artifact.js.map +0 -1
  234. package/dist/tools/verify-bundle.d.ts +0 -13
  235. package/dist/tools/verify-bundle.d.ts.map +0 -1
  236. package/dist/tools/verify-bundle.js +0 -6
  237. package/dist/tools/verify-bundle.js.map +0 -1
  238. package/dist/types.d.ts +0 -261
  239. package/dist/types.d.ts.map +0 -1
  240. package/dist/types.js +0 -8
  241. package/dist/types.js.map +0 -1
@@ -0,0 +1,45 @@
1
+ import { signStr, sigToB64, b64ToSig, pkToHex, hexToPk, verifyStr } from '../crypto/sign.js';
2
+ import { sha256Str } from '../crypto/hash.js';
3
+ import { canonicalize } from '../utils/canonical.js';
4
+ import { utcNow } from '../utils/timestamp.js';
5
+ import { SCHEMA_VERSION, PROTOCOL_VERSION } from '../utils/constants.js';
6
+ import type { KeyPair, HashHex } from '../crypto/types.js';
7
+ import type { PolicyArtifact, SubjectIdentifier, EnforcementParams, DisclosurePolicy, EvidenceCommitmentRecord } from './types.js';
8
+
9
+ export interface ArtifactInput {
10
+ subject_identifier: SubjectIdentifier;
11
+ policy_reference: HashHex;
12
+ policy_version: number;
13
+ sealed_hash: HashHex;
14
+ seal_salt: string;
15
+ enforcement_parameters: EnforcementParams;
16
+ disclosure_policy: DisclosurePolicy;
17
+ evidence_commitments: EvidenceCommitmentRecord[];
18
+ issuer_keypair: KeyPair;
19
+ effective_timestamp?: string;
20
+ expiration_timestamp?: string | null;
21
+ }
22
+
23
+ export function generateArtifact(input: ArtifactInput): PolicyArtifact {
24
+ const now = utcNow();
25
+ const unsigned: Omit<PolicyArtifact, 'signature'> = {
26
+ schema_version: SCHEMA_VERSION, protocol_version: PROTOCOL_VERSION,
27
+ subject_identifier: input.subject_identifier, policy_reference: input.policy_reference,
28
+ policy_version: input.policy_version, sealed_hash: input.sealed_hash,
29
+ seal_salt: input.seal_salt, issued_timestamp: now,
30
+ effective_timestamp: input.effective_timestamp ?? now,
31
+ expiration_timestamp: input.expiration_timestamp ?? null,
32
+ issuer_identifier: pkToHex(input.issuer_keypair.publicKey),
33
+ enforcement_parameters: input.enforcement_parameters,
34
+ disclosure_policy: input.disclosure_policy,
35
+ evidence_commitments: input.evidence_commitments,
36
+ };
37
+ return { ...unsigned, signature: sigToB64(signStr(canonicalize(unsigned), input.issuer_keypair.secretKey)) };
38
+ }
39
+
40
+ export function hashArtifact(a: PolicyArtifact): HashHex { return sha256Str(canonicalize(a)); }
41
+
42
+ export function verifyArtifactSignature(a: PolicyArtifact, issuerPkHex: string): boolean {
43
+ const { signature, ...unsigned } = a;
44
+ return verifyStr(b64ToSig(signature), canonicalize(unsigned), hexToPk(issuerPkHex));
45
+ }
@@ -0,0 +1,33 @@
1
+ import { saltedCommitment, generateSalt } from '../crypto/salt.js';
2
+ import { sha256HexCat } from '../crypto/hash.js';
3
+ import type { SubjectIdentifier, EvidenceCommitmentRecord } from './types.js';
4
+ import type { HashHex, SaltHex } from '../crypto/types.js';
5
+
6
+ export interface AttestationInput {
7
+ subject_identifier: SubjectIdentifier;
8
+ policy_reference: HashHex;
9
+ evidence_items: Array<{ label: string; content: string }>;
10
+ }
11
+
12
+ export interface AttestationResult {
13
+ success: boolean;
14
+ sealed_hash: HashHex | null;
15
+ seal_salt: SaltHex | null;
16
+ evidence_commitments: EvidenceCommitmentRecord[];
17
+ rejection_reason: string | null;
18
+ }
19
+
20
+ export function performAttestation(input: AttestationInput): AttestationResult {
21
+ const evidence_commitments = input.evidence_items.map(item => {
22
+ const { commitment, salt } = saltedCommitment(item.content);
23
+ return { commitment, salt, label: item.label };
24
+ });
25
+ const seal_salt = generateSalt();
26
+ const sealed_hash = sha256HexCat(
27
+ input.subject_identifier.bytes_hash,
28
+ input.subject_identifier.metadata_hash,
29
+ input.policy_reference,
30
+ seal_salt
31
+ );
32
+ return { success: true, sealed_hash, seal_salt, evidence_commitments, rejection_reason: null };
33
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Behavioral Drift Detection.
3
+ * NIST-2025-0035: "Governance mechanisms must measure behavioral outputs
4
+ * and decision patterns in addition to static artifacts."
5
+ *
6
+ * Tracks tool invocation patterns and compares against a behavioral
7
+ * baseline sealed in the policy artifact.
8
+ */
9
+ import { sha256Str } from '../crypto/hash.js';
10
+ import type { HashHex } from '../crypto/types.js';
11
+
12
+ export interface ToolInvocation {
13
+ tool_name: string;
14
+ timestamp: string;
15
+ args_hash: HashHex; // hash of args, not args themselves (privacy)
16
+ }
17
+
18
+ export interface BehavioralBaseline {
19
+ /** Allowed tools in this policy context */
20
+ permitted_tools: string[];
21
+ /** Maximum invocations per tool per measurement window */
22
+ rate_limits: Record<string, number>;
23
+ /** Forbidden tool sequences (e.g., read_secret then send_email) */
24
+ forbidden_sequences: string[][];
25
+ /** Measurement window in milliseconds */
26
+ window_ms: number;
27
+ }
28
+
29
+ export interface BehavioralMeasurement {
30
+ window_start: string;
31
+ window_end: string;
32
+ invocations: ToolInvocation[];
33
+ violations: BehavioralViolation[];
34
+ behavioral_hash: HashHex; // hash of the behavioral pattern
35
+ drift_detected: boolean;
36
+ }
37
+
38
+ export type BehavioralViolation =
39
+ | { type: 'UNAUTHORIZED_TOOL'; tool: string }
40
+ | { type: 'RATE_EXCEEDED'; tool: string; count: number; limit: number }
41
+ | { type: 'FORBIDDEN_SEQUENCE'; sequence: string[] };
42
+
43
+ export class BehavioralMonitor {
44
+ private invocations: ToolInvocation[] = [];
45
+ private baseline: BehavioralBaseline | null = null;
46
+
47
+ setBaseline(baseline: BehavioralBaseline): void {
48
+ this.baseline = baseline;
49
+ }
50
+
51
+ recordInvocation(toolName: string, argsHash: HashHex): void {
52
+ this.invocations.push({
53
+ tool_name: toolName,
54
+ timestamp: new Date().toISOString(),
55
+ args_hash: argsHash,
56
+ });
57
+ }
58
+
59
+ measure(): BehavioralMeasurement {
60
+ if (!this.baseline) {
61
+ return {
62
+ window_start: '', window_end: '', invocations: [],
63
+ violations: [], behavioral_hash: sha256Str('no-baseline'),
64
+ drift_detected: false,
65
+ };
66
+ }
67
+
68
+ const now = Date.now();
69
+ const windowStart = now - this.baseline.window_ms;
70
+ const windowInvocations = this.invocations.filter(
71
+ i => Date.parse(i.timestamp) >= windowStart
72
+ );
73
+
74
+ const violations: BehavioralViolation[] = [];
75
+
76
+ // Check unauthorized tools
77
+ for (const inv of windowInvocations) {
78
+ if (!this.baseline.permitted_tools.includes(inv.tool_name)) {
79
+ violations.push({ type: 'UNAUTHORIZED_TOOL', tool: inv.tool_name });
80
+ }
81
+ }
82
+
83
+ // Check rate limits
84
+ const counts: Record<string, number> = {};
85
+ for (const inv of windowInvocations) {
86
+ counts[inv.tool_name] = (counts[inv.tool_name] ?? 0) + 1;
87
+ }
88
+ for (const [tool, count] of Object.entries(counts)) {
89
+ const limit = this.baseline.rate_limits[tool];
90
+ if (limit !== undefined && count > limit) {
91
+ violations.push({ type: 'RATE_EXCEEDED', tool, count, limit });
92
+ }
93
+ }
94
+
95
+ // Check forbidden sequences
96
+ const toolSequence = windowInvocations.map(i => i.tool_name);
97
+ for (const forbidden of this.baseline.forbidden_sequences) {
98
+ if (containsSubsequence(toolSequence, forbidden)) {
99
+ violations.push({ type: 'FORBIDDEN_SEQUENCE', sequence: forbidden });
100
+ }
101
+ }
102
+
103
+ // Compute behavioral hash (pattern fingerprint)
104
+ const pattern = windowInvocations.map(i => i.tool_name).join('|');
105
+ const behavioral_hash = sha256Str(pattern);
106
+
107
+ return {
108
+ window_start: new Date(windowStart).toISOString(),
109
+ window_end: new Date(now).toISOString(),
110
+ invocations: windowInvocations,
111
+ violations,
112
+ behavioral_hash,
113
+ drift_detected: violations.length > 0,
114
+ };
115
+ }
116
+
117
+ reset(): void {
118
+ this.invocations = [];
119
+ }
120
+ }
121
+
122
+ function containsSubsequence(haystack: string[], needle: string[]): boolean {
123
+ if (needle.length === 0) return true;
124
+ let ni = 0;
125
+ for (const h of haystack) {
126
+ if (h === needle[ni]) {
127
+ ni++;
128
+ if (ni === needle.length) return true;
129
+ }
130
+ }
131
+ return false;
132
+ }
@@ -0,0 +1,45 @@
1
+ import { signStr, sigToB64, b64ToSig, hexToPk, verifyStr, pkToHex } from '../crypto/sign.js';
2
+ import { verifyProof } from '../crypto/merkle.js';
3
+ import { canonicalize } from '../utils/canonical.js';
4
+ import type { KeyPair, MerkleInclusionProof } from '../crypto/types.js';
5
+ import type { EvidenceBundle, PolicyArtifact, SignedReceipt, CheckpointReference, VerificationTier } from './types.js';
6
+
7
+ /**
8
+ * Generate an evidence bundle. Original signature preserved for backward compatibility.
9
+ * Tiered bundle generation (CAISI §3b):
10
+ * BRONZE - artifact + receipts only (proofs omitted)
11
+ * SILVER - artifact + receipts + Merkle proofs
12
+ * GOLD - artifact + receipts + Merkle proofs + anchor checkpoint reference
13
+ */
14
+ export function generateBundle(artifact: PolicyArtifact, receipts: SignedReceipt[], proofs: MerkleInclusionProof[], checkpoint: CheckpointReference, kp: KeyPair, tier?: VerificationTier): EvidenceBundle {
15
+ const effectiveTier = tier ?? 'GOLD';
16
+ const bundleProofs = effectiveTier === 'BRONZE' ? [] : proofs;
17
+ const bundleCheckpoint: CheckpointReference = effectiveTier === 'GOLD' ? checkpoint : {
18
+ ...checkpoint,
19
+ transaction_id: effectiveTier === 'BRONZE' ? '' : checkpoint.transaction_id,
20
+ anchor_network: effectiveTier === 'BRONZE' ? '' : checkpoint.anchor_network,
21
+ };
22
+ const unsigned = { artifact, receipts, merkle_proofs: bundleProofs, checkpoint_reference: bundleCheckpoint, public_key: pkToHex(kp.publicKey), verification_tier: effectiveTier };
23
+ return { ...unsigned, bundle_signature: sigToB64(signStr(canonicalize(unsigned), kp.secretKey)) };
24
+ }
25
+
26
+ export interface VerificationResult {
27
+ step1_artifact_sig: boolean; step2_receipt_sigs: boolean;
28
+ step3_merkle_proofs: boolean; step4_anchor: 'SKIPPED_OFFLINE' | boolean;
29
+ overall: boolean; errors: string[];
30
+ }
31
+
32
+ export function verifyBundleOffline(bundle: EvidenceBundle, pinnedPkHex: string): VerificationResult {
33
+ const errors: string[] = [];
34
+ const { signature: aSig, ...aU } = bundle.artifact;
35
+ const s1 = verifyStr(b64ToSig(aSig), canonicalize(aU), hexToPk(pinnedPkHex));
36
+ if (!s1) errors.push('Artifact signature failed');
37
+ let s2 = true;
38
+ for (const r of bundle.receipts) {
39
+ const { portal_signature, ...rU } = r;
40
+ if (!verifyStr(b64ToSig(portal_signature), canonicalize(rU), hexToPk(bundle.public_key))) { s2 = false; errors.push(`Receipt ${r.receipt_id} sig failed`); }
41
+ }
42
+ let s3 = true;
43
+ for (const p of bundle.merkle_proofs) { if (!verifyProof(p)) { s3 = false; errors.push(`Merkle proof failed leaf ${p.leafIndex}`); } }
44
+ return { step1_artifact_sig: s1, step2_receipt_sigs: s2, step3_merkle_proofs: s3, step4_anchor: 'SKIPPED_OFFLINE', overall: s1 && s2 && s3, errors };
45
+ }
@@ -0,0 +1,72 @@
1
+ import { sha256Str } from '../crypto/hash.js';
2
+ import { signStr, sigToB64, pkToHex } from '../crypto/sign.js';
3
+ import { canonicalize } from '../utils/canonical.js';
4
+ import { utcNow } from '../utils/timestamp.js';
5
+ import { uuid } from '../utils/uuid.js';
6
+ import { SCHEMA_VERSION, PROTOCOL_VERSION, TAXONOMY_VERSION } from '../utils/constants.js';
7
+ import type { KeyPair, HashHex } from '../crypto/types.js';
8
+ import type { ContinuityEvent, GenesisPayload, StructuralMetadata, EventType } from './types.js';
9
+
10
+ /** Leaf hash from structural metadata ONLY. Payload EXCLUDED. Uses "||" delimiter. */
11
+ export function computeLeafHash(m: StructuralMetadata): HashHex {
12
+ return sha256Str([
13
+ m.schema_version, m.protocol_version, m.event_type, m.event_id,
14
+ String(m.sequence_number), m.timestamp, m.previous_leaf_hash ?? 'NULL'
15
+ ].join('||'));
16
+ }
17
+
18
+ export function computePayloadHash(payload: unknown): HashHex {
19
+ return sha256Str(canonicalize(payload));
20
+ }
21
+
22
+ function buildEvent(type: EventType, payload: unknown, seq: number, prevLeaf: HashHex | null, kp: KeyPair): ContinuityEvent {
23
+ const id = uuid(), ts = utcNow();
24
+ const meta: StructuralMetadata = {
25
+ schema_version: SCHEMA_VERSION, protocol_version: PROTOCOL_VERSION,
26
+ event_type: type, event_id: id, sequence_number: seq,
27
+ timestamp: ts, previous_leaf_hash: prevLeaf,
28
+ };
29
+ const leafHash = computeLeafHash(meta);
30
+ const payloadHash = computePayloadHash(payload);
31
+ const sig = signStr(canonicalize({ ...meta, leaf_hash: leafHash, payload, payload_hash: payloadHash }), kp.secretKey);
32
+ return {
33
+ schema_version: SCHEMA_VERSION, protocol_version: PROTOCOL_VERSION,
34
+ event_type: type, event_id: id, sequence_number: seq,
35
+ timestamp: ts, previous_leaf_hash: prevLeaf,
36
+ leaf_hash: leafHash, payload, payload_hash: payloadHash,
37
+ event_signature: sigToB64(sig),
38
+ };
39
+ }
40
+
41
+ export function createGenesisEvent(kp: KeyPair, specHash: HashHex): ContinuityEvent {
42
+ const payload: GenesisPayload = {
43
+ protocol_version: PROTOCOL_VERSION, taxonomy_version: TAXONOMY_VERSION,
44
+ root_fingerprint: pkToHex(kp.publicKey), specification_hash: specHash, marker: 'GENESIS',
45
+ };
46
+ return buildEvent('GENESIS', payload, 0, null, kp);
47
+ }
48
+
49
+ export function appendEvent(type: EventType, payload: unknown, prev: ContinuityEvent, kp: KeyPair): ContinuityEvent {
50
+ return buildEvent(type, payload, prev.sequence_number + 1, prev.leaf_hash, kp);
51
+ }
52
+
53
+ export function verifyChainIntegrity(events: ContinuityEvent[]): {
54
+ valid: boolean; brokenAt: number | null; error: string | null;
55
+ } {
56
+ for (let i = 0; i < events.length; i++) {
57
+ const e = events[i];
58
+ const meta: StructuralMetadata = {
59
+ schema_version: e.schema_version, protocol_version: e.protocol_version,
60
+ event_type: e.event_type, event_id: e.event_id,
61
+ sequence_number: e.sequence_number, timestamp: e.timestamp,
62
+ previous_leaf_hash: e.previous_leaf_hash,
63
+ };
64
+ if (e.leaf_hash !== computeLeafHash(meta))
65
+ return { valid: false, brokenAt: e.sequence_number, error: `Leaf hash mismatch at seq ${e.sequence_number}` };
66
+ if (i > 0 && e.previous_leaf_hash !== events[i - 1].leaf_hash)
67
+ return { valid: false, brokenAt: e.sequence_number, error: `Chain linkage broken at seq ${e.sequence_number}` };
68
+ if (e.payload_hash !== computePayloadHash(e.payload))
69
+ return { valid: false, brokenAt: e.sequence_number, error: `Payload hash mismatch at seq ${e.sequence_number}` };
70
+ }
71
+ return { valid: true, brokenAt: null, error: null };
72
+ }
@@ -0,0 +1,22 @@
1
+ import { buildMerkleTree, inclusionProof } from '../crypto/merkle.js';
2
+ import { utcNow } from '../utils/timestamp.js';
3
+ import { uuid } from '../utils/uuid.js';
4
+ import type { ContinuityEvent, CheckpointReference, AnchorBatchPayload } from './types.js';
5
+ import type { MerkleInclusionProof } from '../crypto/types.js';
6
+
7
+ export function createCheckpoint(events: ContinuityEvent[], anchorNetwork = 'local'): { checkpoint: CheckpointReference; payload: AnchorBatchPayload } {
8
+ if (!events.length) throw new Error('No events to checkpoint');
9
+ const { root } = buildMerkleTree(events.map(e => e.leaf_hash));
10
+ const checkpoint: CheckpointReference = {
11
+ merkle_root: root, batch_start_sequence: events[0].sequence_number,
12
+ batch_end_sequence: events[events.length - 1].sequence_number,
13
+ anchor_network: anchorNetwork, transaction_id: `${anchorNetwork}:${uuid()}`, timestamp: utcNow(),
14
+ };
15
+ return { checkpoint, payload: { checkpoint_reference: checkpoint, leaf_count: events.length } };
16
+ }
17
+
18
+ export function eventInclusionProof(events: ContinuityEvent[], targetSeq: number): MerkleInclusionProof {
19
+ const idx = events.findIndex(e => e.sequence_number === targetSeq);
20
+ if (idx === -1) throw new Error(`Sequence ${targetSeq} not in batch`);
21
+ return inclusionProof(events.map(e => e.leaf_hash), idx);
22
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Constrained Sub-Agent Delegation.
3
+ * NCCoE filing: "Scope can only diminish through delegation, never expand."
4
+ *
5
+ * Primary agent's portal issues a derived artifact to secondary agent:
6
+ * - TTL <= parent's remaining TTL
7
+ * - Scope can only diminish, never expand
8
+ * - Secondary's genesis links to parent's chain
9
+ */
10
+ import { generateArtifact, hashArtifact } from './artifact.js';
11
+ import { isExpired } from '../utils/timestamp.js';
12
+ import type { PolicyArtifact, EnforcementAction, MeasurementType } from './types.js';
13
+ import type { KeyPair } from '../crypto/types.js';
14
+
15
+ export interface DelegationRequest {
16
+ /** Subset of parent's enforcement triggers */
17
+ enforcement_triggers: EnforcementAction[];
18
+ /** Subset of parent's measurement types */
19
+ measurement_types: MeasurementType[];
20
+ /** Requested TTL in seconds (will be clamped to parent remaining) */
21
+ requested_ttl_seconds: number;
22
+ /** Description of the delegation purpose */
23
+ delegation_purpose: string;
24
+ }
25
+
26
+ export interface DelegationResult {
27
+ success: boolean;
28
+ child_artifact?: PolicyArtifact;
29
+ child_artifact_hash?: string;
30
+ parent_artifact_hash: string;
31
+ effective_ttl_seconds?: number;
32
+ scope_reduction?: {
33
+ triggers_removed: string[];
34
+ measurement_types_removed: string[];
35
+ };
36
+ error?: string;
37
+ }
38
+
39
+ /**
40
+ * Derive a constrained artifact from a parent artifact.
41
+ * Key rule: scope can only diminish, never expand.
42
+ */
43
+ export function deriveArtifact(
44
+ parentArtifact: PolicyArtifact,
45
+ request: DelegationRequest,
46
+ issuerKP: KeyPair
47
+ ): DelegationResult {
48
+ const parentHash = hashArtifact(parentArtifact);
49
+
50
+ // Validate parent is not expired
51
+ if (isExpired(parentArtifact.issued_timestamp, parentArtifact.enforcement_parameters.ttl_seconds)) {
52
+ return { success: false, parent_artifact_hash: parentHash, error: 'Parent artifact TTL has expired' };
53
+ }
54
+
55
+ // Calculate parent remaining TTL
56
+ const parentIssuedMs = Date.parse(parentArtifact.issued_timestamp);
57
+ const parentExpiresMs = parentIssuedMs + (parentArtifact.enforcement_parameters.ttl_seconds * 1000);
58
+ const remainingMs = parentExpiresMs - Date.now();
59
+ const remainingSeconds = Math.max(0, Math.floor(remainingMs / 1000));
60
+
61
+ // Clamp child TTL to parent remaining
62
+ const effectiveTTL = Math.min(request.requested_ttl_seconds, remainingSeconds);
63
+ if (effectiveTTL <= 0) {
64
+ return { success: false, parent_artifact_hash: parentHash, error: 'No remaining TTL to delegate' };
65
+ }
66
+
67
+ // Validate triggers are subset of parent
68
+ const parentTriggers = new Set(parentArtifact.enforcement_parameters.enforcement_triggers);
69
+ const invalidTriggers = request.enforcement_triggers.filter(t => !parentTriggers.has(t));
70
+ if (invalidTriggers.length > 0) {
71
+ return { success: false, parent_artifact_hash: parentHash, error: `Cannot expand scope: triggers [${invalidTriggers.join(', ')}] not in parent` };
72
+ }
73
+
74
+ // Validate measurement types are subset of parent
75
+ const parentTypes = new Set<string>(parentArtifact.enforcement_parameters.measurement_types);
76
+ const invalidTypes = request.measurement_types.filter(t => !parentTypes.has(t));
77
+ if (invalidTypes.length > 0) {
78
+ return { success: false, parent_artifact_hash: parentHash, error: `Cannot expand scope: measurement types [${invalidTypes.join(', ')}] not in parent` };
79
+ }
80
+
81
+ // Build constrained child artifact
82
+ const childArtifact = generateArtifact({
83
+ subject_identifier: parentArtifact.subject_identifier,
84
+ policy_reference: parentArtifact.policy_reference,
85
+ policy_version: parentArtifact.policy_version,
86
+ sealed_hash: parentArtifact.sealed_hash,
87
+ seal_salt: parentArtifact.seal_salt,
88
+ enforcement_parameters: {
89
+ measurement_cadence_ms: parentArtifact.enforcement_parameters.measurement_cadence_ms,
90
+ ttl_seconds: effectiveTTL,
91
+ enforcement_triggers: request.enforcement_triggers,
92
+ re_attestation_required: parentArtifact.enforcement_parameters.re_attestation_required,
93
+ measurement_types: request.measurement_types,
94
+ },
95
+ disclosure_policy: parentArtifact.disclosure_policy, // cannot expand
96
+ evidence_commitments: parentArtifact.evidence_commitments,
97
+ issuer_keypair: issuerKP,
98
+ });
99
+
100
+ // Track scope reduction
101
+ const triggersRemoved = [...parentTriggers].filter(t => !request.enforcement_triggers.includes(t as EnforcementAction));
102
+ const typesRemoved = [...parentTypes].filter(t => !request.measurement_types.includes(t as MeasurementType));
103
+
104
+ return {
105
+ success: true,
106
+ child_artifact: childArtifact,
107
+ child_artifact_hash: hashArtifact(childArtifact),
108
+ parent_artifact_hash: parentHash,
109
+ effective_ttl_seconds: effectiveTTL,
110
+ scope_reduction: {
111
+ triggers_removed: triggersRemoved,
112
+ measurement_types_removed: typesRemoved,
113
+ },
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Validate that a child artifact is a valid delegation of a parent.
119
+ */
120
+ export function validateDelegation(parent: PolicyArtifact, child: PolicyArtifact): { valid: boolean; errors: string[] } {
121
+ const errors: string[] = [];
122
+
123
+ // TTL must be <= parent TTL
124
+ if (child.enforcement_parameters.ttl_seconds > parent.enforcement_parameters.ttl_seconds) {
125
+ errors.push(`Child TTL (${child.enforcement_parameters.ttl_seconds}s) exceeds parent (${parent.enforcement_parameters.ttl_seconds}s)`);
126
+ }
127
+
128
+ // Triggers must be subset
129
+ const pTriggers = new Set<string>(parent.enforcement_parameters.enforcement_triggers);
130
+ for (const t of child.enforcement_parameters.enforcement_triggers) {
131
+ if (!pTriggers.has(t)) errors.push(`Child trigger '${t}' not in parent scope`);
132
+ }
133
+
134
+ // Measurement types must be subset
135
+ const pTypes = new Set<string>(parent.enforcement_parameters.measurement_types);
136
+ for (const t of child.enforcement_parameters.measurement_types) {
137
+ if (!pTypes.has(t)) errors.push(`Child measurement type '${t}' not in parent scope`);
138
+ }
139
+
140
+ // Subject must match
141
+ if (child.subject_identifier.bytes_hash !== parent.subject_identifier.bytes_hash) {
142
+ errors.push('Child subject bytes_hash does not match parent');
143
+ }
144
+
145
+ return { valid: errors.length === 0, errors };
146
+ }
@@ -0,0 +1,32 @@
1
+ import { signStr, sigToB64 } from '../crypto/sign.js';
2
+ import { canonicalize } from '../utils/canonical.js';
3
+ import { utcNow } from '../utils/timestamp.js';
4
+ import { uuid } from '../utils/uuid.js';
5
+ import type { KeyPair } from '../crypto/types.js';
6
+ import type { DisclosureRequest, DisclosurePolicy, SubstitutionReceipt, DisclosureMode } from './types.js';
7
+
8
+ export interface DisclosureResult {
9
+ permitted: boolean; disclosed_claim_id: string | null; disclosed_value: unknown;
10
+ mode: DisclosureMode; was_substituted: boolean; substitution_receipt: SubstitutionReceipt | null;
11
+ }
12
+
13
+ export function processDisclosure(req: DisclosureRequest, policy: DisclosurePolicy, values: Record<string, unknown>, policyVersion: number, chainSeq: number, kp: KeyPair): DisclosureResult {
14
+ const claim = policy.claims_taxonomy.find(c => c.claim_id === req.requested_claim_id);
15
+ if (!claim) return { permitted: false, disclosed_claim_id: null, disclosed_value: null, mode: req.mode, was_substituted: false, substitution_receipt: null };
16
+ if (claim.permitted_modes.includes(req.mode))
17
+ return { permitted: true, disclosed_claim_id: claim.claim_id, disclosed_value: fv(values[claim.claim_id], req.mode), mode: req.mode, was_substituted: false, substitution_receipt: null };
18
+ for (const subId of claim.substitutes) {
19
+ const sub = policy.claims_taxonomy.find(c => c.claim_id === subId);
20
+ if (sub?.permitted_modes.includes(req.mode) && !sub.inference_risks.includes(req.requested_claim_id))
21
+ return { permitted: true, disclosed_claim_id: subId, disclosed_value: fv(values[subId], req.mode), mode: req.mode, was_substituted: true,
22
+ substitution_receipt: sr(req.requested_claim_id, subId, policyVersion, 'SENSITIVITY_DENIED', chainSeq, kp) };
23
+ }
24
+ return { permitted: false, disclosed_claim_id: null, disclosed_value: null, mode: req.mode, was_substituted: false,
25
+ substitution_receipt: sr(req.requested_claim_id, null, policyVersion, 'NO_PERMITTED_SUBSTITUTE', chainSeq, kp) };
26
+ }
27
+
28
+ function fv(v: unknown, m: DisclosureMode): unknown { return m === 'PROOF_ONLY' ? v != null : v; }
29
+ function sr(orig: string, sub: string | null, pv: number, reason: string, seq: number, kp: KeyPair): SubstitutionReceipt {
30
+ const u = { receipt_id: uuid(), original_claim_id: orig, substitute_claim_id: sub, policy_version: pv, reason_code: reason, timestamp: utcNow(), chain_sequence_ref: seq };
31
+ return { ...u, signature: sigToB64(signStr(canonicalize(u), kp.secretKey)) };
32
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Identity and Key Lifecycle.
3
+ *
4
+ * Non-biometric cryptographic identity: agent identity derived from
5
+ * cryptographic key pair bound to append-only attestation history in
6
+ * continuity chain. Authority from valid signature history, not biometric traits.
7
+ *
8
+ * Functions:
9
+ * - keyFingerprint: re-exported from crypto/sign.ts for convenience
10
+ * - isKeyValid: check key TTL expiry
11
+ * - rotateKeyPair: generate new key pair for rotation
12
+ * - recordKeyRotation: append KEY_ROTATION event to chain
13
+ */
14
+ import { generateKeyPair, keyFingerprint, pkToHex } from '../crypto/sign.js';
15
+ import { isExpired } from '../utils/timestamp.js';
16
+ import { appendEvent } from './chain.js';
17
+ import type { KeyPair } from '../crypto/types.js';
18
+ import type { ContinuityEvent, KeyRotationRecord } from './types.js';
19
+
20
+ // Re-export keyFingerprint for identity module consumers (NCCoE §2)
21
+ export { keyFingerprint } from '../crypto/sign.js';
22
+
23
+ /**
24
+ * Check whether a key is still valid given its issuance time and TTL.
25
+ * NCCoE §2: non-biometric identity validity check.
26
+ */
27
+ export function isKeyValid(issuedAt: string, ttlSeconds: number): boolean {
28
+ return !isExpired(issuedAt, ttlSeconds);
29
+ }
30
+
31
+ /**
32
+ * Generate a new key pair for rotation, returning both old and new for
33
+ * a transition period defined by policy. NCCoE §3: key rotation.
34
+ */
35
+ export function rotateKeyPair(currentKeyPair: KeyPair): { oldKeyPair: KeyPair; newKeyPair: KeyPair } {
36
+ const newKeyPair = generateKeyPair();
37
+ return { oldKeyPair: currentKeyPair, newKeyPair };
38
+ }
39
+
40
+ /**
41
+ * Record a key rotation event on the continuity chain.
42
+ * NCCoE §3: "Key rotation is handled by including both old and new public keys
43
+ * during a transition period defined by policy."
44
+ */
45
+ export function recordKeyRotation(
46
+ prevEvent: ContinuityEvent,
47
+ keypairType: string,
48
+ oldPublicKeyHex: string,
49
+ newPublicKeyHex: string,
50
+ reason: string,
51
+ signingKeyPair: KeyPair,
52
+ ): ContinuityEvent {
53
+ const payload: KeyRotationRecord = {
54
+ keypair_type: keypairType,
55
+ old_public_key: oldPublicKeyHex,
56
+ new_public_key: newPublicKeyHex,
57
+ reason,
58
+ rotation_timestamp: new Date().toISOString(),
59
+ chain_sequence: prevEvent.sequence_number + 1,
60
+ };
61
+ return appendEvent('KEY_ROTATION', payload, prevEvent, signingKeyPair);
62
+ }
@@ -0,0 +1,14 @@
1
+ export * from './types.js';
2
+ export * from './subject.js';
3
+ export * from './attestation.js';
4
+ export * from './artifact.js';
5
+ export * from './receipt.js';
6
+ export * from './chain.js';
7
+ export * from './portal.js';
8
+ export * from './quarantine.js';
9
+ export * from './checkpoint.js';
10
+ export * from './bundle.js';
11
+ export * from './disclosure.js';
12
+ export * from './behavioral.js';
13
+ export * from './delegation.js';
14
+ export * from './identity.js';