Device Binding & Integration Readiness — Interface Contract Matrix
Version: 1.0 | Date: 2026-05-12
Purpose: Map every external interface to its source-of-truth document, current code state, and sample-integration readiness.
This is a working document, not a production spec. Provisional contracts are explicitly marked.
1. Document Source List
| # | Document | Path | Source of Truth For |
|---|---|---|---|
| D01 | AGENTS.md | AGENTS.md | Project rules, brand, scope boundaries, deploy SOP |
| D02 | AOVIS App 登录认证接口规范 v1.0 | docs/AOVIS_App_登录认证接口规范_v1.0.md | App auth API contract |
| D03 | App JWT Token Issuance | docs/app-jwt-token-issuance.md | App token lifecycle |
| D04 | Mobile App Auth API Handoff | docs/mobile-app-auth-api-handoff.md | App auth architecture context |
| D05 | EIOTCLUB Apifox Export | docs/eiotclub-apifox-export.md | EIOTCLUB API sign, error codes, card/package APIs |
| D06 | EIOTCLUB Claude Dev Brief | docs/eiotclub-claude-dev-brief.md | EIOTCLUB implementation guidance |
| D07 | EIOTCLUB Integration Handoff | docs/eiotclub-integration-handoff.md | EIOTCLUB state machine, dedup, webhook mapping |
| D08 | EIOTCLUB Integration | docs/eiotclub-integration.md | EIOTCLUB env vars, IP whitelist, smoke tests |
| D09 | Stripe Data Plan Webhook Setup | docs/stripe-data-plan-webhook-setup.md | Data plan webhook contract |
| D10 | AWS KVS Technical Quickstart PDF | Onedrive path (see below) | AWS KVS/IoT/Cognito architecture |
| D11 | Prisma Schema | prisma/schema.prisma | Data models |
| D12 | .env.example | .env.example | Env var baseline |
PDF path:
/Users/guorui/Library/CloudStorage/OneDrive-Personal/IP Camera/AWS KVS/AOVIS — AWS 技术快速上手指南.pdf
2. Interface Contract Matrix
2.1 App Auth Token
| Field | Value |
|---|---|
| Source of Truth | D02 §4.1, D03 §3.1 |
| Current Code | app/api/auth/app-token/route.ts, lib/app-token.ts, lib/verify-app-request.ts |
| Current State | Partially compliant |
| Issues | Missing invalid_provider error (currently returns invalid_token for bad provider); missing_fields returns invalid_token instead of 400 |
| Fix Needed | Add provider validation → invalid_provider (400); missing fields → missing_fields (400) |
| Verification | Unit test: bad provider returns 400, missing fields returns 400 |
| Affects Production Payment/Auth | No |
| Formal or Provisional | Formal (per D02) |
2.2 App Magic Link
| Field | Value |
|---|---|
| Source of Truth | D02 §4.2 |
| Current Code | app/api/auth/app-magic-link/route.ts |
| Current State | Compliant |
| Issues | None identified |
| Fix Needed | None |
| Verification | Existing test coverage |
| Affects Production Payment/Auth | No |
| Formal or Provisional | Formal |
2.3 App Token Revoke
| Field | Value |
|---|---|
| Source of Truth | D02 §4.4, D03 §3.4 |
| Current Code | app/api/auth/app-token/route.ts (DELETE handler) |
| Current State | Compliant |
| Issues | None identified |
| Fix Needed | None |
| Verification | UI test: DELETE with valid token returns {revoked: true}, subsequent usage returns 401 |
| Affects Production Payment/Auth | No |
| Formal or Provisional | Formal |
2.4 Stripe Data Plan Checkout
| Field | Value |
|---|---|
| Source of Truth | D09 |
| Current Code | lib/data-plans.ts, app/api/checkout/data-plan/route.ts |
| Current State | Compliant |
| Issues | None identified |
| Fix Needed | None |
| Verification | Checkout flow test |
| Affects Production Payment/Auth | No (separate endpoint) |
| Formal or Provisional | Formal |
2.5 Stripe Data Plan Webhook
| Field | Value |
|---|---|
| Source of Truth | D09 |
| Current Code | app/api/webhooks/data-plan/route.ts |
| Current State | Non-compliant |
| Issues | (1) Missing STRIPE_DATA_PLAN_WEBHOOK_SECRET returns 200 instead of 500. (2) stripeSessionId lacks unique constraint in DB. (3) Signature errors return 200 not 400. |
| Fix Needed | Return 500 when secret missing; add @unique on DataPlanPurchase.stripeSessionId; return 400 on signature error |
| Verification | Unit test: no secret → 500; bad signature → 400; replay → only one purchase |
| Affects Production Payment/Auth | Yes — webhook path |
| Formal or Provisional | Formal |
2.6 EIOTCLUB API Request Signing
| Field | Value |
|---|---|
| Source of Truth | D05 (sign algorithm), D06, D07 |
| Current Code | lib/eiotclub.ts — buildSignatureSource(), generateSign() |
| Current State | Compliant |
| Issues | None — golden sample test passes |
| Fix Needed | None |
| Verification | Golden sample test in lib/__tests__/eiotclub-sign.test.ts |
| Affects Production Payment/Auth | No (EIOTCLUB outbound) |
| Formal or Provisional | Formal |
2.7 EIOTCLUB Webhook Signing
| Field | Value |
|---|---|
| Source of Truth | D05 (sign algorithm — webhooks exclude appkey), D07 |
| Current Code | lib/eiotclub.ts — verifyCallbackSignature() |
| Current State | Non-compliant (security) |
| Issues | Secret leaked in logs. Logs include stringParmSign: source which contains the full secret=xxx value. Also in app/api/webhooks/eiotclub/route.ts line 117. |
| Fix Needed | Redact secret from webhook signature failure logs. Log hash or truncated expected/received sign only. |
| Verification | Audit logs must not contain secret=; use rg "secret=" to confirm |
| Affects Production Payment/Auth | No (security hardening) |
| Formal or Provisional | Formal |
2.8 EIOTCLUB Webhook PkgEffective (Package Activated)
| Field | Value |
|---|---|
| Source of Truth | D07 (event mapping table) |
| Current Code | lib/eiotclub-webhook-handlers.ts — handlePackageActivated() |
| Current State | Compliant |
| Issues | None identified |
| Fix Needed | None |
| Verification | Test passes: webhook promotes purchase to ACTIVE |
| Affects Production Payment/Auth | No (webhook path) |
| Formal or Provisional | Formal |
2.9 EIOTCLUB Webhook Refund
| Field | Value |
|---|---|
| Source of Truth | D07 |
| Current Code | lib/eiotclub-webhook-handlers.ts — handleRefund() |
| Current State | Compliant |
| Issues | None identified |
| Fix Needed | None |
| Verification | Test passes: refund → REFUNDED; replay → duplicate |
| Affects Production Payment/Auth | No (webhook path) |
| Formal or Provisional | Formal |
2.10 EIOTCLUB SIM Usage / Flow Alert
| Field | Value |
|---|---|
| Source of Truth | D07 |
| Current Code | lib/eiotclub-webhook-handlers.ts — handleFlowWarning() |
| Current State | Compliant |
| Issues | None identified |
| Fix Needed | None |
| Verification | Manual via replay script |
| Affects Production Payment/Auth | No |
| Formal or Provisional | Formal |
2.11 Device Binding / Activation
| Field | Value |
|---|---|
| Source of Truth | No formal document exists. Only informal references in README and AGENTS.md |
| Current Code | app/api/devices/activate/route.ts |
| Current State | Non-compliant (provisional only) |
| Issues | (1) No proof-of-possession (accepts aovis_device_id alone). (2) Does NOT create DeviceOwnership record. (3) Writes directly to Device.ownerUserId without unified ownership model. (4) No audit log. (5) No activation code / claim token / QR secret. (6) Stripe payment success is NOT distinguished from entitlement activation. (7) AWS provisioning in activation is provisional (registerDevice creates real AWS resources which may fail in non-configured environments). |
| Fix Needed | Add DeviceOwnership creation; add audit log; distinguish ownerUserId vs DeviceOwnership source of truth; mark as provisional |
| Verification | Test: activation creates DeviceOwnership; same-user reactivation is idempotent; other-user activation returns 403 |
| Affects Production Payment/Auth | No (deferred feature) |
| Formal or Provisional | Provisional — Sample Integration Contract |
2.12 DeviceOwnership
| Field | Value |
|---|---|
| Source of Truth | D11 (Prisma schema) |
| Current Code | prisma/schema.prisma — DeviceOwnership model |
| Current State | Partially compliant |
| Issues | DeviceOwnership exists as a join table with unique (deviceId, userId) constraint, but device activation writes to Device.ownerUserId instead of creating DeviceOwnership. The account devices page reads from DeviceOwnership. |
| Fix Needed | Device activation must create DeviceOwnership record. Make DeviceOwnership the unified source of truth for ownership checks. |
| Verification | After activation, DeviceOwnership exists for userId |
| Affects Production Payment/Auth | No |
| Formal or Provisional | Formal (model) / Provisional (activation flow) |
2.13 SIM / ICCID / EID / IMSI Relationship
| Field | Value |
|---|---|
| Source of Truth | D11 (SimCard model), D05 (EIOTCLUB API fields) |
| Current Code | prisma/schema.prisma — SimCard model |
| Current State | Compliant |
| Issues | ICCID lacks front-end masking (only data-plans.ts maskIccid exists). Need to ensure ICCID not returned in public API responses. |
| Fix Needed | Add ICCID masking helper for public API responses |
| Verification | grep for ICCID exposure in public API responses |
| Affects Production Payment/Auth | No |
| Formal or Provisional | Formal |
2.14 KVS Stream Access
| Field | Value |
|---|---|
| Source of Truth | D10 (PDF §架构, §App/Web端) |
| Current Code | app/api/devices/[id]/stream/route.ts, lib/aws/kvs.ts |
| Current State | Partially compliant |
| Issues | (1) verifyDeviceAccess checks ownerUserId but NOT DeviceOwnership. (2) No entitlement check. (3) AWS SDK clients use default credential chain (may fall back to env vars causing long-term key exposure concern; but actually only region is set in awsConfig). (4) No assertDeviceServiceAccess unified function. (5) No mockable service layer — currently creates real AWS clients. |
| Fix Needed | Create lib/aws/device-service-access.ts with assertDeviceServiceAccess(); add entitlement check |
| Verification | Test: non-owner gets 404; owner without entitlement gets 403 |
| Affects Production Payment/Auth | No (KVS is future feature) |
| Formal or Provisional | Provisional — Sample Integration Contract |
2.15 KVS Clip Access
| Field | Value |
|---|---|
| Source of Truth | D10 (PDF §GetClip API) |
| Current Code | app/api/devices/[id]/clip/route.ts, lib/aws/kvs.ts |
| Current State | Same as Stream Access |
| Issues | Same as KVS Stream Access |
| Fix Needed | Same as KVS Stream Access |
| Formal or Provisional | Provisional |
2.16 KVS WebRTC Access
| Field | Value |
|---|---|
| Source of Truth | D10 (PDF §KVS WebRTC) |
| Current Code | app/api/devices/[id]/webrtc/route.ts, lib/aws/kvs-webrtc.ts |
| Current State | Same as Stream Access |
| Issues | Same as KVS Stream Access. Additionally: WebRTC endpoints expose channel_arn directly in response. |
| Fix Needed | Same as KVS Stream Access |
| Formal or Provisional | Provisional |
2.17 IoT Event Ingestion
| Field | Value |
|---|---|
| Source of Truth | D10 (PDF §IoT Rule / Lambda data flow) |
| Current Code | app/api/internal/aws/iot-event/route.ts |
| Current State | Non-compliant |
| Issues | (1) HMAC timingSafeEqual called without length check — will throw if signature length differs from expected. (2) No timestamp window for replay protection. (3) No dedup key / event id. (4) No audit log for failures/duplicates. (5) No structured error logging. |
| Fix Needed | Add length check before timingSafeEqual; add timestamp window; add dedup via event hash; add audit logging |
| Verification | Test: bad sig returns 401, stale timestamp rejected, replay rejected |
| Affects Production Payment/Auth | No (future IoT path) |
| Formal or Provisional | Provisional — Sample Integration Contract |
2.18 AI Event / Bedrock Nova Lite
| Field | Value |
|---|---|
| Source of Truth | D10 (PDF §AI 事件分类), PDF model ID: us.amazon.nova-lite-v1:0 |
| Current Code | lib/aws/bedrock.ts |
| Current State | Partially compliant |
| Issues | (1) No ownership check before AI analysis. (2) No entitlement check. (3) Uses full video to Bedrock (not thumbnail/frame). (4) Model ID default is us.amazon.nova-pro-v1:0 not us.amazon.nova-lite-v1:0. |
| Fix Needed | Add ownership+entitlement check; change default to lite model for classification; add env for not_configured handling |
| Verification | Test: non-owner gets 403; not_configured returns feature_disabled |
| Affects Production Payment/Auth | No |
| Formal or Provisional | Provisional |
2.19 Entitlement Check
| Field | Value |
|---|---|
| Source of Truth | D11 (Entitlement model), implied by PDF |
| Current Code | lib/entitlement.ts (not yet read, but referenced in activate route) |
| Current State | Not integrated |
| Issues | No unified entitlement check in device service access layer. startTrial() is called on activation but no assertDeviceServiceAccess capability check. |
| Fix Needed | Create assertDeviceServiceAccess() that checks ownership + entitlement |
| Verification | Test: access without entitlement returns 403 |
| Affects Production Payment/Auth | No |
| Formal or Provisional | Provisional |
2.20 AuditLog
| Field | Value |
|---|---|
| Source of Truth | D11 (DataPlanPurchaseAuditLog, OrderAuditLog) |
| Current Code | prisma/schema.prisma — per-model audit logs |
| Current State | Partially compliant |
| Issues | No unified AuditLog model. Each domain has its own audit log (DataPlanPurchaseAuditLog, OrderAuditLog, SimEvent). Missing audit for: device activation/binding, admin auth, IoT event failures. |
| Fix Needed | Add audit log creation in device activation; add IoT event failure audit |
| Verification | Test: activation creates audit entry |
| Affects Production Payment/Auth | No |
| Formal or Provisional | Provisional |
2.21 Checkout Success IDOR
| Field | Value |
|---|---|
| Source of Truth | Security best practice |
| Current Code | app/checkout/success/page.tsx — getOrderByCheckoutSessionId(sessionId) |
| Current State | Non-compliant (security) |
| Issues | The success page does NOT check if the logged-in user owns the order. getOrderByCheckoutSessionId looks up by session_id without userId filter. |
| Fix Needed | Add userId check: if user is logged in, verify order.userId === session.user.id; if not logged in (guest), show minimal info only. |
| Verification | Test: user A cannot see user B's order via session_id |
| Affects Production Payment/Auth | Yes — order PII exposure |
| Formal or Provisional | Formal (security fix) |
3. Required But Missing Environment Variables
The following AWS env vars are required by code but missing from .env.example:
| Env Var | Used In | Status |
|---|---|---|
AWS_COGNITO_USER_POOL_ID | Not yet used | Missing — App auth should reference |
AWS_COGNITO_CLIENT_ID | Not yet used | Missing |
AWS_COGNITO_IDENTITY_POOL_ID | Not yet used | Missing |
AWS_IOT_CREDENTIAL_PROVIDER_ENDPOINT | Not yet used | Missing |
AWS_IOT_ROLE_ALIAS | Not yet used | Missing |
AWS_KVS_PLAYBACK_ROLE_ARN | Not yet used | Missing |
AWS_KVS_WEBRTC_ROLE_ARN | Not yet used | Missing |
Note: Per AGENTS.md §4, these AWS env vars are intended for Cognito-based device/app access but no Cognito pool is configured yet. Current code uses awsConfig = { region } only, relying on the default AWS credential chain (env vars, ~/.aws/credentials, etc.). This is acceptable for provisional readiness but not for production.
4. Required Prisma Schema Changes
| Change | Reason | Risk |
|---|---|---|
DataPlanPurchase.stripeSessionId → @unique | Prevent duplicate purchase from webhook replay | Medium — requires migration |
DataPlanPurchase.stripePaymentId → @unique | Prevent duplicate from payment webhook | Medium — requires migration, some may be null |
5. Summary: Changes Required for Sample Integration Readiness
| # | File | Change | Risk | Category |
|---|---|---|---|---|
| 1 | lib/eiotclub.ts | Redact secret from signature failure logs | Low | Security |
| 2 | app/api/webhooks/eiotclub/route.ts | Redact secret from signature failure logs | Low | Security |
| 3 | app/api/webhooks/data-plan/route.ts | Missing secret → 500; bad sig → 400 | Medium | Payment |
| 4 | prisma/schema.prisma | Add @unique on stripeSessionId | Medium | DB |
| 5 | app/api/devices/activate/route.ts | Create DeviceOwnership; add audit log; add activation proof | Medium | Device |
| 6 | app/api/devices/[id]/*/route.ts | Use unified assertDeviceServiceAccess | Low | Device/KVS |
| 7 | lib/aws/verify-device-access.ts | Add entitlement check; use DeviceOwnership | Low | KVS |
| 8 | lib/aws/iot-event/route.ts | Fix timingSafeEqual; add timestamp window; add dedup | Medium | IoT |
| 9 | app/checkout/success/page.tsx | Add userId ownership check | High | Security/IDOR |
| 10 | lib/commerce.ts | Filter order by userId in getOrderByCheckoutSessionId | High | Security/IDOR |
| 11 | .env.example | Add missing AWS env vars | Low | Config |
| 12 | lib/aws/device-service-access.ts (new) | Create assertDeviceServiceAccess() | Low | New util |
| 13 | Tests (multiple files) | Add coverage for all fixes | Medium | Testing |
6. Decision Record
| Decision | Rationale |
|---|---|
assertDeviceServiceAccess as separate module | Keeps ownership+entitlement check centralized, replaceable |
| Provisional activation endpoint → keep but fix | Need some endpoint for sample integration before formal provisioning contract |
| No new AuditLog model → use per-model logs | Consistent with current pattern; DataPlanPurchaseAuditLog and OrderAuditLog already exist |
Add IotEvent.dedupKey field | Enable reliable dedup for IoT event ingestion |
| No DeviceAwsResource model yet | Current schema embeds AWS fields on Device; adding a separate model would be a cleaner abstraction but unnecessary for sample readiness |