Cloud Console Runtime
This document defines cloud runtime behavior for the Lambda-backed kbsc console API.
Endpoints
Tenant-scoped API routes use:
/{account}/{project}/api/config/{account}/{project}/api/issues/{account}/{project}/api/issues/{id}/{account}/{project}/api/events/{account}/{project}/api/events/realtime/{account}/{project}/api/realtime/bootstrap/{account}/{project}/api/auth/bootstrap/api/auth/bootstrap
Realtime Bootstrap Contract
GET /{account}/{project}/api/realtime/bootstrap
Returns JSON:
mode: realtime mode (mqtt_iot)region: AWS region used for IoT WebSocket signingiot_endpoint: AWS IoT data endpoint hostnameiot_wss_url: MQTT-over-WSS URL derived from endpoint (wss://<endpoint>/mqtt)topic: tenant topic (projects/{account}/{project}/events)account: resolved account segment from routeproject: resolved project segment from route
Required environment variables:
KANBUS_IOT_DATA_ENDPOINTAWS_REGION(orAWS_DEFAULT_REGION)
If required variables are missing, the endpoint returns 500 with an error payload.
Auth Bootstrap Contract
GET /{account}/{project}/api/auth/bootstrap (tenant-scoped)
GET /api/auth/bootstrap (global/callback-safe)
Returns JSON with:
mode:none(local) orcognito_pkce(cloud)cognito_domain_urlcognito_client_idcognito_redirect_uricognito_logout_uricognito_issueridentity_pool_idtenant_account_claim_keyandtenant_project_claim_key
Cloud mode requires tenant claim parity (custom:account, custom:project) for protected tenant routes.
Tenant Model
Current cloud tenancy is route + claim scoped:
- Tenant identity comes from URL:
/{account}/{project}/... - Access is allowed only when Cognito claims match route:
custom:account == {account}custom:project == {project}
- A user is currently single-tenant in one session because claims hold one account/project pair.
Practical implications:
- Root route (
/dev/) has no tenant context and is not sufficient for data access. - Use tenant route URLs directly, for example:
/dev/anthus/kanbus/
How A Tenant Project Exists
There is no separate tenant registry yet. A tenant project exists when <account>/<project> is used and data is synced/written.
For Git-backed cloud projects:
- Browser/API calls use tenant route
/{account}/{project}. - Webhook ingress receives tenant coordinates from the URL path:
POST /internal/webhooks/github/{account}/{project}
- Git sync Lambda clones/syncs repo to S3 tarball:
{account}/{project}/{sha}.tar.gz
- EFS writer Lambda extracts tarball to EFS:
/mnt/data/{account}/{project}/repo
- Console APIs read project data from EFS and publish realtime events to:
projects/{account}/{project}/events
Multi-Tenant User Roadmap
Current v1 supports one tenant mapping per user (single custom:account + custom:project).
To support one user across many projects/accounts, add:
- tenant membership source (group mapping or membership table),
GET /api/tenantsdiscovery endpoint,- explicit tenant switch UX in console,
- authorizer/API checks against membership set (not single claim pair).
Operator Runbook (v1)
Create a New Cloud Tenant Project
There is no tenant registry table in v1. A tenant project is created by syncing a repo into the tenant path on EFS and then serving it from the tenant route.
- Pick tenant coordinates:
<account>and<project>. - Register a GitHub repository webhook with payload URL:
{ApiBaseUrl}internal/webhooks/github/<account>/<project>
- Trigger a sync (GitHub push webhook or manual sync path) so git sync and EFS writer materialize:
/mnt/data/<account>/<project>/repo
- Open the tenant URL:
/dev/<account>/<project>/
If repo sync succeeds, the project exists operationally and APIs can serve data.
Grant Browser Access (Cognito)
Browser auth is Cognito Hosted UI with tenant claim parity checks.
- Create/update Cognito user attributes:
custom:account=<account>custom:project=<project>
- User signs in through Hosted UI.
- API and realtime access is allowed only if route tenant matches claims.
- Claim mismatch returns
403for tenant-scoped API calls.
Current v1 constraint: one account/project pair per user session.
Grant CLI MQTT Access (API Token + Custom Authorizer)
CLI auth is non-OAuth API token via IoT custom authorizer.
- Admin user mints token:
kbs cloud token create --base-url <api_base> --id-token <admin_id_token> --account <account> --project <project> --scopes subscribe --days 90
- For mutation clients that must publish, include publish scope:
--scopes publish,subscribe
- Distribute token value securely (shown once at creation).
- Revoke when needed:
kbs cloud token revoke --base-url <api_base> --id-token <admin_id_token> --token-id <token_id>
Two-Mac MQTT Realtime Validation
Use this to verify shared cloud realtime between two machines and external consumers.
On both Macs:
export KANBUS_REALTIME_TRANSPORT=mqtt
export KANBUS_REALTIME_BROKER="mqtts://<iot-endpoint>:443"
export KANBUS_REALTIME_MQTT_CUSTOM_AUTHORIZER_NAME="kanbus-mqtt-token-dev"
export KANBUS_REALTIME_MQTT_API_TOKEN="<tenant_token>"
Mac A (observer):
kbsc --host 0.0.0.0 --port 4242
# optional raw stream observer:
kbs gossip watch --print
Mac B (mutator):
kbs create "MQTT cross-mac test issue" --type task --parent <epic_id>
kbs update <issue_id> --status in_progress
kbs comment <issue_id> "cross-mac realtime validation"
Expected result:
- Mac A
kbscboard updates without local UDS dependency on Mac B. - Any watcher on same tenant topic receives matching
issue.mutatedevents. - If MQTT is unavailable but
kbsandkbscrun on same host, local UDS path still carries updates.
SSE Fallback Behavior
/api/events/realtime is currently a Lambda compatibility alias to /api/events.
Current fallback stream behavior:
- Initial snapshot is emitted immediately as SSE
data:payload. - Server checks for snapshot changes every 15 seconds.
- No-op intervals emit no update payloads.
- Connection uses standard SSE headers:
Content-Type: text/event-streamCache-Control: no-cacheConnection: keep-alive
Client Reconnect Guidance
For cloud clients:
- Attempt MQTT-over-WSS first using
/api/realtime/bootstrap. - If MQTT connect/subscribe fails, fall back to
/api/events/realtimeSSE. - SSE fallback in cloud carries
access_tokenquery auth so browserEventSourcecan connect. - Treat SSE as bounded and reconnect-safe.
- On
error, allow browser/EventSource reconnect behavior to re-establish stream.
Verification
Lambda route and realtime bootstrap tests live in:
rust/src/bin/console_lambda.rs
Run:
cd rust
cargo test --bin console_lambda