Repository navigation
Hold a field change that a saved query or play names in core's update_schema (closes #3786) - #3790
Conversation
malibio
left a comment
There was a problem hiding this comment.
Code Review Summary
Verdict (self-authored PR, posted as a comment): net positive, no blockers; fix the one invariant gap below, then merge.
Moving the what-names-it hold into core's update_schema is the right place for it: the CLI and every other caller now meet the same refusal the agent tool used to apply alone, the agent keeps a single agreement (force), and the narrowing of the issue (no blanket refusal of User-protected seeded fields) is consistent with ADR-076 and is reflected consistently in the describer, bound chat, skill and ADR-100. The reordering of the play check is safe: nothing between the old and new position read affected/affected_names (the only later use is the result's affected_plays at the end, which is only reachable with force), and the check is a pure read.
Findings
Critical Issues
None.
Suggested Improvements
packages/core/src/schema/mod.rs(hold block, comment "The title templates that name it are refused above"): the invariant "no hard refusal can follow a hold" is not fully true. Only a rename a title template names is refused in Phase 0. A removal of a field the title template names is refused only later, bySchemaNodeBehavior.validate_schema_node("Schema validation failed"), after the hold. So a removal that a saved query and the title template both name asks the caller to agree viaforce, and then refuses it outright. Either add the removal case to the Phase 0 title-template check (cheap:template.contains("{field}")forremove_fields, ahead of the hold), or soften the comment. The first is better, since it is exactly the situation the PR set out to remove. The same applies to the other late refusals (Phase 1 rename failures, Phase 2 additions), which are unrelated toforceand acceptable, but the comment "Last of the refusals ahead of Phase 1" should not be read as "before every refusal".packages/core/src/schema/mod.rsnear line 25: the newFIELD_CHANGE_REFERENCES_SHOWNconst was inserted between the/// Reserved core property names...doc comment andRESERVED_CORE_PROPERTIES, so rustdoc now attaches that sentence to the wrong item andRESERVED_CORE_PROPERTIESloses its doc. Move the const above the doc comment.- Failure mode changed: the agent tool used to log a warning and let the change through when the reference lookup failed (fail open); core now returns an
internal_errorand blocks the change (fail closed) unlessforce=true. That is defensible for a destructive write and consistent with the impact-analysis check, but it is a behaviour change worth a sentence in the PR body, and there is no test for it. - Test gap, ordering:
test_update_schema_holds_a_user_field_of_a_built_in_type...asserts that removingtask.statuswithforce: trueis refused, which would be refused regardless of order becauseforceskips the hold. The ordering property ("a hard refusal is not preceded by a hold") is only pinned in the agent tool test (!contains("force=true") && contains("protected")). Add the unforced variant to the core test so the invariant is pinned where it lives. - Test gap, composition: nothing in core's tests covers (a) a saved query and an active play in one refusal (first-sentence shape with the ", and it would affect N active play(s)" join), (b) a play reference found by
find_field_references(only saved queries are exercised), or (c) theFIELD_CHANGE_REFERENCES_SHOWNcap and the "N more" entry. The first-sentence assertion splits on ". ", which a saved-query title containing ". " would also break; fine for a test, but it is worth knowing the "first sentence" contract is by convention only.
Behaviour notes (no change required)
forcesemantics: callers that already passedforce=true(core_type_registry_test, existing schema tests, any CLI/desktop caller) now also skip the saved-query hold, which is the stated intent. Callers that did not pass it and rename/remove a field a saved query or play names are newly refused; I found no non-test caller in cli/daemon/desktop that does so.- A play that triggers on the type and names the field is reported twice in the combined message: once as a reference ("play ...") and once as an affected active play. Harmless, but noisy; dedupe by play id if the message ever gets long.
- Cost:
find_field_referencesscans all schemas (with aresolve_type_chaineach), all query nodes and all play nodes, and runs only when!forceand a removal or identity rename is present, so additive calls and forced calls pay nothing. Acceptable for a rare, destructive, user-agreed operation. It builds references for every declared field of the schema, not only the changed ones; filtering bychangedbefore scanning would cut the work, but is not worth doing now.
Nitpicks
- Nit:
packages/core/src/schema/mod.rs, kind mapping_ => "play"hides any futureReferenceKindvariant behind a wildcard; matchPlayexplicitly and makeTitleTemplateunreachable/filtered at the match. - Nit: the
forcedescription in the agent tool ("naming a field that is gone or affect active plays") reads awkwardly; "or would affect active plays".
Docs consistency
ADR-100's skill paragraph, understanding-a-type.md, the describer text and the generated update-schema-params.ts doc comment all agree with the code. No node-type, field or relationship change, so node-types.md needs nothing. No remaining mentions of confirmed or field_change_refusal in the repo. Skill file size: the sequence change is a net small addition; confirm the CLI skill-size integration test still passes (it is at its cap per project notes), since understanding-a-type.md grew by roughly 150 characters.
Not merging; no re-review needed beyond the title-template ordering fix.
Address review: summaryThe review was a net positive with no blockers and one invariant gap. Addressed
Noted, not changed
Checks: Re-Review DecisionDecision: RE-REVIEW NEEDED (narrow) Rationale: a new refusal was added to core's |
Re-review summary (commit 74827d5)Verdict (self-authored PR, posted as a comment): net positive, no blockers. The earlier invariant gap is closed. One narrow over-refusal is worth a two-line fix before merge. The previous round's findings are all addressed: the const sits above the doc comment it displaced, the kind match is explicit, the wording is corrected, and the new tests pin the ordering, composition and cap properties in core. FindingsCritical IssuesNone. Suggested Improvements
Nitpicks
Checked and fine
|
Address review (re-review round): summaryThe re-review found the invariant gap closed and one narrow over-refusal. Addressed
Not changed
Checks: Re-Review DecisionDecision: NO RE-REVIEW NEEDED Rationale: the delta only narrows a refusal this PR added, by skipping names the call declares again, and adds its test. Nothing new is refused. |
…_schema (closes #3786) The what-names-it check moves from the agent's update_schema tool into the schema write, so the CLI and every other caller meet it. One agreement (force) covers active plays and the saved queries or plays that name a removed or renamed field, in one refusal whose first sentence carries the list. The tool loses its own guard and its confirmed parameter. The bound chat now calls only core- and system-protected fields built in: a user-protected field of a built-in type (task priority) is the user's to change once what names it is dealt with. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ver ordering, composition and cap - Refuse a removal the title template (as the call leaves it) names, ahead of the hold, so a caller is not asked to agree to what is then refused - Move the new const above the doc comment it displaced - Name every ReferenceKind in the kind mapping - Tests: unforced core-protected removal is not held; saved query and play in one refusal; the cap of 10 with the rest counted; template-named removal Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…plate valid Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
212bb54 to
f0d9713
Compare
Summary
update_schemanow holds a removal or identity rename that a saved query or play names, in core's write, so the CLI and every other caller meet what the agent's tool used to check alone. One agreement,force, covers active plays and these references, in one refusal whose first sentence carries the list (the failure notice a user sees keeps only that sentence).field_change_refusal) and itsconfirmedparameter, and declaresforce.core/system-protected fields built in. Auser-protected field of a built-in type (task.priority, the dates) is described as changeable once what names it is dealt with, matching core and ADR-076.Decision
The issue as first filed also asked core to refuse removal of any built-in field. Core's seeded
Userprotection is deliberate (ADR-076; the shape check treatspriorityas the user's to remove), so that half was dropped and the issue narrowed. ADR-100's skill paragraph is updated to say so.Tests
schema_test.rs: a saved query holds a rename and a removal untilforce; a relabel and an unnamed field go freely;task.priorityis held for a saved query whiletask.statusis refused whatever is agreed.force; bound-chat and describer tests updated; tool-surface golden updated for the parameter rename.bun run test:changedpasses; clippy clean on core, agent and daemon.Closes #3786
🤖 Generated with Claude Code