principles-disciple 1.107.0 → 1.108.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/openclaw.plugin.json +1 -1
- package/package.json +2 -2
- package/src/core/init.ts +3 -1
- package/src/core/workspace-dir-validation.ts +3 -3
- package/tests/core-anti-growth.test.ts +0 -13
- package/tests/hooks/prompt-characterization.test.ts +1 -11
- package/tests/hooks/prompt-diet.test.ts +3 -11
- package/tests/hooks/prompt-size-guard.test.ts +0 -10
- package/tests/hooks/runtime-v2-prompt-activation.test.ts +0 -10
- package/tests/index.test.ts +1 -1
- package/tests/runtime-v2-discovery-guard.test.ts +1 -2
- package/vitest.config.ts +2 -3
- package/vitest.unit.config.ts +12 -0
- package/src/core/evolution-hook.ts +0 -74
- package/src/core/file-storage-adapter.ts +0 -203
- package/src/core/merge-gate-audit.ts +0 -314
- package/src/core/pain-context-extractor.ts +0 -306
- package/src/core/pain-lifecycle.ts +0 -38
- package/src/core/pain-signal-adapter.ts +0 -42
- package/src/core/pain-signal.ts +0 -22
- package/src/core/principle-injector.ts +0 -84
- package/src/core/principle-tree-migration.ts +0 -196
- package/src/core/storage-adapter.ts +0 -65
- package/src/core/telemetry-event.ts +0 -109
- package/src/core/training-program.ts +0 -632
- package/src/core/workspace-dir-service.ts +0 -119
- package/src/hooks/lifecycle-routing.ts +0 -125
- package/src/service/event-log-auditor.ts +0 -284
- package/src/service/evolution-queue-lock.ts +0 -47
- package/src/service/failure-classifier.ts +0 -79
- package/src/service/internalization-trigger-adapter.ts +0 -302
- package/src/service/monitoring-query-service.ts +0 -67
- package/src/service/subagent-workflow/index.ts +0 -17
- package/src/tools/critique-prompt.ts +0 -1
- package/src/tools/model-index.ts +0 -1
- package/src/types/event-payload.ts +0 -16
- package/src/utils/glob-match.ts +0 -50
- package/src/utils/nlp.ts +0 -25
- package/src/utils/plugin-logger.ts +0 -97
- package/src/utils/subagent-probe.ts +0 -81
- package/tests/core/evolution-hook.test.ts +0 -123
- package/tests/core/file-storage-adapter.test.ts +0 -285
- package/tests/core/merge-gate-audit.test.ts +0 -117
- package/tests/core/pain-context-extractor.test.ts +0 -279
- package/tests/core/pain-lifecycle.test.ts +0 -38
- package/tests/core/pain-signal-adapter.test.ts +0 -116
- package/tests/core/pain-signal.test.ts +0 -190
- package/tests/core/principle-injector.test.ts +0 -90
- package/tests/core/principle-tree-migration.test.ts +0 -77
- package/tests/core/storage-conformance.test.ts +0 -429
- package/tests/core/telemetry-event.test.ts +0 -119
- package/tests/core/training-program.test.ts +0 -472
- package/tests/core/workspace-dir-service.test.ts +0 -68
- package/tests/core/workspace-dir-validation.test.ts +0 -143
- package/tests/integration/internalization-trigger-guard.test.ts +0 -69
- package/tests/integration/pain-lifecycle-e2e.test.ts +0 -75
- package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +0 -209
- package/tests/service/failure-classifier.test.ts +0 -171
- package/tests/service/internalization-trigger-adapter.test.ts +0 -251
- package/tests/service/monitoring-query-service.test.ts +0 -67
- package/tests/utils/nlp.test.ts +0 -35
- package/tests/utils/plugin-logger.test.ts +0 -156
- package/tests/utils/subagent-probe.test.ts +0 -79
package/src/core/pain-signal.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type PainSeverity as CorePainSeverity,
|
|
3
|
-
type PainSignal as CorePainSignal,
|
|
4
|
-
type PainSignalValidationResult as CorePainSignalValidationResult,
|
|
5
|
-
PainSeveritySchema,
|
|
6
|
-
PainSignalSchema,
|
|
7
|
-
deriveSeverity,
|
|
8
|
-
validatePainSignal,
|
|
9
|
-
} from '@principles/core/runtime-v2';
|
|
10
|
-
|
|
11
|
-
export type PainSeverity = CorePainSeverity;
|
|
12
|
-
export const PainSeverity = PainSeveritySchema;
|
|
13
|
-
|
|
14
|
-
export type PainSignal = CorePainSignal;
|
|
15
|
-
export type PainSignalValidationResult = CorePainSignalValidationResult;
|
|
16
|
-
|
|
17
|
-
export {
|
|
18
|
-
PainSignalSchema,
|
|
19
|
-
deriveSeverity,
|
|
20
|
-
validatePainSignal,
|
|
21
|
-
};
|
|
22
|
-
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PrincipleInjector interface for the Evolution SDK.
|
|
3
|
-
*
|
|
4
|
-
* Wraps the existing principle injection logic into a framework-agnostic
|
|
5
|
-
* contract. Per D-05, this delegates to selectPrinciplesForInjection and
|
|
6
|
-
* formatPrinciple without any behavioral changes.
|
|
7
|
-
*
|
|
8
|
-
* Per D-06, InjectionContext contains only generic fields (domain, sessionId,
|
|
9
|
-
* budgetChars) -- no framework-specific fields.
|
|
10
|
-
*/
|
|
11
|
-
import type { InjectablePrinciple } from './principle-injection.js';
|
|
12
|
-
import { selectPrinciplesForInjection, formatPrinciple } from './principle-injection.js';
|
|
13
|
-
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
// InjectionContext
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
|
|
18
|
-
/** Generic injection context -- no framework-specific fields. */
|
|
19
|
-
export interface InjectionContext {
|
|
20
|
-
/** Domain context (e.g., 'coding', 'writing', 'analysis') */
|
|
21
|
-
domain: string;
|
|
22
|
-
/** Session identifier */
|
|
23
|
-
sessionId: string;
|
|
24
|
-
/** Maximum characters allowed for injected principles */
|
|
25
|
-
budgetChars: number;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
// PrincipleInjector Interface
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Framework-agnostic principle injection interface.
|
|
34
|
-
*
|
|
35
|
-
* Wraps the existing budget-aware selection and formatting logic.
|
|
36
|
-
* Framework adapters convert their context to InjectionContext before calling.
|
|
37
|
-
*/
|
|
38
|
-
export interface PrincipleInjector {
|
|
39
|
-
/**
|
|
40
|
-
* Select principles relevant for injection within a character budget.
|
|
41
|
-
* Delegates to selectPrinciplesForInjection from principle-injection.ts.
|
|
42
|
-
*
|
|
43
|
-
* @param principles - All available principles to select from
|
|
44
|
-
* @param context - Generic injection context with budget constraint
|
|
45
|
-
* @returns Selected principles in injection order
|
|
46
|
-
*/
|
|
47
|
-
getRelevantPrinciples(
|
|
48
|
-
principles: InjectablePrinciple[],
|
|
49
|
-
context: InjectionContext,
|
|
50
|
-
): InjectablePrinciple[];
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Format a single principle for prompt injection.
|
|
54
|
-
* Delegates to formatPrinciple from principle-injection.ts.
|
|
55
|
-
*
|
|
56
|
-
* Format: "- [ID] text"
|
|
57
|
-
*
|
|
58
|
-
* @param principle - The principle to format
|
|
59
|
-
* @returns Formatted string for prompt injection
|
|
60
|
-
*/
|
|
61
|
-
formatForInjection(principle: InjectablePrinciple): string;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// ---------------------------------------------------------------------------
|
|
65
|
-
// Default Implementation
|
|
66
|
-
// ---------------------------------------------------------------------------
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Default implementation that delegates to existing functions.
|
|
70
|
-
* Zero rewrite risk -- behavior is identical to calling the functions directly.
|
|
71
|
-
*/
|
|
72
|
-
export class DefaultPrincipleInjector implements PrincipleInjector {
|
|
73
|
-
getRelevantPrinciples(
|
|
74
|
-
principles: InjectablePrinciple[],
|
|
75
|
-
context: InjectionContext,
|
|
76
|
-
): InjectablePrinciple[] {
|
|
77
|
-
const result = selectPrinciplesForInjection(principles, context.budgetChars);
|
|
78
|
-
return result.selected;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
formatForInjection(principle: InjectablePrinciple): string {
|
|
82
|
-
return formatPrinciple(principle);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Principle Tree Migration — Migrates trainingStore to tree.principles
|
|
3
|
-
*
|
|
4
|
-
* This migration handles the Phase 11 gap: existing principles in trainingStore
|
|
5
|
-
* were never written to tree.principles, blocking the Rule/Implementation layer.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* - Called automatically by migratePrincipleTree() during plugin initialization
|
|
9
|
-
* - Or run manually: node scripts/migrate-principle-tree.mjs <workspace-dir>
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
loadLedger,
|
|
14
|
-
saveLedger,
|
|
15
|
-
type LedgerPrinciple,
|
|
16
|
-
} from './principle-tree-ledger.js';
|
|
17
|
-
import type { LegacyPrincipleTrainingState } from './principle-tree-ledger.js';
|
|
18
|
-
import { SystemLogger } from './system-logger.js';
|
|
19
|
-
|
|
20
|
-
export interface PrincipleTreeMigrationResult {
|
|
21
|
-
migratedCount: number;
|
|
22
|
-
skippedCount: number;
|
|
23
|
-
errorCount: number;
|
|
24
|
-
details: {
|
|
25
|
-
principleId: string;
|
|
26
|
-
status: 'migrated' | 'skipped' | 'error';
|
|
27
|
-
reason?: string;
|
|
28
|
-
}[];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Check if migration is needed by comparing trainingStore and tree.principles
|
|
33
|
-
*/
|
|
34
|
-
export function needsMigration(stateDir: string): boolean {
|
|
35
|
-
const ledger = loadLedger(stateDir);
|
|
36
|
-
return Object.keys(ledger.trainingStore).some((principleId) => !ledger.tree.principles[principleId]);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Create a minimal LedgerPrinciple from LegacyPrincipleTrainingState
|
|
41
|
-
*/
|
|
42
|
-
function trainingStateToTreePrinciple(
|
|
43
|
-
principleId: string,
|
|
44
|
-
state: LegacyPrincipleTrainingState,
|
|
45
|
-
now: string
|
|
46
|
-
): LedgerPrinciple {
|
|
47
|
-
return {
|
|
48
|
-
id: principleId,
|
|
49
|
-
version: 1,
|
|
50
|
-
text: `Principle ${principleId}`, // Minimal text, will be enriched from PRINCIPLES.md if available
|
|
51
|
-
triggerPattern: '', // Unknown from legacy data
|
|
52
|
-
action: '', // Unknown from legacy data
|
|
53
|
-
|
|
54
|
-
status: mapInternalizationStatusToPrincipleStatus(state.internalizationStatus),
|
|
55
|
-
priority: 'P1', // Default priority
|
|
56
|
-
scope: 'general',
|
|
57
|
-
evaluability: state.evaluability,
|
|
58
|
-
valueScore: 0,
|
|
59
|
-
adherenceRate: state.complianceRate * 100, // Convert 0-1 to 0-100
|
|
60
|
-
painPreventedCount: 0,
|
|
61
|
-
derivedFromPainIds: [],
|
|
62
|
-
ruleIds: [],
|
|
63
|
-
conflictsWithPrincipleIds: [],
|
|
64
|
-
createdAt: now,
|
|
65
|
-
updatedAt: now,
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Map internalization status to principle status
|
|
71
|
-
*/
|
|
72
|
-
function mapInternalizationStatusToPrincipleStatus(
|
|
73
|
-
status: LegacyPrincipleTrainingState['internalizationStatus']
|
|
74
|
-
): 'candidate' | 'active' | 'deprecated' {
|
|
75
|
-
switch (status) {
|
|
76
|
-
case 'internalized':
|
|
77
|
-
case 'deployed_pending_eval':
|
|
78
|
-
return 'active';
|
|
79
|
-
case 'regressed':
|
|
80
|
-
case 'needs_training':
|
|
81
|
-
return 'candidate';
|
|
82
|
-
case 'prompt_only':
|
|
83
|
-
case 'in_training':
|
|
84
|
-
return 'candidate';
|
|
85
|
-
default:
|
|
86
|
-
return 'candidate';
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Migrate trainingStore principles to tree.principles
|
|
92
|
-
*
|
|
93
|
-
* This function is idempotent: it only migrates principles that don't exist
|
|
94
|
-
* in tree.principles yet.
|
|
95
|
-
*/
|
|
96
|
-
|
|
97
|
-
export function migratePrincipleTree(
|
|
98
|
-
stateDir: string,
|
|
99
|
-
workspaceDir?: string
|
|
100
|
-
): PrincipleTreeMigrationResult {
|
|
101
|
-
const result: PrincipleTreeMigrationResult = {
|
|
102
|
-
migratedCount: 0,
|
|
103
|
-
skippedCount: 0,
|
|
104
|
-
errorCount: 0,
|
|
105
|
-
details: [],
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
try {
|
|
109
|
-
const ledger = loadLedger(stateDir);
|
|
110
|
-
const now = new Date().toISOString();
|
|
111
|
-
|
|
112
|
-
for (const [principleId, state] of Object.entries(ledger.trainingStore)) {
|
|
113
|
-
// Skip if already exists in tree.principles
|
|
114
|
-
if (ledger.tree.principles[principleId]) {
|
|
115
|
-
result.skippedCount++;
|
|
116
|
-
result.details.push({
|
|
117
|
-
principleId,
|
|
118
|
-
status: 'skipped',
|
|
119
|
-
reason: 'Already exists in tree.principles',
|
|
120
|
-
});
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
try {
|
|
125
|
-
const treePrinciple = trainingStateToTreePrinciple(principleId, state, now);
|
|
126
|
-
const nextLedger = loadLedger(stateDir);
|
|
127
|
-
if (!nextLedger.tree.principles[principleId]) {
|
|
128
|
-
nextLedger.tree.principles[principleId] = treePrinciple;
|
|
129
|
-
nextLedger.tree.lastUpdated = now;
|
|
130
|
-
saveLedger(stateDir, nextLedger);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
result.migratedCount++;
|
|
134
|
-
result.details.push({
|
|
135
|
-
principleId,
|
|
136
|
-
status: 'migrated',
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
if (workspaceDir) {
|
|
140
|
-
SystemLogger.log(
|
|
141
|
-
workspaceDir,
|
|
142
|
-
'PRINCIPLE_TREE_MIGRATED',
|
|
143
|
-
`Migrated ${principleId} from trainingStore to tree.principles`
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
} catch (err) {
|
|
147
|
-
result.errorCount++;
|
|
148
|
-
result.details.push({
|
|
149
|
-
principleId,
|
|
150
|
-
status: 'error',
|
|
151
|
-
reason: String(err),
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
if (workspaceDir) {
|
|
155
|
-
SystemLogger.log(
|
|
156
|
-
workspaceDir,
|
|
157
|
-
'PRINCIPLE_TREE_MIGRATION_ERROR',
|
|
158
|
-
`Failed to migrate ${principleId}: ${String(err)}`
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (workspaceDir && result.migratedCount > 0) {
|
|
165
|
-
SystemLogger.log(
|
|
166
|
-
workspaceDir,
|
|
167
|
-
'PRINCIPLE_TREE_MIGRATION_COMPLETE',
|
|
168
|
-
`Migrated ${result.migratedCount} principles to tree.principles (${result.skippedCount} skipped, ${result.errorCount} errors)`
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
} catch (err) {
|
|
172
|
-
if (workspaceDir) {
|
|
173
|
-
SystemLogger.log(
|
|
174
|
-
workspaceDir,
|
|
175
|
-
'PRINCIPLE_TREE_MIGRATION_FAILED',
|
|
176
|
-
`Migration failed: ${String(err)}`
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return result;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Run migration if needed (called during plugin initialization)
|
|
186
|
-
*/
|
|
187
|
-
export function runMigrationIfNeeded(
|
|
188
|
-
stateDir: string,
|
|
189
|
-
workspaceDir?: string
|
|
190
|
-
): PrincipleTreeMigrationResult | null {
|
|
191
|
-
if (!needsMigration(stateDir)) {
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return migratePrincipleTree(stateDir, workspaceDir);
|
|
196
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* StorageAdapter interface for the Evolution SDK.
|
|
3
|
-
*
|
|
4
|
-
* This interface decouples the evolution engine from specific persistence
|
|
5
|
-
* implementations (file system, SQLite, remote API). All higher-level
|
|
6
|
-
* modules that need to read or mutate the principle ledger should depend
|
|
7
|
-
* on this interface rather than importing principle-tree-ledger directly.
|
|
8
|
-
*
|
|
9
|
-
* The interface uses HybridLedgerStore from principle-tree-ledger as the
|
|
10
|
-
* canonical store shape, but future adapters can map alternative backends
|
|
11
|
-
* to this shape.
|
|
12
|
-
*/
|
|
13
|
-
import type { HybridLedgerStore } from './principle-tree-ledger.js';
|
|
14
|
-
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
// StorageAdapter Interface
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Abstract storage adapter for the principle ledger.
|
|
21
|
-
*
|
|
22
|
-
* Implementations must guarantee:
|
|
23
|
-
* - Atomic writes (no partial/corrupted state on crash)
|
|
24
|
-
* - Thread-safe concurrent access (locking or equivalent)
|
|
25
|
-
* - Consistent read-after-write visibility
|
|
26
|
-
*
|
|
27
|
-
* The `mutateLedger` method is the preferred way to perform read-modify-write
|
|
28
|
-
* cycles. It encapsulates locking so callers never need to manage it.
|
|
29
|
-
*/
|
|
30
|
-
export interface StorageAdapter {
|
|
31
|
-
/**
|
|
32
|
-
* Load the current ledger state from persistence.
|
|
33
|
-
*
|
|
34
|
-
* If no persisted state exists (first run), returns an empty store.
|
|
35
|
-
*/
|
|
36
|
-
loadLedger(): Promise<HybridLedgerStore>;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Persist the full ledger state.
|
|
40
|
-
*
|
|
41
|
-
* Must be atomic — partial writes must never be visible to readers.
|
|
42
|
-
*/
|
|
43
|
-
saveLedger(store: HybridLedgerStore): Promise<void>;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Perform a read-modify-write cycle with automatic locking.
|
|
47
|
-
*
|
|
48
|
-
* The `mutate` function receives the current store and may return a
|
|
49
|
-
* value synchronously or via Promise. The store is persisted after
|
|
50
|
-
* the mutate function resolves, regardless of its return value.
|
|
51
|
-
*
|
|
52
|
-
* If two concurrent `mutateLedger` calls overlap, one must wait for
|
|
53
|
-
* the other to complete (pessimistic locking) or retry on conflict
|
|
54
|
-
* (optimistic locking). The choice is left to the implementation.
|
|
55
|
-
*
|
|
56
|
-
* @example
|
|
57
|
-
* ```ts
|
|
58
|
-
* const count = await adapter.mutateLedger((store) => {
|
|
59
|
-
* store.tree.principles['p-1'] = newPrinciple;
|
|
60
|
-
* return Object.keys(store.tree.principles).length;
|
|
61
|
-
* });
|
|
62
|
-
* ```
|
|
63
|
-
*/
|
|
64
|
-
mutateLedger<T>(mutate: (store: HybridLedgerStore) => T | Promise<T>): Promise<T>;
|
|
65
|
-
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TelemetryEvent schema for the Evolution SDK.
|
|
3
|
-
*
|
|
4
|
-
* TypeBox schema describing the shape of in-process evolution events.
|
|
5
|
-
* Per D-07, this is a documentation artifact -- the existing EvolutionLogger
|
|
6
|
-
* output should conform to this schema. No new TelemetryService is created.
|
|
7
|
-
*
|
|
8
|
-
* Per D-08, covers the 3 core events aligned with EvolutionHook:
|
|
9
|
-
* - pain_detected (maps to EvolutionStage 'pain_detected')
|
|
10
|
-
* - principle_candidate_created (maps to EvolutionStage 'principle_generated')
|
|
11
|
-
* - principle_promoted (maps to EvolutionStage 'completed')
|
|
12
|
-
*
|
|
13
|
-
* Injection and storage events are out of scope for this phase.
|
|
14
|
-
*/
|
|
15
|
-
import { Type, type Static } from '@sinclair/typebox';
|
|
16
|
-
import { Value } from '@sinclair/typebox/value';
|
|
17
|
-
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
// Event Type Union
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* The 3 core telemetry event types, aligned with EvolutionHook methods.
|
|
24
|
-
*
|
|
25
|
-
* Mapping to existing EvolutionLogger stages:
|
|
26
|
-
* - pain_detected -> EvolutionStage 'pain_detected'
|
|
27
|
-
* - principle_candidate_created -> EvolutionStage 'principle_generated'
|
|
28
|
-
* - principle_promoted -> EvolutionStage 'completed'
|
|
29
|
-
*/
|
|
30
|
-
export const TelemetryEventType = Type.Union([
|
|
31
|
-
Type.Literal('pain_detected'),
|
|
32
|
-
Type.Literal('principle_candidate_created'),
|
|
33
|
-
Type.Literal('principle_promoted'),
|
|
34
|
-
]);
|
|
35
|
-
|
|
36
|
-
export type TelemetryEventType = Static<typeof TelemetryEventType>;
|
|
37
|
-
|
|
38
|
-
// ---------------------------------------------------------------------------
|
|
39
|
-
// TelemetryEvent Schema
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Schema for an in-process telemetry event.
|
|
44
|
-
*
|
|
45
|
-
* Fields align with existing EvolutionLogEntry:
|
|
46
|
-
* - traceId <-> EvolutionLogEntry.traceId
|
|
47
|
-
* - timestamp <-> EvolutionLogEntry.timestamp
|
|
48
|
-
* - sessionId <-> EvolutionLogEntry.sessionId
|
|
49
|
-
* - payload <-> EvolutionLogEntry.metadata
|
|
50
|
-
*
|
|
51
|
-
* No PII fields. The agentId field is optional and contains only
|
|
52
|
-
* system identifiers (e.g., 'main', 'builder'), never user data.
|
|
53
|
-
*/
|
|
54
|
-
export const TelemetryEventSchema = Type.Object({
|
|
55
|
-
/** Event type (one of the 3 core types) */
|
|
56
|
-
eventType: TelemetryEventType,
|
|
57
|
-
/** Correlation trace ID for linking events across the pipeline */
|
|
58
|
-
traceId: Type.String({ minLength: 1 }),
|
|
59
|
-
/** ISO 8601 timestamp */
|
|
60
|
-
timestamp: Type.String({ minLength: 1 }),
|
|
61
|
-
/** Session identifier */
|
|
62
|
-
sessionId: Type.String(),
|
|
63
|
-
/** Agent identifier (system identifier only, no PII) */
|
|
64
|
-
agentId: Type.Optional(Type.String()),
|
|
65
|
-
/** Event-specific payload */
|
|
66
|
-
payload: Type.Record(Type.String(), Type.Unknown()),
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
export type TelemetryEvent = Static<typeof TelemetryEventSchema>;
|
|
70
|
-
|
|
71
|
-
// ---------------------------------------------------------------------------
|
|
72
|
-
// Validation
|
|
73
|
-
// ---------------------------------------------------------------------------
|
|
74
|
-
|
|
75
|
-
export interface TelemetryEventValidationResult {
|
|
76
|
-
valid: boolean;
|
|
77
|
-
errors: string[];
|
|
78
|
-
event?: TelemetryEvent;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Validates an arbitrary object against the TelemetryEvent schema.
|
|
83
|
-
*
|
|
84
|
-
* Returns a structured result with:
|
|
85
|
-
* - `valid`: whether the input conforms to the schema
|
|
86
|
-
* - `errors`: human-readable list of validation failures
|
|
87
|
-
* - `event`: the typed event (only present when valid)
|
|
88
|
-
*/
|
|
89
|
-
export function validateTelemetryEvent(input: unknown): TelemetryEventValidationResult {
|
|
90
|
-
if (typeof input !== 'object' || input === null || Array.isArray(input)) {
|
|
91
|
-
return { valid: false, errors: ['Input must be a non-null object'] };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const errors = [...Value.Errors(TelemetryEventSchema, input)];
|
|
95
|
-
if (errors.length > 0) {
|
|
96
|
-
return {
|
|
97
|
-
valid: false,
|
|
98
|
-
errors: errors.map(
|
|
99
|
-
(e) => `${e.path ? `${e.path}: ` : ''}${e.message}`,
|
|
100
|
-
),
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
valid: true,
|
|
106
|
-
errors: [],
|
|
107
|
-
event: Value.Cast(TelemetryEventSchema, input) as TelemetryEvent,
|
|
108
|
-
};
|
|
109
|
-
}
|