[verified] bind snapshots to manifests and raw hashes

This commit is contained in:
Kunthawat Greethong
2026-08-23 14:58:15 +07:00
parent f55ff69c31
commit ba9114d2e8
10 changed files with 278 additions and 22 deletions

View File

@@ -4,8 +4,8 @@
- Path: `/Users/kunthawat/Gitea/set50-alternative-data-platform`
- Branch: `main`
- Verified code commit: `7f7a614``[verified] add SET price snapshot adapter` (M2.5 pending commit)
- Current milestone: M2.5 research runner and durable paper workflow complete; validated backtest blocked
- Verified code commit: `f55ff69``[verified] add frozen research runner and durable paper ledger` (integrity hardening pending commit)
- Current milestone: M2.5 integrity hardening complete; validated backtest blocked
- Mode: research + paper only
- Frontend: Vue 3 + Vite
- Backend: Flask `0.5.0`
@@ -29,6 +29,7 @@
- Frozen research runner: `POST /api/v1/research/tourism/run` and `GET /api/v1/research/tourism/latest`.
- Headless runner: `backend/scripts/run_tourism_research.py`.
- Durable atomic paper ledger under `backend/data/paper/ledger.json` when configured.
- Raw and normalized snapshot integrity binding with explicit `sha256-json-canonical-v1` metadata.
- Ranked target weights and LONG/SHORT/NEUTRAL classification.
- English dashboard with live/provisional source label, sign-aware surprise copy and lineage fields.
- HttpOnly paper session and internal paper ledger.
@@ -98,10 +99,12 @@ Paper writes use a server-side token exchange and HttpOnly `paper_session` cooki
- One independent source release is not enough for a valid event study; current historical rows are not treated as point-in-time vintages.
- The event-study engine is deterministic and tested. Yahoo price history is connected for plumbing, but it is revised vendor history, not point-in-time data.
- Research runner is operational and replayable, but correctly emits a blocked report until both evidence gates pass.
- Snapshot loads fail closed when raw files, normalized payloads or manifest metadata do not match their recorded hashes.
- The hash boundary protects local artifacts against corruption/partial writes; hostile host-level rewrite of code, manifests and runtime environment is outside this local threat model.
- Revisions are deduplicated by canonical `(source_id, published_at)`; the latest observed revision is selected once.
- Event-study default execution is the next trading session, including weekend/holiday event dates.
- Browser screenshot verification remains blocked by the Chrome remote-debugging permission prompt; served HTML/source, live API, fresh Vite build and replay integrity were verified instead.
## Exact next action
Collect independent BOT releases over time and replace/supplement revised vendor history with a point-in-time daily price source. The operator can run the frozen research check now; only raise the gate when both requirements pass. Add another metric only when its historical release coverage is real.
Collect independent BOT releases over time and replace/supplement revised vendor history with a point-in-time daily price source. The operator can run the frozen research check now; only raise the gate when both requirements pass. Add a trusted deployment signing key before treating the app as a multi-user or hostile-host service.

View File

@@ -10,6 +10,7 @@
| M2.3 event-study gate | complete/blocked | 30 tests, pure engine and truthful 409 readiness API | add point-in-time price provider |
| M2.4 price snapshot adapter | complete/blocked | 38 tests, live 9-symbol Yahoo snapshot, revised-history gate | evaluate point-in-time price source |
| M2.5 research runner + durable paper ledger | complete/blocked | 54 tests, immutable blocked report, restart-safe paper path, live API/UI workflow | collect independent releases and point-in-time prices |
| M2.5 integrity hardening | complete/blocked | 57 tests, normalized/raw hash binding, manifest cross-checks, live re-collection | protect point-in-time gate with a trusted deployment secret if threat model expands |
| Tourism deterministic signal | complete | live foreign-arrivals YoY surprise | add occupancy/airport metric |
| Internal paper ledger | complete | atomic local JSON persistence and restart test | shared store before multi-worker deployment |
| Dashboard | complete | Vite build + served source check with live-sign copy | visual browser capture after permission is available |
@@ -27,7 +28,7 @@
## Verification
- Backend: 54 unittest tests pass.
- Backend: 57 unittest tests pass.
- Independent M1 review: **PASSED**; no concrete security or logic blockers.
- Reviewer suggestions: set `PAPER_COOKIE_SECURE=1` outside local HTTP; replace in-memory sessions before multi-worker deployment.
- M1 reviewer backlog: add schema-drift, duplicate/reordered-row, and malformed-vintage regression fixtures.
@@ -51,4 +52,8 @@
- Paper ledger persists atomically under ignored `backend/data/paper/ledger.json` when configured.
- Live API `0.5.0` created and replayed the same blocked Tourism research report; UI served the research-run panel and human-readable gate reason.
- Independent M2.5 review: **PASSED**; no concrete security or logic blockers.
- Integrity hardening: normalized snapshots and raw payloads are bound to manifest metadata; malformed boolean flags, stale manifests, and cached-ready replay after tampering are rejected.
- Canonical hash algorithm is explicit: `sha256-json-canonical-v1` using sorted-key compact UTF-8 JSON after excluding only the normalized hash field.
- Integrity scope is local artifact/corruption detection. A hostile machine owner who can rewrite code, manifests, raw files and runtime environment is outside this local research app's threat model.
- Independent integrity-hardening review: **PASSED** under the stated local single-user threat model.
- Browser visual capture was blocked by Chrome remote-debugging permission; no permission dialog was clicked.

