Skip to content

For technical evaluators

The technical posture, in plain English.

The architectural decisions, the security model, the AI governance, the compliance posture — and the honest gaps. Written for CTOs, IT directors, MSP partners considering reselling, and technical evaluators at procurement. Read it before you ask us; bring the questions it doesn’t cover.

The stack

The stack — no surprises.

Microsoft-native by binding rule. Every layer is a deliberate, defensible choice.

LayerWhat we useWhy this choice
IdentityMicrosoft Entra ID + MSALEnterprise SSO; MFA and conditional access through the same directory the client already operates. Multi-portal app registrations under a single API audience.
ApplicationC# / .NET (LTS releases only)Long-term-supported runtimes; large talent pool; first-class Azure SDK integration.
FrontendTypeScript (strict) + React + Vite + TailwindTypeScript types generated from the OpenAPI spec — no hand-written DTOs. Strict mode mandatory.
Data accessRaw ADO.NET against Azure SQLNo ORM. Query behaviour stays explicit and predictable. (Reasoned in the FAQ below.)
HostingAzure App Service (Linux) + Azure Front DoorAuto-scale, slot-swap zero-downtime deployment, WAF + DDoS at the edge. Static Web Apps is never used.
AIAzure OpenAI (managed identity) + Azure AI Document IntelligenceRuns in the customer tenant. No API keys. No training on customer data.
CommsAzure Communication Services (email / SMS / push)Native to Azure; no third-party messaging providers in the data path.
DevOpsAzure DevOps Repos + Bicep IaCSource and infrastructure-as-code in one place; audit trail; pipeline gates.
StorageAzure Blob (SAS-token, managed identity)No file uploads through the API; short-lived SAS tokens for direct browser uploads.
Background workHosted services + Azure FunctionsRight tool for the cadence; queue-triggered Functions for scale-out work.

Why no ORM?

Direct SQL keeps query behaviour explicit and predictable, eliminates the "what query did the ORM just run?" surprise class, and works with stored procedures and complex joins without object-relational impedance. We accept the boilerplate cost for behaviour transparency.

Why no Azure Static Web Apps?

Static Web Apps is a separate product with different deployment, identity, and runtime characteristics from App Service. Standardising on App Service across both portals keeps the operational surface uniform.

Why .NET, not Node for the backend?

Long-term support, strong typing, mature data-access tooling, and easier to staff at the seniority level the work needs.

Portal separation

Three portals, separated by deployment.

  • Admin Portal

    Internal staff, behind your Entra ID. The full operational surface.

  • Client Portal

    External customers. Self-service, document access, status tracking, online payments. Public marketing pages and authenticated client pages co-located in one Vite SPA.

  • Employee Self-Service

    Staff self-service — leave, timesheets, expenses, payslips, profile updates.

Each portal is a separate Vite project with its own build, deployment, URL, and Entra ID App Registration. They share the backend API and shared workspace packages — @yourorg/ui and @yourorg/api-client — but not deployments.

Combining client-facing and internal functionality into a single deployable frontend is a binding architectural defect under our framework. The API is the security boundary; the portals are the audience boundary. Mixing them is what causes admin-tools-leaked-to-clients incidents in the wild.

Security model

Security, in layers.

Authentication
Microsoft Entra ID, OAuth 2.0 / OpenID Connect via MSAL.js on the frontend; JWT bearer-token validation on every API request (issuer, audience, expiration, signature). The API accepts tokens from each portal’s app registration via configured trusted issuers.
Authorisation
Role-based access control via Entra ID App Roles. Roles arrive as claims in the JWT; the API enforces with [Authorize(Roles = "…")] or policy-based authorisation. Frontend role checks are UX-only — the API is the enforcement boundary.
CORS
Allowed origins listed explicitly, per environment, per portal. AllowAnyOrigin() is never used in staging or production.
HTTP headers
HSTS, X-Content-Type-Options: nosniff, X-Frame-Options: DENY, a tailored Content-Security-Policy (no unsafe-inline without justification), Referrer-Policy: strict-origin-when-cross-origin, minimised Permissions-Policy, Cache-Control: no-store on authenticated API responses.
Rate limiting
ASP.NET Core rate-limiting middleware; tiered per endpoint category (public 30/min, authenticated 100/min, admin 200/min, auth endpoints 5 per 5 minutes); 429 responses carry Retry-After.
Managed identity
Every Azure-internal hop — App Service to SQL, Storage, Key Vault, Cache, Service Bus, App Configuration — uses system-assigned managed identity in staging and production. No API keys stored anywhere except Key Vault.
Secrets
All third-party secrets via Azure Key Vault references in App Service configuration. Never in source, environment variables, or app-settings literals.
Audit logging
Security-sensitive operations write to a dedicated, append-only audit-log table (separate from operational logs) with a minimum 12-month retention.
WAF / DDoS
Azure Front Door Standard with a WAF policy in prevention mode (not detection) is mandatory for every production deployment.

Data

Every field has a tier.

TierExamplesHandling
1 — PublicMarketing content, published helpNo restrictions.
2 — InternalOperational metrics, dashboardsAuthenticated access; encryption at rest via Azure SQL TDE.
3 — ConfidentialCustomer names, emails, business data, invoicesAuthorised roles only; encrypted at rest; access logged.
4 — RestrictedPasswords, tokens, financial account numbers, health data, gov IDsField-level encryption via Key Vault; access logged and alerted; never stored if avoidable.
  • PII handling

    Every spec classifies fields. A binding rule requires the AI to flag suspected PII and confirm classification before generating code. PII never appears in URLs, logs, error messages, cache keys, or test data.

  • Optimistic concurrency

    Every mutable table carries a ROWVERSION column; updates filter on it and conflicts return 409 with an RFC 7807 problem-details body. No silent last-write-wins.

  • Soft delete + retention

    A standard audit-column set on every table. Soft-deleted records are purged after a defined retention period (default 90 days; longer where a sector regulates it).

  • Right of erasure

    Per-user data export (machine-readable) and anonymisation/purge are supported. Each PII-bearing table is tracked so an erasure request can be fulfilled across the whole dataset.

The AI model

AI bound by enforceable rules.

Your platform’s runtime AI runs under enforceable controls — read-versus-mutate separation, tier limits with autonomous action prohibited, per-invocation audit, and user-facing labelling, backed in code. The refusal and anti-hallucination discipline is what the delivery tooling is built under — upstream of what ships.

Read vs mutate, by hard rule
The AI may invoke read-only functions freely. Mutating functions (create, update, delete, send a communication) require a human approval step — the AI prepares the action, a human reviews and approves, and the application (not the AI) executes the mutation.
Tier classification
Every AI feature is classified Tier 1 (minimal), Tier 2 (limited — AI drafts for human review), or Tier 3 (high — affects user-facing experience). Tier 4 (autonomous action affecting users, transactions, or external parties) is prohibited and rejected at spec time.
Audit logging
Every AI invocation produces an audit record: agent version, input summary (PII sanitised), output summary, human-review outcome, timestamp, acting user.
Transparency to end users
Tier 2 and Tier 3 outputs are clearly labelled in the UI; Tier 3 includes an opt-out mechanism.
Built to refuse non-compliance
The AI delivery tooling — the assistants that build your platform — is bound to refuse non-compliant output rather than ship it. This is the discipline your platform is built under, upstream of what runs.
Hallucination is a defect
Invented APIs, SDK methods, platform capabilities, or fields that don’t exist are treated as a build defect: the delivery tooling states the limitation rather than guessing. A build-time discipline, distinct from the runtime controls above.

Models run on Azure OpenAI in the customer’s tenant via managed identity. Inputs and outputs never leave the customer’s Azure compliance boundary, and are stored in Australia for Australian clients. Foundation models only — no fine-tuning unless explicitly contracted with a separate ADR for data sources and bias evaluation.

CI/CD

The pipeline that gets in the way — in a good way.

