Skip to content

Add provenance scanner for diagnostics links - #293

Open
tomvothecoder wants to merge 26 commits into
E3SM-Project:mainfrom
tomvothecoder:diags/240-prov-scan
Open

Add provenance scanner for diagnostics links#293
tomvothecoder wants to merge 26 commits into
E3SM-Project:mainfrom
tomvothecoder:diags/240-prov-scan

Conversation

@tomvothecoder

@tomvothecoder tomvothecoder commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Description

Closes #240.

Adds a diagnostics provenance scanner that discovers diagnostic archives, validates scanner inputs, and idempotently links case diagnostics through the API. Persists successful scanner provenance state so scans can be resumed safely, with deployment helpers and operational documentation for supported sites.

Also adds regression coverage for scanner-state lookup, authorization, validation, missing-machine handling, idempotent links, rollback behavior, and scanner ingestion behavior.

Validation

  • make backend-test (1003 passed; 100% coverage)
  • Pre-commit hooks: Ruff, formatting, and mypy passed

Deployment Notes

Apply the included Alembic migration before enabling scheduled scanner jobs. Configure site credentials and scheduler entries as documented.

@tomvothecoder
tomvothecoder marked this pull request as ready for review August 14, 2026 00:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds diagnostics provenance scanning, API-backed state persistence, deployment helpers, tests, and operational documentation.

Changes:

  • Adds archive discovery, validation, retry, and linking workflows.
  • Adds transactional provenance-state storage and scanner APIs.
  • Adds site wrappers, tests, migrations, and documentation.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
mkdocs.yml Adds diagnostics documentation navigation.
docs/user/diagnostics.md Documents user publishing workflow.
docs/README.md Links the user guide.
docs/github-issues/174-zppy-links/plan.md Updates the implementation plan.
docs/github-issues/174-zppy-links/phase-4-provenance-scanner.md Expands scanner design details.
docs/developer/README.md Links architecture documentation.
docs/architecture/metadata-ingestion.md Cross-references diagnostics linkage.
docs/architecture/diagnostics-linkage.md Documents scanner architecture and state flow.
backend/tests/features/ingestion/test_diagnostics_link_scanner.py Tests scanner behavior.
backend/tests/features/catalog/test_diagnostic_provenance_state.py Tests scanner APIs and persistence.
backend/migrations/versions/20260811_000000_add_diagnostic_provenance_state.py Creates provenance-state storage.
backend/app/scripts/README.md Documents scanner operation.
backend/app/scripts/ingestion/sites/nersc.crontab.example Adds scheduled scanner examples.
backend/app/scripts/ingestion/sites/nersc-diagnostics-scanner.sh Adds the NERSC wrapper.
backend/app/scripts/ingestion/sites/lcrc-diagnostics-scanner.sh Adds the LCRC wrapper.
backend/app/scripts/ingestion/diagnostics_link_scanner.py Implements provenance scanning and submission.
backend/app/scripts/ingestion/diagnostics_archives.py Defines supported archive locations.
backend/app/features/catalog/schemas.py Adds scanner request/state schemas.
backend/app/features/catalog/models.py Adds the provenance-state model.
backend/app/features/catalog/api.py Adds scanner state and linking endpoints.
Suppressed comments (4)

backend/app/scripts/ingestion/diagnostics_link_scanner.py:378

  • The valid archive layouts are <tier>/<case> and <tier>/<case_group>/<case>, but this rejects the ungrouped two-part form and accepts an unsupported four-part form. As a result, normal ungrouped zppy publications are never discovered, while over-nested directories can be linked. The group calculation must use the middle component only for the three-part layout.
    if len(parts) not in {3, 4} or parts[0] not in {"production", "development"}:

backend/app/scripts/ingestion/diagnostics_link_scanner.py:398

  • A filename can match the regex but contain an invalid timestamp (for example, month 13). strptime() then raises ValueError outside the per-candidate exception handler and aborts the entire scan, contrary to the requirement to skip malformed provenance. Treat an invalid parsed timestamp as a non-candidate.
    return datetime.strptime(match.group(1), "%Y%m%d_%H%M%S_%f").replace(
        tzinfo=timezone.utc
    )

backend/app/scripts/ingestion/diagnostics_link_scanner.py:264

  • This boundary check uses the raw URL path, so dot segments bypass it: /archive/../outside starts with /archive/ but resolves to /outside. Percent-encoded dot or slash variants can have the same effect depending on the server. Normalize and validate the decoded path before accepting the diagnostics URL so links cannot escape the configured archive.
                if (url.scheme, url.netloc) != (
                    base.scheme,
                    base.netloc,
                ) or not url.path.startswith(base.path.rstrip("/") + "/"):

docs/user/diagnostics.md:26

  • This says the scanner runs every 15 minutes, but the included scheduler example runs it hourly (nersc.crontab.example:29). Users will wait for a cadence the deployment does not provide. Avoid hardcoding the cadence here or align it with the deployed schedule.
4. Wait for the scheduled SimBoard scanner to attempt linkage. Linking is not immediate; the scanner runs periodically every 15 minutes.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +257 to +259
settings_bytes = _read_settings_bytes(settings)
values = _parse_settings_bytes(settings_bytes)
url = urlparse(values["diagnostics_url"])
4. For each unlinked or changed candidate, it calls `POST /api/v1/diagnostics/scanner/link` with one diagnostic link and provenance metadata. The API resolves the target SimBoard Case from case name, machine, and HPC username, then atomically upserts the case diagnostic link and scanner state. A successful request returns no content.
5. On a later run, the persisted state makes a matching candidate unchanged. A changed filename or fingerprint is submitted again and updates the state for that machine/path identity.

Scanner API access requires the diagnostics-scanner role. A state lookup can return no state, and it returns an error when the supplied machine is unknown. The scanner-link endpoint requires exactly one diagnostic and rejects unsafe archive-relative paths; case-resolution failures also prevent a successful state update.
20 * * * * cd ${REPO_DIR} && ${REPO_DIR}/backend/app/scripts/ingestion/sites/nersc-diagnostics-scanner.sh >> ${REPO_DIR}/backend/app/scripts/ingestion/sites/nersc-diagnostics-scanner.log 2>&1

# Chrysalis diagnostics provenance scan; use its local checkout path for REPO_DIR.
25 * * * * cd ${REPO_DIR} && ${REPO_DIR}/backend/app/scripts/ingestion/sites/lcrc-diagnostics-scanner.sh >> ${REPO_DIR}/backend/app/scripts/ingestion/sites/lcrc-diagnostics-scanner.log 2>&1
Comment thread docs/user/diagnostics.md

## Publish and find the link

1. Run and publish the zppy diagnostics using the configured `simulation_type`. This generates the `provenance.settings` file used for SimBoard publication.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Diags]: Add provenance scanner for diagnostics links

2 participants