fix(tool-todo): declare the unknown-key rejection in the item schema

additionalProperties stays true in the published schema while execute
rejected extra keys, so generated typings and validation disagreed with
runtime behavior. The item schema now declares additionalProperties:
false — the registry's arg validation rejects extra keys with a
path-qualified violation before execute runs — and the redundant manual
check is dropped (tool catalog regenerated).
This commit is contained in:
Chinesezjc
2026-07-24 22:47:01 +08:00
parent c1ae98940c
commit ba75229638
3 changed files with 8 additions and 12 deletions
+6 -10
View File
@@ -28,21 +28,17 @@ const DESCRIPTION =
/**
* Validate the value constraints the ParameterSchemaSpec can't express and build the canonical {@link
* TodoItem}[]: known keys only, trimmed non-empty unique content, and at most one in-progress
* item. The registry has already enforced the status enum; the cast below records that
* guarantee. Unknown keys are rejected rather than dropped — the logged snapshot must equal
* what the model believes it wrote (model-visible ⟺ logged), so a nested/extended item shape
* fails loud instead of silently flattening.
* TodoItem}[]: trimmed non-empty unique content and at most one in-progress item. The registry
* has already enforced the status enum and rejected unknown item keys (`additionalProperties:
* false` — the logged snapshot must equal what the model believes it wrote, so a nested/extended
* item shape fails loud at the schema boundary instead of silently flattening); the cast below
* records that guarantee.
*/
function toTodoList(raw: { content: string; status: string }[]): TodoItem[] {
const todos: TodoItem[] = []
const seen = new Set<string>()
let inProgress = 0
for (const item of raw) {
const unknown = Object.keys(item).filter(key => key !== 'content' && key !== 'status')
if (unknown.length > 0) {
throw new Error(`invalid todo: unknown key(s) ${unknown.map(k => JSON.stringify(k)).join(', ')} — each item is exactly { content, status }`)
}
const content = item.content.trim()
if (content.length === 0) {
throw new Error('invalid todo: `content` must be a non-empty string')
@@ -73,7 +69,7 @@ export function apply(ctx: Context): void {
description: 'The COMPLETE task list, replacing any previous list.',
items: {
type: 'object',
additionalProperties: true,
additionalProperties: false,
properties: {
content: { type: 'string', required: true, description: 'What the task is — a short imperative line.' },
status: {