Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions .abcd/development/agents/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,167 @@ over the brief's earlier `1.0.0`-at-close expectation). The four M6 synthesis
agents below entered at `0.1.0`, wired to their `abcd disembark` verbs and
unmeasured; `lifeboat-oracle` has since become `lifeboat-reviewer` at `0.1.1`.

## 2026-10-10 (iss-2610091942156774 — sub-agents are taught the rm the guard refuses)

### cold-reading-comparative 0.1.4

PATCH: the definition ends with the generated guard-lessons block, which
restates the guard's `rm-unguarded-variable-path` rule as the SHELL domain
renders it: an `rm` whose path starts with a variable that can be empty
followed by `/` is refused, and the operand is written `"${VAR:?}"/...` or as
a literal path instead. A sub-agent is handed none of the session's SHELL
rules, so the block is where it learns the rule before the guard refuses the
command. Nothing else changes. Unmeasured, in the `0.x` band.

### cold-reading-detection 0.1.5

PATCH: the definition ends with the generated guard-lessons block, which
restates the guard's `rm-unguarded-variable-path` rule as the SHELL domain
renders it: an `rm` whose path starts with a variable that can be empty
followed by `/` is refused, and the operand is written `"${VAR:?}"/...` or as
a literal path instead. A sub-agent is handed none of the session's SHELL
rules, so the block is where it learns the rule before the guard refuses the
command. Nothing else changes. Unmeasured, in the `0.x` band.

### cold-reading-entailment 0.1.4

PATCH: the definition ends with the generated guard-lessons block, which
restates the guard's `rm-unguarded-variable-path` rule as the SHELL domain
renders it: an `rm` whose path starts with a variable that can be empty
followed by `/` is refused, and the operand is written `"${VAR:?}"/...` or as
a literal path instead. A sub-agent is handed none of the session's SHELL
rules, so the block is where it learns the rule before the guard refuses the
command. Nothing else changes. Unmeasured, in the `0.x` band.

### cold-reading-widening 0.2.4

PATCH: the definition ends with the generated guard-lessons block, which
restates the guard's `rm-unguarded-variable-path` rule as the SHELL domain
renders it: an `rm` whose path starts with a variable that can be empty
followed by `/` is refused, and the operand is written `"${VAR:?}"/...` or as
a literal path instead. A sub-agent is handed none of the session's SHELL
rules, so the block is where it learns the rule before the guard refuses the
command. Nothing else changes. Unmeasured, in the `0.x` band.

### docs-currency-reviewer 0.2.1

PATCH: the definition ends with the generated guard-lessons block, which
restates the guard's `rm-unguarded-variable-path` rule as the SHELL domain
renders it: an `rm` whose path starts with a variable that can be empty
followed by `/` is refused, and the operand is written `"${VAR:?}"/...` or as
a literal path instead. A sub-agent is handed none of the session's SHELL
rules, so the block is where it learns the rule before the guard refuses the
command. Nothing else changes. Unmeasured, in the `0.x` band.

### graveyard-interpreter 0.1.2

PATCH: the definition ends with the generated guard-lessons block, which
restates the guard's `rm-unguarded-variable-path` rule as the SHELL domain
renders it: an `rm` whose path starts with a variable that can be empty
followed by `/` is refused, and the operand is written `"${VAR:?}"/...` or as
a literal path instead. A sub-agent is handed none of the session's SHELL
rules, so the block is where it learns the rule before the guard refuses the
command. Nothing else changes. Unmeasured, in the `0.x` band.

### intent-auditor 0.5.1

PATCH: the definition ends with the generated guard-lessons block, which
restates the guard's `rm-unguarded-variable-path` rule as the SHELL domain
renders it: an `rm` whose path starts with a variable that can be empty
followed by `/` is refused, and the operand is written `"${VAR:?}"/...` or as
a literal path instead. A sub-agent is handed none of the session's SHELL
rules, so the block is where it learns the rule before the guard refuses the
command. Nothing else changes. Unmeasured, in the `0.x` band.

### lifeboat-reviewer 0.1.2

PATCH: the definition ends with the generated guard-lessons block, which
restates the guard's `rm-unguarded-variable-path` rule as the SHELL domain
renders it: an `rm` whose path starts with a variable that can be empty
followed by `/` is refused, and the operand is written `"${VAR:?}"/...` or as
a literal path instead. A sub-agent is handed none of the session's SHELL
rules, so the block is where it learns the rule before the guard refuses the
command. Nothing else changes. Unmeasured, in the `0.x` band.

### planning-interviewer 0.1.1

PATCH: the definition ends with the generated guard-lessons block, which
restates the guard's `rm-unguarded-variable-path` rule as the SHELL domain
renders it: an `rm` whose path starts with a variable that can be empty
followed by `/` is refused, and the operand is written `"${VAR:?}"/...` or as
a literal path instead. A sub-agent is handed none of the session's SHELL
rules, so the block is where it learns the rule before the guard refuses the
command. Nothing else changes. Unmeasured, in the `0.x` band.

### press-release-composer 0.1.2

PATCH: the definition ends with the generated guard-lessons block, which
restates the guard's `rm-unguarded-variable-path` rule as the SHELL domain
renders it: an `rm` whose path starts with a variable that can be empty
followed by `/` is refused, and the operand is written `"${VAR:?}"/...` or as
a literal path instead. A sub-agent is handed none of the session's SHELL
rules, so the block is where it learns the rule before the guard refuses the
command. Nothing else changes. Unmeasured, in the `0.x` band.

### principle-distiller 0.2.1

PATCH: the definition ends with the generated guard-lessons block, which
restates the guard's `rm-unguarded-variable-path` rule as the SHELL domain
renders it: an `rm` whose path starts with a variable that can be empty
followed by `/` is refused, and the operand is written `"${VAR:?}"/...` or as
a literal path instead. A sub-agent is handed none of the session's SHELL
rules, so the block is where it learns the rule before the guard refuses the
command. Nothing else changes. Unmeasured, in the `0.x` band.

### question-drafter 0.1.2

PATCH: the definition ends with the generated guard-lessons block, which
restates the guard's `rm-unguarded-variable-path` rule as the SHELL domain
renders it: an `rm` whose path starts with a variable that can be empty
followed by `/` is refused, and the operand is written `"${VAR:?}"/...` or as
a literal path instead. A sub-agent is handed none of the session's SHELL
rules, so the block is where it learns the rule before the guard refuses the
command. Nothing else changes. Unmeasured, in the `0.x` band.

### reflection-composer 0.3.1

PATCH: the definition ends with the generated guard-lessons block, which
restates the guard's `rm-unguarded-variable-path` rule as the SHELL domain
renders it: an `rm` whose path starts with a variable that can be empty
followed by `/` is refused, and the operand is written `"${VAR:?}"/...` or as
a literal path instead. A sub-agent is handed none of the session's SHELL
rules, so the block is where it learns the rule before the guard refuses the
command. Nothing else changes. Unmeasured, in the `0.x` band.

### release-changelog-composer 0.4.2

PATCH: the definition ends with the generated guard-lessons block, which
restates the guard's `rm-unguarded-variable-path` rule as the SHELL domain
renders it: an `rm` whose path starts with a variable that can be empty
followed by `/` is refused, and the operand is written `"${VAR:?}"/...` or as
a literal path instead. A sub-agent is handed none of the session's SHELL
rules, so the block is where it learns the rule before the guard refuses the
command. Nothing else changes. Unmeasured, in the `0.x` band.

### ruthless-reviewer 0.2.1

PATCH: the definition ends with the generated guard-lessons block, which
restates the guard's `rm-unguarded-variable-path` rule as the SHELL domain
renders it: an `rm` whose path starts with a variable that can be empty
followed by `/` is refused, and the operand is written `"${VAR:?}"/...` or as
a literal path instead. A sub-agent is handed none of the session's SHELL
rules, so the block is where it learns the rule before the guard refuses the
command. Nothing else changes. Unmeasured, in the `0.x` band.

### security-reviewer 0.3.1

PATCH: the definition ends with the generated guard-lessons block, which
restates the guard's `rm-unguarded-variable-path` rule as the SHELL domain
renders it: an `rm` whose path starts with a variable that can be empty
followed by `/` is refused, and the operand is written `"${VAR:?}"/...` or as
a literal path instead. A sub-agent is handed none of the session's SHELL
rules, so the block is where it learns the rule before the guard refuses the
command. Nothing else changes. Unmeasured, in the `0.x` band.
## 2026-10-10 (iss-2610100626211810 — a question's chip sets the mode)

### question-drafter 0.1.1
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
schema_version: 1
id: "iss-2610100938485695"
slug: "the-rm-unguarded-variable-path-guard-entry-misjudges-three"
severity: "minor"
category: "bug"
source: "impl-review"
found_during: "Fable review of the rm-unguarded-variable-path entry, 2026-10-10"
origin: researcher-authored
production_mode: hand-written
found_at: "internal/core/guard/varpath.go"
remedy: "Carry the non-empty guarantee of :? / :- / := through the named re-read of a double-quoted shell string, read nested defaults recursively, and treat a variable followed by a substitution before the slash as emptyable; one fixture per shape, watched fail first."
---

The rm-unguarded-variable-path guard entry misjudges three edge shapes found in its review. (1) Inside a double-quoted shell string the successor's own rewrite is refused: sh -c "rm -rf ${X:?}/y" blocks, because the re-read string spells ${X:?} as ${X} (payload.go spellParameterAt), so the refusal tells the agent to do what it did; single quotes avoid it. (2) A nested guard is not read: "${VAR:-${OTHER:?}}"/y and "${VAR:-${OTHER:-/tmp}}"/y block although both are guarded (varpath.go stripEmptyableRefs strips the inner reference). (3) A site followed by a substitution is not followed: rm -rf $VAR$(true)/x is allowed (varpath.go).
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
schema_version: 1
id: "iss-2610091942156774"
slug: "guard-allows-rm-on-unguarded-variable-path"
severity: "minor"
category: "ux"
source: "user-observation"
found_during: "2026-10-09 security-drain coordination, product thinker report"
origin: researcher-authored
production_mode: hand-written
found_at: "internal/core/guard"
remedy: "Product thinker's ruling 2026-10-09, options 1 and 2: (1) add a guard registry entry that blocks `rm` whose operand is an unguarded variable path (`\"$VAR\"/...`, `$VAR/...`, `\"${VAR}\"/...`) and allows `\"${VAR:?}\"/...` and literal paths, with that rewrite as its successor, so the agent rewrites the command before the host prompts; prove in a live host session that the hook's refusal lands before the host's prompt; (2) teach the same rule through the SHELL domain (generated from the entry) and in abcd's agent definition files, which sub-agents read. Build after the v0.13.4 cut; never a silent rewrite of the command."
resolution: "The bundled registry gains the blocker rm-unguarded-variable-path: an rm whose operand starts with a variable that can be empty directly followed by / is refused, with the \"${VAR:?}\"/... rewrite or a literal path as its successor, through a new arg_shapes operand predicate. The SHELL domain teaches it from the entry, and every shell-capable agent definition but the scribe carries a block generated from the same entry."
impact: fix
---

An agent's `rm -f "$VAR"/*` triggers the host's own dangerous-rm permission prompt ("possibly-empty variable path ... rewrite it as "${VAR:?}"/* or use a literal path"), even with permissions skipped. The person cannot tell whether the variable is empty and cannot make the agent take the suggested form, so they must approve blind. abcd's guard allows the shape today, and sub-agents never receive the injected SHELL rules, so nothing steers an agent away from it. Reported by the product thinker on 2026-10-09 from two prompts raised by a review sub-agent.
12 changes: 11 additions & 1 deletion agents/cold-reading-comparative.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: >-
Cold reading at the comparative position. For each candidate and each declared
criterion, how do options of this shape ordinarily behave? Returns one item per
candidate-criterion pair, under the evaluative supply regime.
prompt_version: 0.1.3
prompt_version: 0.1.4
reads_untrusted_input: true
capability_scope:
task_classes: [cold_reading]
Expand Down Expand Up @@ -132,3 +132,13 @@ ingest verb's to compose rather than yours.
"characterisation": "<how options of this shape ordinarily behave against it>"
}
```

<!-- generated: guard-lessons -->
<!-- Written by `go test ./internal/core/guard -run TestAgentDefinitionsCarryTheGuardLessons -update` from the guard registry (internal/core/guard/defaults/guard.json); edit the entry there, never this block. -->

## Shell commands the guard refuses

A sub-agent is not handed the shell rules the session is taught, so the rules for the commands you are most likely to write are restated here. The guard refuses a command that breaks one before it runs; write it the way the rule says from the start.

- Refused by the guard (rm-unguarded-variable-path): `rm` with an operand that starts with a variable that can be empty followed by `/` (`"$VAR"/…`, `$VAR/…`, `"${VAR}"/…`, `${VAR}/…`). A path that starts with a variable followed by `/` names a path from the filesystem root when the variable is empty or unset — `rm -f "$VAR"/*` becomes `rm -f /*` — and nothing on the line says which, so whoever is asked to approve it cannot tell either. Instead: Write the variable as `"${VAR:?}"/...` (`rm -f -- "${VAR:?}"/*`), which stops the shell with an error when it is empty or unset, or use a literal path.
<!-- /generated -->
12 changes: 11 additions & 1 deletion agents/cold-reading-detection.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: >-
Cold reading at the detection position. Where is the shipped tree in tension
with the claim record? Returns tensions, each with the constraint in play and
why it is a tension, under the registrative supply regime.
prompt_version: 0.1.4
prompt_version: 0.1.5
reads_untrusted_input: true
capability_scope:
task_classes: [cold_reading]
Expand Down Expand Up @@ -137,3 +137,13 @@ ingest verb's to compose rather than yours.
"why_a_tension": "<why the two cannot both hold as stated>"
}
```

<!-- generated: guard-lessons -->
<!-- Written by `go test ./internal/core/guard -run TestAgentDefinitionsCarryTheGuardLessons -update` from the guard registry (internal/core/guard/defaults/guard.json); edit the entry there, never this block. -->

## Shell commands the guard refuses

A sub-agent is not handed the shell rules the session is taught, so the rules for the commands you are most likely to write are restated here. The guard refuses a command that breaks one before it runs; write it the way the rule says from the start.

- Refused by the guard (rm-unguarded-variable-path): `rm` with an operand that starts with a variable that can be empty followed by `/` (`"$VAR"/…`, `$VAR/…`, `"${VAR}"/…`, `${VAR}/…`). A path that starts with a variable followed by `/` names a path from the filesystem root when the variable is empty or unset — `rm -f "$VAR"/*` becomes `rm -f /*` — and nothing on the line says which, so whoever is asked to approve it cannot tell either. Instead: Write the variable as `"${VAR:?}"/...` (`rm -f -- "${VAR:?}"/*`), which stops the shell with an error when it is empty or unset, or use a literal path.
<!-- /generated -->
12 changes: 11 additions & 1 deletion agents/cold-reading-entailment.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: >-
being the kind of thing it is, that its articulation does not state? Returns
surfaced claims, each with its claim type and what implies it, under the
explicative supply regime.
prompt_version: 0.1.3
prompt_version: 0.1.4
reads_untrusted_input: true
capability_scope:
task_classes: [cold_reading]
Expand Down Expand Up @@ -136,3 +136,13 @@ rather than yours.
"what_implies_it": "<the passed material that implies it, named>"
}
```

<!-- generated: guard-lessons -->
<!-- Written by `go test ./internal/core/guard -run TestAgentDefinitionsCarryTheGuardLessons -update` from the guard registry (internal/core/guard/defaults/guard.json); edit the entry there, never this block. -->

## Shell commands the guard refuses

A sub-agent is not handed the shell rules the session is taught, so the rules for the commands you are most likely to write are restated here. The guard refuses a command that breaks one before it runs; write it the way the rule says from the start.

- Refused by the guard (rm-unguarded-variable-path): `rm` with an operand that starts with a variable that can be empty followed by `/` (`"$VAR"/…`, `$VAR/…`, `"${VAR}"/…`, `${VAR}/…`). A path that starts with a variable followed by `/` names a path from the filesystem root when the variable is empty or unset — `rm -f "$VAR"/*` becomes `rm -f /*` — and nothing on the line says which, so whoever is asked to approve it cannot tell either. Instead: Write the variable as `"${VAR:?}"/...` (`rm -f -- "${VAR:?}"/*`), which stops the shell with an error when it is empty or unset, or use a literal path.
<!-- /generated -->
12 changes: 11 additions & 1 deletion agents/cold-reading-widening.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: >-
construes it, what configurations does the construal admit that are not
present in what has been committed to? Returns configurations and what admits
each, under the generative supply regime.
prompt_version: 0.2.3
prompt_version: 0.2.4
reads_untrusted_input: true
capability_scope:
task_classes: [cold_reading]
Expand Down Expand Up @@ -136,3 +136,13 @@ the record identity — is the ingest verb's to compose rather than yours.
"what_admits_it": "<the passed material that admits it, named>"
}
```

<!-- generated: guard-lessons -->
<!-- Written by `go test ./internal/core/guard -run TestAgentDefinitionsCarryTheGuardLessons -update` from the guard registry (internal/core/guard/defaults/guard.json); edit the entry there, never this block. -->

## Shell commands the guard refuses

A sub-agent is not handed the shell rules the session is taught, so the rules for the commands you are most likely to write are restated here. The guard refuses a command that breaks one before it runs; write it the way the rule says from the start.

- Refused by the guard (rm-unguarded-variable-path): `rm` with an operand that starts with a variable that can be empty followed by `/` (`"$VAR"/…`, `$VAR/…`, `"${VAR}"/…`, `${VAR}/…`). A path that starts with a variable followed by `/` names a path from the filesystem root when the variable is empty or unset — `rm -f "$VAR"/*` becomes `rm -f /*` — and nothing on the line says which, so whoever is asked to approve it cannot tell either. Instead: Write the variable as `"${VAR:?}"/...` (`rm -f -- "${VAR:?}"/*`), which stops the shell with an error when it is empty or unset, or use a literal path.
<!-- /generated -->
Loading
Loading