From ba5382bc7c02043b62f0a705af6ee32ddd4b9420 Mon Sep 17 00:00:00 2001 From: gionnibgud Date: Mon, 20 Jul 2026 19:33:24 +0200 Subject: [PATCH 1/2] ci: add reusable plugin-validate workflow for plugin repositories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lets any plugin repository run the spec's validator as its own CI gate in three lines (uses: .../plugin-validate.yml@main) instead of vendoring the schema or validator, which would drift. The plugin is staged under a directory named from its manifest id before validation, so repository naming conventions (feedBack-plugin-) don't conflict with spec §5.2. Groundwork for an org-owned feedBack-plugin-template starter repo, where community plugins get this gate green from day one. Co-Authored-By: Claude Opus 4.8 Signed-off-by: gionnibgud --- .github/workflows/plugin-validate.yml | 71 +++++++++++++++++++++++++++ README.md | 10 ++++ 2 files changed, 81 insertions(+) create mode 100644 .github/workflows/plugin-validate.yml diff --git a/.github/workflows/plugin-validate.yml b/.github/workflows/plugin-validate.yml new file mode 100644 index 0000000..fea0174 --- /dev/null +++ b/.github/workflows/plugin-validate.yml @@ -0,0 +1,71 @@ +# Reusable validation workflow for feedBack plugin repositories. +# +# Call it from a plugin repo's CI in three lines: +# +# jobs: +# validate: +# uses: got-feedback/feedBack-plugin-spec/.github/workflows/plugin-validate.yml@main +# +# It runs tools/validate.py — the same gate this repo runs on its own +# examples — against the caller's plugin, so validation logic and the +# manifest schema stay single-sourced here instead of being vendored +# (and drifting) in every plugin repo. +# +# The plugin is first staged under a directory named after its manifest +# `id`, so a repository can be named e.g. `feedBack-plugin-foo` while the +# plugin id is `foo`: spec §5.2 (directory name == id) is enforced against +# the staged copy, matching how the plugin actually sits inside a Host's +# plugins/ directory after installation. + +name: plugin-validate + +on: + workflow_call: + inputs: + plugin-dir: + description: "Path to the plugin directory inside the caller repository." + type: string + required: false + default: "." + spec-ref: + description: "Ref of this spec repository to validate against." + type: string + required: false + default: "main" + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Check out plugin + uses: actions/checkout@v4 + with: + path: caller + + - name: Check out spec + uses: actions/checkout@v4 + with: + repository: got-feedback/feedBack-plugin-spec + ref: ${{ inputs.spec-ref }} + path: spec + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install dependencies + run: python -m pip install --upgrade jsonschema + + - name: Stage plugin under its manifest id (spec §5.2) + run: | + set -eu + SRC="caller/${{ inputs.plugin-dir }}" + ID="$(python -c "import json, sys; print(json.load(open(sys.argv[1]))['id'])" "$SRC/plugin.json")" + mkdir -p "staged/$ID" + cp -R "$SRC/." "staged/$ID/" + rm -rf "staged/$ID/.git" + echo "STAGED=staged/$ID" >> "$GITHUB_ENV" + + - name: Validate against the spec + run: python spec/tools/validate.py "$STAGED" diff --git a/README.md b/README.md index 15fc771..64f69c2 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,16 @@ The validator is also a minimal reference implementation of the discovery contra checks `plugin.json`, enforces that the directory name equals the `id`, and confirms every file the manifest references exists. +Plugin repositories can run the same gate in their own CI via the reusable workflow +[`plugin-validate.yml`](.github/workflows/plugin-validate.yml), keeping the schema and +validation logic single-sourced here: + +```yaml +jobs: + validate: + uses: got-feedback/feedBack-plugin-spec/.github/workflows/plugin-validate.yml@main +``` + ## Versioning Three version axes are kept separate (see [spec §9](spec/plugin-spec-v1.md#9-versioning-and-compatibility)): From 98054ff7256d052ed4cc3ad18fa35d62edef3058 Mon Sep 17 00:00:00 2001 From: gionnibgud Date: Tue, 21 Jul 2026 10:25:50 +0200 Subject: [PATCH 2/2] ci: harden plugin-validate staging step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses review findings on the reusable workflow: - persist-credentials: false on both checkouts; the workflow only reads files and never pushes, so the token has no reason to stay in the runner's git config. - Pass plugin-dir via env instead of interpolating ${{ }} into the shell, removing the template-injection surface. - Sanitise and default the manifest id before it reaches mkdir/cp. This step runs before validate.py, so the schema's id pattern is not yet enforced and the manifest is still untrusted: strip path components and fall back to a placeholder when plugin.json is missing, unparseable, or has no id. The fallback also improves diagnostics — a bad manifest now produces validate.py's own error (missing 'id', invalid JSON, no plugin.json) instead of an opaque shell traceback from the staging step. Verified against five fixtures (valid, id-less, traversal id, absent manifest, malformed JSON): staging always succeeds, traversal stays contained, and each invalid case fails in validate.py with its specific message. Co-Authored-By: Claude Opus 4.8 Signed-off-by: gionnibgud --- .github/workflows/plugin-validate.yml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/.github/workflows/plugin-validate.yml b/.github/workflows/plugin-validate.yml index fea0174..71042c6 100644 --- a/.github/workflows/plugin-validate.yml +++ b/.github/workflows/plugin-validate.yml @@ -41,6 +41,7 @@ jobs: uses: actions/checkout@v4 with: path: caller + persist-credentials: false - name: Check out spec uses: actions/checkout@v4 @@ -48,6 +49,7 @@ jobs: repository: got-feedback/feedBack-plugin-spec ref: ${{ inputs.spec-ref }} path: spec + persist-credentials: false - name: Set up Python uses: actions/setup-python@v5 @@ -58,10 +60,23 @@ jobs: run: python -m pip install --upgrade jsonschema - name: Stage plugin under its manifest id (spec §5.2) + env: + PLUGIN_DIR: ${{ inputs.plugin-dir }} run: | set -eu - SRC="caller/${{ inputs.plugin-dir }}" - ID="$(python -c "import json, sys; print(json.load(open(sys.argv[1]))['id'])" "$SRC/plugin.json")" + SRC="caller/$PLUGIN_DIR" + # This step runs *before* validate.py, so the manifest is still + # untrusted here — the schema's id pattern (^[a-z0-9][a-z0-9_-]*$) + # has not been enforced yet. Strip any path components and fall back + # to a placeholder when plugin.json is missing, unreadable, or has no + # usable id, so a bad manifest yields validate.py's diagnostic rather + # than a shell traceback or a write outside staged/. + ID="$(python -c 'import json, os, sys + try: v = json.load(open(sys.argv[1])).get("id") + except Exception: v = None + v = os.path.basename(str(v or "").strip()) + print(v if v not in ("", ".", "..") else "invalid-manifest")' "$SRC/plugin.json" 2>/dev/null || true)" + [ -n "$ID" ] || ID="invalid-manifest" mkdir -p "staged/$ID" cp -R "$SRC/." "staged/$ID/" rm -rf "staged/$ID/.git"