Skip to content

feat(crosscheck): make the cross-family reviewer lane executable on a named registry - #281

Merged
ruby-dlee merged 8 commits into
mainfrom
feat/crosscheck-cross-family-reviewer
Aug 21, 2026
Merged

feat(crosscheck): make the cross-family reviewer lane executable on a named registry#281
ruby-dlee merged 8 commits into
mainfrom
feat/crosscheck-cross-family-reviewer

Conversation

@ruby-dlee

@ruby-dlee ruby-dlee commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Read this first: R6 is NOT closed by this PR

The acceptance for R6 is two completed cross-family reviews (a codex-authored change and a claude-authored change, each reviewed by a GLM-backed reviewer with bound identity and the codex lane's evidence discipline). Zero exist. None completed for this change either. Correct wiring is not the acceptance, and this body does not claim it is.

What this PR actually does is make the lane executable at all (it was not), close two reproduced high-severity bypasses, and write down honestly what is still missing. docs/azure-requirements.md R6 now opens with Status: NOT DONE.

Why the lane was not executable

R6 was bound to GLM-5.2 on Azure AI Foundry's Fireworks partner lane. That lane cannot work on this subscription, and the reason is Azure Marketplace billing, not the vendor:

  • Every request to FW-GLM-5.2 returned HTTP 500 invalid_model_endpoint_authentication in ~0.2s, at every input size, on both routes, with both auth schemes, and from a fresh account in another region.
  • The credential was fine: a bad key returns 401, and these 500s carried fully computed, decrementing x-ratelimit-* headers, which only happens after the caller is authenticated and metered.
  • The discriminator is the publisher: on the same account and key, DeepSeek-V4-Pro and Kimi-K2.7-Code returned HTTP 200; every FW-* deployment is published by Fireworks AI through Marketplace and failed.

Per Microsoft Learn, "Foundry Models from partners and community", credit-only sponsored subscriptions and subscriptions without an active pay-as-you-go billing method cannot purchase Marketplace SaaS offers. This subscription is "Microsoft Azure Sponsorship", and both Microsoft.SaaS resources and accepted Microsoft.MarketplaceOrdering agreements were verified ZERO even after the owner deployed through the Foundry portal. Partner models require Marketplace, so the linkage to the publisher backend can never be established.

The owner opened a direct Fireworks account, which bypasses Marketplace entirely. Separately verified: the Foundry account aif-fm7c799d-eus01 now has zero deployments, so azure-glm / FW-GLM-5.2 on main are dead references and the pre-existing lane could not have served a review under any circumstances.

Design decision: named registry, not a repoint

I generalized to a named cross-family reviewer lane registry rather than repointing the GLM constants. The deciding fact: the serving model changed three times during this work (Azure GLM, then Kimi + DeepSeek, then direct Fireworks GLM), and every repoint would have meant editing five call sites, two mirrored files and three test suites.

bin/fm-crosscheck.py carries CROSS_FAMILY_LANES. Each entry is a complete endpoint allowlist entry: model id, Pi provider slot, chat-completions api surface, endpoint host, the one accepted base URL, and the exact model-level compat a credential may carry. PI_MODEL_PROVIDERS, allowed_profiles, the credential inspector, the family-provenance marker, the Azure provider-host derivation, the credential archive, and the model-guest launcher are all derived from it. The registered-lane execution test is driven from the registry itself, so a lane added there is covered without touching the test.

The line I deliberately did not cross: the registry is code, not config. Selecting among registered lanes is a config change (the roster names the model). Admitting a new endpoint stays a reviewed code change, because the endpoint allowlist is the security control. If the pin lived in $FM_HOME/config, a file outside code review could introduce an endpoint the policy never named, which is exactly what the inspector exists to prevent.

This is also strictly less duplication than before: fm-crosscheck-azure.py mirrored four GLM constants with four assertions; it now mirrors the registry with one whole-dict equality assertion, so a lane added on one side and not the other fails the suite instead of producing a divergent allowlist.

Registered lane: slot fireworks-glm, model accounts/fireworks/models/glm-5p2, endpoint https://api.fireworks.ai/inference/v1, api openai-completions, no compat pin. The pinned model, not accounts/fireworks/routers/glm-5p2-fast: a router may re-point to a different serving variant, and the reviewer identity this gate records must name an exact model. Seconds of latency are not worth an unattributable reviewer on a multi-minute review.

Two high-severity bypasses, found by a real review and fixed

Both were found by an actual completed crosscheck review of this branch (codex-family fallback lane), and both were reproduced by the reviewer before being fixed here.

cc-5ec330d3c74d — provider-qualified authors bypassed family separation. pi records a model as <provider-slot>/<model>, and the family classifier fell back to model_identity, which keeps only the final segment. The same lane model reached through another author-side slot therefore read as a different family, so a some-slot/accounts/fireworks/models/glm-5p2 author was admitted the fireworks-glm reviewer with no relaxation and no degraded marker. Family membership is now judged on the final segment against every registered lane, which errs toward calling two models the same family. Lane selection for credentials and providers keeps its exact match, because there the safe error runs the other way.

cc-ca5848b19ac3 — the compat pin checked one of several layers. pi builds the effective model from mergeCompat(providerConfig.compat, definition.compat) plus a topmost modelOverrides[<id>] layer that also carries compat and headers (dist/core/provider-composer.js). A credential could still set supportsFinishReason: false behind the lane's empty pin and blunt the truncated-verdict refusal. Naming fields to refuse was the wrong shape: the credential is now an allowlist at every layer — the provider may carry only baseUrl, api, apiKey, models; a model entry only its descriptive fields plus the lane's pinned compat; the document only providers. The Azure archive gate and the model guest mirror it.

An earlier review of the same branch also produced cc-4dcd7873f71a (independence compared exact model ids, so a gpt-5.5 author was admitted a gpt-5.6-sol reviewer with no marker) and cc-769d7eba2ded (operator docs required an openai-codex auth.json for every Pi reviewer, which misprovisions a cross-family home). Both fixed.

Truncation is a failed review, not a verdict

GLM-5.2 is a reasoning model and spends the output budget on reasoning first. Measured live: max_tokens=600 returned finish_reason: length with empty visible content; 4000 completed cleanly. Empty is the dangerous shape, because it looks like a response while carrying no verdict.

Refused on two independent grounds, both pre-existing: pi maps finish_reason: length to stopReason: "length" and the parser refuses any final turn whose stop reason is not stop; and the verdict must parse as JSON against the review schema, which truncated output cannot. Now pinned by an end-to-end test that emits a complete, schema-valid clear verdict whose only defect is the stop reason, and requires the run to record tool-failure with no citations. The lane declares maxTokens 32000.

Verdict fence handling (a deliberate parser relaxation — please push back if you disagree)

GLM-5.2 through pi presents its JSON inside a Markdown fence, which read as malformed verdict artifact and cost the lane its first review attempt. Exactly one fence spanning the whole message is now unwrapped. Prose around the fence, several fences, and an unclosed fence all still refuse, because each is a case where the reviewer said more than one thing and the gate must not choose which part was the verdict. A truncated verdict never closes its fence, so this cannot turn one into an answer. The refusal also now names a bounded, repr-escaped prefix of the offending text — the previous message named no cause at all, which is what made the first lane failure unreadable — and the escaping means reviewer output cannot inject a line into an operator's log.

crosscheck-same-model: set to off

It was on, a sanctioned degraded state that existed only because no cross-family lane could finish a review, so the codex fallback had to be allowed to review codex-authored work.

With a non-OpenAI primary configured, leaving it on would mean a transient provider hiccup silently drops back to same-family review — the exact defect R6 removes — and a degraded relaxation left enabled after its cause is addressed is how a temporary state becomes permanent. With it off, the fallback still serves any author outside the codex family, and a codex-authored PR whose primary is down fails closed rather than being self-reviewed. The single-primary risk is accepted deliberately; flipping it back on is an operator act.

Security properties, all preserved and now per layer

  • Endpoint allowlist — one chat-completions base URL per lane, refused by name otherwise; a Responses-API surface still refused explicitly.
  • Model-level override refusal — pi resolves definition.api ?? providerConfig.api, so a model entry's own baseUrl/api would outrank the provider pin. Still refused, including an override repeating the pinned values, with tests for each field alone and together.
  • Fail-closed inspector — missing file, malformed file, unexpected provider slot, pooled providers, missing model id, absent key material, stray top-level key: all refuse.
  • Model image attestation guard in fm-crosscheck-azure.py untouched and still refusing an image that does not attest the dispatched harness.
  • No credential material anywhere — reviewer identity is the non-secret slot:host/model binding; the credential identifier is a digest of host + model + endpoint, provably key-independent (two configs differing only in apiKey produce byte-identical identifiers). No key appears in this diff, its tests, or this body.
  • New: a registered lane whose PI_MODEL_PROVIDERS entry does not resolve to its own slot refuses instead of launching.

Compartment codex-family lane: it could never have run

Found by a live compartment run and fixed here because this PR already rewrites the file heavily.

Two readers derived the same executing-account identity and disagreed by a literal prefix: account_identity returned codex:<id> / openai-codex:<id>, while create_credential_archive derived the bare <id> separately and then refused on archived_identity != reviewer_account_identity. That comparison was structurally always true, so no codex-family compartment review has ever run — a live run refused at 04:43Z before any billable resource. Only the cross-family branch passed, because both sides there read one shared value, which is exactly why it went unnoticed.

Fixed at the source of the disagreement, not by loosening the comparison: account_identity_from_credential is now the single derivation; account_identity reads the home's auth.json and calls it, and the archive parses the bytes it is about to package and calls the same function. No third place strips prefixes. Pinned by a test that drives the real readers end to end for both codex-family branches, is red on the two-derivation code with the exact live message, and also asserts a genuinely different account still refuses — so the fix did not buy its pass by weakening the check.

Second finding, carried: the refusal left no durable record at all. create_credential_archive is called outside the window whose handlers persist a run, so this class of error escaped to main()'s catch-all and no ledger, report, or data/ directory was written. It now converts to a tool failure — the same treatment the model-image attestation refusal twenty lines above already gets — so the attempt is recorded and the roster rotates to the next account.

Both owning documents were wrong in the same direction and are corrected: each said the compartment lane needed only a config flip. enabled: false was masking an independent in-code blocker. That is the second merely-disabled claim R6 has had to retract without anyone reaching the code, after the stale pi-binary reason.

R6's acceptance is unevidenceable as written, and is amended

Traced from code: eligibility derives from the task meta's model and never from harness; the ledger records no author identity at all (author_account_identity=""); and git carries no harness signal by design. The task meta is the only authorship input and it is a declaration, not a record. "A claude-authored change" is therefore not a checkable property of any artifact this system produces.

The acceptance becomes what the system can evidence, which is also what the 2026-08-19 amendment's own reasoning needs:

The cross-family lane completes a review end to end, and the family screen admits that reviewer against both a codex-model author and a non-codex-model author, with the ledger recording the reviewer model, the review family mode, and whether the crosscheck-same-model relaxation was required.

Recorded with it: declaring a codex author is the safe error (it can only narrow eligibility and forces the relaxation into the record); declaring a claude author is the dangerous one (it widens eligibility silently and prints "relaxation was not required"). And a correction to this program's own artifacts — the two runs in data/crossfamily-r6-281/ asserted harness=claude over a PR whose real author harness is not recorded anywhere, so they must not be counted toward the acceptance.

Second review round: two blockers fixed

Blocker 1 — the identity fix was incomplete, and cost a VM. My previous commit unified the two host derivations; the model guest carried a third that still returned the bare account id, so both codex-family branches refused inside a booted, paid VM rather than during staging. My own docstring warned against a third place; the third place already existed. All three now derive the prefixed identity. The guest ships self-contained onto the VM and cannot import the host readers, so the duplication is inherent and the mitigation is coverage.

Blocker 2 — a truncated verdict was defeated by any preceding complete fence. A truncated fence contributes zero complete blocks, so a draft or example fence earlier in the message left the count at one and the gate certified that block — stopReason: stop, parse succeeds, real answer discarded. Worse than what it replaced: the old shape failed loudly, this passed quietly with a stale verdict. Two rules now, and the second was found by the first test I wrote for the first: any unterminated fence refuses outright, and a single complete block is unwrapped only when it is the only JSON-bearing content, because a complete example fence followed by a truncated bare verdict also counts one block.

HIGH — the model guest had zero behavioral coverage. That is why blocker 1 was invisible: the suite asserted substrings were present in the guest source and never ran it. model_guest_executing_account_unit now extracts the guest's own credential block from the shipped bytes and executes it against real archives and the host readers. It also pins registration completeness — a lane added to the guest table without a case "$MODEL" arm previously passed every test and died at runtime with exit 125.

MEDIUMs fixed: the archive gate's allowlists now reference core's rather than a local copy that had already drifted weaker (no model-level allowlist, no api check, so openai-responses could be archived — forbidden by R6); family matching normalizes separators so gpt5.6-sol no longer reads as its own family, and each lane carries its model's aliases so an author on GLM-5.2 under any vendor id cannot take the GLM reviewer unmarked (lane selection stays exact — only the family screen folds); and the TOCTOU refusal four lines after the previous durability fix now persists as a tool failure.

Stated honestly, not fixed: AzureCrosscheckError is a bare RuntimeError and roughly 20 further refusals in that region still vanish. Making it subclass CrosscheckToolError matches the diagnosis, but it converts every one of those sites into "persist and rotate the roster" and I could not exercise the compartment lane live to confirm that is right at every site. Also unfixed: crosscheck-same-model: off fails closed correctly but files the refusal as tool-failure, which the fleet reads as "environment fault, rotate" rather than "this PR is unreviewed" — a policy refusal mislabeled as an environment fault.

Ambient-seal interaction (#282), checked rather than assumed

Re-ran all three suites through tests/run.sh — the authoritative sealed entry point — with the operator environment deliberately present, so the seal had something real to drop. It fired: FM_AMBIENT_SEAL dropped= 26 names including FM_HOME and the whole FM_AZURE_* set. All three suites pass sealed: 90 / 17 / 21, exit 0.

No crosscheck unit depended on an ambient FM_* the seal removes — nothing changed state between the unsealed and sealed runs. Worth noting the seal helps this PR: FM_HOME is exactly the variable my task brief warned made fm-worker-lifecycle non-hermetic, and #282 now removes it at admission.

Your suspicion about sibling scope is confirmed, and it is broader than the two switches you named. The seal is exact FM_HOME, exact FM_WORKER_PROVIDER_COMMAND, exact FM_SPAWN_CLOUD, prefix FM_AZURE_, prefix AZURE_, prefix ARM_. All 23 FM_CROSSCHECK_* variables are unsealed — none matches an exact name or a prefix. That includes the two routing switches you asked about, FM_CROSSCHECK_REVIEWER_CONFIG and FM_CROSSCHECK_EXECUTION_MODE, and also four that redirect executables: FM_CROSSCHECK_PI_BIN, FM_CROSSCHECK_CODEX_BIN, FM_CROSSCHECK_SANDBOX_BIN, FM_CROSSCHECK_PYTHON. Same shape as FM_SPAWN_CLOUD sealed / FM_SPAWN_SECONDMATE_CLOUD not. Not fixed here — route it to the branch holding that finding.

Admit-red mutations

Each removes an admission control; each goes red because the wrong thing was accepted, not because a message changed. The source is restored from a byte copy and the restore is digest-verified.

mutation red with
widen the model allowlist and drop the baseUrl/api refusal AssertionError: unusable cross-family credential was accepted: model-level baseUrl/api override
map the lane model to openai-codex provider-mapping assertion fails against the lane registry
drop the final-segment lane match in model_family AssertionError: some-other-slot/accounts/fireworks/models/glm-5p2
drop the provider-layer allowlist AssertionError: unusable cross-family credential was accepted: provider-level fields the lane does not pin
accept stopReason: length not ok - truncated cross-family verdict: expected exit 1, got 0 (the truncated verdict became a clear verdict)

Verification

python3 tests/run-one.py bash tests/<suite>.test.sh, FM_HOME unset: fm-crosscheck 90 ok, fm-crosscheck-azure 16 ok, fm-crosscheck-slack 21 ok, all exit 0. bin/fm-lint.sh clean over 246 scripts.

Lane status: five attempts, each failing further along

# outcome
1 complete turn, final text not bare JSON → malformed verdict artifact
2 bounded command timed out after 1800 seconds
3 real verdict naming the exact head, in bare JSON, Unterminated string ... column 3045
4 complete, schema-valid verdict after a 424s review → UNREVIEWED: the reviewer's own executed_reproduction.command did not name both SHAs
5 identical to 4 — so this is reproduced behavior, not a flaky run

Attempt 4 is the milestone: the ledger recorded a genuine cross-family reviewer record against a codex-model author — model: accounts/fireworks/models/glm-5p2, review_family_mode: cross-family-primary, no model_independence marker, credential_source: pi-fireworks-glm-models-file, credential_identifier: provider-binding:fireworks-glm:d2a164ff....

Measured along the way: reasoning_content in streamed deltas is not the problem (390 and 867 reasoning deltas reach [DONE] in 21–24s, max gap 3.4s), which answers R6's own unverified Work item and disproves the suspect this doc previously named; and the token cap is not the truncator (both max_completion_tokens and max_tokens honored at 20000).

What remains is the reviewer obeying the review's own evidence discipline. That must not be relaxed to get a green verdict — a lane that earns its first verdict by lowering the evidence bar is worth less than no lane. Strengthening the instruction is the legitimate next step and is deliberately not done here: that prompt is shared with the codex lane, which satisfies the clause today.

What is still open after this PR

  • The R6 acceptance itself. No accepted verdict yet. Under the amended reading the family-screen clause is now evidenced for a codex-model author; the non-codex-model author half and an accepted verdict are not.
  • Review guards sized to the model's context window. Two of the three exist and are stronger than asked (strict findings schema; citations validated by escape check, git ls-files --error-unmatch, and line-in-range). The third does not: MAX_LEDGER_PROMPT_BYTES (64,000) and MAX_PROJECTED_FINDINGS (512) are fixed constants and nothing reads the lane's declared contextWindow.
  • A status command answering whether the cross-family lane is serving or the fallback is active. Still does not exist. Substantive, not cosmetic: the fallback is active right now and only a stderr line says so.
  • reasoning_content in streamed deltas, never verified, and the leading suspect for pi hanging against this provider (hangs at 420s, 900s and 1800s while direct streaming of the same model answers in 1.56s and reaches [DONE]). Labelled a suspicion, not a diagnosis.
  • A per-review spend meter. The provider slot declares real per-million costs (1.45 / 4.69; pi's unit convention is per million, confirmed in @earendil-works/pi-ai/dist/models.js), but this change does not make the ledger cost non-zero: bin/fm-crosscheck.py records no token usage at all, so R10's daily_budget_usd still has nothing to bind to.

No cloud resources were created by this change. Do not merge on my account.

ruby-dlee added a commit that referenced this pull request Aug 21, 2026
…edit check

The acceptance is two completed cross-family reviews, one over a
codex-authored change and one over a claude-authored change. Zero exist.
Two attempts were made against #281 on the direct Fireworks lane: the
first refused a complete model turn whose final text was not bare JSON,
the second timed out in pi at 1800 seconds. Record both, record what is
established by execution (the provider answers in under two seconds and
streams to DONE, so the hang is in pi rather than the provider), and name
unverified `reasoning_content` streaming as a suspicion rather than a
cause.

Retire the startup-credit decrement check as moot. It assumed Fireworks
pay-per-token on Foundry billed as Azure consumption; Fireworks direct
bills a Fireworks account and touches no Azure credit, so that charge can
never appear in Cost Management. The Foundry account now has zero
deployments, so the historical numbers are a record of a dead lane.

Also state plainly what this change does not close: the acceptance, the
status command that would make a silent fallback impossible, the
`reasoning_content` verification, and a per-review spend meter. The
provider slot now declares real per-million costs, but the ledger records
no token usage at all, so R10's daily_budget_usd still has nothing to
bind to.
@ruby-dlee
ruby-dlee force-pushed the feat/crosscheck-cross-family-reviewer branch from 08ad000 to 5a21df7 Compare August 21, 2026 03:58
@ruby-dlee ruby-dlee changed the title feat(crosscheck): bind the cross-family reviewer lane to a named registry feat(crosscheck): make the cross-family reviewer lane executable on a named registry Aug 21, 2026
ruby-dlee added a commit that referenced this pull request Aug 21, 2026
…edit check

The acceptance is two completed cross-family reviews, one over a
codex-authored change and one over a claude-authored change. Zero exist.
Two attempts were made against #281 on the direct Fireworks lane: the
first refused a complete model turn whose final text was not bare JSON,
the second timed out in pi at 1800 seconds. Record both, record what is
established by execution (the provider answers in under two seconds and
streams to DONE, so the hang is in pi rather than the provider), and name
unverified `reasoning_content` streaming as a suspicion rather than a
cause.

Retire the startup-credit decrement check as moot. It assumed Fireworks
pay-per-token on Foundry billed as Azure consumption; Fireworks direct
bills a Fireworks account and touches no Azure credit, so that charge can
never appear in Cost Management. The Foundry account now has zero
deployments, so the historical numbers are a record of a dead lane.

Also state plainly what this change does not close: the acceptance, the
status command that would make a silent fallback impossible, the
`reasoning_content` verification, and a per-review spend meter. The
provider slot now declares real per-million costs, but the ledger records
no token usage at all, so R10's daily_budget_usd still has nothing to
bind to.
@ruby-dlee
ruby-dlee force-pushed the feat/crosscheck-cross-family-reviewer branch from d15719c to 264c75c Compare August 21, 2026 08:57
…stry

R6 requires reviews outside the author's model family. The lane was
hardcoded to GLM-5.2 on Azure Foundry's Fireworks partner lane, which has
never produced a completed review and cannot on this subscription:
Fireworks-published deployments are Marketplace SaaS offers, and a
credit-only "Microsoft Azure Sponsorship" subscription cannot purchase
them, so `FW-GLM-5.2` returns HTTP 500
`invalid_model_endpoint_authentication` while non-Fireworks publishers on
the same account, key and subscription return HTTP 200.

Replace the single hardcoded model with CROSS_FAMILY_LANES, a code-side
registry of vetted reviewer lanes. Each entry is a complete endpoint
allowlist entry: deployment, Pi provider slot, chat-completions api
surface, Foundry resource, host, and the one accepted base URL. The
roster selects the serving lane by naming the model, so substituting
among registered lanes is a config change; admitting a new endpoint stays
a reviewed code change, because the allowlist is the security control.

Registered: azure-kimi (Kimi-K2.7-Code), azure-deepseek
(DeepSeek-V4-Pro), and azure-glm (FW-GLM-5.2), retained as evidence and
out of the roster. Two distinct non-OpenAI vendors ahead of the codex
fallback means one vendor outage does not drop reviews back to the
author's own family.

Every security property is preserved per slot rather than for one
hardcoded slot: the endpoint allowlist, the refusal of a model-level
baseUrl/api override (pi's composer lets a model-level field outrank the
provider pin), the fail-closed inspector on a missing, malformed, or
unexpected-provider credential, and the model image attestation guard.
The lane is always keyed on the reviewer model from the code registry,
never on what a credential file declares about itself, and the reviewer
identity remains the non-secret Foundry resource/deployment binding.

Ledger provenance becomes `cross-family-primary`; the legacy
`glm-primary` value stays readable for durable records and stays bound to
exactly that lane's model.
…M-5.2

The Azure Foundry partner lane cannot work on this subscription, and the
reason is billing, not the vendor. Partner models are Marketplace SaaS
offers; a credit-only "Microsoft Azure Sponsorship" subscription cannot
purchase them, so every FW-* deployment returns HTTP 500
invalid_model_endpoint_authentication while non-Marketplace publishers on
the same account and key return 200. The owner opened a direct Fireworks
account, which bypasses Marketplace entirely, so GLM-5.2 is the reviewer
again on its merits.

Point the lane registry at the single lane fireworks-glm serving
accounts/fireworks/models/glm-5p2 at https://api.fireworks.ai/inference/v1,
and retire the azure-glm slot along with the two Azure R6 attempt models.
The pinned model id, not the glm-5p2-fast router: a router may re-point to
a different serving variant, and the reviewer identity this gate records
has to name an exact model.

Three defenses land with it.

Lane matching is exact against the model id or pi's <slot>/<model> form. A
lane model id now contains slashes, so the previous rsplit-based identity
comparison would have missed the lane entirely.

The registry pins each lane's model-level `compat`. `compat` is the second
model-level object pi honors after baseUrl/api, and supportsFinishReason:
false would blunt the truncated-verdict refusal, so the inspector, the
Azure archive gate, and the model guest all refuse any compat that is not
the lane's own. The registered lane declares none.

Reviewer independence compares the model FAMILY, not the exact id. The
first completed cross-family review of this branch reproduced a gpt-5.5
author being admitted a gpt-5.6-sol reviewer with no same-model marker,
which is the same-family review R6 exists to prevent (cc-4dcd7873f71a).
Registered lanes are their own family, gpt/o1/o3/o4/codex are one OpenAI
family, claude-* one Anthropic family, and an unrecognized model stays its
own family so nothing that previously passed starts failing.

Truncation was already refused on two grounds; an end-to-end test now
proves it, emitting a complete schema-valid clear verdict whose only
defect is stopReason length and requiring the run to record tool-failure
with no citations. Measured live: max_tokens 600 returns finish_reason
length with empty visible content, 4000 completes.

Also fixes the operator documentation that told captains to provision an
openai-codex auth.json for every Pi reviewer, which misprovisions a
cross-family lane home (cc-769d7eba2ded).
…fenced verdicts

Both defects were found by a real completed crosscheck review of this branch
(codex-family fallback lane, head b8deb37) and both were reproduced before
being fixed.

cc-5ec330d3c74d, high: provider-qualified authors bypassed family separation.
pi records a model as <provider-slot>/<model>, and the family classifier fell
back to model_identity, which keeps only the final segment. The SAME lane model
reached through some other author-side slot therefore read as a different
family, so a some-slot/accounts/fireworks/models/glm-5p2 author was admitted
the fireworks-glm reviewer with no relaxation and no degraded marker. Family
membership is now judged on the final segment against every registered lane,
which errs toward calling two models the same family. Lane selection for
credentials and providers keeps its exact match, because there the safe error
runs the other way.

cc-ca5848b19ac3, high: the compat pin checked only one of the layers pi
composes. pi builds the effective model from mergeCompat(providerConfig.compat,
definition.compat) plus a topmost modelOverrides[<id>] layer that also carries
compat and headers, so a credential could still turn off supportsFinishReason
behind the lane's empty pin and blunt the truncated-verdict refusal. Naming
fields to refuse was the wrong shape: the credential is now an ALLOWLIST at
every layer. The provider may carry only baseUrl, api, apiKey and models; a
model entry only its descriptive fields plus the lane's pinned compat; and the
document only providers. The Azure archive gate and the model guest mirror it.

Also: a verdict wrapped in exactly one Markdown code fence spanning the whole
message is unwrapped. GLM-5.2 through pi presents its JSON that way, which read
as a malformed verdict artifact and cost the lane its first review. Prose
around the fence, several fences, and an unclosed fence all still refuse,
because each is a case where the reviewer said more than one thing. A truncated
verdict never closes its fence, so this cannot turn one into an answer. The
refusal now also names a bounded, repr-escaped prefix of the offending text,
which the previous message did not, and reviewer output can therefore not
inject a line into an operator's log.

The registered-lane execution case is driven from the registry itself rather
than a one-element loop, which also clears shellcheck SC2043.
…edit check

The acceptance is two completed cross-family reviews, one over a
codex-authored change and one over a claude-authored change. Zero exist.
Two attempts were made against #281 on the direct Fireworks lane: the
first refused a complete model turn whose final text was not bare JSON,
the second timed out in pi at 1800 seconds. Record both, record what is
established by execution (the provider answers in under two seconds and
streams to DONE, so the hang is in pi rather than the provider), and name
unverified `reasoning_content` streaming as a suspicion rather than a
cause.

Retire the startup-credit decrement check as moot. It assumed Fireworks
pay-per-token on Foundry billed as Azure consumption; Fireworks direct
bills a Fireworks account and touches no Azure credit, so that charge can
never appear in Cost Management. The Foundry account now has zero
deployments, so the historical numbers are a record of a dead lane.

Also state plainly what this change does not close: the acceptance, the
status command that would make a silent fallback impossible, the
`reasoning_content` verification, and a per-review spend meter. The
provider slot now declares real per-million costs, but the ledger records
no token usage at all, so R10's daily_budget_usd still has nothing to
bind to.
…it can evidence

Attempt 1 on the Fireworks lane was never a hang. GLM went through pi,
completed a turn, and was rejected at the last step on output SHAPE.
Measured since, against the gate's exact review instruction and schema:
GLM returns its verdict inside one ```json fence, and a bare parse of that
fails with `Expecting value: line 1 column 1 (char 0)`, byte for byte the
recorded failure.

The extractor now unwraps EXACTLY ONE complete fenced block. One block
leaves nothing to choose between, so surrounding prose is harmless;
several complete blocks still refuse, because then the gate would be
picking which one was the verdict. Requiring the fence to span the whole
message would re-break the lane the first time a model prefaces its answer
with a sentence, and the point of a registry-driven lane is that the next
model needs no prompt re-tuning.

This tolerates a wrapper, never a truncation. A truncated verdict never
closes its fence, yields zero complete blocks, and still fails to parse;
the stop-reason ground refuses it one step earlier. Both stay pinned, and
the truncation mutation stays red. That pairing earned itself: a later
attempt produced a verdict truncated mid-string while pi reported
`stopReason: stop`, so only the JSON parse caught it.

Also amends R6's acceptance to something the system can actually evidence.
Eligibility derives from the task meta's `model` and never from `harness`,
the ledger records no author identity at all, and git carries no harness
signal by design, so "a claude-authored change" is a declaration rather
than a checkable property. The acceptance becomes: the lane completes a
review, and the family screen admits it against both a codex-model and a
non-codex-model author, with the ledger recording the reviewer model, the
family mode, and whether the relaxation was required. Records that
declaring a codex author is the safe error and a claude author the
dangerous one, and corrects this program's own two runs whose claude
authorship was asserted rather than recorded.
…codex lane can run

The compartment lane's codex-family path could never work. Two readers
derived the same executing-account identity and disagreed by a literal
prefix: `account_identity` returned `codex:<id>` / `openai-codex:<id>`,
while `create_credential_archive` derived the BARE `<id>` separately and
then refused on `archived_identity != reviewer_account_identity`. That
comparison was structurally always true, so a live run refused at 04:43Z
before any billable resource, and NO codex-family compartment review has
ever run. Only the cross-family branch passed, because both sides there
read one shared value, which is exactly why it went unnoticed.

Fixed at the source of the disagreement rather than by making the
comparison lenient: `account_identity_from_credential` is now the single
derivation, `account_identity` reads the home's auth.json and calls it, and
the archive parses the bytes it is about to package and calls the same
function. No third place strips prefixes.

Pinned by a test that drives the real readers end to end for BOTH
codex-family branches and is RED on the two-derivation code, failing with
the exact live message. It also asserts a genuinely different account still
refuses, so the fix did not buy the pass by loosening the check.

Carries the second finding too: the refusal left no durable record at all.
`create_credential_archive` is called outside the window whose handlers
persist a run, so this class of error escaped to main()'s catch-all and no
ledger, report or data directory was written. It now converts to a tool
failure, which is the same treatment the model-image attestation refusal
twenty lines above already gets: the attempt is recorded and the roster
rotates to the next reviewer account.

Both owning documents said the compartment lane needed only a config flip.
That was false and is corrected in each: `enabled: false` was masking an
independent in-code blocker. This is the second merely-disabled claim this
requirement has had to retract without anyone reaching the code, after the
stale pi-binary reason.
…l guest

BLOCKER. The previous commit claimed "one executing-account derivation so
the compartment codex lane can run". It could still not run. The model
guest carried a THIRD derivation returning the bare account id while the
host digests the prefixed one, so both codex-family branches refused. Its
own docstring warned against exactly this ("do not fix a future mismatch
by stripping prefixes in a third place") and the third place already
existed.

It was also strictly worse than the bug it replaced. The old refusal fired
in create_credential_archive during stage, before reserve_model_capacity.
This one fires inside a booted, paid VM.

The guest ships self-contained onto the VM and cannot import the host
readers, so the duplication is inherent and the mitigation is coverage:
model_guest_executing_account_unit EXTRACTS the guest's own credential
block from the shipped bytes and EXECUTES it against real archives and the
host readers. It is red on the bare-id form with the exact live message,
and it also proves a foreign account and a credential with no account id
still refuse, so agreement was not bought by dropping the check.

That unit exists because the guest had zero behavioral coverage: the suite
asserted substrings were present in its source and never ran it, which is
why this was invisible. It now also pins registration completeness -
adding a lane to the guest table without a case "$MODEL" arm previously
passed every test and died at runtime with exit 125.

BLOCKER. A truncated verdict was defeated by any preceding complete fence.
A truncated fence contributes zero complete blocks, so a draft or example
fence earlier in the message left the count at one and the gate certified
THAT block, discarding the real answer, with stopReason stop and a
successful parse. Worse than what it replaced: the old shape failed
loudly, this passed quietly with a stale verdict.

Two rules now, and the second was found by the first test written for the
first: any unterminated fence refuses outright, and a single complete
block is unwrapped only when it is the ONLY JSON-bearing content, because
a COMPLETE example fence followed by a truncated BARE verdict also counts
one block. Prose carries no braces, so a wrapper is still tolerated.

Also, all pinned by tests:
- the archive gate's allowlists now reference core's rather than a local
  copy that had already drifted weaker (no model-level allowlist, no api
  check, so openai-responses could be archived - forbidden by R6)
- family matching normalizes separators, so gpt5.6-sol no longer reads as
  its own family, and each lane carries the aliases of its own model, so
  an author on GLM-5.2 under any vendor id cannot take the GLM reviewer
  unmarked. Lane SELECTION stays exact; only the family screen folds.
- the TOCTOU refusal four lines after the previous durability fix now
  persists as a tool failure instead of vanishing

docs/crosscheck.md claimed a truncated verdict "cannot parse against the
review schema either". That was false and is corrected, as are both
owning documents' accounts of the identity fix.
…sh from absence

All three shipped correct and all three were invisible to the suite:
removing each left it green. A guard no test can distinguish from its own
absence is not a control.

The odd-marker rule is not redundant with the brace-remainder rule, which
is why it needed its own case. The shape only it catches is an example
fence followed by a verdict truncated immediately after its OPENING fence:
markers 3, one complete block, and a brace-free remainder, so without the
marker count the example gets certified as the verdict. That is a
plausible truncation point and exactly the failure this lane exists to
close.

The model guest's manifest identity check binds the archive to the
REQUEST, not just the credential to the account, and dropping it admits a
forged manifest - six forgeries now refuse, covering effort, model,
harness, review generation, credential name and credential digest.

The TOCTOU re-proof is extracted into `require_stable_reviewer_credential`
so its exception CLASS can be driven rather than asserted from source.
The test also asserts the two classes stay distinguishable, so it cannot
pass vacuously if AzureCrosscheckError is ever made a CrosscheckToolError
subclass.

Also: `cross_family_provider_host_unit` asserted one hardcoded host for
every lane, so any genuinely new lane failed THERE first and the
registration-completeness guard this repo advertises never ran. It now
checks per-lane consistency instead, and adding a second lane without its
guest `case` arm fails with "lane example-second has no provider dispatch
arm in the guest" - the guard actually claimed for the job.

Records two accepted availability regressions in docs/crosscheck.md: a
verdict whose own string content holds a triple backtick, and a verdict
accompanied by brace-bearing prose, both now refuse. Both fail closed as a
tool-failure that rotates the roster, so they cost a reviewer attempt
rather than correctness - a real cost in a repo full of fences, paid to
make a superseded block impossible to certify.

Records in code that an alias list is inherently incomplete and is not a
security boundary; every listed gap is refused at admission today.

Removes the "refuses a cross-family model whose mapped slot is not that
lane's own slot" claim from docs/crosscheck.md. PI_MODEL_PROVIDERS is
built as {lane["model"]: lane["slot"]}, so that guard is structurally
unreachable. It stays as a cheap consistency assertion, now annotated as
unreachable, and no documentation claims it as a control.
@ruby-dlee
ruby-dlee force-pushed the feat/crosscheck-cross-family-reviewer branch from 264c75c to 47db9d7 Compare August 21, 2026 09:08
@ruby-dlee
ruby-dlee merged commit 6a41486 into main Aug 21, 2026
13 checks passed
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.

1 participant