smallbox

← Back to The System Report

Sample report

NaboWorks A/S — System Report

Modernization decision — gradual extraction, controlled twin, or stabilise-first?

Sample only — client is fictional, structure mirrors the playbook.

System Report

NaboWorks A/S

Modernization decision — gradual extraction, controlled twin, or stabilise-first?

Prepared for — Decision: which path, and what is the first safe package?

Read-only review30+ pagesPDF + Markdown
NaboWorks A/Spage 1 of 34
Page 2

Table of contents

NaboWorks A/Spage 2 of 34
Page 3

Engagement basis

Because this is a sample report, code paths, file names, line numbers, and evidence references are synthetic but representative. The structure, voice, and depth mirror a real Smallbox System Report.

What we were asked to understand, and the boundary of the engagement.

Stated outcome
Decide whether NaboWorks should modernize by gradually extracting the old MVC application or by building a controlled replacement beside it — and define the first safe package without breaking booking, provider response, admin intervention, finance, or operational trust.
Trigger
Mobile conversion is disappointing. Growth wants faster intake changes. Provider onboarding changes are slower than commercial tolerance. Support is buried in avoidable edge cases. Management's framing is "modern frontend and cleaner API"; the question underneath is the modernization decision above.
In scope
Customer request flow; provider offer and matching; admin override paths; invoice and payout flow; reminder and closure jobs; review publication.
Out of scope
Native mobile app; legacy provider mobile-API fragments (suspected dead); marketing site; finance accounting package internals; helpdesk integration.
Engagement duration
Four to five weeks of focused engineering, over four to six weeks elapsed. Repository, schema dump, staging environment, and structured interviews with the engineering lead, the operations manager, the head of provider trust, and the head of finance.
NaboWorks A/Spage 3 of 34
Page 4

Executive summary

We recommend controlled twin replacement. The current system has too many hidden dependencies for a broad gradual-extraction path to be priced or promised responsibly as the primary path.

NaboWorks is a 12-year-old ASP.NET MVC service marketplace. The risk is not that MVC is old. The risk is that important behaviour — provider eligibility, request publication, offer-to-booking transitions, admin overrides, reminder timing, money movement — is spread across controllers, Razor views, SQL views and stored procedures, scheduled jobs, admin screens, exports, and staff memory. A frontend-first rewrite of this system would carry those rules forward partially, into a new system that does not yet know them.

Controlled twin replacement keeps the old system running while a clean replacement is built beside it. Business rules are mapped first. Representative operational data is migrated and anonymised so the new system can be exercised against real shapes. Old and new behaviour can be compared. Staff can preview and test before any production decision. Production responsibility does not move during the Beta.

The recommended first package is a Parallel Replacement Beta over roughly three to four months. Approximately €30,000. €10,000 to start, €20,000 on beta delivery. After the Beta, NaboWorks owns a hosted, inspectable, client-owned replacement candidate; the parity map; the working business dictionary; the seeded / mirrored operational data approach; and the open-question list — even if they do not continue. Production cutover is reserved for an optional Parity + Transition package, decided only after the Beta is reviewed. If access, environment data, or business-rule confirmation prove worse than this report assumes, the recommendation downgrades to stabilise-first before any replacement work begins.

NaboWorks A/Spage 4 of 34
Page 5

Primary outcome

The verbatim primary outcome the client and Smallbox agreed at intake. Everything that follows is in service of this question.

Question

If this report achieves only one thing, what should that one thing be?

Answer

It should decide whether NaboWorks should modernize by gradually extracting the old MVC application, or by building a controlled replacement beside it — and define the first safe package without breaking booking, provider response, admin intervention, finance, or operational trust.

Findings are ranked by their relevance to that decision, not by how they look in isolation. The recommended package is bounded so the company commits to evidence, not to a path it cannot reverse.
NaboWorks A/Spage 5 of 34
Page 6

Access, evidence, and assumptions

What was inspected with high confidence, what was reading-only, what was not reached, and what we had to assume.

High-confidence inspection
Repository (full); SQL Server schema dump; existing Hangfire and scheduled-job configuration; deployment checklist; three trace paths walked end-to-end with the engineering lead (request creation, provider offer, payout batch); structured interviews with the operations manager and the head of provider trust.
Reading-only
Production runtime behaviour; payment-provider webhooks (read in logs, not interactively triggered); finance accounting exports; one year of historical AdminAction rows.
Not reached
Production database access; production logs older than 30 days; the helpdesk ticket queue (out of engagement scope).
Assumed
The seasonal campaign subsystem is dead (verbal confirmation, not traffic logs). The legacy provider-app mobile API endpoints are dead (verbal). Two specific hidden-hold states in Provider eligibility are deliberate (verbal with the head of provider trust). All assumptions tagged in the relevant findings.
NaboWorks A/Spage 6 of 34
Page 7

The system, in plain English

NaboWorks A/S runs a Danish local-services marketplace. Households, landlords, housing associations, and small office managers create service requests; vetted local providers respond with offers; the platform takes a commission on completed work and pays providers out later. The business mixes fixed-price categories (cleaning, simple window cleaning) and request-first categories (painting, repairs, handyman) where assessment visits and back-and-forth are normal.

Trust and timing are load-bearing — payout behaviour, review publication windows, dispute recovery, and notification timing are not back-office details but the platform's primary contract with both sides of the marketplace. The system is the operational centre of gravity for ~58 employees: support and operations live inside it daily, finance depends on it weekly, management watches its reports daily.

NaboWorks A/Spage 7 of 34
Page 8

Working business dictionary

Before deeper findings, definitions of the terms used in this report. These are working names — proposed by Smallbox, confirmed in interview with the operations manager and the head of provider trust — not final business truth. Their purpose is to give the client, Smallbox, and any future developer the same vocabulary when discussing this system.

Entities
  • Customer — the household, landlord, housing association, or office manager who creates a service request.
  • Provider — the vetted local services business that responds to requests with offers.
  • ServiceRequest — the demand record. Created by the customer; visible to matched providers when published.
  • Offer — a provider's response to a service request, with price, time window, and notes.
  • Booking — the chosen offer, in execution.
  • AdminAction — an explicit operational intervention performed by support or trust staff.
  • AuditLog — the record that keeps admin and significant operational events traceable.
Concepts
  • Provider eligibility — whether a provider may currently see new requests, submit offers, and be paid out. Today this is computed by reading several different data sources in different combinations (Finding 4).
  • Request publication — the moment a request becomes visible to matched providers. Today this is implied by status transitions plus a notification job.
  • Admin override — an action that changes workflow state outside the normal customer / provider path (Finding 3).
  • Operational state — the granular state a request, offer, or booking is in internally; gates notifications, staff filters, and finance treatment.
  • Customer-visible state — the simpler label shown to the customer and provider; a strict subset of operational state.
  • Payout readiness — when a provider's earnings on a completed booking become eligible for payout. Treated in this report as a later transition concern, deliberately not part of the Beta.
Ambiguous words. Internally, "job" is used to mean a ServiceRequest, a Booking, and a Hangfire scheduled task — three different things. This report avoids "job" for marketplace work and uses ServiceRequest, Offer, or Booking by intent.
NaboWorks A/Spage 8 of 34
Page 9

The system, technically

Architecture, runtime, data layer, and the deployment reality at a glance.

  • ASP.NET MVC 5 on .NET Framework 4.7.2; Razor views; jQuery; Bootstrap 3.
  • Entity Framework 6 (database-first, EDMX) over SQL Server. ~80 tables; five carry most behaviour.
  • Background work split between Hangfire and a small set of older scheduled executables that have not been retired.
  • Stripe-Connect-style marketplace integration for payments and payouts; transactional email provider; SMS gateway; geocoding service.
  • Single deployable web project carries public, provider, and admin surfaces. Services library exists but is not authoritative — business logic is also in controllers, SQL views, HTML helpers, and jobs.
  • Manual deployment with a wiki checklist; staging exists but is not production-faithful; local development requires a sanitised snapshot and per-developer ritual; no end-to-end safety rails around the hardest business flows.
The same conceptual rule (e.g. ServiceRequest status) is encoded as integer enum in C#, string in export pipelines, CSS branch in Razor views, and filter condition in finance SQL views. The application contains behaviour in more places than the team can name confidently.
NaboWorks A/Spage 9 of 34
Page 10

Findings — overview

Seven findings, ranked by relevance to the modernization decision. Each carries severity and confidence; full pages follow.

Finding 1
Business behaviour is scattered across many writes · Blocking · High
Finding 2
Money movement crosses controller, SQL, and export paths · Blocking · High
Finding 3
Admin override paths bypass validation · High · High
Finding 4
Provider eligibility is implemented in three places · High · High
Finding 5
Payout batch retry can double-pay · High · High
Finding 6
No production-faithful staging; no end-to-end safety rails · High · High
Finding 7
Knowledge is concentrated in two people · Medium · High
NaboWorks A/Spage 10 of 34
Page 11

Finding 1 — Business behaviour is scattered across many writes

BlockingConfidence: High

PlainNaboWorks' core business rules — what counts as an open request, who can offer on it, when work is done, when payout is allowed, when a customer is refunded — are not implemented in one place. The same rule lives in C# controllers, in service classes, in SQL views, in stored procedures, in admin override screens, and in scheduled jobs. A frontend-first rewrite would not surface those rules; it would carry them forward partially, into a new system that does not yet know them.

TechnicalServiceRequest status is encoded as an integer enum in C#, a string in export pipelines, a CSS branch in Razor views, and a filter condition in finance SQL views. Provider eligibility (Finding 4) depends on Provider.IsActive, ComplianceDocument expiry, the complaints/quality table, and a hidden-hold state set via AdminAction. Notification timing is decided by a mix of Hangfire jobs and one older scheduled executable. There is no canonical writer for any of these rules.

Business impactThis is the load-bearing reason gradual extraction is not the safest primary path. A migration that leaves rules in their current places quietly inherits the scatter; the new system disagrees with the old on edge cases finance and support care about, and there is no single owner to fix.

  • Trace #1 walks RequestController.Create + Submit through controller validation, EF writes, Hangfire enqueue, and the FinanceMonthlyPayables SQL view.
  • Trace #2 shows the eligibility decision branching across four data sources.
  • FinanceMonthlyPayables filters status by string while ServiceRequest.Status is an integer enum; the mapping lives only in the view definition.

RecommendationChoose controlled twin replacement (Path B). The first work in the Parallel Replacement Beta is business-rule mapping — naming each scattered rule, choosing its single canonical implementation in the new spine, and recording the parity decision per rule.

Risk if ignoredThree months in, the new system disagrees with the old on a money- or trust-affecting edge case. There is no single writer to fix. The migration's credibility erodes from inside the team before it has even reached customers.

NaboWorks A/Spage 11 of 34
Page 12

Finding 2 — Money movement crosses controller, SQL, and export paths

BlockingConfidence: High

PlainWhen NaboWorks calculates an invoice, takes a customer payment, withholds a provider's share, processes a refund, or runs a payout batch, the numbers travel across multiple places that don't fully agree on how to compute them. A small change in one place can produce a number that looks correct everywhere except finance.

TechnicalInvoice totals are computed in InvoicingService.Calculate, but adjustments can be applied in admin controllers, and finance reports re-aggregate via SQL views with their own commission and VAT rules. Refunds debit the platform balance and do not automatically reverse downstream provider transfers. ProviderPayout state is mutated in a scheduled batch job that holds no idempotency key (Finding 5). A partial failure mid-batch leaves transfer state inconsistent with persisted payout state.

Business impactMoney mistakes cost trust faster than feature lateness. Provider trust is the platform's most fragile asset, and money mistakes hit it directly.

  • InvoicingService.cs:89 vs the FinanceMonthlyPayables SQL view — overlapping commission/VAT logic.
  • Trace #4 (invoice/payout) end-to-end walk.
  • PayoutBatchJob.Run (lines 120–180) — loop without idempotency key.
  • An anonymised partial-refund support ticket where finance had to manually withhold against a provider balance because the platform did not reverse the downstream transfer.

