feat(mcpd): ResourceRevision + ResourceProposal services + Prompt revision integration
Phase 2 of the Skills + Revisions + Proposals work. Stands up the generic
revision/proposal layer and wires Prompt into it. Skills will plug into the
same infrastructure in PR-3 with no further service changes required.
This PR is intentionally additive: PromptRequest table and routes are
unchanged. The /api/v1/proposals API runs side-by-side with the legacy
/api/v1/promptrequests API. The PromptRequest cutover (rename + backfill +
mcplocal rewire) is deferred to a later PR so this one stays reviewable.
## What's added
### Repositories (src/mcpd/src/repositories/)
- resource-revision.repository.ts — append-only revision log keyed by
(resourceType, resourceId). Soft FK; no relations declared. Supports
history listing, semver lookup, and contentHash cross-resource search.
- resource-proposal.repository.ts — generic propose queue. Status lifecycle
pending → approved | rejected. Mirrors Prompt's `?? ''` workaround for
nullable-FK compound lookups.
### Services (src/mcpd/src/services/)
- resource-revision.service.ts — record() inserts a revision with a stable
sha256 contentHash computed from canonicalised JSON (key-sorted at every
level so reordered objects produce the same hash). Caller passes a
pre-computed semver; service does NOT decide bump policy.
- resource-proposal.service.ts — propose / approve / reject / list, with a
per-resourceType handler registry. PromptService registers the 'prompt'
handler at construction; the SkillService will register 'skill' in PR-3.
approve() runs in a Prisma $transaction so the resource update + revision
insert + proposal status flip are atomic.
### Pure utility (src/mcpd/src/utils/semver.ts)
- bumpSemver(current, kind) for major / minor / patch
- compareSemver(a, b) — numeric, not lex (10 > 9)
- isValidSemver(s)
- Invalid input falls back to '0.1.0' rather than throwing — keeps the
audit-write path from blowing up the prompt update if a row's semver
ever drifts out of MAJOR.MINOR.PATCH shape.
### Routes (src/mcpd/src/routes/)
- revisions.ts — GET /api/v1/revisions?resourceType=&resourceId=,
GET /api/v1/revisions/:id, GET /api/v1/revisions/:id/diff?against=<id|live>
(unified-format diff via the `diff` package), and POST
/api/v1/prompts/:id/restore-revision { revisionId, note? }.
- proposals.ts — GET / POST /api/v1/proposals,
GET /api/v1/proposals/:id, PUT for body updates, POST .../approve and
POST .../reject, plus DELETE.
## What's changed
- PromptService.create / update now record a ResourceRevision when the
revision service is wired. Update auto-bumps patch on content change;
authors can override via `--bump major|minor|patch` or `--semver X.Y.Z`
on the CLI (forwarded into the PUT body). Best-effort: revision write
failures are swallowed so the prompt save still succeeds (revision is
audit, not source of truth).
- PromptService.setProposalService registers a 'prompt' approval handler
with the proposal service. Approval runs in a Prisma transaction:
upsert prompt → record revision → update currentRevisionId → flip
proposal status. semver bumps to 0.1.0 on first approval, patch
thereafter.
- New CLI flags on `mcpctl edit prompt`: --bump, --semver, --note. They're
prompt-only (validated client-side); other resources reject them.
- Aliases in shared.ts: `proposal`/`prop` → proposals,
`revision`/`rev` → revisions.
- diff dependency added to mcpd.
## Tests
- src/mcpd/tests/utils/semver.test.ts — covers bump/compare/validate
including numeric (not lex) semver compare and invalid-input fallback.
- prompt-service.test.ts updated: makePrompt fixture now sets semver +
agentId + currentRevisionId; updatePrompt assertion expects the
auto-bumped patch in the same update call.
- prompt-routes.test.ts updated symmetrically.
## RBAC
`proposals` and `revisions` URL segments map to the existing `prompts`
permission for now. PR-7 may split if a "reviewer" role becomes useful.
## Verification
Full suite: 158 test files / 2127 tests green.
`pnpm build` clean across all 6 workspace packages.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
17
pnpm-lock.yaml
generated
17
pnpm-lock.yaml
generated
@@ -130,6 +130,9 @@ importers:
|
||||
bcrypt:
|
||||
specifier: ^5.1.1
|
||||
version: 5.1.1
|
||||
diff:
|
||||
specifier: ^5.2.0
|
||||
version: 5.2.2
|
||||
dockerode:
|
||||
specifier: ^4.0.9
|
||||
version: 4.0.9
|
||||
@@ -146,6 +149,9 @@ importers:
|
||||
'@types/bcrypt':
|
||||
specifier: ^5.0.2
|
||||
version: 5.0.2
|
||||
'@types/diff':
|
||||
specifier: ^5.2.3
|
||||
version: 5.2.3
|
||||
'@types/dockerode':
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1
|
||||
@@ -1095,6 +1101,9 @@ packages:
|
||||
'@types/deep-eql@4.0.2':
|
||||
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
|
||||
|
||||
'@types/diff@5.2.3':
|
||||
resolution: {integrity: sha512-K0Oqlrq3kQMaO2RhfrNQX5trmt+XLyom88zS0u84nnIcLvFnRUMRRHmrGny5GSM+kNO9IZLARsdQHDzkhAgmrQ==}
|
||||
|
||||
'@types/diff@8.0.0':
|
||||
resolution: {integrity: sha512-o7jqJM04gfaYrdCecCVMbZhNdG6T1MHg/oQoRFdERLV+4d+V7FijhiEAbFu0Usww84Yijk9yH58U4Jk4HbtzZw==}
|
||||
deprecated: This is a stub types definition. diff provides its own type definitions, so you do not need this installed.
|
||||
@@ -1680,6 +1689,10 @@ packages:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
diff@5.2.2:
|
||||
resolution: {integrity: sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==}
|
||||
engines: {node: '>=0.3.1'}
|
||||
|
||||
diff@8.0.3:
|
||||
resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==}
|
||||
engines: {node: '>=0.3.1'}
|
||||
@@ -4092,6 +4105,8 @@ snapshots:
|
||||
|
||||
'@types/deep-eql@4.0.2': {}
|
||||
|
||||
'@types/diff@5.2.3': {}
|
||||
|
||||
'@types/diff@8.0.0':
|
||||
dependencies:
|
||||
diff: 8.0.3
|
||||
@@ -4689,6 +4704,8 @@ snapshots:
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
|
||||
diff@5.2.2: {}
|
||||
|
||||
diff@8.0.3: {}
|
||||
|
||||
docker-modem@5.0.6:
|
||||
|
||||
Reference in New Issue
Block a user