@descryy/runtime-evidence-store 0.0.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/dist/evidence-store.d.ts +204 -0
- package/dist/evidence-store.d.ts.map +1 -0
- package/dist/evidence-store.js +511 -0
- package/dist/evidence-store.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/redaction.d.ts +120 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +337 -0
- package/dist/redaction.js.map +1 -0
- package/dist/replay.d.ts +118 -0
- package/dist/replay.d.ts.map +1 -0
- package/dist/replay.js +104 -0
- package/dist/replay.js.map +1 -0
- package/package.json +29 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evidence store (plan §13; plan §36 gate 9 "evidence persistence").
|
|
3
|
+
* Persistence keyed by executionId, backed by `node:sqlite` — mirrors
|
|
4
|
+
* descry-core's choice (CLAUDE.md: "node:sqlite behind a driver
|
|
5
|
+
* interface") for the same reason: zero native deps, no node-gyp-per-
|
|
6
|
+
* platform distribution problem. A full swappable-driver abstraction
|
|
7
|
+
* (descry-core's `SqlDriver`) is skipped here — one table, no real
|
|
8
|
+
* swap-target in view — but the row-rehydration pattern is kept:
|
|
9
|
+
* `node:sqlite` returns null-prototype row objects, which trips up
|
|
10
|
+
* `JSON.stringify` and structural-equality checks in ways that are easy to
|
|
11
|
+
* miss, so every row is spread into a plain object before use.
|
|
12
|
+
*
|
|
13
|
+
* `node:sqlite` is experimental in this Node version and prints an
|
|
14
|
+
* `ExperimentalWarning`. descry-core's own policy is to suppress that only
|
|
15
|
+
* at a CLI entrypoint, never in library code or tests — this package has
|
|
16
|
+
* no entrypoint of its own, so nothing here suppresses it.
|
|
17
|
+
*/
|
|
18
|
+
import { type Evidence, type EvidenceSource, type RuntimeEventType } from "@descryy/runtime-contracts";
|
|
19
|
+
import { type RedactionPolicy } from "./redaction.ts";
|
|
20
|
+
import { type EnvironmentFingerprint, type RecordedExecution, type Replay } from "./replay.ts";
|
|
21
|
+
export interface EvidenceStoreOptions {
|
|
22
|
+
/** A file path, or ":memory:" for tests. Persistence across process restarts (plan §36 gate 9) requires a real path. */
|
|
23
|
+
readonly path: string;
|
|
24
|
+
/** Omit for the restricted default (RedactionPolicy's own defaults) -- a caller opts into anything looser, never the reverse. */
|
|
25
|
+
readonly redactionPolicy?: RedactionPolicy;
|
|
26
|
+
/**
|
|
27
|
+
* How long evidence survives a purgeExpired() call, in milliseconds
|
|
28
|
+
* since its own `timestamp`. Null/omitted: no retention limit, nothing
|
|
29
|
+
* is ever purged automatically -- purgeExpired() is only ever called
|
|
30
|
+
* explicitly by a caller, never on a timer this store owns itself.
|
|
31
|
+
* Orthogonal to replay (plan §30, not built here): retention controls
|
|
32
|
+
* how long evidence is kept, not what a replay does with what remains.
|
|
33
|
+
*/
|
|
34
|
+
readonly retentionMs?: number;
|
|
35
|
+
}
|
|
36
|
+
export type EvidenceInput = Omit<Evidence, "evidenceId" | "redactionStatus">;
|
|
37
|
+
/**
|
|
38
|
+
* Criteria for `EvidenceStore.query`. Every field combines with AND, and
|
|
39
|
+
* every match is exact — see `query`'s own header for why `executionId` is
|
|
40
|
+
* required-but-nullable rather than optional.
|
|
41
|
+
*/
|
|
42
|
+
export interface EvidenceQuery {
|
|
43
|
+
/** Required. `null` is a deliberate statement that a cross-execution answer is wanted; there is no honest default. */
|
|
44
|
+
readonly executionId: string | null;
|
|
45
|
+
readonly traceId?: string;
|
|
46
|
+
readonly requestId?: string;
|
|
47
|
+
readonly correlationId?: string;
|
|
48
|
+
readonly service?: string;
|
|
49
|
+
readonly source?: EvidenceSource;
|
|
50
|
+
readonly eventType?: RuntimeEventType;
|
|
51
|
+
readonly graphNodeId?: string;
|
|
52
|
+
/** Matches `sourceLocation.file` exactly. Evidence with no resolved location never matches. */
|
|
53
|
+
readonly sourceFile?: string;
|
|
54
|
+
/** Matches `sourceLocation.line` exactly. */
|
|
55
|
+
readonly sourceLine?: number;
|
|
56
|
+
/** Inclusive lower bound on `timestamp` (ISO-8601). */
|
|
57
|
+
readonly from?: string;
|
|
58
|
+
/** Inclusive upper bound on `timestamp` (ISO-8601). */
|
|
59
|
+
readonly to?: string;
|
|
60
|
+
readonly limit?: number;
|
|
61
|
+
}
|
|
62
|
+
export declare class EvidenceStore {
|
|
63
|
+
#private;
|
|
64
|
+
constructor(options: EvidenceStoreOptions);
|
|
65
|
+
/**
|
|
66
|
+
* The only way to persist evidence. Redaction runs here, unconditionally
|
|
67
|
+
* — `EvidenceInput` has no `redactionStatus` field a caller could set,
|
|
68
|
+
* so there is no way to write a record claiming redaction happened
|
|
69
|
+
* without redact() actually having run. This is the structural
|
|
70
|
+
* guarantee plan §28 asks for: "raw runtime evidence must not
|
|
71
|
+
* automatically become unrestricted AI context."
|
|
72
|
+
*/
|
|
73
|
+
write(input: EvidenceInput): Evidence;
|
|
74
|
+
/** Every item this execution produced, oldest first — retrievable after the execution has ended, as long as the store is reopened against the same file (plan §36 gate 9). */
|
|
75
|
+
getByExecution(executionId: string): Evidence[];
|
|
76
|
+
getById(evidenceId: string): Evidence | null;
|
|
77
|
+
/**
|
|
78
|
+
* Every row whose enum fields this build cannot interpret (§3).
|
|
79
|
+
*
|
|
80
|
+
* The explicit runtime path for an unknown event. Reads the raw columns
|
|
81
|
+
* without rehydrating, so it works on precisely the store that `query()`
|
|
82
|
+
* refuses — a caller that hits the refusal can call this to learn which
|
|
83
|
+
* rows, which fields and which values, instead of having a throw and no
|
|
84
|
+
* next step.
|
|
85
|
+
*
|
|
86
|
+
* Deliberately reports rather than repairs. Rewriting an unknown
|
|
87
|
+
* `event_type` to something this build knows would be inventing an
|
|
88
|
+
* observation, and deleting the row would destroy evidence that a newer
|
|
89
|
+
* build can read perfectly well.
|
|
90
|
+
*/
|
|
91
|
+
integrityCheck(): readonly {
|
|
92
|
+
readonly evidenceId: string;
|
|
93
|
+
readonly field: string;
|
|
94
|
+
readonly value: string;
|
|
95
|
+
}[];
|
|
96
|
+
/**
|
|
97
|
+
* Persist the execution behind an `executionId` (§24).
|
|
98
|
+
*
|
|
99
|
+
* **The configuration is redacted on the way in, and that is not an
|
|
100
|
+
* over-application of §20.** Every one of §20's rows is about an evidence
|
|
101
|
+
* payload, but `configuration.services[x].command` is a command line, and
|
|
102
|
+
* a command line is one of the classic places a credential travels:
|
|
103
|
+
* `node server.mjs --api-key=…`. The same `redact()` the evidence write
|
|
104
|
+
* path uses runs here, on the same terms, and `redactedKeys` reports what
|
|
105
|
+
* it caught rather than leaving a caller to guess.
|
|
106
|
+
*
|
|
107
|
+
* Re-recording the same `executionId` replaces the row. An execution is
|
|
108
|
+
* recorded once by the component that owns it; a second record for the
|
|
109
|
+
* same id is a re-run of that recording, not a second execution.
|
|
110
|
+
*/
|
|
111
|
+
recordExecution(input: {
|
|
112
|
+
readonly executionId: string;
|
|
113
|
+
readonly application: string;
|
|
114
|
+
readonly repository: string;
|
|
115
|
+
readonly commit: string;
|
|
116
|
+
readonly configuration: unknown;
|
|
117
|
+
readonly recordedAt?: string;
|
|
118
|
+
readonly environment?: EnvironmentFingerprint;
|
|
119
|
+
}): RecordedExecution;
|
|
120
|
+
getExecution(executionId: string): RecordedExecution | null;
|
|
121
|
+
/**
|
|
122
|
+
* Replay an execution's evidence (§24).
|
|
123
|
+
*
|
|
124
|
+
* **Replays evidence, not the application.** Nothing is spawned, driven or
|
|
125
|
+
* reissued; this returns what was recorded, in recorded order, so
|
|
126
|
+
* correlation, findings and an evidence package can be re-derived without
|
|
127
|
+
* booting anything. A replay that quietly re-executed would produce a *new*
|
|
128
|
+
* observation while claiming to reproduce an old one.
|
|
129
|
+
*
|
|
130
|
+
* Throws for an unrecorded execution rather than returning an empty
|
|
131
|
+
* replay. Evidence rows can exist for an `executionId` that was never
|
|
132
|
+
* recorded — that is precisely the pre-§24 state — and answering with
|
|
133
|
+
* "here is your replay, minus the execution" would present the gap as a
|
|
134
|
+
* result.
|
|
135
|
+
*/
|
|
136
|
+
replay(executionId: string, currentEnvironment?: EnvironmentFingerprint): Replay;
|
|
137
|
+
/**
|
|
138
|
+
* Record that correlation resolved this evidence to a graph node.
|
|
139
|
+
*
|
|
140
|
+
* **Written because the correlation was never durable, and that made two
|
|
141
|
+
* of the evidence package's four selection tiers inert.** Found by
|
|
142
|
+
* mutation, not by reading: `assembleEvidencePackage`'s tier 2 queries
|
|
143
|
+
* `graphNodeId`, and a mutation that dropped the execution scope from that
|
|
144
|
+
* query changed nothing at all — because in the real pipeline no stored row
|
|
145
|
+
* has a `graphNodeId` to match. `graphNodeId` was set in memory at
|
|
146
|
+
* composition time, on a copy, and thrown away with the array that held it.
|
|
147
|
+
* Every downstream question of the form "what else named this node" was
|
|
148
|
+
* therefore answerable only for evidence some caller happened to still be
|
|
149
|
+
* holding.
|
|
150
|
+
*
|
|
151
|
+
* **Refuses to re-attribute to a different node.** One observation
|
|
152
|
+
* genuinely can name two graph entities (RT-052's backend exception names
|
|
153
|
+
* the endpoint in its text and the function in its stack), and the answer
|
|
154
|
+
* to that is `EvidenceAttribution` passed alongside the evidence — an
|
|
155
|
+
* output of correlation, not a property of the observation. Letting this
|
|
156
|
+
* method overwrite would silently discard the first correlation and leave
|
|
157
|
+
* the store asserting the second as if it were the only one. Idempotent
|
|
158
|
+
* for the same node, so re-running a correlation pass is safe.
|
|
159
|
+
*
|
|
160
|
+
* Throws for an unknown `evidenceId`: a caller attributing evidence this
|
|
161
|
+
* store does not hold has a bug, and writing nothing would hide it.
|
|
162
|
+
*/
|
|
163
|
+
attribute(evidenceId: string, graphNodeId: string): void;
|
|
164
|
+
/**
|
|
165
|
+
* The general query surface (§19).
|
|
166
|
+
*
|
|
167
|
+
* Before this, the store answered two questions — by id and by execution —
|
|
168
|
+
* and §19 asks for six axes: trace, service, graph node, source location,
|
|
169
|
+
* time, and execution. **Five of the six did not exist**, which is
|
|
170
|
+
* invisible from "evidence persistence works" and was found by auditing
|
|
171
|
+
* the checklist rather than by anything failing.
|
|
172
|
+
*
|
|
173
|
+
* **`executionId` is required and may be explicitly `null`, and that is
|
|
174
|
+
* the load-bearing design choice here.** Every other field is optional.
|
|
175
|
+
* Evidence not leaking between executions is a guarantee this repo
|
|
176
|
+
* enforces elsewhere (`composeFinding` excludes foreign executions with a
|
|
177
|
+
* named reason), and a query is the obvious way to lose it: ask for a
|
|
178
|
+
* trace id and quietly receive a prior run's evidence too. Making the
|
|
179
|
+
* field optional-with-a-default would mean the safe behaviour depends on
|
|
180
|
+
* remembering to pass something, which is how the default gets forgotten
|
|
181
|
+
* exactly once. Passing `null` is a deliberate statement that a
|
|
182
|
+
* cross-execution answer is wanted — the same discipline
|
|
183
|
+
* `resolveSymbolNode` applies to `repo`, where there is no honest default
|
|
184
|
+
* either.
|
|
185
|
+
*
|
|
186
|
+
* Criteria combine with AND. An empty criteria object plus a null
|
|
187
|
+
* execution returns everything, which is a real request (a store dump)
|
|
188
|
+
* and is spelled out rather than reachable by accident.
|
|
189
|
+
*/
|
|
190
|
+
query(criteria: EvidenceQuery): Evidence[];
|
|
191
|
+
/**
|
|
192
|
+
* Deletes every row whose own `timestamp` is older than `retentionMs`
|
|
193
|
+
* (constructor option) relative to `now` (defaults to the real current
|
|
194
|
+
* time; overridable so a test can purge without a real sleep). Never
|
|
195
|
+
* called automatically -- no timer, no call from `write()` -- a caller
|
|
196
|
+
* decides when retention actually runs. Throws rather than silently
|
|
197
|
+
* no-op-ing if `retentionMs` was never configured: a caller explicitly
|
|
198
|
+
* asking to purge with no policy in place is a configuration mistake
|
|
199
|
+
* worth surfacing, not masking.
|
|
200
|
+
*/
|
|
201
|
+
purgeExpired(now?: Date): number;
|
|
202
|
+
close(): void;
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=evidence-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evidence-store.d.ts","sourceRoot":"","sources":["../src/evidence-store.ts"],"names":[],"mappings":"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"}
|