[Blog](https://featkpr.com/blog) / 30 Sep 2026

# Proving that one feature depends on another

A link between two features is a guess from code until a run shows it. Paired runs proved 27 of BookStack's links; the last one found a bug.

Tim Derzhavets · 30 Sep 2026

Two real pairs from the paired run on BookStack, both proven. A step is a filled node; the box is what the leg must end on. A pair counts as proven only when every leg ends as drawn.

**27** of 28 links between BookStack's goals proven by a run; 0 refuted BookStack at 1633b3ab · the paired run of 29 Sep 2026, run-scripts lane 36580253632, folded in production

## A map says how features lead into each other

featkpr maps a web app’s features, the flows people take through them and the goals they reach. Goals do not stand alone. In BookStack you cannot turn a book into a shelf until the book exists, and once an API token is deleted nobody can update it. On the map these are links between goals: creating a book enables turning it into a shelf; deleting a token disables updating it.

featkpr reads such links from the code, from which record a goal creates and which one another goal needs. A link read from the code is still a guess. The code can say a book is needed and still let the request through without one, or refuse for a reason that has nothing to do with the book.

## So each link gets a paired run

A paired run takes the two goals of one link, call them a and b, and runs their tests on one fresh copy of BookStack. The figure at the top shows the legs, one lane per kind:

- **enables** takes two legs. First b alone, on a fresh copy where nothing a makes exists: b must be refused. Then a, and b on the record a just made: b must pass. Only both together show that a is what b needs.
- **disables** takes one leg. First a, then b on the record a acted on: b must be refused, and b’s step must change nothing.

A pair is proven when every leg ends as drawn, and refuted when a leg ends the other way, for example when b passes without a. If a leg ends on something neither rule expects, such as a server error, the pair stays unproven. A crash is not a refusal, so it proves nothing either way.

## What happened on BookStack

At commit 1633b3ab, featkpr’s map of BookStack held 229 links of the two kinds a pair can run. 28 of them had a runnable test at both ends. The run of 29 Sep 2026 sent 39 legs in 167 seconds, about 7 minutes of the homelab lane with its setup. 27 pairs were proven, 0 refuted and 1 left unproven.

the 28 pairs that could run

- **27** proven
- **0** refuted
- **1** unproven: a BookStack bug stopped it

the 201 links still waiting for a test of one of their goals

BookStack at 1633b3ab · the paired run of 29 Sep 2026, run-scripts lane 36580253632, folded in production · one pin per link

The proven pairs are small, plain facts: creating a user enables updating that user, deleting a role disables opening it, creating a book enables turning it into a shelf. Each one now carries the runs that show it, where before it carried only a line of code.

## The one left unproven is a BookStack bug

The last pair was “Delete the revision” disables “Restore the page”. After a page revision is deleted, restoring it should be refused as not found, the way BookStack already answers for showing, comparing or deleting that revision. BookStack answered with a server error instead.

after a deletes revision id , b sends

`PUT /books/{book}/page/{page}/revisions/{id}/restore`

must answer

404 Not Found

as showing, comparing or deleting that revision already do

answered

500 Server Error

BookStack v26.09.1, reproduced by hand

Reported upstream as BookStack issue #6233 on 30 Sep 2026 · [codeberg.org/bookstack/bookstack/issues/6233](https://codeberg.org/bookstack/bookstack/issues/6233)

We reproduced it by hand on BookStack v26.09.1 and reported it upstream as [issue #6233](https://codeberg.org/bookstack/bookstack/issues/6233) . The link may well hold once the error is fixed, but until BookStack refuses the request cleanly the run cannot say, so the pair stays unproven rather than refuted.

## What comes next

201 links still wait. One of their goals has no runnable test yet, mostly deleting a book, a page or a chapter. Once those tests exist the same plan runs them; no model writes the missing tests for the pairs. After that come the other kinds of link, such as leads to and undoes. Both are on [the roadmap](https://featkpr.com/roadmap#more-pair-kinds) .

In the app, a link a paired run proved is drawn solid where the goals are compared, and a link read from the code stays dashed. Production shows them now: after the re-seed of 30 Sep it holds 731 links between BookStack’s goals, and 22 goals carry a paired-run proof on the Ledger. The other links are still read from the code.

**Compare, in featkpr's app** · on a recorded world (goals-bookstack), not BookStack's production records

This post was drafted with AI assistance and checked by Tim. Every number in it is filled in from featkpr’s records when the site is built; [how we write and check](https://featkpr.com/about/how-we-write) says how, and how to report an error.

### What this shows

- 27 links between BookStack's goals, each shown by a run on a fresh copy, not only read from the code.
- The legs each kind of link needs, and the rule that a pair stopped by something else stays unproven.
- One BookStack bug found on the way, reproduced by hand and reported upstream.

### What it doesn't

- The other 201 links: one of their goals has no runnable test yet.
- Kinds of link beyond enables and disables, such as leads to or undoes.

written by

[Tim Derzhavets](https://featkpr.com/about)

30 Sep 2026 · BookStack

figures from

BookStack at 1633b3ab · the paired run of 29 Sep 2026, run-scripts lane 36580253632, folded in production

next

[See it live on BookStack](https://featkpr.com/demo)

## See how BookStack's features lead into each other

Half an hour, live: the map, the goals and a paired run, your questions first. Your own app comes after.

[Ask for a private demo](https://featkpr.com/demo)

---

The page this twin stands for: https://featkpr.com/blog/2026-09-30-proving-one-feature-depends-on-another. Every page on this site has a `.md` twin, and answers `Accept: text/markdown`.
