codehere 1.0.1 → 1.0.2

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 (163) hide show
  1. package/README.md +36 -192
  2. package/dist/application/use-cases/check-claimed-tests.js +81 -0
  3. package/dist/application/use-cases/detect-test-weakening.js +237 -0
  4. package/dist/application/use-cases/rerun-tests.js +110 -7
  5. package/dist/index.js +38 -1212
  6. package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
  7. package/dist/presentation/cli/commands/check-command.js +178 -23
  8. package/dist/presentation/cli/commands/help-command.js +21 -285
  9. package/dist/ui.js +5 -236
  10. package/dist/utils/version.js +0 -6
  11. package/package.json +9 -38
  12. package/dist/application/services/verdict-service.js +0 -154
  13. package/dist/application/use-cases/assess-cross-vendor.js +0 -126
  14. package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
  15. package/dist/application/use-cases/domain-trust.js +0 -110
  16. package/dist/application/use-cases/export-aat.js +0 -107
  17. package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
  18. package/dist/application/use-cases/generate-final-report.js +0 -259
  19. package/dist/application/use-cases/generate-narration.js +0 -130
  20. package/dist/application/use-cases/generate-thread-title.js +0 -181
  21. package/dist/application/use-cases/narrator-prompt-template.js +0 -158
  22. package/dist/application/use-cases/plan-auto-review.js +0 -57
  23. package/dist/application/use-cases/reconcile-audit.js +0 -100
  24. package/dist/application/use-cases/search-sessions.js +0 -70
  25. package/dist/application/use-cases/should-auto-review.js +0 -71
  26. package/dist/application/use-cases/thread-export.js +0 -245
  27. package/dist/application/use-cases/verify-claim.js +0 -456
  28. package/dist/application/use-cases/write-pass.js +0 -277
  29. package/dist/domain/audit.js +0 -11
  30. package/dist/domain/services/codebase-detector.js +0 -85
  31. package/dist/domain/task.js +0 -91
  32. package/dist/error-handler.js +0 -139
  33. package/dist/infrastructure/completion/bash-completion.js +0 -29
  34. package/dist/infrastructure/completion/fish-completion.js +0 -17
  35. package/dist/infrastructure/completion/zsh-completion.js +0 -25
  36. package/dist/infrastructure/config/config-manager.js +0 -174
  37. package/dist/infrastructure/config/product-context.js +0 -49
  38. package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
  39. package/dist/infrastructure/context/adapters/figma-source.js +0 -198
  40. package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
  41. package/dist/infrastructure/context/adapters/linear-source.js +0 -190
  42. package/dist/infrastructure/context/context-resolver.js +0 -144
  43. package/dist/infrastructure/context/context-source.js +0 -114
  44. package/dist/infrastructure/context/github-api.js +0 -110
  45. package/dist/infrastructure/context/github-device-flow.js +0 -65
  46. package/dist/infrastructure/context/oauth-flow.js +0 -207
  47. package/dist/infrastructure/context/oauth-store.js +0 -122
  48. package/dist/infrastructure/context/registry-bootstrap.js +0 -95
  49. package/dist/infrastructure/context/write-pass-resolver.js +0 -106
  50. package/dist/infrastructure/cost/budget-config.js +0 -114
  51. package/dist/infrastructure/cost/cost-router.js +0 -97
  52. package/dist/infrastructure/cost/spend-aggregator.js +0 -222
  53. package/dist/infrastructure/cost/team-ledger.js +0 -355
  54. package/dist/infrastructure/discovery/discovery-log.js +0 -85
  55. package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
  56. package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
  57. package/dist/infrastructure/hooks/hook-mode.js +0 -87
  58. package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
  59. package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
  60. package/dist/infrastructure/hooks/session-analyzer.js +0 -177
  61. package/dist/infrastructure/observability/execution-tracer.js +0 -227
  62. package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
  63. package/dist/infrastructure/orchestration/agent-installer.js +0 -101
  64. package/dist/infrastructure/orchestration/agents-config-seed.js +0 -68
  65. package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
  66. package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
  67. package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
  68. package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
  69. package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
  70. package/dist/infrastructure/orchestration/context-handoff.js +0 -162
  71. package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
  72. package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
  73. package/dist/infrastructure/orchestration/detect-ollama.js +0 -75
  74. package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
  75. package/dist/infrastructure/orchestration/failover-config.js +0 -67
  76. package/dist/infrastructure/orchestration/failover-policy.js +0 -106
  77. package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
  78. package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
  79. package/dist/infrastructure/orchestration/graphify-context.js +0 -209
  80. package/dist/infrastructure/orchestration/memory-injector.js +0 -380
  81. package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
  82. package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
  83. package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
  84. package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
  85. package/dist/infrastructure/orchestration/opencode-adapter.js +0 -580
  86. package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
  87. package/dist/infrastructure/orchestration/readiness.js +0 -103
  88. package/dist/infrastructure/orchestration/session-manager.js +0 -1590
  89. package/dist/infrastructure/orchestration/session-persistence.js +0 -394
  90. package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
  91. package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
  92. package/dist/infrastructure/protocols/cloud-sync.js +0 -207
  93. package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
  94. package/dist/infrastructure/protocols/mcp-server-impl.js +0 -3935
  95. package/dist/infrastructure/protocols/mcp-tools.js +0 -401
  96. package/dist/infrastructure/protocols/probe-port.js +0 -36
  97. package/dist/infrastructure/protocols/thread-phase.js +0 -105
  98. package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
  99. package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
  100. package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
  101. package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
  102. package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
  103. package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
  104. package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
  105. package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
  106. package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
  107. package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
  108. package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
  109. package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
  110. package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
  111. package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
  112. package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
  113. package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
  114. package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
  115. package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
  116. package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
  117. package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
  118. package/dist/infrastructure/protocols/ui/styles.js +0 -2147
  119. package/dist/infrastructure/protocols/ui/tokens.js +0 -96
  120. package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
  121. package/dist/infrastructure/protocols/verify-page.js +0 -1677
  122. package/dist/infrastructure/security/file-collection.js +0 -118
  123. package/dist/infrastructure/security/fix-code.js +0 -144
  124. package/dist/infrastructure/security/fix-secret.js +0 -89
  125. package/dist/infrastructure/security/permissions-config.js +0 -235
  126. package/dist/infrastructure/security/scan-types.js +0 -10
  127. package/dist/infrastructure/security/ship-scan.js +0 -195
  128. package/dist/infrastructure/security/trust-scorer.js +0 -724
  129. package/dist/infrastructure/security/vibe-checks.js +0 -99
  130. package/dist/infrastructure/storage/atomic-write.js +0 -86
  131. package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
  132. package/dist/infrastructure/storage/project-thread-migration.js +0 -237
  133. package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
  134. package/dist/infrastructure/storage/project-thread-reader.js +0 -409
  135. package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
  136. package/dist/monitoring.js +0 -212
  137. package/dist/presentation/cli/commands/audit-command.js +0 -201
  138. package/dist/presentation/cli/commands/budget-command.js +0 -79
  139. package/dist/presentation/cli/commands/config-command.js +0 -125
  140. package/dist/presentation/cli/commands/context-source-command.js +0 -217
  141. package/dist/presentation/cli/commands/hook-command.js +0 -265
  142. package/dist/presentation/cli/commands/init-command.js +0 -590
  143. package/dist/presentation/cli/commands/memory-command.js +0 -1031
  144. package/dist/presentation/cli/commands/narrator-command.js +0 -101
  145. package/dist/presentation/cli/commands/permission-command.js +0 -105
  146. package/dist/presentation/cli/commands/protocols-builder.js +0 -254
  147. package/dist/presentation/cli/commands/protocols-command.js +0 -84
  148. package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
  149. package/dist/presentation/cli/commands/report-command.js +0 -403
  150. package/dist/presentation/cli/commands/scan-command.js +0 -52
  151. package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
  152. package/dist/presentation/cli/commands/search-command.js +0 -51
  153. package/dist/presentation/cli/commands/setup-command.js +0 -330
  154. package/dist/presentation/cli/commands/status-command.js +0 -614
  155. package/dist/presentation/cli/commands/team-command.js +0 -256
  156. package/dist/presentation/cli/commands/thread-command.js +0 -370
  157. package/dist/presentation/cli/commands/trust-command.js +0 -328
  158. package/dist/presentation/cli/commands/verify-command.js +0 -298
  159. package/dist/presentation/cli/error-display.js +0 -328
  160. package/dist/presentation/cli/repl.js +0 -805
  161. package/dist/presentation/cli/task-deliverable.js +0 -153
  162. package/dist/testing/cassette.js +0 -125
  163. package/dist/testing/replay-adapter.js +0 -147
