docs: establish canonical documentation architecture #1210

Merged
mos-dt-0 merged 47 commits from docs/ia-merge-current into next 2026-08-13 17:56:15 +00:00
302 changed files with 4652 additions and 1588 deletions
Showing only changes of commit f2661d2c6e - Show all commits
+2 -3
View File
@@ -149,6 +149,5 @@ OTEL_SERVICE_NAME=mosaic-gateway
# KEYCLOAK_CLIENT_ID=mosaic
# KEYCLOAK_CLIENT_SECRET=
# Feature flags — set to true alongside provider credentials to show SSO buttons in the UI
# NEXT_PUBLIC_WORKOS_ENABLED=true
# NEXT_PUBLIC_KEYCLOAK_ENABLED=true
# The web login page discovers configured providers dynamically from
# GET /api/sso/providers. No NEXT_PUBLIC_* provider feature flag is required.
+1 -1
View File
@@ -9,7 +9,7 @@ coverage
*.tsbuildinfo
.pnpm-store
__pycache__/
docs/reports/
docs/.obsidian
# Step-CA dev password — real file is gitignored; commit only the .example
infra/step-ca/dev-password
+50
View File
@@ -0,0 +1,50 @@
# Administrator Guide
> **Status:** Partially migrated. Current SSO and local upgrade/recovery procedures are available; held procedures are labeled non-operative.
This book is the canonical home for installation, configuration, deployment, routine operations, security controls, incident response, and recovery. User workflows belong in [`USER-GUIDE/`](../USER-GUIDE/); implementation and contributor material belongs in [`DEVELOPER-GUIDE/`](../DEVELOPER-GUIDE/).
## Start here
- [Documentation atlas](../README.md) — placement rules and source-of-truth boundaries.
- [Documentation sitemap](../SITEMAP.md) — resolvable current navigation and authority-gated migration summary.
- [Product requirements](../PRD.md) — normative requirements, currently marked draft.
- [Operations index](operations/README.md) — current local procedures and explicitly held operational outlines.
- [Security index](security/README.md) — current SSO provider configuration.
## Chapter map
| Chapter | Scope | Status |
| ------------------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| `installation/` | Prerequisites, installation, and first deployment. | Scaffold only. |
| `configuration/` | Environment, provider, tier, and runtime configuration. | Scaffold only. |
| `deployment/` | Topologies, rollout, migration, and upgrade procedures. | Scaffold only. |
| [`operations/`](operations/README.md) | Health, observability, routine operation, and maintenance. | Local upgrade/recovery is current; connector lease operations are held. |
| [`security/`](security/README.md) | Authentication, authorization, SSO, secrets, and security controls. | SSO provider guide is current; other pages are planned. |
| `recovery/` | Incident response, backup, rollback, and recovery. | Scaffold only. |
Every promoted page must be added to this index and to [`SITEMAP.md`](../SITEMAP.md) in the same migration slice.
## Evidence — not current operator guidance
- [`P8-003 performance report`](../reports/qa/p8-003-performance-optimization.md) — historical performance evidence; implementation alignment is partial, and production metrics remain unverified. It is not an operational SLO or runbook.
## Migration backlog — not current operator guidance
These are source candidates, not verified runbooks:
- `_old_structure/guides/admin-guide.md` — quarantined historical source; verify claims before promotion.
- `_old_structure/guides/deployment.md` — quarantined historical source; deployment commands and assumptions remain held.
- See the [documentation catalog](../reports/documentation/2026-08-10-docs-catalog-audit.md) for file-level dispositions.
Do not treat a migration candidate as current until its commands, paths, permissions, and safety status are checked against source and tests.
## Authoring boundary
New administrator documentation belongs under one of the chapter directories above. Operationally sensitive pages must identify prerequisites, ownership, source-of-truth dependencies, and whether any procedure is current, illustrative, held, or non-operative.
## Related
- [[README|Documentation contract]]
- [[PRD|Product requirements]]
- [[DEVELOPER-GUIDE/architecture/README|Architecture index]]
+19
View File
@@ -0,0 +1,19 @@
# Administrator Operations
> **Status:** Partially migrated. Procedures explicitly identify whether they are current or held.
## Current procedures
- [Upgrade safety and recovery](upgrade-safety-and-recovery.md) — installed-CLI and local-PGlite upgrade, rollback, and framework-configuration recovery.
## Held procedures
- [Mos connector lease operations](mos-connector-lease-operations.md) — non-operative M1 outline while the gateway policy remains deny-all and no connector is activated.
A held page is architecture and readiness context, not command authority. PostgreSQL, federated, bare-metal, Compose, Gateway/Web activation, and migration-runner procedures remain outside the current local route unless a later page explicitly removes the hold with verified evidence.
## Related
- [[ADMIN-GUIDE/README|Administrator guide]]
- [[ADMIN-GUIDE/security/README|Administrator security]]
- [[DEVELOPER-GUIDE/architecture/README|Architecture index]]
@@ -0,0 +1,79 @@
# Mos Connector Lease Operations — M1
> **Status:** Held / non-operative.
> **Operational authority:** None. This page is a future runbook outline, not a current command, endpoint, migration, or activation procedure.
> **Hold condition:** The gateway's `DenyConnectorLeasePolicy` remains the default policy and rejects every lease and grant operation. No connector is activated by this page.
## Current state
M1 provides an implemented lease, fencing, audit, and gateway policy boundary. It does not currently provide an operator-facing lease endpoint, activate a connector, cut over a channel, or connect an existing runtime provider to `ConnectorExecutionContext`. The default gateway module is deny-all, so operators must not treat the schema or internal service as an available lease-control surface.
There is no current operator command sequence to acquire, renew, take over, release, or grant connector authority. Do not attempt to operate the held procedure through direct database writes or by bypassing the gateway policy. Any future activation requires a separately approved server-side policy, concrete adapter, downstream fencing design, and an updated operational runbook.
## Held future procedure — not current command authority
The following records the intended shape of a later runbook. It is deliberately non-operative while deny-all remains:
### Events and evidence to monitor later
If an authorized policy and adapter are activated in a future work package, correlation IDs may be used to inspect `connector_lease_audit_log` events:
| Event | Meaning |
| ---------- | ---------------------------------------------------------------------------------- |
| `acquire` | First holder inserted for an unused binding |
| `renew` | Current holder heartbeat extended the TTL |
| `takeover` | Authorized compare-and-swap replaced the holder and incremented epoch |
| `release` | Current holder explicitly relinquished authority |
| `expiry` | An expired current lease was observed |
| `reject` | Policy, compare-and-swap, expiry, scope, or fencing validation denied an operation |
Audit records are metadata-only. Raw grant objects, connector payloads, scopes, tokens, approval references, and credentials must never be added to audit output. The current schema does not independently enforce append-only storage; database-level protection remains a future hardening requirement.
### Held incident-review outline
For a future suspected duplicate or stale connector effect, an approved operator procedure would:
1. correlate the attempted operation with its `reject`, `takeover`, or `expiry` event;
2. compare the durable row's connector ID, lease UUID, epoch, expiry, and release time with the adapter's normalized execution context;
3. treat an old epoch, old lease UUID, expired lease, or released lease as non-authoritative rather than retrying it as the old holder;
4. use only the authorized takeover path with the observed expected epoch, never ordinary acquire, for an expired or released row; and
5. preserve evidence without assuming lease fencing provides exactly-once replay safety if an external effect may already have occurred.
These are held review requirements, not instructions to bypass the current deny-all policy.
### Held migration and rollback notes
The checked-in `0016_salty_morlocks.sql` artifact is additive: it creates the lease and audit tables and indexes without changing existing authorization/session tables. A future database rollout would still require the repository's approved migration, backup, verification, and rollback controls. Application rollback would leave additive lease/audit tables in place; dropping them would destroy evidence and is not an automatic rollback step.
This page does not authorize running migrations, connecting to PostgreSQL, initializing PGlite, or starting a connector. Those activities remain outside this held procedure and subject to repository/runtime gates.
### Held security prerequisites
Before this page could become operative, the activation work would need to demonstrate at least:
- tenant authority derived from authenticated gateway context, not connector request fields;
- authorized policy decisions over normalized identity and scopes, with policy TTL handling based on the requested input and coordinator hard caps still applied;
- explicit takeover authorization and expected-epoch compare-and-swap;
- application-boundary normalization plus any separately approved database constraints or protections;
- validation and rejection audit before adapter side effects;
- a concrete adapter that consumes and propagates the normalized epoch/context; and
- existing authorization and exact-action approval controls remaining in force.
M1 currently satisfies the boundary contract and default-deny posture, not these activation prerequisites.
## Explicit non-goals while held
This page does not authorize or claim:
1. lease administration from the dashboard, CLI, HTTP, SQL console, or a connector;
2. production connector or channel activation;
3. exactly-once delivery, side-effect journaling, checkpoint/handoff recovery, or replay safety;
4. automatic failover, rollback, or stale-effect recovery across Claude, Pi, Codex, Matrix, tmux, or provider sessions; or
5. database-enforced audit immutability or database-enforced application normalization.
The page may be promoted to an operative runbook only after deny-all is intentionally replaced, concrete adapters are reviewed, activation evidence exists, and this hold is explicitly removed by the owning work package.
## Related contract
- [M1 logical identity and fencing decision](../../DEVELOPER-GUIDE/architecture/decisions/mos-runtime-portability-m1.md)
- [MOS-PORT requirements](../../PRD.md#mos-runtime-portability-workstream-mos-port)
@@ -0,0 +1,248 @@
# Upgrade safety and recovery
> **Supported route:** an already installed `mosaic` CLI using the local PGlite
> configuration. This is a filesystem and CLI runbook; it does not activate a
> service or connect to a database.
This page covers the supported upgrade, rollback, health, and recovery checks for
Mosaic framework configuration under `MOSAIC_HOME`. It deliberately does not
turn the repository's deployment or PostgreSQL material into an operative
procedure.
## Support boundary
Use this runbook only when all of the following are true:
- `mosaic` resolves to the installed Mosaic CLI (`command -v mosaic`).
- The active application configuration is the local tier: `tier: "local"`,
`storage.type: "pglite"`, and `queue.type: "local"`.
- `DATABASE_URL` is unset. An inherited PostgreSQL DSN is outside this route and
must be removed before continuing.
- `MOSAIC_STORAGE_TIER` is unset or `local`; standalone and federated overrides
are outside this route.
- No Gateway, Web, Compose, or other service activation is required.
The following routes are **held/non-operative** in this guide:
| Route or operation | Status |
| -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| PostgreSQL-backed `standalone` storage | Held; do not activate or probe it here. |
| Federated storage and peers | Held; do not activate or probe it here. |
| Bare-metal deployment | Held; no service installation or lifecycle action is authorized. |
| Gateway/Web activation or HTTP health checks | Held; `/health`, `/health/ready`, and `mosaic gateway ...` are not evidence for this route. |
| Compose startup | Held; do not start a Compose profile or the full stack. |
| Migration runners and tier migration | Held; do not run `mosaic-db-migrator`, `pnpm --filter @mosaicstack/db db:migrate`, `mosaic storage migrate`, or `mosaic storage migrate-tier`. |
A command being present in the CLI does not make a held route operative.
## Storage and path terminology
Keep these locations separate:
- **`MOSAIC_HOME`** — the framework/operator configuration directory. It
defaults to `~/.config/mosaic` and can be overridden with `MOSAIC_HOME`.
- **PGlite data** — the local, in-process database. The checked-in local config
uses `.mosaic/storage-pglite` as its storage `dataDir`; `.mosaic/queue` is the
local queue directory. These are project data, not framework configuration.
- **Durable upgrade snapshots** — operator-file snapshots stored under
`${XDG_STATE_HOME:-$HOME/.local/state}/mosaic/backups/`. They are outside
`MOSAIC_HOME` and do not contain a PostgreSQL dump.
The configuration tier is named `local`; the storage CLI reports the backend as
`pglite`. PGlite is not PostgreSQL and does not require a PostgreSQL server.
Local PGlite schema setup is adapter-owned; this page does not authorize a
separate migration runner.
## Pre-upgrade health gate
Run these checks from a shell that has no inherited database DSN:
```bash
command -v mosaic
mosaic --version
mosaic config path
test -d "$(mosaic config path)"
env -u DATABASE_URL -u MOSAIC_STORAGE_TIER mosaic storage tier show
env -u DATABASE_URL -u MOSAIC_STORAGE_TIER mosaic storage status
mosaic restore --list
```
For the supported route, the storage checks should report the `pglite` backend
and say that no network check is needed. `mosaic storage status` reports
`PGLITE_DATA_DIR` when that variable is set; otherwise it reports the CLI's
`:memory:` fallback. That output is an inspection of the CLI environment, not a
claim that PGlite contents are healthy or durable.
If `DATABASE_URL` is set, stop. Do not point it at a local PostgreSQL instance
to make the check pass. If the active configuration is not local/PGlite, stop;
no operative route is documented here.
The supported health gate is intentionally limited to CLI resolution, framework
configuration path, local storage selection, and available upgrade snapshots.
It does not prove Gateway/Web readiness, provider connectivity, queue health, or
PGlite data integrity.
## Safe upgrade procedure
1. **Record the baseline.** Save the output of `mosaic --version`,
`mosaic config path`, and `mosaic restore --list`. Do not copy secrets into a
ticket or report.
2. **Check for updates without installing them:**
```bash
mosaic update --check
```
Exit status `0` means no update was reported; status `2` means an update is
available. Other failures are not a successful health result. The check is a
package-registry check and does not start Mosaic services or access storage.
3. **Run the installed-CLI upgrade when approved:**
```bash
mosaic update
```
The command updates the installed `@mosaicstack/*` packages through npm and,
when the framework package changed or framework drift is detected, re-seeds
framework files in keep mode. Leave the default re-seed enabled. Do not use
`--relaunch` in this runbook: that option can restart durable fleet agents
and is outside the supported no-activation route. `--no-reseed` is also not a
normal upgrade path because it intentionally leaves framework files stale.
4. **Repeat the health gate.** Confirm the CLI version, the same
`MOSAIC_HOME`, the local/PGlite storage selection, and the snapshot listing.
A successful framework upgrade must not be used as evidence that a held
Gateway, Web, PostgreSQL, or federated route is ready.
Do not replace this procedure with a direct edit of `~/.config/mosaic`, a
repository `tools/install.sh` invocation, a Compose startup, or a migration
command. The supported operator entry point for this page is the installed
`mosaic update` command.
## What an upgrade protects
For an existing keep-mode installation, the framework installer takes two
separate snapshots before the file sync:
1. An ephemeral whole-directory snapshot under `${TMPDIR:-/tmp}/` is used to
restore the previous framework directory if the sync is interrupted or
fails. If that restore cannot complete, the installer prints the retained
temporary snapshot path for manual recovery.
2. A durable snapshot captures the existing operator-owned files before the
upgrade at:
```text
${XDG_STATE_HOME:-$HOME/.local/state}/mosaic/backups/pre-update-<UTC timestamp>/
```
Snapshot directories are mode `0700`; captured files are mode `0600`.
Retention is five snapshots by default and can be changed with
`MOSAIC_BACKUP_RETENTION`. A durable snapshot failure is a warning and does
not replace the manifest and crash-rollback protections.
After the keep-mode sync, the installer compares each captured operator file
with its target. If a file was changed or removed unexpectedly, it restores the
snapshot copy and emits a warning. A symlinked parent is not followed; that
case is left for manual recovery from the snapshot path.
Keep mode is manifest-driven and fail-safe for operator paths, but it does not
mean every file under `MOSAIC_HOME` is user-owned. The framework contract files
`CONSTITUTION.md`, `AGENTS.md`, and `STANDARDS.md` are refreshed by upgrades;
keep local policy in the supported local overlays rather than editing those
contract files as rollback data. A durable snapshot is for operator-owned
configuration, not for the installed npm package, framework-owned contracts, or
PGlite data.
## Rollback and recovery
### If the upgrade fails or is interrupted
The framework sync attempts an automatic rollback from its ephemeral snapshot.
Do not delete `MOSAIC_HOME`, the PGlite data directory, or the temporary snapshot
named in the error. Preserve the command output, then run the health gate.
If the installed framework reports that the automatic restore did not complete,
use the durable snapshot procedure below. The durable snapshot is the recovery
pointer that survives a successful upgrade and the removal of the temporary
snapshot.
### Restore operator configuration from a durable snapshot
`mosaic restore` is confirmation-gated and reports counts and relative paths,
not file contents. First list snapshots, then preview the selected timestamp:
```bash
mosaic restore --list
mosaic restore --from <UTC-timestamp> --dry-run
```
If the preview is correct, run the interactive restore:
```bash
mosaic restore --from <UTC-timestamp>
```
Use `--yes` only when the overwrite has been explicitly approved:
```bash
mosaic restore --from <UTC-timestamp> --yes
```
The timestamp is the value printed by `mosaic restore --list`; the command also
accepts the full `pre-update-<timestamp>` name. For a non-default configuration
home, pass the same target explicitly:
```bash
mosaic restore --mosaic-home "$MOSAIC_HOME" --from <UTC-timestamp>
```
After restoring, repeat the health gate. Restore writes only the operator
surface represented by that snapshot. It does not downgrade the installed CLI,
restore framework-owned contract files, restore `.mosaic/storage-pglite`, or
run any migration.
### If local PGlite data is missing or corrupt
Do not use a framework snapshot as a database backup. Do not run a migration
runner, start PostgreSQL, start Compose, or activate Gateway/Web to investigate.
The current CLI does not provide a wired PGlite export/import operation;
`mosaic storage export` and `mosaic storage import` only print direct-copy
guidance. Preserve the configured PGlite data directory and route data
restoration through the held data-layer procedure rather than improvising a
recursive delete or copy.
### Recovery decision tree
- **CLI or framework path is wrong:** stop, verify `command -v mosaic`,
`mosaic --version`, `MOSAIC_HOME`, and `mosaic config path`; do not activate a
held service route.
- **Upgrade failed during framework sync:** use the installer's automatic
rollback first; if it reports incomplete recovery, list and preview the
durable snapshot, then restore it interactively.
- **An operator file changed after a reported-successful upgrade:** select the
pre-upgrade snapshot with `mosaic restore --list`, preview it with `--dry-run`,
and restore only after reviewing the relative-path plan.
- **PGlite data is affected:** preserve the data directory and stop at the
boundary described above. Framework rollback cannot recover database rows.
- **Output requests PostgreSQL, federated, bare-metal, Compose, Gateway/Web, or
a migration runner:** stop; that is a held/non-operative route, not a next
command for this runbook.
## Post-recovery verification
Run the non-mutating checks again:
```bash
mosaic --version
mosaic config path
env -u DATABASE_URL -u MOSAIC_STORAGE_TIER mosaic storage tier show
env -u DATABASE_URL -u MOSAIC_STORAGE_TIER mosaic storage status
mosaic restore --list
```
A green result here means the installed CLI resolves, the framework path is
present, the CLI selects local PGlite without a network probe, and snapshots can
be enumerated. It does not certify database contents or any held deployment
route.
+20
View File
@@ -0,0 +1,20 @@
# Security
> **Status:** Partially migrated. The SSO provider and Discord ingress security pages are current.
This chapter will contain authentication, authorization, SSO, secrets, RBAC, and security-control guidance for administrators.
## Planned pages
- [`sso-providers.md`](sso-providers.md) — current provider configuration, discovery, callbacks, and failure modes.
- [`discord-ingress.md`](discord-ingress.md) — current Discord service authentication, allowlists, bindings, roles, replay, and failure controls.
- `secrets.md` — document general secret handling after source/configuration verification.
- `rbac.md` — document roles and permissions from the canonical implementation.
The current SSO page reflects dynamic provider discovery. The Discord page documents only the verified Discord compatibility boundary; it does not claim Telegram or Matrix parity. Do not revive retired root documents or add frontend feature flags that the web flow does not consume.
## Related
- [`Administrator guide`](../README.md)
- [`API documentation`](../../API/README.md)
- [[DEVELOPER-GUIDE/architecture/README|Architecture index]]
@@ -0,0 +1,137 @@
# Discord ingress security
> **Status:** Current Discord behavior only. Telegram shared-contract parity, Matrix channel ingress, and a gateway-wide shared adapter registry are not implemented or are not proven by the current source/tests.
>
> **Last verified:** 2026-08-10 against the Discord plugin, gateway ingress/authentication code, and focused tests linked in [Evidence](#evidence).
>
> **Audience:** Administrators provisioning the Discord remote-control boundary.
This page documents the security boundary that exists today. It is not a deployment recipe for Telegram or Matrix, and it does not turn the gateway's lifecycle plugin list into a universal channel registry.
## Security model
Discord ingress has two current layers:
1. **Native Discord admission** in `@mosaicstack/discord-plugin` applies guild/channel/user allowlists, pairing, role, rate, and thread rules before a thread is created or a message is dispatched.
2. **Gateway compatibility admission** authenticates the Discord Socket.IO service, verifies the signed envelope again, re-checks the allowlists and binding, validates the conversation route, rejects replayed native message IDs, and then dispatches the message to the trusted agent configuration.
The current gateway namespace is `/chat`. The Discord plugin connects with a Socket.IO handshake value named `discordServiceToken`; this is distinct from the environment variable name `DISCORD_SERVICE_TOKEN` that supplies the value to the plugin and gateway.
### Admission and authorization order
For an inbound guild message, the current implementation:
1. Ignores bot-authored messages and messages without a guild. Discord DMs are therefore not handled by this ingress path, even though the client requests a direct-message intent.
2. Uses the configured thread parent as the authorization channel for a thread. A normal Discord category parent is never substituted for a text channel.
3. Requires the guild, authorization channel, and user to appear in their respective allowlists.
4. Resolves a configuration-owned binding and paired user. `viewer` cannot send a turn. Ordinary `send` requires `operator` or `admin`; `approve` and `stop` require `admin`.
5. Applies the message and mention-thread rate limits before any thread creation or gateway dispatch.
6. Derives the route from the binding's logical-agent instance and the response channel/thread. The route does not accept a provider, model, harness, process, or runtime-session selector from Discord.
7. Creates or reuses a thread only after the checks above pass.
The gateway then verifies the HMAC-SHA-256 envelope with `DISCORD_SERVICE_TOKEN`, re-applies the allowlists and binding/role check, requires the conversation ID to match the bound logical agent and channel/thread, and claims the native Discord message ID in a bounded replay cache. The default replay cache is in-process, retains IDs for 15 minutes, and is bounded at 10,000 entries; it is not a durable inbox.
For ordinary chat, the gateway attempts persistence and dispatch using `DISCORD_SERVICE_USER_ID`; `DISCORD_SERVICE_TENANT_ID` is used when configured and otherwise ordinary chat falls back to the service user ID as its tenant. The Discord external route is not a UUID, while persisted conversation IDs are UUIDs; no current route-to-UUID mapping proves that ordinary Discord persistence succeeds. The gateway may continue dispatch after a persistence/binding failure, so successful live delivery is not durable-history evidence.
Privileged approval and stop additionally require a configured tenant, a paired `mosaicUserId`, and a previously enrolled durable session. The durable session's logical agent must match the binding before approval or stop is accepted. The approval is consumed once against the exact runtime target; the Discord service account is not substituted for the approving paired user.
The trusted `agentConfigId` in each binding is resolved by the gateway. Its provisioned agent name must exactly equal `instanceId`. Discord cannot choose an arbitrary agent, provider, or model in the message payload, and Discord ingress does not use the general routing engine for a new session.
## Required configuration
The gateway's current plugin factory is in [`plugin.module.ts`](../../../apps/gateway/src/plugin/plugin.module.ts). When `DISCORD_BOT_TOKEN` is present, the following Discord values are required or validated as shown:
| Name | Required/current behavior |
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `DISCORD_BOT_TOKEN` | Enables the Discord plugin and supplies the Discord bot credential. |
| `DISCORD_SERVICE_TOKEN` | Required when the bot is enabled. Authenticates the Socket.IO service handshake and signs/verifies ingress envelopes. Treat as a high-entropy secret. |
| `DISCORD_SERVICE_USER_ID` | Required when the bot is enabled. Provisioned Mosaic service principal used for ordinary Discord dispatch and attempted persistence; durable history is not guaranteed. |
| `DISCORD_SERVICE_TENANT_ID` | Not required to start ordinary Discord chat, but required for the `/approve` and `/stop <approval>` control path. Use the provisioned tenant for the service boundary. |
| `DISCORD_GATEWAY_URL` | Base gateway URL. The plugin connects to `${DISCORD_GATEWAY_URL}/chat`; the gateway factory default is `http://localhost:14242`. |
| `DISCORD_GUILD_ID` | Optional guild ID used only by the current project-channel provisioning helper. It is not the message authorization allowlist. |
| `DISCORD_ALLOWED_GUILD_IDS` | Required, comma-separated guild IDs. Empty or missing values fail closed during plugin creation. |
| `DISCORD_ALLOWED_CHANNEL_IDS` | Required, comma-separated parent text-channel IDs. Thread messages are checked against their configured parent. |
| `DISCORD_ALLOWED_USER_IDS` | Required, comma-separated Discord user IDs. This allowlist is checked in addition to `pairedUsers`. |
| `DISCORD_INTERACTION_BINDINGS` | Required, non-empty JSON array of configuration-owned bindings. Malformed or empty data fails plugin creation. |
| `DISCORD_MESSAGE_RATE_LIMIT_PER_MINUTE` | Optional positive integer; default is `30` authorized turns per guild/channel/user window. Zero, negative, and non-integer values are rejected. |
| `DISCORD_THREAD_RATE_LIMIT_PER_MINUTE` | Optional positive integer; default is `5` mention-triggered thread routes per guild/channel/user window. Invalid values are rejected. |
`MOSAIC_AGENT_NAME` and `MOSAIC_AGENT_CONFIG_ID` are not substitutes for a Discord binding. The current Discord binding uses `instanceId` and `agentConfigId` inside `DISCORD_INTERACTION_BINDINGS`; do not invent a different environment-based routing contract.
### Binding shape
Use placeholders for identifiers and keep credentials out of the JSON:
```json
[
{
"instanceId": "interaction-agent",
"agentConfigId": "provisioned-agent-config-id",
"guildId": "guild-id",
"channelId": "parent-channel-id",
"pairedUsers": {
"discord-user-id": {
"role": "operator",
"mosaicUserId": "provisioned-mosaic-user-id"
}
}
}
]
```
Each binding requires `instanceId`, `agentConfigId`, `guildId`, `channelId`, and a non-empty `pairedUsers` object. Pairing roles are `viewer`, `operator`, and `admin`. A role-only pairing remains accepted for ordinary non-privileged compatibility, but it has no `mosaicUserId` and cannot authorize the privileged approval/stop path. The guild and parent channel must also be present in their allowlists.
The bot needs permission to view and send messages in the configured channels and to create and send public threads. A category parent is not an authorization boundary. A thread inherits authorization only from its configured parent text channel.
### Secret handling
Supply `DISCORD_BOT_TOKEN` and `DISCORD_SERVICE_TOKEN` through the approved runtime secret mechanism. Do not commit them, put them in binding JSON, or pass them on a command line.
The current `mosaic gateway config --set KEY=VALUE` implementation writes the gateway `.env` file and prints the value in its confirmation; its mask list does not include `DISCORD_SERVICE_TOKEN`. Do **not** use that command for the service token. `mosaic gateway config --edit` exists for local configuration, but production secret provisioning must remain outside the repository and follow the approved secret path.
## Applying configuration safely
These are the current CLI commands exposed by `@mosaicstack/mosaic`; they manage the gateway daemon and do not constitute a Discord protocol:
```bash
mosaic gateway install
mosaic gateway config --edit
mosaic gateway status
mosaic gateway verify
mosaic gateway restart
mosaic gateway logs --lines 50
```
The daemon reads its environment from `~/.config/mosaic/gateway/.env` by default; `MOSAIC_GATEWAY_HOME` can change that home. `mosaic gateway config --set KEY=VALUE` and `--unset KEY` are also implemented for non-secret values. After changing Discord configuration, restart the gateway so the plugin factory is rebuilt. `mosaic gateway status` and `mosaic gateway verify` check the gateway daemon/health surfaces; the current lifecycle host does not expose a channel-specific `healthAll` command, so a green gateway check alone is not proof that Discord is connected.
## Failure and abuse behavior
| Condition | Current result |
| ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Missing service token/user, allowlist, or interaction bindings when Discord is enabled | Gateway plugin creation fails rather than enabling an unconfigured remote-control surface. |
| Invalid optional rate limit | Plugin creation fails; values must be positive integers. |
| Unallowlisted guild/channel/user, unpaired user, or insufficient role | Message is ignored before thread creation and gateway dispatch. |
| Mentioned message cannot create or fetch its requested thread | The message is not dispatched because its response target cannot be honored. |
| Invalid HMAC, malformed envelope, route mismatch, wrong binding, or replayed native message ID | Gateway rejects the ingress without dispatch. |
| Unsafe attachment metadata or URL | Gateway rejects the message before acknowledgement/dispatch. Current bounds include at most 10 attachments, HTTPS URLs without credentials, query strings, or fragments, and bounded ID/name/URL/metadata lengths. |
| Missing `DISCORD_SERVICE_TENANT_ID` for approval/stop | The privileged control handler returns without creating or consuming an approval. |
| Agent configuration ID does not resolve or its name differs from `instanceId` | Gateway refuses to create the Discord-bound session. |
| External route cannot be persisted as a UUID conversation | Gateway may still dispatch live output; durable history and restart/resume continuity are not guaranteed and must not be inferred from delivery. |
## Explicitly not current
- **Telegram:** `TELEGRAM_BOT_TOKEN` and `TELEGRAM_GATEWAY_URL` can instantiate the raw legacy Telegram plugin, but that plugin does not use the shared channel DTOs, Discord-style service authentication, allowlists, pairing, route validation, or a tested gateway security boundary. Do not treat these variables as a secured Telegram equivalent of the Discord configuration above.
- **Matrix:** No current gateway channel adapter, binding, authentication path, or focused channel test establishes Matrix ingress. Matrix-related fleet/runtime code is not evidence of a Matrix channel deployment procedure.
- **Shared registry parity:** The current gateway `PLUGIN_REGISTRY` hosts lifecycle wrappers (`name`, `start`, `stop`, and optional project provisioning). It does not expose a universal channel health/ingress/egress registry. That is follow-up work, not an administrator capability today.
## Evidence
- [`plugins/discord/src/index.ts`](../../../plugins/discord/src/index.ts) — Discord allowlists, bindings, roles, thread routing, signed envelope, typed ingress/egress, limits, retry, and health.
- [`plugins/discord/src/index.test.ts`](../../../plugins/discord/src/index.test.ts) — authorization ordering, thread behavior, attachments, stable routes, egress, rate limits, and health.
- [`apps/gateway/src/chat/chat.gateway.ts`](../../../apps/gateway/src/chat/chat.gateway.ts) — `/chat` authentication, envelope validation, replay, trusted agent selection, ordinary dispatch, approval, and stop.
- [`apps/gateway/src/chat/chat.gateway-auth.ts`](../../../apps/gateway/src/chat/chat.gateway-auth.ts) — timing-safe service-token and BetterAuth session validation.
- [`apps/gateway/src/plugin/discord-ingress.security.spec.ts`](../../../apps/gateway/src/plugin/discord-ingress.security.spec.ts) — signature, allowlist, replay, attachment, binding, approval, stop, and logical-agent checks.
- [`apps/gateway/src/__tests__/integration/tess-cross-surface.integration.test.ts`](../../../apps/gateway/src/__tests__/integration/tess-cross-surface.integration.test.ts) — control-flow evidence with explicit durable-session pre-enrollment, not proof of ordinary Discord persistence.
- [`packages/mosaic/src/commands/gateway.ts`](../../../packages/mosaic/src/commands/gateway.ts) and [`gateway/config.ts`](../../../packages/mosaic/src/commands/gateway/config.ts) — verified gateway CLI command names and configuration behavior.
- [Channel protocol architecture](../../DEVELOPER-GUIDE/architecture/channel-protocol.md) — canonical shared-contract and parity boundary.
- [Discord conversation workflow](../../USER-GUIDE/workflows/discord-conversations.md) — end-user behavior.
+192
View File
@@ -0,0 +1,192 @@
---
title: SSO Providers
type: runbook
audience: admin
status: current
source_of_truth: false
---
# SSO Providers
Configure optional enterprise single sign-on for Mosaic Stack through Better Auth's generic OAuth integration. The gateway owns provider configuration and discovery; the web application renders only providers reported by the gateway.
> **Current behavior:** Authentik, WorkOS, and Keycloak are supported in the checked-in implementation. Authentik and WorkOS use OIDC. Keycloak supports OIDC and an optional direct SAML login URL. The web application does **not** read `NEXT_PUBLIC_WORKOS_ENABLED` or `NEXT_PUBLIC_KEYCLOAK_ENABLED`; provider buttons are discovered dynamically from the gateway.
## Prerequisites
Before configuring a provider, establish these gateway settings:
- `BETTER_AUTH_URL` — the public base URL used to construct OAuth callback URLs.
- `BETTER_AUTH_SECRET` — a strong secret for Better Auth sessions and tokens.
- `GATEWAY_CORS_ORIGIN` — the web origin or comma-separated origins allowed by the gateway.
Keep client secrets and Better Auth secrets in the deployment secret store. Do not commit them to `.env` files or expose them to the web bundle.
## Provider configuration
### Authentik OIDC
Set all three variables to enable Authentik:
```bash
AUTHENTIK_ISSUER=https://auth.example.com/application/o/mosaic
AUTHENTIK_CLIENT_ID=...
AUTHENTIK_CLIENT_SECRET=...
```
The implementation derives OIDC discovery and endpoint URLs from `AUTHENTIK_ISSUER`. An optional team-claim label can be exposed in provider discovery:
```bash
AUTHENTIK_TEAM_SYNC_CLAIM=groups
```
The default reported claim is `groups`. The discovery payload reports this claim; verify any downstream membership-sync behavior separately before treating it as an authorization guarantee.
Register this redirect URI with the Authentik application:
```text
{BETTER_AUTH_URL}/api/auth/oauth2/callback/authentik
```
### WorkOS OIDC
Set all three variables to enable WorkOS:
```bash
WORKOS_ISSUER=https://your-company.authkit.app
WORKOS_CLIENT_ID=client_...
WORKOS_CLIENT_SECRET=...
```
Use the WorkOS AuthKit issuer or custom authentication domain, not a raw WorkOS REST API hostname. Mosaic derives the OIDC discovery URL by appending `/.well-known/openid-configuration` to the issuer. WorkOS uses PKCE and issuer validation in the current auth configuration.
An optional team-claim label can be exposed in provider discovery:
```bash
WORKOS_TEAM_SYNC_CLAIM=organization_id
```
The default reported claim is `organization_id`. The discovery payload reports this claim; verify any downstream membership-sync behavior separately before treating it as an authorization guarantee.
Register this redirect URI with the WorkOS application:
```text
{BETTER_AUTH_URL}/api/auth/oauth2/callback/workos
```
### Keycloak OIDC
Use either an explicit issuer or the URL-plus-realm form. The client ID and secret are required in both forms.
Explicit issuer:
```bash
KEYCLOAK_ISSUER=https://auth.example.com/realms/mosaic
KEYCLOAK_CLIENT_ID=mosaic
KEYCLOAK_CLIENT_SECRET=...
```
Derived issuer:
```bash
KEYCLOAK_URL=https://auth.example.com
KEYCLOAK_REALM=mosaic
KEYCLOAK_CLIENT_ID=mosaic
KEYCLOAK_CLIENT_SECRET=...
```
`KEYCLOAK_ISSUER` takes precedence when both forms are present. Keycloak uses PKCE and issuer validation in the current auth configuration.
An optional team-claim label can be exposed in provider discovery:
```bash
KEYCLOAK_TEAM_SYNC_CLAIM=groups
```
The default reported claim is `groups`. The discovery payload reports this claim; verify any downstream membership-sync behavior separately before treating it as an authorization guarantee.
Register this redirect URI with the Keycloak client:
```text
{BETTER_AUTH_URL}/api/auth/oauth2/callback/keycloak
```
### Keycloak direct SAML fallback
The current web flow supports a direct SAML link for Keycloak when `KEYCLOAK_SAML_LOGIN_URL` is configured:
```bash
KEYCLOAK_SAML_LOGIN_URL=https://auth.example.com/realms/mosaic/protocol/saml
```
This creates a configured Keycloak provider with `loginMode: saml`. The web login button links directly to the supplied URL as `Continue with Keycloak (SAML)`; there is no Better Auth OIDC callback for this mode. The URL must be the provider's valid SAML launch URL for the deployment.
A SAML-only Keycloak configuration does not require the Keycloak OIDC client variables. Do not combine an incomplete OIDC variable set with SAML-only configuration: partial OIDC configuration is rejected during auth setup.
## Callback and discovery contract
Better Auth is mounted at `/api/auth`. OIDC callbacks use:
```text
{BETTER_AUTH_URL}/api/auth/oauth2/callback/{providerId}
```
The gateway exposes provider discovery at:
```text
GET /api/sso/providers
```
The response includes `authentik`, `workos`, and `keycloak` records with:
- `configured` — whether a usable OIDC or Keycloak SAML configuration is present.
- `protocols` — supported protocols for the provider record.
- `loginMode``oidc`, `saml`, or `null`.
- `callbackPath` — the OIDC callback path, or `null` for SAML-only mode.
- `teamSync` — the configured/default claim label exposed to the UI.
- `samlFallback` — whether a direct Keycloak SAML URL is configured.
- `warnings` — partial OIDC configuration warnings when reported.
The web login page filters this response to configured providers. OIDC buttons use Better Auth's `signIn.oauth2` flow. A configured Keycloak SAML fallback is rendered as a direct link. No provider-specific `NEXT_PUBLIC_*_ENABLED` flag is required or consumed.
## Configuration procedure
1. Set `BETTER_AUTH_URL` to the public gateway URL that the identity provider can reach.
2. Set `BETTER_AUTH_SECRET` and the correct `GATEWAY_CORS_ORIGIN` values.
3. Choose one provider configuration above and set its complete required variable group.
4. Register the exact OIDC callback URI with the identity provider, when using OIDC.
5. Restart or redeploy the gateway so it loads the changed environment.
6. Inspect discovery without exposing secrets:
```bash
curl "$BETTER_AUTH_URL/api/sso/providers"
```
7. Open the web login page and confirm that only configured providers are shown.
8. Complete a sign-in and verify the callback returns to the configured application.
## Partial configuration and failure modes
Provider configuration is optional. If no provider variables are set, the gateway can run without SSO providers and the web login page renders no SSO section.
For Authentik and WorkOS, setting only part of the issuer/client ID/client secret group raises a configuration error. For Keycloak OIDC, the client ID, client secret, and either an explicit issuer or a complete URL-plus-realm pair are required. Empty or whitespace-only values are treated as unset.
Common failures:
| Symptom | Check |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Provider does not appear on the login page | Query `/api/sso/providers`; verify the complete provider variable group is present in the gateway environment. |
| OAuth callback is rejected | Compare the registered redirect URI character-for-character with `BETTER_AUTH_URL` and the provider callback path. |
| Provider is shown but sign-in cannot start | Check `loginMode`, issuer discovery, client credentials, and gateway logs. |
| Keycloak SAML button is absent | Set `KEYCLOAK_SAML_LOGIN_URL` to the provider's direct launch URL and reload the gateway. |
| Startup/auth initialization reports missing variables | Remove the partial provider configuration or provide the complete required group. |
| SSO succeeds but team membership is unexpected | Treat `teamSync.claim` as discovery metadata and verify the actual claim mapping and membership-sync implementation. |
Do not enable a provider by adding the obsolete `NEXT_PUBLIC_WORKOS_ENABLED` or `NEXT_PUBLIC_KEYCLOAK_ENABLED` variables. They are not read by the current web application.
## Related
- [Administrator guide](../README.md)
- [Security chapter](README.md)
- [API documentation index](../../API/README.md)
- [Documentation atlas](../../README.md)
+31
View File
@@ -0,0 +1,31 @@
# API Documentation
> **Status:** Scaffold only. The canonical gateway contract has not yet been migrated into this directory.
This directory is the single API documentation boundary. `OPENAPI.yaml` will be the machine-readable contract, and `ENDPOINTS.md` will provide the human-readable endpoint, authentication, permission, and error index. Neither file exists here yet; do not describe this scaffold as a complete API reference.
## Start here
- [Documentation atlas](../README.md) — placement rules and source-of-truth boundaries.
- [Documentation sitemap](../SITEMAP.md) — current API transition status and authority-gated backlog.
- [Documentation catalog audit](../reports/documentation/2026-08-10-docs-catalog-audit.md) — current API artifact inventory and migration evidence.
## Contract map
| Artifact | Purpose | Status |
| ---------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------- |
| `OPENAPI.yaml` | Canonical machine-readable HTTP/WebSocket API contract. | Planned; not present yet. |
| `ENDPOINTS.md` | Human index for endpoint behavior, auth, permissions, and errors. | Planned; not present yet. |
| [`../openapi-tess.yaml`](../openapi-tess.yaml) | Legacy Tess-scoped OpenAPI artifact with 17 paths. | Migration candidate; not the complete gateway contract. |
A contract migration must verify paths, schemas, authentication, permissions, error behavior, and generated/client references before the legacy artifact is retired. Keep scoped contracts explicitly labeled if they remain alongside the consolidated contract.
## Authoring boundary
New API contracts belong here. Use `OPENAPI.yaml` for machine-readable authority and `ENDPOINTS.md` for human-readable constraints that OpenAPI cannot fully express. Guide books may explain usage workflows, but must link back to this directory rather than copying endpoint definitions.
## Related
- [[README|Documentation contract]]
- [[DEVELOPER-GUIDE/architecture/README|Architecture index]]
- [[ADMIN-GUIDE/security/README|SSO provider chapter]]
+50
View File
@@ -0,0 +1,50 @@
# Developer Guide
> **Status:** Partially migrated. Architecture, lease-broker verification, and channel-adapter authoring pages are current; other contributor chapters remain unmigrated.
This book is the canonical home for architecture, package and application guides, local development, testing, contribution workflow, and integration authoring. User-facing procedures belong in [`USER-GUIDE/`](../USER-GUIDE/); operator procedures belong in [`ADMIN-GUIDE/`](../ADMIN-GUIDE/); API contracts belong in [`API/`](../API/).
## Start here
- [Documentation atlas](../README.md) — placement rules and source-of-truth boundaries.
- [Architecture index](architecture/README.md) — current architecture chapter scaffold.
- [Documentation audit](../reports/documentation/2026-08-10-docs-catalog-audit.md) — evidence-based migration inventory.
- [Product requirements](../PRD.md) — normative requirements, currently marked draft.
## Chapter map
| Chapter | Scope | Status |
| ----------------------------------------- | -------------------------------------------------------------------- | ---------------------------------------------- |
| [`architecture/`](architecture/README.md) | System model, components, data flow, security model, ADRs, and RFCs. | Partially migrated. |
| `packages/` | Package- and application-level contracts and guides. | Scaffold only. |
| `local-development/` | Safe local setup and development routes. | Scaffold only. |
| `testing/` | Test strategy, verification, and quality gates. | Lease-broker verification boundary is current. |
| `contributing/` | Contribution, review, and delivery workflow. | Scaffold only. |
| `integrations/` | Plugin, provider, and adapter authoring. | Channel-adapter authoring boundary is current. |
### Current contributor pages
- [Lease-broker operations and verification](testing/lease-broker-operations.md) — safe static/test commands plus explicitly held live operations.
- [Channel adapters](integrations/channel-adapters.md) — current shared contracts and Discord reference boundary; future adapter parity is draft.
Every promoted page must be added to this index and to [`SITEMAP.md`](../SITEMAP.md) in the same migration slice.
## Migration backlog — not current developer guidance
These are source candidates or stale records, not verified current instructions:
- [`archived TUI PRD`](../archive/tui/PRD-TUI_Improvements.md) — contradicted/stale; it references a missing `packages/cli`, while current TUI code is under `packages/mosaic`.
- [`archived TUI task ledger`](../archive/tui/TASKS-TUI_Improvements.md) — historical task ledger; its status and worktree claims require revalidation.
- `_old_structure/guides/dev-guide.md` — quarantined historical source; verify paths and commands before promotion. See the [documentation catalog](../reports/documentation/2026-08-10-docs-catalog-audit.md) for its disposition.
Do not make a legacy or archived page current by linking it from a chapter as if it were already promoted.
## Authoring boundary
New developer documentation belongs under one of the chapter directories above. Architecture decisions and RFCs must identify their status and authority; executable behavior must be checked against current code and tests.
## Related
- [[README|Documentation contract]]
- [[DEVELOPER-GUIDE/architecture/README|Architecture index]]
- [[API/README|API index]]
@@ -0,0 +1,50 @@
# Architecture
> **Status:** Partially migrated. The lease-broker security-contract pages below are current references; the remaining architecture pages are still being classified.
This chapter is the canonical home for Mosaic Stack's system model, component boundaries, data and control flow, security model, architecture decisions, and RFCs. It explains why the system has its shape; it does not replace [`PRD.md`](../../PRD.md), [`TASKS.md`](../../TASKS.md), or the API contract.
## Promoted pages
- [`lease-broker-protocol.md`](lease-broker-protocol.md) — authenticated Unix-socket protocol, identity binding, framing, persistence, and lease transitions.
- [`lease-broker-security.md`](lease-broker-security.md) — identity, ancestry, filesystem, whole-class, observer, and named residual security boundaries.
- [`mutator-class-gate.md`](mutator-class-gate.md) — default-deny tool authorization, runtime adapters, launch choke point, and parser assurance boundary.
- [`compaction-revocation.md`](compaction-revocation.md) — Claude/Pi observer lifecycle, runtime generations, revocation, and the bounded residual stale window.
- [`channel-protocol.md`](channel-protocol.md) — current shared channel DTOs and Discord compatibility baseline, with unimplemented adapter work explicitly marked draft.
- [`decisions/mos-runtime-portability-m1.md`](decisions/mos-runtime-portability-m1.md) — current logical identity, connector lease, grant, audit, and fencing decision; connector activation remains held.
These pages are current security-contract references and are consumed by the lease-broker acceptance suites. Their live deployment gaps remain explicitly labeled in the pages; this migration does not change runtime behavior.
## Planned pages
| Path | Purpose | Status |
| ----------------------------------- | --------------------------------------------------------------------- | ------------------------- |
| `system-overview.md` | Platform boundary and major request, event, and agent-runtime flows. | Planned. |
| `component-map.md` | Apps, packages, plugins, and dependency ownership. | Planned. |
| `data-flow.md` | Data, event, and control-plane movement. | Planned. |
| `security-model.md` | Trust boundaries, authority, authentication, and authorization model. | Planned. |
| [`decisions/`](decisions/README.md) | Approved architecture decision records. | Partially migrated. |
| [`rfcs/`](rfcs/README.md) | Proposals and protocol RFCs. | Draft egress RFC indexed. |
### Draft RFCs
- [`rfcs/optional-ai-egress-gateways.md`](rfcs/optional-ai-egress-gateways.md) — proposed model-egress boundary; not approved or integrated.
Promoted pages must be linked here, from [`DEVELOPER-GUIDE/README.md`](../README.md), and from [`SITEMAP.md`](../../SITEMAP.md). Do not create duplicate architecture pages in `docs/mosaic-stack/` or the docs root.
## Migration backlog — not current architecture
- [`docs/README.md`](../../README.md) — current documentation contract and placement rules.
- [`Documentation information architecture design`](../../plans/2026-08-10-docs-information-architecture-design.md) — approved documentation structure decision, not product architecture.
- [`Documentation catalog audit`](../../reports/documentation/2026-08-10-docs-catalog-audit.md) — evidence and migration recommendations, not normative architecture.
## Source-of-truth boundary
Architecture pages explain approved design and current system boundaries. Requirements remain in [`PRD.md`](../../PRD.md); active work remains in [`TASKS.md`](../../TASKS.md); executable behavior remains authoritative in source and tests. Draft proposals belong in `rfcs/` or [`docs/plans/`](../../plans/), with status clearly labeled.
## Related
- [[README|Documentation contract]]
- [[DEVELOPER-GUIDE/README|Developer guide]]
- [[PRD|Product requirements]]
- [[API/README|API index]]
@@ -0,0 +1,285 @@
# Channel protocol architecture
> **Status:** Current shared type contract and Discord compatibility baseline. The shared gateway registry, Telegram parity, Matrix integration, identity-linking, and multi-surface multiplexing described below are draft or unimplemented.
>
> **Audience:** Developers maintaining `@mosaicstack/types`, channel plugins, the gateway chat/plugin boundaries, or future official adapters.
>
> **Last verified:** 2026-08-10 against the source and focused tests listed in [Evidence](#evidence).
>
> **Authority:** Executable source and tests are authoritative for current behavior. This page explains the boundary; it is not a runtime registry, an API contract, a requirements document, or proof that every channel uses the shared DTOs.
## Reading this page
This page intentionally separates three states:
- **Current** — implemented in the repository and supported by the cited tests.
- **Compatibility** — an existing wire path that preserves current behavior but does not yet mean that the shared channel ports are wired through the gateway.
- **Draft** — a design direction or follow-up work item. Draft sections have no implementation authority and must not be used as instructions for operating Telegram, Matrix, identity linking, or cross-surface fanout.
The migration from `docs/_old_structure/architecture/channel-protocol.md` is a documentation correction. It does not add adapters, change gateway behavior, change authentication, or create database objects.
## Authority and evidence boundaries
| Boundary | Current authority | What this page may claim |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| Shared channel types and ports | [`channel.dto.ts`](../../../packages/types/src/channel/channel.dto.ts), [`channel-adapter.ts`](../../../packages/types/src/channel/channel-adapter.ts), and their exports | The TypeScript shapes and method signatures that are currently published from `@mosaicstack/types`. |
| Discord native behavior | [`plugins/discord/src/index.ts`](../../../plugins/discord/src/index.ts) and [`index.test.ts`](../../../plugins/discord/src/index.test.ts) | The Discord allowlist, pairing, role, thread, ingress, egress, retry, and health behavior covered by source and tests. |
| Discord gateway compatibility | [`chat.gateway.ts`](../../../apps/gateway/src/chat/chat.gateway.ts), [`chat.gateway-auth.ts`](../../../apps/gateway/src/chat/chat.gateway-auth.ts), and the focused gateway tests | The signed Socket.IO service path, gateway validation, raw chat events, and current session dispatch behavior. |
| Plugin hosting | [`plugin.interface.ts`](../../../apps/gateway/src/plugin/plugin.interface.ts), [`plugin.module.ts`](../../../apps/gateway/src/plugin/plugin.module.ts), and [`plugin.service.ts`](../../../apps/gateway/src/plugin/plugin.service.ts) | The lifecycle registry that exists today. It is not evidence of a shared `OfficialChannelAdapter` registry. |
| Telegram | [`plugins/telegram/src/index.ts`](../../../plugins/telegram/src/index.ts) and [`package.json`](../../../plugins/telegram/package.json) | The raw legacy behavior that exists. It is not evidence of shared-contract parity or a working authenticated gateway integration. |
| Matrix, identity linking, and multiplexing | No matching current implementation and test boundary was found for the old page's designs | These topics remain explicitly draft/unimplemented here. |
The current source boundaries also distinguish two identities:
1. A channel route carries a configuration-owned logical agent and response destination.
2. The gateway chooses provider, model, and runtime session internally. Durable-session enrollment is separate and is not proven by the external channel route alone.
A route is therefore not a claim that a channel adapter owns or exposes a harness, provider, model, process, or native runtime-session identity.
## Current shared contract
The channel types are exported through `packages/types/src/channel/index.ts` and `packages/types/src/index.ts`. They define a transport-neutral vocabulary, but TypeScript interfaces alone do not prove that every producer or consumer uses that vocabulary.
### DTOs
The current DTO surface is:
| Type | Current shape and boundary |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ChannelMetadataValue` | JSON-safe strings, numbers, booleans, `null`, arrays, and nested objects. |
| `ChannelAttachmentDto` | `id`, `name`, `mimeType`, `url`, and optional `sizeBytes`. |
| `ChannelMessageDto` | `id`, `channelName`, `channelId`, `senderId`, `senderKind`, `content`, `contentKind`, `timestamp`, and `metadata`; `threadId`, `replyToId`, and `attachments` are optional. |
| `ChannelAuthorizedPrincipalDto` | Native `channelUserId`, a `viewer`/`operator`/`admin` role, and an optional `mosaicUserId` for privileged gateway policy. |
| `ChannelBindingDto` | Configuration-owned `bindingId`, `channelName`, `workspaceId`, `channelId`, `logicalAgentId`, and paired `principals`. Credentials are intentionally absent. |
| `ChannelResponseTargetDto` | `channelId` and an optional `threadId`. |
| `ChannelConversationRouteDto` | `bindingId`, `logicalAgentId`, `conversationId`, `channelName`, `authorizationChannelId`, and `responseTarget`. |
| `ChannelIngressDto` | `correlationId`, `nativeMessageId`, an operation, an authorized principal, a normalized message, and a stable route. |
| `ChannelEgressDto` | `correlationId`, a normalized message, and the stable route. |
| `ChannelAdapterHealthDto` | `status` of `connected`, `degraded`, or `disconnected`, with optional `detail`. |
The available operations are `message.send`, `approval.create`, and `session.stop`. The available sender kinds are `user`, `agent`, and `system`; content kinds are `text`, `markdown`, `code`, `image`, and `file`.
### Lifecycle and ports
The shared adapter file currently defines these seams:
```typescript
interface OfficialChannelAdapter {
readonly name: string;
start(): Promise<void>;
stop(): Promise<void>;
health(): Promise<ChannelAdapterHealthDto>;
}
interface ChannelIngressPort {
receive(ingress: ChannelIngressDto): Promise<void>;
}
interface ChannelEgressPort {
send(egress: ChannelEgressDto): Promise<void>;
}
```
`ChannelDeliveryError` currently has only these codes: `invalid_route`, `destination_unavailable`, and `delivery_failed`. The type surface does not define a revoked-auth error code or an executable protocol version `1.0.0`.
### Stable route rule
`ChannelConversationRouteDto` deliberately omits provider, harness, model, process, and native runtime-session fields. The Discord implementation derives its current conversation address as:
```text
<logical-agent-id>:discord:<response-channel-id>
```
and derives its binding address from the configured guild, parent channel, and logical-agent instance. The gateway validates the expected Discord conversation address before dispatch. This is a route-integrity rule, not a claim that the shared DTO is already the gateway's universal session API.
### What is and is not wired today
The Discord class implements both `OfficialChannelAdapter` and `ChannelEgressPort`, and accepts an optional `ChannelIngressPort` dependency. The direct ingress seam is exercised by the Discord tests. However, the gateway host currently registers `IChannelPlugin` objects with only `name`, `start`, `stop`, and optional project provisioning. Its `PLUGIN_REGISTRY` is an array of those lifecycle wrappers; it does not expose `health()`, `ChannelRegistry.healthAll()`, or shared port wiring.
The gateway's current output path is also still Socket.IO event streaming (`agent:start`, `agent:text`, and `agent:end`). No gateway service in the cited implementation produces a `ChannelEgressDto` for a registered adapter. The shared ports are therefore current contracts and a tested Discord seam, not a completed gateway-wide adapter architecture.
## Current Discord compatibility path
Discord is the current reference implementation for the shared contract and the compatibility path. Its behavior is split between native Discord translation in the plugin and gateway-side validation/dispatch.
### Native ingress and authorization
For an inbound Discord message, the plugin currently:
1. Ignores bot-authored messages and messages without a guild.
2. Uses the configured parent text channel as the authorization channel only when the message is in a thread. A normal channel's category parent is not substituted for the channel itself.
3. Applies default-deny guild, channel, and user allowlists.
4. Resolves a configuration-owned binding and paired user role before creating a thread or dispatching to the gateway. `viewer` cannot send turns; approval and stop are admin operations.
5. Applies per-user/channel message and mention-thread rate limits before Discord thread creation or gateway dispatch.
6. Builds a stable route from the configured logical-agent instance and the response channel/thread.
7. Normalizes the authorized turn to `ChannelIngressDto` when a direct `ingressPort` dependency is supplied.
The normalized `ChannelMessageDto` currently includes:
- `channelName: "discord"`;
- the response channel as `channelId`;
- the Discord author as `senderId` and `senderKind: "user"`;
- `markdown` for non-empty text, or `image`/`file` for attachment-only input;
- attachments mapped to `ChannelAttachmentDto`; and
- `metadata` containing `channelMessageId` and `guildId`.
The implementation does **not** currently populate `channelType`, mentions, embeds, or `replyToId` in that normalized metadata. The old page's broader Discord metadata table must not be treated as current behavior.
### Direct shared ingress versus compatibility envelope
When a direct port is present, the plugin calls `ChannelIngressPort.receive()` with the complete normalized ingress DTO. In the current gateway-hosted path, the plugin instead signs a compatibility envelope and emits one of these Socket.IO events:
| Shared operation | Compatibility event | Current envelope boundary |
| ----------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `message.send` | `message` | Correlation ID, native Discord message ID, guild/channel/user IDs, conversation ID, content, optional thread ID, and attachments. |
| `approval.create` | `discord:approve` | The same signed Discord identity and route fields, carrying the approval command. |
| `session.stop` | `discord:stop` | The same signed Discord identity and route fields, carrying the stop command. |
The signature is HMAC-SHA-256 over the ordered envelope payload using the injected Discord service token. The token is used for service authentication and is not part of the protocol payload.
### Gateway validation and dispatch
The gateway exposes the `/chat` Socket.IO namespace. A Discord connection authenticates with `discordServiceToken`; ordinary clients use a BetterAuth session. For Discord service messages, the gateway:
1. Verifies the signed envelope with `DISCORD_SERVICE_TOKEN`.
2. Re-applies the configured guild, channel, and user allowlists.
3. Resolves the configured binding and operation role.
4. Checks that the conversation ID matches the bound logical-agent instance and channel/thread.
5. Rejects a repeated native Discord message ID through the bounded replay protector.
6. Reconstructs a gateway `ChatSocketMessageDto` containing the conversation ID, content, and validated attachments.
7. Uses the configured Discord service principal/tenant for ordinary chat dispatch and the paired `mosaicUserId` for privileged approval/stop policy where required.
8. Selects the trusted `agentConfigId` from the binding and verifies that the provisioned agent name matches the binding's logical-agent instance.
This path is intentionally described as compatibility: the gateway receives a signed Discord envelope and reconstructs chat input; it does not currently receive a complete `ChannelIngressDto` from the host registry.
### Persistence and durability boundary
The Discord `conversationId` is an external route string such as `<logical-agent-id>:discord:<channel-or-thread-id>`. Persisted conversations and messages use UUID conversation IDs. No current route-mapping layer was found that resolves the external route to a generated UUID before ordinary Discord writes. The gateway catches persistence/binding failures and may continue dispatch, so live output does not prove durable history or restart/resume continuity.
The focused cross-surface integration test explicitly pre-enrolls a durable session before exercising control flow. It does not prove that a fresh ordinary Discord message creates durable conversation/message rows. Current architecture claims are therefore limited to authenticated routing and live delivery. Durable Discord continuity requires a route-to-UUID mapping, observable persistence failures, ordinary-ingress enrollment where required, and a fresh-database restart test.
### Thread and conversation behavior
| Inbound case | Current route and side effect |
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Authorized untagged message in a parent channel | Uses the parent channel as the response target; no thread is created. |
| Authorized bot mention in a parent channel | Creates a public thread, or reuses the thread already attached to that message, and routes the response to that thread. |
| Authorized follow-up in an existing thread | Authorizes against the configured parent channel and keeps the existing thread; it does not create a nested thread. |
| `/approve` or `/stop <approval>` | Uses the current parent/thread route and requires an already enrolled durable session; ordinary chat does not prove enrollment. |
| Requested thread creation fails | Does not dispatch the message, because the requested response target cannot be honored. |
### Discord egress and health
`DiscordPlugin.send()` is a typed egress implementation, even though the current gateway does not wire it as a universal `ChannelEgressPort`. It:
- rejects a forged or route-misaligned conversation before looking up a Discord destination;
- rejects a missing destination with `destination_unavailable`;
- chunks text at a Discord-safe 1,900-character boundary;
- retries transient rate-limit, server, and network failures up to three attempts;
- derives one stable nonce per correlation/chunk and sends with Discord's enforced nonce option; and
- does not retry permanent delivery failures.
The plugin reports `connected`, `degraded`, or `disconnected` from Discord client readiness and gateway socket connectivity. The health method is tested without exposing provider or runtime state. Outbound delivery uses Discord's native send operation; code-content wrapping from the former page is not implemented.
Agent output reaches the plugin through the current `agent:start`/`agent:text`/`agent:end` Socket.IO events. The plugin buffers the text by conversation ID and calls its typed Discord egress method when the stream ends.
## Draft: shared adapter registry and gateway wiring
**Status: Draft / unimplemented.**
The gateway does have a startup `IChannelPlugin[]` registry, but that registry is a lifecycle host for the current Discord and Telegram wrappers. It does not register `OfficialChannelAdapter` instances, inject `ChannelIngressPort` and `ChannelEgressPort` through a common gateway service, expose adapter health, or implement a dynamic `ChannelRegistry` with `getAdapter`, `listAdapters`, and `healthAll` semantics.
The former page's claim that adapters are already registered uniformly, or that new adapters can be added without channel-specific gateway branches, is not current. The gateway still has Discord-specific authentication, envelope, approval, stop, replay, and binding branches.
A future implementation may define a registry and host lifecycle, but that work must first specify:
- ownership and injection of ingress and egress ports;
- health and failure semantics;
- binding and credential loading boundaries;
- compatibility behavior for existing Socket.IO clients; and
- tests proving that an adapter cannot bypass gateway authorization or route validation.
Until then, this section is design context only.
## Draft: Telegram shared-contract parity
**Status: Raw legacy adapter exists; shared protocol parity and authenticated gateway participation are unimplemented/unproven.**
The current Telegram plugin is not an `OfficialChannelAdapter` implementation. Its source currently:
- launches a Telegraf bot and a Socket.IO client;
- accepts only messages with a text field and ignores attachment-only messages;
- maps each Telegram chat ID to `telegram-<chatId>`;
- emits a raw `{ conversationId, content, role: "user" }` object rather than `ChannelIngressDto`;
- has no shared DTO import, channel binding, principal/role policy, native message ID, attachment mapping, route validation, or health method; and
- sends plain `sendMessage` responses in chunks, without the former page's claimed MarkdownV2, photo, or document handling.
The Telegram Socket.IO client does not provide the Discord service token or a BetterAuth session in its connection options. The source therefore does not establish participation in the gateway's current authenticated connection path. The package's test script uses `--passWithNoTests`, and no package test file is present in this checkout.
Future Telegram parity is draft work. It would need an explicit identity/authentication boundary, shared ingress normalization, binding and operation policy, route-safe egress, health reporting, and focused tests before this page could describe Telegram as an official shared-contract adapter.
## Draft: Matrix integration
**Status: Draft / unimplemented in the channel protocol.**
No current gateway adapter, shared-port wiring, channel binding, identity resolver, room/conversation persistence boundary, or focused channel tests were found for the Matrix design described by the former page. The old Conduit choice, appservice registration, room and Space mappings, ghost users, encryption defaults, retention jobs, and agent-room behavior are therefore proposals, not current system behavior.
Those details must not be copied into implementation instructions or treated as deployment requirements. A future Matrix effort must independently decide and implement its homeserver/appservice boundary, authentication, route mapping, persistence, authorization, delivery, and tests.
## Draft: channel identity linking
**Status: Draft / unimplemented.**
The shared contract carries an already-authorized `ChannelAuthorizedPrincipalDto`; it does not implement a generic channel-identity database or linking flow. Discord currently uses configuration-owned allowlists and pairings. A pairing may include a `mosaicUserId` for privileged operations, while ordinary Discord chat dispatch uses the configured service principal and tenant in the gateway.
No current evidence establishes the former page's proposed `channel_identities` table, OAuth/deep-link flow, anonymous-principal behavior, persistent Matrix session, or revocation endpoint. Those are not implied by the optional `mosaicUserId` field and must remain planned work until schema, auth, gateway, and adapter implementations exist together.
## Draft: multi-surface conversation multiplexing
**Status: Partial raw chat-session support exists; the proposed channel-protocol fanout architecture is unimplemented.**
The gateway currently tracks client/conversation sessions and emits raw typed Socket.IO chat events. Focused gateway tests verify isolation of concurrent conversation streams sharing one socket. The chat event contract is `ChatMessagePayload` plus `agent:*` events, not `ChannelMessageDto` fanout.
There is no evidence in the cited current path for the former page's complete `ConversationService` plus Valkey pub/sub topology, canonical cross-surface `ChannelMessageDto` persistence, or Matrix fanout. Concurrent stream isolation must not be presented as multi-surface channel multiplexing.
A future multiplexing design must define canonical message ownership, subscription and fanout boundaries, replay/ordering behavior, conflict semantics, and per-surface authorization before it can become architecture guidance.
## Current limitations and version boundary
The following are intentionally not claimed as current protocol policy:
- A semantic protocol version of `1.0.0`. `packages/types/package.json` currently reports package version `0.0.2`, while `packages/types/src/index.ts` exports `VERSION = "0.0.0"`; no migration machinery is present in the cited channel code.
- A generic revoked-auth `ChannelDeliveryError` code. The current union contains only `invalid_route`, `destination_unavailable`, and `delivery_failed`.
- Structured log records with a universal `{ channel, event, ... }` schema. Current plugin logs are string-prefixed.
- Universal adapter health monitoring by the gateway host. Discord exposes health; the `IChannelPlugin` host does not.
- Complete `ChannelEgressDto` delivery from the gateway to every channel. Discord's current gateway egress remains Socket.IO stream events followed by plugin-side delivery.
These limitations are evidence boundaries, not requests to change implementation in this documentation migration.
## Evidence
Current-contract evidence:
- [`packages/types/src/channel/channel.dto.ts`](../../../packages/types/src/channel/channel.dto.ts) — DTOs, enums, route fields, and metadata shape.
- [`packages/types/src/channel/channel-adapter.ts`](../../../packages/types/src/channel/channel-adapter.ts) — adapter lifecycle, ingress/egress ports, and delivery errors.
- [`packages/types/src/channel/index.ts`](../../../packages/types/src/channel/index.ts) and [`packages/types/src/index.ts`](../../../packages/types/src/index.ts) — export surface and current package `VERSION`.
Discord evidence:
- [`plugins/discord/src/index.ts`](../../../plugins/discord/src/index.ts) — native translation, authorization, thread routing, signed compatibility envelope, typed ingress/egress seam, retry, and health.
- [`plugins/discord/src/index.test.ts`](../../../plugins/discord/src/index.test.ts) — direct ingress, routing/thread behavior, authorization ordering, attachments, route-safe egress, retries, chunking, and health.
- [`apps/gateway/src/chat/chat.gateway.ts`](../../../apps/gateway/src/chat/chat.gateway.ts) — `/chat` namespace, service/session authentication, signed-envelope reconstruction, trusted binding selection, and raw stream egress.
- [`apps/gateway/src/chat/chat.gateway-auth.ts`](../../../apps/gateway/src/chat/chat.gateway-auth.ts) — Discord service-token and BetterAuth session validation.
- [`apps/gateway/src/plugin/discord-ingress.security.spec.ts`](../../../apps/gateway/src/plugin/discord-ingress.security.spec.ts) — signature, allowlist, replay, binding, attachment, approval, stop, and logical-agent checks.
- [`apps/gateway/src/__tests__/integration/tess-cross-surface.integration.test.ts`](../../../apps/gateway/src/__tests__/integration/tess-cross-surface.integration.test.ts) — focused control-flow test with explicit durable-session pre-enrollment; not ordinary persistence evidence.
Hosting and compatibility evidence:
- [`apps/gateway/src/plugin/plugin.interface.ts`](../../../apps/gateway/src/plugin/plugin.interface.ts), [`plugin.module.ts`](../../../apps/gateway/src/plugin/plugin.module.ts), and [`plugin.service.ts`](../../../apps/gateway/src/plugin/plugin.service.ts) — current lifecycle-only plugin host.
- [`packages/types/src/chat/events.ts`](../../../packages/types/src/chat/events.ts) — raw Socket.IO chat event contracts.
- [`apps/gateway/src/chat/chat.gateway-redaction.spec.ts`](../../../apps/gateway/src/chat/chat.gateway-redaction.spec.ts) — current per-client/per-conversation stream isolation evidence.
Telegram evidence:
- [`plugins/telegram/src/index.ts`](../../../plugins/telegram/src/index.ts) — current raw Telegraf and Socket.IO behavior.
- [`plugins/telegram/package.json`](../../../plugins/telegram/package.json) — package scripts, including `--passWithNoTests`.
@@ -1,5 +1,9 @@
# Compaction observer revocation and runtime generations
> **Status:** Current contract reference.
> **Audience:** Developer and security reviewer.
> **Evidence:** Mutator-gate and framework portability acceptance suites consume this page; live deployment gaps remain explicitly labeled below.
WI-3 connects Claude and Pi compaction/session lifecycle events to the existing authenticated lease-broker state machine. It does not add a second lease store or let runtime hooks assert identity. Each observer inherits the broker-minted session, resolves the current private runtime generation, and sends the existing `revoke_lease` action over the authenticated Unix socket.
## Observer matrix
@@ -0,0 +1,15 @@
# Architecture Decisions
> **Status:** Current decision index. A decision describes an implemented and accepted boundary; draft proposals belong under `rfcs/` or `docs/plans/`.
## Current decisions
- [Mos runtime portability M1 — logical identity and fencing](mos-runtime-portability-m1.md) — implemented lease, grant, audit, policy, and fencing boundary; connector activation remains held.
Decision pages do not override product requirements, API contracts, or executable behavior. Each page must identify implementation evidence, operational status, and explicit non-goals.
## Related
- [[DEVELOPER-GUIDE/architecture/README|Architecture index]]
- [[PRD|Product requirements]]
- [[ADMIN-GUIDE/operations/mos-connector-lease-operations|Held connector lease operations]]
@@ -0,0 +1,96 @@
# Mos Runtime Portability M1 — Logical Identity and Fencing
> **Decision status:** Current implemented decision (M1).
> **Operational status:** The lease/fencing boundary is implemented and test-covered; connector activation remains held.
> **Audience:** Mosaic developers, gateway/runtime adapter authors, and security reviewers.
> **Authority:** This page records the current M1 boundary. The executable source and tests are authoritative for behavior; [`PRD.md`](../../../PRD.md) remains authoritative for requirements.
## Decision
M1 separates the logical Mosaic agent from any Claude, Pi, Codex, tmux, Matrix, or provider-native session. The core identity is:
```text
(tenant_id, logical_agent_id, binding_id)
```
`logical_agent_id` is a Mosaic logical identifier, not a runtime session identifier. The gateway derives the tenant from authenticated actor scope. The current internal service boundary normalizes the supplied logical-agent identifier and binding; a future public boundary must resolve and authorize those values server-side. Runtime-native session IDs are not part of the lease or execution-grant contract.
A connector is a replaceable holder of authority for one logical binding. It is not the logical agent identity.
## Durable lease model
The additive migration creates `logical_agent_connector_leases` with one unique row per tenant/logical-agent/binding tuple. The current row contains:
- an opaque lease UUID;
- connector ID and normalized allowed scopes;
- a positive decimal fencing epoch exposed by the application and stored as PostgreSQL `bigint`;
- acquisition, heartbeat, expiry, release, and update timestamps.
Initial acquisition is insert-only. An existing active row yields `lease_held`. An expired or released row yields `takeover_required`; ordinary acquisition does not recover it. An authorized takeover compares and swaps the expected epoch, rotates the lease UUID, and increments the epoch atomically. Heartbeat and release match the full identity, binding, connector, lease UUID, and epoch.
### Audit and enforcement precision
`connector_lease_audit_log` is an **application-level append-only contract**: the repository has an insert-only audit path and writes credential-safe lifecycle/rejection metadata. The checked-in schema and migration add a table and indexes, but do not add database triggers, permissions, or other database-level protection against `UPDATE` or `DELETE`. M1 therefore does not claim database-enforced immutability; an append-only guarantee at the database security boundary is a later hardening requirement.
Likewise, identifier, scope, and epoch normalization is performed at the application boundary by the shared types/coordinator and gateway service. The database enforces column types, non-null fields, the lease primary key, and the unique tenant/logical-agent/binding index, but it does not independently enforce all application formats or semantics such as positive epochs, canonical scope ordering/deduplication, or constrained identifier patterns. Database shape must not be mistaken for a second normalization layer.
## Execution grants and TTL boundaries
`ConnectorLeaseCoordinator` issues a short-lived internal grant only after rereading the durable current lease. It enforces hard defense-in-depth maxima of five minutes for leases and thirty seconds for grants; constructor options may only tighten those limits. A grant is bound to tenant, logical agent, binding, connector, lease UUID, scope subset, expiry, correlation ID, and epoch.
The gateway policy and coordinator have deliberately separate TTL responsibilities:
- `ConnectorLeaseService` passes the request's numeric `ttlMs` to policy as `requestedTtlMs` for acquire, takeover, heartbeat, and grant issuance. Identity and scopes are normalized before policy evaluation, but this TTL is a request input, not a coordinator-normalized or effective TTL.
- Release and read policy checks use `null` because they do not request a TTL.
- After policy authorization, the coordinator validates the TTL as a positive safe integer and rejects values above the applicable hard cap (`300000ms` for leases or `30000ms` for grants). It does not silently clamp an over-cap value.
- A policy may impose a stricter duration limit, but it cannot expand the coordinator cap. A policy implementation must validate the requested value itself if its decision depends on a bounded or canonical TTL.
Grant validation occurs immediately before adapter invocation and rereads the durable current row. Validation denies:
- grants not minted by the current gateway process, including cloned or forged objects;
- expired grants or leases;
- released leases;
- stale epochs or replaced connectors/lease UUIDs;
- missing, cross-tenant, cross-agent, or cross-binding leases; and
- scopes not authorized by both the grant and the current lease.
A gateway restart intentionally invalidates process-local grant provenance. The durable lease and epoch survive, but a fresh grant is required after current-lease and policy validation.
## Adapter boundary and activation state
The normalized `ConnectorExecutionContext` and `FencedConnectorAdapter` contract define the future adapter boundary. When a concrete adapter is activated, it must receive the context only after the coordinator's final validation and must propagate the epoch/context to downstream effect boundaries that need to fence races after gateway validation.
That contract is **not evidence of an activated adapter**. The current production module registers the lease repository, service, and deny-all policy. The coordinator and gateway integration tests use test adapters, but current production runtime providers and channel paths do not consume `ConnectorExecutionContext` or call `executeGrant`. No concrete Claude, Pi, Codex, Matrix, tmux, provider, or channel connector is activated by M1.
`ConnectorLeaseService` is the gateway-owned policy surface. It derives tenant scope from authenticated context, records policy denials, and is internal: no M1 connector-lease HTTP controller or administration endpoint is registered. The default `DenyConnectorLeasePolicy` rejects every lease and grant operation until a separately authorized server-side policy is supplied.
## Current implementation evidence
The current boundary is represented by the following checked-in surfaces:
- Contract and application normalizers: `packages/types/src/agent/connector-lease.dto.ts` and its unit specification.
- Coordinator, TTL caps, grant provenance, reread, and fencing checks: `packages/agent/src/connector-lease.ts` and its unit tests.
- Gateway policy, tenant derivation, and default deny wiring: `apps/gateway/src/agent/connector-lease.service.ts` and `agent.module.ts`.
- Durable repository and compare-and-swap mutations: `apps/gateway/src/agent/connector-lease.repository.ts`.
- Schema and additive migration artifacts: `packages/db/src/schema.ts` and `packages/db/drizzle/0016_salty_morlocks.sql`.
- Gateway integration and repository specifications: `apps/gateway/src/agent/connector-lease.integration.test.ts` and `connector-lease.repository.test.ts`.
These references establish the implemented boundary; they do not imply that a production database, connector policy, or concrete connector is currently active.
## Explicit non-goals
M1 does **not** provide or authorize:
1. Production connector activation, channel cutover, or cross-harness failover/rollback E2E.
2. Concrete Claude, Pi, Codex, Matrix, tmux, provider, or channel adapters, or integration of existing runtime providers with this lease context.
3. A connector-lease HTTP API or caller-controlled tenant authority.
4. Exactly-once connector receipts, a side-effect journal, checkpoint/handoff payloads, replay deduplication, or recovery semantics for an external effect that may already have happened.
5. Database-enforced append-only audit rows or database-enforced copies of application identifier/scope/epoch normalization.
6. A claim that gateway validation alone fences an effect after an adapter has crossed the boundary; downstream adapters and effect journals remain later work.
A valid lease or grant is therefore not a claim of exactly-once delivery, production connector readiness, or completed runtime cutover.
## Related contract
- [M1 connector lease operations — held/non-operative](../../../ADMIN-GUIDE/operations/mos-connector-lease-operations.md)
- [MOS-PORT requirements](../../../PRD.md#mos-runtime-portability-workstream-mos-port)
@@ -1,5 +1,9 @@
# Authenticated external lease broker protocol
> **Status:** Current contract reference.
> **Audience:** Developer and security reviewer.
> **Evidence:** Lease-broker acceptance suites consume this page; executable behavior remains authoritative in source and tests.
The compaction-refresh lease broker is a Linux-only, newline-framed JSON protocol over a Unix stream socket. It is runtime-neutral; M1 consumers are limited to Claude and Pi. This is an internal process boundary, not an HTTP API, so it is intentionally absent from OpenAPI.
The broker, never the caller, obtains `(pid, uid, gid)` from kernel `SO_PEERCRED`. It correlates the PID with `/proc/<pid>/stat` field 22 (`starttime`) and mints `session_id` on `register_anchor`. Presence of `session_id` in that request is refused even when its value is `null` or empty. Later requests must originate from the anchor or a descendant. The broker walks parent PIDs to the `(pid,starttime)` anchor and then rereads every walked PID's starttime before accepting the chain.
@@ -1,5 +1,9 @@
# WI-1 lease broker security notes
> **Status:** Current contract reference.
> **Audience:** Developer and security reviewer.
> **Evidence:** The lease-broker implementation and acceptance material cross-check this boundary; deployment-review requirements remain explicitly labeled below.
- Trusted identity comes only from Linux `SO_PEERCRED` plus `/proc` starttime, never request identity fields.
- Descendant authorization is anchored to `(pid,starttime)` and uses a complete second starttime pass to fail closed on disappearance or PID-reuse races.
- Runtime generations are monotonic per anchor; a bump revokes prior-incarnation tokens before persistence commits. WI-3 stores the live generation in an owner-only locked file so same-PID Pi reload/new/resume/fork and Claude resume/clear transitions cannot inherit a VERIFIED lease.
@@ -1,5 +1,9 @@
# Whole mutator-class lease gate
> **Status:** Current contract reference.
> **Audience:** Developer and security reviewer.
> **Evidence:** Runtime launch-guard and mutator-gate tests cross-check this boundary; parser residuals and deployment gaps remain explicitly labeled below.
WI-2 adds the framework-native authorization boundary for Claude (including the supported Claudex overlay) and Pi. Every runtime-reported tool name reaches the lease broker before execution. The gate classifies capabilities by the whole tool class; it never parses a Bash command to decide whether that particular string looks read-only.
## Default-deny policy
@@ -0,0 +1,15 @@
# Architecture RFCs
> **Status:** Current proposal index. RFCs are draft design material and have no operational or implementation authority until an approved decision and implementation evidence supersede them.
## Draft proposals
- [Optional AI egress gateways](optional-ai-egress-gateways.md) — proposed model-egress boundary; LiteLLM and Bifrost are not integrated, and Claudex remains experimental harness tooling.
A draft RFC must not be cited as a supported feature, deployment path, or approved architecture decision. Approved implemented boundaries belong under [`../decisions/`](../decisions/README.md).
## Related
- [[DEVELOPER-GUIDE/architecture/README|Architecture index]]
- [[DEVELOPER-GUIDE/architecture/decisions/README|Architecture decisions]]
- [[PRD|Product requirements]]
@@ -0,0 +1,371 @@
# RFC: Optional AI Egress Gateways
> **Status:** Draft / proposed — not approved, not current, and not integrated.
>
> **Authority:** Non-operative design proposal. This document does not authorize an
> integration, deployment, provider selection, credential flow, or production use.
> It is intentionally an RFC rather than an approved architecture decision.
>
> **Scope:** Optional model/inference egress only. `IProviderAdapter` and
> `AgentRuntimeProvider` are separate boundaries; this RFC does not merge them.
- **Date:** 2026-07-14
- **Related issues:** #754, #755
- **Decision owner:** Mosaic Gateway / provider-adapter architecture
## Context
The emergency Mos continuity path kept Claude Code as the harness and translated
Anthropic Messages traffic to Codex OAuth through a small localhost proxy. That
preserved the existing Claude Discord plugin and transcript, but exposed two
architectural facts:
1. Harness identity, channel entitlement, provider credentials, inference
transport, and runtime sessions are separate concerns.
2. A generic AI gateway could eventually improve model routing, budgets, and
observability, but it must not become Mosaic's identity, authorization,
tenant, connector, or orchestration boundary.
The Tess qualification work also found that provider rebinding is not, by
itself, identity-continuous failover. Any future design still needs a logical
agent identity, durable connector lease/fencing, canonical handoff/checkpoint,
exactly-once receipts, concrete runtime adapters, and cross-runtime rollback
validation.
This page is a reclassified migration of the historical egress-gateway
proposal. The move to `docs/DEVELOPER-GUIDE/architecture/rfcs/` does not promote
it to a decision and does not change runtime behavior.
## Current status and non-goals
This RFC describes a possible future integration boundary. It does **not** say
that any of the candidates below is supported by Mosaic Gateway today:
- **LiteLLM is not integrated.** It is only a candidate for a future model-egress
adapter and remains subject to the prerequisites in this RFC.
- **Bifrost is not integrated.** Its routing, virtual-key, and failover features
are research inputs only, not Mosaic authority.
- **Claudex and `claude-code-proxy` are experimental harness tooling, not
gateway egress.** The `mosaic claudex` path runs a Claude Code harness against
a local translation proxy for evaluation. It is not a Mosaic Gateway egress
integration, does not define the `IProviderAdapter` model contract, and does
not define the `AgentRuntimeProvider` runtime/session contract.
In particular, this RFC does not propose that a generic gateway receive channel
traffic directly, own Mosaic sessions, replace the runtime-provider registry,
or become a second authorization or tenant system. No database, service, or
runtime change is implied by this document.
## Corrected contract boundary
The historical proposal conflated two different adapter families. They must
remain separate.
### `IProviderAdapter`: model and inference egress
`IProviderAdapter` is the model-provider abstraction. Its current contract
covers provider registration, model discovery, provider health, and a future
direct completion stream. `ProviderService` aggregates these adapters and
connects model availability to the Pi `ModelRegistry`.
It does **not** represent an agent process or session. It does not own channel
identity, Mosaic actor or tenant identity, logical-agent ownership, runtime
session IDs, runtime attachments, handoff state, or tool authorization. An
optional LiteLLM, Bifrost, or similar model gateway would therefore be a
candidate for this model-egress path only, after approval and qualification.
### `AgentRuntimeProvider`: runtime and session boundary
`AgentRuntimeProvider` is the runtime-neutral session contract. It covers
runtime capabilities and health plus operations such as listing sessions,
reading session trees, streaming a session, sending a message, attaching or
detaching, and terminating a session. Its `RuntimeScope` is derived from
trusted actor, tenant, channel, and correlation context.
The runtime-provider service performs the gateway-side capability, authorization,
approval, and metadata-only audit boundary before invoking a runtime provider.
It is not a model-egress gateway and must not be substituted for
`IProviderAdapter`. A runtime implementation may use model selection or a model
service internally, but that dependency is an explicit composition between two
contracts; it does not make the runtime provider a model provider or make a
model gateway a session provider.
The checked-in contract and implementation references are:
- [`IProviderAdapter` and provider types](../../../../packages/types/src/provider/index.ts)
- [`ProviderService`](../../../../apps/gateway/src/agent/provider.service.ts)
- [`AgentRuntimeProvider`](../../../../packages/types/src/agent/agent-runtime-provider.ts)
- [`RuntimeProviderService`](../../../../apps/gateway/src/agent/runtime-provider-registry.service.ts)
- [`AgentRuntimeProviderRegistry`](../../../../packages/agent/src/runtime-provider-registry.ts)
### Proposed relationship
If a future implementation is approved, the model and runtime paths remain
parallel and explicitly composed at the Mosaic boundary:
```text
Discord / Matrix / CLI / web
|
v
Mosaic Gateway: authenticated actor + tenant, policy, approvals,
logical agent, connector lease/fence, audit, checkpoints,
idempotency, and side-effect receipts
|
+-----+-----------------------+
| |
v v
model request path runtime/session path
ProviderService / routing RuntimeProviderService
| |
v v
IProviderAdapter AgentRuntimeProvider
| |
v v
optional model-egress native or external runtime/session
gateway (future only) transport
|
v
upstream model/provider
```
The optional egress gateway may receive only a validated model request from the
Mosaic model path. It is not a channel ingress, runtime-session transport,
connector owner, or source of Mosaic identity.
## Draft proposal
This RFC proposes, for review only, that Mosaic could support an optional
model-egress gateway through a dedicated `IProviderAdapter` implementation or
adapter-owned model transport. Any such integration would remain subordinate
to Mosaic Gateway policy and would not add a second runtime/session boundary.
Mosaic Gateway would remain authoritative for:
- authenticated actor and tenant identity;
- logical-agent identity, connector binding, lease epoch, and stale-holder
fencing;
- authorization, approval, and model/tool policy;
- runtime/session access through the separate `AgentRuntimeProvider` boundary;
- audit correlation, redaction, retention, and operational evidence;
- canonical handoff, checkpoint, and recovery state; and
- idempotency keys, operation identity, and durable side-effect receipts.
An optional model-egress gateway must not:
- receive channel ingress directly;
- authorize tools, connector ownership, runtime sessions, or approvals;
- define Mosaic actors, tenants, logical agents, or session identity;
- treat downstream virtual keys as Mosaic principals;
- persist raw Mosaic handoffs, channel credentials, or unredacted telemetry;
- bypass adapter capability negotiation or gateway policy;
- retry or fail over an operation whose side-effect state is ambiguous; or
- silently select an unhealthy or unauthorized provider merely to return a
result.
## Candidate assessment
These dispositions are deliberately non-integrated and do not constitute
approval.
### LiteLLM
**Disposition:** Not integrated; future candidate for a formal
`IProviderAdapter` model-egress prototype only.
Potentially useful features include broad provider routing, virtual keys,
budgets, observability, and OpenAI/Anthropic-compatible surfaces. A future
review must verify, rather than assume, the exact ChatGPT subscription OAuth
flow, supported models, provider terms, token storage, encryption, revocation,
refresh, scope, and incident response.
A prototype would also have to prove streaming, tool calls, reasoning controls,
cancellation, tenant isolation, audit-correlation preservation, retry behavior,
and idempotency. Virtual keys must remain downstream credentials and must not
become Mosaic actors or tenants. Channel ingress and connector credentials
would remain outside LiteLLM.
Source references:
- [LiteLLM ChatGPT subscription provider](https://docs.litellm.ai/docs/providers/chatgpt)
- [LiteLLM providers](https://docs.litellm.ai/docs/providers)
### Bifrost
**Disposition:** Not integrated; future candidate for governance and routing
research, with subscription OAuth compatibility unverified.
Virtual keys, budgets, rate limits, weighted load balancing, and provider
failover may inform a future Mosaic egress design, but they are not Mosaic
authority. A future review must verify Codex/ChatGPT subscription OAuth rather
than assume API-key compatibility, map all policy to server-derived Mosaic
tenants, prove that failover preserves connector leases and approvals, and
redact request/response telemetry before persistence.
Automatic failover must be disabled or constrained whenever policy, approval,
or side-effect state is ambiguous.
Source references:
- [Bifrost overview](https://docs.getbifrost.ai/overview)
- [Bifrost repository](https://github.com/maximhq/bifrost)
### Claudex / `claude-code-proxy`
**Disposition:** Experimental harness overlay and local translation proxy; not
Mosaic Gateway egress and not an approved provider integration.
The reviewed path runs GPT models inside the Claude Code harness through a
local `claude-code-proxy` that translates Anthropic Messages traffic to a
ChatGPT-subscription (Codex OAuth) backend. It is intended for evaluation and
must not be described as a current Mosaic model gateway, an `IProviderAdapter`
implementation, or an `AgentRuntimeProvider` implementation.
Its constraints remain important: it is not a multi-tenant control plane, it
must not own connector leasing or canonical handoff, and local proxy
credentials and listener exposure require isolation. The current Mosaic
launcher documents its experimental status and isolated Claude configuration:
- [`mosaic` claudex documentation](../../../../packages/mosaic/README.md)
- [`claudex` launch composition](../../../../packages/mosaic/src/commands/claudex.ts)
- [`claude-code-proxy`](https://github.com/raine/claude-code-proxy)
### `teremterem/claude-code-gpt-5-codex`
**Disposition:** Historical recipe; not selected and not integrated.
The reviewed repository uses `OPENAI_API_KEY`, tells previously authenticated
Claude users to log out, and documents a Claude Web Search schema
incompatibility. That does not satisfy the subscription-OAuth plus built-in-
channel continuity requirement observed in the Mos cutover.
Source references:
- [Repository](https://github.com/teremterem/claude-code-gpt-5-codex)
- [Environment template](https://github.com/teremterem/claude-code-gpt-5-codex/blob/main/.env.template)
## Prerequisites before approval or integration
No candidate may be integrated, enabled as a Mosaic feature, or treated as a
current supported path until all of the following are complete for the exact
provider, adapter, configuration, and deployed revision.
### 1. Recorded approval and governance
- An architecture decision explicitly approves the model-egress scope and
records that `IProviderAdapter` remains separate from `AgentRuntimeProvider`.
- Product/operations ownership, provider terms review, and independent security
review are recorded against the exact change and provider revision.
- The approval identifies allowed providers, models, regions, data handling,
rollback owner, support status, and a time-bounded experimental or production
phase. This RFC itself is not that approval.
- No candidate is advertised as integrated, current, or production-ready while
the approval record is absent or expired.
### 2. Security and credential controls
- A threat model covers prompt/tool data, model output, streaming, cancellation,
retries, failover, SSRF, endpoint authentication, TLS, egress allowlists,
supply-chain risk, provider terms, and denial-of-service behavior.
- OAuth tokens, API keys, virtual keys, refresh tokens, and proxy credentials
have documented ownership, storage, encryption, rotation, revocation,
expiry, least-privilege scope, and incident-response procedures. Secrets do
not enter prompts, logs, traces, audit payloads, or commits.
- Request, response, tool-schema, and error telemetry is classified and
redacted before persistence or external channel delivery. Retention and
deletion are tenant-scoped.
- The adapter fails closed on missing, expired, revoked, malformed, or
unauthorized credentials and on uncertain provider health. Loopback-only
experimental proxies remain process-isolated and cannot become a gateway
bypass.
### 3. Actor and tenant isolation
- Actor and tenant identity are derived from authenticated Mosaic context at
the Gateway; callers cannot select a tenant, logical agent, credential, or
provider principal by supplying an ID.
- Downstream virtual keys and provider account identifiers are mapped to
server-owned tenant policy. They never grant Mosaic authorization and never
replace RBAC, approvals, connector leases, or runtime scope.
- Model configuration, budgets, rate limits, data residency, credential use,
logs, caches, and failure handling are isolated per tenant. Cross-tenant
reads, writes, cache hits, telemetry, and failover are denied and tested.
- The egress adapter receives only the minimum scoped request needed for the
authorized model operation; channel ingress and runtime/session control stay
in Mosaic-owned boundaries.
### 4. Idempotency and side-effect safety
- Mosaic assigns a durable operation ID and idempotency key before an egress
request can cause a tool or external side effect. The provider gateway must
preserve the correlation and idempotency metadata or be wrapped by an
adapter that does so.
- Durable receipts record request, attempt, provider, outcome, and replay state
without storing unredacted content. Retries and failover are allowed only
when the operation contract proves they cannot duplicate a side effect.
- Ambiguous timeout, disconnect, stream-resume, cancellation, and provider
failover outcomes fail closed until the receipt is reconciled. The egress
gateway must not claim exactly-once behavior that Mosaic has not proven.
- Failure injection demonstrates no duplicate tool, connector, channel, or
external side effect across gateway, adapter, proxy, and provider retries.
### 5. Contract and rollback qualification
- Separate contract tests pass for `IProviderAdapter` model registration,
health, streaming, tools, reasoning controls, cancellation, errors, and
audit correlation.
- Separate `AgentRuntimeProvider` contract tests continue to pass for runtime
capability negotiation, session ownership, approval, attach/detach,
termination, and normalized events. A model-egress candidate must not be
used as evidence for runtime/session compatibility.
- A verified rollback to the prior model path is exercised, including revoked
credentials, unhealthy providers, partial streams, and an adapter version
mismatch.
- The exact deployed revision receives independent code and security review,
with terminal-green CI and operator recovery evidence before any activation.
## Security consequences
- Subscription OAuth grants are high-value credentials and require the same
lifecycle controls as service credentials.
- Downstream virtual keys reduce provider-key exposure but do not establish
user, tenant, agent, or runtime authority.
- Automatic retry or failover can duplicate tool and external side effects
unless Mosaic owns operation IDs, durable receipts, and reconciliation.
- Gateway telemetry can contain prompts, tool schemas, and model output;
redaction and retention policy must apply before persistence.
- A localhost translation endpoint must remain loopback-only, process-isolated,
authenticated where applicable, and outside the Mosaic Gateway ingress path.
- A model-egress gateway outage must not weaken Mosaic authorization, lease
fencing, tenant isolation, approval, or runtime-session boundaries.
## Acceptance before any production use
The following are minimum exit criteria for a future, separately approved
implementation; they are not satisfied by this RFC:
1. The recorded approval and provider-terms review cover the exact integration.
2. Credential lifecycle, revocation, rotation, redaction, and incident drills
are documented and exercised.
3. Tenant-bound authorization remains entirely in Mosaic Gateway and passes
cross-tenant negative tests.
4. `IProviderAdapter` contract tests pass without treating
`AgentRuntimeProvider` tests as substitutes.
5. Failure injection proves no duplicate side effects across retries or
provider failover, with durable idempotency receipts.
6. Streaming, tools, cancellation, reasoning policy, errors, and audit
correlation are qualified for the exact model path.
7. Rollback to the prior provider path is exercised and operator-verifiable.
8. Independent code and security reviews approve the exact deployed revision.
## Follow-up
- #754 owns cross-runtime logical identity, checkpoint, receipt, adapter, and
failover work.
- #755 / PR #757 implements the first logical identity and connector
lease/fencing boundary.
- A later issue may prototype LiteLLM or Bifrost behind the model-provider
boundary only after the recorded approval, security, tenant, idempotency,
contract, and rollback prerequisites are met.
- The page remains **Draft / proposed** until an explicit decision changes its
status. Until then, it must not be cited as an approved architecture,
current integration, or operational runbook.
@@ -0,0 +1,181 @@
# Channel adapters
> **Status:** Current shared channel types plus the Discord reference/compatibility implementation. A shared gateway adapter registry, Telegram parity, and Matrix channel integration remain unimplemented or unproven.
>
> **Last verified:** 2026-08-10 against the source and focused tests listed in [Evidence](#evidence).
>
> **Audience:** Developers implementing or reviewing channel integrations.
The gateway remains the policy and runtime boundary. Channel code translates native events, applies its native admission checks, and delivers normalized ingress/egress; it must not choose a provider, harness, process, or native runtime session on behalf of the gateway.
## Current source boundaries
| Boundary | Current authority | Current claim |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Shared contract | [`packages/types/src/channel/channel.dto.ts`](../../../packages/types/src/channel/channel.dto.ts), [`channel-adapter.ts`](../../../packages/types/src/channel/channel-adapter.ts) | DTOs, ports, lifecycle health, and delivery error codes exported by `@mosaicstack/types`. |
| Discord translation | [`plugins/discord/src/index.ts`](../../../plugins/discord/src/index.ts) | First adapter implementing the shared lifecycle/egress seam, with an optional direct ingress port and a tested Socket.IO compatibility path. |
| Discord behavior | [`plugins/discord/src/index.test.ts`](../../../plugins/discord/src/index.test.ts) | Current allowlist, pairing, role, thread, route, attachment, replay-envelope, egress, retry, and health behavior. |
| Gateway compatibility | [`apps/gateway/src/chat/chat.gateway.ts`](../../../apps/gateway/src/chat/chat.gateway.ts), [`chat.gateway-auth.ts`](../../../apps/gateway/src/chat/chat.gateway-auth.ts) | `/chat` Socket.IO service/session authentication, signed Discord envelope validation, trusted binding selection, raw chat dispatch, and raw stream egress. |
| Host registry | [`apps/gateway/src/plugin/plugin.interface.ts`](../../../apps/gateway/src/plugin/plugin.interface.ts), [`plugin.module.ts`](../../../apps/gateway/src/plugin/plugin.module.ts), [`plugin.service.ts`](../../../apps/gateway/src/plugin/plugin.service.ts) | Lifecycle-only `IChannelPlugin[]` hosting. This is not a universal `OfficialChannelAdapter` registry. |
| Telegram | [`plugins/telegram/src/index.ts`](../../../plugins/telegram/src/index.ts), [`package.json`](../../../plugins/telegram/package.json) | Raw legacy Telegraf/Socket.IO behavior only; no shared-contract or authenticated gateway parity. |
| Matrix | No current channel adapter boundary in the cited implementation | Matrix-related fleet/runtime code is not evidence of a gateway channel adapter. |
Executable source and tests outrank older architecture or quarantine pages. The canonical architecture summary is [`architecture/channel-protocol.md`](../architecture/channel-protocol.md).
## Shared contract
`@mosaicstack/types` currently exports the channel types through [`packages/types/src/channel/index.ts`](../../../packages/types/src/channel/index.ts) and [`packages/types/src/index.ts`](../../../packages/types/src/index.ts).
The lifecycle and port seams are:
```typescript
interface OfficialChannelAdapter {
readonly name: string;
start(): Promise<void>;
stop(): Promise<void>;
health(): Promise<ChannelAdapterHealthDto>;
}
interface ChannelIngressPort {
receive(ingress: ChannelIngressDto): Promise<void>;
}
interface ChannelEgressPort {
send(egress: ChannelEgressDto): Promise<void>;
}
```
The current DTO boundary includes:
- `ChannelMessageDto` for normalized native messages and JSON-safe metadata;
- `ChannelAttachmentDto` for bounded external attachment references;
- `ChannelAuthorizedPrincipalDto` for the already-authorized channel actor and `viewer`/`operator`/`admin` role;
- `ChannelBindingDto` for configuration-owned workspace/channel/logical-agent binding;
- `ChannelResponseTargetDto` for a channel and optional thread;
- `ChannelConversationRouteDto` for binding, logical agent, stable conversation ID, authorization channel, and response target;
- `ChannelIngressDto` for correlation, native message ID, operation, principal, message, and route; and
- `ChannelEgressDto` for correlation, normalized output, and route.
Current operations are `message.send`, `approval.create`, and `session.stop`. Current delivery errors are `invalid_route`, `destination_unavailable`, and `delivery_failed`; there is no shared revoked-auth error code or executable protocol-version contract in this surface.
### Stable route rule
The Discord adapter derives:
```text
<logical-agent-id>:discord:<response-channel-id>
```
The binding address is derived from the configured guild, parent channel, and logical-agent instance. The route intentionally omits runtime provider, harness, model, process, and native runtime-session identifiers. Gateway provider/runtime layers own those identities; durable-session ownership applies only after explicit enrollment and is not implied by the external route.
This is a route-integrity rule, not proof that the gateway has a universal channel session API. A future adapter must derive its route from trusted configuration and native channel/thread identity; it must not accept a caller-selected logical agent or runtime target.
## Current Discord implementation
### Native ingress
`DiscordPlugin` currently:
1. ignores bot-authored and non-guild messages;
2. resolves a thread's actual parent text channel, while leaving normal category parents out of authorization;
3. applies default-deny guild, parent-channel, and user allowlists;
4. resolves configuration-owned `instanceId`/`agentConfigId` bindings and paired-user roles before thread creation or dispatch;
5. applies message and mention-thread limits before side effects;
6. creates/reuses a mention thread or preserves an existing thread target; and
7. maps the event to a `ChannelIngressDto` when an `ingressPort` dependency is supplied.
The normalized message uses `channelName: "discord"`, the response target as `channelId`, `senderKind: "user"`, `markdown` for non-empty text, `image`/`file` for attachment-only input, mapped attachments, and metadata containing the native channel message ID and guild ID. It does not currently claim a universal metadata shape for mentions, embeds, channel type, or replies.
### Socket.IO compatibility path
The gateway-hosted plugin is currently constructed without a direct `ChannelIngressPort` in [`plugin.module.ts`](../../../apps/gateway/src/plugin/plugin.module.ts), so it uses the established compatibility path:
1. `start()` connects to `${DISCORD_GATEWAY_URL}/chat` with `auth.discordServiceToken`.
2. Normal sends emit a signed `message` envelope; approval and stop emit `discord:approve` and `discord:stop` envelopes.
3. The HMAC-SHA-256 signature covers the ordered Discord payload using `DISCORD_SERVICE_TOKEN`.
4. The gateway verifies the service token/signature, allowlists, binding/operation role, stable conversation ID, attachment bounds, and replay key before dispatch.
5. The gateway selects the binding's trusted `agentConfigId` and verifies its name equals the logical-agent instance. It attempts normal conversation binding/persistence before dispatch, but the external route is not a UUID and no route-to-UUID mapping currently proves durable ordinary-chat history.
6. Agent output currently returns as raw Socket.IO `agent:start`, `agent:text`, and `agent:end` events. The plugin buffers the text and calls its typed Discord egress at stream end.
This compatibility path carries enough normalized identity to preserve current security and routing behavior, but it is not a gateway-produced `ChannelIngressDto`/`ChannelEgressDto` flow through a shared host registry. The gateway can continue live dispatch after persistence failure; adapter authors must not claim durable continuity until external routes map to UUID conversations and fresh-message restart tests pass.
### Egress and health
`DiscordPlugin.send()` is a typed `ChannelEgressPort` implementation. It validates the route and message alignment before destination lookup, sends at a 1,900-character boundary, retries transient 429/5xx/network failures up to three times, uses one deterministic enforced nonce per correlation/chunk, and does not retry permanent failures. It reports `connected`, `degraded`, or `disconnected` from Discord client readiness and gateway socket connectivity.
The host wrapper currently exposes only `name`, `start`, `stop`, and optional project provisioning. It does not expose `health()`, inject the direct ingress port, produce `ChannelEgressDto` values from the gateway, or provide `healthAll`/`getAdapter` semantics.
## Adapter authoring boundary
For a future official adapter, preserve these current architectural boundaries:
1. Normalize native messages to the shared DTOs and preserve native message ID, correlation ID, channel/thread identity, attachments, and response target.
2. Enforce native guild/room/channel/user/pairing/role policy before thread/room creation or gateway dispatch.
3. Select the logical agent from trusted configuration; never from a caller-controlled provider, model, harness, or route field.
4. Keep the route stable when the gateway changes runtime provider or harness.
5. Validate egress route and destination before sending; bound chunks, retries, and attachment metadata.
6. Return sanitized errors and expose non-throwing lifecycle health for ordinary disconnected state.
7. Add happy-path and failure-path tests for authorization ordering, replay/idempotency, route integrity, native side effects, egress, reconnect, and health.
These are implementation constraints for future work, not evidence that the missing shared registry already exists.
## Parity status
### Telegram: raw legacy adapter, not shared parity
The current Telegram source:
- launches Telegraf and a Socket.IO client;
- reads `TELEGRAM_BOT_TOKEN` and `TELEGRAM_GATEWAY_URL` through the gateway plugin factory, whose URL default is `http://localhost:14242`;
- accepts text messages only and ignores attachment-only messages;
- maps each Telegram `chat.id` to `telegram-<chatId>`;
- emits a raw `{ conversationId, content, role: "user" }` object rather than `ChannelIngressDto`;
- has no shared DTO import, channel binding, principal/role policy, native message ID, attachment mapping, route-safe egress, or health method; and
- has no package test file in this checkout; its script is `vitest run --passWithNoTests`.
Its Socket.IO connection does not send the Discord service token or a BetterAuth session. A configured `TELEGRAM_BOT_TOKEN` therefore must not be described as an authenticated official channel. Shared Telegram parity requires a separate implementation and focused security/contract tests.
### Matrix: no current channel adapter
No current gateway adapter, shared-port wiring, channel binding, identity resolver, persistence boundary, authentication path, or focused channel test establishes Matrix as a Mosaic channel. Matrix code elsewhere in the repository belongs to other transport/runtime work and must not be promoted into channel-adapter instructions without a separate contract and evidence.
### Shared registry: draft/unimplemented
The current `PLUGIN_REGISTRY` is an array of `IChannelPlugin` lifecycle wrappers. It is not a registry of `OfficialChannelAdapter` instances and does not inject `ChannelIngressPort`/`ChannelEgressPort`, aggregate adapter health, or remove the gateway's Discord-specific auth/envelope/approval/stop/replay branches.
A future registry must specify binding and credential ownership, ingress/egress injection, health/error semantics, compatibility with the existing Socket.IO clients, and tests proving that adapters cannot bypass gateway authorization or route validation before it can be documented as current architecture.
## Safe verification commands
The focused package commands used by the current Discord evidence are:
```bash
pnpm --filter @mosaicstack/types build
pnpm --filter @mosaicstack/types typecheck
pnpm --filter @mosaicstack/discord-plugin typecheck
pnpm --filter @mosaicstack/discord-plugin lint
pnpm --filter @mosaicstack/discord-plugin test
cd apps/gateway && pnpm exec vitest run \
src/plugin/discord-ingress.security.spec.ts \
src/chat/chat.gateway-redaction.spec.ts \
src/__tests__/integration/tess-cross-surface.integration.test.ts
```
These commands do not require starting Gateway, Discord, Telegram, Matrix, a queue, or a database. The Discord package test includes its configured coverage thresholds; the gateway command is a focused Vitest run rather than a claim of full repository integration coverage.
## Evidence
- [`packages/types/src/channel/channel.dto.ts`](../../../packages/types/src/channel/channel.dto.ts) — shared DTOs, operations, route fields, and metadata types.
- [`packages/types/src/channel/channel-adapter.ts`](../../../packages/types/src/channel/channel-adapter.ts) — adapter lifecycle, ingress/egress ports, and delivery errors.
- [`plugins/discord/src/index.ts`](../../../plugins/discord/src/index.ts) — native translation, auth ordering, compatibility envelope, route-safe egress, retry, and health.
- [`plugins/discord/src/index.test.ts`](../../../plugins/discord/src/index.test.ts) — focused Discord contract and behavior tests.
- [`apps/gateway/src/chat/chat.gateway.ts`](../../../apps/gateway/src/chat/chat.gateway.ts) — service/session auth, signed envelope validation, replay, trusted agent selection, and raw stream events.
- [`apps/gateway/src/chat/chat.gateway-auth.ts`](../../../apps/gateway/src/chat/chat.gateway-auth.ts) — timing-safe Discord service-token and BetterAuth session checks.
- [`apps/gateway/src/plugin/discord-ingress.security.spec.ts`](../../../apps/gateway/src/plugin/discord-ingress.security.spec.ts) — gateway security and privileged-operation tests.
- [`apps/gateway/src/__tests__/integration/tess-cross-surface.integration.test.ts`](../../../apps/gateway/src/__tests__/integration/tess-cross-surface.integration.test.ts) — control-flow test with explicit durable-session pre-enrollment; not ordinary Discord persistence evidence.
- [`apps/gateway/src/plugin/plugin.interface.ts`](../../../apps/gateway/src/plugin/plugin.interface.ts), [`plugin.module.ts`](../../../apps/gateway/src/plugin/plugin.module.ts), and [`plugin.service.ts`](../../../apps/gateway/src/plugin/plugin.service.ts) — lifecycle-only host registry.
- [`plugins/telegram/src/index.ts`](../../../plugins/telegram/src/index.ts) and [`plugins/telegram/package.json`](../../../plugins/telegram/package.json) — raw Telegram behavior and no-test script.
- [Canonical channel protocol architecture](../architecture/channel-protocol.md) — shared contract and explicit current/draft boundary.
- [Discord ingress security](../../ADMIN-GUIDE/security/discord-ingress.md) — administrator-facing config and auth claims.
- [Discord conversations](../../USER-GUIDE/workflows/discord-conversations.md) — user-facing routing behavior.
- [Developer Guide](../README.md)
@@ -0,0 +1,292 @@
---
title: Lease-broker operations
type: runbook
audience: developer
status: current
source_of_truth: false
---
# Lease-broker operations
> **Status:** Current for repository inspection and test verification. Live broker
> startup, recovery, lease mutation, service management, and cleanup remain
> **held/non-operative**.
>
> **Operational authority:** This page authorizes only the non-mutating
> inspection and test commands in [Safe static inspection](#safe-static-inspection).
> It does not authorize starting a daemon or systemd unit, connecting to a live
> socket, invoking recovery, promoting or revoking a lease, executing a
> consequential runtime tool, or changing a database.
The lease broker is a Linux-only internal process boundary. Its executable
behavior is authoritative in the [shipped broker daemon](../../../packages/mosaic/framework/tools/lease-broker/daemon.py)
and tests, not in this page. The current architecture references are:
- [Broker protocol](../architecture/lease-broker-protocol.md) — framing,
kernel identity, ancestry, generations, persistence, and state transitions.
- [Lease-broker security](../architecture/lease-broker-security.md) — trust
boundaries, filesystem hardening, observer behavior, and residuals.
- [Whole mutator-class gate](../architecture/mutator-class-gate.md) — default
deny, launch choke points, and broker-owned promotion order.
- [Compaction revocation](../architecture/compaction-revocation.md) — Claude
and Pi lifecycle observers, generation fencing, and the bounded residual.
## Safe static inspection
These are the only operative commands documented here. They inspect checked-in
files or run isolated tests; they do not start a user service, activate a
runtime, connect to PostgreSQL, or mutate repository/product state.
### Source and launch inventory
From the repository root, inspect the current implementation and its permanent
runtime-launch inventory:
```bash
find packages/mosaic/framework/tools/lease-broker packages/mosaic/src/lease-broker packages/mosaic/src/mutator-gate -maxdepth 1 -type f -print | sort
python3 packages/mosaic/framework/tools/lease-broker/check-runtime-launches.py --root . --json
```
The launch inventory is a static completeness guard. A clean result means the
checked-in production launch sites are classified by the guard; it does not
prove that a broker, runtime, or service is running.
### Safe unit and static contract tests
The focused standard-library tests can be run directly:
```bash
python3 packages/mosaic/src/lease-broker/daemon_deadline_unittest.py
python3 packages/mosaic/src/lease-broker/normative_fragments_unittest.py
python3 packages/mosaic/src/lease-broker/receipt_challenge_unittest.py
python3 packages/mosaic/src/lease-broker/context_recovery_unittest.py
python3 packages/mosaic/src/lease-broker/state_store_unittest.py
python3 packages/mosaic/src/lease-broker/framework_skill_portability_unittest.py
python3 packages/mosaic/src/mutator-gate/runtime_tools_unittest.py
python3 packages/mosaic/src/mutator-gate/runtime_launch_guard_unittest.py
python3 packages/mosaic/src/mutator-gate/version_coupling_unittest.py
```
`recovery_runtime_unittest.py` and `recovery_b1_adversarial_unittest.py` are
also safe when run as tests: they use private temporary daemons, sockets, and
fixtures, never the installed user service or a model stream. They are not
operator recovery instructions:
```bash
python3 packages/mosaic/src/lease-broker/recovery_runtime_unittest.py
python3 packages/mosaic/src/lease-broker/recovery_b1_adversarial_unittest.py
```
The [lease-broker Vitest acceptance suite](../../../packages/mosaic/src/lease-broker/lease-broker.acceptance.spec.ts)
and [mutator-gate Vitest acceptance suite](../../../packages/mosaic/src/mutator-gate/mutator-gate.acceptance.spec.ts)
provide additional private-fixture coverage. Test-created child processes and
Unix sockets are disposable test fixtures, not live-service authority.
## Current implementation facts
### Protected paths and persistence
The broker and supervisor source establish these invariants:
| Object | Current contract |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Parent directory | The parent containing the socket and state must already exist and have exactly mode `0700`. |
| Broker socket | The daemon refuses an existing path or symlink, binds a new Unix socket, sets it to `0600`, and removes only the inode it created during normal shutdown. |
| State file | A present state file must be a regular, non-symlink file protected as mode `0600`, no larger than 4 MiB, and valid state-version-1 JSON. Corrupt, incompatible, oversized, or unsafe state refuses startup. |
| State writes | Changes are serialized through a mode-`0600` temporary file, complete-write loop, `fsync`, atomic replace, and parent-directory `fsync`. Post-replace durability uncertainty poisons the store and terminates service processing. |
| Volatile authority | `VERIFIED` leases are not restored as live authority after broker restart. Persisted session identity and valid pending-token state are separate from volatile lease state. |
| Runtime generation | `launch-runtime.py` creates `generation-<broker-session>.state` beside the socket. It is an owner-only, locked, monotonic generation file; unsafe, non-regular, oversized, or non-private state fails closed. |
The resolved socket path is, in order: explicit
`MOSAIC_LEASE_BROKER_SOCKET`, `$XDG_RUNTIME_DIR/mosaic-lease/broker.sock`, or
`/run/user/<uid>/mosaic-lease/broker.sock`. The state file is `state.json` next
to that resolved socket. The production observer transport is a separate
`receipt-observer.sock` by default; `--test-observer-file` is a private test
fixture option, not a production deployment path.
The supervisor implementation can materialize a user unit, wrapper, and
co-located daemon sources under caller-supplied paths, but
[`applyBrokerSupervisor`](../../../packages/mosaic/src/lease-broker/broker-supervisor.ts)
never runs `systemctl`, starts `daemon.py`, or enables the unit. The checked-in
unit and [`start-lease-broker.sh`](../../../packages/mosaic/framework/tools/lease-broker/start-lease-broker.sh)
are therefore implementation inputs, not live activation authority for this
page.
### Protocol and identity boundary
The protocol accepts one UTF-8 JSON object followed by one newline, capped at
64 KiB. The client must half-close its write side after the newline and before
waiting for the response (`shutdown(SHUT_WR)` for POSIX clients or
`socket.end()` for Node). Unterminated, multiple, delayed-second, malformed,
oversized, or deadline-exceeded requests fail closed. This is an internal Unix
socket protocol, not an HTTP/OpenAPI endpoint.
The broker obtains `(pid, uid, gid)` from kernel `SO_PEERCRED`, binds the
session to the anchor's `/proc/<pid>/stat` starttime, and revalidates every
walked ancestor's starttime. A caller cannot choose `session_id`; a sibling or
unrelated process cannot authenticate with another process's session. A higher
runtime generation replaces the prior incarnation and revokes its tokens and
lease authority; a lower generation is stale.
### Lease and tool authorization
The broker is the sole lease writer. The effective default-deny policy is:
- Claude read-only classes: `Read`, `Grep`, `Glob`, `Ls`, and `Find`.
- Pi read-only classes: `read`, `grep`, `find`, and `ls`.
- Both runtimes expose the fixed `mosaic_context_recover` identity as the
constrained recovery exception.
- Every other built-in, unknown, custom, MCP, shell, edit, write, deployment,
provider, or filesystem mutator is consequential and is denied while the
session is not `VERIFIED`.
The normal broker transition is revoke-first and promote-last:
1. `begin_verification` authenticates the broker-minted session and current
generation, revokes existing authority and pending tokens, validates the
exact source construction and binding, and enters a pending state.
2. The receipt challenge and binding are broker-generated. A trusted observer
must provide the exact current-cycle assistant entry; caller-supplied
`latest_assistant_message` is rejected on the public broker socket.
3. Promotion consumes the evidence-backed one-time token before volatile
`VERIFIED` becomes visible. A token or receipt cannot be replayed against a
later cycle.
4. `revoke_lease`, generation replacement, broker restart, or monotonic TTL
expiry removes consequential-tool authority. TTL is positive and capped at
300 seconds.
`launch-runtime.py` is the register-before-`exec` choke point for Claude and
Pi. It performs the activation-capability version check, registers the anchor,
creates the private generation file, exports the broker identity to descendants,
and only then executes the requested runtime. Registration, capability,
generation-file, broker-reply, or `exec` failure denies launch. Claude's raw
`--dangerously-skip-permissions` flag is owned by this wrapper; callers request
only its semantic dangerous mode.
Claude's all-tools `PreToolUse` hook and Pi's `tool_call` handler submit the
runtime-reported tool name to the gate. The gate does not inspect a shell string
to decide that one command is safe. Missing identity, malformed input or reply,
timeout, broker unavailability, unsafe generation state, and denial all fail
closed.
### Lifecycle and recovery boundaries
Claude and Pi lifecycle observers use the same authenticated session and
broker state machine. Compaction revocation and same-PID replacement behavior
are summarized below:
| Runtime event | Current source-backed behavior |
| -------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| Claude `PreCompact` | Revoke before compaction; a failed hook blocks the lifecycle transition. |
| Claude `SessionStart` matcher `compact` | Revoke again after compacted context starts. |
| Claude `SessionStart` matcher `resume\|clear` | Bump the private generation, then revoke the replacement incarnation. |
| Pi `session_before_compact` | Revoke before compaction; failure cancels the transition. |
| Pi `session_compact` then first `context` | Run one independent post-compaction revoke; a failed post-observer locally blocks later tools until retry. |
| Pi `session_start` reason `reload`, `new`, `resume`, or `fork` | Bump the private generation before revoking and reusing the replacement session. |
| Fired observer with unavailable broker | Advance the private generation as a local fence and return failure; do not continue consequential work. |
The [source-resident context-refresh skill](../../../packages/mosaic/framework/skills/mosaic-context-refresh/SKILL.md)
is a thin adapter over `recover-context.py`. Recovery begins with a validated
normative-fragment construction, broker-side revoke-first transition, and a
fresh `PENDING_DELIVERY` challenge. The trusted observer records only finalized
Claude Stop-hook or Pi `message_end` content. Completion supplies no receipt or
challenge argument; the broker obtains its current challenge, checks the exact
observed entry, consumes evidence, and promotes last. Recovery does not accept
normal-path receipt text as replayable authority.
A receipt is only a delivery/liveness prerequisite. It is not a safety,
obedience, comprehension, or residency proof. Absent, malformed,
prefix-truncated, and observably mutated terminal receipts do not promote. A
tail-preserving middle drop is explicitly not receipt-detectable and remains a
server-side/T-C residual.
### Named residual
If both compaction observers are missed while a lease remains unexpired,
consequential tools are **ALLOWED** inside the bounded residual stale window.
The mutator gate makes no within-window action-count or timing claim. After the
monotonic TTL expires, the next consequential tool is **DENIED**. This is
separate from a fired observer that cannot reach the broker, which fails closed
through lifecycle cancellation, a generation fence, and/or the Pi local latch.
Protected-branch controls and required review/CI remain the irreducible
server-side backstop for the T-C total-hook-miss boundary.
## Held future procedure — non-operative
There is no current command authority for the following live procedures. The
sequence below records source-backed intent for a separately approved
activation/recovery work package; it must not be copied into an operator shell.
### Startup and restart outline (held)
1. An activation owner would first materialize the exact framework unit,
wrapper, and co-located source copies, then verify the resolved parent,
socket, state, observer, and generation paths and their no-symlink/private
posture.
2. The approved supervisor would start the daemon only after confirming that
the exact socket path is not owned by another process. `READY` from the
daemon and a live Unix socket would be health evidence; a unit file alone
would not be healthy.
3. A broker crash would require preserving the state file, identifying the
socket owner, and making an explicit restart decision. Restart intentionally
clears volatile `VERIFIED` leases; it is not a way to restore authority.
4. A leftover socket would be handled only after the owning service is
confirmed stopped and the exact path is deliberately reviewed. This page
supplies no deletion, `systemctl`, enablement, or start command.
The source's `daemon.py` argument parser and the user unit show how a future
activation is wired, but neither source file grants this page authority to
invoke that wiring. `applyBrokerSupervisor` is materialization only; the
supervisor source explicitly leaves enable/start as a separate held step.
### Recovery outline (held)
The future adapter flow is: runtime supplies validated construction and current
non-negative epochs; broker performs `begin_recovery` revoke-first and returns
one fresh receipt; the adapter delivers that exact receipt; the authenticated
observer records the finalized assistant entry; and the adapter requests
completion without presenting receipt text or a challenge. Any absent,
malformed, stale, duplicated, or mismatched observation leaves the session
`UNVERIFIED`; retry starts a new recovery cycle.
Claude's adapter is restricted to the exact literal recovery argv shape checked
by the gate. Pi uses only the registered `mosaic_context_recover` tool; Pi
`bash` and all other tools remain gated. Do not manually invoke the revoker to
restore authority, send assistant text through the public broker request, or
reuse a normal-path receipt. The [context-refresh skill](../../../packages/mosaic/framework/skills/mosaic-context-refresh/SKILL.md)
and its [runtime boundary tests](../../../packages/mosaic/src/lease-broker/recovery_runtime_unittest.py)
are references for future adapter qualification, not an active operator route.
### Lease, mutator, and incident handling outline (held)
- There is no supported operator CLI or HTTP endpoint for sending raw
`begin_verification`, `promote_lease`, `revoke_lease`, or `authorize_tool`
requests. Do not hand-craft JSON frames, mint tokens, replay receipts, or
treat a successful read-only authorization as a promotion.
- After a runtime exits, its generation file may be removed only after an
approved check establishes that no process for that broker-minted session
remains. Retain stale files during incident analysis; they carry no lease
authority by themselves.
- Corrupt, oversized, symlinked, or non-regular state must be preserved for
review and not overwritten in place. Establishing new state is an explicit
operational decision that invalidates prior sessions and tokens; no recovery
command is supplied here.
- Directory `0700` and socket/state `0600` are same-principal hardening only.
They do not prevent the same UID from unlinking and replacing a socket. A
stronger deployment needs an external protected proxy, ACL, or service
boundary that preserves the peer identity required by `SO_PEERCRED` and
ancestry checks. No such deployment procedure is current here.
## Related source and tests
- [Broker daemon](../../../packages/mosaic/framework/tools/lease-broker/daemon.py)
- [Register-and-exec launcher](../../../packages/mosaic/framework/tools/lease-broker/launch-runtime.py)
- [Mutator gate](../../../packages/mosaic/framework/tools/lease-broker/mutator-gate.py)
- [Recovery command](../../../packages/mosaic/framework/tools/lease-broker/recover-context.py)
- [Lease broker acceptance tests](../../../packages/mosaic/src/lease-broker/lease-broker.acceptance.spec.ts)
- [Mutator gate acceptance tests](../../../packages/mosaic/src/mutator-gate/mutator-gate.acceptance.spec.ts)
- [Pi lifecycle tests](../../../packages/mosaic/src/mutator-gate/pi-compaction-lifecycle.spec.ts)
This migration changes documentation placement and verified wording only. It does
not start or change the broker, a runtime, systemd, PostgreSQL, or any other
service.
+221
View File
@@ -0,0 +1,221 @@
# Mosaic Stack Documentation
This directory is the canonical home for Mosaic Stack product, architecture, API, operations, and delivery documentation.
This file is the **documentation contract**. It defines where information belongs, which files are authoritative, how pages connect, and how agents must maintain the documentation set. The structure below is the target structure for the documentation migration; existing content is not considered migrated until it has been classified and moved deliberately.
## Start here
Choose the path that matches your purpose:
- **Understand the product:** start with [`PRD.md`](PRD.md), then use the user guide and the architecture overview.
- **Use Mosaic Stack:** use `USER-GUIDE/`.
- **Install, configure, deploy, or recover Mosaic Stack:** use `ADMIN-GUIDE/`.
- **Change or extend the codebase:** use `DEVELOPER-GUIDE/`.
- **Integrate with the gateway:** use `API/`.
- **Find a page or follow the documentation graph:** use [`SITEMAP.md`](SITEMAP.md).
- **Understand active delivery state:** read [`TASKS.md`](TASKS.md), subject to its single-writer policy.
The root README is an atlas and authoring guide, not a replacement for the user, administrator, developer, or API books.
## Canonical directory structure
The following is the complete target structure. Directories and pages may be created incrementally, but new documentation must use these locations.
```text
docs/
├── README.md # this documentation contract and atlas
├── PRD.md # canonical requirements source
├── TASKS.md # active orchestrator task rollup
├── SITEMAP.md # complete navigation index
├── .obsidian/ # optional Obsidian vault metadata only
├── USER-GUIDE/ # end-user documentation
│ ├── README.md # user-book index
│ ├── getting-started/ # first install/use and quickstarts
│ ├── concepts/ # user-facing concepts and terminology
│ ├── workflows/ # task-oriented user procedures
│ └── troubleshooting/ # user-visible failures and fixes
├── ADMIN-GUIDE/ # operator and administrator documentation
│ ├── README.md # admin-book index
│ ├── installation/ # installation and prerequisites
│ ├── configuration/ # configuration and environment
│ ├── deployment/ # deployment topologies and rollout
│ ├── operations/ # routine operation and observability
│ ├── security/ # auth, RBAC, secrets, and security controls
│ └── recovery/ # incident response, backup, and recovery
├── DEVELOPER-GUIDE/ # contributor and maintainer documentation
│ ├── README.md # developer-book index
│ ├── architecture/ # system model and technical design
│ │ ├── README.md # architecture index
│ │ ├── system-overview.md # platform boundary and major flows
│ │ ├── component-map.md # apps, packages, plugins, and dependencies
│ │ ├── data-flow.md # data, event, and control-plane movement
│ │ ├── security-model.md # trust boundaries and authority model
│ │ ├── decisions/ # ADRs and approved design decisions
│ │ └── rfcs/ # proposals and protocol RFCs
│ ├── packages/ # package- and application-level guides
│ ├── local-development/ # local setup and safe development routes
│ ├── testing/ # test strategy and verification workflow
│ ├── contributing/ # contribution and review workflow
│ └── integrations/ # plugin, provider, and adapter authoring
├── API/ # machine- and human-readable API contract
│ ├── README.md # API documentation index
│ ├── OPENAPI.yaml # canonical OpenAPI contract
│ └── ENDPOINTS.md # endpoint, auth, permission, and error index
├── assets/ # diagrams and documentation media
├── reports/ # evidence and findings; never normative by itself
│ ├── code-review/ # review reports
│ ├── documentation/ # documentation audits and checklists
│ ├── qa/ # QA and verification reports
│ ├── security/ # security reviews and threat evidence
│ └── deferred/ # unresolved or explicitly deferred findings
├── tasks/ # archived task snapshots and learnings
├── plans/ # approved design and implementation plans
├── scratchpads/ # active task-specific working notes
├── releases/ # release notes and compatibility notes
├── archive/ # superseded but intentionally retained docs
└── _old_structure/ # temporary read-only migration quarantine
```
### Directory rules
- `.obsidian/` is optional tool metadata. It is not a content directory. Do not put Markdown pages, reports, plans, task notes, or source-of-truth files there.
- `USER-GUIDE/`, `ADMIN-GUIDE/`, and `DEVELOPER-GUIDE/` are books. Each book must have a `README.md` that links to every chapter and page in that book.
- Each chapter is a directory for one topic area. Each page should cover one concern or workflow.
- `API/OPENAPI.yaml` is the API contract. `API/ENDPOINTS.md` explains details that OpenAPI cannot fully express.
- `reports/`, `tasks/`, `plans/`, `scratchpads/`, `releases/`, and `archive/` are artifact boundaries, not alternative guide books.
- `_old_structure/` is temporary migration quarantine. It is read-only, is not current documentation, and is never a destination for new work.
- `docs/mosaic-stack/` is retired as a content boundary. Do not create new files there. Cross-cutting architecture belongs in `DEVELOPER-GUIDE/architecture/` and navigation belongs here and in `SITEMAP.md`.
- Do not add miscellaneous Markdown files directly under `docs/`. The permitted root files are `README.md`, `PRD.md`, `TASKS.md`, and `SITEMAP.md`; all other content belongs in a scoped directory.
## Where agents must place documents
Classify a document by its primary reader and purpose before creating it. Use this matrix instead of guessing from an existing filename.
| Content | Required location | Examples |
| -------------------------------------------------------------------------- | ---------------------------------------------------------------------- | --------------------------------------------------------- |
| Documentation contract and top-level map | `docs/README.md` | Folder rules, source-of-truth policy, authoring workflow |
| Product requirements and acceptance criteria | `docs/PRD.md` or an explicitly scoped workstream PRD | Objectives, scope, requirements, acceptance criteria |
| Active orchestrator task state | `docs/TASKS.md` | Milestone/task rollup; single writer is the orchestrator |
| Documentation navigation | `docs/SITEMAP.md` and the relevant book `README.md` | Page indexes and reader paths |
| User-visible workflow or troubleshooting | `docs/USER-GUIDE/<chapter>/` | Chat, projects, task workflows, user setup |
| Installation, configuration, deployment, operations, security, or recovery | `docs/ADMIN-GUIDE/<chapter>/` | SSO, tiers, secrets, health checks, incident runbooks |
| Architecture, component map, data flow, package design, ADR, or RFC | `docs/DEVELOPER-GUIDE/architecture/` or its relevant developer chapter | System design, protocol decisions, package contracts |
| Local development, testing, contribution, or integration authoring | `docs/DEVELOPER-GUIDE/<chapter>/` | Setup, test commands, plugin development |
| HTTP/WebSocket API contract | `docs/API/OPENAPI.yaml` and `docs/API/ENDPOINTS.md` | Paths, schemas, auth, permissions, errors |
| Diagram, screenshot, or other documentation media | `docs/assets/` or an owning chapter's asset directory | Architecture diagrams, workflow images |
| Approved design or implementation plan | `docs/plans/` | Design docs and task-by-task execution plans |
| Active task working notes and verification log | `docs/scratchpads/<task-id>-<slug>.md` | Assumptions, progress, commands, evidence, blockers |
| Code review, QA, audit, security, or deferral evidence | `docs/reports/<category>/` | Review findings, test reports, security evidence |
| Archived task snapshot or orchestrator learning | `docs/tasks/` | Closed task breakdowns and retained learnings |
| Release notes or version-specific compatibility information | `docs/releases/` | Release summaries, upgrade notes, deprecations |
| Superseded documentation that must remain discoverable | `docs/archive/` | Historical guides, retired proposals, old mission records |
If content seems to fit multiple locations, choose one canonical home and link to it from the other relevant indexes. Do not create copies merely to satisfy multiple audiences.
## Source-of-truth and precedence
Use these rules when documents disagree:
1. **Requirements:** `PRD.md` is the project requirements source. A scoped PRD may add detail, but must link to and remain consistent with the root PRD.
2. **Active work state:** `TASKS.md` is the active orchestrator rollup. Workers read it; they do not rewrite its status or schema unless the orchestrator authorizes that change.
3. **API:** `API/OPENAPI.yaml` is the machine-readable API contract. `ENDPOINTS.md` is the human index and may explain constraints not represented by OpenAPI.
4. **Current behavior:** guide pages describe verified current behavior. If implementation changes, update the affected guide in the same logical change set.
5. **Architecture and decisions:** approved decisions under `DEVELOPER-GUIDE/architecture/decisions/` and RFCs explain why the system has its current shape. They do not silently override the PRD or API contract.
6. **Evidence:** reports record what was reviewed, tested, or deferred. They are evidence, not a substitute for current requirements or operational instructions.
7. **Plans:** plans describe intended work. After delivery, update the canonical guide, contract, or decision page rather than treating the plan as the current behavior.
8. **Scratchpads:** scratchpads are working memory and verification records. They are not product documentation and must not become hidden requirements.
9. **Archive:** archived pages are historical. Every retained page should identify its status and replacement, if one exists.
10. **Code is authoritative for executable behavior:** documentation must not claim commands, paths, APIs, or safety properties that the current code and tests do not support. When the desired behavior differs from current behavior, record the desired behavior in the PRD or an approved design and label operational procedures as held/non-operative when necessary.
## Page conventions
Every canonical page should:
1. Use a descriptive lowercase kebab-case filename, except for established root control files and required API filenames.
2. Cover one concern, concept, decision, or workflow.
3. Start with a clear title and a short purpose statement.
4. Identify its audience and lifecycle status when it is more than a simple index.
5. State prerequisites, dependencies, and source-of-truth references.
6. Mark examples and commands as current, illustrative, held, or non-operative when that distinction matters.
7. Include an owner or maintenance responsibility for operationally sensitive content.
8. Link to the relevant book index and related canonical pages.
Recommended front matter for canonical pages:
```yaml
---
title: Human-readable page title
type: guide
audience: developer
status: current
source_of_truth: false
---
```
Allowed `type` values include `guide`, `concept`, `reference`, `decision`, `rfc`, and `runbook`. Allowed `audience` values are `user`, `admin`, `developer`, and `all`. Allowed `status` values are `current`, `draft`, `deprecated`, and `historical`.
Indexes may omit front matter when their purpose is self-evident. A page with normative authority must explicitly identify the authority it owns and the boundaries of that authority.
## Obsidian and link conventions
Mosaic Stack documentation is compatible with Obsidian without making Git-hosted navigation unusable.
- Use Obsidian wikilinks for graph-oriented internal relationships, for example `[[DEVELOPER-GUIDE/architecture/component-map|Component map]]`.
- Use normal relative Markdown links in `SITEMAP.md` and book `README.md` indexes so links render on Gitea/GitHub and other Markdown hosts. Obsidian resolves these links too.
- Use `Related`, `Depends on`, and `Referenced by` sections when a page has meaningful relationships to other pages.
- Use wikilink heading targets when a specific section matters, for example `[[DEVELOPER-GUIDE/architecture/system-overview#Gateway boundary|Gateway boundary]]`.
- Omit `.md` in wikilinks. Use an alias when the path is not a readable label.
- Use standard Markdown links for external URLs, source files, commands, and API paths.
- Link to stable repository-relative paths, not temporary branches, line numbers, or machine-local paths.
- Every current canonical page must be reachable from a book index or `SITEMAP.md`; do not create orphan pages.
- Do not use `_old_structure/` links as current navigation. Historical references must explain why the page is retained and point to its replacement.
## Authoring workflow for agents
For every documentation change:
1. **Search first.** Look for an existing page, requirement, report, task, or scratchpad before creating a new file.
2. **Classify.** Choose the primary audience, content type, lifecycle status, and source-of-truth role.
3. **Choose the canonical home.** Apply the placement matrix; do not place content in the docs root or `docs/mosaic-stack/`.
4. **Write one concern.** Keep the page focused and link to existing pages instead of copying them.
5. **Connect the page.** Add it to the owning book index and `SITEMAP.md`; add `Related`, `Depends on`, or `Referenced by` links where useful.
6. **Record non-trivial work.** Create or update `docs/scratchpads/<task-id>-<slug>.md` with objective, assumptions, progress, commands, risks, and evidence. Active `docs/TASKS.md` changes remain under its single-writer policy.
7. **Verify claims.** Check commands, paths, API schemas, permissions, and status against source and tests. Label held or non-operative procedures explicitly.
8. **Format and review.** Run the repository's Markdown formatting check, inspect links and headings, and review the diff for stale paths or duplicated authority.
9. **Commit a coherent change.** Keep documentation changes with the related code/API/operation change when applicable, and do not include unrelated staged files.
## Migration policy
The initial structure pass defined the target structure without moving or rewriting the existing documentation set. Subsequent migration slices may move or rewrite classified pages deliberately, with repository references and indexes updated together.
- Treat `_old_structure/` as read-only migration quarantine. Do not add new content there.
- Treat the current root-level legacy pages (`openapi-tess.yaml` and the task/mission documents) as migration backlog, not permission to create more root files. The former empty `QUICKSTART.md` placeholder now lives as `USER-GUIDE/getting-started/quickstart.md`; the verified SSO runbook lives under `ADMIN-GUIDE/security/`; historical evidence such as the P8-003 performance report belongs under `reports/qa/`.
- Candidate destinations include `USER-GUIDE/getting-started/`, `ADMIN-GUIDE/security/`, `DEVELOPER-GUIDE/architecture/`, `API/`, `tasks/`, `plans/`, and `archive/`; classify each page before moving it.
- Existing source code and tests still reference legacy paths such as `docs/fleet/` and `docs/federation/`. Update those references deliberately as part of the relevant migration slice; do not delete a referenced path blindly.
- When a page is moved, update all repository links, source comments, tests, book indexes, and `SITEMAP.md` in the same logical change.
- Preserve historical evidence in `reports/`, `tasks/`, `releases/`, or `archive/` instead of mixing it into current guides.
- Remove the empty `docs/mosaic-stack/` boundary only after confirming no source, test, or documentation reference requires it.
- Remove `_old_structure/` only after migration verification proves that current navigation and required historical retention are intact.
## Current transition state
Classified migration is active and the audience books now contain verified current pages. During this transition:
- The target directories listed above remain the placement contract; some planned chapters are not populated yet.
- `docs/_old_structure/` remains available for migration evidence but is not current documentation or command authority.
- Root control and API artifacts remain until their authority and destination decisions are approved.
- `docs/SITEMAP.md` contains only resolvable current navigation plus a non-linked summary of authority-gated migration groups.
- No external publishing platform is assumed. The canonical source remains this repository under `docs/`.
## Current migration boundaries
- Do not bulk-promote or bulk-rewrite quarantined documentation; classify and verify each coherent slice.
- Do not rewrite product requirements, orchestrator-owned task state, mission status, or the API contract without the required authority decision.
- Do not create a documentation website or publishing pipeline as part of content migration.
- Do not treat Obsidian metadata as product or project source of truth.
+63 -83
View File
@@ -1,103 +1,83 @@
# Documentation Sitemap
## Compaction refresh lease broker
> **Status:** Current navigation index. Quarantined and authority-gated material is summarized without being presented as current guidance.
- [Internal broker protocol](architecture/lease-broker-protocol.md) — kernel identity, ancestry and generation invariants, framed requests, responses, and persisted cycle bindings.
- [Broker operations](guides/lease-broker-operations.md) — protected paths, startup, constrained recovery, fail-closed posture, distinct-principal deployment, and residual risk.
- [Constrained recovery skill](../packages/mosaic/framework/skills/mosaic-context-refresh/SKILL.md) — source-resident thin wrapper, receipt scope, C4 replay boundary, and T-C middle-drop disclosure.
- [Lease-broker security notes](architecture/lease-broker-security.md) — identity, whole-class authorization, threat boundaries, and coordinator review requirements.
- [Whole mutator-class gate](architecture/mutator-class-gate.md) — default-deny policy, revoke-first/promote-last state machine, TTL, runtime adapters, and T-B/T-C assurance boundary.
- [Compaction revocation lifecycle](architecture/compaction-revocation.md) — Claude/Pi observer matrix, same-PID generation rollover, failure fencing, and the named bounded residual stale window.
## Start here
## CLI and skill management
- [Documentation atlas](README.md) — placement, source-of-truth, linking, and migration rules.
- [User guide](USER-GUIDE/README.md) — end-user workflows and product behavior.
- [Administrator guide](ADMIN-GUIDE/README.md) — installation, configuration, operations, security, and recovery.
- [Developer guide](DEVELOPER-GUIDE/README.md) — architecture, testing, integrations, and contributor material.
- [API documentation](API/README.md) — API contract migration status.
- [Reports index](reports/README.md) — review, audit, QA, security, and retained evidence.
- [Archive index](archive/README.md) — superseded historical pages.
- [Skill registration user guide](guides/user-guide.md#claude-code-skill-registration) — register, unregister, list statuses, automatic install/update reconciliation, and Claude reload behavior.
- [Skill bridge developer guide](guides/dev-guide.md#claude-code-skill-bridge) — path-validation, ownership, clobber-protection, install/update wiring, tests, and Pi/Codex scope notes.
## Product and delivery control
## Fleet configuration management
- [Product requirements](PRD.md) — normative requirements; currently marked draft and retaining authority-gated legacy references.
- [Active task rollup](TASKS.md) — orchestrator-owned work state; workers do not modify it.
- [MVP mission manifest](MISSION-MANIFEST.md) — declares itself active; current workstream status and stale outbound links await orchestrator/maintainer validation.
- [Documentation catalog and truth audit](reports/documentation/2026-08-10-docs-catalog-audit.md) — complete baseline inventory, evidence labels, broken-link clusters, and migration recommendations.
- [Fleet configuration entry point](fleet/README.md) — desired-versus-observed decision tree and complete operator link map.
- [Desired, derived, and observed state](fleet/concepts/desired-vs-observed-state.md) — roster authority, generation, ownership, and drift.
- [Identity, class, and runtime](fleet/concepts/identity-class-runtime.md) — stable name, display alias, class, runtime, provider, and model separation.
- [Role authority and leases](fleet/concepts/role-authority-and-leases.md) — validator/merge-gate separation and bounded lease authority.
- [Generated launch chain](fleet/concepts/generated-env-launch-chain.md) — strict data parsing, precedence, and quarantine.
- [Roster v2 structural contract](fleet/reference/roster-v2-fields.md) — schema, supported values, required fields, defaults, and constraints.
- [Fleet CLI reference](fleet/reference/cli.md) — local desired-state commands, JSON/exit behavior, and gateway-catalog separation.
- [Lifecycle transitions](fleet/reference/lifecycle-transitions.md) — create/apply/reboot/migration/rollback boundaries.
- [Status and drift](fleet/reference/status-and-drift.md) — desired/managed/observed state and current/future classifications.
- [Safe agent CRUD](fleet/how-to/create-update-delete-agent.md) — expected generation, dry-run, and partial-failure recovery.
- [Local lifecycle operations](fleet/how-to/start-stop-restart.md) — persisted versus one-shot actions.
- [Configurable interaction instance](fleet/how-to/configure-tess-interaction.md) and [validator instance](fleet/how-to/configure-ultron-validator.md) — generic identities and protected limits.
- [Reconcile and recover](fleet/operations/reconcile-and-recover.md) — plan/apply lock and recovery behavior.
- [Environment quarantine](fleet/operations/env-quarantine.md) — private evidence and value-free diagnostics.
- [Systemd/tmux troubleshooting](fleet/operations/systemd-tmux-troubleshooting.md) — socket, holder, unmanaged-session, and lock decisions.
- [Backup/restore boundary](fleet/operations/backup-restore.md) and [upgrade-assets hold](fleet/operations/upgrade-assets.md).
- [v1-to-v2 migration preview](fleet/migration/v1-to-v2.md) and [executable artifact dispositions](fleet/migration/example-profile-disposition.md).
- [FCM M5 closure evidence](reports/documentation/758-fleet-config-ia-closure.md) and [approved deferrals](reports/deferred/758-fleet-config-deferrals.md).
## User documentation
## Official channel plugins
- [Quickstart](USER-GUIDE/getting-started/quickstart.md) — installed-CLI first-use route with local PGlite safety boundaries.
- [Web dashboard](USER-GUIDE/product/web-dashboard.md) — current routes, views, chat persistence, settings, and admin behavior.
- [Discord conversations](USER-GUIDE/workflows/discord-conversations.md) — current authorized parent-channel, thread, attachment, and control workflow.
- [Channel protocol architecture](architecture/channel-protocol.md) — shared lifecycle, message, stable-route, authorization, and response-target contracts.
- [Discord administrator configuration](guides/admin-guide.md#discord-ingress-security) — secrets, allowlists, bindings, role policy, and thread permissions.
- [Discord user workflow](tess/USER-GUIDE.md#discord-conversations) — in-channel messages, mention-created threads, and runtime-transparent continuity.
- [Channel plugin authoring](tess/PLUGIN-GUIDE.md#official-channel-adapter-contract) — requirements for future Matrix, Slack, and other official adapters.
- [Discord package guide](../plugins/discord/README.md) — package behavior, configuration shape, and development commands.
## Administrator documentation
## Native Kanban and canonical task SOT
- [Administrator operations](ADMIN-GUIDE/operations/README.md) — current local procedures and explicitly held outlines.
- [Upgrade safety and recovery](ADMIN-GUIDE/operations/upgrade-safety-and-recovery.md) — installed-CLI/local-PGlite upgrade and framework recovery.
- [Mos connector lease operations](ADMIN-GUIDE/operations/mos-connector-lease-operations.md) — held/non-operative M1 outline while policy remains deny-all.
- [Administrator security](ADMIN-GUIDE/security/README.md) — current security chapter index.
- [SSO providers](ADMIN-GUIDE/security/sso-providers.md) — Authentik, WorkOS, and Keycloak configuration and discovery.
- [Discord ingress security](ADMIN-GUIDE/security/discord-ingress.md) — service authentication, allowlists, bindings, roles, replay, and failure controls.
- [Canonical requirements](requirements/native-kanban-sot.md) — ratified P0P3 requirements and acceptance criteria.
- [Workstream index](native-kanban-sot/INDEX.md) — artifact map, lane partition, and delivery order.
- [Mission manifest](native-kanban-sot/MISSION-MANIFEST.md) — scope, authority, invariants, and gate model.
- [Task decomposition](native-kanban-sot/TASKS.md) — dependency-ordered implementation slices and ownership boundaries.
- [KBN-101 database role split](native-kanban-sot/KBN-101-DB-ROLE-SPLIT.md) — rc.16 direct-Drizzle storage-wrapper hold: legacy N-1/uncertified/non-operative pending -02/-03/-06/-08; exact README/user-guide wrapper forms fail before masking and source-consistency rejects runner-delegation copy; held bootstrap → TLS/roles → run → verify → readiness; plus prior attestation, pgvector owner, classifier, TLS, activation, and certification prerequisite.
- [Federated tier data migration](guides/migrate-tier.md) — active KBN-101-07 operator route: runner-produced target attestation, dedicated non-DDL importer, and paired credential-/attestation-file references only.
- [Frozen shared contract](native-kanban-sot/SHARED-CONTRACT.md) — schema, API, Coordinator, health, recovery, and migration contracts.
- [KBN-101 exact-head security review](reports/native-kanban-sot/kbn-101-contract-security-review-82ce325.md) — retained prior REQUEST CHANGES evidence for `da742ca`; rc.16 awaits independent exact-head re-review after closing the current generic storage-wrapper authority HIGH finding.
- [Initial independent review](reports/native-kanban-sot/canon-initial-review-no-go.md) — KCR-001016 findings that blocked the first draft.
- [Final independent re-review](reports/native-kanban-sot/canon-final-rereview-go.md) — closure evidence and GO verdict.
- [Ultron final gate](reports/native-kanban-sot/ultron-final-go.md) — final requirements, authority, schema, migration, recovery, and evidence review.
## Developer documentation
## Tess interaction agent
- [Architecture index](DEVELOPER-GUIDE/architecture/README.md) — current architecture contracts, decisions, and draft RFCs.
- [Channel protocol](DEVELOPER-GUIDE/architecture/channel-protocol.md) — shared DTOs and current Discord compatibility boundary; future adapters are draft.
- [Lease-broker protocol](DEVELOPER-GUIDE/architecture/lease-broker-protocol.md) — authenticated Unix-socket protocol and persistence boundary.
- [Lease-broker security](DEVELOPER-GUIDE/architecture/lease-broker-security.md) — identity, ancestry, filesystem, observer, and residual boundaries.
- [Whole mutator-class gate](DEVELOPER-GUIDE/architecture/mutator-class-gate.md) — default-deny tool authorization and launch choke point.
- [Compaction revocation](DEVELOPER-GUIDE/architecture/compaction-revocation.md) — lifecycle observers, generation fencing, and residual stale window.
- [Architecture decisions](DEVELOPER-GUIDE/architecture/decisions/README.md) — implemented and accepted boundaries.
- [Mos runtime portability M1](DEVELOPER-GUIDE/architecture/decisions/mos-runtime-portability-m1.md) — logical identity, connector lease, grants, audit, and fencing.
- [Architecture RFCs](DEVELOPER-GUIDE/architecture/rfcs/README.md) — draft proposals without operational authority.
- [Optional AI egress gateways](DEVELOPER-GUIDE/architecture/rfcs/optional-ai-egress-gateways.md) — draft proposal; LiteLLM and Bifrost are not integrated.
- [Lease-broker operations and verification](DEVELOPER-GUIDE/testing/lease-broker-operations.md) — safe static/test workflow; live procedures remain held.
- [Channel adapter authoring](DEVELOPER-GUIDE/integrations/channel-adapters.md) — current shared contract and Discord reference boundary.
### Operator guides
## API transition
- [User guide](tess/USER-GUIDE.md) — authorized session, attach, send, stop, and handoff workflows.
- [Admin guide](tess/ADMIN-GUIDE.md) — deployment configuration, policy, and approval controls.
- [Developer guide](tess/DEVELOPER-GUIDE.md) — provider contracts, scope boundaries, and test workflow.
- [Plugin guide](tess/PLUGIN-GUIDE.md) — adapter, redaction, and identity-as-data requirements.
- [Operations guide](tess/OPERATIONS-GUIDE.md) — readiness, recovery, and incident-safe procedures.
- [API index](API/README.md) — consolidated gateway contract remains planned.
- [Legacy Tess-scoped OpenAPI contract](openapi-tess.yaml) — valid OpenAPI 3.1 artifact with incomplete gateway coverage; canonical scope requires maintainer approval.
### Architecture and security
## Evidence and planning
- [Architecture](tess/ARCHITECTURE.md)
- [Threat model](tess/THREAT-MODEL.md)
- [Mos coordination boundary](tess/MOS-COORDINATION.md)
- [Hermes runtime adapter design](tess/hermes-runtime-adapter-design.md)
- [Operator plugin sketch](tess/M4-003-OPERATOR-PLUGIN-SKETCH.md)
- [Reports index](reports/README.md) — all tracked report categories and evidence boundaries.
- [Archived missions](archive/missions/README.md) — historical CLI, harness, install UX, and storage-abstraction delivery records.
- [Archived planning](archive/planning/README.md) — historical briefs, board reviews, and work-package specifications.
- [Archived work records](archive/work-records/README.md) — historical task scratchpads without live consumers.
- [P8-003 performance report](reports/qa/p8-003-performance-optimization.md) — historical implementation evidence, not a current SLO.
- [Plans index](plans/README.md) — approved intent and implementation/audit plans.
- [Documentation information-architecture design](plans/2026-08-10-docs-information-architecture-design.md) — approved documentation structure decision.
- [Documentation catalog-audit plan](plans/2026-08-10-docs-catalog-audit.md) — evidence method and migration acceptance criteria.
- [Scratchpads index](scratchpads/README.md) — working memory and verification records.
- [Documentation migration scratchpad](scratchpads/DOCS-IA-002-catalog-audit.md) — coordinator progress, verification, and resumability record.
### API contract
## Authority-gated migration backlog
- [Tess OpenAPI contract](openapi-tess.yaml)
The complete file-level backlog remains in the [documentation catalog](reports/documentation/2026-08-10-docs-catalog-audit.md). The groups below are intentionally not linked as current pages:
### Migration and qualification
- **Fleet configuration:** quarantined pages have executable source/test consumers and require a coordinated path, authority, and test migration.
- **Federation:** active-versus-historical workstream status and normative authority require maintainer/orchestrator confirmation.
- **Native Kanban/KBN-101:** requirements, task state, reports, and held PostgreSQL procedures require product-owner and orchestrator decisions.
- **Tess:** mixed user, administrator, developer, migration, qualification, and API material requires audience splitting and a decision on active versus historical status.
- **Gateway API:** the Tess-scoped OpenAPI artifact must not be renamed into the canonical full-gateway contract until scope, authentication, errors, schemas, and transport coverage are approved.
- **Deployment and tier migration:** PostgreSQL, federated, bare-metal, Compose, Gateway/Web activation, and migration-runner procedures remain held under the repository safety policy.
- **Mixed legacy guides and scratchpads:** remaining records are coupled to control documents, tests/fixtures, mission evidence, or held procedures; migrate them with their owners.
- **Compaction-refresh probes:** scripts are Mos-gated and path-coupled; do not move or execute them as documentation cleanup.
- [Migration inventory](tess/M5-MIGRATION-INVENTORY.md)
- [Cutover procedure](tess/M5-MIGRATION-CUTOVER.md)
- [Rollback procedure](tess/M5-MIGRATION-ROLLBACK.md)
- [Retention and deprecation evidence](tess/M5-MIGRATION-RETENTION-DEPRECATION.md)
- [Verification matrix](tess/VERIFICATION-MATRIX.md)
- [Documentation checklist](tess/M5-003-DOCUMENTATION-CHECKLIST.md)
- [Independent Option 2 runtime-portability qualification (2026-07-14)](tess/qualification/2026-07-14-option2-runtime-portability.md)
## Runtime-neutral Mos portability
- [Optional AI egress gateway ADR](architecture/ADR-MOS-EGRESS-GATEWAYS.md) — placement and gates for LiteLLM, Bifrost, and purpose-built translation proxies.
- [Runtime-neutral Mos identity and failover mission](https://git.mosaicstack.dev/mosaicstack/stack/issues/754)
- [Logical identity and connector lease/fencing implementation](https://git.mosaicstack.dev/mosaicstack/stack/issues/755)
- [M1 logical identity and fencing architecture](architecture/mos-runtime-portability-m1.md)
- [M1 connector lease operations](guides/mos-connector-lease-operations.md)
## Comms evolution — Matrix-native MACP (design, draft)
- [RFC-001 — MACP: a Mosaic-native, Matrix-native comms layer](rfcs/RFC-001-MACP-MATRIX-NATIVE.md) — Synapse + Mosaic appservice backbone, MACP v1 protocol, presence/escalation, federation, strangler migration off the Hermes MCP bridge.
- [RFC-002 — Install, configuration & topology for the Matrix/MACP comms system](rfcs/RFC-002-INSTALL-CONFIG-TOPOLOGY.md) — open-source install topology modes, ACME cert provisioning, pluggable secret backend, and config precedence.
`docs/_old_structure/` remains read-only migration quarantine. It is not current navigation and must not be used as command authority.
-111
View File
@@ -1,111 +0,0 @@
# SSO Providers
Mosaic Stack supports optional enterprise single sign-on through Better Auth's generic OAuth flow. The gateway mounts Better Auth under `/api/auth`, so every provider callback terminates at:
```text
{BETTER_AUTH_URL}/api/auth/oauth2/callback/{providerId}
```
For the providers in this document:
- Authentik: `{BETTER_AUTH_URL}/api/auth/oauth2/callback/authentik`
- WorkOS: `{BETTER_AUTH_URL}/api/auth/oauth2/callback/workos`
- Keycloak: `{BETTER_AUTH_URL}/api/auth/oauth2/callback/keycloak`
## Required environment variables
### Authentik
```bash
AUTHENTIK_ISSUER=https://auth.example.com/application/o/mosaic
AUTHENTIK_CLIENT_ID=...
AUTHENTIK_CLIENT_SECRET=...
```
### WorkOS
```bash
WORKOS_ISSUER=https://your-company.authkit.app
WORKOS_CLIENT_ID=client_...
WORKOS_CLIENT_SECRET=...
NEXT_PUBLIC_WORKOS_ENABLED=true
```
`WORKOS_ISSUER` should be the WorkOS AuthKit issuer or custom auth domain, not the raw REST API hostname. Mosaic derives the OIDC discovery URL from that issuer.
### Keycloak
```bash
KEYCLOAK_ISSUER=https://auth.example.com/realms/master
KEYCLOAK_CLIENT_ID=mosaic
KEYCLOAK_CLIENT_SECRET=...
NEXT_PUBLIC_KEYCLOAK_ENABLED=true
```
If you prefer, you can keep the issuer split as:
```bash
KEYCLOAK_URL=https://auth.example.com
KEYCLOAK_REALM=master
```
The auth package will derive `KEYCLOAK_ISSUER` from those two values.
## WorkOS setup
1. In WorkOS, create or select the application that will back Mosaic login.
2. Configure an AuthKit domain or custom authentication domain for the application.
3. Add the redirect URI:
```text
{BETTER_AUTH_URL}/api/auth/oauth2/callback/workos
```
4. Copy the application's `client_id` and `client_secret` into `WORKOS_CLIENT_ID` and `WORKOS_CLIENT_SECRET`.
5. Set `WORKOS_ISSUER` to the AuthKit domain from step 2.
6. Create the WorkOS organization and attach the enterprise SSO connection you want Mosaic to use.
7. Set `NEXT_PUBLIC_WORKOS_ENABLED=true` in the web deployment so the login button is rendered.
## Keycloak setup
1. Start from an existing Keycloak realm or create a dedicated realm for Mosaic.
2. Create a confidential OIDC client named `mosaic` or your preferred client ID.
3. Set the valid redirect URI to:
```text
{BETTER_AUTH_URL}/api/auth/oauth2/callback/keycloak
```
4. Set the web origin to the public Mosaic web URL.
5. Copy the client secret into `KEYCLOAK_CLIENT_SECRET`.
6. Set either `KEYCLOAK_ISSUER` directly or `KEYCLOAK_URL` + `KEYCLOAK_REALM`.
7. Set `NEXT_PUBLIC_KEYCLOAK_ENABLED=true` in the web deployment so the login button is rendered.
### Local Keycloak smoke test
If you want to test locally with Docker:
```bash
docker run --rm --name mosaic-keycloak \
-p 8080:8080 \
-e KEYCLOAK_ADMIN=admin \
-e KEYCLOAK_ADMIN_PASSWORD=admin \
quay.io/keycloak/keycloak:26.1 start-dev
```
Then configure:
```bash
KEYCLOAK_ISSUER=http://localhost:8080/realms/master
KEYCLOAK_CLIENT_ID=mosaic
KEYCLOAK_CLIENT_SECRET=...
NEXT_PUBLIC_KEYCLOAK_ENABLED=true
```
## Web flow
The web login page renders provider buttons from `NEXT_PUBLIC_*_ENABLED` flags. Each button links to `/auth/provider/{providerId}`, and that page initiates Better Auth's `signIn.oauth2` flow before handing off to the provider.
## 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.
+49
View File
@@ -0,0 +1,49 @@
# User Guide
> **Status:** Partially migrated. The quickstart, web-dashboard reference, and Discord conversation workflow are current.
This book is the canonical home for end-user workflows, user-visible behavior, product concepts, and user troubleshooting. Keep installation, deployment, security controls, and recovery procedures in [`ADMIN-GUIDE/`](../ADMIN-GUIDE/); keep implementation detail in [`DEVELOPER-GUIDE/`](../DEVELOPER-GUIDE/).
## Start here
- [Documentation atlas](../README.md) — placement rules and source-of-truth boundaries.
- [Documentation sitemap](../SITEMAP.md) — resolvable current navigation and authority-gated migration summary.
- [Quickstart](getting-started/quickstart.md) — install Mosaic, complete setup, and launch a session.
- [Web dashboard](product/web-dashboard.md) — current routes, navigation, chat persistence, projects/tasks views, settings, and admin behavior.
- [Discord conversations](workflows/discord-conversations.md) — current authorized parent-channel, thread, attachment, and control workflow.
## Chapter map
| Chapter | Scope | Status |
| ------------------ | ------------------------------------------------------------- | ---------------------------------------------------- |
| `getting-started/` | First-use setup, orientation, and quickstarts. | Quickstart is current; additional pages are planned. |
| `concepts/` | User-facing terminology, product concepts, and mental models. | Scaffold only. |
| `workflows/` | Task-oriented procedures for using Mosaic Stack. | Discord conversation workflow is current. |
| `product/` | Current product surfaces and visible behavior. | Web dashboard reference is current. |
| `troubleshooting/` | User-visible failures, diagnostics, and fixes. | Scaffold only. |
### Current pages
- [Quickstart](getting-started/quickstart.md) — the verified installed-CLI first-use path.
- [Web dashboard](product/web-dashboard.md) — verified current Next.js dashboard behavior and limitations.
- [Discord conversations](workflows/discord-conversations.md) — verified current Discord user workflow.
Every promoted page must be added to this index and to [`SITEMAP.md`](../SITEMAP.md) in the same migration slice.
## Migration backlog — not current navigation
These are source candidates, not current user guidance:
- `_old_structure/guides/user-guide.md` — quarantined historical source; verify every claim before promotion. See the [documentation catalog](../reports/documentation/2026-08-10-docs-catalog-audit.md) for its disposition.
- The former root `QUICKSTART.md` was an empty placeholder and has been replaced by the current page above.
Do not link to the quarantine as a current user path. Create a new page only after classifying its audience, status, and evidence in the migration report.
## Authoring boundary
New user-facing documentation belongs under one of the chapter directories above. Use a lowercase kebab-case page name, state whether commands are current or held, and link back to this index plus related canonical sources.
## Related
- [[README|Documentation contract]]
- [[PRD|Product requirements]]
@@ -0,0 +1,134 @@
---
title: Mosaic Stack Quickstart
type: guide
audience: user
status: current
source_of_truth: false
---
# Mosaic Stack Quickstart
Get the Mosaic CLI installed, complete first-run setup, connect to a gateway, and launch an agent session. This page covers the supported installed-CLI path with the default local storage tier.
> **Scope:** This is an end-user installation route. It does not authorize PostgreSQL setup, production deployment, or starting Gateway/Web directly from a source checkout. Use the [administrator guide](../../ADMIN-GUIDE/README.md) for deployment and the [developer guide](../../DEVELOPER-GUIDE/README.md) for contributor setup.
## Requirements
- Node.js 20 or newer.
- npm, for the global Mosaic CLI installation.
- At least one supported agent runtime:
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code)
- [Codex](https://github.com/openai/codex)
- [OpenCode](https://opencode.ai)
- [Pi](https://pi.dev)
- Credentials for the runtime or model provider you plan to use.
## 1. Install Mosaic
The stable installer installs the Mosaic framework and the `mosaic` CLI, then launches the setup wizard by default:
```bash
curl -fsSL https://mosaicstack.dev/install.sh | bash
```
If your security policy requires reviewing the script before execution, download it first and inspect it. The installer also supports the direct repository URL:
```bash
curl -fsSL https://git.mosaicstack.dev/mosaicstack/stack/raw/branch/main/tools/install.sh -o /tmp/mosaic-install.sh
less /tmp/mosaic-install.sh
bash /tmp/mosaic-install.sh
```
To install without automatically launching the wizard:
```bash
bash /tmp/mosaic-install.sh --no-auto-launch
```
The installer places framework files under `~/.config/mosaic/` and installs the CLI under the configured npm global prefix, `~/.npm-global/` by default. Ensure that prefix is on your `PATH` if your shell cannot find `mosaic`.
## 2. Complete first-run setup
If the installer skipped the wizard, run it manually:
```bash
mosaic wizard
```
The wizard guides framework setup and gateway installation. It can collect your agent identity, preferences, provider configuration, and gateway administrator details interactively.
For a separately installed or existing gateway, skip local gateway installation and use its URL in the login step below.
## 3. Verify and sign in
For a gateway installed on this machine, check its health and setup state:
```bash
mosaic gateway status
mosaic gateway verify
```
Sign in without putting your password in shell history or process listings:
```bash
mosaic gateway login
```
The command prompts for the gateway URL, email, and password as needed. Do not pass passwords with `--password`.
For a remote gateway, provide its URL explicitly:
```bash
mosaic gateway login --gateway https://gateway.example.com
```
## 4. Launch Mosaic
Open the interactive terminal interface:
```bash
mosaic tui
```
The TUI defaults to `http://localhost:14242` and can prompt for login if no valid session is saved. To connect it to another gateway:
```bash
mosaic tui --gateway https://gateway.example.com
```
You can also launch a supported runtime through Mosaic:
```bash
mosaic pi
mosaic claude
mosaic codex
mosaic opencode
```
Use the launcher matching the runtime you installed and authenticated.
## 5. Inspect configuration and health
These commands are safe diagnostics and do not change the product requirements or active task ledger:
```bash
mosaic config show
mosaic doctor
mosaic gateway logs
```
If the gateway is unhealthy, run `mosaic gateway status` and `mosaic gateway logs` before attempting a reinstall. If your session expires, run `mosaic gateway login` again.
## Storage and deployment boundary
The default local gateway tier uses embedded PGlite and does not require an external PostgreSQL or Valkey service. This quickstart intentionally does not configure `DATABASE_URL`, PostgreSQL, pgvector, or a federated deployment.
For standalone or federated storage, deployment topology, secrets, SSO, backups, or recovery, stop here and use the [administrator guide](../../ADMIN-GUIDE/README.md). For work from a repository checkout, keep `DATABASE_URL` unset and follow the [developer guide](../../DEVELOPER-GUIDE/README.md); do not use root `pnpm dev` as a local PGlite route while the current dotenv safety hold remains active.
## Related
- [User Guide](../README.md)
- [Documentation atlas](../../README.md)
- [Administrator Guide](../../ADMIN-GUIDE/README.md)
- [Developer Guide](../../DEVELOPER-GUIDE/README.md)
- [Repository README](../../../README.md)
+276
View File
@@ -0,0 +1,276 @@
---
title: Mosaic web dashboard
type: guide
audience: user
status: current
source_of_truth: false
---
# Mosaic Web Dashboard
This page documents the current Next.js dashboard: its routes, navigation, visible
views, and the chat persistence behavior supported by the checked-in web and gateway
implementation.
> **Current UI boundary:** Projects and tasks can be displayed in the dashboard, but
> the current dashboard does not provide **New Project** or **New Task** controls.
> Those entities can be created through authenticated gateway API clients; that API
> surface is separate from the views described here.
## Access and routes
The dashboard uses the gateway session. Dashboard routes are protected by the web
`AuthGuard`; the admin route also requires the `admin` role.
| Path | Access | Current behavior |
| --------------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/` | Any | Redirects to `/chat`. A signed-out visitor is then redirected to `/login`. |
| `/login` | Signed out | Email/password sign-in, plus buttons for configured SSO providers. Successful sign-in returns to `/chat`. |
| `/register` | Signed out | Creates an account with name, email, and password, then returns to `/chat`. |
| `/auth/provider/[provider]` | SSO handoff | Looks up the requested provider and starts an OIDC sign-in when that provider is configured and supports OIDC. Unknown, disabled, or incompatible providers show an error and a link back to login. |
| `/chat` | Signed in | Conversation list and streamed assistant chat. |
| `/projects` | Signed in | Project cards and the Active Mission status section. |
| `/projects/[id]` | Signed in | Project detail view with overview, tasks, missions, and an optional read-only PRD tab. |
| `/tasks` | Signed in | Task data in Kanban or list view. |
| `/settings` | Signed in | Profile, appearance, notifications, and provider tabs. |
| `/admin` | Signed-in admin | User Management and System Health tabs. Non-admin users are redirected away from the admin page. |
Settings and admin tabs are client-side tabs; changing a tab does not change the URL.
## Dashboard navigation
The **Workspace** sidebar contains these links, in order:
1. **Chat**`/chat`
2. **Tasks**`/tasks`
3. **Projects**`/projects`
4. **Settings**`/settings`
5. **Admin**`/admin`
The active link is highlighted, including the parent link when viewing a project
such as `/projects/<id>`. The Admin link is rendered in the shared sidebar, but the
page itself is still restricted to administrators.
The top bar provides:
- a sidebar toggle (collapse/expand on desktop; open/close an overlay on mobile),
- the light/dark theme toggle,
- the signed-in user's name, and
- **Sign out**, which returns to `/login`.
The applied global theme is stored in browser local storage under `mosaic-theme`.
## Chat
### Starting and managing conversations
Open `/chat` and either select a conversation or choose **Start new conversation**
in the empty state. The conversation sidebar also has **New conversation**. Creating
one calls the gateway and gives the conversation a server-side ID before the first
message is sent.
The sidebar currently supports:
- searching conversation titles,
- selecting a conversation,
- renaming a conversation inline (Enter or leaving the field commits the new title),
- deleting a conversation after confirmation, and
- grouping conversations by project when project data is available.
Archived conversations are filtered out of the sidebar. The current dashboard does
not expose archive/restore controls.
If no conversation is selected when a message is sent, the dashboard creates one
automatically. A new conversation is titled from the first 60 characters of the
first message; an empty placeholder conversation is similarly retitled after its
first message. The active conversation is held in page state, not in the URL: the
route remains `/chat` rather than changing to `/chat/<id>`. After a refresh, select
the stored conversation again from the sidebar.
### Sending and streaming
The chat composer provides:
- a model selector populated from available gateway providers and models,
- a multiline message field,
- character and approximate token counts, and
- a **Send** button.
Use **Cmd/Ctrl+Enter** to send. The composer is disabled while a response is
streaming. The interface displays a **Stop** control during streaming, but the
current `ChatPage` does not pass a stop handler, so cancellation is not a reliable
current dashboard action.
Assistant and user messages render Markdown. Assistant messages can show model and
token metadata when it is present in the returned message, and rendered user/assistant
messages have a copy control.
### What is persisted
Assistant replies are not page-only or memory-only. The current flow is:
1. The browser creates or selects a conversation and optimistically displays the
user's message.
2. The browser submits the user message to the gateway conversation-message API and
sends the turn over the authenticated `/chat` WebSocket namespace.
3. The gateway streams the assistant response to the page. At `agent_end`, it saves
non-empty assistant text to the conversation with model, provider, tool-call, and
token-usage metadata when available.
4. Selecting the conversation later loads `/api/conversations/<id>/messages`, so
stored user and assistant messages are shown again. When the gateway has to create
or resume the agent session for that conversation, it also loads stored conversation
history as context.
The live page appends the completed response as soon as the stream ends; the gateway
persistence write is asynchronous. Therefore a persistence error can leave a reply
visible in the current page while it is unavailable after a later reload. The gateway
redacts sensitive content before emitting and storing assistant text.
## Projects and missions
### Project list: `/projects`
The project page loads the signed-in user's projects and shows either:
- project cards with name, status, description, and creation date, or
- **No projects yet** with the message that projects appear when created through the
gateway API.
The page also shows **Active Mission**. It reports the mission ID, phase, task
completion count, and status when coordination data is available; otherwise it
shows **No active mission detected**.
There is no New Project button or project form on this page. The gateway has
project CRUD endpoints, but this dashboard page currently reads project data only.
### Project detail: `/projects/<id>`
A project detail page shows its name, status, description, created/updated dates,
and task summary counts for total, done, in progress, and blocked tasks. Its tabs
are:
- **Overview** — up to five recently updated tasks, a mission summary, and non-empty
project metadata.
- **Tasks (`n`)** — task status filters and clickable task rows.
- **Missions (`n`)** — a status-ordered mission timeline.
- **PRD** — present only when project metadata contains non-empty `prd` or
`prdContent` text; the content is displayed read-only.
Selecting a task from the project detail Tasks tab opens a detail dialog with its
status, priority, description, assignee, due date, timestamps, tags, pull-request
links, and notes when those fields exist. The dialog can be closed with its close
button, the backdrop, or Escape. The dashboard does not provide project or task
edit controls in this view.
## Tasks
Open `/tasks` to load the tasks visible to the signed-in user. The default view is
**Kanban**; a toggle switches to **List**.
- Kanban columns are **Not Started**, **In Progress**, **Blocked**, and **Done**.
- Kanban cards show title, priority, optional description, status, and due date.
- List view shows title, status, priority, and due date.
- The supported task status set also includes `cancelled`; it is not a Kanban
column, but a returned cancelled task can appear in List view.
The top-level Tasks page has no New Task button, form, or working task edit dialog.
Clicking a task in that page does not open the project-detail dialog. The gateway
supports authenticated task CRUD separately, while this dashboard page currently
reads and presents task data.
## Settings
Open `/settings`. The page has four tabs and opens on **Profile**.
### Profile
- **Display Name** can be edited.
- **Email** is displayed but disabled; the page says it cannot be changed there.
- **Avatar URL** can be edited.
- **Save changes** sends the profile update through BetterAuth and reports saving,
saved, or an error state.
### Appearance
The tab presents **System**, **Light**, and **Dark** theme choices, a **Collapse
sidebar by default** switch, and a **Default Model** text field. **Save changes**
saves these as user preferences.
Current implementation limits are worth noting: the shared sidebar provider starts
expanded and does not read `ui.sidebar_collapsed` on page load; the global applied
theme is controlled by the top-bar theme toggle; and the chat page initially selects
the first available provider model rather than reading `ui.default_model` itself.
Do not treat these preference fields as proof that those defaults are applied across
all dashboard sessions.
### Notifications
The tab presents and saves three email preferences:
- **Agent task completed** — initially off,
- **Mentions** — initially on, and
- **Weekly digest** — initially off.
### Providers
The Providers tab contains SSO discovery and LLM provider discovery/testing areas:
- **SSO Providers** shows configured providers and their protocols, callback path,
team-sync claim, SAML fallback, and warnings when supplied by the gateway. It does
not configure SSO from the dashboard.
- **LLM Providers** shows configured providers as Active or Inactive. A provider can
be tested for reachability; the result may include latency, an error, and the
number of discovered models. Expanding a provider shows model capabilities,
context size, cost, and the default-model marker.
When no LLM providers are configured, the page displays setup guidance mentioning
`OLLAMA_BASE_URL` and `MOSAIC_CUSTOM_PROVIDERS`. Provider credentials and provider
configuration are not editable in this dashboard tab.
## Admin panel
The `/admin` page is wrapped in an admin-role guard. It opens on **User Management**
and provides **System Health** as the second tab.
### User Management
The page loads the user list and provides:
- a user count,
- **+ New User**, with name, email, password, and `member`/`admin` role fields,
- role promotion/demotion,
- ban/unban,
- deletion after confirmation, and
- a retry action when loading fails.
The table shows name/email, role, active or banned status, creation date, and
available actions.
### System Health
The health tab loads the gateway's overall `ok` or `degraded` status and provides a
**Refresh** action. Its cards cover:
- PostgreSQL database status and latency/error,
- Valkey cache status and latency/error,
- active agent-session count, and
- configured LLM providers and model counts.
## Evidence used for this page
The behavior above was checked against the current implementation and focused tests,
not copied forward as-is from the historical mixed guide. The main evidence files
are:
- `apps/web/src/app/(dashboard)/` route pages and `apps/web/src/app/page.tsx`,
- `apps/web/src/components/layout/`, `apps/web/src/components/chat/`,
`apps/web/src/components/projects/`, and `apps/web/src/components/tasks/`,
- `apps/web/e2e/navigation.spec.ts`, `chat.spec.ts`, `projects.spec.ts`,
`settings.spec.ts`, and `admin.spec.ts`,
- `apps/gateway/src/chat/chat.gateway.ts` and
`apps/gateway/src/__tests__/conversation-persistence.test.ts`,
- `apps/gateway/src/chat/chat.gateway-redaction.spec.ts`, and
- the authenticated gateway controllers under `apps/gateway/src/conversations/`,
`projects/`, `tasks/`, and `admin/`.
Related: [User Guide index](../README.md) and [SSO provider runbook](../../ADMIN-GUIDE/security/sso-providers.md).
@@ -0,0 +1,128 @@
# Discord conversations
> **Status:** Current Discord workflow for an administrator-provisioned, authorized guild channel.
>
> Telegram shared-contract parity, Matrix channel conversations, and a gateway-wide shared adapter registry are not current features. See [Current versus planned](#current-versus-planned) before using any older channel instructions.
>
> **Audience:** People conversing with an agent through Discord.
This workflow assumes an administrator has configured the Discord bot, gateway connection, allowlists, and a logical-agent binding. Users cannot create a binding or authorize themselves from Discord.
## Current versus planned
| Surface | Status | What you can rely on |
| ----------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Discord guild messages | **Current live routing** | Authorized messages route to the configured logical agent; parent/thread delivery is tested, but ordinary durable history is not guaranteed. |
| Telegram | **Not shared-contract parity** | A raw legacy plugin exists, but its current source has no equivalent documented authorization, pairing, route, or focused package tests. |
| Matrix | **Not implemented as a channel workflow** | No current gateway channel adapter and test boundary establishes a Matrix conversation workflow. |
| Shared channel registry | **Not implemented** | The gateway's current registry hosts lifecycle wrappers; it does not provide universal channel routing or health. |
## Start in a configured parent channel
Send a normal message in the administrator-configured parent text channel. You do **not** need to mention the bot for an ordinary turn.
For an authorized user, Mosaic:
1. checks the guild, parent channel, user allowlist, pairing, role, and rate limit;
2. keeps the response target in the parent channel; and
3. routes the turn to the binding's logical agent using a stable conversation address.
No Discord thread is created for this untagged parent-channel case. The response is sent back to that same channel.
Messages from an unconfigured guild/channel, an unallowlisted user, an unpaired user, or a user without a role that can send are ignored without creating a thread or dispatching to the gateway. Bot-authored messages are ignored. Direct messages are not handled by the current guild ingress path.
## Start a threaded topic with a mention
Mention the bot in a parent channel when you want a separate topic:
```text
@Mosaic investigate the deployment failure
```
The current Discord adapter creates a public thread for the message, removes the bot mention from the content sent to the agent, and targets the response to that thread. If the message already has a Discord thread attached, the adapter reuses it instead of creating another one.
Authorization happens before thread creation. If the user, guild, parent channel, pairing, role, or rate check fails, no thread is created. If Discord cannot create or fetch the requested thread, the message is not dispatched because Mosaic cannot guarantee the requested response destination.
## Continue inside a thread
Reply in the existing authorized thread without mentioning the bot again. The adapter:
- authorizes the message against the configured parent text channel;
- keeps the thread as the response target; and
- never attempts to create a nested thread.
A category above the text channel is not used as the authorization parent. Only the actual configured text-channel parent grants thread inheritance.
The stable conversation address is formed from the configured logical agent, channel name, and response channel/thread, for example:
```text
<logical-agent-id>:discord:<response-channel-id>
```
It does not contain Claude, Codex, Pi, OpenCode, a model, a provider, a process, or a native runtime-session ID. The gateway owns runtime selection behind that route, so changing the runtime/provider does not require a new Discord address.
### Durability limitation
Treat the current Discord path as **live routing and delivery**, not guaranteed durable conversation history. The Discord conversation address above is an external route string, while persisted conversation/message rows use UUID conversation IDs. No current external-route-to-UUID mapping was found. The gateway can continue dispatching after a persistence/binding failure, so a reply may appear in Discord without durable history or restart/resume continuity.
Do not rely on Discord as the sole record of a conversation. Durable history requires an implementation that maps the external route to a UUID, surfaces persistence failure, and proves fresh-message persistence and restart recovery in an integration test.
## Attachments
An authorized message may contain text, attachments, or an attachment without text. The current adapter maps attachments into the shared message shape and preserves the native attachment ID, name, URL, content type, and optional size.
The gateway accepts only bounded attachment metadata: at most 10 attachments, HTTPS URLs without credentials, query strings, or fragments, and bounded ID, name, URL, MIME-type, size, and total metadata values. An unsafe or malformed attachment is rejected before the message is acknowledged or dispatched. Binary content is not embedded in the gateway message; the attachment remains a validated external reference.
## Runtime controls
The current Discord text controls are:
```text
/approve
/stop <approval>
```
They remain on the current parent/thread route and do not create a new topic. Approval and stop require an already enrolled durable session; ordinary Discord chat does not prove that enrollment occurred. These are privileged operations: the paired user must have the `admin` role and a provisioned `mosaicUserId`, the gateway must have a tenant configured for the control path, and the enrolled durable session must still belong to the bound logical agent. A stop must present the exact approval reference created for that target; approval consumption is one-time.
If these checks fail, the control operation is denied or produces no successful control result. Do not assume that being able to read a channel grants control authority.
## Response and delivery behavior
The gateway emits raw stream events to the current Discord compatibility path. The plugin buffers `agent:start`/`agent:text` output and sends the completed response on `agent:end`; this is not a claim of token-by-token Discord rendering.
Outbound Discord text is split at a 1,900-character boundary. Transient rate-limit, server, and network failures are retried up to three attempts with one deterministic nonce per correlation/chunk; permanent delivery failures are not retried. A response route is checked against the configured logical-agent/channel binding before Discord is contacted.
## If a message gets no response
Check these possibilities with the administrator:
1. You are in a direct message, an unconfigured guild/channel, or a thread whose parent is not configured.
2. Your Discord user ID is missing from the user allowlist or `pairedUsers`.
3. Your pairing is `viewer`, which cannot send ordinary agent turns.
4. The per-user/channel message or mention-thread limit was reached.
5. The bot is not connected to Discord or the gateway Socket.IO `/chat` namespace.
6. A mentioned topic could not create/fetch its thread.
7. The gateway rejected the signed envelope, route, attachment, or replayed native message ID.
8. `/approve` or `/stop <approval>` was attempted without the required admin pairing, tenant, pre-enrolled durable session, or exact approval.
These failures are intentionally fail-closed; an unauthorized or unverifiable message should not create a thread or agent side effect.
## Not current: Telegram and Matrix
Do not substitute Telegram or Matrix instructions for this workflow:
- The current Telegram plugin uses raw Telegraf and Socket.IO messages, maps a chat to `telegram-<chatId>`, accepts text only, and does not establish the Discord-style service-token, allowlist, pairing, shared-route, or attachment boundary.
- No current Matrix gateway channel adapter, channel binding, user authorization flow, or focused channel tests establish a Matrix conversation workflow.
- The current gateway plugin list is lifecycle-only; it is not proof that every channel shares this Discord behavior.
Those are parity/design gaps, not alternate user workflows.
## Evidence and related pages
- [Channel protocol architecture](../../DEVELOPER-GUIDE/architecture/channel-protocol.md) — current shared types, Discord compatibility path, and explicit parity boundary.
- [Discord ingress security](../../ADMIN-GUIDE/security/discord-ingress.md) — administrator configuration, authentication, authorization, and failure controls.
- [`plugins/discord/src/index.ts`](../../../plugins/discord/src/index.ts) — native Discord routing and delivery implementation.
- [`plugins/discord/src/index.test.ts`](../../../plugins/discord/src/index.test.ts) — parent, mention, existing-thread, authorization, attachment, rate, egress, and health tests.
- [`apps/gateway/src/plugin/discord-ingress.security.spec.ts`](../../../apps/gateway/src/plugin/discord-ingress.security.spec.ts) — gateway signature, replay, binding, attachment, approval, and stop tests.
- [`apps/gateway/src/__tests__/integration/tess-cross-surface.integration.test.ts`](../../../apps/gateway/src/__tests__/integration/tess-cross-surface.integration.test.ts) — Discord control-flow test with explicit durable-session pre-enrollment; it is not fresh-message persistence evidence.
- [User Guide](../README.md)

Some files were not shown because too many files have changed in this diff Show More