About the workflow_states index#

A site whose content participates in several workflows has to be able to find that content by any of their states. This page explains the shape chosen for that, and the consequence it has for queries written before the package existed.

One index, not one per workflow#

The obvious design gives each additional workflow its own index, named after its state variable. This package uses a single KeywordIndex describing the whole chain instead.

The reason is growth. Behaviors are added over a site's lifetime, and an index-per-workflow design means the catalog grows a new index every time someone enables one—each needing a profile step, an upgrade step, and a reindex. One index for the chain means a site's catalog schema stops changing after the add-on is installed, whatever behaviors arrive later.

The cost is that values must carry their workflow with them, hence <workflow-id>|<state-id>. A KeywordIndex holds several values per object naturally, so an object in three workflows is three entries and any of them can be queried.

Why chain order is part of the contract#

The index holds its values in chain order, and the first value is always the workflow driving review_state, for every object, whether or not anything was contributed to it.

The indexer is registered for all content rather than for participating content alone, which is what makes that guarantee hold site-wide. A caller can therefore read entry zero without first asking whether the object participates, and code that wants "the publication state" has one rule rather than two.

Why one of the three maintenance paths is a patch#

An index is only as good as whatever keeps it current, and an object's state can change in three ways. Two of them are events: a transition, which Plone already reindexes for, and a transition on a workflow with a bespoke state variable, which this package's own subscriber covers.

The third is import, and it is not an event at all. plone.exportimport restores an object's state by assigning workflow_history directly, which is the correct thing to do (a real transition would rewrite role mappings and invent history) and which fires nothing. So neither of the first two mechanisms runs, and the imported object keeps the catalog entry it was created with.

There is no subscriber to register for that, because nothing is notified. The only place to act is inside the importer, which is why this package patches plone.exportimport rather than adding a third listener. The patch is deliberately generic: it reindexes whatever the object's workflow variables are named, so it is the change this package would like to see upstream, not a special case for one index.

What makes the bug worth this trouble is the shape of it. review_state is imported by a real transition, so the publication half of the index is always right, and imported content looks healthy under exactly the spot-check anyone would perform. Only the additional workflow is stale, and only in the catalog: the object itself is correct, so a debugging session that reads the object rather than querying for it finds nothing wrong.

Why review_state queries are rewritten#

A site installing this add-on already has collections, saved searches, and code querying review_state with values like published.

Those values no longer match anything, because the states now live in a differently named index under qualified values. Left alone, such a query returns nothing—and an empty result reads as no content is in that state, not as this query can no longer work. That is the worst available failure: silent, plausible, and indistinguishable from a true answer.

So a parsed review_state query is redirected onto workflow_states, and a bare state id is qualified with the site's default workflow. The rewriting is deliberately conservative: it renames the index, qualifies only string values, and passes every other key of the parsed query through untouched, because those keys carry the operator of an all of criterion and the negation of an excludes one.

The result is that an existing query keeps meaning what its author meant, and a new one can name any workflow it likes.

See also

Catalog index for the exact rules, and How to search by an additional workflow state for how to write a query.