pi-cohort 5.0.1 → 5.1.1
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.
- package/CHANGELOG.md +29 -0
- package/package.json +1 -1
- package/skills/pi-cohort/SKILL.md +1 -1
- package/skills/pi-cohort/reference/config-fields.md +20 -3
- package/src/extension/index.ts +3 -12
- package/src/extension/schemas.ts +56 -52
- package/src/runs/foreground/subagent-executor.ts +28 -0
- package/src/runs/shared/acceptance.ts +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [5.1.1] - 2026-08-07
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- subagent tool schema shrunk from 12,374 to 9,483 bytes serialized, plus ~600
|
|
8
|
+
chars off the tool description: the chain parallel-item schemas (both the
|
|
9
|
+
static array items and the dynamic fanout template) and `control` are now
|
|
10
|
+
permissive stubs (`additionalProperties: true`) keeping only structural keys
|
|
11
|
+
explicit (parallel: agent/task/count/as; control: enabled); the
|
|
12
|
+
MANAGEMENT/CONTROL description blocks compressed to one line each. Widening
|
|
13
|
+
only - every previously valid call stays valid; dynamic-template unknown
|
|
14
|
+
keys are still rejected at runtime (assertOnlyKeys), and the control field
|
|
15
|
+
table moved to the pi-cohort skill reference/config-fields.md.
|
|
16
|
+
|
|
17
|
+
## [5.1.0] - 2026-08-06
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- subagent tool schema shrunk from 18.0KB to 12.4KB serialized: the acceptance
|
|
22
|
+
override shape (previously inlined 5x, 44% of the schema) now appears in full
|
|
23
|
+
only at top-level `acceptance`; the 4 nested sites (tasks items, chain steps,
|
|
24
|
+
parallel members, dynamic fanout template) carry a compact stub. Gate-level
|
|
25
|
+
`evidence`/`severity` dropped from the schema (still accepted at runtime).
|
|
26
|
+
Deep acceptance validation moved to the executor boundary with per-site path
|
|
27
|
+
labels. Nested `acceptance: false` is no longer schema-accepted - use
|
|
28
|
+
`level: "none"`. Unknown acceptance keys are now rejected everywhere,
|
|
29
|
+
including saved `.chain.json` chains that previously loaded with the key
|
|
30
|
+
silently ignored. ([#6](https://github.com/jjuraszek/pi-cohort/issues/6))
|
|
31
|
+
|
|
3
32
|
## [5.0.1] - 2026-08-06
|
|
4
33
|
|
|
5
34
|
### Changed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cohort",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.1.1",
|
|
4
4
|
"description": "Delegate Pi work to focused child agents: code review, scouting, implementation, parallel audits, saved chains, and background jobs.",
|
|
5
5
|
"author": "Jacek Juraszek",
|
|
6
6
|
"license": "MIT",
|
|
@@ -115,7 +115,7 @@ Use this when a broad diff has known reviewer findings across several items and
|
|
|
115
115
|
|
|
116
116
|
Prefer `async: true`, `context: "fresh"` for planners/validators, `outputMode: "file-only"` for large summaries, and per-stage output names that will not collide. Add `phase` and `label` to make async status readable, and use `as` plus `{outputs.name}` when a later step needs a specific earlier result instead of the whole `{previous}` blob. Use this pattern instead of launching several writer workers into a dirty worktree. Include non-blocking suggestions in the writer prompt only when they are small, safe, and do not expand product scope; otherwise record them as deferred.
|
|
117
117
|
|
|
118
|
-
When the first step can return a structured target list, prefer dynamic fanout instead of hand-authoring a static parallel group. Use `outputSchema` and `as` on the producer, then an `expand` step with `from: { output, path }`, an explicit `maxItems`, one `parallel` child template, and `collect.as`. Item templates may use `{item}` or a named item such as `{target.path}`. Do not use dynamic fanout for prose outputs, nested fanout, dynamic agent selection, reducers, `when` conditions, or arbitrary expressions; `.chain.md` does not support this syntax, so use direct JSON or a saved `.chain.json`.
|
|
118
|
+
When the first step can return a structured target list, prefer dynamic fanout instead of hand-authoring a static parallel group. Use `outputSchema` and `as` on the producer, then an `expand` step with `from: { output, path }`, an explicit `maxItems`, one `parallel` child template, and `collect.as`. Item templates may use `{item}` or a named item such as `{target.path}`. The template's `label` also supports item templates. Do not use dynamic fanout for prose outputs, nested fanout, dynamic agent selection, reducers, `when` conditions, or arbitrary expressions; `.chain.md` does not support this syntax, so use direct JSON or a saved `.chain.json`.
|
|
119
119
|
|
|
120
120
|
Example shape:
|
|
121
121
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# subagent `config`
|
|
1
|
+
# subagent field reference: `config` and call-time `control`
|
|
2
2
|
|
|
3
|
-
Fields accepted by `subagent({ action: "create" | "update", config: {...} })
|
|
3
|
+
Fields accepted by `subagent({ action: "create" | "update", config: {...} })`, plus the call-time `control` overrides (last section).
|
|
4
4
|
`config` may be an object or a JSON string. Presence of `steps` makes it a chain.
|
|
5
|
-
Scope: this is the management create/update path (`parseStepList`), which accepts exactly the step fields below and requires `outputSchema` to be a file path; file-authored `.chain.md` chains support additional step fields (parallel, expand, collect, concurrency, failFast, worktree, acceptance, inline outputSchema) - see SKILL.md chain authoring.
|
|
5
|
+
Scope (for `config`): this is the management create/update path (`parseStepList`), which accepts exactly the step fields below and requires `outputSchema` to be a file path; file-authored `.chain.md` chains support additional step fields (parallel, expand, collect, concurrency, failFast, worktree, acceptance, inline outputSchema) - see SKILL.md chain authoring.
|
|
6
6
|
|
|
7
7
|
## Agent config
|
|
8
8
|
|
|
@@ -49,3 +49,20 @@ Per step:
|
|
|
49
49
|
| `model` | string | Model override. |
|
|
50
50
|
| `skills` | string[] \| false | Skills to inject (NOTE: plural `skills` here, unlike the execution-time `skill` param). |
|
|
51
51
|
| `progress` | boolean | progress.md tracking. |
|
|
52
|
+
|
|
53
|
+
## Call-time `control` overrides
|
|
54
|
+
|
|
55
|
+
Per-call attention-tracking overrides: `subagent({ ..., control: {...} })`. Run-level field - sits beside `tasks`/`chain`, not inside task items. The tool schema declares only `enabled`; all fields below are accepted (`additionalProperties: true`) and re-parsed defensively at runtime (`resolveControlConfig`, `src/runs/shared/subagent-control.ts`) - invalid values fall back to the defaults.
|
|
56
|
+
|
|
57
|
+
| Field | Type | Meaning | Default |
|
|
58
|
+
|---|---|---|---|
|
|
59
|
+
| `enabled` | boolean | Toggle attention tracking. | `true` |
|
|
60
|
+
| `needsAttentionAfterMs` | integer >= 1 | No-activity window before `needs_attention`. | `60000` |
|
|
61
|
+
| `activeNoticeAfterMs` | integer >= 1 | Elapsed-ms threshold for the long-running notice. | `240000` |
|
|
62
|
+
| `inFlightSilenceCeilingMs` | integer >= 1 | Silent ms in-flight before escalation. | `600000` |
|
|
63
|
+
| `inFlightSilenceKillMs` | integer >= 1 | SIGTERM the child after this silent span (clamped above the needs-attention window). | `1800000` |
|
|
64
|
+
| `activeNoticeAfterTurns` | integer >= 1 | Notice by assistant turns. | off |
|
|
65
|
+
| `activeNoticeAfterTokens` | integer >= 1 | Notice by total tokens. | off |
|
|
66
|
+
| `failedToolAttemptsBeforeAttention` | integer >= 1 | Mutating-tool failures before `needs_attention`. | `3` |
|
|
67
|
+
| `notifyOn` | array of `"active_long_running"` \| `"needs_attention"` | Which events notify the parent. | both |
|
|
68
|
+
| `notifyChannels` | array of `"event"` \| `"async"` \| `"intercom"` | Delivery channels. | all three |
|
package/src/extension/index.ts
CHANGED
|
@@ -439,18 +439,9 @@ CHAIN TEMPLATE VARIABLES (use in task strings):
|
|
|
439
439
|
|
|
440
440
|
Example: { chain: [{agent:"agent-a", task:"Analyze {task}"}, {agent:"agent-b", task:"Plan based on {previous}"}] }
|
|
441
441
|
|
|
442
|
-
MANAGEMENT (use
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
• { action: "create", config: { name: "custom-agent", package: "code-analysis", systemPrompt, systemPromptMode, inheritProjectContext, inheritSkills, defaultContext, ... } }
|
|
446
|
-
• { action: "update", agent: "code-analysis.custom-agent", config: { package: "analysis", ... } } - merge
|
|
447
|
-
• { action: "delete", agent: "code-analysis.custom-agent" }
|
|
448
|
-
• Use chainName for chain operations; packaged chains also use dotted runtime names
|
|
449
|
-
|
|
450
|
-
CONTROL:
|
|
451
|
-
• { action: "status", id: "..." } - inspect an async/background run by id or prefix
|
|
452
|
-
• { action: "interrupt", id?: "..." } - soft-interrupt the current child turn and leave the run paused
|
|
453
|
-
• { action: "resume", id: "...", message: "...", index?: 0 } - follow up with a live async child or revive a completed async/foreground child from its session
|
|
442
|
+
MANAGEMENT: action=list/get/create/update/delete (chainName for chains; packaged agents use dotted names); config fields: pi-cohort skill reference/config-fields.md.
|
|
443
|
+
|
|
444
|
+
CONTROL: action=status/interrupt/resume (id/runId, message, index); details: pi-cohort skill.
|
|
454
445
|
|
|
455
446
|
DIAGNOSTICS:
|
|
456
447
|
• { action: "doctor" } - read-only report for runtime paths, discovery, sessions, and intercom`,
|
package/src/extension/schemas.ts
CHANGED
|
@@ -65,12 +65,13 @@ const AcceptanceEvidenceKind = Type.String({
|
|
|
65
65
|
],
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
+
// Gate-level `evidence`/`severity` are accepted at runtime (normalizeCriteria) but omitted
|
|
69
|
+
// from the schema: dead flexibility from a tool call, and the evidence enum is heavy.
|
|
70
|
+
// additionalProperties stays open so inputs carrying them are not provider-rejected.
|
|
68
71
|
const AcceptanceGateSchema = Type.Object({
|
|
69
72
|
id: Type.String(),
|
|
70
73
|
must: Type.String(),
|
|
71
|
-
|
|
72
|
-
severity: Type.Optional(Type.String({ enum: ["required", "recommended"] })),
|
|
73
|
-
}, { additionalProperties: false });
|
|
74
|
+
}, { additionalProperties: true });
|
|
74
75
|
|
|
75
76
|
const AcceptanceVerifyCommandSchema = Type.Object({
|
|
76
77
|
id: Type.String(),
|
|
@@ -121,6 +122,21 @@ const AcceptanceOverride = Type.Unsafe({
|
|
|
121
122
|
description: "Acceptance: auto if omitted; verified needs cmds.",
|
|
122
123
|
});
|
|
123
124
|
|
|
125
|
+
// Nested acceptance sites carry a compact stub instead of the full AcceptanceOverride:
|
|
126
|
+
// inlined 5x it was 44% of the serialized schema. The full shape is documented once at
|
|
127
|
+
// top-level `acceptance`; deep validation happens at the executor boundary
|
|
128
|
+
// (validateAcceptanceInput), not provider-side. The object branch keeps a non-empty
|
|
129
|
+
// `properties` because some providers reject bare {type:"object"} in function
|
|
130
|
+
// declarations - the same wall that rules out $defs/$ref (see issue #6).
|
|
131
|
+
// {const:false} is intentionally absent: deprecated shorthand, use level:"none".
|
|
132
|
+
const AcceptanceOverrideStub = Type.Unsafe({
|
|
133
|
+
anyOf: [
|
|
134
|
+
{ type: "string", enum: ["auto", "none", "attested", "checked", "verified", "reviewed"] },
|
|
135
|
+
{ type: "object", additionalProperties: true, properties: { level: { type: "string" } } },
|
|
136
|
+
],
|
|
137
|
+
description: "Acceptance override; same shape as top-level acceptance.",
|
|
138
|
+
});
|
|
139
|
+
|
|
124
140
|
const TaskItem = Type.Object({
|
|
125
141
|
agent: Type.String(),
|
|
126
142
|
task: Type.String(),
|
|
@@ -132,26 +148,26 @@ const TaskItem = Type.Object({
|
|
|
132
148
|
progress: Type.Optional(Type.Boolean({ description: "true enables progress.md tracking; omit or false disables." })),
|
|
133
149
|
model: Type.Optional(Type.String()),
|
|
134
150
|
skill: Type.Optional(brief(SkillOverride, "Skill override.")),
|
|
135
|
-
acceptance: Type.Optional(
|
|
151
|
+
acceptance: Type.Optional(AcceptanceOverrideStub),
|
|
136
152
|
});
|
|
137
153
|
|
|
138
|
-
// Parallel
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
// Parallel-item stubs (round two): structural/wiring keys stay explicit (agent/task,
|
|
155
|
+
// count/as = arity + {outputs.name} routing); every per-task override (model, label,
|
|
156
|
+
// phase, output, outputMode, reads, skill, cwd, progress, outputSchema, acceptance)
|
|
157
|
+
// passes through additionalProperties and is documented in the pi-cohort skill.
|
|
158
|
+
// Widening-only: dynamic-arm unknown keys are still rejected at runtime by
|
|
159
|
+
// assertOnlyKeys (dynamic-fanout.ts); static-arm unknown keys were always ignored.
|
|
160
|
+
const ParallelTaskSchema = Type.Unsafe({
|
|
161
|
+
type: "object",
|
|
162
|
+
additionalProperties: true,
|
|
163
|
+
required: ["agent"],
|
|
164
|
+
properties: {
|
|
165
|
+
agent: { type: "string" },
|
|
166
|
+
task: { type: "string", description: "{task},{previous},{chain_dir} template; defaults to {previous}." },
|
|
167
|
+
count: { type: "integer", minimum: 1, description: "Repeat N times." },
|
|
168
|
+
as: { type: "string", description: "Identifier for {outputs.name}." },
|
|
169
|
+
},
|
|
170
|
+
description: "Per-task overrides (model, label, output, skill, ...): pi-cohort skill.",
|
|
155
171
|
});
|
|
156
172
|
|
|
157
173
|
const DynamicExpandSchema = Type.Object({
|
|
@@ -165,21 +181,16 @@ const DynamicExpandSchema = Type.Object({
|
|
|
165
181
|
onEmpty: Type.Optional(Type.String({ enum: ["skip", "fail"], description: "Defaults to skip." })),
|
|
166
182
|
}, { additionalProperties: false });
|
|
167
183
|
|
|
168
|
-
const DynamicParallelTemplateSchema = Type.
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking." })),
|
|
179
|
-
model: Type.Optional(Type.String()),
|
|
180
|
-
skill: Type.Optional(brief(SkillOverride, "Skill override.")),
|
|
181
|
-
acceptance: Type.Optional(brief(AcceptanceOverride, "Acceptance override.")),
|
|
182
|
-
}, { additionalProperties: false });
|
|
184
|
+
const DynamicParallelTemplateSchema = Type.Unsafe({
|
|
185
|
+
type: "object",
|
|
186
|
+
additionalProperties: true,
|
|
187
|
+
required: ["agent"],
|
|
188
|
+
properties: {
|
|
189
|
+
agent: { type: "string" },
|
|
190
|
+
task: { type: "string", description: "{item},{item.path},{task},{previous},{chain_dir},{outputs.name} template." },
|
|
191
|
+
},
|
|
192
|
+
description: "Per-task overrides (model, label, output, skill, ...): pi-cohort skill.",
|
|
193
|
+
});
|
|
183
194
|
|
|
184
195
|
const DynamicCollectSchema = Type.Object({
|
|
185
196
|
as: Type.String({ description: "Collected result array name." }),
|
|
@@ -203,7 +214,7 @@ const ChainItem = Type.Object({
|
|
|
203
214
|
progress: Type.Optional(Type.Boolean({ description: "Enable progress.md tracking in {chain_dir}" })),
|
|
204
215
|
model: Type.Optional(Type.String()),
|
|
205
216
|
skill: Type.Optional(brief(SkillOverride, "Skill override.")),
|
|
206
|
-
acceptance: Type.Optional(
|
|
217
|
+
acceptance: Type.Optional(AcceptanceOverrideStub),
|
|
207
218
|
parallel: Type.Optional(Type.Unsafe({
|
|
208
219
|
anyOf: [
|
|
209
220
|
Type.Array(ParallelTaskSchema, { minItems: 1 }),
|
|
@@ -228,21 +239,14 @@ const ChainItem = Type.Object({
|
|
|
228
239
|
],
|
|
229
240
|
});
|
|
230
241
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
failedToolAttemptsBeforeAttention: Type.Optional(Type.Integer({ minimum: 1, description: "Mutating-tool failures before needs_attention (default: 3)." })),
|
|
240
|
-
notifyOn: Type.Optional(Type.Array(Type.String({ enum: ["active_long_running", "needs_attention"] }), {
|
|
241
|
-
description: "To parent (default: active_long_running, needs_attention).",
|
|
242
|
-
})),
|
|
243
|
-
notifyChannels: Type.Optional(Type.Array(Type.String({ enum: ["event", "async", "intercom"] }), {
|
|
244
|
-
description: "Default: event, async, intercom.",
|
|
245
|
-
})),
|
|
242
|
+
// Control stub (round two): 10 fields collapsed; the full table lives in the pi-cohort
|
|
243
|
+
// skill reference/config-fields.md. resolveControlConfig (subagent-control.ts) re-parses
|
|
244
|
+
// every field defensively at runtime, so mistyped values degrade to defaults.
|
|
245
|
+
const ControlOverrides = Type.Unsafe({
|
|
246
|
+
type: "object",
|
|
247
|
+
additionalProperties: true,
|
|
248
|
+
properties: { enabled: { type: "boolean" } },
|
|
249
|
+
description: "Attention-tracking overrides (needsAttentionAfterMs, notifyOn, ...); fields: pi-cohort skill reference/config-fields.md.",
|
|
246
250
|
});
|
|
247
251
|
|
|
248
252
|
export const SubagentParams = Type.Object({
|
|
@@ -17,6 +17,7 @@ import { recordSyncCost, renderGrandTotal, sumNestedCost } from "../../extension
|
|
|
17
17
|
import { resolveModelCandidate } from "../shared/model-fallback.ts";
|
|
18
18
|
import { aggregateParallelOutputs } from "../shared/parallel-utils.ts";
|
|
19
19
|
import { recordRun } from "../shared/run-history.ts";
|
|
20
|
+
import { validateAcceptanceInput } from "../shared/acceptance.ts";
|
|
20
21
|
import {
|
|
21
22
|
buildChainInstructions,
|
|
22
23
|
writeInitialProgressFile,
|
|
@@ -858,6 +859,33 @@ function validateExecutionInput(
|
|
|
858
859
|
}
|
|
859
860
|
}
|
|
860
861
|
|
|
862
|
+
// Nested acceptance sites only carry a compact stub in the tool schema (see
|
|
863
|
+
// AcceptanceOverrideStub in extension/schemas.ts); this is the deep-validation boundary.
|
|
864
|
+
const acceptanceErrors: string[] = [
|
|
865
|
+
...validateAcceptanceInput(params.acceptance, "acceptance"),
|
|
866
|
+
];
|
|
867
|
+
for (const [i, task] of (params.tasks ?? []).entries()) {
|
|
868
|
+
acceptanceErrors.push(...validateAcceptanceInput(task.acceptance, `tasks[${i}].acceptance`));
|
|
869
|
+
}
|
|
870
|
+
for (const [i, rawStep] of (params.chain ?? []).entries()) {
|
|
871
|
+
const step = rawStep as ChainStep;
|
|
872
|
+
acceptanceErrors.push(...validateAcceptanceInput((rawStep as { acceptance?: unknown }).acceptance, `chain[${i}].acceptance`));
|
|
873
|
+
if (isParallelStep(step)) {
|
|
874
|
+
for (const [j, member] of step.parallel.entries()) {
|
|
875
|
+
acceptanceErrors.push(...validateAcceptanceInput(member.acceptance, `chain[${i}].parallel[${j}].acceptance`));
|
|
876
|
+
}
|
|
877
|
+
} else if (isDynamicParallelStep(step)) {
|
|
878
|
+
acceptanceErrors.push(...validateAcceptanceInput(step.parallel.acceptance, `chain[${i}].parallel.acceptance`));
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
if (acceptanceErrors.length > 0) {
|
|
882
|
+
return {
|
|
883
|
+
content: [{ type: "text", text: `Invalid acceptance input:\n${acceptanceErrors.map((error) => `- ${error}`).join("\n")}` }],
|
|
884
|
+
isError: true,
|
|
885
|
+
details: { mode: hasChain ? "chain" as const : hasTasks ? "parallel" as const : "single" as const, results: [] },
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
|
|
861
889
|
return null;
|
|
862
890
|
}
|
|
863
891
|
|
|
@@ -131,6 +131,8 @@ function explicitAcceptanceCanDisable(explicit: AcceptanceConfig): boolean {
|
|
|
131
131
|
return explicit.level === "none" && typeof explicit.reason === "string" && explicit.reason.trim().length > 0;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
const KNOWN_ACCEPTANCE_KEYS = new Set(["level", "criteria", "evidence", "verify", "review", "stopRules", "reason"]);
|
|
135
|
+
|
|
134
136
|
export function validateAcceptanceInput(input: unknown, pathLabel = "acceptance"): string[] {
|
|
135
137
|
const errors: string[] = [];
|
|
136
138
|
if (input === undefined) return errors;
|
|
@@ -144,6 +146,11 @@ export function validateAcceptanceInput(input: unknown, pathLabel = "acceptance"
|
|
|
144
146
|
return errors;
|
|
145
147
|
}
|
|
146
148
|
const value = input as Record<string, unknown>;
|
|
149
|
+
for (const key of Object.keys(value)) {
|
|
150
|
+
if (!KNOWN_ACCEPTANCE_KEYS.has(key)) {
|
|
151
|
+
errors.push(`${pathLabel}.${key} is not a recognized acceptance field (known: level, criteria, evidence, verify, review, stopRules, reason).`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
147
154
|
if (value.level !== undefined && (typeof value.level !== "string" || !VALID_LEVELS.has(value.level as AcceptanceLevel))) {
|
|
148
155
|
errors.push(`${pathLabel}.level must be one of auto, none, attested, checked, verified, reviewed.`);
|
|
149
156
|
}
|