@thebackstoryis/engineering-with-ai 0.2.9

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 (334) hide show
  1. package/Docs/README.md +50 -0
  2. package/Docs/adoption/consultancy-and-multi-project-rollout.md +135 -0
  3. package/Docs/adoption/non-technical-team-guide.md +126 -0
  4. package/Docs/archaeology-technology-and-hosting-discovery.md +212 -0
  5. package/Docs/blast-radius-and-impact-routing-guide.md +325 -0
  6. package/Docs/blueprints/internal-blueprint-catalogue.md +146 -0
  7. package/Docs/blueprints/maintaining-organisation-blueprints.md +154 -0
  8. package/Docs/blueprints/validation-and-troubleshooting.md +168 -0
  9. package/Docs/cli-reference.md +113 -0
  10. package/Docs/completed-phase-evidence-amendments.md +74 -0
  11. package/Docs/consultancy-network-rollout-control-plane-guide.md +202 -0
  12. package/Docs/context-aware-delivery-companion-guide.md +198 -0
  13. package/Docs/context-aware-delivery-companion-user-guide.md +184 -0
  14. package/Docs/context-management-and-token-efficiency.md +113 -0
  15. package/Docs/design-systems/design-system-implementation-guide.md +85 -0
  16. package/Docs/design-systems/design-system-pack-authoring-guide.md +95 -0
  17. package/Docs/design-systems/design-system-review-guide.md +51 -0
  18. package/Docs/design-systems/design-system-user-guide.md +96 -0
  19. package/Docs/design-systems/product-owner-guide.md +49 -0
  20. package/Docs/designing-organisation-blueprint-packs.md +384 -0
  21. package/Docs/developer-delivery-guide.md +224 -0
  22. package/Docs/error-reporting-guide.md +110 -0
  23. package/Docs/error-reporting-provider-guide.md +49 -0
  24. package/Docs/examples/error-report-adapter.md +70 -0
  25. package/Docs/examples/meeting-review.md +76 -0
  26. package/Docs/examples/minimal-design-system.md +67 -0
  27. package/Docs/examples/prototype-review-inputs.md +175 -0
  28. package/Docs/examples/reproducible-archaeology-depth-example.md +144 -0
  29. package/Docs/examples/test-scenario-input.md +68 -0
  30. package/Docs/examples/worked-examples.md +147 -0
  31. package/Docs/existing-project-onboarding-guide.md +214 -0
  32. package/Docs/explanation/core-concepts.md +26 -0
  33. package/Docs/explanation/delivery-workflow.md +48 -0
  34. package/Docs/governance/governance-team-guide.md +139 -0
  35. package/Docs/governed-starter-project-materialisation-guide.md +284 -0
  36. package/Docs/guide-catalogue.md +117 -0
  37. package/Docs/guided-discovery-facilitator-guide.md +172 -0
  38. package/Docs/guided-intent-workspace-guide.md +109 -0
  39. package/Docs/guided-phase-evidence-drafting-guide.md +119 -0
  40. package/Docs/human-approval-and-assurance-guide.md +146 -0
  41. package/Docs/knowledge-proposals-implementer-guide.md +106 -0
  42. package/Docs/knowledge-proposals-user-guide.md +247 -0
  43. package/Docs/maintainers/context-benchmarks.md +29 -0
  44. package/Docs/maintainers/contributing.md +58 -0
  45. package/Docs/maintainers/evidence-depth-acceptance.md +72 -0
  46. package/Docs/maintainers/verification-walkthroughs.md +104 -0
  47. package/Docs/meeting-evidence-implementer-guide.md +132 -0
  48. package/Docs/meeting-evidence-user-guide.md +200 -0
  49. package/Docs/operations/dashboard-and-delivery-state.md +153 -0
  50. package/Docs/operations/dashboard-configuration.md +87 -0
  51. package/Docs/operations/installation-updating-and-entitlements.md +135 -0
  52. package/Docs/operations/premium-personas-setup.md +76 -0
  53. package/Docs/operations/troubleshooting-and-recovery.md +205 -0
  54. package/Docs/organisation-rollout-guide.md +142 -0
  55. package/Docs/persona-entitlement-provider-guide.md +199 -0
  56. package/Docs/persona-guided-prototype-iteration.md +129 -0
  57. package/Docs/personas/organisation-specific-personas.md +103 -0
  58. package/Docs/personas/persona-authoring-cookbook.md +176 -0
  59. package/Docs/personas/persona-engagement-ui.md +133 -0
  60. package/Docs/personas/persona-governance.md +118 -0
  61. package/Docs/platform-export-analysis-guide.md +336 -0
  62. package/Docs/policies/governance-owner-guide.md +36 -0
  63. package/Docs/policies/implementation-guide.md +42 -0
  64. package/Docs/policies/organisation-policy-design-gates.md +58 -0
  65. package/Docs/policies/policy-pack-authoring-guide.md +108 -0
  66. package/Docs/policies/product-owner-guide.md +43 -0
  67. package/Docs/policies/technical-owner-guide.md +37 -0
  68. package/Docs/product-owner-guide.md +327 -0
  69. package/Docs/project-portfolio-orchestration-guide.md +199 -0
  70. package/Docs/quality/manual-qa-and-acceptance.md +162 -0
  71. package/Docs/quality/persona-driven-test-scenarios.md +172 -0
  72. package/Docs/quality/reproducible-archaeology-depth-review-checklist.md +89 -0
  73. package/Docs/reference/capabilities-and-project-layout.md +678 -0
  74. package/Docs/reference/cli-and-configuration.md +398 -0
  75. package/Docs/reference/contributions-api.md +23 -0
  76. package/Docs/reference/security-adapter-authoring.md +81 -0
  77. package/Docs/reference/starter-adapter-authoring.md +74 -0
  78. package/Docs/repository-source-map-guide.md +381 -0
  79. package/Docs/reproducible-archaeology-and-discovery-depth.md +292 -0
  80. package/Docs/screen-prototype-creation-guide.md +324 -0
  81. package/Docs/security-validation-guide.md +353 -0
  82. package/Docs/solution-readiness-review-guide.md +123 -0
  83. package/Docs/standards/project-standards-authoring.md +157 -0
  84. package/Docs/team-hub-guide.md +162 -0
  85. package/Docs/team-hub-resource-registry-guide.md +167 -0
  86. package/Docs/tutorials/first-delivery.md +83 -0
  87. package/Docs/tutorials/first-session.md +62 -0
  88. package/Docs/using-lifecycle-hooks.md +381 -0
  89. package/Docs/working-with-personas.md +274 -0
  90. package/LICENSE +165 -0
  91. package/README.md +96 -0
  92. package/agents-src/claude/ewai-security-reviewer.md +15 -0
  93. package/bin/ewai +5 -0
  94. package/config/archaeology-record-families.yaml +59 -0
  95. package/config/delivery-artifacts.yaml +121 -0
  96. package/config/delivery-stages.yaml +77 -0
  97. package/config/design-system.schema.json +46 -0
  98. package/config/error-reporting.schema.json +79 -0
  99. package/config/evidence-depth.schema.json +53 -0
  100. package/config/intent.schema.json +90 -0
  101. package/config/knowledge-proposals-proposal.schema.json +34 -0
  102. package/config/lifecycle-event.schema.json +68 -0
  103. package/config/lifecycle-handler.schema.json +45 -0
  104. package/config/lifecycle-hook-ack.schema.json +19 -0
  105. package/config/meeting-evidence-candidate.schema.json +102 -0
  106. package/config/organisation-policy.schema.json +137 -0
  107. package/config/pack.schema.json +250 -0
  108. package/config/persona-pack.schema.json +21 -0
  109. package/config/persona.schema.json +17 -0
  110. package/config/policy-evaluation.schema.json +77 -0
  111. package/config/policy-facts.schema.json +140 -0
  112. package/config/portfolio.schema.json +68 -0
  113. package/config/project.schema.json +313 -0
  114. package/config/prototype-iteration.schema.json +128 -0
  115. package/config/rollout.schema.json +87 -0
  116. package/config/security-adapter.schema.json +31 -0
  117. package/config/security-scan-request.schema.json +64 -0
  118. package/config/security-scan-response.schema.json +52 -0
  119. package/config/security-validation-policy.schema.json +74 -0
  120. package/config/starter-source-acknowledgement.schema.json +13 -0
  121. package/config/starter-source-adapter.schema.json +38 -0
  122. package/config/starter-source-request.schema.json +61 -0
  123. package/package.json +77 -0
  124. package/packs/core/pack.yaml +7 -0
  125. package/packs/design-systems/default/experience-promise.md +9 -0
  126. package/packs/design-systems/default/intentional-review.md +10 -0
  127. package/packs/design-systems/default/interaction-and-entry.md +9 -0
  128. package/packs/design-systems/default/meaningful-content-and-states.md +9 -0
  129. package/packs/design-systems/default/pack.yaml +44 -0
  130. package/packs/design-systems/default/principles.md +10 -0
  131. package/packs/personas/core/pack.yaml +7 -0
  132. package/packs/personas/core/personas/archaeologist.md +37 -0
  133. package/packs/personas/core/personas/end-user.md +17 -0
  134. package/packs/personas/core/personas/maintainer.md +17 -0
  135. package/packs/personas/core/personas/operator.md +17 -0
  136. package/packs/personas/core/personas/specs-knowledge-curator.md +35 -0
  137. package/packs/technologies/laravel/pack.yaml +30 -0
  138. package/packs/technologies/laravel-nuxt/pack.yaml +30 -0
  139. package/packs/technologies/nuxt/pack.yaml +30 -0
  140. package/packs/technologies/power-platform/pack.yaml +31 -0
  141. package/packs/technologies/salesforce/pack.yaml +25 -0
  142. package/public/app.js +4896 -0
  143. package/public/apple-touch-icon.png +0 -0
  144. package/public/assets/backstory-icon.png +0 -0
  145. package/public/dashboard-navigation.js +98 -0
  146. package/public/favicon-16.png +0 -0
  147. package/public/favicon-32.png +0 -0
  148. package/public/favicon.ico +0 -0
  149. package/public/index.html +789 -0
  150. package/public/styles.css +2693 -0
  151. package/public/team-hub/app.js +202 -0
  152. package/public/team-hub/index.html +79 -0
  153. package/public/team-hub/styles.css +90 -0
  154. package/scripts/publication-check.mjs +140 -0
  155. package/scripts/setup.mjs +21 -0
  156. package/skills-src/ewai-archaeology/SKILL.md +334 -0
  157. package/skills-src/ewai-archaeology/agents/openai.yaml +4 -0
  158. package/skills-src/ewai-archaeology/references/archaeology-contract.md +201 -0
  159. package/skills-src/ewai-archaeology/references/lifecycle-reconstruction.md +177 -0
  160. package/skills-src/ewai-archaeology/references/maximum-detail-reconstruction.md +97 -0
  161. package/skills-src/ewai-archaeology/references/model-routing.md +26 -0
  162. package/skills-src/ewai-architecture/SKILL.md +108 -0
  163. package/skills-src/ewai-architecture/agents/openai.yaml +4 -0
  164. package/skills-src/ewai-architecture/references/architecture-contract.md +176 -0
  165. package/skills-src/ewai-context/SKILL.md +68 -0
  166. package/skills-src/ewai-context/agents/openai.yaml +4 -0
  167. package/skills-src/ewai-context-import/SKILL.md +118 -0
  168. package/skills-src/ewai-context-import/agents/openai.yaml +4 -0
  169. package/skills-src/ewai-context-import/references/context-import-contract.md +106 -0
  170. package/skills-src/ewai-dashboard-configuration/SKILL.md +20 -0
  171. package/skills-src/ewai-deliver/SKILL.md +122 -0
  172. package/skills-src/ewai-deliver/references/delivery-evidence.md +92 -0
  173. package/skills-src/ewai-deliver/references/phase-routing.md +31 -0
  174. package/skills-src/ewai-design-system-apply/SKILL.md +27 -0
  175. package/skills-src/ewai-design-system-apply/agents/openai.yaml +4 -0
  176. package/skills-src/ewai-design-system-apply/references/application-contract.md +36 -0
  177. package/skills-src/ewai-design-system-author/SKILL.md +28 -0
  178. package/skills-src/ewai-design-system-author/agents/openai.yaml +4 -0
  179. package/skills-src/ewai-design-system-author/references/authoring-contract.md +38 -0
  180. package/skills-src/ewai-design-system-review/SKILL.md +26 -0
  181. package/skills-src/ewai-design-system-review/agents/openai.yaml +4 -0
  182. package/skills-src/ewai-design-system-review/references/review-contract.md +40 -0
  183. package/skills-src/ewai-error-reporting/SKILL.md +46 -0
  184. package/skills-src/ewai-error-reporting/agents/openai.yaml +4 -0
  185. package/skills-src/ewai-error-reporting/references/provider-contract.md +74 -0
  186. package/skills-src/ewai-evidence-depth/SKILL.md +72 -0
  187. package/skills-src/ewai-evidence-depth/agents/openai.yaml +4 -0
  188. package/skills-src/ewai-evidence-depth/references/evidence-depth-contract.md +127 -0
  189. package/skills-src/ewai-intent/SKILL.md +68 -0
  190. package/skills-src/ewai-intent/agents/openai.yaml +4 -0
  191. package/skills-src/ewai-intent/references/intent-contract.md +42 -0
  192. package/skills-src/ewai-knowledge-proposals/SKILL.md +105 -0
  193. package/skills-src/ewai-knowledge-proposals/agents/openai.yaml +4 -0
  194. package/skills-src/ewai-knowledge-proposals/references/proposal-contract.md +59 -0
  195. package/skills-src/ewai-meeting-evidence/SKILL.md +106 -0
  196. package/skills-src/ewai-meeting-evidence/agents/openai.yaml +4 -0
  197. package/skills-src/ewai-meeting-evidence/references/candidate-contract.md +64 -0
  198. package/skills-src/ewai-organisation-policy/SKILL.md +62 -0
  199. package/skills-src/ewai-organisation-policy/agents/openai.yaml +4 -0
  200. package/skills-src/ewai-organisation-policy/references/policy-contract.md +94 -0
  201. package/skills-src/ewai-palace-housekeeping/SKILL.md +55 -0
  202. package/skills-src/ewai-palace-housekeeping/agents/openai.yaml +4 -0
  203. package/skills-src/ewai-persona-entitlement/SKILL.md +60 -0
  204. package/skills-src/ewai-persona-entitlement/agents/openai.yaml +4 -0
  205. package/skills-src/ewai-phase-evidence/SKILL.md +79 -0
  206. package/skills-src/ewai-phase-evidence/agents/openai.yaml +4 -0
  207. package/skills-src/ewai-pipeline/SKILL.md +130 -0
  208. package/skills-src/ewai-pipeline/agents/openai.yaml +4 -0
  209. package/skills-src/ewai-pipeline/references/cli.md +86 -0
  210. package/skills-src/ewai-pipeline/references/specs-contract.md +16 -0
  211. package/skills-src/ewai-portfolio/SKILL.md +70 -0
  212. package/skills-src/ewai-portfolio/agents/openai.yaml +4 -0
  213. package/skills-src/ewai-portfolio/references/portfolio-contract.md +67 -0
  214. package/skills-src/ewai-project-discovery/SKILL.md +95 -0
  215. package/skills-src/ewai-project-discovery/agents/openai.yaml +4 -0
  216. package/skills-src/ewai-project-discovery/references/discovery-contract.md +34 -0
  217. package/skills-src/ewai-prototype-iteration/SKILL.md +30 -0
  218. package/skills-src/ewai-prototype-iteration/agents/openai.yaml +4 -0
  219. package/skills-src/ewai-prototype-iteration/references/review-contract.md +49 -0
  220. package/skills-src/ewai-retro/SKILL.md +48 -0
  221. package/skills-src/ewai-retro/agents/openai.yaml +4 -0
  222. package/skills-src/ewai-retro/references/asset-routing.md +14 -0
  223. package/skills-src/ewai-rollout/SKILL.md +74 -0
  224. package/skills-src/ewai-rollout/agents/openai.yaml +4 -0
  225. package/skills-src/ewai-rollout/references/rollout-contract.md +74 -0
  226. package/skills-src/ewai-shape-intents/SKILL.md +84 -0
  227. package/skills-src/ewai-shape-intents/agents/openai.yaml +4 -0
  228. package/skills-src/ewai-shape-intents/references/intent-mapping-contract.md +109 -0
  229. package/skills-src/ewai-solution-readiness/SKILL.md +55 -0
  230. package/skills-src/ewai-solution-readiness/agents/openai.yaml +4 -0
  231. package/skills-src/ewai-standards-check/SKILL.md +93 -0
  232. package/skills-src/ewai-standards-check/agents/openai.yaml +4 -0
  233. package/skills-src/ewai-standards-check/references/report-contract.md +116 -0
  234. package/skills-src/ewai-test-scenarios/SKILL.md +94 -0
  235. package/skills-src/ewai-test-scenarios/agents/openai.yaml +4 -0
  236. package/skills-src/ewai-test-scenarios/references/scenario-contract.md +88 -0
  237. package/src/afk-worker.mjs +16 -0
  238. package/src/archaeology.mjs +1333 -0
  239. package/src/checkin.mjs +261 -0
  240. package/src/cli.mjs +2427 -0
  241. package/src/companion-guidance.mjs +257 -0
  242. package/src/companion-opening.mjs +62 -0
  243. package/src/companion.mjs +256 -0
  244. package/src/context.mjs +210 -0
  245. package/src/dashboard-preferences.mjs +80 -0
  246. package/src/delivery-artifacts.mjs +204 -0
  247. package/src/delivery-documents.mjs +248 -0
  248. package/src/delivery-gates.mjs +317 -0
  249. package/src/delivery.mjs +1433 -0
  250. package/src/design-system-application.mjs +291 -0
  251. package/src/design-system-authoring.mjs +101 -0
  252. package/src/design-systems.mjs +466 -0
  253. package/src/discovery.mjs +1314 -0
  254. package/src/error-reporting.mjs +323 -0
  255. package/src/evidence-depth.mjs +543 -0
  256. package/src/execution-state.mjs +243 -0
  257. package/src/install.mjs +166 -0
  258. package/src/intent-dependencies.mjs +117 -0
  259. package/src/intent-maps.mjs +402 -0
  260. package/src/intents.mjs +747 -0
  261. package/src/knowledge-proposals.mjs +717 -0
  262. package/src/launcher.mjs +51 -0
  263. package/src/meeting-evidence.mjs +703 -0
  264. package/src/network-rollout.mjs +386 -0
  265. package/src/organisation-blueprints.mjs +438 -0
  266. package/src/organisation-policies.mjs +448 -0
  267. package/src/packs.mjs +44 -0
  268. package/src/paths.mjs +62 -0
  269. package/src/persona-entitlements.mjs +438 -0
  270. package/src/persona-licence-config.mjs +98 -0
  271. package/src/persona-website-provider.mjs +134 -0
  272. package/src/persona-zip.mjs +87 -0
  273. package/src/personas.mjs +159 -0
  274. package/src/platform-metadata-analysis.mjs +314 -0
  275. package/src/policy-design-gates.mjs +623 -0
  276. package/src/policy-gate-integration.mjs +318 -0
  277. package/src/portfolio.mjs +509 -0
  278. package/src/power-platform-source-map.mjs +190 -0
  279. package/src/project.mjs +449 -0
  280. package/src/prototype-iterations.mjs +730 -0
  281. package/src/repository-source-map.mjs +603 -0
  282. package/src/runtime/afk-conductor.mjs +973 -0
  283. package/src/runtime/context-assembly.mjs +457 -0
  284. package/src/runtime/context-benchmarks.mjs +115 -0
  285. package/src/runtime/dashboard-actions.mjs +109 -0
  286. package/src/runtime/dashboard-handoffs.mjs +149 -0
  287. package/src/runtime/dashboard-server.mjs +1272 -0
  288. package/src/runtime/dashboard.mjs +197 -0
  289. package/src/runtime/database.mjs +789 -0
  290. package/src/runtime/error-reporting.mjs +581 -0
  291. package/src/runtime/evidence-depth-workspace.mjs +412 -0
  292. package/src/runtime/execution-leases.mjs +299 -0
  293. package/src/runtime/guided-discovery.mjs +350 -0
  294. package/src/runtime/guided-intents.mjs +517 -0
  295. package/src/runtime/impact-analysis.mjs +535 -0
  296. package/src/runtime/intents.mjs +222 -0
  297. package/src/runtime/knowledge.mjs +86 -0
  298. package/src/runtime/lifecycle-hooks.mjs +1239 -0
  299. package/src/runtime/mcp-config.mjs +110 -0
  300. package/src/runtime/mcp-server.mjs +1885 -0
  301. package/src/runtime/palace.mjs +362 -0
  302. package/src/runtime/paths.mjs +58 -0
  303. package/src/runtime/persona-engagement.mjs +255 -0
  304. package/src/runtime/phase-contributions.mjs +594 -0
  305. package/src/runtime/policy-workspace.mjs +170 -0
  306. package/src/runtime/prototype-iterations.mjs +235 -0
  307. package/src/runtime/provider-adapters.mjs +163 -0
  308. package/src/runtime/repository-index.mjs +838 -0
  309. package/src/runtime/runs.mjs +185 -0
  310. package/src/runtime/security-validation.mjs +1230 -0
  311. package/src/runtime/starter-materialisation.mjs +1155 -0
  312. package/src/runtime/team-hub-client.mjs +479 -0
  313. package/src/runtime/team-hub-database.mjs +288 -0
  314. package/src/runtime/team-hub-server.mjs +191 -0
  315. package/src/runtime/team-hub.mjs +110 -0
  316. package/src/runtime/tree-sitter-index.mjs +390 -0
  317. package/src/runtime/version.mjs +1 -0
  318. package/src/runtime/work.mjs +633 -0
  319. package/src/salesforce-source-map.mjs +212 -0
  320. package/src/security-validation-config.mjs +224 -0
  321. package/src/solution-readiness.mjs +620 -0
  322. package/src/starter-materialisation-contract.mjs +407 -0
  323. package/src/task-graph.mjs +544 -0
  324. package/src/team-hub-resources.mjs +239 -0
  325. package/src/team-hub.mjs +242 -0
  326. package/src/test-scenarios.mjs +622 -0
  327. package/src/validation-config.mjs +289 -0
  328. package/templates/SPECS/1.Scope/personas/registry.yaml +12 -0
  329. package/templates/SPECS/5.Strategy/patterns/context-packet.md +119 -0
  330. package/templates/SPECS/6.Build/_tracker-template.md +16 -0
  331. package/templates/SPECS/pipeline.yaml +62 -0
  332. package/templates/discovery-answers.yaml +86 -0
  333. package/templates/intent-body.md +21 -0
  334. package/tests/fixtures/context-benchmarks.json +9 -0