View File

@@ -0,0 +1,74 @@
# 2026-08-23 — snapshot integrity hardening
## Plan status
- Root-cause fix for fail-open point-in-time metadata: complete.
- Raw payload and normalized snapshot binding: complete.
- Manifest/source metadata cross-checks: complete.
- Cached research report revalidation: complete.
- Trusted host/signing boundary: documented as local-only; multi-user deployment deferred.
## Changed files
- `backend/app/vintages.py` — canonical normalized hash, raw payload verification, manifest/source cross-checks, timezone-equivalent revision handling.
- `backend/app/prices.py` — canonical normalized hash, strict boolean point-in-time metadata, raw/manifest/source cross-checks.
- `backend/app/research.py` — validate selected snapshots before cached report lookup and derive the price gate from the verified snapshot source.
- `backend/app/__init__.py` — price health validates the latest snapshot before reporting availability.
- `backend/tests/test_vintages.py` — normalized payload tamper regression.
- `backend/tests/test_research.py` — manifest tamper and cached-ready replay regressions.
## Root cause
The previous implementation trusted manifest metadata too early. A truthy string such as `"false"` could pass a boolean gate, and a ready report could be returned from cache before current snapshot bytes were revalidated. The previous vintage loader also checked identity but not raw/normalized content against the manifest.
## Fix
All new persisted snapshots carry:
```text
normalized_hash_algorithm: sha256-json-canonical-v1
normalized_snapshot_hash: SHA-256(sorted-key compact UTF-8 JSON, excluding only the hash field)
raw_payload_hash: SHA-256(raw provider payload)
```
Loads now verify raw files, normalized content, manifest metadata, strict boolean types, and snapshot identity before the research runner can return a cached result or run an event study.
## Live evidence
```text
BOT source restarted and replayable=true.
Price snapshot re-collected under the new schema; 9 symbols; point_in_time=false.
GET /api/v1/prices/health → HTTP 200, available=true, revised_vendor_history.
POST /api/v1/research/tourism/run → HTTP 200, blocked 1/12 releases.
```
## Verification
```text
PYTHONPATH=backend .venv/bin/python -W error -m unittest discover -s backend/tests -v
Ran 57 tests ... OK
npm run build
Vite build completed successfully.
npm audit --omit=dev --audit-level=high
found 0 vulnerabilities
python -m compileall -q backend
python /tmp/set50_platform_security_scan.py → {}
git diff --check
```
## Boundary
Content hashes are a local artifact/corruption control. They are not a signature against a hostile operator who can rewrite the application, manifests, raw files and runtime environment. Add a trusted deployment signing key before multi-user or hostile-host deployment.
## Independent review
```text
passed: true
security_concerns: []
logic_errors: []
```
Non-blocking backlog: retain the canonicalization/hash-version contract, keep crash/partial-write coverage, and introduce signed manifests when the deployment threat model expands.

View File

@@ -0,0 +1,48 @@
# Test evidence — 2026-08-23 integrity hardening
## Automated
```text
PYTHONPATH=backend .venv/bin/python -W error -m unittest discover -s backend/tests -v
Ran 57 tests ... OK
npm run build
Vite build completed successfully.
npm audit --omit=dev --audit-level=high
found 0 vulnerabilities
python -m compileall -q backend
python /tmp/set50_platform_security_scan.py
{}
git diff --check
```
## Regression coverage
- Tampered normalized Tourism snapshot is rejected.
- Tampered price manifest `point_in_time` metadata is rejected.
- A cached ready report is not replayed after price manifest tampering.
- Raw payload hash mismatch is rejected.
- Timezone-equivalent releases are treated as revisions.
- Weekend/holiday events use the first trading session after the event date.
## Live
```text
GET /api/v1/health → HTTP 200, version=0.5.0
GET /api/v1/prices/health → HTTP 200, available=true, point_in_time=false
GET /api/v1/data-health → HTTP 200, replayable=true
POST /api/v1/research/tourism/run → HTTP 200, blocked=1/12
```
## Independent review
```text
passed: true
security_concerns: []
logic_errors: []
```
Review passed under the stated local single-user threat model.