test
This commit is contained in:
479
.scratch/alpha-hardening/prd.json
Normal file
479
.scratch/alpha-hardening/prd.json
Normal file
@@ -0,0 +1,479 @@
|
|||||||
|
{
|
||||||
|
"name": "Alpha Hardening",
|
||||||
|
"description": "Pre-release alpha hardening of neuron-tai money/trust path. Generated from .scratch/alpha-hardening/issues for Ralph unattended sequential execution.",
|
||||||
|
"branchName": "ralph/alpha-hardening",
|
||||||
|
"userStories": [
|
||||||
|
{
|
||||||
|
"id": "AH-001",
|
||||||
|
"title": "01 \u2014 C1: Authenticate hive gossip endpoints",
|
||||||
|
"description": "# 01 \u2014 C1: Authenticate hive gossip endpoints\n\n## What to build\n\nAdd authenticated peer identity to all tracker gossip mutation endpoints. Today any caller can push billing, account, and stats events without verification.\n\n**Code refs:**\n\n- `packages/tracker/meshnet_tracker/server.py` \u2014 `_handle_billing_gossip` (~2414\u20132427)\n- `packages/tracker/meshnet_tracker/server.py` \u2014 `_handle_accounts_gossip` (~2610\u20132623)\n- `packages/tracker/meshnet_tracker/server.py` \u2014 `_handle_stats_gossip` (~2355\u20132364)\n- `packages/tracker/meshnet_tracker/billing.py` \u2014 `apply_events` (~301\u2013311)\n- `packages/tracker/meshnet_tracker/accounts.py` \u2014 `apply_events` (~220\u2013226)\n\nImplement per ADR-0017 \u00a73 using the auth helper/config from issue 02: shared hive HMAC (body + timestamp) or mutual TLS between configured tracker peers. Reject unauthenticated gossip with 401.\n\n**Note:** `/v1/gossip` (node throughput fan-out, `server.py` ~1331) is **not** in scope for this issue \u2014 see ADR-0017 \u00a73 out-of-scope note.\n\n## Test-first\n\n1. Red: unauthenticated POST to `/v1/billing/gossip` applies a credit event today \u2014 test must fail after fix.\n2. Red: authenticated peer with valid HMAC applies events; invalid/missing auth returns 401 and `applied: 0`.\n3. Green: wire the issue-02 verifier/config (`--hive-secret` or peer cert paths) into the three hive mutation endpoints.\n\n## Acceptance criteria\n\n- [ ] `/v1/billing/gossip`, `/v1/accounts/gossip`, `/v1/stats/gossip` reject requests without valid hive auth\n- [ ] Authenticated peers replicate events as today (id-dedup preserved)\n- [ ] Config documented for multi-tracker dev setups\n- [ ] Tests cover reject + accept paths without live network\n\n## ADR links\n\n- [ADR-0017](../../docs/adr/0017-tracker-authentication-and-authorization.md)\n- [ADR-0016](../../docs/adr/0016-alpha-scope-and-known-limitations.md)\n\n## Blocked by\n\n- `02-a2-unified-auth-boundary.md` \u2014 owns shared auth middleware/config. Implement in the same PR if simpler.",
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
"`/v1/billing/gossip`, `/v1/accounts/gossip`, `/v1/stats/gossip` reject requests without valid hive auth",
|
||||||
|
"Authenticated peers replicate events as today (id-dedup preserved)",
|
||||||
|
"Config documented for multi-tracker dev setups",
|
||||||
|
"Tests cover reject + accept paths without live network",
|
||||||
|
"Keep the implementation scoped to this issue; do not refactor unrelated server.py areas",
|
||||||
|
"Update the source issue file Status header to done when complete",
|
||||||
|
"Run relevant pytest tests; run the full suite when practical or document why not"
|
||||||
|
],
|
||||||
|
"priority": 1,
|
||||||
|
"passes": true,
|
||||||
|
"status": "done",
|
||||||
|
"notes": "Source issue: .scratch/alpha-hardening/issues/01-c1-gossip-auth.md",
|
||||||
|
"dependsOn": [
|
||||||
|
"AH-002"
|
||||||
|
],
|
||||||
|
"completionNotes": "Marked done in source alpha-hardening issue file before PRD generation."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AH-002",
|
||||||
|
"title": "02 \u2014 A2: Unified auth boundary for privileged and financial reads",
|
||||||
|
"description": "# 02 \u2014 A2: Unified auth boundary for privileged and financial reads\n\n## What to build\n\nReplace header-presence stubs with a single auth middleware that resolves API keys, admin sessions, validator service tokens, and hive peer identity. Close leaks on financial and operator endpoints. This is the auth foundation issue; issue 01 should only apply hive auth to gossip endpoints once the helper exists.\n\n**Code refs:**\n\n- `packages/tracker/meshnet_tracker/server.py` \u2014 `_handle_billing_forfeit` (~2429\u20132464) \u2014 H3: non-empty `Authorization` only\n- `packages/tracker/meshnet_tracker/server.py` \u2014 `_handle_benchmark_hop_penalty` (~2650\u20132658), `_handle_benchmark_results` (~2745\u20132748) \u2014 H3\n- `packages/tracker/meshnet_tracker/server.py` \u2014 `_handle_billing_summary` (~2366\u20132371) \u2014 H4\n- `packages/tracker/meshnet_tracker/server.py` \u2014 `_handle_billing_settlements` (~2407\u20132412) \u2014 H4\n- `packages/tracker/meshnet_tracker/server.py` \u2014 `_handle_registry_wallets` (~2391\u20132405) \u2014 H4\n- `packages/tracker/meshnet_tracker/server.py` \u2014 `_session_account` (~2468+), `_handle_admin_accounts` (~2588\u20132608) \u2014 H4\n- `packages/tracker/meshnet_tracker/accounts.py` \u2014 `session_account()`, `create_session()` only (session store; not handler wiring)\n\nPer ADR-0017 \u00a74: forfeit \u2192 validator or admin; benchmark \u2192 admin; billing summary/settlements/registry wallets \u2192 admin session. Include the validator service token shape from `20-validator-service-token.md` in the same implementation if practical.\n\n## Test-first\n\n1. Red: POST `/v1/billing/forfeit` with `Authorization: Bearer garbage` succeeds today \u2014 must require validator/admin identity.\n2. Red: GET `/v1/billing/summary` without admin session returns 401/403.\n3. Green: middleware + role checks; existing inference API-key path unchanged.\n\n## Acceptance criteria\n\n- [ ] Single `_require_auth(role=...)` (or equivalent) used by all privileged handlers\n- [ ] Shared auth config supports admin sessions, validator service token, and hive peer HMAC/mTLS\n- [ ] Forfeit accepts only validator service token or admin session \u2014 not arbitrary Bearer strings\n- [ ] Financial read endpoints require admin session (alpha posture)\n- [ ] Benchmark write/read require admin or service token\n- [ ] Integration tests for each endpoint class (reject unauth, accept valid)\n\n## ADR links\n\n- [ADR-0017](../../docs/adr/0017-tracker-authentication-and-authorization.md)\n\n## Related\n\n- `20-validator-service-token.md` \u2014 checklist for validator service token format, rotation, forfeit auth\n\n## Blocked by\n\nNone. This issue should land before `01-c1-gossip-auth.md`.",
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
"Single `_require_auth(role=...)` (or equivalent) used by all privileged handlers",
|
||||||
|
"Shared auth config supports admin sessions, validator service token, and hive peer HMAC/mTLS",
|
||||||
|
"Forfeit accepts only validator service token or admin session \u2014 not arbitrary Bearer strings",
|
||||||
|
"Financial read endpoints require admin session (alpha posture)",
|
||||||
|
"Benchmark write/read require admin or service token",
|
||||||
|
"Integration tests for each endpoint class (reject unauth, accept valid)",
|
||||||
|
"Keep the implementation scoped to this issue; do not refactor unrelated server.py areas",
|
||||||
|
"Update the source issue file Status header to done when complete",
|
||||||
|
"Run relevant pytest tests; run the full suite when practical or document why not"
|
||||||
|
],
|
||||||
|
"priority": 2,
|
||||||
|
"passes": true,
|
||||||
|
"status": "done",
|
||||||
|
"notes": "Source issue: .scratch/alpha-hardening/issues/02-a2-unified-auth-boundary.md",
|
||||||
|
"dependsOn": [],
|
||||||
|
"completionNotes": "Marked done in source alpha-hardening issue file before PRD generation."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AH-003",
|
||||||
|
"title": "03 \u2014 C5 + M1: Starting credit 0, funded-account gate, spend cap",
|
||||||
|
"description": "# 03 \u2014 C5 + M1: Starting credit 0, funded-account gate, spend cap\n\n## What to build\n\nClose the free-credit faucet. New API keys start at **0 USDT**; inference requires a real deposit or admin credit. Add a configurable per-request spend cap (M1) to limit runaway charges on compromised keys.\n\n**Code refs:**\n\n- `packages/tracker/meshnet_tracker/billing.py` \u2014 `DEFAULT_STARTING_CREDIT = 1.0` (~22), `ensure_client` (~73\u201385), `has_funds` (~87\u201388), duplicate credit on charge (~130\u2013138)\n- `packages/tracker/meshnet_tracker/server.py` \u2014 billing gate before routing (~1667\u20131690)\n\nPer ADR-0017 \u00a72 and ADR-0016 \u00a73.\n\n## Test-first\n\n1. Red: new API key gets 1.0 USDT implicit credit \u2014 test expects 0 balance until deposit.\n2. Red: first inference without deposit returns 402.\n3. Green: `DEFAULT_STARTING_CREDIT = 0.0`; optional `--max-charge-per-request` config.\n\n## Acceptance criteria\n\n- [ ] `DEFAULT_STARTING_CREDIT` is 0.0; no automatic caller credit on first touch\n- [ ] `has_funds` false for fresh keys; 402 before routing (server.py ~1684)\n- [ ] Admin `credit_client` or bound-wallet deposit still funds accounts\n- [ ] Configurable max charge per request (M1) rejects oversize completions with clear error\n- [ ] Tests: fresh key blocked; after credit/deposit, inference proceeds\n\n## ADR links\n\n- [ADR-0017](../../docs/adr/0017-tracker-authentication-and-authorization.md)\n- [ADR-0016](../../docs/adr/0016-alpha-scope-and-known-limitations.md)\n- [ADR-0015](../../docs/adr/0015-usdt-custodial-settlement.md)\n\n## Blocked by\n\n- `02-a2-unified-auth-boundary.md` (admin credit path secured)",
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
"`DEFAULT_STARTING_CREDIT` is 0.0; no automatic caller credit on first touch",
|
||||||
|
"`has_funds` false for fresh keys; 402 before routing (server.py ~1684)",
|
||||||
|
"Admin `credit_client` or bound-wallet deposit still funds accounts",
|
||||||
|
"Configurable max charge per request (M1) rejects oversize completions with clear error",
|
||||||
|
"Tests: fresh key blocked; after credit/deposit, inference proceeds",
|
||||||
|
"Keep the implementation scoped to this issue; do not refactor unrelated server.py areas",
|
||||||
|
"Update the source issue file Status header to done when complete",
|
||||||
|
"Run relevant pytest tests; run the full suite when practical or document why not"
|
||||||
|
],
|
||||||
|
"priority": 3,
|
||||||
|
"passes": false,
|
||||||
|
"status": "open",
|
||||||
|
"notes": "Source issue: .scratch/alpha-hardening/issues/03-c5-starting-credit-zero.md",
|
||||||
|
"dependsOn": [
|
||||||
|
"AH-002"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AH-004",
|
||||||
|
"title": "04 \u2014 H2: Tracker-authoritative token and work-unit accounting",
|
||||||
|
"description": "# 04 \u2014 H2: Tracker-authoritative token and work-unit accounting\n\n## What to build\n\nStop trusting node-reported usage for billing. The tracker already proxies responses \u2014 use tracker-observed response data and request limits to cap billable tokens, and compute work units from the **route it constructed**, not node declarations.\n\n**Code refs:**\n\n- `packages/tracker/meshnet_tracker/server.py` \u2014 `node_work` from route construction (~1776\u20131782, ~1781\u20131782)\n- `packages/tracker/meshnet_tracker/server.py` \u2014 streaming token/chunk billing (~1890\u20131921)\n- `packages/tracker/meshnet_tracker/server.py` \u2014 non-streaming `_usage_total_tokens` (~1938\u20131943)\n- `packages/tracker/meshnet_tracker/billing.py` \u2014 `charge_request` node_work split (~104\u2013151)\n\nAccounting fraud = inflating tokens or shard span. Per ADR-0018 \u00a75.\n\n## Test-first\n\n1. Red: mock upstream returns inflated `usage.total_tokens` in body but tracker bills that value \u2014 test expects the tracker to cap billable tokens from observed stream chunks or request bounds.\n2. Red: node registers false `shard_end`; billing uses tracker route span, not registration field alone.\n3. Green: authoritative counters; ignore node-reported work units on charge path.\n\n## Acceptance criteria\n\n- [ ] Streaming token count uses tracker-observed chunks/tokens; upstream `usage.total_tokens` can only lower or match that observed count, never inflate it\n- [ ] Non-streaming token count caps upstream `usage.total_tokens` by tracker-known request bounds (`max_tokens`, and prompt estimate if available); exact tokenizer-backed counts are deferred unless already available locally\n- [ ] Work units = tracker-computed layer span per hop at route build time (~1781\u20131782)\n- [ ] Nodes cannot increase payout by lying about shard range mid-request\n- [ ] Integration test: malicious node metadata does not inflate `charge_request` shares\n\n## ADR links\n\n- [ADR-0018](../../docs/adr/0018-fraud-detection-verification-and-reputation.md) \u00a75\n\n## Blocked by\n\n- `02-a2-unified-auth-boundary.md`",
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
"Streaming token count uses tracker-observed chunks/tokens; upstream `usage.total_tokens` can only lower or match that observed count, never inflate it",
|
||||||
|
"Non-streaming token count caps upstream `usage.total_tokens` by tracker-known request bounds (`max_tokens`, and prompt estimate if available); exact tokenizer-backed counts are deferred unless already available locally",
|
||||||
|
"Work units = tracker-computed layer span per hop at route build time (~1781\u20131782)",
|
||||||
|
"Nodes cannot increase payout by lying about shard range mid-request",
|
||||||
|
"Integration test: malicious node metadata does not inflate `charge_request` shares",
|
||||||
|
"Keep the implementation scoped to this issue; do not refactor unrelated server.py areas",
|
||||||
|
"Update the source issue file Status header to done when complete",
|
||||||
|
"Run relevant pytest tests; run the full suite when practical or document why not"
|
||||||
|
],
|
||||||
|
"priority": 4,
|
||||||
|
"passes": false,
|
||||||
|
"status": "open",
|
||||||
|
"notes": "Source issue: .scratch/alpha-hardening/issues/04-h2-tracker-authoritative-accounting.md",
|
||||||
|
"dependsOn": [
|
||||||
|
"AH-002"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AH-005",
|
||||||
|
"title": "05 \u2014 A1/A5: Persist strike, ban, and reputation state",
|
||||||
|
"description": "# 05 \u2014 A1/A5: Persist strike, ban, and reputation state\n\n## What to build\n\nRegistry strike/ban/reputation state today lives in RAM-only `_LocalContractState` \u2014 tracker restart wipes penalties. Persist to SQLite (same pattern as `BillingLedger` and `AccountStore`) so reputation carries forward per ADR-0016 \u00a74.\n\n**Code refs:**\n\n- `packages/contracts/meshnet_contracts/__init__.py` \u2014 `RegistryContract`, `RegistryWallet`, in-memory `_state.registry` (~103\u2013206)\n- `packages/tracker/meshnet_tracker/billing.py` \u2014 SQLite persistence pattern (~60, event log)\n- `packages/tracker/meshnet_tracker/accounts.py` \u2014 SQLite + event replication (~40\u201356)\n\nInclude fields for: `strike_count`, `banned`, `completed_job_count`, graduated **reputation score** (float, default 1.0), `last_audit_ts`, probation tracking.\n\n**Scope split:** this issue owns **schema + persistence + load/reload** only. Reputation **scoring deltas** (audit pass/fail adjustments, decay rules) belong in issue 08.\n\n## Test-first\n\n1. Red: record strike, restart tracker process, strike count is 0 \u2014 must fail.\n2. Green: persist + reload; gossip replicates strike events if multi-tracker.\n3. Red: banned wallet registers node \u2014 must reject (wire to routing).\n\n## Acceptance criteria\n\n- [ ] Strike/ban/reputation survive tracker restart (SQLite or equivalent)\n- [ ] `RegistryContract.list_wallets` reflects persisted state\n- [ ] Banned wallet rejected at registration and excluded from routes\n- [ ] Reputation score field present for routing/audit issues (08\u201309)\n- [ ] Event-sourced mutations compatible with future Raft (ADR-0019)\n\n## ADR links\n\n- [ADR-0016](../../docs/adr/0016-alpha-scope-and-known-limitations.md) \u00a74\n- [ADR-0018](../../docs/adr/0018-fraud-detection-verification-and-reputation.md) \u00a76\n\n## Blocked by\n\n- `02-a2-unified-auth-boundary.md`",
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
"Strike/ban/reputation survive tracker restart (SQLite or equivalent)",
|
||||||
|
"`RegistryContract.list_wallets` reflects persisted state",
|
||||||
|
"Banned wallet rejected at registration and excluded from routes",
|
||||||
|
"Reputation score field present for routing/audit issues (08\u201309)",
|
||||||
|
"Event-sourced mutations compatible with future Raft (ADR-0019)",
|
||||||
|
"Keep the implementation scoped to this issue; do not refactor unrelated server.py areas",
|
||||||
|
"Update the source issue file Status header to done when complete",
|
||||||
|
"Run relevant pytest tests; run the full suite when practical or document why not"
|
||||||
|
],
|
||||||
|
"priority": 5,
|
||||||
|
"passes": false,
|
||||||
|
"status": "open",
|
||||||
|
"notes": "Source issue: .scratch/alpha-hardening/issues/05-a1-a5-persist-strike-ban-reputation.md",
|
||||||
|
"dependsOn": [
|
||||||
|
"AH-002"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AH-006",
|
||||||
|
"title": "06 \u2014 FRAUD: TOPLOC integration (teacher-forced audit primitive)",
|
||||||
|
"description": "# 06 \u2014 FRAUD: TOPLOC integration (teacher-forced audit primitive)\n\n## What to build\n\nAdopt [TOPLOC](https://github.com/PrimeIntellect-ai/toploc) (MIT, `pip install toploc`) for activation fingerprint commit and verify. Replace string-equality validator checks with teacher-forced prefill + TOPLOC tolerance matching.\n\n**Estimated effort:** 2+ sessions. First landing should be the validator-only TOPLOC primitive and docs; node runtime commitments/on-demand capture can follow in issue 07 if this grows.\n\n| Subtask | Owner package | Deliverable |\n|---|---|---|\n| Validator audit primitive | `packages/validator/` | Teacher-forced prefill, TOPLOC verify, unit tests with stub tensors |\n| Node runtime commitments | `packages/node/` (if prover-side) | On-demand activation fingerprint generation on audit-selected requests; move to issue 07 if it blocks the validator primitive |\n\n**Code refs:**\n\n- `packages/validator/meshnet_validator/__init__.py` \u2014 `_run_reference`, `_outputs_match` (~92\u2013148)\n- `packages/validator/README.md` \u2014 deterrence math (update for 19\u00d7 at p=0.05)\n- Research: `.scratch/alpha-hardening/research-verifiable-inference.md` \u00a78 layers 1\u20132, build-vs-adopt table\n\nPin one canonical precision/quantization per model preset. Add `toploc` to validator (and node if prover-side) dependencies.\n\n## Test-first\n\n1. Red: validator compares final text strings \u2014 fails on cross-GPU honest divergence (document expected).\n2. Green: stub activation tensors + TOPLOC proofs round-trip in unit test.\n3. Integration: reference node teacher-forces tokens; verify accepts honest proof, rejects swapped precision.\n\n## Acceptance criteria\n\n- [ ] `toploc` dependency declared; `build_proofs_*` / `verify_proofs_*` wired\n- [ ] Validator re-runs claimed token sequence as prefill, not free generation\n- [ ] Model preset documents canonical dtype/quantization\n- [ ] README updated: 19\u00d7 deterrence at 5% audit (research \u00a71.1)\n- [ ] Tests with deterministic stub tensors (no GPU required in CI)\n\n## ADR links\n\n- [ADR-0018](../../docs/adr/0018-fraud-detection-verification-and-reputation.md) \u00a72\n- Research: [research-verifiable-inference.md](../research-verifiable-inference.md) \u00a78, \u00a79 build-vs-adopt\n\n## Blocked by\n\n- `05-a1-a5-persist-strike-ban-reputation.md`\n\n**Prod gate:** do not enable production audit thresholds until `21-honest-noise-calibration-corpus.md` completes (see README Phase 2 note).",
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
"`toploc` dependency declared; `build_proofs_*` / `verify_proofs_*` wired",
|
||||||
|
"Validator re-runs claimed token sequence as prefill, not free generation",
|
||||||
|
"Model preset documents canonical dtype/quantization",
|
||||||
|
"README updated: 19\u00d7 deterrence at 5% audit (research \u00a71.1)",
|
||||||
|
"Tests with deterministic stub tensors (no GPU required in CI)",
|
||||||
|
"Keep the implementation scoped to this issue; do not refactor unrelated server.py areas",
|
||||||
|
"Update the source issue file Status header to done when complete",
|
||||||
|
"Run relevant pytest tests; run the full suite when practical or document why not"
|
||||||
|
],
|
||||||
|
"priority": 6,
|
||||||
|
"passes": false,
|
||||||
|
"status": "open",
|
||||||
|
"notes": "Source issue: .scratch/alpha-hardening/issues/06-fraud-toploc-integration.md",
|
||||||
|
"dependsOn": [
|
||||||
|
"AH-005"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AH-007",
|
||||||
|
"title": "07 \u2014 FRAUD: On-demand commitment + hop bisection blame",
|
||||||
|
"description": "# 07 \u2014 FRAUD: On-demand commitment + hop bisection blame\n\n## What to build\n\nOn audit selection, require nodes to supply TOPLOC-style fingerprints of **output boundary activations** per hop (on-demand, brief retention). On verify failure, referee identifies the **first divergent hop** \u2014 not always the last text node.\n\n**Code refs:**\n\n- `packages/validator/meshnet_validator/__init__.py` \u2014 `_slash_route`, `_final_text_node` bug (~102\u2013140) \u2014 blames `max(shard_end)` only\n- `packages/tracker/meshnet_tracker/server.py` \u2014 route hop construction (~1774\u20131783) \u2014 cut-points for bisection\n- Research: `.scratch/alpha-hardening/research-verifiable-inference.md` \u00a71.2, \u00a78 layer 3 (Verde **pattern**, not on-chain game)\n\n## Test-first\n\n1. Red: two-hop route, corrupt hop-0 activations \u2014 `_final_text_node` blames hop-1 \u2014 test must fail.\n2. Green: bisection selects hop-0; forfeit targets hop-0 wallet.\n3. On-demand: commitment requested only when audit flag set on proxied request.\n\n## Acceptance criteria\n\n- [ ] Audit requests carry tracker RNG/VRF flag indistinguishable from normal traffic (research \u00a76)\n- [ ] Nodes retain recent boundary activations for on-demand commit window (configurable TTL)\n- [ ] Validator/tracker compares fingerprints at each hop cut-point; first mismatch = culprit\n- [ ] `_final_text_node` removed or limited to text-only fallback\n- [ ] Integration test: multi-hop pipeline, fault injected at known hop\n\n## ADR links\n\n- [ADR-0018](../../docs/adr/0018-fraud-detection-verification-and-reputation.md) \u00a73\u20134\n\n## Blocked by\n\n- `06-fraud-toploc-integration.md`",
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
"Audit requests carry tracker RNG/VRF flag indistinguishable from normal traffic (research \u00a76)",
|
||||||
|
"Nodes retain recent boundary activations for on-demand commit window (configurable TTL)",
|
||||||
|
"Validator/tracker compares fingerprints at each hop cut-point; first mismatch = culprit",
|
||||||
|
"`_final_text_node` removed or limited to text-only fallback",
|
||||||
|
"Integration test: multi-hop pipeline, fault injected at known hop",
|
||||||
|
"Keep the implementation scoped to this issue; do not refactor unrelated server.py areas",
|
||||||
|
"Update the source issue file Status header to done when complete",
|
||||||
|
"Run relevant pytest tests; run the full suite when practical or document why not"
|
||||||
|
],
|
||||||
|
"priority": 7,
|
||||||
|
"passes": false,
|
||||||
|
"status": "open",
|
||||||
|
"notes": "Source issue: .scratch/alpha-hardening/issues/07-fraud-commitment-bisection-blame.md",
|
||||||
|
"dependsOn": [
|
||||||
|
"AH-006"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AH-008",
|
||||||
|
"title": "08 \u2014 FRAUD: Reputation model + persistence",
|
||||||
|
"description": "# 08 \u2014 FRAUD: Reputation model + persistence\n\n## What to build\n\nImplement graduated reputation per ADR-0018 \u00a76: score derives only from tracker audit outcomes + uptime/latency. Slow build, instant loss, inactivity decay. \u00d70.8 routing multiplier per strike (not whole penalty \u2014 forfeiture stays full pending).\n\n**Scope split:** issue 05 owns **schema + SQLite persistence**; this issue owns **scoring rules** (deltas, decay, strike\u2192multiplier wiring) on top of persisted fields.\n\n**Code refs:**\n\n- `packages/contracts/meshnet_contracts/__init__.py` \u2014 extend `RegistryWallet` / persistence from issue 05\n- `packages/validator/meshnet_validator/__init__.py` \u2014 `_slash_route` forfeiture path (~125\u2013133)\n- `packages/tracker/meshnet_tracker/billing.py` \u2014 `forfeit_pending` (~280\u2013292)\n- Research: `.scratch/alpha-hardening/research-verifiable-inference.md` \u00a76\n\n## Test-first\n\n1. Red: persisted reputation/strike fields from issue 05 are ignored by scoring/routing today.\n2. Green: clean audit +0.05 (tunable); failed audit \u22120.3 and strike; three strikes \u2192 ban persisted via issue-05 fields.\n3. Inactivity decay after N days without completed jobs.\n\n## Acceptance criteria\n\n- [ ] Uses `reputation_score` and strike/ban fields persisted by issue 05; does not introduce a second schema path\n- [ ] Audit pass/fail updates score with documented deltas\n- [ ] Strike applies \u00d70.8 multiplier to routing weight (separate from forfeiture amount)\n- [ ] Ban at 3 strikes; probation job count still enforced\n- [ ] No peer-to-peer reputation inputs\n\n## ADR links\n\n- [ADR-0018](../../docs/adr/0018-fraud-detection-verification-and-reputation.md) \u00a76\n\n## Blocked by\n\n- `05-a1-a5-persist-strike-ban-reputation.md`\n- `07-fraud-commitment-bisection-blame.md` (audit outcomes feed reputation)",
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
"Uses `reputation_score` and strike/ban fields persisted by issue 05; does not introduce a second schema path",
|
||||||
|
"Audit pass/fail updates score with documented deltas",
|
||||||
|
"Strike applies \u00d70.8 multiplier to routing weight (separate from forfeiture amount)",
|
||||||
|
"Ban at 3 strikes; probation job count still enforced",
|
||||||
|
"No peer-to-peer reputation inputs",
|
||||||
|
"Keep the implementation scoped to this issue; do not refactor unrelated server.py areas",
|
||||||
|
"Update the source issue file Status header to done when complete",
|
||||||
|
"Run relevant pytest tests; run the full suite when practical or document why not"
|
||||||
|
],
|
||||||
|
"priority": 8,
|
||||||
|
"passes": false,
|
||||||
|
"status": "open",
|
||||||
|
"notes": "Source issue: .scratch/alpha-hardening/issues/08-fraud-reputation-model-persistence.md",
|
||||||
|
"dependsOn": [
|
||||||
|
"AH-005",
|
||||||
|
"AH-007"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AH-009",
|
||||||
|
"title": "09 \u2014 FRAUD: Reputation-weighted routing + adaptive audit rate",
|
||||||
|
"description": "# 09 \u2014 FRAUD: Reputation-weighted routing + adaptive audit rate\n\n## What to build\n\nWire reputation into route selection and audit sampling. Default network audit budget \u22485% \u2014 **not a cap**. New/low-reputation nodes: 20\u201330% audit rate; veterans: 2\u20133% floor \u22652%. Tripwires escalate rate without direct punishment.\n\n**Code refs:**\n\n- `packages/tracker/meshnet_tracker/server.py` \u2014 route selection `_select_route`, `_effective_throughput` (~1747, routing helpers)\n- `packages/validator/meshnet_validator/__init__.py` \u2014 `sample_rate=0.05`\n- Research: `.scratch/alpha-hardening/research-verifiable-inference.md` \u00a71.1, \u00a76, \u00a78 layers 2\u20134\n\nAudit selection must be unpredictable at request time (tracker RNG after commitment window opens).\n\n## Test-first\n\n1. Red: uniform 5% sample regardless of reputation \u2014 test expects higher rate for low-reputation wallet.\n2. Green: budget balancer keeps fleet-wide average \u2248 configured target.\n3. Routing prefers higher reputation among equal throughput candidates.\n\n## Acceptance criteria\n\n- [ ] Per-wallet audit probability function of reputation (newcomer high, veteran low, floor \u22652%)\n- [ ] Fleet-wide audit budget configurable (~5% default target); over \u22651000 requests with fixed seed, measured fleet audit rate within **\u00b11.0 percentage point** of configured target (e.g. 4.0\u20136.0% at 5% default)\n- [ ] Route scoring includes reputation multiplier (earnings scale with tenure)\n- [ ] Passive tripwire flags (perplexity/repetition) bump audit rate only\n- [ ] Tests: deterministic seed for sampling distribution checks\n\n## ADR links\n\n- [ADR-0018](../../docs/adr/0018-fraud-detection-verification-and-reputation.md) \u00a71, \u00a76\u20137\n- [ADR-0013](../../docs/adr/0013-rolling-stats-smart-routing.md)\n\n## Blocked by\n\n- `08-fraud-reputation-model-persistence.md`",
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
"Per-wallet audit probability function of reputation (newcomer high, veteran low, floor \u22652%)",
|
||||||
|
"Fleet-wide audit budget configurable (~5% default target); over \u22651000 requests with fixed seed, measured fleet audit rate within **\u00b11.0 percentage point** of configured target (e.g. 4.0\u20136.0% at 5% default)",
|
||||||
|
"Route scoring includes reputation multiplier (earnings scale with tenure)",
|
||||||
|
"Passive tripwire flags (perplexity/repetition) bump audit rate only",
|
||||||
|
"Tests: deterministic seed for sampling distribution checks",
|
||||||
|
"Keep the implementation scoped to this issue; do not refactor unrelated server.py areas",
|
||||||
|
"Update the source issue file Status header to done when complete",
|
||||||
|
"Run relevant pytest tests; run the full suite when practical or document why not"
|
||||||
|
],
|
||||||
|
"priority": 9,
|
||||||
|
"passes": false,
|
||||||
|
"status": "open",
|
||||||
|
"notes": "Source issue: .scratch/alpha-hardening/issues/09-fraud-reputation-routing-adaptive-audit.md",
|
||||||
|
"dependsOn": [
|
||||||
|
"AH-008"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AH-010",
|
||||||
|
"title": "10 \u2014 FRAUD: Penalty calibration wiring (forfeit + strike + ban)",
|
||||||
|
"description": "# 10 \u2014 FRAUD: Penalty calibration wiring (forfeit + strike + ban)\n\n## What to build\n\nEnd-to-end wiring: confirmed audit failure \u2192 atomic pending forfeiture + strike + reputation decay + audit-rate snap to max. Ensure payout cannot race penalty (ADR-0015). Document 19\u00d7 deterrence math in validator README.\n\n**Code refs:**\n\n- `packages/validator/meshnet_validator/__init__.py` \u2014 `_slash_route` (~102\u2013134)\n- `packages/tracker/meshnet_tracker/server.py` \u2014 `_handle_billing_forfeit` (~2429\u20132464)\n- `packages/tracker/meshnet_tracker/billing.py` \u2014 `forfeit_pending` (~280\u2013292), payout exclusion for banned (~3337\u20133344 in settlement loop)\n- `packages/validator/README.md` \u2014 update 20\u00d7 \u2192 19\u00d7 at p=0.05\n\nPer ADR-0018: **full pending forfeiture** is primary penalty; \u00d70.8 is routing decay per strike, not partial forfeit.\n\n## Test-first\n\n1. Red: integration from issue 34 \u2014 extend with multi-hop blame wallet from issue 07.\n2. Green: node with pending balance \u2192 audit fail \u2192 pending zero, strike++, banned on 3rd, excluded from next settlement.\n3. Settlement loop skips banned wallets (~3337\u20133344).\n\n## Acceptance criteria\n\n- [ ] Audit failure triggers forfeiture + strike in one tracker transaction\n- [ ] Banned nodes excluded from `payables` / settlement\n- [ ] Validator uses authenticated forfeit endpoint (issue 02)\n- [ ] README: `L > 19\u00d7 g` at p=0.05; pending balance = collateral\n- [ ] Integration test: 60-request fraud scenario \u2192 ban within threshold\n\n## ADR links\n\n- [ADR-0018](../../docs/adr/0018-fraud-detection-verification-and-reputation.md)\n- [ADR-0015](../../docs/adr/0015-usdt-custodial-settlement.md)\n- Research: [research-verifiable-inference.md](../research-verifiable-inference.md) \u00a71.1\n\n## Blocked by\n\n- `07-fraud-commitment-bisection-blame.md`\n- `08-fraud-reputation-model-persistence.md`\n- `02-a2-unified-auth-boundary.md`",
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
"Audit failure triggers forfeiture + strike in one tracker transaction",
|
||||||
|
"Banned nodes excluded from `payables` / settlement",
|
||||||
|
"Validator uses authenticated forfeit endpoint (issue 02)",
|
||||||
|
"README: `L > 19\u00d7 g` at p=0.05; pending balance = collateral",
|
||||||
|
"Integration test: 60-request fraud scenario \u2192 ban within threshold",
|
||||||
|
"Keep the implementation scoped to this issue; do not refactor unrelated server.py areas",
|
||||||
|
"Update the source issue file Status header to done when complete",
|
||||||
|
"Run relevant pytest tests; run the full suite when practical or document why not"
|
||||||
|
],
|
||||||
|
"priority": 10,
|
||||||
|
"passes": false,
|
||||||
|
"status": "open",
|
||||||
|
"notes": "Source issue: .scratch/alpha-hardening/issues/10-fraud-penalty-calibration-wiring.md",
|
||||||
|
"dependsOn": [
|
||||||
|
"AH-007",
|
||||||
|
"AH-008",
|
||||||
|
"AH-002"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AH-011",
|
||||||
|
"title": "11 \u2014 C6: Wallet binding ownership proof + binding overwrite safety",
|
||||||
|
"description": "# 11 \u2014 C6: Wallet binding ownership proof + binding overwrite safety\n\n## What to build\n\n`POST /v1/wallet/register` binds a client Solana wallet to an API key for deposit attribution. Today any Bearer key can bind any wallet string without proving ownership. Prevent hijack and accidental overwrite.\n\n**Code refs:**\n\n- `packages/tracker/meshnet_tracker/server.py` \u2014 `_handle_wallet_register` (~2625\u20132648)\n- `packages/tracker/meshnet_tracker/billing.py` \u2014 `bind_wallet` (~153+), `_wallet_bindings` / direct overwrite on apply (~351)\n\nRequire signed message from wallet pubkey (ed25519 via `cryptography` / solders). Reject rebinding without admin or signed release. Use explicit overwrite policy \u2014 today `~351` overwrites binding directly; gossip apply must reject conflicting binds instead of silently clobbering.\n\n## Test-first\n\n1. Red: bind wallet A with only API key, no signature \u2014 must fail after fix.\n2. Red: wallet already bound to key1; key2 cannot steal without proof.\n3. Green: valid signature binds; deposit watcher credits correct API key.\n\n## Acceptance criteria\n\n- [ ] Wallet binding requires cryptographic proof of pubkey ownership\n- [ ] One wallet \u2192 one API key (or documented admin override)\n- [ ] Gossip `bind` events cannot overwrite existing binding via direct overwrite at `~351`\n- [ ] Tests with deterministic keypairs (local adapter)\n\n## ADR links\n\n- [ADR-0017](../../docs/adr/0017-tracker-authentication-and-authorization.md) \u00a75\n- [ADR-0015](../../docs/adr/0015-usdt-custodial-settlement.md)\n\n## Blocked by\n\n- `02-a2-unified-auth-boundary.md`\n- `03-c5-starting-credit-zero.md`",
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
"Wallet binding requires cryptographic proof of pubkey ownership",
|
||||||
|
"One wallet \u2192 one API key (or documented admin override)",
|
||||||
|
"Gossip `bind` events cannot overwrite existing binding via direct overwrite at `~351`",
|
||||||
|
"Tests with deterministic keypairs (local adapter)",
|
||||||
|
"Keep the implementation scoped to this issue; do not refactor unrelated server.py areas",
|
||||||
|
"Update the source issue file Status header to done when complete",
|
||||||
|
"Run relevant pytest tests; run the full suite when practical or document why not"
|
||||||
|
],
|
||||||
|
"priority": 11,
|
||||||
|
"passes": false,
|
||||||
|
"status": "open",
|
||||||
|
"notes": "Source issue: .scratch/alpha-hardening/issues/11-c6-wallet-binding-proof.md",
|
||||||
|
"dependsOn": [
|
||||||
|
"AH-002",
|
||||||
|
"AH-003"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AH-012",
|
||||||
|
"title": "12 \u2014 C2: On-chain settlement idempotency (deferred)",
|
||||||
|
"description": "# 12 \u2014 C2: On-chain settlement idempotency (deferred)\n\n## What to build\n\nHarden payout idempotency so Solana transaction retries never double-pay. Design accepted in ADR-0019 \u00a71; **implementation deferred post-alpha**.\n\n**Code refs:**\n\n- `packages/tracker/meshnet_tracker/server.py` \u2014 `_settlement_loop` resend (~3331\u20133356), `_send_settlement` (~3358\u20133376)\n- `packages/contracts/meshnet_contracts/solana_adapter.py` \u2014 `send_payouts` (~186\u2013213)\n\nToday: pending debited before broadcast with stable `settlement_id`; unconfirmed batches resent. Gap: on-chain confirmation vs ledger state if tx succeeds but confirm fails.\n\n## Acceptance criteria\n\n- [ ] `confirm_settlement` only after RPC finalized confirmation\n- [ ] Retry path reuses same `settlement_id` and detects already-confirmed signature\n- [ ] Property test: N retries \u2192 single on-chain transfer per wallet per settlement_id\n- [ ] Document recovery procedure for stuck unconfirmed batches\n\n## ADR links\n\n- [ADR-0019](../../docs/adr/0019-money-path-consistency-multi-tracker.md) \u00a71\n\n## Blocked by\n\nAlpha release (ADR-0016 single settlement tracker)",
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
"`confirm_settlement` only after RPC finalized confirmation",
|
||||||
|
"Retry path reuses same `settlement_id` and detects already-confirmed signature",
|
||||||
|
"Property test: N retries \u2192 single on-chain transfer per wallet per settlement_id",
|
||||||
|
"Document recovery procedure for stuck unconfirmed batches",
|
||||||
|
"Keep the implementation scoped to this issue; do not refactor unrelated server.py areas",
|
||||||
|
"Update the source issue file Status header to done when complete",
|
||||||
|
"Run relevant pytest tests; run the full suite when practical or document why not"
|
||||||
|
],
|
||||||
|
"priority": 12,
|
||||||
|
"passes": false,
|
||||||
|
"status": "in-design",
|
||||||
|
"notes": "Source issue: .scratch/alpha-hardening/issues/12-c2-on-chain-idempotency.md",
|
||||||
|
"dependsOn": [],
|
||||||
|
"status_reason": "Source issue is ready-for-human/deferred; skipped by unattended Ralph auto."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AH-013",
|
||||||
|
"title": "13 \u2014 C3/C4: Consensus-gated money mutations (deferred)",
|
||||||
|
"description": "# 13 \u2014 C3/C4: Consensus-gated money mutations (deferred)\n\n## What to build\n\nRoute money-affecting ledger events through Raft commit, not gossip-only apply. Extend `raft.py` command set beyond register/deregister. Settlement remains leader-only with treasury key.\n\n**Code refs:**\n\n- `packages/tracker/meshnet_tracker/server.py` \u2014 settlement leader gate (~3331\u20133332), payout batch (~3353\u20133356)\n- `packages/tracker/meshnet_tracker/raft.py` \u2014 log entry types (~26\u201327)\n- `packages/tracker/meshnet_tracker/billing.py` \u2014 `apply_events` (~301\u2013311)\n\nDesign: ADR-0019 \u00a72. **Deferred post-alpha** while single operator holds settlement.\n\n## Acceptance criteria\n\n- [ ] `charge`, `payout`, `forfeit`, `credit`, `settlement`, `bind` commit via Raft log\n- [ ] Followers reject direct gossip money mutations\n- [ ] Leader-only `_settlement_loop` unchanged in semantics\n- [ ] Migration plan from gossip-only billing to Raft-backed log\n\n## ADR links\n\n- [ADR-0019](../../docs/adr/0019-money-path-consistency-multi-tracker.md) \u00a72\n\n## Blocked by\n\n- `12-c2-on-chain-idempotency.md`\n- `14-a3-raft-durable-term-vote.md`",
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
"`charge`, `payout`, `forfeit`, `credit`, `settlement`, `bind` commit via Raft log",
|
||||||
|
"Followers reject direct gossip money mutations",
|
||||||
|
"Leader-only `_settlement_loop` unchanged in semantics",
|
||||||
|
"Migration plan from gossip-only billing to Raft-backed log",
|
||||||
|
"Keep the implementation scoped to this issue; do not refactor unrelated server.py areas",
|
||||||
|
"Update the source issue file Status header to done when complete",
|
||||||
|
"Run relevant pytest tests; run the full suite when practical or document why not"
|
||||||
|
],
|
||||||
|
"priority": 13,
|
||||||
|
"passes": false,
|
||||||
|
"status": "in-design",
|
||||||
|
"notes": "Source issue: .scratch/alpha-hardening/issues/13-c3-c4-consensus-gated-settlement.md",
|
||||||
|
"dependsOn": [
|
||||||
|
"AH-012",
|
||||||
|
"AH-014"
|
||||||
|
],
|
||||||
|
"status_reason": "Source issue is ready-for-human/deferred; skipped by unattended Ralph auto."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AH-014",
|
||||||
|
"title": "14 \u2014 A3: Durable Raft term and vote state (deferred)",
|
||||||
|
"description": "# 14 \u2014 A3: Durable Raft term and vote state (deferred)\n\n## What to build\n\nPersist Raft `currentTerm`, `votedFor`, and log metadata to disk. In-memory-only term (~26) risks split leadership after tracker restart \u2192 duplicate settlement epochs.\n\n**Code refs:**\n\n- `packages/tracker/meshnet_tracker/raft.py` \u2014 `LogEntry.term` (~25\u201327), election state in `RaftNode`\n\n## Acceptance criteria\n\n- [ ] Term/vote persisted alongside tracker data dir\n- [ ] Restart resumes as follower/candidate with monotonic term\n- [ ] Test: kill leader mid-settlement, restart, no duplicate payout batch\n\n## ADR links\n\n- [ADR-0019](../../docs/adr/0019-money-path-consistency-multi-tracker.md) \u00a73\n\n## Blocked by\n\nAlpha single-settlement posture",
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
"Term/vote persisted alongside tracker data dir",
|
||||||
|
"Restart resumes as follower/candidate with monotonic term",
|
||||||
|
"Test: kill leader mid-settlement, restart, no duplicate payout batch",
|
||||||
|
"Keep the implementation scoped to this issue; do not refactor unrelated server.py areas",
|
||||||
|
"Update the source issue file Status header to done when complete",
|
||||||
|
"Run relevant pytest tests; run the full suite when practical or document why not"
|
||||||
|
],
|
||||||
|
"priority": 14,
|
||||||
|
"passes": false,
|
||||||
|
"status": "in-design",
|
||||||
|
"notes": "Source issue: .scratch/alpha-hardening/issues/14-a3-raft-durable-term-vote.md",
|
||||||
|
"dependsOn": [],
|
||||||
|
"status_reason": "Source issue is ready-for-human/deferred; skipped by unattended Ralph auto."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AH-015",
|
||||||
|
"title": "15 \u2014 H1: Commutative forfeit event ordering (deferred)",
|
||||||
|
"description": "# 15 \u2014 H1: Commutative forfeit event ordering (deferred)\n\n## What to build\n\nDefine deterministic ordering when `forfeit`, `charge`, and `payout` events replicate concurrently. Forfeit snapshots amount at creation (~287) but apply order can desync pending balances under gossip.\n\n**Code refs:**\n\n- `packages/tracker/meshnet_tracker/billing.py` \u2014 `forfeit_pending` (~280\u2013292), `_apply_locked` forfeit branch (~345\u2013349)\n- `packages/tracker/meshnet_tracker/billing.py` \u2014 `_pending_since.setdefault` (~324), wallet bind direct overwrite (~351)\n\n## Acceptance criteria\n\n- [ ] Documented commit order: charges before forfeit before payout for same wallet epoch\n- [ ] Forfeit events carry pending snapshot or `(term, index)` for tie-break\n- [ ] `setdefault` replaced with explicit merge rules on out-of-order apply\n- [ ] Property tests under shuffled event delivery\n\n## ADR links\n\n- [ADR-0019](../../docs/adr/0019-money-path-consistency-multi-tracker.md) \u00a74\n\n## Blocked by\n\n- `13-c3-c4-consensus-gated-settlement.md`",
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
"Documented commit order: charges before forfeit before payout for same wallet epoch",
|
||||||
|
"Forfeit events carry pending snapshot or `(term, index)` for tie-break",
|
||||||
|
"`setdefault` replaced with explicit merge rules on out-of-order apply",
|
||||||
|
"Property tests under shuffled event delivery",
|
||||||
|
"Keep the implementation scoped to this issue; do not refactor unrelated server.py areas",
|
||||||
|
"Update the source issue file Status header to done when complete",
|
||||||
|
"Run relevant pytest tests; run the full suite when practical or document why not"
|
||||||
|
],
|
||||||
|
"priority": 15,
|
||||||
|
"passes": false,
|
||||||
|
"status": "in-design",
|
||||||
|
"notes": "Source issue: .scratch/alpha-hardening/issues/15-h1-commutative-forfeit.md",
|
||||||
|
"dependsOn": [
|
||||||
|
"AH-013"
|
||||||
|
],
|
||||||
|
"status_reason": "Source issue is ready-for-human/deferred; skipped by unattended Ralph auto."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AH-016",
|
||||||
|
"title": "16 \u2014 DOC: US-006 reconciliation note",
|
||||||
|
"description": "# 16 \u2014 DOC: US-006 reconciliation note\n\n## What to build\n\nReconcile stale US-006 (Solana testnet stake contracts) with ADR-0015/0016 devnet custodial settlement. Issue `docs/issues/06-solana-stake-and-settlement.md` says \"never devnet\"; ADR-0015 explicitly targets devnet mock-USDT.\n\nAlso reconcile legacy fraud issues with the alpha-hardening fraud arc:\n\n- `docs/issues/07-fraud-detection-slash.md` \u2014 on-chain stake slash model superseded by pending-balance forfeiture + TOPLOC (ADR-0018)\n- `docs/issues/34-forfeiture-penalty.md` \u2014 partially implemented; remaining fraud work lives in `.scratch/alpha-hardening/issues/06-fraud-toploc-integration.md` through `10-fraud-penalty-calibration-wiring.md`\n\n## Acceptance criteria\n\n- [ ] Add reconciliation comment atop `docs/issues/06-solana-stake-and-settlement.md` (Status: superseded for alpha \u2014 see ADR-0015, issue 33/34)\n- [ ] Add **superseded** banner atop `docs/issues/07-fraud-detection-slash.md` \u2192 ADR-0018 + issues 06\u201310\n- [ ] Add **superseded for remaining scope** banner atop `docs/issues/34-forfeiture-penalty.md` \u2192 ADR-0018 + issues 06\u201310 (note done items: basic forfeiture wired)\n- [ ] Update `docs/prd.json` US-006 description footnote if present\n- [ ] Cross-link ADR-0015 devnet decision\n- [ ] No production code changes\n\n## ADR links\n\n- [ADR-0015](../../docs/adr/0015-usdt-custodial-settlement.md)\n- [ADR-0016](../../docs/adr/0016-alpha-scope-and-known-limitations.md)\n\n## Blocked by\n\nNone",
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
"Add reconciliation comment atop `docs/issues/06-solana-stake-and-settlement.md` (Status: superseded for alpha \u2014 see ADR-0015, issue 33/34)",
|
||||||
|
"Add **superseded** banner atop `docs/issues/07-fraud-detection-slash.md` \u2192 ADR-0018 + issues 06\u201310",
|
||||||
|
"Add **superseded for remaining scope** banner atop `docs/issues/34-forfeiture-penalty.md` \u2192 ADR-0018 + issues 06\u201310 (note done items: basic forfeiture wired)",
|
||||||
|
"Update `docs/prd.json` US-006 description footnote if present",
|
||||||
|
"Cross-link ADR-0015 devnet decision",
|
||||||
|
"No production code changes",
|
||||||
|
"Keep the implementation scoped to this issue; do not refactor unrelated server.py areas",
|
||||||
|
"Update the source issue file Status header to done when complete",
|
||||||
|
"Run relevant pytest tests; run the full suite when practical or document why not"
|
||||||
|
],
|
||||||
|
"priority": 16,
|
||||||
|
"passes": false,
|
||||||
|
"status": "open",
|
||||||
|
"notes": "Source issue: .scratch/alpha-hardening/issues/16-doc-us006-reconciliation.md",
|
||||||
|
"dependsOn": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AH-017",
|
||||||
|
"title": "17 \u2014 DOC: Duplicate US-020 issue dedup",
|
||||||
|
"description": "# 17 \u2014 DOC: Duplicate US-020 issue dedup\n\n## What to build\n\nTwo files share the US-020 number with different slugs:\n\n- `docs/issues/20-memory-budget-shard-slots-and-dropout-relocation.md` (ready-for-agent)\n- `docs/issues/20-tracker-node-hardening.md` (done)\n\nResolve numbering collision without losing history.\n\n## Acceptance criteria\n\n- [ ] Document canonical mapping in this issue's Comments or a short `docs/issues/README.md` note\n- [ ] Renumber or prefix disambiguation (e.g. keep done item as US-020a, renumber memory-budget to next slot) \u2014 **human approval before git mv**\n- [ ] Update any prd.json / cross-links that reference US-020 ambiguously\n- [ ] No production code changes\n\n## Blocked by\n\nHuman approval for renumbering. An agent may prepare the mapping note, but must not run `git mv` or rewrite cross-links until the canonical number is approved.",
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
"Document canonical mapping in this issue's Comments or a short `docs/issues/README.md` note",
|
||||||
|
"Renumber or prefix disambiguation (e.g. keep done item as US-020a, renumber memory-budget to next slot) \u2014 **human approval before git mv**",
|
||||||
|
"Update any prd.json / cross-links that reference US-020 ambiguously",
|
||||||
|
"No production code changes",
|
||||||
|
"Keep the implementation scoped to this issue; do not refactor unrelated server.py areas",
|
||||||
|
"Update the source issue file Status header to done when complete",
|
||||||
|
"Run relevant pytest tests; run the full suite when practical or document why not"
|
||||||
|
],
|
||||||
|
"priority": 17,
|
||||||
|
"passes": false,
|
||||||
|
"status": "in-design",
|
||||||
|
"notes": "Source issue: .scratch/alpha-hardening/issues/17-doc-duplicate-us020-dedup.md",
|
||||||
|
"dependsOn": [],
|
||||||
|
"status_reason": "Source issue is ready-for-human/deferred; skipped by unattended Ralph auto."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AH-018",
|
||||||
|
"title": "18 \u2014 DOC: Operational runbooks (stubs)",
|
||||||
|
"description": "# 18 \u2014 DOC: Operational runbooks (stubs)\n\n## What to build\n\nAdd operational runbook stubs for alpha operators under `docs/runbooks/` (or `.scratch/alpha-hardening/runbooks/` until close-feature):\n\n1. **Ledger backup** \u2014 billing SQLite, accounts SQLite, registry DB paths; gossip pause procedure\n2. **Treasury key rotation** \u2014 devnet mock-USDT mint + treasury keypair rotation without double-credit\n3. **Upgrade path** \u2014 tracker rolling restart with persisted strike/reputation (post issue 05)\n\n## Acceptance criteria\n\n- [ ] Three markdown runbook stubs with prerequisites, steps, rollback\n- [ ] Reference ADR-0015 settlement loop and ADR-0016 trust assumptions\n- [ ] Secrets handling: never commit `.env.devnet`, keypairs\n- [ ] No production code changes\n\n## ADR links\n\n- [ADR-0015](../../docs/adr/0015-usdt-custodial-settlement.md)\n- [ADR-0016](../../docs/adr/0016-alpha-scope-and-known-limitations.md)\n\n## Blocked by\n\nNone (stubs can land before issue 05; update after persistence ships)",
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
"Three markdown runbook stubs with prerequisites, steps, rollback",
|
||||||
|
"Reference ADR-0015 settlement loop and ADR-0016 trust assumptions",
|
||||||
|
"Secrets handling: never commit `.env.devnet`, keypairs",
|
||||||
|
"No production code changes",
|
||||||
|
"Keep the implementation scoped to this issue; do not refactor unrelated server.py areas",
|
||||||
|
"Update the source issue file Status header to done when complete",
|
||||||
|
"Run relevant pytest tests; run the full suite when practical or document why not"
|
||||||
|
],
|
||||||
|
"priority": 18,
|
||||||
|
"passes": false,
|
||||||
|
"status": "open",
|
||||||
|
"notes": "Source issue: .scratch/alpha-hardening/issues/18-doc-operational-runbooks.md",
|
||||||
|
"dependsOn": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AH-019",
|
||||||
|
"title": "19 \u2014 DOC: Cryptography dependency + test environment note",
|
||||||
|
"description": "# 19 \u2014 DOC: Cryptography dependency + test environment note\n\n## What to build\n\nDocument and verify test/dev environment setup for wallet crypto paths. `packages/node/meshnet_node/wallet.py` uses `cryptography`; failures occur when `.venv` lacks deps. `cryptography>=41` is already declared in `packages/node/pyproject.toml`, so this issue should focus on documenting the editable-install path and only add root/dev extras if tests still import the node wallet without installing the node package.\n\n**Code refs:**\n\n- `packages/node/pyproject.toml` \u2014 `cryptography>=41` (verify declared)\n- `packages/node/meshnet_node/wallet.py`\n- Handoff: tests fail without `cryptography`, `openai`, `langchain` in `.venv`\n\n## Acceptance criteria\n\n- [ ] Confirm `cryptography>=41` remains in node package deps; add to root/dev extras only if tests import wallet without node install\n- [ ] Add short **Test environment** section to `docs/dev/test-env.md` (or `CONTRIBUTING.md` if created): use `.venv/Scripts/python.exe`, `pip install -e packages/node ...`, optional dep skips\n- [ ] Note which tests require optional deps (`--ignore=test_openai_gateway,...`)\n- [ ] No unrelated production code changes\n\n## Blocked by\n\nNone",
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
"Confirm `cryptography>=41` remains in node package deps; add to root/dev extras only if tests import wallet without node install",
|
||||||
|
"Add short **Test environment** section to `docs/dev/test-env.md` (or `CONTRIBUTING.md` if created): use `.venv/Scripts/python.exe`, `pip install -e packages/node ...`, optional dep skips",
|
||||||
|
"Note which tests require optional deps (`--ignore=test_openai_gateway,...`)",
|
||||||
|
"No unrelated production code changes",
|
||||||
|
"Keep the implementation scoped to this issue; do not refactor unrelated server.py areas",
|
||||||
|
"Update the source issue file Status header to done when complete",
|
||||||
|
"Run relevant pytest tests; run the full suite when practical or document why not"
|
||||||
|
],
|
||||||
|
"priority": 19,
|
||||||
|
"passes": false,
|
||||||
|
"status": "open",
|
||||||
|
"notes": "Source issue: .scratch/alpha-hardening/issues/19-doc-cryptography-test-env.md",
|
||||||
|
"dependsOn": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AH-020",
|
||||||
|
"title": "20 \u2014 Validator service token for `/v1/billing/forfeit`",
|
||||||
|
"description": "# 20 \u2014 Validator service token for `/v1/billing/forfeit`\n\n## What to build\n\nDefine and implement a **validator service token** distinct from client API keys and admin sessions. The validator process must authenticate when calling `POST /v1/billing/forfeit`; arbitrary Bearer strings and client API keys must be rejected. This is a checklist subtask for issue 02 and should normally land in the same PR as the unified auth middleware.\n\nPer [ADR-0017 \u00a74](../../docs/adr/0017-tracker-authentication-and-authorization.md): forfeit accepts **validator service identity or admin session** only.\n\n## Configuration\n\n| Item | Alpha default |\n|---|---|\n| Env var | `MESHNET_VALIDATOR_SERVICE_TOKEN` (tracker + validator) |\n| Config flag | `--validator-service-token` / tracker config file equivalent |\n| Header format | `Authorization: Bearer <service-token>` with a dedicated prefix or separate header scheme documented in runbooks (e.g. `Authorization: Service <token>` \u2014 pick one and test consistently) |\n| Rotation | Manual: set new token on tracker + validator, restart both; document zero-downtime rotation as post-alpha |\n\n## Rejection rules\n\n- Client API keys (`sk-mesh-\u2026`) \u2192 **403** on forfeit (even if valid for inference)\n- Non-empty garbage Bearer \u2192 **401/403**\n- Missing auth \u2192 **401**\n- Valid validator service token \u2192 **200** (existing forfeit semantics)\n- Admin session \u2192 **200** (operator override)\n\n## Test-first\n\n1. Red: validator (or test client) posts forfeit with a valid API key \u2014 must fail after fix.\n2. Red: `Authorization: Bearer garbage` \u2014 must fail (covered by issue 02; this issue defines the accepted token).\n3. Green: configured service token succeeds; wrong token fails.\n\n## Acceptance criteria\n\n- [ ] Service token configurable via env/flag on tracker and validator\n- [ ] Unified auth middleware resolves service token \u2192 `validator` role (issue 02)\n- [ ] API keys explicitly rejected on forfeit path\n- [ ] Integration test: validator client with service token forfeit succeeds; API key forfeit fails\n- [ ] Runbook stub: rotation procedure (manual alpha)\n\n## ADR links\n\n- [ADR-0017](../../docs/adr/0017-tracker-authentication-and-authorization.md) \u00a74\n\n## Related\n\n- `02-a2-unified-auth-boundary.md` \u2014 middleware + role checks\n\n## Blocked by\n\n- `02-a2-unified-auth-boundary.md`",
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
"Service token configurable via env/flag on tracker and validator",
|
||||||
|
"Unified auth middleware resolves service token \u2192 `validator` role (issue 02)",
|
||||||
|
"API keys explicitly rejected on forfeit path",
|
||||||
|
"Integration test: validator client with service token forfeit succeeds; API key forfeit fails",
|
||||||
|
"Runbook stub: rotation procedure (manual alpha)",
|
||||||
|
"Keep the implementation scoped to this issue; do not refactor unrelated server.py areas",
|
||||||
|
"Update the source issue file Status header to done when complete",
|
||||||
|
"Run relevant pytest tests; run the full suite when practical or document why not"
|
||||||
|
],
|
||||||
|
"priority": 20,
|
||||||
|
"passes": true,
|
||||||
|
"status": "done",
|
||||||
|
"notes": "Source issue: .scratch/alpha-hardening/issues/20-validator-service-token.md",
|
||||||
|
"dependsOn": [
|
||||||
|
"AH-002"
|
||||||
|
],
|
||||||
|
"completionNotes": "Marked done in source alpha-hardening issue file before PRD generation."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AH-021",
|
||||||
|
"title": "21 \u2014 Honest-noise TOPLOC calibration corpus",
|
||||||
|
"description": "# 21 \u2014 Honest-noise TOPLOC calibration corpus\n\n## What to build\n\nBefore enabling production TOPLOC audit thresholds, collect an **honest-noise baseline** across the heterogeneous volunteer fleet. Run identical inference jobs on every active node/GPU combo; measure the divergence envelope (TOPLOC exponent/mantissa deltas, logprob-rank spread) under real hardware variance.\n\nPer [ADR-0018 consequences](../../docs/adr/0018-fraud-detection-verification-and-reputation.md): threshold calibration requires an honest-noise corpus across the fleet before production thresholds.\n\nResearch anchor: `.scratch/alpha-hardening/research-verifiable-inference.md` \u00a78 layer 3 \u2014 \"collect this first \u2014 run identical jobs across the current node fleet to measure the honest divergence envelope before setting thresholds.\"\n\n## Deliverables\n\n- [ ] Scripted benchmark job (fixed prompt, model preset, seed policy) runnable on all nodes\n- [ ] Aggregated corpus artifact (per node: GPU model, dtype, TOPLOC deltas vs reference)\n- [ ] Recommended tolerance thresholds documented (p99 honest envelope + safety margin)\n- [ ] Gate checklist: production audit enable blocked until corpus covers \u2265N distinct hardware profiles (define N in runbook, suggest \u22653)\n\n## Acceptance criteria\n\n- [ ] Corpus collected from current fleet (or documented subset + extrapolation note)\n- [ ] Threshold constants in validator config derived from corpus, not guessed\n- [ ] False-positive rate estimate documented at chosen thresholds\n- [ ] README / runbook cross-link: **do not enable production audits** until this issue closes\n\n## ADR links\n\n- [ADR-0018](../../docs/adr/0018-fraud-detection-verification-and-reputation.md) \u2014 Consequences (honest-noise corpus)\n\n## Blocked by\n\n- `06-fraud-toploc-integration.md` (TOPLOC wired; calibration uses same primitive)\n\n## Blocks (prod gate)\n\n- Production enable of adaptive audit thresholds (issues 09\u201310 in prod)",
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
"Corpus collected from current fleet (or documented subset + extrapolation note)",
|
||||||
|
"Threshold constants in validator config derived from corpus, not guessed",
|
||||||
|
"False-positive rate estimate documented at chosen thresholds",
|
||||||
|
"README / runbook cross-link: **do not enable production audits** until this issue closes",
|
||||||
|
"Keep the implementation scoped to this issue; do not refactor unrelated server.py areas",
|
||||||
|
"Update the source issue file Status header to done when complete",
|
||||||
|
"Run relevant pytest tests; run the full suite when practical or document why not"
|
||||||
|
],
|
||||||
|
"priority": 21,
|
||||||
|
"passes": false,
|
||||||
|
"status": "in-design",
|
||||||
|
"notes": "Source issue: .scratch/alpha-hardening/issues/21-honest-noise-calibration-corpus.md",
|
||||||
|
"dependsOn": [
|
||||||
|
"AH-006"
|
||||||
|
],
|
||||||
|
"status_reason": "Source issue is ready-for-human/deferred; skipped by unattended Ralph auto."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "AH-022",
|
||||||
|
"title": "22 \u2014 DOC: MEMORY.md + project-status alpha-hardening index",
|
||||||
|
"description": "# 22 \u2014 DOC: MEMORY.md + project-status alpha-hardening index\n\n## What to build\n\nUpdate persistent memory files so agents and humans find the alpha-hardening feature without stale handoff paths.\n\n## Acceptance criteria\n\n- [x] `.claude/memory/MEMORY.md` \u2014 index entry for alpha-hardening (`.scratch/alpha-hardening/`, ADRs 0016\u20130019, issue count)\n- [x] `.claude/memory/project-status.md` \u2014 brief alpha-hardening section: planning complete, Bucket 1 blockers next, link README\n- [x] Cross-link `.scratch/alpha-hardening/handoff.md` from README (not temp path)\n\n## ADR links\n\n- [ADR-0016](../../docs/adr/0016-alpha-scope-and-known-limitations.md)\n\n## Blocked by\n\nNone \u2014 completed\n\n## Comments\n\n2026-07-04 triage: already satisfied by `.claude/memory/MEMORY.md`, `.claude/memory/project-status.md`, and `.scratch/alpha-hardening/README.md`.",
|
||||||
|
"acceptanceCriteria": [
|
||||||
|
"[x] `.claude/memory/MEMORY.md` \u2014 index entry for alpha-hardening (`.scratch/alpha-hardening/`, ADRs 0016\u20130019, issue count)",
|
||||||
|
"[x] `.claude/memory/project-status.md` \u2014 brief alpha-hardening section: planning complete, Bucket 1 blockers next, link README",
|
||||||
|
"[x] Cross-link `.scratch/alpha-hardening/handoff.md` from README (not temp path)",
|
||||||
|
"Keep the implementation scoped to this issue; do not refactor unrelated server.py areas",
|
||||||
|
"Update the source issue file Status header to done when complete",
|
||||||
|
"Run relevant pytest tests; run the full suite when practical or document why not"
|
||||||
|
],
|
||||||
|
"priority": 22,
|
||||||
|
"passes": true,
|
||||||
|
"status": "done",
|
||||||
|
"notes": "Source issue: .scratch/alpha-hardening/issues/22-doc-memory-project-status.md",
|
||||||
|
"dependsOn": [],
|
||||||
|
"completionNotes": "Marked done in source alpha-hardening issue file before PRD generation."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
128
tests/test_registry_persistence.py
Normal file
128
tests/test_registry_persistence.py
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
"""Issue 05 (A1/A5) tests: strike/ban/reputation state survives tracker restart.
|
||||||
|
|
||||||
|
Registry wallet state was RAM-only (`_LocalContractState.registry`) — a tracker
|
||||||
|
restart wiped strikes, bans, and job counts. These tests pin the event-sourced
|
||||||
|
SQLite persistence (same replication model as BillingLedger / AccountStore)
|
||||||
|
per ADR-0016 §4 and ADR-0018 §6.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from meshnet_contracts import LocalSolanaContracts, RegistryWallet
|
||||||
|
from meshnet_tracker.server import TrackerServer
|
||||||
|
|
||||||
|
|
||||||
|
def _post_json(url: str, payload: dict) -> dict:
|
||||||
|
data = json.dumps(payload).encode()
|
||||||
|
req = urllib.request.Request(
|
||||||
|
url, data=data, headers={"Content-Type": "application/json"}, method="POST"
|
||||||
|
)
|
||||||
|
with urllib.request.urlopen(req) as r:
|
||||||
|
return json.loads(r.read())
|
||||||
|
|
||||||
|
|
||||||
|
# ---- schema: new reputation fields ----
|
||||||
|
|
||||||
|
|
||||||
|
def test_registry_wallet_has_reputation_fields():
|
||||||
|
wallet = RegistryWallet()
|
||||||
|
assert wallet.reputation == 1.0
|
||||||
|
assert wallet.last_audit_ts is None
|
||||||
|
|
||||||
|
|
||||||
|
# ---- persistence across restart ----
|
||||||
|
|
||||||
|
|
||||||
|
def test_strike_survives_restart(tmp_path):
|
||||||
|
db = str(tmp_path / "registry.sqlite")
|
||||||
|
contracts = LocalSolanaContracts(registry_db=db)
|
||||||
|
contracts.registry.record_strike("wallet-a")
|
||||||
|
contracts.save_to_db()
|
||||||
|
|
||||||
|
reopened = LocalSolanaContracts(registry_db=db)
|
||||||
|
assert reopened.registry.get_wallet("wallet-a").strike_count == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_ban_stake_and_jobs_survive_restart(tmp_path):
|
||||||
|
db = str(tmp_path / "registry.sqlite")
|
||||||
|
contracts = LocalSolanaContracts(registry_db=db)
|
||||||
|
contracts.registry.submit_stake("wallet-b", 500)
|
||||||
|
contracts.registry.submit_slash_proof(
|
||||||
|
wallet_address="wallet-b", slash_amount=200, strike_threshold=1
|
||||||
|
)
|
||||||
|
contracts.registry.record_completed_job("wallet-b")
|
||||||
|
contracts.save_to_db()
|
||||||
|
|
||||||
|
reopened = LocalSolanaContracts(registry_db=db)
|
||||||
|
wallet = reopened.registry.get_wallet("wallet-b")
|
||||||
|
assert wallet.banned is True
|
||||||
|
assert wallet.strike_count == 1
|
||||||
|
assert wallet.stake_balance == 300
|
||||||
|
assert wallet.completed_job_count == 1
|
||||||
|
# list_wallets must reflect persisted state
|
||||||
|
assert "wallet-b" in reopened.registry.list_wallets()
|
||||||
|
|
||||||
|
|
||||||
|
def test_reputation_and_audit_ts_survive_restart(tmp_path):
|
||||||
|
db = str(tmp_path / "registry.sqlite")
|
||||||
|
contracts = LocalSolanaContracts(registry_db=db)
|
||||||
|
contracts.registry.set_reputation("wallet-c", 0.8)
|
||||||
|
contracts.registry.record_audit("wallet-c", ts=1234.5)
|
||||||
|
contracts.save_to_db()
|
||||||
|
|
||||||
|
reopened = LocalSolanaContracts(registry_db=db)
|
||||||
|
wallet = reopened.registry.get_wallet("wallet-c")
|
||||||
|
assert wallet.reputation == pytest.approx(0.8)
|
||||||
|
assert wallet.last_audit_ts == pytest.approx(1234.5)
|
||||||
|
|
||||||
|
|
||||||
|
# ---- event replication (multi-tracker gossip / future Raft) ----
|
||||||
|
|
||||||
|
|
||||||
|
def test_registry_events_replicate_between_instances():
|
||||||
|
a = LocalSolanaContracts()
|
||||||
|
b = LocalSolanaContracts()
|
||||||
|
a.registry.record_strike("wallet-d")
|
||||||
|
a.registry.submit_stake("wallet-d", 100)
|
||||||
|
|
||||||
|
events, cursor = a.registry_log.events_since(0)
|
||||||
|
assert cursor == len(events) >= 2
|
||||||
|
applied = b.registry_log.apply_events(events)
|
||||||
|
assert applied == len(events)
|
||||||
|
assert b.registry.get_wallet("wallet-d") == a.registry.get_wallet("wallet-d")
|
||||||
|
|
||||||
|
# idempotent: re-applying the same events is a no-op
|
||||||
|
assert b.registry_log.apply_events(events) == 0
|
||||||
|
|
||||||
|
|
||||||
|
# ---- banned wallet rejected at registration after restart ----
|
||||||
|
|
||||||
|
|
||||||
|
def test_banned_wallet_rejected_at_registration_after_restart(tmp_path):
|
||||||
|
db = str(tmp_path / "registry.sqlite")
|
||||||
|
contracts = LocalSolanaContracts(registry_db=db)
|
||||||
|
contracts.registry.ban_wallet("wallet-evil")
|
||||||
|
contracts.save_to_db()
|
||||||
|
|
||||||
|
tracker = TrackerServer(contracts=LocalSolanaContracts(registry_db=db))
|
||||||
|
port = tracker.start()
|
||||||
|
try:
|
||||||
|
with pytest.raises(urllib.error.HTTPError) as excinfo:
|
||||||
|
_post_json(
|
||||||
|
f"http://127.0.0.1:{port}/v1/nodes/register",
|
||||||
|
{
|
||||||
|
"endpoint": "http://127.0.0.1:7900",
|
||||||
|
"model": "test-model",
|
||||||
|
"num_layers": 4,
|
||||||
|
"shard_start": 0,
|
||||||
|
"shard_end": 3,
|
||||||
|
"wallet_address": "wallet-evil",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert excinfo.value.code == 403
|
||||||
|
finally:
|
||||||
|
tracker.stop()
|
||||||
Reference in New Issue
Block a user