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
290 changes: 189 additions & 101 deletions .github/workflows/npm-stage.yml

Large diffs are not rendered by default.

144 changes: 79 additions & 65 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@ on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag:
description: Existing stable tag to recover after npm delivery succeeded
required: true
type: string
- "!v*-beta.*"

permissions:
contents: read
Expand All @@ -19,15 +14,53 @@ concurrency:
cancel-in-progress: false

jobs:
authorize:
name: Authorize owner release tag
permissions: {}
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Verify immutable owner and public repository identity
env:
EXPECTED_ACTOR_ID: "894119"
EXPECTED_REPOSITORY: "hraness/kb"
EXPECTED_REPOSITORY_ID: "1308971873"
REF_PROTECTED: ${{ github.ref_protected }}
run: |
set -euo pipefail
if [[ "$GITHUB_EVENT_NAME" != push || \
"$GITHUB_ACTOR_ID" != "$EXPECTED_ACTOR_ID" || \
"$GITHUB_REPOSITORY" != "$EXPECTED_REPOSITORY" || \
"$GITHUB_REPOSITORY_ID" != "$EXPECTED_REPOSITORY_ID" || \
"$REF_PROTECTED" != true ]]; then
echo "::error::Release requires an owner-created protected tag in the exact public repository"
exit 1
fi
EXPECTED_ACTOR_ID="$EXPECTED_ACTOR_ID" \
EXPECTED_REPOSITORY="$EXPECTED_REPOSITORY" \
EXPECTED_REPOSITORY_ID="$EXPECTED_REPOSITORY_ID" node <<'NODE'
const { readFileSync } = require("node:fs");
const event = JSON.parse(readFileSync(process.env.GITHUB_EVENT_PATH, "utf8"));
if (
event.sender?.id !== Number(process.env.EXPECTED_ACTOR_ID)
|| event.sender?.type !== "User"
|| event.repository?.id !== Number(process.env.EXPECTED_REPOSITORY_ID)
|| event.repository?.full_name !== process.env.EXPECTED_REPOSITORY
|| event.repository?.visibility !== "public"
|| event.repository?.private !== false
|| event.repository?.default_branch !== "main"
) throw new Error("Tag-push sender or public repository identity is not the immutable release authority");
NODE

verify:
name: Verify
needs: authorize
permissions:
contents: read
runs-on: ubuntu-latest
timeout-minutes: 45
outputs:
default_branch: ${{ steps.identity.outputs.default_branch }}
event_mode: ${{ steps.identity.outputs.event_mode }}
source_sha: ${{ steps.identity.outputs.source_sha }}
verified_tag: ${{ steps.identity.outputs.tag }}
workflow_sha: ${{ steps.identity.outputs.workflow_sha }}
Expand Down Expand Up @@ -56,7 +89,7 @@ jobs:
id: identity
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
RECOVERY_TAG: ${{ inputs.tag }}
REF_PROTECTED: ${{ github.ref_protected }}
run: |
set -euo pipefail
git check-ref-format "refs/heads/$DEFAULT_BRANCH"
Expand All @@ -65,30 +98,13 @@ jobs:
default_head="$(git rev-parse "origin/$DEFAULT_BRANCH")"
checked_out_head="$(git rev-parse HEAD)"

case "$GITHUB_EVENT_NAME" in
push)
event_mode="push-tag"
release_tag="$GITHUB_REF_NAME"
if [[ "$GITHUB_REF" != "refs/tags/$release_tag" ]]; then
echo "::error::Release push is not an exact tag ref"
exit 1
fi
;;
workflow_dispatch)
event_mode="recovery"
release_tag="$RECOVERY_TAG"
if [[ "$GITHUB_REF" != "refs/heads/$DEFAULT_BRANCH" || \
"$GITHUB_SHA" != "$default_head" || \
"$checked_out_head" != "$default_head" ]]; then
echo "::error::Recovery must run from current $DEFAULT_BRANCH head $default_head"
exit 1
fi
;;
*)
echo "::error::Unsupported release event $GITHUB_EVENT_NAME"
exit 1
;;
esac
release_tag="$GITHUB_REF_NAME"
if [[ "$GITHUB_EVENT_NAME" != push || \
"$GITHUB_REF" != "refs/tags/$release_tag" || \
"$REF_PROTECTED" != true ]]; then
echo "::error::Release requires a protected owner-created stable tag"
exit 1
fi

if [[ ! "$release_tag" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
echo "::error::Release tag $release_tag is not a stable semantic version"
Expand All @@ -97,14 +113,18 @@ jobs:
git check-ref-format "refs/tags/$release_tag"
release_ref="refs/kb-release-tags/$release_tag"
git fetch --no-tags origin "refs/tags/$release_tag:$release_ref"
if [[ "$(git cat-file -t "$release_ref")" != tag ]]; then
echo "::error::Release tag must be annotated"
exit 1
fi
tag_commit="$(git rev-parse "$release_ref^{commit}")"
if [[ ! "$tag_commit" =~ ^[a-f0-9]{40}$ ]]; then
echo "::error::Release tag did not resolve to one commit"
exit 1
fi
if [[ "$event_mode" == push-tag && \
( "$GITHUB_SHA" != "$tag_commit" || "$checked_out_head" != "$tag_commit" ) ]]; then
echo "::error::Tag event does not match the checked release commit $tag_commit"
if [[ "$GITHUB_SHA" != "$tag_commit" || \
"$checked_out_head" != "$tag_commit" ]]; then
echo "::error::Tag does not match the checked release commit $tag_commit"
exit 1
fi
if ! git merge-base --is-ancestor "$tag_commit" "$default_head"; then
Expand Down Expand Up @@ -148,8 +168,8 @@ jobs:
exit 1
fi

printf 'default_branch=%s\nevent_mode=%s\nsource_sha=%s\ntag=%s\nworkflow_sha=%s\n' \
"$DEFAULT_BRANCH" "$event_mode" "$tag_commit" "$release_tag" "$checked_out_head" \
printf 'default_branch=%s\nsource_sha=%s\ntag=%s\nworkflow_sha=%s\n' \
"$DEFAULT_BRANCH" "$tag_commit" "$release_tag" "$checked_out_head" \
>> "$GITHUB_OUTPUT"
- name: Materialize exact tagged source
id: source
Expand Down Expand Up @@ -213,6 +233,21 @@ jobs:
registry_pack_json="$registry_directory/npm-pack.json"
registry_view_json="$registry_directory/npm-view.json"

registry_ready=false
for registry_poll in {1..60}; do
published_version="$(npm view "$package_spec" version --json \
--registry=https://registry.npmjs.org 2>/dev/null || true)"
if [[ "$published_version" == "\"$package_version\"" ]]; then
registry_ready=true
break
fi
sleep 10
done
if [[ "$registry_ready" != true ]]; then
echo "::error::$package_spec did not become readable from npm in time"
exit 1
fi

if [[ "$(git -C "$GITHUB_WORKSPACE" rev-parse HEAD)" != "$WORKFLOW_SHA" ]]; then
echo "::error::Reviewed workflow checkout changed before package verification"
exit 1
Expand Down Expand Up @@ -280,7 +315,6 @@ jobs:
timeout-minutes: 5
env:
DEFAULT_BRANCH: ${{ needs.verify.outputs.default_branch }}
EVENT_MODE: ${{ needs.verify.outputs.event_mode }}
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ github.token }}
VERIFIED_SOURCE_SHA: ${{ needs.verify.outputs.source_sha }}
Expand All @@ -296,28 +330,13 @@ jobs:
echo "::error::Verified release identity is incomplete"
exit 1
fi
case "$EVENT_MODE" in
push-tag)
if [[ "$GITHUB_EVENT_NAME" != push || \
"$GITHUB_REF" != "refs/tags/$VERIFIED_TAG" || \
"$GITHUB_SHA" != "$VERIFIED_SOURCE_SHA" ]]; then
echo "::error::Tag release event changed after verification"
exit 1
fi
;;
recovery)
if [[ "$GITHUB_EVENT_NAME" != workflow_dispatch || \
"$GITHUB_REF" != "refs/heads/$DEFAULT_BRANCH" || \
"$GITHUB_SHA" != "$WORKFLOW_SHA" ]]; then
echo "::error::Recovery event changed after verification"
exit 1
fi
;;
*)
echo "::error::Verified release event mode is invalid"
exit 1
;;
esac
if [[ "$GITHUB_EVENT_NAME" != push || \
"$GITHUB_REF" != "refs/tags/$VERIFIED_TAG" || \
"$GITHUB_SHA" != "$VERIFIED_SOURCE_SHA" || \
"$GITHUB_SHA" != "$WORKFLOW_SHA" ]]; then
echo "::error::Protected tag release event changed after verification"
exit 1
fi

current_tag_sha="$(gh api "/repos/$GITHUB_REPOSITORY/commits/$VERIFIED_TAG" --jq '.sha')"
current_default_sha="$(gh api "/repos/$GITHUB_REPOSITORY/commits/$DEFAULT_BRANCH" --jq '.sha')"
Expand All @@ -332,11 +351,6 @@ jobs:
echo "::error::Tag $VERIFIED_TAG is no longer reachable from $DEFAULT_BRANCH"
exit 1
fi
if [[ "$EVENT_MODE" == recovery && "$current_default_sha" != "$WORKFLOW_SHA" ]]; then
echo "::error::$DEFAULT_BRANCH advanced to $current_default_sha during recovery verification"
exit 1
fi

repository_tags="$(gh api --paginate \
"/repos/$GITHUB_REPOSITORY/tags?per_page=100" \
--jq '.[].name')"
Expand Down
9 changes: 6 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
- `kb/` – this source repository's authored rationale, maintained synthesis, and implementation plans; it is separate from the package's graph implementation and fixtures.
- `WRITING.md` and `STYLE.md` – internal and public prose contracts.
- `docs/` – design, capture, and agent-workflow documentation.
- `.github/workflows/` – read-only branch validation, stable-version-triggered and recovery-dispatched stage-only npm publication, and checks-gated immutable GitHub Release automation.
- `.github/workflows/` – read-only branch validation, stable-or-beta direct OIDC npm publication, and checks-gated immutable GitHub Release automation.
- `portfolio-inventory.json`, `scripts/check-portfolio-inventory.ts`, and `scripts/check-installed-command-docs.ts` – canonical public package inventory and standalone public-command consistency gates.
- `README.md`, `CONTRIBUTING.md`, `SECURITY.md`, and `LICENSE` – public usage, project policy, threat model, and terms.
- `package.json`, `tsconfig.json`, and `bun.lock` – standalone package and frozen verification configuration.
Expand Down Expand Up @@ -51,10 +51,13 @@
- Keep `portfolio-inventory.json` byte-canonical and consistent with the public package identity, version, repository, direct `@hraness/*` dependency edges, and Hraness-owned dependencies pinned by exact immutable GitHub specifiers.
- Pair concrete behavior tests with property tests for parsing, resolution, ordering, path confinement, and round-trip laws.
- Run `bun test src/benchmark.test.ts src/evaluation.test.ts src/evaluation-kb.test.ts src/search.test.ts src/sdk.test.ts` when changing rank fusion, retrieval defaults, frozen-corpus execution, or built-in evaluation adapters. The six-case synthetic rank-fusion fixture is a deterministic regression, not a retrieval-quality or performance benchmark. Keep real-corpus manifests versioned, judgments independent of rankings, raw lane evidence intact, and performance claims tied to named hardware and measured runs. Run `bun run check` before handing off a change; it must leave committed `dist/` and `bun.lock` unchanged.
- Follow `docs/publishing.md` for the interactive npm bootstrap and later stage-only trusted publishing. A `package.json` push to `main` may continue only when the exact prior and current manifests prove a strictly increasing stable package version; an unchanged version must stop successfully before verification or OIDC, and `workflow_dispatch` remains the current-`main` recovery path. Keep source checkout, install, build, test, pack, and artifact upload in a read-only job; only its minimal dependent staging job may request OIDC, and that job must use the exact `npm-stage` environment, restrict deployments to `main` without required deployment reviewers, and rebind the exact three-file artifact and current `main` before the canonical-registry stage-only mutation. Bind npm's trusted publisher to that exact environment. Disallow traditional publishing tokens, inspect the staged tarball, and approve its promotion with human 2FA. Preserve `contentPolicy.class=dual-use` and the root `DISCLOSURE` in every published version.
- Treat a `v*` tag as a release request, not a completed release. Publish the exact npm version first. Before tagging, confirm repository-level immutable releases are enabled; use a strictly increasing stable package version, keep the tag equal to `v<package.json version>` on `main`, and let the read-only verification job compare the source and registry packages by exact extracted path, type, mode, size, and regular-file hashes before its write-scoped publisher creates the Release. Verify each transport's npm and registry integrity independently because compressed tarball bytes may vary across operating systems. Recover a failed post-tag Release only through the explicit current-`main` workflow dispatch; bind the current workflow helpers to reviewed Git blobs and invoke them by absolute path against the tagged working tree after its explicit check/build, with tag-owned Bun config and environment loading disabled. Keep `npm pack --ignore-scripts` so recovery never depends on or reruns a historical `prepack`. Never move the tag or republish npm. Do not create the next tag until that workflow and Release are verified because GitHub concurrency is not a durable queue. After tagging, verify the matching non-draft immutable Release is Latest.
- Follow `docs/publishing.md` for the historical interactive bootstrap and direct OIDC trusted publishing. After the exact active CI workflow's `push` run and `Required` job succeed on protected current `main`, an agent acting under standing release authorization runs `bun run ./scripts/push-npm-release-tag.ts <version>` with already-available owner `gh` and Git credentials. The script must keep authentication opaque; bind immutable owner `User` ID `894119`, public repository ID `1308971873`, the exact live `npm-stage` environment with administrator bypass disabled, sole `branch_policy` protection, and sole `v*` tag deployment policy, plus the exact package/current-main/active-CI run and attempt/Required job; enforce bounded monotonic stable or numeric `beta.N` remote-tag inventory; refuse conflicts and inherited local refs; and push only one exact annotated `v<version>` ref. An exact same remote annotated tag and commit is an idempotent no-op. Missing authentication or ambiguous evidence fails closed.
- Use two exact active rulesets matching `refs/tags/v*`: **Immutable version tags** restricts update and deletion with an empty bypass list, while **Release tag creation** restricts creation and has owner `User` ID `894119` as its sole always-bypass actor. The local tag command must read back both before mutation. Never grant generic GitHub Actions integration ID `15368`, an administrator, a repository role, a team, or another integration this bypass; never combine creation with update/delete or create probe tags. Protected tag-push workflows must bind the actor and event sender to that owner and the exact public repository before checkout, require the annotated tag/package/source identity, keep source verification read-only, publish each unique version directly through the minimal OIDC job with its final initial tag (`latest` or `beta`), verify exact registry content/integrity/channel/signatures/provenance, and never use staged publishing or dist-tag promotion. Stable tags independently wait for npm before the immutable Latest Release; beta tags stop after npm readback. Trust a dedicated Release App only after its isolated credential and exact immutable App ID are configured explicitly. Never move a tag or republish npm, and finish one stable release before requesting the next because workflow concurrency is not a durable queue.

<!-- hra-local-efficiency:start -->
- Treat the user's request to change this repository as standing authorization for routine task-owned commits, pushes, pull requests, merges, releases, deployments, and production verification after the repository's required validation, review, identity, and rollout gates pass. Do not ask for another confirmation at each delivery step.
- Use the repository's documented delivery workflow and preserve every runtime-enforced approval, branch protection, environment rule, safety policy, and final gate. Ask for user input only when delivery needs a material product decision, missing credentials or authority, an irreversibly destructive action outside task scope, or resolution of a release failure that cannot be handled safely and autonomously.
- Prefer short-lived repository workload identities such as OIDC trusted publishing, GitHub Apps, and narrowly scoped machine identities. Do not add long-lived personal tokens, weaken two-factor authentication, or bypass provider controls to eliminate an interactive prompt. Batch unavoidable human-gated production promotions into intentional stable releases while agents publish validated prerelease or beta channels through workload identities when the repository supports them.
- Preserve useful reasoning fan-out, but avoid unnecessary checkout fan-out. Prefer subagents in the current task for bounded research, review, diagnosis, and focused checks when they can safely share one working tree; create a separate task or worktree only for independently deliverable divergent edits, an isolated verification tree, or a different execution environment.
- Give each expensive focused validation command and external wait one owner. The integration owner reviews that evidence and runs the repository-required aggregate or final gate once after convergence. Reuse evidence only for the exact Git tree, command, lockfiles, toolchain, relevant environment, and validity period, and never to skip a required final integration, merge, release, deployment, or production-verification gate.
- On Hraness development machines, use `$hra-local-efficiency` and the installed host scheduler for heavyweight top-level commands when available. Keep ordinary work in the compute lane; give authenticated browser/dev-server/Chromium work one `browser-auth` owner and Mac-only validation one `mac-native` owner.
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
Loading