@holmes-lab/holmes-kit 0.19.6 → 0.20.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 (128) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +8 -1
  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/gitignore-merge.d.ts +0 -5
  13. package/dist/holmes/cli/gitignore-merge.js +6 -2
  14. package/dist/holmes/cli/index.js +176 -139
  15. package/dist/holmes/cli/playbook-skills.d.ts +2 -22
  16. package/dist/holmes/cli/playbook-skills.js +10 -54
  17. package/dist/holmes/governance/approval-grants.d.ts +22 -0
  18. package/dist/holmes/governance/approval-grants.js +55 -1
  19. package/dist/holmes/governance/approval-queue.d.ts +43 -4
  20. package/dist/holmes/governance/approval-queue.js +86 -12
  21. package/dist/holmes/governance/display-id-aliases.d.ts +29 -0
  22. package/dist/holmes/governance/display-id-aliases.js +86 -0
  23. package/dist/holmes/governance/ledger-store.d.ts +42 -14
  24. package/dist/holmes/governance/ledger-store.js +49 -3
  25. package/dist/holmes/governance/ledger-timeline.d.ts +3 -1
  26. package/dist/holmes/governance/ledger-timeline.js +11 -1
  27. package/dist/holmes/governance/provenance-chain.d.ts +2 -0
  28. package/dist/holmes/governance/provenance-chain.js +13 -2
  29. package/dist/holmes/governance/provenance-ledger.d.ts +28 -0
  30. package/dist/holmes/governance/provenance-ledger.js +69 -16
  31. package/dist/holmes/governance/session-context.d.ts +3 -0
  32. package/dist/holmes/governance/session-context.js +26 -8
  33. package/dist/holmes/guardrail/risk-classifier.js +2 -2
  34. package/dist/holmes/guardrail/write-target.js +2 -1
  35. package/dist/holmes/hooks/pre-tool-use.js +12 -1
  36. package/dist/holmes/hooks/stop.js +14 -3
  37. package/dist/holmes/mcp/handlers/adoption.d.ts +118 -0
  38. package/dist/holmes/mcp/handlers/adoption.js +186 -0
  39. package/dist/holmes/mcp/handlers/entity-integration.d.ts +100 -0
  40. package/dist/holmes/mcp/handlers/entity-integration.js +324 -0
  41. package/dist/holmes/mcp/handlers/entity-renumber.d.ts +137 -0
  42. package/dist/holmes/mcp/handlers/entity-renumber.js +228 -0
  43. package/dist/holmes/mcp/handlers/entity-store.d.ts +78 -0
  44. package/dist/holmes/mcp/handlers/entity-store.js +212 -0
  45. package/dist/holmes/mcp/handlers/graph-operations.d.ts +90 -0
  46. package/dist/holmes/mcp/handlers/graph-operations.js +260 -0
  47. package/dist/holmes/mcp/handlers/maintenance-analysis.d.ts +34 -0
  48. package/dist/holmes/mcp/handlers/maintenance-analysis.js +288 -0
  49. package/dist/holmes/mcp/handlers/maintenance-evidence.d.ts +40 -0
  50. package/dist/holmes/mcp/handlers/maintenance-evidence.js +104 -0
  51. package/dist/holmes/mcp/handlers/operator-inspection.d.ts +167 -0
  52. package/dist/holmes/mcp/handlers/operator-inspection.js +284 -0
  53. package/dist/holmes/mcp/handlers/phase-queries.d.ts +34 -0
  54. package/dist/holmes/mcp/handlers/phase-queries.js +86 -0
  55. package/dist/holmes/mcp/handlers/review-evidence.d.ts +47 -0
  56. package/dist/holmes/mcp/handlers/review-evidence.js +370 -0
  57. package/dist/holmes/mcp/handlers/review-queries.d.ts +68 -0
  58. package/dist/holmes/mcp/handlers/review-queries.js +79 -0
  59. package/dist/holmes/mcp/handlers/risk-assessment.d.ts +25 -0
  60. package/dist/holmes/mcp/handlers/risk-assessment.js +181 -0
  61. package/dist/holmes/mcp/handlers/slice-orchestration.d.ts +55 -0
  62. package/dist/holmes/mcp/handlers/slice-orchestration.js +324 -0
  63. package/dist/holmes/mcp/handlers/spec-approval.d.ts +105 -0
  64. package/dist/holmes/mcp/handlers/spec-approval.js +504 -0
  65. package/dist/holmes/mcp/handlers/spec-authoring.d.ts +57 -0
  66. package/dist/holmes/mcp/handlers/spec-authoring.js +326 -0
  67. package/dist/holmes/mcp/handlers/spec-lifecycle.d.ts +122 -0
  68. package/dist/holmes/mcp/handlers/spec-lifecycle.js +339 -0
  69. package/dist/holmes/mcp/handlers/spec-queries.d.ts +30 -0
  70. package/dist/holmes/mcp/handlers/spec-queries.js +74 -0
  71. package/dist/holmes/mcp/handlers/taint-screening.d.ts +48 -0
  72. package/dist/holmes/mcp/handlers/taint-screening.js +183 -0
  73. package/dist/holmes/mcp/handlers/test-execution.d.ts +63 -0
  74. package/dist/holmes/mcp/handlers/test-execution.js +210 -0
  75. package/dist/holmes/mcp/handlers/workspace-identity.d.ts +30 -0
  76. package/dist/holmes/mcp/handlers/workspace-identity.js +68 -0
  77. package/dist/holmes/mcp/handlers/workspace-queries.d.ts +77 -0
  78. package/dist/holmes/mcp/handlers/workspace-queries.js +193 -0
  79. package/dist/holmes/mcp/handlers.d.ts +653 -424
  80. package/dist/holmes/mcp/handlers.js +267 -3250
  81. package/dist/holmes/mcp/server.js +2 -1
  82. package/dist/holmes/mcp/tool-schemas.js +43 -2
  83. package/dist/holmes/project/execution-context.d.ts +17 -0
  84. package/dist/holmes/project/execution-context.js +120 -0
  85. package/dist/holmes/project/installer-markers.d.ts +34 -0
  86. package/dist/holmes/project/installer-markers.js +65 -0
  87. package/dist/holmes/project/root.d.ts +3 -1
  88. package/dist/holmes/project/root.js +4 -3
  89. package/dist/holmes/project/workspace-identity.d.ts +29 -0
  90. package/dist/holmes/project/workspace-identity.js +181 -0
  91. package/dist/holmes/review/test-outcomes.d.ts +6 -1
  92. package/dist/holmes/review/test-outcomes.js +23 -5
  93. package/dist/holmes/review/test-runner.d.ts +18 -0
  94. package/dist/holmes/review/test-runner.js +132 -4
  95. package/dist/holmes/rtm/graph-store.d.ts +50 -1
  96. package/dist/holmes/rtm/graph-store.js +244 -3
  97. package/dist/holmes/rtm/incremental.d.ts +1 -0
  98. package/dist/holmes/rtm/incremental.js +12 -3
  99. package/dist/holmes/rtm/localize.js +7 -0
  100. package/dist/holmes/rtm/rtm-graph.d.ts +9 -0
  101. package/dist/holmes/rtm/rtm-graph.js +13 -0
  102. package/dist/holmes/semantic/vector-cache.d.ts +16 -1
  103. package/dist/holmes/semantic/vector-cache.js +155 -19
  104. package/dist/holmes/spec/approval-status.d.ts +10 -0
  105. package/dist/holmes/spec/approval-status.js +7 -3
  106. package/dist/holmes/spec/entity-git-snapshot.d.ts +50 -0
  107. package/dist/holmes/spec/entity-git-snapshot.js +276 -0
  108. package/dist/holmes/spec/entity-integration-discharge.d.ts +73 -0
  109. package/dist/holmes/spec/entity-integration-discharge.js +233 -0
  110. package/dist/holmes/spec/entity-integration-transaction.d.ts +179 -0
  111. package/dist/holmes/spec/entity-integration-transaction.js +1221 -0
  112. package/dist/holmes/spec/entity-integration.d.ts +207 -0
  113. package/dist/holmes/spec/entity-integration.js +747 -0
  114. package/dist/holmes/spec/entity-renumber-transaction.d.ts +82 -0
  115. package/dist/holmes/spec/entity-renumber-transaction.js +711 -0
  116. package/dist/holmes/spec/entity-renumber.d.ts +32 -0
  117. package/dist/holmes/spec/entity-renumber.js +156 -0
  118. package/dist/holmes/spec/entity-store.d.ts +135 -0
  119. package/dist/holmes/spec/entity-store.js +1051 -0
  120. package/dist/holmes/spec/entity-transaction.d.ts +89 -0
  121. package/dist/holmes/spec/entity-transaction.js +701 -0
  122. package/dist/holmes/spec/renumber.d.ts +58 -0
  123. package/dist/holmes/spec/renumber.js +200 -1
  124. package/dist/holmes/spec/spec-store.d.ts +3 -2
  125. package/dist/holmes/spec/spec-store.js +23 -2
  126. package/dist/holmes/spec/transition-policy.d.ts +36 -0
  127. package/dist/holmes/spec/transition-policy.js +135 -0
  128. package/package.json +1 -1
