forked from bchanot/claude
chore(memory): BDR-090, LRN-153, journal — autoMode tier rebuild
BDR-090 records why the ask tier was abandoned rather than repopulated, the three alternatives rejected, and the deliberate caveat that the guardrail hard_deny bars removing a deny entry but not adding one. LRN-153 records the two traps the block carries: every autoMode list is a full replacement without "$defaults", and a user-scope block reaches every project on the machine. TODO also logs F1-F3, found but not fixed: .claude/settings.local.json is a 14.6 KB shadow copy of the global settings at higher precedence, including a PreToolUse hook whose script does not exist.
This commit is contained in:
@@ -99,6 +99,7 @@ rules:
|
||||
| BDR-087 | 2026-09-03 | Stop hook = attention signal only, never control flow; one script for Notification + Stop | accepted |
|
||||
| BDR-088 | 2026-09-15 | gstack Playwright bump shared via lib, re-applied after submodule update; update helper never touches the submodule tree | accepted |
|
||||
| BDR-089 | 2026-09-15 | No Playwright browser-cache pruner; read-only doctor report — .links proved 0 bytes reclaimable | accepted |
|
||||
| BDR-090 | 2026-09-15 | Destructive shell work → autoMode soft_deny/hard_deny; `ask` tier abandoned (inert under auto) | accepted |
|
||||
|
||||
---
|
||||
|
||||
@@ -1146,3 +1147,13 @@ Branch feature/user-writing-web-rules, UNMERGED (human gate).
|
||||
- **Alternatives rejected**: hand-rolled pruner guarded on "revision resolved by gstack's local playwright" (the originally requested shape) — that guard keeps 1228 and DELETES 1243, breaking gsd-pi. The guard was wrong, not just its implementation.
|
||||
- **Status**: accepted.
|
||||
- **Reference**: commit 2cebecb. Links [[LRN-151]], [[BDR-088]].
|
||||
|
||||
## BDR-090 — Destructive shell work → autoMode soft_deny/hard_deny; `ask` tier abandoned
|
||||
- **Date**: 2026-09-15
|
||||
- **Decision**: 10 rules leave the static tiers (user's own edit): `rsync` `kill -9` `killall` `pkill` out of `deny`; `python3 -c` `python -c` `xargs` `sed` `cp` `mv` out of `ask`. Cover rebuilt in `autoMode` — 7 `soft_deny` (write outside cwd, `rsync --delete`, SIGKILL/kill-by-name, in-place edit spanning >1 file, directory move, inline interpreter or `xargs` that deletes or writes outside cwd) + 3 `hard_deny` (secret exfiltration, prod deploy, disarming guardrails). Intent clears a soft block for the CURRENT TURN only — encoded as a rule line, no setting exists for it. `classifyAllShell` stays false. `permissions.deny` +10 `.env` reader rules (`sed awk cut tr sort uniq diff od xxd strings`), 6 of which sat in `allow`.
|
||||
- **Why**: `ask` raises no prompt under `defaultMode: auto` ([[LRN-146]], verified live). It gated nothing, so a destructive rule moved deny→ask was a silent loosening dressed as a confirmation. `soft_deny` = the tier the classifier enforces and user intent clears. `hard_deny` = the 3 classes no command pattern can express — read-then-send spans turns, a prod target is a name not a verb, widening a deny list is self-disarming.
|
||||
- **Alternatives rejected**: keep them in `ask` — inert, false sense of a gate. Back to `deny` — blocks legit process cleanup and inter-project copy, and the user works Bash-first under auto mode. `classifyAllShell: true` — closes the allow-tier blind spot but bills a classifier call on every `git status`. Published-history rewrite as `hard_deny` — user declined; `rebase` then an ordinary push stays uncovered, known gap.
|
||||
- **Scope fix (same commit)**: `autoMode.environment` named `/home/bchanot/Documents/atlast`, its FTP deploy target and its customer data, inside the file `link.sh:21` symlinks to `~/.claude/settings.json`. Every project received atlast's facts, and this repo's own Gitea remote contradicted the block's "no remote configured". Global block now machine-generic; atlast facts moved to atlast's gitignored `.claude/settings.local.json`.
|
||||
- **Caveat**: the guardrail `hard_deny` bars REMOVING a `deny`/`soft_deny`/`hard_deny` entry, not adding one. Future loosening goes through `/permissions` or the user's own edit — deliberate, confirmed with the user.
|
||||
- **Status**: accepted.
|
||||
- **Reference**: `settings.json`, `doctor.sh` `check_automode`, `templates/settings/SETTINGS.md`. Links [[LRN-153]], [[LRN-146]], [[BDR-004]].
|
||||
|
||||
@@ -462,3 +462,10 @@ rules:
|
||||
- Attention signal refined: per-event labels (BDR-087 follow-on), silence on non-attention events, and no turn-end signal while `background_tasks` non-empty ([[LRN-149]]). Payload dump beat the docs: `background_tasks` undocumented for Stop but present on the wire. Branch bugfix/notify-subagent-spawn.
|
||||
- gstack Playwright: bump extracted to `lib/gstack-playwright.sh`, now re-applied after a successful submodule update ([[BDR-088]]); read-only browsers report in doctor, no pruner — `.links` proved 0 bytes reclaimable and the guard I first proposed would have deleted gsd-pi's rev 1243 ([[BDR-089]], [[LRN-151]]). 4 challengers → 6 BLOCKER, recovery branch withdrawn at the gate ([[EVAL-029]]). 2cebecb on feature/gstack-playwright-lib.
|
||||
- Node checked against Playwright: already v24 (1.61 needs >=18, 1.63 needs >=20), not the macOS constraint. macOS audit deferred to its own cycle — found statically: `sed -i` with no suffix x3 in install-plugins.sh (BSD sed eats the next arg), `${x,,}` in url-guard.sh (bash 4+, macOS ships 3.2), `readlink -f` in doctor.sh (absent pre-Monterey 12.3).
|
||||
|
||||
## 2026-09-15
|
||||
- Aligned repo config + deployment on the user's hand-edited `settings.json`. Destructive shell work rebuilt in `autoMode` soft_deny/hard_deny once `ask` was established as inert under auto mode ([[BDR-090]]); `permissions.deny` +10 `.env` reader rules, 6 of which sat in `allow`.
|
||||
- `autoMode.environment` was scoped to ANOTHER project inside the user-scope file, so every repo got atlast's facts. Rewritten machine-generic, atlast facts moved to atlast's own `settings.local.json`, `$defaults` added to all three lists ([[LRN-153]]).
|
||||
- `doctor.sh` gained `check_automode` (missing `$defaults`, foreign-repo scope, both arms tested). `SETTINGS.md` documents the block + a tier-choice table. README's magic-MCP "ask = live confirmation" claim corrected — false under `defaultMode: auto`.
|
||||
- Found, not fixed: `.claude/settings.local.json` = 14.6 KB shadow copy of the global settings at HIGHER precedence, incl. a `config-protection.sh` hook whose script does not exist. Logged F1-F3 in TODO.
|
||||
- `make test` 0 RED, `doctor.sh` 0 errors, `shellcheck` clean.
|
||||
|
||||
@@ -142,6 +142,7 @@ rules:
|
||||
| LRN-150 | 2026-09-15 | Sourced lib shares caller shell: bare `ok/warn/info` override its printers, and its `set -e` applies inside | any new lib/*.sh |
|
||||
| LRN-151 | 2026-09-15 | Playwright cache truth = union over `.links`, dir name maps `_`→`-`, revisionOverrides exist | shared versioned binary caches |
|
||||
| LRN-152 | 2026-09-15 | git `protocol.file=user` blocks submodule fixtures; `-c` misses the code under test, `GIT_CONFIG_*` env does not | tests building git fixtures |
|
||||
| LRN-153 | 2026-09-15 | `autoMode` lists replace built-ins without `"$defaults"`; a user-scope block reaches every project | any `autoMode` edit |
|
||||
|
||||
---
|
||||
|
||||
@@ -1450,3 +1451,12 @@ Rule: when editing a doctrine file under structure locks, grep the test's lock s
|
||||
- **Also**: fixture repos need LOCAL `user.email`/`user.name` (no global identity here) and `git init -b main` + explicit `submodule.<name>.branch`, else `--remote` resolves a different branch than production does.
|
||||
- **Future application**: any test building a git submodule fixture. Symptom is a hard "transport 'file' not allowed" before the first assertion, which reads like a broken test rather than a policy.
|
||||
- **Reference**: `lib/tests/gstack-playwright.test.sh`.
|
||||
|
||||
## LRN-153 — `autoMode` lists replace built-ins unless `"$defaults"` is spliced in
|
||||
- **Date**: 2026-09-15
|
||||
- **Pattern**: every list under `autoMode` (`allow` `soft_deny` `hard_deny` `environment`) is a FULL replacement by default. Omit the literal `"$defaults"` and the built-in classifier rules are dropped silently — no warning, no schema error, the classifier just runs thinner. Put `"$defaults"` first, own entries after: built-ins inherited, then refined.
|
||||
- **Scope trap, same block**: `autoMode` in `~/.claude/settings.json` reaches EVERY project. A block generated while working in one repo (its deploy target, its secrets, its data) ships that repo's facts to all the others, and contradicts whichever repo is actually open. Project facts belong in that project's `.claude/settings.local.json`.
|
||||
- **Format**: these lists are prose spliced into the classifier prompt, not permission-rule syntax. Write "Sending SIGKILL reaches processes outside this session", never `Bash(kill -9 *)`.
|
||||
- **Backstop**: `doctor.sh` `check_automode` warns on a list missing `$defaults` and on a user-scope `environment` naming a git repo other than the config repo. Both arms exercised against the defective block before shipping.
|
||||
- **Future application**: any `autoMode` edit — check `$defaults` presence and scope before anything else.
|
||||
- **Reference**: `doctor.sh`, `templates/settings/SETTINGS.md`. Links [[BDR-090]].
|
||||
|
||||
@@ -1,5 +1,88 @@
|
||||
# TODO
|
||||
|
||||
## 2026-09-15 — align config + deployment on the hand-edited settings.json (feature/automode-config-alignment)
|
||||
User edited global `settings.json` by hand: 4 destructive rules moved
|
||||
deny→ask (`rsync`, `kill -9`, `killall`, `pkill`), 4 removed from ask
|
||||
(`xargs`, `sed`, `cp`, `mv` — coherent with auto mode's Bash-first
|
||||
workflow; the `.env`-scoped `cp`/`mv`/`xargs` deny rules still stand),
|
||||
and an `autoMode.environment` block added. Two defects found:
|
||||
(1) the environment block describes **atlast** (`bin/deploy.sh` lftp/FTP
|
||||
to OVH, quote-request data, "no remote configured") but lives in the
|
||||
user-scope file symlinked to `~/.claude/settings.json` by `link.sh:21`
|
||||
— so every project gets atlast's facts; claude-config itself has a
|
||||
Gitea remote, contradicting the block. (2) no `"$defaults"` sentinel,
|
||||
so the built-in classifier environment entries are replaced, not
|
||||
extended. Third finding: LRN-146 records, verified in session, that
|
||||
`ask` rules raise no prompt under `defaultMode: auto` — the deny→ask
|
||||
move therefore traded a static block for a classifier decision.
|
||||
User decisions (2026-09-15): atlast block → atlast's own
|
||||
`settings.local.json`, global block rewritten machine-generic; the 4
|
||||
destructive rules → `autoMode.soft_deny` (the section that actually
|
||||
binds under auto mode) instead of `ask`.
|
||||
- [x] T1 global `settings.json` — machine-generic `autoMode.environment`
|
||||
with `$defaults`; new `autoMode.soft_deny` with `$defaults` + the
|
||||
4 destructive rules; drop those 4 from `permissions.ask`
|
||||
- [x] T2 `/home/bchanot/Documents/atlast/.claude/settings.local.json` —
|
||||
receives the atlast-specific `autoMode.environment` (gitignored,
|
||||
personal scope); verify project-scope `autoMode` is honored
|
||||
- [x] T3 `templates/settings/SETTINGS.md` — document the `autoMode`
|
||||
block (environment / soft_deny / hard_deny / allow, `$defaults`
|
||||
semantics, `classifyAllShell`) + the "ask ≠ prompt under auto"
|
||||
caveat that makes soft_deny the right tier
|
||||
- [x] T4 `README.md` — magic-MCP paragraph claims the `ask` tier makes
|
||||
every `mcp__magic__*` call "require a live confirmation and never
|
||||
auto-execute"; false under auto mode per LRN-146. Correct the
|
||||
claim, flag the soft_deny option to the user (don't decide it)
|
||||
- [x] T5 `doctor.sh` — permissions section is blind to `autoMode`, now a
|
||||
live security surface. Add a check: block present, `$defaults`
|
||||
inherited, no foreign absolute project path hardcoded
|
||||
- [x] T6a CHANGELOG (Added/Changed/Fixed under [Unreleased])
|
||||
- [ ] T6b registries BDR-090 + LRN-153 + journal — drafted, awaiting user approval
|
||||
- [x] T7 verify: `make test`, `bash doctor.sh`, `shellcheck`
|
||||
NOT in scope: the 3 dirty `skills/graphify/*` files (pre-existing,
|
||||
unrelated) — never staged.
|
||||
|
||||
### Second pass (2026-09-15, user decisions)
|
||||
User confirmed the `ask` removals were deliberate (`/permissions`), asked
|
||||
for the diff vs develop and for guards where the removals left a hole.
|
||||
Answered: writes outside cwd → soft_deny; in-place edits beyond one named
|
||||
file → soft_deny; inline interpreters + `xargs` → soft_deny when they
|
||||
delete or write outside cwd; hard_deny for secret exfiltration, prod
|
||||
deploy, disarming guardrails (history rewrite NOT retained, so a `rebase`
|
||||
then an ordinary push stays uncovered); extend the static deny family to
|
||||
the `.env` readers; `classifyAllShell` stays false; intent clears a soft
|
||||
block for the CURRENT TURN only.
|
||||
- [x] S1 `permissions.deny` +10 reader rules (sed awk cut tr sort uniq
|
||||
diff od xxd strings vs `.env*`) — 6 of them were in `allow`
|
||||
- [x] S2 `autoMode.soft_deny` — 7 rules + the intent-scope line
|
||||
- [x] S3 `autoMode.hard_deny` — 3 rules, "adding a restriction is fine,
|
||||
removing one is not"
|
||||
- [x] S4 `SETTINGS.md` — tier-choice table + scope-of-intent section
|
||||
- [x] S5 CHANGELOG — Changed rewritten, new Security block
|
||||
- [ ] S6 CONSEQUENCE to confirm: the hard_deny guardrail rule means I can
|
||||
no longer edit a deny/soft_deny/hard_deny list to REMOVE an entry.
|
||||
Tightening stays allowed. Future permission loosening goes through
|
||||
`/permissions` or the user's own edit.
|
||||
|
||||
### Follow-up found while doing this (not fixed, needs a decision)
|
||||
`.claude/settings.local.json` (gitignored, 14.6 KB) is a near-complete
|
||||
shadow copy of the global `settings.json` at a HIGHER precedence tier:
|
||||
185 allow / 30 ask / 106 deny, plus its own `cleanupPeriodDays`,
|
||||
`attribution`, `statusLine`, `enabledPlugins`, `extraKnownMarketplaces`,
|
||||
`effortLevel`, `remoteControlAtStartup`, `inputNeededNotifEnabled`,
|
||||
`skipAutoPermissionPrompt` — all identical to the global today, so the
|
||||
duplication is invisible until the global drifts, which it just did
|
||||
(no `autoMode`, 106 deny vs 116). It defeats the config-guard premise
|
||||
(hand-curated `settings.json`) with a file nobody reviews.
|
||||
- [ ] F1 `WebSearch` sits in global `ask` and in local `allow` — in this
|
||||
repo it never reaches the ask tier. Intended or drift?
|
||||
- [ ] F2 local `hooks` block registers `bash ~/.claude/hooks/config-protection.sh`
|
||||
on PreToolUse/Bash. That script does not exist, in `hooks/` or in
|
||||
`~/.claude/hooks/`. Dead hook firing on every Bash call here.
|
||||
- [ ] F3 decide: prune the local file down to the session-accumulated
|
||||
allow rules only, dropping every key that merely restates the
|
||||
global, or keep the copy deliberately and document why.
|
||||
|
||||
## 2026-08-25 — darwin fresh baseline: 32 skill-systems + 23 agents (feature/darwin-optimize-20260825)
|
||||
User: `/darwin-skill all skills and agents` (background). Fresh-from-zero
|
||||
(results.tsv wiped 2026-06-23, journal 2026-06-30). Scope per BDR-015/043 +
|
||||
|
||||
Reference in New Issue
Block a user