# Known limitations and accepted risks `GAUNTLET.md` Track I, item I5. `docs/audit/scope.md` (I1) drew the boundary, `threat-model.md` (I4) named the actors and trust boundaries, `invariant-coverage.md` (I3) and `test-coverage-map.md` (A3) proved what is tested. This document is the last piece an auditor needs before it all adds up to a verdict: **the things that are true about this repo today, on purpose or not yet fixed, that the other seven documents do not already resolve.** Nothing here is a surprise to the team — every line either cites a signed `DECISIONS.md` entry (the owner already chose to accept the risk) or an open one (the owner has not yet chosen, and the option analysis is already written). This is a judgement document, not a regenerated one, and it goes stale the moment a `DECISIONS.md` status changes from `open`/`pending` to `signed`/`closed` — whoever closes one of the open items below should update this file in the same change, the same rule `CLAUDE.md` states for CONCEPT.md drift. ## 1. Accepted-by-design (owner has already signed off) These are not bugs; they are choices, made deliberately and cheaper to document than to build around. Listed because an auditor reading the code cold could mistake any of them for an oversight. - **The number line ENDS. `AUCTIONABLE_MAX = 42949672959999`** (10⁴·2³² − 1, ≈4.29·10¹³, fourteen digits) is the largest `n` this protocol can mint or auction, asserted on every entry point that takes an `n` (`primes_market.tolk`, and mirrored by the ledger's `OMEGA_MAX` derivation). It is not a rate limit or a phase-one setting — it is the last number, and nothing in the system raises it. Signed **D-11/D-18**; CONCEPT.md §3.2 uses it as the input to the ω(n) ≤ 12 bound (37# ≈ 7.42·10¹² fits under it, 41# ≈ 3.04·10¹⁴ does not), and it is what makes Miller–Rabin overflow-safe here: at n < 2⁴⁶ a squared residue stays under 2⁹², four orders inside int257, so the 78-digit argument that threw exit 4 as `P2.C1` has no reachable value left. **Reaching it is a horizon, not a risk:** mints are strictly sequential at 1 TON each, so the head arriving at the cap would mean ~4.29·10¹³ mints. Listed here because a reviewer reading the app sees an unbounded number line and either misses the ceiling or finds it in the contract and reports it as undisclosed. Bound to the contract by `contracts/tests/ReservableCap.spec.ts` — the figure above is compared against `primes_market.tolk`'s own constant, never retyped by hand. - **`T` is not `swapped + pending`.** The ledger's `T` (ratchet numerator) includes a `seeded` term — founder capital genesis credits straight into `T` to back `S` from the DeDust LP, and it never passes through `pending`. A two-term reading reports a permanent, false "solvency gap." Signed **D-26** (2026-08-19); pinned by `C3`'s three-term property test. `CLAUDE.md`'s own invariant list states this explicitly so the mistake is not repeated. - **`flush()` bounces every time on testnet, and that is correct.** The DeDust vault address configured today is a throwaway stand-in with no real liquidity pool behind it — every `Flush` that clears the cooldown and finds `pending > 0` sends a real message that bounces for lack of a pool, and `getFlushStatus().lastOutcome` records `Bounced` honestly. Proven live in **H6** (2026-08-29): a brand-new, never-used wallet flushed permissionlessly and got the identical state-based rejection any wallet would. Not a defect — it is what "no real DEX pool exists yet" looks like from outside. - **The swap payload's wire format is verified against DeDust's published schema and round-trip proven against a schema-faithful mock vault — still not against a real, live DeDust pool.** Filed as **GAUNTLET V2** (2026-09-09): the pre-fix payload (`op`, `query_id`, `coins(min_out)` and nothing else) had no `pool_addr` and no `swap_params` ref at all — checkably wrong, not merely unverified, against `docs.dedust.io/reference/tlb-schemes`. Rebuilt field-for-field against that published schema (`primes_ratchet.tolk`'s `handleFlush`, `shared/schemas/ratchet.tlb`). `WireFormatDeDustSwap.spec.ts` parses the real outbound cell field-by-field to prove it matches. **V2.1** (2026-09-09) went a step further: `contracts/test-mocks/ mock_dedust_vault.tolk`, a TEST-ONLY contract (never deployed, no opcode in `shared/opcodes.ts`, excluded from every `docs/audit/*` generator's contract count) parses the real schema and genuinely fills or genuinely throws on a slippage rejection, and `MockDedustVaultSwap.spec.ts` proves the ratchet's `TransferNotification` fill-detection (GAUNTLET B2.1) and `onBouncedMessage` bounce-reversal both round-trip correctly against it — stronger than every other flush-path spec, which stands a plain treasury in for the vault and would accept any payload at all. What this still does NOT prove: that a REAL DeDust pool, running DeDust's actual deployed bytecode, accepts this exact cell. No live testnet pool exists (the bullet above) — that would need an owner action (funding a real pool) this loop cannot take on its own. - ~~**Patron mode goes negative-EV for the patron above `n ≈ 1173`.**~~ **RETIRED 2026-09-07 — the mechanic no longer exists.** `DECISIONS.md` **D-106** deleted patron mode in its entirety (the §4.1 patron line, `patronOf`, `PATRON_CAP`, the purse and the whole ownerless-item lifecycle) and amended **D-57**, which is what put this entry here. Kept rather than deleted because an auditor who read an earlier copy of this pack, or D-57 itself, deserves to be told where it went rather than find it silently absent. **What replaced it:** §4.7's recruit ladder now runs on the §4.5 gift-mint edge — a gift to an address the ledger's `minterState` has never seen credits the payer +1 recruit — which pays **no TON at all**, so the negative-EV question this entry asked has no referent. There is no cap to decay, because nothing is paid back. - **A Telegram alert page can be silently dropped if two ERROR/CRITICAL lines land in the same fluent-bit flush window** — the second is coalesced away before it reaches Telegram. Accepted, not fixed, alongside **G9d** (2026-08-29): every alert record is a clone of a `docker.*` record, so the full record still reaches Cloud Logging via that parallel route regardless of whether the Telegram page itself lands — a human paged by the surviving alert can always pull the complete picture there. - **Special composites above 1 TON is a named, bounded exception, not a lane leak.** `CLAUDE.md`'s "no lane may terminate above 1 TON" invariant carries one signed carve-out (**D-47**/**D-64**, §9.6): a special number's post-decay parked price may remain above 1 TON until bought, via a vault purchase and re-listing rather than a lane exception. `C6`/`C6.1` pin that this is the only exception in the route-selection function. - **`STAKE_UPLIFT_MAX = 0`.** Staking ships live but buys nothing — signed **D-34** (2026-08-19), gated pending a future sim answer on headroom sizing. **B11** proved the gate holds and the disabled path cannot be reached; the custody arms (`Unstake`, `TransferNotification`) are live and tested independently of the uplift being zero. - **English only.** The RU bundle was deleted rather than left to drift — signed **D-74** (2026-08-29) — while the app is still in active development; re-localizing is a future decision, not a regression. - **No operator PRIMES allocation at genesis, but a 9-month vesting grant exists.** Signed **D-46** (disclosed operator genesis allocation, no seeded/burned LP at deploy), **D-77** (2026-08-29, nine equal tranches 90 days apart, no admin surface on the vesting contract) and **D-91** (2026-09-01, re-cut to four equal 25% tranches 90 days apart, the last at day 270, when the allocation's purpose changed from the pool's PRIMES side to an operations runway). D-91's own point 3 records the cost it accepts: **25% — 500,000 PRIMES, ~4.17% of `S₀` — is liquid at block one**, against D-77's 1.85%. All are on-chain and auditable via `getVesting`/`getVestingSchedule` (live since **E14a**, 2026-08-29). - **`read-proxy`/`event-poller`'s `/health` were liveness-only; both now publish `degraded`.** Signed **D-70** (2026-08-31, `read-proxy`: a rolling 60s/3-failure window of classified upstream errors) and **D-71** (2026-08-31, `event-poller`: `consecutiveFailures >= 2`, threaded from the poller loop's own already-tracked state). `keeper` was deliberately left liveness-only per D-71's own analysis — its two candidate signals each answer only half of "is this keeper actually working," which is a real design call, not a same-run one. Closed `GAUNTLET.md` **E1.1**/**E8.1**. - **No subresource-integrity or signed-manifest check protects the mint builder from a compromised deploy host.** Signed **D-76** (2026-08-31, option 3: accepted risk, no code) — genuine protection needs new runtime security code on the mint's hot path, and this is pre-launch testnet with no funds at risk yet. **This remains the single largest true security gap in the stack that is not a contract bug** (D-76's own framing, unchanged by accepting it) — revisit alongside `F12b` once a mainnet date exists. Closed `GAUNTLET.md` **I4.1**. ## 2. Open decisions — brief written, owner has not yet chosen Each of these has a full options-and-recommendation brief already sitting in `DECISIONS.md`; nothing below is a gap in analysis, only in sign-off. Referenced by ID so whoever signs one can grep straight to it. | ID | One line | Where it bites | |----|----------|----------------| | **D-48** | Delete `primes_item.getRarityFlags()`/`getIsUnity`, or keep them? | Dead-code question, no economic exposure. | | **D-50** | `open_head_lot` lets anyone permanently occupy contract storage for ~0.02 TON gas — bound it how? | A storage-griefing vector on the auction lane, cheap to execute, not yet capped. `B3.1b` is blocked on this and on a second, sharper conflict: `CLAUDE.md` rule 3 (never invent an economic parameter) versus D-50's own delegation of "size the deposit/cap" to the implementing iteration — this loop cannot pick a number under its own standing authority. | | **D-51** | A bounced `MintItem` leaves the registrar's naming rights and prime rank permanently wrong. | Reachability proven on a real test (`RecordMintBounceGap.spec.ts`, **B5.1**); the owner signed a direction (bounce-aware registrar) but a later mechanism-clarification pass (iter #9) found the signed brief pointed at the wrong contract's `onBouncedMessage` and left the undo semantics (unconditional clear vs. guarded) unresolved — still open pending a second, sharper sign-off. | | **D-52** | A bounced rebate mint is silently lost and permanently inflates `S`. | The ratchet's own floor denominator can drift upward against value nobody received. Signed once (`S` rollback + retry re-send), then found not-yet-safe-to-implement-as-signed at iter #10 — a live conflict note, not a stalled brief. | | **D-53** | A bounced era/special-number bounty is marked paid but never delivered. | Same shape as D-52, three siblings (`B12.1`, `B5.3`, `B11.1`) folded into one decision so the owner answers "retry journal or accept the loss" once. | | **D-54** | §4.7/§4.7.1's "rank buys a tribute multiplier" claim has no code behind it. | A CONCEPT.md claim with nothing checkable behind it — wire it or strike it. | | **D-55** | Rebate vesting (`VEST_NOW`/`VEST_H`) does not exist in code. | §8.1 calls the current state "a prevented risk-free race"; today it is simply the current, unprevented state. Doc-vs-code drift, not a fund-safety issue. | | **D-72** | A composite's storage runway (D-62's ~9-year rent clock) has no client-reachable data to render at all. | Blocks a `DESIGN.md` surface, not a fund-safety gap — the top-up path itself is already signed (D-62, option 2) and built. | | **D-73** | `/connect`/`/story` bot commands are unregistered because bot-side quest scoring is deliberately v1.1. | A live-paying quest (`story-verified-reach`) whose only advertised delivery channel does not exist yet — sequencing gap, not a security issue. Blocks `S11b`. | | **D-75** | `tokens.css` is "dark only, on purpose," but `F10b` asked for Telegram theme-syncing. | A design-vs-engineering conflict on a cosmetic surface, no economic or custody exposure. | | **D-78** | S86's vesting-lock UI needs a read-proxy route design-iteration cannot add itself. | Scope-boundary escalation, not a risk question — `GET /vesting` already exists (E14, E14a) once the owner or an in-scope loop wires the read; the design lane is blocked only on that plumbing existing, which it now does. | ## 3. Structural gaps — real, not a decision away, blocked on time or resources Not owner-decision items. These need either real wall-clock time to elapse on the live testnet deployment, or more TON in a wallet than this loop has authority to mint from nothing (`CLAUDE.md` rule 3 — a gauntlet iteration cannot fund itself an economic input). - **H4/H5 — the ascending lot's CLOSE, which mints, unproven on the current genesis.** *(Read — this entry was "settle/claim, and the release-for-90% path" until `DECISIONS.md` **D-90** deleted all three: the escrowed claim, the permissionless `settle(n)` and D-32's release right with its 90/10 split. A won lot now mints the number in the transaction that closes it, so `close(n)` is the whole of what is left unproven here. The entry already cited D-90 for the redeploy below without updating its own subject — corrected rather than left standing.)* D-45 deleted the early-close-on-head-arrival shortcut for ascending lots; `close(n)` now asserts `blockchain.now() >= rec.closesAt` unconditionally, and `closesAt` is `open time + AUCTION_WINDOW` (7 real days). **Updated 2026-09-04 (Sweep #19, iter #351) — the E14a genesis this entry described is dead**: R6 (`DECISIONS.md` D-88/D-89/D-90/D-91) redeployed the whole market 2026-09-02. The boring `n = 2^30` lot this loop's own precedent opens is live again on R6 (`getLot(1073741824)`, opened 2026-09-03T18:47:52Z, `closesAt` **2026-09-10T18:47:52Z**) — no ascending lot on this genesis can be provably settled before that date. The mechanic itself (`open`, `bid`) is proven on R6 too; only the post-window half is not yet exercised live. - **H7/H8 — era trophy award/transfer, and the referral payout shape, unproven on the current genesis.** *(Read — the patron payout shape was the second half of this entry until `DECISIONS.md` **D-106** deleted patron mode on 2026-09-07. It is not unproven; there is nothing left to prove. The live figures below were measured 2026-09-04, before that deletion, and are left as measured rather than re-estimated.)* **Updated 2026-09-04 (Sweep #19, iter #351) — re-checked live against R6, not carried over from the dead E14a numbers.** Era 1's trophy closed with no winner before the redeploy (D-14/D-36's "nobody is invented" branch, a real, permanent, correctly-decided outcome, not a bug); on R6, `getEraLeader()` again shows a leader with `recruits = 1` and `getTrophy(30)` = `(holder: null, transferable: 1)` — era 2's boundary at `n = 30` is the only remaining live opportunity, same as before the redeploy. `getHead()` = **16** (live-read 2026-09-04), so **14 more composite mints** away (skipping auto-skipped primes), a smaller gap than pre-redeploy but not a closed one. The combined balance of every keeper/treasury/deploy-subwallet this mnemonic controls is **≈1.23 TON**, against the ~14.7 TON the remaining mints plus a gift-mint→primorial-mint→`sendTransferTrophy` sequence need — a figure measured when that sequence still carried D-106's now-deleted claim and activation legs, so the true requirement is *lower* than 14.7 and has not been re-measured — funding is *further* from sufficient than the pre-redeploy reading (~5.81 TON right after R6 ignited), because other loops' proofs (H8a's patron-line proof, concurrent minting) have been spending down the same shared wallets since. Neither wallet can be topped up by this loop under its own authority (an owner/faucet action, the same class as `J5`'s GA4→BigQuery export — testnet faucets were already checked and found unusable headlessly from this sandbox, `docs/testnet-deployment.md`). Whoever next has funding authority and sees `getHead()` closer to 30 should re-check before attempting the sequence — do not force it on a wallet shared with unrelated concurrent chain activity, which risks seqno collisions that cost the OTHER actor's in-flight work, not just this one. - **H12 — a bounty vault that is short of `TRANSFER_GAS` forfeits a one-shot payout in silence, and cannot retry it.** Filed 2026-09-16, open, `GAUNTLET.md` H12. The vault spends 0.1 TON of its own balance on each jetton hop; below that the compute phase succeeds (`exit 0`) and the ACTION phase fails with result 37, reverting the whole transaction. The mint that triggered it survives — the ledger's leg is deliberately `BounceMode.NoBounce` so that a vault failure never unwinds a mint — so nothing reports anything, `RetryBountyPayout` finds no journal entry (it would have been written by the transaction that reverted) and `onBouncedMessage` never fires because an action-phase failure is not a bounce. Each trigger is one-shot, so the payout is permanently forfeit. **The funding half of this finding is FIXED and must not be read as open** (`GAUNTLET.md` H11, closed 2026-09-16): `payOut`'s `responseAddr` now names the vault, so TEP-74's excess comes home and each payout nets **+0.0124 TON** — measured on the live 2026-09-14b set across eight consecutive payouts, vault balance 2.13 TON after 11. The vault therefore cannot drain itself into this state any more; it can only be short if it starts short. What remains open is the choice between adding a pre-send balance check that journals without sending (a contract change and a redeploy) and accepting the hole with operational monitoring — an owner decision, briefed when H12 is taken. *(This bullet previously described a different H11 — a live wrong-tier answer on `/trophy/special/:n`. That entry was doubly superseded and is deleted rather than kept as provenance, because an audit-facing limitations note that names a live §9.1 violation must not describe a mechanic that no longer exists: D-99 replaced the three rarity tiers with one additive score, and `/trophy/special/:n` was replaced by `/trophy/score/:n` (`backend/read-proxy/src/routes/trophy.ts`); the redeploy it was waiting on has since happened twice. `GAUNTLET.md` H11 is, and since 2026-09-04 has been, the bounty-vault item.)* - **G25 — is `AppHost` rate-limiting `deploy.ps1`'s SSH connections under this repo's own connection frequency?** G15 (closed 2026-09-02) shipped the retry that makes a momentary `ssh` exit 255 survivable, but deliberately left the root cause open. Needs a real host's `sshd`/fail2ban logs around a steward run to answer; this sandbox has no live host to observe, so `/ops-steward` — not this loop — is where it closes. ## 4. Frontend/mainnet-readiness notes, deferred on purpose - **F12b** — `resolveTonNetwork()` silently defaults to `"testnet"` when `VITE_TON_NETWORK` is unset or unrecognized. Today's default happens to be the safe direction (nobody accidentally builds a mainnet bundle by omission); revisit whether a missing value should instead refuse to build once a real mainnet deploy is on the table. - **D-76** (noted again from §1 above because it is also, structurally, a mainnet-readiness item, not only a signed-off risk) — SRI/signed-manifest protection for the mint builder is the one item on this whole list that changes character between "pre-launch testnet" and "real money is at stake." Track H's own mainnet path should re-open it rather than let it stay closed by the pre-launch deferral indefinitely. ## Sign-off This document is drafted, not signed. Per this loop's standing authority (`.claude/skills/gauntlet/SKILL.md`, "Standing authority"), a gauntlet iteration may not close a `DECISIONS.md` item or invent the owner's answer to one — it can only compile what is already true and already briefed. The owner's review of §2's table (accept as listed, or resolve one or more entries) is the one action outstanding against this document. **Owner sign-off:** _______________________ Date: _______________