diff --git a/docs/reference.md b/docs/reference.md index a228443..ed17613 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -1554,7 +1554,7 @@ Recommended layout: - `cache/ncbi/download_plan.tsv`: `record_id`, `normalized_id`, `assembly_accession`, `expected_genome_path`, `datasets_zip_path`, `download_dir`, `status`, `notes` - `bounded_download_smoke_plan.tsv`: isolated subset of `cache/ncbi/download_plan.tsv` for a separately authorized bounded NCBI download smoke. Fields are the `cache/ncbi/download_plan.tsv` fields plus `assembly_level`, `refseq_category`, and `quality_tier`; every row has `status=planned`. `download-smoke inspect` also accepts older bounded smoke plans whose field order matches only `cache/ncbi/download_plan.tsv`. - `bounded_download_smoke_commands.tsv`: complete command handoff manifest written by `download-smoke prepare --write`, with one row per bounded plan row. Fields are `record_id`, `assembly_accession`, `assembly_level`, `refseq_category`, `quality_tier`, `datasets_zip_path`, and `command_json`; `command_json` is a compact JSON array for the bounded `datasets download genome accession ... --include genome --filename ...` command. This file is for operator or AI-controller inspection only; it does not run `datasets`, authorize downloads, contact providers, mutate manifests, or create strict deliverables. -- `bounded_download_smoke_summary.json`: read-only JSON summary for `download-smoke prepare`. Stable fields include `schema_version`, `command`, `source_download_plan_path`, `limit`, `requested_quality_tier`, `resolved_quality_tier`, `quality_tier`, `selected_row_count`, `selected_high_quality_row_count`, `selected_quality_tier_counts`, `selected_assembly_level_counts`, `selected_refseq_category_counts`, `selected_accession_quality_preview`, `selected_accession_quality_preview_truncated`, `selected_datasets_command_preview`, `selected_datasets_command_preview_truncated`, `selected_datasets_command_preview_only`, `inspection_min_fasta_n50_bases`, `inspection_max_fasta_record_count`, `inspection_max_fasta_ambiguous_bases`, `inspection_min_fasta_total_bases`, `inspection_min_fasta_longest_record_bases`, `inspection_quality_profile`, `inspection_block_fragmented_fasta`, `inspection_block_fasta_header_keywords`, `recommended_inspection_request_target`, `recommended_inspection_request`, `recommended_inspection_next_command`, `recommended_inspection_command`, `handoff_checklist`, `source_planned_row_count`, `source_high_quality_planned_row_count`, `source_draft_or_fragmented_planned_row_count`, `source_unknown_assembly_level_planned_row_count`, `source_refseq_category_counts`, `source_total_rows`, `ready`, `blockers`, `execution_boundary`, `safe_for_unattended_download`, `downloads_triggered`, `providers_contacted`, `network_access`, `external_tools`, `manifest_mutated`, and `strict_scientific_deliverable`. `selected_high_quality_row_count`, `selected_quality_tier_counts`, and `selected_refseq_category_counts` count only selected rows; source-level high-quality and RefSeq-category availability are reported separately. `selected_accession_quality_preview` includes selected accessions, normalized assembly levels, normalized RefSeq category labels, and quality tiers. `selected_datasets_command_preview` lists at most five bounded `datasets download genome accession ... --include genome --filename ...` command arrays for operator inspection; it is a preview only and does not run `datasets`. `handoff_checklist` marks bounded datasets execution and final genome acceptance as separate approval/review steps. The `recommended_inspection_*` fields are empty until `prepare --write` has produced an isolated bounded plan, then point to `download-smoke inspect` with that plan path and an operator-chosen isolated inspection output directory. Controllers can pass `recommended_inspection_request` to `commands render`; `recommended_inspection_command` remains a compatibility argv list for operator inspection. The default `inspection_quality_profile=fragmentation` carries `--quality-profile fragmentation` into that recommended handoff, enabling local blockers for fragmented FASTA signals and WGS/scaffold/contig header keywords during the later inspection; `--inspection-quality-profile none` leaves those profile gates disabled unless explicit gate flags are supplied. The `inspection_*` fields only carry optional local inspection quality gates into that recommended handoff; prepare does not inspect ZIPs, run downloads, contact providers, or decide final genome usability. `quality_tier` is the resolved effective tier. `--quality-tier recommended` is the default and resolves the readiness recommendation to `high`, `all`, or `none`; `--quality-tier high` selects only planned Complete Genome or Chromosome rows; Scaffold and Contig remain valid planned rows when `all` is selected explicitly or when `recommended` falls back to `all`. +- `bounded_download_smoke_summary.json`: read-only JSON summary for `download-smoke prepare`. Stable fields include `schema_version`, `command`, `source_download_plan_path`, `limit`, `requested_quality_tier`, `resolved_quality_tier`, `quality_tier`, `selected_row_count`, `selected_high_quality_row_count`, `selected_quality_tier_counts`, `selected_assembly_level_counts`, `selected_refseq_category_counts`, `selected_accession_quality_preview`, `selected_accession_quality_preview_truncated`, `selected_datasets_command_preview`, `selected_datasets_command_preview_truncated`, `selected_datasets_command_preview_only`, `inspection_min_fasta_n50_bases`, `inspection_max_fasta_record_count`, `inspection_max_fasta_ambiguous_bases`, `inspection_min_fasta_total_bases`, `inspection_min_fasta_longest_record_bases`, `inspection_quality_profile`, `inspection_block_fragmented_fasta`, `inspection_block_fasta_header_keywords`, `recommended_inspection_request_target`, `recommended_inspection_request`, `recommended_inspection_next_command`, `recommended_inspection_command`, `handoff_checklist`, `source_planned_row_count`, `source_high_quality_planned_row_count`, `source_draft_or_fragmented_planned_row_count`, `source_unknown_assembly_level_planned_row_count`, `source_refseq_category_counts`, `source_total_rows`, `ready`, `blockers`, `execution_boundary`, `safe_for_unattended_download`, `downloads_triggered`, `providers_contacted`, `network_access`, `external_tools`, `manifest_mutated`, and `strict_scientific_deliverable`. `selected_high_quality_row_count`, `selected_quality_tier_counts`, and `selected_refseq_category_counts` count only selected rows; source-level high-quality and RefSeq-category availability are reported separately. `selected_accession_quality_preview` includes selected accessions, normalized assembly levels, normalized RefSeq category labels, and quality tiers. `selected_datasets_command_preview` lists at most five bounded `datasets download genome accession ... --include genome --filename ...` command arrays for operator inspection; it is a preview only and does not run `datasets`. `handoff_checklist` marks bounded datasets execution and final genome acceptance as separate approval/review steps. The `recommended_inspection_*` fields are empty until `prepare --write` has produced an isolated bounded plan, then point to `download-smoke inspect` with that plan path and the deterministic sibling `inspection/` output directory. Controllers can pass `recommended_inspection_request` to `commands render`; `recommended_inspection_command` remains a compatibility argv list for operator inspection. The default `inspection_quality_profile=fragmentation` carries `--quality-profile fragmentation` into that recommended handoff, enabling local blockers for fragmented FASTA signals and WGS/scaffold/contig header keywords during the later inspection; `--inspection-quality-profile none` leaves those profile gates disabled unless explicit gate flags are supplied. The `inspection_*` fields only carry optional local inspection quality gates into that recommended handoff; prepare does not inspect ZIPs, run downloads, contact providers, or decide final genome usability. `quality_tier` is the resolved effective tier. `--quality-tier recommended` is the default and resolves the readiness recommendation to `high`, `all`, or `none`; `--quality-tier high` selects only planned Complete Genome or Chromosome rows; Scaffold and Contig remain valid planned rows when `all` is selected explicitly or when `recommended` falls back to `all`. - `bounded_download_smoke_execution.tsv`: bounded execution audit rows for `download-smoke execute`. Fields are `record_id`, `assembly_accession`, `assembly_level`, `refseq_category`, `quality_tier`, `datasets_zip_path`, `command_json`, `command_valid`, `executed`, `returncode`, `status`, and `notes`. A row is executed only when `command_json` exactly matches the bounded datasets command contract and `--execute` is supplied; otherwise the command is validation-only. Execution success means a ZIP is ready for `download-smoke inspect`, not that a genome is final-use accepted. - `bounded_download_smoke_execution_summary.json`: read-only JSON summary for `download-smoke execute`. Stable fields include `schema_version`, `command`, `source_commands_manifest_path`, `limit`, `selected_row_count`, `command_valid_count`, `command_invalid_count`, `execute_requested`, `executed_command_count`, `datasets_zip_ready_for_inspection_count`, `status_counts`, `ready`, `blockers`, `execution_boundary`, `safe_for_unattended_download`, `recommended_inspection_request_target`, `recommended_inspection_request`, `recommended_inspection_request_blockers`, `recommended_inspection_next_command`, `recommended_inspection_command`, `downloads_triggered`, `providers_contacted`, `network_access`, `external_tools`, `manifest_mutated`, `strict_scientific_deliverable`, and legacy `recommended_next_command`. When execution audit outputs are written and the sibling `bounded_download_smoke_plan.tsv` is present, the structured `recommended_inspection_*` fields point to the matching local `download-smoke inspect` handoff. If the sibling plan is absent, the request fields stay empty and `recommended_inspection_request_blockers` includes `bounded_download_smoke_plan_missing`. Dry-run validation leaves `downloads_triggered=0`, `network_access=false`, and `external_tools=false`. Explicit execution may set those fields when commands run, but it still writes only isolated audit outputs and requires later ZIP/FASTA inspection before any bounded-smoke quality decision. `--download-smoke-execution-dir ` is an explicit read-only report/package surface for these two files. Report-only mode may show `## Bounded Download Smoke Execution`; `package-results --include reports` and `--include all` may copy validated members under `download_smoke/` with `evidence_policy=download_smoke_execution_audit` and `strict_scientific_deliverable=false`. Missing input is omitted; partial or malformed input copies only valid members and emits a compact warning. Failed-handoff packages exclude the execution files. diff --git a/tests/test_cli_download_smoke.py b/tests/test_cli_download_smoke.py index 8f39a10..8e19d7a 100644 --- a/tests/test_cli_download_smoke.py +++ b/tests/test_cli_download_smoke.py @@ -247,6 +247,7 @@ def test_download_smoke_prepare_write_outputs_isolated_pair(capsys, tmp_path): summary = json.loads( (outdir / "bounded_download_smoke_summary.json").read_text(encoding="utf-8") ) + inspection_outdir = outdir / "inspection" assert payload["writes_outputs"] is True assert payload["output_files"] == { "bounded_download_smoke_plan": str( @@ -309,13 +310,13 @@ def test_download_smoke_prepare_write_outputs_isolated_pair(capsys, tmp_path): "download_plan": str(outdir / "bounded_download_smoke_plan.tsv"), "quality_profile": "fragmentation", "write": True, - "outdir": "", + "outdir": str(inspection_outdir), } assert summary["recommended_inspection_next_command"] == ( "typetreeflow download-smoke inspect --download-plan " f"{outdir / 'bounded_download_smoke_plan.tsv'} " "--quality-profile fragmentation --write --outdir " - "" + f"{inspection_outdir}" ) assert summary["recommended_inspection_command"] == [ "typetreeflow", @@ -327,8 +328,9 @@ def test_download_smoke_prepare_write_outputs_isolated_pair(capsys, tmp_path): "fragmentation", "--write", "--outdir", - "", + str(inspection_outdir), ] + assert not inspection_outdir.exists() assert payload["bounded_download_smoke_summary"][ "recommended_inspection_command" ] == summary["recommended_inspection_command"] @@ -422,13 +424,14 @@ def test_download_smoke_execute_dry_run_validates_command_manifest( assert summary["executed_command_count"] == 0 assert summary["status_counts"] == {"execution_planned": 1} assert summary["ready"] is True + inspection_outdir = commands.parent / "inspection" assert summary["recommended_inspection_request_target"] == "download-smoke inspect" assert summary["recommended_inspection_request"] == { "command": "download-smoke", "subcommand": "inspect", "download_plan": str(commands.parent / "bounded_download_smoke_plan.tsv"), "write": True, - "outdir": "", + "outdir": str(inspection_outdir), "quality_profile": "fragmentation", } assert summary["recommended_inspection_request_blockers"] == [] @@ -436,7 +439,7 @@ def test_download_smoke_execute_dry_run_validates_command_manifest( "typetreeflow download-smoke inspect --download-plan " f"{commands.parent / 'bounded_download_smoke_plan.tsv'} " "--quality-profile fragmentation --write --outdir " - "" + f"{inspection_outdir}" ) assert summary["recommended_inspection_command"] == [ "typetreeflow", @@ -448,7 +451,7 @@ def test_download_smoke_execute_dry_run_validates_command_manifest( "fragmentation", "--write", "--outdir", - "", + str(inspection_outdir), ] assert ( main( @@ -705,6 +708,7 @@ def test_download_smoke_prepare_write_carries_inspection_quality_gates( summary = json.loads( (outdir / "bounded_download_smoke_summary.json").read_text(encoding="utf-8") ) + inspection_outdir = outdir / "inspection" assert summary["inspection_min_fasta_n50_bases"] == 50000 assert summary["inspection_max_fasta_record_count"] == 10 assert summary["inspection_max_fasta_ambiguous_bases"] == 100 @@ -735,7 +739,7 @@ def test_download_smoke_prepare_write_carries_inspection_quality_gates( "--block-fasta-header-keywords", "--write", "--outdir", - "", + str(inspection_outdir), ] assert summary["recommended_inspection_request"] == { "command": "download-smoke", @@ -750,7 +754,7 @@ def test_download_smoke_prepare_write_carries_inspection_quality_gates( "block_fragmented_fasta": True, "block_fasta_header_keywords": True, "write": True, - "outdir": "", + "outdir": str(inspection_outdir), } assert payload["bounded_download_smoke_summary"][ "recommended_inspection_command" @@ -786,6 +790,7 @@ def test_download_smoke_prepare_write_can_disable_inspection_quality_profile( summary = json.loads( (outdir / "bounded_download_smoke_summary.json").read_text(encoding="utf-8") ) + inspection_outdir = outdir / "inspection" assert summary["inspection_quality_profile"] == "none" assert summary["inspection_block_fragmented_fasta"] is False assert summary["inspection_block_fasta_header_keywords"] is False @@ -794,7 +799,7 @@ def test_download_smoke_prepare_write_can_disable_inspection_quality_profile( "subcommand": "inspect", "download_plan": str(outdir / "bounded_download_smoke_plan.tsv"), "write": True, - "outdir": "", + "outdir": str(inspection_outdir), } assert "--quality-profile" not in summary["recommended_inspection_command"] assert payload["bounded_download_smoke_summary"][ @@ -1405,18 +1410,19 @@ def test_download_smoke_inspect_passes_when_selected_zip_contains_genome( assert summary["recommended_quality_gate_request_target"] == ( "download-smoke inspect" ) + inspection_outdir = plan.parent / "inspection" assert summary["recommended_quality_gate_request"] == { "command": "download-smoke", "subcommand": "inspect", "download_plan": str(plan), "quality_profile": "fragmentation", "write": True, - "outdir": "", + "outdir": str(inspection_outdir), } assert summary["recommended_quality_gate_next_command"] == ( "typetreeflow download-smoke inspect --download-plan " f"{plan} --quality-profile fragmentation --write --outdir " - "" + f"{inspection_outdir}" ) assert summary["recommended_quality_gate_command"] == [ "typetreeflow", @@ -1428,7 +1434,7 @@ def test_download_smoke_inspect_passes_when_selected_zip_contains_genome( "fragmentation", "--write", "--outdir", - "", + str(inspection_outdir), ] assert ( main( diff --git a/typetreeflow/download_smoke_cli.py b/typetreeflow/download_smoke_cli.py index 38de826..9650142 100644 --- a/typetreeflow/download_smoke_cli.py +++ b/typetreeflow/download_smoke_cli.py @@ -606,12 +606,13 @@ def _recommended_inspection_request( block_fragmented_fasta: bool = False, block_fasta_header_keywords: bool = False, ) -> dict[str, object]: + inspection_outdir = Path(bounded_plan_path).parent / "inspection" request: dict[str, object] = { "command": "download-smoke", "subcommand": "inspect", "download_plan": str(bounded_plan_path), "write": True, - "outdir": "", + "outdir": str(inspection_outdir), } if min_fasta_n50_bases > 0: request["min_fasta_n50_bases"] = min_fasta_n50_bases @@ -721,7 +722,7 @@ def _recommended_inspection_command( [ "--write", "--outdir", - "", + str(Path(bounded_plan_path).parent / "inspection"), ] ) return command @@ -1260,6 +1261,8 @@ def execute_bounded_download_smoke_commands( succeeded_count = sum( 1 for row in results if row["status"] == "datasets_zip_ready_for_inspection" ) + bounded_plan_path = manifest_path.parent / OUTPUT_PLAN_NAME + inspection_outdir = manifest_path.parent / "inspection" summary = { "schema_version": EXECUTION_SCHEMA_VERSION, "command": EXECUTE_COMMAND, @@ -1291,12 +1294,12 @@ def execute_bounded_download_smoke_commands( "download-smoke", "inspect", "--download-plan", - "", + str(bounded_plan_path), "--quality-profile", "fragmentation", "--write", "--outdir", - "", + str(inspection_outdir), ], "summary": ( "Bounded datasets command manifest is valid."