diff --git a/.taskmaster/tasks/tasks.json b/.taskmaster/tasks/tasks.json index 40f3167..7c19711 100644 --- a/.taskmaster/tasks/tasks.json +++ b/.taskmaster/tasks/tasks.json @@ -3261,6 +3261,146 @@ "status": "done", "subtasks": [], "updatedAt": "2026-08-08T15:42:14.594Z" + }, + { + "id": "164", + "title": "RBAC enforcement tests for the agentic-teams resources", + "description": "The permissions surface changed (5 new resources + the tasks namespace split) with no test proving the gating actually works.", + "details": "Verified missing: no test references `epics` or `gitproviders` outside the backfill test.\n\n- Assert mapUrlToPermission returns the right resource+action for every new route, including the non-CRUD verbs (transition/accept/reject → run:tasks, import/probe → run:gitproviders — note these are NOT currently special-cased and default to create, which may be wrong).\n- Assert a caller with only view:epics cannot POST /api/v1/epics.\n- Assert name-scoped grants (view:epics:) resolve through nameResolvers.\n- Assert list filtering via getAllowedScope actually narrows /api/v1/epics.\n- Assert a pre-split view:tasks grant authorizes /api/v1/inference-tasks after the backfill and NOT /api/v1/tasks.", + "testStrategy": "src/mcpd/tests/rbac-agentic-teams.test.ts, modelled on rbac-name-scope-integration.test.ts.", + "priority": "high", + "dependencies": [ + "134" + ], + "status": "pending", + "subtasks": [], + "updatedAt": "2026-08-09T19:36:34.052Z" + }, + { + "id": "165", + "title": "Back up the agentic-teams resources (silent data loss today)", + "description": "BACKUP_KINDS does not include epics/epictasks/tasks/repos/gitproviders, so git-backup never captures them and a restore loses all of it.", + "details": "Verified: BACKUP_KINDS = [server, secret, project, user, group, rbac, prompt, skill, template].\n\n- Add the new kinds to BACKUP_KINDS + APPLY_ORDER (dependency order: gitprovider → repo → epic → epictask → task) + kindMap in yaml-serializer.ts, plus the export loop and serializeResource in git-backup.service.ts.\n- Wire main.ts kindFromSegment / importResource / deleteResource.\n- PRE-EXISTING BUG found while checking: `skill` is missing from importResource and deleteResource in main.ts, so skills are exported to git but never restored from it. Fix in the same pass.\n- Credentials must NOT be serialized: back up credRef (secret name + key), never the resolved value.", + "testStrategy": "Extend src/mcpd/tests/yaml-serializer.test.ts; assert a round-trip export→import for each new kind and that no serialized blob contains a resolved secret value.", + "priority": "high", + "dependencies": [ + "135" + ], + "status": "pending", + "subtasks": [], + "updatedAt": "2026-08-09T19:36:34.052Z" + }, + { + "id": "166", + "title": "Make AgentProject real — chat still uses the legacy Agent.projectId", + "description": "The AgentProject junction has existed since the data-model commit but nothing reads it: chat.service resolves context from the single Agent.projectId scalar, so an agent still cannot meaningfully join many projects.", + "details": "Verified: 0 references to agentProject in chat.service.ts.\n\n- Routes to join/leave a project with a role (task 152).\n- prepareContext resolves the project from the membership when the caller names one, falling back to the scalar during the transition release.\n- `mcpctl chat --project ` picks the membership; error clearly when the agent is not a member.\n- Per-membership llmId override honoured (this is the brain-ownership question, §15 Q1).\n- Only after that: drop Agent.projectId.", + "testStrategy": "Service tests for membership resolution + a smoke test where one agent is used from two projects and gets different context in each.", + "priority": "high", + "dependencies": [ + "120", + "152" + ], + "status": "pending", + "subtasks": [], + "updatedAt": "2026-08-09T19:36:34.052Z" + }, + { + "id": "167", + "title": "describe formatters for the agentic-teams resources", + "description": "epics/epictasks/tasks/repos/gitproviders fall through to the generic key-value dump, which breaks the project rule that describe shows clean sections and tables.", + "details": "Verified: no case for these in describe.ts.\n\n- describe epic: vision, done-criteria table with met/unmet, accepted tradeoffs, and the roadmap (nodes + per-node completeness + which are ready) from GET /epics/:id/roadmap — the API exists and has no client.\n- describe task: state, legal transitions, risk + which gates it still needs, PR link, verdicts, transition history.\n- describe repo / gitprovider: credential REFERENCE only, never a resolved value.", + "testStrategy": "src/cli/tests/commands/describe.test.ts cases per resource; assert no secret value ever appears in output.", + "priority": "medium", + "dependencies": [ + "136" + ], + "status": "pending", + "subtasks": [], + "updatedAt": "2026-08-09T19:36:34.052Z" + }, + { + "id": "168", + "title": "Docs for the agentic-teams resources + the tasks rename note", + "description": "No docs/*.md for the new resources, and the breaking `mcpctl get tasks` rename is recorded only in a commit message.", + "details": "- docs/epics.md, docs/tasks.md, docs/repos.md, docs/gitproviders.md following the house shape (Model / CLI / RBAC / See also).\n- README Resources table (already stale — missing skill/proposal/revision too) and the CLAUDE.md resource cheatsheet.\n- Call out prominently that `mcpctl get tasks` now means WORK ITEMS and the inference queue moved to inference-tasks/itasks.\n- Document the `mcpctl` system agent and its permission model (acts as the caller, loopback through the API).", + "testStrategy": "Manual read-through; assert every documented command actually exists by running --help.", + "priority": "medium", + "dependencies": [ + "154" + ], + "status": "pending", + "subtasks": [], + "updatedAt": "2026-08-09T19:36:34.052Z" + }, + { + "id": "169", + "title": "Smoke tests for gitprovider discovery and import", + "description": "The agentic-teams smoke covers epic/epictask/task but not the forge flow, which is the part with a live external dependency.", + "details": "Register a provider against a reachable Gitea, discover, import, assert provenance (sourceProvider/sourceFullName), assert re-discovery marks imported, assert import of an unknown name is skipped with a reason rather than failing the batch. Skip cleanly when no forge credential is configured.", + "testStrategy": "src/mcplocal/tests/smoke/gitprovider.smoke.test.ts", + "priority": "medium", + "dependencies": [ + "133" + ], + "status": "pending", + "subtasks": [], + "updatedAt": "2026-08-09T19:36:34.052Z" + }, + { + "id": "170", + "title": "Reconcile Epic completeness against delivered work", + "description": "Epic completeness counts only its own doneCriteria, which nobody flips automatically. An Epic can read 0% while every task under it is merged — the exact blind spot the Epic exists to remove.", + "details": "EpicService.completeness reads epic.doneCriteria and nothing else; there is no path from task state → epictask criteria → epic criteria.\n\n- Surface delivered-vs-claimed: for an Epic, how many of its tasks are merged vs how many criteria are marked met, and flag the mismatch in both directions (work delivered but unclaimed; criteria claimed with no delivering task).\n- This is the mechanical half of the Tier-2 comparison (task 158) and is worth having before the critic persona exists.", + "testStrategy": "Service tests over a fixture epic with a mix of merged/open tasks and met/unmet criteria; assert both mismatch directions are reported.", + "priority": "medium", + "dependencies": [ + "131", + "126" + ], + "status": "pending", + "subtasks": [], + "updatedAt": "2026-08-09T19:36:34.052Z" + }, + { + "id": "171", + "title": "End-to-end test of `mcpctl chat mcpctl` through mcplocal", + "description": "The mcpctl system agent tool layer is verified live, but the full path — CLI → mcplocal → mcpd → LLM picks a tool → loopback — has never been run.", + "details": "The tool provider and its permission model are unit-tested and were exercised directly against a live mcpd. What is untested is the LLM actually selecting an mcpctl tool, and the token surviving the mcplocal hop (mcplocal forwards Authorization; unverified for this path).\n\nNeeds a real model, so it belongs in smoke rather than unit tests. Assert: a request like the original example creates the prompt; a user without permission gets a reported 403 rather than a retry loop.", + "testStrategy": "src/mcplocal/tests/smoke/mcpctl-agent.smoke.test.ts using the ChatReporter helper.", + "priority": "medium", + "dependencies": [ + "163" + ], + "status": "pending", + "subtasks": [], + "updatedAt": "2026-08-09T19:36:34.052Z" + }, + { + "id": "172", + "title": "Release: nothing from the agentic-teams branch is live", + "description": "Every change on feat/agentic-teams has been verified against a locally-run mcpd on the test database. The deployed mcpd and the installed mcpctl/mcplocal have none of it.", + "details": "- mcpd needs a k8s deploy (scripts/deploy-k8s.sh) before any API or the system agent exists in the real environment.\n- mcplocal needs an RPM rebuild for the 30s proxy timeout fix (da4752b); the fix is committed but the running daemon predates it. Restart drops in-memory MCP sessions.\n- The rbac-tasks-backfill runs once on the first upgraded start: verify the log line and spot-check that pre-split grants moved to inference-tasks.\n- The RBAC smoke test (rbac-tasks-split.smoke.test.ts) is RED until this lands, by design.", + "testStrategy": "After deploy: pnpm test:smoke should go green for rbac-tasks-split and agentic-teams.", + "priority": "high", + "dependencies": [], + "status": "pending", + "subtasks": [], + "updatedAt": "2026-08-09T19:36:34.052Z" + }, + { + "id": "173", + "title": "Web UI and Pulumi kinds for the agentic-teams resources", + "description": "src/web has no pages for the new resources, and src/pulumi/src/kinds.ts has no mapping (it falls back gracefully, so this is cosmetic).", + "details": "Lowest priority of the gap set — the CLI is the primary surface and it is complete.", + "testStrategy": "Existing web test patterns.", + "priority": "low", + "dependencies": [ + "136" + ], + "status": "pending", + "subtasks": [], + "updatedAt": "2026-08-09T19:36:34.052Z" } ], "metadata": { @@ -3273,4 +3413,4 @@ ] } } -} \ No newline at end of file +} diff --git a/src/mcpd/src/main.ts b/src/mcpd/src/main.ts index 06f67f6..58cd157 100644 --- a/src/mcpd/src/main.ts +++ b/src/mcpd/src/main.ts @@ -869,6 +869,9 @@ async function main(): Promise { case 'group': await groupService.upsertByName(data); break; case 'rbac': await rbacDefinitionService.upsertByName(data); break; case 'prompt': await promptService.upsertByName(data); break; + // Skills were exported and enqueued but never restored, so a git restore + // silently dropped every skill. SkillService always had the method. + case 'skill': await skillService.upsertByName(data); break; case 'template': await templateService.upsertByName(data); break; } }; @@ -881,6 +884,7 @@ async function main(): Promise { case 'group': await groupService.deleteByName(name); break; case 'rbac': await rbacDefinitionService.deleteByName(name); break; case 'prompt': await promptService.deleteByName(name); break; + case 'skill': await skillService.deleteByName(name); break; case 'template': await templateService.deleteByName(name); break; } };