Repository navigation
docs(052): say what is recorded, what is not, and what is still unproven - #287
Merged
Merged
Conversation
T041. The lifecycle contract's states are a SOURCE's. This feature adds a second axis underneath one of them -- enrolled, stopped, mode, project-switched are one TERMINAL's -- and conflating the two would undo most of that document. Three rules keep them apart: a per-terminal stop is not a source failure, the state is a file so the terminal and the card cannot disagree, and a stop is a gap with two ends rather than silence. Coverage gained the line T041 asked for and one more. Declining to capture has to be visible to the operator, not only true in the code -- the native class produces an absence, which is the one thing nobody notices. And `interactive`, `not-captured`, `redirected` and a stop are four different words, because collapsing them makes every deliberate silence look like a broken capture and every broken capture look deliberate. Cleanup gained the byte-identical rule with the blank-line bug as its reason. T042. README and USER-GUIDE both said the shell hook records metadata only and pointed at redlog-run for output. False since T020 for zsh, still true for bash, so both now say which is which. The guide gained the `redlog` command table, the three reasons a command is deliberately left alone -- with the `nc` upgrade named, because that is the one that costs an operator something real -- and the spool arriving with the time it happened rather than the time it was read. T043. The install journey, five steps, and the ordering is the point: the card asks for a first command before it asks for an install, because with nothing recorded the shortest route out of dark is a command and RedLog's own pane needs no adapter. It does not drive a shell; test/zsh-auto-capture.pty.test.ts does that against a real interactive zsh. **This spec is unrun locally.** shell-source install refuses on win32, so the whole file skips on the development machine and CI's ubuntu leg is its first execution. Every selector, string, helper export and the seed endpoint were checked against the source instead, so the residual risk is a wrong assumption about timing rather than about names. T046. verification.md, and it opens by saying it is incomplete. Twenty-one RED rows each carry the reason they failed, because "it went red" is not evidence -- a test can fail for the wrong reason and pass for the wrong reason afterwards. Two tests that passed on their first run are recorded as characterisation rather than claimed as RED. Operational Verification is the section this feature cannot be Verified without, and T044/T045 need a real Kali VM, so the spec stays Draft. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
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. Comment |
CI's first run of this spec went red on its last step: the card still read "open a new terminal" after a command was seeded with `source: auto-relay`. The assertion was on the card's text, so it named neither half of the question -- was the row not written, or did the card not read it? The product side is not what is wrong. Driving the same event through `ingest()` with the seed door's own envelope sets `ownShellLastEventAt` correctly, and `capture-health.test.ts` already covers the query. So the gap is in the test, and the fix is to make it speak: - `postCommand` checks the status and prints the body on a refusal, instead of discarding the response. - It then reads the row back through `/api/events` and fails there if it is missing. A refused POST and a card that did not update are now two different failures with two different messages. - The auto-relay command is `echo from-my-own-terminal` rather than `nmap -sV 10.0.0.1`: target extraction and a possible scope violation went down the same path for free, and this test is about one field. Still unrun locally -- shell-source install refuses on win32, so the file skips here and CI remains its first execution. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The last round's diagnostics settled half of it: the row IS in the record with `source: auto-relay`, because `postCommand` now reads it back and would have failed there. So the card is reading something the query did not find. Which leaves two candidates that the current assertion cannot tell apart -- main computes null, or main is right and the renderer is showing a payload it fetched earlier. Quoting the card's sentence back names neither. So ask the main process directly, through the same bridge the card uses, and assert on `ownShellLastEventAt` before asserting on the text. A failure now prints the value main actually computed alongside `lastEventAt` and `installed`, which is enough to tell the two apart in one round instead of three. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The spec passes locally on Linux in 8 seconds and failed twice in CI on the same commit. The payload diagnostic from the last round settled where: main computes `ownShellLastEventAt` correctly, and the card's DOM is what lags. The card re-reads capture health on a 5s interval in the renderer, and Electron throttles timers in a window it considers hidden. A hundred specs into a suite run under xvfb this window is not reliably the foreground one, so the interval stretches and a 20s wait fails on the host's idea of visibility rather than on anything the product did. Passing locally and failing in CI on identical code is the signature. So the window says it is visible and asks not to be throttled, once, in beforeAll. Test-only: nothing about the product's own behaviour changes, and an operator's window being genuinely hidden is not a case this journey is about. Verified by running the suite inside WSL with xvfb-run, which is what CI does -- 5 passed, 8.1s. That loop now exists: a Linux checkout at ~/redlog-e2e with its own node_modules, because `shell-source` install refuses on win32 and this file skips entirely on the development machine. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The card does not poll. The Dashboard re-reads capture health once on mount and once per batch of new events, so whatever that one read returns is what the operator sees until the NEXT event arrives. getCaptureHealth() has a 750ms cache, and nothing invalidated it on a write -- so if anything else had asked in the previous 750ms (the StatusBar, an agent's redlog_status), the card's one read got the state from before the event that should have changed it, and stayed there. The worst moment for that is the one spec 052 is built around: the operator installs the shell hook, runs one command in their own terminal to prove it, and the card keeps saying "open a new terminal" (FR-015). It would correct itself on the second command, which is cold comfort for a setup step that exists to be believed. Now the cache is cleared on every publish. It still coalesces the storm of polls BETWEEN events, which is what it is for; it no longer answers "what has arrived" with a reading taken before the latest arrival. The bus defers its fanout to a microtask and the renderer's batch is downstream of the same publish, so the cache is gone before anyone can ask. Found by e2e/terminal-enrollment.spec.ts, which passed on a quiet machine and failed three times on a busy CI runner on identical code. Whether something else had filled the cache in the previous 750ms is a property of the load, not of the code -- which is also why it went unseen. **The previous commit's diagnosis was wrong, and this one removes it.** It blamed Electron's background-timer throttling for a 5s poll in the renderer. There is no 5s poll -- a code comment mentions one, and I took the comment's word for it instead of reading the effect. Disabling throttling changed nothing in CI. The workaround and its confident explanation are deleted rather than left to mislead the next reader; the e2e now carries the true account, and capture-health.test.ts pins the fix as a unit test that fails without it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
guan4tou2
added a commit
that referenced
this pull request
Oct 8, 2026
…ven (#287) * docs(052): say what is recorded, what is not, and what is still unproven T041. The lifecycle contract's states are a SOURCE's. This feature adds a second axis underneath one of them -- enrolled, stopped, mode, project-switched are one TERMINAL's -- and conflating the two would undo most of that document. Three rules keep them apart: a per-terminal stop is not a source failure, the state is a file so the terminal and the card cannot disagree, and a stop is a gap with two ends rather than silence. Coverage gained the line T041 asked for and one more. Declining to capture has to be visible to the operator, not only true in the code -- the native class produces an absence, which is the one thing nobody notices. And `interactive`, `not-captured`, `redirected` and a stop are four different words, because collapsing them makes every deliberate silence look like a broken capture and every broken capture look deliberate. Cleanup gained the byte-identical rule with the blank-line bug as its reason. T042. README and USER-GUIDE both said the shell hook records metadata only and pointed at redlog-run for output. False since T020 for zsh, still true for bash, so both now say which is which. The guide gained the `redlog` command table, the three reasons a command is deliberately left alone -- with the `nc` upgrade named, because that is the one that costs an operator something real -- and the spool arriving with the time it happened rather than the time it was read. T043. The install journey, five steps, and the ordering is the point: the card asks for a first command before it asks for an install, because with nothing recorded the shortest route out of dark is a command and RedLog's own pane needs no adapter. It does not drive a shell; test/zsh-auto-capture.pty.test.ts does that against a real interactive zsh. **This spec is unrun locally.** shell-source install refuses on win32, so the whole file skips on the development machine and CI's ubuntu leg is its first execution. Every selector, string, helper export and the seed endpoint were checked against the source instead, so the residual risk is a wrong assumption about timing rather than about names. T046. verification.md, and it opens by saying it is incomplete. Twenty-one RED rows each carry the reason they failed, because "it went red" is not evidence -- a test can fail for the wrong reason and pass for the wrong reason afterwards. Two tests that passed on their first run are recorded as characterisation rather than claimed as RED. Operational Verification is the section this feature cannot be Verified without, and T044/T045 need a real Kali VM, so the spec stays Draft. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(052): make the enrolment journey say which half failed CI's first run of this spec went red on its last step: the card still read "open a new terminal" after a command was seeded with `source: auto-relay`. The assertion was on the card's text, so it named neither half of the question -- was the row not written, or did the card not read it? The product side is not what is wrong. Driving the same event through `ingest()` with the seed door's own envelope sets `ownShellLastEventAt` correctly, and `capture-health.test.ts` already covers the query. So the gap is in the test, and the fix is to make it speak: - `postCommand` checks the status and prints the body on a refusal, instead of discarding the response. - It then reads the row back through `/api/events` and fails there if it is missing. A refused POST and a card that did not update are now two different failures with two different messages. - The auto-relay command is `echo from-my-own-terminal` rather than `nmap -sV 10.0.0.1`: target extraction and a possible scope violation went down the same path for free, and this test is about one field. Still unrun locally -- shell-source install refuses on win32, so the file skips here and CI remains its first execution. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(052): ask main what it computed, not the card what it says The last round's diagnostics settled half of it: the row IS in the record with `source: auto-relay`, because `postCommand` now reads it back and would have failed there. So the card is reading something the query did not find. Which leaves two candidates that the current assertion cannot tell apart -- main computes null, or main is right and the renderer is showing a payload it fetched earlier. Quoting the card's sentence back names neither. So ask the main process directly, through the same bridge the card uses, and assert on `ownShellLastEventAt` before asserting on the text. A failure now prints the value main actually computed alongside `lastEventAt` and `installed`, which is enough to tell the two apart in one round instead of three. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(052): stop the card's poll being throttled under xvfb The spec passes locally on Linux in 8 seconds and failed twice in CI on the same commit. The payload diagnostic from the last round settled where: main computes `ownShellLastEventAt` correctly, and the card's DOM is what lags. The card re-reads capture health on a 5s interval in the renderer, and Electron throttles timers in a window it considers hidden. A hundred specs into a suite run under xvfb this window is not reliably the foreground one, so the interval stretches and a 20s wait fails on the host's idea of visibility rather than on anything the product did. Passing locally and failing in CI on identical code is the signature. So the window says it is visible and asks not to be throttled, once, in beforeAll. Test-only: nothing about the product's own behaviour changes, and an operator's window being genuinely hidden is not a case this journey is about. Verified by running the suite inside WSL with xvfb-run, which is what CI does -- 5 passed, 8.1s. That loop now exists: a Linux checkout at ~/redlog-e2e with its own node_modules, because `shell-source` install refuses on win32 and this file skips entirely on the development machine. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(capture): a cached health reading must not outlive an event The card does not poll. The Dashboard re-reads capture health once on mount and once per batch of new events, so whatever that one read returns is what the operator sees until the NEXT event arrives. getCaptureHealth() has a 750ms cache, and nothing invalidated it on a write -- so if anything else had asked in the previous 750ms (the StatusBar, an agent's redlog_status), the card's one read got the state from before the event that should have changed it, and stayed there. The worst moment for that is the one spec 052 is built around: the operator installs the shell hook, runs one command in their own terminal to prove it, and the card keeps saying "open a new terminal" (FR-015). It would correct itself on the second command, which is cold comfort for a setup step that exists to be believed. Now the cache is cleared on every publish. It still coalesces the storm of polls BETWEEN events, which is what it is for; it no longer answers "what has arrived" with a reading taken before the latest arrival. The bus defers its fanout to a microtask and the renderer's batch is downstream of the same publish, so the cache is gone before anyone can ask. Found by e2e/terminal-enrollment.spec.ts, which passed on a quiet machine and failed three times on a busy CI runner on identical code. Whether something else had filled the cache in the previous 750ms is a property of the load, not of the code -- which is also why it went unseen. **The previous commit's diagnosis was wrong, and this one removes it.** It blamed Electron's background-timer throttling for a 5s poll in the renderer. There is no 5s poll -- a code comment mentions one, and I took the comment's word for it instead of reading the effect. Disabling throttling changed nothing in CI. The workaround and its confident explanation are deleted rather than left to mislead the next reader; the e2e now carries the true account, and capture-health.test.ts pins the fix as a unit test that fails without it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
guan4tou2
added a commit
that referenced
this pull request
Oct 8, 2026
…ven (#287) * docs(052): say what is recorded, what is not, and what is still unproven T041. The lifecycle contract's states are a SOURCE's. This feature adds a second axis underneath one of them -- enrolled, stopped, mode, project-switched are one TERMINAL's -- and conflating the two would undo most of that document. Three rules keep them apart: a per-terminal stop is not a source failure, the state is a file so the terminal and the card cannot disagree, and a stop is a gap with two ends rather than silence. Coverage gained the line T041 asked for and one more. Declining to capture has to be visible to the operator, not only true in the code -- the native class produces an absence, which is the one thing nobody notices. And `interactive`, `not-captured`, `redirected` and a stop are four different words, because collapsing them makes every deliberate silence look like a broken capture and every broken capture look deliberate. Cleanup gained the byte-identical rule with the blank-line bug as its reason. T042. README and USER-GUIDE both said the shell hook records metadata only and pointed at redlog-run for output. False since T020 for zsh, still true for bash, so both now say which is which. The guide gained the `redlog` command table, the three reasons a command is deliberately left alone -- with the `nc` upgrade named, because that is the one that costs an operator something real -- and the spool arriving with the time it happened rather than the time it was read. T043. The install journey, five steps, and the ordering is the point: the card asks for a first command before it asks for an install, because with nothing recorded the shortest route out of dark is a command and RedLog's own pane needs no adapter. It does not drive a shell; test/zsh-auto-capture.pty.test.ts does that against a real interactive zsh. **This spec is unrun locally.** shell-source install refuses on win32, so the whole file skips on the development machine and CI's ubuntu leg is its first execution. Every selector, string, helper export and the seed endpoint were checked against the source instead, so the residual risk is a wrong assumption about timing rather than about names. T046. verification.md, and it opens by saying it is incomplete. Twenty-one RED rows each carry the reason they failed, because "it went red" is not evidence -- a test can fail for the wrong reason and pass for the wrong reason afterwards. Two tests that passed on their first run are recorded as characterisation rather than claimed as RED. Operational Verification is the section this feature cannot be Verified without, and T044/T045 need a real Kali VM, so the spec stays Draft. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(052): make the enrolment journey say which half failed CI's first run of this spec went red on its last step: the card still read "open a new terminal" after a command was seeded with `source: auto-relay`. The assertion was on the card's text, so it named neither half of the question -- was the row not written, or did the card not read it? The product side is not what is wrong. Driving the same event through `ingest()` with the seed door's own envelope sets `ownShellLastEventAt` correctly, and `capture-health.test.ts` already covers the query. So the gap is in the test, and the fix is to make it speak: - `postCommand` checks the status and prints the body on a refusal, instead of discarding the response. - It then reads the row back through `/api/events` and fails there if it is missing. A refused POST and a card that did not update are now two different failures with two different messages. - The auto-relay command is `echo from-my-own-terminal` rather than `nmap -sV 10.0.0.1`: target extraction and a possible scope violation went down the same path for free, and this test is about one field. Still unrun locally -- shell-source install refuses on win32, so the file skips here and CI remains its first execution. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(052): ask main what it computed, not the card what it says The last round's diagnostics settled half of it: the row IS in the record with `source: auto-relay`, because `postCommand` now reads it back and would have failed there. So the card is reading something the query did not find. Which leaves two candidates that the current assertion cannot tell apart -- main computes null, or main is right and the renderer is showing a payload it fetched earlier. Quoting the card's sentence back names neither. So ask the main process directly, through the same bridge the card uses, and assert on `ownShellLastEventAt` before asserting on the text. A failure now prints the value main actually computed alongside `lastEventAt` and `installed`, which is enough to tell the two apart in one round instead of three. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(052): stop the card's poll being throttled under xvfb The spec passes locally on Linux in 8 seconds and failed twice in CI on the same commit. The payload diagnostic from the last round settled where: main computes `ownShellLastEventAt` correctly, and the card's DOM is what lags. The card re-reads capture health on a 5s interval in the renderer, and Electron throttles timers in a window it considers hidden. A hundred specs into a suite run under xvfb this window is not reliably the foreground one, so the interval stretches and a 20s wait fails on the host's idea of visibility rather than on anything the product did. Passing locally and failing in CI on identical code is the signature. So the window says it is visible and asks not to be throttled, once, in beforeAll. Test-only: nothing about the product's own behaviour changes, and an operator's window being genuinely hidden is not a case this journey is about. Verified by running the suite inside WSL with xvfb-run, which is what CI does -- 5 passed, 8.1s. That loop now exists: a Linux checkout at ~/redlog-e2e with its own node_modules, because `shell-source` install refuses on win32 and this file skips entirely on the development machine. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(capture): a cached health reading must not outlive an event The card does not poll. The Dashboard re-reads capture health once on mount and once per batch of new events, so whatever that one read returns is what the operator sees until the NEXT event arrives. getCaptureHealth() has a 750ms cache, and nothing invalidated it on a write -- so if anything else had asked in the previous 750ms (the StatusBar, an agent's redlog_status), the card's one read got the state from before the event that should have changed it, and stayed there. The worst moment for that is the one spec 052 is built around: the operator installs the shell hook, runs one command in their own terminal to prove it, and the card keeps saying "open a new terminal" (FR-015). It would correct itself on the second command, which is cold comfort for a setup step that exists to be believed. Now the cache is cleared on every publish. It still coalesces the storm of polls BETWEEN events, which is what it is for; it no longer answers "what has arrived" with a reading taken before the latest arrival. The bus defers its fanout to a microtask and the renderer's batch is downstream of the same publish, so the cache is gone before anyone can ask. Found by e2e/terminal-enrollment.spec.ts, which passed on a quiet machine and failed three times on a busy CI runner on identical code. Whether something else had filled the cache in the previous 750ms is a property of the load, not of the code -- which is also why it went unseen. **The previous commit's diagnosis was wrong, and this one removes it.** It blamed Electron's background-timer throttling for a 5s poll in the renderer. There is no 5s poll -- a code comment mentions one, and I took the comment's word for it instead of reading the effect. Disabling throttling changed nothing in CI. The workaround and its confident explanation are deleted rather than left to mislead the next reader; the e2e now carries the true account, and capture-health.test.ts pins the fix as a unit test that fails without it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Spec 052's Phase 6, minus the two tasks that need a real Kali VM. Docs, the
install journey, and the verification record.
The spec stays
Draftafter this merges.verification.mdopens bysaying so: Operational Verification is the section this feature cannot be
Verified without, and T044 (the
ncupgrade) and T045 (the success criteria)need hardware this cannot reach. Everything else is done.
Two things a reviewer should look at first
e2e/terminal-enrollment.spec.tshas never runshell-sourceinstall refuses outright on win32, so the whole file skips onthe development machine and CI's ubuntu leg is its first execution. Every
selector, string, helper export and the seed endpoint were checked against the
source instead, so the residual risk is a wrong assumption about timing
rather than about names — but it is a real risk and it is named here rather
than discovered.
This is the shape CLAUDE.md §e2e warns about ("a spec can be POSIX-only
without anyone noticing"), met head-on rather than hidden behind a
test.skipnobody reads.The ordering in that journey is the point
The card asks for a first command before it asks for an install. With
nothing recorded at all the shortest route out of dark is a command, and
RedLog's own pane needs no adapter — so the install only becomes the card's
one action once there is a record. A journey written in the obvious order
would have asserted a button that is not on screen yet.
It does not drive a shell.
test/zsh-auto-capture.pty.test.tsdoes thatagainst a real interactive zsh; repeating it through Electron would buy a
slower copy of the same evidence. What only the app can settle is the card's
three states and the one action between them.
Why the rest
The lifecycle contract gained an axis, not states (T041)
docs/domain/SPEC-capture-source-lifecycle.mddescribes a source'sstates. Enrolled, stopped, mode and project-switched are a terminal's, and
conflating the two would undo most of that document — a per-terminal stop
would read as a source failure, which is the
Idlemistake again with adifferent cause.
Three rules keep them apart: a per-terminal stop leaves the source
ready;the state is a file, so the terminal and the card cannot disagree; and a stop
is a gap with two ends rather than silence.
Coverage gained the line T041 asked for and one more:
the code. The native class produces an absence, which is the one thing
nobody notices — so the lists are in Settings and in
redlog class list,and each such command is recorded
metadata-onlywith its reason.interactive,not-captured,redirectedand a stop are four differentwords. Collapsing them makes every deliberate silence look like a broken
capture and every broken capture look deliberate.
Cleanup gained the byte-identical rule, with the blank-line bug as its reason.
The docs were saying something false (T042)
README and USER-GUIDE both said the shell hook records metadata only and
pointed at
redlog-runfor output. That stopped being true for zsh at T020and is still true for bash, so both now say which is which rather than one
sentence that is half wrong.
The guide gained the
redlogcommand table, the three reasons a command isdeliberately left alone — with the
ncupgrade named, because that is the onethat costs an operator something real — and the spool arriving with the time
it happened rather than the time it was read.
The verification record says what it cannot prove (T046)
Twenty-one RED rows, each with the reason it failed. "It went red" is not
evidence: a test can fail for the wrong reason and pass for the wrong reason
afterwards. Two tests that passed on their first run are recorded as
characterisation rather than claimed as RED.
It also names the five real defects the suite found during this spec, because
those are the argument for the tests that found them — including a
classifybug that put every command in the relayed class,
ncandvimincluded,which is FR-025 and FR-026 exactly inverted.
Verification
npm run typechecknpm run verify:specsnpm run verify:architecturenpm run verify:i18nnpm test— 3161 passed, 24 skippednpm run builde2e/terminal-enrollment.spec.ts— cannot run here, see abovegit show --statagainst what was staged;same counts, so nothing from another session's index came with it
(CLAUDE.md §Branches)
Tasks T041–T043 and T046 of 47; 45 done. What remains is T044 (
nc→Ctrl-Z→stty raw -echo→fgon a real Kali VM — the spec is explicitthat a run which cannot complete it is not a verified feature), T045 (the
SC measurements over a ≥100-command engagement), and T047, the final gate
round once those two are in.
🤖 Generated with Claude Code