parseHandleFile refuses the whole document when any single entry fails validation. In a manifest three tenants write to, that means one tenant's bad row takes the others down with it.
Measured against a two-provider file — one valid, one with a cross-provider credential_id:
{ providers: [ deepseek (valid), anthropic (credential_id "oauth:openai") ] }
-> HandleFileValidationError: provider 1 account main has invalid credential id
The valid deepseek block is never returned. Every check in the accounts loop calls invalid(), which throws, so this is the file-wide convention rather than a property of any one predicate — it predates the provider-segment scoping in #44 and applies equally to a bad handle, a duplicate label, or a bad shape.
A peer tenant's vendored reimplementation of the same parser drops the offending entry and keeps the rest. They surfaced it by re-deriving their copy against #44's diff by hand and asking whether our guard covered an input theirs did. So the two live implementations of this contract have different failure models, and neither side had chosen that — it was discovered, not decided.
The tradeoff is not one-sided
- Whole-file refusal (current) is fail-closed. A manifest that fails validation anywhere is refused entirely, and nothing serves from a document we could not fully verify.
- Partial parse (theirs) keeps unaffected tenants working, at the cost of a tenant silently losing a binding it believes it wrote. A missing account and a rejected file are different wrongs, and the silent one is arguably worse for the tenant that owns the bad row.
There is also a blast-radius asymmetry worth weighing: under whole-file refusal, a tenant that writes a malformed row degrades tenants it has no relationship with. Under partial parse, a tenant's mistake is contained to that tenant — but only if the surviving tenants can tell the difference between "my block is absent because nobody wrote it" and "my block is absent because it was dropped".
Not changing this in #44
That PR is a predicate fix. This is a failure-model change affecting every validation in the parser, and it wants its own decision with both tenants party to it, rather than riding a scoping change.
What a fix would need beyond picking a side
Whichever way this goes, the two implementations should agree, and agreement needs a mechanism rather than a reading. Today the goldens are compared byte-for-byte across repos and the predicate is reimplemented by hand — so this class of divergence is structurally invisible to the existing drift check. Any resolution should come with a shared conformance case that both sides run, not just a matching code comment.
parseHandleFilerefuses the whole document when any single entry fails validation. In a manifest three tenants write to, that means one tenant's bad row takes the others down with it.Measured against a two-provider file — one valid, one with a cross-provider
credential_id:The valid
deepseekblock is never returned. Every check in the accounts loop callsinvalid(), which throws, so this is the file-wide convention rather than a property of any one predicate — it predates the provider-segment scoping in #44 and applies equally to a bad handle, a duplicate label, or a bad shape.A peer tenant's vendored reimplementation of the same parser drops the offending entry and keeps the rest. They surfaced it by re-deriving their copy against #44's diff by hand and asking whether our guard covered an input theirs did. So the two live implementations of this contract have different failure models, and neither side had chosen that — it was discovered, not decided.
The tradeoff is not one-sided
There is also a blast-radius asymmetry worth weighing: under whole-file refusal, a tenant that writes a malformed row degrades tenants it has no relationship with. Under partial parse, a tenant's mistake is contained to that tenant — but only if the surviving tenants can tell the difference between "my block is absent because nobody wrote it" and "my block is absent because it was dropped".
Not changing this in #44
That PR is a predicate fix. This is a failure-model change affecting every validation in the parser, and it wants its own decision with both tenants party to it, rather than riding a scoping change.
What a fix would need beyond picking a side
Whichever way this goes, the two implementations should agree, and agreement needs a mechanism rather than a reading. Today the goldens are compared byte-for-byte across repos and the predicate is reimplemented by hand — so this class of divergence is structurally invisible to the existing drift check. Any resolution should come with a shared conformance case that both sides run, not just a matching code comment.