Skip to content

[C] #514 slice 14: Insights read facade for reports, exports and audit reads #856

Description

@mforce

Important

Track C is unscheduled. These slices move production code, and the 2026-08 design's own status
line still governs: "proposed architecture; no production refactor is authorized by this
document."
Filed so the analysis is tracked work rather than a comment, not as a commitment to
run it.

The 2026-09-14 re-plan
recommends gating Track C on evidence from Tracks A and B: if the #842 ratchet fires repeatedly in
real pull requests, these become worth their cost; if it stays quiet, they do not. Track C is
57–95 focused engineering days and produces no user-visible change.

Read the re-plan before picking any of these up. Several assumptions in the 2026-08 plan are
corrected there.

Reports, exports and audit reads behind a read-only facade that may query across owners.

The principle

A controlled cross-owner read is a privilege, not a dependency from one writer on another
writer's repository. Insights may execute documented AsNoTracking SQL across owner tables through a
restricted read session, including the current repeatable-read export snapshot.

It may not expose IQueryable, entities or the context, and may not call SaveChanges. The
seam-surface guard (#847) already enforces the type half of that.

The alternative — Insights calling every module repeatedly to assemble a report — was rejected in the
2026-08 design and should stay rejected: shallow interfaces, poor query plans, and an inconsistent
snapshot across calls.

Scope

  • IInsightsModule returning materialised or streamed DTOs.
  • Move ReportQueries and ExportQueries — both in src/Cluckwork.Infrastructure/Repositories/,
    not Persistence/ — without changing their SQL semantics.
  • Expose audit reads. The audit append sink stays in Platform and keeps enlisting in the caller's
    transaction; audit is an atomic write, not a decoupled event.
  • Retires two of the five compatibility exceptions.

Done when

Report totals, tenant isolation, exact export datasets, the repeatable-read snapshot, cancellation
and stream-failure behaviour all pass unedited. SQL byte-identical where it moved. A guard
forbids writes and IQueryable on the read session.

Ordering keys must not shift: AuditEvents orders by OccurredAtUtc then the shadow Sequence
(#508), and business lists order by business date, CreatedAtUtc, then Sequence (#819).

Explicitly not in this slice

The audit writer. Projections or read models built from events — not needed, and not a way to
manufacture module purity.

Activity

  1. added this to the Modular monolith milestone on Sep 14, 2026
  2. added
    sliceThin vertical work item
    area:apiAPI/endpoint layer
    size:MA day or two; migration or a multi-state UI
    epic-514Modular monolith architecture (#514)
    on Sep 14, 2026
  3. changed the title [-]#514 slice 14: Insights read facade for reports, exports and audit reads[/-] [+][C] #514 slice 14: Insights read facade for reports, exports and audit reads[/+] on Sep 14, 2026
  4. added
    track:C#514 Track C — moves production code; UNSCHEDULED, needs authorisation
    on Sep 14, 2026
  5. mforce commented on Sep 26, 2026

    @mforce
    OwnerAuthor

    Audit 2026-09-26, against main at e47288c

    Body left as written. Two corrections and a scheduling change.

    This slice should move near the front of Track C. Six endpoint files inject IAuditEventRepository directly to decorate responses with provenance: expenses, daily entries, egg grades, flocks, sales, and Insights' own audit endpoints. Five of those belong to other modules' slices, and one of them is the pilot's (ExpenseEndpoints, see the audit comment on #849). Until this facade exists, every one of those slices either keeps a direct injection it was supposed to remove, or blocks. Scheduling this at slice 14 of 17 means five later slices inherit the same unresolvable item. Recommended position is immediately after the pilot.

    Correction: this facade creates six declared edges, it does not remove any. ReportQueries, ExportQueries and AuditEventRepository live in Cluckwork.Infrastructure.Repositories, which the ledger maps to Platform, the free hub. That is the only reason the Insights row is empty today. Move them into an Insights-owned namespace as scoped and the module ledger will require Insights to Flock Management, Egg Operations, Commerce, General Inventory, Finance and Farm. The empty row becomes a mostly-R row. That is arguably the point, since governed cross-owner reads become visible, but the issue does not say it and anyone sizing from the current text will be surprised.

    Correction: there are no cross-owner SQL joins. Every "cross-owner read" here is method-level assembly of several single-owner queries in C#, never a join spanning owner tables. GetProfitAsync runs one Commerce query, one Finance query and one Farm query and does the arithmetic in memory. ExportQueries.GetDataset is twenty single-table switch arms. The audit provenance CTEs join AuditEvents only to itself. This matters for the design: the facade is composing reads, not hiding a join, so it can be a thin aggregator rather than a query-rewriting layer.

    Premises that hold. No writes anywhere in the three classes: no SaveChanges, no ExecuteSql. The repeatable-read export snapshot is still ExportQueries.BeginConsistentReadAsync on a separate non-retrying context. The surface is about nine methods in three groups: four report methods, two export methods, two audit reads.

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

    area:apiAPI/endpoint layerepic-514Modular monolith architecture (#514)priority:tier4Deferred or speculativesize:MA day or two; migration or a multi-state UIsliceThin vertical work itemtrack:C#514 Track C — moves production code; UNSCHEDULED, needs authorisation

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions