980 lines
38 KiB
Python
980 lines
38 KiB
Python
from __future__ import annotations
|
|
|
|
import sqlite3
|
|
from collections.abc import Iterable
|
|
from contextlib import closing
|
|
|
|
from src.domain.contracts import (
|
|
AgentJobErrorCategory,
|
|
AgentJobStatus,
|
|
AgentJobType,
|
|
ArticleWorkflowStatus,
|
|
AssetStatus,
|
|
AssetType,
|
|
ClaimRiskLevel,
|
|
ClaimSupportStatus,
|
|
PlanReviewStatus,
|
|
PublishingStatus,
|
|
Role,
|
|
ScriptConfigVersionStatus,
|
|
WorkflowTemplateStatus,
|
|
)
|
|
|
|
|
|
def _quoted_values(values: Iterable[str]) -> str:
|
|
return ", ".join(f"'{value}'" for value in values)
|
|
|
|
|
|
ROLE_VALUES = _quoted_values(role.value for role in Role)
|
|
ARTICLE_STATUS_VALUES = _quoted_values(status.value for status in ArticleWorkflowStatus)
|
|
PUBLISHING_STATUS_VALUES = _quoted_values(status.value for status in PublishingStatus)
|
|
SCRIPT_CONFIG_STATUS_VALUES = _quoted_values(
|
|
status.value for status in ScriptConfigVersionStatus
|
|
)
|
|
WORKFLOW_TEMPLATE_STATUS_VALUES = _quoted_values(
|
|
status.value for status in WorkflowTemplateStatus
|
|
)
|
|
PLAN_REVIEW_STATUS_VALUES = _quoted_values(status.value for status in PlanReviewStatus)
|
|
CLAIM_SUPPORT_STATUS_VALUES = _quoted_values(
|
|
status.value for status in ClaimSupportStatus
|
|
)
|
|
CLAIM_RISK_LEVEL_VALUES = _quoted_values(level.value for level in ClaimRiskLevel)
|
|
ASSET_TYPE_VALUES = _quoted_values(asset_type.value for asset_type in AssetType)
|
|
ASSET_STATUS_VALUES = _quoted_values(status.value for status in AssetStatus)
|
|
AGENT_JOB_TYPE_VALUES = _quoted_values(job_type.value for job_type in AgentJobType)
|
|
AGENT_JOB_STATUS_VALUES = _quoted_values(status.value for status in AgentJobStatus)
|
|
AGENT_JOB_ERROR_CATEGORY_VALUES = _quoted_values(
|
|
category.value for category in AgentJobErrorCategory
|
|
)
|
|
|
|
|
|
POSTGRES_SCHEMA_STATEMENTS: tuple[str, ...] = (
|
|
'CREATE EXTENSION IF NOT EXISTS "pgcrypto"',
|
|
f"""
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
email TEXT NOT NULL UNIQUE,
|
|
display_name TEXT NOT NULL,
|
|
role TEXT NOT NULL CHECK (role IN ({ROLE_VALUES})),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS target_sites (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name TEXT NOT NULL,
|
|
slug TEXT NOT NULL UNIQUE,
|
|
publishing_type TEXT NOT NULL DEFAULT 'git_next',
|
|
default_language TEXT NOT NULL DEFAULT 'en',
|
|
brand_voice TEXT NOT NULL,
|
|
audience TEXT NOT NULL,
|
|
seo_rules JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
visual_rules JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
source_rules JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
publishing_rules JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
active_script_config_version_id UUID,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
)
|
|
""",
|
|
f"""
|
|
CREATE TABLE IF NOT EXISTS script_config_versions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
target_site_id UUID NOT NULL REFERENCES target_sites(id) ON DELETE CASCADE,
|
|
version INTEGER NOT NULL,
|
|
status TEXT NOT NULL CHECK (status IN ({SCRIPT_CONFIG_STATUS_VALUES})),
|
|
created_by UUID NOT NULL REFERENCES users(id),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
diff JSONB NOT NULL DEFAULT '{{}}'::jsonb,
|
|
rollback_target_version_id UUID REFERENCES script_config_versions(id),
|
|
activated_at TIMESTAMPTZ,
|
|
publishing_yaml TEXT NOT NULL,
|
|
publishing_yaml_hash TEXT NOT NULL,
|
|
transform_script TEXT NOT NULL,
|
|
transform_script_hash TEXT NOT NULL,
|
|
UNIQUE (target_site_id, version)
|
|
)
|
|
""",
|
|
f"""
|
|
CREATE TABLE IF NOT EXISTS workflow_templates (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name TEXT NOT NULL,
|
|
slug TEXT NOT NULL UNIQUE,
|
|
description TEXT NOT NULL,
|
|
status TEXT NOT NULL CHECK (status IN ({WORKFLOW_TEMPLATE_STATUS_VALUES})),
|
|
version INTEGER NOT NULL,
|
|
created_by UUID NOT NULL REFERENCES users(id),
|
|
updated_by UUID REFERENCES users(id),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
activated_at TIMESTAMPTZ,
|
|
archived_at TIMESTAMPTZ
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS workflow_template_stages (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
workflow_template_id UUID NOT NULL REFERENCES workflow_templates(id) ON DELETE CASCADE,
|
|
stable_key TEXT NOT NULL,
|
|
display_name TEXT NOT NULL,
|
|
description TEXT NOT NULL,
|
|
position INTEGER NOT NULL,
|
|
owner_role TEXT NOT NULL CHECK (owner_role IN ('ADMIN', 'EDITOR')),
|
|
runner_profile_key TEXT NOT NULL,
|
|
required_inputs JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
expected_outputs JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
acceptance_criteria JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
requires_human_approval BOOLEAN NOT NULL DEFAULT false,
|
|
retry_policy JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
parts JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE (workflow_template_id, stable_key)
|
|
)
|
|
""",
|
|
f"""
|
|
CREATE TABLE IF NOT EXISTS workflow_template_events (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
workflow_template_id UUID NOT NULL REFERENCES workflow_templates(id) ON DELETE CASCADE,
|
|
event_type TEXT NOT NULL,
|
|
actor_user_id UUID REFERENCES users(id),
|
|
payload JSONB NOT NULL DEFAULT '{{}}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
)
|
|
""",
|
|
f"""
|
|
CREATE TABLE IF NOT EXISTS articles (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
target_site_id UUID NOT NULL REFERENCES target_sites(id),
|
|
workflow_template_id UUID REFERENCES workflow_templates(id),
|
|
workflow_template_version INTEGER,
|
|
workflow_template_snapshot JSONB,
|
|
status TEXT NOT NULL CHECK (status IN ({ARTICLE_STATUS_VALUES})),
|
|
publishing_status TEXT NOT NULL CHECK (
|
|
publishing_status IN ({PUBLISHING_STATUS_VALUES})
|
|
),
|
|
brief_description TEXT NOT NULL,
|
|
working_title TEXT,
|
|
language TEXT NOT NULL DEFAULT 'en',
|
|
content_type TEXT NOT NULL DEFAULT 'longform_guide',
|
|
primary_keyword TEXT,
|
|
assigned_editor_id UUID REFERENCES users(id),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
)
|
|
""",
|
|
"ALTER TABLE articles ADD COLUMN IF NOT EXISTS workflow_template_id UUID REFERENCES workflow_templates(id)",
|
|
"ALTER TABLE articles ADD COLUMN IF NOT EXISTS workflow_template_version INTEGER",
|
|
"ALTER TABLE articles ADD COLUMN IF NOT EXISTS workflow_template_snapshot JSONB",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS boundary_questions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
article_id UUID NOT NULL REFERENCES articles(id) ON DELETE CASCADE,
|
|
sort_order INTEGER NOT NULL,
|
|
category TEXT NOT NULL DEFAULT 'general',
|
|
question TEXT NOT NULL,
|
|
answer TEXT,
|
|
is_required BOOLEAN NOT NULL DEFAULT true,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE (article_id, sort_order)
|
|
)
|
|
""",
|
|
"ALTER TABLE boundary_questions ADD COLUMN IF NOT EXISTS category TEXT NOT NULL DEFAULT 'general'",
|
|
f"""
|
|
CREATE TABLE IF NOT EXISTS article_plans (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
article_id UUID NOT NULL REFERENCES articles(id) ON DELETE CASCADE,
|
|
version INTEGER NOT NULL,
|
|
status TEXT NOT NULL CHECK (status IN ({PLAN_REVIEW_STATUS_VALUES})),
|
|
title_options JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
recommended_title TEXT,
|
|
reader_persona TEXT,
|
|
search_intent TEXT,
|
|
thesis TEXT,
|
|
claims_to_prove JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
evidence_needs JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
visual_needs JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
seo_notes JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
source_requirements JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
excluded_sources JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
tone TEXT,
|
|
audience TEXT,
|
|
risks JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE (article_id, version)
|
|
)
|
|
""",
|
|
"ALTER TABLE article_plans ADD COLUMN IF NOT EXISTS title_options JSONB NOT NULL DEFAULT '[]'::jsonb",
|
|
"ALTER TABLE article_plans ADD COLUMN IF NOT EXISTS reader_persona TEXT",
|
|
"ALTER TABLE article_plans ADD COLUMN IF NOT EXISTS evidence_needs JSONB NOT NULL DEFAULT '[]'::jsonb",
|
|
"ALTER TABLE article_plans ADD COLUMN IF NOT EXISTS visual_needs JSONB NOT NULL DEFAULT '[]'::jsonb",
|
|
"ALTER TABLE article_plans ADD COLUMN IF NOT EXISTS seo_notes JSONB NOT NULL DEFAULT '[]'::jsonb",
|
|
"ALTER TABLE article_plans ADD COLUMN IF NOT EXISTS source_requirements JSONB NOT NULL DEFAULT '[]'::jsonb",
|
|
"ALTER TABLE article_plans ADD COLUMN IF NOT EXISTS excluded_sources JSONB NOT NULL DEFAULT '[]'::jsonb",
|
|
"ALTER TABLE article_plans ADD COLUMN IF NOT EXISTS tone TEXT",
|
|
"ALTER TABLE article_plans ADD COLUMN IF NOT EXISTS audience TEXT",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS plan_sections (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
article_plan_id UUID NOT NULL REFERENCES article_plans(id) ON DELETE CASCADE,
|
|
sort_order INTEGER NOT NULL,
|
|
heading TEXT NOT NULL,
|
|
purpose TEXT,
|
|
key_points JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
evidence_needs JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
claims_to_support JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
target_word_count INTEGER,
|
|
UNIQUE (article_plan_id, sort_order)
|
|
)
|
|
""",
|
|
"ALTER TABLE plan_sections ADD COLUMN IF NOT EXISTS key_points JSONB NOT NULL DEFAULT '[]'::jsonb",
|
|
"ALTER TABLE plan_sections ADD COLUMN IF NOT EXISTS evidence_needs JSONB NOT NULL DEFAULT '[]'::jsonb",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS evidence_items (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
article_id UUID NOT NULL REFERENCES articles(id) ON DELETE CASCADE,
|
|
source_title TEXT NOT NULL,
|
|
source_url TEXT NOT NULL,
|
|
source_type TEXT NOT NULL,
|
|
source_quality_score NUMERIC(4, 3) NOT NULL,
|
|
summary TEXT NOT NULL,
|
|
supports_claims JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
artifact_manifest_id UUID,
|
|
retrieved_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
review_status TEXT NOT NULL DEFAULT 'PENDING'
|
|
)
|
|
""",
|
|
"ALTER TABLE evidence_items ADD COLUMN IF NOT EXISTS review_status TEXT NOT NULL DEFAULT 'PENDING'",
|
|
f"""
|
|
CREATE TABLE IF NOT EXISTS claims (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
article_id UUID NOT NULL REFERENCES articles(id) ON DELETE CASCADE,
|
|
section_id UUID REFERENCES plan_sections(id) ON DELETE SET NULL,
|
|
claim_text TEXT NOT NULL,
|
|
support_status TEXT NOT NULL CHECK (
|
|
support_status IN ({CLAIM_SUPPORT_STATUS_VALUES})
|
|
),
|
|
risk_level TEXT NOT NULL CHECK (risk_level IN ({CLAIM_RISK_LEVEL_VALUES})),
|
|
evidence_item_ids JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
)
|
|
""",
|
|
f"""
|
|
CREATE TABLE IF NOT EXISTS article_drafts (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
article_id UUID NOT NULL REFERENCES articles(id) ON DELETE CASCADE,
|
|
version INTEGER NOT NULL,
|
|
title TEXT NOT NULL,
|
|
slug TEXT NOT NULL,
|
|
meta_title TEXT,
|
|
meta_description TEXT,
|
|
body_object_key TEXT,
|
|
body_markdown TEXT NOT NULL DEFAULT '',
|
|
faq_items JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
visual_placeholders JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
evidence_references JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
unsupported_claim_warnings JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
based_on_draft_id UUID REFERENCES article_drafts(id) ON DELETE SET NULL,
|
|
status TEXT NOT NULL CHECK (status IN ({ARTICLE_STATUS_VALUES})),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE (article_id, version)
|
|
)
|
|
""",
|
|
"ALTER TABLE article_drafts ADD COLUMN IF NOT EXISTS body_markdown TEXT NOT NULL DEFAULT ''",
|
|
"ALTER TABLE article_drafts ADD COLUMN IF NOT EXISTS faq_items JSONB NOT NULL DEFAULT '[]'::jsonb",
|
|
"ALTER TABLE article_drafts ADD COLUMN IF NOT EXISTS visual_placeholders JSONB NOT NULL DEFAULT '[]'::jsonb",
|
|
"ALTER TABLE article_drafts ADD COLUMN IF NOT EXISTS evidence_references JSONB NOT NULL DEFAULT '[]'::jsonb",
|
|
"ALTER TABLE article_drafts ADD COLUMN IF NOT EXISTS unsupported_claim_warnings JSONB NOT NULL DEFAULT '[]'::jsonb",
|
|
"ALTER TABLE article_drafts ADD COLUMN IF NOT EXISTS based_on_draft_id UUID REFERENCES article_drafts(id) ON DELETE SET NULL",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS article_review_reports (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
article_id UUID NOT NULL REFERENCES articles(id) ON DELETE CASCADE,
|
|
review_kind TEXT NOT NULL CHECK (review_kind IN ('SEO', 'LANGUAGE')),
|
|
draft_id UUID NOT NULL REFERENCES article_drafts(id) ON DELETE CASCADE,
|
|
score INTEGER NOT NULL,
|
|
issues JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
recommended_slug TEXT NOT NULL,
|
|
recommended_title TEXT NOT NULL,
|
|
schema_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
rules_snapshot JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS article_review_suggestions (
|
|
id UUID PRIMARY KEY,
|
|
article_id UUID NOT NULL REFERENCES articles(id) ON DELETE CASCADE,
|
|
review_kind TEXT NOT NULL CHECK (review_kind IN ('SEO', 'LANGUAGE')),
|
|
report_id UUID NOT NULL REFERENCES article_review_reports(id) ON DELETE CASCADE,
|
|
suggestion_key TEXT NOT NULL,
|
|
severity TEXT NOT NULL,
|
|
location TEXT NOT NULL,
|
|
message TEXT NOT NULL,
|
|
suggested_fix TEXT,
|
|
suggested_rewrite TEXT,
|
|
patch JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
status TEXT NOT NULL CHECK (status IN ('PENDING', 'ACCEPTED', 'REJECTED', 'EDITED')),
|
|
applied_text TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE (article_id, review_kind, suggestion_key)
|
|
)
|
|
""",
|
|
f"""
|
|
CREATE TABLE IF NOT EXISTS assets (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
article_id UUID NOT NULL REFERENCES articles(id) ON DELETE CASCADE,
|
|
section_id UUID REFERENCES plan_sections(id) ON DELETE SET NULL,
|
|
asset_type TEXT NOT NULL CHECK (asset_type IN ({ASSET_TYPE_VALUES})),
|
|
title TEXT NOT NULL,
|
|
prompt TEXT,
|
|
object_key TEXT,
|
|
file_url TEXT,
|
|
alt_text TEXT,
|
|
caption TEXT,
|
|
status TEXT NOT NULL CHECK (status IN ({ASSET_STATUS_VALUES})),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
)
|
|
""",
|
|
"ALTER TABLE assets ADD COLUMN IF NOT EXISTS section_id UUID REFERENCES plan_sections(id) ON DELETE SET NULL",
|
|
"ALTER TABLE assets ADD COLUMN IF NOT EXISTS object_key TEXT",
|
|
f"""
|
|
CREATE TABLE IF NOT EXISTS asset_revisions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
article_id UUID NOT NULL REFERENCES articles(id) ON DELETE CASCADE,
|
|
asset_id UUID NOT NULL REFERENCES assets(id) ON DELETE CASCADE,
|
|
revision_index INTEGER NOT NULL,
|
|
action TEXT NOT NULL,
|
|
actor_user_id UUID REFERENCES users(id),
|
|
payload JSONB NOT NULL DEFAULT '{{}}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
)
|
|
""",
|
|
f"""
|
|
CREATE TABLE IF NOT EXISTS workflow_events (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
article_id UUID NOT NULL REFERENCES articles(id) ON DELETE CASCADE,
|
|
event_type TEXT NOT NULL,
|
|
from_status TEXT CHECK (from_status IN ({ARTICLE_STATUS_VALUES})),
|
|
to_status TEXT CHECK (to_status IN ({ARTICLE_STATUS_VALUES})),
|
|
actor_user_id UUID REFERENCES users(id),
|
|
payload JSONB NOT NULL DEFAULT '{{}}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
)
|
|
""",
|
|
f"""
|
|
CREATE TABLE IF NOT EXISTS agent_jobs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
article_id UUID REFERENCES articles(id) ON DELETE SET NULL,
|
|
parent_job_id UUID REFERENCES agent_jobs(id) ON DELETE SET NULL,
|
|
attempt INTEGER NOT NULL DEFAULT 1,
|
|
job_type TEXT NOT NULL CHECK (job_type IN ({AGENT_JOB_TYPE_VALUES})),
|
|
agent_profile TEXT NOT NULL,
|
|
status TEXT NOT NULL CHECK (status IN ({AGENT_JOB_STATUS_VALUES})),
|
|
workspace_path TEXT,
|
|
input_files JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
output_files JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
payload JSONB NOT NULL DEFAULT '{{}}'::jsonb,
|
|
error_category TEXT CHECK (
|
|
error_category IS NULL
|
|
OR error_category IN ({AGENT_JOB_ERROR_CATEGORY_VALUES})
|
|
),
|
|
error_message TEXT,
|
|
stdout TEXT NOT NULL DEFAULT '',
|
|
stderr TEXT NOT NULL DEFAULT '',
|
|
exit_code INTEGER,
|
|
duration_ms INTEGER,
|
|
queued_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
started_at TIMESTAMPTZ,
|
|
finished_at TIMESTAMPTZ
|
|
)
|
|
""",
|
|
"ALTER TABLE agent_jobs ADD COLUMN IF NOT EXISTS parent_job_id UUID REFERENCES agent_jobs(id) ON DELETE SET NULL",
|
|
"ALTER TABLE agent_jobs ADD COLUMN IF NOT EXISTS attempt INTEGER NOT NULL DEFAULT 1",
|
|
"ALTER TABLE agent_jobs ADD COLUMN IF NOT EXISTS stdout TEXT NOT NULL DEFAULT ''",
|
|
"ALTER TABLE agent_jobs ADD COLUMN IF NOT EXISTS stderr TEXT NOT NULL DEFAULT ''",
|
|
"ALTER TABLE agent_jobs ADD COLUMN IF NOT EXISTS exit_code INTEGER",
|
|
"ALTER TABLE agent_jobs ADD COLUMN IF NOT EXISTS duration_ms INTEGER",
|
|
"ALTER TABLE agent_jobs ADD COLUMN IF NOT EXISTS payload JSONB NOT NULL DEFAULT '{}'::jsonb",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS research_run_manifests (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
article_id UUID NOT NULL REFERENCES articles(id) ON DELETE CASCADE,
|
|
agent_job_id UUID NOT NULL REFERENCES agent_jobs(id) ON DELETE CASCADE,
|
|
s3_prefix TEXT NOT NULL,
|
|
source_urls JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
object_keys JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
content_hashes JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
artifact_types JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
metadata_references JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
artifacts JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
)
|
|
""",
|
|
f"""
|
|
CREATE TABLE IF NOT EXISTS publish_commits (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
article_id UUID NOT NULL REFERENCES articles(id) ON DELETE CASCADE,
|
|
target_site_id UUID NOT NULL REFERENCES target_sites(id),
|
|
repository_url TEXT NOT NULL,
|
|
branch TEXT NOT NULL,
|
|
commit_sha TEXT,
|
|
content_bundle_manifest JSONB NOT NULL DEFAULT '{{}}'::jsonb,
|
|
status TEXT NOT NULL CHECK (status IN ({PUBLISHING_STATUS_VALUES})),
|
|
deployment_status TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
)
|
|
""",
|
|
f"""
|
|
CREATE TABLE IF NOT EXISTS prompt_versions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
prompt_key TEXT NOT NULL,
|
|
version INTEGER NOT NULL,
|
|
status TEXT NOT NULL CHECK (status IN ({SCRIPT_CONFIG_STATUS_VALUES})),
|
|
body TEXT NOT NULL,
|
|
created_by UUID NOT NULL REFERENCES users(id),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
activated_at TIMESTAMPTZ,
|
|
diff JSONB NOT NULL DEFAULT '{{}}'::jsonb,
|
|
UNIQUE (prompt_key, version)
|
|
)
|
|
""",
|
|
f"""
|
|
CREATE TABLE IF NOT EXISTS script_config_version_events (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
target_site_id UUID NOT NULL REFERENCES target_sites(id) ON DELETE CASCADE,
|
|
version_id UUID NOT NULL REFERENCES script_config_versions(id) ON DELETE CASCADE,
|
|
event_type TEXT NOT NULL,
|
|
actor_user_id UUID REFERENCES users(id),
|
|
payload JSONB NOT NULL DEFAULT '{{}}'::jsonb,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
)
|
|
""",
|
|
"CREATE INDEX IF NOT EXISTS idx_target_sites_slug ON target_sites (slug)",
|
|
"CREATE INDEX IF NOT EXISTS idx_workflow_templates_slug ON workflow_templates (slug)",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_workflow_templates_status_updated
|
|
ON workflow_templates (status, updated_at DESC)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_workflow_template_stages_order
|
|
ON workflow_template_stages (workflow_template_id, position)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_workflow_template_events_history
|
|
ON workflow_template_events (workflow_template_id, created_at)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_articles_dashboard
|
|
ON articles (target_site_id, status, publishing_status, updated_at DESC)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_articles_editor_dashboard
|
|
ON articles (assigned_editor_id, updated_at DESC)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_articles_workflow_template
|
|
ON articles (workflow_template_id, workflow_template_version)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_agent_jobs_queue
|
|
ON agent_jobs (status, queued_at)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_agent_jobs_article_timeline
|
|
ON agent_jobs (article_id, job_type, queued_at)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_workflow_events_timeline
|
|
ON workflow_events (article_id, created_at)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_asset_revisions_asset
|
|
ON asset_revisions (asset_id, revision_index, created_at)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_research_run_manifests_article
|
|
ON research_run_manifests (article_id, created_at)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_publish_commits_article
|
|
ON publish_commits (article_id, created_at)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_script_config_version_events_site
|
|
ON script_config_version_events (target_site_id, created_at)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_article_review_reports_article
|
|
ON article_review_reports (article_id, review_kind, created_at)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_article_review_suggestions_report
|
|
ON article_review_suggestions (report_id, status)
|
|
""",
|
|
)
|
|
|
|
|
|
SQLITE_SCHEMA_STATEMENTS: tuple[str, ...] = (
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id TEXT PRIMARY KEY,
|
|
email TEXT NOT NULL UNIQUE,
|
|
display_name TEXT NOT NULL,
|
|
role TEXT NOT NULL,
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS target_sites (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
slug TEXT NOT NULL UNIQUE,
|
|
publishing_type TEXT NOT NULL DEFAULT 'git_next',
|
|
default_language TEXT NOT NULL DEFAULT 'en',
|
|
brand_voice TEXT NOT NULL,
|
|
audience TEXT NOT NULL,
|
|
seo_rules TEXT NOT NULL DEFAULT '{}',
|
|
visual_rules TEXT NOT NULL DEFAULT '{}',
|
|
source_rules TEXT NOT NULL DEFAULT '{}',
|
|
publishing_rules TEXT NOT NULL DEFAULT '{}',
|
|
active_script_config_version_id TEXT,
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS script_config_versions (
|
|
id TEXT PRIMARY KEY,
|
|
target_site_id TEXT NOT NULL,
|
|
version INTEGER NOT NULL,
|
|
status TEXT NOT NULL,
|
|
created_by TEXT NOT NULL,
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
diff TEXT NOT NULL DEFAULT '{}',
|
|
rollback_target_version_id TEXT,
|
|
activated_at TEXT,
|
|
publishing_yaml TEXT NOT NULL,
|
|
publishing_yaml_hash TEXT NOT NULL,
|
|
transform_script TEXT NOT NULL,
|
|
transform_script_hash TEXT NOT NULL,
|
|
UNIQUE (target_site_id, version)
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS workflow_templates (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
slug TEXT NOT NULL UNIQUE,
|
|
description TEXT NOT NULL,
|
|
status TEXT NOT NULL,
|
|
version INTEGER NOT NULL,
|
|
created_by TEXT NOT NULL,
|
|
updated_by TEXT,
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
activated_at TEXT,
|
|
archived_at TEXT
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS workflow_template_stages (
|
|
id TEXT PRIMARY KEY,
|
|
workflow_template_id TEXT NOT NULL,
|
|
stable_key TEXT NOT NULL,
|
|
display_name TEXT NOT NULL,
|
|
description TEXT NOT NULL,
|
|
position INTEGER NOT NULL,
|
|
owner_role TEXT NOT NULL,
|
|
runner_profile_key TEXT NOT NULL,
|
|
required_inputs TEXT NOT NULL DEFAULT '[]',
|
|
expected_outputs TEXT NOT NULL DEFAULT '[]',
|
|
acceptance_criteria TEXT NOT NULL DEFAULT '[]',
|
|
requires_human_approval INTEGER NOT NULL DEFAULT 0,
|
|
retry_policy TEXT NOT NULL DEFAULT '{}',
|
|
parts TEXT NOT NULL DEFAULT '[]',
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE (workflow_template_id, stable_key)
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS workflow_template_events (
|
|
id TEXT PRIMARY KEY,
|
|
workflow_template_id TEXT NOT NULL,
|
|
event_type TEXT NOT NULL,
|
|
actor_user_id TEXT,
|
|
payload TEXT NOT NULL DEFAULT '{}',
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS articles (
|
|
id TEXT PRIMARY KEY,
|
|
target_site_id TEXT NOT NULL,
|
|
workflow_template_id TEXT,
|
|
workflow_template_version INTEGER,
|
|
workflow_template_snapshot TEXT,
|
|
status TEXT NOT NULL,
|
|
publishing_status TEXT NOT NULL,
|
|
brief_description TEXT NOT NULL,
|
|
working_title TEXT,
|
|
language TEXT NOT NULL DEFAULT 'en',
|
|
content_type TEXT NOT NULL DEFAULT 'longform_guide',
|
|
primary_keyword TEXT,
|
|
assigned_editor_id TEXT,
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS boundary_questions (
|
|
id TEXT PRIMARY KEY,
|
|
article_id TEXT NOT NULL,
|
|
sort_order INTEGER NOT NULL,
|
|
category TEXT NOT NULL DEFAULT 'general',
|
|
question TEXT NOT NULL,
|
|
answer TEXT,
|
|
is_required INTEGER NOT NULL DEFAULT 1,
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE (article_id, sort_order)
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS article_plans (
|
|
id TEXT PRIMARY KEY,
|
|
article_id TEXT NOT NULL,
|
|
version INTEGER NOT NULL,
|
|
status TEXT NOT NULL,
|
|
title_options TEXT NOT NULL DEFAULT '[]',
|
|
recommended_title TEXT,
|
|
reader_persona TEXT,
|
|
search_intent TEXT,
|
|
thesis TEXT,
|
|
claims_to_prove TEXT NOT NULL DEFAULT '[]',
|
|
evidence_needs TEXT NOT NULL DEFAULT '[]',
|
|
visual_needs TEXT NOT NULL DEFAULT '[]',
|
|
seo_notes TEXT NOT NULL DEFAULT '[]',
|
|
source_requirements TEXT NOT NULL DEFAULT '[]',
|
|
excluded_sources TEXT NOT NULL DEFAULT '[]',
|
|
tone TEXT,
|
|
audience TEXT,
|
|
risks TEXT NOT NULL DEFAULT '[]',
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE (article_id, version)
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS plan_sections (
|
|
id TEXT PRIMARY KEY,
|
|
article_plan_id TEXT NOT NULL,
|
|
sort_order INTEGER NOT NULL,
|
|
heading TEXT NOT NULL,
|
|
purpose TEXT,
|
|
key_points TEXT NOT NULL DEFAULT '[]',
|
|
evidence_needs TEXT NOT NULL DEFAULT '[]',
|
|
claims_to_support TEXT NOT NULL DEFAULT '[]',
|
|
target_word_count INTEGER,
|
|
UNIQUE (article_plan_id, sort_order)
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS evidence_items (
|
|
id TEXT PRIMARY KEY,
|
|
article_id TEXT NOT NULL,
|
|
source_title TEXT NOT NULL,
|
|
source_url TEXT NOT NULL,
|
|
source_type TEXT NOT NULL,
|
|
source_quality_score REAL NOT NULL,
|
|
summary TEXT NOT NULL,
|
|
supports_claims TEXT NOT NULL DEFAULT '[]',
|
|
artifact_manifest_id TEXT,
|
|
retrieved_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
review_status TEXT NOT NULL DEFAULT 'PENDING'
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS claims (
|
|
id TEXT PRIMARY KEY,
|
|
article_id TEXT NOT NULL,
|
|
section_id TEXT,
|
|
claim_text TEXT NOT NULL,
|
|
support_status TEXT NOT NULL,
|
|
risk_level TEXT NOT NULL,
|
|
evidence_item_ids TEXT NOT NULL DEFAULT '[]',
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS article_drafts (
|
|
id TEXT PRIMARY KEY,
|
|
article_id TEXT NOT NULL,
|
|
version INTEGER NOT NULL,
|
|
title TEXT NOT NULL,
|
|
slug TEXT NOT NULL,
|
|
meta_title TEXT,
|
|
meta_description TEXT,
|
|
body_object_key TEXT,
|
|
body_markdown TEXT NOT NULL DEFAULT '',
|
|
faq_items TEXT NOT NULL DEFAULT '[]',
|
|
visual_placeholders TEXT NOT NULL DEFAULT '[]',
|
|
evidence_references TEXT NOT NULL DEFAULT '[]',
|
|
unsupported_claim_warnings TEXT NOT NULL DEFAULT '[]',
|
|
based_on_draft_id TEXT,
|
|
status TEXT NOT NULL,
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE (article_id, version)
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS article_review_reports (
|
|
id TEXT PRIMARY KEY,
|
|
article_id TEXT NOT NULL,
|
|
review_kind TEXT NOT NULL,
|
|
draft_id TEXT NOT NULL,
|
|
score INTEGER NOT NULL,
|
|
issues TEXT NOT NULL DEFAULT '[]',
|
|
recommended_slug TEXT NOT NULL,
|
|
recommended_title TEXT NOT NULL,
|
|
schema_json TEXT NOT NULL DEFAULT '{}',
|
|
rules_snapshot TEXT NOT NULL DEFAULT '{}',
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS article_review_suggestions (
|
|
id TEXT PRIMARY KEY,
|
|
article_id TEXT NOT NULL,
|
|
review_kind TEXT NOT NULL,
|
|
report_id TEXT NOT NULL,
|
|
suggestion_key TEXT NOT NULL,
|
|
severity TEXT NOT NULL,
|
|
location TEXT NOT NULL,
|
|
message TEXT NOT NULL,
|
|
suggested_fix TEXT,
|
|
suggested_rewrite TEXT,
|
|
patch TEXT NOT NULL DEFAULT '{}',
|
|
status TEXT NOT NULL,
|
|
applied_text TEXT,
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE (article_id, review_kind, suggestion_key)
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS assets (
|
|
id TEXT PRIMARY KEY,
|
|
article_id TEXT NOT NULL,
|
|
section_id TEXT,
|
|
asset_type TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
prompt TEXT,
|
|
object_key TEXT,
|
|
file_url TEXT,
|
|
alt_text TEXT,
|
|
caption TEXT,
|
|
status TEXT NOT NULL,
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS asset_revisions (
|
|
id TEXT PRIMARY KEY,
|
|
article_id TEXT NOT NULL,
|
|
asset_id TEXT NOT NULL,
|
|
revision_index INTEGER NOT NULL,
|
|
action TEXT NOT NULL,
|
|
actor_user_id TEXT,
|
|
payload TEXT NOT NULL DEFAULT '{}',
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS workflow_events (
|
|
id TEXT PRIMARY KEY,
|
|
article_id TEXT NOT NULL,
|
|
event_type TEXT NOT NULL,
|
|
from_status TEXT,
|
|
to_status TEXT,
|
|
actor_user_id TEXT,
|
|
payload TEXT NOT NULL DEFAULT '{}',
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS agent_jobs (
|
|
id TEXT PRIMARY KEY,
|
|
article_id TEXT,
|
|
parent_job_id TEXT,
|
|
attempt INTEGER NOT NULL DEFAULT 1,
|
|
job_type TEXT NOT NULL,
|
|
agent_profile TEXT NOT NULL,
|
|
status TEXT NOT NULL,
|
|
workspace_path TEXT,
|
|
input_files TEXT NOT NULL DEFAULT '[]',
|
|
output_files TEXT NOT NULL DEFAULT '[]',
|
|
payload TEXT NOT NULL DEFAULT '{}',
|
|
error_category TEXT,
|
|
error_message TEXT,
|
|
stdout TEXT NOT NULL DEFAULT '',
|
|
stderr TEXT NOT NULL DEFAULT '',
|
|
exit_code INTEGER,
|
|
duration_ms INTEGER,
|
|
queued_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
started_at TEXT,
|
|
finished_at TEXT
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS research_run_manifests (
|
|
id TEXT PRIMARY KEY,
|
|
article_id TEXT NOT NULL,
|
|
agent_job_id TEXT NOT NULL,
|
|
s3_prefix TEXT NOT NULL,
|
|
source_urls TEXT NOT NULL DEFAULT '[]',
|
|
object_keys TEXT NOT NULL DEFAULT '[]',
|
|
content_hashes TEXT NOT NULL DEFAULT '[]',
|
|
artifact_types TEXT NOT NULL DEFAULT '[]',
|
|
metadata_references TEXT NOT NULL DEFAULT '[]',
|
|
artifacts TEXT NOT NULL DEFAULT '[]',
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS publish_commits (
|
|
id TEXT PRIMARY KEY,
|
|
article_id TEXT NOT NULL,
|
|
target_site_id TEXT NOT NULL,
|
|
repository_url TEXT NOT NULL,
|
|
branch TEXT NOT NULL,
|
|
commit_sha TEXT,
|
|
content_bundle_manifest TEXT NOT NULL DEFAULT '{}',
|
|
status TEXT NOT NULL,
|
|
deployment_status TEXT,
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS prompt_versions (
|
|
id TEXT PRIMARY KEY,
|
|
prompt_key TEXT NOT NULL,
|
|
version INTEGER NOT NULL,
|
|
status TEXT NOT NULL,
|
|
body TEXT NOT NULL,
|
|
created_by TEXT NOT NULL,
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
activated_at TEXT,
|
|
diff TEXT NOT NULL DEFAULT '{}',
|
|
UNIQUE (prompt_key, version)
|
|
)
|
|
""",
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS script_config_version_events (
|
|
id TEXT PRIMARY KEY,
|
|
target_site_id TEXT NOT NULL,
|
|
version_id TEXT NOT NULL,
|
|
event_type TEXT NOT NULL,
|
|
actor_user_id TEXT,
|
|
payload TEXT NOT NULL DEFAULT '{}',
|
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
""",
|
|
"CREATE INDEX IF NOT EXISTS idx_target_sites_slug ON target_sites (slug)",
|
|
"CREATE INDEX IF NOT EXISTS idx_workflow_templates_slug ON workflow_templates (slug)",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_workflow_templates_status_updated
|
|
ON workflow_templates (status, updated_at)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_workflow_template_stages_order
|
|
ON workflow_template_stages (workflow_template_id, position)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_workflow_template_events_history
|
|
ON workflow_template_events (workflow_template_id, created_at)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_articles_dashboard
|
|
ON articles (target_site_id, status, publishing_status, updated_at)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_articles_editor_dashboard
|
|
ON articles (assigned_editor_id, updated_at)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_articles_workflow_template
|
|
ON articles (workflow_template_id, workflow_template_version)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_agent_jobs_queue
|
|
ON agent_jobs (status, queued_at)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_agent_jobs_article_timeline
|
|
ON agent_jobs (article_id, job_type, queued_at)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_workflow_events_timeline
|
|
ON workflow_events (article_id, created_at)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_asset_revisions_asset
|
|
ON asset_revisions (asset_id, revision_index, created_at)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_research_run_manifests_article
|
|
ON research_run_manifests (article_id, created_at)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_publish_commits_article
|
|
ON publish_commits (article_id, created_at)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_script_config_version_events_site
|
|
ON script_config_version_events (target_site_id, created_at)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_article_review_reports_article
|
|
ON article_review_reports (article_id, review_kind, created_at)
|
|
""",
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_article_review_suggestions_report
|
|
ON article_review_suggestions (report_id, status)
|
|
""",
|
|
)
|
|
|
|
|
|
def schema_statements_for_dsn(dsn: str) -> tuple[str, ...]:
|
|
if dsn.startswith("sqlite:///"):
|
|
return SQLITE_SCHEMA_STATEMENTS
|
|
return POSTGRES_SCHEMA_STATEMENTS
|
|
|
|
|
|
def setup_database(dsn: str) -> None:
|
|
statements = schema_statements_for_dsn(dsn)
|
|
if dsn.startswith("sqlite:///"):
|
|
sqlite_path = dsn.removeprefix("sqlite:///")
|
|
with closing(sqlite3.connect(sqlite_path)) as connection:
|
|
for statement in statements:
|
|
connection.execute(statement)
|
|
connection.commit()
|
|
return
|
|
|
|
import psycopg
|
|
|
|
with psycopg.connect(dsn) as connection:
|
|
with connection.cursor() as cursor:
|
|
for statement in statements:
|
|
cursor.execute(statement)
|