Skip to content

Complete source and presenter-profile intake (4.3.1) - #137

Merged
leynos merged 24 commits into
mainfrom
4-3-1-complete-source-and-presenter-profile-intake-script-generation
Jun 14, 2026
Merged

Complete source and presenter-profile intake (4.3.1)#137
leynos merged 24 commits into
mainfrom
4-3-1-complete-source-and-presenter-profile-intake-script-generation

Conversation

@lodyai

@lodyai lodyai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

This branch completes the Roadmap 4.3.1 follow-up after the post-merge completeness assessment of the source and presenter-profile intake slice. It closes the remaining runtime, durability, concurrency, observability, property-testing, and documentation gaps so the implemented direct multipart intake workflow can be marked complete.

Roadmap task: (4.3.1)
Execplan: docs/execplans/4-3-1-source-and-presenter-profile-intake-script-generation.md

The branch implements the planned completion by wiring the runtime object store, binding idempotency to the authenticated principal, making upload persistence recoverable across object-store writes, making SQLAlchemy idempotency acquisition first-writer-wins under concurrency, and closing the roadmap and guide documentation. Follow-up review feedback is also addressed: object-store tests now assert persisted bytes, source-intake command paths use injectable time and identifier providers, idempotency has an added principal-scoping property, and bounded logs and metrics are emitted at the new decision boundaries. The deliberate scope decision is unchanged: POST /v1/uploads/init remains deferred, while POST /v1/uploads is the shipped direct multipart upload contract.

Review walkthrough

Validation

  • make check-fmt: passed.
  • make markdownlint: passed with zero errors.
  • make nixie: passed; all diagrams validated successfully.
  • make build: passed before the PR-feedback follow-up.
  • make check-migrations: passed before the PR-feedback follow-up; Alembic migration drift check completed.
  • make lint: passed with Hecate, Ruff, and Pylint clean.
  • make typecheck: passed with ty 0.0.32.
  • make test: passed with 870 passed and 2 skipped.
  • Focused PR-feedback regression command for runtime object storage, SQLAlchemy idempotency, and idempotency properties: passed with 7 passed.
  • coderabbit review --agent --type uncommitted: passed for the code milestone with zero findings.
  • coderabbit review --agent --type uncommitted: passed for the documentation milestone with zero findings.
  • coderabbit review --agent --type uncommitted: passed after the final ExecPlan update with zero findings.
  • coderabbit review --agent --type uncommitted: passed after the PR-feedback fixes with zero findings.
  • coderabbit review --agent --type uncommitted: passed after the PR-feedback ExecPlan note with zero findings.

Notes

POST /v1/uploads/init remains explicitly deferred. The shipped contract is direct multipart upload through POST /v1/uploads; a later pre-signed upload flow can extend the same upload and idempotency ports without changing the current public contract.

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 62a6c118-5483-42b8-ba8d-150eaaf64c6a

📥 Commits

Reviewing files that changed from the base of the PR and between 4b55bca and 6a7af03.

📒 Files selected for processing (2)
  • tests/steps/source_intake_support.py
  • tests/test_source_intake_api_contract.py

Roadmap 4.3.1 completion: source-and-presenter-profile intake idempotency and runtime wiring

This PR closes Roadmap item 4.3.1 by completing the source-and-presenter-profile intake slice with idempotent multipart upload handling, object-store runtime wiring, and supporting API extensions.

Core functionality: runtime environment and idempotency

Object-store wiring: The SOURCE_INTAKE_OBJECT_STORE_ROOT environment variable is now required at app startup (episodic/api/runtime.py). When present, it is validated, stored as a pathlib.Path on RuntimeConfig, and wired into ApiDependencies as a FilesystemObjectStore. Startup fails fast with a RuntimeError if the root directory is missing or blank, and _normalize_database_urls() refactors the probe connection to use psycopg connection kwargs directly rather than rendering credentials into a connection string.

