Contracts

events.md

Event Contracts: Glossary Semantic Integrity

Feature: 041-mission-glossary-semantic-integrity Date: 2026-02-16 Status: Complete Authority: Feature 007 (spec-kitty-events package)

Overview

This document references the canonical glossary event schemas defined in Feature 007. CLI implementation MUST import these events from spec-kitty-events package, NOT redefine them locally.

Package import path: spec_kitty_events.glossary.events

Contract authority: /Users/robert/ClaudeCowork/Spec-Kitty-Cowork/spec-kitty-planning/product-ideas/prd-mission-glossary-semantic-integrity-v1.md


Canonical Events (Feature 007)

1. GlossaryScopeActivated

Trigger: Mission starts or scope selected Purpose: Record which glossary scopes are active for this mission run

Required fields:

{
    "event_type": "GlossaryScopeActivated",
    "scope_id": str,              # e.g., "team_domain"
    "glossary_version_id": str,   # e.g., "v3"
    "timestamp": datetime,
    "mission_id": str,
    "run_id": str
}

Consumers: Orchestration runtime, projection layer

Example:

GlossaryScopeActivated(
    scope_id="team_domain",
    glossary_version_id="v3",
    timestamp=datetime(2026, 2, 16, 12, 0, 0),
    mission_id="041-mission",
    run_id="run-2026-02-16-001"
)

2. TermCandidateObserved

Trigger: New or uncertain term appears in step input/output Purpose: Record term extraction for candidate glossary entry

Required fields:

{
    "event_type": "TermCandidateObserved",
    "term": str,                # e.g., "workspace"
    "source_step": str,         # e.g., "step-specify-001"
    "actor_id": str,            # e.g., "user:alice" or "llm:claude-sonnet-4"
    "confidence": float,        # 0.0-1.0
    "extraction_method": str,   # e.g., "metadata_hint", "casing_pattern"
    "context": str,             # e.g., "description field"
    "timestamp": datetime,
    "mission_id": str,
    "run_id": str
}

Consumers: Candidate scorer, clarification policy

Example:

TermCandidateObserved(
    term="workspace",
    source_step="step-specify-001",
    actor_id="llm:claude-sonnet-4",
    confidence=0.8,
    extraction_method="casing_pattern",
    context="description field",
    timestamp=datetime(2026, 2, 16, 12, 5, 0),
    mission_id="041-mission",
    run_id="run-2026-02-16-001"
)

3. SemanticCheckEvaluated

Trigger: Step-level pre-generation validation completes Purpose: Record conflict detection results and recommended action

Required fields:

{
    "event_type": "SemanticCheckEvaluated",
    "step_id": str,
    "mission_id": str,
    "run_id": str,
    "timestamp": datetime,
    "findings": List[ConflictFinding],  # See structure below
    "overall_severity": str,            # "low" | "medium" | "high"
    "confidence": float,                # 0.0-1.0
    "effective_strictness": str,        # "off" | "medium" | "max"
    "recommended_action": str,          # "proceed" | "warn" | "block"
    "blocked": bool                     # True if generation was blocked
}

ConflictFinding structure:

{
    "term": str,
    "conflict_type": str,  # "unknown" | "ambiguous" | "inconsistent" | "unresolved_critical"
    "severity": str,       # "low" | "medium" | "high"
    "confidence": float,
    "candidate_senses": List[SenseRef],  # See structure below
    "context": str
}

SenseRef structure:

{
    "surface": str,
    "scope": str,
    "definition": str,
    "confidence": float
}

Consumers: Execution gate, UX prompt, dashboards

Example:

SemanticCheckEvaluated(
    step_id="step-specify-001",
    mission_id="041-mission",
    run_id="run-2026-02-16-001",
    timestamp=datetime(2026, 2, 16, 12, 10, 0),
    findings=[
        ConflictFinding(
            term="workspace",
            conflict_type="ambiguous",
            severity="high",
            confidence=0.9,
            candidate_senses=[
                SenseRef(surface="workspace", scope="team_domain",
                         definition="Git worktree directory", confidence=0.9),
                SenseRef(surface="workspace", scope="team_domain",
                         definition="VS Code workspace file", confidence=0.7)
            ],
            context="description field"
        )
    ],
    overall_severity="high",
    confidence=0.9,
    effective_strictness="medium",
    recommended_action="block",
    blocked=True
)

4. GlossaryClarificationRequested

Trigger: Policy requires user clarification (high severity or low confidence critical term) Purpose: Record clarification question sent to user

Required fields:

{
    "event_type": "GlossaryClarificationRequested",
    "question": str,          # Human-readable question
    "term": str,              # Term requiring clarification
    "options": List[str],     # Ranked candidate definitions
    "urgency": str,           # "high" | "medium" | "low"
    "timestamp": datetime,
    "mission_id": str,
    "run_id": str,
    "step_id": str,
    "conflict_id": str        # UUID for tracking resolution
}

Consumers: Mission participant UI, CLI, SaaS decision inbox

Example:

GlossaryClarificationRequested(
    question="What does 'workspace' mean in this context?",
    term="workspace",
    options=[
        "Git worktree directory for a work package",
        "VS Code workspace configuration file"
    ],
    urgency="high",
    timestamp=datetime(2026, 2, 16, 12, 15, 0),
    mission_id="041-mission",
    run_id="run-2026-02-16-001",
    step_id="step-specify-001",
    conflict_id="uuid-1234-5678"
)

5. GlossaryClarificationResolved

Trigger: User/participant answer accepted Purpose: Record conflict resolution (selected from candidates)

Required fields:

{
    "event_type": "GlossaryClarificationResolved",
    "conflict_id": str,           # UUID from GlossaryClarificationRequested
    "term_surface": str,          # e.g., "workspace"
    "selected_sense": SenseRef,   # See structure in SemanticCheckEvaluated
    "actor": ActorIdentity,       # See structure below
    "timestamp": datetime,
    "resolution_mode": str,       # "interactive" | "async"
    "provenance": Provenance      # See structure below
}

ActorIdentity structure:

{
    "actor_id": str,
    "actor_type": str,  # "human" | "llm" | "service"
    "display_name": str
}

Provenance structure:

{
    "source": str,      # e.g., "user_clarification"
    "timestamp": datetime,
    "actor_id": str
}

Consumers: Glossary updater, execution gate

Example:

GlossaryClarificationResolved(
    conflict_id="uuid-1234-5678",
    term_surface="workspace",
    selected_sense=SenseRef(
        surface="workspace",
        scope="team_domain",
        definition="Git worktree directory for a work package",
        confidence=0.9
    ),
    actor=ActorIdentity(
        actor_id="user:alice",
        actor_type="human",
        display_name="Alice"
    ),
    timestamp=datetime(2026, 2, 16, 12, 20, 0),
    resolution_mode="interactive",
    provenance=Provenance(
        source="user_clarification",
        timestamp=datetime(2026, 2, 16, 12, 20, 0),
        actor_id="user:alice"
    )
)

6. GlossarySenseUpdated

Trigger: New sense or sense edit accepted (custom definition from user) Purpose: Record glossary modification

Required fields:

{
    "event_type": "GlossarySenseUpdated",
    "term_surface": str,
    "scope": str,                # e.g., "team_domain"
    "new_sense": TermSense,      # See structure below
    "actor": ActorIdentity,
    "timestamp": datetime,
    "update_type": str,          # "create" | "update"
    "provenance": Provenance
}

TermSense structure:

{
    "surface": str,
    "scope": str,
    "definition": str,
    "confidence": float,
    "status": str  # "draft" | "active" | "deprecated"
}

Consumers: Projection, audit views

Example:

