fix: hoist $defs to the schema root for set_tasks
Refs like "#/$defs/AddTask" resolve from the document root, but Type.Cyclic nested the $defs inside tasks.items, so the moonshot-flavored gateway reported "$defs not found for reference". Hoist all $defs entries to the parameter schema root and strip the synthetic $id that would change fragment resolution. Update the regression test to pin the layout. Bump version to 0.2.3.
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pi-todo-herdr",
|
"name": "pi-todo-herdr",
|
||||||
"version": "0.2.2",
|
"version": "0.2.3",
|
||||||
"description": "Hierarchical task tools for Pi with a live widget and Herdr sidebar integration.",
|
"description": "Hierarchical task tools for Pi with a live widget and Herdr sidebar integration.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./index.ts",
|
"main": "./index.ts",
|
||||||
|
|||||||
+38
-1
@@ -38,6 +38,41 @@ function normalizeLocalRefs<T>(schema: T): T {
|
|||||||
return schema;
|
return schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TypeBox's Type.Cyclic nests $defs inside the referenced item schema, but
|
||||||
|
* refs like "#/$defs/AddTask" resolve from the document root. Strict gateways
|
||||||
|
* (moonshot-flavored) reject a root-style ref when the definition is nested.
|
||||||
|
* Hoist every $defs entry to the schema root and drop the synthetic $id, which
|
||||||
|
* would otherwise change fragment resolution inside the definition.
|
||||||
|
*/
|
||||||
|
function hoistDefsToRoot<T>(schema: T): T {
|
||||||
|
const defs = new Map<string, unknown>();
|
||||||
|
function walk(node: unknown): void {
|
||||||
|
if (Array.isArray(node)) {
|
||||||
|
for (const item of node) walk(item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!node || typeof node !== "object") return;
|
||||||
|
const obj = node as Record<string, unknown>;
|
||||||
|
const nested = obj["$defs"];
|
||||||
|
if (nested && typeof nested === "object") {
|
||||||
|
for (const [name, def] of Object.entries(nested)) {
|
||||||
|
if (!defs.has(name)) defs.set(name, def);
|
||||||
|
}
|
||||||
|
delete obj["$defs"];
|
||||||
|
}
|
||||||
|
for (const value of Object.values(obj)) walk(value);
|
||||||
|
}
|
||||||
|
walk(schema);
|
||||||
|
for (const def of defs.values()) {
|
||||||
|
if (def && typeof def === "object") {
|
||||||
|
delete (def as Record<string, unknown>)["$id"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (defs.size > 0) (schema as Record<string, unknown>)["$defs"] = Object.fromEntries(defs);
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
const AddTaskSchema = Type.Cyclic(
|
const AddTaskSchema = Type.Cyclic(
|
||||||
{
|
{
|
||||||
AddTask: Type.Object(
|
AddTask: Type.Object(
|
||||||
@@ -61,7 +96,8 @@ const AddTaskSchema = Type.Cyclic(
|
|||||||
"AddTask",
|
"AddTask",
|
||||||
);
|
);
|
||||||
|
|
||||||
export const SetTaskParams = normalizeLocalRefs(
|
export const SetTaskParams = hoistDefsToRoot(
|
||||||
|
normalizeLocalRefs(
|
||||||
Type.Object(
|
Type.Object(
|
||||||
{
|
{
|
||||||
mode: SetTaskModeSchema,
|
mode: SetTaskModeSchema,
|
||||||
@@ -74,6 +110,7 @@ export const SetTaskParams = normalizeLocalRefs(
|
|||||||
},
|
},
|
||||||
{ additionalProperties: false },
|
{ additionalProperties: false },
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const TaskPatchSchema = Type.Object(
|
const TaskPatchSchema = Type.Object(
|
||||||
|
|||||||
+10
-6
@@ -31,14 +31,18 @@ test("registers five focused tools without the old add_task alias", () => {
|
|||||||
assert.deepEqual(Object.keys(tools.get("get_task").parameters.properties), ["ids"]);
|
assert.deepEqual(Object.keys(tools.get("get_task").parameters.properties), ["ids"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("set_tasks schema uses #/$defs/ refs accepted by strict providers", () => {
|
test("set_tasks schema hoists $defs to the root with #/$defs/ refs", () => {
|
||||||
const { tools } = registeredTools();
|
const { tools } = registeredTools();
|
||||||
const itemSchema = tools.get("set_tasks").parameters.properties.tasks.items;
|
const params = tools.get("set_tasks").parameters;
|
||||||
// Every local $ref must be a URI reference; bare names ("AddTask") are
|
const itemSchema = params.properties.tasks.items;
|
||||||
// rejected by moonshot-flavored schema validators (Console Go upstream).
|
// $defs must live at the document root: root-style refs resolve from there,
|
||||||
|
// and moonshot-flavored validators reject nested definitions (Console Go).
|
||||||
|
assert.ok(params.$defs.AddTask, "$defs hoisted to the parameter schema root");
|
||||||
|
assert.equal(itemSchema.$defs, undefined, "no $defs nested inside tasks items");
|
||||||
assert.equal(itemSchema.$ref, "#/$defs/AddTask");
|
assert.equal(itemSchema.$ref, "#/$defs/AddTask");
|
||||||
assert.equal(itemSchema.$defs.AddTask.properties.children.items.$ref, "#/$defs/AddTask");
|
assert.equal(params.$defs.AddTask.properties.children.items.$ref, "#/$defs/AddTask");
|
||||||
const serialized = JSON.stringify(tools.get("set_tasks").parameters);
|
assert.equal(params.$defs.AddTask.$id, undefined, "synthetic $id stripped");
|
||||||
|
const serialized = JSON.stringify(params);
|
||||||
assert.ok(!/"\$ref":"(?!\#)/.test(serialized), "no bare $ref values remain");
|
assert.ok(!/"\$ref":"(?!\#)/.test(serialized), "no bare $ref values remain");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user