ai-slop-gate โ€” Canonical Architecture๏ƒ

Single source of truth: ai_slop_gate_snapshot.json (v7.4.0) If this document diverges from the snapshot, the snapshot wins.

Stage: 6 (current) | Status: canonical


Architectural Principles๏ƒ

Principle

Meaning

No hidden logic

Every decision is traceable to policy

Policy is source of truth

policy.yml drives all enforcement

Compliance is a sidecar

Runs alongside analysis, never inside it

Engine is pure

No IO, no printing, no exit calls

CLI is thin

Argument parsing and wiring only

Tests lock contracts, not behavior

Critical invariants, not implementation

Cache is cost control, not performance

LLM only, prevents duplicate token spend


Execution Flow๏ƒ

CLI parses flags
  โ†’ Policy loaded and resolved
  โ†’ Providers collect or analyze inputs
      โ†’ LLM providers optionally wrapped by CachedProvider
  โ†’ Compliance sidecar optionally runs
  โ†’ Policy engine evaluates all observations
  โ†’ Decision produced (allow | advisory | blocking)
  โ†’ Reporters render output
  โ†’ Exit code derived from decision (0 | 1)

Directory Structure๏ƒ

ai_slop_gate/
โ”œโ”€โ”€ cli/                    # CLI entry points and subcommands (thin layer)
โ”‚   โ”œโ”€โ”€ main.py             # Entrypoint: python -m ai_slop_gate.cli.main
โ”‚   โ”œโ”€โ”€ run.py              # run command logic: run_analysis()
โ”‚   โ”œโ”€โ”€ args.py             # Argument parsing
โ”‚   โ”œโ”€โ”€ context.py          # Runtime context
โ”‚   โ”œโ”€โ”€ logger.py           # Logging setup
โ”‚   โ””โ”€โ”€ utils.py            # CLI utilities
โ”œโ”€โ”€ engine/
โ”‚   โ””โ”€โ”€ provider_factory.py # Instantiates providers from registry
โ”œโ”€โ”€ domain/
โ”‚   โ”œโ”€โ”€ observation.py      # Observation dataclass (immutable)
โ”‚   โ”œโ”€โ”€ decision.py         # Decision dataclass (allow|advisory|blocking)
โ”‚   โ”œโ”€โ”€ policy.py           # PolicyRule dataclass
โ”‚   โ”œโ”€โ”€ policy_engine.py    # Evaluates observations โ†’ Decision
โ”‚   โ”œโ”€โ”€ checks.py           # CheckReport
โ”‚   โ”œโ”€โ”€ check_mapper.py     # Maps checks to observations
โ”‚   โ”œโ”€โ”€ signals.py          # Signal definitions
โ”‚   โ”œโ”€โ”€ contracts.py        # Policy evaluation contracts
โ”‚   โ”œโ”€โ”€ observation_factory.py
โ”‚   โ”œโ”€โ”€ observation_result.py
โ”‚   โ””โ”€โ”€ compliance/         # Compliance sidecar
โ”‚       โ”œโ”€โ”€ detector.py
โ”‚       โ”œโ”€โ”€ enforcement.py
โ”‚       โ”œโ”€โ”€ gateway.py
โ”‚       โ”œโ”€โ”€ pipeline.py
โ”‚       โ”œโ”€โ”€ profile_resolver.py
โ”‚       โ”œโ”€โ”€ profiles.py
โ”‚       โ””โ”€โ”€ rules.py
โ”œโ”€โ”€ providers/
โ”‚   โ”œโ”€โ”€ base.py             # BaseProvider ABC + ProviderObservation
โ”‚   โ”œโ”€โ”€ registry.py         # Provider registry
โ”‚   โ”œโ”€โ”€ cached_provider.py  # CachedProvider wrapper (LLM only)
โ”‚   โ”œโ”€โ”€ rate_limit_guard.py
โ”‚   โ”œโ”€โ”€ llm/
โ”‚   โ”‚   โ”œโ”€โ”€ llm_provider.py # LlmProvider base (chunked file scanning)
โ”‚   โ”‚   โ”œโ”€โ”€ gemini.py       # GeminiProvider
โ”‚   โ”‚   โ”œโ”€โ”€ groq.py         # GroqProvider
โ”‚   โ”‚   โ”œโ”€โ”€ ollama.py       # OllamaProvider (local, no API key)
โ”‚   โ”‚   โ””โ”€โ”€ prompts/
โ”‚   โ”‚       โ”œโ”€โ”€ gemini/deep.prompt
โ”‚   โ”‚       โ”œโ”€โ”€ groq/deep.prompt
โ”‚   โ”‚       โ”œโ”€โ”€ groq/fast.prompt
โ”‚   โ”‚       โ”œโ”€โ”€ ollama/qwen.prompt
โ”‚   โ”‚       โ””โ”€โ”€ ollama/mistral.prompt
โ”‚   โ””โ”€โ”€ static/
โ”‚       โ”œโ”€โ”€ static.py               # StaticProvider (general)
โ”‚       โ”œโ”€โ”€ static_security.py      # StaticSecurityProvider
โ”‚       โ”œโ”€โ”€ static_pipeline.py      # StaticPipelineProvider
โ”‚       โ”œโ”€โ”€ static_python.py        # StaticPythonProvider
โ”‚       โ”œโ”€โ”€ static_js.py            # StaticJSProvider
โ”‚       โ”œโ”€โ”€ static_ts_js.py         # StaticTSJSProvider
โ”‚       โ”œโ”€โ”€ static_docker.py        # StaticDockerProvider
โ”‚       โ”œโ”€โ”€ cpp_static.py           # StaticCppProvider
โ”‚       โ”œโ”€โ”€ csharp_static.py        # StaticCSharpProvider
โ”‚       โ”œโ”€โ”€ java_static.py          # StaticJavaProvider
โ”‚       โ”œโ”€โ”€ ruby_static.py          # StaticRubyProvider
โ”‚       โ”œโ”€โ”€ eslint.py               # ESLintProvider (JS/TS rules)
โ”‚       โ”œโ”€โ”€ k8s_static.py           # KubernetesStaticProvider
โ”‚       โ”œโ”€โ”€ k8s_runtime.py          # K8sRuntimeProvider (kind: infra)
โ”‚       โ”œโ”€โ”€ terraform_static.py     # TerraformStaticProvider
โ”‚       โ”œโ”€โ”€ terraform_plan.py       # TerraformPlanProvider
โ”‚       โ”œโ”€โ”€ supply_chain.py         # SupplyChainProvider
โ”‚       โ”œโ”€โ”€ trivy.py                # TrivyProvider (CVE scanning)
โ”‚       โ”œโ”€โ”€ sbom.py                 # SBOMProvider (Syft)
โ”‚       โ””โ”€โ”€ dead_code.py            # DeadCodeProvider
โ”œโ”€โ”€ reporters/
โ”‚   โ”œโ”€โ”€ base.py             # Reporter ABC
โ”‚   โ”œโ”€โ”€ console.py          # ConsoleReporter (stdout, human-readable)
โ”‚   โ”œโ”€โ”€ github_pr.py        # GitHubPRReporter (PR comments)
โ”‚   โ”œโ”€โ”€ github_checks.py    # GitHubChecksReporter (check-run annotations)
โ”‚   โ”œโ”€โ”€ gitlab_mr.py        # GitLabMRReporter (MR comments)
โ”‚   โ””โ”€โ”€ formatter.py        # PR comment formatter
โ”œโ”€โ”€ github/
โ”‚   โ””โ”€โ”€ pr_commenter.py     # GitHub PR comment logic
โ”œโ”€โ”€ cache/                  # LLM response cache
โ”œโ”€โ”€ rulesets/
โ”‚   โ””โ”€โ”€ eslint/             # ESLint rules for JS/TS
โ”‚       โ”œโ”€โ”€ base.mjs
โ”‚       โ”œโ”€โ”€ prod_safety.mjs
โ”‚       โ””โ”€โ”€ secrets.mjs
โ””โ”€โ”€ tests/
    โ”œโ”€โ”€ unit/
    โ””โ”€โ”€ integration/

Core Contracts๏ƒ

ProviderObservation๏ƒ

Every provider must return this. It is immutable (frozen=True):

@dataclass(frozen=True)
class ProviderObservation:
    provider: str
    model: str
    observations: List[Any]   # list of Observation objects
    raw_text: str

BaseProvider๏ƒ

class BaseProvider(ABC):
    name: str
    kind: str  # "llm" | "static" | "infra"

    @abstractmethod
    def analyze(self, code: str, input_file: str = "") -> ProviderObservation:
        # LLM: analyze PR diff or code snippet
        ...

    @abstractmethod
    def collect(self, base_path: str = ".") -> ProviderObservation:
        # Static/infra: scan a directory
        # LLM: delegates to LlmProvider.analyze_files() (chunked scan)
        ...

    def analyze_pr(self, repo: str, pr_id: int, token: str) -> ProviderObservation:
        # Optional: direct GitHub PR analysis
        # Default raises NotImplementedError
        ...

Observation๏ƒ

@dataclass(frozen=True)
class Observation:
    category: str               # security | quality | architecture | ...
    signal: str                 # snake_case identifier
    confidence: float           # 0.0 โ€“ 1.0
    message: str
    severity: Optional[Severity]
    evidence: Optional[Dict[str, Any]]
    rule_id: Optional[str]
    location: Optional[Location]  # { file: str, line: Optional[int] }

Decision๏ƒ

@dataclass(frozen=True)
class Decision:
    mode: DecisionMode          # allow | advisory | blocking
    reasons: List[str]
    annotations: Optional[List[Annotation]]

Exit code mapping:

mode

exit code

allow

0

advisory

0

blocking

1


Provider Inventory๏ƒ

LLM Providers (kind = "llm")๏ƒ

Class

name

Prompt files

analyze_pr

GeminiProvider

gemini

gemini/deep.prompt

โœ…

GroqProvider

groq

groq/deep.prompt, groq/fast.prompt

โœ…

OllamaProvider

ollama

ollama/qwen.prompt, ollama/mistral.prompt

โŒ

LLM providers support both analyze() (diff/snippet) and collect() (full repo via chunked scanning in LlmProvider.analyze_files()).

Static Providers (kind = "static")๏ƒ

Class

name

Language/Target

StaticProvider

static

General

StaticSecurityProvider

static_security

Security patterns

StaticPipelineProvider

static_pipeline

CI/CD pipelines

StaticPythonProvider

static_python

Python AST

StaticJSProvider

static_js

JavaScript

StaticTSJSProvider

static_ts_js

TypeScript/JavaScript

StaticDockerProvider

static_docker

Dockerfile

StaticCppProvider

cpp_static

C++

StaticCSharpProvider

csharp_static

C#

StaticJavaProvider

java_static

Java

StaticRubyProvider

ruby_static

Ruby

ESLintProvider

eslint

JS/TS (ESLint rules)

KubernetesStaticProvider

k8s_static

Kubernetes manifests

TerraformStaticProvider

terraform_static

Terraform HCL

TerraformPlanProvider

terraform_plan

Terraform plan JSON

SupplyChainProvider

supply_chain

Dependency risk

TrivyProvider

trivy

CVE scanning

SBOMProvider

sbom

SBOM generation (Syft)

DeadCodeProvider

dead_code

Unused code detection

Infra Providers (kind = "infra")๏ƒ

Class

name

Target

K8sRuntimeProvider

k8s_runtime

Kubernetes runtime


Cache๏ƒ

  • Purpose: prevent repeated LLM token spending (not a performance cache)

  • Scope: LLM providers only โ€” never caches static providers

  • Integration: via CachedProvider wrapper

  • Default location: .ai-slop-cache/

  • Cache key components: provider_id + model + profile + policy_hash + normalized_input_fingerprint

  • Invariant: same key must NEVER trigger an LLM call twice


Compliance Sidecar๏ƒ

  • Role: runs alongside analysis, never inside engine

  • Enabled by: policy.compliance.enabled or --compliance / --compliance-only flag

  • Capabilities: forbidden license detection (GPL, AGPL), secret detection, GDPR/DSGVO data residency enforcement

Profile

Description

default

inherits base compliance config

eu

enforces GPL/AGPL ban + EU residency

eu-strict

blocking enforcement


Reporters๏ƒ

Class

Output

Description

ConsoleReporter

stdout

Human-readable, short or verbose mode

GitHubPRReporter

GitHub PR

Posts analysis as PR comment

GitHubChecksReporter

GitHub Checks

Creates check-run with annotations

GitLabMRReporter

GitLab MR

Posts analysis as MR comment

All reporters receive CheckReport and are provider-agnostic.


Non-Goals๏ƒ

  • Automatic code fixing

  • Code rewriting

  • Business logic inside CLI

  • Provider-specific policy logic

  • Caching for non-LLM providers