SPEC-148
ID:SPEC-148Status:draft

Places plugin composition audit

claude/v0-37-0-review-vqpl41 View source
Branches 2
claude/v0-37-0-review-vqpl41 current draft
claude/nifty-ptolemy-80epot draftmain draft
History 9
  1. 1580a8d
    Created (draft)by bjornolofandersson
  2. d3dfd24
    Content editedby Claude
    docs(plan): ADR-039 + SPEC-157 — where a rune lives, with all nine audit
  3. 239e899
    Content editedby Claude
    docs(plan): SPEC-155 — media audit; playlist rebuilds where it could pro
  4. bd2cf7e
    Content editedby Claude
    docs(plan): SPEC-154 — learning audit, where the question becomes "build
  5. ee8bb0c
    Content editedby Claude
    docs(plan): SPEC-152 — plan audit; dedupe inside a plugin that stays
  6. e13ce96
    Content editedby Claude
    docs(plan): SPEC-151 — marketing audit, and the number that justifies SP
  7. 68a16b3
    Content editedby Claude
    docs(plan): SPEC-150 — design audit; the first plugin that stays a plugi
  8. 5e2e184
    Content editedby Claude
    docs(plan): SPEC-149 — business audit; path B splits, and the first plug
  9. fc5bf1a
    Content editedby Claude
    docs(plan): SPEC-148 — places audit, per-property, plus the delimiter co

Summary

The second plugin audited against SPEC-145, after SPEC-147's storytelling. Places is 960 lines across three runes and three children, and the result differs from storytelling's in a way worth having in writing: it does not retire, it splits. Two runes compose; one never can, because it is not a domain rune at all.

It also establishes the per-property audit method, because per-rune verdicts proved unstable — twice in the course of this audit a rune was called composable or blocked on the strength of one property, when its other properties took different paths.

Why per-property, not per-rune

A schema row's source resolves through one of five paths, and only one of them is broken by composition (SPEC-146, Problem 2). Which path a property takes is a property of that property, not of its rune — so a rune with four properties can be three parts fine and one part broken, and a per-rune verdict is a coin toss on which property you happened to look at first.

PathResolverAcross a composition boundary
A — properties from an attributestamp → bag fallback (schema-table.ts:309-312)works
B — properties from a nodefindAllByName (:235)fails silently
C — textfindByName, same walkfails silently
D — entitiesbuildEntity — node first, then bag (:384-395)node source fails, attribute source works
E — childrenfindChildren (:528)works, and over-matches (SPEC-146 Problem 1)

event is the clearest demonstration in the codebase, and its own source comment says so: "location … exists only as an attribute value, so the applier rebuilds a carrier from the field bag rather than stamping a node. headline / blurb come from pageSectionProperties and survive as refs, so those stamp in place. One table, three of the mechanism's resolution paths."

The audit

event — three paths in one table, one of them blocked

Propertyschema.orgSourcePathComposed
headlinenameref from pageSectionPropertiesBlost
blurbdescriptionref from pageSectionPropertiesBlost
datestartDateattribute → properties metaA✓
endDateendDateattribute → properties metaA✓
urlurlattribute → properties metaA✓
locationnested Place.nameattribute → entitiesD (attribute)✓

Measured against today's applier, the same table over a declared and a composed tree:

// declared
{ "@type": "Event", "name": "Tech Conference 2025", "description": "Three days of talks.",
  "startDate": "2025-06-15", "endDate": "2025-06-17", "url": "https://example.com/register",
  "location": { "@type": "Place", "name": "San Francisco, CA" } }

// composed — the header placed inside a {% card %}
{ "@type": "Event", "startDate": "2025-06-15", "endDate": "2025-06-17",
  "url": "https://example.com/register",
  "location": { "@type": "Place", "name": "San Francisco, CA" } }