Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
75f90e2
feat(fees): integrate v0.6 tooling (#180)
MuncleUscles Jun 8, 2026
02eeabc
fix: include execution budget floor in fee defaults (#186)
MuncleUscles Jun 9, 2026
2af6928
feat(fees)!: estimation correctness, wait-for-decided semantics, v0.6…
MuncleUscles Jun 10, 2026
1b7f50a
ci: keep main forwarded to active dev branch (#189)
MuncleUscles Jun 10, 2026
761233d
docs: add branching guide (#191)
MuncleUscles Jun 11, 2026
7679ea0
fix(fees)!: move wildcard callKey sentinel to keccak256(empty), deplo…
MuncleUscles Jun 11, 2026
60e8ca3
feat(sdk): developer-NFT read/claim actions + nonce coercion fix (#195)
MuncleUscles Jul 7, 2026
6f691ce
feat: vesting delegator actions + ABI (#193)
MuncleUscles Jul 7, 2026
a338ad5
feat: epoch-zero SDK helpers (getCurrentEpoch, isValidatorBelowMinSta…
MuncleUscles Jul 7, 2026
666d115
chore: untrack vitest-generated tsconfig.vitest-temp.json (#196)
MuncleUscles Jul 8, 2026
289839d
fix: make git install build script self contained (#197)
MuncleUscles Jul 8, 2026
5788928
fix: build package during git prepare (#198)
MuncleUscles Jul 8, 2026
bf42f13
feat(staking,vesting,accounts): provider-lane writes + native transfe…
MuncleUscles Jul 10, 2026
50a936c
feat(calldata)!: move method-call key from "method" to "" (empty stri…
MuncleUscles Jul 23, 2026
0b7e3bd
fix: resolve triggered transaction IDs from decision receipts (#202)
MuncleUscles Jul 26, 2026
6f12738
fix: support proof-bearing validator joins (#206)
MuncleUscles Aug 9, 2026
7666f0b
Revert "fix: support proof-bearing validator joins (#206)" (#207)
MuncleUscles Aug 11, 2026
bae1eee
feat: proof-bearing validator joins (restores #206, gated on its 4 co…
MuncleUscles Aug 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions .claude/skills/release/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
---
name: release
description: Cut a release of genlayer-js. Bumps version, updates CHANGELOG, tags, pushes — CI then publishes to npm and creates the GitHub Release. Use when a human asks "release v1.x.y" or "ship a new version".
---

# Release skill — genlayer-js

This repo follows a branch-per-major release model. There is no auto-bump on push. A release happens when a human (or you on their behalf) runs `scripts/release.sh` on the target stable branch.

## When to use this skill

User asks anything like:
- "release v1.2.0"
- "ship a patch"
- "cut a new minor"
- "tag the latest fix as a release"

If they ask "publish to npm directly" — refuse and point at this flow. The repo doesn't have an unprotected npm push path; the tag is the only release entry point.

## What this repo's release model expects

- Branches are named after the major they ship: `v1` (current stable), `v2-dev` / `v2` (next major when it exists).
- Tags live within those branches: `v1.1.9`, `v1.2.0`, ...
- A major bump means **cutting a new branch**, not tagging on the current one. The release script refuses major bumps unless `--allow-major` is passed.
- `CHANGELOG.md` is updated in the release commit (release-it via `@release-it/conventional-changelog`).
- `publish.yml` fires on the tag push and does the npm publish + GitHub Release.

## Steps

1. **Confirm intent with the user.**
- Which version? If unspecified, ask whether it's patch / minor / explicit.
- Which branch? Default `v1`. If they're shipping a back-port to an older major, the branch is `v<old>`.

2. **Switch to the target branch + sync.**
```bash
git checkout v1
git pull --ff-only origin v1
```
If the working tree isn't clean, stop and surface what's there — never stash and ship.

3. **Verify the head is shippable.**
- Latest CI run on this commit is green (the release script also checks, but check first so you don't half-run the script):
```bash
gh run list --branch v1 --commit "$(git rev-parse HEAD)" --limit 1
```
- Inspect the last few commits since the previous tag for surprises:
```bash
git log "$(git describe --tags --abbrev=0)..HEAD" --oneline
```
- If anything looks unexpected (e.g. an in-flight refactor accidentally landed), surface it and wait for the user's call.

4. **Run the release script.**
```bash
scripts/release.sh <X.Y.Z> # or patch / minor
```
It will: bump `package.json`, prepend `CHANGELOG.md`, commit `Release v<X.Y.Z> [skip ci]`, tag `v<X.Y.Z>`, and push both the branch commit and the tag. It will NOT publish to npm — CI handles that.

5. **Watch the publish workflow.**
```bash
gh run watch
```
or
```bash
gh run list --workflow=publish.yml --limit 1
```
If `publish.yml` fails (typical causes: tag/package.json mismatch — caused by hand-editing `package.json` outside the script; `NPM_TOKEN` rotated; npm provenance check), report the failure verbatim and stop. Do not retry blindly.

6. **Confirm on npm.**
```bash
npm view genlayer-js dist-tags
```
The `latest` tag should show the new version. Report back to the user with the version and the GitHub Release URL.

## Things to refuse

- **Major bump on the current branch** without `--allow-major`. The right move for a major is a new branch + new track in the runner matrix (separate workflow).
- **Releasing from `main`** — `main` is retired. If somehow `main` exists locally, the script will refuse; explain why.
- **Hand-editing `package.json` to bump the version** instead of running the script. The script keeps `package.json`, the CHANGELOG entry, the commit message, and the tag in lockstep; doing it by hand drifts them.
- **Publishing a tag where `publish.yml` failed** — fix the underlying issue, re-cut the release (delete the bad tag both locally and on origin, re-run the script). Don't manually `npm publish`.

## Roll-back

If a release shipped but is broken:

1. **Don't unpublish from npm** unless someone with elevated permissions has assessed the impact — npm unpublish has a 72-hour window and a deprecation path that consumers prefer.
2. **Deprecate the bad version**:
```bash
npm deprecate "genlayer-js@<X.Y.Z>" "broken release; install <X.Y.Z+1> or later"
```
3. **Ship a follow-up patch** via the same flow (`scripts/release.sh patch`).

## Why no auto-bump?

The previous flow auto-bumped on every push to `main`, which:
- Twice landed accidental major bumps (`0.28.7 → 1.0.0`, `v1-prerelease → v2-yanked` in testing-suite) because conventional-commit `BREAKING CHANGE` notes are too easy to drop into a PR.
- Tied "shipping a release" to "merging a PR", which conflated two decisions.
- Left no human checkpoint between "code lands" and "users get it".

Manual + scripted is the trade we made: small overhead per release in exchange for never shipping a surprise.
2 changes: 1 addition & 1 deletion .github/e2e-track
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.6-dev
v0.6
107 changes: 107 additions & 0 deletions .github/scripts/validate-branch-policy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/usr/bin/env bash
set -euo pipefail

failed=0

error() {
echo "::error::$*"
failed=1
}

warning() {
echo "::warning::$*"
}

active_branch_file="support/ci/ACTIVE_DEV_BRANCH"
if [[ ! -f "${active_branch_file}" ]]; then
error "${active_branch_file} is required."
active_branch=""
else
active_branch="$(tr -d '[:space:]' < "${active_branch_file}")"
fi

if [[ -z "${active_branch}" ]]; then
error "${active_branch_file} must not be empty."
elif [[ "${active_branch}" == "main" ]]; then
error "${active_branch_file} must point to a dev branch, not main."
elif [[ "${active_branch}" != *-dev ]]; then
warning "${active_branch_file} should normally point to a -dev branch; got ${active_branch}."
fi

release_branch="${active_branch%-dev}"
default_branch="${GITHUB_DEFAULT_BRANCH:-}"
event_name="${GITHUB_EVENT_NAME:-local}"
base_ref="${GITHUB_BASE_REF:-}"
head_ref="${GITHUB_HEAD_REF:-}"
ref_name="${GITHUB_REF_NAME:-}"
actor="${GITHUB_ACTOR:-}"

if [[ -n "${default_branch}" && "${default_branch}" != "main" ]]; then
warning "Repository default branch should be main after branch-policy rollout; currently ${default_branch}."
fi

if [[ -n "${base_ref}" && "${base_ref}" == "main" ]]; then
warning "PR targets main; retarget-main-prs should move it to ${active_branch}."
fi

if [[ -n "${base_ref}" && -n "${active_branch}" ]]; then
if [[ "${base_ref}" == "${release_branch}" && "${head_ref}" != "${active_branch}" && "${ALLOW_DIRECT_RELEASE_PR:-false}" != "true" ]]; then
error "PRs into ${release_branch} must come from ${active_branch}. Merge feature work into ${active_branch}, then promote ${active_branch} -> ${release_branch}."
fi
fi

if [[ "${event_name}" == "push" && "${ref_name}" == "main" ]]; then
case "${actor}" in
github-actions[bot]|ci-core-e2e-runner[bot])
;;
*)
error "main should only move by automation from ${active_branch}; direct push actor was ${actor:-unknown}."
;;
esac
fi

if [[ ! -f ".github/workflows/fast-forward-main.yaml" ]]; then
error ".github/workflows/fast-forward-main.yaml is required."
fi

if [[ ! -f ".github/workflows/retarget-main-prs.yaml" ]]; then
error ".github/workflows/retarget-main-prs.yaml is required."
fi

if [[ -f ".github/workflows/release-from-main.yml" ]]; then
error ".github/workflows/release-from-main.yml is forbidden. Releases must be tag/version-branch driven."
fi

if [[ -f "release.config.js" ]]; then
error "release.config.js is forbidden in versioned tooling branches; semantic-release-on-main must not be restored."
fi

if [[ -f ".github/workflows/release-from-tag.yml" ]]; then
if ! grep -Fq 'v*.*.*' .github/workflows/release-from-tag.yml; then
error "release-from-tag.yml must trigger only from version tags matching v*.*.*."
fi
if ! grep -Fq 'refs/remotes/origin/${version_branch}' .github/workflows/release-from-tag.yml || \
! grep -Fq 'tag_commit' .github/workflows/release-from-tag.yml || \
! grep -Fq 'branch_head' .github/workflows/release-from-tag.yml; then
error "release-from-tag.yml must verify the tag commit is the current matching version branch head."
fi
fi

if [[ -f ".github/workflows/manual-docker-release.yml" ]]; then
if ! grep -Fq 'expected_branch=' .github/workflows/manual-docker-release.yml; then
error "manual-docker-release.yml must derive and enforce the expected version branch from the tag."
fi
if ! grep -Fq './.github/workflows/release-from-tag.yml' .github/workflows/manual-docker-release.yml; then
error "manual-docker-release.yml must delegate image promotion to release-from-tag.yml."
fi
fi

if [[ "${failed}" -ne 0 ]]; then
exit 1
fi

if [[ -n "${base_ref}" ]]; then
echo "Branch policy ok for PR ${head_ref} -> ${base_ref}; active dev branch is ${active_branch}."
else
echo "Branch policy ok for ${event_name} on ${ref_name:-detached ref}; active dev branch is ${active_branch}."
fi
24 changes: 24 additions & 0 deletions .github/workflows/branch-policy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Branch Policy

on:
pull_request:
types: [opened, synchronize, reopened, edited, ready_for_review]
push:
branches:
- "**"
workflow_dispatch:

permissions:
contents: read

jobs:
branch-policy:
name: Validate branch policy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Validate branch policy
env:
GITHUB_DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: ./.github/scripts/validate-branch-policy.sh
8 changes: 6 additions & 2 deletions .github/workflows/chains-drift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ name: Chains Drift Check
on:
pull_request:
branches:
- main
- v1
- v2-dev
- v2
push:
branches:
- main
- v1
- v2-dev
- v2
schedule:
# Daily at 07:00 UTC. Catches upstream contract deployments even when no
# PRs are open against the SDK.
Expand Down
84 changes: 84 additions & 0 deletions .github/workflows/e2e-housekeeper.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: E2E Housekeeper

# ===========================================================================
# Source-of-truth E2E housekeeper workflow.
#
# This file is BOTH:
# - The workflow that runs in this repo (genlayer-e2e) for genlayer-
# e2e's own cache pool.
# - The file synced byte-identically to every consumer repo as
# `.github/workflows/e2e-housekeeper.yml` (sync-template.yaml owns
# the fan-out).
#
# Mirrors the architecture of e2e-pipeline.yml — one file in the source
# repo, copied verbatim to consumers, no wrapper layer. Adding a new
# scheduled upkeep step (artifact pruning, runner sweep, …) only needs
# editing this file; sync-template.yaml opens a PR in each consumer.
#
# Cache storage is per-repo, so the eviction step runs in the CALLER's
# context: `gh cache list` / `gh cache delete` operate on the caller's
# pool via the inherited GITHUB_TOKEN. The runner's filesystem starts
# empty, so step 1 checks out genlayer-e2e to access the extracted
# `evict-stale-caches.sh` script — github.token in a synced consumer
# context still has cross-org read access on the org's private repos.
# ===========================================================================

on:
schedule:
- cron: '0 6 * * *' # daily 06:00 UTC
# No inputs: production triggers (schedule today, PR-merged later)
# don't pass them, and the script's defaults (24h idle / 200 page
# cap) are stable. workflow_dispatch stays as a no-arg "run the
# cron now" button — handy in genlayer-e2e while iterating.
workflow_dispatch:

# Least-privilege. `evict-stale-caches.sh` invokes `gh cache list`
# (read) and `gh cache delete` (write) against the caller's cache pool
# via secrets.GITHUB_TOKEN. The GHA Cache API lives under the actions:
# permission namespace, so deletion requires actions:write. `contents:
# read` covers the checkout that fetches this repo's scripts.
permissions:
actions: write
contents: read

# Serialize runs so two overlapping firings (manual dispatch + schedule)
# don't both try to delete the same entry.
concurrency:
group: cache-cleanup
cancel-in-progress: false

jobs:
evict-stale:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
# Pull the extracted script. Explicit `repository:` because — when
# this file is synced to a consumer — actions/checkout's default
# target is the CONSUMER's repo, not genlayer-e2e. github.token
# in the consumer context has org-wide read on private repos so
# the clone works without an App token.
- name: Checkout genlayer-e2e
uses: actions/checkout@v6
with:
repository: genlayerlabs/genlayer-e2e
token: ${{ github.token }}

- name: Delete idle caches
env:
# `gh cache delete` consumes GH_TOKEN. github.repository
# resolves to the caller's repo (where the cron fires + the
# cache pool lives).
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: ./taskfiles/housekeeper/scripts/evict-stale-caches.sh --age 24 --limit 200

# Companion sweep for artifacts (logs, shard outputs, per-component
# summaries). Same 24h idle window so the two sweeps stay in sync.
# `if: always()` so an early cache-sweep failure doesn't skip the
# artifact pass — they're independent.
- name: Delete idle artifacts
if: always()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: ./taskfiles/housekeeper/scripts/evict-stale-artifacts.sh --age 24 --limit 1000
Loading
Loading