diff --git a/GOALS.md b/GOALS.md index 640e21a3..3e6f52bc 100644 --- a/GOALS.md +++ b/GOALS.md @@ -109,3 +109,9 @@ Scope: src/modules/punk/repl-999999.0a1.0.tm, src/modules/punk/repl/codethread-9 Detail: goals/G-010-subshell-tree-navigation.md Goal: subshells form a navigable tree - a subshell can be suspended rather than quit, listed, resumed, and the console switched to any live subshell in the tree (e.g. grandchild to grandparent, or across branches) with each subshell's console state re-applied via its G-008 state set, building on the non-nested subshell model of G-002. Acceptance: from a grandchild subshell a single switch command reaches the grandparent without unwinding through the intermediate parent; switching between subshells on different branches preserves each subshell's session state and re-applies its console state set on activation; suspended subshells can be listed and resumed; `quit` still unwinds to the launching parent as today; a subshell whose switch commands are hidden/restricted cannot initiate switches. + +### G-011 [proposed] Optional per-console err channel with defined stderr semantics +Scope: src/modules/punk/console-999999.0a1.0.tm, src/modules/opunk/console-999999.0a1.0.tm, src/modules/punk/repl-999999.0a1.0.tm +Detail: goals/G-011-console-stderr-semantics.md +Goal: a console optionally carries an err channel as an attribute of its canonical {in out} identity - {in out err} specs accepted everywhere -console is, err resolving to process stderr for the default console and to the console's out channel elsewhere - so diagnostics and emit-to-err are first-class per-console operations instead of raw puts stderr. +Acceptance: console_spec_resolve and every -console site accept an {in out err} spec (err optional; existing pair/instance-name/object spec forms unchanged); opunk::Console exposes the err channel (nullable, additive base-class change); an unset err resolves to stderr for the default console and to the console's own out channel otherwise; punk::console's own warnings/diagnostics emitted while operating on a resolvable console go to that console's err (raw puts stderr remains only where no console is in play); an emit-to-err path exists and is exercised by at least one real consumer (e.g punk::repl); the effective err is discoverable from any thread/interp via console_fact_get; ownership/fact/mode-cache keys remain canonical {in out}; the existing console test suites pass unchanged. diff --git a/goals/G-011-console-stderr-semantics.md b/goals/G-011-console-stderr-semantics.md new file mode 100644 index 00000000..65d34677 --- /dev/null +++ b/goals/G-011-console-stderr-semantics.md @@ -0,0 +1,89 @@ +# G-011 Optional per-console err channel with defined stderr semantics + +Status: proposed +Scope: src/modules/punk/console-999999.0a1.0.tm, src/modules/opunk/console-999999.0a1.0.tm, src/modules/punk/repl-999999.0a1.0.tm +Acceptance: console_spec_resolve and every -console site accept an {in out err} spec (err optional; existing pair/instance-name/object spec forms unchanged); opunk::Console exposes the err channel (nullable, additive base-class change); an unset err resolves to stderr for the default console and to the console's own out channel otherwise; punk::console's own warnings/diagnostics emitted while operating on a resolvable console go to that console's err (raw puts stderr remains only where no console is in play); an emit-to-err path exists and is exercised by at least one real consumer (e.g punk::repl); the effective err is discoverable from any thread/interp via console_fact_get; ownership/fact/mode-cache keys remain canonical {in out}; the existing console test suites pass unchanged. + +## Context + +punk::console models exactly two channels per console: {in out}, defaulting to {stdin stdout}. +For pure ANSI terminal control that is a faithful model - queries and responses share one +terminal connection by protocol design - but the goals collectively move punk::console from +"terminal control library" toward "session-wide console abstraction" (G-001 pluggable backends, +G-007 location transparency, G-008 scoped state, G-009 themed profiles), and there the missing +stderr stream becomes load-bearing: + +- **Error/diagnostic routing is ad-hoc.** punk::console's own warnings (enableRaw/disableRaw + failures, query timeouts, reader-cooperation warnings, test_char_width failures - dozens of + sites) go to raw `puts stderr`, bypassing the abstraction the module provides. A console + backed by an ssh channel or tk widget (G-001) has no principled way to receive its own error + output: backend authors must either merge stderr into stdout or bypass punk::console for + errors, fragmenting the abstraction. +- **No console-level separation of result vs diagnostic streams.** The shellrun + run/runout/runerr/runx family preserves the stdout/stderr distinction at the process layer, + but once output enters the console the distinction collapses into the single out channel. A + subshell wanting to display a process's stderr differently (colour, prefix, separate pane) + must do so outside the console model - and a G-009 theme has no stderr stream to style. +- **Cross-interp discovery has no slot.** The G-007 fact store and ownership registry are keyed + by canonical {in out}; "what is my console's stderr?" has no fact to read, so location + transparency covers stdout-shaped operations only. +- **Raw-mode restore is asymmetric.** `previous_stty_state_stderr` is tracked for stty-state + restoration, but the model gives no way to address stderr as a console channel. + +## Approach + +**err is an attribute of a console, not part of its identity.** Console identity - the key for +the ownership registry (punk_console_owners), fact store (punk_console_facts) and mode cache +(punk_console_modecache) - remains the canonical {in out} pair: two consoles differing only in +err target are the same terminal, and keeping the key stable means the G-007 slices are +untouched. + +1. **Spec grammar.** console_spec_resolve (and the -console option everywhere) accepts a + 3-element {in out err} list. The 3-element slot is free in the existing grammar (pairs are + llength 2, instance names 1, voo object values >= 7). Existing spec forms are unchanged. +2. **Object layer.** ::opunk::Console gains a nullable err field beside o_in/o_out (additive + base-class change; subclasses without a stderr-equivalent leave it unset). +3. **Resolution defaults.** An unset err resolves to process stderr for the default console + {stdin stdout} (matching where its diagnostics go today) and to the console's own out + channel for any other console (single-stream backends merge, losing nothing they had). +4. **Diagnostics routing.** punk::console warnings/diagnostics raised while operating on a + resolvable console are emitted to that console's effective err. Raw `puts stderr` remains + only for contexts where no console is in play (e.g module-load-time warnings). +5. **Emit path.** A first-class emit-to-err form (shape decided at implementation time - e.g an + err-target variant of the opt_console_out convention) exercised by at least one real + consumer such as punk::repl, proving subshells can present stderr distinctly. +6. **Cross-thread discovery.** The effective err is exposed through console_fact_get so any + thread/interp of a session can answer "where do this console's diagnostics go" (G-007 + location transparency extends to the err attribute). + +## Alternatives considered + +- **err in the identity key: canonical {in out err} triple** - rejected: conflates routing with + identity (a console does not become a different terminal when its diagnostic target changes), + and churns the G-007 registry/fact/cache keying and its tests for no modelling gain. +- **Scope-boundary documentation only (no third channel)** - rejected by the user for subshell + flexibility: G-001 backends and G-009 themes need a distinct diagnostic stream to be + first-class, and a parallel unmanaged diagnostics mechanism would fragment the abstraction. + (Had punk::console stayed a pure terminal-control library, this would have been the right + answer - the dual-channel model is correct for ANSI control itself.) +- **Uniform default err == out (including the default console)** - rejected: silently changes + where the default console's diagnostics land today (process stderr), breaking redirection + and 2>-style capture users rely on. +- **Unset err means none (error on emit-to-err)** - rejected: pushes fallback burden onto every + caller and makes diagnostics emission failure-prone exactly where it must be reliable. + +## Notes + +- **Channel-name locality.** Like in/out, an err channel name is thread-local for non-std + channels; the fact-store exposure inherits the owner-qualified keying that already handles + this for other facts. What is discoverable cross-thread is the owner-context name (and the + default-console case, where std names are process-wide). +- **G-001 interaction.** G-001's acceptance requires the base class unchanged *by G-001's own + work*; the nullable err field is an additive change made by this goal. If G-011 lands first, + G-001 subclasses simply inherit the field; sequencing either way is safe. +- **G-008/G-009 interaction.** Once err exists, a per-subshell re-route of diagnostics is + console state that a G-008 state set could capture; that is enrichment for those goals, not + part of this goal's acceptance. +- **Not in scope.** Channel transforms/filtering on err (shellfilter stacks), a separate err + reader, and any change to shellrun's process-layer stdout/stderr handling - this goal defines + the console-side slot they can target.