@@ -0,0 +1,581 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { spawn, spawnSync } from 'node:child_process';
3
+ import {
4
+ accessSync,
5
+ constants,
6
+ existsSync,
7
+ lstatSync,
8
+ mkdirSync,
9
+ readFileSync,
10
+ readdirSync,
11
+ realpathSync,
12
+ renameSync,
13
+ rmSync,
14
+ statSync,
15
+ writeFileSync,
16
+ } from 'node:fs';
17
+ import { isAbsolute, relative, resolve } from 'node:path';
18
+ import {
19
+ canonicalJson,
20
+ createErrorReportDraft,
21
+ errorReportIdentity,
22
+ finaliseErrorReport,
23
+ reviseErrorReport,
24
+ } from '../error-reporting.mjs';
25
+ import { atomicJson, isWithin, now, sha256 } from '../delivery-documents.mjs';
26
+ import { runtimePaths } from './paths.mjs';
27
+
28
+ const reportIdPattern = /^report_[a-z0-9_]{3,80}$/;
29
+ const adapterIdPattern = /^[a-z][a-z0-9.-]{1,79}$/;
30
+ const versionPattern = /^[0-9]+\.[0-9]+\.[0-9]+(?:[-+][0-9A-Za-z.-]+)?$/;
31
+ const manifestFields = new Set(['schema', 'id', 'name', 'publisher', 'version', 'protocolVersion', 'entrypoint']);
32
+ const publisherFields = new Set(['id', 'name']);
33
+ const acknowledgementFields = new Set(['schema', 'attemptId', 'archiveDigest', 'status', 'externalReference']);
34
+ const maxManifestBytes = 64 * 1024;
35
+ const maxAdapterFiles = 1024;
36
+ const maxAdapterBytes = 64 * 1024 * 1024;
37
+ const maxAdapterOutputBytes = 1024 * 1024;
38
+ const defaultTimeoutMs = 30_000;
39
+
40
+ const DEFAULT_SETTINGS = Object.freeze({
41
+ schema: 'ewai.error-report-settings/v1',
42
+ automaticLocalDrafts: false,
43
+ supportEmail: '',
44
+ });
45
+
46
+ function strictFields(value, fields, label) {
47
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${label} must be an object.`);
48
+ for (const key of Object.keys(value)) if (!fields.has(key)) throw new Error(`${label} contains unknown field: ${key}`);
49
+ }
50
+
51
+ function safeReportId(value) {
52
+ const id = String(value ?? '').trim();
53
+ if (!reportIdPattern.test(id)) throw new Error('Error report id is invalid.');
54
+ return id;
55
+ }
56
+
57
+ function paths(projectRoot) {
58
+ const base = runtimePaths(projectRoot);
59
+ for (const folder of [
60
+ base.errorReportsRoot,
61
+ base.errorReportDraftsRoot,
62
+ base.errorReportPackagesRoot,
63
+ base.errorReportReceiptsRoot,
64
+ base.errorReportArchiveRoot,
65
+ base.errorReportAdaptersRoot,
66
+ ]) mkdirSync(folder, { recursive: true });
67
+ if (!existsSync(base.errorReportSettingsPath)) atomicJson(base.errorReportSettingsPath, DEFAULT_SETTINGS);
68
+ return base;
69
+ }
70
+
71
+ function readJson(path, fallback = null) {
72
+ if (!existsSync(path)) return fallback;
73
+ return JSON.parse(readFileSync(path, 'utf8'));
74
+ }
75
+
76
+ function atomicBuffer(path, bytes) {
77
+ mkdirSync(resolve(path, '..'), { recursive: true });
78
+ const temporary = `${path}.ewai-${process.pid}-${randomUUID()}.tmp`;
79
+ try {
80
+ writeFileSync(temporary, bytes);
81
+ renameSync(temporary, path);
82
+ } finally {
83
+ rmSync(temporary, { force: true });
84
+ }
85
+ }
86
+
87
+ function draftPath(base, id) {
88
+ return resolve(base.errorReportDraftsRoot, `${safeReportId(id)}.json`);
89
+ }
90
+
91
+ function reportPackageRoot(base, id) {
92
+ return resolve(base.errorReportPackagesRoot, safeReportId(id));
93
+ }
94
+
95
+ function revisionFolder(base, id, revision) {
96
+ if (!Number.isInteger(revision) || revision < 1) throw new Error('Error report revision is invalid.');
97
+ return resolve(reportPackageRoot(base, id), `revision-${revision}`);
98
+ }
99
+
100
+ function latestPackagedReport(base, id) {
101
+ const root = reportPackageRoot(base, id);
102
+ if (!existsSync(root)) return null;
103
+ const revisions = readdirSync(root, { withFileTypes: true })
104
+ .filter((entry) => entry.isDirectory() && /^revision-[1-9][0-9]*$/.test(entry.name))
105
+ .map((entry) => Number(entry.name.slice('revision-'.length)))
106
+ .sort((left, right) => right - left);
107
+ for (const revision of revisions) {
108
+ const report = readJson(resolve(revisionFolder(base, id, revision), 'report.json'));
109
+ if (report) return report;
110
+ }
111
+ return null;
112
+ }
113
+
114
+ function archivedReport(base, id) {
115
+ const root = resolve(base.errorReportArchiveRoot, safeReportId(id));
116
+ if (!existsSync(root)) return null;
117
+ const candidates = readdirSync(root).filter((name) => /^revision-[1-9][0-9]*\.json$/.test(name))
118
+ .sort((left, right) => Number(right.match(/\d+/)[0]) - Number(left.match(/\d+/)[0]));
119
+ return candidates.length ? readJson(resolve(root, candidates[0])) : null;
120
+ }
121
+
122
+ function safeProjection(report) {
123
+ if (!report) return null;
124
+ return JSON.parse(JSON.stringify(report));
125
+ }
126
+
127
+ export function errorReportSettings(projectRoot) {
128
+ const base = paths(projectRoot);
129
+ const settings = readJson(base.errorReportSettingsPath, DEFAULT_SETTINGS);
130
+ strictFields(settings, new Set(['schema', 'automaticLocalDrafts', 'supportEmail']), 'Error report settings');
131
+ if (settings.schema !== DEFAULT_SETTINGS.schema || typeof settings.automaticLocalDrafts !== 'boolean' || typeof settings.supportEmail !== 'string') {
132
+ throw new Error('Error report settings are invalid.');
133
+ }
134
+ return { ...settings };
135
+ }
136
+
137
+ export function updateErrorReportSettings(projectRoot, changes) {
138
+ strictFields(changes, new Set(['automaticLocalDrafts', 'supportEmail']), 'Error report settings update');
139
+ const current = errorReportSettings(projectRoot);
140
+ const next = { ...current };
141
+ if ('automaticLocalDrafts' in changes) {
142
+ if (typeof changes.automaticLocalDrafts !== 'boolean') throw new Error('automaticLocalDrafts must be boolean only.');
143
+ next.automaticLocalDrafts = changes.automaticLocalDrafts;
144
+ }
145
+ if ('supportEmail' in changes) {
146
+ const email = String(changes.supportEmail ?? '').trim();
147
+ if (email && (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) || email.length > 254)) throw new Error('Support email is invalid.');
148
+ next.supportEmail = email;
149
+ }
150
+ atomicJson(paths(projectRoot).errorReportSettingsPath, next);
151
+ return next;
152
+ }
153
+
154
+ export function createLocalErrorReport(projectRoot, input, options = {}) {
155
+ const base = paths(projectRoot);
156
+ const report = createErrorReportDraft(input, options);
157
+ if (readLocalErrorReport(projectRoot, report.id)) throw new Error(`Error report already exists: ${report.id}`);
158
+ atomicJson(draftPath(base, report.id), report);
159
+ return safeProjection(report);
160
+ }
161
+
162
+ export function readLocalErrorReport(projectRoot, reportId) {
163
+ const base = paths(projectRoot);
164
+ const id = safeReportId(reportId);
165
+ return safeProjection(readJson(draftPath(base, id)) ?? latestPackagedReport(base, id) ?? archivedReport(base, id));
166
+ }
167
+
168
+ export function listLocalErrorReports(projectRoot, options = {}) {
169
+ const base = paths(projectRoot);
170
+ const ids = new Set();
171
+ for (const entry of readdirSync(base.errorReportDraftsRoot)) if (entry.endsWith('.json')) ids.add(entry.slice(0, -5));
172
+ for (const entry of readdirSync(base.errorReportPackagesRoot, { withFileTypes: true })) if (entry.isDirectory()) ids.add(entry.name);
173
+ for (const entry of readdirSync(base.errorReportArchiveRoot, { withFileTypes: true })) if (entry.isDirectory()) ids.add(entry.name);
174
+ let reports = [...ids].map((id) => readLocalErrorReport(projectRoot, id)).filter(Boolean)
175
+ .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
176
+ if (options.status) reports = reports.filter((report) => report.status === options.status);
177
+ return { schema: 'ewai.error-report-workspace/v1', localOnly: true, settings: errorReportSettings(projectRoot), reports };
178
+ }
179
+
180
+ export function updateLocalErrorReport(projectRoot, reportId, changes, options = {}) {
181
+ const base = paths(projectRoot);
182
+ const current = readLocalErrorReport(projectRoot, reportId);
183
+ if (!current) throw new Error('Error report not found.');
184
+ let source = current;
185
+ if (current.status === 'finalised' || current.status === 'archived') {
186
+ source = { ...current, status: 'draft', attachments: [] };
187
+ delete source.package;
188
+ }
189
+ const revised = reviseErrorReport(source, changes, options);
190
+ atomicJson(draftPath(base, revised.id), revised);
191
+ return safeProjection(revised);
192
+ }
193
+
194
+ function packageLocation(base, report) {
195
+ return resolve(revisionFolder(base, report.id, report.revision), `${report.id}-r${report.revision}.zip`);
196
+ }
197
+
198
+ export function finaliseLocalErrorReport(projectRoot, reportId, options = {}) {
199
+ const base = paths(projectRoot);
200
+ const current = readLocalErrorReport(projectRoot, reportId);
201
+ if (!current) throw new Error('Error report not found.');
202
+ if (current.status === 'finalised') throw new Error('Error report is already finalised.');
203
+ if (current.status !== 'draft') throw new Error('Only a draft error report can be finalised.');
204
+ const result = finaliseErrorReport(current, options);
205
+ const folder = revisionFolder(base, current.id, current.revision);
206
+ const zipPath = packageLocation(base, current);
207
+ mkdirSync(folder, { recursive: true });
208
+ atomicBuffer(zipPath, result.package.bytes);
209
+ atomicJson(resolve(folder, 'report.json'), result.report);
210
+ rmSync(draftPath(base, current.id), { force: true });
211
+ return {
212
+ report: safeProjection(result.report),
213
+ package: {
214
+ relativePath: relative(resolve(projectRoot), zipPath).replaceAll('\\', '/'),
215
+ packageDigest: result.package.packageDigest,
216
+ archiveDigest: result.package.archiveDigest,
217
+ size: result.package.size,
218
+ memberCount: result.package.memberCount,
219
+ },
220
+ };
221
+ }
222
+
223
+ function dedupePath(base) {
224
+ return resolve(base.errorReportsRoot, 'deduplication.json');
225
+ }
226
+
227
+ export function captureErrorReportFailure(projectRoot, input, options = {}) {
228
+ if (!errorReportSettings(projectRoot).automaticLocalDrafts) return { captured: false, reason: 'automatic-local-drafts-disabled' };
229
+ const base = paths(projectRoot);
230
+ const candidate = createErrorReportDraft(input, { ...options, id: options.id ?? `report_${randomUUID().replaceAll('-', '')}` });
231
+ const fingerprint = errorReportIdentity({ ...candidate, id: 'report_deduplicated', revision: 1 });
232
+ const index = readJson(dedupePath(base), { schema: 'ewai.error-report-deduplication/v1', reports: {} });
233
+ const existing = index.reports[fingerprint] ? readLocalErrorReport(projectRoot, index.reports[fingerprint]) : null;
234
+ if (existing) return { captured: true, deduplicated: true, report: existing, transmission: 'none' };
235
+ atomicJson(draftPath(base, candidate.id), candidate);
236
+ index.reports[fingerprint] = candidate.id;
237
+ atomicJson(dedupePath(base), index);
238
+ return { captured: true, deduplicated: false, report: safeProjection(candidate), transmission: 'none' };
239
+ }
240
+
241
+ function finalisedReport(projectRoot, reportId) {
242
+ const report = readLocalErrorReport(projectRoot, reportId);
243
+ if (!report) throw new Error('Error report not found.');
244
+ if (report.status !== 'finalised' || !report.package?.archiveDigest) throw new Error('Error report must be finalised first.');
245
+ return report;
246
+ }
247
+
248
+ function assertDigest(report, expectedDigest) {
249
+ if (!expectedDigest || expectedDigest !== report.package.archiveDigest) throw new Error('Exact finalised package digest confirmation does not match.');
250
+ }
251
+
252
+ export function prepareErrorReportEmail(projectRoot, reportId, options = {}) {
253
+ const base = paths(projectRoot);
254
+ const report = finalisedReport(projectRoot, reportId);
255
+ assertDigest(report, options.expectedDigest);
256
+ const recipient = String(options.recipient ?? errorReportSettings(projectRoot).supportEmail ?? '').trim();
257
+ if (recipient && (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(recipient) || recipient.length > 254)) throw new Error('Email recipient is invalid.');
258
+ const subject = `EWAI error report ${report.id}`;
259
+ const body = `Report: ${report.id}\nPackage digest: ${report.package.archiveDigest}\n\nThe ZIP must be attached manually before sending.`;
260
+ const query = new URLSearchParams({ subject, body });
261
+ const zipPath = packageLocation(base, report);
262
+ const preparation = {
263
+ schema: 'ewai.error-report-email-preparation/v1',
264
+ id: randomUUID(),
265
+ reportId: report.id,
266
+ revision: report.revision,
267
+ archiveDigest: report.package.archiveDigest,
268
+ status: 'prepared-not-sent',
269
+ mailto: `mailto:${recipient}?${query.toString()}`,
270
+ packageReference: relative(resolve(projectRoot), zipPath).replaceAll('\\', '/'),
271
+ message: 'Email prepared but not sent. Reveal the package and attach the ZIP manually before you send it.',
272
+ preparedAt: String(options.at ?? now()),
273
+ };
274
+ if (options.launch === true) preparation.handoff = launchEmailHandoff(zipPath, preparation.mailto, options);
275
+ const folder = resolve(base.errorReportReceiptsRoot, report.id);
276
+ mkdirSync(folder, { recursive: true });
277
+ atomicJson(resolve(folder, `email-${preparation.id}.json`), preparation);
278
+ return preparation;
279
+ }
280
+
281
+ function handoffCommands(zipPath, mailto, platform) {
282
+ if (platform === 'darwin') return [
283
+ { target: 'package', command: 'open', args: ['-R', zipPath] },
284
+ { target: 'email-client', command: 'open', args: [mailto] },
285
+ ];
286
+ if (platform === 'win32') return [
287
+ { target: 'package', command: 'explorer.exe', args: [`/select,${zipPath}`] },
288
+ { target: 'email-client', command: 'rundll32.exe', args: ['url.dll,FileProtocolHandler', mailto] },
289
+ ];
290
+ if (platform === 'linux') return [
291
+ { target: 'package', command: 'xdg-open', args: [resolve(zipPath, '..')] },
292
+ { target: 'email-client', command: 'xdg-open', args: [mailto] },
293
+ ];
294
+ return [];
295
+ }
296
+
297
+ function launchEmailHandoff(zipPath, mailto, options = {}) {
298
+ const platform = String(options.platform ?? process.platform);
299
+ const commands = handoffCommands(zipPath, mailto, platform);
300
+ if (!commands.length) return {
301
+ requested: true,
302
+ status: 'unsupported-platform',
303
+ packageReveal: 'not-launched',
304
+ emailClient: 'not-launched',
305
+ };
306
+ const launcher = options.launcher ?? ((command, args) => spawnSync(command, args, {
307
+ env: { PATH: process.env.PATH ?? '', LANG: 'C' },
308
+ shell: false,
309
+ stdio: 'ignore',
310
+ timeout: 5_000,
311
+ }));
312
+ const outcomes = commands.map(({ target, command, args }) => {
313
+ try {
314
+ const result = launcher(command, args);
315
+ return [target, result?.status === 0 && !result?.error ? 'launch-requested' : 'launch-failed'];
316
+ } catch {
317
+ return [target, 'launch-failed'];
318
+ }
319
+ });
320
+ const byTarget = Object.fromEntries(outcomes);
321
+ return {
322
+ requested: true,
323
+ status: outcomes.every(([, status]) => status === 'launch-requested') ? 'launch-requested' : 'partially-launched',
324
+ packageReveal: byTarget.package,
325
+ emailClient: byTarget['email-client'],
326
+ };
327
+ }
328
+
329
+ export function archiveErrorReport(projectRoot, reportId, options = {}) {
330
+ if (options.confirmed !== true) throw new Error('Archiving local report material requires explicit confirmation.');
331
+ const base = paths(projectRoot);
332
+ const report = readLocalErrorReport(projectRoot, reportId);
333
+ if (!report) throw new Error('Error report not found.');
334
+ const archived = { ...report, status: 'archived', updatedAt: String(options.at ?? now()) };
335
+ const folder = resolve(base.errorReportArchiveRoot, report.id);
336
+ mkdirSync(folder, { recursive: true });
337
+ atomicJson(resolve(folder, `revision-${report.revision}.json`), archived);
338
+ rmSync(draftPath(base, report.id), { force: true });
339
+ rmSync(reportPackageRoot(base, report.id), { recursive: true, force: true });
340
+ return safeProjection(archived);
341
+ }
342
+
343
+ export function deleteErrorReport(projectRoot, reportId, options = {}) {
344
+ if (options.confirmed !== true) throw new Error('Deleting local error-report material requires explicit confirmation.');
345
+ const base = paths(projectRoot);
346
+ const id = safeReportId(reportId);
347
+ const existed = Boolean(readLocalErrorReport(projectRoot, id));
348
+ rmSync(draftPath(base, id), { force: true });
349
+ rmSync(reportPackageRoot(base, id), { recursive: true, force: true });
350
+ rmSync(resolve(base.errorReportArchiveRoot, id), { recursive: true, force: true });
351
+ return {
352
+ schema: 'ewai.error-report-deletion/v1',
353
+ reportId: id,
354
+ localMaterialRemoved: existed,
355
+ providerReceiptsRetained: listErrorReportReceipts(projectRoot, id).receipts.length,
356
+ externalRecall: false,
357
+ };
358
+ }
359
+
360
+ function packageDigest(root) {
361
+ let count = 0;
362
+ let total = 0;
363
+ const files = [];
364
+ const visit = (folder) => {
365
+ for (const entry of readdirSync(folder, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name))) {
366
+ const path = resolve(folder, entry.name);
367
+ const stats = lstatSync(path);
368
+ if (stats.isSymbolicLink()) throw new Error('Error-report provider packages cannot contain symbolic links.');
369
+ if (stats.isDirectory()) visit(path);
370
+ else if (stats.isFile()) {
371
+ count += 1;
372
+ total += stats.size;
373
+ if (count > maxAdapterFiles || total > maxAdapterBytes) throw new Error('Error-report provider package exceeds safe limits.');
374
+ files.push({ path, name: relative(root, path).replaceAll('\\', '/'), mode: stats.mode & 0o777 });
375
+ } else throw new Error('Error-report provider packages may contain only regular files and directories.');
376
+ }
377
+ };
378
+ visit(root);
379
+ const hash = createHash('sha256');
380
+ for (const file of files) {
381
+ const bytes = readFileSync(file.path);
382
+ hash.update(`${file.name.length}:${file.name}:${file.mode}:${bytes.length}:`);
383
+ hash.update(bytes);
384
+ }
385
+ return `sha256:${hash.digest('hex')}`;
386
+ }
387
+
388
+ function readProviderManifest(adapterRoot) {
389
+ const root = resolve(adapterRoot);
390
+ if (!existsSync(root) || !lstatSync(root).isDirectory() || lstatSync(root).isSymbolicLink()) throw new Error('Provider root must be an existing non-symbolic directory.');
391
+ const trustedRoot = realpathSync(root);
392
+ const path = resolve(trustedRoot, 'error-report-provider.json');
393
+ if (!existsSync(path) || !lstatSync(path).isFile() || lstatSync(path).isSymbolicLink()) throw new Error('Provider requires error-report-provider.json.');
394
+ if (statSync(path).size > maxManifestBytes) throw new Error('Provider manifest is too large.');
395
+ const bytes = readFileSync(path);
396
+ let manifest;
397
+ try { manifest = JSON.parse(bytes.toString('utf8')); } catch { throw new Error('Provider manifest must be valid JSON.'); }
398
+ strictFields(manifest, manifestFields, 'Provider manifest');
399
+ if (manifest.schema !== 'ewai.error-report-provider/v1') throw new Error('Unsupported provider manifest schema.');
400
+ if (!adapterIdPattern.test(String(manifest.id ?? ''))) throw new Error('Provider id is invalid.');
401
+ if (!String(manifest.name ?? '').trim() || String(manifest.name).length > 120) throw new Error('Provider name is invalid.');
402
+ strictFields(manifest.publisher, publisherFields, 'Provider publisher');
403
+ if (!adapterIdPattern.test(String(manifest.publisher.id ?? '')) || !String(manifest.publisher.name ?? '').trim()) throw new Error('Provider publisher is invalid.');
404
+ if (!versionPattern.test(String(manifest.version ?? '')) || manifest.protocolVersion !== '1') throw new Error('Provider version or protocol version is invalid.');
405
+ const entry = String(manifest.entrypoint ?? '').trim();
406
+ if (!entry || isAbsolute(entry)) throw new Error('Provider entrypoint must be relative.');
407
+ const entrypoint = resolve(trustedRoot, entry);
408
+ if (!isWithin(trustedRoot, entrypoint) || !existsSync(entrypoint) || lstatSync(entrypoint).isSymbolicLink() || !lstatSync(entrypoint).isFile() || !isWithin(trustedRoot, realpathSync(entrypoint))) {
409
+ throw new Error('Provider entrypoint must be a regular file within the trusted package.');
410
+ }
411
+ try { accessSync(entrypoint, constants.X_OK); } catch { throw new Error('Provider entrypoint must be executable.'); }
412
+ return { manifest, trustedRoot, entrypoint, manifestDigest: `sha256:${sha256(bytes)}`, packageDigest: packageDigest(trustedRoot) };
413
+ }
414
+
415
+ function safeProvider(value) {
416
+ return {
417
+ id: value.manifest.id,
418
+ name: value.manifest.name,
419
+ publisher: value.manifest.publisher,
420
+ version: value.manifest.version,
421
+ protocolVersion: value.manifest.protocolVersion,
422
+ manifestDigest: value.manifestDigest,
423
+ packageDigest: value.packageDigest,
424
+ registeredAt: value.registeredAt ?? null,
425
+ };
426
+ }
427
+
428
+ export function validateErrorReportProvider(adapterRoot) {
429
+ return safeProvider(readProviderManifest(adapterRoot));
430
+ }
431
+
432
+ function registryPath(base) {
433
+ return resolve(base.errorReportAdaptersRoot, 'registry.json');
434
+ }
435
+
436
+ function providerRegistry(base) {
437
+ return readJson(registryPath(base), { schema: 'ewai.error-report-provider-registry/v1', providers: [] });
438
+ }
439
+
440
+ export function registerErrorReportProvider(projectRoot, adapterRoot, options = {}) {
441
+ if (options.confirmed !== true) throw new Error('Provider registration requires explicit confirmation because it trusts local code.');
442
+ const base = paths(projectRoot);
443
+ const validated = readProviderManifest(adapterRoot);
444
+ const registry = providerRegistry(base);
445
+ const previous = registry.providers.find((entry) => entry.manifest.id === validated.manifest.id);
446
+ if (previous && previous.manifest.version === validated.manifest.version && previous.packageDigest !== validated.packageDigest) {
447
+ throw new Error('Provider same-version package digest drift is not allowed.');
448
+ }
449
+ const record = { ...validated, registeredAt: now() };
450
+ registry.providers = [...registry.providers.filter((entry) => entry.manifest.id !== validated.manifest.id), record]
451
+ .sort((left, right) => left.manifest.id.localeCompare(right.manifest.id));
452
+ atomicJson(registryPath(base), registry);
453
+ return safeProvider(record);
454
+ }
455
+
456
+ export function listErrorReportProviders(projectRoot) {
457
+ const base = paths(projectRoot);
458
+ return { schema: 'ewai.error-report-providers/v1', providers: providerRegistry(base).providers.map(safeProvider) };
459
+ }
460
+
461
+ function invokeProvider(provider, request, timeoutMs) {
462
+ return new Promise((resolvePromise) => {
463
+ const child = spawn(provider.entrypoint, [], {
464
+ cwd: provider.trustedRoot,
465
+ env: { PATH: process.env.PATH ?? '', LANG: 'C', EWAI_ERROR_REPORT_PROTOCOL: '1' },
466
+ shell: false,
467
+ stdio: ['pipe', 'pipe', 'pipe'],
468
+ });
469
+ let stdout = Buffer.alloc(0);
470
+ let stderrSize = 0;
471
+ let oversized = false;
472
+ let timedOut = false;
473
+ child.stdout.on('data', (chunk) => {
474
+ if (stdout.length + chunk.length > maxAdapterOutputBytes) { oversized = true; child.kill('SIGTERM'); }
475
+ else stdout = Buffer.concat([stdout, chunk]);
476
+ });
477
+ child.stderr.on('data', (chunk) => {
478
+ stderrSize += chunk.length;
479
+ if (stderrSize > maxAdapterOutputBytes) { oversized = true; child.kill('SIGTERM'); }
480
+ });
481
+ const timer = setTimeout(() => { timedOut = true; child.kill('SIGTERM'); }, timeoutMs);
482
+ timer.unref();
483
+ child.once('error', (error) => { clearTimeout(timer); resolvePromise({ exitCode: -1, stdout, oversized, timedOut, error }); });
484
+ child.once('close', (code) => { clearTimeout(timer); resolvePromise({ exitCode: code ?? -1, stdout, oversized, timedOut, error: null }); });
485
+ child.stdin.end(`${JSON.stringify(request)}\n`);
486
+ });
487
+ }
488
+
489
+ function safeFailureReason(result, error) {
490
+ if (result?.timedOut) return 'provider-timeout';
491
+ if (result?.oversized) return 'provider-output-limit';
492
+ if (result?.error || result?.exitCode !== 0) return 'provider-execution-failed';
493
+ if (/digest/i.test(String(error?.message))) return 'provider-digest-mismatch';
494
+ if (/rejected/i.test(String(error?.message))) return 'provider-rejected';
495
+ return 'provider-response-invalid';
496
+ }
497
+
498
+ function validateAcknowledgement(response, expected) {
499
+ strictFields(response, acknowledgementFields, 'Provider acknowledgement');
500
+ if (response.schema !== 'ewai.error-report-provider-ack/v1') throw new Error('Provider acknowledgement schema is invalid.');
501
+ if (response.attemptId !== expected.attemptId) throw new Error('Provider attempt identity does not match.');
502
+ if (response.archiveDigest !== expected.archiveDigest) throw new Error('Provider package digest does not match.');
503
+ if (!['accepted', 'rejected'].includes(response.status)) throw new Error('Provider status is invalid.');
504
+ if (response.status !== 'accepted') throw new Error('Provider rejected the report package.');
505
+ const externalReference = String(response.externalReference ?? '').trim();
506
+ if (!externalReference || externalReference.length > 240 || /[\u0000-\u001f]/.test(externalReference)) throw new Error('Provider external reference is invalid.');
507
+ return { status: 'accepted', externalReference };
508
+ }
509
+
510
+ function receiptFolder(base, id) {
511
+ return resolve(base.errorReportReceiptsRoot, safeReportId(id));
512
+ }
513
+
514
+ export async function submitErrorReportProvider(projectRoot, reportId, providerId, options = {}) {
515
+ if (options.confirmed !== true) throw new Error('Provider submission requires explicit confirmation.');
516
+ const base = paths(projectRoot);
517
+ const report = finalisedReport(projectRoot, reportId);
518
+ assertDigest(report, options.expectedDigest);
519
+ if (!adapterIdPattern.test(String(providerId ?? ''))) throw new Error('Provider id is invalid.');
520
+ const provider = providerRegistry(base).providers.find((entry) => entry.manifest.id === providerId);
521
+ if (!provider) throw new Error('Registered error-report provider not found.');
522
+ const current = readProviderManifest(provider.trustedRoot);
523
+ if (current.packageDigest !== provider.packageDigest || current.manifestDigest !== provider.manifestDigest) throw new Error('Registered provider package has changed and must be registered again.');
524
+ const attemptId = randomUUID();
525
+ const zipPath = packageLocation(base, report);
526
+ const request = {
527
+ schema: 'ewai.error-report-submission/v1',
528
+ attemptId,
529
+ reportId: report.id,
530
+ revision: report.revision,
531
+ archiveDigest: report.package.archiveDigest,
532
+ packagePath: zipPath,
533
+ packageSize: report.package.size,
534
+ };
535
+ const folder = receiptFolder(base, report.id);
536
+ mkdirSync(folder, { recursive: true });
537
+ let result;
538
+ let acknowledged;
539
+ try {
540
+ result = await invokeProvider(current, request, Math.min(Math.max(Number(options.timeoutMs ?? defaultTimeoutMs), 100), 120_000));
541
+ if (result.timedOut || result.oversized || result.error || result.exitCode !== 0) throw new Error('Provider execution did not complete safely.');
542
+ let response;
543
+ try { response = JSON.parse(result.stdout.toString('utf8')); } catch { throw new Error('Provider response is malformed.'); }
544
+ acknowledged = validateAcknowledgement(response, request);
545
+ } catch (error) {
546
+ const attempt = {
547
+ schema: 'ewai.error-report-attempt/v1', id: attemptId, reportId: report.id, revision: report.revision,
548
+ providerId, archiveDigest: report.package.archiveDigest, status: 'failed', reason: safeFailureReason(result, error),
549
+ attemptedAt: String(options.at ?? now()), retryable: true,
550
+ };
551
+ atomicJson(resolve(folder, `attempt-${attemptId}.json`), attempt);
552
+ return attempt;
553
+ }
554
+ const attempt = {
555
+ schema: 'ewai.error-report-attempt/v1', id: attemptId, reportId: report.id, revision: report.revision,
556
+ providerId, archiveDigest: report.package.archiveDigest, status: acknowledged.status,
557
+ externalReference: acknowledged.externalReference, attemptedAt: String(options.at ?? now()), retryable: false,
558
+ };
559
+ const receipt = {
560
+ schema: 'ewai.error-report-receipt/v1', id: randomUUID(), attemptId, reportId: report.id,
561
+ providerId, archiveDigest: report.package.archiveDigest, status: 'accepted',
562
+ externalReference: acknowledged.externalReference, acceptedAt: attempt.attemptedAt,
563
+ assurance: 'Transport accepted by the provider; issue resolution, security acceptance and release remain unproven.',
564
+ };
565
+ atomicJson(resolve(folder, `attempt-${attemptId}.json`), attempt);
566
+ atomicJson(resolve(folder, `receipt-${receipt.id}.json`), receipt);
567
+ return attempt;
568
+ }
569
+
570
+ export function listErrorReportReceipts(projectRoot, reportId) {
571
+ const base = paths(projectRoot);
572
+ const folder = receiptFolder(base, reportId);
573
+ if (!existsSync(folder)) return { schema: 'ewai.error-report-receipts/v1', reportId: safeReportId(reportId), attempts: [], receipts: [] };
574
+ const values = readdirSync(folder).filter((name) => name.endsWith('.json')).map((name) => readJson(resolve(folder, name))).filter(Boolean);
575
+ return {
576
+ schema: 'ewai.error-report-receipts/v1',
577
+ reportId: safeReportId(reportId),
578
+ attempts: values.filter((value) => value.schema === 'ewai.error-report-attempt/v1').sort((a, b) => b.attemptedAt.localeCompare(a.attemptedAt)),
579
+ receipts: values.filter((value) => value.schema === 'ewai.error-report-receipt/v1').sort((a, b) => b.acceptedAt.localeCompare(a.acceptedAt)),
580
+ };
581
+ }