Compare commits

..
Author SHA1 Message Date
veronica 98f3384554 docs(W4): mark the Applied table as first-pass and point it forward (fred's F1)
ci/woodpecker/pr/ci Pipeline was successful
The Applied table carried 107 stamped and 17 held, the counts from the first
pass, with nothing telling a reader they had moved. The re-closed arithmetic at
the end of the document was already correct, so the document was not wrong
overall, but a reader hitting the table got a stale count with no forward
pointer. In this PR specifically that is the defect the PR exists to remove,
appearing in the PR's own worklist.

The first-pass numbers are left standing and labelled rather than overwritten,
matching every other correction in this document: a record of what a pass
produced is worth more than a number silently updated to still look right. The
current census follows it, re-measured: 108 stamped + 16 + 3 + 1 = 128, kinds 54
guide / 34 record / 13 spec / 7 tracking, status 106 active / 2 completed, and
the two files carrying source_of_truth: true named.
2026-08-20 20:46:16 -05:00
veronica 4211fb87f8 docs(W4): withdraw the test count from the method paragraph, do not replace it
ci/woodpecker/pr/ci Pipeline was canceled
The paragraph arguing 'run the suite before concluding a docs change has no code
consumers' carried a number: 'CI 2592 ran 1617 tests, 1 failed, 1616 passed'.
That number is wrong and is withdrawn in place rather than deleted.

Extracting per-test totals from these pipeline logs is not a reliable method:
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,
so no corrected count is substituted.

The claim is restated in what the log carries reliably, the named FAIL list and
the turbo task line. The count was decoration on a claim already carried by a
specific name: one named failing spec is what refuted three hand-searches.
2026-08-20 20:41:52 -05:00
veronica ff40794bfe docs(W4): revert channel-protocol to guide (F withdrawn), stamp docs/README.md as the contract's source of truth (Q1)
ci/woodpecker/pr/ci Pipeline was successful
fred withdrew decision F after re-measuring channel-protocol.md with a control:
zero uppercase RFC2119 terms, seven lowercase 'must' all disclaiming authority,
under a banner refusing requirements status. F rested on 'the doc graph outranks
the page's own banner', which contradicts decision D, 'kind follows content'.
D is the rule; F was its counterexample. Neither rescue (vision's adjective edit
on the citing docs, or a banner edit) was taken: a kind that survives only by
editing the evidence around it is not a classification.

Q1 answered: docs/README.md prescribes the contract, so by D its content is a
spec, and by B it is the source of truth for that contract. A plan never
outranks the artifact it planned, so the flatten plan's stale 7-value enum is
corrected there rather than competing here.

128 live docs = 108 stamped + 16 operator-held + 3 supersede deferrals + 1 generated.
2026-08-20 20:21:06 -05:00
veronica 94a7d5b692 test(mosaic): pin quickstart.md to status: active, the contract's value for in-force
ci/woodpecker/pr/ci Pipeline failed
CI 2592 proved the old front-matter schema HAD a consumer. vision's C2 concluded "no consumer
found", fred's decision A rested on "no installed base to protect", and I accepted both. All three
of us were wrong.

installation-documentation.spec.ts:39 asserted a raw regex over the markdown text:

    expect(markdown).toMatch(/^---\n[\s\S]*?\nstatus: current\n[\s\S]*?\n---\n/);

Not a front-matter parse and not a key lookup, which is why a search organised around parsing
primitives could not see it. Stamping docs/USER-GUIDE/getting-started/quickstart.md replaced
`status: current` with `status: active` and turned it red.

Updated to the contract's vocabulary, intent unchanged: the page must still declare itself in
force, and must still teach no curl-pipe-to-shell install. 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.

2592 also confirms the NORTH_STAR revert took: the north-star drift test is green, `format` and
`lint` are green, and this was the only failure in 1617 tests.

Method point worth keeping: the full suite is a stronger consumer search than any grep, because it
does not depend on guessing how a consumer reads the file.
2026-08-20 20:04:35 -05:00
veronica 12d5258e20 docs(W4): apply fred's six contract decisions from PR #1350 comment 23693
A - docs/README.md:149-190 rewritten. It prescribed a competing front-matter schema
    (title/type/audience/status/source_of_truth) adopted by 4 of 128 live documents. Two
    documented conventions in one repo is the defect this pass removes, so the README now
    documents the contract and the 4 files convert in the same commit: `type` dropped
    (kind replaces it), `title`/`audience`/`source_of_truth` kept.
B - source-of-truth leaves the kind enum, which is now 6 values, and returns as an orthogonal
    boolean. kind was carrying two independent facts. docs/requirements/native-kanban-sot.md
    is stamped `kind: spec` + `source_of_truth: true`, which is what it always was.
C - status gains `completed`. Applied to the two executed plans, on artifact evidence rather
    than on their own say-so: --purpose push|merge ships in ci-queue-wait.sh, and every section
    the README plan specifies exists in docs/README.md today.
D - kind follows content, never filename. docs/native-kanban-sot/TASKS.md is `kind: spec`
    because its body says "a build plan, not a task tracker". The name stays wrong; that is a
    rename and it is out of scope here.
E - the contract covers .md only, written into the README as a decision with vision's
    YAML.parse measurement as the reason, so the omission does not read as an oversight.
F - channel-protocol.md guide -> spec. Applied, with a correction the reviewer should see: the
    ruling cites "7 normative MUSTs" and there are ZERO uppercase RFC2119 terms in that file.
    Control: the identical grep returns 25 lines in docs/requirements/native-kanban-sot.md. The
    citation half of the finding does hold and is larger than stated. Consequence recorded in
    the worklist: the file's own banner now contradicts its header.

Verified: 128 live .md under docs/ (127 baseline + this PR's worklist), 107 stamped, 0 invalid
kinds, 17 operator-held + 3 supersede-stamp deferrals + 1 generated = 21 unstamped. 107+21=128.
Control: the verifier reports valid=False when a kind is corrupted to `nonsense`, so the
0-invalid result is a real result. prettier --check clean across docs/.
2026-08-20 19:58:17 -05:00
veronica f6fbeaf57a docs(W4): response to the W5 adversarial pass, in the diff rather than a PR comment
pr-review.sh -n 1350 -a comment fails with HTTP 401 'invalid username, password or token'.
Measured cause with a control: veronica's seat token returns 200 on /api/v1/user and the shared
broker credential gitea-mosaicstack returns 401 on the same endpoint. The wrapper logic is fine,
the credential it is handed is dead, so the PR wrappers are down for every seat on this host.
MOSAIC_GIT_IDENTITY appears nowhere in credentials.sh, which is FD-9.

Constitution gate 8 makes that operation blocked, so the corrections land in the branch instead,
where they survive the PR anyway.
2026-08-20 19:55:23 -05:00
veronica 8a55c04108 docs(W4): prettier the worklist table padding
ci/woodpecker/pr/ci Pipeline failed
CI 2589 step `format` failed on exactly one file: this one, the plan document I hand-wrote.
prettier --check reproduces locally, rc=1, same single file.

That is worth stating for the review: the other 103 stamped documents pass
`prettier --check` unchanged, so the `---\nkind:\nstatus:\n---` front matter block is
already prettier-clean. The formatting failure was in my prose, not in the contract header.

Diff is 31 lines in, 31 out, all table column padding. No content change.
2026-08-20 19:48:07 -05:00
veronica bea47543f3 docs(W4): revert the NORTH_STAR.md stamp; a projection cannot carry a hand-added header
ci/woodpecker/pr/ci Pipeline was canceled
Found by vision's adversarial pass (W5) and re-measured here. The finding was raised as
latent; it is live in this PR.

docs/fleet/NORTH_STAR.md is generated by renderNorthStarMarkdown()
(packages/mosaic/src/commands/fleet.ts:373), whose first emitted line is the H1 and which
emits no front matter at all. fleet-north-star.spec.ts:110-114 is a drift test that reads
the committed file from disk and asserts full-string equality against the renderer output:

    expect(rendered).toBe(committed);

Stamping the file changed line 1 from '# Mosaic Fleet - NORTH STAR' to '---', so the
assertion fails. vitest could not be run in this worktree (node_modules empty, 'Cannot find
package yaml'), which is a harness gap and not a test result, so this is shown by
construction: the assertion above, the renderer's first lines.push, and line 1 before
versus after.

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 lines, so the diff command does
report differences when they exist.

The header for a projection has to come from the renderer, not the file. That is a code
change and belongs in the flatten PR beside the resolveNorthStarPaths() fix.

Stamped count 104 -> 103. Untouched 23 -> 24. Worklist records it as a fourth held row.
2026-08-20 19:46:24 -05:00
veronica 37cd00e60d docs(W4): operator worklist for the 19 held rows, plus two contract conflicts
ci/woodpecker/pr/ci Pipeline failed
Records what the stamping pass applied, what it held and why, and the two
places where the contract contradicts itself or the repository:

- docs/native-kanban-sot/TASKS.md declares itself a build plan; the contract
  says TASKS.md is tracking and never a spec.
- docs/requirements/native-kanban-sot.md is hand-authored prose canon; plan
  section 5.2 calls that source-of-truth, the contract table requires
  source-of-truth to be machine-readable.
- docs/README.md already documents a competing front-matter convention on a
  colliding status key, applied to 4 files, with no code consumer.
- The contract has no defined form for a .yaml document, including
  docs/fleet/NORTH_STAR.yaml itself.
2026-08-20 19:31:25 -05:00
veronica f0d2dd9920 docs(W4): stamp kind and status front matter on 104 live documents
Applies the document contract from
docs/plans/2026-08-20_stack-docs-flatten-and-alignment.md section 3, partially:
`kind` and `status` only. `parent` is deliberately held until the flatten in
section 4 lands, so that 127 documents do not have to be re-pointed by hand
when docs/fleet/NORTH_STAR.yaml moves to docs/NORTH_STAR.yaml.

Scope, measured on origin/next at 63069149:

  127 live docs   = all *.md under docs/ minus docs/archive/ minus docs/_old_structure/
  104 stamped     here
   19 held        operator judgement (plan section 9), worklist in the same PR
    3 held        the SUPERSEDED TASKS.md stamps, which cite the moving path
    1 untouched   docs/fleet/FLEET-DOCTRINE.md, already stamped in W1

Kinds applied: 54 guide, 34 record, 9 spec, 6 tracking, 1 projection.
Every row carries a confidence and a one-line rationale in the worklist.

Two collisions with the existing state, both flagged rather than resolved:

1. docs/README.md:150-160 already documents a front-matter convention
   (title/type/audience/status/source_of_truth) with its own allowed values.
   It is applied to 4 of 127 files. Its `status` vocabulary is
   current|draft|deprecated|historical; the new contract's is active|superseded-by.
   The key collides. This commit lets the new contract win and rewrites
   `status: current` to `status: active` on those 4 files, keeping their other
   legacy keys untouched. No code reads any of them: `git grep source_of_truth`
   outside docs/ returns nothing. docs/README.md still prescribes the old
   convention and is an operator row, so it is not edited here.

2. Two of the plan's 20 operator rows are YAML files, not markdown
   (docs/fleet/examples/roster-v2.yaml, docs/openapi-tess.yaml), and the
   contract's front-matter form has no defined meaning for a .yaml document.
   That gap also applies to docs/fleet/NORTH_STAR.yaml, the source of truth
   itself. Raised in the worklist.

A third row from the plan, docs/fleet/north-star.md, no longer exists: W1
renamed it to docs/fleet/FLEET-DOCTRINE.md.

Verification: 104/104 parse with the expected kind and status in front matter;
the check was shown to reject a wrong kind before it was trusted. The diff
removes 4 lines total, all of them `status: current`.
2026-08-20 19:30:25 -05:00
fred 6306914965 fix(lease-broker): no lease held is a no-op success, not a denied transition (#1339)
ci/woodpecker/push/publish Pipeline was successful
2026-08-20 23:40:09 +00:00
fred af43a7a63e docs(fleet): tier the north star and declare the tier-0 operator surface (#1337)
ci/woodpecker/push/publish Pipeline was canceled
2026-08-20 23:05:34 +00:00
fred ca97b885b0 fix(#808): never borrow a session identity for non-tmux senders (#1335)
ci/woodpecker/push/publish Pipeline was successful
2026-08-20 21:03:50 +00:00
code-infra-01 6db0bead44 fix(#1327): sentinel-managed PATH block, default-home-only profile writes (#1330)
ci/woodpecker/push/publish Pipeline failed
Co-authored-by: code-infra-01 <[email protected]>
2026-08-20 20:20:33 +00:00
GhostfredGhost <>
c671290d77 docs: define main/next branch model, sequencing, responsibilities, and merge process (#1214) (#1215)
ci/woodpecker/push/publish Pipeline was canceled
Co-authored-by: Ghost <>
2026-08-20 20:19:39 +00:00
code-infra-01andfred 6a9b2cf6c1 fix(git-tools): pr-merge queue guard reads CI status from the BASE repo for fork PRs (B1) (#1334)
ci/woodpecker/push/publish Pipeline was canceled
Co-authored-by: code-infra-01 <[email protected]>
2026-08-20 20:18:59 +00:00
Ghostgate-merge-01Ghost <>
6bd93a621d scratchpads: fleet identity/comms/mosaic-tree continuation record (2026-08-17) (#1296)
ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: Ghost <>
2026-08-20 19:11:59 +00:00
GhostfredGhost <>
e9485c3d96 docs(l0): parameterize hard gates on the project's integration trunk (#1216, Option A) (#1217)
ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: Ghost <>
2026-08-20 18:38:18 +00:00
Ghostgate-merge-01Ghost <>
d2f0846dcc fleet: put the bootstrapped Node on PANE_PATH (#1256) (#1258)
ci/woodpecker/push/publish Pipeline was canceled
Co-authored-by: Ghost <>
2026-08-20 18:31:30 +00:00
fredandgate-merge-01 4f22a58041 guides: two measurement rules about pinned tool versions (#1316)
ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: fred <[email protected]>
2026-08-20 16:40:58 +00:00
Ghostgate-merge-01Ghost <>
9b6869fab7 fix(#1179): require security authority wiring (#1189)
ci/woodpecker/push/publish Pipeline was canceled
Co-authored-by: Ghost <>
2026-08-20 16:19:45 +00:00
ops-ci-01 20ad89c86b docs(ci): state the measured push-CI model in ci.yml's when-comment (#1326)
ci/woodpecker/push/publish Pipeline failed
Co-authored-by: ops-ci-01 <[email protected]>
2026-08-20 15:48:46 +00:00
ops-01 a55d1a1812 Merge pull request 'merge: absorb main into next — 23-commit divergence (08-05..13 base=main window)' (#1324) from merge/main-into-next into next
ci/woodpecker/push/publish Pipeline was successful
2026-08-20 01:07:05 +00:00
ops-01 9abd7e386f chore: serialize CI run — same tree as 9af456c, fresh pipeline head
ci/woodpecker/pr/ci Pipeline was successful
Per ops-ci-01 (PR #1324 comments 23413/23422) and fred's one-at-a-time
authorization: one serialized run of #1324's tree on the pinned image
(ci-base:lock-9cb7ffcd8828, carried since the cb9a0d1 absorb). This commit
changes no files — the tree is identical to 9af456c; it exists only to mint a
fresh pipeline head instead of restarting 2545.
2026-08-19 19:45:20 -05:00
ops-01 9af456c240 merge: absorb next (pin cb9a0d1 + #1325 + #1129) for the controlled pinned-image rerun
ci/woodpecker/pr/ci Pipeline failed
Per ops-ci-01's procedure (comments 23386/23394, brain D27): pipeline config
travels with the commit under test, so the controlled rerun requires the pinned
anchor in the PR head itself. This merge absorbs everything next took since the
original merge parent (1bdeed62): the ci-base pin (cb9a0d1, lock-9cb7ffcd8828),
the legacy-credential removal (#1325), and the ci-queue-wait statuses:null fix
(#1129 — the branch from the divergence analysis, now landed).

One conflict, same file as round 1: test:framework-shell union — our 54-entry
chain plus next's new test-ci-queue-wait-no-status.sh entry at its position
(55 entries, every target existence-verified). Enumeration guard green:
population 61, enumerated 46, excluded (signed) 16.
2026-08-19 18:43:19 -05:00
ops-ci-01 cb9a0d1642 ci: pin ci-base to immutable lock-9cb7ffcd8828 (Closes #1328) (#1329)
ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: ops-ci-01 <[email protected]>
2026-08-19 23:42:05 +00:00
fargo 420507da77 fix(#1323): remove legacy credential read and force-merge recipe from mosaic-gitea (#1325)
ci/woodpecker/push/publish Pipeline was successful
2026-08-19 22:51:35 +00:00
ops-01 2508f0aa99 fix(merge): round-2 CI failures — pipefail sites, verify:release mirror, format
ci/woodpecker/pr/ci Pipeline failed
Pipeline 2534 (018d96a), two test-step failures plus the format failure
root-caused by rev-code-01 (id 202):

1. pipefail-early-exit.test.mjs: main's window wrote the test but never
   wired scripts/*.test.mjs into its own CI, so its tools/install.sh pipes
   were never executed against it; next's test wiring runs them and flags
   two load-bearing pipes. Both rewritten pipefail-safe via process
   substitution (newest_matching_file ls|head, check_fleet_transport
   sed|head|tr|awk). Test now 7/7 locally.

2. verify-release.test.mjs: merge kept main's 9-command ci.yml sanitization
   but next's 4-command canonical stage. Canonical updated to mirror ci.yml
   exactly (9=9, order verified). upgrade-guard checked equal (4=4).

3. format: docs/reports/quality/1099-pipefail-sweep.md (from main f158be8)
   prettier-formatted under pinned 3.8.1.

Enumeration guard re-verified: population 60, enumerated 45, excluded 16.
2026-08-19 17:27:30 -05:00
Ghostops-02Ghost <>
d339e8fd21 fix(git-tools): ci-queue-wait — Gitea statuses:null no longer malformed; CI-less repos pushable again (#1129)
ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: Ghost <>
2026-08-19 22:08:34 +00:00
ops-01 018d96a412 fix(merge): drop fleet test-start-agent-session.sh from framework-shell chain — review B1
ci/woodpecker/pr/ci Pipeline failed
Review stack#1324 id 201 (rev-code-01): the union resolution chained main's #1073
fleet test while keeping next's signed exclusion of it (#1271 burn-down: the test
asserts missing-binary behavior but CI provides pi on PATH — fails by design).
check-test-enumeration CONTRADICTORY EXCLUSION, CI pipeline 2532 terminal failure.

Fix per verified review recommendation: remove the chain entry (55 -> 54 parts),
keeping next's exclusion and #1271 state. Guard now passes:
population 60, enumerated 45, excluded (signed) 16.
2026-08-19 16:44:31 -05:00
ops-01 b01950e92f merge: absorb main into next — 23-commit divergence (08-05..13 base=main window)
ci/woodpecker/pr/ci Pipeline failed
17 content commits + 3 merge bubbles were genuinely missing from next (~8,000 lines:
goal controller #1152, framework enforcement #1174/#1195, pr-edit wrapper #1173/#1200,
pipefail series #1100/#1105/#1106/#1107, git-tools fixes #1073/#1085/#1086/#1089,
#991, #1007, enrollment tolerance #1094). 3 commits were already in next by content
(#1060 identical, #1066/#1062 evolved twins — conflicts resolved to next's side).

Per-commit classification and evidence: mosaic-brain fleet/lanes/stack-remediation/main-next-divergence.md.
Conflict resolutions (6 files) itemized in the PR body.
2026-08-19 16:27:17 -05:00
fargo 1bdeed62eb fix(#1320): placeholder-ize private-network topology, drop raw-curl force-merge recipe (#1322)
ci/woodpecker/push/publish Pipeline was successful
2026-08-19 21:15:54 +00:00
fargo 840c2b0d96 Merge pull request 'skills: fold agent-skills into the monorepo, single install path, promote ms-unslop (plan phase D)' (#1319) from fold-agent-skills into next
ci/woodpecker/push/publish Pipeline was successful
2026-08-19 20:08:09 +00:00
fargo 95d5cb32d4 format: cover folded skills' js/ts scripts with repo prettier
ci/woodpecker/pr/ci Pipeline was successful
The repo format:check glob covers ts/js alongside md; four non-markdown
scripts inside the folded tree were flagged after the md pass. Same pinned
prettier 3.8.1, same markup-only class (verified: node --check still passes
on the js files).
2026-08-19 14:41:18 -05:00
fargo d5f3fae896 skills: promote ms-unslop from skills-local into the package
Phase D3 of plan 2026-08-19 (decision S21): skills-local is the local test
bed; promote individually as each proves out. ms-unslop is the only one of
the seven local skills with working enforcement evidence — a checker
(tools/unslop-hook/unslop-check.js), a machine-source list (lists.json), a
19-test suite, a measured corpus, and a regression fixture.

The checker itself stays fleet-local for now (it binds to a specific
harness extension surface); this promotes the skill document only.

Gates verified on the moved file: sanitization denylist clean, prettier
3.8.1 clean (6730 chars was fred's measure at assignment; 7249 as shipped
today — both pass).
2026-08-19 14:39:46 -05:00
fargo 1556982dbc skills: single install path — canonical skills ship with the framework
Phase D2 of plan 2026-08-19: single package, single install, single command.

The framework installer already treats skills/** as a shipped, manifest-owned
framework subtree, so the folded skills now install into
$MOSAIC_HOME/skills with the rest of the framework — no second repository,
no separate sync step:

- mosaic-sync-skills (bash + powershell): the fetch machinery is gone (clone,
  pull, dirty-state migration, rsync from sources/agent-skills). The script
  now only links installed skills into runtime homes. --link-only is a compat
  no-op; --no-link exits having nothing to do.
- catalog.ts: the sources/agent-skills fallback is dead and removed.
- install.sh, launch.ts, defaults/README.md, README.md, skills/README.md:
  references to the second repo rewritten to describe the shipped path.

Verified: clean install into a fresh MOSAIC_HOME produces 102 skills with no
sources/ directory; the linker then links the selected skills into the four
runtime homes with no git involvement.
2026-08-19 14:39:28 -05:00
fargo 1a822493ba format: apply repo prettier (3.8.1) to the folded skills tree
963 markdown files reformatted with the repository's pinned prettier so
pnpm format:check covers the folded tree like every other repo file.

The formatter's embedded-language pass also normalized code fences
(TS semicolons, closed HTML tags in examples, lowercased CSS hex colors,
one renumbered list that skipped an index). Alphanumeric token deltas vs
the fold commit were audited file-by-file; all are formatter-equivalent
markup normalizations plus the four sanitized skills.
2026-08-19 14:37:17 -05:00
fargo d2eeb64433 skills: sanitize operator-identity tokens from folded ops skills
Four folded skills carried operator identity tokens that the sanitization
gate (verify-sanitized.sh) forbids in the public framework package:

- kickstart: template path pointed at a private brain checkout; now uses the
  framework-shipped $MOSAIC_HOME/templates/docs/TASKS.md.template
- mosaic-deploy: dropped one estate-specific stack-name row from the example
  table
- mosaic-portainer, mosaic-woodpecker: credentials now name the framework
  credentials store (load_credentials <service>) instead of a private
  checkout path

Estate-specific values can live in a skills-local override, which the linker
applies with precedence over canonical skills.
2026-08-19 14:34:00 -05:00
fargo 5e58597dbe fold: absorb mosaicstack/agent-skills into the framework skills tree
Fold the agent-skills repository into the monorepo as the shipped canonical
skills package (plan 2026-08-19 phase D1, decision S19: single package, single
install, single command).

History is preserved by rewriting each commit's paths from skills/ to
packages/mosaic/framework/skills/ (git fast-export/import) and merging the
rewritten history with --allow-unrelated-histories, so the original commits
with their authors, dates, and messages remain reachable. Blob content is
untouched by the rewrite; tree fidelity was verified blob-sha-for-blob-sha.

This change must be merged with a real merge commit (not squash) or the
history link is destroyed.
2026-08-19 14:33:03 -05:00
jason.woltje fe4fa20309 Merge pull request 'guides: add SEAT-IDENTITY and FLEET-COMMS; harden CODE-REVIEW evidence rules' (#1313) from fred/guides-seat-identity-fleet-comms into next
ci/woodpecker/push/publish Pipeline was successful
Reviewed-on: #1313
Reviewed-by: rev-code-01 <[email protected]>
2026-08-19 15:44:57 +00:00
fred 5e93ef70bd guides: fix the cross-reference direction in SEAT-IDENTITY
ci/woodpecker/pr/ci Pipeline was successful
rev-code-01's non-blocking nit on #1313 round 2. The no-linking-step paragraph
pointed at the bridge explanation as 'described below'; it is above. Now names the
section, which survives further reordering better than a direction word does.

Text-only. Verified with the repo's PINNED prettier (3.8.1 via pnpm-lock.yaml) and
the sanitization gate, both clean.
2026-08-18 19:10:09 -05:00
fred 3884f2de4d guides: address rev-code-01's review of #1313 (B1, B2, S1, S2)
ci/woodpecker/pr/ci Pipeline was successful
All four findings reproduced before fixing. rev-code-01 was right on each.

B2 (blocker, mine). SEAT-IDENTITY provisioning step 4 said to symlink the
framework store entry to the seat slot, while the same file says those bridges
must not be recreated. The same bridge, told both ways, in one document. I
rewrote the resolution and token-location sections when the deploy made them
stale and did not carry the change into the numbered steps. Step 4 is gone and
the file now says explicitly that no provisioning step links the store to the
slot, so the omission cannot read as an oversight.

S1 (mine). The guide claimed the helper "attempts a fleet notification" on
refusal. The shipped helper does no such thing — its only reference to
notification is a comment saying an alert built on the record is best-effort, and
there is no send or wake call anywhere in the file. Now: it writes a durable
record, the record is what exists, and nobody should wait for a notification that
nothing sends. A guide that promises an alert is worse than one that promises
nothing.

S2. Estate-local content removed from files that ship to every estate: the
~/.mosaic/fleet/bin script paths (dead paths elsewhere) and the 2026-08-18 dates,
which dated a specific host's migration rather than describing behavior. The
bridge-removal passage now states the ORDERING that matters — remove bridges only
after a seat-aware helper can reach the slot, never before — which is the part
that transfers.

B1. prettier reformatted all three files. Reproduced the pipeline 2515 failure
locally before and confirmed clean after; the other three guides prettier flags
are untouched by this branch (0 changes vs origin/next) and are pre-existing.

Sanitization gate re-run and passing.

Verified for the record, since I could not verify my own work: rev-code-01
confirmed the no-fallback claim TRUE against helper content on origin/next, and
judged the evidence rules actionable on the grounds that each names an executable
replacement.
2026-08-18 18:51:15 -05:00
fred a3c50d91ca guides: genericize the operator name in SEAT-IDENTITY provisioning
ci/woodpecker/pr/ci Pipeline failed
Pipeline 2514 failed the sanitization gate on 'Jason mints the token into the
seat slot'. The denylist is jarvis|jason|woltje|... and a shipped framework file
must not carry operator identity. My mistake: I generalized the estate paths and
seat names when promoting this guide and did not check the operator name.

Now reads 'the estate operator', with the accompanying rule that an agent does
not ask another agent to mint one either.

Verified by running tools/quality/scripts/verify-sanitized.sh locally rather than
guessing at the pattern: gate passes.
2026-08-18 18:28:37 -05:00
fred 2fd102e6af guides: state the decree, drop the mechanism
ci/woodpecker/pr/ci Pipeline failed
The #1280 prohibition carried an explanation of how the tools misattribute and
why the failure is invisible from inside them. A reader who is not going to use
the tool cannot act on any of it. Same for rule 2's closing clause about what
reviews commonly miss. Both cut to the decree and the corrective action.

Rules 1 and 3-12 keep their trailing sentences: those are corrective actions or
the detail that makes the case recognizable, not justification.
2026-08-18 18:25:47 -05:00
fred efb3c3a10c guides: add SEAT-IDENTITY and FLEET-COMMS; harden CODE-REVIEW evidence rules
ci/woodpecker/pr/ci Pipeline failed
Three guides that existed only as one host's working copy, promoted to framework
templates so every estate gets them. A working copy under ~/.mosaic binds one
host; only a template here binds all of them.

SEAT-IDENTITY.md (new) documents how a seat's git credential is actually
resolved after #1311: identity from MOSAIC_GIT_IDENTITY, then
mosaic.gitIdentity, then the stdin username; host mapped to a store prefix; then
ONE of two stores chosen by whether the seat directory exists, with no
precedence and no fallback between them. A seat with a directory and an empty
slot fails closed rather than reaching the service store, and that is the point.

It also corrects how to find the helper. credential.helper commonly names an
absolute path, so `command -v git-credential-mosaic` answers a different question
than the one git asks, and the two stop agreeing the moment the PATH copy is
removed. Git also tries EVERY configured helper in order, so a fail-closed helper
in front silently hands the request to whatever is configured behind it. The
guide says to read the whole list.

FLEET-COMMS.md (new) documents agent-send.sh: the class table, the addressing
preamble, and the exit codes — including that rc=2 means the text reached the
pane as an unsubmitted draft, so retrying double-sends it. Confirm with
capture-pane instead. It also says to measure the fleet rather than trust
roster.yaml, which on a live host was simultaneously naming a socket that did not
exist, listing seats that were not running, and omitting seats that were.

CODE-REVIEW.md gains an Evidence Discipline section: a green is not a result
until you have shown it could go red, measurement and explanation are separate
sentences, verify by content on the ref that ships rather than by ancestry of a
local sha, and confidence is part of a finding. Plus four shell-measurement rules
earned on #1311, each of which produced a wrong conclusion first — `cmd | tail;
echo rc=$?` reports tail's status, a missed glob under pipefail exits 2 and kills
the run under set -e, nonzero-with-no-output is an environment question before it
is a code question, and `git -C` in a non-repo directory answers from the
enclosing repo.

The estate-specific repository exception that lived in the working copy is not
carried here. The template says an estate may document one, scoped to a named
repository and never precedent for a second.

Both new guides are added to the two routing tables that agents read.
2026-08-18 18:15:50 -05:00
jason.woltje d4d32a80b2 Merge pull request 'git credentials: fail closed, and read a seat's token from its own slot' (#1311) from fred/credential-fail-closed-seat-slots into next
ci/woodpecker/push/publish Pipeline was successful
Reviewed-on: #1311
2026-08-18 22:40:46 +00:00
fred c703cc50eb git-credential-mosaic: escape the escalation record, and stop naming a record that was never written
ci/woodpecker/pr/ci Pipeline was successful
Both defects found in review by rev-code-01 on #1311.

F3 — the JSONL record interpolated every field with a bare %s. An identity comes
from git config or the environment and a cwd is whatever directory git ran in, so
either can contain a quote or a backslash. One such refusal turned the day's spool
into unparseable JSONL, and the operator would only discover it while reading the
record that explains an outage. Fields are now JSON-escaped.

F2 — the diagnostic printed "record: <spool>/<date>.jsonl" unconditionally, but
the record is only written inside the branch where mkdir -p succeeded. When the
spool cannot be created the helper named a file that does not exist, on exactly
the hosts where the escalation was lost. It now reports the real path or says
NOT WRITTEN.

Also: prettier on README.md, which was the format-step failure on pipeline 2508.
It reflowed only the two tables this branch added.

Tests: cases 14 and 15 cover both. Verified discriminating — against the previous
helper with these same tests, case 14 fails with the unparseable record printed
and case 15 fails on both assertions; against this one both pass.

The first draft of case 14 used `ls "$spool"/*.jsonl | head -1`, which under
`set -o pipefail` exits 2 on a missed glob and killed the suite with zero output
— the same silent-nonzero failure rev-code-01 hit from a partial tools/ extraction
and the reason this file exists. Replaced with a glob loop and a comment.
2026-08-18 17:04:55 -05:00
fred 3d2b712355 git credentials: fail closed, and read a seat's token from its own slot
ci/woodpecker/pr/ci Pipeline failed
Two changes to one rule: a credential is resolved from exactly one place,
and an identity that cannot be resolved is refused rather than substituted.

FAIL CLOSED. Both readers ended in an unconditional fall-through to the
shared Gitea account whenever an identity did not resolve. Every seat in a
fleet therefore pushed, opened PRs and filed reviews under one account, and
a record made that way cannot be traced to the agent that made it
afterwards. The fallback now applies only where there is no attribution to
lose: a host with no fleet. Where seats exist, an unresolvable request emits
nothing, exits nonzero, explains itself on stderr, and — in the git helper —
appends a record naming the identity, host, reason and cwd, and no token
value, to ${MOSAIC_CREDENTIAL_SPOOL:-~/.local/state/mosaic-credential-escalations}.

A host runs a fleet when <brain>/fleet/agents exists, which is the signal
packages/mosaic/src/fleet/brain-home.ts already uses to decide a brain is
active, resolved the same way (MOSAIC_BRAIN_HOME, else ~/.mosaic). This is
what keeps the change a no-op for an operator who has not provisioned
per-slot tokens: no fleet directory, shared account, unchanged. It is also
why there is no environment variable to restore the old behavior — one would
reintroduce the substitution being removed.

STORE SELECTION. Both readers hardcoded ~/.config/mosaic/secrets/gitea-tokens,
so a seat's own secrets/ slot was invisible to the framework: a seat could
hold a valid credential and still be served the shared account. The store is
now chosen by what the identity is. An identity with a directory under
<brain>/fleet/agents/ is a seat and is read only from
<brain>/fleet/agents/<id>/secrets/; any other identity is a service identity
and is read from the framework store. There is no precedence between them
and no fallback from one to the other, so a seat with an empty slot is
refused even when a same-named token sits in the framework store. Two copies
of one credential are drift rather than redundancy, and drift surfaces as
the stale copy returning 401, which reads as a revoked token and sends
whoever debugs it somewhere else.

detect-platform.sh is in scope alongside git-credential-mosaic because they
are the two readers of these tokens. Patching only the git helper would make
"one credential, one location" true for push and fetch and false for
pr-create.sh, issue-create.sh and pr-review.sh, which is the harder failure
to notice.

TESTS. The three assertions that pinned the shared-account fall-through are
now fail-closed assertions, and a refusal is checked four independent ways:
nonzero exit, empty stdout, a stderr diagnostic naming identity and host,
and no shared token value anywhere in the output. The exit code alone would
pass against a helper that emitted the credential and then failed. Added:
seat-slot resolution, the no-cross-store-fallback case with a control
proving the framework-store file it declines to read is readable, no-identity
on a fleet host, the fleet gate firing on the default ~/.mosaic and not only
on an injected MOSAIC_BRAIN_HOME, and a cross-host leak check. Both suites
were run against the pre-change code as a control and fail there on exactly
the shared-token emission.

shellcheck is not installed on the authoring host, so the rewritten helper
is unlinted locally and CI is the first lint of it.
2026-08-18 16:19:43 -05:00
fargo 245e0c427d feat(quality-rails): typed evaluator absorbs QC-19/QC-20; verify-release wiring (RI-3-002, #1275) (#1308)
ci/woodpecker/push/publish Pipeline failed
2026-08-18 17:54:47 +00:00
jarvisandfargo ff45f7b5d0 docs(ri-050): forge fail-closed docs + TASKS status catch-up (#1275) (#1299)
ci/woodpecker/push/publish Pipeline failed
Co-authored-by: Jarvis <[email protected]>
2026-08-18 16:00:36 +00:00
jarvisandfargo 64350892e7 docs(ri-050): RI-3-001 complete quality-rails probe inventory (#1275) (#1302)
ci/woodpecker/push/publish Pipeline was canceled
Co-authored-by: jarvis <[email protected]>
2026-08-18 15:59:34 +00:00
jason.woltjeandjarvis 6e9df3c640 fix(doctor): greenfield brain lock-in note (fred's #1301 follow-up, #1288) (#1307)
ci/woodpecker/push/publish Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
2026-08-18 07:57:37 +00:00
coder2andMos 7102ccb93e docs(tools): index pull request edit wrapper (#1200)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: coder2 <[email protected]>
2026-08-13 14:50:38 +00:00
Mos afdaa6d0e6 framework: make tool discoverability, workspace placement and model tiering mechanical (#1174)
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish Pipeline was successful
2026-08-13 14:21:22 +00:00
coder3andMos 41749bbd33 fix(framework): detect installed tool drift (#1194) (#1195)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: coder3 <[email protected]>
2026-08-13 10:43:11 +00:00
Mos 120af4e193 feat(git-tools): add pull request edit wrapper (#1080) (#1173)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-08-13 06:28:01 +00:00
mos-dt-0 ec260e678f Merge pull request 'fix(git): accept http/https as one scheme class in comment URL verification (#991)' (#1022) from fix/991-comment-url-scheme-normalise into main
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-08-12 01:11:09 +00:00
Jason Woltje b590a5c3d8 fix(git): accept http/https as one scheme class in comment URL verification (#991)
ci/woodpecker/pr/ci Pipeline was successful
issue-comment.sh and pr-review.sh verify a durable write by pinning the
provider-returned object URL's origin and full path. The origin included the
SCHEME verbatim. On a Gitea whose ROOT_URL is configured `http://` while every
client reaches it over `https://`, the provider returns `http://` object URLs,
so the comparison rejects the provider's own truthful answer about a write that
LANDED. The failure is deterministic, not intermittent: every comment, every
time, on such a deployment.

The scheme was never what the check defends. The forgeries it exists to catch —
look-alike host, decoy path prefix, wrong owner/repo/kind/number — all vary the
HOST or the PATH. Both stay strict. `http` and `https` now collapse to one
scheme class; any other scheme (file:, ftp:, javascript:) stays distinguishing,
and an EXPLICIT non-default port still distinguishes, because a different port
is a different service on the same host.

Consequences of the bug, both observed:

- The wrapper reports failure on a comment that is durably on the issue/PR, and
  attributes it to #865 ("no durable comment created"). The write landed; the
  citation is wrong. Reproduced here: the harness's persisted state contains the
  record while the wrapper exits 1.
- pr-review.sh's comment path is worse. On a host where no seat can create a
  review OBJECT, comment-form is the only gate-16 review record obtainable, and
  this check refuses all of it.

Test gap this closes: every URL fixture in both harnesses was `https://`, and
every negative case varied only host or path. The one axis that fails in
production had zero coverage — the fixtures encoded the assumption that breaks.
Added, in both suites:

- scheme-downgrade (http vs https, otherwise correct) — must be ACCEPTED. Fails
  against the unmodified wrappers, passes against the fixed ones; verified in
  both directions, and the negative control's captured output is the #865
  misattribution above.
- explicit non-default port (`:8443`) — must stay REJECTED.
- non-web scheme (`ftp://`) — must stay REJECTED.

Also fixes test-issue-comment-readback.sh hermeticity (#1007), without which the
suite cannot run on any seat that has a per-agent Gitea token: detect-platform's
step-0 identity lookup reads ~/.config/mosaic/gitea-tokens/<identity>, outside
both XDG_CONFIG_HOME and MOSAIC_CREDENTIALS_FILE, so the suite resolved a
PRODUCTION credential and died at HTTP 401 before case 1. Same two-part fix
already merged for test-pr-review-gitea-comment.sh in #1006: a sandboxed HOME
plus an empty REPO-LOCAL mosaic.gitIdentity to shadow the global. Note the
env-var route does NOT work — detect-platform.sh reads `${MOSAIC_GIT_IDENTITY:-}`
and `:-` treats set-but-empty identically to unset.

The owner-side half of #991 (setting the deployment's Gitea ROOT_URL to https)
is not in scope here and is not made unnecessary by this change; this makes the
wrappers correct against a deployment that returns either scheme.
2026-08-11 19:00:31 -05:00
mos-dt-0 540ec5b6ef Merge pull request 'fix(git): #1007 suite hermeticity — pin repo-local mosaic.gitIdentity in five test suites' (#1024) from fix/1007-suite-hermeticity into main
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline failed
2026-08-11 23:19:49 +00:00
mos-dt-0 563d1ac053 Merge pull request 'fix(shell): remove wake validation pipe hazards' (#1107) from fix/1099-pipefail-wake into main
ci/woodpecker/push/publish Pipeline was canceled
ci/woodpecker/push/ci Pipeline was canceled
2026-08-11 23:19:45 +00:00
Mos 4d8ddb9a0a fix: quote SKILL.md descriptions containing colons (silent skill-load failure) (#3) 2026-08-11 22:53:47 +00:00
mos-dt-0 722163671f feat(pi): add persistent Mosaic /goal controller (#1152)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-08-10 22:54:43 +00:00
f10-coder f158be8003 fix(shell): remove wake validation pipe hazards
ci/woodpecker/pr/ci Pipeline was successful
2026-08-07 06:13:47 -05:00
f10-coderandMos b0f7d26dd9 fix(shell): remove test harness pipe hazards (#1106)
ci/woodpecker/push/publish Pipeline failed
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/manual/ci-image Pipeline failed
ci/woodpecker/manual/publish Pipeline failed
ci/woodpecker/manual/ci Pipeline was successful
Co-authored-by: f10-coder <[email protected]>
2026-08-07 11:12:17 +00:00
f10-coderandMos 3a1203b2f8 fix(shell): remove runtime early-exit pipe hazards (#1105)
ci/woodpecker/push/publish Pipeline failed
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: f10-coder <[email protected]>
2026-08-07 09:38:37 +00:00
be-coder-08 df4c591ab4 fix(fleet): make framework shell assertions SIGPIPE-safe (#1100)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-08-07 08:21:20 +00:00
be-coder-06andMos 4fa2768962 fix(fleet): propagate roster git identity (#1073)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: be-coder-06 <[email protected]>
2026-08-07 07:07:45 +00:00
Mos aa0a7b5fa2 fix(tools/git): issue-close.sh silently dropped the closing comment (#1085)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-08-07 05:59:12 +00:00
Mos 42ac19af48 test(gateway): size the enrollment clamp tolerance to CI jitter, not to a fast machine (closes #1090) (#1094)
ci/woodpecker/push/publish Pipeline failed
ci/woodpecker/push/ci Pipeline was successful
2026-08-07 05:33:38 +00:00
Mos f744f32214 feat(tools/git): explain tea's misleading user does not exist error (stale token, not a missing account) (#1086)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-08-07 05:07:40 +00:00
Mos 8ff7aac0ca fix(tools/git): detect-platform died silently outside a repo, taking every wrapper with it (#1089)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-08-07 04:26:36 +00:00
be-coder-08andMos 80a45b1e1c feat(pr-merge): preserve linked authors in squash messages (#1066)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: be-coder-08 <[email protected]>
2026-08-06 05:36:59 +00:00
be-coder-08andMos 85d2108e4e fix(ci): remove upgrade rollback signal race (#1060)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: be-coder-08 <[email protected]>
2026-08-05 22:14:15 +00:00
be-coder-08andMos 16f91157a1 test(ci): make queue guard harness deterministic (#1062)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: be-coder-08 <[email protected]>
2026-08-05 21:49:44 +00:00
Jason Woltje 2fa6bcd576 fix(git): #1007 — test-issue-comment-readback is a FIFTH affected suite (second census correction)
ci/woodpecker/pr/ci Pipeline was successful
My previous commit said four. It is five. `test-issue-comment-readback.sh` has
the same defect and is fixed the same way, and I had already looked straight at
it and filed it as an *unrelated* silent failure. Correcting that here rather
than folding it in quietly.

WHY IT WAS MISSED — the general lesson, not the excuse. `run_comment()` sends
the wrapper's stdout AND stderr to `$OUTPUT_FILE`, and the `EXIT` trap deletes
`$WORK_DIR`. The suite therefore exits 1 with ZERO bytes on stdout and stderr,
and the one line that says what went wrong —

    Error: Gitea authenticated-identity read failed with HTTP 401

— lives only inside a directory that no longer exists when anyone looks. Every
oracle I had swept the family with greps for a SYMPTOM in surviving output, so
against this suite all of them returned "nothing found", which I read as "clean"
in the first sweep and as "unrelated pre-existing failure" in the second. A
suite that discards or deletes its own evidence converts a post-hoc assay into a
non-measurement, and I wrote that sentence into the previous commit while it was
already false about a file in the same directory.

HOW IT WAS ACTUALLY FOUND. Intercept the identity read at its SOURCE instead of
grepping for its consequence: a PATH shim over `git` that logs every
`mosaic.gitIdentity` read — args, rc, and resolved value — to a file OUTSIDE any
suite's work dir, then execs the real git. Deletion-proof by construction, and
it measures the defect's cause rather than one of its symptoms. Sweeping all 16
suites with it under an ordinary invocation:

  resolves a REAL identity (`mos-dt-0`) before the fix:
    test-issue-comment-readback          1 read   rc=1 (RED on every seat)
    test-pr-review-repo-host-override    6 reads  rc=0
    test-ci-queue-wait-branch-absent     3 reads  rc=0
  the four fixed in the previous commit now read empty; the rest never read at all.

The latter two are NOT affected and are deliberately left alone: under a seat
replica (identity set, no per-slot token) neither reaches `get_gitea_token`'s
fail-loud branch, and under a canary HOME neither carries the canary credential
into any surviving artifact. They read the identity and never enter a credential
path. That residual is structural and belongs to the wrapper half of #1007 —
scoping the read with `git -C "$repo"` removes it for everyone at once.

An earlier version of that sweep reported the four fixed suites as still
resolving a real identity. That was my grep, not the suites: `value=\[..*\]` is
satisfied by `value=[] args=[…]`, because `.*` runs past the empty pair and
matches the closing bracket of the NEXT one. `value=\[[^]]` is the correct test.
Recorded because the wrong pattern failed in the direction that would have sent
me re-fixing four already-correct files.

VERIFICATION of this suite, four HOME arms, all rc=0 with zero non-empty
identity reads and the pass line on stdout: real HOME, seat replica, canary
HOME, and an empty HOME with no identity at all. Full 16-suite sweep after the
change: every suite rc=0.

CONSEQUENCE FOR THE FINDING LIST IN THE PREVIOUS COMMIT: item 2 there — the
"silently red, unrelated to #1007" suite — is withdrawn. It was #1007 all along.
Item 1 (`pr-metadata.sh:89-92`, the anonymous fallback that reports an HTTP 200
carrying valid JSON as "unknown API error") stands and is still unfixed here.

Refs #1007
2026-07-31 07:27:10 -05:00
Jason Woltje 1afe2b36dc fix(git): #1007 suite hermeticity — pin repo-local mosaic.gitIdentity in four test suites
CENSUS CORRECTION: FOUR suites, not the three my own #1007 audit named. The
fourth (test-pr-metadata-gitea.sh) was outside the candidate set that audit
worked from and was found only by sweeping the discriminator across all 16
tools/git/test-*.sh suites. Recording that as a correction to my finding, not
as part of the original claim.

THE DEFECT. get_gitea_token() (detect-platform.sh:502-599) resolves a per-agent
identity at STEP 0, from `git config --get mosaic.gitIdentity`, BEFORE both the
Mosaic credential loader (step 1) and the GITEA_TOKEN env check (step 2). On a
provisioned agent seat that value is set GLOBALLY in ~/.gitconfig and is
inherited by any freshly-`git init`ed repo, so step 0 reads a REAL per-slot
token out of $HOME and returns it without ever consulting the suite's own
MOSAIC_CREDENTIALS_FILE / GITEA_TOKEN fixtures. The suites were running against
production credentials, and the fixture credential each one carefully
constructs was inert.

THE FIX: an empty repo-local `mosaic.gitIdentity`. An empty local value shadows
the global one and reads back empty at rc=0, so step 0 declines. The env route
does NOT work: detect-platform.sh reads "${MOSAIC_GIT_IDENTITY:-}", and `:-`
treats set-but-empty identically to unset.

OPERATIVE vs CONTAINMENT — the two mechanisms are not interchangeable and the
comment in each suite says so. The pin is operative: it prevents the resolution.
The sandboxed HOME each suite now also gets is containment: it bounds a failure
the pin should already have prevented. Conflating them is how this class stays
invisible, because a decoy HOME REMOVES the trigger (~/.gitconfig is where the
global identity lives), so any suite audited under one reads clean however
vulnerable it is. To MEASURE, replicate a seat: a decoy HOME whose .gitconfig
sets mosaic.gitIdentity with no per-slot token, so step 0 reaches its fail-loud
branch. That note is in each file for the next auditor.

SECOND, INDEPENDENT DEFECT in test-pr-metadata-gitea.sh. Applying the pin alone
turned that suite RED — and a control at baseline 826a8b3 under a plain HOME
reproduced the same failure, so it is pre-existing, not introduced. Its
`GITEA_TOKEN="stub-token"` / `GITEA_URL="https://git.example.test"` pair can
never satisfy step 2, because step 2 accepts GITEA_TOKEN only when GITEA_URL
matches the remote host and this repo's origin is git.uscllc.com. The suite had
therefore only ever passed by resolving a REAL credential — step 0 on a seat, or
step 1 from the operator's own credentials.json. A MOSAIC_CREDENTIALS_FILE
fixture is added rather than leaning on the sandboxed HOME making step 1 find
nothing: a test that passes because production configuration is ABSENT fails the
moment it is present. Shipping the pin without this would have moved the failure
rather than removed it.

NO CI ARM. .woodpecker/ci.yml does not run these suites; packages/mosaic/
package.json:28 (test:framework-shell) runs an ENUMERATED list that excludes all
four. They run only by hand — i.e. exclusively on a provisioned seat, the one
environment where the defect is live. "Passes in CI, fails on a seat" does not
apply here; there is no CI observation at all.

VERIFICATION (seat replica = decoy HOME with mosaic.gitIdentity set, no per-slot
token; canary = same plus a marked non-credential at both per-slot paths; plain
= empty HOME; real = ordinary invocation):
  - bash -n clean on all four.
  - Sweep of all 16 suites at baseline 826a8b3 under the seat replica:
    test-gitea-login-resolution rc=1 REACHES-STEP0; test-issue-create-
    interactive-auth rc=1 REACHES-STEP0; test-pr-merge-gitea-empty-uid rc=1
    REACHES-STEP0; test-pr-metadata-gitea rc=1 REACHES-STEP0.
  - Same sweep after: every row rc=0 with step0 absent.
  - test-gitea-token-identity flags REACHES-STEP0 in BOTH arms and is NOT a
    defect: it runs under `env -i HOME="$FAKE_HOME"` (line 77) and its hit is
    its own deliberate assert_failloud fixtures (lines 158-171). The fail-loud
    grep matches the intended behaviour as well as the defect, so it needs the
    second discriminator; recorded here so the next sweep does not re-file it.
  - Durable-argv assay (a PATH shim that tees argv out of each suite's own mock
    curl, because test-pr-merge-gitea-empty-uid truncates its log between phases
    and its EXIT trap removes the sandbox — a post-hoc read of that suite is a
    non-measurement, and "no trace" there is not a clearance):
      test-pr-merge-gitea-empty-uid  before: canary token in argv, fixture never
        used. after: fixture token in argv, canary absent. 5 curl calls both arms.
      test-pr-metadata-gitea         before: canary in argv. after: both calls
        carry the fixture token against git.uscllc.com.
  - test-pr-metadata-gitea across seat/canary/plain HOMEs after the fix: rc=0,
    rc=0, rc=0.
  - All four under the real HOME: rc=0. No regression to ordinary invocation.

The comment block is duplicated across the four files rather than pointing at a
shared note. Deliberate, and matching the merged #1006 precedent
(test-pr-review-gitea-comment.sh:87-95): the reader who needs it is auditing one
file.

TWO FINDINGS DELIBERATELY NOT FIXED HERE (out of this branch's scope, to be
filed):
  1. pr-metadata.sh:89-92 — the anonymous curl fallback does not check ^2, so an
     HTTP 200 carrying valid JSON is reported as "unknown API error" at rc=1.
  2. test-issue-comment-readback.sh exits 1 with ZERO bytes on stdout AND
     stderr, dying at its first seed_state python3 heredoc. Reproduces at
     baseline 826a8b3 under both a seat replica and the real HOME. Silently red
     at main for everyone; unrelated to #1007.

Refs #1007
2026-07-31 07:13:13 -05:00
jason.woltje 63e77887a8 feat: add mosaic-tools skill (fleet toolkit fast path) (#1) 2026-06-19 18:30:58 +00:00
Jarvis 809ca9a1d9 feat: add mosaic ops skills (portainer, gitea, woodpecker, deploy, orchestrator)
- mosaic-portainer: stack list/status/redeploy/logs via Portainer API scripts
- mosaic-gitea: PR/issue/milestone ops for git.mosaicstack.dev
- mosaic-woodpecker: pipeline status, trigger, CI wait
- mosaic-deploy: full end-to-end deploy flow (push → CI → merge → redeploy)
- mosaic-orchestrator: mission init/run/status + worker launch rules
2026-03-22 15:32:05 +00:00
Jason Woltje 57435bb879 switch skill docs to xdg mosaic config path 2026-02-17 14:12:07 -06:00
Jason Woltje 8c51bf7575 remove legacy nested skill link artifacts 2026-02-17 14:08:02 -06:00
Jason Woltje c3d2179ad8 add delegation mode fallback to matrix rail in kickstart 2026-02-17 14:05:43 -06:00
Jason Woltje b47c4024cc standardize skills to mosaic-first paths and docs 2026-02-17 13:09:06 -06:00
Jason Woltje 74d1cdc7c1 migrate kickstart skill to mosaic-first paths 2026-02-17 13:01:49 -06:00
Jason WoltjeandClaude Opus 4.6 8cae9e0883 feat: Add lint skill (zero-tolerance) + strengthen kickstart linting mandate
New skill: lint — zero-tolerance linting enforcement for all code changes.
Detects project linter, fixes ALL violations, never disables rules.

Updated kickstart: linting now explicit standing order #3 in worker template
with "NON-NEGOTIABLE" language and zero-tolerance enforcement.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-02-16 17:13:00 -06:00
Jason WoltjeandClaude Opus 4.6 2c524b6da2 feat: Add kickstart skill — orchestrator launcher via /kickstart command
/kickstart [milestone|issue|task] replaces manual orchestrator boilerplate.
Auto-discovers project context, fetches issues from Gitea/GitHub, bootstraps
tracking files, and transforms the session into an orchestrator.

Modes: milestone, issue, task ID, resume, interactive (no args)
Built-in: quality gates, Two-Phase Completion, context handoff protocol

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-02-16 16:59:04 -06:00
Jason WoltjeandClaude Opus 4.6 b4f2019529 security: Remove vercel-deploy (data exfiltration), annotate LD_PRELOAD shims
Security audit findings:
- CRITICAL: vercel-deploy uploaded entire project to external endpoint — REMOVED
- ANNOTATED: docx/pptx/xlsx soffice.py LD_PRELOAD shims — security warnings added
- README updated to 93 skills with full security audit section and Vue/Vite ecosystem

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-02-16 16:39:04 -06:00
Jason WoltjeandClaude Opus 4.6 b1eb1fb2f9 feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories:
- anthropics/skills: 16 (docs, design, MCP, testing)
- obra/superpowers: 14 (TDD, debugging, agents, planning)
- coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth)
- better-auth/skills: 5 (auth patterns)
- vercel-labs/agent-skills: 5 (React, design, Vercel)
- antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo)
- Plus 13 individual skills from various repos

Mosaic Stack is not limited to coding — the Orchestrator and
subagents serve coding, business, design, marketing, writing,
logistics, analysis, and more.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-02-16 16:27:42 -06:00
Jason WoltjeandClaude Opus 4.6 dfeb4d9692 feat: Expand fleet to 23 skills across all domains
New skills (14):
- nestjs-best-practices: 40 priority-ranked rules (kadajett)
- fastapi: Pydantic v2, async SQLAlchemy, JWT auth (jezweb)
- architecture-patterns: Clean Architecture, Hexagonal, DDD (wshobson)
- python-performance-optimization: Profiling and optimization (wshobson)
- ai-sdk: Vercel AI SDK streaming and agent patterns (vercel)
- create-agent: Modular agent architecture with OpenRouter (openrouterteam)
- proactive-agent: WAL Protocol, compaction recovery, self-improvement (halthelobster)
- brand-guidelines: Brand identity enforcement (anthropics)
- ui-animation: Motion design with accessibility (mblode)
- marketing-ideas: 139 ideas across 14 categories (coreyhaines31)
- pricing-strategy: SaaS pricing and tier design (coreyhaines31)
- programmatic-seo: SEO at scale with playbooks (coreyhaines31)
- competitor-alternatives: Comparison page architecture (coreyhaines31)
- referral-program: Referral and affiliate programs (coreyhaines31)

README reorganized by domain: Code Quality, Frontend, Backend,
Auth, AI/Agent Building, Marketing, Design, Meta.

Mosaic Stack is not limited to coding — the Orchestrator serves
coding, business, design, marketing, writing, logistics, and analysis.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-02-16 16:22:53 -06:00
Jason WoltjeandClaude Opus 4.6 7ea13332ed feat: Add 5 curated skills for Mosaic Stack
New skills:
- next-best-practices: Next.js 15+ RSC, async patterns, self-hosting (vercel-labs)
- better-auth-best-practices: Official Better-Auth with Drizzle adapter (better-auth)
- verification-before-completion: Evidence-based completion claims (obra/superpowers)
- shadcn-ui: Component patterns with Tailwind v4 adaptation note (developer-kit)
- writing-skills: TDD methodology for skill authoring (obra/superpowers)

README reorganized by category with Mosaic Stack alignment section.
Total: 9 skills (4 existing + 5 new).

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-02-16 16:17:40 -06:00
Jason WoltjeandClaude Opus 4.6 b032d23889 docs: Add npx install commands and clone instructions
- Add npx skills add commands for single, all, and non-interactive install
- Document .git suffix requirement for Gitea-hosted repos
- Add git clone step to manual installation
- Use ln -sf for idempotent symlinks

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-02-16 16:09:05 -06:00
Jason WoltjeandClaude Opus 4.6 ecde74439c feat: Initial agent-skills repo — 4 adapted skills for Mosaic Stack
Skills included:
- pr-reviewer: Adapted for Gitea/GitHub via platform-aware scripts
  (dropped fetch_pr_data.py and add_inline_comment.py, kept generate_review_files.py)
- code-review-excellence: Methodology and checklists (React, TS, Python, etc.)
- vercel-react-best-practices: 57 rules for React/Next.js performance
- tailwind-design-system: Tailwind CSS v4 patterns, CVA, design tokens

New shell scripts added to ~/.claude/scripts/git/:
- pr-diff.sh: Get PR diff (GitHub gh / Gitea API)
- pr-metadata.sh: Get PR metadata as normalized JSON

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-02-16 16:03:39 -06:00
1669 changed files with 279933 additions and 801 deletions
+4
View File
@@ -0,0 +1,4 @@
{
"integration_trunk": "next",
"release_branch": "main"
}
+50 -6
View File
@@ -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,28 @@ 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 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
+6 -1
View File
@@ -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
+67
View File
@@ -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.
+9 -1
View File
@@ -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,
+9 -11
View File
@@ -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' };
+5
View File
@@ -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.
+5
View File
@@ -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
+5
View File
@@ -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.
+2 -2
View File
@@ -1,8 +1,8 @@
---
kind: guide
status: active
title: SSO Providers
type: runbook
audience: admin
status: current
source_of_truth: false
---
+5
View File
@@ -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.
+5
View File
@@ -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
---
+5
View File
@@ -1,3 +1,8 @@
---
kind: tracking
status: active
---
# Mission Manifest — MVP
> Top-level rollup tracking Mosaic Stack MVP execution.
+251
View File
@@ -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
View File
@@ -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
View File
@@ -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
+5
View File
@@ -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
---
+2 -2
View File
@@ -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.
+38 -1
View File
@@ -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.
+55 -3
View File
@@ -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
+5
View File
@@ -1,3 +1,8 @@
---
kind: tracking
status: active
---
# Mission Manifest — Federation v1
> Persistent document tracking full mission scope, status, and session history.
+16
View File
@@ -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 |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- |
| 01 | 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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+6 -1
View File
@@ -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.350.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.350.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
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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.
+5
View File
@@ -1,3 +1,8 @@
---
kind: guide
status: active
---
# Customize Fleet Roles
Mosaic resolves persona contracts through two layers:
+5
View File
@@ -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
+5
View File
@@ -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
+5
View File
@@ -1,3 +1,8 @@
---
kind: guide
status: active
---
# Fleet Configuration Backup and Restore Boundary
**Issue:** #758 · **Card:** FCM-M4-001
+5
View File
@@ -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.
+5
View File
@@ -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.
+5
View File
@@ -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.
+5
View File
@@ -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.
+5
View File
@@ -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
+5
View File
@@ -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,
+5
View File
@@ -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.
+5
View File
@@ -1,3 +1,8 @@
---
kind: guide
status: active
---
# Deployment Guide
> **Status: non-operative for PostgreSQL, federated, and bare-metal production.** The checked-in
+87 -2
View File
@@ -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.
+5
View File
@@ -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
+5
View File
@@ -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 P0P3
**Mission status:** CANON INDEPENDENTLY APPROVED; publication in progress under issue [#751](https://git.mosaicstack.dev/mosaicstack/stack/issues/751)
+15 -1
View File
@@ -1,10 +1,24 @@
---
kind: spec
status: active
---
# Native Kanban/SOT P0P3 — 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.
+5
View File
@@ -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.
+8 -3
View File
@@ -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.
@@ -23,10 +28,10 @@
| RI-2-001 | done | RI-N2 (Forge): remove stub-executor false success; `--simulate` typed `simulated` results that satisfy nothing; literal-`true` gates and echo-review replaced with real gates or typed waiting-for-authority | #1275 | pi-glm-5.3 | mosaicstack/stack | fix/ri-050-forge-fail-closed | RI-0-001 | 20K | Independent review APPROVED 2026-08-17 (Gitea review 172 on PR #1278, head 99b8f6ea; reviewing seat fargo — recorded under shared host principal mos-dt-0, provenance correction posted by fred; wrapper gap filed by fred). Executed at head: forge tests 116/116, lint green, typecheck green after building macp dist (minimal-install artifact, not a defect), workspace typecheck 45/45, no external type consumers of the changed interfaces. CI red = known lane-wide fleet-test failure only, carries no information about this change (fred, log-content analysis, pipelines 2456-2458). Non-blocking finding: README L141-143 + skills/mosaic-forge/SKILL.md document bare forge run/resume, which now fails closed — fast-follow docs touch. Merge queued behind #1270. UPDATE 2026-08-18: #1270 merged; CI GREEN at head 4917df1f via serialized retry (pipeline 2477) - root cause of prior reds was CI-agent contention (web SPA timeouts under concurrent pipelines), superseding the fleet-test-failure theory. |
| RI-2-002 | done | RI-N2 (MACP): gate runner fails closed on empty commands, stub executors, and unimplemented CI-provider gates unless explicit simulate; typed capability failures | #1275 | pi-glm-5.3 | mosaicstack/stack | fix/ri-050-macp-fail-closed | RI-0-001 | 15K | PR #1293 (head 2097379e): CI green (pipeline 2465), independent review APPROVED (Gitea review 173, jarvis seat, 2026-08-17) - macp 109/109 verified at head. Merge queued behind #1276/#1277/#1278. |
| RI-3-001 | done | RI-N4: complete probe inventory mapping every TS and shell quality-rail check to one canonical check with disposition (preserve/strengthen/retire, each named) | #1275 | pi-glm-5.3 | mosaicstack/stack | docs/ri-050-qr-probe-inventory | RI-0-001 | 12K | PR #1302 (head e06a47fac591): CI green (2484), independent review APPROVED (Gitea review 187, fargo seat, 2026-08-18) — 54 rows / 21 canonical checks / dispositions 43-2-9-0 verified by row-count and code spot-checks. Merged by fargo at pinned head. |
| RI-3-002 | done | RI-N4: TS evaluator absorbs effective shell probes; typed results (passed/failed/blocked/error/not-applicable) with versioned digested check definitions; shell commands become thin adapters; contract/parity/negative-control tests | #1275 | pi-glm-5.3 | mosaicstack/stack | feat/ri-050-qr-evaluator | RI-3-001 | 30K | | PR #1308 (head 68279d61): CI green (2506), independent review APPROVED (Gitea review 188, fred, seven mutations incl. vacuous-pass + stage-removal). Merged by fargo at pinned head → next @ 245e0c4. Follow-up #1309 (digest wording). |
| RI-4-001 | done | RI-N3: one PRD application service — `mission --plan` persists mission↔PRD linkage (ids/versions/selected requirements); `mosaic prdy` routes through the service or becomes a named import/export adapter; Markdown is a labeled generated view; explicit conflict-aware import | #1275 | pi-glm-5.3 | mosaicstack/stack | feat/ri-050-prd-authority | RI-0-001 | 35K | PR #1294 (head 8d258e1d): CI green (pipeline 2466), independent review APPROVED (Gitea review 174, jarvis seat, 2026-08-17) - prdy 20/20 + command specs 9/9 at head. Merge queued behind #1276/#1277/#1278. | PR #1294 (head 8d258e1d): CI green (2466), review 174. Merged 2026-08-18 overnight wave → next @ d92de53. |
| RI-3-002 | not-started | RI-N4: TS evaluator absorbs effective shell probes; typed results (passed/failed/blocked/error/not-applicable) with versioned digested check definitions; shell commands become thin adapters; contract/parity/negative-control tests | #1275 | pi-glm-5.3 | mosaicstack/stack | feat/ri-050-qr-evaluator | RI-3-001 | 30K | |
| RI-4-001 | in-progress | RI-N3: one PRD application service — `mission --plan` persists mission↔PRD linkage (ids/versions/selected requirements); `mosaic prdy` routes through the service or becomes a named import/export adapter; Markdown is a labeled generated view; explicit conflict-aware import | #1275 | pi-glm-5.3 | mosaicstack/stack | feat/ri-050-prd-authority | RI-0-001 | 35K | PR #1294 (head 8d258e1d): CI green (pipeline 2466), independent review APPROVED (Gitea review 174, jarvis seat, 2026-08-17) - prdy 20/20 + command specs 9/9 at head. Merge queued behind #1276/#1277/#1278. |
| RI-5-001 | done | RI-N5: typed freshness states (current/stale/partial/unknown/unavailable); no failed-fetch-renders-empty; stale derived verdicts → unknown; mutations disabled when stale; failure-matrix tests | #1275 | pi-glm-5.3 | mosaicstack/stack | feat/ri-050-web-stale-safety | RI-0-001 | 25K | |
| RI-V-001 | in-progress | Final verification + release evidence: all cards verified merged, negative controls demonstrated, real `next` publish run green on exact commit, evidence pack recorded | #1275 | pi-glm-5.3 | mosaicstack/stack | docs/ri-050-release-evidence | RI-1-002, RI-2-001, RI-2-002, RI-3-002, RI-4-001, RI-5-001 | 10K | | Evidence pack live on branch docs/ri-050-release-evidence — all five requirements evidenced; registry credential fixed (jarvis, #1275 c23239) and PROVEN green: pipeline 2517 (retry of 2512, identical commit) all steps green incl. build-gateway; pack PR next, then topher review + merge, close #1275. |
| RI-V-001 | not-started | Final verification + release evidence: all cards verified merged, negative controls demonstrated, real `next` publish run green on exact commit, evidence pack recorded | #1275 | pi-glm-5.3 | mosaicstack/stack | docs/ri-050-release-evidence | RI-1-002, RI-2-001, RI-2-002, RI-3-002, RI-4-001, RI-5-001 | 10K | |
## Dispatch waves (max 2 parallel workers)
-121
View File
@@ -1,121 +0,0 @@
# RI-050 Release Evidence Pack (alpha 0.0.50 release-integrity floor)
> Status: **DRAFT — proof complete, awaiting review + merge**. All five normative requirements (RI-N1..N5) merged to `next` behind the live gate. Registry credential fixed 2026-08-18 23:47Z and **proven end-to-end**: push pipeline **2517** (retry of failed 2512 at the identical commit d4d32a8, only the secret changed between runs) — all steps green including `build-gateway`. Remaining for closure: this pack PR reviewed (topher), merged to `next`, its own push pipeline green, #1275 closed. Last updated 2026-08-19 by fargo (day-takeover orchestrator).
> Card: RI-V-001. All sections marked ⏳ pending their card's merge. Normative source:
> `docs/PRD.md` § Release Integrity Workstream (#1275).
## RI-N1 — Canonical terminal verification + exact-commit publish gate
| exhibit | evidence | where |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------- |
| Gate live, fail-closed | Push pipeline **2486**: `verify` ran at exact commit, FAILED on a real latent defect (gateway cross-user-isolation cleanup on the no-DB path), and `build` / `publish-npm` / `build-gateway` were all **skipped**. First push in repo history that did not publish ungated (prior ungated publishes beside failing builds: 2439, 2462, 2482). | Woodpecker repo 47 pipeline 2486 |
| Gate-caught defect fixed | PR **#1304** (afterAll honors `dbAvailable`; both paths verified: dead-port 28 skipped + file passes; live-5433 28 passed). Review 180 (fred). | PR #1304 |
| First gated green npm publish | Push pipeline **2488** (post-#1304): `verify` GREEN → `build` GREEN → `publish-next-npm` GREEN, all publish effects behind the gate. | Woodpecker pipeline 2488 |
| Negative controls | PR **#1305**: structural DAG tests (S1 missing edge, S2 renamed effect incl. command-based npm/kaniko detection, S3 detach, S4 failure:ignore/success override, S5 when-filter, S6 HEAD-mover between verify and publish with legitimate-recheckout positive control, S7 removal) + subset-stage composition control in verify-release.test.mjs. Mutation-verified by the dispatching seat in both directions (true bypass → S1 assertion fires; non-bypass edit → correctly green). Scripts tests 20/20, CI 2490 green. | PR #1305 |
| ✅ Canonical command | `scripts/verify-release.mjs` (stage table pinned to ci.yml by checked-in test). Merged with #1277; now also invokes the RI-N4 evaluator via its `quality-rails` stage (#1308). | `scripts/verify-release.mjs` |
## RI-N2 — Forge + MACP fail-closed (typed explicit simulation)
- ✅ Forge: PR **#1278** merged (head 4917df1f; CI 2477; review 184 fred at pinned head — prior review 172 dismissed by rebase, correctly re-taken).
- ✅ MACP: PR **#1293** merged (head 2097379e; CI 2465; review 173).
- ✅ Post-merge behavior docs: PR **#1299** merged (head 8a405b14; CI 2497; review 186 fargo at pinned head — legitimate independent seat; merged 2026-08-18 with --expect-head pin, content-verified on next @ ff45f7b).
## RI-N3 — PRD authority
- ✅ PR **#1294** merged (head 8d258e1d; CI 2466; review 174).
## RI-N4 — Quality-rails evaluator
- ✅ Probe inventory: PR **#1302** merged (head e06a47fac59; CI 2484; review 187 fargo at pinned head; 54 rows / 21 canonical checks / dispositions 43-2-9-0 row-count-verified; merged 2026-08-18, content-verified on next @ 6435089).
- ✅ TS evaluator absorbs shell probes: PR **#1308** merged (head 68279d61; CI 2506; review 188 fred at pinned head — seven targeted mutations, seven detections, incl. the vacuous-pass hole M1 and stage-removal M7). Evaluator: typed fail-closed verdicts, digested versioned definitions, per-subject sets; QC-19 absorbed (verbatim-list parity oracle), QC-20 as thin adapter (verify.sh unmodified); verify-release `quality-rails` stage wired (RI-N1 consumes the evaluator). Worker-produced, independently verified by the dispatching seat (quality-rails 40/40 incl. sabotage control 6-failed/34-passed restored sha-verified; root build 25/25; typecheck 45/45).
## RI-N5 — Consequence-aware stale UI
- ✅ PR **#1300** merged (head a337d787; CI 2481; review 179). Web suite 199 → 281 tests (failure matrix + negative controls), independently re-run by the dispatching seat before merge.
## Known-open infrastructure item (not a card)
Gateway/ci-base **image** pushes fail on registry credentials: Woodpecker repo
secrets `REGISTRY_USERNAME`/`REGISTRY_PASSWORD` are rejected by the Gitea
container registry (explicit `UNAUTHORIZED` at `/v2/token`; pipeline 2494 after
PR #1306 corrected the secret references — previously masked as an ambiguous
push-permission error since at least 2439). Requires a package-scoped token
(Jason). The npm publish path is green and gated; this item tracks image pushes
only and predates the RI-050 floor.
**Update 2026-08-18 (fargo):** Jason set new secret values ~17:25Z; pipeline
**2507** (the #1308 merge push, first after the update, 18:0xZ) still fails
`build-gateway` with the identical `UNAUTHORIZED`. Read-only isolation (no
secrets read, no CI retries): the registry endpoint and auth mechanism are
HEALTHY — a valid Gitea token via basic-auth mints a JWT at `/v2/token` (200),
bad credentials 401 cleanly. Therefore the failure is isolated to the secret
VALUES, not the endpoint or pipeline. Most likely shape error (labeled guess):
the registry authenticates username + **API token with package scope**, not
username + login password; if REGISTRY_PASSWORD holds a login password rather
than a minted token value, `/v2/token` 401s exactly as observed. npm publishes
remained green in 2507; every publish step except the image push is gated and
green.
**Resolution 2026-08-18 23:47Z — FIXED on the Gitea server (jarvis, #1275
comment 23239).** Root cause was neither scope nor a missing token:
`REGISTRY_USERNAME` held `mosaic`, the **pre-rename org name**. Gitea's rename
redirect covers API/web paths but not Basic-auth username lookup, and
`mosaicstack` is an organization, which has no password — the pair could never
authenticate. Fix: `REGISTRY_USERNAME`=`woodpecker` (the existing service
account, Gitea user 41, already in `ci-publish`) and `REGISTRY_PASSWORD`= a
newly minted `write:package`-only token (`gitea admin user generate-access-token`
in the Gitea container; minting with a token is forbidden server-side). Events
`[push, tag]` preserved. Verified **without a pipeline run**:
`POST /v2/<pkg>/blobs/uploads/` opened then cancelled a session — **202** on
all four kaniko destinations (gateway, appservice, web, ci-base), anonymous
control **401**, wrong-owner control **401**. The earlier "Requires a
package-scoped token (Jason)" expectation is superseded: the defect was a
stale value from the org rename, not a scope grant Jason owed.
**Proof 2026-08-19 ~00:2xZ (fargo): pipeline 2517 green at build-gateway.**
Woodpecker retry of 2512 — identical commit d4d32a8, identical pipeline
config, only the server-side secret changed between runs — went green on
every step (clone, install, verify, build, publish-next-npm,
**build-gateway**). A/B at the same commit isolates the credential as the
variable; the stored value is byte-intact. Retry was serialized (sole run in
flight; merge-purpose CI queue guard had blocked on 2512's terminal failure
at the `next` head, which this retry also clears). The item is closed.
## Process record (audit trail)
- Merges executed under the jarvis principal (topher seat; identity provisioning
pending) via the Gitea API replicating `pr-merge.sh` semantics (head-pin +
squash + keep branch): `pr-merge.sh` hard-codes `main`-only targets and cannot
express this repo's `next` trunk — wrapper gap captured to OpenBrain
(id 9db7a95a) and to the framework queue.
- Reviews tonight: 175/178 (zane's #1298, both heads, by topher); 176/177/179/
180/181/182 (fred) — cross-review rule (producer ≠ reviewer) held on every
merge: producers were pi workers / zane; reviewers were the other seat.
- CI contention note: concurrent PR pipelines on the single CI agent can time
out the web SPA suite (measured 2470/2472 vs serialized 2475/2476/2477);
serialize retries when the queue is busy.
## Process record — 2026-08-18 day takeover (fargo)
- Takeover directive: Jason (via jarvis router + both seats' handoff documents,
relayed verbatim over comms). First-move conflict between the two handoffs
(zane: doctor PR first; topher: review-queue first) resolved on dependency
grounds per jarvis's read — topher's order won; zane's finding-2 doctor PR
(upgraded by fred's measurement) remains queued, nothing depends on it.
- Reviews 186 (#1299) + 187 (#1302): fargo, at pinned heads, as the legitimate
independent seat (topher dispatched both producers; cross-review rule held).
Both merged with --expect-head pinning via the REPO-COPY pr-merge.sh
(allows next; the installed copy still lags — zane's route, not the raw-API
break-glass), each preceded by ci-queue-wait -B next -R mosaicstack/stack.
CI green at both heads (2497, 2484). Merges content-verified on the shipping
ref (TASKS anchors at ff45f7b / 6435089).
- RI-3-002: one pi worker (zai/glm-5.3:high), independently verified by the
dispatching seat before push; PR #1308 reviewed by fred (188, seven
mutations incl. vacuous-pass and stage-removal) and merged head-pinned at
68279d61 → next @ 245e0c4.
- Registry-credential isolation measurement (above) performed read-only; no
secret values read, no retry-pushes against CI.
- One reviewer-scope disclosure (fred, review 188): fred's approval explicitly
did NOT re-run root build/typecheck/mosaic-vitest — those remain the
dispatching seat's numbers. The changed-package suites, verify-release
suite, and seven mutations were fred's own.
+5
View File
@@ -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

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