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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ jobs:
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "24"
package-manager-cache: false
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: "1.3.14"
- run: npm install --global --ignore-scripts npm@11.19.0
- run: bun install --frozen-lockfile --ignore-scripts
- run: bun run check
- run: |
Expand Down
163 changes: 163 additions & 0 deletions .github/workflows/npm-stage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
name: Stage npm package

on:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: npm-stage
cancel-in-progress: false

jobs:
verify:
name: Verify package
permissions:
contents: read
runs-on: ubuntu-latest
timeout-minutes: 35
outputs:
archive_name: ${{ steps.artifact.outputs.archive_name }}
archive_sha512: ${{ steps.artifact.outputs.archive_sha512 }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "24"
package-manager-cache: false
registry-url: "https://registry.npmjs.org"
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: "1.3.14"
- name: Install staging-capable npm
run: npm install --global --ignore-scripts npm@11.19.0
- name: Verify default-branch package identity
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: |
set -euo pipefail
if [[ "$GITHUB_REF" != "refs/heads/$DEFAULT_BRANCH" ]]; then
echo "::error::Dispatch this workflow from $DEFAULT_BRANCH"
exit 1
fi
git fetch origin "$DEFAULT_BRANCH"
remote_head="$(git rev-parse "origin/$DEFAULT_BRANCH")"
if [[ "$GITHUB_SHA" != "$remote_head" ]]; then
echo "::error::Dispatch commit $GITHUB_SHA is not current $DEFAULT_BRANCH head $remote_head"
exit 1
fi
package_name="$(node -p 'require("./package.json").name')"
package_version="$(node -p 'require("./package.json").version')"
if [[ "$package_name" != "@hraness/kb" ]]; then
echo "::error::Unexpected package name $package_name"
exit 1
fi
if [[ ! "$package_version" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
echo "::error::Package version $package_version is not a stable semantic version"
exit 1
fi
npm view "$package_name" name --json \
--registry=https://registry.npmjs.org >/dev/null
version_error="$RUNNER_TEMP/npm-version-error.txt"
if npm view "$package_name@$package_version" version --json \
--registry=https://registry.npmjs.org \
>/dev/null 2>"$version_error"; then
echo "::error::$package_name@$package_version already exists"
exit 1
fi
if ! grep -q 'E404' "$version_error"; then
cat "$version_error"
echo "::error::Could not prove that $package_name@$package_version is unpublished"
exit 1
fi
- run: bun install --frozen-lockfile --ignore-scripts
- run: bun run check
- name: Require committed generated outputs
run: |
generated_status="$(git status --porcelain --untracked-files=all -- dist bun.lock)"
if [[ -n "$generated_status" ]]; then
printf '%s\n' "$generated_status"
exit 1
fi
- name: Build and verify the exact npm artifact
id: artifact
run: |
set -euo pipefail
artifact_dir="$RUNNER_TEMP/npm-package"
metadata="$RUNNER_TEMP/npm-pack.json"
mkdir -p "$artifact_dir"
npm pack --json --ignore-scripts --pack-destination "$artifact_dir" > "$metadata"
cat "$metadata"
archive_name="$(node -e '
const fs = require("node:fs");
const value = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
if (!Array.isArray(value) || value.length !== 1 || typeof value[0]?.filename !== "string") process.exit(1);
process.stdout.write(value[0].filename);
' "$metadata")"
archive="$artifact_dir/$archive_name"
bun run ./scripts/package-smoke.ts --archive "$archive"
archive_sha512="$(sha512sum "$archive" | cut -d ' ' -f 1)"
printf 'archive_name=%s\n' "$archive_name" >> "$GITHUB_OUTPUT"
printf 'archive_sha512=%s\n' "$archive_sha512" >> "$GITHUB_OUTPUT"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: npm-package-${{ github.sha }}
path: ${{ runner.temp }}/npm-package/${{ steps.artifact.outputs.archive_name }}
compression-level: 0
if-no-files-found: error
retention-days: 7

stage:
name: Stage verified package
needs: verify
permissions:
contents: read
id-token: write
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "24"
package-manager-cache: false
registry-url: "https://registry.npmjs.org"
- name: Install staging-capable npm
run: npm install --global --ignore-scripts npm@11.19.0
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: npm-package-${{ github.sha }}
path: ${{ runner.temp }}/npm-package
- name: Stage verified package
env:
ARCHIVE_NAME: ${{ needs.verify.outputs.archive_name }}
ARCHIVE_SHA512: ${{ needs.verify.outputs.archive_sha512 }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
run: |
set -euo pipefail
archive="$RUNNER_TEMP/npm-package/$ARCHIVE_NAME"
if [[ ! -f "$archive" || -L "$archive" ]]; then
echo "::error::Verified package archive is missing or linked"
exit 1
fi
actual_sha512="$(sha512sum "$archive" | cut -d ' ' -f 1)"
if [[ "$actual_sha512" != "$ARCHIVE_SHA512" ]]; then
echo "::error::Downloaded package digest does not match the verified artifact"
exit 1
fi
git fetch origin "$DEFAULT_BRANCH"
remote_head="$(git rev-parse "origin/$DEFAULT_BRANCH")"
if [[ "$GITHUB_SHA" != "$remote_head" ]]; then
echo "::error::Dispatch commit $GITHUB_SHA is no longer current $DEFAULT_BRANCH head $remote_head"
exit 1
fi
npm stage publish "$archive" \
--access public \
--registry=https://registry.npmjs.org
31 changes: 31 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@ jobs:
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "24"
package-manager-cache: false
registry-url: "https://registry.npmjs.org"
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: "1.3.14"
- run: npm install --global --ignore-scripts npm@11.19.0
- name: Verify release identity
id: identity
env:
Expand Down Expand Up @@ -69,6 +72,34 @@ jobs:
- run: bun pm pack --dry-run --ignore-scripts
- run: >-
node --input-type=module -e 'const manifest = (await import("./package.json", { with: { type: "json" } })).default; await Promise.all(Object.values(manifest.exports).map(({ import: path }) => import(path)))'
- name: Verify published npm artifact
run: |
set -euo pipefail
package_name="$(node -p 'require("./package.json").name')"
package_version="$(node -p 'require("./package.json").version')"
source_dir="$RUNNER_TEMP/npm-source"
registry_dir="$RUNNER_TEMP/npm-registry"
source_metadata="$RUNNER_TEMP/npm-source.json"
registry_metadata="$RUNNER_TEMP/npm-registry.json"
mkdir -p "$source_dir" "$registry_dir"
npm pack --json --ignore-scripts --pack-destination "$source_dir" > "$source_metadata"
npm pack --json --ignore-scripts --pack-destination "$registry_dir" \
--registry=https://registry.npmjs.org \
"$package_name@$package_version" > "$registry_metadata"
registry_archive="$(node -e '
const fs = require("node:fs");
const [sourcePath, registryPath] = process.argv.slice(1);
const source = JSON.parse(fs.readFileSync(sourcePath, "utf8"));
const registry = JSON.parse(fs.readFileSync(registryPath, "utf8"));
if (!Array.isArray(source) || source.length !== 1 || !Array.isArray(registry) || registry.length !== 1) process.exit(1);
if (typeof source[0]?.integrity !== "string" || source[0].integrity !== registry[0]?.integrity) {
console.error("::error::Published npm tarball differs from the checked source artifact");
process.exit(1);
}
if (typeof registry[0]?.filename !== "string") process.exit(1);
process.stdout.write(registry[0].filename);
' "$source_metadata" "$registry_metadata")"
bun run ./scripts/package-smoke.ts --archive "$registry_dir/$registry_archive"

publish:
name: Publish
Expand Down
5 changes: 3 additions & 2 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 and checks-gated immutable GitHub Release automation.
- `.github/workflows/` – read-only branch validation, manually dispatched stage-only 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 @@ -50,4 +50,5 @@
- 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.
- Treat a `v*` tag as a release request, not a completed release. 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 complete before its write-scoped publisher creates the Release. 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 interactive npm bootstrap and later releases. After the package exists, trust only `.github/workflows/npm-stage.yml` with `npm stage publish` permission, disallow traditional publishing tokens, inspect the staged tarball, and approve it with 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 public npm tarball with the checked source before its write-scoped publisher creates the Release. 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.
43 changes: 43 additions & 0 deletions DISCLOSURE
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Dual-use functionality disclosure

`@hraness/kb` is a local-first Markdown knowledge-base and source-capture tool.
Its intended use is to preserve research, plans, decisions, and sources that the
operator is authorized to read.

The package includes security-relevant capabilities that can be used for both
legitimate and harmful purposes:

- It can read cookies from a local browser profile or a user-supplied cookie
file when the operator explicitly selects that source.
- It can read an attached Chrome or Chromium tab, or use an owned browser
session, to capture public or signed-in content.
- It can make bounded network requests, follow validated redirects, download
selected assets, and query fixed metadata and archive providers.
- It can invoke bounded local browser, media, PDF, OCR, Git, Rust, and search
subprocesses when the operator requests a feature that needs them.
- It writes captured Markdown, metadata, evidence, and localized assets to a
caller-selected local vault.

Use these capabilities only for public content or content that you are entitled
and permitted to automate. Do not use this package to bypass authentication,
paywalls, CAPTCHAs, rate limits, DRM, audience controls, platform rules, or any
other access restriction. Do not use it to access another person's private
data.

Cookie-backed capture reads a selected store and keeps matching cookies in
memory, except for a short-lived mode-0600 cookie jar used by the optional
yt-dlp path. Path-backed browser profiles are copied to a temporary directory;
the source profile is not modified. Attached browser sessions retain their own
network behavior. Current-tab capture does not navigate, click, type, upload,
or submit. URL-based rendered capture can navigate and scroll within fixed
limits.

Captured pages, screenshots, cookies, source evidence, and terminal output can
contain credentials, private text, account names, or personal data. Review
every authenticated capture before committing, sharing, or processing it with
another service. The package applies path, network, resource, redaction, and
atomic-write controls, but those controls do not grant authorization or make
hostile content trustworthy.

Report suspected vulnerabilities through GitHub private vulnerability
reporting at https://github.com/hraness/kb/security/advisories/new.
Loading