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
2 changes: 2 additions & 0 deletions bins/design-challenge/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,7 @@ fn build_app_state(
staging_root: cli.staging_root.clone(),
stage_delay,
auto_retry_max: cli.auto_retry_max,
emit_poll: Duration::from_secs(15),
},
Arc::clone(&store),
sandbox,
Expand Down Expand Up @@ -673,6 +674,7 @@ async fn cmd_serve(cli: Cli) -> Result<(), String> {
}
tokio::spawn(Arc::clone(&orch).run_round_loop());
tokio::spawn(Arc::clone(&orch).run_sweeper());
tokio::spawn(Arc::clone(&orch).run_emitter());

let app = design_router(state);
let listener = TcpListener::bind(cli.bind)
Expand Down
49 changes: 49 additions & 0 deletions crates/design-challenge/src/orchestrator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ pub struct OrchestratorConfig {
/// Auto-retry budget for infra-class failures (initial attempt + this
/// many retries). Default 3; `cheat` / `rejected` are always terminal.
pub auto_retry_max: u32,
/// Poll interval for the late-tempo D24 filler.
pub emit_poll: Duration,
}

impl Default for OrchestratorConfig {
Expand All @@ -161,6 +163,7 @@ impl Default for OrchestratorConfig {
staging_root: PathBuf::from("/var/lib/design/staging"),
stage_delay: Duration::ZERO,
auto_retry_max: 3,
emit_poll: Duration::from_secs(15),
}
}
}
Expand All @@ -177,6 +180,8 @@ pub struct Orchestrator<C: ChainClient + Send + Sync> {
gating: Option<Arc<dyn GatingStore>>,
/// Shared chain-epoch cache (HTTP `AppState.epoch` + sweeper clock).
epoch_cache: Option<Arc<AtomicU64>>,
/// Last epoch successfully covered by [`Self::emit_leaves`].
emitted_epoch: AtomicU64,
}

impl<C: ChainClient + Send + Sync> std::fmt::Debug for Orchestrator<C> {
Expand Down Expand Up @@ -209,6 +214,7 @@ impl<C: ChainClient + Send + Sync + 'static> Orchestrator<C> {
sk,
gating: None,
epoch_cache: None,
emitted_epoch: AtomicU64::new(0),
}
}

Expand Down Expand Up @@ -279,6 +285,42 @@ impl<C: ChainClient + Send + Sync + 'static> Orchestrator<C> {
}
}

/// Late-tempo D24 filler: `NotAttempted` coverage when no admin award fired
/// (waits ~last 48 blocks so `award_round` can land Score leaves first).
pub async fn run_emitter(self: Arc<Self>)
where
C: Sync,
{
loop {
if let Err(e) = self.emitter_tick().await {
warn!(error = %e, "design emitter tick error");
}
sleep(self.cfg.emit_poll).await;
}
}

