ADEBLED Services · IT Training Division

FixIT — build plan

Monetization, super admin, and cPanel deployment for the browser-based IT support training simulator. Extends the two source specifications; replaces neither.

Stack
PHP 8 · MySQL · vanilla JS
Hosting
cPanel shared, no SSH
Payments
Paystack · NGN
Status
Planning · no code yet
01

Two rules everything else depends on

Adding payments to a training product creates two ways to ruin it. Both are avoidable, and both have to be decided now rather than patched later.

Rule one

Money unlocks content. Accuracy unlocks tiers. A subscription buys access to Tiers 2–5. The 70% accuracy gate between tiers is not for sale, at any price, to anyone. A course you can buy your way through is worth nothing to the trainee or to whoever hires them.

Rule two

The server is authoritative — now for money too. Scoring already lives server-side because trainees read your JavaScript. Prices, entitlement, and credit balances join it, because the client now has a financial reason to lie.

Practically, rule two means three things. Checkout sends a plan_code, never an amount — the price is looked up server-side. Nothing is granted when the browser returns from Paystack; the webhook is the only path that grants access. And every credit movement is an append-only ledger row, with the balance treated as a cache of it.

02

Subscriptions and credits, doing different jobs

Two currencies only work if they never overlap. The subscription is a door; credits are a vending machine.

SubscriptionCredits
BuysAccess to content (tiers)Consumables and conveniences
RenewsYes, via Paystack PlansNo — spend-down balance
Runs outAt period endWhen the balance hits zero
Unlocks a tier?Yes — the payment axisOnly a single-scenario pass
Skips the 70% gate?NeverNever
Reveals an answer?NoNo

Why credits exist at all

A wallet is only worth its complexity if it pays for something that genuinely costs you money or protects a friction you designed on purpose. Yours has four real jobs.

  • Instructor review — the real one. The task ladder already says Tier 4–5 written outputs should be graded against a rubric with borderline submissions flagged for a human. That review is a person reading an escalation note. It is the only part of FixIT with a genuine cost per use, and exactly what a credit system is for.
  • Protecting designed friction. The 30-minute retry cooldown is pedagogical. Removing it free would undermine the learning; removing it for credits makes the impatient trainee pay for their impatience and leaves the default intact.
  • Giving free users one thing to buy. Someone who finished Tier 1 but is not ready to commit to a month can spend 5 credits on a single Tier 2 fault. A far better conversion ramp than a hard wall — and it tells you which faults actually sell.
  • Making the subscription obviously better. Every plan grants a monthly credit allowance. Annual covers a certificate.
Hard list, enforced in code

Credits must never buy: hints, root causes, or accepted fixes · score, badges, or leaderboard position · tier progression · certificate eligibility. Credits pay for a certificate's issuance, never for qualifying for one.

What credits cost

ActionWhat it doesCreditsWho
instructor_reviewHuman review and written feedback on a Tier 4/5 submission, within 48h15Subscribers
certificateGenerate and issue the completion certificate25Completers
performance_reportPer-theme accuracy, command-efficiency trend, weak areas20All
scenario_pass24-hour unlock for one locked scenario5Free tier
instant_retrySkip the 30-minute cooldown after a failed fault3All
extra_attemptOne attempt beyond the daily cap2All
hint_packHints on a fault — subscribers take the score penalty and pay nothing1Free tier

That last row matters. Hints already cost score. Charging subscribers credits as well is a double penalty and reads as gouging. All of these are editable from the admin console without a deploy.

Plans

Assumption, not a recommendation

These figures are placeholders to react to. I do not know your market, your competitors, or what your existing ADEBLED students already pay. The structure I stand behind; the numbers need your judgement.

PlanPriceCeilingCreditsNotes
Free₦0Tier 1 — 6 faults3 onceNo card. Full engine, full realism.
Monthly₦6,500Tier 5 — all 3020 / moThe anchor; the curriculum is 30 days.
Quarterly₦16,500Tier 570~15% off. The realistic completion window.
Annual₦48,000Tier 5300~38% off. Certificate included.

The free tier has to be genuinely good — all six Tier 1 faults, the real terminal, byte-accurate output, streamed timings, Services console, Task Manager. A crippled demo converts nobody. Tier 1 ends at exactly the moment the product gets interesting, when distractors and wrong paths arrive in Tier 2, and that is the honest place to ask for money.

Granted credits expire at period end; purchased credits never do. Easy to explain, and it keeps your outstanding liability bounded.

03

Paystack

Four flows, one file that must be right, and two prerequisites with lead time you should start this week.

Start now — both have lead time

Live keys need a verified Nigerian business: CAC registration, a business bank account in the business name, director ID, BVN. Approval takes days. You can build and fully test everything on test keys meanwhile — but you cannot take a naira until it clears.

Confirm your host allows outbound HTTPS from PHP. Some shared cPanel plans block it by default, which silently breaks every Paystack call. It is a ten-minute check that can cost you a week if you leave it to the end.

The four flows

FlowShapeThe catch
A · First subscriptionInitialize → Paystack-hosted card page → callback → webhook grantsYou never see card data, which is what keeps you out of PCI scope
B · RenewalPaystack charges the saved card on scheduleNo user, no callback. Grant on callback only and every renewal after the first silently fails
C · Credit top-upSame as A minus the plan parameterWebhook writes a ledger row, not a subscription
D · CancellationSet cancel-at-period-end, let access run outCutting someone off mid-period for cancelling earns you a chargeback

The webhook is the whole integration

billing/webhook.php is publicly reachable, unauthenticated, and grants access. It is the most security-sensitive file in the application.

 billing/webhook.php
// NO session_start(). NO CSRF check. NO auth guard. Paystack is not a browser.
$raw = file_get_contents('php://input');

// 1. Verify the signature BEFORE parsing anything. On cPanel read the header
//    from $_SERVER — apache_request_headers() is unreliable under PHP-FPM.
$sig = $_SERVER['HTTP_X_PAYSTACK_SIGNATURE'] ?? '';
if (!hash_equals(hash_hmac('sha512', $raw, $secret), $sig)) {
    http_response_code(401); exit;   // log it, then stop. Never process.
}

$event = json_decode($raw, true);

// 2. Idempotency. Paystack WILL deliver the same event more than once —
//    that is normal behaviour, not an error. UNIQUE(provider,event_id)
//    makes the second insert fail harmlessly.
if (!webhook_claim('paystack', $event['data']['id'], $event)) {
    http_response_code(200); exit;   // already handled
}

// 3. Dispatch inside a DB transaction: grant, ledger, and event-completion
//    all commit together or not at all.
switch ($event['event']) {
    case 'charge.success':         handle_charge_success(...);  break;
    case 'subscription.create':    handle_sub_create(...);      break;
    case 'subscription.disable':   handle_sub_disable(...);     break;
    case 'invoice.payment_failed': handle_payment_failed(...);  break;
    case 'refund.processed':      handle_refund(...);          break;
}
http_response_code(200);

Non-negotiable, in this order:

  1. Verify the HMAC before parsing. Compare with hash_equals, not ==.
  2. Idempotency is mandatory, not defensive. Without the unique constraint, one retry grants two months of access or double-credits a wallet.
  3. Re-check the amount server-side. Trust the signature for authenticity, then still compare against the plan price.
  4. Never trust metadata for authorisation. Use it to find the user, then re-derive entitlement from your own tables.
  5. Log every event, verified or rejected. When a trainee says "I paid and nothing happened", this is how you answer in thirty seconds.
  6. Exclude the path from maintenance mode and every rewrite rule. A 302 on your webhook URL means Paystack marks deliveries failed.
The safety net

Webhooks get missed — a host wobble, a deploy, exhausted retries. An hourly reconciliation cron diffs Paystack's transactions against yours, verifies stale pendings, confirms expiries against Paystack before downgrading anyone, and recomputes every wallet balance from the ledger. Build it in the same phase as the webhook, not later. This is the piece that lets you sleep.

Grace, and what a downgrade must not do

A failed renewal moves the subscription to past_due with three days of full access. Card declines from insufficient balance, expired debit cards and bank downtime are routine in Nigeria and are not the trainee's fault; cutting access the instant a charge fails punishes people for their bank's problems. Email on day 0, 1 and 3, then downgrade.

Downgrading must be non-destructive. An expired trainee keeps their account, full attempt history, badges, leaderboard position, and unspent purchased credits. They lose access to Tier 2+ content and nothing else. Resubscribe three months later and everything is where they left it. Hiding progress on downgrade is the most common way subscription products lose returning customers.

04

The two-axis access model

There are two entirely separate questions to answer before showing a trainee a scenario. Conflating them is what turns a training product into a slot machine.

Trainee opens a Tier 3 fault
Axis 1 · Entitlement
Can they pay for this?
  • Active subscription tier ceiling
  • Admin grant or cohort access
  • Single-scenario credit pass
Money moves this axis
Axis 2 · Progression
Have they earned it?
  • 70% average accuracy on the tier below
  • Curriculum day unlock
  • Derived from attempts, never stored
Only skill moves this axis
Both pass → start the attempt

Both checks live in one function, can_start_scenario(), and it returns two distinct failure codes. locked_payment shows an upgrade button. locked_progression shows "You need 70% on Tier 2. You're at 61% — replay NET-202 to lift it." Showing an upgrade button to someone who simply has not earned the tier yet is the single fastest way to make training feel like a shakedown.

The escape hatch, deliberately awkward

If you need a demo account that skips progression, it is a bypass_progression flag on the user — shown as a warning badge in admin, excluded from certificates and leaderboards, and written to the audit log. Never a quiet side effect of an admin "grant access" button.

Surface separation

Three front-ends share one database and never share assets. The admin console can load 200 KB of CSS and nobody cares — you use it on a laptop, a few times a day. The trainee app must never ship a byte the simulator does not need, which means zero payment logic in the JS bundle: no price table, no plan codes, no Paystack key. Checkout is an ordinary server-rendered page. A bug in billing can then never break a scenario mid-attempt.

05

Data

The original seven tables survive intact. Twenty-four more carry money, platform settings, and administration. The full schema is importable as-is.

GroupTables
Identityusers · cohorts · login_attempts · auth_tokens
Contentscenarios · scenario_versions · attempts · badges · user_badges · review_queue
Moneyplans · subscriptions · entitlement_grants · transactions · wallet_ledger · credit_products · credit_costs · coupons · coupon_redemptions · refunds · webhook_events
Platformsettings · feature_flags · audit_log · impersonation_log · email_templates · email_queue · announcements · cron_runs · ip_blocklist · rate_limits · certificates

Three decisions worth defending

Kobo, always

Every money column is INT UNSIGNED in kobo. Paystack's API works in kobo, so integers matching the provider's unit mean zero conversion at the boundary — which is where conversion bugs live. Never FLOAT for money: 0.1 + 0.2 != 0.3 in binary floating point and the error compounds across a ledger. ₦6,500 is stored as 650000.

Ledger as truth, balance as cache

wallet_ledger is append-only — no UPDATE, no DELETE, ever. A correction is a new compensating row. This is the only way to answer "why does this trainee have 7 credits?" six months later, and the only defensible position in a dispute. The cached balance columns are written in the same transaction and reconciled nightly.

 the double-spend guard
