Skip to content

fix(validator): make the certification canary runnable and fail closed before a lease - #1921

Open
ai-mountain wants to merge 24 commits into
mainfrom
agent/coding-certification-validator-readiness
Open

ai-mountain wants to merge 24 commits into
mainfrom
agent/coding-certification-validator-readiness

Conversation

@ai-mountain

@ai-mountain ai-mountain commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Why

B2 (approved by Peyton; unblocked 2026-09-15 once the rootless router candidate.router.source probe passed, #1919). As deployed today, the contract-v1 certification canary cannot run on the production validator:

  • the Compose scorer URL was rejected;
  • the pack wasn't in the scorer image;
  • a rootless daemon is required, but only rootful DinD exists;
  • the exchange URL host didn't match;
  • the certify timeout was 30 s.

A post-claim failure would also strand the one-shot lease. This PR is the validator, scorer and infra side. The Platform side (lease expiry, allowlist, attempt cap) is a separate PR.

What

  • Scorer origin: one shared scorer_control_origin rule for both the canary and the local shadow supervisor, which admits exactly the Compose origin http://sandbox-docker:8000. One private no-proxy scorer client is shared.
  • Pack:
    • baked into the scorer image, read-only;
    • LoadPublicPack verifies grader files against the manifest's grader_files and the visible workspace against a pinned listing digest, and rejects extras and links;
    • .dockerignore and the loader skip exactly __pycache__, *.pyc and .DS_Store.
  • Coding host isolation: if the coding host fails to build, it is logged as a refusal and every coding route returns 404. Ordinary scoring keeps working; no more log.Fatalf for the whole scorer.
  • Rootless daemon required:
    • DITTOBENCH_CODING_DOCKER_HOST / validator_stack_coding_docker_host must be a unix:// socket;
    • tcp://, /var/run/docker.sock, /run/docker.sock and the scorer's own DOCKER_HOST are refused;
    • every coding Docker call uses that endpoint.
  • Readiness before any irreversible step: GET /v1/coding/certifier/canary/readiness (same bearer, no side effects) re-verifies the pack, checks rootless and isolated labels, confirms the runtime image digest is present, and returns the pack digests. The validator refuses to issue unless it's explicitly ready, and aborts rather than claims if the lease's digests differ from the scorer's. The wire format is pinned by a shared contract vector.
  • Strict validator targeting:
    • VALIDATOR_CODING_CANARY_AGENT_IDS (1–16 UUIDs) plus VALIDATOR_CODING_CANARY_VALIDATOR_HOTKEY, which must equal the local hotkey; empty refuses all;
    • offers for anything else are dropped; targets are rechecked before issue;
    • a lease for another agent or hotkey is aborted before claim.
  • Runtime image repository: Docker repository grammar, identical in Go and Ansible.
  • Deadline: the certify budget is the time left on the lease, enforced by asyncio.timeout with short connect/write/pool limits. Every deadline error says "deadline exceeded". Docs state 20-minute leases.
  • Exchange URL: platform_coding_validator_api_base_url defaults to https://{{ platform_domain }}, the validator-facing origin. The prod host_vars pin is gone, and the assert requires exactly that origin.
  • Rendering: validator_stack renders the default-off canary with two switches, and the role refuses either switch without the rootless socket and exact targets.

Tests

  • Python: 353 passed across 12 files, plus 12 Platform exchange tests. ruff and mypy are clean.
  • Go: codingexecutor, codingcanary, codinghost, sandbox, codingharness, codinghostedruntime, codinghostedinput, the executor-scorer and the cmd/dittobench-api coding and control-plane tests all pass. -race passes on codingcanary, codinghost and codingexecutor.
  • Ansible 2.21.2: the check playbook gives ok=152, rescued=44, failed=0, with each of the 44 reject cases failing at its intended assertion. Syntax checks pass for gcp-validator-prod and gcp-platform-app.
  • Pack image stage: built with junk files planted; the junk was excluded and the loader accepted the pinned manifest digest.
  • Mutations: all caught (rootful or tcp endpoint allowed, readiness ignored or hard-wired, targets or hotkey dropped, pack binding removed, pre- and post-issue checks removed).

Default-off / not authorized

Both switches default off, and targets default empty (refuse all). No certification run, no production activation, no host change.

Still required before any certification attempt

  • Dedicated isolated rootless coding daemon (separate PR):
    • separate from sandbox-docker, labelled io.heyditto.dittobench.isolated=true, with rootless in SecurityOptions;
    • its socket mounted into dittobench-api via DITTOBENCH_CODING_DOCKER_HOST, with egress network and proxy env set;
    • host gateway and port 11438 reachable;
    • runtime image preloaded by digest, and stale-resource cleanup;
    • readiness returns ready: true.
  • Platform lease and allowlist PR: merged and verified. A receipt-margin rule from that PR may need a small follow-up here, e.g. the certify budget becomes the lease deadline minus a submission margin.

Questions for Peyton

  1. Is non-fatal coding host failure also OK for the shadow gate?
  2. The role requires 1–16 targets once the validator switch is on, so it can't be staged in refuse-all mode. Is that OK?
  3. Should the validator also check the artifact identity (Platform's allowlist binds it)? Done; see the 2026-09-16 update.
  4. after_score re-offers an already certified agent. Should the validator skip it?
  5. Is the visible workspace digest acceptable as a Go constant?
  6. Should CodingPublicationClient's separate local-origin check be unified?

Update (2026-09-15): certification-only runtime settings (settings leak fix)

Peyton's decision: give certification its own proxy, gateway and no-CA settings.

The leak. The coding host built its Docker client with sandbox.NewLocalDocker(), which reads the ordinary scorer's environment. On Compose that meant the coding harness would inherit:

  • DITTOBENCH_SANDBOX_EGRESS_NETWORK=ditto-sandbox;
  • the OpenRouter shim CA bundle (DITTOBENCH_OPENROUTER_SHIM_CA_BUNDLE_PATH, mounted and injected as SSL_CERT_FILE/REQUESTS_CA_BUNDLE/CURL_CA_BUNDLE/NODE_EXTRA_CA_CERTS);
  • any DITTOBENCH_SANDBOX_EGRESS_PROXY, host gateway, memory/tmpfs/pids/start overrides, seccomp/AppArmor names, GITHUB_TOKEN_FILE and DITTOBENCH_ALLOW_PRIVATE_HARNESS.

Setting a proxy for the coding host would also have injected HTTPS_PROXY into every ordinary scoring container.

Fix.

  • sandbox.NewCodingRuntimeDocker builds the coding client only from explicit settings:
    • DITTOBENCH_CODING_EGRESS_NETWORK;
    • DITTOBENCH_CODING_EGRESS_PROXY, exactly http://<private IPv4>:<port>;
    • DITTOBENCH_CODING_HOST_GATEWAY_IP, a private IPv4;
    • optional DITTOBENCH_CODING_SECCOMP_PROFILE and DITTOBENCH_CODING_APPARMOR_PROFILE (unconfined refused).
  • It mounts no CA bundle, carries no GitHub token, keeps the private-harness guard on, and fixes the limits (3g memory, 512m tmpfs, 2 CPUs, 512 pids).
  • Any missing value refuses the coding gate before side effects, with no fallback to DITTOBENCH_SANDBOX_*. A coding proxy or gateway equal to the sandbox value is also refused.
  • Docker CLI calls to a dedicated endpoint (sandbox and codingexecutor) drop inherited DOCKER_* selectors (context, TLS, config, API version) and proxy variables.
  • Compose passes the three new variables empty (refuse). validator_stack renders them only behind the scorer canary switch and validates them.

Tests.

  • Go: a whole-struct comparison under a poisoned shared environment; harness docker run args contain no CA mount or TLS variables and only the coding proxy, network and gateway; 25 refusal shapes; CLI environment selectors; the cmd no-fallback, equal-to-shared and before-side-effects tests.
  • Python: the compose defaults assertion, plus the earlier suites.
  • Ansible 2.21.2: validator-stack-coding-canary.yml gives ok=215, rescued=65, failed=0 (21 new reject cases).
  • Also resynced with main (the router ledger seam conflicted in the validator entrypoint, config and control routes).

Superseded by #1932 for the canary. #1932 moves certification to a host-level service reached over a Unix socket. Its readiness v2 requires a placement proof the Compose scorer cannot provide, so the Compose canary route here is never ready there.

Head: 746b8d310.

Review update (2026-09-15): independent adversarial review

An independent security and correctness review of the settings leak fix found no leak. Nothing falls back to DITTOBENCH_SANDBOX_*, the coding LocalDocker mounts no CA bundle, and no harness -e or TLS variable reaches the coding container. The proxy and gateway are validated, and a value equal to the sandbox value is refused.

Test defect (fixed). TestDedicatedDockerEndpointInheritsNoDockerOrProxySelector used the production inheritedDockerSelector as its own oracle. As a result, narrowing the stripped set (for example to DOCKER_HOST only, or dropping HTTPS_PROXY) still passed. The test now uses an independent DOCKER_* / *_PROXY oracle. The executor twin also poisons all_proxy and http_proxy, so each dropped variable is mutation-checked.

Head: 9e3c6a59e. CI is green.

Update (2026-09-16): targets bind the artifact and screened-image digests

The validator's targets now match #1923's four-field Platform allowlist: agent_id, artifact_sha256, screened_image_sha256 and validator_hotkey. Before, they bound only the agent and the hotkey, so the validator would have let through a lease for a rebuilt image or a different artifact.

What changed

  • Setting. VALIDATOR_CODING_CANARY_TARGETS replaces VALIDATOR_CODING_CANARY_AGENT_IDS. It takes 1 to 16 unique <agent_id>:<artifact_sha256>:<screened_image_sha256> entries: a canonical non-nil UUID and two lowercase 64-hex digests, the same form as the lease authority. VALIDATOR_CODING_CANARY_VALIDATOR_HOTKEY is still the fourth field and must equal the local hotkey. Empty still refuses everything. Agent-only entries, sha256: prefixes, uppercase or short digests, duplicates and more than 16 entries are refused at config parse.
  • Worker (CodingCanaryTargets).
    • An offer is queued only if its agent has a target.
    • After issue, binds() requires the lease's agent, hotkey, agent_artifact_sha256 and screened_image_sha256 to be exactly one listed tuple. Otherwise the still-issued lease is aborted before claim.
    • After claim, the claimed authority must keep the issued identity and still bind. It is checked before the harness launch, grant, exchange or certify call.
    • One agent may be listed with two exact image identities, as on Platform. A target for another image never matches.
  • validator_stack. validator_stack_coding_canary_targets is a list of mappings with exactly agent_id, artifact_sha256 and screened_image_sha256. It renders as the setting above. Validation checks list shape, 1 to 16 entries, the exact key set, UUID and digest grammar, a non-nil agent and unique tuples.
  • Compose and docs are renamed to match.

Tests

  • Python: 1574 passed across ditto/tests/validator and the compose, runtime, scorer image, workflow and release-plan guards. ruff and mypy are clean. New cases cover:
    • binds() for each of the four fields, including swapped artifact and image digests;
    • an issued lease with a different artifact, image or both (aborted, never claimed);
    • a claimed lease with a different artifact or image (no launch, grant, exchange, revoke, certify or submit);
    • 18 malformed target values.
  • Ansible 2.21.2: tests/validator-stack-coding-canary.yml gives ok=242, rescued=74, failed=0. All 17 target reject cases fail at their intended assertion (checked with -vv), and the complete profile renders the exact target string.

Head: 6eac25140.

Restack

Restacked on main e1a8fa2 (#1937 v13 integration). Clean merge.

🤖 Generated with Claude Code

Note

Make the certification canary runnable and fail closed before a lease

  • Adds a readiness probe that checks pack integrity, executor daemon isolation, and runtime image eligibility before the canary issues a lease; the worker aborts any lease whose authority or scorer-pack identity does not match the configured exact target set.
  • Routes all coding Docker CLI calls through a dedicated rootless Unix-socket endpoint, separate from the ordinary scorer daemon, with inherited Docker and proxy selectors stripped from the environment.
  • Loads and re-verifies the certification pack against pinned visible-workspace and grader-tree digests, rejecting tampered, extra, linked, or oversized files.
  • Wires operator inputs through Ansible validation, Compose environment defaults, and a read-only certification-pack Dockerfile stage; all canary and coding-runtime settings default off and empty.
  • Risk: codingDockerHostFromEnvironment and sandbox.NewCodingRuntimeDocker reject endpoints shared with the ordinary scorer daemon; a coding-host construction failure now disables coding routes instead of terminating startup, so reviewers should confirm that no existing deployment relies on fatal startup behavior for misconfigured coding settings.

Macroscope summarized 2b7df44.

ai-mountain and others added 14 commits September 15, 2026 02:45
The default-off canary runtime accepted only HTTPS or loopback scorer
origins, so enabling VALIDATOR_CODING_CANARY_ENABLED against the Compose
value http://sandbox-docker:8000 raised during worker construction.
Accept exactly that fixed service origin in addition to HTTPS/loopback;
reject every other plaintext host, port, path, userinfo, query, or
fragment.

The certify call streamed a whole run through the shared 30 s client with
no per-request bound. Derive the request timeout from the lease deadline,
capped at the scorer's 32-minute operation bound, refuse to send once the
deadline has passed, and wrap the stream in asyncio.timeout so a stalled
scorer or task cancellation closes the connection. The call stays
single-shot; the worker still revokes the grant in its shielded finally.

The scorer bearer and per-lease broker private key now travel on a
dedicated no-proxy client owned by the coding exit stack, and the runtime
refuses a proxy-inheriting client.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…r image

A canary-enabled scorer loads a repo-shaped certification/v1 root, but the
sandbox image carried only the locked policy, so enabling
DITTOBENCH_CODING_CANARY_ENABLED made codinghost construction fail and
log.Fatalf took ordinary scoring down with it.

Add a small coding-certification-pack stage that copies the committed
certification/v1 capsule and the locked policy its manifest names, checks
every file against pinned SHA-256 digests (including the manifest digest
Platform binds into leases), rejects extra files and links, and makes the
tree 0555/0444. Only the sandbox target copies it, root-owned, to
/opt/ditto/coding/certification-root.

The policy COPY's --chmod=0444 is also applied by BuildKit to the
implicitly created /opt/ditto/coding, which the uid 65532 scorer cannot
traverse (reproduced locally: the policy and the pack were both
unreadable). Restore 0555 on that directory.

Compose pins DITTOBENCH_CODING_CERTIFICATION_ROOT to the image path. It is
read only when the canary gate, still false by default, is enabled and is
not operator-selectable, because the runtime loader does not re-hash the
workspace or grader files. Pack edits now select the scorer release.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… validator

The production validator stack rendered none of the canary settings, so
the supported contract-v1 certification path could not be enabled there
without hand-editing the host .env.

Add two independent switches, both false: the scorer switch renders
DITTOBENCH_CODING_CANARY_ENABLED plus the runtime image repository and
digest (empty unless on), and the validator switch renders
VALIDATOR_CODING_CANARY_ENABLED and its poll interval. A pure validation
task runs before any host mutation and requires the scorer switch for the
validator worker, excludes the executor connectivity canary, bounds the
poll to [1, 300], and demands a sha256 digest and a well-formed repository
whenever the scorer switch is on. The certification root stays pinned by
Compose to the image-baked pack and is deliberately not rendered.

A localhost check-mode playbook, wired into infra CI, proves the shipped
defaults render disabled and empty, renders staged and complete profiles,
and requires the validation task itself to reject 15 unsafe profiles.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…form origin

Validators accept a coding grant exchange URL only when it equals
{VALIDATOR_PLATFORM_API_URL}/api/v1/validator/..., for both shadow tickets
and certification leases, and Platform derives the certification exchange
URL from DITTO_CODING_INFERENCE_EXCHANGE_URL. Production rendered that
URL from its inference origin https://dittobench.ai while every validator
uses https://platform-api.heyditto.ai/, so an issued certification grant
would fail the validator's exact check after the lease was claimed.

Render the exchange URL from a new platform_coding_validator_api_base_url
that defaults to the inference origin (dev and other hosts render exactly
as before) and pin it to https://platform-api.heyditto.ai for production.
Activation now also asserts the exchange URL equals that origin plus the
fixed path. The validator's single-origin check is unchanged rather than
widened to accept the inference host. platform_coding_shadow_enabled stays
unset in production, so nothing renders until a reviewed activation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Describe the accepted Compose scorer origin, the deadline-bounded
single-shot certify call, the build-verified scorer pack and pinned root,
the two validator_stack switches with their staging order, and the
validator-facing exchange origin.

Record what is still missing before the production validator can
certify: a rootless, isolated coding daemon (checked only after claim) and
targeting. Also record the one-shot lease risk that the separate Platform
lease-expiry PR mitigates and the one-hour certificate window.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…kerfile pins

The scorer build failed on any stray local file under certification/v1
because the pack stage compared the copied tree against a hand-maintained
digest list, and the root .dockerignore entries `__pycache__` and `*.pyc`
match only at the context root. A capsule `tests/__pycache__` from running
the capsule tests, or a `.DS_Store`, broke every scorer rebuild even though
the canary is default-off. The same list was duplicated in the Python and Go
tests, and the runtime loader re-hashed nothing.

LoadPublicPack now verifies what it executes. The grader tree must contain
exactly the manifest's grader_files (path, size, SHA-256), which Platform
binds into every lease through the manifest digest. The visible workspace
must match one pinned listing digest. Links, special files, and any other
file fail closed. executionPlans bundles only the verified bytes, and Verify
re-reads both trees on demand.

One exclusion rule covers interpreter caches and Finder metadata
(`__pycache__/`, `*.pyc`, `.DS_Store`). The loader never verifies or bundles
those names, and the root .dockerignore now excludes them at any depth
(`**/`). The pack stage only copies the tree and makes it read-only, so a bad
pack can no longer fail the scorer build. The Go and Python tests share that
rule: junk-laden roots load with identical plans, and tampered, extra,
missing, or linked files are rejected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… grammar

validImageRepository accepted references Docker rejects, such as an empty
path component (`registry.invalid//runtime`) or a colon inside the path
(`registry.invalid/a:b/c`). The scorer then started normally, and the bad
reference failed only when the executor built `repo@sha256:...` during
certify, after the lease was already claimed.

Match the Docker reference grammar for a repository name instead, restricted
to lowercase: an optional registry host[:port] component, then path
components of alphanumerics joined by `.`, `_`, `__`, or dashes, separated by
single slashes, at most 255 bytes, with no tag or digest. The validator_stack
role applies the same grammar before rendering.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…diness

Three scorer-side gaps let a default-off canary strand a claimed lease or stop
ordinary scoring.

Dedicated coding daemon. The coding host shared the scorer's LocalDocker, so
its harnesses and executors used the stack's DOCKER_HOST: on Compose, the
privileged rootful sandbox-docker daemon. An enabled coding gate now builds
its own runtime whose every Docker CLI call, in both the sandbox runtime and
the executor, targets DITTOBENCH_CODING_DOCKER_HOST. That endpoint is
unset by default. It must be a local Unix socket, must not be a
conventional rootful socket or the ordinary scoring daemon, and must still
prove rootless mode and the isolated-daemon label live. A source check keeps
every sandbox Docker call on that one helper.

Readiness before claim. The validator's only pre-claim check was that the
canary route existed, so it issued and claimed a lease that certify's
executor preflight then refused. GET /v1/coding/certifier/canary/readiness,
with the canary's bearer, runs no harness, container, grant, or lease. It
re-verifies the loaded pack, reruns the harness daemon check and the
executor's rootless and isolated-label checks, and confirms that the exact
runtime image digest is present with the supervisor contract. It reports
ready only when all of these pass, together with the pack identity, so the
validator can refuse before issue and compare the issued lease before claim.
Probes are serialized and bounded to 20 seconds.

Non-fatal coding host. A DITTOBENCH_CODING_CANARY_ENABLED or
DITTOBENCH_CODING_SHADOW_ENABLED construction failure was log.Fatalf for the
whole scorer, which stopped every benchmark ticket. The failure is now logged
as a refusal. Every coding route, including readiness, answers 404, and
ordinary scoring keeps running. The endpoint check runs first, before the
private root or source listener is touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…lient

The certification canary accepted the Compose scorer origin
http://sandbox-docker:8000 through a canary-only special case. The local-mode
CodingSupervisorRuntime ran a near-duplicate check that still rejected the
same VALIDATOR_DITTOBENCH_API_URL, so enabling the local shadow worker on that
stack failed construction, and the two checks would keep drifting.

coding_executor_transport.scorer_control_origin is now the single rule for
both runtimes: any HTTPS origin, a loopback HTTP origin, or exactly the
Compose service origin, with no path, userinfo, query, fragment, or malformed
port. Remote executor mode still requires the private HTTPS :9443 origin.

With both coding gates on, the canary and the local shadow worker also opened
two identical no-proxy clients, each with its own connection pool, to the same
scorer. They now share one lazily created private client that ignores proxy
settings and closes with the coding exit stack. A disabled canary still
constructs no client.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…anary lease

The canary worker issued and claimed a one-shot certification lease for every
agent the validator scored. Its only pre-claim check was that the scorer's
canary route existed, so a rootful daemon, a missing runtime image, or a
mismatched pack failed certify only after the claim.

Readiness. CodingCanaryRuntime.require_ready reads the scorer's read-only
readiness probe with the same bearer. It refuses (and the worker keeps the
offer) unless the answer is 200, private, well-formed, and explicitly ready,
with the pack loaded, the dedicated daemon rootless and isolated, and the
runtime image present. The worker probes only when an offer is queued, before
issue. It then compares the issued lease's five pack digests with the ready
scorer's and aborts the still-issued lease on any mismatch instead of
claiming it.

Targets. VALIDATOR_CODING_CANARY_AGENT_IDS (at most 16 canonical, non-nil,
unique UUIDs) and VALIDATOR_CODING_CANARY_VALIDATOR_HOTKEY define the only
leases the worker may take. Both default to empty, which refuses everything.
The listed hotkey must equal this validator's own hotkey, so copied targets
cannot make another validator run the canary. Offers outside the targets are
dropped. Targets are rechecked before issue, and an issued lease for another
agent or hotkey is aborted before claim. An enabled worker with no usable
targets logs that it refuses every lease.

Deadline. The certify call is bounded by the time left on the lease and
nothing else. The misleading 32-minute cap is gone: the scorer runs a
20-minute default, and Platform issues 20-minute leases. asyncio.timeout alone
enforces the bound, with only short connect, write, and pool limits and no
read timeout that could fire first with the wrong error. Every deadline error
says "deadline exceeded". The dead math.isfinite check is removed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… and targets

The validator_stack role accepted a fully enabled canary on today's stack,
where Compose's only Docker daemon is the privileged rootful sandbox-docker.
Nothing in the role encoded the rootless-daemon prerequisite or any
targeting, and its repository pattern accepted references Docker rejects.

Dedicated daemon. validator_stack_coding_docker_host (empty by default)
renders DITTOBENCH_CODING_DOCKER_HOST, which Compose passes to the scorer
only while the scorer switch is on. Either canary switch now requires a local
`unix:///...sock` endpoint with no empty or dot segments. The conventional
rootful sockets and any tcp:// endpoint, including the sandbox daemon
tcp://127.0.0.1:2375, are refused. The scorer additionally refuses its own
DOCKER_HOST and proves rootless isolation live. This role installs no such
daemon, so both switches stay unusable until the separate rootless coding
daemon change lands.

Targets. validator_stack_coding_canary_agent_ids (1-16 canonical, non-nil,
unique UUIDs) and validator_stack_coding_canary_validator_hotkey, which must
equal validator_stack_hotkey, render the validator's exact targets only while
the validator switch is on. Both default to empty, which refuses every lease.
The hotkey is never defaulted; it comes from the role's validated hotkey.

Repository. The runtime image repository check is the Docker reference
grammar shared with the scorer: at most 255 bytes, anchored with \Z, and
refusing empty path components, colons inside the path, and tags.

The defaults now describe the scorer switch's real scope: it builds the whole
coding host, and a failure disables only the coding routes. The localhost
check-mode playbook renders the new values and rejects 44 unsafe profiles,
each at its intended assertion.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… origin

platform_coding_validator_api_base_url defaulted to the inference origin, so
production needed a host_vars pin. The new equality assert compared the
exchange URL with that same variable, which made it trivially true: on any
host whose inference origin differs from platform_domain, the default
rendered the exchange on the inference origin, the playbook passed, and every
validator rejected every shadow and certification grant.

Default the base to https://{{ platform_domain }}, the primary vhost
validators use as VALIDATOR_PLATFORM_API_URL. That is correct for dev (where
it matches the inference origin) and for production, so the production pin is
removed. Shadow transport activation now asserts that both the base and the
exchange URL are exactly that origin, which refuses an override that points
at the inference origin.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… prerequisite

Replace claims that no longer hold: the 32-minute certify cap, Dockerfile pack
pins, the canary route as the pre-enable check, production's exchange-origin
pin, and "no targeting". Document the shared scorer-origin rule and private
client, the dedicated rootless coding daemon endpoint and its refusals, exact
targets, the readiness probe before issue and claim, the lease-deadline-only
certify bound, loader-enforced pack integrity with its one exclusion rule, the
scorer switch's real scope and non-fatal failure, and the exchange origin
default.

List exactly what the separate rootless coding daemon change must provide, and
state the remaining one-shot risk until the companion Platform lease-expiry
change is merged and verified.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ct vector

The Go readiness handler and the validator's require_ready agreed only through
hand-written payloads on each side, so a renamed field would stay green in
both suites and refuse every lease at runtime.

coding_certification_canary_readiness_v1.json freezes the ready answer for
the committed certification/v1 pack and its not-ready failure codes. The Go
handler must encode it exactly and produce every listed failure code. The
validator runtime must accept it, return its pack identity, and refuse each
failure code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

No dashboard preview for this commit. This PR resolved to preview profiles stack with no dashboard profile: it changes no apps/platform/dashboard/ path, so there is no dashboard build to publish. Profile selection fails closed rather than guessing, which is why a PR touching no recognized runtime path still reads as stack.

Stack previews are never automatic. A maintainer can provision one for this PR:

gh workflow run preview-stack.yml -f pr=1921                    # provision
gh workflow run preview-stack.yml -f pr=1921 -f action=retire   # free the slot

It runs on the default branch, resolves the head commit from the API, and comments dashboard, Platform, and isolated Backroom URLs once readiness succeeds. profile defaults to auto, which picks stack-copy when the PR touches apps/platform/alembic/. Eight slots cap every active preview; a re-dispatch reuses the slot this PR already holds. Reconciliation keeps it while the PR is open, up to a 24-hour lease cap, and retires it four hours after the PR closes.

Exact SHA: 2b7df4422b10c71036f99a292c0fee75f027ddef

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ai-mountain and others added 5 commits September 15, 2026 20:05
…o CA bundle

The coding host built its Docker client with sandbox.NewLocalDocker, so it
inherited the ordinary scorer's DITTOBENCH_SANDBOX_* egress network, proxy,
host gateway, limits and confinement, the OpenRouter shim CA bundle,
GITHUB_TOKEN_FILE and the private-harness switch. It now uses
DITTOBENCH_CODING_EGRESS_NETWORK, DITTOBENCH_CODING_EGRESS_PROXY and
DITTOBENCH_CODING_HOST_GATEWAY_IP only, refuses a proxy or gateway equal to
the sandbox value, mounts no CA bundle, and fixes its limits. Docker CLI calls
to a dedicated daemon drop inherited DOCKER_* selectors and proxy variables.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… gateway

Compose passes empty DITTOBENCH_CODING_EGRESS_NETWORK, _EGRESS_PROXY and
_HOST_GATEWAY_IP by default, and validator_stack renders them only behind the
scorer canary switch after validating a private IPv4 proxy and gateway.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Resolves the validator entrypoint, config and control-plane route conflicts
with the router ledger read seam from main.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ai-mountain added a commit that referenced this pull request Sep 15, 2026
Brings main (router ledger read seam) in through #1921 and keeps both the
certification socket settings and router_ledger_read_enabled in the
validator config.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…racle

The sandbox test classified the child environment with the production
inheritedDockerSelector itself, so weakening that rule also weakened the
check. Use an independent DOCKER_*/_PROXY oracle, and poison all_proxy and
http_proxy in the executor twin so each dropped variable is exercised.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…cation canary targets

Platform's certification allowlist admits exact (agent, artifact digest,
screened-image digest, validator hotkey) tuples. The validator's targets
bound only the agent and hotkey, so a lease for a rebuilt image or another
artifact passed the validator-side check.

VALIDATOR_CODING_CANARY_TARGETS replaces VALIDATOR_CODING_CANARY_AGENT_IDS
with 1-16 unique <agent_id>:<artifact_sha256>:<screened_image_sha256>
entries. An issued lease whose four fields are not exactly one listed tuple
is aborted before claim, and a claimed lease is checked again before the
harness launch, grant or certify call. validator_stack renders the targets
from mappings with exactly those three fields and validates them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@Peyton-Spencer Peyton-Spencer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Open Code Review pass completed at 6eac25140b67581be9c437e933dde7f529e4e188 using z-ai/glm-5.3-flash through Ditto’s Code Review endpoint. Coverage: 57/57 added, modified, renamed, or type-changed files in the PR diff.

Model candidates were checked against the source; independently confirmed actionable findings, if any, are posted separately as inline review comments. This was a static review; no PR code or test suite was executed. This comment is not a merge approval.

…diness

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ai-mountain added a commit that referenced this pull request Sep 16, 2026
Picks up main e1a8fa2 through the updated #1921 base; keeps both the
rootless router gateway tests and main's v13 egress-proof tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@Peyton-Spencer Peyton-Spencer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Open Code Review pass completed at b9f63a4e4d16cdb1083a2723fd73188c7b512dcd using z-ai/glm-5.3-flash through Ditto’s Code Review endpoint. Coverage: 57/57 added, modified, renamed, or type-changed files in the PR diff.

This refresh re-reviewed the changed PR files and carried forward completed coverage for unchanged files. Model candidates were checked against the source; independently confirmed actionable findings, if any, are posted separately as inline review comments. This was a static review; no PR code or test suite was executed. This comment is not a merge approval.

ai-mountain added a commit that referenced this pull request Sep 16, 2026
…fixture after #1921's validator API origin default

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…adiness

Keep main's custody service and PostgreSQL environment infra CI steps
alongside this PR's certification canary default-off step.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ai-mountain added a commit that referenced this pull request Sep 16, 2026
…s @ 2b7df44

# Conflicts:
#	.github/workflows/infra-ci.yml
#	docker-compose.yml
#	infra/ansible/roles/validator_stack/defaults/main.yml
#	infra/ansible/roles/validator_stack/tasks/main.yml
#	infra/ansible/roles/validator_stack/templates/validator.env.j2
ai-mountain added a commit that referenced this pull request Sep 16, 2026
's launch_journal_dir in #1909's attempt config

- #1929 unit-uninstall guard: list the read-only live-unit tasks of #1920,
  #1897, #1899 and #1925
- #1923 canary targets in #1921's receipt-window worker test
- #1923 strict allowlist admit in #1911's v1 certification test
- #1919 router host fields and #1941 launch_journal_dir in #1909's attempt
  config (HostLayout.launch_journal, verify_host private directory)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ai-mountain added a commit that referenced this pull request Sep 16, 2026
…fixture after #1921's validator API origin default

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ai-mountain
ai-mountain marked this pull request as ready for review September 17, 2026 19:47
@ai-mountain

Copy link
Copy Markdown
Contributor Author

Ready for review. Merge-time note: once #1901's signer test playbook is on main, infra/ansible/tests/coding-hosted-control-signer.yml needs platform_domain: platform.invalid, otherwise the playbook fails with 'platform_domain' is undefined. I push it when #1901 lands. Queue: #1940 (comment)

@ditto-code ditto-code Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ditto Review · Beta

Review completed for 2b7df4422b10c71036f99a292c0fee75f027ddef.

No actionable findings in the reviewed files. This is not an approval or proof that the PR is bug-free.

Configure Ditto Review. Inference is billed through your Ditto Router endpoint; targeted verification is included during beta.

This branch was successfully deployed

1 active deployment
preview — 2b7df442 Deployed Sep 16, 2026 by ai-mountain via dashboard-publish / retire #2632
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants