StatifierBlocks.Shelf (StatifierBlocks v0.12.0)

Copy Markdown View Source

The two placement facts io/1 cannot carry, and the identity every other surface asks about the shelf and the gap marker.

ADR-0002's amendment of 2026-08-31, section G12, states the two facts and campaign-024 ruling R-b puts their enforcement in the compiler's Structure stage; ADR-0004's amendment of the same date, section D3, names the two codes. This module owns both, and owns nothing else: kind admission, slot arity and the data-flow walk are unchanged and are still StatifierBlocks.Assignability's and StatifierBlocks.SlotValidation's.

Why this module is small on purpose

:draft_shelf does the placement work. Every slot in the shipped core.* vocabulary accepts [:step] or [:interrupt_handler], so a block declaring only that kind is refused by all of them through the intersection ADR-0003 decision 3 already performs, with no new rule, no per-type list, and nothing in this module. What is left over is exactly two things the intersection cannot express, and they are the only two rules here:

FindingFact
:drafts_block_misplaceda shelf is a direct child of the root block's body slot and nowhere else - a constraint on depth, which is a property of the document rather than of either block (G12a)
:duplicate_drafts_blocka document carries at most one shelf - cardinality across a document, closest in shape to ADR-0001 decision 3's document-unique ids and checked the same way (G12b)

Both refuse documents that would otherwise be admitted and neither changes the meaning of a document that is already valid, so relaxing either later is additive (G12c).

Why the type name and not the module

Every predicate here matches Block.type, the string ADR-0002 decision 1 puts in the document, rather than resolving a palette entry and comparing modules. The record says "a core.drafts block" throughout and core. is the reserved namespace decision 4 provides for, so the name is what is being talked about; a name test needs no palette, cannot disagree with one, and leaves the compiler and the editor with no dependency on a core type module. A host that wants its own shelf mints its own kind, its own container and its own structure rule, which ADR-0003's amendment of this date already records as the intended path.

The second block, not the first

:duplicate_drafts_block names the second and every later shelf in document order. The first is the one the author almost certainly means to keep, and a finding on it would ask them to fix the block that is not the problem - the same reasoning ADR-0001 decision 11f applies to a shadowed root, arriving at a different rule for the same reason (D3).

Summary

Types

A placement violation, in the tuple shape StatifierBlocks.Compiler adapts into a :structure finding. The first element is the code ADR-0004's amendment of 2026-08-31, section D3, names.

Functions

The type name a shelf is stored under.

Whether this block is a gap marker.

Whether this type name is the gap marker's.

The type name a gap marker is stored under.

Whether {parent_id, slot} is the one position a shelf is admitted in: the root block's body slot (G12a).

Whether this block is the shelf.

Whether this type name is the shelf's.

Every shelf in document, in Document.blocks/1 pre-order.

Every placement violation in document, in document order. :ok when there are none.

Types

finding()

@type finding() ::
  {:drafts_block_misplaced, StatifierBlocks.Block.id()}
  | {:duplicate_drafts_block, StatifierBlocks.Block.id()}

A placement violation, in the tuple shape StatifierBlocks.Compiler adapts into a :structure finding. The first element is the code ADR-0004's amendment of 2026-08-31, section D3, names.

Functions

drafts_type()

@spec drafts_type() :: StatifierBlocks.Block.type_name()

The type name a shelf is stored under.

marker?(block)

@spec marker?(StatifierBlocks.Block.t()) :: boolean()

Whether this block is a gap marker.

marker_type?(type)

@spec marker_type?(StatifierBlocks.Block.type_name()) :: boolean()

Whether this type name is the gap marker's.

placeholder_type()

@spec placeholder_type() :: StatifierBlocks.Block.type_name()

The type name a gap marker is stored under.

root_body?(document, parent_id, slot)

Whether {parent_id, slot} is the one position a shelf is admitted in: the root block's body slot (G12a).

This is the admission half of the depth rule, and it has to exist as well as the refusal half. Every container in the shipped vocabulary declares slot_accepts [:step] or [:interrupt_handler], the root included, so the ordinary intersection refuses a :draft_shelf everywhere - at the one position the record admits it as well as at every position it does not. G12a is what says the root's body admits one anyway, and StatifierBlocks.Assignability consults this to say so.

Reaching it through assignability rather than through a filter on the compiler's findings is deliberate: valid_targets/4 is the same decision, so an author dragging a shelf onto the root's body is offered the position for the same reason a shelf already there does not refuse. Two answers would be two chances to disagree.

shelf?(block)

@spec shelf?(StatifierBlocks.Block.t()) :: boolean()

Whether this block is the shelf.

shelf_type?(type)

@spec shelf_type?(StatifierBlocks.Block.type_name()) :: boolean()

Whether this type name is the shelf's.

shelves(document)

Every shelf in document, in Document.blocks/1 pre-order.

A document is well-formed with none and with exactly one; more than one is validate/1's business, not this function's.

validate(document)

@spec validate(StatifierBlocks.Document.t()) :: :ok | {:error, [finding()]}

Every placement violation in document, in document order. :ok when there are none.

The two rules are independent and both are reported: a second shelf that is also nested inside a group carries both codes, which is ADR-0004 decision 10's "within a stage every finding is reported" - the two are siblings rather than one being a consequence of the other.