@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,87 @@
1
+ /**
2
+ * Gate Emitter (KINDLING-004)
3
+ *
4
+ * Emits gate_evaluated observations when gates are checked.
5
+ * Gate evaluations form the governance record -- why things were
6
+ * allowed or blocked.
7
+ */
8
+
9
+ import { randomUUID } from 'node:crypto';
10
+ import type { KindlingService } from '../kindling-service.js';
11
+ import type { GateEvaluatedObservation } from '../observation-contract.js';
12
+ import { createDebugger } from '../utils/debug.js';
13
+
14
+ const debug = createDebugger('kindling');
15
+
16
+ // =============================================================================
17
+ // Input Types
18
+ // =============================================================================
19
+
20
+ /**
21
+ * Gate evaluation result to be recorded
22
+ */
23
+ export interface GateResult {
24
+ session_id: string;
25
+ gate_id: string;
26
+ gate_version?: string;
27
+ inputs: {
28
+ file_count?: number;
29
+ changed_files?: string[];
30
+ baseline_hash?: string;
31
+ };
32
+ outcome: 'pass' | 'fail' | 'error' | 'skipped';
33
+ rules_evaluated: string[];
34
+ rules_violated?: string[];
35
+ enforcement: 'blocking' | 'warning' | 'informational';
36
+ duration_ms: number;
37
+ violation_count?: number;
38
+ warning_count?: number;
39
+ }
40
+
41
+ // =============================================================================
42
+ // Emitter
43
+ // =============================================================================
44
+
45
+ /**
46
+ * Emit a gate_evaluated observation.
47
+ *
48
+ * @param service - KindlingService instance
49
+ * @param gateResult - Gate evaluation result
50
+ * @returns The generated gate_eval_id
51
+ */
52
+ export function emitGateEvaluated(service: KindlingService, gateResult: GateResult): string {
53
+ const gateEvalId = randomUUID();
54
+ debug('emitting gate_evaluated', {
55
+ gateId: gateResult.gate_id,
56
+ outcome: gateResult.outcome,
57
+ gateEvalId,
58
+ });
59
+
60
+ const observation: GateEvaluatedObservation = {
61
+ kind: 'gate_evaluated',
62
+ session_id: gateResult.session_id,
63
+ timestamp: new Date().toISOString(),
64
+ gate_eval_id: gateEvalId,
65
+ gate_id: gateResult.gate_id,
66
+ gate_version: gateResult.gate_version,
67
+ inputs: {
68
+ file_count: gateResult.inputs.file_count,
69
+ changed_files: gateResult.inputs.changed_files,
70
+ baseline_hash: gateResult.inputs.baseline_hash,
71
+ },
72
+ outcome: gateResult.outcome,
73
+ rules_evaluated: gateResult.rules_evaluated,
74
+ rules_violated: gateResult.rules_violated,
75
+ enforcement: gateResult.enforcement,
76
+ duration_ms: gateResult.duration_ms,
77
+ violation_count: gateResult.violation_count,
78
+ warning_count: gateResult.warning_count,
79
+ };
80
+
81
+ // Fire-and-forget
82
+ service.emit(observation).catch(() => {
83
+ // Silently swallow
84
+ });
85
+
86
+ return gateEvalId;
87
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Human Input Emitter (KINDLING-007a)
3
+ *
4
+ * Emits human_input observations when a user makes a decision
5
+ * (approval, override, rejection, manual edit, confirmation, cancellation).
6
+ */
7
+
8
+ import { randomUUID } from 'node:crypto';
9
+ import type { KindlingService } from '../kindling-service.js';
10
+ import type { HumanInputObservation } from '../observation-contract.js';
11
+
12
+ // =============================================================================
13
+ // Input Types
14
+ // =============================================================================
15
+
16
+ /**
17
+ * Human input details to be recorded
18
+ */
19
+ export interface HumanInputDetails {
20
+ session_id: string;
21
+ input_type:
22
+ | 'approval'
23
+ | 'override'
24
+ | 'rejection'
25
+ | 'manual_edit'
26
+ | 'confirmation'
27
+ | 'cancellation';
28
+ context: {
29
+ prompt?: string;
30
+ target?: string;
31
+ };
32
+ decision: string;
33
+ reason?: string;
34
+ user_identifier: string;
35
+ }
36
+
37
+ // =============================================================================
38
+ // Emitter
39
+ // =============================================================================
40
+
41
+ /**
42
+ * Emit a human_input observation.
43
+ *
44
+ * @param service - KindlingService instance
45
+ * @param input - Human input details
46
+ * @returns A generated ID for linking purposes
47
+ */
48
+ export function emitHumanInput(service: KindlingService, input: HumanInputDetails): string {
49
+ const inputId = randomUUID();
50
+
51
+ const observation: HumanInputObservation = {
52
+ kind: 'human_input',
53
+ session_id: input.session_id,
54
+ timestamp: new Date().toISOString(),
55
+ input_type: input.input_type,
56
+ context: {
57
+ prompt: input.context.prompt,
58
+ target: input.context.target,
59
+ },
60
+ decision: input.decision,
61
+ reason: input.reason,
62
+ user_identifier: input.user_identifier,
63
+ };
64
+
65
+ // Fire-and-forget
66
+ service.emit(observation).catch(() => {
67
+ // Silently swallow
68
+ });
69
+
70
+ return inputId;
71
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Emitters Barrel Export (KINDLING-003 through 008)
3
+ *
4
+ * All emitter functions and their input types, re-exported for convenience.
5
+ */
6
+
7
+ // Session (KINDLING-003)
8
+ export {
9
+ emitSessionStart,
10
+ emitSessionEnd,
11
+ type SessionStartContext,
12
+ type SessionEndOutcome,
13
+ } from './session-emitter.js';
14
+
15
+ // Gate (KINDLING-004)
16
+ export { emitGateEvaluated, type GateResult } from './gate-emitter.js';
17
+
18
+ // Action (KINDLING-005)
19
+ export { emitActionExecuted, type ActionDetails } from './action-emitter.js';
20
+
21
+ // Plan (KINDLING-006)
22
+ export {
23
+ emitPlanCreated,
24
+ emitPlanEdited,
25
+ emitPlanApproved,
26
+ emitPlanRejected,
27
+ type PlanCreatedInput,
28
+ type PlanEditedInput,
29
+ type PlanApprovedInput,
30
+ type PlanRejectedInput,
31
+ } from './plan-emitter.js';
32
+
33
+ // Human Input (KINDLING-007a)
34
+ export { emitHumanInput, type HumanInputDetails } from './human-input-emitter.js';
35
+
36
+ // Constraint (KINDLING-007b)
37
+ export { emitConstraintApplied, type ConstraintDetails } from './constraint-emitter.js';
38
+
39
+ // Error (KINDLING-008)
40
+ export { emitError, type ErrorDetails } from './error-emitter.js';
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Plan Emitter (KINDLING-006)
3
+ *
4
+ * Emits plan lifecycle observations: created, edited, approved, rejected.
5
+ * Plans are the governance artifacts that authorize actions.
6
+ */
7
+
8
+ import { randomUUID } from 'node:crypto';
9
+ import type { KindlingService } from '../kindling-service.js';
10
+ import type {
11
+ PlanCreatedObservation,
12
+ PlanEditedObservation,
13
+ PlanApprovedObservation,
14
+ PlanRejectedObservation,
15
+ } from '../observation-contract.js';
16
+ import { createDebugger } from '../utils/debug.js';
17
+
18
+ const debug = createDebugger('kindling');
19
+
20
+ // =============================================================================
21
+ // Input Types
22
+ // =============================================================================
23
+
24
+ export interface PlanCreatedInput {
25
+ session_id: string;
26
+ plan_id?: string;
27
+ plan_version: string;
28
+ plan_path: string;
29
+ plan_hash: string;
30
+ created_by: 'human' | 'ai' | 'system';
31
+ source?: string;
32
+ }
33
+
34
+ export interface PlanEditedInput {
35
+ session_id: string;
36
+ plan_id: string;
37
+ previous_version: string;
38
+ new_version: string;
39
+ previous_hash: string;
40
+ new_hash: string;
41
+ edited_by: 'human' | 'ai' | 'system';
42
+ change_summary?: string;
43
+ }
44
+
45
+ export interface PlanApprovedInput {
46
+ session_id: string;
47
+ plan_id: string;
48
+ plan_version: string;
49
+ approved_by: string;
50
+ approval_method: 'cli_confirm' | 'explicit_flag' | 'ci_gate';
51
+ }
52
+
53
+ export interface PlanRejectedInput {
54
+ session_id: string;
55
+ plan_id: string;
56
+ plan_version: string;
57
+ rejected_by: string;
58
+ rejection_reason?: string;
59
+ }
60
+
61
+ // =============================================================================
62
+ // Emitters
63
+ // =============================================================================
64
+
65
+ /**
66
+ * Emit a plan_created observation.
67
+ *
68
+ * If no plan_id is provided, one is generated.
69
+ *
70
+ * @param service - KindlingService instance
71
+ * @param plan - Plan creation details
72
+ * @returns The plan_id (generated or provided)
73
+ */
74
+ export function emitPlanCreated(service: KindlingService, plan: PlanCreatedInput): string {
75
+ const planId = plan.plan_id ?? randomUUID();
76
+ debug('emitting plan_created', { planId, createdBy: plan.created_by });
77
+
78
+ const observation: PlanCreatedObservation = {
79
+ kind: 'plan_created',
80
+ session_id: plan.session_id,
81
+ timestamp: new Date().toISOString(),
82
+ plan_id: planId,
83
+ plan_version: plan.plan_version,
84
+ plan_path: plan.plan_path,
85
+ plan_hash: plan.plan_hash,
86
+ created_by: plan.created_by,
87
+ source: plan.source,
88
+ };
89
+
90
+ // Fire-and-forget
91
+ service.emit(observation).catch(() => {
92
+ // Silently swallow
93
+ });
94
+
95
+ return planId;
96
+ }
97
+
98
+ /**
99
+ * Emit a plan_edited observation.
100
+ *
101
+ * @param service - KindlingService instance
102
+ * @param edit - Plan edit details
103
+ * @returns The plan_id
104
+ */
105
+ export function emitPlanEdited(service: KindlingService, edit: PlanEditedInput): string {
106
+ debug('emitting plan_edited', { planId: edit.plan_id, editedBy: edit.edited_by });
107
+
108
+ const observation: PlanEditedObservation = {
109
+ kind: 'plan_edited',
110
+ session_id: edit.session_id,
111
+ timestamp: new Date().toISOString(),
112
+ plan_id: edit.plan_id,
113
+ previous_version: edit.previous_version,
114
+ new_version: edit.new_version,
115
+ previous_hash: edit.previous_hash,
116
+ new_hash: edit.new_hash,
117
+ edited_by: edit.edited_by,
118
+ change_summary: edit.change_summary,
119
+ };
120
+
121
+ // Fire-and-forget
122
+ service.emit(observation).catch(() => {
123
+ // Silently swallow
124
+ });
125
+
126
+ return edit.plan_id;
127
+ }
128
+
129
+ /**
130
+ * Emit a plan_approved observation.
131
+ *
132
+ * @param service - KindlingService instance
133
+ * @param approval - Plan approval details
134
+ * @returns The plan_id
135
+ */
136
+ export function emitPlanApproved(service: KindlingService, approval: PlanApprovedInput): string {
137
+ debug('emitting plan_approved', { planId: approval.plan_id, approvedBy: approval.approved_by });
138
+
139
+ const observation: PlanApprovedObservation = {
140
+ kind: 'plan_approved',
141
+ session_id: approval.session_id,
142
+ timestamp: new Date().toISOString(),
143
+ plan_id: approval.plan_id,
144
+ plan_version: approval.plan_version,
145
+ approved_by: approval.approved_by,
146
+ approval_method: approval.approval_method,
147
+ };
148
+
149
+ // Fire-and-forget
150
+ service.emit(observation).catch(() => {
151
+ // Silently swallow
152
+ });
153
+
154
+ return approval.plan_id;
155
+ }
156
+
157
+ /**
158
+ * Emit a plan_rejected observation.
159
+ *
160
+ * @param service - KindlingService instance
161
+ * @param rejection - Plan rejection details
162
+ * @returns The plan_id
163
+ */
164
+ export function emitPlanRejected(service: KindlingService, rejection: PlanRejectedInput): string {
165
+ debug('emitting plan_rejected', { planId: rejection.plan_id, rejectedBy: rejection.rejected_by });
166
+
167
+ const observation: PlanRejectedObservation = {
168
+ kind: 'plan_rejected',
169
+ session_id: rejection.session_id,
170
+ timestamp: new Date().toISOString(),
171
+ plan_id: rejection.plan_id,
172
+ plan_version: rejection.plan_version,
173
+ rejected_by: rejection.rejected_by,
174
+ rejection_reason: rejection.rejection_reason,
175
+ };
176
+
177
+ // Fire-and-forget
178
+ service.emit(observation).catch(() => {
179
+ // Silently swallow
180
+ });
181
+
182
+ return rejection.plan_id;
183
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Session Emitter (KINDLING-003)
3
+ *
4
+ * Emits session_start and session_end observations.
5
+ * Sessions form the "spine" of Kindling -- every other observation
6
+ * is linked to a session via session_id.
7
+ */
8
+
9
+ import { randomUUID } from 'node:crypto';
10
+ import type { KindlingService } from '../kindling-service.js';
11
+ import type { SessionStartObservation, SessionEndObservation } from '../observation-contract.js';
12
+ import { createDebugger } from '../utils/debug.js';
13
+
14
+ const debug = createDebugger('kindling');
15
+
16
+ // =============================================================================
17
+ // Input Types
18
+ // =============================================================================
19
+
20
+ /**
21
+ * Context needed to start a session observation
22
+ */
23
+ export interface SessionStartContext {
24
+ working_directory: string;
25
+ git_ref?: string;
26
+ git_dirty?: boolean;
27
+ anvil_version: string;
28
+ command: string;
29
+ args: string[];
30
+ environment: 'development' | 'ci' | 'production' | 'unknown';
31
+ plan_id?: string;
32
+ }
33
+
34
+ /**
35
+ * Outcome data for ending a session
36
+ */
37
+ export interface SessionEndOutcome {
38
+ outcome: 'success' | 'failure' | 'partial' | 'cancelled';
39
+ exit_code: number;
40
+ duration_ms: number;
41
+ summary: {
42
+ gates_evaluated: number;
43
+ gates_passed: number;
44
+ gates_failed: number;
45
+ actions_executed: number;
46
+ errors_encountered: number;
47
+ };
48
+ }
49
+
50
+ // =============================================================================
51
+ // Emitters
52
+ // =============================================================================
53
+
54
+ /**
55
+ * Emit a session_start observation.
56
+ *
57
+ * Generates a new session_id and emits fire-and-forget.
58
+ * Returns the generated session_id so callers can link subsequent observations.
59
+ *
60
+ * @param service - KindlingService instance
61
+ * @param context - Session start context
62
+ * @returns The generated session_id
63
+ */
64
+ export function emitSessionStart(service: KindlingService, context: SessionStartContext): string {
65
+ const sessionId = randomUUID();
66
+ debug('emitting session_start', {
67
+ sessionId,
68
+ command: context.command,
69
+ environment: context.environment,
70
+ });
71
+
72
+ const observation: SessionStartObservation = {
73
+ kind: 'session_start',
74
+ session_id: sessionId,
75
+ timestamp: new Date().toISOString(),
76
+ context: {
77
+ working_directory: context.working_directory,
78
+ git_ref: context.git_ref,
79
+ git_dirty: context.git_dirty,
80
+ anvil_version: context.anvil_version,
81
+ command: context.command,
82
+ args: context.args,
83
+ environment: context.environment,
84
+ },
85
+ plan_id: context.plan_id,
86
+ };
87
+
88
+ // Fire-and-forget: catch errors silently to not affect main flow
89
+ service.emit(observation).catch(() => {
90
+ // Silently swallow -- Kindling must never break the host
91
+ });
92
+
93
+ return sessionId;
94
+ }
95
+
96
+ /**
97
+ * Emit a session_end observation.
98
+ *
99
+ * @param service - KindlingService instance
100
+ * @param sessionId - The session_id from the corresponding session_start
101
+ * @param outcome - Session outcome data
102
+ * @returns The session_id (same as input, for chaining)
103
+ */
104
+ export function emitSessionEnd(
105
+ service: KindlingService,
106
+ sessionId: string,
107
+ outcome: SessionEndOutcome
108
+ ): string {
109
+ debug('emitting session_end', {
110
+ sessionId,
111
+ outcome: outcome.outcome,
112
+ duration_ms: outcome.duration_ms,
113
+ });
114
+
115
+ const observation: SessionEndObservation = {
116
+ kind: 'session_end',
117
+ session_id: sessionId,
118
+ timestamp: new Date().toISOString(),
119
+ outcome: outcome.outcome,
120
+ exit_code: outcome.exit_code,
121
+ duration_ms: outcome.duration_ms,
122
+ summary: outcome.summary,
123
+ };
124
+
125
+ // Fire-and-forget
126
+ service.emit(observation).catch(() => {
127
+ // Silently swallow
128
+ });
129
+
130
+ return sessionId;
131
+ }