Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
87b077b
Add execplan for no-QA generation runs and TEI-P5 retrieval (4.3.2)
leynos Jun 15, 2026
814ad44
Start no-QA slice implementation
leynos Jun 24, 2026
e486ca3
Add no-QA slice behavioural scaffold
leynos Jun 24, 2026
02cd7d0
Record M0 review completion
leynos Jun 24, 2026
8d5365e
Add no-QA generation run metadata
leynos Jun 24, 2026
10a9700
Record M1 review completion
leynos Jun 24, 2026
a97d9a2
Add durable generation-run storage
leynos Jun 24, 2026
02ef3b6
Record M2a review completion
leynos Jun 24, 2026
4bfe5e9
Add episode TEI revision updates
leynos Jun 24, 2026
2640fa1
Record M2b review completion
leynos Jun 24, 2026
7ae7cfd
Add draft script persistence services
leynos Jun 24, 2026
7158eab
Record M3 review completion
leynos Jun 24, 2026
ca53f1d
Add in-process generation run launcher
leynos Jun 24, 2026
9a8552b
Repair post-rebase architecture config
leynos Jun 25, 2026
7d93da8
Introduce generation run status updates
leynos Jun 25, 2026
959a3ce
Repair post-rebase validation
Jul 21, 2026
22fbeb6
Fix documentation validation
Jul 21, 2026
d95979a
Reconcile no-QA ExecPlan status
Jul 22, 2026
e320b00
Close launcher milestone review
Jul 22, 2026
572c86c
Add generation run REST resources
Jul 22, 2026
c4bcb33
Add episode TEI retrieval
Jul 22, 2026
7c3ebc3
Exercise the no-QA REST slice end to end
Jul 22, 2026
9faa7f6
Pass bound presenters into draft generation
Jul 22, 2026
5deae22
Document no-QA generation delivery
Jul 22, 2026
92b0ce4
Keep no-QA BDD steps within module limit
Jul 22, 2026
c463b37
Harden no-QA generation inputs and transitions
Jul 22, 2026
2a69598
Reconcile no-QA implementation status
Jul 22, 2026
ff5cfa3
Mark the no-QA ExecPlan complete
Jul 22, 2026
0e8dd4a
Tighten no-QA docs wording
Jul 22, 2026
7dffa77
Expand checkpoint and episode model docstrings
Jul 22, 2026
3ccf773
Tighten ExecPlan review note
Jul 22, 2026
fabb16e
Harden no-QA review boundaries
Jul 22, 2026
8129acc
Add 4.3.2 review follow-up note
Jul 22, 2026
8a23498
Update execplan review evidence
Jul 22, 2026
b43d0ab
Resolve no-QA code health findings
Jul 22, 2026
182a039
Record code health review evidence
Jul 22, 2026
00d0c92
Repair DF12 rebase integration
leynos Jul 31, 2026
44ed010
Record DF12 rebase validation
leynos Jul 31, 2026
1f41969
Update final rebase evidence
leynos Jul 31, 2026
ec2748a
Record rewritten-target rebase evidence
leynos Jul 31, 2026
e2acaf9
Record stacked PR publication
leynos Jul 31, 2026
be8303d
Format rebase-affected Python modules
leynos Aug 11, 2026
e658f97
Record DF12 rebase validation
leynos Aug 11, 2026
64efd65
Record rebase publication outcome
leynos Aug 11, 2026
28a1767
Extract generation-run event setup
leynos Aug 11, 2026
91343c2
Extract TEI response assertions
leynos Aug 11, 2026
8be6d84
Centralize generation-run rejection logs
leynos Aug 11, 2026
f222875
Strengthen generation-run persistence contracts
leynos Aug 12, 2026
6d00373
Document generation-run lease recovery
leynos Aug 14, 2026
fdeb9b0
Align value objects with updated lint rules
leynos Aug 14, 2026
b7737e4
Update generation-run route documentation
leynos Aug 14, 2026
bf2bb4a
Stabilize generation-run runtime wiring
leynos Aug 14, 2026
07411ed
Categorize missing episode generation failures
leynos Aug 14, 2026
bb16b1a
Clarify generation run episode lookup
leynos Aug 15, 2026
f5a3d6e
Extract generation event-page assertions
leynos Aug 16, 2026
fde42bc
Unify cost recorder contracts
leynos Aug 16, 2026
eaea7c9
Harden generation-run execution
leynos Aug 16, 2026
b4dc700
Shorten materialization lock scope
leynos Aug 16, 2026
377f22e
Clarify no-QA plan progress
leynos Aug 16, 2026
58605ae
Harden generation persistence failures
leynos Aug 16, 2026
fce19d4
Unify TEI text validation
leynos Aug 16, 2026
a4f9597
Assert no-QA error envelopes
leynos Aug 16, 2026
bed05e4
Instrument generation-run admission
leynos Aug 16, 2026
902ce50
Wrap ADR index links
leynos Aug 16, 2026
6a17ad0
Strengthen generation API test contracts
leynos Aug 17, 2026
164528c
Harden TEI conditional retrieval
leynos Aug 17, 2026
9921083
Clarify concurrent claim test failures
leynos Aug 17, 2026
72a5ef0
Document episode TEI API contracts
leynos Aug 17, 2026
5c7b8e4
Persist immediate shutdown cancellations
leynos Aug 17, 2026
39f14d4
Reconcile no-QA generation documentation
leynos Aug 17, 2026
8e9e656
Preserve materialisation service spelling
leynos Aug 17, 2026
0531f7e
Wire production metrics into API runtime
leynos Aug 17, 2026
78c1e70
Extract runtime metrics wiring test
leynos Aug 17, 2026
4cf7959
Harden lease recovery procedure
leynos Aug 17, 2026
d17eb6d
Document observability and episode errors
leynos Aug 17, 2026
dc0b4a0
Document draft generation contracts
leynos Aug 17, 2026
845328a
Support generation-run SQL foreign keys in tests
leynos Aug 17, 2026
9b5b1d9
Document review remediation decisions
leynos Aug 17, 2026
1b7dcd6
Harden generation-run persistence boundaries
leynos Aug 17, 2026
b695738
Document draft generation failures
leynos Aug 17, 2026
dc5e0c3
Harden generation-run review paths
leynos Aug 17, 2026
bd0fd61
Refine generation-run review contracts
leynos Aug 17, 2026
c7e844c
Extract episode materialisation identity phase
leynos Aug 17, 2026
cbf9de1
Extract claim outcome log assertions
leynos Aug 17, 2026
99fa7a2
Match claim log helper contract
leynos Aug 17, 2026
9656f1a
Group claim log expectations
leynos Aug 17, 2026
dcade79
Group error envelope expectations
leynos Aug 17, 2026
d5a80b1
Check readiness before missing sources
leynos Aug 17, 2026
288116a
Align generation documentation status
leynos Aug 17, 2026
98d1591
Type-check generation run composite stub
leynos Aug 17, 2026
f8994a6
Share ingestion persistence error setup
leynos Aug 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 194 additions & 0 deletions alembic/versions/20260624_000010_add_generation_run_tables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
"""Add durable generation-run and event-log tables."""

