Skip to content

feat(regression): repoint the build + generators at DATASET_DIR/regression (S2-E2) - #153

Merged
mousebrains merged 3 commits into
mainfrom
s2-e2-build-repoint
Jun 9, 2026
Merged

feat(regression): repoint the build + generators at DATASET_DIR/regression (S2-E2)#153
mousebrains merged 3 commits into
mainfrom
s2-e2-build-repoint

Conversation

@mousebrains

Copy link
Copy Markdown
Owner

S2-E2 of the regression slice. The dataset now carries the validated reports (kayak_data #15/#16), but levels build still rendered them from the engine's docs/regression/. E2 repoints the read at the dataset (DATASET_DIR/regression/) so the served reports come from where they're owned and validated. Engine-only; no kayak_data change.

Changes

  • Build (web/build/deploy.py): _deploy_regression_artifacts reads DATASET_DIR / "regression" (was BASE_DIR / "docs" / "regression"); DATASET_DIR added to the kayak.config import; docstrings updated. The missing-dir early-return ("none configured") is unchanged — a dataset without regression/ serves no reports rather than failing.
  • Generators (scripts/regression/gauge_pair_linear.py + gauge_lead_lag.py): --out defaults to $DATASET_DIR/regression/<name>.md when DATASET_DIR is set, via a new _resolve_out() using stdlib os.environ only — the "no kayak imports / runnable without the venv" standalone contract is preserved. The generated calc_expression.note now points at regression/<name>.md (dataset-relative). README/docstring examples updated.
  • wheel-smoke: build runs with DATASET_DIR=<fixture> (HOME→$WORK + SUDO_USER cleared so config ignores a dev ~/.config/kayak/.env; no-op in CI) and asserts the fixture report renders + is sanitized under /static/regression/ — the full E2 path end-to-end in the packaged wheel.
  • Tests: the 4 test_deploy_regression_* monkeypatch build_mod.DATASET_DIR + seed base/regression/; _resolve_out unit tests for both generators.
  • Doc sweep: models.py provenance_slug comment, the validate_dataset _check_regression docstring, and docs/live-tree-workflow.md's frozen-install table — the regression read is now env-located (DATASET_DIR), off BASE_DIR.

Cutover safety

deploy.sh pulls kayak_data (which has regression/, from D1) before it builds, so the read finds the same reports it served from docs/regression (byte-identical). The engine's docs/regression/ copy is now unread by code; E3 deletes it.

Verified

  • Full gate green: ruff / format / mypy / pytest -m "not slow" (1540 passed) / git diff --check.
  • wheel-smoke renders the fixture report from DATASET_DIR/regression and asserts it's sanitized.
  • A real build against kayak_data renders all 25 report stems (25 html/svg/json), no <script> leak — parity with the pre-E2 docs/regression output.
  • Adversarial self-review (build repoint, wheel-smoke CI-vs-local hermeticity, generator env-default + standalone contract) — sound; closed two non-blocking hardening notes (SUDO_USER edge, mirror test).

Follow-up (E3)

Delete docs/regression/*.{md,svg,json} from the engine; move the report index into kayak_data/regression/README.md; repoint tests/test_regression.py's real-artifact tests at the dataset/fixture (else they silently skip once the engine copies are gone).

🤖 Generated with Claude Code

…ssion (S2-E2)

The dataset now carries the validated regression reports (kayak_data #15/#16), but
the build still rendered them from the engine's docs/regression/. E2 repoints the
read at the dataset so the served reports come from where they're owned + validated.

- web/build/deploy.py: _deploy_regression_artifacts reads DATASET_DIR/"regression"
  (was BASE_DIR/"docs"/"regression"); DATASET_DIR added to the kayak.config import;
  docstrings updated. The missing-dir early-return ("none configured") is unchanged,
  so a dataset without regression/ serves no reports rather than failing.
- scripts/regression/gauge_pair_linear.py + gauge_lead_lag.py: --out now defaults to
  $DATASET_DIR/regression/<name>.md when DATASET_DIR is set in the env, via a new
  _resolve_out() helper using stdlib os.environ only — the "no kayak imports /
  runnable without the venv" standalone contract is preserved. The generated
  calc_expression.note points at regression/<name>.md (dataset-relative). README +
  docstring examples updated to the DATASET_DIR flow.
- scripts/wheel-smoke.sh: build now runs with DATASET_DIR=<fixture> (HOME→$WORK and
  SUDO_USER cleared so config ignores a dev ~/.config/kayak/.env; no-op in CI) and
  asserts the fixture report renders + is sanitized under /static/regression/ —
  exercising the full E2 path end-to-end in the packaged wheel.
- tests: the 4 test_deploy_regression_* monkeypatch build_mod.DATASET_DIR and seed
  base/regression/; add _resolve_out unit tests for both generators.
- doc sweep: models.py provenance_slug comment, validate_dataset _check_regression
  docstring, and docs/live-tree-workflow.md frozen-install table all updated — the
  regression read is now env-located (DATASET_DIR), off BASE_DIR.

Cutover-safe: deploy.sh pulls kayak_data (which has regression/) before it builds,
so the read finds the same reports it served from docs/regression (byte-identical).
The engine's docs/regression/ copy is now unread by code; E3 deletes it.

Verified: full gate green; wheel-smoke renders the fixture report from
DATASET_DIR/regression; a real build against kayak_data renders all 25 reports
(25 html/svg/json), no <script> leak.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mousebrains

Copy link
Copy Markdown
Owner Author

Adversarial review — PR #153 (S2-E2: repoint build + generators at DATASET_DIR/regression)

Reviewed on the prod host, where the dataset cutover is already in place (kayak_data #15/#16 are pulled — DATASET_DIR/regression/ has the 76 files and validate-dataset is clean), so I could reason about the live read path directly.

Verdict: clean — ready to merge. The repoint is surgical and the E1 security posture is fully preserved. One low/medium availability hardening note, no blockers.

What I verified

  • The build change is only the source directory. _deploy_regression_artifacts flips BASE_DIR/"docs"/"regression"DATASET_DIR/"regression"; everything below — validate_svg (re-serialize), render_markdown_safe (nh3), validate_json_sidecar, is_safe_slug skip, html.escape title, fail-closed re-raise — is untouched. So dataset content is still sanitized at build, which is exactly why moving the source to the (less-trusted) dataset is safe. Security posture = unchanged.
  • Output parity is assured. The dataset's regression/ is byte-identical to the engine's docs/regression/ (I confirmed that in the kayak_data Gradient profile: max_gradient + statistically-binned chart per reach #15 review), and the extra regression/README.md is correctly skipped (the stem.lower()=="readme" guard) — so the served set is the same 25 stems as pre-E2. No <script> leak path changes.
  • Generators are sound. _resolve_out defaults --out to $DATASET_DIR/regression/<name>.md only when the env var is set, else stdout; explicit --out wins; stdlib os.environ only, preserving the "runnable without the venv / no kayak import" standalone contract. The generated calc_expression.note repoints to regression/<name>.md (dataset-relative). Consistent.
  • wheel-smoke is a real hermetic E2 e2e: builds with DATASET_DIR=<fixture> and HOME=$WORK/SUDO_USER="" (disables config's ~/.config/kayak/.env fallback so a dev .env can't leak in — no-op in CI), then asserts the fixture report renders to /static/regression/ with no raw <script> and a re-serialized <svg>. The 4 test_deploy_regression_* tests repoint their monkeypatch to build_mod.DATASET_DIR + seed base/regression/. Coverage tracks the change.

Findings

1. [Low–Med, availability not security] The build now silently serves no regression reports if DATASET_DIR/regression is absent or stale. The early-return ("none configured") is unchanged, but its meaning shifts: pre-E2 the source was the engine's in-tree docs/regression/ (always present); post-E2 it's the live DATASET_DIR checkout. Crucially, the hourly pipeline build does not pull kayak_data (it's fetch→…→build, no git pull) — only deploy.sh refreshes the checkout. So steady-state regression output is only as current as the last deploy/manual pull, and if the dir ever goes missing (bad pull, wrong DATASET_DIR), the reports just 404 with no build error. validate-dataset catches the slugs-without-dir case at deploy time, but the hourly build has no equivalent signal. No current impact (I've pulled #15/#16, so the dir is present and the next build will render from it). Suggestion: have the build emit a log.warning when DATASET_DIR/regression is absent but the DB carries provenance_slugs — so a stale/misconfigured checkout surfaces instead of silently dropping the pages (parallel to _check_regression's warning).

2. [Nit] _resolve_out is duplicated verbatim in both generators. Intentional — the standalone "no kayak import" contract forbids a shared helper module — so this is acceptable; just noting it'll drift if one is edited without the other.

Deploy-readiness note (for when this lands)

E2 is safe to deploy now specifically because the dataset cutover preceded it: the live DATASET_DIR/regression/ is present (#15/#16 pulled) and byte-identical to what the build served from docs/regression, so the post-pull hourly build renders identical output — true parity, no gap. (Had E2 deployed before the dataset content, the build would have served no reports — degraded, not crashed.) E3 (deleting docs/regression + repointing tests/test_regression.py's real-artifact tests) remains the follow-up; as the PR notes, those tests will silently skip once the engine copies are gone unless repointed.

Net: a faithful, security-preserving cutover with good test/wheel-smoke coverage. Worth the small build-time warning so a stale dataset checkout can't quietly drop the reports.

@mousebrains

Copy link
Copy Markdown
Owner Author

Adversarial rereview — PR #153 @ caa6539

No new blockers found. The PR head is unchanged from the earlier review, and CI is green.

The prior non-blocking hardening note still stands: _deploy_regression_artifacts() silently returns when DATASET_DIR/regression/ is absent, so a stale/misconfigured data checkout can drop regression pages without a build failure. That is availability/observability, not a security regression, and I would not block this PR on it. A warning when the DB has provenance_slugs but the dataset has no regression/ directory would make the failure mode visible.

Fresh checks I ran:

  • gh pr checks 153: all checks passing.
  • Targeted tests: 222 passed for tests/test_build_deploy.py, both regression generator test files, tests/test_regression.py, and tests/test_cli/test_build.py.
  • levels validate-dataset /Users/pat/tpw/kayak_data: OK against current kayak_data main.
  • Live DB build using the fresh DB in ../DB: 412 reaches, 225 gauges, build complete with 190 installed, 0 orphans removed.
  • Built regression output from DATASET_DIR=/Users/pat/tpw/kayak_data: 25 HTML, 25 SVG, 25 JSON; no <script> hits in rendered regression assets.
  • Every one of the live DB's 17 distinct calc_expression.provenance_slug values has matching generated .html, .svg, and .json assets.
  • scripts/wheel-smoke.sh: passed, including the packaged-install fixture regression render from DATASET_DIR/regression.

Verdict: clean rereview, ready to merge. The previous warning/hardening idea can ride as a follow-up.

Address the S2-E2 review's availability note: the hourly pipeline build
doesn't pull kayak_data, so a stale/misconfigured DATASET_DIR checkout
(no regression/ directory) would silently publish zero reports even when
calc rows declare provenance_slugs. Surface that as a build-time warning,
the parallel to validate-dataset's deploy-time slug↔dir check.

_build_to_dir counts the non-empty CalcExpression.provenance_slug rows and
threads the count through _deploy_source_files → _deploy_static_assets →
_deploy_regression_artifacts; the missing-dir early return warns via the
extracted _warn_regression_dir_missing helper (keeps the deploy function
under the C901 complexity limit). A dataset that simply ships no reports
(zero slugs) stays silent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mousebrains

Copy link
Copy Markdown
Owner Author

Addressed the availability note (non-blocking suggestion) in 2f2a086.

What it does: _build_to_dir now counts the non-empty CalcExpression.provenance_slug rows and threads the count through _deploy_source_files_deploy_static_assets_deploy_regression_artifacts. When DATASET_DIR/regression/ is absent but the DB declares one or more provenance slugs, the build emits:

WARNING: N calc_expression provenance_slug(s) declared but <path>/regression is absent —
no regression reports will be published; is DATASET_DIR / the kayak_data checkout stale?

This is the build-time parallel to validate-dataset's deploy-time slug↔dir check — the hourly pipeline build doesn't pull kayak_data, so a stale checkout would otherwise drop the report pages silently. A dataset that simply ships no reports (zero slugs) stays silent — no spurious warning.

The warning lives in an extracted _warn_regression_dir_missing helper to keep _deploy_regression_artifacts under the C901 complexity limit. New test: test_deploy_regression_warns_when_slugs_but_no_dir (asserts the warning fires, no crash, no output dir). Full gate green (ruff/format/mypy/pytest -m 'not slow'/wheel-smoke/git diff --check).

@mousebrains

Copy link
Copy Markdown
Owner Author

Adversarial re-review — PR #153 (after 2f2a086)

Re-reviewed the new commit on the prod host. It addresses my round-1 availability finding; the E2 repoint core is otherwise unchanged.

Verdict: ready to merge. Finding #1 resolved cleanly, correctly scoped (warn, not fail).

The new commit (2f2a086 — warn when slugs declared but dataset dir absent)

This is exactly the hardening I asked for. Verified:

  • Detection is accurate. _build_to_dir counts non-empty CalcExpression.provenance_slug rows (SQL is_not(None) + Python s.strip()); I ran the same query against the live DB → 17, matching the real slug count. So a stale/missing DATASET_DIR/regression would warn "17 provenance_slug(s) declared…", not stay silent.
  • Warns, doesn't fail. _warn_regression_dir_missing does logger.warning then the early-return continues — a missing dir is a degradation, not a security failure, so a log warning (the same level as validate-dataset's deploy-time slug↔dir check) is the right call, and it doesn't fail-close a legitimate report-less dataset.
  • No false positive. if not provenance_slug_count: return — a dataset that ships zero reports stays silent.
  • Clean threading + complexity. The count flows _build_to_dir → _deploy_source_files → _deploy_static_assets → _deploy_regression_artifacts via keyword-only provenance_slug_count, and the warn path is extracted to a helper to stay under C901. The query is a cheap read on the existing session.
  • Scope confirmed: git diff caa6539..2f2a086 touches only deploy.py + the test — the E2 build/generator repoint, sanitization, wheel-smoke, and parity I verified in round 1 are untouched and still stand.

Residual (Nit)

The new test exercises the leaf (_deploy_regression_artifacts(..., provenance_slug_count=3)) — it proves the warn logic, but not the end-to-end threading (that _build_to_dir actually computes the count and passes it through the three hops). The threading is simple keyword-passing (low risk), but a full-build test asserting the warning fires when the DB has slugs and DATASET_DIR/regression is absent would close the one untested link. Optional.

Note

Merge state shows BLOCKED (vs CLEAN at my last review) — likely a pending required check / branch update, not something the diff reveals; my PAT can't read check-runs, so I'm reviewing the code, not the CI result.

Net: the availability gap is closed correctly. Ship it (modulo the optional end-to-end threading test).

@mousebrains

Copy link
Copy Markdown
Owner Author

Adversarial rereview — PR #153 after 2f2a086

No blockers found. CI is now green on the updated head.

The follow-up commit resolves the prior availability note for the important missing-directory case: when the live DB has regression-derived calc rows but DATASET_DIR/regression/ is absent, the build now emits a warning and continues instead of silently dropping the pages.

Fresh verification:

  • gh pr checks 153: all checks passing.
  • Targeted tests: 223 passed for tests/test_build_deploy.py, tests/test_cli/test_build.py, both regression generator test files, and tests/test_regression.py.
  • levels validate-dataset /Users/pat/tpw/kayak_data: OK.
  • Negative live-DB build with DATASET_DIR pointed at an empty dataset directory: build completed and emitted the expected warning for 17 calc_expression provenance_slug(s).
  • Normal live-DB build with DATASET_DIR=/Users/pat/tpw/kayak_data: 412 reaches, 225 gauges, 190 installed, and published 25 regression HTML, 25 SVG, 25 JSON.
  • Every one of the live DB's 17 non-empty calc_expression.provenance_slug values has matching generated .html, .svg, and .json assets.
  • No <script> hits in the generated regression assets.
  • scripts/wheel-smoke.sh: passed.

Residual non-blocking boundary: the new warning only covers an absent regression/ directory. If the directory exists but is empty/incomplete, the build still completes silently; I verified that with an empty regression/ directory. I would not block this PR on that because the normal deploy path runs validate-dataset, which catches missing slug artifacts before build. If the goal is to catch every stale checkout shape in the hourly build too, the next step would be comparing the DB slug set against the dataset report stems rather than only checking directory presence.

Verdict: clean rereview, ready to merge.

…reading

Address the re-review's optional residual: the prior test exercised only the
leaf (_deploy_regression_artifacts with an explicit count), not that the count
is actually computed from the DB or threaded through the deploy hops.

Extract the inline provenance_slug count into a _count_regression_slugs(session)
helper (also tidies _build_to_dir) and unit-test it — including the NULL and
whitespace-only slug edge cases that must not count. Add a threading test that
drives the full _deploy_source_files → _deploy_static_assets →
_deploy_regression_artifacts chain and asserts the warning fires, closing the
untested wiring link between the leaf warn test and _build_to_dir.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mousebrains

Copy link
Copy Markdown
Owner Author

Addressed the re-review residuals in 5ddb876.

Residual A (optional — end-to-end threading test): done. Two angles, no full-build harness needed (the existing deploy tests drive the helpers directly):

  • Extracted the inline count into _count_regression_slugs(session) (also tidies _build_to_dir) and unit-tested it — test_count_regression_slugs asserts NULL and whitespace-only slugs don't count, and a real slug does (2 of 4 rows). This covers the DB→count link.
  • test_deploy_source_files_threads_slug_count_to_warning drives the full _deploy_source_files → _deploy_static_assets → _deploy_regression_artifacts chain with provenance_slug_count=7 and an absent DATASET_DIR/regression/, asserting the warning fires. This covers the three-hop threading link.

Together these close the gap between the leaf-level warn test and _build_to_dir (which is now just n_reg_slugs = _count_regression_slugs(session) + the keyword pass-through).

Residual B (empty/incomplete regression/ directory): deferring, with rationale. The warning intentionally covers only the absent-directory case. Detecting a present-but-incomplete dir means comparing the DB slug set against the dataset's report stems — i.e. duplicating validate-dataset's per-slug closure check into the build hot path. The deploy gate already runs validate-dataset (which catches missing slug→{md,svg,json} triples before build), so the only uncovered shape is a checkout that's been corrupted between deploys — narrow, and arguably better served by reusing the validator than re-implementing it in deploy.py. Happy to file it as a follow-up if you'd rather close that boundary too.

Full gate green (ruff/format/mypy/pytest -m 'not slow' → 1543 passed/wheel-smoke/git diff --check).

@mousebrains
mousebrains merged commit 5816550 into main Jun 9, 2026
9 checks passed
@mousebrains
mousebrains deleted the s2-e2-build-repoint branch June 9, 2026 14:30
mousebrains added a commit that referenced this pull request Jun 9, 2026
…154)

E2 (#153) repointed the build at DATASET_DIR/regression/ and the dataset
(kayak_data) now owns + validates the 25 published reports, so the engine's
in-tree docs/regression/ copy is dead code. Delete it (76 files).

- Repoint tests/test_regression.py's real-artifact tests from the deleted
  docs/regression/ to the committed tests/fixtures/dataset/regression/ so the
  skipif(not _REAL_*) sanitizer tests keep running (2 fixture stems -> 6 cases)
  instead of silently dropping to zero once the engine copies are gone. The
  real 25 reports are sanitizer-gated by kayak_data's own validate CI.
- Sweep stale docs/regression/ prose: the markdown-dep comment (pyproject),
  the pip-audit/markdown-DoS suppression rationale (ci.yml -- input is now
  dataset-authored but PR-reviewed + rendered/validated at validate-dataset),
  and two docs/one-offs scripts. docs/one-offs/*.py refs are left untouched.

The dataset's regression/README.md gains the report index + lead/lag table in
the paired kayak_data PR. Verified: a real build against kayak_data still
renders all 25 reports from DATASET_DIR/regression/ (the deleted dir was unread).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant