Skip to content

Domain: lifecycle states and the session's result line - #32

Merged
tomkaczocha merged 7 commits into
mainfrom
t3/factory-issue-10
Oct 15, 2026
Merged

tomkaczocha merged 7 commits into
mainfrom
t3/factory-issue-10

Conversation

@rawphp

@rawphp rawphp commented Oct 15, 2026 •

Copy link
Copy Markdown

Closes #10

Contract

  • done: apps/server/src/factory/domain/lifecycle.ts is a pure (state, event) → { next, actions } over the seven factory:* labels, with softfac's table (same action order) plus one deliberate addition, needs-info + approved → ready, which follows softfac's documented lifecycle where its table has no exit (17 valid pairs), labels, colours, descriptions and event names; an invalid pair returns a typed InvalidTransition failure with softfac's message. apps/server/src/factory/domain/resultLine.ts reads the last line of a session message as done / needs-info / stuck with softfac's exact reasons, marker rules and pull request URL rules. Both test files are ported from src/lifecycle.rs and src/runner/tests.rs at rawphp/softfac@bf273a1.
  • out: ledger, approval, limits, fairness, runner wiring, the wrong-account check (runner-level, not the parser).
  • risk: none
  • test: vp test run apps/server/src/factory → pass (5 files, 53 tests)

Acceptance criteria

  • lifecycle.ts is a pure function with softfac's table; tests ported — lifecycle.test.ts (27 cases, one per Rust test, incl. exactly the table pairs succeed, set label always matches next state, assignment follows the state entered)
  • resultLine.ts reads FACTORY-DONE: <url>, FACTORY-STOP: needs-info, FACTORY-STOP: stuck or none with softfac's rules; tests ported — resultLine.test.ts (13 ported cases plus helper and Unicode cases)
  • Neither file imports outside apps/server/src/factory/domain/ — both files have no imports at all; boundary.test.ts passes

Gate

  • vp test run apps/server/src/factory → exit 0
  • vp run --filter t3 typecheck → exit 0

Models

  • maker: claude-sonnet-5-5 (host)
  • checker: claude-opus-5-5 (host)

Review

  • cycles: 2 (correctness: approve, 2 nits; testing: 12 mutants, 11 killed, 1 survived → fixed)
  • fixed: pinned the Rust White_Space rules (U+0085 stripped, U+FEFF kept); reworded the 2^53 comment as a deliberate, unreachable divergence from u64; added note FACTORY-DONE: …, FACTORY-DONE: …, > FACTORY-DONE: … to the whole-last-line list so a non-anchored marker match fails
  • leftovers: none

Notes

  • Worktree was fast-forwarded from e4bcce9f6 to origin/main (f1dba9a17) at Isolate; it had been cut before Factory skeleton: an empty FactoryLayer wired in through one seam #1 landed.
  • softfac's docs/lifecycle.md is not in this repo, so the description test pins the seven label-table rows as a literal instead of reading the doc.
  • Numbers past 2^53 are rejected by the port and accepted by Rust's u64; no GitHub pull request number reaches that. Repo comparison lowercases ASCII only, as Rust eq_ignore_ascii_case does.
  • No seam touched: only new files under apps/server/src/factory/domain/.
  • No labels created: all seven already existed on the repo.

Lessons

  • A ported test list inherits the reference's gaps; mutate the anchor of every marker. Symptom: softfac's whole-last-line list prefixes only FACTORY-STOP, so includes instead of startsWith on FACTORY-DONE survived every ported test. Cause: a port copies the spec's cases and the spec's blind spots with them. Default: when naming mutants for a port, add one anchor mutant per marker or prefix the code checks, even where the reference has no case.

Commits

  • 9463426d0 test(factory): port lifecycle and result-line tests from softfac — Make slice (red)
  • f0a605da0 feat(factory): lifecycle transitions and result-line parsing in the pure domain — Make slice (green)
  • 0768fc424 test(factory): pin the Unicode whitespace rules of the result line — Check cycle 1
  • 2ff87a206 test(factory): text before FACTORY-DONE on the last line is no result line — Check cycle 2

Files

  • apps/server/src/factory/domain/lifecycle.ts — states, labels, colours, descriptions, events, actions, transition
  • apps/server/src/factory/domain/lifecycle.test.ts — ported from src/lifecycle.rs tests
  • apps/server/src/factory/domain/resultLine.ts — splitLastLine, pullRequestUrl, parseSessionResult, reason constants
  • apps/server/src/factory/domain/resultLine.test.ts — ported from src/runner/tests.rs parser tests

Model: Claude Fable 5.1 orchestrating, Sonnet 5.5 maker, Opus 5.5 reviewers. Harness: Claude Code in T3 Code.

Issue: #10

Both suites fail: ./lifecycle.ts and ./resultLine.ts do not exist yet.
Issue: #10

U+0085 is stripped and U+FEFF kept, as Rust's White_Space set says and
JS trim does not. The comment on the 2^53 case now calls the divergence
from u64 what it is: deliberate and unreachable.
… line

Issue: #10

Pins the marker to the start of the line: a non-anchored match passed
every ported test, because softfac's list only prefixes FACTORY-STOP.
@rawphp
rawphp marked this pull request as ready for review October 15, 2026 10:54
@rawphp

rawphp commented Oct 15, 2026

Copy link
Copy Markdown
Author

Factory gate: passed (attempt 1, run by the factory in a fresh checkout of the pull request's head 2ff87a2 merged with the base branch tip f1dba9a)

Command Exit Time
vp test run apps/server/src/factory 0 1.0 s
vp run --filter t3 typecheck 0 4.5 s

…ker exports

A whitespace run followed by other text made the `[...]+$` trim backtrack
quadratically on untrusted session output (200k spaces ran past a minute).
Trim with index loops over the same Rust White_Space set instead.

DONE_MARKER, NEEDS_INFO_LINE and STUCK_LINE had no importer, which fails
knip:check; they are module-private until a caller needs them.
@rawphp

rawphp commented Oct 15, 2026

Copy link
Copy Markdown
Author

Factory gate: passed again (attempt 2, run by the factory in a fresh checkout of the pull request's head 2ff87a2 merged with the base branch tip b679ddd)

Command Exit Time
factory/scripts/check.sh 0 11.2 s

transition threw a plain Error, so an Effect caller saw a stale or duplicate
webhook's event as an untyped defect. It now returns a Result whose failure is
a tagged InvalidTransition, as softfac's transition returns Result.
No event left needs-info, so setting factory:ready on an answered issue was an
invalid transition. approved now moves needs-info to ready with no actions, as
for proposed and stuck. softfac's table lacks this pair while its lifecycle doc
promises it; this follows the doc.
@tomkaczocha
tomkaczocha merged commit cb35ead into main Oct 15, 2026
@rawphp
rawphp deleted the t3/factory-issue-10 branch October 28, 2026 02:53
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.

Domain: lifecycle states and the session's result line

2 participants