Insights separates HTTP delivery, scheduling, queue execution, persistence, platform APIs, and credential storage so each can change or scale independently.
%%{init: {"theme":"base","themeVariables":{"primaryColor":"#DBEAFE","primaryTextColor":"#172554","primaryBorderColor":"#2563EB","secondaryColor":"#DCFCE7","secondaryTextColor":"#14532D","secondaryBorderColor":"#16A34A","tertiaryColor":"#F3E8FF","tertiaryTextColor":"#581C87","tertiaryBorderColor":"#9333EA","lineColor":"#64748B","noteBkgColor":"#FEF3C7","noteTextColor":"#78350F","actorBkg":"#E0E7FF","actorBorder":"#4F46E5","actorTextColor":"#1E1B4B","signalColor":"#475569","signalTextColor":"#334155"}}}%%
flowchart TB
UI[Dashboard / API client] --> HTTP[Vapor HTTP service]
HTTP --> PG[(PostgreSQL)]
SCH[Single scheduler] --> V[(Valkey / Redis)]
HTTP --> V
V --> W[One or more named workers]
W --> API[GitHub / Hugging Face / future platforms]
W --> SP[SecretProvider]
SP -. current adapter .-> TV[Tapis Vault]
W --> PG
| Component | Owns | Does not own |
|---|---|---|
| Controllers | HTTP decoding, validation, responses | Provider-specific secret access |
| Scheduler | Deciding when dispatcher jobs run | Slow platform API calls |
| Dispatchers | Finding eligible records and enqueueing typed jobs | Executing the sync |
| Workers | Claiming and executing queued jobs | Clock evaluation |
| PostgreSQL | Catalog, readings, due dates, watermarks | Queue delivery |
| Valkey | Durable queued payloads | Domain records or metric history |
SecretProvider |
Named credential lifecycle | Metrics or platform routing |
| Platform jobs | Fetching and translating provider data | Choosing the secret backend |
%%{init: {"theme":"base","themeVariables":{"primaryColor":"#DBEAFE","primaryTextColor":"#172554","primaryBorderColor":"#2563EB","secondaryColor":"#DCFCE7","secondaryTextColor":"#14532D","secondaryBorderColor":"#16A34A","tertiaryColor":"#F3E8FF","tertiaryTextColor":"#581C87","tertiaryBorderColor":"#9333EA","lineColor":"#64748B","noteBkgColor":"#FEF3C7","noteTextColor":"#78350F","actorBkg":"#E0E7FF","actorBorder":"#4F46E5","actorTextColor":"#1E1B4B","signalColor":"#475569","signalTextColor":"#334155"}}}%%
sequenceDiagram
participant S as Scheduler
participant D as CollectDueResources
participant DB as PostgreSQL
participant Q as Valkey metrics queue
participant W as Worker
participant P as SecretProvider
participant A as Platform API
S->>D: hourly trigger
D->>DB: nextCollectionAt <= now
D->>Q: dispatch typed resource job
D->>DB: book nextCollectionAt
W->>Q: atomically claim job
W->>P: readSecret(name)
W->>A: fetch complete response set
W->>DB: write snapshots / watermark fold
configure.swift is the one place concrete infrastructure is selected. Consumers use
protocols or framework abstractions. For example, jobs call application.secrets; configuration
chooses TapisClient.Vaults when SECRET_PROVIDER=tapis.
Sources/Insights/
├── Commands/ one-shot operator commands, incl. service-token
├── Controllers/ HTTP boundaries, health probes, dashboard redirect
├── DTOs/ request and public response shapes
├── Errors/ configuration and job failures
├── Middlewares/ authenticators, Require, rate limits, headers, request IDs
├── Migrations/ PostgreSQL schema and development snapshot
├── Models/ Fluent domain persistence
├── Queues/ scheduled sweeps, queue jobs, routing, watermark folds
├── Services/
│ ├── Admins/ who holds administrative access
│ ├── Notifications/ FailureNotifier contract, Slack and noop adapters
│ ├── Secrets/ provider-neutral protocol, redacted value, app storage
│ ├── ServiceTokens/ webhook token claims, signing keyset, mint/revoke/list
│ └── Tapis/ current Tapis Vault adapter and tenant key fetch
└── configure.swift composition root
Middlewares/ holds things that conform to AsyncMiddleware or AsyncBearerAuthenticator, plus
the Authenticatable identities they produce. Anything that merely hangs off Application belongs
with the domain it serves — signing-key lifecycle sits beside the issuer that uses it, and admin
resolution beside the model it reads — because that is where someone looks for it.
Read invariants.md before changing interactions between these components.
%%{init: {"theme":"base","themeVariables":{"primaryColor":"#DBEAFE","primaryTextColor":"#172554","primaryBorderColor":"#2563EB","secondaryColor":"#DCFCE7","secondaryTextColor":"#14532D","secondaryBorderColor":"#16A34A","lineColor":"#64748B"}}}%%
flowchart LR
R[Request] --> SEC[SecurityHeaders + CORS + RequestID]
SEC --> ERR[ErrorMiddleware]
ERR --> F[FileMiddleware]
F --> RL[RateLimiter per IP]
RL --> AU[TapisAuthenticator + ServiceTokenAuthenticator]
AU --> RQ{Require}
RQ --> C[Controller]
C --> DB[(PostgreSQL)]
The first group registers at: .beginning, ahead of ErrorMiddleware, because response headers
are stamped on the way back out — a middleware added later never sees an error response.
The authenticators populate req.auth and never reject; Require reads it and is the only place
401 and 403 are produced. Routes without a Require are public by construction, which is what
serves the dashboard to anonymous callers.
Health probes (/health, /ready) sit outside /api, so orchestrator polling is neither rate
limited nor authenticated.
#icicle-insights# #architecture# #swift# #vapor# #developer-documentation#