Skip to content

REFORMATION BRIEF R-01 — THE DOOR

RTO Onboarding — Search Recognition + Claim Flow + L3 Dashboard Shell

UCCA Platform · 18 March 2026


R-00 built the rails. R-01 opens the door. This brief is deliberately functional, not beautiful. Wire it in. Make it work. Beauty comes later.


SURFACE: rtopacks-site (rtopacks.com.au) + ucca-api (engine/ucca-engine/backend/) DO NOT TOUCH: ucca-site, ucca-ir, ucca-keys, ucca-ops, ucca-track Cloudflare Account: e5a9830215a8d88961dc6c80a8c7442a engine-db: 0efa8970-0053-4623-8436-4e877af10887 rtopacks-db: 334ac8fb Auth0 Domain: dev-0jm2d1q8khz1eiqu.au.auth0.com Auth0 Client ID: w5CGFlNFZQvq05LZemGpeTx8IcK3mu66 RTOpacks Org ID: org_giDMY4hPkEKTiMen


→ ALEX

1. Search Bar — RTO Recognition Hook

The search bar on rtopacks.com.au already works. Do not redesign it. Add one behaviour:

When a search result is an RTO (not a qualification or unit), check if the user's email domain matches the RTO's registered domain in rtopacks-db.

If the user has typed an email anywhere on the page (or is logged in), and the email domain matches an RTO in the results: - Add a subtle "Claim this RTO" prompt below that result - Label: "Is this your RTO? Set up your free account →" - This is a plain text link or simple button — no modal, no animation yet - Clicking it starts the claim flow (Section 2)

If no email is known yet: - Show the prompt anyway: "Are you from [RTO Name]? Set up your account →" - Clicking it pre-fills the RTO in the claim flow

Search improvement — also do this:

Current fuzzy logic returns RTOs, qualifications, and units. Add a result type indicator to each result so users can visually distinguish: - RTO badge — green - Qualification badge — blue
- Unit badge — grey

No redesign. Just a small coloured badge prefix on each result row. One line of CSS.


2. Claim Flow — Three Funnels, One Destination

All three funnels land at the same endpoint: a provisioned L3 tenant in engine-db, seeded with TGA data, ready to log in.

Funnel 1 — Organic (via search "Claim this RTO") User clicks claim → lands on /claim page on rtopacks.com.au

Funnel 2 — Direct (via rtopacks.com.au/claim) Same page, no pre-fill

Funnel 3 — Outbound (via email link) Link includes ?rto_id=XXXXX param → pre-fills RTO on claim page


The /claim Page

Scaffold this page on rtopacks.com.au. Deliberately simple — no theatre, just function.

Step 1 — Identity

[ Your email address        ]
[ Your mobile number        ]  ← include country code selector, default +61
[ Your RTO name or number   ]  ← autocomplete from rtopacks-db, pre-filled if known

[ Continue ]

On submit: 1. Look up email domain against rtos table in rtopacks-db → find TGA match 2. If matched → pre-fill RTO name/number, show: "We found [RTO Name] (RTO #XXXXX) — is that right?" 3. If no TGA match → show corporate trainer branch (Step 1B below) 4. Send email OTP via Auth0 passwordless email connection 5. Send SMS OTP via Twilio Verify to the mobile number provided

Step 1B — Corporate Trainer Branch (no TGA match)

We couldn't find your organisation in the national training register.
Are you a corporate trainer or enterprise L&D team?

[ Yes, set up as a corporate trainer ]
[ Search again ]

Corporate trainer path → same dual auth → provision tenant with tenant_type = 'corporate', tga_rto_id = NULL → land in same dashboard shell with manual scope setup wizard (stub the wizard for now — just a placeholder card).

Step 2 — Verify

We've sent a code to [email] and a message to [mobile].
Enter both to continue.

Email code:   [ _ _ _ _ _ _ ]
SMS code:     [ _ _ _ _ _ _ ]

[ Verify & Continue ]

Both must verify before provisioning. If either fails → resend option.

Step 3 — Provision

On successful dual verification:

  1. Check if tenant already exists in engine-db for this tga_rto_id:
  2. If yes → log them in, skip to dashboard
  3. If no → provision new tenant

  4. Provision new tenant:

    # In ucca-api — POST /v1/provision-tenant
    tenant = {
        "id": uuid4(),
        "slug": slugify(rto_name),
        "display_name": rto_name,
        "tenant_type": "rto",  # or "corporate"
        "tga_rto_id": rto_id,  # NULL for corporate
        "config_json": json.dumps({
            "rto_name": rto_name,
            "rto_number": rto_id,
            "training_packages": [],  # populated from TGA data
            "onboarding_complete": False
        })
    }
    db.insert("tenants", tenant)
    
    # Create L3 user
    user = {
        "id": uuid4(),
        "email": email,
        "display_name": display_name,
        "auth0_sub": None  # set on first Auth0 login
    }
    db.insert("users", user)
    
    # Grant L3 role
    db.insert("user_tenant_roles", {
        "id": uuid4(),
        "user_id": user["id"],
        "tenant_id": tenant["id"],
        "role": "L3",
        "granted_by": "00000000-0000-0000-0000-000000000001"  # Tim as L1 granter
    })
    
    # Write audit log
    write_audit(...)
    

  5. Seed TGA data into tenant config:

  6. Pull RTO's training packages from rtopacks-db using tga_rto_id
  7. Store as training_packages array in config_json

  8. Write to Auth0 — create user in RTOpacks organization:

  9. Auth0 Management API → create user → add to org_giDMY4hPkEKTiMen
  10. Set app_metadata.tenant_id and app_metadata.role = "L3"

  11. Redirect to dashboard with session token

Write platform_audit_log entry: tenant.provision with full payload.


3. L3 Dashboard Shell — rtopacks.com.au/dashboard

Scaffold this page. Deliberately a skeleton. Real data, rough layout.

Header:

RTOpacks                          [RTO Name] · RTO #XXXXX    [Sign Out]

Four cards — all real data from engine-db + rtopacks-db:

Card 1 — Your RTO

[RTO Name]
RTO Number: XXXXX
Registration Status: [from TGA data]
Registration Expiry: [from TGA data]

Card 2 — Your Scope

Training Packages: [count]
Qualifications: [count]  
Units of Competency: [count]
[ View Scope → ]  ← stub, links nowhere yet

Card 3 — Course Packs

You haven't purchased any course packs yet.
[ Browse Catalogue → ]  ← links back to rtopacks.com.au search

Card 4 — Compliance

⚠ Email domain mismatch detected.
Your RTO is registered with [tga_email_domain] but you signed up 
with [user_email_domain]. Update your TGA contact details to match.
[ Learn more → ]  ← stub
Only show Card 4 if domain mismatch exists. Otherwise show a green tick: "Your contact details match your TGA registration."

No nav, no sidebar, no mega menu. Just four cards. This is day one.


4. Twilio Verify — SMS OTP

Add Twilio Verify to ucca-api for the dual auth SMS step.

Secrets to add to ucca-api Worker:

TWILIO_ACCOUNT_SID    ← Tim to provide from Twilio console
TWILIO_AUTH_TOKEN     ← Tim to provide from Twilio console  
TWILIO_VERIFY_SID     ← Tim to create a Verify Service in Twilio console, provide SID

Endpoints to add to ucca-api:

# POST /v1/verify/send
# Body: { "phone": "+61412345678" }
# Sends OTP via Twilio Verify

# POST /v1/verify/check  
# Body: { "phone": "+61412345678", "code": "123456" }
# Returns: { "valid": true/false }

5. Email OTP — Auth0 Passwordless

Auth0 already has the email passwordless connection configured. Use it for the email OTP step.

Auth0 Management API call to send email OTP:

