@eddacraft/anvil-kindling-integration 0.1.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/LICENSE +14 -0
- package/README.md +542 -0
- package/dist/adapter.d.ts +49 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +100 -0
- package/dist/config.d.ts +89 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +173 -0
- package/dist/emitters/action-emitter.d.ts +40 -0
- package/dist/emitters/action-emitter.d.ts.map +1 -0
- package/dist/emitters/action-emitter.js +52 -0
- package/dist/emitters/constraint-emitter.d.ts +32 -0
- package/dist/emitters/constraint-emitter.d.ts.map +1 -0
- package/dist/emitters/constraint-emitter.js +41 -0
- package/dist/emitters/error-emitter.d.ts +33 -0
- package/dist/emitters/error-emitter.d.ts.map +1 -0
- package/dist/emitters/error-emitter.js +50 -0
- package/dist/emitters/gate-emitter.d.ts +37 -0
- package/dist/emitters/gate-emitter.d.ts.map +1 -0
- package/dist/emitters/gate-emitter.js +53 -0
- package/dist/emitters/human-input-emitter.d.ts +30 -0
- package/dist/emitters/human-input-emitter.d.ts.map +1 -0
- package/dist/emitters/human-input-emitter.js +38 -0
- package/dist/emitters/index.d.ts +13 -0
- package/dist/emitters/index.d.ts.map +1 -0
- package/dist/emitters/index.js +19 -0
- package/dist/emitters/plan-emitter.d.ts +75 -0
- package/dist/emitters/plan-emitter.d.ts.map +1 -0
- package/dist/emitters/plan-emitter.js +116 -0
- package/dist/emitters/session-emitter.d.ts +57 -0
- package/dist/emitters/session-emitter.d.ts.map +1 -0
- package/dist/emitters/session-emitter.js +80 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +111 -0
- package/dist/kindling-service.d.ts +122 -0
- package/dist/kindling-service.d.ts.map +1 -0
- package/dist/kindling-service.js +203 -0
- package/dist/observation-contract.d.ts +561 -0
- package/dist/observation-contract.d.ts.map +1 -0
- package/dist/observation-contract.js +391 -0
- package/dist/query-contract.d.ts +463 -0
- package/dist/query-contract.d.ts.map +1 -0
- package/dist/query-contract.js +314 -0
- package/dist/query-limits.d.ts +40 -0
- package/dist/query-limits.d.ts.map +1 -0
- package/dist/query-limits.js +79 -0
- package/dist/query-service.d.ts +109 -0
- package/dist/query-service.d.ts.map +1 -0
- package/dist/query-service.js +140 -0
- package/dist/retention.d.ts +79 -0
- package/dist/retention.d.ts.map +1 -0
- package/dist/retention.js +81 -0
- package/dist/sensitive-data-validator.d.ts +47 -0
- package/dist/sensitive-data-validator.d.ts.map +1 -0
- package/dist/sensitive-data-validator.js +135 -0
- package/dist/status.d.ts +104 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +136 -0
- package/dist/utils/debug.d.ts +9 -0
- package/dist/utils/debug.d.ts.map +1 -0
- package/dist/utils/debug.js +55 -0
- package/package.json +114 -0
- package/src/adapter.ts +117 -0
- package/src/config.ts +202 -0
- package/src/emitters/action-emitter.ts +90 -0
- package/src/emitters/constraint-emitter.ts +73 -0
- package/src/emitters/error-emitter.ts +86 -0
- package/src/emitters/gate-emitter.ts +87 -0
- package/src/emitters/human-input-emitter.ts +71 -0
- package/src/emitters/index.ts +40 -0
- package/src/emitters/plan-emitter.ts +183 -0
- package/src/emitters/session-emitter.ts +131 -0
- package/src/index.ts +254 -0
- package/src/kindling-service.ts +272 -0
- package/src/malicious-ai.test.ts +949 -0
- package/src/observation-contract.ts +500 -0
- package/src/query-contract.ts +389 -0
- package/src/query-limits.ts +106 -0
- package/src/query-service.ts +217 -0
- package/src/retention.ts +153 -0
- package/src/sensitive-data-validator.ts +167 -0
- package/src/status.ts +221 -0
- package/src/utils/debug.ts +65 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kindling Integration (v1)
|
|
3
|
+
*
|
|
4
|
+
* This package provides the complete integration layer between Anvil and Kindling.
|
|
5
|
+
*
|
|
6
|
+
* Three surfaces:
|
|
7
|
+
*
|
|
8
|
+
* 1. Observation Contract (Write-Only)
|
|
9
|
+
* - 11 observation kinds covering session, plan, gate, action, constraint, human, error
|
|
10
|
+
* - Immutable, timestamped, linked facts
|
|
11
|
+
*
|
|
12
|
+
* 2. Query Contract (Read-Only)
|
|
13
|
+
* - 4 query scopes: session, plan, gate, action
|
|
14
|
+
* - Mandatory constraints, throttling, output guarantees
|
|
15
|
+
*
|
|
16
|
+
* 3. Service Layer (Orchestration)
|
|
17
|
+
* - KindlingService: validation, sensitive-data checks, store delegation
|
|
18
|
+
* - Emitters: fire-and-forget observation emission
|
|
19
|
+
* - KindlingQueryService: high-level query convenience methods
|
|
20
|
+
* - Configuration, retention, and query limit enforcement
|
|
21
|
+
*
|
|
22
|
+
* GOVERNING RULE:
|
|
23
|
+
* Kindling is a system of record, not a reasoning engine.
|
|
24
|
+
* Queries may retrieve facts; interpretation is the caller's responsibility.
|
|
25
|
+
*
|
|
26
|
+
* @packageDocumentation
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
// =============================================================================
|
|
30
|
+
// Observation Contract (Write-Only)
|
|
31
|
+
// =============================================================================
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
// Version
|
|
35
|
+
OBSERVATION_CONTRACT_VERSION,
|
|
36
|
+
|
|
37
|
+
// Schemas (individual)
|
|
38
|
+
SessionStartObservationSchema,
|
|
39
|
+
SessionEndObservationSchema,
|
|
40
|
+
PlanCreatedObservationSchema,
|
|
41
|
+
PlanEditedObservationSchema,
|
|
42
|
+
PlanApprovedObservationSchema,
|
|
43
|
+
PlanRejectedObservationSchema,
|
|
44
|
+
ActionExecutedObservationSchema,
|
|
45
|
+
GateEvaluatedObservationSchema,
|
|
46
|
+
ConstraintAppliedObservationSchema,
|
|
47
|
+
HumanInputObservationSchema,
|
|
48
|
+
ErrorObservationSchema,
|
|
49
|
+
|
|
50
|
+
// Schema (union)
|
|
51
|
+
ObservationSchema,
|
|
52
|
+
|
|
53
|
+
// Types
|
|
54
|
+
type SessionStartObservation,
|
|
55
|
+
type SessionEndObservation,
|
|
56
|
+
type PlanCreatedObservation,
|
|
57
|
+
type PlanEditedObservation,
|
|
58
|
+
type PlanApprovedObservation,
|
|
59
|
+
type PlanRejectedObservation,
|
|
60
|
+
type ActionExecutedObservation,
|
|
61
|
+
type GateEvaluatedObservation,
|
|
62
|
+
type ConstraintAppliedObservation,
|
|
63
|
+
type HumanInputObservation,
|
|
64
|
+
type ErrorObservation,
|
|
65
|
+
type Observation,
|
|
66
|
+
|
|
67
|
+
// Utilities
|
|
68
|
+
validateObservation,
|
|
69
|
+
containsSensitiveData,
|
|
70
|
+
} from './observation-contract.js';
|
|
71
|
+
|
|
72
|
+
// =============================================================================
|
|
73
|
+
// Query Contract (Read-Only)
|
|
74
|
+
// =============================================================================
|
|
75
|
+
|
|
76
|
+
export {
|
|
77
|
+
// Version
|
|
78
|
+
KINDLING_QUERY_CONTRACT_VERSION,
|
|
79
|
+
|
|
80
|
+
// Query scopes
|
|
81
|
+
QueryScopeSchema,
|
|
82
|
+
type QueryScope,
|
|
83
|
+
|
|
84
|
+
// Result shape
|
|
85
|
+
ResultShapeSchema,
|
|
86
|
+
type ResultShape,
|
|
87
|
+
|
|
88
|
+
// Output format
|
|
89
|
+
OutputFormatSchema,
|
|
90
|
+
type OutputFormat,
|
|
91
|
+
|
|
92
|
+
// Query requests (individual)
|
|
93
|
+
SessionQuerySchema,
|
|
94
|
+
PlanQuerySchema,
|
|
95
|
+
GateQuerySchema,
|
|
96
|
+
ActionQuerySchema,
|
|
97
|
+
|
|
98
|
+
// Query request (union)
|
|
99
|
+
QueryRequestSchema,
|
|
100
|
+
QueryRequestBaseSchema,
|
|
101
|
+
type QueryRequest,
|
|
102
|
+
type QueryRequestBase,
|
|
103
|
+
type SessionQuery,
|
|
104
|
+
type PlanQuery,
|
|
105
|
+
type GateQuery,
|
|
106
|
+
type ActionQuery,
|
|
107
|
+
|
|
108
|
+
// Query response
|
|
109
|
+
QueryResponseSchema,
|
|
110
|
+
QueryResponseMetadataSchema,
|
|
111
|
+
ProvenanceLinkSchema,
|
|
112
|
+
type QueryResponse,
|
|
113
|
+
type QueryResponseMetadata,
|
|
114
|
+
type ProvenanceLink,
|
|
115
|
+
|
|
116
|
+
// Utilities
|
|
117
|
+
validateQueryRequest,
|
|
118
|
+
validateQueryResponse,
|
|
119
|
+
} from './query-contract.js';
|
|
120
|
+
|
|
121
|
+
// Re-export Observation from query-contract for convenience
|
|
122
|
+
export { ObservationSchema as QueryObservationSchema } from './query-contract.js';
|
|
123
|
+
|
|
124
|
+
// =============================================================================
|
|
125
|
+
// Configuration (KINDLING-002)
|
|
126
|
+
// =============================================================================
|
|
127
|
+
|
|
128
|
+
export {
|
|
129
|
+
KindlingConfigSchema,
|
|
130
|
+
CaptureConfigSchema,
|
|
131
|
+
RetentionConfigSchema,
|
|
132
|
+
QueryLimitConfigSchema,
|
|
133
|
+
type KindlingConfig,
|
|
134
|
+
type CaptureConfig,
|
|
135
|
+
type RetentionConfig,
|
|
136
|
+
type QueryLimitConfig,
|
|
137
|
+
DEFAULT_KINDLING_CONFIG,
|
|
138
|
+
loadKindlingConfig,
|
|
139
|
+
shouldCapture,
|
|
140
|
+
} from './config.js';
|
|
141
|
+
|
|
142
|
+
// =============================================================================
|
|
143
|
+
// Service Layer (KINDLING-001)
|
|
144
|
+
// =============================================================================
|
|
145
|
+
|
|
146
|
+
export {
|
|
147
|
+
type IKindlingStore,
|
|
148
|
+
NoOpKindlingStore,
|
|
149
|
+
KindlingService,
|
|
150
|
+
ObservationValidationError,
|
|
151
|
+
QueryValidationError,
|
|
152
|
+
createKindlingService,
|
|
153
|
+
} from './kindling-service.js';
|
|
154
|
+
|
|
155
|
+
// =============================================================================
|
|
156
|
+
// Sensitive Data Validation (KINDLING-015)
|
|
157
|
+
// =============================================================================
|
|
158
|
+
|
|
159
|
+
export {
|
|
160
|
+
validateNoSensitiveData,
|
|
161
|
+
redactSensitiveFields,
|
|
162
|
+
type SensitiveDataValidationResult,
|
|
163
|
+
} from './sensitive-data-validator.js';
|
|
164
|
+
|
|
165
|
+
// =============================================================================
|
|
166
|
+
// Emitters (KINDLING-003 through 008)
|
|
167
|
+
// =============================================================================
|
|
168
|
+
|
|
169
|
+
export {
|
|
170
|
+
// Session
|
|
171
|
+
emitSessionStart,
|
|
172
|
+
emitSessionEnd,
|
|
173
|
+
type SessionStartContext,
|
|
174
|
+
type SessionEndOutcome,
|
|
175
|
+
|
|
176
|
+
// Gate
|
|
177
|
+
emitGateEvaluated,
|
|
178
|
+
type GateResult,
|
|
179
|
+
|
|
180
|
+
// Action
|
|
181
|
+
emitActionExecuted,
|
|
182
|
+
type ActionDetails,
|
|
183
|
+
|
|
184
|
+
// Plan
|
|
185
|
+
emitPlanCreated,
|
|
186
|
+
emitPlanEdited,
|
|
187
|
+
emitPlanApproved,
|
|
188
|
+
emitPlanRejected,
|
|
189
|
+
type PlanCreatedInput,
|
|
190
|
+
type PlanEditedInput,
|
|
191
|
+
type PlanApprovedInput,
|
|
192
|
+
type PlanRejectedInput,
|
|
193
|
+
|
|
194
|
+
// Human Input
|
|
195
|
+
emitHumanInput,
|
|
196
|
+
type HumanInputDetails,
|
|
197
|
+
|
|
198
|
+
// Constraint
|
|
199
|
+
emitConstraintApplied,
|
|
200
|
+
type ConstraintDetails,
|
|
201
|
+
|
|
202
|
+
// Error
|
|
203
|
+
emitError,
|
|
204
|
+
type ErrorDetails,
|
|
205
|
+
} from './emitters/index.js';
|
|
206
|
+
|
|
207
|
+
// =============================================================================
|
|
208
|
+
// Query Service (KINDLING-009)
|
|
209
|
+
// =============================================================================
|
|
210
|
+
|
|
211
|
+
export {
|
|
212
|
+
KindlingQueryService,
|
|
213
|
+
type QueryOptions,
|
|
214
|
+
type SessionQueryOptions,
|
|
215
|
+
type PlanQueryOptions,
|
|
216
|
+
type ActionQueryOptions,
|
|
217
|
+
} from './query-service.js';
|
|
218
|
+
|
|
219
|
+
// =============================================================================
|
|
220
|
+
// Query Limits (KINDLING-010)
|
|
221
|
+
// =============================================================================
|
|
222
|
+
|
|
223
|
+
export { enforceQueryLimits, limitsFromConfig, type QueryLimits } from './query-limits.js';
|
|
224
|
+
|
|
225
|
+
// =============================================================================
|
|
226
|
+
// Retention (KINDLING-016)
|
|
227
|
+
// =============================================================================
|
|
228
|
+
|
|
229
|
+
export {
|
|
230
|
+
type IRetentionCapableStore,
|
|
231
|
+
type StorageStats,
|
|
232
|
+
type PruneResult,
|
|
233
|
+
isRetentionCapable,
|
|
234
|
+
pruneOldObservations,
|
|
235
|
+
getStorageStats,
|
|
236
|
+
} from './retention.js';
|
|
237
|
+
|
|
238
|
+
// =============================================================================
|
|
239
|
+
// Status Utility (KINDLING-014)
|
|
240
|
+
// =============================================================================
|
|
241
|
+
|
|
242
|
+
export {
|
|
243
|
+
getKindlingStatus,
|
|
244
|
+
formatKindlingStatus,
|
|
245
|
+
type KindlingStatus,
|
|
246
|
+
type KindlingStatusConfig,
|
|
247
|
+
type KindlingStatusStore,
|
|
248
|
+
} from './status.js';
|
|
249
|
+
|
|
250
|
+
// =============================================================================
|
|
251
|
+
// Adapter (Anvil → Kindling Bridge)
|
|
252
|
+
// =============================================================================
|
|
253
|
+
|
|
254
|
+
export { AnvilKindlingAdapter, type AnvilKindlingAdapterConfig } from './adapter.js';
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KindlingService (KINDLING-001)
|
|
3
|
+
*
|
|
4
|
+
* Core service wrapper that mediates between Anvil and the Kindling store.
|
|
5
|
+
* Handles validation, sensitive-data checks, and delegation to the store adapter.
|
|
6
|
+
*
|
|
7
|
+
* The service is built against the abstract `IKindlingStore` interface so that
|
|
8
|
+
* it compiles without @kindling/core or @kindling/store-sqlite installed.
|
|
9
|
+
* The actual storage backend is plugged in at runtime via the factory function.
|
|
10
|
+
*
|
|
11
|
+
* When no store is provided, the service operates in "disabled mode" using a
|
|
12
|
+
* no-op store that silently discards all observations.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { Observation } from './observation-contract.js';
|
|
16
|
+
import { validateObservation } from './observation-contract.js';
|
|
17
|
+
import type { QueryRequest, QueryResponse } from './query-contract.js';
|
|
18
|
+
import { QueryRequestSchema } from './query-contract.js';
|
|
19
|
+
import type { KindlingConfig } from './config.js';
|
|
20
|
+
import { DEFAULT_KINDLING_CONFIG, shouldCapture } from './config.js';
|
|
21
|
+
import { validateNoSensitiveData, redactSensitiveFields } from './sensitive-data-validator.js';
|
|
22
|
+
import { createDebugger } from './utils/debug.js';
|
|
23
|
+
|
|
24
|
+
const debug = createDebugger('kindling');
|
|
25
|
+
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// Store Interface (Abstract Adapter)
|
|
28
|
+
// =============================================================================
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Abstract storage adapter interface.
|
|
32
|
+
*
|
|
33
|
+
* Implementations must provide emit (write), query (read), and close (cleanup).
|
|
34
|
+
* This decouples the service layer from any concrete Kindling SDK dependency.
|
|
35
|
+
*/
|
|
36
|
+
export interface IKindlingStore {
|
|
37
|
+
/**
|
|
38
|
+
* Persist an observation to the store.
|
|
39
|
+
* The observation has already been validated and redacted by the service layer.
|
|
40
|
+
*/
|
|
41
|
+
emit(observation: Observation): Promise<void>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Execute a bounded query against the store.
|
|
45
|
+
* The request has already been validated by the service layer.
|
|
46
|
+
*/
|
|
47
|
+
query(request: QueryRequest): Promise<QueryResponse>;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Release resources (close database connections, flush buffers, etc.)
|
|
51
|
+
*/
|
|
52
|
+
close(): Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// =============================================================================
|
|
56
|
+
// No-Op Store (Disabled Mode)
|
|
57
|
+
// =============================================================================
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* No-op store used when Kindling is disabled or no store is provided.
|
|
61
|
+
* All operations succeed silently without side effects.
|
|
62
|
+
*/
|
|
63
|
+
export class NoOpKindlingStore implements IKindlingStore {
|
|
64
|
+
async emit(_observation: Observation): Promise<void> {
|
|
65
|
+
// Intentionally empty -- disabled mode
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async query(_request: QueryRequest): Promise<QueryResponse> {
|
|
69
|
+
return {
|
|
70
|
+
metadata: {
|
|
71
|
+
query_id: crypto.randomUUID(),
|
|
72
|
+
executed_at: new Date().toISOString(),
|
|
73
|
+
contract_version: '1.0.0',
|
|
74
|
+
result_count: 0,
|
|
75
|
+
truncated: false,
|
|
76
|
+
truncation_reason: 'none',
|
|
77
|
+
},
|
|
78
|
+
observations: [],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async close(): Promise<void> {
|
|
83
|
+
// Intentionally empty -- nothing to close
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// =============================================================================
|
|
88
|
+
// Service Errors
|
|
89
|
+
// =============================================================================
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Error thrown when observation validation fails
|
|
93
|
+
*/
|
|
94
|
+
export class ObservationValidationError extends Error {
|
|
95
|
+
constructor(
|
|
96
|
+
message: string,
|
|
97
|
+
public readonly issues: string[]
|
|
98
|
+
) {
|
|
99
|
+
super(message);
|
|
100
|
+
this.name = 'ObservationValidationError';
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Error thrown when query validation fails
|
|
106
|
+
*/
|
|
107
|
+
export class QueryValidationError extends Error {
|
|
108
|
+
constructor(message: string) {
|
|
109
|
+
super(message);
|
|
110
|
+
this.name = 'QueryValidationError';
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// =============================================================================
|
|
115
|
+
// KindlingService
|
|
116
|
+
// =============================================================================
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Core Kindling service that wraps the store adapter with validation,
|
|
120
|
+
* sensitive-data checks, and config-driven behavior.
|
|
121
|
+
*/
|
|
122
|
+
export class KindlingService {
|
|
123
|
+
private readonly store: IKindlingStore;
|
|
124
|
+
private readonly config: KindlingConfig;
|
|
125
|
+
private closed = false;
|
|
126
|
+
|
|
127
|
+
constructor(store: IKindlingStore, config: KindlingConfig) {
|
|
128
|
+
this.store = store;
|
|
129
|
+
this.config = config;
|
|
130
|
+
debug('KindlingService created', { enabled: config.enabled });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Whether the service is enabled (will actually emit observations)
|
|
135
|
+
*/
|
|
136
|
+
get enabled(): boolean {
|
|
137
|
+
return this.config.enabled;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The active configuration
|
|
142
|
+
*/
|
|
143
|
+
get configuration(): Readonly<KindlingConfig> {
|
|
144
|
+
return this.config;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Emit an observation to the Kindling store.
|
|
149
|
+
*
|
|
150
|
+
* This method is designed to be async and non-blocking. It:
|
|
151
|
+
* 1. Checks if the service is enabled and the observation kind should be captured
|
|
152
|
+
* 2. Validates the observation against the contract schema
|
|
153
|
+
* 3. Checks for and redacts sensitive data
|
|
154
|
+
* 4. Delegates to the store adapter
|
|
155
|
+
*
|
|
156
|
+
* Validation errors are thrown. Store errors are thrown (callers should catch
|
|
157
|
+
* if they want fire-and-forget semantics -- see emitters).
|
|
158
|
+
*
|
|
159
|
+
* @param observation - The observation to emit
|
|
160
|
+
* @throws ObservationValidationError if the observation is invalid
|
|
161
|
+
*/
|
|
162
|
+
async emit(observation: Observation): Promise<void> {
|
|
163
|
+
if (this.closed) {
|
|
164
|
+
debug('emit skipped: service is closed');
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Check if this kind should be captured
|
|
169
|
+
if (!shouldCapture(this.config, observation.kind)) {
|
|
170
|
+
debug('emit skipped: kind not captured', observation.kind);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Validate against the contract schema
|
|
175
|
+
const validation = validateObservation(observation);
|
|
176
|
+
if (!validation.success) {
|
|
177
|
+
debug('emit validation failed', validation.error);
|
|
178
|
+
throw new ObservationValidationError(`Invalid observation: ${validation.error}`, [
|
|
179
|
+
validation.error ?? 'Unknown validation error',
|
|
180
|
+
]);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Check for sensitive data and redact if found
|
|
184
|
+
const sensitiveCheck = validateNoSensitiveData(observation);
|
|
185
|
+
let safeObservation = observation;
|
|
186
|
+
|
|
187
|
+
if (sensitiveCheck.hasSensitiveData) {
|
|
188
|
+
debug('sensitive data detected, redacting', sensitiveCheck.issues);
|
|
189
|
+
safeObservation = redactSensitiveFields(observation);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Delegate to store (async, non-blocking from caller's perspective)
|
|
193
|
+
debug('emitting observation', { kind: observation.kind, session_id: observation.session_id });
|
|
194
|
+
await this.store.emit(safeObservation);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Execute a query against the Kindling store.
|
|
199
|
+
*
|
|
200
|
+
* Validates the query request against the contract schema and enforces
|
|
201
|
+
* configured query limits before delegating to the store.
|
|
202
|
+
*
|
|
203
|
+
* @param request - The query request
|
|
204
|
+
* @returns Query response with observations
|
|
205
|
+
* @throws QueryValidationError if the request is invalid
|
|
206
|
+
*/
|
|
207
|
+
async query(request: QueryRequest): Promise<QueryResponse> {
|
|
208
|
+
if (this.closed) {
|
|
209
|
+
debug('query rejected: service is closed');
|
|
210
|
+
throw new QueryValidationError('Service is closed');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Validate the request
|
|
214
|
+
const validation = QueryRequestSchema.safeParse(request);
|
|
215
|
+
if (!validation.success) {
|
|
216
|
+
debug('query validation failed', validation.error.format());
|
|
217
|
+
throw new QueryValidationError(
|
|
218
|
+
`Invalid query request: ${validation.error.format()._errors.join(', ')}`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
debug('executing query', { scope: request.scope });
|
|
223
|
+
|
|
224
|
+
// Enforce configured query limits (use config defaults if request has higher values)
|
|
225
|
+
const limitedRequest = {
|
|
226
|
+
...validation.data,
|
|
227
|
+
max_results: Math.min(validation.data.max_results, this.config.query_limits.max_results),
|
|
228
|
+
max_payload_bytes: Math.min(
|
|
229
|
+
validation.data.max_payload_bytes,
|
|
230
|
+
this.config.query_limits.max_payload_bytes
|
|
231
|
+
),
|
|
232
|
+
} as QueryRequest;
|
|
233
|
+
|
|
234
|
+
return this.store.query(limitedRequest);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Close the service and release underlying store resources.
|
|
239
|
+
* After calling close(), emit() becomes a no-op and query() throws.
|
|
240
|
+
*/
|
|
241
|
+
async close(): Promise<void> {
|
|
242
|
+
if (this.closed) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
debug('closing KindlingService');
|
|
246
|
+
this.closed = true;
|
|
247
|
+
await this.store.close();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// =============================================================================
|
|
252
|
+
// Factory
|
|
253
|
+
// =============================================================================
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Create a KindlingService instance.
|
|
257
|
+
*
|
|
258
|
+
* If no store is provided, the service operates in disabled mode with a no-op store.
|
|
259
|
+
* This allows code to unconditionally call emit/query without checking for null.
|
|
260
|
+
*
|
|
261
|
+
* @param config - Kindling configuration (defaults to disabled config)
|
|
262
|
+
* @param store - Optional store adapter (defaults to NoOpKindlingStore)
|
|
263
|
+
* @returns Configured KindlingService instance
|
|
264
|
+
*/
|
|
265
|
+
export function createKindlingService(
|
|
266
|
+
config: KindlingConfig = DEFAULT_KINDLING_CONFIG,
|
|
267
|
+
store?: IKindlingStore
|
|
268
|
+
): KindlingService {
|
|
269
|
+
const effectiveStore = store ?? new NoOpKindlingStore();
|
|
270
|
+
debug('creating KindlingService', { enabled: config.enabled, hasStore: !!store });
|
|
271
|
+
return new KindlingService(effectiveStore, config);
|
|
272
|
+
}
|