Date: 2026-06-10 Status: Complete. 108 friction points identified, 22 new ADR decisions required. Method: Three parallel architect agents analyzed three user stories against the 23 approved ADR decisions.
Summary
The 23 approved architecture decisions are excellent on security, reliability, and infrastructure. They are thin on user-facing surfaces: the developer experience, the API contracts, the data flow, the app platform, and the plugin system. The architecture designs the engine well but leaves the interfaces to the engine almost entirely unspecified.
Coverage Gaps By Area
| Area | Specification Level | Impact |
| Infrastructure & Security | ~95% specified | Low gap |
| Reliability & Observability | ~90% specified | Low gap |
| End-to-End Data Flow | ~20% specified | Critical gap |
| App Platform (hosting, editor, BFF, SDK) | ~5% specified | Critical gap |
| Plugin System (interfaces, packaging, lifecycle) | ~10% specified | Critical gap |
| API Contracts (URLs, errors, pagination) | ~0% specified | Critical gap |
| CLI Design (commands, storage, profiles) | ~5% specified | Critical gap |
| SDK Architecture (typing, generation, modules) | ~10% specified | High gap |
| First-Run Experience | ~0% specified | Critical gap |
| Outbound Events (webhooks, event catalog) | ~0% specified | Critical gap |
User Story 1: "I Want to Build an App on the Platform"
Personas
- Alex — data engineer building a dashboard on ingested data
- Blake — developer building an internal CRUD tool for their team
- Casey — non-expert with a spreadsheet wanting a simple viewer
CRITICAL Friction Points
| ID | Description | Missing ADR |
| APP-C01 | No first-run experience. docker compose up leaves user with no URL, no wizard, no initial admin creation. Bootstrap auth problem (who creates the first user when no users exist?) is unresolved. | ADR-24 |
| APP-C02 | OP_MASTER_KEY chicken-and-egg. Key must exist before services start but is generated by setup wizard that runs after services start. | ADR-24 |
| APP-C03 | App hosting infrastructure entirely undefined. Where do app builds live? How served? Container per app vs static bundle vs in-process? | ADR-25 |
| APP-C04 | App-SDK API surface entirely undefined. No method signatures, no query syntax, no response shapes. | ADR-26 |
| APP-C05 | BFF mechanics undefined. App Service as BFF stated but not designed. Request flow from browser → App Service → internal services not traced. | ADR-26 |
| APP-C06 | OAuth client lifecycle for platform-hosted apps undefined. Who creates OAuth clients? When? How? | ADR-27 |
| APP-C07 | In-browser code editor entirely undefined. No editor library, file structure, dependency management, or build trigger mechanism. | ADR-28 |
| APP-C08 | Build pipeline undefined. What builds app code? Where do artifacts go? How long does it take? | ADR-25 |
| APP-C09 | End-to-end data flow undefined. Path from Ingestion → mapping → storage → queryable via app has no design. This is the #1 value proposition. | ADR-29 |
| APP-C10 | Real-time event pipeline undefined. Who emits events on data change? How do browser apps subscribe? Where does fan-out happen? | ADR-30 |
| APP-C11 | Non-expert persona (Casey) entirely blocked. No auto-infer, no drag-drop, no CSV-to-chart path. | ADR-25 |
| APP-C12 | App-level access control undefined. Who can access deployed apps? How does the developer configure it? External users? | ADR-27 |
HIGH Friction Points
| ID | Description |
| APP-H01 | Token storage in browser not specified (httpOnly cookies vs localStorage vs sessionStorage) |
| APP-H02 | Dashboard information architecture undefined. What does user see after login? |
| APP-H03 | Connector interface not defined. Connector type referenced but never specified. |
| APP-H04 | Data sync model undefined. Full vs incremental, cursor/watermark, large dataset handling. |
| APP-H05 | App rollback not designed. Users can deploy but cannot safely roll back. |
| APP-H06 | Object storage gap. App build artifacts (binary bundles) have no storage destination. No MinIO/S3 in architecture. |
| APP-H07 | App versioning undefined. No versioning model for app code. |
| APP-H08 | Ontology auto-inference missing. No auto-detect from CSV headers or database introspection. |
| APP-H09 | WebSocket lifecycle undefined. Connection management, load balancing, reconnection. |
| APP-H10 | App debugging experience undefined. No console panel, error overlay, or breakpoints. |
| APP-H11 | Service startup ordering undefined. No depends_on health check strategy. |
| APP-H12 | External app user access undefined. Can non-platform users access apps? |
| APP-H13 | Password reset/email verification not mentioned. Table-stakes auth features. |
MEDIUM Friction Points
| ID | Description |
| APP-M01 | "Ontology" is jargon. Non-experts confused. Should be configurable alias. |
| APP-M02 | CSV parsing configuration undefined. Delimiter auto-detect, malformed row handling. |
| APP-M03 | Real-time backpressure unspecified. Fan-out strategy for high-frequency events. |
| APP-M04 | App preview vs production undefined. Separate "preview" mode? Real data? |
| APP-M05 | Data envelope contract undefined. JSON format for ingested records not specified. |
| APP-M06 | Rate limits for ingestion undefined. What about 50M-row databases? |
| APP-M07 | Ontology code generation timing unclear. When runs? Blocks UI? |
| APP-M08 | App slug uniqueness scope. Per-tenant or global? |
| APP-M09 | Cross-tenant app sharing undefined. |
| APP-M10 | No getting-started content (tutorials, example apps, sample datasets). |
User Story 2: "I Want to Build a Plugin to Extend Functionality"
Personas
- Connector author — adding Shopify data ingestion
- Transformer author — adding custom data masking
- Destination author — adding Snowflake export
- AuthProvider author — adding SAML SSO
- Widget author — adding a custom chart type
CRITICAL Friction Points
| ID | Description | Missing ADR |
| PLG-C01 | No interface method signatures for ANY of the 5 plugin types. A developer cannot write a conforming plugin because no contract exists. | ADR-31 |
| PLG-C02 | No plugin.manifest.json schema. No canonical declaration of what a plugin is. | ADR-32 |
| PLG-C03 | No package format decision. "How do I ship this?" completely unanswered. | ADR-32 |
| PLG-C04 | No op plugin create scaffold command. No entry point for developers. | ADR-35 |
| PLG-C05 | Connector plugin type conflated with pipeline hooks. A source connector IS the data source, not a before/after hook. Architecture needs a distinct integration point. | ADR-36 |
| PLG-C06 | No outbound URL allowlist mechanism for plugin-initiated network calls. Connector plugins can't call external APIs. | ADR-32 |
| PLG-C07 | No per-tenant scoping for injected db API in plugin sandboxes. Security gap — plugin could read other tenants' data. | ADR-33 |
HIGH Friction Points
| ID | Description |
| PLG-H01 | No plugin installation mechanism. op plugin install not specified. |
| PLG-H02 | No code signing or integrity verification for plugin artifacts. |
| PLG-H03 | No credential vault access pattern for plugins. Connectors need external service credentials. |
| PLG-H04 | No plugin configuration schema format. No way to declare what fields user fills in. |
| PLG-H05 | No list of available hook stages. Developers can't know what to register for. |
| PLG-H06 | No plugin error taxonomy. Platform can't distinguish rate limit from auth failure. |
| PLG-H07 | No timeout model for Connector plugins. 30s hook timeout inappropriate for full historical sync. |
| PLG-H08 | Service RBAC matrix missing Plugin Service entries (Ingestion→Plugin, Plugin→Execution). |
| PLG-H09 | No local dev server specification. Named but not designed. |
| PLG-H10 | No hook simulation tool. Testing requires triggering real pipelines. |
| PLG-H11 | No sandbox-consistent local execution. Code may pass locally but fail in production. |
| PLG-H12 | No plugin update/rollback mechanism. |
| PLG-H13 | Widget plugin sandbox model undefined. iframe vs same-origin has major security implications. |
MEDIUM Friction Points
| ID | Description |
| PLG-M01 | No plugin versioning/coexistence model. |
| PLG-M02 | Plugin disable/uninstall state undefined. What happens to mid-execution jobs? |
| PLG-M03 | Data orphan handling on uninstall undefined. |
| PLG-M04 | No plugin-level audit trail. |
| PLG-M05 | Plugin errors don't have defined path to DLQ. |
| PLG-M06 | No OTEL span injection for plugin code. Plugin performance invisible. |
| PLG-M07 | No per-tenant plugin configuration. |
User Story 3: "I Want to Use the API/CLI/SDK"
Personas
- DevOps engineer — infrastructure-as-code, CI/CD
- Data engineer — trigger pipelines from Airflow
- App developer — external app using OnePlatform data
- Platform admin — manage everything via CLI
CRITICAL Friction Points
| ID | Description | Missing ADR |
| API-C01 | Outbound webhook system completely absent. Platform can receive webhooks but cannot push events. Every integration pattern (Airflow, Slack, mobile push) requires push-based notifications. | ADR-37 |
| API-C02 | API contract entirely unspecified. Zero request/response schemas, URL conventions, error format, status codes defined for any endpoint. | ADR-38 |
| API-C03 | Config export/import is one sentence with no specification. Entire IaC use case blocked. | ADR-42 |
| API-C04 | API key scoping unspecified. Every programmatic client gets full admin access, violating least privilege. | ADR-41 |
| API-C05 | CLI credential storage unspecified. Path, format, encryption all undefined. | ADR-39 |
| API-C06 | Ontology-typed SDK unspecified. "Ontology-typed data access" mentioned but mechanism (static codegen? runtime validation?) undefined. | ADR-40 |
| API-C07 | No Terraform provider or Kubernetes operator. Modern DevOps don't use CLIs for infra. | Roadmap |
HIGH Friction Points
| ID | Description |
| API-H01 | OpenAPI spec URL not defined. No /api/v1/openapi.json well-known endpoint. |
| API-H02 | Multi-profile CLI support unspecified. No dev/staging/prod management. |
| API-H03 | Pipeline trigger API contract undefined. Most common programmatic operation has no spec. |
| API-H04 | Real-time event catalog not defined. Event types, schemas, filter options unknown. |
| API-H05 | Browser OAuth flow details missing. Auth endpoint URL, scopes, CORS, token storage. |
| API-H06 | Pagination contract not specified. Every service will implement differently. |
| API-H07 | Error response format not specified. No standard error envelope. |
| API-H08 | No standalone CLI binary. Non-Node.js environments blocked. |
| API-H09 | API versioning communication undefined. No deprecation/sunset headers. |
| API-H10 | Bulk API operations missing. Creating 100 records requires 100 API calls. |
| API-H11 | Rate limit UX incomplete. Users can't discover limits or request higher tier. |
MEDIUM Friction Points
| ID | Description |
| API-M01 | Plugin install source format unspecified. |
| API-M02 | Email delivery dependency not specified (SMTP or fallback). |
| API-M03 | op logs tail --follow not specified. |
| API-M04 | App environment variables not specified. |
| API-M05 | DLQ management CLI not specified. |
| API-M06 | Version pinning for CLI in CI not addressed. |
| API-M07 | SDK error classification (retryable vs not) unspecified. |
| API-M08 | SDK pagination iterator API unspecified. |
| API-M09 | op config diff command missing. |
| API-M10 | Pipeline run cancellation not specified. |
| API-M11 | Connector credential update workflow undefined. |
| API-M12 | OnePlatform as OAuth authorization server not specified. |
Required New ADR Decisions (Consolidated)
The 22 identified gaps consolidate into 13 new architecture decisions (ADR 24–36):
ADR-24: First-Run Experience and Bootstrap
OP_MASTER_KEY generation via init service before other services start - Bootstrap endpoint
POST /api/v1/auth/bootstrap (auto-disables after first use) - Initial admin + tenant creation flow
- Setup wizard screens and URL
- Docker Compose service startup ordering with health checks
- App code storage (MinIO/S3-compatible object store, add to Docker Compose)
- Build pipeline (esbuild in Execution Service sandbox for security)
- Build artifact storage and versioning
- App rollback (pointer change to previous version)
- In-browser editor (Monaco Editor, MIT licensed)
- Virtual file system for apps
- Hot-reload preview mechanism
- External IDE support (
op app dev local workflow) - Auto-generated starter app from ontology
- Minimum viable non-expert experience (auto-infer schema, starter app)
ADR-26: App-SDK and BFF Design
- Complete API surface of
@oneplatform/app-sdk: useQuery(entity, options) — typed data access useMutation(entity) — write operations useSubscription(entity, options) — real-time useUser() — current user context usePermission(action, resource) — permission check useAppStorage(key, default) — per-app/per-user KV - BFF request flow: browser app → App Service proxy → internal services
- Token handling: httpOnly cookies via App Service BFF
- User identity in app context
- RBAC enforcement at BFF layer
ADR-27: App Access Control and OAuth Client Lifecycle
- OAuth client auto-registration on app deploy
- Client ID scheme, redirect URI management
- Token binding to app+user pairs
- Two access modes: Platform-User Only (default) and Public/Guest Access
- Guest role with limited ontology RBAC
- App-level roles separate from platform roles
- Cross-tenant sharing rules
ADR-28: End-to-End Data Flow
- Canonical path: Ingestion → raw staging → Ontology mapping → tenant data tables
- Raw data storage (separate from ontology-mapped data)
- Data envelope format (source metadata, ingestion timestamp, record ID)
- Incremental vs full sync (cursor-based with watermark tracking)
- Large dataset handling (batching, backpressure, progress tracking)
- Connector interface specification (methods, configuration schema)
- Auto-inference of ontology from uploaded data / database introspection
ADR-29: API Contract Standard
- URL convention:
/api/v1/{resource}/{id} - HTTP method semantics (PATCH for partial, PUT for full replace)
- Request/response envelope:
{data: T, meta?: {...}} - Error format:
{error: {code: string, message: string, details?: {}, requestId: string}} - Pagination: cursor-based
{items: T[], nextCursor: string | null, total?: number} - Filter/sort DSL:
?filter[field]=value&sort=-createdAt&fields=id,name - Bulk operations:
POST /api/v1/{resource}/bulk - OpenAPI spec at
GET /api/v1/openapi.json - Deprecation/Sunset headers (RFC 8594)
- Health check standard:
GET /healthz (liveness), GET /readyz (readiness)
ADR-30: Outbound Event System
- Outbound webhook registration CRUD API
- Event catalog (data., pipeline., ingestion., ontology., app., plugin., auth., system.)
- Event schema:
{eventId, eventType, tenantId, timestamp, actor, data} - HMAC-SHA256 signature verification
- Delivery guarantees (at-least-once via BullMQ)
- Retry policy (exponential backoff, max 24h, then dead-letter)
- SSE event replay on reconnection
- Real-time subscription filter syntax
- Connection lifecycle and backpressure
ADR-31: Plugin Interface Specifications
- Complete TypeScript interfaces for all 5 plugin types:
Connector: connect, fetchBatch, subscribeToEvents, disconnect, declareOutputSchema, declareConfigSchema Transformer: transform, transformBatch, declareInputSchema, declareOutputSchema Destination: write, writeStream, declareAcceptedSchema, declareDeliveryGuarantee AuthProvider: validateToken, getAuthorizationUrl, handleCallback, refreshToken, mapClaimsToRoles Widget: render, declareDataRequirements, declareSlot (iframe sandbox with postMessage) - Injected context APIs: CredentialAccessor, FetchProxy, CacheAccessor, PluginLogger, TenantContext, OntologyAccessor
- Plugin error taxonomy: PluginError base + AuthError, RateLimitError, TimeoutError, DataError subtypes
- Complete list of hook stages across all services
- Hook registration API
- Format: signed tarball (bundle.js + plugin.manifest.json + bundle.js.sha256)
- Manifest schema: name, version, type, entrypoint, configSchema, hooks, requiredExternalUrls, requiredApis, minPlatformVersion, permissions
- Code signing: SHA-256 hash in manifest, optional GPG signing
- Outbound URL allowlist (declared in manifest, admin approves on install)
- Plugin instance model (multiple instances with different configs)
- Per-tenant vs platform-wide configuration
- Plugin registry design (deferred but manifest format compatible)
ADR-33: Plugin Lifecycle and Installation
- Installation mechanism:
op plugin install accepts local tarball, URL, or registry slug - Plugin storage after install (Plugin Service database + volume)
- Code distribution: Execution Service fetches from Plugin Service on demand, LRU cache
- Authorization model (platform admin installs, tenant admin configures)
- State machine: draft → installed → enabled → disabled → deprecated → uninstalled
- Graceful disable (in-flight jobs complete, new jobs skip)
- Uninstall requirements (no active jobs, orphaned data prompt)
- Version coexistence (old version held in disabled for 24h rollback window)
- Connector plugin as primary source (separate from hook integration)
- Connector timeout model (configurable per-connector, separate from 30s hook timeout)
- Service RBAC matrix additions: Ingestion→Plugin.listConnectors(), Plugin→Execution.runPlugin()
ADR-34: CLI Design Specification
- ~85 commands across 12 groups (auth, profile, user, ontology, data, ingestion, webhook-out, pipeline, exec, app, plugin, logs, config, status, service)
- Credential storage:
~/.config/op/credentials.json, encrypted via system keychain (keytar) with plain-text fallback + warning - Multi-profile system:
~/.config/op/profiles/ - Environment variable precedence:
--flag > OP_* env vars > profile config > defaults - Output formats:
--output json|table|tsv - Auto-retry on 429 with backoff and progress indicator
- Non-interactive mode flags (
--no-input, --yes) - Tab completion (
op completion bash|zsh|fish) - Standalone binary distribution (GitHub Releases:
op-linux-amd64, op-darwin-arm64, etc.) - Version pinning recommendation for CI
ADR-35: SDK Architecture
- OpenAPI code generator:
@hey-api/openapi-ts (typed, tree-shakeable) - Ontology-typed SDK via
op sdk generate command (tenant-specific codegen) - Module format: ESM primary, CJS secondary, dual-package exports
- Browser vs Node detection (auto-select auth strategy)
- Error taxonomy: typed errors with
isRetryable, statusCode, code - Async iterator pagination:
sdk.data.list() returns AsyncIterable<Page<T>> - Auto-retry: configurable RetryPolicy (retry on 429/503, 3 attempts, exponential backoff)
- Real-time subscriptions:
sdk.realtime.subscribe(filter, handler) with reconnect/replay
ADR-36: Supporting Infrastructure
- API key scoping (data:read, data:write, pipelines:trigger, admin scopes)
- API key rotation workflow with overlap period
- Email delivery: SMTP via
OP_SMTP_* env vars, fallback "link-copy" mode - Predefined role taxonomy (admin, editor, viewer, custom)
- Config export/import specification (YAML, topological sort, conflict resolution modes, dry-run)
- MinIO (MIT, S3-compatible) added to Docker Compose for app builds + file uploads
- CORS policy (
OP_ALLOWED_ORIGINS)
Key Recommendations
-
Write ADR-29 (API Contract Standard) first. Every service implementation bakes in URL conventions, error format, and pagination. Changing later is a platform-wide breaking change.
-
Write ADR-28 (Data Flow) second. This is the #1 value proposition. Without the path from ingestion to queryable data, nothing else matters.
-
Write ADR-31 (Plugin Interfaces) third. The architecture says "built-in features use same interfaces as third-party plugins." Those interfaces must exist before ANY service is implemented.
-
Write ADR-24 (First-Run) early. The bootstrap problem (first user creation, master key generation, startup ordering) must be solved before Docker Compose works.
UX Improvements (beyond fixing gaps)
- Progressive disclosure for ontology editor. Simple mode (table view) + Advanced mode (ER diagram).
- Instant preview with hot reload. Every save triggers incremental esbuild compile (~100ms).
- One-command local dev:
op app dev --app my-dashboard for external IDE workflow. - Guided first-app experience: Sample data → auto-infer schema → generate starter app → deploy in 5 min.
- Query builder panel in app editor for non-coders.
- Connection status indicators throughout the platform (data source sync status, pipeline failures, DLQ).
- Plugin scaffold with sandbox-consistent local dev — if it works locally, it works in production.
Files Reference
| File | Purpose |
docs/decisions/001-architecture-decisions.md | L0 architecture (23 decisions, APPROVED) |
docs/USER-STORIES-ANALYSIS.md | THIS FILE — friction point analysis |
docs/HANDOFF.md | Comprehensive project context |
DEVELOPMENT-PROCESS.md | Development pipeline |
docs/WORKING-STATE.md | Current state tracker |