Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
bd885df
fix(release): isolate hook Git state
Cranot Jul 20, 2026
98c5ee0
test(atomic): accept stronger POSIX tamper guard
Cranot Jul 20, 2026
146c0bc
test(dead): execute installed module fail closed
Cranot Jul 20, 2026
c3150cf
fix(hooks): restore executable metadata
Cranot Jul 20, 2026
ab096e1
fix(mcp): use active interpreter for subprocess tools
Cranot Jul 20, 2026
721cb30
test(mcp): pin early receipt redirect rejection
Cranot Jul 20, 2026
49ef952
test(atomic): preserve POSIX identity-safe cleanup contract
Cranot Jul 20, 2026
fbe4530
fix(release): harden ledger and compile freshness
Cranot Jul 20, 2026
158821d
fix(evidence): preserve replay verification context
Cranot Jul 20, 2026
e05365d
fix(index): recover interrupted builds before analysis
Cranot Jul 20, 2026
c097dc3
docs(release): document crash-safe analysis contracts
Cranot Jul 20, 2026
2fb05d0
docs(release): set 13.10 publication date
Cranot Jul 20, 2026
2803efe
fix(index): serialize durable lifecycle ownership
Cranot Jul 20, 2026
c59506f
fix: preserve transcript safety error contract
Cranot Jul 20, 2026
4dfc375
test: support descriptor-based temp cleanup
Cranot Jul 20, 2026
5c9e93b
fix: normalize transcript containment races
Cranot Jul 20, 2026
bcbe6e4
test: bind verify hook fixture to receipt protocol
Cranot Jul 20, 2026
8f6dc8e
fix: bind verify receipts to target scope
Cranot Jul 20, 2026
b0128e2
fix: harden release surface verification
Cranot Jul 20, 2026
06aee3e
fix: constrain hook history trust set
Cranot Jul 20, 2026
a3acf8b
fix: close landing page count drift gaps
Cranot Jul 20, 2026
f3bb841
test: use canonical root in recovery subprocesses
Cranot Jul 21, 2026
09f71b0
chore: finalize 13.10 release metadata
Cranot Jul 21, 2026
febeb02
fix: harden prerelease CI validation
Cranot Jul 21, 2026
8be05a2
fix: preserve virtualenv component isolation
Cranot Jul 21, 2026
f22e7e7
chore: align release publication date
Cranot Jul 22, 2026
77d5cd9
docs: align release announcement date
Cranot Jul 22, 2026
42f2204
fix: bound native release gate threads
Cranot Jul 22, 2026
a7fd57f
chore: set release publication date
Cranot Jul 22, 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
Empty file modified .githooks/commit-msg
100644 → 100755
Empty file.
Empty file modified .githooks/pre-commit
100644 → 100755
Empty file.
14 changes: 14 additions & 0 deletions .githooks/pre-push
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ set -e

REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"

# Git exports repository-local control variables to hooks. They are valid
# while this hook operates on REPO_ROOT, but the FAST gate runs pytest suites
# that create and mutate their own temporary repositories. In a linked
# worktree, inheriting GIT_INDEX_FILE makes a fixture's `git add` replace the
# real worktree index. Resolve the root first, then clear every variable Git
# itself classifies as repository-local and anchor subsequent Git commands by
# cwd. Author/committer identity and other non-local Git settings survive.
GIT_LOCAL_ENV_VARS="$(git rev-parse --local-env-vars 2>/dev/null || true)"
for var in $GIT_LOCAL_ENV_VARS; do
unset "$var"
done
unset GIT_LOCAL_ENV_VARS
cd "$REPO_ROOT"

PY="${PYTHON:-python}"
if ! command -v "$PY" >/dev/null 2>&1; then
if command -v py >/dev/null 2>&1; then
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/roam-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -394,8 +394,12 @@ jobs:
fetch-depth: 0 # full history required for pr-risk and git analysis
persist-credentials: false

# Bind prerelease self-analysis to this exact local action checkout.
# Explicit source mode has no PyPI fallback, so an unpublished version
# can test itself without weakening downstream exact-version defaults.
- uses: ./ # local composite action — tests action.yml itself
with:
version: 'source'
commands: 'health pr-risk'
changed-only: 'true'
sarif: 'true'
Expand Down
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

## [13.10.0] — 2026-07-19
## [13.10.0] — 2026-07-23

### Added
- **`roam savings` + `roam savings-backfill` — proof-oriented Procedure Foundry telemetry** — prompt compilation and Claude Stop hooks now share a per-turn `episode_id`, preserving compile identity, terminal outcome, verification state, changed-file count, and privacy-preserving diff identity in an append-only local ledger. `savings` materializes the ledger into SQLite and withholds savings claims until known-answer canaries, telemetry integrity, hook-version coverage, compile joins, terminal coverage, and explicit verification-health coverage pass. `savings-backfill` mines local Claude/Codex transcripts into a replaceable historical discovery snapshot: raw prompts, responses, paths, shell values, and tool arguments are excluded, while keyed intent fingerprints plus sanitized shell templates/sequences/n-grams retain repeated workflow structure. Historical frequency can nominate automation experiments but can never satisfy the prospective evidence gate.
Expand All @@ -23,7 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
- **`roam at` ranges and whole-symbol reads** — `roam at FILE:START-END` replaces arbitrary `sed` windows, while `roam at FILE:LINE --whole-symbol` expands a search hit to its complete indexed function/class. `--max-lines` bounds output and reports truncation instead of silently dropping source.

### Changed
- **The canonical pre-push gate now has an explicit bounded worker budget** — full-suite pytest execution defaults to at most four workers, accepts a validated `--workers 1..4` override, and schedules by file with `--dist loadfile`. This prevents a large host from translating CPU count into destructive memory and temp-directory contention while retaining deterministic parallel release coverage.
- **The canonical pre-push gate now has an explicit bounded concurrency budget** — full-suite pytest execution defaults to at most four workers, accepts a validated `--workers 1..4` override, schedules by file with `--dist loadfile`, and forces native BLAS/OpenMP runtimes to one thread per worker. This prevents a large host—or poisoned inherited thread settings—from multiplying a bounded worker pool into destructive CPU, memory, and temp-directory contention while retaining deterministic parallel release coverage.
- **CI and the reusable action now resolve only reviewed dependency inputs** — every source-checkout workflow environment is created from the exact `uv.lock`, release-only tools install from hash-locked wheelhouses, dependency auditing checks the installed locked environment without re-resolving it, and every third-party action is pinned by full commit SHA. The composite action validates all caller-controlled inputs before they reach shell or JavaScript, maps expressions through bounded environment variables, and requires an explicit opt-in before resolving the moving `latest` release.
- **Generated and copy-paste CI templates inherit the same fail-closed policy** — GitHub, GitLab, Azure, Jenkins, CircleCI, and Bitbucket templates pin Roam 13.10.0; GitHub actions and supported runners are immutable, and container-based examples bind verified multi-architecture OCI digests. Required Guard, health, SLSA, and SARIF evidence fails closed; deliberately advisory scans preserve stdout, stderr, exit code, and an explicit state sidecar instead of using blanket `|| true` or `continue-on-error` fallbacks.
- **Roam now owns its Claude hook maintenance authority end to end** — canonical v11 hook bodies invoke maintenance-only `verify` and `index` operations with the explicit audited mode override themselves. Compile Code no longer rewrites installed Roam source or hook bodies, so one signed Roam artifact defines the hook behavior on every client.
Expand All @@ -32,6 +32,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
- **Generated constitution upgrades are provenance-bound** — fresh `constitution init` files record a semantic digest for the generated mode snapshot. New defaults flow into runtime policy only while that snapshot remains unchanged; customized and legacy policies stay authoritative. `roam constitution check` now exposes migration state, while `roam constitution upgrade` previews per-mode changes and requires both explicit replacement acknowledgement and the preview's expected digest before replacing an unproven policy.

### Fixed
- **Prerelease self-analysis no longer depends on an artifact that cannot exist yet** — the composite action now has an explicit `version: source` mode that installs from its validated action checkout, requires matching non-editable PEP 610 provenance, and never falls back to PyPI. Roam's own PR self-analysis uses this mode, while downstream defaults remain pinned to the exact published release.
- **Interrupted indexing can no longer masquerade as a complete graph** — every index run acquires a held OS lock and publishes the same PID, process-start identity (where available), and random generation in its lock and durably synced `in_progress` marker before the first SQLite mutation. Recovery never unlinks a lock pathname, release only unlocks the exact owning descriptor, and consumers fail closed on unprovable ownership while accepting a completed marker despite stale PID reuse. A proven interrupted generation or stale pre-marker numeric lock is carried by the claim and overrides every direct caller to `force=True, light=False`; watch and Verify refreshes cannot certify a partial DB through an incremental/light run. Genuinely complete legacy DBs and released legacy locks retain normal incremental behavior. `complete` is published only after SQLite checkpoint/fsync and a durable marker rename. This closes the crash/OOM case where a present-but-partial database returned plausible empty results and caused Compile probes to re-derive answers.
- **PR Replay preserves honest verification context on clean checkouts** — every successful replay carries a small deterministic report-generation manifest, so evidence coverage distinguishes “the replay producer ran, but no verification proof exists” from “verification was never considered.” The manifest earns only partial Q7 coverage; it never fabricates `tests_run` or upgrades the packet to verified.
- **Python 3.14 no longer strips MCP alias annotations at the concurrency boundary** — Python 3.14 stopped copying a runtime-assigned `__annotations__` mapping through `functools.wraps`, leaving FastMCP/Pydantic with a synthesized signature but no matching type hints and crashing server import with `KeyError: 'symbol'`. The guard now preserves a private copy of realized annotations for sync and async tools; direct Pydantic schema generation and FastMCP 3.4 registration are regression-tested without constraining FastMCP to an older major.
- **PR Replay policy assurance is deterministic on fresh checkouts** — every successful replay now records the selected current-detector-set policy as a typed `not_evaluated` decision, explicitly avoiding a fabricated pass/fail result. Constitution, rule, permit, lease, audit-trail, and review decisions remain additive, while Q6 no longer depends on incidental local `.roam/` history.
- **MCP receipts and signed run events cannot cross repository roots** — the dispatcher snapshots one canonical invocation root and active run before execution, then binds policy, receipt placement, and HMAC linkage to that immutable context even if CWD or `ROAM_RUN_ID` changes during the call. Foreign, inactive, malformed, hard-linked, symlinked, NTFS-junction, redirected, or concurrently replaced receipt/metadata/event/key paths fail closed. Ledger appends are inode-pinned, and POSIX receipt replacement uses directory handles so a detached-parent race cannot strand evidence outside the verified tree.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ refactors, never lose entries).

## What's New

**v13.10 (2026-07-19) — repeated work becomes measurable procedures, and post-edit verification becomes proof-complete.** Privacy-preserving transcript/shell-template mining can nominate repeated-work interventions without exposing raw prompts or claiming causal savings; `roam savings` promotes only prospectively joined, integrity-checked outcomes. The Claude adapter now binds every edited turn to a strict Verify receipt and blocks unavailable, malformed, incomplete, or failing evidence. Roam owns the canonical hooks end to end—Compile Code no longer rewrites installed source. Full notes: [CHANGELOG.md](CHANGELOG.md).
**v13.10 (2026-07-23) — repeated work becomes measurable procedures, and post-edit verification becomes proof-complete.** Privacy-preserving transcript/shell-template mining can nominate repeated-work interventions without exposing raw prompts or claiming causal savings; `roam savings` promotes only prospectively joined, integrity-checked outcomes. The Claude adapter now binds every edited turn to a strict Verify receipt and blocks unavailable, malformed, incomplete, or failing evidence. Interrupted indexes carry a generation-bound, durably synced lifecycle marker and force a full non-light rebuild before analysis regardless of the direct caller; completion is published only after SQLite checkpoint/fsync, so a crash cannot turn partial graph state into plausible empty answers. Roam owns the canonical hooks end to end—Compile Code no longer rewrites installed source. Full notes: [CHANGELOG.md](CHANGELOG.md).

<details>
<summary><strong>Earlier release notes — v13.6 → v13.0</strong></summary>
Expand Down
83 changes: 67 additions & 16 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ branding:

inputs:
version:
description: 'Exact roam-code version to install; use latest only with allow-latest=true'
description: 'Exact roam-code version to install; source installs this action checkout; latest requires allow-latest=true'
required: false
default: '13.10.0'
allow-latest:
Expand Down Expand Up @@ -190,7 +190,11 @@ runs:

validate_scalar "version" "${INPUT_VERSION}" 64
version_re='^[0-9]+(\.[0-9]+){1,3}((a|b|rc)[0-9]+)?(\.post[0-9]+)?(\.dev[0-9]+)?$'
if [[ "${INPUT_VERSION}" == "latest" ]]; then
if [[ "${INPUT_VERSION}" == "source" ]]; then
if [[ "${INPUT_ALLOW_LATEST}" != "false" ]]; then
fail_input "version" "source requires allow-latest=false"
fi
elif [[ "${INPUT_VERSION}" == "latest" ]]; then
if [[ "${INPUT_ALLOW_LATEST}" != "true" ]]; then
fail_input "version" "latest requires allow-latest=true"
fi
Expand Down Expand Up @@ -338,22 +342,69 @@ runs:
env:
ROAM_VERSION: ${{ steps.validate-inputs.outputs.version }}
ALLOW_LATEST: ${{ steps.validate-inputs.outputs.allow-latest }}
ROAM_ACTION_PATH: ${{ steps.validate-inputs.outputs.action-path }}
run: |
set -euo pipefail
if [[ "${ROAM_VERSION}" == "latest" ]]; then
if [[ "${ALLOW_LATEST}" != "true" ]]; then
echo "::error::Mutable latest install was not explicitly authorized" >&2
exit 2
fi
python -m pip install --quiet --disable-pip-version-check roam-code
else
python -m pip install --quiet --disable-pip-version-check "roam-code==${ROAM_VERSION}"
INSTALLED_VERSION=$(python -c 'from importlib.metadata import version; print(version("roam-code"))')
if [[ "${INSTALLED_VERSION}" != "${ROAM_VERSION}" ]]; then
echo "::error::Installed roam-code ${INSTALLED_VERSION}; expected ${ROAM_VERSION}" >&2
exit 1
fi
fi
case "${ROAM_VERSION}" in
source)
if [[ "${ALLOW_LATEST}" != "false" ]]; then
echo "::error::Source install must not enable the mutable latest path" >&2
exit 2
fi
if [[ ! -f "${ROAM_ACTION_PATH}/pyproject.toml" ]]; then
echo "::error::Source install requires pyproject.toml in the validated action checkout" >&2
exit 1
fi
python -m pip install --quiet --disable-pip-version-check --no-cache-dir --force-reinstall "${ROAM_ACTION_PATH}"
python - <<'PY'
import importlib.metadata
import json
import os
from pathlib import Path
from urllib.parse import urlparse
from urllib.request import url2pathname

expected = Path(os.environ["ROAM_ACTION_PATH"]).resolve(strict=True)
distribution = importlib.metadata.distribution("roam-code")
installed_name = str(distribution.metadata.get("Name", "")).lower().replace("_", "-")
if installed_name != "roam-code":
raise SystemExit(f"installed unexpected distribution name: {installed_name!r}")
raw_direct_url = distribution.read_text("direct_url.json")
if not raw_direct_url:
raise SystemExit("source install produced no PEP 610 direct_url.json")
direct_url = json.loads(raw_direct_url)
parsed = urlparse(direct_url.get("url", ""))
if parsed.scheme != "file" or parsed.netloc not in ("", "localhost"):
raise SystemExit("source install provenance is not a local file URL")
if parsed.params or parsed.query or parsed.fragment:
raise SystemExit("source install provenance contains unexpected URL components")
actual = Path(url2pathname(parsed.path)).resolve(strict=True)
if actual != expected:
raise SystemExit(f"source install provenance mismatch: {actual} != {expected}")
dir_info = direct_url.get("dir_info")
if not isinstance(dir_info, dict):
raise SystemExit("source install provenance has no directory information")
editable = dir_info.get("editable")
if editable is not None and editable is not False:
raise SystemExit("source install unexpectedly used editable mode")
PY
;;
latest)
if [[ "${ALLOW_LATEST}" != "true" ]]; then
echo "::error::Mutable latest install was not explicitly authorized" >&2
exit 2
fi
python -m pip install --quiet --disable-pip-version-check roam-code
;;
*)
python -m pip install --quiet --disable-pip-version-check "roam-code==${ROAM_VERSION}"
INSTALLED_VERSION=$(python -c 'from importlib.metadata import version; print(version("roam-code"))')
if [[ "${INSTALLED_VERSION}" != "${ROAM_VERSION}" ]]; then
echo "::error::Installed roam-code ${INSTALLED_VERSION}; expected ${ROAM_VERSION}" >&2
exit 1
fi
;;
esac
python -m pip check

# ---- 6. Build/refresh index ---------------------------------------------
Expand Down
7 changes: 6 additions & 1 deletion docs/ci-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ uploads SARIF findings to GitHub Code Scanning, and enforces quality gates.

| Input | Default | Description |
|-------|---------|-------------|
| `version` | `13.10.0` | Exact roam-code version to install from PyPI. Accepts a closed PEP 440-style release grammar; URLs, VCS references, pip options, whitespace, and local-version suffixes are rejected. |
| `version` | `13.10.0` | Exact roam-code version to install from PyPI. The explicit `source` mode installs from the selected action checkout; `latest` requires `allow-latest: true`. URLs, VCS references, pip options, whitespace, and local-version suffixes are rejected. |
| `allow-latest` | `false` | Explicit opt-in required when `version: latest` is requested. This keeps the mutable package path visible in review. |
| `commands` | `health` | Space-separated roam commands to run. Each command produces JSON output that feeds into the PR comment and quality gate. |
| `changed-only` | `false` | Incremental CI mode. Adapts supported commands to changed files and transitive dependents (when detectable). |
Expand All @@ -135,6 +135,11 @@ ranges, because a copied downstream action cannot consume this repository's
check out roam-code and materialize `uv.lock`, as Roam's own source workflows
do. `version: latest` trades that release-level reproducibility for automatic
first-party upgrades and therefore requires `allow-latest: 'true'`.
`version: source` is intended for prerelease validation of a selected action
checkout. It installs only from the action's validated `github.action_path`,
requires matching non-editable PEP 610 provenance, and has no PyPI fallback.
It binds first-party Roam code to that checkout, while transitive dependencies
still follow declared package ranges rather than this repository's `uv.lock`.

## Outputs

Expand Down
Loading