doc.haus Atlas Legal-document workflows on OpenCode, bound to their code
219 documents

Jurisdiction-pack anatomy

What a jurisdiction pack is, how the loader validates it, and what one pack actually tells the model — read in pack order from registry to prompt text to loading code.

dochaus/lib/jurisdiction.ts76 lines · loadJurisdiction L36–49
Outline 8 symbols
1import { existsSync, readFileSync } from "node:fs"
2import path from "node:path"
3
4// A jurisdiction pack is a config-only bundle under dochaus/jurisdiction/<code>/:
5// profile.json — machine fields (code, name, citationStyle, preferredModel)
6// prompt.md — the system-prompt fragment injected for matters in this
7// jurisdiction (citation conventions, statutory framing, which
8// authority is binding versus persuasive).
9// Packs are swappable: a contributor adds a jurisdiction by dropping a new
10// directory here, with no code change (issue #18). The loader resolves packs
11// relative to this file so it works regardless of the engine's process cwd.
12
13export type JurisdictionProfile = {
14 code: string
15 name: string
16 citationStyle: string
17 // The model a matter in this jurisdiction should run on once sovereign routing
18 // (issues #11/#15) lands. Declarative today: it is recorded but not yet
19 // enforced — there is only one (global) inference region, so nothing routes on
20 // it. Kept here so the routing layer has the per-jurisdiction preference ready.
21 preferredModel?: string | null
22}
23
24export type JurisdictionPack = JurisdictionProfile & { prompt: string }
25
26const ROOT = path.join(import.meta.dir, "..", "jurisdiction")
27
28// A pack code becomes a directory name joined to a filesystem path. It arrives
29// from a matter's matter.json, so reject anything that could climb out of the
30// jurisdiction directory before it reaches the path.
31function packDir(code: string) {
32 if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(code)) return undefined
33 return path.join(ROOT, code)
34}
35
36export async function loadJurisdiction(code: string): Promise<JurisdictionPack | undefined> {
37 const dir = packDir(code)
38 const profile = readJurisdictionProfile(code)
39 if (!dir || !profile) return undefined
40 const promptPath = path.join(dir, "prompt.md")
41 if (!existsSync(promptPath)) {
42 // A pack without a prompt fragment still loads (profile fields keep working),
43 // but the matter loses the pack's reasoning/citation steering — make that
44 // visible instead of silently injecting nothing.
45 console.warn(`[jurisdiction] pack "${profile.code}" has no prompt.md (${promptPath}); injecting profile only`)
46 return { ...profile, prompt: "" }
47 }
48 return { ...profile, prompt: await Bun.file(promptPath).text() }
49}
50
51export function readMatterJurisdictionCodes(directory: string) {
52 return readMatterJurisdictions(directory)
53 .filter((code, index, codes) => codes.indexOf(code) === index)
54 .flatMap((code) => readJurisdictionProfile(code) ? [code] : [])
55}
56
57// The jurisdiction codes a matter carries, read from its matter.json. A matter
58// can span several (a cross-border deal), so this is a list. The ingest service
59// owns that file; the engine only reads it to steer reasoning.
60export function readMatterJurisdictions(directory: string) {
61 const file = path.join(directory, "matter.json")
62 if (!existsSync(file)) return []
63 return (JSON.parse(readFileSync(file, "utf8")) as { jurisdictions?: string[] }).jurisdictions ?? []
64}
65
66function readJurisdictionProfile(code: string): JurisdictionProfile | undefined {
67 const dir = packDir(code)
68 if (!dir || !existsSync(path.join(dir, "profile.json"))) return undefined
69 const profile = JSON.parse(readFileSync(path.join(dir, "profile.json"), "utf8")) as JurisdictionProfile
70 if (profile.code !== code) {
71 console.warn(`[jurisdiction] pack directory "${code}" declares profile code "${profile.code}"; ignoring it`)
72 return undefined
73 }
74 return profile
75}
76