Skip to content

5. spot-sdk Shared Package

Date: 2025-11-04
Status: Accepted
Deciders: Core Team
Related: ADR-001 (Microservices), ADR-003 (Pydantic), ADR-004 (Separate Repos)

Context

With separate repositories for each analyzer, we need a way to:

  • Share interface definitions across all services
  • Define common data models (Email, AnalysisResult, etc.)
  • Ensure analyzers implement compatible interfaces
  • Version the contract independently from implementations
  • Support multiple programming languages (Python, TypeScript)
  • Make it easy for external developers to create custom analyzers

Requirements:

  • Single source of truth for all interfaces
  • Semantic versioning for compatibility tracking
  • Language-agnostic contract definitions
  • Easy to consume from analyzer projects
  • Support for both internal and external developers

Decision

Create spot-sdk as a separate repository and package containing:

  • Python SDK (spot-sdk-python) with Pydantic models
  • TypeScript SDK (spot-sdk-typescript) with type definitions
  • OpenAPI specifications for HTTP endpoints
  • Interface definitions (AnalyzerInterface ABC)
  • Common enums (ThreatLevel, IndicatorType)

Published to GitLab package registry for internal use.

Rationale

  1. Single Source of Truth: All interfaces defined in one place
  2. Versioning: Semantic versioning allows tracking compatibility
  3. Language Support: Generate SDKs for multiple languages
  4. External Friendly: Third parties can create analyzers
  5. Type Safety: Strong contracts prevent integration issues
  6. Documentation: Interfaces serve as API documentation
  7. Tooling: OpenAPI specs enable code generation

Consequences

Positive

  • Clear contracts prevent integration bugs
  • Analyzers can be developed independently
  • Version incompatibility detected early
  • External developers can create analyzers
  • Automatic API documentation from schemas
  • Type checking in IDE prevents errors
  • Can generate clients in any language

Negative

  • Need to maintain separate package
  • Breaking changes require coordination
  • Version management overhead
  • Publish/consume workflow complexity
  • Need to version and release contracts separately

Alternatives Considered

Alternative 1: Copy contracts to each repo

  • Pros:
  • No external dependency
  • Each repo self-contained
  • Simple to understand
  • Cons:
  • Contracts drift over time
  • Manual synchronization needed
  • No single source of truth
  • Version tracking impossible
  • Changes require updates in multiple places
  • Why rejected: Too error-prone, defeats purpose of contracts

Alternative 2: Platform repo as dependency

  • Pros:
  • No additional repository
  • Contracts live with main code
  • Cons:
  • Analyzers depend on entire platform
  • Cannot version contracts independently
  • External developers need access to platform repo
  • Circular dependency issues
  • Tighter coupling than desired
  • Why rejected: Creates unwanted coupling between platform and analyzers

Alternative 3: Protocol Buffers

  • Pros:
  • Language-agnostic binary format
  • Efficient serialization
  • Built-in versioning
  • Industry standard
  • Cons:
  • Less human-readable
  • More complex toolchain
  • Overkill for HTTP/JSON APIs
  • Less Python-idiomatic
  • Steeper learning curve
  • Why rejected: JSON/Pydantic simpler for our HTTP-based architecture

Implementation Notes

Package structure:

spot-sdk/
├── interfaces/           # Python interfaces and models
│   ├── analyzer.py      # AnalyzerInterface ABC
│   ├── email.py         # Email data models
│   ├── results.py       # AnalysisResult models
│   ├── orchestrator.py  # Orchestration models
│   └── workflow.py      # Workflow models
├── sdk/
│   ├── python/          # Python SDK (Pydantic models)
│   └── typescript/      # TypeScript SDK (generated)
├── openapi/             # OpenAPI specifications
├── docs/
│   └── ANALYZER_DEVELOPMENT.md
└── pyproject.toml       # Package metadata

Consumption in analyzers:

[tool.poetry.dependencies]
python = "^3.11"
spot-sdk-python = "^2.0.0"

Usage example:

from spot_sdk import Email, AnalysisResult, AnalyzerInterface

class MyAnalyzer(AnalyzerInterface):
    async def analyze(self, email: Email) -> AnalysisResult:
        # Implementation
        pass

Versioning:

  • MAJOR: Breaking changes to interfaces
  • MINOR: Backward-compatible additions
  • PATCH: Bug fixes, documentation

References