Add SkillLockService interface and managed install flag - #5893
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #5893 +/- ##
==========================================
- Coverage 71.84% 71.84% -0.01%
==========================================
Files 708 708
Lines 72811 72823 +12
==========================================
+ Hits 52311 52317 +6
- Misses 16765 16768 +3
- Partials 3735 3738 +3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
e124faf to
e07cfea
Compare
|
Tracked in #5899 (RFC THV-0080 stack). |
4997552 to
f0f5060
Compare
dcca019 to
ef4a99e
Compare
JAORMX
left a comment
There was a problem hiding this comment.
🤖 Automated panel review — PR 2/6 (SkillLockService + managed flag)
Multi-agent review, each finding adversarially verified against the diff. Advisory. Whole stack gated behind TOOLHIVE_SKILLS_LOCK_ENABLED (off by default).
API boundary verdict: splitting SkillLockService from SkillService is the right call and conforms to the RFC — no objection there.
🟡 Minor
- Three Sigstore
FailureReasonvalues ship with no producer (options.go:~160):signature-invalid,signer-mismatch,unsigned-rejected. Nothing in this lock-only stack can emit them (classifySyncFailureonly ever producesregistry-unreachable/digest-missing/validation-rejected/lock-write-failed/unknown). They're dead exported JSON-API constants pinned before the Sigstore design exists — if that stack refines the taxonomy (e.g. renamessigner-mismatch), it's now a breaking change. It's also internally inconsistent with this PR deliberately deferring the siblingsigner-change-blockedvalue. Gate them into the stack that produces them, or comment themreserved. FailureReasonRegistryUnreachableis OCI-registry-specific (options.go:~154) yet is the only transport-failure reason — git-source fetch failures have no accurate typed reason for the CI/automation consumers the enum exists for.InstalledSkill.Managedusesjson:",omitempty"(types.go:~170), so the newly-added user-facing field is omitted wheneverfalse— i.e. for every user-scope install and every unmanaged project install (the common case the PR says it exposes). Contradicts the swagger contract and hides thefalsestate from list/get consumers. Dropomitemptyfor a bool that's meant to be observable.UpgradeResult.Outcomeslacksomitemptyand is a slice (options.go:~258), so an empty result marshals to{"outcomes":null}rather than[]— mildly awkward for typed clients.
🟢 Verified praise
- Migration 003 is well covered: a test proves rows created pre-003 default to unmanaged, and
Downcleanly drops only themanagedcolumn while leaving 001/002 intact — plus an app-levelerrManagedRequiresProjectScopeguard enforcing managed-implies-project-scope with round-trip tests. These are the security-correct defaults.
ef4a99e to
611f480
Compare
|
All findings addressed in the latest push:
|
JAORMX
left a comment
There was a problem hiding this comment.
🤖 Automated panel re-review on 611f480 → approving
All findings from the earlier review are resolved against the actual code:
- ✅ Premature Sigstore
FailureReasonconstants removed (andref-change-blockeddemoted to aUpgradeStatus, not a reason); the type doc now states the "only reasons the current feature set can produce are defined" policy. - ✅
FailureReasonRegistryUnreachabledoc broadened to cover OCI and git remotes while keeping the RFC-pinned wire string. - ✅
InstalledSkill.Manageddroppedomitempty(with regenerated swagger) —falsenow always serializes. - ↩️
UpgradeResult.Outcomesnull-vs-[]nit reasonably deferred to producer PR #5896 (addingomitemptyhere wouldn't help; only a guaranteed non-nil slice at the producer yields[]).
Bonus: the new errManagedRequiresProjectScope invariant guard (rejecting managed on non-project installs, tested in Create + Update) is a net improvement. No regressions. LGTM.
0a45fce to
141d980
Compare
611f480 to
2e75912
Compare
2e75912 to
a72e34a
Compare
JAORMX
left a comment
There was a problem hiding this comment.
Panel review (spec / standards / security / library-reuse / duplication / golang-conventions / UX) — clean across the board.
Solid foundation: SkillLockService is cleanly separated from SkillService, the managed SQLite flag + migration 003 are correct (store enforces the project-scope invariant on Create and Update, migration test verifies the default), and the diff is genuinely lean.
Non-blocking notes for follow-up layers in the stack:
- Spec gap (deferral?): RFC THV-0080 lists a public
PreserveSourceinstall option alongsideSkillLockService, butInstallOptionsis untouched here. Assume it's coming in a later PR — flagging so it doesn't fall through. errManagedRequiresProjectScope(skill_store.go) is unexported, so the API layer can'terrors.Is-match it to a 400 vs 500. Fine if intentionally internal.- Migration test hardcodes
entry_id = 1— safe on a fresh DB, brittle if a pre-seeded fixture ever lands.
Approving.
RFC THV-0080's sync and upgrade commands operate over a project's
whole lock file rather than a single named skill, so they need
their own service boundary (SkillLockService) instead of extending
SkillService. Installed skills also need a durable "is this
tracked in the lock file" marker so sync --prune can distinguish
lock-managed installs from ones a user manages by hand.
- SkillLockService{Sync,Upgrade} interface with generated mock
- Sync/Upgrade option and result types, RFC's typed failure reasons
- installed_skills.managed column (migration 003)
Part of the production stack for #5715 (RFC THV-0080). Stack: 2/6.
No consumers yet — skillsvc wiring lands in PR3.
Managed is documented as pinning a skill in a project's lock file, but Create/Update wrote it unconditionally regardless of scope, and the existing round-trip test created a user-scoped skill with Managed: true. Reject the invalid combination in the store instead of only in a doc comment. Also fix a self-contradictory SyncResult doc comment and an inconsistent JSON tag on AlreadyCurrent.
Four typed failure reasons shipped in the JSON API with no code able to produce them: the three Sigstore verification reasons (deferred to the signing stack) and ref-change-blocked (which surfaces as an UpgradeStatus, not a failure). Pinning unproducible API constants now would make refining them later a breaking change — they land with their producers instead. Also drop omitempty from InstalledSkill's Managed flag so list/get consumers can observe the false state, and reword registry-unreachable's doc to cover git hosts, which map to the same reason.
a72e34a to
bae6917
Compare
Summary
syncandupgradecommands operate over a project's whole lock file, not a single named skill — a different shape thanSkillService's per-skill operations. Lock-managed installs also need a durable marker sosync --prunecan tell lock-managed skills apart from ones a user installed/manages by hand outside the lock file.SkillLockServiceinterface (Sync,Upgrade) with generated mock, kept separate fromSkillServiceper the RFC's API design.SyncOptions/SyncResult/SyncFailure,UpgradeOptions/UpgradeResult/UpgradeOutcome, and the RFC's typedFailureReasonenum (registry-unreachable,digest-missing,validation-rejected,lock-write-failed,ref-change-blocked,signature-invalid,signer-mismatch,unsigned-rejected,unknown).InstalledSkill.Managed+ migration 003 (installed_skills.managed), wired through the SQLite store.Part of the production stack for #5715 (RFC THV-0080). Stack: 2/6 — lockfile → lock-service → install-hooks → sync → upgrade → cli-exitcodes. Based on #5892 (PR1); no new consumers yet —
skillsvcwiring lands in PR3.Type of change
Test plan
task test)task lint-fix)Added: migration test proving 003 defaults existing rows to unmanaged and its Down cleanly drops the column without disturbing 001/002 tables; store-level round-trip test for
Managed(create, then flip viaUpdate).Does this introduce a user-facing change?
Yes —
InstalledSkillgains amanagedfield, visible in the existingGET/list skills API responses (swagger updated accordingly). No new endpoints yet.Special notes for reviewers
UpgradeOptions/UpgradeStatusintentionally omitAllowSignerChange/signer-change-blocked— those are Sigstore-stack concerns (PR13).Entry-levelprovenance/unsignedfields land in the Sigstore stack too (PR7).SyncResult.Unmanaged,UpgradeOptions.DryRun) — these are new APIs, shipped clean.🤖 Generated with Claude Code