Skip to content

fix(sessions): scope every session-store refusal to sessions - #2648

Merged
ScriptedAlchemy merged 1 commit into
masterfrom
fleet/scoped-refusals-all
Sep 29, 2026
Merged

ScriptedAlchemy merged 1 commit into
masterfrom
fleet/scoped-refusals-all

Conversation

@ScriptedAlchemy

Copy link
Copy Markdown
Owner

Follow-up to #2613. That PR scoped only one refusal kind: a session store whose observation rows predate the unified identity. A project sessions store refused for any other session authority still failed admission outright, so project open stalled and every code tool answered reset_required.

Root cause

classify_registered_schema_authorities returned Err for an LCM schema version skew, a git correlation version skew, or a workflow schema identity mismatch. The store never mounted. #2613's scoping (RefusedAuthorityV1 carried on the admitted owner and lease) only covered the observation check, which runs later, during installation.

Change

Every kind now goes through #2613's mechanism. No second mechanism was added.

  • RefusedAuthorityV1 is now an enum. Shape { authority, reason } covers observations and workflow. Version { component, found_version, required_version } covers LCM and git correlation, so the census keeps the typed versions.
  • Classification collects the first session refusal (LCM, then workflow, then git correlation) and keeps classifying the store's other authorities. If those admit, the store is admitted untouched: no install, no index builds, no convergence. It carries its refused authority. ensure_attached_registered_schema returns RegisteredSchemaAttachmentV1::{Admitted(plan), SessionsRefused(refusal)}, and the daemon path only schedules convergence for an admitted plan. Nothing fabricates a plan for a refused store.
  • If a later non-session authority also fails, the earliest session refusal stays the store's hard verdict. This keeps the existing precedence: a legacy store still fails with its LCM identity (legacy_profile_requires_reset_without_carrying_session_content_forward is unchanged and passes).
  • The existing census, doctor, tracedecay wipe --stale --yes, and refuse_reset_required session gating from fix(sessions)!: scope stale session-store refusals and reset #2613 already handle any refused authority, so they needed no change.
  • The fix(sessions)!: scope stale session-store refusals and reset #2613 observation journey and the three new kinds now share one journey, refused_session_stores_serve_code_until_their_scoped_reset, parameterized by refusal. The test file got 129 lines shorter.

Configuration, session temporal (it holds the code query cursor keys), remote-deletion, and registry refusals still refuse the whole store. Project open and code search read those authorities, so they are not session-only data.

Fails on master, passes here

stale_sessions_store_reset runs one journey per kind against a physically spawned daemon in an isolated HOME. It ages only the project sessions store: LCM version = 12, git correlation version = 5, or a foreign workflow definition_digest. Then it checks:

  • MCP initialize and tools/list serve.
  • search, callers, and file_dependents serve.
  • The census lists exactly that store with typed versions.
  • lcm_status refuses with reset_required and names the authority and tracedecay wipe --stale --yes. So do the kind's own session tool (lcm_grep, sessions_for, workflow_list_definitions).
  • The profile session store keeps serving.
  • Doctor reports 1 pending operator action(s), no issues., and never Stalled.
  • wipe --stale removes only that store's files, and every other profile file (including user-sessions.db) stays byte-identical.
  • After a restart, all features serve and the census is empty.

On base 75926a1 with only the tests applied, all three new cases fail:

code search never answered `probe`: {... "code":"application.surface.unavailable" ... "kind":"unavailable" ...}
test result: FAILED. 1 passed; 3 failed

On this branch: 4 passed.

The global-db tests that pinned "store refused whole" now assert the new contract: the store is admitted, reset_required() returns the typed refusal, and the main database bytes are unchanged. These are an_older_git_correlation_schema_is_refused_without_mutation, the four workflow *_requires_reset_without_mutation tests, and stale_or_future_lcm_marker_requires_reset_without_rewriting_marker.

Runtime journey (debug CLI from this branch, isolated HOME, cargo-slot clone, daemon in a MemoryMax=6G scope)

The corpus was indexed ({"outcome": "reached"} 1122 symbols), the daemon stopped, and UPDATE session_schema_migrations SET version = 12 WHERE name = 'lcm' run on the project sessions store. Then the daemon was restarted:

status: {"outcome": "reached"} 1122 [{"authority": "LCM", "found_version": 12, "reason": "LCM profile schema 12 is incompatible with required schema 13; reset the profile", "remedy": "tracedecay wipe --stale --yes", "required_version": 13, "store": "project sessions proj_6808402dd024d50f"}]
--- search            -> 10 results, no problem
--- callers main      -> evidence
--- lcm_status project -> reset_required {"authority": "LCM", "found_version": 12, ..., "remedy": "tracedecay wipe --stale --yes", "required_version": 13}
--- lcm_status user    -> evidence
--- MCP serve: initialize error: None; tools/list: 229 tools; has search/callers/status: True
--- doctor
  … Store project sessions proj_6808402dd024d50f requires reset (LCM profile schema 12 is incompatible with required schema 13; reset the profile). Pending operator action: run `tracedecay wipe --stale --yes`
1 pending operator action(s), 2 warning(s), no issues.
--- tracedecay wipe --stale --yes
reset project sessions proj_6808402dd024d50f (5 entries removed from …/projects/proj_6808402dd024d50f); the daemon recreates it empty
exit=0
--- sha256 of every profile file, before vs after: only the 5 project-sessions entries differ (removed); 119 other files identical
--- restart: {"outcome": "reached"} 1122, lcm_status project -> evidence, no reset-required stores

Verification (final tree)

  • tracedecay-global-db lib 385 passed; tracedecay-store-runtime lib 123 passed (1 pre-existing ignore); tracedecay-mcp lib 387 passed; tracedecay-application lib 482 passed, application_suite 66 passed.
  • mcp_suite: 609 passed, 0 failed.
  • transport_acceptance_suite: 18 passed, 0 failed.
  • tracedecay lib: 770 passed, 2 failed, neither from this change:
  • cargo clippy -p tracedecay-global-db -p tracedecay-store-runtime -p tracedecay-mcp -p tracedecay -p tracedecay-cli --all-targets --features tracedecay/test-transport,tracedecay/test-helpers,tracedecay-cli/test-transport -- -D warnings: clean. cargo fmt --all -- --check: clean.

#2611 (absent_query_port_fails_closed_for_every_awaiting_graph_handler) was a production ordering bug: the session-store requirement ran before request validation and graph admission. It landed independently as #2640 while this was in flight, so this PR carries no copy of that fix. The test passes on this tree.

A session store whose LCM schema version, git correlation version, or
workflow schema identity this binary refuses is now admitted untouched
for its other authorities, the same way #2613 admits a store whose
observation rows predate the unified identity. Project open, code
intelligence, and MCP initialize/tools/list keep serving; every session
feature answers the typed reset refusal naming
`tracedecay wipe --stale --yes`; doctor counts the store as a pending
operator action; and the scoped reset deletes exactly that store.

When a later authority also fails, the earliest session refusal stays
the store's hard verdict, so a legacy store keeps its LCM identity.
@changeset-bot

changeset-bot Bot commented Sep 29, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: e06dc19

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@ScriptedAlchemy
ScriptedAlchemy merged commit d842ec3 into master Sep 29, 2026
5 of 7 checks passed
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-29T14:25:52.774161Z e06dc19 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@ScriptedAlchemy
ScriptedAlchemy deleted the fleet/scoped-refusals-all branch September 29, 2026 14:20

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e06dc1927d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

.await?
{
RegisteredSchemaAttachmentV1::Admitted(convergence) => (Some(convergence), None),
RegisteredSchemaAttachmentV1::SessionsRefused(refused) => (None, Some(refused)),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Block remote replay for refused session stores

When a project store hits one of the newly softened LCM/workflow/git refusals, this still constructs a normal mounted owner. mount_project_session_store then unconditionally registers its weak issuer as a remote-replay target, while issue_target_lease never checks reset_required() before dispatching RemoteObservationReplay. A remote capture arriving before the operator resets the store can therefore receive a committed receipt and be written into a database that wipe --stale subsequently deletes, even though ordinary session calls correctly refuse the same store. Keep refused owners out of the replay/fence target or enforce the refusal when issuing replay leases.

AGENTS.md reference: AGENTS.md:L209-L211

Useful? React with 👍 / 👎.

Comment on lines +438 to +440
let surface = |refused: Option<RefusedAuthorityV1>| {
move |error| refused.map_or(error, RefusedAuthorityV1::error)
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve operational failures after a schema refusal

The surface closure replaces every later error whenever an earlier session refusal exists, rather than only choosing precedence among schema-reset verdicts. For example, with a stale LCM marker, a ConfigurationSchemaError::Storage from the freshness query is first converted into the correct database-operation error and then discarded here in favor of ProfileResetRequired; attach fails and the registry advertises the destructive stale-store reset even though the additional failure may be I/O or database corruption. Preserve operational/storage errors and apply the earlier-refusal precedence only to other typed schema incompatibilities.

AGENTS.md reference: AGENTS.md:L189-L191

Useful? React with 👍 / 👎.

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