P1/P2/P3 runtime: archive (Node.js, TCP 27101, no blocks, no tip VM) + on-demand + daemon client.
After a programming or spec change on one of these faces, update that face’s RULES in the same task. Do not leave the detail only in chat. See conet-dle-write-back-subproject.mdc.
| Face | Canonical RULES |
|---|---|
| Archive HTTP / route table | this file §Archive |
| On-demand pool / 7+2 | src/shared/ondemand/RULES.md |
| Daemon / browser client | src/daemon/RULES.md |
Lab route table (src/shared/labRoute.ts):
liveGroupIds(table)— unique non-emptyownGroupId+ eachgroups[].groupId+planeDirectorykeys, sortedliveGroupCount(table)—Math.max(1, ids.length)planeDirectory— wallets of other live groups for plane-wide gather. Not BFT peers. First-groupconfig.peersstay intra-group only.foreignChains— mark a foreignchainNftId(e.g. NFT 42 on G2) with that group’s wallets. Do not put G2 hosts into NFT 42 BFT.
Laboratory fission (G_e: 1 \to 2). Not production DePIN and not 30-day qualification. G2 L1 registerLiveGroup landed 2026-08-16 (tx 0xf781f2c2…876d5153). After the 2026-08-16 keep-deploy, hosts emit that tx as hop1.ownGroupId / liveGroupIds. canonicalGroupId still aliases laboratory keccak / 2 / 0x2.
| Item | Value |
|---|---|
| G2 Group ID | 0xf781f2c23fe3b3dac09dc3e1929016b0af200ee93978e916df64d750876d5153 — L1 registerLiveGroup tx. Laboratory keccak 0x7b3b8eb959dcc0f75a309fcc16e7f840efe76dc27f2ef0d4eca8b8617f9b1a07 / uint 2 / 0x2 alias it. Do not advertise uint 2 as Group ID. |
| G2 hosts | 7 greenfield machines, 5 active + 2 standby, TCP 27101, dir /home/peter/dle-m6-g2 |
| G2 flags | enableBft: false, enableOndemand: false, seedFissionMarker: true |
| Marker | DleLabFissionMarkerV1, chainNftId = 6000000006, hash labFissionMarkerHash(ownGroupId) (seeds from laboratory keccak so 0x7ca21e5a…e2345c stays valid). Re-seed after ownGroupId cutover is first-write-wins: existing locator → skip freezer rewrite. |
Hash gather (locatePlane / get):
locate()stays synchronous this-group onlythisGroupOnly: truenever upgrades a miss to plane-wideGe === 1→ this-groupnotFound(planeWideNull: false)Ge >= 2→ after every live group returns a trusted this-groupnotFound,hashLookupPlaneNotFound(planeWideNull: true,scope: 'allLiveGroups')- One group timeout / no plane wallets →
unavailable, not JSON-RPCnull - Foreign hop:
historyProviders(chainNftId)orplaneDirectory[locator.groupId]. No local freezer fallback eth_getBlockByHash/eth_getTransactionByHashreturn JSON-RPCnullonly whenplaneWideNull === true
Deploy: npm run lab:deploy-m6 then npm run lab:accept-m6. G1 keep-update only (no wipe). G2 rolling restart keeps /home/peter/dle-m6-g2/data (only replaces app/). Never restart geth / beacon / validator. Do not stop on-demand 30 or newchain-user.
tipStateRoot and membershipRoot are first-class HashObjectKinds. A hit returns DleLabTipStateRootV1 / DleLabMembershipRootV1, not the Archive Certificate. Do not catalogue them via boundField alias onto kind=ac.
| Rule | Meaning |
|---|---|
| First-write-wins | Same hash + same kind + same chainNftId at a later height is skipped (skipped: 'first-write-wins'). Membership roots are usually stable across heights. |
ZERO32 |
Invalid / zero hashes are not catalogued. |
| Side-index | indexCertificate / indexRecord side-index these roots after the AC / lab record. Side-index failure MUST NOT fail the parent AC. |
| Kind / nft conflict | Existing locator with a different kind or nft → { ok: true, skipped: 'conflict' }, not a parent failure. |
| G2 | G2 has enableBft: false, so M7 objects appear on G1 NFT 42 ACs (and G1 newchain tipStateRoot). Do not invent a G2 AC just to index roots. |
| Not qualification | M7 is not L1 registerLiveGroup and not 30-day qualification. |
Kinds: ac \| prevoteQc \| tipStateRoot \| membershipRoot \| block \| tx \| daRootProof.
HTTP (src/archive/http.ts clusterView):
| Path | Fields |
|---|---|
GET /health |
liveGroupCount, liveGroupIds — spread after extraHealth so they are not overwritten |
GET /api/v2/dle |
same at top level and inside archive |
Genesis / no fission: liveGroupCount === 1, liveGroupIds === ["0x3076a806de71ab75b2d48063cc3f1e7d8f8e3d54cb1d45a7469c75c9276f2ad0"] (bootstrap Group ID = L1 register tx hash). After lab M6 + G2 L1 register + host cutover: liveGroupCount === 2, adding 0xf781f2c23fe3b3dac09dc3e1929016b0af200ee93978e916df64d750876d5153. Hosts emit those two register txs. canonicalGroupId / sameGroupId still alias laboratory keccak 0x7b3b8eb9…7f9b1a07, 2, and 0x2 to the G2 register tx. Legacy clients may still send dle.lab.group.v1 / 1; those alias to the first-group hash. Do not advertise L1 storage uints 1 or 2 as Group ID. labFissionMarkerHash still seeds from the laboratory keccak so the already-sown marker 0x7ca21e5a…e2345c stays valid.
Emit / persist (mandatory): every archive HTTP, JSON-RPC, hop receipt, hash-index, route, and on-demand view that includes groupId / liveGroupIds must emit canonicalGroupId(...). Disk WAL / hash-index.json / on-demand selection that still stores dle.lab.group.v1 must be treated as the bootstrap hash on load (sameGroupId); a later write of the same locator with the hash must not ERR_HASH_LOCATOR_CONFLICT — rewrite the stored string to the hash. GET /api/v2/dle top level includes chainName: CoNET-DLE Testnet.
registerLabChainNft copies NFT 42’s wallets into the same ownGroupId. New lab chains do not add a group.
openHashStore loads hash-index.json + hash-freezer.json once into process memory. getLocator / listLocators / getBody are in-memory. locatorCount() is a maintained integer; listLocators() returns a sorted snapshot invalidated on putLocator outside a batch, and on endBatch(). Disk writes happen only on putLocator / putBody. Catch-up must wrap ingest in beginBatch() / endBatch() so a 128-object batch writes index + freezer once and does not rebuild hashIndex per object. inventoryNow() keeps its last snapshot while isBatching(). Do not re-read the JSON files on every /health or hop.
hashIndexRootOf / proveHashIndex / hashIndexRootView share one in-process Merkle cache keyed by locator fingerprint. Do not rebuild all leaf hashes + layers per sample, per Explorer dle_proveHash, or per incoming challenge. A cache miss still costs O(L log L); a hit is O(path).
GET /health hashIndex must use a cached hashIndexRootView (invalidate when locatorCount() changes). Peer heartbeats use GET /liveness (tiny {ok,command,domainId}), never full /health. Extra /health fields are cached ~2s.
newchain-state.json / hash-index / freezer writes are atomic (*.tmp + rename). Vote snapshots debounce 2s; accept and Archive Certificate flush immediately. /newchain/bft gossip is batched: at most 3 pending + 2 certified topics per 1s tick (idle: 3 certified every 5s). Forming or adopting an AC immediately broadcasts that one topic so laggards can adoptCertificate. Do not send every pending topic in one POST (timeout drops the whole vote set). Do not re-index an already-installed PrevoteQC / AC. Duplicate votes (same slot + votesEqual, including EIP-712 signature / signer) and votes on an already-certified topic must not persist newchain-state.json.
On newchain engine load: register each chainNftId. Attach genesis BFT when archiveCertificatePending === true or an AC is already stored (certified topics stay in the map so gossip can leak them). Do not re-run indexRouteAndTip for every persisted record (700+ sync freezer writes). New POST /newchain/request still indexes tipStateRoot. Empty /newchain/bft gossip is skipped. HTTP maxConnections = 128.
Independent of NFT 42 Tendermint / bft-state.json. Not an L1 birth certificate or 30-day qualification.
| Path | Role |
|---|---|
POST /newchain/request |
Accept DleLabNewChainRequestV1; Mode A replay; require (Q_V=5/7) EIP-712 ArchiveValidatorQuorumAttest; persist newchain-state.json |
POST /newchain/bft |
Per-chain genesis gossip (DleLabNewChainBftMessageV1). Never /bft/message |
GET /newchain/chains / GET /newchain/queue |
List accepted lab chains |
/health |
newchainLabOnly, newchainNotL1Nft, newchainCount, newchainByClass, newchainValidatorQuorum: 5, newchainValidatorQuorumEip712, newchainHmacForgeable, newchainArchivePending, newchainArchiveCertified |
chainNftId = 1000 + keccak % 998_999_000, never 42. Accept catalogues tipStateRoot immediately. Do not catalogue valueHash as kind=ac until the real PrecommitQC (freezer append-only). G1 (enableBft: true) forms a per-chainNftId 4-of-5 AC via labChainObjectLocator. G2 (enableBft: false) does Mode A + (Q_V) only. New-chain AC must not update NFT 42 tip / eth_blockNumber. Legacy records without P6 fields stay register + tip only; old DleLabGenesisCertificateV1 stubs keep notArchiveCertificate: true.
Parallel to lab POST /newchain/request (notL1Nft: true). Uses a real local ERC-1155 tokenId from Hardhat/Anvil DLEChainRegistry1155V1. Do not upgrade lab requests. Do not wire CoNET mainnet 0x100DC8f0…. Do not use WaitingPool / POST /ondemand/hook as trade mempool.
| Path | Role |
|---|---|
POST /mockl1/register |
Accept MockL1ChainRegistrationV1; require bound view assignmentStatus=2 (BOUND); reject notL1Nft: true |
GET /mockl1/chains / GET /mockl1/queue |
List mock-L1 genesis records |
POST /trade/submit / POST /trade/cancel |
EventIngress order pool (EIP-191 signed sell/buy) |
POST /trade/scan / POST /trade/candidate |
Scanner proposes MockL1MatchCandidateV1 only |
POST /trade/check |
Archive legality: when MOCK_L1_RPC_URL + MOCK_L1_SETTLEMENT (or verifyL1Custody hook) are set, eth_call / hook custody — client l1EscrowCustody flags are ignored. Otherwise lab flags fallback. Then freeze-then-draw → propose TradeMatchCertificateV1 |
POST /trade/attest |
Committee signatures (lab keccak beacon; notProductionBeacon) |
POST /trade/list |
MVP round 5: seller escrows NFT via MockDleAuctionSettlement.list. Body { candidateHash, sellerPrivateKey } (lab session key; not stored). Must match sell maker. Hook submitL1ListTx or MOCK_L1_RPC_URL + MOCK_L1_SETTLEMENT → listMockL1Auction. Sets listTxHash / listError |
POST /trade/unlist |
MVP round 9: seller reclaim via MockDleAuctionSettlement.unlist. Body { candidateHash, sellerPrivateKey } (lab session key; not stored). Requires listTxHash; phase match_certified or settlement_failed. Hook submitL1UnlistTx or RPC → unlistMockL1Auction. On success: set unlistTxHash, clear listTxHash, clear list errors; do not change phase. WAL trade-unlist |
POST /trade/approve |
MVP round 6: buyer ERC-20 approve(settlement, amount) for quote. Body { candidateHash, buyerPrivateKey, amount? } (lab session key; not stored). Must match buy maker; default amount = clearingPrice. Hook submitL1ApproveTx or RPC → approveMockL1AuctionQuote (idempotent if allowance sufficient). Sets approveTxHash / approveError |
POST /trade/preflight |
MVP round 8: read-only settle readiness via evaluateSettlePreflight (phase + listTxHash + approveTxHash + optional RPC eth_call). Returns checks / fees / onChain. Does not mutate phase |
POST /trade/settle |
Mode A settlement states. MVP round 3: executeOnChain: true / MOCK_L1_SETTLE_ONCHAIN / submitL1SettlementTx hook → authority calls MockDleAuctionSettlement.settle (seller must have listed first). Success → settlement_submitted → settled with real settlementTxHash; failure → settlement_failed. MVP round 7/8: unless skipSettlePreflight: true, refuse on-chain settle via shared evaluateSettlePreflight without record listTxHash + approveTxHash (400, phase stays match_certified); when RPC+settlement set, also preflightMockL1AuctionSettle eth_call gate before executeOnChain. MVP round 9: outcome: 'failed' marks settlement_failed for lab recovery (then unlist) |
GET /trade/orders / /trade/matches / /trade/timeline |
Read-only status |
Fee: 1 bps of clearing price; 50% scanner / 50% committee. Mode A events 0x1302–0x1306 (MatchProposed → … → Settled / SettlementFailed); keep TradeOpened (0x1301) replay compatible.
/health may expose tradeRpcCustodyConfigured / tradeRpcCustodyMode (rpc | hook | clientFlags), tradeOnChainSettleConfigured / tradeOnChainSettleMode, Round 5 tradeListConfigured / tradeListMode (rpc | hook), Round 6 tradeApproveConfigured / tradeApproveMode (rpc | hook), and Round 9 tradeUnlistConfigured / tradeUnlistMode (rpc | hook). MVP round 2 (2026-08): Archive-side custody via shared/mockL1Custody.ts; in-process demo npm run mock-auction-demo. MVP round 3 (2026-08): on-chain settle via shared/mockL1Settle.ts; E2E npm run mock-auction-e2e (needs MOCK_L1_*); root one-shot npm run dle:mock-auction-e2e (hardhat node → deploy → e2e). MVP round 4 (2026-08): Explorer may POST /trade/settle (authority key stays on Archive); Settlement summary shows phase + settlementTxHash capsules. MVP round 5 (2026-08): Explorer may POST /trade/list with session seller key; Settlement summary shows listTxHash before settle. MVP round 6 (2026-08): Explorer may POST /trade/approve with session buyer key; Settlement summary shows approveTxHash; e2e/demo prefer Archive list + approve after attest. MVP round 7 (2026-08): settle preflight (listTxHash/approveTxHash + optional RPC eth_call); CLI list/approve + settle --executeOnChain; Explorer one-shot List → Approve → Settle. MVP round 8 (2026-08): read-only POST /trade/preflight + shared evaluateSettlePreflight; CLI preflight; Explorer Preflight CTA + fee split on Settlement summary. MVP round 9 (2026-08): POST /trade/unlist + MockDleAuctionSettlement.unlist; lab outcome: 'failed' → settlement_failed then unlist reclaim; Explorer Unlist / Mark failed / Cancel sell; CLI unlist. MVP round 10 (2026-08): e2e/demo MOCK_L1_E2E_MODE / MOCK_L1_DEMO_MODE = settle|recovery (recovery = list → fail → unlist + ownerOf assert); one-shot dle:mock-auction-e2e runs recovery then settle on the same deploy. MVP round 11 (2026-08): CI gates — DLE npm run mock-auction-ci + parent Hardhat test:dle / dle:mock-auction-e2e; Explorer recovery English copy + Mark failed → Unlist.
Live keep evidence (2026-08-16): pilot/evidence/conet-dle-p6-genesis-2026-08/p6-live-accept.json — trade chainNftId=326990096, 7/7 DleLabArchiveCertificateV1 in ~10s, NFT 42 eth_blockNumber stayed 0x1, liveGroupCount=2. Completing P6 is not 30-day qualification.
Do not expose Archive Certificate height as a growing tip. After AC, NFT 42 height is 0x1. Explorer Home no longer shows Tip height; it shows Clusters from these fields.
Whitepaper §5.2.0f. IdentityEligible ≠ SyncQualified. Catch-up SYNCING is not seating. A self-reported completed sync is only CLAIMED_SYNC. Other current-active archives of the target group MUST run ArchiveStateChallengeV1. The four inventory roots bind every hosted chainNftId. Laboratory openings (P9) then apply tip + history + DA + one hashIndex leaf on every hosted chainNftId (LAB_SYNC_OPEN_ALL_HOSTED_CHAINS; LAB_SYNC_MAX_HOSTED_CHAINS = 0). Locators are grouped once by chain — do not locators.filter per nft. P20 landed: wait hooks are not intra-group gossip (ondemandHookNotGossip / ondemandMustFanoutToEveryActiveArchive; ERR_ONDEMAND_HOOK_NOT_GOSSIP). Lab POST /ondemand/hook on TCP 27101 is not production DePIN gossip. P19 landed: on-demand freeze-then-bind (ondemandFreezeBeforeBeacon / ondemandLabBeaconAfterFreeze; not production CL RANDAO). P18 landed: P6 (Q_V) attests are EIP-712 ArchiveValidatorQuorumAttest (newchainValidatorQuorumEip712; newchainHmacForgeable: false). P17 landed: on-demand attests are EIP-712 ArchiveOnDemandAttest (hmacForgeable: false). The on-demand lab beacon was unchanged at P17 / P18. P16 landed: BFT AC votes are EIP-712 ArchiveBftVote (hmacForgeable: false). P15 landed: openings / challenge envelopes are EIP-712 ArchiveStateChallenge (samplesRoot; hmacForgeable: false). P13 landed: freeze hostedChainSetRoot / lastACRef / candidate set before binding a beacon; lab seed is keccak after freeze (or an injected CL view), not production (C_G) / live CL RANDAO. P14 landed: lab freezer hosted-set stays the lab opening; production (C_G) is L1 archiveGroupId only — not the 2249-chain freezer set, not a publicrpc/rpc1 scan. P12 landed: seating votes on POST /sync/vote / POST /sync/reject are EIP-712 ArchiveSyncQualificationCertificate (domain CoNET-DLE-Archive, chainId 224422). HMAC seating votes are rejected (ERR_SYNC_HMAC_CUTOVER). This is not OperatorDomain / L1 MembershipCheckpoint settle. Seating quorum is (Q_A=4/5) (already (>2/3); 3/5 forbidden). Candidate does not vote. Standbys do not vote. Offline / syncing members do not lower (Q_A).
A pass is seating-grade possession of the committed inventory, not a linear scan of every historical byte. Three layers: (1) byte-exact lastAC / membershipRoot / hashIndexRoot; (2) unpredictable local-freezer openings — hop-1 / historyProviders / remote parrot during the challenge is REJECTED (dle_getObject only); (3) stratified samples on every hosted chainNftId (lab P9). Post-seat availability remains UnavailableChallenge.
Laboratory honesty (2026-08-16):
| Signal | Meaning | Seating? |
|---|---|---|
GET /health 2xx |
Process reachable | No |
GET /liveness |
Tiny heartbeat | No |
HMAC lastQuorumOk |
Lab peer pulse | No |
Self-reported eth_syncing: false |
Claim only | No |
GET /sync/status seatingQualified |
Lab certificate after ArchiveStateChallengeV1 (votes = P12 EIP-712; challenge / opening = P15 EIP-712) |
Yes (lab only) |
seatingEip712 |
Lab seating votes use EIP-712 recoverAddress |
Not production OperatorDomain / L1 settle |
ArchiveStateChallengeV1 |
Implemented on this lab facade (P15 EIP-712 challenge / opening; P13 freeze-then-bind lab beacon; not live CL RANDAO) | Lab seating openings only |
challengeEip712 |
Lab challenge / opening use EIP-712 recoverAddress + samplesRoot |
Not production OperatorDomain / L1 settle |
bftEip712 |
Lab BFT AC votes use EIP-712 ArchiveBftVote |
Not a frozen L1 wrapper or corpus SSZ |
ondemandEip712 |
Lab on-demand attests use EIP-712 ArchiveOnDemandAttest |
Not 30-day qualification / production beacon |
newchainValidatorQuorumEip712 |
Lab new-chain (Q_V) uses EIP-712 ArchiveValidatorQuorumAttest |
Not production secp256k1 / 30-day qualification |
freezeBeforeBeacon / labBeaconAfterFreeze |
Challenge roots persist before seed; lab keccak after freeze or injected view | Not production (R^{\mathrm{sync}}_e) |
ondemandFreezeBeforeBeacon / ondemandLabBeaconAfterFreeze |
On-demand pool freeze persists before bind; lab keccak after freeze or injected view | Not production CL RANDAO |
ondemandHookNotGossip / ondemandMustFanoutToEveryActiveArchive |
Lab HTTP wait hook is not intra-group gossip; miner must POST every active archive | Not production DePIN gossip |
Do not advertise a catching-up host as seating-qualified. Green Explorer pill only when seatingQualified === true. Completing this spec MUST NOT start pilotStartedAt / PilotQualificationGate. This is not the 30-day gate.
FSM (normative; wired in runtime/src/archive/syncQualification/):
IdentityEligible → SYNCING → CLAIMED_SYNC → STATE_CHALLENGE → QUALIFIED | REJECTED
Zero-join lab evidence (2026-08-16): wipe only G1 fd-05 / fd-07 ~/dle-30d-lab/data (never geth/beacon). Keepers fd-01..04 must be QUALIFIED and share the same four roots before wipe. pilot/evidence/conet-dle-sync-join-2026-08/wipe.json + accept.json (ok:true, joiners QUALIFIED at leaf 4956). Not 30-day qualification.
HTTP: GET /sync/status /sync/inventory /sync/opening /sync/roster; POST /sync/challenge /sync/vote /sync/reject /sync/standby-ready. NFT 42 BFT, newchain genesis BFT, and on-demand ondemand.start() wait until this host is QUALIFIED, alignedQualifiedCount() ≥ SYNC_ACTIVE_COUNT (5 active seats, same four roots), no active is unseated, and LAB_HOLD_BFT_AFTER_BOOT_MS (30 min after process start) has elapsed. Seating votes still use (Q_A=4/5). Starting lab BFT / on-demand at 4/5 — or immediately after certificate restore on restart — keeps moving lastACRef / hashIndexRoot, stale-kills CLAIMED_SYNC challenges, and starves /liveness. A standby QUALIFIED must not fill (Q_A) or start BFT. Certificates from a split inventory must not start BFT. canVote counts only aligned active QUALIFIED toward (Q_A). Do not gossip /newchain/bft while SYNCING (it stringifies the 6 MB state and starves /liveness). Process restart of REJECTED is a new seating attempt (SYNCING). AC sample grade matches valueHash|membershipRoot|tipStateRoot, not the signer list.
Lab ops (must not starve HTTP):
inventoryNow()is cached bylocatorCount(append-only store).lastACRefOfis one pass over locators (O(L)), neverO(chains × locators).hashIndexproofs reuse the process Merkle cache (see Hash store hot path).start()must notawaitthe first tick; HTTP listen stays live.- Claim / catch-up compare the four inventory roots from
/sync/statusagainst the richest same-groupIdpeer (leaf count, then majorityhashIndexRoot, thenseatingQualified). AQUALIFIEDkeeper still merges extras from a richer same-group peer, at most once perSYNC_QUALIFIED_CATCHUP_MIN_MS(30s) — not every 2s tick (that starves/liveness).SYNCINGjoiners still catch up every tick. The seat certificate is not revoked. Full/sync/inventoryis fetched only to catch up. Foreign-group inventories are ignored. MissinggroupIdis not treated as same-group. - A voter whose roots do not match the candidate challenge skips during bootstrap. A
QUALIFIEDkeeper mayREJECTERR_SYNC_ROOT_MISMATCHonly when the candidateholdClaimed === true(explicitclaimSync()). Auto-claim peers self-demote when the richest moves. InboundROOT_MISMATCH/INDEX_PROOF/OBJECT_MISMATCH/SEED_MISMATCHwithoutholdClaimedreturns them toSYNCING(not terminal). InboundERR_SYNC_CHALLENGER_MISSING*is a no-op (voter miss ≠ candidate miss; do notREJECTEDand do not demote). A voter that gradesCHALLENGER_MISSINGmust notPOST /sync/reject.holdClaimed+OBJECT_MISMATCHis terminalREJECTED. Hop during the opening is always terminal.QUALIFIEDignores inbound reject.health()exposeshasUnseatedActive/alignedQualifiedCountwithout building a challenge. GET /sync/statusrebuildspendingChallengewhenever the live four roots drift or the persisted challenge does not cover the live hosted set (challengeCoversLiveOpening— old 8-cap shapes withoutopenedAllHostedChainsare rebuilt).GET /sync/openingis read-only (labCgOpeningView): no persist, no nonce bump.GET /healthmust not build samples; it only reportshostedChainCount+labCgOpening: 'all-hosted'. Answering a challenge whose roots no longer match live inventory returnsERR_SYNC_CHALLENGE_STALEinstead of a livehashIndexproof against a frozen root (that wasERR_SYNC_INDEX_PROOF).- Hash-index proofs grade against
challenge.hashIndexRoot, not the voter’s later live root. - A seating certificate is not a live root lock. Later freezer appends must not move
QUALIFIEDback toSYNCING. - Challenge answers echo the frozen challenge roots. AC samples match on
valueHash|membershipRoot|tipStateRoot|bodyCommitment(membership optional). Lab first-write may differ on signer set /domainId/acceptedAt; a differentvalueHashstill fails.DleLabGenesisCertificateV1stubs arenotArchiveCertificatebut are stored askind=acand must grade this way. - Lab catalogues tip first. A
kind=aclocator without a freezer AC must answer/grade againsttipStateRootat that height. If the challenger already holds an AC commitment, the candidate must open the AC object (P8b); tip-only answers fail even whentipStateRootmatches. Catch-up must notputLocatorfor a kind the donor slot does not hold, and must retry a peer that does. SYNC_CATCHUP_BATCH = 128withbeginBatch+ yield every 16 objects. Start probes wait onGET /liveness, never/health.- Lab (C_G) has thousands of hosted
chainNftIds. P9 landed (2026-08-17): seven G1/sync/openingallopened===hostedunique 2103,policy=all-hosted,sampleCount=2104(tip-heavy + onehashIndex). Evidencep9-opening.jsonok:trueat2026-08-17T06:35:54.834Z./healthhostedChainCountis rawchainNftIds.length(2104 after this keep).POST /sync/challengeusesSYNC_CHALLENGE_TIMEOUT_MS = 180s. Roster / status reads useSYNC_STATUS_TIMEOUT_MS = 30s. Each voter challenges one candidate per tick. Re-smoke:npm run lab:smoke-cg-open. Do not scrape/sync/statuson all seven just to prove opening. - Redeploy G1 keep-data only:
npm run lab:deploy-g1-keep(merges G1+G2planeDirectory). Do not uselab:deploy-m6just to refresh G1 (it restarts G2).lab:deploy-archive-keepstill dropsplaneDirectory. Remapped NYC keepers only:npm run lab:keep-remap-l2(keep-datafd-01/fd-03, then peer-refreshfd-02/fd-04/fd-05; never starts standby). - Bootstrap deadlock: candidate cannot vote. All 5 G1 actives must be up before wipe. Keepers fd-01..04 must already be
QUALIFIED. - Zero-join accept:
npm run lab:wipe-sync-jointhennpm run lab:accept-sync-join. Wipe set is wipe-safe joiners only (fd-05/fd-06/fd-07); never keepers. Default P8d pick is random 2 hosts and must include the only wipe-safe activefd-05(else cataloguing does not freeze). Override withLAB_SYNC_JOIN_WIPE_DOMAIN_IDS. Never geth/beacon. Evidence:pilot/evidence/conet-dle-sync-join-2026-08/.
Applies to cold start / restart of an assigned member, standby replacement from UnassignedPool, and new-group formation (empty (C_G) still challenges the witness historySnapshotRoot / empty-inventory commitment).
Status (2026-08-17): P7 seating facade accepted (leaf 4956). P8a–P8d landed. P8d random wipe fd-05 + fd-06 (not fd-07 this round; never keepers): wipe leaf 5194 → accept leaf 5194, leafGrew: false, stale: false, waitedMs≈128098, accept.json ok:true at 2026-08-17T06:18:41.255Z. Completing P8 MUST NOT start pilotStartedAt. This is not full (C_G), not EIP-712, not PilotQualificationGate.
P7 honesty gap that P8 closed: wipe keepers were at leaf 4951; accept landed at 4956. P8d re-ran with freeze on: leaf stayed 5194.
| Step | Status | Deliver |
|---|---|---|
| P8a | landed | inventoryFreeze.ts + indexLabHashObject refuse new locators (ERR_INVENTORY_FROZEN). Catch-up putLocator is not frozen. inventoryShouldFreeze() = hasUnseatedActive() or local/roster STATE_CHALLENGE. Do not freeze on pendingChallenge !== null (status() would fake-freeze QUALIFIED). Heartbeat/applyInventoryFreeze must use resolveInventoryFreezeState(auto, autoReason) — operator sticky (operator-inventory-freeze.json, reason operator) outranks auto-unfreeze. POST /sync/inventory-freeze accepts only {frozen:true}; live HTTP must not unfreeze. lab-cli stops BFT / newchain / on-demand while frozen; start() resets stopped. Health: bftProcessStarted vs bftDiskNetworked (networked: true is the disk/facade bit, not engine.start()). Fresh POST /newchain/request → 409; duplicates stay 200. |
| P8b | landed | sampleBodiesMatch: if the challenger already has an AC commitment, the candidate must open the AC object. Tip fallback only when expected has no AC body (genesis tip-first). |
| P8c | landed | health() does not call status() / ensurePendingChallenge. Slim syncQualification (phase / seating / four roots / nonce / no pendingChallenge). GET /sync/status still rebuilds the challenge for voters. Deploy probes stay on GET /liveness. |
| P8d | landed | Random wipe-safe pair fd-05 + fd-06. Join-window leafCount stayed 5194. No ERR_SYNC_CHALLENGE_STALE. Accept polls /health (not /sync/status) so QUALIFIED keepers are not challenge-rebuilt every scrape. |
Status (2026-08-17): P9 landed. lab:deploy-g1-keep LIVE_OK on seven G1 hosts (no wipe). lab:smoke-cg-open ok:true: unique hosted 2103 === opened 2103, sampleCount=2104 (hosted tips + one hashIndex), all seven openedAllHostedChains=true, policy=all-hosted. Post-keep /health: all seven QUALIFIED, inventoryFrozen=false, leaf 5225. Completing P9 MUST NOT start pilotStartedAt. This is not production (C_G), not EIP-712, not CL RANDAO, not PilotQualificationGate. Do not write this lab open into the whitepaper as production (C_G).
| Item | Lab P9 | Production §5.2.0f |
|---|---|---|
| Hosted set | every live unique chainNftId (2103) |
every chainNftId |
| Beacon | lab keccak | CL RANDAO |
| Vote | HMAC | EIP-712 |
| HTTP | GET /sync/opening (no persist) |
L1 / certificate |
After P9: consider malicious missing-object / permanent REJECTED. Then EIP-712 / CL RANDAO / 30-day gate. Do not wipe for a full-open join unless newly authorized.
Status (2026-08-17): P10 landed (engine + unit tests + live safety smoke). Whitepaper “missing object / mismatch” means the candidate freezer miss, not the voter’s. gradeChallenge still emits ERR_SYNC_OBJECT_MISMATCH:<kind>:<nft> (candidate null) vs ERR_SYNC_CHALLENGER_MISSING:<nft> (voter freezer miss). P10 honesty fix: voter skip + inbound no-op + reject() no-op for CHALLENGER_MISSING. Locator-only catalogues can still align the four roots; bodies decide the grade. Live keep (lab:deploy-g1-keep, no wipe) + lab:smoke-rejected-safety: seven G1 QUALIFIED, no active REJECTED (p10-rejected-safety.json ok:true, at=2026-08-17T06:55:13.921Z).
| Case | Engine |
|---|---|
Voter grades CHALLENGER_MISSING |
skip; no POST /sync/reject |
Inbound CHALLENGER_MISSING |
no-op (stay CLAIMED_SYNC / SYNCING) |
Auto-claim + OBJECT_MISMATCH |
demote SYNCING |
holdClaimed + OBJECT_MISMATCH |
terminal REJECTED |
| Hop during opening | always terminal |
Process restart of REJECTED |
new seating (SYNCING) |
Permanent REJECTED active at (Q_A=4) |
hasUnseatedActive=true → inventory stays frozen (unit-tested; do not wipe a live keeper to reproduce) |
Adversarial cases are unit tests only (runtime/test/sync-qualification.test.ts). Live: npm run lab:smoke-rejected-safety scrapes live G1 /health for all official seats, including remapped fd-01 / fd-03. Fail if any live active is REJECTED or live keepers fd-01..04 are not QUALIFIED. Remapped HostHatch NYC keepers 45.132.74.220 / 45.132.74.221 now run keep-data L2 (lab:keep-remap-l2, 2026-08-17). Evidence pilot/evidence/conet-dle-sync-join-2026-08/p10-rejected-safety.json. Never inject a missing object or claimSync against a live active. Never wipe keepers to “fix” REJECTED. Completing P10 MUST NOT start pilotStartedAt. Still HMAC, not production (C_G) / EIP-712 / CL RANDAO.
After P10: EIP-712 / CL RANDAO / 30-day gate. A full-open from-zero join needs a newly authorized empty datadir. Do not reuse the P8d wipe path (it still forces fd-05).
Seat identity is domainId + participantWallet, not the IP. Official roster stays 7. fd-01-ionos-45 stays live; only old IONOS 74.208.224.45 is excluded. Live sshHost is 45.132.74.220. fd-03-ionos-98 stays live; only old IONOS 198.251.77.98 is excluded. Live sshHost is 45.132.74.221. Keep-data L2 is running on both remapped hosts after authorized lab:keep-remap-l2 (2026-08-17). Do not use official-seven keep (lab:keep-p11-peers / lab:deploy-archive-keep / lab:deploy-g1-keep) just to refresh these two seats — that would also start standby fd-06 / fd-07. Peer refresh after remap is only fd-02 / fd-04 / fd-05.
MVP exclude: 74.208.224.45, 198.251.77.98, and leftover 216.225.193.174. Do not SSH, keep-deploy, extraPeers, on-demand hook, newchain-user, or nginx-upstream those IPs. loadLabHosts / runSsh / runScp must refuse them. agentConfigFor(fd-01) / fd-03 / fd-06 must succeed; peers must use 45.132.74.220, 45.132.74.221, and 70.35.205.77. Historical pilot/evidence/** may still name the old IPs — do not rewrite evidence. This exclude is not an L1 health-check delist.
| domainId | live sshHost | role |
|---|---|---|
fd-01-ionos-45 |
45.132.74.220 |
active keeper (L2 keep-data running) |
fd-02-ionos-189 |
216.225.197.189 |
active keeper |
fd-03-ionos-98 |
45.132.74.221 |
active keeper (L2 keep-data running) |
fd-04-hosthatch-tokyo1 |
167.254.243.38 |
active keeper |
fd-05-hosthatch-tokyo2 |
170.205.39.67 |
active wipe-safe |
fd-06-ionos-174 |
70.35.205.77 |
official standby (keep-data remapped 2026-08-18; DLE isolated in ~/dle-30d-lab) |
fd-07-ionos-207 |
212.227.242.207 |
standby |
Live keepers are fd-01 / fd-02 / fd-03 / fd-04. Do not auto-promote a standby. Do not wipe fd-01 or fd-03. Completing this remap MUST NOT start pilotStartedAt.
Explorer nginx (dle.conet.network, 2026-08-17 authorized): 45.132.74.220:27101, 45.132.74.221:27101, 167.254.243.38:27101, 170.205.39.67:27101. Do not proxy shared-beacon 216.225.197.189 or standby 212.227.242.207. Do not proxy leftover 216.225.193.174. Do not add Explorer host 70.35.205.77:27101 to upstream (fd-06 standby is now colocated there; standby is not an upstream). Do not stop nginx / dle-ondemand-clients / dle-newchain-user on .77 when starting the fd-06 DLE.
Authorized 2026-08-17: HostHatch HK1 167.104.98.104 as extra standby fd-08-hosthatch-hk1. Official G1 inventory stays exactly 7 (5 active + 2 standby). This joiner is not an 8th voting domain. membershipRoot still covers the five seeded actives. Never wipe fd-01..07. Never wipe fd-05 “to freeze the catalogue.”
Sequence (BFT/ondemand stay held 30 minutes after the keep restart):
npm run lab:probe-p11-joiner— greenfield SSH; no geth/beacon/validatornpm run lab:keep-p11-peers— official sevenkeepData:true; mergeextraPeers+ G1+G2planeDirectory- Confirm four keepers
QUALIFIED npm run lab:deploy-p11-joiner— empty~/dle-30d-lab/dataonly on167.104.98.104(START_ARCHIVEwipe). Do not use this to refresh binaries after P11 is seated.- Binary refresh after P11:
npm run lab:keep-p11-joiner— same extras / planeDirectory,keepData:true/START_ARCHIVE_KEEP_ALL. Never wipefd-08to ship P12–P20. npm run lab:accept-p11-join— joinerQUALIFIED+ keepers +fd-05stay seated; joiner/sync/openingpolicy=all-hostedandopened===hosted(>8)
Combined: npm run lab:p11-full-open-join. Evidence: pilot/evidence/conet-dle-sync-join-2026-08/p11-probe.json / p11-keep.json / p11-deploy.json / p11-accept.json / p11-opening.json. Do not reuse P8d wipe.json / accept.json.
P11 landed 2026-08-17T07:52:17.884Z: extra joiner QUALIFIED; official seven + fd-05 stayed QUALIFIED (never wiped); membershipRoot still 0xdeb200a9…e22241 (five seeded actives); joiner /sync/opening unique hosted 2249 === opened 2249, sampleCount=2250, policy=all-hosted; accept waitedMs=333122 (~5.6 min); joiner leaf 5673, official leaf 5674. Completing P11 MUST NOT start pilotStartedAt. P11 itself was still HMAC openings. Seating votes cut over in P12.
Status (2026-08-17): P12 landed (engine + unit tests). POST /sync/vote / POST /sync/reject accept only EIP-712 ArchiveSyncQualificationCertificate. HMAC seating votes return ERR_SYNC_HMAC_CUTOVER. Domain CoNET-DLE-Archive, version 1, chainId 224422, verifyingContract = deployed ArchiveCertificateVerifierV1 proxy 0xdA06E6d06eB2816795102B18171a079E3bEA948f (bind only; no L1 call, no verifier upgrade, no MembershipCheckpoint settle). Typed data does not include domainId; identity is recoverAddress + envelope domainId.
recoverAddress must equal labSeatingAddress(vote.domainId) and vote.signer. Voter ∈ current actives. membershipRoot must equal current membershipRootOf(actives) (formula unchanged). ACCEPT also binds groupId + four inventory roots to the challenge inventory (ERR_SYNC_ROOT_MISMATCH). REJECT uses the same struct with accept: false and does not require hosted / lastAC / hashIndex alignment. New quorum counts only EIP-712 ACCEPT votes at (Q_A=4/5).
Lab seating key is deterministic secp256k1: keccak256(utf8("dle.archive.lab.seating.operator.v1|" + domainId)). Honest flags: eip712: true, hmacForgeable: false, labDeterministicSeatingKey: true, notProductionOperatorKey: true, notL1Settled: true. Knowing the algorithm can still derive the key — P12 delivers real EIP-712 + recoverAddress bound to the active set, not OperatorDomain / L1 member keys.
Keep-only: disk HMAC certificates still restore QUALIFIED (restoreIfCertificateHolds does not re-verify old votes). Unsigned disk challenges are treated as no pending (P15 rebuilds). BFT AC votes later cut over in P16; on-demand attests later cut over in P17. status() / health().syncQualification may expose seatingEip712: true / challengeEip712: true — Explorer green pills stay seatingQualified === true only; do not paint those flags as production.
Archive tarball (P12+): npm run runtime:build must npm install --omit=dev --prefix runtime/dist/archive so ethers ships inside dle-archive-runtime.tgz. Remote unpack must not overwrite app/package.json with a stub that drops node_modules. Missing ethers crashes bft/mac.js after listen and takes the official seven down. Binary refresh: lab:keep-p11-peers then lab:keep-p11-joiner (keepData:true). Never lab:deploy-p11-joiner to ship this cutover.
Tests: runtime/test/sync-qualification.test.ts (HMAC cutover, recoverAddress bind, 4/5 vs 3/5). Completing P12 MUST NOT start pilotStartedAt. Do not change whitepaper production terms.
Status (2026-08-17): P13 landed (engine + unit tests). Whitepaper §5.2.0f production seed is (R^{\mathrm{sync}}_e = H(\texttt{dle.archive.sync.challenge.v1} \Vert \texttt{L1BeaconFinalizedRandomness}_e \Vert \ldots)). This gate does not change that formula. It only stops the lab from deriving seed from the same instant keccak as the freeze hex.
Sequence:
freezeChallengeRoots→ArchiveSyncFreezeV1(beaconBound: false,waitingForClBeacon: true). PersistshostedChainSetRoot/lastACRef/candidateSetRoot/freezeHex. Noseed/samples/labBeacon.- Engine
persist()spendingFreezebefore bind (pendingChallenge = nullat that write). probeFinalizedClRandomnessis read-only and does not HTTP-fetch.publicrpc/rpc1/rpc.conet.network→forbidden_el_rpc_as_cl. Default:no_finalized_cl_view(honest lab wait). Optional 32-byte hex (DLE_ARCHIVE_CL_FINALIZED_RANDOMNESSor injected) isinjected-cl-viewand stillnotClRandao/notProductionBeacon.bindChallengeBeaconthen binds seed. No finalized view →labSyncBeaconAfterFreeze(freezeHex, revealSalt)withrevealSalt = postFreezeRevealSalt(...)(engine:postFreezeRevealMaterialorDate.now()). InstantlabSyncBeacon(freezeHex)is contrast only and must not equal the boundlabBeacon.
Honest flags on the bound challenge / status() / health().syncQualification: freezeBeforeBeacon, labBeaconAfterFreeze, notProductionBeacon, publicrpcNotClRandao, notClRandao. Challenge / opening cut over in P15 (hmacForgeable: false). BFT AC votes later cut over in P16; on-demand attests later cut over in P17; the on-demand lab beacon is unchanged. health() still does not rebuild pendingChallenge (P8c). Explorer green pills stay seatingQualified === true only — do not paint labBeaconAfterFreeze as production CL RANDAO.
Tests: runtime/test/sync-qualification.test.ts (freeze has no seed; post-freeze ≠ keccak(freezeHex); publicrpc/rpc1 rejected; engine persist-then-bind). Completing P13 MUST NOT start pilotStartedAt. Do not change whitepaper production terms.
Status (2026-08-17): P14 landed (engine + unit tests). Whitepaper §5.2.0f production (C_G) is the L1 archiveGroupId ordered chainNftId set ∪ {lastAC, membershipRoot, hashIndexRoot}. This gate does not change that formula and does not write the lab freezer 2249-chain set into the whitepaper.
- Laboratory seating openings stay on the freezer hosted-set (
labCgOpeningView; P9/P11opened===hostedunchanged). Live seven-domain seating is not reduced to an L1 small-set. probeProductionCgis read-only and does not HTTP-scanpublicrpc/rpc1/rpc.conet.network(forbidden_el_rpc_as_production_cg). Default:no_l1_archive_group_id_view(honest wait).- Optional injected small-set (
DLE_ARCHIVE_PRODUCTION_CG_JSONorproductionCgProbe) is stillnotLiveL1Scan/notProductionCg.groupStorageKeyis the L1 uint storage key (notUserVisibleGroupId). If the injectedchainNftIdsequal a non-empty lab hosted-set →lab_hosted_set_is_not_production_cg. GET /sync/openingremainsDleLabCgOpeningV1on the freezer set (notProductionCg). It may attachproductionCgsmoke (DleProductionCgOpeningSmokeV1) that opens only the injected L1 set. Smoke never substitutes the lab freezer opening.health()attachesproductionCgHealthViewonly — no samples (P8c). Flags:labHostedSetNotProductionCg,publicrpcNotProductionCg,productionCgAvailable.- Explorer green pills stay
seatingQualified === trueonly. Do not paint lab 2249 /labCgOpening/productionCgAvailable/ an injected small-set as live L1 production (C_G). - BFT AC votes later cut over in P16; on-demand attests later cut over in P17; the on-demand lab beacon is unchanged.
Tests: runtime/test/sync-qualification.test.ts (publicrpc/rpc1 rejected; lab hosted-set rejected as (C_G); injected small-set smoke; engine keeps freezer opening). Completing P14 MUST NOT start pilotStartedAt. Do not change whitepaper production terms.
Status (2026-08-17): P15 landed (engine + unit tests). Same domain as P12: CoNET-DLE-Archive / 1 / chainId 224422 / verifyingContract 0xdA06E6d06eB2816795102B18171a079E3bEA948f (bind only; no L1 call). Typed data is ArchiveStateChallenge. Do not put 2250 samples in the EIP-712 array — bind samplesRoot instead.
samplesRoot = keccak256Utf8(samples.map(s => `${chainNftId}|${height}|${kind}|${hash}`).join(';'))
challengeHash = keccak256Utf8("dle.archive.sync.challenge.hash.v1|" + seed + "|" + candidate + "|" + nonce + "|" + hashIndexRoot)
challengeHashOf formula is unchanged. bindChallengeBeacon / buildChallenge sign with the challenger lab seating key. recoverAddress must equal labSeatingAddress(challenger) and challenge.signer. HMAC / unsigned / hmacForgeable===true / missing signature → ERR_SYNC_CHALLENGE_HMAC_CUTOVER. Bad recover / samplesRoot / stored challengeHash → ERR_SYNC_CHALLENGE_SIG. challengeSamplesMatchSeed (recompute via buildStratifiedSamples + existing sampleKey) fail → ERR_SYNC_CHALLENGE_SAMPLES. Do not require challenger === candidate. Do not sign answers. Unsigned disk pendingChallenge is treated as no pending.
GET /sync/opening / status() / health().syncQualification: eip712: true, hmacForgeable: false, challengeEip712: true. health() still does not rebuild pendingChallenge (P8c). Explorer green pills stay seatingQualified === true only. BFT AC votes later cut over in P16; on-demand attests later cut over in P17.
Tests: runtime/test/sync-qualification.test.ts (HMAC cutover; tampered samples SIG; resigned miss-seed SAMPLES; engine claimSync signed). Completing P15 MUST NOT start pilotStartedAt. Do not change whitepaper production terms.
Status (2026-08-17): P16 landed (engine + unit tests). Same domain as P12 / P15: CoNET-DLE-Archive / 1 / chainId 224422 / verifyingContract 0xdA06E6d06eB2816795102B18171a079E3bEA948f (bind only; no L1 call). Typed data is ArchiveBftVote. Typed data does not include domainId; identity is recoverAddress + envelope domainId.
ArchiveBftVote: valueHash, height, round, step, membershipRoot, prevoteQCRef
Reuses the P12 lab seating key keccak256(utf8("dle.archive.lab.seating.operator.v1|" + domainId)). Do not invent a second BFT key. recoverAddress must equal labSeatingAddress(domainId) and vote.signer. HMAC / unsigned / hmacForgeable===true / eip712!==true / missing signature → ERR_BFT_HMAC_CUTOVER. Bad recover / signer bind → ERR_BFT_VOTE_SIG. Active set, membershipRoot, step, and double-sign (ERR_WAL_DOUBLE_SIGN) are unchanged. Do not change membershipRootOf / topicQcRef / Mode A valueHash.
Keep-only: disk HMAC certificates still restore tip finality (parseCertificate does not re-verify old votes). HMAC votes loaded from disk are skipped by acceptVote (prevoteCount may be 0). New votes must be EIP-712.
status() / health(): eip712: true, hmacForgeable: false, bftEip712: true. Explorer green pills stay seatingQualified === true only. Do not paint bftEip712 as a frozen L1 wrapper or corpus SSZ. At P16 landing this gate did not replace on-demand HMAC, P6 (Q_V) HMAC (validatorQuorum.ts still hmacForgeable: true), wipe, promote fd-08, or start pilotStartedAt. On-demand attests later cut over in P17.
Tests: runtime/test/bft-tendermint.test.ts (HMAC cutover; recoverAddress bind + tampered SIG; keep-only disk HMAC certificate). Full npm run runtime:test 125/125 at P16 landing; later 128/128 after P17. Completing P16 MUST NOT start pilotStartedAt. Do not change whitepaper production terms. Later P21 added hashIndexRoot after membershipRoot and did change topicQcRef encoding — see §P21.
Status (2026-08-17): P17 landed (engine + unit tests). Same domain as P12 / P15 / P16: CoNET-DLE-Archive / 1 / chainId 224422 / verifyingContract 0xdA06E6d06eB2816795102B18171a079E3bEA948f (bind only; no L1 call). Typed data is ArchiveOnDemandAttest. Typed data does not include domainId; identity is recoverAddress + envelope domainId.
ArchiveOnDemandAttest: poolRoot, epoch, shardId, roulette
Reuses the P12 lab seating key keccak256(utf8("dle.archive.lab.seating.operator.v1|" + domainId)). Do not invent a second on-demand key. recoverAddress must equal labSeatingAddress(domainId) and attest.signer. HMAC / unsigned / hmacForgeable===true / eip712!==true / missing signature → ERR_ONDEMAND_HMAC_CUTOVER. Bad recover / signer bind → ERR_ONDEMAND_ATTEST_SIG. Parse without mac and without signature → ERR_INVALID_ATTEST.
Keep-only: disk HMAC attests still restore via verifyLabPoolAttestForRestore and may still count toward endorsed. New ingest / adoptAttest must be EIP-712.
health() / SelectionLog: eip712: true, hmacForgeable: false, ondemandEip712: true. Explorer green pills stay seatingQualified === true only. Do not paint ondemandEip712 / endorsed as 30-day qualification or a production beacon. Did not replace the on-demand lab beacon (dle.lab.beacon.afterFreeze.v1 / keccak after freeze), P6 (Q_V) HMAC (validatorQuorum.ts still hmacForgeable: true), gossip wait-hook, wipe, promote fd-08, or start pilotStartedAt. SelectionLog is not an Archive Certificate and does not change Mode A valueHash.
Tests: runtime/test/ondemand-eip712.test.ts (HMAC cutover; recoverAddress bind + tampered SIG; keep-only disk HMAC attests). Full npm run runtime:test 128/128 at P17 landing; later 131/131 after P18. Completing P17 MUST NOT start pilotStartedAt. Do not change whitepaper production terms.
Status (2026-08-17): P18 landed (engine + unit tests). Same domain as P12 / P15 / P16 / P17: CoNET-DLE-Archive / 1 / chainId 224422 / verifyingContract 0xdA06E6d06eB2816795102B18171a079E3bEA948f (bind only; no L1 call). Typed data is ArchiveValidatorQuorumAttest. Typed data does not include validatorId / domainId; identity is recoverAddress + envelope validatorId.
ArchiveValidatorQuorumAttest: requestId, valueHash, tipStateRoot, bodyCommitment
Reuses the P12 lab seating key keccak256(utf8("dle.archive.lab.seating.operator.v1|" + validatorId)) where validatorId is the request-derived committee hex (labValidatorId(requestId, index)). Do not invent dle.archive.lab.validator.operator.v1. Do not change the P6 committee formula. recoverAddress must equal labSeatingAddress(validatorId) and attest.signer. HMAC / unsigned / hmacForgeable===true / eip712!==true / missing signature → ERR_VALIDATOR_QUORUM_HMAC_CUTOVER. Bad recover / signer bind → ERR_VALIDATOR_QUORUM_SIG. Committee not derived from requestId still fails. Fewer than 5 valid EIP-712 attests fails (needs 5).
Keep-only: disk HMAC (Q_V) still restore via verifyLabValidatorQuorumForRestore / parseLabValidatorQuorum. New POST /newchain/request / verifyLabValidatorQuorum must be EIP-712.
health(): newchainValidatorQuorumEip712: true, newchainHmacForgeable: false (namespaced so they do not collide with sync / BFT / on-demand hmacForgeable). Explorer green pills stay seatingQualified === true only. Do not paint newchainValidatorQuorumEip712 as production secp256k1 / 30-day qualification. Did not replace the on-demand lab beacon (dle.lab.beacon.afterFreeze.v1 / keccak after freeze), gossip wait-hook, seating / challenge / BFT / on-demand attest, membershipRootOf / Mode A valueHash, chainNftId, NFT 42 tip, or new-chain-user HTTP (still only schema === 'DleLabValidatorQuorumV1').
Tests: runtime/test/validator-quorum.test.ts + runtime/test/validator-quorum-eip712.test.ts (HMAC cutover; recoverAddress bind + tampered SIG; keep-only disk HMAC (Q_V)). Full npm run runtime:test 131/131 at P18 landing; later 134/134 after P19. Completing P18 MUST NOT start pilotStartedAt. Do not change whitepaper production terms.
Status (2026-08-17): P19 landed (engine + unit tests). Aligns on-demand with P13: freeze first, then bind a lab beacon. Whitepaper §7.8.1 production seed stays live CL RANDAO. This gate does not change that formula and does not replace P17 ArchiveOnDemandAttest or P18 (Q_V).
Sequence:
applyFreeze→ persistpoolRoot+ondemandFreezeHex(dle.lab.ondemand.freeze.v1). No beacon / roulette / committee at that write.bindBeaconthen binds:options.beacon→options-beaconprobe.available(probeFinalizedClRandomness; no HTTP topublicrpc/rpc1) →injected-cl-view- default honest-wait:
revealSalt = ondemandHonestWaitReveal(freezeHex)→labOnDemandBeaconAfterFreeze(freezeHex, revealSalt)→lab-after-freeze - explicit
options.postFreezeRevealMaterialonly →ondemandPostFreezeRevealSalt(Date.now() salt; seven independent freezes would diverge)
- Instant
labBeaconAfterFreeze(poolRoot)is contrast only and must not equal a new freeze's bound beacon. drawCommitteestill accepts an optional beacon (instant keccak default for pure-function contrast / old “same (R_e)” tests). New engine freezes must pass the bound beacon.
Keep-only: disk SelectionLog with instant-keccak beacon still restores endorsed (legacy-instant). computeDraw must use selection.beacon.
health() (namespaced; do not collide with sync labBeaconAfterFreeze): ondemandFreezeBeforeBeacon, ondemandLabBeaconAfterFreeze, ondemandNotProductionBeacon, ondemandPublicrpcNotClRandao, ondemandBeaconSource. Explorer green pills stay seatingQualified === true only. Do not paint ondemandLabBeaconAfterFreeze as production CL RANDAO.
Did not replace P17 attest / P18 (Q_V) / seating / challenge / BFT, membershipRootOf / Mode A valueHash, gossip wait-hook, 7+2 formula, MIN_WAIT_POOL, anti-hoard, or start pilotStartedAt.
Tests: runtime/test/ondemand-beacon-after-freeze.test.ts (new freeze ≠ instant keccak; injected view; publicrpc rejected; keep-only legacy-instant). Full npm run runtime:test 134/134 at P19 landing; later 140/140 after P20. Completing P19 MUST NOT start pilotStartedAt. Do not change whitepaper production terms.
Status (2026-08-17): P20 landed (engine + daemon + unit tests). Whitepaper §5.4 / §8.1: a wait hook must not assume same-group archives already gossiped the miner. miner / daemon must POST the same hook to every active archive in that group. One accept ≠ group waiting-pool consistency. Lab POST /ondemand/hook on TCP 27101 is not production DePIN gossip. Explorer nginx must not expose POST /ondemand/hook.
This is not “turn the HTTP hook into production DePIN gossip”. Production gossip (IdentityEligible / OperatorDomain / (U_e)) stays parked.
Engine gossip() still forwards attests + selection only — never miners. P20 is an honesty gate:
ingest(): ifbody.miners/body.hooks/body.hookexist → reject the whole packetERR_ONDEMAND_HOOK_NOT_GOSSIP(no miner merge, no attest processing).hook()200 / 409:notGossiped,mustFanoutToEveryActiveArchive,notProductionDepinGossip; queued note =LAB_HOOK_QUEUED_NOTE.pool():hookNotGossip/mustFanoutToEveryActiveArchive/notProductionDepinGossip.health():ondemandHookNotGossip,ondemandMustFanoutToEveryActiveArchive,ondemandNotProductionDepinGossip.
Daemon OnDemandWaitSession required flags: hookNotGossip, mustFanoutToEveryActiveArchive, notProductionDepinGossip, singleArchiveAcceptNotGroupPool, fanoutComplete.
submitWaitHook:fanoutComplete: false; note =LAB_HOOK_SINGLE_ARCHIVE_NOTEsubmitWaitHookToArchives:fanoutComplete = allQueued; incomplete →LAB_HOOK_FANOUT_INCOMPLETE_NOTE
Explorer green pills stay seatingQualified === true only. Do not paint ondemandHookNotGossip as production DePIN gossip.
Did not replace P12–P19 tickets / beacons / (Q_V), membershipRootOf / Mode A valueHash, chainNftId, NFT 42 tip, 7+2 formula, MIN_WAIT_POOL, anti-hoard, attest / selection HTTP gossip (/ondemand/message), explorer nginx, or start production DePIN gossip / pilotStartedAt.
Tests: runtime/test/ondemand-hook-not-gossip.test.ts (health + hook flags; ingest miners/hooks rejected; A hook does not gossip to B; extra-miner freeze mismatch; single-archive fanoutComplete === false; incomplete fan-out rejected). Full npm run runtime:test 140/140. Completing P20 MUST NOT start pilotStartedAt. Do not change whitepaper production terms.
Status (2026-08-17): P21 landed (engine + unit tests). Bind the live / bound hashIndexRoot into lab BFT vote / QC / AC typed data and topicQcRef. P16 typed data omitted hashIndexRoot and forbade changing topicQcRef. This gate does add hashIndexRoot after membershipRoot (before prevoteQCRef) and does change topicQcRef encoding.
ArchiveBftVote: valueHash, height, round, step, membershipRoot, hashIndexRoot, prevoteQCRef
boundHashIndexRootOf(votes, liveRoot): first vote’s hashIndexRoot, else live tree root (hashIndexRootOf(store.hash.listLocators())). Empty-store live root is emptyHashIndexRoot(), not ZERO32. Disk load / ensureTopic omit expectedHashIndexRoot. New addOwnVote / ingest acceptVote pass expectedHashIndexRoot: boundHashIndexRoot(). Incoming QC / adoptCertificate require hashIndexRoot === boundHashIndexRoot().
Tree honesty: hashIndexRootView / proveHashIndex / JSON-RPC dle_getHashIndexRoot / dle_proveHash keep committedInAc: false. Do not flip the tree field when AC binds a root. Overlay hashIndexCommittedInAc(certificate) is true only when AC has a non-zero hashIndexRoot. emptyHashIndexRoot() is domain-separated and ≠ ZERO32, so a freshly certified empty store can have overlay true. Disk HMAC AC missing the field parses as ZERO32 → overlay false. Health / Explorer may show the overlay. Do not paint overlay as production AC commitment or the 30-day gate.
Keep-only: skip QC / AC rebuild if a certificate already exists; keep disk QC if rebuilt qcRef mismatches. HMAC / bad sig still win over ERR_BFT_HASH_INDEX_ROOT. Pre-P21 EIP-712 votes signed without hashIndexRoot fail verify after this gate (intended).
Did not change membershipRootOf / Mode A valueHash / daemon / on-demand / production AC commitment formula. Explorer green seating pills stay seatingQualified === true only.
Tests: runtime/test/bft-tendermint.test.ts + runtime/test/hash-index-tree.test.ts. Full npm run runtime:test 148/148 at P21 landing; later 153/153 after P22. Completing P21 MUST NOT start pilotStartedAt. Do not change whitepaper production terms.
Status (2026-08-17): P22 landed (engine + unit tests). Official standbys (fd-06 / fd-07) sign EIP-712 ArchiveStandbyReadiness after QUALIFIED. Extra fd-08 / fd-08-hosthatch-hk1 may ingest but does not count toward OFFICIAL_STANDBY_COUNT = 2. This is not production OperatorDomain / secp256k1 / the 30-day gate.
ArchiveStandbyReadiness: groupId, hostedChainSetRoot, lastACRef, membershipRoot, hashIndexRoot, ready
Typed data does not include domainId. Identity is recoverAddress + envelope domainId. Existing groupId is string, not bytes32. Follow the per-type hash/sign/recover triplet — no generic hashArchiveTypedData. recoverAddress must equal labSeatingAddress(domainId) and envelope signer. HMAC / unsigned envelopes ERR_SYNC_STANDBY_HMAC_CUTOVER. Bad sig ERR_SYNC_STANDBY_SIG. Role ≠ standby ERR_SYNC_STANDBY_ROLE. Four inventory roots + groupId must match local inventory (ERR_SYNC_STANDBY_ROOT).
HTTP: POST /sync/standby-ready (lab-cli and isolated node.ts). Official standby auto-signs and gossips after QUALIFIED on lab-cli. Persist standbyReady map; REJECTED reset keeps the map. Extra fd-08 is ingest-only (extraStandbyReadyDoesNotCount).
New-chain accept (lab-cli and isolated node.ts via syncHolder): if officialStandbysReady() === false → 409 ERR_NEWCHAIN_STANDBY_NOT_READY. P24 landed: node.ts passes the same callback into createNewChainEngine. Isolated node.ts does not sync.start() (no seating tick against dummy peer URLs) and does not apply inventory freeze. sync.health().inventoryFrozen from hasUnseatedActive must not block accept.
Health overlays: standbyReadyEip712, officialStandbyReadyCount, officialStandbysReady, extraStandbyReadyDoesNotCount. Newchain health: newchainOfficialStandbysReady, newchainStandbyReadyEip712. Do not paint these as production or as seating. Explorer green pills stay seatingQualified === true only. Do not change archiveSeating.ts.
Did not change seating votes / challenge / BFT / on-demand / (Q_V), membershipRootOf / Mode A valueHash, Home green-pill logic, or start pilotStartedAt.
Tests: runtime/test/sync-standby-ready.test.ts + newchain.test.ts gate + sync-qualification.test.ts fakeFetch. Full npm run runtime:test was 153/153 at P22; 154/154 after P24. Completing P22 MUST NOT start pilotStartedAt. Do not change whitepaper production terms.
P23 live evidence (2026-08-17): npm run lab:deploy-g1-keep put the P12–P22 binary on official G1. 6/7 LIVE_OK. Official standby fd-06-ionos-174 STARTED after hung-lab-cli SIGKILL but missed LIVE_OK; a second keep-data-only retry still missed liveness (STARTED=652654). Process can sit at ~101% CPU with /liveness timeout — not a stable official standby host. fd-01 POST /newchain/request walked 409 ERR_NEWCHAIN_STANDBY_NOT_READY (count=0 at 2026-08-17T23:13:41.428Z) → 200 accept (requestId 0xe8229f1635d681d5b48430b5cd4a09e2c7787d4e4338a60c512ce9ab9d81b472, count=2 at 2026-08-17T23:18:22.095Z). Overlay fields live under health.syncQualification on the six LIVE_OK hosts. officialStandbysReady is not a durable seven-host true (roots drift drops the count). Evidence: pilot/evidence/conet-dle-p23-live-2026-08/. Extra fd-08 unofficial. G2 BFT/ondemand off. Not 7/7 healthy / not 30-day / not production.
P24 isolated node.ts gate (2026-08-17): startArchiveNode now constructs createSyncQualificationEngine (role-only peers fd-06 / fd-07 official + fd-08 extra, url: '') and passes officialStandbysReady: () => syncHolder.current?.officialStandbysReady() === true into createNewChainEngine. HTTP: POST /sync/standby-ready, GET /sync/status, GET /sync/inventory. Extra fd-08 still does not count. Isolated node does not sync.start() and does not call applyInventoryFreeze. Test: runtime/test/node-standby-gate.test.ts. Full npm run runtime:test 154/154. Completing P24 MUST NOT start pilotStartedAt. Not 7/7 healthy / not 30-day / not production.
P25 Explorer overlays (2026-08-17, historical): Explorer Certificates + Home show non-green read-only chips for officialStandbysReady / hashIndexCommittedInAc (explorer/src/lib/labOverlays.ts). Green seating pills stay seatingQualified === true only (archiveSeating.ts unchanged). Missing overlay fields omit the chip (trusted-fetch). Tests at P25: npm run explorer:test 8/8. P25 public SPA (2026-08-18T00:18:49Z, historical): index-U1o9ul_I.js replaced stale index-DaEv6psZ.js. Live /health at that publish still officialStandbysReady=false (count=1), hashIndexCommittedInAc=false, seatingQualified=true, pilotStartedAt=null — chips were warn/neutral, not green ready. P25 seating-copy honesty (same gate, not P26): Home / archive-detail seating copy is lab EIP-712, never HMAC. Completing P25 MUST NOT start pilotStartedAt. Current public SPA is the later clock overlay index-C8IdTq4H.js (2026-08-18T10:15:00Z; explorer:test 10/10). Not 7/7 healthy / not 30-day / not production.
fd-06 remapped (2026-08-18, not P26): Operator authorized 70.35.205.77 for the official standby seat fd-06-ionos-174 (domainId + wallet stay). Keep-data copied from leftover 216.225.193.174; leftover IP is MVP-excluded. DLE lives only in ~/dle-30d-lab on TCP 27101. This host already runs Explorer nginx, dle-ondemand-clients, and dle-newchain-user — do not stop those, and do not add .77:27101 to Explorer nginx upstream. Use npm run lab:keep-remap-fd06 (keep-data, onlyDomainIds fd-06 + P11 extras). Do not run lab:deploy-g1-keep / lab:keep-remap-l2 just to refresh fd-06. Do not restart leftover L1. Do not count fd-08. Completing this remap MUST NOT start pilotStartedAt. Scrape 2026-08-18T00:52Z: honest 7/7 GET :27101/liveness LIVE_OK (fd-06 on 70.35.205.77; leftover .174 timeout). fd-01 /health still officialStandbysReady=false (count=1; ready is not sticky). Four roots match fd-06 (leafCount=9141). Fan-out 2026-08-18T00:57Z: keep-data left fd-06/fd-07 with a locally matching ArchiveStandbyReadiness; gossipOwnStandbyReady early-returned and did not POST. Forwarding the current lab EIP-712 envelopes to official :27101/sync/standby-ready returned 200 on all 7. At 00:57:49Z six official seats were officialStandbysReady=true (count=2; fd-06 still count=1). Unfrozen inventory then grew (leafCount 9141→9145); at 00:59:38Z all seven were ready=false again. pilotStartedAt=null. Engine now reuses a matching local envelope but still fan-outs every QUALIFIED tick (test in runtime/test/sync-standby-ready.test.ts). Live hosts still run the old early-return until the next authorized standby keep — do not run lab:deploy-g1-keep just to pick this up. Peer-stale 2026-08-18T01:03Z: fd-01 / fd-07 config.json still pointed fd-06 at leftover .174. Peer-refresh 2026-08-18T01:10Z: npm run lab:keep-refresh-fd06-peers ok (STOP_LAB_ONLY; no leftover EL/CL). Official six + fd-06 keep-restarted; every official config.json now uses http://70.35.205.77:27101 for fd-06 (.174 count=0). Honest 7/7 LIVE_OK at 01:11Z (4/7 ready=true count=2). At 01:12:01Z seven seats flickered ready=true count=2 (fd-05 /liveness timed out that pass; /sync/status 200). At 01:12:24Z honest 7/7 LIVE_OK again; only fd-03 + fd-06 still ready=true (roots already drifted 0x57a38d… → 0x77ae8a…). .77 coexistence held (nginx 9; :80/:443/:27101; lab pid 208858). pilotStartedAt=null. Ready is not sticky while inventory is unfrozen. 7/7 liveness + a flicker of ready=true is not the 30-day gate. Split 2026-08-18T07:05Z / 07:08Z: honest 7/7 LIVE_OK; every official seat officialStandbysReady=false (count 0 or 1). The two official standbys sit on different four roots (camp A fd-01/03/06 leaf 9640→9645; camp B fd-02/04/05/07 leaf 9641→9646). QUALIFIED catch-up (30s) cannot close a +1 leaf race while unfrozen cataloguing / lab BFT still grow. Envelope POST cannot make count=2. Flicker 2026-08-18T08:01:32Z: 6/7 ready=true count=2 with both official standbys on leaf 9712; 21s later fd-07 already on 9713 and ready dropped to 2/7. Do not keep-restart to chase ready. Operator authorized inventory freeze (2026-08-18, not P26): npm run lab:keep-freeze-inventory — STOP_LAB_ONLY official 7 + extra fd-08 first, keep-data start with ~/dle-30d-lab/data/operator-inventory-freeze.json, then POST /sync/inventory-freeze {frozen:true} only. Engine + tests: runtime:test 157/157; pilot 14/14; explorer:test 8/8. Live freeze 2026-08-18T08:35Z: lab:keep-freeze-inventory ok. Official 7 + extra fd-08 inventoryFrozen=true reason=operator. Honest 7/7 LIVE_OK; officialStandbysReady=true count=2; leaf 9750 four roots aligned (lastACRef 0xbc4d0655…); bftProcessStarted=false; pilotStartedAt=null. Confirm scrape ~08:36Z same (fd-04 /sync/status filled after the first pass). Evidence: pilot/evidence/conet-dle-p23-live-2026-08/operator-inventory-freeze.json (do not rewrite fd06-remap-70-35-205-77.json). Ready + freeze is not the 30-day clock. Operator authorized clock start (2026-08-18, not P26, not qualification): npm run lab:start-pilot-clock — keep-data; preserve freeze; POST /sync/pilot-clock {start:true, warmupStartedAt, pilotStartedAt}. Persist ~/dle-30d-lab/data/operator-pilot-clock.json. /health may show warmupStartedAt / pilotStartedAt / clockIsNotQualification. pilotQualified stays false. Counters stay 0. Explorer nginx must not expose that POST. Green pills stay seatingQualified === true only. Do not invent P26. Live clock 2026-08-18T09:53:58.092Z: official 7 + extra fd-08 warmupStartedAt=2026-08-14T17:10:16.786Z pilotStartedAt=2026-08-18T09:53:58.092Z warmupComplete=true pilotRunning=true pilotQualified=false clockIsNotQualification=true; honest 7/7 LIVE_OK; freeze kept; officialStandbysReady=true count=2; leaf 9750; counters still 0. Evidence: pilot/evidence/conet-dle-p23-live-2026-08/operator-pilot-clock.json + pilot/evidence/conet-dle-30d-lab-2026-08/gate.json. Engine + tests: runtime:test 159/159; pilot 19/19; explorer:test 10/10. Clock is not 30-day qualification. Explorer clock overlay (2026-08-18, not P26): Certificates + Home show a non-green pilotClockPill from /health.pilotRunning (else derive from pilotStartedAt). Running → warn 30-day clock running (not qualified); explicit not-started → neutral 30-day clock not started; missing fields omit the chip. pilotQualified: true is ignored. Never tone: 'ok'. Green seating pills stay seatingQualified === true only. Tests: npm run explorer:test 10/10. Public SPA published (2026-08-18T10:15:00Z): https://dle.conet.network/ now serves index-C8IdTq4H.js (replaced pre-clock index-U1o9ul_I.js). Live /health: pilotRunning=true, pilotStartedAt=2026-08-18T09:53:58.092Z, pilotQualified=false, clockIsNotQualification=true, officialStandbysReady=true count=2. Clock chip is warn, not green qualified. Explorer-host POST /sync/pilot-clock returns 405 (not proxied). Do not invent P26.
P11 closed the seating control plane. P12 / P13 / P14 / P15 / P16 / P17 / P18 / P19 / P20 / P21 / P22 landed (engine + tests). Snapshot: src/canvas/dle-mvp-p12-milestones-2026-08.md. Completing any of these MUST NOT start pilotStartedAt. Do not change whitepaper production terms in this track.
| Gate | Goal | Not |
|---|---|---|
| P12 | landed (engine + tests). Seating votes = EIP-712 ArchiveSyncQualificationCertificate (domain CoNET-DLE-Archive, chainId 224422). After cutover, reject HMAC seating votes. keep-only. recoverAddress must land on current-active membershipRoot. |
L1 MembershipCheckpoint settle; replacing BFT AC HMAC or on-demand HMAC in the same gate; wipe; pilotStartedAt |
| P13 | landed (engine + tests). Freeze hostedChainSetRoot / lastACRef / candidate set before the bound beacon. Bind to a finalized CL view if one is injected; otherwise an honest lab-labeled wait (labSyncBeaconAfterFreeze). |
Reading publicrpc / rpc1 as live CL RANDAO; claiming keccak-after-freeze is production (R^{\mathrm{sync}}_e); HTTP-fetching EL RPC |
| P14 | landed (engine + tests). Lab freezer hosted-set stays lab-only. Production (C_G) = L1 archiveGroupId ∪ {lastAC, membershipRoot, hashIndexRoot}. Optional injected L1 small-set smoke. |
Writing 2249 lab chains into the whitepaper as production (C_G); HTTP-scanning publicrpc/rpc1 as production (C_G); shrinking live seating to the L1 small-set |
| P15 | landed (engine + tests). Challenge / opening = EIP-712 ArchiveStateChallenge (samplesRoot; hmacForgeable: false). HMAC / unsigned envelopes ERR_SYNC_CHALLENGE_HMAC_CUTOVER. Samples must rematch seed. |
Replacing BFT AC HMAC or on-demand HMAC in the same gate; putting 2250 samples in typed data; changing challengeHashOf; pilotStartedAt |
| P16 | landed (engine + tests). BFT AC votes = EIP-712 ArchiveBftVote (same domain; seating key reused). HMAC / unsigned votes ERR_BFT_HMAC_CUTOVER. keep-only disk HMAC certificates. |
Replacing on-demand HMAC or P6 (Q_V) HMAC in the same gate; changing membershipRootOf / Mode A valueHash; L1 wrapper / corpus SSZ; wipe; pilotStartedAt |
| P17 | landed (engine + tests). On-demand attests = EIP-712 ArchiveOnDemandAttest (same domain; seating key reused). HMAC / unsigned attests ERR_ONDEMAND_HMAC_CUTOVER. keep-only disk HMAC attests. |
Replacing the on-demand lab beacon or P6 (Q_V) HMAC; changing SelectionLog into AC; gossip wait-hook; wipe; pilotStartedAt |
| P18 | landed (engine + tests). P6 (Q_V) = EIP-712 ArchiveValidatorQuorumAttest (same domain; seating key reused on request-derived validatorId). HMAC / unsigned attests ERR_VALIDATOR_QUORUM_HMAC_CUTOVER. keep-only disk HMAC (Q_V). |
Replacing the on-demand lab beacon; gossip wait-hook; changing committee / membershipRootOf / Mode A valueHash; wipe; pilotStartedAt |
| P19 | landed (engine + tests). On-demand freeze-then-bind: persist ondemandFreezeHex first, then bind honest-wait / injected CL view / options beacon. Instant labBeaconAfterFreeze(poolRoot) is contrast only. publicrpc / rpc1 rejected. keep-only legacy-instant. |
Painting ondemandLabBeaconAfterFreeze as production CL RANDAO; gossip wait-hook (later P20); replacing P17 attest / P18 (Q_V); wipe; pilotStartedAt |
| P20 | landed (engine + daemon + tests). Wait hooks are not intra-group gossip. ingest rejects miners / hooks / hook (ERR_ONDEMAND_HOOK_NOT_GOSSIP). Daemon must fan out to every active archive; one accept ≠ group pool. Lab HTTP :27101 is not production DePIN gossip. |
Turning HTTP hook into production DePIN gossip; forwarding miners on /ondemand/message; exposing hook on explorer nginx; wipe; pilotStartedAt |
| P21 | landed (engine + tests). Lab BFT vote / QC / AC bind live/bound hashIndexRoot (after membershipRoot; topicQcRef encoding changes). Tree committedInAc stays false. Overlay hashIndexCommittedInAc when AC root ≠ ZERO32. keep-only skip QC/AC rebuild if certificate exists. |
Flipping tree committedInAc; painting overlay as production AC commitment / 30-day; changing membershipRootOf / Mode A valueHash; wipe; pilotStartedAt |
| P22 | landed (engine + tests). Official standby readiness = lab EIP-712 ArchiveStandbyReadiness (string groupId; seating key reused). Extra fd-08 ingest-only, does not count. POST /sync/standby-ready. New-chain accept waits for two official standbys (ERR_NEWCHAIN_STANDBY_NOT_READY). Isolated node.ts wiring is P24. |
Painting as production OperatorDomain / secp256k1 / 30-day; counting fd-08; changing Home green pills; wipe; pilotStartedAt |
| P23 | landed (live keep-deploy + evidence; honest 6/7). lab:deploy-g1-keep on official G1. Six hosts LIVE_OK with P12–P22 overlay fields under syncQualification. fd-01 new-chain 409 → 200 (ERR_NEWCHAIN_STANDBY_NOT_READY then accept 0xe8229f16…81b472). Official standby fd-06 HTTP unstable (event-loop starve; retry still no LIVE_OK). Extra fd-08 unofficial. G2 BFT/ondemand off. |
Claiming 7/7 healthy or durable officialStandbysReady; treating unit tests as the live proof; wipe; pilotStartedAt; promoting fd-08; forcing G2 voting; restarting EL/CL |
| P24 | landed (engine + tests). Isolated node.ts new-chain accept uses the same officialStandbysReady callback as lab-cli syncHolder. No sync.start() / no inventory freeze. Extra fd-08 still does not count. runtime:test 154/154. |
Production OperatorDomain; changing archiveSeating.ts; claiming 7/7 healthy; sync.start() against dummy peer URLs; pilotStartedAt |
| P25 | landed (Explorer overlay + tests + historical SPA). Certificates + Home non-green read-only overlays for officialStandbysReady / hashIndexCommittedInAc (explorer/src/lib/labOverlays.ts; P25 explorer:test 8/8). Home / archive-detail seating copy is lab EIP-712, never HMAC. P25 public SPA was index-U1o9ul_I.js (2026-08-18T00:18:49Z). Current public SPA is clock overlay index-C8IdTq4H.js (2026-08-18T10:15:00Z; explorer:test 10/10). Green pills stay seatingQualified === true only. Missing overlay fields omit the chip. |
Changing archiveSeating.ts seating logic; painting overlays as production AC / 30-day; flipping tree committedInAc; inventing P26 |
Parked (later tracks, not the next gate): IdentityEligible / OperatorDomain / (U_e); PilotQualificationGate; flipping tree committedInAc; production DePIN gossip; live CL RANDAO / production (C_G). Lab overlay hashIndexCommittedInAc is display-only (tree stays committedInAc: false; production AC commitment formula unchanged). Official standby readiness is no longer parked. 2026-08-17 review: src/canvas/dle-mvp-milestone-assessment-2026-08-17.md.
Must not: treat pilotStartedAt as 30-day qualification; reset warmup; invent P26; wipe keepers / fd-05; promote fd-08 to an official 8th voter; live-inject a missing object; restart EL/CL.
command: archive,runtime: nodejs,producesBlocks: false,hasTipVm: falseeth_chainId/net_version= CoNET-DLE Testnet EIP-155 id0x44c45/ 281669 — unique versus CoNET L1224422and Base8453. This is the plane chain id, not a group id. Never proxy L1publicrpc.dle_info.chainName=CoNET-DLE Testnet- Reject
eth_call/eth_getBalance/ writes - Deploy: keep-data only (
lab:deploy-g1-keep/lab:deploy-archive-keep/lab:deploy-m6).lab:deploy-archive-keep会重写 G1config.json且不带planeDirectory— 恢复 (G_e=2) 必须再跑lab:deploy-g1-keep(只动 G1)或lab:deploy-m6(会重启 G2)。Never restart geth / beacon / validator。停 archive 时若 SIGTERM 8s 后仍活(事件循环饿死),仅对dle-30d-lab/dle-m6-g2的lab-cli/agent补 SIGKILL;protect 命中 geth/beacon/validator 则 abort
See src/shared/ondemand/RULES.md. Waiting pool / SelectionLog is not (G_e). P17 landed on-demand attest EIP-712 ArchiveOnDemandAttest and did not replace the on-demand lab beacon. P18 landed P6 (Q_V) EIP-712 and did not replace the on-demand lab beacon or gossip wait-hook. P19 landed on-demand freeze-then-bind (honest-wait / injected view; not production CL RANDAO). P20 landed wait-hook honesty: hooks are not intra-group gossip; miner must POST every active archive; lab HTTP is not production DePIN gossip. P21 binds hashIndexRoot into lab BFT and did not change on-demand. P22 lands official standby readiness EIP-712 and did not change on-demand. P25 lands Explorer overlays and did not change on-demand.
See src/daemon/RULES.md. Daemon must not treat dle_tip.height as cluster count. P18 landed P6 (Q_V) EIP-712 and did not change daemon hook / new-chain-user HTTP. P19 landed on-demand freeze-then-bind and did not change gossip wait-hook. P20 landed daemon fan-out honesty (fanoutComplete / singleArchiveAcceptNotGroupPool); new-chain-user HTTP still only schema === 'DleLabValidatorQuorumV1'. P21 binds hashIndexRoot into lab BFT and did not change daemon hook / new-chain-user HTTP. P22 lands official standby readiness EIP-712 (lab-cli + new-chain accept gate) and did not change daemon hook / new-chain-user HTTP. P24 wires isolated node.ts to the same callback and did not change daemon hook / new-chain-user HTTP. P25 lands Explorer overlays and did not change daemon hook / new-chain-user HTTP. Do not treat challengeEip712 / bftEip712 / ondemandEip712 / newchainValidatorQuorumEip712 / ondemandLabBeaconAfterFreeze / ondemandHookNotGossip / hashIndexCommittedInAc / standbyReadyEip712 / officialStandbysReady / newchainOfficialStandbysReady / productionCgAvailable as production OperatorDomain / L1 wrapper / live CL RANDAO / 30-day qualification / (C_G) / production DePIN gossip / production AC commitment / production secp256k1.
Lab random-create user: src/daemon/newchain-user-cli.ts on 70.35.205.77:/home/peter/dle-newchain-user (npm run lab:deploy-newchain-user). Genesis smoke is asset + storage + trade; then a setTimeout chain (15–45s) posts a random class. Do not mix this process with dle-ondemand-clients.
Mock-L1 auction CLI: src/daemon/mock-l1-auction-cli.ts (npm run mock-auction-cli). In-process demo: npm run mock-auction-demo. Signs locally and posts to archive /mockl1/* + /trade/*. Client must not claim Archive legality or certificate quorum itself.