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: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ body:
attributes:
label: linux-context version
description: Output of `./bin/linux-context --version`; for checkouts also include `git rev-parse HEAD`. Unreleased checkouts can share a version string with a release.
placeholder: linux-context 1.0.0
placeholder: linux-context 1.1.0
validations:
required: true
- type: dropdown
Expand Down
145 changes: 145 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
name: Release

# Only successful CI of trusted main pushes may publish. PR runs never qualify.
on:
workflow_run:
workflows: [CI]
types: [completed]
branches: [main]

permissions:
contents: read

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

jobs:
publish:
if: >-
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main' &&
github.event.workflow_run.head_repository.full_name == github.repository
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
contents: write
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_COMMIT: ${{ github.event.workflow_run.head_sha }}
steps:
- name: Check out the exact successful CI commit
uses: actions/checkout@v7
with:
ref: ${{ github.event.workflow_run.head_sha }}
fetch-depth: 0
persist-credentials: false
- name: Decide whether this version needs publication
id: version
shell: bash
run: |
set -euo pipefail
[[ "$(git rev-parse HEAD)" == "$RELEASE_COMMIT" ]]
current=$(gh api "repos/$GH_REPO/commits/main" --jq .sha)
if [[ "$current" != "$RELEASE_COMMIT" ]]; then
echo 'Main advanced; wait for CI of its latest commit.'
exit 0
fi
version=$(cat VERSION)
[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
tag="v$version"
gh api --paginate "repos/$GH_REPO/releases" \
--jq ".[] | select(.tag_name == \"$tag\")" > "$RUNNER_TEMP/existing-release.json"
resume=false
if [[ -s "$RUNNER_TEMP/existing-release.json" ]]; then
if [[ "$(jq -r .draft "$RUNNER_TEMP/existing-release.json")" == false ]]; then
echo "$tag is already published; nothing to do."
exit 0
fi
[[ "$(jq -r .target_commitish "$RUNNER_TEMP/existing-release.json")" == "$RELEASE_COMMIT" ]] || {
echo 'Existing draft targets a different commit; refusing replacement.' >&2
exit 1
}
resume=true
fi
if git rev-parse --verify "refs/tags/$tag" >/dev/null 2>&1; then
[[ "$(git rev-parse "$tag^{commit}")" == "$RELEASE_COMMIT" ]] || {
echo 'Existing tag points elsewhere; refusing to retag.' >&2
exit 1
}
fi
printf 'ready=true\nversion=%s\ntag=%s\nresume=%s\n' "$version" "$tag" "$resume" >> "$GITHUB_OUTPUT"
- name: Build and verify the committed source archive
if: steps.version.outputs.ready == 'true'
env:
RELEASE_VERSION: ${{ steps.version.outputs.version }}
shell: bash
run: |
set -euo pipefail
bash scripts/build-release.sh "$RUNNER_TEMP/release"
mkdir "$RUNNER_TEMP/extracted"
tar -xzf "$RUNNER_TEMP/release/linux-context-v$RELEASE_VERSION.tar.gz" -C "$RUNNER_TEMP/extracted"
cd "$RUNNER_TEMP/extracted/linux-context-v$RELEASE_VERSION"
[[ "$(./bin/linux-context --version)" == "linux-context $RELEASE_VERSION" ]]
make check
bash tests/smoke-distribution.sh
- name: Prepare release notes
if: steps.version.outputs.ready == 'true'
env:
RELEASE_VERSION: ${{ steps.version.outputs.version }}
shell: bash
run: |
python3 -B -S - <<'PY'
import os, re
from pathlib import Path
version = os.environ['RELEASE_VERSION']
text = Path('CHANGELOG.md').read_text()
match = re.search(r'^## ' + re.escape(version) + r' - [^\n]+\n(.*?)(?=^## |\Z)', text, re.M | re.S)
if not match:
raise SystemExit('Release section is missing from CHANGELOG.md')
temp = Path(os.environ['RUNNER_TEMP'])
checksum = (temp / 'release' / f'linux-context-v{version}.tar.gz.sha256').read_text().strip()
notes = f'# linux-context v{version}\n\n' + match.group(1).strip() + '\n\n'
notes += '## Download and verification\n\nThe attached source archive is built from commit `' + os.environ['RELEASE_COMMIT'] + '`, tested after extraction, and preserves executable script modes.\n\n'
notes += 'SHA-256:\n\n```text\n' + checksum + '\n```\n\n'
notes += '```bash\nsha256sum -c linux-context-v' + version + '.tar.gz.sha256\ntar -xzf linux-context-v' + version + '.tar.gz\ncd linux-context-v' + version + '\n./bin/linux-context --version\nsudo ./bin/linux-context --profile max\n```\n\nSend `context.ai.json` first; retrieve graph/evidence only as needed. Review diagnostic data before sharing.\n'
notes += '\n[Full changelog](https://github.com/' + os.environ['GH_REPO'] + '/blob/v' + version + '/CHANGELOG.md)\n'
(temp / 'release-notes.md').write_text(notes)
PY
- name: Upload draft assets and verify their downloaded bytes
if: steps.version.outputs.ready == 'true'
env:
RELEASE_TAG: ${{ steps.version.outputs.tag }}
RELEASE_VERSION: ${{ steps.version.outputs.version }}
RESUME_DRAFT: ${{ steps.version.outputs.resume }}
shell: bash
run: |
set -euo pipefail
archive="$RUNNER_TEMP/release/linux-context-v$RELEASE_VERSION.tar.gz"
if [[ "$RESUME_DRAFT" == true ]]; then
gh release upload "$RELEASE_TAG" "$archive" "$archive.sha256" --clobber
gh release edit "$RELEASE_TAG" --notes-file "$RUNNER_TEMP/release-notes.md"
else
gh release create "$RELEASE_TAG" "$archive" "$archive.sha256" \
--draft --target "$RELEASE_COMMIT" --title "linux-context $RELEASE_TAG" \
--notes-file "$RUNNER_TEMP/release-notes.md"
fi
mkdir -p "$RUNNER_TEMP/downloaded"
gh release download "$RELEASE_TAG" --pattern 'linux-context-*.tar.gz*' --dir "$RUNNER_TEMP/downloaded"
(cd "$RUNNER_TEMP/downloaded" && sha256sum -c "linux-context-v$RELEASE_VERSION.tar.gz.sha256")
cmp "$archive" "$RUNNER_TEMP/downloaded/linux-context-v$RELEASE_VERSION.tar.gz"
cmp "$archive.sha256" "$RUNNER_TEMP/downloaded/linux-context-v$RELEASE_VERSION.tar.gz.sha256"
- name: Publish the verified release
if: steps.version.outputs.ready == 'true'
env:
RELEASE_TAG: ${{ steps.version.outputs.tag }}
shell: bash
run: |
set -euo pipefail
current=$(gh api "repos/$GH_REPO/commits/main" --jq .sha)
[[ "$current" == "$RELEASE_COMMIT" ]] || { echo 'Main advanced before publication; draft retained.' >&2; exit 1; }
gh release edit "$RELEASE_TAG" --draft=false --latest
[[ "$(gh api "repos/$GH_REPO/commits/$RELEASE_TAG" --jq .sha)" == "$RELEASE_COMMIT" ]]
gh release view "$RELEASE_TAG" --json url,isDraft,isPrerelease
127 changes: 70 additions & 57 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,76 @@

## Unreleased

Unreleased entries describe changes after the 1.0.0 release, even while the
checkout's `VERSION` remains 1.0.0. Older sections describe historical behavior,
not the current acquisition/format contract.

### Documentation

- Align user, format, collector, contribution and security guides with current
main; clarify AI-first retrieval, baseline/target selection, byte-budget scope,
incomplete coverage and directory/archive failure boundaries.
- Add complete collector/profile/API tables and a troubleshooting guide,
including the remaining BusyBox setpriv limitation in optional owner probes.
- Preserve historical audit measurements with exact revision and CI references;
distinguish tool/schema/encoding versions and checkout changes from releases.

### Portability, correctness and performance

- Add lossless AI encoding v2: endpoint references, label/provenance defaults and
pooled observations; select the smallest of canonical/v1/v2 encodings and keep
backward-compatible decoding. The supplied rich sample shrinks from 114,586
canonical bytes (96,592 old AI bytes) to 74,134 bytes without dropping data.
- Cache collector metadata and initialize stages/hash manifests in single Python
passes; batch selected sysctls, reuse package inventories for counts, and share
one interpreter across the final redaction/validation/AI/scan/manifest gates.
- Use sysfs adapters for automatic Bluetooth discovery; reserve bounded BlueZ
controller queries for explicit targets (three-second discovery limit).
- Add Python timeout and owner-handoff fallbacks for minimal/BusyBox userlands;
bound detection, collector execution and previously unbounded daemon calls.
- Read vendor os-release fallback safely, expose PID 1, gate systemd runtime,
suppress container reboot guesses, and distinguish installed network clients
from running managers and failed LSM queries from disabled protection.
- Filter residual Debian package records; avoid false zero/exact counts on
failed or truncated inventories; support portable filesystem/process/boot/cron
observations and preserve IPv6 routes and coexisting firewall state.
- Observe procfs truncation by actual captured bytes; retain caller shell flags;
reject missing worker status/invalid catalog/empty targets and reconcile final
redacted evidence/context byte budgets before publication.
- Add package-family, runtime/fallback, model/privacy, manager-state, final-budget
and randomized AI round-trip regressions plus four-distribution container CI.
- Accept IEC capacity units (GiB/MiB and related sizes) from newer dmidecode
output, restoring module/installed capacity and memory-array limits.
- Bound Bluetooth controller discovery with probe telemetry;
failed/truncated discovery leaves the controller count unknown instead of zero.
- Stop and reap collector process trees before private staging cleanup on INT/TERM
or early failure, including producers in GNU timeout's separate process groups.
- Add hanging/failed/empty/successful Bluetooth and INT/TERM cleanup regressions.
- Add standard-profile typed SMBIOS RAM arrays/modules with capacity, DDR type,
manufacturer/part number, slot/bank, rank, ECC, rated/configured speeds and
explicit missing-tool/access/firmware/truncation coverage. Keep serial/asset
identifiers out of acquisition output.
- Add typed CPU topology/cache/selected ISA features, firmware models, GPU PCI
name/driver/available VRAM, disk models/transport/capacity, system
battery full/design capacity health, available RAM and swap counters.
- Reduce duplicate hardware text in automatic snapshots; retain focused evidence
under named hardware targets and `all`, without changing the schema or budgets.
- Add a lossless dictionary-coded `context.ai.json` AI view with round-trip
validation, residual scanning and manifest/archive coverage.
- Add hardware parser/sysfs/privacy fixtures and memory-collector integration tests.
No changes yet. Unreleased entries describe changes after the latest tagged
release; older sections record historical behavior.

## 1.1.0 - 2026-10-03

### Hardware identification

- Add standard-tier typed RAM arrays/modules: capacity, DDR type,
manufacturer/part number, slots, ECC, rank and rated/configured speeds.
Missing tools/access/firmware, unknown sizes and truncated inventories stay
explicit. Parse IEC capacity units from newer dmidecode versions.
- Model CPU topology/cache/selected features, firmware, GPU driver/available
VRAM, disks and system battery full/design capacity. Omit module/device serial
identifiers and redundant automatic hardware command dumps; retain focused
evidence through named targets/all.

### AI representation and performance

- Add `context.ai.json`, with lossless v1/v2 dictionaries for graph names,
endpoints, labels, provenance and repeated observations. Select the smallest
canonical/v1/v2 representation; preserve types/conflicts and validate round trips.
Canonical schema v5 and older v1 decoding remain supported.
- Re-encoding the supplied rich sample reduced the previous AI file from 96,592
to 74,134 bytes (23.3%); no data was dropped. This is a byte measurement, not a
tokenizer result or a universal size guarantee.
- Cache collector metadata, batch stage initialization/checksums/finalization,
reuse package inventories and batch selected sysctls. Five alternating local
before/after pairs reduced median quick/standard/max elapsed time by
42.5%/17.7%/3.9%; hardware/daemon performance varies. See `docs/AUDIT.md`.
- Use sysfs Bluetooth adapter counts automatically; query BlueZ only for focused
bluetooth/workstation/all targets with a three-second discovery limit. Failed
or truncated discovery leaves controller counts unknown.

### Portability and correctness

- Add Python timeout/owner-handoff paths and portable statvfs/procfs/boot/cron
observations. Bound metadata/detection and collector acquisition, and stop
nested producer trees before cleanup on timeout, failure or INT/TERM.
- Safely parse vendor os-release fallback, expose PID 1, require systemd runtime,
distinguish clients from running managers, and keep failed protection queries
separate from disabled LSM state. Avoid container reboot guesses.
- Filter Debian residual package records; avoid false zero/exact counts on failed
or truncated inventories. Preserve IPv6 routes and coexisting firewall evidence.
- Detect procfs truncation from captured bytes, preserve caller shell flags,
reject malformed catalogs/missing worker status/empty targets, and reconcile
final sanitized evidence/context budgets and graph integrity before publication.

### Documentation and release delivery

- Rewrite and align user/consumer/developer/security guides with current code;
document the full 25-collector catalog, profile settings and collector API.
Add troubleshooting and update issue/PR templates.
- Add reproducible source packaging from the committed tree and SHA-256 assets.
Successful trusted main CI triggers release preparation; extracted archives
pass checks/smoke and uploaded draft bytes are verified before publication.

### Validation and limits

- 31 regression scripts cover package families, models, managers, privacy,
timeout/tree cleanup, final budgets and type-sensitive AI round trips.
CI covers Python 3.9/3.11/3.13 plus Debian 12, Fedora 43, Arch and Alpine 3.22,
including all-profile archive/manifest/AI/privacy smoke checks.
- Container CI does not establish every live hardware/init/desktop configuration.
Native Gentoo/Nix/Slackware package inventories remain outside current support.
- Optional per-user runner probes still select setpriv by presence and can fail
with BusyBox setpriv under sudo without runuser. Publication handoff has the
capability-aware Python fallback; see `docs/TROUBLESHOOTING.md` for the gap.
- Canonical/evidence byte ceilings do not cap full graph/metadata, transient
acquisition or total archive size. Review bundles before sharing; zero known
high-confidence residuals does not prove arbitrary content secret-free.

## 1.0.0

Expand Down
19 changes: 16 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,22 @@ Keep historical changelog entries as history; use Unreleased for new behavior.
`VERSION` is the tool version source. Do not infer a new release number from a
schema bump or a merge. When preparing a release, intentionally update VERSION
and current-version examples, move Unreleased entries to the release section and
validate the exact tagged tree. No automated release-packaging workflow currently
ships in this repository; published archive/checksum examples depend on the
maintainer supplying those files. Preserve executable script modes.
validate the exact tagged tree. Preserve executable script modes.

`bash scripts/build-release.sh OUTPUT_DIR` creates the versioned source archive
and SHA-256 file from Git HEAD, not uncommitted files. Git and gzip/sha256sum are
build tools, not extra collection dependencies. Gzip timestamps are omitted for
repeatable bytes from the same committed tree. Existing output files are refused.

The Release workflow runs only after successful CI of a trusted main push. It
checks out that exact commit, skips already published versions, refuses conflicting
tags/drafts, builds/tests the extracted archive and verifies downloaded draft
assets before publishing as latest. It never consumes PR artifacts or runs for
PR CI. Main must still point at the tested commit at publication. A failed
same-commit draft can be resumed by rerunning the successful main CI run; a draft
for another commit requires maintainer review. `contents: write` is scoped to the
release job. Version changes on main therefore request a stable release after CI;
do not bump VERSION just to label an unreleased development commit.

PRs should explain the trigger, resulting behavior and validation/limits. Include
version/commit, distribution and relevant tool variants for portability changes.
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ test:
syntax: shell-check python-check

shell-check:
@find bin collectors lib tests -type f -name '*.sh' -print0 | xargs -0 -n1 bash -n
@find bin collectors lib tests scripts -type f -name '*.sh' -print0 | xargs -0 -n1 bash -n
@bash -n bin/linux-context

python-check:
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ without making a large command dump the initial prompt. Collection inspects the
machine; it does not perform remediation. Diagnostic data can still be sensitive,
so review a bundle before sharing it.

The checkout's version is defined by [`VERSION`](VERSION), currently `1.0.0`.
The checkout's version is defined by [`VERSION`](VERSION), currently `1.1.0`.
Changes under [Unreleased](CHANGELOG.md#unreleased) may be newer than a tagged
release with the same version string. Include a Git commit when reporting a
problem from a checkout.
Expand Down Expand Up @@ -259,7 +259,7 @@ to identify the code you run.
If a release provides a checksum file, verify it before extraction:

```bash
sha256sum -c linux-context-v1.0.0.tar.gz.sha256
sha256sum -c linux-context-v1.1.0.tar.gz.sha256
```

This example requires the named files from that release; no checksum file is
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.0
1.1.0
3 changes: 2 additions & 1 deletion docs/TROUBLESHOOTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ From the source root:
git rev-parse HEAD
```

`VERSION` may still be `1.0.0` on a checkout containing newer Unreleased changes.
`VERSION` may match a tagged release (for example `1.1.0`) even when a checkout
contains newer Unreleased changes.
List commands inspect metadata, not actual host support. The launcher expects
Linux, Bash 4+, Python 3.9+, executable collectors, common utilities and archive
tooling unless `--no-archive` is used. Use the entire trusted project directory,
Expand Down
Loading