Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
132 commits
Select commit Hold shift + click to select a range
4ff24cf
feat: outbound HTTP response streaming (stream response / wait for ne…
claude Jul 22, 2026
6991f58
docs: lock design for streamed server responses (item 3) and streamin…
claude Jul 22, 2026
73e66dd
feat: streamed server responses (start streaming response / write lin…
claude Jul 22, 2026
ed61ba5
feat: concurrent request handlers (main loop concurrently:)
claude Jul 22, 2026
e704b87
fix: address PR #641 review — backward-compat, status validation, doc…
claude Jul 22, 2026
ed89b65
ci: skip non-executable streaming docs examples in run_integration_tests
claude Jul 22, 2026
d2d88c4
docs: adopt Logbie Testing Policy (root testing.md) + WFL testing pro…
claude Jul 22, 2026
ad6c099
fix: streaming bindings use define_or_replace (loop re-reads + no bin…
claude Jul 22, 2026
465c3da
fix: address PR #641 review — transpile-fail, streaming type checks, …
claude Jul 22, 2026
149d1c6
fix: isolate per-handler run-state under `main loop concurrently:` (P…
claude Jul 22, 2026
c45c276
fix: auto-close server response streams on handler exit (spec item 5;…
claude Jul 22, 2026
04f7983
fix: 500 immediately when a handler ends without responding (P1 #3)
claude Jul 22, 2026
c20bec5
fix: `write line/chunk <var> to <file>` keeps the classic file write …
claude Jul 22, 2026
c243056
docs: reconcile concurrency/testing status with shipped reality (review)
claude Jul 22, 2026
5f1d1bc
fix: parse `content type <var>` in start-streaming; tighten review te…
claude Jul 22, 2026
143621b
docs: qualify concurrent-handler isolation wording in example (review)
claude Jul 22, 2026
4448b19
test: wait for server readiness instead of a fixed sleep (fix flaky CI)
claude Jul 22, 2026
6f14943
fix: mark streaming-statement vars used in analyzer; tidy body type h…
claude Jul 24, 2026
19ebb79
test+docs: broaden streaming unused-var test; qualify phase-plan clai…
claude Jul 24, 2026
90137fc
docs: align write-chunk value types, note write-line `with` limit, dr…
claude Jul 24, 2026
1e48764
fix: drop RefCell borrows before await; re-enable await_holding_refce…
claude Jul 24, 2026
ae518ce
fix: budget-check server write size before materializing bytes (P1 #6)
claude Jul 24, 2026
3366021
fix: finalize top-level streams/requests on every exit path (P1 #5)
claude Jul 24, 2026
d32f3fd
fix: only dispatch `start` to streaming when `streaming` follows (bac…
claude Jul 24, 2026
b632b59
fix: back off and cap consecutive failures in concurrent main loop (P…
claude Jul 24, 2026
f74bd37
feat: absolute total deadline for outbound streaming responses (P1 #2)
claude Jul 24, 2026
1778efb
ci: gate docs-validation and web tests; make scripts executable (P1 g…
claude Jul 24, 2026
7e0ea7a
test: surface server-thread interpret errors and re-raise join panics
claude Jul 24, 2026
430db00
fix: tighten ambiguous write line/chunk analysis
claude Jul 24, 2026
6127e72
fix: require wait-for-request first in concurrent loop; enforce text …
claude Jul 24, 2026
d86720a
docs: restore report_undefined_name doc comment to its function
claude Jul 24, 2026
6ea80e2
fix(ci): give Start-Process distinct stdout/stderr targets in web tes…
claude Jul 24, 2026
59d4809
fix(ci): address WFL server on 127.0.0.1 (not localhost) in Windows w…
claude Jul 24, 2026
a7c3a23
fix(ci): deterministic flush + monotonic deadline + exit code in web …
claude Jul 24, 2026
4a678bd
fix: robust redirect Location extraction; exact streaming dispatch; h…
claude Jul 24, 2026
9ea03c0
fix: parse with-continuation in write line/chunk; harden Windows web …
claude Jul 24, 2026
12dc3ea
docs: write line/chunk now supports with-concatenation directly
claude Jul 24, 2026
7b26602
fix: parse ambiguous write line/chunk readings independently; analyze…
claude Jul 24, 2026
6e8c7eb
fix(typechecker): accept stream handles in close, not just File
claude Jul 24, 2026
5259a1d
fix(typechecker): only File custom type is closeable; drop misleading…
claude Jul 24, 2026
982fe7b
fix(ci): guard Kill() and warn before racing TLS temp cleanup
claude Jul 24, 2026
31cda41
fix(typechecker): distinct stream-handle types so close rejects ordin…
claude Jul 24, 2026
38e6bde
style: rustfmt the stream-handle index-access match arm
claude Jul 24, 2026
2cc153f
fix(analyzer): stop rejecting valid classic writes with desugared values
claude Jul 24, 2026
8852786
fix(typechecker): support dot access on stream handles; require text …
claude Jul 24, 2026
8a8df6a
fix(ci): surface TLS temp-dir cleanup failure instead of hiding it
claude Jul 24, 2026
c912f57
fix(parser): do not require the unused classic reading to parse (writ…
claude Jul 24, 2026
9d31e94
fix(parser): consume a connective before 'as' in start streaming resp…
claude Jul 24, 2026
271d989
fix(P1): bound an active outbound read by the absolute stream deadline
claude Jul 24, 2026
9919c35
test: build WflConfig with struct-update syntax (clippy field_reassig…
claude Jul 24, 2026
60e6bda
fix(P1): make outbound streams handler-owned, closed on every exit path
claude Jul 24, 2026
9cc7327
fix(P1): cancel a blocked upstream read when the downstream client di…
claude Jul 24, 2026
1ea0d87
docs: outbound-stream deadline/ownership/disconnect now accurate (P1 …
claude Jul 24, 2026
fb737d9
test(P1): failing burst-disconnect regression for the concurrent loop
claude Jul 24, 2026
3c1fb80
fix(P1): treat a client disconnect as cancellation, not a handler fai…
claude Jul 24, 2026
2334272
style: cargo fmt (expand HttpClientError::Timeout, wrap assert)
claude Jul 24, 2026
9a3ffb3
test(P1): failing coverage for property-then-index parsing
claude Jul 24, 2026
d938427
fix(P1): compose a bracket index after a .property/.method access
claude Jul 24, 2026
c931fba
test(P1): failing coverage for span-mismatched write fallback
claude Jul 24, 2026
bcf88c1
test(P1): failing coverage for absolute stream lifetime on buffered r…
claude Jul 24, 2026
7fa2eb0
fix(P1): drop a span-mismatched classic write fallback
claude Jul 24, 2026
ed4764e
fix(P1): enforce the absolute stream lifetime on buffered reads
claude Jul 24, 2026
b760db3
test(P1): failing coverage for outbound cleanup on a dropped interpret()
claude Jul 24, 2026
3ba8351
fix(P1): close outbound handles when the interpret() future is dropped
claude Jul 24, 2026
0c750fc
test: bind server tests to an OS-assigned free port, not a hardcoded one
claude Jul 24, 2026
825c215
test(P1): failing coverage for pre-head disconnect cancellation
claude Jul 24, 2026
706200f
fix(P1): cancel a blocked upstream head open on client disconnect
claude Jul 24, 2026
879fc36
ci: free runner disk in the heavy build jobs to avoid linker Bus errors
claude Jul 24, 2026
87ab64c
docs: record re-review P1 lifecycle fixes (dev diary + design status)
claude Jul 24, 2026
a5518cc
test(P2): failing coverage for undefined in desugared write continuation
claude Jul 24, 2026
e11cdb6
fix(P2): analyze the shared continuation of a desugared ambiguous write
claude Jul 24, 2026
c19e67a
test(P2): failing coverage for streaming-operand type enforcement
claude Jul 24, 2026
e593378
fix(clippy): collapse the ambiguous-write lead guard into the match arm
claude Jul 24, 2026
daaf99e
fix(P2): enforce stream-handle operand types for wait/write/flush
claude Jul 24, 2026
c2bbbdb
test(P2): failing coverage for flush operand postfix accessors
claude Jul 24, 2026
a0ef057
fix(P2): compose postfix accessors onto merged-lead operands
claude Jul 24, 2026
bf700a9
docs: extend re-review dev diary with the P2 items (#7, #8)
claude Jul 24, 2026
e8c9712
fix(parser): anchor the '.' end-of-input diagnostic to the dot token
claude Jul 24, 2026
0d6e8e1
test(P1): failing real-socket + unit coverage for the re-review blockers
claude Jul 24, 2026
911ccc3
fix(P1): streaming lifecycle, cancellation, and ambiguous-write corre…
claude Jul 24, 2026
ec1af0a
ci(P1): run the documented integration gate and fix the Windows scripts
claude Jul 24, 2026
04bbbb9
docs(P1): real-time outbound cap, streaming-write timeout, re-review …
claude Jul 24, 2026
b465600
fix(ci,docs): make run_integration_tests.sh executable; correct write…
claude Jul 24, 2026
19bf271
refactor(parser): anchor the write `of` call to the `of` keyword
claude Jul 24, 2026
5fd93a9
test(parser): Red coverage for write `of`-arg precedence + method-cal…
claude Jul 24, 2026
f41f6b7
fix(parser): consistent `of`-arg precedence and method-call postfix i…
claude Jul 24, 2026
b25aed5
docs: clarify how a concurrent handler failure reaches the client
claude Jul 24, 2026
5e01e44
fix: improve concurrent server stability and streaming compatibility
logbie Jul 24, 2026
a262036
style: cargo fmt for CI gate
logbie Jul 24, 2026
fce5d86
fix: address #642 re-review blockers (lifecycle, timeouts, parser)
logbie Jul 24, 2026
8e8be0f
fix: finalize issue #642 pass to harden server stability and streaming
logbie Jul 25, 2026
09115f8
test: expose full streaming status operands
logbie Jul 25, 2026
9935320
fix: parse complete streaming status operands
logbie Jul 25, 2026
f23fb6b
test: use valid streaming status fixture
logbie Jul 25, 2026
d97f15b
test: expose postfix loss after of calls
logbie Jul 25, 2026
764685c
fix: resume postfix parsing after of calls
logbie Jul 25, 2026
c8cfa08
test: expose false type response boundary
logbie Jul 25, 2026
485bc34
fix: remove false type response boundary
logbie Jul 25, 2026
55f3d50
test: expose unmerged flush operands
logbie Jul 25, 2026
4a83845
fix: dispatch unmerged flush operands
logbie Jul 25, 2026
8b10f8b
test: expose response stream scope leaks
logbie Jul 25, 2026
a1bdd9d
fix: mirror runtime child scopes in type checker
logbie Jul 25, 2026
7bafc6d
test: use valid try scope fixture
logbie Jul 25, 2026
24f57d6
test: expose missing response stream type joins
logbie Jul 25, 2026
046b012
fix: join conditional type states
logbie Jul 25, 2026
a30fe4f
test: expose missing local file handle types
logbie Jul 25, 2026
370073e
fix: recreate local file handle types
logbie Jul 25, 2026
f0dc05d
test: exercise asynchronous file read type
logbie Jul 25, 2026
85768c2
test: use unambiguous handle collision guard
logbie Jul 25, 2026
0e98fe3
test: expose real file handle write fallback
logbie Jul 25, 2026
5bef235
test: expose stale deadline after clean eof
logbie Jul 25, 2026
af800a7
fix: preserve clean eof after stream deadline
logbie Jul 25, 2026
5d8fa3d
test: expose unbounded expired stream metadata
logbie Jul 25, 2026
c7f57b9
fix: bound expired outbound stream state
logbie Jul 25, 2026
00a2a3f
refactor: satisfy formatter and lint gates
logbie Jul 25, 2026
4c45f16
test: expose response expression disconnect stalls
logbie Jul 25, 2026
0d4b26b
fix: cancel disconnected response evaluations
logbie Jul 25, 2026
90a225d
refactor: make expiry tie handling deterministic
logbie Jul 25, 2026
edb8ce8
test: replace false-positive streaming lifecycle coverage
logbie Jul 25, 2026
3bc38c6
test: expose residual response cancellation leaks
logbie Jul 25, 2026
c73260f
fix: close residual response cancellation gaps
logbie Jul 25, 2026
7554ed1
test: complete issue 642 evidence coverage
logbie Jul 25, 2026
c9c748c
docs:correct-response-stream-scope-characterization
logbie Jul 25, 2026
96d5305
test: expose retained clean eof stream state
logbie Jul 25, 2026
68569b3
test: expose checker backedge and handler state gaps
logbie Jul 25, 2026
b32ff55
fix: terminalize clean eof streams immediately
logbie Jul 25, 2026
527b8fb
fix: stabilize checker control-flow state
logbie Jul 25, 2026
03966f0
test: expose eof latch and try scope races
logbie Jul 25, 2026
de34e32
fix: linearize eof and analyzer try state
logbie Jul 25, 2026
81b2574
test: cover clean eof missing-slot races
logbie Jul 25, 2026
a543650
docs: record stream state review fixes
logbie Jul 25, 2026
690be0a
fix: restore backward compatibility for classic syntax regressions
logbie Jul 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 71 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,20 @@ jobs:
with:
components: rustfmt, clippy

# Reclaim runner disk before building. This job compiles the workspace
# several times (debug + two release builds) plus the whole test suite, and
# the release profile keeps full debuginfo (`debug = true`), so the target
# tree is large; a full GitHub-hosted runner can otherwise exhaust its disk
# mid-link (a linker `Bus error`/SIGBUS). Removing preinstalled SDKs we do
# not use frees ~20 GB with no third-party action.
- name: Free disk space (Linux)
if: runner.os == 'Linux'
run: |
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc \
/opt/hostedtoolcache/CodeQL /usr/local/share/boost /usr/local/graalvm || true
sudo docker image prune --all --force > /dev/null 2>&1 || true
df -h /

# Cache Cargo registry and target directory for faster builds
- name: Cache Cargo registry and target directory
uses: Swatinem/rust-cache@v2
Expand Down Expand Up @@ -114,9 +128,11 @@ jobs:
- name: Build LSP
run: cargo build -p wfl-lsp --verbose

# Run Clippy for code quality
# Run Clippy for code quality. `--all-features` matches the binding gate in
# testing.md (the only features are opt-in dhat profiling, so this just
# compiles the feature-gated code for linting — it never runs it).
- name: Run Clippy
run: cargo clippy --all-targets -- -D warnings
run: cargo clippy --all-targets --all-features -- -D warnings

# Cross-platform integration test verification
integration-tests:
Expand All @@ -135,6 +151,19 @@ jobs:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable

# Reclaim runner disk before building: this job builds the (debuginfo-heavy)
# release tree AND every integration test binary (`cargo test --test '*'`),
# which together can exhaust a full runner's disk mid-link (linker
# `Bus error`/SIGBUS). Freeing unused preinstalled SDKs gives ~20 GB of
# headroom. Linux only — the Windows runner is not affected.
- name: Free disk space (Linux)
if: runner.os == 'Linux'
run: |
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc \
/opt/hostedtoolcache/CodeQL /usr/local/share/boost /usr/local/graalvm || true
sudo docker image prune --all --force > /dev/null 2>&1 || true
df -h /

# Cache Cargo registry and target directory for faster builds
- name: Cache Cargo registry and target directory
uses: Swatinem/rust-cache@v2
Expand Down Expand Up @@ -164,13 +193,47 @@ jobs:
}
Write-Host "✓ Release binary found: target/release/wfl.exe"

# Run integration tests specifically
- name: Run Integration Tests
run: cargo test --test split_functionality --verbose
# Run the DOCUMENTED WFL integration gate (testing.md): the same script a
# contributor runs locally. It executes the integration test binaries
# (`cargo test --test '*'`) AND the TestPrograms end-to-end programs —
# crucially including the intentional-error programs, which it asserts exit
# nonzero (previously those assertions lived only in this script and the
# script was never invoked in CI, so they never ran). Uses the release
# binary built above. Run on BOTH OSes so the declared Windows integration
# command is actually exercised, not merely documented.
- name: Run Integration Gate (Unix)
if: runner.os != 'Windows'
run: ./scripts/run_integration_tests.sh

- name: Run Integration Gate (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: ./scripts/run_integration_tests.ps1

# Validate that documentation examples still parse/analyze/lint against the
# current release binary (testing.md requires docs validation in CI).
# `--force` ignores the committed cache so CI always re-validates rather
# than trusting a stale cached result.
- name: Validate Docs Examples (Unix)
if: runner.os != 'Windows'
run: python3 scripts/validate_docs_examples.py --ci --force

- name: Validate Docs Examples (Windows)
if: runner.os == 'Windows'
run: python scripts/validate_docs_examples.py --ci --force

# Web-server integration tests: start real WFL servers and exercise them
# over HTTP (testing.md requires web tests in CI for the R3 web/streaming
# surface). Uses the release binary built above. Both OSes are covered so
# Windows web-server behavior does not go unvalidated.
- name: Run Web Server Tests (Unix)
if: runner.os != 'Windows'
run: ./scripts/run_web_tests.sh

# Run all integration tests to ensure comprehensive coverage
- name: Run All Integration Tests
run: cargo test --test '*' --verbose
- name: Run Web Server Tests (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: ./scripts/run_web_tests.ps1

# Database integration tests against live PostgreSQL and MariaDB servers.
# SQLite database tests need no services and already run everywhere via
Expand Down
33 changes: 31 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ Binding community and contribution policy lives at the **repo root** (not only u
| `AI_POLICY.md` | **AI-assisted work is welcome** — WFL was built with AI; do not discriminate against AI use; human author remains accountable |
| `CONTRIBUTING.md` | How to contribute; **Contributor application** process (Discussion or email) |
| `SECURITY.md` | Private vulnerability reporting only — never file security bugs as public issues |
| `testing.md` | **Binding Logbie Testing Policy + WFL testing profile** — Red→Green TDD evidence, required test layers, risk classes, and merge/release gates (see **Testing Guidelines** below) |

**Agent implications (already in force via governance):**

- **AI is first-class** — use coding agents freely; same quality bar as hand-written work (tests, docs, compatibility, reviewability).
- **Backward compatibility is sacred** — never break existing WFL programs without the documented deprecation path.
- **TDD mandatory** — failing tests first (`tests/`, `TestPrograms/`).
- **TDD mandatory** — failing tests first (`tests/`, `TestPrograms/`), governed by the binding **Logbie Testing Policy** in root `testing.md`: auditable **Red→Green** evidence for every behavioral change, coverage at the lowest useful layer plus every affected higher layer.
Comment on lines +15 to +21

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Make the testing-policy adoption status consistent across all governance files.

The agent guides describe testing.md as binding, while its embedded policy remains “Proposed” and effective only “Upon adoption.”

  • AGENTS.md#L15-L21: state the local adoption status and effective date consistently.
  • AGENTS.md#L125-L128: align the “Binding policy” wording with that status.
  • CLAUDE.md#L17-L23: align the governance reference with the adopted status.
  • CLAUDE.md#L163-L167: align the section title and introduction.
  • testing.md#L137-L143: mark the policy adopted locally, or explicitly document why it remains proposed.
📍 Affects 3 files
  • AGENTS.md#L15-L21 (this comment)
  • AGENTS.md#L125-L128
  • CLAUDE.md#L17-L23
  • CLAUDE.md#L163-L167
  • testing.md#L137-L143
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` around lines 15 - 21, Make the testing-policy adoption status
consistent across all governance references: update AGENTS.md lines 15-21 and
125-128, CLAUDE.md lines 17-23 and 163-167, and testing.md lines 137-143 to
state that the policy is locally adopted and effective, including a consistent
effective date; alternatively, explicitly document why it remains proposed
across every site. Preserve the existing testing requirements while aligning the
policy wording, status, and section titles.

- **Docs ship with the feature** — same change; validate examples; Dev Diary for non-trivial work.
- **Quality gates** — `cargo fmt`, `clippy -D warnings`, `cargo test`; conventional commits.
- **Do not invent maintainer identity or process** — Contributor status is by application; Maintainers own merges and releases unless those responsibilities are **explicitly delegated**. Prefer first name **Brad** only if referring to the primary maintainer in docs (no last name).
Expand Down Expand Up @@ -121,12 +122,40 @@ Source Code → Lexer → Parser → Analyzer → Type Checker → Interpreter
- Constants: `SCREAMING_SNAKE_CASE`

## Testing Guidelines
- **TDD is mandatory**: Write failing tests FIRST for any feature or bug fix.

**Binding policy:** root `testing.md` holds the **Logbie Testing Policy** and the
WFL testing profile. It governs every behavioral change. Non-negotiables an agent
MUST follow:

- **Red → Green → Refactor → Broaden → Record** — write the smallest useful test
FIRST, run it, confirm it **fails for the intended reason**, then make it pass;
a defect fix reproduces the defect. Keep auditable Red evidence (a Red commit
that is an ancestor of Green, or a timestamped CI artifact). A test first
observed after the code already passed is **not** a valid Red step. (§3, §6)
- **Classify risk first (R0–R3)** — concurrency, cancellation, lifecycle,
streaming, untrusted input, crypto/secrets, and backward compatibility are
**R3** and require negative/failure-path plus §11 risk-triggered tests. Risk is
never lowered to dodge a gate. (§5, §11)
- **Real boundaries, real assertions** — don't mock the boundary under test;
assert outcomes + side effects (not "didn't crash"); use negative assertions
for cancellation, writes-after-close, denial. (§7, §8.3)
- **No manufactured green** — never retry/skip/quarantine a required test to go
green; a flaky required test is failing. Non-executable docs programs use the
runner's `// CI-SKIP:` first-line directive and stay statically validated. (§8.2)
- **Concurrency/streaming/lifecycle (§11.3)** — for this repo's async/web/
streaming work, prove races/ordering, cancellation, timeouts, disconnects,
bounded queues/backpressure, resource limits, clean shutdown, writes-after-
close, and that one slow/failed handler doesn't block unrelated work.
- **PR evidence (§15)** — record risk class, acceptance criteria → tests, Red
evidence, layers run, and residual risk (template in `testing.md`).

### Mechanics
- **Locations**:
- Rust Unit/Integration: `tests/`
- WFL End-to-End: `TestPrograms/` (must pass with release build)
- WFL Test Framework: Use `describe`/`test` blocks, run with `wfl --test <file>`
- **Conventions**: feature‑oriented names (`*_test.rs`, `*.test.wfl`), keep perf benches under `benches/`.
- **Commands & profile**: one command per layer + the "run all presubmit" block are in root `testing.md`.
- **Testing Guide**: See `Docs/guides/testing-guide.md` for WFL testing framework documentation.

## Commit & Pull Request Guidelines
Expand Down
43 changes: 40 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ Binding community and contribution policy lives at the **repo root** (not only u
| `AI_POLICY.md` | **AI-assisted work is welcome** — WFL was built with AI; do not discriminate against AI use; human author remains accountable |
| `CONTRIBUTING.md` | How to contribute; **Contributor application** process (Discussion or email) |
| `SECURITY.md` | Private vulnerability reporting only — never file security bugs as public issues |
| `testing.md` | **Binding Logbie Testing Policy + WFL testing profile** — Red→Green TDD evidence, required test layers, risk classes, and merge/release gates (see **Testing Policy** below) |

**Agent implications (already in force via governance):**

- **AI is first-class** — use coding agents freely; same quality bar as hand-written work (tests, docs, compatibility, reviewability).
- **Backward compatibility is sacred** — never break existing WFL programs without the documented deprecation path.
- **TDD mandatory** — failing tests first (`tests/`, `TestPrograms/`).
- **TDD mandatory** — failing tests first (`tests/`, `TestPrograms/`). Governed by the binding **Logbie Testing Policy** in root `testing.md` (see **Testing Policy** below): every behavioral change needs auditable **Red→Green** evidence and coverage at the lowest useful layer plus every affected higher layer.
- **Docs ship with the feature** — same change; validate examples; Dev Diary for non-trivial work.
- **Quality gates** — `cargo fmt`, `clippy -D warnings`, `cargo test`; conventional commits.
- **Do not invent maintainer identity or process** — Contributor status is by application; Maintainers own merges and releases unless those responsibilities are **explicitly delegated**. Prefer first name **Brad** only if referring to the primary maintainer in docs (no last name).
Expand Down Expand Up @@ -159,13 +160,49 @@ Source Code → Lexer → Parser → Analyzer → Type Checker → Interpreter
- Types/Traits: `CamelCase`
- Constants: `SCREAMING_SNAKE_CASE`

## Testing Guidelines
- **TDD is mandatory**: Write failing tests FIRST for any feature or bug fix.
## Testing Policy (binding — root `testing.md`)

WFL adopts the **Logbie Testing Policy** (full text + the WFL testing profile in
root `testing.md`). It is binding for every behavioral change; the highlights an
agent MUST follow:

- **Red → Green → Refactor → Broaden → Record.** Write the smallest useful test
FIRST and run it to confirm it **fails for the intended reason**, then make it
pass. A defect fix MUST reproduce the defect. Keep auditable evidence (a Red
test-only commit that is an ancestor of the Green commit, or a timestamped CI
artifact) — a test first observed *after* the code already passed does **not**
establish Red. (§3, §6)
- **Risk class first.** Classify R0–R3 before implementing; when ambiguous, the
higher class applies, and it MUST NOT be lowered to dodge a gate. Anything
touching **concurrency, cancellation, lifecycle, streaming, untrusted input,
crypto/secrets, or backward compatibility is R3** and needs negative/
failure-path + the §11.3/§11.1 risk-triggered tests. (§5, §11)
- **Real boundaries.** A test MUST NOT mock the boundary it claims to verify;
"end-to-end" means the real binary/socket/file. Assert outcomes and side
effects, not "did not crash." Use negative assertions where absence matters
(cancellation, writes-after-close, denial). (§7, §8.3)
- **No manufactured green.** Required tests are never made green via retries,
skips, ignores, quarantine, or relaxed assertions; a flaky required test is a
failing test. Non-executable docs examples use the runner's `// CI-SKIP:`
first-line directive and are still validated statically. (§8.2)
- **Concurrency/streaming/lifecycle (§11.3) — always required for this repo's
async/web/streaming work:** prove races/ordering, cancellation, timeouts,
disconnects, bounded queues/backpressure, resource limits, clean shutdown, and
writes-after-close, and that one slow/failed handler does not block unrelated
work.
- **PR evidence (§15).** Every behavioral PR records risk class, acceptance
criteria → tests, Red evidence, the layers run, and residual risk (template in
`testing.md`).
- **Same bar for AI work.** AI-authored code/tests get the same verification —
"the model said it works" is not evidence.

### Testing mechanics
- **Locations**:
- Rust Unit/Integration: `tests/`
- WFL End-to-End: `TestPrograms/` (must pass with release build)
- WFL Test Framework: Use `describe`/`test` blocks, run with `wfl --test <file>`
- **Conventions**: feature‑oriented names (`*_test.rs`, `*.test.wfl`), keep perf benches under `benches/`.
- **Commands & profile**: one command per layer + the "run all presubmit" block are in root `testing.md`.
- **Testing Guide**: See `Docs/guides/testing-guide.md` for WFL testing framework documentation.

## Commit & Pull Request Guidelines
Expand Down
72 changes: 72 additions & 0 deletions Dev diary/2026-07-22-concurrent-handler-run-state-isolation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Dev Diary — 2026-07-22 — Per-handler run-state isolation for `main loop concurrently:`

## Context

PR #641 shipped `main loop concurrently:` — opt-in cooperative concurrency for
HTTP request handlers, driven by a `FuturesUnordered` on the single interpreter
thread. Review (maintainer P1 #1, echoed by Copilot) flagged a soundness gap:
the concurrent loop isolated each handler's **environment** (variables), but the
interpreter's **run-state** — the count-loop variable and its flag
(`current_count` / `in_count_loop`), the live recursion depth (`call_depth`),
the diagnostic call stack (`call_stack`), and the current block's overload-dup
set — still lived on the shared `Interpreter` behind `RefCell`/`Cell`.

Under serial execution that state is never contended. Under
`main loop concurrently:` several handler futures interleave on one thread, so at
every `await` one handler's run-state was visible to — and overwritable by —
whichever sibling was polled next. A handler that yielded *inside a `count` loop*
would resume and read a `count` set by another handler.

## The bug, concretely

`count` does not resolve through the environment while a count loop is active;
`try_evaluate_variable_sync` short-circuits on `in_count_loop` and reads
`self.current_count` directly. Both fields are global, so two concurrent count
loops share one `current_count`. A handler counting `1..5` that yields mid-loop
could come back reading `100..104` from a sibling.

## Fix — a poll-swap wrapper (no `Rc`→`Arc`, no threads)

The interpreter core stays single-threaded and `Rc`-based (a hard constraint).
Rather than thread a per-handler execution context through every `&self` method,
each handler owns a `RunState` snapshot and an `IsolatedHandler` future wraps the
handler:

- On **each `poll`**, `swap_run_state` swaps the handler's `RunState` into the
interpreter's live fields (a field-by-field `mem::swap`, its own inverse).
- The inner handler future is polled.
- The instant `poll` returns — `Ready` **or** `Pending` — the state is swapped
back out into the handler's `RunState`.

So the interpreter's run-state fields become effectively poll-local: exactly one
handler's state is installed at a time, and a suspended handler's state is parked
in its own `RunState` where no sibling can touch it. Each handler starts from
`RunState::fresh(base_call_depth)`. The inner future is already wrapped in
`catch_unwind`, so a panic surfaces as `Ready` and the swap-back still runs,
leaving the scratch fields clean for the next sibling.

Serial execution is completely untouched — `IsolatedHandler` is used only by
`execute_concurrent_main_loop`.

## Testing (Red → Green)

`tests/concurrent_main_loop_test.rs::test_concurrent_handlers_do_not_share_count_loop_state`:
two concurrent handlers each run a `count` loop over a **disjoint** range
(`1..5` vs `100..104`), yielding via `wait for` mid-iteration and then reading
`count`. With isolation each handler observes only its own range.

- **Red** (isolation bypassed — plain handler pushed to `FuturesUnordered`):
`/a` returned `100-101-102-103-104-`, i.e. it observed the *other* handler's
entire count range. `assertion left == right failed`.
- **Green** (isolation restored): `/a` → `1-2-3-4-5-`, `/b` →
`100-101-102-103-104-`.

Risk class **R3** (concurrency + lifecycle). The test asserts a concrete wrong
outcome under sharing, not merely "did not crash".

## Follow-ups still open from the review

Larger P1 items remain and are tracked in
`Docs/development/concurrency-phase-plan.md`: immediate-500 on pre-respond
failure, browser-disconnect/504 cancellation threaded into `wait for` and
upstream reads, and an absolute total-stream deadline.
Loading
Loading