from __future__ import annotations from datetime import UTC, datetime from hashlib import sha256 from uuid import NAMESPACE_URL, UUID, uuid5 from src.application.demo_runtime import ( ensure_demo_bare_repository, is_demo_mode, resolve_demo_repository_path, ) from src.domain.contracts import ( PublishingRules, Role, ScriptConfigVersionStatus, WorkflowTemplateStatus, ) from src.domain.schema import ( SEEDED_ADMIN_EMAIL, SEEDED_EDITOR_EMAIL, SEEDED_TARGET_SITE_SLUG, ) SEED_TIMESTAMP = datetime(2026, 1, 1, tzinfo=UTC) SEEDED_WORKFLOW_TEMPLATE_SLUG = "demo-article-production" def seed_reference_data(repository: object) -> None: admin_id = _stable_uuid("user:admin") editor_id = _stable_uuid("user:editor") target_site_id = _stable_uuid(f"target-site:{SEEDED_TARGET_SITE_SLUG}") script_config_version_id = _stable_uuid( f"script-config-version:{SEEDED_TARGET_SITE_SLUG}:1" ) repository.users.upsert( user_id=admin_id, email=SEEDED_ADMIN_EMAIL, display_name="Admin User", role=Role.ADMIN, created_at=SEED_TIMESTAMP, updated_at=SEED_TIMESTAMP, ) repository.users.upsert( user_id=editor_id, email=SEEDED_EDITOR_EMAIL, display_name="Editor User", role=Role.EDITOR, created_at=SEED_TIMESTAMP, updated_at=SEED_TIMESTAMP, ) repository_url = _repository_url_for_seed() publishing_rules = PublishingRules( repository_url=repository_url, production_branch="main", content_format="mdx", content_path_template="content/articles/{slug}.mdx", asset_path_template="public/articles/{slug}/{filename}", frontmatter_mapping={ "title": "title", "description": "meta_description", "publishedAt": "published_at", }, transform_script_version_id=script_config_version_id, dry_run_renderer="next-mdx", ) repository.target_sites.upsert( site_id=target_site_id, name="B2B SaaS Blog", slug=SEEDED_TARGET_SITE_SLUG, publishing_type="git_next", default_language="en", brand_voice="Clear, practical, evidence-led B2B editorial voice.", audience="B2B SaaS founders and growth teams.", seo_rules={ "title_max_length": 60, "meta_description_max_length": 155, "primary_keyword_required": True, }, visual_rules={ "hero_style": "product editorial", "diagram_format": "mermaid", }, source_rules={ "minimum_sources": 3, "preferred_source_types": ["primary", "analyst", "vendor_docs"], }, publishing_rules=publishing_rules, active_script_config_version_id=script_config_version_id, created_at=SEED_TIMESTAMP, updated_at=SEED_TIMESTAMP, ) publishing_yaml = f"""target: b2b_saas_blog repository: url: {repository_url} branch: main content: format: mdx path_template: content/articles/{{slug}}.mdx assets: path_template: public/articles/{{slug}}/{{filename}} """ transform_script = """export function transformArticle(article) { return { frontmatter: article.frontmatter, body: article.body, assets: article.assets, }; } """ repository.script_config_versions.upsert( version_id=script_config_version_id, target_site_id=target_site_id, version=1, status=ScriptConfigVersionStatus.ACTIVE, created_by=admin_id, created_at=SEED_TIMESTAMP, updated_at=SEED_TIMESTAMP, diff={"summary": "Initial Git-backed Next.js publishing configuration."}, rollback_target_version_id=None, activated_at=SEED_TIMESTAMP, publishing_yaml=publishing_yaml, publishing_yaml_hash=_sha256(publishing_yaml), transform_script=transform_script, transform_script_hash=_sha256(transform_script), ) _seed_demo_workflow_template(repository, admin_id=admin_id) def _seed_demo_workflow_template(repository: object, *, admin_id: UUID) -> None: existing = next( ( workflow for workflow in repository.workflow_templates.list() if workflow.slug == SEEDED_WORKFLOW_TEMPLATE_SLUG ), None, ) if existing is not None: return workflow = repository.workflow_templates.create( name="Demo article production workflow", slug=SEEDED_WORKFLOW_TEMPLATE_SLUG, description="Active demo workflow for article intake, drafting, review, and publishing.", status=WorkflowTemplateStatus.DRAFT, version=1, created_by=admin_id, updated_by=admin_id, created_at=SEED_TIMESTAMP, updated_at=SEED_TIMESTAMP, ) for stage in _demo_workflow_stages(): repository.workflow_template_stages.create( workflow_id=workflow.id, stable_key=stage["stable_key"], display_name=stage["display_name"], description=stage["description"], position=stage["position"], owner_role=stage["owner_role"], runner_profile_key=stage["runner_profile_key"], required_inputs=stage["required_inputs"], expected_outputs=stage["expected_outputs"], acceptance_criteria=stage["acceptance_criteria"], requires_human_approval=stage["requires_human_approval"], retry_policy=stage["retry_policy"], parts=stage["parts"], updated_by=admin_id, created_at=SEED_TIMESTAMP, updated_at=SEED_TIMESTAMP, ) repository.workflow_templates.activate( workflow_id=workflow.id, updated_by=admin_id, activated_at=SEED_TIMESTAMP, ) def _demo_workflow_stages() -> list[dict[str, object]]: return [ { "stable_key": "intake-boundary-questions", "display_name": "Boundary question intake", "description": "Collect required editorial context before plan generation.", "position": 1, "owner_role": Role.EDITOR, "runner_profile_key": "boundary-question-agent-v1", "required_inputs": ["article_brief", "target_site"], "expected_outputs": ["answered_boundary_questions"], "acceptance_criteria": ["All required boundary questions are answered."], "requires_human_approval": True, "retry_policy": {"max_attempts": 1, "backoff_seconds": 0}, "parts": [], }, { "stable_key": "plan-review", "display_name": "Plan review", "description": "Generate and approve the article plan before research.", "position": 2, "owner_role": Role.EDITOR, "runner_profile_key": "plan-generation-agent-v1", "required_inputs": ["answered_boundary_questions"], "expected_outputs": ["approved_plan"], "acceptance_criteria": ["Plan is approved by the editor."], "requires_human_approval": True, "retry_policy": {"max_attempts": 2, "backoff_seconds": 30}, "parts": [], }, { "stable_key": "research-evidence", "display_name": "Research and evidence", "description": "Run research and approve evidence before production starts.", "position": 3, "owner_role": Role.EDITOR, "runner_profile_key": "research-agent-v1", "required_inputs": ["approved_plan"], "expected_outputs": ["approved_evidence_matrix"], "acceptance_criteria": ["Evidence matrix is ready and approved."], "requires_human_approval": True, "retry_policy": {"max_attempts": 2, "backoff_seconds": 60}, "parts": [], }, { "stable_key": "parallel-production", "display_name": "Parallel production", "description": "Create section scaffolds and draft-ready assets in parallel.", "position": 4, "owner_role": Role.EDITOR, "runner_profile_key": "section-scaffold-agent-v1", "required_inputs": ["approved_evidence_matrix"], "expected_outputs": ["section_scaffolds", "asset_specs"], "acceptance_criteria": ["All production jobs have succeeded or been retried."], "requires_human_approval": False, "retry_policy": {"max_attempts": 2, "backoff_seconds": 60}, "parts": [], }, { "stable_key": "draft-assembly", "display_name": "Draft assembly", "description": "Assemble a draft from the approved plan and production artifacts.", "position": 5, "owner_role": Role.EDITOR, "runner_profile_key": "draft-assembly-agent-v1", "required_inputs": ["section_scaffolds", "approved_assets"], "expected_outputs": ["article_draft"], "acceptance_criteria": ["Draft is assembled for review."], "requires_human_approval": False, "retry_policy": {"max_attempts": 2, "backoff_seconds": 60}, "parts": [], }, { "stable_key": "seo-language-review", "display_name": "SEO and language review", "description": "Run automated quality reviews and resolve required suggestions.", "position": 6, "owner_role": Role.EDITOR, "runner_profile_key": "review-agent-v1", "required_inputs": ["article_draft"], "expected_outputs": ["resolved_review_suggestions"], "acceptance_criteria": ["Required SEO and language suggestions are resolved."], "requires_human_approval": True, "retry_policy": {"max_attempts": 2, "backoff_seconds": 60}, "parts": [], }, { "stable_key": "final-publishing", "display_name": "Final approval and publishing", "description": "Approve final draft, dry-run the bundle, and create the publish commit.", "position": 7, "owner_role": Role.EDITOR, "runner_profile_key": "publishing-agent-v1", "required_inputs": ["resolved_review_suggestions", "approved_assets"], "expected_outputs": ["publish_commit"], "acceptance_criteria": ["Publish dry-run passes and commit is created."], "requires_human_approval": True, "retry_policy": {"max_attempts": 1, "backoff_seconds": 0}, "parts": [], }, ] def _repository_url_for_seed() -> str: if not is_demo_mode(): return "git@github.com:example/site.git" demo_repo_path = resolve_demo_repository_path() ensure_demo_bare_repository(demo_repo_path) return str(demo_repo_path) def _stable_uuid(value: str) -> UUID: return uuid5(NAMESPACE_URL, f"ai-content-pipeline:{value}") def _sha256(value: str) -> str: return sha256(value.encode("utf-8")).hexdigest()