Skip to content

feat(052): lose the recording, never the shell (US3) - #282

Merged
guan4tou2 merged 2 commits into
mainfrom
feat/052-us3-degradation
Oct 8, 2026
Merged

guan4tou2 merged 2 commits into
mainfrom
feat/052-us3-degradation

Conversation

@guan4tou2

Copy link
Copy Markdown
Owner

Base is feat/052-us2-terminal-control, not main — US3 builds on the
stand-down gate and the state machine US2 added, so a PR into main would
show #281's commits too. This one needs #281 merged first, and then needs
its base checked.
CLAUDE.md §Branches: a PR into a feature branch is the
thing that quietly needs a second step afterwards.

What changed

Spec 052's US3: the rule the whole feature is subordinate to. An audit logger
may lose the recording, and may never take the shell with it.

This is not a defensive extra. The adapter now sits in preexec on every
command of an engagement, holding the shell's descriptors — so every way it can
fail is a way the operator's shell can fail, in the middle of work that may not
be repeatable. Unrecorded is bad. A wedged prompt, a lost reverse shell, or a
command that reports someone else's exit status is worse.

Why

Silence was the bug, not the fallback (T036, FR-009)

With RedLog unreachable the commands already ran — silently. An operator whose
RedLog crashed two hours ago has been working unrecorded with no way to know.
They are told now, once, on the way in, and again only if RedLog comes back
and goes away. A warning on every prompt is one an operator learns to read
past, and then the one that matters is read past too.

The startup banner said "command metadata will be logged" and offered
redlog-run as the way to get output. Both stopped being true at T020, so zsh
now announces "commands and their output" and bash announces "commands". A
banner that overstates what is recorded is worse than none: an operator who
believes the output is in the record stops checking.

T037 turned out to be a characterisation test

All three assertions passed on the first run. Killing the relay underneath a
running command already leaves the shell alive and already reports the
command's own status to both the shell and the record — that is T020's design
working. It is now pinned rather than assumed.

The shapes the relay has to decline (T038, FR-029)

Four of the six already stood down. The two that did not:

cat log | less relayed on cat, and then the pager's stdout is a pipe instead of a terminal — it stops being a pager. Any native or pty stage anywhere in the line stands the whole thing down.
nmap | tee f not a hazard and worth capturing: the last stage still writes to the terminal, which is what the relay holds. Standing down on every pipeline would have lost this.
nmap -oN f > out stdout is already pointed elsewhere, so two processes are no longer started to watch an empty descriptor. The record says redirected either way (FR-008).

A command from engagement A must never be filed under B (T039, FR-010)

The dangerous outcome is not a missing command. It is a lie in a document a
client reads, written by the tool whose whole job is to be believable. A
terminal is pinned to the project it was opened against; when the operator
switches project the terminal stops, says why, and cannot be talked back into
recording
— redlog start is not the way around the guarantee, and the safe
answer is a new terminal (research.md D5).

Detected by watching the active identity file's mtime with the zstat builtin,
so the common case — nothing changed — costs a stat and no process.

The spool carried everything except when (T040)

The spool and the replay both existed. The payload did not carry a time, so a
command run at 02:00 with RedLog closed arrived claiming 09:00 — the moment the
operator next opened the project. A record whose times are the times someone
read it is not a record of the engagement
(Domain Invariant #8).

One field: source_timestamp on every event the adapter sends. insertEvent
already validates it and hashes the rejection, so a wrong unit is refused
rather than dragging the row to 1970.

Found while building, fixed here

  • The payload builder is a double-quoted shell string, and prose punctuation
    is executable in there.
    A comment written with backticks around a function
    name ran it; the follow-up comment quoting the resulting error message
    ended the string early and truncated the builder, so no event was sent at
    all
    . Both were caught by the suite within one run of each other.
  • shell-adapter-boundaries holds a real line. Paths into the RedLog
    directory are transport, not lifecycle. The identity file is now named by
    shell-common.sh; the zsh adapter only watches what it is handed.
  • Two test notes that are easy to lose. pkill -f matches full command
    lines, so a command that kills its own relay matches itself and reports
    143 — which looks exactly like the bug being tested for. And RedLog cannot
    be made unreachable by omitting the credential files on a developer machine,
    because under WSL the adapter deliberately falls back to the Windows
    profile's .redlog, which is a real install.

Verification

  • npm run typecheck
  • npm run verify:specs
  • npm run verify:architecture
  • npm run verify:i18n
  • npm test — 3140 passed, 25 skipped
  • npm run build
  • full e2e — 101 passed, 4 skipped (5.4 min)
  • both commits read back with git show --stat and carry only their own
    paths (CLAUDE.md §Branches, the shared index)

Tasks T036–T040 of 47; Phase 5 (US3) complete, 40 of 47 done. What is left is
Phase 6: docs (T041/T042), the e2e install journey (T043), the SC measurements
(T045), verification.md (T046), the final gate round (T047) — and T044,
which is not something CI can do
: the nc → Ctrl-Z → stty raw -echo →
fg upgrade on a real Kali VM. The spec is explicit that a run which cannot do
that is not a verified feature, whatever the unit suite says.

🤖 Generated with Claude Code

guan4tou2 and others added 2 commits October 7, 2026 18:19
The rule the whole feature is subordinate to. The adapter now sits in preexec
on every command of an engagement, holding the shell's descriptors, so every
way it can fail is a way the operator's shell can fail -- in the middle of work
that may not be repeatable. Unrecorded is bad; a wedged prompt, a lost reverse
shell or a command reporting someone else's exit status is worse.

T036, FR-009: with RedLog unreachable the commands already ran. Silently,
which is the bug -- an operator whose RedLog crashed two hours ago has been
working unrecorded with no way to know. They are now told, once, on the way
in, and told again only if it comes back and goes away. A warning on every
prompt is one an operator learns to read past, and then the one that matters
is read past too.

The startup banner said "command metadata will be logged" and offered
redlog-run as the way to get output. Both stopped being true at T020, so the
zsh adapter now announces "commands and their output" and bash announces
"commands" -- a banner that overstates what is recorded is worse than none,
because an operator who believes the output is in the record stops checking.

T037 turned out to be a characterisation test: killing the relay underneath a
running command already leaves the shell alive and already reports the
command's own status to both the shell and the record. That is the T020 design
working, and it is now pinned.

T038, FR-029: the relay declines the shapes it cannot hold, before the command
runs rather than noticing afterwards.

  cat log | less     relayed on `cat`, the pager's stdout becomes a pipe and
                     it stops being a pager. Any native or pty stage anywhere
                     in a pipeline stands the whole line down.
  nmap | tee f       not a hazard, and worth capturing: the last stage still
                     writes to the terminal, which is what the relay holds.
  nmap -oN f > out   stdout is already pointed elsewhere, so two processes are
                     not started to watch an empty descriptor. The record says
                     `redirected` either way (FR-008).

One test needed a note of its own: `pkill -f` matches full command lines, so a
command that kills its own relay matches itself and reports 143 -- which looks
exactly like the bug being tested for. And RedLog cannot be made unreachable
by omitting the credential files on this machine, because under WSL the
adapter deliberately falls back to the Windows profile's .redlog, which is a
real install.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
T039, FR-010. The dangerous outcome is not a missing command. It is a command
from engagement A filed under engagement B -- a lie in a document a client
reads, written by the tool whose whole job is to be believable. A terminal is
pinned to the project it was opened against; when the operator switches
project in RedLog the terminal stops, says why, and cannot be talked back into
recording. `redlog start` is not the way around the guarantee; the safe answer
is a new terminal (research.md D5).

Detected by watching the active identity file's mtime with the zstat builtin,
so the common case -- nothing changed -- costs a stat and no process. Only a
changed mtime pays for the relay call. The session_end row lands in whichever
project is open now, which is where someone wondering why the terminal went
quiet will be looking, and names the engagement whose work stopped explicitly
rather than letting the attribution speak for it.

T040. The spool and the replay both existed. What the payload did not carry
was WHEN: a command run at 02:00 with RedLog closed arrived claiming 09:00,
the moment the operator next opened the project. A record whose times are the
times someone read it is not a record of the engagement (Domain Invariant #8).
One field -- source_timestamp on every event the adapter sends. insertEvent
already validates it and hashes the rejection, so a wrong unit is refused
rather than dragging the row to 1970.

Two self-inflicted failures the suite caught, both worth the note:

- The payload builder is a double-quoted shell string. A comment written with
  backticks around a function name RAN it; the follow-up comment quoting the
  resulting error message ended the string early and truncated the builder, so
  no event was sent at all. Prose punctuation is executable in there.
- shell-adapter-boundaries holds the line that paths into the RedLog directory
  are transport, not lifecycle. The identity file is now named by
  shell-common; the zsh adapter only watches what it is handed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d1ad4f59-cf18-4d4a-a053-29cd9a105f37
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@guan4tou2
guan4tou2 changed the base branch from feat/052-us2-terminal-control to main October 8, 2026 02:52
@guan4tou2
guan4tou2 merged commit aaad9c9 into main Oct 8, 2026
5 checks passed
@guan4tou2
guan4tou2 deleted the feat/052-us3-degradation branch October 8, 2026 03:19
guan4tou2 added a commit that referenced this pull request Oct 8, 2026
feat(052): lose the recording, never the shell (US3)
guan4tou2 added a commit that referenced this pull request Oct 8, 2026
feat(052): lose the recording, never the shell (US3)
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.

1 participant