fix: workspace rules state lives outside the project
Issue for this PR
No public issue on purpose: this fixes a security bug in the released v0.12.9, and it is tracked internally until a fixed release ships. A maintainer can confirm.
Type of change
- Bug fix
- New feature
- Refactor / code improvement
- Documentation
What does this PR do?
The workspace rules state was read from .altimate-code/learn/workspace-rules/rules.json in the working tree and acted on with the signed-in person's credential:
- its queued proposals were sent;
- its pending approvals were replayed, including "approve anyway";
- its review notices were shown.
The file is git-ignored, but a branch, a merge or a pull can still bring one in. A state with no scope was adopted as is. In a workspace owner's client, a planted outbox entry or approval became an active team rule for every member. This affects main since #1605 and the v0.12.9 release.
The fix stops trusting anything about workspace rules that comes from the repository:
- The state lives outside the project, at
$XDG_STATE_HOME/altimate-code/workspace-rules/<sha256 of the project's real path>.json(usually~/.local/state/...). The file is0600and its directory0700. Arules.jsonin the project is never read.- The path is in
rules/state-path.ts, which the store loads lazily, because importing@/globalat load time is circular. - It uses the same key as the workspace link cache (the folder's real path), so two clones or worktrees of one repository each keep their own state.
- The path is in
- Team rules are delivered from that state.
RulesStore.vouchedbuilds each rule's lesson (wording, pin, tags, paths) from the state, and delivery gives those. Theworkspace-rules/approved.jsoncopy in the project decides nothing that is delivered. A resumed session keeps a cached team rule only while the state still holds its id. - Only for the account that pulled them. A pull records the credential it ran with (
pulled_with). Rules are vouched for only while that credential is signed in, so a sign-out or a switch to another account stops them at once instead of at the next sync. session/prompt.ts: the "can this project have team rules" check looks for the state file in its new location.
No migration, on purpose. An old in-project rules.json is exactly what must not be trusted. After upgrading, a client re-pulls its team rules on the next sync. These, stored by v0.12.9 in the old location, are not carried over:
- proposals queued but never sent;
- dismissed topics, and the personal learning choice: someone who turned learning off for themselves in
/rulesis back on the workspace setting; - which usage was already reported: the first report after upgrading can count sessions of the last 30 days again, so "applied in N sessions" and "corrected again" can be inflated once;
- session snapshots are kept as they are. A session that ran on v0.12.9 and froze a team rule's wording keeps it on resume while the state holds that rule's id.
Not covered here:
- Recurrence analysis and
learn showstill read the project's team-rules copy. A planted copy can only affect local analysis: it can make a correction topic look covered, which suppresses a proposal, andlearn showwould list it. It is never sent or delivered. - Usage reporting is still built from the project's
.sessionssnapshots and recurrence signals. A branch that commits snapshots can inflate the "applied" and "corrected again" counts sent with the person's credential. Counts only: nothing is proposed, approved or delivered. - The learn lock directory is still in the project, as for every learn store. A lock directory committed to a branch would look held after checkout and block learn writes in that project until it goes stale (10 minutes).
The Claude Code and Codex plugin PRs (AltimateAI/altimate-claude-plugin#13, AltimateAI/altimate-codex-plugin#7) depend on this fix, and #1626 rebases onto it.
How did you verify your code works?
bun run typecheckand the marker guard (analyze.ts --markers --strict).- New tests:
- a state planted in the project, with a queued proposal marked for approval, a pending approval and a future-dated notice, is not read, and
pushmakes no request (everyfetchcounted); - the real state file is outside the project and private;
- a planted team-rules copy, or one whose wording differs from the state's, is not delivered;
- a copy that keeps an approved rule's id and wording but changes its pin or paths is delivered as the state holds it;
- a resumed session drops its cached team rules while the rules state can't be read;
mayHaveWorkspaceRulescounts a state in the new location, with no workspace binding, and ignores arules.jsonin the project;- team rules stop at once after a sign-out or a switch to another account, cached section included;
- a pull that finishes after someone else signed in is not stamped with its credential.
- a state planted in the project, with a queued proposal marked for approval, a pending approval and a future-dated notice, is not read, and
- rules, learn, rules-routes and learn-prompt suites: 2201 pass, 0 fail. A wider run (session, server, cli, telemetry) had 9 failures, which also fail on a clean
mainlocally or pass alone (timeouts under load):experimentalandmcpHttpApi,--time-budget,serves read routes,held-back promotion.
Cost side effects
None. No model calls, queries or cloud resources are added. The credential check reads the local credential file.
Screenshots / recordings
Not a UI change.
Checklist
- I have tested my changes locally
- I have not included unrelated changes in this PR
- I checked for cost side effects (model calls, queries or cloud resources that grow with user data) and described them, or wrote "none"
🤖 Generated with Claude Code
Summary by CodeRabbit
- Bug Fixes
- Workspace rules are delivered only while signed in to the account that synced them. Project copies alone do not qualify, and delivery stops after sign-out or an account change.
- Project-local rule files no longer determine workspace-rule settings or which team rules are delivered.
- Documentation
- Clarified that workspace rules and the
/ruleslearning choice are stored in user state outside the project.
- Clarified that workspace rules and the