From f3a9e8c43f69a5d02cdb433114f7eaca39fe8cb6 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Thu, 8 Oct 2026 20:23:03 +0000 Subject: [PATCH 1/2] Evidence rules: a new evidence file lands free (rule 2 binds modified and deleted files); the recorder refuses a missing or directory tree path at record time; the kills batch cites the two experiments' own files, not the class-v6 directory Co-Authored-By: Claude Fable 5.1 --- docs/plans/igneum-2.0-test-registry.json | 24 ++++++++++++------------ tools/ci/batches/kills-20261008.json | 7 ++++--- tools/ci/registry-evidence-check.sh | 15 ++++++++++----- tools/ci/test-record.mjs | 20 +++++++++++++++++++- 4 files changed, 45 insertions(+), 21 deletions(-) diff --git a/docs/plans/igneum-2.0-test-registry.json b/docs/plans/igneum-2.0-test-registry.json index c4cb5d883..b7a5470f8 100644 --- a/docs/plans/igneum-2.0-test-registry.json +++ b/docs/plans/igneum-2.0-test-registry.json @@ -1547,9 +1547,9 @@ "manual_page": 24, "owner_lane": "adversary lane (a1a9876a88f5a72fc)", "run_status": "FAIL", - "evidence_path": "docs/analysis/class-v6/connected-state.md; docs/analysis/class-v6", + "evidence_path": "docs/analysis/class-v6/connected-state.md", "run_id": "kills-20261008", - "updated": "2026-10-08T20:10:24.624Z", + "updated": "2026-10-08T20:23:03.042Z", "evidence_record": { "requirement_id": "POW-03", "decision": "FAIL", @@ -1557,7 +1557,7 @@ "cell": "experiment:connected-state", "manifest_sha": "7cfa422a", "run_id": "kills-20261008", - "evidence": "docs/analysis/class-v6", + "evidence": "docs/analysis/class-v6/connected-state.md", "in_progress": false, "coverage": "the experiment ran and its claim failed: live state does not make the work unavoidable for a chip; the master's register row 18 reads FAIL, published, never PASSED", "release_identity": { @@ -1570,7 +1570,7 @@ }, "claim_impact": "the claims the two experiments carried are withdrawn: live state as an ASIC barrier (connected state), a cheaper mixed-FP32 lane", "reviewer": "", - "at": "2026-10-08T20:10:24.624Z" + "at": "2026-10-08T20:23:03.042Z" }, "approvals": { "scope_approved": null, @@ -1608,7 +1608,7 @@ "cell": "experiment:connected-state", "manifest_sha": "7cfa422a", "run_id": "kills-20261008", - "evidence": "docs/analysis/class-v6", + "evidence": "docs/analysis/class-v6/connected-state.md", "in_progress": false, "coverage": "the experiment ran and its claim failed: live state does not make the work unavoidable for a chip; the master's register row 18 reads FAIL, published, never PASSED", "release_identity": { @@ -1621,7 +1621,7 @@ }, "claim_impact": "the claims the two experiments carried are withdrawn: live state as an ASIC barrier (connected state), a cheaper mixed-FP32 lane", "reviewer": "", - "at": "2026-10-08T20:10:24.624Z" + "at": "2026-10-08T20:23:03.042Z" } } }, @@ -1897,9 +1897,9 @@ "manual_page": 26, "owner_lane": "hash lane (a690540514aa453d7)", "run_status": "FAIL", - "evidence_path": "docs/analysis/class-v6/rows/pow-07-fp32-unreachable.md; docs/analysis/class-v6", + "evidence_path": "docs/analysis/class-v6/rows/pow-07-fp32-unreachable.md; docs/analysis/class-v6/mixed-fp32.md; docs/analysis/class-v6/rows/pow-07-fp32-unreachable.md", "run_id": "kills-20261008", - "updated": "2026-10-08T20:10:24.624Z", + "updated": "2026-10-08T20:23:03.042Z", "evidence_record": { "requirement_id": "POW-07", "decision": "FAIL", @@ -1907,7 +1907,7 @@ "cell": "experiment:mixed-fp32", "manifest_sha": "7cfa422a", "run_id": "kills-20261008", - "evidence": "docs/analysis/class-v6", + "evidence": "docs/analysis/class-v6/mixed-fp32.md; docs/analysis/class-v6/rows/pow-07-fp32-unreachable.md", "in_progress": false, "coverage": "the branch ran and its claim failed: the FP32 branch costs the card more energy and widens the chip edge; the master's register row 12 reads FAIL, published; the branch is excluded and unreachable on master (the grep evidence stays)", "release_identity": { @@ -1920,7 +1920,7 @@ }, "claim_impact": "the claims the two experiments carried are withdrawn: live state as an ASIC barrier (connected state), a cheaper mixed-FP32 lane", "reviewer": "", - "at": "2026-10-08T20:10:24.624Z" + "at": "2026-10-08T20:23:03.042Z" }, "approvals": { "scope_approved": null, @@ -1962,7 +1962,7 @@ "cell": "experiment:mixed-fp32", "manifest_sha": "7cfa422a", "run_id": "kills-20261008", - "evidence": "docs/analysis/class-v6", + "evidence": "docs/analysis/class-v6/mixed-fp32.md; docs/analysis/class-v6/rows/pow-07-fp32-unreachable.md", "in_progress": false, "coverage": "the branch ran and its claim failed: the FP32 branch costs the card more energy and widens the chip edge; the master's register row 12 reads FAIL, published; the branch is excluded and unreachable on master (the grep evidence stays)", "release_identity": { @@ -1975,7 +1975,7 @@ }, "claim_impact": "the claims the two experiments carried are withdrawn: live state as an ASIC barrier (connected state), a cheaper mixed-FP32 lane", "reviewer": "", - "at": "2026-10-08T20:10:24.624Z" + "at": "2026-10-08T20:23:03.042Z" } } }, diff --git a/tools/ci/batches/kills-20261008.json b/tools/ci/batches/kills-20261008.json index 12a847760..f2484f46e 100644 --- a/tools/ci/batches/kills-20261008.json +++ b/tools/ci/batches/kills-20261008.json @@ -7,12 +7,13 @@ { "cell": "experiment:connected-state", "status": "FAIL", - "evidence": "docs/analysis/class-v6" + "evidence": "docs/analysis/class-v6/connected-state.md" }, { "cell": "experiment:mixed-fp32", "status": "FAIL", - "evidence": "docs/analysis/class-v6" + "evidence": "docs/analysis/class-v6/mixed-fp32.md; docs/analysis/class-v6/rows/pow-07-fp32-unreachable.md" } - ] + ], + "note": " Evidence narrowed at 21:3x UK from the class-v6 directory to the two experiments' own files (a directory citation bound every file beneath it to these rows and refused every other lane's class-v6 landing); the verdicts are unchanged." } diff --git a/tools/ci/registry-evidence-check.sh b/tools/ci/registry-evidence-check.sh index a7118073d..caa475bf6 100755 --- a/tools/ci/registry-evidence-check.sh +++ b/tools/ci/registry-evidence-check.sh @@ -20,11 +20,11 @@ check() { # ; prints "refused ..." lines; returns 1 when any local base="$1" head="$2" rc=0 git cat-file -e "$head:$REG" 2>/dev/null || return 0 local tmp_h tmp_b; tmp_h=$(mktemp); tmp_b=$(mktemp); git show "$head:$REG" > "$tmp_h"; git show "$base:$REG" > "$tmp_b" 2>/dev/null || : > "$tmp_b" - local touched; touched=$(git diff --name-only "$base" "$head" --) - python3 - "$tmp_h" "$tmp_b" "$head" "$touched" <<'PY' || rc=1 + local touched added; touched=$(git diff --name-only "$base" "$head" --); added=$(git diff --name-only --diff-filter=A "$base" "$head" --) + python3 - "$tmp_h" "$tmp_b" "$head" "$touched" "$added" <<'PY' || rc=1 import json, sys, subprocess, os h = json.load(open(sys.argv[1])); b = json.load(open(sys.argv[2])) if os.path.getsize(sys.argv[2]) else {} -head = sys.argv[3]; touched = [t for t in sys.argv[4].split('\n') if t] +head = sys.argv[3]; touched = [t for t in sys.argv[4].split('\n') if t]; added = set(t for t in sys.argv[5].split('\n') if t) cases = lambda r: [t for s in r.get('suites', []) for t in s.get('tests', [])] hb = {c['id']: c for c in cases(b)}; refused = [] approved = bool(h.get('approval')); pinned = h.get('pinned_manifest_sha') @@ -62,6 +62,7 @@ for c in cases(h): REG_PATH = os.environ.get('REGISTRY_PATH', 'docs/plans/igneum-2.0-test-registry.json') for t in touched: if t == REG_PATH: continue # the registry names itself as GOV-02's evidence (the approval recorded in it); it always moves with itself + if t in added: continue # a file first appearing in the tree is the evidence arriving, not evidence changing under a row: a row that cited it before it landed stands (lane D's class, 8 October 2026 21:2x UK); only a MODIFIED or deleted evidence file must move its rows owners = [ids for p, ids in named.items() if t == p or t.startswith(p + '/')] if not owners and not t.startswith('docs/analysis/'): continue ids = sorted({i for ids in owners for i in ids}) @@ -97,7 +98,9 @@ PY git checkout -q -b e base; echo r2 > docs/analysis/row.md; git -c user.name=t -c user.email=t@t commit -qam touched && git tag touched # evidence-with-row: the same change with X-2's updated moved git checkout -q -b e2 base; echo r2 > docs/analysis/row.md; mk docs/plans/igneum-2.0-test-registry.json 1 - '{"X-1": {}, "X-2": {"run_status": "RUNNING", "evidence_path": "docs/analysis/row.md", "updated": "t1"}}'; git -c user.name=t -c user.email=t@t commit -qam with-row && git tag with-row - # stale: PASS on evidence pinned to another manifest + # added: a NEW evidence file another lane's row already cites lands free (the row cited it before it existed; rule 2 binds modified files only) + git checkout -q -b a base; mkdir -p docs/analysis/new; echo n1 > docs/analysis/new/log.tsv; mk docs/plans/igneum-2.0-test-registry.json 1 - '{"X-1": {}, "X-2": {"run_status": "RUNNING", "evidence_path": "docs/analysis/row.md; docs/analysis/new/log.tsv", "updated": "t0"}}'; git add -A; git -c user.name=t -c user.email=t@t commit -qm added && git tag added + git checkout -q -b a2 added; echo n2 > docs/analysis/new/log.tsv; git -c user.name=t -c user.email=t@t commit -qam added-then-touched && git tag added-touched git checkout -q -b s base; mk docs/plans/igneum-2.0-test-registry.json 1 aaaaaaaa '{"X-1": {"run_status": "PASS", "evidence_path": "evidence/a.log", "updated": "t1", "evidence_record": {"manifest_sha": "bbbbbbbb"}}, "X-2": {}}'; git -c user.name=t -c user.email=t@t commit -qam stale && git tag stale # self-ref: a row names the registry itself as its evidence; a registry change must not trip rule 2 on it git checkout -q -b r base; mk docs/plans/igneum-2.0-test-registry.json 1 - '{"X-1": {"run_status": "RUNNING", "evidence_path": "evidence/a.log", "updated": "t1"}, "X-2": {"run_status": "RUNNING", "evidence_path": "docs/plans/igneum-2.0-test-registry.json", "updated": "t0"}}'; git -c user.name=t -c user.email=t@t commit -qam self-ref && git tag self-ref @@ -108,10 +111,12 @@ PY out=$(bash "$ME" base pass-missing 2>&1) && { echo "self-test failed: a PASS naming a missing path passed"; fails=1; }; case "$out" in *"not in the tree"*) ;; *) echo "self-test failed: the missing path was not named: $out"; fails=1 ;; esac out=$(bash "$ME" base touched 2>&1) && { echo "self-test failed: a touched evidence file without its row passed"; fails=1; }; case "$out" in *"X-2"*"move together"*) ;; *) echo "self-test failed: the unmoved row was not named: $out"; fails=1 ;; esac bash "$ME" base with-row >/dev/null 2>&1 || { echo "self-test failed: a touched evidence file with its row updated was refused: $(bash "$ME" base with-row 2>&1)"; fails=1; } + bash "$ME" base added >/dev/null 2>&1 || { echo "self-test failed: a new evidence file a row already cites was refused on arrival: $(bash "$ME" base added 2>&1)"; fails=1; } + out=$(bash "$ME" added added-touched 2>&1) && { echo "self-test failed: the same file modified after it landed passed without its row"; fails=1; } out=$(bash "$ME" base stale 2>&1) && { echo "self-test failed: a PASS on stale evidence passed"; fails=1; }; case "$out" in *"stale evidence"*) ;; *) echo "self-test failed: the stale evidence was not named: $out"; fails=1 ;; esac bash "$ME" base self-ref >/dev/null 2>&1 || { echo "self-test failed: a row naming the registry itself as evidence tripped the move-together rule on a registry change: $(bash "$ME" base self-ref 2>&1)"; fails=1; } out=$(bash "$ME" base unapproved 2>&1) && { echo "self-test failed: a run_status without approval passed"; fails=1; }; case "$out" in *"thresholds before results"*) ;; *) echo "self-test failed: the missing approval was not named: $out"; fails=1 ;; esac - [ "$fails" = 0 ] && echo "self-test passed: a PASS must name evidence that exists (tree or box); a touched evidence file moves with its registry row; evidence pinned to another manifest cannot be PASS; a run_status needs the registry's approval" + [ "$fails" = 0 ] && echo "self-test passed: a PASS must name evidence that exists (tree or box); a modified evidence file moves with its registry row and a new one lands free; evidence pinned to another manifest cannot be PASS; a run_status needs the registry's approval" exit $fails fi [ $# -eq 2 ] || { echo "usage: registry-evidence-check.sh | --self-test" >&2; exit 2; } diff --git a/tools/ci/test-record.mjs b/tools/ci/test-record.mjs index e5836dfb7..8c16570c9 100644 --- a/tools/ci/test-record.mjs +++ b/tools/ci/test-record.mjs @@ -74,6 +74,16 @@ function record(reg, map, batch) { if (status === 'DEFERRED') { status = 'NOT RUN'; deferral = cell.deferral || 'deferred by the founder'; } if (!DECISIONS.has(status)) throw new Error(`cell ${cell.cell}: status ${cell.status} is not one of NOT RUN, BLOCKED, FAIL, PASS (RUNNING reads as NOT RUN in progress; DEFERRED as NOT RUN with a deferral note)`); const method = cell.method || batch.method; if (!METHODS.has(method)) throw new Error(`cell ${cell.cell}: method must be one of ${[...METHODS].join(', ')} (never conflated); got ${method}`); + // evidence paths (the coordinator's and lane D's classes, 8 October 2026 21:3x UK): a tree path must exist at record time and name a file, + // never a directory (a directory citation makes every file beneath it the row's evidence and trips the move-together rule on every + // other lane's landing); an absolute path or a box path (host:/path) is outside the tree and is not checked here (rule 1 reads it at the merge) + for (const one of String(cell.evidence || '').split(';').map((x) => x.trim()).filter(Boolean)) { + if (one.startsWith('/') || one.includes(':/')) continue; + if (process.env.TEST_RECORD_NO_TREE === '1') continue; + const abs = path.resolve(ROOT, one); + if (!fs.existsSync(abs)) throw new Error(`cell ${cell.cell}: evidence ${one} is not in the tree at record time (cite only files that exist here; another lane's unlanded file is cited after it lands)`); + if (fs.statSync(abs).isDirectory()) throw new Error(`cell ${cell.cell}: evidence ${one} is a directory; name the file (a directory citation binds every file beneath it to this row)`); + } const missing = Object.entries(cell.prereqs || {}).filter(([, v]) => v !== 'present').map(([k]) => k); if (missing.length) status = 'BLOCKED'; // a cell may narrow its write to named cases of the cell ("cases": [...]) so an incident on one case never writes a cell's other cases @@ -112,7 +122,7 @@ function record(reg, map, batch) { } const acceptSnapshot = (reg) => JSON.stringify(casesOf(reg).map((c) => { const o = { id: idOf(c) }; for (const k of ACCEPT_KEYS) if (k in c) o[k] = c[k]; return o; })); if (args.includes('--self-test')) { - const d = fs.mkdtempSync('/tmp/test-record-'); let fails = 0; + const d = fs.mkdtempSync('/tmp/test-record-'); let fails = 0; process.env.TEST_RECORD_NO_TREE = '1'; // the fixture cells cite fake paths; the tree rule is exercised by its own cases below const reg = { cases: [ { id: 'C1', method: 'Automated', accept: 'one million vectors agree' }, { id: 'C2', method: 'Automated', accept: 'parser refuses ten thousand malformed' }, { id: 'C3', method: 'Manual review', accept: 'the reviewer signs' }, { id: 'C4', method: 'Automated; manual', accept: 'frozen verify time holds' } ] }; @@ -128,6 +138,14 @@ if (args.includes('--self-test')) { record(reg, map, { run_id: 'r1r', manifest_sha: 'abc', method: 'native', cells: [{ cell: 'pow', status: 'RUNNING', evidence: '/e/pow.log' }] }); if (!(reg.cases[0].run_status === 'NOT RUN' && reg.cases[0].in_progress_since)) { console.log('self-test failed: RUNNING did not become NOT RUN with in_progress_since'); fails = 1; } let badStatus = false; try { record(reg, map, { run_id: 'x', manifest_sha: 'abc', method: 'native', cells: [{ cell: 'pow', status: 'GREEN' }] }); } catch { badStatus = true; } + delete process.env.TEST_RECORD_NO_TREE; let badPath = ''; try { record(reg, map, { run_id: 'x', manifest_sha: 'abc', method: 'native', cells: [{ cell: 'pow', status: 'FAIL', evidence: `${d}/missing.log`.replace(/^\//, 'rel/') }] }); } catch (e) { badPath = String(e.message); } + if (!/not in the tree at record time/.test(badPath)) { console.log(`self-test failed: an evidence path not in the tree was accepted at record time: ${badPath}`); fails = 1; } + fs.mkdirSync(`${d}/dir`); const relDir = path.relative(ROOT, `${d}/dir`); let badDir = ''; + try { record(reg, map, { run_id: 'x', manifest_sha: 'abc', method: 'native', cells: [{ cell: 'pow', status: 'FAIL', evidence: relDir }] }); } catch (e) { badDir = String(e.message); } + if (!/is a directory/.test(badDir)) { console.log(`self-test failed: a directory cited as evidence was accepted: ${badDir}`); fails = 1; } + fs.writeFileSync(`${d}/dir/f.log`, 'x'); let okFile = true; try { record(reg, map, { run_id: 'x', manifest_sha: 'abc', method: 'native', cells: [{ cell: 'pow', status: 'FAIL', evidence: `${relDir}/f.log` }] }); } catch { okFile = false; } + if (!okFile) { console.log('self-test failed: a tree file that exists was refused as evidence'); fails = 1; } + process.env.TEST_RECORD_NO_TREE = '1'; if (!badStatus) { console.log('self-test failed: a status outside the vocabulary was accepted'); fails = 1; } let badMethod = false; try { record(reg, map, { run_id: 'x', manifest_sha: 'abc', cells: [{ cell: 'pow', status: 'PASS' }] }); } catch { badMethod = true; } if (!badMethod) { console.log('self-test failed: a batch with no method was accepted (methods are never conflated)'); fails = 1; } From 63d0d43e28abb918153c26aaa7b902b02f4055e5 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Thu, 8 Oct 2026 20:25:03 +0000 Subject: [PATCH 2/2] Pool record: the devnet-4 pair's class (no job on an unsynced node's state) in docs/plans/pool.md 11a and the test map's suite:pool coverage (state_provider::tests::an_unsynced_node_hands_the_pool_no_state_and_no_job; b5531f46 on release-2.0.2, 7ffd29af on pool-2.0, 52 of 52 on build-6, known-failed first against the c24d5080 provider); the freeze read-back (igneum-pow/src tree 93c36844 on every 2.0.x line) Co-Authored-By: Claude Fable 5.1 --- docs/plans/igneum-2.0-test-harness-map.md | 2 +- docs/plans/pool.md | 4 ++++ tools/ci/test-map.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/plans/igneum-2.0-test-harness-map.md b/docs/plans/igneum-2.0-test-harness-map.md index 6bacb2699..572138472 100644 --- a/docs/plans/igneum-2.0-test-harness-map.md +++ b/docs/plans/igneum-2.0-test-harness-map.md @@ -265,7 +265,7 @@ Rule: a case maps to a cell only where the cell's tests visibly answer it; cover - Fixtures: F0, F1 - Cases: - UX-05 Keep voting keys with the miner through pooling: partial: the vote-key commitment on the wire (verify::tests::a_share_under_another_key_is_refused_before_the_hash: known-pass the member's key, known-fail another key on the share and a job naming another key, refused with code vote_key before the hash), the open pool's sidechain::check_key (the claimed key, the header's key and the reveal are one key), the same-nonce-other-template wrong_hash test, the TLS binding test; the malicious-pool run and the leave-and-retain run on the devnet-4 pair are the UX-05 batch's other evidence; review B INT-15 (8 October 2026): server::authorise_tests::a_replayed_proof_of_possession_is_not_an_authorisation (a public PoP replayed into another session, payout, pool or chain, or under another key, refused; only the challenge-bound binding v2 admits on a public network) and the_binding_policy_follows_the_network - - UX-04 Pay small operators without hidden custody: partial: PPLNS distribution tests (fee first, never overpays, late joiner), the payout key file round trip, the signed transfer decode; the live payout path on the devnet-4 pair is the UX-04 batch's evidence; review B F12 and INT-03/INT-04 (8 October 2026): payout::intent_tests (a restart from the pre-broadcast snapshot, a lost send response, a crash on either side of the broadcast, a stuck transaction replaced under the same intent, a reorged receipt and a finality pause, a failed transaction, a ledger that cannot be written: never a duplicate or a lost obligation, finalised only under the chain's final lock) and state::ledger_tests (an unreadable ledger is refused or restored, never emptied); F13/INT-16: frame, admission and pool::share_tests (the frame bound while reading, the bounded outgoing queue, one membership per session, the nonce and in-flight bounds, the share and connection budgets) + - UX-04 Pay small operators without hidden custody: partial: PPLNS distribution tests (fee first, never overpays, late joiner), the payout key file round trip, the signed transfer decode; the live payout path on the devnet-4 pair is the UX-04 batch's evidence; review B F12 and INT-03/INT-04 (8 October 2026): payout::intent_tests (a restart from the pre-broadcast snapshot, a lost send response, a crash on either side of the broadcast, a stuck transaction replaced under the same intent, a reorged receipt and a finality pause, a failed transaction, a ledger that cannot be written: never a duplicate or a lost obligation, finalised only under the chain's final lock) and state::ledger_tests (an unreadable ledger is refused or restored, never emptied); F13/INT-16: frame, admission and pool::share_tests (the frame bound while reading, the bounded outgoing queue, one membership per session, the nonce and in-flight bounds, the share and connection budgets); the devnet-4 pair's class (8 October 2026 21:24 UK): state_provider::tests::an_unsynced_node_hands_the_pool_no_state_and_no_job (no job on a node whose igneum_getExecStatus reads synced false, blocked or re-executing; known-failed first against the 8ff7a0f4 provider; pool-synced-guard-202 f3e99e9c on release-2.0.2, 986252e7 on pool-2.0, 52 of 52 on build-6) ### harness:economics-model diff --git a/docs/plans/pool.md b/docs/plans/pool.md index 5155a57a8..e968ceb9b 100644 --- a/docs/plans/pool.md +++ b/docs/plans/pool.md @@ -254,3 +254,7 @@ The second external review (docs/analysis/review-2026-10-08-b) and the full-syst Known-failed first: commit 75383929 on pool-review-b carried the tests over the 8 October semantics, and the suite read seven red on build-6 (the three payout tests paid 200 of 100 due or never settled, the frame reader blocked on a newline that never came, the registry kept two members, the queue took 10,000 lines, the nonce set held 100,000 refused nonces); 065148a2 and 4a8dcbc0 turned them green, 51 of 51. Consequences per tier. A pool member on any card: a payout cannot be paid twice or lost across a pool crash, a lost node answer, a reorg or a stuck transaction; a payment shows `mined` until the chain's own finality covers it, so the row a member reads as final is final. A pool operator: the ledger cannot start empty over a corrupt file, the previous snapshot and its digest are always there to restore from, and one bad member cannot hold the daemon's memory or its verifier (every bound has a flag and a default). The network: a captured `authorize` cannot be replayed to redirect a member's pay or to vote under its key from another session, and the vote key stays the member's in every template (the pin "vote keys stay with the miner", docs/plans/igneum-2.0.md). + +### 11a. 21:24 UK, the devnet-4 pair's class: no job on an unsynced node's state + +The pair's evidence (the pool seat): identical epoch seed, day, class v5 and era on node, pool and member, the freeze's generator (`igneum-pow fingerprint cbc5bd0a`, read back on every 2.0.x line: `igneum-pow/src` is tree 93c36844, the freeze commit 1c420786's) on all three binaries, and every member share `wrong_hash`, because the node was still in class v5 catch-up and answered `igneum_getPowStateLeaves` from its still-settling state. Closed by construction: `pool/src/state_provider.rs` reads `igneum_getExecStatus` beside the leaves and refuses a stream while the node reads `synced` false, `blocked` or `reexecuting` (a missing field is not synced), so the engine builds no epoch and no job goes out on catch-up leaves; the STATUS line says `node not synced (class v5 catch-up): no jobs` with the exec RPC named while it holds. Known-failed first against the 8ff7a0f4 provider on build-6, then 52 of 52. Shas: f3e99e9c (release-2.0.2), 986252e7 (pool-2.0). Consequence per tier: a pool member is never handed work its pool's node cannot verify; an operator reads the cause, not a reject count. diff --git a/tools/ci/test-map.json b/tools/ci/test-map.json index ef7fa6c3c..dca147e97 100644 --- a/tools/ci/test-map.json +++ b/tools/ci/test-map.json @@ -455,7 +455,7 @@ ], "coverage": { "UX-05": "partial: the vote-key commitment on the wire (verify::tests::a_share_under_another_key_is_refused_before_the_hash: known-pass the member's key, known-fail another key on the share and a job naming another key, refused with code vote_key before the hash), the open pool's sidechain::check_key (the claimed key, the header's key and the reveal are one key), the same-nonce-other-template wrong_hash test, the TLS binding test; the malicious-pool run and the leave-and-retain run on the devnet-4 pair are the UX-05 batch's other evidence; review B INT-15 (8 October 2026): server::authorise_tests::a_replayed_proof_of_possession_is_not_an_authorisation (a public PoP replayed into another session, payout, pool or chain, or under another key, refused; only the challenge-bound binding v2 admits on a public network) and the_binding_policy_follows_the_network", - "UX-04": "partial: PPLNS distribution tests (fee first, never overpays, late joiner), the payout key file round trip, the signed transfer decode; the live payout path on the devnet-4 pair is the UX-04 batch's evidence; review B F12 and INT-03/INT-04 (8 October 2026): payout::intent_tests (a restart from the pre-broadcast snapshot, a lost send response, a crash on either side of the broadcast, a stuck transaction replaced under the same intent, a reorged receipt and a finality pause, a failed transaction, a ledger that cannot be written: never a duplicate or a lost obligation, finalised only under the chain's final lock) and state::ledger_tests (an unreadable ledger is refused or restored, never emptied); F13/INT-16: frame, admission and pool::share_tests (the frame bound while reading, the bounded outgoing queue, one membership per session, the nonce and in-flight bounds, the share and connection budgets)" + "UX-04": "partial: PPLNS distribution tests (fee first, never overpays, late joiner), the payout key file round trip, the signed transfer decode; the live payout path on the devnet-4 pair is the UX-04 batch's evidence; review B F12 and INT-03/INT-04 (8 October 2026): payout::intent_tests (a restart from the pre-broadcast snapshot, a lost send response, a crash on either side of the broadcast, a stuck transaction replaced under the same intent, a reorged receipt and a finality pause, a failed transaction, a ledger that cannot be written: never a duplicate or a lost obligation, finalised only under the chain's final lock) and state::ledger_tests (an unreadable ledger is refused or restored, never emptied); F13/INT-16: frame, admission and pool::share_tests (the frame bound while reading, the bounded outgoing queue, one membership per session, the nonce and in-flight bounds, the share and connection budgets); the devnet-4 pair's class (8 October 2026 21:24 UK): state_provider::tests::an_unsynced_node_hands_the_pool_no_state_and_no_job (no job on a node whose igneum_getExecStatus reads synced false, blocked or re-executing; known-failed first against the 8ff7a0f4 provider; pool-synced-guard-202 f3e99e9c on release-2.0.2, 986252e7 on pool-2.0, 52 of 52 on build-6)" } }, "harness:economics-model": {