Skip to content

Commit 30b9e2b

Browse files
committed
Implement replay action handling in build model
- Added support for replay actions in the build model, including new event types for declaring actions, adding inputs, outputs, arguments, and environment variables. - Introduced functions to handle replay events and manage their associated data within the build model. - Updated the pipeline to include replay action information in the build graph snapshot. - Enhanced the test suite to validate the correct handling of replay actions and their representation in the output. - Created a new query session structure to cache effective items and values related to replay actions, improving performance and reducing redundant computations.
1 parent 735789a commit 30b9e2b

39 files changed

Lines changed: 3357 additions & 399 deletions

docs/build_model/build_model_query.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ consume query-visible model semantics instead of bypassing the frozen model.
1717
- Public functions return explicit spans or scalar values.
1818
- No public API returns a mutable pointer into the model.
1919
- Effective-property queries may use a caller-provided scratch arena.
20+
- Repeated derived queries may also use a canonical `BM_Query_Session`.
2021

2122
## 3. Public Query Surface
2223

@@ -176,6 +177,48 @@ The `*_items(...)` variants are the canonical codegen-facing API. They preserve
176177
`BM_String_Span` variants remain compatibility wrappers that strip each item
177178
down to `value` only.
178179
180+
### Query Session
181+
182+
`C1M` adds an arena-owned memoized derived-query surface for consumers that
183+
issue many repeated effective or imported-target lookups:
184+
185+
```c
186+
typedef struct BM_Query_Session BM_Query_Session;
187+
188+
typedef struct {
189+
size_t effective_item_hits;
190+
size_t effective_item_misses;
191+
size_t effective_value_hits;
192+
size_t effective_value_misses;
193+
size_t target_file_hits;
194+
size_t target_file_misses;
195+
size_t imported_link_language_hits;
196+
size_t imported_link_language_misses;
197+
} BM_Query_Session_Stats;
198+
199+
BM_Query_Session *bm_query_session_create(Arena *arena, const Build_Model *model);
200+
const BM_Query_Session_Stats *bm_query_session_stats(const BM_Query_Session *session);
201+
```
202+
203+
The session is query-time only:
204+
205+
- it does not mutate or persist anything back into `Build_Model`
206+
- it preserves the same raw/effective semantics as the stateless APIs
207+
- it owns memoized lifetime through the caller-provided arena
208+
209+
The canonical memoized surface covers:
210+
211+
- effective include directories, compile definitions, compile options, link
212+
libraries, link options, and link directories in both item and value form
213+
- effective compile features
214+
- effective imported/local target file and linker file resolution
215+
- imported link-language lookup
216+
217+
Memoization keys must include all caller-visible semantic context required to
218+
keep derived results correct, including target, family, usage mode, current
219+
target, config, compile language, platform id, and build/install interface
220+
state.
221+
179222
### Other Domains
180223

181224
Release-1 must also include query helpers for:
@@ -192,6 +235,7 @@ Release-1 must also include query helpers for:
192235
The canonical replay query surface is:
193236

