Skip to content

Trim tool-schema copy; merge recipe upsert; unique recipe titles - #545

Merged
sysread merged 2 commits into
mainfrom
trim-and-recipe-upsert
Sep 8, 2026
Merged

sysread merged 2 commits into
mainfrom
trim-and-recipe-upsert

Conversation

@sysread

@sysread sysread commented Sep 8, 2026

Copy link
Copy Markdown
Owner

SYNOPSIS

Copy trims on tools staying separate, recipe_update folded into recipe_save as an id-optional edit form, and recipe titles made unique with a dedup heuristic.

PURPOSE

Continues the tool-consolidation work from #544. The photo quartet and record file/link pairs deliberately STAY separate (disjoint param sets - a merged schema would show every form's params at once and required cannot be conditional), so their token cost comes from copy trim instead. The recipe pair is a true upsert (shared fields superset) and merges. Title uniqueness gives the merged tool the same natural-key dedup wiki_save has.

DESCRIPTION

  • Copy trim (~540 tokens): photo quartet + record file/link schemas drop cross-reference tails, redundant "change_message REQUIRED" prose (required is already in the wire schema), and change_message "Examples:" tails.
  • recipe_save upsert: id present = edit (p_set_* RPC pairs unchanged, photos carried forward, topics dropped from the echo), id absent = create (change_message defaults to "Initial version"). The rating veto is enforced pre-route on both forms.
  • Unique titles: recipes_user_title_unique index on (user_id, lower(btrim(title))), verified collision-free against prod before landing. Natural-key dedup in recipe_save: exact-title create routes to update (matched_existing: true in the result), near-match refused with candidates named. RPC 23505 handlers attached to nested BEGIN..END blocks (a bare mid-body exception clause parses but silently swallows subsequent statements - caught by QA).

NOTES

  • Wire: 56 -> 55 tools, 65414 chars (~18.7k tokens, down from ~20.1k at session start).
  • QA'd end-to-end vs glm-5-3-flash on the local stack: create-then-update in one turn (id passed from the first call's return), version rows correct, rating veto held, lowercase-variant create surfaced matched_existing: true and the model read and explained the flag.
  • Deliberately not merged: photo quartet, record file/link pairs, memory levers, close/dismiss (disjoint params or semantically distinct verbs).

recipe_update folds into recipe_save as the id-optional edit form -
the same upsert pattern as wiki_save (one shared fields superset,
binary id decision, rating veto enforced pre-route on both forms).
The edit form's RPC contract is unchanged (p_set_* pairs, photos
carried forward, topics dropped from the echo); the create form keeps
its change_message default. Wire: 56 -> 55 tools, 65414 chars.

Copy trim on the tools that stay separate: the photo quartet and
record file/link schemas drop their cross-reference tails and the
redundant 'change_message REQUIRED' prose (required is already in the
wire schema), and the change_message 'Examples:' tails (the
convention + cap is what matters). ~540 tokens saved.

QA vs glm-5-3-flash on the local stack: create-then-update in one
turn with the id passed from the first call's return; version rows
show 'Initial version' then 'Correct cook time from 3 to 4 minutes
per side'; rating stayed unset.
Adds recipes_user_title_unique - a unique index on
(user_id, lower(btrim(title))). Duplicate titles read as bugs (two
cards that drift apart as one gets edited), and the uniqueness key is
what the recipe upsert's dedup heuristic needs to do what wiki_save
does: a create whose title exactly matches an existing recipe routes
to that recipe's update, a near-match is refused with candidates
named.

Two implementation notes QA forced:
- Enforced as a unique INDEX, not a table constraint: Postgres only
  accepts expressions in index form.
- The RPC 23505 handlers are attached to nested BEGIN..END blocks. A
  bare  clause mid-body parses but silently swallows every
  statement after it - empirically caught when a fresh-title create
  produced a recipe row with no version row.
- When the heuristic matches, the edit form's required change_message
  defaults to 'Matched existing "<title>"' - the model had no reason
  to provide it (the schema calls it optional on create).

Constraint verified collision-free against prod (50 recipes, 0
ci-collisions) before landing. End-to-end QA vs glm-5-3-flash: create
form, then a lowercase-variant create surfaced matched_existing: true
to the model, which read and explained the flag correctly.
@sysread
sysread merged commit 4dbadf6 into main Sep 8, 2026
1 check passed
@sysread
sysread deleted the trim-and-recipe-upsert branch September 8, 2026 22:41
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