feat(run): C2.0 — shippable step-7 run entrance + K2.9 seam bindings (closes C-N2, R-10, K2.9)
- run.py: compose_run_context (§5: merge inbox -> seed -> fold, read-only on the inbox) + execute_run (§8 meter, artifacts persisted on BOTH outcomes, structured exit 3 on budget stop) + thin CLI (python -m ..run). The model client is injected; only default_client_factory constructs the SDK client (wired, never executed by the suite). The navigated docs dir comes from the validated startup contract (resolves review OBS-2 on the shippable path; run_s10.py stays byte-frozen fasit -> won't-fix there). - test_run_entrance_loadbearing.py: inbox verdict reaches the composed context (detach-proven: merge dropped -> red), empty/missing-inbox controls, read-only inbox byte-proof, R-10 budget-stop binding via the NEW entrance (detach-proven: stop persistence dropped -> red), happy path through the CLI with the inbox signal surviving the chain, SDK-wiring test. - test_ingest_adoption.py (K2.9): the two library guarantees the consumer relies on, bound through the seam — empty CSV -> typed SourceError with NO partial bundle on disk; non-SELECT SQL -> SourceError 'returned no columns' (behavior verified empirically against pin dae0bd1a before binding). - README: inbox section now points at the shippable entrance; run.py added to the run layer; stale test count 265 -> 395. 386 -> 395 tests, full gate green (pytest, ruff check+format, mypy strict); goldens unchanged; runs/s10 and run_s10.py untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
5732d13369
commit
3587854074
4 changed files with 448 additions and 3 deletions
|
|
@ -99,3 +99,60 @@ class TestErrorContract:
|
|||
ingest.SourceError,
|
||||
):
|
||||
assert issubclass(exc, ingest.IngestError)
|
||||
|
||||
|
||||
class TestLibraryGuarantees:
|
||||
"""K2.9 (R-3): library-side guarantees this consumer relies on, bound at the seam.
|
||||
|
||||
Pre-adoption the local connector crashed mid-materialization on an empty
|
||||
CSV and left a PARTIAL bundle on disk (run-proven R-3). The library stages
|
||||
in memory, so the typed ``SourceError`` fires BEFORE the disk phase. Bound
|
||||
THROUGH the consumer entry point so a pin bump can never silently regress
|
||||
either guarantee.
|
||||
"""
|
||||
|
||||
def test_empty_csv_fails_typed_before_any_disk_write(self, tmp_path: Path) -> None:
|
||||
case = tmp_path / "case"
|
||||
fixture = case / "fixture"
|
||||
fixture.mkdir(parents=True)
|
||||
(fixture / "e.csv").write_text("", encoding="utf-8")
|
||||
manifest = {
|
||||
"manifest_version": 1,
|
||||
"source": {"type": "file", "id": "arkiv", "root": "fixture"},
|
||||
"bundle_summary": "s",
|
||||
"extractions": [
|
||||
{"id": "e", "title": "T", "query": "e.csv", "okf_type": "dataset", "max_rows": 5}
|
||||
],
|
||||
}
|
||||
manifest_path = case / "manifest.json"
|
||||
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
|
||||
bundle = tmp_path / "bundle"
|
||||
with pytest.raises(ingest.SourceError):
|
||||
ingest.materialize(manifest_path, bundle, INGESTED_AT)
|
||||
assert not bundle.exists() # never a partial bundle (in-memory staging)
|
||||
|
||||
def test_non_select_sql_fails_typed_with_no_partial_bundle(
|
||||
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
import sqlite3
|
||||
|
||||
db = tmp_path / "src.sqlite"
|
||||
con = sqlite3.connect(db)
|
||||
con.execute("CREATE TABLE t (a INTEGER)")
|
||||
con.commit()
|
||||
con.close()
|
||||
monkeypatch.setenv("SRC_DSN", str(db))
|
||||
manifest = {
|
||||
"manifest_version": 1,
|
||||
"source": {"type": "sql", "id": "db", "connection_ref": "SRC_DSN"},
|
||||
"bundle_summary": "s",
|
||||
"extractions": [
|
||||
{"id": "e", "title": "T", "query": "BEGIN", "okf_type": "dataset", "max_rows": 5}
|
||||
],
|
||||
}
|
||||
manifest_path = tmp_path / "manifest.json"
|
||||
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
|
||||
bundle = tmp_path / "bundle"
|
||||
with pytest.raises(ingest.SourceError, match="returned no columns"):
|
||||
ingest.materialize(manifest_path, bundle, INGESTED_AT)
|
||||
assert not bundle.exists()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue