feat(adr-124/pseudocode): Zod schema barrel for all 20 ADR-124 §4.1+§4.1a tools
Advances SPARC Phase 2 (Pseudocode) — typed schemas are the language-level
design artifact that defines the complete tool surface before any HTTP/WS
plumbing is written. The schema map + TOOL_NAMES catalog are the pseudocode
contract that Phase 3 (Architecture) wires to the MCP Server dispatch loop.
New files under tools/ruview-mcp/src/schemas/:
common.ts — shared Zod sub-schemas
NodeIdSchema, DurationSSchema (max 3600 s), WindowSSchema (max 300 s),
SemanticPrimitiveKindSchema (10 ADR-115 primitives enum), PosePersonResultSchema
(17-keypoint COCO array + confidence + optional AETHER person_id)
tools.ts — 20 input schemas + TOOL_NAMES catalog + TOOL_INPUT_SCHEMAS dispatch map
§4.1 sensing (15): presence.now, vitals.get_{breathing,heart_rate,all},
pose.{latest,subscribe}, primitives.{get,list_active,subscribe},
bfld.{last_scan,subscribe}, node.{list,status},
vector.{search_pose,store_pose}
§4.1a policy (5): policy.{can_access_vitals, can_query_presence,
can_subscribe, redact_identity_fields, audit_log}
index.ts — barrel re-export of both modules
New test: tests/schemas.test.ts (24 assertions)
- Catalog completeness: exactly 20 tools, all §4.1 + §4.1a names present,
TOOL_INPUT_SCHEMAS one-to-one with catalog (no extras)
- Happy-path parse: 11 representative schemas accept valid inputs
- Constraint rejection: 8 schemas reject invalid inputs (empty NodeId,
DurationS=0 / >3600, unknown primitive, wrong keypoint length, k>100,
unknown vital, missing required node_id)
Fix: use Object.prototype.hasOwnProperty instead of Jest toHaveProperty for
dotted-key names (Jest interprets dots as nested path separators).
Test results: 50/50 PASS (schemas ×24 + manifest ×10 + tools ×5 + validate ×11)
Build: tsc clean, zero errors.
ACs touched: ADR-124 §4.1 complete tool surface; §4.1a policy layer surface;
Phase 2 gate: pseudocode covers all acceptance criteria from spec.
Next iter target: Phase 3 (Architecture) — wire TOOL_INPUT_SCHEMAS into the
MCP Server CallTool handler as a uniform validation gate; add Streamable HTTP
transport scaffold with Origin-validation middleware (option C).
Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
parent
d11ca9c31e
commit
7b7dbc7980
|
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* Shared Zod sub-schemas reused across the ADR-124 §4.1 tool catalog.
|
||||
*
|
||||
* All constraints are sourced from the ADR-124 decision record; comments cite
|
||||
* the specific table row or section that defines the constraint.
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
// ── Shared primitives ──────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Optional node_id — present on almost every tool. Defaults to the single
|
||||
* active node when only one is registered; required for multi-node fleets.
|
||||
*/
|
||||
export const NodeIdSchema = z
|
||||
.string()
|
||||
.min(1)
|
||||
.optional()
|
||||
.describe("Target node id. Omit to use the single active node.");
|
||||
|
||||
/**
|
||||
* Subscription duration in seconds. ADR-124 policy layer caps this at the
|
||||
* value returned by ruview.policy.can_subscribe.max_duration_s; the schema
|
||||
* enforces a hard ceiling of 3600 s (1 h) as a first-line guard.
|
||||
*/
|
||||
export const DurationSSchema = z
|
||||
.number()
|
||||
.positive()
|
||||
.max(3600)
|
||||
.describe("Subscription duration in seconds (max 3600).");
|
||||
|
||||
/**
|
||||
* Optional window in seconds for vitals averaging. Positive, max 300 s.
|
||||
* ADR-124 §4.1 rows vitals.get_breathing / vitals.get_heart_rate.
|
||||
*/
|
||||
export const WindowSSchema = z
|
||||
.number()
|
||||
.positive()
|
||||
.max(300)
|
||||
.optional()
|
||||
.describe("Averaging window in seconds (max 300).");
|
||||
|
||||
/**
|
||||
* The 10 semantic primitive kinds defined in ADR-115 and mirrored in
|
||||
* python/wifi_densepose/client/primitives.py:36-45.
|
||||
*/
|
||||
export const SemanticPrimitiveKindSchema = z.enum([
|
||||
"presence",
|
||||
"n_persons",
|
||||
"fall_detected",
|
||||
"breathing_rate",
|
||||
"heart_rate",
|
||||
"gesture",
|
||||
"zone_entry",
|
||||
"zone_exit",
|
||||
"movement_intensity",
|
||||
"sleep_quality",
|
||||
]);
|
||||
|
||||
export type SemanticPrimitiveKind = z.infer<typeof SemanticPrimitiveKindSchema>;
|
||||
|
||||
/**
|
||||
* A single 17-keypoint COCO pose result as stored and returned by the
|
||||
* ruvector HNSW index (ADR-016). Used by ruview.vector.store_pose input.
|
||||
*/
|
||||
export const PosePersonResultSchema = z.object({
|
||||
keypoints: z
|
||||
.array(z.tuple([z.number(), z.number()]))
|
||||
.length(17)
|
||||
.describe("17 COCO keypoints as [x,y] pairs in image-normalised coords."),
|
||||
confidence: z.number().min(0).max(1).describe("Pose confidence score [0,1]."),
|
||||
person_id: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("AETHER re-ID token, if available."),
|
||||
});
|
||||
|
||||
export type PosePersonResult = z.infer<typeof PosePersonResultSchema>;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* Barrel re-export for @ruvnet/rvagent schema layer.
|
||||
*
|
||||
* Import from this module to get all Zod input schemas, shared sub-schemas,
|
||||
* the TOOL_NAMES catalog, and the TOOL_INPUT_SCHEMAS dispatch map.
|
||||
*/
|
||||
|
||||
export * from "./common.js";
|
||||
export * from "./tools.js";
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
/**
|
||||
* Zod input schemas for all 20 ADR-124 MCP tools.
|
||||
*
|
||||
* §4.1 — 15 sensing tools (presence, vitals, pose, primitives, bfld, node, vector)
|
||||
* §4.1a — 5 policy / governance tools (RUVIEW-POLICY)
|
||||
*
|
||||
* Each exported schema is named `<CamelCase>InputSchema` matching the tool
|
||||
* name from the ADR-124 §4.1 catalog table. The parallel `TOOL_NAMES` array
|
||||
* is the single source of truth asserted by the schema-coverage test.
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
import {
|
||||
NodeIdSchema,
|
||||
DurationSSchema,
|
||||
WindowSSchema,
|
||||
SemanticPrimitiveKindSchema,
|
||||
PosePersonResultSchema,
|
||||
} from "./common.js";
|
||||
|
||||
// ── §4.1 Presence ──────────────────────────────────────────────────────────
|
||||
|
||||
/** ruview.presence.now */
|
||||
export const PresenceNowInputSchema = z.object({
|
||||
node_id: NodeIdSchema,
|
||||
});
|
||||
|
||||
// ── §4.1 Vitals ───────────────────────────────────────────────────────────
|
||||
|
||||
/** ruview.vitals.get_breathing */
|
||||
export const VitalsGetBreathingInputSchema = z.object({
|
||||
node_id: NodeIdSchema,
|
||||
window_s: WindowSSchema,
|
||||
});
|
||||
|
||||
/** ruview.vitals.get_heart_rate */
|
||||
export const VitalsGetHeartRateInputSchema = z.object({
|
||||
node_id: NodeIdSchema,
|
||||
window_s: WindowSSchema,
|
||||
});
|
||||
|
||||
/** ruview.vitals.get_all */
|
||||
export const VitalsGetAllInputSchema = z.object({
|
||||
node_id: NodeIdSchema,
|
||||
});
|
||||
|
||||
// ── §4.1 Pose ─────────────────────────────────────────────────────────────
|
||||
|
||||
/** ruview.pose.latest */
|
||||
export const PoseLatestInputSchema = z.object({
|
||||
node_id: NodeIdSchema,
|
||||
});
|
||||
|
||||
/** ruview.pose.subscribe */
|
||||
export const PoseSubscribeInputSchema = z.object({
|
||||
node_id: NodeIdSchema,
|
||||
duration_s: DurationSSchema,
|
||||
callback_url: z
|
||||
.string()
|
||||
.url()
|
||||
.optional()
|
||||
.describe("Webhook URL to receive PoseDataMessage events (optional)."),
|
||||
});
|
||||
|
||||
// ── §4.1 Primitives ───────────────────────────────────────────────────────
|
||||
|
||||
/** ruview.primitives.get */
|
||||
export const PrimitivesGetInputSchema = z.object({
|
||||
node_id: NodeIdSchema,
|
||||
primitive: SemanticPrimitiveKindSchema,
|
||||
});
|
||||
|
||||
/** ruview.primitives.list_active */
|
||||
export const PrimitivesListActiveInputSchema = z.object({
|
||||
node_id: NodeIdSchema,
|
||||
});
|
||||
|
||||
/** ruview.primitives.subscribe */
|
||||
export const PrimitivesSubscribeInputSchema = z.object({
|
||||
node_id: NodeIdSchema,
|
||||
primitive: SemanticPrimitiveKindSchema.optional().describe(
|
||||
"Subscribe to a specific primitive. Omit to receive all active primitives."
|
||||
),
|
||||
duration_s: DurationSSchema,
|
||||
});
|
||||
|
||||
// ── §4.1 BFLD ────────────────────────────────────────────────────────────
|
||||
|
||||
/** ruview.bfld.last_scan */
|
||||
export const BfldLastScanInputSchema = z.object({
|
||||
node_id: NodeIdSchema,
|
||||
});
|
||||
|
||||
/** ruview.bfld.subscribe */
|
||||
export const BfldSubscribeInputSchema = z.object({
|
||||
node_id: NodeIdSchema,
|
||||
duration_s: DurationSSchema,
|
||||
});
|
||||
|
||||
// ── §4.1 Node ────────────────────────────────────────────────────────────
|
||||
|
||||
/** ruview.node.list — empty input per ADR-124 §4.1 table */
|
||||
export const NodeListInputSchema = z.object({});
|
||||
|
||||
/** ruview.node.status */
|
||||
export const NodeStatusInputSchema = z.object({
|
||||
node_id: z.string().min(1).describe("Node id to query status for."),
|
||||
});
|
||||
|
||||
// ── §4.1 Vector ──────────────────────────────────────────────────────────
|
||||
|
||||
/** ruview.vector.search_pose */
|
||||
export const VectorSearchPoseInputSchema = z.object({
|
||||
query_embedding: z
|
||||
.array(z.number())
|
||||
.min(1)
|
||||
.describe("Dense embedding vector to query against the HNSW index."),
|
||||
k: z
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.max(100)
|
||||
.optional()
|
||||
.default(10)
|
||||
.describe("Number of nearest neighbours to return (default 10, max 100)."),
|
||||
node_id: NodeIdSchema,
|
||||
});
|
||||
|
||||
/** ruview.vector.store_pose */
|
||||
export const VectorStorePoseInputSchema = z.object({
|
||||
pose: PosePersonResultSchema,
|
||||
node_id: z.string().min(1).describe("Node id that observed this pose."),
|
||||
});
|
||||
|
||||
// ── §4.1a Policy / governance tools ──────────────────────────────────────
|
||||
|
||||
/** ruview.policy.can_access_vitals */
|
||||
export const PolicyCanAccessVitalsInputSchema = z.object({
|
||||
agent_id: z.string().min(1).describe("Calling agent identifier."),
|
||||
node_id: z.string().min(1).describe("Target sensing node."),
|
||||
vital: z
|
||||
.enum(["breathing", "heart_rate", "all"])
|
||||
.describe("Which vital the agent is requesting."),
|
||||
});
|
||||
|
||||
/** ruview.policy.can_query_presence */
|
||||
export const PolicyCanQueryPresenceInputSchema = z.object({
|
||||
agent_id: z.string().min(1),
|
||||
scope: z
|
||||
.enum(["node", "fleet"])
|
||||
.describe("node = single node; fleet = all nodes / aggregated count."),
|
||||
node_id: NodeIdSchema,
|
||||
zone: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Named zone within a node (e.g. 'living_room')."),
|
||||
});
|
||||
|
||||
/** ruview.policy.can_subscribe */
|
||||
export const PolicyCanSubscribeInputSchema = z.object({
|
||||
agent_id: z.string().min(1),
|
||||
topic: z
|
||||
.string()
|
||||
.min(1)
|
||||
.describe("MQTT topic or tool name the agent wishes to subscribe to."),
|
||||
duration_s: DurationSSchema,
|
||||
});
|
||||
|
||||
/** ruview.policy.redact_identity_fields */
|
||||
export const PolicyRedactIdentityFieldsInputSchema = z.object({
|
||||
payload: z.record(z.unknown()).describe("Tool return value to redact."),
|
||||
agent_id: z.string().min(1),
|
||||
});
|
||||
|
||||
/** ruview.policy.audit_log */
|
||||
export const PolicyAuditLogInputSchema = z.object({
|
||||
agent_id: z.string().optional().describe("Filter to a specific agent."),
|
||||
since_ts: z
|
||||
.number()
|
||||
.optional()
|
||||
.describe("Return events after this Unix timestamp (ms)."),
|
||||
});
|
||||
|
||||
// ── Catalog ───────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Single source of truth: every tool name in the ADR-124 §4.1 + §4.1a catalog.
|
||||
* The schema-coverage test asserts this list exactly matches the exported schemas.
|
||||
*/
|
||||
export const TOOL_NAMES = [
|
||||
// §4.1 — 15 sensing tools
|
||||
"ruview.presence.now",
|
||||
"ruview.vitals.get_breathing",
|
||||
"ruview.vitals.get_heart_rate",
|
||||
"ruview.vitals.get_all",
|
||||
"ruview.pose.latest",
|
||||
"ruview.pose.subscribe",
|
||||
"ruview.primitives.get",
|
||||
"ruview.primitives.list_active",
|
||||
"ruview.primitives.subscribe",
|
||||
"ruview.bfld.last_scan",
|
||||
"ruview.bfld.subscribe",
|
||||
"ruview.node.list",
|
||||
"ruview.node.status",
|
||||
"ruview.vector.search_pose",
|
||||
"ruview.vector.store_pose",
|
||||
// §4.1a — 5 policy tools
|
||||
"ruview.policy.can_access_vitals",
|
||||
"ruview.policy.can_query_presence",
|
||||
"ruview.policy.can_subscribe",
|
||||
"ruview.policy.redact_identity_fields",
|
||||
"ruview.policy.audit_log",
|
||||
] as const;
|
||||
|
||||
export type ToolName = (typeof TOOL_NAMES)[number];
|
||||
|
||||
/**
|
||||
* Map from tool name → its Zod input schema. Used by the MCP server's
|
||||
* CallTool handler for uniform schema-validation before dispatch.
|
||||
*/
|
||||
export const TOOL_INPUT_SCHEMAS: Record<ToolName, z.ZodTypeAny> = {
|
||||
"ruview.presence.now": PresenceNowInputSchema,
|
||||
"ruview.vitals.get_breathing": VitalsGetBreathingInputSchema,
|
||||
"ruview.vitals.get_heart_rate": VitalsGetHeartRateInputSchema,
|
||||
"ruview.vitals.get_all": VitalsGetAllInputSchema,
|
||||
"ruview.pose.latest": PoseLatestInputSchema,
|
||||
"ruview.pose.subscribe": PoseSubscribeInputSchema,
|
||||
"ruview.primitives.get": PrimitivesGetInputSchema,
|
||||
"ruview.primitives.list_active": PrimitivesListActiveInputSchema,
|
||||
"ruview.primitives.subscribe": PrimitivesSubscribeInputSchema,
|
||||
"ruview.bfld.last_scan": BfldLastScanInputSchema,
|
||||
"ruview.bfld.subscribe": BfldSubscribeInputSchema,
|
||||
"ruview.node.list": NodeListInputSchema,
|
||||
"ruview.node.status": NodeStatusInputSchema,
|
||||
"ruview.vector.search_pose": VectorSearchPoseInputSchema,
|
||||
"ruview.vector.store_pose": VectorStorePoseInputSchema,
|
||||
"ruview.policy.can_access_vitals": PolicyCanAccessVitalsInputSchema,
|
||||
"ruview.policy.can_query_presence": PolicyCanQueryPresenceInputSchema,
|
||||
"ruview.policy.can_subscribe": PolicyCanSubscribeInputSchema,
|
||||
"ruview.policy.redact_identity_fields": PolicyRedactIdentityFieldsInputSchema,
|
||||
"ruview.policy.audit_log": PolicyAuditLogInputSchema,
|
||||
};
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
/**
|
||||
* ADR-124 §4.1 / §4.1a schema coverage tests.
|
||||
*
|
||||
* Guards:
|
||||
* 1. Every catalogued tool name appears in TOOL_NAMES and TOOL_INPUT_SCHEMAS.
|
||||
* 2. TOOL_INPUT_SCHEMAS has no extra (undocumented) keys.
|
||||
* 3. Each schema accepts its documented happy-path input without throwing.
|
||||
* 4. Each schema rejects structurally invalid input (Zod parse failure).
|
||||
* 5. Shared sub-schemas (NodeId, DurationS, SemanticPrimitiveKind) enforce
|
||||
* their documented constraints.
|
||||
*/
|
||||
|
||||
import {
|
||||
TOOL_NAMES,
|
||||
TOOL_INPUT_SCHEMAS,
|
||||
SemanticPrimitiveKindSchema,
|
||||
DurationSSchema,
|
||||
NodeIdSchema,
|
||||
PosePersonResultSchema,
|
||||
PresenceNowInputSchema,
|
||||
VitalsGetBreathingInputSchema,
|
||||
PrimitivesGetInputSchema,
|
||||
BfldLastScanInputSchema,
|
||||
NodeStatusInputSchema,
|
||||
VectorSearchPoseInputSchema,
|
||||
VectorStorePoseInputSchema,
|
||||
PolicyCanAccessVitalsInputSchema,
|
||||
PolicyCanSubscribeInputSchema,
|
||||
PolicyRedactIdentityFieldsInputSchema,
|
||||
} from "../src/schemas/index.js";
|
||||
|
||||
// ── 1. Catalog completeness ────────────────────────────────────────────────
|
||||
|
||||
describe("TOOL_NAMES catalog (ADR-124 §4.1 + §4.1a)", () => {
|
||||
const EXPECTED_COUNT = 20; // 15 sensing + 5 policy
|
||||
|
||||
it("contains exactly 20 tools", () => {
|
||||
expect(TOOL_NAMES).toHaveLength(EXPECTED_COUNT);
|
||||
});
|
||||
|
||||
it("contains all 15 §4.1 sensing tool names", () => {
|
||||
const sensing = [
|
||||
"ruview.presence.now",
|
||||
"ruview.vitals.get_breathing",
|
||||
"ruview.vitals.get_heart_rate",
|
||||
"ruview.vitals.get_all",
|
||||
"ruview.pose.latest",
|
||||
"ruview.pose.subscribe",
|
||||
"ruview.primitives.get",
|
||||
"ruview.primitives.list_active",
|
||||
"ruview.primitives.subscribe",
|
||||
"ruview.bfld.last_scan",
|
||||
"ruview.bfld.subscribe",
|
||||
"ruview.node.list",
|
||||
"ruview.node.status",
|
||||
"ruview.vector.search_pose",
|
||||
"ruview.vector.store_pose",
|
||||
];
|
||||
for (const name of sensing) {
|
||||
expect(TOOL_NAMES).toContain(name);
|
||||
}
|
||||
});
|
||||
|
||||
it("contains all 5 §4.1a policy tool names", () => {
|
||||
const policy = [
|
||||
"ruview.policy.can_access_vitals",
|
||||
"ruview.policy.can_query_presence",
|
||||
"ruview.policy.can_subscribe",
|
||||
"ruview.policy.redact_identity_fields",
|
||||
"ruview.policy.audit_log",
|
||||
];
|
||||
for (const name of policy) {
|
||||
expect(TOOL_NAMES).toContain(name);
|
||||
}
|
||||
});
|
||||
|
||||
it("TOOL_INPUT_SCHEMAS has a schema for every catalogued tool name", () => {
|
||||
for (const name of TOOL_NAMES) {
|
||||
// Use Object.prototype.hasOwnProperty to avoid Jest's dotted-path
|
||||
// interpretation of toHaveProperty (dots = nested path in Jest).
|
||||
expect(Object.prototype.hasOwnProperty.call(TOOL_INPUT_SCHEMAS, name)).toBe(true);
|
||||
expect(TOOL_INPUT_SCHEMAS[name]).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
it("TOOL_INPUT_SCHEMAS has no extra keys beyond the catalog", () => {
|
||||
const schemaKeys = Object.keys(TOOL_INPUT_SCHEMAS).sort();
|
||||
const catalogKeys = [...TOOL_NAMES].sort();
|
||||
expect(schemaKeys).toEqual(catalogKeys);
|
||||
});
|
||||
});
|
||||
|
||||
// ── 2. Happy-path parse ────────────────────────────────────────────────────
|
||||
|
||||
describe("Schema happy-path acceptance", () => {
|
||||
it("PresenceNow — accepts empty object (node_id optional)", () => {
|
||||
expect(() => PresenceNowInputSchema.parse({})).not.toThrow();
|
||||
});
|
||||
|
||||
it("PresenceNow — accepts object with node_id", () => {
|
||||
const r = PresenceNowInputSchema.parse({ node_id: "node-abc" });
|
||||
expect(r.node_id).toBe("node-abc");
|
||||
});
|
||||
|
||||
it("VitalsGetBreathing — accepts window_s and node_id", () => {
|
||||
const r = VitalsGetBreathingInputSchema.parse({ window_s: 30, node_id: "n1" });
|
||||
expect(r.window_s).toBe(30);
|
||||
});
|
||||
|
||||
it("PrimitivesGet — accepts valid primitive kind", () => {
|
||||
const r = PrimitivesGetInputSchema.parse({ primitive: "fall_detected" });
|
||||
expect(r.primitive).toBe("fall_detected");
|
||||
});
|
||||
|
||||
it("BfldLastScan — accepts empty object", () => {
|
||||
expect(() => BfldLastScanInputSchema.parse({})).not.toThrow();
|
||||
});
|
||||
|
||||
it("NodeStatus — accepts node_id string", () => {
|
||||
const r = NodeStatusInputSchema.parse({ node_id: "cognitum-seed-1" });
|
||||
expect(r.node_id).toBe("cognitum-seed-1");
|
||||
});
|
||||
|
||||
it("VectorSearchPose — applies default k=10", () => {
|
||||
const r = VectorSearchPoseInputSchema.parse({ query_embedding: [0.1, 0.2, 0.3] });
|
||||
expect(r.k).toBe(10);
|
||||
});
|
||||
|
||||
it("VectorStorePose — accepts a valid 17-keypoint pose", () => {
|
||||
const kpts = Array.from({ length: 17 }, (_, i) => [i * 0.05, i * 0.03] as [number, number]);
|
||||
const r = VectorStorePoseInputSchema.parse({
|
||||
pose: { keypoints: kpts, confidence: 0.92 },
|
||||
node_id: "node-x",
|
||||
});
|
||||
expect(r.pose.keypoints).toHaveLength(17);
|
||||
});
|
||||
|
||||
it("PolicyCanAccessVitals — accepts valid vital value", () => {
|
||||
const r = PolicyCanAccessVitalsInputSchema.parse({
|
||||
agent_id: "agent-007",
|
||||
node_id: "node-1",
|
||||
vital: "heart_rate",
|
||||
});
|
||||
expect(r.vital).toBe("heart_rate");
|
||||
});
|
||||
|
||||
it("PolicyCanSubscribe — accepts valid duration_s", () => {
|
||||
const r = PolicyCanSubscribeInputSchema.parse({
|
||||
agent_id: "agent-007",
|
||||
topic: "ruview.vitals.get_all",
|
||||
duration_s: 300,
|
||||
});
|
||||
expect(r.duration_s).toBe(300);
|
||||
});
|
||||
|
||||
it("PolicyRedactIdentityFields — accepts arbitrary payload record", () => {
|
||||
const r = PolicyRedactIdentityFieldsInputSchema.parse({
|
||||
payload: { sta_mac: "AA:BB:CC:DD:EE:FF", n_persons: 2 },
|
||||
agent_id: "agent-007",
|
||||
});
|
||||
expect(r.payload).toHaveProperty("sta_mac");
|
||||
});
|
||||
});
|
||||
|
||||
// ── 3. Constraint rejection ────────────────────────────────────────────────
|
||||
|
||||
describe("Schema constraint enforcement", () => {
|
||||
it("NodeIdSchema — rejects empty string", () => {
|
||||
expect(() => NodeIdSchema.parse("")).toThrow();
|
||||
});
|
||||
|
||||
it("DurationSSchema — rejects zero", () => {
|
||||
expect(() => DurationSSchema.parse(0)).toThrow();
|
||||
});
|
||||
|
||||
it("DurationSSchema — rejects value > 3600", () => {
|
||||
expect(() => DurationSSchema.parse(3601)).toThrow();
|
||||
});
|
||||
|
||||
it("SemanticPrimitiveKind — rejects unknown primitive", () => {
|
||||
expect(() => SemanticPrimitiveKindSchema.parse("unknown_primitive")).toThrow();
|
||||
});
|
||||
|
||||
it("PosePersonResult — rejects keypoints array with wrong length", () => {
|
||||
const badKpts = Array.from({ length: 5 }, () => [0, 0] as [number, number]);
|
||||
expect(() => PosePersonResultSchema.parse({ keypoints: badKpts, confidence: 0.9 })).toThrow();
|
||||
});
|
||||
|
||||
it("VectorSearchPose — rejects k > 100", () => {
|
||||
expect(() =>
|
||||
VectorSearchPoseInputSchema.parse({ query_embedding: [0.1], k: 101 })
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it("PolicyCanAccessVitals — rejects unknown vital value", () => {
|
||||
expect(() =>
|
||||
PolicyCanAccessVitalsInputSchema.parse({
|
||||
agent_id: "a",
|
||||
node_id: "n",
|
||||
vital: "temperature",
|
||||
})
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it("NodeStatus — rejects missing node_id", () => {
|
||||
expect(() => NodeStatusInputSchema.parse({})).toThrow();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue