fix(pact): add MCPPrompt.Writes so a write-directing prompt sits under the read/write ceiling

- pact.MCPPrompt.Writes (additive; the zero value keeps today's listing)
- pier's allowsPrompt hides a prompt declared with Writes from an identity
  without write, as it hides a tool that is not read-only (C-IN-01
  framework part, P-28)
This commit is contained in:
Jakub Zych
2026-10-23 16:08:27 +02:00
parent 104db9959f
commit 211cec11b0
5 changed files with 15 additions and 5 deletions

View File

@@ -121,7 +121,7 @@ What pier does with it at boot:
- **Input.** `Input` is the zero value of a struct. A `path:"id"` field fills the `{id}` segment, a `query:"q"` field goes to the query string, every other field forms the JSON body; a `jsonschema:"..."` tag is the field's description and a field without `omitempty` is required. pier reflects the input schema from the struct, validates every call's arguments against it, and forwards the body as the agent sent it minus the path and query keys, so an update route that checks key presence sees exactly what the agent set. A `GET` or `DELETE` tool may carry only path and query fields, and an embedded struct on one fails boot (its promoted fields would be body fields). A `path:` field must be required: one with `omitempty` or `omitzero` fails boot. At call time pier passes a route only path values the HTTP mux could produce: a missing value, an empty, `.` or `..` segment, or a `/` inside a single-segment value is a tool error before the route runs. Numbers keep their literal digits in path and query values, so integer ids above 2^53 stay exact. `Output`, when set, is the struct the 2xx body matches and becomes the output schema.
- **Annotations.** `GET` is read-only and idempotent, `PUT` idempotent, `DELETE` destructive; `ReadOnly`, `Destructive`, `Idempotent` and `OpenWorld` override. The read-only hint also decides what a read-only grant sees (below).
- **Results.** A 2xx body is returned as text plus structured content (an object as is, any other JSON wrapped as `{"data": …}`); a 4xx or 5xx is a tool error carrying the route's own body, so the agent reads the real validation errors. A failure of the guard provider itself (for example a database error while resolving the principal) is the fixed tool error `pier: the tool could not be run for this caller`, logged server-side. The dispatched request carries the MCP request's `Host`, `X-Forwarded-Proto` and `X-Forwarded-Host`, so a route that builds absolute links builds them as over HTTP.
- **Prompts.** A `pact.MCPPrompt` is prefixed and filtered like a tool, names its guard, declares its arguments, and renders its messages in `Render`; required arguments are checked first. A prompt never dispatches a route.
- **Prompts.** A `pact.MCPPrompt` is prefixed and filtered like a tool, names its guard, declares its arguments, and renders its messages in `Render`; required arguments are checked first. A prompt never dispatches a route. Set `Writes` on a prompt whose text directs tools that are not read-only (for example "draft a post, then create it"): it then sits under the same read/write ceiling as those tools, so a read-only grant is not offered a prompt it cannot follow.
- **Descriptions.** `Title` and `Description` are static text, at most 1024 characters, passed through unchanged. Keep them factual one-liners and never build them from data: tool text is read by the model, so data-derived text would be a prompt-injection surface.
## Identity and sessions