Skip to content

Release 0.9.1 - #149

Merged
jothimani-rajendran merged 10 commits into
mainfrom
claude/epic-gauss-l6f2i4
Sep 20, 2026
Merged

jothimani-rajendran merged 10 commits into
mainfrom
claude/epic-gauss-l6f2i4

Conversation

@jothimani-rajendran

@jothimani-rajendran jothimani-rajendran commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator

What

The release-prep PR for 0.9.1, one commit. Everything the release ships is already on main in #148; this moves the version and cuts the notes.

  • pyproject.toml, CITATION.cff, action.yml's version default and the docs/installation.md example → 0.9.1. tests/test_release_surfaces.py holds the last two to pyproject.toml, so a bump that forgot either fails the suite.
  • CHANGELOG.md: ## Unreleased becomes ## 0.9.1 — … with no empty Unreleased heading above it, because release.yml extracts the first ## section as the release notes.
  • docs/assets/social-preview.{svg,png} regenerated: they print the version.

Why a patch and not 0.10.0. No emitted byte changes: the runtime goldens are untouched and every adopter's chock sync --check stays clean. The two installer fixes (a policy-less repo keeps the runtime its arm hook runs; the runtime is written LF on Windows) make the install match what it already claimed. The baseline check, the gate-log ignore rule and the pin-github-actions dogfood are additive.

After the tag. The tag runs the full CI matrix, Windows and macOS included. v0.9.0's run was red on all three Windows validate jobs (run 35519645109); the fixes in #148 were made from that job log without a Windows machine, so the v0.9.1 tag run is their confirmation. Check it. Then chock-quickstart#8 can merge.

Round-2 review fixes

Twelve findings from the pre-0.9.1 deep review, each in its own commit:

  1. chock add refuses a catalog pack carrying a symlink before hashing it (critical).
  2. chock add refuses a manifest whose id differs from its folder name.
  3. chock add --ref honours a commit id and a local checkout; a path-like artifact id is a clean error, not a traceback.
  4. chock check --only verify attests every pack sync compiles, nested packs included, and fails cleanly on a lockfile that is not valid JSON (high).
  5. The baseline check compares policy reach, not surface count, and errors instead of passing on an unresolvable base ref or invalid YAML (high).
  6. Config shapes (policies: null, a bare string in disabled, a null or string override, surfaces: 5) are normalised instead of crashing or misreading, and POL-1 holds in sync too.
  7. validate names the exact config-shape mistake instead of crashing.
  8. egress_allowlist reads a URL the way a fetch library will, closing several host-spelling bypasses (medium).
  9. validate rejects a gate regex that fails to compile, instead of tracebacking at the first commit it guards (medium).
  10. sync wires in-agent hooks only for supported_agents, not every vendor chock knows (medium).
  11. chock review require judges a pull request by the stricter of the base's review policy and the head's, so the change being judged cannot choose what it is judged on (critical).
  12. spec/policy-spec.md, spec/enforcement-matrix.md and CHANGELOG.md record all of the above.

Verification

ruff check .                                 All checks passed!
ruff format --check .                        286 files already formatted
pytest -q                                    1375 passed, 6 skipped
pytest acceptance/                           21 passed
chock check                                  [PASS], 0 errors, 0 warnings
chock sync --repo . --check                  Compiled artifacts match their manifests.
chock check --only verify                    verify: all packs match lockfile
chock registry scan                          no diff
docs/assets/gen_brand_assets.py --check      social-preview.svg is current

Not in this PR

The tag. release.yml publishes to PyPI on a v* tag whose version matches pyproject.toml — your click after merge.

Definition of done

  • chock check → 0 errors, 0 warnings; the two [INFO] SEC-4 lines on injection-defense/evals/suite.yaml predate this PR
  • chock check --only matrix passes; no behaviour change
  • chock sync --repo . --check clean
  • chock check --only verify clean
  • Registry rescanned; no policy added or changed
  • pytest -q green
  • pytest acceptance/ green
  • Existing artifacts migrated — none needed
  • Touched manifests: version bump + changelog — this is the version bump + changelog
  • ruff check . and ruff format --check . clean

