Skip to content

Fix acquire-sky-supply-basis workflow: canonical idempotency, chronology, signing-key validation, DB safety net#116

Merged
fafa33 merged 1 commit into
mainfrom
fix/acquire-sky-supply-basis-workflow
Jul 26, 2026
Merged

Fix acquire-sky-supply-basis workflow: canonical idempotency, chronology, signing-key validation, DB safety net#116
fafa33 merged 1 commit into
mainfrom
fix/acquire-sky-supply-basis-workflow

Conversation

@fafa33

@fafa33 fafa33 commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Summary

Full end-to-end audit and fix of .github/workflows/acquire-sky-supply-basis.yml (the one-shot escape hatch that persists PR #110's missing Sky SupplyBasisSnapshot). Two prior incremental patches (PR #112, then #114/#115) only fixed the status != "ok" typo — this audit found and fixed everything else, including a genuine chronology bug that would have caused non-deterministic failures on any real run.

Root causes found (none previously fixed)

  1. Approximate logical-ID reimplementation. The idempotency check hand-rolled hunter.value_capture.service._logical_id()'s hash formula instead of calling the repository's own public strict_known_supply lookup. Numerically correct by luck, but fragile and against the "no approximate reimplementation" principle.
  2. No rule-existence precondition. Nothing verified the Sky ValueCaptureRuleSnapshot already existed before building a SupplyBasisSnapshot that depends on the same evidence chain.
  3. No signing-key validation. bytes.fromhex(os.environ[...]) with no pre-check — a missing/malformed secret produced a raw traceback, not an actionable error.
  4. Semantic mismatch in market-fact selection. Requested spot_price (unused in the supply-basis payload) and referenced it in observed_market_fact_ids anyway — an unrelated price observation cited as supply-basis evidence.
  5. No database diff-boundary safety net. Nothing checked that only snapshot rows changed before committing — the repository already suffered exactly this failure mode once (tests/test_data_ops.py's hardcoded DB path polluting data/data_ops.sqlite with unrelated automation-job rows during PR Issue #107 Milestone 1: entity-agnostic valuation evidence acquisition (Sky pilot) #110's own remediation).
  6. Real chronology bug, found only by local simulation. The script reused one external now for both the market-fact request's requested_at/recorded_at and the value-capture acquisition's effective_at/acquired_at, but CoinGeckoObservedMarketFactProvider stamps its own real result.acquired_at/known_at independently. Any real execution would have non-deterministically failed authority checks depending on provider call latency — this would never have shown up from reading the code, only from running it.

Fix

Extracted acquisition logic to scripts/acquire_sky_supply_basis.py (ruff/black/mypy-covered — pyproject.toml's mypy.files/pytest.pythonpath updated to include scripts), fixing all six issues. Added tests/test_acquire_sky_supply_basis.py (14 tests). The workflow YAML now checks out the PR #110 branch as before (it owns the canonical database) and materializes the reviewed script from main via git show origin/main:... into that checkout without ever committing it there — only data/data_ops.sqlite is committed, unchanged from before. Pinned action SHAs, permissions, and concurrency group are unchanged.

Local simulation (not a live CI run — see below)

api.coingecko.com is policy-denied in this session (confirmed via the proxy status endpoint), so I could not run the live workflow. Instead, ran the corrected script directly against a real copy of PR #110's branch database and configs, with an injected schema-valid (not live) CoinGecko response:

  • Market facts accepted (circulating_supply, total_supply).
  • Existing evidence (c53f556b...) and rule (f774f946...) found via canonical strict_known_* lookups — no hardcoded IDs.
  • SupplyBasisSnapshot persisted; independently re-verified linked by entity/representation/evidence/market-fact/chronology.
  • Second run: SupplyBasisSnapshot already exists ... — idempotent no-op, zero database writes, and the injected transport's get_json (which raises AssertionError if called) was never invoked — proving no network call is attempted on a no-op rerun.
  • Database diff: exactly the 4 expected new snapshot rows (2 market facts + 1 receipt + 1 supply record); automation-job (10) and automation-run (20) counts unchanged before/after.

Is a live rerun required?

Yes. This PR only fixes the workflow; it does not and cannot persist the real SupplyBasisSnapshot itself (no live CoinGecko access in this session, and no data was fabricated). After this PR merges: Actions → "Acquire Sky SupplyBasisSnapshot (Milestone 1)" → Run workflow → branch main, with HUNTER_VALUE_CAPTURE_SIGNING_KEY_ID/HUNTER_VALUE_CAPTURE_SIGNING_KEY repository secrets already set (unchanged requirement from PR #112).

Verification

  • ruff check . — pass
  • black --check . — pass
  • mypy — pass (570 files)
  • pytest1134 passed, 0 failed (1120 pre-existing + 14 new, zero regression)

Remaining limitations

Not merging or rerunning the live workflow — leaving both for review/authorization, per instructions.


Generated by Claude Code

…real bugs

The embedded-YAML Python had already gone through two incremental
guess-and-patch fixes (PR #112, PR #114/#115) that only addressed the
mf_result.status != "ok" typo. A full audit against the actual market_facts
and value_capture contracts, plus a real end-to-end local simulation against
a copy of PR #110's branch database, found further defects the incremental
patches never reached:

- Idempotency check hand-rolled value_capture.service._logical_id()'s hash
  formula instead of using the repository's own public strict_known_supply
  lookup. It happened to be numerically correct, but was fragile and
  violated the "no approximate reimplementation" principle. Replaced with
  strict_known_supply (also removes the only need for a hashlib import).
- No precondition check that the Sky ValueCaptureRuleSnapshot already
  exists before attempting to build a SupplyBasisSnapshot that implicitly
  depends on it existing elsewhere in the evidence chain.
- Signing-key env vars were decoded with a bare bytes.fromhex() with no
  validation, producing a raw traceback instead of an actionable error on
  a missing/malformed secret.
- requested_fact_types included spot_price, which is never used in the
  supply-basis payload, and got referenced in observed_market_fact_ids
  anyway, creating a semantic mismatch (an unrelated price observation
  cited as supply-basis evidence).
- No check that only "snapshot" rows changed in the canonical database
  before allowing a commit; the repository already suffered exactly this
  failure mode once (tests/test_data_ops.py's hardcoded DB path polluting
  data/data_ops.sqlite with unrelated automation-job rows during PR #110's
  own remediation).
- A genuine chronology bug local simulation caught directly: the script
  reused one external `now` for both the market-fact request's
  requested_at/recorded_at AND the value-capture acquisition's
  effective_at/acquired_at, but CoinGeckoObservedMarketFactProvider stamps
  its own real result.acquired_at/known_at independently. Any real
  execution would non-deterministically fail authority checks depending on
  provider call latency. Fixed by anchoring requested_at a safety margin
  before `now`, using the provider's own result.known_at for recorded_at,
  and anchoring the supply acquisition's timestamp to
  max(now, *market_fact.known_at) so it can never precede what it cites.

Extracted the acquisition logic to scripts/acquire_sky_supply_basis.py
(ruff/black/mypy-covered, pyproject.toml files/pythonpath updated) with
tests/test_acquire_sky_supply_basis.py (14 tests): signing-key validation,
the real "success" status literal, missing-evidence/missing-rule
preconditions, full run() end-to-end against a real synthetic DB with
cross-linked evidence/rule/market-fact/supply verification, idempotent
rerun (asserting the network transport is never called and row counts are
byte-for-byte unchanged), and the database diff-boundary safety net.

Locally simulated the corrected script end-to-end against a real copy of
PR #110's branch database (data/data_ops.sqlite, configs/*.yaml) with an
injected, schema-valid (not live) CoinGecko response, since api.coingecko.com
is policy-denied in this session. Confirmed: market facts accepted, the
existing evidence (c53f556b...) and rule (f774f946...) records found via
canonical strict-known lookups, SupplyBasisSnapshot persisted and
independently re-verified linked by entity/representation/evidence/
market-fact/chronology, a second run exits idempotently with zero writes
and zero network calls, and the database diff was exactly the 4 expected
new snapshot rows with automation-job/automation-run counts unchanged.

Workflow YAML: acquisition logic now lives in scripts/ on main (lint/type/
test-covered) rather than embedded in the workflow. Checkout still targets
the PR #110 branch (it owns the canonical database), with a new step that
materializes the reviewed script from main via `git show origin/main:...`
without committing it to the PR branch — only data/data_ops.sqlite is ever
committed there, unchanged from before. Pinned action SHAs, permissions,
concurrency group, and the commit/push procedure are unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ZwAhhLUYJMGgJm9yyzs7H
@fafa33
fafa33 marked this pull request as ready for review July 26, 2026 06:05
@fafa33
fafa33 merged commit 2772c9c into main Jul 26, 2026
1 check passed
@fafa33
fafa33 deleted the fix/acquire-sky-supply-basis-workflow branch July 26, 2026 06:05
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.

2 participants