From 9367a4141a25cce8ca8c2fdf7301300983552f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Mon, 1 Jun 2026 01:35:40 +0000 Subject: [PATCH] fix: CLI put @schema uses putSchema() for recursive validation (#82) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CLI 'ucas put @schema' now detects meta-schema target and routes through putSchema() which uses isValidSchema() — a proper recursive validator — instead of ajv against the meta-schema (which cannot express recursive constraints for nested property sub-schemas). This fixes the core issue where schemas with typed properties like {"type":"string","minLength":1} were rejected by the CLI path. Added CLI E2E test: put @schema with nested constraints (minLength, maximum, uniqueItems). 494 tests, 0 fail. Refs #82 --- packages/cli-json-cas/src/cli.test.ts | 33 +++++++++++++++++++++++++++ packages/cli-json-cas/src/index.ts | 18 +++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/packages/cli-json-cas/src/cli.test.ts b/packages/cli-json-cas/src/cli.test.ts index 7537b9d..9497564 100644 --- a/packages/cli-json-cas/src/cli.test.ts +++ b/packages/cli-json-cas/src/cli.test.ts @@ -224,6 +224,39 @@ describe("@ Alias Resolution - put", () => { expect(exitCode).not.toBe(0); expect(stderr.length).toBeGreaterThan(0); }); + + test("ucas put @schema with nested type constraints should succeed", async () => { + await runCliAlias("init"); + + const schemaFile = join(testDir, "constrained-schema.json"); + writeFileSync( + schemaFile, + JSON.stringify({ + type: "object", + properties: { + name: { type: "string", minLength: 1, maxLength: 50 }, + age: { type: "number", minimum: 0, maximum: 150 }, + tags: { + type: "array", + items: { type: "string" }, + minItems: 1, + uniqueItems: true, + }, + }, + required: ["name"], + }), + ); + + const { stdout, stderr, exitCode } = await runCliAlias( + "put", + "@schema", + schemaFile, + ); + + expect(exitCode).toBe(0); + expect(stderr).toBe(""); + expect(envValue(stdout)).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/); + }); }); describe("@ Alias Resolution - hash", () => { diff --git a/packages/cli-json-cas/src/index.ts b/packages/cli-json-cas/src/index.ts index 1c55fd4..f09abd6 100644 --- a/packages/cli-json-cas/src/index.ts +++ b/packages/cli-json-cas/src/index.ts @@ -13,6 +13,7 @@ import { getSchema, InvalidTagFormatError, InvalidVariableNameError, + putSchema, refs, renderAsync, renderDirect, @@ -187,6 +188,23 @@ async function cmdPut(args: string[]): Promise { const payload = readJsonFile(file); const store = await openStore(); + // Schema nodes: use putSchema() which validates via isValidSchema() (recursive) + // instead of ajv against meta-schema (which can't express recursive constraints) + const builtinSchemas = await bootstrap(store); + const metaHash = builtinSchemas["@schema"]; + if (typeHash === metaHash) { + try { + const hash = await putSchema(store, payload as Record); + out(await wrapEnvelope(store, "@output/put", hash)); + } catch (e) { + console.error( + `Validation failed: payload in ${file} does not match schema ${typeHash}`, + ); + process.exit(1); + } + return; + } + // Check if schema exists const schema = getSchema(store, typeHash); if (schema === null) { -- 2.43.0