Skip to content

refactor(sessions): decompose the sessions node into eight children - #496

Merged
simonheimlicher merged 12 commits into
mainfrom
work/decompose-sessions-node
Jul 30, 2026
Merged

refactor(sessions): decompose the sessions node into eight children#496
simonheimlicher merged 12 commits into
mainfrom
work/decompose-sessions-node

Conversation

@simonheimlicher

@simonheimlicher simonheimlicher commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Decompose the sessions node from 60 assertions on one spec into eight children, each owning one concern with its evidence co-located.
  • Preserve assertion semantics exactly: 60 before, 60 after, every assertion moved byte-identically.

Background

The node carried 60 assertions against the roughly-seven signal that decomposing.md treats as requiring decomposition analysis. The changeset reviewer raised it as architecture debt on PR 494.

The concerns it held are independently governable and were tangled: the session store and its command contract, the closing flow, per-thread continuation disposition, document shape, the operator closeout, claim reconciliation, and the resumption flow.

Changes

New children under spx/21-spec-tree.enabler/76-sessions.enabler:

  • 15-session-store.enabler (16) — the .spx/sessions/ store and spx session command contract, with test_sessions.scenario.l1.py
  • 25-handoff.enabler (1) — the occupancy-preservation rule spanning its children, with 20-closure.enabler (9), 40-continuation-disposition.enabler (10), 60-session-document.enabler (5), 80-closeout-report.enabler (2)
  • 28-pickup.enabler (1) — the checkout-currency precondition, with 30-claim-verification.enabler (8) carrying both pickup verification tests, and 60-resumption.enabler (5)

The parent keeps the three compaction rules, which are cross-cutting rather than owned by any child.

The claim-verification decision moved from index 65 at the parent to index 20 inside the pickup node. At 65 it constrained neither pickup child, because a decision reaches only siblings above it; at 20 it constrains both.

Repaired what the move invalidated: the [eval] link re-rooted for its new depth, the eval owned_paths gains the closure node, both decision records repointed to the session-document child, and the ancestor plan worklist repointed to closure.md.

Test plan

  • just check passes all 27 selected steps
  • The 25 relocated tests collect and pass at their new paths
  • Assertion count across the node and descendants is 60, matching the node before the split

Refs

  • spx/21-spec-tree.enabler/76-sessions.enabler

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

@claude

claude Bot commented Jul 29, 2026

Copy link
Copy Markdown

BLOCKING [consistency]: spx/21-spec-tree.enabler/76-sessions.enabler/15-session-store.enabler/session-store.md:11

Reference: PR body: "Preserve assertion semantics exactly: 60 before, 60 after, every assertion moved byte-identically." Governing rule at spx/21-spec-tree.enabler/54-refactoring.enabler/refactoring.md line 15: "NEVER: change assertion semantics during a refactoring operation, refactoring moves structure, not meaning."
Evidence: the first Scenario in sessions.md (base) read: "Given continuation by the agent is impossible because the user halted the work, context is exhausted, or an external blocker prevents the next action, when /handoff runs without --no-session, then a session document is created in .spx/sessions/todo/ with the current tree state and active node path". The moved assertion at 15-session-store.enabler/session-store.md line 11 now reads: "Given a session payload naming the current tree state and an active node path, when spx session handoff runs, then a session document carrying that node path is created in .spx/sessions/todo/". The subject changed from the /handoff skills closing decision, gated on "continuation impossible" and "without --no-session", to the bare spx session handoff CLI contract, and the gating condition was dropped entirely: this is a meaning change, not a relocation. The other 10 Scenarios, both Mappings, and all 44 other Compliance assertions verified byte-identical against the base at their new locations; this is the single exception. It is also the same class of defect the changeset review already caught twice in this PRs own ISSUES.md items 2 and 3, a dropped assertion and a repointed-but-non-matching citation, both explicitly deferred as out of scope for a decomposition; this one was not caught or deferred.
Required: either restore the assertions original wording verbatim in its new location, or, if the narrower CLI-only claim is intentional, treat it as a content change owed its own spec-audit justification, per the pattern already applied to ISSUES.md items 2 and 3, rather than folding it silently into this decomposition, and correct the PR descriptions "byte-identically" claim accordingly.

@claude

claude Bot commented Jul 29, 2026

Copy link
Copy Markdown

BLOCKING [consistency]: .github/workflows/spec-tree-evals.yml:104

