@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
@@ -0,0 +1,314 @@
1
+ /**
2
+ * Kindling Query Contract (v1)
3
+ *
4
+ * Defines the read-only, bounded query surface for Kindling observations.
5
+ * This is a system of record, not a reasoning engine.
6
+ *
7
+ * GOVERNING RULE:
8
+ * Queries may retrieve facts; interpretation is the caller's responsibility.
9
+ * User-supplied AI may read, but may not mutate, infer, or generalise via Kindling.
10
+ *
11
+ * @see plans/modules/kindling-integration.aps.md for integration plan
12
+ */
13
+ import { z } from 'zod';
14
+ // =============================================================================
15
+ // Schema Version
16
+ // =============================================================================
17
+ export const KINDLING_QUERY_CONTRACT_VERSION = '1.0.0';
18
+ // =============================================================================
19
+ // Query Scopes (Mandatory Boundary)
20
+ // =============================================================================
21
+ /**
22
+ * Every query must specify exactly one scope.
23
+ * No free-text search. No global scans. No cross-project reads.
24
+ */
25
+ export const QueryScopeSchema = z.enum([
26
+ 'session', // "What happened in this run?"
27
+ 'plan', // "What happened because of this plan?"
28
+ 'gate', // "Why did this gate pass/fail?"
29
+ 'action', // "What exactly did this action do?"
30
+ ]);
31
+ // =============================================================================
32
+ // Result Shape
33
+ // =============================================================================
34
+ /**
35
+ * How results should be structured
36
+ */
37
+ export const ResultShapeSchema = z.enum([
38
+ 'timeline', // Ordered observations grouped by phase
39
+ 'list', // Flat list of observations
40
+ 'entity', // Single entity with metadata
41
+ ]);
42
+ // =============================================================================
43
+ // Output Format
44
+ // =============================================================================
45
+ /**
46
+ * Result serialisation format
47
+ */
48
+ export const OutputFormatSchema = z.enum([
49
+ 'json', // Machine-readable
50
+ 'text', // Human-readable (for CLI)
51
+ ]);
52
+ // =============================================================================
53
+ // Query Request (Base)
54
+ // =============================================================================
55
+ /**
56
+ * Base query request with mandatory constraints
57
+ */
58
+ export const QueryRequestBaseSchema = z.object({
59
+ scope: QueryScopeSchema.describe('Query scope (mandatory)'),
60
+ shape: ResultShapeSchema.describe('Result structure (mandatory)'),
61
+ format: OutputFormatSchema.default('json').describe('Output format'),
62
+ // Time bounds (optional but encouraged)
63
+ time_after: z.string().datetime().optional().describe('Include observations after this time'),
64
+ time_before: z.string().datetime().optional().describe('Include observations before this time'),
65
+ // Result limits (anti-vacuum-cleaner)
66
+ max_results: z
67
+ .number()
68
+ .int()
69
+ .positive()
70
+ .max(1000)
71
+ .default(100)
72
+ .describe('Maximum observations to return'),
73
+ max_payload_bytes: z
74
+ .number()
75
+ .int()
76
+ .positive()
77
+ .max(10 * 1024 * 1024) // 10MB
78
+ .default(1024 * 1024) // 1MB
79
+ .describe('Maximum total payload size'),
80
+ });
81
+ // =============================================================================
82
+ // Session Query
83
+ // =============================================================================
84
+ /**
85
+ * Query: "What happened in this run?"
86
+ *
87
+ * Returns ordered observations grouped by phase (plan / gate / action / outcome).
88
+ * Raw payloads only, no summaries.
89
+ */
90
+ export const SessionQuerySchema = QueryRequestBaseSchema.extend({
91
+ scope: z.literal('session'),
92
+ session_id: z.string().uuid().describe('Session/run ID (mandatory)'),
93
+ include_phases: z
94
+ .array(z.enum(['plan', 'gate', 'action', 'outcome', 'error']))
95
+ .optional()
96
+ .describe('Filter to specific phases'),
97
+ });
98
+ // =============================================================================
99
+ // Plan Query
100
+ // =============================================================================
101
+ /**
102
+ * Query: "What happened because of this plan?"
103
+ *
104
+ * Returns plan metadata, versions, and linked executions.
105
+ * This is the ONLY cross-session read allowed, via explicit plan_id.
106
+ */
107
+ export const PlanQuerySchema = QueryRequestBaseSchema.extend({
108
+ scope: z.literal('plan'),
109
+ plan_id: z.string().describe('Plan ID (mandatory)'),
110
+ include_executions: z.boolean().default(true).describe('Include linked execution run IDs'),
111
+ include_versions: z.boolean().default(true).describe('Include plan version history'),
112
+ });
113
+ // =============================================================================
114
+ // Gate Query
115
+ // =============================================================================
116
+ /**
117
+ * Query: "Why did this gate pass/fail?"
118
+ *
119
+ * Returns gate evaluation details with rule IDs, inputs (sanitised), and outcomes.
120
+ * No prose. No explanation layer.
121
+ */
122
+ export const GateQuerySchema = QueryRequestBaseSchema.extend({
123
+ scope: z.literal('gate'),
124
+ gate_eval_id: z.string().describe('Gate evaluation ID (mandatory)'),
125
+ });
126
+ // =============================================================================
127
+ // Action Query
128
+ // =============================================================================
129
+ /**
130
+ * Query: "What exactly did this action do?"
131
+ *
132
+ * Returns action details with redacted command, environment, and linked governance.
133
+ * This is the atomic unit of accountability.
134
+ */
135
+ export const ActionQuerySchema = QueryRequestBaseSchema.extend({
136
+ scope: z.literal('action'),
137
+ action_id: z.string().describe('Action ID (mandatory)'),
138
+ include_approval_chain: z
139
+ .boolean()
140
+ .default(true)
141
+ .describe('Include approval requirements and state'),
142
+ });
143
+ // =============================================================================
144
+ // Query Request (Union)
145
+ // =============================================================================
146
+ /**
147
+ * All query types (discriminated union)
148
+ */
149
+ export const QueryRequestSchema = z.discriminatedUnion('scope', [
150
+ SessionQuerySchema,
151
+ PlanQuerySchema,
152
+ GateQuerySchema,
153
+ ActionQuerySchema,
154
+ ]);
155
+ // =============================================================================
156
+ // Query Response (Output Guarantees)
157
+ // =============================================================================
158
+ /**
159
+ * Standard metadata for all responses
160
+ */
161
+ export const QueryResponseMetadataSchema = z.object({
162
+ query_id: z.string().uuid().describe('Unique query identifier (for debugging)'),
163
+ executed_at: z.string().datetime().describe('When query was executed'),
164
+ contract_version: z.string().describe('Query contract version used'),
165
+ result_count: z.number().int().nonnegative().describe('Number of observations returned'),
166
+ truncated: z.boolean().describe('Whether results were truncated (hit limits)'),
167
+ truncation_reason: z
168
+ .enum(['max_results', 'max_payload_bytes', 'none'])
169
+ .optional()
170
+ .describe('Why truncation occurred'),
171
+ });
172
+ /**
173
+ * Standard provenance link
174
+ */
175
+ export const ProvenanceLinkSchema = z.object({
176
+ type: z.enum(['caused_by', 'governed_by', 'approved_by', 'linked_to']).describe('Link type'),
177
+ entity_type: z
178
+ .enum(['session', 'plan', 'gate', 'action', 'human'])
179
+ .describe('Target entity type'),
180
+ entity_id: z.string().describe('Target entity ID'),
181
+ timestamp: z.string().datetime().describe('When link was created'),
182
+ });
183
+ /**
184
+ * Observation (base type for all returned data)
185
+ */
186
+ export const ObservationSchema = z.object({
187
+ id: z.string().uuid().describe('Observation ID'),
188
+ kind: z
189
+ .enum([
190
+ 'session_start',
191
+ 'session_end',
192
+ 'plan_created',
193
+ 'plan_edited',
194
+ 'plan_approved',
195
+ 'plan_rejected',
196
+ 'gate_evaluated',
197
+ 'action_executed',
198
+ 'constraint_applied',
199
+ 'human_input',
200
+ 'error',
201
+ ])
202
+ .describe('Observation kind'),
203
+ timestamp: z.string().datetime().describe('When observation was recorded'),
204
+ session_id: z.string().uuid().describe('Session this observation belongs to'),
205
+ // Provenance (explicit links)
206
+ provenance: z.array(ProvenanceLinkSchema).describe('Explicit links to other entities'),
207
+ // Payload (fact data, no inference)
208
+ payload: z.record(z.string(), z.unknown()).describe('Observation-specific data (raw facts only)'),
209
+ });
210
+ /**
211
+ * Query response
212
+ */
213
+ export const QueryResponseSchema = z.object({
214
+ metadata: QueryResponseMetadataSchema.describe('Query execution metadata'),
215
+ observations: z.array(ObservationSchema).describe('Ordered observations (facts only)'),
216
+ });
217
+ // =============================================================================
218
+ // Output Guarantees (Documented Requirements)
219
+ // =============================================================================
220
+ /**
221
+ * Every Kindling response guarantees:
222
+ *
223
+ * 1. Stable field names - No field names change between queries
224
+ * 2. Explicit timestamps - Every observation has ISO8601 timestamp
225
+ * 3. Explicit links - Provenance via typed links (caused_by, governed_by, approved_by)
226
+ * 4. No hidden inference - Payload contains only raw facts
227
+ * 5. No reordered history - Observations returned in recorded order
228
+ *
229
+ * This makes Kindling LLM-safe by construction.
230
+ *
231
+ * AI can:
232
+ * - Narrate events
233
+ * - Summarise outcomes
234
+ * - Explain facts
235
+ *
236
+ * But AI will always be explaining facts, not ghosts.
237
+ */
238
+ // =============================================================================
239
+ // Read-Only Enforcement (Anti-Pattern Markers)
240
+ // =============================================================================
241
+ /**
242
+ * Operations that MUST NOT exist in the query API:
243
+ *
244
+ * ❌ write()
245
+ * ❌ update()
246
+ * ❌ delete()
247
+ * ❌ annotate()
248
+ * ❌ tag()
249
+ * ❌ learn()
250
+ * ❌ embed()
251
+ * ❌ infer()
252
+ *
253
+ * If user AI wants memory, it must bring its own store.
254
+ */
255
+ // =============================================================================
256
+ // Explicit Non-Goals (v1 Boundary)
257
+ // =============================================================================
258
+ /**
259
+ * The following are explicitly OUT OF SCOPE for v1:
260
+ *
261
+ * ❌ Semantic search
262
+ * ❌ Similarity queries
263
+ * ❌ Embeddings
264
+ * ❌ Cross-plan discovery
265
+ * ❌ Learned relevance
266
+ * ❌ Auto-summaries (stored in Kindling)
267
+ * ❌ AI-generated annotations stored in Kindling
268
+ *
269
+ * These belong to Edda / Ember, not Kindling v1.
270
+ */
271
+ // =============================================================================
272
+ // Validation Utilities
273
+ // =============================================================================
274
+ /**
275
+ * Validate a query request
276
+ */
277
+ export function validateQueryRequest(data) {
278
+ const result = QueryRequestSchema.safeParse(data);
279
+ if (result.success) {
280
+ return { success: true, data: result.data };
281
+ }
282
+ return { success: false, error: result.error.format()._errors.join(', ') };
283
+ }
284
+ /**
285
+ * Validate a query response
286
+ */
287
+ export function validateQueryResponse(data) {
288
+ const result = QueryResponseSchema.safeParse(data);
289
+ if (result.success) {
290
+ return { success: true, data: result.data };
291
+ }
292
+ return { success: false, error: result.error.format()._errors.join(', ') };
293
+ }
294
+ // =============================================================================
295
+ // CLI Command Mapping (Human-First, AI-Compatible)
296
+ // =============================================================================
297
+ /**
298
+ * All queries have CLI equivalents:
299
+ *
300
+ * anvil run show <run_id> --json
301
+ * → SessionQuery { scope: 'session', session_id: run_id, shape: 'timeline' }
302
+ *
303
+ * anvil plan trace <plan_id> --json
304
+ * → PlanQuery { scope: 'plan', plan_id: plan_id, shape: 'entity' }
305
+ *
306
+ * anvil gate show <gate_eval_id> --json
307
+ * → GateQuery { scope: 'gate', gate_eval_id: gate_eval_id, shape: 'entity' }
308
+ *
309
+ * anvil action show <action_id> --json
310
+ * → ActionQuery { scope: 'action', action_id: action_id, shape: 'entity' }
311
+ *
312
+ * The CLI is a thin wrapper over this query surface.
313
+ * That symmetry is intentional.
314
+ */
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Query Limits Enforcement (KINDLING-010)
3
+ *
4
+ * Provides post-query truncation and metadata flagging.
5
+ * Used by KindlingQueryService before returning results to callers,
6
+ * as a defense-in-depth layer on top of store-level limits.
7
+ */
8
+ import type { QueryResponse } from './query-contract.js';
9
+ import type { QueryLimitConfig } from './config.js';
10
+ /**
11
+ * Limits to enforce on a query response
12
+ */
13
+ export interface QueryLimits {
14
+ /** Maximum number of observations to return */
15
+ max_results: number;
16
+ /** Maximum total payload size in bytes */
17
+ max_payload_bytes: number;
18
+ }
19
+ /**
20
+ * Enforce query limits on a response by truncating results if necessary.
21
+ *
22
+ * This is applied after the store returns results, as a safety net.
23
+ * If the store already respects limits, this is a no-op.
24
+ *
25
+ * Sets the `truncated` and `truncation_reason` metadata flags when
26
+ * results are truncated.
27
+ *
28
+ * @param response - The query response from the store
29
+ * @param limits - The limits to enforce
30
+ * @returns A new QueryResponse with limits enforced
31
+ */
32
+ export declare function enforceQueryLimits(response: QueryResponse, limits: QueryLimits): QueryResponse;
33
+ /**
34
+ * Create QueryLimits from a QueryLimitConfig.
35
+ *
36
+ * @param config - Query limit configuration
37
+ * @returns QueryLimits object
38
+ */
39
+ export declare function limitsFromConfig(config: QueryLimitConfig): QueryLimits;
40
+ //# sourceMappingURL=query-limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-limits.d.ts","sourceRoot":"","sources":["../src/query-limits.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAyB,MAAM,qBAAqB,CAAC;AAChF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAMpD;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,+CAA+C;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,WAAW,GAAG,aAAa,CAkD9F;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,WAAW,CAKtE"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Query Limits Enforcement (KINDLING-010)
3
+ *
4
+ * Provides post-query truncation and metadata flagging.
5
+ * Used by KindlingQueryService before returning results to callers,
6
+ * as a defense-in-depth layer on top of store-level limits.
7
+ */
8
+ // =============================================================================
9
+ // Enforcement
10
+ // =============================================================================
11
+ /**
12
+ * Enforce query limits on a response by truncating results if necessary.
13
+ *
14
+ * This is applied after the store returns results, as a safety net.
15
+ * If the store already respects limits, this is a no-op.
16
+ *
17
+ * Sets the `truncated` and `truncation_reason` metadata flags when
18
+ * results are truncated.
19
+ *
20
+ * @param response - The query response from the store
21
+ * @param limits - The limits to enforce
22
+ * @returns A new QueryResponse with limits enforced
23
+ */
24
+ export function enforceQueryLimits(response, limits) {
25
+ let observations = response.observations;
26
+ let truncated = response.metadata.truncated;
27
+ let truncationReason = response.metadata.truncation_reason;
28
+ // Check max_results
29
+ if (observations.length > limits.max_results) {
30
+ observations = observations.slice(0, limits.max_results);
31
+ truncated = true;
32
+ truncationReason = 'max_results';
33
+ }
34
+ // Check max_payload_bytes
35
+ const serialized = JSON.stringify(observations);
36
+ const payloadBytes = new TextEncoder().encode(serialized).byteLength;
37
+ if (payloadBytes > limits.max_payload_bytes) {
38
+ // Binary search for the maximum number of observations that fit
39
+ let lo = 0;
40
+ let hi = observations.length;
41
+ while (lo < hi) {
42
+ const mid = Math.floor((lo + hi + 1) / 2);
43
+ const slice = observations.slice(0, mid);
44
+ const sliceBytes = new TextEncoder().encode(JSON.stringify(slice)).byteLength;
45
+ if (sliceBytes <= limits.max_payload_bytes) {
46
+ lo = mid;
47
+ }
48
+ else {
49
+ hi = mid - 1;
50
+ }
51
+ }
52
+ observations = observations.slice(0, lo);
53
+ truncated = true;
54
+ // Only override reason if not already set (max_results takes precedence)
55
+ truncationReason = truncationReason === 'max_results' ? 'max_results' : 'max_payload_bytes';
56
+ }
57
+ const metadata = {
58
+ ...response.metadata,
59
+ result_count: observations.length,
60
+ truncated,
61
+ truncation_reason: truncated ? truncationReason : 'none',
62
+ };
63
+ return {
64
+ metadata,
65
+ observations,
66
+ };
67
+ }
68
+ /**
69
+ * Create QueryLimits from a QueryLimitConfig.
70
+ *
71
+ * @param config - Query limit configuration
72
+ * @returns QueryLimits object
73
+ */
74
+ export function limitsFromConfig(config) {
75
+ return {
76
+ max_results: config.max_results,
77
+ max_payload_bytes: config.max_payload_bytes,
78
+ };
79
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Kindling Query Service (KINDLING-009)
3
+ *
4
+ * High-level query interface that wraps KindlingService.query() with
5
+ * convenience methods for each query scope. Applies query limit enforcement
6
+ * before returning results.
7
+ *
8
+ * This is the primary read API for Anvil CLI commands and tooling.
9
+ */
10
+ import type { KindlingService } from './kindling-service.js';
11
+ import type { QueryResponse, ResultShape, OutputFormat } from './query-contract.js';
12
+ /**
13
+ * Common options for all query methods
14
+ */
15
+ export interface QueryOptions {
16
+ /** Result structure (default: 'list') */
17
+ shape?: ResultShape;
18
+ /** Output format (default: 'json') */
19
+ format?: OutputFormat;
20
+ /** Include observations after this time */
21
+ time_after?: string;
22
+ /** Include observations before this time */
23
+ time_before?: string;
24
+ /** Maximum observations to return (capped by config) */
25
+ max_results?: number;
26
+ /** Maximum total payload size in bytes (capped by config) */
27
+ max_payload_bytes?: number;
28
+ }
29
+ /**
30
+ * Options specific to session queries
31
+ */
32
+ export interface SessionQueryOptions extends QueryOptions {
33
+ /** Filter to specific phases */
34
+ include_phases?: Array<'plan' | 'gate' | 'action' | 'outcome' | 'error'>;
35
+ }
36
+ /**
37
+ * Options specific to plan queries
38
+ */
39
+ export interface PlanQueryOptions extends QueryOptions {
40
+ /** Include linked execution run IDs (default: true) */
41
+ include_executions?: boolean;
42
+ /** Include plan version history (default: true) */
43
+ include_versions?: boolean;
44
+ }
45
+ /**
46
+ * Options specific to action queries
47
+ */
48
+ export interface ActionQueryOptions extends QueryOptions {
49
+ /** Include approval chain (default: true) */
50
+ include_approval_chain?: boolean;
51
+ }
52
+ /**
53
+ * High-level query service for Kindling observations.
54
+ *
55
+ * Provides typed convenience methods for each query scope and enforces
56
+ * query limits from the service configuration.
57
+ */
58
+ export declare class KindlingQueryService {
59
+ private readonly service;
60
+ constructor(service: KindlingService);
61
+ /**
62
+ * Query: "What happened in this run?"
63
+ *
64
+ * Returns ordered observations for a specific session, optionally
65
+ * filtered by phase.
66
+ *
67
+ * @param sessionId - Session/run ID
68
+ * @param options - Query options
69
+ * @returns Query response with session observations
70
+ */
71
+ querySession(sessionId: string, options?: SessionQueryOptions): Promise<QueryResponse>;
72
+ /**
73
+ * Query: "What happened because of this plan?"
74
+ *
75
+ * Returns plan metadata, versions, and linked executions.
76
+ * This is the only cross-session read allowed.
77
+ *
78
+ * @param planId - Plan ID
79
+ * @param options - Query options
80
+ * @returns Query response with plan observations
81
+ */
82
+ queryPlan(planId: string, options?: PlanQueryOptions): Promise<QueryResponse>;
83
+ /**
84
+ * Query: "Why did this gate pass/fail?"
85
+ *
86
+ * Returns gate evaluation details with rule IDs, inputs, and outcomes.
87
+ *
88
+ * @param gateEvalId - Gate evaluation ID
89
+ * @param options - Query options
90
+ * @returns Query response with gate observations
91
+ */
92
+ queryGate(gateEvalId: string, options?: QueryOptions): Promise<QueryResponse>;
93
+ /**
94
+ * Query: "What exactly did this action do?"
95
+ *
96
+ * Returns action execution details with redacted command, environment,
97
+ * and linked governance.
98
+ *
99
+ * @param actionId - Action ID
100
+ * @param options - Query options
101
+ * @returns Query response with action observations
102
+ */
103
+ queryAction(actionId: string, options?: ActionQueryOptions): Promise<QueryResponse>;
104
+ /**
105
+ * Apply query limits from the service configuration as a defense-in-depth layer.
106
+ */
107
+ private applyLimits;
108
+ }
109
+ //# sourceMappingURL=query-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-service.d.ts","sourceRoot":"","sources":["../src/query-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EACV,aAAa,EAKb,WAAW,EACX,YAAY,EACb,MAAM,qBAAqB,CAAC;AAU7B;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,yCAAyC;IACzC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,sCAAsC;IACtC,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6DAA6D;IAC7D,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,YAAY;IACvD,gCAAgC;IAChC,cAAc,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC,CAAC;CAC1E;AAED;;GAEG;AACH,MAAM,WAAW,gBAAiB,SAAQ,YAAY;IACpD,uDAAuD;IACvD,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,mDAAmD;IACnD,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACtD,6CAA6C;IAC7C,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAMD;;;;;GAKG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;gBAE9B,OAAO,EAAE,eAAe;IAIpC;;;;;;;;;OASG;IACG,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,aAAa,CAAC;IAmBhG;;;;;;;;;OASG;IACG,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,aAAa,CAAC;IAoBvF;;;;;;;;OAQG;IACG,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,aAAa,CAAC;IAkBvF;;;;;;;;;OASG;IACG,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,aAAa,CAAC;IAuB7F;;OAEG;IACH,OAAO,CAAC,WAAW;CAIpB"}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Kindling Query Service (KINDLING-009)
3
+ *
4
+ * High-level query interface that wraps KindlingService.query() with
5
+ * convenience methods for each query scope. Applies query limit enforcement
6
+ * before returning results.
7
+ *
8
+ * This is the primary read API for Anvil CLI commands and tooling.
9
+ */
10
+ import { enforceQueryLimits, limitsFromConfig } from './query-limits.js';
11
+ import { createDebugger } from './utils/debug.js';
12
+ const debug = createDebugger('kindling');
13
+ // =============================================================================
14
+ // Query Service
15
+ // =============================================================================
16
+ /**
17
+ * High-level query service for Kindling observations.
18
+ *
19
+ * Provides typed convenience methods for each query scope and enforces
20
+ * query limits from the service configuration.
21
+ */
22
+ export class KindlingQueryService {
23
+ service;
24
+ constructor(service) {
25
+ this.service = service;
26
+ }
27
+ /**
28
+ * Query: "What happened in this run?"
29
+ *
30
+ * Returns ordered observations for a specific session, optionally
31
+ * filtered by phase.
32
+ *
33
+ * @param sessionId - Session/run ID
34
+ * @param options - Query options
35
+ * @returns Query response with session observations
36
+ */
37
+ async querySession(sessionId, options = {}) {
38
+ debug('querySession', { sessionId, shape: options.shape });
39
+ const request = {
40
+ scope: 'session',
41
+ session_id: sessionId,
42
+ shape: options.shape ?? 'timeline',
43
+ format: options.format ?? 'json',
44
+ time_after: options.time_after,
45
+ time_before: options.time_before,
46
+ max_results: options.max_results ?? this.service.configuration.query_limits.max_results,
47
+ max_payload_bytes: options.max_payload_bytes ?? this.service.configuration.query_limits.max_payload_bytes,
48
+ include_phases: options.include_phases,
49
+ };
50
+ const response = await this.service.query(request);
51
+ return this.applyLimits(response);
52
+ }
53
+ /**
54
+ * Query: "What happened because of this plan?"
55
+ *
56
+ * Returns plan metadata, versions, and linked executions.
57
+ * This is the only cross-session read allowed.
58
+ *
59
+ * @param planId - Plan ID
60
+ * @param options - Query options
61
+ * @returns Query response with plan observations
62
+ */
63
+ async queryPlan(planId, options = {}) {
64
+ debug('queryPlan', { planId, shape: options.shape });
65
+ const request = {
66
+ scope: 'plan',
67
+ plan_id: planId,
68
+ shape: options.shape ?? 'entity',
69
+ format: options.format ?? 'json',
70
+ time_after: options.time_after,
71
+ time_before: options.time_before,
72
+ max_results: options.max_results ?? this.service.configuration.query_limits.max_results,
73
+ max_payload_bytes: options.max_payload_bytes ?? this.service.configuration.query_limits.max_payload_bytes,
74
+ include_executions: options.include_executions ?? true,
75
+ include_versions: options.include_versions ?? true,
76
+ };
77
+ const response = await this.service.query(request);
78
+ return this.applyLimits(response);
79
+ }
80
+ /**
81
+ * Query: "Why did this gate pass/fail?"
82
+ *
83
+ * Returns gate evaluation details with rule IDs, inputs, and outcomes.
84
+ *
85
+ * @param gateEvalId - Gate evaluation ID
86
+ * @param options - Query options
87
+ * @returns Query response with gate observations
88
+ */
89
+ async queryGate(gateEvalId, options = {}) {
90
+ debug('queryGate', { gateEvalId });
91
+ const request = {
92
+ scope: 'gate',
93
+ gate_eval_id: gateEvalId,
94
+ shape: options.shape ?? 'entity',
95
+ format: options.format ?? 'json',
96
+ time_after: options.time_after,
97
+ time_before: options.time_before,
98
+ max_results: options.max_results ?? this.service.configuration.query_limits.max_results,
99
+ max_payload_bytes: options.max_payload_bytes ?? this.service.configuration.query_limits.max_payload_bytes,
100
+ };
101
+ const response = await this.service.query(request);
102
+ return this.applyLimits(response);
103
+ }
104
+ /**
105
+ * Query: "What exactly did this action do?"
106
+ *
107
+ * Returns action execution details with redacted command, environment,
108
+ * and linked governance.
109
+ *
110
+ * @param actionId - Action ID
111
+ * @param options - Query options
112
+ * @returns Query response with action observations
113
+ */
114
+ async queryAction(actionId, options = {}) {
115
+ debug('queryAction', { actionId });
116
+ const request = {
117
+ scope: 'action',
118
+ action_id: actionId,
119
+ shape: options.shape ?? 'entity',
120
+ format: options.format ?? 'json',
121
+ time_after: options.time_after,
122
+ time_before: options.time_before,
123
+ max_results: options.max_results ?? this.service.configuration.query_limits.max_results,
124
+ max_payload_bytes: options.max_payload_bytes ?? this.service.configuration.query_limits.max_payload_bytes,
125
+ include_approval_chain: options.include_approval_chain ?? true,
126
+ };
127
+ const response = await this.service.query(request);
128
+ return this.applyLimits(response);
129
+ }
130
+ // ===========================================================================
131
+ // Private
132
+ // ===========================================================================
133
+ /**
134
+ * Apply query limits from the service configuration as a defense-in-depth layer.
135
+ */
136
+ applyLimits(response) {
137
+ const limits = limitsFromConfig(this.service.configuration.query_limits);
138
+ return enforceQueryLimits(response, limits);
139
+ }
140
+ }