// Without FOR UPDATE, two tabs both read balance=5, both spend 3,
// and you have given away a credit. Trainees DO open two tabs —
// it is already on your own pre-launch test list.
$db->prepare('SELECT credits_purchased, credits_granted
              FROM users WHERE id = ? FOR UPDATE')->execute([$userId]);

// Spend the perishable bucket first: granted credits expire,
// purchased ones never do.
$fromGranted   = min($granted, $cost);
$fromPurchased = $cost - $fromGranted;

// idempotency_key is UNIQUE — a double-tapped button or a retried
// request rolls back instead of charging twice.
ledger_insert($db, $userId, 'granted', -$fromGranted, $action, $idemKey);

Derive skill, store money

The build plan is right that progress must never be a stored column — it drifts. Six rows from one indexed query give you attempts, solves, accuracy and average commands per tier. That last figure is what the task ladder calls the best available proxy for growing diagnostic instinct, and it arrives free, so put it on the profile and the certificate.

Money is the exception. Entitlement is stored, because a subscription's state depends on events — renewals, cancellations, grace periods — that cannot be reconstructed from a payment list alone.

06

Super admin console

Three roles, not one — because the day you hand instructor access to someone else, you want that boundary already enforced rather than bolted on.

RoleCanCannot
instructorView their cohort, grade the review queue, see stuck-point analytics, draft scenariosTouch money, publish content, change settings
adminThe above, plus publish scenarios, manage users, issue refunds, run couponsChange platform settings or scoring weights, manage roles, delete accounts
super_adminEverything

Every admin file calls require_role() on line one, before any output. Never guard by hiding a nav link — a guard that lives only in the menu is not a guard.

Modules

Priority: P1 before the pilot · P2 before real money · P3 after, driven by what actually hurts.

Dashboard & health P1

Revenue, MRR, active subs, signups, live attempts — plus a health strip: last cron per job, failed webhooks, mail backlog, ledger drift, PHP errors. On shared hosting failure is silent; put this where you cannot miss it.

Users & access P1

Search, detail view with everything on one page, suspend, force-verify, change role, grant or revoke entitlement, adjust credits — every adjustment writing a ledger row with a mandatory reason.

Fault builder P1

A form, not a JSON textarea, with server-side schema validation, a preview sandbox that records no stats, and versioning with diff and rollback. You will not get a fault's difficulty right without playing it.

Settings & flags P1

Scoring weights, tier gate, cooldowns, maintenance mode with an allowlist, feature toggles, a billing master switch for when Paystack has an incident.

System info P1

Actively tests outbound HTTPS, Paystack reachability, SMTP, DB writes, log writability. Turns a class of "broken and I have no SSH" problems into a page you can read.

Transactions & reconciliation P2

Filterable list with raw payloads, a Sync-with-Paystack diff showing matched / Paystack-only / FixIT-only, one-click resolve, and refunds through the API.

Webhook inspector P2

Every event, signature status, payload, replay. This is how "I paid and nothing happened" gets answered in thirty seconds.

Test / live banner P2

Permanent and unmissable when Paystack is in test mode. A fortnight of "live" signups that were all test-mode is a real and depressingly common outcome.

Email queue & templates P2

View queued and failed, retry, preview rendered output, send-test-to-me. Mail deliverability on cPanel is the most common silent failure.

Cron monitor P2

Last run, duration, status per job, with an alert when one misses its window. Shared-hosting cron fails quietly.

Impersonation P3

Read-only, reason required, logged, red banner, 30-minute cap, super admins exempt. The most useful support tool you will build and the easiest to abuse.

Stuck-point analytics P3

Per fault: where they give up, off-path commands, recurring wrong diagnoses, which hint unblocks them. Tells you where to add hints where the data says, not where you guessed.

Revenue reports P3

MRR, ARPU, new vs renewal, churn, failed-charge rate, plan mix, and credit liability outstanding — money taken but not yet delivered on.

Operations kit P3

Global search, audit log viewer, error log tail, announcements, data export, backups, session killer, rate-limit and IP management, cache buster, certificates with public verification.

Admin hardening — decide before you build, not after

Mandatory TOTP for admin and super admin (~60 lines of pure PHP, no library) · step-up re-auth for prices, refunds, grants, roles, deletions and settings · optional IP allowlist · every mutation writing before/after JSON to the audit log, especially your own · shorter idle timeout and a separate cookie name.

The last super admin cannot be deleted, suspended, or demoted. Enforce it with a count check in code. Locking yourself out of a no-SSH app means editing the database by hand through phpMyAdmin at 2am.

The ordering rule for everything else: build the tool the first time you need it manually, not before. The two exceptions are the audit log and the health strip — retrofitting an audit log means you have no record of exactly the period you most want one for.

07

cPanel deployment

Empty cPanel account to a live, HTTPS, payment-taking install. No SSH anywhere.

Do this first, not last

Push a hello-world index.php through the full pipeline before writing a line of the engine. Every problem below is easier to solve against an empty app — and discovering in week six that your host blocks outbound cURL is a genuinely bad week.

  1. PreflightPHP 8.1+, extensions, MySQL 5.7+, cron, Git Version Control — and the outbound HTTPS test.
  2. SubdomainDocument root at fixit/public_html so the app sits one level above the web root.
  3. DatabaseCreate, add user with all privileges, import schema.sql via phpMyAdmin.
  4. Code onto the servercPanel Git Version Control with a .cpanel.yml, or File Manager zip.
  5. Configurationconfig.php outside the web root, chmod 600, gitignored.
  6. .htaccessHTTPS force, security headers, gzip, long asset cache, error log to file.
  7. HTTPSAutoSSL, then HSTS and secure session cookies. Paystack rejects http webhooks.
  8. EmailAuthenticated SMTP, queue + cron, SPF and DKIM verified in Email Deliverability.
  9. CronMail queue 5-min, subscriptions hourly, maintenance and backup nightly.
  10. PaystackTest mode, plans created, webhook URL set, all three test cards run.
  11. Smoke testInfrastructure, application, money, admin — every line, every deploy.
  12. BackupsDB export before each deploy, nightly dump, weekly copy taken off the server.

The layout that makes secrets safe

When you create the subdomain, uncheck "share document root" and point it at fixit/public_html. Then fixit/ itself is not web-accessible, and that is where includes/, storage/, vendor/, bin/ and scenarios/ live. Strictly better than the .htaccess-deny fallback: no single misconfiguration can expose your Paystack secret key.

Note that Composer is not available without SSH. Upload PHPMailer's src/ by hand and require it directly. You need nothing else — the Paystack integration is plain cURL and the app is deliberately framework-free.

Gotchas, with their causes

SymptomCauseFix
Paystack calls fail silentlyOutbound HTTPS blockedHost support ticket for api.paystack.co
Webhook shows 302 in PaystackYour HTTPS or www rewrite catching itExclude the webhook path from rewrites
Webhook shows 403mod_security ruleAsk the host to whitelist the path
Signature header emptyapache_request_headers() under PHP-FPMRead $_SERVER['HTTP_X_PAYSTACK_SIGNATURE']
Cron produces nothingWrong PHP binary pathCopy it from Select PHP Version
Mail lands in spamMissing SPF / DKIMEmail Deliverability → repair
CSS changes invisibleOne-year cache headerBump the asset version from admin
Import fails on JSON columnsMariaDB older than 10.2Replace JSON with LONGTEXT
Random 508sResource limit — usually an N+1 queryCheck Resource Usage; find the query
500 after deployconfig.php overwritten or wrong permissionsConfirm it exists, is 600, returns an array
When to leave shared hosting

The client-side engine and three-server-hits-per-scenario design mean shared hosting will carry several hundred concurrent trainees. The signals to move are sustained CPU throttling in Resource Usage, MySQL connection limits during cohort sessions, or the day you want SSH, Composer and Redis more than you want the simplicity. None of those are launch problems.

08

Roadmap

The source plan lays out roughly 25 days for engine, auth, content, admin basics and pilot. That is reasonable for what it covers. It does not include payments, a subscription lifecycle, a credit wallet, or a super admin console — realistically 20–25 further working days.

ScopeDays, focused full-timeElapsed, evenings & weekends
Original plan~258–10 weeks
+ monetization + super admin~4814–18 weeks

Plan against the right-hand column unless FixIT is your full-time job. Nobody sustains eight focused hours a day on a side project, and the gap between those columns is where most projects quietly die.

Phase 0Days 1–2
Deploy pipeline

The most valuable change to the original plan: make deployment work before building anything. Preflight, subdomain, database, git deploy, HTTPS, error logging — and submit the Paystack business application.

Done when you can commit, click Deploy, and see it live in under a minute.

Phase 1Days 3–9
Engine

Unchanged from the source plan. Terminal, parser, output generators, byte-accurate layout, streamed timings, verbatim errors, DocumentFragment rendering. No database.

Done when you solve NET-101 end to end, it feels instant, and a working technician does not spot it as fake in the first ten seconds. Find one and watch their face.

Phase 2Days 10–16
Auth and persistence

Signup, verification, reset, sessions, CSRF, rate limiting, lockout, mail queue. Attempts recorded, autosave, resume. Scoring moved server-side.

Done when two accounts solve the same fault with separate correct progress, and a tampered client score is rejected.

Phase 3Days 17–24
Content and instructor admin

Fault builder, sandbox, versioning, audit log skeleton. All six Tier 1 faults plus one from each higher tier — NET-201, EP-301, MULTI-401, INC-501 — exactly as the task ladder recommends, to prove every mechanic before writing the other twenty.

Done when you can add a fault without touching code.

Phase 4Days 25–33
Monetization

Pricing page, checkout, verify, webhook with signature and idempotency, subscription lifecycle, wallet with row locking, reconciliation cron, receipts and dunning email, entitlement gating.

Done when a test card buys access, a replayed webhook grants nothing extra, and two tabs cannot double-spend a credit.

Phase 5Days 34–40
Super admin console

The P1 and P2 modules: health dashboard, users, payments and reconciliation, webhook inspector, settings and flags, email queue, cron monitor, TOTP and step-up auth.

Done when you can run a support request end to end without opening phpMyAdmin.

Phase 6Days 41–48
Content completion and pilot

Write out to 30 faults. Run a small live cohort. Watch where they stall and add hints where the data says. Expect what the pilot exposes to be pacing and copy, not code.

Done when a cohort clears Tiers 1–2 with no support request you could not answer from admin.

Phase 7After pilot
Expansion

In build order: the M365/Entra console — the highest-value module, precisely because nobody hands a trainee a live tenant — then Event Viewer, the instructor review queue, certificates, ticket-craft mode, timed incidents, and B2B seats if the demand shows up.

If you are running late, cut in this order

  1. Coupons, announcements, data export, certificates — post-pilot anyway
  2. Impersonation — useful, but you can read the database
  3. The credit wallet entirely. Subscriptions alone are a complete product. Ship subscription-only and add credits once you know which consumables people actually want. Biggest saving available, and it costs almost nothing because the schema is already there.
  4. Tiers 4–5 content — launch with 22 faults across three tiers
Never cut these

Server-side scoring · webhook signature verification · webhook idempotency · wallet row locking · the audit log · stripping answers from the client payload. Every one is a "we'll add it later" that turns into an incident.

09

Open questions

Grouped by when the answer actually blocks something. Everything above works under the stated assumptions; these are where a different answer changes the build.

A · Before Phase 0 — this week
A1

Subdomain or subfolder?I have assumed fixit.adebled.com.ng with its own document root. A subfolder works but complicates cookie scope, .htaccess inheritance, and the webhook URL.

A2

Which cPanel host and plan?Specifically whether it offers Git Version Control, cron, and outbound HTTPS from PHP. Send me the hostname or a screenshot of the cPanel home and I can tell you what to expect before you hit it.

A3

Is ADEBLED SERVICES CAC-registered with a business bank account?Paystack live keys need both. If not, that application becomes your longest-lead item and everything else proceeds around it.

A4

Does the domain already send email, with SPF and DKIM set up?If the training division already runs MailerLite or Mailjet, reusing it beats configuring cPanel SMTP.

B · Before Phase 4 — payments
B1

Are the prices right?My figures have no basis beyond structure. What do your current ADEBLED students pay? What does a competing Nigerian IT-support course charge? This is the assumption I have least confidence in.

B2

Should annual exist at launch?Best cash flow and most fee-efficient, but selling a year of access to a product with three weeks of history is a refund risk.

B3

A free trial of the paid tiers, separate from the free tier?My default is no — free Tier 1 already does that job without a card.

B4

What is the refund policy?It needs to be on the pricing page before the first payment, and it needs to be one you will actually honour.

B5

Which payment channels?Card only is simplest. Bank transfer and USSD widen reach materially in Nigeria — but recurring charges require a saved card, so those users would need manual renewal. A product decision, not a technical one.

B6

What happens when a subscriber finishes all 30 faults?They stop paying, which is correct. Is there anything after — refresher mode, new faults monthly, a community tier? Right now this is a course being billed like a subscription. Worth deciding early.

C · Before the pilot
C1

Who grades the Tier 4–5 written submissions?The 48-hour SLA is only credible if someone is on the other end. If it is only you, price the credits high enough that volume stays manageable.

C2

What anchors curriculum day 1?Signup, first attempt, or cohort start. I default to first attempt, with the clock pausing during a lapsed subscription — nobody should lose curriculum days to a card decline.

C3

Leaderboard: global, cohort-only, or opt-in?Self-serve means no cohorts by default. But public rankings discourage exactly the nervous beginners you most want to keep. Opt-in with a display name is the compromise.

C4

Does the certificate need to mean something externally?If AQskill or an employer will ever verify one, you need the public verification page, a serial scheme, and a defensible standard for what earns it.

D · No deadline, but worth thinking about
D1

Is mobile first-class or a fallback?A Windows terminal on a 6-inch screen with a soft keyboard is a poor experience — but Nigerian trainees are more likely to be on a phone than a laptop. This affects a lot of CSS and is far cheaper to decide now.

D2

Offline tolerance?The architecture is unusually well suited to it — the engine is already client-side. A service worker could let someone finish a scenario through a network drop. Not v1, but in Nigeria it may be a genuine differentiator.

D3

Who maintains content accuracy?Windows changes, M365 renames things constantly, error strings drift. Byte-accurate output is a maintenance commitment, not a one-time build.

D4

Do you care about account sharing?Randomisation stops answer-passing, but one account shared between five friends is a revenue problem the schema does not currently address.