@@ -0,0 +1,324 @@
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.renumberedMappingInputs = renumberedMappingInputs;
37
+ exports.integrationSealTransitions = integrationSealTransitions;
38
+ exports.createEntityIntegrationHandlers = createEntityIntegrationHandlers;
39
+ // @implements A-SPEC-629
40
+ // @implements A-SPEC-645
41
+ // @implements A-SPEC-642
42
+ // @implements A-SPEC-639
43
+ // @implements A-SPEC-632
44
+ // @implements A-SPEC-635
45
+ const fs = __importStar(require("node:fs"));
46
+ const path = __importStar(require("node:path"));
47
+ const entity_store_1 = require("../../spec/entity-store");
48
+ const root_1 = require("../../project/root");
49
+ const entity_transaction_1 = require("../../spec/entity-transaction");
50
+ const entity_integration_discharge_1 = require("../../spec/entity-integration-discharge");
51
+ const entity_integration_transaction_1 = require("../../spec/entity-integration-transaction");
52
+ const approval_grants_1 = require("../../governance/approval-grants");
53
+ const approval_queue_1 = require("../../governance/approval-queue");
54
+ const ledger_store_1 = require("../../governance/ledger-store");
55
+ const provenance_chain_1 = require("../../governance/provenance-chain");
56
+ const spec_parser_1 = require("../../spec/spec-parser");
57
+ const spec_digest_1 = require("../../spec/spec-digest");
58
+ /**
59
+ * @implements A-SPEC-635 — the seal transitions an integration performs, derived from its plan: every
60
+ * `unseal` step withdraws a seal, every `approve` step issues one whose digest is read from the
61
+ * published document bytes and must be that document's own `specDigest` (otherwise the seal is not
62
+ * what the transition policy computed → `audit-conflict`). Through the public tool a forged step is
63
+ * already refused `plan-changed` by step revalidation; this check is the audit's own invariant.
64
+ */
65
+ /**
66
+ * @implements A-SPEC-645
67
+ * The `entity:`/`old-id:`/`new-id:` inputs for every mapping step whose record changes its display id,
68
+ * so an integration's renumbers are readable from the ledger like entity_renumber's. Without them the
69
+ * evidence consumers had no way to follow a document an integration renamed.
70
+ */
71
+ function renumberedMappingInputs(steps, renames = []) {
72
+ const out = [];
73
+ for (const r of renames)
74
+ for (const input of ['entity:' + r.entityId, 'old-id:' + r.oldDisplayId, 'new-id:' + r.newDisplayId])
75
+ if (!out.includes(input))
76
+ out.push(input);
77
+ const decode = (bytes) => {
78
+ if (bytes === null)
79
+ return null;
80
+ try {
81
+ const value = JSON.parse(Buffer.from(bytes, 'base64').toString('utf8'));
82
+ return value && typeof value === 'object' ? value : null;
83
+ }
84
+ catch {
85
+ return null;
86
+ }
87
+ };
88
+ for (const step of steps) {
89
+ if (step.phase !== 'mapping' || !step.change.locator.startsWith('.ax/entities/records/'))
90
+ continue;
91
+ const before = decode(step.change.before), after = decode(step.change.after);
92
+ if (!before || !after || typeof before.displayId !== 'string' || typeof after.displayId !== 'string' || before.displayId === after.displayId)
93
+ continue;
94
+ const entity = step.entityId ?? (typeof after.entityId === 'string' ? after.entityId : undefined);
95
+ for (const input of [...(entity ? ['entity:' + entity] : []), 'old-id:' + before.displayId, 'new-id:' + after.displayId])
96
+ if (!out.includes(input))
97
+ out.push(input);
98
+ }
99
+ return out;
100
+ }
101
+ function integrationSealTransitions(operation) {
102
+ const transitions = [];
103
+ for (const step of operation.steps) {
104
+ if (step.phase === 'unseal' && step.specId)
105
+ transitions.push({ kind: 'spec-unsealed', id: step.specId });
106
+ else if (step.phase === 'approve' && step.specId) {
107
+ if (typeof step.change.after !== 'string')
108
+ throw new entity_store_1.EntityStoreError('audit-conflict', 'Approve step ' + step.specId + ' publishes no document.');
109
+ let digest, computed;
110
+ try {
111
+ const parsed = (0, spec_parser_1.parseSpec)(Buffer.from(step.change.after, 'base64').toString('utf8'));
112
+ digest = parsed.frontmatter.approved_digest;
113
+ computed = (0, spec_digest_1.specDigest)(parsed);
114
+ }
115
+ catch {
116
+ throw new entity_store_1.EntityStoreError('audit-conflict', 'Approve step ' + step.specId + ' publishes an unreadable document.');
117
+ }
118
+ if (typeof digest !== 'string' || digest !== computed)
119
+ throw new entity_store_1.EntityStoreError('audit-conflict', 'Approve step ' + step.specId + ' is not sealed by its own digest.');
120
+ transitions.push({ kind: 'spec-approved', id: step.specId, digest });
121
+ }
122
+ }
123
+ return transitions;
124
+ }
125
+ function createEntityIntegrationHandlers(context) {
126
+ return {
127
+ async entity_integrate(a) {
128
+ try {
129
+ if (!context.storeRoot)
130
+ throw new entity_store_1.EntityStoreError('unsupported-store', 'Git integration requires a configured filesystem spec store.');
131
+ const bound = (0, entity_store_1.entityStoreBinding)(context.storeRoot);
132
+ if (a.root !== undefined && fs.realpathSync((0, root_1.resolveProjectRoot)(a.root, { includeGit: false }).root) !== bound.root)
133
+ throw new entity_store_1.EntityStoreError('foreign-workspace', 'The requested workspace differs from the configured store.');
134
+ const recoveryTarget = (plan) => 'entity-store:integrate-recover:' + (0, entity_transaction_1.entityContentVersion)(JSON.stringify(plan));
135
+ const requireStore = (plan) => {
136
+ if (plan.storeLocator !== bound.locator)
137
+ throw new entity_store_1.EntityStoreError('foreign-store', 'Integration plan differs from the configured store.');
138
+ };
139
+ // @implements A-SPEC-642 — an authority-spending operation is refused, naming the exit, while the
140
+ // merged ledger holds an unreconciled double-spend; the audit adapter's `audit-conflict` stays behind.
141
+ if (a.operation === 'apply' || a.operation === 'recover' || a.operation === 'abandon' || a.operation === 'discharge') {
142
+ const conflict = (0, ledger_store_1.ledgerConflictReason)(path.join(bound.root, '.ax', 'ledger'));
143
+ if (conflict)
144
+ throw new entity_store_1.EntityStoreError('ledger-conflict', conflict);
145
+ }
146
+ // @implements A-SPEC-632 — replica-local discharge of a completed operation's derived obligations.
147
+ if (a.operation === 'obligations')
148
+ return { ok: true, obligations: (0, entity_integration_discharge_1.inspectDerivedObligations)(bound.root) };
149
+ if (a.operation === 'discharge') {
150
+ if (typeof a.operationId !== 'string')
151
+ throw new entity_store_1.EntityStoreError('invalid-request', 'Provide the completed integration operationId.');
152
+ if (!context.rebuildFor)
153
+ throw new entity_store_1.EntityStoreError('unsupported-store', 'Discharge requires graph and semantic rebuild wiring.');
154
+ return await (0, entity_integration_discharge_1.dischargeDerivedObligations)(bound.root, a.operationId, context.rebuildFor(bound.root));
155
+ }
156
+ if (a.operation === 'recovery-plan') {
157
+ if (typeof a.operationId !== 'string')
158
+ throw new entity_store_1.EntityStoreError('invalid-request', 'Provide the integration operationId.');
159
+ const plan = (0, entity_integration_transaction_1.inspectEntityIntegrationRecovery)(bound.root, a.operationId);
160
+ requireStore(plan.plan);
161
+ return { ok: true, plan, target: recoveryTarget(plan), executable: plan.conflicts.length === 0 };
162
+ }
163
+ if (a.operation === 'abandon') {
164
+ // @implements A-SPEC-639 — an authorized exit for a pending operation a third state blocked.
165
+ const id = a.plan?.operationId;
166
+ if (typeof id !== 'string')
167
+ throw new entity_store_1.EntityStoreError('invalid-request', 'Provide the complete inspected recovery plan to abandon.');
168
+ const recovery = (0, entity_integration_transaction_1.validateEntityIntegrationRecoveryRequest)(bound.root, a.plan);
169
+ requireStore(recovery.plan);
170
+ const target = 'entity-store:integrate-abandon:' + (0, entity_transaction_1.entityContentVersion)(JSON.stringify(a.plan));
171
+ let env;
172
+ try {
173
+ env = process.env.HOLMES_APPROVAL ? JSON.parse(process.env.HOLMES_APPROVAL) : undefined;
174
+ }
175
+ catch {
176
+ env = undefined;
177
+ }
178
+ const authorities = new Set();
179
+ const requireAuthority = () => {
180
+ const action = { kind: 'config-write', target };
181
+ const resolved = (0, approval_grants_1.resolveApproval)(bound.root, env, action, new Date().toISOString());
182
+ if (!resolved)
183
+ throw new entity_store_1.EntityStoreError('approval-required', 'Current authority is required to abandon this integration.' + (0, approval_queue_1.queueHint)(bound.root, { ...action, why: 'Abandon the blocked integration: revert its own publications, preserve foreign bytes, release ownership.' }));
184
+ authorities.add((0, provenance_chain_1.authorizationRef)(resolved.approval.actor, resolved.approval.token));
185
+ return resolved;
186
+ };
187
+ requireAuthority();
188
+ const audit = (operation, outcome) => {
189
+ const authority = requireAuthority(), ledger = new ledger_store_1.FileLedgerStore(path.join(bound.root, '.ax/ledger'));
190
+ if (!ledger.verifyAll().ok)
191
+ throw new entity_store_1.EntityStoreError('audit-conflict', 'Abandonment audit cannot extend an invalid ledger.');
192
+ const ref = 'entity-operation:' + operation.operationId;
193
+ if (ledger.loadAll().some(event => event.kind === 'entity-integration-abandoned' && event.inputs?.includes(ref)))
194
+ return;
195
+ ledger.append({ ts: new Date().toISOString(), actor: authority.approval.actor, kind: 'entity-integration-abandoned', summary: 'Abandoned Git entity integration ' + operation.operationId,
196
+ inputs: [ref, (0, entity_integration_transaction_1.entityIntegrationTarget)(operation), 'workspace:' + operation.workspaceId, 'store:' + operation.storeId, recovery.journalVersion, target,
197
+ ...outcome.restored.map(locator => 'restored:' + locator), ...outcome.preserved.map(locator => 'preserved:' + locator), ...authorities],
198
+ rationale: authority.approval.rationale, authorization: (0, provenance_chain_1.authorizationRef)(authority.approval.actor, authority.approval.token) });
199
+ };
200
+ return await (0, entity_integration_transaction_1.abandonEntityIntegration)(bound.root, recovery, requireAuthority, audit);
201
+ }
202
+ if (a.operation === 'apply' || a.operation === 'recover') {
203
+ let recovery;
204
+ if (a.operation === 'recover') {
205
+ const id = a.plan?.operationId;
206
+ if (typeof id !== 'string')
207
+ throw new entity_store_1.EntityStoreError('invalid-request', 'Provide the complete inspected recovery plan.');
208
+ recovery = (0, entity_integration_transaction_1.validateEntityIntegrationRecoveryRequest)(bound.root, a.plan);
209
+ }
210
+ const plan = recovery ? recovery.plan : (0, entity_integration_transaction_1.validateEntityIntegrationPlan)(a.plan);
211
+ requireStore(plan);
212
+ const target = recovery ? recoveryTarget(recovery) : (0, entity_integration_transaction_1.entityIntegrationTarget)(plan);
213
+ let env;
214
+ try {
215
+ env = process.env.HOLMES_APPROVAL ? JSON.parse(process.env.HOLMES_APPROVAL) : undefined;
216
+ }
217
+ catch {
218
+ env = undefined;
219
+ }
220
+ const grants = new Set(), authorities = new Set(), usedGrantRefs = new Set();
221
+ const grantRef = (approval) => 'grant-use:' + (0, entity_transaction_1.entityContentVersion)(JSON.stringify({ actor: approval.actor, token: approval.token, nonce: approval.nonce, scope: approval.scope, expires: approval.expires }));
222
+ const requireAuthority = (specId) => {
223
+ const action = { kind: specId ? 'spec-approve' : 'config-write', target: specId ?? target };
224
+ const resolved = (0, approval_grants_1.resolveApproval)(bound.root, env, action, new Date().toISOString());
225
+ if (!resolved)
226
+ throw new entity_store_1.EntityStoreError(specId ? 'spec-approval-required' : 'approval-required', 'Current authority is required for this integration.' + (0, approval_queue_1.queueHint)(bound.root, { ...action, why: 'Integrate the exact Git inputs while preserving entity identity and official seal transitions.' }));
227
+ if (resolved.source === 'grant' && resolved.approval.nonce)
228
+ grants.add(resolved.approval.nonce);
229
+ if (resolved.source === 'grant')
230
+ usedGrantRefs.add(grantRef(resolved.approval));
231
+ authorities.add((0, provenance_chain_1.authorizationRef)(resolved.approval.actor, resolved.approval.token));
232
+ return resolved;
233
+ };
234
+ const bindingFor = (operation) => ['entity-operation:' + operation.operationId, (0, entity_integration_transaction_1.entityIntegrationTarget)(operation), 'workspace:' + operation.workspaceId, 'store:' + operation.storeId,
235
+ ...['base', 'left', 'right'].map(side => 'git-' + side + ':' + operation.commits[side])];
236
+ const priorGrantUses = (ledger, operation) => {
237
+ if (!ledger.verifyAll().ok)
238
+ throw new entity_store_1.EntityStoreError('audit-conflict', 'Integration cannot rely on an invalid ledger.');
239
+ const binding = bindingFor(operation), refs = new Set();
240
+ for (const event of ledger.loadAll().filter(event => event.kind === 'entity-integration-grant-used' && event.inputs?.includes(binding[0]))) {
241
+ const usages = (event.inputs ?? []).filter(ref => ref.startsWith('grant-use:'));
242
+ if (binding.some(ref => !event.inputs?.includes(ref)) || usages.length === 0 || usages.some(ref => !/^grant-use:sha256:[0-9a-f]{64}$/.test(ref) || refs.has(ref)))
243
+ throw new entity_store_1.EntityStoreError('audit-conflict', 'Integration grant usage has conflicting bindings.');
244
+ for (const ref of usages)
245
+ refs.add(ref);
246
+ }
247
+ return refs;
248
+ };
249
+ const recordGrantUse = (operation) => {
250
+ const authority = requireAuthority(), ledger = new ledger_store_1.FileLedgerStore(path.join(bound.root, '.ax/ledger'));
251
+ const prior = priorGrantUses(ledger, operation), additional = [...usedGrantRefs].filter(ref => !prior.has(ref));
252
+ for (const ref of prior)
253
+ usedGrantRefs.add(ref);
254
+ if (!additional.length)
255
+ return;
256
+ ledger.append({ ts: new Date().toISOString(), actor: authority.approval.actor, kind: 'entity-integration-grant-used', summary: 'Recorded grant usage for integration ' + operation.operationId,
257
+ inputs: [...bindingFor(operation), target, ...additional, ...authorities], rationale: authority.approval.rationale, authorization: (0, provenance_chain_1.authorizationRef)(authority.approval.actor, authority.approval.token) });
258
+ };
259
+ const audit = (operation, verifyOnly = false, renumbered = []) => {
260
+ const authority = requireAuthority(), ledger = new ledger_store_1.FileLedgerStore(path.join(bound.root, '.ax/ledger'));
261
+ const binding = bindingFor(operation);
262
+ for (const ref of priorGrantUses(ledger, operation))
263
+ usedGrantRefs.add(ref);
264
+ const events = ledger.loadAll(), existing = events.filter(event => event.kind === 'entity-integrated' && event.inputs?.includes(binding[0]));
265
+ if (existing.length) {
266
+ if (existing.length !== 1 || binding.some(ref => !existing[0].inputs?.includes(ref)))
267
+ throw new entity_store_1.EntityStoreError('audit-conflict', 'Integration completion audit has conflicting bindings.');
268
+ for (const ref of existing[0].inputs ?? [])
269
+ if (ref.startsWith('grant-use:'))
270
+ usedGrantRefs.add(ref);
271
+ // Retain the original completion event, but persist any new authority use
272
+ // before recovery/verification consumes that grant file.
273
+ recordGrantUse(operation);
274
+ return;
275
+ }
276
+ if (verifyOnly)
277
+ throw new entity_store_1.EntityStoreError('audit-conflict', 'Completion receipt has no matching integration audit.');
278
+ recordGrantUse(operation);
279
+ // @implements A-SPEC-635 — the seal history the official acts would have written, attributed
280
+ // to the per-spec authority and bound to this operation; skipped when already present so
281
+ // recovery after an interrupted audit appends each exactly once.
282
+ for (const transition of integrationSealTransitions(operation)) {
283
+ if (events.some(event => event.kind === transition.kind && event.inputs?.includes(transition.id) && event.inputs?.includes(binding[0])))
284
+ continue;
285
+ const sealAuthority = requireAuthority(transition.id);
286
+ ledger.append({ ts: new Date().toISOString(), actor: sealAuthority.approval.actor, kind: transition.kind,
287
+ summary: transition.kind === 'spec-unsealed' ? 'unsealed ' + transition.id + ' (integration ' + operation.operationId + ')' : 'approved ' + transition.id + ' sealing ' + transition.digest,
288
+ inputs: transition.kind === 'spec-unsealed' ? [transition.id, binding[0]] : [transition.id, transition.digest, binding[0]],
289
+ rationale: sealAuthority.approval.rationale, authorization: (0, provenance_chain_1.authorizationRef)(sealAuthority.approval.actor, sealAuthority.approval.token) });
290
+ }
291
+ ledger.append({ ts: new Date().toISOString(), actor: authority.approval.actor, kind: 'entity-integrated', summary: 'Completed Git entity integration ' + operation.operationId,
292
+ inputs: [...binding, target, ...new Set(operation.steps.flatMap(step => [
293
+ ...(step.entityId ? ['entity:' + step.entityId] : []), (step.change.after === null ? 'deleted-path:' : 'changed-path:') + step.change.locator
294
+ ])),
295
+ ...renumberedMappingInputs(operation.steps, renumbered).filter(input => !input.startsWith('entity:')), // A-SPEC-645
296
+ 'derived-obligations:' + operation.operationId, ...authorities, ...usedGrantRefs],
297
+ rationale: authority.approval.rationale, authorization: (0, provenance_chain_1.authorizationRef)(authority.approval.actor, authority.approval.token) });
298
+ };
299
+ requireAuthority();
300
+ const result = recovery ? await (0, entity_integration_transaction_1.recoverEntityIntegration)(bound.root, recovery, requireAuthority, audit, recordGrantUse)
301
+ : await (0, entity_integration_transaction_1.applyEntityIntegration)(bound.root, plan, requireAuthority, audit, recordGrantUse);
302
+ for (const grant of (0, approval_grants_1.readGrants)(bound.root).grants)
303
+ if (grant.nonce && usedGrantRefs.has(grantRef(grant)))
304
+ grants.add(grant.nonce);
305
+ for (const nonce of grants)
306
+ (0, approval_grants_1.consumeGrantFile)(bound.root, nonce);
307
+ return result;
308
+ }
309
+ if (a.operation !== undefined && a.operation !== 'plan')
310
+ throw new entity_store_1.EntityStoreError('unsupported-operation', 'Use plan, apply, recovery-plan, recover, abandon, discharge or obligations.');
311
+ if (typeof a.base !== 'string' || typeof a.left !== 'string' || typeof a.right !== 'string')
312
+ throw new entity_store_1.EntityStoreError('invalid-request', 'Provide base, left and right Git revisions.');
313
+ const reviewed = await (0, entity_integration_transaction_1.reviewEntityIntegration)(bound.root, { base: a.base, left: a.left, right: a.right }, bound.locator);
314
+ return { ok: true, executable: !!reviewed.plan, ...reviewed, ...(reviewed.plan ? { target: (0, entity_integration_transaction_1.entityIntegrationTarget)(reviewed.plan) } : {}),
315
+ blockers: [...reviewed.preview.conflicts, ...reviewed.preview.pending.map(item => item.file + ': ' + item.reason)] };
316
+ }
317
+ catch (error) {
318
+ if (error instanceof entity_store_1.EntityStoreError)
319
+ return { ok: false, code: error.code, reason: error.message };
320
+ throw error;
321
+ }
322
+ },
323
+ };
324
+ }
@@ -0,0 +1,137 @@
1
+ import { EntityRenumberChange } from '../../spec/entity-renumber';
2
+ import { EntityRenumberPlan } from '../../spec/entity-renumber-transaction';
3
+ export declare function createEntityRenumberHandlers(context: {
4
+ storeRoot?: string;
5
+ }): {
6
+ entity_renumber(a: {
7
+ root?: string;
8
+ operation?: string;
9
+ changes?: EntityRenumberChange[];
10
+ plan?: unknown;
11
+ }): Promise<{
12
+ ok: true;
13
+ state: "completed";
14
+ operationId: string;
15
+ } | {
16
+ restored: string[];
17
+ untouched: string[];
18
+ preserved: string[];
19
+ ok: true;
20
+ state: "abandoned";
21
+ operationId: string;
22
+ } | {
23
+ ok: boolean;
24
+ plan: import("../../spec/entity-renumber-transaction").EntityRenumberRecoveryPlan;
25
+ code?: undefined;
26
+ reason?: undefined;
27
+ conflicts?: undefined;
28
+ ids?: undefined;
29
+ executable?: undefined;
30
+ blockers?: undefined;
31
+ inputVersion?: undefined;
32
+ preview?: undefined;
33
+ recoveryAvailable?: undefined;
34
+ } | {
35
+ ok: boolean;
36
+ code: string;
37
+ reason: string;
38
+ plan?: undefined;
39
+ conflicts?: undefined;
40
+ ids?: undefined;
41
+ executable?: undefined;
42
+ blockers?: undefined;
43
+ inputVersion?: undefined;
44
+ preview?: undefined;
45
+ recoveryAvailable?: undefined;
46
+ } | {
47
+ ok: boolean;
48
+ code: string;
49
+ conflicts: string[];
50
+ plan?: undefined;
51
+ reason?: undefined;
52
+ ids?: undefined;
53
+ executable?: undefined;
54
+ blockers?: undefined;
55
+ inputVersion?: undefined;
56
+ preview?: undefined;
57
+ recoveryAvailable?: undefined;
58
+ } | {
59
+ ok: boolean;
60
+ code: string;
61
+ ids: string[];
62
+ reason: string;
63
+ plan?: undefined;
64
+ conflicts?: undefined;
65
+ executable?: undefined;
66
+ blockers?: undefined;
67
+ inputVersion?: undefined;
68
+ preview?: undefined;
69
+ recoveryAvailable?: undefined;
70
+ } | {
71
+ ok: boolean;
72
+ executable: boolean;
73
+ blockers: string[];
74
+ inputVersion: string;
75
+ preview: {
76
+ proposal: import("../../spec/entity-renumber").EntityRenumberProposal;
77
+ sources: import("../../spec/renumber").SourceRenumberInventory;
78
+ workspaceId: string;
79
+ storeId: string;
80
+ storeLocator: string;
81
+ registrationVersion: string;
82
+ entries: {
83
+ entityId: string;
84
+ documentLocator: string;
85
+ text: string;
86
+ mapping: string;
87
+ mappingVersion: string;
88
+ }[];
89
+ referenceNamespaces: {
90
+ entityId: string;
91
+ displayId: string;
92
+ storeId: string;
93
+ workspaceId: string;
94
+ }[];
95
+ schema: string;
96
+ };
97
+ plan?: undefined;
98
+ code?: undefined;
99
+ reason?: undefined;
100
+ conflicts?: undefined;
101
+ ids?: undefined;
102
+ recoveryAvailable?: undefined;
103
+ } | {
104
+ ok: boolean;
105
+ executable: boolean;
106
+ plan: EntityRenumberPlan;
107
+ blockers: never[];
108
+ recoveryAvailable: boolean;
109
+ inputVersion: string;
110
+ preview: {
111
+ proposal: import("../../spec/entity-renumber").EntityRenumberProposal;
112
+ sources: import("../../spec/renumber").SourceRenumberInventory;
113
+ workspaceId: string;
114
+ storeId: string;
115
+ storeLocator: string;
116
+ registrationVersion: string;
117
+ entries: {
118
+ entityId: string;
119
+ documentLocator: string;
120
+ text: string;
121
+ mapping: string;
122
+ mappingVersion: string;
123
+ }[];
124
+ referenceNamespaces: {
125
+ entityId: string;
126
+ displayId: string;
127
+ storeId: string;
128
+ workspaceId: string;
129
+ }[];
130
+ schema: string;
131
+ };
132
+ code?: undefined;
133
+ reason?: undefined;
134
+ conflicts?: undefined;
135
+ ids?: undefined;
136
+ }>;
137
+ };