feat(ctx): rebase universal CLI onto bounty+proof (off relearn cmds) - #213
Conversation
Rebase of #207 onto main post #210/#211. Live catalog stays bounty+proof. ctx relearn|image|agent remain for local stacks and print an off warning. xtask now pins DEFAULT_GATEWAY from the binary and refuses miner-doc placeholders and operator env names. Co-authored-by: Mathis <echobt@users.noreply.github.com>
|
@greptileai review |
Greptile SummaryThis change restores local Relearn CLI flows and adds related documentation coverage. Three command behaviors need correction before merging: Image hash-only submissions and JSON prompt fetches can report success for failed operations, while empty file manifests can create rejected submissions after a successful-looking command. Confidence Score: 3/5Not safe to merge until the Image submission and JSON prompt error behaviors are corrected; the empty file-manifest behavior is a separate non-blocking reliability concern. Two reproduced command paths return successful exit statuses despite an immediately rejected submission or an HTTP failure. A third reproduced path permits an empty file manifest to create a rejected record. Files Needing Attention: bins/ctx/src/off.rs
What T-Rex did
|
| if let Some(path) = &input.manifest_file { | ||
| let raw = std::fs::read_to_string(path) | ||
| .map_err(|e| format!("read manifest {}: {e}", path.display()))?; | ||
| return serde_json::from_str::<Value>(&raw) | ||
| .map_err(|e| format!("manifest {} is not valid JSON: {e}", path.display())); |
There was a problem hiding this comment.
--manifest-file accepts syntactically valid {} without applying the selected challenge’s evidence requirements. The command then posts an empty manifest and exits successfully when the service returns 201 Created, even if that created submission is already rejected for contamination_evidence_missing. This is a non-blocking CLI concern, but it leaves users with a persisted rejected submission after reporting success; validate file-supplied manifests before posting them.
Artifacts
- This authored Python script starts a local-only gateway stub and executes the compiled ctx CLI in the absent-manifest and empty-manifest cases, showing the behavioral difference.
- The executed before command exits 1, reports contamination_evidence_missing, and makes zero gateway requests, showing that ordinary empty evidence is stopped locally.
- The executed after command exits 0 after posting manifest {} to the loopback selected-challenge endpoint; the captured HTTP 201 Created response says rejected with contamination_evidence_missing, confirming the claim.
| let declared = input.train_ids.len() + input.train_hashes.len() + input.train_datasets.len(); | ||
| if declared == 0 { | ||
| return Err(missing_evidence_help(challenge_id)); | ||
| } | ||
| match challenge_id { | ||
| "relearn" => Ok(json!({ | ||
| "train_item_ids": input.train_ids, | ||
| "train_image_hashes": input.train_hashes, | ||
| "train_dataset_ids": input.train_datasets, | ||
| })), | ||
| "relearn-agent" => Ok(json!({ | ||
| "train_episode_ids": input.train_ids, | ||
| "train_observation_hashes": input.train_hashes, | ||
| "train_environment_ids": input.train_datasets, | ||
| })), | ||
| "relearn-image" => Ok(json!({ | ||
| "base": input.base.clone().unwrap_or_else(|| IMAGE_BASE.to_owned()), | ||
| "base_license": input | ||
| .base_license | ||
| .clone() | ||
| .unwrap_or_else(|| IMAGE_BASE_LICENSE.to_owned()), | ||
| "train_prompt_ids": input.train_ids, | ||
| "train_dataset_ids": input.train_datasets, | ||
| "claimed_output_hashes": parse_claimed(&input.claimed_outputs)?, |
There was a problem hiding this comment.
ctx image submit --train-hash counts the hash as evidence, but the Image manifest does not serialize hashes and supports only prompt and dataset IDs. A hash-only submission therefore sends empty applicable evidence and exits successfully on a 201 Created response even though the returned submission is rejected for contamination_evidence_missing. Resolve this before merging: users receive a successful command result for a submission that the service has already rejected. Reject unsupported Image hashes or exclude them from Image’s evidence count.
Artifacts
- This executable starts a local-only gateway stub, runs the compiled CLI with only `--train-hash`, captures its request, and asserts the faulty success path; it demonstrates that the hash is omitted.
- This captured command output shows the compiled CLI exited 0 after HTTP 201 Created while sending an empty applicable-evidence manifest and receiving a rejected contamination result; the claimed defect is reproduced.
- This executable temporarily adds a non-committed Image hash-only rejection guard, runs the same command, and restores the source; it demonstrates the fail-closed behavior required to prevent the bad request.
- This captured command output shows CLI exit 1 with a clear unsupported-hash error and no endpoint request; local validation prevents the rejected record.
| if json { | ||
| println!("{}", reply.body); | ||
| return Ok(()); | ||
| } | ||
| if !reply.ok() { | ||
| return Err(format!("HTTP {}: {}", reply.status, reply.message())); | ||
| } |
There was a problem hiding this comment.
ctx image prompts --json prints the response body and returns before checking the HTTP status. For both 404 and 500 responses, it emits the error JSON but exits with status 0, unlike normal output mode. Resolve this before merging: scripts can treat a failed prompt fetch as valid output; check the response status before selecting JSON formatting.
Artifacts
- A Python executable that builds ctx and sends its prompts command to a local 127.0.0.1 error gateway for JSON and normal modes, showing the compared behavior.
- Captured execution output from the loopback gateway validation showing JSON mode exits 0 for both errors while normal mode exits 1, confirming the finding.
Summary
Rebase of #207 onto current
main(post #210 Proof+Bounty-only and #211 security hotfix).#210already landed the miner CLI for the two live challenges. This PR keeps that catalog and ports the leftover #207 work without putting Relearn/Prism back on the trust root:bounty(7000 bps) +proof(3000 bps).ctx challenges/ctx statusonly list those two.ctx relearn|image|agentremain for a local stack (--gateway). Every invocation prints that the challenge is off (no trust-root row, no emission). Empty manifests are still refused locally (contamination_evidence_missing).xtask external-docs-checkreadsDEFAULT_GATEWAYfrombins/ctx/src/api.rs, requires miner pages to name that host, walksdocs/external-miner/recursively for<gateway>/<host>placeholders, and refusesBOUNTY_CHAT_COMMAND/BOUNTY_BACKEND_PUBLIC_URL/BASE_GATEWAY_*on miner pages (validators.mdis exempt).README.mdgains a Mine section with the install one-liner,ctx challenges, and the public gateway host.Does not reintroduce Relearn/Prism as live. Does not weaken the #211 HTTP API-key refusal or rust-toolchain pin.
Supersedes #207 (that branch still treats Relearn as live and conflicts with
main).Greptile
Every PR is reviewed by Greptile before merge. Config:
.greptile/.@greptileai reviewTest plan
cargo test --workspace --locked— green locallycargo test -p ctx— 37 passed (off-command parse + live catalog still bounty+proof)cargo fmt --all -- --checkcargo clippy --workspace --all-targets -- -D warningscargo run -p xtask -- loc-cap(bins/ctx1481 / 1500)cargo run -p xtask -- consensus-lint/spec-check/design-check/external-docs-checkfmt · clippy · test · deny · xtask— greenRisk
Docs + miner CLI only. No challenge scoring, emission, model pin, or deploy path is touched. Off commands can still POST to a local Relearn service; they cannot earn weight without a trust-root row.
Naming
I did not rename
BASE_*environment variables, deployed host paths(
/opt/base,/run/base, …), GHCRbaseintelligence/basepackage names, orbase-*-v1cryptographic domain tags, unless this PR’s purpose is a coordinatedcutover documented in
docs/NAMING.md.