8000 Mission role gates: rounds 3 and 4 review fixes (M9.9, M9.42) by adeelahmad · Pull Request #92 · adeelahmad/aloop · GitHub
[go: up one dir, main page]

Skip to content

Mission role gates: rounds 3 and 4 review fixes (M9.9, M9.42) - #92

Merged
adeelahmad merged 10 commits into
masterfrom
claude/m9-roles-9u038t
Oct 20, 2026
Merged

adeelahmad merged 10 commits into
masterfrom
claude/m9-roles-9u038t

Conversation

@adeelahmad
@adeelahmad adeelahmad commented Oct 20, 2026 •
Copy link
Copy Markdown
Owner

Requested by Adeel · project thread

Before: two adversarial reviews of the M9.9 gates (#74) confirmed findings that let common shell idioms slip past the mission role path guard, such as if [[ … ]]; then … fi, for f in src/*.py; do sed -i … "$f"; done, export OUT=…, curl -O, a script fed to bash on a heredoc, [ -d x ] && cd x; touch src/a.py and { cd docs/agents/mission; }; echo > eval/x.json. Other everyday commands were blocked by mistake: BSD sed -i '', find -exec … {}.bak, find . -path ./node_modules -prune -o -name '*.pyc' -exec rm -f {} +, and … | xargs -I{} rm -f {}.

After: every confirmed reader and rule finding from rounds 3 and 4 is a row in mission-scope-bash.test.py that failed before its fix, with its allow-controls as rows too. No mission role or builder can set ALOOP_JUDGE_CMD (M9.42), a non-planner role creates no Aladdin intent, and a builder runs no writing mission evaluate|assess.

The reader now follows every state a command may leave the shell in. A cd inside an if, case, loop body, { …; } group or function, or after && or ||, may or may not have run, so the rest of the command is judged from each dir it may leave.

How:

  • Reader (_shellread.py, plus walk/fork in gate-mission-scope):
    • Compounds are marked cwd+, cwd| (elif, else, ;;) and cwd-. A compound's own } > f is marked own and judged where the compound started.
    • States are deduplicated across the whole call. Past 1024 walks or 50 000 judged commands the command blocks, which keeps the gate inside the 15 s hook timeout.
    • A loop body is judged once per item, then the rest once per state it leaves.
    • An unquoted $VAR splits into words.
    • eval and source /dev/stdin run in the calling shell.
    • A background job's cd stays in the job.
    • Nesting deeper than 8 levels blocks; before, it was let through.
    • Carried over from round 3: ]] glued to an operator, loop variables, export/declare/local (also behind command -p and builtin), arrays, heredoc and here-string programs, perl -0pi, brace expansion, time -p --, and the su, setsid and runuser wrappers. runuser options after -u USER are now read too.
  • Rules (gate-mission-scope):
    • Downloads and installs that write to a fixed place. This now covers wget --spider -O, a global npm install with --prefix, and --global=false.
    • In-place tools and formatters, mv/rsync sources, and removals that reach docs/agents/mission/.
    • The rest of the git writes.
    • find's -name tests bound a removal through alternatives: -o groups count, and a negation or -path no longer voids the bound. A test after the action, or one past an -o or a comma, bounds nothing. An expression the gate cannot read bounds nothing either.
    • -fprint, -fls and -fprintf count as writes.
    • os.environ.__setitem__ and update([(…)]) count as judge-variable sets.
    • xargs -i and -I{}: a plain {} operand is an unknown stdin word, and a {} inside code is re-read.
  • Merge of master (Core agent principles and the one-way / two-way door rule (M9.30, M9.38); hard-lined goals retire (M9.29) #86 doors and later), following the Core agent principles and the one-way / two-way door rule (M9.30, M9.38); hard-lined goals retire (M9.29) #86 thread's recipe. doors.py skips the new markers, and a pipeline element's cd does not leak there either.

Not in this PR:

  • The artifact-gate findings come in a separate PR.
  • The reader's remaining residuals are listed as ROADMAP M9.54: a computed cd, a cd that fails before ;, cds that accumulate over loop iterations, ;&, eval with a redirect, function bodies judged at their definition, and interpreter code. The backstop for all of them is M9.41, the tree diff at a role's stop.

Evidence:

  • mission-scope-bash.test.py: 718 commands (472 blocked, 246 allowed); it had 225 on master.
  • Every row was run on master's gate and on this one. 30 rows are newly allowed, and all of them are want-0 over-block fixes. No row that wants a block is allowed.
  • mission-doors.test.py: 21 checks. mission-roles.test.py: 34 checks.
  • make ci passes locally on 539918e: conformance 78/78, tracking 95 requirements.

🤖 Generated with Claude Code

https://claude.ai/code/session_0197nSutKR1QAAqgJhFZ8fPj

Adversarial review of the Bash reader (_shellread.py) and the scope gate's
plumbing around it. Reader changes only; the rule tables are untouched.

Coverage (writes that slipped past the gate, now judged):
- `]]` glued to `;` `&&` `||` `)` ends the [[ test, so the rest of the
  command (and later lines) is read again (reader-coverage/F1).
- a `for`/`select` loop variable is bound to its list, so `"$f"` writes are
  judged (reader-coverage/F2).
- export/declare/local/readonly/typeset NAME=VALUE records NAME, like a bare
  assignment (reader-coverage/F3).
- a heredoc or here-string fed to a shell on stdin is read as its program
  (reader-coverage/F7).
- xargs -I{} replaces only the {} inside the sh -c string, so the inner
  command is read (reader-coverage/F8).
- perl -0pi / -lpi: -0 and -l take only an octal value, so a clustered -i is
  found (reader-coverage/F13).
- a simple {a,b} brace at the start of a word expands to its words
  (reader-coverage/F14).
- `cd` inside a function body, an untaken branch or a pipeline element no
  longer moves where a later write lands (reader-coverage/F15).
- su finds `-c` after the user (`su USER -c`); setsid and runuser are looked
  through (reader-coverage/F20).

False blocks (legitimate commands, now allowed):
- the same `]]`-glued fix lets writes to /tmp after a glued test through, and
  reads the heredoc body as data (reader-false-positive/F2).
- a `$` right before a closing double quote is a literal `$` (an end-anchored
  regex), not a nested locale string (reader-false-positive/F6).
- the redirect of a `{ ... }` group is judged where the shell opens it, before
  a `cd` inside the group (reader-false-positive/F7).

Tests: scripts/test/mission-scope-bash.test.py grows a "round 3: the reader"
section (220 -> 279 rows). mission-roles and opencode-plugin unchanged; make ci
green. The reader stays linear on long inputs (brace expansion is capped).

Deferred: `CMD | while read VAR` (read's input is runtime-unknown), the
`echo ... | bash` pipe form of F7, and the `at`/`crontab` stdin forms of F20 —
each needs machinery beyond a reader change and the findings mark them optional.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197nSutKR1QAAqgJhFZ8fPj
…42 (round 3)

Fixes the confirmed adversarial-review findings for the scope track (ROADMAP
§4.12, M9.9), each with its reproduction and allow-controls added as a new
"round 3: the rules" section of mission-scope-bash.test.py (159 new rows).

Coverage (writes that slipped through):
  - F4  curl -O/-J/--remote-name and bare/`-r`/`--mirror` wget save into the cwd
        (judged: --output-dir / -P, else ".").
  - F5  the supervisor's bare-dir exemption now only covers mkdir/touch; a copy,
        extract or recursive removal into or over docs/agents/mission/ (or an
        ancestor it reaches) is a write of its contents. A tree removal bounded by
        find's -name tests is checked against what is actually there.
  - F6  mv/rsync --remove-source-files/tar --remove-files sources are removed → judged.
  - F9  gh repo clone / release download and npm/pip installs judged by their fixed
        output location (operand/basename, --prefix/-d, else ".").
  - F10 git bisect/remote/fetch/update-index/read-tree/checkout-index/symbolic-ref/
        gc/prune/repack/maintenance/lfs/hash-object added to GIT_WRITES with their
        read forms; format-patch/archive/bundle/init judged by output target; bisect
        run / rebase -x / submodule foreach run their command.
  - F11 a builder's git rm/mv/checkout/restore/reset/stash pathspecs into
        docs/agents/mission/ are judged (role repo-writes unchanged; supervisor
        git `--` left by design).
  - F12 in-place tools and formatters (sort -o, sponge, yq -i, ed/ex/vim -c,
        dos2unix, iconv -o, split, mktemp, scp, gzip, ruff --fix, black, prettier
        -w, gofmt -w) and the npx wrapper.
  - F19 `events decide --role human` and the plan.approved human type.

False positives (legitimate work blocked):
  - F1  git -C <abs> after an unresolved cd no longer crashes the gate (join d or "/").
  - F3  BSD `sed -i ''` drops the empty suffix before splitting script from files.
  - F4  an unknown tar -C / unzip -d / patch -d / cp -t value is judged by its known
        prefix, not treated as ".".
  - F5  find substitutes {} inside any argument against the start path; -execdir
        runs in the match's dir.
  - F8  cp -t with an unknown dir judges the dir, never a source operand.
  - F9  tar -xO / --to-stdout writes nothing.
  - F10 git config get/list, tag/branch --sort <key>, unzip -h, find -L … -delete.

New items:
  - M9.42/judge-cmd: no mission role or builder may set ALOOP_JUDGE_CMD/
    ALOOP_JUDGE_TIMEOUT (env prefix, env VAR=, export/declare, or a mention in a
    bash -c / interpreter string); the supervisor may, and `mission judge run` stays.
  - real-areas/F5: `intent new` and `aladdin [run] "<intent>"` refused for a mission
    role other than mission-planner.
  - real-areas/F9: corrected the ledger-recovery advice (put the file back; name that
    rows after the last commit are lost; the artifact-gate part is another track's).
  - reader-false-positive/X1: a builder's bare `mission evaluate` / `mission assess`
    (which write a report) are blocked; read-only forms stay allowed.

make ci green (mission-scope-bash: 379 rows, 235 blocked / 144 allowed; mission-roles: 32 checks).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197nSutKR1QAAqgJhFZ8fPj
x=( $(git commit …) ) and declare -a x=( `…` ) now judge the commands
they run, and the `--` after time [-p] no longer hides the command.
Both found by M9.30's third review of the shared reader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197nSutKR1QAAqgJhFZ8fPj
…42 and the reader

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197nSutKR1QAAqgJhFZ8fPj
@adeelahmad
adeelahmad marked this pull request as draft October 20, 2026 09:25
…edger advice, narrow items)

- find -exec/-execdir: CMD is judged once per start path, with {} a match
  under it and the text around {} kept (the round-3 regression); -name tests
  bound a removal of an ancestor only when every match must pass them.
- Over-blocks fixed: wget --spider/--version/--help, the supervisor's
  `find -prune -exec rm|mv {}`, gh release download -O, global npm/pnpm/yarn
  installs, pip wheel -w, git format-patch --stdout, isort --check-only,
  dos2unix -n IN OUT, `aladdin stats|sweep|capabilities|ids|config` (tools in
  BIN), ALOOP_JUDGE_CMD named as data (only a set blocks), and the maxsplit
  DeprecationWarning on export/declare.
- F9: a blocked ledger write says to leave the ledger as it was, and names
  the repair for its layout (tracked: git diff/checkout HEAD only when the
  damage is all that changed, else the human; untracked: the human).
  mission-roles checks both layouts end on a ledger that verifies.
- Narrow items: removals and extracts through an ancestor (the tree root too),
  the supervisor's git rm/mv/clean/stash -u, builder pathspecs through an
  ancestor and without `--`, mv -t and zip -m sources, git merge-file, clone/
  download/install forms, in-place tools and formatters, `intent new` and
  `intent check|normalize` by non-planner roles, the `aloop|aladdin <tool>`
  chain and package runners, and the judge var set by read/printf -v/mapfile/
  let/nameref and interpreter env mappings.
- Linear-time bounds: an oversized find (start paths x -exec words), a
  {}/.. climb past 32 levels, and more than 16 distinct git clean/stash
  filters block; reaches() and git ls-files answers are memoized per call.

Rows: 379 -> 601 (110 of them fail on the base gate); checks 1 -> 2;
mission-roles 33 -> 34 checks. make ci passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197nSutKR1QAAqgJhFZ8fPj
…er and rule fixes

The merge follows the #86 thread's recipe for _shellread.py (Word.body ours,
Cmd keeps end plus loop/pipe, their heredoc registration, `for NAME do`),
and doors.py skips the reader's compound markers and keeps a pipeline
element's cd in the element.

Round 4 (the verifiers of round 3): the scope gate walks every state a
command may leave the shell in. A cd inside an if/case/loop body, group or
function, or after && / ||, may or may not have run, so the rest is judged
from each dir (a compound's own `} > f` from where it started); a loop body
once per item; unquoted $VAR splits; eval and source /dev/stdin run in the
calling shell; a background job's cd stays in it; nesting past 8 levels,
more than 1024 walks or 50 000 judged commands blocks. find's -name tests
bound a removal through -o alternatives and negations; -fprint/-fls write;
wget --spider -O, npm -g --prefix, --global=false, os.environ.__setitem__/
update, xargs -i/-I{}, runuser options and `command -p export` are read.
Residuals are ROADMAP M9.54. mission-scope-bash.test.py: 718 commands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197nSutKR1QAAqgJhFZ8fPj
@adeelahmad adeelahmad changed the title Mission role gates: second review's fixes (M9.9 round 3, M9.42) Mission role gates: rounds 3 and 4 review fixes (M9.9, M9.42) Oct 20, 2026
@adeelahmad
adeelahmad marked this pull request as ready for review October 20, 2026 13:52
@adeelahmad
adeelahmad merged commit bebfe73 into master Oct 20, 2026
5 checks passed
adeelahmad pushed a commit that referenced this pull request Oct 20, 2026
…e M9.31 branch

ROADMAP: master's M9.54 kept; this branch's new items renumbered M9.55-M9.60.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012rsDo9GYzNx8tXucywZHnA
adeelahmad pushed a commit that referenced this pull request Oct 20, 2026
…nto the dsh follow-ups

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AiLhAWTRtwbgMCDQNJUbea
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.

2 participants

0