Skip to content

Let a declaration reach into the body and count up what goes out - #39

Merged
haruotsu merged 3 commits into
mainfrom
openapi-body-reach
Aug 27, 2026
Merged

Let a declaration reach into the body and count up what goes out#39
haruotsu merged 3 commits into
mainfrom
openapi-body-reach

Conversation

@haruotsu

Copy link
Copy Markdown
Collaborator

Two additions to openapi:, designed together because they are one
mechanism seen from both sides: what the declaration pins, and what the
model may write.

A dotted params name reaches into the JSON body

Services commonly nest what matters. A helpdesk ticket update carries its
comment inside the record:

{"ticket": {"comment": {"body": "...", "public": true}}}

Whether that comment goes out to the requester — mail, irreversible — is a
boolean two levels down. params could only name arguments and top-level
body properties, so the one value that separates an internal note from an
outgoing reply was the model's to write, guarded by nothing but describe
and the instruction. The code such a declaration replaces kept the two
apart as differently named functions; the declaration has to be able to pin
the same value structurally.

params:
  UpdateTicket:
    ticket.comment.public: {value: false}
  • A dotted name is read as a path into the body only when no parameter
    carries the name literally
    — query parameters with dots in their names
    exist in the wild and keep meaning what they always meant. A name both
    readings can claim is refused, like the existing parameter-vs-body
    ambiguity.
  • A fixed leaf disappears from the declaration shown to the model, and the
    declared value is written wherever its parent object is sent — overwriting
    anything found there (smuggled values included), never conjuring the
    parent up: no comment written means no comment sent.
  • A parent that arrives as a non-object refuses the request instead of
    sending it around the fix.
  • validate walks the path through $ref and one level of allOf per
    step, reports certain misses with the properties that do exist at the
    level that missed, and stays quiet where the schema cannot be enumerated —
    the same certainty rule flat names follow. prefix/suffix still demand
    a string leaf.

only counts up what the model may write

Published update operations accept the whole record — status, assignee,
tags, custom fields — when an agent is only meant to add a comment. With
fixes alone one would enumerate every unwanted field, and that list goes
stale silently as the description grows. only turns the enumeration
around:

only:
  UpdateTicket: [ticket_id, ticket.comment.body]
  • Everything unlisted is taken out of the declaration and never sent, even
    when smuggled into the arguments. A params value still rides — that is
    how a hidden field is pinned.
  • A dotted entry exposes just that path: the declared schema is narrowed to
    the named subtree, and the outgoing body is filtered to it.
  • validate holds the two against each other: an entry cannot name what a
    fix already pins, a prefix cannot wrap what only withholds, a nested
    fix cannot sit under a parent only never sends, and a required argument
    must be either listed or fixed.

The README example shows the pair composed: the model writes nothing but
the ticket id and the comment text, and the declaration keeps the comment
internal.

A helpdesk ticket update carries its comment inside the record:
{"ticket": {"comment": {"body": ..., "public": true}}}. Whether that
comment goes out to the requester is a boolean two levels down, and
params could only name arguments and top-level body properties - the
one value that separates an internal note from an outgoing mail was
the model's to write. The code such a declaration replaces kept the
two apart as differently named functions; a declaration must be able
to pin the same value structurally, not ask nicely in the instruction.

params now reads a dotted name as a path into the JSON body when no
parameter carries the name literally. A fixed leaf disappears from the
declaration and is written wherever its parent object is sent -
overwriting anything found there, never conjuring the parent up.

Published update operations also accept the whole record - status,
assignee, tags - when an agent is only meant to add a comment. Pinning
every unwanted field enumerates a list that goes stale silently as the
description grows. The new only key turns the enumeration around: it
names what the model may write, everything else leaves the declaration
and is never sent, and a new field stays unexposed until someone
declares it. The two are one mechanism seen from both sides, so their
rules hold each other: a fix cannot pin what only hands the model, a
prefix cannot wrap what only withholds, and a required argument cannot
fall between them.

🤖 Generated with Claude Code
A dot in an only entry was always read as a path into the body, while
params gives a literal match the first claim. The two readings drifted
apart: a parameter whose name carries a dot validated cleanly when
listed, and the runtime then silently took it out of the declaration -
and a dotted entry keyed exposure by its first segment, so a request
parameter sharing that name rode along whole, never listed, against
only's promise that everything unlisted stays unsent.

Now a name some parameter carries literally stays whole on both sides,
and the readings that cannot be told apart are refused like the
existing two-places rule: an entry naming both a request parameter and
a body property, and a body path whose first segment a request
parameter also claims.

🤖 Generated with Claude Code
When everything the model wrote inside an object was unlisted, the
filter kept the emptied shell and the request carried it - a write the
model never made, and one the nested-fix path already refuses to
conjure. Nothing surviving the filter now means nothing is sent, the
same reading "no comment written means no comment sent" gives the
fixes.

🤖 Generated with Claude Code
@haruotsu
haruotsu merged commit dcd69ce into main Aug 27, 2026
3 checks passed
@haruotsu
haruotsu deleted the openapi-body-reach branch August 27, 2026 14:52
@github-actions github-actions Bot mentioned this pull request Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant