AI Content Pipeline
Dockerized monorepo foundation is running.
+
+ {initialArticles.articles.length} articles in the contract-backed dashboard.
+
);
}
diff --git a/apps/runner/Dockerfile b/apps/runner/Dockerfile
index e0665b2..f1318fd 100644
--- a/apps/runner/Dockerfile
+++ b/apps/runner/Dockerfile
@@ -5,6 +5,10 @@ ENV PYTHONUNBUFFERED=1
WORKDIR /app
+COPY apps/runner/requirements.txt ./requirements.txt
+RUN pip install --no-cache-dir -r requirements.txt
+
+COPY apps/backend/src/domain/contracts ./backend_contracts/contracts
COPY apps/runner/src ./src
EXPOSE 8010
diff --git a/apps/runner/requirements.txt b/apps/runner/requirements.txt
index e69de29..4b5fe4d 100644
--- a/apps/runner/requirements.txt
+++ b/apps/runner/requirements.txt
@@ -0,0 +1 @@
+pydantic==2.13.4
diff --git a/apps/runner/src/application/job_output_validation.py b/apps/runner/src/application/job_output_validation.py
new file mode 100644
index 0000000..075323c
--- /dev/null
+++ b/apps/runner/src/application/job_output_validation.py
@@ -0,0 +1,63 @@
+from __future__ import annotations
+
+import importlib.util
+import os
+import sys
+from functools import lru_cache
+from pathlib import Path
+from typing import Any, Mapping
+
+
+@lru_cache(maxsize=1)
+def _backend_contracts_module() -> Any:
+ contracts_dir = _backend_contracts_dir()
+ init_file = contracts_dir / "__init__.py"
+ module_name = "_pipeline_backend_domain_contracts"
+
+ existing = sys.modules.get(module_name)
+ if existing is not None and Path(str(existing.__file__)).resolve() == init_file.resolve():
+ return existing
+ sys.modules.pop(module_name, None)
+
+ spec = importlib.util.spec_from_file_location(
+ module_name,
+ init_file,
+ submodule_search_locations=[str(contracts_dir)],
+ )
+ if spec is None or spec.loader is None:
+ raise RuntimeError(f"Cannot load backend contract module from {init_file}")
+
+ module = importlib.util.module_from_spec(spec)
+ sys.modules[module_name] = module
+ spec.loader.exec_module(module)
+ return module
+
+
+def _backend_contracts_dir() -> Path:
+ for candidate in _backend_contract_candidates():
+ init_file = candidate / "__init__.py"
+ if init_file.is_file():
+ return candidate
+
+ candidates = ", ".join(str(path) for path in _backend_contract_candidates())
+ raise RuntimeError(f"Cannot find backend contract package. Checked: {candidates}")
+
+
+def _backend_contract_candidates() -> tuple[Path, ...]:
+ configured = os.environ.get("PIPELINE_BACKEND_CONTRACTS_DIR")
+ candidates: list[Path] = []
+ if configured:
+ candidates.append(Path(configured))
+
+ current_file = Path(__file__).resolve()
+ candidates.append(Path("/app/backend_contracts/contracts"))
+
+ for parent in current_file.parents:
+ candidates.append(parent / "apps" / "backend" / "src" / "domain" / "contracts")
+
+ return tuple(dict.fromkeys(candidates))
+
+
+def validate_agent_job_output(payload: Mapping[str, Any]) -> Any:
+ contracts = _backend_contracts_module()
+ return contracts.AgentJobOutput.model_validate(payload)
diff --git a/apps/runner/tests/test_job_output_validation.py b/apps/runner/tests/test_job_output_validation.py
new file mode 100644
index 0000000..832fdf2
--- /dev/null
+++ b/apps/runner/tests/test_job_output_validation.py
@@ -0,0 +1,62 @@
+from __future__ import annotations
+
+import sys
+import os
+import shutil
+import tempfile
+import unittest
+from pathlib import Path
+
+from pydantic import ValidationError
+
+
+REPO_ROOT = Path(__file__).resolve().parents[3]
+RUNNER_ROOT = REPO_ROOT / "apps" / "runner"
+sys.path.insert(0, str(RUNNER_ROOT))
+
+from src.application.job_output_validation import ( # noqa: E402
+ _backend_contracts_module,
+ validate_agent_job_output,
+)
+
+
+class RunnerJobOutputValidationTest(unittest.TestCase):
+ def test_valid_runner_job_output_uses_backend_contracts(self) -> None:
+ output = validate_agent_job_output(
+ {
+ "status": "SUCCEEDED",
+ "output_files": [{"path": "outputs/plan.json"}],
+ "payload": {"artifact": "plan"},
+ }
+ )
+
+ self.assertEqual("SUCCEEDED", output.status.value)
+
+ def test_invalid_runner_job_output_status_is_rejected(self) -> None:
+ with self.assertRaises(ValidationError):
+ validate_agent_job_output({"status": "NOT_A_STATUS"})
+
+ def test_runner_image_layout_can_use_copied_backend_contracts(self) -> None:
+ source_contracts = REPO_ROOT / "apps" / "backend" / "src" / "domain" / "contracts"
+
+ with tempfile.TemporaryDirectory() as temp_dir:
+ copied_contracts = Path(temp_dir) / "backend_contracts" / "contracts"
+ shutil.copytree(source_contracts, copied_contracts)
+
+ previous = os.environ.get("PIPELINE_BACKEND_CONTRACTS_DIR")
+ os.environ["PIPELINE_BACKEND_CONTRACTS_DIR"] = str(copied_contracts)
+ _backend_contracts_module.cache_clear()
+ try:
+ output = validate_agent_job_output({"status": "SUCCEEDED"})
+ finally:
+ _backend_contracts_module.cache_clear()
+ if previous is None:
+ os.environ.pop("PIPELINE_BACKEND_CONTRACTS_DIR", None)
+ else:
+ os.environ["PIPELINE_BACKEND_CONTRACTS_DIR"] = previous
+
+ self.assertEqual("SUCCEEDED", output.status.value)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/package.json b/package.json
index a046e2b..bbef9f7 100644
--- a/package.json
+++ b/package.json
@@ -1,9 +1,12 @@
{
"name": "ai-content-pipeline",
"private": true,
+ "packageManager": "pnpm@10.27.0",
"scripts": {
"dev:stack": "docker compose up --build",
- "smoke": "bash tests/smoke/public-health.sh"
+ "generate:contracts": "python3 scripts/generate_openapi_contracts.py",
+ "smoke": "bash tests/smoke/public-health.sh",
+ "typecheck": "pnpm --filter @pipeline/shared typecheck && pnpm --filter @pipeline/frontend typecheck"
},
"workspaces": [
"apps/frontend",
diff --git a/packages/shared/openapi.json b/packages/shared/openapi.json
new file mode 100644
index 0000000..7a7cbe0
--- /dev/null
+++ b/packages/shared/openapi.json
@@ -0,0 +1,1708 @@
+{
+ "components": {
+ "schemas": {
+ "AgentJobErrorCategory": {
+ "enum": [
+ "FAILED_SCHEMA_VALIDATION",
+ "CLI_EXIT_CODE_FAILURE",
+ "TIMEOUT",
+ "MISSING_OUTPUT_FILE",
+ "UNSUPPORTED_CLAIMS_FOUND",
+ "SOURCE_RETRIEVAL_FAILED",
+ "RESEARCH_ARTIFACT_UPLOAD_FAILED",
+ "PUBLISH_DRY_RUN_FAILED",
+ "GIT_CHECKOUT_FAILED",
+ "GIT_COMMIT_FAILED",
+ "GIT_PUSH_NON_FAST_FORWARD",
+ "PUBLISH_VERIFICATION_FAILED"
+ ],
+ "title": "AgentJobErrorCategory",
+ "type": "string"
+ },
+ "AgentJobOutput": {
+ "additionalProperties": false,
+ "properties": {
+ "artifacts": {
+ "items": {
+ "$ref": "#/components/schemas/ResearchArtifactSummary"
+ },
+ "title": "Artifacts",
+ "type": "array"
+ },
+ "error_category": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/AgentJobErrorCategory"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null
+ },
+ "error_message": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Error Message"
+ },
+ "output_files": {
+ "items": {
+ "$ref": "#/components/schemas/RunnerFileRef"
+ },
+ "title": "Output Files",
+ "type": "array"
+ },
+ "payload": {
+ "additionalProperties": true,
+ "title": "Payload",
+ "type": "object"
+ },
+ "status": {
+ "$ref": "#/components/schemas/AgentJobStatus"
+ }
+ },
+ "required": [
+ "status"
+ ],
+ "title": "AgentJobOutput",
+ "type": "object"
+ },
+ "AgentJobStatus": {
+ "enum": [
+ "QUEUED",
+ "RUNNING",
+ "SUCCEEDED",
+ "FAILED",
+ "CANCELLED"
+ ],
+ "title": "AgentJobStatus",
+ "type": "string"
+ },
+ "AgentJobSummary": {
+ "additionalProperties": false,
+ "properties": {
+ "agent_profile": {
+ "minLength": 1,
+ "title": "Agent Profile",
+ "type": "string"
+ },
+ "article_id": {
+ "format": "uuid",
+ "title": "Article Id",
+ "type": "string"
+ },
+ "error_category": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/AgentJobErrorCategory"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null
+ },
+ "error_message": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Error Message"
+ },
+ "finished_at": {
+ "anyOf": [
+ {
+ "format": "date-time",
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Finished At"
+ },
+ "id": {
+ "format": "uuid",
+ "title": "Id",
+ "type": "string"
+ },
+ "input_files": {
+ "items": {
+ "$ref": "#/components/schemas/RunnerFileRef"
+ },
+ "title": "Input Files",
+ "type": "array"
+ },
+ "job_type": {
+ "$ref": "#/components/schemas/AgentJobType"
+ },
+ "output_files": {
+ "items": {
+ "$ref": "#/components/schemas/RunnerFileRef"
+ },
+ "title": "Output Files",
+ "type": "array"
+ },
+ "started_at": {
+ "anyOf": [
+ {
+ "format": "date-time",
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Started At"
+ },
+ "status": {
+ "$ref": "#/components/schemas/AgentJobStatus"
+ },
+ "workspace_path": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Workspace Path"
+ }
+ },
+ "required": [
+ "id",
+ "article_id",
+ "job_type",
+ "agent_profile",
+ "status"
+ ],
+ "title": "AgentJobSummary",
+ "type": "object"
+ },
+ "AgentJobType": {
+ "enum": [
+ "BOUNDARY_QUESTIONS",
+ "PLAN_GENERATION",
+ "RESEARCH",
+ "EVIDENCE_MATRIX",
+ "SECTION_SCAFFOLD",
+ "DRAFT_ASSEMBLY",
+ "SEO_REVIEW",
+ "LANGUAGE_REVIEW",
+ "PUBLISH_DRY_RUN",
+ "PUBLISH_COMMIT",
+ "TEST_CODEX"
+ ],
+ "title": "AgentJobType",
+ "type": "string"
+ },
+ "ArticleCreateRequest": {
+ "additionalProperties": false,
+ "properties": {
+ "assigned_editor_id": {
+ "anyOf": [
+ {
+ "format": "uuid",
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Assigned Editor Id"
+ },
+ "brief_description": {
+ "minLength": 1,
+ "title": "Brief Description",
+ "type": "string"
+ },
+ "content_type": {
+ "default": "longform_guide",
+ "minLength": 1,
+ "title": "Content Type",
+ "type": "string"
+ },
+ "language": {
+ "default": "en",
+ "minLength": 2,
+ "title": "Language",
+ "type": "string"
+ },
+ "primary_keyword": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Primary Keyword"
+ },
+ "target_site_id": {
+ "format": "uuid",
+ "title": "Target Site Id",
+ "type": "string"
+ },
+ "working_title": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Working Title"
+ }
+ },
+ "required": [
+ "target_site_id",
+ "brief_description"
+ ],
+ "title": "ArticleCreateRequest",
+ "type": "object"
+ },
+ "ArticleCreateResponse": {
+ "additionalProperties": false,
+ "properties": {
+ "article": {
+ "$ref": "#/components/schemas/ArticleSummary"
+ }
+ },
+ "required": [
+ "article"
+ ],
+ "title": "ArticleCreateResponse",
+ "type": "object"
+ },
+ "ArticleDetailResponse": {
+ "additionalProperties": false,
+ "properties": {
+ "agent_jobs": {
+ "items": {
+ "$ref": "#/components/schemas/AgentJobSummary"
+ },
+ "title": "Agent Jobs",
+ "type": "array"
+ },
+ "article": {
+ "$ref": "#/components/schemas/ArticleSummary"
+ },
+ "assets": {
+ "items": {
+ "$ref": "#/components/schemas/AssetSummary"
+ },
+ "title": "Assets",
+ "type": "array"
+ },
+ "claims": {
+ "items": {
+ "$ref": "#/components/schemas/ClaimSummary"
+ },
+ "title": "Claims",
+ "type": "array"
+ },
+ "draft": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/DraftSummary"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null
+ },
+ "evidence": {
+ "items": {
+ "$ref": "#/components/schemas/EvidenceSummary"
+ },
+ "title": "Evidence",
+ "type": "array"
+ },
+ "plan": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/PlanSummary"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null
+ },
+ "publish_commit": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/PublishCommitSummary"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null
+ },
+ "research_manifests": {
+ "items": {
+ "$ref": "#/components/schemas/ResearchArtifactManifestSummary"
+ },
+ "title": "Research Manifests",
+ "type": "array"
+ },
+ "reviews": {
+ "items": {
+ "$ref": "#/components/schemas/ReviewSummary"
+ },
+ "title": "Reviews",
+ "type": "array"
+ },
+ "target_site": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/TargetSiteConfig"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null
+ }
+ },
+ "required": [
+ "article"
+ ],
+ "title": "ArticleDetailResponse",
+ "type": "object"
+ },
+ "ArticleListResponse": {
+ "additionalProperties": false,
+ "properties": {
+ "articles": {
+ "items": {
+ "$ref": "#/components/schemas/ArticleSummary"
+ },
+ "title": "Articles",
+ "type": "array"
+ }
+ },
+ "required": [
+ "articles"
+ ],
+ "title": "ArticleListResponse",
+ "type": "object"
+ },
+ "ArticleSummary": {
+ "additionalProperties": false,
+ "properties": {
+ "assigned_editor_id": {
+ "anyOf": [
+ {
+ "format": "uuid",
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Assigned Editor Id"
+ },
+ "brief_description": {
+ "title": "Brief Description",
+ "type": "string"
+ },
+ "content_type": {
+ "title": "Content Type",
+ "type": "string"
+ },
+ "created_at": {
+ "format": "date-time",
+ "title": "Created At",
+ "type": "string"
+ },
+ "id": {
+ "format": "uuid",
+ "title": "Id",
+ "type": "string"
+ },
+ "language": {
+ "title": "Language",
+ "type": "string"
+ },
+ "primary_keyword": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Primary Keyword"
+ },
+ "publishing_status": {
+ "$ref": "#/components/schemas/PublishingStatus"
+ },
+ "status": {
+ "$ref": "#/components/schemas/ArticleWorkflowStatus"
+ },
+ "target_site_id": {
+ "format": "uuid",
+ "title": "Target Site Id",
+ "type": "string"
+ },
+ "updated_at": {
+ "format": "date-time",
+ "title": "Updated At",
+ "type": "string"
+ },
+ "working_title": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Working Title"
+ }
+ },
+ "required": [
+ "id",
+ "target_site_id",
+ "status",
+ "publishing_status",
+ "brief_description",
+ "language",
+ "content_type",
+ "created_at",
+ "updated_at"
+ ],
+ "title": "ArticleSummary",
+ "type": "object"
+ },
+ "ArticleWorkflowStatus": {
+ "enum": [
+ "ARTICLE_BRIEF_CREATED",
+ "BOUNDARY_QUESTIONS_GENERATED",
+ "BOUNDARY_ANSWERS_SUBMITTED",
+ "PLAN_GENERATED",
+ "PLAN_REVIEW_REQUIRED",
+ "PLAN_REVISION_REQUIRED",
+ "RESEARCH_RUNNING",
+ "EVIDENCE_MATRIX_READY",
+ "PARALLEL_PRODUCTION_RUNNING",
+ "DRAFT_ASSEMBLED",
+ "SEO_AND_LANGUAGE_REVIEW_READY",
+ "FINAL_REVIEW_REQUIRED",
+ "FINAL_REVISION_REQUIRED",
+ "PUBLISH_DRY_RUN_REQUIRED",
+ "PUBLISH_COMMIT_READY",
+ "PUBLISH_COMMIT_CREATED"
+ ],
+ "title": "ArticleWorkflowStatus",
+ "type": "string"
+ },
+ "AssetStatus": {
+ "enum": [
+ "PENDING",
+ "GENERATED",
+ "APPROVED",
+ "REJECTED"
+ ],
+ "title": "AssetStatus",
+ "type": "string"
+ },
+ "AssetSummary": {
+ "additionalProperties": false,
+ "properties": {
+ "alt_text": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Alt Text"
+ },
+ "article_id": {
+ "format": "uuid",
+ "title": "Article Id",
+ "type": "string"
+ },
+ "asset_type": {
+ "$ref": "#/components/schemas/AssetType"
+ },
+ "caption": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Caption"
+ },
+ "file_url": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "File Url"
+ },
+ "id": {
+ "format": "uuid",
+ "title": "Id",
+ "type": "string"
+ },
+ "prompt": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Prompt"
+ },
+ "status": {
+ "$ref": "#/components/schemas/AssetStatus"
+ },
+ "title": {
+ "minLength": 1,
+ "title": "Title",
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "article_id",
+ "asset_type",
+ "title",
+ "status"
+ ],
+ "title": "AssetSummary",
+ "type": "object"
+ },
+ "AssetType": {
+ "enum": [
+ "hero_image",
+ "diagram",
+ "table",
+ "inline_image"
+ ],
+ "title": "AssetType",
+ "type": "string"
+ },
+ "ClaimRiskLevel": {
+ "enum": [
+ "low",
+ "medium",
+ "high"
+ ],
+ "title": "ClaimRiskLevel",
+ "type": "string"
+ },
+ "ClaimSummary": {
+ "additionalProperties": false,
+ "properties": {
+ "article_id": {
+ "format": "uuid",
+ "title": "Article Id",
+ "type": "string"
+ },
+ "claim_text": {
+ "minLength": 1,
+ "title": "Claim Text",
+ "type": "string"
+ },
+ "evidence_item_ids": {
+ "items": {
+ "format": "uuid",
+ "type": "string"
+ },
+ "title": "Evidence Item Ids",
+ "type": "array"
+ },
+ "id": {
+ "format": "uuid",
+ "title": "Id",
+ "type": "string"
+ },
+ "risk_level": {
+ "$ref": "#/components/schemas/ClaimRiskLevel"
+ },
+ "section_id": {
+ "anyOf": [
+ {
+ "format": "uuid",
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Section Id"
+ },
+ "support_status": {
+ "$ref": "#/components/schemas/ClaimSupportStatus"
+ }
+ },
+ "required": [
+ "id",
+ "article_id",
+ "claim_text",
+ "support_status",
+ "risk_level"
+ ],
+ "title": "ClaimSummary",
+ "type": "object"
+ },
+ "ClaimSupportStatus": {
+ "enum": [
+ "SUPPORTED",
+ "UNSUPPORTED",
+ "NEEDS_REVIEW"
+ ],
+ "title": "ClaimSupportStatus",
+ "type": "string"
+ },
+ "DraftSummary": {
+ "additionalProperties": false,
+ "properties": {
+ "article_id": {
+ "format": "uuid",
+ "title": "Article Id",
+ "type": "string"
+ },
+ "body_object_key": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Body Object Key"
+ },
+ "id": {
+ "format": "uuid",
+ "title": "Id",
+ "type": "string"
+ },
+ "meta_description": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Meta Description"
+ },
+ "meta_title": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Meta Title"
+ },
+ "slug": {
+ "minLength": 1,
+ "title": "Slug",
+ "type": "string"
+ },
+ "status": {
+ "$ref": "#/components/schemas/ArticleWorkflowStatus"
+ },
+ "title": {
+ "minLength": 1,
+ "title": "Title",
+ "type": "string"
+ },
+ "updated_at": {
+ "format": "date-time",
+ "title": "Updated At",
+ "type": "string"
+ },
+ "version": {
+ "minimum": 1,
+ "title": "Version",
+ "type": "integer"
+ }
+ },
+ "required": [
+ "id",
+ "article_id",
+ "version",
+ "title",
+ "slug",
+ "status",
+ "updated_at"
+ ],
+ "title": "DraftSummary",
+ "type": "object"
+ },
+ "EvidenceSummary": {
+ "additionalProperties": false,
+ "properties": {
+ "article_id": {
+ "format": "uuid",
+ "title": "Article Id",
+ "type": "string"
+ },
+ "artifact_manifest_id": {
+ "format": "uuid",
+ "title": "Artifact Manifest Id",
+ "type": "string"
+ },
+ "id": {
+ "format": "uuid",
+ "title": "Id",
+ "type": "string"
+ },
+ "retrieved_at": {
+ "format": "date-time",
+ "title": "Retrieved At",
+ "type": "string"
+ },
+ "source_quality_score": {
+ "maximum": 1,
+ "minimum": 0,
+ "title": "Source Quality Score",
+ "type": "number"
+ },
+ "source_title": {
+ "minLength": 1,
+ "title": "Source Title",
+ "type": "string"
+ },
+ "source_type": {
+ "minLength": 1,
+ "title": "Source Type",
+ "type": "string"
+ },
+ "source_url": {
+ "minLength": 1,
+ "title": "Source Url",
+ "type": "string"
+ },
+ "summary": {
+ "minLength": 1,
+ "title": "Summary",
+ "type": "string"
+ },
+ "supports_claims": {
+ "items": {
+ "format": "uuid",
+ "type": "string"
+ },
+ "title": "Supports Claims",
+ "type": "array"
+ }
+ },
+ "required": [
+ "id",
+ "article_id",
+ "source_title",
+ "source_url",
+ "source_type",
+ "source_quality_score",
+ "summary",
+ "artifact_manifest_id",
+ "retrieved_at"
+ ],
+ "title": "EvidenceSummary",
+ "type": "object"
+ },
+ "HTTPValidationError": {
+ "properties": {
+ "detail": {
+ "items": {
+ "$ref": "#/components/schemas/ValidationError"
+ },
+ "title": "Detail",
+ "type": "array"
+ }
+ },
+ "title": "HTTPValidationError",
+ "type": "object"
+ },
+ "PlanReviewStatus": {
+ "enum": [
+ "PENDING_REVIEW",
+ "APPROVED",
+ "REVISION_REQUESTED"
+ ],
+ "title": "PlanReviewStatus",
+ "type": "string"
+ },
+ "PlanSectionSummary": {
+ "additionalProperties": false,
+ "properties": {
+ "article_plan_id": {
+ "format": "uuid",
+ "title": "Article Plan Id",
+ "type": "string"
+ },
+ "claims_to_support": {
+ "items": {
+ "type": "string"
+ },
+ "title": "Claims To Support",
+ "type": "array"
+ },
+ "heading": {
+ "minLength": 1,
+ "title": "Heading",
+ "type": "string"
+ },
+ "id": {
+ "format": "uuid",
+ "title": "Id",
+ "type": "string"
+ },
+ "purpose": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Purpose"
+ },
+ "sort_order": {
+ "minimum": 1,
+ "title": "Sort Order",
+ "type": "integer"
+ },
+ "target_word_count": {
+ "anyOf": [
+ {
+ "minimum": 1,
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Target Word Count"
+ }
+ },
+ "required": [
+ "id",
+ "article_plan_id",
+ "sort_order",
+ "heading"
+ ],
+ "title": "PlanSectionSummary",
+ "type": "object"
+ },
+ "PlanSummary": {
+ "additionalProperties": false,
+ "properties": {
+ "article_id": {
+ "format": "uuid",
+ "title": "Article Id",
+ "type": "string"
+ },
+ "claims_to_prove": {
+ "items": {
+ "type": "string"
+ },
+ "title": "Claims To Prove",
+ "type": "array"
+ },
+ "created_at": {
+ "format": "date-time",
+ "title": "Created At",
+ "type": "string"
+ },
+ "id": {
+ "format": "uuid",
+ "title": "Id",
+ "type": "string"
+ },
+ "recommended_title": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Recommended Title"
+ },
+ "risks": {
+ "items": {
+ "type": "string"
+ },
+ "title": "Risks",
+ "type": "array"
+ },
+ "search_intent": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Search Intent"
+ },
+ "sections": {
+ "items": {
+ "$ref": "#/components/schemas/PlanSectionSummary"
+ },
+ "title": "Sections",
+ "type": "array"
+ },
+ "status": {
+ "$ref": "#/components/schemas/PlanReviewStatus"
+ },
+ "thesis": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Thesis"
+ },
+ "version": {
+ "minimum": 1,
+ "title": "Version",
+ "type": "integer"
+ }
+ },
+ "required": [
+ "id",
+ "article_id",
+ "version",
+ "status",
+ "created_at"
+ ],
+ "title": "PlanSummary",
+ "type": "object"
+ },
+ "PublishCommitSummary": {
+ "additionalProperties": false,
+ "properties": {
+ "article_id": {
+ "format": "uuid",
+ "title": "Article Id",
+ "type": "string"
+ },
+ "branch": {
+ "minLength": 1,
+ "title": "Branch",
+ "type": "string"
+ },
+ "commit_sha": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Commit Sha"
+ },
+ "content_bundle_manifest": {
+ "additionalProperties": true,
+ "title": "Content Bundle Manifest",
+ "type": "object"
+ },
+ "created_at": {
+ "format": "date-time",
+ "title": "Created At",
+ "type": "string"
+ },
+ "deployment_status": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Deployment Status"
+ },
+ "id": {
+ "format": "uuid",
+ "title": "Id",
+ "type": "string"
+ },
+ "repository_url": {
+ "minLength": 1,
+ "title": "Repository Url",
+ "type": "string"
+ },
+ "status": {
+ "$ref": "#/components/schemas/PublishingStatus"
+ },
+ "target_site_id": {
+ "format": "uuid",
+ "title": "Target Site Id",
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "article_id",
+ "target_site_id",
+ "repository_url",
+ "branch",
+ "status",
+ "created_at"
+ ],
+ "title": "PublishCommitSummary",
+ "type": "object"
+ },
+ "PublishingRules": {
+ "additionalProperties": false,
+ "properties": {
+ "asset_path_template": {
+ "minLength": 1,
+ "title": "Asset Path Template",
+ "type": "string"
+ },
+ "content_format": {
+ "default": "mdx",
+ "minLength": 1,
+ "title": "Content Format",
+ "type": "string"
+ },
+ "content_path_template": {
+ "minLength": 1,
+ "title": "Content Path Template",
+ "type": "string"
+ },
+ "dry_run_renderer": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Dry Run Renderer"
+ },
+ "frontmatter_mapping": {
+ "additionalProperties": true,
+ "title": "Frontmatter Mapping",
+ "type": "object"
+ },
+ "production_branch": {
+ "minLength": 1,
+ "title": "Production Branch",
+ "type": "string"
+ },
+ "repository_url": {
+ "minLength": 1,
+ "title": "Repository Url",
+ "type": "string"
+ },
+ "transform_script_version_id": {
+ "anyOf": [
+ {
+ "format": "uuid",
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Transform Script Version Id"
+ }
+ },
+ "required": [
+ "repository_url",
+ "production_branch",
+ "content_path_template",
+ "asset_path_template"
+ ],
+ "title": "PublishingRules",
+ "type": "object"
+ },
+ "PublishingStatus": {
+ "enum": [
+ "PUBLISH_NOT_STARTED",
+ "PUBLISH_DRY_RUN_REQUIRED",
+ "PUBLISH_DRY_RUN_RUNNING",
+ "PUBLISH_DRY_RUN_FAILED",
+ "PUBLISH_COMMIT_READY",
+ "PUBLISH_COMMIT_CREATED",
+ "PUBLISH_VERIFICATION_FAILED"
+ ],
+ "title": "PublishingStatus",
+ "type": "string"
+ },
+ "ResearchArtifactManifestSummary": {
+ "additionalProperties": false,
+ "properties": {
+ "agent_job_id": {
+ "format": "uuid",
+ "title": "Agent Job Id",
+ "type": "string"
+ },
+ "article_id": {
+ "format": "uuid",
+ "title": "Article Id",
+ "type": "string"
+ },
+ "artifacts": {
+ "items": {
+ "$ref": "#/components/schemas/ResearchArtifactSummary"
+ },
+ "title": "Artifacts",
+ "type": "array"
+ },
+ "created_at": {
+ "format": "date-time",
+ "title": "Created At",
+ "type": "string"
+ },
+ "id": {
+ "format": "uuid",
+ "title": "Id",
+ "type": "string"
+ },
+ "s3_prefix": {
+ "minLength": 1,
+ "title": "S3 Prefix",
+ "type": "string"
+ }
+ },
+ "required": [
+ "id",
+ "article_id",
+ "agent_job_id",
+ "s3_prefix",
+ "created_at"
+ ],
+ "title": "ResearchArtifactManifestSummary",
+ "type": "object"
+ },
+ "ResearchArtifactSummary": {
+ "additionalProperties": false,
+ "properties": {
+ "artifact_type": {
+ "minLength": 1,
+ "title": "Artifact Type",
+ "type": "string"
+ },
+ "content_hash": {
+ "minLength": 1,
+ "title": "Content Hash",
+ "type": "string"
+ },
+ "metadata": {
+ "additionalProperties": true,
+ "title": "Metadata",
+ "type": "object"
+ },
+ "object_key": {
+ "minLength": 1,
+ "title": "Object Key",
+ "type": "string"
+ },
+ "source_url": {
+ "minLength": 1,
+ "title": "Source Url",
+ "type": "string"
+ }
+ },
+ "required": [
+ "artifact_type",
+ "source_url",
+ "object_key",
+ "content_hash"
+ ],
+ "title": "ResearchArtifactSummary",
+ "type": "object"
+ },
+ "ReviewStatus": {
+ "enum": [
+ "PENDING",
+ "APPROVED",
+ "CHANGES_REQUESTED"
+ ],
+ "title": "ReviewStatus",
+ "type": "string"
+ },
+ "ReviewSummary": {
+ "additionalProperties": false,
+ "properties": {
+ "article_id": {
+ "format": "uuid",
+ "title": "Article Id",
+ "type": "string"
+ },
+ "created_at": {
+ "format": "date-time",
+ "title": "Created At",
+ "type": "string"
+ },
+ "id": {
+ "format": "uuid",
+ "title": "Id",
+ "type": "string"
+ },
+ "notes": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Notes"
+ },
+ "review_type": {
+ "$ref": "#/components/schemas/ReviewType"
+ },
+ "reviewer_id": {
+ "anyOf": [
+ {
+ "format": "uuid",
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Reviewer Id"
+ },
+ "status": {
+ "$ref": "#/components/schemas/ReviewStatus"
+ }
+ },
+ "required": [
+ "id",
+ "article_id",
+ "review_type",
+ "status",
+ "created_at"
+ ],
+ "title": "ReviewSummary",
+ "type": "object"
+ },
+ "ReviewType": {
+ "enum": [
+ "PLAN",
+ "EVIDENCE",
+ "ASSET",
+ "SEO_LANGUAGE",
+ "FINAL"
+ ],
+ "title": "ReviewType",
+ "type": "string"
+ },
+ "Role": {
+ "enum": [
+ "ADMIN",
+ "EDITOR"
+ ],
+ "title": "Role",
+ "type": "string"
+ },
+ "RunnerFileRef": {
+ "additionalProperties": false,
+ "properties": {
+ "content_hash": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Content Hash"
+ },
+ "path": {
+ "minLength": 1,
+ "title": "Path",
+ "type": "string"
+ }
+ },
+ "required": [
+ "path"
+ ],
+ "title": "RunnerFileRef",
+ "type": "object"
+ },
+ "ScriptConfigVersionStatus": {
+ "enum": [
+ "DRAFT",
+ "ACTIVE",
+ "DEPRECATED"
+ ],
+ "title": "ScriptConfigVersionStatus",
+ "type": "string"
+ },
+ "ScriptConfigVersionSummary": {
+ "additionalProperties": false,
+ "properties": {
+ "created_at": {
+ "format": "date-time",
+ "title": "Created At",
+ "type": "string"
+ },
+ "created_by": {
+ "format": "uuid",
+ "title": "Created By",
+ "type": "string"
+ },
+ "id": {
+ "format": "uuid",
+ "title": "Id",
+ "type": "string"
+ },
+ "publishing_yaml_hash": {
+ "minLength": 1,
+ "title": "Publishing Yaml Hash",
+ "type": "string"
+ },
+ "status": {
+ "$ref": "#/components/schemas/ScriptConfigVersionStatus"
+ },
+ "target_site_id": {
+ "format": "uuid",
+ "title": "Target Site Id",
+ "type": "string"
+ },
+ "transform_script_hash": {
+ "minLength": 1,
+ "title": "Transform Script Hash",
+ "type": "string"
+ },
+ "version": {
+ "minimum": 1,
+ "title": "Version",
+ "type": "integer"
+ }
+ },
+ "required": [
+ "id",
+ "target_site_id",
+ "version",
+ "status",
+ "publishing_yaml_hash",
+ "transform_script_hash",
+ "created_by",
+ "created_at"
+ ],
+ "title": "ScriptConfigVersionSummary",
+ "type": "object"
+ },
+ "TargetSiteConfig": {
+ "additionalProperties": false,
+ "properties": {
+ "active_script_config_version_id": {
+ "anyOf": [
+ {
+ "format": "uuid",
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Active Script Config Version Id"
+ },
+ "audience": {
+ "minLength": 1,
+ "title": "Audience",
+ "type": "string"
+ },
+ "brand_voice": {
+ "minLength": 1,
+ "title": "Brand Voice",
+ "type": "string"
+ },
+ "created_at": {
+ "format": "date-time",
+ "title": "Created At",
+ "type": "string"
+ },
+ "default_language": {
+ "default": "en",
+ "minLength": 2,
+ "title": "Default Language",
+ "type": "string"
+ },
+ "id": {
+ "format": "uuid",
+ "title": "Id",
+ "type": "string"
+ },
+ "name": {
+ "minLength": 1,
+ "title": "Name",
+ "type": "string"
+ },
+ "publishing_rules": {
+ "$ref": "#/components/schemas/PublishingRules"
+ },
+ "publishing_type": {
+ "default": "git_next",
+ "minLength": 1,
+ "title": "Publishing Type",
+ "type": "string"
+ },
+ "seo_rules": {
+ "additionalProperties": true,
+ "title": "Seo Rules",
+ "type": "object"
+ },
+ "slug": {
+ "minLength": 1,
+ "title": "Slug",
+ "type": "string"
+ },
+ "source_rules": {
+ "additionalProperties": true,
+ "title": "Source Rules",
+ "type": "object"
+ },
+ "updated_at": {
+ "format": "date-time",
+ "title": "Updated At",
+ "type": "string"
+ },
+ "visual_rules": {
+ "additionalProperties": true,
+ "title": "Visual Rules",
+ "type": "object"
+ }
+ },
+ "required": [
+ "id",
+ "name",
+ "slug",
+ "brand_voice",
+ "audience",
+ "publishing_rules",
+ "created_at",
+ "updated_at"
+ ],
+ "title": "TargetSiteConfig",
+ "type": "object"
+ },
+ "UserSummary": {
+ "additionalProperties": false,
+ "properties": {
+ "display_name": {
+ "minLength": 1,
+ "title": "Display Name",
+ "type": "string"
+ },
+ "id": {
+ "format": "uuid",
+ "title": "Id",
+ "type": "string"
+ },
+ "role": {
+ "$ref": "#/components/schemas/Role"
+ }
+ },
+ "required": [
+ "id",
+ "display_name",
+ "role"
+ ],
+ "title": "UserSummary",
+ "type": "object"
+ },
+ "ValidationError": {
+ "properties": {
+ "loc": {
+ "items": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "integer"
+ }
+ ]
+ },
+ "title": "Location",
+ "type": "array"
+ },
+ "msg": {
+ "title": "Message",
+ "type": "string"
+ },
+ "type": {
+ "title": "Error Type",
+ "type": "string"
+ }
+ },
+ "required": [
+ "loc",
+ "msg",
+ "type"
+ ],
+ "title": "ValidationError",
+ "type": "object"
+ }
+ }
+ },
+ "info": {
+ "title": "AI Content Pipeline Backend",
+ "version": "0.1.0"
+ },
+ "openapi": "3.1.0",
+ "paths": {
+ "/api/articles": {
+ "post": {
+ "operationId": "create_article_api_articles_post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ArticleCreateRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "201": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ArticleCreateResponse"
+ }
+ }
+ },
+ "description": "Successful Response"
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ "description": "Validation Error"
+ }
+ },
+ "summary": "Create Article",
+ "tags": [
+ "articles"
+ ]
+ }
+ },
+ "/health": {
+ "get": {
+ "operationId": "health_health_get",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "additionalProperties": {
+ "type": "string"
+ },
+ "title": "Response Health Health Get",
+ "type": "object"
+ }
+ }
+ },
+ "description": "Successful Response"
+ }
+ },
+ "summary": "Health"
+ }
+ },
+ "/health/dependencies": {
+ "get": {
+ "operationId": "dependency_health_health_dependencies_get",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {}
+ }
+ },
+ "description": "Successful Response"
+ }
+ },
+ "summary": "Dependency Health"
+ }
+ }
+ }
+}
diff --git a/packages/shared/src/api-types.ts b/packages/shared/src/api-types.ts
new file mode 100644
index 0000000..b336379
--- /dev/null
+++ b/packages/shared/src/api-types.ts
@@ -0,0 +1,273 @@
+// Generated from backend OpenAPI by scripts/generate_openapi_contracts.py.
+// Do not edit by hand.
+
+export type AgentJobErrorCategory = "FAILED_SCHEMA_VALIDATION" | "CLI_EXIT_CODE_FAILURE" | "TIMEOUT" | "MISSING_OUTPUT_FILE" | "UNSUPPORTED_CLAIMS_FOUND" | "SOURCE_RETRIEVAL_FAILED" | "RESEARCH_ARTIFACT_UPLOAD_FAILED" | "PUBLISH_DRY_RUN_FAILED" | "GIT_CHECKOUT_FAILED" | "GIT_COMMIT_FAILED" | "GIT_PUSH_NON_FAST_FORWARD" | "PUBLISH_VERIFICATION_FAILED";
+
+export type AgentJobOutput = {
+ artifacts?: ResearchArtifactSummary[];
+ error_category?: AgentJobErrorCategory | null;
+ error_message?: string | null;
+ output_files?: RunnerFileRef[];
+ payload?: Record