@holmes-lab/holmes-kit 0.19.6 → 0.20.1

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 (139) hide show
  1. package/CHANGELOG.md +143 -0
  2. package/README.md +9 -2
  3. package/dist/.build-id +1 -1
  4. package/dist/holmes/cli/approve-status.d.ts +24 -0
  5. package/dist/holmes/cli/approve-status.js +166 -0
  6. package/dist/holmes/cli/approve-watch.d.ts +6 -0
  7. package/dist/holmes/cli/approve-watch.js +6 -0
  8. package/dist/holmes/cli/approve.d.ts +27 -0
  9. package/dist/holmes/cli/approve.js +68 -7
  10. package/dist/holmes/cli/cli-execution.d.ts +2 -0
  11. package/dist/holmes/cli/cli-execution.js +24 -0
  12. package/dist/holmes/cli/doctor-check.d.ts +15 -0
  13. package/dist/holmes/cli/doctor-check.js +2 -0
  14. package/dist/holmes/cli/doctor.d.ts +2 -8
  15. package/dist/holmes/cli/doctor.js +12 -3
  16. package/dist/holmes/cli/gitignore-merge.d.ts +0 -5
  17. package/dist/holmes/cli/gitignore-merge.js +6 -2
  18. package/dist/holmes/cli/index.js +176 -139
  19. package/dist/holmes/cli/npx-cache-check.d.ts +16 -0
  20. package/dist/holmes/cli/npx-cache-check.js +130 -0
  21. package/dist/holmes/cli/playbook-skills.d.ts +2 -22
  22. package/dist/holmes/cli/playbook-skills.js +10 -54
  23. package/dist/holmes/cli/probe-process.d.ts +17 -2
  24. package/dist/holmes/cli/probe-process.js +44 -11
  25. package/dist/holmes/governance/approval-grants.d.ts +22 -0
  26. package/dist/holmes/governance/approval-grants.js +55 -1
  27. package/dist/holmes/governance/approval-queue.d.ts +43 -4
  28. package/dist/holmes/governance/approval-queue.js +91 -13
  29. package/dist/holmes/governance/display-id-aliases.d.ts +29 -0
  30. package/dist/holmes/governance/display-id-aliases.js +86 -0
  31. package/dist/holmes/governance/ledger-store.d.ts +42 -14
  32. package/dist/holmes/governance/ledger-store.js +49 -3
  33. package/dist/holmes/governance/ledger-timeline.d.ts +3 -1
  34. package/dist/holmes/governance/ledger-timeline.js +11 -1
  35. package/dist/holmes/governance/provenance-chain.d.ts +2 -0
  36. package/dist/holmes/governance/provenance-chain.js +13 -2
  37. package/dist/holmes/governance/provenance-ledger.d.ts +28 -0
  38. package/dist/holmes/governance/provenance-ledger.js +69 -16
  39. package/dist/holmes/governance/session-context.d.ts +3 -0
  40. package/dist/holmes/governance/session-context.js +26 -8
  41. package/dist/holmes/guardrail/risk-classifier.js +2 -2
  42. package/dist/holmes/guardrail/write-target.js +2 -1
  43. package/dist/holmes/hooks/pre-tool-use.js +12 -1
  44. package/dist/holmes/hooks/stop.js +14 -3
  45. package/dist/holmes/mcp/handlers/adoption.d.ts +118 -0
  46. package/dist/holmes/mcp/handlers/adoption.js +186 -0
  47. package/dist/holmes/mcp/handlers/entity-integration.d.ts +100 -0
  48. package/dist/holmes/mcp/handlers/entity-integration.js +324 -0
  49. package/dist/holmes/mcp/handlers/entity-renumber.d.ts +137 -0
  50. package/dist/holmes/mcp/handlers/entity-renumber.js +228 -0
  51. package/dist/holmes/mcp/handlers/entity-store.d.ts +78 -0
  52. package/dist/holmes/mcp/handlers/entity-store.js +212 -0
  53. package/dist/holmes/mcp/handlers/graph-operations.d.ts +90 -0
  54. package/dist/holmes/mcp/handlers/graph-operations.js +260 -0
  55. package/dist/holmes/mcp/handlers/maintenance-analysis.d.ts +34 -0
  56. package/dist/holmes/mcp/handlers/maintenance-analysis.js +288 -0
  57. package/dist/holmes/mcp/handlers/maintenance-evidence.d.ts +40 -0
  58. package/dist/holmes/mcp/handlers/maintenance-evidence.js +104 -0
  59. package/dist/holmes/mcp/handlers/operator-inspection.d.ts +167 -0
  60. package/dist/holmes/mcp/handlers/operator-inspection.js +284 -0
  61. package/dist/holmes/mcp/handlers/phase-queries.d.ts +34 -0
  62. package/dist/holmes/mcp/handlers/phase-queries.js +86 -0
  63. package/dist/holmes/mcp/handlers/review-evidence.d.ts +47 -0
  64. package/dist/holmes/mcp/handlers/review-evidence.js +370 -0
  65. package/dist/holmes/mcp/handlers/review-queries.d.ts +68 -0
  66. package/dist/holmes/mcp/handlers/review-queries.js +79 -0
  67. package/dist/holmes/mcp/handlers/risk-assessment.d.ts +25 -0
  68. package/dist/holmes/mcp/handlers/risk-assessment.js +181 -0
  69. package/dist/holmes/mcp/handlers/slice-orchestration.d.ts +72 -0
  70. package/dist/holmes/mcp/handlers/slice-orchestration.js +373 -0
  71. package/dist/holmes/mcp/handlers/spec-approval.d.ts +105 -0
  72. package/dist/holmes/mcp/handlers/spec-approval.js +504 -0
  73. package/dist/holmes/mcp/handlers/spec-authoring.d.ts +57 -0
  74. package/dist/holmes/mcp/handlers/spec-authoring.js +326 -0
  75. package/dist/holmes/mcp/handlers/spec-lifecycle.d.ts +122 -0
  76. package/dist/holmes/mcp/handlers/spec-lifecycle.js +339 -0
  77. package/dist/holmes/mcp/handlers/spec-queries.d.ts +30 -0
  78. package/dist/holmes/mcp/handlers/spec-queries.js +74 -0
  79. package/dist/holmes/mcp/handlers/taint-screening.d.ts +48 -0
  80. package/dist/holmes/mcp/handlers/taint-screening.js +183 -0
  81. package/dist/holmes/mcp/handlers/test-execution.d.ts +63 -0
  82. package/dist/holmes/mcp/handlers/test-execution.js +210 -0
  83. package/dist/holmes/mcp/handlers/workspace-identity.d.ts +30 -0
  84. package/dist/holmes/mcp/handlers/workspace-identity.js +68 -0
  85. package/dist/holmes/mcp/handlers/workspace-queries.d.ts +77 -0
  86. package/dist/holmes/mcp/handlers/workspace-queries.js +193 -0
  87. package/dist/holmes/mcp/handlers.d.ts +661 -424
  88. package/dist/holmes/mcp/handlers.js +270 -3251
  89. package/dist/holmes/mcp/server-specs-dir.d.ts +2 -0
  90. package/dist/holmes/mcp/server-specs-dir.js +71 -0
  91. package/dist/holmes/mcp/server.js +12 -2
  92. package/dist/holmes/mcp/tool-schemas.js +43 -2
  93. package/dist/holmes/project/execution-context.d.ts +17 -0
  94. package/dist/holmes/project/execution-context.js +121 -0
  95. package/dist/holmes/project/installer-markers.d.ts +34 -0
  96. package/dist/holmes/project/installer-markers.js +65 -0
  97. package/dist/holmes/project/root.d.ts +12 -1
  98. package/dist/holmes/project/root.js +22 -3
  99. package/dist/holmes/project/workspace-identity.d.ts +29 -0
  100. package/dist/holmes/project/workspace-identity.js +181 -0
  101. package/dist/holmes/review/test-outcomes.d.ts +6 -1
  102. package/dist/holmes/review/test-outcomes.js +23 -5
  103. package/dist/holmes/review/test-runner.d.ts +18 -0
  104. package/dist/holmes/review/test-runner.js +136 -5
  105. package/dist/holmes/rtm/graph-store.d.ts +50 -1
  106. package/dist/holmes/rtm/graph-store.js +244 -3
  107. package/dist/holmes/rtm/incremental.d.ts +1 -0
  108. package/dist/holmes/rtm/incremental.js +12 -3
  109. package/dist/holmes/rtm/localize.js +7 -0
  110. package/dist/holmes/rtm/rtm-graph.d.ts +9 -0
  111. package/dist/holmes/rtm/rtm-graph.js +13 -0
  112. package/dist/holmes/rtm/test-scope.js +4 -1
  113. package/dist/holmes/semantic/vector-cache.d.ts +16 -1
  114. package/dist/holmes/semantic/vector-cache.js +155 -19
  115. package/dist/holmes/spec/approval-status.d.ts +10 -0
  116. package/dist/holmes/spec/approval-status.js +7 -3
  117. package/dist/holmes/spec/entity-git-snapshot.d.ts +50 -0
  118. package/dist/holmes/spec/entity-git-snapshot.js +276 -0
  119. package/dist/holmes/spec/entity-integration-discharge.d.ts +73 -0
  120. package/dist/holmes/spec/entity-integration-discharge.js +233 -0
  121. package/dist/holmes/spec/entity-integration-transaction.d.ts +179 -0
  122. package/dist/holmes/spec/entity-integration-transaction.js +1221 -0
  123. package/dist/holmes/spec/entity-integration.d.ts +216 -0
  124. package/dist/holmes/spec/entity-integration.js +760 -0
  125. package/dist/holmes/spec/entity-renumber-transaction.d.ts +82 -0
  126. package/dist/holmes/spec/entity-renumber-transaction.js +711 -0
  127. package/dist/holmes/spec/entity-renumber.d.ts +32 -0
  128. package/dist/holmes/spec/entity-renumber.js +156 -0
  129. package/dist/holmes/spec/entity-store.d.ts +135 -0
  130. package/dist/holmes/spec/entity-store.js +1053 -0
  131. package/dist/holmes/spec/entity-transaction.d.ts +105 -0
  132. package/dist/holmes/spec/entity-transaction.js +741 -0
  133. package/dist/holmes/spec/renumber.d.ts +58 -0
  134. package/dist/holmes/spec/renumber.js +200 -1
  135. package/dist/holmes/spec/spec-store.d.ts +3 -2
  136. package/dist/holmes/spec/spec-store.js +23 -2
  137. package/dist/holmes/spec/transition-policy.d.ts +36 -0
  138. package/dist/holmes/spec/transition-policy.js +135 -0
  139. package/package.json +5 -2
@@ -0,0 +1,284 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.createOperatorInspectionHandlers = createOperatorInspectionHandlers;
37
+ // @implements A-SPEC-605, A-SPEC-605.1
38
+ // @implements A-SPEC-642
39
+ // @implements A-SPEC-631
40
+ const fs = __importStar(require("node:fs"));
41
+ const path = __importStar(require("node:path"));
42
+ const spec_store_1 = require("../../spec/spec-store");
43
+ const approval_status_1 = require("../../spec/approval-status");
44
+ const ledger_store_1 = require("../../governance/ledger-store");
45
+ const ledger_timeline_1 = require("../../governance/ledger-timeline");
46
+ const provenance_ledger_1 = require("../../governance/provenance-ledger");
47
+ const provenance_chain_1 = require("../../governance/provenance-chain");
48
+ const approval_grants_1 = require("../../governance/approval-grants");
49
+ function createOperatorInspectionHandlers(context) {
50
+ return {
51
+ /**
52
+ * @implements A-SPEC-538.2
53
+ * Read-only: report a spec's approval/seal state — sealed?, approved_digest, each parent's seal
54
+ * state, and the concrete blockers still standing between it and approval — so a caller need not
55
+ * parse files to ask "what is the approval state right now". Reuses `sealOf` and `approvalBlockers`
56
+ * (the same predicates the code gate and spec_approve read) so the report cannot drift from the
57
+ * acts it describes. No writes, no ledger append.
58
+ */
59
+ async approval_status(a) {
60
+ context.assertStoreReachable('approval_status', a.root); // @implements A-SPEC-419
61
+ const all = await context.listSpecs();
62
+ if (all.filter((s) => s.id === a.id).length > 1) {
63
+ return {
64
+ ok: false,
65
+ reason: `${a.id}이(가) 스토어에 두 번 이상 존재합니다 — 어느 사본의 상태를 물었는지 도구가 고를 수 없어 거부합니다.`,
66
+ };
67
+ }
68
+ const cur = await context.readSpec(a.id);
69
+ if (!cur) {
70
+ const dir = context.specsRoot();
71
+ const unreadable = typeof dir === 'string' ? (0, spec_store_1.unreadableSpecFiles)(dir) : [];
72
+ return { ok: false, reason: (0, spec_store_1.notFoundReason)(a.id, unreadable) };
73
+ }
74
+ // @implements A-SPEC-572.1
75
+ // The graph advisory, delivered at DESIGN time. Measured on our own work: A-SPEC-571.1's
76
+ // sealing advisory named the exact cluster three regressions then landed in — and by then
77
+ // the design was done. The calculation was never the gap; the delivery time was. So the
78
+ // read-only "what is blocking this right now" tool also answers "what will this scope leak,
79
+ // and where is it dense" — using the SAME functions spec_approve calls, so the preview can
80
+ // never disagree with the seal. Read-only stays read-only: no scan, no build, no ledger
81
+ // append (a query must not pollute the observation denominator), and every failure degrades
82
+ // to an absent field on an otherwise identical response.
83
+ let graphPreview;
84
+ try {
85
+ if (cur.spec.type === 'A-SPEC' && a.root) {
86
+ const dbPath = path.join(a.root, '.ax', 'rtm.sqlite');
87
+ // @implements A-SPEC-631 — non-mutating inspection of the live graph; an empty or
88
+ // unreadable file is reported as such instead of previewing "no impact" from nothing.
89
+ const { openPublishedGraph, headCommitOf } = require('../../rtm/graph-store');
90
+ const { cleanSubprocessEnv } = require('../../project/root');
91
+ const view = openPublishedGraph(dbPath, headCommitOf(a.root, cleanSubprocessEnv()));
92
+ if (view.status === 'empty' || view.status === 'unreadable') {
93
+ graphPreview = { unavailable: view.status, ...(view.graphAsOf ? { graphAsOf: view.graphAsOf } : {}) };
94
+ }
95
+ else if (view.graph !== null) {
96
+ const { declaredImpactGap } = require('../../rtm/impact-advisory');
97
+ const { anchorDensityFindings } = require('../../rtm/anchor-density');
98
+ const { filesToTouch } = require('../../spec/compat-impact');
99
+ const graph = view.graph;
100
+ const condition = { status: view.status, sourceCommit: view.sourceCommit, head: view.head };
101
+ try {
102
+ const ftt = filesToTouch(cur.spec);
103
+ const impact = declaredImpactGap(ftt, graph, (rel) => { try {
104
+ return fs.readFileSync(path.join(a.root, rel), 'utf8');
105
+ }
106
+ catch {
107
+ return null;
108
+ } });
109
+ const density = anchorDensityFindings(ftt, graph.implementsAnchorCounts());
110
+ // @implements A-SPEC-574.2 — the computation lives in cycle-detect; this file holds
111
+ // the wiring only (measured: 85 anchors here against a p90 of 7).
112
+ const { cycleAdvisory, classifyEdgeByTarget, CYCLE_ADVISORY_NOTE } = require('../../cpg/cycle-detect');
113
+ const readCache = new Map();
114
+ const readSource = (rel) => {
115
+ const hit = readCache.get(rel);
116
+ if (hit !== undefined)
117
+ return hit;
118
+ // A read failure degrades to the conservative kind rather than losing the finding.
119
+ let text = '';
120
+ try {
121
+ text = fs.readFileSync(path.join(a.root, rel), 'utf8');
122
+ }
123
+ catch {
124
+ text = '';
125
+ }
126
+ readCache.set(rel, text);
127
+ return text;
128
+ };
129
+ const cycleFindings = cycleAdvisory(ftt, graph.importEdges().map((edge) => ({
130
+ ...edge,
131
+ kind: classifyEdgeByTarget(readSource(edge.from), edge.from, edge.to),
132
+ })));
133
+ // @implements A-SPEC-574.5 — the same import edges the cycle pass already read, plus
134
+ // the parent-time symbol spans the graph already holds. No new scan, no new parse.
135
+ const { architectureObservation } = require('../../cpg/arch-observe');
136
+ const { TreeSitterTsParser } = require('../../cpg/language-parser');
137
+ const { langForPath } = require('../../cpg/cpg-scanner');
138
+ // Bounded to the DECLARED files, whose text is read once and used for both numbers.
139
+ const archParser = new TreeSitterTsParser();
140
+ const archText = new Map();
141
+ const readArch = (f) => {
142
+ if (!archText.has(f)) {
143
+ try {
144
+ archText.set(f, fs.readFileSync(path.join(a.root, f), 'utf8'));
145
+ }
146
+ catch {
147
+ archText.set(f, null);
148
+ }
149
+ }
150
+ return archText.get(f) ?? null;
151
+ };
152
+ const spans = new Map(ftt.map((f) => {
153
+ const text = readArch(f);
154
+ if (text === null)
155
+ return [f, []];
156
+ try {
157
+ return [f, archParser.extractSymbols(text, langForPath(f))
158
+ .filter((sy) => sy.kind !== 'class')
159
+ .map((sy) => ({ startLine: sy.startLine, endLine: sy.endLine }))];
160
+ }
161
+ catch {
162
+ return [f, []];
163
+ }
164
+ }));
165
+ const arch = architectureObservation(ftt, spans, graph.importEdges(), (f) => { const t = readArch(f); return t === null ? null : t.split('\n').length; });
166
+ if (impact || density.length > 0 || cycleFindings.length > 0 || arch.length > 0) {
167
+ const graphAsOf = (() => { try {
168
+ return fs.statSync(dbPath).mtime.toISOString();
169
+ }
170
+ catch {
171
+ return undefined;
172
+ } })();
173
+ graphPreview = {
174
+ graph: condition,
175
+ ...(impact ? { impact } : {}),
176
+ ...(density.length > 0 ? { density } : {}),
177
+ ...(cycleFindings.length > 0
178
+ ? { cycles: { findings: cycleFindings, note: CYCLE_ADVISORY_NOTE } } : {}),
179
+ ...(arch.length > 0 ? { architecture: arch } : {}),
180
+ ...(graphAsOf ? { graphAsOf } : {}),
181
+ };
182
+ }
183
+ }
184
+ finally {
185
+ graph.close();
186
+ }
187
+ }
188
+ }
189
+ }
190
+ catch {
191
+ graphPreview = undefined;
192
+ }
193
+ return { ok: true, ...(0, approval_status_1.describeApproval)(cur.spec, context.resolver(all)), ...(graphPreview ? { graphPreview } : {}) };
194
+ },
195
+ /**
196
+ * @implements A-SPEC-538.3
197
+ * Read-only: return the provenance ledger's events in time order (optionally narrowed to one
198
+ * spec) so the governance history — approved / unsealed / retired / review-needed … — is legible
199
+ * at a glance without reading raw JSONL. Reuses FileLedgerStore.loadAll(); the ordering/filtering/
200
+ * projection is the pure `timelineFrom`. No writes. An absent ledger is an empty history, not an
201
+ * error.
202
+ */
203
+ async ledger_timeline(a) {
204
+ const dest = context.resolveLedgerRoot(a.root);
205
+ if (!dest.ok)
206
+ return { ok: false, reason: dest.reason };
207
+ const dir = path.join(dest.root, '.ax', 'ledger');
208
+ const events = fs.existsSync(dir) ? new ledger_store_1.FileLedgerStore(dir).loadAll() : [];
209
+ return { ok: true, events: (0, ledger_timeline_1.timelineFrom)(events, a.id) };
210
+ },
211
+ /**
212
+ * @implements A-SPEC-642
213
+ * The one exit from a merged cross-replica double-spend. Under out-of-band authority of kind
214
+ * `ledger-reconcile` for that nonce, append `nonce-double-spend-reconciled` on THIS replica's chain
215
+ * naming the nonce and every consuming event's hash; `verifyAll`, the Stop hook, baseline trust and
216
+ * the ledger-conflict refusals then clear. Nothing is erased: both consumptions stay in their
217
+ * chains, and a later consumption of the same nonce is a new, open double-spend. Refuses to extend
218
+ * a broken chain, because a reconciliation that itself cannot be verified lifts nothing.
219
+ */
220
+ async ledger_reconcile(a) {
221
+ const dest = context.resolveLedgerRoot(a.root);
222
+ if (!dest.ok)
223
+ return { ok: false, reason: dest.reason };
224
+ const nonce = typeof a.nonce === 'string' ? a.nonce.trim() : '';
225
+ if (!nonce)
226
+ return { ok: false, reason: 'ledger_reconcile: `nonce` is required — the nonce input exactly as verifyAll / doubleSpends names it (usually "nonce:<12 hex>").' };
227
+ const dir = path.join(dest.root, '.ax', 'ledger');
228
+ const store = new ledger_store_1.FileLedgerStore(dir);
229
+ const pending = store.doubleSpends();
230
+ const match = pending.find((d) => d.nonce === nonce);
231
+ if (!match) {
232
+ return { ok: true, reconciled: false, nonce, pending: pending.map((d) => d.nonce),
233
+ reason: pending.length === 0 ? 'the merged ledger holds no unreconciled double-spend' : `${nonce} is not an unreconciled double-spend; pending: ${pending.map((d) => d.nonce).join(', ')}` };
234
+ }
235
+ const replicas = [...new Set(match.events.map((e) => e.replicaId ?? 'legacy'))];
236
+ let approval;
237
+ try {
238
+ approval = process.env.HOLMES_APPROVAL ? JSON.parse(process.env.HOLMES_APPROVAL) : undefined;
239
+ }
240
+ catch {
241
+ approval = undefined;
242
+ }
243
+ const now = new Date().toISOString();
244
+ const resolved = context.resolveHandlerApproval(dest.root, approval, { kind: 'ledger-reconcile', target: nonce }, now);
245
+ if (!resolved) {
246
+ return { ok: false, code: 'approval-required',
247
+ reason: `ledger_reconcile requires an out-of-band HOLMES_APPROVAL (or grant) that COVERS kind "ledger-reconcile" for ${nonce} — accepting that ${match.events.length} consumptions on ${replicas.join(', ')} were intended is an operator decision, not the agent's.`
248
+ + context.refusalQueueHint(dest.root, { kind: 'ledger-reconcile', target: nonce, why: `Reconcile the cross-replica double-spend of ${nonce} (${match.events.length} consumptions on ${replicas.join(', ')})` }) };
249
+ }
250
+ const chainBreaks = store.verifyAll().broken.filter((b) => b.kind === 'chain');
251
+ if (chainBreaks.length > 0) {
252
+ return { ok: false, code: 'audit-conflict', reason: 'ledger_reconcile cannot extend a broken chain: ' + chainBreaks.map((b) => `${b.replicaId ?? 'legacy'}: ${b.detail ?? 'broken'}`).join('; ') };
253
+ }
254
+ store.append({
255
+ ts: now, actor: resolved.approval.actor, kind: provenance_ledger_1.DOUBLE_SPEND_RECONCILED,
256
+ summary: `reconciled cross-replica double-spend of ${nonce}: ${match.events.length} consumptions on ${replicas.join(', ')}`.slice(0, 200),
257
+ inputs: [nonce, ...match.events.map((e) => 'event:' + e.hash), ...replicas.map((r) => 'replica:' + r)],
258
+ rationale: typeof a.rationale === 'string' && a.rationale.trim() !== '' ? a.rationale.trim() : resolved.approval.rationale,
259
+ authorization: (0, provenance_chain_1.authorizationRef)(resolved.approval.actor, resolved.approval.token),
260
+ });
261
+ if (resolved.source === 'grant' && resolved.approval.nonce)
262
+ (0, approval_grants_1.consumeGrantFile)(resolved.root ?? dest.root, String(resolved.approval.nonce));
263
+ return { ok: true, reconciled: true, nonce, events: match.events, remaining: store.doubleSpends().map((d) => d.nonce) };
264
+ },
265
+ // @implements A-SPEC-545.3 — "show me the RTM dashboard" launches (idempotently) the server the
266
+ // agent would otherwise start by hand, and returns its URL plus an honesty census of what it shows.
267
+ async rtm_dashboard(a) {
268
+ const dest = context.resolveLedgerRoot(a.root);
269
+ if (!dest.ok)
270
+ return { ok: false, reason: dest.reason };
271
+ try {
272
+ const { startDashboardServer } = await Promise.resolve().then(() => __importStar(require('../../server/dashboard')));
273
+ const { ensureDashboard, dashboardCensus } = await Promise.resolve().then(() => __importStar(require('../../server/dashboard-launcher')));
274
+ const launch = await ensureDashboard(dest.root, a.port, (opts) => startDashboardServer(opts));
275
+ const rtm = await context.fetchJson(`${launch.url}/api/rtm`);
276
+ const heatmap = await context.fetchJson(`${launch.url}/api/rtm/heatmap`);
277
+ return { ok: true, url: launch.url, running: launch.running, census: dashboardCensus(rtm, heatmap) };
278
+ }
279
+ catch (err) {
280
+ return { ok: false, reason: `대시보드 기동 실패: ${err?.message ?? String(err)}` };
281
+ }
282
+ },
283
+ };
284
+ }
@@ -0,0 +1,34 @@
1
+ import { Spec } from '../../spec/spec-parser';
2
+ import { Action } from '../../guardrail/phase';
3
+ export interface PhaseQueryContext {
4
+ listSpecs(): Promise<Spec[]>;
5
+ boundSpecsRoot(): string | undefined;
6
+ projectRootOf(root: string): string;
7
+ }
8
+ export declare function createPhaseQueryHandlers(context: PhaseQueryContext): {
9
+ phase_status(a: {
10
+ root?: string;
11
+ }): Promise<{
12
+ ok: boolean;
13
+ reason: string;
14
+ specs?: undefined;
15
+ note?: undefined;
16
+ } | {
17
+ specs: {
18
+ id: string;
19
+ type: import("../../spec/spec-types").SpecType;
20
+ status: "draft" | "review" | "approved" | "outdated";
21
+ }[];
22
+ note: string;
23
+ ok?: undefined;
24
+ reason?: undefined;
25
+ }>;
26
+ phase_check(a: {
27
+ action?: Action;
28
+ target: string;
29
+ targetAspecId?: string;
30
+ }): Promise<import("../../guardrail/phase").CheckResult | {
31
+ decision: string;
32
+ note: string;
33
+ }>;
34
+ };
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.createPhaseQueryHandlers = createPhaseQueryHandlers;
37
+ // @implements A-SPEC-606, A-SPEC-100.2
38
+ const fs = __importStar(require("node:fs"));
39
+ const path = __importStar(require("node:path"));
40
+ const phase_1 = require("../../guardrail/phase");
41
+ const write_target_1 = require("../../guardrail/write-target");
42
+ function createPhaseQueryHandlers(context) {
43
+ return {
44
+ async phase_status(a) {
45
+ // @implements A-SPEC-189 §6 — `root` was advertised and never read, so a call naming another
46
+ // project (or a path that does not exist) got THIS server's corpus in a success shape and the
47
+ // caller derived its phase from someone else's specs. Same anchor discipline as spec_approve.
48
+ const boundSpecsRoot = typeof a?.root === 'string' && a.root !== '' ? context.boundSpecsRoot() : undefined;
49
+ if (typeof a?.root === 'string' && a.root !== '' && boundSpecsRoot !== undefined) {
50
+ let asked;
51
+ let bound;
52
+ let derivationFoundMarker = false;
53
+ try {
54
+ asked = (0, write_target_1.resolveTarget)(context.projectRootOf(a.root), '.');
55
+ const derived = context.projectRootOf(boundSpecsRoot);
56
+ derivationFoundMarker = fs.existsSync(path.join(derived, '.ax'));
57
+ bound = (0, write_target_1.resolveTarget)(derived, '.');
58
+ }
59
+ catch (e) {
60
+ return { ok: false, reason: `root를 해석할 수 없습니다: ${String(e.message)}` };
61
+ }
62
+ // @implements A-SPEC-189 §7 (round 10) — spec_approve's round-3 lesson, copied here at last:
63
+ // resolveProjectRoot returns its INPUT when no `.ax` ancestor exists, so a custom
64
+ // HOLMES_SPECS outside any project "derived" the spec store itself as the project and this
65
+ // guard then refused the CORRECT root. Only a derivation that actually found a marker
66
+ // speaks for a project.
67
+ if (derivationFoundMarker && asked !== bound) {
68
+ return {
69
+ ok: false,
70
+ reason: `이 서버는 ${bound} 프로젝트에 바인딩되어 있습니다 — 요청한 root ${a.root}는 ${asked} 를 가리킵니다.`
71
+ + ' 다른 프로젝트의 코퍼스를 이 서버가 대신 답하지 않습니다.',
72
+ };
73
+ }
74
+ }
75
+ const specs = await context.listSpecs();
76
+ return { specs: specs.map((s) => ({ id: s.id, type: s.type, status: s.status })), note: 'derive phase via phase_check' };
77
+ },
78
+ async phase_check(a) {
79
+ const specs = await context.listSpecs();
80
+ const action = a.action ?? (0, phase_1.classifyAction)(a.target);
81
+ if (!action)
82
+ return { decision: 'allow', note: 'unclassified target' };
83
+ return (0, phase_1.phaseCheck)(action, { specs, targetAspecId: a.targetAspecId });
84
+ },
85
+ };
86
+ }
@@ -0,0 +1,47 @@
1
+ import { Finding } from '../../review/findings';
2
+ import { Approval } from '../../guardrail/risk-gate';
3
+ import { ElicitOutcome } from '../elicit-approval';
4
+ import { Basis } from '../basis';
5
+ export interface ReviewEvidenceContext {
6
+ foreignRootReason(root?: string): string | null;
7
+ refusal(reason: string): Error;
8
+ boundFindingsLedger(root: string): string;
9
+ boundNonceLedger(): string | null;
10
+ projectRootOf(root: string): string;
11
+ basisFor(root: string | undefined, withDisk?: boolean): Basis;
12
+ hasElicitor(): boolean;
13
+ tryElicit(kind: string, target: string, summary: string): Promise<ElicitOutcome>;
14
+ elicitApproval(reason?: string): Approval;
15
+ resolveHandlerApproval(root: string | undefined, approval: Approval | undefined, action: {
16
+ kind: string;
17
+ target: string;
18
+ }, now: string): {
19
+ approval: Approval;
20
+ source: 'env' | 'grant';
21
+ root?: string;
22
+ } | undefined;
23
+ refusalQueueHint(root: string | undefined, request: {
24
+ kind: string;
25
+ target: string;
26
+ why: string;
27
+ }): string;
28
+ }
29
+ export declare function createReviewEvidenceHandlers(context: ReviewEvidenceContext): {
30
+ review_record(a: {
31
+ root: string;
32
+ findings: Finding[];
33
+ }): Promise<{
34
+ recorded: number;
35
+ }>;
36
+ review_status(a: {
37
+ root: string;
38
+ }): Promise<{
39
+ blocked: boolean;
40
+ open: {
41
+ critical: number;
42
+ important: number;
43
+ minor: number;
44
+ };
45
+ reviewNeeded: import("../../review/review-targets").ReviewNeeded[];
46
+ }>;
47
+ };