diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 8990c7e6b..5e539da52 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -1,5 +1,5 @@ name: Bug report -description: Something in the Cortex control plane is wrong +description: Report a problem in the Cortex research network software labels: ["bug"] body: - type: markdown diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml index b676d254c..981a2a58e 100644 --- a/.github/ISSUE_TEMPLATE/feature.yml +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -1,5 +1,5 @@ name: Feature request -description: Propose a change to the Cortex control plane +description: Propose an improvement to the Cortex research network labels: ["enhancement"] body: - type: markdown diff --git a/.greptile/rules.md b/.greptile/rules.md index 358a50a4c..94f1dc207 100644 --- a/.greptile/rules.md +++ b/.greptile/rules.md @@ -1,6 +1,6 @@ # Cortex review rules -This is a Bittensor subnet control plane (`CortexLM/cortex`), not an app-platform +This implements an autonomous research network on Bittensor (`CortexLM/cortex`), not an app-platform or SOC2 checklist. Two **live** challenge ids: `bounty` (2000 bps) and `proof` (8000). Proof-weighted 20%/80% regardless of eval digest. Proof's eval digest is pinned (`ghcr.io/cortexlm/proof-eval@sha256:78b614a1…`); do not invent a different sha256. Empty digest stays fail-closed (503). Sum is 10000. `relearn`, `relearn-image`, @@ -8,6 +8,9 @@ eval digest is pinned (`ghcr.io/cortexlm/proof-eval@sha256:78b614a1…`); do not row, no compose services). Historical miner stubs stay under `docs/external-miner/`. Frozen specs remain for xtask gates. Leftover `prism-*` crates are the Lium harvest stack used by Proof. +- **Distinguish vision from implementation.** `docs/WHITEPAPER.md` maps the + proposal to code. Do not market unfinished judging, research persistence, + synthesis, or Proof emission as deployed capabilities. - **Fail-closed.** Missing holdout file, commitment mismatch, unpinned eval digest, or unset judge credentials → refuse / 503. Never score the public split as a substitute. `*_FORCE_SIM` is CI/local only and must be the *only* way to diff --git a/AGENTS.md b/AGENTS.md index 0bc8a5122..29b9da854 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,8 +1,10 @@ -# AGENTS.md — Cortex control plane +# AGENTS.md — Cortex research network Short contract for agents and operators. Prefer linking over restating runbooks. -**Product:** Cortex ([`CortexLM/cortex`](https://github.com/CortexLM/cortex)) — Bittensor subnet control plane. **Two live challenges:** `bounty` (2000 bps) and `proof` (8000 bps). Proof-weighted 20%/80% lock regardless of eval digest. Proof eval digest is pinned (`ghcr.io/cortexlm/proof-eval@sha256:78b614a1…`, RLM judge via digest-pinned `InferenceOffer`); live submits still 503 until harvest is wired, a baseline is sealed, and ≥1 topic is open. Empty digest stays fail-closed (do not invent a sha256). Sum is 10000. `relearn`, `relearn-image`, `relearn-agent`, `relearn-mm`, `design`, and `prism` are **removed as products** — no trust-root row, no compose services, no emission, and no leaf may verify. Historical miner stubs stay under [`docs/external-miner/`](docs/external-miner/) so old links do not 404. Frozen specs (`docs/DESIGN_CHALLENGE.md`, `docs/PRISM.md`) remain for xtask gates. Leftover `prism-*` crates are the **Lium harvest stack** used by Proof, not a live Prism challenge. Proof scores operator-published research topics (dynamic `topic_id`, digest-pinned RLM judge, `wta` or `discovery` payout). Naming split (Cortex vs leftover `base` / `BASE_*`): [`docs/NAMING.md`](docs/NAMING.md). +**Product:** Cortex ([`CortexLM/cortex`](https://github.com/CortexLM/cortex)) — an autonomous research network on Bittensor. **Two live challenge ids:** `bounty` (2000 bps) and `proof` (8000 bps). Proof-weighted 20%/80% lock regardless of eval digest. Proof eval digest is pinned (`ghcr.io/cortexlm/proof-eval@sha256:78b614a1…`, RLM judge via digest-pinned `InferenceOffer`); live submits still 503 until harvest is wired, a baseline is sealed, and ≥1 topic is open. Empty digest stays fail-closed (do not invent a sha256). Sum is 10000. `relearn`, `relearn-image`, `relearn-agent`, `relearn-mm`, `design`, and `prism` are **removed as products** — no trust-root row, no compose services, no emission, and no leaf may verify. Historical miner stubs stay under [`docs/external-miner/`](docs/external-miner/) so old links do not 404. Frozen specs (`docs/DESIGN_CHALLENGE.md`, `docs/PRISM.md`) remain for xtask gates. Leftover `prism-*` crates are the **Lium harvest stack** used by Proof, not a live Prism challenge. Proof scores operator-published research topics (dynamic `topic_id`, digest-pinned RLM judge, `wta` or `discovery` payout). Naming split (Cortex vs leftover `base` / `BASE_*`): [`docs/NAMING.md`](docs/NAMING.md). + +**Vision vs implementation:** start with [`docs/OVERVIEW.md`](docs/OVERVIEW.md) and the [`whitepaper comparison`](docs/WHITEPAPER.md). Do not describe the proposed synthesiser, recursive research judge, durable research corpus, or automatic Proof emission as complete. Current Python judging is partial; Proof uses in-memory submission state and its binary does not drive the payout/emission helpers. A pinned image and `can_score` are not proof of scientific reproduction or end-to-end payment. PRs require a [Greptile](https://greptile.com) review (`.greptile/`). If the bot is silent, comment `@greptileai review`. @@ -52,7 +54,7 @@ Each live challenge has miner docs in this repo. Public repos (when they exist) | Bounty | this repo [`docs/external-miner/bounty.md`](docs/external-miner/bounty.md) | Miner pairing + report path; subnet **reads** CortexLM/backend public API (does not serve one) | | Proof | this repo [`docs/external-miner/proof.md`](docs/external-miner/proof.md) | Dynamic operator-published topics + digest-pinned RLM judge | -This control-plane repo is `CortexLM/cortex`. Off/archived miner pointers stay under [`docs/external-miner/`](docs/external-miner/) (`relearn.md`, `relearn-image.md`, `relearn-agent.md`, `relearn-mm.md`) so historical links do not 404; they are not live products. Frozen specs (`docs/DESIGN_CHALLENGE.md`, `docs/PRISM.md`) stay archived. Do not send miners to Design, Prism, or Relearn docs as live work. +This network implementation repo is `CortexLM/cortex`. Off/archived miner pointers stay under [`docs/external-miner/`](docs/external-miner/) (`relearn.md`, `relearn-image.md`, `relearn-agent.md`, `relearn-mm.md`) so historical links do not 404; they are not live products. Frozen specs (`docs/DESIGN_CHALLENGE.md`, `docs/PRISM.md`) stay archived. Do not send miners to Design, Prism, or Relearn docs as live work. **When a challenge product or public API changes**, agents **must** update: @@ -112,6 +114,7 @@ Match CI (`.github/workflows/ci.yml`): | Need | Start here | |------|------------| +| Purpose, research reuse, and proposal vs implementation | [`docs/OVERVIEW.md`](docs/OVERVIEW.md), [`docs/WHITEPAPER.md`](docs/WHITEPAPER.md) | | System map / process topology | [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | | Cortex vs leftover `base` names | [`docs/NAMING.md`](docs/NAMING.md) | | Deploy / Compose / DO topology | [`deploy/README.md`](deploy/README.md) + [`deploy/AGENTS.md`](deploy/AGENTS.md) | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4dced9b00..3039f4ef1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,9 @@ # Contributing to Cortex -This is the Rust control-plane monorepo for the Cortex Bittensor subnet -([`CortexLM/cortex`](https://github.com/CortexLM/cortex)). +This repository implements Cortex, an autonomous research network on Bittensor. +Start with the [overview](docs/OVERVIEW.md) and +[whitepaper comparison](docs/WHITEPAPER.md) for the purpose and current limits. +Network services use Rust; the research evaluation image uses Python. ## Before you start @@ -33,8 +35,14 @@ cargo run -p xtask -- consensus-lint cargo run -p xtask -- spec-check cargo run -p xtask -- design-check cargo run -p xtask -- external-docs-check +cargo clippy -p validator-bin --features dcap --all-targets -- -D warnings +bash deploy/scripts/assert-compose-matrix.sh ``` +For changes to the Python evaluation image, install its development dependencies +in an isolated environment and run `python -m pytest eval/tests` from the repo +root. GPU/runtime validation is separate from these contract tests. + Local subnet stack (Docker Compose, secrets via age): see [deploy/README.md](deploy/README.md) and [docs/runbooks/local-testnet-e2e.md](docs/runbooks/local-testnet-e2e.md). diff --git a/Cargo.lock b/Cargo.lock index 7bd9222e4..eb6da9e2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -342,7 +342,6 @@ dependencies = [ "db", "hex", "serde", - "serde_json", "tokio", "tracing", "trustroot", @@ -682,8 +681,6 @@ dependencies = [ "merkle", "parity-scale-codec", "proptest", - "rand_core 0.6.4", - "schnorrkel", "sha2 0.10.9", "thiserror 2.0.19", "trustroot", @@ -771,9 +768,6 @@ name = "chain" version = "0.1.0" dependencies = [ "parity-scale-codec", - "reqwest 0.12.28", - "serde", - "serde_json", ] [[package]] @@ -790,10 +784,8 @@ dependencies = [ "rand_core 0.6.4", "reqwest 0.12.28", "schnorrkel", - "serde", "serde_json", "sha2 0.10.9", - "thiserror 2.0.19", "tle", "tokio", "tracing", @@ -1123,7 +1115,6 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.9", - "tokio", "tracing", ] @@ -1484,7 +1475,6 @@ dependencies = [ "axum", "bundle", "chain", - "config", "crosscheck", "crypto", "hex", @@ -1493,10 +1483,7 @@ dependencies = [ "serde", "sha2 0.10.9", "thiserror 2.0.19", - "tokio", - "tracing", "trustroot", - "wiremock", ] [[package]] @@ -2100,7 +2087,6 @@ dependencies = [ "async-trait", "prism-lium", "prism-lium-types", - "tokio", ] [[package]] @@ -3302,10 +3288,8 @@ dependencies = [ "prism-recipe", "prism-tree", "reqwest 0.12.28", - "serde", "serde_json", "sha2 0.10.9", - "thiserror 2.0.19", "tokio", "tracing", "wiremock", @@ -3357,12 +3341,10 @@ dependencies = [ "async-trait", "hex", "reqwest 0.12.28", - "serde", "serde_json", "sha2 0.10.9", "thiserror 2.0.19", "tokio", - "tower", "wiremock", ] @@ -3379,7 +3361,6 @@ dependencies = [ "serde", "serde_json", "sqlx", - "thiserror 2.0.19", "tokio", "tracing", ] @@ -3401,7 +3382,6 @@ version = "0.1.0" dependencies = [ "hex", "serde", - "serde_json", "sha2 0.10.9", "tempfile", "thiserror 2.0.19", @@ -3549,7 +3529,6 @@ dependencies = [ "proof-score", "proof-task", "serde", - "serde_json", "sha2 0.10.9", "thiserror 2.0.19", ] @@ -4920,7 +4899,6 @@ dependencies = [ "metrics", "metrics-exporter-prometheus", "serde", - "serde_json", "thiserror 2.0.19", "tokio", "tower", @@ -5453,7 +5431,6 @@ dependencies = [ "tempfile", "thiserror 2.0.19", "tokio", - "tracing", "wiremock", ] @@ -5461,7 +5438,6 @@ dependencies = [ name = "updater-bin" version = "0.1.0" dependencies = [ - "tokio", "tracing", "tracing-subscriber", "updater", @@ -5528,7 +5504,6 @@ dependencies = [ "gateway", "hex", "keystore", - "parity-scale-codec", "reqwest 0.12.28", "serde", "serde_json", @@ -6144,7 +6119,6 @@ dependencies = [ "serde_yaml", "sha2 0.10.9", "twox-hash", - "walkdir", ] [[package]] diff --git a/README.md b/README.md index f102a29c7..fca1acdb3 100644 --- a/README.md +++ b/README.md @@ -3,147 +3,121 @@ [![CI](https://github.com/CortexLM/cortex/actions/workflows/ci.yml/badge.svg)](https://github.com/CortexLM/cortex/actions/workflows/ci.yml) [![License](https://img.shields.io/github/license/CortexLM/cortex)](LICENSE) -Rust control plane for [Bittensor](https://bittensor.com/) subnet **100**: miners submit over HTTP, the master scores two live challenges, validators verify a sealed weight bundle and `set_weights`. - -## Features - -| Capability | Detail | -|---|---| -| **Two live challenges** | **Bounty** (`bounty`, 2000 bps) and **Proof** (`proof`, 8000 bps). Sum is 10000. `relearn`, `relearn-image`, `relearn-agent`, `relearn-mm`, `design`, and `prism` are off. | -| **One public gateway** | [https://network.cortex.foundation](https://network.cortex.foundation) — `ctx` or `curl`. | -| **Master-only scoring** | Gateway + `bounty-challenge` + `proof-challenge` run on the owner host. Validators do not re-run evals. | -| **Fail-closed scoring** | Empty Proof eval digest, no open topic, or an unreadable Bounty feed answers **503** instead of inventing a verdict. | -| **Sealed weights** | Gateway seals an epoch bundle. Validators check it against owner-signed files on disk, then submit (CRV4 when enabled). | - -Some env vars and host paths still spell `BASE_*`. That is leftover naming, not a second product. See [docs/NAMING.md](docs/NAMING.md). - -## Quickstart +**An autonomous research network.** + +Cortex is building a shared research process on +[Bittensor](https://bittensor.com/) subnet **100**. Its goal is to turn independent +AI experiments into reusable knowledge that improves the network's shared +software, training methods, and data practices. + +[Understand Cortex in five minutes](docs/OVERVIEW.md) · +[Whitepaper](whitepaper.pdf) · +[Documentation](docs/README.md) · +[Start contributing research](docs/external-miner/README.md) + +## Why Cortex exists + +A competition that only selects a finished model checkpoint can lose the most +useful part of research: how the result was obtained. If recipes stay private, +the next contributor must discover them again. A fixed, visible benchmark can +also reward tuning to the test rather than an improvement that works elsewhere. + +Cortex's proposed alternative is **verified research as the unit of work**: +a claim, reproducible code, a data manifest, a compute budget, and measured +evidence. The aim is to retain useful methods from many contributors, not just +the weights of one winner. + +The [whitepaper, §7](whitepaper.pdf#page=4) describes the next step: an autonomous +agent combines proven findings into a proposed update to the shared stack. +That update must pass evaluation against the current stack before adoption. +**This synthesis agent is a goal, not a shipped component.** See +[the paper-to-code comparison](docs/WHITEPAPER.md) for what exists today. + +## The research loop + +1. **Set a research goal.** Operators publish a question, evaluation rules, and a + measured reference result. +2. **Run experiments.** Contributors, called miners, submit a claim with the code, + experiment files, and compute budget needed to check it. +3. **Check the result.** The intended evaluation reproduces the experiment and + measures it on private test data against the reference. +4. **Reward useful work.** Challenge scores feed signed reward calculations that + validators verify before submitting weights to Bittensor. +5. **Build on what worked.** Preserve the research recipe for reuse. Automated + synthesis and verified adoption into a shared stack remain future work. + +Autonomous describes the research workflow the network is building. It does +**not** mean the current system operates without people: operators choose topics, +configure evaluation, and adjudicate bug reports. Validators verify the signed +results and reward calculation; they do not independently repeat every experiment. + +## Two ways to contribute + +| Challenge | Useful work | Share of network emissions | +|-----------|-------------|----------------------------| +| **Proof** | Reproducible AI research against published topics | **80%** | +| **Bounty** | Verified bugs in Cortex products and backend services | **20%** | + +Proof rewards follow each topic's rules: the best result wins, or qualifying +discoveries share rewards. Bounty rewards depend on report accuracy and severity. +These are configured allocations, **not guaranteed earnings or investment returns**. + +## What exists today + +This repository includes the submission tools, challenge services, evaluation +components, validator software, and deployment tooling. + +Implemented components are not the same as an end-to-end research system: + +- **Proof** has signed topics, submission intake, evaluation guards, and + winner/discovery payout functions. The Python judge is still partial, submission + state is in memory, and the service does not yet drive automatic reward-leaf + emission. The complete autonomous research loop is not implemented. +- Proof evaluation needs an open topic, verified private test data, a sealed + baseline, a pinned evaluation image, and configured Lium and judge access. +- **Bounty** needs a readable public scoring feed from CortexLM/backend. A valid + report without an assigned severity cannot earn a reward. +- Missing scoring prerequisites cause **503** refusals. Check `ctx status` before + spending compute, but do not treat readiness alone as proof of reproduction or + an end-to-end payment path. + +See [implementation status and known limits](docs/COMPLETENESS.md). This README +describes the software, not a claim that every research topic is open or every +deployment is ready. + +## Get started + +The public gateway is [https://network.cortex.foundation](https://network.cortex.foundation). + +Install the `ctx` command-line tool using +[`scripts/install-ctx.sh`](scripts/install-ctx.sh): ```bash curl -fsSL https://raw.githubusercontent.com/CortexLM/cortex/main/scripts/install-ctx.sh | sh - -ctx challenges # the two live challenges and what they pay for -ctx status # can each challenge score right now, and is the epoch sealed -``` - -`ctx` lives in [`bins/ctx`](bins/ctx). A local stack uses `--gateway http://127.0.0.1:8080`. Never put a mnemonic or a challenge signing key in a miner client. Check `can_score` before you spend GPU time or Lium rent. - -## Mine - -| Challenge | id | Emission | Start with | Guide | -|-----------|-----|----------|------------|-------| -| **Bounty** | `bounty` | 2000 bps | `ctx bounty pair` then `ctx bounty report` | [How to mine — Bounty](docs/external-miner/bounty.md) | -| **Proof** | `proof` | 8000 bps | `ctx proof topics` then `ctx proof submit` | [How to mine — Proof](docs/external-miner/proof.md) | - -A→Z index: [docs/external-miner/](docs/external-miner/README.md). Install or 503 issues: [troubleshoot](docs/external-miner/troubleshoot.md). - -### Bounty - -Pair a Bittensor hotkey to a **dedicated** Cortex Chat mining account, then file real bugs on Cortex product and backend surfaces. Operators adjudicate (`valid` / `already_fixed_not_prod` / `invalid_malicious` / `duplicate`). Pay is precision times severity; an unpriced `valid` row is not creditable. - -Scoring **reads** the CortexLM/backend public JSON feed. This repo does not serve a public leaderboard. If that feed is unreadable, reports answer **503** and the share pays nobody. - -### Proof - -Submit **claim + code + FLOPs + artifact** against an operator-published `topic_id`. Topics are signed documents, not a catalog in git. Each open topic pays `wta` or `discovery`. Your paid score is the **sum** of per-topic masses. - -The judge is a digest-pinned eval image plus a live `InferenceOffer`. The pin is in [`config/proof-pin.toml`](config/proof-pin.toml) (`ghcr.io/cortexlm/proof-eval`, digest `sha256:78b614a1…`). Do not invent a digest. Empty digest, unwired harvest, unsealed baseline, or zero open topics → **503**. Proof miners pay Lium (`LIUM_API_KEY` / `X-Lium-Api-Key`); `ctx` forwards the key and never prints it. - -`ctx proof topics` never leaks holdout records. - -## Validate - -Validators pull the sealed bundle, verify it, and submit weights on-chain. They do not run Bounty adjudication or Proof harvest. - -1. Pull `GET /v1/weights/latest` from the master gateway. -2. Verify signatures, completeness, and the owner trust root on **local disk** (`config/challenges.toml`, `config/measurements.toml`). -3. `set_weights` on-chain (CRV4 timelock when enabled). - -Do not submit an unsealed burn vector (`sealed: false`, uid 0 = 100%), and do not submit a persisted last-known-good seal while latest is unsealed. - -Guide: [How to validate](docs/external-miner/validators.md) · compose role: [`deploy/compose/role-validator.yml`](deploy/compose/role-validator.yml). - -## Architecture - -```text - Miners (ctx / curl) - │ HTTP submit - ▼ - ┌─────────────────────────────────────┐ - │ Master host │ - │ postgres · gateway │ - │ bounty-challenge · proof-challenge │ - └──────────────────┬────────────────────┘ - │ GET /v1/weights/latest - │ (sealed epoch bundle) - ▼ - Validator hosts - local trust roots on disk - │ - ▼ - set_weights (CRV4) +ctx challenges +ctx status ``` -One epoch: challenge services sign leaves → the gateway seals `EpochBundleV1` → validators fetch, verify, recompute, and submit. - -| Path | Role | -|------|------| -| [`bins/`](bins/) | `gateway`, `validator`, `ctx`, challenge services, `updater` | -| [`crates/`](crates/) | Shared libraries (bundle, aggregate, trustroot, chain, …) | -| [`deploy/`](deploy/) | Compose matrix, Terraform, digest pins | -| [`config/`](config/) | Trust-root TOML, Proof pin | -| [`docs/`](docs/) | Specs, runbooks, miner guides | - -Full map: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). Byte contract: [docs/BUNDLE_SPEC.md](docs/BUNDLE_SPEC.md). - -## Docs +Then follow the [Proof guide](docs/external-miner/proof.md), +[Bounty guide](docs/external-miner/bounty.md), or +[validator guide](docs/external-miner/validators.md). +Never put wallet recovery phrases or challenge signing keys in a miner client. -| Doc | Audience | -|-----|----------| -| [docs/external-miner/README.md](docs/external-miner/README.md) | Miners | -| [docs/external-miner/bounty.md](docs/external-miner/bounty.md) | Bounty miners | -| [docs/external-miner/proof.md](docs/external-miner/proof.md) | Proof miners | -| [docs/external-miner/validators.md](docs/external-miner/validators.md) | Validators | -| [docs/BOUNTY.md](docs/BOUNTY.md) / [docs/PROOF.md](docs/PROOF.md) | Operator challenge specs | -| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | Process topology | -| [docs/COMPLETENESS.md](docs/COMPLETENESS.md) | What is actually wired | -| [docs/THREAT_MODEL.md](docs/THREAT_MODEL.md) | What is claimed, and what is not | -| [docs/runbooks/](docs/runbooks/) | Local e2e, staging, rotation, failover | -| [deploy/README.md](deploy/README.md) | Compose / droplets | -| [whitepaper.pdf](whitepaper.pdf) | Whitepaper | -| [SUPPORT.md](SUPPORT.md) | How to get help | +## Explore the project -## Contributing - -Read [CONTRIBUTING.md](CONTRIBUTING.md) and [AGENTS.md](AGENTS.md) before you open a PR. - -Rust **1.96.0** via [`rust-toolchain.toml`](rust-toolchain.toml). `unsafe_code` is forbidden; `unwrap` / `expect` stay in tests. - -```bash -cargo fmt --all -- --check -cargo clippy --workspace --all-targets -- -D warnings -cargo test --workspace -cargo deny check -cargo run -p xtask -- loc-cap -cargo run -p xtask -- consensus-lint -cargo run -p xtask -- spec-check -cargo run -p xtask -- design-check -cargo run -p xtask -- external-docs-check -``` - -Local smoke (Docker Compose, testnet 541): - -```bash -./deploy/scripts/materialize-env.sh -./deploy/scripts/local-e2e.sh --smoke -``` +| I want to… | Start here | +|------------|------------| +| Understand the opportunity and current limits | [Project overview](docs/OVERVIEW.md) | +| Find a guide | [Documentation index](docs/README.md) | +| Understand the software | [Architecture](docs/ARCHITECTURE.md) | +| Run the network | [Deployment](deploy/README.md) | +| Contribute code | [Contributing](CONTRIBUTING.md) | +| Get help or report a vulnerability | [Support](SUPPORT.md) · [Security](SECURITY.md) | -- Target **`main`**. Subject: `type(scope): summary` (lowercase, ≤72 chars). -- Frozen specs are pinned by xtask. Do not rewrite incentive or consensus semantics in a drive-by. -- Do not rename `BASE_*` env vars, `/opt/base` paths, or `base-*-v1` domain tags. -- PRs need a [Greptile](https://greptile.com) review (`.greptile/`). If the bot is silent, comment `@greptileai review`. -- Security: [SECURITY.md](SECURITY.md). +The implementation uses Rust, with Python for research evaluation. These are +implementation choices, not the definition of Cortex. Historical `BASE_*` names +remain where required for compatibility, as explained in [Naming](docs/NAMING.md). ## License -Apache License 2.0 — see [LICENSE](LICENSE). +Apache License 2.0. See [LICENSE](LICENSE). diff --git a/SECURITY.md b/SECURITY.md index cd7928905..02273c9ab 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -21,7 +21,7 @@ Please include: ## Scope -In scope: this control-plane repo — gateway, validator, challenge services, +In scope: this network implementation repo — gateway, validator, challenge services, deploy compose/scripts as documented, and the sealed-weight path. Out of scope for this document: miner-submitted harnesses (untrusted by diff --git a/SUPPORT.md b/SUPPORT.md index 79e635f11..d0f99f52c 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -4,7 +4,7 @@ | Kind | Where | |------|--------| -| Bug in this control plane | [GitHub issue — bug](https://github.com/CortexLM/cortex/issues/new?template=bug.yml) | +| Bug in the network software | [GitHub issue — bug](https://github.com/CortexLM/cortex/issues/new?template=bug.yml) | | Feature or docs change | [GitHub issue — feature](https://github.com/CortexLM/cortex/issues/new?template=feature.yml) | | Miner submit / scoring questions | [docs/external-miner/](docs/external-miner/) and the public miner repos linked there | | Operator deploy | [deploy/README.md](deploy/README.md), [docs/runbooks/](docs/runbooks/) | @@ -18,8 +18,10 @@ repo. Mentions of maintainers go through GitHub and ## What this repo is (and is not) -- **Is:** Cortex Rust workspace — gateway, validator, bounty and proof - challenges, deploy matrix. +- **Is:** the implementation of Cortex's autonomous research network: gateway, + validators, Bounty and Proof services, research evaluation, and deployment tools. +- **Start here:** [overview](docs/OVERVIEW.md) for the purpose; + [implementation status](docs/COMPLETENESS.md) for current limits. - **Is not:** a miner SDK rewrite, a protocol fork, or a live Relearn / Design / Prism product. diff --git a/bins/updater/Cargo.toml b/bins/updater/Cargo.toml index 00b71a7e0..bf7b53551 100644 --- a/bins/updater/Cargo.toml +++ b/bins/updater/Cargo.toml @@ -14,7 +14,6 @@ path = "src/main.rs" [dependencies] updater = { path = "../../crates/updater" } -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } diff --git a/crates/attest-http/Cargo.toml b/crates/attest-http/Cargo.toml index aaa76c399..b0c5dc93f 100644 --- a/crates/attest-http/Cargo.toml +++ b/crates/attest-http/Cargo.toml @@ -21,7 +21,6 @@ crypto = { path = "../crypto" } db = { path = "../db" } trustroot = { path = "../trustroot" } serde = { version = "1", features = ["derive"] } -serde_json = "1" hex = "0.4" tokio = { version = "1", features = ["macros", "rt-multi-thread", "sync"] } tracing = "0.1" diff --git a/crates/bundle/Cargo.toml b/crates/bundle/Cargo.toml index 1d587e38a..91dccb04b 100644 --- a/crates/bundle/Cargo.toml +++ b/crates/bundle/Cargo.toml @@ -21,8 +21,6 @@ thiserror = "2" [dev-dependencies] proptest = "1" -rand_core = { version = "0.6", features = ["getrandom"] } -schnorrkel = { version = "0.11.5", default-features = false, features = ["std", "getrandom"] } [lints] workspace = true diff --git a/crates/chain-live/Cargo.toml b/crates/chain-live/Cargo.toml index 6986b15ff..b532c994d 100644 --- a/crates/chain-live/Cargo.toml +++ b/crates/chain-live/Cargo.toml @@ -19,10 +19,8 @@ parity-scale-codec = { version = "3.7", default-features = false, features = ["d rand_core = "0.6.4" reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] } schnorrkel = { version = "0.11.5", default-features = false, features = ["std"] } -serde = { version = "1", features = ["derive"] } serde_json = "1" sha2 = "0.10" -thiserror = "2" # Pin matches subtensor / bittensor_drand (wire-compatible CRV4 ciphertext). tle = { git = "https://github.com/ideal-lab5/timelock", rev = "5416406cfd32799e31e1795393d4916894de4468", default-features = false, features = ["std"] } tracing = "0.1" diff --git a/crates/chain/Cargo.toml b/crates/chain/Cargo.toml index a415b1010..dff919c70 100644 --- a/crates/chain/Cargo.toml +++ b/crates/chain/Cargo.toml @@ -8,15 +8,8 @@ repository.workspace = true rust-version.workspace = true publish = false -[features] -default = [] -live = ["dep:reqwest", "dep:serde_json", "dep:serde"] - [dependencies] parity-scale-codec = { version = "3.7", default-features = false, features = ["derive"] } -reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"], optional = true } -serde = { version = "1", features = ["derive"], optional = true } -serde_json = { version = "1", optional = true } [lints] workspace = true diff --git a/crates/chain/src/lib.rs b/crates/chain/src/lib.rs index 5eee844d9..62c182720 100644 --- a/crates/chain/src/lib.rs +++ b/crates/chain/src/lib.rs @@ -1,4 +1,4 @@ -//! Chain client abstraction for base validators/miners. +//! Chain client abstraction for Cortex validators and miners. //! //! # Commit-reveal (CRV4) //! @@ -21,8 +21,7 @@ //! # Implementations //! //! - [`FakeChain`] — deterministic in-memory (required for unit tests). -//! - [`NotImplementedChain`] — stub returning [`ChainError::NotImplemented`] (SDK/live later). -//! - [`LiveRpcChain`] (feature `live`) — minimal JSON-RPC for `current_block` / headers. +//! - `chain_live::LiveChainClient` in the `chain-live` crate — production reads and signed submissions. #![forbid(unsafe_code)] @@ -172,11 +171,6 @@ pub enum ChainError { /// Human-readable alternate path. alternate: &'static str, }, - /// Operation not wired yet (live SDK / RPC). - NotImplemented { - /// What was requested. - what: &'static str, - }, /// Weights rate limit hit; caller should retry. RateLimited { /// Optional retry-after hint in blocks. @@ -200,9 +194,6 @@ impl fmt::Display for ChainError { "commit-reveal disabled; use alternate path `{alternate}` (see metadata/testnet.lock call_indices.set_weights)" ) } - Self::NotImplemented { what } => { - write!(f, "chain client not implemented: {what}") - } Self::RateLimited { retry_after_blocks } => match retry_after_blocks { Some(n) => write!(f, "weights rate limited; retry after {n} blocks"), None => write!(f, "weights rate limited"), @@ -697,327 +688,6 @@ impl ChainClient for FakeChain { } } -// --------------------------------------------------------------------------- -// Stub / live -// --------------------------------------------------------------------------- - -/// Placeholder client until a full SDK-backed impl lands. -/// -/// TODO(task-13-followup): wire bittensor-core or expand [`LiveRpcChain`] beyond tip reads. -#[derive(Debug, Default, Clone, Copy)] -pub struct NotImplementedChain; - -impl ChainClient for NotImplementedChain { - fn current_block(&self) -> Result { - Err(ChainError::NotImplemented { - what: "current_block", - }) - } - - fn block_hash(&self, _n: u64) -> Result<[u8; 32], ChainError> { - Err(ChainError::NotImplemented { what: "block_hash" }) - } - - fn metagraph_at(&self, _block_hash: &[u8; 32]) -> Result { - Err(ChainError::NotImplemented { - what: "metagraph_at", - }) - } - - fn subnet_owner_hotkey(&self, _netuid: u16) -> Result, ChainError> { - Err(ChainError::NotImplemented { - what: "subnet_owner_hotkey", - }) - } - - fn axon(&self, _netuid: u16, _hotkey: &[u8]) -> Result, ChainError> { - Err(ChainError::NotImplemented { what: "axon" }) - } - - fn axons(&self, _netuid: u16) -> Result, AxonInfo)>, ChainError> { - Err(ChainError::NotImplemented { what: "axons" }) - } - - fn commit_reveal_enabled(&self, _netuid: u16) -> Result { - Err(ChainError::NotImplemented { - what: "commit_reveal_enabled", - }) - } - - fn commit_reveal_version(&self, _netuid: u16) -> Result { - Err(ChainError::NotImplemented { - what: "commit_reveal_version", - }) - } - - fn tempo(&self, _netuid: u16) -> Result { - Err(ChainError::NotImplemented { what: "tempo" }) - } - - fn reveal_period_epochs(&self, _netuid: u16) -> Result { - Err(ChainError::NotImplemented { - what: "reveal_period_epochs", - }) - } - - fn block_time(&self) -> Result { - Err(ChainError::NotImplemented { what: "block_time" }) - } - - fn last_epoch_block(&self, _netuid: u16) -> Result { - Err(ChainError::NotImplemented { - what: "last_epoch_block", - }) - } - - fn pending_epoch_at(&self, _netuid: u16) -> Result { - Err(ChainError::NotImplemented { - what: "pending_epoch_at", - }) - } - - fn subnet_epoch_index(&self, _netuid: u16) -> Result { - Err(ChainError::NotImplemented { - what: "subnet_epoch_index", - }) - } - - fn blocks_since_last_step(&self, _netuid: u16) -> Result { - Err(ChainError::NotImplemented { - what: "blocks_since_last_step", - }) - } - - fn submit_timelocked_weights( - &self, - _mecid: u8, - _payload: WeightsTlockPayload, - _reveal_round: u64, - ) -> Result<(), ChainError> { - Err(ChainError::NotImplemented { - what: "submit_timelocked_weights", - }) - } - - fn set_weights( - &self, - _netuid: u16, - _uids: Vec, - _values: Vec, - _version_key: u64, - ) -> Result<(), ChainError> { - Err(ChainError::NotImplemented { - what: "set_weights", - }) - } -} - -#[cfg(feature = "live")] -mod live { - use super::{AxonInfo, ChainClient, ChainError, Metagraph, WeightsTlockPayload}; - use serde_json::{json, Value}; - - /// Finney testnet default (same as `metadata/testnet.lock` / config). - pub const DEFAULT_TESTNET_ENDPOINT: &str = "wss://test.finney.opentensor.ai:443"; - - /// Minimal HTTPS JSON-RPC chain client (no bittensor-core). - /// - /// Fully implements only tip/`current_block` and `block_hash` for smoke tests; - /// other methods return [`ChainError::NotImplemented`] until expanded. - #[derive(Debug)] - pub struct LiveRpcChain { - http: reqwest::blocking::Client, - endpoint: String, - } - - impl LiveRpcChain { - /// Connect using `wss://` or `https://` endpoint (WSS rewritten to HTTPS). - /// - /// # Errors - /// - /// HTTP client build failure. - pub fn connect(endpoint: &str) -> Result { - let http = reqwest::blocking::Client::builder() - .timeout(std::time::Duration::from_secs(30)) - .build() - .map_err(|e| ChainError::Other(format!("http client: {e}")))?; - Ok(Self { - http, - endpoint: http_endpoint(endpoint), - }) - } - - fn rpc(&self, method: &str, params: Value) -> Result { - let body = json!({ - "jsonrpc": "2.0", - "id": 1, - "method": method, - "params": params, - }); - let resp = self - .http - .post(&self.endpoint) - .json(&body) - .send() - .map_err(|e| ChainError::Other(format!("rpc send: {e}")))?; - let v: Value = resp - .json() - .map_err(|e| ChainError::Other(format!("rpc json: {e}")))?; - if let Some(err) = v.get("error") { - return Err(ChainError::Other(format!("rpc error: {err}"))); - } - v.get("result") - .cloned() - .ok_or_else(|| ChainError::Other("rpc missing result".into())) - } - } - - fn http_endpoint(endpoint: &str) -> String { - if let Some(rest) = endpoint.strip_prefix("wss://") { - format!("https://{rest}") - } else if let Some(rest) = endpoint.strip_prefix("ws://") { - format!("http://{rest}") - } else { - endpoint.to_owned() - } - } - - fn parse_hex_u64(hex_num: &str) -> Result { - let s = hex_num.strip_prefix("0x").unwrap_or(hex_num); - u64::from_str_radix(s, 16).map_err(|e| ChainError::Other(format!("bad hex u64: {e}"))) - } - - impl ChainClient for LiveRpcChain { - fn current_block(&self) -> Result { - let header = self.rpc("chain_getHeader", json!([]))?; - let num = header - .get("number") - .and_then(Value::as_str) - .ok_or_else(|| ChainError::Other("header.number missing".into()))?; - parse_hex_u64(num) - } - - fn block_hash(&self, n: u64) -> Result<[u8; 32], ChainError> { - let hex_n = format!("0x{n:x}"); - let result = self.rpc("chain_getBlockHash", json!([hex_n]))?; - let s = result - .as_str() - .ok_or_else(|| ChainError::Other("block hash not string".into()))?; - let s = s.strip_prefix("0x").unwrap_or(s); - if s.len() != 64 { - return Err(ChainError::Other(format!( - "expected 32-byte hash, got len {}", - s.len() / 2 - ))); - } - let mut out = [0_u8; 32]; - for i in 0..32 { - out[i] = u8::from_str_radix(&s[i * 2..i * 2 + 2], 16) - .map_err(|e| ChainError::Other(format!("hex: {e}")))?; - } - Ok(out) - } - - fn metagraph_at(&self, _block_hash: &[u8; 32]) -> Result { - Err(ChainError::NotImplemented { - what: "live metagraph_at", - }) - } - - fn subnet_owner_hotkey(&self, _netuid: u16) -> Result, ChainError> { - Err(ChainError::NotImplemented { - what: "live subnet_owner_hotkey", - }) - } - - fn axon(&self, _netuid: u16, _hotkey: &[u8]) -> Result, ChainError> { - Err(ChainError::NotImplemented { what: "live axon" }) - } - - fn axons(&self, _netuid: u16) -> Result, AxonInfo)>, ChainError> { - Err(ChainError::NotImplemented { what: "live axons" }) - } - - fn commit_reveal_enabled(&self, _netuid: u16) -> Result { - Err(ChainError::NotImplemented { - what: "live commit_reveal_enabled", - }) - } - - fn commit_reveal_version(&self, _netuid: u16) -> Result { - Err(ChainError::NotImplemented { - what: "live commit_reveal_version", - }) - } - - fn tempo(&self, _netuid: u16) -> Result { - Err(ChainError::NotImplemented { what: "live tempo" }) - } - - fn reveal_period_epochs(&self, _netuid: u16) -> Result { - Err(ChainError::NotImplemented { - what: "live reveal_period_epochs", - }) - } - - fn block_time(&self) -> Result { - Err(ChainError::NotImplemented { - what: "live block_time", - }) - } - - fn last_epoch_block(&self, _netuid: u16) -> Result { - Err(ChainError::NotImplemented { - what: "live last_epoch_block", - }) - } - - fn pending_epoch_at(&self, _netuid: u16) -> Result { - Err(ChainError::NotImplemented { - what: "live pending_epoch_at", - }) - } - - fn subnet_epoch_index(&self, _netuid: u16) -> Result { - Err(ChainError::NotImplemented { - what: "live subnet_epoch_index", - }) - } - - fn blocks_since_last_step(&self, _netuid: u16) -> Result { - Err(ChainError::NotImplemented { - what: "live blocks_since_last_step", - }) - } - - fn submit_timelocked_weights( - &self, - _mecid: u8, - _payload: WeightsTlockPayload, - _reveal_round: u64, - ) -> Result<(), ChainError> { - Err(ChainError::NotImplemented { - what: "live submit_timelocked_weights", - }) - } - - fn set_weights( - &self, - _netuid: u16, - _uids: Vec, - _values: Vec, - _version_key: u64, - ) -> Result<(), ChainError> { - Err(ChainError::NotImplemented { - what: "live set_weights", - }) - } - } -} - -#[cfg(feature = "live")] -pub use live::{LiveRpcChain, DEFAULT_TESTNET_ENDPOINT}; - // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- @@ -1179,14 +849,6 @@ mod tests { )); } - #[test] - fn s3_not_implemented_stub_errors_clearly() { - let c = NotImplementedChain; - let err = c.current_block().expect_err("stub"); - assert!(matches!(err, ChainError::NotImplemented { .. })); - assert!(err.to_string().contains("not implemented")); - } - #[test] fn s3_unsupported_cr_version_rejected_on_submit() { let chain = FakeChain::new(FakeChainConfig { @@ -1353,26 +1015,4 @@ mod tests { .expect("third succeeds"); assert_eq!(chain.submissions().len(), 1); } - - /// Live testnet smoke: `current_block() > 0`. - /// - /// Run with: - /// `cargo test -p chain --features live testnet_current_block -- --ignored --nocapture` - #[test] - #[ignore = "requires network access to finney testnet"] - fn testnet_current_block_positive() { - #[cfg(feature = "live")] - { - let client = LiveRpcChain::connect(DEFAULT_TESTNET_ENDPOINT).expect("connect testnet"); - let n = client.current_block().expect("current_block"); - assert!(n > 0, "expected tip > 0, got {n}"); - } - #[cfg(not(feature = "live"))] - { - panic!( - "enable --features live to run testnet_current_block_positive against {}", - "wss://test.finney.opentensor.ai:443" - ); - } - } } diff --git a/crates/crosscheck/Cargo.toml b/crates/crosscheck/Cargo.toml index 16f2a75de..470daeb10 100644 --- a/crates/crosscheck/Cargo.toml +++ b/crates/crosscheck/Cargo.toml @@ -23,7 +23,6 @@ tracing = "0.1" [dev-dependencies] crypto = { path = "../crypto" } sha2 = "0.10" -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } [lints] workspace = true diff --git a/crates/dissent/Cargo.toml b/crates/dissent/Cargo.toml index 5bcd49c25..8f6ac16b0 100644 --- a/crates/dissent/Cargo.toml +++ b/crates/dissent/Cargo.toml @@ -13,7 +13,6 @@ axum = { version = "0.8", default-features = false, features = ["http1", "tokio" aggregate = { path = "../aggregate" } bundle = { path = "../bundle" } chain = { path = "../chain" } -config = { path = "../config" } crosscheck = { path = "../crosscheck" } crypto = { path = "../crypto" } trustroot = { path = "../trustroot" } @@ -22,13 +21,7 @@ metrics = "0.24" parity-scale-codec = { version = "3.7", default-features = false, features = ["derive"] } serde = { version = "1", features = ["derive"] } thiserror = "2" -tracing = "0.1" sha2 = "0.10" -[dev-dependencies] -crypto = { path = "../crypto" } -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } -wiremock = "0.6" - [lints] workspace = true diff --git a/crates/gateway/src/proxy.rs b/crates/gateway/src/proxy.rs index 71fa706ff..4dd955334 100644 --- a/crates/gateway/src/proxy.rs +++ b/crates/gateway/src/proxy.rs @@ -6,7 +6,7 @@ use axum::body::Body; use axum::extract::{Path, Request, State}; -use axum::http::{header, HeaderMap, HeaderName, HeaderValue, Method, StatusCode, Uri}; +use axum::http::{header, HeaderMap, HeaderName, HeaderValue, Method, StatusCode}; use axum::response::{IntoResponse, Response}; use axum::routing::any; use axum::Router; @@ -218,13 +218,6 @@ async fn forward( ForwardResult::Ok(response) } -/// Build upstream URL including the original request query string. -#[must_use] -#[allow(dead_code)] -pub fn upstream_uri(base: &str, rest: &str, original: &Uri) -> String { - upstream_url(base, rest, original.query()) -} - /// Collapse `.` / empty / `..` segments the same way `url`/`reqwest` will before /// the upstream request — used so gateway gates cannot be skipped via `v1/./admin`. #[must_use] @@ -317,10 +310,15 @@ mod tests { } #[test] - fn upstream_uri_joins_path_and_query() { - let u: Uri = "http://gw/challenge/c1/v1/score?x=1".parse().unwrap(); - let out = upstream_uri("http://127.0.0.1:9", "v1/score", &u); - assert_eq!(out, "http://127.0.0.1:9/v1/score?x=1"); + fn upstream_url_joins_path_and_query() { + assert_eq!( + upstream_url("http://127.0.0.1:9", "v1/score", Some("x=1")), + "http://127.0.0.1:9/v1/score?x=1" + ); + assert_eq!( + upstream_url("http://127.0.0.1:9/", "/v1/score", None), + "http://127.0.0.1:9/v1/score" + ); } #[test] diff --git a/crates/harvest-pod/Cargo.toml b/crates/harvest-pod/Cargo.toml index a4048311c..3b707ead3 100644 --- a/crates/harvest-pod/Cargo.toml +++ b/crates/harvest-pod/Cargo.toml @@ -13,8 +13,5 @@ async-trait = "0.1" prism-lium = { path = "../prism-lium" } prism-lium-types = { path = "../prism-lium-types" } -[dev-dependencies] -tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] } - [lints] workspace = true diff --git a/crates/prism-lium/Cargo.toml b/crates/prism-lium/Cargo.toml index b18b39da6..f63f0d156 100644 --- a/crates/prism-lium/Cargo.toml +++ b/crates/prism-lium/Cargo.toml @@ -17,10 +17,8 @@ prism-recipe = { path = "../prism-recipe" } prism-tree = { path = "../prism-tree" } async-trait = "0.1" reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] } -serde = { version = "1", features = ["derive"] } serde_json = "1" sha2 = "0.10" -thiserror = "2" tokio = { version = "1", features = ["macros", "rt-multi-thread", "time", "sync", "process", "io-util"] } tracing = "0.1" diff --git a/crates/prism-lium/src/ssh.rs b/crates/prism-lium/src/ssh.rs index fa2c7b7fb..e7c0d3844 100644 --- a/crates/prism-lium/src/ssh.rs +++ b/crates/prism-lium/src/ssh.rs @@ -334,7 +334,6 @@ pub async fn ssh_exec_stdin( /// Successful SSH run output. #[derive(Debug, Clone)] pub struct SshExecOutput { - #[allow(dead_code)] pub returncode: i32, pub stdout: String, pub stderr: String, diff --git a/crates/prism-review/Cargo.toml b/crates/prism-review/Cargo.toml index 56e09804e..7721b23ac 100644 --- a/crates/prism-review/Cargo.toml +++ b/crates/prism-review/Cargo.toml @@ -12,14 +12,12 @@ publish = false async-trait = "0.1" hex = "0.4" reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] } -serde = { version = "1", features = ["derive"] } serde_json = "1" sha2 = "0.10" thiserror = "2" [dev-dependencies] tokio = { version = "1", features = ["macros", "rt-multi-thread"] } -tower = { version = "0.5", features = ["util"] } wiremock = "0.6" [lints] diff --git a/crates/prism-review/prompts/similarity_v2.md b/crates/prism-review/prompts/similarity_v2.md deleted file mode 100644 index 079cf12d3..000000000 --- a/crates/prism-review/prompts/similarity_v2.md +++ /dev/null @@ -1,37 +0,0 @@ -You are the PRISM architecture similarity judge (anti-farming) for a -pretraining-recipe challenge. - -The CANDIDATE below is the `architecture.py` of a miner submission. The -CORPUS lists reference architectures: the operator `baseline` and historical -miner submissions. Decide whether the CANDIDATE architecture is effectively -a copy of any corpus architecture. - -SCOPE: judge `architecture.py` ONLY. The companion `training.py` is NOT part -of this judgment — the same training script on two different architectures -is legitimate, and the same architecture with a different training script is -still an architecture copy. - -Definitions: -- `copied`: near-verbatim architecture, or trivial renaming/formatting - shuffles, or the same model definition with cosmetic deltas (renamed - identifiers, reordered methods, comment edits). Hard zero. -- `suspicious`: strong structural overlap (same layer stack, same shapes, - same forward flow) but rewritten enough to blur; flag for closer scrutiny. -- `original`: normal engineering resemblance, standard components (vanilla - transformer blocks, rotary embeddings, RMSNorm, …), or clear novelty. - Standard libraries/patterns NEVER count as copying. - -Shuffling the order of functions/classes does NOT matter. - -Output STRICT JSON only: -{"kind": "original|suspicious|copied", - "score": float 0..1, - "closest": "", - "evidence": [str, str, str]} -evidence: at most 3 short strings, no markdown. - -=== CANDIDATE architecture.py === -{ARCH} - -=== CORPUS (architecture.py only) === -{CORPUS} diff --git a/crates/prism-review/src/llm.rs b/crates/prism-review/src/llm.rs index 684472ffb..23b247376 100644 --- a/crates/prism-review/src/llm.rs +++ b/crates/prism-review/src/llm.rs @@ -1,7 +1,5 @@ //! `OpenRouter` chat client (master-side; pod never sees a key). -use std::path::PathBuf; - use async_trait::async_trait; use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT}; @@ -298,13 +296,6 @@ impl ReviewBackend for OpenRouterClient { } } -/// Default key path (documented for deployments). -#[must_use] -#[allow(dead_code)] // used by operators reading the crate public surface -pub fn default_key_path() -> PathBuf { - PathBuf::from("/run/base/openrouter/api_key") -} - #[cfg(test)] mod tests { #![allow(clippy::unwrap_used, clippy::expect_used)] diff --git a/crates/prism-review/src/prompts.rs b/crates/prism-review/src/prompts.rs index 88fe35789..308f04780 100644 --- a/crates/prism-review/src/prompts.rs +++ b/crates/prism-review/src/prompts.rs @@ -16,9 +16,6 @@ pub const REVIEW_PROMPT_V3: &str = include_str!("../prompts/review_v3.md"); /// /// v3: corpus is champions (top + ex-tops) + baseline; hard ban on citing /// standard LM components (`RMSNorm` / `RoPE` / `SwiGLU` / …) as plagiarism evidence. -#[allow(dead_code)] // retained for audit / diff against similarity-v3 -pub const SIMILARITY_PROMPT_V2: &str = include_str!("../prompts/similarity_v2.md"); -/// Current similarity prompt (v3). pub const SIMILARITY_PROMPT_V3: &str = include_str!("../prompts/similarity_v3.md"); /// Version string for the review prompt. diff --git a/crates/prism-store/Cargo.toml b/crates/prism-store/Cargo.toml index d4579a57e..b366047f6 100644 --- a/crates/prism-store/Cargo.toml +++ b/crates/prism-store/Cargo.toml @@ -21,7 +21,6 @@ sqlx = { version = "0.8", default-features = false, features = [ "postgres", "json", ] } -thiserror = "2" tracing = "0.1" [dev-dependencies] diff --git a/crates/prism-tree/Cargo.toml b/crates/prism-tree/Cargo.toml index 859e39f16..55b8adb5b 100644 --- a/crates/prism-tree/Cargo.toml +++ b/crates/prism-tree/Cargo.toml @@ -11,7 +11,6 @@ publish = false [dependencies] hex = "0.4" serde = { version = "1", features = ["derive"] } -serde_json = "1" sha2 = "0.10" thiserror = "2" diff --git a/crates/proof-score/src/lib.rs b/crates/proof-score/src/lib.rs index 230ed06a6..18f6cc95b 100644 --- a/crates/proof-score/src/lib.rs +++ b/crates/proof-score/src/lib.rs @@ -452,27 +452,6 @@ pub fn judge_topic( } } -/// Sum of per-topic lattices over currently open topics, capped at [`SCORE_MAX`]. -/// -/// Paid emission uses [`payout_lattices`] (WTA / discovery). This helper is -/// the binary fallback: each topic is 0 or [`SCORE_MAX`], then averaged so a -/// skipped open topic still pulls the miner down. An empty open set is a host -/// problem (503), not a miner score of 0 — callers must not emit this as a paid -/// leaf. -#[must_use] -pub fn mean_lattice(per_topic: &BTreeMap, open_ids: &[String]) -> u64 { - if open_ids.is_empty() { - return 0; - } - let mut sum: u128 = 0; - for id in open_ids { - sum = sum.saturating_add(u128::from( - per_topic.get(id).copied().unwrap_or(0).min(SCORE_MAX), - )); - } - u64::try_from(sum / u128::from(open_ids.len() as u64)).unwrap_or(0) -} - /// Empty split map with one slot per scored stratum (tests / sim). #[must_use] pub fn empty_splits() -> BTreeMap { @@ -739,12 +718,7 @@ mod tests { } #[test] - fn skipped_open_topics_pull_the_mean_to_zero() { - let mut scores = BTreeMap::new(); - scores.insert("dt-no-ib-v0".into(), SCORE_MAX); - let open = ["dt-no-ib-v0".into(), "other-v0".into()]; - assert_eq!(mean_lattice(&scores, &open), SCORE_MAX / 2); - assert_eq!(mean_lattice(&scores, &[]), 0); + fn topic_shares_split_the_proof_allocation() { assert_eq!(topic_share_bps(2), 4_000); assert_eq!(topic_share_bps(0), 0); assert_eq!(PROOF_SHARE_BPS, 8_000); diff --git a/crates/proof-store/Cargo.toml b/crates/proof-store/Cargo.toml index 2a1b6f6fe..e8dceb5f6 100644 --- a/crates/proof-store/Cargo.toml +++ b/crates/proof-store/Cargo.toml @@ -16,8 +16,5 @@ sha2 = "0.10" hex = "0.4" thiserror = "2" -[dev-dependencies] -serde_json = "1" - [lints] workspace = true diff --git a/crates/telemetry/Cargo.toml b/crates/telemetry/Cargo.toml index 71f2d7814..1fed96953 100644 --- a/crates/telemetry/Cargo.toml +++ b/crates/telemetry/Cargo.toml @@ -14,7 +14,6 @@ http = "1" metrics = "0.24" metrics-exporter-prometheus = { version = "0.16", default-features = false } serde = { version = "1", features = ["derive"] } -serde_json = "1" thiserror = "2" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } tower = { version = "0.5", features = ["util"] } diff --git a/crates/updater/Cargo.toml b/crates/updater/Cargo.toml index 889699752..b5246b94d 100644 --- a/crates/updater/Cargo.toml +++ b/crates/updater/Cargo.toml @@ -14,7 +14,6 @@ reqwest = { version = "0.12", default-features = false, features = ["json", "rus serde = { version = "1", features = ["derive"] } serde_json = "1" thiserror = "2" -tracing = "0.1" [dev-dependencies] tempfile = "3" diff --git a/crates/validator/Cargo.toml b/crates/validator/Cargo.toml index d6d46a6bd..19b9c5b88 100644 --- a/crates/validator/Cargo.toml +++ b/crates/validator/Cargo.toml @@ -28,7 +28,6 @@ dissent = { path = "../dissent" } submit = { path = "../submit" } telemetry = { path = "../telemetry" } trustroot = { path = "../trustroot" } -parity-scale-codec = { version = "3.7", default-features = false, features = ["derive"] } reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/deploy/AGENTS.md b/deploy/AGENTS.md index 1c11e74aa..71aa1ddbf 100644 --- a/deploy/AGENTS.md +++ b/deploy/AGENTS.md @@ -39,6 +39,7 @@ Compose always runs a digest-pinned `postgres` service (`base-pgdata` volume, he | Validator attestations (when DB configured) | **Postgres** | | Gateway challenge **backend registry** | **in-memory** — re-seed after gateway restart (`remote-deploy.sh` does this on master) | | site-api (`GET /v1/site/*`) | no DB — proxies bounty/proof upstreams via gateway | +| Proof submissions + scores | **in-memory** — the current service uses `MemoryStore`; Postgres in Compose does not make these durable | | Unit/integration tests | may construct `Memory*Store` directly; omit `BASE_DATABASE_URL` only there | Migrations (`crates/db/migrations`) run on boot in gateway when `BASE_DATABASE_URL` is set. Compose requires `deploy/env/bounty-challenge.env` and `deploy/env/proof-challenge.env` so live challenges cannot silently boot without operator config. @@ -61,6 +62,11 @@ droplet overlay sets it). Live submits stay fail-closed until harvest is wired, a baseline is sealed, and ≥1 topic is open. Do not invent an eval digest. +Current Proof judging and emission are incomplete: the Python judge uses static +checks and an acknowledgement request, and the binary does not drive its +leaf-signing helpers. Deployment configuration cannot fill these implementation +gaps. See [`docs/WHITEPAPER.md`](../docs/WHITEPAPER.md). + ## Local testnet E2E Full procedure: [`docs/runbooks/local-testnet-e2e.md`](../docs/runbooks/local-testnet-e2e.md). @@ -85,7 +91,7 @@ Full procedure: [`docs/runbooks/local-testnet-e2e.md`](../docs/runbooks/local-te **Weights seal smoke (default on `--smoke`):** after healthz, `local-e2e.sh` runs `weights-smoke` — signed bounty leaves for the live metagraph → `POST /v1/admin/seal` → assert `GET /v1/weights/latest` is **200** with **`sealed: true`**. Skip with `--no-weights-smoke`. Pre-seal, latest is **200 burn** (`sealed: false`, uid 0 = 100%) — never 404; that is unrelated to a missing gateway owner wallet. Prefer `--burn` on mainnet when sealing without real challenge scores (all `NoScore` → uid 0). -**Interim prod burn seal (retired while prism auto-emits):** `weights-smoke --burn` posts all-`NoScore` at a **block-scale** epoch. That hid the live Prism 2.1 WTA winner (chain epoch ~24k) because `/v1/weights/latest` had no chain-scale bundle to prefer. Keep the script for emergency burn-only windows; **do not** enable `base-burn-seal.timer` when Prism is emitting scores. `remote-deploy` on master enables real-seal and disables the burn timer. +**Emergency burn seal:** `weights-smoke --burn` posts all-`NoScore` at a **block-scale** epoch. Keep the script for explicitly approved burn-only windows; **do not** enable `base-burn-seal.timer` alongside real challenge scores. `remote-deploy` on master enables real-seal and disables the burn timer. Historical Prism behavior is not a reason to restore that retired product. Historical install (burn-only, no live scores): @@ -105,7 +111,7 @@ cargo run -q --release -p weights-smoke -- \ A seal older than ~256 blocks can never be verified by the validator (public RPC prunes state) — if `GET /v1/weights/latest` shows `metagraph_block` lagging tip by thousands of blocks, check `systemctl status base-burn-seal.timer` and `/var/log/base-burn-seal.log` on the master. -**Real-epoch sealer (default on master):** `base-real-seal.timer` (every **2 min**) drives [`scripts/prod-real-seal.sh`](scripts/prod-real-seal.sh), which walks **current … current−N** chain epochs (`REAL_SEAL_WALK_BACK`, default 16) with `block_b = LastEpochBlock − k×tempo`. Tip reseal is expected: when prism tip-supersedes leaves mid-epoch, seal rebuilds and appends `epoch_bundle.revision` so `/v1/weights/latest` tracks the WTA winner; identical merkle/vector is a no-op 200. Seal strips 0-bps challenge leftovers (design today) so they cannot 409 D24. The gateway prefers chain-scale bundles over the reserved smoke range (`>= 8_000_000`). Install / `remote-deploy` does this: +**Real-epoch sealer (default on master):** `base-real-seal.timer` (every **2 min**) drives [`scripts/prod-real-seal.sh`](scripts/prod-real-seal.sh), which walks **current … current−N** chain epochs (`REAL_SEAL_WALK_BACK`, default 16) with `block_b = LastEpochBlock − k×tempo`. Tip reseal is expected: when a challenge supersedes leaves mid-epoch, seal rebuilds and appends `epoch_bundle.revision`; identical merkle/vector is a no-op 200. The gateway excludes non-paying challenge leaves and prefers chain-scale bundles over the reserved smoke range (`>= 8_000_000`). The sealer does not generate missing Proof leaves. Install / `remote-deploy` does this: ```bash install -m 0755 deploy/scripts/prod-real-seal.sh /opt/base/deploy/scripts/prod-real-seal.sh @@ -133,7 +139,7 @@ Validator logs should show `Match epoch=` then `Match → submit_intent` / `subm **Legacy Python agents (mainnet):** `validator-5gzi` (`95.133.252.120`) may point `master_url` / `weights_url` / `registry_url` at `https://chain.joinbase.ai` with **`submit_on_chain_enabled: false`**. Coordination shims live in `gateway-compat` (`/v1/validators/*`, `/v1/registry`, empty assignments). `GET /v1/weights/latest` refreshes `computed_at` / `expires_at` at serve time so Python pydantic clients accept sealed vectors older than 720s. Do **not** start `base-weight-submitter-5gzi` on `validator-root` unless CR ownership is moved off Rust. -**Challenge verification:** on **master** only (validator has **no challenge exec**). Simulate submissions end-to-end — submit **baseline** + submit **cheat**, poll `/v1/runs/{id}` + `/events` + `/logs`, probe edges (bad harness, sanitize, quota, routes), then **admin winners** (`GET/POST /v1/admin/rounds/{id}/…` with bearer from `deploy/secrets/design/annotator_tokens`) and confirm leaf → seal → `GET /v1/weights/latest` **`sealed: true`**. **Never host Sim in staging/prod** (`BASE_ALLOW_HOST_SIM` / host `SimSandbox` are CI/local only). Healthz alone is insufficient. +**Challenge verification:** on **master** only (validator has **no challenge exec**). Bounty: pair, report, probe quota/auth/fail-closed paths, then verify feed-driven leaves. Proof: submit against a signed `topic_id`, probe rejected and unavailable-evaluation paths, and distinguish library tests from the unwired live emitter. Verify leaf → seal → `GET /v1/weights/latest` **`sealed: true`** where the full path is available. Follow the root [verification contract](../AGENTS.md#challenge-verification-mandatory-path-coverage); do not use retired Design run/winner endpoints. **Never host Sim in staging/prod** (`BASE_ALLOW_HOST_SIM` / host `SimSandbox` are CI/local only). Healthz alone is insufficient. Tunnel writes gitignored `deploy/env/local-tunnel.env` (`BASE_GATEWAY_PUBLIC_URL`). Co-located validator stays on `http://gateway:8080`; external clients use the tunnel URL. Host probe ports default to `2808x` (avoid staging SSH on `1808x`). diff --git a/deploy/README.md b/deploy/README.md index 0ac2e4247..9a07c0170 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -1,17 +1,27 @@ # Cortex deploy (compose) +Operator reference for Cortex's autonomous research network. For the product +purpose and current research limits, start with the +[overview](../docs/OVERVIEW.md). This guide describes deployment, not evidence +that the complete Proof research loop is ready. + ## Services | Service | Profile | Image | |---------|---------|--------| | `postgres` | default | `postgres@sha256:33f9…` (16) | | `validator` | default | build `deploy/Dockerfile` target `validator` | -| `updater` | default | build target `updater` | +| `updater` | optional **`auto-update`** | build target `updater` | | `socket-proxy` | default | `tecnativa/docker-socket-proxy@sha256:9e4b…` | | `gateway` | **`master`** | build target `gateway` | +| `bounty-challenge` | default; disabled on validator hosts | build target `bounty-challenge` | +| `proof-challenge` | default; disabled on validator hosts | build target `proof-challenge` | -Default `docker compose up -d` starts **4** services and does **not** start gateway. -Owner host: `docker compose --profile master up -d` starts **5**. +The base Compose file has **5 default services**, without gateway or updater. +Adding `--profile master` adds the gateway. Operator hosts must also use the role +overlays: `role-master.yml` disables the validator, and `role-validator.yml` +keeps only Postgres and the validator. A co-located validator is for local E2E, +not a second production weight submitter. ## Hard rules @@ -22,35 +32,21 @@ Owner host: `docker compose --profile master up -d` starts **5**. ## Quick start (local) -```bash -# 1) Release binaries (or set BASE_DOCKER_BUILD_FROM=source for full in-Docker rustc 1.96) -cargo build --release -p validator-bin -p gateway-bin -p updater-bin - -# 2) Env files at 0600 -./deploy/scripts/materialize-env.sh - -# 3) Build service images + start default stack -export BASE_DOCKER_BUILD_FROM=prebuilt -docker compose build -docker compose up -d -docker compose ps - -# 4) Master profile (gateway) -docker compose --profile master up -d -``` - -### Full local testnet E2E (recommended) - Master + gateway + validator + challenges on **testnet 541**, with an ephemeral cloudflared public URL for the gateway. Procedure: [`docs/runbooks/local-testnet-e2e.md`](../docs/runbooks/local-testnet-e2e.md). ```bash ./deploy/scripts/local-e2e.sh --help +./deploy/scripts/materialize-env.sh ./deploy/scripts/local-e2e.sh --dry-run ./deploy/scripts/local-e2e.sh --smoke # or --live when wallets are present ``` +Follow the runbook's build and key prerequisites before `--smoke`. Health checks +alone do not validate scoring; simulate submissions and verify a real sealed +bundle. See [the challenge verification contract](../AGENTS.md#challenge-verification-mandatory-path-coverage). + ## Age secrets (production) ```bash @@ -66,12 +62,14 @@ export AGE_IDENTITY=/etc/base/age-identity.txt | Host | Droplet | VPC IP | Role | Hotkey | Gateway | |------|---------|--------|------|--------|---------| -| staging master | `base-staging` (`68.183.23.51`) | 10.116.0.2 | owner control plane | **yes** (`BASE_GATEWAY_HOTKEY`) | **yes** (`--profile master`) — public API **`staging.api.joinbase.ai`** (`BASE_DOMAIN`, cleartext `:80`/`:8080`) | +| staging master | `base-staging` (`68.183.23.51`) | 10.116.0.2 | network master | **yes** (`BASE_GATEWAY_HOTKEY`) | **yes** (`--profile master`) — public API **`staging.api.joinbase.ai`** (`BASE_DOMAIN`, cleartext `:80`/`:8080`) | | staging validator | `base-staging-validator` | 10.116.0.4 | normal validator | **no** | **no** — uses master gateway over VPC `:8080` | +| prod master | `base-prod` | 10.116.0.3 | network master | yes | yes | +| prod validator | `base-prod-validator` | 10.116.0.5 (assigned) | normal validator | **no** | **no** — uses prod master gateway over VPC `:8080` | DNS (operator): `staging.api.joinbase.ai` **A** → staging master public IPv4 (`STAGING_MASTER_HOST` / `68.183.23.51`). -| prod master | `base-prod` | 10.116.0.3 | owner control plane | yes | yes | -| prod validator | `base-prod-validator` | 10.116.0.5 (assigned) | normal validator | **no** | **no** — uses prod master gateway over VPC `:8080` | +The hotkey column refers to gateway owner identity. Validators need their own +wallet to submit weights; they do not need the owner's key. Deploy (manual or via CI): @@ -115,7 +113,7 @@ combination. Verify locally: `./deploy/scripts/assert-compose-matrix.sh`. ### Auto CI deploy -- `.github/workflows/deploy-staging.yml` — after successful `ci` on `main` (and manual dispatch) +- `.github/workflows/ci.yml` — auto-deploy staging after its `ci` job succeeds on `main`; `deploy-staging.yml` is the manual lane - `.github/workflows/deploy-prod.yml` — on push of `v*.*.*` tags from `main` (and manual dispatch with SHA) **Prod release flow (tag-based):** @@ -145,14 +143,14 @@ Required GitHub secrets: | `SPACES_SECRET_ACCESS_KEY` | Spaces secret (fallback: `AWS_SECRET_ACCESS_KEY`) | | `BASE_BACKUP_BUCKET` | optional, default `base-backups` | -> **Not AWS EKS.** Control plane stays Docker Compose on DigitalOcean droplets (existing design). A separate DOKS cluster on this account (`basecrawl-prod-nyc3`) is unrelated and must not host base. +> **Not AWS EKS.** Network services stay on Docker Compose on DigitalOcean droplets. A separate DOKS cluster on this account (`basecrawl-prod-nyc3`) is unrelated and must not host Cortex. ## Infrastructure (DigitalOcean) -Terraform lives in [`terraform/`](./terraform/): two `s-8vcpu-16gb-amd` droplets -(`base-staging`, `base-prod`) in `nyc1` (nyc3 has no 8vCPU/16GB slug on this account) plus a firewall (SSH from operator IP -only; 80/443 open). Cloud-init installs Docker + Compose only. +Terraform lives in [`terraform/`](./terraform/): staging and production master +and validator droplets, plus the firewall. See the four-host topology above and +the Terraform inputs for sizes. Cloud-init installs Docker + Compose only. Age delivery helpers: diff --git a/docker-compose.yml b/docker-compose.yml index d2f47e967..017a18deb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,13 @@ -# base control-plane stack +# Cortex research network stack (Compose project name stays base) # -# Default: postgres + validator + updater + socket-proxy + the two live +# Default: postgres + validator + socket-proxy + the two live # challenges (bounty, proof). relearn*, design, and prism are gone — they # have no trust-root row and no remaining compose services. # Master/owner host only: # docker compose --profile master up -d -# brings gateway as an additional service (D3). +# brings gateway as an additional service (D3). Use role-master.yml on +# operator hosts to disable the co-located validator. Updater is opt-in +# under the auto-update profile. # # External images are digest-pinned (no floating tags). # docker.sock is mounted ONLY on socket-proxy (read-only). diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 128a24f86..7bd57759a 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -8,6 +8,7 @@ How to treat documentation in this repo. |------|-------|----------| | **Normative** | `ARCHITECTURE.md`, `NAMING.md`, frozen specs (`BUNDLE_SPEC.md`, `DESIGN_CHALLENGE.md`, `PRISM.md`, …), `THREAT_MODEL.md`, `OPERATOR_SECURITY.md`, `COMPLETENESS.md`, `runbooks/`, `external-miner/` | Source of truth for contracts, ops, naming, and status | | **Non-normative** | `evidence/`, `spikes/` | Historical ops notes / experiments. **Do not** implement against them as spec; **do not** delete in cleanup passes without an explicit ops decision | +| **Explanatory** | `README.md`, `OVERVIEW.md`, `WHITEPAPER.md`, `CLEANUP.md`, `../whitepaper.pdf` | Navigation, product rationale, proposal-to-code comparison, and cleanup evidence; not replacements for current contracts | When a spike or evidence report conflicts with a frozen spec or runbook, the normative doc wins. @@ -27,7 +28,8 @@ Repo-wide agent contract: [`../AGENTS.md`](../AGENTS.md). ## Challenge public miner repos -Public miner docs live **outside** this monorepo (examples + human guides only — no control-plane code): +Current miner guides live in this monorepo. Separate public miner repositories, +when present, contain examples and human guides only, not network service code: | Challenge | Repo | |-----------|------| diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 6a019445a..c3563b80c 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,6 +1,9 @@ # Cortex architecture -Operator-facing map of the control plane. Normative byte contracts live in the frozen specs: +Technical map of Cortex's autonomous research network. For the purpose and +research-reuse model, start with the [overview](OVERVIEW.md). The +[whitepaper comparison](WHITEPAPER.md) distinguishes proposed mechanisms from +current implementation. Normative byte contracts live in the frozen specs: | Spec | Status | Role | |------|--------|------| @@ -25,8 +28,8 @@ Runbooks: [`runbooks/`](./runbooks/). ## 1. Goals -- Lighter Rust control plane than the prior Python stack. -- Gateway runs **only** as subnet owner (master). Startup asserts hotkey == on-chain `SubnetOwnerHotkey` or exits `2` before bind. +- Coordinate reproducible research and bug reports, evaluate evidence, and verify reward allocation. +- Gateway runs on the **master** host. With `BASE_GATEWAY_REQUIRE_OWNER=1`, startup asserts hotkey == on-chain `SubnetOwnerHotkey` or exits `2` before bind; advisory local/staging configuration is distinct. - Validators **recompute** the weight vector from a signed, merkle-rooted epoch bundle. Challenge keys and measurements come from **owner-signed local files**, never from gateway HTTP. - CRV4 timelock commit-reveal on Bittensor testnet/mainnet as configured. Reveal is automatic on-chain. - Live challenges accept miner work over **HTTP** (Bounty → pair + reports; Proof → topic_id + artifact). Proof miners pay Lium when a key is present. @@ -36,40 +39,38 @@ Runbooks: [`runbooks/`](./runbooks/). ## 2. Process topology ```text - ┌─────────────────────────────────────┐ - │ Master host (compose profile master) │ - │ postgres · gateway · validator · │ - │ updater · socket-proxy · │ - │ bounty-challenge · │ - │ proof-challenge │ - └───────────────┬─────────────────────┘ - │ TLS terminates in gateway (D20) - │ /challenge/{id}/* /v1/bundle/* - ┌───────────────▼─────────────────────┐ - │ Other validator hosts (no gateway, │ - │ no challenge services / socket-proxy)│ - │ validator · local trust roots │ - │ peer root exchange (HTTPS + hotkey) │ - └───────────────┬─────────────────────┘ - │ HTTP submit - ┌───────────────▼─────────────────────┐ - │ Miner clients │ - │ bounty pair + bug reports │ - │ proof topic_id + experiment artifact │ - └─────────────────────────────────────┘ +Miner clients + │ HTTP: Bounty pair/reports or Proof topic/experiment + ▼ +Master host (role-master overlay + master profile) + gateway · postgres · socket-proxy + bounty-challenge · proof-challenge + │ │ + │ └─ Proof harvest → Lium evaluation pod + │ signed bundles + ▼ +Validator host (role-validator overlay) + validator · postgres · local owner-signed trust roots + │ peer cross-checks; no challenge execution + ▼ +Bittensor: verified reward weights ``` +The master overlay disables the co-located validator; local E2E may explicitly +enable one. `updater` is an optional `auto-update` profile. TLS currently +terminates in the host reverse proxy, not in the gateway process. + | Binary / crate | Role | |----------------|------| -| `gateway` | Master-only: registry, reverse proxy, bundle seal/serve, sole TLS owner; mounts marketing [`SITE_API.md`](./SITE_API.md) (`GET /v1/site/*`) | +| `gateway` | Master-only: registry, reverse proxy, bundle seal/serve; mounts public website [`SITE_API.md`](./SITE_API.md) (`GET /v1/site/*`) | | `validator` | Fetch/mirror bundle, verify, recompute, peer cross-check, CRV4 submit, dissent | | `bounty-challenge` | **Master-only:** internal pair/reports/adjudicate; **reads** CortexLM/backend public API for scoring and signs leaves from those rows. An unreadable feed pays nobody — `E` is covered with `ChallengeInternal`, share burns to uid 0 — rather than scoring offline | -| `proof-challenge` | **Master-only:** operator-published signed topics, per-topic holdout unseal, digest-pinned RLM judge, mean-of-open-topics lattice, sign leaves | +| `proof-challenge` | **Master-only:** signed topics, holdout loading, evaluation orchestration. Library payout is a sum of WTA/discovery topic masses; the binary has no automatic leaf-emission loop yet | | `updater` | Digest-pinned rollouts via `docker-socket-proxy` (master) | | `trustroot` | Offline keygen / sign / verify for owner-signed TOML | | `bundle` | SCALE types, seal, verify (`PROTOCOL_VERSION`) | | `aggregate` | Integer aggregation (Hamilton house 65535) | -| `chain` | Chain client trait + SDK wiring | +| `chain` / `chain-live` | Shared chain trait + deterministic test backend / production JSON-RPC client and signed weight submission | | `trustroot` (lib) | Load local signed challenges/measurements; dual-accept rotation | | `base-attest-*` | Parse / replay / policy for TDX quotes (bundle measurement pin) | | `crosscheck` / `dissent` | Peer roots and three-outcome policy | @@ -80,6 +81,10 @@ Runbooks: [`runbooks/`](./runbooks/). ## 3. Data flow (one epoch) +This is the bundle pipeline. Bounty drives its emitter; Proof's corresponding +payout/signing helpers still need service wiring. See +[implementation status](COMPLETENESS.md#proof-challenge). + 1. **Pin.** Gateway (or seal path) pins `block_hash` / metagraph root at epoch boundary. 2. **Leaves.** Challenge backends produce challenge-signed `Score` or `NoScore` leaves for the **validator-derived** expected set (D24). Tip epochs may **supersede** a leaf when the signed `payload_digest` changes for the same `(challenge, epoch, miner)`; identical digests stay idempotent. 3. **Seal.** Gateway builds `EpochBundleV1`, computes merkle root, signs the body. Tip reseal appends `epoch_bundle.revision` when leaves/merkle change; no-op if identical. **Does not** put the merkle root into the on-chain weight payload (there is no field; see BUNDLE_SPEC §12 / D5). @@ -110,7 +115,9 @@ a baseline is sealed, and ≥1 topic is open. Do not invent a sha256. claiming those ids fails the trust-root check. Each live challenge signs leaves under its **own** key; no two rows share one. -Gateway DB is **routing only**. It is never a source of challenge keys, emission shares, or measurements (D18, D23). +The gateway database persists raw weights and sealed bundles; backend routing +remains in memory. Neither is **trust authority**: challenge keys, emission +shares, and measurements come from the owner-signed local files (D18, D23). Ceremony: [`config/CEREMONY.md`](../config/CEREMONY.md). Rotation: [`runbooks/trust-root-rotation.md`](./runbooks/trust-root-rotation.md) (D21). @@ -121,9 +128,11 @@ Rotation: [`runbooks/trust-root-rotation.md`](./runbooks/trust-root-rotation.md) | Profile | Services | |---------|----------| -| default | postgres, validator, updater, socket-proxy, bounty-challenge, proof-challenge | -| `master` | + gateway (owner host only); live challenges stay on master | -| `role-validator` overlay | disables gateway, updater, challenges, socket-proxy | +| default | postgres, validator, socket-proxy, bounty-challenge, proof-challenge | +| `master` | adds gateway; use the master role overlay on operator hosts | +| `role-master` overlay | disables validator; challenges stay on master | +| `role-validator` overlay | disables gateway, updater, challenges, socket-proxy; keeps postgres and validator | +| `auto-update` | optional updater; not part of the default stack | | `evil-gateway` | **test-only** adversarial harness (task 48). Never prod. | See [`deploy/README.md`](../deploy/README.md) and root [`docker-compose.yml`](../docker-compose.yml). @@ -138,6 +147,8 @@ See D19 in [`THREAT_MODEL.md`](./THREAT_MODEL.md). Short form: - Owner honesty is out of scope (owner signs roots and runs gateway). - Non-equivocation is **peer-consensus + local evidence**, not a public on-chain `(epoch → bundle_root)` anchor. - Gateway HA is **not** claimed (R9): restart policy + manual failover only. +- A complete autonomous research judge, durable shared research collection, and + synthesis/adoption loop are **not** implemented. See [the paper-to-code comparison](WHITEPAPER.md). --- diff --git a/docs/BOUNTY.md b/docs/BOUNTY.md index 2ba34d6ae..494ea4347 100644 --- a/docs/BOUNTY.md +++ b/docs/BOUNTY.md @@ -1,6 +1,6 @@ # Bounty Challenge (live challenge) -Control-plane notes. Miners start at [`external-miner/bounty.md`](./external-miner/bounty.md). +Operator reference. Miners start at [`external-miner/bounty.md`](./external-miner/bounty.md). Validators start at [`external-miner/validators.md`](./external-miner/validators.md). **Public transparency lives in CortexLM/backend.** This subnet **reads** @@ -27,11 +27,11 @@ board. | `challenge_id` | `bounty` | | `challenge_scoring_version` | `1` | | Port | `8096` (local host `28096`) | -| Emission | `7000` bps | +| Emission | `2000` bps (20%; Proof has the other 80%) | -## Why this challenge is not gated like the Relearn ones +## Why bug reports need different evaluation -The LLM challenges defend a private holdout against a model that memorised it. +Proof's research evaluation protects private holdouts against memorization. Bounty has no model and no holdout to memorise; its scarce resource is **adjudication**, and its failure modes are volume plays against a human or agent triage queue. Copying the LLM gate stack here would gate the wrong thing. diff --git a/docs/CLEANUP.md b/docs/CLEANUP.md new file mode 100644 index 000000000..41a48f7cd --- /dev/null +++ b/docs/CLEANUP.md @@ -0,0 +1,46 @@ +# Cleanup scope and retained contracts + +This cleanup removes obsolete implementation paths and clarifies the purpose of +Cortex. It does not implement the whitepaper's future mechanisms or change +scoring, signatures, migrations, image pins, or reward allocation. + +## Removed + +| Item | Evidence and replacement | +|------|--------------------------| +| `chain::NotImplementedChain`, `chain::LiveRpcChain`, and the old `chain/live` feature | No workspace consumers; runnable services use `chain_live::LiveChainClient`. The `ChainClient` trait and deterministic `FakeChain` test backend remain | +| Gateway `upstream_uri` | Unused private helper. Production uses `upstream_url`; regression tests now exercise that path directly | +| Prism similarity-v2 prompt and unused key-path helper | No active callers; the embedded v3 prompts and version tags are unchanged | +| Metadata storage type fields and key-flattening helper | Stored or computed values had no readers. Both supported metadata versions use the same upstream storage-entry type, so the projection is shared | +| Proof `mean_lattice` | Only its own test called it. Current payout uses the sum of WTA/discovery topic masses, not this obsolete binary average | +| Unused direct and development dependencies | Checked against package sources and target builds; manifests and the lockfile change together | +| SSH return-code dead-code allowance | The field is read by the Lium client; the field remains, only the unnecessary suppression is removed | + +Storage projection tests cover hasher order, default bytes, and plain entries. +Gateway tests cover query strings and slash joining. Existing payout tests still +cover skipped topics, exact ties, discovery splits, and empty topic sets. + +## Deliberately retained + +- **Proof's `prism-*` dependencies:** the Lium harvest path still uses them. + A retired product name does not make its shared libraries dead code. +- **Applied SQL migrations:** deployed databases may already contain those + tables. History is not disposable scaffolding. +- **`BASE_*`, deployed paths, crypto domains, and compatibility routes:** these + protect existing configuration, clients, signatures, and measurement pins. + See [Naming](NAMING.md). +- **Frozen specs, retired miner links, evidence, and spikes:** they preserve + verification contracts and operational history. +- **Explicit fail-closed interfaces and local test backends:** missing live + wiring is documented, not hidden by deleting guards or inventing scores. + +## Documentation changes + +The [overview](OVERVIEW.md) explains research reuse for new readers and investors. +The [whitepaper guide](WHITEPAPER.md) separates the proposal from implementation. +Architecture, contribution, miner, and deployment references now use that +positioning while retaining technical contracts. + +The audit also records existing Proof gaps, including partial judging, +in-memory records, and missing automatic emission. These need separate +implementation and end-to-end validation, not a branding change. diff --git a/docs/COMPLETENESS.md b/docs/COMPLETENESS.md index 01ee67b49..917d6f03c 100644 --- a/docs/COMPLETENESS.md +++ b/docs/COMPLETENESS.md @@ -1,6 +1,11 @@ # Cortex completeness matrix -Honest per-component status as of `main` HEAD. Updated as phases land. +Per-component implementation status. Updated as phases land. This is not a fresh +production-health check; infrastructure entries record the documented operator +baseline, not observations made by reading this repository. + +For new readers: [overview](OVERVIEW.md). For the difference between the +whitepaper's research vision and current code: [implementation comparison](WHITEPAPER.md). ## Legend @@ -9,7 +14,7 @@ Honest per-component status as of `main` HEAD. Updated as phases land. | **done** | Implemented, tested, wired into a running binary. | | **sim** | Code exists and passes tests, but the running binary uses a simulated backend, not live data. | | **lib-only** | Library crate is complete; no binary drives it in production. | -| **stub** | Trait method returns `NotImplemented`; placeholder for future wiring. | +| **partial** | Some behavior exists, but the advertised end-to-end mechanism is incomplete. | | **test-only** | Compiled and exercised by tests; deliberately unreachable from any shipped binary. | | **missing** | No code, no compose service, no CI image. | @@ -19,9 +24,7 @@ Honest per-component status as of `main` HEAD. Updated as phases land. |-----------|--------|-------| | `ChainClient` trait | done | 14 methods, full trait surface. | | `FakeChain` | test-only | Deterministic in-memory. No longer reachable from any binary; used by unit and adversarial tests. | -| `NotImplementedChain` | stub | Every method returns `Err(NotImplemented)`. | -| `LiveRpcChain` (feature `live` on older chain helpers) | stub | Legacy stub surface: `current_block` + `block_hash` only; metagraph / weight submit paths `NotImplemented`. **Not** the production backend. | -| `chain-live` crate (`LiveChainClient`) | **done** | Production chain client: full JSON-RPC reads (`Identity` hasher, `Keys` double-map enumeration, `ValueQuery` defaults) + sr25519 signed `set_weights` / `commit_timelocked_mechanism_weights`. The **only** backend in `bins/validator` and `bins/gateway`; both fail fast if the chain is unreachable. Four `#[ignore]` tests read live testnet 541. Do not confuse with stub `LiveRpcChain` above. | +| `chain-live` crate (`LiveChainClient`) | **done** | Production chain client: full JSON-RPC reads (`Identity` hasher, `Keys` double-map enumeration, `ValueQuery` defaults) + sr25519 signed `set_weights` / `commit_timelocked_mechanism_weights`. The **only** backend in `bins/validator` and `bins/gateway`; both fail fast if the chain is unreachable. Four `#[ignore]` tests read live testnet 541. Obsolete alternative stubs were removed; see [cleanup scope](CLEANUP.md). | | `BASE_CHAIN_ENDPOINT` / `BASE_CHAIN_ENDPOINTS` | done | Read by `config::Config`; consumed by `chain-live::LiveChainClient::connect`. The plural var is an ordered comma-separated failover list (wins over the singular); a rate-limited (HTTP 429 / `-32005`) or unreachable endpoint cools 60s and the call tries the next in order. | | CRV4 tlock encryption | **done** | Drand Quicknet TLE via git-pinned `tle` (same rev as subtensor / `bittensor_drand`); `LiveChainClient::submit_timelocked_weights` encrypts SCALE `WeightsTlockPayload` before signing. Fail-closed on encrypt error — never downgrades to `set_weights` while CR is enabled. | @@ -70,7 +73,7 @@ specs (`DESIGN_CHALLENGE.md`, `PRISM.md`) remain for `xtask` gates. Leftover | Component | Status | Notes | |-----------|--------|-------| | Challenge id | **done** | `proof` on the wire. Topics are operator-published signed documents; git carries no catalog. | -| Crates (`crates/proof-*`) | **done** | task (signed topics, holdout commitments, global pin, `payout_mode` / English `validation`), score (per-topic pass + WTA/discovery sum), store, eval (RLM judge, fail-closed readiness), harvest, http, challenge. | +| Crates (`crates/proof-*`) | **partial** | Signed topics, holdout commitments, global pin, per-topic pass + WTA/discovery payout, in-memory store, readiness checks, harvest, and HTTP exist. They do not constitute the full autonomous research loop. | | Binary (`bins/proof-challenge`) | **done** | HTTP API on `:8100`. | | Miner CLI (`bins/ctx`) | **done** | `ctx proof submit|show|status|topics`. Unpinned digest / unwired harvest / no open topic → 503. | | Compose / images | **done** | Default compose + `images.yml` target `proof-challenge`. | @@ -79,7 +82,11 @@ specs (`DESIGN_CHALLENGE.md`, `PRISM.md`) remain for `xtask` gates. Leftover | Topics | **done** | sr25519 under the `proof` trust-root key (`base-proof-topic-v1`). Admin `POST /v1/admin/proof/topics`. A topic must be sealed to `open`. | | Holdout | **done** | Per-topic operator file (`PROOF_HOLDOUT_FILE`). Commitment in the topic document, never in the pin. `xtask proof-holdout --topic-id`. | | Live harvest | **done** | `crates/proof-harvest` over `harvest-pod`; `PROOF_FORCE_SIM` is local-only. | -| Emission | **8000 bps** | Proof-weighted 20%/80% regardless of digest. Unwired harvest / unsealed baseline / empty open set → 503 / `NoScore(ChallengeInternal)`. Split equally across currently `open` topics, then `wta` or `discovery`. Empty digest still 503s (never invent a sha256). | +| Configured allocation | **8000 bps** | Proof-weighted 20%/80% regardless of digest. Payout splits equally across currently `open` topics, then `wta` or `discovery`. Empty digest / missing evaluation prerequisites still fail closed. | +| Automatic emission | **lib-only** | `proof-challenge::emit_epoch` signs payout leaves, but `bins/proof-challenge` does not call it or run an emission loop; the HTTP state starts at epoch `0`. Do not infer payments from `can_score`. | +| Autonomous research judge | **partial** | Python `judge.py` requests an acknowledgement, while `agent.py` uses static text checks. General recipe reproduction and the paper's recursive investigation are not implemented. | +| Research persistence | **missing** | The service uses `MemoryStore`; submissions and scores are lost on restart. Public HTTP records are not a durable artifact archive. | +| Synthesis / shared-stack adoption | **missing** | The second agent and verified adoption loop described in whitepaper §7 are not implemented. | | Spec | live | [`PROOF.md`](PROOF.md). | ## Infrastructure @@ -121,6 +128,8 @@ Agent/operator contracts: root [`AGENTS.md`](../AGENTS.md), [`deploy/AGENTS.md`] | Gap | Impact | |-----|--------| +| Proof research-to-payment path | Partial Python judging, in-memory results, epoch `0` HTTP state, and no automatic emitter prevent treating the current service as the full whitepaper mechanism. See [the source comparison](WHITEPAPER.md#proposal-versus-current-code). | +| Proof scoring vs whitepaper | Current code uses equal topic masses, one primary metric, exact WTA ties, and digest duplicate checks. The paper's multi-metric frontier, method-descriptor novelty, and synthesiser are proposals, not shipped guarantees. | | DCAP verify holds the attest mutex | A cold Intel PCS fetch (up to 20 s) serialises attestation submissions. | | DCAP error classification | Matches on `anyhow` message text; re-run `cargo test -p attest-policy --features dcap` after any `dcap-qvl` bump. | | Bounty severity on the backend feed | Scoring credits a `valid` row only when the backend publishes a `severity`. Until CortexLM/backend emits it, valid rows land as `valid_unpriced`, no miner can be crowned, and the share burns. Fail-closed by design: an unpriced bug cannot be paid for. | diff --git a/docs/NAMING.md b/docs/NAMING.md index ef0c7e742..bf5da26e1 100644 --- a/docs/NAMING.md +++ b/docs/NAMING.md @@ -3,6 +3,13 @@ **Product name:** Cortex **Org / repo:** [`CortexLM/cortex`](https://github.com/CortexLM/cortex) +**Positioning:** an autonomous research network. Describe the purpose first: +reproducible findings that can improve shared research methods, not only isolated +model checkpoints. Rust and Python are implementation choices. “Control plane” +may describe a technical coordination component, but is not the product's name +or value proposition. Keep the whitepaper's intended synthesis loop separate +from [current capabilities](WHITEPAPER.md). + This repo used to ship as “BASE” / `BaseIntelligence/base`. Human-facing docs and GitHub metadata now say Cortex. A large set of **wire, deploy, and crypto identifiers still spell `base` / `BASE_*`**. Changing those forks the diff --git a/docs/OVERVIEW.md b/docs/OVERVIEW.md new file mode 100644 index 000000000..a1b48656b --- /dev/null +++ b/docs/OVERVIEW.md @@ -0,0 +1,145 @@ +# Cortex in five minutes + +**Cortex is an autonomous research network.** + +Its purpose is to make AI research accumulate: useful findings from independent +contributors should become reusable methods for the whole network, not disappear +when a competition selects its winning model. Cortex uses Bittensor subnet +**100** for network incentives. + +## Why another research network? + +The [whitepaper](../whitepaper.pdf) targets a specific pattern: contributors train +separate models, submit finished checkpoints, and tune them to a fixed evaluator. +If the network keeps only the winning checkpoint while recipes remain private, +it selects a result without retaining all the discoveries that produced it. +Repeated feedback from a visible test can also reward memorizing that test. + +Proof changes the proposed unit of work from **“here are my model weights”** to +**“here is a finding, the recipe to reproduce it, and evidence that it works.”** + +| Checkpoint-only competition, as modeled in the paper | Cortex's proposed research loop | +|----------------------------------------------------|--------------------------------| +| Select one finished model | Retain reproducible findings from multiple contributors | +| Recipe and data choices may stay private | Require code, a data manifest, and a declared compute budget | +| Optimize against a fixed, observable test | Publish research topics with private evaluation data and a sealed reference | +| Start another isolated training round | Reuse proven methods in a shared stack, then verify the combined update | + +This is a design argument, not evidence that Cortex already outperforms every +other network. It depends on disclosure, sound evaluation, and actual reuse. +Other networks may also share recipes or use private evaluation. + +## How research can accumulate + +Imagine one contributor finds a faster training method and another finds a +better data-filtering rule. Keeping only one model can hide both recipes. +Keeping their code and reproducible results makes both methods available to +test in later work. + +In the whitepaper's intended loop: + +1. Contributors submit experiments against signed research topics. +2. A judge investigates reproduction and cheating; a separate harness measures + private test data against a previously measured reference. +3. Accepted artifacts form a shared research collection. +4. A **synthesis agent** proposes an update to shared software, training recipes, + or data policy using those findings. +5. The update must pass the same verification process against the current stack + before becoming the next version. + +**The synthesis agent is not implemented.** Nor does collecting two good results +prove they work together: the combined recipe must be tested. The paper's +“frontier” records the best findings across metrics; it is not automatically a +single model that achieves all of them. + +See [§7 and Figure 7, pages 4–5](../whitepaper.pdf#page=4), and the +[paper-to-code comparison](WHITEPAPER.md). + +## Two complementary contributions + +- **Proof:** research into training, optimizers, data, systems, or agent methods. + The intended output is a reusable experiment with measured evidence. +- **Bounty:** reproducible defects in Cortex products and services, reviewed by + an operator. This supports product reliability, not automatic model training. + +These are research and maintenance incentives, not a claim that every proposed +topic is currently available. + +## How incentives work + +The current configuration allocates **80% to Proof** and **20% to Bounty**. +The implemented scoring rules, not the paper's proposed equations, govern the +software: + +- Proof divides its allocation equally among open topics. A topic either rewards + the best qualifying result, with ties sharing, or uses a discovery model that + splits a pass floor and an improvement pool. The current improvement calculation + uses a primary metric and duplicate-artifact checks, not the paper's full + multi-metric novelty model. +- Bounty rewards report accuracy and bug severity. Duplicate or already-fixed + reports do not earn rewards; malicious reports can reduce a contributor's score. +- An allocation does not guarantee payment. Work must meet the evaluation rules, + and a challenge without the evidence needed to score cannot pay contributors. + +The Proof payout functions still need a service emission loop. Configured shares +are not evidence of completed payments, revenue, equity, or a promised yield. + +## What “autonomous” means today + +The software includes signed topics, submission intake, evaluation orchestration, +payout functions, signed bundles, and validator reward checks. These are building +blocks, not proof that the complete autonomous workflow is running. + +**People still make important decisions.** Operators publish topics, configure +the judge, measure reference results, and adjudicate Bounty reports. Evaluation +runs through services on the operator's master host and rented compute. +Validators check signatures and calculations, not the scientific truth of every +claim. + +Important gaps remain: the Python judge currently makes an authenticated +acknowledgement request and applies static checks, rather than investigating and +reproducing arbitrary submitted code. Proof submissions live in memory, and its +automatic reward emission is not wired into the service. Durable research +publication and the synthesis/adoption loop are also unfinished. + +See the [implementation comparison](WHITEPAPER.md#proposal-versus-current-code) +and [component status](COMPLETENESS.md). A configured endpoint or image digest +does not establish scientific correctness. + +## What to assess before investing + +This repository demonstrates software and its tests. It does not establish +traction, revenue, research quality, or the economics of running the network. +Those require separate evidence. + +Useful diligence questions include: + +- How many topics are open, and how many independent contributors complete them? +- What proportion of submissions produce reproducible improvements? +- What does evaluation cost relative to the value of accepted work? +- How many accepted recipes have been reused, combined, and independently checked? +- Do combined updates improve the shared stack without regressions? +- How concentrated are contributions, rewards, and operator control? + +Technical limits also matter. Proof needs the missing end-to-end wiring as well +as configured evaluation and open topics. Bounty depends on an external scoring feed. +The system does not guarantee honest operators or judges, and the master gateway +does not have automatic high availability. See the +[implementation status](COMPLETENESS.md) and [threat model](THREAT_MODEL.md). + +## A short glossary + +| Term | Meaning | +|------|---------| +| Miner | A contributor who submits research or bug reports | +| Topic | A research question with evaluation and reward rules | +| Baseline | A measured reference result used for comparison | +| Artifact | The submitted code or experiment files, identified by a content hash | +| Holdout | Evaluation data kept private to reduce test memorization | +| Shared stack | The software, training methods, and data practices the network aims to improve together | +| Validator | Software that verifies signed results and reward calculations | +| Weights | The relative allocation of network rewards | +| Fail closed | Stop rather than accept or reward work that cannot be verified | + +[Participate](external-miner/README.md) · [Explore the architecture](ARCHITECTURE.md) · +[Browse all documentation](README.md) diff --git a/docs/PROOF.md b/docs/PROOF.md index 02e87711b..0c4495276 100644 --- a/docs/PROOF.md +++ b/docs/PROOF.md @@ -1,5 +1,18 @@ # Proof challenge +Proof is Cortex's research contribution path: the intended output is a +reproducible finding that others can reuse, not just a finished model checkpoint. +Read the [overview](OVERVIEW.md) for the purpose and the +[whitepaper comparison](WHITEPAPER.md) for the proposed shared-research loop. + +**Implementation limits:** the Python judge currently performs an authenticated +acknowledgement request and static checks, not the paper's autonomous investigation +and arbitrary recipe reproduction. The service stores submissions in memory and +does not run an automatic reward-leaf emitter. Payout/signing helpers exist, but +readiness checks alone do not establish a complete research-to-payment path. +The rules below describe the current interfaces and scoring functions, not a +claim that these gaps are closed. + Live challenge id: **`proof`**. Emission **8000 bps** (80% of the subnet; bounty is 2000). This 20%/80% lock is independent of eval digest. Eval digest `sha256:78b614a1…` is pinned (`ghcr.io/cortexlm/proof-eval`). The @@ -70,7 +83,7 @@ baseline + an open topic are on the host. droplet overlays. - No Modal. No secrets, hosts, holdout records, or teacher endpoints in git. -## How Mathis injects a challenge (English) +## Publish a research topic 1. Write a YAML or JSON draft with `id`, English `statement`, `payout_mode` (`wta` | `discovery`), `validation.{score_on,accept_if,reject_if}`, and diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..c79f99905 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,48 @@ +# Cortex documentation + +Cortex is building an **autonomous research network** where reproducible findings +can improve shared methods, rather than remain isolated model checkpoints. + +## Start with your goal + +| Audience | Read first | Next | +|----------|------------|------| +| Investors and new readers | [Project overview](OVERVIEW.md) | [Current capabilities and limits](COMPLETENESS.md) | +| Research contributors | [Getting started](external-miner/README.md) | [Proof](external-miner/proof.md) | +| Bug hunters | [Bounty](external-miner/bounty.md) | [Troubleshooting](external-miner/troubleshoot.md) | +| Validators | [Validator guide](external-miner/validators.md) | [Architecture](ARCHITECTURE.md) | +| Developers | [Contributing](../CONTRIBUTING.md) | [Architecture](ARCHITECTURE.md), [naming and compatibility](NAMING.md) | +| Operators | [Deployment](../deploy/README.md) | [Runbooks](AGENTS.md#runbook-index), [security checklist](OPERATOR_SECURITY.md) | + +## Technical reference + +These documents keep the exact rules needed to operate and verify the network. +You do not need to read them to understand the project. + +- [Proof](PROOF.md): topic publication, evaluation, and research rewards. +- [Bounty](BOUNTY.md): pairing, bug reports, adjudication, and rewards. +- [Bundle specification](BUNDLE_SPEC.md): signed results and reward verification. +- [Site API](SITE_API.md): public website data. +- [Threat model](THREAT_MODEL.md): security guarantees and their limits. +- [Repository cleanup notes](CLEANUP.md): what was removed and what must stay. + +## Research rationale + +The [whitepaper](../whitepaper.pdf), *A Proposal for a New Incentive Mechanism on +Cortex*, explains why the network exists. Start with §2 for the checkpoint and +static-evaluator critique, and §7 for research reuse and the proposed synthesis +agent. + +The [plain-language reading guide and implementation comparison](WHITEPAPER.md) +separates that proposal from current code. The paper is a primary source for the +vision, not a deployment-readiness report or a replacement for current scoring +contracts. + +## Historical material + +[Design](DESIGN_CHALLENGE.md), [Prism](PRISM.md), [Prism recipes](PRISM_RECIPE.md), +the retired miner pages, [evidence](evidence/), and [experiments](spikes/) provide +historical background. They are **not a list of open challenges**. + +Frozen specifications and operational evidence remain available for verification +and old links. Use the guides above for current participation and deployment. diff --git a/docs/WHITEPAPER.md b/docs/WHITEPAPER.md new file mode 100644 index 000000000..e8474cec2 --- /dev/null +++ b/docs/WHITEPAPER.md @@ -0,0 +1,91 @@ +# Reading the Cortex whitepaper + +Source: [*A Proposal for a New Incentive Mechanism on Cortex (Bittensor Subnet +100): Bounty and Proof*](../whitepaper.pdf), version 1.1, September 2026. +This guide covers all six pages. It explains the proposal; it does not change +the protocol, scoring rules, or deployment configuration. + +## The central idea + +**Pay for reproducible discoveries that others can build on, not only for the +best finished model.** + +The paper critiques a particular research competition: miners train separate +checkpoints, recipes remain private, and a static evaluator selects a winner. +The network gets the chosen model, but does not necessarily retain the methods +behind the other experiments. Repeated tuning to a visible evaluator can also +improve the test score without improving performance elsewhere. + +Proof proposes submitting a claim together with code, an environment/data +manifest, a compute budget, and an artifact commitment. Operators publish signed +topics. A pinned judge investigates reproduction and cheating, while a separate +harness measures private holdouts against a baseline committed before opening. +The useful object is the reproducible experiment, not weights alone. + +## What “improving the network” means + +Section 7 proposes keeping proven artifacts so complementary findings can be +reused across the network. One result might improve training speed; another +might improve data quality. A collection preserves both instead of reducing +research to the identity of one winning checkpoint. + +The proposed **synthesiser** is a second, digest-pinned agent. It reads those +artifacts and proposes changes to the shared harness, training recipe, or data +policy. It does not merge raw miner weights. Its proposal must itself pass +evaluation against the current stack before adoption. + +The paper explicitly calls this the **intended end state**, not a deployed +component (§7–8, pages 4–5). A multi-metric frontier is a record of best findings; +it does not prove a combined implementation can achieve them all. That requires +another experiment. + +## Reading map + +| Pages / sections | Argument | +|------------------|----------| +| 1, §1–2 | Incentives determine what a subnet produces; visible static tests can reward overfitting and hide recipes | +| 2–3, §3–4, Figures 2–5 | Signed topics, reproducible artifacts, sealed baselines, private measurements, winner/discovery rewards | +| 3–5, §5, Figure 6 | Bounty pays for human-adjudicated production defects with published evidence and severity | +| 4 and 6, §6, Figure 8 | Hashes, signatures, commitments, and later disclosure make parts of centralized evaluation auditable | +| 4–5, §7, Figure 7 | A verified research collection could support shared, repeatedly tested improvements | +| 5, §8 | Operator/judge trust, reproduction scale, and unfinished synthesis remain limitations | +| 5–6 | References | + +## Proposal versus current code + +The whitepaper is not an exact specification of the current implementation. +Use [Proof](PROOF.md), [Bounty](BOUNTY.md), and the +[bundle specification](BUNDLE_SPEC.md) for today's contracts. + +| Area | Paper's proposal | Current repository | +|------|------------------|--------------------| +| Topics and baselines | Signed dynamic topics; measured baseline sealed before opening | Topic signatures, floors, holdout commitments, and baseline verification exist in [`proof-task`](../crates/proof-task/src/topic.rs), [`proof-store`](../crates/proof-store/src/lib.rs), and [`proof-eval`](../crates/proof-eval/src/lib.rs); operators supply the files and configuration | +| Autonomous judge | Plan an investigation, reproduce submitted code, detect cheating | [`judge.py`](../eval/src/proof_eval/judge.py) requests a short acknowledgement and does not parse a scientific verdict. [`agent.py`](../eval/src/proof_eval/agent.py) performs static text checks. This is not the proposed recursive research agent | +| Artifact reproduction | Run the committed recipe under its declared budget | [`cli.py`](../eval/src/proof_eval/cli.py) inspects the claim plus a local baseline script and measures a configured model directory. It does not implement general reproduction of arbitrary committed miner recipes | +| Private measurements | Blind harness, canaries, provenance checks, later replay | Manifest overlap checks and holdout commitments exist. [`harness.py`](../eval/src/proof_eval/harness.py) measures model loss and optional throughput; custom and canary metrics remain unset. This is not evidence that all proposed scientific checks are complete | +| Winner payout | Multi-metric improvement; earliest commitment breaks ties | [`payout.rs`](../crates/proof-score/src/payout.rs) ranks one primary metric and splits exact ties | +| Discovery payout | Ordered multi-metric frontier increments plus method-descriptor novelty and floor controls | The code splits a pass floor and a primary-metric improvement pool, using the sealed baseline/optional champion and exact artifact-digest duplicate checks. It does not implement the paper's full frontier or descriptor-distance rule | +| Topic allocation | Weighted topics and proposed composition equations | Open topics receive equal masses; miner scores sum those masses. The signed configuration allocates Proof 8000 bps and Bounty 2000 bps | +| Proof emission | Evaluated research reaches network rewards | Signing/payout helpers exist in [`proof-challenge`](../crates/proof-challenge/src/lib.rs), but [`main.rs`](../bins/proof-challenge/src/main.rs) starts HTTP with epoch `0` and no automatic leaf-emission loop | +| Shared research collection | Open artifacts with durable, replayable reports | Proof exposes submission records through HTTP, but the service uses `MemoryStore`; a restart loses submissions and scores. An artifact URI/digest is not a durable public archive | +| Bounty and validators | Validators consume public adjudications directly | The Bounty service consumes the external feed and emits signed leaves. Validators verify bundles and recompute allocation, rather than independently re-judge research or poll that feed | +| Failure handling | Abstain rather than invent scores | Missing prerequisites refuse scoring. Current bundle rules use explicit `NoScore` leaves and burn allocation; an unsealed fallback is never a valid validator submit path | +| Synthesis and adoption | Combine proven methods, verify the proposal, adopt only without regression | No synthesiser or automatic shared-stack adoption loop is implemented | + +These gaps are documented, not silently filled by this cleanup. In particular, +the scoring equations and reward configuration are unchanged. + +## What the comparison does not prove + +- The checkpoint example assumes private recipes and selection of one result. + It is not a description of every other subnet or of checkpoint sharing itself. +- The paper's compute and scaling comparison is a model under assumptions, not + a measured cost saving or demonstrated advantage for a deployed Cortex system. +- Private tests and pinned images can reduce some attack opportunities; they do + not guarantee zero overfitting, an honest operator, or a correct judge. +- A signature establishes who signed a result. A digest identifies content. + Neither alone proves that the committed code ran correctly or that a claim + generalizes. See the [threat model](THREAT_MODEL.md). +- Progress should be measured by reproducible findings, independent reuse, + verified combined improvements, and evaluation cost, not submission volume + alone. diff --git a/docs/external-miner/README.md b/docs/external-miner/README.md index 3bb4ee24c..d2819563f 100644 --- a/docs/external-miner/README.md +++ b/docs/external-miner/README.md @@ -2,6 +2,15 @@ # How to mine +Cortex aims to reward reproducible research that can improve shared methods, +rather than only selecting a finished model. New to the project? Read the +[overview](../OVERVIEW.md). + +**Before spending compute on Proof:** review the +[implementation limits](../WHITEPAPER.md#proposal-versus-current-code). The judge +is partial, submission state is in memory, and automatic Proof reward emission +is not wired into the service. A ready status is not an end-to-end payment guarantee. + **Bundle `protocol_version`:** `1` **Miner pays Lium** (`LIUM_API_KEY` / `X-Lium-Api-Key`) on Proof. @@ -62,6 +71,6 @@ https://network.cortex.foundation/challenge/proof/... Never put mnemonics or challenge signing keys in miner clients. Read `LIUM_API_KEY` from the environment. Do not commit it. -Control-plane PRs on `CortexLM/cortex` need a Greptile review before merge +Network software PRs on `CortexLM/cortex` need a Greptile review before merge (`.greptile/`; comment `@greptileai review` if the bot is silent). That is an operator gate, not a miner submit step. diff --git a/docs/external-miner/proof.md b/docs/external-miner/proof.md index 961671686..5e3c1019e 100644 --- a/docs/external-miner/proof.md +++ b/docs/external-miner/proof.md @@ -2,9 +2,15 @@ # Proof — miners -Challenge id is `proof`. One of two live challenges (`bounty` **2000 bps**, -`proof` **8000 bps**, 20/80). That split is live now. It is **not** 7000/3000, -and it is **not** waiting on a digest retune to 5000/5000. +Challenge id is `proof`. The two configured challenges are `bounty` +(**2000 bps**) and `proof` (**8000 bps**), a 20/80 allocation. + +**Implementation warning:** Proof's Python judge is partial, submission records +are in memory, and the service does not yet drive automatic reward-leaf emission. +Do not spend compute on the assumption that `can_score` proves the complete +research-to-payment path. Read the +[paper-to-code comparison](../WHITEPAPER.md#proposal-versus-current-code) and +confirm deployment support with the operator first. **Gateway:** [https://network.cortex.foundation](https://network.cortex.foundation) **CLI:** `ctx proof topics`, then `ctx proof submit` (install: @@ -33,12 +39,12 @@ time). You submit **against that `topic_id`**: 2. a **code artifact** (reproducible recipe — code + lockfile / entrypoint) 3. **declared FLOPs** (must be `≤ topic.flops_budget`) -The artifact is a recipe the digest-pinned RLM judge can re-run under the -topic's FLOP / wall budget. **A weight dump alone is not an artifact.** The -judge never trusts your numbers: it re-runs the code, compares the claim to -the public split, and the harness fills holdout NLL / throughput. Holdout -records stay sealed until after your submission digest is frozen. You never -see them. +The artifact contract requires a recipe reproducible under the topic's FLOP / +wall budget. **A weight dump alone is not an artifact.** The intended judge +re-runs that recipe and a separate harness measures holdout loss or throughput. +The current Python image does not yet implement arbitrary recipe reproduction; +its static checks and model measurements are only part of that design. +Holdout records are not included in public topic responses. `GET /challenge/proof/v1/status` shows `can_score`, `eval_backend`, `force_sim`, `live_harvest_wired`, `baseline_sealed`, public pin `inference` @@ -51,8 +57,8 @@ Muon, token superposition, and “decentralized training without InfiniBand” are *examples* of solutions or of topics — they are not the product. Pass gates (reproduced, no contamination, under budget, beat epsilon) are -fail-closed. What you are **paid** after a pass depends on the topic's -`payout_mode`. Your paid score is the **sum of per-topic** masses over +fail-closed. The implemented **payout calculation** after a pass depends on the +topic's `payout_mode`. The score is the **sum of per-topic** masses over currently `open` ids, not a mean of binary lattices. A skipped topic is 0 on that topic. Zero open topics → the host cannot score (`503`), not a paid 0. diff --git a/eval/README.md b/eval/README.md index fdf422c91..2ad72ae29 100644 --- a/eval/README.md +++ b/eval/README.md @@ -1,6 +1,6 @@ # Proof eval image -Digest-pinned scorer for challenge `proof`. The control plane boots +Evaluation image for challenge `proof`. The network's harvest service boots `ghcr.io/cortexlm/proof-eval@sha256:…` on a Lium pod the miner pays for, stages `request.json` over stdin, and runs: @@ -23,3 +23,16 @@ No secrets, holdout text, teacher hosts, or Modal references are baked in. Shard bytes arrive via `PROOF_HOLDOUT_STORE/`. Optional local measurement weights via `PROOF_PROXY_MODEL_DIR` or `PROOF_ALLOW_MODEL_DOWNLOAD=1`. + +## Implementation limits + +This is not yet the autonomous research judge described in the whitepaper. +`judge.py` requests an authenticated acknowledgement and does not parse a +research verdict. `agent.py` performs static text checks; `cli.py` inspects the +claim and a local baseline script, then measures a configured model directory. +General reproduction of a committed miner recipe is not implemented. + +`harness.py` measures model loss and optional throughput. It leaves custom and +canary metrics unset. A digest pin or successful judge HTTP request is not proof +that arbitrary research was reproduced. See the +[paper-to-code comparison](../docs/WHITEPAPER.md). diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index c95c4852e..b66b85b2b 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -27,7 +27,6 @@ serde_json = "1.0" serde_yaml = "0.9" sha2 = "0.10" twox-hash = "2.1" -walkdir = "2.5" [lints] workspace = true diff --git a/xtask/src/metadata_snapshot.rs b/xtask/src/metadata_snapshot.rs index 7bee8852f..66e670b75 100644 --- a/xtask/src/metadata_snapshot.rs +++ b/xtask/src/metadata_snapshot.rs @@ -380,12 +380,9 @@ struct PalletView { constants: Vec, } -#[allow(dead_code)] struct StorageView { name: String, hashers: Vec, - key_types: Vec, // kept for future typed decode - value_type: u32, default_bytes: Vec, } @@ -412,11 +409,7 @@ fn from_v15(m: RuntimeMetadataV15) -> MetaView { .map(|p| { let (storage_prefix, storage) = match p.storage { Some(s) => { - let entries = s - .entries - .into_iter() - .map(|e| storage_from_entry(e, &types)) - .collect(); + let entries = s.entries.into_iter().map(storage_from_entry).collect(); (Some(s.prefix), entries) } None => (None, Vec::new()), @@ -450,11 +443,7 @@ fn from_v14(m: frame_metadata::v14::RuntimeMetadataV14) -> MetaView { .map(|p| { let (storage_prefix, storage) = match p.storage { Some(s) => { - let entries = s - .entries - .into_iter() - .map(|e| storage_from_entry_v14(e, &types)) - .collect(); + let entries = s.entries.into_iter().map(storage_from_entry).collect(); (Some(s.prefix), entries) } None => (None, Vec::new()), @@ -480,77 +469,18 @@ fn from_v14(m: frame_metadata::v14::RuntimeMetadataV14) -> MetaView { MetaView { pallets, types } } -fn storage_from_entry( - e: frame_metadata::v15::StorageEntryMetadata, - types: &PortableRegistry, -) -> StorageView { - let (hashers, key_types, value_type) = match e.ty { - StorageEntryType::Plain(ty) => (vec![], vec![], ty.id), - StorageEntryType::Map { - hashers, - key, - value, - } => { - let hasher_names = hashers.iter().map(|h| format!("{h:?}")).collect(); - // key may be a tuple of N types matching N hashers - let key_types = flatten_key_types(key.id, hashers.len(), types); - (hasher_names, key_types, value.id) - } +fn storage_from_entry(e: frame_metadata::v15::StorageEntryMetadata) -> StorageView { + let hashers = match e.ty { + StorageEntryType::Plain(_) => Vec::new(), + StorageEntryType::Map { hashers, .. } => hashers.iter().map(|h| format!("{h:?}")).collect(), }; StorageView { name: e.name, hashers, - key_types, - value_type, default_bytes: e.default, } } -fn storage_from_entry_v14( - e: frame_metadata::v14::StorageEntryMetadata, - types: &PortableRegistry, -) -> StorageView { - use frame_metadata::v14::StorageEntryType as T; - let (hashers, key_types, value_type) = match e.ty { - T::Plain(ty) => (vec![], vec![], ty.id), - T::Map { - hashers, - key, - value, - } => { - let hasher_names = hashers.iter().map(|h| format!("{h:?}")).collect(); - let key_types = flatten_key_types(key.id, hashers.len(), types); - (hasher_names, key_types, value.id) - } - }; - StorageView { - name: e.name, - hashers, - key_types, - value_type, - default_bytes: e.default, - } -} - -fn flatten_key_types(key_id: u32, n_hashers: usize, types: &PortableRegistry) -> Vec { - if n_hashers <= 1 { - return vec![key_id]; - } - if let Some(ty) = types.resolve(key_id) { - if let TypeDef::Tuple(t) = &ty.type_def { - if t.fields.len() == n_hashers { - return t.fields.iter().map(|f| f.id).collect(); - } - } - if let TypeDef::Composite(c) = &ty.type_def { - if c.fields.len() == n_hashers { - return c.fields.iter().map(|f| f.ty.id).collect(); - } - } - } - vec![key_id] -} - fn find_pallet<'a>(meta: &'a MetaView, name: &str) -> Result<&'a PalletView, String> { meta.pallets .iter() @@ -968,6 +898,46 @@ fn normalize_json(text: &str) -> Result { mod tests { use super::*; + #[test] + fn storage_view_keeps_map_hashers_and_value_query_default() { + // v15 re-exports v14 storage entries, so both metadata versions + // must use the same projection without changing hasher order. + use frame_metadata::v14::{StorageEntryMetadata, StorageEntryModifier, StorageHasher}; + + let entry = StorageEntryMetadata:: { + name: "CommitRevealWeightsVersion".into(), + modifier: StorageEntryModifier::Default, + ty: StorageEntryType::Map { + hashers: vec![StorageHasher::Twox64Concat, StorageHasher::Identity], + key: 1_u32.into(), + value: 2_u32.into(), + }, + default: 4_u16.encode(), + docs: Vec::new(), + }; + let view = storage_from_entry(entry); + assert_eq!(view.name, "CommitRevealWeightsVersion"); + assert_eq!(view.hashers, ["Twox64Concat", "Identity"]); + assert_eq!(decode_u16(&view.default_bytes), Some(4)); + } + + #[test] + fn storage_view_plain_entry_has_no_hashers() { + use frame_metadata::v15::{StorageEntryMetadata, StorageEntryModifier}; + + let entry = StorageEntryMetadata:: { + name: "Value".into(), + modifier: StorageEntryModifier::Default, + ty: StorageEntryType::Plain(1_u32.into()), + default: vec![0], + docs: Vec::new(), + }; + let view = storage_from_entry(entry); + assert_eq!(view.name, "Value"); + assert!(view.hashers.is_empty()); + assert_eq!(view.default_bytes, [0]); + } + fn sample_lock() -> Lockfile { Lockfile { schema_version: 1,