Idempotency scoping by principal: The AuthorizationResult dataclass (episodic/api/authorization.py) now carries both an authorisation decision and an optional principal_id extracted from the authenticated context. The idempotency subsystem uses req.context.principal_id (set by AuthorizationMiddleware on permit) rather than client-controlled headers, ensuring idempotency keys are scoped to the authenticated principal. Replay returns 409 Conflict if the same key is reused with a different canonical request body.

Multi-phase upload persistence: register_upload (episodic/canonical/source_intake_service.py) splits persistence into separate unit-of-work transactions: commit a pending UploadRecord row first (ensuring recovery if bytes-to-store fails), write the payload to the object store with optional precomputed SHA-256 validation, then commit a second unit-of-work to mark the upload ready. If the ready-commit fails, a warning is logged, a ready_commit_failed counter is incremented, and the exception is re-raised; the pending row remains for subsequent retry.

SQLAlchemy idempotency with first-writer-wins: SqlAlchemyIdempotencyStore.acquire() (episodic/canonical/storage/source_intake_repositories.py) now captures monotonic-clock start time, fetches any existing idempotency row by logical key, deletes expired rows, and inserts new IN_FLIGHT records via nested transactions with race-condition handling via SQLAlchemy IntegrityError catch-and-refetch. A new fail(record_id) method deletes in-flight records on work failure, allowing immediate client retry. Outcome metrics and structured logging are emitted at all decision boundaries with explicit outcome labels.

API contract extensions

Two new GET endpoints complement the upload/attachment workflow:

  • GET /v1/uploads/{upload_id} returns upload metadata including the computed content_hash formatted as sha256:<hex>, or 404 upload_not_found when missing (UploadResource in episodic/api/resources/source_intake.py).
  • GET /v1/ingestion-jobs/{job_id}/sources lists source attachments with pagination (total count and items), or 404 ingestion_job_not_found when the job is missing (on_get extension to IngestionJobSourcesResource).

Both endpoints are registered in the app (episodic/api/app.py) and fully integrated with the test contract suite.

Code quality: refactoring and consolidation

Large-method extraction:

  • SqlAlchemyIdempotencyStore.acquire() (80 lines) extracted nested-transaction insert-and-recovery logic into _insert_in_flight(), dropping both methods below the 70-line threshold.
  • register_upload() (113 lines) was refactored into a thin orchestrator with three phase helpers: _commit_pending_upload(), _store_upload_bytes(), and _commit_ready_upload().
  • Alembic migration upgrade() was split into five schema-specific helpers (_create_enums(), _upgrade_ingestion_jobs(), _create_uploads_table(), etc.), reducing the orchestrator to seven lines.

Test helper consolidation: Five CodeScene duplication warnings in test files were eliminated by extracting shared BDD setup patterns. In tests/steps/source_intake_support.py, a reusable _SourceIntakeAppConfig parameter object and _run_single_error_call() helper centralise ASGI in-process execution, while _attach_upload_to_new_job() shares profile/job/attachment setup across error-path scenarios.

Testing: property, integration, and contract validation

Property-based tests (tests/test_idempotency_properties.py):

  • Identical principal+key+body replays yield a single Acquired outcome.
  • Identical key with differing body hashes yield Conflict.
  • Same key with different principals yields independent acquisitions.

Integration tests validate idempotency under concurrency (both acquisitions in first-writer-wins semantics), persistence of uploaded bytes under <object_store_root>/uploads/<id>, principal scoping of idempotency, and work-failure cleanup via fail().

Contract-driven tests (tests/test_source_intake_api_contract.py) assert error codes and HTTP statuses for unsupported content types (415), oversized payloads (413), invalid attachment discriminators (422), missing jobs/uploads (404), and not-ready uploads (409).

Documentation and design decisions

The ExecPlan (docs/execplans/4-3-1-source-and-presenter-profile-intake-script-generation.md) marks 4.3.1 as COMPLETE with timestamped post-merge assessment. It documents closure of four runtime/behaviour gaps via commit beb8ea0, confirms the accepted POST /v1/uploads/init deferral, and records deterministic gate and CodeRabbit review evidence.

The developers-guide (docs/developers-guide.md) now documents DATABASE_URL and SOURCE_INTAKE_OBJECT_STORE_ROOT runtime requirements, async psycopg connection normalisation, object-store root validation, and authorisation-context principal scoping for idempotency. It references ADR-015 (docs/adr/adr-015-upload-and-idempotency-ports.md) and updates the SHA-256 worked-vector digest to match implementation.

The users-guide (docs/users-guide.md) documents the end-to-end intake workflow, idempotency-key requirements scoped by authenticated principal, 409 Conflict semantics on body mismatch, and clarifies the shipping REST contract (POST /v1/uploads, POST /v1/ingestion-jobs, POST /v1/ingestion-jobs/{job_id}/sources, GET /v1/ingestion-jobs/{job_id}) while deferring POST /v1/uploads/init for a future object-store adapter.

The roadmap (docs/roadmap.md) expands 4.3.1 with concrete multipart contract details: size limits, content-type allowlists, idempotency keys, content hashes, upload metadata, and object-store wiring.

Observability and injectable dependencies

Structured logs are emitted at decision boundaries (pending-commit, object-stored, ready, ready-commit-failure) with operation labels only (no sensitive principal/key data). Bounded counters and latency metrics use explicit outcome labels. Time and randomness providers (self._runtime.clock() and self._runtime.uuid_factory()) are now injectable throughout command paths via SourceIntakeRuntime, replacing direct calls to dt.datetime.now() and uuid.uuid4() for testability.

Configuration and defaults

The default maximum upload size (ApiDependencies.upload_max_bytes) was increased from 25 MiB to 50 MiB. CI workflows now set CARGO_HTTP_MULTIPLEXING=false and CARGO_NET_RETRY=10 for Rust extension build stability.

Validation and review

All 870 tests pass (2 skipped); formatting, markdown linting, diagram validation, type checking, and linting all pass. CodeRabbit review completed with zero findings after PR-feedback fixes. The worklist tracked eight categories of findings (code health, test coverage, architecture, documentation, property testing, observability, and test consolidation), all addressed in the final state.

Walkthrough

Update roadmap 4.3.1 intake and idempotency guidance, wire request-scoped principals and object-store startup requirements, refactor upload persistence and idempotency storage into staged transactions, and extend the API, storage, and test coverage for uploads and source listings.

Changes

Upload idempotency and intake wiring

Layer / File(s) Summary
Docs, roadmap, execplan and CI updates
docs/developers-guide.md, docs/execplans/4-3-1-source-and-presenter-profile-intake-script-generation.md, docs/roadmap.md, docs/users-guide.md, docs/adr/adr-015-upload-and-idempotency-ports.md, .github/workflows/ci.yml
Update roadmap status to completed, execplan with completion dates and evidence, developer and user guidance on intake workflow and idempotency scoping, correct ADR multipart fingerprint digest, and add CI Cargo network settings for Rust extension builds.
Authorisation result and runtime bootstrap
episodic/api/authorization.py, episodic/api/runtime.py, episodic/api/app.py, episodic/api/dependencies.py
Add AuthorizationResult dataclass with optional principal ID, widen protocol to accept decision or result, store authenticated principal in Falcon request context, require SOURCE_INTAKE_OBJECT_STORE_ROOT and validate at startup, refactor database readiness probe to use psycopg connection kwargs instead of rendered URLs, wire filesystem object store into app dependencies, and increase default upload max bytes from 25 to 50 MiB.
Multipart parsing, digest computation and intake resources
episodic/api/source_intake_support.py, episodic/api/resources/source_intake.py, episodic/api/resources/__init__.py, episodic/api/source_idempotency.py
Stream multipart uploads in bounded chunks, compute SHA-256 digest during upload parsing, expose computed digest in ParsedUpload, refactor upload idempotency to use precomputed payload hash, add UploadResource GET endpoint for upload metadata lookup, extend IngestionJobSourcesResource with GET endpoint for paginated source listing, derive principal ID from request context instead of header, catch work failures and clean up in-flight idempotency records, enforce 64 KiB limit on encoded idempotency outcome payloads.
Canonical intake contracts, types, errors and runtime
episodic/canonical/source_intake_types.py, episodic/canonical/source_intake_errors.py, episodic/canonical/source_intake_runtime.py, episodic/canonical/object_store.py, episodic/canonical/idempotency_service.py, episodic/canonical/upload_protocols.py
Add request dataclasses (UploadBytesRequest, CreateIngestionJobRequest, AttachSourceRequest) and pagination response shapes (IngestionJobPage, IngestionJobSourcePage), add domain exception types for missing series profile/job/upload and upload validation failures, introduce SourceIntakeRuntime bundling clock, UUID factory, metrics and monotonic clock with production defaults, extend ObjectStorePort.put with optional precomputed_sha256 parameter, remove multipart hash worked-vector special-case, add fail method to IdempotencyStore protocol.
Service orchestration and two-phase uploads
episodic/canonical/source_intake_service.py
Refactor register_upload to accept uow_factory and optional runtime providers, compute payload SHA-256 upfront for validation, implement two-transaction pattern (pending commit, store bytes, ready commit), add failure recovery logging and metrics for ready-commit phase, update create_ingestion_job and attach_source_to_ingestion_job to use runtime-derived UUIDs and timestamps, add get_upload lookup with UploadNotFoundError, add list_ingestion_job_sources with pagination and total count, update _validate_declared_upload to accept precomputed digest.
Filesystem storage, idempotency persistence and migrations
episodic/canonical/storage/filesystem_object_store.py, episodic/canonical/storage/source_intake_repositories.py, episodic/canonical/storage/uow.py, episodic/canonical/storage/source_intake_models.py, episodic/canonical/storage/entity_models.py, alembic/versions/20260610_000009_add_source_intake_tables.py
Extend FilesystemObjectStore.put to skip digest computation when precomputed_sha256 is supplied, implement SourceIntakeStorageRuntime with injected clock/UUID/metrics/monotonic clock, rewrite idempotency store acquire to handle concurrent races via nested transactions and IntegrityError catching, delete expired rows and remap record state to outcome codes, add fail(record_id) to delete in-flight records for immediate retry, wire idempotency runtime through unit-of-work initialisation, add database indexes on (series_profile_id, intake_state, created_at DESC) and (state, created_at), refactor migration into helper-based enum/table/index creation and deletion orchestration.
BDD scaffolding, fixtures and test infrastructure
tests/features/source_intake.feature, tests/fixtures/api.py, tests/steps/source_intake_api_helpers.py, tests/steps/test_http_service_scaffold_steps.py
Add Gherkin scenarios for upload validation failures (content type, size, hash), source attachment errors (discriminator, missing job/upload, not ready), and source listing success; extend API dependency fixture to accept upload_max_bytes override; add HTTP scaffold to wire object-store root via SOURCE_INTAKE_OBJECT_STORE_ROOT environment variable; implement async HTTP helpers for posting uploads with idempotency headers, creating profiles/jobs, persisting pending uploads, and building attachment payloads.
BDD intake workflow and step delegation
tests/steps/source_intake_support.py, tests/steps/test_source_intake_steps.py
Introduce shared SourceIntakeContext dataclass for cross-step state, implement centralised ASGI test runner with app dependency wiring and object-store configuration, add support module step functions for happy-path workflow and multiple error scenarios, refactor step module to delegate to support module helpers and remove duplicated implementation, update assertion steps to validate error envelope structure (error_status, error_code) and source list pagination and upload ID matching.
Runtime environment and contract test coverage
tests/test_env_runtime_wiring.py, tests/test_source_intake_api_contract.py, tests/test_idempotency_service.py
Add tests verifying SOURCE_INTAKE_OBJECT_STORE_ROOT requirement raises RuntimeError when missing, test database URL rendering hides passwords whilst probe kwargs retain them and respect query-port overrides, add end-to-end test uploading to POST /v1/uploads and verifying bytes persist on disk under object store, add contract tests for upload error codes (415 unsupported type, 413 oversized payload), source attachment validation (422 unknown discriminator, 404 missing job/upload, 409 not-ready upload), ingestion job creation (404 missing series profile), and read endpoints (GET /v1/uploads/{upload_id}, GET /v1/ingestion-jobs/{job_id}/sources) including not-found handling and pagination validation; update hash worked vector test assertion.
Storage and property-based tests
tests/canonical_storage/test_source_intake_repositories.py, tests/canonical_storage/test_source_intake_transition_properties.py, tests/test_filesystem_object_store.py
Add deterministic idempotency-store test with injected clock and UUID factory verifying record ID and timestamp consistency, add concurrent-acquire test validating first-writer-wins with exactly one Acquired outcome, add expired-record re-acquire test confirming old record is deleted and new one inserted, add ingestion job state transition concurrency property test using Hypothesis to verify at-most-once semantics, test filesystem object store precomputed-hash path by monkeypatching digest to raise and verifying returned metadata uses supplied digest.
Principal-scoped idempotency and service resilience
tests/test_source_intake_api.py, tests/test_source_intake_service.py, tests/test_idempotency_properties.py
Add integration test verifying idempotency scope by principal (same key/payload under different principals yields different upload IDs, replay under original principal reuses ID), add tests for idempotent response failure handling (work failure triggers in-flight record deletion and allows immediate retry), test outcome encoding rejects payloads exceeding 64 KiB, add service tests for two-transaction upload registration verifying pending row survives ready-commit failure with stored payload intact, test precomputed hash validation raises UploadHashMismatchError on mismatch, add property test confirming idempotency scoping by authenticated principal generates distinct record IDs.

Possibly related issues

Possibly related PRs

  • leynos/episodic#131: Shares documentation and execplan updates for intake idempotency and completion semantics.
  • leynos/episodic#105: Relates to the /v1 route prefix pattern used in registering the upload resource endpoint.

Suggested labels

Roadmap

Poem

A principal now steers the gate,
And uploads write their fate in state.
The hash is known before it flies,
The store and runtime synchronise.
From pending to ready, the bytes alight ✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 4-3-1-complete-source-and-presenter-profile-intake-script-generation

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @LodyAI[bot], you have reached your weekly rate limit of 2500000 diff characters.

Please try again later or upgrade to continue using Sourcery

codescene-delta-analysis[bot]

This comment was marked as outdated.

@lodyai
lodyai Bot marked this pull request as ready for review June 12, 2026 09:46
@coderabbitai coderabbitai Bot added the Roadmap label Jun 12, 2026
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos

leynos commented Jun 12, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

episodic/canonical/storage/source_intake_repositories.py

Comment on file

        request: IdempotencyAcquireRequest,
    ) -> IdempotencyOutcome:
        """Acquire or inspect an idempotency record."""
        started_at = self._runtime.monotonic_clock.monotonic_seconds()

❌ New issue: Large Method
SqlAlchemyIdempotencyStore.acquire has 80 lines, threshold = 70

@leynos

leynos commented Jun 12, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

episodic/canonical/source_intake_service.py

Comment on file

async def register_upload(
    uow: CanonicalUnitOfWork,
    uow_factory: UowFactory,

❌ New issue: Large Method
register_upload has 113 lines, threshold = 70

@leynos

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== tests/test_env_runtime_wiring.py ==="
cat tests/test_env_runtime_wiring.py

echo ""
echo "=== episodic/canonical/source_intake_service.py ==="
cat episodic/canonical/source_intake_service.py

echo ""
echo "=== episodic/canonical/storage/source_intake_repositories.py (acquire method area) ==="
rg -n "datetime|uuid4|monotonic|acquire|clock|id_factory" episodic/canonical/storage/source_intake_repositories.py -A2 -B2

echo ""
echo "=== docs/users-guide.md (source-to-script section) ==="
rg -n "principal|409|Conflict|idempoten|scoped|authoris" docs/users-guide.md -A3 -B3

echo ""
echo "=== Hypothesis / property-based tests ==="
rg -rn "`@given`\|hypothesis\|from hypothesis" tests/

echo ""
echo "=== Observability: logger/metrics in service and repositories ==="
rg -n "logger\|logging\|structlog\|metrics\|span\|trace\|instrument" episodic/canonical/source_intake_service.py episodic/canonical/storage/source_intake_repositories.py episodic/api/runtime.py

Length of output: 27340


🏁 Script executed:

#!/bin/bash
echo "=== episodic/api/runtime.py ==="
cat episodic/api/runtime.py

echo ""
echo "=== Property-based tests (full scan) ==="
rg -rn "given|hypothesis" tests/ --type py -i

Length of output: 18142


🏁 Script executed:

#!/bin/bash
echo "=== tests/test_idempotency_properties.py ==="
cat tests/test_idempotency_properties.py

echo ""
echo "=== git log for test_idempotency_properties.py ==="
git log --oneline tests/test_idempotency_properties.py | head -10

Length of output: 7385



@leynos All five checks are now resolved. Here is a full status breakdown.


✅ Testing (Overall) — Resolved

test_create_app_from_env_wires_object_store_for_uploads now asserts file presence and byte-for-byte content:

stored_path = object_store_root / "uploads" / response_body["id"]
assert response_body["content_hash"] == f"sha256:{expected_hash}"
assert stored_path.is_file(), f"expected upload payload at {stored_path}"
assert stored_path.read_bytes() == payload

The oracle on FilesystemObjectStore output is fully in place.


✅ Unit Architecture — Resolved

Both command paths now use injectable abstractions instead of bare calls:

Symbol Abstraction
register_upload SourceIntakeRuntime.clock / .uuid_factory via _source_intake_runtime(runtime)
SqlAlchemyIdempotencyStore.acquire SourceIntakeStorageRuntime.clock / .uuid_factory via self._runtime

dt.datetime.now and uuid.uuid4 are isolated behind _utc_now / _new_uuid factory functions and only reach production code through the defaults in _source_intake_runtime / source_intake_storage_runtime.


✅ User-Facing Documentation — Resolved

docs/users-guide.md now states:

Idempotency-Key; the server scopes that key by the authenticated principal from authorisation. Repeated requests from the same principal with the same key and canonical body replay the stored response. Reusing the same key with a different canonical body returns 409 Conflict.

Both principal-scoping and the conflict behaviour are documented.


✅ Testing (Property / Proof) — Resolved

tests/test_idempotency_properties.py (added in commit 3a8bd57) contains three @given-decorated Hypothesis property tests:

Test Invariant
test_identical_body_for_key_replays_one_completed_resource Same principal + key + body → Replay; exactly one record
test_different_body_for_key_conflicts Same principal + key, differing body → Conflict; one record
test_same_key_is_scoped_by_authenticated_principal Same key + body across distinct principals → two independent Acquired outcomes

Upload-persistence failure invariants remain covered only by example-based tests (test_register_upload_keeps_pending_row_when_ready_commit_fails), but that is an edge-case invariant already well-exercised by the example test and not a gate blocker.


✅ Observability — Resolved

episodic/api/runtime.py now emits structured log entries at every relevant boundary:

log_warning(logger, "runtime_config_missing setting=%s", "SOURCE_INTAKE_OBJECT_STORE_ROOT")
# ... RuntimeError raised
log_info(logger, "runtime_config_loaded source_intake_object_store_root=%s", object_store_root)

episodic/canonical/source_intake_service.py emits log_info / log_warning and metrics.increment_counter / metrics.observe_latency_ms at pending_committed, object_stored, ready_commit_failed, and ready boundaries. SqlAlchemyIdempotencyStore does the same via _record_acquire_metrics with outcome labels.


All originally failing checks are addressed. No further agent prompts are required for this PR.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@lodyai

lodyai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos

leynos commented Jun 12, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

Excess Number of Function Arguments

episodic/canonical/source_intake_service.py: _store_upload_bytes

What lead to degradation?

_store_upload_bytes has 5 arguments, max arguments = 4

Why does this problem occur?

Functions with many arguments indicate either a) low cohesion where the function has too many responsibilities, or b) a missing abstraction that encapsulates those arguments.
The threshold for the Python language is 4 function arguments.

