Add deterministic member app channel API and conformance docs

This commit is contained in:
Joshua 2026-02-17 12:18:01 -08:00
parent 2e8348826f
commit afe14c33d6
10 changed files with 523 additions and 9 deletions

View File

@ -55,6 +55,7 @@ docs/
membership-platform-interfaces.md
conformance/
membership-gating-vectors.md
member-channel-vectors.md
deployment/
README.md
chain-config.template.json
@ -63,12 +64,15 @@ docs/
api/
secret-system.openapi.yaml
marketplace.openapi.yaml
member-channel.openapi.yaml
examples/
secret-system.examples.md
marketplace.examples.md
member-channel.examples.md
handoff/
membership-backend-checklist.md
marketplace-backend-checklist.md
member-channel-backend-checklist.md
schemas/
offer.v1.schema.json
entitlement.v1.schema.json

View File

@ -0,0 +1,103 @@
# Member App Channel API Examples
## Register Device Channel
### Request
```http
POST /member/channel/device/register
Authorization: Bearer <wallet-session>
Content-Type: application/json
{
"wallet": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
"chain_id": 8453,
"device_id": "desktop-7f6f3a9b",
"platform": "desktop",
"app_version": "0.1.0",
"push_provider": "none"
}
```
### Response
```json
{
"channel_binding_id": "ch_01J9A4MQQ6V7H1S0A7R7ME7D4J",
"status": "active",
"poll_interval_seconds": 30,
"server_time": "2026-02-17T20:43:18Z"
}
```
## Poll Events
### Request
```http
GET /member/channel/events?wallet=0x742d35Cc6634C0532925a3b844Bc454e4438f44e&device_id=desktop-7f6f3a9b&cursor=evt_1030&limit=25
Authorization: Bearer <wallet-session>
```
### Response
```json
{
"wallet": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
"device_id": "desktop-7f6f3a9b",
"events": [
{
"event_id": "evt_1031",
"class": "platform_update",
"created_at": "2026-02-17T20:42:11Z",
"title": "Platform update available",
"body": "A deterministic runtime update is ready for your current platform channel.",
"dedupe_key": "platform_update:0.1.1",
"requires_ack": true,
"policy_hash": "sha256:8b903f0d3a...",
"payload": {
"version": "0.1.1",
"channel": "stable"
}
}
],
"next_cursor": "evt_1031",
"server_time": "2026-02-17T20:43:20Z"
}
```
## Acknowledge Event
### Request
```http
POST /member/channel/events/evt_1031/ack
Authorization: Bearer <wallet-session>
Content-Type: application/json
{
"wallet": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
"device_id": "desktop-7f6f3a9b",
"acknowledged_at": "2026-02-17T20:43:25Z"
}
```
### Response
```json
{
"status": "acknowledged",
"event_id": "evt_1031",
"acknowledged_at": "2026-02-17T20:43:25Z"
}
```
## Membership Inactive Error
```json
{
"error": "membership_inactive",
"code": "membership_inactive",
"correlation_id": "req_01J9A4Q9GPDXDNZEWJ2FJS6F5R"
}
```

View File

@ -0,0 +1,311 @@
openapi: 3.1.0
info:
title: EDUT Member App Channel API
version: 1.0.0
description: |
Deterministic member communication channel contract.
App notifications are derived from wallet-authenticated membership and entitlement state.
servers:
- url: https://api.edut.ai
security:
- WalletSession: []
paths:
/member/channel/device/register:
post:
summary: Register or refresh a member app device channel binding.
operationId: registerMemberDevice
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DeviceRegisterRequest'
responses:
'200':
description: Device channel binding active.
content:
application/json:
schema:
$ref: '#/components/schemas/DeviceRegisterResponse'
'403':
description: Membership is not active for this wallet.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/member/channel/device/unregister:
post:
summary: Remove a device channel binding.
operationId: unregisterMemberDevice
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DeviceUnregisterRequest'
responses:
'200':
description: Device channel binding removed.
content:
application/json:
schema:
$ref: '#/components/schemas/DeviceUnregisterResponse'
'404':
description: Device channel binding not found.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/member/channel/events:
get:
summary: Poll deterministic member channel events.
operationId: listMemberChannelEvents
parameters:
- in: query
name: wallet
required: true
schema:
type: string
description: Wallet address tied to session.
- in: query
name: device_id
required: true
schema:
type: string
- in: query
name: cursor
required: false
schema:
type: string
- in: query
name: limit
required: false
schema:
type: integer
minimum: 1
maximum: 100
default: 25
responses:
'200':
description: Event page for in-app inbox rendering.
content:
application/json:
schema:
$ref: '#/components/schemas/EventListResponse'
'403':
description: Membership inactive or suspended.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/member/channel/events/{event_id}/ack:
post:
summary: Acknowledge event delivery/read state.
operationId: acknowledgeMemberChannelEvent
parameters:
- in: path
name: event_id
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/EventAckRequest'
responses:
'200':
description: Event acknowledged.
content:
application/json:
schema:
$ref: '#/components/schemas/EventAckResponse'
'404':
description: Event not found.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
components:
securitySchemes:
WalletSession:
type: http
scheme: bearer
bearerFormat: EDUT-WALLET-SESSION
schemas:
DeviceRegisterRequest:
type: object
required:
- wallet
- chain_id
- device_id
- platform
- app_version
properties:
wallet:
type: string
description: Hex wallet address.
chain_id:
type: integer
description: Active chain id in app session.
device_id:
type: string
description: Stable app-install identifier.
platform:
type: string
enum: [ios, android, desktop]
app_version:
type: string
push_provider:
type: string
enum: [apns, fcm, webpush, none]
default: none
push_token:
type: string
description: Provider token if push is enabled.
DeviceRegisterResponse:
type: object
required:
- channel_binding_id
- status
- poll_interval_seconds
- server_time
properties:
channel_binding_id:
type: string
status:
type: string
enum: [active]
poll_interval_seconds:
type: integer
minimum: 10
server_time:
type: string
format: date-time
DeviceUnregisterRequest:
type: object
required:
- wallet
- device_id
properties:
wallet:
type: string
device_id:
type: string
DeviceUnregisterResponse:
type: object
required:
- status
- wallet
- device_id
properties:
status:
type: string
enum: [removed]
wallet:
type: string
device_id:
type: string
EventListResponse:
type: object
required:
- wallet
- device_id
- events
- next_cursor
- server_time
properties:
wallet:
type: string
device_id:
type: string
events:
type: array
items:
$ref: '#/components/schemas/MemberEvent'
next_cursor:
type: string
nullable: true
server_time:
type: string
format: date-time
MemberEvent:
type: object
required:
- event_id
- class
- created_at
- title
- body
- dedupe_key
- policy_hash
properties:
event_id:
type: string
class:
type: string
enum:
- offer_available
- entitlement_activated
- platform_update
- publisher_update
- membership_policy
created_at:
type: string
format: date-time
title:
type: string
body:
type: string
dedupe_key:
type: string
requires_ack:
type: boolean
default: true
policy_hash:
type: string
payload:
type: object
additionalProperties: true
EventAckRequest:
type: object
required:
- wallet
- device_id
- acknowledged_at
properties:
wallet:
type: string
device_id:
type: string
acknowledged_at:
type: string
format: date-time
EventAckResponse:
type: object
required:
- status
- event_id
- acknowledged_at
properties:
status:
type: string
enum: [acknowledged]
event_id:
type: string
acknowledged_at:
type: string
format: date-time
ErrorResponse:
type: object
required:
- error
- code
properties:
error:
type: string
code:
type: string
correlation_id:
type: string

View File

@ -35,6 +35,14 @@ No active membership -> no app access channel.
2. Delivery payloads must include deterministic event ids for dedupe and audit.
3. Membership revocation/suspension immediately suppresses member-channel delivery.
4. No marketing list fan-out disconnected from entitlement state.
5. Event polling inbox is canonical; push transport is optional acceleration.
## Contract References
1. `docs/api/member-channel.openapi.yaml`
2. `docs/api/examples/member-channel.examples.md`
3. `docs/handoff/member-channel-backend-checklist.md`
4. `docs/conformance/member-channel-vectors.md`
## Non-Goals

View File

@ -0,0 +1,22 @@
# Conformance Vectors: Member App Channel v1
These vectors verify deterministic member communication behavior.
## Vector Set
1. `MC-001` Register device with active membership -> returns `status=active`.
2. `MC-002` Register device with inactive membership -> returns `membership_inactive`.
3. `MC-003` Poll events with valid cursor -> returns monotonic events + next cursor.
4. `MC-004` Poll events with tampered cursor -> fail closed with validation error.
5. `MC-005` Ack existing event -> returns `status=acknowledged`.
6. `MC-006` Ack same event again -> idempotent success, no duplicate side effects.
7. `MC-007` Membership revoked after registration -> subsequent event poll blocked.
8. `MC-008` Push unavailable -> event still available in poll inbox.
9. `MC-009` Dedupe key collision attempt -> backend rejects duplicate event insertion for same member stream.
10. `MC-010` Wallet mismatch between session and payload -> request rejected.
## Pass Criteria
1. All vectors pass in CI and staging.
2. Any failure blocks release per `docs/release-gate.md`.
3. Evidence artifact includes vector id, input, output, and correlation id.

View File

@ -0,0 +1,53 @@
# Backend Handoff Checklist: Member App Channel
This checklist defines backend requirements for app-native member communication.
## Required Endpoints
1. `POST /member/channel/device/register`
2. `POST /member/channel/device/unregister`
3. `GET /member/channel/events`
4. `POST /member/channel/events/{event_id}/ack`
## Contract Source
1. `docs/api/member-channel.openapi.yaml`
2. `docs/api/examples/member-channel.examples.md`
## Deterministic Rules
1. Wallet session is mandatory for all channel calls.
2. Membership status must be active for event delivery.
3. Event ids and dedupe keys are immutable once emitted.
4. Event ordering is monotonic by server sequence.
5. `ack` is idempotent per `event_id` + `device_id`.
## Data Requirements
Persist at minimum:
1. Device channel binding (`wallet`, `device_id`, `platform`, `push_provider`, `push_token`)
2. Membership status snapshot at event emission time
3. Event envelope (`event_id`, class, title/body, payload, policy_hash, dedupe_key)
4. Delivery and ack timestamps
## Security Requirements
1. Rate limit register/events/ack paths.
2. Validate wallet in request matches authenticated session wallet.
3. Reject event polling for suspended/revoked memberships.
4. Protect against cursor tampering and replay.
## Observability Requirements
1. Counter metrics per event class emitted.
2. Counter metrics for successful/failed ack calls.
3. Channel registration churn metrics by platform.
4. Correlation id in all responses and logs.
## Done Criteria
1. App can register channel and poll events deterministically.
2. Push loss does not lose events because inbox polling is canonical.
3. Membership status changes immediately gate event visibility.
4. API behavior matches OpenAPI contract and examples.

View File

@ -11,6 +11,7 @@
1. Intent/verify/quote/confirm/status endpoints.
2. Deterministic state transitions and persistence.
3. Chain verification and policy hash enforcement.
4. Member app channel endpoints for device registration and event polling.
## Runtime/Kernel Responsibilities

View File

@ -13,21 +13,24 @@ This gate controls deploy/no-deploy decisions for membership-gated commerce chan
## Deploy Criteria (All Required)
1. `docs/conformance/membership-gating-vectors.md`: all vectors pass.
2. OpenAPI and implementation remain compatible.
3. Signature replay tests pass.
4. Quote expiry tests pass.
5. Tx mismatch tests pass.
6. Membership gate blocks non-members in all checkout paths.
7. Terms/privacy copy still match utility-access framing.
8. Structured logs and metrics are emitted for each state transition.
2. `docs/conformance/member-channel-vectors.md`: all vectors pass.
3. OpenAPI and implementation remain compatible.
4. Signature replay tests pass.
5. Quote expiry tests pass.
6. Tx mismatch tests pass.
7. Membership gate blocks non-members in all checkout paths.
8. Member channel blocks inactive memberships.
9. Terms/privacy copy still match utility-access framing.
10. Structured logs and metrics are emitted for each state transition.
## No-Deploy Triggers
1. Any conformance vector failure.
2. Any path that allows purchase without active membership.
3. Any activation path that proceeds with non-active entitlement.
4. Any missing audit evidence on successful purchase.
5. Any breaking API change without version bump and migration note.
4. Any member channel path serving events to suspended/revoked memberships.
5. Any missing audit evidence on successful purchase.
6. Any breaking API change without version bump and migration note.
## Evidence Bundle Required for Release

View File

@ -89,3 +89,10 @@ This roadmap is intentionally step-based and dependency-ordered. No timeline com
- visit -> signature -> membership mint -> first entitlement purchase.
2. Tune copy, wallet guidance, and pricing policy using deterministic metrics.
3. Expand issuer ecosystem only after quality and support controls are stable.
## Step 14: Lock Member App Channel
1. Register device channels with wallet-authenticated session.
2. Deliver deterministic event inbox (polling canonical, push optional).
3. Gate event visibility by active membership status.
4. Track ack receipts with immutable event ids and dedupe keys.

View File

@ -40,6 +40,7 @@ Implemented now:
13. Issuer onboarding pack, migration policy, trust page spec, and integration mapping docs.
14. Public `/trust` page scaffold aligned with trust-page spec.
15. Dedicated marketplace OpenAPI contract and examples.
16. Member app channel contract, examples, backend handoff checklist, and conformance vectors.
Remaining in this repo:
@ -52,3 +53,4 @@ Cross-repo dependencies (kernel/backend/contracts):
2. Implement membership contract and membership status reads.
3. Implement checkout APIs and entitlement mint pipeline.
4. Implement runtime entitlement gate and evidence receipts.
5. Implement member app channel APIs and deterministic event stream storage.