Skip to content

Record the mainnet v0.17.0 upgrade, and catch the next one automatically - #184

Merged
nickemmons merged 5 commits into
allora-network:mainfrom
Ansel-14:harden/network-drift-namespace
Aug 24, 2026
Merged

Record the mainnet v0.17.0 upgrade, and catch the next one automatically#184
nickemmons merged 5 commits into
allora-network:mainfrom
Ansel-14:harden/network-drift-namespace

Conversation

@Ansel-14

Copy link
Copy Markdown

Mainnet applied the v0.17.0 upgrade plan at block 10511421 (2026-08-18T19:37:21Z), so both networks now run the same build and both serve emissions/v10. #183 updated abci_version for both, which is everything the drift check compares. The rest of the manifest, and the prose built on it, stayed on the pre-upgrade story.

This PR includes #183's commit, so it supersedes it.

What was wrong

emissions_namespace for mainnet still read emissions/v9, which the network no longer routes:

$ curl -s https://allora-api.mainnet.allora.network/emissions/v9/params
{"code":12,"message":"Not Implemented"}

Consequences, all live:

  • Every documented mainnet LCD path returned 501, including both mainnet curl commands on /build/forge/topics.
  • The nightly topics job has failed every run since 2026-08-19T04:15Z. scripts/generateTopics.js reads the namespace from this manifest:
    GET https://allora-api.mainnet.allora.network/emissions/v9/next_topic_id failed after 3 attempts: HTTP 501 Not Implemented
  • /api/topics.json is therefore frozen at 2026-07-31, and drifting. Testnet is at 41 active topics; the published table says 39.

deployed_version for mainnet still read v0.16.0, with versions.json to match.

/reference/networks and /consume/rpc-grpc described a two-tier network where mainnet served single unlabeled values under emissions/v9. That premise no longer holds.

Why it survived every check

The release tag has a component and a gate behind it. The namespace had neither, so five pages hand-typed it and nothing connected the prose to the fact.

components/NetworkValue.tsx gives it a single source, mirroring components/Version.tsx, with a matching renderer in scripts/lib/docsPages.js so the generated corpus carries the value rather than the tag.

Stopping the recurrence

scripts/checkNetworkDrift.js now probes <lcd>/emissions/v<N>/params upward from the recorded namespace and takes the highest routed one as what the network serves. Two requests in the steady state. A namespace move is written to the manifest like abci_version is, and reported in the PR body as the release upgrade it is, naming the deployed_version each network still records instead of mentioning the field in passing.

Probing the LCD makes it a fetch target, so hostsOf derives the allowlist from lcd as well as rpc, under the same own-property and type discipline. The URL vetting is now shared rather than copied. abciInfoUrl keeps its signature and its messages.

scripts/checkVersionStrings.js covers the other half. A namespace inside an endpoint URL is judged against the network that URL names, so a testnet path is caught while testnet is a release ahead and mainnet is legitimately still on the older one. A namespace in prose is judged against the lowest any network serves. Both are derived from the manifest, so there is no list to maintain.

A source-tree path in a pinned permalink (x/emissions/proto/emissions/v1/reputer.proto) is a protobuf package, not a REST namespace, and is left alone.

One decision for review

deployed_version is set to v0.17.0, not v0.17.1.

Both networks report build HEAD-b6104eda6b2b009ea0714d2f724d53f4f0365fc0, which is #982, the v0.17.1 hotfix candidate. But v0.17.1 is still a draft release and its plan never ran:

$ curl -s .../cosmos/upgrade/v1beta1/applied_plan/v0.17.1
{"height":"0"}
$ curl -s .../cosmos/upgrade/v1beta1/applied_plan/v0.17.0
{"height":"10511421"}

If you would rather name the running binary, say so and I will change networks.json and versions.json together.

Verification

  • npm run build passes, 61/61 pages prerendered. Rendered /reference/networks shows emissions/v10 for both networks and no emissions/v9.
  • yarn testdrift: 143/143 and 37/37. 17 new cases cover the namespace walk, the statuses (501 and 404 are answers, 5xx is not), and the namespace's path into a URL.
  • Replayed against the pre-fix manifest, the new drift check reports mainnet: recorded emissions/v9 -> serves emissions/v10 and emits a body naming mainnet still records deployed_version: v0.16.0.
  • Against a simulated testnet-ahead divergence, the version gate flags stale testnet URLs and leaves correct mainnet URLs alone.

Merging the first commit alone unbreaks the nightly topics job.

github-actions Bot and others added 3 commits August 19, 2026 06:15
Mainnet applied the v0.17.0 upgrade plan at block 10511421
(2026-08-18T19:37:21Z, confirmed via
/cosmos/upgrade/v1beta1/applied_plan/v0.17.0), so both networks now run the
same build and both serve emissions/v10. The drift PR updated abci_version
for both, which is all that check compares; the rest of the manifest and the
prose built on it stayed on the pre-upgrade story.

Three things were wrong and user-visible:

