BOHICA-LABS / BOHICA-LABS/vsdd-factory

process-gap(per-story-delivery + state-manager): parallel/interleaved delivery leaves factory bookkeeping unreconciled — STATE.md 'next story' pointer sends the next session to re-deliver an already-merged story

Open
#692 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
2
Forks
1
Avg merge
6h 43m
Merged PRs (30d)
29

Description

## Summary

When two per-story deliveries interleave across sessions, the story delivered by the *earlier* session can land its code + PR merge to `develop` while its factory bookkeeping (`.factory/` — story frontmatter `status`, `STORY-INDEX` row, `STATE.md` narrative) is never reconciled. A later session that trusts `STATE.md`'s "next unblocked leaf" pointer then selects a story that is **already merged**, and — absent a disk-verify-first discipline — nearly re-delivers it from scratch.

## Observed instance

- Session A delivered `S-BL.NODE-ADMISSION-PROVISIONING` via a squash-merge PR to `develop` (merge commit on the protected branch), then ended without running the terminal state reconciliation (`sm-LAST`). The story file stayed `status: draft`, its `STORY-INDEX` row stayed `draft (v1.0)`, and no delivery record was written.
- Session B (mine) had earlier merged a *different* story (`S-BL.ADMISSION-SYNC-WIRE`), and its `sm-LAST` wrote `STATE.md` from a view that pre-dated Session A's merge — so `STATE.md` said the just-merged story was "the next unblocked leaf … ready for dispatch," and the resume protocol pointed straight at it.
- Session B's local `develop` was behind `origin/develop` by 2 commits, so a naive worktree would not even have contained the delivered code — the "story looks undelivered" illusion was total (draft status + draft index row + STATE pointer + stale local tree all agreeing).
- Caught only because the operator ran `git log origin/develop` **before** dispatching any implementation agent, saw the story's own delivery commit already on the protected branch, and stopped. Re-implementation was averted, but nothing in the engine flagged the desync.

## Why this is an engine gap, not a one-off

- `STATE.md`'s next-story pointer is authored by whichever session ran `sm-LAST` last; it is not derived from ground truth (git history + merged PRs). A stale pointer is silently authoritative.
- The per-story cycle has no **post-selection guard** that asks "does the story I'm about to deliver already have a merge commit on the base branch?" — the one check that would have caught this deterministically.
- Reconciliation (`status: delivered`, STORY-INDEX row, delivery record) is a trailing step owned by `sm-LAST`; when a session ends before it (crash, context exhaustion, handoff), the delivered story is indistinguishable from an undelivered one by the artifacts the *next* session reads.

## Suggested shapes (pick per engine philosophy)

1. **Delivery-vs-history reconciliation gate at story selection:** before Red Gate, cross-check the candidate story ID against merged PRs / commit messages on the base branch; if a merge for that story ID exists, treat the story as delivered-but-unreconciled and route to a reconcile step instead of a delivery.
2. **STATE.md next-pointer as derived, not authored:** compute "next unblocked leaf" from the dependency DAG + delivered-set (merged PRs) at read time, rather than trusting a hand-written pointer that can lag a parallel merge.
3. **Idempotent reconciliation entrypoint:** a `reconcile-delivered ` operation the orchestrator can run when it detects the desync, that back-fills `status: delivered` + STORY-INDEX row + delivery record from the merge commit — so a dropped `sm-LAST` is recoverable without re-delivery.
4. **`sm-LAST` push-before-end enforcement:** the terminal reconciliation + push should be a hard session-close obligation with a visible signal when skipped (the earlier session simply ended without it).

## Impact

Silent near-duplication of an entire per-story delivery (worktree, Red Gate, TDD, adversarial convergence, PR) against a story that is already on `develop`. In a single-checkout single-writer setup this is unlikely; in the multi-session / parallel-lane usage the methodology explicitly supports, the "next story" pointer is a shared-mutable field with no reconciliation guard. Only an operator's manual `git log` habit stood between the desync and wasted re-delivery.

Contributor guide

Open the contributing guide

Research direction

The issue describes a race condition in a story delivery system. Start by examining the STATE.md file and the story selection logic, likely in the orchestrator or state manager. Look for the 'next unblocked leaf' pointer and the Red Gate step. Check how the system currently reconciles delivered stories via sm-LAST. The fix involves adding a guard to cross-check a candidate story ID against merged PRs on the base branch (e.g., via git log) before delivery. Understanding the dependency DAG and the existing reconciliation artifacts (.factory/ directory, STORY-INDEX) is key. Done looks like a new check that prevents re-delivery of an already-merged story.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
backend-api-design, devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.