refactor: rename casRef to x-cas-ref for JSON Schema compliance (Phase 3) #292

Merged
xiaomo merged 1 commits from feat/285-phase3-x-cas-ref into main 2026-05-16 11:55:25 +00:00
4 changed files with 65 additions and 9 deletions
@@ -0,0 +1,53 @@
import { describe, expect, test } from "bun:test";
import {
END,
START,
type ModeratorTable,
type WorkflowDefinition,
} from "@uncaged/workflow-protocol";
import * as z from "zod/v4";
import { buildDescriptor } from "../src/bundle/build-descriptor.js";
const phaseSchema = z.object({
hash: z.string().meta({ "x-cas-ref": true }),
title: z.string(),
});
type TestMeta = {
planner: { phases: Array<{ hash: string; title: string }>; label: string };
};
const testTable: ModeratorTable<TestMeta> = {
[START]: [{ condition: "FALLBACK", role: "planner" }],
planner: [{ condition: "FALLBACK", role: END }],
};
describe("buildDescriptor", () => {
test("preserves x-cas-ref in role JSON Schema", () => {
const def: WorkflowDefinition<TestMeta> = {
description: "test workflow",
roles: {
planner: {
description: "plans work",
systemPrompt: "plan",
schema: z.object({
phases: z.array(phaseSchema),
label: z.string(),
}),
},
},
table: testTable,
};
const descriptor = buildDescriptor(def);
const props = (descriptor.roles.planner.schema as { properties: Record<string, unknown> })
.properties;
const phaseProps = (
(props.phases as { items: { properties: Record<string, unknown> } }).items
).properties;
expect((phaseProps.hash as Record<string, unknown>)["x-cas-ref"]).toBe(true);
expect((phaseProps.title as Record<string, unknown>)["x-cas-ref"]).toBeUndefined();
expect((props.label as Record<string, unknown>)["x-cas-ref"]).toBeUndefined();
});
});
+3
View File
@@ -29,6 +29,9 @@
"zod": "^4.0.0",
"typescript": "^5.8.3"
},
"scripts": {
"test": "bun test"
},
"publishConfig": {
"access": "public"
}
@@ -3,7 +3,7 @@ import * as z from "zod/v4";
import { collectCasRefs } from "../src/collect-cas-refs.js";
const phaseSchema = z.object({
hash: z.string().meta({ casRef: true }),
hash: z.string().meta({ 'x-cas-ref': true }),
title: z.string(),
});
@@ -19,9 +19,9 @@ const plannerMetaSchema = z.discriminatedUnion("status", [
]);
describe("collectCasRefs", () => {
test("1. flat field with casRef annotation", () => {
test("1. flat field with x-cas-ref annotation", () => {
const schema = z.object({
completedPhase: z.string().meta({ casRef: true }),
completedPhase: z.string().meta({ 'x-cas-ref': true }),
});
expect(collectCasRefs(schema, { completedPhase: "BHAAAAAAAAAAA" })).toEqual(["BHAAAAAAAAAAA"]);
});
@@ -29,7 +29,7 @@ describe("collectCasRefs", () => {
test("2. plain string without annotation is ignored", () => {
const schema = z.object({
summary: z.string(),
completedPhase: z.string().meta({ casRef: true }),
completedPhase: z.string().meta({ 'x-cas-ref': true }),
});
expect(
collectCasRefs(schema, {
@@ -76,8 +76,8 @@ describe("collectCasRefs", () => {
test("5. null and undefined annotated fields are skipped", () => {
const schema = z.object({
ref: z.string().meta({ casRef: true }).nullable(),
optionalRef: z.string().meta({ casRef: true }).optional(),
ref: z.string().meta({ 'x-cas-ref': true }).nullable(),
optionalRef: z.string().meta({ 'x-cas-ref': true }).optional(),
});
expect(collectCasRefs(schema, { ref: null, optionalRef: undefined })).toEqual([]);
expect(collectCasRefs(schema, { ref: "BH55555555555", optionalRef: undefined })).toEqual([
@@ -89,7 +89,7 @@ describe("collectCasRefs", () => {
const schema = z.object({
label: z.string(),
phase: z.object({
hash: z.string().meta({ casRef: true }),
hash: z.string().meta({ 'x-cas-ref': true }),
title: z.string(),
}),
tags: z.array(z.string()),
@@ -6,7 +6,7 @@ type DefPipeIn = { in: ZodSchema };
function hasCasRef(schema: ZodSchema): boolean {
const meta = z.globalRegistry.get(schema);
return meta !== undefined && meta.casRef === true;
return meta !== undefined && meta["x-cas-ref"] === true;
}
function walkOptional(schema: z.ZodOptional<ZodSchema>, data: unknown): string[] {
@@ -116,7 +116,7 @@ function walkCasRefs(schema: ZodSchema, data: unknown): string[] {
}
}
/** Collect CAS content hashes from meta using `casRef` annotations on the Zod schema. */
/** Collect CAS content hashes from meta using `x-cas-ref` annotations on the Zod schema. */
export function collectCasRefs(schema: ZodSchema, data: unknown): string[] {
return walkCasRefs(schema, data);
}