/// One emitter tick. `Ok(true)` when a leaf set was submitted this tick.
///
/// # Errors
/// Chain / sign / submit failures (retried next tick).
pub async fn emitter_tick(&self) -> Result<bool, String>
where
C: Sync,
{
let state = chain::gather_schedule_state(self.chain.as_ref(), self.cfg.netuid)
.map_err(|e| format!("schedule: {e}"))?;
let epoch = state.subnet_epoch_index;
if epoch == 0 || self.emitted_epoch.load(Ordering::Relaxed) >= epoch {
return Ok(false);
}
let tempo = u64::from(state.tempo.max(1));
if state.blocks_since_last_step.saturating_add(48) < tempo {
return Ok(false);
}
self.emit_leaves().await?;
Comment on lines +310 to +320

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Inspect all callers and the gateway submission contract.
ast-grep outline crates/design-challenge/src/orchestrator.rs --items all
rg -n -C 8 '\b(award_round|emitter_tick|emit_leaves|submit_signed_leaf_set)\b|emitted_epoch|/v1/weights/raw' crates bins

Repository: BaseIntelligence/base

Length of output: 50377


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Read the relevant orchestrator sections and design contract docs without modifying files.
sed -n '288,410p' crates/design-challenge/src/orchestrator.rs
printf '\n--- award_round scoring and emit section ---\n'
sed -n '404,570p' crates/design-challenge/src/orchestrator.rs
printf '\n--- emit_leaves section ---\n'
sed -n '1104,1157p' crates/design-challenge/src/orchestrator.rs

printf '\n--- design docs relevant excerpts ---\n'
fd -a . docs | sed 's#^\./##' | rg 'DESIGN_CHALLENGE|design_challenge|DESIGN.*CHALLENGE|challenge' | head -80
for f in $(fd -i 'design_challenge\.(md|json|yaml|yml)|DESIGN_CHALLENGE\.(md|json|yaml|yml)' docs); do
  echo "%%%%%% $f %%%%%%"
  wc -l "$f"
  rg -n -C 6 '(d24|D24|leaf set|leaf sets|weights.raw|weights/-raw|expected set|expected set|submission|scoring|award|NotAttempted|NoScore|sealed|emitter|emitter_tick|emitted_epoch)' "$f" || true
done

Repository: BaseIntelligence/base

Length of output: 26702


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo '--- BUNDLE_SPEC relevant leaf seal excerpts ---'
sed -n '1,260p' docs/BUNDLE_SPEC.md

echo '--- raw weight store implementation excerpts ---'
fd -a '.*' crates/gateway crates/challenge-common | rg 'weights_store|raw|submit|leaf' | head -80 || true
rg -n -C 5 'struct RawWeightStore|RawWeightStore|RawWeightAccepted|RawWeightRequest|get\(|accept_raw_weight|expected_set|sealed|signature|weights_store' crates/gateway crates/challenge-common crates/gateway-core 2>/dev/null || true

echo '--- async-lock availability and design-challenge Cargo deps ---'
rg -n 'tokio::sync::Mutex|async-lock|futures::lock::Mutex|Mutex<T>' crates/design-challenge Cargo.toml crates -g '*.toml' | head -40

Repository: BaseIntelligence/base

Length of output: 50380


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo '--- gateway raw weight acceptance ---'
sed -n '33,155p' crates/gateway/src/weights.rs

echo '--- raw weight store implementations ---'
rg -n -C 12 'impl RawWeightStore for MemoryRawWeightStore|AcceptError|raw_weight|Postgres.*raw|pgx|insert|Conflict|get_by_epoch|latest_epoch|get_by_epoch|epoch_end|epoch' crates/gateway crates/gateway-core crates -g 'weights_store.rs' -g 'sealer.rs' -g 'seal*.rs' | sed -n '1,240p'

echo '--- design store schema around scores_for_epoch ---'
rg -n -C 10 'scores_for_epoch|ratings|final_score|rating' crates/design-store crates/design-challenge src crates 2>/dev/null | sed -n '1,220p'

echo '--- Cargo dependencies Tokio/async lock ---'
rg -n 'tokio =|async-lock|futures|parking_lot' Cargo.toml devtools crates/design-challenge/Cargo.toml Cargo.lock || true

Repository: BaseIntelligence/base

Length of output: 34677


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo '--- all scores_for_epoch implementations ---'
rg -n -C 8 'async fn scores_for_epoch|fn scores_for_epoch|scores_for_epoch\(' crates/design-store crates -g '*.rs'

echo '--- gateway raw weight persistence implementations and list helper ---'
rg -n -C 12 'struct.*RawWeight|RawWeightStore for|impl RawWeightStore|insert\(|get\(|list_for_epoch|epochs_for_challenges|list_challenges|seal.*epoch|seal_epoch' crates/gateway crates -g '*.rs' | sed -n '1,260p'

echo '--- orchestrator award_round exact end ---'
sed -n '540,570p' crates/design-challenge/src/orchestrator.rs

Repository: BaseIntelligence/base

Length of output: 27022


Serialize D24 leaf projection with award_round.

run_emitter can submit NotAttempted leaves before award_round persists scores and submits scored leaves. Since emitted_epoch only guards the emitter and award_round does not recheck it, emit_leaves can expose a stale D24 projection to sealing. Use one shared async lock, or a store-backed transaction, held across score persistence and leaf submission in award_round; acquire it before rechecking emitted_epoch in emitter_tick as well.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/design-challenge/src/orchestrator.rs` around lines 314 - 330,
Serialize D24 leaf projection updates between run_emitter and award_round using
one shared async lock or store transaction. Hold it across score persistence and
leaf submission in award_round, and acquire it in emitter_tick before rechecking
emitted_epoch and calling emit_leaves, so stale NotAttempted leaves cannot be
exposed before scored leaves.

Source: Coding guidelines

Ok(true)
}

async fn sweep_unscored_timeouts(&self) -> Result<(), String> {
let current = self.current_epoch();
if current == 0 {
Expand Down Expand Up @@ -1089,6 +1131,13 @@ impl<C: ChainClient + Send + Sync + 'static> Orchestrator<C> {
submit_signed_leaf_set(self.gateway.as_ref(), &signed)
.await
.map_err(|e| e.to_string())?;
self.emitted_epoch.store(epoch, Ordering::Relaxed);
info!(
epoch,
participants = expected_set.len(),
last_epoch_block = state.last_epoch_block,
"design leaf set submitted"
);
Ok(())
}
}
Expand Down
8 changes: 5 additions & 3 deletions deploy/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ Terraform: [`terraform/`](terraform/). Firewall: SSH from operator IP; CI uses e

| File | Purpose |
|------|---------|
| `compose/role-master.yml` | gateway profile, VPC publish |
| `compose/role-validator.yml` | no gateway; external gateway endpoint |
| `compose/role-master.yml` | gateway profile, VPC publish; **no validator** (avoids dual CRV4 submit) |
| `compose/role-validator.yml` | no gateway; external gateway endpoint; sole on-chain submitter |
| `compose/env-staging.yml` | testnet 541, faster coordination |
| `compose/env-prod.yml` | mainnet, conservative intervals |
| `compose/env-local.yml` | **local only** — ports/smoke knobs/tunnel env; always on top of `env-staging` |
Expand Down Expand Up @@ -120,7 +120,9 @@ Prod (`env-prod.yml` + `base-burn-seal.service`): onfinality `public-ws` primary

Validator logs should show `Match epoch=` then `Match → submit_intent` / `submit_timelocked ok`. Keep legacy Python weight submit **stopped** to avoid double-commit.

**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. Sole on-chain submitter for hotkey `5Gzi…` is the Rust validator on `192.81.218.11` — do **not** start `base-weight-submitter-5gzi` on `validator-root` unless CR ownership is moved off Rust.
**Sole on-chain submitter (mainnet hotkey `5Gzi…`):** Rust `base-validator-1` on **`base-prod-validator` (`192.81.218.11`) only**. `role-master.yml` profiles the validator under `never`; `remote-deploy.sh --role master` force-removes any leftover container. Do **not** run a second validator (or Python weight submitter) with the same wallet — dual submitters fight `WeightsSetRateLimit` and can leave CRV4 commits stuck while incentive still shows a prior monopoly UID.

**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.

Expand Down
3 changes: 3 additions & 0 deletions deploy/compose/env-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ services:
- ./.local/trust-root:/etc/base/config:ro

validator:
# role-master disables on-chain submit on droplet masters; local-e2e needs
# a co-located validator for Match/CRV4 smoke against the local gateway.
profiles: !override []
# Avoid role-master 18080 — often taken by staging SSH tunnels on laptops.
ports: !override
- "127.0.0.1:${LOCAL_VALIDATOR_HOST_PORT:-28080}:8080"
Expand Down
6 changes: 4 additions & 2 deletions deploy/compose/role-master.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# Master role — gateway enabled, published on VPC for validator peers.
# Used with: docker compose -f docker-compose.yml -f deploy/compose/role-master.yml --profile master
#
# On-chain weight submit must NOT run here (sole submitter = validator host).
# Co-located validator is local-e2e only — env-local.yml clears the profile.
services:
gateway:
profiles: ["master"]
ports:
- "8080:8080"
validator:
ports:
- "127.0.0.1:18080:8080"
profiles: ["never"]
9 changes: 7 additions & 2 deletions deploy/scripts/assert-compose-matrix.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ for banned in design-challenge design-egress-proxy prism-challenge socket-proxy;
done
echo "OK: validator role does not render gateway or challenge services"

# --- master role: gateway + challenge services present ---
# --- master role: gateway + challenge services present; no on-chain validator ---
services=$(render \
-f docker-compose.yml \
-f deploy/compose/role-master.yml \
Expand All @@ -72,7 +72,10 @@ for required in design-challenge design-egress-proxy prism-challenge socket-prox
fail "master role does not render $required (must)"
fi
done
echo "OK: master role renders gateway and challenge services"
if echo "$services" | grep -qx "validator"; then
fail "master role renders validator (dual submitter; must not — use validator host)"
fi
echo "OK: master role renders gateway and challenge services (no validator)"

# --- evil-gateway not in default or master ---
services=$(render \
Expand Down Expand Up @@ -206,6 +209,8 @@ local_services=$(render \
config --services)
echo "$local_services" | grep -qx "gateway" \
|| fail "env-local master stack does not render gateway"
echo "$local_services" | grep -qx "validator" \
|| fail "env-local master stack does not render co-located validator"
echo "$local_services" | grep -qx "prism-challenge" \
|| fail "env-local master stack does not render prism-challenge"
echo "$local_services" | grep -qx "design-challenge" \
Expand Down
26 changes: 19 additions & 7 deletions deploy/scripts/remote-deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -416,23 +416,35 @@ fi
docker compose ${COMPOSE_FILES[*]} ${PROFILE_ARGS[*]} \$UP_PROFILE "\${UP_ARGS[@]}"
# Profile-disabled services are not started, but an older compose project may
# still be running them. On validator, force-remove master-only challenge
# surfaces so smoke health does not see stale unhealthy containers.
# surfaces so smoke health does not see stale unhealthy containers. On master,
# force-remove the validator so a prior dual-submitter cannot fight the
# validator-host wallet for WeightsSetRateLimit / CRV4 commits.
if [[ '$ROLE' == 'validator' ]]; then
docker compose ${COMPOSE_FILES[*]} rm -sf \
prism-challenge design-challenge design-egress-proxy socket-proxy \
>/dev/null 2>&1 || true
Comment on lines 422 to 425

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)deploy/scripts/remote-deploy\.sh$|(^|/)deploy/.*/role-.*\.ya?ml$|(^|/)deploy/[^/]+\.ya?ml$|(^|/)docker-compose.*ya?ml$' || true

echo "== remote-deploy relevant =="
if [ -f deploy/scripts/remote-deploy.sh ]; then
  wc -l deploy/scripts/remote-deploy.sh
  sed -n '350,460p' deploy/scripts/remote-deploy.sh | cat -n
fi

echo "== compose service/profile snippets =="
rg -n "gateway|updater|validator|role-validator|role-master|profile:" deploy --glob '*.{yml,yaml,sh,tf,hcl}' | head -n 200

echo "== behavioral probe: docker compose rm semantics from compose docs? no runtime =="
echo "Inspect compose files for profiles explicitly to validate transition hypothesis:"
python3 - <<'PY'
from pathlib import Path
for p in sorted(Path('deploy').rglob('*')):
    if p.is_file() and p.suffix in {'.yml','.yaml'}:
        text=p.read_text(errors='ignore')
        if 'gateway' in text or 'updater' in text or 'profile:' in text:
            print(f'--- {p} ---')
            for i,line in enumerate(text.splitlines(),1):
                if any(s in line for s in ['gateway','updater','profile:','profiles:', 'profiles']):
                    print(f'{i}: {line}')
PY

Repository: BaseIntelligence/base

Length of output: 30485


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== role-validator.yml complete =="
cat -n deploy/compose/role-validator.yml

echo "== role-master.yml complete =="
cat -n deploy/compose/role-master.yml

echo "== base docker service profile definitions for gateway/updater =="
python3 - <<'PY'
from pathlib import Path
import re
for p in [Path('docker-compose.yml'), Path('docker-compose.e2e.yml')]:
    if p.exists():
        text=p.read_text(errors='ignore')
        lines=text.splitlines()
        in_svc=False
        svc=None
        for i,line in enumerate(lines,1):
            if re.match(r'^[A-Za-z0-9_\-=/.]+\s*:$', line):
                in_svc=True; svc=line.split(':')[0]
                if svc in {'gateway','updater'}:
                    print(f'{p}:{i}: {line.strip()}')
                    while i < len(lines) and (not re.match(r'^[A-Za-z0-9_][A-Za-z0-9_./-]*:', lines[i]) or lines[i].startswith(' ') or lines[i].startswith('\t')):
                        print(f'{i+1}: {lines[i]}')
                        i+=1
                    break
PY

echo "== master profile arguments context =="
sed -n '60,85p;230,255p;396,440p' deploy/scripts/remote-deploy.sh | cat -n

echo "== read-only compose YAML service/profile model =="
python3 - <<'PY'
from pathlib import Path
import re
def parse_overrides(fname):
    hits=[]
    t=Path(fname).read_text(errors='ignore').splitlines()
    for i,line in enumerate(t,1):
        m=re.match(r'^\s+(gateway|updater|validator|prism-challenge|design-challenge|design-egress-proxy|socket-proxy):\s*(.*)?$', line)
        if m:
            svc=m[1]; tail=m[2]
            profiles=[]
            while i < len(t):
                nxt=t[i]
                if re.match(r'^\s{4}profiles:\s*\[([^\]]*)\]', nxt):
                    profiles=profiles + [x.strip().strip('"\'') for x in re.findall(r'[^,\]\s"]+', nxt)]
                elif nxt.strip().startswith('profiles:'):
                    profiles=[]
                elif re.match(r'^[A-Za-z0-9_][A-Za-z0-9_./-]*:', nxt):
                    break
                i+=1
            hits.append((svc, tail or '', profiles))
    return hits
for f in ['deploy/compose/role-master.yml','deploy/compose/role-validator.yml']:
    print(f'--- {f} ---')
    for h in parse_overrides(f):
        print(h)
PY

Repository: BaseIntelligence/base

Length of output: 8422


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== docker compose files =="
git ls-files | rg '(^docker-compose\.ya?ml$|(^|/)compose/.*\.ya?ml$)'

echo "== gateway/updater definitions in base compose =="
for f in docker-compose.yml docker-compose.e2e.yml; do
  [ -f "$f" ] || continue
  echo "--- $f ---"
  rg -n "gateway|updater|profiles:" "$f" -A 8 -B 2 || true
done

echo "== all gateway/updater occurrences =="
rg -n "^\s*gateway:\s*$|^\s*updater:\s*$|profiles:" deploy docker-compose.yml docker-compose.e2e.yml -A 5 -B 2 || true

Repository: BaseIntelligence/base

Length of output: 15895


Remove stale gateways during validator redeploy.

docker compose ... without --profile master leaves an existing gateway container running; deselecting the master profile does not remove it. The validator cleanup now only removes challenges, so a master-to-validator role switch can leave gateway serving on the validator host. Remove gateway from the validator cleanup path and fail if it remains afterward.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/scripts/remote-deploy.sh` around lines 422 - 425, Update the validator
cleanup block in the role check to include gateway alongside the existing
challenge and proxy services passed to docker compose rm. Ensure the cleanup
does not suppress failure when gateway remains, so the redeploy fails if the
stale gateway container cannot be removed.

Source: Coding guidelines

elif [[ '$ROLE' == 'master' ]]; then
docker compose ${COMPOSE_FILES[*]} ${PROFILE_ARGS[*]} rm -sf validator \
>/dev/null 2>&1 || true
Comment on lines +427 to +428

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -u

echo "== locate file =="
git ls-files | rg '(^|/)remote-deploy\.sh$' || true

echo "== file stats =="
wc -l deploy/scripts/remote-deploy.sh

echo "== relevant lines 380-460 =="
sed -n '380,460p' deploy/scripts/remote-deploy.sh | cat -n -v | sed 's/^/L/'

echo "== search validator/profile/compose refs =="
rg -n "validator|base-validator-1|base-prod-validator|profile|PROFILE_ARGS|COMPOSE_FILES|docker compose ps|profile:|profiles:" deploy -S

Repository: BaseIntelligence/base

Length of output: 50377


🏁 Script executed:

#!/bin/bash
set -u

echo "== compose files and profiles =="
fd -e yml -e yaml . deploy | sort | xargs -r rg -n "name:|services:|profiles:|profile:" -S || true

echo "== validator occurrences in repo =="
rg -n "\bvalidator\b|base-validator-1|base-prod-validator|ROLE|role-master" deploy -S

Repository: BaseIntelligence/base

Length of output: 50377


🏁 Script executed:

#!/bin/bash
set -u

echo "== remote-deploy master profile and validator check =="
sed -n '236,260p;416,448p' deploy/scripts/remote-deploy.sh | cat -n

echo "== master role compose =="
cat -n deploy/compose/role-master.yml

echo "== env compose validator sections =="
for f in deploy/compose/env-prod.yml deploy/compose/env-staging.yml deploy/compose/env-local.yml; do
  echo "--- $f ---"
  cat -n "$f" | sed -n '1,70p'
done

echo "== script error handling/exit behavior around absent validator message =="
rg -n "set -[ef]|trap|main|function main|\\}# exit|exit 0|echo \"remote-deploy: done|echo \"master: validator absent" deploy/scripts/remote-deploy.sh

Repository: BaseIntelligence/base

Length of output: 16582


🌐 Web query:

Docker Compose profiles ps --status running includes containers of inactive services

💡 Result:

The docker compose ps command is not currently profile-aware, meaning it does not automatically filter its output to match the profiles specified in the command [1][2]. Even if you execute docker compose --profile ps, the command will typically list all containers defined in your project, regardless of whether those containers belong to an active profile or an inactive one [1][2]. This behavior is a known limitation of the docker compose ps command, which was designed to maintain backward compatibility by listing all containers associated with the project [2]. While you can use the --status flag to filter by the state of the containers (e.g., --status running), this does not implicitly filter out containers based on their configured profiles [3][4]. If you need to limit the output to specific containers, you may need to rely on alternative methods such as using standard CLI tools (e.g., piping to grep) or specifically targeting the service names directly in the command (e.g., docker compose ps <service_name>), as the project continues to prioritize compatibility over adding profile-aware filtering to the ps command [2][5].

Citations:


Fail closed on validator removal and inspection.

rm -sf validator >/dev/null 2>&1 || true can hide a failed cleanup, and a later Compose/Docker failure can leave a stale dual-submit base-validator-1 container. Make the master remove the inactive validator container and exit loudly if removal or the subsequent absence check fails.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deploy/scripts/remote-deploy.sh` around lines 427 - 428, Update the validator
cleanup flow around the docker compose rm command to fail closed: remove the
unconditional “|| true” and suppressed output, ensure removal errors are
reported and terminate the master, then perform an explicit absence check for
the inactive validator container and likewise exit loudly if it still exists.
Preserve the existing validator removal target and use the surrounding
deployment error-handling conventions.

Source: Coding guidelines

fi
docker compose ${COMPOSE_FILES[*]} ${PROFILE_ARGS[*]} \$UP_PROFILE ps
# Local health probes via published tunnels if present, else container exec.
sleep 5
if curl -fsS -m 5 http://127.0.0.1:18080/healthz >/dev/null 2>&1; then
echo "validator tunnel health: \$(curl -fsS -m 5 http://127.0.0.1:18080/healthz)"
elif docker compose ${COMPOSE_FILES[*]} ${PROFILE_ARGS[*]} exec -T validator curl -fsS -m 5 http://127.0.0.1:8080/healthz >/dev/null 2>&1; then
echo "validator health: ok (in-container)"
else
echo "validator health: probe deferred (container may still be starting)"
if [[ '$ROLE' == 'validator' ]]; then
if curl -fsS -m 5 http://127.0.0.1:18080/healthz >/dev/null 2>&1; then
echo "validator tunnel health: \$(curl -fsS -m 5 http://127.0.0.1:18080/healthz)"
elif docker compose ${COMPOSE_FILES[*]} ${PROFILE_ARGS[*]} exec -T validator curl -fsS -m 5 http://127.0.0.1:8080/healthz >/dev/null 2>&1; then
echo "validator health: ok (in-container)"
else
echo "validator health: probe deferred (container may still be starting)"
fi
fi
if [[ '$ROLE' == 'master' ]]; then
if docker compose ${COMPOSE_FILES[*]} ${PROFILE_ARGS[*]} ps --status running --services 2>/dev/null | grep -qx validator; then
echo "remote-deploy: ERROR: validator still running on master (dual submitter)" >&2
exit 1
fi
echo "master: validator absent (sole on-chain submitter is validator host)"
if docker compose ${COMPOSE_FILES[*]} ${PROFILE_ARGS[*]} exec -T gateway curl -fsS -m 5 http://127.0.0.1:8080/healthz >/dev/null 2>&1; then
echo "gateway health: ok"
else
Expand Down
4 changes: 3 additions & 1 deletion docs/DESIGN_CHALLENGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,9 @@ Expected set `E` = all metagraph hotkeys for the pinned epoch (policy

- Exactly one signed leaf per `h ∈ E`
- **Refuses subset and superset** — Silence is a bug
- Emit at round close and at each epoch boundary via `POST /v1/weights/raw`
- Emit at round close (scored) and near each epoch boundary via `POST /v1/weights/raw`
(`Orchestrator::run_emitter` fills `NotAttempted` when no admin award fired, so
D24 seals keep advancing under 50/50 emission shares)

Absence codes used on this path include `NotAttempted`, `Timeout`,
`InvalidResponse`, `MinerError`, `RateLimited`, `ChallengeInternal` (bundle enum).
Expand Down
Loading