@a5c-ai/tasks-mux 5.0.1-staging.04ca6ab00d21

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 (175) hide show
  1. package/README.md +103 -0
  2. package/dist/auth/forge-interface.d.ts +67 -0
  3. package/dist/auth/forge-interface.d.ts.map +1 -0
  4. package/dist/auth/forge-interface.js +69 -0
  5. package/dist/auth/github-app.d.ts +64 -0
  6. package/dist/auth/github-app.d.ts.map +1 -0
  7. package/dist/auth/github-app.js +141 -0
  8. package/dist/auth/github-oauth.d.ts +27 -0
  9. package/dist/auth/github-oauth.d.ts.map +1 -0
  10. package/dist/auth/github-oauth.js +89 -0
  11. package/dist/auth/index.d.ts +8 -0
  12. package/dist/auth/index.d.ts.map +1 -0
  13. package/dist/auth/index.js +14 -0
  14. package/dist/auth/jwt.d.ts +24 -0
  15. package/dist/auth/jwt.d.ts.map +1 -0
  16. package/dist/auth/jwt.js +43 -0
  17. package/dist/auth/middleware.d.ts +22 -0
  18. package/dist/auth/middleware.d.ts.map +1 -0
  19. package/dist/auth/middleware.js +36 -0
  20. package/dist/auth/ssh-keys.d.ts +21 -0
  21. package/dist/auth/ssh-keys.d.ts.map +1 -0
  22. package/dist/auth/ssh-keys.js +59 -0
  23. package/dist/auth/types.d.ts +165 -0
  24. package/dist/auth/types.d.ts.map +1 -0
  25. package/dist/auth/types.js +53 -0
  26. package/dist/backend.d.ts +117 -0
  27. package/dist/backend.d.ts.map +1 -0
  28. package/dist/backend.js +15 -0
  29. package/dist/backends/git-native.d.ts +51 -0
  30. package/dist/backends/git-native.d.ts.map +1 -0
  31. package/dist/backends/git-native.js +324 -0
  32. package/dist/backends/github-issues.d.ts +77 -0
  33. package/dist/backends/github-issues.d.ts.map +1 -0
  34. package/dist/backends/github-issues.js +796 -0
  35. package/dist/backends/index.d.ts +48 -0
  36. package/dist/backends/index.d.ts.map +1 -0
  37. package/dist/backends/index.js +139 -0
  38. package/dist/backends/server.d.ts +41 -0
  39. package/dist/backends/server.d.ts.map +1 -0
  40. package/dist/backends/server.js +298 -0
  41. package/dist/cli/auth-store.d.ts +49 -0
  42. package/dist/cli/auth-store.d.ts.map +1 -0
  43. package/dist/cli/auth-store.js +150 -0
  44. package/dist/cli/client-config.d.ts +10 -0
  45. package/dist/cli/client-config.d.ts.map +1 -0
  46. package/dist/cli/client-config.js +87 -0
  47. package/dist/cli/commands/ask.d.ts +3 -0
  48. package/dist/cli/commands/ask.d.ts.map +1 -0
  49. package/dist/cli/commands/ask.js +171 -0
  50. package/dist/cli/commands/auth.d.ts +3 -0
  51. package/dist/cli/commands/auth.d.ts.map +1 -0
  52. package/dist/cli/commands/auth.js +510 -0
  53. package/dist/cli/commands/breakpoints.d.ts +3 -0
  54. package/dist/cli/commands/breakpoints.d.ts.map +1 -0
  55. package/dist/cli/commands/breakpoints.js +152 -0
  56. package/dist/cli/commands/responder-loop.d.ts +3 -0
  57. package/dist/cli/commands/responder-loop.d.ts.map +1 -0
  58. package/dist/cli/commands/responder-loop.js +78 -0
  59. package/dist/cli/commands/responders.d.ts +3 -0
  60. package/dist/cli/commands/responders.d.ts.map +1 -0
  61. package/dist/cli/commands/responders.js +74 -0
  62. package/dist/cli/commands/server.d.ts +3 -0
  63. package/dist/cli/commands/server.d.ts.map +1 -0
  64. package/dist/cli/commands/server.js +34 -0
  65. package/dist/cli/index.d.ts +4 -0
  66. package/dist/cli/index.d.ts.map +1 -0
  67. package/dist/cli/index.js +9 -0
  68. package/dist/cli/output.d.ts +26 -0
  69. package/dist/cli/output.d.ts.map +1 -0
  70. package/dist/cli/output.js +143 -0
  71. package/dist/cli/program.d.ts +6 -0
  72. package/dist/cli/program.d.ts.map +1 -0
  73. package/dist/cli/program.js +32 -0
  74. package/dist/client/answer-poller.d.ts +52 -0
  75. package/dist/client/answer-poller.d.ts.map +1 -0
  76. package/dist/client/answer-poller.js +199 -0
  77. package/dist/client/auth-client.d.ts +200 -0
  78. package/dist/client/auth-client.d.ts.map +1 -0
  79. package/dist/client/auth-client.js +309 -0
  80. package/dist/client/breakpoint-router.d.ts +45 -0
  81. package/dist/client/breakpoint-router.d.ts.map +1 -0
  82. package/dist/client/breakpoint-router.js +45 -0
  83. package/dist/client/index.d.ts +17 -0
  84. package/dist/client/index.d.ts.map +1 -0
  85. package/dist/client/index.js +16 -0
  86. package/dist/client/profile-validator.d.ts +34 -0
  87. package/dist/client/profile-validator.d.ts.map +1 -0
  88. package/dist/client/profile-validator.js +89 -0
  89. package/dist/client/responder-client.d.ts +39 -0
  90. package/dist/client/responder-client.d.ts.map +1 -0
  91. package/dist/client/responder-client.js +72 -0
  92. package/dist/client/responder-matcher.d.ts +49 -0
  93. package/dist/client/responder-matcher.d.ts.map +1 -0
  94. package/dist/client/responder-matcher.js +226 -0
  95. package/dist/client/server-client.d.ts +124 -0
  96. package/dist/client/server-client.d.ts.map +1 -0
  97. package/dist/client/server-client.js +266 -0
  98. package/dist/client/timeout-manager.d.ts +47 -0
  99. package/dist/client/timeout-manager.d.ts.map +1 -0
  100. package/dist/client/timeout-manager.js +77 -0
  101. package/dist/config.d.ts +20 -0
  102. package/dist/config.d.ts.map +1 -0
  103. package/dist/config.js +93 -0
  104. package/dist/harness/index.d.ts +4 -0
  105. package/dist/harness/index.d.ts.map +1 -0
  106. package/dist/harness/index.js +2 -0
  107. package/dist/harness/interaction-provider.d.ts +71 -0
  108. package/dist/harness/interaction-provider.d.ts.map +1 -0
  109. package/dist/harness/interaction-provider.js +124 -0
  110. package/dist/harness/routing-rules.d.ts +7 -0
  111. package/dist/harness/routing-rules.d.ts.map +1 -0
  112. package/dist/harness/routing-rules.js +37 -0
  113. package/dist/index.d.ts +19 -0
  114. package/dist/index.d.ts.map +1 -0
  115. package/dist/index.js +26 -0
  116. package/dist/mcp/backend-resolver.d.ts +43 -0
  117. package/dist/mcp/backend-resolver.d.ts.map +1 -0
  118. package/dist/mcp/backend-resolver.js +111 -0
  119. package/dist/mcp/http-transport.d.ts +37 -0
  120. package/dist/mcp/http-transport.d.ts.map +1 -0
  121. package/dist/mcp/http-transport.js +103 -0
  122. package/dist/mcp/index.d.ts +14 -0
  123. package/dist/mcp/index.d.ts.map +1 -0
  124. package/dist/mcp/index.js +11 -0
  125. package/dist/mcp/server.d.ts +20 -0
  126. package/dist/mcp/server.d.ts.map +1 -0
  127. package/dist/mcp/server.js +121 -0
  128. package/dist/mcp/tools/answer-breakpoint.d.ts +32 -0
  129. package/dist/mcp/tools/answer-breakpoint.d.ts.map +1 -0
  130. package/dist/mcp/tools/answer-breakpoint.js +45 -0
  131. package/dist/mcp/tools/ask-breakpoint.d.ts +58 -0
  132. package/dist/mcp/tools/ask-breakpoint.d.ts.map +1 -0
  133. package/dist/mcp/tools/ask-breakpoint.js +78 -0
  134. package/dist/mcp/tools/check-status.d.ts +16 -0
  135. package/dist/mcp/tools/check-status.d.ts.map +1 -0
  136. package/dist/mcp/tools/check-status.js +18 -0
  137. package/dist/mcp/tools/claim-breakpoint.d.ts +18 -0
  138. package/dist/mcp/tools/claim-breakpoint.d.ts.map +1 -0
  139. package/dist/mcp/tools/claim-breakpoint.js +28 -0
  140. package/dist/mcp/tools/list-breakpoints.d.ts +16 -0
  141. package/dist/mcp/tools/list-breakpoints.d.ts.map +1 -0
  142. package/dist/mcp/tools/list-breakpoints.js +14 -0
  143. package/dist/mcp/tools/list-responders.d.ts +18 -0
  144. package/dist/mcp/tools/list-responders.d.ts.map +1 -0
  145. package/dist/mcp/tools/list-responders.js +37 -0
  146. package/dist/mcp/tools/poll-breakpoints.d.ts +18 -0
  147. package/dist/mcp/tools/poll-breakpoints.d.ts.map +1 -0
  148. package/dist/mcp/tools/poll-breakpoints.js +36 -0
  149. package/dist/mcp/tools/verify-answer.d.ts +16 -0
  150. package/dist/mcp/tools/verify-answer.d.ts.map +1 -0
  151. package/dist/mcp/tools/verify-answer.js +38 -0
  152. package/dist/proven/index.d.ts +5 -0
  153. package/dist/proven/index.d.ts.map +1 -0
  154. package/dist/proven/index.js +3 -0
  155. package/dist/proven/keys.d.ts +33 -0
  156. package/dist/proven/keys.d.ts.map +1 -0
  157. package/dist/proven/keys.js +117 -0
  158. package/dist/proven/sign.d.ts +16 -0
  159. package/dist/proven/sign.d.ts.map +1 -0
  160. package/dist/proven/sign.js +60 -0
  161. package/dist/proven/types.d.ts +26 -0
  162. package/dist/proven/types.d.ts.map +1 -0
  163. package/dist/proven/types.js +5 -0
  164. package/dist/proven/verify.d.ts +6 -0
  165. package/dist/proven/verify.d.ts.map +1 -0
  166. package/dist/proven/verify.js +58 -0
  167. package/dist/types.d.ts +4034 -0
  168. package/dist/types.d.ts.map +1 -0
  169. package/dist/types.js +244 -0
  170. package/package.json +95 -0
  171. package/responder/README.md +42 -0
  172. package/responder/backend-responder.json +9 -0
  173. package/responder/devops-responder.json +9 -0
  174. package/responder/frontend-responder.json +9 -0
  175. package/responder/schema.json +52 -0
@@ -0,0 +1,38 @@
1
+ import { z } from "zod";
2
+ import { selectBreakpointAnswer, supportsProvenAnswers, unsupportedBackendFeatureMessage, } from "../../backend.js";
3
+ import { isProvenBreakpointAnswer } from "../../types.js";
4
+ import { verifyAnswer } from "../../proven/verify.js";
5
+ // ── Tool Description ────────────────────────────────────────────────────
6
+ export const verifyBreakpointAnswerDescription = "Verify the cryptographic signature of a breakpoint answer against " +
7
+ "trusted public keys. Returns verification status and signer identity.";
8
+ // ── Tool Param Schema ───────────────────────────────────────────────────
9
+ export const verifyBreakpointAnswerParams = {
10
+ breakpointId: z.string().describe("The ID of the breakpoint whose answer to verify."),
11
+ backend: z.string().optional(),
12
+ breakpointsDir: z.string().optional(),
13
+ };
14
+ // ── Handler ─────────────────────────────────────────────────────────────
15
+ export async function handleVerifyBreakpointAnswer(params, backend) {
16
+ if (!params.breakpointId || params.breakpointId.length === 0) {
17
+ throw new Error("breakpointId is required and must be non-empty");
18
+ }
19
+ const breakpoint = await backend.getBreakpoint(params.breakpointId);
20
+ if (!breakpoint.answers || breakpoint.answers.length === 0) {
21
+ throw new Error("No answers found for this breakpoint");
22
+ }
23
+ const answer = selectBreakpointAnswer(breakpoint);
24
+ if (!answer) {
25
+ if (breakpoint.selectedAnswer) {
26
+ throw new Error(`Selected answer ${breakpoint.selectedAnswer} was not found on breakpoint ${breakpoint.id}`);
27
+ }
28
+ throw new Error("No answers found for this breakpoint");
29
+ }
30
+ if (!isProvenBreakpointAnswer(answer)) {
31
+ if (!supportsProvenAnswers(backend.name)) {
32
+ throw new Error(unsupportedBackendFeatureMessage(backend.name, "signed answers"));
33
+ }
34
+ throw new Error("Answer is not signed/proven -- no cryptographic signature found");
35
+ }
36
+ const result = await verifyAnswer(answer, params.breakpointsDir);
37
+ return result;
38
+ }
@@ -0,0 +1,5 @@
1
+ export { generateKeyPair, saveTrustedPublicKey, savePrivateKey, loadTrustedPublicKeys, loadPrivateKey, rotateKey, } from "./keys.js";
2
+ export type { KeyPairMetadata, PublicKeyRecord, PrivateKeyRecord, } from "./keys.js";
3
+ export { signAnswer, signAnswerWithKeyRecord } from "./sign.js";
4
+ export { verifyAnswer } from "./verify.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/proven/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,cAAc,EACd,SAAS,GACV,MAAM,WAAW,CAAC;AAEnB,YAAY,EACV,eAAe,EACf,eAAe,EACf,gBAAgB,GACjB,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,UAAU,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { generateKeyPair, saveTrustedPublicKey, savePrivateKey, loadTrustedPublicKeys, loadPrivateKey, rotateKey, } from "./keys.js";
2
+ export { signAnswer, signAnswerWithKeyRecord } from "./sign.js";
3
+ export { verifyAnswer } from "./verify.js";
@@ -0,0 +1,33 @@
1
+ import type { PublicKeyRecord, PrivateKeyRecord } from "./types.js";
2
+ /**
3
+ * Generate a new Ed25519 key pair for a responder.
4
+ */
5
+ export declare function generateKeyPair(responderId: string, responderName: string): {
6
+ publicKeyRecord: PublicKeyRecord;
7
+ privateKeyRecord: PrivateKeyRecord;
8
+ };
9
+ /**
10
+ * Save a public key to the trusted keys directory (git-tracked).
11
+ */
12
+ export declare function saveTrustedPublicKey(publicKeyRecord: PublicKeyRecord, baseDir?: string): Promise<string>;
13
+ /**
14
+ * Save a private key to the private keys directory (gitignored).
15
+ */
16
+ export declare function savePrivateKey(privateKeyRecord: PrivateKeyRecord, baseDir?: string): Promise<string>;
17
+ /**
18
+ * Load all trusted public keys from the trusted directory.
19
+ */
20
+ export declare function loadTrustedPublicKeys(baseDir?: string): Promise<PublicKeyRecord[]>;
21
+ /**
22
+ * Load a private key by fingerprint.
23
+ */
24
+ export declare function loadPrivateKey(fingerprint: string, baseDir?: string): Promise<PrivateKeyRecord | null>;
25
+ /**
26
+ * Rotate a key: generate new pair, save both, mark old public key as expired.
27
+ */
28
+ export declare function rotateKey(responderId: string, responderName: string, oldFingerprint: string, baseDir?: string): Promise<{
29
+ publicKeyRecord: PublicKeyRecord;
30
+ privateKeyRecord: PrivateKeyRecord;
31
+ }>;
32
+ export type { PublicKeyRecord, PrivateKeyRecord, KeyPairMetadata } from "./types.js";
33
+ //# sourceMappingURL=keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/proven/keys.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEpE;;GAEG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,GACpB;IAAE,eAAe,EAAE,eAAe,CAAC;IAAC,gBAAgB,EAAE,gBAAgB,CAAA;CAAE,CAyB1E;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,eAAe,EAAE,eAAe,EAChC,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CASjB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,gBAAgB,EAAE,gBAAgB,EAClC,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CASjB;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,eAAe,EAAE,CAAC,CAmB5B;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAYlC;AAED;;GAEG;AACH,wBAAsB,SAAS,CAC7B,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,cAAc,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,eAAe,EAAE,eAAe,CAAC;IAAC,gBAAgB,EAAE,gBAAgB,CAAA;CAAE,CAAC,CAqBnF;AAED,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,117 @@
1
+ import { generateKeyPairSync, createHash } from "node:crypto";
2
+ import { promises as fs } from "node:fs";
3
+ import * as path from "node:path";
4
+ /**
5
+ * Generate a new Ed25519 key pair for a responder.
6
+ */
7
+ export function generateKeyPair(responderId, responderName) {
8
+ const { publicKey, privateKey } = generateKeyPairSync("ed25519");
9
+ const pubDer = publicKey.export({ type: "spki", format: "der" });
10
+ const privPkcs8 = privateKey.export({ type: "pkcs8", format: "der" });
11
+ const fingerprint = createHash("sha256").update(pubDer).digest("hex");
12
+ const now = new Date().toISOString();
13
+ return {
14
+ publicKeyRecord: {
15
+ publicKey: pubDer.toString("base64"),
16
+ metadata: {
17
+ fingerprint,
18
+ responderId,
19
+ responderName,
20
+ createdAt: now,
21
+ },
22
+ },
23
+ privateKeyRecord: {
24
+ privateKey: privPkcs8.toString("base64"),
25
+ fingerprint,
26
+ responderId,
27
+ },
28
+ };
29
+ }
30
+ /**
31
+ * Save a public key to the trusted keys directory (git-tracked).
32
+ */
33
+ export async function saveTrustedPublicKey(publicKeyRecord, baseDir) {
34
+ const dir = baseDir
35
+ ? path.join(baseDir, ".keys", "trusted")
36
+ : path.resolve(process.cwd(), ".breakpoints", ".keys", "trusted");
37
+ await fs.mkdir(dir, { recursive: true });
38
+ const filePath = path.join(dir, `${publicKeyRecord.metadata.fingerprint}.pub.json`);
39
+ await fs.writeFile(filePath, JSON.stringify(publicKeyRecord, null, 2) + "\n", "utf-8");
40
+ return filePath;
41
+ }
42
+ /**
43
+ * Save a private key to the private keys directory (gitignored).
44
+ */
45
+ export async function savePrivateKey(privateKeyRecord, baseDir) {
46
+ const dir = baseDir
47
+ ? path.join(baseDir, ".keys", "private")
48
+ : path.resolve(process.cwd(), ".breakpoints", ".keys", "private");
49
+ await fs.mkdir(dir, { recursive: true });
50
+ const filePath = path.join(dir, `${privateKeyRecord.fingerprint}.key.json`);
51
+ await fs.writeFile(filePath, JSON.stringify(privateKeyRecord, null, 2) + "\n", "utf-8");
52
+ return filePath;
53
+ }
54
+ /**
55
+ * Load all trusted public keys from the trusted directory.
56
+ */
57
+ export async function loadTrustedPublicKeys(baseDir) {
58
+ const dir = baseDir
59
+ ? path.join(baseDir, ".keys", "trusted")
60
+ : path.resolve(process.cwd(), ".breakpoints", ".keys", "trusted");
61
+ let files;
62
+ try {
63
+ files = await fs.readdir(dir);
64
+ }
65
+ catch {
66
+ return [];
67
+ }
68
+ const keys = [];
69
+ for (const file of files) {
70
+ if (!file.endsWith(".pub.json"))
71
+ continue;
72
+ const raw = await fs.readFile(path.join(dir, file), "utf-8");
73
+ keys.push(JSON.parse(raw));
74
+ }
75
+ return keys;
76
+ }
77
+ /**
78
+ * Load a private key by fingerprint.
79
+ */
80
+ export async function loadPrivateKey(fingerprint, baseDir) {
81
+ const dir = baseDir
82
+ ? path.join(baseDir, ".keys", "private")
83
+ : path.resolve(process.cwd(), ".breakpoints", ".keys", "private");
84
+ const filePath = path.join(dir, `${fingerprint}.key.json`);
85
+ try {
86
+ const raw = await fs.readFile(filePath, "utf-8");
87
+ return JSON.parse(raw);
88
+ }
89
+ catch {
90
+ return null;
91
+ }
92
+ }
93
+ /**
94
+ * Rotate a key: generate new pair, save both, mark old public key as expired.
95
+ */
96
+ export async function rotateKey(responderId, responderName, oldFingerprint, baseDir) {
97
+ // Mark old key as expired
98
+ const trustedDir = baseDir
99
+ ? path.join(baseDir, ".keys", "trusted")
100
+ : path.resolve(process.cwd(), ".breakpoints", ".keys", "trusted");
101
+ const oldKeyPath = path.join(trustedDir, `${oldFingerprint}.pub.json`);
102
+ try {
103
+ const raw = await fs.readFile(oldKeyPath, "utf-8");
104
+ const oldKey = JSON.parse(raw);
105
+ oldKey.metadata.expiresAt = new Date().toISOString();
106
+ oldKey.metadata.note = `Rotated. Superseded by new key for ${responderId}.`;
107
+ await fs.writeFile(oldKeyPath, JSON.stringify(oldKey, null, 2) + "\n", "utf-8");
108
+ }
109
+ catch {
110
+ // Old key may not exist; that's fine
111
+ }
112
+ // Generate and save new pair
113
+ const newPair = generateKeyPair(responderId, responderName);
114
+ await saveTrustedPublicKey(newPair.publicKeyRecord, baseDir);
115
+ await savePrivateKey(newPair.privateKeyRecord, baseDir);
116
+ return newPair;
117
+ }
@@ -0,0 +1,16 @@
1
+ import type { BreakpointAnswer, ProvenBreakpointAnswer } from "../types.js";
2
+ import type { PrivateKeyRecord } from "./types.js";
3
+ /**
4
+ * Sign a BreakpointAnswer with the responder's private key.
5
+ *
6
+ * Returns a ProvenBreakpointAnswer with the signature and key metadata.
7
+ */
8
+ export declare function signAnswer(answer: BreakpointAnswer, fingerprint: string, baseDir?: string): Promise<ProvenBreakpointAnswer>;
9
+ /**
10
+ * Sign a BreakpointAnswer using an already-loaded PrivateKeyRecord.
11
+ *
12
+ * This avoids requiring the key to be persisted on disk and is useful
13
+ * for backends that load the key from a configured path.
14
+ */
15
+ export declare function signAnswerWithKeyRecord(answer: BreakpointAnswer, keyRecord: PrivateKeyRecord): ProvenBreakpointAnswer;
16
+ //# sourceMappingURL=sign.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sign.d.ts","sourceRoot":"","sources":["../../src/proven/sign.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAE5E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AA4BnD;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,gBAAgB,EACxB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,sBAAsB,CAAC,CAOjC;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,gBAAgB,EACxB,SAAS,EAAE,gBAAgB,GAC1B,sBAAsB,CAiBxB"}
@@ -0,0 +1,60 @@
1
+ import { createPrivateKey, sign as cryptoSign } from "node:crypto";
2
+ import { loadPrivateKey } from "./keys.js";
3
+ /**
4
+ * Fields included in the signature. The order is canonical.
5
+ */
6
+ const SIGNED_FIELDS = [
7
+ "id",
8
+ "breakpointId",
9
+ "responderId",
10
+ "text",
11
+ "approved",
12
+ "confidence",
13
+ "answeredAt",
14
+ ];
15
+ /**
16
+ * Build the canonical signing payload from an answer.
17
+ * Fields are sorted, null/undefined serialized as empty string.
18
+ */
19
+ function buildSigningPayload(answer) {
20
+ const parts = [];
21
+ for (const field of SIGNED_FIELDS) {
22
+ const value = answer[field];
23
+ parts.push(`${field}=${value ?? ""}`);
24
+ }
25
+ return Buffer.from(parts.join("\n"), "utf-8");
26
+ }
27
+ /**
28
+ * Sign a BreakpointAnswer with the responder's private key.
29
+ *
30
+ * Returns a ProvenBreakpointAnswer with the signature and key metadata.
31
+ */
32
+ export async function signAnswer(answer, fingerprint, baseDir) {
33
+ const keyRecord = await loadPrivateKey(fingerprint, baseDir);
34
+ if (!keyRecord) {
35
+ throw new Error(`Private key not found for fingerprint: ${fingerprint}`);
36
+ }
37
+ return signAnswerWithKeyRecord(answer, keyRecord);
38
+ }
39
+ /**
40
+ * Sign a BreakpointAnswer using an already-loaded PrivateKeyRecord.
41
+ *
42
+ * This avoids requiring the key to be persisted on disk and is useful
43
+ * for backends that load the key from a configured path.
44
+ */
45
+ export function signAnswerWithKeyRecord(answer, keyRecord) {
46
+ const privateKey = createPrivateKey({
47
+ key: Buffer.from(keyRecord.privateKey, "base64"),
48
+ format: "der",
49
+ type: "pkcs8",
50
+ });
51
+ const payload = buildSigningPayload(answer);
52
+ const signature = cryptoSign(null, payload, privateKey);
53
+ return {
54
+ ...answer,
55
+ signature: signature.toString("base64"),
56
+ publicKeyFingerprint: keyRecord.fingerprint,
57
+ signedAt: new Date().toISOString(),
58
+ signedFields: [...SIGNED_FIELDS],
59
+ };
60
+ }
@@ -0,0 +1,26 @@
1
+ export interface KeyPairMetadata {
2
+ /** Hex-encoded fingerprint (SHA-256 of public key DER). */
3
+ fingerprint: string;
4
+ /** Responder identity associated with this key. */
5
+ responderId: string;
6
+ responderName: string;
7
+ /** ISO timestamp of key creation. */
8
+ createdAt: string;
9
+ /** ISO timestamp when this key should no longer be used for signing. */
10
+ expiresAt?: string;
11
+ /** Human-readable note. */
12
+ note?: string;
13
+ }
14
+ export interface PublicKeyRecord {
15
+ /** Base64-encoded Ed25519 public key (DER/SPKI format). */
16
+ publicKey: string;
17
+ metadata: KeyPairMetadata;
18
+ }
19
+ export interface PrivateKeyRecord {
20
+ /** Base64-encoded Ed25519 private key (PKCS8 format). */
21
+ privateKey: string;
22
+ /** Fingerprint linking to the corresponding public key. */
23
+ fingerprint: string;
24
+ responderId: string;
25
+ }
26
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/proven/types.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,eAAe;IAC9B,2DAA2D;IAC3D,WAAW,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,eAAe,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB"}
@@ -0,0 +1,5 @@
1
+ // ── Proven-specific types ───────────────────────────────────────────────
2
+ // These types are used internally by the proven subsystem for key management.
3
+ // The ProvenBreakpointAnswer and ProvenVerificationResult schemas live in
4
+ // src/types.ts as they are part of the public domain model.
5
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { ProvenBreakpointAnswer, ProvenVerificationResult } from "../types.js";
2
+ /**
3
+ * Verify a proven breakpoint answer against trusted public keys.
4
+ */
5
+ export declare function verifyAnswer(provenAnswer: ProvenBreakpointAnswer, baseDir?: string): Promise<ProvenVerificationResult>;
6
+ //# sourceMappingURL=verify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/proven/verify.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,wBAAwB,EAAoB,MAAM,aAAa,CAAC;AAetG;;GAEG;AACH,wBAAsB,YAAY,CAChC,YAAY,EAAE,sBAAsB,EACpC,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,wBAAwB,CAAC,CAiDnC"}
@@ -0,0 +1,58 @@
1
+ import { createPublicKey, verify as cryptoVerify } from "node:crypto";
2
+ import { loadTrustedPublicKeys } from "./keys.js";
3
+ /**
4
+ * Rebuild the canonical signing payload for verification.
5
+ */
6
+ function buildSigningPayload(answer, signedFields) {
7
+ const parts = [];
8
+ for (const field of signedFields) {
9
+ const value = answer[field];
10
+ parts.push(`${field}=${value ?? ""}`);
11
+ }
12
+ return Buffer.from(parts.join("\n"), "utf-8");
13
+ }
14
+ /**
15
+ * Verify a proven breakpoint answer against trusted public keys.
16
+ */
17
+ export async function verifyAnswer(provenAnswer, baseDir) {
18
+ const trustedKeys = await loadTrustedPublicKeys(baseDir);
19
+ const matchingKey = trustedKeys.find((k) => k.metadata.fingerprint === provenAnswer.publicKeyFingerprint);
20
+ if (!matchingKey) {
21
+ return {
22
+ valid: false,
23
+ publicKeyFingerprint: provenAnswer.publicKeyFingerprint,
24
+ reason: "Public key not found in trusted keys",
25
+ verifiedAt: new Date().toISOString(),
26
+ };
27
+ }
28
+ // Check key expiration
29
+ if (matchingKey.metadata.expiresAt) {
30
+ const expiresAt = new Date(matchingKey.metadata.expiresAt);
31
+ const signedAt = new Date(provenAnswer.signedAt);
32
+ if (signedAt > expiresAt) {
33
+ return {
34
+ valid: false,
35
+ publicKeyFingerprint: provenAnswer.publicKeyFingerprint,
36
+ responderName: matchingKey.metadata.responderName,
37
+ reason: "Key was expired at time of signing",
38
+ verifiedAt: new Date().toISOString(),
39
+ };
40
+ }
41
+ }
42
+ // Verify signature
43
+ const publicKey = createPublicKey({
44
+ key: Buffer.from(matchingKey.publicKey, "base64"),
45
+ format: "der",
46
+ type: "spki",
47
+ });
48
+ const payload = buildSigningPayload(provenAnswer, provenAnswer.signedFields);
49
+ const signatureBuffer = Buffer.from(provenAnswer.signature, "base64");
50
+ const isValid = cryptoVerify(null, payload, publicKey, signatureBuffer);
51
+ return {
52
+ valid: isValid,
53
+ publicKeyFingerprint: provenAnswer.publicKeyFingerprint,
54
+ responderName: matchingKey.metadata.responderName,
55
+ reason: isValid ? "Signature verified successfully" : "Signature verification failed",
56
+ verifiedAt: new Date().toISOString(),
57
+ };
58
+ }