Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
120 commits
Select commit Hold shift + click to select a range
c741437
Add execplan for no-QA generation runs and TEI-P5 retrieval (4.3.2)
leynos Jun 15, 2026
27b77ec
Start no-QA slice implementation
leynos Jun 24, 2026
9ea272a
Add no-QA slice behavioural scaffold
leynos Jun 24, 2026
91accf1
Record M0 review completion
leynos Jun 24, 2026
d1d85d1
Add no-QA generation run metadata
leynos Jun 24, 2026
98cac76
Record M1 review completion
leynos Jun 24, 2026
d31160f
Add durable generation-run storage
leynos Jun 24, 2026
91eec01
Record M2a review completion
leynos Jun 24, 2026
3350583
Add episode TEI revision updates
leynos Jun 24, 2026
5c42815
Record M2b review completion
leynos Jun 24, 2026
f2ac278
Add draft script persistence services
leynos Jun 24, 2026
a415cec
Record M3 review completion
leynos Jun 24, 2026
d80c086
Add in-process generation run launcher
leynos Jun 24, 2026
caeddf8
Repair post-rebase architecture config
leynos Jun 25, 2026
6d2077d
Introduce generation run status updates
leynos Jun 25, 2026
8d961ec
Repair post-rebase validation
Jul 21, 2026
43a702c
Fix documentation validation
Jul 21, 2026
4f4e945
Reconcile no-QA ExecPlan status
Jul 22, 2026
49c023b
Close launcher milestone review
Jul 22, 2026
0b778e5
Add generation run REST resources
Jul 22, 2026
56b6d2d
Add episode TEI retrieval
Jul 22, 2026
c98e69f
Exercise the no-QA REST slice end to end
Jul 22, 2026
0b2ed41
Pass bound presenters into draft generation
Jul 22, 2026
012aa7b
Document no-QA generation delivery
Jul 22, 2026
5bfde1a
Keep no-QA BDD steps within module limit
Jul 22, 2026
969fc04
Harden no-QA generation inputs and transitions
Jul 22, 2026
6d87d36
Reconcile no-QA implementation status
Jul 22, 2026
1243258
Mark the no-QA ExecPlan complete
Jul 22, 2026
1a8cce8
Tighten no-QA docs wording
Jul 22, 2026
85f412a
Expand checkpoint and episode model docstrings
Jul 22, 2026
7d2389b
Tighten ExecPlan review note
Jul 22, 2026
559baf1
Harden no-QA review boundaries
Jul 22, 2026
0860387
Add 4.3.2 review follow-up note
Jul 22, 2026
31830be
Update execplan review evidence
Jul 22, 2026
bcb8c9b
Resolve no-QA code health findings
Jul 22, 2026
3dbbbd2
Record code health review evidence
Jul 22, 2026
8e7cb19
Repair DF12 rebase integration
leynos Jul 31, 2026
e5b8fee
Record DF12 rebase validation
leynos Jul 31, 2026
d655be6
Update final rebase evidence
leynos Jul 31, 2026
11a15ec
Record rewritten-target rebase evidence
leynos Jul 31, 2026
55bc639
Record stacked PR publication
leynos Jul 31, 2026
6a29107
Format rebase-affected Python modules
leynos Aug 11, 2026
a1c6348
Record DF12 rebase validation
leynos Aug 11, 2026
811483e
Record rebase publication outcome
leynos Aug 11, 2026
d50c9e7
Extract generation-run event setup
leynos Aug 11, 2026
019f9f3
Extract TEI response assertions
leynos Aug 11, 2026
7f73aee
Centralize generation-run rejection logs
leynos Aug 11, 2026
cc95e02
Strengthen generation-run persistence contracts
leynos Aug 12, 2026
cfca787
Document generation-run lease recovery
leynos Aug 14, 2026
b6d4897
Align value objects with updated lint rules
leynos Aug 14, 2026
8ad5fc7
Update generation-run route documentation
leynos Aug 14, 2026
12c0571
Stabilize generation-run runtime wiring
leynos Aug 14, 2026
6e376d5
Categorize missing episode generation failures
leynos Aug 14, 2026
ae8d636
Clarify generation run episode lookup
leynos Aug 15, 2026
50c898d
Extract generation event-page assertions
leynos Aug 16, 2026
8797453
Unify cost recorder contracts
leynos Aug 16, 2026
0b6600e
Harden generation-run execution
leynos Aug 16, 2026
b6a5fc3
Shorten materialization lock scope
leynos Aug 16, 2026
c426251
Clarify no-QA plan progress
leynos Aug 16, 2026
d2f398a
Harden generation persistence failures
leynos Aug 16, 2026
b6a9566
Unify TEI text validation
leynos Aug 16, 2026
ac613ef
Assert no-QA error envelopes
leynos Aug 16, 2026
caca499
Instrument generation-run admission
leynos Aug 16, 2026
a1ad604
Wrap ADR index links
leynos Aug 16, 2026
1119f4f
Strengthen generation API test contracts
leynos Aug 17, 2026
dffead4
Harden TEI conditional retrieval
leynos Aug 17, 2026
332d0e7
Clarify concurrent claim test failures
leynos Aug 17, 2026
923bc69
Document episode TEI API contracts
leynos Aug 17, 2026
bbfdcdf
Persist immediate shutdown cancellations
leynos Aug 17, 2026
5334b35
Reconcile no-QA generation documentation
leynos Aug 17, 2026
4002af8
Preserve materialisation service spelling
leynos Aug 17, 2026
b9829bb
Wire production metrics into API runtime
leynos Aug 17, 2026
36a0e68
Extract runtime metrics wiring test
leynos Aug 17, 2026
bf592f7
Harden lease recovery procedure
leynos Aug 17, 2026
994d7c2
Document observability and episode errors
leynos Aug 17, 2026
eb1e449
Document draft generation contracts
leynos Aug 17, 2026
5276f3d
Support generation-run SQL foreign keys in tests
leynos Aug 17, 2026
33b4c07
Document review remediation decisions
leynos Aug 17, 2026
522477f
Harden generation-run persistence boundaries
leynos Aug 17, 2026
87ab3af
Document draft generation failures
leynos Aug 17, 2026
51f9bb0
Harden generation-run review paths
leynos Aug 17, 2026
3a74758
Refine generation-run review contracts
leynos Aug 17, 2026
c48ecdd
Extract episode materialisation identity phase
leynos Aug 17, 2026
b0b01ea
Extract claim outcome log assertions
leynos Aug 17, 2026
10d36f0
Match claim log helper contract
leynos Aug 17, 2026
5a2c2be
Group claim log expectations
leynos Aug 17, 2026
04bd3da
Group error envelope expectations
leynos Aug 17, 2026
e3ed8cb
Check readiness before missing sources
leynos Aug 17, 2026
aaae941
Align generation documentation status
leynos Aug 17, 2026
59722b5
Type-check generation run composite stub
leynos Aug 17, 2026
1ece813
Share ingestion persistence error setup
leynos Aug 17, 2026
3793e04
Record CodeScene stub duplication suppression rationale
leynos Aug 20, 2026
eebd0f0
Share source document ID lookup
leynos Aug 20, 2026
4a7181c
Clarify generation review contracts
leynos Aug 20, 2026
bc0ad5b
Harden generation-run execution boundaries (#141)
leynos Aug 20, 2026
3eec9e7
Record generation hardening publication
leynos Aug 20, 2026
33e64cd
Document exact error-envelope matching
leynos Aug 20, 2026
53f06ca
Refactor generation loading helpers
leynos Aug 21, 2026
d2d9a7e
Extract API test assertions
leynos Aug 21, 2026
0651e3b
Refactor lifecycle and metric test helpers
leynos Aug 21, 2026
5d84584
Name generated lifecycle arrangement helper
leynos Aug 21, 2026
8e160f4
Name TEI trace assertion helper
leynos Aug 21, 2026
fc9dfa7
Extract generation-run poll assertions
leynos Aug 21, 2026
3989d55
Split bounded upload source reading
leynos Aug 21, 2026
1310ef6
Inject deterministic generation-run storage seams
leynos Aug 21, 2026
0460acd
Parameterize malformed upload diagnostics
leynos Aug 21, 2026
36a9a11
Add malformed upload assertion context
leynos Aug 21, 2026
3435b57
Preserve unauthenticated intake test paths
leynos Aug 21, 2026
9a59020
Add SQL TEI revision property tests
leynos Aug 21, 2026
c9b269b
Document event pagination interaction
leynos Aug 21, 2026
fab4b85
Document generation persistence boundaries
leynos Aug 21, 2026
1662166
Assert durable materialisation association
leynos Aug 21, 2026
0031527
Harden generation-run ownership boundaries
leynos Aug 21, 2026
3c7a0a9
Align Skylos configuration after rebase
leynos Aug 21, 2026
bfd1b66
Correct Skylos runtime entry points
leynos Aug 21, 2026
b0d32f0
Clarify generation auth documentation
leynos Aug 21, 2026
8914003
Document generation storage and hydration contracts
leynos Aug 21, 2026
da8c3b9
Harden no-QA generation workflow
leynos Aug 21, 2026
8ba2357
Harden generation-run review boundaries
leynos Aug 22, 2026
68ea6f3
Harden generation persistence finalisation
leynos Aug 22, 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")
29 changes: 29 additions & 0 deletions alembic/versions/20260624_000012_add_ingestion_job_owner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Add an authenticated-principal owner to ingestion jobs."""

import sqlalchemy as sa

from alembic import op

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


def upgrade() -> None:
"""Persist optional ownership for jobs created before authentication."""
op.add_column(
"ingestion_jobs",
sa.Column("owner_principal_id", sa.String(length=240), nullable=True),
)
op.create_index(
"ix_ij_owner_created",
"ingestion_jobs",
["owner_principal_id", "created_at", "id"],
)


def downgrade() -> None:
"""Remove ingestion-job ownership."""
op.drop_index("ix_ij_owner_created", "ingestion_jobs")
op.drop_column("ingestion_jobs", "owner_principal_id")
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 017
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 017: No-QA generation execution and TEI persistence](adr-017-no-qa-generation-run-execution-and-tei-persistence.md)
Loading
Loading