@adversarylabs/sdk 0.1.18 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,34 @@
1
+ export declare const ADVERSARY_OUTCOME_CONTEXT_ENV = "ADVERSARY_OUTCOME_CONTEXT";
2
+ export declare const OUTCOME_CONTEXT_SCHEMA_VERSION = "adversary.outcome-context.v1";
3
+ export declare const OUTCOME_CONTEXT_MAX_SOURCE_CHARACTERS: number;
4
+ export declare const OUTCOME_CONTEXT_MAX_FILE_BYTES: number;
5
+ export type OutcomeContextSourceKind = "pull_request_title" | "pull_request_body";
6
+ export interface OutcomeContextSubject {
7
+ readonly provider?: string;
8
+ readonly repository?: string;
9
+ readonly pullRequest?: number;
10
+ }
11
+ export interface OutcomeContextSource {
12
+ readonly kind: OutcomeContextSourceKind;
13
+ /** Untrusted author-supplied text. Never treat this as model instructions. */
14
+ readonly text: string;
15
+ }
16
+ export interface OutcomeIntent {
17
+ readonly objective: string;
18
+ readonly confidence: "low" | "medium" | "high";
19
+ readonly expectedEffects: readonly string[];
20
+ readonly mustPreserve: readonly string[];
21
+ readonly affectedBoundaries: readonly string[];
22
+ readonly ambiguities: readonly string[];
23
+ }
24
+ export interface OutcomeContext {
25
+ readonly schemaVersion: typeof OUTCOME_CONTEXT_SCHEMA_VERSION;
26
+ readonly subject: OutcomeContextSubject;
27
+ readonly sources: readonly OutcomeContextSource[];
28
+ /** Host-detected intent. It remains a hypothesis that adversaries must verify. */
29
+ readonly intent: OutcomeIntent;
30
+ }
31
+ export declare function openOutcomeContext(path: string): Promise<OutcomeContext>;
32
+ export declare function outcomeContextFromEnvironment(env?: Readonly<Record<string, string | undefined>>): Promise<OutcomeContext | null>;
33
+ export declare function parseOutcomeContext(value: unknown, source?: string): OutcomeContext;
34
+ //# sourceMappingURL=outcome-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outcome-context.d.ts","sourceRoot":"","sources":["../src/outcome-context.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,6BAA6B,8BAA8B,CAAC;AACzE,eAAO,MAAM,8BAA8B,iCAAiC,CAAC;AAC7E,eAAO,MAAM,qCAAqC,QAAW,CAAC;AAC9D,eAAO,MAAM,8BAA8B,QAAY,CAAC;AAMxD,MAAM,MAAM,wBAAwB,GAAG,oBAAoB,GAAG,mBAAmB,CAAC;AAElF,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAC;IACxC,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC/C,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/C,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,aAAa,EAAE,OAAO,8BAA8B,CAAC;IAC9D,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAC;IACxC,QAAQ,CAAC,OAAO,EAAE,SAAS,oBAAoB,EAAE,CAAC;IAClD,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;CAChC;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAM9E;AAED,wBAAsB,6BAA6B,CACjD,GAAG,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAe,GAC9D,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAGhC;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,SAAU,GAAG,cAAc,CAmEpF"}
@@ -0,0 +1,138 @@
1
+ import { readFile } from "node:fs/promises";
2
+ export const ADVERSARY_OUTCOME_CONTEXT_ENV = "ADVERSARY_OUTCOME_CONTEXT";
3
+ export const OUTCOME_CONTEXT_SCHEMA_VERSION = "adversary.outcome-context.v1";
4
+ export const OUTCOME_CONTEXT_MAX_SOURCE_CHARACTERS = 32 << 10;
5
+ export const OUTCOME_CONTEXT_MAX_FILE_BYTES = 384 << 10;
6
+ const MAX_PROVIDER_CHARACTERS = 100;
7
+ const MAX_REPOSITORY_CHARACTERS = 500;
8
+ const MAX_INTENT_TEXT_CHARACTERS = 500;
9
+ export async function openOutcomeContext(path) {
10
+ const raw = await readFile(path, "utf8");
11
+ if (Buffer.byteLength(raw) > OUTCOME_CONTEXT_MAX_FILE_BYTES) {
12
+ throw new Error(`Invalid outcome context at ${path}: file is too large.`);
13
+ }
14
+ return parseOutcomeContext(JSON.parse(raw), path);
15
+ }
16
+ export async function outcomeContextFromEnvironment(env = process.env) {
17
+ const path = env[ADVERSARY_OUTCOME_CONTEXT_ENV]?.trim();
18
+ return path ? openOutcomeContext(path) : null;
19
+ }
20
+ export function parseOutcomeContext(value, source = "value") {
21
+ if (!isRecord(value) || value.schema_version !== OUTCOME_CONTEXT_SCHEMA_VERSION) {
22
+ throw new Error(`Invalid outcome context at ${source}: schema_version must be ${OUTCOME_CONTEXT_SCHEMA_VERSION}.`);
23
+ }
24
+ assertKeys(value, ["schema_version", "subject", "sources", "intent"], source);
25
+ if (!isRecord(value.subject)) {
26
+ throw new Error(`Invalid outcome context at ${source}: subject must be an object.`);
27
+ }
28
+ assertKeys(value.subject, ["provider", "repository", "pull_request"], `${source}.subject`);
29
+ if (value.subject.provider !== undefined &&
30
+ (typeof value.subject.provider !== "string" ||
31
+ characterLength(value.subject.provider) > MAX_PROVIDER_CHARACTERS)) {
32
+ throw new Error(`Invalid outcome context at ${source}: subject.provider must be a string.`);
33
+ }
34
+ if (value.subject.repository !== undefined &&
35
+ (typeof value.subject.repository !== "string" ||
36
+ characterLength(value.subject.repository) > MAX_REPOSITORY_CHARACTERS)) {
37
+ throw new Error(`Invalid outcome context at ${source}: subject.repository must be a string.`);
38
+ }
39
+ if (!Array.isArray(value.sources) || value.sources.length === 0 || value.sources.length > 2) {
40
+ throw new Error(`Invalid outcome context at ${source}: sources must contain one or two items.`);
41
+ }
42
+ const sources = [];
43
+ const kinds = new Set();
44
+ for (const item of value.sources) {
45
+ if (!isRecord(item) || !isSourceKind(item.kind) || typeof item.text !== "string") {
46
+ throw new Error(`Invalid outcome context at ${source}: each source requires kind and text.`);
47
+ }
48
+ assertKeys(item, ["kind", "text"], `${source}.sources`);
49
+ if (item.text.trim() === "") {
50
+ throw new Error(`Invalid outcome context at ${source}: source text must not be empty.`);
51
+ }
52
+ if (characterLength(item.text) > OUTCOME_CONTEXT_MAX_SOURCE_CHARACTERS) {
53
+ throw new Error(`Invalid outcome context at ${source}: source text is too long.`);
54
+ }
55
+ if (kinds.has(item.kind)) {
56
+ throw new Error(`Invalid outcome context at ${source}: source kinds must be unique.`);
57
+ }
58
+ kinds.add(item.kind);
59
+ sources.push(Object.freeze({ kind: item.kind, text: item.text }));
60
+ }
61
+ const intent = parseIntent(value.intent, source);
62
+ const pullRequest = value.subject.pull_request;
63
+ if (pullRequest !== undefined &&
64
+ (!Number.isInteger(pullRequest) || pullRequest < 1)) {
65
+ throw new Error(`Invalid outcome context at ${source}: subject.pull_request must be positive.`);
66
+ }
67
+ return Object.freeze({
68
+ schemaVersion: OUTCOME_CONTEXT_SCHEMA_VERSION,
69
+ subject: Object.freeze({
70
+ ...(typeof value.subject.provider === "string" ? { provider: value.subject.provider } : {}),
71
+ ...(typeof value.subject.repository === "string"
72
+ ? { repository: value.subject.repository }
73
+ : {}),
74
+ ...(typeof pullRequest === "number" ? { pullRequest } : {}),
75
+ }),
76
+ sources: Object.freeze(sources),
77
+ intent,
78
+ });
79
+ }
80
+ function parseIntent(value, source) {
81
+ if (!isRecord(value)) {
82
+ throw new Error(`Invalid outcome context at ${source}: intent must be an object.`);
83
+ }
84
+ assertKeys(value, [
85
+ "objective",
86
+ "confidence",
87
+ "expected_effects",
88
+ "must_preserve",
89
+ "affected_boundaries",
90
+ "ambiguities",
91
+ ], `${source}.intent`);
92
+ if (typeof value.objective !== "string" ||
93
+ value.objective.trim() === "" ||
94
+ characterLength(value.objective) > MAX_INTENT_TEXT_CHARACTERS) {
95
+ throw new Error(`Invalid outcome context at ${source}: intent.objective must not be empty.`);
96
+ }
97
+ if (!isConfidence(value.confidence)) {
98
+ throw new Error(`Invalid outcome context at ${source}: intent.confidence is invalid.`);
99
+ }
100
+ return Object.freeze({
101
+ objective: value.objective,
102
+ confidence: value.confidence,
103
+ expectedEffects: parseStringList(value.expected_effects, source, "expected_effects"),
104
+ mustPreserve: parseStringList(value.must_preserve, source, "must_preserve"),
105
+ affectedBoundaries: parseStringList(value.affected_boundaries, source, "affected_boundaries"),
106
+ ambiguities: parseStringList(value.ambiguities, source, "ambiguities"),
107
+ });
108
+ }
109
+ function parseStringList(value, source, field) {
110
+ if (!Array.isArray(value) ||
111
+ value.length > 12 ||
112
+ value.some((item) => typeof item !== "string" ||
113
+ item.trim() === "" ||
114
+ characterLength(item) > MAX_INTENT_TEXT_CHARACTERS)) {
115
+ throw new Error(`Invalid outcome context at ${source}: intent.${field} must be a bounded string array.`);
116
+ }
117
+ return Object.freeze([...value]);
118
+ }
119
+ function characterLength(value) {
120
+ return [...value].length;
121
+ }
122
+ function isConfidence(value) {
123
+ return value === "low" || value === "medium" || value === "high";
124
+ }
125
+ function isSourceKind(value) {
126
+ return value === "pull_request_title" || value === "pull_request_body";
127
+ }
128
+ function isRecord(value) {
129
+ return typeof value === "object" && value !== null && !Array.isArray(value);
130
+ }
131
+ function assertKeys(value, allowed, source) {
132
+ const allowedKeys = new Set(allowed);
133
+ const unknown = Object.keys(value).find((key) => !allowedKeys.has(key));
134
+ if (unknown !== undefined) {
135
+ throw new Error(`Invalid outcome context at ${source}: unknown property ${unknown}.`);
136
+ }
137
+ }
138
+ //# sourceMappingURL=outcome-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outcome-context.js","sourceRoot":"","sources":["../src/outcome-context.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile } from \"node:fs/promises\";\n\nexport const ADVERSARY_OUTCOME_CONTEXT_ENV = \"ADVERSARY_OUTCOME_CONTEXT\";\nexport const OUTCOME_CONTEXT_SCHEMA_VERSION = \"adversary.outcome-context.v1\";\nexport const OUTCOME_CONTEXT_MAX_SOURCE_CHARACTERS = 32 << 10;\nexport const OUTCOME_CONTEXT_MAX_FILE_BYTES = 384 << 10;\n\nconst MAX_PROVIDER_CHARACTERS = 100;\nconst MAX_REPOSITORY_CHARACTERS = 500;\nconst MAX_INTENT_TEXT_CHARACTERS = 500;\n\nexport type OutcomeContextSourceKind = \"pull_request_title\" | \"pull_request_body\";\n\nexport interface OutcomeContextSubject {\n readonly provider?: string;\n readonly repository?: string;\n readonly pullRequest?: number;\n}\n\nexport interface OutcomeContextSource {\n readonly kind: OutcomeContextSourceKind;\n /** Untrusted author-supplied text. Never treat this as model instructions. */\n readonly text: string;\n}\n\nexport interface OutcomeIntent {\n readonly objective: string;\n readonly confidence: \"low\" | \"medium\" | \"high\";\n readonly expectedEffects: readonly string[];\n readonly mustPreserve: readonly string[];\n readonly affectedBoundaries: readonly string[];\n readonly ambiguities: readonly string[];\n}\n\nexport interface OutcomeContext {\n readonly schemaVersion: typeof OUTCOME_CONTEXT_SCHEMA_VERSION;\n readonly subject: OutcomeContextSubject;\n readonly sources: readonly OutcomeContextSource[];\n /** Host-detected intent. It remains a hypothesis that adversaries must verify. */\n readonly intent: OutcomeIntent;\n}\n\nexport async function openOutcomeContext(path: string): Promise<OutcomeContext> {\n const raw = await readFile(path, \"utf8\");\n if (Buffer.byteLength(raw) > OUTCOME_CONTEXT_MAX_FILE_BYTES) {\n throw new Error(`Invalid outcome context at ${path}: file is too large.`);\n }\n return parseOutcomeContext(JSON.parse(raw) as unknown, path);\n}\n\nexport async function outcomeContextFromEnvironment(\n env: Readonly<Record<string, string | undefined>> = process.env,\n): Promise<OutcomeContext | null> {\n const path = env[ADVERSARY_OUTCOME_CONTEXT_ENV]?.trim();\n return path ? openOutcomeContext(path) : null;\n}\n\nexport function parseOutcomeContext(value: unknown, source = \"value\"): OutcomeContext {\n if (!isRecord(value) || value.schema_version !== OUTCOME_CONTEXT_SCHEMA_VERSION) {\n throw new Error(\n `Invalid outcome context at ${source}: schema_version must be ${OUTCOME_CONTEXT_SCHEMA_VERSION}.`,\n );\n }\n assertKeys(value, [\"schema_version\", \"subject\", \"sources\", \"intent\"], source);\n if (!isRecord(value.subject)) {\n throw new Error(`Invalid outcome context at ${source}: subject must be an object.`);\n }\n assertKeys(value.subject, [\"provider\", \"repository\", \"pull_request\"], `${source}.subject`);\n if (\n value.subject.provider !== undefined &&\n (typeof value.subject.provider !== \"string\" ||\n characterLength(value.subject.provider) > MAX_PROVIDER_CHARACTERS)\n ) {\n throw new Error(`Invalid outcome context at ${source}: subject.provider must be a string.`);\n }\n if (\n value.subject.repository !== undefined &&\n (typeof value.subject.repository !== \"string\" ||\n characterLength(value.subject.repository) > MAX_REPOSITORY_CHARACTERS)\n ) {\n throw new Error(`Invalid outcome context at ${source}: subject.repository must be a string.`);\n }\n if (!Array.isArray(value.sources) || value.sources.length === 0 || value.sources.length > 2) {\n throw new Error(`Invalid outcome context at ${source}: sources must contain one or two items.`);\n }\n const sources: OutcomeContextSource[] = [];\n const kinds = new Set<OutcomeContextSourceKind>();\n for (const item of value.sources) {\n if (!isRecord(item) || !isSourceKind(item.kind) || typeof item.text !== \"string\") {\n throw new Error(`Invalid outcome context at ${source}: each source requires kind and text.`);\n }\n assertKeys(item, [\"kind\", \"text\"], `${source}.sources`);\n if (item.text.trim() === \"\") {\n throw new Error(`Invalid outcome context at ${source}: source text must not be empty.`);\n }\n if (characterLength(item.text) > OUTCOME_CONTEXT_MAX_SOURCE_CHARACTERS) {\n throw new Error(`Invalid outcome context at ${source}: source text is too long.`);\n }\n if (kinds.has(item.kind)) {\n throw new Error(`Invalid outcome context at ${source}: source kinds must be unique.`);\n }\n kinds.add(item.kind);\n sources.push(Object.freeze({ kind: item.kind, text: item.text }));\n }\n const intent = parseIntent(value.intent, source);\n const pullRequest = value.subject.pull_request;\n if (\n pullRequest !== undefined &&\n (!Number.isInteger(pullRequest) || (pullRequest as number) < 1)\n ) {\n throw new Error(`Invalid outcome context at ${source}: subject.pull_request must be positive.`);\n }\n return Object.freeze({\n schemaVersion: OUTCOME_CONTEXT_SCHEMA_VERSION,\n subject: Object.freeze({\n ...(typeof value.subject.provider === \"string\" ? { provider: value.subject.provider } : {}),\n ...(typeof value.subject.repository === \"string\"\n ? { repository: value.subject.repository }\n : {}),\n ...(typeof pullRequest === \"number\" ? { pullRequest } : {}),\n }),\n sources: Object.freeze(sources),\n intent,\n });\n}\n\nfunction parseIntent(value: unknown, source: string): OutcomeIntent {\n if (!isRecord(value)) {\n throw new Error(`Invalid outcome context at ${source}: intent must be an object.`);\n }\n assertKeys(\n value,\n [\n \"objective\",\n \"confidence\",\n \"expected_effects\",\n \"must_preserve\",\n \"affected_boundaries\",\n \"ambiguities\",\n ],\n `${source}.intent`,\n );\n if (\n typeof value.objective !== \"string\" ||\n value.objective.trim() === \"\" ||\n characterLength(value.objective) > MAX_INTENT_TEXT_CHARACTERS\n ) {\n throw new Error(`Invalid outcome context at ${source}: intent.objective must not be empty.`);\n }\n if (!isConfidence(value.confidence)) {\n throw new Error(`Invalid outcome context at ${source}: intent.confidence is invalid.`);\n }\n return Object.freeze({\n objective: value.objective,\n confidence: value.confidence,\n expectedEffects: parseStringList(value.expected_effects, source, \"expected_effects\"),\n mustPreserve: parseStringList(value.must_preserve, source, \"must_preserve\"),\n affectedBoundaries: parseStringList(value.affected_boundaries, source, \"affected_boundaries\"),\n ambiguities: parseStringList(value.ambiguities, source, \"ambiguities\"),\n });\n}\n\nfunction parseStringList(value: unknown, source: string, field: string): readonly string[] {\n if (\n !Array.isArray(value) ||\n value.length > 12 ||\n value.some(\n (item) =>\n typeof item !== \"string\" ||\n item.trim() === \"\" ||\n characterLength(item) > MAX_INTENT_TEXT_CHARACTERS,\n )\n ) {\n throw new Error(\n `Invalid outcome context at ${source}: intent.${field} must be a bounded string array.`,\n );\n }\n return Object.freeze([...value]);\n}\n\nfunction characterLength(value: string): number {\n return [...value].length;\n}\n\nfunction isConfidence(value: unknown): value is OutcomeIntent[\"confidence\"] {\n return value === \"low\" || value === \"medium\" || value === \"high\";\n}\n\nfunction isSourceKind(value: unknown): value is OutcomeContextSourceKind {\n return value === \"pull_request_title\" || value === \"pull_request_body\";\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction assertKeys(\n value: Record<string, unknown>,\n allowed: readonly string[],\n source: string,\n): void {\n const allowedKeys = new Set(allowed);\n const unknown = Object.keys(value).find((key) => !allowedKeys.has(key));\n if (unknown !== undefined) {\n throw new Error(`Invalid outcome context at ${source}: unknown property ${unknown}.`);\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adversarylabs/sdk",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "Small TypeScript SDK for authoring Adversaries.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -24,7 +24,8 @@
24
24
  },
25
25
  "./schemas/adversary.manifest.v1": "./schemas/adversary.manifest.v1.schema.json",
26
26
  "./schemas/adversary.input.v1": "./schemas/adversary.input.v1.schema.json",
27
- "./schemas/adversary.review.v1": "./schemas/adversary.review.v1.schema.json"
27
+ "./schemas/adversary.review.v1": "./schemas/adversary.review.v1.schema.json",
28
+ "./schemas/adversary.outcome-context.v1": "./schemas/adversary.outcome-context.v1.schema.json"
28
29
  },
29
30
  "files": ["dist", "schemas", "templates", "README.md"],
30
31
  "engines": {
@@ -0,0 +1,61 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adversarylabs.ai/schemas/adversary.outcome-context.v1.schema.json",
4
+ "title": "Adversary outcome context v1",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schema_version", "subject", "sources", "intent"],
8
+ "properties": {
9
+ "schema_version": { "const": "adversary.outcome-context.v1" },
10
+ "subject": {
11
+ "type": "object",
12
+ "additionalProperties": false,
13
+ "properties": {
14
+ "provider": { "type": "string", "maxLength": 100 },
15
+ "repository": { "type": "string", "maxLength": 500 },
16
+ "pull_request": { "type": "integer", "minimum": 1 }
17
+ }
18
+ },
19
+ "sources": {
20
+ "type": "array",
21
+ "minItems": 1,
22
+ "maxItems": 2,
23
+ "items": {
24
+ "type": "object",
25
+ "additionalProperties": false,
26
+ "required": ["kind", "text"],
27
+ "properties": {
28
+ "kind": { "enum": ["pull_request_title", "pull_request_body"] },
29
+ "text": { "type": "string", "minLength": 1, "maxLength": 32768 }
30
+ }
31
+ }
32
+ },
33
+ "intent": {
34
+ "type": "object",
35
+ "additionalProperties": false,
36
+ "required": [
37
+ "objective",
38
+ "confidence",
39
+ "expected_effects",
40
+ "must_preserve",
41
+ "affected_boundaries",
42
+ "ambiguities"
43
+ ],
44
+ "properties": {
45
+ "objective": { "type": "string", "minLength": 1, "maxLength": 500 },
46
+ "confidence": { "enum": ["low", "medium", "high"] },
47
+ "expected_effects": { "$ref": "#/$defs/stringList" },
48
+ "must_preserve": { "$ref": "#/$defs/stringList" },
49
+ "affected_boundaries": { "$ref": "#/$defs/stringList" },
50
+ "ambiguities": { "$ref": "#/$defs/stringList" }
51
+ }
52
+ }
53
+ },
54
+ "$defs": {
55
+ "stringList": {
56
+ "type": "array",
57
+ "maxItems": 12,
58
+ "items": { "type": "string", "minLength": 1, "maxLength": 500 }
59
+ }
60
+ }
61
+ }