@@ -1,336 +0,0 @@
1
- /**
2
- * R2 · sign the live audit-chain TIP with the machine's persisted ed25519 key.
3
- *
4
- * The hash chain catches a NAIVE edit, but a determined attacker can edit a
5
- * mid-chain entry and RE-WALK every later hash to produce a chain that verifies
6
- * green (`computeEntryHash` is public). Signing the tip closes that for anyone
7
- * who verifies with the public key: a re-walk changes the hash of the SIGNED
8
- * entry, so the stored signature — made with a key the tamperer doesn't hold —
9
- * no longer matches. Truncation below the signed point likewise fails.
10
- *
11
- * VERIFICATION IS AN ANCESTOR CHECK, not a tip-equality check (devil's-advocate
12
- * audit 2026-06-09 caught the original tip-equality version false-alarming
13
- * "tampered" on honest chains): the chain appends CONTINUOUSLY (every tool call,
14
- * spawns, overrides) while signing happens at session completion — so the signed
15
- * entry is usually an ANCESTOR of the live tip, and that is honest. Verification
16
- * asserts the signed (seq, entryHash) still exists UNMODIFIED at that seq.
17
- * Entries appended after the most recent signing are not yet covered — they're
18
- * covered by the next completion's signature. The protected prefix only ever
19
- * grows.
20
- *
21
- * `rm <audit>.tipsig` bypass (same audit): once a chain has EVER been signed, a
22
- * missing signature file must BLOCK — otherwise the forger just deletes the
23
- * sidecar. Sign-state lives in the keystore dir (`keys/audit-sign-state.json`),
24
- * separate from the audit file, so an attacker who deletes the sidecar but not
25
- * the keystore is caught. NOTE: sign-state is LOCAL-ONLY and path-keyed — it does
26
- * NOT travel with an exported chain, so deleted-sidecar detection only works on
27
- * the machine that signed; an exported/shared chain carries its `.tipsig` but a
28
- * recipient can't tell it was ever signed if the sidecar is stripped. (An
29
- * owner-level attacker who can also rewrite the keystore defeats this entirely —
30
- * see HONEST SCOPE; the real fix is R1's daemon-held key + R3's external anchor.)
31
- *
32
- * R1 DOES NOT CLOSE THE OWNER GAP — evaluated 2026-08-08. A daemon-held key is
33
- * still reachable by the owner, because the daemon runs as the owner. R1 raises
34
- * the cost of the attack; it does not change who can defeat it. Only an EXTERNAL
35
- * anchor does, and that collides head-on with local-first + zero telemetry: an
36
- * anchor has to be outside the machine to be an anchor. Those two values are in
37
- * direct tension and no amount of local cryptography resolves it.
38
- *
39
- * THE ANCHOR THAT FITS, and it needs no service of ours: the user's own git
40
- * remote. `git notes --ref=codehere-audit` on HEAD carries the signed tip, and
41
- * nothing leaves the machine until the USER pushes — which they were doing
42
- * anyway. Proven end-to-end 2026-08-08 in a throwaway repo:
43
- * · the commit SHA is unchanged and the working tree stays clean (non-invasive)
44
- * · a history rewrite ORPHANS the note, so the rewrite is visible
45
- * · after the owner deletes the note locally, the REMOTE still holds it and a
46
- * reviewer can fetch it back and read the original tip hash
47
- * · erasing the remote copy needs an explicit destructive push, which shows up
48
- * in host audit logs / branch protection / anyone who already fetched
49
- * So it converts "silently forgeable by the owner" into "forgeable only by a
50
- * visible act on a third party the owner does not solely control" — the same
51
- * standard a signed git commit gets, no more and no less. It is NOT
52
- * cryptographic owner-proofing; nothing local-first can be. Known costs:
53
- * refs/notes are not pushed by default (needs a refspec), and a user who never
54
- * pushes has no witness. It also lands exactly on the PR surface.
55
- * NOT BUILT — this is the evaluation, not the feature.
56
- *
57
- * HONEST SCOPE: this defends against a 3RD PARTY (or the owner sharing an export)
58
- * editing the chain after it was signed. It does NOT defend against the agent or
59
- * owner who CAN reach the signing key + keystore and re-sign / rewrite sign-state
60
- * — that needs a daemon-held key the agent can't reach (R1) + an external anchor
61
- * (R3). SIGNATURE-ORACLE limit (R1.3): a signature attests the daemon
62
- * RECORDED the entry — never that the described tool call actually happened
63
- * (any local process can POST an honest-shaped entry). Surface wording must
64
- * stay "recorded-by-daemon"-shaped. The tip signature also TRANSITIVELY
65
- * notarizes the whole prefix — including hook-direct entries and any
66
- * well-chained same-uid direct append (even one carrying a forged
67
- * writer:'daemon'; the endpoint's rejection binds only endpoint callers).
68
- * Until R3 the honest words remain
69
- * "integrity-checked" + "signed at
70
- * write-time".
71
- *
72
- * Deliberately standalone (reads the chain from the file itself) so the audit
73
- * hot-path in claude-sdk-hook.ts never has to import the signing stack — keeps
74
- * the dependency graph acyclic.
75
- */
76
- import { existsSync, readFileSync, writeFileSync, chmodSync, mkdirSync, openSync, fstatSync, readSync, closeSync, renameSync, } from 'fs';
77
- import { join } from 'path';
78
- import { sign as edSign, verify as edVerify } from 'crypto';
79
- import { loadOrCreateKeypair, keysDir } from '../orchestration/thread-key-store.js';
80
- import { exportPublicPem, importPublicPem, publicKeyToBase64, } from '../../application/use-cases/thread-export.js';
81
- /** Read the audit chain's current tip (last real line's seq + entryHash).
82
- * TAIL-READ (last 64KB), not a full-file read: sign-at-write (R1.3) calls
83
- * this per daemon append on a file with no rotation — a full read is O(N)
84
- * on the daemon event loop, measured 4-5ms on a 6.4MB chain vs 0.2ms for
85
- * the tail (refuter B2). The tip is by definition in the tail. */
86
- export function readAuditTip(auditFile) {
87
- if (!existsSync(auditFile))
88
- return null;
89
- try {
90
- const fd = openSync(auditFile, 'r');
91
- let text;
92
- try {
93
- const size = fstatSync(fd).size;
94
- const start = Math.max(0, size - 65536);
95
- const buf = Buffer.alloc(size - start);
96
- readSync(fd, buf, 0, buf.length, start);
97
- text = buf.toString('utf-8');
98
- if (start > 0)
99
- text = text.slice(text.indexOf('\n') + 1); // drop partial first line
100
- }
101
- finally {
102
- closeSync(fd);
103
- }
104
- const lines = text.trimEnd().split('\n');
105
- for (let i = lines.length - 1; i >= 0; i--) {
106
- const line = lines[i].trim();
107
- if (!line)
108
- continue;
109
- const e = JSON.parse(line);
110
- if (typeof e.seq === 'number' && typeof e.entryHash === 'string') {
111
- return { seq: e.seq, entryHash: e.entryHash };
112
- }
113
- return null; // last real line is a pre-chain legacy entry → no tip to sign
114
- }
115
- }
116
- catch {
117
- /* unreadable / malformed → no tip */
118
- }
119
- return null;
120
- }
121
- /** Read the entry hash recorded at a given seq, or null if no such entry. */
122
- function readEntryHashAtSeq(auditFile, seq) {
123
- if (!existsSync(auditFile))
124
- return null;
125
- try {
126
- for (const line of readFileSync(auditFile, 'utf-8').split('\n')) {
127
- const t = line.trim();
128
- if (!t)
129
- continue;
130
- const e = JSON.parse(t);
131
- if (e.seq === seq && typeof e.entryHash === 'string')
132
- return e.entryHash;
133
- }
134
- }
135
- catch {
136
- /* malformed line mid-file → treat as not found (the chain verifier reports it) */
137
- }
138
- return null;
139
- }
140
- function tipSigPath(auditFile) {
141
- return auditFile + '.tipsig';
142
- }
143
- function tipMessage(tip) {
144
- return Buffer.from(`${tip.seq}:${tip.entryHash}`, 'utf-8');
145
- }
146
- function signStatePath() {
147
- return join(keysDir(), 'audit-sign-state.json');
148
- }
149
- function loadSignState() {
150
- try {
151
- const raw = JSON.parse(readFileSync(signStatePath(), 'utf-8'));
152
- if (raw && raw.schema === 1 && typeof raw.files === 'object')
153
- return raw;
154
- }
155
- catch {
156
- /* missing/corrupt → empty */
157
- }
158
- return { schema: 1, files: {} };
159
- }
160
- function recordSignState(auditFile, seq, signedAt) {
161
- try {
162
- mkdirSync(keysDir(), { recursive: true });
163
- const state = loadSignState();
164
- state.files[auditFile] = { seq, signedAt };
165
- {
166
- const tmpState = signStatePath() + '.tmp';
167
- writeFileSync(tmpState, JSON.stringify(state, null, 2) + '\n', { mode: 0o600 });
168
- renameSync(tmpState, signStatePath());
169
- }
170
- }
171
- catch {
172
- /* best-effort — sign-state is a detection aid, never blocks signing */
173
- }
174
- }
175
- /** Was this audit file ever signed (per the keystore's sign-state)? */
176
- export function chainWasSigned(auditFile) {
177
- return !!loadSignState().files[auditFile];
178
- }
179
- /**
180
- * S9 · forget a chain's sign-state entry. ONLY for the mock practice chain:
181
- * the daemon wipes the mock chain at boot, and without pruning, the stale
182
- * sign-state key would flag the fresh chain as an rm-bypass (a false tamper
183
- * alarm in practice mode) while dead keys accumulate in the REAL keystore.
184
- * Never call this for a real chain — losing sign-state is losing detection.
185
- */
186
- export function pruneSignState(auditFile) {
187
- try {
188
- const state = loadSignState();
189
- if (!state.files[auditFile])
190
- return;
191
- delete state.files[auditFile];
192
- const tmpState = signStatePath() + '.tmp';
193
- writeFileSync(tmpState, JSON.stringify(state, null, 2) + '\n', { mode: 0o600 });
194
- renameSync(tmpState, signStatePath());
195
- }
196
- catch {
197
- /* best-effort — same contract as recordSignState */
198
- }
199
- }
200
- // Cache the keypair per-process — loadOrCreateKeypair does disk I/O each call,
201
- // and signing happens repeatedly in a long-lived daemon.
202
- let cachedKeys = null;
203
- function keys() {
204
- return (cachedKeys ??= loadOrCreateKeypair());
205
- }
206
- /**
207
- * Sign the current chain tip, writing `<auditFile>.tipsig` + recording
208
- * sign-state in the keystore. Best-effort: returns null (no-op) when there's
209
- * no chain yet. Never throws.
210
- */
211
- export function signAuditTip(auditFile, now = Date.now()) {
212
- const tip = readAuditTip(auditFile);
213
- if (!tip)
214
- return null;
215
- try {
216
- const k = keys();
217
- const sig = edSign(null, tipMessage(tip), k.privateKey).toString('base64');
218
- const rec = {
219
- schema: 1,
220
- alg: 'ed25519',
221
- seq: tip.seq,
222
- entryHash: tip.entryHash,
223
- sig,
224
- pubPem: exportPublicPem(k.publicKey),
225
- signedAt: now,
226
- };
227
- // Atomic (tmp+rename): per-write signing multiplies rewrite frequency
228
- // ~1000x vs per-session — a crash mid-write must not leave a corrupt
229
- // .tipsig that fails honest verification (refuter N1).
230
- const tmpSig = tipSigPath(auditFile) + '.tmp';
231
- writeFileSync(tmpSig, JSON.stringify(rec) + '\n', { mode: 0o600 });
232
- renameSync(tmpSig, tipSigPath(auditFile));
233
- try {
234
- chmodSync(tipSigPath(auditFile), 0o600);
235
- }
236
- catch {
237
- /* perms best-effort */
238
- }
239
- recordSignState(auditFile, tip.seq, now);
240
- return rec;
241
- }
242
- catch {
243
- return null; // signing must never break the path that called it
244
- }
245
- }
246
- /**
247
- * Verify the tip signature against the chain. The signed (seq, entryHash) must
248
- * still be an UNMODIFIED ancestor of the current chain — appends since signing
249
- * are honest and expected (signing happens at session completion; the chain
250
- * grows continuously between completions). Pass `trustedKeys` to also check the
251
- * signer is known.
252
- */
253
- export function verifyAuditTip(auditFile, trustedKeys) {
254
- const base = {
255
- present: false,
256
- expectedButMissing: false,
257
- signatureValid: false,
258
- ancestorIntact: false,
259
- matchesTip: false,
260
- trusted: false,
261
- signerKeyB64: null,
262
- reason: '',
263
- };
264
- if (!existsSync(tipSigPath(auditFile))) {
265
- if (chainWasSigned(auditFile)) {
266
- return {
267
- ...base,
268
- expectedButMissing: true,
269
- reason: 'this chain was previously signed but the signature file is GONE — it may have been deleted to hide an edit',
270
- };
271
- }
272
- return { ...base, reason: 'no tip signature on this chain' };
273
- }
274
- let rec;
275
- try {
276
- rec = JSON.parse(readFileSync(tipSigPath(auditFile), 'utf-8'));
277
- }
278
- catch {
279
- return { ...base, present: true, reason: 'tip signature file is unreadable or corrupt' };
280
- }
281
- if (rec.alg !== 'ed25519' || typeof rec.entryHash !== 'string' || typeof rec.seq !== 'number') {
282
- return { ...base, present: true, reason: 'tip signature is malformed' };
283
- }
284
- base.present = true;
285
- let pub;
286
- try {
287
- pub = importPublicPem(rec.pubPem);
288
- }
289
- catch {
290
- return { ...base, reason: 'tip signature carries an invalid public key' };
291
- }
292
- base.signerKeyB64 = publicKeyToBase64(pub);
293
- base.trusted = !trustedKeys || trustedKeys.has(base.signerKeyB64);
294
- try {
295
- base.signatureValid = edVerify(null, tipMessage({ seq: rec.seq, entryHash: rec.entryHash }), pub, Buffer.from(rec.sig, 'base64'));
296
- }
297
- catch {
298
- base.signatureValid = false;
299
- }
300
- const hashAtSeq = readEntryHashAtSeq(auditFile, rec.seq);
301
- base.ancestorIntact = hashAtSeq !== null && hashAtSeq === rec.entryHash;
302
- const tip = readAuditTip(auditFile);
303
- base.matchesTip = !!tip && tip.seq === rec.seq && tip.entryHash === rec.entryHash;
304
- if (!base.signatureValid)
305
- base.reason = 'tip signature is invalid — forged or corrupt';
306
- else if (!base.ancestorIntact)
307
- base.reason =
308
- 'the signed entry is no longer intact in the chain — it was edited (re-walked) or truncated after signing';
309
- else if (!base.trusted)
310
- base.reason = 'chain is signed and intact, but by an untrusted key';
311
- else if (base.matchesTip)
312
- base.reason = "chain tip is signed and matches — no edits by anyone without this machine's key";
313
- else
314
- base.reason = `signed at seq ${rec.seq} · the chain has grown since · the signed prefix is intact`;
315
- return base;
316
- }
317
- /**
318
- * Does the tip-signature state BLOCK verification? Blocks when:
319
- * - sign-state says the chain was signed but the .tipsig is GONE (the
320
- * delete-the-sidecar bypass), or
321
- * - a signature is present but invalid, its signed entry is no longer an
322
- * intact ancestor (edit/re-walk/truncation), or the signer is untrusted
323
- * (an attacker re-signing a forgery with their OWN key would otherwise pass).
324
- * A never-signed chain does not block (pre-R2 chains stay valid). Kept pure —
325
- * this is the verdict the `audit verify` exit code depends on.
326
- */
327
- export function tipBlocksVerification(tip) {
328
- if (tip.expectedButMissing)
329
- return true;
330
- return tip.present && (!tip.signatureValid || !tip.ancestorIntact || !tip.trusted);
331
- }
332
- /** Reset the per-process key cache (tests). */
333
- export function __resetTipKeyCache() {
334
- cachedKeys = null;
335
- }
336
- //# sourceMappingURL=audit-tip-signature.js.map