From f97cbac8a44f449f9947b80b0e61915a131ce522 Mon Sep 17 00:00:00 2001 From: Amal Date: Sun, 5 Jul 2026 09:13:29 -0700 Subject: [PATCH 1/2] feat(api): carry language + jurisdictions in the .mikeworkflow.json interchange format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rebased onto the upstream workflow-metadata sync: main already persists and normalizes language/jurisdictions on create/update (via the metadata envelope), ships the modal fields, metadata bar, scope tabs, Tiptap tables and the DB migration — those parts of this branch were dropped as superseded. What remains is the genuinely additive slice: the .mikeworkflow.json interchange format now carries the two metadata fields. - workflowFormat.ts (the SINGLE zod source): two new optional fields, language and jurisdictions, plus updated examples. The published schemas/workflow.schema.json is regenerated from it (via `npm run generate:workflow-schema`) — never hand-edited; the drift test (workflowFormat.test.ts) guards the pair. - exportWorkflow selects and emits the columns. - importWorkflow normalizes them with main's existing normalizeOptionalString / normalizeJurisdictions helpers, falling back to the 'English' / ['General'] defaults so files predating the fields still import onto the same baseline the UI shows. Co-Authored-By: Claude Opus 4.8 (1M context) Co-Authored-By: Claude Fable 5 --- .../src/modules/workflows/workflowFormat.ts | 18 +++++++++ .../modules/workflows/workflows.service.ts | 12 +++++- schemas/workflow.schema.json | 37 ++++++++++++++++++- 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/apps/api/src/modules/workflows/workflowFormat.ts b/apps/api/src/modules/workflows/workflowFormat.ts index 68d0e774a..63c737eae 100644 --- a/apps/api/src/modules/workflows/workflowFormat.ts +++ b/apps/api/src/modules/workflows/workflowFormat.ts @@ -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.", + ), }), }); @@ -122,6 +136,8 @@ export function buildWorkflowPackJsonSchema(): Record { "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"], }, }, { @@ -157,6 +173,8 @@ export function buildWorkflowPackJsonSchema(): Record { }, ], practice: "corporate", + language: "English", + jurisdictions: ["England and Wales"], }, }, ], diff --git a/apps/api/src/modules/workflows/workflows.service.ts b/apps/api/src/modules/workflows/workflows.service.ts index e1ce525fe..be8ed798a 100644 --- a/apps/api/src/modules/workflows/workflows.service.ts +++ b/apps/api/src/modules/workflows/workflows.service.ts @@ -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(); @@ -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, }, }; @@ -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(); diff --git a/schemas/workflow.schema.json b/schemas/workflow.schema.json index ed21ec6fb..ff927f88c 100644 --- a/schemas/workflow.schema.json +++ b/schemas/workflow.schema.json @@ -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": [ @@ -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" + ] } }, { @@ -148,7 +177,11 @@ "type": "flag" } ], - "practice": "corporate" + "practice": "corporate", + "language": "English", + "jurisdictions": [ + "England and Wales" + ] } } ] From 118c8b1de3667c11fb4ba9e29d4e5e2265fe085f Mon Sep 17 00:00:00 2001 From: Amal Date: Sun, 12 Jul 2026 13:15:23 -0700 Subject: [PATCH 2/2] test(workflows): cover metadata interchange and legacy defaults --- .../modules/workflows/workflowFormat.test.ts | 34 ++++++++++ .../workflows.import-metadata.test.ts | 67 +++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 apps/api/src/modules/workflows/workflows.import-metadata.test.ts diff --git a/apps/api/src/modules/workflows/workflowFormat.test.ts b/apps/api/src/modules/workflows/workflowFormat.test.ts index 565a02a1a..3ca1008bb 100644 --- a/apps/api/src/modules/workflows/workflowFormat.test.ts +++ b/apps/api/src/modules/workflows/workflowFormat.test.ts @@ -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, diff --git a/apps/api/src/modules/workflows/workflows.import-metadata.test.ts b/apps/api/src/modules/workflows/workflows.import-metadata.test.ts new file mode 100644 index 000000000..a86142f73 --- /dev/null +++ b/apps/api/src/modules/workflows/workflows.import-metadata.test.ts @@ -0,0 +1,67 @@ +import { describe, expect, it } from "vitest"; +import { importWorkflow } from "./workflows.service"; + +function database() { + const inserts: Record[] = []; + const db = { + from(table: string) { + expect(table).toBe("workflows"); + return { + insert(row: Record) { + inserts.push(row); + return { + select() { + return { + single: async () => ({ + data: { id: "wf-1", ...row }, + error: null, + }), + }; + }, + }; + }, + }; + }, + }; + return { db: db as Parameters[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"], + }); + }); +});