@agilno-tech/rivet 0.1.0-alpha.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 (283) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +6 -0
  3. package/README.md +59 -0
  4. package/bin/cli.js +42 -0
  5. package/dist/agents/backend-django-agent/SKILL.md +35 -0
  6. package/dist/agents/backend-nestjs-agent/SKILL.md +36 -0
  7. package/dist/agents/boss-agent/SKILL.md +28 -0
  8. package/dist/agents/bounded-worker/SKILL.md +28 -0
  9. package/dist/agents/data-performance-agent/SKILL.md +32 -0
  10. package/dist/agents/delivery-ticketing-agent/SKILL.md +36 -0
  11. package/dist/agents/devops-agent/SKILL.md +34 -0
  12. package/dist/agents/engineering-manager/SKILL.md +28 -0
  13. package/dist/agents/frontend-nextjs-agent/SKILL.md +34 -0
  14. package/dist/agents/frontend-web-agent/SKILL.md +31 -0
  15. package/dist/agents/incident-response-agent/SKILL.md +42 -0
  16. package/dist/agents/jest-agent/SKILL.md +77 -0
  17. package/dist/agents/mobile-agent/SKILL.md +32 -0
  18. package/dist/agents/playwright-agent/SKILL.md +54 -0
  19. package/dist/agents/product-design-manager/SKILL.md +27 -0
  20. package/dist/agents/qa-agent/SKILL.md +29 -0
  21. package/dist/agents/quality-manager/SKILL.md +28 -0
  22. package/dist/agents/vitest-agent/SKILL.md +77 -0
  23. package/dist/governance/pre-push-rules.md +75 -0
  24. package/dist/governance/prompt-hygiene.md +22 -0
  25. package/dist/governance/review-checklist.md +24 -0
  26. package/dist/governance/safety-and-data.md +27 -0
  27. package/dist/governance/usage-rules.md +21 -0
  28. package/dist/mandatory/address-pr-feedback/SKILL.md +196 -0
  29. package/dist/mandatory/agentic-goal/SKILL.md +42 -0
  30. package/dist/mandatory/agentic-status/SKILL.md +74 -0
  31. package/dist/mandatory/apply-design/SKILL.md +228 -0
  32. package/dist/mandatory/check-ac/SKILL.md +124 -0
  33. package/dist/mandatory/create-pr-and-commit/SKILL.md +304 -0
  34. package/dist/mandatory/design/SKILL.md +312 -0
  35. package/dist/mandatory/feature-workflow/SKILL.md +87 -0
  36. package/dist/mandatory/hotfix/SKILL.md +195 -0
  37. package/dist/mandatory/pre-push/SKILL.md +272 -0
  38. package/dist/mandatory/project-context/SKILL.md +180 -0
  39. package/dist/mandatory/release-docs/SKILL.md +191 -0
  40. package/dist/mandatory/review-pr/SKILL.md +323 -0
  41. package/dist/mandatory/security-review/SKILL.md +157 -0
  42. package/dist/skills/api-contract/SKILL.md +77 -0
  43. package/dist/skills/backend-django/SKILL.md +27 -0
  44. package/dist/skills/backend-nestjs/SKILL.md +65 -0
  45. package/dist/skills/bug-ticket-creation/SKILL.md +43 -0
  46. package/dist/skills/cloudwatch-troubleshooting/SKILL.md +73 -0
  47. package/dist/skills/database-migration/SKILL.md +97 -0
  48. package/dist/skills/debugging/SKILL.md +32 -0
  49. package/dist/skills/devops-infra/SKILL.md +26 -0
  50. package/dist/skills/documentation/SKILL.md +24 -0
  51. package/dist/skills/frontend-nextjs/SKILL.md +27 -0
  52. package/dist/skills/incident-postmortem/SKILL.md +38 -0
  53. package/dist/skills/kubernetes-troubleshooting/SKILL.md +71 -0
  54. package/dist/skills/mobile-react-native/SKILL.md +37 -0
  55. package/dist/skills/postgres-analytics/SKILL.md +29 -0
  56. package/dist/skills/product-jira-ticketing/SKILL.md +28 -0
  57. package/dist/skills/qa-bug-analysis/SKILL.md +27 -0
  58. package/dist/skills/refactoring/SKILL.md +32 -0
  59. package/dist/skills/sprint-planning/SKILL.md +32 -0
  60. package/dist/skills/testing-quality/SKILL.md +62 -0
  61. package/dist/v2/protocols/agent-orchestration.md +49 -0
  62. package/dist/v2/protocols/delivery.md +45 -0
  63. package/dist/v2/protocols/design-authority.md +29 -0
  64. package/dist/v2/protocols/goal-graph.md +44 -0
  65. package/dist/v2/protocols/pattern-first-development.md +29 -0
  66. package/dist/v2/protocols/qa-evidence.md +29 -0
  67. package/dist/v2/protocols/security.md +29 -0
  68. package/dist/v2/schemas/event.schema.json +86 -0
  69. package/dist/v2/schemas/evidence.schema.json +94 -0
  70. package/dist/v2/schemas/feature-decomposition.schema.json +48 -0
  71. package/dist/v2/schemas/feature-plan.schema.json +95 -0
  72. package/dist/v2/schemas/goal-graph.schema.json +61 -0
  73. package/dist/v2/schemas/integration.schema.json +119 -0
  74. package/dist/v2/schemas/orchestration.schema.json +69 -0
  75. package/dist/v2/schemas/project.schema.json +209 -0
  76. package/dist/v2/schemas/providers.schema.json +142 -0
  77. package/dist/v2/schemas/quality.schema.json +50 -0
  78. package/dist/v2/schemas/work-action.schema.json +44 -0
  79. package/dist/v2/schemas/work-request.schema.json +253 -0
  80. package/dist/v2/templates/evidence/qa-bundle.json +194 -0
  81. package/dist/v2/templates/github-actions/rivet-deploy.yml +98 -0
  82. package/dist/v2/templates/harness/SKILL.md +90 -0
  83. package/dist/v2/templates/project/.rivet/orchestration.yaml +42 -0
  84. package/dist/v2/templates/project/.rivet/project.yaml +14 -0
  85. package/dist/v2/templates/project/.rivet/providers.yaml +8 -0
  86. package/dist/v2/templates/project/.rivet/quality.yaml +16 -0
  87. package/package.json +58 -0
  88. package/protocols/agent-orchestration.md +49 -0
  89. package/protocols/delivery.md +45 -0
  90. package/protocols/design-authority.md +29 -0
  91. package/protocols/goal-graph.md +44 -0
  92. package/protocols/pattern-first-development.md +29 -0
  93. package/protocols/qa-evidence.md +29 -0
  94. package/protocols/security.md +29 -0
  95. package/schemas/event.schema.json +86 -0
  96. package/schemas/evidence.schema.json +94 -0
  97. package/schemas/feature-decomposition.schema.json +48 -0
  98. package/schemas/feature-plan.schema.json +95 -0
  99. package/schemas/goal-graph.schema.json +61 -0
  100. package/schemas/integration.schema.json +119 -0
  101. package/schemas/orchestration.schema.json +69 -0
  102. package/schemas/project.schema.json +209 -0
  103. package/schemas/providers.schema.json +142 -0
  104. package/schemas/quality.schema.json +50 -0
  105. package/schemas/work-action.schema.json +44 -0
  106. package/schemas/work-request.schema.json +253 -0
  107. package/src/adapters/confluence.js +138 -0
  108. package/src/adapters/contract.js +657 -0
  109. package/src/adapters/factory.js +122 -0
  110. package/src/adapters/figma.js +178 -0
  111. package/src/adapters/fixtures.js +183 -0
  112. package/src/adapters/github.js +314 -0
  113. package/src/adapters/http.js +704 -0
  114. package/src/adapters/jira.js +195 -0
  115. package/src/adapters/linear.js +171 -0
  116. package/src/adapters/node-transport.js +59 -0
  117. package/src/cli/integration-setup-prompt.js +32 -0
  118. package/src/cli/interrupt.js +25 -0
  119. package/src/cli/main.js +596 -0
  120. package/src/cli/output.js +174 -0
  121. package/src/cli/parse-args.js +246 -0
  122. package/src/cli/project-discovery.js +70 -0
  123. package/src/cli/task-confirmation.js +38 -0
  124. package/src/cli/task-presentation.js +22 -0
  125. package/src/clients/claude.js +214 -0
  126. package/src/clients/codex.js +180 -0
  127. package/src/clients/compatibility.js +23 -0
  128. package/src/clients/contract.js +224 -0
  129. package/src/clients/fake.js +170 -0
  130. package/src/clients/process-runner.js +657 -0
  131. package/src/clients/result-contract.js +78 -0
  132. package/src/commands/delivery-publish.js +80 -0
  133. package/src/commands/delivery-remote.js +230 -0
  134. package/src/commands/delivery-review-update.js +39 -0
  135. package/src/commands/delivery-tracker.js +94 -0
  136. package/src/commands/delivery-transition.js +79 -0
  137. package/src/commands/delivery.js +223 -0
  138. package/src/commands/dependency-approval.js +21 -0
  139. package/src/commands/doctor.js +191 -0
  140. package/src/commands/evidence.js +44 -0
  141. package/src/commands/feature.js +212 -0
  142. package/src/commands/goals.js +231 -0
  143. package/src/commands/human-run.js +208 -0
  144. package/src/commands/human-task.js +278 -0
  145. package/src/commands/init.js +848 -0
  146. package/src/commands/install.js +814 -0
  147. package/src/commands/integration-setup.js +65 -0
  148. package/src/commands/integrations.js +54 -0
  149. package/src/commands/models.js +158 -0
  150. package/src/commands/orchestrate.js +61 -0
  151. package/src/commands/preflight.js +125 -0
  152. package/src/commands/protocols.js +228 -0
  153. package/src/commands/repositories.js +77 -0
  154. package/src/commands/setup-remote.js +41 -0
  155. package/src/commands/setup.js +214 -0
  156. package/src/commands/status.js +224 -0
  157. package/src/commands/support.js +44 -0
  158. package/src/commands/task-completion.js +104 -0
  159. package/src/commands/uninstall.js +234 -0
  160. package/src/commands/verify.js +171 -0
  161. package/src/commands/work.js +220 -0
  162. package/src/commands/worktrees.js +52 -0
  163. package/src/config/command-readiness.js +218 -0
  164. package/src/config/commands.js +206 -0
  165. package/src/config/defaults.js +55 -0
  166. package/src/config/load.js +230 -0
  167. package/src/config/validate.js +560 -0
  168. package/src/delivery/bitbucket-review.js +123 -0
  169. package/src/delivery/branch-publication.js +23 -0
  170. package/src/delivery/contract.js +379 -0
  171. package/src/delivery/github-deployment.js +132 -0
  172. package/src/delivery/github-review.js +5 -0
  173. package/src/delivery/github.js +382 -0
  174. package/src/delivery/gitlab-review.js +5 -0
  175. package/src/delivery/gitlab.js +395 -0
  176. package/src/delivery/prepare.js +90 -0
  177. package/src/delivery/publication-process.js +33 -0
  178. package/src/delivery/publication-transport.js +112 -0
  179. package/src/delivery/review-request.js +184 -0
  180. package/src/delivery/review-update.js +74 -0
  181. package/src/delivery/service.js +400 -0
  182. package/src/delivery/store.js +76 -0
  183. package/src/delivery/tracker-target.js +51 -0
  184. package/src/delivery/tracker-transition.js +158 -0
  185. package/src/delivery/tracker.js +164 -0
  186. package/src/discovery/git.js +85 -0
  187. package/src/discovery/portable.js +108 -0
  188. package/src/discovery/project.js +345 -0
  189. package/src/discovery/tools.js +145 -0
  190. package/src/evaluations/approval.js +20 -0
  191. package/src/evaluations/cost-policy.js +20 -0
  192. package/src/evaluations/harness-attempt.js +113 -0
  193. package/src/evaluations/live-runner.js +55 -0
  194. package/src/evaluations/profile.js +28 -0
  195. package/src/evaluations/report.js +11 -0
  196. package/src/evaluations/text-attempt.js +18 -0
  197. package/src/evidence/checksum.js +87 -0
  198. package/src/evidence/collect.js +727 -0
  199. package/src/evidence/validate.js +354 -0
  200. package/src/feature/accepted-integration.js +46 -0
  201. package/src/feature/actions.js +37 -0
  202. package/src/feature/client-profile.js +56 -0
  203. package/src/feature/decomposition-contract.js +58 -0
  204. package/src/feature/host-execution.js +633 -0
  205. package/src/feature/host-lock-recovery.js +96 -0
  206. package/src/feature/host-run-lock.js +32 -0
  207. package/src/feature/local-approval.js +183 -0
  208. package/src/feature/plan-contract.js +215 -0
  209. package/src/feature/planner.js +253 -0
  210. package/src/feature/run-store.js +241 -0
  211. package/src/feature/runtime-bridge.js +840 -0
  212. package/src/feature/verification-report.js +168 -0
  213. package/src/feature/workflow.js +375 -0
  214. package/src/git/client.js +509 -0
  215. package/src/git/integration-worktree.js +153 -0
  216. package/src/git/reconcile.js +222 -0
  217. package/src/git/reservations.js +450 -0
  218. package/src/git/worktrees.js +478 -0
  219. package/src/graph/completion.js +189 -0
  220. package/src/graph/fixtures.js +116 -0
  221. package/src/graph/reducer.js +124 -0
  222. package/src/graph/scheduler.js +252 -0
  223. package/src/graph/validate.js +106 -0
  224. package/src/install/managed.js +579 -0
  225. package/src/install/project-reference.js +33 -0
  226. package/src/install/project-runtime.js +142 -0
  227. package/src/install/runtime-integrity.cjs +141 -0
  228. package/src/integrations/capabilities.js +69 -0
  229. package/src/integrations/host-observation.js +26 -0
  230. package/src/integrations/registry.js +56 -0
  231. package/src/models/delegate.js +102 -0
  232. package/src/models/profiles.js +53 -0
  233. package/src/models/protocols.js +208 -0
  234. package/src/models/registry.js +83 -0
  235. package/src/models/transport.js +59 -0
  236. package/src/policy/approvals.js +251 -0
  237. package/src/policy/authority.js +282 -0
  238. package/src/policy/budget.js +149 -0
  239. package/src/policy/command-bootstrap.js +219 -0
  240. package/src/policy/commands.js +803 -0
  241. package/src/prompts/launch-contract.js +43 -0
  242. package/src/prompts/planning-contract.js +92 -0
  243. package/src/protocols/presentation.js +53 -0
  244. package/src/protocols/project.js +289 -0
  245. package/src/quality/runner.js +497 -0
  246. package/src/quality/traceability.js +215 -0
  247. package/src/repositories/identity.js +59 -0
  248. package/src/repositories/index.js +2 -0
  249. package/src/repositories/provider.js +210 -0
  250. package/src/runtime/application.js +353 -0
  251. package/src/runtime/dependency-directory.js +33 -0
  252. package/src/runtime/harness-discovery.js +121 -0
  253. package/src/runtime/heartbeat.js +47 -0
  254. package/src/runtime/instance-store.js +112 -0
  255. package/src/runtime/orchestrator.js +1133 -0
  256. package/src/runtime/portable-dependencies.js +94 -0
  257. package/src/runtime/recovery.js +190 -0
  258. package/src/runtime/retry.js +54 -0
  259. package/src/runtime/supervisor.js +166 -0
  260. package/src/runtime/worktree-bootstrap.js +169 -0
  261. package/src/state/event-store.js +333 -0
  262. package/src/state/lock.js +266 -0
  263. package/src/state/paths.js +274 -0
  264. package/src/state/redact.js +106 -0
  265. package/src/state/snapshot-store.js +310 -0
  266. package/src/status/public/app.js +96 -0
  267. package/src/status/public/index.html +37 -0
  268. package/src/status/public/styles.css +50 -0
  269. package/src/status/server.js +323 -0
  270. package/src/status/view-model.js +356 -0
  271. package/src/support/bundle.js +195 -0
  272. package/src/support/failure-report.js +96 -0
  273. package/src/work-request/contract.js +250 -0
  274. package/src/work-request/host.js +78 -0
  275. package/src/work-request/local.js +163 -0
  276. package/src/work-request/tracker.js +105 -0
  277. package/templates/evidence/qa-bundle.json +194 -0
  278. package/templates/github-actions/rivet-deploy.yml +98 -0
  279. package/templates/harness/SKILL.md +90 -0
  280. package/templates/project/.rivet/orchestration.yaml +42 -0
  281. package/templates/project/.rivet/project.yaml +14 -0
  282. package/templates/project/.rivet/providers.yaml +8 -0
  283. package/templates/project/.rivet/quality.yaml +16 -0
