Skip to content

One tenant's malformed manifest row denies every other tenant in the file #45

Description

@iceteaSA

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions