Follow-up from #158, deliberately left out of it.
Background
#158 established one shape for capability absence, in spacepilot/capability.py. CHECKPOINT_SYNC, LORA_TRAINING and MODEL_DOWNLOAD build both their refusal message and their capability block from the same text, so a gate's message and its block cannot drift.
The convention the train settled on:
- bad input ->
error plus the valid set (e.g. {"error": ..., "known": [...]})
- good input, missing capability -> 200 plus
{implemented, gated, detail}
These answer different questions and are deliberately kept distinct: collapsing them would make "you typo'd an id" and "we haven't built this" the same shape.
#158 wired the block into the checkpoint listings. The LoRA and recommender listings still return a bare list, so an empty result reads as "you have no adapters" rather than "training is gated and there can be none".
Why it was not done in #158
It is not a one-liner. It means taking GET /api/lora/adapters off response_model=List[LoRAAdapterSpec], plus the matching MCP tool, spacepilot lora list, and their tests — four surfaces and a response-model change. #158 was already the largest PR in the train (+611/-218) and this work lands on the same listings #156 was reshaping.
The lane flagged it rather than growing the PR, which was the right call.
What closing it looks like
Attach the capability block to the LoRA and recommender listings on every surface — HTTP, MCP, CLI — using the existing spacepilot/capability.py shape. The shape already exists in one place, so this is mechanical wiring, not design.
Worth doing while the convention is fresh, so a third refusal idiom does not appear later.
Provenance
2026-09-22 train, #158.
Follow-up from #158, deliberately left out of it.
Background
#158 established one shape for capability absence, in
spacepilot/capability.py.CHECKPOINT_SYNC,LORA_TRAININGandMODEL_DOWNLOADbuild both their refusal message and their capability block from the same text, so a gate's message and its block cannot drift.The convention the train settled on:
errorplus the valid set (e.g.{"error": ..., "known": [...]}){implemented, gated, detail}These answer different questions and are deliberately kept distinct: collapsing them would make "you typo'd an id" and "we haven't built this" the same shape.
#158 wired the block into the checkpoint listings. The LoRA and recommender listings still return a bare list, so an empty result reads as "you have no adapters" rather than "training is gated and there can be none".
Why it was not done in #158
It is not a one-liner. It means taking
GET /api/lora/adaptersoffresponse_model=List[LoRAAdapterSpec], plus the matching MCP tool,spacepilot lora list, and their tests — four surfaces and a response-model change. #158 was already the largest PR in the train (+611/-218) and this work lands on the same listings #156 was reshaping.The lane flagged it rather than growing the PR, which was the right call.
What closing it looks like
Attach the capability block to the LoRA and recommender listings on every surface — HTTP, MCP, CLI — using the existing
spacepilot/capability.pyshape. The shape already exists in one place, so this is mechanical wiring, not design.Worth doing while the convention is fresh, so a third refusal idiom does not appear later.
Provenance
2026-09-22 train, #158.