@hunsu/protocol 0.1.0

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.
Files changed (47) hide show
  1. package/LICENSE +157 -0
  2. package/dist/command-decoder.d.ts +5 -0
  3. package/dist/command-decoder.js +329 -0
  4. package/dist/constants.d.ts +15 -0
  5. package/dist/constants.js +15 -0
  6. package/dist/decoder-helpers.d.ts +58 -0
  7. package/dist/decoder-helpers.js +1757 -0
  8. package/dist/errors.d.ts +14 -0
  9. package/dist/errors.js +28 -0
  10. package/dist/event-decoder.d.ts +5 -0
  11. package/dist/event-decoder.js +232 -0
  12. package/dist/index.d.ts +10 -0
  13. package/dist/index.js +10 -0
  14. package/dist/lifecycle.d.ts +67 -0
  15. package/dist/lifecycle.js +271 -0
  16. package/dist/model.d.ts +1058 -0
  17. package/dist/model.js +1 -0
  18. package/dist/primitives.d.ts +59 -0
  19. package/dist/primitives.js +135 -0
  20. package/dist/projection.d.ts +8 -0
  21. package/dist/projection.js +875 -0
  22. package/dist/prompt-template.d.ts +12 -0
  23. package/dist/prompt-template.js +83 -0
  24. package/dist/protocol-validation.d.ts +36 -0
  25. package/dist/protocol-validation.js +1139 -0
  26. package/dist/result.d.ts +20 -0
  27. package/dist/result.js +21 -0
  28. package/dist/workflow-helpers.d.ts +31 -0
  29. package/dist/workflow-helpers.js +193 -0
  30. package/dist/workflow.d.ts +10 -0
  31. package/dist/workflow.js +594 -0
  32. package/package.json +30 -0
  33. package/src/command-decoder.ts +292 -0
  34. package/src/constants.ts +27 -0
  35. package/src/decoder-helpers.ts +1672 -0
  36. package/src/errors.ts +38 -0
  37. package/src/event-decoder.ts +225 -0
  38. package/src/index.ts +43 -0
  39. package/src/lifecycle.ts +403 -0
  40. package/src/model.ts +1233 -0
  41. package/src/primitives.ts +196 -0
  42. package/src/projection.ts +1081 -0
  43. package/src/prompt-template.ts +97 -0
  44. package/src/protocol-validation.ts +1252 -0
  45. package/src/result.ts +35 -0
  46. package/src/workflow-helpers.ts +239 -0
  47. package/src/workflow.ts +753 -0
