Skip to content

(triggers): confirm a chain step's submission by the CLI descriptor (#407) - #410

Merged
devsuitup merged 3 commits into
mainfrom
fix/407-trigger-submit-proof
Oct 2, 2026
Merged

devsuitup merged 3 commits into
mainfrom
fix/407-trigger-submit-proof

Conversation

@devsuitup

Copy link
Copy Markdown
Owner

Closes #407

After a /compact chain step, the next step's text landed in the composer but its Enter became a line break, while the log said Chain step 1 sent. Two causes: the submission check was a level probe that the spinner ending the compaction satisfied (so the recovery Enter was never armed), and nothing waited for the CLI to be back at its prompt.

What changes:

  • ctx.getCliStatus(sessionId) exposes the CLI's own descriptor (status, statusUpdatedAt) through the cache cli-session-state.js already keeps (local sessions only; no new watcher).
  • Readiness: a chain step that follows a /compact step waits for status: "idle" with a statusUpdatedAt later than the compact send, bounded by SWITCHBOARD_CLI_READY_WAIT_MS (default 60 s) and the step deadline; on expiry it proceeds with a warning.
  • Proof by edge: with a descriptor, a submission counts only if the descriptor reads busy with a statusUpdatedAt at or after our Enter. Otherwise one recovery \r (existing rules), then confirmed: false.
  • Logs and results: submitted only when confirmed; not confirmed submitted warning otherwise (never sent). Steps carry submit_confirmed, the chain result lists unconfirmed_steps; an unconfirmed step reads submitted: "assumed". Without a descriptor nothing changes (level probe, sent).

Tests (test/trigger-descriptor-proof.test.js, test/trigger-context.test.js): fake timers and a fake descriptor for submitWithVerify / waitForCliIdleAfter (edge confirmed without retry; stale busy plus level spinner gives one retry then unconfirmed; edge after the recovery Enter; no-descriptor fallback; readiness idle/older idle/waiting/timeout/no descriptor), and the real watcher for three chain scenarios. Run against the old code the chain tests fail (no readiness wait, three Enters expected, no unconfirmed_steps). Mutations, each turning at least one test red: removing the readiness wait, dropping the statusUpdatedAt > afterMs bound, accepting a stale busy edge, falling back to the level probe, logging unconfirmed as info, dropping the timeout warning, marking the recovery always confirmed, forcing edge mode without a descriptor, dropping unconfirmed_steps, an unbounded readiness wait, and (context) answering for remote sessions.

Not verified: a real CLI. Whether waiting for the descriptor's idle makes the Enter after /compact submit is the measurement named in the issue (SWITCHBOARD_SUBMIT_ENTER_DELAY_MS 50 / 500 / 3000, with and without the wait). A command that never makes the CLI busy (an instant local slash command) is reported unconfirmed after a harmless extra Enter. task check: lint 0 errors; the suite shows only load-dependent flakes in unrelated files (git-changes real-git, passing in isolation) and the known viewer-file-watch libuv assertion.

After /compact the next step's Enter was absorbed as a line break while the
log said "sent": the level probe was satisfied by the compaction spinner and
nothing waited for the CLI to be back at its prompt.

A step after /compact now waits for the descriptor to report idle after the
compact, and a submission counts only on a busy edge after our Enter, with one
recovery Enter and an explicit "not confirmed submitted" otherwise. Without a
descriptor the old behaviour is unchanged.

Closes #407
@devsuitup

Copy link
Copy Markdown
Owner Author

Reviewing ef43c38 (adversarial review in progress).

@devsuitup

Copy link
Copy Markdown
Owner Author

Adversarial review at ef43c38: changes needed. (1) A fast turn (busy→idle inside one 150 ms flush + 100 ms poll) is only seen as idle with a newer timestamp, so it is reported unconfirmed and an extra Enter is written — any status write after our Enter will count as proof. (2) A permission dialog opened right after the Enter leaves the descriptor at waiting; that is not counted as submitted, and the recovery \r then lands in the dialog and accepts its default option — waiting will count as submitted and the recovery write will be refused whenever the descriptor reads waiting or busy. (3) A descriptor with a non-integer statusUpdatedAt turns edge mode on with no possible match — it will fall back to the old probe. Checked and holding: same-host ms-epoch clock basis, remote sessions keep the old behaviour, stale idle rejected by > afterMs; 27 targeted tests pass.

…wer a dialog

A turn too fast to show busy, or a dialog opened by our Enter, is a
reaction of the CLI: any status write at or after the Enter proves the
submission. The recovery Enter is withheld while the descriptor reads waiting
or busy, and a descriptor without an integer statusUpdatedAt is treated as
absent. A skipped readiness wait is logged.

Refs #407
@devsuitup

Copy link
Copy Markdown
Owner Author

Re-review at 2a99607: closed — any busy/idle/waiting status write with statusUpdatedAt >= enterAt counts as submission (a fast turn or an immediate permission dialog is no longer "unconfirmed"); the recovery \r is never written while the descriptor reads waiting or busy, even with an unusable timestamp; a non-integer statusUpdatedAt is treated as no descriptor (old behaviour); 6 new tests red on ef43c38, green now, 4 mutations killed. Remaining, stated: without a descriptor (remote sessions) the old level probe and its recovery Enter are unchanged; not run against a live CLI. Ready to merge once CI is green on 2a99607.

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.

(triggers): after /compact, a chain step's Enter becomes a line break and the step is logged as sent

1 participant