Skip to content

fix: repair main — Phases 2 and 3 each shipped their own lifecycle reader#745

Merged
macanderson merged 1 commit into
mainfrom
fix/main-lifecycle-reader
Jul 26, 2026
Merged

fix: repair main — Phases 2 and 3 each shipped their own lifecycle reader#745
macanderson merged 1 commit into
mainfrom
fix/main-lifecycle-reader

Conversation

@macanderson

Copy link
Copy Markdown
Owner

main does not compile.

error[E0432]: unresolved import `tuning::session_lifecycle_enabled`
  --> stella-cli/src/memory.rs:78:9

Cause

Phase 2 (#738) and Phase 3 (#736) were built in parallel, and each independently needed to read context.lifecycle.enabled. Neither could see the other, so both wrote the reader — with a byte-identical body and the same fail-closed reasoning, differing only in name and visibility:

Phase 3 pub(super) fn lifecycle_enabled(..)
Phase 2 pub fn session_lifecycle_enabled(..)

Phase 3 merged first. Phase 2 merged second from a rebase that predated it, so tuning.rs kept Phase 3's function while memory.rs kept Phase 2's re-export of a name that no longer exists.

Neither PR was wrong, and neither conflicted textually with the main that existed when it merged.

Fix

The resolution both branches were converging on anyway: one reader, pub because Phase 2's engine-config builder calls it from outside the module, documented with both phases' reasoning, and one call site updated.

The pattern, since this is the third repair this week

Parallel branches that merge cleanly are not thereby compatible. A branch that lands second is verified against the main it was rebased onto, not the main it lands on, and nothing in the tooling notices the difference. Re-verifying the second branch against the actual post-merge tree is the step that catches this, and it is far cheaper than a red main.

Gate

make gate green: exit 0, 3801 tests, check-file-size: OK — 475 Rust files, none over 1500 lines except 25 grandfathered (none grew).

…le reader

`main` does not compile:

    error[E0432]: unresolved import `tuning::session_lifecycle_enabled`
      --> stella-cli/src/memory.rs:78:9

Third instance of the same failure mode this week, and the clearest one yet.
Phase 2 (#738) and Phase 3 (#736) were built in parallel, and each independently
needed to read `context.lifecycle.enabled`. Neither could see the other, so both
wrote the reader — with a byte-identical body and the same fail-closed
reasoning, differing only in name and visibility:

  Phase 3: pub(super) fn lifecycle_enabled(..)
  Phase 2: pub        fn session_lifecycle_enabled(..)

Phase 3 merged first. Phase 2 merged second from a rebase that predated it, so
`tuning.rs` kept Phase 3's function while `memory.rs` kept Phase 2's re-export
of a name that no longer exists. Neither PR was wrong and neither conflicted
textually with what was on main at the moment it merged.

The fix is the resolution both branches were converging on anyway: one reader,
`pub` because Phase 2's engine-config builder calls it from outside the module,
documented with both phases' reasoning, and one call site updated.

Worth stating plainly, because it has now cost three repairs: parallel branches
that merge cleanly are not thereby compatible. A merge that lands second is
verified against the main it was rebased onto, not the main it lands on, and
nothing in the tooling notices the difference. Re-verifying the second branch
against the actual post-merge tree is the step that catches this, and it is
cheap compared to a red main.

`make gate` green: exit 0, 3801 tests, check-file-size OK.
@vercel

vercel Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
stella-cli-docs Ignored Ignored Jul 26, 2026 11:27pm

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @macanderson, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@macanderson
macanderson merged commit 71dfe68 into main Jul 26, 2026
9 checks passed
@macanderson
macanderson deleted the fix/main-lifecycle-reader branch July 26, 2026 23:34
macanderson added a commit that referenced this pull request Jul 26, 2026
`context.lifecycle.enabled` now defaults to **`true`**. The whole
lifecycle — decomposed recall, frame identity, recall telemetry,
selection reasons, the typed learning loop, the ledger and its review
surface — is what a workspace gets without configuring anything.

> Stacked on #745, which repairs a `main` that does not currently
compile. Review that first.

## Why this is safe to default on

The behavior-compatibility suite asserts **every spec §8 guarantee on
both the typed path and the opted-out one**, from the same assertions:
tombstone suppression across re-learning on every surface, the
per-session creation cap, never clobbering a hand-edited file,
deterministic candidate identity, and failure isolation.

Phase 3 deliberately kept the lexical loop reachable rather than
deleting it, so "behavior-compatible" stays a checkable claim rather
than a promise. Defaulting on is the point of having built that evidence
— a lifecycle nobody runs cannot be evaluated.

## The flip is two changes, not one

This is the part worth reviewing closely. **Flipping
`LifecycleSettings::default()` alone would have shipped nothing.**

The reader asked:

```rust
.and_then(|s| s.context).is_some_and(|c| c.lifecycle.enabled)
```

A workspace with no `settings.json` — the common case, and every new
user — answered `false` regardless of the struct's default, because the
`context` block was **absent** rather than present-and-false. An absent
block now means "the defaults", which is what it always should have
meant.

There are tests for the no-settings-file case and the empty-block case
specifically, because that is exactly the gap that would have shipped
this as a silent no-op.

## What still fails closed

An **unreadable or malformed** settings file stays OFF. That file may be
the one in which someone turned the lifecycle off, and silently
overriding an opt-out we failed to parse is worse than leaving a default
unapplied. An explicit `"enabled": false` is honored and restores the
previous behavior wholesale.

## Notes for the reviewer

- The test fixtures that used to get the lexical loop *by writing
nothing* now opt out explicitly, since "write nothing and get the old
path" is no longer true. That is a change to what the fixtures **say**,
not to what they **assert** — no assertion was relaxed.
- `DriverConfig::lifecycle_enabled` still defaults `false` on purpose: a
programmatically built config should opt in deliberately rather than
inherit a product default it never read. The CLI passes the real
setting.
- `learning.mode` and `governance.mode` remain inert — they have no
consumers outside settings tests. `lifecycle.enabled` is the only real
switch.

## Gate

`make gate` green: **exit 0, 3805 tests**, `check-file-size: OK — 475
Rust files, none over 1500 lines except 25 grandfathered (none grew).`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant