Skip to content

ADR: a source-scoped projection cannot be the basis for an unscoped predicate — ship the predicate #2019

Description

@steilerDev

[product-owner] Follow-up from the PR #2015 review (product-architect §5). Non-blocking. Owner: product-architect — this is an ADR on their wiki page; I am filing the tracking issue, not writing the content.

As a developer designing a source-scoped or tenant-scoped API projection, I want the rule about what such a projection may and may not be used to conclude written down once, so that it stops being re-derived from scratch after each incident.

Parent Epic: none — parent-less Bank Report Wizard cluster.
Priority: Should Have

Why this is worth an ADR rather than a comment

The principle — a source-scoped projection can never be the basis for an unscoped predicate; ship the predicate — has now generated three issues (#1898, #1902, #1911) and one four-reader shared misconception (the if/else in buildReportContent.ts, two page-object JSDoc blocks, and a Scenario 18 assertion, all encoding the same false invariant).

That is the case for writing it down: the same reasoning has been performed from first principles at least four times, and got the wrong answer three of them. #1911's fix — shipping splitKind as an explicit unscoped predicate rather than letting the client infer one — is the worked example of the correct resolution.

Acceptance Criteria

  • 1.1 An ADR states the principle, its rationale, and the failure mode it prevents (a client silently inferring a global fact from a filtered array, with no signal that the array was filtered).
  • 1.2 The converse is stated with equal prominence: a source-scoped projection is the correct basis for a predicate at the same scope. Without this, the ADR will condemn correct code — hasOwnTaggedDeposit (see buildReportContent: guard test pinning hasOwnTaggedDeposit to the step-i deposit filter scope #2018) is already a live example of a same-scope predicate that the principle's forward direction appears to forbid but does not. An ADR that generates false positives will be ignored, which costs more than not writing it.
  • 1.3 The decision rule is stated operationally, so it can be applied at review time without re-deriving it. Suggested form: identify the scope of the projection and the scope of the conclusion; if the conclusion's scope is wider, the predicate must be shipped from the server.
  • 1.4 splitKind (Source report: add splitKind to distinguish deposit-driven splits from line splits #1911) is documented as the worked example of the correct resolution — including that the server already held the right answer (isSplit was true all along) and only the client's inference was wrong.
  • 1.5 The naming corollary is cross-referenced (SourceReportInvoice: rename budgetLines/deposits to name their source scoping (budgetLinesForSource, depositsVisibleToSource) #2017): scoped fields should say so in their names, because prose alone did not stop four readers.
  • 1.6 Registered on the ADR Index wiki page.
  • 1.7 No code change. Documentation only.

Notes

I own what gets built, not the architecture — the content, ADR number, and structure are the product-architect's call. ACs 1.2 and 1.4 are the two I would ask to be honoured regardless of how the rest is written: an ADR that only states the prohibition, without the permission and without the worked example, will be applied mechanically and will start rejecting correct code.

Related: #1898, #1902, #1911, #2016, #2017, #2018.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions