Closes the long-range feedback loop: outcome signals now enrich the full
traceability chain and feed back into routing, triage, and AI proposals.
Schema (T01):
- outcome_correlations (CHECK correlation_type)
- pattern_performance_records
- adaptive_threshold_configs
- institutional_knowledge_entries (GIN tsvector FTS)
- learning_insights (CHECK insight_type)
- ALTER TABLE decision_records + requirement_candidates: outcome_summary JSONB
- AFTER INSERT trigger trg_enrich_lineage on outcome_signals
- contracts/core/ updated (outcome-summary-columns-v1, append-only addendum)
Correlation engine (T02):
- Application/Helper/CorrelationEngine.hs: pure annotation→outcome SQL
- Web/Controller/OutcomeCorrelations.hs: ComputeCorrelationsAction + index
Pattern performance (T03):
- Web/Controller/PatternPerformance.hs: ComputePatternPerformanceAction
Adaptive thresholds (T04):
- Web/Controller/AdaptiveThresholds.hs: CalibrateThresholdsAction
- Application/Helper/FrictionScore.hs: applyAdaptiveWeights
Institutional knowledge (T05):
- DistilDecisionAction in DecisionRecords controller
- Web/Controller/InstitutionalKnowledge.hs: QueryKnowledgeBaseAction
Lineage enrichment (T06):
- Web/Controller/LineageEnrichment.hs: EnrichLineageAction (batch backfill)
- enrich_lineage_on_outcome_batch() PL/pgSQL helper in migration
Learning dashboard (T07):
- Web/Controller/LearningDashboard.hs: 5-panel autoRefresh view
- "Learning" nav link in FrontController
API v2 learning endpoints (T08):
- GET /api/v2/outcome-correlations, /pattern-performance, /knowledge-base/{id}
- OpenAPI schemas: OutcomeCorrelation, PatternPerformanceRecord, InstitutionalKnowledgeEntry
GAAF scorecard + docs (T09):
- Core 3.8→3.9, Functional 3.6→3.8, overall 3.61→3.68
- CLAUDE.md: IHF v0.2 complete, no active workplan
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3.6 KiB
Append-Only Events Contract
Name: append-only-events Version: 1.0 Date: 2026-03-31 Status: Active Layer: Core Immutable: Yes — this invariant is permanent and cannot be relaxed
Purpose
Interaction events and outcome signals are the primary observational record of the IHF. Their integrity as an append-only log is a foundational invariant: governance, traceability, and antifragility all depend on the fact that the historical record cannot be silently rewritten.
Invariant
The following tables are append-only:
| Table | Trigger (no update) | Trigger (no delete) |
|---|---|---|
interaction_events |
interaction_events_no_update |
interaction_events_no_delete |
outcome_signals |
outcome_signals_no_update |
outcome_signals_no_delete |
No row in either table may be modified or deleted after insertion. This
invariant is enforced at the PostgreSQL level by BEFORE UPDATE and
BEFORE DELETE triggers that raise exceptions. It cannot be bypassed by
application code.
Enforcement
-- Implemented in Application/Schema.sql
CREATE TRIGGER interaction_events_no_update
BEFORE UPDATE ON interaction_events
FOR EACH ROW EXECUTE FUNCTION prevent_interaction_event_mutation();
CREATE TRIGGER interaction_events_no_delete
BEFORE DELETE ON interaction_events
FOR EACH ROW EXECUTE FUNCTION prevent_interaction_event_mutation();
-- Same pattern for outcome_signals
The trigger function raises:
"interaction_events is append-only: UPDATE and DELETE are not permitted"
Correction Policy
Erroneous events must not be corrected by modifying the original row. The correct approach is to insert a new event:
{
"event_type": "retracted",
"metadata": {
"retracted_event_id": "<uuid of original event>",
"reason": "incorrect actor attribution"
}
}
The original event remains in the table. Downstream analysis should treat
retracted events as markers that exclude the referenced event from
calculations.
Failure Mode
Any attempt to UPDATE or DELETE a row in interaction_events or
outcome_signals raises a PostgreSQL exception with SQLSTATE P0001.
The calling transaction is aborted. No partial mutation is possible.
Scope
This contract applies only to interaction_events and outcome_signals.
Other append-oriented tables (triage_states, widget_ownerships,
stewardship_roles) use the same conceptual pattern (soft expiry instead of
update, no hard delete) but are not covered by DB-trigger enforcement. Their
append semantics are enforced by application-layer controller conventions.
Phase 12 Addendum — Lineage Enrichment Trigger
Added in Phase 12 (IHUB-WP-0013 T06): outcome_signals now has an
AFTER INSERT trigger trg_enrich_lineage that calls
enrich_lineage_on_outcome().
This trigger:
- Is AFTER INSERT only — it never fires on UPDATE or DELETE.
- Does not modify
outcome_signals— it only enriches upstream records (decision_records.outcome_summary,requirement_candidates.outcome_summary). - Is consistent with the append-only invariant: the
outcome_signalsrow itself remains immutable after insertion.
Implementation Reference
- Functions:
prevent_interaction_event_mutation(),prevent_outcome_signal_mutation()inApplication/Schema.sql - Phase 12:
enrich_lineage_on_outcome()— AFTER INSERT trigger onoutcome_signals; enriches non-append-only columns on upstream tables only - The architectural fitness function
Test/Architecture/LayerBoundarySpec.hs(Test 1) verifies these trigger names are present in the schema