Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions apps/api/src/modules/workflows/workflowFormat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,40 @@ describe("workflow pack schema — validation behavior", () => {
expect(workflowPackSchema.safeParse(validPack).success).toBe(true);
});

it("accepts and preserves language and jurisdiction metadata", () => {
const result = workflowPackSchema.safeParse({
formatVersion: 1,
workflow: {
title: "Cross-border NDA",
type: "assistant",
language: "French",
jurisdictions: ["France", "England and Wales"],
},
});

expect(result.success).toBe(true);
if (result.success) {
expect(result.data.workflow.language).toBe("French");
expect(result.data.workflow.jurisdictions).toEqual([
"France",
"England and Wales",
]);
}
});

it("rejects non-string jurisdiction entries", () => {
expect(
workflowPackSchema.safeParse({
formatVersion: 1,
workflow: {
title: "Invalid metadata",
type: "assistant",
jurisdictions: ["France", 42],
},
}).success,
).toBe(false);
});

it("rejects a wrong formatVersion", () => {
const result = workflowPackSchema.safeParse({
...validPack,
Expand Down
18 changes: 18 additions & 0 deletions apps/api/src/modules/workflows/workflowFormat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,20 @@ export const workflowPackSchema = z.strictObject({
.describe(
"Optional legal practice area tag (e.g. 'corporate', 'ip', 'employment'). Used for filtering in the workflow picker.",
),
language: z
.string()
.nullable()
.optional()
.describe(
"Optional drafting/analysis language (e.g. 'English', 'French'). Defaults to 'English' on import when omitted.",
),
jurisdictions: z
.array(z.string())
.nullable()
.optional()
.describe(
"Optional governing-law jurisdiction tags (e.g. ['England and Wales', 'Singapore']). Defaults to ['General'] on import when omitted.",
),
}),
});

Expand Down Expand Up @@ -122,6 +136,8 @@ export function buildWorkflowPackJsonSchema(): Record<string, unknown> {
"Review the provided NDA and identify:\n1. Key definitions and their scope\n2. Exclusions from confidential information\n3. Duration of confidentiality obligations\n4. Any unusual or unfair clauses\n\nProvide a structured summary with a risk rating (Low / Medium / High).",
columns_config: null,
practice: "corporate",
language: "English",
jurisdictions: ["General"],
},
},
{
Expand Down Expand Up @@ -157,6 +173,8 @@ export function buildWorkflowPackJsonSchema(): Record<string, unknown> {
},
],
practice: "corporate",
language: "English",
jurisdictions: ["England and Wales"],
},
},
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { describe, expect, it } from "vitest";
import { importWorkflow } from "./workflows.service";

function database() {
const inserts: Record<string, unknown>[] = [];
const db = {
from(table: string) {
expect(table).toBe("workflows");
return {
insert(row: Record<string, unknown>) {
inserts.push(row);
return {
select() {
return {
single: async () => ({
data: { id: "wf-1", ...row },
error: null,
}),
};
},
};
},
};
},
};
return { db: db as Parameters<typeof importWorkflow>[0], inserts };
}

describe("workflow import metadata", () => {
it("stores explicit language and jurisdictions", async () => {
const { db, inserts } = database();
const result = await importWorkflow(db, {
userId: "user-1",
body: {
formatVersion: 1,
workflow: {
title: "Cross-border review",
type: "assistant",
language: "French",
jurisdictions: ["France"],
},
},
});

expect(result.ok).toBe(true);
expect(inserts[0]).toMatchObject({
language: "French",
jurisdictions: ["France"],
});
});

it("applies stable defaults to legacy version-one packs", async () => {
const { db, inserts } = database();
await importWorkflow(db, {
userId: "user-1",
body: {
formatVersion: 1,
workflow: { title: "Legacy review", type: "assistant" },
},
});

expect(inserts[0]).toMatchObject({
language: "English",
jurisdictions: ["General"],
});
});
});
12 changes: 11 additions & 1 deletion apps/api/src/modules/workflows/workflows.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -862,7 +862,9 @@ export async function exportWorkflow(

const { data: wf } = await db
.from("workflows")
.select("title, type, prompt_md, columns_config, practice")
.select(
"title, type, prompt_md, columns_config, practice, language, jurisdictions",
)
.eq("id", workflowId)
.eq("user_id", userId)
.single();
Expand All @@ -878,6 +880,8 @@ export async function exportWorkflow(
prompt_md: wf.prompt_md ?? null,
columns_config: wf.columns_config ?? null,
practice: wf.practice ?? null,
language: wf.language ?? null,
jurisdictions: wf.jurisdictions ?? null,
},
};

Expand Down Expand Up @@ -927,6 +931,12 @@ export async function importWorkflow(
prompt_md: wf.prompt_md ?? null,
columns_config: wf.columns_config ?? null,
practice: wf.practice ?? null,
// Imported files may predate these fields — normalize to the defaults.
language:
normalizeOptionalString(wf.language) ?? DEFAULT_WORKFLOW_LANGUAGE,
jurisdictions:
normalizeJurisdictions(wf.jurisdictions) ??
DEFAULT_WORKFLOW_JURISDICTIONS,
})
.select("*")
.single();
Expand Down
37 changes: 35 additions & 2 deletions schemas/workflow.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,31 @@
"type": "null"
}
]
},
"language": {
"description": "Optional drafting/analysis language (e.g. 'English', 'French'). Defaults to 'English' on import when omitted.",
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"jurisdictions": {
"description": "Optional governing-law jurisdiction tags (e.g. ['England and Wales', 'Singapore']). Defaults to ['General'] on import when omitted.",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "null"
}
]
}
},
"required": [
Expand All @@ -116,7 +141,11 @@
"type": "assistant",
"prompt_md": "Review the provided NDA and identify:\n1. Key definitions and their scope\n2. Exclusions from confidential information\n3. Duration of confidentiality obligations\n4. Any unusual or unfair clauses\n\nProvide a structured summary with a risk rating (Low / Medium / High).",
"columns_config": null,
"practice": "corporate"
"practice": "corporate",
"language": "English",
"jurisdictions": [
"General"
]
}
},
{
Expand Down Expand Up @@ -148,7 +177,11 @@
"type": "flag"
}
],
"practice": "corporate"
"practice": "corporate",
"language": "English",
"jurisdictions": [
"England and Wales"
]
}
}
]
Expand Down
Loading