Reference: CLAUDE.md, Generated Sources in Verification Scope, states that spx/local/generated-sources.toml declares the BEGIN eval-trigger-paths / END eval-trigger-paths blocks in .github/workflows/spec-tree-evals.yml as a generated relation sourced from spx//evals//eval.toml and regenerated by just build-eval-triggers. CLAUDE.md instructs: Do not hand-edit the marker-delimited paths blocks in .github/workflows/spec-tree-evals.yml; the gates eval-triggers step fails on drift.
Evidence: The diff edits .github/workflows/spec-tree-evals.yml at lines 104 and 187 to add spx/21-spec-tree.enabler/76-sessions.enabler/25-handoff.enabler/20-closure.enabler/closure.md to two paths lists. Both edited lines fall inside the BEGIN eval-trigger-paths, line 55, END eval-trigger-paths, line 131 region, and the second BEGIN, line 138, END, line 214 region. The corresponding owned_paths entry was correctly added to spx/21-spec-tree.enabler/76-merging.enabler/evals/local-completion-boundary/eval.toml, but the workflow file was then hand-edited to mirror that change instead of being produced by the generator.
Required: Revert the manual edits inside the generated blocks in .github/workflows/spec-tree-evals.yml and run just build-eval-triggers to regenerate them from the updated eval.toml, then commit the regenerated output so the eval-triggers drift check cannot fail and the generated versus authored boundary is preserved.

No other findings survived review. The 60 assertion count was verified line by line against the diffs removed and added hunks and totals exactly 60 both before and after the split across all nine children plus the three retained in sessions.md. Every test and eval relative path link was re-rooted correctly for its new directory depth. The renamed decision record 65-pickup-claim-verification.adr.md and all citations to it were checked repo wide with no dangling references remaining. The single eval referencing 76-sessions.enabler/sessions.md is local-completion-boundary, and its owned_paths and trigger entries were updated consistently everywhere that reference appears.

Move the session store and the spx session command contract into
15-session-store.enabler, and claim reconciliation into
28-pickup.enabler/30-claim-verification.enabler, each with its
co-located tests. Both sit below 32-session-skill-invocation.enabler
because that node declares the invocation surface for flags whose
behavior these concerns define.

24 of the node's 60 assertions move; 36 remain on the parent covering
handoff and pickup resumption, with the compaction rules staying as
cross-cutting. The count across the node and its descendants is
unchanged.

PLAN.md records the reserved indices, the ordering-evidence matrix, and
the destination of every remaining assertion, so the rest of the
composition is mechanical.

Refs: spx/21-spec-tree.enabler/76-sessions.enabler
Narrow the session-store kind statement to the todo/doing transitions it
actually specifies, and restate its first scenario as the CLI behavior
its linked test establishes rather than the /handoff trigger judgment
the test never exercises.

Delete the foundation-marker exemption from session-store. The same rule
already stands at spx/21-spec-tree.enabler/18-context-loading.enabler
over a wider command set, so restating it here was duplication rather
than a second concern. The node total falls 60 to 59 for that reason.

Give 28-pickup.enabler the checkout-currency rule as its own assertion.
It constrains claim reconciliation and resumption alike, and an enabler
whose Assertions section carries no claim-shape heading is not valid
structure.

Refs: spx/21-spec-tree.enabler/76-sessions.enabler
ISSUES.md gave 36 where the node holds 35, and credited the two new
children with 24 assertions where they carry 23 before the separately
named currency rule.

The currency assertion cited the claim-verification decision, which
explains why reconciliation is needed but mandates no checkout
operation. The handoff-persistence decision is what requires /pickup to
fetch and check out the ref before reading.

Counts surfaced by the changeset review, the citation by the spec audit.

Refs: spx/21-spec-tree.enabler/76-sessions.enabler
Add the "Required by" column decomposing.md mandates, naming for each
edge the successor assertion or workflow step that depends on the
predecessor's contribution, and add the row for the handoff-persistence
decision that constrains the session store.

Replace the disposition sentence that called 15-session-store.enabler
and 21-compact-continuity.pdr.md "independent peers at the same index"
while assigning them 15 and 21. The checkpoint now states every sibling
pair under the node, including the two decision records it never
examined, and says plainly when a different-index pair is sound without
an ordering edge.

Both surfaced by the changeset review.

Refs: spx/21-spec-tree.enabler/76-sessions.enabler
Create 25-handoff.enabler with closure, continuation-disposition,
session-document, and closeout-report children, and
28-pickup.enabler/60-resumption.enabler. The 32 assertions move
verbatim; the parent keeps only the three compaction rules, which are
cross-cutting rather than owned by any child.

Repair what the move invalidated: the [eval] link is re-rooted for its
new depth and the eval's owned_paths gains the closure node so a change
there still fires it; both decision records now cite the
session-document child for the pointer contract that left sessions.md;
and the ancestor plan's EXCLUDE worklist entry points at closure.md.

The subtree total stays at 59. PLAN.md reduces to the one open
question, decision placement; the composition issue is resolved and its
ISSUES.md entry removed.

Refs: spx/21-spec-tree.enabler/76-sessions.enabler
The occupancy-preservation rule spans document creation, artifact
archiving, and branch release, so it belongs to the handoff concern as a
whole rather than to closure alone. The payload command form governs the
invocation that writes the document, so it moves to the session-document
child.

Refs: spx/21-spec-tree.enabler/76-sessions.enabler
The composition deleted this node's SPEC_TREE_FOUNDATION exemption for
spx session commands, because
spx/21-spec-tree.enabler/18-context-loading.enabler states the same rule
over a wider command set and a spec audit rejected the restatement as
duplication.

Nothing in the tree recorded that, so a reader comparing the node before
and after the split sees a rule vanish. The node now names where the
rule lives and that no descendant restates it.

Surfaced by the changeset review as a dropped assertion.

Refs: spx/21-spec-tree.enabler/76-sessions.enabler
Restore the foundation-marker exemption to 15-session-store.enabler. It
duplicates spx/21-spec-tree.enabler/18-context-loading.enabler, which a
spec audit correctly identified, but deleting a declaration is a content
change and refactoring.md forbids changing assertion semantics during
tree surgery. ISSUES.md entry 2 carries the removal as its own change.
The subtree total returns to 60, matching the node before the split.

Move the claim-verification decision from index 65 at this node to index
20 inside 28-pickup.enabler. A decision reaches only siblings above it,
so from 65 it constrained neither 30-claim-verification.enabler nor
60-resumption.enabler; from 20 it constrains both. PLAN.md stated the
opposite and now states the reach each decision actually has.

Both surfaced by the changeset review.

Refs: spx/21-spec-tree.enabler/76-sessions.enabler
Restore the ordering-evidence matrix, extended to cover the handoff and
pickup sub-children and the three decision records. decomposing.md
requires the matrix recorded before index assignment, and it is the
durable justification for the tree's shape rather than scaffolding the
composition consumes.

Disposition the four children that sit above the roughly-seven signal.
Each carries one validation boundary stated across its cases, so the
count is accepted here with the condition that would reopen it, matching
how other nodes in this tree dispose of the same signal.

Both surfaced by the changeset review.

Refs: spx/21-spec-tree.enabler/76-sessions.enabler
The checkout-currency assertion cited the claim-verification decision
before the move, so a pure relocation cites that decision at its renamed
path. Repointing it to the handoff-persistence decision was more
accurate but changed the assertion's meaning inside tree surgery, which
refactoring.md forbids and which this changeset already reverted once
for a deleted assertion.

ISSUES.md entry 3 carries the correction, with the evidence that the
cited decision states no currency rule and the one that does.

Refs: spx/21-spec-tree.enabler/76-sessions.enabler
The first session-store Scenario was narrowed to the CLI behavior its
linked test establishes, dropping the /handoff trigger conditions from
its Given clause. The observation behind that is sound — the test drives
the CLI directly and exercises neither condition — but narrowing an
assertion's subject is a meaning change, and this changeset is tree
surgery.

With this restored, every one of the 60 assertions is byte-identical to
its form before the split, apart from one citation that follows its
target's rename, which refactoring.md requires of a move.

ISSUES.md entry 4 carries the correction and the evidence for it.

Refs: spx/21-spec-tree.enabler/76-sessions.enabler
The ordering-evidence matrix and decision-placement notes addressed
siblings by bare name, which is ambiguous because numeric prefixes are
sibling-local: index 20 holds both a closure node and a claim-verification
decision under different parents in this node.

Refs: spx/21-spec-tree.enabler/76-sessions.enabler
@simonheimlicher
simonheimlicher force-pushed the work/decompose-sessions-node branch from 2e7241b to fbaec52 Compare July 30, 2026 17:28
@sonarqubecloud

Copy link
Copy Markdown

@claude

claude Bot commented Jul 30, 2026

Copy link
Copy Markdown

No findings.
Verified: assertion counts sum to 60 in both the old sessions.md (11 Scenarios + 2 Mappings + 47 Compliance) and across the 9 new/retained nodes (session-store 16, handoff.md 1, closure 9, continuation-disposition 10, session-document 5, closeout-report 2, pickup.md 1, claim-verification 8, resumption 5, plus 3 compaction assertions retained in sessions.md); spot-checked several assertions (including the long closeout-report ALWAYS clause) for byte-identical text across the move.
Citation repoints in 13-handoff-persistence.adr.md and the renamed 28-pickup.enabler/20-claim-verification.adr.md (from sessions.md to 60-session-document.enabler/session-document.md) match the relocated assertion text. The eval link in closure.md re-roots correctly for its new depth (../../../76-merge.enabler/evals/... vs the old ../76-merge.enabler/evals/...), and both the eval owned_paths list and the CI workflow trigger paths gained the new closure.md path.
Renamed test files resolve via package-absolute imports (outcomeeng_testing.), so the directory moves do not break collection, and the old 76-sessions.enabler/tests/ directory is fully emptied and removed. Confirmed no repo-wide references to the old paths (65-pickup-claim-verification.adr.md, old tests/test_sessions.scenario.l1.py, old tests/test_pickup_verification.) remain.
The three newly-filed ISSUES.md entries (duplicate foundation-marker exemption, mis-cited checkout-currency ADR, overclaiming Scenario) are pre-existing defects carried forward unchanged rather than fixed in-place, which is correct under spx/21-spec-tree.enabler/54-refactoring.enabler/refactoring.md:15 (NEVER: change assertion semantics during a refactoring operation - refactoring moves structure, not meaning).

@simonheimlicher
simonheimlicher merged commit 9159a6f into main Jul 30, 2026
6 checks passed
@simonheimlicher
simonheimlicher deleted the work/decompose-sessions-node branch July 30, 2026 17:40
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