POST https://dev-0jm2d1q8khz1eiqu.au.auth0.com/passwordless/start
{
  "client_id": "w5CGFlNFZQvq05LZemGpeTx8IcK3mu66",
  "connection": "email",
  "email": "[user email]",
  "send": "code"
}

Verify OTP:

POST https://dev-0jm2d1q8khz1eiqu.au.auth0.com/oauth/token
{
  "grant_type": "http://auth0.com/oauth/grant-type/passwordless/otp",
  "client_id": "w5CGFlNFZQvq05LZemGpeTx8IcK3mu66",
  "client_secret": "[from 1Password]",
  "username": "[user email]",
  "otp": "[code entered]",
  "realm": "email",
  "scope": "openid profile email"
}

Note: Auth0 default email provider active for now — dev only. SendGrid wired in R-02.


6. Schema Addition — rtopacks-db

Add one column to the RTO table in rtopacks-db to support domain matching:

ALTER TABLE rtos ADD COLUMN IF NOT EXISTS email_domain TEXT;

Populate from existing email data:

UPDATE rtos 
SET email_domain = LOWER(SUBSTR(email, INSTR(email, '@') + 1))
WHERE email IS NOT NULL AND email != '';

Create index:

CREATE INDEX IF NOT EXISTS idx_rtos_email_domain ON rtos(email_domain);


7. New API Endpoints Summary

All in ucca-api (engine/ucca-engine/backend/):

Endpoint Method Auth Purpose
/v1/rto/lookup GET None Look up RTO by email domain or RTO ID
/v1/verify/send POST None Send SMS OTP via Twilio
/v1/verify/check POST None Verify SMS OTP
/v1/provision-tenant POST None* Provision new L3 tenant
/v1/dashboard GET L3 JWT Return dashboard data for tenant

*/v1/provision-tenant — protected by dual-auth token from the claim flow, not a public endpoint.


OPS STUB TASK

Add to ops console task queue under IDENTITY & TENANCY world:

R-01 · THE DOOR
Search recognition hook live ✓
/claim page live ✓
Dual auth flow live (email OTP + SMS OTP) ✓
Tenant provisioning endpoint live ✓
L3 dashboard shell live ✓
TGA data seeded on provision ✓
email_domain index added to rtopacks-db ✓

→ TIM

What this brief does:

R-01 builds the front door of the platform. Three things happen:

  1. The search bar gets smarter — when an RTO appears in search results, it shows a "claim this RTO" prompt. The search also gets result type badges (RTO / Qualification / Unit) so users can orient themselves faster. No redesign — just two small additions to what's already there.

  2. The claim flow goes live — an RTO admin types their email and mobile, we match them to TGA data, send a code to both email and SMS, they verify both, and their tenant is automatically provisioned with their TGA data pre-loaded. Corporate trainers get the same flow with a manual setup wizard (stubbed for now). Three funnels (organic search, direct URL, outbound email link) all land at the same place.

  3. The L3 dashboard shell goes live — after claiming, the RTO lands on a simple four-card dashboard: their RTO details, their scope of registration, their course packs (empty for now with a "browse catalogue" link), and a compliance flag if their email domain doesn't match their TGA registration. Rough but real. Everything on it is live data.

What you need to do before Alex can execute:

  • Provide Twilio credentials: Account SID, Auth Token, and create a Verify Service in the Twilio console (console.twilio.com → Verify → Services → Create). Give Alex the three values.
  • Auth0 Client Secret — Alex already has this from R-00.

What this is NOT: - Not beautiful — four plain cards, basic form, no animation - Not the search redesign — that's a later brief - Not Stripe — payments come in R-02 - Not the full scope explorer — that's R-03

The goal is one RTO claiming their account and landing in their dashboard. That's the coin in the jukebox.


REFORMATION BRIEF R-01 · UCCA Inc · 18 March 2026 "Open the door. Make it pretty later."