@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
|
@@ -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
|
+
}
|