@feltdb/core 0.6.9 → 0.6.11
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/dist/cli/application-lifecycle.js +112 -0
- package/dist/cli/application.js +10 -0
- package/dist/cli/commands.js +407 -119
- package/dist/cli/development-session.js +54 -0
- package/dist/cli/index.js +1 -1
- package/dist/cli/workspace-integration.js +146 -20
- package/dist/collection.d.ts +110 -2
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +228 -3
- package/dist/create/cli.js +4 -2
- package/dist/create/create.js +42 -6
- package/dist/create/frameworks.js +18 -0
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +408 -178
- package/dist/create/server-source/crates/feltdb/src/lib.rs +49 -5
- package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +659 -0
- package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +285 -0
- package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +582 -0
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +141 -0
- package/dist/create/workspace-initialization.js +4 -5
- package/dist/db.d.ts +63 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +145 -4
- package/dist/development-runtime-bridge.d.ts +60 -0
- package/dist/development-runtime-bridge.d.ts.map +1 -0
- package/dist/development-runtime-bridge.js +171 -0
- package/dist/embedded-transaction.d.ts +77 -0
- package/dist/embedded-transaction.d.ts.map +1 -0
- package/dist/embedded-transaction.js +102 -0
- package/dist/feltdb.d.ts +49 -1
- package/dist/feltdb.d.ts.map +1 -1
- package/dist/file-db.d.ts +33 -0
- package/dist/file-db.d.ts.map +1 -1
- package/dist/file-db.js +128 -4
- package/dist/freshness.d.ts +119 -0
- package/dist/freshness.d.ts.map +1 -0
- package/dist/freshness.js +115 -0
- package/dist/http-db.d.ts +48 -0
- package/dist/http-db.d.ts.map +1 -1
- package/dist/http-db.js +77 -1
- package/dist/index-backend.d.ts +8 -0
- package/dist/index-backend.d.ts.map +1 -1
- package/dist/index-backend.js +13 -0
- package/dist/index-core.d.ts +80 -0
- package/dist/index-core.d.ts.map +1 -0
- package/dist/index-core.js +74 -0
- package/dist/index-manager.d.ts +11 -0
- package/dist/index-manager.d.ts.map +1 -1
- package/dist/index-manager.js +30 -13
- package/dist/index-types.d.ts +17 -0
- package/dist/index-types.d.ts.map +1 -1
- package/dist/index.browser.d.ts +14 -0
- package/dist/index.browser.d.ts.map +1 -0
- package/dist/index.browser.js +13 -0
- package/dist/index.d.ts +8 -59
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -54
- package/dist/indexeddb-db.d.ts +32 -0
- package/dist/indexeddb-db.d.ts.map +1 -1
- package/dist/indexeddb-db.js +151 -1
- package/dist/memory-db.d.ts +26 -0
- package/dist/memory-db.d.ts.map +1 -1
- package/dist/memory-db.js +86 -0
- package/dist/runtime-registry.d.ts +32 -0
- package/dist/runtime-registry.d.ts.map +1 -0
- package/dist/runtime-registry.js +38 -0
- package/dist/studio/app.d.ts +2 -1
- package/dist/studio/app.d.ts.map +1 -1
- package/dist/studio/components/StateExplorer.d.ts +2 -1
- package/dist/studio/components/StateExplorer.d.ts.map +1 -1
- package/dist/studio/components/index.js +1 -1
- package/dist/studio/{components-9kDSWiGL.js → components-C5p2TfIU.js} +25 -14
- package/dist/studio/index.js +234 -160
- package/dist/studio-app/assets/{feltdb_wasm-B4wq4mqp.js → feltdb_wasm-CJv3wHzi.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-C8TG8r2n.wasm +0 -0
- package/dist/studio-app/assets/index-DospFFYE.js +28 -0
- package/dist/studio-app/index.html +1 -1
- package/dist/transaction.d.ts +98 -0
- package/dist/transaction.d.ts.map +1 -0
- package/dist/transaction.js +86 -0
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/dist/workspace/browser.d.ts +22 -0
- package/dist/workspace/browser.d.ts.map +1 -0
- package/dist/workspace/browser.js +17 -0
- package/dist/workspace/development-node.d.ts +16 -0
- package/dist/workspace/development-node.d.ts.map +1 -1
- package/dist/workspace/development-node.js +85 -1
- package/dist/workspace/git-identity.d.ts +35 -0
- package/dist/workspace/git-identity.d.ts.map +1 -0
- package/dist/workspace/git-identity.js +81 -0
- package/dist/workspace/index.d.ts +12 -2
- package/dist/workspace/index.d.ts.map +1 -1
- package/dist/workspace/index.js +7 -1
- package/dist/workspace/investigation-analysis.d.ts +47 -0
- package/dist/workspace/investigation-analysis.d.ts.map +1 -0
- package/dist/workspace/investigation-analysis.js +181 -0
- package/dist/workspace/investigation-lifecycle-manager.d.ts +88 -0
- package/dist/workspace/investigation-lifecycle-manager.d.ts.map +1 -0
- package/dist/workspace/investigation-lifecycle-manager.js +259 -0
- package/dist/workspace/investigation-supervisor.d.ts +100 -0
- package/dist/workspace/investigation-supervisor.d.ts.map +1 -0
- package/dist/workspace/investigation-supervisor.js +238 -0
- package/dist/workspace/runtime-investigation.d.ts +5 -1
- package/dist/workspace/runtime-investigation.d.ts.map +1 -1
- package/dist/workspace/runtime-investigation.js +50 -21
- package/dist/workspace/runtime-observation.d.ts +82 -0
- package/dist/workspace/runtime-observation.d.ts.map +1 -0
- package/dist/workspace/runtime-observation.js +170 -0
- package/dist/workspace/runtime-observer.d.ts +57 -0
- package/dist/workspace/runtime-observer.d.ts.map +1 -0
- package/dist/workspace/runtime-observer.js +272 -0
- package/dist/workspace/workspace-connection.d.ts.map +1 -1
- package/dist/workspace/workspace-connection.js +14 -5
- package/dist/workspace/workspace-identity.d.ts +1 -1
- package/dist/workspace/workspace-identity.d.ts.map +1 -1
- package/dist/workspace/workspace-identity.js +4 -4
- package/dist/workspace/workspace-types.d.ts +208 -0
- package/dist/workspace/workspace-types.d.ts.map +1 -1
- package/package.json +12 -4
- package/dist/create/server-source/crates/feltdb/src/phase_1c_three_node.rs +0 -523
- package/dist/studio-app/assets/feltdb_wasm_bg-Ceyi7l21.wasm +0 -0
- package/dist/studio-app/assets/index-BF45M757.js +0 -28
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Investigation analysis.
|
|
3
|
+
*
|
|
4
|
+
* This module draws the line the rest of the system depends on:
|
|
5
|
+
*
|
|
6
|
+
* Evidence — facts the runtime observed. Never generated by FeltDB.
|
|
7
|
+
* Finding — FeltDB's engineering interpretation of that evidence.
|
|
8
|
+
*
|
|
9
|
+
* The two are separate types with separate provenance markers
|
|
10
|
+
* (`source: "runtime_observation"` vs `origin: "feltdb_analysis"`) so that a
|
|
11
|
+
* generated diagnosis can never be presented, stored, or handed to an agent as
|
|
12
|
+
* though it were raw runtime evidence.
|
|
13
|
+
*
|
|
14
|
+
* This module is pure: it performs no I/O and imports no Node built-ins.
|
|
15
|
+
*/
|
|
16
|
+
import type { InvestigationEvidence, InvestigationFinding, RuntimeRequestObservation, SourceLocation, VerificationCriterion } from './workspace-types.js';
|
|
17
|
+
/**
|
|
18
|
+
* Turn a runtime observation into evidence records.
|
|
19
|
+
*
|
|
20
|
+
* Every statement here restates something the runtime actually saw. Nothing in
|
|
21
|
+
* this function may infer a cause.
|
|
22
|
+
*/
|
|
23
|
+
export declare function collectEvidence(investigationId: string, observation: RuntimeRequestObservation): InvestigationEvidence[];
|
|
24
|
+
/**
|
|
25
|
+
* Interpret the evidence.
|
|
26
|
+
*
|
|
27
|
+
* This is FeltDB's analysis, explicitly marked as such. It states what the
|
|
28
|
+
* evidence means for the engineer without claiming to be an observation.
|
|
29
|
+
*/
|
|
30
|
+
export declare function deriveFinding(investigationId: string, observation: RuntimeRequestObservation, evidence: InvestigationEvidence[]): InvestigationFinding;
|
|
31
|
+
/** Candidate source paths that could implement the observed endpoint. */
|
|
32
|
+
export declare function candidateSourcePaths(url: string): string[];
|
|
33
|
+
/** The recommended change FeltDB proposes, stated as a request, not a patch. */
|
|
34
|
+
export declare function recommendChanges(observation: RuntimeRequestObservation): string[];
|
|
35
|
+
/**
|
|
36
|
+
* The verification contract.
|
|
37
|
+
*
|
|
38
|
+
* This is what FeltDB will hold the runtime to after the agent has worked. It
|
|
39
|
+
* must be expressed as an observable runtime outcome, never as a build,
|
|
40
|
+
* test, or commit result.
|
|
41
|
+
*/
|
|
42
|
+
export declare function verificationContractFor(observation: RuntimeRequestObservation): VerificationCriterion[];
|
|
43
|
+
/** A one-line summary of why the investigation was opened. */
|
|
44
|
+
export declare function summarizeDefect(observation: RuntimeRequestObservation): string;
|
|
45
|
+
/** Source locations restricted to files the caller confirmed exist. */
|
|
46
|
+
export declare function resolveRelevantSource(url: string, existingFiles: string[]): SourceLocation[];
|
|
47
|
+
//# sourceMappingURL=investigation-analysis.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"investigation-analysis.d.ts","sourceRoot":"","sources":["../../src/workspace/investigation-analysis.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EACpB,yBAAyB,EACzB,cAAc,EACd,qBAAqB,EACtB,MAAM,sBAAsB,CAAC;AAK9B;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,eAAe,EAAE,MAAM,EACvB,WAAW,EAAE,yBAAyB,GACrC,qBAAqB,EAAE,CAsEzB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,eAAe,EAAE,MAAM,EACvB,WAAW,EAAE,yBAAyB,EACtC,QAAQ,EAAE,qBAAqB,EAAE,GAChC,oBAAoB,CAoBtB;AAED,yEAAyE;AACzE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAe1D;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,yBAAyB,GAAG,MAAM,EAAE,CAYjF;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,yBAAyB,GAAG,qBAAqB,EAAE,CAUvG;AAED,8DAA8D;AAC9D,wBAAgB,eAAe,CAAC,WAAW,EAAE,yBAAyB,GAAG,MAAM,CAE9E;AAUD,uEAAuE;AACvE,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,CAG5F"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Investigation analysis.
|
|
3
|
+
*
|
|
4
|
+
* This module draws the line the rest of the system depends on:
|
|
5
|
+
*
|
|
6
|
+
* Evidence — facts the runtime observed. Never generated by FeltDB.
|
|
7
|
+
* Finding — FeltDB's engineering interpretation of that evidence.
|
|
8
|
+
*
|
|
9
|
+
* The two are separate types with separate provenance markers
|
|
10
|
+
* (`source: "runtime_observation"` vs `origin: "feltdb_analysis"`) so that a
|
|
11
|
+
* generated diagnosis can never be presented, stored, or handed to an agent as
|
|
12
|
+
* though it were raw runtime evidence.
|
|
13
|
+
*
|
|
14
|
+
* This module is pure: it performs no I/O and imports no Node built-ins.
|
|
15
|
+
*/
|
|
16
|
+
import { describeDefect } from './runtime-observation.js';
|
|
17
|
+
const uniqueId = (prefix) => `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
|
|
18
|
+
/**
|
|
19
|
+
* Turn a runtime observation into evidence records.
|
|
20
|
+
*
|
|
21
|
+
* Every statement here restates something the runtime actually saw. Nothing in
|
|
22
|
+
* this function may infer a cause.
|
|
23
|
+
*/
|
|
24
|
+
export function collectEvidence(investigationId, observation) {
|
|
25
|
+
const base = {
|
|
26
|
+
investigationId,
|
|
27
|
+
observationId: observation.observationId,
|
|
28
|
+
source: 'runtime_observation',
|
|
29
|
+
observedAt: observation.timestamp,
|
|
30
|
+
};
|
|
31
|
+
const evidence = [];
|
|
32
|
+
if (observation.networkFailure) {
|
|
33
|
+
evidence.push({
|
|
34
|
+
...base,
|
|
35
|
+
id: uniqueId('evidence'),
|
|
36
|
+
kind: 'network_failure',
|
|
37
|
+
statement: `${observation.method} ${observation.url} produced no HTTP response.`,
|
|
38
|
+
detail: { method: observation.method, url: observation.url, ...observation.responseCharacteristics },
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
evidence.push({
|
|
43
|
+
...base,
|
|
44
|
+
id: uniqueId('evidence'),
|
|
45
|
+
kind: 'http_response',
|
|
46
|
+
statement: `${observation.method} ${observation.url} responded with HTTP ${observation.status}.`,
|
|
47
|
+
detail: {
|
|
48
|
+
method: observation.method,
|
|
49
|
+
url: observation.url,
|
|
50
|
+
status: observation.status,
|
|
51
|
+
...observation.responseCharacteristics,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (typeof observation.durationMs === 'number') {
|
|
56
|
+
evidence.push({
|
|
57
|
+
...base,
|
|
58
|
+
id: uniqueId('evidence'),
|
|
59
|
+
kind: 'timing',
|
|
60
|
+
statement: `The request completed in ${observation.durationMs}ms.`,
|
|
61
|
+
detail: { durationMs: observation.durationMs },
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (observation.browser || observation.page) {
|
|
65
|
+
evidence.push({
|
|
66
|
+
...base,
|
|
67
|
+
id: uniqueId('evidence'),
|
|
68
|
+
kind: 'browser_context',
|
|
69
|
+
statement: `Observed by the ${observation.browser || 'unknown'} runtime on page ${observation.page || 'unknown'}.`,
|
|
70
|
+
detail: {
|
|
71
|
+
browser: observation.browser,
|
|
72
|
+
engine: observation.engine,
|
|
73
|
+
page: observation.page,
|
|
74
|
+
runtime: observation.runtime,
|
|
75
|
+
correlationId: observation.correlationId,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
for (const event of observation.correlatedEvents || []) {
|
|
80
|
+
evidence.push({
|
|
81
|
+
...base,
|
|
82
|
+
id: uniqueId('evidence'),
|
|
83
|
+
kind: event.kind === 'console_error' || event.kind === 'console_warn' ? 'console_error' : 'runtime_error',
|
|
84
|
+
statement: `Correlated ${event.kind.replace(/_/g, ' ')}: ${event.message}`,
|
|
85
|
+
observedAt: event.timestamp,
|
|
86
|
+
detail: { stack: event.stack, source: event.source, correlationId: observation.correlationId },
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return evidence;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Interpret the evidence.
|
|
93
|
+
*
|
|
94
|
+
* This is FeltDB's analysis, explicitly marked as such. It states what the
|
|
95
|
+
* evidence means for the engineer without claiming to be an observation.
|
|
96
|
+
*/
|
|
97
|
+
export function deriveFinding(investigationId, observation, evidence) {
|
|
98
|
+
const path = safePath(observation.url);
|
|
99
|
+
const correlated = evidence.filter(item => item.kind === 'console_error' || item.kind === 'runtime_error');
|
|
100
|
+
const statement = observation.networkFailure
|
|
101
|
+
? `${path} is unreachable from the browser runtime. The request never produced an HTTP response, so the endpoint is the source of the observed runtime failure.`
|
|
102
|
+
: `${path} is returning HTTP ${observation.status}. The endpoint is the source of the observed runtime failure${correlated.length > 0 ? ', and it is accompanied by a correlated runtime error' : ''}.`;
|
|
103
|
+
return {
|
|
104
|
+
id: uniqueId('finding'),
|
|
105
|
+
investigationId,
|
|
106
|
+
statement,
|
|
107
|
+
origin: 'feltdb_analysis',
|
|
108
|
+
// Confidence is higher when the runtime corroborated the response with an
|
|
109
|
+
// error of its own.
|
|
110
|
+
confidence: correlated.length > 0 ? 'high' : observation.status >= 500 ? 'high' : 'medium',
|
|
111
|
+
derivedFromEvidence: evidence.map(item => item.id),
|
|
112
|
+
createdAt: Date.now(),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/** Candidate source paths that could implement the observed endpoint. */
|
|
116
|
+
export function candidateSourcePaths(url) {
|
|
117
|
+
const path = safePath(url).replace(/^\//, '').replace(/\/$/, '');
|
|
118
|
+
if (!path)
|
|
119
|
+
return [];
|
|
120
|
+
const extensions = ['ts', 'tsx', 'js', 'jsx', 'mjs'];
|
|
121
|
+
const candidates = [];
|
|
122
|
+
for (const extension of extensions) {
|
|
123
|
+
candidates.push(`app/${path}/route.${extension}`);
|
|
124
|
+
candidates.push(`src/app/${path}/route.${extension}`);
|
|
125
|
+
candidates.push(`pages/${path}.${extension}`);
|
|
126
|
+
candidates.push(`src/pages/${path}.${extension}`);
|
|
127
|
+
candidates.push(`${path}.${extension}`);
|
|
128
|
+
candidates.push(`src/${path}.${extension}`);
|
|
129
|
+
candidates.push(`server/${path}.${extension}`);
|
|
130
|
+
}
|
|
131
|
+
return candidates;
|
|
132
|
+
}
|
|
133
|
+
/** The recommended change FeltDB proposes, stated as a request, not a patch. */
|
|
134
|
+
export function recommendChanges(observation) {
|
|
135
|
+
const path = safePath(observation.url);
|
|
136
|
+
if (observation.networkFailure) {
|
|
137
|
+
return [
|
|
138
|
+
`Make ${observation.method} ${path} reachable so the browser receives an HTTP response.`,
|
|
139
|
+
'Investigate the handler registration and the server startup path for this route.',
|
|
140
|
+
];
|
|
141
|
+
}
|
|
142
|
+
return [
|
|
143
|
+
`Change ${observation.method} ${path} so it returns a successful response instead of HTTP ${observation.status}.`,
|
|
144
|
+
'Correct the failure inside the request handler rather than suppressing the error at the call site.',
|
|
145
|
+
];
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The verification contract.
|
|
149
|
+
*
|
|
150
|
+
* This is what FeltDB will hold the runtime to after the agent has worked. It
|
|
151
|
+
* must be expressed as an observable runtime outcome, never as a build,
|
|
152
|
+
* test, or commit result.
|
|
153
|
+
*/
|
|
154
|
+
export function verificationContractFor(observation) {
|
|
155
|
+
return [{
|
|
156
|
+
id: uniqueId('criterion'),
|
|
157
|
+
kind: 'primary',
|
|
158
|
+
method: observation.method,
|
|
159
|
+
url: observation.url,
|
|
160
|
+
originalStatus: observation.status,
|
|
161
|
+
expectedStatuses: [200, 201, 202, 204],
|
|
162
|
+
description: `${observation.method} ${safePath(observation.url)} must return a successful HTTP status when observed by the browser runtime.`,
|
|
163
|
+
}];
|
|
164
|
+
}
|
|
165
|
+
/** A one-line summary of why the investigation was opened. */
|
|
166
|
+
export function summarizeDefect(observation) {
|
|
167
|
+
return `${observation.method} ${safePath(observation.url)}: ${describeDefect(observation)}`;
|
|
168
|
+
}
|
|
169
|
+
function safePath(url) {
|
|
170
|
+
try {
|
|
171
|
+
return new URL(url, 'http://localhost').pathname;
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return url;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/** Source locations restricted to files the caller confirmed exist. */
|
|
178
|
+
export function resolveRelevantSource(url, existingFiles) {
|
|
179
|
+
const candidates = new Set(candidateSourcePaths(url));
|
|
180
|
+
return existingFiles.filter(file => candidates.has(file)).map(file => ({ file }));
|
|
181
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable investigation lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* Investigations live in `.feltdb/investigations/<id>.json` inside the
|
|
5
|
+
* workspace. That location is what makes the development loop durable rather
|
|
6
|
+
* than browser-session memory: a refresh, a process exit, or a full workspace
|
|
7
|
+
* restart all leave the investigation, its evidence, its finding, its Git
|
|
8
|
+
* association, and its verification result intact and queryable.
|
|
9
|
+
*
|
|
10
|
+
* Every state change goes through the lifecycle state machines in
|
|
11
|
+
* `runtime-investigation.ts`, so an invalid transition throws instead of
|
|
12
|
+
* silently producing an impossible record.
|
|
13
|
+
*/
|
|
14
|
+
import type { InvestigationEvidence, InvestigationFinding, RemediationContract, RepositoryIdentity, RuntimeInvestigation, RuntimeRequestObservation } from './workspace-types.js';
|
|
15
|
+
/** Aggregate facts about the durable investigation store. */
|
|
16
|
+
export interface InvestigationStoreMetrics {
|
|
17
|
+
investigations: number;
|
|
18
|
+
observations: number;
|
|
19
|
+
evidenceRecords: number;
|
|
20
|
+
verifiedInvestigations: number;
|
|
21
|
+
serializedBytes: number;
|
|
22
|
+
loadMilliseconds: number;
|
|
23
|
+
}
|
|
24
|
+
export declare class InvestigationLifecycleManager {
|
|
25
|
+
private readonly workspaceDir;
|
|
26
|
+
private readonly investigationsDir;
|
|
27
|
+
constructor(workspaceDir: string);
|
|
28
|
+
private ensureDirectories;
|
|
29
|
+
/** Open an investigation for a runtime observation. */
|
|
30
|
+
createInvestigation(observation: RuntimeRequestObservation, workspaceId?: string): RuntimeInvestigation;
|
|
31
|
+
persistInvestigation(investigation: RuntimeInvestigation): void;
|
|
32
|
+
loadInvestigation(id: string): RuntimeInvestigation | null;
|
|
33
|
+
listInvestigations(): RuntimeInvestigation[];
|
|
34
|
+
/** Find the investigation opened for a given observation, if any. */
|
|
35
|
+
findByObservationId(observationId: string): RuntimeInvestigation | null;
|
|
36
|
+
/** Add an observation reference without changing the factual observation. */
|
|
37
|
+
linkRuntimeObservationToInvestigation(observationId: string, investigationId: string): RuntimeInvestigation;
|
|
38
|
+
private require;
|
|
39
|
+
/** OBSERVED -> INVESTIGATING, attaching the runtime evidence. */
|
|
40
|
+
transitionToInvestigating(id: string, evidence?: InvestigationEvidence[]): RuntimeInvestigation;
|
|
41
|
+
/**
|
|
42
|
+
* INVESTIGATING -> FINDING.
|
|
43
|
+
*
|
|
44
|
+
* Accepts a structured {@link InvestigationFinding}. A bare string is still
|
|
45
|
+
* accepted so existing callers keep working; it is stored as an analysis
|
|
46
|
+
* finding with no evidence attribution.
|
|
47
|
+
*/
|
|
48
|
+
transitionToFinding(id: string, finding: InvestigationFinding | string): RuntimeInvestigation;
|
|
49
|
+
/**
|
|
50
|
+
* FINDING -> PROPOSED and NOT_STARTED -> SENT_TO_AGENT.
|
|
51
|
+
*
|
|
52
|
+
* Records the repository state at handoff so a later comparison can prove
|
|
53
|
+
* what the agent changed.
|
|
54
|
+
*/
|
|
55
|
+
sendToAgent(id: string, contract: Omit<RemediationContract, 'id' | 'observationId' | 'createdAt' | 'updatedAt'>, gitBefore?: RepositoryIdentity): RuntimeInvestigation;
|
|
56
|
+
/** SENT_TO_AGENT -> IMPLEMENTING. The agent has started work. */
|
|
57
|
+
markImplementing(id: string): RuntimeInvestigation;
|
|
58
|
+
/**
|
|
59
|
+
* IMPLEMENTING -> CHANGES_DETECTED, using observable repository state.
|
|
60
|
+
*
|
|
61
|
+
* Returns the investigation unchanged when the repository has not moved:
|
|
62
|
+
* FeltDB will not advance the lifecycle on an agent's say-so.
|
|
63
|
+
*/
|
|
64
|
+
detectRepositoryChange(id: string, projectDir: string): RuntimeInvestigation;
|
|
65
|
+
/**
|
|
66
|
+
* CHANGES_DETECTED -> IMPLEMENTATION_COMPLETE, and the verification state
|
|
67
|
+
* machine becomes ready to watch the runtime.
|
|
68
|
+
*
|
|
69
|
+
* A detected repository change means remediation happened. It does not mean
|
|
70
|
+
* the defect is fixed, so this only opens verification — it never concludes it.
|
|
71
|
+
*/
|
|
72
|
+
completeImplementation(id: string): RuntimeInvestigation;
|
|
73
|
+
/**
|
|
74
|
+
* Evaluate the verification contract against a runtime observation.
|
|
75
|
+
*
|
|
76
|
+
* The outcome is decided by the observation alone. A missing or
|
|
77
|
+
* non-matching observation yields INCONCLUSIVE; it never yields VERIFIED.
|
|
78
|
+
*/
|
|
79
|
+
recordVerification(id: string, contract: RemediationContract, observation?: RuntimeRequestObservation): RuntimeInvestigation;
|
|
80
|
+
/**
|
|
81
|
+
* Measure the durable development-plane store.
|
|
82
|
+
*
|
|
83
|
+
* This is an empirical baseline for durability, queryability, and restart
|
|
84
|
+
* cost. It is not a scale claim.
|
|
85
|
+
*/
|
|
86
|
+
metrics(): InvestigationStoreMetrics;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=investigation-lifecycle-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"investigation-lifecycle-manager.d.ts","sourceRoot":"","sources":["../../src/workspace/investigation-lifecycle-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EAEnB,kBAAkB,EAClB,oBAAoB,EACpB,yBAAyB,EAE1B,MAAM,sBAAsB,CAAC;AAU9B,6DAA6D;AAC7D,MAAM,WAAW,yBAAyB;IACxC,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,6BAA6B;IACxC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;gBAE/B,YAAY,EAAE,MAAM;IAKhC,OAAO,CAAC,iBAAiB;IAMzB,uDAAuD;IACvD,mBAAmB,CAAC,WAAW,EAAE,yBAAyB,EAAE,WAAW,SAAK,GAAG,oBAAoB;IAwBnG,oBAAoB,CAAC,aAAa,EAAE,oBAAoB,GAAG,IAAI;IAS/D,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI;IAM1D,kBAAkB,IAAI,oBAAoB,EAAE;IAe5C,qEAAqE;IACrE,mBAAmB,CAAC,aAAa,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI;IAKvE,6EAA6E;IAC7E,qCAAqC,CAAC,aAAa,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,oBAAoB;IAgB3G,OAAO,CAAC,OAAO;IAQf,iEAAiE;IACjE,yBAAyB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,GAAE,qBAAqB,EAAO,GAAG,oBAAoB;IAOnG;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,GAAG,MAAM,GAAG,oBAAoB;IAmB7F;;;;;OAKG;IACH,WAAW,CACT,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,IAAI,CAAC,mBAAmB,EAAE,IAAI,GAAG,eAAe,GAAG,WAAW,GAAG,WAAW,CAAC,EACvF,SAAS,CAAC,EAAE,kBAAkB,GAC7B,oBAAoB;IAiBvB,iEAAiE;IACjE,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,oBAAoB;IAMlD;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,oBAAoB;IAgB5E;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE,EAAE,MAAM,GAAG,oBAAoB;IASxD;;;;;OAKG;IACH,kBAAkB,CAChB,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,mBAAmB,EAC7B,WAAW,CAAC,EAAE,yBAAyB,GACtC,oBAAoB;IAuBvB;;;;;OAKG;IACH,OAAO,IAAI,yBAAyB;CA0BrC"}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable investigation lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* Investigations live in `.feltdb/investigations/<id>.json` inside the
|
|
5
|
+
* workspace. That location is what makes the development loop durable rather
|
|
6
|
+
* than browser-session memory: a refresh, a process exit, or a full workspace
|
|
7
|
+
* restart all leave the investigation, its evidence, its finding, its Git
|
|
8
|
+
* association, and its verification result intact and queryable.
|
|
9
|
+
*
|
|
10
|
+
* Every state change goes through the lifecycle state machines in
|
|
11
|
+
* `runtime-investigation.ts`, so an invalid transition throws instead of
|
|
12
|
+
* silently producing an impossible record.
|
|
13
|
+
*/
|
|
14
|
+
import fs from 'node:fs';
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
import { createRemediationContract, evaluateVerification, transitionInvestigation, transitionRemediation, transitionVerification, } from './runtime-investigation.js';
|
|
17
|
+
import { trackRemediation } from './git-identity.js';
|
|
18
|
+
export class InvestigationLifecycleManager {
|
|
19
|
+
constructor(workspaceDir) {
|
|
20
|
+
this.workspaceDir = workspaceDir;
|
|
21
|
+
this.investigationsDir = path.join(workspaceDir, '.feltdb', 'investigations');
|
|
22
|
+
}
|
|
23
|
+
ensureDirectories() {
|
|
24
|
+
if (!fs.existsSync(this.investigationsDir)) {
|
|
25
|
+
fs.mkdirSync(this.investigationsDir, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Open an investigation for a runtime observation. */
|
|
29
|
+
createInvestigation(observation, workspaceId = '') {
|
|
30
|
+
this.ensureDirectories();
|
|
31
|
+
const id = `inv_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
32
|
+
const now = Date.now();
|
|
33
|
+
const investigation = {
|
|
34
|
+
id,
|
|
35
|
+
workspaceId: observation.workspaceId || workspaceId,
|
|
36
|
+
observationId: observation.observationId || `obs_${now}`,
|
|
37
|
+
observationIds: [observation.observationId || `obs_${now}`],
|
|
38
|
+
runtimeInstanceId: observation.runtimeInstanceId,
|
|
39
|
+
sessionId: observation.sessionId,
|
|
40
|
+
remediationContractId: '',
|
|
41
|
+
investigationState: 'OBSERVED',
|
|
42
|
+
remediationState: 'NOT_STARTED',
|
|
43
|
+
verificationState: 'NOT_READY',
|
|
44
|
+
originalObservation: observation,
|
|
45
|
+
evidence: [],
|
|
46
|
+
createdAt: now,
|
|
47
|
+
updatedAt: now,
|
|
48
|
+
};
|
|
49
|
+
this.persistInvestigation(investigation);
|
|
50
|
+
return investigation;
|
|
51
|
+
}
|
|
52
|
+
persistInvestigation(investigation) {
|
|
53
|
+
this.ensureDirectories();
|
|
54
|
+
const filePath = path.join(this.investigationsDir, `${investigation.id}.json`);
|
|
55
|
+
// Write-then-rename so a crash mid-write cannot leave a truncated record.
|
|
56
|
+
const temporary = `${filePath}.tmp`;
|
|
57
|
+
fs.writeFileSync(temporary, JSON.stringify(investigation, null, 2), 'utf8');
|
|
58
|
+
fs.renameSync(temporary, filePath);
|
|
59
|
+
}
|
|
60
|
+
loadInvestigation(id) {
|
|
61
|
+
const filePath = path.join(this.investigationsDir, `${id}.json`);
|
|
62
|
+
if (!fs.existsSync(filePath))
|
|
63
|
+
return null;
|
|
64
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
65
|
+
}
|
|
66
|
+
listInvestigations() {
|
|
67
|
+
if (!fs.existsSync(this.investigationsDir))
|
|
68
|
+
return [];
|
|
69
|
+
return fs.readdirSync(this.investigationsDir)
|
|
70
|
+
.filter(file => file.endsWith('.json'))
|
|
71
|
+
.map(file => this.loadInvestigation(file.replace(/\.json$/, '')))
|
|
72
|
+
// Only records that are actually investigations count. Anything else
|
|
73
|
+
// that lands in the directory must not be reported as one.
|
|
74
|
+
.filter((investigation) => investigation !== null
|
|
75
|
+
&& typeof investigation.id === 'string'
|
|
76
|
+
&& typeof investigation.investigationState === 'string'
|
|
77
|
+
&& typeof investigation.verificationState === 'string')
|
|
78
|
+
.sort((left, right) => left.createdAt - right.createdAt);
|
|
79
|
+
}
|
|
80
|
+
/** Find the investigation opened for a given observation, if any. */
|
|
81
|
+
findByObservationId(observationId) {
|
|
82
|
+
return this.listInvestigations().find(item => item.observationId === observationId || item.observationIds?.includes(observationId)) || null;
|
|
83
|
+
}
|
|
84
|
+
/** Add an observation reference without changing the factual observation. */
|
|
85
|
+
linkRuntimeObservationToInvestigation(observationId, investigationId) {
|
|
86
|
+
const investigation = this.require(investigationId);
|
|
87
|
+
const existing = this.findByObservationId(observationId);
|
|
88
|
+
if (existing && existing.id !== investigationId) {
|
|
89
|
+
throw new Error(`Runtime observation ${observationId} is already linked to ${existing.id}`);
|
|
90
|
+
}
|
|
91
|
+
investigation.observationIds = [...new Set([
|
|
92
|
+
investigation.observationId,
|
|
93
|
+
...(investigation.observationIds || []),
|
|
94
|
+
observationId,
|
|
95
|
+
])];
|
|
96
|
+
investigation.updatedAt = Date.now();
|
|
97
|
+
this.persistInvestigation(investigation);
|
|
98
|
+
return investigation;
|
|
99
|
+
}
|
|
100
|
+
require(id) {
|
|
101
|
+
const investigation = this.loadInvestigation(id);
|
|
102
|
+
if (!investigation)
|
|
103
|
+
throw new Error(`Investigation ${id} not found`);
|
|
104
|
+
return investigation;
|
|
105
|
+
}
|
|
106
|
+
// --- investigation phase ------------------------------------------------
|
|
107
|
+
/** OBSERVED -> INVESTIGATING, attaching the runtime evidence. */
|
|
108
|
+
transitionToInvestigating(id, evidence = []) {
|
|
109
|
+
const updated = transitionInvestigation(this.require(id), 'INVESTIGATING');
|
|
110
|
+
updated.evidence = [...(updated.evidence || []), ...evidence];
|
|
111
|
+
this.persistInvestigation(updated);
|
|
112
|
+
return updated;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* INVESTIGATING -> FINDING.
|
|
116
|
+
*
|
|
117
|
+
* Accepts a structured {@link InvestigationFinding}. A bare string is still
|
|
118
|
+
* accepted so existing callers keep working; it is stored as an analysis
|
|
119
|
+
* finding with no evidence attribution.
|
|
120
|
+
*/
|
|
121
|
+
transitionToFinding(id, finding) {
|
|
122
|
+
const updated = transitionInvestigation(this.require(id), 'FINDING');
|
|
123
|
+
updated.finding = typeof finding === 'string'
|
|
124
|
+
? {
|
|
125
|
+
id: `finding_${Date.now()}`,
|
|
126
|
+
investigationId: id,
|
|
127
|
+
statement: finding,
|
|
128
|
+
origin: 'feltdb_analysis',
|
|
129
|
+
confidence: 'medium',
|
|
130
|
+
derivedFromEvidence: (updated.evidence || []).map(item => item.id),
|
|
131
|
+
createdAt: Date.now(),
|
|
132
|
+
}
|
|
133
|
+
: finding;
|
|
134
|
+
this.persistInvestigation(updated);
|
|
135
|
+
return updated;
|
|
136
|
+
}
|
|
137
|
+
// --- remediation phase --------------------------------------------------
|
|
138
|
+
/**
|
|
139
|
+
* FINDING -> PROPOSED and NOT_STARTED -> SENT_TO_AGENT.
|
|
140
|
+
*
|
|
141
|
+
* Records the repository state at handoff so a later comparison can prove
|
|
142
|
+
* what the agent changed.
|
|
143
|
+
*/
|
|
144
|
+
sendToAgent(id, contract, gitBefore) {
|
|
145
|
+
const investigation = this.require(id);
|
|
146
|
+
const proposed = transitionInvestigation(investigation, 'PROPOSED');
|
|
147
|
+
const remediation = createRemediationContract({
|
|
148
|
+
...contract,
|
|
149
|
+
originalObservation: contract.originalObservation || investigation.originalObservation,
|
|
150
|
+
});
|
|
151
|
+
proposed.remediationContract = remediation;
|
|
152
|
+
proposed.remediationContractId = remediation.id;
|
|
153
|
+
const sent = transitionRemediation(proposed, 'SENT_TO_AGENT');
|
|
154
|
+
if (gitBefore) {
|
|
155
|
+
sent.remediation = { investigationId: id, gitBefore, changedPaths: [] };
|
|
156
|
+
}
|
|
157
|
+
this.persistInvestigation(sent);
|
|
158
|
+
return sent;
|
|
159
|
+
}
|
|
160
|
+
/** SENT_TO_AGENT -> IMPLEMENTING. The agent has started work. */
|
|
161
|
+
markImplementing(id) {
|
|
162
|
+
const updated = transitionRemediation(this.require(id), 'IMPLEMENTING');
|
|
163
|
+
this.persistInvestigation(updated);
|
|
164
|
+
return updated;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* IMPLEMENTING -> CHANGES_DETECTED, using observable repository state.
|
|
168
|
+
*
|
|
169
|
+
* Returns the investigation unchanged when the repository has not moved:
|
|
170
|
+
* FeltDB will not advance the lifecycle on an agent's say-so.
|
|
171
|
+
*/
|
|
172
|
+
detectRepositoryChange(id, projectDir) {
|
|
173
|
+
const investigation = this.require(id);
|
|
174
|
+
const before = investigation.remediation?.gitBefore;
|
|
175
|
+
if (!before)
|
|
176
|
+
throw new Error(`Investigation ${id} has no repository baseline; call sendToAgent with gitBefore`);
|
|
177
|
+
const tracking = trackRemediation(id, before, projectDir);
|
|
178
|
+
if (!tracking.changeIdentity) {
|
|
179
|
+
investigation.remediation = tracking;
|
|
180
|
+
this.persistInvestigation(investigation);
|
|
181
|
+
return investigation;
|
|
182
|
+
}
|
|
183
|
+
const updated = transitionRemediation(investigation, 'CHANGES_DETECTED');
|
|
184
|
+
updated.remediation = tracking;
|
|
185
|
+
this.persistInvestigation(updated);
|
|
186
|
+
return updated;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* CHANGES_DETECTED -> IMPLEMENTATION_COMPLETE, and the verification state
|
|
190
|
+
* machine becomes ready to watch the runtime.
|
|
191
|
+
*
|
|
192
|
+
* A detected repository change means remediation happened. It does not mean
|
|
193
|
+
* the defect is fixed, so this only opens verification — it never concludes it.
|
|
194
|
+
*/
|
|
195
|
+
completeImplementation(id) {
|
|
196
|
+
const complete = transitionRemediation(this.require(id), 'IMPLEMENTATION_COMPLETE');
|
|
197
|
+
const waiting = transitionVerification(complete, 'WAITING_FOR_RUNTIME');
|
|
198
|
+
this.persistInvestigation(waiting);
|
|
199
|
+
return waiting;
|
|
200
|
+
}
|
|
201
|
+
// --- verification phase -------------------------------------------------
|
|
202
|
+
/**
|
|
203
|
+
* Evaluate the verification contract against a runtime observation.
|
|
204
|
+
*
|
|
205
|
+
* The outcome is decided by the observation alone. A missing or
|
|
206
|
+
* non-matching observation yields INCONCLUSIVE; it never yields VERIFIED.
|
|
207
|
+
*/
|
|
208
|
+
recordVerification(id, contract, observation) {
|
|
209
|
+
let current = this.require(id);
|
|
210
|
+
if (current.verificationState === 'NOT_READY')
|
|
211
|
+
current = transitionVerification(current, 'WAITING_FOR_RUNTIME');
|
|
212
|
+
if (current.verificationState === 'WAITING_FOR_RUNTIME')
|
|
213
|
+
current = transitionVerification(current, 'VERIFYING');
|
|
214
|
+
const attempt = evaluateVerification(contract, observation);
|
|
215
|
+
const settled = transitionVerification(current, attempt.result);
|
|
216
|
+
settled.verificationId = attempt.verificationId;
|
|
217
|
+
settled.verificationAttempt = attempt;
|
|
218
|
+
if (observation) {
|
|
219
|
+
settled.verificationObservation = observation;
|
|
220
|
+
settled.observationIds = [...new Set([
|
|
221
|
+
settled.observationId,
|
|
222
|
+
...(settled.observationIds || []),
|
|
223
|
+
observation.observationId,
|
|
224
|
+
])];
|
|
225
|
+
}
|
|
226
|
+
this.persistInvestigation(settled);
|
|
227
|
+
return settled;
|
|
228
|
+
}
|
|
229
|
+
// --- store facts --------------------------------------------------------
|
|
230
|
+
/**
|
|
231
|
+
* Measure the durable development-plane store.
|
|
232
|
+
*
|
|
233
|
+
* This is an empirical baseline for durability, queryability, and restart
|
|
234
|
+
* cost. It is not a scale claim.
|
|
235
|
+
*/
|
|
236
|
+
metrics() {
|
|
237
|
+
const startedAt = Date.now();
|
|
238
|
+
const investigations = this.listInvestigations();
|
|
239
|
+
const loadMilliseconds = Date.now() - startedAt;
|
|
240
|
+
let serializedBytes = 0;
|
|
241
|
+
if (fs.existsSync(this.investigationsDir)) {
|
|
242
|
+
for (const file of fs.readdirSync(this.investigationsDir).filter(name => name.endsWith('.json'))) {
|
|
243
|
+
serializedBytes += fs.statSync(path.join(this.investigationsDir, file)).size;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
investigations: investigations.length,
|
|
248
|
+
observations: investigations.reduce((total, item) => total + new Set([
|
|
249
|
+
item.observationId,
|
|
250
|
+
...(item.observationIds || []),
|
|
251
|
+
item.verificationObservation?.observationId,
|
|
252
|
+
].filter(Boolean)).size, 0),
|
|
253
|
+
evidenceRecords: investigations.reduce((total, item) => total + (item.evidence?.length || 0), 0),
|
|
254
|
+
verifiedInvestigations: investigations.filter(item => item.verificationState === 'VERIFIED').length,
|
|
255
|
+
serializedBytes,
|
|
256
|
+
loadMilliseconds,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
}
|