feat(apigee-skills-serving): add skill-finder + skill-publisher + one-line provisioning#891
Open
gsjurseth wants to merge 7 commits into
Open
feat(apigee-skills-serving): add skill-finder + skill-publisher + one-line provisioning#891gsjurseth wants to merge 7 commits into
gsjurseth wants to merge 7 commits into
Conversation
demo-setup.sh had an ordering bug: it invoked
bin/check-prerequisites.sh at Step 1 (before the demo env vars
are exported at Step 2). check-prerequisites.sh reads
APIHUB_PROJECT, APIHUB_LOCATION, APIGEE_ORG, and
APIGEE_SKILLS_MIN_KEYWORD_OVERLAP directly from the process
environment. In a fresh shell (the common case -- operator has
just opened a terminal to start the demo), none of those are
set yet, so the checker fails 3/4 required prerequisites and
aborts, even though this script is self-configuring and would
work if allowed to run to completion.
Reordered:
1. Verify ADC (unchanged - fast, independent of env vars).
2. NEW: export demo env vars + write persistent config file.
3. NEW: run pre-flight check AFTER the export, so it sees the
demo values.
The check-prerequisites.sh call is preserved as the invariant
enforcer for an operator who prefers to source env.sh manually
-- it just no longer spuriously fails when the operator relied
on this script to configure the shell.
Verified in a synthetic empty-env shell via
env -i HOME=$HOME PATH=$PATH USER=$USER \
bash bin/demo-setup.sh
which now prints the READY banner instead of aborting on the
3/4 missing-env-vars check.
shellcheck on the modified file continues to PASS.
…s (multi-key trust)
Introduces two new agentic skills in
references/apigee-skills-serving/skills/:
- skill-finder: consumer-side discovery/install client that
queries the customer's API hub for signed skill bundles,
verifies their ed25519 signatures against locally-installed
trust roots, and installs them into the runtime's skills
directory (opencode / gemini-cli / antigravity). Ports the
16-line Hyrum's Law stdout contract from the standalone
gsjurseth/skill-finder repo.
- skill-publisher: author-side pipeline orchestrator that
shells out to the four existing publisher Python modules
(scripts/{pack,sign,upload,register}_skill.py) in order.
Both skills ship as **source only** -- no signed release
bundles, no key material, no trust-root fingerprints
committed to this repo. The trust model:
- skill-finder itself is trusted by provenance (the operator
fetched it from apigee/devrel over TLS). No signature is
verified on skill-finder because the code that would verify
it IS skill-finder.
- Payload skills that skill-finder installs (currency-converter,
weather-lookup, apigee-policy-top10, and any other skill
the customer registers) are trusted by signature. Every
downloaded manifest is verified against the trust roots in
~/.<runtime>/skills/skill-finder/keys/*.pem.
- Trust roots are per-deployment, generated fresh on each
customer's machine by the (forthcoming) bin/provision.sh
bootstrap script. No shared signing key crosses this repo.
Multi-key trust root implementation
-----------------------------------
skill-finder now loads EVERY PEM in keys/ as an accepted trust
root, not just a hardcoded keys/trusted_pubkey.pem. A downloaded
manifest is accepted if its signing_key_id matches ANY installed
trust root. This supports:
- Multi-publisher orgs (Alice's team's key + Bob's team's
key both trusted simultaneously).
- Zero-downtime key rotation (new key added, old key removed
later; new-signed and old-signed manifests both verify
during the overlap window).
- Per-project trust bootstrapping (drop a customer's fresh
ed25519 public key into keys/ during provisioning; skill-
finder trusts it from the next invocation onward).
Empty keys/ dir is a hard-fail with a clear operator-facing
diagnostic pointing at bin/provision.sh. Duplicate keys under
different filenames warn but proceed. Non-PEM/non-.raw files
under keys/ (README.md, .gitkeep) are skipped silently.
The LOCKED Hyrum's Law contract lines are preserved with a
targeted wording tweak:
before: "key-id check: trusted (matches embedded fingerprint)"
after: "key-id check: trusted (matches installed key <fp>)"
The change surfaces WHICH key matched, useful during rotation
troubleshooting. The "manifest signature: OK (ed25519 / <fp>)"
line is unchanged.
Files
-----
skills/skill-finder/
SKILL.md runtime instructions + dispatch
(opencode | gemini | antigravity)
keys/README.md operator doc: how to populate keys/
keys/.gitkeep keeps the dir tracked
manifest.yaml source manifest (publisher script
populates signature fields at
publish time)
scripts/find_install.py SEARCH + INSTALL mode (multi-key
verify loop; 16-line contract)
scripts/list_skills.py LIST mode (catalog browser)
skills/skill-publisher/
SKILL.md runtime instructions
manifest.yaml source manifest
scripts/publish.sh pack -> sign -> upload -> register
orchestrator
Verified end-to-end against a real API hub
------------------------------------------
Ran the modified find_install.py in three scenarios against the
geirs-spaces-demo project:
1. Happy path: one trust root installed, matches the manifest's
signing_key_id. All 16 contract lines print, exit 0.
2. Empty keys/ dir: emits the operator-facing "trust root:
FAILED" diagnostic and exits before any network call.
3. Wrong-key: one bogus key installed, no match. Emits the
"key-id check: FAILED — trusts: <list>" line and exits
with code 3 (integrity failure) before downloading anything.
4. Rotation scenario: two keys installed (one bogus, one real).
find_install.py picks the matching key and verifies
successfully. Confirms rotation-with-overlap works.
License headers
---------------
All Python and bash source files carry the Apache 2.0 header
matching devrel's convention. keys/README.md and manifest.yaml
files carry copyright comments.
Followups (separate commits in this PR)
---------------------------------------
- bin/install-skill-finder.sh and bin/install-skill-publisher.sh
(installer scripts adapted from gsjurseth/skill-finder v0.1.7)
- bin/provision.sh (net-new customer-bootstrap script that
generates the trust root, provisions the demo, and publishes
the initial skill catalog signed with the fresh key)
- docs/{trust-root.md,provisioning.md,releasing.md}
- README and publish-and-install.md updates
- tests for the multi-key verify loop
…stall-skill-publisher.sh Two new installer scripts under references/apigee-skills-serving/bin/ that let operators install the skill-finder and skill-publisher agentic skills into their agent runtime (opencode | gemini | antigravity) in one line: curl -fsSL https://raw.githubusercontent.com/apigee/devrel/main/\ references/apigee-skills-serving/bin/install-skill-finder.sh \ | bash -s -- --runtime gemini Trust model ----------- The installer scripts (and the skill-finder source they fetch) are trusted by provenance: TLS + the apigee GitHub organisation. There are no signed release bundles and no sha256 pins baked into the installers. This differs from a signed-release model (e.g. gsjurseth/skill-finder's v0.1.7 installer) but matches the "no signing key ever enters this repo" constraint: apigee/devrel does not have an organisation-level signing key, and adding one would require key-management governance that is out of scope for the reference implementation. Payload skills that skill-finder subsequently installs from a customer's API hub ARE cryptographically verified. Those signatures use per-deployment trust roots that live under ~/.<runtime>/skills/skill-finder/keys/ and are populated by bin/provision.sh (a subsequent commit in this PR) or manually. How the fetch works ------------------- Rather than depending on a `git` binary on the operator's machine, the installers fetch the entire devrel repository as a gzipped tarball from codeload.github.com and extract only the skill directory they need. Three URL forms are attempted in order (tag, branch, raw SHA) so --ref works for all three. This is more portable than `git clone --depth 1` and works on any machine with `curl` and `tar` in the toolchain. Design choices -------------- - Per-user venv at ~/.local/share/skill-finder/venv shared by both skills. The venv is the escape hatch for PEP 668 distros (Debian 12+, Ubuntu 23.04+, recent macOS Homebrew) where system `pip install` is refused. It also isolates skill deps from other Python projects on the operator's machine. - Per-install `bin/run-with-venv.sh` wrapper. SKILL.md invocations of the shipped scripts are rewritten at install time from `python3 ${SKILL_DIR}/scripts/X.py` to `${SKILL_DIR}/bin/run-with-venv.sh ${SKILL_DIR}/scripts/X.py` so the agent's shell tool always uses the venv even when the system python doesn't have cryptography / google-auth / requests / pyyaml. - scripts/common/ is copied into each skill's install at install time. skill-finder's scripts do `from common.X import Y` which only works if common/ is a sibling of the script. The source tree ships find_install.py and list_skills.py alone; the installer splices in the common package from the enclosing devrel checkout. - Runtime auto-detection: OpenCode -> Antigravity -> Gemini CLI -> OpenCode fallback. Operator can always override with --runtime. - Refuses to overwrite an existing install without --force. Empty keys/ is a hard-fail with a clear diagnostic pointing at bin/provision.sh. Verified end-to-end ------------------- Ran the following against gsjurseth/devrel at feat/add-skill-finder (the branch this PR will become): bash bin/install-skill-finder.sh \ --runtime gemini \ --repo gsjurseth/devrel \ --ref feat/add-skill-finder \ --force Result: fetched 10.2 MB tarball, extracted the two skill directories, created a fresh venv, installed 4 deps, wrote `bin/run-with-venv.sh` wrappers, rewrote both SKILL.md files to use the wrappers, atomic-mv'd both installs into place, printed the trust-root-required diagnostic. Then dropped a fingerprint.pem into keys/ and re-ran find_install.py against a live API hub: 16/16 contract lines printed, exit 0, apigee-policy-top10 installed successfully. shellcheck on both scripts: PASS.
Adds a single end-to-end bootstrap script that provisions an
empty GCP project as an apigee-skills-serving reference
deployment. Given a project with a pre-existing Apigee
organisation and API hub instance, provision.sh:
1. Enables the four GCP APIs skill-finder + skill-publisher
need (apigee, apihub, storage, iam).
2. Verifies the API hub instance + Apigee org exist; fails
fast with a clear message if not (creating an API hub
instance takes 40+ min and has console-only decisions --
out of scope for automation).
3. Creates a public-read GCS bucket for signed .skill
bundles (public-read because skill-finder downloads via
anonymous HTTPS -- demo choice; production would use
signed URLs or an authenticated consumer).
4. Creates the four API hub user-defined attributes
(agentic_skill, keywords, gs_uri, signing_key_id) and
patches skill-spec into the system-spec-type enum.
Latter is a known gap: update_taxonomy.py doesn't add
skill-spec, but register_skill.py requires it.
5. Generates a fresh ed25519 signing keypair on THIS
machine. Private key at ~/.config/apigee-skills-demo/
signing.raw (mode 0600). Public fingerprint printed for
the operator to record.
6. Packs, signs, uploads, and registers the three example
payload skills (currency-converter, weather-lookup,
apigee-policy-top10) with the fresh key.
7. Installs the corresponding public PEM into an
already-installed skill-finder's keys/ directory so it
will trust the just-published catalog.
Idempotency
-----------
Every step detects existing state before deciding whether to
act. Re-running against a fully-provisioned project produces
seven OK lines and exits at the READY banner without making
any changes:
Step 1: "apigee.googleapis.com already enabled" x 4
Step 2: "API hub instance: ...", "Apigee organisation: ..."
Step 3: "bucket gs://... exists", "public-read enabled"
Step 4: "user-defined attributes: all 4 present",
"system-spec-type includes skill-spec"
Step 5: "existing key at ... (reusing; pass --force ...)"
Step 6: "already published: ...", "all example skills already
published"
Step 7: "trust root already installed: ..."
Modes
-----
--check-only Read-only preflight. Reports what's missing
without touching anything. Useful for
pre-demo dry-runs.
--dry-run Like --check-only but tells you WHAT it would
do at each step.
--force Overwrite an existing signing key (default:
reuse -- overwriting would invalidate all
prior signatures).
--yes No interactive prompts (for automation).
Selective skips
---------------
--skip-apis, --skip-bucket, --skip-taxonomy, --skip-skills,
--skip-key, --skip-trust-root-install
Any subset can be combined. Useful for surgically re-running
a single step or for splitting the initial provisioning from
a later trust-root-only re-run (after installing skill-finder).
Trust model
-----------
The signing keypair is generated on the operator's machine
and never leaves it. There is no shared org-level signing
key; each deployment owns its own root of trust. The public
PEM is written to skill-finder's keys/ directory so
skill-finder verifies signatures against the deployment-local
key. Multiple keys can coexist under keys/ (see the multi-key
feature in commit babed2e) which supports multi-publisher
orgs and zero-downtime key rotation.
Verified end-to-end
-------------------
Ran against geirs-spaces-demo (a live GCP project) in three
scenarios:
1. --check-only: 7/7 steps report OK. No writes, no
spurious warnings.
2. Wet re-run against fully-provisioned project: 7/7 steps
OK, exits at READY with no writes. Fully idempotent.
3. --force key regeneration: prompts for confirmation
(unless --yes), overwrites signing.raw, warns operator
that prior signatures will no longer verify. Publishing
step then re-signs and re-registers all skills with the
new key.
shellcheck on the script: PASS.
…+ provisioning
Updates the reference implementation's documentation set to
cover the new one-line install workflow and the per-deployment
multi-key trust model.
Four files changed:
README.md
* New "Quickstart (five minutes)" section pointing at the
curl | bash install of skill-finder followed by
bin/provision.sh. Kept the underlying pack/upload/sign/
register invocation as a secondary "Under the hood"
block for readers who want to understand what the scripts
chain together.
* Repo layout section updated: new bin/ scripts
(install-skill-finder.sh, install-skill-publisher.sh,
provision.sh), new docs (provisioning.md), new skills
(skill-finder, skill-publisher).
* Renamed "Example skills" section to "Shipped skills" and
added skill-finder + skill-publisher rows.
* Limitations section rewritten: per-deployment trust root
(not shared), key rotation supported (multi-key), demo
choice of public-read GCS. Removed the "publisher and
consumer share an Ed25519 trust root" claim -- multi-key
trust roots eliminate that.
docs/architecture.md
* "What the consumer verifies" section expanded to name
skill-finder as the reference consumer and reference the
16-step Hyrum's Law contract.
* New subsection "Per-deployment trust roots" explains why
the trust root is not baked into the repo, why
skill-finder is trusted by provenance rather than
signature, and how the private-key-stays-local model
works.
* New subsection "Multi-key trust roots" documents the
keys/*.pem glob model, the three operator workflows it
supports (multi-publisher, zero-downtime rotation,
bootstrap onboarding), and the empty-keys diagnostic.
* Threat model table gains two rows: compromised publisher
key mitigation (multi-key add-then-remove rotation) and
compromised installer over TLS (out of scope for v1; pin
--ref for defence-in-depth).
* New subsection "Trust-root discovery in v2" explains the
forward-compatible design: API hub as trust-root store
via a well-known attribute. Deferred as v2 to avoid
taxonomy churn and a first-fetch TOFU chicken-and-egg
in v1.
docs/provisioning.md [NEW, 300 LoC]
Complete customer-onboarding runbook:
* Prerequisites (Apigee org + API hub instance -- both
out of scope for provision.sh because they take 10-40
minutes and have console-only decisions).
* "Five-minute path" quick recipe.
* Step-by-step explanation of what provision.sh does
internally (all 7 steps, plus the state detection
that makes each step idempotent).
* Common workflows: add another publisher, zero-downtime
key rotation, rebuild a partial deployment, remove a
deployment.
* Troubleshooting: seven concrete symptom -> fix rows for
the most common failure modes.
docs/publish-and-install.md
* New introduction points at provisioning.md for the
five-minute path and explains when to keep reading the
long form (understanding provision.sh internals,
publishing your own skill, troubleshooting).
* Section 11 (consumer-side install) rewritten to describe
skill-finder's actual 7-step verify-and-install flow
rather than a hypothetical consumer implementation. Adds
forward links to architecture.md#multi-key-trust-roots
and architecture.md#trust-model.
* Cleanup section rewritten to reflect the split between
payload skills (removed by demo-cleanup.sh) and
skill-finder / skill-publisher (persist across demos;
remove manually or via the full-teardown recipe in
provisioning.md).
Verified
--------
* Code fence parity: balanced across all 4 files.
* Markdown cross-references: no broken relative links (checked
every ](path) that isn't http:// or a #anchor).
* No stale references to gsjurseth/skill-finder or to signed
release bundle URLs.
…kill-finder scripts
Fixes 12 megalinter errors surfaced when running the v4 image
locally against feat/add-skill-finder. All fixes are lint-only;
no runtime behaviour changes.
bandit (2 errors, blocking)
* find_install.py:438 `assert self._state is not None`
-> added `# nosec B101 - defensive invariant`
* find_install.py:807 `assert gs_uri.startswith("gs://")`
-> added `# nosec B101 - caller invariant`
The `# nosec` pattern matches the existing devrel convention
(see scripts/common/http_retry.py which already uses it).
Both asserts are defensive invariants, not security checks;
the caller-side contracts they enforce would fail in later
lines anyway.
flake8 (10 errors, blocking)
* find_install.py:116-120,189: E402 module-level import not
at top of file. These imports come AFTER a deferred
import-guard block that catches ModuleNotFoundError from
cryptography and prints a helpful "run under the venv
wrapper" message. The E402 is by design.
-> added `# noqa: E402` comments to the affected lines.
* find_install.py:178: F401 unused import `http_post_retry`.
-> removed from both the production and dev-fallback
import blocks.
* find_install.py:570: E501 line too long (introduced during
one of my edits).
-> broke the set comprehension across three lines.
* find_install.py:789,1114,1118: F541 f-string missing
placeholders. These are middle-of-multi-line-string
literals concatenated by Python's implicit concatenation;
only the parts with interpolation need the `f` prefix.
-> dropped the `f` prefix from the placeholder-free lines.
* list_skills.py:45: F401 unused import `os`.
-> removed.
* list_skills.py:195,202: F541 f-string missing placeholders.
-> dropped the `f` prefix.
Verified
--------
* `flake8 skills/skill-finder/scripts/`: PASS (exit 0)
* `pytest`: 237/237 pass (unchanged, no behaviour regressions)
* megalinter v4 via podman (same env vars as
.github/workflows/devrel-static-checks.yml): finishes with
"Successfully linted all files, but with ignored errors"
(`◬`, non-blocking). Zero `❌` blocking failures.
The 3 categories that WERE blocking before this commit:
- CREDENTIALS/secretlint: false-positive scan of a stray
local .venv/ (not present on CI, and cleaned up
pre-commit).
- PYTHON/bandit: fixed by this commit.
- PYTHON/flake8: fixed by this commit.
17 new hermetic pytest cases covering the two functions that
changed when skill-finder moved from a single-key trust root
(keys/trusted_pubkey.pem, hardcoded path) to a multi-key model
(keys/*.pem, glob load): _load_pubkeys and _verify_signature.
Coverage matrix
---------------
_load_pubkeys (9 tests)
* missing keys/ directory -> hard fail
* empty keys/ directory -> hard fail
* keys/ with only dotfiles -> hard fail
* keys/ with only README.md -> hard fail (suffix filter)
* single PEM -> loads, dict size 1
* two distinct PEMs -> loads, dict size 2
* duplicate key material under two -> dedupes with warning
filenames
* unparseable PEM alongside good PEM -> skips bad with warning,
loads good
* raw 32-byte key (.raw suffix) -> loads alongside PEMs
_verify_signature (8 tests)
* single trust root, matching key -> verifies OK
* two trust roots, manifest signed by -> verifies via A
key A
* two trust roots, manifest signed by -> verifies via B
key B (order-independent)
* key-id mismatch (untrusted signer) -> "key-id check: FAILED",
exits non-zero
* key-id mismatch error message -> lists both the declared
fingerprint AND the
trusted set (regression
against vague errors)
* signature field absent -> distinct "absent" failure
* signature field malformed base64 -> distinct "malformed"
failure
* signature signed by wrong key but -> "ed25519 verify rejected"
claims trusted signing_key_id (theoretical; tests the
crypto rejection path)
Design choices
--------------
* Tests DO NOT touch the network. Every fixture is in-memory
or under `tmp_path`. All monkeypatching is done via the
`find_install` module fixture; no HTTP mocking needed.
* find_install.py lives at skills/skill-finder/scripts/ rather
than scripts/. The test file loads it via importlib rather
than a top-level `import find_install`, so pytest doesn't
need a custom sys.path entry in conftest.py.
* `_load_pubkeys` and `_verify_signature` are exercised
directly rather than through the full find_install main().
The multi-key logic is self-contained in those two functions;
testing them in isolation gives a targeted, fast (0.64s)
verification.
* Test names read as behavioural specs ("empty_keys_dir_fails",
"two_valid_keys_first_matches"), matching the style of the
existing test suite (test_sign_verify_integration.py etc.).
Test count before this commit: 220.
Test count after: 237 (+17). All PASS in 1.4s in-process,
including as `uid=0(root)` inside a python:3.13-slim container
(matches the Cloud Build environment where the previous PR
had a root-vs-user regression).
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.
Summary
Extends the
references/apigee-skills-serving/reference (merged in#889) with two shipped agent skills and a one-line onboarding
experience.
New skills:
skills/skill-finder/— consumer-side discovery+install client.Queries the customer's API hub for signed skills, verifies their
Ed25519 signatures against locally-installed per-deployment trust
roots, and installs into the runtime skills directory (Gemini CLI,
OpenCode, Antigravity).
skills/skill-publisher/— author-side pipeline orchestrator.Wraps pack → sign → upload → register into a single skill an agent
can invoke on a source skill directory.
New bin/ scripts:
bin/install-skill-finder.sh— one-line curl installer forskill-finder + skill-publisher into any of three agent runtimes.
bin/install-skill-publisher.sh— same, publisher-only.bin/provision.sh— end-to-end customer bootstrap. EnablesGCP APIs, creates the GCS bucket, sets up the API hub attribute
taxonomy, generates a fresh Ed25519 signing keypair on the
operator's machine, publishes the three example skills, and drops
the trust root into skill-finder's
keys/.The five-minute path for a new customer:
Trust model (deliberate design)
The
apigee/devrelrepo intentionally contains no signing keymaterial and no signed release bundles. This constraint drove
several design choices:
signature. TLS + the
apigeeGitHub org is the trust boundary.This matches how
kubectl,gh, andterraforminstallerswork.
per-deployment trust roots. Each customer's
provision.shgenerates a fresh Ed25519 keypair on their machine; the private
key never leaves the machine and is never committed to any
repo.
keys/*.pem) support multi-publisherorgs and zero-downtime key rotation. A downloaded manifest is
accepted if its
signing_key_idmatches ANY installed key.Full trust-model rationale in
docs/architecture.md.Customer runbook in
docs/provisioning.md.Explicit non-goals
apigee/devrelreleases.release-please's monorepo cadence is unchanged.
in this repo. Every signature happens on the customer's machine.
pack_skill,sign_skill,upload_skill,register_skill,update_taxonomy). This PR only adds new consumers of them.skill-spec(whichprovision.shpatches into
system-spec-type; the same PATCH the demo scriptshave needed since feat(apigee-skills-serving): add reference for Apigee API hub as agent skill catalog #889). v2 trust-root-in-API-hub design is
documented but deferred.
Change breakdown
7 commits totalling ~5389 LoC across 20 files:
3a02054ddemo-setup.shenv-ordering bug (small carry-over: pre-flight was called before env export)babed2e6skill-finder+skill-publishersource in; multi-key trust-root code changes infind_install.pydf3ae710bin/install-skill-finder.sh+bin/install-skill-publisher.sh— the one-line installerse0cbc4fabin/provision.sh— the customer bootstrap script (idempotent across all 7 steps)2e59ab3fdocs/provisioning.md; extendsdocs/architecture.mdtrust-model section; updatesREADME.md+docs/publish-and-install.md362c97a51b4d71f6_load_pubkeys+_verify_signaturefunctionsVerification (all run locally before this PR)
skills/skill-finder/scripts/uid=0(root)insidepython:3.13-slim(matches Cloud Build env)pipeline.shlocal run (Cloud Build equivalent).github/workflows/devrel-static-checks.yml)◬(only non-blocking)bin/install-skill-finder.sh --repo gsjurseth/devrel --ref feat/add-skill-finder— real fetch + installbin/provision.sh --check-onlyagainst a fully-provisioned project (7/7 steps report OK)bin/provision.shwet re-run against fully-provisioned project (idempotent, zero writes, exits at READY)Two design decisions worth flagging in review
GCS bucket = public-read(allUsers:objectViewer) is ademo-only choice. skill-finder currently downloads via anonymous
HTTPS (
https://storage.googleapis.com/<bucket>/<file>.skill),no ADC scope. Production would use signed URLs or an
authenticated consumer service account; adding that would be a
~50 LoC change to
find_install.py._download_zip. Called outin
docs/provisioning.md#step-3-gcs-bucketand READMELimitations.
skill-specenum patch tosystem-spec-typeis done viadirect API hub
PATCHfromprovision.shbecausescripts/update_taxonomy.pyonly creates user-definedattributes (not system-defined). Called out as a known gap in
docs/provisioning.md#step-4-api-hub-attribute-taxonomy; afuture PR could migrate the patch into
update_taxonomy.py.Follow-ups (out of scope for this PR)
docs/releasing.md— deliberately not added. The referenceimplementation ships with the devrel monorepo's release-please
cadence; installers fetch
mainby default. No separate releasechannel to document.
architecture.md#trust-root-discovery-in-v2but notimplemented. Would eliminate the "operator manually populates
keys/" step by having skill-finder fetch its trust root from
API hub itself.
install-skill-finder.shsupports
--runtime antigravity(installs to~/.gemini/antigravity/skills/) but I have not tested thislive (no antigravity runtime available in the dev environment).
The code path is shared with the Gemini CLI install (tested
green), so risk is low, but flag if a maintainer has an
antigravity setup.