Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 13 additions & 0 deletions .github/BRIEF.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ verify the changes and repository protections from concrete evidence.
and artifact; spec PRs show compatibility failures; unrun checks are labeled as such.
- Idempotency: refresh tests prove unchanged bytes preserve the document and date,
including repeated proposals; one fixed bot branch prevents daily duplicate PRs.
- Release correctness: semantic spec fixtures distinguish contract changes from
annotations. All four native jobs pass project checks and the standalone smoke
harness. Artifact tests reject wrong identity, digest, file type, archive paths
and incomplete platform sets before the writer can upload.
- Release recovery: HTTP integration tests prove draft-only creation, exact asset
verification, resumption of matching uploads, conflict rejection and preservation
of published releases. Published releases are never an automation output.

## Oracle

Expand Down Expand Up @@ -58,3 +65,9 @@ and manual dependency patch workflow retain the exceptions documented in SPEC.md
reopens the draft PR to trigger pull-request CI. Manual dispatch is diagnostic;
acceptance requires all seven checks attached to the current PR revision.
Evidence: [repository verification](SPEC.md#decisions).

2026-09-09, ratified by request: spec PRs propose version/changelog changes and
reviewed versions produce four-platform verified draft releases. Public
publication of v0.2.0 is authorized; future drafts require a maintainer to publish.
Release builds use fresh native hosted VMs with read-only repository tokens;
the separate draft writer never installs dependencies or executes artifacts.
64 changes: 58 additions & 6 deletions .github/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ updating the default branch.
platform limitation, not an authorization granted by this repository.
- REQ-REPO-003: Workflows default to read-only contents permissions, hosted runners,
bounded timeouts, commit-pinned actions, and checkout without persisted credentials.
Fork workflows receive no write tokens or secrets. Only the spec PR job receives
contents and pull-requests write permissions; it does not install dependencies
Fork workflows receive no write tokens or secrets. The spec PR job receives
contents and pull-requests write permissions; the release draft job receives
contents write permission. Neither writer installs dependencies
or execute project checks or the refreshed spec. Its builtin-only validator
runs from the trusted main revision selected when the workflow started.
- REQ-REPO-004: The default branch requires a PR, one code-owner approval from
Expand All @@ -35,10 +36,10 @@ updating the default branch.
manual review. Invalid responses fail before writing files.
- REQ-REPO-007: One dedicated branch, `automation/brale-spec`, holds the latest
spec proposal. A repeated upstream revision preserves its prior fetch date.
Only the vendored document and pin file enter its PR. The updater reports
Only the vendored document, pin, CLI package version and changelog enter its PR. The updater reports
compatibility checks and opens a draft even when those checks fail, so upstream
changes remain visible. Fetch/preparation failures do not publish a proposal.
Package versions and releases are separate maintainer decisions.
Version proposals require maintainer review; public releases require publication.
- REQ-REPO-008: Preparation installs no project dependencies and uploads only
raw spec bytes and revision metadata. Compatibility runs in a separate job.
The publisher downloads the preparation artifact by immutable ID, rejects
Expand All @@ -51,14 +52,44 @@ updating the default branch.
from tracked working-tree files without Git metadata or local credentials.
Untrusted logs cannot issue Actions workflow commands. Exit status determines
success. The action-manifest audit requires a separate networked container.
- REQ-REPO-010: Validated spec proposals include a CLI version and changelog
proposal when the semantic contract changes. Whitespace and known annotation
changes alone do not bump versions. Schema property names and payload values
remain meaningful. Automated proposals conservatively increment the minor
version before 1.0 and the major version thereafter; reviewers can adjust the
proposal after assessing compatibility. The writer derives these files from
trusted main, never accepts executable files or release metadata from upstream,
and repeated proposals against the same base are identical.
- REQ-REPO-011: A release workflow on canonical-main pushes and manual dispatch
checks the CLI version for a pending release. Four separate read-only jobs build
and smoke-test darwin-arm64, darwin-x64, linux-arm64 and linux-x64 natively,
with a locked dependency install, Bun 1.3.3, and synthetic credentials. Failed
builds or smoke tests prevent draft creation. Native release jobs run on fresh
hosted VMs and are outside the Docker isolation of REQ-REPO-009.
- REQ-REPO-012: A separate trusted writer validates all four archives, exact file
names, regular file types, bounded sizes, archive contents, checksums, source
revision and version before uploading. It installs no project dependencies and
never executes a downloaded binary. Drafts contain four archives, SHA256SUMS,
and build-info.json. Only artifacts from the same successful workflow run enter
this writer.
- REQ-REPO-013: Automation creates draft releases only. Repeated runs preserve
complete releases; interrupted draft uploads reconcile matching assets and fail
on conflicts rather than replacing bytes. A published version is never changed.
Client PRs include reviewed version/changelog changes when a release is needed;
multiple changes may share one release. Maintainers attach every asset before
publishing, because repository immutability locks future published releases.
- REQ-REPO-014: The v0.2.0 release contains the reviewed onboarding commands and
refreshed contract. All four exact shipped binaries pass the smoke harness;
release metadata identifies their source revision and checksums. Publication
of this version is authorized by the user; future publications remain manual.

## Invariants and non-goals

Workflow jobs never receive Brale credentials or move funds. External contributors
cannot initiate this repository's CI; workflows run independently in their own
forks are outside this repository's control. Maintainers inspect outside changes
before bringing them onto a repository branch for CI. There is no package
publication or deployment workflow. Repository visibility and licensing do not
before bringing them onto a repository branch for CI. There is no npm package
publication or automatic public release workflow. Repository visibility and licensing do not
change as part of these protections.

The isolation assumes reviewed workflow, runner and publisher definitions. The
Expand Down Expand Up @@ -91,9 +122,30 @@ REQ-REPO-009; maintainers review its main-branch inputs before running it.
- [ ] REQ-REPO-009: The Docker adversarial verifier proves credential/host isolation,
blocked outbound access, working loopback, skipped lifecycle scripts and
failure propagation. Compatibility passes inside the same container runner.
- [x] REQ-REPO-010: Semantic projection and trusted proposal integration tests pass,
including annotation-only changes, schema field names, bounds and repeatability.
- [x] REQ-REPO-012/013: Archive validation and HTTP integration tests pass; existing
lightweight and annotated tags must resolve to the build commit; conflicting
release assets cause zero replacement writes. Script typechecks run in CI.
- [x] REQ-REPO-011/013: Workflow policy tests and actionlint verify native runner
mappings, main-only execution, successful-build dependencies, stable artifact
names across failed-job reruns and draft writer isolation.
- [ ] REQ-REPO-011/014: All four exact v0.2.0 binaries pass standalone smoke;
the source revision, archive digests and published immutability are verified.

## Decisions

- 2026-09-09, ratified by request: implement spec version/changelog proposals,
verified four-platform builds and draft releases; deliver v0.2.0. Future public
releases are manual. Repository release immutability and non-updatable,
non-deletable v* tags are active. Existing mutable releases remain unchanged.
- 2026-09-09, provisional: release artifacts use one stable name per platform per
workflow run. Rebuilding a leg replaces its workflow artifact; successful legs
remain available across failed-job reruns. Each artifact is immutable once
uploaded and draft release assets are never overwritten. Conflicts stop for
inspection. An existing version tag is independently resolved before and after
uploads; an absent tag remains valid for a draft until manual publication.

- 2026-09-08, ratified by request: daily spec checking and automated PR creation
are authorized; PR approval and merging remain human decisions.
- 2026-09-08, provisional: native `GITHUB_TOKEN` avoids a new long-lived secret;
Expand Down
124 changes: 124 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
name: Prepare Release

on:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: release-preparation
cancel-in-progress: false

jobs:
candidate:
name: Check pending version
if: github.repository == '0xsend/bralecli' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
prepare: ${{ steps.inspect.outputs.prepare }}
version: ${{ steps.inspect.outputs.version }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
ref: ${{ github.sha }}
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version-file: .node-version
- name: Inspect pending release
id: inspect
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
printf '%s' "$GH_TOKEN" | node scripts/release-draft.ts inspect > "$RUNNER_TEMP/release-candidate.json"
jq -r '"prepare=\(.prepare)\nversion=\(.version)"' "$RUNNER_TEMP/release-candidate.json" >> "$GITHUB_OUTPUT"
cat "$RUNNER_TEMP/release-candidate.json" >> "$GITHUB_STEP_SUMMARY"

build:
name: Build and smoke (${{ matrix.platform }})
needs: candidate
if: needs.candidate.outputs.prepare == 'true'
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- platform: darwin-arm64
runner: macos-15
- platform: darwin-x64
runner: macos-15-intel
- platform: linux-arm64
runner: ubuntu-24.04-arm
- platform: linux-x64
runner: ubuntu-24.04
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
ref: ${{ github.sha }}
persist-credentials: false
- uses: ./.github/actions/setup-nub
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.3
- name: Check, compile and smoke-test native executable
env:
PLATFORM: ${{ matrix.platform }}
VERSION: ${{ needs.candidate.outputs.version }}
shell: bash
run: |
set -euo pipefail
stop_token=$(openssl rand -hex 32)
echo "::stop-commands::$stop_token"
trap 'echo "::$stop_token::"' EXIT
nub ci --ignore-scripts
nub run check
nub run build
nub run knip
nub run jscpd
bun --no-env-file scripts/build-binaries.mjs "$RUNNER_TEMP/binaries" "$PLATFORM"
python3 scripts/smoke-binary.py "$RUNNER_TEMP/binaries/bralecli-$PLATFORM" "$VERSION"
node scripts/release-artifacts.ts package "$RUNNER_TEMP/binaries" "$PLATFORM" "$RUNNER_TEMP/package"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
# Stable names retain successful legs when only failed jobs are rerun.
# A rebuilt leg replaces its workflow artifact, never a release asset.
name: release-${{ github.run_id }}-${{ matrix.platform }}
overwrite: true
path: ${{ runner.temp }}/package/
if-no-files-found: error
retention-days: 14

draft:
name: Validate and prepare draft
needs: [candidate, build]
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
ref: ${{ github.sha }}
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version-file: .node-version
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
pattern: release-${{ github.run_id }}-*
merge-multiple: true
path: ${{ runner.temp }}/release-inputs
- name: Validate all artifacts and reconcile draft assets
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
printf '%s' "$GH_TOKEN" | node scripts/release-draft.ts upload "$RUNNER_TEMP/release-inputs" "$RUNNER_TEMP/verified-release" > "$RUNNER_TEMP/release-result.json"
cat "$RUNNER_TEMP/release-result.json" >> "$GITHUB_STEP_SUMMARY"
echo 'Draft only. Review its source, changelog, checksums and all four assets before publishing. Publication locks assets under repository release immutability.' >> "$GITHUB_STEP_SUMMARY"
17 changes: 13 additions & 4 deletions .github/workflows/update-spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ jobs:
artifact-ids: ${{ needs.prepare.outputs.artifact-id }}
merge-multiple: true
path: ${{ runner.temp }}/spec-update
- name: Validate data and reconstruct trusted pin
- name: Validate data and propose version from trusted source
run: node scripts/apply-spec-proposal.ts --proposal-dir "$RUNNER_TEMP/spec-update"
- name: Create or update draft PR
id: proposal
Expand All @@ -133,13 +133,21 @@ jobs:
add-paths: |
packages/brale/openapi/brale.json
packages/brale/src/spec.ts
apps/cli/package.json
apps/cli/CHANGELOG.md
commit-message: 'chore(spec): refresh Brale OpenAPI contract'
title: 'chore(spec): refresh Brale OpenAPI contract'
draft: always-true
maintainer-can-modify: false
body: |
Brale's upstream OpenAPI bytes changed. This PR updates the vendored
document and its SHA-256/fetch-date pin; package versions are unchanged.
document and its SHA-256/fetch-date pin. A semantic contract change
also proposes a CLI version and changelog entry from trusted main.
Documentation-only or formatting-only updates keep the version unchanged.
Contract changes conservatively propose a minor release before 1.0,
and a major release from 1.0 onward; review the diff and adjust the
proposed version and release note when appropriate before merging.
A merged version change enters the draft-release build workflow.

Upstream SHA-256: `${{ needs.prepare.outputs.hash }}`

Expand All @@ -152,8 +160,9 @@ jobs:

Preparation installs no project dependencies. Compatibility runs on a
separate runner in a credential-free, offline container. The publisher
validates the preparation artifact and reconstructs the pin from trusted
source; compatibility code cannot replace the proposed files.
validates the preparation artifact and reconstructs the pin, version,
and changelog from trusted source; compatibility code cannot replace
the proposed files. The artifact contains no package or changelog files.

Review the contract diff, then close and reopen this draft PR as a
maintainer to trigger pull-request CI under this repository's actor
Expand Down
48 changes: 47 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ metadata. A separate job checks compatibility in the CI container. The publisher
validates the immutable preparation artifact and reconstructs the pin from its
trusted main checkout, then opens or updates the draft PR on
`automation/brale-spec`. It never accepts executable source from the artifact.
When the contract changes, the trusted publisher also proposes a CLI version and
changelog entry. Formatting and known documentation annotations alone do not
trigger a release. Because an upstream contract change may be breaking, automatic
proposals increment the minor version before 1.0 and the major version thereafter.
Reviewers assess compatibility and can adjust the proposed bump before merging.
Failed compatibility checks remain visible in the PR and do not suppress it.

To activate the workflow after merging it to `main`:
Expand Down Expand Up @@ -102,4 +107,45 @@ Verify the checks attached to the PR itself.

The bot replaces its dedicated branch on later upstream changes. Make fixes on
a separate branch, and close an obsolete proposal if upstream reverts to the
vendored revision. The updater does not bump package versions or publish a release.
vendored revision. Its version and changelog changes remain proposals until merged.

## Releases

Include a CLI version bump in `apps/cli/package.json` and an entry in
`apps/cli/CHANGELOG.md` when a client change needs to ship. Multiple changes may
share a version. Spec PRs propose these files automatically. Repository-only
changes do not require a release.

**Prepare Release** runs on maintainer pushes to `main`, including reviewed PR
merges, and supports manual dispatch on `main`. An already completed version is a
no-op. A pending version runs locked installs and project checks, then builds and
smoke-tests each executable on its native macOS/Linux and ARM64/x64 runner. These
jobs are read-only, skip install scripts, and use synthetic credentials; they run
on fresh hosted VMs rather than the CI Docker sandbox.

A separate writer downloads artifacts from that successful run, validates the
archive contents, source revision, version and checksums, then creates a draft
containing four archives, `SHA256SUMS` and `build-info.json`. It never executes
downloaded binaries or installs dependencies. Failed uploads leave a draft;
reruns resume matching assets and reject conflicts without replacing bytes.

Review the draft's source revision, changelog and all four build/smoke results.
Confirm all six assets are attached before publishing. Repository release
immutability locks the assets and tag when the draft is published. Corrections
ship under a new version. Automation never publishes a draft or approves/merges
a PR. Existing mutable releases are not changed by this workflow.

For local recovery, use a clean checkout of the exact intended source commit:

```sh
nub ci --ignore-scripts
bun --no-env-file scripts/build-binaries.mjs /absolute/binaries
# Run scripts/smoke-binary.py against each target on a compatible machine.
nub scripts/release-artifacts.ts package /absolute/binaries darwin-arm64 /absolute/package-darwin-arm64
# Repeat packaging for the other three platforms; collect their files in inputs.
gh auth token | nub scripts/release-draft.ts upload /absolute/inputs /absolute/new-verified-output
```

Packaging does not replace the required smoke tests. The upload command validates
the inputs and leaves the release as a draft. Pass tokens only through stdin;
never place them in command arguments or files.
Loading
Loading