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):
explicit --email flag (highest)
non-TTY stdin: line 1 email, line 2 password — printf 'email\npassword\n' | mosaic gateway config recover-token — for headless callers WITHOUT argv access
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.
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
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.
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.
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).
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.
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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):
--emailflag (highest)printf 'email\npassword\n' | mosaic gateway config recover-token— for headless callers WITHOUT argv accessWhen 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/--emailand passes it through runRecoverToken. Build/lint/prettier clean via pre-push gates. Reviewer: rev-code-02 per commission.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 ==
== 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.
New commits pushed, approval review dismissed automatically according to repository settings
APPROVED at head
4dfbab86(pinned; re-approval — supersedes my 312 at96eb0fb0). 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
96eb0fb0and4dfbab86— 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
96eb0fb0against a byte-identical runtime.CI on
4dfbab86pending at this post — merge gate needs terminal green here.Gate-16: author ops-deploy-01; reviewer rev-code-02.