Skip to content

Add project-level skills lock file (POC)#5715

Open
samuv wants to merge 20 commits into
mainfrom
poc-thv-skills-lock
Open

Add project-level skills lock file (POC)#5715
samuv wants to merge 20 commits into
mainfrom
poc-thv-skills-lock

Conversation

@samuv

@samuv samuv commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Why: Project-scoped skill installs had no reproducible pin, no integrity verification beyond content hashing, and no safe upgrade path when publisher identity changes. Teams could not commit a lock file and trust that sync/upgrade would restore or advance skills safely.
  • What: Introduce toolhive.lock.yaml for project scope with thv skill sync and thv skill upgrade, then fold RFC THV-0080 v1 Sigstore signing/verification into the same POC: verify on consume, sign on push, pin publisher identity (provenance) or record unsigned: true, and re-verify stored bundles offline in sync --check.

POC scope (RFC THV-0080)

This PR is a feasibility vertical slice, not production-hardening split across many small PRs yet. It proves:

Question How this PR answers it
Reproducible project installs? Lock pins source, resolvedReference, digest, contentDigest
On-disk tampering? contentDigest dirhash + sync --check
Adopt out-of-band installs? sync --adopt writes lock entries (with provenance or unsigned)
Transitive deps? toolhive.requires materialized; requiredBy + cascade uninstall
Safe upgrades? Digest re-resolve; immutable pins; --allow-ref-change; --allow-signer-change
Supply-chain trust? Sigstore verify on install/sync/upgrade; TOFU identity in lock; offline bundle re-check
Publisher signing? thv skill push signs with --key (cosign); --no-sign escape hatch
CI/scripting? Typed failure reasons, --yes, exit codes 2/3/4

Trust model (v1)

Three layers (see docs/arch/12-skills-system.md):

  1. Cryptographic — Fulcio + Rekor for OCI/gitsign; offline bundle re-verification in sync --check
  2. Publisher identity (TOFU)provenance.signerIdentity + provenance.certIssuer pinned at first verified install; signer-mismatch on divergence
  3. Human review — PR review of toolhive.lock.yaml; unsigned: true is an explicit reviewed exception

Deferred: keyless OIDC push (E2E against Sigstore staging); catalog-supplied expected identity (needs toolhive-core Provenance on Skill types).

Lock entry schema (v1)

version: 1
skills:
  - name: code-review
    version: "1.0.0"
    source: ghcr.io/org/code-review
    resolvedReference: ghcr.io/org/code-review:v1.2.0
    digest: sha256:abc...
    contentDigest: sha256:def...
    provenance:
      signerIdentity: https://github.com/org/repo/.github/workflows/release.yml@refs/heads/main
      certIssuer: https://token.actions.githubusercontent.com
      repositoryURI: https://github.com/org/repo
      sigstoreURL: https://rekor.sigstore.dev
    requiredBy: [parent-skill]
    explicit: true
  - name: local-build
    source: local-build
    digest: sha256:...
    contentDigest: sha256:...
    unsigned: true

CLI surface

thv skill sync--check, --adopt, --prune, --yes, --clients, --project-root

thv skill upgrade [name...]--preview, --fail-on-changes, --allow-ref-change, --allow-signer-change, --yes

thv skill install--allow-unsigned (project scope)

thv skill push--key, --no-sign

Exit codes: 0 success · 2 check/freshness failure · 3 partial failure · 4 policy rejection

Commit structure (19 commits, atomic by layer)

Lock file core

  1. Add project-level skills lock file with sync and upgrade commands
  2. Extend lockfile schema with contentDigest and validation
  3. Split SkillLockService from SkillService
  4. Add managed flag for lock-managed skill installs
  5. Harden install hooks and materialize toolhive.requires
  6. Implement RFC sync with check, adopt, and prune
  7. Implement RFC upgrade with preview and ref-change guard
  8. Wire lock service through API, client, and CLI
  9. Rename SyncResult UpToDate field to fix codespell
  10. Document skills lock file sync and upgrade behavior

