@ucca/vmc — Verified Media Capture¶
Engineering Brief v1.0¶
Classification: Reusable Platform Module¶
Author: UCCA Platform / Tim Rignold¶
Date: 2026-03-08¶
1. PURPOSE¶
@ucca/vmc is a standalone, reusable module for capturing verified voice and video
messages tied to a cryptographically provable identity gate.
It is NOT a contact form feature. It is a platform primitive.
The module captures WHO recorded something, WHAT they recorded, WHEN they recorded it, FROM WHAT device and context — and delivers the complete provenance manifest alongside the media artifact to any configured destination.
First consumer: ucca.online GET IN TOUCH modal. Future consumers: RTOpacks onboarding, ops console, any UCCA world deployment.
2. PACKAGE LOCATION¶
ucca-engine/
└── packages/
└── vmc/
├── package.json
├── README.md
├── src/
│ ├── index.ts — public API
│ ├── otp/
│ │ ├── send.ts — Twilio OTP dispatch
│ │ └── verify.ts — OTP verification
│ ├── capture/
│ │ ├── recorder.ts — MediaRecorder wrapper
│ │ ├── voice.ts — voice capture config
│ │ └── video.ts — video capture config
│ ├── upload/
│ │ └── r2.ts — R2 presigned upload
│ ├── notify/
│ │ └── resend.ts — Resend notification
│ ├── manifest/
│ │ └── build.ts — provenance manifest builder
│ ├── ratelimit/
│ │ └── kv.ts — KV rate limiting
│ └── types.ts — all TypeScript interfaces
└── tests/
└── manifest.test.ts
3. PUBLIC API¶
The module exposes a single clean interface. Consumers pass config. The module handles everything.
import { createVMC } from '@ucca/vmc'
const vmc = createVMC({
// Identity gate
otp: {
twilioAccountSid: env.TWILIO_ACCOUNT_SID,
twilioAuthToken: env.TWILIO_AUTH_TOKEN,
fromNumber: env.TWILIO_FROM_NUMBER, // configurable per deployment
},
// Storage
r2: {
accountId: env.CF_ACCOUNT_ID,
bucketName: env.VMC_R2_BUCKET, // configurable per deployment
accessKeyId: env.R2_ACCESS_KEY_ID,
secretAccessKey: env.R2_SECRET_ACCESS_KEY,
presignExpirySeconds: 172800, // 48 hours default
},
// Notification
resend: {
apiKey: env.RESEND_API_KEY,
from: env.VMC_FROM_EMAIL, // configurable per deployment
to: env.VMC_TO_EMAIL, // configurable per deployment
},
// Rate limiting
rateLimit: {
kvNamespace: env.VMC_KV_NAMESPACE,
maxPerNumber: 1, // submissions per window
windowHours: 24, // rolling window
},
// Capture config
capture: {
maxVoiceSeconds: 120, // 2 minutes
maxVideoSeconds: 90, // 90 seconds
allowVideo: true, // can disable per deployment
},
// Context — injected by consumer, appears in manifest
context: {
surface: 'ucca.online', // which surface deployed on
deploymentId: 'ucca-marketing-v1',
},
})
4. THE FLOW¶
1. GATE
User enters mobile number
→ VMC calls Twilio, sends 6-digit OTP via SMS
→ User enters OTP
→ VMC verifies
→ On success: session token issued (short-lived, stored in memory)
→ On failure: 3 attempts max, then 1 hour lockout
2. RATE LIMIT CHECK
→ VMC checks KV: has this number submitted in the last 24 hours?
→ If yes: "You've already sent a message.
You can send another after [timestamp]."
→ If no: proceed
3. CAPTURE MODE SELECTION
User chooses:
→ // TYPE A MESSAGE (text — always available)
→ // LEAVE A VOICE MESSAGE (audio — mic required)
→ // LEAVE A VIDEO MESSAGE (video — camera+mic,
shown only if device supports it)
Device capability detection:
→ Check navigator.mediaDevices.getUserMedia support
→ Check camera availability for video option
→ Graceful degradation — if no mic, voice option hidden
→ If no camera, video option hidden
4. RECORDING
Voice:
→ Hold-to-record OR tap start/stop
→ Live waveform visualiser (ambient, not distracting)
→ Countdown timer visible
→ Auto-stops at maxVoiceSeconds
Video:
→ Live preview in modal
→ Tap to start/stop
→ Countdown timer visible
→ Auto-stops at maxVideoSeconds
Text:
→ Simple textarea
→ 1000 char limit
→ No OTP required for text — gate is voice/video only
(text is low abuse risk, no storage cost)
5. PREVIEW
→ User can play back before sending
→ "Send" or "Re-record" options
→ No editing — what you recorded is what you send
6. UPLOAD
→ Blob uploaded directly to R2 via presigned URL
→ Upload progress indicator
→ File naming convention:
vmc/{surface}/{YYYY-MM-DD}/{phone-hash}/{uuid}.webm
Note: phone number is HASHED in the file path (privacy)
but stored in full in the manifest.
7. MANIFEST BUILD
→ On successful upload, manifest is built (see Section 5)
8. NOTIFICATION
→ Resend fires to configured recipient
→ Email contains full manifest + R2 presigned link
→ Link expires in 48 hours
9. CONFIRMATION
→ User sees: "Received. We'll be in touch."
→ Modal closes
→ No further detail — clean exit
5. PROVENANCE MANIFEST¶
Every submission generates a manifest. This is the provenance record.
Stored in R2 alongside the media file as {uuid}.manifest.json.
Also delivered in full in the Resend notification email.
{
"manifest_version": "1.0",
"uuid": "a3f7c291-...",
"submitted_at_utc": "2026-03-08T08:45:16Z",
"identity": {
"phone_verified": true,
"phone_number": "+61422334489",
"phone_hash": "sha256:a3f9...",
"otp_verified_at_utc": "2026-03-08T08:44:52Z",
"verification_method": "sms-otp"
},
"media": {
"type": "voice",
"duration_seconds": 103,
"size_bytes": 824320,
"mime_type": "audio/webm",
"r2_key": "vmc/ucca.online/2026-03-08/a3f9.../a3f7c291.webm",
"r2_presign_url": "https://...",
"r2_presign_expires_utc": "2026-03-10T08:45:16Z"
},
"device": {
"user_agent": "Mozilla/5.0 ...",
"platform": "iPhone",
"os": "iOS 17.4",
"browser": "Safari",
"viewport_width": 390,
"viewport_height": 844,
"media_recorder_mime": "audio/webm;codecs=opus"
},
"context": {
"surface": "ucca.online",
"deployment_id": "ucca-marketing-v1",
"page_language": "ja",
"page_url": "https://ucca.online",
"referrer": "https://google.com",
"timezone": "Asia/Tokyo",
"local_time": "2026-03-08T17:45:16+09:00"
},
"rate_limit": {
"number_hash": "sha256:a3f9...",
"submissions_in_window": 1,
"window_resets_utc": "2026-03-09T08:44:52Z"
}
}
6. R2 BUCKET STRUCTURE¶
vmc/
└── {surface}/
└── {YYYY-MM-DD}/
└── {phone-hash-prefix}/
├── {uuid}.webm — media file
└── {uuid}.manifest.json — provenance manifest
Bucket: ucca-vmc (new dedicated bucket, separate from terraform state)
Lifecycle rule: auto-delete after 90 days (configurable)
Public access: NONE. Presigned URLs only.
7. RESEND NOTIFICATION TEMPLATE¶
Subject: VMC // {type} // {surface} // {duration} // {local_time}
Example: VMC // VOICE // ucca.online // 1m 43s // Tokyo 17:45
Body (plain text + HTML):
VERIFIED MEDIA CAPTURE
──────────────────────
Surface: ucca.online
Type: Voice — 1m 43s
Received: 2026-03-08 08:45:16 UTC
(Tokyo: 17:45:16 JST)
IDENTITY
──────────────────────
Phone: +61 422 334 489 (verified)
OTP at: 08:44:52 UTC
CONTEXT
──────────────────────
Language: Japanese (ja)
Device: iPhone / iOS 17 / Safari
Referrer: google.com
Timezone: Asia/Tokyo
MEDIA
──────────────────────
[PLAY RECORDING]
https://r2-presigned-url...
Link expires: 2026-03-10 08:45 UTC
[VIEW MANIFEST]
https://r2-presigned-manifest-url...
──────────────────────
@ucca/vmc v1.0
8. RATE LIMITING — KV SCHEMA¶
Key: vmc:ratelimit:{phone_hash}
Value: {
"count": 1,
"first_at": "2026-03-08T08:44:52Z",
"last_at": "2026-03-08T08:44:52Z",
"window_expires": "2026-03-09T08:44:52Z"
}
TTL: 86400 seconds (24 hours, rolling)
KV Namespace: VMC_RATELIMIT (new namespace, separate from REG_INTEL)
Blacklist:
Key: vmc:blacklist:{phone_hash}
Value: { "reason": "abuse", "added_at": "..." }
TTL: none (permanent until manually removed)
9. ABUSE PREVENTION SUMMARY¶
| Layer | Mechanism | Coverage |
|---|---|---|
| 1 | Phone OTP verification | Requires real mobile number |
| 2 | 1 submission per number per 24hr | KV rate limit |
| 3 | 3 OTP attempts max then 1hr lockout | KV lockout |
| 4 | 50MB hard upload size cap | R2 presign config |
| 5 | audio/webm + video/webm only | MIME type whitelist |
| 6 | Phone blacklist | KV permanent block |
| 7 | Text submissions ungated | Low risk, no storage cost |
10. DEVICE CAPABILITY DETECTION¶
const capabilities = await detectCapabilities()
// Returns:
{
hasMicrophone: boolean,
hasCamera: boolean,
supportsMediaRecorder: boolean,
supportedMimeTypes: string[], // e.g. ['audio/webm;codecs=opus']
isMobile: boolean,
isSecureContext: boolean, // must be true for getUserMedia
}
Rules:
- Voice option shown only if: hasMicrophone && supportsMediaRecorder && isSecureContext
- Video option shown only if: hasCamera && hasMicrophone && supportsMediaRecorder && isSecureContext
- Text always shown
- If only text available: no mention of voice/video — modal just shows the form
11. FIRST CONSUMER — ucca.online GET IN TOUCH¶
// apps/marketing/app/components/ContactModal.tsx
import { VMCModal } from '@ucca/vmc/react'
<VMCModal
config={vmcConfig}
trigger={<button>GET IN TOUCH</button>}
theme="ucca-dark" // dark theme, green accents, IBM Plex
strings={{
title: t.contact_title,
subtitle: t.contact_subtitle,
confirmed: t.contact_confirmed,
}}
/>
The VMC React component accepts a theme prop and a strings prop for i18n.
All UI strings passed in from the consumer's translation system.
The module itself has no hardcoded user-facing strings.
12. ENVIRONMENT VARIABLES REQUIRED¶
# Twilio
TWILIO_ACCOUNT_SID=
TWILIO_AUTH_TOKEN=
VMC_TWILIO_FROM_NUMBER= # which number to send OTP from
# R2
CF_ACCOUNT_ID= # already exists
R2_ACCESS_KEY_ID= # already exists (or create VMC-specific)
R2_SECRET_ACCESS_KEY= # already exists (or create VMC-specific)
VMC_R2_BUCKET=ucca-vmc # new bucket
# Resend
RESEND_API_KEY= # already exists
VMC_FROM_EMAIL= # e.g. noreply@ucca.online
VMC_TO_EMAIL= # e.g. tim@ucca.online
# KV
VMC_KV_NAMESPACE_ID= # new KV namespace: VMC_RATELIMIT
13. CLOUDFLARE INFRASTRUCTURE REQUIRED¶
New resources — add to Terraform:
# New R2 bucket
resource "cloudflare_r2_bucket" "vmc" {
account_id = var.cloudflare_account_id
name = "ucca-vmc"
}
# New KV namespace
resource "cloudflare_workers_kv_namespace" "vmc_ratelimit" {
account_id = var.cloudflare_account_id
title = "VMC_RATELIMIT"
}
New Worker required: vmc-api.ucca.online
Handles: OTP send/verify, R2 presign generation, manifest storage, Resend dispatch
The browser never touches R2 directly — all via the Worker.
14. NEW I18N KEYS REQUIRED¶
Add to translations.ts for all locales:
contact_title
contact_subtitle
contact_type_text
contact_type_voice
contact_type_video
contact_phone_prompt
contact_otp_prompt
contact_otp_error
contact_record_instruction_voice
contact_record_instruction_video
contact_preview_send
contact_preview_rerecord
contact_sending
contact_confirmed
contact_ratelimit_message
contact_error_no_mic
contact_error_no_camera
contact_error_upload
15. BUILD ORDER¶
Alex should build in this sequence:
1. Create packages/vmc/ skeleton — types.ts, index.ts
2. Terraform — R2 bucket + KV namespace
3. vmc-api Worker — OTP send/verify endpoints
4. R2 presign endpoint in Worker
5. Resend notification in Worker
6. KV rate limiting in Worker
7. packages/vmc/src — client-side capture logic
8. packages/vmc/react — VMCModal React component
9. Wire into ucca.online ContactModal
10. Test end-to-end: OTP → record → upload → manifest → email
11. Deploy vmc-api.ucca.online
12. Deploy ucca.online with VMC wired in
16. SURFACE RULE¶
SURFACE: ucca-engine (packages/vmc/ + new Worker)
ucca.online (apps/marketing — consumer only)
ucca-infra (Terraform — R2 + KV)
DO NOT touch: ops console, time.ucca.online,
any other surface
17. COMMIT CONVENTION¶
feat(vmc): scaffold package structure and types
feat(vmc): terraform R2 bucket and KV namespace
feat(vmc): OTP send/verify worker endpoints
feat(vmc): R2 presign and manifest storage
feat(vmc): resend notification template
feat(vmc): KV rate limiting and blacklist
feat(vmc): client MediaRecorder capture module
feat(vmc): VMCModal React component
feat(vmc): wire into ucca.online contact modal
feat(vmc): end-to-end test and deploy
@ucca/vmc v1.0 — Verified Media Capture Build once. Deploy everywhere. Who recorded it. What they recorded. When. Immutable.
Version History¶
| Version | Date | Change | Author |
|---|---|---|---|
| 1.0 | 2026-03-08 | Initial creation from engineering brief | Tim Rignold |