import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

from alembic import op

revision = "20260624_000010"
down_revision = "20260601_000009"
branch_labels = None
depends_on = None


def _enum(name: str, *values: str) -> postgresql.ENUM:
"""Return an existing-or-creatable PostgreSQL enum."""
return postgresql.ENUM(*values, name=name, create_type=False)


def _create_enums() -> None:
"""Create generation-run PostgreSQL enums."""
_enum(
"generation_run_status",
"pending",
"running",
"paused",
"succeeded",
"failed",
"cancelled",
).create(op.get_bind(), checkfirst=True)
_enum("quality_mode", "draft_without_qa").create(op.get_bind(), checkfirst=True)
_enum("qa_status", "skipped").create(op.get_bind(), checkfirst=True)


def _drop_enums() -> None:
"""Drop generation-run PostgreSQL enums."""
_enum("qa_status", "skipped").drop(op.get_bind(), checkfirst=True)
_enum("quality_mode", "draft_without_qa").drop(op.get_bind(), checkfirst=True)
_enum(
"generation_run_status",
"pending",
"running",
"paused",
"succeeded",
"failed",
"cancelled",
).drop(op.get_bind(), checkfirst=True)


def _create_generation_runs_table() -> None:
"""Create durable generation-run records."""
op.create_table(
"generation_runs",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column(
"episode_id",
postgresql.UUID(as_uuid=True),
sa.ForeignKey("episodes.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column(
"source_bundle_id",
postgresql.UUID(as_uuid=True),
sa.ForeignKey("ingestion_jobs.id"),
nullable=False,
),
sa.Column("actor", sa.String(length=240), nullable=False),
sa.Column(
"status",
_enum(
"generation_run_status",
"pending",
"running",
"paused",
"succeeded",
"failed",
"cancelled",
),
nullable=False,
),
sa.Column("current_node", sa.String(length=160), nullable=True),
sa.Column("budget_snapshot", postgresql.JSONB(), nullable=False),
sa.Column("configuration", postgresql.JSONB(), nullable=False),
sa.Column(
"quality_mode",
_enum("quality_mode", "draft_without_qa"),
nullable=False,
),
sa.Column("qa_status", _enum("qa_status", "skipped"), nullable=True),
sa.Column("skip_qa_rationale", sa.Text(), nullable=True),
sa.Column(
"idempotency_principal_id",
sa.String(length=200),
nullable=False,
),
sa.Column("idempotency_key", sa.String(length=512), nullable=True),
sa.Column("error_message", sa.Text(), nullable=True),
sa.Column("error_category", sa.String(length=120), nullable=True),
sa.Column("lease_expires_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("started_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("ended_at", sa.DateTime(timezone=True), nullable=True),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
server_default=sa.func.now(),
nullable=False,
),
sa.Column(
"updated_at",
sa.DateTime(timezone=True),
server_default=sa.func.now(),
nullable=False,
),
sa.UniqueConstraint(
"idempotency_principal_id",
"idempotency_key",
name="uq_generation_runs_idempotency_principal_key",
),
)
op.create_index(
"ix_generation_runs_episode_id",
"generation_runs",
["episode_id"],
)
op.create_index("ix_generation_runs_status", "generation_runs", ["status"])
op.create_index(
"ix_generation_runs_started_at",
"generation_runs",
["started_at"],
)


def _drop_generation_runs_table() -> None:
"""Drop durable generation-run records."""
op.drop_index("ix_generation_runs_started_at", table_name="generation_runs")
op.drop_index("ix_generation_runs_status", table_name="generation_runs")
op.drop_index("ix_generation_runs_episode_id", table_name="generation_runs")
op.drop_table("generation_runs")


def _create_generation_events_table() -> None:
"""Create append-only generation-run event records."""
op.create_table(
"generation_events",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column(
"generation_run_id",
postgresql.UUID(as_uuid=True),
sa.ForeignKey("generation_runs.id"),
nullable=False,
),
sa.Column("seq", sa.Integer(), nullable=False),
sa.Column("kind", sa.String(length=160), nullable=False),
sa.Column("payload", postgresql.JSONB(), nullable=False),
sa.Column("occurred_at", sa.DateTime(timezone=True), nullable=False),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
server_default=sa.func.now(),
nullable=False,
),
sa.UniqueConstraint(
"generation_run_id",
"seq",
name="uq_generation_events_run_seq",
),
)
op.create_index(
"ix_generation_events_generation_run_id",
"generation_events",
["generation_run_id"],
)


def _drop_generation_events_table() -> None:
"""Drop append-only generation-run event records."""
op.drop_index(
"ix_generation_events_generation_run_id",
table_name="generation_events",
)
op.drop_table("generation_events")


def upgrade() -> None:
"""Create generation-run persistence tables."""
_create_enums()
_create_generation_runs_table()
_create_generation_events_table()


def downgrade() -> None:
"""Drop generation-run persistence tables."""
_drop_generation_events_table()
_drop_generation_runs_table()
_drop_enums()
71 changes: 71 additions & 0 deletions alembic/versions/20260624_000011_add_episode_tei_revisioning.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""Add episode TEI revision metadata."""

import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

from alembic import op

revision = "20260624_000011"
down_revision = "20260624_000010"
branch_labels = None
depends_on = None


def _qa_status_enum() -> postgresql.ENUM:
"""Return the existing QA status PostgreSQL enum."""
return postgresql.ENUM("skipped", name="qa_status", create_type=False)


def upgrade() -> None:
"""Add optimistic TEI revision metadata to episodes."""
op.add_column(
"episodes",
sa.Column("tei_revision", sa.Integer(), server_default="1", nullable=False),
)
op.create_check_constraint(
"ck_episodes_tei_revision_positive",
"episodes",
"tei_revision >= 1",
)
op.add_column(
"episodes",
sa.Column("tei_content_hash", sa.String(length=128), nullable=True),
)
op.add_column(
"episodes",
sa.Column("qa_status", _qa_status_enum(), nullable=True),
)
op.add_column(
"episodes",
sa.Column(
"last_generation_run_id",
postgresql.UUID(as_uuid=True),
nullable=True,
),
)
op.create_foreign_key(
"fk_episodes_last_generation_run_id_generation_runs",
"episodes",
"generation_runs",
["last_generation_run_id"],
["id"],
ondelete="SET NULL",
)


def downgrade() -> None:
"""Remove optimistic TEI revision metadata from episodes."""
op.drop_constraint(
"fk_episodes_last_generation_run_id_generation_runs",
"episodes",
type_="foreignkey",
)
op.drop_column("episodes", "last_generation_run_id")
op.drop_column("episodes", "qa_status")
op.drop_column("episodes", "tei_content_hash")
op.drop_constraint(
"ck_episodes_tei_revision_positive",
"episodes",
type_="check",
)
op.drop_column("episodes", "tei_revision")
8 changes: 6 additions & 2 deletions docs/adr/adr-009-source-to-script-rest-vertical-slice.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,9 @@ vertical-slice tasks:
The first task owns upload resources, ingestion-job source attachment, and
presenter-profile use through existing reusable reference-document semantics.
The second task depends on the first task and owns the generation-run contract,
the explicit no-QA quality mode, run polling, and TEI retrieval.
the explicit no-QA quality mode, run polling, and TEI retrieval. ADR 016
records its execution, persistence, recovery-hook, and content-negotiation
decisions.

The target API is `/v1`. Existing unversioned routes do not need preservation
when the vertical slice is implemented because the project has not reached
Expand Down Expand Up @@ -248,7 +250,8 @@ the same first-write-wins and conflict-detection invariants.

## References

See also the system design[^7] and TUI API design.[^8]
See also the system design[^7], TUI API design,[^8] and the no-QA execution
decision.[^9]

[^1]: [RFC 6129: The `application/tei+xml` media type](https://datatracker.ietf.org/doc/html/rfc6129)
[^2]: [ADR 014: Hexagonal architecture enforcement](adr-014-hexagonal-architecture-enforcement.md)
Expand All @@ -258,3 +261,4 @@ See also the system design[^7] and TUI API design.[^8]
[^6]: [Stripe idempotent request guidance](https://docs.stripe.com/api/idempotent_requests)
[^7]: [Episodic podcast generation system design](../episodic-podcast-generation-system-design.md)
[^8]: [Episodic TUI API design](../episodic-tui-api-design.md)
[^9]: [ADR 016: No-QA generation execution and TEI persistence](adr-016-no-qa-generation-run-execution-and-tei-persistence.md)
Loading
Loading