- mainnet's emissions_namespace still read emissions/v9, which the network
  stopped routing. Every documented mainnet LCD path answered 501 Not
  Implemented, including the two curl commands on /build/forge/topics, and
  the nightly topics job had been failing on
  /emissions/v9/next_topic_id since the upgrade -- scripts/generateTopics.js
  reads the namespace from this manifest.
- mainnet's deployed_version still read v0.16.0, with versions.json to match.
- /reference/networks and /consume/rpc-grpc described a two-tier network
  where mainnet returned single unlabeled values under emissions/v9. That
  premise no longer holds.

The namespace had no single source: <Version of="chain-mainnet"/> covers the
release tag, but nothing covered the namespace, so pages hand-typed it and
nothing connected the prose to the fact. components/NetworkValue.tsx gives it
one, mirroring components/Version.tsx, with a matching renderer in
scripts/lib/docsPages.js so the generated corpus carries the value rather
than the tag.

public/raw/get-started/cli.md changes on its own: that page already read the
mainnet version through the component.
The nightly drift check compares abci_version and nothing else, because that
was the only value it could read automatically. That is a build identifier,
so it caught the mainnet v0.17.0 upgrade as a hash change and left the
consequences to a paragraph of follow-up advice in the PR body. The advice
was not acted on, and for a day mainnet's recorded emissions namespace stayed
at emissions/v9 while the network no longer routed it: every documented
mainnet LCD path answered 501, and the nightly topics job failed on the same
call.

The namespace is queryable, so it is queried. checkNetworkDrift.js probes
<lcd>/emissions/v<N>/params from the recorded namespace upward and takes the
highest routed one as what the network serves -- two requests in the steady
state. A namespace move is written to the manifest like abci_version is, and
reported in the PR body as the release upgrade it is, naming the
deployed_version each network still records rather than mentioning the field
in passing.

Probing the LCD makes it a fetch target, so hostsOf now derives the allowlist
from lcd as well as rpc, under the same own-property and type discipline. The
URL vetting is shared rather than copied: a second copy of the SSRF guard is
the hazard this file already names for the address ranges. abciInfoUrl keeps
its signature and its messages.

checkVersionStrings.js gains the other half. The release tag had a component
and a gate; the namespace had neither, which is why emissions/v9 survived in
four pages and two runnable curl examples with every check green. A namespace
in an endpoint URL is judged against the network that URL names -- so a
testnet path is caught while testnet is a release ahead and mainnet is
legitimately still on the older one -- and a namespace in prose is judged
against the lowest any network serves. Both are derived from the manifest, so
there is no list to maintain and nothing to forget at the next upgrade.

A source-tree path in a pinned GitHub permalink
(x/emissions/proto/emissions/v1/reputer.proto) is a protobuf package, not a
REST namespace, and is left alone.

17 new cases in testNetworkDriftGuard.js cover the walk, the statuses (501
and 404 are answers, 5xx is not), and the namespace's path into a URL.
@netlify

netlify Bot commented Aug 24, 2026

Copy link
Copy Markdown

Deploy Preview for alloradocs ready!

Name Link
🔨 Latest commit 93c5747
🔍 Latest deploy log https://app.netlify.com/projects/alloradocs/deploys/6a8c9b0b9a1985000916eadb
😎 Deploy Preview https://deploy-preview-184--alloradocs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 15 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="scripts/checkNetworkDrift.js">

<violation number="1" location="scripts/checkNetworkDrift.js:625">
P3: When the LCD returns a redirect or non-retryable 4xx, this catch retries the deterministic failure three times despite the retry policy comment. Retry only transport failures and retryable 5xx responses so endpoint errors fail promptly.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant LiveNet as Allora Network (RPC/LCD)
    participant DriftScript as scripts/checkNetworkDrift.js
    participant Manifest as public/api/networks.json
    participant BuildScript as scripts/checkVersionStrings.js
    participant MDX as MDX Pages / Components
    participant DocSite as Rendered Docs (HTML/LLM files)

    Note over LiveNet, DocSite: Automated Drift Detection & Manifest Update

    DriftScript->>Manifest: Read recorded "emissions_namespace" (e.g., v9)
    DriftScript->>LiveNet: CHANGED: Fetch abci_info (RPC) for build hash
    
    loop NEW: Namespace Probe Walk (vN to vN+3)
        DriftScript->>LiveNet: NEW: GET /emissions/v[N]/params (LCD)
        alt HTTP 200
            LiveNet-->>DriftScript: Routed (Namespace Valid)
        else HTTP 501 / 404
            LiveNet-->>DriftScript: Not Implemented (Stale)
        end
    end

    opt NEW: Drift Detected
        DriftScript->>Manifest: NEW: Update "emissions_namespace" to highest routed version
        DriftScript->>Manifest: CHANGED: Update "abci_version"
    end

    Note over LiveNet, DocSite: Build-time Validation & Rendering

    BuildScript->>Manifest: Read current namespaces & calculate "floor" version
    BuildScript->>MDX: Scan for hardcoded "emissions/vX" strings
    alt Stale literal found (< Floor)
        BuildScript->>BuildScript: Throw Error: Fails build
    else Literal matches host in URL
        BuildScript->>BuildScript: Validate against host-specific namespace
    end

    MDX->>Manifest: NEW: Lookup field via <NetworkValue /> component
    Manifest-->>MDX: Return dynamic string (e.g., "emissions/v10")
    MDX->>DocSite: Prerender static content with injected values

    Note over MDX, DocSite: Result: llms-full.txt and docs show live network state automatically
Loading

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread scripts/checkVersionStrings.js
Comment thread scripts/checkVersionStrings.js Outdated
Comment thread scripts/checkNetworkDrift.js
Comment thread public/llms-full.txt Outdated
Comment thread public/llms-full.txt
Comment thread scripts/checkNetworkDrift.js Outdated
Comment thread scripts/checkNetworkDrift.js
Comment thread scripts/checkNetworkDrift.js Outdated
All eight from the PR review, none declined:

- A missing or malformed emissions_namespace now fails the manifest check
  loudly instead of silently disarming the stale-namespace gate: the matcher
  derived its floor from the field, so a broken manifest was the one state in
  which the gate matched nothing. checkManifestsAgree enforces the shape,
  next to the deployed_version agreement it already owns, and the failure
  headline now covers both problem classes.
- A manifest host is never judged "foreign", whatever its domain: the host
  map test runs before the allora.network test, so an LCD moved off
  allora.network keeps its URLs judged against its network rather than
  skipped.
- A version number past MAX_SAFE_INTEGER is rejected before the walk.
  version++ stops changing the value up there, and the manifest can arrive
  from the machine-owned drift branch, so the hang was reachable from
  outside this repository.
- A 2xx alone no longer proves a namespace is routed: the params body every
  emissions version answers with is required, so a catch-all gateway that
  200s every path becomes a probe error instead of a fabricated namespace
  written by --write.
- A redirect is now a settled refusal the retry loop rethrows immediately,
  which is what the retry-policy comment already claimed; the comment and
  the code agree again. The abci probe keeps its existing behavior.
- When nothing at or above the recorded namespace answers, the walk now
  looks below it before giving up, so a manifest recorded past the chain --
  a hand-bump that overshot, or a rollback -- reports as ordinary drift and
  is repaired by --write rather than surfacing as an unreachable network.
- Two wording fixes on /build/forge/topics: the generated-tables note no
  longer reads as if /api/networks.json were itself the namespace, and the
  verify section no longer asserts the segment "differs by network" now that
  both networks serve the same one; it says the segment is per-network and
  moves with upgrades, which is true in both states.

testNetworkDriftGuard grows from 143 to 148 cases: body validation (non-JSON
and JSON-without-params 200s refused), the deterministic redirect flag, the
unsafe-number rejection with zero probes, the downward walk with its exact
probe order, and the v1 floor of the downward range.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 6 files (changes from recent commits).

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread scripts/testNetworkDriftGuard.js
Comment thread scripts/checkNetworkDrift.js Outdated
Comment thread scripts/checkNetworkDrift.js Outdated
Comment thread scripts/checkVersionStrings.js
Comment thread scripts/checkVersionStrings.js
The second review pass found three defects in the previous commit's fixes and
two quality problems. All five are addressed.

The infinite loop was still reachable. The safe-integer guard checked only the
starting version, so a `from` that is itself safe could still have
`from + maxNamespaceLookahead` fall outside the range, where `version++` stops
advancing and the walk never ends. The ceiling is now checked too. This is
demonstrated rather than argued: with the ceiling check removed, a walk from
`emissions/v9007199254740991` runs past fifty probes without terminating.

The params body check accepted an array. `typeof [] === 'object'` and a
non-empty array is truthy, so `{"params":[]}` passed as a routed namespace --
exactly the catch-all-gateway response the check exists to reject.
`Array.isArray` now excludes it, and the case list in the test covers `[]`,
`[1,2]` and `null` alongside the shapes it already had.

The per-network host judgment ignored ports. `urlHostAt` captured `host:port`
with a regex while the manifest map is keyed on `hostname`, so an LCD on a
non-default port matched nothing in the map and its own endpoint URLs were
skipped as foreign -- the judgment silently off for precisely the URLs it
describes. Both sides now use `new URL().hostname`.

Manifest problems are reported by kind. A malformed `emissions_namespace` was
printed under guidance about keeping `deployed_version` in step across two
files, which sends the reader to the wrong field in the wrong file. Namespace
problems now carry their own explanation and their own fix, and point at
`checkNetworkDrift.js` for the namespace each network actually routes.

Tests go from 148 to 152, and the new ones were checked by removing the
behaviour they name: dropping the `Array.isArray` guard fails the body case,
and dropping the ceiling guard produces the non-terminating walk above. The
boundary cases cover `MAX_SAFE_INTEGER` and the two values below it whose
lookahead overflows, plus a safe start ten below it that must still walk and
stop.
@nickemmons
nickemmons merged commit 46d0f44 into allora-network:main Aug 24, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants