Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions .github/workflows/release-ctx.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
name: release-ctx

# Builds the miner CLI for every platform miners actually run and attaches the
# archives (plus SHA256SUMS.txt) to the GitHub Release for the tag.
# scripts/install-ctx.sh downloads from that release and verifies the checksum,
# so a release without the sums file installs nothing.

on:
push:
tags: ["v*.*.*"]
workflow_dispatch:
inputs:
tag:
description: "Existing tag to build and attach assets to"
required: true
type: string

permissions:
contents: write

env:
CARGO_TERM_COLOR: always

jobs:
build:
name: ctx ${{ matrix.name }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- name: linux-amd64
runner: ubuntu-latest
target: x86_64-unknown-linux-musl
musl: true
- name: linux-arm64
runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
musl: true
- name: darwin-amd64
runner: macos-latest
target: x86_64-apple-darwin
- name: darwin-arm64
runner: macos-latest
target: aarch64-apple-darwin
- name: windows-amd64
runner: windows-latest
target: x86_64-pc-windows-msvc
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.tag || github.ref }}

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 security Mutable action publishes releases

The release workflow runs dtolnay/rust-toolchain@master while it has authority to write repository contents and publish dist/* release assets. If that mutable upstream reference is retargeted or compromised, its code can publish attacker-controlled CLI binaries and checksums. Pin third-party actions to reviewed full commit SHAs and restrict write authority to only the release work that needs it.

How this was verified: The workflow grants contents-write authority, invokes the mutable @master action, and uploads dist/* through the release step.

Knowledge Base Used: Deployment automation and infrastructure

Artifacts

Release workflow assertion harness

  • This executable parses action references, contents permission, and release asset publication in the workflow.

Immutable-action control output

  • The control reports immutable references, read-only contents, and no release asset glob.

Release workflow assertion output

  • The workflow contains the mutable master ref, contents-write permission, and dist asset publication.

View artifacts

T-Rex Ran code and verified through T-Rex

with:
toolchain: "1.96.0"
targets: ${{ matrix.target }}

- name: Cache cargo
uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}

# `ring` needs a C toolchain, and the musl targets are what make the
# Linux archives run on any distro without a glibc floor.
- name: Install musl toolchain
if: matrix.musl
run: sudo apt-get update && sudo apt-get install -y musl-tools

- name: Build
run: cargo build -p ctx --release --locked --target ${{ matrix.target }}

- name: Package (unix)
if: matrix.name != 'windows-amd64'
run: |
set -euo pipefail
install -m 0755 "target/${{ matrix.target }}/release/ctx" ctx
tar -czf "ctx-${{ matrix.name }}.tar.gz" ctx
rm ctx

- name: Package (windows)
if: matrix.name == 'windows-amd64'
shell: pwsh
run: |
Copy-Item "target/${{ matrix.target }}/release/ctx.exe" ctx.exe
Compress-Archive -Path ctx.exe -DestinationPath "ctx-${{ matrix.name }}.zip"
Remove-Item ctx.exe

- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: ctx-${{ matrix.name }}
path: |
ctx-${{ matrix.name }}.tar.gz
ctx-${{ matrix.name }}.zip
if-no-files-found: error
retention-days: 7

release:
name: attach assets
needs: [build]
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
pattern: ctx-*
merge-multiple: true
path: dist

# The install script fails closed without this file, so generate it from
# the archives that were actually built rather than from a static list.
- name: Checksums
run: |
set -euo pipefail
cd dist
ls -1
sha256sum ctx-* > SHA256SUMS.txt
cat SHA256SUMS.txt

- name: Attach to release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ inputs.tag || github.ref_name }}
files: dist/*
fail_on_unmatched_files: true
# Keep hand-written release notes; only add the install block.
append_body: true
body: |
Cortex subnet CLI (`ctx`) for ${{ inputs.tag || github.ref_name }}.

```bash
curl -fsSL https://raw.githubusercontent.com/CortexLM/cortex/main/scripts/install-ctx.sh | sh
ctx challenges
ctx status
```

Default gateway: https://network.cortex.foundation
9 changes: 5 additions & 4 deletions .greptile/rules.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Cortex review rules

This is a Bittensor subnet control plane (`CortexLM/cortex`), not an app-platform
or SOC2 checklist. Five **live** challenge ids: `relearn` (3000 bps),
`relearn-image` (1000; crates keep the `relearn-t2i-*` spelling),
`relearn-agent` (1000), `bounty` (3000), `proof` (2000). `relearn-mm` is **off** (no trust-root
row, `mm` compose profile only).
or SOC2 checklist. Two **live** challenge ids: `bounty` (7000 bps) and
`proof` (3000). Proof's eval digest is empty (503), so an equal 5000/5000
split would leave half the subnet unpayable. Sum is 10000. `relearn`, `relearn-image`,
`relearn-agent`, `relearn-mm`, `design`, and `prism` are **off** (no trust-root
row). Relearn* code stays behind the `relearn` / `mm` compose profiles.

- **Fail-closed.** Missing holdout file, commitment mismatch, unpinned eval
digest, or unset teacher → refuse / 503. Never score the public split as a
Expand Down
34 changes: 12 additions & 22 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Short contract for agents and operators. Prefer linking over restating runbooks.

**Product:** Cortex ([`CortexLM/cortex`](https://github.com/CortexLM/cortex)) — Bittensor subnet control plane. **Five live challenges:** `relearn` (3000 bps), `relearn-image` (1000), `relearn-agent` (1000), `bounty` (3000), `proof` (2000). Encoder-attach Multimodal (`relearn-mm`) is **off** — no trust-root row, `mm` compose profile only. `relearn` and `relearn-agent` post-train the **same** base `Qwen/Qwen3.8-27B` (teacher `incoai/GLM-5.3-NVFP4` from `RELEARN_TEACHER_LOCAL_DIR`); Agent is a separate challenge scored on replayed tool traces, not a rename of `relearn`. Proof scores operator-published research topics (dynamic `topic_id`, digest-pinned RLM judge); it is not a rename of `relearn`. Relearn eval images live in [`CortexLM/relearn`](https://github.com/CortexLM/relearn). Naming split (Cortex vs leftover `base` / `BASE_*`, and `relearn-image` vs the `relearn-t2i-*` crates): [`docs/NAMING.md`](docs/NAMING.md).
**Product:** Cortex ([`CortexLM/cortex`](https://github.com/CortexLM/cortex)) — Bittensor subnet control plane. **Two live challenges:** `bounty` (7000 bps) and `proof` (3000 bps). Proof's `eval_image_digest` is empty (submits 503), so 7000/3000 keeps most emission payable; retune to 5000/5000 in the same ceremony that pins a non-empty proof-eval digest. Sum is 10000. `relearn`, `relearn-image`, `relearn-agent`, `relearn-mm`, `design`, and `prism` are **off** — no trust-root row, so they have no emission and no leaf may verify. Relearn* code stays behind the `relearn` / `mm` compose profiles. Proof scores operator-published research topics (dynamic `topic_id`, digest-pinned RLM judge); empty `eval_image_digest` → 503 (do not invent a sha256). Naming split (Cortex vs leftover `base` / `BASE_*`): [`docs/NAMING.md`](docs/NAMING.md).

PRs require a [Greptile](https://greptile.com) review (`.greptile/`). If the bot is silent, comment `@greptileai review`.

Expand Down Expand Up @@ -36,29 +36,23 @@ Working branch: **`main`**. Prod ships from annotated tags `v*.*.*` cut on `main
|-----|-----|------------|
| `gateway_sk` | Gateway | Bundle **seal** signatures (`POST /v1/admin/seal`) |
| `gateway_admin_token` | Gateway + seal scripts | Bearer for **`/v1/admin/*`** (seal, backends, attest-grant). **Required** when `BASE_GATEWAY_REQUIRE_OWNER=1` |
| `relearn_sk` | Relearn / smoke | Signed leaves (`POST /v1/weights/raw`); pub must match trust root |
| `relearn_t2i_sk` | Relearn Image | Signed `relearn-image` leaves; pub must match trust root |
| `relearn_agent_sk` | Relearn Agent | Signed `relearn-agent` leaves; pub must match trust root |
| `bounty_sk` | Bounty / smoke | Signed bounty leaves; pub must match trust root |
| `proof_sk` | Proof / smoke | Signed `proof` leaves and topic documents; pub must match trust root |
| Gateway owner wallet + `BASE_GATEWAY_REQUIRE_OWNER` | Gateway | Master-only **identity** check (live/prod). **Not** required to seal or serve `/v1/weights/latest` |
| Validator wallet | Validator | On-chain weight **submit** only — validators *fetch* sealed weights; they do not need a gateway wallet |

`GET /v1/weights/latest` is **fail-closed**: with no sealed bundle (or decode error) the gateway serves a **burn vector** (uid 0 = 100%, `sealed: false`) rather than 404. A missing gateway wallet is unrelated.
`GET /v1/weights/latest` is **fail-closed**: with no sealed bundle (or decode error) the gateway serves a **burn vector** (uid 0 = 100%, `sealed: false`) rather than 404. A missing gateway wallet is unrelated. Validators MUST NOT submit that unsealed vector and MUST NOT submit a persisted LKG seal while latest is unsealed. A sealed uid0=100% vector to the registered owner is also not a submit path.

## Challenge public docs (miner-facing repos)

Each live challenge has a **separate public GitHub repo** for miners. Those repos must contain **only** human miner documentation plus example / test harness code — **never** control-plane, gateway, validator, or orchestrator source. Public repos use a `docs/` layout (hero README + banner under `assets/`).
Each live challenge has miner docs in this repo. Public repos (when they exist) must contain **only** human miner documentation plus example / test harness code — **never** control-plane, gateway, validator, or orchestrator source.

| Challenge | Public repo | Role |
| Challenge | Public docs | Role |
|-----------|-------------|------|
| Relearn | [`CortexLM/relearn`](https://github.com/CortexLM/relearn) | Eval image, harness, generators, teacher, miner docs |
| Relearn Image | [`CortexLM/relearn`](https://github.com/CortexLM/relearn) | Cosmos3 fine-tune harness + Q-Judger runner; in-repo pointer [`docs/external-miner/relearn-image.md`](docs/external-miner/relearn-image.md) |
| Relearn Agent | [`CortexLM/relearn`](https://github.com/CortexLM/relearn) | Episode environment, trace replay, ablation arms; in-repo pointer [`docs/external-miner/relearn-agent.md`](docs/external-miner/relearn-agent.md) |
| 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`. Short miner pointers: [`docs/external-miner/relearn.md`](docs/external-miner/relearn.md), [`docs/external-miner/relearn-image.md`](docs/external-miner/relearn-image.md), [`docs/external-miner/relearn-agent.md`](docs/external-miner/relearn-agent.md), [`docs/external-miner/bounty.md`](docs/external-miner/bounty.md), [`docs/external-miner/proof.md`](docs/external-miner/proof.md). Historical frozen specs (`docs/DESIGN_CHALLENGE.md`, `docs/PRISM.md`) stay archived; they are not live products. Do not send miners to Design or Prism docs.
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.

**When a challenge product or public API changes**, agents **must** update:

Expand All @@ -73,18 +67,14 @@ When verifying a challenge (local-e2e, staging, or focused tests), **simulate a

1. Happy-path harness / intake POST (or equivalent) through the challenge service on master.
2. Edge / failure probes: bad harness, sanitize reject, quota, wrong routes/auth.
3. **Relearn — submit:** `POST /v1/submissions` with a 64-hex hotkey + artifact digest (optional `X-Lium-Api-Key`). Poll `GET /v1/submissions/{id}` until `awaiting_admin` or `rejected`. Holdout must stay sealed until the digest freezes. A regression must not become champion.
4. **Relearn — promote:** with operator bearer (`deploy/secrets/relearn/admin_tokens`), `POST /v1/admin/promote` only for an eligible paired win.
5. **Relearn Image — submit:** `POST /v1/submissions` with a manifest naming the pinned Cosmos3 base and OpenMDW 1.1. A Flux-family base must be a `400`, not a low score. `GET /v1/prompts` must publish the public split's frozen strings **and** seeds, and must never leak a holdout id. Probe contamination (declare a scored prompt id) and a pillar collapse; both must reject.
6. **Relearn Agent — submit:** `POST /v1/submissions` with a declared training manifest. An empty manifest must fail `contamination_evidence_missing`, not pass. A run whose tool-ablation or observation-shuffle arm is missing, or whose ablation drop is under the floor, must yield lattice `0` — a model that answers without the tools is not an agent.
7. **Bounty — pair + report:** `cortex-bounty pair --hotkey <ss58> --account-id <id>`, then `POST /v1/pair` (terms + signature) and `POST /v1/reports`. Operator bearer `POST /v1/admin/adjudicate` (`valid` / `already_fixed_not_prod` / `invalid_malicious` / `duplicate`). Scoring **reads** CortexLM/backend public JSON (`BOUNTY_BACKEND_PUBLIC_URL`); do not serve `/v1/public/*` from this repo.
8. **Bounty — fail-closed scorer:** the CortexLM/backend public feed is the only scorer. With no readable `BOUNTY_BACKEND_PUBLIC_URL`, `POST /v1/reports` must answer **503** and the emitter must pay **nobody** — it still covers `E` with `NoScore(ChallengeInternal)`, because a paid challenge with no leaves 409s the seal for every challenge. `BOUNTY_FORCE_SIM` is retired — do not reintroduce an offline bounty scorer. See [`docs/BOUNTY.md`](docs/BOUNTY.md).
9. **Proof — submit:** `POST /v1/submissions` with a `topic_id`. Missing / unknown / not-open → **400** (no row). Architecture ≠ the baked proxy → **400**. Empty `eval_image_digest`, zero open topics, or an unsealed baseline → **503**. Contamination / empty manifest persist **rejected** without rent. `GET /v1/proof/topics` must never leak holdout records.
10. Leaf emission → `POST /v1/weights/raw` → seal → `GET /v1/weights/latest` with **`sealed: true`** (burn fallback alone is not a real seal).
3. **Bounty — pair + report:** `ctx bounty pair --hotkey <ss58> --account-id <id> --accept-terms`, then `POST /v1/pair` (terms + signature) and `POST /v1/reports`. Operator bearer `POST /v1/admin/adjudicate` (`valid` / `already_fixed_not_prod` / `invalid_malicious` / `duplicate`). Scoring **reads** CortexLM/backend public JSON (`BOUNTY_BACKEND_PUBLIC_URL`); do not serve `/v1/public/*` from this repo.
4. **Bounty — fail-closed scorer:** the CortexLM/backend public feed is the only scorer. With no readable `BOUNTY_BACKEND_PUBLIC_URL`, `POST /v1/reports` must answer **503** and the emitter must pay **nobody** — it still covers `E` with `NoScore(ChallengeInternal)`, because a paid challenge with no leaves 409s the seal for every challenge. `BOUNTY_FORCE_SIM` is retired — do not reintroduce an offline bounty scorer. See [`docs/BOUNTY.md`](docs/BOUNTY.md).
5. **Proof — submit:** `POST /v1/submissions` with a `topic_id`. Missing / unknown / not-open → **400** (no row). Architecture ≠ the baked proxy → **400**. Empty `eval_image_digest`, zero open topics, or an unsealed baseline → **503**. Contamination / empty manifest persist **rejected** without rent. `GET /v1/proof/topics` must never leak holdout records.
6. Leaf emission → `POST /v1/weights/raw` → seal → `GET /v1/weights/latest` with **`sealed: true`** (burn fallback alone is not a real seal).

**Never host Sim in staging/prod** for live scoring. `RELEARN_FORCE_SIM=1`, `RELEARN_T2I_FORCE_SIM=1`, `RELEARN_MM_FORCE_SIM=1`, and `PROOF_FORCE_SIM=1` are CI/local opt-in only (`deploy/scripts/assert-compose-matrix.sh` fails if a droplet overlay sets one). Live rent requires a digest pin in the matching `config/*-pin.toml` plus miner BYOK (`LIUM_API_KEY` / `X-Lium-Api-Key`). Never log or commit that key.
**Never host Sim in staging/prod** for live scoring. `PROOF_FORCE_SIM=1` is CI/local opt-in only (`deploy/scripts/assert-compose-matrix.sh` fails if a droplet overlay sets one). Live Proof rent requires a digest pin in `config/proof-pin.toml` plus miner BYOK (`LIUM_API_KEY` / `X-Lium-Api-Key`). Never log or commit that key. Do not invent `eval_image_digest`.

**Relearn Image product rules (do not weaken):** the generator seed is `nvidia/Cosmos3-Super-Text2Image` under OpenMDW 1.1; Flux-family bases are refused; Q-Judger (`Qwen/Qwen-Image-Bench`) is the only judge and its card-fixed inference parameters are part of the contract; eval prompts are frozen in the pin so no miner brings its own upsampler to the scored split; the holdout lives in git only as a commitment. **Relearn Agent product rules (do not weaken):** the unit of work is an episode (goal + tool environment), not a prompt; trace replay, tool ablation, and observation shuffle are all mandatory arms and a missing arm fails closed; the capability canary stays off the visible score. **Bounty product rules:** pay is precision x severity, an unpriced `valid` row is not creditable, and the triage-noise ratio stays off the visible score. **Proof product rules (do not weaken):** topics are operator-published signed documents, not a git catalog; a topic may tighten a floor never loosen it; a baseline must be sealed to open; the paid score is the mean of per-topic lattices over currently `open` ids; empty open set / empty eval digest fails closed (`503`); `custom` unknown ids refuse at publish.
**Bounty product rules:** pay is precision x severity, an unpriced `valid` row is not creditable, and the triage-noise ratio stays off the visible score. **Proof product rules (do not weaken):** topics are operator-published signed documents, not a git catalog; a topic may tighten a floor never loosen it; a baseline must be sealed to open; the paid score is the mean of per-topic lattices over currently `open` ids; empty open set / empty eval digest fails closed (`503`); `custom` unknown ids refuse at publish.

Local smoke automates the weights seal step via `weights-smoke` inside `./deploy/scripts/local-e2e.sh --smoke` (see [`deploy/AGENTS.md`](deploy/AGENTS.md) and [`docs/runbooks/local-testnet-e2e.md`](docs/runbooks/local-testnet-e2e.md)).

Expand Down Expand Up @@ -129,7 +119,7 @@ Match CI (`.github/workflows/ci.yml`):
| Doc authority vs evidence | [`docs/AGENTS.md`](docs/AGENTS.md) |
| Component status | [`docs/COMPLETENESS.md`](docs/COMPLETENESS.md) |
| Frozen contracts | [`docs/BUNDLE_SPEC.md`](docs/BUNDLE_SPEC.md), [`docs/DESIGN_CHALLENGE.md`](docs/DESIGN_CHALLENGE.md), [`docs/PRISM.md`](docs/PRISM.md) |
| Relearn miners | [`docs/external-miner/relearn.md`](docs/external-miner/relearn.md), [`relearn-image.md`](docs/external-miner/relearn-image.md), [`relearn-agent.md`](docs/external-miner/relearn-agent.md) · long guide: [CortexLM/relearn](https://github.com/CortexLM/relearn) |
| Bounty miners | [`docs/external-miner/bounty.md`](docs/external-miner/bounty.md) · operator spec: [`docs/BOUNTY.md`](docs/BOUNTY.md) |
| Proof miners | [`docs/external-miner/proof.md`](docs/external-miner/proof.md) · operator spec: [`docs/PROOF.md`](docs/PROOF.md) |
| Validators | [`docs/external-miner/validators.md`](docs/external-miner/validators.md) |
| Threat / operator checklist | [`docs/THREAT_MODEL.md`](docs/THREAT_MODEL.md), [`docs/OPERATOR_SECURITY.md`](docs/OPERATOR_SECURITY.md) |
Expand Down
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading