Compare commits
1 Commits
docs/feder
...
chore/gate
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6856e6fdf |
2
.npmrc
2
.npmrc
@@ -1 +1 @@
|
||||
@mosaicstack:registry=https://git.mosaicstack.dev/api/packages/mosaicstack/npm/
|
||||
@mosaic:registry=https://git.mosaicstack.dev/api/packages/mosaic/npm/
|
||||
|
||||
@@ -59,7 +59,7 @@ steps:
|
||||
sleep 1
|
||||
done
|
||||
# Run migrations (DATABASE_URL is set in environment above)
|
||||
- pnpm --filter @mosaicstack/db run db:migrate
|
||||
- pnpm --filter @mosaic/db run db:migrate
|
||||
# Run all tests
|
||||
- pnpm test
|
||||
depends_on:
|
||||
|
||||
@@ -33,44 +33,15 @@ steps:
|
||||
- *enable_pnpm
|
||||
# Configure auth for Gitea npm registry
|
||||
- |
|
||||
echo "//git.mosaicstack.dev/api/packages/mosaicstack/npm/:_authToken=$NPM_TOKEN" > ~/.npmrc
|
||||
echo "@mosaicstack:registry=https://git.mosaicstack.dev/api/packages/mosaicstack/npm/" >> ~/.npmrc
|
||||
# Publish non-private packages to Gitea.
|
||||
#
|
||||
# The only publish failure we tolerate is "version already exists" —
|
||||
# that legitimately happens when only some packages were bumped in
|
||||
# the merge. Any other failure (registry 404, auth error, network
|
||||
# error) MUST fail the pipeline loudly: the previous
|
||||
# `|| echo "... continuing"` fallback silently hid a 404 from the
|
||||
# Gitea org rename and caused every @mosaicstack/* publish to fall
|
||||
# on the floor while CI still reported green.
|
||||
- |
|
||||
# Portable sh (Alpine ash) — avoid bashisms like PIPESTATUS.
|
||||
set +e
|
||||
pnpm --filter "@mosaicstack/*" --filter "!@mosaicstack/web" publish --no-git-checks --access public >/tmp/publish.log 2>&1
|
||||
EXIT=$?
|
||||
set -e
|
||||
cat /tmp/publish.log
|
||||
if [ "$EXIT" -eq 0 ]; then
|
||||
echo "[publish] all packages published successfully"
|
||||
exit 0
|
||||
fi
|
||||
# Hard registry / auth / network errors → fatal. Match npm's own
|
||||
# error lines specifically to avoid false positives on arbitrary
|
||||
# log text that happens to contain "E404" etc.
|
||||
if grep -qE "npm (error|ERR!) code (E404|E401|ENEEDAUTH|ECONNREFUSED|ETIMEDOUT|ENOTFOUND)" /tmp/publish.log; then
|
||||
echo "[publish] FATAL: registry/auth/network error detected — failing pipeline" >&2
|
||||
exit 1
|
||||
fi
|
||||
# Only tolerate the explicit "version already published" case.
|
||||
# npm returns this as E403 with body "You cannot publish over..."
|
||||
# or EPUBLISHCONFLICT depending on version.
|
||||
if grep -qE "EPUBLISHCONFLICT|You cannot publish over|previously published" /tmp/publish.log; then
|
||||
echo "[publish] some packages already at this version — continuing (non-fatal)"
|
||||
exit 0
|
||||
fi
|
||||
echo "[publish] FATAL: publish failed with unrecognized error — failing pipeline" >&2
|
||||
exit 1
|
||||
echo "//git.mosaicstack.dev/api/packages/mosaic/npm/:_authToken=$NPM_TOKEN" > ~/.npmrc
|
||||
echo "@mosaic:registry=https://git.mosaicstack.dev/api/packages/mosaic/npm/" >> ~/.npmrc
|
||||
# Publish non-private packages to Gitea (--no-git-checks skips dirty/branch checks in CI)
|
||||
# --filter excludes web (private)
|
||||
- >
|
||||
pnpm --filter "@mosaic/*"
|
||||
--filter "!@mosaic/web"
|
||||
publish --no-git-checks --access public
|
||||
|| echo "[publish] Some packages may already exist at this version — continuing"
|
||||
depends_on:
|
||||
- build
|
||||
|
||||
@@ -103,12 +74,12 @@ steps:
|
||||
- mkdir -p /kaniko/.docker
|
||||
- echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json
|
||||
- |
|
||||
DESTINATIONS="--destination git.mosaicstack.dev/mosaicstack/stack/gateway:sha-${CI_COMMIT_SHA:0:7}"
|
||||
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/mosaic-stack/gateway:sha-${CI_COMMIT_SHA:0:7}"
|
||||
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
|
||||
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/gateway:latest"
|
||||
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/gateway:latest"
|
||||
fi
|
||||
if [ -n "$CI_COMMIT_TAG" ]; then
|
||||
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/gateway:$CI_COMMIT_TAG"
|
||||
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/gateway:$CI_COMMIT_TAG"
|
||||
fi
|
||||
/kaniko/executor --context . --dockerfile docker/gateway.Dockerfile $DESTINATIONS
|
||||
depends_on:
|
||||
@@ -128,12 +99,12 @@ steps:
|
||||
- mkdir -p /kaniko/.docker
|
||||
- echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json
|
||||
- |
|
||||
DESTINATIONS="--destination git.mosaicstack.dev/mosaicstack/stack/web:sha-${CI_COMMIT_SHA:0:7}"
|
||||
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/mosaic-stack/web:sha-${CI_COMMIT_SHA:0:7}"
|
||||
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
|
||||
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/web:latest"
|
||||
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/web:latest"
|
||||
fi
|
||||
if [ -n "$CI_COMMIT_TAG" ]; then
|
||||
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/web:$CI_COMMIT_TAG"
|
||||
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/web:$CI_COMMIT_TAG"
|
||||
fi
|
||||
/kaniko/executor --context . --dockerfile docker/web.Dockerfile $DESTINATIONS
|
||||
depends_on:
|
||||
|
||||
28
AGENTS.md
28
AGENTS.md
@@ -21,11 +21,11 @@ Mosaic Stack is a self-hosted, multi-user AI agent platform. TypeScript monorepo
|
||||
| `apps/web` | Next.js dashboard | React 19, Tailwind |
|
||||
| `packages/types` | Shared TypeScript contracts | class-validator |
|
||||
| `packages/db` | Drizzle ORM schema + migrations | drizzle-orm, postgres |
|
||||
| `packages/auth` | BetterAuth configuration | better-auth, @mosaicstack/db |
|
||||
| `packages/brain` | Data layer (PG-backed) | @mosaicstack/db |
|
||||
| `packages/auth` | BetterAuth configuration | better-auth, @mosaic/db |
|
||||
| `packages/brain` | Data layer (PG-backed) | @mosaic/db |
|
||||
| `packages/queue` | Valkey task queue + MCP | ioredis |
|
||||
| `packages/coord` | Mission coordination | @mosaicstack/queue |
|
||||
| `packages/mosaic` | Unified `mosaic` CLI + TUI | Ink, Pi SDK, commander |
|
||||
| `packages/coord` | Mission coordination | @mosaic/queue |
|
||||
| `packages/cli` | Unified CLI + Pi TUI | Ink, Pi SDK |
|
||||
| `plugins/discord` | Discord channel plugin | discord.js |
|
||||
| `plugins/telegram` | Telegram channel plugin | Telegraf |
|
||||
|
||||
@@ -33,9 +33,9 @@ Mosaic Stack is a self-hosted, multi-user AI agent platform. TypeScript monorepo
|
||||
|
||||
1. Gateway is the single API surface — all clients connect through it
|
||||
2. Pi SDK is ESM-only — gateway and CLI must use ESM
|
||||
3. Socket.IO typed events defined in `@mosaicstack/types` enforce compile-time contracts
|
||||
3. Socket.IO typed events defined in `@mosaic/types` enforce compile-time contracts
|
||||
4. OTEL auto-instrumentation loads before NestJS bootstrap
|
||||
5. BetterAuth manages auth tables; schema defined in `@mosaicstack/db`
|
||||
5. BetterAuth manages auth tables; schema defined in `@mosaic/db`
|
||||
6. Docker Compose provides PG (5433), Valkey (6380), OTEL Collector (4317/4318), Jaeger (16686)
|
||||
7. Explicit `@Inject()` decorators required in NestJS (tsx/esbuild doesn't emit decorator metadata)
|
||||
|
||||
@@ -58,14 +58,14 @@ pnpm typecheck && pnpm lint && pnpm format:check # Quality gates
|
||||
|
||||
The `agent` column specifies the required model for each task. **This is set at task creation by the orchestrator and must not be changed by workers.**
|
||||
|
||||
| Value | When to use | Budget |
|
||||
| --------- | ----------------------------------------------------------- | -------------------------- |
|
||||
| `codex` | All coding tasks (default for implementation) | OpenAI credits — preferred |
|
||||
| `glm-5.1` | Cost-sensitive coding where Codex is unavailable | Z.ai credits |
|
||||
| `haiku` | Review gates, verify tasks, status checks, docs-only | Cheapest Claude tier |
|
||||
| `sonnet` | Complex planning, multi-file reasoning, architecture review | Claude quota |
|
||||
| `opus` | Major cross-cutting architecture decisions ONLY | Most expensive — minimize |
|
||||
| `—` | No preference / auto-select cheapest capable | Pipeline decides |
|
||||
| Value | When to use | Budget |
|
||||
| -------- | ----------------------------------------------------------- | -------------------------- |
|
||||
| `codex` | All coding tasks (default for implementation) | OpenAI credits — preferred |
|
||||
| `glm-5` | Cost-sensitive coding where Codex is unavailable | Z.ai credits |
|
||||
| `haiku` | Review gates, verify tasks, status checks, docs-only | Cheapest Claude tier |
|
||||
| `sonnet` | Complex planning, multi-file reasoning, architecture review | Claude quota |
|
||||
| `opus` | Major cross-cutting architecture decisions ONLY | Most expensive — minimize |
|
||||
| `—` | No preference / auto-select cheapest capable | Pipeline decides |
|
||||
|
||||
Pipeline crons read this column and spawn accordingly. Workers never modify `docs/TASKS.md` — only the orchestrator writes it.
|
||||
|
||||
|
||||
10
CLAUDE.md
10
CLAUDE.md
@@ -10,7 +10,7 @@ Self-hosted, multi-user AI agent platform. TypeScript monorepo.
|
||||
- **Web**: Next.js 16 + React 19 (`apps/web`)
|
||||
- **ORM**: Drizzle ORM + PostgreSQL 17 + pgvector (`packages/db`)
|
||||
- **Auth**: BetterAuth (`packages/auth`)
|
||||
- **Agent**: Pi SDK (`packages/agent`, `packages/mosaic`)
|
||||
- **Agent**: Pi SDK (`packages/agent`, `packages/cli`)
|
||||
- **Queue**: Valkey 8 (`packages/queue`)
|
||||
- **Build**: pnpm workspaces + Turborepo
|
||||
- **CI**: Woodpecker CI
|
||||
@@ -26,13 +26,13 @@ pnpm test # Vitest (all packages)
|
||||
pnpm build # Build all packages
|
||||
|
||||
# Database
|
||||
pnpm --filter @mosaicstack/db db:push # Push schema to PG (dev)
|
||||
pnpm --filter @mosaicstack/db db:generate # Generate migrations
|
||||
pnpm --filter @mosaicstack/db db:migrate # Run migrations
|
||||
pnpm --filter @mosaic/db db:push # Push schema to PG (dev)
|
||||
pnpm --filter @mosaic/db db:generate # Generate migrations
|
||||
pnpm --filter @mosaic/db db:migrate # Run migrations
|
||||
|
||||
# Dev
|
||||
docker compose up -d # Start PG, Valkey, OTEL, Jaeger
|
||||
pnpm --filter @mosaicstack/gateway exec tsx src/main.ts # Start gateway
|
||||
pnpm --filter @mosaic/gateway exec tsx src/main.ts # Start gateway
|
||||
```
|
||||
|
||||
## Conventions
|
||||
|
||||
160
README.md
160
README.md
@@ -7,39 +7,26 @@ Mosaic gives you a unified launcher for Claude Code, Codex, OpenCode, and Pi —
|
||||
## Quick Install
|
||||
|
||||
```bash
|
||||
curl -fsSL https://mosaicstack.dev/install.sh | bash
|
||||
```
|
||||
|
||||
Or use the direct URL:
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://git.mosaicstack.dev/mosaicstack/stack/raw/branch/main/tools/install.sh)
|
||||
```
|
||||
|
||||
The installer auto-launches the setup wizard, which walks you through gateway install and verification. Flags for non-interactive use:
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL …) --yes # Accept all defaults
|
||||
bash <(curl -fsSL …) --yes --no-auto-launch # Install only, skip wizard
|
||||
bash <(curl -fsSL https://git.mosaicstack.dev/mosaic/mosaic-stack/raw/branch/main/tools/install.sh)
|
||||
```
|
||||
|
||||
This installs both components:
|
||||
|
||||
| Component | What | Where |
|
||||
| ----------------------- | ---------------------------------------------------------------- | -------------------- |
|
||||
| **Framework** | Bash launcher, guides, runtime configs, tools, skills | `~/.config/mosaic/` |
|
||||
| **@mosaicstack/mosaic** | Unified `mosaic` CLI — TUI, gateway client, wizard, auto-updater | `~/.npm-global/bin/` |
|
||||
| Component | What | Where |
|
||||
| --------------- | ----------------------------------------------------- | -------------------- |
|
||||
| **Framework** | Bash launcher, guides, runtime configs, tools, skills | `~/.config/mosaic/` |
|
||||
| **@mosaic/cli** | TUI, gateway client, wizard, auto-updater | `~/.npm-global/bin/` |
|
||||
|
||||
After install, the wizard runs automatically or you can invoke it manually:
|
||||
After install, set up your agent identity:
|
||||
|
||||
```bash
|
||||
mosaic wizard # Full guided setup (gateway install → verify)
|
||||
mosaic init # Interactive wizard
|
||||
```
|
||||
|
||||
### Requirements
|
||||
|
||||
- Node.js ≥ 20
|
||||
- npm (for global @mosaicstack/mosaic install)
|
||||
- npm (for global @mosaic/cli install)
|
||||
- One or more runtimes: [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Codex](https://github.com/openai/codex), [OpenCode](https://opencode.ai), or [Pi](https://github.com/mariozechner/pi-coding-agent)
|
||||
|
||||
## Usage
|
||||
@@ -62,32 +49,10 @@ The launcher verifies your config, checks for `SOUL.md`, injects your `AGENTS.md
|
||||
|
||||
```bash
|
||||
mosaic tui # Interactive TUI connected to the gateway
|
||||
mosaic gateway login # Authenticate with a gateway instance
|
||||
mosaic login # Authenticate with a gateway instance
|
||||
mosaic sessions list # List active agent sessions
|
||||
```
|
||||
|
||||
### Gateway Management
|
||||
|
||||
```bash
|
||||
mosaic gateway install # Install and configure the gateway service
|
||||
mosaic gateway verify # Post-install health check
|
||||
mosaic gateway login # Authenticate and store a session token
|
||||
mosaic gateway config rotate-token # Rotate your API token
|
||||
mosaic gateway config recover-token # Recover a token via BetterAuth cookie
|
||||
```
|
||||
|
||||
If you already have a gateway account but no token, use `mosaic gateway config recover-token` to retrieve one without recreating your account.
|
||||
|
||||
### Configuration
|
||||
|
||||
```bash
|
||||
mosaic config show # Print full config as JSON
|
||||
mosaic config get <key> # Read a specific key
|
||||
mosaic config set <key> <val># Write a key
|
||||
mosaic config edit # Open config in $EDITOR
|
||||
mosaic config path # Print config file path
|
||||
```
|
||||
|
||||
### Management
|
||||
|
||||
```bash
|
||||
@@ -100,80 +65,6 @@ mosaic coord init # Initialize a new orchestration mission
|
||||
mosaic prdy init # Create a PRD via guided session
|
||||
```
|
||||
|
||||
### Sub-package Commands
|
||||
|
||||
Each Mosaic sub-package exposes its API surface through the unified CLI:
|
||||
|
||||
```bash
|
||||
# User management
|
||||
mosaic auth users list
|
||||
mosaic auth users create
|
||||
mosaic auth sso
|
||||
|
||||
# Agent brain (projects, missions, tasks)
|
||||
mosaic brain projects
|
||||
mosaic brain missions
|
||||
mosaic brain tasks
|
||||
mosaic brain conversations
|
||||
|
||||
# Agent forge pipeline
|
||||
mosaic forge run
|
||||
mosaic forge status
|
||||
mosaic forge resume
|
||||
mosaic forge personas
|
||||
|
||||
# Structured logging
|
||||
mosaic log tail
|
||||
mosaic log search
|
||||
mosaic log export
|
||||
mosaic log level
|
||||
|
||||
# MACP protocol
|
||||
mosaic macp tasks
|
||||
mosaic macp submit
|
||||
mosaic macp gate
|
||||
mosaic macp events
|
||||
|
||||
# Agent memory
|
||||
mosaic memory search
|
||||
mosaic memory stats
|
||||
mosaic memory insights
|
||||
mosaic memory preferences
|
||||
|
||||
# Task queue (Valkey)
|
||||
mosaic queue list
|
||||
mosaic queue stats
|
||||
mosaic queue pause
|
||||
mosaic queue resume
|
||||
mosaic queue jobs
|
||||
mosaic queue drain
|
||||
|
||||
# Object storage
|
||||
mosaic storage status
|
||||
mosaic storage tier
|
||||
mosaic storage export
|
||||
mosaic storage import
|
||||
mosaic storage migrate
|
||||
```
|
||||
|
||||
### Telemetry
|
||||
|
||||
```bash
|
||||
# Local observability (OTEL / Jaeger)
|
||||
mosaic telemetry local status
|
||||
mosaic telemetry local tail
|
||||
mosaic telemetry local jaeger
|
||||
|
||||
# Remote telemetry (dry-run by default)
|
||||
mosaic telemetry status
|
||||
mosaic telemetry opt-in
|
||||
mosaic telemetry opt-out
|
||||
mosaic telemetry test
|
||||
mosaic telemetry upload # Dry-run unless opted in
|
||||
```
|
||||
|
||||
Consent state is persisted in config. Remote upload is a no-op until you run `mosaic telemetry opt-in`.
|
||||
|
||||
## Development
|
||||
|
||||
### Prerequisites
|
||||
@@ -185,8 +76,8 @@ Consent state is persisted in config. Remote upload is a no-op until you run `mo
|
||||
### Setup
|
||||
|
||||
```bash
|
||||
git clone git@git.mosaicstack.dev:mosaicstack/stack.git
|
||||
cd stack
|
||||
git clone git@git.mosaicstack.dev:mosaic/mosaic-stack.git
|
||||
cd mosaic-stack
|
||||
|
||||
# Start infrastructure (Postgres, Valkey, Jaeger)
|
||||
docker compose up -d
|
||||
@@ -195,7 +86,7 @@ docker compose up -d
|
||||
pnpm install
|
||||
|
||||
# Run migrations
|
||||
pnpm --filter @mosaicstack/db run db:migrate
|
||||
pnpm --filter @mosaic/db run db:migrate
|
||||
|
||||
# Start all services in dev mode
|
||||
pnpm dev
|
||||
@@ -235,12 +126,13 @@ npm packages are published to the Gitea package registry on main merges.
|
||||
## Architecture
|
||||
|
||||
```
|
||||
stack/
|
||||
mosaic-stack/
|
||||
├── apps/
|
||||
│ ├── gateway/ NestJS API + WebSocket hub (Fastify, Socket.IO, OTEL)
|
||||
│ └── web/ Next.js dashboard (React 19, Tailwind)
|
||||
├── packages/
|
||||
│ ├── mosaic/ Unified CLI — TUI, gateway client, wizard, sub-package commands
|
||||
│ ├── cli/ Mosaic CLI — TUI, gateway client, wizard
|
||||
│ ├── mosaic/ Framework — wizard, runtime detection, update checker
|
||||
│ ├── types/ Shared TypeScript contracts (Socket.IO typed events)
|
||||
│ ├── db/ Drizzle ORM schema + migrations (pgvector)
|
||||
│ ├── auth/ BetterAuth configuration
|
||||
@@ -261,7 +153,7 @@ stack/
|
||||
│ ├── macp/ OpenClaw MACP runtime plugin
|
||||
│ └── mosaic-framework/ OpenClaw framework injection plugin
|
||||
├── tools/
|
||||
│ └── install.sh Unified installer (framework + npm CLI, --yes / --no-auto-launch)
|
||||
│ └── install.sh Unified installer (framework + npm CLI)
|
||||
├── scripts/agent/ Agent session lifecycle scripts
|
||||
├── docker-compose.yml Dev infrastructure
|
||||
└── .woodpecker/ CI pipeline configs
|
||||
@@ -271,7 +163,7 @@ stack/
|
||||
|
||||
- **Gateway is the single API surface** — all clients (TUI, web, Discord, Telegram) connect through it
|
||||
- **ESM everywhere** — `"type": "module"`, `.js` extensions in imports, NodeNext resolution
|
||||
- **Socket.IO typed events** — defined in `@mosaicstack/types`, enforced at compile time
|
||||
- **Socket.IO typed events** — defined in `@mosaic/types`, enforced at compile time
|
||||
- **OTEL auto-instrumentation** — loads before NestJS bootstrap
|
||||
- **Explicit `@Inject()` decorators** — required since tsx/esbuild doesn't emit decorator metadata
|
||||
|
||||
@@ -308,13 +200,7 @@ Each stage has a dispatch mode (`exec` for research/review, `yolo` for coding),
|
||||
Run the installer again — it handles upgrades automatically:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://mosaicstack.dev/install.sh | bash
|
||||
```
|
||||
|
||||
Or use the direct URL:
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://git.mosaicstack.dev/mosaicstack/stack/raw/branch/main/tools/install.sh)
|
||||
bash <(curl -fsSL https://git.mosaicstack.dev/mosaic/mosaic-stack/raw/branch/main/tools/install.sh)
|
||||
```
|
||||
|
||||
Or use the CLI:
|
||||
@@ -329,12 +215,10 @@ The CLI also performs a background update check on every invocation (cached for
|
||||
### Installer Flags
|
||||
|
||||
```bash
|
||||
bash tools/install.sh --check # Version check only
|
||||
bash tools/install.sh --framework # Framework only (skip npm CLI)
|
||||
bash tools/install.sh --cli # npm CLI only (skip framework)
|
||||
bash tools/install.sh --ref v1.0 # Install from a specific git ref
|
||||
bash tools/install.sh --yes # Non-interactive, accept all defaults
|
||||
bash tools/install.sh --no-auto-launch # Skip auto-launch of wizard
|
||||
bash tools/install.sh --check # Version check only
|
||||
bash tools/install.sh --framework # Framework only (skip npm CLI)
|
||||
bash tools/install.sh --cli # npm CLI only (skip framework)
|
||||
bash tools/install.sh --ref v1.0 # Install from a specific git ref
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "@mosaicstack/gateway",
|
||||
"version": "0.0.6",
|
||||
"name": "@mosaic/gateway",
|
||||
"version": "0.0.2",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.mosaicstack.dev/mosaicstack/stack.git",
|
||||
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
|
||||
"directory": "apps/gateway"
|
||||
},
|
||||
"type": "module",
|
||||
@@ -15,7 +15,7 @@
|
||||
"dist"
|
||||
],
|
||||
"publishConfig": {
|
||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
|
||||
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -28,28 +28,28 @@
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.80.0",
|
||||
"@fastify/helmet": "^13.0.2",
|
||||
"@mariozechner/pi-ai": "^0.65.0",
|
||||
"@mariozechner/pi-coding-agent": "^0.65.0",
|
||||
"@mariozechner/pi-ai": "~0.57.1",
|
||||
"@mariozechner/pi-coding-agent": "~0.57.1",
|
||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||
"@mosaicstack/auth": "workspace:^",
|
||||
"@mosaicstack/brain": "workspace:^",
|
||||
"@mosaicstack/config": "workspace:^",
|
||||
"@mosaicstack/coord": "workspace:^",
|
||||
"@mosaicstack/db": "workspace:^",
|
||||
"@mosaicstack/discord-plugin": "workspace:^",
|
||||
"@mosaicstack/log": "workspace:^",
|
||||
"@mosaicstack/memory": "workspace:^",
|
||||
"@mosaicstack/queue": "workspace:^",
|
||||
"@mosaicstack/storage": "workspace:^",
|
||||
"@mosaicstack/telegram-plugin": "workspace:^",
|
||||
"@mosaicstack/types": "workspace:^",
|
||||
"@mosaic/auth": "workspace:^",
|
||||
"@mosaic/brain": "workspace:^",
|
||||
"@mosaic/config": "workspace:^",
|
||||
"@mosaic/coord": "workspace:^",
|
||||
"@mosaic/db": "workspace:^",
|
||||
"@mosaic/discord-plugin": "workspace:^",
|
||||
"@mosaic/log": "workspace:^",
|
||||
"@mosaic/memory": "workspace:^",
|
||||
"@mosaic/queue": "workspace:^",
|
||||
"@mosaic/storage": "workspace:^",
|
||||
"@mosaic/telegram-plugin": "workspace:^",
|
||||
"@mosaic/types": "workspace:^",
|
||||
"@nestjs/common": "^11.0.0",
|
||||
"@nestjs/core": "^11.0.0",
|
||||
"@nestjs/platform-fastify": "^11.0.0",
|
||||
"@nestjs/platform-socket.io": "^11.0.0",
|
||||
"@nestjs/throttler": "^6.5.0",
|
||||
"@nestjs/websockets": "^11.0.0",
|
||||
"@opentelemetry/auto-instrumentations-node": "^0.72.0",
|
||||
"@opentelemetry/auto-instrumentations-node": "^0.71.0",
|
||||
"@opentelemetry/exporter-metrics-otlp-http": "^0.213.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.213.0",
|
||||
"@opentelemetry/resources": "^2.6.0",
|
||||
@@ -72,17 +72,11 @@
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/testing": "^11.1.18",
|
||||
"@swc/core": "^1.15.24",
|
||||
"@swc/helpers": "^0.5.21",
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"@types/supertest": "^7.2.0",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"supertest": "^7.2.2",
|
||||
"tsx": "^4.0.0",
|
||||
"typescript": "^5.8.0",
|
||||
"unplugin-swc": "^1.5.9",
|
||||
"vitest": "^2.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { BadRequestException, NotFoundException } from '@nestjs/common';
|
||||
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
||||
import type { ConversationHistoryMessage } from '../agent/agent.service.js';
|
||||
import { ConversationsController } from '../conversations/conversations.controller.js';
|
||||
import type { Message } from '@mosaicstack/brain';
|
||||
import type { Message } from '@mosaic/brain';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shared test data
|
||||
|
||||
@@ -18,13 +18,13 @@
|
||||
*/
|
||||
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { createDb } from '@mosaicstack/db';
|
||||
import { createConversationsRepo } from '@mosaicstack/brain';
|
||||
import { createAgentsRepo } from '@mosaicstack/brain';
|
||||
import { createPreferencesRepo, createInsightsRepo } from '@mosaicstack/memory';
|
||||
import { users, conversations, messages, agents, preferences, insights } from '@mosaicstack/db';
|
||||
import { eq } from '@mosaicstack/db';
|
||||
import type { DbHandle } from '@mosaicstack/db';
|
||||
import { createDb } from '@mosaic/db';
|
||||
import { createConversationsRepo } from '@mosaic/brain';
|
||||
import { createAgentsRepo } from '@mosaic/brain';
|
||||
import { createPreferencesRepo, createInsightsRepo } from '@mosaic/memory';
|
||||
import { users, conversations, messages, agents, preferences, insights } from '@mosaic/db';
|
||||
import { eq } from '@mosaic/db';
|
||||
import type { DbHandle } from '@mosaic/db';
|
||||
|
||||
// ─── Fixed IDs so the afterAll cleanup is deterministic ──────────────────────
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Controller, Get, Inject, UseGuards } from '@nestjs/common';
|
||||
import { sql, type Db } from '@mosaicstack/db';
|
||||
import { createQueue } from '@mosaicstack/queue';
|
||||
import { sql, type Db } from '@mosaic/db';
|
||||
import { createQueue } from '@mosaic/queue';
|
||||
import { DB } from '../database/database.module.js';
|
||||
import { AgentService } from '../agent/agent.service.js';
|
||||
import { ProviderService } from '../agent/provider.service.js';
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { randomBytes, createHash } from 'node:crypto';
|
||||
import { eq, type Db, adminTokens } from '@mosaicstack/db';
|
||||
import { eq, type Db, adminTokens } from '@mosaic/db';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { DB } from '../database/database.module.js';
|
||||
import { AdminGuard } from './admin.guard.js';
|
||||
|
||||
@@ -13,8 +13,8 @@ import {
|
||||
Post,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { eq, type Db, users as usersTable } from '@mosaicstack/db';
|
||||
import type { Auth } from '@mosaicstack/auth';
|
||||
import { eq, type Db, users as usersTable } from '@mosaic/db';
|
||||
import type { Auth } from '@mosaic/auth';
|
||||
import { AUTH } from '../auth/auth.tokens.js';
|
||||
import { DB } from '../database/database.module.js';
|
||||
import { AdminGuard } from './admin.guard.js';
|
||||
|
||||
@@ -8,9 +8,9 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { fromNodeHeaders } from 'better-auth/node';
|
||||
import type { Auth } from '@mosaicstack/auth';
|
||||
import type { Db } from '@mosaicstack/db';
|
||||
import { eq, adminTokens, users as usersTable } from '@mosaicstack/db';
|
||||
import type { Auth } from '@mosaic/auth';
|
||||
import type { Db } from '@mosaic/db';
|
||||
import { eq, adminTokens, users as usersTable } from '@mosaic/db';
|
||||
import type { FastifyRequest } from 'fastify';
|
||||
import { AUTH } from '../auth/auth.tokens.js';
|
||||
import { DB } from '../database/database.module.js';
|
||||
|
||||
@@ -8,13 +8,12 @@ import {
|
||||
Post,
|
||||
} from '@nestjs/common';
|
||||
import { randomBytes, createHash } from 'node:crypto';
|
||||
import { count, eq, type Db, users as usersTable, adminTokens } from '@mosaicstack/db';
|
||||
import type { Auth } from '@mosaicstack/auth';
|
||||
import { count, eq, type Db, users as usersTable, adminTokens } from '@mosaic/db';
|
||||
import type { Auth } from '@mosaic/auth';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { AUTH } from '../auth/auth.tokens.js';
|
||||
import { DB } from '../database/database.module.js';
|
||||
import { BootstrapSetupDto } from './bootstrap.dto.js';
|
||||
import type { BootstrapStatusDto, BootstrapResultDto } from './bootstrap.dto.js';
|
||||
import type { BootstrapSetupDto, BootstrapStatusDto, BootstrapResultDto } from './bootstrap.dto.js';
|
||||
|
||||
@Controller('api/bootstrap')
|
||||
export class BootstrapController {
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
/**
|
||||
* E2E integration test — POST /api/bootstrap/setup
|
||||
*
|
||||
* Regression guard for the `import type { BootstrapSetupDto }` class-erasure
|
||||
* bug (IUV-M01, issue #436).
|
||||
*
|
||||
* When `BootstrapSetupDto` is imported with `import type`, TypeScript erases
|
||||
* the class at compile time. NestJS then sees `Object` as the `@Body()`
|
||||
* metatype, and ValidationPipe with `whitelist:true + forbidNonWhitelisted:true`
|
||||
* treats every property as non-whitelisted, returning:
|
||||
*
|
||||
* 400 { message: ["property email should not exist", "property password should not exist"] }
|
||||
*
|
||||
* The fix is a plain value import (`import { BootstrapSetupDto }`), which
|
||||
* preserves the class reference so Nest can read the class-validator decorators.
|
||||
*
|
||||
* This test MUST fail if `import type` is re-introduced on `BootstrapSetupDto`.
|
||||
* A controller unit test that constructs ValidationPipe manually won't catch
|
||||
* this — only the real DI binding path exercises the metatype lookup.
|
||||
*/
|
||||
|
||||
import 'reflect-metadata';
|
||||
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { ValidationPipe, type INestApplication } from '@nestjs/common';
|
||||
import { FastifyAdapter, type NestFastifyApplication } from '@nestjs/platform-fastify';
|
||||
import request from 'supertest';
|
||||
import { BootstrapController } from './bootstrap.controller.js';
|
||||
import type { BootstrapResultDto } from './bootstrap.dto.js';
|
||||
|
||||
// ─── Minimal mock dependencies ───────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* We use explicit `@Inject(AUTH)` / `@Inject(DB)` in the controller so we
|
||||
* can provide mock values by token without spinning up the real DB or Auth.
|
||||
*/
|
||||
import { AUTH } from '../auth/auth.tokens.js';
|
||||
import { DB } from '../database/database.module.js';
|
||||
|
||||
const MOCK_USER_ID = 'mock-user-id-001';
|
||||
|
||||
const mockAuth = {
|
||||
api: {
|
||||
createUser: () =>
|
||||
Promise.resolve({
|
||||
user: {
|
||||
id: MOCK_USER_ID,
|
||||
name: 'Admin',
|
||||
email: 'admin@example.com',
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
// Override db.select() so the second query (verify user exists) returns a user.
|
||||
// The bootstrap controller calls select().from() twice:
|
||||
// 1. count() to check zero users → returns [{total: 0}]
|
||||
// 2. select().where().limit() → returns [the created user]
|
||||
let selectCallCount = 0;
|
||||
const mockDbWithUser = {
|
||||
select: () => {
|
||||
selectCallCount++;
|
||||
return {
|
||||
from: () => {
|
||||
if (selectCallCount === 1) {
|
||||
// First call: count — zero users
|
||||
return Promise.resolve([{ total: 0 }]);
|
||||
}
|
||||
// Subsequent calls: return a mock user row
|
||||
return {
|
||||
where: () => ({
|
||||
limit: () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
id: MOCK_USER_ID,
|
||||
name: 'Admin',
|
||||
email: 'admin@example.com',
|
||||
role: 'admin',
|
||||
},
|
||||
]),
|
||||
}),
|
||||
};
|
||||
},
|
||||
};
|
||||
},
|
||||
update: () => ({
|
||||
set: () => ({
|
||||
where: () => Promise.resolve([]),
|
||||
}),
|
||||
}),
|
||||
insert: () => ({
|
||||
values: () => ({
|
||||
returning: () =>
|
||||
Promise.resolve([
|
||||
{
|
||||
id: 'token-id-001',
|
||||
label: 'Initial setup token',
|
||||
},
|
||||
]),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
// ─── Test suite ───────────────────────────────────────────────────────────────
|
||||
|
||||
describe('POST /api/bootstrap/setup — ValidationPipe DTO binding', () => {
|
||||
let app: INestApplication;
|
||||
|
||||
beforeAll(async () => {
|
||||
selectCallCount = 0;
|
||||
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
controllers: [BootstrapController],
|
||||
providers: [
|
||||
{ provide: AUTH, useValue: mockAuth },
|
||||
{ provide: DB, useValue: mockDbWithUser },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
app = moduleRef.createNestApplication<NestFastifyApplication>(new FastifyAdapter());
|
||||
|
||||
// Mirror main.ts configuration exactly — this is what reproduced the 400.
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
whitelist: true,
|
||||
forbidNonWhitelisted: true,
|
||||
transform: true,
|
||||
}),
|
||||
);
|
||||
|
||||
await app.init();
|
||||
// Fastify requires waiting for the adapter to be ready
|
||||
await app.getHttpAdapter().getInstance().ready();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it('returns 201 (not 400) when a valid {name, email, password} body is sent', async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post('/api/bootstrap/setup')
|
||||
.send({ name: 'Admin', email: 'admin@example.com', password: 'password123' })
|
||||
.set('Content-Type', 'application/json');
|
||||
|
||||
// Before the fix (import type), Nest ValidationPipe returned 400 with
|
||||
// "property email should not exist" / "property password should not exist"
|
||||
// because the DTO class was erased and every field looked non-whitelisted.
|
||||
expect(res.status).not.toBe(400);
|
||||
expect(res.status).toBe(201);
|
||||
const body = res.body as BootstrapResultDto;
|
||||
expect(body.user).toBeDefined();
|
||||
expect(body.user.email).toBe('admin@example.com');
|
||||
expect(body.token).toBeDefined();
|
||||
expect(body.token.plaintext).toBeDefined();
|
||||
});
|
||||
|
||||
it('returns 400 when extra forbidden properties are sent', async () => {
|
||||
// This proves ValidationPipe IS active and working (forbidNonWhitelisted).
|
||||
const res = await request(app.getHttpServer())
|
||||
.post('/api/bootstrap/setup')
|
||||
.send({
|
||||
name: 'Admin',
|
||||
email: 'admin@example.com',
|
||||
password: 'password123',
|
||||
extraField: 'should-be-rejected',
|
||||
})
|
||||
.set('Content-Type', 'application/json');
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it('returns 400 when email is invalid', async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post('/api/bootstrap/setup')
|
||||
.send({ name: 'Admin', email: 'not-an-email', password: 'password123' })
|
||||
.set('Content-Type', 'application/json');
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it('returns 400 when password is too short', async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post('/api/bootstrap/setup')
|
||||
.send({ name: 'Admin', email: 'admin@example.com', password: 'short' })
|
||||
.set('Content-Type', 'application/json');
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
});
|
||||
@@ -62,7 +62,7 @@ function restoreEnv(saved: Map<EnvKey, string | undefined>): void {
|
||||
}
|
||||
|
||||
function makeRegistry(): ModelRegistry {
|
||||
return ModelRegistry.inMemory(AuthStorage.inMemory());
|
||||
return new ModelRegistry(AuthStorage.inMemory());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { RoutingService } from '../routing.service.js';
|
||||
import type { ModelInfo } from '@mosaicstack/types';
|
||||
import type { ModelInfo } from '@mosaic/types';
|
||||
|
||||
const mockModels: ModelInfo[] = [
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
IProviderAdapter,
|
||||
ModelInfo,
|
||||
ProviderHealth,
|
||||
} from '@mosaicstack/types';
|
||||
} from '@mosaic/types';
|
||||
|
||||
/**
|
||||
* Anthropic provider adapter.
|
||||
|
||||
@@ -6,7 +6,7 @@ import type {
|
||||
IProviderAdapter,
|
||||
ModelInfo,
|
||||
ProviderHealth,
|
||||
} from '@mosaicstack/types';
|
||||
} from '@mosaic/types';
|
||||
|
||||
/** Embedding models that Ollama ships with out of the box */
|
||||
const OLLAMA_EMBEDDING_MODELS: ReadonlyArray<{
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
IProviderAdapter,
|
||||
ModelInfo,
|
||||
ProviderHealth,
|
||||
} from '@mosaicstack/types';
|
||||
} from '@mosaic/types';
|
||||
|
||||
/**
|
||||
* OpenAI provider adapter.
|
||||
|
||||
@@ -6,7 +6,7 @@ import type {
|
||||
IProviderAdapter,
|
||||
ModelInfo,
|
||||
ProviderHealth,
|
||||
} from '@mosaicstack/types';
|
||||
} from '@mosaic/types';
|
||||
|
||||
const OPENROUTER_BASE_URL = 'https://openrouter.ai/api/v1';
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import type {
|
||||
IProviderAdapter,
|
||||
ModelInfo,
|
||||
ProviderHealth,
|
||||
} from '@mosaicstack/types';
|
||||
} from '@mosaic/types';
|
||||
import { getModelCapability } from '../model-capabilities.js';
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
Post,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import type { Brain } from '@mosaicstack/brain';
|
||||
import type { Brain } from '@mosaic/brain';
|
||||
import { BRAIN } from '../brain/brain.tokens.js';
|
||||
import { AuthGuard } from '../auth/auth.guard.js';
|
||||
import { CurrentUser } from '../auth/current-user.decorator.js';
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
type AgentSessionEvent,
|
||||
type ToolDefinition,
|
||||
} from '@mariozechner/pi-coding-agent';
|
||||
import type { Brain } from '@mosaicstack/brain';
|
||||
import type { Memory } from '@mosaicstack/memory';
|
||||
import type { Brain } from '@mosaic/brain';
|
||||
import type { Memory } from '@mosaic/memory';
|
||||
import { BRAIN } from '../brain/brain.tokens.js';
|
||||
import { MEMORY } from '../memory/memory.tokens.js';
|
||||
import { EmbeddingService } from '../memory/embedding.service.js';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ModelCapability } from '@mosaicstack/types';
|
||||
import type { ModelCapability } from '@mosaic/types';
|
||||
|
||||
/**
|
||||
* Comprehensive capability matrix for all target models.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'node:crypto';
|
||||
import type { Db } from '@mosaicstack/db';
|
||||
import { providerCredentials, eq, and } from '@mosaicstack/db';
|
||||
import type { Db } from '@mosaic/db';
|
||||
import { providerCredentials, eq, and } from '@mosaic/db';
|
||||
import { DB } from '../database/database.module.js';
|
||||
import type { ProviderCredentialSummaryDto } from './provider-credentials.dto.js';
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import type {
|
||||
ModelInfo,
|
||||
ProviderHealth,
|
||||
ProviderInfo,
|
||||
} from '@mosaicstack/types';
|
||||
} from '@mosaic/types';
|
||||
import {
|
||||
AnthropicAdapter,
|
||||
OllamaAdapter,
|
||||
@@ -67,7 +67,7 @@ export class ProviderService implements OnModuleInit, OnModuleDestroy {
|
||||
|
||||
async onModuleInit(): Promise<void> {
|
||||
const authStorage = AuthStorage.inMemory();
|
||||
this.registry = ModelRegistry.inMemory(authStorage);
|
||||
this.registry = new ModelRegistry(authStorage);
|
||||
|
||||
// Build the default set of adapters that rely on the registry
|
||||
this.adapters = [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Body, Controller, Delete, Get, Inject, Param, Post, UseGuards } from '@nestjs/common';
|
||||
import type { RoutingCriteria } from '@mosaicstack/types';
|
||||
import type { RoutingCriteria } from '@mosaic/types';
|
||||
import { AuthGuard } from '../auth/auth.guard.js';
|
||||
import { CurrentUser } from '../auth/current-user.decorator.js';
|
||||
import { ProviderService } from './provider.service.js';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import type { ModelInfo } from '@mosaicstack/types';
|
||||
import type { RoutingCriteria, RoutingResult, CostTier } from '@mosaicstack/types';
|
||||
import type { ModelInfo } from '@mosaic/types';
|
||||
import type { RoutingCriteria, RoutingResult, CostTier } from '@mosaic/types';
|
||||
import { ProviderService } from './provider.service.js';
|
||||
|
||||
/** Per-million-token cost thresholds for tier classification */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Inject, Injectable, Logger, type OnModuleInit } from '@nestjs/common';
|
||||
import { routingRules, type Db, sql } from '@mosaicstack/db';
|
||||
import { routingRules, type Db, sql } from '@mosaic/db';
|
||||
import { DB } from '../../database/database.module.js';
|
||||
import type { RoutingCondition, RoutingAction } from './routing.types.js';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import { routingRules, type Db, and, asc, eq, or } from '@mosaicstack/db';
|
||||
import { routingRules, type Db, and, asc, eq, or } from '@mosaic/db';
|
||||
import { DB } from '../../database/database.module.js';
|
||||
import { ProviderService } from '../provider.service.js';
|
||||
import { classifyTask } from './task-classifier.js';
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
Post,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { routingRules, type Db, and, asc, eq, or, inArray } from '@mosaicstack/db';
|
||||
import { routingRules, type Db, and, asc, eq, or, inArray } from '@mosaic/db';
|
||||
import { DB } from '../../database/database.module.js';
|
||||
import { AuthGuard } from '../../auth/auth.guard.js';
|
||||
import { CurrentUser } from '../../auth/current-user.decorator.js';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Routing engine types — M4-002 (condition types) and M4-003 (action types).
|
||||
*
|
||||
* These types are re-exported from `@mosaicstack/types` for shared use across packages.
|
||||
* These types are re-exported from `@mosaic/types` for shared use across packages.
|
||||
*/
|
||||
|
||||
// ─── Classification primitives ───────────────────────────────────────────────
|
||||
@@ -23,7 +23,7 @@ export type Domain = 'frontend' | 'backend' | 'devops' | 'docs' | 'general';
|
||||
|
||||
/**
|
||||
* Cost tier for model selection.
|
||||
* Extends the existing `CostTier` in `@mosaicstack/types` with `local` for self-hosted models.
|
||||
* Extends the existing `CostTier` in `@mosaic/types` with `local` for self-hosted models.
|
||||
*/
|
||||
export type CostTier = 'cheap' | 'standard' | 'premium' | 'local';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Type } from '@sinclair/typebox';
|
||||
import type { ToolDefinition } from '@mariozechner/pi-coding-agent';
|
||||
import type { Brain } from '@mosaicstack/brain';
|
||||
import type { Brain } from '@mosaic/brain';
|
||||
|
||||
export function createBrainTools(brain: Brain): ToolDefinition[] {
|
||||
const listProjects: ToolDefinition = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Type } from '@sinclair/typebox';
|
||||
import type { ToolDefinition } from '@mariozechner/pi-coding-agent';
|
||||
import type { Memory } from '@mosaicstack/memory';
|
||||
import type { EmbeddingProvider } from '@mosaicstack/memory';
|
||||
import type { Memory } from '@mosaic/memory';
|
||||
import type { EmbeddingProvider } from '@mosaic/memory';
|
||||
|
||||
/**
|
||||
* Create memory tools bound to the session's authenticated userId.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
import { toNodeHandler } from 'better-auth/node';
|
||||
import type { Auth } from '@mosaicstack/auth';
|
||||
import type { Auth } from '@mosaic/auth';
|
||||
import type { NestFastifyApplication } from '@nestjs/platform-fastify';
|
||||
import { AUTH } from './auth.tokens.js';
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { fromNodeHeaders } from 'better-auth/node';
|
||||
import type { Auth } from '@mosaicstack/auth';
|
||||
import type { Auth } from '@mosaic/auth';
|
||||
import type { FastifyRequest } from 'fastify';
|
||||
import { AUTH } from './auth.tokens.js';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { createAuth, type Auth } from '@mosaicstack/auth';
|
||||
import type { Db } from '@mosaicstack/db';
|
||||
import { createAuth, type Auth } from '@mosaic/auth';
|
||||
import type { Db } from '@mosaic/db';
|
||||
import { DB } from '../database/database.module.js';
|
||||
import { AUTH } from './auth.tokens.js';
|
||||
import { SsoController } from './sso.controller.js';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { buildSsoDiscovery, type SsoProviderDiscovery } from '@mosaicstack/auth';
|
||||
import { buildSsoDiscovery, type SsoProviderDiscovery } from '@mosaic/auth';
|
||||
|
||||
@Controller('api/sso/providers')
|
||||
export class SsoController {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { createBrain, type Brain } from '@mosaicstack/brain';
|
||||
import type { Db } from '@mosaicstack/db';
|
||||
import { createBrain, type Brain } from '@mosaic/brain';
|
||||
import type { Db } from '@mosaic/db';
|
||||
import { DB } from '../database/database.module.js';
|
||||
import { BRAIN } from './brain.tokens.js';
|
||||
|
||||
|
||||
@@ -11,15 +11,15 @@ import {
|
||||
} from '@nestjs/websockets';
|
||||
import { Server, Socket } from 'socket.io';
|
||||
import type { AgentSessionEvent } from '@mariozechner/pi-coding-agent';
|
||||
import type { Auth } from '@mosaicstack/auth';
|
||||
import type { Brain } from '@mosaicstack/brain';
|
||||
import type { Auth } from '@mosaic/auth';
|
||||
import type { Brain } from '@mosaic/brain';
|
||||
import type {
|
||||
SetThinkingPayload,
|
||||
SlashCommandPayload,
|
||||
SystemReloadPayload,
|
||||
RoutingDecisionInfo,
|
||||
AbortPayload,
|
||||
} from '@mosaicstack/types';
|
||||
} from '@mosaic/types';
|
||||
import { AgentService, type ConversationHistoryMessage } from '../agent/agent.service.js';
|
||||
import { AUTH } from '../auth/auth.tokens.js';
|
||||
import { BRAIN } from '../brain/brain.tokens.js';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { CommandExecutorService } from './command-executor.service.js';
|
||||
import type { SlashCommandPayload } from '@mosaicstack/types';
|
||||
import type { SlashCommandPayload } from '@mosaic/types';
|
||||
|
||||
// Minimal mock implementations
|
||||
const mockRegistry = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { forwardRef, Inject, Injectable, Logger, Optional } from '@nestjs/common';
|
||||
import type { QueueHandle } from '@mosaicstack/queue';
|
||||
import type { Brain } from '@mosaicstack/brain';
|
||||
import type { SlashCommandPayload, SlashCommandResultPayload } from '@mosaicstack/types';
|
||||
import type { QueueHandle } from '@mosaic/queue';
|
||||
import type { Brain } from '@mosaic/brain';
|
||||
import type { SlashCommandPayload, SlashCommandResultPayload } from '@mosaic/types';
|
||||
import { AgentService } from '../agent/agent.service.js';
|
||||
import { ChatGateway } from '../chat/chat.gateway.js';
|
||||
import { SessionGCService } from '../gc/session-gc.service.js';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { CommandRegistryService } from './command-registry.service.js';
|
||||
import type { CommandDef } from '@mosaicstack/types';
|
||||
import type { CommandDef } from '@mosaic/types';
|
||||
|
||||
const mockCmd: CommandDef = {
|
||||
name: 'test',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable, type OnModuleInit } from '@nestjs/common';
|
||||
import type { CommandDef, CommandManifest } from '@mosaicstack/types';
|
||||
import type { CommandDef, CommandManifest } from '@mosaic/types';
|
||||
|
||||
@Injectable()
|
||||
export class CommandRegistryService implements OnModuleInit {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { CommandRegistryService } from './command-registry.service.js';
|
||||
import { CommandExecutorService } from './command-executor.service.js';
|
||||
import type { SlashCommandPayload } from '@mosaicstack/types';
|
||||
import type { SlashCommandPayload } from '@mosaic/types';
|
||||
|
||||
// ─── Mocks ───────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { forwardRef, Inject, Module, type OnApplicationShutdown } from '@nestjs/common';
|
||||
import { createQueue, type QueueHandle } from '@mosaicstack/queue';
|
||||
import { createQueue, type QueueHandle } from '@mosaic/queue';
|
||||
import { ChatModule } from '../chat/chat.module.js';
|
||||
import { GCModule } from '../gc/gc.module.js';
|
||||
import { ReloadModule } from '../reload/reload.module.js';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { loadConfig, type MosaicConfig } from '@mosaicstack/config';
|
||||
import { loadConfig, type MosaicConfig } from '@mosaic/config';
|
||||
|
||||
export const MOSAIC_CONFIG = 'MOSAIC_CONFIG';
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import type { Brain } from '@mosaicstack/brain';
|
||||
import type { Brain } from '@mosaic/brain';
|
||||
import { BRAIN } from '../brain/brain.tokens.js';
|
||||
import { AuthGuard } from '../auth/auth.guard.js';
|
||||
import { CurrentUser } from '../auth/current-user.decorator.js';
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
type MissionStatusSummary,
|
||||
type MissionTask,
|
||||
type TaskDetail,
|
||||
} from '@mosaicstack/coord';
|
||||
} from '@mosaic/coord';
|
||||
import { promises as fs } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ import { mkdirSync } from 'node:fs';
|
||||
import { homedir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { Global, Inject, Module, type OnApplicationShutdown } from '@nestjs/common';
|
||||
import { createDb, createPgliteDb, type Db, type DbHandle } from '@mosaicstack/db';
|
||||
import { createStorageAdapter, type StorageAdapter } from '@mosaicstack/storage';
|
||||
import type { MosaicConfig } from '@mosaicstack/config';
|
||||
import { createDb, createPgliteDb, type Db, type DbHandle } from '@mosaic/db';
|
||||
import { createStorageAdapter, type StorageAdapter } from '@mosaic/storage';
|
||||
import type { MosaicConfig } from '@mosaic/config';
|
||||
import { MOSAIC_CONFIG } from '../config/config.module.js';
|
||||
|
||||
export const DB_HANDLE = 'DB_HANDLE';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Module, type OnApplicationShutdown, Inject } from '@nestjs/common';
|
||||
import { createQueue, type QueueHandle } from '@mosaicstack/queue';
|
||||
import { createQueue, type QueueHandle } from '@mosaic/queue';
|
||||
import { SessionGCService } from './session-gc.service.js';
|
||||
import { REDIS } from './gc.tokens.js';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import type { QueueHandle } from '@mosaicstack/queue';
|
||||
import type { LogService } from '@mosaicstack/log';
|
||||
import type { QueueHandle } from '@mosaic/queue';
|
||||
import type { LogService } from '@mosaic/log';
|
||||
import { SessionGCService } from './session-gc.service.js';
|
||||
|
||||
type MockRedis = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Inject, Injectable, Logger, type OnModuleInit } from '@nestjs/common';
|
||||
import type { QueueHandle } from '@mosaicstack/queue';
|
||||
import type { LogService } from '@mosaicstack/log';
|
||||
import type { QueueHandle } from '@mosaic/queue';
|
||||
import type { LogService } from '@mosaic/log';
|
||||
import { LOG_SERVICE } from '../log/log.tokens.js';
|
||||
import { REDIS } from './gc.tokens.js';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Body, Controller, Get, Inject, Param, Post, Query, UseGuards } from '@nestjs/common';
|
||||
import type { LogService } from '@mosaicstack/log';
|
||||
import type { LogService } from '@mosaic/log';
|
||||
import { LOG_SERVICE } from './log.tokens.js';
|
||||
import { AuthGuard } from '../auth/auth.guard.js';
|
||||
import type { IngestLogDto, QueryLogsDto } from './log.dto.js';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { createLogService, type LogService } from '@mosaicstack/log';
|
||||
import type { Db } from '@mosaicstack/db';
|
||||
import { createLogService, type LogService } from '@mosaic/log';
|
||||
import type { Db } from '@mosaic/db';
|
||||
import { DB } from '../database/database.module.js';
|
||||
import { LOG_SERVICE } from './log.tokens.js';
|
||||
import { LogController } from './log.controller.js';
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import type { LogService } from '@mosaicstack/log';
|
||||
import type { Memory } from '@mosaicstack/memory';
|
||||
import type { LogService } from '@mosaic/log';
|
||||
import type { Memory } from '@mosaic/memory';
|
||||
import { LOG_SERVICE } from './log.tokens.js';
|
||||
import { MEMORY } from '../memory/memory.tokens.js';
|
||||
import { EmbeddingService } from '../memory/embedding.service.js';
|
||||
import type { Db } from '@mosaicstack/db';
|
||||
import { sql, summarizationJobs } from '@mosaicstack/db';
|
||||
import type { Db } from '@mosaic/db';
|
||||
import { sql, summarizationJobs } from '@mosaic/db';
|
||||
import { DB } from '../database/database.module.js';
|
||||
|
||||
const SUMMARIZATION_PROMPT = `You are a knowledge extraction assistant. Given the following agent interaction logs, extract the key decisions, learnings, and patterns. Output a concise summary (2-4 sentences) that captures the most important information for future reference. Focus on actionable insights, not raw events.
|
||||
|
||||
@@ -19,7 +19,7 @@ import { NestFactory } from '@nestjs/core';
|
||||
import { Logger, ValidationPipe } from '@nestjs/common';
|
||||
import { FastifyAdapter, type NestFastifyApplication } from '@nestjs/platform-fastify';
|
||||
import helmet from '@fastify/helmet';
|
||||
import { listSsoStartupWarnings } from '@mosaicstack/auth';
|
||||
import { listSsoStartupWarnings } from '@mosaic/auth';
|
||||
import { AppModule } from './app.module.js';
|
||||
import { mountAuthHandler } from './auth/auth.controller.js';
|
||||
import { mountMcpHandler } from './mcp/mcp.controller.js';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { fromNodeHeaders } from 'better-auth/node';
|
||||
import type { Auth } from '@mosaicstack/auth';
|
||||
import type { Auth } from '@mosaic/auth';
|
||||
import type { NestFastifyApplication } from '@nestjs/platform-fastify';
|
||||
import type { McpService } from './mcp.service.js';
|
||||
import { AUTH } from '../auth/auth.tokens.js';
|
||||
|
||||
@@ -3,8 +3,8 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { z } from 'zod';
|
||||
import type { Brain } from '@mosaicstack/brain';
|
||||
import type { Memory } from '@mosaicstack/memory';
|
||||
import type { Brain } from '@mosaic/brain';
|
||||
import type { Memory } from '@mosaic/memory';
|
||||
import { BRAIN } from '../brain/brain.tokens.js';
|
||||
import { MEMORY } from '../memory/memory.tokens.js';
|
||||
import { EmbeddingService } from '../memory/embedding.service.js';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import type { EmbeddingProvider } from '@mosaicstack/memory';
|
||||
import type { EmbeddingProvider } from '@mosaic/memory';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Environment-driven configuration
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import type { Memory } from '@mosaicstack/memory';
|
||||
import type { Memory } from '@mosaic/memory';
|
||||
import { MEMORY } from './memory.tokens.js';
|
||||
import { AuthGuard } from '../auth/auth.guard.js';
|
||||
import { CurrentUser } from '../auth/current-user.decorator.js';
|
||||
|
||||
@@ -5,10 +5,10 @@ import {
|
||||
createMemoryAdapter,
|
||||
type MemoryAdapter,
|
||||
type MemoryConfig,
|
||||
} from '@mosaicstack/memory';
|
||||
import type { Db } from '@mosaicstack/db';
|
||||
import type { StorageAdapter } from '@mosaicstack/storage';
|
||||
import type { MosaicConfig } from '@mosaicstack/config';
|
||||
} from '@mosaic/memory';
|
||||
import type { Db } from '@mosaic/db';
|
||||
import type { StorageAdapter } from '@mosaic/storage';
|
||||
import type { MosaicConfig } from '@mosaic/config';
|
||||
import { MOSAIC_CONFIG } from '../config/config.module.js';
|
||||
import { DB, STORAGE_ADAPTER } from '../database/database.module.js';
|
||||
import { MEMORY } from './memory.tokens.js';
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
Post,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import type { Brain } from '@mosaicstack/brain';
|
||||
import type { Brain } from '@mosaic/brain';
|
||||
import { BRAIN } from '../brain/brain.tokens.js';
|
||||
import { AuthGuard } from '../auth/auth.guard.js';
|
||||
import { CurrentUser } from '../auth/current-user.decorator.js';
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
type OnModuleDestroy,
|
||||
type OnModuleInit,
|
||||
} from '@nestjs/common';
|
||||
import { DiscordPlugin } from '@mosaicstack/discord-plugin';
|
||||
import { TelegramPlugin } from '@mosaicstack/telegram-plugin';
|
||||
import { DiscordPlugin } from '@mosaic/discord-plugin';
|
||||
import { TelegramPlugin } from '@mosaic/telegram-plugin';
|
||||
import { PluginService } from './plugin.service.js';
|
||||
import type { IChannelPlugin } from './plugin.interface.js';
|
||||
import { PLUGIN_REGISTRY } from './plugin.tokens.js';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { PreferencesService, PLATFORM_DEFAULTS, IMMUTABLE_KEYS } from './preferences.service.js';
|
||||
import type { Db } from '@mosaicstack/db';
|
||||
import type { Db } from '@mosaic/db';
|
||||
|
||||
/**
|
||||
* Build a mock Drizzle DB where the select chain supports:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import { eq, and, sql, type Db, preferences as preferencesTable } from '@mosaicstack/db';
|
||||
import { eq, and, sql, type Db, preferences as preferencesTable } from '@mosaic/db';
|
||||
import { DB } from '../database/database.module.js';
|
||||
|
||||
export const PLATFORM_DEFAULTS: Record<string, unknown> = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { createQueue, type QueueHandle } from '@mosaicstack/queue';
|
||||
import { createQueue, type QueueHandle } from '@mosaic/queue';
|
||||
|
||||
const SESSION_SYSTEM_KEY = (sessionId: string) => `mosaic:session:${sessionId}:system`;
|
||||
const SESSION_SYSTEM_FRAGMENTS_KEY = (sessionId: string) =>
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
Post,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import type { Brain } from '@mosaicstack/brain';
|
||||
import type { Brain } from '@mosaic/brain';
|
||||
import { BRAIN } from '../brain/brain.tokens.js';
|
||||
import { AuthGuard } from '../auth/auth.guard.js';
|
||||
import { CurrentUser } from '../auth/current-user.decorator.js';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { createQueueAdapter, type QueueAdapter } from '@mosaicstack/queue';
|
||||
import type { MosaicConfig } from '@mosaicstack/config';
|
||||
import { createQueueAdapter, type QueueAdapter } from '@mosaic/queue';
|
||||
import type { MosaicConfig } from '@mosaic/config';
|
||||
import { MOSAIC_CONFIG } from '../config/config.module.js';
|
||||
import { QueueService } from './queue.service.js';
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
type OnModuleDestroy,
|
||||
} from '@nestjs/common';
|
||||
import { Queue, Worker, type Job, type ConnectionOptions } from 'bullmq';
|
||||
import type { LogService } from '@mosaicstack/log';
|
||||
import type { LogService } from '@mosaic/log';
|
||||
import { LOG_SERVICE } from '../log/log.tokens.js';
|
||||
import type { JobDto, JobStatus } from './queue-admin.dto.js';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Controller, HttpCode, HttpStatus, Inject, Post, UseGuards } from '@nestjs/common';
|
||||
import type { SystemReloadPayload } from '@mosaicstack/types';
|
||||
import type { SystemReloadPayload } from '@mosaic/types';
|
||||
import { AdminGuard } from '../admin/admin.guard.js';
|
||||
import { ChatGateway } from '../chat/chat.gateway.js';
|
||||
import { ReloadService } from './reload.service.js';
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
type OnApplicationBootstrap,
|
||||
type OnApplicationShutdown,
|
||||
} from '@nestjs/common';
|
||||
import type { SystemReloadPayload } from '@mosaicstack/types';
|
||||
import type { SystemReloadPayload } from '@mosaic/types';
|
||||
import { CommandRegistryService } from '../commands/command-registry.service.js';
|
||||
import { isMosaicPlugin } from './mosaic-plugin.interface.js';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { eq, type Db, skills } from '@mosaicstack/db';
|
||||
import { eq, type Db, skills } from '@mosaic/db';
|
||||
import { DB } from '../database/database.module.js';
|
||||
|
||||
type Skill = typeof skills.$inferSelect;
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import type { Brain } from '@mosaicstack/brain';
|
||||
import type { Brain } from '@mosaic/brain';
|
||||
import { BRAIN } from '../brain/brain.tokens.js';
|
||||
import { AuthGuard } from '../auth/auth.guard.js';
|
||||
import { CurrentUser } from '../auth/current-user.decorator.js';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import type { Brain } from '@mosaicstack/brain';
|
||||
import type { Brain } from '@mosaic/brain';
|
||||
import { BRAIN } from '../brain/brain.tokens.js';
|
||||
import { PluginService } from '../plugin/plugin.service.js';
|
||||
import { WorkspaceService } from './workspace.service.js';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import { eq, and, type Db, teams, teamMembers, projects } from '@mosaicstack/db';
|
||||
import { eq, and, type Db, teams, teamMembers, projects } from '@mosaic/db';
|
||||
import { DB } from '../database/database.module.js';
|
||||
|
||||
@Injectable()
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
"rootDir": "../..",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@mosaicstack/auth": ["../../packages/auth/src/index.ts"],
|
||||
"@mosaicstack/brain": ["../../packages/brain/src/index.ts"],
|
||||
"@mosaicstack/coord": ["../../packages/coord/src/index.ts"],
|
||||
"@mosaicstack/db": ["../../packages/db/src/index.ts"],
|
||||
"@mosaicstack/log": ["../../packages/log/src/index.ts"],
|
||||
"@mosaicstack/memory": ["../../packages/memory/src/index.ts"],
|
||||
"@mosaicstack/types": ["../../packages/types/src/index.ts"],
|
||||
"@mosaicstack/discord-plugin": ["../../plugins/discord/src/index.ts"],
|
||||
"@mosaicstack/telegram-plugin": ["../../plugins/telegram/src/index.ts"]
|
||||
"@mosaic/auth": ["../../packages/auth/src/index.ts"],
|
||||
"@mosaic/brain": ["../../packages/brain/src/index.ts"],
|
||||
"@mosaic/coord": ["../../packages/coord/src/index.ts"],
|
||||
"@mosaic/db": ["../../packages/db/src/index.ts"],
|
||||
"@mosaic/log": ["../../packages/log/src/index.ts"],
|
||||
"@mosaic/memory": ["../../packages/memory/src/index.ts"],
|
||||
"@mosaic/types": ["../../packages/types/src/index.ts"],
|
||||
"@mosaic/discord-plugin": ["../../plugins/discord/src/index.ts"],
|
||||
"@mosaic/telegram-plugin": ["../../plugins/telegram/src/index.ts"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import swc from 'unplugin-swc';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
@@ -6,22 +5,4 @@ export default defineConfig({
|
||||
globals: true,
|
||||
environment: 'node',
|
||||
},
|
||||
plugins: [
|
||||
swc.vite({
|
||||
jsc: {
|
||||
parser: {
|
||||
syntax: 'typescript',
|
||||
decorators: true,
|
||||
},
|
||||
transform: {
|
||||
decoratorMetadata: true,
|
||||
legacyDecorator: true,
|
||||
},
|
||||
target: 'es2022',
|
||||
},
|
||||
module: {
|
||||
type: 'nodenext',
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { NextConfig } from 'next';
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
output: 'standalone',
|
||||
transpilePackages: ['@mosaicstack/design-tokens'],
|
||||
transpilePackages: ['@mosaic/design-tokens'],
|
||||
|
||||
// Enable gzip/brotli compression for all responses.
|
||||
compress: true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@mosaicstack/web",
|
||||
"name": "@mosaic/web",
|
||||
"version": "0.0.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -12,7 +12,7 @@
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mosaicstack/design-tokens": "workspace:^",
|
||||
"@mosaic/design-tokens": "workspace:^",
|
||||
"better-auth": "^1.5.5",
|
||||
"clsx": "^2.1.0",
|
||||
"next": "^16.0.0",
|
||||
|
||||
@@ -7,7 +7,7 @@ import { defineConfig, devices } from '@playwright/test';
|
||||
* - Next.js web app running on http://localhost:3000
|
||||
* - NestJS gateway running on http://localhost:14242
|
||||
*
|
||||
* Run with: pnpm --filter @mosaicstack/web test:e2e
|
||||
* Run with: pnpm --filter @mosaic/web test:e2e
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: './e2e',
|
||||
|
||||
@@ -1,73 +1,70 @@
|
||||
# Mission Manifest — Install UX v2
|
||||
# Mission Manifest — Harness Foundation
|
||||
|
||||
> Persistent document tracking full mission scope, status, and session history.
|
||||
> Updated by the orchestrator at each phase transition and milestone completion.
|
||||
|
||||
## Mission
|
||||
|
||||
**ID:** install-ux-v2-20260405
|
||||
**Statement:** The install-ux-hardening mission shipped the plumbing (uninstall, masked password, hooks consent, unified flow, headless path), but the first real end-to-end run surfaced a critical regression and a collection of UX failings that make the wizard feel neither quick nor intelligent. This mission closes the bootstrap regression as a hotfix, then rethinks the first-run experience around a provider-first, intent-driven flow with a drill-down main menu and a genuinely fast quick-start.
|
||||
**Phase:** Execution
|
||||
**Current Milestone:** IUV-M03
|
||||
**Progress:** 2 / 3 milestones
|
||||
**Status:** active
|
||||
**Last Updated:** 2026-04-05 (IUV-M02 complete — CORS/FQDN + skill installer rework)
|
||||
**Parent Mission:** [install-ux-hardening-20260405](./archive/missions/install-ux-hardening-20260405/MISSION-MANIFEST.md) (complete — `mosaic-v0.0.25`)
|
||||
|
||||
## Context
|
||||
|
||||
Real-run testing of `@mosaicstack/mosaic@0.0.25` uncovered:
|
||||
|
||||
1. **Critical:** admin bootstrap fails with HTTP 400 `property email should not exist` — `bootstrap.controller.ts` uses `import type { BootstrapSetupDto }`, erasing the class at runtime. Nest's `@Body()` falls back to plain `Object` metatype, and ValidationPipe with `forbidNonWhitelisted` rejects every property. One-character fix (drop the `type` keyword), but it blocks the happy path of the release that just shipped.
|
||||
2. The wizard reports `✔ Wizard complete` and `✔ Done` _after_ the bootstrap 400 — failure only propagates in headless mode (`wizard.ts:147`).
|
||||
3. The gateway port prompt does not prefill `14242` in the input buffer.
|
||||
4. `"What is Mosaic?"` intro copy does not mention Pi SDK (the actual agent runtime behind Claude/Codex/OpenCode).
|
||||
5. CORS origin prompt is confusing — the user should be able to supply an FQDN/hostname and have the system derive the CORS value.
|
||||
6. Skill / additional feature install section is unusable in practice.
|
||||
7. Quick-start asks far too many questions to be meaningfully "quick".
|
||||
8. No drill-down main menu — everything is a linear interrogation.
|
||||
9. Provider setup happens late and without intelligence. An OpenClaw-style provider-first flow would let the user describe what they want in natural language, have the agent expound on it, and have the agent choose its own name based on that intent.
|
||||
**ID:** harness-20260321
|
||||
**Statement:** Transform Mosaic Stack from a functional demo into a real multi-provider, task-routing AI harness. Persist all conversations, integrate frontier LLM providers (Anthropic, OpenAI, OpenRouter, Z.ai, Ollama), build granular task-aware agent routing, harden agent sessions, replace cron with BullMQ, and design the channel protocol for future Matrix/remote integration.
|
||||
**Phase:** Complete
|
||||
**Current Milestone:** All milestones done
|
||||
**Progress:** 7 / 7 milestones
|
||||
**Status:** complete
|
||||
**Last Updated:** 2026-03-22 UTC
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [x] AC-1: Admin bootstrap completes successfully end-to-end on a fresh install (DTO value import, no forbidNonWhitelisted regression); covered by an integration or e2e test that exercises the real DTO binding. _(PR #440)_
|
||||
- [x] AC-2: Wizard fails loudly (non-zero exit, clear error) when the bootstrap stage returns `completed: false`, in both interactive and headless modes. No more silent `✔ Wizard complete` after a 400. _(PR #440)_
|
||||
- [x] AC-3: Gateway port prompt prefills `14242` in the input field (user can press Enter to accept). _(PR #440)_
|
||||
- [x] AC-4: `"What is Mosaic?"` intro copy mentions Pi SDK as the underlying agent runtime. _(PR #440)_
|
||||
- [x] AC-5: Release `mosaic-v0.0.26` tagged and published to the Gitea npm registry, unblocking the 0.0.25 happy path. _(tag: mosaic-v0.0.26, registry: 0.0.26 live)_
|
||||
- [ ] AC-6: CORS origin prompt replaced with FQDN/hostname input; CORS string is derived from that.
|
||||
- [ ] AC-7: Skill / additional feature install section is reworked until it is actually usable end-to-end (worker defines the concrete failure modes during diagnosis).
|
||||
- [ ] AC-8: First-run flow has a drill-down main menu with at least `Plugins` (Recommended / Custom), `Providers`, and the other top-level configuration groups. Linear interrogation is gone.
|
||||
- [ ] AC-9: `Quick Start` path completes with a minimal, curated set of questions (target: under 90 seconds for a returning user; define the exact baseline during design).
|
||||
- [ ] AC-10: Provider setup happens first, driven by a natural-language intake prompt. The agent expounds on the user's intent and chooses its own name based on that intent (OpenClaw-style). Naming is confirmable / overridable.
|
||||
- [ ] AC-11: All milestones ship as merged PRs with green CI and closed issues.
|
||||
- [x] AC-1: Send messages in TUI → restart TUI → resume conversation → agent has full history and context
|
||||
- [x] AC-2: Route a coding task to Claude Opus 4.6, a simple question to Haiku, a summarization to GLM-5 — all via granular routing rules
|
||||
- [x] AC-3: Two users exist, User A's memory searches never return User B's data
|
||||
- [x] AC-4: `/model claude-sonnet-4-6` in TUI switches the active model for subsequent messages
|
||||
- [x] AC-5: `/agent coding-agent` in TUI switches to a different agent with different system prompt and tools
|
||||
- [x] AC-6: BullMQ jobs execute on schedule, failures retry with backoff, admin can inspect via `/api/admin/jobs`
|
||||
- [x] AC-7: Channel protocol document exists with Matrix integration points defined, reviewed, and approved
|
||||
- [x] AC-8: Embeddings run on Ollama local models (no external API dependency for vector operations)
|
||||
- [x] AC-9: All five providers (Anthropic, OpenAI, OpenRouter, Z.ai, Ollama) connect, list models, and complete chat requests
|
||||
- [x] AC-10: Routing transparency — TUI displays which model was selected and the routing reason for each response
|
||||
|
||||
## Milestones
|
||||
|
||||
| # | ID | Name | Status | Branch | Issue | Started | Completed |
|
||||
| --- | ------- | ------------------------------------------------------------ | ----------- | ---------------------- | ----- | ---------- | ---------- |
|
||||
| 1 | IUV-M01 | Hotfix: bootstrap DTO + wizard failure + port prefill + copy | complete | fix/bootstrap-hotfix | #436 | 2026-04-05 | 2026-04-05 |
|
||||
| 2 | IUV-M02 | UX polish: CORS/FQDN, skill installer rework | complete | feat/install-ux-polish | #437 | 2026-04-05 | 2026-04-05 |
|
||||
| 3 | IUV-M03 | Provider-first intelligent flow + drill-down main menu | not-started | feat/install-ux-intent | #438 | — | — |
|
||||
| # | ID | Name | Status | Branch | Issue | Started | Completed |
|
||||
| --- | ------ | ---------------------------------- | ------ | ------ | --------- | ---------- | ---------- |
|
||||
| 1 | ms-166 | Conversation Persistence & Context | done | — | #224–#231 | 2026-03-21 | 2026-03-21 |
|
||||
| 2 | ms-167 | Security & Isolation | done | — | #232–#239 | 2026-03-21 | 2026-03-21 |
|
||||
| 3 | ms-168 | Provider Integration | done | — | #240–#251 | 2026-03-21 | 2026-03-22 |
|
||||
| 4 | ms-169 | Agent Routing Engine | done | — | #252–#264 | 2026-03-22 | 2026-03-22 |
|
||||
| 5 | ms-170 | Agent Session Hardening | done | — | #265–#272 | 2026-03-22 | 2026-03-22 |
|
||||
| 6 | ms-171 | Job Queue Foundation | done | — | #273–#280 | 2026-03-22 | 2026-03-22 |
|
||||
| 7 | ms-172 | Channel Protocol Design | done | — | #281–#288 | 2026-03-22 | 2026-03-22 |
|
||||
|
||||
## Subagent Delegation Plan
|
||||
## Deployment
|
||||
|
||||
| Milestone | Recommended Tier | Rationale |
|
||||
| --------- | ---------------- | --------------------------------------------------------------------- |
|
||||
| IUV-M01 | sonnet | Tight bug cluster with known fix sites + small release cycle |
|
||||
| IUV-M02 | sonnet | UX rework, moderate surface, diagnostic-heavy for the skill installer |
|
||||
| IUV-M03 | opus | Architectural redesign of first-run flow, state machine + LLM intake |
|
||||
| Target | URL | Method |
|
||||
| -------------------- | --------- | -------------------------- |
|
||||
| Docker Compose (dev) | localhost | docker compose up |
|
||||
| Production | TBD | Docker Swarm via Portainer |
|
||||
|
||||
## Risks
|
||||
## Coordination
|
||||
|
||||
- **Hotfix regression surface** — the `import type` → `import` fix on the DTO class is one character but needs an integration test that binds the real DTO, not just a controller unit test, to prevent the same class-erasure regression from sneaking back in.
|
||||
- **LLM-driven intake latency / offline** — M03's provider-first intent flow assumes an available LLM call to expound on user input and choose a name. Offline installs need a deterministic fallback.
|
||||
- **Menu vs. linear back-compat** — M03 changes the top-level flow shape; existing `tools/install.sh --yes` + env-var headless path must continue to work.
|
||||
- **Scope creep in M03** — "redesign the wizard" can absorb arbitrary work. Keep it bounded with explicit non-goals.
|
||||
- **Primary Agent:** claude-opus-4-6
|
||||
- **Sibling Agents:** sonnet (workers), haiku (verification)
|
||||
- **Shared Contracts:** docs/PRD-Harness_Foundation.md, docs/TASKS.md
|
||||
|
||||
## Out of Scope
|
||||
## Token Budget
|
||||
|
||||
- Migrating the wizard to a GUI / web UI (still terminal-first)
|
||||
- Replacing the Gitea registry or the Woodpecker publish pipeline
|
||||
- Multi-tenant / multi-user onboarding (still single-admin bootstrap)
|
||||
- Reworking `mosaic uninstall` (M01 of the parent mission — stable)
|
||||
| Metric | Value |
|
||||
| ------ | ------ |
|
||||
| Budget | — |
|
||||
| Used | ~2.5M |
|
||||
| Mode | normal |
|
||||
|
||||
## Session History
|
||||
|
||||
| Session | Runtime | Started | Duration | Ended Reason | Last Task |
|
||||
| ------- | --------------- | ---------- | -------- | ------------ | ----------------- |
|
||||
| 1 | claude-opus-4-6 | 2026-03-21 | ~6h | complete | M7-008 — all done |
|
||||
|
||||
## Scratchpad
|
||||
|
||||
Path: `docs/scratchpads/harness-20260321.md`
|
||||
|
||||
@@ -153,7 +153,7 @@ for any `<Image>` components added in the future.
|
||||
|
||||
```bash
|
||||
# Run the DB migration (requires a live DB)
|
||||
pnpm --filter @mosaicstack/db exec drizzle-kit migrate
|
||||
pnpm --filter @mosaic/db exec drizzle-kit migrate
|
||||
|
||||
# Or, in Docker/Swarm — migrations run automatically on gateway startup
|
||||
# via runMigrations() in packages/db/src/migrate.ts
|
||||
|
||||
@@ -57,7 +57,7 @@ Multi-panel layout with keyboard navigation.
|
||||
|
||||
- **Ink 5** (React for CLI) — already in deps
|
||||
- **Component architecture** — break monolithic `app.tsx` into composable components
|
||||
- **Typed Socket.IO events** — leverage `@mosaicstack/types` `ServerToClientEvents` / `ClientToServerEvents`
|
||||
- **Typed Socket.IO events** — leverage `@mosaic/types` `ServerToClientEvents` / `ClientToServerEvents`
|
||||
- **Local state only** (Wave 1) — cwd/branch read from `process.cwd()` and `git` at startup
|
||||
- **Gateway metadata** (future) — extend socket handshake or add REST endpoint for model info, token usage
|
||||
|
||||
|
||||
164
docs/PRD.md
164
docs/PRD.md
@@ -8,7 +8,7 @@
|
||||
- **Best-Guess Mode:** true
|
||||
- Repo (target): `git.mosaicstack.dev/mosaic/mosaic-stack`
|
||||
- Baseline: `~/src/jarvis-old` (jarvis v0.2.0)
|
||||
- Package source: `~/src/mosaic-mono-v0` (@mosaicstack/\* packages)
|
||||
- Package source: `~/src/mosaic-mono-v0` (@mosaic/\* packages)
|
||||
- Agent harness: [pi](https://github.com/badlogic/pi-mono) (v0.57.1)
|
||||
- Remote control reference: [OpenClaw](https://github.com/openclaw/openclaw) (upstream, canonical)
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
## Problem Statement
|
||||
|
||||
Jarvis (v0.2.0) is a self-hosted AI assistant with a Python FastAPI backend and Next.js frontend. It handles chat, projects, tasks, and LLM routing but lacks orchestration depth, agent coordination, shared memory, and remote access. The Mosaic framework (`~/.config/mosaic`) provides agent guides, shell-based orchestration tools, and quality rails — but these are loose scripts, not an integrated platform. The `@mosaicstack/*` packages in mosaic-mono-v0 began consolidating these into TypeScript packages (brain, queue, coord, cli, prdy, quality-rails) but have no UI, no auth, and no agent runtime integration.
|
||||
Jarvis (v0.2.0) is a self-hosted AI assistant with a Python FastAPI backend and Next.js frontend. It handles chat, projects, tasks, and LLM routing but lacks orchestration depth, agent coordination, shared memory, and remote access. The Mosaic framework (`~/.config/mosaic`) provides agent guides, shell-based orchestration tools, and quality rails — but these are loose scripts, not an integrated platform. The `@mosaic/*` packages in mosaic-mono-v0 began consolidating these into TypeScript packages (brain, queue, coord, cli, prdy, quality-rails) but have no UI, no auth, and no agent runtime integration.
|
||||
|
||||
**The gap:** Three codebases with overlapping concerns, no unified runtime, no remote control surface (Discord/Telegram), no gateway orchestrator, and a Python backend that doesn't align with the target TypeScript-everywhere stack.
|
||||
|
||||
@@ -32,7 +32,7 @@ Jarvis (v0.2.0) is a self-hosted AI assistant with a Python FastAPI backend and
|
||||
4. **Gateway orchestrator** — Central routing layer that dispatches tasks to appropriate agents based on capability, cost, and context
|
||||
5. **Shared memory** — PostgreSQL canonical store + vector DB for semantic search + tiered log summarization to prevent context creep
|
||||
6. **Multi-user with SSO** — BetterAuth with Authentik/WorkOS/Keycloak SSO, RBAC for family/team/business use
|
||||
7. **Full @mosaicstack/\* package integration** — brain, queue, coord, mosaic, prdy, quality-rails, cli all integrated
|
||||
7. **Full @mosaic/\* package integration** — brain, queue, coord, mosaic, prdy, quality-rails, cli all integrated
|
||||
8. **Extensible** — MCP capability, skill import interface, plugin architecture for LLM providers and remote channels
|
||||
|
||||
---
|
||||
@@ -44,7 +44,7 @@ Jarvis (v0.2.0) is a self-hosted AI assistant with a Python FastAPI backend and
|
||||
1. Chat/conversation UI (web) — carry forward from jarvis-old, rewrite frontend to work with new backend
|
||||
2. Pi TUI integration — terminal-based agent interaction using Pi SDK
|
||||
3. Web dashboard — settings, task management, projects, PRDs, missions, agent status
|
||||
4. Gateway orchestrator (`@mosaicstack/gateway`) — central dispatch for agent tasks with routing logic
|
||||
4. Gateway orchestrator (`@mosaic/gateway`) — central dispatch for agent tasks with routing logic
|
||||
5. Task management — CRUD, kanban, mission-scoped tasks, dependency tracking
|
||||
6. Project management — projects, milestones, PRDs linked to missions
|
||||
7. Shared memory system — learned preferences, behaviors, defaults; tiered storage with summarization
|
||||
@@ -55,13 +55,13 @@ Jarvis (v0.2.0) is a self-hosted AI assistant with a Python FastAPI backend and
|
||||
12. Agent routing — task-based model/provider selection (cost/capability matrix)
|
||||
13. MCP capability — server and client, tool registration
|
||||
14. Skill import interface — browse, install, manage agent skills
|
||||
15. `@mosaicstack/brain` — structured data layer (migrated to PG + vector DB backend)
|
||||
16. `@mosaicstack/queue` — Valkey-backed task queue with MCP tools
|
||||
17. `@mosaicstack/coord` — mission coordination engine
|
||||
18. `@mosaicstack/mosaic` — install wizard / bootstrap
|
||||
19. `@mosaicstack/prdy` — PRD wizard
|
||||
20. `@mosaicstack/quality-rails` — code quality scaffolder
|
||||
21. `@mosaicstack/cli` — unified `mosaic` CLI
|
||||
15. `@mosaic/brain` — structured data layer (migrated to PG + vector DB backend)
|
||||
16. `@mosaic/queue` — Valkey-backed task queue with MCP tools
|
||||
17. `@mosaic/coord` — mission coordination engine
|
||||
18. `@mosaic/mosaic` — install wizard / bootstrap
|
||||
19. `@mosaic/prdy` — PRD wizard
|
||||
20. `@mosaic/quality-rails` — code quality scaffolder
|
||||
21. `@mosaic/cli` — unified `mosaic` CLI
|
||||
22. Docker Compose deployment + bare-metal capability
|
||||
23. Agent log service — ingest, parse, tier, summarize agent interaction logs
|
||||
|
||||
@@ -94,14 +94,14 @@ Jarvis (v0.2.0) is a self-hosted AI assistant with a Python FastAPI backend and
|
||||
│ └──────────────┴───────┬───────┴────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────▼──────────┐ │
|
||||
│ │ @mosaicstack/gateway │ ← Central Orchestrator│
|
||||
│ │ @mosaic/gateway │ ← Central Orchestrator│
|
||||
│ │ (NestJS+Fastify) │ │
|
||||
│ └────┬────┬────┬─────┘ │
|
||||
│ │ │ │ │
|
||||
│ ┌──────────────┤ │ ├──────────────┐ │
|
||||
│ │ │ │ │ │ │
|
||||
│ ┌───────▼──────┐ ┌────▼────▼──┐ │ ┌───────────▼────────┐ │
|
||||
│ │ @mosaicstack/brain│ │ @mosaicstack/ │ │ │ Agent Pool │ │
|
||||
│ │ @mosaic/brain│ │ @mosaic/ │ │ │ Agent Pool │ │
|
||||
│ │ (Data Layer) │ │ queue │ │ │ (Pi SDK sessions) │ │
|
||||
│ └───────┬──────┘ └────────────┘ │ │ - Anthropic │ │
|
||||
│ │ │ │ - Codex │ │
|
||||
@@ -111,12 +111,12 @@ Jarvis (v0.2.0) is a self-hosted AI assistant with a Python FastAPI backend and
|
||||
│ └──────────────┴───────────┘ │ │ - llama.cpp │ │
|
||||
│ │ └────────────────────┘ │
|
||||
│ ┌─────────────▼──────┐ │
|
||||
│ │ @mosaicstack/coord │ │
|
||||
│ │ @mosaic/coord │ │
|
||||
│ │ Mission lifecycle │ │
|
||||
│ └────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
|
||||
│ │ @mosaicstack/cli │ │ @mosaicstack/prdy │ │ @mosaicstack/ │ │
|
||||
│ │ @mosaic/cli │ │ @mosaic/prdy │ │ @mosaic/ │ │
|
||||
│ │ │ │ │ │ quality-rails │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
|
||||
│ │
|
||||
@@ -130,20 +130,20 @@ Jarvis (v0.2.0) is a self-hosted AI assistant with a Python FastAPI backend and
|
||||
|
||||
| Layer | Technology | Rationale |
|
||||
| ------------------ | ------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
|
||||
| **Web Frontend** | Next.js 16 + React 19 + Tailwind CSS | SSR, RSC; design tokens from @mosaicstack/design-tokens (mosaic-stack-website) |
|
||||
| **Web Frontend** | Next.js 16 + React 19 + Tailwind CSS | SSR, RSC; design tokens from @mosaic/design-tokens (mosaic-stack-website) |
|
||||
| **API / Gateway** | NestJS + Fastify adapter | Module system, DI, guards/interceptors for complex gateway; Fastify performance underneath |
|
||||
| **Agent Runtime** | Pi SDK (embedded) | Extensible harness with tools, skills, session management |
|
||||
| **TUI** | Pi interactive mode | Native terminal agent interaction |
|
||||
| **Auth** | BetterAuth + SSO adapters | Multi-user RBAC with Authentik/WorkOS/Keycloak |
|
||||
| **Database** | PostgreSQL 17 + pgvector | Canonical store; pgvector for embedding search |
|
||||
| **Vector DB** | pgvector + VectorStore interface | pgvector for v0.1.0; `VectorStore` abstraction in @mosaicstack/memory makes Qdrant a drop-in later |
|
||||
| **Cache / Queue** | Valkey 8 | Redis-compatible; proven in @mosaicstack/queue |
|
||||
| **Vector DB** | pgvector + VectorStore interface | pgvector for v0.1.0; `VectorStore` abstraction in @mosaic/memory makes Qdrant a drop-in later |
|
||||
| **Cache / Queue** | Valkey 8 | Redis-compatible; proven in @mosaic/queue |
|
||||
| **ORM** | Drizzle ORM | TypeScript-native, lightweight, good migration story |
|
||||
| **Validation** | Zod | Already used across @mosaicstack/\* packages |
|
||||
| **Validation** | Zod | Already used across @mosaic/\* packages |
|
||||
| **Build** | pnpm workspaces + Turborepo | Proven in both jarvis-old and mosaic-mono-v0 |
|
||||
| **Testing** | Vitest + Playwright | Unit/integration via Vitest, E2E via Playwright |
|
||||
| **Remote Control** | Discord.js + Telegraf | Inspired by OpenClaw plugin architecture |
|
||||
| **MCP** | @modelcontextprotocol/sdk | Already used in @mosaicstack/brain and @mosaicstack/queue |
|
||||
| **MCP** | @modelcontextprotocol/sdk | Already used in @mosaic/brain and @mosaic/queue |
|
||||
| **Container** | Docker Compose | Self-hosted; bare-metal also supported |
|
||||
| **CI** | Woodpecker CI | Existing infrastructure at git.mosaicstack.dev |
|
||||
| **Observability** | OpenTelemetry + SigNoz | Wide-event logging from day one; OTEL auto-instrumentation for NestJS/PG/HTTP; SigNoz as all-in-one backend |
|
||||
@@ -158,12 +158,12 @@ The jarvis-old FastAPI backend is not carried forward as code. Its domain logic
|
||||
Instead of a custom LLM provider abstraction (jarvis-old's `BaseLLMProvider`), Pi SDK manages agent sessions. Pi handles model selection, tool calling, context management, and compaction. The gateway dispatches work to Pi sessions configured with appropriate providers.
|
||||
|
||||
**AD-3: Gateway as the central nervous system (NestJS + Fastify adapter)**
|
||||
`@mosaicstack/gateway` is the single API surface. The web app, TUI, Discord, and Telegram all talk to the gateway. The gateway routes to brain (data), queue (coordination), agent pool (LLM work), and coord (mission lifecycle). This replaces the direct FastAPI-to-DB pattern from jarvis-old.
|
||||
`@mosaic/gateway` is the single API surface. The web app, TUI, Discord, and Telegram all talk to the gateway. The gateway routes to brain (data), queue (coordination), agent pool (LLM work), and coord (mission lifecycle). This replaces the direct FastAPI-to-DB pattern from jarvis-old.
|
||||
|
||||
NestJS was chosen over raw Fastify because the gateway is inherently complex — it hosts channel plugins, agent pool management, routing engine, WebSocket hub, MCP server, auth middleware, and integrates brain, queue, memory, and log services. NestJS provides the module system, dependency injection, guards, and interceptors needed to organize this cleanly. NestJS uses Fastify as its HTTP adapter, so Fastify's performance is preserved. This also aligns with the stated stack preference in USER.md ("NestJS API + Next.js web"). @mosaicstack/brain's existing Fastify code migrates naturally into a NestJS module with Fastify adapter.
|
||||
NestJS was chosen over raw Fastify because the gateway is inherently complex — it hosts channel plugins, agent pool management, routing engine, WebSocket hub, MCP server, auth middleware, and integrates brain, queue, memory, and log services. NestJS provides the module system, dependency injection, guards, and interceptors needed to organize this cleanly. NestJS uses Fastify as its HTTP adapter, so Fastify's performance is preserved. This also aligns with the stated stack preference in USER.md ("NestJS API + Next.js web"). @mosaic/brain's existing Fastify code migrates naturally into a NestJS module with Fastify adapter.
|
||||
|
||||
**AD-4: Brain migrates from JSON files to PostgreSQL**
|
||||
`@mosaicstack/brain` currently uses a JSON file store. For Mosaic Stack, brain's data model (tasks, projects, events, agents, missions, tickets) moves to PostgreSQL via Drizzle ORM. Brain's REST + MCP interface is preserved — only the storage backend changes.
|
||||
`@mosaic/brain` currently uses a JSON file store. For Mosaic Stack, brain's data model (tasks, projects, events, agents, missions, tickets) moves to PostgreSQL via Drizzle ORM. Brain's REST + MCP interface is preserved — only the storage backend changes.
|
||||
|
||||
**AD-5: Tiered memory with summarization**
|
||||
Agent interaction logs are ingested into a log service. Raw logs are stored short-term. A summarization pipeline (using a cheap LLM) periodically compresses logs into structured insights stored in the vector DB. This prevents unbounded log growth while preserving searchable context.
|
||||
@@ -189,8 +189,8 @@ The gateway includes a cron scheduler for recurring tasks: log summarization run
|
||||
**AD-12: Web search tool (DuckDuckGo MCP)**
|
||||
Agent sessions include a web search tool for information retrieval. DuckDuckGo via MCP server is the primary option (privacy-respecting, no API key required). Falls back to other search MCP providers if configured. Registered as a standard MCP tool available to all agent sessions.
|
||||
|
||||
**AD-13: Design system from @mosaicstack/design-tokens**
|
||||
The web dashboard uses the Mosaic Stack design system established in `mosaic-stack-website`. The `@mosaicstack/design-tokens` package provides CSS custom properties, Tailwind preset, and TS color/font/radius exports. Dark theme default with light theme support. Fonts: Outfit (sans), Fira Code (mono). Color palette: deep blue-grays with blue/purple/teal accents.
|
||||
**AD-13: Design system from @mosaic/design-tokens**
|
||||
The web dashboard uses the Mosaic Stack design system established in `mosaic-stack-website`. The `@mosaic/design-tokens` package provides CSS custom properties, Tailwind preset, and TS color/font/radius exports. Dark theme default with light theme support. Fonts: Outfit (sans), Fira Code (mono). Color palette: deep blue-grays with blue/purple/teal accents.
|
||||
|
||||
**AD-14: Multi-tier deployment readiness**
|
||||
Code is structured assuming eventual multi-node deployment with dedicated roles (gateway nodes, agent worker nodes, brain/DB nodes). Packages communicate via well-defined APIs (HTTP/WS/MCP), not in-process calls where avoidable. Service boundaries are clean: gateway is stateless (state in PG/Valkey), agent pool can scale independently, brain is a separate service. v0.1.0 runs single-node; the architecture doesn't fight horizontal scaling later.
|
||||
@@ -205,25 +205,25 @@ Code is structured assuming eventual multi-node deployment with dedicated roles
|
||||
mosaic-mono-v1/
|
||||
├── apps/
|
||||
│ ├── web/ Next.js 16 web dashboard
|
||||
│ └── gateway/ @mosaicstack/gateway — NestJS API + WebSocket
|
||||
│ └── gateway/ @mosaic/gateway — NestJS API + WebSocket
|
||||
├── packages/
|
||||
│ ├── types/ @mosaicstack/types — shared type contracts
|
||||
│ ├── brain/ @mosaicstack/brain — data layer (PG-backed)
|
||||
│ ├── queue/ @mosaicstack/queue — Valkey task queue + MCP
|
||||
│ ├── coord/ @mosaicstack/coord — mission coordination
|
||||
│ ├── mosaic/ @mosaicstack/mosaic — install wizard
|
||||
│ ├── prdy/ @mosaicstack/prdy — PRD wizard
|
||||
│ ├── quality-rails/ @mosaicstack/quality-rails — code quality scaffolder
|
||||
│ ├── cli/ @mosaicstack/cli — unified CLI
|
||||
│ ├── auth/ @mosaicstack/auth — BetterAuth config + SSO adapters
|
||||
│ ├── db/ @mosaicstack/db — Drizzle schema, migrations, connection
|
||||
│ ├── agent/ @mosaicstack/agent — Pi SDK integration, agent pool manager
|
||||
│ ├── memory/ @mosaicstack/memory — tiered memory + summarization service
|
||||
│ ├── log/ @mosaicstack/log — agent log ingest + processing
|
||||
│ └── design-tokens/ @mosaicstack/design-tokens — CSS vars, Tailwind preset, colors
|
||||
│ ├── types/ @mosaic/types — shared type contracts
|
||||
│ ├── brain/ @mosaic/brain — data layer (PG-backed)
|
||||
│ ├── queue/ @mosaic/queue — Valkey task queue + MCP
|
||||
│ ├── coord/ @mosaic/coord — mission coordination
|
||||
│ ├── mosaic/ @mosaic/mosaic — install wizard
|
||||
│ ├── prdy/ @mosaic/prdy — PRD wizard
|
||||
│ ├── quality-rails/ @mosaic/quality-rails — code quality scaffolder
|
||||
│ ├── cli/ @mosaic/cli — unified CLI
|
||||
│ ├── auth/ @mosaic/auth — BetterAuth config + SSO adapters
|
||||
│ ├── db/ @mosaic/db — Drizzle schema, migrations, connection
|
||||
│ ├── agent/ @mosaic/agent — Pi SDK integration, agent pool manager
|
||||
│ ├── memory/ @mosaic/memory — tiered memory + summarization service
|
||||
│ ├── log/ @mosaic/log — agent log ingest + processing
|
||||
│ └── design-tokens/ @mosaic/design-tokens — CSS vars, Tailwind preset, colors
|
||||
├── plugins/
|
||||
│ ├── discord/ @mosaicstack/discord-plugin — Discord channel
|
||||
│ └── telegram/ @mosaicstack/telegram-plugin — Telegram channel
|
||||
│ ├── discord/ @mosaic/discord-plugin — Discord channel
|
||||
│ └── telegram/ @mosaic/telegram-plugin — Telegram channel
|
||||
├── docker/
|
||||
│ ├── gateway.Dockerfile
|
||||
│ ├── web.Dockerfile
|
||||
@@ -244,7 +244,7 @@ mosaic-mono-v1/
|
||||
|
||||
### Package Responsibilities
|
||||
|
||||
#### `apps/gateway` — @mosaicstack/gateway (NEW — critical path)
|
||||
#### `apps/gateway` — @mosaic/gateway (NEW — critical path)
|
||||
|
||||
The central nervous system. All clients connect here. Built with NestJS (Fastify adapter).
|
||||
|
||||
@@ -303,7 +303,7 @@ Carried forward from jarvis-old with significant refactoring.
|
||||
- User management (admin RBAC panel)
|
||||
- Auth pages (login, SSO redirect, registration)
|
||||
|
||||
#### `packages/types` — @mosaicstack/types
|
||||
#### `packages/types` — @mosaic/types
|
||||
|
||||
Migrated from mosaic-mono-v0. Extended with:
|
||||
|
||||
@@ -313,7 +313,7 @@ Migrated from mosaic-mono-v0. Extended with:
|
||||
- Memory types (preference, insight, summary)
|
||||
- Plugin channel types (Discord, Telegram message mapping)
|
||||
|
||||
#### `packages/brain` — @mosaicstack/brain
|
||||
#### `packages/brain` — @mosaic/brain
|
||||
|
||||
Migrated from mosaic-mono-v0. **Storage backend changes from JSON to PostgreSQL.**
|
||||
|
||||
@@ -324,7 +324,7 @@ Migrated from mosaic-mono-v0. **Storage backend changes from JSON to PostgreSQL.
|
||||
- New: computed endpoints (today, stale, stats, search, audit) run against PG
|
||||
- New: appreciation collection preserved for family use
|
||||
|
||||
#### `packages/queue` — @mosaicstack/queue
|
||||
#### `packages/queue` — @mosaic/queue
|
||||
|
||||
Migrated from mosaic-mono-v0 with minimal changes.
|
||||
|
||||
@@ -332,7 +332,7 @@ Migrated from mosaic-mono-v0 with minimal changes.
|
||||
- MCP server with 8 tools
|
||||
- Used by gateway for agent task dispatch and coordination
|
||||
|
||||
#### `packages/coord` — @mosaicstack/coord
|
||||
#### `packages/coord` — @mosaic/coord
|
||||
|
||||
Migrated from mosaic-mono-v0.
|
||||
|
||||
@@ -342,7 +342,7 @@ Migrated from mosaic-mono-v0.
|
||||
- Continuation prompt generation
|
||||
- Integration with gateway for mission-driven orchestration
|
||||
|
||||
#### `packages/db` — @mosaicstack/db (NEW)
|
||||
#### `packages/db` — @mosaic/db (NEW)
|
||||
|
||||
Shared database package.
|
||||
|
||||
@@ -351,7 +351,7 @@ Shared database package.
|
||||
- Connection pool configuration
|
||||
- Shared by gateway, brain, auth, memory
|
||||
|
||||
#### `packages/auth` — @mosaicstack/auth (NEW)
|
||||
#### `packages/auth` — @mosaic/auth (NEW)
|
||||
|
||||
Authentication and authorization.
|
||||
|
||||
@@ -361,7 +361,7 @@ Authentication and authorization.
|
||||
- API key generation for brain/MCP access
|
||||
- Session management middleware
|
||||
|
||||
#### `packages/agent` — @mosaicstack/agent (NEW — critical path)
|
||||
#### `packages/agent` — @mosaic/agent (NEW — critical path)
|
||||
|
||||
Pi SDK integration layer.
|
||||
|
||||
@@ -372,7 +372,7 @@ Pi SDK integration layer.
|
||||
- Skill management — loads and configures Pi skills for agent sessions
|
||||
- Session lifecycle — create, monitor, complete, fail, timeout
|
||||
|
||||
#### `packages/memory` — @mosaicstack/memory (NEW)
|
||||
#### `packages/memory` — @mosaic/memory (NEW)
|
||||
|
||||
Tiered memory system.
|
||||
|
||||
@@ -382,7 +382,7 @@ Tiered memory system.
|
||||
- Summarization pipeline — compress raw logs into structured insights
|
||||
- Memory API — used by gateway and agent sessions
|
||||
|
||||
#### `packages/log` — @mosaicstack/log (NEW)
|
||||
#### `packages/log` — @mosaic/log (NEW)
|
||||
|
||||
Agent log service.
|
||||
|
||||
@@ -392,7 +392,7 @@ Agent log service.
|
||||
- Summarization trigger — invokes cheap LLM to compress aging logs
|
||||
- Retention policy — configurable TTLs per tier
|
||||
|
||||
#### `packages/mosaic` — @mosaicstack/mosaic
|
||||
#### `packages/mosaic` — @mosaic/mosaic
|
||||
|
||||
Migrated from mosaic-mono-v0, updated for v1.
|
||||
|
||||
@@ -400,7 +400,7 @@ Migrated from mosaic-mono-v0, updated for v1.
|
||||
- Detects existing installations, offers upgrade path
|
||||
- Configures `~/.config/mosaic/` with guides, tools, runtime configs
|
||||
|
||||
#### `packages/prdy` — @mosaicstack/prdy
|
||||
#### `packages/prdy` — @mosaic/prdy
|
||||
|
||||
Migrated from mosaic-mono-v0.
|
||||
|
||||
@@ -408,7 +408,7 @@ Migrated from mosaic-mono-v0.
|
||||
- Template-based PRD creation with Zod validation
|
||||
- CLI integration via `mosaic prdy`
|
||||
|
||||
#### `packages/quality-rails` — @mosaicstack/quality-rails
|
||||
#### `packages/quality-rails` — @mosaic/quality-rails
|
||||
|
||||
Migrated from mosaic-mono-v0.
|
||||
|
||||
@@ -416,15 +416,15 @@ Migrated from mosaic-mono-v0.
|
||||
- Generates ESLint, tsconfig, Woodpecker, husky, lint-staged configs
|
||||
- Supports project types: monorepo, typescript-node, nextjs
|
||||
|
||||
#### `packages/cli` — @mosaicstack/cli
|
||||
#### `packages/cli` — @mosaic/cli
|
||||
|
||||
Migrated from mosaic-mono-v0, extended.
|
||||
|
||||
- Unified `mosaic` binary
|
||||
- Subcommands: `mosaic coord`, `mosaic prdy`, `mosaic queue`, `mosaic quality`, `mosaic gateway`, `mosaic brain`
|
||||
- Plugin discovery for installed @mosaicstack/\* packages
|
||||
- Plugin discovery for installed @mosaic/\* packages
|
||||
|
||||
#### `plugins/discord` — @mosaicstack/discord-plugin (NEW — high priority)
|
||||
#### `plugins/discord` — @mosaic/discord-plugin (NEW — high priority)
|
||||
|
||||
Discord remote control channel. Architecture inspired by OpenClaw (https://github.com/openclaw/openclaw).
|
||||
|
||||
@@ -436,7 +436,7 @@ Discord remote control channel. Architecture inspired by OpenClaw (https://githu
|
||||
- Bot pairing and permission management (Discord user → Mosaic user mapping)
|
||||
- DM support for private conversations
|
||||
|
||||
#### `plugins/telegram` — @mosaicstack/telegram-plugin (NEW)
|
||||
#### `plugins/telegram` — @mosaic/telegram-plugin (NEW)
|
||||
|
||||
Telegram remote control channel.
|
||||
|
||||
@@ -547,7 +547,7 @@ Telegram remote control channel.
|
||||
- WebSocket hub — real-time updates for chat, agent status, notifications
|
||||
- Rate limiting and request validation
|
||||
|
||||
### FR-3: Agent Pool (@mosaicstack/agent)
|
||||
### FR-3: Agent Pool (@mosaic/agent)
|
||||
|
||||
- Manage concurrent Pi SDK sessions
|
||||
- Provider configuration: API key management, endpoint URLs, model lists
|
||||
@@ -582,7 +582,7 @@ Telegram remote control channel.
|
||||
- Mission CRUD (linked to project and PRD)
|
||||
- Mission tasks with phases, dependencies, ordering
|
||||
- Mission summary with computed progress
|
||||
- Mission coordination via @mosaicstack/coord
|
||||
- Mission coordination via @mosaic/coord
|
||||
- Active mission dashboard in web UI
|
||||
|
||||
### FR-7: Memory System
|
||||
@@ -844,7 +844,7 @@ Telegram remote control channel.
|
||||
- [ ] Database migrations run automatically on first start
|
||||
- [ ] `.env.example` documents all required configuration
|
||||
|
||||
### AC-11: @mosaicstack/\* Packages
|
||||
### AC-11: @mosaic/\* Packages
|
||||
|
||||
- [ ] All 7 migrated packages build, pass tests, and integrate with gateway
|
||||
- [ ] `mosaic` CLI provides subcommands for each package
|
||||
@@ -870,7 +870,7 @@ Telegram remote control channel.
|
||||
|
||||
| Risk | Likelihood | Impact | Mitigation |
|
||||
| -------------------------------------------------- | ---------- | ------ | ---------------------------------------------------------------------------------------- |
|
||||
| Pi SDK API instability (pre-1.0) | Medium | High | Pin version, abstract behind @mosaicstack/agent interface |
|
||||
| Pi SDK API instability (pre-1.0) | Medium | High | Pin version, abstract behind @mosaic/agent interface |
|
||||
| Brain PG migration complexity | Medium | Medium | Preserve Brain REST/MCP API contract; only storage changes |
|
||||
| Discord plugin complexity (OpenClaw has ~60 files) | Medium | Medium | Start minimal (DM + mention in channel), single-guild only; expand iteratively post-beta |
|
||||
| LLM provider subscription auth varies by provider | Medium | Medium | Abstract behind provider interface; implement per-provider adapters |
|
||||
@@ -882,7 +882,7 @@ Telegram remote control channel.
|
||||
|
||||
| # | Question | Priority | Status |
|
||||
| --- | ------------------------------------------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| 1 | Pi SDK version to pin for v0.1.0? | High | ✅ Resolved — Pin `@mariozechner/pi-coding-agent@~0.57.1` (current stable). Abstract behind `@mosaicstack/agent` interface to insulate from breaking changes. Bump deliberately after testing. |
|
||||
| 1 | Pi SDK version to pin for v0.1.0? | High | ✅ Resolved — Pin `@mariozechner/pi-coding-agent@~0.57.1` (current stable). Abstract behind `@mosaic/agent` interface to insulate from breaking changes. Bump deliberately after testing. |
|
||||
| 2 | Authentik vs WorkOS vs Keycloak — which SSO provider to implement first? | Medium | ✅ Resolved — Authentik first (already in Jason's infrastructure) |
|
||||
| 3 | Vector DB: pgvector sufficient or need Qdrant from the start? | Medium | ✅ Resolved — pgvector with VectorStore interface abstraction. Qdrant drops in later if needed. |
|
||||
| 4 | Summarization LLM: which model for log compression? | Medium | ✅ Resolved — Haiku-tier default with structured output guardrails, configurable via routing engine. |
|
||||
@@ -910,9 +910,9 @@ All work is **alpha** (< 0.1.0) until Jason approves 0.1.0 beta release.
|
||||
### Phase 0: Foundation (v0.0.1)
|
||||
|
||||
- Scaffold monorepo (pnpm + turbo + tsconfig + eslint + vitest)
|
||||
- `@mosaicstack/types` — migrate and extend from v0
|
||||
- `@mosaicstack/db` — Drizzle schema, PG connection, migrations
|
||||
- `@mosaicstack/auth` — BetterAuth setup with email/password
|
||||
- `@mosaic/types` — migrate and extend from v0
|
||||
- `@mosaic/db` — Drizzle schema, PG connection, migrations
|
||||
- `@mosaic/auth` — BetterAuth setup with email/password
|
||||
- OTEL foundation — `@opentelemetry/sdk-node` setup, SigNoz in docker-compose, trace propagation wired
|
||||
- Docker Compose (PG 17 + Valkey + SigNoz)
|
||||
- CI pipeline (Woodpecker)
|
||||
@@ -921,19 +921,19 @@ All work is **alpha** (< 0.1.0) until Jason approves 0.1.0 beta release.
|
||||
### Phase 1: Core API (v0.0.2)
|
||||
|
||||
- `apps/gateway` — NestJS server (Fastify adapter), auth middleware, health endpoints
|
||||
- `@mosaicstack/brain` — migrate from v0, swap JSON store for PG via @mosaicstack/db
|
||||
- `@mosaicstack/queue` — migrate from v0 (minimal changes)
|
||||
- `@mosaic/brain` — migrate from v0, swap JSON store for PG via @mosaic/db
|
||||
- `@mosaic/queue` — migrate from v0 (minimal changes)
|
||||
- Gateway routes: conversations, tasks, projects, missions
|
||||
- WebSocket server for chat streaming
|
||||
- Basic agent dispatch (single provider, no routing)
|
||||
|
||||
### Phase 2: Agent Layer (v0.0.3)
|
||||
|
||||
- `@mosaicstack/agent` — Pi SDK integration, agent pool manager
|
||||
- `@mosaic/agent` — Pi SDK integration, agent pool manager
|
||||
- Multi-provider support (Anthropic + Ollama minimum)
|
||||
- Agent routing engine (cost/capability matrix)
|
||||
- Tool registration (brain, queue, memory tools injected into agent sessions)
|
||||
- `@mosaicstack/coord` — migrate from v0, integrate with gateway
|
||||
- `@mosaic/coord` — migrate from v0, integrate with gateway
|
||||
|
||||
### Phase 3: Web Dashboard (v0.0.4)
|
||||
|
||||
@@ -946,25 +946,25 @@ All work is **alpha** (< 0.1.0) until Jason approves 0.1.0 beta release.
|
||||
|
||||
### Phase 4: Memory & Intelligence (v0.0.5)
|
||||
|
||||
- `@mosaicstack/memory` — preference store, insight store, semantic search
|
||||
- `@mosaicstack/log` — log ingest, parsing, tiered storage
|
||||
- `@mosaic/memory` — preference store, insight store, semantic search
|
||||
- `@mosaic/log` — log ingest, parsing, tiered storage
|
||||
- Summarization pipeline
|
||||
- Memory integration into agent sessions
|
||||
- Skill management interface (web UI + CLI)
|
||||
|
||||
### Phase 5: Remote Control (v0.0.6)
|
||||
|
||||
- `@mosaicstack/discord-plugin` — Discord channel plugin
|
||||
- `@mosaicstack/telegram-plugin` — Telegram channel plugin
|
||||
- `@mosaic/discord-plugin` — Discord channel plugin
|
||||
- `@mosaic/telegram-plugin` — Telegram channel plugin
|
||||
- Plugin host in gateway
|
||||
- SSO configuration (Authentik)
|
||||
|
||||
### Phase 6: CLI & Tools (v0.0.7)
|
||||
|
||||
- `@mosaicstack/cli` — unified CLI with all subcommands
|
||||
- `@mosaicstack/prdy` — migrate from v0
|
||||
- `@mosaicstack/quality-rails` — migrate from v0
|
||||
- `@mosaicstack/mosaic` — install wizard updated for v1
|
||||
- `@mosaic/cli` — unified CLI with all subcommands
|
||||
- `@mosaic/prdy` — migrate from v0
|
||||
- `@mosaic/quality-rails` — migrate from v0
|
||||
- `@mosaic/mosaic` — install wizard updated for v1
|
||||
- Pi TUI integration (`mosaic tui`)
|
||||
|
||||
### Phase 7: Polish & Beta (v0.0.8 → v0.1.0)
|
||||
@@ -982,11 +982,11 @@ All work is **alpha** (< 0.1.0) until Jason approves 0.1.0 beta release.
|
||||
|
||||
## Assumptions
|
||||
|
||||
1. RESOLVED: **pgvector is sufficient** for semantic search at v0.1.0 scale (personal/family/team = thousands to low hundreds-of-thousands of vectors). `@mosaicstack/memory` defines a `VectorStore` interface with pgvector as the default adapter. The interface boundary makes Qdrant a drop-in migration if PG resource contention or scale demands it later. Zero additional infrastructure for v0.1.0. Rationale: Reduces ops burden; pgvector HNSW indexes are fast at this scale; interface abstraction costs almost nothing now.
|
||||
1. RESOLVED: **pgvector is sufficient** for semantic search at v0.1.0 scale (personal/family/team = thousands to low hundreds-of-thousands of vectors). `@mosaic/memory` defines a `VectorStore` interface with pgvector as the default adapter. The interface boundary makes Qdrant a drop-in migration if PG resource contention or scale demands it later. Zero additional infrastructure for v0.1.0. Rationale: Reduces ops burden; pgvector HNSW indexes are fast at this scale; interface abstraction costs almost nothing now.
|
||||
|
||||
2. RESOLVED: **Authentik is the first SSO provider** — confirmed, already running in Jason's infrastructure. WorkOS and Keycloak adapters follow in Phase 7.
|
||||
|
||||
3. RESOLVED: **NestJS with Fastify adapter for the gateway.** The gateway's complexity (plugin host, agent pool, routing engine, WebSocket hub, MCP server, auth, brain/queue/memory/log integration) warrants NestJS's module system, DI, and guards. Fastify performance preserved via adapter. Aligns with USER.md stated stack ("NestJS API + Next.js web"). @mosaicstack/brain's Fastify code migrates into a NestJS module.
|
||||
3. RESOLVED: **NestJS with Fastify adapter for the gateway.** The gateway's complexity (plugin host, agent pool, routing engine, WebSocket hub, MCP server, auth, brain/queue/memory/log integration) warrants NestJS's module system, DI, and guards. Fastify performance preserved via adapter. Aligns with USER.md stated stack ("NestJS API + Next.js web"). @mosaic/brain's Fastify code migrates into a NestJS module.
|
||||
|
||||
4. RESOLVED: **OpenTelemetry from Phase 0.** Wide-event logging is required from the start. OTEL auto-instrumentation for NestJS/PG/HTTP via `@opentelemetry/sdk-node`. SigNoz as the all-in-one OTEL backend (single Docker service). Every significant operation emits structured events with rich context. Custom spans for agent dispatch, routing decisions, memory writes. Rationale: Retrofitting observability is painful; baking it in from day one means consistent instrumentation across all services.
|
||||
|
||||
@@ -1002,4 +1002,4 @@ All work is **alpha** (< 0.1.0) until Jason approves 0.1.0 beta release.
|
||||
|
||||
10. ASSUMPTION: **Conversations and messages get their own PG tables** (not stored in brain's entity model). They follow a chat-specific schema with proper foreign keys to users and projects. Rationale: Chat has different access patterns (streaming, pagination, search) than brain entities.
|
||||
|
||||
11. RESOLVED: **Pi handles all target LLM providers natively.** Anthropic, OpenAI/Codex, Z.ai, Ollama, LM Studio, and llama.cpp are all supported via Pi's built-in providers or `models.json` configuration with `openai-completions` API type. No custom provider adapters needed in @mosaicstack/agent — only configuration management.
|
||||
11. RESOLVED: **Pi handles all target LLM providers natively.** Anthropic, OpenAI/Codex, Z.ai, Ollama, LM Studio, and llama.cpp are all supported via Pi's built-in providers or `models.json` configuration with `openai-completions` API type. No custom provider adapters needed in @mosaic/agent — only configuration management.
|
||||
|
||||
@@ -108,4 +108,4 @@ The web login page renders provider buttons from `NEXT_PUBLIC_*_ENABLED` flags.
|
||||
|
||||
## Failure mode
|
||||
|
||||
Provider config is optional, but partial config is rejected at startup. If any provider-specific env var is present without the full required set, `@mosaicstack/auth` throws a bootstrap error with the missing keys instead of silently registering a broken provider.
|
||||
Provider config is optional, but partial config is rejected at startup. If any provider-specific env var is present without the full required set, `@mosaic/auth` throws a bootstrap error with the missing keys instead of silently registering a broken provider.
|
||||
|
||||
@@ -91,7 +91,7 @@ packages/cli/src/tui/
|
||||
|
||||
```bash
|
||||
cd /home/jwoltje/src/mosaic-mono-v1-worktrees/tui-improvements
|
||||
pnpm --filter @mosaicstack/cli exec tsx src/cli.ts tui
|
||||
pnpm --filter @mosaic/cli exec tsx src/cli.ts tui
|
||||
# or after build:
|
||||
node packages/cli/dist/cli.js tui --gateway http://localhost:14242
|
||||
```
|
||||
@@ -99,7 +99,7 @@ node packages/cli/dist/cli.js tui --gateway http://localhost:14242
|
||||
### Quality Gates
|
||||
|
||||
```bash
|
||||
pnpm --filter @mosaicstack/cli typecheck && pnpm --filter @mosaicstack/cli lint
|
||||
pnpm --filter @mosaicstack/gateway typecheck && pnpm --filter @mosaicstack/gateway lint
|
||||
pnpm --filter @mosaicstack/types typecheck
|
||||
pnpm --filter @mosaic/cli typecheck && pnpm --filter @mosaic/cli lint
|
||||
pnpm --filter @mosaic/gateway typecheck && pnpm --filter @mosaic/gateway lint
|
||||
pnpm --filter @mosaic/types typecheck
|
||||
```
|
||||
|
||||
@@ -1,39 +1,30 @@
|
||||
# Tasks — Install UX v2
|
||||
# Tasks — Storage Abstraction Retrofit
|
||||
|
||||
> Single-writer: orchestrator only. Workers read but never modify.
|
||||
>
|
||||
> **Mission:** install-ux-v2-20260405
|
||||
> **Schema:** `| id | status | description | issue | agent | branch | depends_on | estimate | notes |`
|
||||
> **Status values:** `not-started` | `in-progress` | `done` | `blocked` | `failed` | `needs-qa`
|
||||
> **Agent values:** `codex` | `sonnet` | `haiku` | `opus` | `—` (auto)
|
||||
> **Mission:** Decouple gateway from hardcoded Postgres/Valkey backends. Introduce interface-driven middleware so the gateway is backend-agnostic. Default to local tier (SQLite + JSON) for zero-dependency installs.
|
||||
>
|
||||
> **`agent` column values:** `codex` | `sonnet` | `haiku` | `glm-5` | `opus` | `—` (auto/default)
|
||||
|
||||
## Milestone 1 — Hotfix: bootstrap DTO + wizard failure + port prefill + copy (IUV-M01)
|
||||
|
||||
| id | status | description | issue | agent | branch | depends_on | estimate | notes |
|
||||
| --------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | ------ | -------------------- | ---------- | -------- | --------------------------------------------------------------------------------------- |
|
||||
| IUV-01-01 | done | Fix `apps/gateway/src/admin/bootstrap.controller.ts:16` — switch `import type { BootstrapSetupDto }` to a value import so Nest's `@Body()` binds the real class | #436 | sonnet | fix/bootstrap-hotfix | — | 3K | PR #440 merged `0ae932ab` |
|
||||
| IUV-01-02 | done | Add integration / e2e test that POSTs `/api/bootstrap/setup` with `{name,email,password}` against a real Nest app instance and asserts 201 — NOT a mocked controller unit test | #436 | sonnet | fix/bootstrap-hotfix | IUV-01-01 | 10K | `apps/gateway/src/admin/bootstrap.e2e.spec.ts` — 4 tests; unplugin-swc added for vitest |
|
||||
| IUV-01-03 | done | `packages/mosaic/src/wizard.ts:147` — propagate `!bootstrapResult.completed` as a wizard failure in **interactive** mode too (not only headless); non-zero exit + no `✔ Wizard complete` line | #436 | sonnet | fix/bootstrap-hotfix | IUV-01-02 | 5K | removed `&& headlessRun` guard |
|
||||
| IUV-01-04 | done | Gateway port prompt prefills `14242` in the input buffer — investigate why `promptPort`'s `defaultValue` isn't reaching the user-visible input | #436 | sonnet | fix/bootstrap-hotfix | IUV-01-03 | 5K | added `initialValue` through prompter interface → clack |
|
||||
| IUV-01-05 | done | `"What is Mosaic?"` intro copy updated to mention Pi SDK as the underlying agent runtime (alongside Claude Code / Codex / OpenCode) | #436 | sonnet | fix/bootstrap-hotfix | IUV-01-04 | 2K | `packages/mosaic/src/stages/welcome.ts` |
|
||||
| IUV-01-06 | done | Tests + code review + PR merge + tag `mosaic-v0.0.26` + Gitea release + npm registry republish | #436 | sonnet | fix/bootstrap-hotfix | IUV-01-05 | 10K | PRs #440/#441/#442 merged; tag `mosaic-v0.0.26`; registry latest=0.0.26 ✓ |
|
||||
|
||||
## Milestone 2 — UX polish: CORS/FQDN, skill installer rework (IUV-M02)
|
||||
|
||||
| id | status | description | issue | agent | branch | depends_on | estimate | notes |
|
||||
| --------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------ | ----- | ------ | ---------------------- | ---------- | -------- | ---------------------------------------------------------------------- |
|
||||
| IUV-02-01 | done | Replace CORS origin prompt with FQDN / hostname input; derive the CORS value internally; default to `localhost` with clear help text | #437 | sonnet | feat/install-ux-polish | — | 10K | `deriveCorsOrigin()` pure fn; MOSAIC_HOSTNAME headless var; PR #444 |
|
||||
| IUV-02-02 | done | Diagnose and document the concrete failure modes of the current skill / additional feature install section end-to-end | #437 | sonnet | feat/install-ux-polish | IUV-02-01 | 8K | selection→install gap, silent catch{}, no whitelist concept |
|
||||
| IUV-02-03 | done | Rework the skill installer so it is usable end-to-end (selection, install, verify, failure reporting) | #437 | sonnet | feat/install-ux-polish | IUV-02-02 | 20K | MOSAIC_INSTALL_SKILLS env var whitelist; SyncSkillsResult typed return |
|
||||
| IUV-02-04 | done | Tests + code review + PR merge | #437 | sonnet | feat/install-ux-polish | IUV-02-03 | 10K | 18 new tests (13 CORS + 5 skills); PR #444 merged `172bacb3` |
|
||||
|
||||
## Milestone 3 — Provider-first intelligent flow + drill-down main menu (IUV-M03)
|
||||
|
||||
| id | status | description | issue | agent | branch | depends_on | estimate | notes |
|
||||
| --------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | ----- | ---------------------- | ---------- | -------- | ------------------------------------------------------------- |
|
||||
| IUV-03-01 | not-started | Design doc: new first-run state machine — main menu (Plugins / Providers / …), Quick Start vs Custom paths, provider-first flow, intent intake + naming loop | #438 | opus | feat/install-ux-intent | — | 15K | scratchpad + explicit non-goals |
|
||||
| IUV-03-02 | not-started | Implement drill-down main menu (Plugins: Recommended / Custom, Providers, …) as the top-level entry point of `mosaic wizard` | #438 | opus | feat/install-ux-intent | IUV-03-01 | 25K | |
|
||||
| IUV-03-03 | not-started | Quick Start path: curated minimum question set — define the exact baseline, delete everything else from the fast path | #438 | opus | feat/install-ux-intent | IUV-03-02 | 15K | |
|
||||
| IUV-03-04 | not-started | Provider-first natural-language intake: user describes intent → agent expounds → agent proposes a name (confirmable / overridable) — OpenClaw-style | #438 | opus | feat/install-ux-intent | IUV-03-03 | 25K | offline fallback required (deterministic default name + path) |
|
||||
| IUV-03-05 | not-started | Preserve backward-compat: headless path (`MOSAIC_ASSUME_YES=1` + env vars) still works end-to-end; `tools/install.sh --yes` unchanged | #438 | opus | feat/install-ux-intent | IUV-03-04 | 10K | |
|
||||
| IUV-03-06 | not-started | Tests + code review + PR merge + `mosaic-v0.0.27` release | #438 | opus | feat/install-ux-intent | IUV-03-05 | 15K | |
|
||||
| id | status | agent | description | tokens |
|
||||
| --------- | ----------- | ------ | ---------------------------------------------------------------- | ------ |
|
||||
| SA-P1-001 | done | sonnet | Define QueueAdapter interface in packages/queue/src/types.ts | 3K |
|
||||
| SA-P1-002 | done | sonnet | Define StorageAdapter interface in packages/storage/src/types.ts | 3K |
|
||||
| SA-P1-003 | done | sonnet | Define MemoryAdapter interface in packages/memory/src/types.ts | 3K |
|
||||
| SA-P1-004 | done | sonnet | Create adapter factory pattern + config types | 3K |
|
||||
| SA-P2-001 | done | sonnet | Refactor @mosaic/queue: wrap ioredis as BullMQ adapter | 3K |
|
||||
| SA-P2-002 | done | sonnet | Create @mosaic/storage: wrap Drizzle as Postgres adapter | 6K |
|
||||
| SA-P2-003 | done | sonnet | Refactor @mosaic/memory: extract pgvector adapter | 4K |
|
||||
| SA-P2-004 | done | sonnet | Update gateway modules to use factories + DI tokens | 5K |
|
||||
| SA-P2-005 | done | opus | Verify Phase 2: all tests pass, typecheck clean | — |
|
||||
| SA-P3-001 | done | sonnet | Implement local queue adapter: JSON file persistence | 5K |
|
||||
| SA-P3-002 | done | sonnet | Implement SQLite storage adapter with better-sqlite3 | 8K |
|
||||
| SA-P3-003 | done | sonnet | Implement keyword memory adapter — no vector dependency | 4K |
|
||||
| SA-P3-004 | done | opus | Verify Phase 3: 42 new tests, 347 total passing | — |
|
||||
| SA-P4-001 | done | sonnet | MosaicConfig schema + loader with tier auto-detection | 6K |
|
||||
| SA-P4-002 | done | sonnet | CLI: mosaic gateway init — interactive wizard | 4K |
|
||||
| SA-P4-003 | done | sonnet | CLI: mosaic gateway start/stop/status lifecycle | 5K |
|
||||
| SA-P4-004 | done | opus | Verify Phase 4: 381 tests passing, 40/40 tasks clean | — |
|
||||
| SA-P5-001 | not-started | codex | Migration tooling: mosaic storage export/import | — |
|
||||
| SA-P5-002 | not-started | codex | Docker Compose profiles: local vs team | — |
|
||||
| SA-P5-003 | not-started | codex | Final verification + docs: README, architecture diagram | — |
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
# Mission Manifest — CLI Unification & E2E First-Run
|
||||
|
||||
> Persistent document tracking full mission scope, status, and session history.
|
||||
> Updated by the orchestrator at each phase transition and milestone completion.
|
||||
|
||||
## Mission
|
||||
|
||||
**ID:** cli-unification-20260404
|
||||
**Statement:** Transform the Mosaic CLI from a partially-duplicated, manually-assembled experience into a single cohesive entry point that installs, configures, and controls the entire Mosaic system. Every Mosaic package gets first-class CLI surface. The first-run experience works end-to-end with no manual stitching. Gateway token recovery is possible without the web UI. Opt-in telemetry uses the published telemetry clients.
|
||||
**Phase:** Complete
|
||||
**Current Milestone:** —
|
||||
**Progress:** 8 / 8 milestones
|
||||
**Status:** completed
|
||||
**Last Updated:** 2026-04-05
|
||||
**Release:** [`mosaic-v0.0.24`](https://git.mosaicstack.dev/mosaicstack/mosaic-stack/releases/tag/mosaic-v0.0.24) (`@mosaicstack/mosaic@0.0.24`, alpha — stays in 0.0.x until GA)
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [x] AC-1: Fresh machine `bash <(curl …install.sh)` → single command lands on a working authenticated gateway with a usable admin token; no secondary manual wizards required
|
||||
- [x] AC-2: `mosaic --help` lists every sub-package as a top-level command and is alphabetized for readability
|
||||
- [x] AC-3: `mosaic auth`, `mosaic brain`, `mosaic forge`, `mosaic log`, `mosaic macp`, `mosaic memory`, `mosaic queue`, `mosaic storage`, `mosaic telemetry` each expose at least one working subcommand that exercises the underlying package
|
||||
- [x] AC-4: Gateway admin token can be rotated or recovered from the CLI alone — operator is never stranded because the web UI is inaccessible
|
||||
- [x] AC-5: `mosaic telemetry` uses the published `@mosaicstack/telemetry-client-js` (from the Gitea npm registry); local OTEL stays for wide-event logging / post-mortems; remote upload is opt-in and disabled by default
|
||||
- [x] AC-6: Install → wizard → gateway install → TUI verification flow is a single cohesive path with clear state transitions and no dead ends
|
||||
- [x] AC-7: `@mosaicstack/mosaic` is the sole `mosaic` binary owner; `@mosaicstack/cli` is gone from the repo and all docs
|
||||
- [x] AC-8: All milestones ship as merged PRs with green CI, closed issues, and updated release notes
|
||||
|
||||
## Milestones
|
||||
|
||||
| # | ID | Name | Status | Branch | Issue | Started | Completed |
|
||||
| --- | ------ | ------------------------------------------------------------------------ | ------ | ----------------------------------- | --------------------------------- | ---------- | ---------- |
|
||||
| 1 | cu-m01 | Kill legacy @mosaicstack/cli package | done | chore/remove-cli-package-duplicate | #398 | 2026-04-04 | 2026-04-04 |
|
||||
| 2 | cu-m02 | Archive stale mission state + scaffold new mission | done | docs/mission-cli-unification | #399 | 2026-04-04 | 2026-04-04 |
|
||||
| 3 | cu-m03 | Fix gateway bootstrap token recovery (server + CLI paths) | done | feat/gateway-token-recovery | #411, #414 | 2026-04-05 | 2026-04-05 |
|
||||
| 4 | cu-m04 | Alphabetize + group `mosaic --help` output | done | feat/help-sort + feat/mosaic-config | #402, #408 | 2026-04-05 | 2026-04-05 |
|
||||
| 5 | cu-m05 | Sub-package CLI surface (auth/brain/forge/log/macp/memory/queue/storage) | done | feat/mosaic-\*-cli (x9) | #403–#407, #410, #412, #413, #415 | 2026-04-05 | 2026-04-05 |
|
||||
| 6 | cu-m06 | `mosaic telemetry` — local OTEL + opt-in remote upload | done | feat/mosaic-telemetry | #417 | 2026-04-05 | 2026-04-05 |
|
||||
| 7 | cu-m07 | Unified first-run UX (install.sh → wizard → gateway → TUI) | done | feat/mosaic-first-run-ux | #418 | 2026-04-05 | 2026-04-05 |
|
||||
| 8 | cu-m08 | Docs refresh + release tag | done | docs/cli-unification-release-v0.1.0 | #419 | 2026-04-05 | 2026-04-05 |
|
||||
|
||||
## Deployment
|
||||
|
||||
| Target | URL | Method |
|
||||
| -------------------- | --------- | ----------------------------------------------- |
|
||||
| Local tier (default) | localhost | `mosaic gateway install` — pglite + local queue |
|
||||
| Team tier | any host | `mosaic gateway install` — PG + Valkey |
|
||||
| Docker Compose (dev) | localhost | `docker compose up` for PG/Valkey/OTEL/Jaeger |
|
||||
|
||||
## Coordination
|
||||
|
||||
- **Primary Agent:** claude-opus-4-6[1m]
|
||||
- **Sibling Agents:** sonnet (standard implementation), haiku (status/explore/verify), codex (coding-heavy tasks)
|
||||
- **Shared Contracts:** `docs/PRD.md` (existing v0.1.0 PRD — still the long-term target), this manifest, `docs/TASKS.md`, `docs/scratchpads/cli-unification-20260404.md`
|
||||
|
||||
## Token Budget
|
||||
|
||||
| Metric | Value |
|
||||
| ------ | ------ |
|
||||
| Budget | TBD |
|
||||
| Used | ~80K |
|
||||
| Mode | normal |
|
||||
|
||||
## Session History
|
||||
|
||||
| Session | Runtime | Started | Duration | Ended Reason | Last Task |
|
||||
| ------- | --------------- | ---------- | -------- | ---------------- | ------------------------------------------------------------ |
|
||||
| 1 | claude-opus-4-6 | 2026-04-04 | ~4h | context-budget | cu-m01 + cu-m02 merged (#398, #399); open questions resolved |
|
||||
| 2 | claude-opus-4-6 | 2026-04-05 | ~6h | mission-complete | cu-m03..cu-m08 all merged; mosaic-v0.1.0 released |
|
||||
|
||||
## Scratchpad
|
||||
|
||||
Path: `docs/scratchpads/cli-unification-20260404.md`
|
||||
@@ -1,90 +0,0 @@
|
||||
# Tasks — CLI Unification & E2E First-Run
|
||||
|
||||
> Single-writer: orchestrator only. Workers read but never modify.
|
||||
>
|
||||
> **Mission:** cli-unification-20260404
|
||||
> **Schema:** `| id | status | description | issue | agent | branch | depends_on | estimate | notes |`
|
||||
> **Status values:** `not-started` | `in-progress` | `done` | `blocked` | `failed` | `needs-qa`
|
||||
> **Agent values:** `codex` | `sonnet` | `haiku` | `opus` | `glm-5` | `—` (auto)
|
||||
|
||||
## Milestone 1 — Kill legacy @mosaicstack/cli (done)
|
||||
|
||||
| id | status | description | issue | agent | branch | depends_on | estimate | notes |
|
||||
| -------- | ------ | ----------------------------------------------------------------- | ----- | ----- | ---------------------------------- | ---------- | -------- | --------------------------- |
|
||||
| CU-01-01 | done | Delete packages/cli directory; update workspace + docs references | #398 | opus | chore/remove-cli-package-duplicate | — | 5K | Merged c39433c3. 6685 LOC−. |
|
||||
|
||||
## Milestone 2 — Archive stale mission + scaffold new mission (done)
|
||||
|
||||
| id | status | description | issue | agent | branch | depends_on | estimate | notes |
|
||||
| -------- | ------ | ------------------------------------------------------------------ | ----- | ----- | ---------------------------- | ---------- | -------- | --------------------------------- |
|
||||
| CU-02-01 | done | Move stale MISSION-MANIFEST / TASKS / PRD-Harness to docs/archive/ | #399 | opus | docs/mission-cli-unification | CU-01-01 | 3K | Harness + storage missions done. |
|
||||
| CU-02-02 | done | Scaffold new MISSION-MANIFEST.md, TASKS.md, scratchpad | #399 | opus | docs/mission-cli-unification | CU-02-01 | 5K | This file + manifest + scratchpad |
|
||||
| CU-02-03 | done | PR review, merge, branch cleanup | #399 | opus | docs/mission-cli-unification | CU-02-02 | 2K | Merged as 6f15a84c |
|
||||
|
||||
## Milestone 3 — Gateway bootstrap token recovery
|
||||
|
||||
| id | status | description | issue | agent | branch | depends_on | estimate | notes |
|
||||
| -------- | ------ | ---------------------------------------------------------------------------------------------- | ----- | ------ | ------ | ---------- | -------- | ----------------------------- |
|
||||
| CU-03-01 | done | Implementation plan for BetterAuth-cookie recovery flow (decision locked 2026-04-04) | — | opus | — | CU-02-03 | 4K | Design locked; plan-only task |
|
||||
| CU-03-02 | done | Server: add recovery/rotate endpoint on apps/gateway/src/admin (gated by design from CU-03-01) | — | sonnet | — | CU-03-01 | 12K | |
|
||||
| CU-03-03 | done | CLI: `mosaic gateway login` — interactive BetterAuth sign-in, persist session | — | sonnet | — | CU-03-02 | 10K | |
|
||||
| CU-03-04 | done | CLI: `mosaic gateway config rotate-token` — mint new admin token via authenticated API | — | sonnet | — | CU-03-03 | 8K | |
|
||||
| CU-03-05 | done | CLI: `mosaic gateway config recover-token` — execute the recovery flow from CU-03-01 | — | sonnet | — | CU-03-03 | 10K | |
|
||||
| CU-03-06 | done | Install UX: fix the "user exists, no token" dead-end in runInstall bootstrapFirstUser path | — | sonnet | — | CU-03-05 | 8K | |
|
||||
| CU-03-07 | done | Tests: integration tests for each recovery path (happy + error) | — | sonnet | — | CU-03-06 | 10K | |
|
||||
| CU-03-08 | done | Code review + remediation | — | haiku | — | CU-03-07 | 4K | |
|
||||
|
||||
## Milestone 4 — `mosaic --help` alphabetize + grouping
|
||||
|
||||
| id | status | description | issue | agent | branch | depends_on | estimate | notes |
|
||||
| -------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | ------ | ------ | ---------- | -------- | ------------------------------- |
|
||||
| CU-04-01 | done | Enable `configureHelp({ sortSubcommands: true })` on root program and each subgroup | — | sonnet | — | CU-02-03 | 3K | |
|
||||
| CU-04-02 | done | Group commands into sections (Runtime, Gateway, Framework, Platform) in help output | — | sonnet | — | CU-04-01 | 5K | |
|
||||
| CU-04-03 | done | Verify help snapshots render readably; update any docs with stale output | — | haiku | — | CU-04-02 | 3K | |
|
||||
| CU-04-04 | done | Top-level `mosaic config` command — `show`, `get <key>`, `set <key> <val>`, `edit`, `path` — wraps packages/mosaic/src/config/config-service.ts (framework/agent config; distinct from `mosaic gateway config`) | — | sonnet | — | CU-02-03 | 10K | New scope (decision 2026-04-04) |
|
||||
| CU-04-05 | done | Tests + code review for CU-04-04 | — | haiku | — | CU-04-04 | 4K | |
|
||||
|
||||
## Milestone 5 — Sub-package CLI surface
|
||||
|
||||
> Pattern: each sub-package exports `register<Name>Command(program: Command)` co-located with the library code (proven by `@mosaicstack/quality-rails`). Wire into `packages/mosaic/src/cli.ts`.
|
||||
|
||||
| id | status | description | issue | agent | branch | depends_on | estimate | notes |
|
||||
| -------- | ------ | --------------------------------------------------------------------------------------------------------- | ----- | ------ | ------ | ---------- | -------- | ------------------- |
|
||||
| CU-05-01 | done | `mosaic forge` — subcommands: `run`, `status`, `resume`, `personas list` | — | sonnet | — | CU-02-03 | 18K | User priority |
|
||||
| CU-05-02 | done | `mosaic storage` — subcommands: `status`, `tier show`, `tier switch`, `export`, `import`, `migrate` | — | sonnet | — | CU-02-03 | 15K | |
|
||||
| CU-05-03 | done | `mosaic queue` — subcommands: `list`, `stats`, `pause/resume`, `jobs tail`, `drain` | — | sonnet | — | CU-02-03 | 12K | |
|
||||
| CU-05-04 | done | `mosaic memory` — subcommands: `search`, `stats`, `insights list`, `preferences list` | — | sonnet | — | CU-02-03 | 12K | |
|
||||
| CU-05-05 | done | `mosaic brain` — subcommands: `projects list/create`, `missions list`, `tasks list`, `conversations list` | — | sonnet | — | CU-02-03 | 15K | |
|
||||
| CU-05-06 | done | `mosaic auth` — subcommands: `users list/create/delete`, `sso list`, `sso test`, `sessions list` | — | sonnet | — | CU-03-03 | 15K | needs gateway login |
|
||||
| CU-05-07 | done | `mosaic log` — subcommands: `tail`, `search`, `export`, `level <level>` | — | sonnet | — | CU-02-03 | 10K | |
|
||||
| CU-05-08 | done | `mosaic macp` — subcommands: `tasks list`, `submit`, `gate`, `events tail` | — | sonnet | — | CU-02-03 | 12K | |
|
||||
| CU-05-09 | done | Wire all eight `register<Name>Command` calls into packages/mosaic/src/cli.ts | — | haiku | — | CU-05-01…8 | 3K | |
|
||||
| CU-05-10 | done | Integration test: `mosaic <cmd> --help` exits 0 for every new command | — | haiku | — | CU-05-09 | 5K | |
|
||||
|
||||
## Milestone 6 — `mosaic telemetry`
|
||||
|
||||
| id | status | description | issue | agent | branch | depends_on | estimate | notes |
|
||||
| -------- | ------ | ------------------------------------------------------------------------------------------------- | ----- | ------ | ------ | ---------- | -------- | ---------------------------------------------- |
|
||||
| CU-06-01 | done | Add `@mosaicstack/telemetry-client-js` as dependency of `@mosaicstack/mosaic` from Gitea registry | — | sonnet | — | CU-02-03 | 3K | |
|
||||
| CU-06-02 | done | `mosaic telemetry local` — status, tail, Jaeger link (wraps existing apps/gateway/src/tracing.ts) | — | sonnet | — | CU-06-01 | 8K | |
|
||||
| CU-06-03 | done | `mosaic telemetry` — status, opt-in, opt-out, test, upload (uses telemetry-client-js) | — | sonnet | — | CU-06-01 | 12K | Dry-run mode when server endpoint not yet live |
|
||||
| CU-06-04 | done | Persistent consent state in mosaic config; disabled by default | — | sonnet | — | CU-06-03 | 5K | |
|
||||
| CU-06-05 | done | Tests + code review | — | haiku | — | CU-06-04 | 5K | |
|
||||
|
||||
## Milestone 7 — Unified first-run UX
|
||||
|
||||
| id | status | description | issue | agent | branch | depends_on | estimate | notes |
|
||||
| -------- | ------ | ---------------------------------------------------------------------------------------------- | ----- | ------ | ------ | ---------- | -------- | ----- |
|
||||
| CU-07-01 | done | tools/install.sh: after npm install, hand off to `mosaic wizard` then `mosaic gateway install` | — | sonnet | — | CU-03-06 | 10K | |
|
||||
| CU-07-02 | done | `mosaic wizard` and `mosaic gateway install` coordination: shared state, no duplicate prompts | — | sonnet | — | CU-07-01 | 12K | |
|
||||
| CU-07-03 | done | Post-install verification step: "gateway healthy, tui connects, admin token on file" | — | sonnet | — | CU-07-02 | 8K | |
|
||||
| CU-07-04 | done | End-to-end test on a clean container from scratch | — | haiku | — | CU-07-03 | 8K | |
|
||||
|
||||
## Milestone 8 — Docs + release
|
||||
|
||||
| id | status | description | issue | agent | branch | depends_on | estimate | notes |
|
||||
| -------- | ------ | ---------------------------------------------------------------------- | ----- | ------ | ------ | ---------- | -------- | ----- |
|
||||
| CU-08-01 | done | Update README.md with new command tree, install flow, and feature list | — | sonnet | — | CU-07-04 | 8K | |
|
||||
| CU-08-02 | done | Update docs/guides/user-guide.md with all new sub-package commands | — | sonnet | — | CU-08-01 | 10K | |
|
||||
| CU-08-03 | done | Version bump `@mosaicstack/mosaic`, publish to Gitea registry | — | opus | — | CU-08-02 | 3K | |
|
||||
| CU-08-04 | done | Release notes, tag `v0.1.0-rc.N`, publish release on Gitea | — | opus | — | CU-08-03 | 3K | |
|
||||
@@ -1,70 +0,0 @@
|
||||
# Mission Manifest — Harness Foundation
|
||||
|
||||
> Persistent document tracking full mission scope, status, and session history.
|
||||
> Updated by the orchestrator at each phase transition and milestone completion.
|
||||
|
||||
## Mission
|
||||
|
||||
**ID:** harness-20260321
|
||||
**Statement:** Transform Mosaic Stack from a functional demo into a real multi-provider, task-routing AI harness. Persist all conversations, integrate frontier LLM providers (Anthropic, OpenAI, OpenRouter, Z.ai, Ollama), build granular task-aware agent routing, harden agent sessions, replace cron with BullMQ, and design the channel protocol for future Matrix/remote integration.
|
||||
**Phase:** Complete
|
||||
**Current Milestone:** All milestones done
|
||||
**Progress:** 7 / 7 milestones
|
||||
**Status:** complete
|
||||
**Last Updated:** 2026-03-22 UTC
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [x] AC-1: Send messages in TUI → restart TUI → resume conversation → agent has full history and context
|
||||
- [x] AC-2: Route a coding task to Claude Opus 4.6, a simple question to Haiku, a summarization to GLM-5 — all via granular routing rules
|
||||
- [x] AC-3: Two users exist, User A's memory searches never return User B's data
|
||||
- [x] AC-4: `/model claude-sonnet-4-6` in TUI switches the active model for subsequent messages
|
||||
- [x] AC-5: `/agent coding-agent` in TUI switches to a different agent with different system prompt and tools
|
||||
- [x] AC-6: BullMQ jobs execute on schedule, failures retry with backoff, admin can inspect via `/api/admin/jobs`
|
||||
- [x] AC-7: Channel protocol document exists with Matrix integration points defined, reviewed, and approved
|
||||
- [x] AC-8: Embeddings run on Ollama local models (no external API dependency for vector operations)
|
||||
- [x] AC-9: All five providers (Anthropic, OpenAI, OpenRouter, Z.ai, Ollama) connect, list models, and complete chat requests
|
||||
- [x] AC-10: Routing transparency — TUI displays which model was selected and the routing reason for each response
|
||||
|
||||
## Milestones
|
||||
|
||||
| # | ID | Name | Status | Branch | Issue | Started | Completed |
|
||||
| --- | ------ | ---------------------------------- | ------ | ------ | --------- | ---------- | ---------- |
|
||||
| 1 | ms-166 | Conversation Persistence & Context | done | — | #224–#231 | 2026-03-21 | 2026-03-21 |
|
||||
| 2 | ms-167 | Security & Isolation | done | — | #232–#239 | 2026-03-21 | 2026-03-21 |
|
||||
| 3 | ms-168 | Provider Integration | done | — | #240–#251 | 2026-03-21 | 2026-03-22 |
|
||||
| 4 | ms-169 | Agent Routing Engine | done | — | #252–#264 | 2026-03-22 | 2026-03-22 |
|
||||
| 5 | ms-170 | Agent Session Hardening | done | — | #265–#272 | 2026-03-22 | 2026-03-22 |
|
||||
| 6 | ms-171 | Job Queue Foundation | done | — | #273–#280 | 2026-03-22 | 2026-03-22 |
|
||||
| 7 | ms-172 | Channel Protocol Design | done | — | #281–#288 | 2026-03-22 | 2026-03-22 |
|
||||
|
||||
## Deployment
|
||||
|
||||
| Target | URL | Method |
|
||||
| -------------------- | --------- | -------------------------- |
|
||||
| Docker Compose (dev) | localhost | docker compose up |
|
||||
| Production | TBD | Docker Swarm via Portainer |
|
||||
|
||||
## Coordination
|
||||
|
||||
- **Primary Agent:** claude-opus-4-6
|
||||
- **Sibling Agents:** sonnet (workers), haiku (verification)
|
||||
- **Shared Contracts:** docs/PRD-Harness_Foundation.md, docs/TASKS.md
|
||||
|
||||
## Token Budget
|
||||
|
||||
| Metric | Value |
|
||||
| ------ | ------ |
|
||||
| Budget | — |
|
||||
| Used | ~2.5M |
|
||||
| Mode | normal |
|
||||
|
||||
## Session History
|
||||
|
||||
| Session | Runtime | Started | Duration | Ended Reason | Last Task |
|
||||
| ------- | --------------- | ---------- | -------- | ------------ | ----------------- |
|
||||
| 1 | claude-opus-4-6 | 2026-03-21 | ~6h | complete | M7-008 — all done |
|
||||
|
||||
## Scratchpad
|
||||
|
||||
Path: `docs/scratchpads/harness-20260321.md`
|
||||
@@ -1,57 +0,0 @@
|
||||
# Mission Manifest — Install UX Hardening
|
||||
|
||||
> Persistent document tracking full mission scope, status, and session history.
|
||||
> Updated by the orchestrator at each phase transition and milestone completion.
|
||||
|
||||
## Mission
|
||||
|
||||
**ID:** install-ux-hardening-20260405
|
||||
**Statement:** Close the remaining gaps in the Mosaic Stack first-run and teardown experience uncovered by the post-`cli-unification` audit. A user MUST be able to cleanly uninstall the stack; the wizard MUST make security-sensitive surfaces visible (hooks, password entry); and CI/headless installs MUST NOT hang on interactive prompts. The longer-term goal is a single cohesive first-run flow that collapses `mosaic wizard` and `mosaic gateway install` into one state-bridged experience.
|
||||
**Phase:** Complete
|
||||
**Current Milestone:** —
|
||||
**Progress:** 3 / 3 milestones
|
||||
**Status:** complete
|
||||
**Last Updated:** 2026-04-05 (mission complete)
|
||||
**Parent Mission:** [cli-unification-20260404](./archive/missions/cli-unification-20260404/MISSION-MANIFEST.md) (complete)
|
||||
|
||||
## Context
|
||||
|
||||
Post-merge audit of `cli-unification-20260404` (AC-1, AC-6) validated that the first-run wizard covers first user, password, admin tokens, gateway instance config, skills, and SOUL.md/USER.md init. The audit surfaced six gaps, grouped into three tracks of independent value.
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [x] AC-1: `mosaic uninstall` (top-level) cleanly reverses every mutation made by `tools/install.sh` — framework data, npm CLI, nested stack deps, runtime asset injections in `~/.claude/`, npmrc scope mapping, PATH edits. Dry-run supported. `--keep-data` preserves memory + user files + gateway DB. (PR #429)
|
||||
- [x] AC-2: `curl … | bash -s -- --uninstall` works without requiring a functioning CLI. (PR #429)
|
||||
- [x] AC-3: Password entry in `bootstrapFirstUser` is masked (no plaintext echo); confirm prompt added. (PR #431)
|
||||
- [x] AC-4: Wizard has an explicit hooks stage that previews which hooks will be installed, asks for confirmation, and records the user's choice. `mosaic config hooks list|enable|disable` surface exists. (PR #431 — consent; PR #433 — finalize-stage gating now honors `state.hooks.accepted === false` end-to-end)
|
||||
- [x] AC-5: `runConfigWizard` and `bootstrapFirstUser` accept a headless path (env vars + `--yes`) so `tools/install.sh --yes` + `MOSAIC_ASSUME_YES=1` completes end-to-end in CI without TTY. (PR #431)
|
||||
- [x] AC-6: `mosaic wizard` and `mosaic gateway install` are collapsed into a single cohesive entry point with shared state; gateway install is now terminal stages 11 & 12 of `runWizard`, session-file bridge removed, `mosaic gateway install` preserved as a thin standalone wrapper. (PR #433)
|
||||
- [x] AC-7: All milestones shipped as merged PRs with green CI and closed issues. (PRs #429, #431, #433)
|
||||
|
||||
## Milestones
|
||||
|
||||
| # | ID | Name | Status | Branch | Issue | Started | Completed |
|
||||
| --- | ------- | --------------------------------------------------------- | ------ | ----------------------- | ----- | ---------- | ---------- |
|
||||
| 1 | IUH-M01 | `mosaic uninstall` — top-level teardown + shell wrapper | done | feat/mosaic-uninstall | #425 | 2026-04-05 | 2026-04-05 |
|
||||
| 2 | IUH-M02 | Wizard remediation — hooks visibility, pwd mask, headless | done | feat/wizard-remediation | #426 | 2026-04-05 | 2026-04-05 |
|
||||
| 3 | IUH-M03 | Unified first-run wizard (collapse wizard + gateway) | done | feat/unified-first-run | #427 | 2026-04-05 | 2026-04-05 |
|
||||
|
||||
## Subagent Delegation Plan
|
||||
|
||||
| Milestone | Recommended Tier | Rationale |
|
||||
| --------- | ---------------- | ---------------------------------------------------------------------- |
|
||||
| IUH-M01 | sonnet | Standard feature work — new command surface mirroring existing install |
|
||||
| IUH-M02 | sonnet | Small surgical fixes across 3-4 files |
|
||||
| IUH-M03 | opus | Architectural refactor; state machine design decisions |
|
||||
|
||||
## Risks
|
||||
|
||||
- **Reversal completeness** — runtime asset linking creates `.mosaic-bak-*` backups; uninstall must honor them vs. when to delete. Ambiguity without an install manifest.
|
||||
- **npm global nested deps** — `npm uninstall -g @mosaicstack/mosaic` removes nested `@mosaicstack/*`, but ownership conflicts with explicitly installed peer packages (`@mosaicstack/gateway`, `@mosaicstack/memory`) need test coverage.
|
||||
- **Headless bootstrap** — admin password via env var is a credential on disk; needs clear documentation that `MOSAIC_ADMIN_PASSWORD` is intended for CI-only and should be rotated post-install.
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- `mosaicstack.dev/install.sh` vanity URL (blocked on marketing site work)
|
||||
- Uninstall for the `@mosaicstack/gateway` database contents — delegated to `mosaic gateway uninstall` semantics already in place
|
||||
- Signature/checksum verification of install scripts
|
||||
@@ -1,41 +0,0 @@
|
||||
# Tasks — Install UX Hardening
|
||||
|
||||
> Single-writer: orchestrator only. Workers read but never modify.
|
||||
>
|
||||
> **Mission:** install-ux-hardening-20260405
|
||||
> **Schema:** `| id | status | description | issue | agent | branch | depends_on | estimate | notes |`
|
||||
> **Status values:** `not-started` | `in-progress` | `done` | `blocked` | `failed` | `needs-qa`
|
||||
> **Agent values:** `codex` | `sonnet` | `haiku` | `opus` | `—` (auto)
|
||||
|
||||
## Milestone 1 — `mosaic uninstall` (IUH-M01)
|
||||
|
||||
| id | status | description | issue | agent | branch | depends_on | estimate | notes |
|
||||
| --------- | ------ | ------------------------------------------------------------------------------------------------------------------- | ----- | ------ | --------------------- | ---------- | -------- | ------------------------------------------------------ |
|
||||
| IUH-01-01 | done | Design install manifest schema (`~/.config/mosaic/.install-manifest.json`) — what install writes on first success | #425 | sonnet | feat/mosaic-uninstall | — | 8K | v1 schema in `install-manifest.ts` |
|
||||
| IUH-01-02 | done | `mosaic uninstall` TS command: `--framework`, `--cli`, `--gateway`, `--all`, `--keep-data`, `--yes`, `--dry-run` | #425 | sonnet | feat/mosaic-uninstall | IUH-01-01 | 25K | `uninstall.ts` |
|
||||
| IUH-01-03 | done | Reverse runtime asset linking in `~/.claude/` — restore `.mosaic-bak-*` if present, remove managed copies otherwise | #425 | sonnet | feat/mosaic-uninstall | IUH-01-02 | 12K | file list hardcoded from mosaic-link-runtime-assets |
|
||||
| IUH-01-04 | done | Reverse npmrc scope mapping and PATH edits made by `tools/install.sh` | #425 | sonnet | feat/mosaic-uninstall | IUH-01-02 | 8K | npmrc reversed; no PATH edits found in v0.0.24 install |
|
||||
| IUH-01-05 | done | Shell fallback: `tools/install.sh --uninstall` path for users without a working CLI | #425 | sonnet | feat/mosaic-uninstall | IUH-01-02 | 10K | |
|
||||
| IUH-01-06 | done | Vitest coverage: dry-run output, `--all`, `--keep-data`, partial state, missing manifest | #425 | sonnet | feat/mosaic-uninstall | IUH-01-05 | 15K | 14 new tests, 170 total |
|
||||
| IUH-01-07 | done | Code review (independent) + remediation | #425 | sonnet | feat/mosaic-uninstall | IUH-01-06 | 5K | |
|
||||
| IUH-01-08 | done | PR open, CI green, review, merge to `main`, close issue | #425 | sonnet | feat/mosaic-uninstall | IUH-01-07 | 3K | PR #429, merge 25cada77 |
|
||||
|
||||
## Milestone 2 — Wizard Remediation (IUH-M02)
|
||||
|
||||
| id | status | description | issue | agent | branch | depends_on | estimate | notes |
|
||||
| --------- | ------ | -------------------------------------------------------------------------------------------------------------- | ----- | ------ | ----------------------- | ---------- | -------- | ----------------------------------------------- |
|
||||
| IUH-02-01 | done | Password masking: replace plaintext `rl.question` in `bootstrapFirstUser` with masked TTY read + confirmation | #426 | sonnet | feat/wizard-remediation | IUH-01-08 | 8K | `prompter/masked-prompt.ts` |
|
||||
| IUH-02-02 | done | Hooks preview stage in wizard: show `framework/runtime/claude/hooks-config.json` entries + confirm prompt | #426 | sonnet | feat/wizard-remediation | IUH-02-01 | 12K | `stages/hooks-preview.ts`; finalize gating TODO |
|
||||
| IUH-02-03 | done | `mosaic config hooks list\|enable\|disable` subcommands | #426 | sonnet | feat/wizard-remediation | IUH-02-02 | 15K | `commands/config.ts` |
|
||||
| IUH-02-04 | done | Headless path: env-var driven `runConfigWizard` + `bootstrapFirstUser` (`MOSAIC_ASSUME_YES`, `MOSAIC_ADMIN_*`) | #426 | sonnet | feat/wizard-remediation | IUH-02-03 | 12K | |
|
||||
| IUH-02-05 | done | Tests + code review + PR merge | #426 | sonnet | feat/wizard-remediation | IUH-02-04 | 10K | PR #431, merge cd8b1f66 |
|
||||
|
||||
## Milestone 3 — Unified First-Run Wizard (IUH-M03)
|
||||
|
||||
| id | status | description | issue | agent | branch | depends_on | estimate | notes |
|
||||
| --------- | ------ | ----------------------------------------------------------------------------------------------------------- | ----- | ----- | ---------------------- | ---------- | -------- | ---------------------------------- |
|
||||
| IUH-03-01 | done | Design doc: unified state machine; decide whether `mosaic gateway install` becomes an internal wizard stage | #427 | opus | feat/unified-first-run | IUH-02-05 | 10K | scratchpad Session 5 |
|
||||
| IUH-03-02 | done | Refactor `runWizard` to invoke gateway install as a stage; drop the 10-minute session-file bridge | #427 | opus | feat/unified-first-run | IUH-03-01 | 25K | stages 11 & 12; bridge removed |
|
||||
| IUH-03-03 | done | Preserve backward-compat: `mosaic gateway install` still works as a standalone entry point | #427 | opus | feat/unified-first-run | IUH-03-02 | 10K | thin wrapper over stages |
|
||||
| IUH-03-04 | done | Tests + code review + PR merge | #427 | opus | feat/unified-first-run | IUH-03-03 | 12K | PR #433, merge 732f8a49; +15 tests |
|
||||
| IUH-03-05 | done | Bonus: honor `state.hooks.accepted` in finalize stage (closes M02 follow-up) | #427 | opus | feat/unified-first-run | IUH-03-04 | 5K | MOSAIC_SKIP_CLAUDE_HOOKS env flag |
|
||||
@@ -1,30 +0,0 @@
|
||||
# Tasks — Storage Abstraction Retrofit
|
||||
|
||||
> Single-writer: orchestrator only. Workers read but never modify.
|
||||
>
|
||||
> **Mission:** Decouple gateway from hardcoded Postgres/Valkey backends. Introduce interface-driven middleware so the gateway is backend-agnostic. Default to local tier (SQLite + JSON) for zero-dependency installs.
|
||||
>
|
||||
> **`agent` column values:** `codex` | `sonnet` | `haiku` | `glm-5` | `opus` | `—` (auto/default)
|
||||
|
||||
| id | status | agent | description | tokens |
|
||||
| --------- | ----------- | ------ | ---------------------------------------------------------------- | ------ |
|
||||
| SA-P1-001 | done | sonnet | Define QueueAdapter interface in packages/queue/src/types.ts | 3K |
|
||||
| SA-P1-002 | done | sonnet | Define StorageAdapter interface in packages/storage/src/types.ts | 3K |
|
||||
| SA-P1-003 | done | sonnet | Define MemoryAdapter interface in packages/memory/src/types.ts | 3K |
|
||||
| SA-P1-004 | done | sonnet | Create adapter factory pattern + config types | 3K |
|
||||
| SA-P2-001 | done | sonnet | Refactor @mosaicstack/queue: wrap ioredis as BullMQ adapter | 3K |
|
||||
| SA-P2-002 | done | sonnet | Create @mosaicstack/storage: wrap Drizzle as Postgres adapter | 6K |
|
||||
| SA-P2-003 | done | sonnet | Refactor @mosaicstack/memory: extract pgvector adapter | 4K |
|
||||
| SA-P2-004 | done | sonnet | Update gateway modules to use factories + DI tokens | 5K |
|
||||
| SA-P2-005 | done | opus | Verify Phase 2: all tests pass, typecheck clean | — |
|
||||
| SA-P3-001 | done | sonnet | Implement local queue adapter: JSON file persistence | 5K |
|
||||
| SA-P3-002 | done | sonnet | Implement SQLite storage adapter with better-sqlite3 | 8K |
|
||||
| SA-P3-003 | done | sonnet | Implement keyword memory adapter — no vector dependency | 4K |
|
||||
| SA-P3-004 | done | opus | Verify Phase 3: 42 new tests, 347 total passing | — |
|
||||
| SA-P4-001 | done | sonnet | MosaicConfig schema + loader with tier auto-detection | 6K |
|
||||
| SA-P4-002 | done | sonnet | CLI: mosaic gateway init — interactive wizard | 4K |
|
||||
| SA-P4-003 | done | sonnet | CLI: mosaic gateway start/stop/status lifecycle | 5K |
|
||||
| SA-P4-004 | done | opus | Verify Phase 4: 381 tests passing, 40/40 tasks clean | — |
|
||||
| SA-P5-001 | not-started | codex | Migration tooling: mosaic storage export/import | — |
|
||||
| SA-P5-002 | not-started | codex | Docker Compose profiles: local vs team | — |
|
||||
| SA-P5-003 | not-started | codex | Final verification + docs: README, architecture diagram | — |
|
||||
@@ -12,9 +12,9 @@ context: Agents coupled directly to infrastructure backends, bypassing intended
|
||||
Current packages are **direct adapters**, not **middleware**:
|
||||
| Package | Current State | Intended Design |
|
||||
|---------|---------------|-----------------|
|
||||
| `@mosaicstack/queue` | `ioredis` hardcoded | Interface → BullMQ OR local-files |
|
||||
| `@mosaicstack/db` | Drizzle + Postgres hardcoded | Interface → Postgres OR SQLite OR JSON/MD |
|
||||
| `@mosaicstack/memory` | pgvector required | Interface → pgvector OR sqlite-vec OR keyword-search |
|
||||
| `@mosaic/queue` | `ioredis` hardcoded | Interface → BullMQ OR local-files |
|
||||
| `@mosaic/db` | Drizzle + Postgres hardcoded | Interface → Postgres OR SQLite OR JSON/MD |
|
||||
| `@mosaic/memory` | pgvector required | Interface → pgvector OR sqlite-vec OR keyword-search |
|
||||
|
||||
## The gateway and TUI import these packages directly, which means they they're coupled to specific infrastructure. Users cannot run Mosaic Stack without Postgres + Valkey.
|
||||
|
||||
@@ -46,15 +46,15 @@ The gateway imports the interface, not the backend. At startup it reads config a
|
||||
|
||||
```typescript
|
||||
// What should have happened:
|
||||
gateway/queue.service.ts → @mosaicstack/queue (interface) → queue.adapter.ts
|
||||
gateway/queue.service.ts → @mosaic/queue (interface) → queue.adapter.ts
|
||||
|
||||
// What actually happened:
|
||||
gateway/queue.service.ts → @mosaicstack/queue → ioredis (hardcoded)
|
||||
gateway/queue.service.ts → @mosaic/queue → ioredis (hardcoded)
|
||||
```
|
||||
|
||||
## The Current State Analysis
|
||||
|
||||
### `@mosaicstack/queue` (packages/queue/src/queue.ts)
|
||||
### `@mosaic/queue` (packages/queue/src/queue.ts)
|
||||
|
||||
```typescript
|
||||
import Redis from 'ioredis'; // ← Direct import of backend
|
||||
@@ -68,7 +68,7 @@ export function createQueue(config?: QueueConfig): QueueHandle {
|
||||
|
||||
**Problem:** `ioredis` is imported in the package, not the adapter interface. Consumers cannot swap backends.
|
||||
|
||||
### `@mosaicstack/db` (packages/db/src/client.ts)
|
||||
### `@mosaic/db` (packages/db/src/client.ts)
|
||||
|
||||
```typescript
|
||||
import { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js';
|
||||
@@ -84,10 +84,10 @@ export function createDb(url?: string): DbHandle {
|
||||
|
||||
**Problem:** Drizzle + Postgres is hardcoded. No SQLite, JSON, or file-based options.
|
||||
|
||||
### `@mosaicstack/memory` (packages/memory/src/memory.ts)
|
||||
### `@mosaic/memory` (packages/memory/src/memory.ts)
|
||||
|
||||
```typescript
|
||||
import type { Db } from '@mosaicstack/db'; // ← Depends on Drizzle/PG
|
||||
import type { Db } from '@mosaic/db'; // ← Depends on Drizzle/PG
|
||||
|
||||
export function createMemory(db: Db): Memory {
|
||||
return {
|
||||
@@ -97,7 +97,7 @@ export function createMemory(db: Db): Memory {
|
||||
}
|
||||
```
|
||||
|
||||
**Problem:** Memory package is tightly coupled to `@mosaicstack/db` (which is Postgres-only). No alternative storage backends.
|
||||
**Problem:** Memory package is tightly coupled to `@mosaic/db` (which is Postgres-only). No alternative storage backends.
|
||||
|
||||
## The Target Interfaces
|
||||
|
||||
@@ -361,7 +361,7 @@ Automated equivalent to Claude Code's "Dream: Memory Consolidation" cycle
|
||||
**Implementation:**
|
||||
|
||||
```typescript
|
||||
// In @mosaicstack/dream (new package)
|
||||
// In @mosaic/dream (new package)
|
||||
export async function runDreamCycle(config: DreamConfig): Promise<DreamResult> {
|
||||
const memory = await loadMemoryAdapter(config.storage);
|
||||
|
||||
@@ -420,7 +420,7 @@ export async function runDreamCycle(config: DreamConfig): Promise<DreamResult> {
|
||||
2. Move Drizzle logic to `packages/storage/src/adapters/postgres.ts`
|
||||
3. Create SQLite adapter in `packages/storage/src/adapters/sqlite.ts`
|
||||
4. Update gateway to use storage factory
|
||||
5. Deprecate direct `@mosaicstack/db` imports
|
||||
5. Deprecate direct `@mosaic/db` imports
|
||||
|
||||
#### 2.3 Memory Refactor
|
||||
|
||||
@@ -488,7 +488,7 @@ packages/
|
||||
│ │ ├── types.ts # StorageAdapter interface
|
||||
│ │ ├── index.ts # Factory function
|
||||
│ │ └── adapters/
|
||||
│ │ ├── postgres.ts # MOVED from @mosaicstack/db
|
||||
│ │ ├── postgres.ts # MOVED from @mosaic/db
|
||||
│ │ ├── sqlite.ts # NEW: SQLite adapter
|
||||
│ │ └── files.ts # NEW: JSON/MD adapter
|
||||
│ └── package.json
|
||||
@@ -530,9 +530,9 @@ packages/
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
1. **`@mosaicstack/db`** → **`@mosaicstack/storage`** (with migration guide)
|
||||
2. Direct `ioredis` imports → Use `@mosaicstack/queue` factory
|
||||
3. Direct `pgvector` queries → Use `@mosaicstack/memory` factory
|
||||
1. **`@mosaic/db`** → **`@mosaic/storage`** (with migration guide)
|
||||
2. Direct `ioredis` imports → Use `@mosaic/queue` factory
|
||||
3. Direct `pgvector` queries → Use `@mosaic/memory` factory
|
||||
4. Gateway startup now requires storage config (defaults to local)
|
||||
|
||||
## Non-Breaking Migration Path
|
||||
|
||||
@@ -1,368 +0,0 @@
|
||||
# Mosaic Stack — Federation Implementation Milestones
|
||||
|
||||
**Companion to:** `PRD.md`
|
||||
**Approach:** Each milestone is a verifiable slice. A milestone is "done" only when its acceptance tests pass in CI against a real (not mocked) dependency stack.
|
||||
|
||||
---
|
||||
|
||||
## Milestone Dependency Graph
|
||||
|
||||
```
|
||||
M1 (federated tier infra)
|
||||
└── M2 (Step-CA + grant schema + CLI)
|
||||
└── M3 (mTLS handshake + list/get + scope enforcement)
|
||||
├── M4 (search + audit + rate limit)
|
||||
│ └── M5 (cache + offline degradation + OTEL)
|
||||
├── M6 (revocation + auto-renewal) ◄── can start after M3
|
||||
└── M7 (multi-user hardening + e2e suite) ◄── depends on M4+M5+M6
|
||||
```
|
||||
|
||||
M5 and M6 can run in parallel once M4 is merged.
|
||||
|
||||
---
|
||||
|
||||
## Test Strategy (applies to all milestones)
|
||||
|
||||
Three layers, all required before a milestone ships:
|
||||
|
||||
| Layer | Scope | Runtime |
|
||||
| ------------------ | --------------------------------------------- | ------------------------------------------------------------------------ |
|
||||
| **Unit** | Per-module logic, pure functions, adapters | Vitest, no I/O |
|
||||
| **Integration** | Single gateway against real PG/Valkey/Step-CA | Vitest + Docker Compose test profile |
|
||||
| **Federation E2E** | Two gateways on a Docker network, real mTLS | Playwright/custom harness (`tools/federation-harness/`) introduced in M3 |
|
||||
|
||||
Every milestone adds tests to these layers. A milestone cannot be claimed complete if the federation E2E harness fails (applies from M3 onward).
|
||||
|
||||
**Quality gates per milestone** (same as stack-wide):
|
||||
|
||||
- `pnpm typecheck` green
|
||||
- `pnpm lint` green
|
||||
- `pnpm test` green (unit + integration)
|
||||
- `pnpm test:federation` green (M3+)
|
||||
- Independent code review passed
|
||||
- Docs updated (`docs/federation/`)
|
||||
- Merged PR on `main`, CI terminal green, linked issue closed
|
||||
|
||||
---
|
||||
|
||||
## M1 — Federated Tier Infrastructure
|
||||
|
||||
**Goal:** A gateway can run in `federated` tier with containerized Postgres + Valkey + pgvector, with no federation logic active yet.
|
||||
|
||||
**Scope:**
|
||||
|
||||
- Add `"tier": "federated"` to `mosaic.config.json` schema and validators
|
||||
- Docker Compose `federated` profile (`docker-compose.federated.yml`) adds: Postgres+pgvector (5433), Valkey (6380), dedicated volumes
|
||||
- Tier detector in gateway bootstrap: reads config, asserts required services reachable, refuses to start otherwise
|
||||
- `pgvector` extension installed + verified on startup
|
||||
- Migration logic: safe upgrade path from `local`/`standalone` → `federated` (data export/import script, one-way)
|
||||
- `mosaic doctor` reports tier + service health
|
||||
- Gateway continues to serve as a normal standalone instance (no federation yet)
|
||||
|
||||
**Deliverables:**
|
||||
|
||||
- `mosaic.config.json` schema v2 (tier enum includes `federated`)
|
||||
- `apps/gateway/src/bootstrap/tier-detector.ts`
|
||||
- `docker-compose.federated.yml`
|
||||
- `scripts/migrate-to-federated.ts`
|
||||
- Updated `mosaic doctor` output
|
||||
- Updated `packages/storage/src/adapters/postgres.ts` with pgvector support
|
||||
|
||||
**Acceptance tests:**
|
||||
| # | Test | Layer |
|
||||
| - | ---------------------------------------------------------------------------------------- | ----------- |
|
||||
| 1 | Gateway boots in `federated` tier with all services present | Integration |
|
||||
| 2 | Gateway refuses to boot in `federated` tier when Postgres unreachable (fail-fast, clear) | Integration |
|
||||
| 3 | `pgvector` extension available in target DB (`SELECT * FROM pg_extension WHERE extname='vector'`) | Integration |
|
||||
| 4 | Migration script moves a populated `local` (PGlite) instance to `federated` (Postgres) with no data loss | Integration |
|
||||
| 5 | `mosaic doctor` reports correct tier and all services green | Unit |
|
||||
| 6 | Existing standalone behavior regression: agent session works end-to-end, no federation references | E2E (single-gateway) |
|
||||
|
||||
**Estimated budget:** ~20K tokens (infra + config + migration script)
|
||||
**Risk notes:** Pgvector install on existing PG installs is occasionally finicky; test the migration path on a realistic DB snapshot.
|
||||
|
||||
---
|
||||
|
||||
## M2 — Step-CA + Grant Schema + Admin CLI
|
||||
|
||||
**Goal:** An admin can create a federation grant and its counterparty can enroll. No runtime traffic flows yet.
|
||||
|
||||
**Scope:**
|
||||
|
||||
- Embed Step-CA as a Docker Compose sidecar with a persistent CA volume
|
||||
- Gateway exposes a short-lived enrollment endpoint (single-use token from the grant)
|
||||
- DB schema: `federation_grants`, `federation_peers`, `federation_audit_log` (table only, not yet written to)
|
||||
- Sealed storage for `client_key_pem` using the existing credential sealing key
|
||||
- Admin CLI:
|
||||
- `mosaic federation grant create --user <id> --peer <host> --scope <file>`
|
||||
- `mosaic federation grant list`
|
||||
- `mosaic federation grant show <id>`
|
||||
- `mosaic federation peer add <enrollment-url>`
|
||||
- `mosaic federation peer list`
|
||||
- Step-CA signs the cert with SAN OIDs for `grantId` + `subjectUserId`
|
||||
- Grant status transitions: `pending` → `active` on successful enrollment
|
||||
|
||||
**Deliverables:**
|
||||
|
||||
- `packages/db` migration: three federation tables + enum types
|
||||
- `apps/gateway/src/federation/ca.service.ts` (Step-CA client)
|
||||
- `apps/gateway/src/federation/grants.service.ts`
|
||||
- `apps/gateway/src/federation/enrollment.controller.ts`
|
||||
- `packages/mosaic/src/commands/federation/` (grant + peer subcommands)
|
||||
- `docker-compose.federated.yml` adds Step-CA service
|
||||
- Scope JSON schema + validator
|
||||
|
||||
**Acceptance tests:**
|
||||
| # | Test | Layer |
|
||||
| - | ---------------------------------------------------------------------------------------- | ----------- |
|
||||
| 1 | `grant create` writes a `pending` row with a scoped bundle | Integration |
|
||||
| 2 | Enrollment endpoint signs a CSR and returns a cert with expected SAN OIDs | Integration |
|
||||
| 3 | Enrollment token is single-use; second attempt returns 410 | Integration |
|
||||
| 4 | Cert `subjectUserId` OID matches the grant's `subject_user_id` | Unit |
|
||||
| 5 | `client_key_pem` is at-rest encrypted; raw DB read shows ciphertext, not PEM | Integration |
|
||||
| 6 | `peer add <url>` on Server A yields an `active` peer record with a valid cert + key | E2E (two gateways, no traffic) |
|
||||
| 7 | Scope JSON with unknown resource type rejected at `grant create` | Unit |
|
||||
| 8 | `grant list` and `peer list` render active / pending / revoked accurately | Unit |
|
||||
|
||||
**Estimated budget:** ~30K tokens (schema + CA integration + CLI + sealing)
|
||||
**Risk notes:** Step-CA's API surface is well-documented but the sealing integration with existing provider-credential encryption is a cross-module concern — walk that seam deliberately.
|
||||
|
||||
---
|
||||
|
||||
## M3 — mTLS Handshake + `list` + `get` with Scope Enforcement
|
||||
|
||||
**Goal:** Two federated gateways exchange real data over mTLS with scope intersecting native RBAC.
|
||||
|
||||
**Scope:**
|
||||
|
||||
- `FederationClient` (outbound): picks cert from `federation_peers`, does mTLS call
|
||||
- `FederationServer` (inbound): NestJS guard validates client cert, extracts `grantId` + `subjectUserId`, loads grant
|
||||
- Scope enforcement pipeline:
|
||||
1. Resource allowlist / excluded-list check
|
||||
2. Native RBAC evaluation as the `subjectUserId`
|
||||
3. Scope filter intersection (`include_teams`, `include_personal`)
|
||||
4. `max_rows_per_query` cap
|
||||
- Verbs: `list`, `get`, `capabilities`
|
||||
- Gateway query layer accepts `source: "local" | "federated:<host>" | "all"`; fan-out for `"all"`
|
||||
- **Federation E2E harness** (`tools/federation-harness/`): docker-compose.two-gateways.yml, seed script, assertion helpers — this is its own deliverable
|
||||
|
||||
**Deliverables:**
|
||||
|
||||
- `apps/gateway/src/federation/client/federation-client.service.ts`
|
||||
- `apps/gateway/src/federation/server/federation-auth.guard.ts`
|
||||
- `apps/gateway/src/federation/server/scope.service.ts`
|
||||
- `apps/gateway/src/federation/server/verbs/{list,get,capabilities}.controller.ts`
|
||||
- `apps/gateway/src/federation/client/query-source.service.ts` (fan-out/merge)
|
||||
- `tools/federation-harness/` (compose + seed + test helpers)
|
||||
- `packages/types` — federation request/response DTOs in `federation.dto.ts`
|
||||
|
||||
**Acceptance tests:**
|
||||
| # | Test | Layer |
|
||||
| -- | -------------------------------------------------------------------------------------------------------- | ----- |
|
||||
| 1 | A→B `list tasks` returns subjectUser's tasks intersected with scope | E2E |
|
||||
| 2 | A→B `list tasks` with `include_teams: [T1]` excludes T2 tasks the user owns | E2E |
|
||||
| 3 | A→B `get credential <id>` returns 403 when `credentials` is in `excluded_resources` | E2E |
|
||||
| 4 | Client presenting cert for grant X cannot query subjectUser of grant Y (cross-user isolation) | E2E |
|
||||
| 5 | Cert signed by untrusted CA rejected at TLS layer (no NestJS handler reached) | E2E |
|
||||
| 6 | Malformed SAN OIDs → 401; cert valid but grant revoked in DB → 403 | Integration |
|
||||
| 7 | `max_rows_per_query` caps response; request for more paginated | Integration |
|
||||
| 8 | `source: "all"` fan-out merges local + federated results, each tagged with `_source` | Integration |
|
||||
| 9 | Federation responses never persist: verify DB row count unchanged after `list` round-trip | E2E |
|
||||
| 10 | Scope cannot grant more than native RBAC: user without access to team T still gets [] even if scope allows T | E2E |
|
||||
|
||||
**Estimated budget:** ~40K tokens (largest milestone — core federation logic + harness)
|
||||
**Risk notes:** This is the critical trust boundary. Code review should focus on scope enforcement bypass and cert-SAN-spoofing paths. Every 403/401 path needs a test.
|
||||
|
||||
---
|
||||
|
||||
## M4 — `search` Verb + Audit Log + Rate Limit
|
||||
|
||||
**Goal:** Keyword search over allowed resources with full audit and per-grant rate limiting.
|
||||
|
||||
**Scope:**
|
||||
|
||||
- `search` verb across `resources` allowlist (intersection of scope + native RBAC)
|
||||
- Keyword search (reuse existing `packages/memory/src/adapters/keyword.ts`); pgvector search stays out of v1 search verb
|
||||
- Every federated request (all verbs) writes to `federation_audit_log`: `grant_id`, `verb`, `resource`, `query_hash`, `outcome`, `bytes_out`, `latency_ms`
|
||||
- No request body captured; `query_hash` is SHA-256 of normalized query params
|
||||
- Token-bucket rate limit per grant (default 60/min, override per grant)
|
||||
- 429 response with `Retry-After` header and structured body
|
||||
- 90-day hot retention for audit log; cold-tier rollover deferred to M7
|
||||
|
||||
**Deliverables:**
|
||||
|
||||
- `apps/gateway/src/federation/server/verbs/search.controller.ts`
|
||||
- `apps/gateway/src/federation/server/audit.service.ts` (async write, no blocking)
|
||||
- `apps/gateway/src/federation/server/rate-limit.guard.ts`
|
||||
- Tests in harness
|
||||
|
||||
**Acceptance tests:**
|
||||
| # | Test | Layer |
|
||||
| - | ------------------------------------------------------------------------------------------------- | ----------- |
|
||||
| 1 | `search` returns ranked hits only from allowed resources | E2E |
|
||||
| 2 | `search` excluding `credentials` does not return a match even when keyword matches a credential name | E2E |
|
||||
| 3 | Every successful request appears in `federation_audit_log` within 1s | Integration |
|
||||
| 4 | Denied request (403) is also audited with `outcome='denied'` | Integration |
|
||||
| 5 | Audit row stores query hash but NOT query body | Unit |
|
||||
| 6 | 61st request in 60s window returns 429 with `Retry-After` | E2E |
|
||||
| 7 | Per-grant override (e.g., 600/min) takes effect without restart | Integration |
|
||||
| 8 | Audit writes are async: request latency unchanged when audit write slow (simulated) | Integration |
|
||||
|
||||
**Estimated budget:** ~20K tokens
|
||||
**Risk notes:** Ensure audit writes can't block or error-out the request path; use a bounded queue and drop-with-counter pattern rather than in-line writes.
|
||||
|
||||
---
|
||||
|
||||
## M5 — Cache + Offline Degradation + Observability
|
||||
|
||||
**Goal:** Sessions feel fast and stay useful when the peer is slow or down.
|
||||
|
||||
**Scope:**
|
||||
|
||||
- In-memory response cache keyed by `(grant_id, verb, resource, query_hash)`, TTL 30s default
|
||||
- Cache NOT used for `search`; only `list` and `get`
|
||||
- Cache flushed on cert rotation and grant revocation
|
||||
- Circuit breaker per peer: after N failures, fast-fail for cooldown window
|
||||
- `_source` tagging extended with `_cached: true` when served from cache
|
||||
- Agent-visible "federation offline for `<peer>`" signal emitted once per session per peer
|
||||
- OTEL spans: `federation.request` with attrs `grant_id`, `peer`, `verb`, `resource`, `outcome`, `latency_ms`, `cached`
|
||||
- W3C `traceparent` propagated across the mTLS boundary (both directions)
|
||||
- `mosaic federation status` CLI subcommand
|
||||
|
||||
**Deliverables:**
|
||||
|
||||
- `apps/gateway/src/federation/client/response-cache.service.ts`
|
||||
- `apps/gateway/src/federation/client/circuit-breaker.service.ts`
|
||||
- `apps/gateway/src/federation/observability/` (span helpers)
|
||||
- `packages/mosaic/src/commands/federation/status.ts`
|
||||
|
||||
**Acceptance tests:**
|
||||
| # | Test | Layer |
|
||||
| - | --------------------------------------------------------------------------------------------- | ----- |
|
||||
| 1 | Two identical `list` calls within 30s: second served from cache, flagged `_cached` | Integration |
|
||||
| 2 | `search` is never cached: two identical searches both hit the peer | Integration |
|
||||
| 3 | After grant revocation, peer's cache is flushed immediately | Integration |
|
||||
| 4 | After N consecutive failures, circuit opens; subsequent requests fail-fast without network call | E2E |
|
||||
| 5 | Circuit closes after cooldown and next success | E2E |
|
||||
| 6 | With peer offline, session completes using local data, one "federation offline" signal surfaced | E2E |
|
||||
| 7 | OTEL traces show spans on both gateways correlated by `traceparent` | E2E |
|
||||
| 8 | `mosaic federation status` prints peer state, cert expiry, last success/failure, circuit state | Unit |
|
||||
|
||||
**Estimated budget:** ~20K tokens
|
||||
**Risk notes:** Caching correctness under revocation must be provable — write tests that intentionally race revocation against cached hits.
|
||||
|
||||
---
|
||||
|
||||
## M6 — Revocation, Auto-Renewal, CRL
|
||||
|
||||
**Goal:** Grant lifecycle works end-to-end: admin revoke, revoke-on-delete, automatic cert renewal, CRL distribution.
|
||||
|
||||
**Scope:**
|
||||
|
||||
- `mosaic federation grant revoke <id>` → status `revoked`, CRL updated, audit entry
|
||||
- DB hook: deleting a user cascades `revoke-on-delete` on all grants where that user is subject
|
||||
- Step-CA CRL endpoint exposed; serving gateway enforces CRL check on every handshake (cached CRL, refresh interval 60s)
|
||||
- Client-side cert renewal job: at T-7 days, submit renewal CSR; rotate cert atomically; flush cache
|
||||
- On renewal failure, peer marked `degraded` and admin-visible alert emitted
|
||||
- Server A detects revocation on next request (TLS handshake fails with specific error) → peer marked `revoked`, user notified
|
||||
|
||||
**Deliverables:**
|
||||
|
||||
- `apps/gateway/src/federation/server/crl.service.ts` + endpoint
|
||||
- `apps/gateway/src/federation/server/revocation.service.ts`
|
||||
- DB cascade trigger or ORM hook for user deletion → grant revocation
|
||||
- `apps/gateway/src/federation/client/renewal.job.ts` (scheduled)
|
||||
- `packages/mosaic/src/commands/federation/grant.ts` gains `revoke` subcommand
|
||||
|
||||
**Acceptance tests:**
|
||||
| # | Test | Layer |
|
||||
| - | ----------------------------------------------------------------------------------------- | ----- |
|
||||
| 1 | Admin `grant revoke` → A's next request fails with TLS-level error | E2E |
|
||||
| 2 | Deleting subject user on B auto-revokes all grants where that user was the subject | Integration |
|
||||
| 3 | CRL endpoint serves correct list; revoked cert present | Integration |
|
||||
| 4 | Server rejects cert listed in CRL even if cert itself is still time-valid | E2E |
|
||||
| 5 | Cert at T-7 days triggers renewal job; new cert issued and installed without dropped requests | E2E |
|
||||
| 6 | Renewal failure marks peer `degraded` and surfaces alert | Integration |
|
||||
| 7 | A marks peer `revoked` after a revocation-caused handshake failure (not on transient network errors) | E2E |
|
||||
|
||||
**Estimated budget:** ~20K tokens
|
||||
**Risk notes:** The atomic cert swap during renewal is the sharpest edge here — any in-flight request mid-swap must either complete on old or retry on new, never fail mid-call.
|
||||
|
||||
---
|
||||
|
||||
## M7 — Multi-User RBAC Hardening + Team-Scoped Grants + Acceptance Suite
|
||||
|
||||
**Goal:** The full multi-tenant scenario from §4 user stories works end-to-end, with no cross-user leakage under any circumstance.
|
||||
|
||||
**Scope:**
|
||||
|
||||
- Three-user scenario on Server B (E1, E2, E3) each with their own Server A
|
||||
- Team-scoped grants exercised: each employee's team-data visible on their own A, but E1's personal data never visible on E2's A
|
||||
- User-facing UI surfaces on both gateways for: peer list, grant list, audit log viewer, scope editor
|
||||
- Negative-path test matrix (every denial path from PRD §8)
|
||||
- All PRD §15 acceptance criteria mapped to automated tests in the harness
|
||||
- Security review: cert-spoofing, scope-bypass, audit-bypass paths explicitly tested
|
||||
- Cold-storage rollover for audit log >90 days
|
||||
- Docs: operator runbook, onboarding guide, troubleshooting guide
|
||||
|
||||
**Deliverables:**
|
||||
|
||||
- Full federation acceptance suite in `tools/federation-harness/acceptance/`
|
||||
- `apps/web` surfaces for peer/grant/audit management
|
||||
- `docs/federation/RUNBOOK.md`, `docs/federation/ONBOARDING.md`, `docs/federation/TROUBLESHOOTING.md`
|
||||
- Audit cold-tier job (daily cron, moves rows >90d to separate table or object storage)
|
||||
|
||||
**Acceptance tests:**
|
||||
Every PRD §15 criterion must be automated and green. Additionally:
|
||||
|
||||
| # | Test | Layer |
|
||||
| --- | ----------------------------------------------------------------------------------------------------- | ---------------- |
|
||||
| 1 | 3-employee scenario: each A sees only its user's data from B | E2E |
|
||||
| 2 | Grant with team scope returns team data; same grant denied access to another employee's personal data | E2E |
|
||||
| 3 | Concurrent sessions from E1's and E2's Server A to B interleave without any leakage | E2E |
|
||||
| 4 | Audit log across 3-user test shows per-grant trails with no mis-attributed rows | E2E |
|
||||
| 5 | Scope editor UI round-trip: edit → save → next request uses new scope | E2E |
|
||||
| 6 | Attempt to use a revoked grant's cert against a different grant's endpoint: rejected | E2E |
|
||||
| 7 | 90-day-old audit rows moved to cold tier; queryable via explicit historical query | Integration |
|
||||
| 8 | Runbook steps validated: an operator following the runbook can onboard, rotate, and revoke | Manual checklist |
|
||||
|
||||
**Estimated budget:** ~25K tokens
|
||||
**Risk notes:** This is the security-critical milestone. Budget review time here is non-negotiable — plan for two independent code reviews (internal + security-focused) before merge.
|
||||
|
||||
---
|
||||
|
||||
## Total Budget & Timeline Sketch
|
||||
|
||||
| Milestone | Tokens (est.) | Can parallelize? |
|
||||
| --------- | ------------- | ---------------------- |
|
||||
| M1 | 20K | No (foundation) |
|
||||
| M2 | 30K | No (needs M1) |
|
||||
| M3 | 40K | No (needs M2) |
|
||||
| M4 | 20K | No (needs M3) |
|
||||
| M5 | 20K | Yes (with M6 after M4) |
|
||||
| M6 | 20K | Yes (with M5 after M3) |
|
||||
| M7 | 25K | No (needs all) |
|
||||
| **Total** | **~175K** | |
|
||||
|
||||
Parallelization of M5 and M6 after M4 saves one milestone's worth of serial time.
|
||||
|
||||
---
|
||||
|
||||
## Exit Criteria (federation feature complete)
|
||||
|
||||
All of the following must be green on `main`:
|
||||
|
||||
- Every PRD §15 acceptance criterion automated and passing
|
||||
- Every milestone's acceptance table green
|
||||
- Security review sign-off on M7
|
||||
- Runbook walk-through completed by operator (not author)
|
||||
- `mosaic doctor` recognizes federated tier and reports peer health accurately
|
||||
- Two-gateway production deployment (woltje.com ↔ uscllc.com) operational for ≥7 days without incident
|
||||
|
||||
---
|
||||
|
||||
## Next Step After This Doc Is Approved
|
||||
|
||||
1. File tracking issues on `git.mosaicstack.dev/mosaicstack/stack` — one per milestone, labeled `epic:federation`
|
||||
2. Populate `docs/TASKS.md` with M1's task breakdown (per-task agent assignment, budget, dependencies)
|
||||
3. Begin M1 implementation
|
||||
@@ -1,85 +0,0 @@
|
||||
# Mission Manifest — Federation v1
|
||||
|
||||
> Persistent document tracking full mission scope, status, and session history.
|
||||
> Updated by the orchestrator at each phase transition and milestone completion.
|
||||
|
||||
## Mission
|
||||
|
||||
**ID:** federation-v1-20260419
|
||||
**Statement:** Jarvis operates across 3–4 workstations in two physical locations (home, USC). The user currently reaches back to a single jarvis-brain checkout from every session; a prior OpenBrain attempt caused cache, latency, and opacity pain. This mission builds asymmetric federation between Mosaic Stack gateways so that a session on a user's home gateway can query their work gateway in real time without data ever persisting across the boundary, with full multi-tenant isolation and standard-PKI (X.509 / Step-CA) trust management.
|
||||
**Phase:** Planning complete — M1 implementation not started
|
||||
**Current Milestone:** FED-M1
|
||||
**Progress:** 0 / 7 milestones
|
||||
**Status:** active
|
||||
**Last Updated:** 2026-04-19 (PRD + MILESTONES + tracking issues filed)
|
||||
**Parent Mission:** None — new mission
|
||||
|
||||
## Context
|
||||
|
||||
Federation is the solution to what originally drove OpenBrain. The prior attempt coupled every agent session to a remote service, introduced cache/latency/opacity pain, and created a hard dependency that punished offline use. This redesign:
|
||||
|
||||
1. Makes federation **gateway-to-gateway**, not agent-to-service
|
||||
2. Keeps each user's home instance as source of truth for their data
|
||||
3. Exposes scoped, read-only data on demand without persisting across the boundary
|
||||
4. Uses X.509 mTLS via Step-CA so rotation/revocation/CRL/OCSP are standard
|
||||
5. Supports multi-tenant serving sides (employees on uscllc.com each federating back to their own home gateway) with no cross-user leakage
|
||||
6. Requires federation-tier instances on both sides (PG + pgvector + Valkey) — local/standalone tiers cannot federate
|
||||
7. Works over public HTTPS (no VPN required); Tailscale is an optional overlay
|
||||
|
||||
Key design references:
|
||||
|
||||
- `docs/federation/PRD.md` — 16-section product requirements
|
||||
- `docs/federation/MILESTONES.md` — 7-milestone decomposition with per-milestone acceptance tests
|
||||
- `docs/federation/TASKS.md` — per-task breakdown (M1 populated; M2-M7 deferred to mission planning)
|
||||
- `docs/research/mempalace-evaluation/` (in jarvis-brain) — why we didn't adopt MemPalace
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [ ] AC-1: Two Mosaic Stack gateways on different hosts can establish a federation grant via CLI-driven onboarding
|
||||
- [ ] AC-2: Server A can query Server B for `tasks`, `notes`, `memory` respecting scope filters
|
||||
- [ ] AC-3: User on B with no grant cannot be queried by A, even if A has a valid grant for another user (cross-user isolation)
|
||||
- [ ] AC-4: Revoking a grant on B causes A's next request to fail with a clear error within one request cycle
|
||||
- [ ] AC-5: Cert rotation happens automatically at T-7 days; in-progress session survives rotation without user action
|
||||
- [ ] AC-6: Rate-limit enforcement returns 429 with `Retry-After`; client backs off
|
||||
- [ ] AC-7: With B unreachable, a session on A completes using local data and surfaces "federation offline for `<peer>`" once per session
|
||||
- [ ] AC-8: Every federated request appears in B's `federation_audit_log` within 1 second
|
||||
- [ ] AC-9: Scope excluding `credentials` means credentials are never returned — even via `search` with matching keywords
|
||||
- [ ] AC-10: `mosaic federation status` shows cert expiry, grant status, last success/failure per peer
|
||||
- [ ] AC-11: Full 3-employee multi-tenant scenario passes with no cross-user leakage
|
||||
- [ ] AC-12: Two-gateway production deployment (woltje.com ↔ uscllc.com) operational ≥7 days without incident
|
||||
- [ ] AC-13: All 7 milestones ship as merged PRs with green CI and closed issues
|
||||
|
||||
## Milestones
|
||||
|
||||
| # | ID | Name | Status | Branch | Issue | Started | Completed |
|
||||
| --- | ------ | --------------------------------------------- | ----------- | ------ | ----- | ------- | --------- |
|
||||
| 1 | FED-M1 | Federated tier infrastructure | not-started | — | #460 | — | — |
|
||||
| 2 | FED-M2 | Step-CA + grant schema + admin CLI | not-started | — | #461 | — | — |
|
||||
| 3 | FED-M3 | mTLS handshake + list/get + scope enforcement | not-started | — | #462 | — | — |
|
||||
| 4 | FED-M4 | search verb + audit log + rate limit | not-started | — | #463 | — | — |
|
||||
| 5 | FED-M5 | Cache + offline degradation + OTEL | not-started | — | #464 | — | — |
|
||||
| 6 | FED-M6 | Revocation + auto-renewal + CRL | not-started | — | #465 | — | — |
|
||||
| 7 | FED-M7 | Multi-user RBAC hardening + acceptance suite | not-started | — | #466 | — | — |
|
||||
|
||||
## Budget
|
||||
|
||||
| Milestone | Est. tokens | Parallelizable? |
|
||||
| --------- | ----------- | ---------------------- |
|
||||
| FED-M1 | 20K | No (foundation) |
|
||||
| FED-M2 | 30K | No (needs M1) |
|
||||
| FED-M3 | 40K | No (needs M2) |
|
||||
| FED-M4 | 20K | No (needs M3) |
|
||||
| FED-M5 | 20K | Yes (with M6 after M4) |
|
||||
| FED-M6 | 20K | Yes (with M5 after M3) |
|
||||
| FED-M7 | 25K | No (needs all) |
|
||||
| **Total** | **~175K** | |
|
||||
|
||||
## Session History
|
||||
|
||||
| Session | Date | Runtime | Outcome |
|
||||
| ------- | ---------- | ------- | --------------------------------------------------- |
|
||||
| S1 | 2026-04-19 | claude | PRD authored, MILESTONES decomposed, 7 issues filed |
|
||||
|
||||
## Next Step
|
||||
|
||||
Begin FED-M1 implementation: federated tier infrastructure. Breakdown in `docs/federation/TASKS.md`.
|
||||
@@ -1,330 +0,0 @@
|
||||
# Mosaic Stack — Federation PRD
|
||||
|
||||
**Status:** Draft v1 (locked for implementation)
|
||||
**Owner:** Jason
|
||||
**Date:** 2026-04-19
|
||||
**Scope:** Enables cross-instance data federation between Mosaic Stack gateways with asymmetric trust, multi-tenant scoping, and no cross-boundary data persistence.
|
||||
|
||||
---
|
||||
|
||||
## 1. Problem Statement
|
||||
|
||||
Jarvis operates across 3–4 workstations in two physical locations (home, USC). The user currently reaches back to a single jarvis-brain checkout from every session, and has tried OpenBrain to solve cross-session state — with poor results (cache invalidation, latency, opacity, hard dependency on a remote service).
|
||||
|
||||
The goal is a federation model where each user's **home instance** remains the source of truth for their personal data, and **work/shared instances** expose scoped data to that user's home instance on demand — without persisting anything across the boundary.
|
||||
|
||||
## 2. Goals
|
||||
|
||||
1. A user logged into their **home gateway** (Server A) can query their **work gateway** (Server B) in real time during a session.
|
||||
2. Data returned from Server B is used in-session only; never written to Server A storage.
|
||||
3. Server B has multiple users, each with their own Server A. No user's data leaks to another user.
|
||||
4. Federation works over public HTTPS (no VPN required). Tailscale is a supported optional overlay.
|
||||
5. Sync latency target: seconds, or at the next data need of the agent.
|
||||
6. Graceful degradation: if the remote instance is unreachable, the local session continues with local data and a clear "federation offline" signal.
|
||||
7. Teams exist on both sides. A federation grant can share **team-owned** data without exposing other team members' personal data.
|
||||
8. Auth and revocation use standard PKI (X.509) so that certificate tooling (Step-CA, rotation, OCSP, CRL) is available out of the box.
|
||||
|
||||
## 3. Non-Goals (v1)
|
||||
|
||||
- Mesh federation (N-to-N). v1 is strictly A↔B pairs.
|
||||
- Cross-instance writes. All federation is **read-only** on the remote side.
|
||||
- Shared agent sessions across instances. Sessions live on one instance; federation is data-plane only.
|
||||
- Cross-instance SSO. Each instance owns its own BetterAuth identity store; federation is service-to-service, not user-to-user.
|
||||
- Realtime push from B→A. v1 is pull-only (A pulls from B during a session).
|
||||
- Global search index. Federation is query-by-query, not index replication.
|
||||
|
||||
## 4. User Stories
|
||||
|
||||
- **US-1 (Solo user at home):** As the sole user on Server A, I want my agent session on workstation-1 to see the same data it saw on workstation-2, without running OpenBrain.
|
||||
- **US-2 (Cross-location):** As a user with a home server and a work server, I want a session on my home laptop to transparently pull my USC-owned tasks/notes when I ask for them.
|
||||
- **US-3 (Work admin):** As the admin of mosaic.uscllc.com, I want to grant each employee's home gateway scoped read access to only their own data plus explicitly-shared team data.
|
||||
- **US-4 (Privacy boundary):** As employee A on mosaic.uscllc.com, my data must never appear in a session on employee B's home gateway — even if both are federated with uscllc.com.
|
||||
- **US-5 (Revocation):** As a work admin, when I delete an employee, their home gateway loses access within one request cycle.
|
||||
- **US-6 (Offline):** As a user in a hotel with flaky wifi, my local session keeps working; federation calls fail fast and are reported as "offline," not hung.
|
||||
|
||||
## 5. Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐ mTLS / X.509 ┌─────────────────────────────────────┐
|
||||
│ Server A — mosaic.woltje.com │ ───────────────────────► │ Server B — mosaic.uscllc.com │
|
||||
│ (home, master for Jason) │ ◄── JSON over HTTPS │ (work, multi-tenant) │
|
||||
│ │ │ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ │ │ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Gateway │ │ Postgres │ │ │ │ Gateway │ │ Postgres │ │
|
||||
│ │ (NestJS) │──│ (local SSOT)│ │ │ │ (NestJS) │──│ (tenant SSOT)│ │
|
||||
│ └──────┬───────┘ └──────────────┘ │ │ └──────┬───────┘ └──────────────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ │ FederationClient │ │ │ FederationServer │
|
||||
│ │ (outbound, scoped query) │ │ │ (inbound, RBAC-gated) │
|
||||
│ └───────────────────────────┼──────────────────────────┼────────┘ │
|
||||
│ │ │ │
|
||||
│ Step-CA (issues A's client cert) │ │ Step-CA (issues B's server cert, │
|
||||
│ │ │ trusts A's CA root on grant)│
|
||||
└─────────────────────────────────────┘ └──────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- Federation is a **transport-layer** concern between two gateways, implemented as a new internal module on each gateway.
|
||||
- Both sides run the same code. Direction (client vs. server role) is per-request.
|
||||
- Nothing in the agent runtime changes — agents query the gateway; the gateway decides local vs. remote.
|
||||
|
||||
## 6. Transport & Authentication
|
||||
|
||||
**Transport:** HTTPS with mutual TLS (mTLS).
|
||||
|
||||
**Identity:** X.509 client certificates issued by Step-CA. Each federation grant materializes as a client cert on the requesting side and a trust-anchor entry (CA root or explicit cert) on the serving side.
|
||||
|
||||
**Why mTLS over HMAC bearer tokens:**
|
||||
|
||||
- Standard rotation/revocation semantics (renew, CRL, OCSP).
|
||||
- The cert subject carries identity claims (user, grant_id) that don't need a separate DB lookup to verify authenticity.
|
||||
- Client certs never transit request bodies, so they can't be logged by accident.
|
||||
- Transport is pinned at the TLS layer, not re-validated per-handler.
|
||||
|
||||
**Cert contents (SAN + subject):**
|
||||
|
||||
- `CN=grant-<uuid>`
|
||||
- `O=<requesting-server-hostname>` (e.g., `mosaic.woltje.com`)
|
||||
- Custom OIDs embedded in SAN otherName:
|
||||
- `mosaic.federation.grantId` (UUID)
|
||||
- `mosaic.federation.subjectUserId` (user on the **serving** side that this grant acts-as)
|
||||
- Default lifetime: **30 days**, with auto-renewal at T-7 days if the grant is still active.
|
||||
|
||||
**Step-CA topology (v1):** Each server runs its own Step-CA instance. During onboarding, the serving side imports the requesting side's CA root. A central/shared Step-CA is out of scope for v1.
|
||||
|
||||
**Handshake:**
|
||||
|
||||
1. Client (A) opens HTTPS to B with its grant cert.
|
||||
2. B validates cert chain against trusted CA roots for that grant.
|
||||
3. B extracts `grantId` and `subjectUserId` from the cert.
|
||||
4. B loads the grant record, checks it is `active`, not revoked, and not expired.
|
||||
5. B enforces scope and rate-limit for this grant.
|
||||
6. Request proceeds; response returned.
|
||||
|
||||
## 7. Data Model
|
||||
|
||||
All tables live on **each instance's own Postgres**. Federation grants are bilateral — each side has a record of the grant.
|
||||
|
||||
### 7.1 `federation_grants` (on serving side, Server B)
|
||||
|
||||
| Field | Type | Notes |
|
||||
| --------------------------- | ----------- | ------------------------------------------------- |
|
||||
| `id` | uuid PK | |
|
||||
| `subject_user_id` | uuid FK | Which local user this grant acts-as |
|
||||
| `requesting_server` | text | Hostname of requesting gateway (e.g., woltje.com) |
|
||||
| `requesting_ca_fingerprint` | text | SHA-256 of trusted CA root |
|
||||
| `active_cert_fingerprint` | text | SHA-256 of currently valid client cert |
|
||||
| `scope` | jsonb | See §8 |
|
||||
| `rate_limit_rpm` | int | Default 60 |
|
||||
| `status` | enum | `pending`, `active`, `suspended`, `revoked` |
|
||||
| `created_at` | timestamptz | |
|
||||
| `activated_at` | timestamptz | |
|
||||
| `revoked_at` | timestamptz | |
|
||||
| `last_used_at` | timestamptz | |
|
||||
| `notes` | text | Admin-visible description |
|
||||
|
||||
### 7.2 `federation_peers` (on requesting side, Server A)
|
||||
|
||||
| Field | Type | Notes |
|
||||
| --------------------- | ----------- | ------------------------------------------------ |
|
||||
| `id` | uuid PK | |
|
||||
| `peer_hostname` | text | e.g., `mosaic.uscllc.com` |
|
||||
| `peer_ca_fingerprint` | text | SHA-256 of peer's CA root |
|
||||
| `grant_id` | uuid | The grant ID assigned by the peer |
|
||||
| `local_user_id` | uuid FK | Who on Server A this federation belongs to |
|
||||
| `client_cert_pem` | text (enc) | Current client cert (PEM); rotated automatically |
|
||||
| `client_key_pem` | text (enc) | Private key (encrypted at rest) |
|
||||
| `cert_expires_at` | timestamptz | |
|
||||
| `status` | enum | `pending`, `active`, `degraded`, `revoked` |
|
||||
| `last_success_at` | timestamptz | |
|
||||
| `last_failure_at` | timestamptz | |
|
||||
| `notes` | text | |
|
||||
|
||||
### 7.3 `federation_audit_log` (on serving side, Server B)
|
||||
|
||||
| Field | Type | Notes |
|
||||
| ------------- | ----------- | ------------------------------------------------ |
|
||||
| `id` | uuid PK | |
|
||||
| `grant_id` | uuid FK | |
|
||||
| `occurred_at` | timestamptz | indexed |
|
||||
| `verb` | text | `query`, `handshake`, `rejected`, `rate_limited` |
|
||||
| `resource` | text | e.g., `tasks`, `notes`, `credentials` |
|
||||
| `query_hash` | text | SHA-256 of normalized query (no payload stored) |
|
||||
| `outcome` | text | `ok`, `denied`, `error` |
|
||||
| `bytes_out` | int | |
|
||||
| `latency_ms` | int | |
|
||||
|
||||
**Audit policy:** Every federation request is logged on the serving side. Read-only requests only — no body capture. Retention: 90 days hot, then roll to cold storage.
|
||||
|
||||
## 8. RBAC & Scope
|
||||
|
||||
Every federation grant has a scope object that answers three questions for every inbound request:
|
||||
|
||||
1. **Who is acting?** — `subject_user_id` from the cert.
|
||||
2. **What resources?** — an allowlist of resource types (`tasks`, `notes`, `credentials`, `memory`, `teams/:id/tasks`, …).
|
||||
3. **Filter expression** — predicates applied on top of the subject's normal RBAC (see below).
|
||||
|
||||
### 8.1 Scope schema
|
||||
|
||||
```json
|
||||
{
|
||||
"resources": ["tasks", "notes", "memory"],
|
||||
"filters": {
|
||||
"tasks": { "include_teams": ["team_uuid_1", "team_uuid_2"], "include_personal": true },
|
||||
"notes": { "include_personal": true, "include_teams": [] },
|
||||
"memory": { "include_personal": true }
|
||||
},
|
||||
"excluded_resources": ["credentials", "api_keys"],
|
||||
"max_rows_per_query": 500
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 Access rule (enforced on serving side)
|
||||
|
||||
For every inbound federated query on resource R:
|
||||
|
||||
1. Resolve effective identity → `subject_user_id`.
|
||||
2. Check R is in `scope.resources` and NOT in `scope.excluded_resources`. Otherwise 403.
|
||||
3. Evaluate the user's **normal RBAC** (what would they see if they logged into Server B directly)?
|
||||
4. Intersect with the scope filter (e.g., only team X, only personal).
|
||||
5. Apply `max_rows_per_query`.
|
||||
6. Return; log to audit.
|
||||
|
||||
### 8.3 Team boundary guarantees
|
||||
|
||||
- Scope filters are additive, never subtractive of the native RBAC. A grant cannot grant access the user would not have had themselves.
|
||||
- `include_teams` means "only these teams," not "these teams in addition to all teams."
|
||||
- `include_personal: false` hides the user's personal data entirely from federation, even if they own it — useful for work-only accounts.
|
||||
|
||||
### 8.4 No cross-user leakage
|
||||
|
||||
When Server B has multiple users (employees) all federating back to their own Server A:
|
||||
|
||||
- Each employee has their own grant with their own `subject_user_id`.
|
||||
- The cert is bound to a specific grant; there is no mechanism by which one grant's cert can be used to impersonate another.
|
||||
- Audit log is per-grant.
|
||||
|
||||
## 9. Query Model
|
||||
|
||||
Federation exposes a **narrow read API**, not arbitrary SQL.
|
||||
|
||||
### 9.1 Supported verbs (v1)
|
||||
|
||||
| Verb | Purpose | Returns |
|
||||
| -------------- | ------------------------------------------ | ------------------------------- |
|
||||
| `list` | Paginated list of a resource type | Array of resources |
|
||||
| `get` | Fetch a single resource by id | One resource or 404 |
|
||||
| `search` | Keyword search within allowed resources | Ranked list of hits |
|
||||
| `capabilities` | What this grant is allowed to do right now | Scope object + rate-limit state |
|
||||
|
||||
### 9.2 Not in v1
|
||||
|
||||
- Write verbs.
|
||||
- Aggregations / analytics.
|
||||
- Streaming / subscriptions (future: see §13).
|
||||
|
||||
### 9.3 Agent-facing integration
|
||||
|
||||
Agents never call federation directly. Instead:
|
||||
|
||||
- The gateway query layer accepts `source: "local" | "federated:<peer_hostname>" | "all"`.
|
||||
- `"all"` fans out in parallel, merges results, tags each with `_source`.
|
||||
- Federation results are in-memory only; the gateway does not persist them.
|
||||
|
||||
## 10. Caching
|
||||
|
||||
- **In-memory response cache** with short TTL (default 30s) for `list` and `get`. `search` is not cached.
|
||||
- Cache is keyed by `(grant_id, verb, resource, query_hash)`.
|
||||
- Cache is flushed on cert rotation and on grant revocation.
|
||||
- No disk cache. No cross-session cache.
|
||||
|
||||
## 11. Bootstrap & Onboarding
|
||||
|
||||
### 11.1 Instance capability tiers
|
||||
|
||||
| Tier | Storage | Queue | Memory | Can federate? |
|
||||
| ------------ | -------- | ------- | -------- | --------------------- |
|
||||
| `local` | PGlite | in-proc | keyword | No |
|
||||
| `standalone` | Postgres | Valkey | keyword | No (can be client) |
|
||||
| `federated` | Postgres | Valkey | pgvector | Yes (server + client) |
|
||||
|
||||
Federation requires `federated` tier on **both** sides.
|
||||
|
||||
### 11.2 Onboarding flow (admin-driven)
|
||||
|
||||
1. Admin on Server B runs `mosaic federation grant create --user <user-id> --peer <peer-hostname> --scope-file scope.json`.
|
||||
2. Server B generates a `grant_id`, prints a one-time enrollment URL containing the grant ID + B's CA root fingerprint.
|
||||
3. Admin on Server A (or the user themselves, if allowed) runs `mosaic federation peer add <enrollment-url>`.
|
||||
4. Server A's Step-CA generates a CSR for the new grant. A submits the CSR to B over a short-lived enrollment endpoint (single-use token in the enrollment URL).
|
||||
5. B's Step-CA signs the cert (with grant ID embedded in SAN OIDs), returns it.
|
||||
6. A stores the signed cert + private key (encrypted) in `federation_peers`.
|
||||
7. Grant status flips from `pending` to `active` on both sides.
|
||||
8. Cert auto-renews at T-7 days using the standard Step-CA renewal flow as long as the grant remains active.
|
||||
|
||||
### 11.3 Revocation
|
||||
|
||||
- **Admin-initiated:** `mosaic federation grant revoke <grant-id>` on B flips status to `revoked`, adds the cert to B's CRL, and writes an audit entry.
|
||||
- **Revoke-on-delete:** Deleting a user on B automatically revokes all grants where that user is the subject.
|
||||
- Server A learns of revocation on the next request (TLS handshake fails) and flips the peer to `revoked`.
|
||||
|
||||
### 11.4 Rate limit
|
||||
|
||||
Default `60 req/min` per grant. Configurable per grant. Enforced at the serving side. A rate-limited request returns `429` with `Retry-After`.
|
||||
|
||||
## 12. Operational Concerns
|
||||
|
||||
- **Observability:** Each federation request emits an OTEL span with `grant_id`, `peer`, `verb`, `resource`, `outcome`, `latency_ms`. Traces correlate across both servers via W3C traceparent.
|
||||
- **Health check:** `mosaic federation status` on each side shows active grants, last-success times, cert expirations, and any CRL mismatches.
|
||||
- **Backpressure:** If the serving side is overloaded, it returns `503` with a structured body; the client marks the peer `degraded` and falls back to local-only until the next successful handshake.
|
||||
- **Secrets:** `client_key_pem` in `federation_peers` is encrypted with the gateway's key (sealed with the instance's master key — same mechanism as `provider_credentials`).
|
||||
- **Credentials never cross:** The `credentials` resource type is in the default excluded list. It must be explicitly added to scope (admin action, logged) and even then is per-grant and per-user.
|
||||
|
||||
## 13. Future (post-v1)
|
||||
|
||||
- B→A push (e.g., "notify A when a task assigned to subject changes") via Socket.IO over mTLS.
|
||||
- Mesh (N-to-N) federation.
|
||||
- Write verbs with conflict resolution.
|
||||
- Shared Step-CA (a "root of roots") so that onboarding doesn't require exchanging CA roots.
|
||||
- Federated memory search over vector indexes with homomorphic filtering.
|
||||
|
||||
## 14. Locked Decisions (was "Open Questions")
|
||||
|
||||
| # | Question | Decision |
|
||||
| --- | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 1 | What happens to a grant when its subject user is deleted? | **Revoke-on-delete.** All grants where the user is subject are auto-revoked and CRL'd. |
|
||||
| 2 | Do we audit read-only requests? | **Yes.** All federated reads are audited on the serving side. Bodies are not captured; query hash + metadata only. |
|
||||
| 3 | Default rate limit? | **60 requests per minute per grant,** override-able per grant. |
|
||||
| 4 | How do we verify the requesting-server's identity beyond the grant token? | **X.509 client cert tied to the user,** issued by Step-CA (per-server) or locally generated. Cert subject carries `grantId` + `subjectUserId`. |
|
||||
|
||||
### M1 decisions
|
||||
|
||||
- **Postgres deployment:** **Containerized** alongside the gateway in M1 (Docker Compose profile). Moving to a dedicated host is a M5+ operational concern, not a v1 feature.
|
||||
- **Instance signing key:** **Separate** from the Step-CA key. Step-CA signs federation certs; the instance master key seals at-rest secrets (client keys, provider credentials). Different blast-radius, different rotation cadences.
|
||||
|
||||
## 15. Acceptance Criteria
|
||||
|
||||
- [ ] Two Mosaic Stack gateways on different hosts can establish a federation grant via the CLI-driven onboarding flow.
|
||||
- [ ] Server A can query Server B for `tasks`, `notes`, `memory` respecting scope filters.
|
||||
- [ ] A user on B with no grant cannot be queried by A, even if A has a valid grant for another user.
|
||||
- [ ] Revoking a grant on B causes A's next request to fail with a clear error within one request cycle.
|
||||
- [ ] Cert rotation happens automatically at T-7 days; an in-progress session survives rotation without user action.
|
||||
- [ ] Rate-limit enforcement returns 429 with `Retry-After`; client backs off.
|
||||
- [ ] With B unreachable, a session on A completes using local data and surfaces a "federation offline for `<peer>`" signal once.
|
||||
- [ ] Every federated request appears in B's `federation_audit_log` within 1 second.
|
||||
- [ ] A scope excluding `credentials` means credentials are not returnable even via `search` with matching keywords.
|
||||
- [ ] `mosaic federation status` shows cert expiry, grant status, and last success/failure per peer.
|
||||
|
||||
## 16. Implementation Milestones (reference)
|
||||
|
||||
Milestones live in `docs/federation/MILESTONES.md` (to be authored next). High-level:
|
||||
|
||||
- **M1:** Server A runs `federated` tier standalone (Postgres + Valkey + pgvector, containerized). No peer yet.
|
||||
- **M2:** Step-CA embedded; `federation_grants` / `federation_peers` schema + admin CLI.
|
||||
- **M3:** Handshake + `list`/`get` verbs with scope enforcement.
|
||||
- **M4:** `search` verb, audit log, rate limits.
|
||||
- **M5:** Cache layer, offline-degradation UX, observability surfaces.
|
||||
- **M6:** Revocation flows (admin + revoke-on-delete), cert auto-renewal.
|
||||
- **M7:** Multi-user RBAC hardening on B, team-scoped grants end-to-end, acceptance suite green.
|
||||
|
||||
---
|
||||
|
||||
**Next step after PRD sign-off:** author `docs/federation/MILESTONES.md` with per-milestone acceptance tests and estimated token budget, then file tracking issues on `git.mosaicstack.dev/mosaicstack/stack`.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user