Claims

  • No surface is described as enforcing more than it installs. No surface, emitter or installer logic changes in this PR.

🤖 Generated with Claude Code

jothimani-rajendran and others added 10 commits September 20, 2026 19:48
Version 0.9.1 in pyproject.toml, CITATION.cff, action.yml's default and the
docs/installation.md example (tests/test_release_surfaces.py holds the last two to
pyproject). The changelog's Unreleased section becomes the 0.9.1 heading, so the
release workflow's first-section extraction yields these notes; brand assets
regenerated for the version they print. Everything else on main since 0.9.0 is
what this release ships: #148.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Jothi Mani Rajendran <250249270+jothimani-rajendran@users.noreply.github.com>
…r a local path or commit id

shutil.copytree dereferenced symlinks, so a catalog pack carrying leak -> ~/.ssh/id_rsa
installed the adopter's private key as a regular file; a manifest whose id differed from
its folder name became two policies (lock and compiled tree under one name, config under
the other); a commit id failed against git clone --branch, and --ref was silently ignored
against a local path; a path-like artifact id tracebacked instead of erroring cleanly.

add() now refuses a pack carrying a symlink or a foreign manifest id before hashing,
routes a local path plus --ref through git so the ref is honoured, falls back to git
init/fetch/checkout FETCH_HEAD when a ref cannot be branch-cloned, and main() catches
ValueError so a bad id is a clean error.

Signed-off-by: Jothi Mani Rajendran <250249270+jothimani-rajendran@users.noreply.github.com>
…cannot be read

An empty or missing chock.lock over installed packs verified clean -- nothing was
compared -- and a pack nested under .agents/policies/<group>/ was compiled but never
locked, since build_lock() read one level while discover_policy_dirs() reads every
level; a lock that was not JSON tracebacked instead of failing.

read_lock() now raises LockError on invalid JSON or shape, which verify_lock() reports
as a failure; build_lock() iterates discover_policy_dirs() and records a nested pack's
path; verify_lock() flags every discovered pack absent from the lock and resolves a
pack from its recorded path or the top-level default.

Signed-off-by: Jothi Mani Rajendran <250249270+jothimani-rajendran@users.noreply.github.com>
…g on a bad ref or YAML

{git-hook, ci-gate} -> {git-hook, ambient-rule} kept the same surface count and passed a
strict-subset comparison while losing a real gate; a base ref that did not resolve read
as "no config there", which means every policy enabled, so an unfetched base silently
passed; a base config that was not valid YAML tracebacked.

weakenings() now flags a head whose surfaces are not a superset of the base's, not just
a smaller set; config_at() resolves the ref first and raises BaselineError when it
cannot, and YAML errors raise the same; check_baseline() reports either as an error
finding ending "nothing was compared".

Signed-off-by: Jothi Mani Rajendran <250249270+jothimani-rajendran@users.noreply.github.com>
… and name the mistake at validate

policies: null, overrides: null, a null or string override, and disabled: scan-secrets
(iterated as letters) each crashed the resolver or misread the config as something other
than what it says; a mandatory policy listed in policies.disabled was skipped by sync,
even though POL-1 says the config cannot disable it.

A _policies() normaliser treats a missing or malformed policies/overrides block as empty,
overrides keep only mapping values, disabled and surfaces accept a bare string as one id,
and policy_status() applies disabled only when the policy is not mandatory. validate's
check_policy_toggles() is split into a _toggles() helper (complexity) and reports a
non-mapping policies, non-list disabled, non-mapping override, and an unknown or
malformed surfaces value as named errors instead of crashing -- including on a
non-mapping override for a block-severity security guard, which the shape check used to
report before an unguarded second lookup crashed on the same value.

Signed-off-by: Jothi Mani Rajendran <250249270+jothimani-rajendran@users.noreply.github.com>
https:\\evil.io, https:/evil.io and https:///evil.io yielded no host and passed;
example.com\@evil.io is one host to a browser and another to curl; evil.io%00.example.com
matched the allowlist's suffix once the null byte was ignored.

_AUTHORITY_RE gains a (?:https?|wss?|ftp):/+ alternative so a run of slashes after the
scheme still matches, and the authority exclusion class no longer excludes a backslash;
_hosts_in() scans the raw text, the percent-decoded text, and both with backslashes
turned to slashes, so every reading a client could take is checked. _ascii_host()
punycodes a non-ASCII host and refuses anything outside the DNS/IP character set as an
undecidable host, which is a block, not a match.

Signed-off-by: Jothi Mani Rajendran <250249270+jothimani-rajendran@users.noreply.github.com>
content_pattern: "(" passed schema validation and sync, then every commit it guarded
died in a re.error traceback instead of being refused up front.

After schema validation, every string param whose name ends _pattern, _regex or _pragma
is compiled; a re.error becomes a named validate finding instead of a runtime crash.

Signed-off-by: Jothi Mani Rajendran <250249270+jothimani-rajendran@users.noreply.github.com>
… chock knows

chock init . --agents claude followed by sync wrote ten vendors' hook files and
runtimes (.cursor/, .codex/, .windsurf/, .devin/, ...), and the SessionStart arm hook
installed whether or not claude was named.

wired_vendors(agents) filters WIRED_VENDORS through CHOCK_AGENT for the requested
agents; the witness and the install loop both use it, and the SessionStart hook installs
only when claude_code is among the wired vendors. docs/cli-reference.md's sync
paragraph is rewrapped to say so within its existing line count.

Signed-off-by: Jothi Mani Rajendran <250249270+jothimani-rajendran@users.noreply.github.com>
…ad's

Required checks, the check registry, attestation_floor and unattestable_paths were all
read from the pull request's own .chock/config.yaml, so the change being judged chose
what it was judged on; and a recorded failure passed require when no required set
existed at all.

New src/chock/review/policy.py (evidence.py was at the 300-line budget) holds
BUILTIN_CHECKS, DEFAULT_UNATTESTABLE and the policy readers. review_policies(root,
base_ref) returns the base's and the head's policy, base read via `git show
<base>:.chock/config.yaml`; required_checks and unattestable_paths are the union across
both, attestation_floor the higher, and check_registry keeps the base's argv for a check
both define. base_ref is threaded through build, verify, require, command_set_hash and
review/cli.py's emit/verify/require. require() refuses any recorded fail regardless of
whether a required set exists, keeping the "required check(s) recorded failing" wording
when every failing check is required.

_touched_unattestable_paths() also now threads base_ref into unattestable_paths(): it
previously read only the head's list, so a head could touch a path the base protects
and drop it from its own unattestable_paths to skip the attestation floor entirely, even
though attestation_floor() and unattestable_paths() themselves already read the stricter
union -- the floor was right, but nothing was ever found to apply it to.

docs/reviewer-evidence.md's judgement table (row 4: "any check ... required or not") and
a new paragraph cover the base/head union.

Signed-off-by: Jothi Mani Rajendran <250249270+jothimani-rajendran@users.noreply.github.com>
spec/policy-spec.md's POL-1 bullet now says sync compiles a mandatory policy in full
whatever policies.disabled says; spec/enforcement-matrix.md's POL-1 row notes that
policy_status() ignores the entry. CHANGELOG.md's 0.9.1 section gets one bullet per
finding: chock add's symlink/foreign-id/--ref fixes, verify attesting every compiled
pack, the baseline check comparing reach instead of size, config shape normalisation
with POL-1 held in sync, egress_allowlist's URL reading, gate regex validation at
validate, sync wiring only supported_agents, and review require judging by the stricter
of base and head.

Signed-off-by: Jothi Mani Rajendran <250249270+jothimani-rajendran@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant