@holmes-lab/holmes-kit 0.19.5 → 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.
- package/CHANGELOG.md +135 -0
- package/README.md +17 -1
- package/dist/.build-id +1 -1
- package/dist/holmes/cli/approve-status.d.ts +24 -0
- package/dist/holmes/cli/approve-status.js +166 -0
- package/dist/holmes/cli/approve-watch.d.ts +6 -0
- package/dist/holmes/cli/approve-watch.js +6 -0
- package/dist/holmes/cli/approve.d.ts +27 -0
- package/dist/holmes/cli/approve.js +68 -7
- package/dist/holmes/cli/cli-execution.d.ts +2 -0
- package/dist/holmes/cli/cli-execution.js +24 -0
- package/dist/holmes/cli/gitignore-merge.d.ts +0 -5
- package/dist/holmes/cli/gitignore-merge.js +6 -2
- package/dist/holmes/cli/index.js +176 -139
- package/dist/holmes/cli/playbook-skills.d.ts +2 -22
- package/dist/holmes/cli/playbook-skills.js +10 -54
- package/dist/holmes/governance/approval-grants.d.ts +22 -0
- package/dist/holmes/governance/approval-grants.js +55 -1
- package/dist/holmes/governance/approval-queue.d.ts +43 -4
- package/dist/holmes/governance/approval-queue.js +86 -12
- package/dist/holmes/governance/display-id-aliases.d.ts +29 -0
- package/dist/holmes/governance/display-id-aliases.js +86 -0
- package/dist/holmes/governance/ledger-store.d.ts +42 -14
- package/dist/holmes/governance/ledger-store.js +49 -3
- package/dist/holmes/governance/ledger-timeline.d.ts +3 -1
- package/dist/holmes/governance/ledger-timeline.js +11 -1
- package/dist/holmes/governance/provenance-chain.d.ts +2 -0
- package/dist/holmes/governance/provenance-chain.js +13 -2
- package/dist/holmes/governance/provenance-ledger.d.ts +28 -0
- package/dist/holmes/governance/provenance-ledger.js +69 -16
- package/dist/holmes/governance/session-context.d.ts +3 -0
- package/dist/holmes/governance/session-context.js +26 -8
- package/dist/holmes/guardrail/risk-classifier.js +2 -2
- package/dist/holmes/guardrail/write-target.js +2 -1
- package/dist/holmes/hooks/pre-tool-use.js +12 -1
- package/dist/holmes/hooks/stop.js +14 -3
- package/dist/holmes/mcp/handlers/adoption.d.ts +118 -0
- package/dist/holmes/mcp/handlers/adoption.js +186 -0
- package/dist/holmes/mcp/handlers/entity-integration.d.ts +100 -0
- package/dist/holmes/mcp/handlers/entity-integration.js +324 -0
- package/dist/holmes/mcp/handlers/entity-renumber.d.ts +137 -0
- package/dist/holmes/mcp/handlers/entity-renumber.js +228 -0
- package/dist/holmes/mcp/handlers/entity-store.d.ts +78 -0
- package/dist/holmes/mcp/handlers/entity-store.js +212 -0
- package/dist/holmes/mcp/handlers/graph-operations.d.ts +90 -0
- package/dist/holmes/mcp/handlers/graph-operations.js +260 -0
- package/dist/holmes/mcp/handlers/maintenance-analysis.d.ts +34 -0
- package/dist/holmes/mcp/handlers/maintenance-analysis.js +288 -0
- package/dist/holmes/mcp/handlers/maintenance-evidence.d.ts +40 -0
- package/dist/holmes/mcp/handlers/maintenance-evidence.js +104 -0
- package/dist/holmes/mcp/handlers/operator-inspection.d.ts +167 -0
- package/dist/holmes/mcp/handlers/operator-inspection.js +284 -0
- package/dist/holmes/mcp/handlers/phase-queries.d.ts +34 -0
- package/dist/holmes/mcp/handlers/phase-queries.js +86 -0
- package/dist/holmes/mcp/handlers/review-evidence.d.ts +47 -0
- package/dist/holmes/mcp/handlers/review-evidence.js +370 -0
- package/dist/holmes/mcp/handlers/review-queries.d.ts +68 -0
- package/dist/holmes/mcp/handlers/review-queries.js +79 -0
- package/dist/holmes/mcp/handlers/risk-assessment.d.ts +25 -0
- package/dist/holmes/mcp/handlers/risk-assessment.js +181 -0
- package/dist/holmes/mcp/handlers/slice-orchestration.d.ts +55 -0
- package/dist/holmes/mcp/handlers/slice-orchestration.js +324 -0
- package/dist/holmes/mcp/handlers/spec-approval.d.ts +105 -0
- package/dist/holmes/mcp/handlers/spec-approval.js +504 -0
- package/dist/holmes/mcp/handlers/spec-authoring.d.ts +57 -0
- package/dist/holmes/mcp/handlers/spec-authoring.js +326 -0
- package/dist/holmes/mcp/handlers/spec-lifecycle.d.ts +122 -0
- package/dist/holmes/mcp/handlers/spec-lifecycle.js +339 -0
- package/dist/holmes/mcp/handlers/spec-queries.d.ts +30 -0
- package/dist/holmes/mcp/handlers/spec-queries.js +74 -0
- package/dist/holmes/mcp/handlers/taint-screening.d.ts +48 -0
- package/dist/holmes/mcp/handlers/taint-screening.js +183 -0
- package/dist/holmes/mcp/handlers/test-execution.d.ts +63 -0
- package/dist/holmes/mcp/handlers/test-execution.js +210 -0
- package/dist/holmes/mcp/handlers/workspace-identity.d.ts +30 -0
- package/dist/holmes/mcp/handlers/workspace-identity.js +68 -0
- package/dist/holmes/mcp/handlers/workspace-queries.d.ts +77 -0
- package/dist/holmes/mcp/handlers/workspace-queries.js +193 -0
- package/dist/holmes/mcp/handlers.d.ts +653 -424
- package/dist/holmes/mcp/handlers.js +267 -3250
- package/dist/holmes/mcp/server.js +2 -1
- package/dist/holmes/mcp/tool-schemas.js +43 -2
- package/dist/holmes/project/execution-context.d.ts +17 -0
- package/dist/holmes/project/execution-context.js +120 -0
- package/dist/holmes/project/installer-markers.d.ts +34 -0
- package/dist/holmes/project/installer-markers.js +65 -0
- package/dist/holmes/project/root.d.ts +3 -1
- package/dist/holmes/project/root.js +4 -3
- package/dist/holmes/project/workspace-identity.d.ts +29 -0
- package/dist/holmes/project/workspace-identity.js +181 -0
- package/dist/holmes/review/test-outcomes.d.ts +6 -1
- package/dist/holmes/review/test-outcomes.js +23 -5
- package/dist/holmes/review/test-runner.d.ts +18 -0
- package/dist/holmes/review/test-runner.js +132 -4
- package/dist/holmes/rtm/graph-store.d.ts +50 -1
- package/dist/holmes/rtm/graph-store.js +244 -3
- package/dist/holmes/rtm/incremental.d.ts +1 -0
- package/dist/holmes/rtm/incremental.js +12 -3
- package/dist/holmes/rtm/localize.js +7 -0
- package/dist/holmes/rtm/rtm-graph.d.ts +9 -0
- package/dist/holmes/rtm/rtm-graph.js +13 -0
- package/dist/holmes/semantic/vector-cache.d.ts +16 -1
- package/dist/holmes/semantic/vector-cache.js +155 -19
- package/dist/holmes/spec/approval-status.d.ts +10 -0
- package/dist/holmes/spec/approval-status.js +7 -3
- package/dist/holmes/spec/entity-git-snapshot.d.ts +50 -0
- package/dist/holmes/spec/entity-git-snapshot.js +276 -0
- package/dist/holmes/spec/entity-integration-discharge.d.ts +73 -0
- package/dist/holmes/spec/entity-integration-discharge.js +233 -0
- package/dist/holmes/spec/entity-integration-transaction.d.ts +179 -0
- package/dist/holmes/spec/entity-integration-transaction.js +1221 -0
- package/dist/holmes/spec/entity-integration.d.ts +207 -0
- package/dist/holmes/spec/entity-integration.js +747 -0
- package/dist/holmes/spec/entity-renumber-transaction.d.ts +82 -0
- package/dist/holmes/spec/entity-renumber-transaction.js +711 -0
- package/dist/holmes/spec/entity-renumber.d.ts +32 -0
- package/dist/holmes/spec/entity-renumber.js +156 -0
- package/dist/holmes/spec/entity-store.d.ts +135 -0
- package/dist/holmes/spec/entity-store.js +1051 -0
- package/dist/holmes/spec/entity-transaction.d.ts +89 -0
- package/dist/holmes/spec/entity-transaction.js +701 -0
- package/dist/holmes/spec/renumber.d.ts +58 -0
- package/dist/holmes/spec/renumber.js +200 -1
- package/dist/holmes/spec/spec-store.d.ts +3 -2
- package/dist/holmes/spec/spec-store.js +23 -2
- package/dist/holmes/spec/transition-policy.d.ts +36 -0
- package/dist/holmes/spec/transition-policy.js +135 -0
- package/package.json +1 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { GraphBasis } from '../rtm/graph-store';
|
|
2
|
+
export declare const DISCHARGE_SCHEMA = "holmes-integration-derived-discharge/1";
|
|
3
|
+
export interface DerivedDischargeRecord {
|
|
4
|
+
schema: typeof DISCHARGE_SCHEMA;
|
|
5
|
+
operationId: string;
|
|
6
|
+
workspaceId: string;
|
|
7
|
+
storeId: string;
|
|
8
|
+
/** `entityContentVersion` of the exact `derived.json` bytes this record discharges. */
|
|
9
|
+
obligationVersion: string;
|
|
10
|
+
/** The plan's destination commit — what the integration was applied onto. */
|
|
11
|
+
destinationHead: string;
|
|
12
|
+
graph: {
|
|
13
|
+
basis: GraphBasis;
|
|
14
|
+
nodes: number;
|
|
15
|
+
edges: number;
|
|
16
|
+
};
|
|
17
|
+
semantic: {
|
|
18
|
+
status: 'discharged';
|
|
19
|
+
tier: string;
|
|
20
|
+
computed: number;
|
|
21
|
+
cached: number;
|
|
22
|
+
} | {
|
|
23
|
+
status: 'not-applicable';
|
|
24
|
+
tier: 'none';
|
|
25
|
+
};
|
|
26
|
+
/** Every changed locator of the plan, classified against the current working tree. */
|
|
27
|
+
changedPaths: {
|
|
28
|
+
intact: string[];
|
|
29
|
+
drifted: string[];
|
|
30
|
+
missing: string[];
|
|
31
|
+
};
|
|
32
|
+
dischargedAt: string;
|
|
33
|
+
}
|
|
34
|
+
export interface DerivedObligationStatus {
|
|
35
|
+
operationId: string;
|
|
36
|
+
obligationVersion?: string;
|
|
37
|
+
state: 'pending' | 'discharged' | 'invalid';
|
|
38
|
+
reason?: string;
|
|
39
|
+
dischargedAt?: string;
|
|
40
|
+
graphBasisCommit?: string;
|
|
41
|
+
}
|
|
42
|
+
/** The rebuild paths a discharge runs — injected so this module never reaches into the MCP layer. */
|
|
43
|
+
export interface DerivedRebuild {
|
|
44
|
+
publishGraph(): Promise<{
|
|
45
|
+
basis: GraphBasis;
|
|
46
|
+
nodes: number;
|
|
47
|
+
edges: number;
|
|
48
|
+
}>;
|
|
49
|
+
warmSemantic(): Promise<{
|
|
50
|
+
status: 'discharged';
|
|
51
|
+
tier: string;
|
|
52
|
+
computed: number;
|
|
53
|
+
cached: number;
|
|
54
|
+
} | {
|
|
55
|
+
status: 'not-applicable';
|
|
56
|
+
tier: 'none';
|
|
57
|
+
} | {
|
|
58
|
+
status: 'unavailable';
|
|
59
|
+
tier: string;
|
|
60
|
+
}>;
|
|
61
|
+
}
|
|
62
|
+
/** Read-only: every completed operation on this replica that carries derived obligations, and where each stands. */
|
|
63
|
+
export declare function inspectDerivedObligations(root: string): DerivedObligationStatus[];
|
|
64
|
+
/**
|
|
65
|
+
* Discharge one completed operation's obligations on this replica. Verifies completion and the exact
|
|
66
|
+
* obligation bytes, rebuilds through the injected paths, verifies the changed paths and publishes one
|
|
67
|
+
* immutable record; a second writer (or a second call) returns the existing record instead.
|
|
68
|
+
*/
|
|
69
|
+
export declare function dischargeDerivedObligations(root: string, operationId: string, rebuild: DerivedRebuild): Promise<{
|
|
70
|
+
ok: true;
|
|
71
|
+
record: DerivedDischargeRecord;
|
|
72
|
+
alreadyDischarged: boolean;
|
|
73
|
+
}>;
|
|
@@ -0,0 +1,233 @@
|
|
|
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.DISCHARGE_SCHEMA = void 0;
|
|
37
|
+
exports.inspectDerivedObligations = inspectDerivedObligations;
|
|
38
|
+
exports.dischargeDerivedObligations = dischargeDerivedObligations;
|
|
39
|
+
// @implements A-SPEC-632
|
|
40
|
+
/**
|
|
41
|
+
* Discharge of a completed integration's derived-state obligations.
|
|
42
|
+
*
|
|
43
|
+
* `applyEntityIntegration` ends by writing `derived.json` (graph: pending, semantic: pending) into
|
|
44
|
+
* the operation's completed directory and the completed retry verifies those bytes exactly, so the
|
|
45
|
+
* obligation itself is immutable history. This module adds the second, immutable half:
|
|
46
|
+
* `discharge.json` states what was rebuilt, from which basis, and whether the tree still holds the
|
|
47
|
+
* integration's bytes. The rebuild is not new machinery — the caller injects the graph publication
|
|
48
|
+
* (REQ-631) and the semantic warming path reindex already uses — so discharge is verification plus
|
|
49
|
+
* binding. Derived state is replica-local (`.ax/state`), therefore so is the record: nothing under
|
|
50
|
+
* `.ax/entities`, the specs, the shared ledger or Git is touched.
|
|
51
|
+
*/
|
|
52
|
+
const fs = __importStar(require("node:fs"));
|
|
53
|
+
const path = __importStar(require("node:path"));
|
|
54
|
+
const node_util_1 = require("node:util");
|
|
55
|
+
const entity_transaction_1 = require("./entity-transaction");
|
|
56
|
+
const entity_integration_transaction_1 = require("./entity-integration-transaction");
|
|
57
|
+
exports.DISCHARGE_SCHEMA = 'holmes-integration-derived-discharge/1';
|
|
58
|
+
const OBLIGATION_SCHEMA = 'holmes-integration-derived-obligations/1';
|
|
59
|
+
const COMPLETED = '.ax/state/entity-completed';
|
|
60
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
61
|
+
const RECORD_KEYS = ['schema', 'operationId', 'workspaceId', 'storeId', 'obligationVersion', 'destinationHead', 'graph', 'semantic', 'changedPaths', 'dischargedAt'];
|
|
62
|
+
/** The obligation `applyEntityIntegration` wrote for this plan — the same computation the completed retry verifies. */
|
|
63
|
+
function expectedObligation(plan) {
|
|
64
|
+
return {
|
|
65
|
+
schema: OBLIGATION_SCHEMA, operationId: plan.operationId, workspaceId: plan.workspaceId, storeId: plan.storeId,
|
|
66
|
+
target: (0, entity_integration_transaction_1.entityIntegrationTarget)(plan), graph: 'pending', semantic: 'pending',
|
|
67
|
+
changedPaths: [...new Set(plan.steps.map((step) => step.change.locator))].sort(),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function parseRecord(bytes) {
|
|
71
|
+
let value;
|
|
72
|
+
try {
|
|
73
|
+
value = JSON.parse(bytes.toString('utf8'));
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
throw new entity_transaction_1.EntityStoreError('discharge-conflict', 'The discharge record is not valid JSON.');
|
|
77
|
+
}
|
|
78
|
+
const record = value;
|
|
79
|
+
const shaped = !!record && typeof record === 'object' && !Array.isArray(record)
|
|
80
|
+
&& (0, node_util_1.isDeepStrictEqual)(Object.keys(record).sort(), [...RECORD_KEYS].sort())
|
|
81
|
+
&& record.schema === exports.DISCHARGE_SCHEMA && typeof record.operationId === 'string' && typeof record.obligationVersion === 'string'
|
|
82
|
+
&& typeof record.dischargedAt === 'string' && !!record.graph && typeof record.graph === 'object' && !!record.semantic && typeof record.semantic === 'object'
|
|
83
|
+
&& !!record.changedPaths && typeof record.changedPaths === 'object';
|
|
84
|
+
if (!shaped)
|
|
85
|
+
throw new entity_transaction_1.EntityStoreError('discharge-conflict', 'The discharge record has an unknown shape.');
|
|
86
|
+
return record;
|
|
87
|
+
}
|
|
88
|
+
/** An existing record for this obligation, undefined when absent; a record bound elsewhere is a conflict. */
|
|
89
|
+
function existingRecord(file, operationId, obligationVersion) {
|
|
90
|
+
const bytes = (0, entity_transaction_1.readEntityBytes)(file);
|
|
91
|
+
if (!bytes)
|
|
92
|
+
return undefined;
|
|
93
|
+
const record = parseRecord(bytes);
|
|
94
|
+
if (record.operationId !== operationId || record.obligationVersion !== obligationVersion) {
|
|
95
|
+
throw new entity_transaction_1.EntityStoreError('discharge-conflict', 'An existing discharge record binds a different operation or obligation; preserve it and inspect before retrying.');
|
|
96
|
+
}
|
|
97
|
+
return record;
|
|
98
|
+
}
|
|
99
|
+
/** Read-only: every completed operation on this replica that carries derived obligations, and where each stands. */
|
|
100
|
+
function inspectDerivedObligations(root) {
|
|
101
|
+
let dir;
|
|
102
|
+
try {
|
|
103
|
+
dir = (0, entity_transaction_1.entityDirectory)(fs.realpathSync(root), COMPLETED);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
let names;
|
|
109
|
+
try {
|
|
110
|
+
names = fs.readdirSync(dir).filter((name) => UUID.test(name)).sort();
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
const out = [];
|
|
116
|
+
for (const operationId of names) {
|
|
117
|
+
const operation = path.join(dir, operationId);
|
|
118
|
+
let derivedBytes;
|
|
119
|
+
try {
|
|
120
|
+
derivedBytes = (0, entity_transaction_1.readEntityBytes)(path.join(operation, 'derived.json'));
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
out.push({ operationId, state: 'invalid', reason: 'derived.json is not a readable regular file' });
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (!derivedBytes)
|
|
127
|
+
continue; // a completed operation without derived obligations is not this module's concern
|
|
128
|
+
let derived = null;
|
|
129
|
+
try {
|
|
130
|
+
derived = JSON.parse(derivedBytes.toString('utf8'));
|
|
131
|
+
}
|
|
132
|
+
catch { /* reported below */ }
|
|
133
|
+
if (!derived || derived.schema !== OBLIGATION_SCHEMA || derived.operationId !== operationId) {
|
|
134
|
+
out.push({ operationId, state: 'invalid', reason: 'derived.json does not carry this operation\'s obligations' });
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const obligationVersion = (0, entity_transaction_1.entityContentVersion)(derivedBytes);
|
|
138
|
+
try {
|
|
139
|
+
const record = existingRecord(path.join(operation, 'discharge.json'), operationId, obligationVersion);
|
|
140
|
+
if (!record)
|
|
141
|
+
out.push({ operationId, obligationVersion, state: 'pending' });
|
|
142
|
+
else
|
|
143
|
+
out.push({ operationId, obligationVersion, state: 'discharged', dischargedAt: record.dischargedAt, graphBasisCommit: record.graph.basis.sourceCommit });
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
out.push({ operationId, obligationVersion, state: 'invalid', reason: error instanceof Error ? error.message : String(error) });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
/** Classify every changed locator of the plan against the current tree; the last step for a locator is its final state. */
|
|
152
|
+
function verifyChangedPaths(root, plan) {
|
|
153
|
+
const final = new Map();
|
|
154
|
+
for (const step of plan.steps)
|
|
155
|
+
final.set(step.change.locator, step.change);
|
|
156
|
+
const result = { intact: [], drifted: [], missing: [] };
|
|
157
|
+
for (const [locator, change] of [...final.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) {
|
|
158
|
+
let current;
|
|
159
|
+
try {
|
|
160
|
+
current = (0, entity_transaction_1.readEntityBytes)(path.join((0, entity_transaction_1.entityDirectory)(root, path.posix.dirname(locator)), path.posix.basename(locator)));
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
current = undefined;
|
|
164
|
+
}
|
|
165
|
+
if (change.after === null) {
|
|
166
|
+
(current === undefined ? result.intact : result.drifted).push(locator);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (current === undefined)
|
|
170
|
+
result.missing.push(locator);
|
|
171
|
+
else if (current.equals(Buffer.from(change.after, 'base64')))
|
|
172
|
+
result.intact.push(locator);
|
|
173
|
+
else
|
|
174
|
+
result.drifted.push(locator);
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Discharge one completed operation's obligations on this replica. Verifies completion and the exact
|
|
180
|
+
* obligation bytes, rebuilds through the injected paths, verifies the changed paths and publishes one
|
|
181
|
+
* immutable record; a second writer (or a second call) returns the existing record instead.
|
|
182
|
+
*/
|
|
183
|
+
async function dischargeDerivedObligations(root, operationId, rebuild) {
|
|
184
|
+
if (!UUID.test(operationId))
|
|
185
|
+
throw new entity_transaction_1.EntityStoreError('invalid-plan', 'Provide the completed integration operation UUID.');
|
|
186
|
+
root = fs.realpathSync(root);
|
|
187
|
+
const observed = (0, entity_integration_transaction_1.inspectEntityIntegrationRecovery)(root, operationId);
|
|
188
|
+
if (observed.state !== 'completed') {
|
|
189
|
+
throw new entity_transaction_1.EntityStoreError('discharge-requires-completion', 'Derived obligations are discharged only for a completed integration; recover the pending operation first.');
|
|
190
|
+
}
|
|
191
|
+
const plan = observed.plan;
|
|
192
|
+
const directory = (0, entity_transaction_1.entityDirectory)(root, COMPLETED + '/' + operationId);
|
|
193
|
+
const derivedBytes = (0, entity_transaction_1.readEntityBytes)(path.join(directory, 'derived.json'));
|
|
194
|
+
if (!derivedBytes)
|
|
195
|
+
throw new entity_transaction_1.EntityStoreError('obligation-conflict', 'The completed integration has no derived obligations record.');
|
|
196
|
+
let derived;
|
|
197
|
+
try {
|
|
198
|
+
derived = JSON.parse(derivedBytes.toString('utf8'));
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
throw new entity_transaction_1.EntityStoreError('obligation-conflict', 'The derived obligations record is not valid JSON.');
|
|
202
|
+
}
|
|
203
|
+
if (!(0, node_util_1.isDeepStrictEqual)(derived, expectedObligation(plan))) {
|
|
204
|
+
throw new entity_transaction_1.EntityStoreError('obligation-conflict', 'Derived obligations differ from the completed plan; the historical record was altered and is preserved as-is.');
|
|
205
|
+
}
|
|
206
|
+
const obligationVersion = (0, entity_transaction_1.entityContentVersion)(derivedBytes);
|
|
207
|
+
const dischargeFile = path.join(directory, 'discharge.json');
|
|
208
|
+
const existing = existingRecord(dischargeFile, operationId, obligationVersion);
|
|
209
|
+
if (existing)
|
|
210
|
+
return { ok: true, record: existing, alreadyDischarged: true };
|
|
211
|
+
const changedPaths = verifyChangedPaths(root, plan);
|
|
212
|
+
const graph = await rebuild.publishGraph();
|
|
213
|
+
const semantic = await rebuild.warmSemantic();
|
|
214
|
+
if (semantic.status === 'unavailable') {
|
|
215
|
+
throw new entity_transaction_1.EntityStoreError('semantic-unavailable', `The configured semantic tier (${semantic.tier}) produced no embeddings; the graph was published and the obligation stays pending — retry once the tier is available.`);
|
|
216
|
+
}
|
|
217
|
+
const record = {
|
|
218
|
+
schema: exports.DISCHARGE_SCHEMA, operationId, workspaceId: plan.workspaceId, storeId: plan.storeId, obligationVersion,
|
|
219
|
+
destinationHead: plan.destination.head, graph, semantic, changedPaths, dischargedAt: new Date().toISOString(),
|
|
220
|
+
};
|
|
221
|
+
try {
|
|
222
|
+
(0, entity_transaction_1.publishEntityRecord)(root, COMPLETED + '/' + operationId + '/discharge.json', JSON.stringify(record));
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
if (error.code !== 'EEXIST')
|
|
226
|
+
throw error;
|
|
227
|
+
const winner = existingRecord(dischargeFile, operationId, obligationVersion); // a concurrent discharger published first
|
|
228
|
+
if (!winner)
|
|
229
|
+
throw error;
|
|
230
|
+
return { ok: true, record: winner, alreadyDischarged: true };
|
|
231
|
+
}
|
|
232
|
+
return { ok: true, record, alreadyDischarged: false };
|
|
233
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { EntityByteChange, entityLockOwner } from './entity-transaction';
|
|
2
|
+
import { EntityGitDocuments, EntityGitMergedSources } from './entity-integration';
|
|
3
|
+
import { entitySourcePublicationOwner } from './entity-renumber-transaction';
|
|
4
|
+
import { EntityRenumberEntry } from './entity-renumber';
|
|
5
|
+
export interface EntityIntegrationTransitions {
|
|
6
|
+
unseal: Array<{
|
|
7
|
+
entityId: string;
|
|
8
|
+
displayId: string;
|
|
9
|
+
documentLocator: string;
|
|
10
|
+
before: string;
|
|
11
|
+
after: string;
|
|
12
|
+
}>;
|
|
13
|
+
publication: EntityRenumberEntry[];
|
|
14
|
+
approve: EntityIntegrationTransitions['unseal'];
|
|
15
|
+
completed: EntityRenumberEntry[];
|
|
16
|
+
}
|
|
17
|
+
/** Produces document transitions only; authority and durable application belong to the executor. */
|
|
18
|
+
export declare function prepareEntityIntegrationTransitions(current: EntityRenumberEntry[], desired: EntityRenumberEntry[], readFile: (file: string) => string | null): EntityIntegrationTransitions;
|
|
19
|
+
export interface EntityIntegrationConflictEvidence {
|
|
20
|
+
kind: 'entity' | 'source' | 'numbering' | 'document' | 'reference' | 'mirror' | 'destination';
|
|
21
|
+
/** @implements A-SPEC-637 — the destination path a planned locator collides with (`destination` kind). */
|
|
22
|
+
existing?: string;
|
|
23
|
+
entityId?: string;
|
|
24
|
+
file?: string;
|
|
25
|
+
reason: string;
|
|
26
|
+
objects: EntityGitMergedSources['files'][number]['objects'];
|
|
27
|
+
records?: EntityGitMergedSources['files'][number]['objects'];
|
|
28
|
+
displayIds?: Record<'base' | 'left' | 'right', string | null> | Record<string, Record<'base' | 'left' | 'right', string | null>>;
|
|
29
|
+
targets?: Record<'base' | 'left' | 'right', string | null>;
|
|
30
|
+
reference?: string;
|
|
31
|
+
entityIds?: string[];
|
|
32
|
+
target?: string;
|
|
33
|
+
other?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface EntityIntegrationCandidate {
|
|
36
|
+
schema: 'holmes-entity-integration-candidate/1';
|
|
37
|
+
stage: 'candidate';
|
|
38
|
+
ok: boolean;
|
|
39
|
+
workspaceId: string;
|
|
40
|
+
storeId: string;
|
|
41
|
+
storeLocator: string;
|
|
42
|
+
commits: Record<'base' | 'left' | 'right', string>;
|
|
43
|
+
destinationHead: string;
|
|
44
|
+
indexVersion: string;
|
|
45
|
+
membershipVersion: string;
|
|
46
|
+
changes: EntityByteChange[];
|
|
47
|
+
checks: Array<{
|
|
48
|
+
locator: string;
|
|
49
|
+
version: string | null;
|
|
50
|
+
mode: string | null;
|
|
51
|
+
}>;
|
|
52
|
+
conflicts: string[];
|
|
53
|
+
/** Original Git input objects, not normalized/generated replacement bytes. */
|
|
54
|
+
conflictEvidence?: EntityIntegrationConflictEvidence[];
|
|
55
|
+
pending: Array<{
|
|
56
|
+
file: string;
|
|
57
|
+
reason: string;
|
|
58
|
+
}>;
|
|
59
|
+
/** @implements A-SPEC-637 — non-regular entries identical in every input, left untouched. */
|
|
60
|
+
excluded?: Array<{
|
|
61
|
+
file: string;
|
|
62
|
+
reason: string;
|
|
63
|
+
}>;
|
|
64
|
+
approval: EntityGitDocuments['approval'];
|
|
65
|
+
transitions?: EntityIntegrationTransitions;
|
|
66
|
+
policyInputs: Array<{
|
|
67
|
+
locator: string;
|
|
68
|
+
version: string | null;
|
|
69
|
+
effectiveVersion: string | null;
|
|
70
|
+
}>;
|
|
71
|
+
steps?: EntityIntegrationStep[];
|
|
72
|
+
/**
|
|
73
|
+
* @implements A-SPEC-645 — documents this integration renumbers (old display id from the side's
|
|
74
|
+
* own record, new id from the numbering), present only when non-empty. A side-CREATED document
|
|
75
|
+
* has a `before: null` mapping step, so this is the only place its old id is known; the audit
|
|
76
|
+
* records these so evidence consumers can follow the document after the integration.
|
|
77
|
+
*/
|
|
78
|
+
renumbered?: Array<{
|
|
79
|
+
entityId: string;
|
|
80
|
+
oldDisplayId: string;
|
|
81
|
+
newDisplayId: string;
|
|
82
|
+
}>;
|
|
83
|
+
}
|
|
84
|
+
export interface EntityIntegrationStep {
|
|
85
|
+
phase: 'unseal' | 'document' | 'mapping' | 'source' | 'approve';
|
|
86
|
+
entityId?: string;
|
|
87
|
+
specId?: string;
|
|
88
|
+
change: EntityByteChange;
|
|
89
|
+
}
|
|
90
|
+
export interface EntityIntegrationPlan {
|
|
91
|
+
schema: 'holmes-entity-integration/1';
|
|
92
|
+
operationId: string;
|
|
93
|
+
workspaceId: string;
|
|
94
|
+
storeId: string;
|
|
95
|
+
storeLocator: string;
|
|
96
|
+
commits: EntityIntegrationCandidate['commits'];
|
|
97
|
+
destination: {
|
|
98
|
+
head: string;
|
|
99
|
+
indexVersion: string;
|
|
100
|
+
membershipVersion: string;
|
|
101
|
+
};
|
|
102
|
+
configVersion: string | null;
|
|
103
|
+
checks: EntityIntegrationCandidate['checks'];
|
|
104
|
+
policyInputs: EntityIntegrationCandidate['policyInputs'];
|
|
105
|
+
steps: EntityIntegrationStep[];
|
|
106
|
+
}
|
|
107
|
+
/** A closed transport contract. Application must additionally regenerate it from current authorized inputs. */
|
|
108
|
+
export declare function validateEntityIntegrationPlan(value: unknown): EntityIntegrationPlan;
|
|
109
|
+
export declare function entityIntegrationTarget(plan: EntityIntegrationPlan): string;
|
|
110
|
+
/** Internal execution core; the public adapter supplies current authority and durable audit. */
|
|
111
|
+
export declare function applyEntityIntegration(root: string, inspected: unknown, authorize: (specId?: string) => void, audit: (plan: EntityIntegrationPlan, verifyOnly?: boolean, renumbered?: NonNullable<EntityIntegrationCandidate['renumbered']>) => void, beforePublish?: (plan: EntityIntegrationPlan, index: number) => void): Promise<{
|
|
112
|
+
ok: true;
|
|
113
|
+
state: 'completed';
|
|
114
|
+
operationId: string;
|
|
115
|
+
}>;
|
|
116
|
+
/**
|
|
117
|
+
* @implements A-SPEC-639 — abandon a PENDING integration a third state blocked: claim both dead
|
|
118
|
+
* generations exactly as recovery does, revert the operation's own publications, preserve every
|
|
119
|
+
* foreign byte, audit, and retire the operation as abandoned. A completed operation is refused.
|
|
120
|
+
*/
|
|
121
|
+
export declare function abandonEntityIntegration(root: string, inspected: unknown, authorize: () => void, audit: (plan: EntityIntegrationPlan, outcome: {
|
|
122
|
+
restored: string[];
|
|
123
|
+
untouched: string[];
|
|
124
|
+
preserved: string[];
|
|
125
|
+
}) => void): Promise<{
|
|
126
|
+
ok: true;
|
|
127
|
+
state: 'abandoned';
|
|
128
|
+
operationId: string;
|
|
129
|
+
restored: string[];
|
|
130
|
+
untouched: string[];
|
|
131
|
+
preserved: string[];
|
|
132
|
+
}>;
|
|
133
|
+
export declare function recoverEntityIntegration(root: string, inspected: EntityIntegrationRecoveryInspection, authorize: (specId?: string) => void, audit: (plan: EntityIntegrationPlan, verifyOnly?: boolean) => void, beforePublish?: (plan: EntityIntegrationPlan, index: number) => void): Promise<{
|
|
134
|
+
ok: true;
|
|
135
|
+
state: 'completed';
|
|
136
|
+
operationId: string;
|
|
137
|
+
}>;
|
|
138
|
+
export interface EntityIntegrationRecoveryInspection {
|
|
139
|
+
schema: 'holmes-entity-integration-recovery-inspection/2';
|
|
140
|
+
state: 'pending' | 'completed';
|
|
141
|
+
operationId: string;
|
|
142
|
+
journalVersion: string;
|
|
143
|
+
completionVersion: string | null;
|
|
144
|
+
plan: EntityIntegrationPlan;
|
|
145
|
+
observedPrefixes: number[];
|
|
146
|
+
conflicts: string[];
|
|
147
|
+
storeOwner: ReturnType<typeof entityLockOwner>;
|
|
148
|
+
sourceOwner: ReturnType<typeof entitySourcePublicationOwner>;
|
|
149
|
+
}
|
|
150
|
+
/** Keep the original approval target while binding a lost-response retry to durable completion. */
|
|
151
|
+
export declare function validateEntityIntegrationRecoveryRequest(root: string, inspected: unknown): EntityIntegrationRecoveryInspection;
|
|
152
|
+
/** Observation only; matching bytes do not authorize replay of a journal's program. */
|
|
153
|
+
export declare function inspectEntityIntegrationRecovery(root: string, operationId: string): EntityIntegrationRecoveryInspection;
|
|
154
|
+
/** Re-derive the pending journal program before any recovery writer may use it. */
|
|
155
|
+
export declare function validateEntityIntegrationRecoveryProgram(root: string, operationId: string): Promise<EntityIntegrationRecoveryInspection>;
|
|
156
|
+
/** Executor boundary: a shape-valid plan still needs regeneration and current destination registration. */
|
|
157
|
+
export declare function withValidatedEntityIntegrationPlan<T>(root: string, inspected: unknown, act: (plan: EntityIntegrationPlan, candidate: EntityIntegrationCandidate) => T | Promise<T>): Promise<T>;
|
|
158
|
+
export declare function prepareEntityIntegrationPlan(root: string, revisions: {
|
|
159
|
+
base: string;
|
|
160
|
+
left: string;
|
|
161
|
+
right: string;
|
|
162
|
+
}, storeLocator: string, operationId?: string): Promise<EntityIntegrationPlan>;
|
|
163
|
+
/** Public review keeps the candidate and executable plan within the same owned inspection. */
|
|
164
|
+
export declare function reviewEntityIntegration(root: string, revisions: {
|
|
165
|
+
base: string;
|
|
166
|
+
left: string;
|
|
167
|
+
right: string;
|
|
168
|
+
}, storeLocator: string): Promise<{
|
|
169
|
+
plan?: EntityIntegrationPlan | undefined;
|
|
170
|
+
preview: EntityIntegrationCandidate;
|
|
171
|
+
}>;
|
|
172
|
+
/** Internal compilation, not validation or authorization of a caller-supplied execution payload. */
|
|
173
|
+
export declare function compileEntityIntegrationSteps(candidate: EntityIntegrationCandidate): EntityIntegrationStep[];
|
|
174
|
+
/** A reviewable candidate, not an authorized executable journal or a source of replacement authority. */
|
|
175
|
+
export declare function planEntityIntegrationCandidate(root: string, revisions: {
|
|
176
|
+
base: string;
|
|
177
|
+
left: string;
|
|
178
|
+
right: string;
|
|
179
|
+
}, storeLocator: string): Promise<EntityIntegrationCandidate>;
|