GlossarySenseUpdated(
    term_surface="workspace",
    scope="team_domain",
    new_sense=TermSense(
        surface="workspace",
        scope="team_domain",
        definition="Git worktree directory for a work package",
        confidence=1.0,
        status="active"
    ),
    actor=ActorIdentity(actor_id="user:alice", actor_type="human", display_name="Alice"),
    timestamp=datetime(2026, 2, 16, 12, 25, 0),
    update_type="create",
    provenance=Provenance(
        source="user_clarification",
        timestamp=datetime(2026, 2, 16, 12, 25, 0),
        actor_id="user:alice"
    )
)

7. GenerationBlockedBySemanticConflict

Trigger: High-severity unresolved conflict at generation boundary Purpose: Record generation gate block decision

Required fields:

{
    "event_type": "GenerationBlockedBySemanticConflict",
    "step_id": str,
    "mission_id": str,
    "run_id": str,
    "timestamp": datetime,
    "conflicts": List[ConflictFinding],  # See structure in SemanticCheckEvaluated
    "strictness_mode": str,              # "off" | "medium" | "max"
    "effective_strictness": str          # Resolved strictness (after precedence)
}

Consumers: Execution runtime, dashboards, audit logs

Example:

GenerationBlockedBySemanticConflict(
    step_id="step-specify-001",
    mission_id="041-mission",
    run_id="run-2026-02-16-001",
    timestamp=datetime(2026, 2, 16, 12, 30, 0),
    conflicts=[
        ConflictFinding(
            term="workspace",
            conflict_type="ambiguous",
            severity="high",
            confidence=0.9,
            candidate_senses=[...],
            context="description field"
        )
    ],
    strictness_mode="medium",
    effective_strictness="medium"
)

CLI-Specific Events (Pending Feature 007 Approval)

8. StepCheckpointed

Trigger: Before generation gate evaluation (for resume capability) Purpose: Save minimal state for deterministic resume after conflict resolution

Required fields:

{
    "event_type": "StepCheckpointed",
    "mission_id": str,
    "run_id": str,
    "step_id": str,
    "strictness": str,           # "off" | "medium" | "max"
    "scope_refs": List[ScopeRef],  # See structure below
    "input_hash": str,           # SHA256 of step inputs
    "cursor": str,               # e.g., "pre_generation_gate"
    "retry_token": str,          # UUID
    "timestamp": datetime
}

ScopeRef structure:

{
    "scope": str,          # e.g., "team_domain"
    "version_id": str      # e.g., "v3"
}

Consumers: Resume middleware, replay engine

Example:

StepCheckpointed(
    mission_id="041-mission",
    run_id="run-2026-02-16-001",
    step_id="step-specify-001",
    strictness="medium",
    scope_refs=[
        ScopeRef(scope="mission_local", version_id="v1"),
        ScopeRef(scope="team_domain", version_id="v3")
    ],
    input_hash="abc123...",
    cursor="pre_generation_gate",
    retry_token="uuid-9999-0000",
    timestamp=datetime(2026, 2, 16, 12, 35, 0)
)

Status: This event may need to be added to Feature 007 canonical events. If not present in spec-kitty-events package, CLI should stub adapter and gate implementation on package update.


Event Emission Guidelines

1. Import from spec-kitty-events package:

``python from spec_kitty_events.glossary.events import ( GlossaryScopeActivated, TermCandidateObserved, SemanticCheckEvaluated, # ... etc ) ``

2. DO NOT redefine event schemas locally - use canonical contracts from package

3. If event not in package yet (e.g., StepCheckpointed):

  • Stub adapter boundary in src/specify_cli/glossary/events.py
  • Document as "pending Feature 007 approval"
  • Gate implementation on package availability

4. Event order matters for replay:

  • Lamport clocks ensure causal ordering
  • Events are append-only, immutable
  • Replay: same events → same state (deterministic)

5. All events MUST include:

  • timestamp (ISO 8601 format)
  • mission_id and run_id (for scoping)
  • Appropriate actor/provenance metadata

Testing Event Contracts

Unit tests (src/specify_cli/glossary/tests/test_events.py):

  • Verify event emission at correct middleware boundaries
  • Validate event payload conforms to schema (all required fields present)
  • Test event ordering (extraction → check → gate → clarification → resolution)

Integration tests:

  • Full pipeline: extract → check → block → clarify → resolve → resume
  • Verify events emitted in correct order
  • Replay events, assert glossary state matches

Contract tests (if spec-kitty-events package available):

  • Import canonical event classes
  • Assert CLI payloads match package schemas
  • Test serialization/deserialization round-trip

See Also

  • data-model.md - Entity definitions
  • middleware.md - Middleware interface contracts
  • Feature 007 spec: /Users/robert/ClaudeCowork/Spec-Kitty-Cowork/spec-kitty-planning/product-ideas/prd-mission-glossary-semantic-integrity-v1.md

middleware.md

Middleware Contracts: Glossary Semantic Integrity

Feature: 041-mission-glossary-semantic-integrity Date: 2026-02-16 Status: Complete

Overview

This document defines the middleware pipeline interfaces for glossary semantic integrity checks. All middleware components follow a common protocol and execute in a fixed order.


Base Middleware Protocol

All glossary middleware components implement this protocol:

from typing import Protocol
from specify_cli.missions.primitives import PrimitiveExecutionContext

class GlossaryMiddleware(Protocol):
    """Base protocol for glossary middleware components."""

    def process(self, context: PrimitiveExecutionContext) -> PrimitiveExecutionContext:
        """
        Process primitive execution context.

        Args:
            context: Current execution context with inputs, metadata, config

        Returns:
            Modified context (may add fields like extracted_terms, conflicts)

        Raises:
            BlockedByConflict: If generation must be blocked
        """
        ...

Middleware Pipeline Order

Middleware executes in this fixed order:

1. GlossaryCandidateExtractionMiddleware - Extract terms from step I/O 2. SemanticCheckMiddleware - Resolve terms, detect conflicts 3. GenerationGateMiddleware - Block generation on high-severity conflicts 4. ClarificationMiddleware - Prompt user for resolution (if blocked) 5. ResumeMiddleware - Load checkpoint, restore state (on retry)


1. GlossaryCandidateExtractionMiddleware

Purpose: Extract candidate terms from step inputs/outputs using metadata hints + deterministic heuristics.

Interface:

class GlossaryCandidateExtractionMiddleware:
    """Extract candidate terms from step inputs/outputs."""

    def __init__(self, extraction_config: ExtractionConfig):
        """
        Initialize extraction middleware.

        Args:
            extraction_config: Configuration for term extraction
                - metadata_hints: glossary_watch_terms, glossary_aliases, etc.
                - heuristic_patterns: quoted_phrases, acronyms, casing
                - confidence_thresholds: high/medium/low boundaries
        """
        self.extraction_config = extraction_config

    def process(self, context: PrimitiveExecutionContext) -> PrimitiveExecutionContext:
        """
        Extract terms from step inputs/outputs.

        Modifies context:
            - Adds context.extracted_terms: List[ExtractedTerm]

        Emits events:
            - TermCandidateObserved (for each extracted term)

        Returns:
            Modified context with extracted_terms field
        """
        ...

ExtractedTerm structure:

from dataclasses import dataclass

@dataclass
class ExtractedTerm:
    surface: str                # e.g., "workspace"
    confidence: float           # 0.0-1.0
    extraction_method: str      # e.g., "metadata_hint", "casing_pattern"
    context: str                # e.g., "description field"
    source_step: str            # e.g., "step-specify-001"

Example usage:

extraction_middleware = GlossaryCandidateExtractionMiddleware(
    extraction_config=ExtractionConfig(
        metadata_hints={
            "glossary_watch_terms": ["workspace", "mission"],
            "glossary_fields": ["description", "requirements"]
        },
        heuristic_patterns=["quoted_phrases", "acronyms", "casing"],
        confidence_thresholds={"high": 0.8, "medium": 0.5, "low": 0.3}
    )
)

context = extraction_middleware.process(context)
# context.extracted_terms = [ExtractedTerm(...), ...]

2. SemanticCheckMiddleware

Purpose: Resolve extracted terms against scope hierarchy, detect conflicts.

Interface:

class SemanticCheckMiddleware:
    """Resolve terms and detect semantic conflicts."""

    def __init__(self, glossary_store: GlossaryStore):
        """
        Initialize semantic check middleware.

        Args:
            glossary_store: Access to active glossary scopes
                - Provides scope resolution (mission_local -> team_domain -> audience_domain -> spec_kitty_core)
                - Loads seed files from .kittify/glossaries/
        """
        self.glossary_store = glossary_store

    def process(self, context: PrimitiveExecutionContext) -> PrimitiveExecutionContext:
        """
        Resolve terms and detect conflicts.

        Requires:
            - context.extracted_terms (from extraction middleware)

        Modifies context:
            - Adds context.conflicts: List[SemanticConflict]

        Emits events:
            - SemanticCheckEvaluated (with findings, overall severity, recommended action)

        Returns:
            Modified context with conflicts field
        """
        ...

SemanticConflict structure (see data-model.md):

from dataclasses import dataclass
from enum import Enum

class ConflictType(Enum):
    UNKNOWN = "unknown"
    AMBIGUOUS = "ambiguous"
    INCONSISTENT = "inconsistent"
    UNRESOLVED_CRITICAL = "unresolved_critical"

class Severity(Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"

@dataclass
class SemanticConflict:
    term: str                       # e.g., "workspace"
    conflict_type: ConflictType
    severity: Severity
    confidence: float               # 0.0-1.0
    candidate_senses: List[SenseRef]
    context: str                    # Usage location

Example usage:

semantic_check_middleware = SemanticCheckMiddleware(
    glossary_store=GlossaryStore(
        scopes=[GlossaryScope.MISSION_LOCAL, GlossaryScope.TEAM_DOMAIN,
                GlossaryScope.AUDIENCE_DOMAIN, GlossaryScope.SPEC_KITTY_CORE]
    )
)

context = semantic_check_middleware.process(context)
# context.conflicts = [SemanticConflict(...), ...]

3. GenerationGateMiddleware

Purpose: Block LLM generation on unresolved high-severity conflicts.

Interface:

class GenerationGateMiddleware:
    """Block generation on unresolved high-severity conflicts."""

    def __init__(self, strictness_policy: StrictnessPolicy):
        """
        Initialize generation gate middleware.

        Args:
            strictness_policy: Strictness mode configuration
                - Resolves precedence: global -> mission -> step -> runtime override
                - Modes: off, medium, max
        """
        self.strictness_policy = strictness_policy

    def process(self, context: PrimitiveExecutionContext) -> PrimitiveExecutionContext:
        """
        Block generation if high-severity conflicts exist.

        Requires:
            - context.conflicts (from semantic check middleware)
            - context.strictness (resolved strictness mode)

        Emits events:
            - GenerationBlockedBySemanticConflict (if blocked)

        Raises:
            BlockedByConflict: If generation must be blocked

        Returns:
            Unmodified context (if pass)
        """
        ...

Strictness policy logic:

from enum import Enum

class Strictness(Enum):
    OFF = "off"       # No blocking
    MEDIUM = "medium" # Block only high-severity
    MAX = "max"       # Block any unresolved conflict

def should_block(strictness: Strictness, conflicts: List[SemanticConflict]) -> bool:
    """Determine if generation should be blocked."""
    if strictness == Strictness.OFF:
        return False
    elif strictness == Strictness.MEDIUM:
        return any(c.severity == Severity.HIGH for c in conflicts)
    elif strictness == Strictness.MAX:
        return len(conflicts) > 0

Example usage:

generation_gate_middleware = GenerationGateMiddleware(
    strictness_policy=StrictnessPolicy(
        global_default=Strictness.MEDIUM,
        mission_override=None,
        step_override=None,
        runtime_override=None
    )
)

try:
    context = generation_gate_middleware.process(context)
    # Generation allowed - proceed to next step
except BlockedByConflict as e:
    # Generation blocked - proceed to clarification
    conflicts = e.conflicts

4. ClarificationMiddleware

Purpose: Render ranked candidates, prompt user for resolution (interactive or async).

Interface:

class ClarificationMiddleware:
    """Prompt user for conflict resolution."""

    def __init__(self, interaction_mode: InteractionMode):
        """
        Initialize clarification middleware.

        Args:
            interaction_mode: Interactive (CLI) or non-interactive (CI)
        """
        self.interaction_mode = interaction_mode

    def process(self, context: PrimitiveExecutionContext) -> PrimitiveExecutionContext:
        """
        Prompt user for conflict resolution.

        Requires:
            - context.conflicts (from generation gate)

        Emits events:
            - GlossaryClarificationRequested (on defer or non-interactive)
            - GlossaryClarificationResolved (on user selection)
            - GlossarySenseUpdated (on custom sense)

        Raises:
            DeferredToAsync: If user defers resolution

        Returns:
            Modified context with updated glossary (if resolved)
        """
        ...

Clarification flow:

from enum import Enum

class ClarificationChoice(Enum):
    CANDIDATE_SELECTED = "candidate"  # User picked from candidates
    CUSTOM_SENSE = "custom"           # User provided custom definition
    DEFER_ASYNC = "defer"             # User deferred to async resolution

def prompt_for_resolution(conflict: SemanticConflict) -> ClarificationChoice:
    """
    Render conflict and prompt for resolution.

    Displays:
        - Term, context, scope
        - Ranked candidate senses (by confidence)
        - Options: 1..N (candidates), C (custom), D (defer)

    Returns:
        User's choice
    """
    ...

Example usage:

clarification_middleware = ClarificationMiddleware(
    interaction_mode=InteractionMode.INTERACTIVE
)

try:
    context = clarification_middleware.process(context)
    # User resolved conflict - glossary updated
except DeferredToAsync:
    # User deferred resolution - exit with blocked status
    exit(1)

5. ResumeMiddleware

Purpose: Load checkpoint from events, restore step execution context.

Interface:

class ResumeMiddleware:
    """Resume execution from checkpoint after conflict resolution."""

    def __init__(self, checkpoint_store: CheckpointStore):
        """
        Initialize resume middleware.

        Args:
            checkpoint_store: Access to StepCheckpointed events
                - Loads from event log
                - Verifies input_hash integrity
        """
        self.checkpoint_store = checkpoint_store

    def process(self, context: PrimitiveExecutionContext) -> PrimitiveExecutionContext:
        """
        Resume from checkpoint.

        Requires:
            - context.retry_token (from user retry request)

        Modifies context:
            - Restores strictness, scope_refs from checkpoint
            - Loads updated glossary state from GlossarySenseUpdated events

        Emits events:
            - None (checkpoint already exists)

        Returns:
            Restored context (ready to resume from cursor)
        """
        ...

Resume flow:

def resume_from_checkpoint(retry_token: str, context: PrimitiveExecutionContext) -> PrimitiveExecutionContext:
    """
    Resume execution from checkpoint.

    Steps:
        1. Load StepCheckpointed event by retry_token
        2. Verify input_hash matches current inputs
        3. If changed: prompt user for confirmation
        4. Restore strictness, scope_refs from checkpoint
        5. Load updated glossary state from events
        6. Resume from cursor (skip already-completed stages)

    Returns:
        Restored context
    """
    ...

Example usage:

resume_middleware = ResumeMiddleware(
    checkpoint_store=CheckpointStore(event_log)
)

context = resume_middleware.process(context)
# context.strictness, context.scope_refs restored from checkpoint
# Skip to cursor: "pre_generation_gate"

Pipeline Composition

Middleware components are composed into a pipeline:

from typing import List

class GlossaryMiddlewarePipeline:
    """Compose glossary middleware components into a pipeline."""

    def __init__(self, middleware: List[GlossaryMiddleware]):
        """
        Initialize pipeline.

        Args:
            middleware: Ordered list of middleware components
        """
        self.middleware = middleware

    def process(self, context: PrimitiveExecutionContext) -> PrimitiveExecutionContext:
        """
        Execute middleware pipeline.

        Args:
            context: Initial execution context

        Returns:
            Final context (after all middleware)

        Raises:
            BlockedByConflict: If generation gate blocks
            DeferredToAsync: If clarification deferred
        """
        for mw in self.middleware:
            context = mw.process(context)
        return context

Example pipeline setup:

pipeline = GlossaryMiddlewarePipeline([
    GlossaryCandidateExtractionMiddleware(extraction_config),
    SemanticCheckMiddleware(glossary_store),
    GenerationGateMiddleware(strictness_policy),
    ClarificationMiddleware(interaction_mode),
    ResumeMiddleware(checkpoint_store)
])

try:
    final_context = pipeline.process(initial_context)
    # All checks passed - proceed to generation
except BlockedByConflict:
    # Generation blocked - clarification required
except DeferredToAsync:
    # User deferred resolution - exit

Exception Hierarchy

class GlossaryError(Exception):
    """Base exception for glossary errors."""

class BlockedByConflict(GlossaryError):
    """Generation blocked by unresolved high-severity conflict."""
    def __init__(self, conflicts: List[SemanticConflict]):
        self.conflicts = conflicts
        super().__init__(f"Generation blocked by {len(conflicts)} conflict(s)")

class DeferredToAsync(GlossaryError):
    """User deferred conflict resolution to async mode."""
    def __init__(self, conflict_id: str):
        self.conflict_id = conflict_id
        super().__init__(f"Conflict {conflict_id} deferred to async resolution")

class AbortResume(GlossaryError):
    """User aborted resume (context changed)."""
    def __init__(self, reason: str):
        self.reason = reason
        super().__init__(f"Resume aborted: {reason}")

Testing Middleware

Unit tests (per middleware component):

  • Mock inputs, assert outputs
  • Verify events emitted at correct stage
  • Test exception handling (BlockedByConflict, DeferredToAsync)

Integration tests (full pipeline):

  • Run extraction → check → gate → clarification → resume
  • Verify event order: TermCandidateObserved → SemanticCheckEvaluated → GenerationBlockedBySemanticConflict → GlossaryClarificationResolved
  • Test edge cases (no conflicts, all conflicts resolved, defer to async)

Example integration test:

def test_full_pipeline_with_conflict_resolution():
    # Setup
    context = PrimitiveExecutionContext(inputs={"description": "The workspace contains files"})
    pipeline = create_pipeline()

    # Execute
    try:
        final_context = pipeline.process(context)
        assert False, "Should have blocked"
    except BlockedByConflict:
        # Expected - clarification required
        pass

    # User resolves conflict
    resolve_conflict(conflict_id="uuid-1234", choice="candidate:1")

    # Retry with resume
    context.retry_token = "uuid-1234"
    final_context = pipeline.process(context)

    # Assert
    assert final_context.glossary_updated
    assert len(final_context.conflicts) == 0

See Also

  • events.md - Event contract schemas
  • ../data-model.md - Entity definitions
  • ../research.md - Architectural research decisions