Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
71da131
feat(agent-challenge): add eval artifact import and download routes
echobt Jul 28, 2026
74ae2bb
feat(agent-challenge): enforce guest dual-hash artifact provenance
echobt Jul 28, 2026
1b18bf7
feat(agent-challenge): discover Phala app_id per account at provision
echobt Jul 28, 2026
6489d24
feat(agent-challenge): add miner agent packaging and loop scripts
echobt Jul 28, 2026
61651c2
feat(agent-challenge): add authenticated raw weight push loop
echobt Jul 28, 2026
d82e476
feat(db): add digest allowlist and attestation nonce tables
echobt Jul 28, 2026
185ceba
feat(compute): add Lium attestation and constation custody path
echobt Jul 28, 2026
5a38603
feat(master): expose constation attestation HTTP routes
echobt Jul 28, 2026
6d9c756
feat(prism): gate scoring on constation attestation evidence
echobt Jul 28, 2026
8ec278d
fix(compute): lock Lium prism training to 1-GPU Blackwell offers
echobt Jul 28, 2026
1417e8d
feat(compute): add Lium capacity scheduler and orphan reconciliation
echobt Jul 28, 2026
4302db4
feat(config): add ConstationSettings and LiumTrainingSettings
echobt Jul 28, 2026
f451840
feat(constation): land prod allowlist pin API and custody runtime mod…
echobt Jul 28, 2026
0bda6db
feat(master): stamp constation pins and tick Lium capacity in orchest…
echobt Jul 28, 2026
e00c170
feat(cli): wire constation runtime and Lium capacity scheduler into m…
echobt Jul 28, 2026
9eac481
fix(master): load per-challenge embed.env into isolated child env
echobt Jul 28, 2026
976a4a4
fix(agent-challenge): resolve review assets under site-packages and g…
echobt Jul 28, 2026
be4e62b
feat(prism): add pod_boot helpers and plagiarism LLM adjudicator
echobt Jul 28, 2026
666f4b3
feat(prism): add plagiarism LLM settings and checkpoint intake status
echobt Jul 28, 2026
663c9a8
test(agent-challenge): align lifecycle tests with gateway-free analyzer
echobt Jul 28, 2026
6b807c2
style: ruff-fix imports and line length on landed hotpatches
echobt Jul 28, 2026
6ecf431
feat(selfdeploy): land disk guards, lifecycle disk cost, and HTTP CVM…
echobt Jul 28, 2026
8795622
fix(agent-challenge): resolve dataset-digest path for site-packages i…
echobt Jul 28, 2026
53caf7d
feat(agent-challenge): merge eval-path concurrency, progress wire, an…
echobt Jul 28, 2026
fc730c0
feat(selfdeploy): add disk_size to discovery deploy and CLI teardown …
echobt Jul 28, 2026
0f7e15a
fix(agent-challenge): tighten review runtime submit_verdict tool-call…
echobt Jul 28, 2026
c6a577e
feat(prism): land worker-plane no-op and plagiarism LLM adjudication
echobt Jul 28, 2026
82e4549
test(agent-challenge): cover bucket-D merges and align pin/budget fix…
echobt Jul 28, 2026
8ee59da
fix(selfdeploy): bind instance types to signed plan and format shape …
echobt Jul 28, 2026
b830e51
fix(selfdeploy): allow http challenge URLs only on loopback with opt-in
echobt Jul 28, 2026
7ae256f
test(selfdeploy): cover shape mismatch formatter and loopback HTTP po…
echobt Jul 28, 2026
dea28d0
fix(selfdeploy): match pre-artifact compose hash and scope encrypt al…
echobt Jul 28, 2026
15df3e9
fix(staging): delete only owned Phala CVMs and never account-sweep
echobt Jul 28, 2026
29b4c23
chore(staging): add local AC staging compose stack and secret gitignores
echobt Jul 28, 2026
e39ae07
docs(staging): document owned-only CVM teardown and local staging loop
echobt Jul 28, 2026
023de37
fix(staging): resolve owned vm_uuid tracks to API cvm_* delete ids
echobt Jul 28, 2026
a58e584
fix(selfdeploy): fail loud on unknown Phala CVM list shapes
echobt Jul 28, 2026
acc67b1
docs(agent-challenge): document prod artifact-aware compose upgrade
echobt Jul 28, 2026
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
17 changes: 17 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,23 @@ UV_CACHE_DIR=/var/tmp/uv-cache uv run pytest tests/unit \
-k "sealer or aggregation or weights" -q
```

## Agent Challenge local staging (before live/prod)

Prefer the isolated AC staging loop before production-facing gate changes:

```bash
packages/challenges/agent-challenge/scripts/staging/run_staging.sh
```

Details: [`packages/challenges/agent-challenge/docs/staging.md`](packages/challenges/agent-challenge/docs/staging.md)
(host loopback `127.0.0.1:18082`, project `ac-staging`; not master embed `:18081`).

- **One command** above is the iteration loop. Driving the **prod** validator over SSH is a last resort and must never be the day-to-day loop.
- Any keypair works for local submit/sign: AC verifies signatures only (no metagraph membership check).
- CVMs are **real** Phala TDX machines (billable). Staging tears down **only CVMs this run owns** (`work/owned_cvms.txt` + per-run track). It never account-sweeps foreign/prod CVMs. Always tear down owned CVMs before you leave.

Real Phala TDX CVMs + dual attestation flags; always tear down to `GET /cvms` count 0.

## Runtime topology (production)

Supported install is **Docker Compose master + PostgreSQL only**:
Expand Down
110 changes: 110 additions & 0 deletions alembic/versions/0017_image_digest_allowlist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
"""BASE-produced image digest allowlist + revocation denylists.

Adds durable storage for mechanism 4 (digest allowlist) of prism-lium image
attestation. Lookup rules live in ``base.compute.digest_allowlist``; these
tables only persist registered bindings and denylist entries.

Revision ID: 0017_digest_allowlist
Revises: 0016_watcher_state
Create Date: 2026-07-26 00:00:00.000000
"""

from __future__ import annotations

from collections.abc import Sequence

import sqlalchemy as sa

from alembic import op

revision: str = "0017_digest_allowlist"
down_revision: str | None = "0016_watcher_state"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
"""Apply the migration."""

op.create_table(
"image_digest_allowlist",
sa.Column("id", sa.Uuid(as_uuid=True), nullable=False),
sa.Column("commit_sha", sa.Text(), nullable=False),
sa.Column("tree_sha", sa.Text(), nullable=False),
sa.Column("variant", sa.Text(), nullable=False),
sa.Column("digest", sa.Text(), nullable=False),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
server_default=sa.func.now(),
nullable=False,
),
sa.Column(
"updated_at",
sa.DateTime(timezone=True),
server_default=sa.func.now(),
nullable=False,
),
sa.PrimaryKeyConstraint("id", name=op.f("pk_image_digest_allowlist")),
sa.UniqueConstraint("digest", name="uq_image_digest_allowlist_digest"),
sa.UniqueConstraint(
"commit_sha",
"tree_sha",
"variant",
name="uq_image_digest_allowlist_commit_tree_variant",
),
)
op.create_index(
"ix_image_digest_allowlist_commit_sha",
"image_digest_allowlist",
["commit_sha"],
unique=False,
)
op.create_index(
"ix_image_digest_allowlist_variant",
"image_digest_allowlist",
["variant"],
unique=False,
)

op.create_table(
"denied_image_digests",
sa.Column("digest", sa.Text(), nullable=False),
sa.Column("reason", sa.Text(), nullable=True),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
server_default=sa.func.now(),
nullable=False,
),
sa.PrimaryKeyConstraint("digest", name=op.f("pk_denied_image_digests")),
)

op.create_table(
"denied_image_commits",
sa.Column("commit_sha", sa.Text(), nullable=False),
sa.Column("reason", sa.Text(), nullable=True),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
server_default=sa.func.now(),
nullable=False,
),
sa.PrimaryKeyConstraint("commit_sha", name=op.f("pk_denied_image_commits")),
)


def downgrade() -> None:
"""Revert the migration."""

op.drop_table("denied_image_commits")
op.drop_table("denied_image_digests")
op.drop_index(
"ix_image_digest_allowlist_variant",
table_name="image_digest_allowlist",
)
op.drop_index(
"ix_image_digest_allowlist_commit_sha",
table_name="image_digest_allowlist",
)
op.drop_table("image_digest_allowlist")
75 changes: 75 additions & 0 deletions alembic/versions/0018_attestation_nonces.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""BASE-issued single-use attestation nonces for prism-lium constation.

Adds durable storage for mechanism 1 (nonce-bound attestation). Issue/consume
rules live in ``base.compute.attestation_nonce``; this table only persists
issued nonces and their consume timestamps (BASE clocks only).

Revision ID: 0018_attestation_nonces
Revises: 0017_digest_allowlist
Create Date: 2026-07-26 00:00:00.000000
"""

from __future__ import annotations

from collections.abc import Sequence

import sqlalchemy as sa

from alembic import op

revision: str = "0018_attestation_nonces"
down_revision: str | None = "0017_digest_allowlist"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
"""Apply the migration."""

op.create_table(
"attestation_nonces",
sa.Column("nonce", sa.Text(), nullable=False),
sa.Column("work_unit_id", sa.Text(), nullable=False),
sa.Column("miner_hotkey", sa.Text(), nullable=False),
sa.Column("pod_id", sa.Text(), nullable=False),
sa.Column("issued_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("consumed_at", sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint("nonce", name=op.f("pk_attestation_nonces")),
)
op.create_index(
"ix_attestation_nonces_work_unit_id",
"attestation_nonces",
["work_unit_id"],
unique=False,
)
op.create_index(
"ix_attestation_nonces_miner_hotkey",
"attestation_nonces",
["miner_hotkey"],
unique=False,
)
op.create_index(
"ix_attestation_nonces_expires_at",
"attestation_nonces",
["expires_at"],
unique=False,
)


def downgrade() -> None:
"""Revert the migration."""

op.drop_index(
"ix_attestation_nonces_expires_at",
table_name="attestation_nonces",
)
op.drop_index(
"ix_attestation_nonces_miner_hotkey",
table_name="attestation_nonces",
)
op.drop_index(
"ix_attestation_nonces_work_unit_id",
table_name="attestation_nonces",
)
op.drop_table("attestation_nonces")
86 changes: 86 additions & 0 deletions docker/master-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ AC_PORT="${BASE_MASTER_AC_PORT:-18081}"
PRISM_DATA_DIR="${BASE_MASTER_PRISM_DATA_DIR:-/var/lib/base/challenges/prism}"
AC_DATA_DIR="${BASE_MASTER_AC_DATA_DIR:-/var/lib/base/challenges/agent-challenge}"

# Operator-supplied overrides merged into the isolated child environments.
# Defaults live beside each challenge's data so they survive image rebuilds.
PRISM_ENV_FILE="${BASE_MASTER_PRISM_ENV_FILE:-${PRISM_DATA_DIR}/embed.env}"
AC_ENV_FILE="${BASE_MASTER_AC_ENV_FILE:-${AC_DATA_DIR}/embed.env}"

# Child PIDs for cleanup (proxy is usually the last foreground wait target).
CHILD_PIDS=()

Expand All @@ -56,6 +61,80 @@ embed_truthy() {
esac
}

# Merge operator-supplied KEY=VALUE lines into an isolated child environment.
#
# Embedded challenges start under `env -i` so Prism never sees CHALLENGE_* and
# agent-challenge never sees PRISM_*. That isolation is deliberate, but it also
# dropped every operator setting -- including the Phala attestation switches
# (CHALLENGE_PHALA_ATTESTATION_ENABLED / CHALLENGE_ATTESTED_REVIEW_ENABLED) and
# the eval/review app identities -- because the built-in list was hardcoded with
# no extension point. This is that extension point: allowlisted keys from the
# file are appended AFTER the defaults, so the file wins.
#
# Values are never logged (secrets hygiene); only key names are.
load_challenge_env_file() {
local -n _target_env="$1"
local env_file="$2"
shift 2
local -a allowed_prefixes=("$@")

if [[ -z "${env_file}" ]]; then
return 0
fi
if [[ ! -e "${env_file}" ]]; then
log "no env file at ${env_file}; using built-in defaults only"
return 0
fi
if [[ ! -r "${env_file}" ]]; then
log "ERROR: env file ${env_file} exists but is not readable"
return 1
fi

local line key value prefix allowed
local -a accepted=()
while IFS= read -r line || [[ -n "${line}" ]]; do
# Trim surrounding whitespace.
line="${line#"${line%%[![:space:]]*}"}"
line="${line%"${line##*[![:space:]]}"}"
if [[ -z "${line}" || "${line}" == '#'* ]]; then
continue
fi
line="${line#export }"
if [[ "${line}" != *=* ]]; then
continue
fi
key="${line%%=*}"
value="${line#*=}"
if [[ ! "${key}" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
continue
fi
# Strip one layer of matching quotes around the value.
if [[ "${value}" == \"*\" && "${#value}" -ge 2 ]]; then
value="${value:1:${#value}-2}"
elif [[ "${value}" == \'*\' && "${#value}" -ge 2 ]]; then
value="${value:1:${#value}-2}"
fi
allowed=0
for prefix in "${allowed_prefixes[@]}"; do
if [[ "${key}" == "${prefix}"* ]]; then
allowed=1
break
fi
done
if (( ! allowed )); then
continue
fi
_target_env+=("${key}=${value}")
accepted+=("${key}")
done < "${env_file}"

if (( ${#accepted[@]} )); then
log "loaded ${#accepted[@]} override(s) from ${env_file}: ${accepted[*]}"
else
log "no applicable overrides in ${env_file}"
fi
}

prepare_challenge_dirs() {
mkdir -p \
"${PRISM_DATA_DIR}/tmp" \
Expand Down Expand Up @@ -155,6 +234,13 @@ start_embedded_challenges() {
ac_env+=("PYTHONPATH=${py_path}")
fi

# Operator overrides win over the defaults above. Prefixes stay disjoint per
# challenge so the env -i isolation is preserved.
load_challenge_env_file prism_env "${PRISM_ENV_FILE}" \
PRISM_ PHALA_ DSTACK_ OPENROUTER_API_KEY
load_challenge_env_file ac_env "${AC_ENV_FILE}" \
CHALLENGE_ BASE_CHALLENGE_ PHALA_ DSTACK_ OPENROUTER_API_KEY

log "starting embedded prism on ${PRISM_HOST}:${PRISM_PORT}"
# env -i: isolate prefixes so Prism never sees CHALLENGE_* and AC never sees
# unrelated PRISM_* secrets as accidental Settings keys.
Expand Down
16 changes: 16 additions & 0 deletions packages/challenges/agent-challenge/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,19 @@ secrets/

# Local documentation evidence (never publish)
.docs-evidence/

# Local AC staging secrets / runtime (see scripts/staging/.gitignore)
scripts/staging/data/
scripts/staging/work/
scripts/staging/config/challenge_token
scripts/staging/config/review_evidence_encryption_key
scripts/staging/config/challenge.env
scripts/staging/config/kr/
scripts/staging/config/kr-server-ca.crt


# Staging harvested public certs (runtime-local; see scripts/staging/.gitignore)
scripts/staging/config/dstack-client-trust.crt
scripts/staging/config/*.crt
!scripts/staging/config/*.crt.example
!scripts/staging/config/*.example
Loading
Loading