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
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
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)
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/
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
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
...
@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] }
@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
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
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
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
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.
Automatic code fixing
Code rewriting
Business logic inside CLI
Provider-specific policy logic
Caching for non-LLM providers