diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e05fed2..96769a8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: SkillSync Release Validation +name: SkillSync GitHub Release on: push: @@ -6,41 +6,79 @@ on: permissions: contents: read - id-token: write + actions: read jobs: - validate: + release: runs-on: ubuntu-latest + permissions: + contents: write + actions: read + id-token: write + attestations: write + artifact-metadata: write steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v6 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: "24" package-manager-cache: false - - run: npm ci - - name: Run the offline test suite - run: npm test - - name: Type-check the release candidate - run: npm run type-check - - name: Lint the release candidate - run: npm run lint - - name: Build the release candidate - run: npm run build - - name: Inspect the public package contents - run: npm pack --dry-run - - name: Generate SBOM (CycloneDX) + - name: Bind the immutable tag to an already verified main source + env: + GH_TOKEN: ${{ github.token }} run: | - npm sbom --sbom-format cyclonedx --sbom-type library > sbom.cyclonedx.json 2>&1 || echo "npm sbom not available on this npm version, falling back to dry-run check" - if [ -f sbom.cyclonedx.json ]; then - echo "SBOM generated at sbom.cyclonedx.json ($(wc -c < sbom.cyclonedx.json) bytes)" - head -20 sbom.cyclonedx.json + set -euo pipefail + [[ "$GITHUB_REF_NAME" =~ ^v[0-9]+[.][0-9]+[.][0-9]+$ ]] + test "$GITHUB_REF_NAME" = "v$(node -p 'require("./package.json").version')" + refs=$(git ls-remote origin "refs/tags/$GITHUB_REF_NAME" "refs/tags/$GITHUB_REF_NAME^{}") + commit=$(printf '%s\n' "$refs" | awk '/\^\{\}$/ {print $1;exit}') + [[ -n "$commit" ]] || commit=$(printf '%s\n' "$refs" | awk 'NR==1 {print $1}') + test "$commit" = "$GITHUB_SHA" + gh api "repos/$GITHUB_REPOSITORY/actions/workflows/skillsync.yml/runs?head_sha=$GITHUB_SHA&branch=main&event=push&status=success&per_page=1" > "$RUNNER_TEMP/source-ci.json" + node --input-type=module - "$RUNNER_TEMP/source-ci.json" <<'JS' + import fs from 'node:fs'; + const runs=JSON.parse(fs.readFileSync(process.argv[2],'utf8')).workflow_runs; + if (!runs.some(run=>run.head_sha===process.env.GITHUB_SHA&&run.head_branch==='main'&&run.conclusion==='success')) process.exit(1); + JS + if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then + echo 'An existing release must not be overwritten' >&2 + exit 1 fi - - name: Publish the package with npm provenance - run: npm publish --provenance --access public - - name: Attest build provenance (if publish succeeded) - if: always() - uses: actions/attest-build-provenance@v2 + - run: npm ci + - run: npm test + - run: npm run type-check + - run: npm run lint + - run: npm run build + - name: Assemble actual package and validated metadata + run: | + set -euo pipefail + mkdir release + npm pack --json --ignore-scripts --pack-destination release > "$RUNNER_TEMP/package.json" + npm sbom --sbom-format cyclonedx --package-lock-only --omit=dev > release/sbom.cyclonedx.json + node --input-type=module - "$RUNNER_TEMP/package.json" <<'JS' + import fs from 'node:fs'; + import crypto from 'node:crypto'; + const [pack]=JSON.parse(fs.readFileSync(process.argv[2],'utf8')); + const version=JSON.parse(fs.readFileSync('package.json','utf8')).version; + if(pack.name!=='@chumanic/skillsync'||pack.version!==version||pack.filename!==`chumanic-skillsync-${version}.tgz`) throw new Error('Package identity mismatch'); + const allowed=/^(dist\/|runner\/|profiles\/|templates\/|fixtures\/(behavior|product|runner|policy)\/|config\/|docs\/[^/]+[.]md$|README[.]md$|LICENSE$|CHANGELOG[.]md$|package[.]json$)/; + if(!pack.files.length||pack.files.some(file=>!allowed.test(file.path)||file.path.split('/').some(part=>part.startsWith('.')&&part!=='.skillsync'))) throw new Error('Unexpected public package file'); + const sbom=JSON.parse(fs.readFileSync('release/sbom.cyclonedx.json','utf8')); + if(sbom.bomFormat!=='CycloneDX'||!Array.isArray(sbom.components)) throw new Error('Invalid SBOM'); + const sha256=crypto.createHash('sha256').update(fs.readFileSync(`release/${pack.filename}`)).digest('hex'); + fs.writeFileSync('release/release-manifest.json',JSON.stringify({package:pack.name,version,tag:process.env.GITHUB_REF_NAME,source_sha:process.env.GITHUB_SHA,archive:pack.filename,sha256},null,2)+'\n'); + JS + (cd release && sha256sum *.tgz sbom.cyclonedx.json release-manifest.json > checksums.txt) + - name: Attest actual package provenance + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4 with: - subject-path: "*.tgz" - continue-on-error: true - + subject-path: release/*.tgz + - name: Publish immutable GitHub release + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --verify-tag \ + --title "SkillSync $GITHUB_REF_NAME" --generate-notes release/* diff --git a/CHANGELOG.md b/CHANGELOG.md index b4ce8d0..d4a2314 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## 0.1.4 - 2026-10-09 + +- Publish new tagged packages through GitHub Releases with SHA256 checksums, + validated CycloneDX SBOM and required GitHub provenance, without an npm account. +- Keep npm-based CI defaults on the actually public 0.1.0 package; source and + GitHub packages retain artifact/reference/target coverage capabilities. +- Preserve the earlier immutable tags and npm 0.1.0. Source, release artifacts + and real external integration remain separate evidence. + # Changelog ## 0.1.3 - 2026-10-09 diff --git a/README.md b/README.md index 43289e4..3da1e3e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **Verify Agent Skills before you trust them.** SkillSync checks a local Skill's provenance, compatibility, and changes without executing it. -> **Alpha · v0.1.3 · Node.js 20+** +> **Alpha · v0.1.4 · Node.js 20+** > SkillSync performs offline checks of local Skill content. It does not execute Skill scripts, does not read credentials, and does not enable live provider, remote-worker, or runtime capabilities. [![Terminal demo](https://raw.githubusercontent.com/Chumaniac/skillsync/main/docs/assets/verify-demo.svg)](https://github.com/Chumaniac/skillsync/blob/main/docs/assets/verify-demo.svg) @@ -18,7 +18,7 @@ npm install -g @chumanic/skillsync@0.1.0 skillsync verify --path . --target codex ``` -**From source (latest 0.1.3):** +**From source (latest 0.1.4):** ```bash git clone https://github.com/Chumaniac/skillsync.git @@ -28,7 +28,28 @@ npm run build node dist/cli/index.js verify --path fixtures/product/trust-loop/review --target codex ``` -> `0.1.3` is a release candidate prepared for the OIDC provenance workflow (`npm publish --provenance --access public` with `id-token: write`, no long-lived token). Until the Trusted Publisher is verified on npm, use `0.1.0` via `npx` or run `0.1.3` from source. The `skillsync ci init` template pins `0.1.3` by default; override with `--package-version 0.1.0` on npm today. +**From GitHub releases (no npm account required):** + +Tagged builds now publish a CLI tarball, checksums, a validated CycloneDX SBOM +and a source manifest on [GitHub Releases](https://github.com/Chumaniac/skillsync/releases). +The current source candidate is 0.1.4; its release is complete only after the +matching immutable tag workflow succeeds. Installation still uses Node.js 20+ +and fetches the package's public dependencies; it needs no npm login. + +```bash +release_base="https://github.com/Chumaniac/skillsync/releases/download/v0.1.4" +for asset in chumanic-skillsync-0.1.4.tgz checksums.txt sbom.cyclonedx.json release-manifest.json; do + curl --fail --location --output "$asset" "$release_base/$asset" +done +shasum -a 256 -c checksums.txt +npm install --prefix ./skillsync-tools ./chumanic-skillsync-0.1.4.tgz +./skillsync-tools/node_modules/.bin/skillsync --version +``` + +The public npm registry remains at 0.1.0. Generated npm-based CI templates pin +that existing public version; a source/GitHub installation supplies the newer +artifact and target-coverage commands. Older tags and the existing npm package +remain available independently of this distribution. The command above verifies the included sample Skill. Replace the fixture path with a directory containing your own `SKILL.md` when you are ready. diff --git a/docs/ci.md b/docs/ci.md index 2e7c0a7..4124484 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -13,7 +13,7 @@ they can be replaced. The generated GitHub Action grants `contents: read` and uploads SARIF findings; it does not execute Skill scripts. The generated consumer command pins the -published SkillSync package version (`@chumanic/skillsync@0.1.3` by default); override it +published SkillSync package version (`@chumanic/skillsync@0.1.0` by default); override it with `ci init --package-version ` when upgrading. Because the current repository publishes a scoped public package, the generated consumer template can be used after that package version is available; the repository's own workflow uses @@ -69,12 +69,13 @@ placeholders is intentionally not accepted as production evidence. ## Release validation -`.github/workflows/release.yml` runs only for tags matching `v*`. It checks the -test suite, type-check, lint, build, and `npm pack --dry-run`, then publishes -`@chumanic/skillsync` with `npm publish --provenance --access public`. The job -uses GitHub OIDC (`id-token: write`) and no long-lived npm token. npm Trusted -Publisher configuration is an external prerequisite; a tag is not permission -to activate a live runtime capability. +`.github/workflows/release.yml` runs only for tags matching `v*`. It requires the +exact tagged source to have a successful main repository verification, then +checks tests, types, lint, build and the actual public package allowlist. It +publishes a tarball, checksums, validated SBOM and source manifest to GitHub +Releases only after required GitHub provenance succeeds. Existing releases are +not overwritten. This distribution needs no npm account or long-lived token; +the npm registry remains at 0.1.0. A tag does not activate a live runtime. The operator-facing activation, revocation, rollback, and evidence review procedure is in [`runtime-operator-runbook.md`](runtime-operator-runbook.md). diff --git a/docs/domain-adaptation.md b/docs/domain-adaptation.md index 5b42a62..8a5fe63 100644 --- a/docs/domain-adaptation.md +++ b/docs/domain-adaptation.md @@ -37,7 +37,7 @@ change. A `report` with both plan and receipt requires matching plan digests. These findings are review material, not automatic permission to run a Skill. The examples are current source additions, not part of the published npm0.1.0 -package. Keep npm installation and source0.1.3 capabilities separate. Capability +package. Keep npm installation and source0.1.4 capabilities separate. Capability profiles are maintained by this project using Agent documentation; a profile is not vendor certification, and unknown/runtime-dependent features stay explicit. diff --git a/package-lock.json b/package-lock.json index 08eb7a2..b282aa2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@chumanic/skillsync", - "version": "0.1.3", + "version": "0.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@chumanic/skillsync", - "version": "0.1.3", + "version": "0.1.4", "license": "MIT", "dependencies": { "commander": "^13.1.0", diff --git a/package.json b/package.json index 4e87ea1..862917d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@chumanic/skillsync", - "version": "0.1.3", + "version": "0.1.4", "private": false, "publishConfig": { "access": "public" diff --git a/src/cli/commands/ci.ts b/src/cli/commands/ci.ts index fa9adc4..d0f6508 100644 --- a/src/cli/commands/ci.ts +++ b/src/cli/commands/ci.ts @@ -23,7 +23,7 @@ export type CiInitResult = { }; const DEFAULT_PATHS = [".agents/skills", ".claude/skills", ".cursor/skills"]; -const DEFAULT_PACKAGE_VERSION = "0.1.3"; +const DEFAULT_PACKAGE_VERSION = "0.1.0"; const PUBLISHED_PACKAGE_NAME = "@chumanic/skillsync"; function validateNodeVersion(value: string): string { diff --git a/src/cli/commands/report.ts b/src/cli/commands/report.ts index e0fb80d..c5c9c79 100644 --- a/src/cli/commands/report.ts +++ b/src/cli/commands/report.ts @@ -278,7 +278,7 @@ export async function runReport(options: ReportOptions): Promise ...(resolvedIssueBaseline(beforeReport, before.rootDigest) === undefined ? {} : { baseline: resolvedIssueBaseline(beforeReport, before.rootDigest) }), - toolVersion: options.toolVersion ?? "0.1.3", + toolVersion: options.toolVersion ?? "0.1.4", }); } diff --git a/src/cli/index.ts b/src/cli/index.ts index 44c29b4..8d5014e 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -60,7 +60,7 @@ import { renderSarif } from "../reporters/sarif.js"; import { renderText } from "../reporters/text.js"; import { parseOutputFormat } from "./output.js"; -const VERSION = "0.1.3"; +const VERSION = "0.1.4"; const COMMANDS = [ ["scan", "Inspect skill files without executing their instructions."], @@ -642,7 +642,7 @@ export function createCli(io: CliIO = defaultCliIO): Command { .command("init") .option("--target ", "Template target: github or pre-commit", "github") .option("--node-version ", "Node.js version for GitHub Actions", "20") - .option("--package-version ", "Pinned published SkillSync package version", "0.1.3") + .option("--package-version ", "Pinned published npm package version", "0.1.0") .option("--path ", "Project Skill paths") .option("--apply", "Write the generated file") .option("--force", "Allow replacing an existing generated file") @@ -650,7 +650,7 @@ export function createCli(io: CliIO = defaultCliIO): Command { const result = await runCiInit({ target: options.target ?? "github", nodeVersion: options.nodeVersion ?? "20", - packageVersion: options.packageVersion ?? "0.1.3", + packageVersion: options.packageVersion ?? "0.1.0", paths: options.path ?? [], apply: options.apply, force: options.force, diff --git a/src/reporters/sarif.ts b/src/reporters/sarif.ts index da1afab..f66b18d 100644 --- a/src/reporters/sarif.ts +++ b/src/reporters/sarif.ts @@ -58,7 +58,7 @@ export function renderSarif(report: VerificationReport): string { tool: { driver: { name: "skillsync", - version: "0.1.3", + version: "0.1.4", rules: [...ruleMap.values()], }, }, diff --git a/templates/github/skillsync.yml b/templates/github/skillsync.yml index ce15814..53da643 100644 --- a/templates/github/skillsync.yml +++ b/templates/github/skillsync.yml @@ -17,8 +17,8 @@ jobs: - uses: actions/setup-node@v4 with: node-version: "20" - # Requires the published @chumanic/skillsync@0.1.3 package. - - run: npx --yes @chumanic/skillsync@0.1.3 verify --format sarif --path .agents/skills > skillsync.sarif + # Requires the published @chumanic/skillsync@0.1.0 package. + - run: npx --yes @chumanic/skillsync@0.1.0 verify --format sarif --path .agents/skills > skillsync.sarif - uses: github/codeql-action/upload-sarif@v4 if: always() with: diff --git a/templates/pre-commit/skillsync.yaml b/templates/pre-commit/skillsync.yaml index 4300337..7d4332a 100644 --- a/templates/pre-commit/skillsync.yaml +++ b/templates/pre-commit/skillsync.yaml @@ -3,6 +3,6 @@ repos: hooks: - id: skillsync-verify name: Verify Agent Skills with SkillSync - entry: npx --yes @chumanic/skillsync@0.1.3 verify --format json --path .claude/skills --path .agents/skills + entry: npx --yes @chumanic/skillsync@0.1.0 verify --format json --path .claude/skills --path .agents/skills language: system pass_filenames: false diff --git a/tests/cli/ci.test.ts b/tests/cli/ci.test.ts index c895241..423cf5b 100644 --- a/tests/cli/ci.test.ts +++ b/tests/cli/ci.test.ts @@ -40,7 +40,7 @@ describe("skillsync ci", () => { cwd: root, }); expect(planned.applied).toBe(false); - expect(planned.content).toContain("@chumanic/skillsync@0.1.3 verify --format sarif"); + expect(planned.content).toContain("@chumanic/skillsync@0.1.0 verify --format sarif"); await expect(access(join(root, ".github/workflows/skillsync.yml"))).rejects.toThrow(); const applied = await runCiInit({ @@ -51,7 +51,7 @@ describe("skillsync ci", () => { apply: true, }); expect(applied.applied).toBe(true); - expect(await readFile(applied.outputPath, "utf8")).toContain("@chumanic/skillsync@0.1.3 verify --format sarif"); + expect(await readFile(applied.outputPath, "utf8")).toContain("@chumanic/skillsync@0.1.0 verify --format sarif"); await expect( runCiInit({ diff --git a/tests/cli/help.test.ts b/tests/cli/help.test.ts index 993c489..97cd2b6 100644 --- a/tests/cli/help.test.ts +++ b/tests/cli/help.test.ts @@ -12,7 +12,7 @@ describe("skillsync CLI", () => { it("reports the exact public version and lists the verification commands", async () => { const version = await runCli(["--version"]); expect(version.exitCode).toBe(0); - expect(version.stdout).toBe("0.1.3\n"); + expect(version.stdout).toBe("0.1.4\n"); const result = await runCli(["--help"]); diff --git a/tests/cli/report.test.ts b/tests/cli/report.test.ts index a1dd34a..e91d7f1 100644 --- a/tests/cli/report.test.ts +++ b/tests/cli/report.test.ts @@ -218,7 +218,7 @@ describe("skillsync report", () => { const json = await runCli(["report", "--before", beforePath, "--after", afterPath, "--format", "json"]); expect(JSON.parse(json.stdout).conclusion).toBe("verified"); - expect(JSON.parse(json.stdout).toolVersion).toBe("0.1.3"); + expect(JSON.parse(json.stdout).toolVersion).toBe("0.1.4"); const sarif = await runCli(["report", "--before", beforePath, "--after", afterPath, "--format", "sarif"]); const parsedSarif = JSON.parse(sarif.stdout) as { @@ -229,7 +229,7 @@ describe("skillsync report", () => { }>; }; expect(parsedSarif.version).toBe("2.1.0"); - expect(parsedSarif.runs[0]?.tool.driver.version).toBe("0.1.3"); + expect(parsedSarif.runs[0]?.tool.driver.version).toBe("0.1.4"); expect(parsedSarif.runs[0]?.results[0]?.properties.issueId).toMatch(/^iss_/); }); diff --git a/tests/docs/documentation.test.ts b/tests/docs/documentation.test.ts index abf90b5..6bc2c8e 100644 --- a/tests/docs/documentation.test.ts +++ b/tests/docs/documentation.test.ts @@ -39,7 +39,7 @@ describe("release documentation", () => { }; expect(readme).toContain("Verify Agent Skills before you trust them."); - expect(readme).toContain("Alpha · v0.1.3 · Node.js 20+"); + expect(readme).toContain("Alpha · v0.1.4 · Node.js 20+"); expect(readme).toContain("## Install"); expect(readme).toContain("npx --yes @chumanic/skillsync@0.1.0 verify"); expect(readme).toContain([ @@ -51,7 +51,9 @@ describe("release documentation", () => { "node dist/cli/index.js verify --path fixtures/product/trust-loop/review --target codex", "```", ].join("\n")); - expect(readme).toContain("npm publish --provenance --access public"); + expect(readme).toContain("GitHub releases (no npm account required)"); + expect(readme).toContain("shasum -a 256 -c checksums.txt"); + expect(readme).toContain("public npm registry remains at 0.1.0"); expect(readme).toContain( "https://raw.githubusercontent.com/Chumaniac/skillsync/main/docs/assets/verify-demo.svg", ); @@ -59,7 +61,7 @@ describe("release documentation", () => { expect(terminalDemo).toContain( "$ node dist/cli/index.js verify --path fixtures/product/trust-loop/review --target codex", ); - expect(terminalDemo).not.toContain("@chumanic/skillsync@0.1.3"); + expect(terminalDemo).not.toContain("@chumanic/skillsync@0.1.4"); expect(readme).toContain("does not execute Skill scripts"); expect(readme).toContain("does not read credentials"); expect(readme).not.toContain("## Documentation index"); @@ -123,15 +125,20 @@ describe("release documentation", () => { expect(runbook).toContain("mTLS"); expect(runbook).toContain("remote Worker"); expect(releaseWorkflow).toContain('tags: ["v*"]'); - expect(releaseWorkflow).toContain("npm publish --provenance --access public"); + expect(releaseWorkflow).toContain("npm pack --json --ignore-scripts"); + expect(releaseWorkflow).toContain("gh release create"); + expect(releaseWorkflow).toContain("--verify-tag"); + expect(releaseWorkflow).toContain("checksums.txt"); + expect(releaseWorkflow).not.toContain("npm publish"); + expect(releaseWorkflow).not.toContain("continue-on-error"); expect(repositoryWorkflow).toContain("git grep -nE"); expect(repositoryWorkflow).not.toContain("rg -n"); expect(repositoryWorkflow).toContain("SkillSync-Complete-Design.md"); expect(repositoryWorkflow).toContain("Competitive-Research-and-Design-Rationale.md"); expect(repositoryWorkflow).toContain("MVP-Implementation-Plan.md"); - expect(githubTemplate).toContain("@chumanic/skillsync@0.1.3"); - expect(preCommitTemplate).toContain("@chumanic/skillsync@0.1.3"); - expect(ci).toContain("@chumanic/skillsync@0.1.3"); + expect(githubTemplate).toContain("@chumanic/skillsync@0.1.0"); + expect(preCommitTemplate).toContain("@chumanic/skillsync@0.1.0"); + expect(ci).toContain("@chumanic/skillsync@0.1.0"); expect(changelog).toContain("## 0.1.2 - 2026-08-31"); expect(changelog).toContain("publish contract"); expect(changelog).toContain("profile registry"); diff --git a/tests/integration/live-runtime-preparation.test.ts b/tests/integration/live-runtime-preparation.test.ts index 36c6637..f9d41ba 100644 --- a/tests/integration/live-runtime-preparation.test.ts +++ b/tests/integration/live-runtime-preparation.test.ts @@ -139,24 +139,35 @@ describe("live runtime preparation", () => { const jobs = asRecord(document.jobs); expect(push.tags).toEqual(["v*"]); - expect(Object.keys(jobs)).toEqual(["validate"]); + expect(Object.keys(jobs)).toEqual(["release"]); - const runs = workflowRuns(document, "validate").join("\n"); + const runs = workflowRuns(document, "release").join("\n"); for (const command of [ "npm test", "npm run type-check", "npm run lint", "npm run build", - "npm pack --dry-run", + "npm pack --json --ignore-scripts", ]) { expect(runs).toContain(command); } - expect(runs).toContain("npm publish --provenance --access public"); + expect(runs).toContain("gh release create"); + expect(runs).toContain("--verify-tag"); + expect(runs).toContain("checksums.txt"); + expect(runs).toContain("branch=main&event=push&status=success"); + expect(runs).toContain("actions/workflows/skillsync.yml/runs"); + expect(runs).toContain("An existing release must not be overwritten"); + const steps = asRecord(jobs.release).steps as Array>; + const attest = steps.find(step => String(step.uses).startsWith("actions/attest@")); + expect(asRecord(attest?.with)["subject-path"]).toBe("release/*.tgz"); + expect(content).not.toContain("continue-on-error"); + expect(content).not.toContain("npm publish"); expect(content).toContain('node-version: "24"'); expect(content).toContain("package-manager-cache: false"); expect(content).not.toMatch(/npm dist-tag|NODE_AUTH_TOKEN|registry-url|secrets\./i); - expect(asRecord(document.permissions)).toEqual({ contents: "read", "id-token": "write" }); + expect(asRecord(document.permissions)).toEqual({ contents: "read", actions: "read" }); + expect(asRecord(asRecord(jobs.release).permissions)).toEqual({ contents: "write", actions: "read", "id-token": "write", attestations: "write", "artifact-metadata": "write" }); }); it("keeps workflow and test sources outside the package artifact allowlist", async () => { diff --git a/tests/reporters/sarif.test.ts b/tests/reporters/sarif.test.ts index e4461f8..02f6c23 100644 --- a/tests/reporters/sarif.test.ts +++ b/tests/reporters/sarif.test.ts @@ -44,7 +44,7 @@ describe("renderSarif", () => { expect(sarif.version).toBe("2.1.0"); expect(sarif.runs[0]?.tool.driver.name).toBe("skillsync"); - expect(sarif.runs[0]?.tool.driver.version).toBe("0.1.3"); + expect(sarif.runs[0]?.tool.driver.version).toBe("0.1.4"); expect(sarif.runs[0]?.tool.driver.rules[0]?.id).toBe("structure.missing-reference"); expect(sarif.runs[0]?.tool.driver.rules[0]?.help?.text).toBe("Add the referenced file."); expect(sarif.runs[0]?.results[0]?.ruleId).toBe("structure.missing-reference");