RecommendationMoney movement is the highest-priority preserve-exactly item in the parity map. Reconciliation reports between old and new must cover commission, VAT, payout amount, and refund unwinding for at least one full month of historical traffic before any cutover decision. Finance and payout parity are reserved for Stage 3 — the Beta does not migrate financial data.

Risk if ignoredA cutover with an undetected parity gap in money movement is, in effect, a rebate or overcharge to providers and customers. The earliest version of this risk is provider mistrust, which a marketplace cannot easily recover from.

NaboWorks A/Spage 12 of 34
Page 13

Finding 3 — Admin override paths bypass validation

HighConfidence: High

PlainNaboWorks' support team can directly change request status, reassign categories, archive records, apply credits, and resend confirmations. These actions write an audit row, but they can skip the same validations the public and provider flows rely on. Support knows which overrides are safe; nobody has written that knowledge down.

TechnicalAdmin actions in Areas/Admin/*Controller mutate ServiceRequest, Booking, Invoice, Payment, and ProviderPayout via service calls or, in some places, direct EF context updates. There is no canonical invariant that "no admin action may persist a state the public flow would reject". The result: any modernization that captures only the public-flow state machine fails on day one of staff use, because the operational state machine is wider.

Business impactOperational authority lives in unwritten rules. A new system that rejects support's daily workarounds without replacing them quietly forces support back to spreadsheet operations and erodes the apparent gain of the migration.

  • AdminAction row inspection over the last 12 months — distribution of override types.
  • Five admin controller methods sampled; three perform EF mutations without a service-layer validation step.
  • One admin route reopens a closed request without firing the corresponding RequestStatusHistory event.

RecommendationInventory admin actions. Classify each as (a) safe and required, (b) dangerous-but-needed, (c) dead. Map the operational state machine separately from the public-flow state machine, and confirm both with the operations manager before migration begins.

Risk if ignoredThe new system rejects support's daily workarounds. Support reverts to spreadsheets and ad hoc database fixes. The migration is technically correct and operationally broken.

NaboWorks A/Spage 13 of 34
Page 14

Finding 4 — Provider eligibility is implemented in three places

HighConfidence: High

PlainWhether a provider can see a new request, submit an offer, get paid out, or be temporarily held depends on three or four different data sources read in different combinations. The team cannot reliably state, today, why a particular provider is not seeing demand right now. A modernization that does not consolidate eligibility ships the same uncertainty into the new system.

TechnicalEligibility checks read Provider.IsActive, ComplianceDocument.ExpiryDate per category, the ProviderQuality complaints table, and a hidden-hold state implemented via AdminAction rows. Different code paths read different combinations. There is no single EligibilityService that owns the decision.

Business impactProvider success and trust spend operational time diagnosing eligibility cases. A consolidation here is one of the most valuable structural improvements available — the kind of thing the controlled-twin path makes natural and the gradual-extraction path tends to defer indefinitely.

  • Trace #2 (provider offer submission) — eligibility decision branches across four data sources.
  • Provider.cs and EligibilityService.cs source review — service exists but is not the only writer of the decision.
  • Open trust ticket where the engineering lead and provider success disagreed for three days about why a specific provider was not receiving requests.

RecommendationOne canonical ProviderEligibility decision in the new domain spine, reading from a single source of truth. Map the four current inputs onto it explicitly. Mark the hidden-hold state as a deliberate input, not an accidental one.

Risk if ignoredThe new system carries the four-source pattern forward. Trust team continues to debug eligibility cases manually. The structural problem is preserved with a fresh coat of paint.

NaboWorks A/Spage 14 of 34
Page 15

Finding 5 — Payout batch retry can double-pay

HighConfidence: High

PlainIf the payout batch job fails partway through, retrying it can pay a provider twice for the same job. The job has no marker for what it already finished.

TechnicalPayoutBatchJob.Run writes to ProviderPayout and emits transfer-API calls in the same loop without an idempotency key per (date, provider-id) pair. Transfer IDs are not persisted to ProviderPayout before the next iteration begins. Partial failure leaves no resume marker.

Business impactSame class of risk as Finding 2 (money movement) but localised to one job. The double-pay scenario has not occurred in the 30 days of logs reviewed; it is reachable.

  • PayoutBatchJob.cs:120–180 — loop without idempotency key.
  • Transfer IDs returned by the payment-provider client are not persisted to ProviderPayout before the next iteration.

RecommendationAdd an idempotency key per (payout-batch-date, provider-id) stored on ProviderPayout. Retry on the key, not on the input. Apply this in the new system from day one and apply it to the old system as a defensive prerequisite — it is not modernization-dependent and should not wait for the migration.

Risk if ignoredA failed batch produces duplicate transfers. Finance recovers manually. Provider trust is dented and the migration carries blame for a defect that predates it.

NaboWorks A/Spage 15 of 34
Page 16

Finding 6 — No production-faithful staging; no end-to-end safety rails

HighConfidence: High

PlainNaboWorks today cannot reliably exercise its hardest business flows in a non-production environment. Change-safety is currently human caution, not infrastructure. Any modernization plan that depends on "we'll test it in staging" starts from a false premise.

TechnicalStaging exists but is not production-faithful. Payment-side behaviour is only partially realistic. Local development requires a sanitised snapshot and per-developer ritual. The test suite covers pricing, category matching, and report calculations — useful, but far from end-to-end. The hardest flows (request creation, provider matching, admin override, invoice/payout, reminder/closure) have no integration-level safety rails.

Business impactWithout production-faithful staging, parity verification — the safety mechanism of the controlled twin path — is harder to perform. This finding constrains the order of work and is the single trigger that downgrades the recommendation to stabilise-first if it proves worse than this report assumes.

  • Engagement-basis interview with the QA / ops hybrid.
  • Deployment checklist review (a wiki page, not an automated pipeline).
  • Existing test suite inventory.

RecommendationTreat production-faithful staging as a precondition of the Parallel Replacement Beta, not as a phase-two improvement. Build a safe data-snapshot pipeline early in month one. Without these, the recommendation downgrades to stabilise-first before further replacement work.

Risk if ignoredA Parallel Replacement Beta is delivered, but parity cannot be verified. The transition phase becomes a production-only debugging exercise — exactly what this report exists to avoid.

NaboWorks A/Spage 16 of 34
Page 17

Finding 7 — Knowledge is concentrated in two people

MediumConfidence: High

PlainNaboWorks' technical and business knowledge sits with two people: the engineering lead (the code) and the operations manager (the business). Any modernization that depends on extracting that knowledge into shared documentation has to budget time for that extraction explicitly.

TechnicalThe engineering lead has ten years inside the system and personally signs off most production deploys. The operations manager owns most operational edge cases. Two developers are still ramping; the QA / ops hybrid is the single point of operational deployment knowledge. The bus factor for the system today is low.

Business impactModernization, by removing the system, removes the artefact those two people use as their shared memory. If their tacit knowledge is not extracted explicitly into the parity map, parity is fundamentally guessed.

  • Team interviews.
  • Commit history showing concentrated authorship in the highest-risk modules.
  • Practical experience that questions in any of the trace paths quickly route to one of these two people.

RecommendationAllocate dedicated interview time per week with both during the Parallel Replacement Beta. Treat their tacit answers as load-bearing inputs to the parity map. Document the eligibility, status, and override rules they describe; have both confirm the written version.

Risk if ignoredOne of them changes role, gets sick, or leaves; the parity map cannot be confirmed; modernization is set back by months.

NaboWorks A/Spage 17 of 34
Page 18

Change-safety plan

What must be true before any work in the recommended package can land safely.

Flows to protect first, in order
  1. Request creation. Characterisation tests through the real database, asserting one row per logical submission and no notification side effects before commit.
  2. Provider matching and offer submission. Characterisation tests against frozen (request, provider) input pairs, plus a concurrency test for the close race.
  3. Invoice and payout. Approval test on payout-batch output for captured input days; idempotency invariant per (date, provider-id).
  4. Admin overrides. Approval tests on the override actions most likely to leak into states the public flow cannot reach.
  5. Reminder and closure. Characterisation tests with a fixed clock and known fixture day.
Tests to add first
  • Integration tests through the real Facade and real database (Testcontainers or equivalent).
  • Approval tests on financial outputs frozen as the spec.
  • Reconciliation reports comparing old-system and new-system outputs row-by-row over one month of historical traffic before any cutover decision.
False-green warning. A green test does not, on its own, prove behaviour is protected. Each safety claim in this report has a trust basis: high-trust if anchored against an independent source (production data, finance exports, real provider statements); marked otherwise. Reports generated by the same code they are supposed to verify do not count as independent.
NaboWorks A/Spage 18 of 34
Page 19

Business rules discovered — where they live today (1 of 2)

Six business rules emerged as the load-bearing behaviour the modernization has to preserve. For each, this page and the next name the plain-English meaning, the places the rule currently lives, the proposed home in the new spine, the parity decision, and the risk if the rule is not preserved cleanly. The list is not exhaustive; it is the smallest set the engineering and operations owners agreed cannot be ignored.

Rule 1 — Provider eligibility to see a request
Plain English
A provider should see a request only if active, approved for the category, inside service area, not blocked by trust or compliance, and allowed by the current operational state.
Where it lives today
Provider-area controller filtering; the matching job; ComplianceDocument validity check; the provider service-area query; the admin trust-hold state set via AdminAction rows.
Future home
ProviderEligibilityService — a single application use case in the new backend, reading from one source of truth per input.
Parity decision
Preserve exactly first; improve structure inside the new home.
Risk if missed
Providers see work they should not, or stop receiving work they should — and the trust team has no single owner to debug it (Finding 4).
Rule 2 — Request publication and provider notification
Plain English
A request should become visible to providers only after required information is valid and the request is in a publishable state. The matching providers should then be notified.
Where it lives today
Public RequestController; Razor / UI restrictions; ServiceArea match logic; RequestStatusHistory write; the notification job.
Future home
RequestPublicationWorkflow — an application service that owns the publication transition and the notification trigger.
Parity decision
Preserve behaviour; improve structure (idempotent enqueue, single status writer).
Risk if missed
Providers notified too early, duplicate requests appear, support absorbs avoidable tickets (Trace #1, Finding 1).
Rule 3 — Offer becoming booking
Plain English
When a customer accepts an offer, the system creates a booking, closes or updates competing offers, writes the correct status history, and prepares later reminders and completion steps.
Where it lives today
Customer controller; BookingService; Offer status update; the notification job; Razor state display.
Future home
BookingAcceptanceFacade with an explicit BookingWorkflowService inside.
Parity decision
Preserve exactly for state transitions; improve structure internally.
Risk if missed
Two providers may believe they are selected, reminders fire incorrectly, or support manually repairs the state (Finding 1).
NaboWorks A/Spage 19 of 34
Page 20

Business rules discovered — where they live today (2 of 2)

Rule 4 — Admin reopen and support override
Plain English
Support can reopen or correct workflow states when real operations do not fit the normal path. The new system must allow this authority while constraining and documenting it.
Where it lives today
Admin controller; direct EF mutations in some places; AdminAction row write; RequestStatusHistory sometimes missing; the reminder job may re-fire after a reopen.
Future home
AdminOverrideWorkflow with an explicit allowed-override-types list. Each override produces a state the public flow can recognise.
Parity decision
Preserve required authority; constrain and document it.
Risk if missed
The new system blocks support's real work, or creates states the rest of the system cannot handle (Finding 3).
Rule 5 — Reminder and closure timing
Plain English
The system should move stale or pending work forward and notify the right people at the right time.
Where it lives today
Scheduled job; status fields; the Notification table; review and completion windows; admin reopen interactions.
Future home
ReminderClosureWorkflow as a scheduled-job boundary, with a single owner for timing decisions.
Parity decision
Preserve behaviour where business-critical; deliberately simplify where confirmed safe.
Risk if missed
Old reminders fire at wrong times; support workload increases; customers and providers receive confusing messages.
Rule 6 — Review and dispute window
Plain English
Reviews and disputes should follow a timing window after completion. Both parties should review without seeing the other's response until the window closes or both have replied.
Where it lives today
Completion workflow; scheduled job; the Review table; the admin moderation screen; the provider trust process.
Future home
ReviewPublicationWorkflow.
Parity decision
Preserve exactly until business confirms a change. Review timing is a trust contract, not a template detail.
Risk if missed
Provider trust changes, disputes are handled inconsistently, reviews publish too early or too late.
These six rules are the parity-map seed. Every rule will be confirmed with a named owner before significant code is written, and the parity decision will be revisited at Beta delivery.
NaboWorks A/Spage 20 of 34
Page 21

Modernization decision — Path A vs Path B vs stabilise-first

We recommend controlled twin replacement because the current system has too many hidden dependencies for a broad gradual-extraction path to be priced or promised responsibly as the primary path.

The risk is not that MVC is old. The risk is that important behaviour is spread across controllers, Razor views, SQL views and stored procedures, scheduled jobs, admin screens, exports, and staff memory. The six rules on the previous two pages are the load-bearing examples. Findings 1–4 ground the same point.

Path A — Gradual extraction (Strangler Fig)

Gradual extraction can be done. It is not automatically cheaper or safer than the alternative. For NaboWorks specifically, it carries four problems this report has not solved: (1) the new slice writes to the same SQL Server database the old MVC writes to, and there is no canonical state machine for ServiceRequest, Offer, or Booking — both systems would write to the same rows with different rules (Finding 1); (2) the existing Forms-auth session would have to be shared with, or duplicated by, the new slice; (3) the operational state machine (admin overrides, Finding 3) is wider than the public state machine, and an extracted slice has to satisfy both; (4) any extracted slice that touches a money-relevant table inherits the no-idempotency-key risk from Finding 5 until it is fixed in the old system first. A safe Path A starts with one read-only slice — customer history, provider profile viewing, brochure-style admin pages — and proves the boundary before anything else moves.

Path B — Controlled twin replacement (recommended)

Controlled twin reduces risk because: the old system keeps running; rules are mapped first; the new system is built beside the old; representative operational data is migrated and anonymised; old and new behaviour can be compared; staff can preview and test before cutover; production responsibility does not move during the Beta. Cutover is a separate decision, made on Stage 3 evidence.

Stabilise-first — the downgrade option

If Finding 6 (no production-faithful staging) proves worse than this report assumes — for example, if a safe data-snapshot pipeline cannot be built in the first month, or if production data masking is blocked by legal or compliance constraints not yet surfaced — then the responsible recommendation is stabilise-first before any replacement work begins. Trusted staging path, safe data-snapshot pipeline, characterisation tests around the most fragile flows, documented deployment that does not depend on one engineer's memory.

Recommendation: controlled twin replacement (Path B), with gradual extraction reserved for narrow read-only slices where evidence shows it is safe. Stabilise-first held in reserve as the explicit downgrade trigger.
NaboWorks A/Spage 21 of 34
Page 23

Implementation strategy

What must be made observable, testable, reversible, and confirmable before the work is safe — written against NaboWorks specifically, not against a generic legacy migration.

Sequencing — why this order, for this system

Rule mapping first, because Finding 1 says rules are scattered and Finding 7 says the people who know them are two. Code spine second, because the spine is the home each scattered rule moves into. Anonymised operational data third, because without it the new spine is exercised against fictional shapes and the parity comparison is theatre. Staff preview fourth, because the operations manager and the head of provider trust have to see the new system handle their real-shaped cases before any production-touching decision. Each step exists because the previous one failed in the live system; skipping it imports the same failure into the new one.

Test strategy — anchors over coverage

NaboWorks already has tests on pricing, category matching, and report calculations. They are useful and they are not safety. The replacement test strategy distinguishes by what each test is anchored against. High-trust: characterisation tests that compare the new system's output against captured production output for the same input — payout-batch days, AdminAction sequences, real provider eligibility decisions, finance export rows. Low-trust: tests where the expected value was generated by the same code path the test exercises. The change-safety page lists which flows get characterisation tests first; tests outside that list do not count toward the parity claim.

Reversibility — Beta vs Transition

During the Beta, reversibility is automatic: the old system carries production. There is no rollback question, only a comparison question — does the new system produce the same shape on the same input. During Transition, the rollback question becomes real and is treated as a named acceptance item. NaboWorks's specific cutover risk is that the operational state machine (the wider one staff use through admin overrides — Finding 3) is harder to reverse than the public state machine. The Transition cutover plan and rollback plan have to address the override paths explicitly, not only the public flow.

Confirmation — who signs off what

Smallbox cannot confirm parity decisions for NaboWorks. The technical owner confirms code and schema decisions. The operations manager confirms request flow, offer-to-booking, admin override, and reminder semantics. The head of provider trust confirms eligibility and trust-state decisions. The head of finance confirms anything in the money-movement parity map — but the Beta excludes finance, so finance sign-off is reserved for Stage 3. Parity-map decisions are signed by the named owner before significant code is written; Beta acceptance is a walkthrough, not a tick-box.

Named downgrade triggers

The recommendation downgrades to stabilise-first if any of the following becomes true and cannot be repaired inside the first month: a safe production-shaped data path cannot be produced for the operational tables (Finding 6, environment page); the four eligibility inputs cannot be reproduced from snapshot data (Finding 4); the operations manager or head of provider trust cannot be available for the parity-map confirmation cadence the Beta requires (Finding 7). The downgrade is named in writing when it triggers, not implied by drift.

NaboWorks A/Spage 23 of 34
Page 24

Why this architecture, in business terms

Separation of concerns is not the goal. The goal is that each NaboWorks rule has a single owner instead of living in five places at once. The architecture below exists for that reason — every piece is chosen against a specific NaboWorks symptom, not because it is good practice in the abstract.

DTOs at the API boundary

Today the shape of a ServiceRequest is reconstructed from a Razor partial, an export pipeline string, and the FinanceMonthlyPayables SQL view — each of which encodes status differently (Finding 1, system page). A DTO names that contract once. The Razor partial, the export, and the finance view become consumers of the same shape, not parallel definitions of it.

Facade / application methods

Today "accept this offer" is reconstructed from customer-controller branches, a BookingService call, an Offer status update, a notification job, and a Razor state display (Rule 3). The replacement names it once: BookingAcceptanceFacade. A future developer reading that one method sees the whole use case; they do not have to walk five files to find out what acceptance does.

Business services

Today provider eligibility is computed from Provider.IsActive, ComplianceDocument expiry per category, the ProviderQuality complaints table, and a hidden-hold state set via AdminAction — read in different combinations across the provider area, the matching job, and admin views (Finding 4). ProviderEligibilityService becomes the single writer of that decision. The four inputs become its named inputs. Trust ticket disagreements about "why is this provider not seeing demand" stop being three-day debates.

Repositories

Today Razor views read EF entities directly — changing a column shape can break a partial in a place no one is looking. Repositories isolate that read so the persistence shape can change without breaking presentation. The same boundary makes SubWebsite-style scoping (or, for NaboWorks, provider-area scoping) consistent — the filter lives where the query lives, not in whichever controller called the EF context.

Integration boundaries for side effects

Today email, SMS, payment-provider transfers, scheduled jobs, and accounting exports each have their own ad-hoc seam. In local development they have to be paused manually or they fire fake side effects in the wrong order (Finding 6, environment page). The replacement gives each one a named boundary — Email, SMS, Payment, Export, Job — that can be stubbed, captured, or redirected in non-production. The Beta needs this not as good practice but as a precondition: without it, a hosted Beta cannot be exercised by staff without sending real notifications.

Swagger / OpenAPI

Today reading the system's contract requires grepping routes across the public, provider, and admin areas. A new developer — or a new internal tool, or an integration partner — can read the new system's contract directly. This matters specifically for NaboWorks because of Finding 7: knowledge concentration. A documented API surface is a way to externalise some of what currently lives in the engineering lead's head.

Separate frontend and backend repositories

Today the single MVC project carries public, provider, and admin surfaces in the same deployable, with Razor mixing UI and business decisions. The CSS branch in a Razor view that decides whether a request looks "open" is the same kind of decision the finance SQL view makes when it filters by status. Splitting frontend and backend removes the path of least resistance for new business rules to land in a view again. The boundary is enforced by the repository structure, not by team discipline.

The point is not the pattern itself. The point is that future changes to NaboWorks should not require rediscovering the same rule in five places.
NaboWorks A/Spage 24 of 34
Page 25

Environment and data strategy

The Beta and the eventual Transition both depend on production-shaped data and neutralised side effects. The current state of NaboWorks does not yet provide either; making the data and environment path safe is part of the engagement, not an assumption left for later.

Current state
  • A staging environment exists, but it is not production-faithful. Payment-side behaviour is partially realistic at best.
  • Local development requires a sanitised snapshot and per-developer ritual.
  • There is no documented data-snapshot or anonymisation pipeline. Snapshots are produced ad hoc by the QA / ops hybrid (Finding 7).
  • Jobs in local environments must be paused manually or they fire fake side effects in the wrong order.
What the Beta requires
  • Operational shape mirrored from production for Customer, Provider, ServiceRequest, Offer, Booking, and selected AdminAction / AuditLog rows. The Beta migrates business shape, not personal identity.
  • Data masking and pseudonymisation defined per category — customer contact data, message-thread content, admin notes — before data is moved. Provider payout details and finance data are not migrated to the Beta.
  • Side effects neutralised in non-production: email, SMS, payment-provider transfers, webhooks, scheduled jobs, accounting export writes. Stub or capture, never live.
  • Provider-side trust events (suspensions, document expiry) reproducible from snapshot, not invented.
What the Transition requires beyond the above
  • Reconciliation against captured production traffic (one month minimum) for money movement, eligibility, status semantics, and notification timing.
  • Shadow validation, where the access boundary allows: real production traffic mirrored against the new system without affecting customers.
  • A cutover plan and a rollback plan, both reviewed with operations before either is exercised.
Downgrade trigger. If a safe data-snapshot or anonymisation path cannot be built within the first month of Stage 2 — for legal, compliance, or technical reasons not yet surfaced — the recommendation downgrades to stabilise-first before further replacement work begins. Building the snapshot path becomes the deliverable instead of building the new spine on top of it.
NaboWorks A/Spage 25 of 34
Page 26

Client responsibilities and environment boundary

The Beta is a shared deliverable. Smallbox builds the technical boundary; the client provides the access and participation that makes it meaningful. Each side's responsibilities are named here so the engagement does not stall on assumed availability.

Smallbox is responsible for

Setting up the non-production Beta environment required for the agreed scope — the clean application spine, the seeded or mirrored data approach, the modules in scope, the documentation, the parity-comparison machinery, and the hosted Beta on Smallbox-managed Hetzner.

The client is responsible for
  • Repository and schema access.
  • Existing deployment and runtime information — how the live system is deployed, what services it depends on, where credentials and secrets live.
  • Infrastructure permissions where needed for staging or beta hosting.
  • Safe production-shaped data access, or permission to build an anonymised snapshot path.
  • A named technical owner who can answer system questions and approve technical decisions.
  • A named operations / support owner who can confirm business behaviour and parity decisions.
  • A finance / trust contact where money or provider trust is in scope.
  • Timely answers to business-rule questions surfaced during the engagement.
  • Staff participation in beta walkthroughs.
  • Staff participation in final testing and sign-off.
Smallbox cannot sign off business parity alone. Business-rule confirmation and final operational acceptance must come from the client's named owners.
Final confidence month

Smallbox can provide technical evidence, comparison reports, tests, and walkthroughs. The client must confirm business behaviour and sign off operational acceptance. The confidence month is collaborative by design; if the client cannot allocate operational time to it, the cutover decision should be deferred rather than rushed.

NaboWorks A/Spage 26 of 34
Page 27

Stage-gated execution model

Work advances by gates, not vibes. Stages do not advance because time has passed; they advance because the previous stage's exit criteria are met.

Each stage has explicit exit criteria. Stage 1 is the System Report itself — this document. Stage 2 is the Parallel Replacement Beta. Stage 3 is Parity + Transition. Each stage's exit criteria are the entry criteria of the next. The week-by-week and month-by-month detail in the next two pages sits inside this gate structure.

Gate 1 — Stage 1 → Stage 2
  • The System Report is delivered, read, and accepted.
  • The recommendation is controlled twin replacement — not gradual extraction or stabilise-first.
  • Code, schema, and operations access for Stage 2 are confirmed.
  • Business-rule owners are named: technical owner, operations owner, finance / trust contact.
  • A production-shaped data path is either available or scoped as Stage 2 month-1 work, with the downgrade trigger acknowledged.
Gate 2 — Stage 2 → Stage 3
  • The Beta is deployed on the Hetzner non-production environment client staff can log into.
  • The agreed Beta scope has been demonstrated end-to-end.
  • The parity map is confirmed by the operations stakeholder named at intake.
  • Architectural decisions, the data approach, and remaining open questions are documented.
  • An acceptance walkthrough has been completed with the technical and operational owners.
Gate 3 — Stage 3 → cutover
  • Migration scripts run end-to-end against a production-shaped snapshot.
  • Reconciliation reports cover money movement, eligibility, status semantics, and notification timing for at least one month of historical traffic.
  • Staff scenario testing has surfaced and resolved the hardest weekly cases.
  • Cutover plan and rollback plan are documented and reviewed.
  • An acceptance walkthrough has been completed.
Time alone does not advance a gate. Month 6 is reserved for confidence; if confidence is not present, cutover is deferred. The contract is the gate, not the calendar.
NaboWorks A/Spage 27 of 34
Page 28

Execution shape — Parallel Replacement Beta

Month 1 is broken into four weeks because that is where most generic plans go wrong. Months 2 and 3 are tighter because the work follows from what month 1 produced.

Week 1 — Access and system entry

Repository, schema, runtime / deployment notes, job schedule, external integrations, current staging and local-development setup, the read-only boundary, named owners. Output: an access and evidence-boundary note, plus the first system map.

Week 2 — Side effects and data

Email, SMS, payment provider, webhooks, scheduled jobs, accounting exports, notification queues. For each: define what is disabled, stubbed, captured, or redirected in non-production. Choose the concrete prototype-migration tables (operational entities, not finance). Define anonymisation and pseudonymisation rules per category. Output: side-effect map, prototype-migration plan, anonymisation rules.

Week 3 — Flow and rule discovery

Trace customer request creation, provider offer, offer-to-booking, admin override, reminder and closure. For each trace: entry point, tables read and written, status changes, side effects, confirming owner, failure modes. Output: flow traces and the first rule-location map (the parity-map seed on the previous two pages).

Week 4 — Architecture and Beta scope

Classify rules into the five buckets — preserve exactly, preserve behaviour and improve structure, change deliberately, do not copy, unknown and must be confirmed. Choose the Beta screens and modules. Design the new backend, frontend, and database spine around the discovered rules, not around screens. Output: parity map, Beta scope, architecture spine, first implementation backlog.

Month 2 — First modules and migrated data

Build the backend API, the frontend shell, the database schema, the prototype migration scripts, the seed and simulation scripts, and the first UI screens — using migrated and anonymised operational data. Begin the old / new comparison shape. Expose a staff-preview environment for the operations manager and the head of provider trust.

Month 3 — Beta delivery

Complete the agreed Beta scope. Deploy to the Smallbox-managed Hetzner non-production environment. Provide Swagger / OpenAPI access. Walk through the Beta with NaboWorks staff. Document architectural decisions, the data path, and the open questions remaining. Acceptance walkthrough with the technical and operational owners.

NaboWorks A/Spage 28 of 34
Page 29

Optional continuation — Parity + Transition

If the Beta is accepted, the next package brings it toward production confidence. Approximately €30,000 over approximately 3 more months. Decided after the Beta is delivered and reviewed; not committed up front.

Month 4–5 — Workflows and validation machinery
  • Complete main workflows for the agreed scope.
  • Migration scripts for finance and payout data — runnable end-to-end against a snapshot of production-shaped data.
  • Reconciliation reports across financial flows, eligibility, status semantics, and notification timing.
  • Staff scenario testing — the hardest weekly cases run through both systems in parallel.
  • Parity verification.
  • Bug fixing.
Month 6 — Confidence month
  • Edge cases and missing details surfaced during scenario testing.
  • Cutover plan documented and reviewed.
  • Rollback plan documented and reviewed.
  • Acceptance walkthrough with technical and operational owners.
The Beta builds the replacement candidate. The Transition proves whether it can safely replace the old system. The confidence month should feel controlled, not dramatic.
NaboWorks A/Spage 29 of 34
Page 30

Acceptance criteria

What "delivered" means at each stage. These criteria are the contract between the recommendation and the work — not aspirational language. Payment shape is secondary; acceptance is what binds.

Beta acceptance — end of Stage 2
  • Agreed beta scope deployed on the Hetzner non-production environment.
  • Scope demonstrated end-to-end to the technical and operational owners.
  • Parity map documented and confirmed by the operations stakeholder named at intake.
  • Documentation describes architectural decisions, the seeded / mirrored data approach, and the open questions remaining.
  • Client staff can log in and exercise the beta scope independently.
Transition acceptance — end of Stage 3
  • Migration scripts run end-to-end against a snapshot of production-shaped data.
  • Reconciliation reports cover money movement, eligibility, status semantics, and notification timing for at least one month of historical traffic.
  • Staff scenario testing has surfaced and resolved the hardest weekly cases.
  • Cutover plan and rollback plan documented and reviewed.
  • Acceptance walkthrough completed with the technical and operational owners.
This is an illustrative recommendation for a sample client, not a universal promise. If access, data realism, or business-rule confirmation prove worse than this report assumes, the recommendation downgrades to stabilise-first before further replacement work begins.
NaboWorks A/Spage 30 of 34
Page 31

AI collaboration strategy

Smallbox uses AI assistance — primarily Claude — for code-level work. This page names where that assistance helps the NaboWorks engagement specifically, where it does not, and the line that does not move.

Where AI helps inside this engagement
  • Tracing flows across the existing MVC project — controllers, services, EF, jobs, Razor, SQL views — and producing the first written description of where each rule reads and writes. Useful for Findings 1, 3, and 4, where the scatter is the problem.
  • Drafting characterisation tests that compare new-system output against captured production output for the same input — payout-batch days, AdminAction sequences, eligibility decisions.
  • Drafting DTOs and repository signatures from the EDMX schema once the parity decision is made.
  • Writing reconciliation queries between the new database and finance SQL views (Stage 3, when finance is in scope).
  • Maintaining the working business dictionary, the parity map, and the open-question list as living documents rather than meeting artefacts.
Where AI does not help, in this engagement
  • Inventing parity decisions for the six rules. The decision "preserve exactly" vs "preserve behaviour, improve structure" vs "deliberately simplify" comes from the operations manager, the head of provider trust, and the head of finance — not from a model reading the codebase.
  • Confirming what is dead. The seasonal campaign subsystem and the legacy provider-app endpoints are confirmed dead in interview, not by static analysis.
  • Reading reconciliation output. A model can produce the report; humans on both sides confirm whether the variance is explainable.
  • Deciding when to downgrade to stabilise-first. That is a Smallbox engineering call confirmed with the client, not a model output.
Why this matters for NaboWorks specifically

Finding 7 names the central knowledge problem: the engineering lead and the operations manager hold most of what the system actually does. AI assistance is one of the more useful tools for externalising that knowledge into written artefacts the rest of the team can read — flow traces, rule maps, characterisation tests, dictionary entries. Used this way, AI shortens the time from "two people know" to "the parity map knows". Used the wrong way — to invent rules the people never confirmed — it accelerates exactly the problem the migration is trying to solve.

The same engagement done without AI would still need a parity map, a test-trust plan, named owners, and stage gates. AI accelerates the work inside that discipline; it does not replace it. A claim by a model that a flow has been traced does not, on its own, count as evidence in the parity map.
NaboWorks A/Spage 31 of 34
Page 32

Closing recommendation

Our recommendation is controlled twin replacement.

The reasoning is on the previous pages: business behaviour is spread across controllers, Razor views, SQL, jobs, admin screens, exports, and staff memory. A frontend-first rewrite would carry those rules forward partially. A controlled twin maps them first, builds beside the old system, and verifies parity against migrated and anonymised operational data before any production decision.

If NaboWorks wants to explore gradual extraction instead, the next step is not to start coding. The next step is to choose one candidate slice and prove that it has a safe boundary. Smallbox can do that analysis, but we would not recommend starting with booking, provider matching, admin override, or finance until rule locations and side effects are better understood.

If access, environment data, or business-rule confirmation prove worse than this report assumes, the recommendation downgrades to stabilise-first before any replacement work begins. That downgrade is named, not silent.

NaboWorks A/Spage 32 of 34
Page 33

Open questions

What remains uncertain or unresolved at delivery of this report. These are the questions the Parallel Replacement Beta has to surface evidence on; none of them block the decision to proceed, but each is load-bearing for the parity map.

  1. Which provider commission exceptions exist in production? Suspected from interview; not visible in code. Confirmation required from the head of finance with finance export data, not from engineering.
  2. Which VIP customer credit and cancellation rules live in staff habit rather than code? Confirmation required from the operations manager with twelve months of AdminAction data.
  3. Which seasonal routing tweaks survive in production today? Confirmation required from the head of provider trust with three months of matching-decision data.
  4. Is there a finance-side spreadsheet pipeline bridging the accounting export with manual reconciliation, treated as "normal"? The engagement did not surface one; the head of finance interview did not deny one. Confirmation required.
  5. Do any of management's "trusted" reports rely on legacy definitions tied to archived statuses? Requires reading the actual SQL view definitions against the operational metric handbook.
  6. Can authentication and session realistically be shared in the hybrid period if a Path-A read-only slice ships first? Open; specific to the slice chosen.
  7. Which data can be mirrored or seeded safely for the Beta, and which must remain read-only until cutover? Specifically open for finance and provider-payout tables — the Beta's working assumption is that finance is excluded.
NaboWorks A/Spage 33 of 34
Page 34

Appendix — trace paths

Five flows reviewed end-to-end. Each is the basis of one or more findings.

Trace #1 — Public customer request creation

Entry: RequestController.Create + Submit. Tables touched: Customer, Address, ServiceRequest, RequestPhoto, ServiceArea, Notification, AuditLog. Failure modes: duplicate creation on repeated submission; notification side effects firing before transaction completion; postcode mismatches into a manual queue. Anchors Findings 1 and 6, and Rules 1–2.

Trace #2 — Provider offer submission

Entry: provider-area controller. Tables touched: Provider, ServiceRequest, Offer, ProviderCategory, ComplianceDocument, Notification, RequestStatusHistory. Failure modes: race condition where a provider submits an offer just after the request closes; locale-specific pricing validation; hidden provider-tier visibility rules. Anchors Findings 1 and 4, and Rule 1.

Trace #3 — Admin override / support intervention

Entry: Areas/Admin/*Controller. Tables touched: most of the core entities. Failure modes: bypassed validation leaving entities in unreachable states; obsolete reminder re-firing on reopen; off-book credits not reflected in trusted finance reports. Anchors Finding 3 and Rule 4.

Trace #4 — Invoice and payout

Entry: invoicing service plus the scheduled payout batch. Tables touched: Booking, Invoice, Payment, ProviderPayout, Review, AccountingExport, ledger-like balance tables. Failure modes: partial refund after payout; webhook re-delivery; rounding drift between application and finance SQL; missing idempotency key. Anchors Findings 2 and 5. Excluded from the Beta migration.

Trace #5 — Reminder and closure

Entry: scheduled job sweep. Tables touched: ServiceRequest, Booking, Invoice, Review, Notification. Failure modes: reminder re-firing after admin reopen; review published into wrong window; reminder before payment state has settled. Anchors Finding 1 and Rules 5–6.

NaboWorks A/Spage 34 of 34