Skip to content

Conversation

@jdoucett
Copy link
Contributor

What

First slice of docs/EXCLUSION_SPEC.md — the identity classifier and the git coverage gate, as pure, additive helpers. Behavior-neutral: nothing in the archive flow calls them yet (same shape as PR #8's primitives-before-wiring slice). Wiring into the four member-set producers + reclaim re-gate follows in the next PR.

Part of the settled staged-hybrid direction (Phase 1 = exclusion). Cardinal rule enforced throughout: it must be impossible to silently drop a data file — exclusion requires tier ∈ {EXCLUDE, OPTIONAL-opted-out} AND a positive git COVERED proof; every uncertain/error verdict → back up.

Functions added to archive.py

  • compile_exclude_spec(spec, exclude_optional) / classify_tier(arcname, compiled) — the 3-tier classifier with spec precedence: DATA allowlist first and un-overridable, then EXCLUDE, then OPTIONAL (first-listed match wins, disposition resolved against the per-asset opt-out), else DATA-default. Rejects bare-extension rules (\.json$, \.md$, …) in the EXCLUDE/OPTIONAL tiers at compile time — the spec's "never classify by extension" rule — while still allowing legitimately extension-based DATA rules (tabular/imagery).
  • git_coverage_preconditions(coverage, repo_root, asset_subtree) — once-per-run fail-safe gate: repo + origin present, origin not under a protected mount (e.g. /depot), durable-remote attestation, no git-LFS, fresh fetch (with FETCH_HEAD-age fallback), clean asset subtree. Any failure → exclusion OFF for the whole run.
  • git_file_verdict(repo_root, full_path, ctx) — the per-file proof (steps L/H/C/P/B/A) returning COVERED | UNTRACKED | DIRTY | STALE | ERROR. Only COVERED permits exclusion; an LFS pointer, a blob/disk size mismatch, an unpushed blob, an EOL-rewriting attribute, or any dirty/staged state all route to back-up.
  • spec_sha256 / _parse_duration helpers (the resume-guard hash and 24h/30m/… parsing).

Imports stay inside the function bodies (Globus Compute / dill, invariant #1); the compiled spec (regex objects) is local-only, the raw dict is what crosses the Compute boundary.

Tests — tests/test_exclusion_primitives.py (+39)

  • Classifier precedence incl. the greenhouse counterexample (greenhouse_climate_2026-06.json → DATA, its …_metadata.json twin → EXCLUDE via the lookahead), the status-prefix rule, OPTIONAL first-match + opt-out, and the bare-extension guard.
  • The full git-gate matrix against real throwaway repos with a local bare remote: COVERED / UNTRACKED / DIRTY (modified + staged-only) / STALE (committed-not-pushed) / ERROR (LFS pointer + EOL filter), plus every precondition (no repo / no attestation / origin-under-forbidden-mount / LFS / dirty subtree).

Suite 60 → 99 green (python3 -m unittest discover -s tests).

Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

🤖 Generated with Claude Code

…no behavior change)

First slice of docs/EXCLUSION_SPEC.md. Pure, additive helpers — NOTHING in the
archive flow calls them yet (behavior-neutral, mirrors PR #8's primitives slice):

  - compile_exclude_spec / classify_tier — the 3-tier identity classifier
    (DATA allowlist first + un-overridable, then EXCLUDE, then OPTIONAL
    first-match, else DATA-default). Rejects bare-extension rules in the
    subtractive tiers at compile time (the spec's "never classify by extension"
    rule). DATA precedence + the greenhouse_climate lookahead are enforced.
  - git_coverage_preconditions / git_file_verdict — the fail-safe git gate.
    Per-run preconditions (repo+origin present, origin off the protected mount,
    durable-remote attestation, no git-LFS, fresh fetch, clean subtree) and the
    per-file proof (L/H/C/P/B/A) returning COVERED|UNTRACKED|DIRTY|STALE|ERROR.
    ONLY COVERED permits exclusion; every uncertain/error verdict -> back up.
  - spec_sha256 / _parse_duration helpers.

Imports stay inside function bodies (Globus Compute / dill, invariant #1); the
compiled spec (regex objects) is local-only, the raw dict crosses the boundary.

Tests: tests/test_exclusion_primitives.py (+39) — classifier precedence incl.
the greenhouse data-vs-_metadata-twin counterexample, the bare-extension guard,
and the full git-gate matrix against real throwaway repos with a local bare
remote. Suite 60 -> 99 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jdoucett
Copy link
Contributor Author

Added the robust error-handling + reporting layer for exclusion under --globus (per request), in commit 0095a27. Still behavior-neutral.

The gate runs on a compute node under --globus, so the contract is: exclusion can never fail an archive, and the reason must travel back to the launcher.

  • evaluate_exclusions(...) — NEVER raises. Returns a fully JSON-serializable diagnostic report (so it crosses the Compute boundary back to the launcher for the banner + completion email):
    • status: OFF (no spec) / DISABLED (couldn't run safely → back up everything, with disabled_reason) / ON.
    • excluded[] (+blob_oid/remote_ref), demoted[] (candidate kept, with verdict+reason), contested_data[] (a DATA file that also matched a sub-rule — logged as proof precedence saved it), counts{}, excluded_bytes.
    • Targeted handlers for malformed spec / missing repo_root / precondition failure / per-file gate error, plus an absolute catch-all backstopDISABLED + error recorded, drop nothing.
  • companion_guard (INV-C): on violation, exclusion is suppressed run-wide (drop nothing) and reported so the wiring can raise a loud [EXCLUSION-ABORT] — data is safe either way.
  • Distinct failure reporting: git unavailable (rc 127 — a likely compute-node PATH gap) is reported distinctly from "repo missing" and never collapses to a false UNTRACKED.

Tests +11 (suite 99 → 110 green): the full OFF/DISABLED matrix incl. git-missing reported distinctly and a never-raises-on-injected-error test, plus live end-to-end (covered→excluded, committed-not-pushed→STALE-demoted, DATA kept, dirty-subtree disables the whole run, contested DATA logged, companion guard suppresses, report JSON-serializes).

One open question for the wiring (Phase 1b): on a companion-guard violation, do you want the run to hard-abort (fail the archive, [EXCLUSION-ABORT] email — what EXCLUSION_SPEC.md §6 literally says) or proceed with everything backed up + the same loud alert (archive still succeeds, nothing dropped)? evaluate_exclusions already guarantees nothing is dropped in that case; this is purely the abort-vs-continue policy for the live path.

Sign in to join this conversation on GitHub.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant