Found while reviewing the stack:1635 set (#1852 → #1860) and deliberately not fixed there, because the fix is a contract decision rather than a cleanup.
What
FountainWeb.Schemas declares permission_policy inline in five places — AgentResponse, AgentRequest, AgentUpdateRequest, Conversation and ConversationCreateRequest. Each copy is now ~22 lines: the ask_timeout property with its minimum/maximum and an 8-line description, plus the additionalProperties oneOf union of the verdict enum and an integer.
The duplication predates #1852 — the additionalProperties enum was already five-way — but that PR roughly tripled the size of the copied fragment, and #1635's ceiling work then had to edit all five again. Any further change to the policy wire shape is a five-place edit with nothing to catch a missed one.
Why it was not fixed in the stack
The file's own idiom is a named nested schema module referenced by name (UsageTotal, and PendingPermissionRequest added in #1859). Applying it here is the obvious fix:
defmodule PermissionPolicy do
OpenApiSpex.schema(%{title: "PermissionPolicy", ...})
end
But that turns the inline object into a $ref, which renames the generated TypeScript type — the five call sites stop producing an anonymous intersection and start producing components["schemas"]["PermissionPolicy"]. That is arguably better for SDK consumers, and it is a contract change that wants deciding on its own rather than riding along in a bug fix.
A module attribute would keep the emitted spec byte-identical, but attributes are lexically scoped to the module being defined, so one declared on FountainWeb.Schemas is not visible inside the nested defmodules — which is why there is no module-attribute precedent in the file.
Decide
- Named schema module and accept the generated-type rename (one SDK minor, no runtime change), or
- leave it and accept the five-place edit.
Either is fine; the point of the issue is that the current state is a silent five-way edit surface and nobody has chosen.
Related: #599 derived the enum lists to stop exactly this class of drift.
Found while reviewing the
stack:1635set (#1852 → #1860) and deliberately not fixed there, because the fix is a contract decision rather than a cleanup.What
FountainWeb.Schemasdeclarespermission_policyinline in five places —AgentResponse,AgentRequest,AgentUpdateRequest,ConversationandConversationCreateRequest. Each copy is now ~22 lines: theask_timeoutproperty with itsminimum/maximumand an 8-line description, plus theadditionalPropertiesoneOfunion of the verdict enum and an integer.The duplication predates #1852 — the
additionalPropertiesenum was already five-way — but that PR roughly tripled the size of the copied fragment, and #1635's ceiling work then had to edit all five again. Any further change to the policy wire shape is a five-place edit with nothing to catch a missed one.Why it was not fixed in the stack
The file's own idiom is a named nested schema module referenced by name (
UsageTotal, andPendingPermissionRequestadded in #1859). Applying it here is the obvious fix:But that turns the inline object into a
$ref, which renames the generated TypeScript type — the five call sites stop producing an anonymous intersection and start producingcomponents["schemas"]["PermissionPolicy"]. That is arguably better for SDK consumers, and it is a contract change that wants deciding on its own rather than riding along in a bug fix.A module attribute would keep the emitted spec byte-identical, but attributes are lexically scoped to the module being defined, so one declared on
FountainWeb.Schemasis not visible inside the nesteddefmodules — which is why there is no module-attribute precedent in the file.Decide
Either is fine; the point of the issue is that the current state is a silent five-way edit surface and nobody has chosen.
Related: #599 derived the enum lists to stop exactly this class of drift.