Repository navigation
feat(api): carry an agent's provisioning provenance in its payload - #8379
Merged
Merged
Conversation
houko
force-pushed
the
feat/8354-expose-agent-provenance
branch
from
September 15, 2026 13:34
8ba4c57 to
e9e1a41
Compare
`guard_provisioned_agent` refuses eleven manifest-writing routes with `423 Locked`, and the kernel has always known which agents those are — `provisioned_resource` is what the guard itself reads. The payload never carried it, so a client could not anticipate the refusal.
Concretely: an operator types an emoji and saves, or picks an image and uploads the whole thing, and is refused at the end by something that was never going to work.
`GET /api/agents` and `GET /api/agents/{id}` now emit `provisioned: { source } | null`. `source` is the declaring file, which is what a surface needs beyond "you cannot" — it says where to go and change it instead. The key is present and `null` when provisioning is off, so no client has to distinguish absent from null.
`enrich_agent_json` takes the provenance as a parameter rather than looking it up: it holds no kernel handle, and whether a list endpoint resolves it per row is the caller's decision. Per-row is what the list does — an `ArcSwap` load behind a map lookup that is empty on every installation which has not opted in, so a pre-pass name index would cost more than it saves.
Ten of the eleven guarded routes documented no response for the refusal at all; they now declare the `423`, and `openapi.json`, the SDKs and the schema baselines are regenerated to match.
`AgentProvenance` is added to the dashboard's `AgentItem` and `AgentDetail` so the field is readable from typed code. Disabling the write controls that consume it is left to the surfaces that own them.
Closes #8354
The comment inherited the issue's count, which included the two avatar routes #8349 adds and main does not have. The PR description and a grep of the tree both say eleven. The same paragraph was hard-wrapped against CLAUDE.md's prose rule, so it is reflowed to one sentence per line while being corrected.
houko
force-pushed
the
feat/8354-expose-agent-provenance
branch
from
September 15, 2026 14:14
e9e1a41 to
d7c4cfb
Compare
houko
enabled auto-merge (squash)
September 15, 2026 14:14
This was referenced Sep 19, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #8354.
What was wrong
guard_provisioned_agentrefuses eleven manifest-writing routes with423 Lockedon an agent the deployment declares. The kernel has always known which agents those are —provisioned_resourceis what the guard itself reads — but the payload never carried it, so no client could anticipate the refusal.grep -rn "provisioned" crates/librefang-api/dashboard/src/returned nothing in production code, because there was nothing to read.The concrete cost: an operator types an emoji and saves, or picks an image off disk and uploads the whole thing, and is refused at the end by something that was never going to work. The toast is correct and useless — it arrives after the attempt.
Changes
GET /api/agentsandGET /api/agents/{id}emit:nullfor an agent the operator created.sourceis the declaring file, which is the one thing a surface needs beyond "you cannot": it says where to go and change it instead. That is exactly the shape the issue proposed; I did not addchecksum/applied_at, whichGET /api/provisioning/statusalready serves and no write-control decision needs.The key is present and null when provisioning is switched off entirely, rather than absent. A field that disappears with the feature would force every client to treat two spellings as one answer.
enrich_agent_jsontakes the provenance as a parameter rather than looking it up. It holds no kernel handle, and whether a list endpoint resolves it per row should be the caller's decision rather than a hidden cost inside a formatter. Per-row is what the list does: anArcSwapload behind a map lookup, empty on every installation that has not opted in, so a pre-pass name index would cost more than it saves.GET /api/agents/{id}builds its body by hand and spells the field there too — that is the drawer offering the controls the guard refuses, so it is the surface that most needs it.Also fixed, same area: ten of the eleven guarded routes documented no response at all for the refusal (only
PUT /api/agents/{id}/model_routingdid), so a generated client treated a423as an unmodelled failure. They now declare it.openapi.json, the SDKs and the schema baselines are regenerated; onlyopenapi.jsonandxtask/baselines/openapi.sha256actually moved, since the SDK generators do not encode response statuses.AgentProvenanceis added to the dashboard'sAgentItemandAgentDetailso the field is readable from typed code.On the issue's last paragraph
The
403-documented-but-423-returned mismatch is real, but it is not onmain:crates/librefang-api/src/routes/agents/avatar.rsdoes not exist here. It arrives with #8349, so the one-line fix belongs in that PR rather than in this one — applying it here would mean creating the file. Flagging rather than silently dropping it.Wiring the dashboard's write controls to disable on
provisionedis likewise left to the surfaces that own them: the agent detail drawer's identity and avatar controls are #8352's and #8349's files, both unmerged. This PR delivers the field and the type those PRs need; putting the consumption here would conflict with them and would be a maintainer's call to sequence, not mine.Verification
The three new tests assert
sourceagainst the real declaring path rather than merely for presence, and the null case is asserted with a provisioned agent in the same response — the two have to be distinguishable by this field alone, which is the whole point of emitting it.Deferred
The two items under "On the issue's last paragraph", both because they live in files that unmerged PRs introduce. Nothing else.