@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.
Files changed (84) hide show
  1. package/LICENSE +14 -0
  2. package/README.md +542 -0
  3. package/dist/adapter.d.ts +49 -0
  4. package/dist/adapter.d.ts.map +1 -0
  5. package/dist/adapter.js +100 -0
  6. package/dist/config.d.ts +89 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +173 -0
  9. package/dist/emitters/action-emitter.d.ts +40 -0
  10. package/dist/emitters/action-emitter.d.ts.map +1 -0
  11. package/dist/emitters/action-emitter.js +52 -0
  12. package/dist/emitters/constraint-emitter.d.ts +32 -0
  13. package/dist/emitters/constraint-emitter.d.ts.map +1 -0
  14. package/dist/emitters/constraint-emitter.js +41 -0
  15. package/dist/emitters/error-emitter.d.ts +33 -0
  16. package/dist/emitters/error-emitter.d.ts.map +1 -0
  17. package/dist/emitters/error-emitter.js +50 -0
  18. package/dist/emitters/gate-emitter.d.ts +37 -0
  19. package/dist/emitters/gate-emitter.d.ts.map +1 -0
  20. package/dist/emitters/gate-emitter.js +53 -0
  21. package/dist/emitters/human-input-emitter.d.ts +30 -0
  22. package/dist/emitters/human-input-emitter.d.ts.map +1 -0
  23. package/dist/emitters/human-input-emitter.js +38 -0
  24. package/dist/emitters/index.d.ts +13 -0
  25. package/dist/emitters/index.d.ts.map +1 -0
  26. package/dist/emitters/index.js +19 -0
  27. package/dist/emitters/plan-emitter.d.ts +75 -0
  28. package/dist/emitters/plan-emitter.d.ts.map +1 -0
  29. package/dist/emitters/plan-emitter.js +116 -0
  30. package/dist/emitters/session-emitter.d.ts +57 -0
  31. package/dist/emitters/session-emitter.d.ts.map +1 -0
  32. package/dist/emitters/session-emitter.js +80 -0
  33. package/dist/index.d.ts +40 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +111 -0
  36. package/dist/kindling-service.d.ts +122 -0
  37. package/dist/kindling-service.d.ts.map +1 -0
  38. package/dist/kindling-service.js +203 -0
  39. package/dist/observation-contract.d.ts +561 -0
  40. package/dist/observation-contract.d.ts.map +1 -0
  41. package/dist/observation-contract.js +391 -0
  42. package/dist/query-contract.d.ts +463 -0
  43. package/dist/query-contract.d.ts.map +1 -0
  44. package/dist/query-contract.js +314 -0
  45. package/dist/query-limits.d.ts +40 -0
  46. package/dist/query-limits.d.ts.map +1 -0
  47. package/dist/query-limits.js +79 -0
  48. package/dist/query-service.d.ts +109 -0
  49. package/dist/query-service.d.ts.map +1 -0
  50. package/dist/query-service.js +140 -0
  51. package/dist/retention.d.ts +79 -0
  52. package/dist/retention.d.ts.map +1 -0
  53. package/dist/retention.js +81 -0
  54. package/dist/sensitive-data-validator.d.ts +47 -0
  55. package/dist/sensitive-data-validator.d.ts.map +1 -0
  56. package/dist/sensitive-data-validator.js +135 -0
  57. package/dist/status.d.ts +104 -0
  58. package/dist/status.d.ts.map +1 -0
  59. package/dist/status.js +136 -0
  60. package/dist/utils/debug.d.ts +9 -0
  61. package/dist/utils/debug.d.ts.map +1 -0
  62. package/dist/utils/debug.js +55 -0
  63. package/package.json +114 -0
  64. package/src/adapter.ts +117 -0
  65. package/src/config.ts +202 -0
  66. package/src/emitters/action-emitter.ts +90 -0
  67. package/src/emitters/constraint-emitter.ts +73 -0
  68. package/src/emitters/error-emitter.ts +86 -0
  69. package/src/emitters/gate-emitter.ts +87 -0
  70. package/src/emitters/human-input-emitter.ts +71 -0
  71. package/src/emitters/index.ts +40 -0
  72. package/src/emitters/plan-emitter.ts +183 -0
  73. package/src/emitters/session-emitter.ts +131 -0
  74. package/src/index.ts +254 -0
  75. package/src/kindling-service.ts +272 -0
  76. package/src/malicious-ai.test.ts +949 -0
  77. package/src/observation-contract.ts +500 -0
  78. package/src/query-contract.ts +389 -0
  79. package/src/query-limits.ts +106 -0
  80. package/src/query-service.ts +217 -0
  81. package/src/retention.ts +153 -0
  82. package/src/sensitive-data-validator.ts +167 -0
  83. package/src/status.ts +221 -0
  84. 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
+ }