Task
Give Surface Tension's discussion desk the instance provenance the design spec promises it, by actually reading /feed/comments and matching its entries to the episode's posts — or, if the answer is that the extra fetch is not worth it, trim spec §4.4's table so the show stops promising something it can never say. Today the code path exists and is never fed, so the desk can only ever report a bare count.
Context
st_write.board_facts(post, comment_entries) already accepts parsed /feed/comments entries and derives instance hosts and thread positions from them, and st_write.comments_for(post, entries) already matches entries to a post by title. scene_violations allows a named host only when it appears in those facts. But nothing in the weekly run ever supplies comment_entries: st_gather deliberately reads the post feeds and /feed/new only, and #176's SKILL.md procedure never fetches the comments feed.
The result is that the hosts list is always empty, so every named server in a switchboard line is refused as invented, and the desk's entire vocabulary is "somebody commented, and that is all I can tell you". That is honest — it is exactly what the guards are for — but it is a thinner scene than spec §4.4's table describes, and the "which instances called" row is currently aspirational rather than implemented.
Worth deciding deliberately rather than leaving as a silent gap: the desk is the show's most fragile scene, and volume-without-provenance is close to the minimum that justifies the seat existing at all.
Motivation
Pointers
skills/surface-tension/st_write.py — board_facts, comments_for, _permalink_host, _thread_position. All already written and unit-tested against the fixture; they need a caller.
skills/surface-tension/st_write.py — scene_violations, whose _HOSTISH_RE check is what currently refuses every named instance.
skills/surface-tension/st_gather.py:279 — feed_specs, and :662 gather, where a comments fetch would go if the answer is "gather it".
skills/surface-tension/st_gather.py:218 — DEFAULT_CONFIG. Note it has no comments_feed key; spec §4.1's example does. Adding one means updating tests/test_st_skill_md.py::test_skill_md_documents_every_config_key_and_no_phantom_ones, which asserts the Setup block and DEFAULT_CONFIG agree in both directions.
tests/data/bubbles_feed_comments.xml — the real capture, with both content shapes (a first comment, and an Nth carrying "Earlier comments").
tests/test_switchboard.py::test_a_host_the_comments_feed_confirms_is_allowed — already proves the whole chain works when entries are supplied.
docs/superpowers/specs/2026-08-24-surface-tension-design.md §2.3, §4.4 — the recon finding and the amended desk role.
Constraints
- No comment bodies, ever. The feed does not carry them and fetching a permalink to recover one is explicitly out of scope in spec §9 — it is a separate decision with consent and third-party-fetch implications. This issue is about hosts and counts only.
- Host, never handle. The permalink path contains the handle; only its host may be used. The existing guards enforce this and must not be relaxed.
- The count must stay fetch-free. Whether a switchboard turn renders at all is decided by
slash:comments on the post's own entry, with no second request. A comments-feed failure must degrade to "count only", never to a dead run or a skipped scene.
- One bad feed must not kill the run — the same posture as
gather_candidates, which logs, records a drop, and continues.
- Matching must stay tolerant. The comments entry title is
New comment on: <post title> (1st, 1 total); the candidate schema does not carry the bubbles entry id, so the title is the only key available. Do not silently drop a post whose title fails to match — log it.
Acceptance criteria
Out of scope
- Fetching Fediverse comment permalinks to recover comment bodies (spec §9). Permanently separate.
- Any use of a commenter's handle, display name, or position.
- Timing claims ("these came in overnight") —
published is available on the comment entries, but every clock-shaped claim needs its own guard and this issue is already about provenance.
Task
Give Surface Tension's discussion desk the instance provenance the design spec promises it, by actually reading
/feed/commentsand matching its entries to the episode's posts — or, if the answer is that the extra fetch is not worth it, trim spec §4.4's table so the show stops promising something it can never say. Today the code path exists and is never fed, so the desk can only ever report a bare count.Context
st_write.board_facts(post, comment_entries)already accepts parsed/feed/commentsentries and derives instance hosts and thread positions from them, andst_write.comments_for(post, entries)already matches entries to a post by title.scene_violationsallows a named host only when it appears in those facts. But nothing in the weekly run ever suppliescomment_entries:st_gatherdeliberately reads the post feeds and/feed/newonly, and #176's SKILL.md procedure never fetches the comments feed.The result is that the hosts list is always empty, so every named server in a switchboard line is refused as invented, and the desk's entire vocabulary is "somebody commented, and that is all I can tell you". That is honest — it is exactly what the guards are for — but it is a thinner scene than spec §4.4's table describes, and the "which instances called" row is currently aspirational rather than implemented.
Worth deciding deliberately rather than leaving as a silent gap: the desk is the show's most fragile scene, and volume-without-provenance is close to the minimum that justifies the seat existing at all.
Motivation
prompts/write_scene.md+ Surface Tension SKILL.md (Surface Tension Phase 2) #176 (feat(surface-tension): scene writer, SKILL.md and the desk's content guards (#176) #185) shipped the guards and the fact-derivation but no source for the facts.Pointers
skills/surface-tension/st_write.py—board_facts,comments_for,_permalink_host,_thread_position. All already written and unit-tested against the fixture; they need a caller.skills/surface-tension/st_write.py—scene_violations, whose_HOSTISH_REcheck is what currently refuses every named instance.skills/surface-tension/st_gather.py:279—feed_specs, and:662gather, where a comments fetch would go if the answer is "gather it".skills/surface-tension/st_gather.py:218—DEFAULT_CONFIG. Note it has nocomments_feedkey; spec §4.1's example does. Adding one means updatingtests/test_st_skill_md.py::test_skill_md_documents_every_config_key_and_no_phantom_ones, which asserts the Setup block andDEFAULT_CONFIGagree in both directions.tests/data/bubbles_feed_comments.xml— the real capture, with both content shapes (a first comment, and an Nth carrying "Earlier comments").tests/test_switchboard.py::test_a_host_the_comments_feed_confirms_is_allowed— already proves the whole chain works when entries are supplied.docs/superpowers/specs/2026-08-24-surface-tension-design.md§2.3, §4.4 — the recon finding and the amended desk role.Constraints
slash:commentson the post's own entry, with no second request. A comments-feed failure must degrade to "count only", never to a dead run or a skipped scene.gather_candidates, which logs, records a drop, and continues.New comment on: <post title> (1st, 1 total); the candidate schema does not carry the bubbles entry id, so the title is the only key available. Do not silently drop a post whose title fails to match — log it.Acceptance criteria
comment_entriestofill_scene_promptandbuild_scene_segment, and a scene built from a post with real comments accepts a line naming one of that post's actual instance hosts.DEFAULT_CONFIG, SKILL.md's Setup block and the drift test stay in agreement.pytest+ruff check .+ruff format --check .green with counts in the PR body.Out of scope
publishedis available on the comment entries, but every clock-shaped claim needs its own guard and this issue is already about provenance.