Compare commits

..
Author SHA1 Message Date
fred 62321700a3 fix(git): review follow-ups for #1357 (S1, S2, indent)
ci/woodpecker/pr/ci Pipeline was successful
S1: get_gitea_login_for_repo_override() now distinguishes "tea is not
installed" from "no tea login named X exists", mirroring the host path.
The old message diagnosed a cause that was never checked and pointed at
seat-logins.sh, which cannot run without tea. Branch 6 in
test-gitea-login-resolution.sh pins it (tea removed from PATH); reverting
the fix fails that branch.

S2: issue-list/pr-list/pr-view override-path error now points at the
lines above for the cause instead of suggesting a default tea login.

verify-release.mjs: indent of the #1356 test line fixed (cosmetic).
2026-08-21 18:12:12 -05:00
fred 341be60723 fix(git-tools): issue-view shows comment bodies and names the real tea failure (#1357)
Four defects in issue-view.sh, each pinned by the new hermetic suite
test-issue-view-comments.sh (mock tea + curl, sandboxed repo):

F1  tea exits 1 in any repo with extensions.worktreeconfig=true. The wrapper
    now names that as a git-config condition and falls back to the API.
F2  The API fallback dumped raw issue JSON, which carries only a comment
    COUNT. It now fetches /comments and renders issue + comment bodies.
F3  The tea path never passed --comments, so comment bodies were never shown
    non-interactively. It now does.
F4  Every tea failure printed the REVOKED OR STALE TOKEN note. The wrapper now
    relays tea's own error line and only hints at credentials when tea did.

The suite joins ci.yml and the verify-release canonical list (mirror test).

Closes #1357
2026-08-21 18:06:06 -05:00
fredandgate-merge-01 888a6ad29b fix(#1356): tea login resolution fails closed on a declared git identity (#1361)
ci/woodpecker/push/publish Pipeline was canceled
Co-authored-by: fred <[email protected]>
2026-08-21 23:04:31 +00:00
fred 24462f460e docs(git-wrappers): correct the stale --login fallback and document the test suite's identity split (#1354)
ci/woodpecker/push/publish Pipeline was successful
2026-08-21 14:42:36 +00:00
veronicaandfred a480ee83dc docs(W4): document contract — stamp kind and status on 104 live docs (#1350)
ci/woodpecker/push/publish Pipeline was canceled
Co-authored-by: veronica <[email protected]>
2026-08-21 14:07:36 +00:00
fred fd43ed5420 fix(git-wrappers): no --login must use the caller's own credential, not a guessed shared login (#1352)
ci/woodpecker/push/publish Pipeline was successful
2026-08-21 04:10:42 +00:00
fargoandfred 1d84bc3f3d fix(fleet): lease-broker activation, symlink-safe unit placement, named launch refusal — Wall 6 (#1292) (#1297)
ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: fargo <[email protected]>
2026-08-21 00:40:29 +00: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
jarvis f5ba042dfa test(ri-050): RI-1-002 publish-gate negative controls (#1275) (#1305)
ci/woodpecker/push/publish Pipeline failed
2026-08-18 05:57:06 +00:00
jarvis 7c7dab3898 feat(ri-050): RI-5-001 typed freshness states and stale-safe Mission Control surfaces (#1275) (#1300)
ci/woodpecker/push/publish Pipeline was canceled
2026-08-18 05:56:58 +00:00
fargoandjarvis d92de53399 feat(prd): one transitional PRD authority — RI-4-001 (#1275) (#1294)
ci/woodpecker/push/publish Pipeline was canceled
Co-authored-by: fargo <[email protected]>
2026-08-18 05:56:51 +00:00
mos-dt-0andjarvis d7e303d3c0 fix(macp): fail-closed typed gate states — RI-2-002 (#1275) (#1293)
ci/woodpecker/push/publish Pipeline was canceled
Co-authored-by: mos-dt-0 <[email protected]>
2026-08-18 05:56:38 +00:00
jarvis 726d2ad3a2 fix(ri-050): forge fails closed without providers; explicit typed simulation (#1275) (#1278)
ci/woodpecker/push/publish Pipeline was canceled
2026-08-18 05:52:45 +00:00
jason.woltjeandjarvis e4ee1acf24 feat(doctor): brain-home fleet-state check (#1298 follow-up) (#1301)
ci/woodpecker/push/publish Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
2026-08-18 05:26:01 +00:00
jarvis 5c5a25e4de fix(ci): image pushes read the registry secrets that exist (#1275) (#1306)
ci/woodpecker/push/publish Pipeline failed
Squash-merged by topher (jarvis principal) via break-glass API path (wrapper main-only gap, documented). Gates: CI 2492 green at head e19013ed, review 181 APPROVED (fred) at pinned head. Diagnostic merge: the push pipeline now exercises kaniko with the REGISTRY_* secrets - valid creds yield the first fully green gated publish; invalid yield an explicit 401.

Co-authored-by: Jarvis <[email protected]>
2026-08-18 05:02:23 +00:00
jarvis 7669321ea2 test(gateway): cross-user-isolation cleanup honors dbAvailable — unblocks gated publish verify (#1275) (#1304)
ci/woodpecker/push/publish Pipeline failed
Squash-merged by topher (jarvis principal) via break-glass API path (wrapper main-only gap, documented in #1275 log). Gates: CI 2487 green at head 81f500bd, review 180 APPROVED (fred) at pinned head. Unblocks gated publish: verify's no-DB path now skips cleanly.

Co-authored-by: Jarvis <[email protected]>
2026-08-18 04:24:48 +00:00
jarvis d8e0aec950 feat(ri-050): bind next publication to exact-commit terminal verification — RI-1-001 (#1275) (#1277)
ci/woodpecker/push/publish Pipeline failed
Squash-merged by topher (jarvis principal) via break-glass: pr-merge.sh hard-codes main-only merge targets and cannot express this repo's next trunk. Gates: CI 2476 green at head 46784c8d, review 177 APPROVED (fred) at pinned head. First gated publish: every publish step now depends on verify-release at the exact commit.
2026-08-18 03:57:43 +00:00
jarvis 49d6136b02 docs(ri-050): bootstrap release-integrity workstream for 0.0.50 (#1275) (#1276)
ci/woodpecker/push/publish Pipeline was canceled
Squash-merged by topher (jarvis principal) via break-glass: pr-merge.sh hard-codes main-only merge targets and cannot express this repo's next trunk. Gates: CI 2475 green at head 758659dd, review 176 APPROVED (fred) at pinned head.
2026-08-18 03:57:27 +00:00
jason.woltjeandjarvis a80bae950d feat(fleet): brain-home split — fleet state under ~/.mosaic, templates stay config-home (#1298)
ci/woodpecker/push/publish Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
2026-08-18 03:23:15 +00:00
jason.woltje 8199261caa Merge pull request 'fix(ci): unwire test-start-agent-session.sh, restore its signed exclusion — unblocks every PR on next' (#1270) from fix/1269-ci-chain-unblock into next
ci/woodpecker/push/publish Pipeline failed
Reviewed-on: #1270
2026-08-17 20:44:59 +00:00
fred 57a2f2b40e docs(ci): point the exclusion at tracking issue #1271, not the closed first filing
ci/woodpecker/pr/ci Pipeline was successful
The first PR for this change was filed under the retired mos-dt-0 principal
(pr-create.sh has no --login flag and find_tea_login_for_host returns the first
host match) and was closed and refiled as #1270. That left in-tree references
pointing at a closed duplicate PR rather than at the burn-down issue, which is
the wrong target for them anyway: the open design question belongs on #1271.
2026-08-16 18:03:02 -05:00
fred 93c1de51e1 fix(ci): unwire test-start-agent-session.sh, restore its signed exclusion (#1269)
ci/woodpecker/pr/ci Pipeline was canceled
The `test` step has failed on every `next` pipeline since #1017 on exactly one
assertion, and it is the same one on unrelated PRs:

    FAIL: host provides 'pi' in the system path; missing-binary cases are not
    measurable here            (framework/tools/fleet/test-start-agent-session.sh:103)

Measured 2026-08-16 across pipelines 2444 (#1256), 2438 (#1240) and 2441
(#1017-quality): exactly one FAIL line in each full log, identical, this line.
Control `zzz-not-present-zzz` -> 0 on all three.

Cause. #1241 (5c35a250) added the guard: the suite shims fake mosaic/pi/npm into
$FAKE_BIN, but the constructed PANE_PATH always ends in the real system path, so
on a host that installs those binaries the missing-binary cases cannot be
measured and a green run would mean nothing. The guard says so instead of
passing. Its own pipeline 2430 was green only because the suite was CI-excluded
at the time, so the guard had never run in CI. #1017 (c56483eb) then enumerated
it and dropped the exclusion. The CI image installs
@earendil-works/[email protected].1 on purpose, so the precondition is
unsatisfiable there. Both commits are mine.

The guard is correct and is not being softened. A check that cannot measure its
property and reports success is the failure mode this repo has been cataloguing
all week; the error was wiring the suite into an image that violates its
precondition, so the wiring is what gets reverted.

Second effect, which is the reason this cost a day rather than an hour:
test:framework-shell is one && chain and this sat at position 44 of 48, so
glpi/test-list-http-status.sh, orchestrator/test-board-roll.sh,
woodpecker/test-ci-wait-exit-matrix.sh and _scripts/test-fleet-transport-check.sh
have not run at all since the merge. The pipeline reported one failure, never
"one failure plus four unrun". All four are green when run directly on
sb-it-1-dt, so the mask hid nothing broken -- but that is a local result on one
host, not a CI-image result.

Verification, with controls:
- enumeration guard OK (population 52, enumerated 36, signed-excluded 16).
- control A, exclusion line removed while unwired -> FAIL UNENUMERATED.
- control B, exclusion line kept while rewired -> FAIL CONTRADICTORY EXCLUSION.
  The gate discriminates in both directions, so its OK is load-bearing.
- the four formerly-masked suites: rc=0 each, run directly.
- the full chain cannot be run to completion on sb-it-1-dt: it stops earlier, at
  the lease-broker Invariant R test, because this host carries the quarantined
  operator-global pi 0.84.2 against a measured 0.84.1. That is host-specific and
  out of scope here -- CI pins 0.84.1, and the single FAIL line in those three
  pipelines proves positions 1-43 passed there.

Burn-down is to control the tail of PANE_PATH inside the test, not to remove pi
from the image. Recorded in the exclusion reason and in #1269.
2026-08-16 17:58:49 -05:00
fred 476db12b92 Merge pull request 'fix(fleet): tell the operator when the fleet transport is missing (#1240)' (#1245) from fix/1240-fleet-transport-check into next
ci/woodpecker/push/publish Pipeline failed
Reviewed by scooby via git comms (terminal ACK 32d986). Merge directed by Jason 2026-08-16. Conflicts from #1229 and #1252 resolved on the branch by a pi seat; pre-merge gate verified: tools/install.sh=5d28f773, framework/install.sh=1578c33b, test:framework-shell=48 links with both #1252 and #1245 suites present.
2026-08-16 18:12:33 +00:00
fred 5198c3f198 merge next into fix/1240-fleet-transport-check
ci/woodpecker/pr/ci Pipeline failed
Resolves conflicts from #1229 (tools/install.sh node provisioning) and #1252
(package.json test:framework-shell). tools/install.sh resolved to the reviewed
composite blob 5d28f773; package.json resolved as a union so both #1252's four
suites and #1245's transport-check suite run (48 links).
2026-08-16 13:11:31 -05:00
fred 19ac0a02d7 Merge pull request 'test(#1017): wire in four CI-fit shell suites, drop their signed exclusions' (#1252) from fix/1017-wire-start-agent-session into next
ci/woodpecker/push/publish Pipeline was canceled
Reviewed by scooby via git comms (no mosaicstack principal on fomo-lin; review is the comms record, terminal ACK 32d986). Merge directed by Jason 2026-08-16. Part of the five-PR greenfield composite verified E2E on two independent bare boxes.
2026-08-16 18:06:53 +00:00
fred 6d9387c857 Merge pull request 'fix(fleet): fail the agent launcher when the pane cannot survive (#1241)' (#1244) from fix/1241-launch-failure-visible into next
ci/woodpecker/push/publish Pipeline was canceled
Reviewed by scooby via git comms (no mosaicstack principal on fomo-lin; review is the comms record, terminal ACK 32d986). Merge directed by Jason 2026-08-16. Part of the five-PR greenfield composite verified E2E on two independent bare boxes.
2026-08-16 18:06:50 +00:00
fred 14cb9c6a1e Merge pull request 'fix(fleet): let ps/install work on a roster-v2 fleet, and refuse add/remove honestly (#1237 piece A)' (#1243) from fix/1237-fleet-v2-dispatch into next
ci/woodpecker/push/publish Pipeline was canceled
Reviewed by scooby via git comms (no mosaicstack principal on fomo-lin; review is the comms record, terminal ACK 32d986). Merge directed by Jason 2026-08-16. Part of the five-PR greenfield composite verified E2E on two independent bare boxes.
2026-08-16 18:06:35 +00:00
fred b5b322f80d Merge pull request 'fix(installer): pin umask and set the 0700 modes the fleet boundary requires (#1236)' (#1242) from fix/1236-installer-dir-modes into next
ci/woodpecker/push/publish Pipeline was canceled
Reviewed by scooby via git comms (no mosaicstack principal on fomo-lin; review is the comms record, terminal ACK 32d986). Merge directed by Jason 2026-08-16. Part of the five-PR greenfield composite verified E2E on two independent bare boxes.
2026-08-16 18:06:21 +00:00
jason.woltje e4674709be Merge pull request 'fix(installer): make a greenfield install actually work — node bootstrap, PATH, wizard profile' (#1229) from fix/installer-path-and-node into next
ci/woodpecker/push/publish Pipeline was canceled
Reviewed-on: #1229
2026-08-16 18:01:07 +00:00
fred c56483eb1b test(#1017): wire in four CI-fit shell suites, drop their signed exclusions
ci/woodpecker/pr/ci Pipeline failed
check-test-enumeration.sh signed four suites as 'likely CI-fit; #1017 burndown'.
Measured all four: each passes standing alone, and each still passes with tmux
removed from PATH entirely (test-start-agent-session.sh writes its own tmux shim
into a fake bin dir, so it never needed the real binary).

Red-first: removing the four exclusion lines makes the guard report exactly four
UNENUMERATED failures. Appending the four to test:framework-shell returns it to
OK, with in-population enumerated going 32 -> 36 and signed exclusions 19 -> 15.

Refs #1017
2026-08-16 01:14:03 -05:00
fred 5c35a250de test(fleet): name what the pane-boundary case's binary check rides on (#1241)
ci/woodpecker/pr/ci Pipeline was successful
Review finding from scooby. This case does not use run_start, so
install_pane_binaries' symlinks land under a home its launcher never consults
(HOME is the trusted parent here). It resolves mosaic and pi through
MOSAIC_RUNTIME_BIN=$FAKE_BIN instead. Valid path, valid green — and a trap for
anyone who later drops that env var believing the symlinks cover it, which
would break the #1241 binary check rather than exercise it.

Comment only; no behavior change. Harness rc=0.

Refs #1241.
2026-08-16 00:21:13 -05:00
fred 10a1f82031 test(fleet): cover the pane-pid-unresolved branch this PR shipped (#1241)
ci/woodpecker/pr/ci Pipeline was canceled
Review finding from scooby: this PR added a failure branch the harness
structurally could not reach. The fake tmux answered `has-session` only for
`=_holder:0.0`, so every non-holder agent landed in the session-is-gone branch
no matter what — the `elif` (tmux still reports the session, no pane PID after
the retries) had zero coverage and no way to get any.

That is the same shape as the bug this PR exists to fix, one layer down: a code
path shipped green where the gate that should measure it cannot. Less severe,
because the branch fails closed at exit 69 rather than reporting success — but
"the harness can't reach it" is the sentence that precedes the next silent
regression, so it gets closed here rather than filed.

`MOSAIC_TEST_HELD_SESSIONS` lets a case name targets the shim should also
answer for. It answers them only AFTER `new-session`, and that detail is the
whole trick: the launcher asks `has-session` about the same name twice — once
at line 255 where a yes means "already running, exit 0", and once at 417 where
a yes means "the session survived". A shim answering yes to both short-circuits
at the first and never reaches the branch under test. It would have looked like
coverage while measuring the idempotency path.

Both failure modes were measured, not reasoned about:
- toggle absent (the old shim): `code=pane-did-not-survive` — the case lands on
  the wrong branch, which is exactly the unreachability being reported.
- toggle answering unconditionally: launcher exits 0 via the idempotency
  short-circuit — "launcher reported success over a session with no resolvable
  pane PID".
- toggle gated on new-session: `code=pane-pid-unresolved`, exit 69.

The case also asserts the diagnostic is not `pane-did-not-survive` and does not
mention the heartbeat, so the two pane faults cannot collapse into one message.

Gates: bash -n · launcher harness rc=0 · test-fleet-units.sh (real tmux) rc=0 ·
fleet specs 342 passed.

Refs #1241.
2026-08-16 00:19:25 -05:00
fred b61789fe26 fix(fleet): tell the operator when the fleet transport is missing (#1240)
ci/woodpecker/pr/ci Pipeline was successful
`mosaic fleet --help` reads "Manage the local Mosaic tmux fleet" and every
roster the CLI scaffolds sets `transport: tmux`, but neither `tools/install.sh`
nor `tools/_scripts/mosaic-doctor` contained the string "tmux" at all. A
greenfield host therefore came out of the installer able to install a fleet,
start a fleet, and run no seat, with `mosaic fleet ps` as the operator's first
and only signal.

Measured on mosaic-sbx-dev (Debian, no tmux, framework installed): `mosaic-doctor`
reported 11 warnings and not one of them named the reason no seat could launch.

The installer gets a warning, not a `require_cmd` hard failure: tmux is required
by the fleet, not by mosaic. Hosts that install this to run `mosaic claude` and
never scaffold a roster are common, and failing their install over a binary they
do not need would be wrong. The check runs in `--check` mode too — "what is the
state of this host" is the question `--check` is asked.

Both checks read the roster's own `transport:` rather than assuming tmux, so a
host declaring something else is pointed at the binary it actually needs instead
of at the wrong package.

The two implementations are deliberately parallel and each carries a comment
pointing at the other. They are separate because the installer must answer this
before the framework's own scripts are guaranteed to be on disk. One harness
drives BOTH from the shipped text — the functions are extracted from the scripts
by awk rather than copied — so the pair cannot drift silently, and the test
cannot keep passing after the shipped copy changes.

The harness is wired into `test:framework-shell`. Without that it would have
tripped the #1017 enumeration guard as UNENUMERATED, which is the guard doing
its job: a check nothing runs is not a check.

Evidence:
- red: the harness fails against origin/next ("could not extract
  fleet_declared_transport"); `grep -ci tmux` on both files at origin/next = 0.
- green on real hosts, all four branches:
  - dev (no tmux, no roster)  -> WARN naming tmux, points at `mosaic fleet init`
  - dev (no tmux, v2 roster)  -> WARN naming the roster, points at `mosaic fleet start`
  - dev installer --check     -> WARN saying start "reports success and no seat comes up"
  - canary (tmux present)     -> `[OK] Fleet transport available: tmux` under --verbose,
                                 silent by default (pass() is verbose-gated), installer silent
- harness green on node:24-alpine/busybox, the CI base image.
- `bash -n` x3, `pnpm typecheck` 45/45, fleet specs 342 passed,
  enumeration guard OK, its self-test OK, prettier clean.

Refs #1240. Upstream of #1237/#1243 and #1241/#1244: a correct fix for either of
those still leaves this host with no live seat.
2026-08-16 00:15:50 -05:00
fred 61a907a12f fix(fleet): fail the agent launcher when the pane cannot survive (#1241)
ci/woodpecker/pr/ci Pipeline was successful
`mosaic fleet start` returned 0 over three dead panes. The launcher knew,
and said the wrong thing at the wrong severity to the wrong layer.

The pane runs `mosaic yolo <runtime>` under PANE_PATH with a cleared
environment. When that binary is absent the pane dies in under a second,
tmux destroys the session, and the diagnostic goes with it. The launcher
then found no PANE_PID, printed a WARNING about the *heartbeat sidecar*,
and exited 0 — so systemd logged "Finished ... successfully" and
`fleet start` reported success. `fleet ps` was the only component telling
the truth.

Two changes, both in start-agent-session.sh:

1. Before any effect, resolve `mosaic` and the roster's runtime against
   PANE_PATH — the pane's own view of the path, not the launcher's.
   `mosaic yolo <runtime>` calls checkRuntime(runtime) and looks for a
   binary named exactly like the runtime, so this asks the same question
   the pane will ask a moment later, while an operator can still see the
   answer. Absent binary -> exit 69, code=missing-binary, no session
   created.

2. Replace the dead-pane WARNING+exit-0. An absent session one second
   after new-session is a runtime that died on startup, not a heartbeat
   problem -> exit 69, code=pane-did-not-survive, with the command to run
   by hand to see why. A present session with no pane PID after five
   attempts -> code=pane-pid-unresolved. Neither branch kills the
   session; destroying a possibly-live pane on a guess is worse than
   leaving it for inspection.

Exit 69 (EX_UNAVAILABLE) is deliberate: the 64s already in this file mean
the projection was bad, and here the data is fine and the host is not
ready. Callers separate the cases by `code=`, the same way fail_env's
codes share 64.

This propagates for free. `fleet start` calls runChecked() for the holder
and each agent, and runChecked throws on non-zero, so layers 4 and 5 stop
lying without a TypeScript change. Two adjacent defects are left for a
follow-up issue rather than widened into this diff: the per-agent loop
aborts on the first failure instead of attempting all and reporting an
aggregate, and runChecked's bare throw surfaces the launcher's message
under a Node unhandled-rejection stack trace because program.parse() is
synchronous.

Tests:

- test-start-agent-session.sh gains three cases: `mosaic` absent from the
  pane path, the runtime absent from the pane path, and a pane that does
  not survive. Each was verified individually red against the unmodified
  origin/next launcher.
- The two cases asserting a valid launch now supply a pane PID. Until now
  the suite's one success path was itself a dead pane the launcher
  reported as fine.
- The harness fakes `npm` so PANE_PATH stops depending on whatever the
  host has installed, and fails loudly if the host provides `mosaic` or
  `pi` in the system path, where the missing-binary cases would not be
  measurable at all.
- test-fleet-units.sh gains a `pi` shim in its runtime bin. The real-tmux
  harness named `pi` in its roster and never installed it; the new
  preflight caught it.

Refs #1241
2026-08-15 23:56:53 -05:00
fred 6f5b4c3dc1 fix(fleet): restore ConditionPathExists dropped by my own red-check
ci/woodpecker/pr/ci Pipeline was successful
Self-inflicted and worth recording rather than quietly amending.

To prove the new tests were red without the fix I ran
`git checkout origin/next -- <fleet.ts> <[email protected]>`. That writes
the *index*, not just the working tree. Copying my versions back afterwards
restored the working tree only, so the unit file sat staged-as-origin/next and
modified-in-tree, and the next commit (67f5014c) committed the index — silently
removing the ConditionPathExists line that 463745e3 had added.

Nothing caught it. The spec reads the file from the working tree, so it stayed
10/10 green against a HEAD that no longer had the guard. Found by reading
`git status` after the push, not by any gate.

Verified by content, not by assumption:
  origin/next  0 occurrences
  463745e3     1
  67f5014c     0   <- the regression
  this commit  1

Refs #1237
2026-08-15 23:35:54 -05:00
fred 67f5014cc0 fix(fleet): refuse v2 add/remove cleanly, and pin the Condition's effect
Two follow-ups from the canary red->green run and scooby's review.

1. The v2 refusal in `add`/`remove` was a bare `throw`, which reaches the CLI
   top level uncaught and prints the guidance under a Node stack trace. The
   message *is* the point of the refusal, so it now goes through
   `command.error()` — the same clean path the roster-config error uses.
   Caught on canary, not in review: the unit tests asserted the message text
   and passed either way.

2. The unit-template test asserted only that ConditionPathExists is present.
   Presence is not effect. Added two tests for the parts that can drift in
   code while that assertion still passes: the condition resolving to exactly
   the file the fleet writes (%h/%i rendered against a real install), and the
   launcher genuinely failing on an absent generated env (exit 64,
   `missing-file`) — which is what makes the condition load-bearing rather
   than decorative.

systemd is not available in the suite, so the effect itself was measured on
canary (2026-08-16), roster v2 generation 3:

  with the condition:    start rc=0, Result=success, ConditionResult=no,
                         journal "skipped, unmet condition check"
  condition removed by
  drop-in, nothing else: start rc=1, Result=exit-code, ExecMainStatus=64,
                         unit failed, "agent environment rejected: missing-file"

Canary red->green for the three commands, same v2 roster, side by side:

  fleet ps               0.0.50-next.2413 rc=1  ->  branch rc=0 (3 agents listed)
  fleet install          0.0.50-next.2413 rc=1  ->  branch rc=0
  fleet remove <name>    0.0.50-next.2413 rc=1  ->  branch rc=1, refusal naming
                                                    delete + apply

All three previously failed with "Fleet roster has unknown field(s):
generation." The #791 negative was measured too: the six existing
*.env.generated files were untouched by `install` (mtimes 20+ minutes older
than the run).

Gates: typecheck 0, eslint 0, prettier clean, fleet specs 382 passed, new spec
10/10 with the fix and 9/10 red against origin/next (the 10th passes there for
an unrelated reason and is annotated as such). Full suite: only
mutator-gate.acceptance.spec.ts fails, pre-existing on origin/next.

Still true and still worth saying: a correct fix here shows install rc=0 and
start rc=0 and STILL no live seat. #1240 (tmux absent) is upstream, #1241
(start reports lifecycle-complete over dead panes) and the missing agent
runtime are downstream.

Refs #1237
Reviewed-by: scooby (by git comms; cannot file a Gitea review from fomo-lin)
2026-08-15 23:34:35 -05:00
fred 463745e314 fix(#1237): let ps/install work on a roster-v2 fleet, and refuse add/remove honestly
On a roster-v2 fleet, `ps`, `install`, `install-systemd`, `add` and `remove`
all failed in the v1 parser. The consequence was that a greenfield v2 box could
never get its unit templates placed, so nothing downstream could start.

The read-only commands get a narrow version-agnostic view of the roster
(version, socket name, holder session, and per agent name/alias/runtime).
This is deliberately not a v2 -> v1 downshift. A downshifted FleetRoster would
be accepted by generateAgentEnvValues, which would make a third writer of
fleet/agents/<name>.env.generated through the v1 mapping and break the #791
single-SSOT invariant that projectRosterV2AgentGeneratedEnv is documented to
hold. The view is too small to write a roster or an env file back from, so that
misuse is unavailable rather than merely discouraged.

So on a v2 roster `install` places the tool files and the unit templates,
enables the units, and writes no generated env at all. Env belongs to `apply`
and `regen`, both already v2-native.

That change alone would have traded an init-time failure for a boot-time one.
`install` enables mosaic-agent@<name>.service (WantedBy=default.target) without
starting it, so a reboot between `install` and the first `apply` would run
ExecStart against an absent env file and fail every seat unit, further from its
cause. The unit template now carries

  ConditionPathExists=%h/.config/mosaic/fleet/agents/%i.env.generated

which skips an enabled-but-unconfigured unit cleanly and starts it on the next
start once the reconciler has written env. On v1 it is a no-op, since v1
`install` writes env itself. Found in review by scooby.

`add` and `remove` are not routed to `create` and `delete`. They are different
operations: the v1 pair edits the roster and drives systemd, the v2 pair is
documented as changing desired state without runtime actions. `add` also
collects four fields where a v2 agent requires eleven, so routing it would mean
inventing an operator's provider, alias, reasoning and tool policy. On v2 both
now fail with the real two-step sequence instead.

Tests: 8 new, 7 of which are red before this change. Includes the greenfield
case scooby asked for — `ps` on a fresh v2 install with nothing running is rc=0
and lists every agent stopped, since that is the command an operator runs to
find out why there is no seat.

Note for anyone verifying this: a correct fix here shows `install` rc=0 and
`start` rc=0 and still no live seat. #1240 (tmux absent) is upstream, #1241
(start reports lifecycle-complete over dead panes) and the missing agent
runtime are downstream. A dead pane after this change is not a regression here.

Refs #1237, #791, #1240, #1241
2026-08-15 23:24:24 -05:00
fredandClaude Opus 5 03eda02c20 fix(installer): warn on a failed credentials/ chmod instead of swallowing it
ci/woodpecker/pr/ci Pipeline was successful
scooby's review flag 1 on #1242. The other three chmods warn; this one was
`|| true`. It is the one directory holding secrets, so a chmod that fails
silently there is the failure most worth a line in the output.

Comment-and-warn only. No behaviour change on the success path.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WYgWocp36goy8hj2ui6ps1
2026-08-15 22:40:47 -05:00
fredandClaude Opus 5 3b4055017e fix(installer): pin umask and set the 0700 modes the fleet boundary requires (#1236)
ci/woodpecker/pr/ci Pipeline was canceled
A greenfield install cannot run `mosaic fleet init --write`. It fails with
`unsafe-permissions` on an unnamed `(directory)` and an unhandled Node throw,
and every mutating `mosaic fleet` command fails the same way. Measured on a
reverted-to-greenfield sandbox VM at CLI 0.0.50-next.2413: `~/.config/mosaic`,
`fleet/` and `credentials/` all land at 0775, and 1735 directories under the
framework root carry `mode & 022`.

Two independent causes, and fixing either one alone leaves it broken.

1. The installer inherited the caller's umask. Debian/Ubuntu ship 002, so every
   `mkdir -p` produced 0775. Fedora/RHEL ship 022 and produced 0755. The
   product therefore worked or did not depending on the operator's login shell,
   with nothing in the install output distinguishing the two. 022 is already
   what this script assumes it produces — `make_durable_snapshot` restores the
   ambient umask specifically so "every later sync copy and new framework dir"
   gets 0644/0755 — so pin it rather than inherit it.

2. Even at a correct 0755, three directories are rejected. The fleet code
   guards its managed paths with two masks in two languages:
   `assertPrivateManagedDirectory` (fleet-reconciler.js, `mode & 0o077`) covers
   MOSAIC_HOME and `fleet/` and runs before the roster lock is taken;
   `assert_private_directory` (tools/fleet/start-agent-session.sh, `mode & 077`)
   covers `fleet/agents` and runs before a pane is spawned. Their laxer
   siblings use `mode & 0o022` and accept 0755. The strict mask wins, so the
   installer states 0700 outright instead of hoping a umask implies it.

The `find -perm /022 -exec chmod go-w` sweep repairs a tree installed before
this change, which the umask alone cannot reach. It strips group/other WRITE
only — never read or execute — and is scoped to directories, so it corrects the
boundary violation without changing who may traverse or read anything. It is
not sufficient for `fleet/agents`: stripping write from 0755 yields 0750 and
`mode & 077` is still non-zero, which is why that path gets its own chmod.

Reported as #1236. The `fleet/agents` half was found by scooby reading
start-agent-session.sh; the umask framing is theirs too — my first report
blamed the distro rather than the umask.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WYgWocp36goy8hj2ui6ps1
2026-08-15 22:29:48 -05:00
fred 07373ede4d docs(install): record the two trust/portability assumptions in install_node
ci/woodpecker/pr/ci Pipeline was successful
Comment-only, no behaviour change. Both raised by scooby in the #1229 review
as non-blocking findings worth writing down rather than fixing here.

F-A: the SHASUMS256.txt check gives integrity, not authenticity. TLS to
$NODE_DIST_BASE is the whole trust root, and MOSAIC_NODE_DIST_BASE widens it
to any mirror with no signature backstop. GPG-verifying SHASUMS256.txt.sig is
filed as its own follow-up so it gets its own review.

F-C: the uname map pulls the glibc build, so musl hosts fail — visibly, via
node_is_suitable, not silently.
2026-08-15 21:50:14 -05:00
fredandClaude Opus 5 fb5bb98a32 Revert "fix(installer): re-link runtime assets after the CLI stage"
ci/woodpecker/pr/ci Pipeline was canceled
This reverts 47e90767. I was wrong: the fix is correct about the cause and
makes the outcome worse.

The acceptance run passed everything I set out to check — greenfield canary
1125, --next --yes, no TTY, rc=0, node v22.23.2 + CLI 0.0.50-next.2413 from a
fresh login shell, and both enforcement hooks wired in ~/.claude/settings.json
where before they were stripped. Then `mosaic doctor` on that same host:

  [ERROR] Lease-enforcement hooks (mutator-gate.py, receipt-observer-client.py)
  are wired in ~/.claude/settings.json, but broker not healthy
  (checkBrokerSupervisorHealth() reports unhealthy). Every gated tool call will
  fail closed and BRICK this agent (see #869).

So the change takes a greenfield host from 'enforcement quietly off, agent
works' to 'enforcement wired, broker absent, agent bricks on the first gated
tool call'. The pre-existing behaviour reaches the safe state for the wrong
reason; this reaches the unsafe state for the right one. Safe-for-the-wrong-
reason still wins.

The real defect is underneath both, and it is not an ordering bug:

  mosaic __link-claude-settings ...   -> rc=0  (leaseEnforcementActivatable:
                                                 activatable, wire the hooks)
  mosaic doctor                       -> ERROR (checkBrokerSupervisorHealth:
                                                 unhealthy, hooks will brick)

Two capability checks, same host, opposite verdicts. And after a complete
install there is no broker supervisor to be healthy: no systemd --user unit
matching lease/broker, nothing under ~/.mosaic but the bootstrapped node, and
no lease or broker script in ~/.config/mosaic/tools/_scripts/. Lease
enforcement cannot be activated on a greenfield host at all, so
leaseEnforcementActivatable() returning true is the thing that is wrong.

Filing that separately. PR #1229 goes back to exactly the four commits scooby
reviewed.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-15 21:42:20 -05:00
fredandClaude Opus 5 47e90767b7 fix(installer): re-link runtime assets after the CLI stage, so greenfield keeps its enforcement hooks
ci/woodpecker/pr/ci Pipeline was canceled
The framework's install.sh ends by running mosaic-link-runtime-assets, which
asks the `mosaic` CLI whether lease enforcement can be activated before
deciding whether to wire the #828 hooks into settings.json. Part 1 (framework)
runs before Part 2 (npm CLI), so on a first install there is no CLI to ask. The
script takes its fail-safe branch, prints a four-line ERROR, and writes
settings.json with mutator-gate.py and receipt-observer-client.py stripped out.

Measured on canary 1125, rolled back to greenfield, `--next --yes`, no TTY:

  framework template ~/.config/mosaic/runtime/claude/settings.json
    mutator-gate.py            1 occurrence
    receipt-observer-client.py 1 occurrence
  installed ~/.claude/settings.json after a clean rc=0 install
    mutator-gate.py            wired: False
    receipt-observer-client.py wired: False

So enforcement ends up off because of the order the two halves install in, not
because of anything about the host. Falsified by running the same script by
hand once the CLI existed: rc=0, both hooks wired: True. The guard's real
verdict on that host was 'activatable' the whole time.

This adds one more pass after Part 2. The script is idempotent (unchanged files
are skipped), so on an upgrade — CLI already present, first pass already
correct — it is a no-op. It deliberately does not pass
--allow-inactive-enforcement: Part 1 does not either, and a repair pass must
not be more permissive than the pass it corrects.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-15 21:38:11 -05:00
fred 00bc602f93 fix(installer): persist the bootstrapped Node on PATH, and stop duplicating PATH lines
ci/woodpecker/pr/ci Pipeline was successful
Two defects found by the second unattended greenfield run on canary (VMID 1125,
rolled back to its greenfield snapshot first).

1. ensure_node() exported the Mosaic-managed Node for the installer process and
   nothing wrote it down. The install finished rc=0, put $PREFIX/bin in
   ~/.profile, and the next login shell found `mosaic` and then died on

       env: 'node': No such file or directory

   The CLI is a Node script, so a CLI on PATH without its runtime is a
   successful install that produces a broken command. persist_node_on_path()
   now writes the runtime's bin dir to the same profile, from both the
   fresh-install and the already-installed-but-not-on-PATH branches.

2. The 'is it already in a shell rc file' guard was a single
   `grep -qslF "$dir" "${rc_files[@]}"` over four paths, most of which do
   not exist on a clean host. Handing grep a missing file makes the exit status
   implementation-defined: GNU grep 3.11 returns 0 when -q already matched an
   earlier file, ugrep 7.5 returns 2 for the missing one regardless. On the 2
   path the caller reads 'not present yet' and appends another PATH line, so
   every re-install grew the profile. Measured: 3 runs produced 3 duplicate
   entries; with the fix, 1.

   path_entry_exists() now tests each file for existence and greps it on its
   own, so the result does not depend on the grep implementation.

The profile-writing body is factored into persist_on_path(), shared by the CLI
prefix and the Node runtime, since both now need identical treatment.

Verified in a scratch $HOME: fresh write, idempotent across three runs, zsh
routes to .zshenv, an unwritable profile warns and survives set -e, and an
already-on-PATH prefix is a no-op that creates no file. Falsified by restoring
the multi-file grep: duplicates return.
2026-08-15 16:07:56 -05:00
fred d0c223bdf9 fix(wizard): write PATH to .profile/.zshenv, never .bashrc
ci/woodpecker/pr/ci Pipeline was successful
getShellProfilePath() preferred ~/.bashrc when it existed, and ~/.zshrc for
zsh. setupPath() in stages/finalize.ts appends the PATH export to whatever
it returns. Debian's default ~/.bashrc opens with

    case $- in *i*) ;; *) return;; esac

so a line appended to the bottom of it never runs for 'bash -lc', for
systemd units, for 'ssh host cmd', or for any agent seat — precisely the
consumers that need the CLI. An install could print its summary and exit 0
while leaving 'mosaic: command not found'. .zshrc has the same problem:
zsh only reads it for interactive shells.

Now ~/.profile, which login shells read and which Debian's copy sources
.bashrc from for interactive shells, so one line covers both. For zsh the
always-sourced file is .zshenv. fish and PowerShell are unchanged.

__tests__/platform/detect.test.ts pins it, including a case asserting that
no shell resolves to an interactive-only rc file. Falsified by inverting
the fix: 5 failed / 1 passed; restored 6/6. Full package suite unchanged at
17 files / 4 tests failing, matching clean origin/next.
2026-08-15 15:54:10 -05:00
fred cc0d24d5c4 fix(installer): bootstrap Node.js on a greenfield host
tools/install.sh required node and npm and installed neither. Measured on a
snapshot-reverted Debian 13 image with no node, npm or git: the run stopped
at `require_cmd node` with "Required command not found: node", exit 1,
nothing installed, and no indication of how to proceed.

Adds ensure_node() to preflight. It fetches an official Node.js release into
$HOME/.mosaic/node, verifies it against that release's SHASUMS256.txt, and
refuses rather than degrades when the entry is missing or the checksum does
not match. sha256sum on Linux, shasum on macOS. .tar.gz over the smaller
.tar.xz because gzip is universally present and xz is not — a minimal image
is the case this exists to handle.

No-op when a suitable node is already on PATH, so it never fights an
operator's nvm/fnm/distro node. MOSAIC_SKIP_NODE_BOOTSTRAP=1 declines the
download and fails with instructions instead.

Inlined rather than factored into a sibling file because this script is
fetched standalone by curl and has nothing to source.
2026-08-15 15:54:09 -05:00
fred 40fecd4d38 fix(installer): put $PREFIX/bin on PATH instead of warning about it
The three duplicated PATH blocks in tools/install.sh only warned, so an
unattended install finished with rc=0 and left `mosaic: command not found`
— there was no operator to read the advice and act on it. Measured on a
greenfield Debian 13 sandbox: `--next --yes` installed
@mosaicstack/[email protected] successfully and the CLI was still
unreachable.

Replaces all three copies with one ensure_prefix_on_path helper that
appends the export to ~/.profile (~/.zshenv under zsh) and is a no-op when
the prefix is already on PATH or already in a shell profile.

Not ~/.bashrc: Debian's default .bashrc returns early for non-interactive
shells, so a line appended there is unreachable to `bash -lc`, systemd
units and agent seats — the consumers that need the CLI.
2026-08-15 15:47:30 -05:00
mos-dt-0 7a6fb024b4 docs: establish canonical documentation architecture (#1210)
ci/woodpecker/push/publish Pipeline failed
2026-08-13 17:56:13 +00:00
mos-dt-0 f82307c4dc fix(lease): raise capability-probe timeout to 10s on both halves (#869) (#1207)
ci/woodpecker/push/publish Pipeline failed
2026-08-13 17:28:00 +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
shaggyandmos-dt-0 216cd72226 refactor(chat): route browser chat through one runtime (P3 Slice-Zero Task 5) (#1172)
ci/woodpecker/push/publish Pipeline failed
Co-authored-by: shaggy <[email protected]>
2026-08-12 20:11:12 +00:00
mos-dt-0 6a8ce66702 Merge pull request 'feat(lease): verified lease-remediation stack (rebased onto next) — promotion trigger + promote CLI + carve-out + TTL' (#1109) from feat/lease-promotion-and-harness-isolation into next
ci/woodpecker/push/publish Pipeline failed
2026-08-12 03:07:32 +00:00
jason.woltjeandMos 9cd9409089 P3 Slice Zero, Task 4 — replace Web free-text selection with the structured harness catalog (#1170)
ci/woodpecker/push/publish Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
2026-08-12 02:50:18 +00:00
Jason WoltjeandClaude Fable 5 13c70a7a10 test(mutator-gate): keep launch ledger out of the shipped framework tree
ci/woodpecker/pr/ci Pipeline was successful
runRuntimeLaunchEntry set MOSAIC_HOME to the shipped framework root, so
launch-runtime.py appended its launch ledger to
framework/fleet/run/sessions/events.ndjson — polluting the tree that
manifest.spec.ts walks and failing its completeness check in CI.

Point MOSAIC_HOME at the per-entry temp root instead. Nothing in the
launch chain resolves tools via MOSAIC_HOME (entry scripts resolve via
SCRIPT_DIR); the ledger is its only consumer here.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Dtdjx4Gxude9fwyLezCrhh
2026-08-11 20:51:03 -05:00
dd6357e670 test(skill): install linker asserts harness-home link topology
The two install-linker-compatibility tests still asserted the pre-isolation
behavior (mosaic skill links planted in $HOME/.claude/skills). This branch
deliberately moved the link farm into the mosaic-owned harness homes
($MOSAIC_HOME/.claude/skills) and demoted the base-install dirs to
cleanup-only legacy targets, so the tests now assert the new topology:
the skill links appear under the harness home, foreign links in the legacy
dir are preserved, and no new mosaic link is planted in the base install.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Dtdjx4Gxude9fwyLezCrhh
2026-08-11 20:51:03 -05:00
Jason Woltje 709a23d08c feat(mosaic): mechanically authorize lease promotion 2026-08-11 20:51:03 -05:00
Jason WoltjeandClaude Opus 4.8 239a2a93f1 test(lease): #1124 regression uses node pane command (real field topology per scooby)
The mosaic wrapper makes pane_current_command=node (RUNTIME_ACCEPTABLE_COMMANDS.claude=['claude','node']); the walk matters precisely in that no-shell-wrapper case. Match reality.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_013SAYFkRhQfhguY7AHfiUC8
2026-08-11 20:51:03 -05:00
Jason WoltjeandClaude Opus 4.8 ea1f058022 fix(lease): resolve lease session id from the claude child, not the tmux pane pid (#1124)
The launcher runs the runtime as a spawnSync CHILD of node(mosaic) (deliberate,
per launch.ts:99 — parent survives to propagate signals), so
MOSAIC_LEASE_SESSION_ID lives on the claude child, not the pane's root pid. The
transport read only pane.pid's /proc/environ and returned RESOLVE_FAILED for
every real 'mosaic claude' seat. Now BFS the pane's process subtree (bounded,
injectable children-reader) and read the first descendant that carries a valid
lease id; fail-closed if none. Unit tests now exercise the real walk (pane=node
without lease -> child=claude with lease) rather than mocking the resolution.

Found by scooby greenfield E2E on fomo-lin with proc-level evidence.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_013SAYFkRhQfhguY7AHfiUC8
2026-08-11 20:51:03 -05:00
Jason Woltje 1fde450ff1 test(lease): align mutator carve-out acceptance 2026-08-11 20:51:03 -05:00
Jason Woltje c136baa052 fix(mosaic): bound promotion transport delivery 2026-08-11 20:51:03 -05:00
Jason Woltje 4f7f6b3281 feat(mosaic): add correlated lease promotion CLI 2026-08-11 20:51:03 -05:00
Jason Woltje 77edb0dea2 feat(lease): add single-turn Claude promotion trigger 2026-08-11 20:51:03 -05:00
Jason WoltjeandClaude Fable 5 3676180ae8 fix(lease): raise lease TTL 300s -> 3600s
MAX_LEASE_TTL_SECONDS (daemon cap+default) and DEFAULT_TTL_SECONDS
(lease_promote client) both move to 3600. The 5-minute TTL made
gated-by-default sessions unusable (re-promotion mid-task); 1 hour
matches a working session. Full test:framework-shell RC=0.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_013SAYFkRhQfhguY7AHfiUC8
2026-08-11 20:51:03 -05:00
Jason Woltje 0e938b66ed fix(lease): ignore benign observer idle replies 2026-08-11 20:51:03 -05:00
Jason Woltje f0fef26eb7 fix(lease): constrain read-only tool carve-outs 2026-08-11 20:51:03 -05:00
Jason Woltje c9bccd4aae test(lease): assert pi carve-out capability 2026-08-11 20:51:03 -05:00
Jason Woltje 8ef2e5b91d test(lease): distinguish pi probe timeouts 2026-08-11 20:51:03 -05:00
Jason Woltje 4cab6c09fe test(lease): enforce read-only tool invariant 2026-08-11 20:51:03 -05:00
Jason Woltje 239fc6d03c docs: measure pi tool registry 2026-08-11 20:51:03 -05:00
Jason WoltjeandClaude Fable 5 d085182dc1 test: close W-0R review findings — assert the omission notice, skip chmod simulations under root
The independent W-0R review of 3592b92e passed but left two PLAUSIBLE
findings: the stderr notice for a legitimately-omitted operator source was
claimed and never asserted (a silent omission is the original defect in
miniature), and the chmod 0o000 unreadable simulations fail spuriously when
euid==0 (CAP_DAC_OVERRIDE). Falsifier for the new assertion: deleting the
notice block turns the suite red (failures=3); restoring returns green.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EHYXhcCQsL3J1Lnm7EraGq
2026-08-11 20:51:03 -05:00
Jason WoltjeandClaude Opus 5 e949fa3767 fix(lease): refuse an incomplete law binding instead of silently shrinking it
build_construction skipped any normative source it could not read
(`except OSError: continue`) and promoted whatever remained. That is not a
degraded binding, it is a forged smaller one: the broker recomputes h_source /
h_payload from the fragments it is SENT (daemon.py:602-616), so an omitted
fragment is internally consistent and PAYLOAD_BINDING_MISMATCH cannot fire. A
partial law promotes exactly like a complete one and nothing downstream can tell
the difference.

Measured before this change, against a seeded home: with only USER.md readable,
the client produced a one-fragment construction with promotion=True. Removing
CONSTITUTION.md, STANDARDS.md or the runtime contract likewise promoted.

The classification mirrors the framework's own file ownership rather than
inventing one:

  * CONSTITUTION.md / AGENTS.md / STANDARDS.md are framework-owned and
    reconciled every upgrade (install.sh FRAMEWORK_OWNED,
    config/file-adapter.ts FRAMEWORK_OWNED_FILES), as is the per-runtime
    RUNTIME.md. Absent => IncompleteBinding. A deployment missing one is broken,
    not minimal.
  * SOUL.md / USER.md are deliberately not seeded by install.sh ("generated by
    `mosaic init`") and TOOLS.md is seeded on first install only, so their
    absence is legitimate. It is reported on stderr, never silent.

Unreadable is handled separately from absent for EVERY source, optional ones
included: a file that will not open is not a file that was never configured, and
collapsing the two is what let a permission change quietly shrink the law.

Also corrects this module's own docstring, which asserted that a VERIFIED lease
means "this agent is running THIS law". It does not. Both sides of the broker's
comparison originate in this client, so it detects corruption in transit and
nothing else. That overstatement is where the belief spread from; the stronger
claim needs the broker re-reading on-disk sources against a manifest the agent
cannot rewrite.

Test: promotion_binding_unittest.py, enumerated in test:framework-shell (the
enumeration guard's population is *test*.sh and does not cover Python, so an
unenumerated test here would simply never run). Falsifier executed: defeating the
guard while leaving the module API intact turns the suite red (12 failures);
restoring it returns green.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EHYXhcCQsL3J1Lnm7EraGq
2026-08-11 20:51:03 -05:00
Jason Woltje f1761c91be Revert "feat(pi): wire lazy lease promotion into the mutator gate"
This reverts 939f2e04. Keeping the revert rather than dropping the commit,
because the failed attempt is the most useful record on this branch.

The wiring worked mechanically — verified with a live model on sb-it-1-dt: the
receipt was emitted verbatim as a whole message, and the broker token was minted
AND consumed, so observe_receipt and promote_lease both succeeded and the lease
reached VERIFIED.

It failed as a DESIGN, for reasons that are properties of the protocol rather
than of this wiring:

  * It puts control-plane traffic in the user-facing conversation channel. An
    operator asking "what model are you?" received a receipt string instead of an
    answer — the model tried a tool, was blocked, complied with the receipt
    instruction, and in one-shot mode that text turn BECAME the reply. Observed
    twice, non-deterministically.
  * The lease TTL is hard-capped at 300s (MAX_LEASE_TTL_SECONDS; ttl_seconds >
    cap raises INVALID_LEASE_TTL). Measured: allowed at T+0, LEASE_EXPIRED at
    T+310. So the visible cost recurs every five minutes of mutator activity.
  * Model compliance is not guaranteed — one run retried the command instead of
    emitting the receipt.

Any model emission is user-visible, so this is not fixable by better wiring; it
needs a design answer about how promotion is triggered and paid for. That is
under adversarial review (docs/scratchpads/lease-remediation/07-liveness-design-brief.md
in the operator's repo). Promotion triggering will return on its own branch once
that lands.

What remains here is independently sound and unblocked: harness-home isolation,
the immutable launch record, the skills relocation, the promotion client itself
(steps 1/4/5), and the #1087 prefix guard.
2026-08-11 20:51:03 -05:00
Jason Woltje 8109f72cf7 fix(sync-skills): guard the pre-existing prune against an empty prefix (#1087)
prune_stale_links_in_target compared "$resolved" == "$canonical_real/"* while
length-checking only $resolved. If $canonical_real were ever empty the pattern
collapses to == "/"* and matches every absolute path.

The failure is precisely inverted, which is what makes it worth fixing rather
than noting: is_mosaic_skill_name already `continue`s for names that ARE current
mosaic skills, so an empty prefix would delete exactly the FOREIGN symlinks in
every target directory and preserve the mosaic ones. On this host that is 4 base
installs, including codex's own .system entry.

Reported by mos-claude as #1087 after I introduced the same guard in the new
legacy-cleanup path in the previous commit and walked past this instance thirty
lines away. Same defect class, same file, one function apart.

$canonical_real is populated by readlink -f after a mkdir -p, so an empty value
requires readlink to fail — unlikely, but the consequence is deleting operator
symlinks across every harness, which is not a risk worth carrying for one test.
2026-08-11 20:51:03 -05:00
Jason Woltje a0be592d84 feat(pi): wire lazy lease promotion into the mutator gate
Completes the promotion path: the client landed in the previous commit, but
nothing drove step 2 — the model emitting the receipt. This wires it.

LAZY, not at session start. Promotion costs an entire model turn, because the
receipt must be the whole message (hmac.compare_digest, "not a transcript
substring"). Minting at session start would collide with the Constitution's
first-response mode declaration — the two cannot share a message, so requiring
both would be unsatisfiable. Deferring to the first DENIED MUTATOR means the
mode declaration happens first and the receipt gets its own later turn, so no
governance change is needed. A read-only session never pays for promotion at all.

Mechanism: on a MUTATOR_UNVERIFIED denial the tool_call hook mints a challenge
and returns the receipt in the block `reason`, which pi feeds back to the model
as the tool result — the existing injection path already used by
lease-lifecycle.ts. The model emits the receipt as its next message, message_end
ships it to the observer, and the extension then calls observe_receipt +
promote_lease.

Only MUTATOR_UNVERIFIED triggers minting. Other denials (GATE_UNAVAILABLE,
STALE_GENERATION, LEASE_EXPIRED, ANCESTRY_MISMATCH) describe conditions a
receipt cannot fix, and begin_verification revokes before it mints, so minting
there would thrash the broker.

Completion is gated on an EXACT text match against the minted receipt. This is
load-bearing, not defensive: message_end also fires for the message that
CONTAINED the blocked tool call — one turn BEFORE the model answers. An earlier
version completed there, so observe_receipt compared against the wrong text,
failed, and burned the challenge before the model ever emitted it. Matching the
text mirrors the broker's own compare_digest semantics and waits for the right
turn. Confirmed by instrumenting message_end and watching it fire with
pending=yes one message too early.

It never posts the receipt itself. receipt-observer-client.py accepts any
string, so self-posting would satisfy the broker while proving nothing — the
whole point is that a live model echoes a challenge it was given.

Bounded by MAX_PROMOTION_ATTEMPTS: model compliance is not guaranteed (observed
a run where the model retried the command instead of emitting the receipt), so
a non-complying model degrades to today's behaviour — denied mutators — rather
than looping.

Verified with a live model on sb-it-1-dt: receipt emitted verbatim as a whole
message, and the broker token was minted AND consumed, i.e. observe_receipt and
promote_lease both succeeded and the lease reached VERIFIED.

Known limitation: under `pi -p`, the receipt is a text-only turn, which ends the
one-shot loop — so promotion completes but the blocked tool is not retried in
that same invocation. Interactive and durable fleet sessions continue and retry
normally.
2026-08-11 20:51:03 -05:00
Jason Woltje f4a24b693e feat(lease-broker): add the missing promotion client
The enforcement half of the lease broker ships and denies; the promotion half
has no production caller anywhere in the package. Verified across 0.0.48, 0.0.49
and 0.0.50-next.2207: begin_verification / observe_receipt / promote_lease are
invoked only by broker-test-client.ts, the acceptance spec, unit tests, and two
probes under docs/.

Consequence: no lease on any host can reach VERIFIED, so mutator-gate denies
every mutator with MUTATOR_UNVERIFIED via a gate that nothing shipped can
satisfy. Runtimes that enforce the gate in-process (pi, via mosaic-extension's
tool_call hook) are bricked for mutators; runtimes whose gate is wired through a
settings hook escape only when that hook is absent — i.e. by being ungated.

This adds the client. It implements protocol steps 1, 4 and 5:

  1. begin_verification  -> mint a challenge, return the exact receipt text
  2. the MODEL emits that text verbatim as its entire latest message
  3. the runtime adapter ships that message to the observer socket
  4. observe_receipt      -> PENDING_PROMOTION
  5. promote_lease        -> VERIFIED

Step 2 is deliberately NOT implemented here, and that is the point.
is_verbatim_receipt uses hmac.compare_digest against the exact minted string —
explicitly "not a transcript substring" — which makes promotion a LIVENESS
PROOF: it requires a live model that received the challenge in its context and
echoed it exactly.

receipt-observer-client.py will post ANY string as the latest assistant message.
A promotion client that posted its own receipt would satisfy the broker while
proving nothing — a gate-disabler indistinguishable from a working fix unless
someone specifically looks. Emitting the receipt therefore belongs to the runtime
adapter, where a real model turn happens. A local diagnostic that posts its own
receipt exists in the operator's repo and is deliberately NOT shipped here.

The construction binds the exact normative source bytes, so a VERIFIED lease
means "this agent is running THIS law", not merely "this session id is known".
h_source/h_payload are derived by importing the framework's own
normative_fragments.build_payload rather than reimplementing it: the broker
derives them the same way and any divergence yields PAYLOAD_BINDING_MISMATCH.
There must be exactly one implementation.

session_identity() prefers the generation FILE over the env var, matching
lease_generation.py. Sending a generation higher than the broker's would revoke
the session's own authority (daemon.py:342-344), so it never guesses.

Verified end-to-end on sb-it-1-dt under a real lease-gated anchor: a mutator
denied rc=2 MUTATOR_UNVERIFIED, then begin -> observe -> promote -> VERIFIED,
then the same mutator allowed rc=0. Negative controls pass: a fresh session is
still denied, and an unrelated session still reads UNVERIFIED — promotion is
per-session and does not leak.

Still open: adapter wiring for step 2. Lazy promotion on first mutator attempt
avoids colliding with the Constitution's first-response mode declaration, since
compare_digest requires the receipt to be the WHOLE message.
2026-08-11 20:51:03 -05:00
Jason Woltje e4dffb7c18 feat(launch): isolate harness homes and record immutable launch provenance
Mosaic wrote into the operator's harness base installs — ~/.claude,
~/.pi/agent, ~/.codex, ~/.config/opencode — for settings, instructions, and a
102-symlink skill farm per harness. Any experiment with hooks or gating
therefore mutated the operator's own tooling, and a broken framework change
could take out the very harness needed to repair it.

Harness home isolation
----------------------
Each runtime now reads config from a dedicated mosaic-owned home via the
harness's own config-dir variable:

  claude    CLAUDE_CONFIG_DIR     ~/.config/mosaic/.claude
  pi        PI_CODING_AGENT_DIR   ~/.config/mosaic/.pi     (replaces ~/.pi/agent)
  codex     CODEX_HOME            ~/.config/mosaic/.codex
  opencode  XDG_CONFIG_HOME       ~/.config/mosaic/.opencode

These paths are manifest-UNKNOWN, so rule 3 (#791) resolves them to operator
ownership and a keep-mode upgrade can neither overwrite nor prune them.
A bare `claude` / `pi` keeps its own config AND auth, making it a structural
break-glass rather than one depending on restoring a file under pressure.

opencode is blunter than the rest: it has no dedicated variable and follows XDG,
so isolation also relocates XDG lookups for anything it spawns. Documented in
place.

mosaic-sync-skills now links into those homes and cleans the legacy farms it
previously planted in base installs. Ownership is proven by RESOLUTION, not by
name — only symlinks resolving inside the canonical/local skills dirs are
removed, mirroring the refusal already in commands/skill.js. Verified against a
real install: codex's own .system directory survived while its 102 mosaic links
were removed. Both resolution prefixes are length-checked first; an empty prefix
would make "$resolved" == "$prefix/"* match every absolute path and delete
foreign symlinks.

Immutable launch record
-----------------------
Every launch now appends one record to fleet/run/sessions/events.ndjson before
exec. Mandatory, mechanical, no model involvement.

pi rewrites its own argv to a bare `pi`, so /proc/<pid>/cmdline destroys the
launch evidence — that has already produced a confident wrong diagnosis ("this
agent bypassed the launcher"), disproved only by the parent's argv and only
because the parent had not yet exited. A record written before exec is the only
place this survives.

The path is the #797 Runtime Session Ledger, already operator-classified and
already covered by test-upgrade-manifest-guard.sh, which seeds it and proves a
populated ledger survives keep-mode upgrades — but nothing shipped ever wrote
it. This implements it in the shape that guard already asserts (0600 files under
a 0700 dir).

`mosaic` writes session.launch; launch-runtime.py appends lease.register with
the broker session id and activation capability. They correlate by an explicit
MOSAIC_LAUNCH_ID, never by pid: execRuntime uses spawnSync, so the runtime is a
child with a different pid.

Records normative fragment digests (CONSTITUTION/AGENTS/SOUL/USER/STANDARDS/
TOOLS/RUNTIME) — the same set the broker hashes for promotion, so drift is
mechanically detectable rather than a matter of judgement.

Credential-safe: env is captured as PRESENT NAMES ONLY, and argv values over
256 bytes become a sha256 + length rather than being inlined.

Also fixes CLI_VERSION resolution: '@mosaicstack/mosaic/package.json' is not in
the package exports map and always throws ERR_PACKAGE_PATH_NOT_EXPORTED.
resolveTool() uses that same failing specifier, which is why its documented
preference for bundled tools over the deployed ~/.config/mosaic copy has never
once applied — noted in place, not fixed here.

Verified on sb-it-1-dt: isolated homes written and base installs byte-identical
for all four harnesses; 408 legacy symlinks removed with 1 foreign entry
preserved; launch records paired across the spawn boundary. typecheck shows zero
errors in launch.ts (the @mosaicstack/types failures are pre-existing and
reproduce on a pristine origin/main worktree).
2026-08-11 20:51:03 -05:00
be-coder-08andJason Woltje f840843908 feat(pr-merge): preserve linked authors in squash messages (#1066)
Co-authored-by: be-coder-08 <[email protected]>
2026-08-11 20:51:03 -05:00
be-coder-08andJason Woltje aacb11b0b9 fix(ci): remove upgrade rollback signal race (#1060)
Co-authored-by: be-coder-08 <[email protected]>
2026-08-11 20:51:03 -05:00
be-coder-08andJason Woltje ce6bda18f2 test(ci): make queue guard harness deterministic (#1062)
Co-authored-by: be-coder-08 <[email protected]>
2026-08-11 20:51:03 -05:00
mos-dt-0 aca28405be Merge pull request 'ci: provision Pi runtime 0.84.1 in the test step (Invariant R)' (#1164) from ci/provision-pi-runtime into next
ci/woodpecker/push/publish Pipeline failed
2026-08-12 01:50:56 +00:00
mos-dt-0 c1eb0659c4 Merge pull request 'docs(framework): adopt MOS-STE writing standard onto next (from #965)' (#1165) from adopt/965-mos-ste-writing-standard into next
ci/woodpecker/push/publish Pipeline failed
2026-08-12 01:18:29 +00:00
Jason WoltjeandClaude Fable 5 b79708fdc7 ci: provision Pi runtime 0.84.1 in the test step (Invariant R)
ci/woodpecker/pr/ci Pipeline was successful
invariant_r_unittest.py (landing with the lease-remediation stack, PR
#1109) hard-requires an installed `pi` binary pinned to the measured
version: it boots Pi's real tool registry and proves the broker's
read-only carve-out resolves to real, unshadowed builtins. Absent
runtime fails loud by design — so CI must provide it.

Install @earendil-works/[email protected].1 (the canonical Pi;
@mariozechner/* is embedded-legacy) at step level in the test step.
Step-level rather than baked into Dockerfile.ci because ci-image
publishes are currently blocked on registry UNAUTHORIZED; baking it in
is the follow-up once registry auth is fixed, at which point this line
degrades to a fast no-op guard like the openssl line above it.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Dtdjx4Gxude9fwyLezCrhh
2026-08-11 20:16:32 -05:00
jason.woltje ebe415132e Merge pull request 'feat(gateway): generic harness catalog + selection HTTP surfaces (P3 Slice Zero, Task 3)' (#1169) from feat/p3-slice0-task3-catalog-selection into next
ci/woodpecker/push/publish Pipeline was canceled
2026-08-12 01:11:21 +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.woltjeandClaude Opus 4.8 f16f206a0a feat(gateway): expose generic harness catalog and selection
ci/woodpecker/pr/ci Pipeline failed
Add the Slice-Zero catalog and selection HTTP surfaces for P3 Task 3:
GET /api/harnesses, GET /api/harnesses/:harnessId/catalog,
GET+PUT /api/chat/preferences/selection. Scope is always server-derived
via scopeFromUser(CurrentUser); selection tuples are validated against the
live catalog with no fallback substitution and persisted in a transitional
owner-scoped in-memory store. HarnessModule is wired into AppModule.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01ESFAnh2t9HmLwng8oW95St
2026-08-11 19:56:11 -05:00
jason.woltje a186922e3a Merge pull request 'feat(gateway): harness registry, fake adapter, no-substitution suite (P3 Slice Zero, Task 2)' (#1168) from feat/p3-slice0-task2-harness-registry into next
ci/woodpecker/push/publish Pipeline failed
2026-08-12 00:40:03 +00:00
jason.woltje 43513c28f7 feat(gateway): add harness registry and fake adapter
ci/woodpecker/pr/ci Pipeline failed
2026-08-11 19:22:27 -05:00
Jason Woltjeandmos-dt-0 b6c12bdfcb style(framework): apply prettier to WRITING-STYLE.md so CI format passes (#965)
ci/woodpecker/pr/ci Pipeline was successful
The `format` step of .woodpecker/ci.yml:89 (`pnpm format:check`) failed on
pipeline 2111 for this branch. Reproduced on a bench with the lockfile-pinned
[email protected] against the repo .prettierrc and .prettierignore, using CI's
exact glob: WRITING-STYLE.md was the only failing file.

The change is mechanical and semantically null: markdown table cell padding
and `*emphasis*` -> `_emphasis_`. Verified by normalizing both revisions
(whitespace removed, `_`/`*` folded, table rules collapsed) — the results are
byte-identical.

This does not address the prose findings published on #965 (P1-P4); those
await a ruling. The `test` step also failed on 2111, on a base ~40 commits
stale — attribution for that failure needs this rerun, and is not claimed here.

Co-authored-by: mos-dt-0 <[email protected]>
2026-08-11 19:00:46 -05: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
jason.woltje fb9f9cda5a Merge pull request 'fix(gateway): resolve InteractionCoordinationService handoff factory via optional DI token (#1145)' (#1167) from fix/1145-coord-di-compiled-boot into next
ci/woodpecker/push/publish Pipeline failed
2026-08-11 23:57:43 +00:00
jason.woltje 400a21ca18 Merge pull request 'feat(types): generic harness contracts (P3 Slice Zero, Task 1)' (#1166) from feat/p3-slice0-task1-harness-contracts into next
ci/woodpecker/push/publish Pipeline is pending
2026-08-11 23:51:50 +00:00
shaggy (mosaic-dev box) 4cefa5cd88 fix(gateway): resolve InteractionCoordinationService handoff factory via optional DI token (#1145)
ci/woodpecker/pr/ci Pipeline failed
root cause: emitDecoratorMetadata reflected the third constructor parameter as Function and Nest attempted to resolve it

fix: optional HANDOFF_ID_FACTORY injection token, no production provider, preserving undefined -> crypto.randomUUID() default and unchanged positional construction

TDD: real CoordModule red at Function index [2], then green; test overrides only unrelated AuthGuard because its AUTH provider comes from AppModule's global AuthModule context

Closes #1145
2026-08-11 18:37:54 -05:00
shaggy (mosaic-dev box) ddf8616716 feat(types): add generic harness contracts
ci/woodpecker/pr/ci Pipeline was canceled
2026-08-11 18:32:39 -05:00
30a694358d fix(framework): key §5 lookup on rendered bullets, not the token (mos-dt round-2)
§5 sent the agent to read direct|friendly|formal in USER.md, but the builder
renders prose bullets, not the token — the documented lookup could not key on
the shipped file. Table now keys on the leading bullet USER.md actually
contains. Also: 'concise, technical' -> 'concise, structured' (drop the round-1
residual value name from a rule-9 guide). Docs-only, no code, no scope growth.

Written-by: jarvis (dragon-lin)
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-11 18:22:15 -05:00
e01dfa0cd7 fix(framework): ride the existing communicationStyle enum, drop the no-op USER.md edit (mos-dt review #960)
F1: defaults/USER.md is never installed (generated from templates/USER.md.template
via buildCommunicationPrefs). Editing it was a no-op asserting a phantom setting —
exactly the false-green §2 warns against. Reverted.
F2: the framework already has communicationStyle (direct|friendly|formal). §5 now
maps THOSE values to output instead of inventing technical|prose|brief (rule 9).
Minor: §6 states no mechanical prose check exists today; rule 1 points at §3.4.

Written-by: jarvis (dragon-lin)
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-11 18:22:15 -05:00
6c4a2eb626 feat(framework): MOS-STE writing standard + Google-style code + per-user comms choice
Adds the agent output standard to the framework SOT so it injects at launch and
is selectable per user (closes the gap: it lived only as a jarvis-brain lab doc + issue #960).

- guides/WRITING-STYLE.md: MOS-STE (adapted ASD-STE100) for docs, Google Style for code,
  verification-artifact emphasis, absolute user-voice carve-out. Written in MOS-STE.
- defaults/STANDARDS.md: Output-standards block (always injected via the prompting contract).
- defaults/AGENTS.md: routing row so writing/doc/comms work reaches the guide.
- defaults/USER.md: per-user 'Comms style' option (technical|prose|brief), default technical.

Refs mosaicstack/stack#960. Owner directive (Jason, 2026-07-30): docs->adapted ASD-STE100,
code->Google style, resumes/personal carved out, comms style a per-user choice.

Written-by: jarvis (dragon-lin)
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-11 18:22:15 -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 9185b0cce4 Merge pull request 'docs(webui): Phase P structure & migration map' (#1147) from docs/webui-phase-p-structure into next 2026-08-11 22:29:10 +00:00
mos-dt-0andClaude Fable 5 8925a502ae style(webui): prettier-format PHASE-P-STRUCTURE.md
ci/woodpecker/pr/ci Pipeline was successful
format:check was the only red CI step on #1147.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Dtdjx4Gxude9fwyLezCrhh
2026-08-11 17:14:38 -05:00
mos-dt-0 0e4eb1445c Merge pull request 'fix(installer): propagate wizard gateway failures' (#1134) from fix/wizard-gateway-failure into next
ci/woodpecker/push/publish Pipeline failed
2026-08-11 22:01:43 +00:00
mos-dt-0 592d60425f Merge pull request 'fix(installer): require Node 22 for the --next lane' (#1130) from fix/next-node-gate into next
ci/woodpecker/push/publish Pipeline was canceled
2026-08-11 22:01:39 +00:00
mos-dt-0 a43f343efd Merge pull request 'fix(security): mosaic-init RCE — eval on prompt answers → printf -v (#1115)' (#1127) from fix/mosaic-init-rce into next
ci/woodpecker/push/publish Pipeline was canceled
2026-08-11 22:01:35 +00:00
jason.woltje 88ef9d4fa5 Merge pull request 'P3-R1: repair routing health-enum (#1) + wire /mcp command (#5)' (#1154) from feat/webui-p3r1-routing-mcp into next
ci/woodpecker/push/publish Pipeline failed
2026-08-11 20:51:52 +00:00
shaggy (mosaic-dev box) bda308efd9 fix(gateway): repair routing health and MCP command wiring
ci/woodpecker/pr/ci Pipeline was successful
2026-08-11 15:28:57 -05:00
jason.woltje 20718b5a27 Merge pull request 'P4-1: read-only Projects + Tasks SPA pages + dev-port pins' (#1153) from feat/webui-p4-1 into next
ci/woodpecker/push/publish Pipeline failed
2026-08-11 04:00:36 +00:00
shaggy (mosaic-dev box) 29db24210c fix(web): P4-1 restore graceful degradation for missions/tasks fetch on project detail
ci/woodpecker/pr/ci Pipeline was successful
2026-08-10 22:43:47 -05:00
shaggy (mosaic-dev box)andClaude Haiku 4.5 a6085eea37 feat(web): add read-only project and task SPA pages
Co-Authored-By: Claude Haiku 4.5 <[email protected]>
2026-08-10 22:23:59 -05:00
Mos e00cc475a2 Merge pull request 'P3 — Typed SPA chat (Phase P webUI)' (#1151) from feat/webui-p3-chat into next
ci/woodpecker/push/publish Pipeline failed
2026-08-10 22:57:07 +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
shaggy (mosaic-dev box) 7d84e4ee03 fix(gateway): sanitize raw exceptions in /mcp status + /reload sources (P3 re-review#5 blocker)
ci/woodpecker/pr/ci Pipeline was successful
2026-08-10 16:58:07 -05:00
shaggy (mosaic-dev box) 4aaf41dd1a fix(web,gateway): close P3 re-review#4 findings — sanitize all executor catches; lock pre-start turn boundary (wire turnId deferred) 2026-08-10 16:07:12 -05:00
mos-dt-0 bf32f29acd fix(ci): make queue guard purpose-sensitive (#1148)
ci/woodpecker/push/publish Pipeline failed
2026-08-10 20:54:09 +00:00
Jason Woltje 1655b1579a Move deploy and briefs into docs folder. Remove old files.
ci/woodpecker/push/publish Pipeline was canceled
2026-08-10 14:44:19 -05:00
Jason Woltje e478a359eb Move briefs directory inside the docs
ci/woodpecker/push/publish Pipeline was canceled
2026-08-10 14:42:04 -05:00
Jason Woltje 76e4242cb1 Reformat Requirements block
ci/woodpecker/push/publish Pipeline was canceled
2026-08-10 14:40:18 -05:00
shaggy (mosaic-dev box)andClaude Opus 4.8 a45f53071a docs(webui): add Phase P structure & migration map
ci/woodpecker/pr/ci Pipeline failed
First-pass structural reference for the Vite SPA migration (apps/web): dual-app
tree during migration, shared lib/ networking layer, origin-relative/same-origin
serving model, build scripts, the P1-P6 increment map, and the #1145 P5 blocker.
Living doc — details to be fleshed out by follow-up.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01ESFAnh2t9HmLwng8oW95St
2026-08-10 14:34:57 -05:00
Jason Woltje 00eb216480 docs(agents): consolidate project guidance 2026-08-10 14:27:59 -05:00
shaggy (mosaic-dev box) d46a2d675a fix(web,gateway): close P3 re-review#3 findings — sanitize command errors, harden turn-lock & caps 2026-08-10 14:21:52 -05:00
shaggy 8c27024d0e Merge pull request 'fix(gateway): gate FederationModule on tier === 'federated' (#1138)' (#1140) from fix/1138-conditional-federation into next
ci/woodpecker/push/publish Pipeline failed
2026-08-10 07:09:51 +00:00
shaggy (mosaic-dev box) 48bb19310d fix(web): close P3 chat re-review findings 2026-08-10 01:58:27 -05:00
shaggy (mosaic-dev box) 406e40584d test(gateway): raise module-graph import timeout for CI load robustness (#1138)
ci/woodpecker/pr/ci Pipeline was successful
2026-08-10 01:34:24 -05:00
shaggy (mosaic-dev box) 677aeb0c93 fix(gateway): restore daemon config discovery (#1138)
ci/woodpecker/pr/ci Pipeline failed
2026-08-10 00:31:58 -05:00
shaggy (mosaic-dev box)andClaude Haiku 4.5 caebf9ef70 fix(web): harden typed SPA chat lifecycle
Co-Authored-By: Claude Haiku 4.5 <[email protected]>
2026-08-10 00:24:20 -05:00
shaggy (mosaic-dev box)andClaude Haiku 4.5 bd0ef2ab25 fix(gateway): anchor remaining config loads (#1138)
Co-Authored-By: Claude Haiku 4.5 <[email protected]>
2026-08-09 23:54:53 -05:00
shaggy (mosaic-dev box) 2d5a8c81ec fix(gateway): anchor config discovery and isolate env tests (#1138) 2026-08-09 23:11:09 -05:00
shaggy (mosaic-dev box) 884d527cc8 fix(gateway): anchor dotenv discovery to module (#1138) 2026-08-09 22:21:17 -05:00
shaggy (mosaic-dev box) b2e005f2b4 feat(web): add typed SPA chat
Bring the chat experience into the Vite/React-Router SPA on the exact typed
Socket.IO /chat contract from @mosaicstack/types, replacing the /chat
placeholder behind AuthGuard. Surfaces message:ack (with an accessible
status), agent:start, streamed agent:text/agent:thinking, tool start/end
status, agent:end with usage, session:info (thinking controls + routing
decision), commands:manifest, command:result, command:approval (with a
one-time approved-run affordance), system:reload (refreshing the rendered
manifest), and error, and emits message/abort/set:thinking/command:execute/
command:approve with exact payloads.

The gateway does not guarantee message:ack is the first event for a new
conversation (session:info, and error on auth/session-creation failure, can
both arrive first) — conversation-scoped events now adopt the conversation
from whichever scoped event names it first while a send is pending, then
filter everything else against that established conversation. A typed error
stops streaming instead of leaving Stop stuck active; agent:end no longer
appends an empty assistant turn when there is no text or thinking; and a
second message can no longer be sent while a turn is streaming.

Command approval is now integrity-checked end to end: only one
command:approve request may be outstanding at a time (a concurrent request
is ignored rather than overwriting the pending command/args), a stale or
mismatched command:approval response cannot replace active approval state,
and running an approved command clears its approval state immediately (via
a ref, before React re-renders) so a double-click cannot replay
command:execute.

The `/chat` socket is now typed at a single boundary: apps/web/src/lib/
socket.ts narrows socket.io-client's untyped `io()` return value to
`ChatSocket` (Socket<ServerToClientEvents, ClientToServerEvents>) once, at
creation, via the one assertion the library's types force; every consumer
(use-chat-connection.ts) then gets fully checked `on`/`emit` calls with no
further casts. The shared contract types live in the new
apps/web/src/lib/chat-contract.ts (replacing the old spa/chat/types.ts
shim), which re-exports them via type-only imports resolved directly
against packages/types/src (apps/web has no @mosaicstack/types package
dependency, so this stays source-only and is erased at compile time —
no package manifest or lockfile is touched). The two recorded-event test
suites now drive a shared, typed fake socket
(spa/chat/test-support/fake-chat-socket.ts) instead of an untyped
`(event: string, payload: unknown)` harness, so a wrong event name or
malformed payload fails to compile.
2026-08-09 21:39:16 -05:00
shaggy 87daa12976 Merge pull request 'P2 — web SPA data layer + same-origin auth' (#1144) from feat/webui-p2-data-auth into next
ci/woodpecker/push/publish Pipeline failed
2026-08-10 01:52:01 +00:00
shaggy (mosaic-dev box) b82a51da80 fix(gateway): load dotenv before federation tier gate (#1138) 2026-08-09 20:50:37 -05:00
shaggy (mosaic-dev box) 90cf286a09 fix(web): reject protocol-relative auth callbacks
ci/woodpecker/pr/ci Pipeline was successful
2026-08-09 20:43:27 -05:00
shaggy (mosaic-dev box) 0aef432052 docs(scratchpad): record P2 remediation evidence 2026-08-09 20:21:53 -05:00
shaggy 41a16cc916 Merge pull request 'fix(docker): gateway image — git in runner, MOSAIC_ROOT workspace dir, scripts/ in builder, EXPOSE 14242' (#1142) from fix/gateway-runner-image into next
ci/woodpecker/push/publish Pipeline failed
2026-08-10 01:21:30 +00:00
shaggy (mosaic-dev box) e16c08aa9f test(web): align jsdom abort signals with Node 2026-08-09 20:20:30 -05:00
shaggy (mosaic-dev box) a34e92cf39 fix(gateway): harden workspace repository cloning
ci/woodpecker/pr/ci Pipeline was successful
2026-08-09 20:12:39 -05:00
shaggy (mosaic-dev box) a4861c221f docs(scratchpad): record WebUI P2 verification 2026-08-09 20:02:22 -05:00
shaggy (mosaic-dev box) 46d68e1ff4 feat(web): add same-origin SPA authentication 2026-08-09 20:00:22 -05:00
shaggy c3496334a5 Merge pull request 'feat(web): P1 — Vite + React Router skeleton beside Next (Phase P RFC, increment 1/6)' (#1143) from feat/webui-p1-vite-skeleton into next
ci/woodpecker/push/publish Pipeline failed
2026-08-10 00:51:02 +00:00
shaggy 6f29d00149 Merge pull request 'fix: break-C — install-hooks no-ops without git; web image builds @mosaicstack/web' (#1141) from fix/break-c-hooks-and-web-image into next
ci/woodpecker/push/publish Pipeline was canceled
2026-08-10 00:50:19 +00:00
shaggy (mosaic-dev box)andClaude Fable 5 068d0f9b1c feat(web): P1 Vite skeleton beside Next — entry, router, guards, vitest 3
ci/woodpecker/pr/ci Pipeline was successful
First increment of the approved Phase P RFC (webui-mission). Adds a Vite + React
Router SPA scaffold coexisting with the Next app: index.html with the theme
anti-flash script, src/main.tsx entry, the v1 parity route table under Guest/Auth
guard shells, and a dev proxy (/api, /socket.io ws) to the gateway on 14242 so the
SPA is same-origin in dev. vitest bumped to v3 (vite 8 pairing); existing specs
pass unchanged. Next remains the served app until the P5 cutover.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01ESFAnh2t9HmLwng8oW95St
2026-08-09 19:02:55 -05:00
shaggy (mosaic-dev box)andClaude Fable 5 13cd673d50 fix(docker): gateway runner needs git + MOSAIC_ROOT workspace dir; EXPOSE actual port 14242
ci/woodpecker/pr/ci Pipeline was successful
WorkspaceService shells out to git at runtime and roots workspaces at
$MOSAIC_ROOT/.workspaces — the runner image had no git binary and no
workspace directory. EXPOSE said 4000 but main.ts defaults to 14242.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01ESFAnh2t9HmLwng8oW95St
2026-08-09 18:03:42 -05:00
shaggy (mosaic-dev box)andClaude Fable 5 620cc608e0 fix(docker): copy scripts/ into web builder — prepare runs install-hooks.mjs on install
ci/woodpecker/pr/ci Pipeline was successful
The layer-cached install copies only manifests and packages/, so the
root prepare script could not be found and pnpm install exited 1
before the git-absent guard could even run.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01ESFAnh2t9HmLwng8oW95St
2026-08-09 18:03:18 -05:00
shaggy (mosaic-dev box)andClaude Fable 5 91e692e3e7 fix: break-C — install-hooks no-ops without git; web image builds @mosaicstack/web
ci/woodpecker/pr/ci Pipeline was canceled
install-hooks.mjs hard-failed (exit 1) in environments without a git
binary — e.g. the docker image builds, which have no git and no repo.
Hook installation is meaningless there; skip with a warning instead.

docker/web.Dockerfile filtered @mosaic/web, but the package is named
@mosaicstack/web, so the image build compiled nothing.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01ESFAnh2t9HmLwng8oW95St
2026-08-09 17:58:13 -05:00
shaggy (mosaic-dev box) b4753a75cd fix(gateway): gate FederationModule on tier federated (#1138)
ci/woodpecker/pr/ci Pipeline was successful
CaService hard-requires STEP_CA_URL/provisioner config at construction, so an
unconditional FederationModule import makes every standalone/local boot die at
DI time. Gate the module on loadConfig().tier === federated, matching the
documented intent of the federation compose profile (must not start in
non-federated dev).

Verified in mosaic-dev box: standalone tier boots to "Gateway listening on
port 14242" with bootstrap/socket.io/auth surfaces responding; federated tier
path unchanged.
2026-08-09 17:26:28 -05:00
velmaandmos-dt-0 24bbd40dc7 docs: WebUI fleet Claude bridge — Task 0 decision plan (#1131)
ci/woodpecker/push/publish Pipeline failed
Docs-only plan PR. FRED_APPROVED_REF=0629361ca39a4dd7fb3e575d11c64bea9e545dae (review 147). Merged by fred (orchestrator) via API: pr-merge.sh policy predates the next lane (main-only hardcode) — wrapper fix tracked separately.

Co-authored-by: Velma <[email protected]>
2026-08-09 10:28:41 +00:00
Jason Woltje dc67590a96 fix(installer): propagate wizard gateway failures (#1120)
ci/woodpecker/pr/ci Pipeline was successful
2026-08-09 05:01:09 -05:00
Jason Woltje baf4306f51 fix(installer): require Node 22 for next lane
ci/woodpecker/pr/ci Pipeline was successful
2026-08-09 02:37:03 -05:00
Jason Woltje 12677a928d fix(mosaic): prevent init prompt code execution
ci/woodpecker/pr/ci Pipeline was successful
2026-08-09 00:28:57 -05: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
mos-dt-0 4df478cdd1 Merge pull request 'chore(sync): merge main → next (B1) — resolve 8 conflicts, restore next current' (#1041) from sync/b1-main-into-next into next
ci/woodpecker/push/publish Pipeline failed
chore(sync): merge main → next (B1) — restore next current, resolve 8 conflicts (#1041)

Brings next current with main incl the RM-03 guard fix (482/5); preserves next's 10 in-flight commits. Closes #1040.
2026-08-03 10:17:06 +00:00
coder-mos2 b8844e1ff0 fix(sync): close local queue semantic merge gap
ci/woodpecker/pr/ci Pipeline was successful
2026-08-02 23:16:09 -05:00
coder-mos2 906ad8dc30 wip(sync): merge main into next with combined resolutions 2026-08-02 23:11:22 -05:00
coder-mos1andmos-dt-0 5916aeefd6 chore(release): @mosaicstack/mosaic 0.0.49 — ship RM-03 guard fix to release channel (#1036)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline failed
Co-authored-by: coder-mos1 <[email protected]>
2026-08-02 20:09:11 +00:00
coder-mos1andmos-dt-0 58b971aba3 fix(rm-03): make CI queue guard fail on asserted non-readiness (#1032)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: coder-mos1 <[email protected]>
2026-08-01 18:55:26 +00:00
coder-mos1andmos-dt-0 f4fd5967fc RM-61: prove ci-postgres teardown discrimination (#1033)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: coder-mos1 <[email protected]>
2026-08-01 14:54:04 +00:00
mos-dt-0andMos f65e9ea656 docs(remediation): mission-state snapshot at the RM-01 seam (#1028)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: mos-dt-0 <[email protected]>
2026-08-01 01:08:11 +00:00
mos-dt-0andMos f58b3699a6 RM-01: reproducible checkout — the pre-push gate fails on code, not environment (#1027)
ci/woodpecker/push/publish Pipeline failed
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: mos-dt-0 <[email protected]>
2026-08-01 00:50:12 +00:00
mos-dt-0andMos 01e966f36d docs(remediation): mission charter + reconciled execution backlog (#1026)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: mos-dt-0 <[email protected]>
2026-07-31 22:47:03 +00:00
mos-dt-0andMos 524146055d fix(hygiene): .prettierignore must exclude Python build/test artifacts (#1025)
ci/woodpecker/push/publish Pipeline failed
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: mos-dt-0 <[email protected]>
2026-07-31 22:24:49 +00:00
mos-dt-0andMos 06e0d40352 feat(quality): CI test-membership guard — enumeration can no longer silently under-run the disk (#1017) (#1018)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: mos-dt-0 <[email protected]>
2026-07-31 14:08:41 +00:00
Mos 166ee8c90f fix(wake): close fd 9 in the detector's sleep child so a dead detector's lock dies with it (#993)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-31 13:48:33 +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
mos-dt-0andMos 826a8b3b26 fix(git): pr-review.sh — surface the provider's stated reason, drop the hardcoded #865 attribution (#1006)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: mos-dt-0 <[email protected]>
2026-07-31 10:52:58 +00:00
mos-dt-0andMos a4280b9c98 fix(wake): #984 fatal source guard + #985 absorb re-scan — #973 follow-up batch (#1001)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: mos-dt-0 <[email protected]>
2026-07-31 10:16:22 +00:00
Mos 4fb44f6345 fix(wake): three-valued grep verdicts — has_match/count_lines across all ten suites (closes #973) (#983)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-31 08:39:04 +00:00
Mos 089615f63b feat(git): push-guard — refuse verifications satisfied by the null case (closes #975) (#974)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-31 03:35:24 +00:00
mos-dt-0andMos 76eef39a29 docs(wake): #953 dead-letter retention recorded as load-bearing at the write site (#968)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Closes #953.

GATE RECORD: review CLEAR at this exact head (author != reviewer, pre-registered diff-blind checks) + terminal-green CI at this exact head + queue guard clear.
CI CAVEAT (#973): green on the wake suites is currently WEAKER THAN IT LOOKS, IN BOTH DIRECTIONS. grep error/spawn exit codes are read as absence across 257 assertion sites in six idiom forms; 36 inverted (&&-fail) sites — including 19 credential-security canaries — fail toward GREEN under load. These greens were obtained on solo reruns after load-correlated FALSE reds (2115/2116/2118; main itself was red). This merge's safety therefore rests on the CONTENT review, not on the green. Remediation charter fa551c2d0 is authored and in flight.

Co-authored-by: mos-dt-0 <[email protected]>
2026-07-30 23:25:38 +00:00
mos-dt-0andMos 47f8689231 fix(wake): #952 quarantine-audit clean sweep names BOTH unprovable residual classes (#967)
ci/woodpecker/push/publish Pipeline was canceled
ci/woodpecker/push/ci Pipeline was canceled
Closes #952.

GATE RECORD: review CLEAR at this exact head (author != reviewer, pre-registered diff-blind checks) + terminal-green CI at this exact head + queue guard clear.
CI CAVEAT (#973): green on the wake suites is currently WEAKER THAN IT LOOKS, IN BOTH DIRECTIONS. grep error/spawn exit codes are read as absence across 257 assertion sites in six idiom forms; 36 inverted (&&-fail) sites — including 19 credential-security canaries — fail toward GREEN under load. These greens were obtained on solo reruns after load-correlated FALSE reds (2115/2116/2118; main itself was red). This merge's safety therefore rests on the CONTENT review, not on the green. Remediation charter fa551c2d0 is authored and in flight.

Co-authored-by: mos-dt-0 <[email protected]>
2026-07-30 23:25:34 +00:00
mos-dt-0andMos 8d1d6e5e76 feat(wake): #958 A11 preimage.sh — durable provenance for the operator-side preimage definition (#964)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Closes #958.

The preimage definition (source-adapter.sh) is the single most consequential file in the wake pipeline — every observed_hash is a sha256 of what it emits — and it was UNVERSIONED: no git history, no backup. When it was edited at 07:27 on 2026-07-30, attribution was recoverable only because an agent transcript happened to still be on disk. A11 gives that file durable provenance (option (2) of #958: recorded content-addressed, not in-band).

DESIGN, per the pre-registration:
- Provenance is OUT-OF-BAND (never on the adapter's stdout) — an in-band record would advance observed_hash for every source at once and manufacture the re-baseline it exists to explain (B3).
- The obligation never depends on the provenance path: a missing/corrupt store cannot halt the detector or swallow a wake (#940 advisory-fields precedent; B4).
- DESC_FMT=d1 is NOT the provenance record — the tag versions the descriptor FORMAT; a behaviour change that keeps descriptor shape re-baselines every hash and leaves the tag unchanged (B6).

CREDENTIAL HARD GATE (rebuilt after the first verdict FAILED it): byte capture is now RECORD-ONLY BY DEFAULT (extras opt in via WAKE_PREIMAGE_CAPTURE), not allow-by-default-refuse-on-shape — because a shape list can only refuse the secrets someone already enumerated, and the tool's own usage text recommended adding detector.env (where HMAC material lives). Deny is evaluated on BOTH raw and resolved path forms with resolved anchors, ordered before allow — closing the realpath-before-deny ordering defect that let a renamed symlink target through.

VERIFICATION (reviewer, mos-dt, independent of the author's claims):
- Seven decoy cases by planted-marker-then-grep-whole-state-dir: known cred path / same-name symlink / RENAMED-target symlink / prefixed secret / prefixless secret / opted-in-symlink-to-DENIED-target all REFUSED; opted-in-symlink-to-ALLOWED-target CAPTURED (positive control that the harness can capture at all, and that C was not closed by breaking every symlink).
- Polarity-completeness self-test RE-RUN with the shape list stubbed always-allow AND both deny lists stubbed — case D still safe: the flip is complete, the shape list is not load-bearing. Each stub proven live first (a stub that silently fails to apply reports the dangerous state as safe).
- B11: rm-then-change fails LOUD (rc=1), refuses to re-baseline, leaves the ledger absent; absent-with-emptied-objects still first-installs cleanly (absent-is-not-corrupt not paid for by breaking first install).
- RED-first reproduced exactly P13-P16 pre-fix; each refusal corroborated three ways (loud stderr, ledger row captured:false WITH a hash so attribution survives refusal, objects/ holding only the adapter).

KNOWN RESIDUAL (filed #969, non-gating): the deny check is both-forms but the suite needles only the resolved form — a deny reduced to resolved-only survives 17/17 green and would leak a renamed-symlink case. No reachable leak at this head (shipped code correct on all seven decoys); it constrains a FUTURE edit. Doctrine: a both-forms fix needs a needle per form; a fixture that satisfies its assertion through a DIFFERENT rule is testing the rule it did not mean to test.

Authored by pepper (sb-it-1-dt); independently reviewed by mos-dt (sb-it-1-dt) under diff-blind pre-registration (7242688b1, predating first read) — NOT CLEAR on the first verdict (B2/B11 failed by decoy), CLEAR at 8aff7d8 after the polarity rebuild. Manifest version 0.7.0.

Co-authored-by: mos-dt-0 <[email protected]>
2026-07-30 21:48:06 +00:00
mos-dt-0andMos 6a7fce34bb fix(wake): #946 digest ack watermark clamped at quarantined seqs — disclose AND clamp (#951)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Closes #946.

The digest omitted a quarantined entry's claim from disclosure while still advancing the ack watermark it instructed the consumer to run — converting a fail-safe HOLD into a silent DISCARD, through the documented normal path. Measured: the burial instruction was re-issued FIVE times, four fresh digests plus one system-initiated redelivery fired purely because the entry had gone unconsumed for 1826s. That redelivery is the proof of the 'indefinitely' half: the mechanism re-asserted itself with no new information.

SCOPE — this was NOT a missing check in the consume path. Measured before the fix: dead-letter occurrences were digest.sh 27, store.sh 0, ack.sh 0, detector.sh 0, reconcile.sh 0. Quarantine was owned ENTIRELY by the renderer; the store that advances the watermark had zero knowledge the ledger existed, so an entry could be quarantined by one subsystem and consumed by another with no possible interaction. The fix is therefore a deliberate cross-module decision — option (b), quarantine recorded into a store-owned file, preserving the existing direction of dependency — pre-registered by the consumer before any diff existed.

VERIFICATION
- Pipeline 2107 terminal SUCCESS at e11bc6622, read clone-inclusive from the provider API rather than through `pipeline-status.sh` (which filters `.type != "clone"` per workflow and would hide a clone failure behind an all-green table): 9/9 children success, exit 0 each, no non-success member. Its `test` step runs all nine wake harnesses via turbo -> packages/mosaic `test` -> `test:framework-shell`.
- Independent review by the consumer on the affected lane: eleven pre-registered acceptance checks, authored and delivered BEFORE the diff was read — the file list deliberately unlooked-at, because a filename alone would have disclosed which option was chosen. All eleven resolved, no blocker.
- The check that decides it: a RAW `ack.sh consumed --upto N` with no digest involved must refuse to advance past a quarantined seq — the case an agent hits when a digest is MISSED, and the one that would have sunk a disclosure-only fix. Covered at the head as a named assertion (T13 ordinary-path bypass), written independently of the reviewer's list.
- Mutation: one asserted site disabled -> TWELVE assertions die, every one BEHAVIOURAL, ZERO count assertions, including one killing across the module boundary the fix spans.
- RED control at base a6b5f6a: 34 and 10 assertions fail, matching the body exactly.
- Coordinator re-verify by a different instrument than the reviewer used: static reference counts across the base/head boundary — store.sh 0 -> 49, ack.sh 0 -> 6, `--agent` unchanged at 4 (so #949 correctly stayed out). A fix present-but-inert passes the count and fails the mutation; a fix behaviourally correct but smuggling #949 passes the mutation and fails the count. Neither result is reachable by repeating the other.

KNOWN RESIDUALS
- The `consumed-hashes` repair criterion is met only for keys that RE-EMIT. A corrupted row whose key never recurs stays false indefinitely; the sweep covers those, and the known-false row named in the acceptance criteria had already self-healed by re-emission rather than by design — safe by population, not by design.
- The audit's clean-sweep message names one unprovable class; a second exists (a surviving dead-letter row with an empty `observed_hash` cannot be convicted either). Wording, not logic. Filed separately.
- The audit's provability bound makes dead-letter RETENTION load-bearing for auditability. Nothing prunes it today, so this is latent — but any future rotation or size cap silently converts provable rows into unprovable ones with no signal at either end. This is not a defect; it is a property that BECAME load-bearing and is recorded nowhere. Filed separately.
- `test-wake-detector.sh` D4 fails at this head AND identically at base, with an empty diff over detector files — pre-existing, tracked, not introduced here.

Authored by pepper (sb-it-1-dt); reviewed independently by mos-dt (sb-it-1-dt). The mos-dt-0 commit and fork identity does not identify the author — attribution collapse tracked separately.

Co-authored-by: mos-dt-0 <[email protected]>
2026-07-30 15:41:03 +00:00
mos-dt-0andMos a6b5f6a01a fix(wake): #944 path becomes a hard-locator arm — detector-shape actionable entries pass the §2.1 gate (#945)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Closes #944.

_has_hard_locator accepted only repo+issue / 40-hex sha / file — the forge vocabulary. The detector emits path (+snapshot_sha when attested) and NEVER emits file/issue/sha, so predicate and sole producer shared ZERO keys and every class=actionable board_file entry dead-lettered. Latent since #920, whose harness pinned the detector's own emission shape as its malformed example — the suite certified the gap it was written to guard.

Fix: `path` becomes a hard-locator arm, and ONLY path. Bare path-less snapshot_sha is a deliberate NON-arm (would widen past the board_file vocabulary); Q11(d) asserts it still quarantines at 7/40/64 chars, spanning the detector's ^[0-9a-f]{7,64}$ attestation range.

VERIFICATION
- Pipeline 2105 terminal-green at fa36da8. Its `test` step reaches all nine wake harnesses via turbo -> packages/mosaic `test` -> `test:framework-shell`, which names each suite explicitly. The two-levels-down indirection matters: no search of .woodpecker/* can see it, and that is exactly why this question was got wrong earlier today and then corrected. CI therefore DOES attest the quarantine suite and the detector suite at this head.
- Independent review (mos-dt, consumer on the affected lane) PASS at fa36da8, from a detached worktree: predicate provably unmoved from fb3c3c3 (comment-stripped sha256 identical, _has_hard_locator body byte-identical), RED control at base reproduced exactly 8 failures all Q11 including "got 6".
- Third reviewer (wake-judge) ACCEPT on both judgment calls: the Q1 assertion reversal is a legitimate correction (the flip was forced, not elective — base fixtures red 19 assertions against the head predicate) and path-alone satisfies §2.1, whose operative test is "one targeted call, never a search" — two of its four named exemplars already resolve to current state. Requiring path+snapshot_sha jointly would permanently dead-letter a declared source class and conflict with #940's advisory-fields ruling.
- Judge's mutation criterion met: with the reconciled exemption disabled, the gate-level assertion ("an ORIENTATION-tier enumeration must NOT be quarantined") dies at this head and did not exist as a casualty before F1.
- D4 (detector lock re-acquisition) fails intermittently at base AND head; git diff base..head over the detector files is EMPTY, so it is out of this PR's surface on structural grounds rather than on a re-roll. Known defect, fix identified (detector.sh:516, fd 9 leaked into sleep), tracked separately.

KNOWN RESIDUALS
- ENUM-B is now the sole address-free reconciled fixture, so the exemption's gate-level guard is a population of one. Safe by population, not by design. Author follow-up: assert ENUM-B carries no hard-locator arm so the harness guards its own premise.
- The binding spec (CONVERGED-DESIGN.md §2.1, separate repo) still enumerates four forge tokens and reads narrower than the shipped gate. Tracked as #948, sequenced after the dragon-lin reseed.
- Hard-locator arms are type-loose: repo/file/path accept any non-null JSON value. Pre-existing; `sha` fails closed only by accident of test(). Tracked separately.

Authored by pepper (sb-it-1-dt); reviewed independently by mos-dt (sb-it-1-dt) and wake-judge. The mos-dt-0 commit/fork identity does not identify the author — attribution collapse tracked in #3092.

Co-authored-by: mos-dt-0 <[email protected]>
2026-07-30 14:01:48 +00:00
mos-dt-0andMos 539b475a92 fix(wake): #943 whole-string validation for WAKE_SNAPSHOT_TS_FUTURE_SLACK + version 0.6.13
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
grep is line-oriented, so a multi-line knob value passed the per-line anchors and was still fatal in arithmetic. Replaced with a case pattern matching the whole string, so an embedded or leading newline rejects. Manifest bumped 0.6.12 -> 0.6.13: three materially different detectors had shipped under one version string, and version= is the component sole self-identity claim.

Authored-by: pepper
Reviewed-by: mos-dt (independent, at this head; transfer proven by blob-hash equality)
Merged-by: Mos
Co-authored-by: mos-dt-0 <[email protected]>
2026-07-30 11:56:18 +00:00
mos-dt-0andMos 3e47fc076f fix(wake): #942 harden WAKE_SNAPSHOT_TS_FUTURE_SLACK — validate shape AND force base-10
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
The slack knob was interpolated raw into $((...)) under set -u: a malformed value was FATAL to the poll, falsifying the poll-never-fails invariant, and a negative value inverted the guard to deny-all. Shape validation alone was insufficient — bash reads a leading zero as octal, so 08/09 passed the regex yet were fatal and 0300 silently meant 192. Now validated ^[0-9]{1,9}$ with a loud fallback to 300, then forced to base-10 via 10# so the knob means what the operator wrote.

Authored-by: pepper
Reviewed-by: mos-dt (independent, found both the original defect and the radix residual)
Merged-by: Mos
Co-authored-by: mos-dt-0 <[email protected]>
2026-07-30 11:17:51 +00:00
mos-dt-0andMos 8710d0f6d7 feat(wake): #940 snapshot-datable digests — fd-3 snapshot-metadata channel (#941)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Adapter emits snapshot sha/ts out-of-band on fd 3 so a changing value never enters the delta-gate hash. Detector validates advisorily (sha regex, epoch sanity before arithmetic, future-skew slack); malformed metadata is dropped loudly and never gates the wake. Digest renders snapshot_sha/snapshot_ts plus a git-show re-verify hint. Adapters that never write fd 3 are byte-identical.

Reviewed-by: Mos (design, independent)
Reviewed-by: mos-dt (artifact, hardening §2)
Co-authored-by: mos-dt-0 <[email protected]>
2026-07-30 10:55:18 +00:00
jason.woltjeandMos b981b4ec10 fix(wake): #934 mount-free, privilege-invariant seq-integrity fault injection (T9/T11 run in non-priv CI) (#936)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-26 16:34:58 +00:00
jason.woltjeandMos 9e81ffd7fc fix(wake): #932 stop reconciler re-enumerating already-CONSUMED detector state (#935)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-26 15:48:40 +00:00
jason.woltjeandMos 9becaf877f fix(wake): #917 gate the final observed_seq cursor write + observed.set/cursor consistency (defense-in-depth) (#933)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-26 15:14:38 +00:00
jason.woltjeandMos 17087efe15 fix(wake): #925 framework-ship canon fallback-wake (systemd timer + schema bound + A10 install/validate) — F7 out of the box (#931)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-26 13:44:51 +00:00
jason.woltjeandMos 0ea41e848b fix(wake): #913 installer adoption gaps — _lib dep-check fail-loud + mosaic-wake.service systemd-search-path link+validate (#930)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-26 12:37:29 +00:00
jason.woltjeandMos 347c1d57c1 fix(wake): #924 route dead-letter quarantine alarm via WAKE_ALARM_SINK_CMD with per-observed_seq dedup (G2a) (#929)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-26 11:45:09 +00:00
jason.woltjeandMos 13e6ce5e5c fix(wake): #927 enqueue TOCTOU — move stale-tmp cleanup off the hot enqueue path (no concurrent in-flight-write clobber) (#928)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-26 10:56:40 +00:00
jason.woltjeandMos 90265ef550 test(wake): #923 de-flake T10 concurrent-enqueue race (deterministic barrier) (#926)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-26 10:23:07 +00:00
jason.woltjeandMos 937a276208 fix(wake): #920 quarantine render-refused drain entry (no head-of-line block) + reconciler enumerations render orientation-tier (reconciled:true, render-tier not class=digest) (#922)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-26 08:45:51 +00:00
jason.woltjeandMos 712c770b7a fix(wake): #912 exercise the digest/HMAC trust suite in real CI (fix runner divergence + openssl + hard-require) (#921)
ci/woodpecker/push/ci-image Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-26 08:11:28 +00:00
jason.woltjeandMos d967a4a926 fix(wake): #914 digest renderer — WAKE_AGENT-prefixed ack line + digest-class locator threading (#916)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-26 07:13:07 +00:00
jason.woltjeandMos c585ac3326 test(wake): #918 de-flake T7 ack-no-network-block (sub-second timing) (#919)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-26 06:46:28 +00:00
jason.woltjeandMos e2ec927b1c fix(wake): #908 unify observed_seq on a single store-side allocator (dissolve detector-private-counter seam) (#915)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-26 06:02:02 +00:00
jason.woltjeandMos 2378665eaf feat(wake): W7 A10 idempotent installer + mosaic-wake.service (component-manifest, Gate-A, blank-reset retire, snapshot-guard, fail-closed install-validate) (#911)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-26 04:19:09 +00:00
jason.woltjeandMos 003cdaa1a6 feat(wake): W6 — off-host dead-man beacon + pluggable alarm-sink adapter (#910)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-26 02:30:28 +00:00
jason.woltjeandMos 320f5bfb6f feat(wake): W5 — synthetic-canary FN-oracle + source-parity reconciler (#909)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-26 02:04:19 +00:00
jason.woltjeandMos 5df47e735e feat(wake): W4 — per-host delta-gated detector daemon (fail-loud source semantics, enqueues to W2 store) (#907)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-26 01:04:55 +00:00
jason.woltjeandMos dd1391fd76 fix(wake): digest hard-locator gate covers top-level .claim entries (Closes #905) (#906)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was canceled
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-26 00:58:40 +00:00
jason.woltjeandMos 10d957d095 feat(wake): W3 — cumulative-state digest renderer + non-circular HMAC signer (#904)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-26 00:32:12 +00:00
jason.woltjeandMos dc45eb7c30 feat(kbn): land KBN-101 Envelope A v6 (rc.20) — declarative sink-RBAC + RLS write-source (Form A) (#902)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-25 23:58:18 +00:00
jason.woltjeandMos 28f022d9c0 feat(wake): W2 — three-cursor durable store + RECEIVED/CONSUMED ack-wrapper + watch-list schema (#903)
ci/woodpecker/push/publish Pipeline was canceled
ci/woodpecker/push/ci Pipeline was canceled
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-25 23:58:08 +00:00
jason.woltjeandMos 2726fab5e0 chore(framework): wire agent-send.test.sh into CI test:framework-shell (W1 follow-up) (#901)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-25 23:23:24 +00:00
jason.woltjeandMos ab6e8e80dc fix(framework): send-message.sh fail-loud submission verdict + regression tests (Patch 6) (#895)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-25 22:57:11 +00:00
jason.woltjeandMos 1933c6cb1d feat(framework): accept digest message class in agent-send.sh (W1) (#894)
ci/woodpecker/push/publish Pipeline was canceled
ci/woodpecker/push/ci Pipeline was canceled
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-25 22:56:46 +00:00
jason.woltjeandMos 48a0c86093 fix(lease-broker): recovery_runtime_unittest wait_ready() connect-probe (co-equal CI flake, cherry-pick #898) (#900)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-25 22:26:38 +00:00
jason.woltjeandMos 79c8647fd9 fix(lease-broker): wait_ready() polls real connect-readiness not socket-file existence (flaky CI race) (#898)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-25 22:10:15 +00:00
jason.woltjeandMos 2483dada33 fix(framework): pr-review.sh -r/--repo + -H/--host overrides + UA + repo preflight (Patches 5/5c) (#896)
ci/woodpecker/push/publish Pipeline was canceled
ci/woodpecker/push/ci Pipeline was canceled
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-25 22:08:08 +00:00
jason.woltjeandMos 4c117afe03 docs(framework): add WAKE-DOCTRINE.md guide (W0) (#893)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was canceled
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-25 21:57:51 +00:00
jason.woltjeandMos 2698ddb7b5 feat(comms): P1 presence — minimal Synapse + fleet presence room + mosaic.presence heartbeat + liveness (#888)
ci/woodpecker/push/ci-image Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-25 21:03:18 +00:00
jason.woltjeandMos fabde1c834 docs(rfc): add RFC-001 (MACP/Matrix-native comms) + RFC-002 (install/config/topology) (#886)
ci/woodpecker/push/publish Pipeline was canceled
ci/woodpecker/push/ci Pipeline was canceled
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-25 21:02:54 +00:00
jason.woltjeandMos 3c7890f17f fix(framework): detect-platform get_gitea_token fail-loud on absent per-slot token (Patch 2b) (#890)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-25 20:51:19 +00:00
jason.woltjeandMos 529c177830 fix(update): mosaic update runs the install-ordering guard post-reseed (#882 --sync-only bypass) (#883)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-23 22:18:34 +00:00
jason.woltjeandMos a32ce4c8f9 feat(869-c4): activation version-coupling assertion (Part of #869)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Part of #869

Mos (id-11) Gate-16 merge: independent APPROVE @90eb48fa (fail-closed identity locks byte-unchanged verified), author id2 != approver id11, clean mosaic-coder author, CI green wp1992. #869 Point-1 CODE COMPLETE (C1/C3/C5/C2/C4).

Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-23 19:07:27 +00:00
jason.woltjeandMos d351caad36 feat(869-c2): install-ordering enforcement-hook guard (Part of #869)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Part of #869

Mos (id-11) Gate-16 merge: independent APPROVE @b6f36564 (8/8, verified vs real production settings template), author id2 != approver id11, clean mosaic-coder author, CI green wp1988.

Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-23 18:48:33 +00:00
jason.woltjeandMos 76b86a246e feat(869-c5): mosaic doctor activation-check (Part of #869)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was canceled
Part of #869

Mos (id-11) Gate-16 merge: independent APPROVE @e75e3238 (8/8), author id2 != approver id11, clean mosaic-coder author, CI green wp1987.

Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-23 18:38:21 +00:00
jason.woltjeandMos 4422231bdb feat: per-agent Gitea identity resolution (#873)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Closes #873

Mos (id-11) Gate-16 merge: independent APPROVE @4b472a22 (author-blocker dissolved via (a) re-author, identical tree hash to tech-approved head), author id2 != approver id11, clean mosaic-coder commit-author, CI green wp1985. Framework train COMPLETE 6/6.

Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-23 18:09:34 +00:00
jason.woltjeandMos 8504216964 fix(pr-review): case-insensitive _belongs slug compare (#875)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Closes #875

Mos (id-11) Gate-16 merge: independent APPROVE @9d8d58ae, author id2 != approver id11, clean mosaic-coder commit-author, CI green wp1982.

Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-23 17:53:26 +00:00
jason.woltjeandMos 7edc9b3121 fix(gitea): direct REST comment/review with fail-closed read-back (#865)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Closes #865

Mos (id-11) Gate-16 merge: fresh confirmatory independent APPROVE @8ac7e70f (1241-case fuzz 0 fail-open), author id2 != approver id11, clean commit-author, CI green wp1966.

Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-23 17:25:32 +00:00
jason.woltjeandMos 2f50c0876b feat(869-c3): lease-broker supervisor unit (Part of #869)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was canceled
Part of #869

Mos (id-11) Gate-16 merge: independent APPROVE @75235ef8 (9/9, no live host mutation), author id2 != approver id11, CI green wp1971.

Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-23 17:19:48 +00:00
jason.woltjeandMos db90da347e feat(869-c1): activation-capability probe (Part of #869)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was canceled
Part of #869

Mos (id-11) Gate-16 merge: independent 3-round APPROVE @c5a2bcc5, author id2 != approver id11, CI green wp1973.

Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-23 17:14:57 +00:00
jason.woltjeandMos 48fd1df28a fix(ci-queue-wait): treat absent branch (404) as queue-clear (#872)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was canceled
Closes #872

Mos (id-11) Gate-16 merge: independent review APPROVE @23cbdaf8, author jason.woltje(id2) != approver Mos(id11), CI green wp1974.

Co-authored-by: jason.woltje <[email protected]>
Co-committed-by: jason.woltje <[email protected]>
2026-07-23 17:08:57 +00:00
jason.woltje b79336a8c1 feat(orchestrator): board-roll.sh — auto-roll LIVE board to LEDGER under byte cap (#868)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline failed
feat(orchestrator): board-roll.sh - auto-roll LIVE board to LEDGER under byte cap

Closes #868
2026-07-22 09:20:03 +00:00
jason.woltje 4e5af23214 Merge pull request 'skills: add glpi-* family (solve, followup, sweep, list, create)' (#863) from feat/glpi-skills into main
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-21 01:09:50 +00:00
Hermes Agent 880c28b191 docs(glpi-skills): genericize operator-specific content per review
ci/woodpecker/pr/ci Pipeline was successful
2026-07-20 19:45:50 -05:00
Jason WoltjeandClaude Opus 4.8 7bc2dfb6c8 skills: add glpi-* family (solve, followup, sweep, list, create)
ci/woodpecker/pr/ci Pipeline was successful
GLPI helpdesk workflow skills written against the portable
tools/glpi/ tooling (session-init.sh, ticket-list.sh, ticket-create.sh),
cross-linked via [[glpi-*]]:

- glpi-solve    — close a ticket by setting status Solved (5); GLPI auto-closes
- glpi-followup — add a followup via the top-level /ITILFollowup endpoint
- glpi-sweep    — read-only hunt for done-but-open tickets needing Solve
- glpi-list     — query tickets by status/recency
- glpi-create   — open a new ticket

Core rule encoded: completing work means setting status Solved, not just
posting a resolution followup (a followup documents; only Solved auto-closes).

Note: illustrative examples in the bodies are USC-flavored (M2M / helpdesk
ticket numbers) and can be genericized in review if preferred.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_019GjBgrb9tHgvq414Fqj37c
2026-07-20 18:04:53 -05:00
jason.woltje b0d78d8632 fix(mosaic): de-flake mutator-class lease gate TTL-expiry test (#861)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-20 10:32:45 +00:00
jason.woltje 344d86a635 fix(#812 follow-up): normalize detect-platform.sh host-match port comparison by scheme (#859)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-20 10:13:29 +00:00
jason.woltje acd7d380f6 fix(framework): install deps on worktree bootstrap + legible deps-preflight at gate seam (#856) (#858)
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish Pipeline was successful
2026-07-20 09:38:12 +00:00
jason.woltje 3b70c66c07 fix(framework): drop unsupported --comment from tea pr approve/reject; route review body via durable comment (#835) (#857)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-20 09:19:48 +00:00
jason.woltje 11d2818453 docs(tasks): FCM-M5-001 done — verified completion evidence (supersedes #848) (#853)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-20 07:45:08 +00:00
jason.woltje aa999daf1b fix(framework): durable Gitea comment posting in pr-review.sh via REST + read-back verify (#812) (#852)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-20 06:45:48 +00:00
jason.woltje 77c9a82614 fix(lease-broker): de-flake recovery_runtime b2 broker-socket ConnectionRefused race (#849) (#851)
ci/woodpecker/push/publish Pipeline was canceled
ci/woodpecker/push/ci Pipeline was canceled
2026-07-20 06:44:37 +00:00
jason.woltje 627cf2bb38 docs(fleet): add operator configuration guide (#789)
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish Pipeline was successful
2026-07-20 05:22:25 +00:00
jason.woltje 0582a8912b WI-7 #834: T-C server-side branch-protection posture + R1 honesty amendment (#847)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-20 04:20:02 +00:00
jason.woltje 2509eb7646 WI-6 (#833): constrained recovery command + mosaic-context-refresh skill wrapper (#846)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-20 03:33:00 +00:00
jason.woltje 07553ead33 WI-5 #832: Receipt-challenge protocol (compaction-refresh, milestone 188) (#845)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-19 23:18:56 +00:00
jason.woltje e522b22fa4 WI-4 (#831): verbatim-hashed normative fragments (B_payload/H_payload) (#844)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-19 20:34:20 +00:00
jason.woltje e4d7d4502d WI-3 (#830): compaction observers → revoke + D4 same-PID generation auto-revoke (#842)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-19 19:46:28 +00:00
jason.woltje 8dfcf1903e fix(#838): bound broker reply deadlines + fail-close empty-read; de-flake acceptance harness (#839)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Bound broker reply deadlines (separate read/lock/send budgets, BROKER_BUSY-before-mutation, fresh post-handle send budget → closes drop-after-commit window); fail-close empty/truncated read → GATE_UNAVAILABLE deny. De-flakes the 1917 acceptance surface. terra CODE APPROVE (pi) + Opus SECREV APPROVED (claude) — RoR comment 18143. Promote-lease-lost-ACK residual = fail-safe two-generals observability-gap, routed to WI-3 D2-v5 as named-disclosed-bounded-residual (route i). Gate-16 3-principal author=gpt-sol.

closes #838
2026-07-18 07:15:50 +00:00
jason.woltje abd2791f59 feat(mosaic): WI-2 mutator-class guard for directive-freshness (#837)
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish Pipeline was successful
WI-2: mutator-class guard for the compaction directive-freshness mechanism — command-position parser (prefix x var-indirection unified) + primitive-anchored invariant backstop + all-tools-hook fail-close. Parser-complete on principle: realistic evasion matrix RED-regression-covered, residual exotic evasions proven backstop-caught (B-tests), lens-convergence reached.

closes #829
2026-07-18 05:51:58 +00:00
jason.woltje 8ec67a1126 feat(mosaic): add authenticated external lease broker (#836)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-18 03:12:23 +00:00
jason.woltje d801d6c4c8 feat(mosaic): add secure skill registration CLI (#826)
ci/woodpecker/push/ci-image Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-07-17 23:45:35 +00:00
jason.woltje d3bf52898b fix: reject unknown installer arguments (#825)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-07-17 22:16:51 +00:00
jason.woltje 3f77229e88 fix(#792): fleet roster ENOENT actionable exit + installer heading printf (#818)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-07-17 21:36:36 +00:00
jason.woltje 686c881fe4 fix: fetch actual Gitea PR head for Codex reviews (#815)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-17 19:44:56 +00:00
jason.woltje fe7a468c9d ci: bake jq into the prebuilt test image (#821)
ci/woodpecker/push/ci-image Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-07-17 19:12:43 +00:00
jason.woltje cabf02e7b9 ci: bake git into the prebuilt test image (#819)
ci/woodpecker/push/ci-image Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-07-17 18:11:16 +00:00
jason.woltje 9ddc6fbda8 feat(fleet): mosaic fleet regen — regenerate roster-derived projections (PR3 of #791) (#813)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-17 03:17:55 +00:00
jason.woltje 31607a4af6 feat(mosaic): durable pre-update snapshot + verify net + restore CLI (#791 PR2) (#811)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-17 00:43:18 +00:00
jason.woltje 32a0ffba13 feat(mosaic): manifest-owned upgrade guard so updates never wipe operator config (#791) (#802)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-16 23:01:26 +00:00
jason.woltje 8536454257 fix(glpi): accept HTTP 206 in list wrappers (#807) (#810)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-16 22:47:58 +00:00
jason.woltje 4f29cc604d fix(tmux): correct cross-socket sender identity (#808) (#809)
ci/woodpecker/push/publish Pipeline was canceled
ci/woodpecker/push/ci Pipeline was canceled
2026-07-16 22:47:25 +00:00
jason.woltje 3be443c96d feat(mosaic): claudex isolated config + env-inject + yolo wiring (P2–P4 of #790) (#806)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-16 22:10:33 +00:00
jason.woltje 59f5f51ffd feat(mosaic): claudex proxy preflight + lifecycle (P1 of #790) (#793)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-16 21:15:33 +00:00
jason.woltje 9745bc3f29 feat(fleet): add reviewed v1-to-v2 migration preview (#788)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-16 13:11:16 +00:00
jason.woltje adad486b6f fix(fleet): enforce exact comms authority (#787)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-16 00:32:23 +00:00
jason.woltje c1aecfabe9 test(fleet): cover reconciler lifecycle gates (#786)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-15 16:46:13 +00:00
jason.woltje 499090508e feat(fleet): reconcile local roster state (#785)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-15 15:03:31 +00:00
jason.woltje c593a15ef8 docs(kbn): freeze KBN-101 database role split contract (#774)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-15 13:34:29 +00:00
jason.woltje bc5e73629e feat(fleet): add generation-guarded agent CRUD (#773)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-15 12:03:05 +00:00
jason.woltje 191efaefeb feat(fleet): enforce generated environment boundary (#772)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-15 08:40:32 +00:00
jason.woltje e9c4aa3e8b test(fleet): validate shipped artifact dispositions (#770)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-15 01:37:12 +00:00
jason.woltje a5e8e55401 feat(fleet): add shared role semantics (#768)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-15 00:53:47 +00:00
jason.woltje eb4e14ae5c feat(mos): add logical identity connector fencing (#757)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-07-14 23:33:06 +00:00
jason.woltje 2e2280070a docs(#753): clear KBN-010 threat and schema gate (#765)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-14 21:46:44 +00:00
jason.woltje aa5b43bba2 feat(fleet): add roster v2 structural compiler (#764)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-14 20:37:52 +00:00
jason.woltje ba13c08890 Fixes #756 (#763)
ci/woodpecker/push/ci-image Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was canceled
2026-07-14 20:26:15 +00:00
jason.woltje c32d85a337 docs(fleet): define declarative configuration M0 (#760)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-14 19:53:12 +00:00
jason.woltje 48b2bc42c9 docs(mos): format Option 2 qualification report (#762)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-14 19:33:09 +00:00
jason.woltje 5e832049bb docs(mos): preserve Option 2 qualification evidence (#759)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline failed
2026-07-14 19:16:11 +00:00
jason.woltje 49e8a54105 docs(#751): Publish native Kanban/SOT canon (#752)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-14 17:08:09 +00:00
jason.woltje d077183554 docs(tess): remediate M5 qualification findings (#750)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-07-13 19:59:38 +00:00
jason.woltje 405984af5a De-hardcode orchestrator and interaction agent names (#748)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-07-13 18:59:27 +00:00
jason.woltje 8dd4e9d541 docs(tess): ledger sync m4 — M5-003 done, #745/#746 merged (#749)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-13 18:14:24 +00:00
jason.woltje bc8016c831 docs(#744): complete Tess documentation gate (#746)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-13 17:44:17 +00:00
jason.woltje e72388b2cb docs(tess): ledger sync m3 — M5-001 + M5-002 done (#745)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-13 16:14:23 +00:00
jason.woltje 6345dbfcf2 feat(agent): add Matrix native runtime provider (#744)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-07-13 15:29:37 +00:00
jason.woltje c6e3cfbd95 docs(tess): ledger sync — W-001 3-of-3 merged, M5-002 approved, M5-001 in TDD (#743)
ci/woodpecker/push/publish Pipeline was canceled
ci/woodpecker/push/ci Pipeline was canceled
2026-07-13 15:29:33 +00:00
jason.woltje 5789711ee0 docs(tess): add migration evidence set (#742)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-13 15:14:29 +00:00
jason.woltje f40e6ba388 docs(tess): sync M4 tracking to merged reality (M4 in-progress / gate-pending) (#741)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-13 15:00:15 +00:00
jason.woltje b7b0f508e6 feat(gateway): register Hermes runtime provider (#740)
ci/woodpecker/push/publish Pipeline failed
ci/woodpecker/push/ci Pipeline was successful
2026-07-13 14:45:08 +00:00
jason.woltje 3378b857eb feat(memory): bind operator plugin to agent sessions (#739)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-07-13 13:44:20 +00:00
jason.woltje e2376190e5 feat(gateway): expose Mos coordination boundary (#737)
ci/woodpecker/push/ci Pipeline is pending
ci/woodpecker/push/publish Pipeline is pending
2026-07-13 13:14:44 +00:00
jason.woltje cca6aaf947 feat(agent): add Hermes transitional capability matrix (#738)
ci/woodpecker/push/ci Pipeline is pending
ci/woodpecker/push/publish Pipeline is pending
2026-07-13 13:14:43 +00:00
jason.woltje 2363f155b4 feat(memory): add operator retrieval plugin (#736)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-07-13 12:44:31 +00:00
jason.woltje 76325ca3f2 feat(tess): add Mos coordination boundary (#735)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-07-13 11:59:16 +00:00
jason.woltje 9e5b9188ce feat(agent): add transitional Hermes runtime adapter (#734)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was canceled
2026-07-13 11:29:27 +00:00
jason.woltje f1c6b37b46 fix(tess): route bare Discord approvals (#733)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-07-13 10:29:10 +00:00
jason.woltje 0b621660c8 feat(tess): wire durable interaction surfaces (#732)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was canceled
2026-07-13 10:05:29 +00:00
jason.woltje 84d884b932 feat(#709): add configured Discord interaction binding (#730)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-07-13 09:02:45 +00:00
jason.woltje 8246ee0137 feat(tess): add generic interaction CLI (#731)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-07-13 05:52:41 +00:00
jason.woltje 99a2d0fc9d feat(tess): persist durable session state (#729)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-07-13 05:14:11 +00:00
jason.woltje e3b5113be2 feat(tess): add configurable Pi interaction service (#728)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-13 02:59:27 +00:00
jason.woltje 24b07d0f83 docs(tess): sync M1 ledger to merged state; M1-V Mos-owned (#727)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-13 02:14:13 +00:00
jason.woltje 86a50138a9 feat(tess): add safe runtime observability (#726)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-07-13 01:29:18 +00:00
jason.woltje 7b9f40d3b7 fix(tess): redact chat persistence and egress (#725)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was canceled
2026-07-13 01:14:16 +00:00
jason.woltje 9a8a572fcf feat(tess): add roster-bound tmux fleet provider (#724)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was canceled
2026-07-13 00:59:24 +00:00
jason.woltje 753a360517 fix(#707): scope session GC retention (#720)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was canceled
2026-07-13 00:44:19 +00:00
jason.woltje e92186d768 feat: add Tess runtime provider registry (#722)
ci/woodpecker/push/ci-image Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-07-12 23:53:54 +00:00
jason.woltje 119f64e69d feat(#707): secure Discord service ingress (#716)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-07-12 23:18:29 +00:00
jason.woltje 46ca3ce742 fix(tess): enforce command authorization approvals (#718)
ci/woodpecker/push/publish Pipeline was canceled
ci/woodpecker/push/ci Pipeline was canceled
2026-07-12 23:18:01 +00:00
jason.woltje 227b73fcdf fix(security): enforce Tess MCP server identity (#717)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-07-12 22:49:00 +00:00
jason.woltje a959b1d6b4 ci: restrict push-event CI to protected branches (halve feature-branch load) (#721)
ci/woodpecker/push/ci Pipeline is pending
ci/woodpecker/push/publish Pipeline is pending
2026-07-12 22:48:58 +00:00
jason.woltje 62f8177806 feat(tess): define runtime provider contract (#719)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was canceled
2026-07-12 22:29:21 +00:00
jason.woltje 353e43c947 fix: enforce Tess session ownership scope (#715)
ci/woodpecker/push/ci Pipeline is pending
ci/woodpecker/push/publish Pipeline is pending
2026-07-12 22:14:17 +00:00
jason.woltje ca9c2b5c23 restore Tess markdown formatting gate (v2, non-author) (#714)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-07-12 21:32:16 +00:00
jason.woltje b580d37d51 Fixes #703 (#705)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline failed
2026-07-12 20:49:43 +00:00
jason.woltje 59e49cfd15 docs(tess): define Pi-native interaction agent mission (#712)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline failed
2026-07-12 18:09:54 +00:00
jason.woltje a99aded26d fix(tools/git): -h/--help now exits 0 across 7 wrappers (#702)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-11 09:23:46 +00:00
jason.woltje 4df38f7e81 fix(tools/_lib): /etc/mosaic host-level fallback for credential resolution (#700)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-10 01:57:12 +00:00
jason.woltje 193331544d fix(wizard): honor MOSAIC_GATEWAY_SKIP_NPM_INSTALL — unblock install.sh --dev gateway testing (#698)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-07-10 01:30:10 +00:00
jason.woltje 4e9e053800 fix(tools/tmux): unique per-invocation paste buffer; track auto-submit-drafts.sh (#697)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-09 17:37:40 +00:00
jason.woltje 851c67c27b docs(bootstrap): add python-is-python3 to agent-host prerequisites (#694)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-03 09:38:40 +00:00
jason.woltje 495f73bfdb fix(wizard): avoid rerunning completed setup steps (#692)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-25 18:44:35 +00:00
jason.woltje b96cc7982a fix(wizard): report gateway failures before success summary (#691)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-25 18:14:40 +00:00
jason.woltje 0883fb91ec fix(wizard): resolve skills sync script path (#690)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-25 17:35:19 +00:00
jason.woltje 56787fabf1 fix(gateway): disable Redis consumers on local tier (#689)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-25 17:17:24 +00:00
jason.woltjeandClaude Opus 4.8 940ae3cc41 feat(installer): prefer npm next lane (#688)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
--next now prefers a fast npm @next install (CLI + gateway from the Gitea registry) and falls back to source build at next if the dist-tag is unavailable. Registry lane gated to non-dev, non-explicit-ref next installs; CLI/gateway prerelease versions must share a pipeline suffix. Adds tools/install-next-lane.test.sh (wired into CI). PR-event CI 1635 fully green + review-of-record APPROVE (functional install test, head 2fd7cfc3).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-25 07:14:24 +00:00
jason.woltjeandClaude Opus 4.8 c25a551c28 ci(#462): add durable next publish pipeline (#687)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
Durable @next integration-line publish: on next pushes, compute <patch+1>-next.<pipeline#> prerelease versions (in-CI, uncommitted) and publish @mosaicstack/* under the next dist-tag; gateway image sha-only on next. Strict guardrails: next-only, never writes latest, never tags from next; main path unchanged. PR-event CI 1631 fully green + review-of-record APPROVE (head b1a887a2). Guardrails independently verified.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-25 05:45:09 +00:00
jason.woltjeandClaude Opus 4.8 94d6538061 feat(installer): add next integration lane (#686)
ci/woodpecker/push/ci Pipeline was successful
Add --next installer flag (build-from-source at the next integration branch; MOSAIC_NEXT=1 env equiv; explicit --ref wins). Three-lane install docs (stable @latest / --next prerelease / --dev source) + @next dist-tag pipeline design doc. Green PR-event CI 1626 + review-of-record APPROVE (head 3a5c12a5).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-25 05:14:32 +00:00
jason.woltjeandClaude Opus 4.8 a3c1ab923c test(#462): add federation M3 integration coverage (#685)
ci/woodpecker/push/ci Pipeline was successful
FED-M3-10 integration tests for the federation M3 verbs (list/get/scope). Test-infra + docs only; green PR-event CI 1623 (all steps incl ci-postgres).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-25 04:14:56 +00:00
jason.woltjeandClaude Opus 4.8 838701bde2 feat(#462): add federation get verb (#683)
ci/woodpecker/push/ci Pipeline was successful
FED-M3-06 get verb. Trust boundary mirrors M3-05 AND-intersect (note returned only when owned by subject AND on an authorized mission). Reviewed (review-of-record APPROVE, head 80a259b2) + green PR-event CI 1620.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-25 03:44:54 +00:00
jason.woltje 86e106fcc9 feat(#462): add federation list verb (#682)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-25 02:15:17 +00:00
jason.woltje 67135d3822 fix(fleet): guard mosaic fleet restart against tight-loop re-entry race (#680)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-25 01:44:48 +00:00
jason.woltje adb153428b feat(installer): --dev flag builds CLI + gateway from source (#681)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-24 23:54:52 +00:00
jason.woltje c739256a2c feat(#462): add federation scope enforcement service (#672)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-24 23:22:46 +00:00
jason.woltje fc2970916f style(federation): re-run prettier on TASKS.md (unblock format:check) (#679)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-24 22:51:22 +00:00
jason.woltje 79eae2ffce fix(fleet): raise fleet-personas.spec timeout to 30s (mirror #665) (#677)
ci/woodpecker/push/ci Pipeline is pending
ci/woodpecker/push/publish Pipeline is pending
2026-06-24 22:16:49 +00:00
jason.woltje 7035cd23bf docs(federation): record M3-07/09 merges + fix M3-11 dependency DAG (#678)
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish Pipeline was successful
2026-06-24 21:44:57 +00:00
jason.woltje 6b94d014a8 feat(#462): add federation capabilities verb (#674)
ci/woodpecker/push/ci Pipeline is pending
ci/woodpecker/push/publish Pipeline is pending
2026-06-24 21:39:56 +00:00
jason.woltje 838c44086c feat(#462): add federation query source routing (#673)
ci/woodpecker/push/ci Pipeline is pending
ci/woodpecker/push/publish Pipeline is pending
2026-06-24 21:39:45 +00:00
jason.woltje 3eeed04e17 docs(federation): sync M3 backlog to origin/main reality (#671)
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish Pipeline was successful
2026-06-24 21:15:14 +00:00
jason.woltje e0e7be70f5 chore(release): @mosaicstack/mosaic 0.0.48 (#670)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-24 20:01:17 +00:00
jason.woltje d7eaa19380 feat(fleet): provision roster from system-type profile (H3) (#665)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-24 19:48:54 +00:00
jason.woltje 248193cd3b fix(fleet): export MOSAIC_AGENT_CLASS into the agent pane so personas inject (#669)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-24 19:38:15 +00:00
jason.woltje a9857c5043 fix(release): republish @mosaicstack/db 0.0.4 with BacklogService; mosaic 0.0.47 (#668)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-24 18:54:33 +00:00
jason.woltje e4ede69144 chore(release): mosaic 0.0.46 (persona contracts live) (#666)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-24 18:29:01 +00:00
jason.woltje a8008138c8 docs(fleet): record per-agent model switch in north star (#667)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-24 18:14:37 +00:00
jason.woltje 0d17a29ebe feat(fleet): export MOSAIC_AGENT_CLASS into agent env (A3a) (#663)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-24 17:19:59 +00:00
jason.woltje 28cfecda94 feat(fleet): inject persona contract at launch (A3b) (#664)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline failed
2026-06-24 17:06:51 +00:00
jason.woltje 6c84ccd0b1 feat(fleet): dedicated orchestrator persona, split from planner (#662)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-24 16:42:23 +00:00
jason.woltje 84d2757817 feat(fleet): update-surviving persona customization (H4) (#661)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-24 16:21:01 +00:00
jason.woltje a738ac1410 feat(fleet): system-type profiles (H2) (#660)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-24 16:02:25 +00:00
jason.woltje 538f0556d5 feat(fleet): cross-domain baseline persona library (H1) (#659)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-24 15:31:56 +00:00
jason.woltje a094c86eea feat(fleet): North Star scope — general-purpose system, personas & system profiles (workstream H) (#658)
ci/woodpecker/push/publish Pipeline was canceled
ci/woodpecker/push/ci Pipeline was canceled
2026-06-24 15:25:57 +00:00
jason.woltje f852250419 feat(fleet): native Mosaic backlog on @mosaicstack/db (atomic claim + TTL) (#657)
ci/woodpecker/push/ci-image Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was canceled
2026-06-24 14:55:10 +00:00
jason.woltje 61b1bdac2a feat(fleet): add machine-readable NORTH_STAR.yaml + Markdown projection (#656)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-24 14:40:09 +00:00
jason.woltje cabb179d5a feat(fleet): seed role registry markdown library (#655)
ci/woodpecker/push/publish Pipeline was canceled
ci/woodpecker/push/ci Pipeline was canceled
2026-06-24 14:39:54 +00:00
jason.woltje eb795bab18 chore(release): mosaic CLI 0.0.45 (#654)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-24 14:11:33 +00:00
jason.woltje 937077f6be fix(fleet): report idle agents as available, reserve stuck for genuine blocks (#653)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-24 13:58:22 +00:00
jason.woltje 1020cfaf9b chore(release): mosaic CLI 0.0.44 (#652)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-24 06:49:04 +00:00
jason.woltje 70661e3fab fix(fleet): derive pane idle from window activity fallback (#651)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-24 06:37:45 +00:00
jason.woltje ec8dd7ca86 chore(release): mosaic CLI 0.0.43 (#650)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-24 06:08:20 +00:00
jason.woltje d887555852 feat(fleet): classify agent readiness in fleet ps (#649)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-24 05:55:47 +00:00
jason.woltje e3adc6a1bc chore(release): mosaic CLI 0.0.42 (#648)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-24 05:28:28 +00:00
jason.woltje aa27c42129 fix(fleet): pre-trust claude agent workdir to clear the folder-trust gate (#644) (#645)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-24 05:16:46 +00:00
jason.woltje 16ae809442 fix(update): re-seed framework on version drift, not just in-command updates (#642) (#646)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-24 05:04:34 +00:00
jason.woltje 6980e40e51 fix(db): stop pglite migration tests flaking CI (timeout + WASM OOM) (#647)
ci/woodpecker/push/ci Pipeline was canceled
ci/woodpecker/push/publish Pipeline was canceled
2026-06-24 05:04:28 +00:00
jason.woltje e6b53ea103 fix(tools): default AGENT_WORK_ROOT to $HOME/mosaic/agent-work (#641)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was canceled
2026-06-23 13:40:13 +00:00
jason.woltje 4da87640e8 feat(tmux): agent-send.sh --class triage tag for the comms daemon (#552)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-23 03:25:16 +00:00
jason.woltje a38a491403 chore(release): mosaic CLI 0.0.41 (#640)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-23 02:21:04 +00:00
jason.woltje 78d67c6261 chore(ci): bump ci-base image node 22 → 24-alpine (#639)
ci/woodpecker/push/ci-image Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-23 00:59:39 +00:00
jason.woltje 94e5cd7a81 ci: eliminate cold pnpm install via pre-baked CI base image (Phase 1) (#635)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-22 22:50:21 +00:00
jason.woltje 4e84f8e850 feat(fleet): comms-block emitter + FLEET-LAUNCH runbook (#633) (#638)
ci/woodpecker/push/ci Pipeline was canceled
ci/woodpecker/push/publish Pipeline was canceled
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-22 22:23:50 +00:00
jason.woltje cf8ceb3095 CI: add pre-baked ci-base image (producer) [Phase 1a] (#637)
ci/woodpecker/push/ci-image Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was canceled
2026-06-22 22:20:48 +00:00
jason.woltje bf2a6745c8 fix(install): preserve user fleet data on re-seed + refresh active units (CRITICAL) (#632)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-22 21:38:09 +00:00
jason.woltje d539d61e0e refactor(fleet): rename tmux socket mosaic-factory → mosaic-fleet (#630)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-22 21:08:43 +00:00
jason.woltje 3f69d45334 docs(fleet): consolidate north-star doctrine (budget + control plane + identity) (#629)
ci/woodpecker/push/publish Pipeline was canceled
ci/woodpecker/push/ci Pipeline was canceled
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-22 21:08:41 +00:00
jason.woltje e2336bb0ca chore(release): mosaic CLI 0.0.40 (#624)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-06-22 19:49:45 +00:00
jason.woltje 7342415a32 fix(fleet): consume model_hint + fix socket-default trap (stand-up fixes) (#627)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-22 19:18:01 +00:00
jason.woltje 095e19443b feat(fleet): onboarding-injection — comms cheat-sheet + peer roster per agent (#621)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-22 17:54:54 +00:00
jason.woltje fabc413407 feat(fleet): F4 Phase 2a — Matrix CS-API connector client + factory (#618)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-22 16:48:17 +00:00
jason.woltje 858d90329d feat(fleet): F4 Phase 1 — chat connector abstraction + Matrix design (#617)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-22 16:14:32 +00:00
jason.woltje 2bf66136e4 feat(fleet): enhancer role + two-agent floor (orchestrator + enhancer) (#615)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-22 13:15:59 +00:00
jason.woltje 4434c3c481 docs(fleet): orchestrator+enhancer two-agent floor + role library + Discord plugin north-star (#613)
ci/woodpecker/push/publish Pipeline was canceled
ci/woodpecker/push/ci Pipeline was canceled
2026-06-22 13:15:05 +00:00
jason.woltje dd0a0d38c6 ci(publish): gate kaniko image builds + publish on changed paths (CI throughput) (#619)
ci/woodpecker/push/publish Pipeline was canceled
ci/woodpecker/push/ci Pipeline was canceled
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-22 13:14:31 +00:00
jason.woltje d46ac40890 fix(fleet): boot-survival symmetry — disable-on-remove + add-enable + init-R5 (#612)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-22 08:12:58 +00:00
jason.woltje 8ddd48c843 feat(mosaic): mosaic update re-seeds framework + relaunches agents (R13) (#610)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-22 03:34:05 +00:00
jason.woltje 528700ceea feat(framework): P6 — docs + compliance matrix + resident-budget CI (#607)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was canceled
ci/woodpecker/tag/publish Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-22 02:20:35 +00:00
jason.woltje 32f4215461 chore(release): bump @mosaicstack/mosaic 0.0.38 -> 0.0.39 (#608)
ci/woodpecker/push/ci Pipeline is pending
ci/woodpecker/push/publish Pipeline is pending
2026-06-22 02:16:12 +00:00
jason.woltje 23343bb7f0 feat(mosaic): P5 — overlay composer (compose-contract + *.local overlays) (#605)
ci/woodpecker/push/ci Pipeline is pending
ci/woodpecker/push/publish Pipeline is pending
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-22 02:16:05 +00:00
jason.woltje c8b2dab0ca chore(release): bump @mosaicstack/mosaic 0.0.37 -> 0.0.38 (#603)
ci/woodpecker/push/publish Pipeline was canceled
ci/woodpecker/push/ci Pipeline was canceled
2026-06-22 01:48:27 +00:00
jason.woltje 6dbe452a9f fix(fleet): watch viewer-session leak + workdir test settle-race (#601)
ci/woodpecker/push/publish Pipeline was canceled
ci/woodpecker/push/ci Pipeline was canceled
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-22 01:43:21 +00:00
jason.woltje 59c755067e feat(fleet): F3-m2 — native Pi heartbeat + model surface + mosaic_mission_status tool (#602)
ci/woodpecker/push/ci Pipeline is pending
ci/woodpecker/push/publish Pipeline is pending
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-22 01:43:18 +00:00
jason.woltje 6ffb27787e fix(fleet): complete HB reader/writer consistency + sidecar hardening (#599)
ci/woodpecker/push/ci Pipeline was canceled
ci/woodpecker/push/publish Pipeline was canceled
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-22 01:22:35 +00:00
jason.woltje 130837365f chore(release): bump @mosaicstack/mosaic 0.0.36 -> 0.0.37 (#597)
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish Pipeline was successful
2026-06-21 23:27:14 +00:00
jason.woltje 67df06f1c4 feat(fleet): orchestrator-mutable fleet — fleet add/remove (F5/R9) (#596)
ci/woodpecker/push/ci Pipeline is pending
ci/woodpecker/push/publish Pipeline is pending
2026-06-21 23:26:21 +00:00
jason.woltje 60a309d5a4 fix(fleet): heartbeat consistency — MOSAIC_HOME path + configurable interval (#595)
ci/woodpecker/push/ci Pipeline is pending
ci/woodpecker/push/publish Pipeline is pending
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-21 23:25:53 +00:00
jason.woltje 2dc0f24828 docs(fleet): Fleet Suite PRD (init/configure/operate + Mos-on-Discord) (#588)
ci/woodpecker/push/ci Pipeline is pending
ci/woodpecker/push/publish Pipeline is pending
2026-06-21 23:17:10 +00:00
jason.woltje 31e7a4d25e docs(framework): P4.1 — fix stale install.sh comments + cmp-equal early-exit (#593)
ci/woodpecker/push/ci Pipeline is pending
ci/woodpecker/push/publish Pipeline is pending
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-21 23:12:31 +00:00
jason.woltje ca19d57bba feat(fleet): config-type presets + AI-free init wizard (F1) (#591)
ci/woodpecker/push/ci Pipeline is pending
ci/woodpecker/push/publish Pipeline is pending
2026-06-21 23:07:41 +00:00
jason.woltje bb7d549080 feat(framework): P4 — upgrade-safe Constitution migration (both installers) (#590)
ci/woodpecker/push/ci Pipeline is pending
ci/woodpecker/push/publish Pipeline is pending
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-21 23:03:48 +00:00
jason.woltje 5bef2c35eb feat(fleet): fleet ps surfaces unmanaged socket sessions (#586)
ci/woodpecker/push/ci Pipeline was canceled
ci/woodpecker/push/publish Pipeline was canceled
2026-06-21 22:37:34 +00:00
jason.woltje 2849a8f9db chore(release): bump @mosaicstack/mosaic 0.0.35 -> 0.0.36 (#585)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-21 21:46:15 +00:00
jason.woltje 7ced5588c9 feat(fleet): launcher heartbeat sidecar — HB for all runtimes (pi/claude/codex) (#584)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was canceled
2026-06-21 21:14:20 +00:00
jason.woltje afcbbb302f feat(fleet): auto-enable units on install + drift recognizes wrapped runtimes (#583)
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish Pipeline was successful
2026-06-21 20:02:19 +00:00
jason.woltje c2c0b5fe8d chore(release): bump @mosaicstack/mosaic 0.0.34 -> 0.0.35 (#582)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-21 18:59:39 +00:00
jason.woltje c9cfe36204 docs(framework): P3.1 fast-follow — governance wording + gate scope + bare-launch note (#577)
ci/woodpecker/push/ci Pipeline was canceled
ci/woodpecker/push/publish Pipeline was canceled
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-21 18:56:50 +00:00
jason.woltje fc90c89913 fix(fleet): durable runtime PATH for detached agent launch (#581)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-21 17:30:40 +00:00
jason.woltje af2eede7a9 feat(fleet): Phase-2 observability — fleet ps + watch + send verify (#579)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-21 04:23:51 +00:00
jason.woltje 5118be74cb feat(framework): P3 — extract Constitution (L0) + gut AGENTS dispatcher (#575)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-21 03:20:32 +00:00
jason.woltje bf24066a49 feat(framework): P1+P2 — public sanitization + blocking CI gate (#572)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-21 02:40:11 +00:00
jason.woltje 92316ab41e feat(framework): P0 — MIT license + executable-leak sanitization (#570)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-21 01:43:49 +00:00
jason.woltje b354bc8fae docs(framework): add agency & persistence patterns to config + guides (#543)
ci/woodpecker/push/ci Pipeline was canceled
ci/woodpecker/push/publish Pipeline was canceled
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-06-21 01:43:36 +00:00
jason.woltje e834bbb83c fix(fleet): install executable tmux helpers (#568)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-20 22:27:46 +00:00
jason.woltje 7498fcb20d fix(fleet): preserve agent env overrides on install (#567)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-20 21:50:46 +00:00
jason.woltje 42d081613f chore(release): bump mosaic cli to 0.0.32 (#566)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-20 21:15:25 +00:00
jason.woltje b5c1381e45 fix(fleet): harden operator sends for release (#565)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-20 20:41:11 +00:00
jason.woltje 6dfd78f643 feat(fleet): add local canary CLI (#563)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-20 17:49:01 +00:00
jason.woltje 45e2c2aad8 docs: plan durable tmux fleet install (#557)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-20 16:19:19 +00:00
jason.woltje 57919c38d8 fix(framework/tools): wrapper hardening — TLS validation, cred-path fallback, no-CI fast-exit (#551)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-20 10:16:38 +00:00
jason.woltje 87f561c1f8 fix(launch): include Pi native skill roots in 'all' mode; dedup 'discover' force-loads (#556)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-19 19:58:09 +00:00
jason.woltje 8c45857859 feat(launch): force-load fleet-critical Pi skills + reconcile skill docs (#555)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-19 18:31:02 +00:00
jason.woltje 63e77887a8 feat: add mosaic-tools skill (fleet toolkit fast path) (#1) 2026-06-19 18:30:58 +00:00
jason.woltje 605221d42f docs(framework/tools): lead TOOLS.md with high-salience fleet-tools cheatsheet (#554)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was canceled
2026-06-19 18:03:03 +00:00
jason.woltje ee584ab48c fix(framework/tools): prettier-format woodpecker README — restore main format gate (#553)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-18 22:39:35 +00:00
jason.woltje ab4e138003 feat(framework/tools): orchestration helpers — lane-brief.sh + ci-wait.sh (#547)
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish Pipeline was canceled
2026-06-18 22:08:40 +00:00
jason.woltje 719c6ac3db fix(framework/tools): eval injection, broken JSON, tmpfile leak (#549)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was canceled
2026-06-18 21:35:32 +00:00
jason.woltje b8807e60df feat(agent-reflection): durable kernel — reflection.v1 capture + risk-floor + Phase-0 (#545)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-16 21:35:40 +00:00
jason.woltje c461380a4a feat(mosaic-as): agent registration + scoped/revocable tokens (US-007) (#541)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-16 01:10:44 +00:00
jason.woltje 98a771c8f8 Fix Gitea wrapper login resolution (#538)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-12 02:34:18 +00:00
jason.woltje bd9527c033 docs(framework): canonize merge-authority policy (hard gate 13 + E2E gate note) (#537)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-11 23:56:20 +00:00
jason.woltje aa221bf92e release(mosaic): bump @mosaicstack/mosaic 0.0.30 -> 0.0.31 (#534)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/tag/publish Pipeline was successful
2026-06-11 19:55:43 +00:00
jason.woltje 799df40f4e feat(appservice): room provisioning (M4c) (#535)
ci/woodpecker/push/publish Pipeline was canceled
ci/woodpecker/push/ci Pipeline was canceled
2026-06-11 19:50:55 +00:00
jason.woltje b79e9f32c6 chore(framework): canonize Vault-as-SSOT + ESO-default secrets policy (#519)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-11 19:07:00 +00:00
jason.woltje 89d69eb23b docs: add mission control and coordination resilience docs (#511)
ci/woodpecker/push/ci Pipeline was canceled
ci/woodpecker/push/publish Pipeline was canceled
2026-06-11 19:06:35 +00:00
jason.woltje 59b611ba8a refactor(framework): thin-core prompt diet — cut injected contract ~53% (#529)
ci/woodpecker/push/ci Pipeline was canceled
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
2026-06-11 18:10:42 +00:00
jason.woltje dfa0be42f6 feat(framework/tools): inter-agent tmux comms — agent-send.sh + addressing standard (#533)
ci/woodpecker/push/ci Pipeline was canceled
ci/woodpecker/push/publish Pipeline was canceled
2026-06-11 18:01:44 +00:00
jason.woltje bb96a3f23e ci: publish mosaic-as appservice image (#532)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-10 23:00:38 +00:00
jason.woltje 48b2f28e45 feat(appservice): mosaic-as daemon host + container (M4a) (#531)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-10 22:16:28 +00:00
jason.woltje 8f09c910a9 feat(appservice): Matrix Application Service core library (M4a) (#530)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-10 21:23:25 +00:00
jason.woltje dde95a59b3 fix(pi): reduce startup skill-token overhead (#527)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-06-05 18:36:42 +00:00
jason.woltje 821e19dcbb fix(mosaic-tools): roll up Gitea and Woodpecker wrapper fixes (#524)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-05-26 20:56:09 +00:00
jason.woltje 755df9079e Merge pull request 'fix(db): bootstrap migrations on local-tier gateway startup' (#510) from fix/db-bootstrap-migrations into main 2026-05-04 22:13:14 +00:00
jason.woltjeandClaude Opus 4.7 ac5650d9f9 fix(db): bootstrap migrations on local-tier gateway startup
Fresh `mosaic gateway install` (npm) left the gateway DB schema empty —
sign-in 500'd with `relation "users" does not exist`, and every entry
point (auth, bootstrap setup) failed because they all query the users
table first. Five stacked bugs on the local (PGlite) tier:

1. `packages/db/package.json` `files: ["dist"]` excluded the `drizzle/`
   SQL migrations from the published tarball.
2. `runMigrations()` only supports postgres-js — unusable for embedded
   PGlite.
3. `apps/gateway/src/database/database.module.ts` never invoked
   migrations at startup.
4. `createPgliteDb` didn't load pgvector, so migration 0001's
   `CREATE EXTENSION vector` failed.
5. Drizzle's PG migrator wraps every migration in one outer
   transaction, which trips Postgres' `check_safe_enum_use` on
   migration 0009 (`ALTER TYPE ADD VALUE 'pending'` → `SET DEFAULT
   'pending'` in the same tx).

Changes:
- Ship `drizzle/` in the published tarball.
- `createPgliteDb` loads `@electric-sql/pglite/vector`.
- New `runPgliteMigrations(handle)` walks the Drizzle journal and
  runs each statement-breakpoint chunk through PGlite's `client.exec()`
  (autocommit per statement). Records into `drizzle.__drizzle_migrations`
  for interop with the postgres-js path. Per-statement try/catch
  surfaces which statement of which migration failed.
- `DatabaseModule` runs migrations in `OnModuleInit` before
  `app.listen()`. Local tier: explicit `runPgliteMigrations` then
  `storageAdapter.migrate()`. Postgres tier: just `storageAdapter.migrate()`,
  which already calls `runMigrations(url)` internally — no double-call.
- Removed `packages/storage/src/test-utils/pglite-with-vector.ts`. The
  "intentionally not exported" rationale is moot now that migration
  0001 forces pgvector load anyway. The integration test uses
  `createPgliteDb` + `runPgliteMigrations` from `@mosaicstack/db`.

Tests: BetterAuth tables exist after migrate; idempotent (re-runs 0009);
partial-failure surfaces statement-level context and leaves no ledger row.

QA on a fresh PGlite install:
- `Applying PGlite schema migrations...` then `Initializing storage
  adapter (pglite)...` in startup log.
- `GET /api/bootstrap/status` → `{"needsSetup":true}` HTTP 200 (was 500).
- `POST /api/bootstrap/setup` reaches Zod validator (was 500).

Scope: this PR fixes the local (PGlite) tier. Postgres-tier first
install still has the outer-transaction problem and a journal ordering
bug (0009's `when` < 0008's). Documented inline as TODO and in the
scratchpad — needs a separate change with real-Postgres validation.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-04 17:06:50 -05:00
jason.woltje bd83f86740 Merge pull request 'feat(federation): mTLS AuthGuard with OID-based grant resolution (FED-M3-03)' (#509) from feat/federation-m3-auth-guard into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-25 13:27:20 +00:00
jarvisandClaude Sonnet 4.6 0af3e218a1 fix(federation/auth-guard): remediate CRIT-1/CRIT-2 + HIGH-1..4 review findings
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
- CRIT-1: Validate cert subjectUserId against grant.subjectUserId from DB;
  use authoritative DB value in FederationContext
- CRIT-2: Add @Inject(GrantsService) decorator (tsx/esbuild requirement)
- HIGH-1: Validate UTF8String TLV tag, length, and bounds in OID parser
- HIGH-2: Collapse all 403 wire messages to a generic string to prevent
  grant enumeration; keep internal logger detail
- HIGH-3: Assert federation wire envelope shape in all guard tests
- HIGH-4: Regression test for subjectUserId cert/DB mismatch

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-25 06:33:37 -05:00
jarvisandClaude Sonnet 4.6 b01c9b3bb0 feat(federation): mTLS AuthGuard with OID-based grant resolution (FED-M3-03)
Adds FederationAuthGuard that validates inbound mTLS client certs on
federation API routes. Extracts custom OIDs (grantId, subjectUserId),
loads the grant+peer from DB in one query, asserts active status, and
validates cert serial as defense-in-depth. Attaches FederationContext
to requests on success and uses federation wire-format error envelopes
(not raw NestJS exceptions) for 401/403 responses.

New files:
- apps/gateway/src/federation/oid.util.ts — shared OID extraction (no dupe ASN.1 logic)
- apps/gateway/src/federation/server/federation-auth.guard.ts — guard impl
- apps/gateway/src/federation/server/federation-context.ts — FederationContext type + module augment
- apps/gateway/src/federation/server/index.ts — barrel export
- apps/gateway/src/federation/server/__tests__/federation-auth.guard.spec.ts — 11 unit tests

Modified:
- apps/gateway/src/federation/grants.service.ts — adds getGrantWithPeer() with join
- apps/gateway/src/federation/federation.module.ts — registers FederationAuthGuard as provider

Closes #462

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-25 06:33:37 -05:00
jason.woltje b67f2c9f08 Merge pull request 'feat(federation): outbound mTLS FederationClient (FED-M3-08)' (#508) from feat/federation-m3-client into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-24 04:30:29 +00:00
jarvisandClaude Sonnet 4.6 37675ae3f2 fix(federation/client): serialize cache fills, destroy evicted Agent, cover env-var guard
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
- HIGH-A: resolveEntry now uses promise-cache pattern so concurrent
  callers serialize on a single in-flight build, eliminating duplicate
  key material in heap and duplicate DB round-trips
- HIGH-B: flushPeer destroys the evicted undici Agent so stale TLS
  connections close on cert rotation
- MED-C: add regression test for PEER_MISCONFIGURED when
  STEP_CA_ROOT_CERT_PATH is unset

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-23 22:56:57 -05:00
jarvisandClaude Opus 4.7 a4a6769a6d fix(federation/client): pin Step-CA root, fix lockfile, harden cache test
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
CRIT-1: regenerate pnpm-lock.yaml so apps/gateway resolves [email protected].6
(prior PR pushed package.json without lockfile update; CI failed with
ERR_PNPM_OUTDATED_LOCKFILE). Incidentally cleans 57 lines of stale
peer-dep entries.

CRIT-2: cache-hit test no longer swallows resolveEntry errors. Calls the
private method directly twice and asserts identity equality plus a
single DB select, removing the silent-failure path the prior assertion
allowed.

HIGH-1: mTLS Agent now pins Step-CA root via STEP_CA_ROOT_CERT_PATH.
Without the env var resolveEntry throws PEER_MISCONFIGURED, refusing to
dial peers against the public trust store. PEM is read once and cached
on the service instance.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-04-23 22:30:09 -05:00
jarvisandClaude Sonnet 4.6 21650fb194 feat(federation): outbound mTLS FederationClient (FED-M3-08)
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
Implements FederationClientService — a NestJS injectable that dials peer
gateways over mTLS (undici Agent with cert+sealed-key from federation_peers),
invokes list/get/capabilities verbs, validates responses via Zod, and surfaces
all failure modes as typed FederationClientError with a coherent error code
taxonomy (PEER_NOT_FOUND, PEER_INACTIVE, PEER_MISCONFIGURED, NETWORK,
FORBIDDEN, HTTP_{status}, INVALID_RESPONSE).

Per-peer Agent instances are cached in a Map for the service lifetime;
flushPeer(peerId) invalidates the cache for M5/M6 cert rotation and
revocation events.

Wired into FederationModule providers + exports so QuerySourceService
(M3-09) can inject it.

13 unit tests covering all required scenarios via undici MockAgent +
real sealClientKey/unsealClientKey round-trip.

Closes #462

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-23 22:16:52 -05:00
jason.woltje 89c733e0b9 feat(federation): two-gateway test harness scaffold (FED-M3-02) (#505)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-24 03:01:25 +00:00
jason.woltje ee3f2defd9 feat(types): federation v1 DTOs (FED-M3-01) (#506)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-24 02:54:40 +00:00
jason.woltje 7342c1290d fix(federation): use real PEM certs in enrollment + ca service tests (#507)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-24 02:43:42 +00:00
jason.woltje e64ddd2c1c docs(federation): M3 mission planning — 14-task decomposition (#504)
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish Pipeline was successful
2026-04-24 01:13:40 +00:00
jason.woltje 4ece6dc643 chore(federation): M2 milestone close (FED-M2-13) (#503)
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/tag/publish Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-22 06:09:54 +00:00
jason.woltje 194c3b603e docs(federation): M2 Step-CA setup guide + admin CLI reference (FED-M2-12) (#502)
ci/woodpecker/push/publish Pipeline failed
ci/woodpecker/push/ci Pipeline failed
2026-04-22 06:06:45 +00:00
jason.woltje fc1600b738 fix(federation): security hardening — OID verification, atomic activation, audit on failure (#501)
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish Pipeline failed
2026-04-22 06:02:52 +00:00
jason.woltje 0ee5b14c68 test(federation): M2 E2E peer-add enrollment flow (FED-M2-10) (#500)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-22 05:37:06 +00:00
jason.woltje 3eee176cc3 test(federation): M2 integration tests (FED-M2-09) (#499)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-22 05:08:24 +00:00
jason.woltje 74fe60d8d6 feat(federation): admin controller + CLI federation commands (FED-M2-08) (#498)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-22 04:39:46 +00:00
jason.woltje 0bfaa56e9e feat(federation): enrollment controller + single-use token flow (FED-M2-07) (#497)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-22 04:23:19 +00:00
jason.woltje 01dd6b9fa1 feat(federation): grants service CRUD + status transitions (FED-M2-06) (#496)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-22 03:57:12 +00:00
jason.woltje 1038ae76e1 feat(federation): Step-CA client service for grant certs (FED-M2-04) (#494)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-22 03:34:37 +00:00
jason.woltje bf082d95a0 feat(federation): seal federation peer client keys at rest (FED-M2-05) (#495)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-22 03:10:20 +00:00
jason.woltje bb24292cf7 fix(federation): healthcheck + restart policy for federated-test stacks (#492)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-22 02:56:40 +00:00
jason.woltje f2cda52e1a fix(deploy): bump gateway image digest to sha-9f1a081 [DEPLOY-IMG-FIX] (#491)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
2026-04-22 02:35:19 +00:00
jason.woltje 7d7cf012f0 feat(federation): scope schema validator [FED-M2-03] (#489)
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish Pipeline failed
2026-04-22 02:31:13 +00:00
jason.woltje c56dda74aa feat(federation): Step-CA sidecar in federated compose [FED-M2-02] (#490)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-22 02:21:49 +00:00
jason.woltje 9f1a08185e docs(federation): S21 tracking — DEPLOY-01/02 done, IMG-FIX in flight, M2-01 in remediation (#487)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-22 02:02:36 +00:00
jason.woltje d2e408656b fix(docker): pnpm deploy for self-contained gateway runtime image (#488)
ci/woodpecker/push/publish Pipeline failed
ci/woodpecker/push/ci Pipeline failed
2026-04-22 02:02:29 +00:00
jason.woltje 54c278b871 feat(db): federation schema — grants/peers/audit_log [FED-M2-01] (#486)
ci/woodpecker/push/publish Pipeline failed
ci/woodpecker/push/ci Pipeline failed
2026-04-22 02:02:21 +00:00
jason.woltje 4dbd429203 feat(deploy): portainer stack template for federation test instances [DEPLOY-02] (#485)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
2026-04-22 01:34:44 +00:00
jason.woltje b985d7bfe2 docs(federation): M2 mission planning — TASKS decomposition + manifest update (#483)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-22 01:24:00 +00:00
jason.woltje 45e8f02c91 feat(mosaic-portainer): PORTAINER_INSECURE flag for self-signed TLS (#484)
ci/woodpecker/push/publish Pipeline failed
ci/woodpecker/push/ci Pipeline failed
2026-04-22 01:21:54 +00:00
jason.woltje 54c422ab06 Merge pull request 'docs(federation): close FED-M1 milestone' (#481) from feat/federation-m1-close into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/tag/publish Pipeline was successful
2026-04-20 02:20:43 +00:00
jarvis b9fb8aab57 docs(federation): close FED-M1 milestone
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
- TASKS.md: mark FED-M1-12 done with PR/issue/tag references
- MISSION-MANIFEST.md: phase=M1 complete, progress 1/7, M1 row done with PR range #470-#481, session log appended
- scratchpad: Session 19 entry covering M1-09 → M1-12 with PR ledger and M1 retrospective learnings

Refs #460
2026-04-19 21:12:52 -05:00
jason.woltje 78841f228a docs(federation): operator setup + migration guides (FED-M1-11) (#480)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-20 02:07:15 +00:00
jason.woltje dc4afee848 fix(storage): redact credentials in driver errors + advisory lock (FED-M1-10) (#479)
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish Pipeline failed
2026-04-20 02:02:57 +00:00
jason.woltje 1e2b8ac8de test(federation): standalone regression canary — no breakage from M1 (FED-M1-09) (#478)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-20 01:46:35 +00:00
jason.woltje 15d849c166 test(storage): integration test for migrate-tier (FED-M1-08) + camelCase column fix (#477)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-20 01:40:02 +00:00
jason.woltje 78251d4af8 test(federation): integration tests for federated tier gateway boot (FED-M1-07) (#476)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-20 01:13:10 +00:00
jason.woltje 1a4b1ebbf1 feat(gateway,storage): mosaic gateway doctor with tier health JSON (FED-M1-06) (#475)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-20 01:00:39 +00:00
jason.woltje ccad30dd27 feat(storage): mosaic storage migrate-tier with dry-run + idempotency (FED-M1-05) (#474)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-20 00:35:08 +00:00
jason.woltje 4c2b177eab feat(gateway): tier-detector with fail-fast PG/Valkey/pgvector probes (FED-M1-04) (#473)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-20 00:07:07 +00:00
jason.woltje 58169f9979 feat(storage): pgvector adapter support gated on tier=federated (FED-M1-03) (#472)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-19 23:42:18 +00:00
jason.woltje 51402bdb6d feat(infra): docker-compose.federated.yml overlay (FED-M1-02) (#471)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-19 23:21:31 +00:00
jason.woltje 9c89c32684 feat(config): add federated tier + rename team→standalone (FED-M1-01) (#470)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-19 23:11:11 +00:00
jason.woltje 8aabb8c5b2 docs(mission): author MVP rollup manifest, archive install-ux-v2 (#469)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-19 22:51:11 +00:00
jason.woltje 66512550df docs(federation): PRD, milestones, mission manifest, and M1 task breakdown (#468)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-19 22:27:09 +00:00
jason.woltje 46dd799548 docs(federation): PRD, milestones, mission manifest, and M1 task breakdown (#467)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-04-19 22:09:20 +00:00
jason.woltje 5f03c05523 chore(release): @mosaicstack/mosaic 0.0.30 (#459)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-04-12 02:18:17 +00:00
jason.woltje c3f810bbd1 fix(mosaic): seed TOOLS.md from defaults on install (#458)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-04-12 02:02:21 +00:00
jason.woltje b2cbf898d7 docs(scratchpad): finalize yolo runtime hotfix evidence (#456)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
Follow-up to mosaicstack/stack#455.

Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-04-11 17:14:00 +00:00
jason.woltje b2cec8c6ba fix(mosaic): stop yolo runtime from leaking runtime name as first user message (#455)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
Fixes mosaicstack/stack#454

Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-04-11 16:57:43 +00:00
jason.woltje 81c1775a03 chore(release): @mosaicstack/mosaic 0.0.29 (#453)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/tag/publish Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-04-08 00:42:54 +00:00
jason.woltje f64ec12f39 fix(installer): preserve credentials dir and seed STANDARDS.md (#452)
ci/woodpecker/push/publish Pipeline failed
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-04-08 00:40:49 +00:00
jason.woltje 026382325c feat(framework): superpowers enforcement, typecheck hook, file-ownership rules (#451)
ci/woodpecker/manual/ci Pipeline was successful
ci/woodpecker/manual/publish Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-04-07 00:44:22 +00:00
jason.woltje 1bfd8570d6 chore(release): @mosaicstack/mosaic 0.0.28 (#450) 2026-04-06 00:46:31 +00:00
jason.woltje 312acd8bad chore: sweep mosaicstack/mosaic-stack → mosaicstack/stack + add short install URL (#448) 2026-04-06 00:39:56 +00:00
jason.woltje d08b969918 fix(mosaic): mask password input in TUI login prompt (#449) 2026-04-06 00:33:54 +00:00
jason.woltje 051de0d8a9 docs: update README for mosaicstack/stack repo rename (#447) 2026-04-06 00:22:20 +00:00
jason.woltje bd76df1a50 feat(mosaic): drill-down main menu + provider-first flow + quick start (#446) 2026-04-06 00:15:23 +00:00
jason.woltje 62b2ce2da1 docs: orchestrator close-out IUV-M02 (#445)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 23:50:55 +00:00
jason.woltje 172bacb30f feat(mosaic): IUV-M02 — CORS/FQDN UX polish + skill installer rework (#444)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-05 23:44:07 +00:00
jason.woltje 43667d7349 docs: orchestrator close-out IUV-M01 — mark tasks done, append session 2 (#443)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 22:40:08 +00:00
jason.woltje 783884376c docs: mark IUV-M01 complete — mosaic-v0.0.26 released (#436) (#442)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-05 22:31:37 +00:00
jason.woltje c08aa6fa46 fix: add vitest.config.ts to eslint allowDefaultProject (#440 build fix) (#441)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/tag/publish Pipeline was successful
2026-04-05 22:01:57 +00:00
jason.woltje 0ae932ab34 fix: bootstrap hotfix — DTO erasure, wizard failure, port prefill, Pi SDK copy (mosaic-v0.0.26) (#440)
ci/woodpecker/push/publish Pipeline failed
ci/woodpecker/push/ci Pipeline was successful
2026-04-05 21:43:30 +00:00
jason.woltje a8cd52e88c docs: scaffold install-ux-v2 mission (#439)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 21:27:19 +00:00
jason.woltje a4c94d9a90 chore(release): @mosaicstack/mosaic 0.0.25 (#435)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/tag/publish Pipeline was successful
2026-04-05 20:53:19 +00:00
jason.woltje cee838d22e docs: close out install-ux-hardening mission (#434)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 19:19:54 +00:00
jason.woltje 732f8a49cf feat: unified first-run flow — merge wizard + gateway install (IUH-M03) (#433)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-05 19:13:02 +00:00
jason.woltje be917e2496 docs: mark IUH-M02 complete, start IUH-M03 (#432)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 18:02:21 +00:00
jason.woltje cd8b1f666d feat: wizard remediation — password mask, hooks preview, headless (IUH-M02) (#431)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 17:47:53 +00:00
jason.woltje 8fa5995bde docs: scaffold install-ux-hardening mission + archive cli-unification (#430)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 17:15:39 +00:00
jason.woltje 25cada7735 feat: mosaic uninstall (IUH-M01) (#429)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 17:06:21 +00:00
jason.woltje be6553101c docs: finalize CLI unification mission at mosaic-v0.0.24 (#424)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 14:54:48 +00:00
jason.woltje 417805f330 fix: bump memory/queue/storage to 0.0.4 to force republish (#423)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/tag/publish Pipeline was successful
2026-04-05 14:39:15 +00:00
jason.woltje 2472ce52e8 fix: bump stale sub-package versions (brain/forge/log) (#422)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 14:26:30 +00:00
jason.woltje 597eb232d7 fix: revert mosaic to 0.0.22 alpha + republish macp (#421)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 14:15:46 +00:00
jason.woltje afe997db82 docs: mission cli-unification-20260404 complete (#420)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 07:54:50 +00:00
jason.woltje b9d464de61 docs: CLI unification release v0.1.0 (M8) (#419)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
ci/woodpecker/tag/publish Pipeline was successful
2026-04-05 07:46:00 +00:00
jason.woltje 872c124581 feat(mosaic): unified first-run UX wizard -> gateway install -> verify (#418)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 07:29:17 +00:00
jason.woltje a531029c5b feat(mosaic): mosaic telemetry command (M6 CU-06-01..05) (#417)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 07:06:42 +00:00
jason.woltje 35ab619bd0 docs: session 2 orchestrator bookkeeping (M3/M4/M5 complete) (#416)
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish Pipeline failed
2026-04-05 07:06:40 +00:00
jason.woltje 831193cdd8 fix(macp): align exports + add CLI smoke test (#415)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-05 06:57:42 +00:00
jason.woltje df460d5a49 feat(macp): mosaic macp CLI surface (#410)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 06:33:52 +00:00
jason.woltje 119ff0eb1b fix(mosaic): gateway token recovery review remediations (#414)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 06:13:29 +00:00
jason.woltje 3abd63ea5c Merge pull request 'feat(mosaic): mosaic auth CLI surface' (#413) from feat/mosaic-auth-cli into main
ci/woodpecker/push/publish Pipeline failed
ci/woodpecker/push/ci Pipeline failed
2026-04-05 06:11:33 +00:00
jason.woltje 641e4604d5 feat(forge): mosaic forge CLI surface (#412)
ci/woodpecker/push/publish Pipeline failed
ci/woodpecker/push/ci Pipeline failed
2026-04-05 06:08:50 +00:00
jarvisandClaude Sonnet 4.6 9b5ecc0171 feat(mosaic): add auth command and stage parallel agent changes
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
Picks up auth command and spec written by parallel agent, and updated
mosaic cli.ts wiring from parallel development during cli-unification.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-05 00:58:03 -05:00
jarvisandClaude Sonnet 4.6 a00325da0e feat(forge): add registerForgeCommand for mosaic forge CLI surface
Adds mosaic forge run|status|resume|personas list subcommands to
@mosaicstack/forge, wires registerForgeCommand into the root mosaic CLI,
and ships a smoke test asserting command structure. Ref CU-05-01
cli-unification-20260404.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-05 00:58:03 -05:00
jason.woltje 4ebce3422d feat(log): mosaic log CLI surface (#407)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-05 05:57:22 +00:00
jason.woltje 751e0ee330 feat(storage): mosaic storage CLI surface (#405)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-05 05:48:13 +00:00
jason.woltje 54b2920ef3 feat(memory): mosaic memory CLI surface (#406)
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish Pipeline failed
2026-04-05 05:44:06 +00:00
jason.woltje 5917016509 feat(mosaic): gateway token recovery via BetterAuth cookie (#411)
ci/woodpecker/push/ci Pipeline is pending
ci/woodpecker/push/publish Pipeline is pending
2026-04-05 05:43:49 +00:00
jason.woltje 7b4f1d249d feat(mosaic): top-level mosaic config command (#408)
ci/woodpecker/push/publish Pipeline failed
ci/woodpecker/push/ci Pipeline failed
2026-04-05 05:37:05 +00:00
jason.woltje 5425f9268e feat(queue): mosaic queue CLI surface (#404)
ci/woodpecker/push/publish Pipeline failed
ci/woodpecker/push/ci Pipeline failed
2026-04-05 05:27:59 +00:00
jason.woltje febd866098 feat(brain): mosaic brain CLI surface (#403)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-05 05:20:44 +00:00
jason.woltje 2446593fff feat(mosaic): alphabetize and group mosaic --help output (#402)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-05 05:12:32 +00:00
jason.woltje 651426cf2e docs(plan): gateway admin token recovery flow (#401)
ci/woodpecker/push/publish Pipeline failed
ci/woodpecker/push/ci Pipeline failed
2026-04-05 05:11:33 +00:00
jason.woltje cf46f6e0ae docs: capture planning decisions + session 1 handoff (#400)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 04:57:00 +00:00
jason.woltje 6f15a84ccf docs: archive stale mission, scaffold CLI unification mission (#399)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 04:47:54 +00:00
jason.woltje c39433c361 chore: remove legacy @mosaicstack/cli package (#398)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-05 04:39:46 +00:00
jason.woltje 257796ce87 Merge pull request 'chore: bump @mosaicstack/mosaic to 0.0.21 for republish' (#397) from chore/bump-mosaic-0.0.21 into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 04:12:05 +00:00
jarvis 2357602f50 chore: bump @mosaicstack/mosaic to 0.0.21 for publish
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
2026-04-04 23:09:52 -05:00
jason.woltje 1230f6b984 ci: fail publish pipeline loudly on registry/auth/network errors (#396)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 03:58:35 +00:00
jason.woltje 14b775f1b9 Merge pull request 'fix: populate KNOWN_PACKAGES for mosaic update command' (#395) from fix/populate-known-packages-list into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
Reviewed-on: http://git.mosaicstack.dev/mosaicstack/mosaic-stack/pulls/395
2026-04-05 03:52:57 +00:00
jarvis c7691d9807 fix: populate KNOWN_PACKAGES with all workspace packages for 'mosaic update'
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
- Remove @mosaicstack/cli (absorbed into @mosaicstack/mosaic)
- Add all 21 remaining workspace packages so the multi-package
  update checker actually covers every published package
2026-04-04 22:49:45 -05:00
jason.woltje 9a53d55678 Merge pull request 'fix: update Gitea org references from mosaic/ to mosaicstack/' (#394) from fix/gitea-org-rename into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
Reviewed-on: http://git.mosaicstack.dev/mosaicstack/mosaic-stack/pulls/394
2026-04-05 03:35:11 +00:00
jarvis 31008ef7ff fix: update Gitea org references from mosaic/ to mosaicstack/
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
- Update all package.json repo URLs (mosaic/mosaic-stack → mosaicstack/mosaic-stack)
- Update npm registry URLs (/api/packages/mosaic/npm → /api/packages/mosaicstack/npm)
- Update woodpecker publish destinations
- Update tools/install.sh registry and repo base URLs
2026-04-04 22:31:20 -05:00
jason.woltje 621ab260c0 fix(mosaic): resumable gateway install + prominent admin token (#393)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/manual/publish Pipeline failed
ci/woodpecker/manual/ci Pipeline failed
2026-04-05 03:19:07 +00:00
jason.woltje 2b1840214e Merge pull request 'fix: rename @mosaic/* packages to @mosaicstack/*' (#392) from fix/rename-mosaic-scope-391 into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-05 03:11:55 +00:00
jarvis 5cfccc2ead fix(mosaic): remove unused hasUpdate variable in formatAllPackagesTable
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
Fixes lint error:
@typescript-eslint/no-unused-vars on hasUpdate
2026-04-04 22:01:01 -05:00
jarvis 774b76447d fix: rename all packages from @mosaic/* to @mosaicstack/*
ci/woodpecker/pr/ci Pipeline failed
ci/woodpecker/push/ci Pipeline failed
- Updated all package.json name fields and dependency references
- Updated all TypeScript/JavaScript imports
- Updated .woodpecker/publish.yml filters and registry paths
- Updated tools/install.sh scope default
- Updated .npmrc registry paths (worktree + host)
- Enhanced update-checker.ts with checkForAllUpdates() multi-package support
- Updated CLI update command to show table of all packages
- Added KNOWN_PACKAGES, formatAllPackagesTable, getInstallAllCommand
- Marked checkForUpdate() with @deprecated JSDoc

Closes #391
2026-04-04 21:43:23 -05:00
jason.woltje 80994bdc8e fix(packages): bump db/memory/queue for PGlite + adapter factories (#389)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 02:20:23 +00:00
jason.woltje 2e31626f87 fix: simplify updater to @mosaic/mosaic only, add explicit tea repo/login flags (#388)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 02:09:23 +00:00
jason.woltje 255ba46a4d fix(packages): republish @mosaic/config and bump dependents (#386)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 01:56:57 +00:00
jason.woltje 10285933a0 fix: retarget updater to @mosaic/mosaic (#384)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-05 01:52:30 +00:00
jason.woltje 543388e18b fix(mosaic): resolve framework scripts via import.meta.url (#385)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
Fixes #383 — resolveTool now uses fileURLToPath(import.meta.url). Adds package.json/framework subpath exports. Bumps @mosaic/mosaic to 0.0.18.
2026-04-05 01:41:46 +00:00
jason.woltje 07a1f5d594 Merge pull request 'feat(mosaic): merge @mosaic/cli into @mosaic/mosaic' (#381) from fix/merge-cli-into-mosaic into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 01:11:33 +00:00
jarvis c6fc090c98 feat(mosaic): merge @mosaic/cli into @mosaic/mosaic
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
@mosaic/mosaic is now the single package providing both:
- 'mosaic' binary (CLI: yolo, coord, prdy, tui, gateway, etc.)
- 'mosaic-wizard' binary (installation wizard)

Changes:
- Move packages/cli/src/* into packages/mosaic/src/
- Convert dynamic @mosaic/mosaic imports to static relative imports
- Add CLI deps (ink, react, socket.io-client, @mosaic/config) to mosaic
- Add jsx: react-jsx to mosaic's tsconfig
- Exclude packages/cli from workspace (pnpm-workspace.yaml)
- Update install.sh to install @mosaic/mosaic instead of @mosaic/cli
- Bump version to 0.0.17

This eliminates the circular dependency between @mosaic/cli and
@mosaic/mosaic that was blocking the build graph.
2026-04-04 20:07:27 -05:00
jason.woltje 9723b6b948 chore: bump @mosaic/cli and @mosaic/mosaic to 0.0.16 (#379)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-05 00:52:09 +00:00
jason.woltje c0d0fd44b7 refactor(storage): replace better-sqlite3 with PGlite adapter (#378)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-04 21:58:14 +00:00
jason.woltje 30c0fb1308 fix: remediate npm deprecation warnings in @mosaic/gateway 0.0.3 (#377)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-04 21:03:54 +00:00
jason.woltje 26fac4722f fix: gateway install preserves npm prefix via registry flag (#376)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-04 20:36:15 +00:00
jason.woltje e3f64c79d9 chore: move gateway default port from 4000 to 14242 (#375)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-04 20:17:40 +00:00
jason.woltje cbd5e8c626 fix: scope Gitea registry to @mosaic packages only (#374)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-04 19:09:14 +00:00
jason.woltje 7560c7dee7 fix: gateway install uses Gitea registry instead of npmjs (#373)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-04 18:59:40 +00:00
jason.woltje 982a0e8f83 chore: bump @mosaic/mosaic and @mosaic/cli to 0.0.11 (#372)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-04 18:47:03 +00:00
jason.woltje fc7fa11923 feat: local tier gateway with PGlite + Gitea-only publishing (#371)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-04 18:39:20 +00:00
jason.woltje 86d6c214fe feat: gateway publishability + npmjs publish script (#370)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-04 18:07:05 +00:00
jason.woltje 39ccba95d0 feat: mosaic gateway CLI daemon management + admin token auth (#369)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-04 18:03:12 +00:00
jason.woltje 202e375f41 Merge pull request 'fix: add build tools to CI install step for better-sqlite3 native bindings' (#368) from feat/task-1775219952-fix-add-build-tools-to-ci-install-step-for-better-sqlite3 into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-03 15:41:23 +00:00
jarvis d0378c5723 fix: add Alpine build tools before pnpm install in CI
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
2026-04-03 09:13:25 -05:00
jason.woltje d6f04a0757 Merge pull request 'fix: add build tools to CI install step for better-sqlite3 native bindings' (#366) from fix/storage-sqlite-ci into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-03 13:41:04 +00:00
jason.woltje afedb8697e Merge pull request 'fix: allow better-sqlite3 build script in pnpm 10' (#367) from fix/pnpm-build-scripts into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-03 13:11:07 +00:00
jarvis 1274df7ffc fix: allow better-sqlite3 build script in pnpm 10
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
2026-04-03 08:06:01 -05:00
jarvis 1b4767bd8b fix: add build tools to CI install step for better-sqlite3 native bindings
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
2026-04-03 07:41:39 -05:00
jason.woltje 0b0fe10b37 Merge pull request 'feat: storage abstraction retrofit — adapters for queue, storage, memory (phases 1-4)' (#365) from feat/storage-abstraction into main
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish Pipeline was successful
2026-04-03 04:40:57 +00:00
jason.woltje acfb31f8f6 fix: quality-rails Commander version mismatch + installer defaults (#364)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-03 02:40:02 +00:00
jarvis fd83bd4f2d chore(orchestrator): Phase 4 complete — config schema + CLI lifecycle commands
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
381 tests passing (347 gateway + 34 CLI), 40/40 tasks clean
2026-04-02 21:38:40 -05:00
jarvisandClaude Opus 4.6 ce3ca1dbd1 feat(cli): add gateway start/stop/status lifecycle commands
Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-04-02 21:37:20 -05:00
jarvisandClaude Opus 4.6 95e7b071d4 feat(cli): add mosaic gateway init command with tier selection wizard
Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-04-02 21:35:32 -05:00
jason.woltje d4c5797a65 fix: installer copies default framework files (AGENTS.md) to mosaicHome (#363)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-03 02:34:43 +00:00
jason.woltje 70a51ba711 fix: all CLI script resolution uses bundled-first resolveTool() (#362)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-03 02:28:07 +00:00
jason.woltje db8023bdbb fix: fwScript prefers npm-bundled scripts over stale deployed copies (#361)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-03 02:21:58 +00:00
jason.woltje 9e597ecf87 chore: bump @mosaic/mosaic and @mosaic/cli to 0.0.6 (#360)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-03 02:13:37 +00:00
jason.woltje a23c117ea4 fix: auto-migrate customized skills to skills-local/ on sync (#359)
ci/woodpecker/push/publish Pipeline failed
ci/woodpecker/push/ci Pipeline failed
2026-04-03 02:11:03 +00:00
jason.woltje 0cf80dab8c fix: stale update banner + skill sync dirty worktree crash (#358)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-03 02:04:05 +00:00
jarvisandClaude Opus 4.6 04a80fb9ba feat(config): add MosaicConfig schema + loader with tier auto-detection
ci/woodpecker/push/ci Pipeline failed
Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-04-02 21:03:00 -05:00
jarvis 626adac363 chore(orchestrator): Phase 3 complete — local tier implemented (SQLite + keyword search + JSON queue)
ci/woodpecker/push/ci Pipeline failed
42 new tests: 4 queue, 18 storage, 20 memory
347 total tests passing
2026-04-02 20:56:39 -05:00
jarvisandClaude Opus 4.6 35fbd88a1d feat(memory): implement keyword search adapter — no vector dependency
Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-04-02 20:55:00 -05:00
jason.woltje 381b0eed7b Merge pull request 'chore: bump @mosaic/mosaic and @mosaic/cli to 0.0.4' (#357) from chore/bump-0.0.4 into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
Reviewed-on: http://git.mosaicstack.dev/mosaic/mosaic-stack/pulls/357
2026-04-03 01:51:55 +00:00
jarvisandClaude Opus 4.6 25383ea645 feat(storage): implement SQLite adapter with better-sqlite3
Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-04-02 20:51:13 -05:00
jarvis e7db9ddf98 chore: bump @mosaic/mosaic and @mosaic/cli to 0.0.4
ci/woodpecker/pr/ci Pipeline failed
ci/woodpecker/push/ci Pipeline failed
2026-04-02 20:50:44 -05:00
jarvisandClaude Opus 4.6 7bb878718d feat(queue): implement local adapter with JSON persistence
Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-04-02 20:46:11 -05:00
jarvis 46a31d4e71 chore(orchestrator): Phase 2 complete — existing backends wrapped as adapters
ci/woodpecker/push/ci Pipeline was successful
2026-04-02 20:44:11 -05:00
jarvisandClaude Opus 4.6 e128a7a322 feat(gateway): wire adapter factories + DI tokens alongside existing providers
Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-04-02 20:44:11 -05:00
jarvisandClaude Opus 4.6 27b1898ec6 refactor(memory): wrap pgvector logic as MemoryAdapter implementation
Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-04-02 20:44:11 -05:00
jarvisandClaude Opus 4.6 d19ef45bb0 feat(storage): implement Postgres adapter wrapping Drizzle + @mosaic/db
Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-04-02 20:44:10 -05:00
jarvisandClaude Opus 4.6 5e852df6c3 refactor(queue): wrap ioredis as bullmq adapter behind QueueAdapter interface
Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-04-02 20:44:10 -05:00
jarvis e0eca771c6 chore(orchestrator): Phase 1 complete — all interfaces defined 2026-04-02 20:44:10 -05:00
jarvisandClaude Opus 4.6 9d22ef4cc9 feat: add adapter factory + registry pattern for queue, storage, memory
Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-04-02 20:44:10 -05:00
jarvisandClaude Opus 4.6 41961a6980 feat(memory): define MemoryAdapter interface types
Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-04-02 20:44:10 -05:00
jarvisandClaude Opus 4.6 e797676a02 feat(storage): define StorageAdapter interface types + scaffold package
Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-04-02 20:44:10 -05:00
jarvisandClaude Opus 4.6 05d61e62be feat(queue): define QueueAdapter interface types
Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-04-02 20:44:10 -05:00
jarvis 73043773d8 chore(orchestrator): Bootstrap storage abstraction retrofit
Mission: Decouple gateway from hardcoded Postgres/Valkey backends.
20 tasks across 5 phases. Estimated total: ~214K tokens.

Phase 1: Interface extraction (4 tasks)
Phase 2: Wrap existing backends as adapters (5 tasks)
Phase 3: Local tier implementation (4 tasks)
Phase 4: Config + CLI commands (4 tasks)
Phase 5: Migration + docs (3 tasks)
2026-04-02 20:44:10 -05:00
jason.woltje 0be9729e40 Merge pull request 'fix: syncDirectory same-path guard, nested .git exclusion, and sync stash handling' (#356) from fix/idempotent-init into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
Reviewed-on: http://git.mosaicstack.dev/mosaic/mosaic-stack/pulls/356
2026-04-03 01:42:18 +00:00
jarvis e83674ac51 fix: mosaic sync — auto-stash dirty worktree before pull --rebase
ci/woodpecker/pr/ci Pipeline failed
ci/woodpecker/push/ci Pipeline was successful
git pull --rebase fails with 'cannot pull with rebase: You have
unstaged changes' when the skills repo has local modifications.

Fix: detect dirty index/worktree, stash before pull, restore after.
Also gracefully handle pull failures (warn and continue with existing
checkout) and stash pop conflicts.
2026-04-02 20:41:11 -05:00
jarvis a6e59bf829 fix: syncDirectory — guard same-path copy and skip nested .git dirs
Two bugs causing 'EACCES: permission denied, copyfile' when source
and target are the same path (e.g. wizard with sourceDir == mosaicHome):

1. No same-path guard — syncDirectory tried to copy every file onto
   itself; git pack files are read-only (0444) so copyFileSync fails.
2. excludeGit only matched top-level .git — nested .git dirs like
   sources/agent-skills/.git were copied, hitting the same permission
   issue.

Fixes:
- Early return when resolve(source) === resolve(target)
- Match .git dirs at any depth via dirName and relPath checks
- Skip files inside .git/ paths

Added file-ops.test.ts with 4 tests covering all cases.
2026-04-02 20:41:11 -05:00
jason.woltje e46f0641f6 Merge pull request 'fix: make mosaic init idempotent — detect existing config files' (#355) from fix/idempotent-init into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
Reviewed-on: http://git.mosaicstack.dev/mosaic/mosaic-stack/pulls/355
2026-04-03 01:30:01 +00:00
jarvis 07efaa9580 chore: bump @mosaic/mosaic and @mosaic/cli to 0.0.3
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-04-02 20:26:01 -05:00
jarvis 361fece023 fix: make mosaic init idempotent — detect existing config files
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
- mosaic-init bash script: detect existing SOUL.md/USER.md/TOOLS.md and
  prompt user to keep, import (re-use values as defaults), or overwrite.
  Non-interactive mode exits cleanly unless --force is passed.
  Overwrite creates timestamped backups before replacing files.

- launch.ts checkSoul(): prefer 'mosaic wizard' over legacy bash script
  when SOUL.md is missing, with fallback to mosaic-init.

- detect-install.ts: pre-populate wizard state with existing values when
  user chooses 'reconfigure', so they see current settings as defaults.

- soul-setup.ts: show existing agent name and communication style as
  defaults during reconfiguration.

- Added tests for reconfigure pre-population and reset non-population.
2026-04-02 20:20:59 -05:00
jason.woltje 80e69016b0 Merge pull request 'chore: bump all packages to 0.0.2 — drop alpha prerelease tag' (#354) from chore/bump-0.0.2 into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
Reviewed-on: http://git.mosaicstack.dev/mosaic/mosaic-stack/pulls/354
2026-04-03 01:12:24 +00:00
jarvis e084a88a9d chore: bump all packages to 0.0.2 — drop alpha prerelease tag
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
Switches from 0.0.1-alpha.2 to 0.0.2. Clean semver, no prerelease
suffixes. We're still alpha (0.0.x range).
2026-04-02 20:03:55 -05:00
jason.woltje 990a88362f Merge pull request 'feat: complete CLI command parity — coord, prdy, seq, upgrade' (#352) from fix/complete-cli-parity into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
Reviewed-on: http://git.mosaicstack.dev/mosaic/mosaic-stack/pulls/352
2026-04-03 00:52:36 +00:00
jarvis ea9782b2dc feat: complete CLI command parity — add coord, prdy, seq, upgrade
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
The #351 merge landed before the force-push with full commands.
This adds the missing subcommands:

- mosaic coord {init,status,mission,continue,run,smoke,resume}
  → delegates to tools/orchestrator/*.sh with --claude/--codex/--pi/--yolo
- mosaic prdy {init,update,validate,status}
  → delegates to tools/prdy/*.sh with --claude/--codex/--pi
- mosaic seq {check,fix,start}
  → sequential-thinking MCP management (native TS)
- mosaic upgrade {release,check,project}
  → delegates to tools/_scripts/mosaic-release-upgrade and mosaic-upgrade

Also removes duplicate prdy registration (was in both launch.ts and
the old registerPrdyCommand — now only in launch.ts).
2026-04-02 19:51:34 -05:00
jason.woltje 8efbaf100e Merge pull request 'feat: unify mosaic CLI — single binary, no PATH conflict' (#351) from feat/unify-mosaic-cli into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-03 00:41:06 +00:00
jarvis 15830e2f2a feat!: unify mosaic CLI — native launcher, no bin/ directory
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
BREAKING CHANGE: ~/.config/mosaic/bin/ is removed entirely.
The mosaic npm CLI is now the only executable.

## What changed

- **bin/ → deleted**: All scripts moved to tools/_scripts/ (internal)
- **mosaic-launch → deleted**: Launcher logic is native TypeScript
  in packages/cli/src/commands/launch.ts
- **mosaic.ps1 → deleted**: PowerShell launcher removed
- **Framework install.sh**: Complete rewrite with migration system
- **Version tracking**: .framework-version file (schema v2)
- **Migration v1→v2**: Auto-removes bin/, cleans old PATH entries
  from shell profiles

## Native TypeScript launcher (commands/launch.ts)

All runtime launch logic ported from bash:
- Runtime prompt builder (AGENTS.md + RUNTIME.md + USER.md + TOOLS.md)
- Mission context injection (reads .mosaic/orchestrator/mission.json)
- PRD status injection (scans docs/PRD.md)
- Pre-flight checks (MOSAIC_HOME, AGENTS.md, SOUL.md, runtime binary)
- Session lock management with signal cleanup
- Per-runtime launch: Claude, Codex, OpenCode, Pi
- Yolo mode flags per runtime
- Pi skill discovery + extension loading
- Framework management (init, doctor, sync, bootstrap) delegates
  to tools/_scripts/ bash implementations

## Installer

- tools/install.sh: detects framework by .framework-version or AGENTS.md
- Framework install.sh: migration system with schema versioning
- Forward-compatible: add migrations as numbered blocks
- No PATH manipulation for framework (npm bin is the only PATH entry)
2026-04-02 19:37:13 -05:00
jason.woltje 04db8591af Merge pull request 'docs: add project README' (#350) from docs/readme into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
Reviewed-on: http://git.mosaicstack.dev/mosaic/mosaic-stack/pulls/350
2026-04-03 00:17:10 +00:00
jarvis 785d30e065 docs: add project README with install, usage, architecture, and dev guide
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
2026-04-02 19:12:28 -05:00
jason.woltje e57a10913d chore: bump all packages to 0.0.1-alpha.2 (#349)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-02 18:21:23 +00:00
jason.woltje 0d12471868 feat: add web search, file edit, MCP management, file refs, and /stop to CLI/TUI (#348)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-02 18:08:30 +00:00
jason.woltje ea371d760d Merge pull request 'feat: unified install.sh + auto-update checker (deprecates mosaic/bootstrap)' (#347) from feat/install-update-checker into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-02 05:41:07 +00:00
jarvis 3b9104429b fix(mosaic): wizard integration test — templates path after monorepo migration
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Templates moved from packages/mosaic/templates/ to
packages/mosaic/framework/templates/ in #345. The test's
existsSync guard silently skipped the copy, causing writeSoul
to early-return without writing SOUL.md.
2026-04-02 00:12:03 -05:00
jarvis 8a83aed9b1 feat: unify install.sh — single installer for framework + npm CLI
- tools/install.sh now installs both components:
  1. Framework (bash launcher, guides, runtime configs) → ~/.config/mosaic/
  2. @mosaic/cli (TUI, gateway client, wizard) → ~/.npm-global/
- Downloads framework from monorepo archive (no bootstrap repo dependency)
- Supports --framework, --cli, --check, --ref flags
- Delete remote-install.sh and remote-install.ps1 (redundant redirectors)
- Update all stale mosaic/bootstrap references → mosaic/mosaic-stack
- Update README.md with monorepo install instructions

Deprecates: mosaic/bootstrap repo
2026-04-02 00:12:03 -05:00
jarvis 2f68237046 fix: remove --registry from npm install to avoid 404 on transitive deps
The @mosaic scope registry is configured in ~/.npmrc. Passing --registry
on the install command overrides the default registry for ALL packages,
causing non-@mosaic deps like @clack/prompts to 404 against Gitea.
2026-04-02 00:11:42 -05:00
jarvis 45f5b9062e feat: install.sh + auto-update checker for CLI
- tools/install.sh: standalone installer/upgrader, curl-pipe safe
  (main() wrapper, process.argv instead of stdin, mkdir -p prefix)
- packages/mosaic/src/runtime/update-checker.ts: version check module
  with 1h cache at ~/.cache/mosaic/update-check.json
- CLI startup: non-blocking background update check on every invocation
- 'mosaic update' command: explicit check + install (--check for CI)
- session-start.sh: warns agents when CLI is outdated
- Proper semver comparison including pre-release precedence
- eslint: allow __tests__ in packages/mosaic for projectService
2026-04-02 00:11:42 -05:00
jason.woltje 147f5f1bec Merge pull request 'fix: remove stale bootstrap repo references' (#346) from fix/stale-bootstrap-refs into main
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish Pipeline was successful
2026-04-02 02:27:49 +00:00
Jason Woltje f05b198882 fix: remove stale bootstrap repo references from CLI error messages
ci/woodpecker/pr/ci Pipeline failed
ci/woodpecker/push/ci Pipeline failed
Replace 'cd ~/src/mosaic-bootstrap && bash install.sh' with
'npm install -g @mosaic/mosaic' now that bootstrap is archived.
2026-04-01 21:26:53 -05:00
jason.woltje d0a484cbb7 Merge pull request 'feat: complete bootstrap → monorepo migration (archive-ready)' (#345) from feat/framework-migration-complete into main
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish Pipeline failed
2026-04-02 02:24:33 +00:00
Jason Woltje 6e6ee37da0 feat: complete framework migration — PowerShell, adapters, guides, profiles, tests
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
Completes the bootstrap repo migration with remaining files:
- PowerShell scripts (.ps1) for Windows support (bin/ + tools/)
- Runtime adapters (claude, codex, generic, pi)
- Guides (17 .md files) and profiles (domains, tech-stacks, workflows)
- Wizard test suite (6 test files from bootstrap tests/)
- Memory placeholder, audit history

Bootstrap repo (mosaic/bootstrap) is now fully superseded:
- All 335 files accounted for
- 5 build config files (package.json, tsconfig, etc.) not needed —
  monorepo has its own at packages/mosaic/
- skills-local/ superseded by monorepo skills/ with mosaic-* naming
- src/ already lives at packages/mosaic/src/
2026-04-01 21:23:26 -05:00
jason.woltje 53199122d8 Merge pull request 'feat: integrate framework files into monorepo' (#344) from feat/framework-into-monorepo into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-02 02:20:17 +00:00
Jason Woltje b38cfac760 feat: integrate framework files into monorepo under packages/mosaic/framework/
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
Moves all Mosaic framework runtime files from the separate bootstrap repo
into the monorepo as canonical source. The @mosaic/mosaic npm package now
ships the complete framework — bin scripts, runtime configs, tools, and
templates — enabling standalone installation via npm install.

Structure:
  packages/mosaic/framework/
  ├── bin/          28 CLI scripts (mosaic, mosaic-doctor, mosaic-sync-skills, etc.)
  ├── runtime/      Runtime adapters (claude, codex, opencode, pi, mcp)
  ├── tools/        Shell tooling (git, prdy, orchestrator, quality, etc.)
  ├── templates/    Agent and repo templates
  ├── defaults/     Default identity files (AGENTS.md, STANDARDS.md, SOUL.md, etc.)
  ├── install.sh    Legacy bash installer
  └── remote-install.sh  One-liner remote installer

Key files with Pi support and recent fixes:
- bin/mosaic: launch_pi() with skills-local loop
- bin/mosaic-doctor: --fix auto-wiring for all 4 harnesses
- bin/mosaic-sync-skills: Pi as 4th link target, symlink-aware find
- bin/mosaic-link-runtime-assets: Pi settings.json patching
- bin/mosaic-migrate-local-skills: Pi skill roots, symlink find
- runtime/pi/RUNTIME.md + mosaic-extension.ts

Package ships 251 framework files in the npm tarball (278KB compressed).
2026-04-01 21:19:21 -05:00
jason.woltje f3cb3e6852 Merge pull request 'fix(web): add public/ directory — fixes Docker build COPY failure' (#343) from fix/web-public-dir into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline was successful
2026-04-01 18:09:40 +00:00
Jason Woltje e599f5fe38 fix(web): add public/ directory — fixes Docker build COPY failure
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
Kaniko fails when COPY --from=builder references a path that doesn't
exist. The web app had no public/ directory, causing build-web to fail
with 'no such file or directory' on the public assets COPY step.
2026-04-01 13:09:03 -05:00
jason.woltje 6357a3fc9c Merge pull request 'fix(ci): use gitea_token secret for npm publish' (#342) from fix/ci-npm-secret into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-01 17:51:32 +00:00
Jason Woltje 92998e6e65 fix(ci): use gitea_token secret for npm publish
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
2026-04-01 12:51:06 -05:00
jason.woltje 2394a2a0dd Merge pull request 'feat: npm publish pipeline + package versioning (0.0.1-alpha.1)' (#341) from feat/npm-publish-pipeline into main 2026-04-01 17:47:10 +00:00
Jason Woltje 13934d4879 feat: npm publish pipeline + package versioning (0.0.1-alpha.1)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
Publish pipeline:
- Add publish-npm step to .woodpecker/publish.yml — publishes all
  @mosaic/* packages to Gitea npm registry on main push/tag
- Requires gitea_npm_token Woodpecker secret (package:write scope)
- publish-npm runs after build, parallel with Docker image builds
- pnpm publish resolves workspace:* to concrete versions automatically

Package configuration:
- All 20 packages versioned at 0.0.1-alpha.1
- publishConfig added to all packages (Gitea registry, public access)
- files field added to all packages (ship only dist/)
- @mosaic/forge includes pipeline/ assets in published package

Meta package (@mosaic/mosaic):
- Now depends on @mosaic/forge, @mosaic/macp, @mosaic/prdy,
  @mosaic/quality-rails, @mosaic/types
- npm install @mosaic/mosaic pulls in the standalone framework

Build fixes:
- Fix forge and macp tsconfig rootDir: '.' -> 'src' so dist/index.js
  resolves correctly (was dist/src/index.js)
- Exclude __tests__ and vitest.config from build includes
- Clean stale build artifacts from old rootDir config

Required Woodpecker secret:
  woodpecker secret add mosaic/mosaic-stack \
    --name gitea_npm_token --value '<token>' \
    --event push,manual,tag
2026-04-01 12:46:13 -05:00
jason.woltje aa80013811 Merge pull request 'feat: mosaic-* skill naming, board/forge/prdy skills, doctor --fix auto-wiring' (#340) from feat/mosaic-skills-doctor-wiring into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-01 17:29:39 +00:00
Jason Woltje 2ee7206c3a feat: mosaic-* skill naming, new board/forge/prdy skills, doctor --fix auto-wiring
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
Skills:
- Rename all repo skills to mosaic-<name> convention (jarvis -> mosaic-jarvis, etc.)
- Update frontmatter name: fields to match directory names
- New mosaic-board skill: standalone Board of Directors multi-persona review
- New mosaic-forge skill: standalone Forge specialist pipeline
- New mosaic-prdy skill: PRD lifecycle (init/update/validate/status)

Wizard (packages/mosaic):
- Add mosaic-board, mosaic-forge, mosaic-prdy, mosaic-standards, mosaic-macp
  to RECOMMENDED_SKILLS
- Add new skills to SKILL_CATEGORIES for categorized browsing

Framework scripts (~/.config/mosaic/bin):
- mosaic (launcher): load skills from both skills/ and skills-local/ for Pi
- mosaic-doctor: add --fix flag for auto-wiring skills into all harnesses,
  Pi skill dir checks, Pi settings.json validation, mosaic-* presence checks
- mosaic-sync-skills: add Pi as 4th link target, fix find to follow symlinks
  in skills-local/, harden is_mosaic_skill_name() with -L fallback
- mosaic-link-runtime-assets: add Pi settings.json skills path patching,
  remove duplicate extension copy (launcher --extension is single source)
- mosaic-migrate-local-skills: add Pi to skill_roots, fix find for symlinks

YAML fixes:
- Quote description values containing colons in mosaic-deploy and
  mosaic-woodpecker SKILL.md frontmatter (fixes Pi parse errors)
2026-04-01 12:28:36 -05:00
jason.woltje be74ca3cf9 feat: add Pi as first-class Mosaic runtime (#339)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-04-01 17:02:23 +00:00
jason.woltje 35123b21ce Merge pull request 'fix(ci): pass DATABASE_URL through Turbo to test tasks' (#338) from fix/turbo-env-passthrough into main
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
2026-03-31 04:03:29 +00:00
jason.woltje 492dc18e14 Merge pull request 'fix(db): add missing migration to Drizzle journal — fixes CI test failures' (#337) from fix/ci-drizzle-migration-journal into main
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish Pipeline failed
2026-03-31 03:03:45 +00:00
jarvis a824a43ed1 fix(ci): pass DATABASE_URL through Turbo to test tasks
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
2026-03-30 22:02:37 -05:00
jarvis 9b72f0ea14 fix(db): add CREATE EXTENSION vector before first migration using pgvector
ci/woodpecker/pr/ci Pipeline failed
ci/woodpecker/push/ci Pipeline failed
The insights table uses vector(1536) but no migration enables the pgvector
extension. CI postgres (pgvector/pgvector:pg17) has the extension available
but it must be explicitly created before use.

Adds CREATE EXTENSION IF NOT EXISTS vector at the top of
0001_cynical_ultimatum.sql (the first migration referencing vector type).
2026-03-30 21:14:44 -05:00
jarvis d367f00077 fix(db): add missing 0001_cynical_ultimatum to Drizzle migration journal
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
The migration file 0001_cynical_ultimatum.sql existed on disk but was not
registered in the Drizzle journal (_journal.json). This caused fresh-database
migrations (CI) to skip creating tables (agent_logs, insights, preferences,
skills, summarization_jobs), then 0002_nebulous_mimic.sql would fail trying
to ALTER the non-existent preferences table.

Fix: insert cynical_ultimatum at idx 1 in the journal and shift all
subsequent entries (idx 2-7).

Verified: pnpm test passes (347 tests, 35 tasks).
2026-03-30 21:09:34 -05:00
jason.woltje 31a5751c6c Merge pull request 'feat(ci): Docker build+push pipeline for gateway and web images' (#335) from fix/ci-docker-publish-test-dep into main
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/push/publish Pipeline failed
2026-03-31 01:48:08 +00:00
jason.woltje fa43989cd5 Merge pull request 'fix: parse VALKEY_URL into RedisOptions for BullMQ — fixes ECONNREFUSED 6379' (#336) from fix/bullmq-valkey-url-port into main
ci/woodpecker/push/ci Pipeline failed
2026-03-31 01:45:37 +00:00
jason.woltje 1b317e8a0a style: fix prettier formatting in plugins/macp (consolidation follow-up)
ci/woodpecker/pr/ci Pipeline failed
ci/woodpecker/push/ci Pipeline failed
2026-03-30 20:43:54 -05:00
jason.woltje 316807581c fix: parse VALKEY_URL into RedisOptions object for BullMQ connection
BullMQ v5 RedisConnection constructor does:
  Object.assign({ port: 6379, host: '127.0.0.1' }, opts)

When opts is a URL string (via 'as unknown as ConnectionOptions'),
Object.assign only copies character-index properties from the string,
so the default port 6379 was never overridden — causing ECONNREFUSED
against the wrong port instead of the configured 6380.

Fix: parse VALKEY_URL with new URL() and return a plain RedisOptions
object { host, port, ... } so Object.assign merges it correctly.
2026-03-30 20:42:03 -05:00
jarvis 3321d4575a fix(ci): wait for postgres readiness before migration + tests
ci/woodpecker/pr/ci Pipeline failed
ci/woodpecker/push/ci Pipeline failed
2026-03-30 20:41:46 -05:00
jarvis 85d4527701 fix(macp): use sh instead of bash in gate-runner — Alpine Linux compatibility
ci/woodpecker/pr/ci Pipeline failed
ci/woodpecker/push/ci Pipeline failed
2026-03-30 20:31:39 -05:00
jarvis 47b7509288 fix(ci): add postgres service sidecar for integration tests
ci/woodpecker/pr/ci Pipeline failed
ci/woodpecker/push/ci Pipeline failed
2026-03-30 20:25:59 -05:00
jarvis 34fad9da81 fix(ci): remove build step from ci.yml — build only in publish pipeline
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
2026-03-30 20:19:29 -05:00
jarvis 48be0aa195 fix(ci): separate publish pipeline — Docker builds independent of test failures
ci/woodpecker/pr/ci Pipeline failed
ci/woodpecker/push/ci Pipeline failed
2026-03-30 20:12:23 -05:00
jarvis f544cc65d2 fix(ci): switch to Kaniko image builder using global gitea secrets
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
2026-03-30 20:04:50 -05:00
jarvis 41e8f91b2d fix(ci): decouple build/publish from test step — DB test requires external Postgres
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
2026-03-30 20:00:35 -05:00
jarvis f161e3cb62 feat(ci): add Docker build+push pipeline for gateway and web images
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
2026-03-30 19:54:28 -05:00
jason.woltje da41724490 Merge pull request 'fix: remove all hardcoded user paths — dynamic OC SDK resolution' (#333) from fix/macp-dynamic-sdk-resolution into main
ci/woodpecker/push/ci Pipeline failed
2026-03-30 19:55:21 +00:00
Mos (Agent) 281e636e4d fix: remove all hardcoded user paths from plugins — dynamic SDK resolution
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
- plugins/macp/src/index.ts: use createRequire + dynamic import() for OC SDK
- plugins/macp/src/acp-runtime-types.ts: local ACP runtime type definitions
- plugins/macp/src/macp-runtime.ts: DEFAULT_REPO_ROOT and PI_RUNNER_PATH use
  os.homedir() instead of hardcoded /home/user/
- plugins/mosaic-framework/src/index.ts: removed hardcoded SDK import
- No hardcoded /home/ paths remain in any plugin source file
- Plugin works on any machine with openclaw installed globally
2026-03-30 19:55:00 +00:00
jason.woltje 87dcd12a65 Merge pull request 'fix: update MACP plugin paths from /home/jarvis to local environment' (#332) from fix/macp-plugin-paths into main
ci/woodpecker/push/ci Pipeline failed
2026-03-30 19:47:11 +00:00
Mos (Agent) d3fdc4ff54 fix: update MACP plugin paths from /home/jarvis to dynamic resolution
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
- plugins/macp/src/index.ts: updated OC SDK imports to local paths
- plugins/macp/src/macp-runtime.ts: DEFAULT_REPO_ROOT → mosaic-stack-new, PI_RUNNER_PATH updated
- plugins/macp/openclaw.plugin.json: default repoRoot description updated
- Removed stale tsconfig.tsbuildinfo with old path references
2026-03-30 19:46:52 +00:00
jason.woltje 9690aba0f5 Merge pull request 'feat: monorepo consolidation — forge, MACP, framework plugin, profiles/guides/skills' (#331) from feat/monorepo-consolidation into main
ci/woodpecker/push/ci Pipeline failed
2026-03-30 19:44:23 +00:00
Mos (Agent) 10689a30d2 feat: monorepo consolidation — forge pipeline, MACP protocol, framework plugin, profiles/guides/skills
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
Work packages completed:
- WP1: packages/forge — pipeline runner, stage adapter, board tasks, brief classifier,
  persona loader with project-level overrides. 89 tests, 95.62% coverage.
- WP2: packages/macp — credential resolver, gate runner, event emitter, protocol types.
  65 tests, 96.24% coverage. Full Python-to-TS port preserving all behavior.
- WP3: plugins/mosaic-framework — OC rails injection plugin (before_agent_start +
  subagent_spawning hooks for Mosaic contract enforcement).
- WP4: profiles/ (domains, tech-stacks, workflows), guides/ (17 docs),
  skills/ (5 universal skills), forge pipeline assets (48 markdown files).

Board deliberation: docs/reviews/consolidation-board-memo.md
Brief: briefs/monorepo-consolidation.md

Consolidates mosaic/stack (forge, MACP, bootstrap framework) into mosaic/mosaic-stack.
154 new tests total. Zero Python — all TypeScript/ESM.
2026-03-30 19:43:24 +00:00
jason.woltje 40c068fcbc Merge pull request 'fix(oc-plugin): MACP OC bridge — route through controller queue instead of Pi-direct' (#330) from fix/macp-oc-bridge into main
ci/woodpecker/push/ci Pipeline failed
2026-03-30 15:48:12 +00:00
jarvis a9340adad7 fix(oc-plugin): replace Pi-direct with MACP controller bridge in runTurn
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
2026-03-30 10:33:32 -05:00
jason.woltje 5cb72e8ca6 Merge pull request 'feat(oc-plugin): MACP ACP runtime backend — sessions_spawn(runtime:macp)' (#329) from feat/oc-macp-plugin-v2 into main
ci/woodpecker/push/ci Pipeline failed
2026-03-30 04:29:47 +00:00
jarvis 48323e7d6e chore: update pnpm lockfile for plugins/macp
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
2026-03-29 23:25:36 -05:00
jarvis 01259f56cd feat(oc-plugin): add MACP ACP runtime backend
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
2026-03-29 23:21:28 -05:00
jason.woltje 472f046a85 chore: Harness Foundation mission COMPLETE — v0.2.0 (#327)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-23 01:29:05 +00:00
jason.woltje dfaf5a52df docs: add M7-003 through M7-007 Matrix architecture sections (#326)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-23 01:26:16 +00:00
jason.woltje 93b3322e45 feat(M5-008,M6-001-005): session hardening tests + BullMQ job queue (#324)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-23 01:21:58 +00:00
jason.woltje a532fd43b2 feat(M6-006,M6-007,M7-001,M7-002): admin jobs API, job event logging, channel adapter interface, message protocol (#325)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-23 01:21:03 +00:00
jason.woltje 701bb69e6c feat(M4-013,M5-001,M5-002,M5-003): routing e2e tests, agent config loading, model+agent switching (#323)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-23 01:09:09 +00:00
jason.woltje 1035d13fc0 feat(M5-004,M5-005,M5-006,M5-007): session-conversation binding, session:info broadcast, agent creation from TUI, and session metrics (#321)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-23 00:58:07 +00:00
jason.woltje b18976a7aa feat(M4-009,M4-010,M4-011): routing rules CRUD, per-user overrides, agent capabilities (#320)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-23 00:48:42 +00:00
jason.woltje 059962fe33 test(M3-012): provider adapter integration tests for all 5 providers (#319)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-23 00:42:00 +00:00
jason.woltje 9b22477643 feat(routing): implement routing decision pipeline — M4-006 (#318)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-23 00:41:04 +00:00
jason.woltje 6a969fbf5f fix(ci)+feat(M3-010/011): skip DB-gated tests in CI + provider_credentials migration (#317)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-23 00:34:31 +00:00
jason.woltje fa84bde6f6 feat(routing): task classifier + default rules + CI test fixes — M4-004/005 (#316)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-23 00:26:49 +00:00
jason.woltje 6f2b3d4f8c feat(M3-005): ZaiAdapter for Z.ai GLM-5 provider (#314)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-23 00:09:16 +00:00
jason.woltje 0ee6bfe9de feat(routing): routing_rules schema + types — M4-001/002/003 (#315)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-23 00:08:56 +00:00
jason.woltje cabd39ba5b chore: update TASKS.md — 25/65 done, Wave 5 in progress (#312)
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-23 00:02:14 +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 10761f3e47 feat(providers): OpenRouter adapter + Ollama embedding support — M3-004/006 (#311)
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 21:38:09 +00:00
jason.woltje 08da6b76d1 feat(M3-003): OpenAI provider adapter for Codex gpt-5.4 (#310)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 21:35:43 +00:00
jason.woltje 5d4efb467c feat(M3-002): implement AnthropicAdapter for Claude Sonnet 4.6, Opus 4.6, and Haiku 4.5 (#309)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 21:33:55 +00:00
jason.woltje 6c6bcbdb7f feat(M3-007,M3-009): provider health check scheduler and Ollama embedding default (#308)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 21:30:15 +00:00
jason.woltje cfdd2b679c chore: M1 + M2 milestones complete — 18/65 tasks done (#307)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 21:21:20 +00:00
jason.woltje 34d4dbbabd feat(M3-008): define model capability matrix (#303)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 21:19:07 +00:00
jason.woltje 78d591b697 test(M2-007): cross-user data isolation integration test (#305)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 21:16:50 +00:00
jason.woltje e95c70d329 feat(M3-001): refactor ProviderService into IProviderAdapter pattern (#306)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 21:16:45 +00:00
jason.woltje d8ac088f3a test(persistence): M1-008 verification — 20 integration tests (#304)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 21:08:19 +00:00
jason.woltje 0d7f3c6d14 chore: Wave 2 complete — 14/65 tasks done (#302)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 21:02:04 +00:00
jason.woltje eddcca7533 feat(gateway): load conversation history on session resume (M1-004, M1-005) (#301)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 21:00:13 +00:00
jason.woltje ad06e00f99 feat(conversations): add search endpoint — M1-006 (#299)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 20:45:50 +00:00
jason.woltje 5b089392fd fix(security): M2-008 Valkey key audit — SCAN over KEYS, restrict /gc to admin (#298)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 20:45:43 +00:00
jason.woltje 02ff3b3256 feat(tui): add /history command — M1-007 (#297)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 20:41:27 +00:00
jason.woltje 1d14ddcfe7 chore: Wave 1 complete — fix merge conflicts, update task status (#296)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 20:37:27 +00:00
jason.woltje 05a805eeca fix(memory): scope InsightsRepo operations to userId — M2-001/002 (#290)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 20:34:42 +00:00
jason.woltje ebf99d9ff7 fix(M2-005,M2-006): enforce user ownership at repo level for conversations and agents (#293)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 20:34:11 +00:00
jason.woltje cf51fd6749 chore: mark M1-001/002/003 and M2-003/004 done (#295)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 20:22:05 +00:00
jason.woltje bb22857fde fix(security): scope memory tools to session userId — M2-003/004 (#294)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 20:19:19 +00:00
jason.woltje 5261048d67 feat(chat): persist messages to DB via ConversationsRepo (M1-001/002/003) (#292)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 20:18:05 +00:00
jason.woltje 36095ad80f chore: bootstrap Harness Foundation mission (Phase 9) (#289)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 20:10:48 +00:00
jason.woltje d06866f501 chore: mark P8-001/002/003 done in TASKS.md (#223)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 18:13:02 +00:00
jason.woltje 02e40f6c3c feat(web): conversation sidebar with search, rename, delete (#222)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 13:10:03 +00:00
jason.woltje de64695ac5 feat(web): design system — ms-* tokens, ThemeProvider, MosaicLogo, sidebar (#221)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 12:57:24 +00:00
jason.woltje dd108b9ab4 feat(auth): add WorkOS and Keycloak SSO providers (rebased) (#220)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-21 12:57:07 +00:00
jason.woltje f3e90df2a0 Merge pull request 'chore: mark P8-001/002/003 in-progress, P8-004 done' (#219) from chore/tasks-p8-status into main
ci/woodpecker/push/ci Pipeline was successful
Reviewed-on: http://git.mosaicstack.dev/mosaic/mosaic-stack/pulls/219
2026-03-21 12:30:03 +00:00
jason.woltje 721e6bbc52 Merge pull request 'feat(web): chat interface — model selector, keybindings, thinking display, v0 styled header' (#216) from feat/ui-chat into main
ci/woodpecker/push/ci Pipeline failed
Reviewed-on: http://git.mosaicstack.dev/mosaic/mosaic-stack/pulls/216
2026-03-21 12:29:29 +00:00
jason.woltje 27848bf42e Merge pull request 'chore: fix prettier formatting on markdown files' (#215) from fix/prettier-format into main
ci/woodpecker/push/ci Pipeline failed
Reviewed-on: http://git.mosaicstack.dev/mosaic/mosaic-stack/pulls/215
2026-03-21 12:29:09 +00:00
jason.woltje 061edcaa78 Merge pull request 'feat(gateway): add Anthropic, OpenAI, Z.ai LLM providers (P8-002)' (#212) from feat/p8-002-llm-providers into main
ci/woodpecker/push/ci Pipeline failed
Reviewed-on: http://git.mosaicstack.dev/mosaic/mosaic-stack/pulls/212
2026-03-21 12:28:50 +00:00
jason.woltje cbb729f377 Merge pull request 'perf: gateway + DB + frontend optimizations (P8-003)' (#211) from feat/p8-003-performance into main
ci/woodpecker/push/ci Pipeline failed
Reviewed-on: http://git.mosaicstack.dev/mosaic/mosaic-stack/pulls/211
2026-03-21 12:28:30 +00:00
jason.woltje cfb491e127 Merge pull request 'feat(auth): add WorkOS and Keycloak SSO providers (P8-001)' (#210) from feat/p8-001-sso-providers into main
ci/woodpecker/push/ci Pipeline failed
Reviewed-on: http://git.mosaicstack.dev/mosaic/mosaic-stack/pulls/210
2026-03-21 12:27:48 +00:00
jason.woltje 20808b9b84 chore: mark P8-001/002/003 in-progress, P8-004 done — PRs open
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-03-19 22:14:14 -05:00
jason.woltje fd61a36b01 chore: mark P8-001/002/003 in-progress, P8-004 done — PRs open
ci/woodpecker/push/ci Pipeline was successful
2026-03-19 22:13:43 -05:00
jason.woltje c0a7bae977 chore: mark P8-001 in-progress (stop cron re-spawn) 2026-03-19 22:11:30 -05:00
jason.woltje 68e056ac91 feat(web): port chat UI — model selector, keybindings, thinking display, styled header
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
2026-03-19 20:42:48 -05:00
jason.woltje 77ba13b41b feat(auth): add WorkOS and Keycloak SSO providers
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
2026-03-19 20:30:00 -05:00
jason.woltjeandClaude Sonnet 4.6 307bb427d6 chore: add P8-001 scratchpad
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-03-19 20:18:59 -05:00
jason.woltjeandClaude Sonnet 4.6 b89503fa8c chore: fix prettier formatting on scratchpad files
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-03-19 20:18:59 -05:00
jason.woltjeandClaude Sonnet 4.6 254da35300 feat(auth): add WorkOS + Keycloak SSO providers (P8-001)
- Refactor auth.ts to build OAuth providers array dynamically; extract
  buildOAuthProviders() for unit-testability
- Add WorkOS provider (WORKOS_CLIENT_ID/SECRET/REDIRECT_URI env vars)
- Add Keycloak provider with realm-scoped OIDC discovery
  (KEYCLOAK_URL/REALM/CLIENT_ID/CLIENT_SECRET env vars)
- Add genericOAuthClient plugin to web auth-client for signIn.oauth2()
- Add WorkOS + Keycloak SSO buttons to login page (NEXT_PUBLIC_*_ENABLED
  feature flags control visibility)
- Update .env.example with SSO provider stanzas
- Add 8 unit tests covering all provider inclusion/exclusion paths

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-03-19 20:18:59 -05:00
jason.woltje 99926cdba2 chore: fix prettier formatting on markdown files
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
2026-03-19 20:17:39 -05:00
jason.woltje 25f880416a Merge pull request 'docs: add TASKS.md agent-column schema to AGENTS.md' (#214) from chore/tasks-schema-agents-md into main
ci/woodpecker/push/ci Pipeline failed
2026-03-20 01:10:56 +00:00
jason.woltje 1138148543 docs: add TASKS.md agent-column schema to AGENTS.md (canonical reference)
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
2026-03-19 20:10:45 -05:00
jason.woltje 4b70b603b3 Merge pull request 'chore: add agent model column to TASKS.md' (#213) from chore/tasks-agent-column into main
ci/woodpecker/push/ci Pipeline failed
2026-03-20 01:08:29 +00:00
jason.woltje 2e7711fe65 chore: add agent model column to TASKS.md schema
ci/woodpecker/pr/ci Pipeline failed
ci/woodpecker/push/ci Pipeline failed
Adds 'agent' column to specify which model should execute each task.
Values: codex | sonnet | haiku | glm-5 | opus | — (auto)
Pipeline crons use this to spawn the cheapest capable model per task.
Phase 8 tasks assigned: P8-001/002/003=codex, P8-004=haiku
2026-03-19 20:08:12 -05:00
jason.woltje 417a57fa00 chore: fix prettier formatting on pre-existing scratchpad (pre-push gate)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
2026-03-18 21:35:04 -05:00
jason.woltje 714fee52b9 feat(gateway): add Anthropic, OpenAI, Z.ai LLM providers (P8-002) 2026-03-18 21:34:38 -05:00
jason.woltje 133668f5b2 chore: format BUG-CLI-scratchpad.md (prettier)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
2026-03-18 21:27:14 -05:00
jason.woltje 3b81bc9f3d perf: gateway + DB + frontend optimizations (P8-003)
- DB client: configure connection pool (max=20, idle_timeout=30s, connect_timeout=5s)
- DB schema: add missing indexes for auth sessions, accounts, conversations, agent_logs
- DB schema: promote preferences(user_id,key) to UNIQUE index for ON CONFLICT upsert
- Drizzle migration: 0003_p8003_perf_indexes.sql
- preferences.service: replace 2-query SELECT+INSERT/UPDATE with single-round-trip upsert
- conversations repo: add ORDER BY + LIMIT to findAll (200) and findMessages (500)
- session-gc.service: make onModuleInit fire-and-forget (removes cold-start TTFB block)
- next.config.ts: enable compress, productionBrowserSourceMaps:false, image avif/webp
- docs/PERFORMANCE.md: full profiling report and change impact notes
2026-03-18 21:26:45 -05:00
jason.woltje cbfd6fb996 fix(web): conversation DELETE — resolve Failed to fetch TypeError (#204)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-17 02:43:56 +00:00
jason.woltje 3f8553ce07 fix(cli): TUI polish — Ctrl+T, React keys, clipboard, version (#205)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-17 02:40:18 +00:00
jason.woltje bf668e18f1 fix(web): admin page role check — stop false redirect to /chat (#203)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-17 02:38:25 +00:00
jason.woltje 1f2b8125c6 fix(cli): sidebar delete conversation — fix silent failure (#201)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-17 02:36:46 +00:00
jason.woltje 93645295d5 fix(gateway): filter projects by ownership — close data privacy leak (#202)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-17 02:35:45 +00:00
jason.woltje 7a52652be6 feat(gateway): Discord channel auto-creation on project bootstrap (#200)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-17 02:32:14 +00:00
jason.woltje 791c8f505e feat(gateway): /system override condensation — accumulate + Haiku merge (#198)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-17 02:26:31 +00:00
jason.woltje 12653477d6 feat(gateway): project bootstrap — docs structure + default agent (#190)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-17 02:12:24 +00:00
jason.woltje dedfa0d9ac fix(gateway): system override TTL 5min → 7 days (#189)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-17 02:06:58 +00:00
jason.woltje c1d3dfd77e fix(cli): disable Ink exitOnCtrlC so double-press handler runs (#188)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-16 13:55:19 +00:00
jason.woltje f0476cae92 fix(cli): wire command:result + system:reload socket events in TUI (#187)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-16 13:21:11 +00:00
jason.woltje b6effdcd6b docs: mark mission complete — 9/9 milestones, all ACs verified (v0.1.0) (#186)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-16 03:51:21 +00:00
jason.woltje 39ef2ff123 feat: verify Phase 8 platform architecture + integration tests (P8-019) (#185)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-16 03:43:42 +00:00
jason.woltje a989b5e549 feat(cli): TUI autocomplete sidebar + fuzzy match + arg hints + input history (P8-017) (#184)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-16 03:30:15 +00:00
jason.woltje ff27e944a1 Merge pull request 'feat(gateway): WorkspaceService + ProjectBootstrapService + TeamsService (P8-015)' (#183) from feat/p8-015-workspaces into main
ci/woodpecker/push/ci Pipeline was successful
2026-03-16 03:14:10 +00:00
jason.woltjeandClaude Sonnet 4.6 0821393c1d feat(gateway): WorkspaceService + ProjectBootstrapService + TeamsService (P8-015)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
- WorkspaceService: path resolution, git init/clone, directory lifecycle (create/delete/exists), user and team root provisioning
- ProjectBootstrapService: orchestrates DB record creation (via Brain) + workspace directory init in a single call
- TeamsService: isMember, canAccessProject, findAll, findById, listMembers via Drizzle DB queries
- WorkspaceController: POST /api/workspaces — auth-guarded project bootstrap endpoint
- TeamsController: GET /api/teams, /:teamId, /:teamId/members, /:teamId/members/:userId
- WorkspaceModule wired into AppModule
- workspace.service.spec.ts: 5 unit tests for resolvePath (user, team, fallback, env var, default)

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-03-15 22:06:01 -05:00
jason.woltje 24f5c0699a feat(gateway): MosaicPlugin lifecycle + ReloadService + hot reload (P8-013) (#182)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-16 03:00:56 +00:00
jason.woltje 96409c40bf feat(gateway): /agent, /provider, /mission, /prdy, /tools commands (P8-012) (#181)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-16 02:50:18 +00:00
jason.woltje 8628f4f93a Merge pull request 'feat(gateway): SessionGCService three-tier GC + /gc command + cron (P8-014)' (#179) from feat/p8-014-session-gc into main
ci/woodpecker/push/ci Pipeline was successful
2026-03-16 02:42:34 +00:00
jason.woltjeandClaude Opus 4.6 b649b5c987 feat(gateway): SessionGCService three-tier GC + /gc command + cron (P8-014)
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Implements three-tier garbage collection for agent sessions:
- SessionGCService.collect() for immediate per-session cleanup on destroySession()
- SessionGCService.sweepOrphans() for daily cron sweep of orphaned Valkey keys
- SessionGCService.fullCollect() for cold-start aggressive cleanup via OnModuleInit
- /gc slash command wired into CommandExecutorService + registered in CommandRegistryService
- SESSION_GC_CRON (daily 4am) added to CronService
- GCModule provides Valkey (ioredis via @mosaic/queue) and is imported by AgentModule, LogModule, CommandsModule, AppModule
- 8 Vitest unit tests covering all three GC tiers

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-15 21:38:48 -05:00
jason.woltje b4d03a8b49 Merge pull request 'feat(gateway): PreferencesService + /preferences REST + /system Valkey override (P8-011)' (#180) from feat/p8-011-preferences into main
ci/woodpecker/push/ci Pipeline was successful
2026-03-16 02:35:38 +00:00
jason.woltjeandClaude Sonnet 4.6 85aeebbde2 feat(gateway): PreferencesService + /preferences REST + /system Valkey override (P8-011)
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
- PreferencesService: platform defaults, user overrides, IMMUTABLE_KEYS enforcement
- PreferencesController: GET /api/preferences, POST /api/preferences, DELETE /api/preferences/:key
- PreferencesModule: global module exporting PreferencesService and SystemOverrideService
- SystemOverrideService: Valkey-backed session-scoped system prompt override with 5-min TTL + renew
- CommandRegistryService: register /system command (socket execution)
- CommandExecutorService: handle /system command via SystemOverrideService
- AgentService: inject system override before each prompt turn, renew TTL; store userId in session
- ChatGateway: pass userId when creating agent sessions
- PreferencesService unit tests: 11 tests covering defaults, overrides, enforcement wins, immutable key errors

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-03-15 21:32:03 -05:00
jason.woltje a4bb563779 feat(gateway): CommandRegistryService + CommandExecutorService (P8-010) (#178)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-16 02:10:31 +00:00
jason.woltje 7f6464bbda feat(gateway): tool path hardening + sandbox escape prevention (P8-016) (#177)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-16 02:02:48 +00:00
jason.woltje f0741e045f feat(cli): TUI slash command parsing + local commands (P8-009) (#176)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-16 01:58:56 +00:00
jason.woltje 5a1991924c feat(db): teams schema + preferences.mutable migration (#175)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-16 01:46:43 +00:00
jason.woltje bd5d14d07f feat(types): CommandDef, CommandManifest, slash command socket events (#174)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-16 01:41:39 +00:00
jason.woltje d5a1791dc5 docs: agent platform architecture plan — augmentation + task breakdown (#173)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-16 01:28:29 +00:00
jason.woltje bd81c12071 docs: update TASKS.md and scratchpad for CLI command architecture (#159)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 23:11:37 +00:00
jason.woltje 4da255bf04 feat(cli): command architecture — agents, missions, gateway-aware prdy (#158)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 23:10:23 +00:00
jason.woltje 82c10a7b33 feat(cli): TUI complete overhaul — components, sidebar, search, branding (#157)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 22:17:19 +00:00
jason.woltje d31070177c fix(ci): remove from_secret to unblock PR pipelines (#156)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 21:48:51 +00:00
jason.woltje 3792576566 fix(web): add jsdom dependency and exclude e2e from vitest (#155)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 20:00:53 +00:00
jason.woltje cd57c75e41 chore(orchestrator): Phase 7 complete — v0.0.8 verified (#154)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 19:50:15 +00:00
jason.woltje 237a863dfd docs(deploy): add deployment guide and expand .env.example (#153)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 19:46:38 +00:00
jason.woltje cb92ba16c1 feat(web): Playwright E2E test suite for critical paths (#152)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 19:46:13 +00:00
jason.woltje 70e9f2c6bc docs: user guide, admin guide, dev guide (closes #57) (#151)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 19:40:44 +00:00
jason.woltje a760401407 feat(admin): web admin panel — user CRUD, role assignment, system health (#150)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 19:18:47 +00:00
jason.woltje 22a5e9791c feat(coord): DB migration — project-scoped missions, multi-tenant RBAC (#149)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 19:18:18 +00:00
jason.woltje d1bef49b4e feat(agent): session cwd sandbox, system prompt config, tool restrictions (#148)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 19:15:05 +00:00
jason.woltje 76abf11eba fix(cli): remove side-effect from agent:end state updater (#133) (#147)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 19:09:13 +00:00
jason.woltje c4850fe6c1 feat(cli): add sessions list/resume/destroy subcommands (#146)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 19:04:10 +00:00
jason.woltje 0809f4e787 feat(web): settings persistence — profile, preferences save to DB (#124) (#145)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 18:43:52 +00:00
jason.woltje 6a4c020179 feat(cli): add --model/--provider flags and /model /provider TUI commands (#144)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 18:41:36 +00:00
jason.woltje 3bb401641e feat(agent): skill invocation — load and execute skills from catalog (#128) (#143)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 18:36:58 +00:00
jason.woltje 54b821d8bd feat(web): provider management UI — list, test, model capabilities (#123) (#142)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 18:33:55 +00:00
jason.woltje 09e649fc7e feat(gateway): MCP client — connect to external MCP servers as agent tools (#127) (#141)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 18:28:31 +00:00
jason.woltje f208f72dc0 feat(web): project detail views — missions, tasks, PRD viewer (#122) (#140)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 18:28:14 +00:00
jason.woltje d42cd68ea4 feat(web): conversation management — search, rename, delete, archive (#121) (#139)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 18:20:15 +00:00
jason.woltje 07647c8382 feat(agent): expand tool registry — file, git, shell, web fetch (#126) (#138)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 18:17:17 +00:00
jason.woltje 8633823257 feat(gateway): add MCP server endpoint with streamable HTTP transport (#137)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 18:11:50 +00:00
jason.woltje d0999a8e37 feat(web): wire WebSocket chat with streaming and conversation switching (#120) (#136)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 18:09:14 +00:00
jason.woltje ea800e3f14 chore(orchestrator): Phase 7 planning — 10-wave execution plan (#135)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 17:47:55 +00:00
jason.woltje 5d2e6fae63 chore(orchestrator): rescope Phase 7 as Feature Completion, add Phase 8 (#119)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 17:44:35 +00:00
jason.woltje fcd22c788a chore(orchestrator): rescope Phase 7 + add Phase 8 (#118)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 17:32:37 +00:00
jason.woltje ab61a15edc fix(agent): register Ollama with api: openai-completions (#117)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 17:10:32 +00:00
jason.woltje 2c60459851 fix(agent): pass dummy apiKey for Ollama provider registration (#116)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 17:08:19 +00:00
jason.woltje ea524a6ba1 fix(cli): add Origin header to auth requests (#115)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 17:03:42 +00:00
jason.woltje 997a6d134f feat(cli): add login command and authenticated TUI sessions (#114)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 17:00:08 +00:00
jason.woltje 8aaf229483 chore: remove deprecated husky v9 shim lines (#113)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 16:48:51 +00:00
jason.woltje 049bb719e8 fix(auth): add CORS headers to BetterAuth raw HTTP handler (#112)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 16:47:27 +00:00
jason.woltje 014ebdacda fix(auth): add trustedOrigins to BetterAuth for cross-origin web dashboard (#111)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 16:44:20 +00:00
jason.woltje 72a73c859c fix(gateway): CORS, memory userId from session, pgvector auto-init (#110)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 16:40:28 +00:00
jason.woltje 6d2b81f6e4 fix(gateway): add missing @Inject() decorators causing silent startup hang (#109)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 01:52:01 +00:00
jason.woltje 9d01a0d484 fix(gateway): load .env from monorepo root via dotenv (#108)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 01:25:09 +00:00
jason.woltje d5102f62fa fix(ci): use from_secret syntax for Woodpecker v2 (#107)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 01:16:36 +00:00
jason.woltje a881e707e2 ci: enable Turbo remote cache + parallelize pipeline steps (#106)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 01:14:56 +00:00
jason.woltje 7d04874f3c chore(orchestrator): complete Phase 6 milestone v0.0.7 (#105)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 01:07:14 +00:00
jason.woltje 9f036242fa feat(cli): add prdy, quality-rails, and wizard subcommands (#104)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 01:05:31 +00:00
jason.woltje c4e52085e3 feat(mosaic): migrate install wizard from v0 to v1 (#103)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 00:59:42 +00:00
jason.woltje 84e1868028 fix(gateway): resolve two startup bugs blocking E2E testing (#102)
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 00:45:28 +00:00
jason.woltje f94f9f672b feat(prdy): migrate @mosaic/prdy from v0 to v1 (#101)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 00:44:02 +00:00
jason.woltje cd29fc8708 feat(quality-rails): migrate @mosaic/quality-rails from v0 to v1 (#100)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 00:23:56 +00:00
jason.woltjeandClaude Opus 4.6 6e22c0fdeb chore(orchestrator): complete Phase 5 milestone — v0.0.6
ci/woodpecker/push/ci Pipeline was successful
- P5-005 done: Telegram plugin wired, .env.example updated
- PR #99 merged, issue #45 closed
- Phase 5 complete, advancing to Phase 6

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-14 19:06:23 -05:00
jason.woltje 1f4d54e474 fix(gateway): wire Telegram plugin into gateway plugin host (#99)
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-15 00:05:27 +00:00
jason.woltje b7a39b45d7 chore(tasks): mark P5-004 done
ci/woodpecker/push/ci Pipeline was successful
2026-03-13 15:16:13 -05:00
jason.woltje 1bfdc91f90 Merge pull request 'feat(auth): P5-004 Authentik OIDC adapter via Better Auth genericOAuth' (#97) from feat/p5-sso-authentik into main
ci/woodpecker/push/ci Pipeline failed
2026-03-13 20:15:50 +00:00
jason.woltje 58a90ac9d7 Merge pull request 'fix(gateway): ownership checks for TasksController findAll/create + MissionsController create' (#98) from fix/task-mission-ownership into main
ci/woodpecker/push/ci Pipeline failed
2026-03-13 20:15:46 +00:00
jason.woltje 684dbdc6a4 fix(gateway): enforce task and mission ownership
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
2026-03-13 14:43:33 -05:00
jason.woltje e92de12cf9 feat(auth): add Authentik OIDC adapter
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
Refs #96
2026-03-13 14:42:05 -05:00
jason.woltje 1f784a6a04 chore(tasks): mark P5-001, P5-003 done; P5-004 in-progress
ci/woodpecker/push/ci Pipeline was successful
2026-03-13 14:33:16 -05:00
jason.woltje ab37c2e69f Merge pull request 'fix(ci): sequential steps + single install to prevent OOM on runner' (#95) from fix/ci-sequential into main
ci/woodpecker/push/ci Pipeline was successful
2026-03-13 18:13:21 +00:00
jason.woltje c8f3e0db44 fix(ci): sequential steps + single install to prevent OOM on runner
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
Each step was re-running pnpm install independently, and all quality
steps (typecheck, lint, format, test) ran in parallel. On merge commits
with more accumulated code this pushed the CI runner over its memory
limit (exit code 254 = OOM kill).

Fix:
- install once, share node_modules via Woodpecker workspace volume
- sequential execution: install → typecheck → lint → format → test → build
- corepack enable in each step (fresh container) but no redundant install
2026-03-13 13:10:30 -05:00
jason.woltje 02772a3910 Merge pull request 'fix(gateway): security hardening — auth guards, ownership checks, validation, rate limiting' (#85) from fix/gateway-security into main
ci/woodpecker/push/ci Pipeline failed
2026-03-13 18:07:01 +00:00
jason.woltje 85a25fd995 fix: add plugin paths to tsconfig.typecheck.json for merged PluginModule
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-03-13 13:03:59 -05:00
jason.woltje 20f302367c chore(gateway): align typecheck paths after rebase 2026-03-13 13:03:09 -05:00
jason.woltje 54c6bfded0 fix(gateway): security hardening — auth guards, ownership checks, validation, rate limiting 2026-03-13 13:03:09 -05:00
jason.woltje ca5472bc31 chore: format docs files 2026-03-13 13:03:09 -05:00
jason.woltje 55b5a31c3c fix(gateway): security hardening — auth guards, ownership checks, validation, rate limiting 2026-03-13 13:03:09 -05:00
jason.woltje 01e9891243 Merge pull request 'feat(plugins): P5-003 Telegram channel plugin' (#93) from feat/p5-telegram-plugin into main
ci/woodpecker/push/ci Pipeline failed
2026-03-13 17:48:01 +00:00
jason.woltje 446a424c1f Merge pull request 'feat(gateway): P5-001 plugin host module' (#92) from feat/p5-plugin-host into main
ci/woodpecker/push/ci Pipeline failed
2026-03-13 17:47:59 +00:00
jason.woltje 02a0d515d9 fix(turbo): typecheck must depend on ^build so package types are available
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-03-13 12:38:55 -05:00
jason.woltje 2bf3816efc fix(turbo): typecheck must depend on ^build so package types are available
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-03-13 12:38:54 -05:00
jason.woltje 96902bab44 feat(plugins): add Telegram channel plugin
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
2026-03-13 12:05:42 -05:00
jason.woltje 280c5351e2 feat(gateway): add plugin host module
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline was successful
2026-03-13 12:04:42 -05:00
jason.woltje 9eb48e1d9b feat(Phase 4): Memory & Intelligence — memory, log, summarization, skills (#91)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 13:56:50 +00:00
jason.woltje d83ebe65e9 verify(P3-008): Phase 3 web dashboard verification (#90)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 13:37:43 +00:00
jason.woltje 4fe7d09e5c feat(web): admin panel with session management (#89)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 13:33:45 +00:00
jason.woltje e44cb7e56a feat(web): settings page with profile, providers, and models (#88)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 13:31:51 +00:00
jason.woltje fd4b7c2ba2 feat(web): project list and mission dashboard views (#87)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 13:30:11 +00:00
jason.woltje a1a1976b38 feat(web): task management with list view and kanban board (#86)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 13:28:17 +00:00
jason.woltje f0d1d4bafa feat(web): chat UI with conversations and WebSocket streaming (#84)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 13:25:28 +00:00
jason.woltje 600da70960 feat(web): wire auth pages with BetterAuth and route guards (#83)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 13:21:33 +00:00
jason.woltje 780f85e0d6 feat(web): scaffold Next.js 16 dashboard with design system and auth client (#82)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 13:18:09 +00:00
jason.woltjeandClaude Opus 4.6 5d936d58a0 fix: add missing @Inject() decorators and fix coord workspace root detection
- Add @Inject() to all gateway constructor params (required without emitDecoratorMetadata)
  - AgentService: ProviderService, CoordService
  - RoutingService: ProviderService
  - ProvidersController: ProviderService, RoutingService
  - SessionsController: AgentService
- Fix coord controller ALLOWED_ROOTS to walk up to monorepo root (pnpm-workspace.yaml)
- Gateway now boots and serves all routes correctly

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-13 08:00:57 -05:00
jason.woltje 8da2759fec fix: coord review remediations (path traversal, JSON parse, race condition) (#81)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 03:43:49 +00:00
jason.woltjeandClaude Opus 4.6 b03c603759 docs: mark Phase 2 complete — update manifest and scratchpad
Phase 0-2 all done. P2-007 closed via PR #79. Mission advances to Phase 3.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-12 22:40:59 -05:00
jason.woltje 77da12a5ee test: verify Phase 2 — routing + coord tests (P2-007) (#79)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 03:40:07 +00:00
jason.woltje 7f6dc43a2d feat: agent session management — metrics, channels, dispose (P2-006) (#78)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 03:35:59 +00:00
jason.woltje f3a7eadcea feat: @mosaic/coord — migrate from v0, gateway integration (P2-005) (#77)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 03:32:20 +00:00
jason.woltje 7f6815feaf feat: tool registration — brain tools for agent sessions (P2-004) (#76)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 03:18:45 +00:00
jason.woltje 7485f32e69 feat: agent routing engine — cost/capability matrix (P2-003) (#75)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 03:13:50 +00:00
jason.woltje 95f95f54cf feat: multi-provider support — Anthropic + Ollama (P2-002) (#74)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 03:10:51 +00:00
jason.woltje aa9ee75a2a fix: auth handler + circular imports — Phase 1 verification (P1-009) (#73)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 03:02:02 +00:00
jason.woltje c54b69f7ce feat: gateway CRUD routes — conversations, projects, missions, tasks (P1-005/006) (#72)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 02:41:03 +00:00
jason.woltje 38897fe423 feat: auth middleware, brain data layer, Valkey queue (P1-002/003/004) (#71)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 02:37:56 +00:00
jason.woltje cbac5902db fix: Phase 0 verification — CI gates green (P0-009) (#70)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 02:28:38 +00:00
jason.woltje 4cd5cbf893 feat: Woodpecker CI pipeline + project docs (P0-007, P0-008) (#69)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 02:25:31 +00:00
jason.woltje 6e3cccc812 feat(auth): @mosaic/auth — BetterAuth email/password setup (#68)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 02:21:15 +00:00
jason.woltje 2b1723e898 feat(db): @mosaic/db — Drizzle schema, PG connection, migrations (#67)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 02:17:18 +00:00
jason.woltje 573484c83e fix: Jaeger image tag + remap PG/Valkey ports (#66)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 02:06:00 +00:00
jason.woltjeandClaude Opus 4.6 bf2cb3a271 docs: mark P0-002, P0-005, P0-006 done (PR #65)
Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-12 20:56:16 -05:00
jason.woltje 35e4e2e527 feat: foundation — Docker Compose, OTEL, shared types (#65)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 01:55:33 +00:00
jason.woltjeandClaude Opus 4.6 f6f05cf23a docs: add FIX-03 agent session sandboxing task (#64)
Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-12 20:49:05 -05:00
jason.woltjeandClaude Opus 4.6 15352448d5 fix: switch gateway to ESM + explicit @Inject for tsx compatibility
Pi SDK is ESM-only. tsx (esbuild) doesn't emit decorator metadata,
so NestJS constructor injection fails without explicit @Inject().

- Set "type": "module" in gateway package.json
- Switch tsconfig to NodeNext module resolution
- Add @Inject(AgentService) to ChatController and ChatGateway

Tested end-to-end: REST /api/chat → Pi SDK → Anthropic → response OK.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-12 20:43:08 -05:00
jason.woltjeandClaude Opus 4.6 c58003e5e3 docs: add gatekeeper review follow-up tasks (FIX-01, FIX-02)
- #62: piSession.dispose() missing in destroySession
- #63: React anti-pattern in TUI agent:end handler

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-12 20:36:17 -05:00
jason.woltjeandClaude Opus 4.6 04528459ec docs: update TASKS.md and scratchpad for communication spine completion
Mark P1-001, P1-007, P1-008, P2-001, P5-002, P6-005 as done (PR #61).
Add session 2 log entry for vertical slice delivery.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-12 20:34:26 -05:00
jason.woltje 4f84a01072 feat: communication spine — gateway, TUI, Discord (#61)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 01:33:32 +00:00
jason.woltjeandClaude Opus 4.6 888bc32be1 docs: mark P0-001 scaffold monorepo as done
PR #60 merged, issue #1 closed.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-12 20:12:44 -05:00
jason.woltje 6d0d288e31 feat(P0-001): scaffold monorepo structure (#60)
Co-authored-by: Jason Woltje <[email protected]>
Co-committed-by: Jason Woltje <[email protected]>
2026-03-13 01:11:46 +00:00
jason.woltjeandClaude Opus 4.6 339641352e docs: record vertical slice reorder in scratchpad
Jason directed: build Pi TUI → Gateway → Discord communication
spine before backfilling horizontal layers.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-12 20:06:17 -05:00
jason.woltjeandClaude Opus 4.6 e7f338e3a9 chore: planning gate — milestones, issues, and task breakdown
Break PRD into 8 milestones (Phase 0–7) with 59 issues on Gitea.
Populate TASKS.md, update mission manifest, initialize scratchpad.
Repo created at git.mosaicstack.dev/mosaic/mosaic-stack.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
2026-03-12 19:51:51 -05: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
5116 changed files with 663841 additions and 344171 deletions
-58
View File
@@ -1,58 +0,0 @@
# Dependencies (installed fresh in Docker)
node_modules
**/node_modules
# Build outputs (built fresh in Docker)
dist
**/dist
.next
**/.next
# TurboRepo cache
.turbo
**/.turbo
# IDE
.idea
.vscode
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Environment files
.env
.env.*
!.env.example
# Credentials
.admin-credentials
# Testing
coverage
**/coverage
# Logs
*.log
# Misc
*.tsbuildinfo
**/*.tsbuildinfo
.pnpm-approve-builds
.husky/_
# Git
.git
.gitignore
# Docker
Dockerfile*
docker-compose*.yml
.dockerignore
# Documentation (not needed in container)
docs
*.md
!README.md
+104 -452
View File
@@ -1,501 +1,153 @@
# ==============================================
# Mosaic Stack Environment Configuration
# ==============================================
# Copy this file to .env and customize for your environment
# ─────────────────────────────────────────────────────────────────────────────
# Mosaic Environment Variables Reference
# Copy this file to .env and fill in the values for your deployment.
# Lines beginning with # are comments; optional vars are commented out.
# ─────────────────────────────────────────────────────────────────────────────
# ======================
# Application Ports
# ======================
API_PORT=3001
API_HOST=0.0.0.0
WEB_PORT=3000
# ======================
# Web Configuration
# ======================
NEXT_PUBLIC_APP_URL=http://localhost:3000
NEXT_PUBLIC_API_URL=http://localhost:3001
# Frontend auth mode:
# - real: Normal auth/session flow
# - mock: Local-only seeded user for FE development (blocked outside NODE_ENV=development)
# Use `mock` locally to continue FE work when auth flow is unstable.
# If omitted, web runtime defaults:
# - development -> mock
# - production -> real
NEXT_PUBLIC_AUTH_MODE=real
# ─── Database (PostgreSQL 17 + pgvector) ─────────────────────────────────────
# Full connection string used by the gateway, ORM, and migration runner.
# Port 5433 avoids conflict with a host-side PostgreSQL instance.
DATABASE_URL=postgresql://mosaic:mosaic@localhost:5433/mosaic
# ======================
# PostgreSQL Database
# ======================
# Bundled PostgreSQL
# SECURITY: Change POSTGRES_PASSWORD to a strong random password in production
DATABASE_URL=postgresql://mosaic:REPLACE_WITH_SECURE_PASSWORD@postgres:5432/mosaic
POSTGRES_USER=mosaic
POSTGRES_PASSWORD=REPLACE_WITH_SECURE_PASSWORD
POSTGRES_DB=mosaic
POSTGRES_PORT=5432
# Docker Compose host-port override for the PostgreSQL container (default: 5433)
# PG_HOST_PORT=5433
# External PostgreSQL (managed service)
# To use an external instance, update DATABASE_URL above
# Example: DATABASE_URL=postgresql://user:[email protected]:5432/mosaic
# PostgreSQL Performance Tuning (Optional)
POSTGRES_SHARED_BUFFERS=256MB
POSTGRES_EFFECTIVE_CACHE_SIZE=1GB
POSTGRES_MAX_CONNECTIONS=100
# ─── Queue (Valkey 8 / Redis-compatible) ─────────────────────────────────────
# Port 6380 avoids conflict with a host-side Redis/Valkey instance.
VALKEY_URL=redis://localhost:6380
# ======================
# Valkey Cache (Redis-compatible)
# ======================
# Bundled Valkey
VALKEY_URL=redis://valkey:6379
VALKEY_HOST=valkey
VALKEY_PORT=6379
# VALKEY_PASSWORD= # Optional: Password for Valkey authentication
VALKEY_MAXMEMORY=256mb
# Docker Compose host-port override for the Valkey container (default: 6380)
# VALKEY_HOST_PORT=6380
# External Redis/Valkey (managed service)
# To use an external instance, update VALKEY_URL above
# Example: VALKEY_URL=redis://elasticache.amazonaws.com:6379
# Example with auth: VALKEY_URL=redis://:[email protected]:6379
# Knowledge Module Cache Configuration
# Set KNOWLEDGE_CACHE_ENABLED=false to disable caching (useful for development)
KNOWLEDGE_CACHE_ENABLED=true
# Cache TTL in seconds (default: 300 = 5 minutes)
KNOWLEDGE_CACHE_TTL=300
# ─── Gateway ─────────────────────────────────────────────────────────────────
# TCP port the NestJS/Fastify gateway listens on (default: 14242)
GATEWAY_PORT=14242
# ======================
# Authentication (Authentik OIDC)
# ======================
# Set to 'true' to enable OIDC authentication with Authentik
# When enabled, OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, and OIDC_REDIRECT_URI are required
OIDC_ENABLED=false
# Comma-separated list of allowed CORS origins.
# Must include the web app origin in production.
GATEWAY_CORS_ORIGIN=http://localhost:3000
# Authentik Server URLs (required when OIDC_ENABLED=true)
# OIDC_ISSUER must end with a trailing slash (/)
OIDC_ISSUER=https://auth.example.com/application/o/mosaic-stack/
OIDC_CLIENT_ID=your-client-id-here
OIDC_CLIENT_SECRET=your-client-secret-here
# Redirect URI must match what's configured in Authentik
# Development: http://localhost:3001/auth/oauth2/callback/authentik
# Production: https://mosaic-api.woltje.com/auth/oauth2/callback/authentik
OIDC_REDIRECT_URI=http://localhost:3001/auth/oauth2/callback/authentik
# Authentik PostgreSQL Database
AUTHENTIK_POSTGRES_USER=authentik
AUTHENTIK_POSTGRES_PASSWORD=REPLACE_WITH_SECURE_PASSWORD
AUTHENTIK_POSTGRES_DB=authentik
# ─── Auth (BetterAuth) ───────────────────────────────────────────────────────
# REQUIRED — random secret used to sign sessions and tokens.
# Generate with: openssl rand -base64 32
BETTER_AUTH_SECRET=change-me-to-a-random-32-char-string
# Authentik Configuration
# CRITICAL: Generate a random secret key with at least 50 characters
# Example: openssl rand -base64 50
AUTHENTIK_SECRET_KEY=REPLACE_WITH_RANDOM_SECRET_MINIMUM_50_CHARS
AUTHENTIK_ERROR_REPORTING=false
# SECURITY: Change bootstrap password immediately after first login
AUTHENTIK_BOOTSTRAP_PASSWORD=REPLACE_WITH_SECURE_PASSWORD
AUTHENTIK_BOOTSTRAP_EMAIL=admin@localhost
AUTHENTIK_COOKIE_DOMAIN=.localhost
# Public base URL of the gateway (used by BetterAuth for callback URLs)
BETTER_AUTH_URL=http://localhost:14242
# Authentik Ports
AUTHENTIK_PORT_HTTP=9000
AUTHENTIK_PORT_HTTPS=9443
# ======================
# CSRF Protection
# ======================
# CRITICAL: Generate a random secret for CSRF token signing
# Required in production; auto-generated in development (not persistent across restarts)
# Command to generate: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
CSRF_SECRET=REPLACE_WITH_64_CHAR_HEX_STRING
# ─── Web App (Next.js) ───────────────────────────────────────────────────────
# Public gateway URL — accessible from the browser, not just the server.
NEXT_PUBLIC_GATEWAY_URL=http://localhost:14242
# ======================
# JWT Configuration
# ======================
# CRITICAL: Generate a random secret key with at least 32 characters
# Example: openssl rand -base64 32
JWT_SECRET=REPLACE_WITH_RANDOM_SECRET_MINIMUM_32_CHARS
JWT_EXPIRATION=24h
# ======================
# BetterAuth Configuration
# ======================
# CRITICAL: Generate a random secret key with at least 32 characters
# This is used by BetterAuth for session management and CSRF protection
# Example: openssl rand -base64 32
BETTER_AUTH_SECRET=REPLACE_WITH_RANDOM_SECRET_MINIMUM_32_CHARS
# Optional explicit BetterAuth origin for callback/error URL generation.
# When empty, backend falls back to NEXT_PUBLIC_API_URL.
BETTER_AUTH_URL=
# ─── OpenTelemetry ───────────────────────────────────────────────────────────
# OTLP HTTP endpoint (otel-collector or any OpenTelemetry-compatible backend)
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
# Trusted Origins (comma-separated list of additional trusted origins for CORS and auth)
# These are added to NEXT_PUBLIC_APP_URL and NEXT_PUBLIC_API_URL automatically
TRUSTED_ORIGINS=
# Service name shown in traces
OTEL_SERVICE_NAME=mosaic-gateway
# Cookie Domain (for cross-subdomain session sharing)
# Leave empty for single-domain setups. Set to ".example.com" for cross-subdomain.
COOKIE_DOMAIN=
# ======================
# Encryption (Credential Security)
# ======================
# CRITICAL: Generate a random 32-byte (256-bit) encryption key
# This key is used for AES-256-GCM encryption of OAuth tokens and sensitive data
# Command to generate: openssl rand -hex 32
# SECURITY: Never commit this key to version control
# SECURITY: Use different keys for development, staging, and production
# SECURITY: Store production keys in a secure secrets manager (see docs/design/credential-security.md)
ENCRYPTION_KEY=REPLACE_WITH_64_CHAR_HEX_STRING_GENERATE_WITH_OPENSSL_RAND_HEX_32
# ─── AI Providers ────────────────────────────────────────────────────────────
# ======================
# OpenBao Secrets Management
# ======================
# OpenBao provides Transit encryption for sensitive credentials
# Enable with: COMPOSE_PROFILES=openbao or COMPOSE_PROFILES=full
# Auto-initialized on first run via openbao-init sidecar
# Ollama (local models — set OLLAMA_BASE_URL to enable)
# OLLAMA_BASE_URL=http://localhost:11434
# OLLAMA_HOST is a legacy alias for OLLAMA_BASE_URL
# OLLAMA_HOST=http://localhost:11434
# Comma-separated list of Ollama model IDs to register (default: llama3.2,codellama,mistral)
# OLLAMA_MODELS=llama3.2,codellama,mistral
# Bundled OpenBao (when openbao profile enabled)
OPENBAO_ADDR=http://openbao:8200
OPENBAO_PORT=8200
# Anthropic (claude-sonnet-4-6, claude-opus-4-6, claude-haiku-4-5)
# ANTHROPIC_API_KEY=sk-ant-...
# External OpenBao/Vault (managed service)
# Disable 'openbao' profile and set OPENBAO_ADDR to your external instance
# Example: OPENBAO_ADDR=https://vault.example.com:8200
# Example: OPENBAO_ADDR=https://vault.hashicorp.com:8200
# AppRole Authentication (Optional)
# If not set, credentials are read from /openbao/init/approle-credentials volume
# Required when using external OpenBao
# OPENBAO_ROLE_ID=your-role-id-here
# OPENBAO_SECRET_ID=your-secret-id-here
# Fallback Mode
# When OpenBao is unavailable, API automatically falls back to AES-256-GCM
# encryption using ENCRYPTION_KEY. This provides graceful degradation.
# ======================
# Ollama (Optional AI Service)
# ======================
# Set OLLAMA_ENDPOINT to use local or remote Ollama
# For bundled Docker service: http://ollama:11434
# For external service: http://your-ollama-server:11434
OLLAMA_ENDPOINT=http://ollama:11434
OLLAMA_PORT=11434
# Embedding Model Configuration
# Model used for generating knowledge entry embeddings
# Default: mxbai-embed-large (1024-dim, padded to 1536)
# Alternative: nomic-embed-text (768-dim, padded to 1536)
# Note: Embeddings are padded/truncated to 1536 dimensions to match schema
OLLAMA_EMBEDDING_MODEL=mxbai-embed-large
# Semantic Search Configuration
# Similarity threshold for semantic search (0.0 to 1.0, where 1.0 is identical)
# Lower values return more results but may be less relevant
# Default: 0.5 (50% similarity)
SEMANTIC_SEARCH_SIMILARITY_THRESHOLD=0.5
# ======================
# OpenAI API (For Semantic Search)
# ======================
# OPTIONAL: Semantic search requires an OpenAI API key
# Get your API key from: https://platform.openai.com/api-keys
# If not configured, semantic search endpoints will return an error
# OpenAI (gpt-4o, gpt-4o-mini, o3-mini)
# OPENAI_API_KEY=sk-...
# ======================
# Application Environment
# ======================
NODE_ENV=development
# Z.ai / GLM (glm-4.5, glm-4.5-air, glm-4.5-flash)
# ZAI_API_KEY=...
# ======================
# Docker Image Configuration
# ======================
# Docker image tag for pulling pre-built images from git.mosaicstack.dev registry
# Used by docker-compose.yml (pulls images) and docker-swarm.yml
# For local builds, use docker-compose.build.yml instead
# Options:
# - latest: Pull latest images from registry (default, built from main branch)
# - <version>: Use specific version tag (e.g., v1.0.0)
IMAGE_TAG=latest
# Custom providers — JSON array of provider configs
# Format: [{"id":"<id>","baseUrl":"<url>","apiKey":"<key>","models":[{"id":"<model-id>","name":"<label>"}]}]
# MOSAIC_CUSTOM_PROVIDERS=
# ======================
# Docker Compose Profiles
# ======================
# Enable optional services via profiles. Combine multiple profiles with commas.
#
# Available profiles:
# - database: PostgreSQL database (disable to use external database)
# - cache: Valkey cache (disable to use external Redis)
# - openbao: OpenBao secrets management (disable to use external vault or fallback encryption)
# - authentik: Authentik OIDC authentication (disable to use external auth provider)
# - ollama: Ollama AI/LLM service (disable to use external LLM service)
# - traefik-bundled: Bundled Traefik reverse proxy (disable to use external proxy)
# - full: Enable all optional services (turnkey deployment)
#
# Examples:
# COMPOSE_PROFILES=full # Everything bundled (development)
# COMPOSE_PROFILES=database,cache,openbao # Core services only
# COMPOSE_PROFILES= # All external services (production)
COMPOSE_PROFILES=full
# ======================
# Traefik Reverse Proxy
# ======================
# TRAEFIK_MODE options:
# - bundled: Use bundled Traefik (requires traefik-bundled profile)
# - upstream: Connect to external Traefik instance
# - none: Direct port exposure without reverse proxy (default)
TRAEFIK_MODE=none
# ─── Embedding Service ───────────────────────────────────────────────────────
# OpenAI-compatible embeddings endpoint (default: OpenAI)
# EMBEDDING_API_URL=https://api.openai.com/v1
# EMBEDDING_MODEL=text-embedding-3-small
# Domain configuration for Traefik routing
MOSAIC_API_DOMAIN=api.mosaic.local
MOSAIC_WEB_DOMAIN=mosaic.local
MOSAIC_AUTH_DOMAIN=auth.mosaic.local
# External Traefik network name (for upstream mode and swarm)
# Must match the network name of your existing Traefik instance
TRAEFIK_NETWORK=traefik-public
TRAEFIK_DOCKER_NETWORK=traefik-public
# ─── Log Summarization Service ───────────────────────────────────────────────
# OpenAI-compatible chat completions endpoint for log summarization (default: OpenAI)
# SUMMARIZATION_API_URL=https://api.openai.com/v1
# SUMMARIZATION_MODEL=gpt-4o-mini
# TLS/SSL Configuration
TRAEFIK_TLS_ENABLED=true
TRAEFIK_ENTRYPOINT=websecure
# Cert resolver name (leave empty if TLS is handled externally or using self-signed certs)
TRAEFIK_CERTRESOLVER=
# For Let's Encrypt (production):
TRAEFIK_ACME_EMAIL=[email protected]
# For self-signed certificates (development), leave TRAEFIK_ACME_EMAIL empty
# Cron schedule for summarization job (default: every 6 hours)
# SUMMARIZATION_CRON=0 */6 * * *
# Traefik Dashboard (bundled mode only)
TRAEFIK_DASHBOARD_ENABLED=true
TRAEFIK_DASHBOARD_PORT=8080
# Cron schedule for log tier management (default: daily at 03:00)
# TIER_MANAGEMENT_CRON=0 3 * * *
# ======================
# Gitea Integration (Coordinator)
# ======================
# Gitea instance URL
GITEA_URL=https://git.mosaicstack.dev
# Coordinator bot credentials (see docs/1-getting-started/3-configuration/4-gitea-coordinator.md)
# SECURITY: Store GITEA_BOT_TOKEN in secrets vault, not in version control
GITEA_BOT_USERNAME=mosaic
GITEA_BOT_TOKEN=REPLACE_WITH_COORDINATOR_BOT_API_TOKEN
GITEA_BOT_PASSWORD=REPLACE_WITH_COORDINATOR_BOT_PASSWORD
# ─── Agent ───────────────────────────────────────────────────────────────────
# Filesystem sandbox root for agent file tools (default: process.cwd())
# AGENT_FILE_SANDBOX_DIR=/var/lib/mosaic/sandbox
# Repository configuration
GITEA_REPO_OWNER=mosaic
GITEA_REPO_NAME=stack
# Comma-separated list of tool names available to non-admin users.
# Leave unset to allow all tools for all authenticated users.
# AGENT_USER_TOOLS=read_file,list_directory,search_files
# Webhook secret for coordinator (HMAC SHA256 signature verification)
# SECURITY: Generate random secret with: openssl rand -hex 32
# Configure in Gitea: Repository Settings → Webhooks → Add Webhook
GITEA_WEBHOOK_SECRET=REPLACE_WITH_RANDOM_WEBHOOK_SECRET
# System prompt injected into every agent session (optional)
# AGENT_SYSTEM_PROMPT=You are a helpful assistant.
# Coordinator API Key (service-to-service authentication)
# CRITICAL: Generate a random API key with at least 32 characters
# Example: openssl rand -base64 32
# The coordinator service uses this key to authenticate with the API
COORDINATOR_API_KEY=REPLACE_WITH_RANDOM_API_KEY_MINIMUM_32_CHARS
# Anthropic API Key (used by coordinator for issue parsing)
# Get your API key from: https://console.anthropic.com/
ANTHROPIC_API_KEY=REPLACE_WITH_ANTHROPIC_API_KEY
# ─── MCP Servers ─────────────────────────────────────────────────────────────
# JSON array of MCP server configs — set to enable MCP tool integration.
# Each entry: {"name":"<id>","url":"<http-or-sse-url>"}
# MCP_SERVERS=[{"name":"my-mcp","url":"http://localhost:3100/sse"}]
# Coordinator tuning
COORDINATOR_POLL_INTERVAL=5.0
COORDINATOR_MAX_CONCURRENT_AGENTS=10
COORDINATOR_ENABLED=true
# ======================
# Rate Limiting
# ======================
# Rate limiting prevents DoS attacks on webhook and API endpoints
# TTL is in seconds, limits are per TTL window
# ─── Coordinator ─────────────────────────────────────────────────────────────
# Root directory used to scope coordinator (worktree/repo) operations.
# Defaults to the monorepo root auto-detected from process.cwd().
# MOSAIC_WORKSPACE_ROOT=/home/user/projects/mosaic
# Global rate limit (applies to all endpoints unless overridden)
# Time window in seconds
RATE_LIMIT_TTL=60
# Requests per window
RATE_LIMIT_GLOBAL_LIMIT=100
# Webhook endpoints (/stitcher/webhook, /stitcher/dispatch) — requests per minute
RATE_LIMIT_WEBHOOK_LIMIT=60
# ─── Discord Plugin (optional — set DISCORD_BOT_TOKEN to enable) ─────────────
# DISCORD_BOT_TOKEN=
# DISCORD_GUILD_ID=
# DISCORD_GATEWAY_URL=http://localhost:14242
# Coordinator endpoints (/coordinator/*) — requests per minute
RATE_LIMIT_COORDINATOR_LIMIT=100
# Health check endpoints (/coordinator/health) — requests per minute (higher for monitoring)
RATE_LIMIT_HEALTH_LIMIT=300
# ─── Telegram Plugin (optional — set TELEGRAM_BOT_TOKEN to enable) ───────────
# TELEGRAM_BOT_TOKEN=
# TELEGRAM_GATEWAY_URL=http://localhost:14242
# Storage backend for rate limiting (redis or memory)
# redis: Uses Valkey for distributed rate limiting (recommended for production)
# memory: Uses in-memory storage (single instance only, for development)
RATE_LIMIT_STORAGE=redis
# ======================
# Discord Bridge (Optional)
# ======================
# Discord bot integration for chat-based control
# Get bot token from: https://discord.com/developers/applications
# DISCORD_BOT_TOKEN=your-discord-bot-token-here
# DISCORD_GUILD_ID=your-discord-server-id
# DISCORD_CONTROL_CHANNEL_ID=channel-id-for-commands
# DISCORD_WORKSPACE_ID=your-workspace-uuid
#
# SECURITY: DISCORD_WORKSPACE_ID must be a valid workspace UUID from your database.
# All Discord commands will execute within this workspace context for proper
# multi-tenant isolation. Each Discord bot instance should be configured for
# a single workspace.
# ─── SSO Providers (add credentials to enable) ───────────────────────────────
# ======================
# Matrix Bridge (Optional)
# ======================
# Matrix bot integration for chat-based control via Matrix protocol
# Requires a Matrix account with an access token for the bot user
# Set these AFTER deploying Synapse and creating the bot account.
#
# SECURITY: MATRIX_WORKSPACE_ID must be a valid workspace UUID from your database.
# All Matrix commands will execute within this workspace context for proper
# multi-tenant isolation. Each Matrix bot instance should be configured for
# a single workspace.
MATRIX_HOMESERVER_URL=http://synapse:8008
MATRIX_ACCESS_TOKEN=
MATRIX_BOT_USER_ID=@mosaic-bot:matrix.woltje.com
MATRIX_SERVER_NAME=matrix.woltje.com
# MATRIX_CONTROL_ROOM_ID=!roomid:matrix.woltje.com
# MATRIX_WORKSPACE_ID=your-workspace-uuid
# --- Authentik (optional — set AUTHENTIK_CLIENT_ID to enable) ---
# AUTHENTIK_ISSUER=https://auth.example.com/application/o/mosaic/
# AUTHENTIK_CLIENT_ID=
# AUTHENTIK_CLIENT_SECRET=
# ======================
# Matrix / Synapse Deployment
# ======================
# Domains for Traefik routing to Matrix services
MATRIX_DOMAIN=matrix.woltje.com
ELEMENT_DOMAIN=chat.woltje.com
# --- WorkOS (optional — set WORKOS_CLIENT_ID to enable) ---
# WORKOS_ISSUER=https://your-company.authkit.app
# WORKOS_CLIENT_ID=client_...
# WORKOS_CLIENT_SECRET=sk_live_...
# Synapse database (created automatically by synapse-db-init in the swarm compose)
SYNAPSE_POSTGRES_DB=synapse
SYNAPSE_POSTGRES_USER=synapse
SYNAPSE_POSTGRES_PASSWORD=REPLACE_WITH_SECURE_SYNAPSE_DB_PASSWORD
# --- Keycloak (optional — set KEYCLOAK_CLIENT_ID to enable) ---
# KEYCLOAK_ISSUER=https://auth.example.com/realms/master
# Legacy alternative if you prefer to compose the issuer from separate vars:
# KEYCLOAK_URL=https://auth.example.com
# KEYCLOAK_REALM=master
# KEYCLOAK_CLIENT_ID=mosaic
# KEYCLOAK_CLIENT_SECRET=
# Image tags for Matrix services
SYNAPSE_IMAGE_TAG=latest
ELEMENT_IMAGE_TAG=latest
# ======================
# Orchestrator Configuration
# ======================
# API Key for orchestrator agent management endpoints
# CRITICAL: Generate a random API key with at least 32 characters
# Example: openssl rand -base64 32
# Required for all /agents/* endpoints (spawn, kill, kill-all, status)
# Health endpoints (/health/*) remain unauthenticated
ORCHESTRATOR_API_KEY=REPLACE_WITH_RANDOM_API_KEY_MINIMUM_32_CHARS
# Runtime safety defaults (recommended for low-memory hosts)
MAX_CONCURRENT_AGENTS=2
SESSION_CLEANUP_DELAY_MS=30000
ORCHESTRATOR_QUEUE_NAME=orchestrator-tasks
ORCHESTRATOR_QUEUE_CONCURRENCY=1
ORCHESTRATOR_QUEUE_MAX_RETRIES=3
ORCHESTRATOR_QUEUE_BASE_DELAY_MS=1000
ORCHESTRATOR_QUEUE_MAX_DELAY_MS=60000
SANDBOX_DEFAULT_MEMORY_MB=256
SANDBOX_DEFAULT_CPU_LIMIT=1.0
# ======================
# AI Provider Configuration
# ======================
# Choose the AI provider for orchestrator agents
# Options: ollama, claude, openai
# Default: ollama (no API key required)
AI_PROVIDER=ollama
# Ollama Configuration (when AI_PROVIDER=ollama)
# For local Ollama: http://localhost:11434
# For remote Ollama: http://your-ollama-server:11434
OLLAMA_MODEL=llama3.1:latest
# Claude API Key
# Required only when AI_PROVIDER=claude.
# Get your API key from: https://console.anthropic.com/
CLAUDE_API_KEY=REPLACE_WITH_CLAUDE_API_KEY
# OpenAI API Configuration (when AI_PROVIDER=openai)
# OPTIONAL: Only required if AI_PROVIDER=openai
# Get your API key from: https://platform.openai.com/api-keys
# OPENAI_API_KEY=sk-...
# ======================
# Speech Services (STT / TTS)
# ======================
# Speech-to-Text (STT) - Whisper via Speaches
# Set STT_ENABLED=true to enable speech-to-text transcription
# STT_BASE_URL is required when STT_ENABLED=true
STT_ENABLED=true
STT_BASE_URL=http://speaches:8000/v1
STT_MODEL=Systran/faster-whisper-large-v3-turbo
STT_LANGUAGE=en
# Text-to-Speech (TTS) - Default Engine (Kokoro)
# Set TTS_ENABLED=true to enable text-to-speech synthesis
# TTS_DEFAULT_URL is required when TTS_ENABLED=true
TTS_ENABLED=true
TTS_DEFAULT_URL=http://kokoro-tts:8880/v1
TTS_DEFAULT_VOICE=af_heart
TTS_DEFAULT_FORMAT=mp3
# Text-to-Speech (TTS) - Premium Engine (Chatterbox) - Optional
# Higher quality voice cloning engine, disabled by default
# TTS_PREMIUM_URL is required when TTS_PREMIUM_ENABLED=true
TTS_PREMIUM_ENABLED=false
TTS_PREMIUM_URL=http://chatterbox-tts:8881/v1
# Text-to-Speech (TTS) - Fallback Engine (Piper/OpenedAI) - Optional
# Lightweight fallback engine, disabled by default
# TTS_FALLBACK_URL is required when TTS_FALLBACK_ENABLED=true
TTS_FALLBACK_ENABLED=false
TTS_FALLBACK_URL=http://openedai-speech:8000/v1
# Whisper model for Speaches STT engine
SPEACHES_WHISPER_MODEL=Systran/faster-whisper-large-v3-turbo
# Speech Service Limits
# Maximum upload file size in bytes (default: 25MB)
SPEECH_MAX_UPLOAD_SIZE=25000000
# Maximum audio duration in seconds (default: 600 = 10 minutes)
SPEECH_MAX_DURATION_SECONDS=600
# Maximum text length for TTS in characters (default: 4096)
SPEECH_MAX_TEXT_LENGTH=4096
# ======================
# Mosaic Telemetry (Task Completion Tracking & Predictions)
# ======================
# Telemetry tracks task completion patterns to provide time estimates and predictions.
# Data is sent to the Mosaic Telemetry API (a separate service).
# Master switch: set to false to completely disable telemetry (no HTTP calls will be made)
MOSAIC_TELEMETRY_ENABLED=true
# URL of the telemetry API server
# For Docker Compose (internal): http://telemetry-api:8000
# For production/swarm: https://tel-api.mosaicstack.dev
MOSAIC_TELEMETRY_SERVER_URL=http://telemetry-api:8000
# API key for authenticating with the telemetry server
# Generate with: openssl rand -hex 32
MOSAIC_TELEMETRY_API_KEY=your-64-char-hex-api-key-here
# Unique identifier for this Mosaic Stack instance
# Generate with: uuidgen or python -c "import uuid; print(uuid.uuid4())"
MOSAIC_TELEMETRY_INSTANCE_ID=your-instance-uuid-here
# Dry run mode: set to true to log telemetry events to console instead of sending HTTP requests
# Useful for development and debugging telemetry payloads
MOSAIC_TELEMETRY_DRY_RUN=false
# ======================
# Logging & Debugging
# ======================
LOG_LEVEL=info
DEBUG=false
# The web login page discovers configured providers dynamically from
# GET /api/sso/providers. No NEXT_PUBLIC_* provider feature flag is required.
-85
View File
@@ -1,85 +0,0 @@
# Traefik Bundled Mode Configuration
# Copy this to .env to enable bundled Traefik reverse proxy
#
# Usage:
# cp .env.traefik-bundled.example .env
# docker compose --profile traefik-bundled up -d
# ======================
# Traefik Configuration
# ======================
TRAEFIK_MODE=bundled
TRAEFIK_ENABLE=true
TRAEFIK_ENTRYPOINT=websecure
TRAEFIK_DOCKER_NETWORK=mosaic-public
# Domain configuration
MOSAIC_API_DOMAIN=api.mosaic.local
MOSAIC_WEB_DOMAIN=mosaic.local
MOSAIC_AUTH_DOMAIN=auth.mosaic.local
# TLS/SSL Configuration
TRAEFIK_TLS_ENABLED=true
# For Let's Encrypt (production):
# [email protected]
# TRAEFIK_CERTRESOLVER=letsencrypt
# For self-signed certificates (development), leave TRAEFIK_ACME_EMAIL empty
TRAEFIK_ACME_EMAIL=
# Traefik Dashboard
TRAEFIK_DASHBOARD_ENABLED=true
TRAEFIK_DASHBOARD_PORT=8080
# Traefik Ports
TRAEFIK_HTTP_PORT=80
TRAEFIK_HTTPS_PORT=443
# ======================
# Application Ports (not exposed when using Traefik)
# ======================
API_PORT=3001
WEB_PORT=3000
# ======================
# PostgreSQL Database
# ======================
POSTGRES_USER=mosaic
POSTGRES_PASSWORD=REPLACE_WITH_SECURE_PASSWORD
POSTGRES_DB=mosaic
POSTGRES_PORT=5432
# ======================
# Valkey Cache
# ======================
VALKEY_PORT=6379
VALKEY_MAXMEMORY=256mb
# ======================
# Authentication (Authentik OIDC)
# ======================
OIDC_ISSUER=https://auth.mosaic.local/application/o/mosaic-stack/
OIDC_CLIENT_ID=your-client-id-here
OIDC_CLIENT_SECRET=your-client-secret-here
OIDC_REDIRECT_URI=https://api.mosaic.local/auth/callback
# Authentik Configuration
AUTHENTIK_SECRET_KEY=REPLACE_WITH_RANDOM_SECRET_MINIMUM_50_CHARS
AUTHENTIK_BOOTSTRAP_PASSWORD=REPLACE_WITH_SECURE_PASSWORD
AUTHENTIK_BOOTSTRAP_EMAIL=admin@localhost
AUTHENTIK_COOKIE_DOMAIN=.mosaic.local
AUTHENTIK_POSTGRES_USER=authentik
AUTHENTIK_POSTGRES_PASSWORD=REPLACE_WITH_SECURE_PASSWORD
AUTHENTIK_POSTGRES_DB=authentik
# ======================
# JWT Configuration
# ======================
JWT_SECRET=REPLACE_WITH_RANDOM_SECRET_MINIMUM_32_CHARS
JWT_EXPIRATION=24h
# ======================
# Docker Compose Profiles
# ======================
# Enable bundled Traefik and optional services
COMPOSE_PROFILES=traefik-bundled,authentik
-83
View File
@@ -1,83 +0,0 @@
# Traefik Upstream Mode Configuration
# Connect to an existing external Traefik instance
#
# Prerequisites:
# 1. External Traefik instance must be running
# 2. External network must exist: docker network create traefik-public
# 3. Copy docker-compose.override.yml.example to docker-compose.override.yml
# 4. Uncomment upstream mode network configuration in override file
#
# Usage:
# cp .env.traefik-upstream.example .env
# docker compose up -d
# ======================
# Traefik Configuration
# ======================
TRAEFIK_MODE=upstream
TRAEFIK_ENABLE=true
TRAEFIK_ENTRYPOINT=websecure
TRAEFIK_DOCKER_NETWORK=traefik-public
TRAEFIK_NETWORK=traefik-public
# Domain configuration
# These domains must be configured in your DNS or /etc/hosts
MOSAIC_API_DOMAIN=api.mosaic.uscllc.com
MOSAIC_WEB_DOMAIN=mosaic.uscllc.com
MOSAIC_AUTH_DOMAIN=auth.mosaic.uscllc.com
# TLS/SSL Configuration
TRAEFIK_TLS_ENABLED=true
# ACME/Certresolver managed by upstream Traefik
TRAEFIK_CERTRESOLVER=
# ======================
# Application Ports (not exposed when using Traefik)
# ======================
# These ports are only used internally within Docker network
API_PORT=3001
WEB_PORT=3000
# ======================
# PostgreSQL Database
# ======================
POSTGRES_USER=mosaic
POSTGRES_PASSWORD=REPLACE_WITH_SECURE_PASSWORD
POSTGRES_DB=mosaic
POSTGRES_PORT=5432
# ======================
# Valkey Cache
# ======================
VALKEY_PORT=6379
VALKEY_MAXMEMORY=256mb
# ======================
# Authentication (Authentik OIDC)
# ======================
OIDC_ISSUER=https://auth.mosaic.uscllc.com/application/o/mosaic-stack/
OIDC_CLIENT_ID=your-client-id-here
OIDC_CLIENT_SECRET=your-client-secret-here
OIDC_REDIRECT_URI=https://api.mosaic.uscllc.com/auth/callback
# Authentik Configuration
AUTHENTIK_SECRET_KEY=REPLACE_WITH_RANDOM_SECRET_MINIMUM_50_CHARS
AUTHENTIK_BOOTSTRAP_PASSWORD=REPLACE_WITH_SECURE_PASSWORD
AUTHENTIK_BOOTSTRAP_EMAIL=admin@localhost
AUTHENTIK_COOKIE_DOMAIN=.mosaic.uscllc.com
AUTHENTIK_POSTGRES_USER=authentik
AUTHENTIK_POSTGRES_PASSWORD=REPLACE_WITH_SECURE_PASSWORD
AUTHENTIK_POSTGRES_DB=authentik
# ======================
# JWT Configuration
# ======================
JWT_SECRET=REPLACE_WITH_RANDOM_SECRET_MINIMUM_32_CHARS
JWT_EXPIRATION=24h
# ======================
# Docker Compose Profiles
# ======================
# Enable optional services (do NOT enable traefik-bundled in upstream mode)
COMPOSE_PROFILES=authentik
+16 -62
View File
@@ -1,71 +1,25 @@
# Dependencies
logs/
node_modules
.pnpm-store
# Build outputs
dist
.next
.turbo
# Compiled source (prevent accidental commits)
apps/*/src/**/*.js
apps/*/src/**/*.d.ts
apps/*/src/**/*.js.map
apps/*/src/**/*.d.ts.map
packages/*/src/**/*.js
packages/*/src/**/*.d.ts
packages/*/src/**/*.js.map
packages/*/src/**/*.d.ts.map
# IDE
.idea
.vscode
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Environment
.next
coverage
.env
.env.local
.env.test
.env.development.local
.env.test.local
.env.production.local
.env.bak.*
*.bak
# Credentials (never commit)
.admin-credentials
# Testing
coverage
# Logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
# Misc
*.tsbuildinfo
.pnpm-approve-builds
.pnpm-store
__pycache__/
docs/.obsidian
# Husky
.husky/_
# Step-CA dev password — real file is gitignored; commit only the .example
infra/step-ca/dev-password
# Orchestrator reports (generated by QA automation, cleaned up after processing)
docs/reports/qa-automation/
# Scratch dirs created by the framework git-wrapper shell test harnesses
.mosaic-test-work/
# Repo-local orchestrator runtime artifacts
.mosaic/orchestrator/orchestrator.pid
.mosaic/orchestrator/state.json
.mosaic/orchestrator/tasks.json
.mosaic/orchestrator/matrix_state.json
.mosaic/orchestrator/logs/*.log
.mosaic/orchestrator/results/*
!.mosaic/orchestrator/logs/.gitkeep
!.mosaic/orchestrator/results/.gitkeep
# Transient config files vite/vitest/esbuild write next to a *.config.ts while
# loading it, then unlink. They are untracked but were not ignored, so turbo's
# package traversal hashed them and intermittently failed CI with "Package
# traversal error: ... .timestamp-*.mjs: No such file or directory" when the
# file vanished mid-scan. Ignoring them removes the race.
*.timestamp-*.mjs
-1
View File
@@ -1,2 +1 @@
npx lint-staged
npx git-secrets --scan || echo "Warning: git-secrets not installed"
+1
View File
@@ -0,0 +1 @@
pnpm preflight && pnpm typecheck && pnpm lint && pnpm format:check
+9
View File
@@ -0,0 +1,9 @@
{
"**/*.{ts,tsx,js,jsx}": [
"prettier --write",
"eslint --fix"
],
"**/*.{json,md,yaml,yml}": [
"prettier --write"
]
}
-48
View File
@@ -1,48 +0,0 @@
// Monorepo-aware lint-staged configuration
// STRICT ENFORCEMENT ENABLED: Blocks commits if affected packages have violations
//
// IMPORTANT: This lints ENTIRE packages, not just changed files.
// If you touch ANY file in a package with violations, you must fix the whole package.
// This forces incremental cleanup - work in a package = clean up that package.
//
export default {
// TypeScript files - lint and typecheck affected packages
'**/*.{ts,tsx}': (filenames) => {
const commands = [];
// 1. Format first (auto-fixes what it can)
commands.push(`prettier --write ${filenames.join(' ')}`);
// 2. Extract affected packages from absolute paths
// lint-staged passes absolute paths, so we need to extract the relative part
const packages = [...new Set(filenames.map(f => {
// Match either absolute or relative paths: .../packages/shared/... or packages/shared/...
const match = f.match(/(?:^|\/)(apps|packages)\/([^/]+)\//);
if (!match) return null;
// Return package name format for turbo (e.g., "@mosaic/api")
return `@mosaic/${match[2]}`;
}))].filter(Boolean);
if (packages.length === 0) {
return commands;
}
// 3. Lint entire affected packages via turbo
// --max-warnings=0 means ANY warning/error blocks the commit
packages.forEach(pkg => {
commands.push(`pnpm turbo run lint --filter=${pkg} -- --max-warnings=0`);
});
// 4. Type-check affected packages
packages.forEach(pkg => {
commands.push(`pnpm turbo run typecheck --filter=${pkg}`);
});
return commands;
},
// Format all other files
'**/*.{js,jsx,json,md,yml,yaml}': [
'prettier --write',
],
};
+63
View File
@@ -13,3 +13,66 @@ This repository is attached to the machine-wide Mosaic framework.
- Keep universal standards in `~/.config/mosaic`
- Keep repo-specific behavior in this repo
- Avoid copying large runtime configs into each project
## Optional Quality Rails
Use `.mosaic/quality-rails.yml` to track whether quality rails are enabled for this repo.
Apply a template:
```bash
~/.config/mosaic/bin/mosaic-quality-apply --template <template> --target .
```
Verify enforcement:
```bash
~/.config/mosaic/bin/mosaic-quality-verify --target .
```
## Optional Matrix Orchestrator Rail
Repo-local orchestrator state lives in `.mosaic/orchestrator/`.
Run one cycle:
```bash
~/.config/mosaic/bin/mosaic-orchestrator-matrix-cycle
~/.config/mosaic/bin/mosaic-orchestrator-run --once
```
Run continuously:
```bash
~/.config/mosaic/bin/mosaic-orchestrator-run --poll-sec 10
```
Bridge events to Matrix:
```bash
~/.config/mosaic/bin/mosaic-orchestrator-matrix-publish
~/.config/mosaic/bin/mosaic-orchestrator-matrix-consume
```
Run until queue is drained (syncs from `docs/tasks.md` first):
```bash
~/.config/mosaic/bin/mosaic-orchestrator-drain
```
Set worker command if auto-detect does not match your CLI:
```bash
export MOSAIC_WORKER_EXEC="codex -p"
# or
export MOSAIC_WORKER_EXEC="opencode -p"
```
Use repo helper (foreground or detached):
```bash
bash scripts/agent/orchestrator-daemon.sh drain
bash scripts/agent/orchestrator-daemon.sh start
bash scripts/agent/orchestrator-daemon.sh status
bash scripts/agent/orchestrator-daemon.sh stop
```
+1 -1
View File
@@ -1,5 +1,5 @@
{
"enabled": true,
"enabled": false,
"transport": "matrix",
"matrix": {
"control_room_id": "",
+4
View File
@@ -0,0 +1,4 @@
{
"last_published_line": 0,
"since": null
}
+10 -76
View File
@@ -1,80 +1,14 @@
{
"schema_version": 1,
"mission_id": "ms21-multi-tenant-rbac-data-migration-20260228",
"name": "MS21 Multi-Tenant RBAC Data Migration",
"description": "Build multi-tenant user/workspace/team management, break-glass auth, RBAC UI enforcement, and migrate jarvis-brain data into Mosaic Stack",
"project_path": "/home/jwoltje/src/mosaic-stack",
"created_at": "2026-02-28T17:10:22Z",
"mission_id": "mvp-20260312",
"name": "MVP",
"description": "",
"project_path": "/home/jwoltje/src/mosaic-mono-v1",
"created_at": "2026-03-13T00:44:02Z",
"status": "active",
"task_prefix": "MS21",
"quality_gates": "pnpm lint && pnpm build && pnpm test",
"milestone_version": "0.0.21",
"milestones": [
{
"id": "phase-1",
"name": "Schema and Admin API",
"status": "pending",
"branch": "schema-and-admin-api",
"issue_ref": "",
"started_at": "",
"completed_at": ""
},
{
"id": "phase-2",
"name": "Break-Glass Authentication",
"status": "pending",
"branch": "break-glass-authentication",
"issue_ref": "",
"started_at": "",
"completed_at": ""
},
{
"id": "phase-3",
"name": "Data Migration",
"status": "pending",
"branch": "data-migration",
"issue_ref": "",
"started_at": "",
"completed_at": ""
},
{
"id": "phase-4",
"name": "Admin UI",
"status": "pending",
"branch": "admin-ui",
"issue_ref": "",
"started_at": "",
"completed_at": ""
},
{
"id": "phase-5",
"name": "RBAC UI Enforcement",
"status": "pending",
"branch": "rbac-ui-enforcement",
"issue_ref": "",
"started_at": "",
"completed_at": ""
},
{
"id": "phase-6",
"name": "Verification",
"status": "pending",
"branch": "verification",
"issue_ref": "",
"started_at": "",
"completed_at": ""
}
],
"sessions": [
{
"session_id": "sess-001",
"runtime": "unknown",
"started_at": "2026-02-28T17:48:51Z",
"ended_at": "",
"ended_reason": "",
"milestone_at_end": "",
"tasks_completed": [],
"last_task_id": ""
}
]
"task_prefix": "",
"quality_gates": "",
"milestone_version": "0.0.1",
"milestones": [],
"sessions": []
}
+19
View File
@@ -0,0 +1,19 @@
{
"generated_at": "2026-03-13T00:44:51Z",
"runtime": "claude",
"mission_id": "mvp-20260312",
"mission_name": "MVP",
"project_path": "/home/jwoltje/src/mosaic-mono-v1",
"quality_gates": "",
"current_milestone": {
"id": "",
"name": ""
},
"next_task": "",
"progress": {
"tasks_done": 0,
"tasks_total": 0,
"pct": 0
},
"current_branch": ""
}
+5 -5
View File
@@ -1,8 +1,8 @@
{
"session_id": "sess-001",
"runtime": "unknown",
"pid": 2396592,
"started_at": "2026-02-28T17:48:51Z",
"project_path": "/tmp/ms21-api-003",
"session_id": "claude-20260312-194506-357136",
"runtime": "claude",
"pid": 357136,
"started_at": "2026-03-13T00:45:06Z",
"project_path": "/home/jwoltje/src/mosaic-mono-v1",
"milestone_id": ""
}
+4
View File
@@ -0,0 +1,4 @@
{
"running_task_id": null,
"updated_at": null
}
+3
View File
@@ -0,0 +1,3 @@
{
"tasks": []
}
+1 -1
View File
@@ -7,4 +7,4 @@ template: ""
# - monorepo
#
# Apply manually:
# ~/.config/mosaic/bin/mosaic-quality-apply --template <template> --target <repo>
# ~/.mosaic/bin/mosaic-quality-apply --template <template> --target <repo>
+13 -25
View File
@@ -1,29 +1,17 @@
#!/usr/bin/env bash
# Repo-specific hooks used by scripts/agent/*.sh for Mosaic Stack.
# Optional repo-specific hooks used by scripts/agent/*.sh
mosaic_hook_session_start() {
echo "[mosaic-stack] Branch: $(git rev-parse --abbrev-ref HEAD)"
echo "[mosaic-stack] Remotes:"
git remote -v | sed 's/^/[mosaic-stack] /'
if command -v node >/dev/null 2>&1; then
echo "[mosaic-stack] Node: $(node -v)"
fi
if command -v pnpm >/dev/null 2>&1; then
echo "[mosaic-stack] pnpm: $(pnpm -v)"
fi
}
# Called by session-start.sh
# mosaic_hook_session_start() {
# echo "Run repo-specific startup checks"
# }
mosaic_hook_critical() {
echo "[mosaic-stack] Recent commits:"
git log --oneline --decorate -n 5 | sed 's/^/[mosaic-stack] /'
echo "[mosaic-stack] Open TODO/FIXME markers (top 20):"
rg -n "(TODO|FIXME|HACK|SECURITY)" apps packages plugins docs --glob '!**/node_modules/**' -S \
| head -n 20 \
| sed 's/^/[mosaic-stack] /' \
|| true
}
# Called by critical.sh
# mosaic_hook_critical() {
# echo "Run repo-specific critical queries"
# }
mosaic_hook_session_end() {
echo "[mosaic-stack] Working tree summary:"
git status --short | sed 's/^/[mosaic-stack] /' || true
}
# Called by session-end.sh
# mosaic_hook_session_end() {
# echo "Run repo-specific end-of-session checks"
# }
+4
View File
@@ -0,0 +1,4 @@
{
"integration_trunk": "next",
"release_branch": "main"
}
+5 -1
View File
@@ -1 +1,5 @@
@mosaicstack:registry=https://git.mosaicstack.dev/api/packages/mosaic/npm/
@mosaicstack:registry=https://git.mosaicstack.dev/api/packages/mosaicstack/npm/
# HOME resolves to /root in the ci-base image, preserving its warmed-store path.
# Non-root checkouts use their own HOME. Override without editing this file via
# NPM_CONFIG_STORE_DIR (pnpm's environment form of the store-dir setting).
store-dir=${HOME}/.local/share/pnpm/store
-1
View File
@@ -1 +0,0 @@
24
+17 -5
View File
@@ -1,6 +1,18 @@
node_modules
dist
.next
.turbo
coverage
pnpm-lock.yaml
**/next-env.d.ts
**/dist
**/node_modules
**/drizzle
**/.next
# Python build/test artifacts — same category as node_modules/dist/.next above.
# Prettier must never scan generated trees; without these a local venv poisons
# `pnpm format:check` with thousands of third-party files.
**/venv
**/__pycache__
**/.mypy_cache
**/.pytest_cache
**/htmlcov
.claude/
docs/tess/TASKS.md
docs/scratchpads/
packages/mosaic/src/fleet/testdata/documentation-publication-v1/inline-migration-v1.json
+6
View File
@@ -0,0 +1,6 @@
{
"singleQuote": true,
"trailingComma": "all",
"semi": true,
"printWidth": 100
}
-10
View File
@@ -1,10 +0,0 @@
{
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf"
}
-36
View File
@@ -1,36 +0,0 @@
# Trivy CVE Suppressions — Upstream Dependencies
# Reviewed: 2026-02-13 | Milestone: M11-CIPipeline
#
# MITIGATED:
# - Go stdlib CVEs (6): gosu rebuilt from source with Go 1.26
# - npm bundled CVEs (5): npm removed from production Node.js images
# - Node.js 20 → 24 LTS migration (#367): base images updated
#
# REMAINING: OpenBao (5 CVEs) + Next.js bundled tar/minimatch (5 CVEs)
# Re-evaluate when upgrading openbao image beyond 2.5.0 or Next.js beyond 16.1.6.
# === OpenBao false positives ===
# Trivy reads Go module pseudo-version (v0.0.0-20260204...) from bin/bao
# and reports CVEs fixed in openbao 2.0.32.4.4. We run openbao:2.5.0.
CVE-2024-8185 # HIGH: DoS via Raft join (fixed in 2.0.3)
CVE-2024-9180 # HIGH: privilege escalation (fixed in 2.0.3)
CVE-2025-59043 # HIGH: DoS via malicious JSON (fixed in 2.4.1)
CVE-2025-64761 # HIGH: identity group root escalation (fixed in 2.4.4)
# === Next.js bundled tar/minimatch CVEs (upstream — waiting on Next.js release) ===
# Next.js 16.1.6 bundles [email protected] and [email protected] in next/dist/compiled/ (pre-compiled).
# These are NOT pnpm dependencies — they're embedded in the Next.js package itself.
# pnpm overrides cannot reach these; only a Next.js upgrade can fix them.
# Affects web image only (orchestrator and API are clean).
# npm was also removed from all production images, eliminating the npm-bundled copy.
# To resolve: upgrade Next.js when a release bundles tar >= 7.5.8 and minimatch >= 10.2.1.
CVE-2026-23745 # HIGH: tar arbitrary file overwrite via unsanitized linkpaths (fixed in 7.5.3)
CVE-2026-23950 # HIGH: tar arbitrary file overwrite via Unicode path collision (fixed in 7.5.4)
CVE-2026-24842 # HIGH: tar arbitrary file creation via hardlink path traversal (needs tar >= 7.5.7)
CVE-2026-26960 # HIGH: tar arbitrary file read/write via malicious archive hardlink (needs tar >= 7.5.8)
CVE-2026-26996 # HIGH: minimatch DoS via specially crafted glob patterns (needs minimatch >= 10.2.1)
# === OpenBao Go stdlib (waiting on upstream rebuild) ===
# OpenBao 2.5.0 compiled with Go 1.25.6, fix needs Go >= 1.25.7.
# Cannot build OpenBao from source (large project). Waiting for upstream release.
CVE-2025-68121 # CRITICAL: crypto/tls session resumption
-141
View File
@@ -1,141 +0,0 @@
# Woodpecker CI Configuration for Mosaic Stack
## Pipeline Architecture
Split per-package pipelines with path filtering. Only affected packages rebuild on push.
```
.woodpecker/
├── api.yml # @mosaic/api (NestJS)
├── web.yml # @mosaic/web (Next.js)
├── orchestrator.yml # @mosaic/orchestrator (NestJS)
├── coordinator.yml # mosaic-coordinator (Python/FastAPI)
├── infra.yml # postgres + openbao Docker images
├── codex-review.yml # AI code/security review (PRs only)
├── README.md
└── schemas/
├── code-review-schema.json
└── security-review-schema.json
```
## Path Filtering
| Pipeline | Triggers On |
| ------------------ | --------------------------------------------------- |
| `api.yml` | `apps/api/**`, `packages/**`, root configs |
| `web.yml` | `apps/web/**`, `packages/**`, root configs |
| `orchestrator.yml` | `apps/orchestrator/**`, `packages/**`, root configs |
| `coordinator.yml` | `apps/coordinator/**` |
| `infra.yml` | `docker/**` |
| `codex-review.yml` | All PRs (no path filter) |
**Root configs** = `pnpm-lock.yaml`, `pnpm-workspace.yaml`, `turbo.json`, `package.json`
## Security Chain
Every pipeline follows the full security chain required by the CI/CD guide:
```
source scanning (lint + pnpm audit / bandit + pip-audit)
-> docker build (Kaniko)
-> container scanning (Trivy: HIGH,CRITICAL)
-> package linking (Gitea registry)
```
Docker builds gate on ALL quality + security steps passing.
## Pipeline Dependency Graphs
### Node.js Apps (api, web, orchestrator)
```
install -> [security-audit, lint, prisma-generate*]
prisma-generate* -> [typecheck, prisma-migrate*]
prisma-migrate* -> test
[all quality gates] -> build -> docker-build -> trivy -> link
```
_\*prisma steps: api.yml only_
### Coordinator (Python)
```
install -> [ruff-check, mypy, security-bandit, security-pip-audit, test]
[all quality gates] -> docker-build -> trivy -> link
```
### Infrastructure
```
[docker-build-postgres, docker-build-openbao]
-> [trivy-postgres, trivy-openbao]
-> link
```
## Docker Images
| Image | Registry Path | Context |
| ------------------ | ----------------------------------------------- | ------------------- |
| stack-api | `git.mosaicstack.dev/mosaic/stack-api` | `.` (monorepo root) |
| stack-web | `git.mosaicstack.dev/mosaic/stack-web` | `.` (monorepo root) |
| stack-orchestrator | `git.mosaicstack.dev/mosaic/stack-orchestrator` | `.` (monorepo root) |
| stack-coordinator | `git.mosaicstack.dev/mosaic/stack-coordinator` | `apps/coordinator` |
| stack-postgres | `git.mosaicstack.dev/mosaic/stack-postgres` | `docker/postgres` |
| stack-openbao | `git.mosaicstack.dev/mosaic/stack-openbao` | `docker/openbao` |
## Image Tagging
| Condition | Tag | Purpose |
| ------------- | -------------------------- | -------------------------- |
| Always | `${CI_COMMIT_SHA:0:8}` | Immutable commit reference |
| `main` branch | `latest` | Current latest build |
| Git tag | tag value (e.g., `v1.0.0`) | Semantic version release |
## Required Secrets
Configure in Woodpecker UI (Settings > Secrets):
| Secret | Scope | Purpose |
| ---------------- | ----------------- | ------------------------------------------- |
| `gitea_username` | push, manual, tag | Gitea registry auth |
| `gitea_token` | push, manual, tag | Gitea registry auth (`package:write` scope) |
| `codex_api_key` | pull_request | Codex AI reviews |
## Codex AI Review Pipeline
The `codex-review.yml` pipeline runs independently on all PRs:
- **Code review**: Correctness, code quality, testing, performance
- **Security review**: OWASP Top 10, hardcoded secrets, injection flaws
Fails on blockers or critical/high severity security findings.
### Local Testing
```bash
~/.claude/scripts/codex/codex-code-review.sh --uncommitted
~/.claude/scripts/codex/codex-security-review.sh --uncommitted
```
## Troubleshooting
### "unauthorized: authentication required"
- Verify `gitea_username` and `gitea_token` secrets in Woodpecker
- Verify token has `package:write` scope
### Trivy scan fails with HIGH/CRITICAL
- Check if the vulnerability is in the base image (not our code)
- Add to `.trivyignore` if it's a known, accepted risk
- Use `--ignore-unfixed` (already set) to skip unfixable CVEs
### Package linking returns 404
- Normal for recently pushed packages — retry logic handles this
- If persistent: verify package name matches exactly (case-sensitive)
### Pipeline runs Docker builds on pull requests
- Docker build steps have `when: branch: [main]` guards
- PRs only run quality gates, not Docker builds
-232
View File
@@ -1,232 +0,0 @@
# API Pipeline - Mosaic Stack
# Quality gates, build, and Docker publish for @mosaic/api
#
# Triggers on: apps/api/**, packages/**, root configs
# Security chain: source audit + Trivy container scan
when:
- event: [push, pull_request, manual]
path:
include:
- "apps/api/**"
- "packages/**"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- "turbo.json"
- "package.json"
- ".woodpecker/api.yml"
- ".trivyignore"
variables:
- &node_image "node:24-alpine"
- &install_deps |
corepack enable
pnpm install --frozen-lockfile
- &use_deps |
corepack enable
- &turbo_env
TURBO_API:
from_secret: turbo_api
TURBO_TOKEN:
from_secret: turbo_token
TURBO_TEAM:
from_secret: turbo_team
- &kaniko_setup |
mkdir -p /kaniko/.docker
echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$GITEA_USER\",\"password\":\"$GITEA_TOKEN\"}}}" > /kaniko/.docker/config.json
services:
postgres:
image: postgres:17.7-alpine3.22
environment:
POSTGRES_DB: test_db
POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_password
steps:
# === Quality Gates ===
install:
image: *node_image
commands:
- *install_deps
security-audit:
image: *node_image
commands:
- *use_deps
- pnpm audit --audit-level=high
depends_on:
- install
prisma-generate:
image: *node_image
environment:
SKIP_ENV_VALIDATION: "true"
commands:
- *use_deps
- pnpm --filter "@mosaic/api" prisma:generate
depends_on:
- install
lint:
image: *node_image
environment:
SKIP_ENV_VALIDATION: "true"
<<: *turbo_env
commands:
- *use_deps
- pnpm turbo lint --filter=@mosaic/api
depends_on:
- prisma-generate
typecheck:
image: *node_image
environment:
SKIP_ENV_VALIDATION: "true"
<<: *turbo_env
commands:
- *use_deps
- pnpm turbo typecheck --filter=@mosaic/api
depends_on:
- prisma-generate
prisma-migrate:
image: *node_image
environment:
SKIP_ENV_VALIDATION: "true"
DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/test_db?schema=public"
commands:
- *use_deps
- pnpm --filter "@mosaic/api" prisma migrate deploy
depends_on:
- prisma-generate
test:
image: *node_image
environment:
SKIP_ENV_VALIDATION: "true"
DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/test_db?schema=public"
ENCRYPTION_KEY: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
commands:
- *use_deps
- pnpm --filter "@mosaic/api" exec vitest run --exclude 'src/auth/auth-rls.integration.spec.ts' --exclude 'src/credentials/user-credential.model.spec.ts' --exclude 'src/job-events/job-events.performance.spec.ts' --exclude 'src/knowledge/services/fulltext-search.spec.ts' --exclude 'src/mosaic-telemetry/mosaic-telemetry.module.spec.ts'
depends_on:
- prisma-migrate
# === Build ===
build:
image: *node_image
environment:
SKIP_ENV_VALIDATION: "true"
NODE_ENV: "production"
<<: *turbo_env
commands:
- *use_deps
- pnpm turbo build --filter=@mosaic/api
depends_on:
- lint
- typecheck
- test
- security-audit
# === Docker Build & Push ===
docker-build-api:
image: gcr.io/kaniko-project/executor:debug
environment:
GITEA_USER:
from_secret: gitea_username
GITEA_TOKEN:
from_secret: gitea_token
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
commands:
- *kaniko_setup
- |
DESTINATIONS=""
if [ -n "$CI_COMMIT_TAG" ]; then
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-api:$CI_COMMIT_TAG"
elif [ "$CI_COMMIT_BRANCH" = "main" ]; then
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-api:latest"
fi
/kaniko/executor --context . --dockerfile apps/api/Dockerfile --snapshot-mode=redo $DESTINATIONS
when:
- branch: [main]
event: [push, manual, tag]
depends_on:
- build
# === Container Security Scan ===
security-trivy-api:
image: aquasec/trivy:latest
environment:
GITEA_USER:
from_secret: gitea_username
GITEA_TOKEN:
from_secret: gitea_token
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
commands:
- |
if [ -n "$$CI_COMMIT_TAG" ]; then
SCAN_TAG="$$CI_COMMIT_TAG"
elif [ "$$CI_COMMIT_BRANCH" = "main" ]; then
SCAN_TAG="latest"
else
SCAN_TAG="latest"
fi
mkdir -p ~/.docker
echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$$GITEA_USER\",\"password\":\"$$GITEA_TOKEN\"}}}" > ~/.docker/config.json
trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed \
--ignorefile .trivyignore \
git.mosaicstack.dev/mosaic/stack-api:$$SCAN_TAG
when:
- branch: [main]
event: [push, manual, tag]
depends_on:
- docker-build-api
# === Package Linking ===
link-packages:
image: alpine:3
environment:
GITEA_TOKEN:
from_secret: gitea_token
commands:
- apk add --no-cache curl
- sleep 10
- |
set -e
link_package() {
PKG="$$1"
echo "Linking $$PKG..."
for attempt in 1 2 3; do
STATUS=$$(curl -s -o /tmp/link-response.txt -w "%{http_code}" -X POST \
-H "Authorization: token $$GITEA_TOKEN" \
"https://git.mosaicstack.dev/api/v1/packages/mosaic/container/$$PKG/-/link/stack")
if [ "$$STATUS" = "201" ] || [ "$$STATUS" = "204" ]; then
echo " Linked $$PKG"
return 0
elif [ "$$STATUS" = "400" ]; then
echo " $$PKG already linked"
return 0
elif [ "$$STATUS" = "404" ] && [ $$attempt -lt 3 ]; then
echo " $$PKG not found yet, retrying in 5s (attempt $$attempt/3)..."
sleep 5
else
echo " FAILED: $$PKG status $$STATUS"
cat /tmp/link-response.txt
return 1
fi
done
}
link_package "stack-api"
when:
- branch: [main]
event: [push, manual, tag]
depends_on:
- security-trivy-api
+40
View File
@@ -0,0 +1,40 @@
# Build & push the pre-baked CI base image (Dockerfile.ci) to the Gitea
# registry CI already publishes to. Reuses the exact kaniko + auth pattern
# from publish.yml (REGISTRY_USER/REGISTRY_PASS from_secret, /kaniko/.docker
# config.json). Other pipelines (ci.yml, publish.yml) pull `ci-base:latest`
# for their install step.
#
# Rebuild ONLY when the dependency set or the image recipe changes — a normal
# code push must not trigger a 25-min image build. `path` applies to push/PR
# events; `event: tag` (releases) rebuilds unconditionally so a tagged release
# always ships a fresh base.
when:
- event: tag
- event: [push, manual]
branch: main
path:
include:
- 'pnpm-lock.yaml'
- 'Dockerfile.ci'
steps:
build-ci-base:
image: gcr.io/kaniko-project/executor:debug
environment:
REGISTRY_USER:
from_secret: REGISTRY_USERNAME
REGISTRY_PASS:
from_secret: REGISTRY_PASSWORD
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
CI_COMMIT_SHA: ${CI_COMMIT_SHA}
commands:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json
- |
# Lockfile-hash tag: an immutable identity for the exact dep set baked
# into this image. `:latest` is the mutable pointer pipelines consume.
LOCK_HASH=$(sha256sum pnpm-lock.yaml | cut -c1-12)
DESTINATIONS="--destination git.mosaicstack.dev/mosaicstack/stack/ci-base:latest"
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/ci-base:lock-$LOCK_HASH"
/kaniko/executor --context . --dockerfile Dockerfile.ci $DESTINATIONS
+227
View File
@@ -0,0 +1,227 @@
# &node_image is the pre-baked CI base built by .woodpecker/ci-image.yml:
# 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:lock-9cb7ffcd8828'
- &enable_pnpm 'corepack enable'
when:
# 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
# Turbo remote cache (turbo.mosaicstack.dev) is configured via Woodpecker
# repository-level environment variables (TURBO_API, TURBO_TEAM, TURBO_TOKEN).
# This avoids from_secret which is blocked on pull_request events.
# If the env vars aren't set, turbo falls back to local cache only.
steps:
install:
image: *node_image
commands:
- corepack enable
# python3/make/g++ are baked into ci-base; --prefer-offline resolves from
# the baked pnpm store.
- pnpm install --frozen-lockfile --prefer-offline
# ---------------------------------------------------------------------------
# The steps below (sanitization, upgrade-guard, typecheck, lint, format,
# test) are the COMPLETE mandatory verification set. SDLC-D-034 mirrors them
# one-for-one in the canonical terminal verification command — root
# `pnpm verify:release` (scripts/verify-release.mjs) — which the publish
# pipeline (.woodpecker/publish.yml `verify` step) runs before ANY publish
# effect. These lines stay direct (not routed through the runner) because the
# #1017 test-enumeration guard audits framework tool paths through THIS
# surface; scripts/verify-release.test.mjs enforces that the runner's stage
# table keeps matching these commands exactly, so the two cannot drift.
# ---------------------------------------------------------------------------
# Canonical verify:release stage `sanitization`.
# Blocking gate: public framework package must contain no operator-specific
# personal data or private $HOME defaults. Runs early (no node_modules needed).
sanitization:
image: *node_image
commands:
- apk add --no-cache bash
- bash packages/mosaic/framework/tools/quality/scripts/verify-sanitized.sh
# Resident line-count ceiling over framework-owned resident files
# (Constitution + dispatcher + each RUNTIME.md slice). See DESIGN §7 / R9.
- bash packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh --self-test
- bash packages/mosaic/framework/tools/quality/scripts/check-resident-budget.sh
# Test-membership guard (#1017): also first link of test:framework-shell.
# Invoked from BOTH surfaces it audits (F2, PR #1018) — the guard is link
# [0] of the pnpm chain, so severing that chain would silence it together
# with everything it guards; this direct line keeps one instrument running.
- bash packages/mosaic/framework/tools/quality/scripts/check-test-enumeration.sh
# Tool-index gate: a shipped wrapper that appears in no resident index doc
# is undiscoverable from inside a session, and an agent that cannot learn a
# wrapper exists reaches for raw curl instead — which is how a Gitea review
# got filed PENDING three times. Ships-and-documented is one commit, or red.
- bash packages/mosaic/framework/tools/quality/scripts/check-tools-index.sh --self-test
- bash packages/mosaic/framework/tools/quality/scripts/check-tools-index.sh
# Hermetic regression for issue-close.sh (#1081): mocks tea/curl onto PATH
# and sandboxes a throwaway git repo, so it resolves no real credentials and
# joins CI directly rather than the exclusions file.
- bash packages/mosaic/framework/tools/git/test-issue-close-fail-closed.sh
# Hermetic regression for the git identity ladder (#1356): mock tea on PATH,
# sandboxed repo, no real credentials (3/3 green under an empty HOME). Pins
# fail-closed: a seat whose login is missing gets a named error, never a
# borrowed identity. Joins CI directly; its #1007 exclusion is burned down.
- bash packages/mosaic/framework/tools/git/test-gitea-login-resolution.sh
# Hermetic regression for issue-view.sh (#1357): mock tea/curl, sandboxed
# repo. Pins that comment BODIES render on both paths and that a tea
# failure is named as what it was (git-config vs credential).
- bash packages/mosaic/framework/tools/git/test-issue-view-comments.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
# operator-owned path. The HARD GATE proves an unanticipated operator sentinel
# survives a keep-mode reseed byte-identical (with rsync present AND absent —
# keep mode is a single cp-based path that must not depend on rsync), and that a
# corrupt/empty/missing manifest aborts fail-closed leaving operator files
# untouched (B2/B3). The rollback gate proves a mid-sync failure is rolled back
# from the pre-update snapshot (B1). The durable-snapshot gate (#791 PR2) proves
# the retained, operator-scoped pre-update backup is taken before any mutation
# (0700/0600, secret never logged, retention-pruned) and that the post-sync
# verify net restores any operator file a manifest bug lets the sync touch. The
# migration matrix pins the v2→v3 contract-file semantics. Pure bash, no
# node_modules — runs early alongside sanitization.
upgrade-guard:
image: *node_image
commands:
- apk add --no-cache bash rsync
- bash packages/mosaic/framework/tools/quality/scripts/test-upgrade-manifest-guard.sh
- bash packages/mosaic/framework/tools/quality/scripts/test-upgrade-rollback.sh
- bash packages/mosaic/framework/tools/quality/scripts/test-upgrade-durable-snapshot.sh
- bash packages/mosaic/framework/tools/quality/scripts/test-install-migration.sh
# Canonical verify:release stage `typecheck` — the same `pnpm typecheck`
# invocation (which runs the checkout preflight first, then turbo).
typecheck:
image: *node_image
commands:
- *enable_pnpm
- pnpm typecheck
depends_on:
- install
- sanitization
- upgrade-guard
# lint, format, and test are independent — run in parallel after typecheck.
# Each runs exactly its canonical verify:release stage command.
lint:
image: *node_image
commands:
- *enable_pnpm
- pnpm lint
depends_on:
- typecheck
format:
image: *node_image
commands:
- *enable_pnpm
- pnpm format:check
depends_on:
- typecheck
# Canonical verify:release stage `test` — the `pnpm test` line below is the
# shared command; everything else in this step is PIPELINE-LEVEL
# prerequisite the canonical command expects its caller to provide (SDLC-D-034):
# the ci-postgres service + pg_isready wait + db:migrate (postgres path),
# `apk add openssl`, and the pinned pi install. None of those can move into
# the runner (it must also work locally on the PGlite path with no database).
test:
image: *node_image
environment:
# Avoid the namespace-level Woodpecker DB service named "postgres".
# The Kubernetes backend exposes service containers by step name.
DATABASE_URL: postgresql://mosaic:mosaic@ci-postgres:5432/mosaic
commands:
- *enable_pnpm
# openssl (#912) is the wake HMAC signer: the digest H1/H2, beacon B12,
# and install I8 legs hard-require it in CI. It is baked into ci-base via
# Dockerfile.ci, but ci-base only rebuilds on push-to-main/tag — this
# `apk add` guarantees openssl is present on PR pipelines too (and is a
# fast no-op once the rebuilt image already ships it).
- apk add --no-cache openssl
# Pi runtime (Invariant R): invariant_r_unittest.py hard-requires an
# installed `pi` binary at exactly this measured version — the test
# boots Pi's real tool registry to prove the read-only carve-out
# resolves to real, unshadowed builtins, and fails loud (by design)
# when the runtime is absent or drifts. The canonical Pi is
# @earendil-works/[email protected] exactly (@mariozechner/* is
# embedded-legacy). Step-level install because ci-base image publishes
# are currently blocked on registry auth; fold into Dockerfile.ci once
# that is fixed, keeping this as a fast no-op guard.
- npm install -g @earendil-works/[email protected]
# postgresql-client (pg_isready) is baked into ci-base.
# Wait up to 60s for CI postgres to be ready; fail fast if it never comes up.
- |
ready=0
for i in $(seq 1 60); do
if pg_isready -h ci-postgres -p 5432 -U mosaic; then
ready=1
break
fi
echo "Waiting for ci-postgres ($i/60)..."
sleep 1
done
if [ "$ready" -ne 1 ]; then
echo "ci-postgres did not become ready" >&2
exit 1
fi
# Run migrations (DATABASE_URL is set in environment above)
- pnpm --filter @mosaicstack/db run db:migrate
# Run all tests
- pnpm test
depends_on:
- typecheck
services:
ci-postgres:
image: pgvector/pgvector:pg17
environment:
POSTGRES_USER: mosaic
POSTGRES_PASSWORD: mosaic
POSTGRES_DB: mosaic
-178
View File
@@ -1,178 +0,0 @@
# Coordinator Pipeline - Mosaic Stack
# Quality gates, build, and Docker publish for mosaic-coordinator (Python)
#
# Triggers on: apps/coordinator/**
# Security chain: bandit + pip-audit + Trivy container scan
when:
- event: [push, pull_request, manual]
path:
include:
- "apps/coordinator/**"
- ".woodpecker/coordinator.yml"
variables:
- &python_image "python:3.11-slim"
- &activate_venv |
cd apps/coordinator
. venv/bin/activate
- &kaniko_setup |
mkdir -p /kaniko/.docker
echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$GITEA_USER\",\"password\":\"$GITEA_TOKEN\"}}}" > /kaniko/.docker/config.json
steps:
# === Quality Gates ===
install:
image: *python_image
commands:
- cd apps/coordinator
- python -m venv venv
- . venv/bin/activate
- pip install --no-cache-dir --upgrade "pip>=25.3"
- pip install --no-cache-dir --extra-index-url https://git.mosaicstack.dev/api/packages/mosaic/pypi/simple/ -e ".[dev]"
- pip install --no-cache-dir bandit pip-audit
ruff-check:
image: *python_image
commands:
- *activate_venv
- ruff check src/ tests/
depends_on:
- install
mypy:
image: *python_image
commands:
- *activate_venv
- mypy src/
depends_on:
- install
security-bandit:
image: *python_image
commands:
- *activate_venv
- bandit -r src/ -c bandit.yaml -f screen
depends_on:
- install
security-pip-audit:
image: *python_image
commands:
- *activate_venv
- pip-audit
depends_on:
- install
test:
image: *python_image
commands:
- *activate_venv
- pytest
depends_on:
- install
# === Docker Build & Push ===
docker-build-coordinator:
image: gcr.io/kaniko-project/executor:debug
environment:
GITEA_USER:
from_secret: gitea_username
GITEA_TOKEN:
from_secret: gitea_token
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
commands:
- *kaniko_setup
- |
DESTINATIONS=""
if [ -n "$CI_COMMIT_TAG" ]; then
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-coordinator:$CI_COMMIT_TAG"
elif [ "$CI_COMMIT_BRANCH" = "main" ]; then
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-coordinator:latest"
fi
/kaniko/executor --context apps/coordinator --dockerfile apps/coordinator/Dockerfile --snapshot-mode=redo $DESTINATIONS
when:
- branch: [main]
event: [push, manual, tag]
depends_on:
- ruff-check
- mypy
- security-bandit
- security-pip-audit
- test
# === Container Security Scan ===
security-trivy-coordinator:
image: aquasec/trivy:latest
environment:
GITEA_USER:
from_secret: gitea_username
GITEA_TOKEN:
from_secret: gitea_token
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
commands:
- |
if [ -n "$$CI_COMMIT_TAG" ]; then
SCAN_TAG="$$CI_COMMIT_TAG"
elif [ "$$CI_COMMIT_BRANCH" = "main" ]; then
SCAN_TAG="latest"
else
SCAN_TAG="latest"
fi
mkdir -p ~/.docker
echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$$GITEA_USER\",\"password\":\"$$GITEA_TOKEN\"}}}" > ~/.docker/config.json
trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed \
--ignorefile .trivyignore \
git.mosaicstack.dev/mosaic/stack-coordinator:$$SCAN_TAG
when:
- branch: [main]
event: [push, manual, tag]
depends_on:
- docker-build-coordinator
# === Package Linking ===
link-packages:
image: alpine:3
environment:
GITEA_TOKEN:
from_secret: gitea_token
commands:
- apk add --no-cache curl
- sleep 10
- |
set -e
link_package() {
PKG="$$1"
echo "Linking $$PKG..."
for attempt in 1 2 3; do
STATUS=$$(curl -s -o /tmp/link-response.txt -w "%{http_code}" -X POST \
-H "Authorization: token $$GITEA_TOKEN" \
"https://git.mosaicstack.dev/api/v1/packages/mosaic/container/$$PKG/-/link/stack")
if [ "$$STATUS" = "201" ] || [ "$$STATUS" = "204" ]; then
echo " Linked $$PKG"
return 0
elif [ "$$STATUS" = "400" ]; then
echo " $$PKG already linked"
return 0
elif [ "$$STATUS" = "404" ] && [ $$attempt -lt 3 ]; then
echo " $$PKG not found yet, retrying in 5s (attempt $$attempt/3)..."
sleep 5
else
echo " FAILED: $$PKG status $$STATUS"
cat /tmp/link-response.txt
return 1
fi
done
}
link_package "stack-coordinator"
when:
- branch: [main]
event: [push, manual, tag]
depends_on:
- security-trivy-coordinator
-170
View File
@@ -1,170 +0,0 @@
# Infrastructure Pipeline - Mosaic Stack
# Docker build, Trivy scan, and publish for postgres + openbao images
#
# Triggers on: docker/**
# No quality gates — infrastructure images (base image + config only)
when:
- event: [push, manual, tag]
path:
include:
- "docker/**"
- ".woodpecker/infra.yml"
variables:
- &kaniko_setup |
mkdir -p /kaniko/.docker
echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$GITEA_USER\",\"password\":\"$GITEA_TOKEN\"}}}" > /kaniko/.docker/config.json
steps:
# === Docker Build & Push ===
docker-build-postgres:
image: gcr.io/kaniko-project/executor:debug
environment:
GITEA_USER:
from_secret: gitea_username
GITEA_TOKEN:
from_secret: gitea_token
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
commands:
- *kaniko_setup
- |
DESTINATIONS=""
if [ -n "$CI_COMMIT_TAG" ]; then
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-postgres:$CI_COMMIT_TAG"
elif [ "$CI_COMMIT_BRANCH" = "main" ]; then
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-postgres:latest"
fi
/kaniko/executor --context docker/postgres --dockerfile docker/postgres/Dockerfile --snapshot-mode=redo $DESTINATIONS
when:
- branch: [main]
event: [push, manual, tag]
docker-build-openbao:
image: gcr.io/kaniko-project/executor:debug
environment:
GITEA_USER:
from_secret: gitea_username
GITEA_TOKEN:
from_secret: gitea_token
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
commands:
- *kaniko_setup
- |
DESTINATIONS=""
if [ -n "$CI_COMMIT_TAG" ]; then
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-openbao:$CI_COMMIT_TAG"
elif [ "$CI_COMMIT_BRANCH" = "main" ]; then
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-openbao:latest"
fi
/kaniko/executor --context docker/openbao --dockerfile docker/openbao/Dockerfile --snapshot-mode=redo $DESTINATIONS
when:
- branch: [main]
event: [push, manual, tag]
# === Container Security Scans ===
security-trivy-postgres:
image: aquasec/trivy:latest
environment:
GITEA_USER:
from_secret: gitea_username
GITEA_TOKEN:
from_secret: gitea_token
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
commands:
- |
if [ -n "$$CI_COMMIT_TAG" ]; then
SCAN_TAG="$$CI_COMMIT_TAG"
elif [ "$$CI_COMMIT_BRANCH" = "main" ]; then
SCAN_TAG="latest"
else
SCAN_TAG="latest"
fi
mkdir -p ~/.docker
echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$$GITEA_USER\",\"password\":\"$$GITEA_TOKEN\"}}}" > ~/.docker/config.json
trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed \
--ignorefile .trivyignore \
git.mosaicstack.dev/mosaic/stack-postgres:$$SCAN_TAG
when:
- branch: [main]
event: [push, manual, tag]
depends_on:
- docker-build-postgres
security-trivy-openbao:
image: aquasec/trivy:latest
environment:
GITEA_USER:
from_secret: gitea_username
GITEA_TOKEN:
from_secret: gitea_token
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
commands:
- |
if [ -n "$$CI_COMMIT_TAG" ]; then
SCAN_TAG="$$CI_COMMIT_TAG"
elif [ "$$CI_COMMIT_BRANCH" = "main" ]; then
SCAN_TAG="latest"
else
SCAN_TAG="latest"
fi
mkdir -p ~/.docker
echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$$GITEA_USER\",\"password\":\"$$GITEA_TOKEN\"}}}" > ~/.docker/config.json
trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed \
--ignorefile .trivyignore \
git.mosaicstack.dev/mosaic/stack-openbao:$$SCAN_TAG
when:
- branch: [main]
event: [push, manual, tag]
depends_on:
- docker-build-openbao
# === Package Linking ===
link-packages:
image: alpine:3
environment:
GITEA_TOKEN:
from_secret: gitea_token
commands:
- apk add --no-cache curl
- sleep 10
- |
set -e
link_package() {
PKG="$$1"
echo "Linking $$PKG..."
for attempt in 1 2 3; do
STATUS=$$(curl -s -o /tmp/link-response.txt -w "%{http_code}" -X POST \
-H "Authorization: token $$GITEA_TOKEN" \
"https://git.mosaicstack.dev/api/v1/packages/mosaic/container/$$PKG/-/link/stack")
if [ "$$STATUS" = "201" ] || [ "$$STATUS" = "204" ]; then
echo " Linked $$PKG"
return 0
elif [ "$$STATUS" = "400" ]; then
echo " $$PKG already linked"
return 0
elif [ "$$STATUS" = "404" ] && [ $$attempt -lt 3 ]; then
echo " $$PKG not found yet, retrying in 5s (attempt $$attempt/3)..."
sleep 5
else
echo " FAILED: $$PKG status $$STATUS"
cat /tmp/link-response.txt
return 1
fi
done
}
link_package "stack-postgres"
link_package "stack-openbao"
when:
- branch: [main]
event: [push, manual, tag]
depends_on:
- security-trivy-postgres
- security-trivy-openbao
-202
View File
@@ -1,202 +0,0 @@
# Orchestrator Pipeline - Mosaic Stack
# Quality gates, build, and Docker publish for @mosaic/orchestrator
#
# Triggers on: apps/orchestrator/**, packages/**, root configs
# Security chain: source audit + Trivy container scan
when:
- event: [push, pull_request, manual]
path:
include:
- "apps/orchestrator/**"
- "packages/**"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- "turbo.json"
- "package.json"
- ".woodpecker/orchestrator.yml"
- ".trivyignore"
variables:
- &node_image "node:24-alpine"
- &install_deps |
corepack enable
pnpm install --frozen-lockfile
- &use_deps |
corepack enable
- &turbo_env
TURBO_API:
from_secret: turbo_api
TURBO_TOKEN:
from_secret: turbo_token
TURBO_TEAM:
from_secret: turbo_team
- &kaniko_setup |
mkdir -p /kaniko/.docker
echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$GITEA_USER\",\"password\":\"$GITEA_TOKEN\"}}}" > /kaniko/.docker/config.json
steps:
# === Quality Gates ===
install:
image: *node_image
commands:
- *install_deps
security-audit:
image: *node_image
commands:
- *use_deps
- pnpm audit --audit-level=high
depends_on:
- install
lint:
image: *node_image
environment:
SKIP_ENV_VALIDATION: "true"
<<: *turbo_env
commands:
- *use_deps
- pnpm turbo lint --filter=@mosaic/orchestrator
depends_on:
- install
typecheck:
image: *node_image
environment:
SKIP_ENV_VALIDATION: "true"
<<: *turbo_env
commands:
- *use_deps
- pnpm turbo typecheck --filter=@mosaic/orchestrator
depends_on:
- install
test:
image: *node_image
environment:
SKIP_ENV_VALIDATION: "true"
<<: *turbo_env
commands:
- *use_deps
- pnpm turbo test --filter=@mosaic/orchestrator
depends_on:
- install
# === Build ===
build:
image: *node_image
environment:
SKIP_ENV_VALIDATION: "true"
NODE_ENV: "production"
<<: *turbo_env
commands:
- *use_deps
- pnpm turbo build --filter=@mosaic/orchestrator
depends_on:
- lint
- typecheck
- test
- security-audit
# === Docker Build & Push ===
docker-build-orchestrator:
image: gcr.io/kaniko-project/executor:debug
environment:
GITEA_USER:
from_secret: gitea_username
GITEA_TOKEN:
from_secret: gitea_token
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
commands:
- *kaniko_setup
- |
DESTINATIONS=""
if [ -n "$CI_COMMIT_TAG" ]; then
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-orchestrator:$CI_COMMIT_TAG"
elif [ "$CI_COMMIT_BRANCH" = "main" ]; then
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-orchestrator:latest"
fi
/kaniko/executor --context . --dockerfile apps/orchestrator/Dockerfile --snapshot-mode=redo $DESTINATIONS
when:
- branch: [main]
event: [push, manual, tag]
depends_on:
- build
# === Container Security Scan ===
security-trivy-orchestrator:
image: aquasec/trivy:latest
environment:
GITEA_USER:
from_secret: gitea_username
GITEA_TOKEN:
from_secret: gitea_token
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
commands:
- |
if [ -n "$$CI_COMMIT_TAG" ]; then
SCAN_TAG="$$CI_COMMIT_TAG"
elif [ "$$CI_COMMIT_BRANCH" = "main" ]; then
SCAN_TAG="latest"
else
SCAN_TAG="latest"
fi
mkdir -p ~/.docker
echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$$GITEA_USER\",\"password\":\"$$GITEA_TOKEN\"}}}" > ~/.docker/config.json
trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed \
--ignorefile .trivyignore \
git.mosaicstack.dev/mosaic/stack-orchestrator:$$SCAN_TAG
when:
- branch: [main]
event: [push, manual, tag]
depends_on:
- docker-build-orchestrator
# === Package Linking ===
link-packages:
image: alpine:3
environment:
GITEA_TOKEN:
from_secret: gitea_token
commands:
- apk add --no-cache curl
- sleep 10
- |
set -e
link_package() {
PKG="$$1"
echo "Linking $$PKG..."
for attempt in 1 2 3; do
STATUS=$$(curl -s -o /tmp/link-response.txt -w "%{http_code}" -X POST \
-H "Authorization: token $$GITEA_TOKEN" \
"https://git.mosaicstack.dev/api/v1/packages/mosaic/container/$$PKG/-/link/stack")
if [ "$$STATUS" = "201" ] || [ "$$STATUS" = "204" ]; then
echo " Linked $$PKG"
return 0
elif [ "$$STATUS" = "400" ]; then
echo " $$PKG already linked"
return 0
elif [ "$$STATUS" = "404" ] && [ $$attempt -lt 3 ]; then
echo " $$PKG not found yet, retrying in 5s (attempt $$attempt/3)..."
sleep 5
else
echo " FAILED: $$PKG status $$STATUS"
cat /tmp/link-response.txt
return 1
fi
done
}
link_package "stack-orchestrator"
when:
- branch: [main]
event: [push, manual, tag]
depends_on:
- security-trivy-orchestrator
+361
View File
@@ -0,0 +1,361 @@
# Build, publish npm packages, and push Docker images
# Runs on main for stable publishes and on next for integration-line prereleases/images
#
# SDLC-D-034 publish gate: every publish effect (publish-npm, publish-next-npm,
# and every image build/push step) depends DIRECTLY on the `verify` step below.
# `verify` (a) asserts the provider's commit identity matches the actual
# checkout (CI_COMMIT_SHA == git rev-parse HEAD, fail closed on mismatch or
# emptiness) and (b) runs the canonical terminal verification command
# (`pnpm verify:release`), which mirrors the PR CI pipeline's complete
# mandatory set (sanitization, upgrade-guard, preflight+typecheck, lint,
# format:check, test, build) — see scripts/verify-release.mjs. A missing,
# failed, skipped, cancelled, or inconclusive verification therefore skips the
# dependent publish effects (fail closed). Path-filtered short-circuits may
# skip publish EFFECTS (e.g. docs-only merges) but never bypass `verify` for a
# publish that does run: `verify` itself carries no path filter.
# scripts/verify-release.test.mjs enforces this DAG invariant at checkout time.
variables:
# Pre-baked CI base (see .woodpecker/ci-image.yml): node:24-alpine +
# toolchain + warm pnpm store. Kills the second cold install publish pays.
# 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
# images (gateway/appservice/web do not depend on @mosaicstack/mosaic). Releases
# (tags) always build everything. Exclude-list keeps the default SAFE: any
# non-excluded change still builds, so no transitive dep can silently go stale.
# (Woodpecker: `when` entries are OR'd; `path` applies to push/PR only — hence
# the separate `event: tag` entry.)
- &image_build_when
- event: tag
- event: [push, manual]
branch: main
path:
exclude:
- 'packages/mosaic/**'
- 'docs/**'
- '**/*.md'
- '.woodpecker/**'
- event: [push, manual]
branch: next
- &main_image_build_when
- event: tag
- event: [push, manual]
branch: main
path:
exclude:
- 'packages/mosaic/**'
- 'docs/**'
- '**/*.md'
- '.woodpecker/**'
when:
- branch: [main, next]
event: [push, manual, tag]
steps:
install:
image: *node_image
commands:
- corepack enable
# Resolve from the baked pnpm store instead of a cold network fetch.
- pnpm install --frozen-lockfile --prefer-offline
# SDLC-D-034 exact-commit publish gate. No `when`/path filter on purpose: it
# runs for every event this pipeline serves so no publish effect can ever
# start without it. Fails closed on commit-identity mismatch (or either SHA
# being empty) and on any incomplete verification.
verify:
image: *node_image
commands:
- *enable_pnpm
# (a) Commit identity: the provider's claimed SHA must equal the actual
# checkout HEAD — verification of anything else must never authorize a
# publish of this commit.
- |
if [ -z "$CI_COMMIT_SHA" ]; then
echo "[verify] FATAL: CI_COMMIT_SHA is empty — cannot certify commit identity" >&2
exit 1
fi
CHECKOUT_SHA="$(git rev-parse HEAD 2>/dev/null || true)"
if [ -z "$CHECKOUT_SHA" ]; then
echo "[verify] FATAL: git rev-parse HEAD returned nothing — cannot certify commit identity" >&2
exit 1
fi
if [ "$CI_COMMIT_SHA" != "$CHECKOUT_SHA" ]; then
echo "[verify] FATAL: provider commit ($CI_COMMIT_SHA) != checkout HEAD ($CHECKOUT_SHA)" >&2
exit 1
fi
echo "[verify] commit identity confirmed: $CHECKOUT_SHA"
# (b) Canonical terminal verification. Caller-provided prerequisites the
# runner expects (see .woodpecker/ci.yml comments): bash/rsync for the
# guard stages, openssl + the pinned pi binary for the test stage. git is
# baked into ci-base but re-asserted here so the identity check above can
# never silently depend on a stale baked image. DATABASE_URL is
# deliberately NOT set: the canonical command must hold on the PGlite
# path too and never sets or requires a database itself.
- apk add --no-cache bash rsync openssl git
- npm install -g @earendil-works/[email protected]
- pnpm verify:release
depends_on:
- install
build:
image: *node_image
commands:
- *enable_pnpm
- pnpm build
depends_on:
- install
- verify
publish-npm:
image: *node_image
# Publish only when a publishable package changed (or on a release tag); a
# pure-docs merge runs no publish. Cheap step, but gated for cleanliness.
when:
- event: tag
- event: [push, manual]
branch: main
path:
include:
- 'packages/**'
environment:
NPM_TOKEN:
from_secret: gitea_token
commands:
- *enable_pnpm
# Configure auth for Gitea npm registry
- |
echo "//git.mosaicstack.dev/api/packages/mosaicstack/npm/:_authToken=$NPM_TOKEN" > ~/.npmrc
echo "@mosaicstack:registry=https://git.mosaicstack.dev/api/packages/mosaicstack/npm/" >> ~/.npmrc
# Publish non-private packages to Gitea.
#
# The only publish failure we tolerate is "version already exists" —
# that legitimately happens when only some packages were bumped in
# the merge. Any other failure (registry 404, auth error, network
# error) MUST fail the pipeline loudly: the previous
# `|| echo "... continuing"` fallback silently hid a 404 from the
# Gitea org rename and caused every @mosaicstack/* publish to fall
# on the floor while CI still reported green.
- |
# Portable sh (Alpine ash) — avoid bashisms like PIPESTATUS.
set +e
pnpm --filter "@mosaicstack/*" --filter "!@mosaicstack/web" publish --no-git-checks --access public >/tmp/publish.log 2>&1
EXIT=$?
set -e
cat /tmp/publish.log
if [ "$EXIT" -eq 0 ]; then
echo "[publish] all packages published successfully"
exit 0
fi
# Hard registry / auth / network errors → fatal. Match npm's own
# error lines specifically to avoid false positives on arbitrary
# log text that happens to contain "E404" etc.
if grep -qE "npm (error|ERR!) code (E404|E401|ENEEDAUTH|ECONNREFUSED|ETIMEDOUT|ENOTFOUND)" /tmp/publish.log; then
echo "[publish] FATAL: registry/auth/network error detected — failing pipeline" >&2
exit 1
fi
# Only tolerate the explicit "version already published" case.
# npm returns this as E403 with body "You cannot publish over..."
# or EPUBLISHCONFLICT depending on version.
if grep -qE "EPUBLISHCONFLICT|You cannot publish over|previously published" /tmp/publish.log; then
echo "[publish] some packages already at this version — continuing (non-fatal)"
exit 0
fi
echo "[publish] FATAL: publish failed with unrecognized error — failing pipeline" >&2
exit 1
depends_on:
- build
- verify
publish-next-npm:
image: *node_image
# Durable @next integration-line publish. Runs only on next; never writes
# the latest dist-tag and never commits the computed prerelease versions.
when:
- event: [push, manual]
branch: next
environment:
NPM_TOKEN:
from_secret: gitea_token
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
CI_PIPELINE_NUMBER: ${CI_PIPELINE_NUMBER}
commands:
- *enable_pnpm
- |
if [ "$CI_COMMIT_BRANCH" != "next" ]; then
echo "[publish-next] FATAL: publish-next-npm may only run on next (got '$CI_COMMIT_BRANCH')" >&2
exit 1
fi
if [ -z "$CI_PIPELINE_NUMBER" ]; then
echo "[publish-next] FATAL: CI_PIPELINE_NUMBER is required for prerelease versioning" >&2
exit 1
fi
echo "//git.mosaicstack.dev/api/packages/mosaicstack/npm/:_authToken=$NPM_TOKEN" > ~/.npmrc
echo "@mosaicstack:registry=https://git.mosaicstack.dev/api/packages/mosaicstack/npm/" >> ~/.npmrc
DIST_TAGS_JSON="$(npm view @mosaicstack/mosaic dist-tags --registry https://git.mosaicstack.dev/api/packages/mosaicstack/npm/ --json)"
DIST_TAGS_JSON="$DIST_TAGS_JSON" node -e 'const tags = JSON.parse(process.env.DIST_TAGS_JSON || "{}"); if (!tags || typeof tags !== "object" || !Object.hasOwn(tags, "latest")) { throw new Error("Gitea npm registry did not return a usable dist-tags object"); } console.log("[publish-next] registry dist-tags OK: latest=" + tags.latest);'
node <<'NODE'
const fs = require('node:fs');
const path = require('node:path');
const pipelineNumber = process.env.CI_PIPELINE_NUMBER;
const roots = ['apps', 'packages', 'plugins'];
const updated = [];
function walk(dir) {
if (!fs.existsSync(dir)) return;
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name === '.turbo') continue;
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
const packagePath = path.join(fullPath, 'package.json');
if (fs.existsSync(packagePath)) updatePackage(packagePath);
walk(fullPath);
}
}
}
function updatePackage(packagePath) {
const manifest = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
if (!manifest.name?.startsWith('@mosaicstack/') || manifest.private) return;
const stableMatch = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(manifest.version);
if (!stableMatch) {
throw new Error(manifest.name + " has unsupported semver version '" + manifest.version + "'");
}
const [, major, minor, patch] = stableMatch;
const oldVersion = manifest.version;
manifest.version = major + '.' + minor + '.' + (Number(patch) + 1) + '-next.' + pipelineNumber;
fs.writeFileSync(packagePath, JSON.stringify(manifest, null, 2) + '\n');
updated.push(manifest.name + ' ' + oldVersion + ' -> ' + manifest.version);
}
for (const root of roots) walk(root);
if (updated.length === 0) throw new Error('No publishable @mosaicstack/* packages found');
console.log('[publish-next] computed prerelease versions for ' + updated.length + ' packages:');
for (const line of updated) console.log('[publish-next] ' + line);
NODE
pnpm --filter "@mosaicstack/*" --filter "!@mosaicstack/web" --filter "!@mosaicstack/mosaic-as" publish --no-git-checks --access public --tag next
EXPECTED_VERSION="$(node -p "require('./packages/mosaic/package.json').version")"
RESOLVED_VERSION="$(npm view @mosaicstack/mosaic@next version --registry https://git.mosaicstack.dev/api/packages/mosaicstack/npm/)"
if [ "$RESOLVED_VERSION" != "$EXPECTED_VERSION" ]; then
echo "[publish-next] FATAL: @mosaicstack/mosaic@next resolved '$RESOLVED_VERSION', expected '$EXPECTED_VERSION'" >&2
exit 1
fi
echo "[publish-next] @mosaicstack/mosaic@next resolves to $RESOLVED_VERSION"
depends_on:
- build
- verify
# TODO: Uncomment when ready to publish to npmjs.org
# publish-npmjs:
# image: *node_image
# environment:
# NPM_TOKEN:
# from_secret: npmjs_token
# commands:
# - *enable_pnpm
# - apk add --no-cache jq bash
# - bash scripts/publish-npmjs.sh
# depends_on:
# - build
# - verify
# when:
# - event: [tag]
build-gateway:
image: gcr.io/kaniko-project/executor:debug
when: *image_build_when
environment:
REGISTRY_USER:
from_secret: REGISTRY_USERNAME
REGISTRY_PASS:
from_secret: REGISTRY_PASSWORD
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
CI_COMMIT_SHA: ${CI_COMMIT_SHA}
commands:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json
- |
DESTINATIONS="--destination git.mosaicstack.dev/mosaicstack/stack/gateway:sha-${CI_COMMIT_SHA:0:7}"
if [ "$CI_COMMIT_BRANCH" = "next" ]; then
if [ -n "$CI_COMMIT_TAG" ]; then
echo "[publish] FATAL: next gateway publish must be sha-only; refusing tag '$CI_COMMIT_TAG'" >&2
exit 1
fi
echo "[publish] next gateway publish is sha-only"
elif [ "$CI_COMMIT_BRANCH" = "main" ]; then
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/gateway:latest"
elif [ -z "$CI_COMMIT_TAG" ]; then
echo "[publish] FATAL: gateway image publish may only run for main, next, or tag events" >&2
exit 1
fi
if [ -n "$CI_COMMIT_TAG" ]; then
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/gateway:$CI_COMMIT_TAG"
fi
/kaniko/executor --context . --dockerfile docker/gateway.Dockerfile $DESTINATIONS
depends_on:
- build
- verify
build-appservice:
image: gcr.io/kaniko-project/executor:debug
when: *main_image_build_when
environment:
REGISTRY_USER:
from_secret: REGISTRY_USERNAME
REGISTRY_PASS:
from_secret: REGISTRY_PASSWORD
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
CI_COMMIT_SHA: ${CI_COMMIT_SHA}
commands:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json
- |
DESTINATIONS="--destination git.mosaicstack.dev/mosaicstack/stack/appservice:sha-${CI_COMMIT_SHA:0:7}"
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/appservice:latest"
fi
if [ -n "$CI_COMMIT_TAG" ]; then
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/appservice:$CI_COMMIT_TAG"
fi
/kaniko/executor --context . --dockerfile docker/appservice.Dockerfile $DESTINATIONS
depends_on:
- build
- verify
build-web:
image: gcr.io/kaniko-project/executor:debug
when: *main_image_build_when
environment:
REGISTRY_USER:
from_secret: REGISTRY_USERNAME
REGISTRY_PASS:
from_secret: REGISTRY_PASSWORD
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
CI_COMMIT_SHA: ${CI_COMMIT_SHA}
commands:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json
- |
DESTINATIONS="--destination git.mosaicstack.dev/mosaicstack/stack/web:sha-${CI_COMMIT_SHA:0:7}"
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/web:latest"
fi
if [ -n "$CI_COMMIT_TAG" ]; then
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/web:$CI_COMMIT_TAG"
fi
/kaniko/executor --context . --dockerfile docker/web.Dockerfile $DESTINATIONS
depends_on:
- build
- verify
-202
View File
@@ -1,202 +0,0 @@
# Web Pipeline - Mosaic Stack
# Quality gates, build, and Docker publish for @mosaic/web
#
# Triggers on: apps/web/**, packages/**, root configs
# Security chain: source audit + Trivy container scan
when:
- event: [push, pull_request, manual]
path:
include:
- "apps/web/**"
- "packages/**"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- "turbo.json"
- "package.json"
- ".woodpecker/web.yml"
- ".trivyignore"
variables:
- &node_image "node:24-alpine"
- &install_deps |
corepack enable
pnpm install --frozen-lockfile
- &use_deps |
corepack enable
- &turbo_env
TURBO_API:
from_secret: turbo_api
TURBO_TOKEN:
from_secret: turbo_token
TURBO_TEAM:
from_secret: turbo_team
- &kaniko_setup |
mkdir -p /kaniko/.docker
echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$GITEA_USER\",\"password\":\"$GITEA_TOKEN\"}}}" > /kaniko/.docker/config.json
steps:
# === Quality Gates ===
install:
image: *node_image
commands:
- *install_deps
security-audit:
image: *node_image
commands:
- *use_deps
- pnpm audit --audit-level=high
depends_on:
- install
lint:
image: *node_image
environment:
SKIP_ENV_VALIDATION: "true"
<<: *turbo_env
commands:
- *use_deps
- pnpm turbo lint --filter=@mosaic/web
depends_on:
- install
typecheck:
image: *node_image
environment:
SKIP_ENV_VALIDATION: "true"
<<: *turbo_env
commands:
- *use_deps
- pnpm turbo typecheck --filter=@mosaic/web
depends_on:
- install
test:
image: *node_image
environment:
SKIP_ENV_VALIDATION: "true"
<<: *turbo_env
commands:
- *use_deps
- pnpm turbo test --filter=@mosaic/web
depends_on:
- install
# === Build ===
build:
image: *node_image
environment:
SKIP_ENV_VALIDATION: "true"
NODE_ENV: "production"
<<: *turbo_env
commands:
- *use_deps
- pnpm turbo build --filter=@mosaic/web
depends_on:
- lint
- typecheck
- test
- security-audit
# === Docker Build & Push ===
docker-build-web:
image: gcr.io/kaniko-project/executor:debug
environment:
GITEA_USER:
from_secret: gitea_username
GITEA_TOKEN:
from_secret: gitea_token
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
commands:
- *kaniko_setup
- |
DESTINATIONS=""
if [ -n "$CI_COMMIT_TAG" ]; then
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-web:$CI_COMMIT_TAG"
elif [ "$CI_COMMIT_BRANCH" = "main" ]; then
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/stack-web:latest"
fi
/kaniko/executor --context . --dockerfile apps/web/Dockerfile --snapshot-mode=redo --build-arg NEXT_PUBLIC_API_URL=https://api.mosaicstack.dev $DESTINATIONS
when:
- branch: [main]
event: [push, manual, tag]
depends_on:
- build
# === Container Security Scan ===
security-trivy-web:
image: aquasec/trivy:latest
environment:
GITEA_USER:
from_secret: gitea_username
GITEA_TOKEN:
from_secret: gitea_token
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
commands:
- |
if [ -n "$$CI_COMMIT_TAG" ]; then
SCAN_TAG="$$CI_COMMIT_TAG"
elif [ "$$CI_COMMIT_BRANCH" = "main" ]; then
SCAN_TAG="latest"
else
SCAN_TAG="latest"
fi
mkdir -p ~/.docker
echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$$GITEA_USER\",\"password\":\"$$GITEA_TOKEN\"}}}" > ~/.docker/config.json
trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed \
--ignorefile .trivyignore \
git.mosaicstack.dev/mosaic/stack-web:$$SCAN_TAG
when:
- branch: [main]
event: [push, manual, tag]
depends_on:
- docker-build-web
# === Package Linking ===
link-packages:
image: alpine:3
environment:
GITEA_TOKEN:
from_secret: gitea_token
commands:
- apk add --no-cache curl
- sleep 10
- |
set -e
link_package() {
PKG="$$1"
echo "Linking $$PKG..."
for attempt in 1 2 3; do
STATUS=$$(curl -s -o /tmp/link-response.txt -w "%{http_code}" -X POST \
-H "Authorization: token $$GITEA_TOKEN" \
"https://git.mosaicstack.dev/api/v1/packages/mosaic/container/$$PKG/-/link/stack")
if [ "$$STATUS" = "201" ] || [ "$$STATUS" = "204" ]; then
echo " Linked $$PKG"
return 0
elif [ "$$STATUS" = "400" ]; then
echo " $$PKG already linked"
return 0
elif [ "$$STATUS" = "404" ] && [ $$attempt -lt 3 ]; then
echo " $$PKG not found yet, retrying in 5s (attempt $$attempt/3)..."
sleep 5
else
echo " FAILED: $$PKG status $$STATUS"
cat /tmp/link-response.txt
return 1
fi
done
}
link_package "stack-web"
when:
- branch: [main]
event: [push, manual, tag]
depends_on:
- security-trivy-web
+159 -55
View File
@@ -1,82 +1,186 @@
# Mosaic Stack — Agent Guidelines
# Agent Guidelines — Mosaic Stack
## Load Order
## Required Load Order
1. `SOUL.md` (repo identity + behavior invariants)
2. `~/.config/mosaic/STANDARDS.md` (machine-wide standards rails)
3. `AGENTS.md` (repo-specific overlay)
4. `.mosaic/repo-hooks.sh` (repo lifecycle hooks)
1. `~/.config/mosaic/SOUL.md`
2. `~/.config/mosaic/STANDARDS.md`
3. `~/.config/mosaic/AGENTS.md`
4. `~/.config/mosaic/guides/E2E-DELIVERY.md`
5. `AGENTS.md` (this file)
6. Runtime-specific guide: `~/.config/mosaic/runtime/<runtime>/RUNTIME.md`
## Runtime Contract
## Project Context
- This file is authoritative for repo-local operations.
- `CLAUDE.md` is a compatibility pointer to `AGENTS.md`.
- Follow universal rails from `~/.config/mosaic/guides/` and `~/.config/mosaic/rails/`.
Mosaic Stack is a self-hosted, multi-user AI agent platform. It is a TypeScript monorepo with a NestJS gateway, Next.js dashboard, Pi SDK agent runtime, and Discord/Telegram plugin architecture.
## Session Lifecycle
### Stack
- **API:** NestJS with Fastify (`apps/gateway`)
- **Web:** Next.js 16 with React 19 (`apps/web`)
- **ORM and database:** Drizzle ORM, PostgreSQL 17, and pgvector (`packages/db`)
- **Authentication:** BetterAuth (`packages/auth`)
- **Agent runtime:** Pi SDK (`apps/gateway`, `packages/mosaic`)
- **Queue:** Valkey 8 (`packages/queue`)
- **Build:** pnpm workspaces and Turborepo
- **CI:** Woodpecker CI
- **Observability:** OpenTelemetry and Jaeger
### Package Map
| Package | Purpose | Key Dependencies |
| ------------------ | ----------------------------- | -------------------------------- |
| `apps/gateway` | NestJS API + WebSocket hub | Fastify, Socket.IO, Pi SDK, OTEL |
| `apps/web` | Next.js dashboard | React 19, Tailwind |
| `packages/types` | Shared TypeScript contracts | class-validator |
| `packages/db` | Drizzle schema and migrations | drizzle-orm, postgres |
| `packages/auth` | BetterAuth configuration | better-auth, @mosaicstack/db |
| `packages/brain` | Structured data layer | @mosaicstack/db |
| `packages/queue` | Valkey task queue and MCP | ioredis |
| `packages/coord` | Mission coordination | @mosaicstack/queue |
| `packages/mosaic` | Unified `mosaic` CLI and TUI | Ink, Pi SDK, commander |
| `plugins/discord` | Discord channel plugin | discord.js |
| `plugins/telegram` | Telegram channel plugin | Telegraf |
## Architecture and Code Conventions
1. Gateway is the single API surface; all clients connect through it.
2. Pi SDK is ESM-only; gateway and CLI code must remain ESM.
3. Use `"type": "module"`, NodeNext module resolution, and `.js` extensions in imports.
4. Keep typed Socket.IO events in `@mosaicstack/types` to enforce client/server contracts.
5. Import OTEL tracing before NestJS bootstrap (`import './tracing.js'`).
6. Use explicit `@Inject()` decorators in NestJS because tsx/esbuild does not emit decorator metadata.
7. Keep DTOs in `*.dto.ts` files at module boundaries.
8. BetterAuth owns authentication tables; their schema is defined in `@mosaicstack/db`.
9. Create a task-specific scratchpad for non-trivial work.
## Development Workflow
Requirements: Node.js 20+, pnpm 10.6.2, and Docker Compose when optional local services are needed.
```bash
bash scripts/agent/session-start.sh
bash scripts/agent/critical.sh
bash scripts/agent/session-end.sh
pnpm install --frozen-lockfile
pnpm preflight
# Optional local queue service only; do not start the full Compose stack.
docker compose up -d valkey
```
Optional:
The pre-push hook requires:
```bash
bash scripts/agent/log-limitation.sh "Short Name"
bash scripts/agent/orchestrator-daemon.sh status
bash scripts/agent/orchestrator-events.sh recent --limit 50
pnpm preflight && pnpm typecheck && pnpm lint && pnpm format:check
```
## Repo Context
- Platform: multi-tenant personal assistant stack
- Monorepo: `pnpm` workspaces + Turborepo
- Core apps: `apps/api` (NestJS), `apps/web` (Next.js), orchestrator/coordinator services
- Infrastructure: Docker Compose + PostgreSQL + Valkey + Authentik
## Quick Command Set
Software delivery also requires the applicable tests. Common repository commands are:
```bash
pnpm install
pnpm dev
pnpm test
pnpm lint
pnpm build
pnpm typecheck # TypeScript checks across the workspace
pnpm lint # ESLint across the workspace
pnpm test # Checkout tests and package Vitest suites
pnpm format:check # Prettier check
pnpm build # Build all packages and applications
```
## Versioning Protocol (HARD GATE)
## Branch Model and Merge Process — `main` and `next` (CANONICAL)
**This project is ALPHA. All versions MUST be `0.0.x`.**
**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`.
- The `0.1.0` release is FORBIDDEN until Jason explicitly authorizes it.
- Every milestone bump increments the patch: `0.0.20``0.0.21``0.0.22`, etc.
- ALL package.json files in the monorepo MUST stay in sync at the same version.
- Use `scripts/version-bump.sh <version>` to bump — it enforces the alpha constraint and updates all packages atomically.
- The script rejects any version >= `0.1.0`.
- When creating a release tag, the tag MUST match the package version: `v0.0.x`.
| 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). |
**Milestone-to-version mapping** is defined in the PRD (`docs/PRD.md`) under "Delivery/Milestone Intent". Agents MUST use the version from that table when tagging a milestone release.
### Contribution sequencing (in order, no skipping)
**Violation of this protocol is a blocking error.** If an agent attempts to set a version >= `0.1.0`, stop and escalate.
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.
## Standards and Quality
### Responsibilities
- Enforce strict typing and no unsafe shortcuts.
- Keep lint/typecheck/tests green before completion.
- Prefer small, focused commits and clear change descriptions.
- **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.
## App-Specific Overlays
### Hotfixes and divergence
- `apps/api/AGENTS.md`
- `apps/web/AGENTS.md`
- `apps/coordinator/AGENTS.md`
- `apps/orchestrator/AGENTS.md`
- 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.
## Additional Guidance
## Database and Local Runtime Safety
- Orchestrator guidance: `docs/claude/orchestrator.md`
- Security remediation context: `docs/reports/codebase-review-2026-02-05/01-security-review.md`
- Code quality context: `docs/reports/codebase-review-2026-02-05/02-code-quality-review.md`
- QA context: `docs/reports/codebase-review-2026-02-05/03-qa-test-coverage.md`
- Current local data-layer work uses in-process PGlite; leave `DATABASE_URL` unset.
- PostgreSQL execution is held until KBN-101-00, KBN-101-03, and KBN-101-05 land.
- Do not invoke a migration runner, initialization SQL, or the Compose PostgreSQL service from this checkout.
- Do not start Gateway/Web or run root `pnpm dev` as a local PGlite route. The current dotenv loader can inherit a daemon PostgreSQL DSN; KBN-101-02 must make that path fail closed first.
- Migration artifact generation is offline and does not authorize PostgreSQL access:
```bash
pnpm --filter @mosaicstack/db db:generate
```
## docs/TASKS.md — Schema (CANONICAL)
The `agent` column specifies the required model for each task. **This is set at task creation by the orchestrator and must not be changed by workers.**
| Value | When to use | Budget |
| --------- | ----------------------------------------------------------- | -------------------------- |
| `codex` | All coding tasks (default for implementation) | OpenAI credits — preferred |
| `glm-5.1` | Cost-sensitive coding where Codex is unavailable | Z.ai credits |
| `haiku` | Review gates, verify tasks, status checks, docs-only | Cheapest Claude tier |
| `sonnet` | Complex planning, multi-file reasoning, architecture review | Claude quota |
| `opus` | Major cross-cutting architecture decisions ONLY | Most expensive — minimize |
| `—` | No preference / auto-select cheapest capable | Pipeline decides |
Pipeline crons read this column and spawn accordingly. Workers never modify `docs/TASKS.md` — only the orchestrator writes it.
**Full schema:**
```
| id | status | description | issue | agent | repo | branch | depends_on | estimate | notes |
```
- `status`: `not-started` | `in-progress` | `done` | `failed` | `blocked` | `needs-qa`
- `agent`: model value from table above (set before spawning)
- `estimate`: token budget e.g. `8K`, `25K`
+3 -8
View File
@@ -1,10 +1,5 @@
# CLAUDE Compatibility Pointer
# Claude Compatibility Pointer
This file exists so Claude Code sessions load Mosaic standards.
@AGENTS.md
## MANDATORY — Read Before Any Response
BEFORE responding to any user message, READ `~/.config/mosaic/AGENTS.md`.
That file is the universal agent configuration. Do NOT respond until you have loaded it.
Then read the project-local `AGENTS.md` in this repository for project-specific guidance.
Do not add project guidance here. Keep `AGENTS.md` authoritative so every agent runtime receives the same instructions.
+48
View File
@@ -0,0 +1,48 @@
# Pre-baked CI base image for Woodpecker pipelines.
#
# Purpose: eliminate the cold `pnpm install` that dominates every pipeline
# (~731s median). This image ships the native toolchain (no per-run `apk add`)
# AND a warm, content-addressable pnpm store with the dependency-tree tarballs
# already fetched at build time. `pnpm fetch` only populates the store from the
# lockfile — it does NOT run the native node-gyp builds (better-sqlite3,
# node-pty, sqlite3, canvas, sharp); those still compile at `pnpm install`,
# which is exactly why the musl toolchain stays baked into this image. A
# pipeline `pnpm install --frozen-lockfile --prefer-offline` then resolves
# tarballs from local hard-links (no network) and compiles natives against the
# already-present toolchain, in tens of seconds instead of ~731s.
#
# Rebuilt only when `pnpm-lock.yaml` or this Dockerfile change
# (see .woodpecker/ci-image.yml).
#
# Node version is pinned to 24 (Active LTS). This is the follow-up bump from
# node:22 — sequenced AFTER the CI cache work landed so the runtime change
# carries zero cache variables. node:26 stays held until it reaches LTS
# (Oct 2026); the Current line risks native-module (node-gyp) breakage on a
# runner that compiles better-sqlite3 / canvas / sharp / node-pty from source.
FROM node:24-alpine
# Native toolchain required to compile node-gyp deps on musl, plus the
# postgresql-client used by the test step's pg_isready readiness probe. `bash`,
# `git`, and `jq` are baked here too — framework shell tests and the shipped
# Codex review wrappers require them without per-run installation in ci.yml.
# `openssl` (#912) is the non-circular HMAC signer for the wake trust layer:
# the digest H1/H2, beacon B12, and install I8 legs hard-require it in CI so the
# §4 G6 evidence comes from an actually-run HMAC leg, not a skipped one.
RUN apk add --no-cache python3 make g++ postgresql-client bash git jq openssl
# Pin pnpm to the repo's packageManager version via corepack.
RUN corepack enable && corepack prepare [email protected] --activate
WORKDIR /app
# Pin the store location so the pipeline can point `store-dir` at the same path.
ENV PNPM_HOME=/root/.local/share/pnpm
RUN pnpm config set store-dir /root/.local/share/pnpm/store
# Warm the store. `pnpm fetch` populates the content-addressable store with the
# dependency tarballs directly from the lockfile (no package.json / workspace
# needed), so a baked store stays valid until the lockfile changes. Note:
# `fetch` does NOT compile native modules — that happens later at `pnpm install`
# in the pipeline, against the toolchain baked above.
COPY pnpm-lock.yaml ./
RUN pnpm fetch --frozen-lockfile
-61
View File
@@ -1,61 +0,0 @@
# Cron Job Configuration - Issue #29
## Overview
Implement cron job configuration for Mosaic Stack, likely as a MoltBot plugin for scheduled reminders/commands.
## Requirements (inferred from CLAUDE.md pattern)
### Plugin Structure
```
plugins/mosaic-plugin-cron/
├── SKILL.md # MoltBot skill definition
├── src/
│ └── cron.service.ts
└── cron.service.test.ts
```
### Core Features
1. Create/update/delete cron schedules
2. Trigger MoltBot commands on schedule
3. Workspace-scoped (RLS)
4. PDA-friendly UI
### API Endpoints (inferred)
- `POST /api/cron` - Create schedule
- `GET /api/cron` - List schedules
- `DELETE /api/cron/:id` - Delete schedule
### Database (Prisma)
```prisma
model CronSchedule {
id String @id @default(uuid())
workspaceId String
expression String // cron expression
command String // MoltBot command to trigger
enabled Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([workspaceId])
}
```
## TDD Approach
1. **RED** - Write tests for CronService
2. **GREEN** - Implement minimal service
3. **REFACTOR** - Add CRUD controller + API endpoints
## Next Steps
- [ ] Create feature branch: `git checkout -b feature/29-cron-config`
- [ ] Write failing tests for cron service
- [ ] Implement service (Green)
- [ ] Add controller & routes
- [ ] Add Prisma schema migration
- [ ] Create MoltBot skill (SKILL.md)
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Mosaic Stack
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-145
View File
@@ -1,145 +0,0 @@
.PHONY: help install dev build test docker-up docker-down docker-logs docker-ps docker-build docker-restart docker-test speech-up speech-down speech-logs clean matrix-up matrix-down matrix-logs matrix-setup-bot
# Default target
help:
@echo "Mosaic Stack - Available commands:"
@echo ""
@echo "Development:"
@echo " make install Install dependencies"
@echo " make dev Start development servers"
@echo " make build Build all applications"
@echo " make test Run all tests"
@echo " make lint Run linters"
@echo " make format Format code"
@echo ""
@echo "Docker:"
@echo " make docker-up Start Docker services (core)"
@echo " make docker-up-full Start Docker services (all)"
@echo " make docker-up-traefik Start with bundled Traefik"
@echo " make docker-down Stop Docker services"
@echo " make docker-logs View Docker logs"
@echo " make docker-ps Show Docker service status"
@echo " make docker-build Rebuild Docker images"
@echo " make docker-restart Restart Docker services"
@echo " make docker-test Run Docker smoke test"
@echo " make docker-test-traefik Run Traefik integration tests"
@echo ""
@echo "Speech Services:"
@echo " make speech-up Start speech services (STT + TTS)"
@echo " make speech-down Stop speech services"
@echo " make speech-logs View speech service logs"
@echo ""
@echo "Matrix Dev Environment:"
@echo " make matrix-up Start Matrix services (Synapse + Element)"
@echo " make matrix-down Stop Matrix services"
@echo " make matrix-logs View Matrix service logs"
@echo " make matrix-setup-bot Create bot account and get access token"
@echo ""
@echo "Database:"
@echo " make db-migrate Run database migrations"
@echo " make db-seed Seed development data"
@echo " make db-studio Open Prisma Studio"
@echo " make db-reset Reset database (WARNING: deletes data)"
@echo ""
@echo "Cleanup:"
@echo " make clean Clean build artifacts"
@echo " make clean-all Clean everything including node_modules"
@echo " make docker-clean Remove Docker containers and volumes"
# Development
install:
pnpm install
dev:
pnpm dev
build:
pnpm build
test:
pnpm test
lint:
pnpm lint
format:
pnpm format
# Docker operations
docker-up:
docker compose up -d
docker-up-full:
docker compose --profile full up -d
docker-up-traefik:
docker compose --profile traefik-bundled up -d
docker-down:
docker compose down
docker-logs:
docker compose logs -f
docker-ps:
docker compose ps
docker-build:
docker compose build
docker-restart:
docker compose restart
docker-test:
./scripts/test-docker-deployment.sh
docker-test-traefik:
./tests/integration/docker/traefik.test.sh all
# Speech services
speech-up:
docker compose -f docker-compose.yml -f docker-compose.speech.yml up -d speaches kokoro-tts
speech-down:
docker compose -f docker-compose.yml -f docker-compose.speech.yml down --remove-orphans
speech-logs:
docker compose -f docker-compose.yml -f docker-compose.speech.yml logs -f speaches kokoro-tts
# Matrix Dev Environment
matrix-up:
docker compose -f docker/docker-compose.yml -f docker/docker-compose.matrix.yml up -d
matrix-down:
docker compose -f docker/docker-compose.yml -f docker/docker-compose.matrix.yml down
matrix-logs:
docker compose -f docker/docker-compose.yml -f docker/docker-compose.matrix.yml logs -f synapse element-web
matrix-setup-bot:
docker/matrix/scripts/setup-bot.sh
# Database operations
db-migrate:
cd apps/api && pnpm prisma:migrate
db-seed:
cd apps/api && pnpm prisma:seed
db-studio:
cd apps/api && pnpm prisma:studio
db-reset:
cd apps/api && pnpm prisma:reset
# Cleanup
clean:
pnpm clean
clean-all:
pnpm clean
rm -rf node_modules
docker-clean:
docker compose down -v
docker system prune -f
+337 -739
View File
File diff suppressed because it is too large Load Diff
+71
View File
@@ -0,0 +1,71 @@
# REPORT A1207
Date: 2026-08-13
Branch: `fix/869-lease-probe-timeout`
Starting head: `2373a5ad345fb316ad2460f6390baab1f45ba08f`
Base: `216cd72226cd9ee17eea461cfe7cd0e010a22f02`
## What changed
- Added Python behavior tests using isolated temporary directories and marker-writing fake `mosaic` executables. They prove that the supplied `PATH` wins over ambient `os.environ["PATH"]`, and that absent or empty supplied `PATH` values do not search ambient paths, platform defaults, or the current directory.
- Bound Python override behavior with executable fakes: a valid `MOSAIC_LEASE_VERSION_PROBE_COMMAND` wins over supplied and ambient `PATH`; an invalid override returns `None` without PATH fallback.
- Added a Python runner binding test that captures kwargs and requires `timeout=10.0`. Existing timeout, transport-error, and nonzero-exit checks remain fail-closed with `None`.
- Added the optional TypeScript dependency-injection seam `CapabilityProbeExecFile`, defaulting to the existing real `execFileSync` implementation. Production callers have no behavior change.
- Added TypeScript tests that capture child-process options and require exactly `timeout: 10_000`. Injected timeout, spawn-error, nonzero-exit, unparseable JSON, and malformed-object cases all return `null`.
- Removed the ambient no-dependency TypeScript smoke case that could execute a built checkout's real CLI. Default resolver and supervisor behavior retain their isolated tests, while capability transport tests now use an isolated artifact or the injected transport.
No Python production code changed relative to `2373a5ad`. The only production delta is the optional TypeScript child-process injection seam.
## Hermeticity incident and correction
An initial ambient-lookup mutation run exposed that the pre-existing Python "not resolvable" test left ambient process PATH uncontrolled. On this host, that mutation resolved and executed the host `mosaic` capability probe. A post-build intermediate TypeScript run also let the pre-existing no-dependency smoke case execute the checkout's built `dist/cli.js` capability probe. No `claude` process was run. I then isolated the Python test's ambient PATH, removed the TypeScript ambient smoke case, repeated the PATH mutation using only marker-writing temporary fakes, and repeated the final suites without either real probe path.
## Mutation evidence
Each mutation was applied independently, its focused suite was run, and the production source was restored before the final run.
| Mutation | Result | Reddened test name(s) |
| ------------------------------------------------------------------------------------------ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `shutil.which("mosaic", path=environ.get("PATH", ""))` to ambient `shutil.which("mosaic")` | RED, three failures | `ProbeActivationCapabilityTest.test_supplied_path_wins_over_ambient_process_path`; `ProbeActivationCapabilityTest.test_absent_or_empty_supplied_path_never_falls_back_or_executes` for both absent and empty PATH subtests |
| Python `PROBE_TIMEOUT_SECONDS: 10.0` to `2.0` | RED, one failure | `ProbeActivationCapabilityTest.test_probe_passes_ten_second_timeout_to_runner` |
| TypeScript `LEASE_CAPABILITY_PROBE_TIMEOUT_MS: 10_000` to `2_000` | RED, one failure | `defaultCapabilityProbe > passes the exact ten-second timeout to the injected child-process transport` |
## Final test run
Dependencies were installed first with `pnpm install --frozen-lockfile`. Workspace dependencies were then built with `pnpm --filter '@mosaicstack/mosaic...' run build` so package type declarations were available.
```text
$ cd packages/mosaic && python3 src/mutator-gate/version_coupling_unittest.py
...................
----------------------------------------------------------------------
Ran 19 tests in 0.007s
OK
$ pnpm exec vitest run src/commands/lease-activation-probe.spec.ts
✓ src/commands/lease-activation-probe.spec.ts (20 tests) 80ms
Test Files 1 passed (1)
Tests 20 passed (20)
```
```text
$ pnpm exec prettier --check packages/mosaic/src/commands/lease-activation-probe.ts packages/mosaic/src/commands/lease-activation-probe.spec.ts
Checking formatting...
All matched files use Prettier code style!
$ pnpm --filter @mosaicstack/mosaic lint
> eslint src
$ pnpm --filter @mosaicstack/mosaic typecheck
> tsc --noEmit
$ python3 -m py_compile packages/mosaic/src/mutator-gate/version_coupling_unittest.py packages/mosaic/framework/tools/lease-broker/activation_version_gate.py
$ git diff --check
```
All commands above exited zero.
## Ambiguities skipped
None.
-20
View File
@@ -1,20 +0,0 @@
# Mosaic Stack Soul
You are Jarvis for the Mosaic Stack repository, running on the current agent runtime.
## Behavioral Invariants
- Identity first: answer identity prompts as Jarvis for this repository.
- Implementation detail second: runtime (Codex/Claude/OpenCode/etc.) is secondary metadata.
- Be proactive: surface risks, blockers, and next actions without waiting.
- Be calm and clear: keep responses concise, chunked, and PDA-friendly.
- Respect canonical sources:
- Repo operations and conventions: `AGENTS.md`
- Machine-wide rails: `~/.config/mosaic/STANDARDS.md`
- Repo lifecycle hooks: `.mosaic/repo-hooks.sh`
## Guardrails
- Do not claim completion without verification evidence.
- Do not bypass lint/type/test quality gates.
- Prefer explicit assumptions and concrete file/command references.
-48
View File
@@ -1,48 +0,0 @@
# Node modules
node_modules
npm-debug.log
yarn-error.log
pnpm-debug.log
# Build output
dist
build
*.tsbuildinfo
# Tests
coverage
.vitest
test
*.spec.ts
*.test.ts
# Development files
.env
.env.*
!.env.example
# IDE
.vscode
.idea
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Git
.git
.gitignore
# Documentation
README.md
docs
# Logs
logs
*.log
# Turbo
.turbo
-40
View File
@@ -1,40 +0,0 @@
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/database
# System Administration
# Comma-separated list of user IDs that have system administrator privileges
# These users can perform system-level operations across all workspaces
# Note: Workspace ownership does NOT grant system admin access
# SYSTEM_ADMIN_IDS=uuid1,uuid2,uuid3
# Federation Instance Identity
# Display name for this Mosaic instance
INSTANCE_NAME=Mosaic Instance
# Publicly accessible URL for federation (must be valid HTTP/HTTPS URL)
INSTANCE_URL=http://localhost:3000
# Encryption (AES-256-GCM for sensitive data at rest)
# CRITICAL: Generate a secure random key for production!
# Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
ENCRYPTION_KEY=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
# CSRF Protection (Required in production)
# Secret key for HMAC binding CSRF tokens to user sessions
# Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# In development, a random key is generated if not set
CSRF_SECRET=fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210
# OpenTelemetry Configuration
# Enable/disable OpenTelemetry tracing (default: true)
OTEL_ENABLED=true
# Service name for telemetry (default: mosaic-api)
OTEL_SERVICE_NAME=mosaic-api
# OTLP exporter endpoint (default: http://localhost:4318/v1/traces)
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318/v1/traces
# Alternative: Jaeger endpoint (legacy)
# OTEL_EXPORTER_JAEGER_ENDPOINT=http://localhost:4318/v1/traces
# Deployment environment (default: development, or uses NODE_ENV)
# OTEL_DEPLOYMENT_ENVIRONMENT=production
# Trace sampling ratio: 0.0 (none) to 1.0 (all) - default: 1.0
# Use lower values in high-traffic production environments
# OTEL_TRACES_SAMPLER_ARG=1.0
-5
View File
@@ -1,5 +0,0 @@
DATABASE_URL="postgresql://test:test@localhost:5432/test"
ENCRYPTION_KEY="test-encryption-key-32-characters"
JWT_SECRET="test-jwt-secret"
INSTANCE_NAME="Test Instance"
INSTANCE_URL="https://test.example.com"
-9
View File
@@ -1,9 +0,0 @@
# WARNING: These are example test credentials for local integration testing.
# Copy this file to .env.test and customize the values for your local environment.
# NEVER use these credentials in any shared environment or commit .env.test to git.
DATABASE_URL="postgresql://test:test@localhost:5432/test"
ENCRYPTION_KEY="test-encryption-key-32-characters"
JWT_SECRET="test-jwt-secret"
INSTANCE_NAME="Test Instance"
INSTANCE_URL="https://test.example.com"
-18
View File
@@ -1,18 +0,0 @@
{
"sourceMaps": true,
"jsc": {
"target": "es2022",
"parser": {
"syntax": "typescript",
"decorators": true
},
"transform": {
"legacyDecorator": true,
"decoratorMetadata": true
},
"keepClassNames": true
},
"module": {
"type": "es6"
}
}
-25
View File
@@ -1,25 +0,0 @@
# api — Agent Context
> Part of the apps layer.
## Patterns
- **Config validation pattern**: Config files use exported validation functions + typed getter functions (not class-validator). See `auth.config.ts`, `federation.config.ts`, `speech/speech.config.ts`. Pattern: export `isXEnabled()`, `validateXConfig()`, and `getXConfig()` functions.
- **Config registerAs**: `speech.config.ts` also exports a `registerAs("speech", ...)` factory for NestJS ConfigModule namespaced injection. Use `ConfigModule.forFeature(speechConfig)` in module imports and access via `this.config.get<string>('speech.stt.baseUrl')`.
- **Conditional config validation**: When a service has an enabled flag (e.g., `STT_ENABLED`), URL/connection vars are only required when enabled. Validation throws with a helpful message suggesting how to disable.
- **Boolean env parsing**: Use `value === "true" || value === "1"` pattern. No default-true -- all services default to disabled when env var is unset.
## Gotchas
- **Prisma client must be generated** before `tsc --noEmit` will pass. Run `pnpm prisma:generate` first. Pre-existing type errors from Prisma are expected in worktrees without generated client.
- **Pre-commit hooks**: lint-staged runs on staged files. If other packages' files are staged, their lint must pass too. Only stage files you intend to commit.
- **vitest runs all test files**: Even when targeting a specific test file, vitest loads all spec files. Many will fail if Prisma client isn't generated -- this is expected. Check only your target file's pass/fail status.
## Key Files
| File | Purpose |
| ------------------------------------- | ---------------------------------------------------------------------- |
| `src/speech/speech.config.ts` | Speech services env var validation and typed config (STT, TTS, limits) |
| `src/speech/speech.config.spec.ts` | Unit tests for speech config validation (51 tests) |
| `src/auth/auth.config.ts` | Auth/OIDC config validation (reference pattern) |
| `src/federation/federation.config.ts` | Federation config validation (reference pattern) |
-115
View File
@@ -1,115 +0,0 @@
# Base image for all stages
# Uses Debian slim (glibc) instead of Alpine (musl) because native Node.js addons
# (matrix-sdk-crypto-nodejs, Prisma engines) require glibc-compatible binaries.
FROM node:24-slim AS base
# Install pnpm globally
RUN corepack enable && corepack prepare [email protected] --activate
# Set working directory
WORKDIR /app
# Copy monorepo configuration files
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./
COPY turbo.json ./
# ======================
# Dependencies stage
# ======================
FROM base AS deps
# Install build tools for native addons (node-pty requires node-gyp compilation)
# and OpenSSL for Prisma engine detection
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 make g++ openssl \
&& rm -rf /var/lib/apt/lists/*
# Copy all package.json files for workspace resolution
COPY packages/shared/package.json ./packages/shared/
COPY packages/ui/package.json ./packages/ui/
COPY packages/config/package.json ./packages/config/
COPY apps/api/package.json ./apps/api/
# Install dependencies (no cache mount — Kaniko builds are ephemeral in CI)
# Then explicitly rebuild node-pty from source since pnpm may skip postinstall
# scripts or fail to find prebuilt binaries for this Node.js version
RUN pnpm install --frozen-lockfile \
&& cd node_modules/.pnpm/node-pty@*/node_modules/node-pty \
&& npx node-gyp rebuild 2>&1 || true
# ======================
# Builder stage
# ======================
FROM base AS builder
# Copy root node_modules from deps
COPY --from=deps /app/node_modules ./node_modules
# Copy all source code FIRST
COPY packages ./packages
COPY apps/api ./apps/api
# Then copy workspace node_modules from deps (these go AFTER source to avoid being overwritten)
COPY --from=deps /app/packages/shared/node_modules ./packages/shared/node_modules
COPY --from=deps /app/packages/config/node_modules ./packages/config/node_modules
COPY --from=deps /app/apps/api/node_modules ./apps/api/node_modules
# Build the API app and its dependencies using TurboRepo
# --force disables turbo cache to ensure fresh build from source
RUN pnpm turbo build --filter=@mosaic/api --force
# ======================
# Production stage
# ======================
FROM node:24-slim AS production
# Install dumb-init for proper signal handling (static binary from GitHub,
# avoids apt-get which fails under Kaniko with bookworm GPG signature errors)
ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_x86_64 /usr/local/bin/dumb-init
# Single RUN to minimize Kaniko filesystem snapshots (each RUN = full snapshot)
# - openssl: Prisma engine detection requires libssl
# - No build tools needed here — native addons are compiled in the deps stage
RUN apt-get update && apt-get install -y --no-install-recommends openssl \
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx \
&& chmod 755 /usr/local/bin/dumb-init \
&& groupadd -g 1001 nodejs && useradd -m -u 1001 -g nodejs nestjs
WORKDIR /app
# Copy node_modules from builder (includes generated Prisma client in pnpm store)
# pnpm stores the Prisma client in node_modules/.pnpm/.../.prisma, so we need the full tree
COPY --from=builder --chown=nestjs:nodejs /app/node_modules ./node_modules
# Copy built packages (includes dist/ directories)
COPY --from=builder --chown=nestjs:nodejs /app/packages ./packages
# Copy built API application
COPY --from=builder --chown=nestjs:nodejs /app/apps/api/dist ./apps/api/dist
COPY --from=builder --chown=nestjs:nodejs /app/apps/api/prisma ./apps/api/prisma
COPY --from=builder --chown=nestjs:nodejs /app/apps/api/package.json ./apps/api/
# Copy app's node_modules which contains symlinks to root node_modules
COPY --from=builder --chown=nestjs:nodejs /app/apps/api/node_modules ./apps/api/node_modules
# Copy entrypoint script (runs migrations before starting app)
COPY --from=builder --chown=nestjs:nodejs /app/apps/api/docker-entrypoint.sh ./apps/api/
# Set working directory to API app
WORKDIR /app/apps/api
# Switch to non-root user
USER nestjs
# Expose API port (default 3001, can be overridden via PORT env var)
EXPOSE ${PORT:-3001}
# Health check uses PORT env var (set by docker-compose or defaults to 3001)
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD node -e "const port = process.env.PORT || 3001; require('http').get('http://localhost:' + port + '/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
# Use dumb-init to handle signals properly
ENTRYPOINT ["dumb-init", "--"]
# Run migrations then start the application
CMD ["sh", "docker-entrypoint.sh"]
-260
View File
@@ -1,260 +0,0 @@
# Mosaic Stack API
The Mosaic Stack API is a NestJS-based backend service providing REST endpoints and WebSocket support for the Mosaic productivity platform.
## Overview
The API serves as the central backend for:
- **Task Management** - Create, update, track tasks with filtering and sorting
- **Event Management** - Calendar events and scheduling
- **Project Management** - Organize work into projects
- **Knowledge Base** - Wiki-style documentation with markdown support and wiki-linking
- **Ideas** - Quick capture and organization of ideas
- **Domains** - Categorize work across different domains
- **Personalities** - AI personality configurations for the Ollama integration
- **Widgets & Layouts** - Dashboard customization
- **Activity Logging** - Track all user actions
- **WebSocket Events** - Real-time updates for tasks, events, and projects
## Available Modules
| Module | Base Path | Description |
| ------------------ | --------------------------- | ---------------------------------------- |
| **Tasks** | `/api/tasks` | CRUD operations for tasks with filtering |
| **Events** | `/api/events` | Calendar events and scheduling |
| **Projects** | `/api/projects` | Project management |
| **Knowledge** | `/api/knowledge/entries` | Wiki entries with markdown support |
| **Knowledge Tags** | `/api/knowledge/tags` | Tag management for knowledge entries |
| **Ideas** | `/api/ideas` | Quick capture and idea management |
| **Domains** | `/api/domains` | Domain categorization |
| **Personalities** | `/api/personalities` | AI personality configurations |
| **Widgets** | `/api/widgets` | Dashboard widget data |
| **Layouts** | `/api/layouts` | Dashboard layout configuration |
| **Ollama** | `/api/ollama` | LLM integration (generate, chat, embed) |
| **Users** | `/api/users/me/preferences` | User preferences |
### Health Check
- `GET /` - API health check
- `GET /health` - Detailed health status including database connectivity
## Authentication
The API uses **BetterAuth** for authentication with the following features:
### Authentication Flow
1. **Email/Password** - Users can sign up and log in with email and password
2. **Session Tokens** - BetterAuth generates session tokens with configurable expiration
### Guards
The API uses a layered guard system:
| Guard | Purpose | Applies To |
| ------------------- | ------------------------------------------------------------------------ | -------------------------- |
| **AuthGuard** | Verifies user authentication via Bearer token | Most protected endpoints |
| **WorkspaceGuard** | Validates workspace membership and sets Row-Level Security (RLS) context | Workspace-scoped resources |
| **PermissionGuard** | Enforces role-based access control | Admin operations |
### Workspace Roles
- **OWNER** - Full control over workspace
- **ADMIN** - Administrative functions (can delete content, manage members)
- **MEMBER** - Standard access (create/edit content)
- **GUEST** - Read-only access
### Permission Levels
Used with `@RequirePermission()` decorator:
```typescript
Permission.WORKSPACE_OWNER; // Requires OWNER role
Permission.WORKSPACE_ADMIN; // Requires ADMIN or OWNER
Permission.WORKSPACE_MEMBER; // Requires MEMBER, ADMIN, or OWNER
Permission.WORKSPACE_ANY; // Any authenticated member including GUEST
```
### Providing Workspace Context
Workspace ID can be provided via:
1. **Header**: `X-Workspace-Id: <workspace-id>` (highest priority)
2. **URL Parameter**: `:workspaceId`
3. **Request Body**: `workspaceId` field
### Example: Protected Controller
```typescript
@Controller("tasks")
@UseGuards(AuthGuard, WorkspaceGuard, PermissionGuard)
export class TasksController {
@Post()
@RequirePermission(Permission.WORKSPACE_MEMBER)
async create(@Body() dto: CreateTaskDto, @Workspace() workspaceId: string) {
// workspaceId is verified and RLS context is set
}
}
```
## Environment Variables
| Variable | Description | Default |
| --------------------- | ----------------------------------------- | ----------------------- |
| `PORT` | API server port | `3001` |
| `DATABASE_URL` | PostgreSQL connection string | Required |
| `NODE_ENV` | Environment (`development`, `production`) | - |
| `NEXT_PUBLIC_APP_URL` | Frontend application URL (for CORS) | `http://localhost:3000` |
| `WEB_URL` | WebSocket CORS origin | `http://localhost:3000` |
## Running Locally
### Prerequisites
- Node.js 18+
- PostgreSQL database
- pnpm workspace (part of Mosaic Stack monorepo)
### Setup
1. **Install dependencies:**
```bash
pnpm install
```
2. **Set up environment variables:**
```bash
cp .env.example .env # If available
# Edit .env with your DATABASE_URL
```
3. **Generate Prisma client:**
```bash
pnpm prisma:generate
```
4. **Run database migrations:**
```bash
pnpm prisma:migrate
```
5. **Seed the database (optional):**
```bash
pnpm prisma:seed
```
### Development
```bash
pnpm dev
```
The API will start on `http://localhost:3001`
### Production Build
```bash
pnpm build
pnpm start:prod
```
### Database Management
```bash
# Open Prisma Studio
pnpm prisma:studio
# Reset database (dev only)
pnpm prisma:reset
# Run migrations in production
pnpm prisma:migrate:prod
```
## API Documentation
The API does not currently include Swagger/OpenAPI documentation. Instead:
- **Controller files** contain detailed JSDoc comments describing each endpoint
- **DTO classes** define request/response schemas with class-validator decorators
- Refer to the controller source files in `src/` for endpoint details
### Example: Reading an Endpoint
```typescript
// src/tasks/tasks.controller.ts
/**
* POST /api/tasks
* Create a new task
* Requires: MEMBER role or higher
*/
@Post()
@RequirePermission(Permission.WORKSPACE_MEMBER)
async create(@Body() createTaskDto: CreateTaskDto, @Workspace() workspaceId: string) {
return this.tasksService.create(workspaceId, user.id, createTaskDto);
}
```
## WebSocket Support
The API provides real-time updates via WebSocket. Clients receive notifications for:
- `task:created` - New task created
- `task:updated` - Task modified
- `task:deleted` - Task removed
- `event:created` - New event created
- `event:updated` - Event modified
- `event:deleted` - Event removed
- `project:updated` - Project modified
Clients join workspace-specific rooms for scoped updates.
## Testing
```bash
# Run unit tests
pnpm test
# Run tests with coverage
pnpm test:coverage
# Run e2e tests
pnpm test:e2e
# Watch mode
pnpm test:watch
```
## Project Structure
```
src/
├── activity/ # Activity logging
├── auth/ # Authentication (BetterAuth config, guards)
├── common/ # Shared decorators and guards
├── database/ # Database module
├── domains/ # Domain management
├── events/ # Event management
├── filters/ # Global exception filters
├── ideas/ # Idea capture and management
├── knowledge/ # Knowledge base (entries, tags, markdown)
├── layouts/ # Dashboard layouts
├── lib/ # Utility functions
├── ollama/ # LLM integration
├── personalities/ # AI personality configurations
├── prisma/ # Prisma service
├── projects/ # Project management
├── tasks/ # Task management
├── users/ # User preferences
├── widgets/ # Dashboard widgets
├── websocket/ # WebSocket gateway
├── app.controller.ts # Root controller (health check)
├── app.module.ts # Root module
└── main.ts # Application bootstrap
```
-8
View File
@@ -1,8 +0,0 @@
#!/bin/sh
set -e
echo "Running database migrations..."
./node_modules/.bin/prisma migrate deploy --schema ./prisma/schema.prisma
echo "Starting application..."
exec node dist/main.js
-16
View File
@@ -1,16 +0,0 @@
import nestjsConfig from "@mosaic/config/eslint/nestjs";
export default [
...nestjsConfig,
{
languageOptions: {
parserOptions: {
project: ["./tsconfig.json"],
tsconfigRootDir: import.meta.dirname,
},
},
},
{
ignores: ["dist/**", "node_modules/**", "**/*.test.ts", "**/*.spec.ts"],
},
];
-8
View File
@@ -1,8 +0,0 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}
-106
View File
@@ -1,106 +0,0 @@
{
"name": "@mosaic/api",
"version": "0.0.20",
"private": true,
"scripts": {
"build": "nest build",
"dev": "nest start --watch",
"start": "node dist/main",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"src/**/*.ts\"",
"lint:fix": "eslint \"src/**/*.ts\" --fix",
"typecheck": "tsc --noEmit",
"clean": "rm -rf dist",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"test:e2e": "vitest run --config ./vitest.e2e.config.ts",
"prisma:generate": "prisma generate",
"prisma:migrate": "prisma migrate dev",
"prisma:migrate:prod": "prisma migrate deploy",
"prisma:studio": "prisma studio",
"prisma:seed": "prisma db seed",
"prisma:reset": "prisma migrate reset",
"migrate:encrypt-llm-keys": "tsx scripts/encrypt-llm-keys.ts"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.72.1",
"@mosaic/shared": "workspace:*",
"@mosaicstack/telemetry-client": "^0.1.1",
"@nestjs/axios": "^4.0.1",
"@nestjs/bullmq": "^11.0.4",
"@nestjs/common": "^11.1.12",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.1.12",
"@nestjs/mapped-types": "^2.1.0",
"@nestjs/platform-express": "^11.1.12",
"@nestjs/platform-socket.io": "^11.1.12",
"@nestjs/throttler": "^6.5.0",
"@nestjs/websockets": "^11.1.12",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/auto-instrumentations-node": "^0.55.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.56.0",
"@opentelemetry/instrumentation-nestjs-core": "^0.44.0",
"@opentelemetry/resources": "^1.30.1",
"@opentelemetry/sdk-node": "^0.56.0",
"@opentelemetry/sdk-trace-base": "^2.5.0",
"@opentelemetry/semantic-conventions": "^1.28.0",
"@prisma/client": "^6.19.2",
"@types/marked": "^6.0.0",
"@types/multer": "^2.0.0",
"adm-zip": "^0.5.16",
"archiver": "^7.0.1",
"axios": "^1.13.5",
"bcryptjs": "^3.0.3",
"better-auth": "^1.4.17",
"bullmq": "^5.67.2",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.3",
"cookie-parser": "^1.4.7",
"discord.js": "^14.25.1",
"gray-matter": "^4.0.3",
"highlight.js": "^11.11.1",
"ioredis": "^5.9.2",
"jose": "^6.1.3",
"marked": "^17.0.1",
"marked-gfm-heading-id": "^4.1.3",
"marked-highlight": "^2.2.3",
"matrix-bot-sdk": "^0.8.0",
"node-pty": "^1.0.0",
"ollama": "^0.6.3",
"openai": "^6.17.0",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"sanitize-html": "^2.17.0",
"slugify": "^1.6.6",
"socket.io": "^4.8.3"
},
"devDependencies": {
"@better-auth/cli": "^1.4.17",
"@mosaic/config": "workspace:*",
"@nestjs/cli": "^11.0.6",
"@nestjs/schematics": "^11.0.1",
"@nestjs/testing": "^11.1.12",
"@opentelemetry/context-async-hooks": "^2.5.0",
"@swc/core": "^1.10.18",
"@types/adm-zip": "^0.5.7",
"@types/archiver": "^7.0.0",
"@types/bcryptjs": "^3.0.0",
"@types/cookie-parser": "^1.4.10",
"@types/express": "^5.0.1",
"@types/highlight.js": "^10.1.0",
"@types/node": "^22.13.4",
"@types/sanitize-html": "^2.16.0",
"@types/supertest": "^6.0.3",
"@vitest/coverage-v8": "^4.0.18",
"dotenv": "^17.2.4",
"express": "^5.2.1",
"prisma": "^6.19.2",
"supertest": "^7.2.2",
"tsx": "^4.21.0",
"typescript": "^5.8.2",
"unplugin-swc": "^1.5.2",
"vitest": "^4.0.18"
}
}
-7
View File
@@ -1,7 +0,0 @@
import { defineConfig } from "prisma/config";
export default defineConfig({
migrations: {
seed: "tsx prisma/seed.ts",
},
});
@@ -1,261 +0,0 @@
-- CreateExtension
CREATE EXTENSION IF NOT EXISTS "vector";
-- CreateExtension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- CreateEnum
CREATE TYPE "TaskStatus" AS ENUM ('NOT_STARTED', 'IN_PROGRESS', 'PAUSED', 'COMPLETED', 'ARCHIVED');
-- CreateEnum
CREATE TYPE "TaskPriority" AS ENUM ('LOW', 'MEDIUM', 'HIGH');
-- CreateEnum
CREATE TYPE "ProjectStatus" AS ENUM ('PLANNING', 'ACTIVE', 'PAUSED', 'COMPLETED', 'ARCHIVED');
-- CreateEnum
CREATE TYPE "WorkspaceMemberRole" AS ENUM ('OWNER', 'ADMIN', 'MEMBER', 'GUEST');
-- CreateEnum
CREATE TYPE "ActivityAction" AS ENUM ('CREATED', 'UPDATED', 'DELETED', 'COMPLETED', 'ASSIGNED', 'COMMENTED');
-- CreateEnum
CREATE TYPE "EntityType" AS ENUM ('TASK', 'EVENT', 'PROJECT', 'WORKSPACE', 'USER');
-- CreateTable
CREATE TABLE "users" (
"id" UUID NOT NULL,
"email" TEXT NOT NULL,
"name" TEXT NOT NULL,
"auth_provider_id" TEXT,
"preferences" JSONB NOT NULL DEFAULT '{}',
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "workspaces" (
"id" UUID NOT NULL,
"name" TEXT NOT NULL,
"owner_id" UUID NOT NULL,
"settings" JSONB NOT NULL DEFAULT '{}',
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
CONSTRAINT "workspaces_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "workspace_members" (
"workspace_id" UUID NOT NULL,
"user_id" UUID NOT NULL,
"role" "WorkspaceMemberRole" NOT NULL DEFAULT 'MEMBER',
"joined_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "workspace_members_pkey" PRIMARY KEY ("workspace_id","user_id")
);
-- CreateTable
CREATE TABLE "tasks" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"status" "TaskStatus" NOT NULL DEFAULT 'NOT_STARTED',
"priority" "TaskPriority" NOT NULL DEFAULT 'MEDIUM',
"due_date" TIMESTAMPTZ,
"assignee_id" UUID,
"creator_id" UUID NOT NULL,
"project_id" UUID,
"parent_id" UUID,
"sort_order" INTEGER NOT NULL DEFAULT 0,
"metadata" JSONB NOT NULL DEFAULT '{}',
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
"completed_at" TIMESTAMPTZ,
CONSTRAINT "tasks_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "events" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"start_time" TIMESTAMPTZ NOT NULL,
"end_time" TIMESTAMPTZ,
"all_day" BOOLEAN NOT NULL DEFAULT false,
"location" TEXT,
"recurrence" JSONB,
"creator_id" UUID NOT NULL,
"project_id" UUID,
"metadata" JSONB NOT NULL DEFAULT '{}',
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
CONSTRAINT "events_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "projects" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"status" "ProjectStatus" NOT NULL DEFAULT 'PLANNING',
"start_date" DATE,
"end_date" DATE,
"creator_id" UUID NOT NULL,
"color" TEXT,
"metadata" JSONB NOT NULL DEFAULT '{}',
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
CONSTRAINT "projects_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "activity_logs" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"user_id" UUID NOT NULL,
"action" "ActivityAction" NOT NULL,
"entity_type" "EntityType" NOT NULL,
"entity_id" UUID NOT NULL,
"details" JSONB NOT NULL DEFAULT '{}',
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "activity_logs_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "memory_embeddings" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"content" TEXT NOT NULL,
"embedding" vector(1536),
"entity_type" "EntityType",
"entity_id" UUID,
"metadata" JSONB NOT NULL DEFAULT '{}',
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
CONSTRAINT "memory_embeddings_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
-- CreateIndex
CREATE UNIQUE INDEX "users_auth_provider_id_key" ON "users"("auth_provider_id");
-- CreateIndex
CREATE INDEX "workspaces_owner_id_idx" ON "workspaces"("owner_id");
-- CreateIndex
CREATE INDEX "workspace_members_user_id_idx" ON "workspace_members"("user_id");
-- CreateIndex
CREATE INDEX "tasks_workspace_id_idx" ON "tasks"("workspace_id");
-- CreateIndex
CREATE INDEX "tasks_workspace_id_status_idx" ON "tasks"("workspace_id", "status");
-- CreateIndex
CREATE INDEX "tasks_workspace_id_due_date_idx" ON "tasks"("workspace_id", "due_date");
-- CreateIndex
CREATE INDEX "tasks_assignee_id_idx" ON "tasks"("assignee_id");
-- CreateIndex
CREATE INDEX "tasks_project_id_idx" ON "tasks"("project_id");
-- CreateIndex
CREATE INDEX "tasks_parent_id_idx" ON "tasks"("parent_id");
-- CreateIndex
CREATE INDEX "events_workspace_id_idx" ON "events"("workspace_id");
-- CreateIndex
CREATE INDEX "events_workspace_id_start_time_idx" ON "events"("workspace_id", "start_time");
-- CreateIndex
CREATE INDEX "events_creator_id_idx" ON "events"("creator_id");
-- CreateIndex
CREATE INDEX "events_project_id_idx" ON "events"("project_id");
-- CreateIndex
CREATE INDEX "projects_workspace_id_idx" ON "projects"("workspace_id");
-- CreateIndex
CREATE INDEX "projects_workspace_id_status_idx" ON "projects"("workspace_id", "status");
-- CreateIndex
CREATE INDEX "projects_creator_id_idx" ON "projects"("creator_id");
-- CreateIndex
CREATE INDEX "activity_logs_workspace_id_idx" ON "activity_logs"("workspace_id");
-- CreateIndex
CREATE INDEX "activity_logs_workspace_id_created_at_idx" ON "activity_logs"("workspace_id", "created_at");
-- CreateIndex
CREATE INDEX "activity_logs_entity_type_entity_id_idx" ON "activity_logs"("entity_type", "entity_id");
-- CreateIndex
CREATE INDEX "activity_logs_user_id_idx" ON "activity_logs"("user_id");
-- CreateIndex
CREATE INDEX "memory_embeddings_workspace_id_idx" ON "memory_embeddings"("workspace_id");
-- AddForeignKey
ALTER TABLE "workspaces" ADD CONSTRAINT "workspaces_owner_id_fkey" FOREIGN KEY ("owner_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "workspace_members" ADD CONSTRAINT "workspace_members_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "workspace_members" ADD CONSTRAINT "workspace_members_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "tasks" ADD CONSTRAINT "tasks_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "tasks" ADD CONSTRAINT "tasks_assignee_id_fkey" FOREIGN KEY ("assignee_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "tasks" ADD CONSTRAINT "tasks_creator_id_fkey" FOREIGN KEY ("creator_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "tasks" ADD CONSTRAINT "tasks_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "tasks" ADD CONSTRAINT "tasks_parent_id_fkey" FOREIGN KEY ("parent_id") REFERENCES "tasks"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "events" ADD CONSTRAINT "events_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "events" ADD CONSTRAINT "events_creator_id_fkey" FOREIGN KEY ("creator_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "events" ADD CONSTRAINT "events_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "projects" ADD CONSTRAINT "projects_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "projects" ADD CONSTRAINT "projects_creator_id_fkey" FOREIGN KEY ("creator_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "activity_logs" ADD CONSTRAINT "activity_logs_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "activity_logs" ADD CONSTRAINT "activity_logs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "memory_embeddings" ADD CONSTRAINT "memory_embeddings_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -1,8 +0,0 @@
-- Add HNSW index for fast vector similarity search on memory_embeddings table
-- Using cosine distance operator for semantic similarity
-- Parameters: m=16 (max connections per layer), ef_construction=64 (build quality)
CREATE INDEX IF NOT EXISTS memory_embeddings_embedding_idx
ON memory_embeddings
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
@@ -1,92 +0,0 @@
-- AlterEnum
-- This migration adds more than one value to an enum.
-- With PostgreSQL versions 11 and earlier, this is not possible
-- in a single migration. This can be worked around by creating
-- multiple migrations, each migration adding only one value to
-- the enum.
ALTER TYPE "ActivityAction" ADD VALUE 'LOGIN';
ALTER TYPE "ActivityAction" ADD VALUE 'LOGOUT';
ALTER TYPE "ActivityAction" ADD VALUE 'PASSWORD_RESET';
ALTER TYPE "ActivityAction" ADD VALUE 'EMAIL_VERIFIED';
-- AlterTable
ALTER TABLE "activity_logs" ADD COLUMN "ip_address" TEXT,
ADD COLUMN "user_agent" TEXT;
-- AlterTable
ALTER TABLE "users" ADD COLUMN "email_verified" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "image" TEXT;
-- CreateTable
CREATE TABLE "sessions" (
"id" UUID NOT NULL,
"user_id" UUID NOT NULL,
"token" TEXT NOT NULL,
"expires_at" TIMESTAMPTZ NOT NULL,
"ip_address" TEXT,
"user_agent" TEXT,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
CONSTRAINT "sessions_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "accounts" (
"id" UUID NOT NULL,
"user_id" UUID NOT NULL,
"account_id" TEXT NOT NULL,
"provider_id" TEXT NOT NULL,
"access_token" TEXT,
"refresh_token" TEXT,
"id_token" TEXT,
"access_token_expires_at" TIMESTAMPTZ,
"refresh_token_expires_at" TIMESTAMPTZ,
"scope" TEXT,
"password" TEXT,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
CONSTRAINT "accounts_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "verifications" (
"id" UUID NOT NULL,
"identifier" TEXT NOT NULL,
"value" TEXT NOT NULL,
"expires_at" TIMESTAMPTZ NOT NULL,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
CONSTRAINT "verifications_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "sessions_token_key" ON "sessions"("token");
-- CreateIndex
CREATE INDEX "sessions_user_id_idx" ON "sessions"("user_id");
-- CreateIndex
CREATE INDEX "sessions_token_idx" ON "sessions"("token");
-- CreateIndex
CREATE INDEX "accounts_user_id_idx" ON "accounts"("user_id");
-- CreateIndex
CREATE UNIQUE INDEX "accounts_provider_id_account_id_key" ON "accounts"("provider_id", "account_id");
-- CreateIndex
CREATE INDEX "verifications_identifier_idx" ON "verifications"("identifier");
-- CreateIndex
CREATE INDEX "activity_logs_action_idx" ON "activity_logs"("action");
-- AddForeignKey
ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "accounts" ADD CONSTRAINT "accounts_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -1,286 +0,0 @@
-- CreateEnum
CREATE TYPE "IdeaStatus" AS ENUM ('CAPTURED', 'PROCESSING', 'ACTIONABLE', 'ARCHIVED', 'DISCARDED');
-- CreateEnum
CREATE TYPE "RelationshipType" AS ENUM ('BLOCKS', 'BLOCKED_BY', 'DEPENDS_ON', 'PARENT_OF', 'CHILD_OF', 'RELATED_TO', 'DUPLICATE_OF', 'SUPERSEDES', 'PART_OF');
-- CreateEnum
CREATE TYPE "AgentStatus" AS ENUM ('IDLE', 'WORKING', 'WAITING', 'ERROR', 'TERMINATED');
-- AlterEnum
-- This migration adds more than one value to an enum.
-- With PostgreSQL versions 11 and earlier, this is not possible
-- in a single migration. This can be worked around by creating
-- multiple migrations, each migration adding only one value to
-- the enum.
ALTER TYPE "EntityType" ADD VALUE 'IDEA';
ALTER TYPE "EntityType" ADD VALUE 'DOMAIN';
-- DropIndex
DROP INDEX "memory_embeddings_embedding_idx";
-- AlterTable
ALTER TABLE "events" ADD COLUMN "domain_id" UUID;
-- AlterTable
ALTER TABLE "projects" ADD COLUMN "domain_id" UUID;
-- AlterTable
ALTER TABLE "tasks" ADD COLUMN "domain_id" UUID;
-- CreateTable
CREATE TABLE "domains" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"name" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"description" TEXT,
"color" TEXT,
"icon" TEXT,
"sort_order" INTEGER NOT NULL DEFAULT 0,
"metadata" JSONB NOT NULL DEFAULT '{}',
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
CONSTRAINT "domains_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ideas" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"domain_id" UUID,
"project_id" UUID,
"title" TEXT,
"content" TEXT NOT NULL,
"status" "IdeaStatus" NOT NULL DEFAULT 'CAPTURED',
"priority" "TaskPriority" NOT NULL DEFAULT 'MEDIUM',
"category" TEXT,
"tags" TEXT[],
"metadata" JSONB NOT NULL DEFAULT '{}',
"embedding" vector(1536),
"creator_id" UUID NOT NULL,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
CONSTRAINT "ideas_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "relationships" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"source_type" "EntityType" NOT NULL,
"source_id" UUID NOT NULL,
"target_type" "EntityType" NOT NULL,
"target_id" UUID NOT NULL,
"relationship" "RelationshipType" NOT NULL,
"metadata" JSONB NOT NULL DEFAULT '{}',
"notes" TEXT,
"creator_id" UUID NOT NULL,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "relationships_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "agents" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"agent_id" TEXT NOT NULL,
"name" TEXT,
"model" TEXT,
"role" TEXT,
"status" "AgentStatus" NOT NULL DEFAULT 'IDLE',
"current_task" TEXT,
"metrics" JSONB NOT NULL DEFAULT '{"totalTasks": 0, "successfulTasks": 0, "failedTasks": 0, "avgResponseTimeMs": 0}',
"last_heartbeat" TIMESTAMPTZ,
"error_count" INTEGER NOT NULL DEFAULT 0,
"last_error" TEXT,
"fired_count" INTEGER NOT NULL DEFAULT 0,
"fire_history" JSONB NOT NULL DEFAULT '[]',
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
"terminated_at" TIMESTAMPTZ,
CONSTRAINT "agents_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "agent_sessions" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"user_id" UUID NOT NULL,
"agent_id" UUID,
"session_key" TEXT NOT NULL,
"label" TEXT,
"channel" TEXT,
"context_summary" TEXT,
"message_count" INTEGER NOT NULL DEFAULT 0,
"is_active" BOOLEAN NOT NULL DEFAULT true,
"started_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"last_message_at" TIMESTAMPTZ,
"ended_at" TIMESTAMPTZ,
"metadata" JSONB NOT NULL DEFAULT '{}',
CONSTRAINT "agent_sessions_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "widget_definitions" (
"id" UUID NOT NULL,
"name" TEXT NOT NULL,
"display_name" TEXT NOT NULL,
"description" TEXT,
"component" TEXT NOT NULL,
"default_width" INTEGER NOT NULL DEFAULT 1,
"default_height" INTEGER NOT NULL DEFAULT 1,
"min_width" INTEGER NOT NULL DEFAULT 1,
"min_height" INTEGER NOT NULL DEFAULT 1,
"max_width" INTEGER,
"max_height" INTEGER,
"config_schema" JSONB NOT NULL DEFAULT '{}',
"is_active" BOOLEAN NOT NULL DEFAULT true,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "widget_definitions_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "user_layouts" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"user_id" UUID NOT NULL,
"name" TEXT NOT NULL,
"is_default" BOOLEAN NOT NULL DEFAULT false,
"layout" JSONB NOT NULL DEFAULT '[]',
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
CONSTRAINT "user_layouts_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "domains_workspace_id_idx" ON "domains"("workspace_id");
-- CreateIndex
CREATE UNIQUE INDEX "domains_workspace_id_slug_key" ON "domains"("workspace_id", "slug");
-- CreateIndex
CREATE INDEX "ideas_workspace_id_idx" ON "ideas"("workspace_id");
-- CreateIndex
CREATE INDEX "ideas_workspace_id_status_idx" ON "ideas"("workspace_id", "status");
-- CreateIndex
CREATE INDEX "ideas_domain_id_idx" ON "ideas"("domain_id");
-- CreateIndex
CREATE INDEX "ideas_project_id_idx" ON "ideas"("project_id");
-- CreateIndex
CREATE INDEX "ideas_creator_id_idx" ON "ideas"("creator_id");
-- CreateIndex
CREATE INDEX "relationships_source_type_source_id_idx" ON "relationships"("source_type", "source_id");
-- CreateIndex
CREATE INDEX "relationships_target_type_target_id_idx" ON "relationships"("target_type", "target_id");
-- CreateIndex
CREATE INDEX "relationships_relationship_idx" ON "relationships"("relationship");
-- CreateIndex
CREATE UNIQUE INDEX "relationships_workspace_id_source_type_source_id_target_typ_key" ON "relationships"("workspace_id", "source_type", "source_id", "target_type", "target_id", "relationship");
-- CreateIndex
CREATE INDEX "agents_workspace_id_idx" ON "agents"("workspace_id");
-- CreateIndex
CREATE INDEX "agents_status_idx" ON "agents"("status");
-- CreateIndex
CREATE UNIQUE INDEX "agents_workspace_id_agent_id_key" ON "agents"("workspace_id", "agent_id");
-- CreateIndex
CREATE INDEX "agent_sessions_workspace_id_idx" ON "agent_sessions"("workspace_id");
-- CreateIndex
CREATE INDEX "agent_sessions_user_id_idx" ON "agent_sessions"("user_id");
-- CreateIndex
CREATE INDEX "agent_sessions_agent_id_idx" ON "agent_sessions"("agent_id");
-- CreateIndex
CREATE INDEX "agent_sessions_is_active_idx" ON "agent_sessions"("is_active");
-- CreateIndex
CREATE UNIQUE INDEX "agent_sessions_workspace_id_session_key_key" ON "agent_sessions"("workspace_id", "session_key");
-- CreateIndex
CREATE UNIQUE INDEX "widget_definitions_name_key" ON "widget_definitions"("name");
-- CreateIndex
CREATE INDEX "user_layouts_user_id_idx" ON "user_layouts"("user_id");
-- CreateIndex
CREATE UNIQUE INDEX "user_layouts_workspace_id_user_id_name_key" ON "user_layouts"("workspace_id", "user_id", "name");
-- CreateIndex
CREATE INDEX "events_domain_id_idx" ON "events"("domain_id");
-- CreateIndex
CREATE INDEX "projects_domain_id_idx" ON "projects"("domain_id");
-- CreateIndex
CREATE INDEX "tasks_domain_id_idx" ON "tasks"("domain_id");
-- AddForeignKey
ALTER TABLE "tasks" ADD CONSTRAINT "tasks_domain_id_fkey" FOREIGN KEY ("domain_id") REFERENCES "domains"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "events" ADD CONSTRAINT "events_domain_id_fkey" FOREIGN KEY ("domain_id") REFERENCES "domains"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "projects" ADD CONSTRAINT "projects_domain_id_fkey" FOREIGN KEY ("domain_id") REFERENCES "domains"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "domains" ADD CONSTRAINT "domains_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ideas" ADD CONSTRAINT "ideas_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ideas" ADD CONSTRAINT "ideas_domain_id_fkey" FOREIGN KEY ("domain_id") REFERENCES "domains"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ideas" ADD CONSTRAINT "ideas_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ideas" ADD CONSTRAINT "ideas_creator_id_fkey" FOREIGN KEY ("creator_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "relationships" ADD CONSTRAINT "relationships_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "relationships" ADD CONSTRAINT "relationships_creator_id_fkey" FOREIGN KEY ("creator_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "agents" ADD CONSTRAINT "agents_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "agent_sessions" ADD CONSTRAINT "agent_sessions_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "agent_sessions" ADD CONSTRAINT "agent_sessions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "agent_sessions" ADD CONSTRAINT "agent_sessions_agent_id_fkey" FOREIGN KEY ("agent_id") REFERENCES "agents"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "user_layouts" ADD CONSTRAINT "user_layouts_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "user_layouts" ADD CONSTRAINT "user_layouts_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -1,158 +0,0 @@
-- CreateEnum
CREATE TYPE "EntryStatus" AS ENUM ('DRAFT', 'PUBLISHED', 'ARCHIVED');
-- CreateEnum
CREATE TYPE "Visibility" AS ENUM ('PRIVATE', 'WORKSPACE', 'PUBLIC');
-- AlterTable
ALTER TABLE "user_layouts" ADD COLUMN "metadata" JSONB NOT NULL DEFAULT '{}';
-- CreateTable
CREATE TABLE "knowledge_entries" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"slug" TEXT NOT NULL,
"title" TEXT NOT NULL,
"content" TEXT NOT NULL,
"content_html" TEXT,
"summary" TEXT,
"status" "EntryStatus" NOT NULL DEFAULT 'DRAFT',
"visibility" "Visibility" NOT NULL DEFAULT 'PRIVATE',
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
"created_by" UUID NOT NULL,
"updated_by" UUID NOT NULL,
CONSTRAINT "knowledge_entries_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "knowledge_entry_versions" (
"id" UUID NOT NULL,
"entry_id" UUID NOT NULL,
"version" INTEGER NOT NULL,
"title" TEXT NOT NULL,
"content" TEXT NOT NULL,
"summary" TEXT,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"created_by" UUID NOT NULL,
"change_note" TEXT,
CONSTRAINT "knowledge_entry_versions_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "knowledge_links" (
"id" UUID NOT NULL,
"source_id" UUID NOT NULL,
"target_id" UUID NOT NULL,
"link_text" TEXT NOT NULL,
"context" TEXT,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "knowledge_links_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "knowledge_tags" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"name" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"color" TEXT,
"description" TEXT,
CONSTRAINT "knowledge_tags_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "knowledge_entry_tags" (
"entry_id" UUID NOT NULL,
"tag_id" UUID NOT NULL,
CONSTRAINT "knowledge_entry_tags_pkey" PRIMARY KEY ("entry_id","tag_id")
);
-- CreateTable
CREATE TABLE "knowledge_embeddings" (
"id" UUID NOT NULL,
"entry_id" UUID NOT NULL,
"embedding" vector(1536) NOT NULL,
"model" TEXT NOT NULL,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
CONSTRAINT "knowledge_embeddings_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "knowledge_entries_workspace_id_status_idx" ON "knowledge_entries"("workspace_id", "status");
-- CreateIndex
CREATE INDEX "knowledge_entries_workspace_id_updated_at_idx" ON "knowledge_entries"("workspace_id", "updated_at");
-- CreateIndex
CREATE INDEX "knowledge_entries_created_by_idx" ON "knowledge_entries"("created_by");
-- CreateIndex
CREATE INDEX "knowledge_entries_updated_by_idx" ON "knowledge_entries"("updated_by");
-- CreateIndex
CREATE UNIQUE INDEX "knowledge_entries_workspace_id_slug_key" ON "knowledge_entries"("workspace_id", "slug");
-- CreateIndex
CREATE INDEX "knowledge_entry_versions_entry_id_version_idx" ON "knowledge_entry_versions"("entry_id", "version");
-- CreateIndex
CREATE UNIQUE INDEX "knowledge_entry_versions_entry_id_version_key" ON "knowledge_entry_versions"("entry_id", "version");
-- CreateIndex
CREATE INDEX "knowledge_links_source_id_idx" ON "knowledge_links"("source_id");
-- CreateIndex
CREATE INDEX "knowledge_links_target_id_idx" ON "knowledge_links"("target_id");
-- CreateIndex
CREATE UNIQUE INDEX "knowledge_links_source_id_target_id_key" ON "knowledge_links"("source_id", "target_id");
-- CreateIndex
CREATE INDEX "knowledge_tags_workspace_id_idx" ON "knowledge_tags"("workspace_id");
-- CreateIndex
CREATE UNIQUE INDEX "knowledge_tags_workspace_id_slug_key" ON "knowledge_tags"("workspace_id", "slug");
-- CreateIndex
CREATE INDEX "knowledge_entry_tags_entry_id_idx" ON "knowledge_entry_tags"("entry_id");
-- CreateIndex
CREATE INDEX "knowledge_entry_tags_tag_id_idx" ON "knowledge_entry_tags"("tag_id");
-- CreateIndex
CREATE UNIQUE INDEX "knowledge_embeddings_entry_id_key" ON "knowledge_embeddings"("entry_id");
-- CreateIndex
CREATE INDEX "knowledge_embeddings_entry_id_idx" ON "knowledge_embeddings"("entry_id");
-- AddForeignKey
ALTER TABLE "knowledge_entries" ADD CONSTRAINT "knowledge_entries_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "knowledge_entry_versions" ADD CONSTRAINT "knowledge_entry_versions_entry_id_fkey" FOREIGN KEY ("entry_id") REFERENCES "knowledge_entries"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "knowledge_links" ADD CONSTRAINT "knowledge_links_source_id_fkey" FOREIGN KEY ("source_id") REFERENCES "knowledge_entries"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "knowledge_links" ADD CONSTRAINT "knowledge_links_target_id_fkey" FOREIGN KEY ("target_id") REFERENCES "knowledge_entries"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "knowledge_tags" ADD CONSTRAINT "knowledge_tags_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "knowledge_entry_tags" ADD CONSTRAINT "knowledge_entry_tags_entry_id_fkey" FOREIGN KEY ("entry_id") REFERENCES "knowledge_entries"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "knowledge_entry_tags" ADD CONSTRAINT "knowledge_entry_tags_tag_id_fkey" FOREIGN KEY ("tag_id") REFERENCES "knowledge_tags"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "knowledge_embeddings" ADD CONSTRAINT "knowledge_embeddings_entry_id_fkey" FOREIGN KEY ("entry_id") REFERENCES "knowledge_entries"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -1,40 +0,0 @@
-- CreateEnum
CREATE TYPE "TeamMemberRole" AS ENUM ('OWNER', 'ADMIN', 'MEMBER');
-- CreateTable
CREATE TABLE "teams" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"metadata" JSONB NOT NULL DEFAULT '{}',
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
CONSTRAINT "teams_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "team_members" (
"team_id" UUID NOT NULL,
"user_id" UUID NOT NULL,
"role" "TeamMemberRole" NOT NULL DEFAULT 'MEMBER',
"joined_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "team_members_pkey" PRIMARY KEY ("team_id","user_id")
);
-- CreateIndex
CREATE INDEX "teams_workspace_id_idx" ON "teams"("workspace_id");
-- CreateIndex
CREATE INDEX "team_members_user_id_idx" ON "team_members"("user_id");
-- AddForeignKey
ALTER TABLE "teams" ADD CONSTRAINT "teams_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "team_members" ADD CONSTRAINT "team_members_team_id_fkey" FOREIGN KEY ("team_id") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "team_members" ADD CONSTRAINT "team_members_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -1,319 +0,0 @@
-- Row-Level Security (RLS) for Multi-Tenant Isolation
-- This migration enables RLS on all tenant-scoped tables and creates policies
-- to ensure users can only access data within their authorized workspaces.
-- =============================================================================
-- ENABLE RLS ON TENANT-SCOPED TABLES
-- =============================================================================
ALTER TABLE workspaces ENABLE ROW LEVEL SECURITY;
ALTER TABLE workspace_members ENABLE ROW LEVEL SECURITY;
ALTER TABLE teams ENABLE ROW LEVEL SECURITY;
ALTER TABLE team_members ENABLE ROW LEVEL SECURITY;
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;
ALTER TABLE events ENABLE ROW LEVEL SECURITY;
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
ALTER TABLE activity_logs ENABLE ROW LEVEL SECURITY;
ALTER TABLE memory_embeddings ENABLE ROW LEVEL SECURITY;
ALTER TABLE domains ENABLE ROW LEVEL SECURITY;
ALTER TABLE ideas ENABLE ROW LEVEL SECURITY;
ALTER TABLE relationships ENABLE ROW LEVEL SECURITY;
ALTER TABLE agents ENABLE ROW LEVEL SECURITY;
ALTER TABLE agent_sessions ENABLE ROW LEVEL SECURITY;
ALTER TABLE user_layouts ENABLE ROW LEVEL SECURITY;
ALTER TABLE knowledge_entries ENABLE ROW LEVEL SECURITY;
ALTER TABLE knowledge_tags ENABLE ROW LEVEL SECURITY;
ALTER TABLE knowledge_entry_tags ENABLE ROW LEVEL SECURITY;
ALTER TABLE knowledge_links ENABLE ROW LEVEL SECURITY;
ALTER TABLE knowledge_embeddings ENABLE ROW LEVEL SECURITY;
ALTER TABLE knowledge_entry_versions ENABLE ROW LEVEL SECURITY;
-- =============================================================================
-- HELPER FUNCTION: Check if user is workspace member
-- =============================================================================
CREATE OR REPLACE FUNCTION is_workspace_member(workspace_uuid UUID, user_uuid UUID)
RETURNS BOOLEAN AS $$
BEGIN
RETURN EXISTS (
SELECT 1 FROM workspace_members
WHERE workspace_id = workspace_uuid
AND user_id = user_uuid
);
END;
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
-- =============================================================================
-- HELPER FUNCTION: Check if user is workspace owner/admin
-- =============================================================================
CREATE OR REPLACE FUNCTION is_workspace_admin(workspace_uuid UUID, user_uuid UUID)
RETURNS BOOLEAN AS $$
BEGIN
RETURN EXISTS (
SELECT 1 FROM workspace_members
WHERE workspace_id = workspace_uuid
AND user_id = user_uuid
AND role IN ('OWNER', 'ADMIN')
);
END;
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
-- =============================================================================
-- HELPER FUNCTION: Get current user ID from session variable
-- =============================================================================
-- Usage in API: SET LOCAL app.current_user_id = 'user-uuid';
CREATE OR REPLACE FUNCTION current_user_id()
RETURNS UUID AS $$
BEGIN
RETURN NULLIF(current_setting('app.current_user_id', TRUE), '')::UUID;
EXCEPTION
WHEN OTHERS THEN
RETURN NULL;
END;
$$ LANGUAGE plpgsql STABLE;
-- =============================================================================
-- WORKSPACES: Users can only see workspaces they're members of
-- =============================================================================
CREATE POLICY workspace_member_access ON workspaces
FOR ALL
USING (
id IN (
SELECT workspace_id FROM workspace_members
WHERE user_id = current_user_id()
)
);
-- =============================================================================
-- WORKSPACE_MEMBERS: Users can see members of their workspaces
-- =============================================================================
CREATE POLICY workspace_members_access ON workspace_members
FOR ALL
USING (
workspace_id IN (
SELECT workspace_id FROM workspace_members
WHERE user_id = current_user_id()
)
);
-- =============================================================================
-- TEAMS: Users can see teams in their workspaces
-- =============================================================================
CREATE POLICY teams_workspace_access ON teams
FOR ALL
USING (
is_workspace_member(workspace_id, current_user_id())
);
-- =============================================================================
-- TEAM_MEMBERS: Users can see team members in their workspaces
-- =============================================================================
CREATE POLICY team_members_access ON team_members
FOR ALL
USING (
team_id IN (
SELECT id FROM teams
WHERE is_workspace_member(workspace_id, current_user_id())
)
);
-- =============================================================================
-- TASKS: Users can only see tasks in their workspaces
-- =============================================================================
CREATE POLICY tasks_workspace_access ON tasks
FOR ALL
USING (
is_workspace_member(workspace_id, current_user_id())
);
-- =============================================================================
-- EVENTS: Users can only see events in their workspaces
-- =============================================================================
CREATE POLICY events_workspace_access ON events
FOR ALL
USING (
is_workspace_member(workspace_id, current_user_id())
);
-- =============================================================================
-- PROJECTS: Users can only see projects in their workspaces
-- =============================================================================
CREATE POLICY projects_workspace_access ON projects
FOR ALL
USING (
is_workspace_member(workspace_id, current_user_id())
);
-- =============================================================================
-- ACTIVITY_LOGS: Users can only see activity in their workspaces
-- =============================================================================
CREATE POLICY activity_logs_workspace_access ON activity_logs
FOR ALL
USING (
is_workspace_member(workspace_id, current_user_id())
);
-- =============================================================================
-- MEMORY_EMBEDDINGS: Users can only see embeddings in their workspaces
-- =============================================================================
CREATE POLICY memory_embeddings_workspace_access ON memory_embeddings
FOR ALL
USING (
is_workspace_member(workspace_id, current_user_id())
);
-- =============================================================================
-- DOMAINS: Users can only see domains in their workspaces
-- =============================================================================
CREATE POLICY domains_workspace_access ON domains
FOR ALL
USING (
is_workspace_member(workspace_id, current_user_id())
);
-- =============================================================================
-- IDEAS: Users can only see ideas in their workspaces
-- =============================================================================
CREATE POLICY ideas_workspace_access ON ideas
FOR ALL
USING (
is_workspace_member(workspace_id, current_user_id())
);
-- =============================================================================
-- RELATIONSHIPS: Users can only see relationships in their workspaces
-- =============================================================================
CREATE POLICY relationships_workspace_access ON relationships
FOR ALL
USING (
is_workspace_member(workspace_id, current_user_id())
);
-- =============================================================================
-- AGENTS: Users can only see agents in their workspaces
-- =============================================================================
CREATE POLICY agents_workspace_access ON agents
FOR ALL
USING (
is_workspace_member(workspace_id, current_user_id())
);
-- =============================================================================
-- AGENT_SESSIONS: Users can only see agent sessions in their workspaces
-- =============================================================================
CREATE POLICY agent_sessions_workspace_access ON agent_sessions
FOR ALL
USING (
is_workspace_member(workspace_id, current_user_id())
);
-- =============================================================================
-- USER_LAYOUTS: Users can only see their own layouts in their workspaces
-- =============================================================================
CREATE POLICY user_layouts_workspace_access ON user_layouts
FOR ALL
USING (
is_workspace_member(workspace_id, current_user_id())
AND user_id = current_user_id()
);
-- =============================================================================
-- KNOWLEDGE_ENTRIES: Users can only see entries in their workspaces
-- =============================================================================
CREATE POLICY knowledge_entries_workspace_access ON knowledge_entries
FOR ALL
USING (
is_workspace_member(workspace_id, current_user_id())
);
-- =============================================================================
-- KNOWLEDGE_TAGS: Users can only see tags in their workspaces
-- =============================================================================
CREATE POLICY knowledge_tags_workspace_access ON knowledge_tags
FOR ALL
USING (
is_workspace_member(workspace_id, current_user_id())
);
-- =============================================================================
-- KNOWLEDGE_ENTRY_TAGS: Users can see tags for entries in their workspaces
-- =============================================================================
CREATE POLICY knowledge_entry_tags_access ON knowledge_entry_tags
FOR ALL
USING (
entry_id IN (
SELECT id FROM knowledge_entries
WHERE is_workspace_member(workspace_id, current_user_id())
)
);
-- =============================================================================
-- KNOWLEDGE_LINKS: Users can see links between entries in their workspaces
-- =============================================================================
CREATE POLICY knowledge_links_access ON knowledge_links
FOR ALL
USING (
source_id IN (
SELECT id FROM knowledge_entries
WHERE is_workspace_member(workspace_id, current_user_id())
)
);
-- =============================================================================
-- KNOWLEDGE_EMBEDDINGS: Users can see embeddings for entries in their workspaces
-- =============================================================================
CREATE POLICY knowledge_embeddings_access ON knowledge_embeddings
FOR ALL
USING (
entry_id IN (
SELECT id FROM knowledge_entries
WHERE is_workspace_member(workspace_id, current_user_id())
)
);
-- =============================================================================
-- KNOWLEDGE_ENTRY_VERSIONS: Users can see versions for entries in their workspaces
-- =============================================================================
CREATE POLICY knowledge_entry_versions_access ON knowledge_entry_versions
FOR ALL
USING (
entry_id IN (
SELECT id FROM knowledge_entries
WHERE is_workspace_member(workspace_id, current_user_id())
)
);
-- =============================================================================
-- GRANT USAGE TO APPLICATION ROLE
-- =============================================================================
-- The application should connect with a role that has appropriate permissions.
-- By default, we assume the owner of the database has full access.
-- In production, create a dedicated role with limited permissions.
-- Example (uncomment and customize for production):
-- GRANT USAGE ON SCHEMA public TO mosaic_app;
-- GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO mosaic_app;
-- GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO mosaic_app;
@@ -1,18 +0,0 @@
-- CreateTable
CREATE TABLE "user_preferences" (
"id" UUID NOT NULL,
"user_id" UUID NOT NULL,
"theme" TEXT NOT NULL DEFAULT 'system',
"locale" TEXT NOT NULL DEFAULT 'en',
"timezone" TEXT,
"settings" JSONB NOT NULL DEFAULT '{}',
"updated_at" TIMESTAMPTZ NOT NULL,
CONSTRAINT "user_preferences_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "user_preferences_user_id_key" ON "user_preferences"("user_id");
-- AddForeignKey
ALTER TABLE "user_preferences" ADD CONSTRAINT "user_preferences_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -1,47 +0,0 @@
-- CreateEnum
CREATE TYPE "AgentTaskStatus" AS ENUM ('PENDING', 'RUNNING', 'COMPLETED', 'FAILED');
-- CreateEnum
CREATE TYPE "AgentTaskPriority" AS ENUM ('LOW', 'MEDIUM', 'HIGH');
-- CreateTable
CREATE TABLE "agent_tasks" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"status" "AgentTaskStatus" NOT NULL DEFAULT 'PENDING',
"priority" "AgentTaskPriority" NOT NULL DEFAULT 'MEDIUM',
"agent_type" TEXT NOT NULL,
"agent_config" JSONB NOT NULL DEFAULT '{}',
"result" JSONB,
"error" TEXT,
"created_by_id" UUID NOT NULL,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
"started_at" TIMESTAMPTZ,
"completed_at" TIMESTAMPTZ,
CONSTRAINT "agent_tasks_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "agent_tasks_workspace_id_idx" ON "agent_tasks"("workspace_id");
-- CreateIndex
CREATE INDEX "agent_tasks_workspace_id_status_idx" ON "agent_tasks"("workspace_id", "status");
-- CreateIndex
CREATE INDEX "agent_tasks_workspace_id_priority_idx" ON "agent_tasks"("workspace_id", "priority");
-- CreateIndex
CREATE INDEX "agent_tasks_created_by_id_idx" ON "agent_tasks"("created_by_id");
-- CreateIndex
CREATE UNIQUE INDEX "agent_tasks_id_workspace_id_key" ON "agent_tasks"("id", "workspace_id");
-- AddForeignKey
ALTER TABLE "agent_tasks" ADD CONSTRAINT "agent_tasks_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "agent_tasks" ADD CONSTRAINT "agent_tasks_created_by_id_fkey" FOREIGN KEY ("created_by_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -1,31 +0,0 @@
-- CreateEnum
CREATE TYPE "FormalityLevel" AS ENUM ('VERY_CASUAL', 'CASUAL', 'NEUTRAL', 'FORMAL', 'VERY_FORMAL');
-- CreateTable
CREATE TABLE "personalities" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"tone" TEXT NOT NULL,
"formality_level" "FormalityLevel" NOT NULL DEFAULT 'NEUTRAL',
"system_prompt_template" TEXT NOT NULL,
"is_default" BOOLEAN NOT NULL DEFAULT false,
"is_active" BOOLEAN NOT NULL DEFAULT true,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
CONSTRAINT "personalities_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "personalities_workspace_id_idx" ON "personalities"("workspace_id");
-- CreateIndex
CREATE INDEX "personalities_workspace_id_is_default_idx" ON "personalities"("workspace_id", "is_default");
-- CreateIndex
CREATE UNIQUE INDEX "personalities_workspace_id_name_key" ON "personalities"("workspace_id", "name");
-- AddForeignKey
ALTER TABLE "personalities" ADD CONSTRAINT "personalities_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -1,41 +0,0 @@
/*
Warnings:
- You are about to drop the `personalities` table. If the table is not empty, all the data it contains will be lost.
- Added the required column `display_text` to the `knowledge_links` table without a default value. This is not possible if the table is not empty.
- Added the required column `position_end` to the `knowledge_links` table without a default value. This is not possible if the table is not empty.
- Added the required column `position_start` to the `knowledge_links` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "personalities" DROP CONSTRAINT "personalities_workspace_id_fkey";
-- DropIndex
DROP INDEX "knowledge_links_source_id_target_id_key";
-- AlterTable: Add new columns with temporary defaults for existing records
ALTER TABLE "knowledge_links"
ADD COLUMN "display_text" TEXT DEFAULT '',
ADD COLUMN "position_end" INTEGER DEFAULT 0,
ADD COLUMN "position_start" INTEGER DEFAULT 0,
ADD COLUMN "resolved" BOOLEAN NOT NULL DEFAULT false,
ALTER COLUMN "target_id" DROP NOT NULL;
-- Update existing records: set display_text to link_text and resolved to true if target exists
UPDATE "knowledge_links" SET "display_text" = "link_text" WHERE "display_text" = '';
UPDATE "knowledge_links" SET "resolved" = true WHERE "target_id" IS NOT NULL;
-- Remove defaults for new records
ALTER TABLE "knowledge_links"
ALTER COLUMN "display_text" DROP DEFAULT,
ALTER COLUMN "position_end" DROP DEFAULT,
ALTER COLUMN "position_start" DROP DEFAULT;
-- DropTable
DROP TABLE "personalities";
-- DropEnum
DROP TYPE "FormalityLevel";
-- CreateIndex
CREATE INDEX "knowledge_links_source_id_resolved_idx" ON "knowledge_links"("source_id", "resolved");
@@ -1,8 +0,0 @@
-- Add HNSW index for fast vector similarity search on knowledge_embeddings table
-- Using cosine distance operator for semantic similarity
-- Parameters: m=16 (max connections per layer), ef_construction=64 (build quality)
CREATE INDEX IF NOT EXISTS knowledge_embeddings_embedding_idx
ON knowledge_embeddings
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
@@ -1,29 +0,0 @@
-- CreateTable
CREATE TABLE "llm_provider_instances" (
"id" UUID NOT NULL,
"provider_type" TEXT NOT NULL,
"display_name" TEXT NOT NULL,
"user_id" UUID,
"config" JSONB NOT NULL,
"is_default" BOOLEAN NOT NULL DEFAULT false,
"is_enabled" BOOLEAN NOT NULL DEFAULT true,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
CONSTRAINT "llm_provider_instances_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "llm_provider_instances_user_id_idx" ON "llm_provider_instances"("user_id");
-- CreateIndex
CREATE INDEX "llm_provider_instances_provider_type_idx" ON "llm_provider_instances"("provider_type");
-- CreateIndex
CREATE INDEX "llm_provider_instances_is_default_idx" ON "llm_provider_instances"("is_default");
-- CreateIndex
CREATE INDEX "llm_provider_instances_is_enabled_idx" ON "llm_provider_instances"("is_enabled");
-- AddForeignKey
ALTER TABLE "llm_provider_instances" ADD CONSTRAINT "llm_provider_instances_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -1,112 +0,0 @@
-- CreateEnum
CREATE TYPE "RunnerJobStatus" AS ENUM ('PENDING', 'QUEUED', 'RUNNING', 'COMPLETED', 'FAILED', 'CANCELLED');
-- CreateEnum
CREATE TYPE "JobStepPhase" AS ENUM ('SETUP', 'EXECUTION', 'VALIDATION', 'CLEANUP');
-- CreateEnum
CREATE TYPE "JobStepType" AS ENUM ('COMMAND', 'AI_ACTION', 'GATE', 'ARTIFACT');
-- CreateEnum
CREATE TYPE "JobStepStatus" AS ENUM ('PENDING', 'RUNNING', 'COMPLETED', 'FAILED', 'SKIPPED');
-- CreateTable
CREATE TABLE "runner_jobs" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"agent_task_id" UUID,
"type" TEXT NOT NULL,
"status" "RunnerJobStatus" NOT NULL DEFAULT 'PENDING',
"priority" INTEGER NOT NULL,
"progress_percent" INTEGER NOT NULL DEFAULT 0,
"result" JSONB,
"error" TEXT,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"started_at" TIMESTAMPTZ,
"completed_at" TIMESTAMPTZ,
CONSTRAINT "runner_jobs_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "job_steps" (
"id" UUID NOT NULL,
"job_id" UUID NOT NULL,
"ordinal" INTEGER NOT NULL,
"phase" "JobStepPhase" NOT NULL,
"name" TEXT NOT NULL,
"type" "JobStepType" NOT NULL,
"status" "JobStepStatus" NOT NULL DEFAULT 'PENDING',
"output" TEXT,
"tokens_input" INTEGER,
"tokens_output" INTEGER,
"started_at" TIMESTAMPTZ,
"completed_at" TIMESTAMPTZ,
"duration_ms" INTEGER,
CONSTRAINT "job_steps_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "job_events" (
"id" UUID NOT NULL,
"job_id" UUID NOT NULL,
"step_id" UUID,
"type" TEXT NOT NULL,
"timestamp" TIMESTAMPTZ NOT NULL,
"actor" TEXT NOT NULL,
"payload" JSONB NOT NULL,
CONSTRAINT "job_events_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "runner_jobs_id_workspace_id_key" ON "runner_jobs"("id", "workspace_id");
-- CreateIndex
CREATE INDEX "runner_jobs_workspace_id_idx" ON "runner_jobs"("workspace_id");
-- CreateIndex
CREATE INDEX "runner_jobs_workspace_id_status_idx" ON "runner_jobs"("workspace_id", "status");
-- CreateIndex
CREATE INDEX "runner_jobs_agent_task_id_idx" ON "runner_jobs"("agent_task_id");
-- CreateIndex
CREATE INDEX "runner_jobs_priority_idx" ON "runner_jobs"("priority");
-- CreateIndex
CREATE INDEX "job_steps_job_id_idx" ON "job_steps"("job_id");
-- CreateIndex
CREATE INDEX "job_steps_job_id_ordinal_idx" ON "job_steps"("job_id", "ordinal");
-- CreateIndex
CREATE INDEX "job_steps_status_idx" ON "job_steps"("status");
-- CreateIndex
CREATE INDEX "job_events_job_id_idx" ON "job_events"("job_id");
-- CreateIndex
CREATE INDEX "job_events_step_id_idx" ON "job_events"("step_id");
-- CreateIndex
CREATE INDEX "job_events_timestamp_idx" ON "job_events"("timestamp");
-- CreateIndex
CREATE INDEX "job_events_type_idx" ON "job_events"("type");
-- AddForeignKey
ALTER TABLE "runner_jobs" ADD CONSTRAINT "runner_jobs_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "runner_jobs" ADD CONSTRAINT "runner_jobs_agent_task_id_fkey" FOREIGN KEY ("agent_task_id") REFERENCES "agent_tasks"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "job_steps" ADD CONSTRAINT "job_steps_job_id_fkey" FOREIGN KEY ("job_id") REFERENCES "runner_jobs"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "job_events" ADD CONSTRAINT "job_events_job_id_fkey" FOREIGN KEY ("job_id") REFERENCES "runner_jobs"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "job_events" ADD CONSTRAINT "job_events_step_id_fkey" FOREIGN KEY ("step_id") REFERENCES "job_steps"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -1,2 +0,0 @@
-- CreateIndex
CREATE INDEX "job_events_job_id_timestamp_idx" ON "job_events"("job_id", "timestamp");
@@ -1,36 +0,0 @@
-- Add tsvector column for full-text search on knowledge_entries
-- Weighted fields: title (A), summary (B), content (C)
-- Step 1: Add the search_vector column
ALTER TABLE "knowledge_entries"
ADD COLUMN "search_vector" tsvector;
-- Step 2: Create GIN index for fast full-text search
CREATE INDEX "knowledge_entries_search_vector_idx"
ON "knowledge_entries"
USING gin("search_vector");
-- Step 3: Create function to update search_vector
CREATE OR REPLACE FUNCTION knowledge_entries_search_vector_update()
RETURNS trigger AS $$
BEGIN
NEW.search_vector :=
setweight(to_tsvector('english', COALESCE(NEW.title, '')), 'A') ||
setweight(to_tsvector('english', COALESCE(NEW.summary, '')), 'B') ||
setweight(to_tsvector('english', COALESCE(NEW.content, '')), 'C');
RETURN NEW;
END
$$ LANGUAGE plpgsql;
-- Step 4: Create trigger to automatically update search_vector on insert/update
CREATE TRIGGER knowledge_entries_search_vector_trigger
BEFORE INSERT OR UPDATE ON "knowledge_entries"
FOR EACH ROW
EXECUTE FUNCTION knowledge_entries_search_vector_update();
-- Step 5: Populate search_vector for existing entries
UPDATE "knowledge_entries"
SET search_vector =
setweight(to_tsvector('english', COALESCE(title, '')), 'A') ||
setweight(to_tsvector('english', COALESCE(summary, '')), 'B') ||
setweight(to_tsvector('english', COALESCE(content, '')), 'C');
@@ -1,118 +0,0 @@
-- CreateEnum
CREATE TYPE "FederationConnectionStatus" AS ENUM ('PENDING', 'ACTIVE', 'SUSPENDED', 'DISCONNECTED');
-- CreateEnum
CREATE TYPE "FederationMessageType" AS ENUM ('QUERY', 'COMMAND', 'EVENT');
-- CreateEnum
CREATE TYPE "FederationMessageStatus" AS ENUM ('PENDING', 'DELIVERED', 'FAILED', 'TIMEOUT');
-- CreateTable
CREATE TABLE "federation_connections" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"remote_instance_id" TEXT NOT NULL,
"remote_url" TEXT NOT NULL,
"remote_public_key" TEXT NOT NULL,
"remote_capabilities" JSONB NOT NULL DEFAULT '{}',
"status" "FederationConnectionStatus" NOT NULL DEFAULT 'PENDING',
"metadata" JSONB NOT NULL DEFAULT '{}',
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
"connected_at" TIMESTAMPTZ,
"disconnected_at" TIMESTAMPTZ,
CONSTRAINT "federation_connections_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "federated_identities" (
"id" UUID NOT NULL,
"local_user_id" UUID NOT NULL,
"remote_user_id" TEXT NOT NULL,
"remote_instance_id" TEXT NOT NULL,
"oidc_subject" TEXT NOT NULL,
"email" TEXT NOT NULL,
"metadata" JSONB NOT NULL DEFAULT '{}',
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
CONSTRAINT "federated_identities_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "federation_messages" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"connection_id" UUID NOT NULL,
"message_type" "FederationMessageType" NOT NULL,
"message_id" TEXT NOT NULL,
"correlation_id" TEXT,
"query" TEXT,
"command_type" TEXT,
"event_type" TEXT,
"payload" JSONB DEFAULT '{}',
"response" JSONB DEFAULT '{}',
"status" "FederationMessageStatus" NOT NULL DEFAULT 'PENDING',
"error" TEXT,
"signature" TEXT NOT NULL,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
"delivered_at" TIMESTAMPTZ,
CONSTRAINT "federation_messages_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "federation_connections_workspace_id_remote_instance_id_key" ON "federation_connections"("workspace_id", "remote_instance_id");
-- CreateIndex
CREATE INDEX "federation_connections_workspace_id_idx" ON "federation_connections"("workspace_id");
-- CreateIndex
CREATE INDEX "federation_connections_workspace_id_status_idx" ON "federation_connections"("workspace_id", "status");
-- CreateIndex
CREATE INDEX "federation_connections_remote_instance_id_idx" ON "federation_connections"("remote_instance_id");
-- CreateIndex
CREATE UNIQUE INDEX "federated_identities_local_user_id_remote_instance_id_key" ON "federated_identities"("local_user_id", "remote_instance_id");
-- CreateIndex
CREATE INDEX "federated_identities_local_user_id_idx" ON "federated_identities"("local_user_id");
-- CreateIndex
CREATE INDEX "federated_identities_remote_instance_id_idx" ON "federated_identities"("remote_instance_id");
-- CreateIndex
CREATE INDEX "federated_identities_oidc_subject_idx" ON "federated_identities"("oidc_subject");
-- CreateIndex
CREATE UNIQUE INDEX "federation_messages_message_id_key" ON "federation_messages"("message_id");
-- CreateIndex
CREATE INDEX "federation_messages_workspace_id_idx" ON "federation_messages"("workspace_id");
-- CreateIndex
CREATE INDEX "federation_messages_connection_id_idx" ON "federation_messages"("connection_id");
-- CreateIndex
CREATE INDEX "federation_messages_message_id_idx" ON "federation_messages"("message_id");
-- CreateIndex
CREATE INDEX "federation_messages_correlation_id_idx" ON "federation_messages"("correlation_id");
-- CreateIndex
CREATE INDEX "federation_messages_event_type_idx" ON "federation_messages"("event_type");
-- AddForeignKey
ALTER TABLE "federation_connections" ADD CONSTRAINT "federation_connections_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "federated_identities" ADD CONSTRAINT "federated_identities_local_user_id_fkey" FOREIGN KEY ("local_user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "federation_messages" ADD CONSTRAINT "federation_messages_connection_id_fkey" FOREIGN KEY ("connection_id") REFERENCES "federation_connections"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "federation_messages" ADD CONSTRAINT "federation_messages_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -1,7 +0,0 @@
-- Add version field for optimistic locking to prevent race conditions
-- This allows safe concurrent updates to runner job status
ALTER TABLE "runner_jobs" ADD COLUMN "version" INTEGER NOT NULL DEFAULT 1;
-- Create index for better performance on version checks
CREATE INDEX "runner_jobs_version_idx" ON "runner_jobs"("version");
@@ -1,34 +0,0 @@
-- CreateTable
CREATE TABLE "federation_event_subscriptions" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"connection_id" UUID NOT NULL,
"event_type" TEXT NOT NULL,
"metadata" JSONB NOT NULL DEFAULT '{}',
"is_active" BOOLEAN NOT NULL DEFAULT true,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
CONSTRAINT "federation_event_subscriptions_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "federation_event_subscriptions_workspace_id_idx" ON "federation_event_subscriptions"("workspace_id");
-- CreateIndex
CREATE INDEX "federation_event_subscriptions_connection_id_idx" ON "federation_event_subscriptions"("connection_id");
-- CreateIndex
CREATE INDEX "federation_event_subscriptions_event_type_idx" ON "federation_event_subscriptions"("event_type");
-- CreateIndex
CREATE INDEX "federation_event_subscriptions_workspace_id_is_active_idx" ON "federation_event_subscriptions"("workspace_id", "is_active");
-- CreateIndex
CREATE UNIQUE INDEX "federation_event_subscriptions_workspace_id_connection_id_even_key" ON "federation_event_subscriptions"("workspace_id", "connection_id", "event_type");
-- AddForeignKey
ALTER TABLE "federation_event_subscriptions" ADD CONSTRAINT "federation_event_subscriptions_connection_id_fkey" FOREIGN KEY ("connection_id") REFERENCES "federation_connections"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "federation_event_subscriptions" ADD CONSTRAINT "federation_event_subscriptions_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -1,18 +0,0 @@
-- Rollback: SQL Injection Hardening for is_workspace_admin() Helper Function
-- This reverts the function to its previous implementation
-- =============================================================================
-- REVERT is_workspace_admin() to original implementation
-- =============================================================================
CREATE OR REPLACE FUNCTION is_workspace_admin(workspace_uuid UUID, user_uuid UUID)
RETURNS BOOLEAN AS $$
BEGIN
RETURN EXISTS (
SELECT 1 FROM workspace_members
WHERE workspace_id = workspace_uuid
AND user_id = user_uuid
AND role IN ('OWNER', 'ADMIN')
);
END;
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
@@ -1,58 +0,0 @@
-- Security Fix: SQL Injection Hardening for is_workspace_admin() Helper Function
-- This migration adds explicit UUID validation to prevent SQL injection attacks
--
-- Related: #355 Code Review - Security CRIT-3
-- Original issue: Migration 20260129221004_add_rls_policies
-- =============================================================================
-- SECURITY FIX: Add explicit UUID validation to is_workspace_admin()
-- =============================================================================
-- The is_workspace_admin() function previously accepted UUID parameters without
-- explicit type casting/validation. Although PostgreSQL's parameter binding provides
-- some protection, explicit UUID type validation is a security best practice.
--
-- This fix adds explicit UUID validation using PostgreSQL's uuid type checking
-- to ensure that non-UUID values cannot bypass the function's intent.
CREATE OR REPLACE FUNCTION is_workspace_admin(workspace_uuid UUID, user_uuid UUID)
RETURNS BOOLEAN AS $$
DECLARE
-- Validate input parameters are valid UUIDs
v_workspace_id UUID;
v_user_id UUID;
BEGIN
-- Explicitly validate workspace_uuid parameter
IF workspace_uuid IS NULL THEN
RETURN FALSE;
END IF;
v_workspace_id := workspace_uuid::UUID;
-- Explicitly validate user_uuid parameter
IF user_uuid IS NULL THEN
RETURN FALSE;
END IF;
v_user_id := user_uuid::UUID;
-- Query with validated parameters
RETURN EXISTS (
SELECT 1 FROM workspace_members
WHERE workspace_id = v_workspace_id
AND user_id = v_user_id
AND role IN ('OWNER', 'ADMIN')
);
END;
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER;
-- =============================================================================
-- NOTES
-- =============================================================================
-- This is a hardening fix that adds defense-in-depth to the is_workspace_admin()
-- helper function. While PostgreSQL's parameterized queries already provide
-- protection against SQL injection, explicit UUID type validation ensures:
--
-- 1. Parameters are explicitly cast to UUID type
-- 2. NULL values are handled defensively
-- 3. The function's intent is clear and secure
-- 4. Compliance with security best practices
--
-- This change is backward compatible and does not affect existing functionality.
@@ -1,91 +0,0 @@
-- Row-Level Security (RLS) for Auth Tables
-- This migration adds FORCE ROW LEVEL SECURITY and policies to accounts and sessions tables
-- to ensure users can only access their own authentication data.
--
-- Related: #350 - Add RLS policies to auth tables with FORCE enforcement
-- Design: docs/design/credential-security.md (Phase 1a)
-- =============================================================================
-- ENABLE FORCE RLS ON AUTH TABLES
-- =============================================================================
-- FORCE means the table owner (mosaic) is also subject to RLS policies.
-- This prevents Prisma (connecting as owner) from bypassing policies.
ALTER TABLE accounts ENABLE ROW LEVEL SECURITY;
ALTER TABLE accounts FORCE ROW LEVEL SECURITY;
ALTER TABLE sessions ENABLE ROW LEVEL SECURITY;
ALTER TABLE sessions FORCE ROW LEVEL SECURITY;
-- =============================================================================
-- ACCOUNTS TABLE POLICIES
-- =============================================================================
-- Owner bypass policy: Allow access to all rows ONLY when no RLS context is set
-- This is required for:
-- 1. Prisma migrations that run without RLS context
-- 2. BetterAuth internal operations during authentication flow (when no user context)
-- 3. Database maintenance operations
-- When RLS context IS set (current_user_id() returns non-NULL), this policy does not apply
--
-- NOTE: If connecting as a PostgreSQL superuser (like the default 'mosaic' role),
-- RLS policies are bypassed entirely. For full RLS enforcement, the application
-- should connect as a non-superuser role. See docs/design/credential-security.md
CREATE POLICY accounts_owner_bypass ON accounts
FOR ALL
USING (current_user_id() IS NULL);
-- User access policy: Users can only access their own accounts
-- Uses current_user_id() helper from migration 20260129221004_add_rls_policies
-- This policy applies to all operations: SELECT, INSERT, UPDATE, DELETE
CREATE POLICY accounts_user_access ON accounts
FOR ALL
USING (user_id = current_user_id());
-- =============================================================================
-- SESSIONS TABLE POLICIES
-- =============================================================================
-- Owner bypass policy: Allow access to all rows ONLY when no RLS context is set
-- See note on accounts_owner_bypass policy about superuser limitations
CREATE POLICY sessions_owner_bypass ON sessions
FOR ALL
USING (current_user_id() IS NULL);
-- User access policy: Users can only access their own sessions
CREATE POLICY sessions_user_access ON sessions
FOR ALL
USING (user_id = current_user_id());
-- =============================================================================
-- VERIFICATION TABLE ANALYSIS
-- =============================================================================
-- The verifications table does NOT need RLS policies because:
-- 1. It stores ephemeral verification tokens (email verification, password reset)
-- 2. It has no user_id column - only identifier (email) and value (token)
-- 3. Tokens are short-lived and accessed by token value, not user context
-- 4. BetterAuth manages access control through token validation, not RLS
-- 5. No cross-user data leakage risk since tokens are random and expire
--
-- Therefore, we intentionally do NOT add RLS to verifications table.
-- =============================================================================
-- IMPORTANT: SUPERUSER LIMITATION
-- =============================================================================
-- PostgreSQL superusers (including the default 'mosaic' role) ALWAYS bypass
-- Row-Level Security policies, even with FORCE ROW LEVEL SECURITY enabled.
-- This is a fundamental PostgreSQL security design.
--
-- For production deployments with full RLS enforcement, create a dedicated
-- non-superuser application role:
--
-- CREATE ROLE mosaic_app WITH LOGIN PASSWORD 'secure-password';
-- GRANT CONNECT ON DATABASE mosaic TO mosaic_app;
-- GRANT USAGE ON SCHEMA public TO mosaic_app;
-- GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO mosaic_app;
-- GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO mosaic_app;
--
-- Then update DATABASE_URL to connect as mosaic_app instead of mosaic.
-- The RLS policies will then be properly enforced for application queries.
--
-- See: https://www.postgresql.org/docs/current/ddl-rowsecurity.html
@@ -1,76 +0,0 @@
-- Rollback: User Credentials Storage with RLS Policies
-- This migration reverses all changes from migration.sql
--
-- Related: #355 - Create UserCredential Prisma model with RLS policies
-- =============================================================================
-- DROP TRIGGERS AND FUNCTIONS
-- =============================================================================
DROP TRIGGER IF EXISTS user_credentials_updated_at ON user_credentials;
DROP FUNCTION IF EXISTS update_user_credentials_updated_at();
-- =============================================================================
-- DISABLE RLS
-- =============================================================================
ALTER TABLE user_credentials DISABLE ROW LEVEL SECURITY;
-- =============================================================================
-- DROP RLS POLICIES
-- =============================================================================
DROP POLICY IF EXISTS user_credentials_owner_bypass ON user_credentials;
DROP POLICY IF EXISTS user_credentials_user_access ON user_credentials;
DROP POLICY IF EXISTS user_credentials_workspace_access ON user_credentials;
-- =============================================================================
-- DROP INDEXES
-- =============================================================================
DROP INDEX IF EXISTS "user_credentials_user_id_workspace_id_provider_name_key";
DROP INDEX IF EXISTS "user_credentials_scope_is_active_idx";
DROP INDEX IF EXISTS "user_credentials_workspace_id_scope_idx";
DROP INDEX IF EXISTS "user_credentials_user_id_scope_idx";
DROP INDEX IF EXISTS "user_credentials_workspace_id_idx";
DROP INDEX IF EXISTS "user_credentials_user_id_idx";
-- =============================================================================
-- DROP FOREIGN KEY CONSTRAINTS
-- =============================================================================
ALTER TABLE "user_credentials" DROP CONSTRAINT IF EXISTS "user_credentials_workspace_id_fkey";
ALTER TABLE "user_credentials" DROP CONSTRAINT IF EXISTS "user_credentials_user_id_fkey";
-- =============================================================================
-- DROP TABLE
-- =============================================================================
DROP TABLE IF EXISTS "user_credentials";
-- =============================================================================
-- DROP ENUMS
-- =============================================================================
-- NOTE: ENUM values cannot be easily removed from an existing enum type in PostgreSQL.
-- To fully reverse this migration, you would need to:
--
-- 1. Remove the 'CREDENTIAL' value from EntityType enum (if not used elsewhere):
-- ALTER TYPE "EntityType" RENAME TO "EntityType_old";
-- CREATE TYPE "EntityType" AS ENUM (...all values except CREDENTIAL...);
-- -- Then rebuild all dependent objects
--
-- 2. Remove credential-related actions from ActivityAction enum (if not used elsewhere):
-- ALTER TYPE "ActivityAction" RENAME TO "ActivityAction_old";
-- CREATE TYPE "ActivityAction" AS ENUM (...all values except CREDENTIAL_*...);
-- -- Then rebuild all dependent objects
--
-- 3. Drop the CredentialType and CredentialScope enums:
-- DROP TYPE IF EXISTS "CredentialType";
-- DROP TYPE IF EXISTS "CredentialScope";
--
-- Due to the complexity and risk of breaking existing data/code that references
-- these enum values, this migration does NOT automatically remove them.
-- If you need to clean up the enums, manually execute the steps above.
--
-- For development environments, you can safely drop and recreate the enums manually
-- using the SQL statements above.
@@ -1,184 +0,0 @@
-- User Credentials Storage with RLS Policies
-- This migration adds the user_credentials table for secure storage of user API keys,
-- OAuth tokens, and other credentials with encryption and RLS enforcement.
--
-- Related: #355 - Create UserCredential Prisma model with RLS policies
-- Design: docs/design/credential-security.md (Phase 3a)
-- =============================================================================
-- CREATE ENUMS
-- =============================================================================
-- CredentialType enum: Types of credentials that can be stored
CREATE TYPE "CredentialType" AS ENUM ('API_KEY', 'OAUTH_TOKEN', 'ACCESS_TOKEN', 'SECRET', 'PASSWORD', 'CUSTOM');
-- CredentialScope enum: Access scope for credentials
CREATE TYPE "CredentialScope" AS ENUM ('USER', 'WORKSPACE', 'SYSTEM');
-- =============================================================================
-- EXTEND EXISTING ENUMS
-- =============================================================================
-- Add CREDENTIAL to EntityType for activity logging
ALTER TYPE "EntityType" ADD VALUE 'CREDENTIAL';
-- Add credential-related actions to ActivityAction
ALTER TYPE "ActivityAction" ADD VALUE 'CREDENTIAL_CREATED';
ALTER TYPE "ActivityAction" ADD VALUE 'CREDENTIAL_ACCESSED';
ALTER TYPE "ActivityAction" ADD VALUE 'CREDENTIAL_ROTATED';
ALTER TYPE "ActivityAction" ADD VALUE 'CREDENTIAL_REVOKED';
-- =============================================================================
-- CREATE USER_CREDENTIALS TABLE
-- =============================================================================
CREATE TABLE "user_credentials" (
"id" UUID NOT NULL DEFAULT uuid_generate_v4(),
"user_id" UUID NOT NULL,
"workspace_id" UUID,
-- Identity
"name" VARCHAR(255) NOT NULL,
"provider" VARCHAR(100) NOT NULL,
"type" "CredentialType" NOT NULL,
"scope" "CredentialScope" NOT NULL DEFAULT 'USER',
-- Encrypted storage
"encrypted_value" TEXT NOT NULL,
"masked_value" VARCHAR(20),
-- Metadata
"description" TEXT,
"expires_at" TIMESTAMPTZ,
"last_used_at" TIMESTAMPTZ,
"metadata" JSONB NOT NULL DEFAULT '{}',
-- Status
"is_active" BOOLEAN NOT NULL DEFAULT true,
"rotated_at" TIMESTAMPTZ,
-- Audit
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT "user_credentials_pkey" PRIMARY KEY ("id")
);
-- =============================================================================
-- CREATE FOREIGN KEY CONSTRAINTS
-- =============================================================================
ALTER TABLE "user_credentials" ADD CONSTRAINT "user_credentials_user_id_fkey"
FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "user_credentials" ADD CONSTRAINT "user_credentials_workspace_id_fkey"
FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- =============================================================================
-- CREATE INDEXES
-- =============================================================================
-- Index for user lookups
CREATE INDEX "user_credentials_user_id_idx" ON "user_credentials"("user_id");
-- Index for workspace lookups
CREATE INDEX "user_credentials_workspace_id_idx" ON "user_credentials"("workspace_id");
-- Index for user + scope queries
CREATE INDEX "user_credentials_user_id_scope_idx" ON "user_credentials"("user_id", "scope");
-- Index for workspace + scope queries
CREATE INDEX "user_credentials_workspace_id_scope_idx" ON "user_credentials"("workspace_id", "scope");
-- Index for scope + active status queries
CREATE INDEX "user_credentials_scope_is_active_idx" ON "user_credentials"("scope", "is_active");
-- =============================================================================
-- CREATE UNIQUE CONSTRAINT
-- =============================================================================
-- Prevent duplicate credentials per user/workspace/provider/name
CREATE UNIQUE INDEX "user_credentials_user_id_workspace_id_provider_name_key"
ON "user_credentials"("user_id", "workspace_id", "provider", "name");
-- =============================================================================
-- ENABLE FORCE ROW LEVEL SECURITY
-- =============================================================================
-- FORCE means the table owner (mosaic) is also subject to RLS policies.
-- This prevents Prisma (connecting as owner) from bypassing policies.
ALTER TABLE user_credentials ENABLE ROW LEVEL SECURITY;
ALTER TABLE user_credentials FORCE ROW LEVEL SECURITY;
-- =============================================================================
-- RLS POLICIES
-- =============================================================================
-- Owner bypass policy: Allow access to all rows ONLY when no RLS context is set
-- This is required for:
-- 1. Prisma migrations that run without RLS context
-- 2. Database maintenance operations
-- When RLS context IS set (current_user_id() returns non-NULL), this policy does not apply
--
-- NOTE: If connecting as a PostgreSQL superuser (like the default 'mosaic' role),
-- RLS policies are bypassed entirely. For full RLS enforcement, the application
-- should connect as a non-superuser role. See docs/design/credential-security.md
CREATE POLICY user_credentials_owner_bypass ON user_credentials
FOR ALL
USING (current_user_id() IS NULL);
-- User access policy: USER-scoped credentials visible only to owner
-- Uses current_user_id() helper from migration 20260129221004_add_rls_policies
CREATE POLICY user_credentials_user_access ON user_credentials
FOR ALL
USING (
scope = 'USER' AND user_id = current_user_id()
);
-- Workspace admin access policy: WORKSPACE-scoped credentials visible to workspace admins
-- Uses is_workspace_admin() helper from migration 20260129221004_add_rls_policies
CREATE POLICY user_credentials_workspace_access ON user_credentials
FOR ALL
USING (
scope = 'WORKSPACE'
AND workspace_id IS NOT NULL
AND is_workspace_admin(workspace_id, current_user_id())
);
-- SYSTEM-scoped credentials are only accessible via owner bypass policy
-- (when current_user_id() IS NULL, which happens for admin operations)
-- =============================================================================
-- AUDIT TRIGGER
-- =============================================================================
-- Update updated_at timestamp on row changes
CREATE OR REPLACE FUNCTION update_user_credentials_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER user_credentials_updated_at
BEFORE UPDATE ON user_credentials
FOR EACH ROW
EXECUTE FUNCTION update_user_credentials_updated_at();
-- =============================================================================
-- NOTES
-- =============================================================================
-- This migration creates the foundation for secure credential storage.
-- The encrypted_value column stores ciphertext in one of two formats:
--
-- 1. OpenBao Transit format (preferred): vault:v1:base64data
-- 2. AES-256-GCM fallback format: iv:authTag:encrypted
--
-- The VaultService (issue #353) handles encryption/decryption with automatic
-- fallback to CryptoService when OpenBao is unavailable.
--
-- RLS enforcement ensures:
-- - USER scope: Only the credential owner can access
-- - WORKSPACE scope: Only workspace admins can access
-- - SYSTEM scope: Only accessible via admin/migration bypass
@@ -1,37 +0,0 @@
-- Encrypt existing plaintext Account tokens
-- This migration adds an encryption_version column and marks existing records for encryption
-- The actual encryption happens via Prisma middleware on first read/write
-- Add encryption_version column to track encryption state
-- NULL = not encrypted (legacy plaintext)
-- 'aes' = AES-256-GCM encrypted
-- 'vault' = OpenBao Transit encrypted (Phase 2)
ALTER TABLE accounts ADD COLUMN IF NOT EXISTS encryption_version VARCHAR(20);
-- Create index for efficient queries filtering by encryption status
-- This index is also declared in Prisma schema (@@index([encryptionVersion]))
-- Using CREATE INDEX IF NOT EXISTS for idempotency
CREATE INDEX IF NOT EXISTS "accounts_encryption_version_idx" ON accounts(encryption_version);
-- Verify index was created successfully by running:
-- SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'accounts' AND indexname = 'accounts_encryption_version_idx';
-- Update statistics for query planner
ANALYZE accounts;
-- Migration Note:
-- This migration does NOT encrypt data in-place to avoid downtime and data corruption risks.
-- Instead, the Prisma middleware (account-encryption.middleware.ts) handles encryption:
--
-- 1. On READ: Detects format (plaintext vs encrypted) and decrypts if needed
-- 2. On WRITE: Encrypts tokens and sets encryption_version = 'aes'
-- 3. Backward compatible: Plaintext tokens (encryption_version = NULL) are passed through unchanged
--
-- To actively encrypt existing tokens, run the companion script:
-- node scripts/encrypt-account-tokens.js
--
-- This approach ensures:
-- - Zero downtime migration
-- - No risk of corrupting tokens during bulk encryption
-- - Progressive encryption as tokens are accessed/refreshed
-- - Easy rollback (middleware is idempotent)
@@ -1,26 +0,0 @@
-- Encrypt LLM Provider API Keys Migration
--
-- This migration enables transparent encryption/decryption of LLM provider API keys
-- stored in the llm_provider_instances.config JSON field.
--
-- IMPORTANT: This is a data migration with no schema changes.
--
-- Strategy:
-- 1. Prisma middleware (llm-encryption.middleware.ts) handles encryption/decryption
-- 2. Middleware auto-detects encryption format:
-- - vault:v1:... = OpenBao Transit encrypted
-- - Otherwise = Legacy plaintext (backward compatible)
-- 3. New API keys are always encrypted on write
-- 4. Existing plaintext keys work until re-saved (lazy migration)
--
-- To actively encrypt all existing API keys NOW:
-- pnpm --filter @mosaic/api migrate:encrypt-llm-keys
--
-- This approach ensures:
-- - Zero downtime migration
-- - No schema changes required
-- - Backward compatible with plaintext keys
-- - Progressive encryption as keys are accessed/updated
-- - Easy rollback (middleware is idempotent)
--
-- Note: No SQL changes needed. This file exists for migration tracking only.
@@ -1,197 +0,0 @@
-- RecreateEnum: FormalityLevel was dropped in 20260129235248_add_link_storage_fields
CREATE TYPE "FormalityLevel" AS ENUM ('VERY_CASUAL', 'CASUAL', 'NEUTRAL', 'FORMAL', 'VERY_FORMAL');
-- RecreateTable: personalities was dropped in 20260129235248_add_link_storage_fields
-- Recreated with current schema (display_name, system_prompt, temperature, etc.)
CREATE TABLE "personalities" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"name" TEXT NOT NULL,
"display_name" TEXT NOT NULL,
"description" TEXT,
"system_prompt" TEXT NOT NULL,
"temperature" DOUBLE PRECISION,
"max_tokens" INTEGER,
"llm_provider_instance_id" UUID,
"is_default" BOOLEAN NOT NULL DEFAULT false,
"is_enabled" BOOLEAN NOT NULL DEFAULT true,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
CONSTRAINT "personalities_pkey" PRIMARY KEY ("id")
);
-- CreateIndex: personalities
CREATE UNIQUE INDEX "personalities_id_workspace_id_key" ON "personalities"("id", "workspace_id");
CREATE UNIQUE INDEX "personalities_workspace_id_name_key" ON "personalities"("workspace_id", "name");
CREATE INDEX "personalities_workspace_id_idx" ON "personalities"("workspace_id");
CREATE INDEX "personalities_workspace_id_is_default_idx" ON "personalities"("workspace_id", "is_default");
CREATE INDEX "personalities_workspace_id_is_enabled_idx" ON "personalities"("workspace_id", "is_enabled");
CREATE INDEX "personalities_llm_provider_instance_id_idx" ON "personalities"("llm_provider_instance_id");
-- AddForeignKey: personalities
ALTER TABLE "personalities" ADD CONSTRAINT "personalities_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "personalities" ADD CONSTRAINT "personalities_llm_provider_instance_id_fkey" FOREIGN KEY ("llm_provider_instance_id") REFERENCES "llm_provider_instances"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- CreateTable
CREATE TABLE "cron_schedules" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"expression" TEXT NOT NULL,
"command" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL DEFAULT true,
"last_run" TIMESTAMPTZ,
"next_run" TIMESTAMPTZ,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
CONSTRAINT "cron_schedules_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "workspace_llm_settings" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"default_llm_provider_id" UUID,
"default_personality_id" UUID,
"settings" JSONB DEFAULT '{}',
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
CONSTRAINT "workspace_llm_settings_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "quality_gates" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"type" TEXT NOT NULL,
"command" TEXT,
"expected_output" TEXT,
"is_regex" BOOLEAN NOT NULL DEFAULT false,
"required" BOOLEAN NOT NULL DEFAULT true,
"order" INTEGER NOT NULL DEFAULT 0,
"is_enabled" BOOLEAN NOT NULL DEFAULT true,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
CONSTRAINT "quality_gates_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "task_rejections" (
"id" UUID NOT NULL,
"task_id" TEXT NOT NULL,
"workspace_id" TEXT NOT NULL,
"agent_id" TEXT NOT NULL,
"attempt_count" INTEGER NOT NULL,
"failures" JSONB NOT NULL,
"original_task" TEXT NOT NULL,
"started_at" TIMESTAMPTZ NOT NULL,
"rejected_at" TIMESTAMPTZ NOT NULL,
"escalated" BOOLEAN NOT NULL DEFAULT false,
"manual_review" BOOLEAN NOT NULL DEFAULT false,
"resolved_at" TIMESTAMPTZ,
"resolution" TEXT,
CONSTRAINT "task_rejections_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "token_budgets" (
"id" UUID NOT NULL,
"task_id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"agent_id" TEXT NOT NULL,
"allocated_tokens" INTEGER NOT NULL,
"estimated_complexity" TEXT NOT NULL,
"input_tokens_used" INTEGER NOT NULL DEFAULT 0,
"output_tokens_used" INTEGER NOT NULL DEFAULT 0,
"total_tokens_used" INTEGER NOT NULL DEFAULT 0,
"estimated_cost" DECIMAL(10,6),
"started_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"last_updated_at" TIMESTAMPTZ NOT NULL,
"completed_at" TIMESTAMPTZ,
"budget_utilization" DOUBLE PRECISION,
"suspicious_pattern" BOOLEAN NOT NULL DEFAULT false,
"suspicious_reason" TEXT,
CONSTRAINT "token_budgets_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "llm_usage_logs" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"user_id" UUID NOT NULL,
"provider" VARCHAR(50) NOT NULL,
"model" VARCHAR(100) NOT NULL,
"provider_instance_id" UUID,
"prompt_tokens" INTEGER NOT NULL DEFAULT 0,
"completion_tokens" INTEGER NOT NULL DEFAULT 0,
"total_tokens" INTEGER NOT NULL DEFAULT 0,
"cost_cents" DOUBLE PRECISION,
"task_type" VARCHAR(50),
"conversation_id" UUID,
"duration_ms" INTEGER,
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "llm_usage_logs_pkey" PRIMARY KEY ("id")
);
-- CreateIndex: cron_schedules
CREATE INDEX "cron_schedules_workspace_id_idx" ON "cron_schedules"("workspace_id");
CREATE INDEX "cron_schedules_workspace_id_enabled_idx" ON "cron_schedules"("workspace_id", "enabled");
CREATE INDEX "cron_schedules_next_run_idx" ON "cron_schedules"("next_run");
-- CreateIndex: workspace_llm_settings
CREATE UNIQUE INDEX "workspace_llm_settings_workspace_id_key" ON "workspace_llm_settings"("workspace_id");
CREATE INDEX "workspace_llm_settings_workspace_id_idx" ON "workspace_llm_settings"("workspace_id");
CREATE INDEX "workspace_llm_settings_default_llm_provider_id_idx" ON "workspace_llm_settings"("default_llm_provider_id");
CREATE INDEX "workspace_llm_settings_default_personality_id_idx" ON "workspace_llm_settings"("default_personality_id");
-- CreateIndex: quality_gates
CREATE UNIQUE INDEX "quality_gates_workspace_id_name_key" ON "quality_gates"("workspace_id", "name");
CREATE INDEX "quality_gates_workspace_id_idx" ON "quality_gates"("workspace_id");
CREATE INDEX "quality_gates_workspace_id_is_enabled_idx" ON "quality_gates"("workspace_id", "is_enabled");
-- CreateIndex: task_rejections
CREATE INDEX "task_rejections_task_id_idx" ON "task_rejections"("task_id");
CREATE INDEX "task_rejections_workspace_id_idx" ON "task_rejections"("workspace_id");
CREATE INDEX "task_rejections_agent_id_idx" ON "task_rejections"("agent_id");
CREATE INDEX "task_rejections_escalated_idx" ON "task_rejections"("escalated");
CREATE INDEX "task_rejections_manual_review_idx" ON "task_rejections"("manual_review");
-- CreateIndex: token_budgets
CREATE UNIQUE INDEX "token_budgets_task_id_key" ON "token_budgets"("task_id");
CREATE INDEX "token_budgets_task_id_idx" ON "token_budgets"("task_id");
CREATE INDEX "token_budgets_workspace_id_idx" ON "token_budgets"("workspace_id");
CREATE INDEX "token_budgets_suspicious_pattern_idx" ON "token_budgets"("suspicious_pattern");
-- CreateIndex: llm_usage_logs
CREATE INDEX "llm_usage_logs_workspace_id_idx" ON "llm_usage_logs"("workspace_id");
CREATE INDEX "llm_usage_logs_workspace_id_created_at_idx" ON "llm_usage_logs"("workspace_id", "created_at");
CREATE INDEX "llm_usage_logs_user_id_idx" ON "llm_usage_logs"("user_id");
CREATE INDEX "llm_usage_logs_provider_idx" ON "llm_usage_logs"("provider");
CREATE INDEX "llm_usage_logs_model_idx" ON "llm_usage_logs"("model");
CREATE INDEX "llm_usage_logs_provider_instance_id_idx" ON "llm_usage_logs"("provider_instance_id");
CREATE INDEX "llm_usage_logs_task_type_idx" ON "llm_usage_logs"("task_type");
CREATE INDEX "llm_usage_logs_conversation_id_idx" ON "llm_usage_logs"("conversation_id");
-- AddForeignKey: cron_schedules
ALTER TABLE "cron_schedules" ADD CONSTRAINT "cron_schedules_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey: workspace_llm_settings
ALTER TABLE "workspace_llm_settings" ADD CONSTRAINT "workspace_llm_settings_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "workspace_llm_settings" ADD CONSTRAINT "workspace_llm_settings_default_llm_provider_id_fkey" FOREIGN KEY ("default_llm_provider_id") REFERENCES "llm_provider_instances"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "workspace_llm_settings" ADD CONSTRAINT "workspace_llm_settings_default_personality_id_fkey" FOREIGN KEY ("default_personality_id") REFERENCES "personalities"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey: quality_gates
ALTER TABLE "quality_gates" ADD CONSTRAINT "quality_gates_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey: llm_usage_logs
ALTER TABLE "llm_usage_logs" ADD CONSTRAINT "llm_usage_logs_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "llm_usage_logs" ADD CONSTRAINT "llm_usage_logs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "llm_usage_logs" ADD CONSTRAINT "llm_usage_logs_provider_instance_id_fkey" FOREIGN KEY ("provider_instance_id") REFERENCES "llm_provider_instances"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -1,2 +0,0 @@
-- AlterTable
ALTER TABLE "workspaces" ADD COLUMN "matrix_room_id" TEXT;
@@ -1,49 +0,0 @@
-- Fix schema drift: tables, indexes, and constraints defined in schema.prisma
-- but never created (or dropped and never recreated) by prior migrations.
-- ============================================
-- CreateTable: instances (Federation module)
-- Never created in any prior migration
-- ============================================
CREATE TABLE "instances" (
"id" UUID NOT NULL,
"instance_id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"url" TEXT NOT NULL,
"public_key" TEXT NOT NULL,
"private_key" TEXT NOT NULL,
"capabilities" JSONB NOT NULL DEFAULT '{}',
"metadata" JSONB NOT NULL DEFAULT '{}',
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ NOT NULL,
CONSTRAINT "instances_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "instances_instance_id_key" ON "instances"("instance_id");
-- ============================================
-- Recreate dropped unique index on knowledge_links
-- Created in 20260129220645_add_knowledge_module, dropped in
-- 20260129235248_add_link_storage_fields, never recreated.
-- ============================================
CREATE UNIQUE INDEX "knowledge_links_source_id_target_id_key" ON "knowledge_links"("source_id", "target_id");
-- ============================================
-- Missing @@unique([id, workspaceId]) composite indexes
-- Defined in schema.prisma but never created in migrations.
-- (agent_tasks and runner_jobs already have these.)
-- ============================================
CREATE UNIQUE INDEX "tasks_id_workspace_id_key" ON "tasks"("id", "workspace_id");
CREATE UNIQUE INDEX "events_id_workspace_id_key" ON "events"("id", "workspace_id");
CREATE UNIQUE INDEX "projects_id_workspace_id_key" ON "projects"("id", "workspace_id");
CREATE UNIQUE INDEX "activity_logs_id_workspace_id_key" ON "activity_logs"("id", "workspace_id");
CREATE UNIQUE INDEX "domains_id_workspace_id_key" ON "domains"("id", "workspace_id");
CREATE UNIQUE INDEX "ideas_id_workspace_id_key" ON "ideas"("id", "workspace_id");
CREATE UNIQUE INDEX "user_layouts_id_workspace_id_key" ON "user_layouts"("id", "workspace_id");
-- ============================================
-- Missing index on agent_tasks.agent_type
-- Defined as @@index([agentType]) in schema.prisma
-- ============================================
CREATE INDEX "agent_tasks_agent_type_idx" ON "agent_tasks"("agent_type");
@@ -1,23 +0,0 @@
-- CreateEnum
CREATE TYPE "TerminalSessionStatus" AS ENUM ('ACTIVE', 'CLOSED');
-- CreateTable
CREATE TABLE "terminal_sessions" (
"id" UUID NOT NULL,
"workspace_id" UUID NOT NULL,
"name" TEXT NOT NULL DEFAULT 'Terminal',
"status" "TerminalSessionStatus" NOT NULL DEFAULT 'ACTIVE',
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
"closed_at" TIMESTAMPTZ,
CONSTRAINT "terminal_sessions_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "terminal_sessions_workspace_id_idx" ON "terminal_sessions"("workspace_id");
-- CreateIndex
CREATE INDEX "terminal_sessions_workspace_id_status_idx" ON "terminal_sessions"("workspace_id", "status");
-- AddForeignKey
ALTER TABLE "terminal_sessions" ADD CONSTRAINT "terminal_sessions_workspace_id_fkey" FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -1,3 +0,0 @@
-- AlterTable: add tone and formality_level columns to personalities
ALTER TABLE "personalities" ADD COLUMN "tone" TEXT NOT NULL DEFAULT 'neutral';
ALTER TABLE "personalities" ADD COLUMN "formality_level" "FormalityLevel" NOT NULL DEFAULT 'NEUTRAL';
@@ -1,3 +0,0 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"
File diff suppressed because it is too large Load Diff
-599
View File
@@ -1,599 +0,0 @@
import {
PrismaClient,
TaskStatus,
TaskPriority,
ProjectStatus,
WorkspaceMemberRole,
EntryStatus,
Visibility,
} from "@prisma/client";
const prisma = new PrismaClient();
async function main() {
console.log("Seeding database...");
// IMPORTANT: This seed script should not be run concurrently
// If running in CI/CD, ensure serialization of seed operations
// to prevent race conditions and data corruption
// Create test user
const user = await prisma.user.upsert({
where: { email: "[email protected]" },
update: {},
create: {
email: "[email protected]",
name: "Development User",
preferences: {
theme: "system",
notifications: true,
},
},
});
console.log(`Created user: ${user.email}`);
// Create workspace
const workspace = await prisma.workspace.upsert({
where: { id: "00000000-0000-0000-0000-000000000001" },
update: {},
create: {
id: "00000000-0000-0000-0000-000000000001",
name: "Development Workspace",
ownerId: user.id,
settings: {
timezone: "America/New_York",
},
},
});
console.log(`Created workspace: ${workspace.name}`);
// Add user as workspace owner
await prisma.workspaceMember.upsert({
where: {
workspaceId_userId: {
workspaceId: workspace.id,
userId: user.id,
},
},
update: {},
create: {
workspaceId: workspace.id,
userId: user.id,
role: WorkspaceMemberRole.OWNER,
},
});
// ============================================
// WIDGET DEFINITIONS (global, not workspace-scoped)
// ============================================
const widgetDefs = [
{
name: "TasksWidget",
displayName: "Tasks",
description: "View and manage your tasks",
component: "TasksWidget",
defaultWidth: 2,
defaultHeight: 2,
minWidth: 1,
minHeight: 2,
maxWidth: 4,
maxHeight: null,
configSchema: {},
},
{
name: "CalendarWidget",
displayName: "Calendar",
description: "View upcoming events and schedule",
component: "CalendarWidget",
defaultWidth: 2,
defaultHeight: 2,
minWidth: 2,
minHeight: 2,
maxWidth: 4,
maxHeight: null,
configSchema: {},
},
{
name: "QuickCaptureWidget",
displayName: "Quick Capture",
description: "Quickly capture notes and tasks",
component: "QuickCaptureWidget",
defaultWidth: 2,
defaultHeight: 1,
minWidth: 2,
minHeight: 1,
maxWidth: 4,
maxHeight: 2,
configSchema: {},
},
{
name: "AgentStatusWidget",
displayName: "Agent Status",
description: "Monitor agent activity and status",
component: "AgentStatusWidget",
defaultWidth: 2,
defaultHeight: 2,
minWidth: 1,
minHeight: 2,
maxWidth: 3,
maxHeight: null,
configSchema: {},
},
{
name: "ActiveProjectsWidget",
displayName: "Active Projects & Agent Chains",
description: "View active projects and running agent sessions",
component: "ActiveProjectsWidget",
defaultWidth: 2,
defaultHeight: 3,
minWidth: 2,
minHeight: 2,
maxWidth: 4,
maxHeight: null,
configSchema: {},
},
{
name: "TaskProgressWidget",
displayName: "Task Progress",
description: "Live progress of orchestrator agent tasks",
component: "TaskProgressWidget",
defaultWidth: 2,
defaultHeight: 2,
minWidth: 1,
minHeight: 2,
maxWidth: 3,
maxHeight: null,
configSchema: {},
},
{
name: "OrchestratorEventsWidget",
displayName: "Orchestrator Events",
description: "Recent orchestration events with stream/Matrix visibility",
component: "OrchestratorEventsWidget",
defaultWidth: 2,
defaultHeight: 2,
minWidth: 1,
minHeight: 2,
maxWidth: 4,
maxHeight: null,
configSchema: {},
},
];
for (const wd of widgetDefs) {
await prisma.widgetDefinition.upsert({
where: { name: wd.name },
update: {
displayName: wd.displayName,
description: wd.description,
component: wd.component,
defaultWidth: wd.defaultWidth,
defaultHeight: wd.defaultHeight,
minWidth: wd.minWidth,
minHeight: wd.minHeight,
maxWidth: wd.maxWidth,
maxHeight: wd.maxHeight,
configSchema: wd.configSchema,
},
create: {
name: wd.name,
displayName: wd.displayName,
description: wd.description,
component: wd.component,
defaultWidth: wd.defaultWidth,
defaultHeight: wd.defaultHeight,
minWidth: wd.minWidth,
minHeight: wd.minHeight,
maxWidth: wd.maxWidth,
maxHeight: wd.maxHeight,
configSchema: wd.configSchema,
},
});
}
console.log(`Seeded ${widgetDefs.length} widget definitions`);
// Use transaction for atomic seed data reset and creation
await prisma.$transaction(async (tx) => {
// Delete existing seed data for idempotency (avoids duplicates on re-run)
await tx.task.deleteMany({ where: { workspaceId: workspace.id } });
await tx.event.deleteMany({ where: { workspaceId: workspace.id } });
await tx.project.deleteMany({ where: { workspaceId: workspace.id } });
// Create sample project
const project = await tx.project.create({
data: {
workspaceId: workspace.id,
name: "Sample Project",
description: "A sample project for development",
status: ProjectStatus.ACTIVE,
creatorId: user.id,
color: "#3B82F6",
},
});
console.log(`Created project: ${project.name}`);
// Create sample tasks
const tasks = [
{
title: "Set up development environment",
status: TaskStatus.COMPLETED,
priority: TaskPriority.HIGH,
},
{
title: "Review project requirements",
status: TaskStatus.IN_PROGRESS,
priority: TaskPriority.MEDIUM,
},
{
title: "Design database schema",
status: TaskStatus.COMPLETED,
priority: TaskPriority.HIGH,
},
{
title: "Implement NestJS integration",
status: TaskStatus.COMPLETED,
priority: TaskPriority.HIGH,
},
{
title: "Create seed data",
status: TaskStatus.IN_PROGRESS,
priority: TaskPriority.MEDIUM,
},
];
// Use createMany for batch insertion (better performance)
await tx.task.createMany({
data: tasks.map((taskData) => ({
workspaceId: workspace.id,
title: taskData.title,
status: taskData.status,
priority: taskData.priority,
creatorId: user.id,
projectId: project.id,
})),
});
console.log(`Created ${tasks.length} sample tasks`);
// Create sample event
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
tomorrow.setHours(10, 0, 0, 0);
await tx.event.create({
data: {
workspaceId: workspace.id,
title: "Morning standup",
description: "Daily team sync",
startTime: tomorrow,
endTime: new Date(tomorrow.getTime() + 30 * 60000), // 30 minutes later
creatorId: user.id,
projectId: project.id,
},
});
console.log("Created sample event");
// ============================================
// KNOWLEDGE MODULE SEED DATA
// ============================================
// Delete existing knowledge data
await tx.knowledgeEmbedding.deleteMany({ where: { entry: { workspaceId: workspace.id } } });
await tx.knowledgeEntryTag.deleteMany({ where: { entry: { workspaceId: workspace.id } } });
await tx.knowledgeLink.deleteMany({ where: { source: { workspaceId: workspace.id } } });
await tx.knowledgeEntryVersion.deleteMany({ where: { entry: { workspaceId: workspace.id } } });
await tx.knowledgeEntry.deleteMany({ where: { workspaceId: workspace.id } });
await tx.knowledgeTag.deleteMany({ where: { workspaceId: workspace.id } });
// Create knowledge tags
const tags = await Promise.all([
tx.knowledgeTag.create({
data: {
workspaceId: workspace.id,
name: "Architecture",
slug: "architecture",
color: "#3B82F6",
description: "System architecture and design decisions",
},
}),
tx.knowledgeTag.create({
data: {
workspaceId: workspace.id,
name: "Development",
slug: "development",
color: "#10B981",
description: "Development practices and guidelines",
},
}),
tx.knowledgeTag.create({
data: {
workspaceId: workspace.id,
name: "Getting Started",
slug: "getting-started",
color: "#F59E0B",
description: "Onboarding and setup guides",
},
}),
]);
console.log(`Created ${tags.length} knowledge tags`);
// Create knowledge entries
const entries = [
{
slug: "welcome",
title: "Welcome to Mosaic Stack Knowledge Base",
content: `# Welcome to Mosaic Stack
This is the knowledge base for the Mosaic Stack project. Here you'll find:
- **[[architecture-overview]]** - High-level system architecture
- **[[development-setup]]** - Getting started with development
- **[[database-schema]]** - Database design and conventions
## About This Knowledge Base
The Knowledge Module provides:
- Wiki-style linking between entries
- Full-text and semantic search
- Version history and change tracking
- Tag-based organization
Start exploring by following the links above!`,
summary: "Introduction to the Mosaic Stack knowledge base and navigation guide",
status: EntryStatus.PUBLISHED,
visibility: Visibility.WORKSPACE,
tags: ["getting-started"],
},
{
slug: "architecture-overview",
title: "Architecture Overview",
content: `# Architecture Overview
The Mosaic Stack is built on a modern, scalable architecture:
## Stack Components
- **Frontend**: Next.js 15+ with React 19
- **Backend**: NestJS with Prisma ORM
- **Database**: PostgreSQL 17 with pgvector
- **Cache**: Valkey (Redis fork)
## Key Modules
1. **Task Management** - See [[development-setup]] for local setup
2. **Event Calendar** - Integrated scheduling
3. **Knowledge Base** - This module! See [[database-schema]]
4. **Agent Orchestration** - AI agent coordination
The database schema is documented in [[database-schema]].`,
summary: "High-level overview of Mosaic Stack architecture and components",
status: EntryStatus.PUBLISHED,
visibility: Visibility.WORKSPACE,
tags: ["architecture"],
},
{
slug: "development-setup",
title: "Development Setup Guide",
content: `# Development Setup Guide
## Prerequisites
- Node.js 22+
- PostgreSQL 17
- pnpm 9+
## Quick Start
\`\`\`bash
# Clone the repository
git clone https://git.mosaicstack.dev/mosaic/stack.git
# Install dependencies
pnpm install
# Set up database
cd apps/api
pnpm prisma migrate dev
pnpm prisma:seed
# Start development servers
pnpm dev
\`\`\`
## Architecture
Before diving in, review the [[architecture-overview]] to understand the system design.
## Database
The database schema is documented in [[database-schema]]. All models use Prisma ORM.
## Next Steps
1. Read the [[architecture-overview]]
2. Explore the codebase
3. Check out the [[database-schema]] documentation`,
summary: "Step-by-step guide to setting up the Mosaic Stack development environment",
status: EntryStatus.PUBLISHED,
visibility: Visibility.WORKSPACE,
tags: ["development", "getting-started"],
},
{
slug: "database-schema",
title: "Database Schema Documentation",
content: `# Database Schema Documentation
## Overview
Mosaic Stack uses PostgreSQL 17 with Prisma ORM. See [[architecture-overview]] for context.
## Key Conventions
- All IDs are UUIDs
- Timestamps use \`@db.Timestamptz\` (timezone-aware)
- Soft deletes via status fields (e.g., ARCHIVED)
- Relations enforce cascade deletes for data integrity
## Core Models
### Task Management
- \`Task\` - Individual work items
- \`Project\` - Task containers
- \`Domain\` - High-level categorization
### Knowledge Module
- \`KnowledgeEntry\` - Wiki pages
- \`KnowledgeLink\` - Page connections
- \`KnowledgeTag\` - Categorization
- \`KnowledgeEmbedding\` - Semantic search (pgvector)
### Agent System
- \`Agent\` - AI agent instances
- \`AgentSession\` - Conversation sessions
## Migrations
Migrations are managed via Prisma:
\`\`\`bash
# Create migration
pnpm prisma migrate dev --name my_migration
# Apply in production
pnpm prisma migrate deploy
\`\`\`
For setup instructions, see [[development-setup]].`,
summary:
"Comprehensive documentation of the Mosaic Stack database schema and Prisma conventions",
status: EntryStatus.PUBLISHED,
visibility: Visibility.WORKSPACE,
tags: ["architecture", "development"],
},
{
slug: "future-ideas",
title: "Future Ideas and Roadmap",
content: `# Future Ideas and Roadmap
## Planned Features
- Real-time collaboration (CRDT)
- Advanced graph visualizations
- AI-powered summarization
- Mobile app
## Research Areas
- Vector search optimization
- Knowledge graph algorithms
- Agent memory systems
This is a draft document. See [[architecture-overview]] for current state.`,
summary: "Brainstorming document for future features and research directions",
status: EntryStatus.DRAFT,
visibility: Visibility.PRIVATE,
tags: [],
},
];
// Create entries and track them for linking
const createdEntries = new Map<string, any>();
for (const entryData of entries) {
const entry = await tx.knowledgeEntry.create({
data: {
workspaceId: workspace.id,
slug: entryData.slug,
title: entryData.title,
content: entryData.content,
summary: entryData.summary,
status: entryData.status,
visibility: entryData.visibility,
createdBy: user.id,
updatedBy: user.id,
},
});
createdEntries.set(entryData.slug, entry);
// Create initial version
await tx.knowledgeEntryVersion.create({
data: {
entryId: entry.id,
version: 1,
title: entry.title,
content: entry.content,
summary: entry.summary,
createdBy: user.id,
changeNote: "Initial version",
},
});
// Add tags
for (const tagSlug of entryData.tags) {
const tag = tags.find((t) => t.slug === tagSlug);
if (tag) {
await tx.knowledgeEntryTag.create({
data: {
entryId: entry.id,
tagId: tag.id,
},
});
}
}
}
console.log(`Created ${entries.length} knowledge entries`);
// Create wiki-links between entries
const links = [
{ source: "welcome", target: "architecture-overview", text: "architecture-overview" },
{ source: "welcome", target: "development-setup", text: "development-setup" },
{ source: "welcome", target: "database-schema", text: "database-schema" },
{ source: "architecture-overview", target: "development-setup", text: "development-setup" },
{ source: "architecture-overview", target: "database-schema", text: "database-schema" },
{
source: "development-setup",
target: "architecture-overview",
text: "architecture-overview",
},
{ source: "development-setup", target: "database-schema", text: "database-schema" },
{ source: "database-schema", target: "architecture-overview", text: "architecture-overview" },
{ source: "database-schema", target: "development-setup", text: "development-setup" },
{ source: "future-ideas", target: "architecture-overview", text: "architecture-overview" },
];
for (const link of links) {
const sourceEntry = createdEntries.get(link.source);
const targetEntry = createdEntries.get(link.target);
if (sourceEntry && targetEntry) {
await tx.knowledgeLink.create({
data: {
sourceId: sourceEntry.id,
targetId: targetEntry.id,
linkText: link.text,
},
});
}
}
console.log(`Created ${links.length} knowledge links`);
});
console.log("Seeding completed successfully!");
}
main()
.catch((e) => {
console.error("Error seeding database:", e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
-166
View File
@@ -1,166 +0,0 @@
/**
* Data Migration: Encrypt LLM Provider API Keys
*
* Encrypts all plaintext API keys in llm_provider_instances.config using OpenBao Transit.
* This script processes records in batches and runs in a transaction for safety.
*
* Usage:
* pnpm --filter @mosaic/api migrate:encrypt-llm-keys
*
* Environment Variables:
* DATABASE_URL - PostgreSQL connection string
* OPENBAO_ADDR - OpenBao server address (default: http://openbao:8200)
* APPROLE_CREDENTIALS_PATH - Path to AppRole credentials file
*/
import { PrismaClient } from "@prisma/client";
import { VaultService } from "../src/vault/vault.service";
import { TransitKey } from "../src/vault/vault.constants";
import { Logger } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
interface LlmProviderConfig {
apiKey?: string;
[key: string]: unknown;
}
interface LlmProviderInstance {
id: string;
config: LlmProviderConfig;
providerType: string;
displayName: string;
}
/**
* Check if a value is already encrypted
*/
function isEncrypted(value: string): boolean {
if (!value || typeof value !== "string") {
return false;
}
// Vault format: vault:v1:...
if (value.startsWith("vault:v1:")) {
return true;
}
// AES format: iv:authTag:encrypted (3 colon-separated hex parts)
const parts = value.split(":");
if (parts.length === 3 && parts.every((part) => /^[0-9a-f]+$/i.test(part))) {
return true;
}
return false;
}
/**
* Main migration function
*/
async function main(): Promise<void> {
const logger = new Logger("EncryptLlmKeys");
const prisma = new PrismaClient();
try {
logger.log("Starting LLM API key encryption migration...");
// Initialize VaultService
const configService = new ConfigService();
const vaultService = new VaultService(configService);
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
await vaultService.onModuleInit();
logger.log("VaultService initialized successfully");
// Fetch all LLM provider instances
const instances = await prisma.llmProviderInstance.findMany({
select: {
id: true,
config: true,
providerType: true,
displayName: true,
},
});
logger.log(`Found ${String(instances.length)} LLM provider instances`);
let encryptedCount = 0;
let skippedCount = 0;
let errorCount = 0;
// Process each instance
for (const instance of instances as LlmProviderInstance[]) {
try {
const config = instance.config;
// Skip if no apiKey field
if (!config.apiKey || typeof config.apiKey !== "string") {
logger.debug(`Skipping ${instance.displayName} (${instance.id}): No API key`);
skippedCount++;
continue;
}
// Skip if already encrypted
if (isEncrypted(config.apiKey)) {
logger.debug(`Skipping ${instance.displayName} (${instance.id}): Already encrypted`);
skippedCount++;
continue;
}
// Encrypt the API key
logger.log(`Encrypting ${instance.displayName} (${instance.providerType})...`);
const encryptedApiKey = await vaultService.encrypt(config.apiKey, TransitKey.LLM_CONFIG);
// Update the instance with encrypted key
await prisma.llmProviderInstance.update({
where: { id: instance.id },
data: {
config: {
...config,
apiKey: encryptedApiKey,
},
},
});
encryptedCount++;
logger.log(`✓ Encrypted ${instance.displayName} (${instance.id})`);
} catch (error: unknown) {
errorCount++;
const errorMsg = error instanceof Error ? error.message : String(error);
logger.error(`✗ Failed to encrypt ${instance.displayName} (${instance.id}): ${errorMsg}`);
}
}
// Summary
logger.log("\n=== Migration Summary ===");
logger.log(`Total instances: ${String(instances.length)}`);
logger.log(`Encrypted: ${String(encryptedCount)}`);
logger.log(`Skipped: ${String(skippedCount)}`);
logger.log(`Errors: ${String(errorCount)}`);
if (errorCount > 0) {
logger.warn("\n⚠️ Some API keys failed to encrypt. Please review the errors above.");
process.exit(1);
} else if (encryptedCount === 0) {
logger.log("\n✓ All API keys are already encrypted or no keys found.");
} else {
logger.log("\n✓ Migration completed successfully!");
}
} catch (error: unknown) {
const errorMsg = error instanceof Error ? error.message : String(error);
logger.error(`Migration failed: ${errorMsg}`);
throw error;
} finally {
await prisma.$disconnect();
}
}
// Run migration
main()
.then(() => {
process.exit(0);
})
.catch((error: unknown) => {
console.error(error);
process.exit(1);
});
@@ -1,314 +0,0 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import { ActivityController } from "./activity.controller";
import { ActivityService } from "./activity.service";
import { ActivityAction, EntityType } from "@prisma/client";
import type { QueryActivityLogDto } from "./dto";
describe("ActivityController", () => {
let controller: ActivityController;
let service: ActivityService;
const mockActivityService = {
findAll: vi.fn(),
findOne: vi.fn(),
getAuditTrail: vi.fn(),
};
const mockWorkspaceId = "workspace-123";
beforeEach(() => {
service = mockActivityService as any;
controller = new ActivityController(service);
vi.clearAllMocks();
});
describe("findAll", () => {
const mockPaginatedResult = {
data: [
{
id: "activity-1",
workspaceId: "workspace-123",
userId: "user-123",
action: ActivityAction.CREATED,
entityType: EntityType.TASK,
entityId: "task-123",
details: {},
createdAt: new Date("2024-01-01"),
user: {
id: "user-123",
name: "Test User",
email: "[email protected]",
},
},
],
meta: {
total: 1,
page: 1,
limit: 50,
totalPages: 1,
},
};
it("should return paginated activity logs using authenticated user's workspaceId", async () => {
const query: QueryActivityLogDto = {
workspaceId: "workspace-123",
page: 1,
limit: 50,
};
mockActivityService.findAll.mockResolvedValue(mockPaginatedResult);
const result = await controller.findAll(query, mockWorkspaceId);
expect(result).toEqual(mockPaginatedResult);
expect(mockActivityService.findAll).toHaveBeenCalledWith({
...query,
workspaceId: "workspace-123",
});
});
it("should handle query with filters", async () => {
const query: QueryActivityLogDto = {
workspaceId: "workspace-123",
userId: "user-123",
action: ActivityAction.CREATED,
entityType: EntityType.TASK,
page: 1,
limit: 10,
};
mockActivityService.findAll.mockResolvedValue(mockPaginatedResult);
await controller.findAll(query, mockWorkspaceId);
expect(mockActivityService.findAll).toHaveBeenCalledWith({
...query,
workspaceId: "workspace-123",
});
});
it("should handle query with date range", async () => {
const startDate = new Date("2024-01-01");
const endDate = new Date("2024-01-31");
const query: QueryActivityLogDto = {
workspaceId: "workspace-123",
startDate,
endDate,
page: 1,
limit: 50,
};
mockActivityService.findAll.mockResolvedValue(mockPaginatedResult);
await controller.findAll(query, mockWorkspaceId);
expect(mockActivityService.findAll).toHaveBeenCalledWith({
...query,
workspaceId: "workspace-123",
});
});
it("should use user's workspaceId even if query provides different one", async () => {
const query: QueryActivityLogDto = {
workspaceId: "different-workspace",
page: 1,
limit: 50,
};
mockActivityService.findAll.mockResolvedValue(mockPaginatedResult);
await controller.findAll(query, mockWorkspaceId);
// Should use authenticated user's workspaceId, not query's
expect(mockActivityService.findAll).toHaveBeenCalledWith({
...query,
workspaceId: "workspace-123",
});
});
});
describe("findOne", () => {
const mockActivity = {
id: "activity-123",
workspaceId: "workspace-123",
userId: "user-123",
action: ActivityAction.CREATED,
entityType: EntityType.TASK,
entityId: "task-123",
details: {},
createdAt: new Date(),
user: {
id: "user-123",
name: "Test User",
email: "[email protected]",
},
};
it("should return a single activity log using authenticated user's workspaceId", async () => {
mockActivityService.findOne.mockResolvedValue(mockActivity);
const result = await controller.findOne("activity-123", mockWorkspaceId);
expect(result).toEqual(mockActivity);
expect(mockActivityService.findOne).toHaveBeenCalledWith("activity-123", "workspace-123");
});
it("should return null if activity not found", async () => {
mockActivityService.findOne.mockResolvedValue(null);
const result = await controller.findOne("nonexistent", mockWorkspaceId);
expect(result).toBeNull();
});
it("should return null if workspaceId is missing (service handles gracefully)", async () => {
mockActivityService.findOne.mockResolvedValue(null);
const result = await controller.findOne("activity-123", undefined as any);
expect(result).toBeNull();
expect(mockActivityService.findOne).toHaveBeenCalledWith("activity-123", undefined);
});
});
describe("getAuditTrail", () => {
const mockAuditTrail = [
{
id: "activity-1",
workspaceId: "workspace-123",
userId: "user-123",
action: ActivityAction.CREATED,
entityType: EntityType.TASK,
entityId: "task-123",
details: { title: "New Task" },
createdAt: new Date("2024-01-01"),
user: {
id: "user-123",
name: "Test User",
email: "[email protected]",
},
},
{
id: "activity-2",
workspaceId: "workspace-123",
userId: "user-456",
action: ActivityAction.UPDATED,
entityType: EntityType.TASK,
entityId: "task-123",
details: { title: "Updated Task" },
createdAt: new Date("2024-01-02"),
user: {
id: "user-456",
name: "Another User",
email: "[email protected]",
},
},
];
it("should return audit trail for a task using authenticated user's workspaceId", async () => {
mockActivityService.getAuditTrail.mockResolvedValue(mockAuditTrail);
const result = await controller.getAuditTrail(EntityType.TASK, "task-123", mockWorkspaceId);
expect(result).toEqual(mockAuditTrail);
expect(mockActivityService.getAuditTrail).toHaveBeenCalledWith(
"workspace-123",
EntityType.TASK,
"task-123"
);
});
it("should return audit trail for an event", async () => {
const eventAuditTrail = [
{
id: "activity-3",
workspaceId: "workspace-123",
userId: "user-123",
action: ActivityAction.CREATED,
entityType: EntityType.EVENT,
entityId: "event-123",
details: {},
createdAt: new Date(),
user: {
id: "user-123",
name: "Test User",
email: "[email protected]",
},
},
];
mockActivityService.getAuditTrail.mockResolvedValue(eventAuditTrail);
const result = await controller.getAuditTrail(EntityType.EVENT, "event-123", mockWorkspaceId);
expect(result).toEqual(eventAuditTrail);
expect(mockActivityService.getAuditTrail).toHaveBeenCalledWith(
"workspace-123",
EntityType.EVENT,
"event-123"
);
});
it("should return audit trail for a project", async () => {
const projectAuditTrail = [
{
id: "activity-4",
workspaceId: "workspace-123",
userId: "user-123",
action: ActivityAction.CREATED,
entityType: EntityType.PROJECT,
entityId: "project-123",
details: {},
createdAt: new Date(),
user: {
id: "user-123",
name: "Test User",
email: "[email protected]",
},
},
];
mockActivityService.getAuditTrail.mockResolvedValue(projectAuditTrail);
const result = await controller.getAuditTrail(
EntityType.PROJECT,
"project-123",
mockWorkspaceId
);
expect(result).toEqual(projectAuditTrail);
expect(mockActivityService.getAuditTrail).toHaveBeenCalledWith(
"workspace-123",
EntityType.PROJECT,
"project-123"
);
});
it("should return empty array if no audit trail found", async () => {
mockActivityService.getAuditTrail.mockResolvedValue([]);
const result = await controller.getAuditTrail(
EntityType.WORKSPACE,
"workspace-999",
mockWorkspaceId
);
expect(result).toEqual([]);
});
it("should return empty array if workspaceId is missing (service handles gracefully)", async () => {
mockActivityService.getAuditTrail.mockResolvedValue([]);
const result = await controller.getAuditTrail(EntityType.TASK, "task-123", undefined as any);
expect(result).toEqual([]);
expect(mockActivityService.getAuditTrail).toHaveBeenCalledWith(
undefined,
EntityType.TASK,
"task-123"
);
});
});
});
@@ -1,35 +0,0 @@
import { Controller, Get, Query, Param, UseGuards } from "@nestjs/common";
import { ActivityService } from "./activity.service";
import { EntityType } from "@prisma/client";
import type { QueryActivityLogDto } from "./dto";
import { AuthGuard } from "../auth/guards/auth.guard";
import { WorkspaceGuard, PermissionGuard } from "../common/guards";
import { Workspace, Permission, RequirePermission } from "../common/decorators";
@Controller("activity")
@UseGuards(AuthGuard, WorkspaceGuard, PermissionGuard)
export class ActivityController {
constructor(private readonly activityService: ActivityService) {}
@Get()
@RequirePermission(Permission.WORKSPACE_ANY)
async findAll(@Query() query: QueryActivityLogDto, @Workspace() workspaceId: string) {
return this.activityService.findAll(Object.assign({}, query, { workspaceId }));
}
@Get("audit/:entityType/:entityId")
@RequirePermission(Permission.WORKSPACE_ANY)
async getAuditTrail(
@Param("entityType") entityType: EntityType,
@Param("entityId") entityId: string,
@Workspace() workspaceId: string
) {
return this.activityService.getAuditTrail(workspaceId, entityType, entityId);
}
@Get(":id")
@RequirePermission(Permission.WORKSPACE_ANY)
async findOne(@Param("id") id: string, @Workspace() workspaceId: string) {
return this.activityService.findOne(id, workspaceId);
}
}
-16
View File
@@ -1,16 +0,0 @@
import { Module } from "@nestjs/common";
import { ActivityController } from "./activity.controller";
import { ActivityService } from "./activity.service";
import { PrismaModule } from "../prisma/prisma.module";
import { AuthModule } from "../auth/auth.module";
/**
* Module for activity logging and audit trail functionality
*/
@Module({
imports: [PrismaModule, AuthModule],
controllers: [ActivityController],
providers: [ActivityService],
exports: [ActivityService],
})
export class ActivityModule {}
File diff suppressed because it is too large Load Diff
-584
View File
@@ -1,584 +0,0 @@
import { Injectable, Logger } from "@nestjs/common";
import { PrismaService } from "../prisma/prisma.service";
import { ActivityAction, EntityType, Prisma, ActivityLog } from "@prisma/client";
import type {
CreateActivityLogInput,
PaginatedActivityLogs,
ActivityLogResult,
} from "./interfaces/activity.interface";
import type { QueryActivityLogDto } from "./dto";
/**
* Service for managing activity logs and audit trails
*/
@Injectable()
export class ActivityService {
private readonly logger = new Logger(ActivityService.name);
constructor(private readonly prisma: PrismaService) {}
/**
* Create a new activity log entry (fire-and-forget)
*
* Activity logging failures are logged but never propagate to callers.
* This ensures activity logging never breaks primary operations.
*
* @returns The created ActivityLog or null if logging failed
*/
async logActivity(input: CreateActivityLogInput): Promise<ActivityLog | null> {
try {
return await this.prisma.activityLog.create({
data: input as unknown as Prisma.ActivityLogCreateInput,
});
} catch (error) {
// Log the error but don't propagate - activity logging is fire-and-forget
this.logger.error(
`Failed to log activity: action=${input.action} entityType=${input.entityType} entityId=${input.entityId}`,
error instanceof Error ? error.stack : String(error)
);
return null;
}
}
/**
* Get paginated activity logs with filters
*/
async findAll(query: QueryActivityLogDto): Promise<PaginatedActivityLogs> {
const page = query.page ?? 1;
const limit = query.limit ?? 50;
const skip = (page - 1) * limit;
// Build where clause
const where: Prisma.ActivityLogWhereInput = {};
if (query.workspaceId !== undefined) {
where.workspaceId = query.workspaceId;
}
if (query.userId) {
where.userId = query.userId;
}
if (query.action) {
where.action = query.action;
}
if (query.entityType) {
where.entityType = query.entityType;
}
if (query.entityId) {
where.entityId = query.entityId;
}
if (query.startDate ?? query.endDate) {
where.createdAt = {};
if (query.startDate) {
where.createdAt.gte = query.startDate;
}
if (query.endDate) {
where.createdAt.lte = query.endDate;
}
}
// Execute queries in parallel
const [data, total] = await Promise.all([
this.prisma.activityLog.findMany({
where,
include: {
user: {
select: {
id: true,
name: true,
email: true,
},
},
},
orderBy: {
createdAt: "desc",
},
skip,
take: limit,
}),
this.prisma.activityLog.count({ where }),
]);
return {
data,
meta: {
total,
page,
limit,
totalPages: Math.ceil(total / limit),
},
};
}
/**
* Get a single activity log by ID
*/
async findOne(id: string, workspaceId: string): Promise<ActivityLogResult | null> {
return await this.prisma.activityLog.findUnique({
where: {
id,
workspaceId,
},
include: {
user: {
select: {
id: true,
name: true,
email: true,
},
},
},
});
}
/**
* Get audit trail for a specific entity
*/
async getAuditTrail(
workspaceId: string,
entityType: EntityType,
entityId: string
): Promise<ActivityLogResult[]> {
return await this.prisma.activityLog.findMany({
where: {
workspaceId,
entityType,
entityId,
},
include: {
user: {
select: {
id: true,
name: true,
email: true,
},
},
},
orderBy: {
createdAt: "asc",
},
});
}
// ============================================
// HELPER METHODS FOR COMMON ACTIVITY TYPES
// ============================================
/**
* Log task creation
*/
async logTaskCreated(
workspaceId: string,
userId: string,
taskId: string,
details?: Prisma.JsonValue
): Promise<ActivityLog | null> {
return this.logActivity({
workspaceId,
userId,
action: ActivityAction.CREATED,
entityType: EntityType.TASK,
entityId: taskId,
...(details && { details }),
});
}
/**
* Log task update
*/
async logTaskUpdated(
workspaceId: string,
userId: string,
taskId: string,
details?: Prisma.JsonValue
): Promise<ActivityLog | null> {
return this.logActivity({
workspaceId,
userId,
action: ActivityAction.UPDATED,
entityType: EntityType.TASK,
entityId: taskId,
...(details && { details }),
});
}
/**
* Log task deletion
*/
async logTaskDeleted(
workspaceId: string,
userId: string,
taskId: string,
details?: Prisma.JsonValue
): Promise<ActivityLog | null> {
return this.logActivity({
workspaceId,
userId,
action: ActivityAction.DELETED,
entityType: EntityType.TASK,
entityId: taskId,
...(details && { details }),
});
}
/**
* Log task completion
*/
async logTaskCompleted(
workspaceId: string,
userId: string,
taskId: string,
details?: Prisma.JsonValue
): Promise<ActivityLog | null> {
return this.logActivity({
workspaceId,
userId,
action: ActivityAction.COMPLETED,
entityType: EntityType.TASK,
entityId: taskId,
...(details && { details }),
});
}
/**
* Log task assignment
*/
async logTaskAssigned(
workspaceId: string,
userId: string,
taskId: string,
assigneeId: string
): Promise<ActivityLog | null> {
return this.logActivity({
workspaceId,
userId,
action: ActivityAction.ASSIGNED,
entityType: EntityType.TASK,
entityId: taskId,
details: { assigneeId },
});
}
/**
* Log event creation
*/
async logEventCreated(
workspaceId: string,
userId: string,
eventId: string,
details?: Prisma.JsonValue
): Promise<ActivityLog | null> {
return this.logActivity({
workspaceId,
userId,
action: ActivityAction.CREATED,
entityType: EntityType.EVENT,
entityId: eventId,
...(details && { details }),
});
}
/**
* Log event update
*/
async logEventUpdated(
workspaceId: string,
userId: string,
eventId: string,
details?: Prisma.JsonValue
): Promise<ActivityLog | null> {
return this.logActivity({
workspaceId,
userId,
action: ActivityAction.UPDATED,
entityType: EntityType.EVENT,
entityId: eventId,
...(details && { details }),
});
}
/**
* Log event deletion
*/
async logEventDeleted(
workspaceId: string,
userId: string,
eventId: string,
details?: Prisma.JsonValue
): Promise<ActivityLog | null> {
return this.logActivity({
workspaceId,
userId,
action: ActivityAction.DELETED,
entityType: EntityType.EVENT,
entityId: eventId,
...(details && { details }),
});
}
/**
* Log project creation
*/
async logProjectCreated(
workspaceId: string,
userId: string,
projectId: string,
details?: Prisma.JsonValue
): Promise<ActivityLog | null> {
return this.logActivity({
workspaceId,
userId,
action: ActivityAction.CREATED,
entityType: EntityType.PROJECT,
entityId: projectId,
...(details && { details }),
});
}
/**
* Log project update
*/
async logProjectUpdated(
workspaceId: string,
userId: string,
projectId: string,
details?: Prisma.JsonValue
): Promise<ActivityLog | null> {
return this.logActivity({
workspaceId,
userId,
action: ActivityAction.UPDATED,
entityType: EntityType.PROJECT,
entityId: projectId,
...(details && { details }),
});
}
/**
* Log project deletion
*/
async logProjectDeleted(
workspaceId: string,
userId: string,
projectId: string,
details?: Prisma.JsonValue
): Promise<ActivityLog | null> {
return this.logActivity({
workspaceId,
userId,
action: ActivityAction.DELETED,
entityType: EntityType.PROJECT,
entityId: projectId,
...(details && { details }),
});
}
/**
* Log workspace creation
*/
async logWorkspaceCreated(
workspaceId: string,
userId: string,
details?: Prisma.JsonValue
): Promise<ActivityLog | null> {
return this.logActivity({
workspaceId,
userId,
action: ActivityAction.CREATED,
entityType: EntityType.WORKSPACE,
entityId: workspaceId,
...(details && { details }),
});
}
/**
* Log workspace update
*/
async logWorkspaceUpdated(
workspaceId: string,
userId: string,
details?: Prisma.JsonValue
): Promise<ActivityLog | null> {
return this.logActivity({
workspaceId,
userId,
action: ActivityAction.UPDATED,
entityType: EntityType.WORKSPACE,
entityId: workspaceId,
...(details && { details }),
});
}
/**
* Log workspace member added
*/
async logWorkspaceMemberAdded(
workspaceId: string,
userId: string,
memberId: string,
role: string
): Promise<ActivityLog | null> {
return this.logActivity({
workspaceId,
userId,
action: ActivityAction.CREATED,
entityType: EntityType.WORKSPACE,
entityId: workspaceId,
details: { memberId, role },
});
}
/**
* Log workspace member removed
*/
async logWorkspaceMemberRemoved(
workspaceId: string,
userId: string,
memberId: string
): Promise<ActivityLog | null> {
return this.logActivity({
workspaceId,
userId,
action: ActivityAction.DELETED,
entityType: EntityType.WORKSPACE,
entityId: workspaceId,
details: { memberId },
});
}
/**
* Log user profile update
*/
async logUserUpdated(
workspaceId: string,
userId: string,
details?: Prisma.JsonValue
): Promise<ActivityLog | null> {
return this.logActivity({
workspaceId,
userId,
action: ActivityAction.UPDATED,
entityType: EntityType.USER,
entityId: userId,
...(details && { details }),
});
}
/**
* Log domain creation
*/
async logDomainCreated(
workspaceId: string,
userId: string,
domainId: string,
details?: Prisma.JsonValue
): Promise<ActivityLog | null> {
return this.logActivity({
workspaceId,
userId,
action: ActivityAction.CREATED,
entityType: EntityType.DOMAIN,
entityId: domainId,
...(details && { details }),
});
}
/**
* Log domain update
*/
async logDomainUpdated(
workspaceId: string,
userId: string,
domainId: string,
details?: Prisma.JsonValue
): Promise<ActivityLog | null> {
return this.logActivity({
workspaceId,
userId,
action: ActivityAction.UPDATED,
entityType: EntityType.DOMAIN,
entityId: domainId,
...(details && { details }),
});
}
/**
* Log domain deletion
*/
async logDomainDeleted(
workspaceId: string,
userId: string,
domainId: string,
details?: Prisma.JsonValue
): Promise<ActivityLog | null> {
return this.logActivity({
workspaceId,
userId,
action: ActivityAction.DELETED,
entityType: EntityType.DOMAIN,
entityId: domainId,
...(details && { details }),
});
}
/**
* Log idea creation
*/
async logIdeaCreated(
workspaceId: string,
userId: string,
ideaId: string,
details?: Prisma.JsonValue
): Promise<ActivityLog | null> {
return this.logActivity({
workspaceId,
userId,
action: ActivityAction.CREATED,
entityType: EntityType.IDEA,
entityId: ideaId,
...(details && { details }),
});
}
/**
* Log idea update
*/
async logIdeaUpdated(
workspaceId: string,
userId: string,
ideaId: string,
details?: Prisma.JsonValue
): Promise<ActivityLog | null> {
return this.logActivity({
workspaceId,
userId,
action: ActivityAction.UPDATED,
entityType: EntityType.IDEA,
entityId: ideaId,
...(details && { details }),
});
}
/**
* Log idea deletion
*/
async logIdeaDeleted(
workspaceId: string,
userId: string,
ideaId: string,
details?: Prisma.JsonValue
): Promise<ActivityLog | null> {
return this.logActivity({
workspaceId,
userId,
action: ActivityAction.DELETED,
entityType: EntityType.IDEA,
entityId: ideaId,
...(details && { details }),
});
}
}
@@ -1,348 +0,0 @@
import { describe, it, expect } from "vitest";
import { validate } from "class-validator";
import { plainToInstance } from "class-transformer";
import { CreateActivityLogDto } from "./create-activity-log.dto";
import { ActivityAction, EntityType } from "@prisma/client";
describe("CreateActivityLogDto", () => {
describe("required fields validation", () => {
it("should pass with all required fields valid", async () => {
const dto = plainToInstance(CreateActivityLogDto, {
workspaceId: "550e8400-e29b-41d4-a716-446655440000",
userId: "550e8400-e29b-41d4-a716-446655440001",
action: ActivityAction.CREATED,
entityType: EntityType.TASK,
entityId: "550e8400-e29b-41d4-a716-446655440002",
});
const errors = await validate(dto);
expect(errors).toHaveLength(0);
});
it("should fail when workspaceId is missing", async () => {
const dto = plainToInstance(CreateActivityLogDto, {
userId: "550e8400-e29b-41d4-a716-446655440001",
action: ActivityAction.CREATED,
entityType: EntityType.TASK,
entityId: "550e8400-e29b-41d4-a716-446655440002",
});
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
const workspaceIdError = errors.find((e) => e.property === "workspaceId");
expect(workspaceIdError).toBeDefined();
});
it("should fail when userId is missing", async () => {
const dto = plainToInstance(CreateActivityLogDto, {
workspaceId: "550e8400-e29b-41d4-a716-446655440000",
action: ActivityAction.CREATED,
entityType: EntityType.TASK,
entityId: "550e8400-e29b-41d4-a716-446655440002",
});
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
const userIdError = errors.find((e) => e.property === "userId");
expect(userIdError).toBeDefined();
});
it("should fail when action is missing", async () => {
const dto = plainToInstance(CreateActivityLogDto, {
workspaceId: "550e8400-e29b-41d4-a716-446655440000",
userId: "550e8400-e29b-41d4-a716-446655440001",
entityType: EntityType.TASK,
entityId: "550e8400-e29b-41d4-a716-446655440002",
});
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
const actionError = errors.find((e) => e.property === "action");
expect(actionError).toBeDefined();
});
it("should fail when entityType is missing", async () => {
const dto = plainToInstance(CreateActivityLogDto, {
workspaceId: "550e8400-e29b-41d4-a716-446655440000",
userId: "550e8400-e29b-41d4-a716-446655440001",
action: ActivityAction.CREATED,
entityId: "550e8400-e29b-41d4-a716-446655440002",
});
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
const entityTypeError = errors.find((e) => e.property === "entityType");
expect(entityTypeError).toBeDefined();
});
it("should fail when entityId is missing", async () => {
const dto = plainToInstance(CreateActivityLogDto, {
workspaceId: "550e8400-e29b-41d4-a716-446655440000",
userId: "550e8400-e29b-41d4-a716-446655440001",
action: ActivityAction.CREATED,
entityType: EntityType.TASK,
});
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
const entityIdError = errors.find((e) => e.property === "entityId");
expect(entityIdError).toBeDefined();
});
});
describe("UUID validation", () => {
it("should fail with invalid workspaceId UUID", async () => {
const dto = plainToInstance(CreateActivityLogDto, {
workspaceId: "invalid-uuid",
userId: "550e8400-e29b-41d4-a716-446655440001",
action: ActivityAction.CREATED,
entityType: EntityType.TASK,
entityId: "550e8400-e29b-41d4-a716-446655440002",
});
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
expect(errors[0].property).toBe("workspaceId");
});
it("should fail with invalid userId UUID", async () => {
const dto = plainToInstance(CreateActivityLogDto, {
workspaceId: "550e8400-e29b-41d4-a716-446655440000",
userId: "not-a-uuid",
action: ActivityAction.CREATED,
entityType: EntityType.TASK,
entityId: "550e8400-e29b-41d4-a716-446655440002",
});
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
const userIdError = errors.find((e) => e.property === "userId");
expect(userIdError).toBeDefined();
});
it("should fail with invalid entityId UUID", async () => {
const dto = plainToInstance(CreateActivityLogDto, {
workspaceId: "550e8400-e29b-41d4-a716-446655440000",
userId: "550e8400-e29b-41d4-a716-446655440001",
action: ActivityAction.CREATED,
entityType: EntityType.TASK,
entityId: "bad-entity-id",
});
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
const entityIdError = errors.find((e) => e.property === "entityId");
expect(entityIdError).toBeDefined();
});
});
describe("enum validation", () => {
it("should pass with all valid ActivityAction values", async () => {
const actions = Object.values(ActivityAction);
for (const action of actions) {
const dto = plainToInstance(CreateActivityLogDto, {
workspaceId: "550e8400-e29b-41d4-a716-446655440000",
userId: "550e8400-e29b-41d4-a716-446655440001",
action,
entityType: EntityType.TASK,
entityId: "550e8400-e29b-41d4-a716-446655440002",
});
const errors = await validate(dto);
expect(errors).toHaveLength(0);
}
});
it("should fail with invalid action value", async () => {
const dto = plainToInstance(CreateActivityLogDto, {
workspaceId: "550e8400-e29b-41d4-a716-446655440000",
userId: "550e8400-e29b-41d4-a716-446655440001",
action: "INVALID_ACTION",
entityType: EntityType.TASK,
entityId: "550e8400-e29b-41d4-a716-446655440002",
});
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
const actionError = errors.find((e) => e.property === "action");
expect(actionError?.constraints?.isEnum).toBeDefined();
});
it("should pass with all valid EntityType values", async () => {
const entityTypes = Object.values(EntityType);
for (const entityType of entityTypes) {
const dto = plainToInstance(CreateActivityLogDto, {
workspaceId: "550e8400-e29b-41d4-a716-446655440000",
userId: "550e8400-e29b-41d4-a716-446655440001",
action: ActivityAction.CREATED,
entityType,
entityId: "550e8400-e29b-41d4-a716-446655440002",
});
const errors = await validate(dto);
expect(errors).toHaveLength(0);
}
});
it("should fail with invalid entityType value", async () => {
const dto = plainToInstance(CreateActivityLogDto, {
workspaceId: "550e8400-e29b-41d4-a716-446655440000",
userId: "550e8400-e29b-41d4-a716-446655440001",
action: ActivityAction.CREATED,
entityType: "INVALID_TYPE",
entityId: "550e8400-e29b-41d4-a716-446655440002",
});
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
const entityTypeError = errors.find((e) => e.property === "entityType");
expect(entityTypeError?.constraints?.isEnum).toBeDefined();
});
});
describe("optional fields validation", () => {
it("should pass with valid details object", async () => {
const dto = plainToInstance(CreateActivityLogDto, {
workspaceId: "550e8400-e29b-41d4-a716-446655440000",
userId: "550e8400-e29b-41d4-a716-446655440001",
action: ActivityAction.UPDATED,
entityType: EntityType.TASK,
entityId: "550e8400-e29b-41d4-a716-446655440002",
details: {
field: "status",
oldValue: "TODO",
newValue: "IN_PROGRESS",
},
});
const errors = await validate(dto);
expect(errors).toHaveLength(0);
});
it("should fail with non-object details", async () => {
const dto = plainToInstance(CreateActivityLogDto, {
workspaceId: "550e8400-e29b-41d4-a716-446655440000",
userId: "550e8400-e29b-41d4-a716-446655440001",
action: ActivityAction.UPDATED,
entityType: EntityType.TASK,
entityId: "550e8400-e29b-41d4-a716-446655440002",
details: "not an object",
});
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
const detailsError = errors.find((e) => e.property === "details");
expect(detailsError?.constraints?.isObject).toBeDefined();
});
it("should pass with valid ipAddress", async () => {
const dto = plainToInstance(CreateActivityLogDto, {
workspaceId: "550e8400-e29b-41d4-a716-446655440000",
userId: "550e8400-e29b-41d4-a716-446655440001",
action: ActivityAction.CREATED,
entityType: EntityType.TASK,
entityId: "550e8400-e29b-41d4-a716-446655440002",
ipAddress: "192.168.1.1",
});
const errors = await validate(dto);
expect(errors).toHaveLength(0);
});
it("should pass with valid IPv6 address", async () => {
const dto = plainToInstance(CreateActivityLogDto, {
workspaceId: "550e8400-e29b-41d4-a716-446655440000",
userId: "550e8400-e29b-41d4-a716-446655440001",
action: ActivityAction.CREATED,
entityType: EntityType.TASK,
entityId: "550e8400-e29b-41d4-a716-446655440002",
ipAddress: "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
});
const errors = await validate(dto);
expect(errors).toHaveLength(0);
});
it("should fail when ipAddress exceeds max length", async () => {
const dto = plainToInstance(CreateActivityLogDto, {
workspaceId: "550e8400-e29b-41d4-a716-446655440000",
userId: "550e8400-e29b-41d4-a716-446655440001",
action: ActivityAction.CREATED,
entityType: EntityType.TASK,
entityId: "550e8400-e29b-41d4-a716-446655440002",
ipAddress: "a".repeat(46),
});
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
const ipError = errors.find((e) => e.property === "ipAddress");
expect(ipError?.constraints?.maxLength).toBeDefined();
});
it("should pass with valid userAgent", async () => {
const dto = plainToInstance(CreateActivityLogDto, {
workspaceId: "550e8400-e29b-41d4-a716-446655440000",
userId: "550e8400-e29b-41d4-a716-446655440001",
action: ActivityAction.CREATED,
entityType: EntityType.TASK,
entityId: "550e8400-e29b-41d4-a716-446655440002",
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
});
const errors = await validate(dto);
expect(errors).toHaveLength(0);
});
it("should fail when userAgent exceeds max length", async () => {
const dto = plainToInstance(CreateActivityLogDto, {
workspaceId: "550e8400-e29b-41d4-a716-446655440000",
userId: "550e8400-e29b-41d4-a716-446655440001",
action: ActivityAction.CREATED,
entityType: EntityType.TASK,
entityId: "550e8400-e29b-41d4-a716-446655440002",
userAgent: "a".repeat(501),
});
const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
const userAgentError = errors.find((e) => e.property === "userAgent");
expect(userAgentError?.constraints?.maxLength).toBeDefined();
});
it("should pass when optional fields are not provided", async () => {
const dto = plainToInstance(CreateActivityLogDto, {
workspaceId: "550e8400-e29b-41d4-a716-446655440000",
userId: "550e8400-e29b-41d4-a716-446655440001",
action: ActivityAction.CREATED,
entityType: EntityType.TASK,
entityId: "550e8400-e29b-41d4-a716-446655440002",
});
const errors = await validate(dto);
expect(errors).toHaveLength(0);
});
});
describe("complete validation", () => {
it("should pass with all fields valid", async () => {
const dto = plainToInstance(CreateActivityLogDto, {
workspaceId: "550e8400-e29b-41d4-a716-446655440000",
userId: "550e8400-e29b-41d4-a716-446655440001",
action: ActivityAction.UPDATED,
entityType: EntityType.PROJECT,
entityId: "550e8400-e29b-41d4-a716-446655440002",
details: {
changes: ["status", "priority"],
metadata: { source: "web-app" },
},
ipAddress: "10.0.0.1",
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
});
const errors = await validate(dto);
expect(errors).toHaveLength(0);
});
});
});
@@ -1,36 +0,0 @@
import { ActivityAction, EntityType } from "@prisma/client";
import { IsUUID, IsEnum, IsOptional, IsObject, IsString, MaxLength } from "class-validator";
/**
* DTO for creating a new activity log entry
*/
export class CreateActivityLogDto {
@IsUUID("4", { message: "workspaceId must be a valid UUID" })
workspaceId!: string;
@IsUUID("4", { message: "userId must be a valid UUID" })
userId!: string;
@IsEnum(ActivityAction, { message: "action must be a valid ActivityAction" })
action!: ActivityAction;
@IsEnum(EntityType, { message: "entityType must be a valid EntityType" })
entityType!: EntityType;
@IsUUID("4", { message: "entityId must be a valid UUID" })
entityId!: string;
@IsOptional()
@IsObject({ message: "details must be an object" })
details?: Record<string, unknown>;
@IsOptional()
@IsString({ message: "ipAddress must be a string" })
@MaxLength(45, { message: "ipAddress must not exceed 45 characters" })
ipAddress?: string;
@IsOptional()
@IsString({ message: "userAgent must be a string" })
@MaxLength(500, { message: "userAgent must not exceed 500 characters" })
userAgent?: string;
}
-2
View File
@@ -1,2 +0,0 @@
export * from "./create-activity-log.dto";
export * from "./query-activity-log.dto";

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