Sigstore v1

  1. Add provenance and unsigned fields to skills lockfile
  2. Add Sigstore verifier and signer packages for skills
  3. Store verified Sigstore bundles on installed skills
  4. Verify Sigstore signatures during project-scope installs
  5. Re-verify stored bundles offline during skill sync
  6. Block skill upgrades on signer identity changes
  7. Sign OCI artifacts after skill push
  8. Expose Sigstore install and push flags in API and CLI
  9. Document skills lock Sigstore trust model and update tests

Type of change

  • New feature

Test plan

  • Unit tests (task testpkg/skills/... passes)
  • Linting (task lint-fix)
  • E2E against Sigstore staging (deferred)
  • Manual: signed OCI push + project install + sync --check

API compatibility

Additive: POST /skills/sync, POST /skills/upgrade; new request fields (allowUnsigned, allowSignerChange, key, skipSigning). SkillLockService is separate from SkillService. No operator CRD changes.

Large PR justification

~6.6k lines / 74 files. Kept unified for POC feasibility: lock integrity, sync/upgrade guardrails, and Sigstore trust are coupled — splitting mid-stack leaves non-functional intermediate states. If validated, split into the stack below for production merge.

Review focus

  1. Is contentDigest the right integrity primitive?
  2. Are cascade uninstall semantics (requiredBy / explicit) correct?
  3. Is TOFU provenance the right default until catalog trust exists?
  4. Is offline bundle re-verification in --check sufficient?
  5. Is SkillLockService the right API boundary?

Does this introduce a user-facing change?

Yes — new sync/upgrade commands, lock file on project install, Sigstore flags, push signing.

@github-actions github-actions Bot added the size/XL Extra large PR: 1000+ lines changed label Jul 3, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Large PR Detected

This PR exceeds 1000 lines of changes and requires justification before it can be reviewed.

How to unblock this PR:

Add a section to your PR description with the following format:

## Large PR Justification

[Explain why this PR must be large, such as:]
- Generated code that cannot be split
- Large refactoring that must be atomic
- Multiple related changes that would break if separated
- Migration or data transformation

Alternative:

Consider splitting this PR into smaller, focused changes (< 1000 lines each) for easier review and reduced risk.

See our Contributing Guidelines for more details.


This review will be automatically dismissed once you add the justification section.

@JAORMX

JAORMX commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Love it! Let's talk on Monday about this. Shall we have an RFC too? Just for docs

Comment thread pkg/skills/lockfile/lockfile.go Dismissed
Comment thread pkg/skills/lockfile/lockfile.go Dismissed
Comment thread pkg/skills/lockfile/lockfile.go Dismissed
Comment thread pkg/skills/lockfile/lockfile.go Dismissed
Comment thread pkg/skills/lockfile/lockfile.go Dismissed
@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 29.44855% with 1241 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.75%. Comparing base (9662681) to head (18d7dd1).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
pkg/skills/verifier/oci_bundle.go 0.00% 181 Missing ⚠️
pkg/skills/skillsvc/sync.go 25.32% 160 Missing and 11 partials ⚠️
pkg/skills/skillsvc/upgrade.go 35.09% 116 Missing and 19 partials ⚠️
pkg/skills/skillsvc/verify.go 23.80% 104 Missing and 8 partials ⚠️
pkg/skills/skillsvc/lock.go 13.08% 91 Missing and 2 partials ⚠️
pkg/skills/signer/cosign_attach.go 0.00% 50 Missing ⚠️
pkg/skills/signer/key.go 0.00% 45 Missing ⚠️
pkg/skills/signer/signer.go 0.00% 43 Missing ⚠️
pkg/skills/verifier/git.go 0.00% 36 Missing ⚠️
pkg/skills/verifier/oci.go 0.00% 32 Missing ⚠️
... and 21 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5715      +/-   ##
==========================================
- Coverage   70.74%   69.75%   -1.00%     
==========================================
  Files         683      704      +21     
  Lines       69194    70884    +1690     
==========================================
+ Hits        48950    49443     +493     
- Misses      16663    17741    +1078     
- Partials     3581     3700     +119     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@samuv

samuv commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Love it! Let's talk on Monday about this. Shall we have an RFC too? Just for docs

Yeah 🚀 , I opened this RFC: stacklok/toolhive-rfcs#80. I just refined it a bit.

@samuv samuv self-assigned this Jul 7, 2026
@github-actions github-actions Bot dismissed their stale review July 7, 2026 10:03

Large PR justification has been provided. Thank you!

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

✅ Large PR justification has been provided. The size review has been dismissed and this PR can now proceed with normal review.

@github-actions github-actions Bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Jul 7, 2026
@samuv samuv force-pushed the poc-thv-skills-lock branch from dafb744 to ae2f41a Compare July 7, 2026 10:16
@github-actions github-actions Bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Jul 7, 2026
Comment thread pkg/skills/signer/key.go Dismissed
Comment thread pkg/skills/signer/signer.go Dismissed
Introduce toolhive.lock.yaml, committed at the project root, that pins the
name, version, source, resolved reference, and digest of every project-scoped
skill install. This gives teams reproducible skill installs (restorable via
"thv skill sync") and a path to refresh pinned content when the catalog moves
("thv skill upgrade"), mirroring the role package-lock.json plays for npm.

- New pkg/skills/lockfile package: schema, load/save, and file-locked
  upsert/remove using pkg/fileutils.WithFileLock; entries sorted by name for
  stable diffs.
- skillsvc install/uninstall hooks: project-scope installs upsert a lock
  entry (recording the original source string for later re-resolution);
  uninstalls remove it. User-scope installs never touch the lock.
- Sync: installs each entry strictly at its pinned resolvedReference@digest,
  reports unmanaged project-scope skills, and prunes them with --prune.
- Upgrade: re-resolves each entry's original source, compares digests, and
  installs + rewrites the entry on change; immutable sources (OCI digest,
  git commit) reported as not-upgradable; --dry-run supported.
- API: POST /skills/sync and POST /skills/upgrade plus skills client methods.
- CLI: thv skill sync and thv skill upgrade with git-root auto-detection and
  --project-root override; JSON/text output.
- Docs: regenerated CLI/swagger docs; updated docs/arch/12-skills-system.md
  with a Project Lock File section, diagram, and endpoint/CLI tables.

Co-authored-by: Cursor <cursoragent@cursor.com>
samuv and others added 18 commits July 8, 2026 16:29
Add requiredBy and explicit fields, load-time validation, and a
deterministic dirhash helper for on-disk integrity verification.

Co-authored-by: Cursor <cursoragent@cursor.com>
Move sync and upgrade behind a dedicated lock service interface with
typed options, results, and regenerated mocks.

Co-authored-by: Cursor <cursoragent@cursor.com>
Track whether a skill was installed by the lock file so sync and
uninstall can distinguish managed from ad-hoc installations.

Co-authored-by: Cursor <cursoragent@cursor.com>
Fail on lock write errors, record content digests, recursively install
transitive dependencies, and cascade uninstall orphaned lock entries.

Co-authored-by: Cursor <cursoragent@cursor.com>
Re-hash content digests, reinstall drifted skills, harden pruning,
and support dry-run check plus divergent client directory adoption.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add preview mode, fail-on-changes, allow-ref-change, and typed failure
reasons for blocked reference changes and content drift.

Co-authored-by: Cursor <cursoragent@cursor.com>
Expose sync and upgrade on the lock service with typed request bodies,
pre-install confirmation gates, and exit codes 2/3/4 for partial failure.

Co-authored-by: Cursor <cursoragent@cursor.com>
Update architecture notes and regenerate CLI and swagger docs for new
flags, exit codes, and lock file integrity semantics.

Co-authored-by: Cursor <cursoragent@cursor.com>
Codespell flags UpToDate as a misspelling; rename the Go field to
AlreadyCurrent while keeping the JSON key up_to_date unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@samuv samuv force-pushed the poc-thv-skills-lock branch from 661d209 to 1f1db15 Compare July 8, 2026 14:30
@github-actions github-actions Bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Jul 8, 2026
CI has no cosign key or keyless OIDC configured, so the push-then-
install E2E test failed once push started signing by default.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions github-actions Bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Jul 8, 2026
@samuv

samuv commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Proposed split for easier review

Of course this PR is not something mergeable as-is — it bundles two logically separate efforts (~6.6k lines / 74 files) into one diff: the project-scope skills lock file (RFC THV-0080 POC) and Sigstore signing/verification v1 on top of it.

The idea is to split the work into 14 smaller PRs, stacked across two dependency chains, so each one is independently reviewable, buildable, and testable (task test, task lint) on its own — instead of asking anyone to review this in one pass.

Stack 1 — Lock file POC (6 PRs, ~4.5k LOC)

PR1 Lockfile package
  → PR2 Service split + storage
    → PR3 Install/uninstall hooks
      → PR4 Sync
        → PR5 Upgrade
          → PR6 API + CLI + docs
PR Scope ~Size
1 pkg/skills/lockfile package: schema, contentDigest dirhash, validation ~350 LOC
2 Split SkillLockService from SkillService, managed flag (migration 003) ~200 LOC
3 Wire install/uninstall to lock file, toolhive.requires materialization, cascade uninstall ~550 LOC
4 Implement sync (--check, --adopt, --prune) ~320 LOC
5 Implement upgrade (--preview, --allow-ref-change) ~190 LOC
6 Wire sync/upgrade into API + CLI, typed exit codes, docs ~450 LOC

Stack 2 — Sigstore v1 (8 PRs, ~2.1k LOC, based on main after Stack 1 merges)

PR7 Lock provenance schema
  → PR8 Verifier package (+ go.mod)
    → PR9 Signer package
      → PR10 Bundle storage + git signature plumbing
        → PR11 Install verification (incl. test updates)
          → PR12 Sync offline verify
            → PR13 Upgrade signer guard
              → PR14 Push signing + CLI/API flags + docs
PR Scope ~Size
7 Add provenance/unsigned fields to lock schema ~100 LOC
8 pkg/skills/verifier package (OCI referrer, gitsign, offline bundle verify) + go.mod ~860 LOC (~150 generated mock)
9 pkg/skills/signer package (key-based signing, Cosign referrer attach) ~330 LOC
10 Persist Sigstore bundle (migration 004) + expose git commit signature ~90 LOC
11 Verify signatures on project-scope install, TOFU identity recording ~480 LOC
12 Offline bundle re-verification in sync --check ~50 LOC
13 Block upgrades on signer identity change, --allow-signer-change ~100 LOC
14 Sign on push (--key/--no-sign), CLI/API flags, trust-model docs ~260 LOC

Notes

  • This is a rewrite-by-file, not a cherry-pick of the existing 19 commits — a couple of the original commits (the initial lock POC commit and the install-verification commit) mix concerns that need to be separated across PRs, and one test fix needs to move earlier so each PR is green on its own.
  • go.mod/go.sum (new sigstore/sigstore-go deps) land in PR8; generated docs land in PR6 and PR14 (the two PRs that change API/CLI surface).
  • PR11 is the largest and highest-risk PR in Stack 2 (core signature-enforcement logic) — flagging it now for extra reviewer attention.

If this split makes sense, I'll open the branch chain (skills-lock/01-lockfile → … → skills-lock/14-sigstore-cli) and close this PR in favor of the stack, linking back here for context. Let me know if you'd prefer a different grouping (e.g. fewer, larger PRs) before I start splitting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XL Extra large PR: 1000+ lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants