Release v4.2.6: Critical bugfix for observation validation

Critical Bugfix:
- Fixed overly defensive observation validation blocking observations from being saved
- Parser now NEVER skips observations - always saves them
- Invalid or missing type defaults to "change" (generic catch-all type)
- Removed validation requiring title, subtitle, and narrative fields
- Prevents critical data loss - partial observations better than no observations

Impact:
- Before: Missing title, subtitle, OR narrative caused entire observation to be discarded
- After: ALL observations preserved regardless of field completeness
- Even partial observations contain valuable data: concepts, files_read, files_modified, facts
- LLMs make mistakes - system must be resilient and save everything
- Consistent with v4.2.5 summary fix

Technical changes:
- Updated src/sdk/parser.ts:52-67 to never skip observations
- Uses "change" as fallback type for invalid/missing types (no schema change)
- Updated ParsedObservation interface to allow null for title, subtitle, narrative
- Updated SessionStore.storeObservation signature to accept nullable fields
- Updated built worker-service.cjs
- Bumped version to 4.2.6 in all metadata files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Alex Newman
2025-10-24 21:25:44 -04:00
parent 21c7ab2929
commit 322cb94c43
8 changed files with 59 additions and 34 deletions
+22 -22
View File
@@ -7,10 +7,10 @@ import { logger } from '../utils/logger.js';
export interface ParsedObservation {
type: string;
title: string;
subtitle: string;
title: string | null;
subtitle: string | null;
facts: string[];
narrative: string;
narrative: string | null;
concepts: string[];
files_read: string[];
files_modified: string[];
@@ -49,39 +49,39 @@ export function parseObservations(text: string, correlationId?: string): ParsedO
const files_read = extractArrayElements(obsContent, 'files_read', 'file');
const files_modified = extractArrayElements(obsContent, 'files_modified', 'file');
// Validate required fields
if (!type || !title || !subtitle || !narrative) {
logger.warn('PARSER', 'Observation missing required fields, skipping', {
correlationId,
hasType: !!type,
hasTitle: !!title,
hasSubtitle: !!subtitle,
hasNarrative: !!narrative
});
continue;
// NOTE FROM THEDOTMACK: ALWAYS save observations - never skip. 10/24/2025
// All fields except type are nullable in schema
// If type is missing or invalid, use "change" as catch-all fallback
// Determine final type
let finalType = 'change'; // Default catch-all
if (type) {
const validTypes = ['bugfix', 'feature', 'refactor', 'change', 'discovery', 'decision'];
if (validTypes.includes(type.trim())) {
finalType = type.trim();
} else {
logger.warn('PARSER', `Invalid observation type: ${type}, using "change"`, { correlationId });
}
} else {
logger.warn('PARSER', 'Observation missing type field, using "change"', { correlationId });
}
// Validate type
const validTypes = ['bugfix', 'feature', 'refactor', 'change', 'discovery', 'decision'];
if (!validTypes.includes(type.trim())) {
logger.warn('PARSER', `Invalid observation type: ${type}, skipping`, { correlationId });
continue;
}
// All other fields are optional - save whatever we have
// Filter out type from concepts array (types and concepts are separate dimensions)
const cleanedConcepts = concepts.filter(c => c !== type.trim());
const cleanedConcepts = concepts.filter(c => c !== finalType);
if (cleanedConcepts.length !== concepts.length) {
logger.warn('PARSER', 'Removed observation type from concepts array', {
correlationId,
type: type.trim(),
type: finalType,
originalConcepts: concepts,
cleanedConcepts
});
}
observations.push({
type: type.trim(),
type: finalType,
title,
subtitle,
facts,