Skip to content

[C] #514 slice 15: Access contract — security hold point, runs last #857

Closed

Description

@mforce

Amended 2026-10-03 (#854, PR #1030): SetStepperUnitHandler reads conversions through Commerce's IEggUnitConversionLookup; confirm's in-transaction role and assignment reads and AccountProvisioner's default-conversion insert were left for this slice. See this comment.

Amended 2026-10-02 (#853, PR #1028): Egg Operations has a contract; EggGradeFloorPolicy's Roles read and AccountProvisioner's default-grade insert were left for this slice. See this comment.

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.

Authentication, users, roles, assignments and refresh credentials behind a contract. Security hold
point — runs last, and only after everything else is proven.

Why last

Highest blast radius in the system. A mistake here does not surface as a failing test; it surfaces as
a revoked credential that still works. Access is deliberately sequenced after every lower-risk module
has proven the extraction pattern.

The invariant that must not weaken

CredentialEpochMiddleware performs a fresh database read per authenticated request (#364). The
round trip is the fail-closed guarantee. Do not cache it, do not move it behind a contract that
memoises, and do not change middleware order. Epoch 0 stays permanently retired, and a missing or
malformed claim stays always a mismatch.

Scope

Ordered, each independently revertible:

  1. Contracts around existing use cases, without rewriting Identity.
  2. Users and me endpoints, and flock assignment, through contracts.
  3. Auth endpoints, preserving cookies, JWT claims, and error and timing behaviour.
  4. Credential middleware onto an epoch-verifier port; prove the fresh read per request survives.
  5. Bootstrap, break-glass recovery and refresh purge behind Access; CLI and jobs call it.
  6. Internalise the Identity implementation and retire the exposed identity provider port.

Carries a known cross-module edge

SetStepperUnitHandler.cs:13 injects IEggUnitConversionRepository — a real Access → Commerce read
the 2026-08 matrix marks —. Contract call or declared edge; not accidental.

Done when

Every security suite passes unedited: credential epoch, suspension and the issuance window
(#579), retry boundary (#269), timing and lockout, bootstrap and break-glass drills (#265, #283),
per-farm email identity and cross-farm isolation (#530, #670). A dedicated security review before any
DI switch. Never dual-issue tokens during a switch.

If OAuth (#788) is ever built

#796 re-establishes these same fail-closed checks on the OAuth path. Two authors in
CredentialEpochMiddleware at once is a blast radius #788 already declined — this slice runs after
#796, or #796 does not exist.

Activity

  1. added this to the Modular monolith milestone on Sep 14, 2026
  2. added
    sliceThin vertical work item
    area:apiAPI/endpoint layer
    size:LSeveral days; wide blast radius or unresolved scope
    epic-514Modular monolith architecture (#514)
    track:C#514 Track C — moves production code; UNSCHEDULED, needs authorisation
    on Sep 14, 2026
  3. changed the title [-]#514 slice 15: Access contract — security hold point, runs last[/-] [+][C] #514 slice 15: Access contract — security hold point, runs last[/+] on Sep 14, 2026
  4. mforce commented on Sep 26, 2026

    @mforce
    OwnerAuthor

    Audit 2026-09-26, against main at e47288c

    No change needed. The invariant this slice is built around is intact. CredentialEpochMiddleware still performs a fresh, uncached database read on every authenticated request except logout, and the comment above it still reasons explicitly about why it must stay uncached. No memoization has been introduced since the issue was filed.

    Census: Access is 59 files and 9 tables, the largest in the system, with four outgoing edges. The heaviest is Access to Farm at 19 symbols, of which 8 are provisioning or break-glass and 11 are ongoing runtime coupling, chiefly IdentityProvider taking an account-wide lock on every user-admin write. That edge is not retired by the Farm slice and lands here, which is consistent with this slice running last.

  5. mforce commented on Oct 2, 2026

    @mforce
    OwnerAuthor

    Amendment from #853 (PR #1028). The body above is left as written.

    Egg Operations now has a contract: IEggOperationsModule for adapters, and the IEggGradeLookup and IDailyEntryLookup read ports for peers. #853 deliberately left two Access-related callers alone:

    • EggGradeFloorPolicy (Egg Operations) still resolves the caller's effective role through Domain.Accounts.Roles to decide who may set a grade's low-stock floor. The ledger's Egg Operations → Farm R edge names it. If this slice moves the role model, that caller moves with it.
    • AccountProvisioner.ProvisionValidatedAsync still inserts a new farm's default grades (EggGrade.Defaults) inside the provisioning transaction, through the declared Access → Egg Operations W edge. It does not go through the Egg Operations contract. Routing it there needs a write port that joins the provisioning transaction and never saves on its own, the IMortalityLedger shape from [C] #514 slice 10: Flock Management contract, eligibility and mortality ports #852.

    Rationale: docs/decisions/853-egg-operations-contract.md, which lands with PR #1028.

  6. mforce commented on Oct 3, 2026

    @mforce
    OwnerAuthor

    Amendment from #854 (PR #1030). The body above is left as written.

    Shipped:

    • SetStepperUnitHandler reads packed-unit conversions through Commerce's new IEggUnitConversionLookup port, not IEggUnitConversionRepository. The port calls the same GetByUnitAsync, so the query and the "active conversion only" check are unchanged. The body's "Carries a known cross-module edge" is now a contract call; the Access -> Commerce ledger edge stays declared with the same symbols.

    Left for this slice, unchanged:

    Rationale: docs/decisions/854-commerce-contract.md, which lands with PR #1030.

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:LSeveral days; wide blast radius or unresolved scopesliceThin 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