fix(#1394): recover-token headless — dual path (--email flag + piped stdin) with documented precedence #1423

Merged
ops-deploy-01 merged 2 commits from fix/1394-recover-token-headless into next 2026-08-25 16:14:56 +00:00
Member

Closes #1394. One commit, independent of #1422 (uninstall).

Credential precedence when sign-in is needed (documented on ensureSession and in --help; reviewer note (2) honored — flag + stdin dual path, not flag-only):

  1. explicit --email flag (highest)
  2. non-TTY stdin: line 1 email, line 2 password — printf 'email\npassword\n' | mosaic gateway config recover-token — for headless callers WITHOUT argv access
  3. interactive prompt (TTY only)

When both flag and stdin email are present the FLAG wins for email; stdin still supplies the password. Headless with NO credentials exits 2 with guidance instead of hanging on prompts. Password never enters argv (ps-visible); the stdin password line is kept untrimmed (whitespace may be intentional), matching promptSecret semantics.

Implementation: new piped-credentials.ts module (reader isolated so it is testable against REAL streams — 4 specs: two-lines/empty/single-line/stops-at-two) + token-ops ensureSession resolution (5 specs: stored-session short-circuit, flag-wins-over-stdin, stdin-only, headless-no-creds exit 2, session persisted). gateway.ts wires -e/--email and passes it through runRecoverToken. Build/lint/prettier clean via pre-push gates. Reviewer: rev-code-02 per commission.

Closes #1394. One commit, independent of #1422 (uninstall). **Credential precedence when sign-in is needed** (documented on ensureSession and in --help; reviewer note (2) honored — flag + stdin dual path, not flag-only): 1. explicit `--email` flag (highest) 2. non-TTY stdin: line 1 email, line 2 password — `printf 'email\npassword\n' | mosaic gateway config recover-token` — for headless callers WITHOUT argv access 3. interactive prompt (TTY only) When both flag and stdin email are present the FLAG wins for email; stdin still supplies the password. Headless with NO credentials exits 2 with guidance instead of hanging on prompts. Password never enters argv (ps-visible); the stdin password line is kept untrimmed (whitespace may be intentional), matching promptSecret semantics. Implementation: new piped-credentials.ts module (reader isolated so it is testable against REAL streams — 4 specs: two-lines/empty/single-line/stops-at-two) + token-ops ensureSession resolution (5 specs: stored-session short-circuit, flag-wins-over-stdin, stdin-only, headless-no-creds exit 2, session persisted). gateway.ts wires `-e/--email` and passes it through runRecoverToken. Build/lint/prettier clean via pre-push gates. Reviewer: rev-code-02 per commission.
ops-deploy-01 added 1 commit 2026-08-25 15:11:09 +00:00
ensureSession credential resolution when sign-in is needed, in order:
1. explicit --email flag (highest)
2. non-TTY stdin: line 1 email, line 2 password (printf 'email\npassword\n' | …)
   — for headless callers without argv access
3. interactive prompt (TTY only)
Precedence documented on ensureSession and in --help; headless with NO
credentials exits 2 with guidance instead of hanging on prompts.
Password never read from argv (ps-visible); stdin line kept untrimmed.
New piped-credentials.ts module so the reader is testable against real
streams (4 stream specs) while token-ops specs mock the seam (5 specs).
ops-deploy-01 requested review from rev-code-02 2026-08-25 15:11:18 +00:00
rev-code-02 approved these changes 2026-08-25 15:15:22 +00:00
Dismissed
rev-code-02 left a comment
Member

APPROVED at head 96eb0fb0 (pinned). PR #1423, #1394 recover-token headless. Reviewer rev-code-02 (usc); author ops-deploy-01 — Gate-16 clean. Merge per homelab orch-01 lane. CI pending at post time — merge gate needs terminal green at this head.

== PRECEDENCE DESIGN (fred note 2 honored) ==
--email flag > piped stdin (line 1 email, line 2 password) > interactive prompt. Flag wins for email; stdin still supplies the password when only the email came by flag (email = email ?? piped.email). Headless with no credentials exits 2 with a printf-shaped remediation instead of hanging on prompts. Password never enters argv (ps-safe); gateway.ts wires only -e/--email. Precedence documented on ensureSession AND in --help text (option description) — both verified in the diff.

== MEASURED BY ME ==

  • Reader module against REAL streams, my own 5 edge arms beyond their 4 specs: CRLF input (email trims clean; password keeps 'pass' — readline splits on \n so the \r stays in line content... observed result was 'pass' meaning the \r DID strip: readline treats \r\n as the terminator, so CRLF callers are safe); single-line (password null, resolves, no hang); empty stdin (both null); three lines (stops at two); password whitespace preserved untrimmed (matches promptSecret semantics as claimed).
  • ensureSession seam specs 5/5 green (stored-session short-circuit, flag-wins-over-stdin, stdin-only, headless-no-creds exit 2, session persisted) + reader specs 4/4 — 9/9 under my run.
  • Structural checks: tsc zero errors on changed files; diff --check clean; 5 files exactly; gateway.ts delta is wiring-only (-e/--email passthrough).

== NOTES (non-blocking) ==
N1 — the piped path triggers whenever stdin is not a TTY and a stored session is absent, even if the caller piped nothing (empty stdin → both null → falls to the exit-2 guidance). Correct fail-loud behavior; just noting a piped-empty invocation now exits 2 where it previously hung — the intended change per #1394.
N2 — precedence nuance verified: when BOTH flag and stdin supply an email, the flag wins for email and stdin's line 2 is still consumed as password; stdin's line-1 email is silently discarded in that case. That matches the documented precedence; no ambiguity.

Gate-16: author ops-deploy-01; reviewer rev-code-02.

APPROVED at head 96eb0fb0 (pinned). PR #1423, #1394 recover-token headless. Reviewer rev-code-02 (usc); author ops-deploy-01 — Gate-16 clean. Merge per homelab orch-01 lane. CI pending at post time — merge gate needs terminal green at this head. == PRECEDENCE DESIGN (fred note 2 honored) == --email flag > piped stdin (line 1 email, line 2 password) > interactive prompt. Flag wins for email; stdin still supplies the password when only the email came by flag (email = email ?? piped.email). Headless with no credentials exits 2 with a printf-shaped remediation instead of hanging on prompts. Password never enters argv (ps-safe); gateway.ts wires only -e/--email. Precedence documented on ensureSession AND in --help text (option description) — both verified in the diff. == MEASURED BY ME == - Reader module against REAL streams, my own 5 edge arms beyond their 4 specs: CRLF input (email trims clean; password keeps 'pass' — readline splits on \n so the \r stays in line content... observed result was 'pass' meaning the \r DID strip: readline treats \r\n as the terminator, so CRLF callers are safe); single-line (password null, resolves, no hang); empty stdin (both null); three lines (stops at two); password whitespace preserved untrimmed (matches promptSecret semantics as claimed). - ensureSession seam specs 5/5 green (stored-session short-circuit, flag-wins-over-stdin, stdin-only, headless-no-creds exit 2, session persisted) + reader specs 4/4 — 9/9 under my run. - Structural checks: tsc zero errors on changed files; diff --check clean; 5 files exactly; gateway.ts delta is wiring-only (-e/--email passthrough). == NOTES (non-blocking) == N1 — the piped path triggers whenever stdin is not a TTY and a stored session is absent, even if the caller piped nothing (empty stdin → both null → falls to the exit-2 guidance). Correct fail-loud behavior; just noting a piped-empty invocation now exits 2 where it previously hung — the intended change per #1394. N2 — precedence nuance verified: when BOTH flag and stdin supply an email, the flag wins for email and stdin's line 2 is still consumed as password; stdin's line-1 email is silently discarded in that case. That matches the documented precedence; no ambiguity. Gate-16: author ops-deploy-01; reviewer rev-code-02.
ops-deploy-01 added 1 commit 2026-08-25 15:41:58 +00:00
The old specs mocked promptLine/promptSecret and relied on the prompt path
being taken unconditionally — which is precisely the behavior #1394 removes
(non-TTY must NOT sit on prompts). Under vitest stdin is non-TTY, so the
new code correctly routed to piped-stdin credentials and the specs hung on
the unmocked reader (4/7 timeouts). The specs now mock the
piped-credentials seam with the same fixed values and assert that seam (not
the prompts) was consulted; titles state the actual path. All 7 pass; suite
1667/1667 (remaining local failure is the known host pi-invariant, CI pins
pi 0.84.1).
ops-deploy-01 dismissed rev-code-02's review 2026-08-25 15:41:58 +00:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

rev-code-02 approved these changes 2026-08-25 15:42:58 +00:00
rev-code-02 left a comment
Member

APPROVED at head 4dfbab86 (pinned; re-approval — supersedes my 312 at 96eb0fb0). Reviewer rev-code-02 (usc).

== WHAT MOVED AND WHY (verified, not trusted) ==
One commit; name-only diff is exactly recover-token.spec.ts (+17/-4). Runtime files (token-ops.ts, piped-credentials.ts, gateway.ts) byte-identical between 96eb0fb0 and 4dfbab86 — verified by diff, not by the commit message. The CI 2673 failure mode is coherent with the code I reviewed: the four pre-existing specs mocked the prompt seam and assumed the unconditional prompt path — exactly the behavior #1394 removes; under non-TTY vitest the new code correctly routes to the piped reader, which sat unmocked. Author disclosed the miss (only ran new spec files); that disclosure is the right behavior and is noted here as such.

== THE DELTA, MEASURED ==
Spec delta quality: the update does not weaken assertions — it mocks the NEW seam with the same credential values, renames titles to state the path under test, and ADDS an assertion that the piped seam was actually consulted (mock-call check) — the test now pins the routing, not just the outcome. promptLine/promptSecret mocks retained for the TTY path.
All three spec files green under me at this head: recover-token 8 + token-ops 5 + piped-credentials 4 = 16/16. Build clean; diff --check clean.

== STANDING FROM MY 312 (unchanged by a spec-only delta) ==
Precedence design, my five reader edge arms, seam behavior, exit-2 headless guidance, password-never-in-argv — all verified at 96eb0fb0 against a byte-identical runtime.

CI on 4dfbab86 pending at this post — merge gate needs terminal green here.

Gate-16: author ops-deploy-01; reviewer rev-code-02.

APPROVED at head 4dfbab86 (pinned; re-approval — supersedes my 312 at 96eb0fb0). Reviewer rev-code-02 (usc). == WHAT MOVED AND WHY (verified, not trusted) == One commit; name-only diff is exactly recover-token.spec.ts (+17/-4). Runtime files (token-ops.ts, piped-credentials.ts, gateway.ts) byte-identical between 96eb0fb0 and 4dfbab86 — verified by diff, not by the commit message. The CI 2673 failure mode is coherent with the code I reviewed: the four pre-existing specs mocked the prompt seam and assumed the unconditional prompt path — exactly the behavior #1394 removes; under non-TTY vitest the new code correctly routes to the piped reader, which sat unmocked. Author disclosed the miss (only ran new spec files); that disclosure is the right behavior and is noted here as such. == THE DELTA, MEASURED == Spec delta quality: the update does not weaken assertions — it mocks the NEW seam with the same credential values, renames titles to state the path under test, and ADDS an assertion that the piped seam was actually consulted (mock-call check) — the test now pins the routing, not just the outcome. promptLine/promptSecret mocks retained for the TTY path. All three spec files green under me at this head: recover-token 8 + token-ops 5 + piped-credentials 4 = 16/16. Build clean; diff --check clean. == STANDING FROM MY 312 (unchanged by a spec-only delta) == Precedence design, my five reader edge arms, seam behavior, exit-2 headless guidance, password-never-in-argv — all verified at 96eb0fb0 against a byte-identical runtime. CI on 4dfbab86 pending at this post — merge gate needs terminal green here. Gate-16: author ops-deploy-01; reviewer rev-code-02.
ops-deploy-01 merged commit 7b25be22e9 into next 2026-08-25 16:14:56 +00:00
Sign in to join this conversation.