How to fix it?

Start by investigating the responsibilities of the function. Make sure it doesn't do too many things, in which case it should be split into smaller and more cohesive functions. Consider the refactoring INTRODUCE PARAMETER OBJECT to encapsulate arguments that refer to the same logical concept.

Excess Number of Function Arguments

episodic/canonical/source_intake_service.py: _commit_ready_upload

What lead to degradation?

_commit_ready_upload has 6 arguments, max arguments = 4

Why does this problem occur?

Functions with many arguments indicate either a) low cohesion where the function has too many responsibilities, or b) a missing abstraction that encapsulates those arguments.
The threshold for the Python language is 4 function arguments.

How to fix it?

Start by investigating the responsibilities of the function. Make sure it doesn't do too many things, in which case it should be split into smaller and more cohesive functions. Consider the refactoring INTRODUCE PARAMETER OBJECT to encapsulate arguments that refer to the same logical concept.

@coderabbitai

This comment was marked as resolved.

@leynos

leynos commented Jun 12, 2026

Copy link
Copy Markdown
Owner

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Reviews resumed.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos

leynos commented Jun 12, 2026

Copy link
Copy Markdown
Owner

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@leynos

leynos commented Jun 12, 2026

Copy link
Copy Markdown
Owner

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@pandalump

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos

leynos commented Jun 12, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

episodic/api/runtime.py

Comment on lines +150 to +154

