From 861c6d80f38293f0b0290c257d7dd31427ccdb92 Mon Sep 17 00:00:00 2001 From: Jude Thomas Zeng <98306986+1613358894@users.noreply.github.com> Date: Mon, 3 Aug 2026 22:52:22 +0800 Subject: [PATCH] Add download smoke execute validation handoff --- docs/guide.md | 18 ++++++++---- docs/reference.md | 2 +- tests/test_cli_download_smoke.py | 46 ++++++++++++++++++++++++++++++ typetreeflow/download_smoke_cli.py | 42 +++++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 6 deletions(-) diff --git a/docs/guide.md b/docs/guide.md index 24dd0a7..d20e8bc 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -1351,6 +1351,13 @@ five corresponding command arrays for operator inspection. It does not run authorize broad downloads. The summary `handoff_checklist` keeps bounded `datasets` execution and final genome acceptance as separate approval/review steps. +When `prepare --write` succeeds, stdout also includes +`recommended_execution_validation_request_target`, +`recommended_execution_validation_request`, and +`recommended_execution_validation_next_command` for the matching +validation-only `download-smoke execute` handoff. That recommended command does +not include `--execute`; it only validates the pinned command manifest and +writes an isolated execution audit. Before running any bounded datasets command, validate the command manifest and write an isolated execution audit: @@ -1412,11 +1419,12 @@ When `prepare --write` succeeds, `recommended_inspection_request_target`, point to the matching local `download-smoke inspect` handoff for the written bounded plan; controllers can pass the request to `commands render` instead of parsing command text. The compatibility `recommended_inspection_command` argv -list remains available for operator inspection; choose a fresh isolated -inspection `--outdir` before running it. By default, that handoff includes -`--quality-profile fragmentation`, which blocks visibly fragmented multi-record -FASTA signals and WGS/scaffold/contig FASTA header keywords during the later -local inspection. Use +list remains available for operator inspection. The default handoff writes to +the deterministic sibling `inspection/` directory; copy the command and change +`--outdir` only when a different isolated inspection directory is needed. By +default, that handoff includes `--quality-profile fragmentation`, which blocks +visibly fragmented multi-record FASTA signals and WGS/scaffold/contig FASTA +header keywords during the later local inspection. Use `--inspection-quality-profile none` when the follow-up inspection should remain descriptive-only unless explicit gate flags are provided. Optional prepare flags such as `--inspection-min-fasta-n50-bases `, diff --git a/docs/reference.md b/docs/reference.md index ed17613..b39d8b5 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 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_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`, `recommended_execution_validation_request_target`, `recommended_execution_validation_request`, `recommended_execution_validation_next_command`, `recommended_execution_validation_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_execution_validation_*` fields are empty until `prepare --write` has produced an isolated command manifest, then point to validation-only `download-smoke execute` without `--execute`; that handoff validates the pinned command manifest and does not download. 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` or `recommended_execution_validation_request` to `commands render`; the `recommended_*_command` fields remain compatibility argv lists 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 8e19d7a..5802d80 100644 --- a/tests/test_cli_download_smoke.py +++ b/tests/test_cli_download_smoke.py @@ -248,6 +248,7 @@ def test_download_smoke_prepare_write_outputs_isolated_pair(capsys, tmp_path): (outdir / "bounded_download_smoke_summary.json").read_text(encoding="utf-8") ) inspection_outdir = outdir / "inspection" + execution_outdir = outdir / "execution" assert payload["writes_outputs"] is True assert payload["output_files"] == { "bounded_download_smoke_plan": str( @@ -337,6 +338,36 @@ def test_download_smoke_prepare_write_outputs_isolated_pair(capsys, tmp_path): assert payload["bounded_download_smoke_summary"][ "recommended_inspection_request" ] == summary["recommended_inspection_request"] + assert summary["recommended_execution_validation_request_target"] == ( + "download-smoke execute" + ) + assert summary["recommended_execution_validation_request"] == { + "command": "download-smoke", + "subcommand": "execute", + "commands_manifest": str(outdir / "bounded_download_smoke_commands.tsv"), + "limit": 1, + "write": True, + "outdir": str(execution_outdir), + } + assert summary["recommended_execution_validation_next_command"] == ( + "typetreeflow download-smoke execute --commands-manifest " + f"{outdir / 'bounded_download_smoke_commands.tsv'} " + f"--limit 1 --write --outdir {execution_outdir}" + ) + assert summary["recommended_execution_validation_command"] == [ + "typetreeflow", + "download-smoke", + "execute", + "--commands-manifest", + str(outdir / "bounded_download_smoke_commands.tsv"), + "--limit", + "1", + "--write", + "--outdir", + str(execution_outdir), + ] + assert "--execute" not in summary["recommended_execution_validation_command"] + assert not execution_outdir.exists() assert ( main( [ @@ -350,6 +381,21 @@ def test_download_smoke_prepare_write_outputs_isolated_pair(capsys, tmp_path): ) rendered = json.loads(capsys.readouterr().out) assert rendered["target_argv"] == summary["recommended_inspection_command"][1:] + assert ( + main( + [ + "commands", + "render", + "--request-json", + json.dumps(summary["recommended_execution_validation_request"]), + ] + ) + == 0 + ) + rendered = json.loads(capsys.readouterr().out) + assert rendered["target_argv"] == ( + summary["recommended_execution_validation_command"][1:] + ) checklist = {item["id"]: item for item in summary["handoff_checklist"]} assert checklist["prepare_bounded_download_smoke_input"]["status"] == "written" assert checklist["run_bounded_datasets_download"]["requires_explicit_approval"] diff --git a/typetreeflow/download_smoke_cli.py b/typetreeflow/download_smoke_cli.py index 9650142..25de136 100644 --- a/typetreeflow/download_smoke_cli.py +++ b/typetreeflow/download_smoke_cli.py @@ -234,6 +234,10 @@ def run_download_smoke_command( return 2 if args.action == "prepare" and args.write: + execution_validation_request = _recommended_execution_validation_request( + Path(args.outdir) / OUTPUT_COMMANDS_NAME, + limit=int(result["summary"].get("selected_row_count", args.limit)), # type: ignore[union-attr] + ) inspection_request = _recommended_inspection_request( Path(args.outdir) / OUTPUT_PLAN_NAME, min_fasta_n50_bases=args.inspection_min_fasta_n50_bases, @@ -261,6 +265,18 @@ def run_download_smoke_command( result["summary"]["recommended_inspection_command"] = ( # type: ignore[index] _recommended_inspection_command_from_request(inspection_request) ) + result["summary"]["recommended_execution_validation_request_target"] = ( # type: ignore[index] + EXECUTE_COMMAND + ) + result["summary"]["recommended_execution_validation_request"] = ( # type: ignore[index] + execution_validation_request + ) + result["summary"]["recommended_execution_validation_next_command"] = ( # type: ignore[index] + _recommended_next_command(execution_validation_request) + ) + result["summary"]["recommended_execution_validation_command"] = ( # type: ignore[index] + _recommended_command_from_request(execution_validation_request) + ) if args.action == "inspect": summary = result["summary"] # type: ignore[index] if args.write and int(summary.get("selected_row_count", 0)) > 0: @@ -520,6 +536,10 @@ def prepare_bounded_download_smoke_input( "recommended_inspection_request": {}, "recommended_inspection_next_command": "", "recommended_inspection_command": [], + "recommended_execution_validation_request_target": "", + "recommended_execution_validation_request": {}, + "recommended_execution_validation_next_command": "", + "recommended_execution_validation_command": [], "handoff_checklist": _prepare_handoff_checklist( writes_outputs=False, ready=not blockers, @@ -633,6 +653,22 @@ def _recommended_inspection_request( return request +def _recommended_execution_validation_request( + commands_manifest_path: str | Path, + *, + limit: int, +) -> dict[str, object]: + commands_path = Path(commands_manifest_path) + return { + "command": "download-smoke", + "subcommand": "execute", + "commands_manifest": str(commands_path), + "limit": limit, + "write": True, + "outdir": str(commands_path.parent / "execution"), + } + + def _recommended_next_command(request: dict[str, object]) -> str: if not request: return "" @@ -648,6 +684,12 @@ def _recommended_next_command(request: dict[str, object]) -> str: def _recommended_inspection_command_from_request( request: dict[str, object], +) -> list[str]: + return _recommended_command_from_request(request) + + +def _recommended_command_from_request( + request: dict[str, object], ) -> list[str]: if not request: return []