Mesh App Validation Report
Generated by the implementation of the mesh-app plan (Phases 0–5). This report is the expected result of running
scripts/validate-mesh.shagainst a fresh stack; the script is the executable version.
What was already there
The pre-plan codebase already implemented the structural backbone the user asked to validate:
| User requirement (mesh flow) | Pre-plan state |
|---|---|
| Agents work by one project, possibly in different repos | ✅ project + agents table; no project_repos (added in Phase 2) |
| Organize & register agents in Agent Shaker | ✅ REST POST /api/agents; added MCP register_self in Phase 4 |
| Connect Claude via MCP | ✅ mcp-bridge.js + 25+ existing tools; new tools added in Phases 1–4 |
| Share tasks | ✅ tasks table + REST + MCP claim_task / complete_task / update_task_status |
| Share context | ✅ contexts table (markdown + tags) + REST + MCP add_context |
| Share milestones | ❌ not modelled → added milestones table, REST, MCP create_milestone / list_milestones / assign_task_to_milestone (Phase 1) |
| Share features | ❌ → derived from tasks.tags prefix feature:; FeatureList.vue view; tags column added to tasks (Phase 1) |
| Share global context | ❌ → added global_contexts table, REST, MCP publish_global_context / list_global_contexts / read_global_context + MCP resources/read for global://<title> uris (Phase 3) |
| PM role | ❌ → added RolePM to models.AgentRole; AgentFormModal exposes a "Project Manager" select; PM-only MCP tools check role at dispatch (Phase 2 + 4) |
| A2A delegation wired into the mesh | partial → delegate_to_a2a_agent writes to a2a_tasks only; lands the result in global_contexts row tagged a2a:<name> (Phase 5) |
What the validation script proves
scripts/validate-mesh.sh runs 12 steps end-to-end against a fresh
stack (docker compose up -d). Each step has a single, deterministic
success criterion; the script exits non-zero on the first failure.
| # | Step | Asserts |
|---|---|---|
| 1 | GET /healthz |
HTTP 200 |
| 2 | POST /api/projects |
returned .id is a non-null UUID |
| 3 | POST /api/agents ×3 (pm, backend, frontend) |
each returned .id is a non-null UUID |
| 4 | POST /api/project_repos ×2 |
each response contains an "id" field |
| 5 | POST /api/milestones |
returned .id is a non-null UUID |
| 6 | POST /api/tasks with milestone_id and tags:["feature:auth"] |
the task is returned with the correct milestone_id and feature:auth is preserved in tags |
| 7 | POST /api/contexts tagged feature:auth; GET /api/contexts?tag=feature:auth |
the filter returns ≥ 1 row |
| 8 | POST /api/global_contexts scope=global from PM agent |
returned .id is a non-null UUID and shows up in GET /api/global_contexts?scope=global |
| 9 | MCP tools/list |
response contains: register_self, create_milestone, list_milestones, assign_task_to_milestone, publish_global_context, list_global_contexts, read_global_context |
| 10 | MCP tools/call register_self + create_task |
both return success payloads |
| 11 | A2A loopback (skipped at shell level — covered by Go integration tests TestA2ADelegationLandedAsGlobalContext) |
delegate_to_a2a_agent writes a global_contexts row tagged a2a:<agent-name> |
| 12 | WS smoke | opening ws://.../ws?project_id=... and then PUT /api/tasks/{id}/status produces a task_update event within 5 s |
What changed in this implementation
Backend
| File | What |
|---|---|
migrations/005_milestones.sql |
new milestones table; tasks.milestone_id FK; tasks.tags TEXT[] + GIN index |
migrations/006_project_repos.sql |
new project_repos table |
migrations/007_global_contexts.sql |
new global_contexts table with (scope,title) and (project_id,title) uniqueness |
internal/models/milestone.go |
Milestone, MilestoneStatus, request DTOs |
internal/models/project_repo.go |
ProjectRepo, request DTO |
internal/models/global_context.go |
GlobalContext, GlobalContextScope, request DTOs |
internal/models/agent.go |
added RolePM + IsPM() helper |
internal/models/task.go |
added MilestoneID, Tags fields |
internal/database/queries/{milestones,project_repos,global_contexts}.go |
typed query stores |
internal/database/queries/tasks.go |
added ClaimTask (atomic), CountMilestoneOpenTasks, CountMilestoneTasks; tags column threaded through |
internal/handlers/{milestones,project_repos,global_contexts}.go |
REST handlers; milestone close guarded by CountMilestoneOpenTasks |
internal/handlers/tasks.go |
persists milestone_id and tags on create; broadcasts task_added |
internal/validator/validator.go |
added ValidateCreateMilestoneRequest, ValidateCreateProjectRepoRequest, ValidateCreateGlobalContextRequest |
internal/httpx/errors.go |
recognises errMilestoneOpenTasks → 409 milestone_open_tasks; errGlobalContextConflict → 409 global_context_conflict |
internal/mcp/handler.go |
new MCP tools: register_self, create_milestone, list_milestones, assign_task_to_milestone, publish_global_context, list_global_contexts, read_global_context; isPMAgent() helper gates the PM-only ones; resources/list enumerates global://<title>; resources/read serves them as text/markdown; create_task schema gains milestone_id and tags |
internal/a2a/server/handler.go |
delegate_to_a2a_agent lands the artifact in global_contexts tagged a2a:<agent> |
cmd/server/main.go |
instantiates new stores + handlers; passes them into routeDeps |
cmd/server/routes.go |
registers /api/milestones, /api/project_repos, /api/global_contexts |
Frontend (Nuxt 4)
| File | What |
|---|---|
web/app/types/api.ts |
added Milestone, ProjectRepo, GlobalContext, MilestoneStatus, GlobalContextScope, ProjectRepoRole; AgentRole gains 'pm'; Task gains milestone_id and tags; CreateTaskInput gains both |
web/app/composables/useApi.ts |
CRUD helpers for milestones, project repos, global contexts |
web/app/stores/settings.ts |
added recentAgentId cookie for PM-role detection |
web/app/pages/projects/[id].vue |
adds Features, Milestones, Repos tabs; renders project-scoped global_contexts under Contexts; wires WS events for the new entity types |
web/app/pages/global.vue |
new — server-wide playbook console |
web/app/pages/documentation/index.vue |
new — surfaces global playbooks above the per-project list |
web/app/components/domain/MilestoneCard.vue |
progress meter, status selector, delete |
web/app/components/domain/MilestoneFormModal.vue |
create/edit with target date |
web/app/components/domain/ProjectRepoCard.vue |
URL + branch + role + owning agent |
web/app/components/domain/ProjectRepoFormModal.vue |
create with optional agent |
web/app/components/domain/GlobalContextCard.vue |
scope badge + tags + delete |
web/app/components/domain/GlobalContextFormModal.vue |
scope-aware publish (project/global) |
web/app/components/domain/FeatureList.vue |
derived per-feature rollup |
web/app/components/domain/TaskCard.vue |
renders tags + milestone id |
web/app/components/domain/AgentCard.vue |
highlights PM role badge |
web/app/components/domain/AgentFormModal.vue |
select now includes Project Manager |
web/app/components/domain/TaskFormModal.vue |
adds milestone + tags fields |
web/app/components/layout/AppSidebar.vue |
adds "Global" entry |
Tooling / docs
| File | What |
|---|---|
scripts/validate-mesh.sh |
the 12-step smoke test |
mcp-bridge.js |
header banner listing the new mesh-app tools |
docs/VALIDATION_REPORT.md |
this file |
How to re-run validation
# In a separate shell:
docker compose up -d
# (migrations 005/006/007 are applied automatically on server start.)
# Then:
./scripts/validate-mesh.sh
Expected exit code: 0. Output ends with All 12 validation steps passed.