Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a8d011baa | ||
|
|
80920950a4 | ||
|
|
0bf563268c | ||
|
|
15644d81d4 | ||
|
|
24462f460e | ||
|
|
a480ee83dc | ||
|
|
fd43ed5420 | ||
|
|
1d84bc3f3d | ||
|
|
6306914965 | ||
|
|
af43a7a63e | ||
|
|
ca97b885b0 | ||
|
|
6db0bead44 | ||
|
|
c671290d77 | ||
|
|
6a9b2cf6c1 | ||
|
|
6bd93a621d | ||
|
|
e9485c3d96 | ||
|
|
d2f0846dcc | ||
|
|
4f22a58041 | ||
|
|
9b6869fab7 | ||
|
|
20ad89c86b | ||
|
|
a55d1a1812 | ||
|
|
9abd7e386f | ||
|
|
9af456c240 | ||
|
|
cb9a0d1642 | ||
|
|
420507da77 | ||
|
|
2508f0aa99 | ||
|
|
d339e8fd21 | ||
|
|
018d96a412 | ||
|
|
b01950e92f | ||
|
|
1bdeed62eb | ||
|
|
840c2b0d96 | ||
|
|
95d5cb32d4 | ||
|
|
d5f3fae896 | ||
|
|
1556982dbc | ||
|
|
1a822493ba | ||
|
|
d2eeb64433 | ||
|
|
5e58597dbe | ||
|
|
fe4fa20309 | ||
|
|
7102ccb93e | ||
|
|
afdaa6d0e6 | ||
|
|
41749bbd33 | ||
|
|
120af4e193 | ||
|
|
ec260e678f | ||
|
|
b590a5c3d8 | ||
|
|
540ec5b6ef | ||
|
|
563d1ac053 | ||
|
|
4d8ddb9a0a | ||
|
|
722163671f | ||
|
|
f158be8003 | ||
|
|
b0f7d26dd9 | ||
|
|
3a1203b2f8 | ||
|
|
df4c591ab4 | ||
|
|
4fa2768962 | ||
|
|
aa0a7b5fa2 | ||
|
|
42ac19af48 | ||
|
|
f744f32214 | ||
|
|
8ff7aac0ca | ||
|
|
80a45b1e1c | ||
|
|
85d2108e4e | ||
|
|
16f91157a1 | ||
|
|
2fa6bcd576 | ||
|
|
1afe2b36dc | ||
|
|
63e77887a8 | ||
|
|
809ca9a1d9 | ||
|
|
57435bb879 | ||
|
|
8c51bf7575 | ||
|
|
c3d2179ad8 | ||
|
|
b47c4024cc | ||
|
|
74d1cdc7c1 | ||
|
|
8cae9e0883 | ||
|
|
2c524b6da2 | ||
|
|
b4f2019529 | ||
|
|
b1eb1fb2f9 | ||
|
|
dfeb4d9692 | ||
|
|
7ea13332ed | ||
|
|
b032d23889 | ||
|
|
ecde74439c |
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"integration_trunk": "next",
|
||||
"release_branch": "main"
|
||||
}
|
||||
+55
-6
@@ -2,16 +2,38 @@
|
||||
# node:24-alpine + python3/make/g++/postgresql-client + pnpm + a warm pnpm
|
||||
# store. The install step resolves from the baked store (--prefer-offline)
|
||||
# instead of paying a ~731s cold fetch + native compile every run.
|
||||
#
|
||||
# PINNED to an immutable lock-tag (#1328, brain D27): ci-image.yml pushes
|
||||
# lock-<sha256(pnpm-lock.yaml)[:12]> atomically with :latest, so the two are
|
||||
# byte-identical at push time. A mutable :latest resolves per-pod at pull time
|
||||
# on the k8s backend, which made CI verdicts non-reproducible (same tree, same
|
||||
# config, different images across runs; see #1324 comment 23382/23386). The pin
|
||||
# changes ONLY through reviewed commits; a wrong tag fails loudly at image pull.
|
||||
#
|
||||
# Bump procedure: when a recipe change (pnpm-lock.yaml / Dockerfile.ci) lands on
|
||||
# main, ci-image.yml pushes lock-<new>; a follow-up PR updates this anchor.
|
||||
# Until then pipelines keep the old pin: reproducible, with the documented
|
||||
# network-fallback lag (frozen-lockfile resolves missing packages from network).
|
||||
# Known limitation: lock- addresses the lockfile only, so a Dockerfile-only
|
||||
# change re-pushes the same tag with new content (#1328 follow-up: recipe-hash).
|
||||
variables:
|
||||
- &node_image 'git.mosaicstack.dev/mosaicstack/stack/ci-base:latest'
|
||||
- &node_image 'git.mosaicstack.dev/mosaicstack/stack/ci-base:lock-9cb7ffcd8828'
|
||||
- &enable_pnpm 'corepack enable'
|
||||
|
||||
when:
|
||||
# PR + manual CI run on any branch — the pull_request pipeline is the merge gate.
|
||||
# push CI is restricted to protected branches (main) so a feature-branch push no
|
||||
# longer fires a redundant SECOND pipeline alongside its PR pipeline. This ~halves
|
||||
# CI load on the storage-constrained runner with zero loss of gating (branch
|
||||
# protection requires no push/ci status context; main still gets full push CI).
|
||||
# PR + manual CI run on any branch: the pull_request pipeline is the merge
|
||||
# gate (next is protected and the default branch since 2026-08-19).
|
||||
# Push CI runs on main only. next deliberately runs NO push ci: post-merge
|
||||
# verification on next is carried by publish.yml's `verify` step
|
||||
# (pnpm verify:release), which mirrors this pipeline's complete mandatory
|
||||
# set step-for-step, enforced by scripts/verify-release.test.mjs. PR CI
|
||||
# tests the PR HEAD tree (refs/pull/N/head, measured 2026-08-19), not a
|
||||
# merge ref, so if next advances before a merge the landed tree differs
|
||||
# from the tested one; publish verify re-runs the full set on the landed
|
||||
# tree (PGlite path). Measured 2026-08-19: the 21 most recent push events
|
||||
# on next each ran exactly one pipeline (publish), zero ci.
|
||||
# Keeping push ci off next also avoids a redundant second full-suite run
|
||||
# per merge on the storage-constrained runner.
|
||||
- event: [pull_request, manual]
|
||||
- event: push
|
||||
branch: main
|
||||
@@ -59,6 +81,33 @@ steps:
|
||||
# [0] of the pnpm chain, so severing that chain would silence it together
|
||||
# with everything it guards; this direct line keeps one instrument running.
|
||||
- bash packages/mosaic/framework/tools/quality/scripts/check-test-enumeration.sh
|
||||
# Tool-index gate: a shipped wrapper that appears in no resident index doc
|
||||
# is undiscoverable from inside a session, and an agent that cannot learn a
|
||||
# wrapper exists reaches for raw curl instead — which is how a Gitea review
|
||||
# got filed PENDING three times. Ships-and-documented is one commit, or red.
|
||||
- bash packages/mosaic/framework/tools/quality/scripts/check-tools-index.sh --self-test
|
||||
- bash packages/mosaic/framework/tools/quality/scripts/check-tools-index.sh
|
||||
# Hermetic regression for issue-close.sh (#1081): mocks tea/curl onto PATH
|
||||
# and sandboxes a throwaway git repo, so it resolves no real credentials and
|
||||
# joins CI directly rather than the exclusions file.
|
||||
- bash packages/mosaic/framework/tools/git/test-issue-close-fail-closed.sh
|
||||
# Hermetic regression for the git identity ladder (#1356): mock tea on PATH,
|
||||
# sandboxed repo, no real credentials (3/3 green under an empty HOME). Pins
|
||||
# fail-closed: a seat whose login is missing gets a named error, never a
|
||||
# borrowed identity. Joins CI directly; its #1007 exclusion is burned down.
|
||||
- bash packages/mosaic/framework/tools/git/test-gitea-login-resolution.sh
|
||||
# Hermetic behavioural regression for the PreToolUse wrapper guard: proves
|
||||
# it still blocks the three mistakes AND still lets reads, unwrapped
|
||||
# endpoints and ordinary commands through. Both directions are asserted —
|
||||
# a guard that over-blocks gets routed around, which fails just as hard.
|
||||
- bash packages/mosaic/framework/tools/git/test-wrapper-guard.sh
|
||||
# Hermetic regression for mosaic-worktree.sh at fleet scale: stubs git onto
|
||||
# PATH so `list` faces ~450 KB of porcelain. The defect it pins is invisible
|
||||
# at small size — `git … | awk '…exit'` gives the producer SIGPIPE, which
|
||||
# under `set -euo pipefail` aborts the caller silently with rc=141 and no
|
||||
# output. A repo only reaches that once it has enough worktrees, so the
|
||||
# stub supplies the scale instead of the host's own checkout.
|
||||
- bash packages/mosaic/framework/tools/git/test-mosaic-worktree-large-repo.sh
|
||||
|
||||
# Canonical verify:release stage `upgrade-guard`.
|
||||
# Blocking gate (#791): a framework upgrade must never write or delete an
|
||||
|
||||
@@ -18,7 +18,12 @@
|
||||
variables:
|
||||
# Pre-baked CI base (see .woodpecker/ci-image.yml): node:24-alpine +
|
||||
# toolchain + warm pnpm store. Kills the second cold install publish pays.
|
||||
- &node_image 'git.mosaicstack.dev/mosaicstack/stack/ci-base:latest'
|
||||
# PINNED to the immutable lock-tag, not :latest (#1328, brain D27): a mutable
|
||||
# tag resolves per-pod at pull time on the k8s backend and made CI verdicts
|
||||
# non-reproducible (#1324). Byte-identical to :latest at pin time (pushed
|
||||
# atomically by the same kaniko run, main 712c770, 2026-07-26). Bump only via
|
||||
# reviewed PR, per the procedure in .woodpecker/ci.yml's header comment.
|
||||
- &node_image 'git.mosaicstack.dev/mosaicstack/stack/ci-base:lock-9cb7ffcd8828'
|
||||
- &enable_pnpm 'corepack enable'
|
||||
# Heavy kaniko image builds (~25 min) — gate them so a merge that only touches
|
||||
# the npm-only CLI (@mosaicstack/mosaic) or docs does NOT rebuild the platform
|
||||
|
||||
@@ -81,6 +81,73 @@ pnpm format:check # Prettier check
|
||||
pnpm build # Build all packages and applications
|
||||
```
|
||||
|
||||
## Branch Model and Merge Process — `main` and `next` (CANONICAL)
|
||||
|
||||
**Every contribution targets `next` first. No exceptions.** Features, fixes, tests,
|
||||
docs, and policy changes all take the same route; urgency changes queue priority,
|
||||
never the route. Agents never commit to or merge into `main`.
|
||||
|
||||
| Branch | Role | Who merges into it |
|
||||
| ------ | ---------------------------------------------------------------- | --------------------------------------------------------------------------- |
|
||||
| `next` | Integration trunk — the only PR target for contributions | The designated merge-gate agent, after all gates pass. Never the PR author. |
|
||||
| `main` | Stable/release line — receives promotion merges from `next` only | Jason only (or an agent he explicitly delegates for a named promotion). |
|
||||
|
||||
### Contribution sequencing (in order, no skipping)
|
||||
|
||||
1. **Issue first.** Work is tracked in a Gitea issue before a branch exists. The
|
||||
issue number appears in the branch name and the PR body.
|
||||
2. **Branch from the current `origin/next` head.** Name it
|
||||
`feat/…`, `fix/…`, `docs/…`, or `test/…` with the issue number
|
||||
(e.g. `docs/1214-branch-process`). Record the base SHA in the PR body.
|
||||
3. **Develop with evidence.** Applicable tests accompany the change. Hooks are
|
||||
never bypassed (`--no-verify` is prohibited). Stage explicit paths — never
|
||||
`git add -A`.
|
||||
4. **Open the PR against `next`.** The body states: scope, base SHA,
|
||||
verification commands with results, and any known pre-existing failures on
|
||||
the base — documented, not retried to green and not absorbed silently.
|
||||
5. **CI must be terminal-green on the exact head.** All bounded Woodpecker
|
||||
steps succeed (`verify-terminal-green` contract). Pipelines for fork PRs
|
||||
start `blocked`; a maintainer approves the run — approving CI is not
|
||||
approving the PR.
|
||||
6. **Independent review. Self-merge is prohibited** — for every agent, on every
|
||||
PR, including trivial ones. Where the change touches protected or
|
||||
contract-bearing content, the reviewer verifies the exact head
|
||||
(exact-byte/exact-blob comparison), not a description of it. An `AMEND`
|
||||
verdict returns the PR to its author; the reviewer's gate stays held until
|
||||
a fresh exact head passes.
|
||||
7. **Merge into `next`** happens only after CI green + review pass, pinned to
|
||||
the reviewed head SHA (a post-review push voids the review).
|
||||
8. **Promotion `next` → `main`** is a deliberate, Jason-owned reconciliation
|
||||
merge — not part of any contribution's lifecycle. Contributors are done at
|
||||
step 7.
|
||||
|
||||
### Responsibilities
|
||||
|
||||
- **Contributor** — base pinning, green CI, evidence in the PR body,
|
||||
responding to AMEND verdicts, never merging own work.
|
||||
- **Reviewer / merge gate** — independent verification on the exact head;
|
||||
holds and lifts gates; executes the merge into `next`.
|
||||
- **Orchestrator / adjudicator** — cross-PR sequencing, disposition when PRs
|
||||
collide, conflict adjudication.
|
||||
- **Jason** — `next` → `main` promotions, merge-authority grants, collaborator
|
||||
and token provisioning. Agents cannot grant themselves or each other any of
|
||||
these.
|
||||
|
||||
### Hotfixes and divergence
|
||||
|
||||
- A hotfix follows the same path: branch from `next`, PR to `next`, gates,
|
||||
merge, then an expedited Jason-owned promotion if `main` needs it urgently.
|
||||
Committing the fix to `main` directly is prohibited even under pressure.
|
||||
- **Never land work on `main` that is not on `next`.** This has happened
|
||||
(issue #1152's goal controller reached `main` without reaching `next`) and
|
||||
every later PR paid for it. If it happens anyway: transplant the work onto
|
||||
a `next`-based branch with provenance-preserving commits
|
||||
(`git cherry-pick -x` or explicit SHA references in the messages), PR it
|
||||
through the normal gates, and let promotion re-align `main`. Do not
|
||||
hand-patch `main` to compensate.
|
||||
- Force-pushing a branch you do not own is prohibited; rebasing your own PR
|
||||
branch is fine before review, and voids any review already given.
|
||||
|
||||
## Database and Local Runtime Safety
|
||||
|
||||
- Current local data-layer work uses in-process PGlite; leave `DATABASE_URL` unset.
|
||||
|
||||
@@ -74,6 +74,14 @@ The launcher verifies your config, checks for `SOUL.md`, injects your `AGENTS.md
|
||||
|
||||
Pi launches default to a token-lean skill posture: `mosaic pi` passes `--no-skills` so Pi does not preload every global skill description into the system prompt. Use `MOSAIC_PI_SKILL_MODE=all mosaic pi` for the legacy all-skills catalog, or `MOSAIC_PI_SKILL_MODE=discover mosaic pi` to let Pi use its native settings/project skill discovery.
|
||||
|
||||
Mosaic also loads its Pi extensions from `~/.config/mosaic/runtime/pi/`. Inside Pi,
|
||||
`/goal set <statement>` starts a bounded persistent loop that checks every turn and successful
|
||||
compaction, requires two evidence-bearing completion reports, and can be inspected or stopped with
|
||||
`/goal status`, `/goal pause`, `/goal resume`, and `/goal cancel`. Controller-owned goal-state
|
||||
entries redact common credential shapes, but Pi's model/tool-call history is separate, so goals and
|
||||
evidence must never contain secrets or raw sensitive output. Mosaic does not install this extension
|
||||
into `~/.pi/agent/extensions/`.
|
||||
|
||||
### TUI & Gateway
|
||||
|
||||
```bash
|
||||
@@ -331,7 +339,7 @@ The framework is the bash-based standards layer installed to every developer mac
|
||||
├── bin/mosaic ← Unified launcher (claude, codex, opencode, pi, yolo)
|
||||
├── guides/ ← E2E delivery, orchestrator protocol, PRD, etc.
|
||||
├── runtime/ ← Per-runtime configs (claude/, codex/, opencode/, pi/)
|
||||
├── skills/ ← Universal skills (synced from agent-skills repo)
|
||||
├── skills/ ← Universal skills (shipped with the framework package)
|
||||
├── tools/ ← Tool suites (orchestrator, git, quality, prdy, etc.)
|
||||
└── memory/ ← Persistent agent memory (preserved across upgrades)
|
||||
```
|
||||
|
||||
@@ -0,0 +1,332 @@
|
||||
import { type Type } from '@nestjs/common';
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
import type { SlashCommandPayload } from '@mosaicstack/types';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { AgentService, type AgentSession } from '../agent/agent.service.js';
|
||||
import { ProviderService } from '../agent/provider.service.js';
|
||||
import { AppModule } from '../app.module.js';
|
||||
import { CommandAuthorizationService } from '../commands/command-authorization.service.js';
|
||||
import { CommandExecutorService } from '../commands/command-executor.service.js';
|
||||
import { CommandsModule } from '../commands/commands.module.js';
|
||||
import { CommandRuntimeApprovalVerifier } from '../commands/runtime-approval-verifier.js';
|
||||
import { PreferencesModule } from '../preferences/preferences.module.js';
|
||||
import { SystemOverrideService } from '../preferences/system-override.service.js';
|
||||
|
||||
const fakeDb = {
|
||||
$client: { exec: async (): Promise<void> => {} },
|
||||
execute: async (): Promise<{ rows: unknown[] }> => ({ rows: [] }),
|
||||
select: () => ({
|
||||
from: () => ({
|
||||
where: async (): Promise<Array<{ count: number }>> => [{ count: 1 }],
|
||||
}),
|
||||
}),
|
||||
insert: () => ({ values: async (): Promise<void> => {} }),
|
||||
};
|
||||
|
||||
const fakeProviderService = {
|
||||
onModuleInit: async (): Promise<void> => {},
|
||||
onModuleDestroy: (): void => {},
|
||||
getRegistry: () => ({ getAvailable: () => [], getAll: () => [], find: () => undefined }),
|
||||
getDefaultModel: () => undefined,
|
||||
listAvailableModels: () => [],
|
||||
listProviders: () => [],
|
||||
getAdapter: () => undefined,
|
||||
getProvidersHealth: () => [],
|
||||
};
|
||||
|
||||
function compileRealAppGraph(): Promise<TestingModule> {
|
||||
return Test.createTestingModule({ imports: [AppModule] })
|
||||
.overrideProvider('DB_HANDLE')
|
||||
.useValue({ db: fakeDb, close: async (): Promise<void> => {} })
|
||||
.overrideProvider('DB')
|
||||
.useValue(fakeDb)
|
||||
.overrideProvider('STORAGE_ADAPTER')
|
||||
.useValue({
|
||||
name: 'required-security-wiring-test',
|
||||
migrate: async (): Promise<void> => {},
|
||||
close: async (): Promise<void> => {},
|
||||
})
|
||||
.overrideProvider('AUTH')
|
||||
.useValue({})
|
||||
.overrideProvider('BRAIN')
|
||||
.useValue({ conversations: {}, agents: {} })
|
||||
.overrideProvider('LOG_SERVICE')
|
||||
.useValue({})
|
||||
.overrideProvider('MEMORY')
|
||||
.useValue({})
|
||||
.overrideProvider('MEMORY_ADAPTER')
|
||||
.useValue({})
|
||||
.overrideProvider(ProviderService)
|
||||
.useValue(fakeProviderService)
|
||||
.compile();
|
||||
}
|
||||
|
||||
function providerToken(provider: unknown): unknown {
|
||||
return typeof provider === 'function' ? provider : (provider as { provide?: unknown })?.provide;
|
||||
}
|
||||
|
||||
interface MaskingConsumer {
|
||||
moduleType: Type<unknown>;
|
||||
token: Type<unknown>;
|
||||
useValue: object;
|
||||
}
|
||||
|
||||
async function compileWithoutProvider(
|
||||
moduleType: Type<unknown>,
|
||||
missingToken: Type<unknown>,
|
||||
maskingConsumer: MaskingConsumer,
|
||||
): Promise<{ error: unknown; moduleRef: TestingModule | undefined }> {
|
||||
const touchedModules = new Set([moduleType, maskingConsumer.moduleType]);
|
||||
const originals = Array.from(touchedModules, (touchedModule: Type<unknown>) => ({
|
||||
moduleType: touchedModule,
|
||||
providers: (Reflect.getMetadata('providers', touchedModule) ?? []) as unknown[],
|
||||
exports: (Reflect.getMetadata('exports', touchedModule) ?? []) as unknown[],
|
||||
}));
|
||||
|
||||
for (const original of originals) {
|
||||
const providers = original.providers.flatMap((provider: unknown): unknown[] => {
|
||||
const token = providerToken(provider);
|
||||
if (original.moduleType === moduleType && token === missingToken) return [];
|
||||
if (original.moduleType === maskingConsumer.moduleType && token === maskingConsumer.token) {
|
||||
return [{ provide: maskingConsumer.token, useValue: maskingConsumer.useValue }];
|
||||
}
|
||||
return [provider];
|
||||
});
|
||||
const exports = original.exports.filter(
|
||||
(exported: unknown): boolean =>
|
||||
original.moduleType !== moduleType || providerToken(exported) !== missingToken,
|
||||
);
|
||||
Reflect.defineMetadata('providers', providers, original.moduleType);
|
||||
Reflect.defineMetadata('exports', exports, original.moduleType);
|
||||
}
|
||||
|
||||
let moduleRef: TestingModule | undefined;
|
||||
let error: unknown;
|
||||
try {
|
||||
moduleRef = await compileRealAppGraph();
|
||||
} catch (caught: unknown) {
|
||||
error = caught;
|
||||
} finally {
|
||||
for (const original of originals) {
|
||||
Reflect.defineMetadata('providers', original.providers, original.moduleType);
|
||||
Reflect.defineMetadata('exports', original.exports, original.moduleType);
|
||||
}
|
||||
}
|
||||
return { error, moduleRef };
|
||||
}
|
||||
|
||||
async function closeIfCompiled(moduleRef: TestingModule | undefined): Promise<void> {
|
||||
if (moduleRef) await moduleRef.close();
|
||||
}
|
||||
|
||||
describe('required security wiring — real AppModule startup refusal', () => {
|
||||
it('FL-01 positive control: the real graph compiles when CommandAuthorizationService is bound', async () => {
|
||||
const moduleRef = await compileRealAppGraph();
|
||||
try {
|
||||
expect(moduleRef.get(CommandAuthorizationService, { strict: false })).toBeInstanceOf(
|
||||
CommandAuthorizationService,
|
||||
);
|
||||
} finally {
|
||||
await moduleRef.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('FL-01 negative control: absence read as permission is refused at module compilation', async () => {
|
||||
const { error, moduleRef } = await compileWithoutProvider(
|
||||
CommandsModule,
|
||||
CommandAuthorizationService,
|
||||
{
|
||||
moduleType: CommandsModule,
|
||||
token: CommandRuntimeApprovalVerifier,
|
||||
useValue: {},
|
||||
},
|
||||
);
|
||||
await closeIfCompiled(moduleRef);
|
||||
|
||||
expect(
|
||||
error,
|
||||
'absence read as permission: AppModule compilation accepted a missing CommandAuthorizationService binding',
|
||||
).toBeInstanceOf(Error);
|
||||
if (!(error instanceof Error)) return;
|
||||
expect(error.message).toContain('CommandExecutorService');
|
||||
expect(error.message).toContain('CommandAuthorizationService');
|
||||
});
|
||||
|
||||
it('FL-11 positive control: the real graph compiles when SystemOverrideService is bound', async () => {
|
||||
const moduleRef = await compileRealAppGraph();
|
||||
try {
|
||||
expect(moduleRef.get(SystemOverrideService, { strict: false })).toBeInstanceOf(
|
||||
SystemOverrideService,
|
||||
);
|
||||
} finally {
|
||||
await moduleRef.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('FL-11 negative control: absence read as permission is refused at module compilation', async () => {
|
||||
const { error, moduleRef } = await compileWithoutProvider(
|
||||
PreferencesModule,
|
||||
SystemOverrideService,
|
||||
{
|
||||
moduleType: CommandsModule,
|
||||
token: CommandExecutorService,
|
||||
useValue: {},
|
||||
},
|
||||
);
|
||||
await closeIfCompiled(moduleRef);
|
||||
|
||||
expect(
|
||||
error,
|
||||
'absence read as permission: AppModule compilation accepted a missing SystemOverrideService binding',
|
||||
).toBeInstanceOf(Error);
|
||||
if (!(error instanceof Error)) return;
|
||||
expect(error.message).toContain('AgentService');
|
||||
expect(error.message).toContain('SystemOverrideService');
|
||||
});
|
||||
});
|
||||
|
||||
const actorScope = { userId: 'security-user', tenantId: 'security-tenant' };
|
||||
const conversationId = 'security-conversation';
|
||||
|
||||
function directExecutorWithoutAuthorization(systemOverrideSet: ReturnType<typeof vi.fn>) {
|
||||
const registry = {
|
||||
getManifest: vi.fn(() => ({
|
||||
version: 1,
|
||||
commands: [
|
||||
{
|
||||
name: 'system',
|
||||
aliases: [],
|
||||
description: 'Set instruction authority',
|
||||
scope: 'agent' as const,
|
||||
execution: 'socket' as const,
|
||||
available: true,
|
||||
},
|
||||
],
|
||||
skills: [],
|
||||
})),
|
||||
};
|
||||
return new CommandExecutorService(
|
||||
registry as never,
|
||||
{ getSession: vi.fn() } as never,
|
||||
{ set: systemOverrideSet, clear: vi.fn() } as never,
|
||||
{ collect: vi.fn() } as never,
|
||||
null,
|
||||
{ agents: {} } as never,
|
||||
null,
|
||||
null,
|
||||
{ getServerStatuses: vi.fn(() => []), getToolDefinitions: vi.fn(() => []) } as never,
|
||||
undefined as never,
|
||||
);
|
||||
}
|
||||
|
||||
function directAgentWithoutSystemOverride(piPrompt: ReturnType<typeof vi.fn>): {
|
||||
service: AgentService;
|
||||
session: AgentSession;
|
||||
} {
|
||||
const service = new AgentService(
|
||||
{
|
||||
getDefaultModel: vi.fn(() => null),
|
||||
getRegistry: vi.fn(() => ({})),
|
||||
findModel: vi.fn(),
|
||||
listAvailableModels: vi.fn(() => []),
|
||||
} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
{ available: false } as never,
|
||||
{} as never,
|
||||
{ getToolDefinitions: vi.fn(() => []) } as never,
|
||||
{ loadForSession: vi.fn(async () => ({ metaTools: [], promptAdditions: [] })) } as never,
|
||||
undefined as never,
|
||||
null,
|
||||
{ collect: vi.fn().mockResolvedValue(undefined) } as never,
|
||||
null,
|
||||
);
|
||||
const session = {
|
||||
id: conversationId,
|
||||
provider: 'test-provider',
|
||||
modelId: 'test-model',
|
||||
piSession: { prompt: piPrompt },
|
||||
listeners: new Set(),
|
||||
unsubscribe: vi.fn(),
|
||||
createdAt: Date.now(),
|
||||
promptCount: 0,
|
||||
channels: new Set(),
|
||||
skillPromptAdditions: [],
|
||||
sandboxDir: process.cwd(),
|
||||
allowedTools: null,
|
||||
userId: actorScope.userId,
|
||||
tenantId: actorScope.tenantId,
|
||||
metrics: {
|
||||
tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
||||
modelSwitches: 0,
|
||||
messageCount: 0,
|
||||
lastActivityAt: new Date(0).toISOString(),
|
||||
},
|
||||
} as unknown as AgentSession;
|
||||
const internals = service as unknown as { sessions: Map<string, AgentSession> };
|
||||
internals.sessions.set(conversationId, session);
|
||||
return { service, session };
|
||||
}
|
||||
|
||||
describe('required security wiring — malformed direct absence has zero effects', () => {
|
||||
it('FL-01 refuses command execution before any command effect when authorization is absent', async () => {
|
||||
const systemOverrideSet = vi.fn().mockResolvedValue(undefined);
|
||||
const executor = directExecutorWithoutAuthorization(systemOverrideSet);
|
||||
const payload: SlashCommandPayload = {
|
||||
command: 'system',
|
||||
args: 'authority that must not be stored',
|
||||
conversationId,
|
||||
};
|
||||
let error: unknown;
|
||||
|
||||
try {
|
||||
await executor.execute(payload, actorScope);
|
||||
} catch (caught: unknown) {
|
||||
error = caught;
|
||||
}
|
||||
|
||||
expect
|
||||
.soft(
|
||||
error,
|
||||
'absence read as permission: direct executor accepted missing command authorization',
|
||||
)
|
||||
.toBeInstanceOf(Error);
|
||||
expect
|
||||
.soft(
|
||||
systemOverrideSet,
|
||||
'absence read as permission: command effect occurred without command authorization',
|
||||
)
|
||||
.not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('FL-11 refuses prompt execution before any provider or session effect when system override authority is absent', async () => {
|
||||
const piPrompt = vi.fn().mockResolvedValue(undefined);
|
||||
const { service, session } = directAgentWithoutSystemOverride(piPrompt);
|
||||
let error: unknown;
|
||||
|
||||
try {
|
||||
await service.prompt(conversationId, 'must not reach provider', actorScope);
|
||||
} catch (caught: unknown) {
|
||||
error = caught;
|
||||
}
|
||||
|
||||
expect
|
||||
.soft(
|
||||
error,
|
||||
'absence read as permission: direct session accepted missing system override authority',
|
||||
)
|
||||
.toBeInstanceOf(Error);
|
||||
expect
|
||||
.soft(
|
||||
piPrompt,
|
||||
'absence read as permission: provider prompt occurred without system override authority',
|
||||
)
|
||||
.not.toHaveBeenCalled();
|
||||
expect
|
||||
.soft(
|
||||
session.promptCount,
|
||||
'absence read as permission: session state changed without system override authority',
|
||||
)
|
||||
.toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -26,7 +26,7 @@ function makeService(operatorMemory: unknown = null): AgentService {
|
||||
{} as never,
|
||||
{ getToolDefinitions: vi.fn(() => []) } as never,
|
||||
{ loadForSession: vi.fn(async () => ({ metaTools: [], promptAdditions: [] })) } as never,
|
||||
null,
|
||||
{ get: vi.fn().mockResolvedValue(null), renew: vi.fn().mockResolvedValue(undefined) } as never,
|
||||
null,
|
||||
{ collect: vi.fn().mockResolvedValue(undefined) } as never,
|
||||
operatorMemory as never,
|
||||
|
||||
@@ -132,9 +132,8 @@ export class AgentService implements OnModuleDestroy {
|
||||
@Inject(CoordService) private readonly coordService: CoordService,
|
||||
@Inject(McpClientService) private readonly mcpClientService: McpClientService,
|
||||
@Inject(SkillLoaderService) private readonly skillLoaderService: SkillLoaderService,
|
||||
@Optional()
|
||||
@Inject(SystemOverrideService)
|
||||
private readonly systemOverride: SystemOverrideService | null,
|
||||
private readonly systemOverride: SystemOverrideService,
|
||||
@Optional()
|
||||
@Inject(PreferencesService)
|
||||
private readonly preferencesService: PreferencesService | null,
|
||||
@@ -709,23 +708,22 @@ export class AgentService implements OnModuleDestroy {
|
||||
throw new Error(`No agent session found: ${sessionId}`);
|
||||
}
|
||||
this.assertSessionScope(session, scope);
|
||||
session.promptCount += 1;
|
||||
|
||||
// Channel attachments are untrusted URI references. Preserve exact,
|
||||
// authenticated metadata for the agent without treating it as authority.
|
||||
const attachmentContext = this.attachmentContext(attachments);
|
||||
|
||||
// Prepend session-scoped system override if present (renew TTL on each turn)
|
||||
// Prepend session-scoped system override if present (renew TTL on each turn).
|
||||
// Required instruction-authority wiring is consulted before session/provider effects.
|
||||
let effectiveMessage = `${message}${attachmentContext}`;
|
||||
if (this.systemOverride) {
|
||||
const override = await this.systemOverride.get(sessionId, scope);
|
||||
if (override) {
|
||||
effectiveMessage = `[System Override]\n${override}\n\n${effectiveMessage}`;
|
||||
await this.systemOverride.renew(sessionId, scope);
|
||||
this.logger.debug(`Applied system override for session ${sessionId}`);
|
||||
}
|
||||
const override = await this.systemOverride.get(sessionId, scope);
|
||||
if (override) {
|
||||
effectiveMessage = `[System Override]\n${override}\n\n${effectiveMessage}`;
|
||||
await this.systemOverride.renew(sessionId, scope);
|
||||
this.logger.debug(`Applied system override for session ${sessionId}`);
|
||||
}
|
||||
|
||||
session.promptCount += 1;
|
||||
try {
|
||||
await session.piSession.prompt(effectiveMessage);
|
||||
} catch (err) {
|
||||
|
||||
@@ -80,6 +80,10 @@ const mockMcpClient = {
|
||||
getToolDefinitions: vi.fn(() => []),
|
||||
};
|
||||
|
||||
const allowAuthorization = {
|
||||
authorize: vi.fn().mockResolvedValue({ allowed: true }),
|
||||
};
|
||||
|
||||
function buildService(
|
||||
redis: typeof mockRedis | null = mockRedis,
|
||||
mcpClient: {
|
||||
@@ -98,6 +102,7 @@ function buildService(
|
||||
null,
|
||||
mockChatGateway as never,
|
||||
mcpClient as never,
|
||||
allowAuthorization as never,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,9 +35,8 @@ export class CommandExecutorService {
|
||||
@Inject(forwardRef(() => ChatGateway))
|
||||
private readonly chatGateway: ChatGateway | null,
|
||||
@Inject(McpClientService) private readonly mcpClient: McpClientService,
|
||||
@Optional()
|
||||
@Inject(CommandAuthorizationService)
|
||||
private readonly authorization: CommandAuthorizationService | null = null,
|
||||
private readonly authorization: CommandAuthorizationService,
|
||||
) {}
|
||||
|
||||
async execute(
|
||||
@@ -57,13 +56,13 @@ export class CommandExecutorService {
|
||||
};
|
||||
}
|
||||
|
||||
const authorization = await this.authorization?.authorize(
|
||||
const authorization = await this.authorization.authorize(
|
||||
def,
|
||||
payload,
|
||||
userId,
|
||||
payload.approvalId,
|
||||
);
|
||||
if (authorization && !authorization.allowed) {
|
||||
if (!authorization.allowed) {
|
||||
return { command, conversationId, success: false, message: authorization.reason };
|
||||
}
|
||||
|
||||
@@ -171,7 +170,7 @@ export class CommandExecutorService {
|
||||
const def = this.registry
|
||||
.getManifest()
|
||||
.commands.find((command) => command.name === payload.command);
|
||||
if (!def || !this.authorization) return null;
|
||||
if (!def) return null;
|
||||
return this.authorization.createApproval(def, payload, scope.userId);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,10 @@ const mockMcpClient = {
|
||||
reconnectServer: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
|
||||
const allowAuthorization = {
|
||||
authorize: vi.fn().mockResolvedValue({ allowed: true }),
|
||||
};
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
function buildRegistry(): CommandRegistryService {
|
||||
@@ -74,6 +78,7 @@ function buildExecutor(registry: CommandRegistryService): CommandExecutorService
|
||||
null, // reloadService (optional)
|
||||
null, // chatGateway (optional)
|
||||
mockMcpClient as never,
|
||||
allowAuthorization as never,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -245,9 +245,21 @@ describe('EnrollmentService.createToken', () => {
|
||||
const after = Date.now();
|
||||
|
||||
const expiresMs = new Date(result.expiresAt).getTime();
|
||||
// Should be at most 900s from now
|
||||
expect(expiresMs - before).toBeLessThanOrEqual(900_000 + 100);
|
||||
|
||||
// The property under test is CLAMPING: a 9999s request must come back as 900s.
|
||||
// The gap between clamped and unclamped is 9_099_000 ms, so the tolerance below
|
||||
// only has to exceed CI scheduling jitter — it does not need to be tight to keep
|
||||
// the assertion discriminating. A 5s allowance consumes 0.05% of that margin and
|
||||
// an unclamped result still misses by three orders of magnitude.
|
||||
//
|
||||
// It was 100ms and failed on a loaded agent at 900_106 — 6ms over (#1090). A
|
||||
// wall-clock budget sized to a fast machine is a flake, not a tighter test.
|
||||
const CI_JITTER_MS = 5_000;
|
||||
expect(expiresMs - before).toBeLessThanOrEqual(900_000 + CI_JITTER_MS);
|
||||
expect(expiresMs - after).toBeGreaterThanOrEqual(0);
|
||||
// Explicitly pin the clamp itself, independent of any timing allowance:
|
||||
// unclamped (9999s) would exceed this by ~9_099_000 ms.
|
||||
expect(expiresMs - before).toBeLessThan(1_000_000);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -159,6 +159,7 @@ describe('ReloadService — /reload command sanitizes plugin errors', () => {
|
||||
reloadService,
|
||||
mockChatGateway as never,
|
||||
mockMcpClient as never,
|
||||
{ authorize: vi.fn().mockResolvedValue({ allowed: true }) } as never,
|
||||
);
|
||||
|
||||
const payload: SlashCommandPayload = { command: 'reload', conversationId: 'conv-1' };
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Administrator Guide
|
||||
|
||||
> **Status:** Partially migrated. Current SSO and local upgrade/recovery procedures are available; held procedures are labeled non-operative.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Administrator Operations
|
||||
|
||||
> **Status:** Partially migrated. Procedures explicitly identify whether they are current or held.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Mos Connector Lease Operations — M1
|
||||
|
||||
> **Status:** Held / non-operative.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Upgrade safety and recovery
|
||||
|
||||
> **Supported route:** an already installed `mosaic` CLI using the local PGlite
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Security
|
||||
|
||||
> **Status:** Partially migrated. The SSO provider and Discord ingress security pages are current.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# 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.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
title: SSO Providers
|
||||
type: runbook
|
||||
audience: admin
|
||||
status: current
|
||||
source_of_truth: false
|
||||
---
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# API Documentation
|
||||
|
||||
> **Status:** Scaffold only. The canonical gateway contract has not yet been migrated into this directory.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Developer Guide
|
||||
|
||||
> **Status:** Partially migrated. Architecture, lease-broker verification, and channel-adapter authoring pages are current; other contributor chapters remain unmigrated.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Architecture
|
||||
|
||||
> **Status:** Partially migrated. The lease-broker security-contract pages below are current references; the remaining architecture pages are still being classified.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# 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.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Compaction observer revocation and runtime generations
|
||||
|
||||
> **Status:** Current contract reference.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Architecture Decisions
|
||||
|
||||
> **Status:** Current decision index. A decision describes an implemented and accepted boundary; draft proposals belong under `rfcs/` or `docs/plans/`.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: record
|
||||
status: active
|
||||
---
|
||||
|
||||
# Mos Runtime Portability M1 — Logical Identity and Fencing
|
||||
|
||||
> **Decision status:** Current implemented decision (M1).
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Authenticated external lease broker protocol
|
||||
|
||||
> **Status:** Current contract reference.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# WI-1 lease broker security notes
|
||||
|
||||
> **Status:** Current contract reference.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Whole mutator-class lease gate
|
||||
|
||||
> **Status:** Current contract reference.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# 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.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: spec
|
||||
status: active
|
||||
---
|
||||
|
||||
# RFC: Optional AI Egress Gateways
|
||||
|
||||
> **Status:** Draft / proposed — not approved, not current, and not integrated.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# 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.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
title: Lease-broker operations
|
||||
type: runbook
|
||||
audience: developer
|
||||
status: current
|
||||
source_of_truth: false
|
||||
---
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: tracking
|
||||
status: active
|
||||
---
|
||||
|
||||
# Mission Manifest — MVP
|
||||
|
||||
> Top-level rollup tracking Mosaic Stack MVP execution.
|
||||
|
||||
+251
@@ -1,5 +1,19 @@
|
||||
---
|
||||
kind: spec
|
||||
status: active
|
||||
---
|
||||
|
||||
# PRD: Mosaic Stack v0.1.0
|
||||
|
||||
## Current addendum: #1194 — Installed framework-tool drift detection
|
||||
|
||||
- Compare the framework tools shipped with the executing Mosaic package against the deployed `$MOSAIC_HOME/tools` tree by content hash.
|
||||
- Treat every shipped `tools/**` file as framework-owned/required according to `framework-manifest.txt`, while excluding the explicit operator-owned credential carve-out and preserving installed-only operator/unknown files.
|
||||
- Distinguish and count `IN_SYNC`, `STALE`, `NOT_INSTALLED`, and installed-only classifications; fail non-zero when shipped tools are stale or absent and refuse self-comparison that would make drift unobservable.
|
||||
- Surface the observational check through `mosaic doctor`; do not refresh files, restart seats, or mutate live tooling.
|
||||
- Document identity/messaging/gate behavior changes in the current stale set, the reviewed quiet-window keep-mode refresh command, and post-refresh probes against the installed path.
|
||||
- Prove by construction that a stale and missing deployed tool are detected; that regression must fail before this checker exists.
|
||||
|
||||
## Metadata
|
||||
|
||||
- **Owner:** Jason Woltje
|
||||
@@ -102,6 +116,128 @@ Context compaction, session replacement, and same-PID runtime reloads can leave
|
||||
|
||||
---
|
||||
|
||||
## Pi Persistent Goal Loop (#1150)
|
||||
|
||||
### Problem and objective
|
||||
|
||||
A Pi agent can stop after a plausible-looking answer even when the operator's broader objective is
|
||||
not complete, and ordinary compaction can weaken or omit the original objective. Mosaic needs an
|
||||
optional, operator-controlled goal loop that keeps a Pi session oriented, checks progress at native
|
||||
lifecycle boundaries, and resumes work until completion is verified or a bounded safety state is
|
||||
reached.
|
||||
|
||||
The objective is a Mosaic-owned Pi extension deployed from the framework into
|
||||
`~/.config/mosaic/runtime/pi/`. It must not install into or depend on `~/.pi/agent/extensions/`.
|
||||
|
||||
### Scope
|
||||
|
||||
#### In scope
|
||||
|
||||
1. `PGL-REQ-01`: The framework SHALL ship a dedicated Pi goal extension under
|
||||
`packages/mosaic/framework/runtime/pi/`, seed it under `$MOSAIC_HOME/runtime/pi/`, and make
|
||||
`mosaic pi` load it alongside the core Mosaic extension when present.
|
||||
2. `PGL-REQ-02`: `/goal` SHALL support setting a goal plus status, pause, resume, cancel, and help
|
||||
operations without silently replacing an active goal.
|
||||
3. `PGL-REQ-03`: Active branch-specific goal state SHALL be persisted in Pi custom session entries,
|
||||
restored on session start and tree navigation, and never rely on a compaction summary as its
|
||||
source of truth.
|
||||
4. `PGL-REQ-04`: A hidden goal contract SHALL be injected through Pi's `context` event before every
|
||||
model request so it remains effective across tool turns, retries, and post-compaction requests.
|
||||
5. `PGL-REQ-05`: The harness SHALL inspect every `turn_end` and successful `session_compact` event.
|
||||
A structured terminating goal-report tool SHALL capture `continue`, evidence-bearing `achieved`,
|
||||
or `blocked` status without requiring a redundant model turn.
|
||||
6. `PGL-REQ-06`: An achievement claim SHALL remain provisional until a second consecutive
|
||||
evidence-bearing verification report. Any continuation report or successful compaction during
|
||||
verification SHALL reset the verification sequence.
|
||||
7. `PGL-REQ-07`: Continuation SHALL be initiated at safe lifecycle boundaries, primarily
|
||||
`agent_settled`; manual compaction and restored active sessions may schedule a deferred idle
|
||||
continuation without re-entering compaction handlers.
|
||||
8. `PGL-REQ-08`: The loop SHALL have operator cancellation plus bounded turn and repeated-no-progress
|
||||
limits. Exhausted or blocked goals pause rather than continuing indefinitely.
|
||||
9. `PGL-REQ-09`: Framework installation and update SHALL preserve normal manifest ownership: the
|
||||
goal extension is framework-owned under `runtime/**`, while no goal extension or configuration
|
||||
asset is created or modified under the operator's main Pi configuration. Pi remains the owner of
|
||||
its native session files used by `appendEntry()`.
|
||||
|
||||
#### Out of scope
|
||||
|
||||
1. A mathematical guarantee that an arbitrary natural-language goal is semantically complete.
|
||||
2. Automatically executing user-supplied shell predicates or accepting executable validation code in
|
||||
`/goal` arguments.
|
||||
3. Restarting Pi after process, host, or supervisor failure; the existing Mosaic fleet/runtime
|
||||
supervisor owns process durability.
|
||||
4. Gateway, database, web UI, Discord, or cross-harness goal orchestration in this slice.
|
||||
|
||||
### User and stakeholder requirements
|
||||
|
||||
- An operator can start a goal from Pi and see its current phase, evidence, limits, and latest report.
|
||||
- The agent remains oriented after each turn and compaction until verified, paused, blocked,
|
||||
exhausted, or cancelled.
|
||||
- Local testing uses a file under `~/.config/mosaic/runtime/pi/`; the feature never writes an
|
||||
extension asset to `~/.pi/agent/extensions/`.
|
||||
- Framework updates deploy the same reviewed extension source through Mosaic's existing manifest
|
||||
sync path.
|
||||
|
||||
### Non-functional requirements
|
||||
|
||||
1. **Safety:** bounded continuation, explicit cancellation, no arbitrary command execution, and no
|
||||
completion without non-empty reported evidence.
|
||||
2. **Reliability:** serialized continuation scheduling, branch-aware restoration, compaction-safe
|
||||
context injection, and stale-timer cancellation on session shutdown.
|
||||
3. **Performance:** no extra nested judge-model request on every turn; structured reporting uses the
|
||||
active agent's final terminating tool call.
|
||||
4. **Observability:** Pi status/notifications expose phase and bounded counters without recording
|
||||
credentials or hidden model reasoning.
|
||||
5. **Maintainability:** the state machine is deterministic and behavior-tested independently from Pi
|
||||
provider/network access.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
1. `AC-PGL-01`: A framework-sync fixture installs the extension at
|
||||
`$MOSAIC_HOME/runtime/pi/goal-extension.ts`, and launcher tests prove both Mosaic Pi extensions are
|
||||
emitted in deterministic order while absent optional files remain backward-compatible.
|
||||
2. `AC-PGL-02`: Command tests prove set/status/pause/resume/cancel behavior, active-goal replacement
|
||||
refusal, and bounded input handling.
|
||||
3. `AC-PGL-03`: Lifecycle tests prove every turn is recorded, active context is injected on every
|
||||
request, two evidence-bearing achievement reports are required, and `agent_settled` continues an
|
||||
unmet goal without duplicate scheduling.
|
||||
4. `AC-PGL-04`: Compaction and restoration tests prove goal state survives, verification is reset and
|
||||
rechecked after compaction, manual compaction continuation is deferred until idle, and tree/session
|
||||
branch state is reconstructed correctly.
|
||||
5. `AC-PGL-05`: Limit tests prove max-turn and repeated-no-progress exhaustion stop autonomous
|
||||
continuation, while pause/cancel/blocked states do not restart.
|
||||
6. `AC-PGL-06`: Focused tests, package typecheck/lint/test, repository quality gates, a local Pi load
|
||||
smoke test from `~/.config/mosaic/runtime/pi/`, independent review, and terminal-green CI pass before
|
||||
issue #1150 closes.
|
||||
|
||||
### Constraints, risks, and assumptions
|
||||
|
||||
- Dependency: Pi's extension API must continue to provide `registerCommand`, `registerTool`,
|
||||
`context`, `turn_end`, `agent_settled`, `session_compact`, session custom entries, and terminating
|
||||
tool results.
|
||||
- Risk: the working agent can overstate completion. Mitigation: structured evidence, a mandatory
|
||||
second verification pass, explicit semantic limitations, and operator-visible reports.
|
||||
- Risk: an impossible goal can consume unbounded resources. Mitigation: hard turn/no-progress bounds
|
||||
and paused terminal states.
|
||||
- Risk: automatic continuation can race compaction or session replacement. Mitigation: drive from
|
||||
`agent_settled`, defer idle restarts, generation-check timers, and clear timers on shutdown.
|
||||
- `ASSUMPTION:` Two consecutive evidence-bearing reports are the initial local verification policy;
|
||||
rationale: it provides a real recheck without doubling every turn's model cost. Future policy may
|
||||
add independent or deterministic validators.
|
||||
- `ASSUMPTION:` Default limits are 40 turns and 6 repeated no-progress reports, configurable only by
|
||||
bounded Mosaic environment settings; rationale: useful persistence with a finite autonomous budget.
|
||||
- `ASSUMPTION:` Documentation remains canonical in-repo for this slice; no external docs publication
|
||||
is requested.
|
||||
|
||||
### Testing and delivery intent
|
||||
|
||||
Use TDD for the deterministic controller and lifecycle invariants. Test with fake Pi lifecycle
|
||||
objects first, then run a local load/smoke test from the deployed Mosaic path. Deliver source, tests,
|
||||
launcher wiring, framework/runtime documentation, user/developer guides, and sitemap updates in one
|
||||
reviewed squash PR to `main` with terminal-green CI.
|
||||
|
||||
---
|
||||
|
||||
## Fleet Declarative Configuration Management Workstream (FCM, #758)
|
||||
|
||||
### Problem and objective
|
||||
@@ -146,6 +282,68 @@ lands. M0 consists only of these normative requirements, the complete task DAG,
|
||||
documentation IA checklist, and the legacy example/profile disposition inventory. Subsequent cards
|
||||
are defined in [docs/TASKS.md](./TASKS.md) and must remain one card/one PR.
|
||||
|
||||
### Fleet git identity launch propagation (#1043)
|
||||
|
||||
#### Problem and objective
|
||||
|
||||
A fleet seat can have a registered per-agent Git credential while its launched runtime process lacks
|
||||
`MOSAIC_GIT_IDENTITY`. The credential resolver then cannot select the seat identity reliably, which
|
||||
blocks repository operations on fail-closed estates and can fall through to an unrelated identity on
|
||||
estates where that refusal is not active. The objective is to make Git identity a deterministic,
|
||||
roster-derived part of the generated launch projection and prove it reaches the launched process.
|
||||
|
||||
#### Normative requirements
|
||||
|
||||
1. `FGI-REQ-01`: Every generated fleet agent projection SHALL declare
|
||||
`MOSAIC_GIT_IDENTITY=<MOSAIC_AGENT_NAME>`; a differing or unsafe identity SHALL fail closed before
|
||||
tmux launch.
|
||||
2. `FGI-REQ-02`: The clean `/usr/bin/env -i` pane boundary SHALL pass every variable declared by the
|
||||
generated projection, including `MOSAIC_GIT_IDENTITY`, to the launched runtime process.
|
||||
3. `FGI-REQ-03`: A behavioral integration test SHALL set-compare the complete generated projection
|
||||
against the launched process environment. Source-text/string-presence assertions are insufficient.
|
||||
4. `FGI-REQ-04`: Verification SHALL include RED-first evidence and a delete-the-subject mutation that
|
||||
removes Git-identity pane propagation and makes the behavioral test fail.
|
||||
|
||||
#### Acceptance criteria
|
||||
|
||||
1. `AC-FGI-01`: A launched seat process contains every key/value pair declared by its generated
|
||||
environment projection, including the roster-derived Git identity.
|
||||
2. `AC-FGI-02`: Missing, unsafe, or split Git identity is rejected before a tmux session is created.
|
||||
3. `AC-FGI-03`: Focused launcher and generated-environment tests, repository quality gates,
|
||||
independent review, and the required RED/green/R7 evidence are recorded before push.
|
||||
|
||||
### Framework shell assertion portability (#1098)
|
||||
|
||||
#### Problem and objective
|
||||
|
||||
The blocking framework-shell chain can report that a pane command omitted `/usr/bin/env -i` even when
|
||||
`-i` matched successfully. A short-circuiting `grep -q` under `set -o pipefail` may close its pipe after
|
||||
the match and cause an upstream producer to exit with SIGPIPE, turning a valid semantic result into a
|
||||
nonzero aggregate pipeline. The objective is to inspect the captured NUL-delimited argv directly and
|
||||
make failures carry the observed records needed for diagnosis.
|
||||
|
||||
#### Normative requirements
|
||||
|
||||
1. `FSP-REQ-01`: The pane-boundary test SHALL validate an adjacent `/usr/bin/env`, `-i` argv pair from
|
||||
the authoritative NUL-delimited tmux capture without a short-circuit pipeline whose upstream status
|
||||
can override a successful match.
|
||||
2. `FSP-REQ-02`: Missing, reversed, or non-adjacent boundary tokens SHALL fail, while valid boundaries
|
||||
SHALL remain valid regardless of trailing argv size, pipe capacity, process scheduling, or host/CI
|
||||
utility implementation.
|
||||
3. `FSP-REQ-03`: A failed boundary check SHALL print stable indexed, shell-escaped observed argv records
|
||||
before exiting nonzero; the fixture SHALL continue to contain generated non-secret launch data only.
|
||||
4. `FSP-REQ-04`: Verification SHALL include RED-first large-payload evidence, negative token-order
|
||||
controls, the complete focused launcher suite, canonical Woodpecker CI, and independent review.
|
||||
|
||||
#### Acceptance criteria
|
||||
|
||||
1. `AC-FSP-01`: A large captured argv with adjacent `/usr/bin/env`, `-i` passes even when the former
|
||||
`grep -q` pipeline returns nonzero from an upstream SIGPIPE.
|
||||
2. `AC-FSP-02`: Missing executable, missing flag, and detached/reversed flag fixtures return nonzero and
|
||||
emit the indexed observed argv.
|
||||
3. `AC-FSP-03`: The focused suite passes on the development host and CI image, and the merged-main
|
||||
Woodpecker pipeline is terminal green before #1098 closes.
|
||||
|
||||
---
|
||||
|
||||
## Exact Cross-Harness Fleet Communications Contract (#766)
|
||||
@@ -1345,6 +1543,59 @@ All work is **alpha** (< 0.1.0) until Jason approves 0.1.0 beta release.
|
||||
|
||||
---
|
||||
|
||||
## Workspace placement guard hardening (#1174)
|
||||
|
||||
### Problem and objective
|
||||
|
||||
The Bash pre-tool guard must prevent Git checkouts and repository state from being placed under
|
||||
`$HOME` without refusing ordinary Git commands merely because a source, option value, branch name,
|
||||
or metadata mentions `$HOME`. A guard that over-blocks routine work is unsafe because operators
|
||||
will route around it.
|
||||
|
||||
### Scope and requirements
|
||||
|
||||
1. `WPG-REQ-01`: `git clone` and `git worktree add` placement SHALL be judged from their placement
|
||||
operands, not from every HOME-shaped word in the command.
|
||||
2. `WPG-REQ-02`: Clone sources, references, templates, environment assignments, and non-placement
|
||||
worktree metadata MAY resolve under HOME when all placement operands resolve elsewhere.
|
||||
3. `WPG-REQ-03`: Both attached and separate-value `--separate-git-dir` forms SHALL remain placement
|
||||
operands and SHALL be refused when they resolve under HOME.
|
||||
4. `WPG-REQ-04`: Option classification SHALL account for Git's rule-generated boolean negations
|
||||
without relying on an enumerable allowlist of flag spellings.
|
||||
5. `WPG-REQ-05`: Quote removal, escapes, shell command boundaries, redirections, and end-of-options
|
||||
handling SHALL preserve existing fail-closed checkout coverage.
|
||||
6. `WPG-REQ-06`: Absolute placement aliases SHALL resolve shell-known HOME spellings, dot segments,
|
||||
repeated separators, and existing symlink parents before the HOME boundary comparison.
|
||||
7. Relative targets whose effective path depends on the shell cwd are out of scope and tracked by
|
||||
#1197.
|
||||
|
||||
### Acceptance and verification
|
||||
|
||||
1. Git's own option parser accepts each tested flag, including generated `--no-*` forms, while the
|
||||
guard allows a HOME-valued source with an explicit safe destination.
|
||||
2. Equivalent clone and worktree fixtures cover rule-generated negations and remain discriminating
|
||||
against the prior head where the defect existed.
|
||||
3. Real HOME destinations and both `--separate-git-dir` forms remain blocked, including placements
|
||||
after shell command boundaries.
|
||||
4. The full hermetic guard suite, syntax/static checks, adversarial probes, independent review, and
|
||||
terminal-green CI pass before merge.
|
||||
5. Any option-classification residual is documented with its deliberate failure direction.
|
||||
|
||||
### Constraints, risks, and assumptions
|
||||
|
||||
- Security and usability are co-equal: neither a placement bypass nor routine over-block is an
|
||||
acceptable repair.
|
||||
- `ASSUMPTION:` The value-taking option surface exposed by the installed Git version is closed and
|
||||
measurable through Git's own parser/help output; rationale: boolean flags are rule-generated,
|
||||
while separate-value options have explicit grammar and must be classified as such.
|
||||
- Risk: a future Git release may add a new value-taking placement option. Mitigation: document the
|
||||
chosen residual direction and pin every currently supported placement option in behavior tests.
|
||||
- Risk: a symlink can be replaced after pre-execution canonicalization. Mitigation: resolve every
|
||||
existing parent physically and document the remaining inherent TOCTOU window; the worktree helper
|
||||
remains the authoritative path-derivation mechanism, with atomic closure tracked by #1199.
|
||||
|
||||
---
|
||||
|
||||
## Assumptions
|
||||
|
||||
1. RESOLVED: **pgvector is sufficient** for semantic search at v0.1.0 scale (personal/family/team = thousands to low hundreds-of-thousands of vectors). `@mosaicstack/memory` defines a `VectorStore` interface with pgvector as the default adapter. The interface boundary makes Qdrant a drop-in migration if PG resource contention or scale demands it later. Zero additional infrastructure for v0.1.0. Rationale: Reduces ops burden; pgvector HNSW indexes are fast at this scale; interface abstraction costs almost nothing now.
|
||||
|
||||
+44
-8
@@ -1,3 +1,9 @@
|
||||
---
|
||||
kind: spec
|
||||
source_of_truth: true
|
||||
status: active
|
||||
---
|
||||
|
||||
# Mosaic Stack Documentation
|
||||
|
||||
This directory is the canonical home for Mosaic Stack product, architecture, API, operations, and delivery documentation.
|
||||
@@ -146,21 +152,51 @@ Every canonical page should:
|
||||
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:
|
||||
Required front matter for every canonical page:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: Human-readable page title
|
||||
type: guide
|
||||
audience: developer
|
||||
status: current
|
||||
source_of_truth: false
|
||||
kind: tracking | projection | spec | guide | record | superseded
|
||||
status: active # or: completed | superseded-by: <path>
|
||||
source_of_truth: false # optional, defaults false
|
||||
audience: developer # optional: user | admin | developer | all
|
||||
title: Human-readable page title # optional
|
||||
---
|
||||
```
|
||||
|
||||
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`.
|
||||
`kind` says what the document **is**. One value, required, and it follows the document's content,
|
||||
never its filename: a file named `TASKS.md` whose body says "this is a build plan, not a task
|
||||
tracker" is a `spec`.
|
||||
|
||||
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.
|
||||
| kind | rule |
|
||||
| ---------- | ---------------------------------------------------------------- |
|
||||
| tracking | Live state, single-writer. Never a spec |
|
||||
| projection | Generated. Never hand-edited. MUST have a drift test |
|
||||
| spec | How to build one goal or workstream |
|
||||
| guide | Explains use. Decides nothing |
|
||||
| record | What happened. Never authoritative, never updated after the fact |
|
||||
| superseded | Kept for history, and NAMES its replacement |
|
||||
|
||||
`source_of_truth` is a separate boolean because authority is **orthogonal to kind**. A document can
|
||||
be a `spec` and still be the thing everything else answers to;
|
||||
`docs/requirements/native-kanban-sot.md` is exactly that. Folding authority into `kind` forced one
|
||||
field to carry two independent facts, which is why an earlier draft of this contract could not
|
||||
classify that file at all.
|
||||
|
||||
`status` has three values. `active` means in force. `completed` means the work the document
|
||||
describes landed and the document is now finished rather than stale; executed implementation plans
|
||||
take this. `superseded-by: <path>` replaces `status` entirely and names the replacement.
|
||||
|
||||
**This contract covers `.md` files only.** It is not an omission: a YAML document cannot carry YAML
|
||||
front matter. The repository's own `[email protected]` throws `Source contains multiple documents` on a
|
||||
front-mattered `.yaml`, and `parseNorthStar` (`packages/mosaic/src/commands/fleet.ts:242`) is a live
|
||||
consumer that would break. `.yaml` sources declare their own kind inside the document or not at all.
|
||||
|
||||
A `parent` field is planned and is deliberately not yet required; it lands once the docs flatten
|
||||
settles the paths it would point at.
|
||||
|
||||
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
|
||||
|
||||
|
||||
+15
-1
@@ -1,5 +1,19 @@
|
||||
# Tasks — MVP (Top-Level Rollup)
|
||||
|
||||
> ---
|
||||
>
|
||||
> **STATUS: SUPERSEDED — 2026-08-20.** kind `tracking` · superseded by `docs/fleet/NORTH_STAR.yaml`
|
||||
>
|
||||
> This file is the pre-backlog tracking mechanism. `NS-2` in the north star declares the
|
||||
> replacement: every backlog item is a Mosaic Backlog card projected from the YAML. That
|
||||
> model replaced this one and nobody retired the old file, so it kept reading as
|
||||
> authoritative while going stale.
|
||||
>
|
||||
> **Do not trust a status in this file.** Verified 2026-08-20: it was already behind the
|
||||
> code when it froze five weeks ago.
|
||||
>
|
||||
> Kept as a record of what was believed. Do not update it; update the YAML.
|
||||
|
||||
> Single-writer: orchestrator only. Workers read but never modify.
|
||||
>
|
||||
> **Mission:** mvp-20260312
|
||||
@@ -108,7 +122,7 @@ Active workstream is **W1 — Federation v1**. Workers should:
|
||||
|
||||
## north-star doctrine consolidation — doc PR — feat/north-star-doctrine
|
||||
|
||||
- Status: applied Mos's consolidated merge-map to docs/fleet/north-star.md (budget governance + control plane/central register + 200k cap + delegation + unified-identity Fleet + role-based naming + tmux security + drift re-captures). Doctrine only; #622/#623/#625/#628 out-of-scope. Conflict checklist green. Detail: scratchpads/north-star-doctrine.md.
|
||||
- Status: applied Mos's consolidated merge-map to docs/fleet/FLEET-DOCTRINE.md (budget governance + control plane/central register + 200k cap + delegation + unified-identity Fleet + role-based naming + tmux security + drift re-captures). Doctrine only; #622/#623/#625/#628 out-of-scope. Conflict checklist green. Detail: scratchpads/north-star-doctrine.md.
|
||||
|
||||
## #631 — re-seed preserves user fleet data (CRITICAL) — fix/631-reseed-preserves-fleet-data
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# User Guide
|
||||
|
||||
> **Status:** Partially migrated. The quickstart, web-dashboard reference, and Discord conversation workflow are current.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
title: Mosaic Stack Quickstart
|
||||
type: guide
|
||||
audience: user
|
||||
status: current
|
||||
source_of_truth: false
|
||||
---
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
title: Mosaic web dashboard
|
||||
type: guide
|
||||
audience: user
|
||||
status: current
|
||||
source_of_truth: false
|
||||
---
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Discord conversations
|
||||
|
||||
> **Status:** Current Discord workflow for an administrator-provisioned, authorized guild channel.
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
3. [Provider Configuration](#provider-configuration)
|
||||
4. [MCP Server Configuration](#mcp-server-configuration)
|
||||
5. [Environment Variables Reference](#environment-variables-reference)
|
||||
6. [Local Fleet Canary](./fleet-local-canary.md)
|
||||
6. [Pi Goal Loop Operations](#pi-goal-loop-operations)
|
||||
7. [Local Fleet Canary](./fleet-local-canary.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -264,6 +265,16 @@ Each OIDC provider requires its client ID, client secret, and issuer URL togethe
|
||||
| `AGENT_SYSTEM_PROMPT` | — | Platform-level system prompt injected into all sessions |
|
||||
| `AGENT_USER_TOOLS` | all tools | Comma-separated allowlist of tools for non-admin users |
|
||||
|
||||
### Mosaic Pi goal loop
|
||||
|
||||
| Variable | Default | Description |
|
||||
| ----------------------------- | ------- | -------------------------------------------------------------------- |
|
||||
| `MOSAIC_GOAL_MAX_TURNS` | `40` | Per-goal autonomous turn limit; accepted range `1..500` |
|
||||
| `MOSAIC_GOAL_MAX_NO_PROGRESS` | `6` | Consecutive identical progress-report limit; accepted range `1..100` |
|
||||
|
||||
These variables are consumed by the framework-owned Pi goal extension at goal creation. Invalid or
|
||||
out-of-range values fall back to the defaults; they do not disable the bounds.
|
||||
|
||||
### Providers
|
||||
|
||||
| Variable | Default | Description |
|
||||
@@ -374,3 +385,29 @@ Session cleanup is scoped to one session identifier and only removes that sessio
|
||||
| Variable | Default | Description |
|
||||
| ----------------------- | ----------------------------- | ------------------------------------------ |
|
||||
| `MOSAIC_WORKSPACE_ROOT` | monorepo root (auto-detected) | Root path for mission workspace operations |
|
||||
|
||||
---
|
||||
|
||||
## Pi Goal Loop Operations
|
||||
|
||||
The reviewed runtime asset is deployed at
|
||||
`~/.config/mosaic/runtime/pi/goal-extension.ts` by framework install/update. Do not install another
|
||||
copy under `~/.pi/agent/extensions/`; duplicate registration can create suffixed commands and two
|
||||
competing lifecycle controllers.
|
||||
|
||||
Operational checks:
|
||||
|
||||
1. Run `mosaic pi` and verify `/goal help` is available.
|
||||
2. Use `/goal status` to inspect phase, turn/no-progress limits, compaction checks, and evidence.
|
||||
Reports persist in Pi session data; controller-owned state redacts common credential shapes, but
|
||||
Pi's model/tool-call history is separate. Operators must not place secrets or raw sensitive output
|
||||
in goals, pause reasons, or evidence.
|
||||
3. Use `/goal pause <reason>` before planned maintenance or manual investigation. Pause and cancel
|
||||
abort the current goal-driven run when Pi is busy.
|
||||
4. Use `/goal resume` only after addressing a blocker; counters restart with the configured bounds.
|
||||
5. Use `/goal cancel` before replacing an unfinished goal.
|
||||
|
||||
A blocked or exhausted goal remains stopped and visible; Mosaic does not automatically raise its
|
||||
limits or restart the process. Framework sync owns file deployment, while Pi's native session file
|
||||
owns branch replay. Process/host restart remains the responsibility of the existing runtime or fleet
|
||||
supervisor.
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
4. [Tasks](#tasks)
|
||||
5. [Settings](#settings)
|
||||
6. [CLI Usage](#cli-usage)
|
||||
7. [Sub-package Commands](#sub-package-commands)
|
||||
8. [Telemetry](#telemetry)
|
||||
9. [Local Fleet Canary](./fleet-local-canary.md)
|
||||
7. [Pi Persistent Goals](#pi-persistent-goals)
|
||||
8. [Sub-package Commands](#sub-package-commands)
|
||||
9. [Telemetry](#telemetry)
|
||||
10. [Local Fleet Canary](./fleet-local-canary.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -317,6 +318,57 @@ mosaic prdy
|
||||
mosaic quality-rails
|
||||
```
|
||||
|
||||
## Pi Persistent Goals
|
||||
|
||||
`mosaic pi` loads a Mosaic-owned goal extension from
|
||||
`~/.config/mosaic/runtime/pi/goal-extension.ts`. It is deliberately not installed in
|
||||
`~/.pi/agent/extensions/`; framework installation and updates manage it with the rest of the Mosaic
|
||||
runtime assets.
|
||||
|
||||
Start Pi, then set a goal:
|
||||
|
||||
```text
|
||||
/goal set Deliver the feature, tests, documentation, and verification evidence
|
||||
# Shorthand:
|
||||
/goal Deliver the feature, tests, documentation, and verification evidence
|
||||
```
|
||||
|
||||
Control and inspect the loop with:
|
||||
|
||||
| Command | Behavior |
|
||||
| ---------------------- | ------------------------------------------------------------------ |
|
||||
| `/goal status` | Show phase, limits, compaction checks, latest report, and evidence |
|
||||
| `/goal pause [reason]` | Stop autonomous continuation while preserving the goal |
|
||||
| `/goal resume` | Resume with fresh turn and no-progress counters |
|
||||
| `/goal cancel` | Cancel the goal and remove its active status |
|
||||
| `/goal help` | Show command help |
|
||||
|
||||
While a goal is active, Mosaic injects its contract before every Pi model request and checks every
|
||||
completed model/tool turn. The agent ends each work cycle with the structured
|
||||
`mosaic_goal_report` tool. `achieved` is provisional until a second consecutive report rechecks the
|
||||
whole goal with evidence. A continuation report or a successful compaction resets provisional
|
||||
verification.
|
||||
|
||||
Goal statements and reports are stored in Pi session data. Mosaic redacts common credential shapes
|
||||
before appending its goal-state entries and before goal tool output or `/goal status`, but
|
||||
pattern-based redaction is not a secret store. Pi's own model-message and tool-call records are
|
||||
outside that redactor. Never put tokens, passwords, private keys, connection strings, or raw
|
||||
sensitive output in a goal or report; cite the command, artifact, and pass/fail result instead.
|
||||
|
||||
The loop stops instead of running forever when it is paused, blocked, cancelled, verified, reaches
|
||||
its turn limit, or repeats the same no-progress report too many times. Defaults are 40 turns and 6
|
||||
repeated no-progress reports. Operators may lower or raise them within enforced bounds before
|
||||
launching Pi:
|
||||
|
||||
```bash
|
||||
MOSAIC_GOAL_MAX_TURNS=60 MOSAIC_GOAL_MAX_NO_PROGRESS=8 mosaic pi
|
||||
```
|
||||
|
||||
Goal state is branch-specific Pi session data. It survives compaction and session resume, but Pi's
|
||||
process still must be relaunched or supervised after a process/host failure. This initial verifier
|
||||
checks structured evidence twice; it cannot mathematically prove every arbitrary natural-language
|
||||
goal. Use explicit acceptance criteria and inspect `/goal status` for consequential work.
|
||||
|
||||
---
|
||||
|
||||
### Claude Code Skill Registration
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: tracking
|
||||
status: active
|
||||
---
|
||||
|
||||
# Mission Manifest — Federation v1
|
||||
|
||||
> Persistent document tracking full mission scope, status, and session history.
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# Tasks — Federation v1
|
||||
|
||||
> ---
|
||||
>
|
||||
> **STATUS: SUPERSEDED — 2026-08-20.** kind `tracking` · superseded by `docs/fleet/NORTH_STAR.yaml`
|
||||
>
|
||||
> This file is the pre-backlog tracking mechanism. `NS-2` in the north star declares the
|
||||
> replacement: every backlog item is a Mosaic Backlog card projected from the YAML. That
|
||||
> model replaced this one and nobody retired the old file, so it kept reading as
|
||||
> authoritative while going stale.
|
||||
>
|
||||
> **Do not trust a status in this file.** Verified 2026-08-20: it was already behind the
|
||||
> code when it froze five weeks ago. `FED-M3-06` is marked not-started and `get.controller.ts`
|
||||
> has existed for eight weeks; `FED-M3-10/11` claim no tests exist while fifteen spec files
|
||||
> do. `FED-M2-DEPLOY-IMG-FIX` names `apps/gateway/Dockerfile`, which is not in the repo.
|
||||
>
|
||||
> Kept as a record of what was believed. Do not update it; update the YAML.
|
||||
|
||||
> Single-writer: orchestrator only. Workers read but never modify.
|
||||
>
|
||||
> **Mission:** federation-v1-20260419
|
||||
|
||||
@@ -1,9 +1,26 @@
|
||||
# Mosaic Fleet — North Star
|
||||
---
|
||||
kind: spec
|
||||
parent: docs/fleet/NORTH_STAR.yaml
|
||||
status: active
|
||||
---
|
||||
|
||||
# Mosaic Fleet — Doctrine
|
||||
|
||||
> **This is the WHY. `NORTH_STAR.yaml` is the WHAT and WHEN.**
|
||||
> Renamed from `north-star.md` on 2026-08-20. It sat one character away from the
|
||||
> generated `NORTH_STAR.md` in the same directory, and the two are read by different
|
||||
> populations — the PRDs and TASKS files cite this one, while the agent role contracts
|
||||
> and the generator spec cite the YAML pair. Same-name-different-thing was the confusion;
|
||||
> the content was never in conflict.
|
||||
>
|
||||
> **Nothing here overrides `NORTH_STAR.yaml`.** Where this document states a plan item,
|
||||
> the YAML is authoritative. Where it states a decision, a rationale, or a role
|
||||
> definition, this document is the record and the YAML carries none of it.
|
||||
>
|
||||
> **Workstream:** W-FLEET (Fleet) under mission `mvp-20260312`
|
||||
> **Umbrella:** [docs/MISSION-MANIFEST.md](../MISSION-MANIFEST.md) · [docs/PRD.md](../PRD.md) (Mosaic Stack v0.1.0)
|
||||
> **Status:** doctrine — authored 2026-06-20. Owner of this file: Fleet workstream lead.
|
||||
> This document does **not** modify the MVP rollup; a rollup row is proposed, not written here.
|
||||
> **Umbrella:** [docs/MISSION-MANIFEST.md](../MISSION-MANIFEST.md)
|
||||
> **Authored:** 2026-06-20. Owner: Fleet workstream lead.
|
||||
> This document does **not** modify the MVP rollup.
|
||||
|
||||
## Vision
|
||||
|
||||
@@ -264,15 +281,17 @@ Dedicated Postgres **instance** vs. dedicated **schema** in the existing instanc
|
||||
Recommendation: dedicated schema, existing instance (a migration file, not new infra);
|
||||
re-evaluate if isolation or write-volume demands it.
|
||||
|
||||
## Phased roadmap
|
||||
## Phased roadmap — SUPERSEDED
|
||||
|
||||
| Phase | Outcome | Status |
|
||||
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- |
|
||||
| 0–1 | tmux PoC, hardening, published CLI v0.0.34 (#565–#568) | ✅ done |
|
||||
| **2 — Observability** | fleet ps (host+tenant aware join), heartbeat protocol + dogfood stub answers it, agent watch (read-only), agent send --verify receipts | ▶ now |
|
||||
| 3 — Real runtimes | claude/codex/pi/opencode answer heartbeat; **hybrid lifecycle** (core always-on: **orchestrator + enhancer**; ephemeral workers per lane) | planned |
|
||||
| 4 — Unified definition | one agent schema in gateway; mosaic agent --new → materialized per-tenant session; uid-tenant provisioning; **`fleet` schema migration + `forge-exec` TaskExecutor adapter (forge → `agent-send.sh`)** | planned |
|
||||
| 5 — Control plane | federation-backed cross-host × cross-tenant fleet view; **webUI** (surface chosen then) for MVP-X1 parity; **central register live (spend ledger, docs-as-projections, multi-host Kanban)** | planned |
|
||||
Superseded 2026-08-20 by [`NORTH_STAR.yaml`](./NORTH_STAR.yaml), whose `goals` carry both
|
||||
a `phase` (build order) and a `tier` (which promise the goal delivers). The five-phase
|
||||
table that stood here could not express those as separate axes, and its "Phase 2 —
|
||||
Observability ▶ now" row stayed unfalsified for two months because a phase has no exit
|
||||
test. Tiers do: see `AC-NS-0` through `AC-NS-7`.
|
||||
|
||||
The phase-2 content itself is not lost — it is specified in
|
||||
[`PRD.md`](./PRD.md) (Fleet Phase 2: Operator Observability) and is now tracked as
|
||||
goals `I1`–`I5` at tier 0.
|
||||
|
||||
## Decisions of record (2026-06-20, with Jason)
|
||||
|
||||
+13
-10
@@ -5,14 +5,14 @@ Generated environment files are rebuildable projections, not an operator-editabl
|
||||
|
||||
## Launch chain
|
||||
|
||||
| Layer | Responsibility |
|
||||
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Roster | `fleet/roster.yaml` supplies the agent name, class, supported runtime, model, reasoning, tool policy, workdir, and tmux socket. |
|
||||
| Projection writer | Renders deterministic fleet/agents/<name>.env.generated from the roster. |
|
||||
| Optional local data | Reads a strict, data-only fleet/agents/<name>.env.local; it cannot shadow generated keys. |
|
||||
| systemd | Starts the launcher with env -i and fixed bootstrap data. It does not preload either environment file. |
|
||||
| session launcher | Validates generated and local data before it queries, creates, or stops an exact tmux session. |
|
||||
| runtime launch | Derives the fixed mosaic yolo <runtime> argument array from validated roster data, then seeds the runtime contract. |
|
||||
| Layer | Responsibility |
|
||||
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Roster | `fleet/roster.yaml` supplies the agent name, class, supported runtime, model, reasoning, tool policy, workdir, and tmux socket; Git identity is derived from the exact agent name. |
|
||||
| Projection writer | Renders deterministic fleet/agents/<name>.env.generated from the roster. |
|
||||
| Optional local data | Reads a strict, data-only fleet/agents/<name>.env.local; it cannot shadow generated keys. |
|
||||
| systemd | Starts the launcher with env -i and fixed bootstrap data. It does not preload either environment file. |
|
||||
| session launcher | Validates generated and local data before it queries, creates, or stops an exact tmux session. |
|
||||
| runtime launch | Derives the fixed mosaic yolo <runtime> argument array from validated roster data, then seeds the runtime contract. |
|
||||
|
||||
The launcher never `source`s or `eval`s an environment file and never accepts an environment-supplied
|
||||
command. `MOSAIC_AGENT_COMMAND`, command/channel overrides, unknown keys, generated-key shadowing,
|
||||
@@ -24,6 +24,7 @@ secret-like key names, duplicate keys, comments, quoted/export syntax, and unsaf
|
||||
|
||||
```dotenv
|
||||
MOSAIC_AGENT_NAME=<roster name>
|
||||
MOSAIC_GIT_IDENTITY=<roster name>
|
||||
MOSAIC_AGENT_CLASS=<roster class>
|
||||
MOSAIC_AGENT_RUNTIME=<roster runtime>
|
||||
MOSAIC_AGENT_MODEL=<roster model hint>
|
||||
@@ -33,8 +34,10 @@ MOSAIC_AGENT_WORKDIR=<absolute roster work directory>
|
||||
MOSAIC_TMUX_SOCKET=<roster socket or empty>
|
||||
```
|
||||
|
||||
The generated launch contract supports `claude`, `codex`, `opencode`, and `pi`. mosaic fleet add
|
||||
rejects another runtime before it writes the roster or modifies generated, local, or quarantine state.
|
||||
`MOSAIC_GIT_IDENTITY` is not independently configurable: it must equal `MOSAIC_AGENT_NAME`, preventing
|
||||
split runtime and repository identity authority. The generated launch contract supports `claude`,
|
||||
`codex`, `opencode`, and `pi`. mosaic fleet add rejects another runtime before it writes the roster or
|
||||
modifies generated, local, or quarantine state.
|
||||
The legacy dogfood stub remains an observability-only canary on its separate `mosaic-factory` socket;
|
||||
it has no generated-launch adapter and cannot be added through this path.
|
||||
|
||||
|
||||
+45
-25
@@ -3,7 +3,7 @@
|
||||
> **Generated file — do not edit by hand.**
|
||||
> Projected deterministically from [`NORTH_STAR.yaml`](./NORTH_STAR.yaml) by the pure
|
||||
> generator in `packages/mosaic/src/commands/fleet.ts` (`renderNorthStarMarkdown`).
|
||||
> Edit the YAML, then regenerate. Self-contained Mosaic — no Hermes dependency.
|
||||
> Edit the YAML, then regenerate. Self-contained Mosaic.
|
||||
|
||||
## Mission
|
||||
|
||||
@@ -11,7 +11,7 @@ A self-driving Mosaic system that 24/7 unattended converts a machine-readable go
|
||||
|
||||
## Substrate
|
||||
|
||||
The Mosaic Backlog is the backlog of record + dispatch engine, built on Mosaic's native Postgres storage service (@mosaicstack/db drizzle; PGlite-embedded by default, full Postgres by config). NOT Hermes.
|
||||
The Mosaic Backlog is the backlog of record + dispatch engine, built on Mosaic's native Postgres storage service (@mosaicstack/db drizzle; PGlite-embedded by default, full Postgres by config).
|
||||
|
||||
## Standing objectives
|
||||
|
||||
@@ -24,16 +24,18 @@ The Mosaic Backlog is the backlog of record + dispatch engine, built on Mosaic's
|
||||
- **NS-7** — Meta-loop (session-review + enhancer) continuously proposes small fleet-improvement PRs.
|
||||
- **NS-8** — Single operator-flippable PAUSE kill-switch (fleet/run/PAUSED) honored before every dispatch and every merge.
|
||||
- **NS-9** — Mosaic is a general-purpose multi-agent system: the user declares the SYSTEM TYPE to run (e.g. software delivery, personal assistant, research, business/operations) and the orchestrator provisions the matching persona roster and org structure from a cross-domain baseline persona library; the delivery/coding fleet is one profile among many.
|
||||
- **NS-10** — An adoption is not complete until the mechanism it replaces is removed. Two live conventions for one concern is the defect, not a transition state. Measured 2026-08-20: brain-home adopted by 9 modules and not 10; MOSAIC_HOME honored in 4 places, each re-deriving it; backlog cards declared while TASKS.md files stayed authoritative. Every one was decided correctly and left half-applied.
|
||||
|
||||
## Success criteria
|
||||
|
||||
- **AC-NS-1** — The supervisor keeps a two-agent floor (1 orchestrator + >=1 enhancer) healthy across reboot.
|
||||
- **AC-NS-2** — A goal added to this YAML is decomposed to cards and either merged or escalated, with no human in the loop.
|
||||
- **AC-NS-3** — No PR merges with failure/error/no-status/timeout CI, and none bypass pr-merge.sh.
|
||||
- **AC-NS-4** — TTL is enforced on claims; token caps remain advisory until a real meter exists.
|
||||
- **AC-NS-5** — Flipping fleet/run/PAUSED halts dispatch and merges within one tick.
|
||||
- **AC-NS-6** — A user can declare a system type and the fleet provisions the matching persona roster + topology from the baseline library, with no code change.
|
||||
- **AC-NS-7** — A user-customized persona (edited or added via the orchestrator) survives mosaic update: baseline reseed never clobbers user overrides.
|
||||
- **AC-NS-0** (tier 0) — The operator launches an agent on any configured harness with one command, observes its state and sends it work without attaching to a terminal multiplexer.
|
||||
- **AC-NS-1** (tier 1) — The supervisor keeps a two-agent floor (1 orchestrator + >=1 enhancer) healthy across reboot.
|
||||
- **AC-NS-2** (tier 1) — A goal added to this YAML is decomposed to cards and either merged or escalated, with no human in the loop.
|
||||
- **AC-NS-3** (tier 1) — No PR merges with failure/error/no-status/timeout CI, and none bypass pr-merge.sh.
|
||||
- **AC-NS-4** (tier 1) — TTL is enforced on claims; token caps remain advisory until a real meter exists.
|
||||
- **AC-NS-5** (tier 1) — Flipping fleet/run/PAUSED halts dispatch and merges within one tick.
|
||||
- **AC-NS-6** (tier 2) — A user can declare a system type and the fleet provisions the matching persona roster + topology from the baseline library, with no code change.
|
||||
- **AC-NS-7** (tier 2) — A user-customized persona (edited or added via the orchestrator) survives mosaic update: baseline reseed never clobbers user overrides.
|
||||
|
||||
## Workstreams
|
||||
|
||||
@@ -45,26 +47,44 @@ The Mosaic Backlog is the backlog of record + dispatch engine, built on Mosaic's
|
||||
| D | Merge-gate — single approver, pr-merge.sh after CI wait |
|
||||
| E | Meta-loop — session-review + enhancer improvement PRs |
|
||||
| F | Safety-rails — TTL claims, advisory spend, PAUSE kill-switch |
|
||||
| G | Kill-switch — operator PAUSE honored before dispatch and merge |
|
||||
| H | Personas & system profiles — cross-domain library, system-type provisioning, update-surviving customization |
|
||||
| I | Operator surface — launcher, fleet visibility, reliable steering (tier 0) |
|
||||
| J | Web control plane — browser surface over the gateway (tier 1) |
|
||||
| K | Clients — desktop and mobile over the same backend (tier 2) |
|
||||
| L | Auth profiles — per-provider accounts, per-session selection (tier 2) |
|
||||
|
||||
## Goals (backlog projection)
|
||||
|
||||
| id | title | phase | priority | depends_on |
|
||||
| --- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | ----------- | ---------- |
|
||||
| A1 | Machine-readable NORTH_STAR.yaml + Markdown projection | 1 | must-have | — |
|
||||
| A2 | Mosaic Backlog schema + storage-service card store (drizzle/PGlite) | 1 | must-have | A1 |
|
||||
| A3a | Card lifecycle — create/claim/release with stable ids + depends_on DAG | 1 | must-have | A2 |
|
||||
| A3b | TTL-bounded claim enforcement (wall-clock) on cards | 1 | must-have | A3a |
|
||||
| A4 | Advisory spend projection per card (degrades to TTL, no real meter) | 1 | should-have | A3a |
|
||||
| B1 | Supervisor tick — readiness scan, two-agent-floor health check | 2 | must-have | A3a |
|
||||
| B2 | Native dispatch/claim — assign ready dependency-satisfied work | 2 | must-have | A3b, B1 |
|
||||
| B3a | Planner decompose — goal added to YAML → cards | 2 | must-have | A2, B1 |
|
||||
| B3b | Replan request on empty backlog; escalate on no-decompose | 2 | should-have | B3a |
|
||||
| G1 | PAUSE kill-switch + merge-gate honored before dispatch and merge | 2 | must-have | B2 |
|
||||
| H1 | Cross-domain baseline persona library (exec, marketing, ops, research, assistant + engineering roles) | 1 | must-have | A1 |
|
||||
| H2 | System-type profiles — declarative mapping of system type to persona roster + topology | 2 | must-have | H1 |
|
||||
| H3 | System-type provisioning — user declares type; orchestrator instantiates the matching roster + structure | 2 | must-have | H2 |
|
||||
| H4 | Update-surviving persona customization — ad-hoc edits/additions persisted in a PRESERVE-protected override layer (baseline merged with overrides) | 2 | must-have | H1 |
|
||||
| id | title | tier | phase | priority | depends_on |
|
||||
| --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | ----- | ----------- | -------------- |
|
||||
| A1 | Machine-readable NORTH_STAR.yaml + Markdown projection | 1 | 1 | must-have | — |
|
||||
| A2 | Mosaic Backlog schema + storage-service card store (drizzle/PGlite) | 1 | 1 | must-have | A1 |
|
||||
| A3a | Card lifecycle — create/claim/release with stable ids + depends_on DAG | 1 | 1 | must-have | A2 |
|
||||
| A3b | TTL-bounded claim enforcement (wall-clock) on cards | 1 | 1 | must-have | A3a |
|
||||
| A4 | Advisory spend projection per card (degrades to TTL, no real meter) | 1 | 1 | should-have | A3a |
|
||||
| B1 | Supervisor tick — readiness scan, two-agent-floor health check | 1 | 2 | must-have | A3a |
|
||||
| B2 | Native dispatch/claim — assign ready dependency-satisfied work | 1 | 2 | must-have | A3b, B1 |
|
||||
| B3a | Planner decompose — goal added to YAML → cards | 1 | 2 | must-have | A2, B1 |
|
||||
| B3b | Replan request on empty backlog; escalate on no-decompose | 1 | 2 | should-have | B3a |
|
||||
| G1 | PAUSE kill-switch + merge-gate honored before dispatch and merge | 1 | 2 | must-have | B2 |
|
||||
| H1 | Cross-domain baseline persona library (exec, marketing, ops, research, assistant + engineering roles) | 2 | 1 | must-have | A1 |
|
||||
| H2 | System-type profiles — declarative mapping of system type to persona roster + topology | 2 | 2 | must-have | H1 |
|
||||
| H3 | System-type provisioning — user declares type; orchestrator instantiates the matching roster + structure | 2 | 2 | must-have | H2 |
|
||||
| H4 | Update-surviving persona customization — ad-hoc edits/additions persisted in a PRESERVE-protected override layer (baseline merged with overrides) | 2 | 2 | must-have | H1 |
|
||||
| A5 | NORTH_STAR schema validator — every goal's workstream declared, every workstream has a goal, every depends_on id exists, every tier has a success criterion; runs in CI beside the Markdown regeneration check | 0 | 1 | must-have | A1 |
|
||||
| I1 | One home resolver — a single function resolving MOSAIC_HOME with a sane default, adopted by every module. Today brain-home.ts is imported by 9 modules while 10 still use DEFAULT_MOSAIC_HOME, and MOSAIC_HOME is re-derived ad hoc in 4 places. NS-10 applies - finish the adoption and delete the second path | 0 | 1 | must-have | — |
|
||||
| I2 | mosaic fleet ps sees the fleet that is actually running. Three measured blockers: the roster declares socket `mosaic-fleet` which does not exist, the 18 live sessions are on the default socket, and nothing writes a roster because seats are launched outside the CLI. Make the socket configurable and the roster written at launch, or make ps read tmux + seat dirs directly | 0 | 1 | must-have | I1 |
|
||||
| I3 | Migrate fleet steering onto mosaic agent send --verify (exists, FLEET-OBS-005, spec FR-5) and retire tools/tmux/agent-send.sh, which forges the sender (D33) and returns an uninformative rc (D16, D34). FR-5 predates those defects by a month | 0 | 1 | must-have | I1 |
|
||||
| I4 | mosaic fleet absorbs what launch-seat.sh does and launch-seat.sh is deprecated: compose the prompt file set, force the skill set, wire the style hook, fail closed on any unreadable input, export per-seat git identity, and register the seat in the roster. launch-seat.sh was a manual method; it is the reference implementation, not the destination | 0 | 1 | must-have | I1, I5 |
|
||||
| I5 | Harness probe matrix — verify a working prompt-injection path for claude, codex, opencode and pi, and refuse any runtime whose path is unverified. AC-NS-0 clause 1 ("any configured harness") rests on this. The probe work in docs/plans/2026-08-19_launch-seat-multi-runtime.md (brain, untracked) transfers; its launch-seat.sh target does not | 0 | 1 | must-have | — |
|
||||
| I6 | Finish the heartbeat responder (FLEET-OBS-002, the only Phase-2 task still in-progress; spec FR-2). Health must mean "answered a heartbeat", not "pane alive" — pane state measured two seats wrong on 2026-08-20 | 0 | 1 | must-have | — |
|
||||
| I7 | Independent review and live-fleet dogfood of the Phase-2 verbs (FLEET-OBS-008), then land them (FLEET-OBS-009). Implementation is done and verification is not; "done" in a task file frozen five weeks is not evidence | 0 | 1 | must-have | I2, I3, I4, I6 |
|
||||
| I8 | Neutralize misleading documentation — supersede headers on docs that state a stale status, a false blocker or a retired mechanism. Cheap, and it is tier 0 because a stale doc does not merely fail to help an agent, it actively misroutes one. Rebuilding the documentation is a separate and later job | 0 | 1 | must-have | — |
|
||||
| I9 | Study t3code's agent-attach and multi-provider auth methods and record what transfers. Reference only — Mosaic implements its own within the stack, never adopts the code and never takes the dependency. Informs HOW I/J/K/L are built, not whether | 0 | 1 | should-have | — |
|
||||
| J1 | Web control plane over the gateway — fleet visibility and steering in a browser, same data source as I2 | 1 | 3 | must-have | I2 |
|
||||
| K1 | Desktop and mobile clients against the gateway, authenticated | 2 | 4 | must-have | J1 |
|
||||
| L1 | Per-provider auth profiles with per-session selection | 2 | 4 | must-have | I4 |
|
||||
|
||||
## Assumptions (vetoable)
|
||||
|
||||
|
||||
+129
-2
@@ -6,7 +6,7 @@
|
||||
# packages/mosaic/src/commands/fleet.ts (renderNorthStarMarkdown). Edit the YAML,
|
||||
# never the .md.
|
||||
#
|
||||
# Self-contained Mosaic. NO Hermes runtime dependency. The backlog of record is
|
||||
# Self-contained Mosaic. The backlog of record is
|
||||
# the Mosaic Backlog on Mosaic's OWN native Postgres storage service.
|
||||
|
||||
version: 1
|
||||
@@ -24,7 +24,7 @@ substrate:
|
||||
note: >-
|
||||
The Mosaic Backlog is the backlog of record + dispatch engine, built on
|
||||
Mosaic's native Postgres storage service (@mosaicstack/db drizzle;
|
||||
PGlite-embedded by default, full Postgres by config). NOT Hermes.
|
||||
PGlite-embedded by default, full Postgres by config).
|
||||
|
||||
standing_objectives:
|
||||
- id: NS-1
|
||||
@@ -69,32 +69,53 @@ standing_objectives:
|
||||
business/operations) and the orchestrator provisions the matching persona
|
||||
roster and org structure from a cross-domain baseline persona library; the
|
||||
delivery/coding fleet is one profile among many.
|
||||
- id: NS-10
|
||||
text: >-
|
||||
An adoption is not complete until the mechanism it replaces is removed.
|
||||
Two live conventions for one concern is the defect, not a transition
|
||||
state. Measured 2026-08-20: brain-home adopted by 9 modules and not 10;
|
||||
MOSAIC_HOME honored in 4 places, each re-deriving it; backlog cards
|
||||
declared while TASKS.md files stayed authoritative. Every one was decided
|
||||
correctly and left half-applied.
|
||||
|
||||
success_criteria:
|
||||
- id: AC-NS-0
|
||||
tier: 0
|
||||
text: >-
|
||||
The operator launches an agent on any configured harness with one
|
||||
command, observes its state and sends it work without attaching to a
|
||||
terminal multiplexer.
|
||||
- id: AC-NS-1
|
||||
tier: 1
|
||||
text: >-
|
||||
The supervisor keeps a two-agent floor (1 orchestrator + >=1 enhancer)
|
||||
healthy across reboot.
|
||||
- id: AC-NS-2
|
||||
tier: 1
|
||||
text: >-
|
||||
A goal added to this YAML is decomposed to cards and either merged or
|
||||
escalated, with no human in the loop.
|
||||
- id: AC-NS-3
|
||||
tier: 1
|
||||
text: >-
|
||||
No PR merges with failure/error/no-status/timeout CI, and none bypass
|
||||
pr-merge.sh.
|
||||
- id: AC-NS-4
|
||||
tier: 1
|
||||
text: >-
|
||||
TTL is enforced on claims; token caps remain advisory until a real meter
|
||||
exists.
|
||||
- id: AC-NS-5
|
||||
tier: 1
|
||||
text: >-
|
||||
Flipping fleet/run/PAUSED halts dispatch and merges within one tick.
|
||||
- id: AC-NS-6
|
||||
tier: 2
|
||||
text: >-
|
||||
A user can declare a system type and the fleet provisions the matching
|
||||
persona roster + topology from the baseline library, with no code change.
|
||||
- id: AC-NS-7
|
||||
tier: 2
|
||||
text: >-
|
||||
A user-customized persona (edited or added via the orchestrator) survives
|
||||
mosaic update: baseline reseed never clobbers user overrides.
|
||||
@@ -112,80 +133,186 @@ workstreams:
|
||||
title: Meta-loop — session-review + enhancer improvement PRs
|
||||
- id: F
|
||||
title: Safety-rails — TTL claims, advisory spend, PAUSE kill-switch
|
||||
- id: G
|
||||
title: Kill-switch — operator PAUSE honored before dispatch and merge
|
||||
- id: H
|
||||
title: Personas & system profiles — cross-domain library, system-type provisioning, update-surviving customization
|
||||
- id: I
|
||||
title: Operator surface — launcher, fleet visibility, reliable steering (tier 0)
|
||||
- id: J
|
||||
title: Web control plane — browser surface over the gateway (tier 1)
|
||||
- id: K
|
||||
title: Clients — desktop and mobile over the same backend (tier 2)
|
||||
- id: L
|
||||
title: Auth profiles — per-provider accounts, per-session selection (tier 2)
|
||||
|
||||
# NOTE: workstreams C, D, E and F are declared but currently project no goals.
|
||||
# That is planning debt, not an editing error: their goals have not been written
|
||||
# yet. The A5 validator below reports it rather than letting it stay invisible.
|
||||
|
||||
goals:
|
||||
- id: A1
|
||||
title: Machine-readable NORTH_STAR.yaml + Markdown projection
|
||||
phase: 1
|
||||
tier: 1
|
||||
priority: must-have
|
||||
depends_on: []
|
||||
- id: A2
|
||||
title: Mosaic Backlog schema + storage-service card store (drizzle/PGlite)
|
||||
phase: 1
|
||||
tier: 1
|
||||
priority: must-have
|
||||
depends_on: [A1]
|
||||
- id: A3a
|
||||
title: Card lifecycle — create/claim/release with stable ids + depends_on DAG
|
||||
phase: 1
|
||||
tier: 1
|
||||
priority: must-have
|
||||
depends_on: [A2]
|
||||
- id: A3b
|
||||
title: TTL-bounded claim enforcement (wall-clock) on cards
|
||||
phase: 1
|
||||
tier: 1
|
||||
priority: must-have
|
||||
depends_on: [A3a]
|
||||
- id: A4
|
||||
title: Advisory spend projection per card (degrades to TTL, no real meter)
|
||||
phase: 1
|
||||
tier: 1
|
||||
priority: should-have
|
||||
depends_on: [A3a]
|
||||
- id: B1
|
||||
title: Supervisor tick — readiness scan, two-agent-floor health check
|
||||
phase: 2
|
||||
tier: 1
|
||||
priority: must-have
|
||||
depends_on: [A3a]
|
||||
- id: B2
|
||||
title: Native dispatch/claim — assign ready dependency-satisfied work
|
||||
phase: 2
|
||||
tier: 1
|
||||
priority: must-have
|
||||
depends_on: [A3b, B1]
|
||||
- id: B3a
|
||||
title: Planner decompose — goal added to YAML → cards
|
||||
phase: 2
|
||||
tier: 1
|
||||
priority: must-have
|
||||
depends_on: [A2, B1]
|
||||
- id: B3b
|
||||
title: Replan request on empty backlog; escalate on no-decompose
|
||||
phase: 2
|
||||
tier: 1
|
||||
priority: should-have
|
||||
depends_on: [B3a]
|
||||
- id: G1
|
||||
title: PAUSE kill-switch + merge-gate honored before dispatch and merge
|
||||
phase: 2
|
||||
tier: 1
|
||||
priority: must-have
|
||||
depends_on: [B2]
|
||||
- id: H1
|
||||
title: Cross-domain baseline persona library (exec, marketing, ops, research, assistant + engineering roles)
|
||||
phase: 1
|
||||
tier: 2
|
||||
priority: must-have
|
||||
depends_on: [A1]
|
||||
- id: H2
|
||||
title: System-type profiles — declarative mapping of system type to persona roster + topology
|
||||
phase: 2
|
||||
tier: 2
|
||||
priority: must-have
|
||||
depends_on: [H1]
|
||||
- id: H3
|
||||
title: System-type provisioning — user declares type; orchestrator instantiates the matching roster + structure
|
||||
phase: 2
|
||||
tier: 2
|
||||
priority: must-have
|
||||
depends_on: [H2]
|
||||
- id: H4
|
||||
title: Update-surviving persona customization — ad-hoc edits/additions persisted in a PRESERVE-protected override layer (baseline merged with overrides)
|
||||
phase: 2
|
||||
tier: 2
|
||||
priority: must-have
|
||||
depends_on: [H1]
|
||||
- id: A5
|
||||
title: NORTH_STAR schema validator — every goal's workstream declared, every workstream has a goal, every depends_on id exists, every tier has a success criterion; runs in CI beside the Markdown regeneration check
|
||||
phase: 1
|
||||
tier: 0
|
||||
priority: must-have
|
||||
depends_on: [A1]
|
||||
- id: I1
|
||||
title: One home resolver — a single function resolving MOSAIC_HOME with a sane default, adopted by every module. Today brain-home.ts is imported by 9 modules while 10 still use DEFAULT_MOSAIC_HOME, and MOSAIC_HOME is re-derived ad hoc in 4 places. NS-10 applies - finish the adoption and delete the second path
|
||||
phase: 1
|
||||
tier: 0
|
||||
priority: must-have
|
||||
depends_on: []
|
||||
- id: I2
|
||||
title: 'mosaic fleet ps sees the fleet that is actually running. Three measured blockers: the roster declares socket `mosaic-fleet` which does not exist, the 18 live sessions are on the default socket, and nothing writes a roster because seats are launched outside the CLI. Make the socket configurable and the roster written at launch, or make ps read tmux + seat dirs directly'
|
||||
phase: 1
|
||||
tier: 0
|
||||
priority: must-have
|
||||
depends_on: [I1]
|
||||
- id: I3
|
||||
title: Migrate fleet steering onto mosaic agent send --verify (exists, FLEET-OBS-005, spec FR-5) and retire tools/tmux/agent-send.sh, which forges the sender (D33) and returns an uninformative rc (D16, D34). FR-5 predates those defects by a month
|
||||
phase: 1
|
||||
tier: 0
|
||||
priority: must-have
|
||||
depends_on: [I1]
|
||||
- id: I4
|
||||
title: 'mosaic fleet absorbs what launch-seat.sh does and launch-seat.sh is deprecated: compose the prompt file set, force the skill set, wire the style hook, fail closed on any unreadable input, export per-seat git identity, and register the seat in the roster. launch-seat.sh was a manual method; it is the reference implementation, not the destination'
|
||||
phase: 1
|
||||
tier: 0
|
||||
priority: must-have
|
||||
depends_on: [I1, I5]
|
||||
- id: I5
|
||||
title: Harness probe matrix — verify a working prompt-injection path for claude, codex, opencode and pi, and refuse any runtime whose path is unverified. AC-NS-0 clause 1 ("any configured harness") rests on this. The probe work in docs/plans/2026-08-19_launch-seat-multi-runtime.md (brain, untracked) transfers; its launch-seat.sh target does not
|
||||
phase: 1
|
||||
tier: 0
|
||||
priority: must-have
|
||||
depends_on: []
|
||||
- id: I6
|
||||
title: Finish the heartbeat responder (FLEET-OBS-002, the only Phase-2 task still in-progress; spec FR-2). Health must mean "answered a heartbeat", not "pane alive" — pane state measured two seats wrong on 2026-08-20
|
||||
phase: 1
|
||||
tier: 0
|
||||
priority: must-have
|
||||
depends_on: []
|
||||
- id: I7
|
||||
title: Independent review and live-fleet dogfood of the Phase-2 verbs (FLEET-OBS-008), then land them (FLEET-OBS-009). Implementation is done and verification is not; "done" in a task file frozen five weeks is not evidence
|
||||
phase: 1
|
||||
tier: 0
|
||||
priority: must-have
|
||||
depends_on: [I2, I3, I4, I6]
|
||||
- id: I8
|
||||
title: Neutralize misleading documentation — supersede headers on docs that state a stale status, a false blocker or a retired mechanism. Cheap, and it is tier 0 because a stale doc does not merely fail to help an agent, it actively misroutes one. Rebuilding the documentation is a separate and later job
|
||||
phase: 1
|
||||
tier: 0
|
||||
priority: must-have
|
||||
depends_on: []
|
||||
- id: I9
|
||||
title: Study t3code's agent-attach and multi-provider auth methods and record what transfers. Reference only — Mosaic implements its own within the stack, never adopts the code and never takes the dependency. Informs HOW I/J/K/L are built, not whether
|
||||
phase: 1
|
||||
tier: 0
|
||||
priority: should-have
|
||||
depends_on: []
|
||||
- id: J1
|
||||
title: Web control plane over the gateway — fleet visibility and steering in a browser, same data source as I2
|
||||
phase: 3
|
||||
tier: 1
|
||||
priority: must-have
|
||||
depends_on: [I2]
|
||||
- id: K1
|
||||
title: Desktop and mobile clients against the gateway, authenticated
|
||||
phase: 4
|
||||
tier: 2
|
||||
priority: must-have
|
||||
depends_on: [J1]
|
||||
- id: L1
|
||||
title: Per-provider auth profiles with per-session selection
|
||||
phase: 4
|
||||
tier: 2
|
||||
priority: must-have
|
||||
depends_on: [I4]
|
||||
|
||||
assumptions:
|
||||
- id: ASM-1
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
---
|
||||
kind: spec
|
||||
status: active
|
||||
---
|
||||
|
||||
# PRD — Mosaic Fleet Suite (init, configure, operate)
|
||||
|
||||
> **Workstream:** W-FLEET (Fleet) under mission `mvp-20260312` · **Phase:** 3→4 productization
|
||||
> **North star:** [docs/fleet/north-star.md](./north-star.md) · prior: Phase-2 observability (#579), durable launch (#581), real-agent enablement (#583/#584/#586), releases 0.0.35–0.0.37
|
||||
> **North star:** [docs/fleet/FLEET-DOCTRINE.md](./FLEET-DOCTRINE.md) · prior: Phase-2 observability (#579), durable launch (#581), real-agent enablement (#583/#584/#586), releases 0.0.35–0.0.37
|
||||
> **Lead:** Jarvis @ `w-jarvis`. **Collaborator:** coder agent @ `dragon-lin` (jwoltje@10.1.10.37:coder0-0).
|
||||
> Owner of this file: Fleet workstream lead. Does not modify MVP single-writer control-plane files.
|
||||
|
||||
|
||||
+6
-1
@@ -1,7 +1,12 @@
|
||||
---
|
||||
kind: spec
|
||||
status: active
|
||||
---
|
||||
|
||||
# PRD — Fleet Phase 2: Operator Observability
|
||||
|
||||
> **Workstream:** W-FLEET under `mvp-20260312` · **Phase:** 2
|
||||
> **North star:** [docs/fleet/north-star.md](./north-star.md)
|
||||
> **North star:** [docs/fleet/FLEET-DOCTRINE.md](./FLEET-DOCTRINE.md)
|
||||
> **Source umbrella PRD:** [docs/PRD.md](../PRD.md) (Mosaic Stack v0.1.0)
|
||||
> **Tracks task:** `fleet-observability-1` — restore operator observability into fleet agent sessions.
|
||||
|
||||
|
||||
+16
-1
@@ -1,10 +1,25 @@
|
||||
# Tasks — W-FLEET (Fleet) Phase 2: Observability
|
||||
|
||||
> ---
|
||||
>
|
||||
> **STATUS: SUPERSEDED — 2026-08-20.** kind `tracking` · superseded by `docs/fleet/NORTH_STAR.yaml`
|
||||
>
|
||||
> This file is the pre-backlog tracking mechanism. `NS-2` in the north star declares the
|
||||
> replacement: every backlog item is a Mosaic Backlog card projected from the YAML. That
|
||||
> model replaced this one and nobody retired the old file, so it kept reading as
|
||||
> authoritative while going stale.
|
||||
>
|
||||
> **Do not trust a status in this file.** Verified 2026-08-20: it was already behind the
|
||||
> code when it froze five weeks ago. The `FLEET-OBS` series was the one thing worth salvaging and
|
||||
> is now carried as goals `I2`, `I3`, `I6` and `I7` at tier 0.
|
||||
>
|
||||
> Kept as a record of what was believed. Do not update it; update the YAML.
|
||||
|
||||
> Workstream task file for the Fleet. Single-writer: Fleet workstream lead (orchestrator).
|
||||
> Workers read but never modify. This is **not** the MVP rollup (`docs/TASKS.md`) — a
|
||||
> rollup row is proposed to the MVP orchestrator, not written here.
|
||||
>
|
||||
> Mission: `mvp-20260312` · PRD: [docs/fleet/PRD.md](./PRD.md) · North star: [docs/fleet/north-star.md](./north-star.md)
|
||||
> Mission: `mvp-20260312` · PRD: [docs/fleet/PRD.md](./PRD.md) · North star: [docs/fleet/FLEET-DOCTRINE.md](./FLEET-DOCTRINE.md)
|
||||
> Status: `not-started` | `in-progress` | `done` | `blocked` | `failed`
|
||||
|
||||
| id | status | description | depends_on | agent | pr | notes |
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Desired, Derived, and Observed Fleet State
|
||||
|
||||
## One writable authority
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Generated Environment Launch Chain
|
||||
|
||||
The launcher consumes validated data, not shell configuration.
|
||||
|
||||
1. Read and validate the canonical roster.
|
||||
2. Render deterministic <name>.env.generated data from that roster.
|
||||
2. Render deterministic <name>.env.generated data from that roster, including `MOSAIC_GIT_IDENTITY` derived exactly from the roster agent name.
|
||||
3. Parse optional <name>.env.local through a strict allowlist.
|
||||
4. Reject generated-key shadowing, unknown or sensitive-looking keys, unsafe paths/values, duplicates, malformed lines, shell syntax, and command overrides.
|
||||
5. Derive the runtime command from validated runtime/model/reasoning data.
|
||||
6. Target only the exact configured tmux socket and roster session after ownership checks.
|
||||
5. Reject a Git identity that is unsafe or differs from the generated agent name.
|
||||
6. Derive the runtime command from validated runtime/model/reasoning data and pass every generated projection entry through the clean process environment boundary.
|
||||
7. Target only the exact configured tmux socket and roster session after ownership checks.
|
||||
|
||||
## File precedence and ownership
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Fleet Identity, Class, and Runtime
|
||||
|
||||
Each roster field has one job. Do not use names or model strings as authority shortcuts.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Fleet Role Authority and Leases
|
||||
|
||||
Role content describes behavior; protected authority is immutable code metadata derived only from the canonical class.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# F4 — Orchestrator chat connector + Matrix (local homeserver)
|
||||
|
||||
> **Issue:** #616 · **Doctrine:** `docs/fleet/north-star.md` (#613) — orchestrator-chat-connector decision.
|
||||
> **Issue:** #616 · **Doctrine:** `docs/fleet/FLEET-DOCTRINE.md` (#613) — orchestrator-chat-connector decision.
|
||||
> **Status:** Phase 1 (abstraction + scaffold) in this PR; Phase 2+ are follow-ups (below).
|
||||
|
||||
## Goal
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Configure an Interaction Instance
|
||||
|
||||
An interaction instance is a configurable local roster member with canonical class: interaction and matching tool_policy: interaction. “Tess” may be used as a display alias, but neither that alias nor the stable name is required or authority-bearing.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Configure a Validator Instance
|
||||
|
||||
A validator instance is a configurable local roster member with canonical class: validator and matching tool_policy: validator. “Ultron” may be used as a display alias, but it is not a required identity, class alias, product name, or source of authority.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Create, Inspect, Update, and Delete a Local Fleet Agent
|
||||
|
||||
Use the local roster-v2 control plane only. These commands change desired state and derived environment projections; they never start, stop, reconcile, inspect, or otherwise act on systemd, tmux, sessions, or runtimes.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Customize Fleet Roles
|
||||
|
||||
Mosaic resolves persona contracts through two layers:
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Safely Reconcile and Control a Local Fleet Agent
|
||||
|
||||
Use the canonical local roster-v2 command surface:
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Executable Fleet Example, Profile, and Service-Preset Dispositions
|
||||
|
||||
**Issue:** #758 · **Card:** FCM-M1-003 · **Status:** M1 executable disposition evidence
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Legacy Fleet Class Aliases
|
||||
|
||||
Fleet class compatibility is intentionally narrow. The shared resolver accepts exactly three legacy
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Previewing a Fleet Roster v1-to-v2 Migration
|
||||
|
||||
**Issue:** #758 · **Card:** FCM-M4-001 · **Effect boundary:** preview only
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Fleet Configuration Backup and Restore Boundary
|
||||
|
||||
**Issue:** #758 · **Card:** FCM-M4-001
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Environment Quarantine Operations
|
||||
|
||||
Legacy <name>.env is input evidence, never current launch authority. Projection preparation classifies it deterministically:
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Reconcile and Recover a Local Fleet
|
||||
|
||||
## Safe sequence
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Systemd and tmux Troubleshooting
|
||||
|
||||
Start with read-only mosaic fleet status, `doctor`, and `verify`. Do not manually adopt, rename, terminate, or recreate sessions while ownership is ambiguous.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Upgrade and Installed-Asset Drift
|
||||
|
||||
Fleet source assets and installed assets can differ after an update, but FCM-M5-001 does not add a trustworthy source-versus-installed revision detector or refresh command. Do not infer freshness from checkout presence, timestamps, generated environment files, running sessions, or a ready migration preview.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Local Fleet Agent Mutations
|
||||
|
||||
FCM-M2-002 provides local roster-v2 create, get, update, delete, and plan operations. They only change desired state and derived environment projections. They never start, stop, inspect, reconcile, or otherwise act on runtimes, systemd units, tmux sessions, or heartbeats.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Fleet Control-Plane CLI
|
||||
|
||||
The local desired-state surface is mosaic fleet. It is distinct from the gateway-backed mosaic agent catalog and from legacy compatibility commands that act on roster v1.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Fleet Generated Environment Boundary
|
||||
|
||||
**Card:** FCM-M2-001 · **Issue:** #758 · **Status:** merged contract
|
||||
@@ -35,6 +40,7 @@ values, credential material, or command text.
|
||||
|
||||
```dotenv
|
||||
MOSAIC_AGENT_NAME=<roster name>
|
||||
MOSAIC_GIT_IDENTITY=<roster name>
|
||||
MOSAIC_AGENT_CLASS=<roster class>
|
||||
MOSAIC_AGENT_RUNTIME=<roster runtime>
|
||||
MOSAIC_AGENT_MODEL=<roster model hint>
|
||||
@@ -44,8 +50,9 @@ MOSAIC_AGENT_WORKDIR=<absolute roster work directory>
|
||||
MOSAIC_TMUX_SOCKET=<roster socket or empty>
|
||||
```
|
||||
|
||||
The generated launch contract supports only `claude`, `codex`, `opencode`, and `pi`. fleet add
|
||||
uses that same runtime authority and rejects any other runtime before it writes the roster or changes
|
||||
`MOSAIC_GIT_IDENTITY` is derived from and must equal `MOSAIC_AGENT_NAME`; it is not a separate
|
||||
operator-controlled identity authority. The generated launch contract supports only `claude`, `codex`,
|
||||
`opencode`, and `pi`. fleet add uses that same runtime authority and rejects any other runtime before it writes the roster or changes
|
||||
projection, local, or quarantine files. The legacy dogfood stub on its separate `mosaic-factory`
|
||||
socket remains an observability canary; it has no generated-launch adapter and cannot be added through
|
||||
this projection path.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Local Fleet Lifecycle Transitions
|
||||
|
||||
Roster-v2 `lifecycle.enabled` and `lifecycle.desired_state` are the only persisted lifecycle authority. Systemd, tmux, generated environment, and heartbeat state are derived or observed.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Fleet Role Classes and Authority
|
||||
|
||||
A fleet role class is a machine identity resolved from the persona library. Resolution uses the
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Fleet Roster v2 Structural Contract
|
||||
|
||||
**Status:** FCM-M1-001 local-tmux structural compiler contract. This document describes parsing,
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Local Fleet Status and Drift
|
||||
|
||||
mosaic fleet status [<name>], `verify`, and `doctor` are observational roster-v2 commands. They emit one JSON result and do not write projections, mutate desired state, operate lifecycle, or change tmux.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Deployment Guide
|
||||
|
||||
> **Status: non-operative for PostgreSQL, federated, and bare-metal production.** The checked-in
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Mosaic Stack — Developer Guide
|
||||
|
||||
## Table of Contents
|
||||
@@ -9,8 +14,9 @@
|
||||
5. [Adding New MCP Tools](#adding-new-mcp-tools)
|
||||
6. [Database Schema and Migrations](#database-schema-and-migrations)
|
||||
7. [Claude Code Skill Bridge](#claude-code-skill-bridge)
|
||||
8. [API Endpoint Reference](#api-endpoint-reference)
|
||||
9. [Local Fleet Canary](./fleet-local-canary.md)
|
||||
8. [Pi Persistent Goal Extension](#pi-persistent-goal-extension)
|
||||
9. [API Endpoint Reference](#api-endpoint-reference)
|
||||
10. [Local Fleet Canary](./fleet-local-canary.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -396,6 +402,85 @@ M1 intentionally manages Claude Code only. Pi's Mosaic launcher can discover the
|
||||
canonical root directly. Codex still relies on the existing full skill-sync
|
||||
linker and needs separate parity analysis before this lifecycle API is extended.
|
||||
|
||||
## Pi Persistent Goal Extension
|
||||
|
||||
The source of the Mosaic-owned Pi goal controller is:
|
||||
|
||||
```text
|
||||
packages/mosaic/framework/runtime/pi/goal-extension.ts
|
||||
```
|
||||
|
||||
The framework manifest classifies `runtime/**` as framework-owned. Both the bash installer and the
|
||||
TypeScript file adapter therefore deploy the same reviewed source to:
|
||||
|
||||
```text
|
||||
$MOSAIC_HOME/runtime/pi/goal-extension.ts
|
||||
# default: ~/.config/mosaic/runtime/pi/goal-extension.ts
|
||||
```
|
||||
|
||||
Do not copy or link this extension into `~/.pi/agent/extensions/`. The launcher function
|
||||
`discoverPiExtensionArgs()` emits the core `mosaic-extension.ts` first and the optional
|
||||
`goal-extension.ts` second, preserving compatibility with an older installed framework that does
|
||||
not have the goal file yet.
|
||||
|
||||
### Lifecycle design
|
||||
|
||||
| Pi API | Goal-controller responsibility |
|
||||
| ------------------------------ | --------------------------------------------------------------------------------- |
|
||||
| `registerCommand('goal')` | Set, inspect, pause, resume, or cancel one branch-specific goal |
|
||||
| `registerTool(...)` | Record a terminating structured progress report with evidence |
|
||||
| `context` | Inject the active goal contract before every provider request |
|
||||
| `turn_end` | Record every turn, reject mixed final reports, and enforce the turn bound |
|
||||
| `agent_settled` | Start one deduplicated continuation only after Pi has no retry/compact/queue work |
|
||||
| `session_compact` | Record the compact check, reset provisional verification, and defer idle work |
|
||||
| `session_start`/`session_tree` | Rebuild state from custom entries on the active branch |
|
||||
| `session_shutdown` | Invalidate deferred callbacks and clear UI state |
|
||||
|
||||
State is appended as `mosaic-goal-state` custom entries, which do not enter model context. The
|
||||
`context` hook creates a fresh hidden `mosaic-goal-context` message for each request instead of
|
||||
trusting compaction summaries. The `mosaic_goal_report` result uses `terminate: true`; when it is the
|
||||
sole final tool call, Pi avoids an unnecessary model response before the controller decides whether
|
||||
to verify, continue, or stop.
|
||||
|
||||
Before state is appended or displayed, the controller applies bounded credential-pattern redaction
|
||||
to the goal statement, report summary/evidence/next step, and stop reason. Fingerprints are computed
|
||||
over redacted report content. Pi session entries are append-only, so a credential-bearing legacy
|
||||
entry cannot honestly be erased by the extension: restoration fails closed, emits a warning, and
|
||||
requires removal of the affected session before setting a new goal. This is defense-in-depth rather
|
||||
than a secret-storage contract, and it does not rewrite Pi's separate model-message/tool-call
|
||||
history. Goal prompts tell the agent not to submit credentials or raw sensitive output, and tests use
|
||||
canaries to prove known forms do not reach new custom entries, status text, context, or tool details
|
||||
while ordinary typed fields such as `token: string` remain intact.
|
||||
|
||||
Completion remains evidence-gated but semantic: two consecutive `achieved` reports are required,
|
||||
and the second run is explicitly a verification pass. This avoids an extra judge-model request after
|
||||
every turn. Deterministic validator commands are intentionally not accepted as `/goal` input in this
|
||||
slice, so never describe this mechanism as proof of arbitrary natural-language completion.
|
||||
|
||||
### Tests and local smoke workflow
|
||||
|
||||
```bash
|
||||
pnpm --filter @mosaicstack/mosaic exec vitest run \
|
||||
src/runtime/pi-goal-extension.spec.ts \
|
||||
src/commands/launch.spec.ts \
|
||||
src/config/file-adapter.test.ts
|
||||
|
||||
bash packages/mosaic/framework/tools/quality/scripts/test-install-migration.sh
|
||||
```
|
||||
|
||||
For an additive local smoke test without reseeding unrelated live framework files:
|
||||
|
||||
```bash
|
||||
install -D -m 0644 \
|
||||
packages/mosaic/framework/runtime/pi/goal-extension.ts \
|
||||
~/.config/mosaic/runtime/pi/goal-extension.ts
|
||||
|
||||
pi --extension ~/.config/mosaic/runtime/pi/goal-extension.ts
|
||||
```
|
||||
|
||||
Use `/goal help`, `/goal set ...`, and `/goal status` in that test session. A released framework
|
||||
sync installs the file, and a released Mosaic CLI loads it automatically through `mosaic pi`.
|
||||
|
||||
## API Endpoint Reference
|
||||
|
||||
All endpoints are served by the gateway at `http://localhost:14242` by default.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Local Fleet Canary
|
||||
|
||||
The local fleet canary runs a small tmux-backed Mosaic agent fleet on an
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Migrating to the Federated Tier
|
||||
|
||||
> **KBN-101-07 ownership:** This active documentation is a **non-operative KBN-101
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: tracking
|
||||
status: active
|
||||
---
|
||||
|
||||
# Mission Manifest — Mosaic Native Kanban and Canonical Task SOT P0–P3
|
||||
|
||||
**Mission status:** CANON INDEPENDENTLY APPROVED; publication in progress under issue [#751](https://git.mosaicstack.dev/mosaicstack/stack/issues/751)
|
||||
|
||||
@@ -1,10 +1,24 @@
|
||||
---
|
||||
kind: spec
|
||||
status: active
|
||||
---
|
||||
|
||||
# Native Kanban/SOT P0–P3 — Dependency-Ordered Build Slices
|
||||
|
||||
**Status:** CANON INDEPENDENTLY APPROVED; PUBLICATION IN PROGRESS
|
||||
**Tracking:** [Mosaic Stack issue #751](https://git.mosaicstack.dev/mosaicstack/stack/issues/751)
|
||||
**Execution:** USC web1 only; collision-free GPT coder2/3/4/5 lanes
|
||||
**Contract:** `SHARED-CONTRACT.md` + four `contracts/*.v1.ts` files
|
||||
**Implementation hold:** no feature slice starts until the canon PR is merged to `main` with terminal-green CI; after merge, each slice remains held until every declared KBN prerequisite is complete.
|
||||
|
||||
> **HOLD LIFTED — 2026-08-20.** The stated condition ("no feature slice starts until the
|
||||
> canon PR is merged to `main` with terminal-green CI") was **met on 2026-07-14** by
|
||||
> `49e8a541` — _docs(#751): Publish native Kanban/SOT canon (#752)_ — which is on both
|
||||
> `main` and `next`, and `docs/requirements/native-kanban-sot.md` is present on `main`.
|
||||
> The line below was never updated, so for five weeks this file advertised a blocker that
|
||||
> did not exist. The status line above ("PUBLICATION IN PROGRESS") is stale for the same
|
||||
> reason. This file is a build plan, not a task tracker, and is NOT superseded.
|
||||
|
||||
**Implementation hold (SATISFIED — see note above):** no feature slice starts until the canon PR is merged to `main` with terminal-green CI; after merge, each slice remains held until every declared KBN prerequisite is complete.
|
||||
|
||||
> This publication file is not a runtime task authority. After cutover, repository `TASKS.md` is generated read-only and never imported.
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: spec
|
||||
status: active
|
||||
---
|
||||
|
||||
# CI Queue Guard Purpose Semantics
|
||||
|
||||
- **Issue:** #1146
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: spec
|
||||
status: completed
|
||||
---
|
||||
|
||||
# CI Queue Guard Purpose Semantics Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: spec
|
||||
status: active
|
||||
---
|
||||
|
||||
# Documentation Catalog and Truth Audit Plan
|
||||
|
||||
**Task:** DOCS-IA-002
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: spec
|
||||
status: active
|
||||
---
|
||||
|
||||
# Documentation Information Architecture Design
|
||||
|
||||
**Status:** Approved
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: spec
|
||||
status: completed
|
||||
---
|
||||
|
||||
# Documentation Structure README Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
@@ -0,0 +1,429 @@
|
||||
---
|
||||
kind: tracking
|
||||
status: active
|
||||
---
|
||||
|
||||
# W4 — document contract worklist
|
||||
|
||||
Companion to `2026-08-20_stack-docs-flatten-and-alignment.md`. That document proposes the
|
||||
contract; this one records what was applied, what was held, and what still needs a decision.
|
||||
|
||||
Measured on `origin/next` at `63069149`. Author: veronica. Review: fred (Gate-16, author is
|
||||
not reviewer), then a pi seat for the adversarial pass.
|
||||
|
||||
## What "live" means here
|
||||
|
||||
All `*.md` under `docs/`, minus `docs/archive/`, minus `docs/_old_structure/`. That is **127**
|
||||
files. The flatten plan says 130; the arithmetic does not close (318 total = 134 archive +
|
||||
57 `_old_structure` + 127 live, not 130).
|
||||
|
||||
## Applied
|
||||
|
||||
**These are the FIRST-PASS counts and they are superseded. The current tree is counted in
|
||||
"Verification arithmetic, re-closed" at the end of this document.** They are left standing rather
|
||||
than overwritten, for the same reason every other correction here is: a record of what a pass
|
||||
produced is worth more than a number silently updated to still look right.
|
||||
|
||||
| bucket | count | note |
|
||||
| --------------------------- | ----- | -------------------------------------- |
|
||||
| stamped `kind` + `status` | 107 | this pass |
|
||||
| held, operator judgement | 17 | section "Needs a decision" below |
|
||||
| held, cites the moving path | 3 | the three `SUPERSEDED` TASKS.md stamps |
|
||||
| held, generated file | 1 | `docs/fleet/NORTH_STAR.md`, see below |
|
||||
|
||||
128 live `.md` under `docs/`, which is the 127 baseline plus this document. 107 + 17 + 3 + 1 = 128.
|
||||
|
||||
Kinds: 53 `guide`, 34 `record`, 13 `spec`, 7 `tracking`. Status: 105 `active`, 2 `completed`.
|
||||
|
||||
After fred's Q1 ruling stamped `docs/README.md`, one file moved from the held bucket to the stamped
|
||||
one and nothing else changed: **108 stamped + 16 + 3 + 1 = 128**, kinds 54 `guide`, 34 `record`,
|
||||
13 `spec`, 7 `tracking`, status 106 `active`, 2 `completed`. Two files carry
|
||||
`source_of_truth: true`, `docs/README.md` and `docs/requirements/native-kanban-sot.md`.
|
||||
|
||||
`parent` is **not** applied. It points at `docs/fleet/NORTH_STAR.yaml`, which the flatten moves
|
||||
to `docs/NORTH_STAR.yaml`. Stamping it now means re-pointing 127 files by hand later. It lands
|
||||
after the move, in one pass, with the post-move path.
|
||||
|
||||
## Held: the three superseded stamps
|
||||
|
||||
`docs/TASKS.md:5`, `docs/federation/TASKS.md:5`, and `docs/fleet/TASKS.md:5` each carry a W1
|
||||
stamp of the form:
|
||||
|
||||
> **STATUS: SUPERSEDED — 2026-08-20.** kind `tracking` · superseded by `docs/fleet/NORTH_STAR.yaml`
|
||||
|
||||
Two problems, both real:
|
||||
|
||||
1. **The tooling cannot read them.** That line is a blockquote below the H1, not YAML front
|
||||
matter. Plan section 6 check 5 ("every live document has a header; no document is
|
||||
unclassified") parses front matter, so all three read as unclassified. The control that the
|
||||
parse itself works is `docs/fleet/FLEET-DOCTRINE.md:3`, which is genuine front matter.
|
||||
2. **They cite the moving path.** Three of the six citations of `docs/fleet/NORTH_STAR.yaml`
|
||||
are these stamps. Converting them before the flatten lands makes them stale on merge.
|
||||
|
||||
Converted after the flatten, not before.
|
||||
|
||||
## Held: the one generated file
|
||||
|
||||
`docs/fleet/NORTH_STAR.md` is a `projection` and was stamped in the first pass. The stamp was
|
||||
**reverted before review**, because stamping it is self-contradictory in a way that is not
|
||||
merely theoretical:
|
||||
|
||||
- The contract says a `projection` is "Generated. Never hand-edited." Adding front matter by
|
||||
hand is a hand edit of a generated file, and the file's own banner says
|
||||
"**Generated file — do not edit by hand.**"
|
||||
- `renderNorthStarMarkdown()` at `packages/mosaic/src/commands/fleet.ts:373` emits the H1 as
|
||||
its first line and no front matter at all.
|
||||
- `fleet-north-star.spec.ts:110-114` asserts full-string equality between the renderer's output
|
||||
and the committed file: `expect(rendered).toBe(committed)`. Before the stamp, line 1 of the
|
||||
committed file was `# Mosaic Fleet — NORTH STAR`, matching the renderer. After it, line 1 was
|
||||
`---`. The assertion fails.
|
||||
|
||||
So the header for a projection cannot live in the file. It has to be emitted by
|
||||
`renderNorthStarMarkdown()`, which is a code change and belongs in the flatten PR alongside the
|
||||
`resolveNorthStarPaths()` fix, not in a documentation pass.
|
||||
|
||||
This generalises: **the contract as written cannot classify any generated document without a
|
||||
matching change to its generator.** `NORTH_STAR.md` is the only projection today, so the cost
|
||||
is one function. It will not stay one.
|
||||
|
||||
## Needs a decision
|
||||
|
||||
Nineteen rows. Seventeen are the plan's section 9 list, minus `docs/fleet/north-star.md`
|
||||
(renamed to `FLEET-DOCTRINE.md` by W1, so the row is closed) and minus the two `.yaml` rows,
|
||||
which are a different problem — see the next section. Two rows are new, found while
|
||||
classifying.
|
||||
|
||||
Fill the `kind` column with a value from the contract, or `superseded-by: <path>`.
|
||||
|
||||
| path | proposed | why it is not mechanical |
|
||||
| --------------------------------------------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `docs/README.md` | `guide` | It also **prescribes** the competing front-matter convention (see below). Whatever kind it gets, its body needs an edit. |
|
||||
| `docs/SITEMAP.md` | `guide` or `projection` | If it is generated from the tree it is a projection and needs a drift test. If hand-maintained it is a guide that goes stale silently. Nobody has said which. |
|
||||
| `docs/federation/SETUP.md` | `guide` | Reads as a guide. Federation tier status is the open part: if the tier is shelved this is `superseded`. |
|
||||
| `docs/fleet/FLEET-CONFIG-DOCS-IA-CHECKLIST.md` | `record` | An acceptance checklist is evidence of a past gate, which is `record`. But if it is still being checked against, it is `tracking`. |
|
||||
| `docs/fleet/FLEET-LAUNCH.md` | `guide` | Runbook. Depends on whether `launch-seat.sh` is deprecated in favour of `mosaic fleet` (campaign W3). If so, `superseded`. |
|
||||
| `docs/fleet/LEGACY-EXAMPLE-PROFILE-DISPOSITION-INVENTORY.md` | `record` | An inventory of dispositions taken. `record` unless dispositions are still pending, which the title implies they were once. |
|
||||
| `docs/fleet/README.md` | `guide` | Section index. Low risk; listed only because the plan lists it. |
|
||||
| `docs/fleet/backlog-conventions.md` | `guide` | Conventions decide things, and `guide` explicitly decides nothing. May be `spec`. |
|
||||
| `docs/fleet/f4-matrix-connector.md` | `spec` | F4 is a workstream. Whether it is live or abandoned decides `spec` versus `superseded`. |
|
||||
| `docs/native-kanban-sot/DOCUMENTATION-CHECKLIST.md` | `record` | Same question as the fleet IA checklist. |
|
||||
| `docs/native-kanban-sot/INDEX.md` | `guide` | Index of the canon. If it is generated from the canon it is a projection. |
|
||||
| `docs/native-kanban-sot/KBN-010-THREAT-AUTH-CONSTRAINT-GATE.md` | `spec` | 415 lines of normative gate. `spec` fits; confirm it is not superseded by the shared contract. |
|
||||
| `docs/native-kanban-sot/KBN-101-DB-ROLE-SPLIT.md` | `spec` | As above. |
|
||||
| `docs/native-kanban-sot/KBN-101-ENVELOPE-A.md` | `spec` | Title says "v6, FINAL". If v6 supersedes v1-v5 elsewhere, those need `superseded-by` pointing here. |
|
||||
| `docs/native-kanban-sot/SHARED-CONTRACT.md` | `spec` | "Remediated Shared Contract v1". Same versioning question. |
|
||||
| `docs/release-integrity/probe-inventory.md` | `record` | An inventory. `tracking` if probes are still being added to it. |
|
||||
| `docs/webui/PHASE-P-STRUCTURE.md` | `spec` | Zero inbound references (plan section 5.4). Either wire it in or mark it superseded; the kind is the smaller question. |
|
||||
| `docs/native-kanban-sot/TASKS.md` | **conflict** | The file says of itself: "This file is a build plan, not a task tracker, and is NOT superseded." The contract says `tracking` is "live state, single-writer (manifests, `TASKS.md`). **Never a spec**." The file claims to be the thing its name forbids. Either the file is wrong or the contract's `TASKS.md` shorthand is. |
|
||||
| `docs/requirements/native-kanban-sot.md` | **conflict** | Plan section 5.2 says if it is hand-authored canon it is `source-of-truth`. The contract's own table says `source-of-truth` is **machine-readable**. This is prose markdown with normative MUST/MUST NOT, RATIFIED 2026-07-14, decision owner Jason. It cannot satisfy both rules. Either it is a `spec` or the machine-readable criterion is wrong. |
|
||||
|
||||
The last two are not slow rows, they are contradictions inside the contract. They want an
|
||||
answer about the contract, not about the file.
|
||||
|
||||
## The contract collides with an existing one
|
||||
|
||||
`docs/README.md` lines 150-160 already document a front-matter convention, with its own
|
||||
allowed values:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: Human-readable page title
|
||||
type: guide # guide | concept | reference | decision | rfc | runbook
|
||||
audience: developer # user | admin | developer | all
|
||||
status: current # current | draft | deprecated | historical
|
||||
source_of_truth: false
|
||||
---
|
||||
```
|
||||
|
||||
Adoption is 4 of 127 files: `docs/ADMIN-GUIDE/security/sso-providers.md`,
|
||||
`docs/DEVELOPER-GUIDE/testing/lease-broker-operations.md`,
|
||||
`docs/USER-GUIDE/getting-started/quickstart.md`, `docs/USER-GUIDE/product/web-dashboard.md`.
|
||||
|
||||
`status` is in both schemas with **disjoint vocabularies**. `type` and `kind` are two names for
|
||||
one idea with different value sets. `source_of_truth: false` is a boolean spelling of
|
||||
`kind: source-of-truth`.
|
||||
|
||||
What this pass did, and it is a decision someone should ratify or reverse: the new contract
|
||||
wins. Those 4 files had `status: current` rewritten to `status: active` and gained `kind:`.
|
||||
Their `title`, `type`, `audience` and `source_of_truth` keys were left alone. Nothing reads any
|
||||
of them — `git grep source_of_truth` outside `docs/` returns zero hits — so no consumer broke.
|
||||
|
||||
`docs/README.md` still prescribes the old convention. It is an operator row above, so this pass
|
||||
did not edit it. Until it is edited, the repository documents two conflicting header
|
||||
conventions and points authors at the one being retired.
|
||||
|
||||
## The contract has no form for a YAML document
|
||||
|
||||
Two of the plan's 20 rows are not markdown: `docs/fleet/examples/roster-v2.yaml` and
|
||||
`docs/openapi-tess.yaml`. Front matter is a markdown convention. A `.yaml` file can carry a
|
||||
leading `---` document, but then it is two YAML documents and every existing parser of that
|
||||
file sees a change.
|
||||
|
||||
This is not an edge case. It applies to **`docs/fleet/NORTH_STAR.yaml`, the source of truth
|
||||
itself**, which is the one file the contract most needs to classify. Section 6 check 5 says no
|
||||
document is unclassified. As written, the source of truth cannot comply.
|
||||
|
||||
Options, none of them chosen here: exclude `.yaml` from the contract and say so; carry their
|
||||
metadata in a sidecar; or add a top-level `kind:` key inside the YAML body rather than as front
|
||||
matter, which for `NORTH_STAR.yaml` is a schema change.
|
||||
|
||||
## Method, and what it cannot tell you
|
||||
|
||||
Classification is per-file, by title and path, recorded with a confidence. It is not a regex
|
||||
sweep. The plan's own warning stands and is why the 19 rows above are held rather than guessed:
|
||||
the first classifier pass classed a RATIFIED requirements document as a projection.
|
||||
|
||||
Rows marked `med` in the working manifest and not listed above: the five `docs/plans/*` specs,
|
||||
`docs/fleet/migration/example-profile-disposition.md`, the one ADR
|
||||
(`decisions/mos-runtime-portability-m1.md`, called `record` because an ADR records a decision
|
||||
taken), and the one RFC (`rfcs/optional-ai-egress-gateways.md`, called `spec` because it
|
||||
proposes work not yet built). Those eight are stamped and are the most likely to be wrong.
|
||||
|
||||
## Verification
|
||||
|
||||
- 103 of 103 files parse with the expected `kind` and `status` in front matter.
|
||||
- The check was shown to reject a wrong kind before it was trusted (asserting
|
||||
`kind: record` on a file stamped `guide` fails).
|
||||
- The whole diff removes 4 lines, all of them `status: current`.
|
||||
- 24 files untouched, matching 19 + 3 + 1 + 1.
|
||||
|
||||
## Response to the W5 adversarial pass
|
||||
|
||||
vision ran a refute-first pass on this branch at `37cd00e` from a fresh worktree. Three of its
|
||||
points changed the branch. Everything is re-measured here before being acted on; where my
|
||||
measurement disagrees with its stated evidence I say so.
|
||||
|
||||
### 1. `docs/fleet/NORTH_STAR.md` stamp reverted (`bea47543`)
|
||||
|
||||
vision raised this as **latent** and scoped to the flatten PR. It is **live in this PR**, so it
|
||||
could not wait.
|
||||
|
||||
`fleet-north-star.spec.ts:110-114` is a drift test that reads the committed file from disk and
|
||||
asserts full-string equality against `renderNorthStarMarkdown()`, whose first emitted line is the
|
||||
H1 and which emits no front matter. Stamping changed line 1 from the H1 to `---`.
|
||||
|
||||
CI 2589 confirms it directly, no longer by construction:
|
||||
|
||||
```
|
||||
× renderNorthStarMarkdown > matches the committed NORTH_STAR.md projection (regenerate if this fails)
|
||||
→ expected '# Mosaic Fleet — NORTH STAR\n\n> **Ge…' to be '---\nkind: projection\nstatus: active…'
|
||||
```
|
||||
|
||||
Reverted to `origin/next` verbatim. `git diff origin/next -- docs/fleet/NORTH_STAR.md` is 0 lines;
|
||||
control on `docs/fleet/reference/cli.md` returns 13, so the diff command does report differences.
|
||||
|
||||
The consequence is a contract-level one, recorded in the worklist: **the contract as written
|
||||
cannot classify any generated document without a matching change to its generator.** A `projection`
|
||||
is "Generated. Never hand-edited," so its header has to come out of the renderer. That is a code
|
||||
change and belongs in the flatten PR beside the `resolveNorthStarPaths()` fix. vision's
|
||||
recommendation, and I agree with it.
|
||||
|
||||
Counts: stamped 104 → **103**. Untouched 23 → **24**.
|
||||
|
||||
### 2. The `docs/` qualifier (vision's C1)
|
||||
|
||||
Stated as asked. **"127 live documentation files" is true for `docs/` only.** Definition: all
|
||||
`*.md` under `docs/`, minus `docs/archive/` and `docs/_old_structure/`. 318 total = 134 archive +
|
||||
57 `_old_structure` + 127 live.
|
||||
|
||||
Repo-wide the phrase undercounts: 21 live markdown files sit outside `docs/` and outside any named
|
||||
exclusion (17 under `guides/`, plus `README.md`, `AGENTS.md`, `CLAUDE.md`, `REPORT-A1207.md` at
|
||||
root). This PR does not stamp them and does not claim to.
|
||||
|
||||
### 3. `format` failure, and what it says about the header (`8a55c041`)
|
||||
|
||||
CI 2589 also failed `prettier --check` on **exactly one file**: the plan document I hand-wrote.
|
||||
Reproduced locally, rc=1, same single file. Fixed; the diff is 31 lines in, 31 out, all table
|
||||
column padding.
|
||||
|
||||
Worth stating for the review rather than burying: **the other 103 stamped documents pass
|
||||
`prettier --check` unchanged.** The `---\nkind:\nstatus:\n---` block is prettier-clean as applied.
|
||||
The formatting failure was in my prose, not in the contract header.
|
||||
|
||||
### 4. R1: the evidence inverts, the finding gets stronger
|
||||
|
||||
vision asks for a `kind` change on `docs/DEVELOPER-GUIDE/architecture/channel-protocol.md`, on the
|
||||
grounds that it "carries 7 normative MUSTs" while the contract says `guide` "decides nothing."
|
||||
|
||||
**The MUST count does not hold.** Uppercase RFC2119 terms (`MUST`, `MUST NOT`, `SHOULD`,
|
||||
`SHOULD NOT`, `SHALL`, `MAY`, `REQUIRED`) in that file: **0 lines**. Control: the identical grep
|
||||
returns 25 lines in `docs/requirements/native-kanban-sot.md`, so it finds them where they exist.
|
||||
The seven lowercase "must" occurrences all _disclaim_ authority rather than assert it: "must not be
|
||||
used as instructions", "must not be treated as current behavior", "must remain planned work", "must
|
||||
first specify", "before it can become architecture guidance." The file's own banner reads "it is
|
||||
not a runtime registry, an API contract, a requirements document."
|
||||
|
||||
**The citation half holds, and is larger than stated.** vision's line numbers are exact. I
|
||||
restated them earlier with wrong directories, which is worth naming because it is the same method
|
||||
failure fred and I already wrote up as C3 in the flatten plan: I matched on basename and assumed
|
||||
the path. The real ones, plus the two vision did not list:
|
||||
|
||||
| citing document | line | words used |
|
||||
| ------------------------------------------------------- | ---- | --------------------------------------------------- |
|
||||
| `docs/ADMIN-GUIDE/security/discord-ingress.md` | 141 | "**canonical** shared-contract and parity boundary" |
|
||||
| `docs/DEVELOPER-GUIDE/integrations/channel-adapters.md` | 28 | "The **canonical** architecture summary is" |
|
||||
| `docs/DEVELOPER-GUIDE/integrations/channel-adapters.md` | 183 | "**Canonical** channel protocol architecture" |
|
||||
| `docs/USER-GUIDE/workflows/discord-conversations.md` | 127 | "current shared types ... explicit parity boundary" |
|
||||
| `docs/SITEMAP.md` | 54 | index entry |
|
||||
| `docs/DEVELOPER-GUIDE/architecture/README.md` | 18 | index entry |
|
||||
|
||||
So the tension is real. Three live documents outside the two indexes cite it, across four
|
||||
citations, and three of those four use the word "canonical" for a document that spends its own
|
||||
banner denying it is canonical. **It is just not a MUST problem, and that
|
||||
changes what the fix is.** If the file is telling the truth about itself, the three "canonical"
|
||||
citations are wrong and the edit belongs in those three files, not in this one's `kind`.
|
||||
|
||||
**Left as `kind: guide` in this PR and flagged for the reviewer's call.** Restamping on evidence
|
||||
that inverts on reading would be worse than leaving it stamped and named.
|
||||
|
||||
### Unchanged
|
||||
|
||||
vision's C2 (no consumer), C4 (holding `parent` blocks nothing) and C5 (a front-mattered `.yaml`
|
||||
throws in `YAML.parse`) all reverified. C1's arithmetic closes at the stated ref.
|
||||
|
||||
## fred's six decisions, applied
|
||||
|
||||
Ruled on PR #1350 as comment 23693. Each is applied here; each is his call, not mine, and any of
|
||||
them is one line to reverse.
|
||||
|
||||
| # | decision | applied as |
|
||||
| --- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
||||
| A | New contract wins; `docs/README.md` is rewritten in this PR and the 4 old-schema files convert in the same pass | `docs/README.md:149-190` rewritten; `type:` dropped from the 4 files, `title`/`audience`/`source_of_truth` kept |
|
||||
| B | `source-of-truth` leaves the `kind` enum and returns as an orthogonal boolean | enum is now 6 values; `docs/requirements/native-kanban-sot.md` stamped `kind: spec` + `source_of_truth: true` |
|
||||
| C | `status` gains a third value, `completed` | the two executed plans take it (evidence below) |
|
||||
| D | Kind follows content, never filename | `docs/native-kanban-sot/TASKS.md` stamped `kind: spec`, because its body says "a build plan, not a task tracker" |
|
||||
| E | The contract covers `.md` only, stated as a decision rather than left as a gap | written into `docs/README.md` with vision's `YAML.parse` measurement as the reason |
|
||||
| F | `channel-protocol.md` becomes `spec` | applied, with one correction and one consequence below |
|
||||
|
||||
### C: evidence the two plans are complete
|
||||
|
||||
Neither plan self-declares completion, so this is measured from the artifacts rather than taken
|
||||
from the documents:
|
||||
|
||||
- `2026-08-10-ci-queue-purpose-implementation.md` — the shipped guard carries the flag the plan
|
||||
specifies: `ci-queue-wait.sh --purpose push|merge`, exercised in this session at rc=0.
|
||||
- `2026-08-10-docs-structure-readme.md` — every section the plan specifies exists in
|
||||
`docs/README.md` today, including the Obsidian conventions and the source-of-truth precedence
|
||||
block. This PR is editing the artifact that plan produced.
|
||||
|
||||
### F: the MUST count does not hold, and the fix changes shape
|
||||
|
||||
Applied as ruled. But the ruling's stated grounds are half wrong, and the half that survives points
|
||||
somewhere else, so this is the one to look at again.
|
||||
|
||||
**Zero** uppercase RFC2119 terms in `channel-protocol.md`, not seven. Control: the identical grep
|
||||
returns 25 lines in `docs/requirements/native-kanban-sot.md`. The seven lowercase "must"
|
||||
occurrences all _disclaim_ authority: "must not be used as instructions", "must not be treated as
|
||||
current behavior", "must remain planned work".
|
||||
|
||||
**The citation half holds and is bigger than stated** (table in the section above).
|
||||
|
||||
**The consequence of applying F:** the file is now stamped `spec` while its own banner says "it is
|
||||
not a runtime registry, an API contract, a requirements document." Header and body now contradict
|
||||
each other, which is the defect this pass exists to remove. Either the banner is edited in this PR,
|
||||
or the three documents calling it canonical are the ones that are wrong. That is a content call and
|
||||
it is left to the reviewer rather than folded into a stamping pass.
|
||||
|
||||
## Q1 — the one question this pass cannot answer for itself
|
||||
|
||||
`docs/README.md` now **prescribes** the document contract, and it is the only live document under
|
||||
`docs/` with no `kind`. It is still on the operator-held list, so it is left unstamped.
|
||||
|
||||
By decision B it is arguably `kind: spec` with `source_of_truth: true` for the subject "document
|
||||
contract". The reason this is not applied unilaterally: it decides which document outranks the
|
||||
other when `docs/README.md` and `docs/plans/2026-08-20_stack-docs-flatten-and-alignment.md` disagree
|
||||
about the contract, and they already differ (the plan's enum has 7 values, the README's now has 6).
|
||||
That is an authority question, not a classification one.
|
||||
|
||||
## The old schema DID have a consumer, and CI found it
|
||||
|
||||
vision's C2 concluded "no consumer found" after searching by parsing primitive rather than by key
|
||||
name. fred's decision A rested on "no installed base to protect." I accepted both. **All three of
|
||||
us were wrong, and the full test suite is what proved it.**
|
||||
|
||||
`packages/mosaic/src/installation-documentation.spec.ts:39` asserted:
|
||||
|
||||
```ts
|
||||
expect(markdown).toMatch(/^---\n[\s\S]*?\nstatus: current\n[\s\S]*?\n---\n/);
|
||||
```
|
||||
|
||||
That is a raw regex over the markdown text, not a front-matter parse and not a key lookup, which is
|
||||
exactly why a search organised around parsing primitives could not see it. It pins
|
||||
`docs/USER-GUIDE/getting-started/quickstart.md` to the old vocabulary. Replacing `status: current`
|
||||
with `status: active` turned it red.
|
||||
|
||||
Updated to `status: active`, the contract's value for "in force", with the reason in a comment
|
||||
beside it. Verified by evaluating both regexes against the real file: old `false`, new `true`.
|
||||
Control: a page carrying `superseded-by` still fails the new regex, so the assertion still asserts
|
||||
something rather than matching anything with front matter.
|
||||
|
||||
**The method point, which outlives this file.** CI 2592 ran the whole suite against the stamped
|
||||
tree, 46 turbo tasks, and returned exactly one failing spec: this one. That is a stronger consumer
|
||||
search than any grep the three of us ran, because it does not depend on guessing how a consumer
|
||||
reads the file.
|
||||
|
||||
An earlier draft of this paragraph put a test count here, "1617 tests, 1 failed". **That number was
|
||||
wrong and it is withdrawn.** Extracting per-test totals from these pipeline logs is not reliable:
|
||||
the same regex over the same log format returns 1003 for 2592, 1022 for 2593 and 3471 for 2594,
|
||||
which are runs of the same suite. Three irreconcilable answers from one method is proof the method
|
||||
does not measure what it claims. What the log does carry reliably is the FAIL list and the turbo
|
||||
task line, so the claim is stated in those terms instead. The point never needed the count: one
|
||||
named failing spec is what refuted three hand-searches. **Run the suite before concluding a
|
||||
documentation change has no code consumers.** Two independent seats and a reviewer searching by
|
||||
hand missed the one that existed.
|
||||
|
||||
## fred's second pass: F withdrawn, Q1 answered
|
||||
|
||||
Both changes below are fred's rulings, applied. Neither is my judgement and I record whose it is.
|
||||
|
||||
### F is withdrawn: `channel-protocol.md` returns to `kind: guide`
|
||||
|
||||
Reverted. fred re-measured the file himself with a control and reached the count I reported: zero
|
||||
uppercase RFC2119 terms case-sensitive, seven lowercase `must`, every one disclaiming authority,
|
||||
under a banner that refuses requirements status. A page like that is a guide.
|
||||
|
||||
The reason this matters past one stamp is fred's own: F rested on "the doc graph outranks the
|
||||
page's own banner", which contradicts his decision D, "kind follows content, never the filename and
|
||||
never what other documents say about it". D is the rule. F was a counterexample to it, written in
|
||||
the same comment.
|
||||
|
||||
Neither rescue was taken. vision's adjective edit on the three citing docs and an edit to the
|
||||
page's banner would both have preserved a stamp that should not have been made. The three citing
|
||||
documents stay as they are: under vision's index-pointer reading, "canonical" claims the best page
|
||||
on a subject rather than normative force, so there is nothing to correct.
|
||||
|
||||
**What this costs the contract: nothing, and that is the point.** A kind that survives only by
|
||||
editing the evidence around it is not a classification.
|
||||
|
||||
### Q1 answered: `docs/README.md` is stamped `kind: spec`, `source_of_truth: true`
|
||||
|
||||
Applied. By D, a document that prescribes the contract has spec content. By B, `source_of_truth` is
|
||||
an orthogonal boolean and this is the authoritative statement of the contract, so it carries both.
|
||||
|
||||
The authority half of Q1 was whether stamping the README settles a conflict with the flatten plan,
|
||||
whose enum still has seven values against the README's six. fred's ruling: **a plan never outranks
|
||||
the artifact it planned.** The plan's enum is pre-decision-B staleness, not a competing authority.
|
||||
The plan is fred's file and he corrects it there.
|
||||
|
||||
The contract now applies to the document that states it. That was the only state in which it could
|
||||
be credible.
|
||||
|
||||
### Verification arithmetic, re-closed
|
||||
|
||||
128 live `.md` under `docs/` = **108 stamped** + 16 operator-held + 3 supersede deferrals + 1
|
||||
generated. The stamped count rose by one and the operator-held set fell by one, both because of the
|
||||
README; every other row is unchanged. Control unchanged: the verifier reports `valid=False` when a
|
||||
kind is corrupted to `nonsense`.
|
||||
|
||||
### One consumer finding that is not a defect
|
||||
|
||||
A sweep of every spec reading a path under `docs/` returns 10 files. Four read a live file:
|
||||
`fleet-north-star.spec.ts` and `installation-documentation.spec.ts` (both already caught by CI),
|
||||
`mutator-gate.acceptance.spec.ts` reading `compaction-revocation.md`, which passes under its `guide`
|
||||
stamp, and `roster-v2.spec.ts:366`, which reads `docs/fleet/reference/roster-v2.schema.json`.
|
||||
|
||||
The fourth is a real read of a real live file under `docs/` and is untouched only because decision E
|
||||
scopes the contract to `.md`. Had the contract covered every file under `docs/`, front matter in
|
||||
that JSON schema would have broken the spec, the same failure as the quickstart regex. E was
|
||||
load-bearing beyond the YAML-parse grounds it was decided on. No action; recorded so the `.md`
|
||||
boundary is not widened later without re-running this sweep.
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Documentation Plans
|
||||
|
||||
> **Status:** Current artifact index. Plans record approved intent and execution approach; they are not current product behavior or operational authority.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: tracking
|
||||
status: active
|
||||
---
|
||||
|
||||
# Tasks — Release Integrity Workstream (RI-050, #1275)
|
||||
|
||||
> Single-writer: the RI-050 orchestrator (jarvis, dragon-lin) only. Workers read but never modify.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: guide
|
||||
status: active
|
||||
---
|
||||
|
||||
# Documentation Reports
|
||||
|
||||
> **Status:** Current evidence index. Reports record reviews, tests, audits, and deferred findings; they are not requirements or operational instructions by themselves.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: record
|
||||
status: active
|
||||
---
|
||||
|
||||
# Independent Code Review — #756 Official Discord Channel Plugin
|
||||
|
||||
**Verdict: APPROVE**
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: record
|
||||
status: active
|
||||
---
|
||||
|
||||
# Code Review Report — Gateway Security Hardening
|
||||
|
||||
## Scope Reviewed
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: record
|
||||
status: active
|
||||
---
|
||||
|
||||
# #830 Documentation Completion Checklist
|
||||
|
||||
## Required artifacts
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: record
|
||||
status: active
|
||||
---
|
||||
|
||||
# FCM-M5-001 Fleet Documentation Deferrals and Holds
|
||||
|
||||
**Issue:** #758 · **Branch:** `docs/758-fleet-config-operator-docs`
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: record
|
||||
status: active
|
||||
---
|
||||
|
||||
# Mosaic Stack Documentation Catalog and Truth Audit
|
||||
|
||||
> **Status:** First-pass static audit — 2026-08-10
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: record
|
||||
status: active
|
||||
---
|
||||
|
||||
# Documentation Completion Checklist — #756 Official Discord plugin
|
||||
|
||||
## Required artifacts
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
---
|
||||
kind: record
|
||||
status: active
|
||||
---
|
||||
|
||||
# FCM-M5-001 Fleet Documentation IA Closure Evidence
|
||||
|
||||
**Issue:** #758 · **Task:** FCM-M5-001
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user