refactor(ingest): move single-source ingest orchestration into an application service - #1167
Merged
Merged
Conversation
… the service extraction
…est-application-service spec
2 tasks done
Owner
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Moves the single-source ingest orchestration out of the CLI adapter into
application/ingest_service.py, so MVP 4's folder watch can call an in-process ingest with a root and get an outcome back instead of shelling out.ingest_source(root, src, policy, *, ports, observer, confirm) -> IngestOutcometakes an explicit workspace root. It never reads the cwd, prompts, renders, inspectssys.stdin, or raisestyper.Exit.IngestUnchangedoutcome (vsIngestWritten).IngestRefusedsubclass carrying the exact user-facing message:SourceNotReadable,NotAWorkspace,RawImmutabilityRefused,InconsistentWorkspace,SymlinkedDestination(bug: ingest writes through a symlinked bundle/<dir>, and query/reindex read symlinked concept files — the #937 guard covers only forget/get #1126, before any write),SourceCheckFailed,PreparationFailed,WriteFailed,DriftDetected,ConfirmationDeclined,ConfirmationUnavailable.cli/main.py::_ingest_singlestays as a thin adapter (used byingestand_ingest_batch): it resolves the cwd, builds the ports from its own seams, asks the prompt when a TTY is present, renders the observer callbacks, and maps refusals to the same exit codes (3 for drift, 1 for the rest, Typer's abort for a declined prompt). Batch semantics, including the non-TTY--autorule and the cost gate, are untouched and stay in the CLI.chat_client,autocommit,after_commit,snapshot_read,clock) are the adapter's own functions, so every existing monkeypatch seam (OllamaClient,_autocommit,_embed_client,_snapshot_read,datetime,Console) still intercepts and the layering guard (nocli/typer/rich/vcsinapplication/) holds.application/ingest.py, and the drift decision to a pureapplication/drift.py::describe_driftthat_reject_drifted_targetsnow wraps.Why a new module rather than growing
application/ingest.py: that module is the effect-free staging and composition core (1.6k lines); this one sequences it with effects. No new ADR: ADR-0018 already decided the shape.No behaviour change and no CHANGELOG entry (nothing user-visible).
Related issue
Closes #1138
Type of change
feat— new featurefix— bug fixdocs— documentation onlyrefactor— no behavior changetest— tests onlychore/ci— tooling, build, or CIHow was this tested?
tests/unit/cli/test_ingest_characterization.py(interactive confirm and decline, non-TTY refusal, regenerate preview, drift onindex.mdandlog.md, missing source, not a workspace, inconsistent workspace, disambiguated destination, malformed config, batch auto, batch non-TTY, batch with a refused file). They were recorded on the tree before the move and pass unchanged after it.tests/unit/application/test_ingest_service.py) run from outside the workspace: outcome variants, every typed refusal, the confirmation matrix, drift via the snapshot port, symlink refusal, commit-then-embed ordering.test_ingest_service_seams.py) for the names that moved offcli.main.test_ingest.py,test_ingest_crash_window.pyand the rest of the suite pass unchanged. Gates:ruff check,ruff format --check,mypy ., fullpytest.Checklist
openspec/specs/ingest-application-service/spec.md).ruff,mypy,pytest).Notes for reviewers
mainbrought in fix(bundle): refuse to read or write through a symlinked bundle path #1159; its two symlink checks now sit inside the service at the same points (before extraction, and after staging chose the derived paths), both before any write.--autorule,_autocommit,_embed_after_ingestand_refresh_derived_after_write(the service reaches them through ports), the spinner and all output wording for the preview and staged-object notices.BackendUnavailableamong the refusals; in the code a backend failure degrades to a Source-only run and never refuses, so there is no such refusal to type.reindex, findings and the merge/reconcile cores, and splittingtest_ingest.py.