def _psycopg_connection_kwargs(url: URL) -> PsycopgConnectKwargs:
    """Return Psycopg connection kwargs without rendering secrets into a URL."""
    connection_kwargs = url.translate_connect_args(
        username="user",
        database="dbname",

❌ New issue: Bumpy Road Ahead
_psycopg_connection_kwargs has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is 2 blocks per function

@leynos

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

codescene-delta-analysis[bot]

This comment was marked as outdated.

Introduce a private app configuration parameter object for source-intake
BDD helper calls so `_run_source_intake_call` stays within the argument
threshold while preserving the existing ASGI dependency wiring.
@lodyai

lodyai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos

leynos commented Jun 13, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Ensure that this is validated against the current version of the codegraph.

If further refinement to address this finding would be deleterious, please supply a clear explanatory one to two paragraph markdown message I can paste into the CodeScene web ui's diagnostic suppression function so this diagnostic can be silenced.

tests/steps/source_intake_support.py

Comment on lines +77 to +100

def upload_unsupported_content_type(
    context: SourceIntakeContext,
    session_factory: async_sessionmaker[AsyncSession],
    tmp_path: Path,
) -> None:
    """Upload source material whose declared content type is not allowlisted."""

    async def _action(
        client: httpx.AsyncClient,
        scenario_context: SourceIntakeContext,
    ) -> None:
        response = await post_text_upload(
            client,
            key="bdd-unsupported-content-type",
            payload=b"source\n",
            content_type="application/octet-stream",
        )
        record_error_response(response, scenario_context)

    _run_source_intake_call(
        context,
        _SourceIntakeAppConfig(session_factory=session_factory, tmp_path=tmp_path),
        _action,
    )

❌ New issue: Code Duplication
The module contains 6 functions with similar structure: attach_missing_upload,attach_pending_upload,attach_to_missing_job,attach_unknown_source_discriminator and 2 more functions

@coderabbitai

This comment was marked as resolved.

Extract the shared single-error-call wrapper used by source-intake BDD
steps so the public helpers only describe the request they exercise while
centralising error recording and app invocation.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos

leynos commented Jun 13, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

tests/steps/source_intake_support.py

Comment on lines +77 to +102

def _run_single_error_call(
    context: SourceIntakeContext,
    session_factory: async_sessionmaker[AsyncSession],
    tmp_path: Path,
    request_fn: cabc.Callable[[httpx.AsyncClient], cabc.Awaitable[httpx.Response]],
    *,
    upload_max_bytes: int | None = None,
) -> None:
    """Run a single HTTP action, recording the error response into context."""

    async def _action(
        client: httpx.AsyncClient,
        scenario_context: SourceIntakeContext,
    ) -> None:
        response = await request_fn(client)
        record_error_response(response, scenario_context)

    _run_source_intake_call(
        context,
        _SourceIntakeAppConfig(
            session_factory=session_factory,
            tmp_path=tmp_path,
            upload_max_bytes=upload_max_bytes,
        ),
        _action,
    )

❌ New issue: Excess Number of Function Arguments
_run_single_error_call has 5 arguments, max arguments = 4

@coderabbitai

This comment was marked as resolved.

Route all single-error source-intake BDD wrappers through the shared
helper so response recording and app invocation live in one place while
preserving the existing request-specific closures.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos

leynos commented Jun 13, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Ensure that this is validated against the current version of the codegraph.

If further refinement to address this finding would be deleterious, please supply a clear explanatory one to two paragraph markdown message I can paste into the CodeScene web ui's diagnostic suppression function so this diagnostic can be silenced.

tests/steps/source_intake_support.py

Comment on lines +176 to +196

def attach_missing_upload(
    context: SourceIntakeContext,
    session_factory: async_sessionmaker[AsyncSession],
    tmp_path: Path,
) -> None:
    """Attach an unknown upload to a known ingestion job."""

    async def _request(client: httpx.AsyncClient) -> httpx.Response:
        profile_id = await create_series_profile(client)
        job_id = await create_ingestion_job(client, profile_id)
        return await client.post(
            f"/v1/ingestion-jobs/{job_id}/sources",
            headers={"Idempotency-Key": "bdd-missing-upload"},
            json=upload_payload(str(uuid.uuid4())),
        )

    _run_single_error_call(
        context,
        _SourceIntakeAppConfig(session_factory=session_factory, tmp_path=tmp_path),
        _request,
    )

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: attach_missing_upload,attach_pending_upload

@leynos

leynos commented Jun 13, 2026

Copy link
Copy Markdown
Owner

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Reviews resumed.

@coderabbitai

This comment was marked as resolved.

@coderabbitai coderabbitai Bot added Issue and removed Roadmap labels Jun 13, 2026
@leynos

leynos commented Jun 14, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

Code Duplication

tests/steps/source_intake_support.py:

What lead to degradation?

The module contains 2 functions with similar structure: attach_missing_upload,attach_pending_upload

Why does this problem occur?

Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health.

How to fix it?

A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. Read More
Once you have identified the similarities across functions, look to extract and encapsulate the concept that varies into its own function(s). These shared abstractions can then be re-used, which minimizes the amount of duplication and simplifies change.

@leynos

leynos commented Jun 14, 2026

Copy link
Copy Markdown
Owner

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

Code Duplication

tests/test_source_intake_api_contract.py:

What lead to degradation?

The module contains 3 functions with similar structure: test_attach_source_reports_missing_ingestion_job,test_attach_upload_reports_missing_upload,test_attach_upload_reports_not_ready_upload

Why does this problem occur?

Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health.

How to fix it?

A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. Read More
Once you have identified the similarities across functions, look to extract and encapsulate the concept that varies into its own function(s). These shared abstractions can then be re-used, which minimizes the amount of duplication and simplifies change.

@coderabbitai

This comment was marked as resolved.

Share the profile, ingestion job, and upload attachment request setup between
BDD error-path helpers while keeping the caller-specific upload id sources
explicit.
@coderabbitai

This comment was marked as resolved.

codescene-delta-analysis[bot]

This comment was marked as outdated.

Share the repeated source attachment POST call in API contract tests while
leaving each scenario's setup and assertions explicit.
@coderabbitai coderabbitai Bot added Roadmap and removed Issue labels Jun 14, 2026
@leynos
leynos merged commit 40d6b79 into main Jun 14, 2026
5 checks passed
@leynos
leynos deleted the 4-3-1-complete-source-and-presenter-profile-intake-script-generation branch June 14, 2026 03:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants