feat(appstore): view detail command + catalogue v2 metadata#255
Merged
Conversation
Adds `pilotctl appstore view <id>` — a human-app-store-style detail page that merges three provenance-labelled bands: the catalogue teaser, a sha-pinned per-app detail doc, and verified local install facts (integrity, size, granted permissions, methods). Works installed or not, catalogued or sideloaded; `--json` emits the merged report and `--all-changelog` shows full history. Introduces catalogue schema v2: optional teaser fields on each index entry plus a `metadata_url` + `metadata_sha256` pin to `catalogue/apps/<id>/metadata.json`, fetched lazily by `view` and verified the same way bundle tarballs are. v1 catalogues still load unchanged and an older pilotctl ignores the new fields, so the bump is backward and forward compatible. The detail schema reserves a `reviews` slot for a future signed reviews service (not implemented here). - cmd/pilotctl/appstore_catalogue.go: v2 entry fields, accept versions 1 and 2, enrich the catalogue listing (vendor/categories/license/size + view hint) - cmd/pilotctl/appstore_metadata.go: appMetadata schema + sha-pinned loader - cmd/pilotctl/appstore_view.go: the view command, band merge, renderer - cmd/pilotctl/appstore.go: dispatch + help wiring - catalogue: v2 catalogue.json + cosift/wallet metadata.json detail docs - catalogue/README.md: v2 index + detail schemas, publishing flow, trust model - scripts/smoke-test-appstore.sh: stage a v2 catalogue + assert view e2e - tests: loader versioning, sha pinning, band-merge precedence, JSON output
TeoSlayer
pushed a commit
that referenced
this pull request
Jun 16, 2026
…st fixtures The previous catalogue trust anchor's private key (publicKeyB64 = "5aCD92R0UoZ2lGW6PYZeRrDw63ZNBC5oJZxFB8RNOPQ=") was lost and is unrecoverable, so the committed catalogue.json.sig could never be regenerated to match an edited catalogue. This anchor existed only on this PR branch — not on main and not in any shipped binary — so rotating it now has zero installed-base impact. Changes: - Generate a fresh ed25519 release keypair (private key stored only outside the repo, never committed) and embed its public half as the new trust anchor: publicKeyB64 = "iHdBWayA/hYjkwUOZopTXY70qOlR90d6ii/hin0ZMdI=" - Re-sign catalogue/catalogue.json with the new key (catalogue/catalogue.json.sig), which now verifies fail-closed against the embedded key. - Fix the test fixtures #255 left unsigned: stageCatalogue now signs each fixture with a per-test ephemeral key (swapped into the embedded anchor for the test's duration via catalogtrust.SignWithEphemeralKey, restored on cleanup). Tests exercise REAL fail-closed verification against a VALID signature — the gate is not skipped, disabled, or weakened; the negative tests (missing-signature, tamper) still pass. Rebased onto origin/main, reconciling #255's v2 metadata schema with this branch's catalogue signing (kept the fail-closed sig gate in loadCatalogue, both `view` and `sign-catalogue` subcommands, and main's metadata pin discipline in catalogue/README.md). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
TeoSlayer
added a commit
that referenced
this pull request
Jun 16, 2026
* feat(appstore): sign the catalogue and verify it fail-closed Replace the placeholder catalogue trust anchor with a real embedded ed25519 key (internal/catalogtrust, ldflags-overridable for rotation): - pilotctl loadCatalogue fetches a detached <url>.sig and verifies it against the embedded key before trusting any entry; unsigned, missing, or tampered catalogues are refused. - New 'pilotctl appstore sign-catalogue' writes the detached signature and refuses to sign with a key that doesn't match the embedded public key. - cmd/daemon passes the embedded key into appstore.Config.CatalogPubkey, retiring the all-zeros placeholder at the wire-up point. - Sign catalogue.json; document signing + key rotation in catalogue README. Tests: catalogtrust verify (happy/tamper/short/no-key), signed-catalogue load + fail-closed (missing sig, tamper). * feat(catalogue): static site rendering the live catalogue Dependency-free page (catalogue/site/index.html) that fetches catalogue.json same-origin and renders one card per app (id, version, description, install command, bundle link; methods when an entry provides them). The Pages deploy workflow is added separately (requires workflow scope on the push token). * fix(catalogue): rotate signing key + re-sign after #255; sign #255 test fixtures The previous catalogue trust anchor's private key (publicKeyB64 = "5aCD92R0UoZ2lGW6PYZeRrDw63ZNBC5oJZxFB8RNOPQ=") was lost and is unrecoverable, so the committed catalogue.json.sig could never be regenerated to match an edited catalogue. This anchor existed only on this PR branch — not on main and not in any shipped binary — so rotating it now has zero installed-base impact. Changes: - Generate a fresh ed25519 release keypair (private key stored only outside the repo, never committed) and embed its public half as the new trust anchor: publicKeyB64 = "iHdBWayA/hYjkwUOZopTXY70qOlR90d6ii/hin0ZMdI=" - Re-sign catalogue/catalogue.json with the new key (catalogue/catalogue.json.sig), which now verifies fail-closed against the embedded key. - Fix the test fixtures #255 left unsigned: stageCatalogue now signs each fixture with a per-test ephemeral key (swapped into the embedded anchor for the test's duration via catalogtrust.SignWithEphemeralKey, restored on cleanup). Tests exercise REAL fail-closed verification against a VALID signature — the gate is not skipped, disabled, or weakened; the negative tests (missing-signature, tamper) still pass. Rebased onto origin/main, reconciling #255's v2 metadata schema with this branch's catalogue signing (kept the fail-closed sig gate in loadCatalogue, both `view` and `sign-catalogue` subcommands, and main's metadata pin discipline in catalogue/README.md). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Teodor Calin <teodor@vulturelabs.io> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
pilotctl appstore view <id>— a detail page for app-store apps, the way a human app store shows one. Today the catalogue only carriesid,version, a one-line description, and the bundle pin; there is nowhere to see a structured description, vendor, changelog, size, source URL, or methods before installing. This adds that.viewmerges three provenance-labelled bands:It works whether or not the app is installed, and whether or not it is in the catalogue (a sideloaded app renders from local facts alone).
--jsonemits the merged report;--all-changelogshows full history. The publisher copy and the verified install facts are kept visually distinct so they are never conflated.Catalogue schema v2
The catalogue index gains optional teaser fields (
display_name,vendor,categories,bundle_size,source_url,license) and ametadata_url+metadata_sha256pin to a per-appcatalogue/apps/<id>/metadata.jsondetail document. The detail doc is fetched lazily byviewand verified against the pin exactly the way bundle tarballs are — the same CDN-substitution defence.The detail schema reserves a
reviewsslot for a future signed reviews service; it is parsed but never written here.Backward compatibility
loadCataloguenow accepts versions 1 and 2; all v2 fields areomitempty.install,status,list, or any existing output shape. The catalogue listing gains a teaser line and aview:hint only when v2 fields are present.Worked example
catalogue.jsonis migrated to v2 with real teaser data + metadata pins, andcatalogue/apps/{io.pilot.cosift,io.pilot.wallet}/metadata.jsonare populated from the real manifests, repo licenses, bundle sizes, and release notes.Tests
loadCatalogueaccepts v1/v2, rejects v3; v1 entries decode with empty v2 fieldsloadAppMetadatasha-pin verification (correct / mismatch / unpinned / id-mismatch)buildAppViewReportband-merge precedence (detail overrides teaser; real on-disk size wins; manifest-method fallback; sideloaded path)cmdAppStoreViewJSON output for the catalogue+metadata+installed casescripts/smoke-test-appstore.shextended to stage a v2 catalogue + detail doc and assertviewend-to-end (catalogue teaser + sha-verified detail + installed manifest)go test ./cmd/pilotctl/,go vet,gofmt -l, andgo build ./...all clean.How to QA / test before deploy
go build -o /tmp/pilotctl ./cmd/pilotctlmetadata_urltofile://so it resolves before merge) and runappstore catalogue+appstore view io.pilot.cosift.scripts/smoke-test-appstore.sh(needs the wallet repo at../wallet).Detailed QA steps are in the PR thread.