Skip to main content
Dejan Demonjić brand logoDejan Demonjić
Decision log
Work
About
Contact

Decision log

Decision records, defect autopsies and field notes from building secure, multi-tenant .NET platforms. Newest first.

  1. Field noteEngineering systems

    Technical SEO for a Blazor site: what the crawler actually sees

    A crawler sees bytes, not intent. Static server-side rendering, one content source for pages, sitemap and feeds, a real 404 and immutable fingerprinted assets get a Blazor site most of the way. The rest is reading your own output: an early export of this site showed a truncated title, modified dates hours before the published dates, an author link to nowhere and a configuration flag the renderer ignored. Each one with the rule it breaks and the fix.

    16 Oct 2026 · 9 min read

  2. Decision recordEngineering systems

    The quality gate that could not see async code

    A gate is a measurement, and a measurement has blind spots you must find before you trust it. Our CRAP gate took complexity from source and coverage from a Cobertura report, and the two never met for an async method, because the compiler had moved its body into a state machine. Of 1,946 async methods, two showed any coverage. What that meant for the gate, why the measurement was left unfixed, and the two rules adopted instead.

    16 Oct 2026 · 8 min read

  3. Decision recordDevices at the edge

    Panel passwords are sealed to the host that uses them

    A secret that one process uses should be readable by that process alone. The decision record for the administrator passwords of access-control panels: write-only entry, a fresh AES-GCM key per secret wrapped with RSA-OAEP under the connector host's key, opened in memory only, one escrow export and then a key nobody can export, a TPM tier that excludes escrow, and why machine-scope DPAPI and the web runtime's key ring were the wrong tools.

    16 Oct 2026 · 8 min read

  4. Field noteDevices at the edge

    Talking to door controllers: what ISAPI teaches about typed outcomes

    A boolean cannot carry what an access-control panel answers. Three measured facts (a failed write with HTTP 200 and no status, an acknowledged write that changed nothing, a reused digest nonce the panel refused) turned a 27-member interface whose every write returned a boolean into a closed set of typed outcomes, a classification order, a read-back rule and a fresh challenge per request; asking which way a door fails added a deadline on anything that unlocks one.

    16 Oct 2026 · 9 min read

  5. Decision recordRealtime transports

    SignalR between a cloud and its sites

    A hub that pushes changes from a cloud to on-premises sites is a security boundary and a lifecycle problem: who may connect, what each connection hears, how long an admission holds, and how a client comes back after the server closes it. The design note: a service-client policy, a group per tenant, a fifteen-second sweep with the same check requests get, and a client that starts itself again because automatic reconnect does not follow a server-side Abort.

    16 Oct 2026 · 8 min read

  6. Decision recordRealtime transports

    Server-Sent Events in ASP.NET Core 10, and when to pick them over SignalR

    When a server talks and a client listens for the length of one request, Server-Sent Events is the right transport: plain HTTP, one direction, a text protocol a proxy carries and a browser reconnects by itself. .NET 10 made it a first-class result. The parts that bite are on the client: the protocol has rules, and some servers decide per response whether to answer with JSON or with a stream.

    16 Oct 2026 · 7 min read

  7. Decision recordMulti-tenant platforms

    One repository, several solutions

    Splitting a product line into repositories is a permissions decision wearing an architecture costume. Azure Repos permissions stop at the repository, so if nobody has to be kept from reading part of the code, one repository with several solution files keeps the compile-time safety that packages trade for version drift. The decision record, the reference matrix, and the test that holds it.

    16 Oct 2026 · 9 min read

  8. Decision recordMulti-tenant platforms

    A schema per tenant: the database owns the name

    When every tenant's rows live in a PostgreSQL schema of their own, the mapping from identifier to schema name is a security boundary. So the mapping moved into the database: a stored generated column, a unique index, COLLATE "C", and a plain CREATE SCHEMA as the claim on a new name. With the two error codes that mean 'taken', the one-at-a-time gate, and an EF Core pooling detail that only production-shaped tests found.

    16 Oct 2026 · 10 min read

  9. Decision recordMulti-tenant platforms

    The tenant comes from the token, not from the header

    In a multi-tenant API the tenant a request acts in is decided from who is calling. A header may name one; the token says whether the caller may act there. The decision record: one middleware owns the header, the status code follows the caller, and authority that arrives through a replicated directory is trusted for one token lifetime.

    16 Oct 2026 · 9 min read

  10. Decision recordIdentity and access

    Mutual TLS and certificate-bound tokens for the links that matter

    Client credentials alone let a token travel. For the links between a site and its cloud, and between an assistant's tool servers and the API, every token is bound to the client's certificate (RFC 8705), tokens are exchanged instead of forwarded (RFC 8693), and resources announce their issuer (RFC 9728). The decision record, with the parts that only building it revealed.

    16 Oct 2026 · 8 min read

  11. Decision recordIdentity and access

    A Content Security Policy for an identity server

    The strictest policy a sign-in page can carry, which directive is left out and why, what base-uri 'none' does to a Blazor page, and why the policy is chosen from the endpoint. A design note from building Hexavera's identity host.

    16 Oct 2026 · 6 min read

  12. Decision recordIdentity and access

    When the token store is down, the honest answer is 503

    A resource server that cannot check a token must refuse it, and a 401 is the wrong refusal: it tells the client its token is bad and signs the user out. The design: fail closed, classify the fault by what the data provider says, answer 503 with a Retry-After drawn per response, and let anonymous routes ask before they say 'not signed in'.

    16 Oct 2026 · 7 min read

  13. Decision recordIdentity and access

    Decide from the endpoint, not from the path

    Rate limits, security headers and access gates are per-endpoint decisions. ASP.NET Core accepts several spellings of one path, so a decision made by comparing the path is a decision made on incomplete information. Put it on the endpoint, and test that every endpoint is covered.

    16 Oct 2026 · 6 min read

  14. Decision recordIdentity and access

    One identity server for the cloud and for every site

    When the same product runs in the cloud and at customer sites, identity is part of the product, so one identity host on OpenIddict and ASP.NET Core Identity serves both. The decision record: the model, the values, the options rejected, and what it costs.

    16 Oct 2026 · 8 min read

  15. Decision recordEngineering systems

    Pre-MVP: make it right, never comply

    Before the first release there is nothing to stay compatible with. Code that adapts to older code, an older schema or stale development data is a defect, not a kindness. The rule, how it was found, where it has held and where it is shaping decisions not yet built, and the day it should end.

    11 Oct 2026 · 6 min read

Follow the log by RSS

Dejan Demonjić

Designing and building secure, multi-tenant .NET platforms, and writing down the decisions behind them.

Read

  • Decision log
  • Work
  • RSS feed

Contact

  • dejan@demonjic.com
  • Write to me
  • About

Elsewhere

© Dejan Demonjić. Written in Belgrade.