@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.
Files changed (122) hide show
  1. package/dist/cli/application-lifecycle.js +112 -0
  2. package/dist/cli/application.js +10 -0
  3. package/dist/cli/commands.js +407 -119
  4. package/dist/cli/development-session.js +54 -0
  5. package/dist/cli/index.js +1 -1
  6. package/dist/cli/workspace-integration.js +146 -20
  7. package/dist/collection.d.ts +110 -2
  8. package/dist/collection.d.ts.map +1 -1
  9. package/dist/collection.js +228 -3
  10. package/dist/create/cli.js +4 -2
  11. package/dist/create/create.js +42 -6
  12. package/dist/create/frameworks.js +18 -0
  13. package/dist/create/package-versions.js +1 -1
  14. package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +408 -178
  15. package/dist/create/server-source/crates/feltdb/src/lib.rs +49 -5
  16. package/dist/create/server-source/crates/feltdb/src/multi_operation_transaction.rs +659 -0
  17. package/dist/create/server-source/crates/feltdb/src/p1_application_atomicity.rs +285 -0
  18. package/dist/create/server-source/crates/feltdb/src/p1_atomicity_acceptance.rs +582 -0
  19. package/dist/create/server-source/crates/feltdb-server/src/main.rs +141 -0
  20. package/dist/create/workspace-initialization.js +4 -5
  21. package/dist/db.d.ts +63 -1
  22. package/dist/db.d.ts.map +1 -1
  23. package/dist/db.js +145 -4
  24. package/dist/development-runtime-bridge.d.ts +60 -0
  25. package/dist/development-runtime-bridge.d.ts.map +1 -0
  26. package/dist/development-runtime-bridge.js +171 -0
  27. package/dist/embedded-transaction.d.ts +77 -0
  28. package/dist/embedded-transaction.d.ts.map +1 -0
  29. package/dist/embedded-transaction.js +102 -0
  30. package/dist/feltdb.d.ts +49 -1
  31. package/dist/feltdb.d.ts.map +1 -1
  32. package/dist/file-db.d.ts +33 -0
  33. package/dist/file-db.d.ts.map +1 -1
  34. package/dist/file-db.js +128 -4
  35. package/dist/freshness.d.ts +119 -0
  36. package/dist/freshness.d.ts.map +1 -0
  37. package/dist/freshness.js +115 -0
  38. package/dist/http-db.d.ts +48 -0
  39. package/dist/http-db.d.ts.map +1 -1
  40. package/dist/http-db.js +77 -1
  41. package/dist/index-backend.d.ts +8 -0
  42. package/dist/index-backend.d.ts.map +1 -1
  43. package/dist/index-backend.js +13 -0
  44. package/dist/index-core.d.ts +80 -0
  45. package/dist/index-core.d.ts.map +1 -0
  46. package/dist/index-core.js +74 -0
  47. package/dist/index-manager.d.ts +11 -0
  48. package/dist/index-manager.d.ts.map +1 -1
  49. package/dist/index-manager.js +30 -13
  50. package/dist/index-types.d.ts +17 -0
  51. package/dist/index-types.d.ts.map +1 -1
  52. package/dist/index.browser.d.ts +14 -0
  53. package/dist/index.browser.d.ts.map +1 -0
  54. package/dist/index.browser.js +13 -0
  55. package/dist/index.d.ts +8 -59
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +11 -54
  58. package/dist/indexeddb-db.d.ts +32 -0
  59. package/dist/indexeddb-db.d.ts.map +1 -1
  60. package/dist/indexeddb-db.js +151 -1
  61. package/dist/memory-db.d.ts +26 -0
  62. package/dist/memory-db.d.ts.map +1 -1
  63. package/dist/memory-db.js +86 -0
  64. package/dist/runtime-registry.d.ts +32 -0
  65. package/dist/runtime-registry.d.ts.map +1 -0
  66. package/dist/runtime-registry.js +38 -0
  67. package/dist/studio/app.d.ts +2 -1
  68. package/dist/studio/app.d.ts.map +1 -1
  69. package/dist/studio/components/StateExplorer.d.ts +2 -1
  70. package/dist/studio/components/StateExplorer.d.ts.map +1 -1
  71. package/dist/studio/components/index.js +1 -1
  72. package/dist/studio/{components-9kDSWiGL.js → components-C5p2TfIU.js} +25 -14
  73. package/dist/studio/index.js +234 -160
  74. package/dist/studio-app/assets/{feltdb_wasm-B4wq4mqp.js → feltdb_wasm-CJv3wHzi.js} +1 -1
  75. package/dist/studio-app/assets/feltdb_wasm_bg-C8TG8r2n.wasm +0 -0
  76. package/dist/studio-app/assets/index-DospFFYE.js +28 -0
  77. package/dist/studio-app/index.html +1 -1
  78. package/dist/transaction.d.ts +98 -0
  79. package/dist/transaction.d.ts.map +1 -0
  80. package/dist/transaction.js +86 -0
  81. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  82. package/dist/workspace/browser.d.ts +22 -0
  83. package/dist/workspace/browser.d.ts.map +1 -0
  84. package/dist/workspace/browser.js +17 -0
  85. package/dist/workspace/development-node.d.ts +16 -0
  86. package/dist/workspace/development-node.d.ts.map +1 -1
  87. package/dist/workspace/development-node.js +85 -1
  88. package/dist/workspace/git-identity.d.ts +35 -0
  89. package/dist/workspace/git-identity.d.ts.map +1 -0
  90. package/dist/workspace/git-identity.js +81 -0
  91. package/dist/workspace/index.d.ts +12 -2
  92. package/dist/workspace/index.d.ts.map +1 -1
  93. package/dist/workspace/index.js +7 -1
  94. package/dist/workspace/investigation-analysis.d.ts +47 -0
  95. package/dist/workspace/investigation-analysis.d.ts.map +1 -0
  96. package/dist/workspace/investigation-analysis.js +181 -0
  97. package/dist/workspace/investigation-lifecycle-manager.d.ts +88 -0
  98. package/dist/workspace/investigation-lifecycle-manager.d.ts.map +1 -0
  99. package/dist/workspace/investigation-lifecycle-manager.js +259 -0
  100. package/dist/workspace/investigation-supervisor.d.ts +100 -0
  101. package/dist/workspace/investigation-supervisor.d.ts.map +1 -0
  102. package/dist/workspace/investigation-supervisor.js +238 -0
  103. package/dist/workspace/runtime-investigation.d.ts +5 -1
  104. package/dist/workspace/runtime-investigation.d.ts.map +1 -1
  105. package/dist/workspace/runtime-investigation.js +50 -21
  106. package/dist/workspace/runtime-observation.d.ts +82 -0
  107. package/dist/workspace/runtime-observation.d.ts.map +1 -0
  108. package/dist/workspace/runtime-observation.js +170 -0
  109. package/dist/workspace/runtime-observer.d.ts +57 -0
  110. package/dist/workspace/runtime-observer.d.ts.map +1 -0
  111. package/dist/workspace/runtime-observer.js +272 -0
  112. package/dist/workspace/workspace-connection.d.ts.map +1 -1
  113. package/dist/workspace/workspace-connection.js +14 -5
  114. package/dist/workspace/workspace-identity.d.ts +1 -1
  115. package/dist/workspace/workspace-identity.d.ts.map +1 -1
  116. package/dist/workspace/workspace-identity.js +4 -4
  117. package/dist/workspace/workspace-types.d.ts +208 -0
  118. package/dist/workspace/workspace-types.d.ts.map +1 -1
  119. package/package.json +12 -4
  120. package/dist/create/server-source/crates/feltdb/src/phase_1c_three_node.rs +0 -523
  121. package/dist/studio-app/assets/feltdb_wasm_bg-Ceyi7l21.wasm +0 -0
  122. 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
+ }