fix(test): exclude central/ + commit central-architecture.md spec#308
Closed
floodsung wants to merge 37 commits into
Closed
fix(test): exclude central/ + commit central-architecture.md spec#308floodsung wants to merge 37 commits into
floodsung wants to merge 37 commits into
Conversation
* feat(installer): allow custom install directory via --dir / -Dir flag install.sh and install.ps1 previously hardcoded the install path to $HOME/metabot. Add a CLI flag (and matching PowerShell parameter) plus an interactive prompt so users can install MetaBot anywhere. - Priority: --dir / -Dir > METABOT_HOME env var > prompt > default. - Tilde expansion + absolute-path validation; refuses to clobber $HOME / system roots. - Persists METABOT_HOME to ~/.bashrc / ~/.zshrc (Linux/macOS) or user-level env (Windows) when non-default, so the mm/mb/metabot CLIs can locate the install in new shells. * fix(codex): show model metadata in cards * fix(codex): mirror skills and avoid bwrap sandbox * fix(codex): tolerate agents deployment failures * fix(codex): install bundled skills when user cache is empty * docs: explain Codex skill migration * feat(cluster): add federated identity foundation --------- Co-authored-by: Flood Sung <floodsung@xvirobotics.ai>
* feat(installer): allow custom install directory via --dir / -Dir flag install.sh and install.ps1 previously hardcoded the install path to $HOME/metabot. Add a CLI flag (and matching PowerShell parameter) plus an interactive prompt so users can install MetaBot anywhere. - Priority: --dir / -Dir > METABOT_HOME env var > prompt > default. - Tilde expansion + absolute-path validation; refuses to clobber $HOME / system roots. - Persists METABOT_HOME to ~/.bashrc / ~/.zshrc (Linux/macOS) or user-level env (Windows) when non-default, so the mm/mb/metabot CLIs can locate the install in new shells. * fix(codex): show model metadata in cards * fix(codex): mirror skills and avoid bwrap sandbox * fix(codex): tolerate agents deployment failures * fix(codex): install bundled skills when user cache is empty * docs: explain Codex skill migration * feat(memory): add namespace scoped instance token --------- Co-authored-by: Flood Sung <floodsung@xvirobotics.ai>
* feat(installer): allow custom install directory via --dir / -Dir flag install.sh and install.ps1 previously hardcoded the install path to $HOME/metabot. Add a CLI flag (and matching PowerShell parameter) plus an interactive prompt so users can install MetaBot anywhere. - Priority: --dir / -Dir > METABOT_HOME env var > prompt > default. - Tilde expansion + absolute-path validation; refuses to clobber $HOME / system roots. - Persists METABOT_HOME to ~/.bashrc / ~/.zshrc (Linux/macOS) or user-level env (Windows) when non-default, so the mm/mb/metabot CLIs can locate the install in new shells. * fix(codex): show model metadata in cards * fix(codex): mirror skills and avoid bwrap sandbox * fix(codex): tolerate agents deployment failures * fix(codex): install bundled skills when user cache is empty * docs: explain Codex skill migration * feat(skills): track owner metadata and hashes --------- Co-authored-by: Flood Sung <floodsung@xvirobotics.ai>
* feat(installer): allow custom install directory via --dir / -Dir flag install.sh and install.ps1 previously hardcoded the install path to $HOME/metabot. Add a CLI flag (and matching PowerShell parameter) plus an interactive prompt so users can install MetaBot anywhere. - Priority: --dir / -Dir > METABOT_HOME env var > prompt > default. - Tilde expansion + absolute-path validation; refuses to clobber $HOME / system roots. - Persists METABOT_HOME to ~/.bashrc / ~/.zshrc (Linux/macOS) or user-level env (Windows) when non-default, so the mm/mb/metabot CLIs can locate the install in new shells. * fix(codex): show model metadata in cards * fix(codex): mirror skills and avoid bwrap sandbox * fix(codex): tolerate agents deployment failures * fix(codex): install bundled skills when user cache is empty * docs: explain Codex skill migration * feat(cluster): bootstrap peers from cluster url --------- Co-authored-by: Flood Sung <floodsung@xvirobotics.ai>
* feat(installer): allow custom install directory via --dir / -Dir flag install.sh and install.ps1 previously hardcoded the install path to $HOME/metabot. Add a CLI flag (and matching PowerShell parameter) plus an interactive prompt so users can install MetaBot anywhere. - Priority: --dir / -Dir > METABOT_HOME env var > prompt > default. - Tilde expansion + absolute-path validation; refuses to clobber $HOME / system roots. - Persists METABOT_HOME to ~/.bashrc / ~/.zshrc (Linux/macOS) or user-level env (Windows) when non-default, so the mm/mb/metabot CLIs can locate the install in new shells. * fix(codex): show model metadata in cards * fix(codex): mirror skills and avoid bwrap sandbox * fix(codex): tolerate agents deployment failures * fix(codex): install bundled skills when user cache is empty * docs: explain Codex skill migration * docs: document federated memory and skill hub --------- Co-authored-by: Flood Sung <floodsung@xvirobotics.ai>
* feat(installer): allow custom install directory via --dir / -Dir flag install.sh and install.ps1 previously hardcoded the install path to $HOME/metabot. Add a CLI flag (and matching PowerShell parameter) plus an interactive prompt so users can install MetaBot anywhere. - Priority: --dir / -Dir > METABOT_HOME env var > prompt > default. - Tilde expansion + absolute-path validation; refuses to clobber $HOME / system roots. - Persists METABOT_HOME to ~/.bashrc / ~/.zshrc (Linux/macOS) or user-level env (Windows) when non-default, so the mm/mb/metabot CLIs can locate the install in new shells. * fix(codex): show model metadata in cards * fix(codex): mirror skills and avoid bwrap sandbox * fix(codex): tolerate agents deployment failures * fix(codex): install bundled skills when user cache is empty * docs: explain Codex skill migration * fix(cli): use instance memory token --------- Co-authored-by: Flood Sung <floodsung@xvirobotics.ai>
* feat(installer): allow custom install directory via --dir / -Dir flag install.sh and install.ps1 previously hardcoded the install path to $HOME/metabot. Add a CLI flag (and matching PowerShell parameter) plus an interactive prompt so users can install MetaBot anywhere. - Priority: --dir / -Dir > METABOT_HOME env var > prompt > default. - Tilde expansion + absolute-path validation; refuses to clobber $HOME / system roots. - Persists METABOT_HOME to ~/.bashrc / ~/.zshrc (Linux/macOS) or user-level env (Windows) when non-default, so the mm/mb/metabot CLIs can locate the install in new shells. * fix(codex): show model metadata in cards * fix(codex): mirror skills and avoid bwrap sandbox * fix(codex): tolerate agents deployment failures * fix(codex): install bundled skills when user cache is empty * docs: explain Codex skill migration * Cache peer Skill Hub artifacts --------- Co-authored-by: Flood Sung <floodsung@xvirobotics.ai>
Co-authored-by: Flood Sung <floodsung@xvirobotics.ai>
* Mirror peer MetaMemory documents * Add stable bot memory namespaces --------- Co-authored-by: Flood Sung <floodsung@xvirobotics.ai>
* Mirror peer MetaMemory documents * Add stable bot memory namespaces * Update lark CLI via metabot update --------- Co-authored-by: Flood Sung <floodsung@xvirobotics.ai>
* Mirror peer MetaMemory documents
* Add stable bot memory namespaces
* Sharpen metabot skill: lead with mb talk for cross-bot messaging
The previous frontmatter description buried `mb talk` behind generic phrasing
("agent collaboration") that mixed it with bot management, skills publishing,
and voice calls. Result: the main agent rarely reached for it when the user
said things like "跟其他 bot 说话" or "delegate to bot X".
Rewrites the description to lead with the talk use case (with CN+EN trigger
phrases) and adds a Quickstart at the top of the body that shows the one-line
local + cross-peer commands, points out the async semantics, and explicitly
distinguishes from Agent Teams' `SendMessage`. Other features (bots/peers/
voice/skill-hub/scheduling/API) move below the Quickstart.
---------
Co-authored-by: Flood Sung <floodsung@xvirobotics.ai>
…#295) The Claude Agent SDK's AskUserQuestionOutput schema (sdk-tools.d.ts) documents the `answers` dict as "question text -> answer string". The SDK's tool_result template looks up `answers[question.question]`; keying by `header` produces a structurally-valid dict that the hook happily forwards to the SDK, but the rendered tool_result text comes out as "User has answered your questions: . You can now continue..." — answers list empty, the model has no idea what the user picked, the turn is wasted. Observed end-to-end: bridge log showed the resolver unblocking with 3 correctly-collected answers, but the SDK transcript recorded `tool_result.content` with no interpolated values (while the structured `toolUseResult.answers` carried them through). Fix four call sites to key by `question.question`: - handleAnswer (in-turn answer collection) - tryHandleBetweenTurnQuestionReply (firstQ + remaining-fill) - autoAnswerRemainingQuestions (timeout fallback) - tests/between-turn-question.test.ts (the prior assertion encoded the same bug, so the regression slipped past unit tests) Verified: 315/315 vitest pass, `tsc` clean, lint clean for changed files, plus end-to-end retest in Feishu confirmed the model now sees the user's selections instead of an empty list. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Mirror peer MetaMemory documents * Add stable bot memory namespaces * feat(cluster): mDNS LAN auto-discovery for MetaBot peers Two MetaBot instances on the same LAN now discover each other within ~30s of startup with zero `.env` configuration. Each instance advertises `_metabot._tcp.local` with a TXT record carrying instanceId, instanceName, optional clusterId, and a SHA-256 fingerprint of the Ed25519 public key — no secrets on the wire. - `src/cluster/mdns.ts`: thin wrapper over `bonjour-service` with an injection seam for tests. Honors discoveryMode: `auto` (default; advertise+browse), `static` (browse only), `standalone`/`off` (no-op). Self-filter by instanceId; optional clusterFilter drops cross-cluster peers. - `PeerManager`: new `source` field on peers (`static|cluster|mdns|manual`) plus `addDynamicPeer` / `removeDynamicPeer`. Static peers win on URL collision so pre-configured secrets keep working; the poll timer is lazily armed when the first dynamic peer arrives. - `src/index.ts`: wires startMdns into startup (always builds PeerManager now so mDNS peers have somewhere to land) and graceful shutdown. - Docs (`docs/features/peers.md` + `.zh.md`, README.md) describe the new flow, the manual two-machine verification, `METABOT_DISCOVERY_MODE` and `METABOT_MDNS_ENABLED` knobs. Stage 1 of 3 from `docs/internal/lan-shared-status-audit.md` §5. ACL changes, token auto-issuance, and federated `mm search` stay in Stages 2 and 3. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Flood Sung <floodsung@xvirobotics.ai> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* Mirror peer MetaMemory documents
* Add stable bot memory namespaces
* feat(peers): auto reader-token handshake (Pragmatic v1)
Two fresh-install MetaBots on the same LAN can now reach each other's
shared memory and skills without operators exchanging secrets. Each
instance generates a stable reader token at ~/.metabot/peer-token on
first start; once peers discover each other (mDNS or manual add), they
exchange tokens through POST /api/peer-handshake and cache the reply
as the secret for outbound calls.
Pragmatic v1 scope — the inbound peer token is resolved by memory-server
to a reader principal scoped to the peer's instanceId. Read ACL is still
gated by folder visibility (default 'shared'); a principal+grants read
gate is deferred to Phase 7 of the federated memory plan. Three deferred
triggers documented: namespace-level revoke, cross-VLAN trust, audit
needing per-grant attribution.
Also in this PR:
- Dynamic peer demote: mDNS/cluster/manual peers continuously unhealthy
for 5 min (METABOT_DYNAMIC_PEER_DEMOTE_MS to tune) are dropped from
the in-memory registry. Static peers are untouched.
- install.sh writes MEMORY_INSTANCE_TOKEN on fresh install and
idempotently backfills it on existing .env files.
- Cluster ID onboarding tip and dynamic-demote knob documented in
docs/features/peers.{md,zh.md}.
- canReadFolder / hasNamespaceGrant logic unchanged; a verbatim
Pragmatic v1 marker comment was added to the read branch.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Flood Sung <floodsung@xvirobotics.ai>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…298) * Mirror peer MetaMemory documents * Add stable bot memory namespaces * fix(memory-proxy): preserve inbound Authorization header for peer-token reads The bridge's /memory/* reverse proxy was unconditionally rewriting the caller's Authorization header to the local admin token before forwarding to memory-server. This silently bypassed the Pragmatic v1 folder- visibility gate for cross-instance traffic: when peer-A hit peer-B's /memory/api/* with B's reader token (from the Stage 2 handshake), B's bridge replaced it with B's own admin token, and memory-server returned the unfiltered admin view — peers effectively had admin reads on the other instance. Fix preserves the inbound Authorization verbatim when present and only falls back to memoryAuthToken when no header is supplied (web UI use case). Proxy logic moved into src/api/routes/memory-proxy.ts so the behaviour can be regression-tested directly. New tests/memory-proxy-auth.test.ts spins up a memory-server and a proxy server, then proves end-to-end that: 1. A peer reader token round-trips through the proxy and folder visibility filters out a private folder + its docs from both the folder tree and search results. 2. An unknown token returns 401 even with admin fallback configured. 3. No-Authorization requests still receive the admin fallback (web UI compatibility). Affects PR #297 (Stage 2 auto reader-token handshake) — Pragmatic v1 was demonstrated only by in-process tests there; this PR makes the cross-instance proxy path uphold the same contract. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Flood Sung <floodsung@xvirobotics.ai> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* Mirror peer MetaMemory documents * Add stable bot memory namespaces * feat(search): federated mm search across local + live peers + cache-stale Stage 3 of the LAN-shared-access initiative. mm search now fans out server-side via a new GET /api/search/federated endpoint on the bridge and returns a single merged payload, with each hit tagged source=local|peer|cache-stale. - src/api/routes/search-routes.ts: orchestrates local memory-server + live peers (only those we hold a reader token for, via Stage 2's handshake or operator-configured secret) + cache-stale entries. - src/api/peer-manager.ts: adds getLivePeersWithSecret() so the fan-out knows which peers to hit and with what token. - Dedup by peerName: if a peer responds live (even with zero hits), its stale cache entries are suppressed so the operator doesn't see dupes. Filter is by peerName, not URL — peers can rotate URLs. - bin/mm search: now hits the bridge's federated endpoint; when METABOT_URL is unreachable, falls back to META_MEMORY_URL/api/search (local-only) and prints a one-line stderr warning. - bin/mm peer-search: kept as a cache-only inspector for debugging. - Pragmatic v1 ACL untouched — folder-visibility default + token gate. Read side reuses /memory proxy whose Authorization passthrough was fixed in PR #298. - Audit doc: flip Top-3 #3 status to DONE. Tests: 4 new tests in tests/federated-search.test.ts cover the merge, the dedup-by-peerName logic, the standalone-deployment fallthrough, and the per-peer error reporting. Suite stays green at 352/352. --------- Co-authored-by: Flood Sung <floodsung@xvirobotics.ai>
…nce peers (#300) * Mirror peer MetaMemory documents * Add stable bot memory namespaces * fix(skill-hub): respect visibility on list/search/get for cross-instance peers Skills already carry a `visibility: private | published | shared` field on publish, but the read paths (`store.list`, `store.search`, and the `GET /api/skills/:name` route) never filtered on it. A cross-instance peer authenticated with the shared API_SECRET could enumerate and fetch skills marked `private` by their owner — a real data leak across the new federated + central-archive deploy shape. Changes: - `SkillHubStore.list/search` now accept an optional `visibility[]` allow-list and add a `WHERE visibility IN (...)` clause when set. - `skill-hub-routes` derives the allow-list from `X-MetaBot-Origin: peer`: local admin (no header) sees everything; peers see only `published` and `shared`. The single-skill GET applies the same gate so guessing the name does not bypass the list filter. - `POST /api/skills/:name/install` is unchanged — peers install onto *their* bots via fetchPeerSkill, which hits the now-gated GET above, so published/shared installs continue to work while private skills are 404. - New regression tests at `tests/skill-hub-visibility.test.ts` cover store-level filtering, HTTP list/search, and the install-path 404. * fix(skill-hub): drop stray audit-log imports A concurrent worktree's audit-log scaffolding leaked into the previous commit on this branch (`AuditOp` import + `deriveSkillOp` / audit hook). That work belongs on `feat/audit-log-jsonl`, not this PR. CI flagged it as a missing module on tsc. Removing here so the visibility filter ships standalone. --------- Co-authored-by: Flood Sung <floodsung@xvirobotics.ai>
…302) * feat(memory): default folder visibility = private + mm share opt-in Phase 0 of the central-architecture pivot — tighten P2P defaults so a new folder is not immediately visible to every peer in the LAN federation. Behavior change: - `POST /api/folders` now defaults `visibility` to `'private'` instead of `'shared'`. Cross-instance peer-token readers cannot see private folders (or their documents) until an admin flips them via `PUT /api/folders/:id { visibility: 'shared' }` or `mm share <path|id>`. - `mm share` / `mm unshare` CLI commands added (accept UUID or path). - Local admin and instance-token (own-namespace) access unaffected. - Existing folders keep their current `visibility` — migration is metadata-only; nothing in the DB is rewritten. Why now: insider-exfil threat model. Central architecture (Phase 1+) removes peer-token visibility entirely, but until that ships every new folder defaulting to `shared` is too loud — one slip and a private project is readable across the whole LAN federation. Tests: - tests/memory-default-private.test.ts — 7 new cases covering default, explicit shared, mm share/unshare, peer-token visibility, admin override. - tests/memory-server.test.ts — updated to mark shared-test folders explicitly. - tests/federated-search.test.ts — opts into `shared` for the cross-instance fan-out scenario it exercises. Docs: - README.md: Phase 0 default-private callout + mm share usage. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs: surface Phase 0 default-private folders in CLAUDE.md and README_EN.md Mirror the loud breaking-change callout already in README.md so English docs + working-mode guidance for Claude both flag the new default. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Flood Sung <floodsung@xvirobotics.ai> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…(Phase 0) (#303) Append-only daily audit log capturing every memory + skill-hub request: who (principal), what (op), where (path), from (sourceIp), result (status), how long (latencyMs). JSON-lines file under `data/audit/YYYY-MM-DD.jsonl`, size-capped with rotation (`<date>.1.jsonl`, `.2.jsonl`, ...). Why now: insider-exfil threat model. Central architecture (Phase 1+) will reuse this same `AuditLog` class server-side; landing the foundation in Phase 0 means we already have a record-of-truth for the interim P2P window AND the migration period. What's logged: - Memory: list, read, create, update, delete, search - Skill hub: list, get, search, publish, install, delete - Principal: 'admin' for local admin token; instanceId for federated peers; `peer:<name>` for skill-hub cross-instance reads - Source IP: X-Forwarded-For first hop, falling back to socket remote Operator UX: - `mm audit <YYYY-MM-DD> [--filter principal=X] [--filter op=X]` — pretty-prints accessible entries from the JSONL file - METABOT_AUDIT_ENABLED=false to disable (default on) - METABOT_AUDIT_DIR overrides location Tests: - 5 cases in tests/audit-log.test.ts covering append, multiline read, date rotation, size rotation, disabled mode - Existing memory-server tests still green (9/9) Co-authored-by: Flood Sung <floodsung@xvirobotics.ai> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* Mirror peer MetaMemory documents
* Add stable bot memory namespaces
* Phase 1: central server module
New top-level `central/` module — Node 20 + better-sqlite3 server that
serves as the centralized memory + skill hub MetaBot is pivoting to
(see docs/internal/central-architecture.md).
Adapted from src/memory/memory-{storage,server,routes}.ts and
src/api/skill-hub-store.ts but with a path-based ACL keyed to a new
Credential model, separate npm package, and self-contained deploy/.
What's in this module:
- src/auth/ Credential model + SQLite store + middleware
- sha256 token hashing, 60s lookup cache
- deferred lastUsedAt batch writes
- admin bootstrap (one-time token → data/admin-bootstrap-token.txt 0600)
- src/memory/ Folders + documents + FTS5 search
- path-based ACL (canRead/canWrite per Credential)
- create-by-path auto-creates intermediate folders
- src/skills/ Publish/list/search/delete + publish-acl
- member requires publishSkill: true
- delete is admin-only
- src/admin/ issue/revoke/list creds + audit query routes + CLI
- src/observability/ JSONL daily audit log (rotates at 100 MB)
- src/server.ts HTTP routing + auth on every /api or /admin request
- bin/central-admin CLI shim → dist/admin/admin-cli.js
- Dockerfile multi-stage Node 20 slim, drops to `node` user
- docker-compose.yml central + optional Caddy profile
- deploy/install.sh idempotent Ubuntu 22.04 installer (Node + Caddy + systemd)
- deploy/Caddyfile TLS terminator template
- deploy/central.service systemd unit, hardened
Tests (21 pass):
- tests/auth.test.ts issue/revoke/lookup/cache/bootstrap
- tests/memory.test.ts CRUD + namespace ACL (admin vs member isolation)
- tests/skills.test.ts publish-acl + visibility filter + remove
- tests/audit.test.ts every authed request logged JSONL
- tests/e2e.test.ts end-to-end over HTTP: bootstrap → issue member →
member writes own ns / 403 on /shared and other
users / revoke → 401 credential_revoked
Scope notes:
- Does NOT modify anything under src/ of the main metabot (per spec)
- No deployment performed — gated on getting an ECS from trunks
- Phase 2 (metabot client mode pointing at central) is a follow-up PR
- Phase 4 (P2P teardown) is a separate follow-up PR
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Flood Sung <floodsung@xvirobotics.ai>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
`central/` is a standalone npm package with its own vitest config that sets `singleFork: true` to serialize port-binding tests (port 18200+ via a module-scoped counter). When the top-level `npm test` picks up `central/tests/**`, vitest's default parallel workers race for the same port and `EADDRINUSE 18200` flakes one test per run. Each package owns its own test run — top-level should not crawl into `central/`. `cd central && npm test` continues to work. Phase 1 (PR #304) introduced this, and PRs #305 / #306 / #307 each independently flagged it. This is the one-line fix. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The Phase 1 central server (PR #304) and Phase 2/3/4 PRs all reference this file, but it was never checked in — multiple READMEs / CLAUDE.md pointers were dangling. Committing the spec as it stood when central- builder agent implemented Phase 1. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Contributor
Author
|
Closed: superseded by main history rewrite on 2026-05-18 — central/ directory no longer exists on main, vitest no longer needs to exclude it. Pre-revert state archived at tag archive/p2p-era-end-20260518-main. |
This was referenced May 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
One-line vitest config fix: exclude
central/**from the top-levelnpm testrun. Three open PRs (#305, #306, #307) all independently flagged a flaky port-bind race incentral/tests/audit.test.ts(EADDRINUSE on 18200). Root cause:central/is a standalone package with its ownvitest.config.tssettingsingleFork: true, but the top-level vitest crawls into it with default parallel workers, breaking the serialization the central tests rely on.Each package owns its own test run.
cd central && npm testcontinues to work.Test plan
npm testat top level — 374/374 passing (was 414/415 with race)cd central && npm test— still works, 21/21🤖 Generated with Claude Code