@@ -0,0 +1,1139 @@
1
+ import { GUARDRAIL_SCOPES, GUARDRAIL_SEVERITIES, REASONING_EFFORTS, SERVICE_TIERS, VOTE_RULES } from "./constants.js";
2
+ import { DomainInvariantError, unwrapDomainModelResult } from "./errors.js";
3
+ import { assertValidPromptTemplate, decodePromptTemplate, promptTemplateFromText } from "./prompt-template.js";
4
+ import { makeNonEmptyText, makePositiveInteger } from "./primitives.js";
5
+ import { err, ok } from "./result.js";
6
+ export const DEFAULT_TEAM_PROMPT = "Create a closure-free ExecutionPlan using the available Members.";
7
+ export const DEFAULT_MANAGER_PROMPT = [
8
+ "Discuss HUNSU changes conversationally and edit only decoded request files under .hunsu-request when a file-backed draft change is needed.",
9
+ "Help the user refine divergent Hunsu changes while preserving the current Execute result as the source of truth."
10
+ ].join(" ");
11
+ export const DEFAULT_MEMBER_EXECUTION = { kind: "read_only", network: "disabled" };
12
+ export const DEFAULT_MEMBER_APPROVAL = { policy: "never" };
13
+ const MEMBER_EXECUTION_KINDS = ["read_only", "worktree_write", "unrestricted"];
14
+ const MEMBER_EXECUTION_NETWORKS = ["disabled", "enabled"];
15
+ const MEMBER_APPROVAL_POLICIES = ["never", "on_request"];
16
+ const MEMBER_APPROVAL_REVIEWERS = ["user", "auto_review"];
17
+ const DEFAULT_ROOT_TEAM_ID = "root-team";
18
+ export function validateHarness(value) {
19
+ return parseHarness(value);
20
+ }
21
+ export function validateHarnessPlanner(value) {
22
+ return parseHarnessPlanner(value);
23
+ }
24
+ export function validateHarnessEntity(value, path = "Harness") {
25
+ const harness = recordResult(value, path);
26
+ if (!harness.ok)
27
+ return harness;
28
+ if (typeof harness.value.rootTeamId !== "string" || harness.value.rootTeamId.trim() === "") {
29
+ return workflowValidationError(`${path}.rootTeamId must be a non-empty string`);
30
+ }
31
+ if (!Array.isArray(harness.value.executors)) {
32
+ return workflowValidationError(`${path}.executors must be an array`);
33
+ }
34
+ if (!Array.isArray(harness.value.resources)) {
35
+ return workflowValidationError(`${path}.resources must be an array`);
36
+ }
37
+ const executorIds = new Set();
38
+ for (const [index, executor] of harness.value.executors.entries()) {
39
+ const validation = executorEntityResult(executor, `${path}.executors[${index}]`);
40
+ if (!validation.ok)
41
+ return validation;
42
+ const id = String(executor.id);
43
+ if (executorIds.has(id)) {
44
+ return workflowValidationError(`${path}.executors must not include duplicate Executor ${id}`);
45
+ }
46
+ executorIds.add(id);
47
+ }
48
+ const root = harness.value.executors.find(executor => {
49
+ const record = executor;
50
+ return record.id === harness.value.rootTeamId && record.kind === "team";
51
+ });
52
+ if (!root) {
53
+ return workflowValidationError(`${path}.rootTeamId must reference a Team Executor`);
54
+ }
55
+ const executorKindById = new Map();
56
+ const teamMemberships = new Map();
57
+ for (const executor of harness.value.executors) {
58
+ const record = executor;
59
+ const id = String(record.id);
60
+ const kind = record.kind === "team" ? "team" : "member";
61
+ executorKindById.set(id, kind);
62
+ if (kind === "team") {
63
+ teamMemberships.set(id, (record.members ?? []).map(membership => String(membership.executorId)));
64
+ }
65
+ }
66
+ for (const [index, executor] of harness.value.executors.entries()) {
67
+ const record = executor;
68
+ if (record.kind !== "team") {
69
+ continue;
70
+ }
71
+ const memberships = record.members;
72
+ for (const [membershipIndex, membership] of memberships.entries()) {
73
+ const memberId = String(membership.executorId);
74
+ const memberKind = executorKindById.get(memberId);
75
+ if (!memberKind) {
76
+ return workflowValidationError(`${path}.executors[${index}].members[${membershipIndex}].executorId references unknown Executor ${membership.executorId}`);
77
+ }
78
+ if (membership.visibleProfile.kind !== memberKind) {
79
+ return workflowValidationError(`${path}.executors[${index}].members[${membershipIndex}].visibleProfile.kind must match Executor ${memberId} kind ${memberKind}`);
80
+ }
81
+ }
82
+ }
83
+ const cycle = findTeamCycle(teamMemberships);
84
+ if (cycle) {
85
+ return workflowValidationError(`${path}.executors must not contain cyclic Team Memberships: ${cycle.join(" -> ")}`);
86
+ }
87
+ for (const [index, resource] of harness.value.resources.entries()) {
88
+ const validation = resourceEntityResult(resource, `${path}.resources[${index}]`);
89
+ if (!validation.ok)
90
+ return validation;
91
+ }
92
+ const guardrails = guardrailsResult(harness.value.guardrails, `${path}.guardrails`);
93
+ if (!guardrails.ok)
94
+ return guardrails;
95
+ if (!Array.isArray(harness.value.artifactActions)) {
96
+ return workflowValidationError(`${path}.artifactActions must be an array`);
97
+ }
98
+ return ok(cloneHarnessEntity(harness.value));
99
+ }
100
+ function findTeamCycle(teamMemberships) {
101
+ const visiting = new Set();
102
+ const visited = new Set();
103
+ const stack = [];
104
+ const visit = (teamId) => {
105
+ if (visiting.has(teamId)) {
106
+ const start = stack.indexOf(teamId);
107
+ return [...stack.slice(start), teamId];
108
+ }
109
+ if (visited.has(teamId)) {
110
+ return undefined;
111
+ }
112
+ visiting.add(teamId);
113
+ stack.push(teamId);
114
+ for (const childId of teamMemberships.get(teamId) ?? []) {
115
+ if (!teamMemberships.has(childId)) {
116
+ continue;
117
+ }
118
+ const cycle = visit(childId);
119
+ if (cycle) {
120
+ return cycle;
121
+ }
122
+ }
123
+ stack.pop();
124
+ visiting.delete(teamId);
125
+ visited.add(teamId);
126
+ return undefined;
127
+ };
128
+ for (const teamId of teamMemberships.keys()) {
129
+ const cycle = visit(teamId);
130
+ if (cycle) {
131
+ return cycle;
132
+ }
133
+ }
134
+ return undefined;
135
+ }
136
+ export function validateMemberConfig(value, path = "Member") {
137
+ const member = recordResult(value, path);
138
+ if (!member.ok)
139
+ return member;
140
+ const validation = memberConfigResult(member.value, path);
141
+ return validation.ok ? ok(cloneMemberConfig(member.value)) : validation;
142
+ }
143
+ export function validateManagerConfig(value, path = "Manager") {
144
+ const manager = recordResult(value, path);
145
+ if (!manager.ok)
146
+ return manager;
147
+ const validation = managerConfigResult(manager.value, path);
148
+ return validation.ok ? ok(cloneManagerConfig(manager.value)) : validation;
149
+ }
150
+ function parseHarness(value) {
151
+ const protocol = recordResult(value, "Harness");
152
+ if (!protocol.ok)
153
+ return protocol;
154
+ const normalized = normalizeHarnessKind(protocol.value);
155
+ switch (normalized.kind) {
156
+ case "team_execution_plan":
157
+ return validateHarnessSnapshotShape({ ...normalized, kind: "team_execution_plan" }, "team_execution_plan", "maxAttemptCount");
158
+ case "role_squad":
159
+ return validateHarnessSnapshotShape(normalized, "role_squad", "maxRoundCount");
160
+ case "council_vote":
161
+ return chainResult(allowedResult("council_vote.voteRule", normalized.voteRule, VOTE_RULES), () => validateHarnessSnapshotShape(normalized, "council_vote", "maxRoundCount"));
162
+ case "court_debate":
163
+ return validateHarnessSnapshotShape(normalized, "court_debate", "maxRoundCount");
164
+ default:
165
+ return workflowValidationError(`Unsupported Harness kind: ${String(normalized.kind)}`);
166
+ }
167
+ }
168
+ function parseHarnessPlanner(value) {
169
+ const protocol = recordResult(value, "Planner Harness");
170
+ if (!protocol.ok)
171
+ return protocol;
172
+ const normalized = normalizeHarnessKind(protocol.value);
173
+ switch (normalized.kind) {
174
+ case "team_execution_plan":
175
+ return validateHarnessPlannerSnapshotShape({ ...normalized, kind: "team_execution_plan" }, "team_execution_plan", "maxAttemptCount");
176
+ case "role_squad":
177
+ return validateHarnessPlannerSnapshotShape(normalized, "role_squad", "maxRoundCount");
178
+ case "council_vote":
179
+ return chainResult(allowedResult("council_vote.voteRule", normalized.voteRule, VOTE_RULES), () => validateHarnessPlannerSnapshotShape(normalized, "council_vote", "maxRoundCount"));
180
+ case "court_debate":
181
+ return validateHarnessPlannerSnapshotShape(normalized, "court_debate", "maxRoundCount");
182
+ default:
183
+ return workflowValidationError(`Unsupported Planner Harness kind: ${String(normalized.kind)}`);
184
+ }
185
+ }
186
+ export function isExecutableHarness(protocol) {
187
+ return protocol.kind === "team_execution_plan";
188
+ }
189
+ export function validateExecutableHarness(protocol) {
190
+ if (!isExecutableHarness(protocol)) {
191
+ return err({ type: "DomainWorkflowError", message: `Harness ${protocol.kind} is not executable yet` });
192
+ }
193
+ return ok(protocol);
194
+ }
195
+ export function createDefaultHarness(instructions = "", skills = []) {
196
+ return {
197
+ kind: "team_execution_plan",
198
+ maxAttemptCount: unwrapDomainModelResult(makePositiveInteger(5, "team_execution_plan.maxAttemptCount")),
199
+ team: {
200
+ promptTemplate: promptTemplateFromText(instructions || DEFAULT_TEAM_PROMPT)
201
+ },
202
+ members: [
203
+ createDefaultMemberConfig("azir", "Plan briefly, then implement the focused goal in this repository.", skills),
204
+ createDefaultMemberConfig("galio", "Verify the completed work against the focused goal with concrete evidence.", [])
205
+ ]
206
+ };
207
+ }
208
+ export function createDefaultMemberConfig(id, prompt = "", skills = [], execution = DEFAULT_MEMBER_EXECUTION, approval = DEFAULT_MEMBER_APPROVAL, plugins = []) {
209
+ return {
210
+ id: unwrapDomainModelResult(makeNonEmptyText(id, "executorId")),
211
+ promptTemplate: promptTemplateFromText(prompt),
212
+ skills: cloneSkills(skills),
213
+ plugins: clonePlugins(plugins),
214
+ model: unwrapDomainModelResult(makeNonEmptyText("codex-default", "member.model")),
215
+ reasoningEffort: "default",
216
+ serviceTier: "default",
217
+ execution: cloneMemberExecution(execution),
218
+ approval: cloneMemberApproval(approval)
219
+ };
220
+ }
221
+ export function createDefaultManagerConfig(id = "manager.hunsu.default", prompt = DEFAULT_MANAGER_PROMPT, skills = [], plugins = []) {
222
+ return {
223
+ id: unwrapDomainModelResult(makeNonEmptyText(id, "manager.id")),
224
+ promptTemplate: promptTemplateFromText(prompt),
225
+ skills: cloneSkills(skills),
226
+ plugins: clonePlugins(plugins)
227
+ };
228
+ }
229
+ export function cloneHarness(protocol) {
230
+ const cloned = JSON.parse(JSON.stringify(protocol));
231
+ return {
232
+ ...cloned,
233
+ guardrails: cloned.guardrails?.map(guardrail => ({ ...guardrail })),
234
+ members: cloned.members.map(cloneMemberConfig)
235
+ };
236
+ }
237
+ export function cloneHarnessPlanner(protocol) {
238
+ const cloned = JSON.parse(JSON.stringify(protocol));
239
+ return {
240
+ ...cloned,
241
+ team: { promptTemplate: { ...cloned.team.promptTemplate } },
242
+ guardrails: cloned.guardrails?.map(guardrail => ({ ...guardrail }))
243
+ };
244
+ }
245
+ export function harnessPlannerFromSnapshot(protocol) {
246
+ const cloned = cloneHarness(protocol);
247
+ const { members: _members, ...planner } = cloned;
248
+ return cloneHarnessPlanner(planner);
249
+ }
250
+ export function composeHarnessSnapshot(planner, members) {
251
+ const memberValidation = memberArrayResult(members, "members.members", true);
252
+ if (!memberValidation.ok) {
253
+ throw new DomainInvariantError(memberValidation.error.message);
254
+ }
255
+ const clonedPlanner = cloneHarnessPlanner(planner);
256
+ const clonedMembers = members.map(cloneMemberConfig);
257
+ return {
258
+ ...clonedPlanner,
259
+ members: clonedMembers
260
+ };
261
+ }
262
+ export function harnessEntityFromSnapshot(protocol, rootTeamId = DEFAULT_ROOT_TEAM_ID) {
263
+ const rootId = executorId(rootTeamId, "rootTeamId");
264
+ const planner = harnessPlannerFromSnapshot(protocol);
265
+ const members = protocol.members.map(cloneMemberConfig);
266
+ const rootMemberships = members.map(member => ({
267
+ executorId: member.id,
268
+ visibleProfile: visibleProfileFromMemberConfig(member)
269
+ }));
270
+ return {
271
+ rootTeamId: rootId,
272
+ executors: [
273
+ {
274
+ kind: "team",
275
+ id: rootId,
276
+ planner: {
277
+ promptTemplate: { ...planner.team.promptTemplate },
278
+ maxAttemptCount: "maxAttemptCount" in planner ? planner.maxAttemptCount : undefined,
279
+ guardrails: planner.guardrails?.map(guardrail => ({ ...guardrail }))
280
+ },
281
+ members: rootMemberships
282
+ },
283
+ ...members.map(memberEntityFromConfig)
284
+ ],
285
+ resources: resourceEntitiesFromMembers(members),
286
+ guardrails: planner.guardrails?.map(guardrail => ({ ...guardrail })) ?? [],
287
+ artifactActions: []
288
+ };
289
+ }
290
+ export function harnessSnapshotForTeam(harness, teamId = harness.rootTeamId) {
291
+ const team = getHarnessTeam(harness, teamId);
292
+ if (!team) {
293
+ throw new DomainInvariantError(`Harness does not define Team ${teamId}`);
294
+ }
295
+ const members = team.members.map(membership => memberConfigForMembership(harness, membership));
296
+ const maxAttemptCount = team.planner.maxAttemptCount
297
+ ?? unwrapDomainModelResult(makePositiveInteger(5, "team.maxAttemptCount"));
298
+ return {
299
+ kind: "team_execution_plan",
300
+ maxAttemptCount,
301
+ guardrails: team.planner.guardrails?.map(guardrail => ({ ...guardrail })),
302
+ team: {
303
+ promptTemplate: { ...team.planner.promptTemplate }
304
+ },
305
+ members
306
+ };
307
+ }
308
+ export function rootHarnessSnapshot(harness) {
309
+ return harnessSnapshotForTeam(harness, harness.rootTeamId);
310
+ }
311
+ export function cloneHarnessEntity(harness) {
312
+ return {
313
+ rootTeamId: harness.rootTeamId,
314
+ executors: harness.executors.map(cloneExecutorEntity),
315
+ resources: harness.resources.map(cloneResourceEntity),
316
+ guardrails: harness.guardrails.map(guardrail => ({ ...guardrail })),
317
+ artifactActions: JSON.parse(JSON.stringify(harness.artifactActions))
318
+ };
319
+ }
320
+ export function getHarnessExecutor(harness, executorId) {
321
+ const executor = harness.executors.find(candidate => candidate.id === executorId);
322
+ return executor ? cloneExecutorEntity(executor) : undefined;
323
+ }
324
+ export function getHarnessTeam(harness, executorId) {
325
+ const executor = harness.executors.find(candidate => candidate.id === executorId && candidate.kind === "team");
326
+ return executor ? cloneExecutorEntity(executor) : undefined;
327
+ }
328
+ export function getHarnessMember(harness, executorId) {
329
+ const executor = harness.executors.find(candidate => candidate.id === executorId && candidate.kind === "member");
330
+ return executor ? cloneMemberEntity(executor) : undefined;
331
+ }
332
+ export function memberConfigFromMemberEntity(member) {
333
+ return {
334
+ id: member.id,
335
+ promptTemplate: { ...member.promptTemplate },
336
+ skills: member.resources
337
+ .filter((binding) => binding.kind === "skill")
338
+ .map(binding => cloneSkill(binding.skill)),
339
+ plugins: member.resources
340
+ .filter((binding) => binding.kind === "plugin")
341
+ .map(binding => ({ ...binding.plugin })),
342
+ model: member.runtimePolicy.model,
343
+ reasoningEffort: member.runtimePolicy.reasoningEffort,
344
+ serviceTier: member.runtimePolicy.serviceTier,
345
+ execution: cloneMemberExecution(member.runtimePolicy.execution),
346
+ approval: cloneMemberApproval(member.runtimePolicy.approval)
347
+ };
348
+ }
349
+ export function getHarnessMemberConfig(protocol, executorId) {
350
+ return protocol.members.find(member => member.id === executorId)
351
+ ? cloneMemberConfig(protocol.members.find(member => member.id === executorId))
352
+ : undefined;
353
+ }
354
+ export function updateHarnessMemberConfig(protocol, executorId, update) {
355
+ if (!protocol.members.some(member => member.id === executorId)) {
356
+ throw new DomainInvariantError(`Unknown Member ${executorId} for Harness ${protocol.kind}`);
357
+ }
358
+ return {
359
+ ...protocol,
360
+ team: { ...protocol.team },
361
+ guardrails: protocol.guardrails?.map(guardrail => ({ ...guardrail })),
362
+ members: protocol.members.map(member => member.id === executorId ? update(cloneMemberConfig(member)) : cloneMemberConfig(member))
363
+ };
364
+ }
365
+ export function updateHarnessTeamPromptTemplate(protocol, promptTemplate) {
366
+ assertValidPromptTemplate(promptTemplate, "Team prompt template");
367
+ return {
368
+ ...protocol,
369
+ team: { promptTemplate: { ...promptTemplate } },
370
+ guardrails: protocol.guardrails?.map(guardrail => ({ ...guardrail })),
371
+ members: protocol.members.map(cloneMemberConfig)
372
+ };
373
+ }
374
+ function executorId(value, path) {
375
+ return unwrapDomainModelResult(makeNonEmptyText(value, path));
376
+ }
377
+ function runtimePolicyFromMemberConfig(member) {
378
+ return {
379
+ model: member.model,
380
+ reasoningEffort: member.reasoningEffort,
381
+ serviceTier: member.serviceTier,
382
+ execution: cloneMemberExecution(member.execution),
383
+ approval: cloneMemberApproval(member.approval)
384
+ };
385
+ }
386
+ function resourceBindingsFromMemberConfig(member) {
387
+ return [
388
+ ...member.skills.map(skill => ({ kind: "skill", skill: cloneSkill(skill) })),
389
+ ...(member.plugins ?? []).map(plugin => ({ kind: "plugin", plugin: { ...plugin } }))
390
+ ];
391
+ }
392
+ function memberEntityFromConfig(member) {
393
+ return {
394
+ kind: "member",
395
+ id: member.id,
396
+ promptTemplate: { ...member.promptTemplate },
397
+ resources: resourceBindingsFromMemberConfig(member),
398
+ runtimePolicy: runtimePolicyFromMemberConfig(member)
399
+ };
400
+ }
401
+ function visibleProfileFromMemberConfig(member) {
402
+ return {
403
+ kind: "member",
404
+ label: member.id,
405
+ summary: unwrapDomainModelResult(makeNonEmptyText(member.promptTemplate.template || `Member ${member.id}`, "visibleProfile.summary")),
406
+ capabilities: member.skills.map(skill => skill.name)
407
+ };
408
+ }
409
+ function memberConfigForMembership(harness, membership) {
410
+ const executor = harness.executors.find(candidate => candidate.id === membership.executorId);
411
+ if (!executor) {
412
+ throw new DomainInvariantError(`Team Membership references unknown Executor ${membership.executorId}`);
413
+ }
414
+ if (executor.kind === "member") {
415
+ return memberConfigFromMemberEntity(executor);
416
+ }
417
+ return createDefaultMemberConfig(executor.id, visibleProfilePrompt(membership.visibleProfile), [], DEFAULT_MEMBER_EXECUTION, DEFAULT_MEMBER_APPROVAL, []);
418
+ }
419
+ function visibleProfilePrompt(profile) {
420
+ const capabilities = profile.capabilities?.length ? `\nCapabilities:\n${profile.capabilities.map(capability => `- ${capability}`).join("\n")}` : "";
421
+ return [
422
+ profile.kind === "team" ? "Composite Team Executor." : "Member Executor.",
423
+ profile.label ? `Label: ${profile.label}` : undefined,
424
+ profile.summary ? `Summary: ${profile.summary}` : undefined,
425
+ capabilities || undefined
426
+ ].filter(Boolean).join("\n");
427
+ }
428
+ function resourceEntitiesFromMembers(members) {
429
+ const resources = [];
430
+ const seen = new Set();
431
+ for (const member of members) {
432
+ for (const binding of resourceBindingsFromMemberConfig(member)) {
433
+ const id = resourceEntityId(member.id, binding);
434
+ if (seen.has(id)) {
435
+ continue;
436
+ }
437
+ seen.add(id);
438
+ resources.push({
439
+ id: id,
440
+ binding
441
+ });
442
+ }
443
+ }
444
+ return resources;
445
+ }
446
+ function resourceEntityId(memberId, binding) {
447
+ if (binding.kind === "skill") {
448
+ return `resource.${memberId}.skill.${binding.skill.name}`;
449
+ }
450
+ if (binding.kind === "plugin") {
451
+ return `resource.${memberId}.plugin.${binding.plugin.id}`;
452
+ }
453
+ return `resource.${memberId}.package.${binding.lock.kind}.${binding.lock.key}.${binding.lock.version}`;
454
+ }
455
+ function cloneExecutorEntity(executor) {
456
+ if (executor.kind === "member") {
457
+ return { ...cloneMemberEntity(executor), packageLock: executor.packageLock ? { ...executor.packageLock } : undefined };
458
+ }
459
+ return {
460
+ ...executor,
461
+ planner: {
462
+ promptTemplate: { ...executor.planner.promptTemplate },
463
+ maxAttemptCount: executor.planner.maxAttemptCount,
464
+ guardrails: executor.planner.guardrails?.map(guardrail => ({ ...guardrail }))
465
+ },
466
+ members: executor.members.map(membership => ({
467
+ executorId: membership.executorId,
468
+ visibleProfile: {
469
+ ...membership.visibleProfile,
470
+ capabilities: membership.visibleProfile.capabilities ? [...membership.visibleProfile.capabilities] : undefined
471
+ }
472
+ })),
473
+ packageLock: executor.packageLock ? { ...executor.packageLock } : undefined
474
+ };
475
+ }
476
+ function cloneMemberEntity(member) {
477
+ return {
478
+ kind: "member",
479
+ id: member.id,
480
+ promptTemplate: { ...member.promptTemplate },
481
+ resources: member.resources.map(cloneResourceBinding),
482
+ runtimePolicy: {
483
+ model: member.runtimePolicy.model,
484
+ reasoningEffort: member.runtimePolicy.reasoningEffort,
485
+ serviceTier: member.runtimePolicy.serviceTier,
486
+ execution: cloneMemberExecution(member.runtimePolicy.execution),
487
+ approval: cloneMemberApproval(member.runtimePolicy.approval)
488
+ }
489
+ };
490
+ }
491
+ function cloneResourceBinding(binding) {
492
+ if (binding.kind === "skill") {
493
+ return { kind: "skill", skill: cloneSkill(binding.skill) };
494
+ }
495
+ if (binding.kind === "plugin") {
496
+ return { kind: "plugin", plugin: { ...binding.plugin } };
497
+ }
498
+ return { kind: "package", lock: { ...binding.lock } };
499
+ }
500
+ function cloneResourceEntity(resource) {
501
+ return {
502
+ id: resource.id,
503
+ binding: cloneResourceBinding(resource.binding),
504
+ packageLock: resource.packageLock ? { ...resource.packageLock } : undefined
505
+ };
506
+ }
507
+ export function validateSkillBinding(value, path) {
508
+ const validation = validateSkillBindingResult(value, path);
509
+ if (!validation.ok) {
510
+ throw new DomainInvariantError(validation.error.message);
511
+ }
512
+ }
513
+ function validateHarnessSnapshotShape(protocol, kind, countField) {
514
+ for (const validation of [
515
+ positiveIntegerResult(protocol[countField], `${kind}.${countField}`),
516
+ guardrailsResult(protocol.guardrails, `${kind}.guardrails`),
517
+ teamConfigResult(protocol.team, `${kind}.team`),
518
+ memberArrayResult(protocol.members, `${kind}.members`, true)
519
+ ]) {
520
+ if (!validation.ok) {
521
+ return validation;
522
+ }
523
+ }
524
+ return ok(cloneHarness(protocol));
525
+ }
526
+ function normalizeHarnessKind(protocol) {
527
+ return protocol.kind === "team_execution_plan"
528
+ ? { ...protocol, kind: "team_execution_plan" }
529
+ : protocol;
530
+ }
531
+ function validateHarnessPlannerSnapshotShape(protocol, kind, countField) {
532
+ if (Object.prototype.hasOwnProperty.call(protocol, "members")) {
533
+ return workflowValidationError(`${kind}.members is stored in executors.json, not harness.json`);
534
+ }
535
+ for (const validation of [
536
+ positiveIntegerResult(protocol[countField], `${kind}.${countField}`),
537
+ guardrailsResult(protocol.guardrails, `${kind}.guardrails`),
538
+ teamConfigResult(protocol.team, `${kind}.team`)
539
+ ]) {
540
+ if (!validation.ok) {
541
+ return validation;
542
+ }
543
+ }
544
+ return ok(cloneHarnessPlanner(protocol));
545
+ }
546
+ function validateSkillBindingResult(value, path) {
547
+ const skill = recordResult(value, path);
548
+ if (!skill.ok)
549
+ return skill;
550
+ if (skill.value.kind !== "local-snapshot" && skill.value.kind !== "local-root-installed" && skill.value.kind !== "registry-package" && skill.value.kind !== "skillMeta") {
551
+ return workflowValidationError(`${path}.kind must be local-snapshot, local-root-installed, registry-package, or skillMeta`);
552
+ }
553
+ if (typeof skill.value.name !== "string" || skill.value.name.trim() === "") {
554
+ return workflowValidationError(`${path}.name must be a non-empty string`);
555
+ }
556
+ if (skill.value.kind === "skillMeta") {
557
+ const exactKeys = exactKeysResult(skill.value, ["agent", "kind", "name", "source"], path);
558
+ if (!exactKeys.ok)
559
+ return exactKeys;
560
+ if (typeof skill.value.source !== "string" || skill.value.source.trim() === "") {
561
+ return workflowValidationError(`${path}.source must be a non-empty string`);
562
+ }
563
+ if (skill.value.agent !== "codex") {
564
+ return workflowValidationError(`${path}.agent must be codex`);
565
+ }
566
+ return ok(undefined);
567
+ }
568
+ if (skill.value.kind === "local-root-installed") {
569
+ if (skill.value.sourcePath !== undefined && (typeof skill.value.sourcePath !== "string" || skill.value.sourcePath.trim() === "")) {
570
+ return workflowValidationError(`${path}.sourcePath must be a non-empty string when provided`);
571
+ }
572
+ return ok(undefined);
573
+ }
574
+ if (skill.value.kind === "local-snapshot") {
575
+ for (const key of ["sourcePath", "contentHash", "snapshotRef"]) {
576
+ if (typeof skill.value[key] !== "string" || skill.value[key].trim() === "") {
577
+ return workflowValidationError(`${path}.${key} must be a non-empty string`);
578
+ }
579
+ }
580
+ if (skill.value.snapshotFiles !== undefined) {
581
+ if (!Array.isArray(skill.value.snapshotFiles)) {
582
+ return workflowValidationError(`${path}.snapshotFiles must be an array`);
583
+ }
584
+ for (const [index, file] of skill.value.snapshotFiles.entries()) {
585
+ const snapshotFile = recordResult(file, `${path}.snapshotFiles[${index}]`);
586
+ if (!snapshotFile.ok)
587
+ return snapshotFile;
588
+ if (typeof snapshotFile.value.path !== "string" || snapshotFile.value.path.trim() === "") {
589
+ return workflowValidationError(`${path}.snapshotFiles[${index}].path must be a non-empty string`);
590
+ }
591
+ if (typeof snapshotFile.value.text !== "string") {
592
+ return workflowValidationError(`${path}.snapshotFiles[${index}].text must be a string`);
593
+ }
594
+ }
595
+ }
596
+ return ok(undefined);
597
+ }
598
+ if (skill.value.registryKind !== "apm") {
599
+ return workflowValidationError(`${path}.registryKind must be apm`);
600
+ }
601
+ for (const key of ["registry", "package", "version", "integrity", "contentHash"]) {
602
+ if (typeof skill.value[key] !== "string" || skill.value[key].trim() === "") {
603
+ return workflowValidationError(`${path}.${key} must be a non-empty string`);
604
+ }
605
+ }
606
+ if (!isExactSemver(String(skill.value.version))) {
607
+ return workflowValidationError(`${path}.version must be an exact semver version`);
608
+ }
609
+ if (skill.value.snapshotFiles !== undefined) {
610
+ return workflowValidationError(`${path}.snapshotFiles is not supported for registry-package skills`);
611
+ }
612
+ return ok(undefined);
613
+ }
614
+ function validateMemberPluginBindingResult(value, path) {
615
+ const plugin = recordResult(value, path);
616
+ if (!plugin.ok)
617
+ return plugin;
618
+ if (plugin.value.kind !== "local-root-installed") {
619
+ return workflowValidationError(`${path}.kind must be local-root-installed`);
620
+ }
621
+ if (typeof plugin.value.id !== "string" || plugin.value.id.trim() === "") {
622
+ return workflowValidationError(`${path}.id must be a non-empty string`);
623
+ }
624
+ return ok(undefined);
625
+ }
626
+ function teamConfigResult(value, path) {
627
+ const team = recordResult(value, path);
628
+ if (!team.ok)
629
+ return team;
630
+ const template = decodePromptTemplate(team.value.promptTemplate, `${path}.promptTemplate`);
631
+ return template.ok ? ok(undefined) : template;
632
+ }
633
+ function memberArrayResult(value, path, requireNonEmpty) {
634
+ if (!Array.isArray(value)) {
635
+ return workflowValidationError(`${path} must be an array`);
636
+ }
637
+ if (requireNonEmpty && value.length === 0) {
638
+ return workflowValidationError(`${path} must include at least one Member`);
639
+ }
640
+ const seen = new Set();
641
+ for (const [index, member] of value.entries()) {
642
+ const record = recordResult(member, `${path}[${index}]`);
643
+ if (!record.ok)
644
+ return record;
645
+ const validation = memberConfigResult(record.value, `${path}[${index}]`);
646
+ if (!validation.ok)
647
+ return validation;
648
+ const id = String(record.value.id);
649
+ if (seen.has(id)) {
650
+ return workflowValidationError(`${path} must not include duplicate Member ${id}`);
651
+ }
652
+ seen.add(id);
653
+ }
654
+ return ok(undefined);
655
+ }
656
+ function memberConfigResult(member, path) {
657
+ if (typeof member.id !== "string" || member.id.trim() === "") {
658
+ return workflowValidationError(`${path}.id must be a non-empty string`);
659
+ }
660
+ const promptTemplate = decodePromptTemplate(member.promptTemplate, `${path}.promptTemplate`);
661
+ if (!promptTemplate.ok)
662
+ return promptTemplate;
663
+ if (typeof member.model !== "string" || member.model.trim() === "") {
664
+ return workflowValidationError(`${path}.model must be a non-empty string`);
665
+ }
666
+ for (const validation of [
667
+ allowedResult(`${path}.reasoningEffort`, member.reasoningEffort, REASONING_EFFORTS),
668
+ member.serviceTier === undefined ? ok(undefined) : allowedResult(`${path}.serviceTier`, member.serviceTier, SERVICE_TIERS),
669
+ memberExecutionResult(member.execution, `${path}.execution`),
670
+ memberApprovalResult(member.approval, `${path}.approval`)
671
+ ]) {
672
+ if (!validation.ok) {
673
+ return validation;
674
+ }
675
+ }
676
+ if (!Array.isArray(member.skills)) {
677
+ return workflowValidationError(`${path}.skills must be an array`);
678
+ }
679
+ for (const [index, skill] of member.skills.entries()) {
680
+ const validation = validateSkillBindingResult(skill, `${path}.skills[${index}]`);
681
+ if (!validation.ok)
682
+ return validation;
683
+ }
684
+ if (member.plugins !== undefined) {
685
+ if (!Array.isArray(member.plugins)) {
686
+ return workflowValidationError(`${path}.plugins must be an array`);
687
+ }
688
+ for (const [index, plugin] of member.plugins.entries()) {
689
+ const validation = validateMemberPluginBindingResult(plugin, `${path}.plugins[${index}]`);
690
+ if (!validation.ok)
691
+ return validation;
692
+ }
693
+ }
694
+ return ok(undefined);
695
+ }
696
+ function managerConfigResult(manager, path) {
697
+ const keys = exactKeysResult(manager, ["id", "plugins", "promptTemplate", "skills"], path);
698
+ if (!keys.ok)
699
+ return keys;
700
+ if (typeof manager.id !== "string" || manager.id.trim() === "") {
701
+ return workflowValidationError(`${path}.id must be a non-empty string`);
702
+ }
703
+ const promptTemplate = decodePromptTemplate(manager.promptTemplate, `${path}.promptTemplate`);
704
+ if (!promptTemplate.ok)
705
+ return promptTemplate;
706
+ if (!Array.isArray(manager.skills)) {
707
+ return workflowValidationError(`${path}.skills must be an array`);
708
+ }
709
+ for (const [index, skill] of manager.skills.entries()) {
710
+ const validation = validateSkillBindingResult(skill, `${path}.skills[${index}]`);
711
+ if (!validation.ok)
712
+ return validation;
713
+ }
714
+ if (!Array.isArray(manager.plugins)) {
715
+ return workflowValidationError(`${path}.plugins must be an array`);
716
+ }
717
+ for (const [index, plugin] of manager.plugins.entries()) {
718
+ const validation = validateMemberPluginBindingResult(plugin, `${path}.plugins[${index}]`);
719
+ if (!validation.ok)
720
+ return validation;
721
+ }
722
+ return ok(undefined);
723
+ }
724
+ function executorEntityResult(value, path) {
725
+ const executor = recordResult(value, path);
726
+ if (!executor.ok)
727
+ return executor;
728
+ if (executor.value.kind === "team") {
729
+ return teamExecutorResult(executor.value, path);
730
+ }
731
+ if (executor.value.kind === "member") {
732
+ return memberExecutorResult(executor.value, path);
733
+ }
734
+ return workflowValidationError(`${path}.kind must be team or member`);
735
+ }
736
+ function teamExecutorResult(team, path) {
737
+ if (typeof team.id !== "string" || team.id.trim() === "") {
738
+ return workflowValidationError(`${path}.id must be a non-empty string`);
739
+ }
740
+ const planner = teamPlannerResult(team.planner, `${path}.planner`);
741
+ if (!planner.ok)
742
+ return planner;
743
+ if (!Array.isArray(team.members)) {
744
+ return workflowValidationError(`${path}.members must be an array`);
745
+ }
746
+ const seen = new Set();
747
+ for (const [index, membership] of team.members.entries()) {
748
+ const validation = membershipResult(membership, `${path}.members[${index}]`);
749
+ if (!validation.ok)
750
+ return validation;
751
+ const executorId = String(membership.executorId);
752
+ if (seen.has(executorId)) {
753
+ return workflowValidationError(`${path}.members must not include duplicate Membership ${executorId}`);
754
+ }
755
+ seen.add(executorId);
756
+ }
757
+ return packageLockResult(team.packageLock, `${path}.packageLock`);
758
+ }
759
+ function teamPlannerResult(value, path) {
760
+ const planner = recordResult(value, path);
761
+ if (!planner.ok)
762
+ return planner;
763
+ const template = decodePromptTemplate(planner.value.promptTemplate, `${path}.promptTemplate`);
764
+ if (!template.ok)
765
+ return template;
766
+ if (planner.value.maxAttemptCount !== undefined) {
767
+ const maxAttemptCount = positiveIntegerResult(planner.value.maxAttemptCount, `${path}.maxAttemptCount`);
768
+ if (!maxAttemptCount.ok)
769
+ return maxAttemptCount;
770
+ }
771
+ return guardrailsResult(planner.value.guardrails, `${path}.guardrails`);
772
+ }
773
+ function membershipResult(value, path) {
774
+ const membership = recordResult(value, path);
775
+ if (!membership.ok)
776
+ return membership;
777
+ if (typeof membership.value.executorId !== "string" || membership.value.executorId.trim() === "") {
778
+ return workflowValidationError(`${path}.executorId must be a non-empty string`);
779
+ }
780
+ return visibleProfileResult(membership.value.visibleProfile, `${path}.visibleProfile`);
781
+ }
782
+ function visibleProfileResult(value, path) {
783
+ const profile = recordResult(value, path);
784
+ if (!profile.ok)
785
+ return profile;
786
+ const kind = allowedResult(`${path}.kind`, profile.value.kind, ["team", "member"]);
787
+ if (!kind.ok)
788
+ return kind;
789
+ for (const key of ["label", "summary"]) {
790
+ if (profile.value[key] !== undefined && (typeof profile.value[key] !== "string" || String(profile.value[key]).trim() === "")) {
791
+ return workflowValidationError(`${path}.${key} must be a non-empty string when provided`);
792
+ }
793
+ }
794
+ if (profile.value.capabilities !== undefined) {
795
+ if (!Array.isArray(profile.value.capabilities)) {
796
+ return workflowValidationError(`${path}.capabilities must be an array`);
797
+ }
798
+ for (const [index, capability] of profile.value.capabilities.entries()) {
799
+ if (typeof capability !== "string" || capability.trim() === "") {
800
+ return workflowValidationError(`${path}.capabilities[${index}] must be a non-empty string`);
801
+ }
802
+ }
803
+ }
804
+ return ok(undefined);
805
+ }
806
+ function memberExecutorResult(member, path) {
807
+ if (typeof member.id !== "string" || member.id.trim() === "") {
808
+ return workflowValidationError(`${path}.id must be a non-empty string`);
809
+ }
810
+ const promptTemplate = decodePromptTemplate(member.promptTemplate, `${path}.promptTemplate`);
811
+ if (!promptTemplate.ok)
812
+ return promptTemplate;
813
+ if (!Array.isArray(member.resources)) {
814
+ return workflowValidationError(`${path}.resources must be an array`);
815
+ }
816
+ for (const [index, resource] of member.resources.entries()) {
817
+ const validation = resourceBindingResult(resource, `${path}.resources[${index}]`);
818
+ if (!validation.ok)
819
+ return validation;
820
+ }
821
+ const runtimePolicy = runtimePolicyResult(member.runtimePolicy, `${path}.runtimePolicy`);
822
+ if (!runtimePolicy.ok)
823
+ return runtimePolicy;
824
+ return packageLockResult(member.packageLock, `${path}.packageLock`);
825
+ }
826
+ function runtimePolicyResult(value, path) {
827
+ const policy = recordResult(value, path);
828
+ if (!policy.ok)
829
+ return policy;
830
+ if (typeof policy.value.model !== "string" || policy.value.model.trim() === "") {
831
+ return workflowValidationError(`${path}.model must be a non-empty string`);
832
+ }
833
+ for (const validation of [
834
+ allowedResult(`${path}.reasoningEffort`, policy.value.reasoningEffort, REASONING_EFFORTS),
835
+ policy.value.serviceTier === undefined ? ok(undefined) : allowedResult(`${path}.serviceTier`, policy.value.serviceTier, SERVICE_TIERS),
836
+ memberExecutionResult(policy.value.execution, `${path}.execution`),
837
+ memberApprovalResult(policy.value.approval, `${path}.approval`)
838
+ ]) {
839
+ if (!validation.ok) {
840
+ return validation;
841
+ }
842
+ }
843
+ return ok(undefined);
844
+ }
845
+ function resourceEntityResult(value, path) {
846
+ const resource = recordResult(value, path);
847
+ if (!resource.ok)
848
+ return resource;
849
+ if (typeof resource.value.id !== "string" || resource.value.id.trim() === "") {
850
+ return workflowValidationError(`${path}.id must be a non-empty string`);
851
+ }
852
+ const binding = resourceBindingResult(resource.value.binding, `${path}.binding`);
853
+ if (!binding.ok)
854
+ return binding;
855
+ return packageLockResult(resource.value.packageLock, `${path}.packageLock`);
856
+ }
857
+ function resourceBindingResult(value, path) {
858
+ const binding = recordResult(value, path);
859
+ if (!binding.ok)
860
+ return binding;
861
+ if (binding.value.kind === "skill") {
862
+ return validateSkillBindingResult(binding.value.skill, `${path}.skill`);
863
+ }
864
+ if (binding.value.kind === "plugin") {
865
+ return validateMemberPluginBindingResult(binding.value.plugin, `${path}.plugin`);
866
+ }
867
+ if (binding.value.kind === "package") {
868
+ return packageLockResult(binding.value.lock, `${path}.lock`);
869
+ }
870
+ return workflowValidationError(`${path}.kind must be skill, plugin, or package`);
871
+ }
872
+ function packageLockResult(value, path) {
873
+ if (value === undefined) {
874
+ return ok(undefined);
875
+ }
876
+ const lock = recordResult(value, path);
877
+ if (!lock.ok)
878
+ return lock;
879
+ for (const key of ["origin", "kind", "key", "version", "integrity"]) {
880
+ if (typeof lock.value[key] !== "string" || String(lock.value[key]).trim() === "") {
881
+ return workflowValidationError(`${path}.${key} must be a non-empty string`);
882
+ }
883
+ }
884
+ return ok(undefined);
885
+ }
886
+ function memberExecutionResult(value, path) {
887
+ const execution = recordResult(value, path);
888
+ if (!execution.ok)
889
+ return execution;
890
+ const kind = allowedResult(`${path}.kind`, execution.value.kind, MEMBER_EXECUTION_KINDS);
891
+ if (!kind.ok)
892
+ return kind;
893
+ const network = allowedResult(`${path}.network`, execution.value.network, MEMBER_EXECUTION_NETWORKS);
894
+ return network.ok ? ok(undefined) : network;
895
+ }
896
+ function memberApprovalResult(value, path) {
897
+ const approval = recordResult(value, path);
898
+ if (!approval.ok)
899
+ return approval;
900
+ const policy = allowedResult(`${path}.policy`, approval.value.policy, MEMBER_APPROVAL_POLICIES);
901
+ if (!policy.ok)
902
+ return policy;
903
+ if (approval.value.policy === "on_request") {
904
+ const reviewer = allowedResult(`${path}.reviewer`, approval.value.reviewer, MEMBER_APPROVAL_REVIEWERS);
905
+ return reviewer.ok ? ok(undefined) : reviewer;
906
+ }
907
+ return "reviewer" in approval.value
908
+ ? workflowValidationError(`${path}.reviewer is only allowed when policy is on_request`)
909
+ : ok(undefined);
910
+ }
911
+ function guardrailsResult(value, path) {
912
+ if (value === undefined) {
913
+ return ok(undefined);
914
+ }
915
+ if (!Array.isArray(value)) {
916
+ return workflowValidationError(`${path} must be an array`);
917
+ }
918
+ for (const [index, entry] of value.entries()) {
919
+ const guardrail = recordResult(entry, `${path}[${index}]`);
920
+ if (!guardrail.ok)
921
+ return guardrail;
922
+ if (typeof guardrail.value.name !== "string" || guardrail.value.name.trim() === "") {
923
+ return workflowValidationError(`${path}[${index}].name must be a non-empty string`);
924
+ }
925
+ const scope = allowedResult(`${path}[${index}].scope`, guardrail.value.scope, GUARDRAIL_SCOPES);
926
+ if (!scope.ok)
927
+ return scope;
928
+ if (typeof guardrail.value.rule !== "string" || guardrail.value.rule.trim() === "") {
929
+ return workflowValidationError(`${path}[${index}].rule must be a non-empty string`);
930
+ }
931
+ const severity = allowedResult(`${path}[${index}].severity`, guardrail.value.severity, GUARDRAIL_SEVERITIES);
932
+ if (!severity.ok)
933
+ return severity;
934
+ }
935
+ return ok(undefined);
936
+ }
937
+ function recordResult(value, path) {
938
+ return value && typeof value === "object" && !Array.isArray(value)
939
+ ? ok(value)
940
+ : workflowValidationError(`${path} must be an object`);
941
+ }
942
+ function exactKeysResult(record, allowedKeys, path) {
943
+ const missing = allowedKeys.filter(key => !(key in record));
944
+ if (missing.length > 0) {
945
+ return workflowValidationError(`${path} is missing required keys: ${missing.join(", ")}`);
946
+ }
947
+ const extra = Object.keys(record).filter(key => !allowedKeys.includes(key));
948
+ return extra.length > 0
949
+ ? workflowValidationError(`${path} has unsupported keys: ${extra.join(", ")}`)
950
+ : ok(undefined);
951
+ }
952
+ function positiveIntegerResult(value, path) {
953
+ return Number.isInteger(value) && Number(value) >= 1
954
+ ? ok(undefined)
955
+ : workflowValidationError(`${path} must be a positive integer`);
956
+ }
957
+ function allowedResult(path, value, allowed) {
958
+ return typeof value === "string" && allowed.includes(value)
959
+ ? ok(value)
960
+ : workflowValidationError(`${path} must be one of: ${allowed.join(", ")}`);
961
+ }
962
+ function chainResult(result, next) {
963
+ return result.ok ? next() : result;
964
+ }
965
+ function workflowValidationError(message) {
966
+ return err({ type: "DomainWorkflowError", message });
967
+ }
968
+ function requireHarnessMemberConfig(protocol, executorId) {
969
+ const member = getHarnessMemberConfig(protocol, executorId);
970
+ if (!member) {
971
+ throw new DomainInvariantError(`Unknown Member ${executorId} for Harness ${protocol.kind}`);
972
+ }
973
+ return member;
974
+ }
975
+ function validateHubPackageLock(lock, expectedKind, label) {
976
+ assertText(lock.origin, `${label} origin must be non-empty`);
977
+ if (lock.kind !== expectedKind) {
978
+ throw new DomainInvariantError(`${label} kind must be ${expectedKind}`);
979
+ }
980
+ assertText(lock.key, `${label} key must be non-empty`);
981
+ assertText(lock.version, `${label} version must be non-empty`);
982
+ assertText(lock.integrity, `${label} integrity must be non-empty`);
983
+ }
984
+ function validateTeamConfig(value, path) {
985
+ const team = requireRecord(value, path);
986
+ assertValidPromptTemplate(team.promptTemplate, `${path}.promptTemplate`);
987
+ }
988
+ function validateMemberArray(value, path, requireNonEmpty) {
989
+ if (!Array.isArray(value)) {
990
+ throw new DomainInvariantError(`${path} must be an array`);
991
+ }
992
+ if (requireNonEmpty && value.length === 0) {
993
+ throw new DomainInvariantError(`${path} must include at least one Member`);
994
+ }
995
+ const seen = new Set();
996
+ value.forEach((member, index) => {
997
+ const record = requireRecord(member, `${path}[${index}]`);
998
+ assertValidMemberConfig(record, `${path}[${index}]`);
999
+ const id = String(record.id);
1000
+ if (seen.has(id)) {
1001
+ throw new DomainInvariantError(`${path} must not include duplicate Member ${id}`);
1002
+ }
1003
+ seen.add(id);
1004
+ });
1005
+ }
1006
+ function assertValidMemberConfig(member, path) {
1007
+ if (typeof member.id !== "string" || member.id.trim() === "") {
1008
+ throw new DomainInvariantError(`${path}.id must be a non-empty string`);
1009
+ }
1010
+ assertValidPromptTemplate(member.promptTemplate, `${path}.promptTemplate`);
1011
+ if (typeof member.model !== "string" || member.model.trim() === "") {
1012
+ throw new DomainInvariantError(`${path}.model must be a non-empty string`);
1013
+ }
1014
+ assertAllowed(`${path}.reasoningEffort`, member.reasoningEffort, REASONING_EFFORTS);
1015
+ if (member.serviceTier !== undefined) {
1016
+ assertAllowed(`${path}.serviceTier`, member.serviceTier, SERVICE_TIERS);
1017
+ }
1018
+ validateMemberExecution(member.execution, `${path}.execution`);
1019
+ validateMemberApproval(member.approval, `${path}.approval`);
1020
+ if (!Array.isArray(member.skills)) {
1021
+ throw new DomainInvariantError(`${path}.skills must be an array`);
1022
+ }
1023
+ member.skills.forEach((skill, index) => validateSkillBinding(skill, `${path}.skills[${index}]`));
1024
+ if (member.plugins !== undefined) {
1025
+ if (!Array.isArray(member.plugins)) {
1026
+ throw new DomainInvariantError(`${path}.plugins must be an array`);
1027
+ }
1028
+ member.plugins.forEach((plugin, index) => validateMemberPluginBinding(plugin, `${path}.plugins[${index}]`));
1029
+ }
1030
+ }
1031
+ function validateMemberPluginBinding(value, path) {
1032
+ const validation = validateMemberPluginBindingResult(value, path);
1033
+ if (!validation.ok) {
1034
+ throw new DomainInvariantError(validation.error.message);
1035
+ }
1036
+ }
1037
+ function validateMemberExecution(value, path) {
1038
+ const execution = requireRecord(value, path);
1039
+ assertAllowed(`${path}.kind`, execution.kind, MEMBER_EXECUTION_KINDS);
1040
+ assertAllowed(`${path}.network`, execution.network, MEMBER_EXECUTION_NETWORKS);
1041
+ }
1042
+ function validateMemberApproval(value, path) {
1043
+ const approval = requireRecord(value, path);
1044
+ assertAllowed(`${path}.policy`, approval.policy, MEMBER_APPROVAL_POLICIES);
1045
+ if (approval.policy === "on_request") {
1046
+ assertAllowed(`${path}.reviewer`, approval.reviewer, MEMBER_APPROVAL_REVIEWERS);
1047
+ return;
1048
+ }
1049
+ if ("reviewer" in approval) {
1050
+ throw new DomainInvariantError(`${path}.reviewer is only allowed when policy is on_request`);
1051
+ }
1052
+ }
1053
+ function validateGuardrails(value, path) {
1054
+ if (value === undefined) {
1055
+ return;
1056
+ }
1057
+ if (!Array.isArray(value)) {
1058
+ throw new DomainInvariantError(`${path} must be an array`);
1059
+ }
1060
+ value.forEach((entry, index) => {
1061
+ const guardrail = requireRecord(entry, `${path}[${index}]`);
1062
+ if (typeof guardrail.name !== "string" || guardrail.name.trim() === "") {
1063
+ throw new DomainInvariantError(`${path}[${index}].name must be a non-empty string`);
1064
+ }
1065
+ assertAllowed(`${path}[${index}].scope`, guardrail.scope, GUARDRAIL_SCOPES);
1066
+ if (typeof guardrail.rule !== "string" || guardrail.rule.trim() === "") {
1067
+ throw new DomainInvariantError(`${path}[${index}].rule must be a non-empty string`);
1068
+ }
1069
+ assertAllowed(`${path}[${index}].severity`, guardrail.severity, GUARDRAIL_SEVERITIES);
1070
+ });
1071
+ }
1072
+ function requireRecord(value, path) {
1073
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1074
+ throw new DomainInvariantError(`${path} must be an object`);
1075
+ }
1076
+ return value;
1077
+ }
1078
+ function assertPositiveInteger(value, path) {
1079
+ if (!Number.isInteger(value) || Number(value) < 1) {
1080
+ throw new DomainInvariantError(`${path} must be a positive integer`);
1081
+ }
1082
+ }
1083
+ function assertAllowed(path, value, allowed) {
1084
+ if (typeof value !== "string" || !allowed.includes(value)) {
1085
+ throw new DomainInvariantError(`${path} must be one of: ${allowed.join(", ")}`);
1086
+ }
1087
+ }
1088
+ function assertText(value, message) {
1089
+ if (value.trim() === "") {
1090
+ throw new DomainInvariantError(message);
1091
+ }
1092
+ }
1093
+ function assertString(value, message) {
1094
+ if (typeof value !== "string") {
1095
+ throw new DomainInvariantError(message);
1096
+ }
1097
+ }
1098
+ function cloneMemberConfig(member) {
1099
+ return {
1100
+ ...member,
1101
+ promptTemplate: { ...member.promptTemplate },
1102
+ skills: cloneSkills(member.skills),
1103
+ plugins: clonePlugins(member.plugins ?? []),
1104
+ execution: cloneMemberExecution(member.execution),
1105
+ approval: cloneMemberApproval(member.approval)
1106
+ };
1107
+ }
1108
+ function cloneManagerConfig(manager) {
1109
+ return {
1110
+ ...manager,
1111
+ promptTemplate: { ...manager.promptTemplate },
1112
+ skills: cloneSkills(manager.skills),
1113
+ plugins: clonePlugins(manager.plugins)
1114
+ };
1115
+ }
1116
+ function cloneMemberExecution(execution) {
1117
+ return { ...execution };
1118
+ }
1119
+ function cloneMemberApproval(approval) {
1120
+ return { ...approval };
1121
+ }
1122
+ function cloneSkills(skills = []) {
1123
+ return skills.map(cloneSkill);
1124
+ }
1125
+ function cloneSkill(skill) {
1126
+ if (skill.kind === "local-snapshot") {
1127
+ return {
1128
+ ...skill,
1129
+ snapshotFiles: skill.snapshotFiles?.map(file => ({ ...file }))
1130
+ };
1131
+ }
1132
+ return { ...skill };
1133
+ }
1134
+ function clonePlugins(plugins = []) {
1135
+ return plugins.map(plugin => ({ ...plugin }));
1136
+ }
1137
+ function isExactSemver(value) {
1138
+ return /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(value);
1139
+ }