Every PR to main runs

  • Build (warnings-as-errors)
  • Lint (Roslyn analysers for C#, ESLint strict for TypeScript)
  • Unit tests (xUnit + Vitest + jest-axe)
  • Contract tests (generated TypeScript types must match the OpenAPI spec)
  • Type-generation staleness check (regenerate; fail if uncommitted)
  • Accessibility audit (axe-core; critical or serious violations fail the build)
  • Security scans (SAST, dependency vulnerability scan, secret scanning)
  • Frontend pre-push: pnpm build, tsc --noEmit, eslint --max-warnings 0

On merge

  • Integration tests against a real test database
  • Build artefacts for backend, admin portal, and client portal separately
  • Auto-deploy to Dev, then smoke tests
  • Staging requires dev-manager approval; Production requires client approval (24-hour expiry)
  • Each component (API, admin, client) deploys and rolls back independently

Production uses Azure App Service deployment slots for zero-downtime swap-based deployment.

Infrastructure & operations

Reproducible and observable.

Infrastructure as code
All Azure resources defined in Bicep. No manual Portal configuration in staging or production — resource group, environment parameter file, repeatable.
Three environments
Development (synthetic data), Staging (anonymised/synthetic for UAT), Production (real data, restricted access). Configuration via Azure App Configuration / App Service settings — never in code.
Observability
Azure Application Insights for request, dependency, exception, and custom-metric telemetry. Correlation IDs propagate frontend → API → database. PII never in operational logs.
Health endpoints
/health/live (process running) and /health/ready (dependencies reachable). Both unauthenticated; both excluded from rate limiting.
Alerts
A baseline set: error-rate spike, response-time degradation, failed-requests spike, health-check failure, SQL DTU exhaustion, auth-failure spike, deployment failure, certificate expiry, budget forecast.
Disaster recovery
A per-project DR runbook. RTO 4 hours and RPO 24 hours as framework defaults (tightenable with client agreement). Quarterly point-in-time restore tests; an annual region-failover walkthrough.

Compliance posture

Compliance, evidenced — not promised.

XCentral-Framework is engineered to align with ISO 9001, ISO/IEC 27001, and ISO/IEC 42001:2023. XCentral Solutions Pty Ltd — the framework vendor — is not itself ISO-certified; the certifications are held by the delivery partner.

  • ISO 9001

    Quality Management

    Certified — held by Conscierra

  • ISO/IEC 27001

    Information Security Management

    Certified — held by Conscierra

  • ISO/IEC 42001:2023

    AI Management Systems

    Framework-aligned

Conscierra — the independent MSP that delivers XCentral platforms (a trading name of B & A Technologies Pty Ltd) — holds ISO 9001 (Quality Management) and ISO/IEC 27001 (Information Security Management) certification — certificates 5991-3435-01 and 5992-3435-01, issued by Compass Assurance Services (JAS-ANZ accredited). Engagements delivered through Conscierra run under those certified processes and inherit those controls.

Compliance evidence is generated as a byproduct of normal platform operation rather than assembled separately. Audit-log retention, change-control history (Azure DevOps), pipeline-gate records (build artefacts), and Architecture Decision Records all serve as evidence sources.

Ownership & exit

You own what should be yours.

What transfers on production deployment + full payment

  • Source code (mirrored to your Azure DevOps; clone rights yours from day one).
  • Database content (your Azure SQL instance; daily backups; exportable on request).
  • Infrastructure as code (Bicep templates; reproducible in another Azure subscription).
  • Architecture documentation, data model, API reference (OpenAPI), integration runbooks.
  • Security and compliance evidence pack.
  • Handover documentation — the path to running the platform without us, documented from day one.

What we retain

XCentral-Framework itself — the binding rules, slash commands, Bicep templates, governance models, and the framework’s own evolution history. The proprietary IP of XCentral Solutions Pty Ltd that built and continues to improve your platform.

The migration path

Azure resources transfer between subscriptions or tenants; Bicep reproduces the environment in any Azure account; source code moves to any Azure DevOps organisation or GitHub; database content exports to any SQL Server destination. The path to running the platform without us is in the contract — not a discretionary policy, a written one.

You own your production code (mirrored to your Azure DevOps), your data, and your Azure infrastructure. We retain XCentral-Framework — the proprietary IP of XCentral Solutions Pty Ltd that built and continues to improve your platform.

The honest gaps

What we don’t have (yet).

We don’t ship perfection. We ship discipline with known edges. Here’s what sophisticated buyers ask about that isn’t in the framework yet.

  • Supply-chain integrity artefacts

    SLSA provenance, SBOM (CycloneDX / SPDX), and artifact signing (cosign) are tracked but not yet implemented in the binding pipeline. On the roadmap.

  • OpenTelemetry explicit SDK

    Application Insights is the observability proxy today. Migration to an explicit OpenTelemetry SDK is on the roadmap, not yet shipped.

  • SLO / error-budget discipline

    Quality objectives are documented (for example, a ≥99.5% uptime target). Formal SLI / SLO / burn-rate alerting language is not yet codified.

  • Chaos / fault-injection testing

    Not currently part of the binding test pyramid.

  • DPIA tooling

    Privacy-impact-assessment templates exist; an automated DPIA workflow does not.

If a gap above is decision-relevant for your engagement, we’ll discuss it at discovery and either work around it, sequence it into your phase plan, or honestly tell you it’s a deal-breaker. What we don’t have yet, we tell you up front — that’s the difference.

FAQ

Common objections, answered.

Why no ORM?
Behaviour transparency. ADO.NET in a base-repository pattern with parameterised queries.
Why no Azure Static Web Apps?
Standardising the runtime and deployment surface on App Service across all frontends.
Why React + Vite, not Next.js / Remix?
A Vite SPA on App Service is the binding architecture; SSR is unnecessary for the audience (public pages pre-render at build).
How does the AI not leak data?
Azure OpenAI in your tenant; no training on your data; managed identity; PII flagged before generation; Tier 4 (autonomous) prohibited.
What if I want to bring it in-house?
Source + IaC + database transfer is contractual; the migration path is documented from day one.
How is multi-tenancy handled?
Default is single-tenant. Multi-tenant is specced explicitly with a TenantId at row level, resolved from the JWT claim — never from a request parameter.
What’s the test-coverage target?
80% line coverage on application services and domain logic. Coverage is a guide, not a goal.
How are secrets rotated?
Key Vault references; rotation triggers a managed-identity refresh; no app restart needed.
What about migrations?
Forward-only, sequentially numbered, idempotent-safe, backwards-compatible with the current API version for one deployment window.

Bring the questions this page didn’t answer.

If your objections are already addressed here, the next conversation is about delivery. If they’re not, that’s the conversation we want to have.