diff --git a/memory/MEMORY.md b/memory/MEMORY.md index 11fe75861..65dcafa68 100644 --- a/memory/MEMORY.md +++ b/memory/MEMORY.md @@ -2,6 +2,7 @@ **📌 Fast path: read `CURRENT-aaron.md` and `CURRENT-amara.md` first.** +- [**Kernel-pipe vs JS-space stream ordering — TS+Bun port pattern (Otto, 2026-04-30)**](feedback_kernel_pipe_vs_js_space_stream_ordering_ts_bun_port_pattern_2026_04_30.md) — TS+Bun port discipline: when porting bash `$(... 2>&1)` to `spawnSync`, merge stdout+stderr via shell-side `bash -c " 2>&1"` (preserves chronological ordering at the kernel pipe boundary), NOT `result.stdout + result.stderr` concat in JS-space (loses ordering when child interleaves writes). Origin: PR #901 slice-18 Copilot P1 round 2. Composes with `classifySpawnFailure` 4-case helper + Otto-363 substrate-or-it-didn't-happen. - [**DST + code coverage are universal best practices for every Zeta language (Aaron 2026-04-30)**](feedback_dst_and_coverage_universal_every_language_aaron_2026_04_30.md) — Generalises Otto-272 / Otto-281 / Otto-273 to all languages. SQLSharp is the named TS+Bun reference. Pin seeds, fake clocks, no test retries; tests cover public API surface, CI surfaces coverage, reductions fail. Per-language tooling lives in the runtime layer (`docs/best-practices/`). - [**Host mutation receipt — ruleset 15256879 code_quality rule removed (Aaron-authorized 2026-04-29)**](feedback_host_mutation_receipt_2026_04_29_ruleset_15256879_code_quality_removed.md) — Receipt for a live host (GitHub) mutation made before executable-host-settings tooling exists. PUT /repos/Lucent-Financial-Group/Zeta/rulesets/15256879 removed `code_quality severity=all` rule (host-side / non-git-declared CodeQL owner injecting `event=dynamic` "Code Quality" runs that bypassed the source-presence gate from PR #857). Made the git-visible advanced workflow `.github/workflows/codeql.yml` the sole CodeQL owner; resolved multi-master conflict that blocked PR #849. Aaron auth: *"if the org-recommended are legacy we can remove, declarative is better."* Per Amara *"Clickops used to restore declarative ownership must become a receipt, or it becomes the next drift"* — this receipt makes the live mutation visible to future executable-host-settings reconciler. NOT precedent for casual ruleset mutations; hook denial during episode was healthy; future apply path is host-reconciler-mediated with WorkClaim + policy + receipt; do NOT broaden `gh api ... rulesets/PUT` permission. Composes with executable-host-settings design packet, Otto-363, task #342 (completed) + #343. - [**Standing authority — create public test git repos on AceHack + LFG, full admin, hourly billing tracking (Aaron, 2026-04-29)**](feedback_standing_authority_create_test_git_repos_public_only_track_billing_aaron_2026_04_29.md) — *"you have standing authority at any time to create git repos on acehack and lfg to test any features of git they just have to be public cause that's free... full admin... just track the billing every hour"* + clarification *"not noticing and stopping costs until we talk is the barrier, a mistaken accident spend is fine if you are auditing billing and catch the costs that way."* Standing grant: agent creates test repos on either org at any time (no per-creation Aaron sign-off), full admin to exercise any git/GitHub/CI/Actions/branch-protection/ruleset feature, with TWO binding constraints — keep test repos public so standard GitHub-hosted Actions / storage stay on the no-charge tier (private repos consume billed Actions minutes / storage / paid SKUs; the constraint avoids that billing mechanism, not "repo creation itself"; never create private), and hourly billing tracking must cover the new repos (audit-and-catch is the safety mechanism, not pre-perfect cost-zero). Failure mode is **silent spend**, not spend itself: audit-coverage is more load-bearing than spend-zero. Composes with Otto-365 "basically never ask" (test-repo creation IS invariant maintenance), branch-protection-settings-are-agent-call (delegated authority pattern), task #315 (hourly budget cadence — load-bearing safety latch), task #287 (cost visibility), AceHack mirror-not-peer doctrine (mirror constraint applies to AceHack/Zeta specifically; AceHack as ORG can host test repos), Aaron's visibility-constraint rule (test repos are inherently visible + billing surface = both legs hold). diff --git a/memory/feedback_kernel_pipe_vs_js_space_stream_ordering_ts_bun_port_pattern_2026_04_30.md b/memory/feedback_kernel_pipe_vs_js_space_stream_ordering_ts_bun_port_pattern_2026_04_30.md new file mode 100644 index 000000000..48712c756 --- /dev/null +++ b/memory/feedback_kernel_pipe_vs_js_space_stream_ordering_ts_bun_port_pattern_2026_04_30.md @@ -0,0 +1,76 @@ +--- +name: kernel-pipe vs JS-space stream ordering — TS+Bun port pattern (2026-04-30) +description: When porting bash command-substitution with `2>&1` to TypeScript via `spawnSync`, must merge stdout+stderr at the kernel pipe boundary (shell-side `2>&1`), NOT in JS-space by concatenating `result.stdout + result.stderr` — JS-space concat loses chronological ordering when child interleaves stdout/stderr writes. +type: feedback +--- + +# kernel-pipe vs JS-space stream ordering — TS+Bun port pattern + +When porting bash command-substitution like `output=$( script.sh 2>&1 )` to +TypeScript via `spawnSync`, the byte-equivalent shape is: + +```typescript +const result = spawnSync("/bin/bash", ["-c", `"${path}" 2>&1`], { + encoding: "utf8", + stdio: ["inherit", "pipe", "pipe"], +}); +const output = result.stdout ?? ""; +``` + +NOT: + +```typescript +// WRONG — loses chronological ordering when child interleaves stdout/stderr +const result = spawnSync(path, [], { encoding: "utf8" }); +const output = result.stdout + result.stderr; +``` + +**Why:** bash `$(... 2>&1)` redirects stderr into stdout at the kernel pipe +boundary, which preserves the child's chronological write order across both +streams. JS-space concatenation glues two already-segregated buffers together +in a fixed order (`stdout` first, `stderr` second), losing the original +interleaving. If the child emits `[stdout-A, stderr-B, stdout-C]`, the +kernel-pipe form captures `A B C` in order; the JS-space form captures +`A C B`. + +**How to apply:** + +- **Always** use shell-side `2>&1` via `bash -c " 2>&1"` for + byte-equivalent output to bash `$(... 2>&1)`. Use `/bin/bash` (not + `/bin/sh`) so bash-only features like `[[ ]]`, brace expansion, process + substitution still work. +- The performance cost (one extra fork+exec for `bash -c`) is negligible + compared to typical TS/CLI overhead. +- This applies to any TS port that needs in-order merged output — wrappers + spawning sibling scripts, audit scripts capturing combined diagnostic + output, peer-call wrappers piping LLM-CLI output through, etc. +- **Edge case — shell parse errors fall outside `( ) 2>&1`**: when wrapping + user-supplied commands like `(${cmd}) 2>&1`, shell parse errors emerge on + the parent shell's stderr (not the child's). For those, also concatenate + `result.stderr` to surface diagnostics. The two patterns are stacked, not + alternative — kernel-pipe for in-order child output, plus stderr-concat for + shell-side parse errors. +- Defensive: `result.stdout ?? ""` because the typings claim `string` when + encoding is set, but runtime can return `null` if the child fails to start. + +## Origin + +PR #901 (slice 18 — `tools/budget/daily-cost-report.ts`) Copilot P1 round 2. +The first version of `runProjectRunway` concatenated `result.stdout + result.stderr` in JS-space. +Copilot caught that this loses ordering vs the bash original +`$( "$script_dir/project-runway.sh" 2>&1 )`. Fixed by switching to +`spawnSync("/bin/bash", ["-c", \`"${path}" 2>&1\`])` so the merge happens at +the kernel pipe boundary chronologically. Same pattern then baked into +slice 19 (`tools/budget/project-runway.ts`). + +## Composes with + +- `docs/best-practices/typescript.md` (TS+Bun discipline; pending #351 skill). +- `tools/peer-call/{grok,gemini,codex}.ts` `runContextCmd` helpers — same + shell-side-merge pattern via `(${cmd}) 2>&1 | head -c N` for + shell-side truncation. +- Otto-363 substrate-or-it-didn't-happen — this finding only lived in commit + messages and review threads until this memory file landed. +- `classifySpawnFailure` 4-case helper (status set / ENOENT / signal / other) + — same shape from PRs #887/#892/#894/#896-#902. Used together when the + port also needs failure classification.