Merge box/master b4a55fc65 into class-v6-floor-k-docs-2 (the steward's b4a55fc6 under it; the repeat batch trimmed to floor lane 2's rows ADV-05 and ADV-06, method model; the registry master's with that batch recorded)
This commit is contained in:
commit
b40b86a8ca
24 changed files with 2194 additions and 243 deletions
54
docs/analysis/pool/pool-pair-2026-10-08.md
Normal file
54
docs/analysis/pool/pool-pair-2026-10-08.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# The devnet-4 pool pair, 8 October 2026 (UX-05, UX-04 evidence; the night's record)
|
||||
|
||||
Pool seat, 8 October 2026, 20:0x to 21:4x UK. Binaries: the node /srv/artefacts/200-12424341/node-lane/igneumd on
|
||||
build-2 (successor-2.0.1's gate build), the pool daemon from pool-2.0 (8106ba27 then e063fdcd; igneum-pow fingerprint
|
||||
cbc5bd0aa10585c8, the freeze 1c420786), two CPU members from the fork at 2b1a247a (the same fingerprint; a test
|
||||
binary, never shipped). Two Vast hosts rented for the pair (i7-5820K, Xeon E5-2609 v3, Haswell) died with Illegal
|
||||
instruction at the class v5 catch-up on every node build and were destroyed; the pair ran on build-2 under lease
|
||||
pool class measure and was brought down by pid file at 21:3x UK (0 leases, 0 processes left).
|
||||
|
||||
## UX-05 Keep voting keys with the miner through pooling: PASS in the crate
|
||||
|
||||
| Test (pool crate, build-2) | Known-pass | Known-fail | Result |
|
||||
|---|---|---|---|
|
||||
| `verify::tests::a_share_under_another_key_is_refused_before_the_hash` | the member's key on job and share: ok | another key on the share, and a job naming another key: `vote_key`, hash 0, under a tenth of an evaluation | ok (51 of 51 at e063fdcd, 52 of 52 at 986252e7) |
|
||||
| `sidechain::tests::a_share_whose_keys_disagree_is_refused_before_its_pow` | make_share | the claim swapped, the header's key swapped, a foreign reveal | ok |
|
||||
| `the same nonce on another template hashes differently` | | a nonce moved to another template: `wrong_hash` | ok |
|
||||
| the TLS binding (replay refused), the admission bounds, the intents (review B) | | | ok |
|
||||
| `an_unsynced_node_hands_the_pool_no_state_and_no_job` (986252e7) | a synced node: leaves served, jobs issued | synced false: no leaves, no job | red against 8106ba27 on build-6, green with the guard |
|
||||
|
||||
The live pair reached: authorise with the members' own keys (the pool log names each key beside its payout address),
|
||||
class v5 seeds issued (epoch 1a87e870..., day 20734, era 7c36b833...), jobs issued with `vote_key_hash` the
|
||||
member's, shares sent. Every share read `wrong_hash`.
|
||||
|
||||
## The wrong_hash cause, from the retained logs (not a generator skew)
|
||||
|
||||
The node, the pool and the member carry the same igneum-pow (fingerprint cbc5bd0aa10585c8, the freeze; pool-2.0's
|
||||
tree 93c36844 byte-identical to 1c420786, read back by the pool lane from the mirror and from the binaries). The pool
|
||||
and the members hashed the identical epoch seed, day, class and era. Build-2's node never reached synced on devnet-4
|
||||
(its log loops on "class v5 execution catch-up" against peers that close the connection; `synced=false` on every
|
||||
read), so the class v5 state leaves the pool and the members fetched by `igneum_getPowStateLeaves` for the epoch's
|
||||
seed block were the still-settling catch-up state, not the node's state at template time; a class v5 lane hash over
|
||||
unsettled leaves does not match the node's. The class kept: a class v5 pool needs its node fully synced before it
|
||||
verifies shares, because the dataset is keyed on settled execution state. The guard by construction is pool-2.0
|
||||
986252e7 (the provider refuses leaves while `igneum_getExecStatus` reads unsynced, blocked or reexecuting; the feed
|
||||
issues no job; the STATUS line says so).
|
||||
|
||||
## Two 2.0 gaps closed by the pair (both on pool-2.0, in the 2.0.2 take)
|
||||
|
||||
1. The pool daemon had no class v5 day-state source and issued no job on a class v5 chain (8ff7a0f4:
|
||||
`state_provider.rs`, `--exec-rpc` defaulting to `--evm-rpc`).
|
||||
2. The daemon read amounts at 8 decimals and refused every 18-decimal template as a high-word amount (8106ba27: the
|
||||
base unit installed from the node's network before any amount is read).
|
||||
|
||||
## UX-04 Pay small operators without hidden custody
|
||||
|
||||
PPLNS distribution, the payout key round trip and the signed transfer decode: PASS in the crate. The live payout path
|
||||
(a member earns, is paid at the minimum, reconnects, a redirection attempt) is NOT RUN: it needs the pair on a
|
||||
fully-synced devnet-4 node with the 2.0.2 kit, tomorrow.
|
||||
|
||||
## Consequences per tier
|
||||
|
||||
A home miner on a pool: the key stays theirs on every job and share and a pool naming another key is refused before
|
||||
the hash; a pool operator: the daemon refuses to issue jobs until its node is synced, so an unsynced start costs idle
|
||||
minutes, never a wrong_hash storm; the network: no change to consensus from any of this.
|
||||
|
|
@ -6,7 +6,7 @@ The fixture every case of the Test and Acceptance Standard (docs/plans/igneum-2.
|
|||
|
||||
| Field | Value | Read from | Owner |
|
||||
|---|---|---|---|
|
||||
| Miner cut tip (release-2.0.1) | aa0e0f45 (9c844503 + the pin file; 9c844503 = 2826f37e + the six-target packaged peer list; the app crate byte-identical to 2ea7b43f's throughout, so the pow and app cells read on 9c844503 cover it by content) | the shipper's line 19:5x | shipper (ae892a8b0f78fe31c) |
|
||||
| Miner cut tip (release-2.0.1) | aa0e0f45 is the shipped tip (the entries stand on its binaries); release-2.0.1's final tip is c30ab32c (aa0e0f45 + the pool lane's pool/ and docs a54dffa3 + the release manifest f03-manifest-201 7437a31a merged at 800faaf7 + the hand-merged spec 09; no app, node pin or packaging change). The clean build from the manifest (R2-F03-R01) read green on c30ab32c at 21:14 UK on build-4: kaspad with igneum-pow, igneum-miner, igneum-pool, igneum-app, igneum-prove-host, and every component's own pin equals packaging/release-manifest.json. (9c844503 + the pin file; 9c844503 = 2826f37e + the six-target packaged peer list; the app crate byte-identical to 2ea7b43f's throughout, so the pow and app cells read on 9c844503 cover it by content) | the shipper's lines 19:5x and 21:0x; the steward's build 21:14 | shipper (ae892a8b0f78fe31c) |
|
||||
| Node sha for the roll | 7cfa422a = ef0f2ed8 (291ee6ae + the 2.0.1 version) + the miner base-unit fix, amended (777214af did not compile: a self-recursive connect, caught by the steward's read at 19:50) | the node lane's and shipper's lines 19:5x | node lane (a283f5f0d364ceef0) |
|
||||
| Node line read green tonight | 4cdcc488, d5981514, bee41b5e, 9fc9f42a, 5713d547, 417c4a57, 291ee6ae (ef0f2ed8 = 291ee6ae + the version bump, its own read on build-4 recorded as the literal) | the steward's matrices on build-2 and build-4 | CI steward |
|
||||
| Network | igneum-devnet-4, a fresh genesis; EVM chain id 4465 (0x1171), set in devnet4_params, read by the canaries as eth_chainId on every candidate tonight, pinned by the digest be5f4068; every 2.0 devnet node, pool and reference app signs with it; mainnet's and the testnet's ids unchanged from the 0.3 line | the node lane's line 19:3x | node lane |
|
||||
|
|
|
|||
48
docs/plans/igneum-2.0-same-work-test.md
Normal file
48
docs/plans/igneum-2.0-same-work-test.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# The cross-backend same-work test (F03, Review B; specified by the CI steward, 8 October 2026, 20:1x UK)
|
||||
|
||||
One job context, six readers, three phases around a transition, bit-for-bit agreement. Run by the fleet lane with the freeze object; the evidence recorded against POW-01 through the batch tools.
|
||||
|
||||
## The job context (one file, `job-context.json`, written once by the steward or the hash lane and copied to every reader)
|
||||
|
||||
| Field | Value tonight | Source |
|
||||
|---|---|---|
|
||||
| object | the class v5 freeze: igneum-pow 1c420786, fingerprint cbc5bd0aa10585c8576e71e37a8ee47a045ae51754e9ddf749d0c21e6a535f88 | packaging/release-manifest.json (generator) |
|
||||
| epoch seed bytes | edc4fa844da9dc98d37e965176f6558a31560e40502ab3ae5491b21aaaabfb07 (the genesis seeds) | the hash lane's P01 line |
|
||||
| day bytes | 69676e65756d2d6461792ffa50000000000000 | the hash lane's P01 line |
|
||||
| class | 5; era 0:<the epoch hex> | the pack's program.json |
|
||||
| prehash | 0000000000000000000000000000000000000000000000000000000000000001 | the P01 convention |
|
||||
| nonce range | 0 .. 2^20 per phase (three phases, three ranges: [0, 2^20), [2^20, 2^21), [2^21, 2^22)) | this page |
|
||||
| transition | the day boundary: day D for phase 1, the boundary block for phase 2 (the last 2^12 nonces of day D and the first 2^12 of day D+1 as the engine sees them under fast-time 60x), day D+1 for phase 3 | infra/fast-time/override-60x.json |
|
||||
|
||||
The transition is a dataset-day rotation (the class and era unchanged, the day bytes change), the one transition every live network crosses hourly at 60x; a class rotation (v5 to v6) is the same test with `class` and the second `day bytes` changed and runs only on a research object until a v6 floor is set.
|
||||
|
||||
## The six readers (every one writes `<reader>-<phase>.json` of the P01 driver's evidence shape: nonce, hash per line in the raw file; agree, disagree, missing, the first ten disagreements, the device line, the pack and program ids, the manifest sha in the JSON)
|
||||
|
||||
1. **node**: `igneumd` at the manifest's node sha, the engine's own re-check (`IgneumEngine::epoch_for` then `hash_bound` per nonce) through `igneum-miner --recheck-vectors` on the node binary's CPU path (the pool.rs seam), on build-2.
|
||||
2. **CPU reference**: `igneum-pow hash-bound --count 2^20 --nonce <start>` from the manifest's generator commit, on build-2 (the hash lane's reference files are this reader).
|
||||
3. **CUDA**: the kit's `igneum-worker-cuda --serve` driven by `tools/ci/p01-vectors.py` with the job context, on a 5090, 4090 and 3090 pod.
|
||||
4. **OpenCL**: `igneum-worker-opencl --serve` the same way, on the AMD pod when one exists, else PC 1's RX 7600 (the only OpenCL retail cell tonight).
|
||||
5. **Metal**: the Mac's own worker, the same driver, on the mini (the morning's run; never on this Mac).
|
||||
6. **pool**: `igneum-pool` at the manifest's sha with its vendored node at the manifest's node sha, the member-side re-check (`pool/src/node.rs` → `kaspa_pow::igneum::IgneumEngine`) on the same job lines, on build-2.
|
||||
|
||||
## The three phases
|
||||
|
||||
Phase 1 (before): every reader on range 1 under day D. Phase 2 (during): every reader on range 2 where the engine's day moves from D to D+1 inside the range at the boundary nonce the fast-time clock sets; every reader must switch program and dataset at the same nonce. Phase 3 (after): every reader on range 3 under day D+1.
|
||||
|
||||
## Acceptance (the registry row POW-01, cell `harness:same-work`, PASS only when all hold)
|
||||
|
||||
- every reader's hash equals the CPU reference's for every nonce of every phase (zero disagreements, zero missing);
|
||||
- the program id and the day each reader reports at phase 2's boundary are the same across readers (the transition is seen at one nonce);
|
||||
- the pool's accepted-share verdict for a sample of 64 nonces per phase equals the node's (the seam closes by a build: `release-manifest-check.sh` refuses a redefined EpochSeeds and an unpinned vendored node);
|
||||
- the run names the manifest sha (packaging/release-manifest.json), and the evidence sits at build-1:/srv/artefacts/tas/same-work-<run id>/.
|
||||
|
||||
A disagreement on any reader is a red to main within fifteen minutes (the coordinator's rule for the cross-checks).
|
||||
|
||||
## What is still to build (owners, defaults)
|
||||
|
||||
| Piece | Owner | Clock | Default if silent |
|
||||
|---|---|---|---|
|
||||
| `p01-vectors.py --job-context <file> --phase N` (the three ranges and the boundary assertion; the driver already drives the workers) | CI steward | 21:30 | built as specified |
|
||||
| the node reader (`igneum-miner --recheck-vectors`, the seam with a count) | node lane | 22:00 | the steward builds it on a branch of release-2.0.0-node under rule 24 |
|
||||
| the pool reader (the member-side re-check over a job-lines file) | pool lane | 22:30 | the pool's existing recheck_pack path over the context, driven by the steward |
|
||||
| the pods and the mini | fleet lane | on the artefact line | as P01 |
|
||||
|
|
@ -133,8 +133,8 @@ Rule: a case maps to a cell only where the cell's tests visibly answer it; cover
|
|||
|
||||
### bench:pc1-packs
|
||||
|
||||
- Command: `tools/ca3-v4-amend/pc1-ca4-packs.ps1 on PC 1 (RTX 5090, the signed-jobs channel, --cards-off, the Power Helper lock at 1,300 MHz, nvidia-smi at 1 Hz, 250 x 2^24 per row, the kit's CUDA worker --bench)`
|
||||
- Box class: PC 1 bench
|
||||
- Command: `tools/ca3-v4-amend/pc1-ca4-packs.ps1 on the project's own rig (RTX 5090, the signed-jobs channel, --cards-off, the Power Helper lock at 1,300 MHz, nvidia-smi at 1 Hz, 250 x 2^24 per row, the kit's CUDA worker --bench)`
|
||||
- Box class: the project's own rig bench
|
||||
- Fixtures: F0, F1
|
||||
- Cases:
|
||||
- GPU-02 Reproduce Ember clock-lock savings: partial: the paired stock and locked rows on the same board, host and workload (the rate held, 2.37 against 3.26 microjoules per hash on the class v5 pack); the historical 34 to 41 percent claim's full configuration set is owed
|
||||
|
|
@ -144,8 +144,8 @@ Rule: a case maps to a cell only where the cell's tests visibly answer it; cover
|
|||
|
||||
### bench:pc1-amd
|
||||
|
||||
- Command: `tools/ca3-v4-amend/pc1-amd-cardin-bench.ps1 on PC 1 (RX 7600, OpenCL, gfx1102, 30 dispatches of 2^24 per size, rate only)`
|
||||
- Box class: PC 1 bench
|
||||
- Command: `tools/ca3-v4-amend/pc1-amd-cardin-bench.ps1 on the project's own rig (RX 7600, OpenCL, gfx1102, 30 dispatches of 2^24 per size, rate only)`
|
||||
- Box class: the project's own rig bench
|
||||
- Fixtures: F0, F1
|
||||
- Cases:
|
||||
- GPU-01 Cover the declared commodity population: partial: the 8 GB AMD cell, fingerprints and rates at 1, 2, 4 and 5.5 GiB; one cell of P02's twelve
|
||||
|
|
@ -243,7 +243,7 @@ Rule: a case maps to a cell only where the cell's tests visibly answer it; cover
|
|||
- Box class: harness (network run on the 2.0 devnet plus Sepolia reads)
|
||||
- Fixtures: none
|
||||
- Cases:
|
||||
- VER-03 Prove successful payment rather than inclusion: partial: a payment receipt is labelled payment only when the receipt is proven against the segment statement's receipts commitment under the finality certificate, inclusion-only receipts read inclusion, a tampered receipt and a flipped status fail; run on the Devnet 3 fixtures, the 2.0 devnet re-run waits for its first paid segment whose last block carries a transaction
|
||||
- VER-03 Prove successful payment rather than inclusion: partial: a payment receipt is labelled payment only when the receipt is proven against the segment statement's receipts commitment under the finality certificate, inclusion-only receipts read inclusion, a tampered receipt and a flipped status fail; run on the the earlier devnet fixtures, the 2.0 devnet re-run waits for its first paid segment whose last block carries a transaction
|
||||
- VER-04 Bound cross-chain oracle trust and replay: partial: the oracle's trust() names the deployer-installed table and the unchecked aggregator signature, a second hash at a stored certificate index is refused, a root claim at an unknown index is refused, another chain id is refused (tools/reference-apps/oracle/test.mjs on Sepolia); Review B F04: the Sepolia verifiers apply the two-thirds rule only, carry no lock-kind field, and an under-threshold (recovery-rule) certificate fails closed in submitCertificate, so no stored root is a recovery lock (the DEX lane's docs/bridge/light-client-bridge.md paragraph); the claimed-option review of the trust model is the reviewer's
|
||||
- VER-05 Reconstruct required state without founder storage: partial: the public node and the reference reader on build-1 executed the 2.0 devnet from genesis with no snapshot (unit ExecStart without --igneum-exec-snapshot), and the pages fetch headers, certificates, coinbase bodies and account proofs from the public read RPC only; the archive and availability model beyond the 2,048-block state ring is the OPS no-founder exercise
|
||||
- VER-06 Detect withholding, corruption and stale data: partial: the read service never serves a certificate from another network, a withheld header in the path and a corrupted proof node are detected, stale data prints its lock age beside every balance, and a client told no certificate never falls back to a trusted RPC balance; detection on the fault network F4 is not run
|
||||
|
|
@ -336,8 +336,8 @@ Rule: a case maps to a cell only where the cell's tests visibly answer it; cover
|
|||
|
||||
### bench:amd-intel-energy
|
||||
|
||||
- Command: `the AMD-and-Intel energy lane jobs on the signed-jobs channel: run-ae-pc2-b580-energy-20261008 (PC 2, Arc B580, the class v6 kit worker --memprobe and --bench-pack on hl-v6-foldrw and hl-v6-all, Level Zero energy counter) and run-ae-pc1-7600-energy-20261008 (PC 1, RX 7600, the same with ADLX watts at stock, plimit -30, gmax -500 + plimit -30); scripts in docs/analysis/class-v6/amd-intel-energy/`
|
||||
- Box class: PC 1 and PC 2 bench (OpenCL)
|
||||
- Command: `the AMD-and-Intel energy lane jobs on the signed-jobs channel: run-ae-pc2-b580-energy-20261008 (PC 2, Arc B580, the class v6 kit worker --memprobe and --bench-pack on hl-v6-foldrw and hl-v6-all, Level Zero energy counter) and run-ae-pc1-7600-energy-20261008 (the project's own rig, RX 7600, the same with ADLX watts at stock, plimit -30, gmax -500 + plimit -30); scripts in docs/analysis/class-v6/amd-intel-energy/`
|
||||
- Box class: the project's own rig and PC 2 bench (OpenCL)
|
||||
- Fixtures: F0, F1
|
||||
- Cases:
|
||||
- GPU-03 Measure the real 64-register GPU cost: partial: the 64-register window on AMD and Intel, rate per unit of work (RX 7600 0 percent, Arc B580 -0.3 percent, kernel throughput, quiet) with the B580 fingerprints equal on both packs and the offline RDNA allocation (160 VGPRs, no spill); energy owed (the 7600 job queued, the B580 counter unsupported unelevated); team-run, not under the standard's paired protocol or a wall meter
|
||||
|
|
@ -367,7 +367,7 @@ Rule: a case maps to a cell only where the cell's tests visibly answer it; cover
|
|||
|
||||
### pc:install-update
|
||||
|
||||
- Command: `signed jobs and relay runs on PC 1 (ae432dc7) and PC 2 (1ccfe586): the installer over the running app (rule 14: installed versions, the process set, the first upload), the OTA path (update-now, the update-return read-back line), and the no-click resume after the install (relay run mining-on-after-200.ps1)`
|
||||
- Command: `signed jobs and relay runs on the project's own rig (ae432dc7) and PC 2 (1ccfe586): the installer over the running app (rule 14: installed versions, the process set, the first upload), the OTA path (update-now, the update-return read-back line), and the no-click resume after the install (relay run mining-on-after-200.ps1)`
|
||||
- Box class: PC (the two Windows PCs; nothing on the Mac)
|
||||
- Fixtures: F2
|
||||
- Cases:
|
||||
|
|
@ -375,6 +375,31 @@ Rule: a case maps to a cell only where the cell's tests visibly answer it; cover
|
|||
- UX-07 Expose actionable failures and safe updates: partial: the install classes (the payload's stop step, the detached installer under a job, the installer refusing a wrong-version name, a stale install flag) and the no-click resume; the recovery-instruction and canary rows are the shipper's
|
||||
- OPS-03 Separate update distribution from consensus authority: partial: the update path's read-back and the signed manifest's verify on the PC (a tampered or rolled-back payload is refused by the engine's manifest check); authority separation is the shipper's review
|
||||
|
||||
### harness:release-manifest
|
||||
|
||||
- Command: `bash tools/ci/release-manifest-check.sh (self-test, then the tree; the pins, the proof guests' hashes on disk, the fork freeze, the pool's vendored node, the proving manifest's source_commit provenance) and bash tools/ci/build-from-manifest.sh --box N on the release tree (kaspad with igneum-pow, igneum-miner, igneum-pool, igneum-app, igneum-prove-host from packaging/release-manifest.json with the node vendored at the manifest's sha)`
|
||||
- Box class: gate
|
||||
- Fixtures: F0
|
||||
- Cases:
|
||||
- R2-F03-R01 Clean build from one manifest, including the pool and workers, with no unpublished vendor tree.: full for the components the tree builds (node, miner, pool, app, prove-host from one manifest, no unpublished vendor tree); the GPU workers are the kit's cross-build and read under the shipper's kit-isa line, not this cell
|
||||
|
||||
### harness:same-work
|
||||
|
||||
- Command: `tools/ci/p01-vectors.py --job-context <job-context.json> --phase 1|2|3 per reader (CUDA, OpenCL, Metal workers), igneum-miner --recheck-vectors (node), igneum-pow hash-bound (CPU reference), the pool's member-side re-check; docs/plans/igneum-2.0-same-work-test.md`
|
||||
- Box class: release (the readers on their pods and boxes)
|
||||
- Fixtures: F0
|
||||
- Cases:
|
||||
- R2-F03-R02 Same job context produces identical accepted work in node, CPU reference, CUDA, Metal, OpenCL and pool.: partial until every reader has run: bit-for-bit agreement per nonce across node, CPU reference, CUDA, OpenCL, Metal and pool on one job context
|
||||
- R2-F03-R03 Cross every scheduled transition with old/new client behavior documented and identical rule identities.: partial: the day-boundary transition in phase 2 (the one transition every live network crosses hourly at 60x); a class rotation is the same test on a research object until a v6 floor is set
|
||||
|
||||
### canary:fresh-install
|
||||
|
||||
- Command: `the rule 33 fresh-install canary on a release tip (the founder, 8 October 2026): install from the published artefact on a non-AVX-512 box with an empty datadir, sync genesis to tip, five minutes mining with zero refusals and an accepted block, one shard claimed, proved and paid or queued, a bounded quit, every line read back; the record tools/ci/canary/<sha>.json (one file per sha, an artefacts list: fleet, windows, mac, hive) read by tools/ci/canary-check.sh <sha> [--artefact kind], which publish-manifest.sh and publish-public.sh refuse without`
|
||||
- Box class: release (a non-AVX-512 box with an empty datadir)
|
||||
- Fixtures: F0
|
||||
- Cases:
|
||||
- INT-07 One v6 object agrees in node, pool, CPU verifier and each supported GPU host across activation.: partial: V6-12's clean-install half (one published object installed fresh, synced, mined, proved and paid on one host per artefact); the cross-host agreement half (node, pool, CPU verifier, each GPU host across activation) is harness:same-work's
|
||||
|
||||
### review:k-lane-shadow-k
|
||||
|
||||
- Command: `floor lane 2's placed and routed cores on ASAP7 in tools/chip-model/rtl (the rows in docs/analysis/class-v6/floor/shadow-k.md); the register landing 50ff1611f recorded ADV-06 against it before the recorder existed`
|
||||
|
|
@ -388,9 +413,9 @@ Rule: a case maps to a cell only where the cell's tests visibly answer it; cover
|
|||
- GOV-02 Approve thresholds before results: the approval is recorded in the registry's approval field; the automated half (thresholds frozen before any run_status) is the gate rule landing by 21:00
|
||||
- GOV-04 Preserve raw and negative evidence: the evidence vault F9 (raw and negative evidence preserved) is the gate rule landing by 21:00: a PASS must carry its evidence file
|
||||
- GOV-08 Invalidate stale evidence and control public status: the stale-evidence rule (evidence older than the manifest sha reads NOT RUN) is the gate rule landing by 21:00
|
||||
- GPU-04 Find the memory-clock operating ladder: the memory-clock ladder has no harness tonight: PC 1 mines nothing under the Devnet 3 off order
|
||||
- GPU-04 Find the memory-clock operating ladder: the memory-clock ladder has no harness tonight: the project's own rig mines nothing under the the earlier devnet off order
|
||||
- GPU-06 Measure accepted work under ordinary connectivity: accepted work under ordinary connectivity needs the fault network F4
|
||||
- GPU-07 Survive sustained thermal and power operation: the sustained thermal and power soak has no harness tonight: PC 1 mines nothing under the Devnet 3 off order
|
||||
- GPU-07 Survive sustained thermal and power operation: the sustained thermal and power soak has no harness tonight: the project's own rig mines nothing under the the earlier devnet off order
|
||||
- POW-05 Prevent amortised cheap winning attempts: amortised cheap winning attempts are the attack lanes' grind and era harnesses (tools/attack/f7-era, f9-grind), not in the release matrix; their rows come from those lanes
|
||||
- ROT-03 Test miner-voted bring-forward governance: miner-voted bring-forward needs a vote harness on the fault network F4
|
||||
- ROT-04 Resist seed selection and faster evaluators: seed-selection resistance is the census harness (the class v6 invention lane), not yet in the matrix
|
||||
|
|
@ -478,7 +503,6 @@ Rule: a case maps to a cell only where the cell's tests visibly answer it; cover
|
|||
- INT-04 Corrupt existing pool state, disk-full and reorg cause safe recovery, not silent empty balances.: INT-04 (R1 integration gate): the gate's harness is the owner lane's (pool lane (a1c484c48a62948c2)); not yet named in the map
|
||||
- INT-05 The supplied finality implementation matches the approved anchor rule after >window healing.: INT-05 (R1 integration gate): the gate's harness is the owner lane's (node lane (a283f5f0d364ceef0)); not yet named in the map
|
||||
- INT-06 Recovery tests state and preserve their weaker fault bound; interfaces never label it as a stronger guarantee.: INT-06 (R1 integration gate): the gate's harness is the owner lane's (node lane (a283f5f0d364ceef0)); not yet named in the map
|
||||
- INT-07 One v6 object agrees in node, pool, CPU verifier and each supported GPU host across activation.: INT-07 (R1 integration gate): the gate's harness is the owner lane's (CI steward with the hash lane (a690540514aa453d7) and the worker lane (a9e87343f008e0edd)); not yet named in the map
|
||||
- INT-08 Census, production acceptance, schedule counters and live-dataset tests use the identical frozen contract.: INT-08 (R1 integration gate): the gate's harness is the owner lane's (CI steward with the hash lane (a690540514aa453d7) and the worker lane (a9e87343f008e0edd)); not yet named in the map
|
||||
- INT-09 Eight- and twelve-GiB epoch transitions recover deliberately without relying on repeated OOM/watchdog cycles.: INT-09 (R1 integration gate): the gate's harness is the owner lane's (app lane); not yet named in the map
|
||||
- INT-10 Metal/CUDA/OpenCL exact dataset geometry agrees; unsupported packs reject before launch.: INT-10 (R1 integration gate): the gate's harness is the owner lane's (CI steward with the hash lane (a690540514aa453d7) and the worker lane (a9e87343f008e0edd)); not yet named in the map
|
||||
|
|
@ -497,9 +521,6 @@ Rule: a case maps to a cell only where the cell's tests visibly answer it; cover
|
|||
- R2-F02-R01 Release build cannot activate test bypass.: R2-F02 (P0, the external review): the regression's harness is the owner lane's (proving lane, CI steward); not yet named in the map
|
||||
- R2-F02-R02 Missing oracle or pinned keys prevents service readiness after enforcement activation.: R2-F02 (P0, the external review): the regression's harness is the owner lane's (proving lane, CI steward); not yet named in the map
|
||||
- R2-F02-R03 Missing proof bytes retry without incorrectly marking a valid block permanently invalid.: R2-F02 (P0, the external review): the regression's harness is the owner lane's (proving lane, CI steward); not yet named in the map
|
||||
- R2-F03-R01 Clean build from one manifest, including the pool and workers, with no unpublished vendor tree.: R2-F03 (P0, the external review): the regression's harness is the owner lane's (CI steward, hash lane (ProgramClass::V6 on freeze), pool lane); not yet named in the map
|
||||
- R2-F03-R02 Same job context produces identical accepted work in node, CPU reference, CUDA, Metal, OpenCL and pool.: R2-F03 (P0, the external review): the regression's harness is the owner lane's (CI steward, hash lane (ProgramClass::V6 on freeze), pool lane); not yet named in the map
|
||||
- R2-F03-R03 Cross every scheduled transition with old/new client behavior documented and identical rule identities.: R2-F03 (P0, the external review): the regression's harness is the owner lane's (CI steward, hash lane (ProgramClass::V6 on freeze), pool lane); not yet named in the map
|
||||
- R2-F04-R01 Native multi-node 40/40/20 partition, equivocation and dust-valid mining on both sides past the window.: R2-F04 (P0, the external review): the regression's harness is the owner lane's (node lane, reference apps, site (explorer)); not yet named in the map
|
||||
- R2-F04-R02 Proof that the chosen recovery guarantee matches the public finality claim; two valid contradictory certificates are a hard failure for strong finality.: R2-F04 (P0, the external review): the regression's harness is the owner lane's (node lane, reference apps, site (explorer)); not yet named in the map
|
||||
- R2-F04-R03 Pause-only resume with historical backfill, missing historical data and all old keys returning.: R2-F04 (P0, the external review): the regression's harness is the owner lane's (node lane, reference apps, site (explorer)); not yet named in the map
|
||||
|
|
@ -537,4 +558,4 @@ Rule: a case maps to a cell only where the cell's tests visibly answer it; cover
|
|||
|
||||
## Count
|
||||
|
||||
171 automated cases: 79 mapped to a cell, 149 NOT RUN with a reason.
|
||||
171 automated cases: 83 mapped to a cell, 145 NOT RUN with a reason.
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -53,7 +53,7 @@ TOKEN_FILE="$HOME/.config/igneum/dl-token"
|
|||
SIGNER="$ROOT/app/igneum-app/target/release/igneum-ota-sign"
|
||||
PRODUCT="app"
|
||||
|
||||
VERSION="" MAC="" WIN="" NOTES="" ACTIVATION="" DEADLINE="" MIN_SUPPORTED="" CHANNEL="devnet" BASE="" DEST="" DEPLOY=0 VERIFY_ONLY=0 TRIES=12
|
||||
RELEASE_SHA="" CANARY_GUARD_SELF_TEST=0 VERSION="" MAC="" WIN="" NOTES="" ACTIVATION="" DEADLINE="" MIN_SUPPORTED="" CHANNEL="devnet" BASE="" DEST="" DEPLOY=0 VERIFY_ONLY=0 TRIES=12
|
||||
NODE_BIN="" NET_DIGEST="" MOVE_CLOCK="" DIGEST_GUARD_SELF_TEST=0
|
||||
OVERRIDE="" TUNING_FILE="" NO_TUNING=0 PUBLIC=0
|
||||
while [ $# -gt 0 ]; do
|
||||
|
|
@ -83,6 +83,8 @@ while [ $# -gt 0 ]; do
|
|||
--network-digest) NET_DIGEST="$2"; shift 2 ;; # the network's CURRENT digest (a 64-hex, or "log:<file or journal:unit>[@user@host]" read by tools/digest-read.sh on the hub)
|
||||
--move-clock) MOVE_CLOCK="$2"; shift 2 ;; # "HH:MM UTC, <reason>": the move's clock, when the entry's digest differs by design; logged in the notes
|
||||
--self-test-digest-guard) DIGEST_GUARD_SELF_TEST=1; shift ;;
|
||||
--release-sha) RELEASE_SHA="$2"; shift 2 ;; # rule 33 (8 Oct 2026): the release tip's commit; its fresh-install canary record must read PASS (tools/ci/canary-check.sh)
|
||||
--self-test-canary-guard) CANARY_GUARD_SELF_TEST=1; shift ;;
|
||||
*) echo "unknown argument: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
|
@ -109,6 +111,47 @@ if [ "$DIGEST_GUARD_SELF_TEST" = 1 ]; then
|
|||
echo "digest guard self-test: a differing digest is refused without a move clock and accepted with one; an equal digest passes; the digest reader parses"
|
||||
exit 0
|
||||
fi
|
||||
# Rule 33 (the founder, 8 October 2026, 21:3x UK, "no more lost time"): a release entry may not publish without a fresh-install
|
||||
# canary record for its sha (install from the artefact on a non-AVX-512 box with an empty datadir, genesis to tip, five minutes
|
||||
# mining with zero refusals and an accepted block, one shard claimed, proved and paid or queued, a bounded quit, every line read
|
||||
# back): tools/ci/canary/<sha>.json read by tools/ci/canary-check.sh. A write to a real downloads folder needs --release-sha and a
|
||||
# PASS record; a loopback --base-url test write and --verify-only do not publish and are not gated.
|
||||
canary_guard() { # <release sha> [kind ...] -> 0 pass (the lines printed), 1 refused; a kind (mac, windows) asks for that entry's own artefact block
|
||||
local sha="$1"; shift; local k
|
||||
[ -n "$sha" ] || { echo "canary guard: REFUSED: a release entry needs --release-sha <release tip commit> with a fresh-install canary record (rule 33; tools/ci/canary-check.sh --form)"; return 1; }
|
||||
if [ $# -eq 0 ]; then bash "$TOOLS/ci/canary-check.sh" "$sha" || { echo "canary guard: REFUSED: the entry's sha ${sha:0:12} has no PASS fresh-install canary record (rule 33)"; return 1; }; return 0; fi
|
||||
for k in "$@"; do bash "$TOOLS/ci/canary-check.sh" "$sha" --artefact "$k" || { echo "canary guard: REFUSED: the $k entry's sha ${sha:0:12} has no PASS fresh-install canary record for its own artefact (rule 33)"; return 1; }; done
|
||||
}
|
||||
if [ "$CANARY_GUARD_SELF_TEST" = 1 ]; then
|
||||
bash "$TOOLS/ci/canary-check.sh" --self-test >/dev/null || exit 1
|
||||
d=$(mktemp -d); trap 'rm -rf "$d"' EXIT; S=0123456789abcdef0123456789abcdef01234567
|
||||
canary_guard "" >/dev/null 2>&1 && { echo "canary guard self-test: FAIL: an entry with no --release-sha was accepted"; exit 1; }
|
||||
CANARY_DIR="$d" canary_guard "$S" >/dev/null 2>&1 && { echo "canary guard self-test: FAIL: a sha with no record was accepted"; exit 1; }
|
||||
bash "$TOOLS/ci/canary-check.sh" --form | python3 -c "
|
||||
import json,sys; r=json.load(sys.stdin); r['sha']='$S'; r['artefact']['sha256']='ab'*32; r['box']={'host':'build-4','isa_line':'kit-isa: clean'}
|
||||
r['sync'].update(tip_height=10, seconds=1); r['quit']['seconds']=1; r['recorded_at']='t'; r['recorded_by']='self-test'; json.dump(r, open('$d/$S.json','w'))"
|
||||
CANARY_DIR="$d" canary_guard "$S" >/dev/null 2>&1 || { echo "canary guard self-test: FAIL: a sha with a PASS record was refused"; exit 1; }
|
||||
python3 -c "import json; p='$d/$S.json'; r=json.load(open(p)); r['mining']['refusals']=1; json.dump(r, open(p,'w'))"
|
||||
CANARY_GUARD_FAILED=0; CANARY_DIR="$d" canary_guard "$S" >/dev/null 2>&1 && { echo "canary guard self-test: FAIL: a record with a refusal was accepted"; exit 1; }
|
||||
python3 -c "
|
||||
import json,copy; p='$d/$S.json'; r=json.load(open(p)); blk={k:r[k] for k in ('artefact','box','datadir','sync','mining','shard','quit','lines_read_back')}; blk['mining']['refusals']=0; arts=[]
|
||||
for kind in ('fleet','windows','mac'):
|
||||
b=copy.deepcopy(blk); b['kind']=kind; arts.append(b)
|
||||
arts[1]['mining']['refusals']=3
|
||||
json.dump({'sha':r['sha'],'artefacts':arts,'verdict':'PASS','recorded_at':'t','recorded_by':'self-test'}, open(p,'w'))"
|
||||
CANARY_DIR="$d" canary_guard "$S" mac >/dev/null 2>&1 || { echo "canary guard self-test: FAIL: the mac entry was refused though its own block passes"; exit 1; }
|
||||
CANARY_DIR="$d" canary_guard "$S" windows >/dev/null 2>&1 && { echo "canary guard self-test: FAIL: the windows entry passed on a failing windows block"; exit 1; }
|
||||
CANARY_DIR="$d" canary_guard "$S" mac windows >/dev/null 2>&1 && { echo "canary guard self-test: FAIL: a mac plus windows publish passed with the windows block failing"; exit 1; }
|
||||
echo "canary guard self-test: no sha, no record and a failing record are refused; a PASS record passes; an entry publishes on its own artefact's block (mac passes, windows refused on its own failing block); the record check's own self-test passes"
|
||||
exit 0
|
||||
fi
|
||||
case "${BASE:-}" in http://127.0.0.1*|http://localhost*|http://\[::1\]*) CANARY_GATED=0 ;; *) CANARY_GATED=1 ;; esac
|
||||
if [ "$VERIFY_ONLY" != 1 ] && [ "$CANARY_GATED" = 1 ] && [ -z "${IGNEUM_CANARY_SELF_TEST_SKIP:-}" ]; then
|
||||
KINDS=""; [ -n "$MAC" ] && KINDS="$KINDS mac"; [ -n "$WIN" ] && KINDS="$KINDS windows"
|
||||
# shellcheck disable=SC2086
|
||||
canary_guard "$RELEASE_SHA" $KINDS || exit 1
|
||||
NOTES="${NOTES:+$NOTES; }release: $RELEASE_SHA (fresh-install canary PASS)"
|
||||
fi
|
||||
if [ -n "$NODE_BIN" ] || [ -n "$NET_DIGEST" ]; then
|
||||
[ -n "$NODE_BIN" ] && [ -n "$NET_DIGEST" ] || { echo "the digest guard needs both --node-bin and --network-digest" >&2; exit 2; }
|
||||
ENTRY_DIGEST=$(bash "$TOOLS/digest-read.sh" binary "$NODE_BIN") || exit 1
|
||||
|
|
|
|||
|
|
@ -32,10 +32,11 @@ KEY="$CFG/ota-signing-key"; PUB_KEY="$CFG/ota-signing-key.pub"
|
|||
SIGNER="${IGNEUM_OTA_SIGN:-$ROOT/app/igneum-app/target/release/igneum-ota-sign}" # a built signer elsewhere (another worktree)
|
||||
HOST="https://dl.igneum.network"
|
||||
|
||||
DO_APP=0 DO_WALLET=0 HIVE="" DO_ALIASES=0 DRY=0 DEPLOY=0 VERIFY=0 PRUNE=1 DEST="" BASE="" TRIES=12
|
||||
RELEASE_SHA="" DO_APP=0 DO_WALLET=0 HIVE="" DO_ALIASES=0 DRY=0 DEPLOY=0 VERIFY=0 PRUNE=1 DEST="" BASE="" TRIES=12
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--app) DO_APP=1; shift ;;
|
||||
--release-sha) RELEASE_SHA="$2"; shift 2 ;; # rule 33: the release tip's commit (read from the app manifest's notes when not given); its canary record must read PASS
|
||||
--wallet) DO_WALLET=1; shift ;;
|
||||
--hive) HIVE="$2"; shift 2 ;;
|
||||
--aliases) DO_ALIASES=1; shift ;;
|
||||
|
|
@ -107,6 +108,26 @@ PY
|
|||
log " $name: $(python3 -c 'import json,sys; m=json.load(open(sys.argv[1])); print(m.get("version"), "+".join(m.get("platforms",{})))' "$out") written, signed, verified; URLs under $BASE_PUB"
|
||||
}
|
||||
|
||||
# Rule 33 (8 October 2026): nothing goes to dl/public/ for a release whose sha has no PASS fresh-install canary record. The app
|
||||
# manifest carries "release: <sha>" in its notes (publish-manifest.sh --release-sha writes it); --release-sha overrides or supplies it
|
||||
# for the HiveOS package. tools/ci/canary-check.sh reads tools/ci/canary/<sha>.json.
|
||||
canary_gate() { # <sha> <what> [kind ...]: each kind (mac, windows, hive) must have its own PASS artefact block; no kind = the whole record
|
||||
local sha="$1" what="$2"; shift 2; local k; local chk; chk="$(cd "$(dirname "$0")/../.." && pwd)/tools/ci/canary-check.sh"
|
||||
[ -n "$sha" ] || { echo "canary guard: REFUSED: $what names no release sha (publish the token entry with publish-manifest.sh --release-sha, or pass --release-sha here); rule 33" >&2; return 1; }
|
||||
if [ $# -eq 0 ]; then bash "$chk" "$sha" | scrub || { echo "canary guard: REFUSED: ${sha:0:12} has no PASS fresh-install canary record; rule 33" >&2; return 1; }; return 0; fi
|
||||
for k in "$@"; do bash "$chk" "$sha" --artefact "$k" | scrub || { echo "canary guard: REFUSED: $what: the $k entry's sha ${sha:0:12} has no PASS fresh-install canary record for its own artefact; rule 33" >&2; return 1; }; done
|
||||
}
|
||||
if [ "$DO_APP" = 1 ] && [ -z "${IGNEUM_CANARY_SELF_TEST_SKIP:-}" ]; then
|
||||
sha="$RELEASE_SHA"
|
||||
[ -n "$sha" ] || sha=$(python3 -c 'import json,re,sys; m=json.load(open(sys.argv[1])); x=re.search(r"release: ([0-9a-f]{8,40})", str(m.get("notes",""))); print(x.group(1) if x else "")' "$SRC/igneum-app-latest.json" 2>/dev/null || true)
|
||||
kinds=$(python3 -c 'import json,sys; m=json.load(open(sys.argv[1])); ks=[]
|
||||
for p in m.get("platforms",{}):
|
||||
ks.append("mac" if "mac" in p.lower() or "darwin" in p.lower() else "windows" if "win" in p.lower() else p)
|
||||
print(" ".join(ks))' "$SRC/igneum-app-latest.json" 2>/dev/null || true)
|
||||
# shellcheck disable=SC2086
|
||||
canary_gate "$sha" "the app manifest" $kinds || exit 1
|
||||
fi
|
||||
if [ -n "$HIVE" ] && [ -z "${IGNEUM_CANARY_SELF_TEST_SKIP:-}" ]; then canary_gate "$RELEASE_SHA" "the HiveOS package" hive || exit 1; fi
|
||||
if [ "$DO_APP" = 1 ]; then log "app manifest -> dl/public/"; publish_manifest igneum-app-latest.json; fi
|
||||
if [ "$DO_WALLET" = 1 ]; then log "wallet manifest -> dl/public/"; publish_manifest igneum-wallet-latest.json; fi
|
||||
if [ -n "$HIVE" ]; then
|
||||
|
|
|
|||
|
|
@ -16,8 +16,10 @@
|
|||
| the kit ISA check (`kit-isa-check.sh`; in the gate as a self-test, in `merge-to-master.sh` over any executable a landing adds under packaging/kits or bin, and in the shipper's cut gate over the kit) | Any binary whose disassembly carries a zmm register, an EVEX opmask or an EVEX-only mnemonic (AVX-512): a fleet binary comes only from the cross-build kit at the x86-64-v3 baseline, never from a box's native gate build (79 fleet hosts died on one, 8 Oct 2026) | 8 Oct 2026 |
|
||||
| kill-by-name rule 5 and the no-kill shim (`kill-by-name-check.sh`, `no-kill-shim/{pkill,killall}` exit 97 when first in PATH) | A `pgrep -f`/`pkill -f` pattern that is a bare path, a log name or an unanchored word; only `^`-anchored command patterns, the bracket form, a variable, -x or -F pass (fifteen Mac processes died to a grep, 8 Oct 2026) | 8 Oct 2026 |
|
||||
| a "cut" batch needs its read-back (`test-record.mjs`) | A batch declaring `cut` without the binary's build-N:/srv path, its commit string read back equal to the manifest sha, and the kit ISA check's clean line; a sha is cut only when its binary exists on build-1 with its commit string read back | 8 Oct 2026 |
|
||||
| rule 33: a release entry publishes only with a PASS fresh-install canary record for its sha (`canary-check.sh`; `tools/ci/canary/<sha>.json`, the form from `--form`; refused without by `packaging/ota/publish-manifest.sh --release-sha`, `publish-public.sh` and deploy-win.sh; the canary cell maps INT-07) | A release entry published before its sha had run a fresh install on a non-AVX-512 box with an empty datadir, synced, mined five minutes with zero refusals, claimed, proved and paid or queued one shard and quit inside a bound, every line read back (the founder's "no more lost time", 8 October 2026) |
|
||||
| the INT suite is generated from the master edition's integration gates (`int-suite.mjs`; the owner per the coordinator's crosswalk) and INT-17 is a rule of the writer: a cell declaring a missing oracle, pinned keys or mandatory real-proof fixture writes BLOCKED, never PASS | A registry whose INT suite drifts from traceability.json; a batch cell with `prereqs` where any is not "present" written as anything but BLOCKED | 8 Oct 2026 |
|
||||
| the REV suite is generated from an external review's findings.json and dispatch.md (`review-suite.mjs`; one case per required regression, NOT RUN, the owner from the dispatch table) | A registry whose REV suite differs from the generator's output (--check) | 8 Oct 2026 |
|
||||
| F03 (Review B): one release manifest (`packaging/release-manifest.json` on a release branch) and every component built from it (`release-manifest-check.sh`, `build-from-manifest.sh`) | A tree whose own pins disagree with the manifest: the Windows node-source pin, the proving manifest's elf and vk sha256s and the files' hashes, the node fork's freeze fingerprint, the pool's vendored node checkout, a redefined EpochSeeds in the pool (the shadow_reps seam closes by a build against the pinned node); the build script puts the fork at the manifest's node sha and checks kaspad with igneum-pow (rule 19), the miner, the pool, the app and the prove host on a box | 8 Oct 2026 |
|
||||
| a registry landing carries its batches (`tools/ci/batches/<run id>.json`; `merge-to-master.sh` replays them onto master's copy at the merge) | Nothing by itself: the registry is a hot file, and a branch whose own copy of it was recorded during a seven-minute gate lost the race to another lane's rows three times in a row (8 Oct 2026, 19:1x UK). A branch that adds batch files is merged with master's registry, every added batch replayed through `test-record.mjs --record` (idempotent), and the evidence rules run on the merged result; rule 26 does not bind the registry path for such a branch | 8 Oct 2026 |
|
||||
| the registry's evidence rules (`registry-evidence-check.sh`, called by `merge-to-master.sh` after rule 26) | A landing that sets a case's run_status to PASS without an evidence_path that exists (in the tree at the landing, or on a build box over ssh; a box that does not answer is a line, not a refusal); a landing that changes a file under docs/analysis/ or a path a registry row names without moving that row's `updated` (the row and its evidence move together, GOV-04); a PASS whose evidence record pins another manifest than the registry's pinned_manifest_sha (stale evidence reads NOT RUN, GOV-08); a run_status written while the registry carries no approval block (thresholds before results, GOV-02) | 8 Oct 2026 |
|
||||
| the acceptance layer (`test-record.mjs`, `test-map.json`, `test-map-doc.mjs`; the founder's Test and Acceptance Standard, docs/plans/igneum-2.0-test-registry.json) | An automated case of the registry with no cell in the map and no NOT RUN reason; a map naming an unknown case; a stale harness-map page (generated from the JSON); the recorder's self-test: a run batch writes run_status, run_id, evidence_path, updated and the evidence record to the mapped cases only, never an accept text, and a case with no harness reads NOT RUN with its reason, never PASS by inference | 8 Oct 2026 |
|
||||
|
|
|
|||
28
tools/ci/batches/canary-20261008-01.json
Normal file
28
tools/ci/batches/canary-20261008-01.json
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"run_id": "canary-20261008-01",
|
||||
"manifest_sha": "c30ab32c",
|
||||
"method": "team-reported",
|
||||
"evidence_dir": "tools/ci/canary (no record yet)",
|
||||
"boxes": [],
|
||||
"release_identity": {
|
||||
"commit": "c30ab32c (release-2.0.1 final tip; 2.0.2 open at c608b341)",
|
||||
"lockfile": "",
|
||||
"binary": "the shipped 2.0.1 entries on aa0e0f45's binaries",
|
||||
"network_object": "igneum-devnet-4, chain id 4465",
|
||||
"activation": "",
|
||||
"profile_hashes": ""
|
||||
},
|
||||
"claim_impact": "INT-07's clean-install half reads BLOCKED until a release tip carries a PASS fresh-install canary record; no published entry may move before one does (rule 33)",
|
||||
"note": "Rule 33 recorded at 21:4x UK: the canary cell exists, its check and the publish-path guards are in the tree; no 2.0.x sha has a record yet, so the case is BLOCKED (prerequisite: the shipper's 2.0.2 canary tonight writes tools/ci/canary/<sha>.json).",
|
||||
"cells": [
|
||||
{
|
||||
"cell": "canary:fresh-install",
|
||||
"cases": [
|
||||
"INT-07"
|
||||
],
|
||||
"status": "BLOCKED",
|
||||
"evidence": "tools/ci/canary-check.sh; packaging/ota/publish-manifest.sh; packaging/ota/publish-public.sh",
|
||||
"note": "no fresh-install canary record exists for any 2.0.x release tip; the 2.0.2 canary is the shipper's tonight"
|
||||
}
|
||||
]
|
||||
}
|
||||
39
tools/ci/batches/f03-manifest-20261008-01.json
Normal file
39
tools/ci/batches/f03-manifest-20261008-01.json
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"run_id": "f03-manifest-20261008-01",
|
||||
"manifest_sha": "c30ab32c",
|
||||
"method": "static",
|
||||
"evidence_dir": "build-1:/srv/artefacts/tas/f03-manifest-20261008-01 (build-from-manifest-c30ab32c-build4.log, sha256 6040ded8e99f0871\u2026); the build tree build-4:/srv/builds/igneum-wt-f03-201 at c30ab32c",
|
||||
"boxes": [
|
||||
"build-4"
|
||||
],
|
||||
"release_identity": {
|
||||
"commit": "c30ab32c",
|
||||
"lockfile": "the release tree's Cargo.lock files at c30ab32c",
|
||||
"binary": "cargo check only: no binary is claimed by this cell; the fleet binaries are the shipper's kit on aa0e0f45",
|
||||
"network_object": "igneum-devnet-4, chain id 4465",
|
||||
"activation": "none (a build fact)",
|
||||
"profile_hashes": "release-manifest-check: every component's own pin equals packaging/release-manifest.json"
|
||||
},
|
||||
"claim_impact": "F03's first rung: one manifest, one build, no unpublished vendor tree; the same-work rung (R02) and the transition rung (R03) stay NOT RUN until the readers run on the job context",
|
||||
"note": "R2-F03-R01, the clean build from one manifest: every component builds from packaging/release-manifest.json on release-2.0.1's final tip c30ab32c with the node vendored at the manifest's sha 7cfa422a as a real checkout (vendor/igneum-node and vendor/igneum-node-exec; a link ships as nothing); the pool builds only from the release tree (its igneum-pow is the class v5 freeze cbc5bd0a, rule 19; master's a65e4c5a refuses it). Earlier runs on aa0e0f45 were red on the pool (KeyReveal.sig_scheme, the pool-review-b shape not yet in the tree) and on prove-host (the exec vendor missing); both closed by the tree, not by the script. The workers are the kit's cross-build (the shipper's kit-isa line), not this build. The build: tools/ci/build-from-manifest.sh --box 4 on release-2.0.1 c30ab32c, 21:11 to 21:14 UK, kaspad with igneum-pow, igneum-miner, igneum-pool, igneum-app and igneum-prove-host all check green from packaging/release-manifest.json, and release-manifest-check reads every component's own pin equal to the manifest; the log on build-1 (sha256 6040ded8e99f0871...).",
|
||||
"cells": [
|
||||
{
|
||||
"cell": "harness:release-manifest",
|
||||
"cases": [
|
||||
"R2-F03-R01"
|
||||
],
|
||||
"status": "PASS",
|
||||
"evidence": "build-1:/srv/artefacts/tas/f03-manifest-20261008-01/build-from-manifest-c30ab32c-build4.log; build-4:/srv/builds/igneum-wt-f03-201; tools/ci/build-from-manifest.sh; tools/ci/release-manifest-check.sh"
|
||||
},
|
||||
{
|
||||
"cell": "check:freeze",
|
||||
"cases": [
|
||||
"GOV-01"
|
||||
],
|
||||
"status": "NOT RUN",
|
||||
"in_progress": true,
|
||||
"evidence": "docs/plans/igneum-2.0-f0-manifest.md; build-4:/srv/builds/igneum-wt-f03-201/vendor/igneum-node/packaging/pow-freeze.txt; build-4:/srv/builds/igneum-wt-f03-201",
|
||||
"note": "GOV-01 moves with its evidence page: the F0 manifest's cut-tip row gained the final tip c30ab32c and the 21:14 UK build-from-manifest fact; the freeze itself is unchanged (class v5 1c420786, fingerprint cbc5bd0a) and GOV-01 stays NOT RUN in progress until the signing block at 23:30"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"run_id": "floor-k-20261008-rows-repeat",
|
||||
"manifest_sha": "86e5b0fb",
|
||||
"cut_tip": "class-v6-floor-k 86e5b0fb (the amendment carries shadow-k.md; the rows that name it move with it, no decision changed: ADV-05 and ADV-06 floor lane 2's on its word, POW-03 and POW-07 the steward's kills-20261008)",
|
||||
"cut_tip": "class-v6-floor-k 86e5b0fb (the amendment carries shadow-k.md; floor lane 2's rows ADV-05 and ADV-06 move with it on its word, method model, no decision changed; the steward's rows no longer cite the directory since d2e1c192)",
|
||||
"evidence_dir": "docs/analysis/class-v6/floor/shadow-k.md",
|
||||
"boxes": [],
|
||||
"cells": [
|
||||
|
|
@ -21,20 +21,6 @@
|
|||
"method": "model",
|
||||
"evidence": "docs/analysis/class-v6/floor/shadow-k.md",
|
||||
"note": "repeat of team-2026-10-08 for ADV-06 at this manifest on floor lane 2's word; no decision changed"
|
||||
},
|
||||
{
|
||||
"cell": "experiment:connected-state",
|
||||
"status": "FAIL",
|
||||
"method": "GPU",
|
||||
"evidence": "docs/analysis/class-v6/connected-state.md",
|
||||
"note": "repeat of kills-20261008 for POW-03, the evidence at its file; the steward's row"
|
||||
},
|
||||
{
|
||||
"cell": "experiment:mixed-fp32",
|
||||
"status": "FAIL",
|
||||
"method": "GPU",
|
||||
"evidence": "docs/analysis/class-v6/rows/pow-07-fp32-unreachable.md",
|
||||
"note": "repeat of kills-20261008 for POW-07, the evidence at its file; the steward's row"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,5 +15,5 @@
|
|||
"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."
|
||||
"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. Replayed at the rule 33 landing (21:4x UK): the narrowed evidence reaches master's rows only through a replay, and the merge replayed added batches alone until this landing."
|
||||
}
|
||||
|
|
|
|||
14
tools/ci/batches/pool-2.0-20261008-03.json
Normal file
14
tools/ci/batches/pool-2.0-20261008-03.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"run_id": "pool-2.0-20261008-03",
|
||||
"manifest_sha": "986252e73",
|
||||
"method": "native",
|
||||
"evidence_dir": "docs/analysis/pool",
|
||||
"cells": [
|
||||
{
|
||||
"cell": "suite:pool",
|
||||
"status": "RUNNING",
|
||||
"method": "native",
|
||||
"evidence": "docs/analysis/pool/pool-pair-2026-10-08.md"
|
||||
}
|
||||
]
|
||||
}
|
||||
33
tools/ci/build-from-manifest.sh
Executable file
33
tools/ci/build-from-manifest.sh
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
#!/usr/bin/env bash
|
||||
# F03 (Review B): every component is built from the one release manifest. Reads packaging/release-manifest.json of this tree,
|
||||
# puts the node fork at the manifest's node sha under vendor/igneum-node (the pool's path dependency, so the EpochSeeds seam closes
|
||||
# by a build against the pinned node), then on a box at gate priority (tools/build-remote.sh): cargo check of kaspad with the
|
||||
# igneum-pow feature (rule 19 proves the generator's fingerprint at build time), igneum-miner, the pool crate (igneum-pool), the app
|
||||
# crate (igneum-app) and the prove host; then tools/ci/release-manifest-check.sh over the tree and the fork. One red = exit 1.
|
||||
# tools/ci/build-from-manifest.sh [--box N] [--dry] from the release branch's worktree; --dry prints the plan
|
||||
set -euo pipefail
|
||||
ROOT=$(git rev-parse --show-toplevel); cd "$ROOT"; BOX=""; DRY=0
|
||||
while [ $# -gt 0 ]; do case "$1" in --box) BOX="$2"; shift 2 ;; --dry) DRY=1; shift ;; *) echo "unknown $1" >&2; exit 2 ;; esac; done
|
||||
M=packaging/release-manifest.json; [ -f "$M" ] || { echo "build-from-manifest: no $M on this tree" >&2; exit 2; }
|
||||
NODE=$(python3 -c "import json;print(json.load(open('$M'))['node']['sha'])"); FP=$(python3 -c "import json;print(json.load(open('$M'))['generator']['fingerprint'])")
|
||||
echo "build-from-manifest: node $NODE, generator fingerprint ${FP:0:16}, miner app $(git rev-parse --short HEAD)"
|
||||
FORK=vendor/igneum-node; MIRROR="${IGNEUM_NODE_MIRROR:-build@188.40.146.49:/srv/igneum-node.git}"
|
||||
if [ "$DRY" = 1 ]; then echo "plan: $FORK at $NODE from $MIRROR; cargo check kaspad(+igneum-pow), igneum-miner, igneum-pool, igneum-app, igneum-prove-host on box ${BOX:-auto} at gate priority; then release-manifest-check.sh $FORK"; exit 0; fi
|
||||
if [ -d "$FORK/.git" ] || [ -f "$FORK/.git" ]; then git -C "$FORK" fetch -q "$MIRROR" "$NODE" 2>/dev/null || git -C "$FORK" fetch -q "$MIRROR" release-2.0.0-node; git -C "$FORK" checkout -q --detach "$NODE"
|
||||
else mkdir -p vendor && git clone -q "$MIRROR" "$FORK" && git -C "$FORK" checkout -q --detach "$NODE"; fi
|
||||
# the proving workspace names the same fork vendor/igneum-node-exec (proving/igneum-prove/Cargo.toml): a second checkout at the same
|
||||
# sha, never a link (build-remote ships directories as overlays to the box; a link ships as nothing)
|
||||
if [ -L vendor/igneum-node-exec ]; then rm -f vendor/igneum-node-exec; fi
|
||||
if [ -d vendor/igneum-node-exec/.git ] || [ -f vendor/igneum-node-exec/.git ]; then git -C vendor/igneum-node-exec fetch -q "$MIRROR" "$NODE" 2>/dev/null || true; git -C vendor/igneum-node-exec checkout -q --detach "$NODE"
|
||||
else git -C "$FORK" worktree add -q --detach "$ROOT/vendor/igneum-node-exec" "$NODE" 2>/dev/null || git clone -q "$MIRROR" vendor/igneum-node-exec && git -C vendor/igneum-node-exec checkout -q --detach "$NODE"; fi
|
||||
echo "build-from-manifest: $FORK at $(git -C "$FORK" rev-parse --short HEAD); vendor/igneum-node-exec at $(git -C vendor/igneum-node-exec rev-parse --short HEAD)"
|
||||
run() { local name="$1" dir="$2"; shift 2; echo "build-from-manifest: $name"; ( cd "$dir" && IGNEUM_AGENT=f03 bash "$ROOT/tools/build-remote.sh" ${BOX:+--box "$BOX"} --no-fetch --priority gate -- "$@" ) > "/tmp/f03-$name.log" 2>&1 || { echo "build-from-manifest: RED: $name (see /tmp/f03-$name.log)" >&2; grep -m3 -E '^error|RED|panicked' "/tmp/f03-$name.log" | cut -c1-160 >&2; return 1; }; echo "build-from-manifest: $name ok"; }
|
||||
rc=0
|
||||
run kaspad "$FORK" check --release -p kaspad --features kaspad/igneum-pow || rc=1
|
||||
run igneum-miner "$FORK" check --release -p igneum-miner || rc=1
|
||||
run igneum-pool pool check --release || rc=1
|
||||
run igneum-app app/igneum-app check --release || rc=1
|
||||
run prove-host proving/igneum-prove check --release -p igneum-prove-host || rc=1
|
||||
bash tools/ci/release-manifest-check.sh "$FORK" || rc=1
|
||||
[ "$rc" = 0 ] && echo "build-from-manifest: every component builds from the manifest and every pin agrees"
|
||||
exit $rc
|
||||
174
tools/ci/canary-check.sh
Executable file
174
tools/ci/canary-check.sh
Executable file
|
|
@ -0,0 +1,174 @@
|
|||
#!/usr/bin/env bash
|
||||
# Rule 33 (the founder's "no more lost time", 8 October 2026, 21:3x UK): a release entry may not publish without a
|
||||
# fresh-install canary record for its sha. The record is one JSON file in the tree, tools/ci/canary/<sha>.json (the
|
||||
# release tip's commit, 8 to 40 hex), written by the lane that ran the canary, with the eight lines the founder named,
|
||||
# each read back with its evidence path on a box (build-N:/srv/... or /srv/...). This check reads the record and says
|
||||
# PASS or names the first line that is missing or failing; the publish path (packaging/ota/publish-manifest.sh,
|
||||
# packaging/ota/publish-public.sh, deploy-win.sh) refuses an entry whose sha has no PASS record.
|
||||
#
|
||||
# tools/ci/canary-check.sh <sha> [--artefact <kind>] exit 0: PASS (the line printed); 1: no record or a line fails (named); 2: bad args
|
||||
# a record holds one artefact block at the top level, or an "artefacts" list (one block per entry's artefact:
|
||||
# kind fleet | windows | mac | hive, each from its own non-AVX-512 box with its own eight lines); --artefact asks
|
||||
# for that kind's block, so a Mac entry publishes on the Mac canary and the Windows entry waits for its own
|
||||
# tools/ci/canary-check.sh --form prints the record form (every field, with what it must hold)
|
||||
# tools/ci/canary-check.sh --self-test
|
||||
#
|
||||
# The record (tools/ci/canary/<sha>.json):
|
||||
# sha the release tip's commit (the file name's sha, full or 8+)
|
||||
# artefact {url or path, sha256}: what was installed, the published artefact itself, not a box's native build
|
||||
# box {host, isa_line}: a non-AVX-512 box; isa_line is the kit-isa clean line read on it (tools/ci/kit-isa-check.sh)
|
||||
# or the host's cpu flags line showing no avx512
|
||||
# datadir {path, empty_at_start: true, read_back}: the empty datadir before the install
|
||||
# sync {genesis_height: 0, tip_height, seconds, read_back}: genesis to the network's tip
|
||||
# mining {minutes >= 5, refusals: 0, accepted_blocks >= 1, read_back}
|
||||
# shard {claimed: true, proved: true, paid_or_queued: "paid" | "queued", read_back}
|
||||
# quit {bounded: true, seconds, read_back}: the process ends on its own quit inside the bound
|
||||
# lines_read_back a list of the eight line names, each with an evidence path on a box
|
||||
# verdict "PASS" (anything else is not a canary record for publishing)
|
||||
# recorded_at, recorded_by UTC stamp, the lane
|
||||
set -euo pipefail
|
||||
HERE="$(cd "$(dirname "$0")" && pwd -P)"; ME="$HERE/$(basename "$0")"; ROOT="${CANARY_ROOT:-$(cd "$HERE/../.." && pwd -P)}"
|
||||
DIR="${CANARY_DIR:-$ROOT/tools/ci/canary}"
|
||||
|
||||
form() {
|
||||
cat <<'EOF'
|
||||
{
|
||||
"sha": "<release tip commit, full>",
|
||||
"artefact": {"url": "https://dl.igneum.network/public/<file>", "sha256": "<64 hex>"},
|
||||
"box": {"host": "build-N or <name>", "isa_line": "kit-isa: clean ... | cpu flags: no avx512"},
|
||||
"datadir": {"path": "/srv/canary/<sha>/data", "empty_at_start": true, "read_back": "build-N:/srv/canary/<sha>/01-datadir.txt"},
|
||||
"sync": {"genesis_height": 0, "tip_height": 0, "seconds": 0, "read_back": "build-N:/srv/canary/<sha>/02-sync.txt"},
|
||||
"mining": {"minutes": 5, "refusals": 0, "accepted_blocks": 1, "read_back": "build-N:/srv/canary/<sha>/03-mining.txt"},
|
||||
"shard": {"claimed": true, "proved": true, "paid_or_queued": "paid", "read_back": "build-N:/srv/canary/<sha>/04-shard.txt"},
|
||||
"quit": {"bounded": true, "seconds": 0, "read_back": "build-N:/srv/canary/<sha>/05-quit.txt"},
|
||||
"lines_read_back": [
|
||||
{"line": "install", "evidence": "build-N:/srv/canary/<sha>/00-install.txt"},
|
||||
{"line": "box", "evidence": "build-N:/srv/canary/<sha>/00-box.txt"},
|
||||
{"line": "datadir", "evidence": "build-N:/srv/canary/<sha>/01-datadir.txt"},
|
||||
{"line": "sync", "evidence": "build-N:/srv/canary/<sha>/02-sync.txt"},
|
||||
{"line": "mining", "evidence": "build-N:/srv/canary/<sha>/03-mining.txt"},
|
||||
{"line": "shard", "evidence": "build-N:/srv/canary/<sha>/04-shard.txt"},
|
||||
{"line": "quit", "evidence": "build-N:/srv/canary/<sha>/05-quit.txt"},
|
||||
{"line": "version", "evidence": "build-N:/srv/canary/<sha>/00-version.txt"}
|
||||
],
|
||||
"verdict": "PASS",
|
||||
"recorded_at": "<UTC>",
|
||||
"recorded_by": "<lane>"
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
check() { # <sha> [kind] -> prints the verdict line; 0 pass, 1 fail
|
||||
local sha="$1" kind="${2:-}" f
|
||||
case "$sha" in *[!0-9a-fA-F]*|"") echo "canary: REFUSED: '$sha' is not a commit sha"; return 1 ;; esac
|
||||
[ "${#sha}" -ge 8 ] || { echo "canary: REFUSED: the sha must be 8 hex or more"; return 1; }
|
||||
f=""; for c in "$DIR/$sha.json" "$DIR"/"${sha:0:8}"*.json; do [ -f "$c" ] && { f="$c"; break; }; done
|
||||
[ -n "$f" ] || { echo "canary: REFUSED: no fresh-install canary record for ${sha:0:12} (rule 33: tools/ci/canary/<sha>.json, the eight lines read back; tools/ci/canary-check.sh --form)"; return 1; }
|
||||
python3 - "$f" "$sha" "$kind" <<'PY'
|
||||
import json, sys
|
||||
f, sha = sys.argv[1], sys.argv[2].lower()
|
||||
try: r = json.load(open(f))
|
||||
except Exception as e: print(f"canary: REFUSED: {f} is not JSON: {e}"); sys.exit(1)
|
||||
def red(m): print(f"canary: REFUSED: {sha[:12]}: {m} ({f})"); sys.exit(1)
|
||||
def need(obj, key, typ=None):
|
||||
if key not in obj: red(f"the record has no '{key}'")
|
||||
v = obj[key]
|
||||
if typ and not isinstance(v, typ): red(f"'{key}' is not {typ.__name__ if not isinstance(typ, tuple) else '/'.join(t.__name__ for t in typ)}")
|
||||
return v
|
||||
rs = str(need(r, 'sha')).lower()
|
||||
if not (rs.startswith(sha) or sha.startswith(rs)) or len(rs) < 8: red(f"the record's sha {rs[:12]} is not {sha[:12]}")
|
||||
def box_path(p): return isinstance(p, str) and (':/' in p or p.startswith('/srv/'))
|
||||
want_kind = sys.argv[3] if len(sys.argv) > 3 else ''
|
||||
blocks = r['artefacts'] if isinstance(r.get('artefacts'), list) else [r]
|
||||
if not blocks: red('the artefacts list is empty')
|
||||
if want_kind:
|
||||
blocks = [b for b in blocks if str(b.get('kind', b.get('artefact', {}).get('kind', ''))).lower() == want_kind.lower()]
|
||||
if not blocks: red(f"no artefact block of kind '{want_kind}' (the entry's own canary: fleet, windows, mac or hive, each from its own box)")
|
||||
lines_out = []
|
||||
for r_ in blocks:
|
||||
r = r_
|
||||
a = need(r, 'artefact', dict)
|
||||
if not (a.get('url') or a.get('path')): red("artefact names no url or path (the published artefact, not a native build)")
|
||||
if not (isinstance(a.get('sha256'), str) and len(a['sha256']) == 64): red("artefact.sha256 is not 64 hex")
|
||||
b = need(r, 'box', dict)
|
||||
if not b.get('host'): red("box.host is empty")
|
||||
isa = str(b.get('isa_line', '')).lower()
|
||||
if not isa or ('clean' not in isa and 'no avx512' not in isa and 'no avx-512' not in isa): red("box.isa_line does not read a non-AVX-512 box (the kit-isa clean line or a cpu flags line with no avx512)")
|
||||
d = need(r, 'datadir', dict)
|
||||
if d.get('empty_at_start') is not True: red("datadir.empty_at_start is not true")
|
||||
if not box_path(d.get('read_back')): red("datadir.read_back is not a box path")
|
||||
s = need(r, 'sync', dict)
|
||||
if s.get('genesis_height') != 0: red("sync.genesis_height is not 0 (the sync starts at genesis)")
|
||||
if not (isinstance(s.get('tip_height'), int) and s['tip_height'] > 0): red("sync.tip_height is not a positive height")
|
||||
if not box_path(s.get('read_back')): red("sync.read_back is not a box path")
|
||||
m = need(r, 'mining', dict)
|
||||
if not (isinstance(m.get('minutes'), (int, float)) and m['minutes'] >= 5): red("mining.minutes is under 5")
|
||||
if m.get('refusals') != 0: red(f"mining.refusals is {m.get('refusals')!r}, not 0")
|
||||
if not (isinstance(m.get('accepted_blocks'), int) and m['accepted_blocks'] >= 1): red("mining.accepted_blocks is under 1")
|
||||
if not box_path(m.get('read_back')): red("mining.read_back is not a box path")
|
||||
h = need(r, 'shard', dict)
|
||||
if h.get('claimed') is not True or h.get('proved') is not True: red("shard.claimed and shard.proved must both be true")
|
||||
if h.get('paid_or_queued') not in ('paid', 'queued'): red("shard.paid_or_queued must be 'paid' or 'queued'")
|
||||
if not box_path(h.get('read_back')): red("shard.read_back is not a box path")
|
||||
q = need(r, 'quit', dict)
|
||||
if q.get('bounded') is not True: red("quit.bounded is not true")
|
||||
if not (isinstance(q.get('seconds'), (int, float)) and q['seconds'] >= 0): red("quit.seconds is not a number")
|
||||
if not box_path(q.get('read_back')): red("quit.read_back is not a box path")
|
||||
lines = need(r, 'lines_read_back', list)
|
||||
names = {str(x.get('line')) for x in lines if isinstance(x, dict)}
|
||||
want = {'install', 'box', 'datadir', 'sync', 'mining', 'shard', 'quit', 'version'}
|
||||
missing = sorted(want - names)
|
||||
if missing: red(f"lines_read_back lacks {', '.join(missing)}")
|
||||
for x in lines:
|
||||
if isinstance(x, dict) and x.get('line') in want and not box_path(x.get('evidence')): red(f"line {x.get('line')} has no box evidence path")
|
||||
top = json.load(open(f))
|
||||
v = r.get('verdict', top.get('verdict'))
|
||||
if v != 'PASS': red(f"verdict is {v!r}, not PASS")
|
||||
if not (r.get('recorded_at') or top.get('recorded_at')) or not (r.get('recorded_by') or top.get('recorded_by')): red("recorded_at or recorded_by is empty")
|
||||
lines_out.append(f"{r.get('kind', 'artefact')} on {b['host']} ({str(a.get('sha256'))[:12]}…): genesis to {s['tip_height']} in {s.get('seconds')} s, {m['minutes']} min mining, 0 refusals, {m['accepted_blocks']} accepted, one shard {h['paid_or_queued']}, quit in {q['seconds']} s; eight lines read back")
|
||||
print(f"canary: PASS: {sha[:12]} fresh-install canary: " + '; '.join(lines_out) + f"; recorded {blocks[0].get('recorded_at') or json.load(open(f)).get('recorded_at')} by {blocks[0].get('recorded_by') or json.load(open(f)).get('recorded_by')}")
|
||||
PY
|
||||
}
|
||||
|
||||
if [ "${1:-}" = --form ]; then form; exit 0; fi
|
||||
if [ "${1:-}" = --self-test ]; then
|
||||
d=$(mktemp -d); trap 'rm -rf "$d"' EXIT; fails=0; export CANARY_DIR="$d"
|
||||
SHA=0123456789abcdef0123456789abcdef01234567
|
||||
form | python3 -c "
|
||||
import json,sys; r=json.load(sys.stdin); r['sha']='$SHA'; r['artefact']['sha256']='ab'*32; r['box']={'host':'build-4','isa_line':'kit-isa: clean: no AVX-512 encoding in 3 binaries'}
|
||||
r['sync'].update(tip_height=3847, seconds=412); r['mining'].update(minutes=5, refusals=0, accepted_blocks=2); r['quit']['seconds']=3; r['recorded_at']='2026-10-08T21:30:00Z'; r['recorded_by']='shipper'
|
||||
json.dump(r, open('$d/$SHA.json','w'))"
|
||||
bash "$ME" "$SHA" >/dev/null 2>&1 || { echo "self-test failed: a complete PASS record was refused: $(bash "$ME" "$SHA" 2>&1)"; fails=1; }
|
||||
bash "$ME" "${SHA:0:12}" >/dev/null 2>&1 || { echo "self-test failed: the record was not found by its short sha"; fails=1; }
|
||||
out=$(bash "$ME" ffffffffffff 2>&1) && { echo "self-test failed: a sha with no record passed"; fails=1; }; case "$out" in *"no fresh-install canary record"*) ;; *) echo "self-test failed: the missing record was not named: $out"; fails=1 ;; esac
|
||||
mut() { python3 -c "
|
||||
import json,sys; p='$d/$SHA.json'; r=json.load(open(p)); exec(sys.argv[1]); json.dump(r, open(p,'w'))" "$1"; }
|
||||
for case in "r['mining']['refusals']=1|refusals" "r['mining']['minutes']=4|under 5" "r['mining']['accepted_blocks']=0|accepted_blocks" "r['shard']['proved']=False|shard.claimed and shard.proved" "r['shard']['paid_or_queued']='lost'|paid_or_queued" "r['quit']['bounded']=False|quit.bounded" "r['datadir']['empty_at_start']=False|empty_at_start" "r['sync']['genesis_height']=100|genesis" "r['box']['isa_line']='avx512f present'|non-AVX-512" "r['lines_read_back']=r['lines_read_back'][:7]|lacks version" "r['lines_read_back'][2]['evidence']='notes.txt'|no box evidence" "r['verdict']='FAIL'|not PASS" "r['artefact']={'url':'x','sha256':'short'}|sha256"; do
|
||||
cp "$d/$SHA.json" "$d/keep.json"; mut "${case%%|*}"
|
||||
out=$(bash "$ME" "$SHA" 2>&1) && { echo "self-test failed: a record with ${case%%|*} passed"; fails=1; }
|
||||
case "$out" in *"${case##*|}"*) ;; *) echo "self-test failed: the failing line was not named for ${case%%|*}: $out"; fails=1 ;; esac
|
||||
cp "$d/keep.json" "$d/$SHA.json"
|
||||
done
|
||||
# the multi-artefact form: one file per sha, an artefacts list (fleet, windows, mac), each block its own box and eight lines; --artefact picks one
|
||||
python3 -c "
|
||||
import json; p='$d/$SHA.json'; r=json.load(open(p)); blk={k:r[k] for k in ('artefact','box','datadir','sync','mining','shard','quit','lines_read_back')}
|
||||
import copy; arts=[]
|
||||
for kind,host in (('fleet','lp-4090-11'),('windows','pc-2'),('mac','mini')):
|
||||
b=copy.deepcopy(blk); b['kind']=kind; b['box']['host']=host; b['artefact']['url']='https://dl.igneum.network/public/'+kind; arts.append(b)
|
||||
m={'sha':r['sha'],'artefacts':arts,'verdict':'PASS','recorded_at':r['recorded_at'],'recorded_by':'shipper'}; json.dump(m, open(p,'w'))"
|
||||
bash "$ME" "$SHA" >/dev/null 2>&1 || { echo "self-test failed: a three-artefact record was refused: $(bash "$ME" "$SHA" 2>&1)"; fails=1; }
|
||||
bash "$ME" "$SHA" --artefact mac >/dev/null 2>&1 || { echo "self-test failed: the mac block of a three-artefact record was refused"; fails=1; }
|
||||
out=$(bash "$ME" "$SHA" --artefact hive 2>&1) && { echo "self-test failed: a kind with no block passed"; fails=1; }; case "$out" in *"no artefact block of kind 'hive'"*) ;; *) echo "self-test failed: the missing kind was not named: $out"; fails=1 ;; esac
|
||||
python3 -c "import json; p='$d/$SHA.json'; r=json.load(open(p)); r['artefacts'][1]['mining']['refusals']=2; json.dump(r, open(p,'w'))"
|
||||
out=$(bash "$ME" "$SHA" --artefact windows 2>&1) && { echo "self-test failed: a failing windows block passed under --artefact windows"; fails=1; }
|
||||
bash "$ME" "$SHA" --artefact mac >/dev/null 2>&1 || { echo "self-test failed: the mac block was refused because the windows block fails (each entry publishes on its own canary)"; fails=1; }
|
||||
out=$(bash "$ME" "$SHA" 2>&1) && { echo "self-test failed: the whole record passed with one failing block"; fails=1; }
|
||||
echo "not json" > "$d/$SHA.json"; bash "$ME" "$SHA" >/dev/null 2>&1 && { echo "self-test failed: a non-JSON record passed"; fails=1; }
|
||||
out=$(bash "$ME" "not-a-sha" 2>&1) && { echo "self-test failed: a non-sha argument passed"; fails=1; }
|
||||
[ "$fails" = 0 ] && echo "self-test passed: a complete fresh-install canary record is PASS and found by its short sha; no record, a refusal, under five minutes, no accepted block, an unproved or lost shard, an unbounded quit, a non-empty datadir, a sync not from genesis, an AVX-512 box, a missing read-back line, a non-box evidence path, a non-PASS verdict or a bad artefact hash is refused and named; a one-file-per-sha record with an artefacts list (fleet, windows, mac, hive) passes whole or per --artefact kind, and a failing block fails its own kind and the whole, never another kind"
|
||||
exit $fails
|
||||
fi
|
||||
KIND=""; SHA_ARG=""
|
||||
while [ $# -gt 0 ]; do case "$1" in --artefact) KIND="$2"; shift 2 ;; *) SHA_ARG="$1"; shift ;; esac; done
|
||||
[ -n "$SHA_ARG" ] || { echo "usage: $0 <sha> [--artefact <kind>] | --form | --self-test" >&2; exit 2; }
|
||||
check "$SHA_ARG" "$KIND"
|
||||
1
tools/ci/canary/README.md
Normal file
1
tools/ci/canary/README.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
# Rule 33 fresh-install canary records, one per release tip sha (tools/ci/canary-check.sh --form; the publish path refuses an entry without a PASS record here)
|
||||
|
|
@ -80,7 +80,9 @@ the registry's evidence rules: a PASS names evidence that exists, a touched evid
|
|||
the kit ISA check: a distribution kit's binaries carry no AVX-512 encoding (the x86-64-v3 baseline; self-test with a fake objdump)
|
||||
F02 (Review B): the proof-rule test bypass cannot reach a release build: an env read with no cfg guard or under a default feature is red; a release binary carrying the bypass string is red (self-test)
|
||||
the test map merges structurally at a landing: master's cells plus the branch's, minus what the branch removed and master left (self-test)
|
||||
rule 33: a release entry publishes only with a PASS fresh-install canary record for its sha (the record check and the publish guard, self-tests)
|
||||
the REV suite is generated from Review B's findings and dispatch and matches them (self-test, then the tree)
|
||||
F03 (Review B): every component's own pin equals packaging/release-manifest.json where a release branch carries one (self-test, then the tree)
|
||||
the public ledger (docs/ledger-public.md) is what docs/fud-ledger.md generates: one row per item, no commit ids, times or team names (self-test first)
|
||||
the ledger page reads both entry heading forms (M1 and AP-F8-1) so no in-house pass row is dropped from /ledger (known-failed first)
|
||||
every workflow job carries timeout-minutes (site 15, changes 10, pow 60, sims 45; the hung-job class of 7 October 2026)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export function suite(tr) {
|
|||
return { code: 'INT', title: 'INT: the master edition\'s integration gates (R1, the full-system review)', source: 'docs/plans/igneum-2.0-master/traceability.json integration_gates', gate: 'Integration gates closed', owner: 'the owner lanes per the coordinator\'s crosswalk', fixtures: ['F0', 'F5'], summary: `${tests.length} integration gates; each reads NOT RUN until its owner lane records a run`, tests };
|
||||
}
|
||||
export function merge(reg, s) { const old = (reg.suites || []).find((x) => x.code === s.code); const live = new Map((old?.tests || []).map((t) => [t.id, t]));
|
||||
for (const t of s.tests) { const o = live.get(t.id); if (o) for (const k of ['run_status', 'run_id', 'evidence_path', 'updated', 'evidence_record', 'in_progress_since', 'deferral_note', 'approvals']) if (k in o) t[k] = o[k]; }
|
||||
for (const t of s.tests) { const o = live.get(t.id); if (o) for (const k of ['run_status', 'run_id', 'evidence_path', 'updated', 'evidence_record', 'evidence_records', 'blocked_on', 'method_recorded', 'in_progress_since', 'deferral_note', 'approvals']) if (k in o) t[k] = o[k]; }
|
||||
if (old?.notes) s.notes = old.notes; reg.suites = [...(reg.suites || []).filter((x) => x.code !== s.code), s]; return reg; }
|
||||
const canon = (o) => JSON.stringify(o, (k, v) => (v && typeof v === 'object' && !Array.isArray(v)) ? Object.fromEntries(Object.keys(v).sort().map((x) => [x, v[x]])) : v);
|
||||
function mapReasons(map, s) { const mapped = new Set(Object.values(map.cells || {}).flatMap((c) => c.cases || [])); map.not_run = map.not_run || {}; let n = 0;
|
||||
|
|
@ -33,8 +33,8 @@ if (args.includes('--self-test')) {
|
|||
let fails = 0; const tr = { integration_gates: [{ id: 'INT-01', requirement: 'r one', status: 'PROPOSED / NOT RUN', source: 'R1' }, { id: 'INT-05', requirement: 'r five', status: 'x', source: 'R1' }, { id: 'INT-07', requirement: 'r seven', status: 'x', source: 'R1' }] };
|
||||
const s = suite(tr); if (!(s.tests[2].definition && /V6-12/.test(s.tests[2].definition) && s.tests[2].accept === 'r seven')) { console.log('self-test failed: INT-07 does not carry V6-12 as a definition beside its verbatim requirement'); fails = 1; }
|
||||
if (!(s.tests.length === 3 && s.tests[0].id === 'INT-01' && s.tests[0].accept === 'r one' && /proving/.test(s.tests[0].owner_lane) && /node/.test(s.tests[1].owner_lane) && s.tests[0].run_status === 'NOT RUN' && s.tests[0].source[0] === 'R1')) { console.log('self-test failed: the INT cases are not shaped from the gates with the crosswalk owners'); fails = 1; }
|
||||
const reg = { suites: [{ code: 'INT', notes: [{ text: 'n' }], tests: [{ id: 'INT-01', run_status: 'NOT RUN', in_progress_since: 't', run_id: 'r' }] }] }; const m = merge(reg, s); const i = m.suites.find((x) => x.code === 'INT');
|
||||
if (!(i.tests[0].in_progress_since === 't' && i.tests[1].run_status === 'NOT RUN' && i.notes?.length === 1)) { console.log('self-test failed: regenerating lost live fields or notes'); fails = 1; }
|
||||
const reg = { suites: [{ code: 'INT', notes: [{ text: 'n' }], tests: [{ id: 'INT-01', run_status: 'NOT RUN', in_progress_since: 't', evidence_records: { 'c:x': { decision: 'PASS' } }, run_id: 'r' }] }] }; const m = merge(reg, s); const i = m.suites.find((x) => x.code === 'INT');
|
||||
if (!(i.tests[0].in_progress_since === 't' && i.tests[0].evidence_records?.['c:x']?.decision === 'PASS' && i.tests[1].run_status === 'NOT RUN' && i.notes?.length === 1)) { console.log('self-test failed: regenerating lost live fields or notes'); fails = 1; }
|
||||
const map = { cells: { c: { cases: ['INT-01'] } }, not_run: {} }; if (!(mapReasons(map, s) === 2 && !map.not_run['INT-01'] && /node lane/.test(map.not_run['INT-05']) && /CI steward/.test(map.not_run['INT-07']))) { console.log('self-test failed: the map reasons'); fails = 1; }
|
||||
if (!fails) console.log('self-test passed: one INT case per integration gate, the requirement verbatim, source R1, NOT RUN, the owner from the crosswalk; regenerating keeps live fields and notes; unmapped gates get a NOT RUN reason naming the owner'); process.exit(fails);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -283,7 +283,7 @@ success 4 u push run
|
|||
node tools/ci/test-record.mjs --record tools/ci/batches/r-branch.json >/dev/null && git add -A && git -c user.name=t -c user.email=t@t commit -q -m "batch r-branch"
|
||||
git checkout -q master; printf '{"run_id":"r-master","manifest_sha":"m","method":"native","cells":[{"cell":"c2","status":"NOT RUN","evidence":"tools/ci/test-map.json"}]}\n' > /tmp/r-master.json
|
||||
node tools/ci/test-record.mjs --record /tmp/r-master.json >/dev/null && git add -A && git -c user.name=t -c user.email=t@t commit -q -m "master row" ) >/dev/null 2>&1
|
||||
out=$( cd "$rb" && TIP=$(git rev-parse master) && SHA=$(git rev-parse branch) && BASE=$(git merge-base "$TIP" "$SHA") && BATCHES=$(git diff --name-only --diff-filter=A "$BASE" "$SHA" -- tools/ci/batches/ | grep -E '\.json$') && REGISTRY_PATH=docs/plans/igneum-2.0-test-registry.json && AUTHOR=(-c user.name=t -c user.email=t@t) && export TEST_RECORD_ROOT="$rb" && merge_with_batches "$TIP" "$SHA" "merge with replay" 2>&1 && python3 -c "
|
||||
out=$( cd "$rb" && TIP=$(git rev-parse master) && SHA=$(git rev-parse branch) && BASE=$(git merge-base "$TIP" "$SHA") && BATCHES=$(git diff --name-only --diff-filter=AM "$BASE" "$SHA" -- tools/ci/batches/ | grep -E '\.json$') && REGISTRY_PATH=docs/plans/igneum-2.0-test-registry.json && AUTHOR=(-c user.name=t -c user.email=t@t) && export TEST_RECORD_ROOT="$rb" && merge_with_batches "$TIP" "$SHA" "merge with replay" 2>&1 && python3 -c "
|
||||
import json; d=json.load(open('docs/plans/igneum-2.0-test-registry.json')); t={c['id']:c.get('run_id') for s in d['suites'] for c in s['tests']}; print('rows', t)" )
|
||||
case "$out" in *"'X-1': 'r-branch'"*"'X-2': 'r-master'"*|*"'X-2': 'r-master'"*"'X-1': 'r-branch'"*) ;; *) echo "self-test failed: the batch replay did not land both the branch's row and master's row: $out"; fails=1 ;; esac
|
||||
# the page race: the branch adds cell c3 to the map (page regenerated), master adds c4 (page regenerated); the merge regenerates the page with both
|
||||
|
|
@ -295,8 +295,17 @@ import json; d=json.load(open('docs/plans/igneum-2.0-test-registry.json')); t={c
|
|||
# both at once: the branch records a batch (its registry copy conflicts with master's) and adds a map cell; the page must regenerate
|
||||
# after the registry is rebuilt, never from a copy with conflict markers (8 October 2026, 20:24 UK: the REV landing lost to this)
|
||||
( cd "$rb" && git checkout -q -b both pbase && printf '{"run_id":"r-both","manifest_sha":"m","method":"native","cells":[{"cell":"c1","status":"NOT RUN","evidence":"tools/ci/test-map.json"}]}\n' > tools/ci/batches/r-both.json && node tools/ci/test-record.mjs --record tools/ci/batches/r-both.json >/dev/null && python3 -c "import json; m=json.load(open('tools/ci/test-map.json')); m['cells']['c5']={'command':'v','box_class':'b','fixtures':[],'cases':['X-1']}; json.dump(m,open('tools/ci/test-map.json','w'))" && node tools/ci/test-map-doc.mjs >/dev/null 2>&1; git add -A; git -c user.name=t -c user.email=t@t commit -q -m both ) >/dev/null 2>&1
|
||||
out=$( cd "$rb" && TIP=$(git rev-parse master) && SHA=$(git rev-parse both) && BASE=$(git merge-base "$TIP" "$SHA") && BATCHES=$(git diff --name-only --diff-filter=A "$BASE" "$SHA" -- tools/ci/batches/ | grep -E '\.json$') && MAP_CHANGED=1 && MAP_PATH=tools/ci/test-map.json && PAGE_PATH=docs/plans/igneum-2.0-test-harness-map.md && REGISTRY_PATH=docs/plans/igneum-2.0-test-registry.json && AUTHOR=(-c user.name=t -c user.email=t@t) && export TEST_RECORD_ROOT="$rb" && merge_with_batches "$TIP" "$SHA" "merge both" 2>&1 && grep -c -E '^### c[45]$' docs/plans/igneum-2.0-test-harness-map.md && python3 -c "import json; d=json.load(open('docs/plans/igneum-2.0-test-registry.json')); print('ok' if any(c.get('run_id')=='r-both' for s in d['suites'] for c in s['tests']) else 'no-batch')" )
|
||||
out=$( cd "$rb" && TIP=$(git rev-parse master) && SHA=$(git rev-parse both) && BASE=$(git merge-base "$TIP" "$SHA") && BATCHES=$(git diff --name-only --diff-filter=AM "$BASE" "$SHA" -- tools/ci/batches/ | grep -E '\.json$') && MAP_CHANGED=1 && MAP_PATH=tools/ci/test-map.json && PAGE_PATH=docs/plans/igneum-2.0-test-harness-map.md && REGISTRY_PATH=docs/plans/igneum-2.0-test-registry.json && AUTHOR=(-c user.name=t -c user.email=t@t) && export TEST_RECORD_ROOT="$rb" && merge_with_batches "$TIP" "$SHA" "merge both" 2>&1 && grep -c -E '^### c[45]$' docs/plans/igneum-2.0-test-harness-map.md && python3 -c "import json; d=json.load(open('docs/plans/igneum-2.0-test-registry.json')); print('ok' if any(c.get('run_id')=='r-both' for s in d['suites'] for c in s['tests']) else 'no-batch')" )
|
||||
case "$out" in *replayed*2*ok*) ;; *) echo "self-test failed: a landing with both a batch and a map change did not land both (the page before the registry rebuild?): $out"; fails=1 ;; esac
|
||||
# a MODIFIED batch replays too (8 October 2026, 21:4x UK: a re-record of the kills batch on a branch never reached master's rows because
|
||||
# the replay read added batches only): master carries r-mod.json, the branch re-records it as FAIL, the merge must move X-2 to FAIL
|
||||
( cd "$rb" && git checkout -q master && git tag premod && printf '{"run_id":"r-mod","manifest_sha":"m","method":"native","cells":[{"cell":"c2","status":"NOT RUN","evidence":"tools/ci/test-map.json"}]}\n' > tools/ci/batches/r-mod.json && node tools/ci/test-record.mjs --record tools/ci/batches/r-mod.json >/dev/null && git add -A && git -c user.name=t -c user.email=t@t commit -q -m r-mod && git tag mbase
|
||||
git checkout -q -b mod mbase && printf '{"run_id":"r-mod","manifest_sha":"m","method":"native","cells":[{"cell":"c2","status":"FAIL","evidence":"tools/ci/test-map.json"}]}\n' > tools/ci/batches/r-mod.json && node tools/ci/test-record.mjs --record tools/ci/batches/r-mod.json >/dev/null && git add -A && git -c user.name=t -c user.email=t@t commit -q -m mod
|
||||
git checkout -q master && printf '{"run_id":"r-m2","manifest_sha":"m","method":"native","cells":[{"cell":"c1","status":"NOT RUN","evidence":"tools/ci/test-map.json"}]}\n' > tools/ci/batches/r-m2.json && node tools/ci/test-record.mjs --record tools/ci/batches/r-m2.json >/dev/null && git add -A && git -c user.name=t -c user.email=t@t commit -q -m m2 ) >/dev/null 2>&1 || { echo "self-test failed: the modified-batch fixture did not build"; fails=1; }
|
||||
out=$( cd "$rb" && TIP=$(git rev-parse master) && SHA=$(git rev-parse mod) && BASE=$(git merge-base "$TIP" "$SHA") && BATCHES=$(git diff --name-only --diff-filter=AM "$BASE" "$SHA" -- tools/ci/batches/ | grep -E '\.json$') && MAP_CHANGED=0 && REGISTRY_PATH=docs/plans/igneum-2.0-test-registry.json && AUTHOR=(-c user.name=t -c user.email=t@t) && export TEST_RECORD_ROOT="$rb" && merge_with_batches "$TIP" "$SHA" "merge mod" 2>&1 && python3 -c "
|
||||
import json; d=json.load(open('docs/plans/igneum-2.0-test-registry.json')); t={c['id']:c.get('run_status') for s in d['suites'] for c in s['tests']}; print('status', t)" )
|
||||
case "$out" in *"'X-2': 'FAIL'"*) ;; *) echo "self-test failed: a batch modified on the branch did not replay onto master's registry: $out"; fails=1 ;; esac
|
||||
( cd "$rb" && git checkout -q master && git reset -q --hard premod && git checkout -q both ) >/dev/null 2>&1 # the fixture back to where the later cases expect it
|
||||
push_race "To x
|
||||
! [remote rejected] HEAD -> master (failed to update ref)
|
||||
remote: error: cannot lock ref 'refs/heads/master': is at a but expected b" || { echo "self-test failed: a lost compare-and-swap was not read as a race"; fails=1; }
|
||||
|
|
@ -313,7 +322,7 @@ error: failed to push some refs" && { echo "self-test failed: a red check was re
|
|||
rm -rf "$ld/master-landing"; unset IGNEUM_LOCK_SSH IGNEUM_LOCK_DIR IGNEUM_LOCK_CAP IGNEUM_LOCK_STALE IGNEUM_LOCK_POLL
|
||||
# the branch-side merge of master under the lock: master moved (c4 and a row), the branch (c3 and a batch) takes it with the transforms
|
||||
( cd "$rb" && git checkout -q both ) >/dev/null 2>&1
|
||||
out=$( cd "$rb" && git checkout -q both && TIP=$(git rev-parse master) && SHA=$(git rev-parse both) && BASE0=$(git merge-base "$TIP" "$SHA") && BATCHES=$(git diff --name-only --diff-filter=A "$BASE0" "$SHA" -- tools/ci/batches/ | grep -E '\.json$') && MAP_CHANGED=1 && MAP_PATH=tools/ci/test-map.json && PAGE_PATH=docs/plans/igneum-2.0-test-harness-map.md && REGISTRY_PATH=docs/plans/igneum-2.0-test-registry.json && AUTHOR=(-c user.name=t -c user.email=t@t) && export TEST_RECORD_ROOT="$rb" && merge_with_batches "$TIP" "$SHA" "merge master into both" branch 2>&1 && git merge-base --is-ancestor master HEAD && echo ancestor && grep -c -E '^### c[45]$' docs/plans/igneum-2.0-test-harness-map.md && python3 -c "import json; d=json.load(open('docs/plans/igneum-2.0-test-registry.json')); print('ok' if any(c.get('run_id')=='r-both' for s in d['suites'] for c in s['tests']) and any(c.get('run_id')=='r-master' for s in d['suites'] for c in s['tests']) else 'rows-lost')" )
|
||||
out=$( cd "$rb" && git checkout -q both && TIP=$(git rev-parse master) && SHA=$(git rev-parse both) && BASE0=$(git merge-base "$TIP" "$SHA") && BATCHES=$(git diff --name-only --diff-filter=AM "$BASE0" "$SHA" -- tools/ci/batches/ | grep -E '\.json$') && MAP_CHANGED=1 && MAP_PATH=tools/ci/test-map.json && PAGE_PATH=docs/plans/igneum-2.0-test-harness-map.md && REGISTRY_PATH=docs/plans/igneum-2.0-test-registry.json && AUTHOR=(-c user.name=t -c user.email=t@t) && export TEST_RECORD_ROOT="$rb" && merge_with_batches "$TIP" "$SHA" "merge master into both" branch 2>&1 && git merge-base --is-ancestor master HEAD && echo ancestor && grep -c -E '^### c[45]$' docs/plans/igneum-2.0-test-harness-map.md && python3 -c "import json; d=json.load(open('docs/plans/igneum-2.0-test-registry.json')); print('ok' if any(c.get('run_id')=='r-both' for s in d['suites'] for c in s['tests']) and any(c.get('run_id')=='r-master' for s in d['suites'] for c in s['tests']) else 'rows-lost')" )
|
||||
case "$out" in *ancestor*2*ok*) ;; *) echo "self-test failed: the branch-side merge of master under the lock did not carry master's rows and cells plus the branch's: $out"; fails=1 ;; esac
|
||||
[ "$fails" = 0 ] && echo "self-test passed: a GitHub remote is refused with exit 2 while the suspension marker stands and a mirror remote is not; the CI rule binds a GitHub remote only; a landed master is fast-forwarded to every mirror and a refused mirror push is a line, not a failure; a green branch run merges; a red one is refused; an unknown read is waited through and refused only at the deadline; a queued run is waited for with the clock; an unrun branch is pushed once and then waited for; a red master refuses every merge but the declared fix; a registry landing's batches replay onto master's copy at the merge; a map change regenerates the harness page at the merge; a push that lost the ref race retries without re-running the hook; the master-landing lock holds for the whole landing and master merges into the branch under it"
|
||||
exit $fails
|
||||
|
|
@ -349,7 +358,7 @@ git fetch -q "$REMOTE" master
|
|||
if ! git merge-base --is-ancestor "$REMOTE/master" "$SHA"; then
|
||||
echo "merge-to-master: master moved since the branch point ($(git rev-parse --short "$REMOTE/master")); merging it into $BRANCH under the lock"
|
||||
PRE_SHA="$SHA"; BASE0=$(git merge-base "$SHA" "$REMOTE/master")
|
||||
BATCHES=$(git diff --name-only --diff-filter=A "$BASE0" "$SHA" -- tools/ci/batches/ | grep -E '\.json$' || true); NOTES=$(git diff --name-only --diff-filter=A "$BASE0" "$SHA" -- tools/ci/notes/ | grep -E '\.json$' || true)
|
||||
BATCHES=$(git diff --name-only --diff-filter=AM "$BASE0" "$SHA" -- tools/ci/batches/ | grep -E '\.json$' || true); NOTES=$(git diff --name-only --diff-filter=AM "$BASE0" "$SHA" -- tools/ci/notes/ | grep -E '\.json$' || true)
|
||||
MAP_CHANGED=0; git diff --quiet "$BASE0" "$SHA" -- "${MAP_PATH:-tools/ci/test-map.json}" 2>/dev/null || MAP_CHANGED=1
|
||||
REGISTRY_PATH="${REGISTRY_PATH:-docs/plans/igneum-2.0-test-registry.json}"; MAP_PATH="${MAP_PATH:-tools/ci/test-map.json}"; PAGE_PATH="${PAGE_PATH:-docs/plans/igneum-2.0-test-harness-map.md}"
|
||||
[ -n "$NOTES" ] || [ "$MAP_CHANGED" = 1 ] || git diff --quiet "$BASE0" "$SHA" -- "$REGISTRY_PATH" 2>/dev/null || BATCHES="${BATCHES:-.}"
|
||||
|
|
@ -366,8 +375,8 @@ bash tools/ci/rule24-crate-gate.sh "$BASE" "$SHA" || { echo "merge-to-master: RE
|
|||
# every batch the branch added onto master's copy of the registry (tools/ci/test-record.mjs --record, idempotent), so the branch's
|
||||
# copy is never what lands and rule 26 does not bind the registry path for such a branch (the evidence rules run on the merged result)
|
||||
REGISTRY_PATH="${REGISTRY_PATH:-docs/plans/igneum-2.0-test-registry.json}"
|
||||
BATCHES=$(git diff --name-only --diff-filter=A "$BASE" "$SHA" -- tools/ci/batches/ | grep -E '\.json$' || true)
|
||||
NOTES=$(git diff --name-only --diff-filter=A "$BASE" "$SHA" -- tools/ci/notes/ | grep -E '\.json$' || true) # {suite, text}, replayed through test-record.mjs --note-file
|
||||
BATCHES=$(git diff --name-only --diff-filter=AM "$BASE" "$SHA" -- tools/ci/batches/ | grep -E '\.json$' || true)
|
||||
NOTES=$(git diff --name-only --diff-filter=AM "$BASE" "$SHA" -- tools/ci/notes/ | grep -E '\.json$' || true) # {suite, text}, replayed through test-record.mjs --note-file
|
||||
REVGEN=0; git diff --quiet "$BASE" "$SHA" -- tools/ci/review-suite.mjs tools/ci/int-suite.mjs docs/analysis/review-2026-10-08-b/findings.json docs/analysis/review-2026-10-08-b/dispatch.md docs/plans/igneum-2.0-master/traceability.json 2>/dev/null || REVGEN=1 # the REV and INT suites regenerate on the merged tree
|
||||
[ -n "$NOTES" ] || [ "$REVGEN" = 1 ] && BATCHES="${BATCHES:-.}" # the registry is rebuilt from master's copy whenever any transform rides
|
||||
RULE26_SKIP_PATHS=""; [ -n "$BATCHES" ] && RULE26_SKIP_PATHS="$REGISTRY_PATH"
|
||||
|
|
|
|||
|
|
@ -11,7 +11,13 @@ the worker never answered.
|
|||
tools/ci/p01-vectors.py --worker <bin> [--worker-arg=X ...] --pack <dir> --reference <file> --count 1000000
|
||||
(a worker argument that itself starts with "--" must be given as --worker-arg=--serve; argparse reads "--worker-arg --serve" as two options)
|
||||
[--start 0] [--job-nonces 1048576] [--prehash <64 hex>] [--manifest <sha>] --out <evidence.json>
|
||||
tools/ci/p01-vectors.py --worker <bin> [--worker-arg X ...] --job-context <job-context.json> --phase 1|2|3 --out <evidence.json>
|
||||
tools/ci/p01-vectors.py --self-test
|
||||
The job context (the same-work test, docs/plans/igneum-2.0-same-work-test.md) names two packs and two references (day D and
|
||||
day D+1), the prehash, the range width (range_log2, 20) and the boundary nonce; phase N runs nonces [(N-1)*2^w, N*2^w): phase 1
|
||||
under day D, phase 3 under day D+1, phase 2 under day D up to the boundary nonce and day D+1 from it, no job line crossing the
|
||||
boundary, and the evidence carries the boundary block (the nonce, the program id and day bytes on each side, the two hashes
|
||||
either side) that the readers are compared on.
|
||||
The job line is pool.rs's: `job <seq> <prehash hex> <share_target64 hex16> <start nonce> <nonces> <epoch seed bytes hex> <day bytes hex> class=<c> era=<era hex>`.
|
||||
"""
|
||||
import argparse, json, os, subprocess, sys, tempfile, time
|
||||
|
|
@ -31,9 +37,36 @@ def pack_fields(pack):
|
|||
cls = j.get('program_class', '?'); era = j.get('era_seed_bytes', '')
|
||||
return j.get('seed_bytes', ''), j['dataset']['day_bytes'], cls, era, j.get('program_id', '?'), j.get('generator', '?')
|
||||
|
||||
def segments_for(a):
|
||||
"""[(start, end, pack dir, reference path)] with no job crossing a segment edge; one segment for the plain form."""
|
||||
if not a.job_context: return [(a.start, a.start + a.count, a.pack, a.reference)], None
|
||||
jc = json.load(open(a.job_context)); w = int(jc.get('range_log2', 20)); n = int(a.phase)
|
||||
if n not in (1, 2, 3): raise SystemExit('p01-vectors: --phase must be 1, 2 or 3')
|
||||
base = os.path.dirname(os.path.abspath(a.job_context))
|
||||
pth = lambda x: x if os.path.isabs(x) else os.path.join(base, x)
|
||||
packs, refs = jc['packs'], jc['references']
|
||||
s0, e0 = (n - 1) << w, n << w
|
||||
if n == 1: segs = [(s0, e0, pth(packs['D']), pth(refs['D']))]
|
||||
elif n == 3: segs = [(s0, e0, pth(packs['D1']), pth(refs['D1']))]
|
||||
else:
|
||||
b = int(jc['boundary_nonce'])
|
||||
if not (s0 < b < e0): raise SystemExit(f'p01-vectors: the boundary nonce {b} is not inside phase 2 [{s0}, {e0})')
|
||||
segs = [(s0, b, pth(packs['D']), pth(refs['D'])), (b, e0, pth(packs['D1']), pth(refs['D1']))]
|
||||
a.start, a.count = s0, e0 - s0
|
||||
if jc.get('prehash'): a.prehash = jc['prehash']
|
||||
if jc.get('manifest') and not a.manifest: a.manifest = jc['manifest']
|
||||
return segs, jc
|
||||
|
||||
def run(a):
|
||||
ref = read_reference(a.reference)
|
||||
seed_bytes, day_bytes, cls, era, program_id, generator = pack_fields(a.pack)
|
||||
segs, jc = segments_for(a)
|
||||
ref = {}; seg_fields = []
|
||||
for (ss, se, pack, rpath) in segs:
|
||||
r = read_reference(rpath); ref.update({k: v for k, v in r.items() if ss <= k < se}); seg_fields.append((ss, se, pack_fields(pack)))
|
||||
seed_bytes, day_bytes, cls, era, program_id, generator = seg_fields[0][2]
|
||||
def fields_at(n):
|
||||
for (ss, se, f) in seg_fields:
|
||||
if ss <= n < se: return ss, se, f
|
||||
return None
|
||||
p = subprocess.Popen([a.worker] + a.worker_arg, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1)
|
||||
ready = None; t0 = time.time()
|
||||
for line in p.stdout:
|
||||
|
|
@ -44,8 +77,9 @@ def run(a):
|
|||
def feed():
|
||||
nonlocal seq, start
|
||||
while start < end and len(pending) < 4:
|
||||
n = min(a.job_nonces, end - start); seq += 1
|
||||
p.stdin.write(f"job {seq} {a.prehash} {'f'*16} {start} {n} {seed_bytes} {day_bytes} class={cls} era={era}\n"); p.stdin.flush()
|
||||
ss, se, (sb, db, c, er, _pid, _gen) = fields_at(start)
|
||||
n = min(a.job_nonces, end - start, se - start); seq += 1 # a job never crosses a segment edge (the day boundary)
|
||||
p.stdin.write(f"job {seq} {a.prehash} {'f'*16} {start} {n} {sb} {db} class={c} era={er}\n"); p.stdin.flush()
|
||||
pending.add(seq); start += n
|
||||
feed()
|
||||
for line in p.stdout:
|
||||
|
|
@ -72,7 +106,15 @@ def run(a):
|
|||
'worker': a.worker, 'worker_args': a.worker_arg, 'device_line': ready, 'manifest_sha': a.manifest, 'prehash': a.prehash,
|
||||
'nonces': {'start': a.start, 'count': a.count}, 'answered': len(got), 'agree': agree, 'disagree': disagree_count[0], 'missing': len(missing),
|
||||
'first_disagreements': disagree, 'first_missing': missing[:10], 'worker_errors': errors[:10], 'seconds': round(time.time() - t0, 1), 'at': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())}
|
||||
ev['verdict'] = 'PASS' if (agree == a.count and not disagree and not missing) else 'FAIL'
|
||||
if jc is not None:
|
||||
ev['job_context'] = os.path.abspath(a.job_context); ev['phase'] = int(a.phase); ev['object'] = jc.get('object')
|
||||
ev['segments'] = [{'start': ss, 'end': se, 'program_id': f[4], 'day_bytes': f[1], 'class': f[2]} for (ss, se, f) in seg_fields]
|
||||
if len(seg_fields) == 2:
|
||||
b = seg_fields[1][0]; fb, fa = seg_fields[0][2], seg_fields[1][2]
|
||||
ev['boundary'] = {'nonce': b, 'program_id_before': fb[4], 'program_id_after': fa[4], 'day_bytes_before': fb[1], 'day_bytes_after': fa[1],
|
||||
'hash_before': got.get(b - 1), 'hash_after': got.get(b), 'reference_before': ref.get(b - 1), 'reference_after': ref.get(b),
|
||||
'switched': bool(fb[1] != fa[1] and got.get(b - 1) == ref.get(b - 1) and got.get(b) == ref.get(b))}
|
||||
ev['verdict'] = 'PASS' if (agree == a.count and not disagree and not missing and (jc is None or len(seg_fields) < 2 or ev['boundary']['switched'])) else 'FAIL'
|
||||
return ev, (0 if ev['verdict'] == 'PASS' else 1)
|
||||
|
||||
disagree_count = [0]; errors = []
|
||||
|
|
@ -92,6 +134,7 @@ for line in sys.stdin:
|
|||
for k in range(start, start + n):
|
||||
if os.environ.get("DROP") == "1" and k == 9: continue
|
||||
h = (k * 0x9e3779b97f4a7c15) % (1 << 64)
|
||||
if p[7] == "dd" * 19: h ^= 0xdd00dd00dd00dd00 # day D+1's bytes hash differently (a reader on the wrong day disagrees)
|
||||
if os.environ.get("WRONG") == "1" and k == 7: h ^= 1
|
||||
print(f"found {seq} {k} {h:016x}", flush=True)
|
||||
print(f"done {seq} {n} 1.0", flush=True)
|
||||
|
|
@ -107,15 +150,40 @@ for line in sys.stdin:
|
|||
if not (rc == 1 and ev.get('verdict') == 'FAIL' and ev['disagree'] == 1 and ev['first_disagreements'][0]['nonce'] == 7): print(f"self-test failed: one wrong hash was not a FAIL naming nonce 7: rc={rc} {ev.get('first_disagreements')}"); fails = 1
|
||||
rc, ev = go({'DROP': '1'}, 'drop')
|
||||
if not (rc == 1 and ev['missing'] == 1 and ev['first_missing'] == [9]): print(f"self-test failed: an unanswered nonce was not a FAIL naming nonce 9: rc={rc} {ev.get('first_missing')}"); fails = 1
|
||||
if not fails: print('self-test passed: a clean million-shape run is PASS with the counts; one wrong hash is FAIL naming the nonce, the gpu and cpu hashes; an unanswered nonce is FAIL naming it; the job lines carry the pack fields and the all-pass target')
|
||||
# the job-context form: two packs (day D cc.., day D+1 dd..), two references, range_log2 4 (16 nonces a phase), boundary 24 inside phase 2
|
||||
pack2 = os.path.join(d, 'pack2'); os.makedirs(pack2)
|
||||
json.dump({'seed_bytes': 'aa' * 32, 'program_class': 'v5', 'era_seed_bytes': 'bb' * 32, 'program_id': '0x2', 'generator': 5, 'dataset': {'day_bytes': 'dd' * 19, 'log2_words': 20}}, open(os.path.join(pack2, 'program.json'), 'w'))
|
||||
refD = os.path.join(d, 'refD.txt'); open(refD, 'w').write(''.join(f"{k} {(k * 0x9e3779b97f4a7c15) % (1 << 64):016x}\n" for k in range(0, 48)))
|
||||
refD1 = os.path.join(d, 'refD1.txt'); open(refD1, 'w').write(''.join(f"{k} {((k * 0x9e3779b97f4a7c15) % (1 << 64)) ^ 0xdd00dd00dd00dd00:016x}\n" for k in range(0, 48)))
|
||||
jc = os.path.join(d, 'job-context.json')
|
||||
json.dump({'object': 'fake', 'prehash': '00' * 31 + '01', 'range_log2': 4, 'boundary_nonce': 24, 'manifest': 'deadbeef', 'packs': {'D': pack, 'D1': pack2}, 'references': {'D': refD, 'D1': refD1}}, open(jc, 'w'))
|
||||
def gojc(phase, tag, boundary=None):
|
||||
if boundary is not None:
|
||||
j = json.load(open(jc)); j['boundary_nonce'] = boundary; json.dump(j, open(jc, 'w'))
|
||||
out = os.path.join(d, f'{tag}.json')
|
||||
r = subprocess.run([sys.executable, __file__, '--worker', sys.executable, '--worker-arg', w, '--job-context', jc, '--phase', str(phase), '--job-nonces', '8', '--out', out], capture_output=True, text=True)
|
||||
return r.returncode, (json.load(open(out)) if os.path.exists(out) else {}), r.stdout + r.stderr
|
||||
for ph, s0, pid in ((1, 0, '0x1'), (3, 32, '0x2')):
|
||||
rc, ev, o = gojc(ph, f'jc{ph}')
|
||||
if not (rc == 0 and ev.get('verdict') == 'PASS' and ev['nonces'] == {'start': s0, 'count': 16} and ev['segments'][0]['program_id'] == pid and 'boundary' not in ev): print(f"self-test failed: phase {ph} of the job context was not a PASS on its range and pack: rc={rc} {ev.get('nonces')} {ev.get('segments')} {o[-200:]}"); fails = 1
|
||||
rc, ev, o = gojc(2, 'jc2')
|
||||
b = ev.get('boundary', {})
|
||||
if not (rc == 0 and ev.get('verdict') == 'PASS' and ev['nonces'] == {'start': 16, 'count': 16} and b.get('nonce') == 24 and b.get('program_id_before') == '0x1' and b.get('program_id_after') == '0x2' and b.get('switched') is True and len(ev['segments']) == 2): print(f"self-test failed: phase 2 did not switch pack at the boundary nonce 24 with the boundary block: rc={rc} {b} {o[-200:]}"); fails = 1
|
||||
rc, ev, o = gojc(2, 'jc2bad', boundary=40)
|
||||
if rc == 0 or 'not inside phase 2' not in o: print(f"self-test failed: a boundary outside phase 2 was not refused: rc={rc} {o[-200:]}"); fails = 1
|
||||
if not fails: print('self-test passed: a clean million-shape run is PASS with the counts; one wrong hash is FAIL naming the nonce, the gpu and cpu hashes; an unanswered nonce is FAIL naming it; the job-context form runs each phase on its range and pack, splits phase 2 at the boundary nonce with the boundary block, and refuses a boundary outside phase 2; the job lines carry the pack fields and the all-pass target')
|
||||
return fails
|
||||
|
||||
if __name__ == '__main__':
|
||||
if '--self-test' in sys.argv: sys.exit(self_test())
|
||||
ap = argparse.ArgumentParser(); ap.add_argument('--worker', required=True); ap.add_argument('--worker-arg', action='append', default=[]); ap.add_argument('--pack', required=True)
|
||||
ap.add_argument('--reference', required=True); ap.add_argument('--count', type=int, default=1_000_000); ap.add_argument('--start', type=int, default=0); ap.add_argument('--job-nonces', type=int, default=1 << 20)
|
||||
ap = argparse.ArgumentParser(); ap.add_argument('--worker', required=True); ap.add_argument('--worker-arg', action='append', default=[]); ap.add_argument('--pack', default='')
|
||||
ap.add_argument('--reference', default=''); ap.add_argument('--count', type=int, default=1_000_000); ap.add_argument('--start', type=int, default=0); ap.add_argument('--job-nonces', type=int, default=1 << 20)
|
||||
ap.add_argument('--prehash', default='00' * 31 + '01'); ap.add_argument('--manifest', default=''); ap.add_argument('--out', required=True)
|
||||
a = ap.parse_args(); ev, rc = run(a)
|
||||
ap.add_argument('--job-context', default=''); ap.add_argument('--phase', type=int, default=0)
|
||||
a = ap.parse_args()
|
||||
if a.job_context and not a.phase: ap.error('--job-context needs --phase 1|2|3')
|
||||
if not a.job_context and not (a.pack and a.reference): ap.error('--pack and --reference, or --job-context with --phase')
|
||||
ev, rc = run(a)
|
||||
os.makedirs(os.path.dirname(os.path.abspath(a.out)), exist_ok=True); json.dump(ev, open(a.out, 'w'), indent=2)
|
||||
print(f"p01-vectors: {ev.get('verdict', 'ERROR')}: answered {ev.get('answered')} agree {ev.get('agree')} disagree {ev.get('disagree')} missing {ev.get('missing')} in {ev.get('seconds')} s -> {a.out}")
|
||||
sys.exit(rc)
|
||||
|
|
|
|||
|
|
@ -179,7 +179,9 @@ tree_checks() {
|
|||
run "the kit ISA check: a distribution kit's binaries carry no AVX-512 encoding (the x86-64-v3 baseline; self-test with a fake objdump)" bash tools/ci/kit-isa-check.sh --self-test
|
||||
run "F02 (Review B): the proof-rule test bypass cannot reach a release build: an env read with no cfg guard or under a default feature is red; a release binary carrying the bypass string is red (self-test)" bash tools/ci/proof-rule-bypass-check.sh --self-test
|
||||
run "the test map merges structurally at a landing: master's cells plus the branch's, minus what the branch removed and master left (self-test)" python3 tools/ci/test-map-merge.py --self-test
|
||||
run "rule 33: a release entry publishes only with a PASS fresh-install canary record for its sha (the record check and the publish guard, self-tests)" bash -c 'bash tools/ci/canary-check.sh --self-test >/dev/null && bash packaging/ota/publish-manifest.sh --self-test-canary-guard >/dev/null'
|
||||
run "the REV suite is generated from Review B's findings and dispatch and matches them (self-test, then the tree)" bash tools/ci/review-suite-check.sh
|
||||
run "F03 (Review B): every component's own pin equals packaging/release-manifest.json where a release branch carries one (self-test, then the tree)" bash -c 'bash tools/ci/release-manifest-check.sh --self-test >/dev/null && bash tools/ci/release-manifest-check.sh'
|
||||
run "the public ledger (docs/ledger-public.md) is what docs/fud-ledger.md generates: one row per item, no commit ids, times or team names (self-test first)" bash -c 'node tools/ledger/export-public.mjs --self-test && node tools/ledger/export-public.mjs --check'
|
||||
run "the ledger page reads both entry heading forms (M1 and AP-F8-1) so no in-house pass row is dropped from /ledger (known-failed first)" node tools/ledger-page.mjs --self-test
|
||||
run "every workflow job carries timeout-minutes (site 15, changes 10, pow 60, sims 45; the hung-job class of 7 October 2026)" bash tools/ci/workflow-timeouts-check.sh --self-test
|
||||
|
|
|
|||
104
tools/ci/release-manifest-check.sh
Executable file
104
tools/ci/release-manifest-check.sh
Executable file
|
|
@ -0,0 +1,104 @@
|
|||
#!/usr/bin/env bash
|
||||
# F03 (Review B, 8 October 2026): one release manifest (packaging/release-manifest.json) pins node, generator, dataset policy,
|
||||
# acceptance rule, host ABI, miner app, pool, proof guests, verifying keys and activation; every component's own pin must equal it:
|
||||
# 1. packaging/windows/node-source.pin == manifest.node.sha
|
||||
# 2. the node fork's packaging/pow-freeze.txt carries manifest.generator.fingerprint (the fork at <fork dir>, when given)
|
||||
# 3. proving/igneum-prove/elf/manifest.json's elf and vk sha256s == manifest.proof_guests, and the files on disk hash to them
|
||||
# 4. the pool's vendored node checkout (pool/../vendor/igneum-node, when present) is at manifest.node.sha
|
||||
# tools/ci/release-manifest-check.sh [--tree <dir>] [<fork dir>] exit 0 when every pin agrees, 1 with every disagreement named
|
||||
# tools/ci/release-manifest-check.sh --self-test
|
||||
set -euo pipefail
|
||||
HERE="$(cd "$(dirname "$0")" && pwd -P)"; ME="$HERE/$(basename "$0")"
|
||||
check() { # <tree> [<fork dir>] ; prints "red ..." lines
|
||||
local tree="$1" fork="${2:-}" rc=0
|
||||
python3 - "$tree" "$fork" <<'PY' || rc=1
|
||||
import json, sys, os, hashlib, subprocess
|
||||
tree, fork = sys.argv[1], sys.argv[2]; red = []
|
||||
m = json.load(open(os.path.join(tree, 'packaging/release-manifest.json')))
|
||||
pin = os.path.join(tree, 'packaging/windows/node-source.pin')
|
||||
if os.path.exists(pin):
|
||||
p = open(pin).read().split()[0] if open(pin).read().split() else ''
|
||||
if not (p.startswith(m['node']['sha']) or m['node']['sha'].startswith(p)): red.append(f"red node-source.pin reads {p}, the manifest pins node {m['node']['sha']}")
|
||||
else: red.append('red packaging/windows/node-source.pin is missing')
|
||||
pm_path = os.path.join(tree, 'proving/igneum-prove/elf/manifest.json')
|
||||
if os.path.exists(pm_path):
|
||||
pm = json.load(open(pm_path))
|
||||
for g in ('shard', 'aggregator'):
|
||||
for k in ('elf_sha256', 'vk_sha256'):
|
||||
if pm[g][k] != m['proof_guests'][g][k]: red.append(f"red proof guest {g} {k}: the proving manifest reads {pm[g][k][:18]}, the release manifest {m['proof_guests'][g][k][:18]}")
|
||||
for fk, sk in (('elf', 'elf_sha256'), ('vk', 'vk_sha256')):
|
||||
fp = os.path.join(tree, 'proving/igneum-prove/elf', pm[g][fk])
|
||||
if os.path.exists(fp):
|
||||
h = '0x' + hashlib.sha256(open(fp, 'rb').read()).hexdigest()
|
||||
if h != m['proof_guests'][g][sk]: red.append(f"red proof guest {g} {fk} on disk hashes to {h[:18]}, the release manifest pins {m['proof_guests'][g][sk][:18]}")
|
||||
else: red.append('red proving/igneum-prove/elf/manifest.json is missing')
|
||||
# provenance (V6-10, the proving lane's class, 8 October 2026): a proving manifest that names its source_commit must name a commit
|
||||
# the tree's HEAD contains; a manifest pinned from a commit this tree never carried (the 5 October manifest had no provenance at all)
|
||||
# is red. A manifest without source_commit is a note, not a red, until igneum-prove-pin writes one everywhere.
|
||||
if os.path.exists(pm_path) and os.path.isdir(os.path.join(tree, '.git')) or os.path.exists(pm_path) and os.path.isfile(os.path.join(tree, '.git')):
|
||||
sc = pm.get('source_commit')
|
||||
if sc:
|
||||
r = subprocess.run(['git', '-C', tree, 'merge-base', '--is-ancestor', sc, 'HEAD'], capture_output=True, text=True)
|
||||
if r.returncode != 0: red.append(f"red proving manifest source_commit {sc[:12]} is not an ancestor of this tree's HEAD (pinned from a commit this branch never carried)")
|
||||
else: print('release-manifest: note: the proving manifest names no source_commit (pre-provenance pin)')
|
||||
if fork:
|
||||
fz = os.path.join(fork, 'packaging/pow-freeze.txt')
|
||||
if os.path.exists(fz):
|
||||
if m['generator']['fingerprint'] not in open(fz).read(): red.append(f"red the fork's packaging/pow-freeze.txt does not carry the manifest's generator fingerprint {m['generator']['fingerprint'][:16]}")
|
||||
else: red.append(f'red {fz} is missing')
|
||||
try:
|
||||
head = subprocess.run(['git', '-C', fork, 'rev-parse', 'HEAD'], capture_output=True, text=True).stdout.strip()
|
||||
if head and not head.startswith(m['node']['sha']): red.append(f"red the fork checkout is at {head[:8]}, the manifest pins node {m['node']['sha']}")
|
||||
except Exception: pass
|
||||
vend = os.path.join(tree, 'vendor/igneum-node')
|
||||
if os.path.isdir(vend):
|
||||
head = subprocess.run(['git', '-C', vend, 'rev-parse', 'HEAD'], capture_output=True, text=True).stdout.strip()
|
||||
if head and not head.startswith(m['node']['sha']): red.append(f"red the pool's vendored node checkout is at {head[:8]}, the manifest pins node {m['node']['sha']} (the shadow_reps seam closes by a build against the pinned node)")
|
||||
nodeas = os.path.join(tree, 'pool/src/node.rs')
|
||||
if os.path.exists(nodeas) and 'struct EpochSeeds' in open(nodeas).read(): red.append('red pool/src/node.rs redefines EpochSeeds; it must import kaspa_pow::igneum::EpochSeeds')
|
||||
# V6-12 (R1 p. 213, the master): the signed manifest binds lockfile hashes, kernel and host binaries, supported devices and drivers, the prover
|
||||
# server patch, memory thresholds and tuner identity; a manifest without the block reads BLOCKED on INT-07, never a pass of it
|
||||
v = m.get('v6_12') or {}
|
||||
for k in ('lockfile_sha256', 'kernel_binaries', 'host_binaries', 'supported_devices', 'supported_drivers', 'prover_server_patch', 'memory_thresholds', 'tuner_identity', 'signature'):
|
||||
if k not in v: print(f'note V6-12 field {k} is not in the manifest (INT-07 reads BLOCKED until it is)')
|
||||
for r in red: print(r)
|
||||
sys.exit(1 if red else 0)
|
||||
PY
|
||||
return $rc
|
||||
}
|
||||
if [ "${1:-}" = --self-test ]; then
|
||||
d=$(mktemp -d); trap 'rm -rf "$d"' EXIT; fails=0
|
||||
mk() { local t="$d/$1"; mkdir -p "$t/packaging/windows" "$t/proving/igneum-prove/elf" "$t/pool/src"; printf 'elf' > "$t/proving/igneum-prove/elf/a.elf"; printf 'vk' > "$t/proving/igneum-prove/elf/a.vk"
|
||||
local es="0x$(printf 'elf' | shasum -a 256 | cut -d' ' -f1)" vs="0x$(printf 'vk' | shasum -a 256 | cut -d' ' -f1)"
|
||||
printf '{"shard":{"elf":"a.elf","elf_sha256":"%s","vk":"a.vk","vk_sha256":"%s"},"aggregator":{"elf":"a.elf","elf_sha256":"%s","vk":"a.vk","vk_sha256":"%s"}}\n' "$es" "$vs" "$es" "$vs" > "$t/proving/igneum-prove/elf/manifest.json"
|
||||
printf '{"node":{"sha":"%s"},"generator":{"fingerprint":"ffff0000"},"proof_guests":{"shard":{"elf_sha256":"%s","vk_sha256":"%s"},"aggregator":{"elf_sha256":"%s","vk_sha256":"%s"}}}\n' "$2" "$es" "$vs" "$es" "$vs" > "$t/packaging/release-manifest.json"
|
||||
printf '%s\n' "$3" > "$t/packaging/windows/node-source.pin"; printf 'use kaspa_pow::igneum::EpochSeeds;\n' > "$t/pool/src/node.rs"; }
|
||||
mk agree abcd1234 abcd1234; mk pinoff abcd1234 ffff1234
|
||||
bash "$ME" --tree "$d/agree" >/dev/null 2>&1 || { echo "self-test failed: agreeing pins were refused: $(bash "$ME" --tree "$d/agree" 2>&1)"; fails=1; }
|
||||
out=$(bash "$ME" --tree "$d/pinoff" 2>&1) && { echo "self-test failed: a node-source pin off the manifest passed"; fails=1; }; case "$out" in *"node-source.pin reads ffff1234"*) ;; *) echo "self-test failed: the pin was not named: $out"; fails=1 ;; esac
|
||||
printf 'elf2' > "$d/agree/proving/igneum-prove/elf/a.elf"; out=$(bash "$ME" --tree "$d/agree" 2>&1) && { echo "self-test failed: a guest file that hashes off the manifest passed"; fails=1; }; case "$out" in *"on disk hashes to"*) ;; *) echo "self-test failed: the hash drift was not named: $out"; fails=1 ;; esac
|
||||
printf 'elf' > "$d/agree/proving/igneum-prove/elf/a.elf"; mkdir -p "$d/fork/packaging"; printf '# f\nffff0000 c1 v5 2026\n' > "$d/fork/packaging/pow-freeze.txt"
|
||||
bash "$ME" --tree "$d/agree" "$d/fork" >/dev/null 2>&1 || { echo "self-test failed: a fork carrying the fingerprint was refused: $(bash "$ME" --tree "$d/agree" "$d/fork" 2>&1)"; fails=1; }
|
||||
printf '# f\n00000000 c1 v5 2026\n' > "$d/fork/packaging/pow-freeze.txt"; out=$(bash "$ME" --tree "$d/agree" "$d/fork" 2>&1) && { echo "self-test failed: a fork without the fingerprint passed"; fails=1; }
|
||||
printf 'pub struct EpochSeeds {}\n' > "$d/agree/pool/src/node.rs"; mkdir -p "$d/agree/vendor/igneum-node" && ( cd "$d/agree/vendor/igneum-node" && git init -q && git -c user.name=t -c user.email=t@t commit -q --allow-empty -m x ) >/dev/null 2>&1
|
||||
out=$(bash "$ME" --tree "$d/agree" 2>&1) && { echo "self-test failed: a redefined EpochSeeds and an unpinned vendored node passed"; fails=1; }; case "$out" in *"redefines EpochSeeds"*) ;; *) echo "self-test failed: the seam was not named: $out"; fails=1 ;; esac
|
||||
# provenance: a git tree whose proving manifest names a source_commit HEAD contains passes; one naming a commit HEAD never carried is red
|
||||
mk prov abcd1234 abcd1234; ( cd "$d/prov" && git init -q && git add -A && git -c user.name=t -c user.email=t@t commit -q -m x ) >/dev/null 2>&1; sc=$(git -C "$d/prov" rev-parse HEAD)
|
||||
python3 - "$d/prov/proving/igneum-prove/elf/manifest.json" "$sc" <<'PY2'
|
||||
import json,sys; p=sys.argv[1]; d=json.load(open(p)); d['source_commit']=sys.argv[2]; json.dump(d,open(p,'w'))
|
||||
PY2
|
||||
bash "$ME" --tree "$d/prov" >/dev/null 2>&1 || { echo "self-test failed: a manifest whose source_commit HEAD contains was refused: $(bash "$ME" --tree "$d/prov" 2>&1)"; fails=1; }
|
||||
python3 - "$d/prov/proving/igneum-prove/elf/manifest.json" <<'PY2'
|
||||
import json,sys; p=sys.argv[1]; d=json.load(open(p)); d['source_commit']='0'*40; json.dump(d,open(p,'w'))
|
||||
PY2
|
||||
out=$(bash "$ME" --tree "$d/prov" 2>&1) && { echo "self-test failed: a manifest pinned from a commit the tree never carried passed"; fails=1; }; case "$out" in *"not an ancestor"*) ;; *) echo "self-test failed: the provenance red was not named: $out"; fails=1 ;; esac
|
||||
[ "$fails" = 0 ] && echo "self-test passed: agreeing pins pass; a node-source pin, a guest file's hash, a fork freeze file, the pool's vendored node checkout, a redefined EpochSeeds or a proving manifest pinned from a commit the tree never carried is red and named"
|
||||
exit $fails
|
||||
fi
|
||||
TREE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"; FORK=""
|
||||
while [ $# -gt 0 ]; do case "$1" in --tree) TREE="$2"; shift 2 ;; *) FORK="$1"; shift ;; esac; done
|
||||
[ -f "$TREE/packaging/release-manifest.json" ] || { echo "release-manifest: no packaging/release-manifest.json on this tree: not a release branch, nothing to pin"; exit 0; }
|
||||
rc=0; out=$(check "$TREE" "$FORK") || rc=1
|
||||
printf '%s\n' "$out" | sed -n 's/^red /release-manifest: RED: /p; s/^note /release-manifest: /p' | grep . || true
|
||||
[ "$rc" = 0 ] && echo "release-manifest: every component's own pin equals packaging/release-manifest.json"
|
||||
exit $rc
|
||||
|
|
@ -36,7 +36,7 @@ function suite(findings, dispatchMd, prefix, sourceNote, master) {
|
|||
}
|
||||
function merge(reg, s) { // replace the suite of the same code, keeping live fields of cases that already exist
|
||||
const old = (reg.suites || []).find((x) => x.code === s.code); const live = new Map((old?.tests || []).map((t) => [t.id, t]));
|
||||
for (const t of s.tests) { const o = live.get(t.id); if (o) for (const k of ['run_status', 'run_id', 'evidence_path', 'updated', 'evidence_record', 'in_progress_since', 'deferral_note', 'approvals']) if (k in o) t[k] = o[k]; }
|
||||
for (const t of s.tests) { const o = live.get(t.id); if (o) for (const k of ['run_status', 'run_id', 'evidence_path', 'updated', 'evidence_record', 'evidence_records', 'blocked_on', 'method_recorded', 'in_progress_since', 'deferral_note', 'approvals']) if (k in o) t[k] = o[k]; }
|
||||
reg.suites = [...(reg.suites || []).filter((x) => x.code !== s.code), s]; return reg;
|
||||
}
|
||||
if (args.includes('--self-test')) {
|
||||
|
|
@ -49,9 +49,9 @@ if (args.includes('--self-test')) {
|
|||
if (!(s.tests[0].title === 'r one' && s.tests[0].accept === 'r one')) { console.log('self-test failed: the title and accept are not the regression line verbatim'); fails = 1; }
|
||||
if (!(s.tests[0].owner_lane === 'lane x, lane y' && s.tests[2].owner_lane === 'lane z')) { console.log(`self-test failed: owners not read from the dispatch table: ${s.tests.map((t) => t.owner_lane)}`); fails = 1; }
|
||||
if (!(s.tests[0].finding === 'R2-F01' && s.tests[0].priority === 'P0' && s.tests[0].run_status === 'NOT RUN' && s.tests[0].method.includes('Automated'))) { console.log('self-test failed: finding, priority, NOT RUN or method missing'); fails = 1; }
|
||||
const reg = { suites: [{ code: 'GOV', tests: [] }, { code: 'REV', tests: [{ id: 'R2-F01-R01', run_status: 'NOT RUN', in_progress_since: 't', run_id: 'r9' }] }] };
|
||||
const reg = { suites: [{ code: 'GOV', tests: [] }, { code: 'REV', tests: [{ id: 'R2-F01-R01', run_status: 'NOT RUN', in_progress_since: 't', evidence_records: { 'c:x': { decision: 'PASS' } }, run_id: 'r9' }] }] };
|
||||
const m = merge(JSON.parse(JSON.stringify(reg)), s); const rev = m.suites.find((x) => x.code === 'REV');
|
||||
if (!(m.suites.length === 2 && rev.tests.length === 3 && rev.tests[0].in_progress_since === 't' && rev.tests[0].run_id === 'r9' && rev.tests[1].run_status === 'NOT RUN')) { console.log('self-test failed: a regenerated suite did not keep the existing case\'s live fields or dropped another suite'); fails = 1; }
|
||||
if (!(m.suites.length === 2 && rev.tests.length === 3 && rev.tests[0].in_progress_since === 't' && rev.tests[0].evidence_records?.['c:x']?.decision === 'PASS' && rev.tests[0].run_id === 'r9' && rev.tests[1].run_status === 'NOT RUN')) { console.log('self-test failed: a regenerated suite did not keep the existing case\'s live fields or dropped another suite'); fails = 1; }
|
||||
const map = { cells: { c1: { cases: ['R2-F01-R01'] } }, not_run: { 'REV-F02-1': 'old' } }; const n = mapReasons(map, s);
|
||||
if (!(n === 2 && !('R2-F01-R01' in map.not_run) && /lane z/.test(map.not_run['REV-F02-1']) && /lane x/.test(map.not_run['REV-F01-2']))) { console.log(`self-test failed: the map's NOT RUN reasons: ${JSON.stringify(map.not_run)} n=${n}`); fails = 1; }
|
||||
if (!fails) console.log('self-test passed: one case per required regression with the id <prefix>-<finding>-<n>, the line verbatim as title and accept, the owner from the dispatch table, finding and priority carried, NOT RUN; regenerating keeps live fields and the other suites; every unmapped case gets a NOT RUN reason naming its owner lane in the map, a mapped one loses it');
|
||||
|
|
|
|||
|
|
@ -225,8 +225,8 @@
|
|||
}
|
||||
},
|
||||
"bench:pc1-packs": {
|
||||
"command": "tools/ca3-v4-amend/pc1-ca4-packs.ps1 on PC 1 (RTX 5090, the signed-jobs channel, --cards-off, the Power Helper lock at 1,300 MHz, nvidia-smi at 1 Hz, 250 x 2^24 per row, the kit's CUDA worker --bench)",
|
||||
"box_class": "PC 1 bench",
|
||||
"command": "tools/ca3-v4-amend/pc1-ca4-packs.ps1 on the project's own rig (RTX 5090, the signed-jobs channel, --cards-off, the Power Helper lock at 1,300 MHz, nvidia-smi at 1 Hz, 250 x 2^24 per row, the kit's CUDA worker --bench)",
|
||||
"box_class": "the project's own rig bench",
|
||||
"fixtures": [
|
||||
"F0",
|
||||
"F1"
|
||||
|
|
@ -245,8 +245,8 @@
|
|||
}
|
||||
},
|
||||
"bench:pc1-amd": {
|
||||
"command": "tools/ca3-v4-amend/pc1-amd-cardin-bench.ps1 on PC 1 (RX 7600, OpenCL, gfx1102, 30 dispatches of 2^24 per size, rate only)",
|
||||
"box_class": "PC 1 bench",
|
||||
"command": "tools/ca3-v4-amend/pc1-amd-cardin-bench.ps1 on the project's own rig (RX 7600, OpenCL, gfx1102, 30 dispatches of 2^24 per size, rate only)",
|
||||
"box_class": "the project's own rig bench",
|
||||
"fixtures": [
|
||||
"F0",
|
||||
"F1"
|
||||
|
|
@ -422,7 +422,7 @@
|
|||
"VER-08"
|
||||
],
|
||||
"coverage": {
|
||||
"VER-03": "partial: a payment receipt is labelled payment only when the receipt is proven against the segment statement's receipts commitment under the finality certificate, inclusion-only receipts read inclusion, a tampered receipt and a flipped status fail; run on the Devnet 3 fixtures, the 2.0 devnet re-run waits for its first paid segment whose last block carries a transaction",
|
||||
"VER-03": "partial: a payment receipt is labelled payment only when the receipt is proven against the segment statement's receipts commitment under the finality certificate, inclusion-only receipts read inclusion, a tampered receipt and a flipped status fail; run on the the earlier devnet fixtures, the 2.0 devnet re-run waits for its first paid segment whose last block carries a transaction",
|
||||
"VER-04": "partial: the oracle's trust() names the deployer-installed table and the unchecked aggregator signature, a second hash at a stored certificate index is refused, a root claim at an unknown index is refused, another chain id is refused (tools/reference-apps/oracle/test.mjs on Sepolia); Review B F04: the Sepolia verifiers apply the two-thirds rule only, carry no lock-kind field, and an under-threshold (recovery-rule) certificate fails closed in submitCertificate, so no stored root is a recovery lock (the DEX lane's docs/bridge/light-client-bridge.md paragraph); the claimed-option review of the trust model is the reviewer's",
|
||||
"VER-05": "partial: the public node and the reference reader on build-1 executed the 2.0 devnet from genesis with no snapshot (unit ExecStart without --igneum-exec-snapshot), and the pages fetch headers, certificates, coinbase bodies and account proofs from the public read RPC only; the archive and availability model beyond the 2,048-block state ring is the OPS no-founder exercise",
|
||||
"VER-06": "partial: the read service never serves a certificate from another network, a withheld header in the path and a corrupted proof node are detected, stale data prints its lock age beside every balance, and a client told no certificate never falls back to a trusted RPC balance; detection on the fault network F4 is not run",
|
||||
|
|
@ -576,8 +576,8 @@
|
|||
}
|
||||
},
|
||||
"bench:amd-intel-energy": {
|
||||
"command": "the AMD-and-Intel energy lane jobs on the signed-jobs channel: run-ae-pc2-b580-energy-20261008 (PC 2, Arc B580, the class v6 kit worker --memprobe and --bench-pack on hl-v6-foldrw and hl-v6-all, Level Zero energy counter) and run-ae-pc1-7600-energy-20261008 (PC 1, RX 7600, the same with ADLX watts at stock, plimit -30, gmax -500 + plimit -30); scripts in docs/analysis/class-v6/amd-intel-energy/",
|
||||
"box_class": "PC 1 and PC 2 bench (OpenCL)",
|
||||
"command": "the AMD-and-Intel energy lane jobs on the signed-jobs channel: run-ae-pc2-b580-energy-20261008 (PC 2, Arc B580, the class v6 kit worker --memprobe and --bench-pack on hl-v6-foldrw and hl-v6-all, Level Zero energy counter) and run-ae-pc1-7600-energy-20261008 (the project's own rig, RX 7600, the same with ADLX watts at stock, plimit -30, gmax -500 + plimit -30); scripts in docs/analysis/class-v6/amd-intel-energy/",
|
||||
"box_class": "the project's own rig and PC 2 bench (OpenCL)",
|
||||
"fixtures": [
|
||||
"F0",
|
||||
"F1"
|
||||
|
|
@ -631,7 +631,7 @@
|
|||
}
|
||||
},
|
||||
"pc:install-update": {
|
||||
"command": "signed jobs and relay runs on PC 1 (ae432dc7) and PC 2 (1ccfe586): the installer over the running app (rule 14: installed versions, the process set, the first upload), the OTA path (update-now, the update-return read-back line), and the no-click resume after the install (relay run mining-on-after-200.ps1)",
|
||||
"command": "signed jobs and relay runs on the project's own rig (ae432dc7) and PC 2 (1ccfe586): the installer over the running app (rule 14: installed versions, the process set, the first upload), the OTA path (update-now, the update-return read-back line), and the no-click resume after the install (relay run mining-on-after-200.ps1)",
|
||||
"box_class": "PC (the two Windows PCs; nothing on the Mac)",
|
||||
"fixtures": [
|
||||
"F2"
|
||||
|
|
@ -647,6 +647,47 @@
|
|||
"OPS-03": "partial: the update path's read-back and the signed manifest's verify on the PC (a tampered or rolled-back payload is refused by the engine's manifest check); authority separation is the shipper's review"
|
||||
}
|
||||
},
|
||||
"harness:release-manifest": {
|
||||
"command": "bash tools/ci/release-manifest-check.sh (self-test, then the tree; the pins, the proof guests' hashes on disk, the fork freeze, the pool's vendored node, the proving manifest's source_commit provenance) and bash tools/ci/build-from-manifest.sh --box N on the release tree (kaspad with igneum-pow, igneum-miner, igneum-pool, igneum-app, igneum-prove-host from packaging/release-manifest.json with the node vendored at the manifest's sha)",
|
||||
"box_class": "gate",
|
||||
"fixtures": [
|
||||
"F0"
|
||||
],
|
||||
"cases": [
|
||||
"R2-F03-R01"
|
||||
],
|
||||
"coverage": {
|
||||
"R2-F03-R01": "full for the components the tree builds (node, miner, pool, app, prove-host from one manifest, no unpublished vendor tree); the GPU workers are the kit's cross-build and read under the shipper's kit-isa line, not this cell"
|
||||
}
|
||||
},
|
||||
"harness:same-work": {
|
||||
"command": "tools/ci/p01-vectors.py --job-context <job-context.json> --phase 1|2|3 per reader (CUDA, OpenCL, Metal workers), igneum-miner --recheck-vectors (node), igneum-pow hash-bound (CPU reference), the pool's member-side re-check; docs/plans/igneum-2.0-same-work-test.md",
|
||||
"box_class": "release (the readers on their pods and boxes)",
|
||||
"fixtures": [
|
||||
"F0"
|
||||
],
|
||||
"cases": [
|
||||
"R2-F03-R02",
|
||||
"R2-F03-R03"
|
||||
],
|
||||
"coverage": {
|
||||
"R2-F03-R02": "partial until every reader has run: bit-for-bit agreement per nonce across node, CPU reference, CUDA, OpenCL, Metal and pool on one job context",
|
||||
"R2-F03-R03": "partial: the day-boundary transition in phase 2 (the one transition every live network crosses hourly at 60x); a class rotation is the same test on a research object until a v6 floor is set"
|
||||
}
|
||||
},
|
||||
"canary:fresh-install": {
|
||||
"command": "the rule 33 fresh-install canary on a release tip (the founder, 8 October 2026): install from the published artefact on a non-AVX-512 box with an empty datadir, sync genesis to tip, five minutes mining with zero refusals and an accepted block, one shard claimed, proved and paid or queued, a bounded quit, every line read back; the record tools/ci/canary/<sha>.json (one file per sha, an artefacts list: fleet, windows, mac, hive) read by tools/ci/canary-check.sh <sha> [--artefact kind], which publish-manifest.sh and publish-public.sh refuse without",
|
||||
"box_class": "release (a non-AVX-512 box with an empty datadir)",
|
||||
"fixtures": [
|
||||
"F0"
|
||||
],
|
||||
"cases": [
|
||||
"INT-07"
|
||||
],
|
||||
"coverage": {
|
||||
"INT-07": "partial: V6-12's clean-install half (one published object installed fresh, synced, mined, proved and paid on one host per artefact); the cross-host agreement half (node, pool, CPU verifier, each GPU host across activation) is harness:same-work's"
|
||||
}
|
||||
},
|
||||
"review:k-lane-shadow-k": {
|
||||
"command": "floor lane 2's placed and routed cores on ASAP7 in tools/chip-model/rtl (the rows in docs/analysis/class-v6/floor/shadow-k.md); the register landing 50ff1611f recorded ADV-06 against it before the recorder existed",
|
||||
"box_class": "none",
|
||||
|
|
@ -663,9 +704,9 @@
|
|||
"GOV-02": "the approval is recorded in the registry's approval field; the automated half (thresholds frozen before any run_status) is the gate rule landing by 21:00",
|
||||
"GOV-04": "the evidence vault F9 (raw and negative evidence preserved) is the gate rule landing by 21:00: a PASS must carry its evidence file",
|
||||
"GOV-08": "the stale-evidence rule (evidence older than the manifest sha reads NOT RUN) is the gate rule landing by 21:00",
|
||||
"GPU-04": "the memory-clock ladder has no harness tonight: PC 1 mines nothing under the Devnet 3 off order",
|
||||
"GPU-04": "the memory-clock ladder has no harness tonight: the project's own rig mines nothing under the the earlier devnet off order",
|
||||
"GPU-06": "accepted work under ordinary connectivity needs the fault network F4",
|
||||
"GPU-07": "the sustained thermal and power soak has no harness tonight: PC 1 mines nothing under the Devnet 3 off order",
|
||||
"GPU-07": "the sustained thermal and power soak has no harness tonight: the project's own rig mines nothing under the the earlier devnet off order",
|
||||
"POW-05": "amortised cheap winning attempts are the attack lanes' grind and era harnesses (tools/attack/f7-era, f9-grind), not in the release matrix; their rows come from those lanes",
|
||||
"ROT-03": "miner-voted bring-forward needs a vote harness on the fault network F4",
|
||||
"ROT-04": "seed-selection resistance is the census harness (the class v6 invention lane), not yet in the matrix",
|
||||
|
|
@ -753,7 +794,6 @@
|
|||
"INT-04": "INT-04 (R1 integration gate): the gate's harness is the owner lane's (pool lane (a1c484c48a62948c2)); not yet named in the map",
|
||||
"INT-05": "INT-05 (R1 integration gate): the gate's harness is the owner lane's (node lane (a283f5f0d364ceef0)); not yet named in the map",
|
||||
"INT-06": "INT-06 (R1 integration gate): the gate's harness is the owner lane's (node lane (a283f5f0d364ceef0)); not yet named in the map",
|
||||
"INT-07": "INT-07 (R1 integration gate): the gate's harness is the owner lane's (CI steward with the hash lane (a690540514aa453d7) and the worker lane (a9e87343f008e0edd)); not yet named in the map",
|
||||
"INT-08": "INT-08 (R1 integration gate): the gate's harness is the owner lane's (CI steward with the hash lane (a690540514aa453d7) and the worker lane (a9e87343f008e0edd)); not yet named in the map",
|
||||
"INT-09": "INT-09 (R1 integration gate): the gate's harness is the owner lane's (app lane); not yet named in the map",
|
||||
"INT-10": "INT-10 (R1 integration gate): the gate's harness is the owner lane's (CI steward with the hash lane (a690540514aa453d7) and the worker lane (a9e87343f008e0edd)); not yet named in the map",
|
||||
|
|
@ -772,9 +812,6 @@
|
|||
"R2-F02-R01": "R2-F02 (P0, the external review): the regression's harness is the owner lane's (proving lane, CI steward); not yet named in the map",
|
||||
"R2-F02-R02": "R2-F02 (P0, the external review): the regression's harness is the owner lane's (proving lane, CI steward); not yet named in the map",
|
||||
"R2-F02-R03": "R2-F02 (P0, the external review): the regression's harness is the owner lane's (proving lane, CI steward); not yet named in the map",
|
||||
"R2-F03-R01": "R2-F03 (P0, the external review): the regression's harness is the owner lane's (CI steward, hash lane (ProgramClass::V6 on freeze), pool lane); not yet named in the map",
|
||||
"R2-F03-R02": "R2-F03 (P0, the external review): the regression's harness is the owner lane's (CI steward, hash lane (ProgramClass::V6 on freeze), pool lane); not yet named in the map",
|
||||
"R2-F03-R03": "R2-F03 (P0, the external review): the regression's harness is the owner lane's (CI steward, hash lane (ProgramClass::V6 on freeze), pool lane); not yet named in the map",
|
||||
"R2-F04-R01": "R2-F04 (P0, the external review): the regression's harness is the owner lane's (node lane, reference apps, site (explorer)); not yet named in the map",
|
||||
"R2-F04-R02": "R2-F04 (P0, the external review): the regression's harness is the owner lane's (node lane, reference apps, site (explorer)); not yet named in the map",
|
||||
"R2-F04-R03": "R2-F04 (P0, the external review): the regression's harness is the owner lane's (node lane, reference apps, site (explorer)); not yet named in the map",
|
||||
|
|
|
|||
Loading…
Reference in a new issue