194237
```c
238+
bool bm_replay_action_id_is_valid(BM_Replay_Action_Id id);
195239
size_t bm_query_replay_action_count(const Build_Model *model);
196240
BM_Replay_Action_Kind bm_query_replay_action_kind(const Build_Model *model,
197241
BM_Replay_Action_Id id);

docs/build_model/build_model_replay.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,14 @@ invent replay actions from evaluator-private state.
137137
Builder requirements:
138138

139139
- only events documented as downstream-consumable may create replay actions
140+
- canonical replay ingest uses dedicated Event IR kinds:
141+
`EVENT_REPLAY_ACTION_DECLARE`, `EVENT_REPLAY_ACTION_ADD_INPUT`,
142+
`EVENT_REPLAY_ACTION_ADD_OUTPUT`, `EVENT_REPLAY_ACTION_ADD_ARGV`,
143+
`EVENT_REPLAY_ACTION_ADD_ENV`
140144
- replay actions copy retained strings into builder-owned storage
141145
- owner directory is captured from the active directory frame at ingest time
142146
- argv, env, inputs, and outputs are stored as explicit spans
147+
- environment entries freeze as normalized `KEY=VALUE` strings
143148
- the draft may keep unresolved symbolic references where necessary, but the
144149
frozen model must expose stable typed accessors only
145150

docs/tests/evaluator_codegen_diff.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ Legal memoization evidence includes:
243243
- targeted repeated-query workloads over closure-harness cases that stress
244244
effective-query and target-resolution access
245245
- before/after measurements tied to the same case set and tool availability
246+
- `BM_Query_Session` hit/miss counters gathered while those workloads run
246247
- proof that canonical memoized query paths replace duplicated first-line
247248
consumer caches without changing case classification or observable outputs
248249

@@ -255,6 +256,8 @@ Memoization evidence does not replace the normal closure gate:
255256

256257
Unless the roadmap later freezes a numeric gate, memoization measurements are
257258
comparative and diagnostic rather than release-blocking by absolute threshold.
259+
Session counters are therefore valid supporting evidence even when a host is
260+
not suitable for stable wall-clock benchmarking.
258261

259262
## Relationship To Other Suites
260263

docs/tests/test_daemon_roadmap.md

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ As of April 9, 2026:
3535
- only the `nobify` product needs portability; test infrastructure does not
3636
- inter-wave breakage remains acceptable if it shortens time-to-daemon
3737

38+
The core execution-owner rewrite is now landed. The main remaining pain is
39+
operational rather than structural: daemon state, recovery, lifecycle, and
40+
watch session ownership now need explicit hardening ahead of any further
41+
throughput or scheduler expansion.
42+
3843
The target end state of this roadmap is:
3944

4045
`./build/nob` front door -> daemon client/supervisor -> reactor `nob_testd` -> shared runner core -> suites
@@ -114,6 +119,7 @@ The target CLI contract is:
114119
- `./build/nob test watch auto`
115120
- `./build/nob test daemon start`
116121
- `./build/nob test daemon stop`
122+
- `./build/nob test daemon restart`
117123
- `./build/nob test daemon status`
118124

119125
## 4. Why This Is A Separate Program
@@ -176,6 +182,22 @@ as a contract that must survive intact.
176182
- remove or hollow out `build/nob_test`
177183
- align docs and scripts around the new mental model
178184

185+
### 5.7 Operational Ergonomics, Recovery, And Introspection
186+
187+
- make daemon lifecycle and active-work state visible and explainable through
188+
CLI and protocol surfaces
189+
- replace opaque `busy`/recovery behavior with structured control-plane
190+
outcomes and explicit admission policy
191+
- remove manual stop/clean/status choreography from the common local workflow
192+
193+
### 5.8 Detached Watch Sessions And Session Ownership
194+
195+
- move long-lived watch ownership fully into the daemon instead of the active
196+
client connection
197+
- let watch sessions survive transient client disconnects by default
198+
- surface detached watch state and lifecycle through `status` and daemon
199+
control paths
200+
179201
## 6. Wave Plan
180202

181203
The wave descriptions below intentionally preserve the state and assumptions
@@ -363,6 +385,64 @@ Exit criteria:
363385
- there is one clear public entrypoint and one clear execution owner
364386
- stale mental models around the old runner are gone
365387

388+
### T7 Operational Hardening And Introspection
389+
390+
Goal:
391+
- make the daemon predictable to operate during normal day-to-day development
392+
393+
Deliverables:
394+
- enriched `daemon status` output covering effective state, active work,
395+
module/profile, run duration, attached client state, preserved logs or
396+
workspace paths, pending cancel/drain state, and cache hit/miss reasons
397+
- explicit admission policy:
398+
- foreground run versus another foreground run rejects with a structured
399+
error instead of only free-form text
400+
- watch reruns use documented replace-running behavior
401+
- the daemon reports which policy was applied for the active request
402+
- `daemon stop` becomes drain-aware instead of failing solely because the
403+
daemon is currently `busy`
404+
- `daemon stop --force` becomes the official immediate-cancel path with
405+
timeout-aware escalation
406+
- `./build/nob test daemon restart` becomes an official lifecycle path
407+
- `./build/nob test clean` stops requiring manual pre-stop coordination:
408+
- default `clean` coordinates daemon drain/stop first
409+
- `clean --force` performs immediate daemon teardown before cleanup
410+
- the binary protocol gains stable error codes plus optional metadata for the
411+
active request so text messages stop being the only control-plane surface
412+
- stale socket/PID recovery and kill-escalation outcomes become visible through
413+
`status` and structured errors instead of remaining purely internal behavior
414+
415+
Non-goals:
416+
- no general parallel scheduler yet
417+
- no detached watch sessions yet
418+
- no portability work
419+
420+
Exit criteria:
421+
- `busy` is explainable and recoverable from the CLI
422+
- `status` shows what the daemon is actually doing
423+
- `stop` and `clean` work from both idle and busy states
424+
- watch replace/cancel behavior is explicit instead of implicit
425+
426+
### T8 Detached Watch Sessions
427+
428+
Goal:
429+
- decouple watch session lifetime from the client connection that started it
430+
431+
Deliverables:
432+
- watch sessions become daemon-owned state instead of attached-client state
433+
- terminal or client disconnect no longer stops watch by default
434+
- `status` and daemon lifecycle surfaces report and control detached watch
435+
sessions explicitly
436+
437+
Non-goals:
438+
- no multi-user sharing model
439+
- no rich attach UX in this wave
440+
441+
Exit criteria:
442+
- watch survives client disconnect
443+
- detached watch ownership and state are visible and controllable through the
444+
daemon surface
445+
366446
## 7. Required Interfaces And Behavior
367447

368448
The implementation target for later waves is:
@@ -374,17 +454,27 @@ The implementation target for later waves is:
374454
embedding them in CLI-only flow
375455
- daemon-facing control flow uses typed request/result structures and stable
376456
ids, even if the CLI remains text-oriented
457+
- the control plane eventually exposes structured daemon states including
458+
`idle`, `busy`, `watching`, `draining`, and `stopping`
459+
- the control plane eventually exposes stable error codes and optional active
460+
request metadata rather than relying on free-form text alone
461+
- daemon lifecycle eventually includes `start`, `stop`, `stop --force`,
462+
`restart`, and `status`
377463
- the daemon is a single-threaded Linux reactor that owns:
378464
- local socket accept/read/write
379465
- child-process supervision
380466
- timer-based debounce and timeout handling
381467
- watch event intake and overflow recovery
468+
- `clean` eventually coordinates with daemon lifecycle instead of requiring
469+
users to manually stop the daemon first
382470
- logs, preserved failure workspaces, and `Temp_tests` layout remain
383471
conceptually intact unless a later wave explicitly replaces them
384472
- old preflight checks stay logically owned by runner-core, but local daemon
385473
runs may cache or skip them under the fast profile
386474
- module registry entries carry the watch roots and routing metadata needed by
387475
`watch auto`
476+
- detached watch session ownership eventually belongs to daemon state rather
477+
than to the lifetime of a single foreground client
388478
- sanitizer, coverage, and explicit heavy suites remain supported, but the
389479
fast loop is optimized around normal local development rather than around
390480
those profiles
@@ -397,14 +487,21 @@ The daemon program is only complete when it has explicit proof for:
397487
- transitional self-rebuild for `src_v2/build/nob_test.c`
398488
- `./build/nob test <module>` end-to-end
399489
- daemon auto-start on first client request
400-
- `daemon start|stop|status`
490+
- `daemon start|stop|restart|status`
491+
- enriched `daemon status` for an active run, including active-work metadata
401492
- stale socket and stale PID recovery
402493
- client/daemon compatibility for the versioned local protocol
403494
- peer validation on the local UNIX socket
495+
- structured `busy` and recovery errors instead of text-only rejection
496+
- `daemon stop` while work is active
497+
- `daemon stop --force` against a stuck worker
498+
- `./build/nob test clean` from both idle and busy daemon states
404499
- watch rerun on save
405500
- `watch auto` routing for representative file changes
406501
- last-write-wins cancellation under save storms
502+
- explicit watch replace-running reporting under save storms
407503
- watch recovery after rename churn or inotify overflow
504+
- detached watch persistence across client disconnect in the T8 wave
408505
- fast profile behavior not regressing strict or sanitizer profiles
409506
- preserved failure workspaces and captured logs still working through daemon
410507
execution

docs/transpiler/event_ir_v2_spec.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,17 @@ Current families:
9797
- `PACKAGE`
9898
- `EXPORT`
9999
100+
Current dual-role downstream replay bridge kinds:
101+
- `EVENT_REPLAY_ACTION_DECLARE`
102+
- `EVENT_REPLAY_ACTION_ADD_INPUT`
103+
- `EVENT_REPLAY_ACTION_ADD_OUTPUT`
104+
- `EVENT_REPLAY_ACTION_ADD_ARGV`
105+
- `EVENT_REPLAY_ACTION_ADD_ENV`
106+
107+
These kinds remain inside the canonical family set and use
108+
`EVENT_ROLE_RUNTIME_EFFECT | EVENT_ROLE_BUILD_SEMANTIC` so downstream replay
109+
consumers can ingest them without introducing a replay-only family.
110+
100111
Base-contract freeze rules:
101112
- canonical `Event_Family` and `Event_Kind` values are append-only
102113
- new canonical entries append at the tail of the enum lists

0 commit comments

Comments
 (0)