@@ -0,0 +1,560 @@
1
+ import { parseRepositoryRemote } from '../repositories/identity.js';
2
+ import { readFileSync } from 'node:fs';
3
+
4
+ import Ajv from 'ajv';
5
+
6
+ import { EVIDENCE_TYPES, SCHEMA_FILES } from './defaults.js';
7
+ import { CommandConfigurationError, compileProjectCommands, compileQualitySteps, compileProjectDependencies } from './commands.js';
8
+
9
+ const ajv = new Ajv({ allErrors: true, strict: true });
10
+ const validators = Object.fromEntries(
11
+ Object.entries(SCHEMA_FILES).map(([name, filename]) => {
12
+ const schema = JSON.parse(readFileSync(new URL(`../../schemas/${filename}`, import.meta.url), 'utf8'));
13
+ return [name, ajv.compile(schema)];
14
+ }),
15
+ );
16
+
17
+ const SAFE_SECRET_KEYS = new Set([
18
+ 'credentials',
19
+ 'apiTokenEnv',
20
+ 'accessTokenEnv',
21
+ 'tokenEnv',
22
+ 'usernameEnv',
23
+ 'tokenLimit',
24
+ ]);
25
+ const SECRET_KEY = /(?:password|passwd|secret|api[-_]?key|authorization|cookie|credential|access[-_]?token|api[-_]?token|\btoken\b)/i;
26
+ const SECRET_VALUE = /^(?:bearer\s+|basic\s+|gh[pousr]_|github_pat_|sk-[A-Za-z0-9]|xox[baprs]-|AKIA[0-9A-Z]{16})/i;
27
+ const ENV_NAME = /^[A-Z][A-Z0-9_]*$/;
28
+ const EVIDENCE_TYPE_SET = new Set(EVIDENCE_TYPES);
29
+ const WINDOWS_RESERVED_PATH_COMPONENT = /^(?:con|prn|aux|nul|conin\$|conout\$|com[1-9]|lpt[1-9])(?:\..*)?$/i;
30
+ const ROLE_AUTHORITY_CEILINGS = Object.freeze({
31
+ boss: new Set(['plan', 'implement', 'delegate', 'verify', 'external-write', 'merge', 'deploy', 'change-authority', 'change-budget']),
32
+ manager: new Set(['implement', 'delegate', 'verify', 'external-write', 'merge']),
33
+ worker: new Set(['implement', 'verify']),
34
+ });
35
+ const ROLE_PERMISSION_CEILINGS = Object.freeze({
36
+ boss: new Set(['externalWrites', 'merge', 'deploy']),
37
+ manager: new Set(['externalWrites', 'merge']),
38
+ worker: new Set(),
39
+ });
40
+ const ROLE_DELEGATION_CEILINGS = Object.freeze({
41
+ boss: new Set(['manager']),
42
+ manager: new Set(['worker']),
43
+ worker: new Set(),
44
+ });
45
+ const HUMAN_FINAL_GATES = new Set(['publication', 'final-delivery']);
46
+
47
+ function safePath(path) {
48
+ if (!path || path === '/') return '/';
49
+ return path
50
+ .split('/')
51
+ .filter(Boolean)
52
+ .map(segment => (/^[A-Za-z0-9_.-]{1,80}$/.test(segment) ? segment : '?'))
53
+ .join('/')
54
+ .replace(/^/, '/');
55
+ }
56
+
57
+ export class ConfigurationError extends Error {
58
+ constructor(path = '/', reason = 'invalid') {
59
+ super(`Tracked configuration is invalid at ${safePath(path)}.`);
60
+ this.name = 'ConfigurationError';
61
+ this.code = 'ERR_INVALID_TRACKED_CONFIG';
62
+ this.safeMessage = this.message;
63
+ this.details = Object.freeze({ path: safePath(path), reason });
64
+ }
65
+ }
66
+
67
+ function fail(path, reason) {
68
+ throw new ConfigurationError(path, reason);
69
+ }
70
+
71
+ function schemaValidate(name, value) {
72
+ const validate = validators[name];
73
+ if (validate(value)) return;
74
+ const path = validate.errors?.[0]?.instancePath || '/';
75
+ fail(path, 'schema');
76
+ }
77
+
78
+ function scanForSecrets(value, path = '') {
79
+ if (!value || typeof value !== 'object') return;
80
+ for (const [key, child] of Object.entries(value)) {
81
+ const childPath = `${path}/${key}`;
82
+ if (SECRET_KEY.test(key) && !SAFE_SECRET_KEYS.has(key)) fail(childPath, 'secret-key');
83
+ if (typeof child === 'string' && SECRET_VALUE.test(child)) fail(childPath, 'secret-value');
84
+ if (typeof child === 'string' && /^https?:\/\//i.test(child)) assertSafeUrl(child, childPath);
85
+ scanForSecrets(child, childPath);
86
+ }
87
+ }
88
+
89
+ function uniqueBy(items, key, path) {
90
+ const seen = new Set();
91
+ for (const item of items) {
92
+ const value = item[key];
93
+ if (seen.has(value)) fail(`${path}/${key}`, 'duplicate-id');
94
+ seen.add(value);
95
+ }
96
+ return seen;
97
+ }
98
+
99
+ function assertSafeUrl(value, path) {
100
+ let parsed;
101
+ try {
102
+ parsed = new URL(value);
103
+ } catch {
104
+ fail(path, 'url');
105
+ }
106
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') fail(path, 'url-scheme');
107
+ if (parsed.username || parsed.password) fail(path, 'url-userinfo');
108
+ if (parsed.hash) fail(path, 'url-fragment');
109
+ let decodedPath = parsed.pathname;
110
+ let stabilized = false;
111
+ for (let pass = 0; pass < 4; pass += 1) {
112
+ let nextPath;
113
+ try {
114
+ nextPath = decodeURIComponent(decodedPath);
115
+ } catch {
116
+ fail(path, 'url-path-encoding');
117
+ }
118
+ if (nextPath === decodedPath) {
119
+ stabilized = true;
120
+ break;
121
+ }
122
+ decodedPath = nextPath;
123
+ }
124
+ if (!stabilized) {
125
+ let nextPath;
126
+ try {
127
+ nextPath = decodeURIComponent(decodedPath);
128
+ } catch {
129
+ fail(path, 'url-path-encoding');
130
+ }
131
+ if (nextPath !== decodedPath) fail(path, 'url-path-encoding-depth');
132
+ }
133
+ if (/%[0-9a-f]{2}/i.test(decodedPath)) fail(path, 'url-path-encoding-depth');
134
+ for (const rawComponent of decodedPath.replace(/\\/g, '/').split('/')) {
135
+ const component = rawComponent.normalize('NFKC');
136
+ if (SECRET_VALUE.test(component)) fail(path, 'url-secret-path');
137
+ const delimiterIndex = component.search(/[=:]/);
138
+ if (delimiterIndex >= 0) {
139
+ const key = component.slice(0, delimiterIndex);
140
+ const componentValue = component.slice(delimiterIndex + 1);
141
+ if (SECRET_KEY.test(key) || SECRET_VALUE.test(componentValue)) fail(path, 'url-secret-path');
142
+ }
143
+ }
144
+ for (const [key, queryValue] of parsed.searchParams) {
145
+ if (SECRET_KEY.test(key) || SECRET_VALUE.test(queryValue)) fail(path, 'url-secret-metadata');
146
+ }
147
+ }
148
+
149
+ function assertTimestamp(value, path) {
150
+ const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,9})?Z$/.exec(value);
151
+ if (!match) fail(path, 'timestamp');
152
+ const date = new Date(value);
153
+ const parts = match.slice(1, 7).map(Number);
154
+ if (
155
+ Number.isNaN(date.getTime())
156
+ || date.getUTCFullYear() !== parts[0]
157
+ || date.getUTCMonth() + 1 !== parts[1]
158
+ || date.getUTCDate() !== parts[2]
159
+ || date.getUTCHours() !== parts[3]
160
+ || date.getUTCMinutes() !== parts[4]
161
+ || date.getUTCSeconds() !== parts[5]
162
+ ) fail(path, 'timestamp');
163
+ }
164
+
165
+ function assertSafeRelativePath(value, path) {
166
+ if (typeof value !== 'string' || value.length === 0) fail(path, 'unsafe-path');
167
+ const canonical = value.normalize('NFKC');
168
+ if (/[\u0000\r\n:]/.test(canonical)) fail(path, 'unsafe-path');
169
+ const normalized = canonical.replace(/\\/g, '/');
170
+ const segments = normalized.split('/');
171
+ if (
172
+ normalized === '.'
173
+ || normalized.startsWith('/')
174
+ || /^[A-Za-z]:/.test(canonical)
175
+ || normalized.includes('//')
176
+ || normalized.endsWith('/')
177
+ || segments.some(segment => (
178
+ segment === ''
179
+ || segment === '.'
180
+ || segment === '..'
181
+ || segment.endsWith('.')
182
+ || segment.endsWith(' ')
183
+ || WINDOWS_RESERVED_PATH_COMPONENT.test(segment)
184
+ ))
185
+ ) fail(path, 'unsafe-path');
186
+ }
187
+
188
+ function validateConfigSemantics(config) {
189
+ scanForSecrets(config);
190
+
191
+ const providerIds = uniqueBy(config.providers.providers, 'id', '/providers/providers');
192
+ if (config.project.deployment && !providerIds.has(config.project.deployment.providerId))
193
+ fail('/project/deployment/providerId', 'provider-reference');
194
+ for (const provider of config.providers.providers) {
195
+ if (provider.endpoint) assertSafeUrl(provider.endpoint, `/providers/providers/${provider.id}/endpoint`);
196
+ for (const [key, envName] of Object.entries(provider.credentials ?? {})) {
197
+ if (!key.endsWith('Env') || !ENV_NAME.test(envName)) fail(`/providers/providers/${provider.id}/credentials/${key}`, 'credential-reference');
198
+ }
199
+ for (const resourceId of provider.resourceIds ?? []) {
200
+ if (/^https?:\/\//i.test(resourceId)) assertSafeUrl(resourceId, `/providers/providers/${provider.id}/resourceIds`);
201
+ else assertSafeRelativePath(resourceId, `/providers/providers/${provider.id}/resourceIds`);
202
+ }
203
+ }
204
+
205
+ try { compileProjectCommands(config.project); }
206
+ catch (error) {
207
+ if (error instanceof CommandConfigurationError) fail(error.path, error.reason);
208
+ fail('/project/commands', 'command');
209
+ }
210
+ for (const path of config.project.repository.sensitivePaths ?? []) assertSafeRelativePath(path, '/project/repository/sensitivePaths');
211
+
212
+ uniqueBy(config.orchestration.roles, 'id', '/orchestration/roles');
213
+ const profileIds = uniqueBy(config.orchestration.completionProfiles, 'id', '/orchestration/completionProfiles');
214
+ const profileMap = new Map(config.orchestration.completionProfiles.map(profile => [profile.id, profile]));
215
+ const kinds = new Set(config.orchestration.roles.map(role => role.kind));
216
+ for (const requiredKind of ['boss', 'manager', 'worker']) {
217
+ if (!kinds.has(requiredKind)) fail('/orchestration/roles', 'missing-role-kind');
218
+ }
219
+ const bossRoles = config.orchestration.roles.filter(role => role.kind === 'boss');
220
+ if (bossRoles.length !== 1) fail('/orchestration/roles', 'boss-count');
221
+ for (const role of config.orchestration.roles) {
222
+ if (role.harness !== undefined && (role.kind !== 'worker' || !['claude', 'codex'].includes(role.harness))) fail(`/orchestration/roles/${role.id}/harness`, 'worker-harness');
223
+ if (role.kind === 'worker' && role.canDelegate) fail(`/orchestration/roles/${role.id}/canDelegate`, 'worker-delegation');
224
+ if (role.canDelegate !== (role.delegatesTo.length > 0)) fail(`/orchestration/roles/${role.id}/delegatesTo`, 'delegation-policy');
225
+ for (const target of role.delegatesTo) {
226
+ if (!ROLE_DELEGATION_CEILINGS[role.kind].has(target)) fail(`/orchestration/roles/${role.id}/delegatesTo`, 'delegation-ceiling');
227
+ }
228
+ for (const scope of role.authorityScopes) {
229
+ if (!ROLE_AUTHORITY_CEILINGS[role.kind].has(scope)) fail(`/orchestration/roles/${role.id}/authorityScopes`, 'authority-ceiling');
230
+ }
231
+ for (const [permission, enabled] of Object.entries(role.permissions)) {
232
+ if (enabled && !ROLE_PERMISSION_CEILINGS[role.kind].has(permission)) {
233
+ fail(`/orchestration/roles/${role.id}/permissions/${permission}`, 'permission-ceiling');
234
+ }
235
+ }
236
+ if (!profileIds.has(role.completionProfile)) fail(`/orchestration/roles/${role.id}/completionProfile`, 'completion-profile-reference');
237
+ for (const providerRef of role.providerRefs) {
238
+ if (!providerIds.has(providerRef)) fail(`/orchestration/roles/${role.id}/providerRefs`, 'provider-reference');
239
+ }
240
+ }
241
+ for (const mandatoryGate of ['activation', 'external-write', 'final-delivery']) {
242
+ if (!config.orchestration.approvalGates.includes(mandatoryGate)) fail('/orchestration/approvalGates', 'mandatory-approval');
243
+ }
244
+ const bossProfile = profileMap.get(bossRoles[0].completionProfile);
245
+ if (!bossProfile.requiredApprovals.includes('human') || !bossProfile.requiredEvidence.includes('human-approval')) {
246
+ fail(`/orchestration/completionProfiles/${bossProfile.id}`, 'boss-human-approval');
247
+ }
248
+
249
+ const commandNames = new Set(Object.keys(config.project.commands));
250
+ const gateIds = uniqueBy(config.quality.commandGates, 'id', '/quality/commandGates');
251
+ for (const gate of config.quality.commandGates) {
252
+ if (!commandNames.has(gate.command)) fail(`/quality/commandGates/${gate.id}/command`, 'command-reference');
253
+ }
254
+ try { compileQualitySteps(config); compileProjectDependencies(config.project); }
255
+ catch (error) {
256
+ if (error instanceof CommandConfigurationError) fail(error.path, error.reason);
257
+ fail('/quality/commandGates', 'command-expansion');
258
+ }
259
+ if (config.project.schemaVersion === 3 && !config.quality.commandGates.some(gate =>
260
+ gate.required && ['test', 'check'].includes(gate.command))) fail('/quality/commandGates', 'mandatory-command-gate');
261
+ for (const mandatoryCommand of config.project.schemaVersion === 3 ? [] : ['build', 'test']) {
262
+ if (![...gateIds].some(id => config.quality.commandGates.some(gate => gate.id === id && gate.command === mandatoryCommand && gate.required))) {
263
+ fail('/quality/commandGates', 'mandatory-command-gate');
264
+ }
265
+ }
266
+ if (config.quality.expectations.visual === 'human-baseline' && !config.quality.evidence.requireHumanBaseline) {
267
+ fail('/quality/evidence/requireHumanBaseline', 'human-baseline');
268
+ }
269
+ for (const providerRef of config.quality.providerRefs) {
270
+ if (!providerIds.has(providerRef)) fail('/quality/providerRefs', 'provider-reference');
271
+ }
272
+ for (const profileRef of config.quality.completionProfileRefs) {
273
+ if (!profileIds.has(profileRef)) fail('/quality/completionProfileRefs', 'completion-profile-reference');
274
+ }
275
+ const selectedProfiles = new Set(config.quality.completionProfileRefs);
276
+ const qualityEvidence = new Set(config.quality.evidence.requiredTypes);
277
+ for (const role of config.orchestration.roles) {
278
+ if (!selectedProfiles.has(role.completionProfile)) fail('/quality/completionProfileRefs', 'role-profile-selection');
279
+ for (const evidenceType of profileMap.get(role.completionProfile).requiredEvidence) {
280
+ if (!qualityEvidence.has(evidenceType)) fail('/quality/evidence/requiredTypes', 'profile-evidence-coverage');
281
+ }
282
+ }
283
+ for (const evidenceType of qualityEvidence) {
284
+ if (!EVIDENCE_TYPE_SET.has(evidenceType)) fail('/quality/evidence/requiredTypes', 'evidence-type');
285
+ }
286
+ }
287
+
288
+ export function validateProjectConfiguration(config) {
289
+ for (const name of ['project', 'providers', 'orchestration', 'quality']) {
290
+ schemaValidate(name, config[name]);
291
+ }
292
+ if (config.project.repository.remote) {
293
+ try {
294
+ const identity = parseRepositoryRemote(config.project.repository.remote.url);
295
+ if (identity.url !== config.project.repository.remote.url) throw new Error();
296
+ } catch { throw new ConfigurationError('/project/repository/remote', 'invalid'); }
297
+ }
298
+ validateConfigSemantics(config);
299
+ return true;
300
+ }
301
+
302
+ function visitDependencies(nodeId, nodeMap, visited) {
303
+ if (visited.has(nodeId)) return;
304
+ const visiting = new Set([nodeId]);
305
+ const stack = [{ nodeId, dependencyIndex: 0 }];
306
+ while (stack.length > 0) {
307
+ const frame = stack[stack.length - 1];
308
+ const dependencies = nodeMap.get(frame.nodeId).dependencies;
309
+ if (frame.dependencyIndex >= dependencies.length) {
310
+ visiting.delete(frame.nodeId);
311
+ visited.add(frame.nodeId);
312
+ stack.pop();
313
+ continue;
314
+ }
315
+ const dependency = dependencies[frame.dependencyIndex];
316
+ frame.dependencyIndex += 1;
317
+ if (visiting.has(dependency)) fail(`/nodes/${dependency}/dependencies`, 'dependency-cycle');
318
+ if (visited.has(dependency)) continue;
319
+ visiting.add(dependency);
320
+ stack.push({ nodeId: dependency, dependencyIndex: 0 });
321
+ }
322
+ }
323
+
324
+ function delegationDepth(nodeId, nodeMap, knownDepths) {
325
+ if (knownDepths.has(nodeId)) return knownDepths.get(nodeId);
326
+ const visiting = new Set();
327
+ const path = [];
328
+ let currentId = nodeId;
329
+ let depth;
330
+ while (true) {
331
+ if (knownDepths.has(currentId)) {
332
+ depth = knownDepths.get(currentId);
333
+ break;
334
+ }
335
+ if (visiting.has(currentId)) fail(`/nodes/${currentId}/parentId`, 'parent-cycle');
336
+ visiting.add(currentId);
337
+ path.push(currentId);
338
+ const parentId = nodeMap.get(currentId).parentId;
339
+ if (!parentId) {
340
+ depth = -1;
341
+ break;
342
+ }
343
+ currentId = parentId;
344
+ }
345
+ for (let index = path.length - 1; index >= 0; index -= 1) {
346
+ depth += 1;
347
+ knownDepths.set(path[index], depth);
348
+ }
349
+ return knownDepths.get(nodeId);
350
+ }
351
+
352
+ export function validateGoalGraph(graph, config) {
353
+ schemaValidate('goalGraph', graph);
354
+ scanForSecrets(graph);
355
+ const nodeIds = uniqueBy(graph.nodes, 'id', '/nodes');
356
+ const nodeMap = new Map(graph.nodes.map(node => [node.id, node]));
357
+ for (const node of graph.nodes) {
358
+ for (const dependency of node.dependencies) {
359
+ if (!nodeIds.has(dependency)) fail(`/nodes/${node.id}/dependencies`, 'dangling-dependency');
360
+ if (dependency === node.id) fail(`/nodes/${node.id}/dependencies`, 'self-dependency');
361
+ }
362
+ if (node.parentId && !nodeIds.has(node.parentId)) fail(`/nodes/${node.id}/parentId`, 'dangling-parent');
363
+ if (node.parentId === node.id) fail(`/nodes/${node.id}/parentId`, 'self-parent');
364
+ }
365
+ const roots = graph.nodes.filter(node => !node.parentId);
366
+ if (roots.length !== 1 || roots[0].owner.role !== 'boss') fail('/nodes', 'boss-root');
367
+ const finalDeliveryNodes = graph.nodes.filter(node => node.approvalGate === 'final-delivery');
368
+ if (finalDeliveryNodes.length !== 1) fail('/nodes', 'final-delivery-node');
369
+ for (const node of graph.nodes) {
370
+ if (node.approvalGate && HUMAN_FINAL_GATES.has(node.approvalGate)) {
371
+ if (node.owner.role !== 'boss' || !node.requiredEvidenceTypes.includes('human-approval')) {
372
+ fail(`/nodes/${node.id}/approvalGate`, 'human-final-gate');
373
+ }
374
+ }
375
+ if (node.approvalGate && node.owner.role === 'worker') {
376
+ fail(`/nodes/${node.id}/approvalGate`, 'worker-approval-gate');
377
+ }
378
+ }
379
+ const visitedDependencies = new Set();
380
+ for (const node of graph.nodes) visitDependencies(node.id, nodeMap, visitedDependencies);
381
+ const knownDelegationDepths = new Map();
382
+ for (const node of graph.nodes) {
383
+ if (delegationDepth(node.id, nodeMap, knownDelegationDepths) > graph.maxDelegationDepth) fail(`/nodes/${node.id}/parentId`, 'delegation-depth');
384
+ }
385
+
386
+ if (config) {
387
+ if (graph.maxDelegationDepth > config.orchestration.maxDelegationDepth) fail('/maxDelegationDepth', 'configured-delegation-depth');
388
+ const providerIds = new Set(config.providers.providers.map(provider => provider.id));
389
+ const roleMap = new Map(config.orchestration.roles.map(role => [role.id, role]));
390
+ const profileIds = new Set(config.orchestration.completionProfiles.map(profile => profile.id));
391
+ const profileMap = new Map(config.orchestration.completionProfiles.map(profile => [profile.id, profile]));
392
+ const rootProfile = profileMap.get(roots[0].completionProfile);
393
+ if (!rootProfile || !rootProfile.requiredApprovals.includes('human') || !rootProfile.requiredEvidence.includes('human-approval')) {
394
+ fail(`/nodes/${roots[0].id}/completionProfile`, 'boss-root-human-approval');
395
+ }
396
+ for (const providerRef of graph.providerRefs) {
397
+ if (!providerIds.has(providerRef)) fail('/providerRefs', 'provider-reference');
398
+ }
399
+ for (const node of graph.nodes) {
400
+ const role = roleMap.get(node.owner.id);
401
+ if (!role || role.kind !== node.owner.role) fail(`/nodes/${node.id}/owner`, 'role-reference');
402
+ if (!profileIds.has(node.completionProfile)) fail(`/nodes/${node.id}/completionProfile`, 'completion-profile-reference');
403
+ if (node.completionProfile !== role.completionProfile) fail(`/nodes/${node.id}/completionProfile`, 'completion-profile-bound');
404
+ if (node.approvalGate && !config.orchestration.approvalGates.includes(node.approvalGate)) fail(`/nodes/${node.id}/approvalGate`, 'approval-reference');
405
+ for (const scope of node.authorityScopes) {
406
+ if (!role.authorityScopes.includes(scope)) fail(`/nodes/${node.id}/authorityScopes`, 'authority-bound');
407
+ }
408
+ for (const budgetName of ['timeMinutes', 'tokenLimit', 'costUsd', 'taskLimit']) {
409
+ if (node.budget[budgetName] > role.budget[budgetName]) fail(`/nodes/${node.id}/budget/${budgetName}`, 'budget-bound');
410
+ }
411
+ const profile = profileMap.get(node.completionProfile);
412
+ if (node.approvalGate && HUMAN_FINAL_GATES.has(node.approvalGate)) {
413
+ if (!profile.requiredApprovals.includes('human') || !profile.requiredEvidence.includes('human-approval')) {
414
+ fail(`/nodes/${node.id}/completionProfile`, 'human-final-profile');
415
+ }
416
+ }
417
+ for (const evidenceType of profile.requiredEvidence) {
418
+ if (!node.requiredEvidenceTypes.includes(evidenceType)) fail(`/nodes/${node.id}/requiredEvidenceTypes`, 'completion-evidence-coverage');
419
+ }
420
+ for (const evidenceType of node.requiredEvidenceTypes) {
421
+ if (!config.quality.evidence.requiredTypes.includes(evidenceType)) fail(`/nodes/${node.id}/requiredEvidenceTypes`, 'quality-evidence-coverage');
422
+ }
423
+ if (node.parentId) {
424
+ const parent = nodeMap.get(node.parentId);
425
+ const parentRole = roleMap.get(parent.owner.id);
426
+ if (node.owner.role === 'boss') {
427
+ const isBoundFinalGate = (
428
+ parent.id === roots[0].id
429
+ && parent.owner.role === 'boss'
430
+ && parent.owner.id === node.owner.id
431
+ && HUMAN_FINAL_GATES.has(node.approvalGate)
432
+ && node.completionProfile === parent.completionProfile
433
+ && node.authorityScopes.every(scope => scope === 'verify')
434
+ );
435
+ if (!isBoundFinalGate) fail(`/nodes/${node.id}/parentId`, 'non-root-boss');
436
+ } else {
437
+ if (!parentRole.canDelegate) fail(`/nodes/${node.id}/parentId`, 'parent-cannot-delegate');
438
+ if (!parentRole.delegatesTo.includes(node.owner.role)) fail(`/nodes/${node.id}/owner`, 'delegation-target');
439
+ }
440
+ for (const scope of node.authorityScopes) {
441
+ if (!parent.authorityScopes.includes(scope)) fail(`/nodes/${node.id}/authorityScopes`, 'parent-authority-bound');
442
+ }
443
+ for (const budgetName of ['timeMinutes', 'tokenLimit', 'costUsd', 'taskLimit']) {
444
+ if (node.budget[budgetName] > parent.budget[budgetName]) fail(`/nodes/${node.id}/budget/${budgetName}`, 'parent-budget-bound');
445
+ }
446
+ }
447
+ }
448
+ }
449
+ return true;
450
+ }
451
+
452
+ function snapshotEventRecord(value, maximumKeys, path) {
453
+ if (!value || typeof value !== 'object' || Array.isArray(value)) fail(path, 'event-snapshot');
454
+ const keys = Reflect.ownKeys(value);
455
+ if (keys.length > maximumKeys || keys.some(key => typeof key !== 'string' || key === '__proto__')) fail(path, 'event-snapshot');
456
+ const output = Object.create(null);
457
+ for (const key of keys) {
458
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
459
+ if (!descriptor?.enumerable) fail(path, 'event-snapshot');
460
+ output[key] = value[key];
461
+ }
462
+ return output;
463
+ }
464
+
465
+ function snapshotEventArray(value, maximumItems, path) {
466
+ if (!Array.isArray(value)) fail(path, 'event-snapshot');
467
+ const length = value.length;
468
+ if (!Number.isSafeInteger(length) || length > maximumItems) fail(path, 'event-snapshot');
469
+ const output = [];
470
+ for (let index = 0; index < length; index += 1) {
471
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
472
+ if (!descriptor?.enumerable) fail(path, 'event-snapshot');
473
+ output.push(value[index]);
474
+ }
475
+ return output;
476
+ }
477
+
478
+ function snapshotEventInput(event) {
479
+ try {
480
+ const stable = snapshotEventRecord(event, 32, '/');
481
+ if (Object.hasOwn(stable, 'actor')) stable.actor = snapshotEventRecord(stable.actor, 4, '/actor');
482
+ if (Object.hasOwn(stable, 'evidenceRefs')) stable.evidenceRefs = snapshotEventArray(stable.evidenceRefs, 64, '/evidenceRefs');
483
+ if (Object.hasOwn(stable, 'authorityDelta')) {
484
+ stable.authorityDelta = snapshotEventRecord(stable.authorityDelta, 2, '/authorityDelta');
485
+ if (Object.hasOwn(stable.authorityDelta, 'added')) stable.authorityDelta.added = snapshotEventArray(stable.authorityDelta.added, 32, '/authorityDelta/added');
486
+ if (Object.hasOwn(stable.authorityDelta, 'removed')) stable.authorityDelta.removed = snapshotEventArray(stable.authorityDelta.removed, 32, '/authorityDelta/removed');
487
+ }
488
+ if (Object.hasOwn(stable, 'budgetDelta')) stable.budgetDelta = snapshotEventRecord(stable.budgetDelta, 4, '/budgetDelta');
489
+ return stable;
490
+ } catch {
491
+ fail('/', 'event-snapshot');
492
+ }
493
+ }
494
+
495
+ export function validateEvent(event) {
496
+ const stable = snapshotEventInput(event);
497
+ schemaValidate('event', stable);
498
+ scanForSecrets(stable);
499
+ assertTimestamp(stable.timestamp, '/timestamp');
500
+ if (stable.type === 'approval-recorded') {
501
+ if (stable.actor.role !== 'human') fail('/actor/role', 'approval-human-actor');
502
+ if (!stable.evidenceRefs.includes(stable.approvalReceiptId)) fail('/approvalReceiptId', 'approval-receipt-reference');
503
+ }
504
+ if (stable.priorState && stable.newState && stable.priorState === stable.newState) fail('/newState', 'unchanged-state');
505
+ return true;
506
+ }
507
+
508
+ export function validateEvidence(evidence, config) {
509
+ schemaValidate('evidence', evidence);
510
+ scanForSecrets(evidence);
511
+ uniqueBy(evidence.items, 'id', '/items');
512
+ if (config) {
513
+ const providerIds = new Set(config.providers.providers.map(provider => provider.id));
514
+ for (const item of evidence.items) {
515
+ if (item.source.providerRef && !providerIds.has(item.source.providerRef)) fail(`/items/${item.id}/source/providerRef`, 'provider-reference');
516
+ }
517
+ }
518
+ for (const item of evidence.items) {
519
+ assertTimestamp(item.timestamp, `/items/${item.id}/timestamp`);
520
+ if (item.location && /^[A-Za-z][A-Za-z0-9+.-]*:/.test(item.location)) {
521
+ if (!/^https?:\/\//i.test(item.location)) fail(`/items/${item.id}/location`, 'location-scheme');
522
+ assertSafeUrl(item.location, `/items/${item.id}/location`);
523
+ } else if (item.location) assertSafeRelativePath(item.location, `/items/${item.id}/location`);
524
+ if (item.source.resourceId) {
525
+ const resourcePath = `/items/${item.id}/source/resourceId`;
526
+ if (/^[A-Za-z][A-Za-z0-9+.-]*:/.test(item.source.resourceId)) {
527
+ if (!/^https?:\/\//i.test(item.source.resourceId)) fail(resourcePath, 'resource-scheme');
528
+ assertSafeUrl(item.source.resourceId, resourcePath);
529
+ } else {
530
+ assertSafeRelativePath(item.source.resourceId, resourcePath);
531
+ }
532
+ }
533
+ if (item.type === 'human-approval') {
534
+ if (item.producer.role !== 'human') fail(`/items/${item.id}/producer/role`, 'human-approver');
535
+ if (item.approval.actorId !== item.producer.id) fail(`/items/${item.id}/approval/actorId`, 'approver-identity');
536
+ if (item.approval.decision !== item.approvalState) fail(`/items/${item.id}/approvalState`, 'approval-decision-state');
537
+ if (item.approval.decision !== evidence.approvalState) fail('/approvalState', 'bundle-decision-state');
538
+ }
539
+ }
540
+ const approvedHumanItems = evidence.items.filter(item => item.type === 'human-approval' && item.approval.decision === 'approved');
541
+ const rejectedHumanItems = evidence.items.filter(item => item.type === 'human-approval' && item.approval.decision === 'rejected');
542
+ if (evidence.approvalState === 'approved') {
543
+ if (approvedHumanItems.length === 0 || evidence.items.some(item => item.approvalState !== 'approved')) fail('/approvalState', 'approved-bundle-evidence');
544
+ }
545
+ if (evidence.approvalState === 'rejected' && rejectedHumanItems.length === 0) {
546
+ fail('/approvalState', 'rejected-bundle-evidence');
547
+ }
548
+ if (evidence.publicationState === 'approved' && (evidence.approvalState !== 'approved' || approvedHumanItems.length === 0)) {
549
+ fail('/publicationState', 'publication-approval');
550
+ }
551
+ if (evidence.publicationState !== 'approved' && evidence.items.some(item => item.classification === 'public')) {
552
+ fail('/publicationState', 'public-evidence-approval');
553
+ }
554
+ if (evidence.publicationState === 'rejected' && evidence.items.some(item => item.classification === 'public')) {
555
+ fail('/publicationState', 'rejected-publication');
556
+ }
557
+ return true;
558
+ }
559
+
560
+ export const validateConfig = validateProjectConfiguration;