alphacouncil-agent 0.9.2 → 1.0.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 (493) hide show
  1. package/.claude/commands/alpha.md +22 -3
  2. package/.claude-plugin/marketplace.json +3 -3
  3. package/.claude-plugin/plugin.json +2 -2
  4. package/.codex-plugin/plugin.json +4 -4
  5. package/.grok/commands/alpha.md +22 -3
  6. package/.opencode/command/alpha.md +22 -3
  7. package/AGENTS.md +42 -4
  8. package/CHANGELOG.md +164 -0
  9. package/CLAUDE.md +59 -7
  10. package/README.ja.md +25 -11
  11. package/README.md +44 -12
  12. package/README.zh-CN.md +37 -12
  13. package/commands/alpha.md +22 -3
  14. package/data/authored/core-seats.mjs +808 -0
  15. package/data/authored/growth-seats.mjs +581 -0
  16. package/data/authored/index.mjs +114 -0
  17. package/data/authored/quant-seats.mjs +564 -0
  18. package/data/authored/value-seats.mjs +663 -0
  19. package/data/build-profile.v1.json +2 -1
  20. package/data/council-evaluation-protocol.v1.json +7 -4
  21. package/data/master-selector-method-locales.v1.mjs +296 -0
  22. package/data/persona-v3-ai-assisted-solo-profile.v1.json +2 -2
  23. package/data/persona-v3-build-specs.v1.mjs +60 -3
  24. package/docs/INSTALL.md +43 -6
  25. package/docs/personas.md +2 -1
  26. package/docs/plans/v1.0-roadmap.md +487 -0
  27. package/docs/releases/v0.9.3.md +86 -0
  28. package/docs/releases/v0.9.4.md +79 -0
  29. package/docs/releases/v0.9.5.md +96 -0
  30. package/docs/releases/v1.0.0.md +164 -0
  31. package/docs/report-contract.md +75 -2
  32. package/knowledge/ai-assisted-solo/experiments/runs/a.json +13 -13
  33. package/knowledge/ai-assisted-solo/experiments/runs/b.json +2 -2
  34. package/knowledge/ai-assisted-solo/experiments/runs/c.json +2 -2
  35. package/knowledge/ai-assisted-solo/experiments/runs/d13.json +126 -126
  36. package/knowledge/ai-assisted-solo/experiments/runs/d26.json +274 -254
  37. package/knowledge/ai-assisted-solo/experiments/runs/e-d13.json +129 -129
  38. package/knowledge/ai-assisted-solo/experiments/runs/e-d26.json +277 -257
  39. package/knowledge/ai-assisted-solo/experiments/runs/h_ai_reference.json +278 -258
  40. package/knowledge/ai-assisted-solo/experiments/simulation-input.json +7278 -556
  41. package/knowledge/ai-assisted-solo/experiments/simulation-manifest.json +22 -22
  42. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_ackman/reviews/{change_value_bridge.ai-review.json → market_capitalisation.ai-review.json} +46 -46
  43. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_ackman/reviews/{power_map.ai-review.json → owner_earnings_yield.ai-review.json} +46 -46
  44. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_aschenbrenner/reviews/{compute_power_bridge.ai-review.json → market_capitalisation.ai-review.json} +46 -46
  45. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_aschenbrenner/reviews/{timeline_reverse_valuation.ai-review.json → price_implied_cash_yield.ai-review.json} +46 -46
  46. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_asness/reviews/{crowding_cost_stress.ai-review.json → nominal_earnings_yield_gap.ai-review.json} +49 -49
  47. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_asness/reviews/{factor_decomposer.ai-review.json → real_earnings_yield_gap.ai-review.json} +49 -49
  48. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_bogle/reviews/expected_return_over_long_bond.ai-review.json +277 -0
  49. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_bogle/reviews/fundamental_expected_return.ai-review.json +277 -0
  50. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_buffett/reviews/{incremental_returns.ai-review.json → market_capitalisation.ai-review.json} +46 -46
  51. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_buffett/reviews/{owner_earnings_rebuilder.ai-review.json → owner_earnings_yield.ai-review.json} +46 -46
  52. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_burry/reviews/{mispricing_carry.ai-review.json → net_current_asset_surplus_per_share.ai-review.json} +49 -49
  53. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_burry/reviews/{capital_structure_reader.ai-review.json → net_current_asset_value_per_share.ai-review.json} +46 -46
  54. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_cathie_wood/reviews/{cost_adoption_curve.ai-review.json → current_cash_yield.ai-review.json} +46 -46
  55. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_cathie_wood/reviews/{capture_valuation_bridge.ai-review.json → market_capitalisation.ai-review.json} +46 -46
  56. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_dalio/reviews/{debt_driver_stress.ai-review.json → policy_real_rate.ai-review.json} +49 -49
  57. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_dalio/reviews/{regime_classifier.ai-review.json → real_curve_slope.ai-review.json} +49 -49
  58. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_damodaran/reviews/{reverse_valuation.ai-review.json → implied_equity_risk_premium.ai-review.json} +49 -49
  59. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_damodaran/reviews/{story_dcf.ai-review.json → premium_versus_long_run_average.ai-review.json} +49 -49
  60. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_druckenmiller/reviews/{liquidity_revision_map.ai-review.json → liquidity_curve_impulse.ai-review.json} +49 -49
  61. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_druckenmiller/reviews/{inflection_payoff.ai-review.json → policy_real_rate.ai-review.json} +49 -49
  62. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_duan_yongping/reviews/{opportunity_cost.ai-review.json → five_year_opportunity_cost.ai-review.json} +46 -46
  63. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_duan_yongping/reviews/{user_value_evidence.ai-review.json → margin_retention.ai-review.json} +46 -46
  64. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_fisher/reviews/{research_productivity.ai-review.json → margin_retention.ai-review.json} +46 -46
  65. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_fisher/reviews/{scuttlebutt_graph.ai-review.json → reinvestment_spread.ai-review.json} +49 -49
  66. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_forensic_short/reviews/{borrow_catalyst.ai-review.json → cash_conversion_gap.ai-review.json} +49 -49
  67. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_forensic_short/reviews/{accounting_rebuilder.ai-review.json → cash_cover_of_leverage.ai-review.json} +46 -46
  68. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_graham/reviews/{asset_floor.ai-review.json → defensive_hurdle_base.ai-review.json} +46 -46
  69. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_graham/reviews/{normalized_earnings.ai-review.json → half_normalised_earnings_yield.ai-review.json} +46 -46
  70. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_jhunjhunwala/reviews/{penetration_liquidity.ai-review.json → cash_quality_gap.ai-review.json} +49 -49
  71. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_jhunjhunwala/reviews/{promoter_governance.ai-review.json → real_structural_growth.ai-review.json} +49 -49
  72. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_klarman/reviews/{recovery_waterfall.ai-review.json → downside_asset_coverage.ai-review.json} +46 -46
  73. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_klarman/reviews/{cash_catalyst_compare.ai-review.json → market_capitalisation.ai-review.json} +46 -46
  74. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_li_lu/reviews/{promise_integrity_ledger.ai-review.json → cash_backed_decade_return.ai-review.json} +46 -46
  75. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_li_lu/reviews/{ten_year_return_bridge.ai-review.json → decade_return_over_long_bond.ai-review.json} +49 -49
  76. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_lynch/reviews/{story_numbers_check.ai-review.json → growth_justified_capitalisation.ai-review.json} +46 -46
  77. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_lynch/reviews/{category_classifier.ai-review.json → market_capitalisation.ai-review.json} +46 -46
  78. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_marks/reviews/{cycle_temperature.ai-review.json → credit_cycle_position.ai-review.json} +49 -49
  79. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_marks/reviews/{implied_expectations.ai-review.json → equity_over_corporate_debt.ai-review.json} +49 -49
  80. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_munger/reviews/{failure_path_graph.ai-review.json → debt_service_cushion.ai-review.json} +49 -49
  81. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_munger/reviews/{incentive_map.ai-review.json → reported_earnings_cash_gap.ai-review.json} +49 -49
  82. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_natenberg/reviews/{surface_builder.ai-review.json → normalised_skew.ai-review.json} +46 -46
  83. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_natenberg/reviews/{greeks_payoff.ai-review.json → round_trip_cost.ai-review.json} +46 -46
  84. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_pabrai/reviews/{downside_floor.ai-review.json → downside_floor_coverage.ai-review.json} +46 -46
  85. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_pabrai/reviews/{discrete_payoff.ai-review.json → market_capitalisation.ai-review.json} +46 -46
  86. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_simons/reviews/{oos_cost_test.ai-review.json → absolute_session_move.ai-review.json} +42 -42
  87. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_simons/reviews/{leakage_audit.ai-review.json → session_move_over_implied.ai-review.json} +46 -46
  88. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_sinclair/reviews/{realized_vol_forecast.ai-review.json → round_trip_cost.ai-review.json} +46 -46
  89. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_sinclair/reviews/{net_edge_sizer.ai-review.json → volatility_per_unit_of_width.ai-review.json} +46 -46
  90. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_soros/reviews/{reversal_monitor.ai-review.json → credit_cycle_position.ai-review.json} +49 -49
  91. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_soros/reviews/{reflexivity_graph.ai-review.json → financing_burden.ai-review.json} +46 -46
  92. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_taleb/reviews/{tail_friction.ai-review.json → debt_service_cushion.ai-review.json} +49 -49
  93. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_taleb/reviews/{payoff_ruin.ai-review.json → normalised_skew.ai-review.json} +46 -46
  94. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_thorp/reviews/{edge_recalculator.ai-review.json → edge_per_unit_of_volatility.ai-review.json} +46 -46
  95. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/master_thorp/reviews/{fractional_kelly.ai-review.json → net_edge.ai-review.json} +49 -49
  96. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-formula-reviews/review-manifest.json +288 -270
  97. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-semantic-adjudications/index.json +2 -2
  98. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-semantic-extractions/index.json +2 -2
  99. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-semantic-skeptic-reviews/index.json +2 -2
  100. package/knowledge/ai-assisted-solo/reviews/persona-v3-ai-source-prereviews/index.json +6 -2
  101. package/knowledge/solo-test/masters/master_ackman/decision_policy.json +122 -11
  102. package/knowledge/solo-test/masters/master_ackman/doctrine.jsonl +2 -2
  103. package/knowledge/solo-test/masters/master_ackman/manifest.json +27 -10
  104. package/knowledge/solo-test/masters/master_ackman/provisional-index.json +3 -3
  105. package/knowledge/solo-test/masters/master_ackman/sources.jsonl +1 -1
  106. package/knowledge/solo-test/masters/master_ackman/tools.json +56 -30
  107. package/knowledge/solo-test/masters/master_aschenbrenner/decision_policy.json +101 -10
  108. package/knowledge/solo-test/masters/master_aschenbrenner/doctrine.jsonl +2 -2
  109. package/knowledge/solo-test/masters/master_aschenbrenner/manifest.json +26 -8
  110. package/knowledge/solo-test/masters/master_aschenbrenner/provisional-index.json +3 -3
  111. package/knowledge/solo-test/masters/master_aschenbrenner/sources.jsonl +1 -1
  112. package/knowledge/solo-test/masters/master_aschenbrenner/tools.json +56 -30
  113. package/knowledge/solo-test/masters/master_asness/decision_policy.json +89 -10
  114. package/knowledge/solo-test/masters/master_asness/doctrine.jsonl +2 -2
  115. package/knowledge/solo-test/masters/master_asness/manifest.json +25 -8
  116. package/knowledge/solo-test/masters/master_asness/provisional-index.json +3 -3
  117. package/knowledge/solo-test/masters/master_asness/sources.jsonl +1 -1
  118. package/knowledge/solo-test/masters/master_asness/tools.json +56 -30
  119. package/knowledge/solo-test/masters/master_bogle/counterfactuals.jsonl +0 -0
  120. package/knowledge/solo-test/masters/master_bogle/decision_cases.jsonl +0 -0
  121. package/knowledge/solo-test/masters/master_bogle/decision_policy.json +170 -0
  122. package/knowledge/solo-test/masters/master_bogle/doctrine.jsonl +14 -0
  123. package/knowledge/solo-test/masters/master_bogle/evaluation/calibration_cases.jsonl +0 -0
  124. package/knowledge/solo-test/masters/master_bogle/evaluation/experiments.json +5 -0
  125. package/knowledge/solo-test/masters/master_bogle/evaluation/golden_cases.jsonl +0 -0
  126. package/knowledge/solo-test/masters/master_bogle/evaluation/pairwise_cases.jsonl +0 -0
  127. package/knowledge/solo-test/masters/master_bogle/failures.jsonl +0 -0
  128. package/knowledge/solo-test/masters/master_bogle/manifest.json +141 -0
  129. package/knowledge/solo-test/masters/master_bogle/memory_policy.json +5 -0
  130. package/knowledge/solo-test/masters/master_bogle/provisional-index.json +41 -0
  131. package/knowledge/solo-test/masters/master_bogle/research_policy.json +11 -0
  132. package/knowledge/solo-test/masters/master_bogle/sources.jsonl +1 -0
  133. package/knowledge/solo-test/masters/master_bogle/tools.json +120 -0
  134. package/knowledge/solo-test/masters/master_bogle/voice.en.md +7 -0
  135. package/knowledge/solo-test/masters/master_bogle/voice.zh.md +7 -0
  136. package/knowledge/solo-test/masters/master_buffett/decision_policy.json +102 -11
  137. package/knowledge/solo-test/masters/master_buffett/doctrine.jsonl +2 -2
  138. package/knowledge/solo-test/masters/master_buffett/manifest.json +24 -8
  139. package/knowledge/solo-test/masters/master_buffett/provisional-index.json +3 -3
  140. package/knowledge/solo-test/masters/master_buffett/sources.jsonl +1 -1
  141. package/knowledge/solo-test/masters/master_buffett/tools.json +55 -29
  142. package/knowledge/solo-test/masters/master_burry/decision_policy.json +102 -12
  143. package/knowledge/solo-test/masters/master_burry/doctrine.jsonl +2 -2
  144. package/knowledge/solo-test/masters/master_burry/manifest.json +26 -10
  145. package/knowledge/solo-test/masters/master_burry/provisional-index.json +3 -3
  146. package/knowledge/solo-test/masters/master_burry/sources.jsonl +1 -1
  147. package/knowledge/solo-test/masters/master_burry/tools.json +56 -30
  148. package/knowledge/solo-test/masters/master_cathie_wood/decision_policy.json +74 -12
  149. package/knowledge/solo-test/masters/master_cathie_wood/doctrine.jsonl +2 -2
  150. package/knowledge/solo-test/masters/master_cathie_wood/manifest.json +26 -10
  151. package/knowledge/solo-test/masters/master_cathie_wood/provisional-index.json +3 -3
  152. package/knowledge/solo-test/masters/master_cathie_wood/sources.jsonl +1 -1
  153. package/knowledge/solo-test/masters/master_cathie_wood/tools.json +56 -30
  154. package/knowledge/solo-test/masters/master_dalio/decision_policy.json +123 -16
  155. package/knowledge/solo-test/masters/master_dalio/doctrine.jsonl +2 -2
  156. package/knowledge/solo-test/masters/master_dalio/manifest.json +24 -8
  157. package/knowledge/solo-test/masters/master_dalio/provisional-index.json +3 -3
  158. package/knowledge/solo-test/masters/master_dalio/sources.jsonl +1 -1
  159. package/knowledge/solo-test/masters/master_dalio/tools.json +56 -30
  160. package/knowledge/solo-test/masters/master_damodaran/decision_policy.json +58 -10
  161. package/knowledge/solo-test/masters/master_damodaran/doctrine.jsonl +2 -2
  162. package/knowledge/solo-test/masters/master_damodaran/manifest.json +23 -8
  163. package/knowledge/solo-test/masters/master_damodaran/provisional-index.json +3 -3
  164. package/knowledge/solo-test/masters/master_damodaran/sources.jsonl +1 -1
  165. package/knowledge/solo-test/masters/master_damodaran/tools.json +54 -28
  166. package/knowledge/solo-test/masters/master_druckenmiller/decision_policy.json +89 -10
  167. package/knowledge/solo-test/masters/master_druckenmiller/doctrine.jsonl +2 -2
  168. package/knowledge/solo-test/masters/master_druckenmiller/manifest.json +25 -8
  169. package/knowledge/solo-test/masters/master_druckenmiller/provisional-index.json +3 -3
  170. package/knowledge/solo-test/masters/master_druckenmiller/sources.jsonl +1 -1
  171. package/knowledge/solo-test/masters/master_druckenmiller/tools.json +55 -29
  172. package/knowledge/solo-test/masters/master_duan_yongping/decision_policy.json +138 -13
  173. package/knowledge/solo-test/masters/master_duan_yongping/doctrine.jsonl +2 -2
  174. package/knowledge/solo-test/masters/master_duan_yongping/manifest.json +31 -11
  175. package/knowledge/solo-test/masters/master_duan_yongping/provisional-index.json +3 -3
  176. package/knowledge/solo-test/masters/master_duan_yongping/sources.jsonl +1 -1
  177. package/knowledge/solo-test/masters/master_duan_yongping/tools.json +82 -30
  178. package/knowledge/solo-test/masters/master_fisher/decision_policy.json +102 -11
  179. package/knowledge/solo-test/masters/master_fisher/doctrine.jsonl +2 -2
  180. package/knowledge/solo-test/masters/master_fisher/manifest.json +27 -10
  181. package/knowledge/solo-test/masters/master_fisher/provisional-index.json +3 -3
  182. package/knowledge/solo-test/masters/master_fisher/sources.jsonl +1 -1
  183. package/knowledge/solo-test/masters/master_fisher/tools.json +58 -32
  184. package/knowledge/solo-test/masters/master_forensic_short/decision_policy.json +93 -14
  185. package/knowledge/solo-test/masters/master_forensic_short/doctrine.jsonl +2 -2
  186. package/knowledge/solo-test/masters/master_forensic_short/manifest.json +20 -8
  187. package/knowledge/solo-test/masters/master_forensic_short/provisional-index.json +3 -3
  188. package/knowledge/solo-test/masters/master_forensic_short/sources.jsonl +1 -1
  189. package/knowledge/solo-test/masters/master_forensic_short/tools.json +53 -27
  190. package/knowledge/solo-test/masters/master_graham/decision_policy.json +103 -12
  191. package/knowledge/solo-test/masters/master_graham/doctrine.jsonl +2 -2
  192. package/knowledge/solo-test/masters/master_graham/manifest.json +26 -8
  193. package/knowledge/solo-test/masters/master_graham/provisional-index.json +3 -3
  194. package/knowledge/solo-test/masters/master_graham/sources.jsonl +1 -1
  195. package/knowledge/solo-test/masters/master_graham/tools.json +67 -28
  196. package/knowledge/solo-test/masters/master_jhunjhunwala/decision_policy.json +77 -12
  197. package/knowledge/solo-test/masters/master_jhunjhunwala/doctrine.jsonl +2 -2
  198. package/knowledge/solo-test/masters/master_jhunjhunwala/manifest.json +25 -10
  199. package/knowledge/solo-test/masters/master_jhunjhunwala/provisional-index.json +3 -3
  200. package/knowledge/solo-test/masters/master_jhunjhunwala/sources.jsonl +1 -1
  201. package/knowledge/solo-test/masters/master_jhunjhunwala/tools.json +54 -28
  202. package/knowledge/solo-test/masters/master_klarman/decision_policy.json +105 -14
  203. package/knowledge/solo-test/masters/master_klarman/doctrine.jsonl +2 -2
  204. package/knowledge/solo-test/masters/master_klarman/manifest.json +22 -6
  205. package/knowledge/solo-test/masters/master_klarman/provisional-index.json +3 -3
  206. package/knowledge/solo-test/masters/master_klarman/sources.jsonl +1 -1
  207. package/knowledge/solo-test/masters/master_klarman/tools.json +55 -29
  208. package/knowledge/solo-test/masters/master_li_lu/decision_policy.json +101 -10
  209. package/knowledge/solo-test/masters/master_li_lu/doctrine.jsonl +2 -2
  210. package/knowledge/solo-test/masters/master_li_lu/manifest.json +27 -10
  211. package/knowledge/solo-test/masters/master_li_lu/provisional-index.json +3 -3
  212. package/knowledge/solo-test/masters/master_li_lu/sources.jsonl +1 -1
  213. package/knowledge/solo-test/masters/master_li_lu/tools.json +58 -32
  214. package/knowledge/solo-test/masters/master_lynch/decision_policy.json +127 -16
  215. package/knowledge/solo-test/masters/master_lynch/doctrine.jsonl +2 -2
  216. package/knowledge/solo-test/masters/master_lynch/manifest.json +28 -10
  217. package/knowledge/solo-test/masters/master_lynch/provisional-index.json +3 -3
  218. package/knowledge/solo-test/masters/master_lynch/sources.jsonl +1 -1
  219. package/knowledge/solo-test/masters/master_lynch/tools.json +67 -28
  220. package/knowledge/solo-test/masters/master_marks/decision_policy.json +71 -9
  221. package/knowledge/solo-test/masters/master_marks/doctrine.jsonl +2 -2
  222. package/knowledge/solo-test/masters/master_marks/manifest.json +25 -10
  223. package/knowledge/solo-test/masters/master_marks/provisional-index.json +3 -3
  224. package/knowledge/solo-test/masters/master_marks/sources.jsonl +1 -1
  225. package/knowledge/solo-test/masters/master_marks/tools.json +54 -28
  226. package/knowledge/solo-test/masters/master_munger/decision_policy.json +141 -16
  227. package/knowledge/solo-test/masters/master_munger/doctrine.jsonl +2 -2
  228. package/knowledge/solo-test/masters/master_munger/manifest.json +24 -10
  229. package/knowledge/solo-test/masters/master_munger/provisional-index.json +3 -3
  230. package/knowledge/solo-test/masters/master_munger/sources.jsonl +1 -1
  231. package/knowledge/solo-test/masters/master_munger/tools.json +52 -26
  232. package/knowledge/solo-test/masters/master_natenberg/decision_policy.json +59 -11
  233. package/knowledge/solo-test/masters/master_natenberg/doctrine.jsonl +2 -2
  234. package/knowledge/solo-test/masters/master_natenberg/manifest.json +23 -10
  235. package/knowledge/solo-test/masters/master_natenberg/provisional-index.json +3 -3
  236. package/knowledge/solo-test/masters/master_natenberg/sources.jsonl +1 -1
  237. package/knowledge/solo-test/masters/master_natenberg/tools.json +50 -24
  238. package/knowledge/solo-test/masters/master_pabrai/decision_policy.json +125 -14
  239. package/knowledge/solo-test/masters/master_pabrai/doctrine.jsonl +2 -2
  240. package/knowledge/solo-test/masters/master_pabrai/manifest.json +22 -6
  241. package/knowledge/solo-test/masters/master_pabrai/provisional-index.json +3 -3
  242. package/knowledge/solo-test/masters/master_pabrai/sources.jsonl +1 -1
  243. package/knowledge/solo-test/masters/master_pabrai/tools.json +55 -29
  244. package/knowledge/solo-test/masters/master_simons/decision_policy.json +59 -11
  245. package/knowledge/solo-test/masters/master_simons/doctrine.jsonl +2 -2
  246. package/knowledge/solo-test/masters/master_simons/manifest.json +22 -8
  247. package/knowledge/solo-test/masters/master_simons/provisional-index.json +3 -3
  248. package/knowledge/solo-test/masters/master_simons/sources.jsonl +1 -1
  249. package/knowledge/solo-test/masters/master_simons/tools.json +43 -30
  250. package/knowledge/solo-test/masters/master_sinclair/decision_policy.json +59 -11
  251. package/knowledge/solo-test/masters/master_sinclair/doctrine.jsonl +2 -2
  252. package/knowledge/solo-test/masters/master_sinclair/manifest.json +22 -10
  253. package/knowledge/solo-test/masters/master_sinclair/provisional-index.json +3 -3
  254. package/knowledge/solo-test/masters/master_sinclair/sources.jsonl +1 -1
  255. package/knowledge/solo-test/masters/master_sinclair/tools.json +54 -28
  256. package/knowledge/solo-test/masters/master_soros/decision_policy.json +98 -10
  257. package/knowledge/solo-test/masters/master_soros/doctrine.jsonl +2 -2
  258. package/knowledge/solo-test/masters/master_soros/manifest.json +24 -11
  259. package/knowledge/solo-test/masters/master_soros/provisional-index.json +3 -3
  260. package/knowledge/solo-test/masters/master_soros/sources.jsonl +1 -1
  261. package/knowledge/solo-test/masters/master_soros/tools.json +54 -28
  262. package/knowledge/solo-test/masters/master_taleb/decision_policy.json +124 -13
  263. package/knowledge/solo-test/masters/master_taleb/doctrine.jsonl +2 -2
  264. package/knowledge/solo-test/masters/master_taleb/manifest.json +23 -9
  265. package/knowledge/solo-test/masters/master_taleb/provisional-index.json +3 -3
  266. package/knowledge/solo-test/masters/master_taleb/sources.jsonl +1 -1
  267. package/knowledge/solo-test/masters/master_taleb/tools.json +50 -24
  268. package/knowledge/solo-test/masters/master_thorp/decision_policy.json +61 -13
  269. package/knowledge/solo-test/masters/master_thorp/doctrine.jsonl +2 -2
  270. package/knowledge/solo-test/masters/master_thorp/manifest.json +25 -10
  271. package/knowledge/solo-test/masters/master_thorp/provisional-index.json +3 -3
  272. package/knowledge/solo-test/masters/master_thorp/sources.jsonl +1 -1
  273. package/knowledge/solo-test/masters/master_thorp/tools.json +56 -30
  274. package/knowledge/solo-test/persona-v3-solo-test-formulas/compilation-manifest.json +290 -264
  275. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_ackman/components/tools.json +58 -32
  276. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_ackman/provisional-derivations/market_capitalisation.derived-proxy-evidence.json +76 -0
  277. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_ackman/provisional-derivations/owner_earnings_yield.derived-proxy-evidence.json +76 -0
  278. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_aschenbrenner/components/tools.json +58 -32
  279. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_aschenbrenner/provisional-derivations/market_capitalisation.derived-proxy-evidence.json +76 -0
  280. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_aschenbrenner/provisional-derivations/price_implied_cash_yield.derived-proxy-evidence.json +76 -0
  281. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_asness/components/tools.json +58 -32
  282. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_asness/provisional-derivations/nominal_earnings_yield_gap.derived-proxy-evidence.json +76 -0
  283. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_asness/provisional-derivations/real_earnings_yield_gap.derived-proxy-evidence.json +76 -0
  284. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_bogle/components/tools.json +120 -0
  285. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_bogle/provisional-derivations/expected_return_over_long_bond.derived-proxy-evidence.json +77 -0
  286. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_bogle/provisional-derivations/fundamental_expected_return.derived-proxy-evidence.json +76 -0
  287. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_buffett/components/tools.json +57 -31
  288. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_buffett/provisional-derivations/market_capitalisation.derived-proxy-evidence.json +76 -0
  289. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_buffett/provisional-derivations/owner_earnings_yield.derived-proxy-evidence.json +76 -0
  290. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_burry/components/tools.json +58 -32
  291. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_burry/provisional-derivations/net_current_asset_surplus_per_share.derived-proxy-evidence.json +77 -0
  292. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_burry/provisional-derivations/net_current_asset_value_per_share.derived-proxy-evidence.json +76 -0
  293. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_cathie_wood/components/tools.json +58 -32
  294. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_cathie_wood/provisional-derivations/current_cash_yield.derived-proxy-evidence.json +76 -0
  295. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_cathie_wood/provisional-derivations/market_capitalisation.derived-proxy-evidence.json +76 -0
  296. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_dalio/components/tools.json +58 -32
  297. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_dalio/provisional-derivations/policy_real_rate.derived-proxy-evidence.json +76 -0
  298. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_dalio/provisional-derivations/real_curve_slope.derived-proxy-evidence.json +76 -0
  299. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_damodaran/components/tools.json +56 -30
  300. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_damodaran/provisional-derivations/implied_equity_risk_premium.derived-proxy-evidence.json +76 -0
  301. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_damodaran/provisional-derivations/{reverse_valuation.derived-proxy-evidence.json → premium_versus_long_run_average.derived-proxy-evidence.json} +29 -16
  302. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_druckenmiller/components/tools.json +57 -31
  303. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_druckenmiller/provisional-derivations/liquidity_curve_impulse.derived-proxy-evidence.json +76 -0
  304. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_druckenmiller/provisional-derivations/policy_real_rate.derived-proxy-evidence.json +76 -0
  305. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_duan_yongping/components/tools.json +84 -32
  306. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_duan_yongping/provisional-derivations/five_year_opportunity_cost.derived-proxy-evidence.json +102 -0
  307. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_duan_yongping/provisional-derivations/margin_retention.derived-proxy-evidence.json +76 -0
  308. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_fisher/components/tools.json +60 -34
  309. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_fisher/provisional-derivations/margin_retention.derived-proxy-evidence.json +76 -0
  310. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_fisher/provisional-derivations/reinvestment_spread.derived-proxy-evidence.json +76 -0
  311. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_forensic_short/components/tools.json +55 -29
  312. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_forensic_short/provisional-derivations/{accounting_rebuilder.derived-proxy-evidence.json → cash_conversion_gap.derived-proxy-evidence.json} +28 -15
  313. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_forensic_short/provisional-derivations/cash_cover_of_leverage.derived-proxy-evidence.json +76 -0
  314. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_graham/components/tools.json +69 -30
  315. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_graham/provisional-derivations/defensive_hurdle_base.derived-proxy-evidence.json +89 -0
  316. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_graham/provisional-derivations/half_normalised_earnings_yield.derived-proxy-evidence.json +76 -0
  317. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_jhunjhunwala/components/tools.json +56 -30
  318. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_jhunjhunwala/provisional-derivations/cash_quality_gap.derived-proxy-evidence.json +76 -0
  319. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_jhunjhunwala/provisional-derivations/real_structural_growth.derived-proxy-evidence.json +76 -0
  320. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_klarman/components/tools.json +57 -31
  321. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_klarman/provisional-derivations/downside_asset_coverage.derived-proxy-evidence.json +76 -0
  322. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_klarman/provisional-derivations/market_capitalisation.derived-proxy-evidence.json +76 -0
  323. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_li_lu/components/tools.json +60 -34
  324. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_li_lu/provisional-derivations/cash_backed_decade_return.derived-proxy-evidence.json +76 -0
  325. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_li_lu/provisional-derivations/decade_return_over_long_bond.derived-proxy-evidence.json +76 -0
  326. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_lynch/components/tools.json +69 -30
  327. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_lynch/provisional-derivations/growth_justified_capitalisation.derived-proxy-evidence.json +89 -0
  328. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_lynch/provisional-derivations/market_capitalisation.derived-proxy-evidence.json +76 -0
  329. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_marks/components/tools.json +56 -30
  330. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_marks/provisional-derivations/credit_cycle_position.derived-proxy-evidence.json +76 -0
  331. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_marks/provisional-derivations/equity_over_corporate_debt.derived-proxy-evidence.json +76 -0
  332. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_munger/components/tools.json +54 -28
  333. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_munger/provisional-derivations/debt_service_cushion.derived-proxy-evidence.json +76 -0
  334. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_munger/provisional-derivations/reported_earnings_cash_gap.derived-proxy-evidence.json +76 -0
  335. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_natenberg/components/tools.json +52 -26
  336. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_natenberg/provisional-derivations/{surface_builder.derived-proxy-evidence.json → normalised_skew.derived-proxy-evidence.json} +27 -14
  337. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_natenberg/provisional-derivations/round_trip_cost.derived-proxy-evidence.json +76 -0
  338. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_pabrai/components/tools.json +57 -31
  339. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_pabrai/provisional-derivations/downside_floor_coverage.derived-proxy-evidence.json +76 -0
  340. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_pabrai/provisional-derivations/market_capitalisation.derived-proxy-evidence.json +76 -0
  341. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_simons/components/tools.json +45 -32
  342. package/knowledge/solo-test/persona-v3-solo-test-formulas/{master_natenberg/provisional-derivations/greeks_payoff.derived-proxy-evidence.json → master_simons/provisional-derivations/absolute_session_move.derived-proxy-evidence.json} +17 -17
  343. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_simons/provisional-derivations/session_move_over_implied.derived-proxy-evidence.json +77 -0
  344. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_sinclair/components/tools.json +56 -30
  345. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_sinclair/provisional-derivations/round_trip_cost.derived-proxy-evidence.json +76 -0
  346. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_sinclair/provisional-derivations/volatility_per_unit_of_width.derived-proxy-evidence.json +76 -0
  347. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_soros/components/tools.json +56 -30
  348. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_soros/provisional-derivations/credit_cycle_position.derived-proxy-evidence.json +76 -0
  349. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_soros/provisional-derivations/financing_burden.derived-proxy-evidence.json +76 -0
  350. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_taleb/components/tools.json +52 -26
  351. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_taleb/provisional-derivations/debt_service_cushion.derived-proxy-evidence.json +76 -0
  352. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_taleb/provisional-derivations/{tail_friction.derived-proxy-evidence.json → normalised_skew.derived-proxy-evidence.json} +27 -14
  353. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_thorp/components/tools.json +58 -32
  354. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_thorp/provisional-derivations/edge_per_unit_of_volatility.derived-proxy-evidence.json +77 -0
  355. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_thorp/provisional-derivations/net_edge.derived-proxy-evidence.json +76 -0
  356. package/mcp/lib/breadth.mjs +122 -0
  357. package/mcp/lib/codex.mjs +3 -3
  358. package/mcp/lib/constants.mjs +76 -25
  359. package/mcp/lib/council-options.mjs +95 -40
  360. package/mcp/lib/council-selection.mjs +95 -9
  361. package/mcp/lib/damodaran.mjs +119 -0
  362. package/mcp/lib/fred.mjs +246 -0
  363. package/mcp/lib/fund-holdings-parsers.mjs +297 -0
  364. package/mcp/lib/fundamentals.mjs +801 -0
  365. package/mcp/lib/funds.mjs +750 -0
  366. package/mcp/lib/gates.mjs +149 -15
  367. package/mcp/lib/grounding.mjs +188 -36
  368. package/mcp/lib/index-aggregate.mjs +718 -0
  369. package/mcp/lib/insider-ownership.mjs +149 -0
  370. package/mcp/lib/instrument-facts.mjs +557 -0
  371. package/mcp/lib/instruments.mjs +191 -0
  372. package/mcp/lib/lang.mjs +109 -2
  373. package/mcp/lib/markdown.mjs +620 -138
  374. package/mcp/lib/master-catalog.mjs +32 -3
  375. package/mcp/lib/orchestrator.mjs +1121 -290
  376. package/mcp/lib/packets.mjs +227 -21
  377. package/mcp/lib/personas/engine.mjs +44 -31
  378. package/mcp/lib/personas-v3/compiler.mjs +2 -0
  379. package/mcp/lib/personas-v3/deterministic-executor.mjs +13 -2
  380. package/mcp/lib/personas-v3/ga-external-evidence.mjs +3 -2
  381. package/mcp/lib/personas-v3/ga-gate.mjs +2 -1
  382. package/mcp/lib/personas-v3/ga-package-evidence.mjs +5 -4
  383. package/mcp/lib/personas-v3/grounding-adapter.mjs +309 -2
  384. package/mcp/lib/personas-v3/loader.mjs +10 -7
  385. package/mcp/lib/personas-v3/production-root.mjs +5 -4
  386. package/mcp/lib/personas-v3/release-formula-evidence.mjs +14 -7
  387. package/mcp/lib/personas-v3/release-source-evidence.mjs +2 -1
  388. package/mcp/lib/personas-v3/releases.mjs +5 -4
  389. package/mcp/lib/personas-v3/runtime-release-integrity.mjs +6 -5
  390. package/mcp/lib/personas-v3/semantic-source-adjudication.mjs +2 -1
  391. package/mcp/lib/personas-v3/semantic-source-extraction.mjs +2 -1
  392. package/mcp/lib/personas-v3/semantic-source-skeptic-review.mjs +2 -1
  393. package/mcp/lib/personas-v3/source-acquisition.mjs +10 -3
  394. package/mcp/lib/personas-v3/source-review-operations.mjs +3 -2
  395. package/mcp/lib/personas-v3/staging.mjs +22 -3
  396. package/mcp/lib/prompts.mjs +119 -2
  397. package/mcp/lib/quotes.mjs +5 -0
  398. package/mcp/lib/rpc.mjs +101 -42
  399. package/mcp/lib/run-store.mjs +28 -3
  400. package/mcp/lib/sec.mjs +32 -0
  401. package/mcp/lib/tables.mjs +11 -0
  402. package/mcp/lib/voice.mjs +122 -0
  403. package/mcp/lib/xbrl-aggregates.mjs +153 -0
  404. package/mcp/lib/xbrl-series.mjs +339 -0
  405. package/mcp/lib/xlsx.mjs +155 -0
  406. package/package.json +5 -5
  407. package/personas/masters/masters-value/bogle.md +145 -0
  408. package/schemas/council-evaluation-protocol-v1.schema.json +2 -2
  409. package/schemas/persona-v3-ai-assisted-solo-profile-v1.schema.json +2 -2
  410. package/schemas/persona-v3-ai-machine-simulation-v1.schema.json +1 -1
  411. package/schemas/persona-v3-build-spec-v1.schema.json +3 -3
  412. package/schemas/persona-v3-release-formula-review-evidence-v1.schema.json +3 -3
  413. package/schemas/persona-v3-release-manifest-v1.schema.json +6 -6
  414. package/schemas/persona-v3-release-source-review-evidence-v1.schema.json +3 -3
  415. package/schemas/persona-v3-tool-graph-v1.schema.json +1 -1
  416. package/schemas/persona-v3.schema.json +37 -2
  417. package/schemas/source-review-batch-v1.schema.json +9 -9
  418. package/scripts/assemble-persona-v3-release.mjs +3 -2
  419. package/scripts/build-persona-v3-solo-test-packs.mjs +6 -8
  420. package/scripts/check-persona-v3-ga.mjs +1 -1
  421. package/scripts/check-persona-v3-production-candidates.mjs +1 -1
  422. package/scripts/compile-persona-v3-formulas.mjs +19 -18
  423. package/scripts/doctor.mjs +11 -9
  424. package/scripts/export-persona-source-review-batch.mjs +1 -1
  425. package/scripts/generate-persona-v3-drafts.mjs +2 -2
  426. package/scripts/lib/build-profile.mjs +24 -0
  427. package/scripts/lib/council-evaluation-protocol.mjs +7 -6
  428. package/scripts/lib/host-capabilities.mjs +2 -1
  429. package/scripts/lib/packaged-host-parity.mjs +63 -8
  430. package/scripts/lib/persona-v3-ai-assisted-solo-status.mjs +6 -4
  431. package/scripts/lib/persona-v3-ai-formula-review.mjs +6 -5
  432. package/scripts/lib/persona-v3-ai-machine-simulations.mjs +32 -4
  433. package/scripts/lib/persona-v3-build-specs.mjs +3 -2
  434. package/scripts/lib/persona-v3-draft-packs.mjs +2 -2
  435. package/scripts/lib/persona-v3-formula-pipeline.mjs +11 -10
  436. package/scripts/lib/persona-v3-production-candidates.mjs +10 -9
  437. package/scripts/lib/persona-v3-solo-formula-pipeline.mjs +182 -16
  438. package/scripts/lib/persona-v3-solo-test-packs.mjs +143 -16
  439. package/scripts/package-smoke.mjs +32 -12
  440. package/scripts/report-persona-corpus-gaps.mjs +4 -1
  441. package/scripts/review-persona-v3-solo-formulas.mjs +12 -8
  442. package/scripts/run-tests.mjs +23 -2
  443. package/scripts/scaffold-persona-v3-staging.mjs +1 -1
  444. package/skills/agent-skills-governance/SKILL.md +8 -2
  445. package/skills/alphacouncil-agent/SKILL.md +60 -9
  446. package/skills/public-equity-investing/SKILL.md +17 -0
  447. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_ackman/provisional-derivations/change_value_bridge.derived-proxy-evidence.json +0 -64
  448. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_ackman/provisional-derivations/power_map.derived-proxy-evidence.json +0 -64
  449. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_aschenbrenner/provisional-derivations/compute_power_bridge.derived-proxy-evidence.json +0 -64
  450. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_aschenbrenner/provisional-derivations/timeline_reverse_valuation.derived-proxy-evidence.json +0 -64
  451. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_asness/provisional-derivations/crowding_cost_stress.derived-proxy-evidence.json +0 -64
  452. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_asness/provisional-derivations/factor_decomposer.derived-proxy-evidence.json +0 -64
  453. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_buffett/provisional-derivations/incremental_returns.derived-proxy-evidence.json +0 -64
  454. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_buffett/provisional-derivations/owner_earnings_rebuilder.derived-proxy-evidence.json +0 -64
  455. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_burry/provisional-derivations/capital_structure_reader.derived-proxy-evidence.json +0 -64
  456. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_burry/provisional-derivations/mispricing_carry.derived-proxy-evidence.json +0 -64
  457. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_cathie_wood/provisional-derivations/capture_valuation_bridge.derived-proxy-evidence.json +0 -64
  458. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_cathie_wood/provisional-derivations/cost_adoption_curve.derived-proxy-evidence.json +0 -64
  459. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_dalio/provisional-derivations/debt_driver_stress.derived-proxy-evidence.json +0 -64
  460. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_dalio/provisional-derivations/regime_classifier.derived-proxy-evidence.json +0 -64
  461. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_damodaran/provisional-derivations/story_dcf.derived-proxy-evidence.json +0 -64
  462. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_druckenmiller/provisional-derivations/inflection_payoff.derived-proxy-evidence.json +0 -64
  463. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_druckenmiller/provisional-derivations/liquidity_revision_map.derived-proxy-evidence.json +0 -64
  464. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_duan_yongping/provisional-derivations/opportunity_cost.derived-proxy-evidence.json +0 -64
  465. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_duan_yongping/provisional-derivations/user_value_evidence.derived-proxy-evidence.json +0 -64
  466. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_fisher/provisional-derivations/research_productivity.derived-proxy-evidence.json +0 -64
  467. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_fisher/provisional-derivations/scuttlebutt_graph.derived-proxy-evidence.json +0 -64
  468. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_forensic_short/provisional-derivations/borrow_catalyst.derived-proxy-evidence.json +0 -64
  469. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_graham/provisional-derivations/asset_floor.derived-proxy-evidence.json +0 -64
  470. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_graham/provisional-derivations/normalized_earnings.derived-proxy-evidence.json +0 -64
  471. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_jhunjhunwala/provisional-derivations/penetration_liquidity.derived-proxy-evidence.json +0 -64
  472. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_jhunjhunwala/provisional-derivations/promoter_governance.derived-proxy-evidence.json +0 -64
  473. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_klarman/provisional-derivations/cash_catalyst_compare.derived-proxy-evidence.json +0 -64
  474. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_klarman/provisional-derivations/recovery_waterfall.derived-proxy-evidence.json +0 -64
  475. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_li_lu/provisional-derivations/promise_integrity_ledger.derived-proxy-evidence.json +0 -64
  476. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_li_lu/provisional-derivations/ten_year_return_bridge.derived-proxy-evidence.json +0 -64
  477. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_lynch/provisional-derivations/category_classifier.derived-proxy-evidence.json +0 -64
  478. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_lynch/provisional-derivations/story_numbers_check.derived-proxy-evidence.json +0 -64
  479. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_marks/provisional-derivations/cycle_temperature.derived-proxy-evidence.json +0 -64
  480. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_marks/provisional-derivations/implied_expectations.derived-proxy-evidence.json +0 -64
  481. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_munger/provisional-derivations/failure_path_graph.derived-proxy-evidence.json +0 -64
  482. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_munger/provisional-derivations/incentive_map.derived-proxy-evidence.json +0 -64
  483. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_pabrai/provisional-derivations/discrete_payoff.derived-proxy-evidence.json +0 -64
  484. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_pabrai/provisional-derivations/downside_floor.derived-proxy-evidence.json +0 -64
  485. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_simons/provisional-derivations/leakage_audit.derived-proxy-evidence.json +0 -64
  486. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_simons/provisional-derivations/oos_cost_test.derived-proxy-evidence.json +0 -64
  487. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_sinclair/provisional-derivations/net_edge_sizer.derived-proxy-evidence.json +0 -64
  488. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_sinclair/provisional-derivations/realized_vol_forecast.derived-proxy-evidence.json +0 -64
  489. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_soros/provisional-derivations/reflexivity_graph.derived-proxy-evidence.json +0 -64
  490. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_soros/provisional-derivations/reversal_monitor.derived-proxy-evidence.json +0 -64
  491. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_taleb/provisional-derivations/payoff_ruin.derived-proxy-evidence.json +0 -64
  492. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_thorp/provisional-derivations/edge_recalculator.derived-proxy-evidence.json +0 -64
  493. package/knowledge/solo-test/persona-v3-solo-test-formulas/master_thorp/provisional-derivations/fractional_kelly.derived-proxy-evidence.json +0 -64
@@ -0,0 +1,750 @@
1
+ /**
2
+ * ETF holdings, and look-through aggregation of the operating companies inside them.
3
+ *
4
+ * `instruments.mjs` already refuses to send an ETF through an operating-company screen: QQQ
5
+ * has no revenue, no EPS and no Form 4. That correctly stops the wrong answer, but it leaves
6
+ * a fund with nothing to say. This module supplies the right answer instead of the missing
7
+ * one -- the fund's own facts, and the weighted fundamentals of what it actually owns.
8
+ *
9
+ * The justification for the second half is Buffett's 1990 letter: Berkshire's reported
10
+ * earnings omitted the undistributed earnings of the companies it part-owned, so he reported
11
+ * "look-through earnings" alongside them. An index fund is the same problem at 500x. The
12
+ * portfolio-level number is a WEIGHTED AGGREGATE of constituent ratios, and it is only worth
13
+ * anything if the coverage is stated with it.
14
+ *
15
+ * Two rules are enforced in code here rather than asked for in a prompt, because both have
16
+ * already produced confidently wrong output elsewhere in this pipeline:
17
+ *
18
+ * 1. Absolute currency amounts can NOT be aggregated. "ETF revenue" and "ETF EPS" are
19
+ * category errors -- a fund does not earn its holdings' earnings, it owns a slice of
20
+ * them. `lookThroughAggregate` accepts a closed whitelist of ratio metrics, so summing
21
+ * revenue is not a discouraged call, it is an unrepresentable one.
22
+ * 2. Below 50% coverage there is no aggregate, only a sample. The function returns null and
23
+ * a diagnostic rather than a number that reads as if it described the fund.
24
+ *
25
+ * Every issuer adapter validates the CONTENT it received rather than the status code, because
26
+ * three of the four endpoints here return HTTP 200 while lying (see the trap notes below).
27
+ */
28
+
29
+ import { Buffer } from "node:buffer";
30
+
31
+ import { LIMITS } from "./constants.mjs";
32
+ import { columnIndex, unzip, xlsxRows, xmlText } from "./xlsx.mjs";
33
+ import { numeric } from "./fund-holdings-parsers.mjs";
34
+ import { linkedAbort } from "./abort.mjs";
35
+ import { fetchText } from "./quotes.mjs";
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Registry
39
+ // ---------------------------------------------------------------------------
40
+
41
+ /**
42
+ * Ticker -> issuer + the identifier that issuer's holdings endpoint actually keys on.
43
+ *
44
+ * Each issuer keys on something different and the wrong key fails in a different way, so the
45
+ * identifier is stored per issuer rather than guessed from the ticker:
46
+ * - iShares keys on a numeric productId. The slug in the URL path is decorative.
47
+ * - SSGA keys on a lowercase ticker slug.
48
+ * - Invesco keys on CUSIP. `idType=ticker` works for QQQ alone; QQQM returns HTTP 500.
49
+ * - Vanguard keys on the ticker.
50
+ *
51
+ * Every CUSIP and productId below was read back from the issuer's own response, not inferred.
52
+ * An unknown ticker is an explicit unsupported result -- never a constructed URL.
53
+ */
54
+ export const FUND_REGISTRY = Object.freeze({
55
+ IVV: { issuer: "ishares", name: "iShares Core S&P 500 ETF", product_id: "239726", cusip: "464287200" },
56
+ IWM: { issuer: "ishares", name: "iShares Russell 2000 ETF", product_id: "239710", cusip: "464287655" },
57
+ EFA: { issuer: "ishares", name: "iShares MSCI EAFE ETF", product_id: "239623", cusip: "464287465" },
58
+ AGG: { issuer: "ishares", name: "iShares Core U.S. Aggregate Bond ETF", product_id: "239458", cusip: "464287226" },
59
+ TLT: { issuer: "ishares", name: "iShares 20+ Year Treasury Bond ETF", product_id: "239454", cusip: "464287432" },
60
+ HYG: { issuer: "ishares", name: "iShares iBoxx $ High Yield Corporate Bond ETF", product_id: "239565", cusip: "464288513" },
61
+ SPY: { issuer: "ssga", name: "SPDR S&P 500 ETF Trust", slug: "spy", cusip: "78462F103" },
62
+ QQQ: { issuer: "invesco", name: "Invesco QQQ Trust Series 1", cusip: "46090E103" },
63
+ QQQM: { issuer: "invesco", name: "Invesco NASDAQ 100 ETF", cusip: "46138G649" },
64
+ VOO: { issuer: "vanguard", name: "Vanguard S&P 500 ETF", ticker: "VOO", cusip: "922908363" },
65
+ VTI: { issuer: "vanguard", name: "Vanguard Total Stock Market ETF", ticker: "VTI" },
66
+ });
67
+
68
+ /**
69
+ * Issuers that publish shares outstanding daily, which is the only honest input to a flow.
70
+ *
71
+ * Vanguard and Invesco publish neither shares outstanding nor AUM on any keyless endpoint
72
+ * (Invesco's /nav and /profile both answer HTTP 501). A flow for those two is a gap. It is
73
+ * NOT a modelling problem to be solved with an estimate -- see `fundFlow`.
74
+ */
75
+ // Imported, not merely re-exported: this module calls all six. A bare `export ... from` makes
76
+ // the names part of the public surface without binding them in this scope, so every fetch path
77
+ // threw `not defined` at runtime while the parser unit tests -- which import them directly --
78
+ // stayed green.
79
+ import {
80
+ checkWeightSum,
81
+ isoDate,
82
+ parseIsharesHoldingsCsv,
83
+ parseInvescoHoldings,
84
+ parseSsgaHoldingsXlsx,
85
+ parseVanguardHoldings,
86
+ } from "./fund-holdings-parsers.mjs";
87
+
88
+ export {
89
+ checkWeightSum,
90
+ isoDate,
91
+ parseIsharesHoldingsCsv,
92
+ parseInvescoHoldings,
93
+ parseSsgaHoldingsXlsx,
94
+ parseVanguardHoldings,
95
+ };
96
+
97
+ export const DAILY_SHARES_ISSUERS = Object.freeze(["ishares", "ssga"]);
98
+
99
+ /** Weights are published rounded, so the sum is never exactly 1. IVV lands at 1.0005. */
100
+
101
+ /** Below this, an aggregate describes a sample rather than the fund. Hard floor, not advice. */
102
+ export const MIN_LOOK_THROUGH_COVERAGE = 0.5;
103
+
104
+ /** Unknown ticker -> an explicit unsupported result. Never a guessed identifier. */
105
+ export function resolveFund(symbol) {
106
+ const key = String(symbol || "").trim().toUpperCase();
107
+ if (!key) return { supported: false, symbol: key, reason: "empty symbol" };
108
+ if (!Object.prototype.hasOwnProperty.call(FUND_REGISTRY, key)) {
109
+ return {
110
+ supported: false,
111
+ symbol: key,
112
+ reason: `${key} is not in the fund registry; holdings would have to be guessed`,
113
+ supported_symbols: Object.keys(FUND_REGISTRY),
114
+ };
115
+ }
116
+ return { supported: true, symbol: key, ...FUND_REGISTRY[key] };
117
+ }
118
+
119
+ // ---------------------------------------------------------------------------
120
+ // URL builders -- the query string is part of the correctness contract
121
+ // ---------------------------------------------------------------------------
122
+
123
+ /**
124
+ * The NEW iShares holdings path. Only the numeric productId matters; the slug segment is
125
+ * arbitrary and is written as `x` so nobody maintains a table of marketing slugs.
126
+ *
127
+ * The old `.../<id>.ajax?fileType=csv` path is deliberately not built anywhere in this file.
128
+ * It still answers HTTP 200 with `Content-Type: text/csv` and a 2.2MB HTML product page as
129
+ * the body, for every User-Agent tried. See `parseIsharesHoldingsCsv`.
130
+ */
131
+ export function isharesHoldingsUrl(productId) {
132
+ return `https://www.ishares.com/us/products/${encodeURIComponent(productId)}/x/latest-holdings.csv`;
133
+ }
134
+
135
+ export function ssgaHoldingsUrl(slug) {
136
+ return `https://www.ssga.com/us/en/intermediary/library-content/products/fund-data/etfs/us/holdings-daily-us-en-${encodeURIComponent(slug)}.xlsx`;
137
+ }
138
+
139
+ /**
140
+ * Invesco's cache API. Both query parameters are load-bearing and both fail SILENTLY:
141
+ * - without `interval=daily` the response can serve a stale cached effectiveDate;
142
+ * - with `loadType=initial` it returns the first 10 holdings while `totalNumberOfHoldings`
143
+ * still reports the true count, so the truncation is invisible unless you compare them.
144
+ * `assertInvescoQuery` re-checks both so a later edit to this string cannot quietly regress.
145
+ *
146
+ * Host choice is also deliberate. `www.invesco.com` answers HTTP 406 with a zero-byte body to
147
+ * any Mozilla-prefixed User-Agent -- which is exactly what `fetchText` sends -- while
148
+ * `dng-api.invesco.com` accepts every User-Agent tested, including a full Chrome one.
149
+ *
150
+ * `dng-api` does rate-limit by IP, and it signals that with the SAME zero-byte 406. Measured:
151
+ * roughly eight requests in quick succession locked it out for about two minutes, for every
152
+ * User-Agent at once, then it recovered on its own. That collision matters because a 406 here
153
+ * reads like a UA problem and is not one -- see `invescoFetchHint`. Changing the UA in
154
+ * response to it accomplishes nothing except hiding the need to back off.
155
+ */
156
+ export function invescoHoldingsUrl(cusip) {
157
+ return "https://dng-api.invesco.com/cache/v1/accounts/en_US/shareclasses/"
158
+ + `${encodeURIComponent(cusip)}/holdings/fund?idType=cusip&interval=daily&productType=ETF`;
159
+ }
160
+
161
+ /**
162
+ * Turn Invesco's ambiguous 406 into the diagnosis that is actually true, so the next reader
163
+ * backs off instead of cycling User-Agents.
164
+ */
165
+ export function invescoFetchHint(error) {
166
+ const message = String(error?.message || error);
167
+ if (!/HTTP 406/u.test(message)) return message;
168
+ return `${message} -- dng-api.invesco.com returns a zero-byte 406 when rate limiting by IP, not because of the User-Agent (it accepts every UA tested). Back off ~2 minutes and retry.`;
169
+ }
170
+
171
+ export function assertInvescoQuery(url) {
172
+ const text = String(url || "");
173
+ if (!/[?&]interval=daily(&|$)/u.test(text)) {
174
+ throw new Error("invesco holdings URL must carry interval=daily or it may serve a stale cached date");
175
+ }
176
+ if (/[?&]loadType=/u.test(text)) {
177
+ throw new Error("invesco holdings URL must not carry loadType; it silently truncates to 10 holdings");
178
+ }
179
+ if (!/[?&]idType=cusip(&|$)/u.test(text)) {
180
+ throw new Error("invesco holdings URL must key on CUSIP; idType=ticker resolves QQQ only");
181
+ }
182
+ return text;
183
+ }
184
+
185
+ const VANGUARD_PAGE_SIZE = 500;
186
+ const VANGUARD_MAX_PAGES = 40; // ~20k holdings; a runaway pager is a bug, not a big fund
187
+
188
+ /**
189
+ * Vanguard month-end holdings, paged 500 at a time.
190
+ *
191
+ * The payload's own `next` link points at `api.vanguard.com`, which is dead, so following it
192
+ * yields nothing and looks like a short fund. Pagination is done here against the live host.
193
+ */
194
+ export function vanguardHoldingsUrl(ticker, start = 1, count = VANGUARD_PAGE_SIZE) {
195
+ const base = `https://investor.vanguard.com/investment-products/etfs/profile/api/${encodeURIComponent(ticker)}/portfolio-holding/stock`;
196
+ return start > 1 ? `${base}?start=${start}&count=${count}` : base;
197
+ }
198
+
199
+ // ---------------------------------------------------------------------------
200
+ // Small shared helpers
201
+ // ---------------------------------------------------------------------------
202
+
203
+
204
+ // ---------------------------------------------------------------------------
205
+ // iShares
206
+ // ---------------------------------------------------------------------------
207
+
208
+ // ---------------------------------------------------------------------------
209
+ // Invesco
210
+ // ---------------------------------------------------------------------------
211
+
212
+ // ---------------------------------------------------------------------------
213
+ // Vanguard
214
+ // ---------------------------------------------------------------------------
215
+
216
+ // ---------------------------------------------------------------------------
217
+ // Fetch
218
+ // ---------------------------------------------------------------------------
219
+
220
+ /**
221
+ * `fetchText` decodes the body as UTF-8, which is irreversibly lossy for a ZIP container --
222
+ * measured on SPY's workbook, 54,450 bytes of xlsx come back as 96,860 bytes of replacement
223
+ * characters. The xlsx issuer therefore needs bytes, not text. Same abort discipline.
224
+ */
225
+ async function fetchBinary(url, timeoutMs = LIMITS.QUOTE_FETCH_MS * 2, upstreamSignal) {
226
+ const abort = linkedAbort(timeoutMs, upstreamSignal);
227
+ try {
228
+ const res = await fetch(url, {
229
+ signal: abort.signal,
230
+ redirect: "follow", // SSGA answers 301 before serving the workbook
231
+ headers: { "User-Agent": "Mozilla/5.0 (AlphaCouncil)" },
232
+ });
233
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
234
+ return Buffer.from(await res.arrayBuffer());
235
+ } finally {
236
+ abort.cleanup();
237
+ }
238
+ }
239
+
240
+ async function fetchVanguardPages(ticker, signal) {
241
+ const pages = [];
242
+ const urls = [];
243
+ let start = 1;
244
+ for (let i = 0; i < VANGUARD_MAX_PAGES; i += 1) {
245
+ const url = vanguardHoldingsUrl(ticker, start);
246
+ urls.push(url);
247
+ const page = JSON.parse(await fetchText(url, LIMITS.QUOTE_FETCH_MS * 2, signal));
248
+ pages.push(page);
249
+ const collected = pages.reduce((total, one) => total + (one?.fund?.entity?.length || 0), 0);
250
+ const declared = Number(page?.size);
251
+ if (!Number.isFinite(declared) || collected >= declared) break;
252
+ start += VANGUARD_PAGE_SIZE;
253
+ }
254
+ return { pages, urls };
255
+ }
256
+
257
+ /**
258
+ * Fund holdings from the issuer, with the issuer's own stated date.
259
+ *
260
+ * `as_of` and `public_at` are the issuer's date, never the run time: a fund file is a dated
261
+ * publication, and stamping it with "now" is exactly how a month-end Vanguard file starts
262
+ * passing for today's portfolio.
263
+ */
264
+ export async function fetchFundHoldings(symbol, { signal } = {}) {
265
+ const fund = resolveFund(symbol);
266
+ if (!fund.supported) {
267
+ return Object.freeze({
268
+ symbol: fund.symbol,
269
+ issuer: null,
270
+ as_of: null,
271
+ holdings: [],
272
+ source_url: null,
273
+ public_at: null,
274
+ unavailable: [`holdings unsupported: ${fund.reason}`],
275
+ });
276
+ }
277
+
278
+ const unavailable = [];
279
+ let parsed = null;
280
+ let sourceUrl = null;
281
+ try {
282
+ if (fund.issuer === "ishares") {
283
+ sourceUrl = isharesHoldingsUrl(fund.product_id);
284
+ parsed = parseIsharesHoldingsCsv(await fetchText(sourceUrl, LIMITS.QUOTE_FETCH_MS * 2, signal), { symbol: fund.symbol });
285
+ } else if (fund.issuer === "invesco") {
286
+ sourceUrl = assertInvescoQuery(invescoHoldingsUrl(fund.cusip));
287
+ parsed = parseInvescoHoldings(await fetchText(sourceUrl, LIMITS.QUOTE_FETCH_MS * 2, signal), { symbol: fund.symbol });
288
+ } else if (fund.issuer === "ssga") {
289
+ sourceUrl = ssgaHoldingsUrl(fund.slug);
290
+ parsed = parseSsgaHoldingsXlsx(await fetchBinary(sourceUrl, LIMITS.QUOTE_FETCH_MS * 2, signal), { symbol: fund.symbol });
291
+ } else if (fund.issuer === "vanguard") {
292
+ const walk = await fetchVanguardPages(fund.ticker, signal);
293
+ sourceUrl = walk.urls;
294
+ parsed = parseVanguardHoldings(walk.pages, { symbol: fund.symbol });
295
+ } else {
296
+ throw new Error(`no holdings adapter for issuer ${fund.issuer}`);
297
+ }
298
+ } catch (error) {
299
+ const detail = fund.issuer === "invesco" ? invescoFetchHint(error) : String(error?.message || error);
300
+ return Object.freeze({
301
+ symbol: fund.symbol,
302
+ issuer: fund.issuer,
303
+ as_of: null,
304
+ holdings: [],
305
+ source_url: sourceUrl,
306
+ public_at: null,
307
+ unavailable: [`${fund.issuer} holdings: ${detail}`],
308
+ });
309
+ }
310
+
311
+ const sumCheck = checkWeightSum(parsed.holdings);
312
+ if (!sumCheck.ok) unavailable.push(`${fund.issuer} ${fund.symbol}: ${sumCheck.gap}`);
313
+ if (!parsed.as_of) unavailable.push(`${fund.issuer} ${fund.symbol}: issuer stated no holdings date`);
314
+ if (parsed.shares_outstanding === null && DAILY_SHARES_ISSUERS.includes(fund.issuer)) {
315
+ unavailable.push(`${fund.issuer} ${fund.symbol}: shares outstanding absent from the holdings file, so no flow can be computed`);
316
+ }
317
+ if (!DAILY_SHARES_ISSUERS.includes(fund.issuer)) {
318
+ unavailable.push(`${fund.issuer} publishes no keyless shares-outstanding endpoint; fund flow is a gap for ${fund.symbol}`);
319
+ }
320
+ if (parsed.cadence === "month_end") {
321
+ unavailable.push(`${fund.issuer} ${fund.symbol}: holdings are month-end only, not daily (stated date ${parsed.as_of})`);
322
+ }
323
+
324
+ return Object.freeze({
325
+ symbol: fund.symbol,
326
+ issuer: fund.issuer,
327
+ fund_name: parsed.fund_name || fund.name,
328
+ as_of: parsed.as_of,
329
+ cadence: parsed.cadence || "daily",
330
+ holdings: Object.freeze(parsed.holdings.map((row) => Object.freeze(row))),
331
+ holdings_count: parsed.holdings.length,
332
+ weight_sum: sumCheck.sum,
333
+ shares_outstanding: parsed.shares_outstanding,
334
+ source_url: sourceUrl,
335
+ // The issuer's stated date IS the publication anchor, exactly as with a FRED observation.
336
+ public_at: parsed.as_of ? `${parsed.as_of}T00:00:00.000Z` : null,
337
+ observation_date: parsed.as_of,
338
+ unavailable: Object.freeze(unavailable),
339
+ });
340
+ }
341
+
342
+ // ---------------------------------------------------------------------------
343
+ // Flow
344
+ // ---------------------------------------------------------------------------
345
+
346
+ /**
347
+ * Creation/redemption flow: the change in shares outstanding priced at NAV.
348
+ *
349
+ * Only iShares and SSGA publish shares outstanding daily. For Vanguard and Invesco this must
350
+ * return null. There is a tempting substitute -- infer flow from AUM change -- and it is
351
+ * wrong, because AUM moves with the market as well as with creations, so it reports a flow on
352
+ * a day with none. The gap is named instead.
353
+ */
354
+ export function fundFlow({ sharesNow, sharesPrior, nav, issuer = null } = {}) {
355
+ const construction = {
356
+ formula: "(shares_outstanding_now - shares_outstanding_prior) * nav",
357
+ unit: "usd",
358
+ inputs: { shares_now: sharesNow ?? null, shares_prior: sharesPrior ?? null, nav: nav ?? null },
359
+ issuer,
360
+ };
361
+
362
+ if (issuer && !DAILY_SHARES_ISSUERS.includes(issuer)) {
363
+ return {
364
+ value: null,
365
+ construction,
366
+ unavailable: [`${issuer} publishes no daily shares outstanding; fund flow is unavailable and must not be estimated from AUM change`],
367
+ };
368
+ }
369
+
370
+ const now = numeric(sharesNow);
371
+ const prior = numeric(sharesPrior);
372
+ const price = numeric(nav);
373
+ const missing = [];
374
+ if (now === null) missing.push("shares_outstanding_now");
375
+ if (prior === null) missing.push("shares_outstanding_prior");
376
+ if (price === null) missing.push("nav");
377
+ if (missing.length) {
378
+ return { value: null, construction, unavailable: [`fund flow needs ${missing.join(", ")}; no estimate is substituted`] };
379
+ }
380
+
381
+ return {
382
+ value: Number(((now - prior) * price).toFixed(2)),
383
+ construction: { ...construction, share_change: now - prior },
384
+ unavailable: [],
385
+ };
386
+ }
387
+
388
+ // ---------------------------------------------------------------------------
389
+ // Coverage
390
+ // ---------------------------------------------------------------------------
391
+
392
+ /**
393
+ * The shortest run of largest holdings that reaches `minWeight` of the fund.
394
+ *
395
+ * Returns what it actually reached, not what was asked for. A fund whose whole book is 0.4 of
396
+ * itself -- a truncated file, or a top-ten sample -- returns `reached: false` and 0.4, so the
397
+ * caller can decline rather than describe 40% of a portfolio as the portfolio.
398
+ */
399
+ export function topHoldingsCoverage(holdings, { minWeight = 0.6 } = {}) {
400
+ const ranked = [...(holdings || [])]
401
+ .filter((row) => Number.isFinite(Number(row?.weight)))
402
+ .sort((a, b) => Number(b.weight) - Number(a.weight));
403
+
404
+ const prefix = [];
405
+ let coverage = 0;
406
+ for (const row of ranked) {
407
+ prefix.push(row);
408
+ coverage += Number(row.weight);
409
+ if (coverage >= minWeight) break;
410
+ }
411
+
412
+ const reached = coverage >= minWeight;
413
+ return {
414
+ holdings: prefix,
415
+ count: prefix.length,
416
+ coverage_weight: Number(coverage.toFixed(6)),
417
+ min_weight: minWeight,
418
+ reached,
419
+ unavailable: reached
420
+ ? []
421
+ : [`top holdings reach only ${coverage.toFixed(4)} of the fund, short of the requested ${minWeight}`],
422
+ };
423
+ }
424
+
425
+ // ---------------------------------------------------------------------------
426
+ // Look-through aggregation
427
+ // ---------------------------------------------------------------------------
428
+
429
+ /**
430
+ * The closed set of metrics that may be look-through aggregated.
431
+ *
432
+ * Membership is the whole enforcement mechanism for "no absolute currency amounts": revenue,
433
+ * EPS, net income and market cap are not listed, so `lookThroughAggregate` cannot express
434
+ * them. There is no flag to turn that off.
435
+ *
436
+ * `aggregation` is not cosmetic. A price-per-unit-of-fundamental ratio (P/E, P/B, P/S) must be
437
+ * aggregated HARMONICALLY, because the portfolio ratio is total price over total earnings:
438
+ *
439
+ * portfolio P/E = sum(w_i * P_i) / sum(w_i * E_i) = 1 / sum(w_i / PE_i)
440
+ *
441
+ * which is the weighted harmonic mean of the constituent P/Es, equivalently the reciprocal of
442
+ * the weighted arithmetic mean of their earnings yields. Taking the arithmetic mean of P/Es
443
+ * instead is the classic error that produces an index P/E in the hundreds: a near-zero-
444
+ * earnings constituent contributes an enormous P/E to an arithmetic average, when in the real
445
+ * aggregate it contributes almost no earnings and a normal amount of price.
446
+ *
447
+ * Metrics that are ALREADY a rate -- a yield, a margin, a growth rate -- aggregate
448
+ * arithmetically, because their denominators are the weights themselves.
449
+ */
450
+ export const LOOK_THROUGH_METRICS = Object.freeze({
451
+ "valuation.pe_ratio": { kind: "price_multiple", aggregation: "weighted_harmonic_mean", reciprocal_of: "valuation.earnings_yield" },
452
+ "valuation.pb_ratio": { kind: "price_multiple", aggregation: "weighted_harmonic_mean", reciprocal_of: "valuation.book_yield" },
453
+ "valuation.ps_ratio": { kind: "price_multiple", aggregation: "weighted_harmonic_mean", reciprocal_of: "valuation.sales_yield" },
454
+ "valuation.ev_ebitda": { kind: "price_multiple", aggregation: "weighted_harmonic_mean", reciprocal_of: null },
455
+ "valuation.earnings_yield": { kind: "rate", aggregation: "weighted_arithmetic_mean", reciprocal_of: null },
456
+ "valuation.dividend_yield": { kind: "rate", aggregation: "weighted_arithmetic_mean", reciprocal_of: null },
457
+ "valuation.free_cash_flow_yield": { kind: "rate", aggregation: "weighted_arithmetic_mean", reciprocal_of: null },
458
+ "profitability.net_margin": { kind: "rate", aggregation: "weighted_arithmetic_mean", reciprocal_of: null },
459
+ "profitability.gross_margin": { kind: "rate", aggregation: "weighted_arithmetic_mean", reciprocal_of: null },
460
+ "profitability.operating_margin": { kind: "rate", aggregation: "weighted_arithmetic_mean", reciprocal_of: null },
461
+ "profitability.return_on_equity": { kind: "rate", aggregation: "weighted_arithmetic_mean", reciprocal_of: null },
462
+ "growth.revenue_growth": { kind: "rate", aggregation: "weighted_arithmetic_mean", reciprocal_of: null },
463
+ "growth.earnings_growth": { kind: "rate", aggregation: "weighted_arithmetic_mean", reciprocal_of: null },
464
+ "leverage.debt_to_equity": { kind: "ratio", aggregation: "weighted_arithmetic_mean", reciprocal_of: null },
465
+ "leverage.net_debt_to_ebitda": { kind: "ratio", aggregation: "weighted_arithmetic_mean", reciprocal_of: null },
466
+ });
467
+
468
+ /**
469
+ * Named only so the refusal explains itself. These are the aggregations people actually try,
470
+ * and each one produces a number that reads like a fact about the fund and is not one.
471
+ */
472
+ const ABSOLUTE_FACT_HINTS = Object.freeze([
473
+ "revenue", "sales", "eps", "earnings_per_share", "net_income", "profit", "ebitda",
474
+ "free_cash_flow", "fcf", "market_cap", "assets", "equity", "debt", "capex", "book_value",
475
+ ]);
476
+
477
+ const looksAbsolute = (factId) => {
478
+ const id = String(factId || "").toLowerCase();
479
+ if (Object.prototype.hasOwnProperty.call(LOOK_THROUGH_METRICS, factId)) return false;
480
+ return ABSOLUTE_FACT_HINTS.some((hint) => id.includes(hint));
481
+ };
482
+
483
+ const refusal = (reason, extra = {}) => ({
484
+ value: null,
485
+ coverage_weight: null,
486
+ method: null,
487
+ constituent_count: 0,
488
+ inputs: [],
489
+ unavailable: [reason],
490
+ ...extra,
491
+ });
492
+
493
+ /**
494
+ * Weight-aggregate one ratio metric across a fund's holdings into a portfolio-level value.
495
+ *
496
+ * This is the Buffett look-through step: the fund has no fundamentals, the companies inside it
497
+ * do, and the portfolio number is their weighted aggregate. Four rules hold:
498
+ *
499
+ * (a) below `MIN_LOOK_THROUGH_COVERAGE` of achieved weight there is no value, only a
500
+ * diagnostic -- the caller gets null and the reason;
501
+ * (b) only whitelisted ratio metrics exist; an absolute currency amount is unrepresentable;
502
+ * (c) `inputs` carries every constituent that contributed, so the arithmetic is checkable
503
+ * by hand rather than trusted;
504
+ * (d) `method` states which mean was taken, because harmonic and arithmetic disagree
505
+ * violently on exactly the multiples people care about.
506
+ *
507
+ * Achieved coverage is computed from the constituents that actually supplied a usable fact --
508
+ * not from the length of the holdings list, and not from the caller's `coverageWeight`, which
509
+ * is recorded and cross-checked but never trusted as the answer.
510
+ */
511
+ export function lookThroughAggregate({ holdings, perHoldingFacts, factId, coverageWeight = null } = {}) {
512
+ const metric = Object.prototype.hasOwnProperty.call(LOOK_THROUGH_METRICS, factId)
513
+ ? LOOK_THROUGH_METRICS[factId]
514
+ : null;
515
+
516
+ if (!metric) {
517
+ const reason = looksAbsolute(factId)
518
+ ? `refused: ${factId} is an absolute amount, not a ratio. A fund does not earn its holdings' earnings, so it cannot be weight-aggregated into a fund-level figure. Aggregate a ratio instead.`
519
+ : `refused: ${factId} is not an aggregatable ratio metric`;
520
+ return refusal(reason, { allowed_metrics: Object.keys(LOOK_THROUGH_METRICS) });
521
+ }
522
+
523
+ const facts = perHoldingFacts instanceof Map
524
+ ? perHoldingFacts
525
+ : new Map(Object.entries(perHoldingFacts || {}));
526
+
527
+ const inputs = [];
528
+ const skipped = [];
529
+ let achieved = 0;
530
+ for (const row of holdings || []) {
531
+ const ticker = row?.ticker ? String(row.ticker).toUpperCase() : null;
532
+ const weight = Number(row?.weight);
533
+ if (!ticker || !Number.isFinite(weight) || weight <= 0) continue;
534
+ const value = numeric(facts.get(ticker));
535
+ if (value === null) continue;
536
+ // A zero price multiple has no reciprocal, so it cannot enter a harmonic mean at all.
537
+ // Negative multiples DO enter it: a loss-making constituent should subtract earnings from
538
+ // the aggregate, which is precisely what the reciprocal does.
539
+ if (metric.aggregation === "weighted_harmonic_mean" && value === 0) {
540
+ skipped.push(`${ticker}: ${factId} is 0 and has no reciprocal`);
541
+ continue;
542
+ }
543
+ inputs.push({ ticker, weight: Number(weight.toFixed(6)), value });
544
+ achieved += weight;
545
+ }
546
+
547
+ achieved = Number(achieved.toFixed(6));
548
+ const declared = Number.isFinite(Number(coverageWeight)) ? Number(coverageWeight) : null;
549
+
550
+ if (!inputs.length) {
551
+ return refusal(`no constituent supplied ${factId}; nothing to aggregate`, { coverage_weight: 0 });
552
+ }
553
+
554
+ if (achieved < MIN_LOOK_THROUGH_COVERAGE) {
555
+ return refusal(
556
+ `look-through coverage ${achieved.toFixed(4)} is below the ${MIN_LOOK_THROUGH_COVERAGE} floor; ${inputs.length} constituents describe a sample, not the fund`,
557
+ { coverage_weight: achieved, constituent_count: inputs.length, declared_coverage_weight: declared },
558
+ );
559
+ }
560
+
561
+ const unavailable = skipped.slice();
562
+ if (declared !== null && achieved > declared + 1e-6) {
563
+ unavailable.push(`declared coverage ${declared} is below the achieved ${achieved}; the caller's coverage figure is stale`);
564
+ }
565
+
566
+ // Both means are weighted by the constituent weights and normalised by the weight actually
567
+ // present, so a partial-coverage aggregate is the average over what was covered rather than
568
+ // a value diluted toward zero by the constituents that were missing.
569
+ const totalWeight = inputs.reduce((sum, row) => sum + row.weight, 0);
570
+ const value = metric.aggregation === "weighted_harmonic_mean"
571
+ ? totalWeight / inputs.reduce((sum, row) => sum + (row.weight / row.value), 0)
572
+ : inputs.reduce((sum, row) => sum + (row.weight * row.value), 0) / totalWeight;
573
+
574
+ if (!Number.isFinite(value)) {
575
+ return refusal(`aggregating ${factId} produced a non-finite value; the constituent set is degenerate`, {
576
+ coverage_weight: achieved,
577
+ constituent_count: inputs.length,
578
+ });
579
+ }
580
+
581
+ return {
582
+ value: Number(value.toFixed(6)),
583
+ coverage_weight: achieved,
584
+ declared_coverage_weight: declared,
585
+ method: metric.aggregation,
586
+ metric_kind: metric.kind,
587
+ fact_id: factId,
588
+ constituent_count: inputs.length,
589
+ inputs,
590
+ unavailable,
591
+ };
592
+ }
593
+
594
+ /**
595
+ * The reciprocal view of a price multiple, e.g. P/E -> earnings yield.
596
+ *
597
+ * Kept separate from `lookThroughAggregate` so the ordering is explicit: the harmonic mean is
598
+ * taken over the MULTIPLES first and inverted after. Inverting each constituent and then
599
+ * averaging arithmetically gives the same number, but doing it in the other order -- average
600
+ * the multiples arithmetically, then invert -- does not, and that is the mistake this
601
+ * separation exists to make visible.
602
+ */
603
+ export function asYield(aggregate) {
604
+ if (!aggregate || aggregate.value === null || !aggregate.value) return null;
605
+ const metric = LOOK_THROUGH_METRICS[aggregate.fact_id];
606
+ if (!metric || metric.aggregation !== "weighted_harmonic_mean") return null;
607
+ return {
608
+ value: Number((1 / aggregate.value).toFixed(6)),
609
+ fact_id: metric.reciprocal_of,
610
+ method: `reciprocal_of_${aggregate.method}`,
611
+ coverage_weight: aggregate.coverage_weight,
612
+ constituent_count: aggregate.constituent_count,
613
+ };
614
+ }
615
+
616
+ // ---------------------------------------------------------------------------
617
+ // Metadata (fees / NAV / AUM)
618
+ // ---------------------------------------------------------------------------
619
+
620
+ export const ISHARES_SCREENER_URL = "https://www.ishares.com/us/product-screener/product-screener-v3.1.jsn"
621
+ + "?dcrPath=/templatedata/config/product-screener-v3/data/en/us-ishares/ishares-product-screener-backend-config"
622
+ + "&siteEntryPassthrough=true";
623
+
624
+ export const SSGA_FUNDFINDER_URL = "https://www.ssga.com/bin/v1/ssmp/fund/fundfinder"
625
+ + "?country=us&language=en&role=intermediary&product=etfs&ui=fund-finder";
626
+
627
+ /**
628
+ * The two metadata feeds wrap every value, in two different shapes, and neither is a number:
629
+ * iShares sends `{d: "0.03", r: 0.03}` and SSGA sends `["0.0945%", 0.0945]`. Both carry a
630
+ * formatted display string beside the raw value; the raw member is the one to read, because
631
+ * the display string is localised and suffixed.
632
+ */
633
+ const screenerNumber = (cell) => {
634
+ if (Array.isArray(cell)) return numeric(cell[1]);
635
+ if (cell && typeof cell === "object") return numeric(cell.r ?? cell.d);
636
+ return numeric(cell);
637
+ };
638
+
639
+ /** The raw member, uncoerced -- SSGA's dates live there already in ISO form. */
640
+ const screenerRaw = (cell) => {
641
+ if (Array.isArray(cell)) return cell[1] ?? cell[0] ?? null;
642
+ if (cell && typeof cell === "object") return cell.d ?? cell.r ?? null;
643
+ return cell ?? null;
644
+ };
645
+
646
+ /**
647
+ * AUM units are NOT the same across the two issuers and mixing them is a factor-of-a-million
648
+ * error: iShares reports total net assets in absolute dollars (IVV: 8.67e11) while SSGA
649
+ * reports millions (SPY: 785173.06, displayed "$785,173.06 M" -- all 180 SSGA funds use that
650
+ * suffix). Both are converted to absolute USD here and the unit is declared on the way out,
651
+ * for the same reason FRED's millions/billions mismatch is converted explicitly rather than
652
+ * assumed away.
653
+ */
654
+ const SSGA_AUM_SCALE = 1e6;
655
+
656
+ export function parseIsharesScreener(payload, symbol) {
657
+ const data = typeof payload === "string" ? JSON.parse(payload) : payload;
658
+ const wanted = String(symbol || "").toUpperCase();
659
+ for (const [productId, row] of Object.entries(data || {})) {
660
+ if (String(row?.localExchangeTicker || "").toUpperCase() !== wanted) continue;
661
+ return {
662
+ issuer: "ishares",
663
+ product_id: productId,
664
+ fund_name: row.fundName || null,
665
+ cusip: row.cusip || null,
666
+ expense_ratio: screenerNumber(row.fees ?? row.ter),
667
+ expense_ratio_unit: "percent",
668
+ nav: screenerNumber(row.navAmount),
669
+ aum: screenerNumber(row.totalNetAssets),
670
+ aum_unit: "usd",
671
+ // iShares sends {d: "Jul 27, 2026", r: 20260727}; the display member is the parseable one.
672
+ as_of: isoDate(screenerRaw(row.navAmountAsOf)),
673
+ };
674
+ }
675
+ return null;
676
+ }
677
+
678
+ export function parseSsgaFundFinder(payload, symbol) {
679
+ const data = typeof payload === "string" ? JSON.parse(payload) : payload;
680
+ const rows = data?.data?.funds?.etfs?.datas;
681
+ if (!Array.isArray(rows)) return null;
682
+ const wanted = String(symbol || "").toUpperCase();
683
+ const row = rows.find((entry) => String(entry?.fundTicker || "").toUpperCase() === wanted);
684
+ if (!row) return null;
685
+ const aumMillions = screenerNumber(row.aum);
686
+ return {
687
+ issuer: "ssga",
688
+ fund_name: row.fundName || null,
689
+ expense_ratio: screenerNumber(row.ter),
690
+ expense_ratio_unit: "percent",
691
+ nav: screenerNumber(row.nav),
692
+ aum: aumMillions === null ? null : aumMillions * SSGA_AUM_SCALE,
693
+ aum_unit: "usd",
694
+ // SSGA's asOfDate arrives as ["Jul 27 2026", "2026-07-27"]: the raw member is already ISO.
695
+ as_of: isoDate(screenerRaw(row.asOfDate)),
696
+ };
697
+ }
698
+
699
+ /**
700
+ * Fees, NAV and AUM where the issuer publishes them keylessly.
701
+ *
702
+ * Vanguard and Invesco do not: Invesco's /nav and /profile both answer HTTP 501, and neither
703
+ * issuer exposes shares outstanding. Those two return a named gap rather than a figure sourced
704
+ * from somewhere the number cannot be traced back to.
705
+ */
706
+ export async function fetchFundMetadata(symbol, { signal } = {}) {
707
+ const fund = resolveFund(symbol);
708
+ if (!fund.supported) {
709
+ return Object.freeze({ symbol: fund.symbol, issuer: null, unavailable: [`metadata unsupported: ${fund.reason}`] });
710
+ }
711
+
712
+ const gap = (reason) => Object.freeze({
713
+ symbol: fund.symbol,
714
+ issuer: fund.issuer,
715
+ expense_ratio: null,
716
+ nav: null,
717
+ aum: null,
718
+ source_url: null,
719
+ public_at: null,
720
+ unavailable: [reason],
721
+ });
722
+
723
+ if (fund.issuer === "vanguard" || fund.issuer === "invesco") {
724
+ // Fund size is recovered from the disclosed positions instead, so the missing figure here
725
+ // is the SHARE COUNT: two dated share counts are what a flow needs, and no arithmetic over
726
+ // one day's positions can reconstruct yesterday's.
727
+ return gap(`${fund.issuer} publishes no keyless shares-outstanding endpoint for ${fund.symbol}`
728
+ + (fund.issuer === "invesco" ? " (its /nav and /profile both answer HTTP 501)" : "")
729
+ + "; fund size is derived from disclosed positions, but flow needs two dated share counts");
730
+ }
731
+
732
+ const url = fund.issuer === "ishares" ? ISHARES_SCREENER_URL : SSGA_FUNDFINDER_URL;
733
+ try {
734
+ const body = await fetchText(url, LIMITS.QUOTE_FETCH_MS * 3, signal);
735
+ const parsed = fund.issuer === "ishares"
736
+ ? parseIsharesScreener(body, fund.symbol)
737
+ : parseSsgaFundFinder(body, fund.symbol);
738
+ if (!parsed) return gap(`${fund.issuer} metadata feed did not list ${fund.symbol}`);
739
+ return Object.freeze({
740
+ symbol: fund.symbol,
741
+ ...parsed,
742
+ source_url: url,
743
+ public_at: parsed.as_of ? `${parsed.as_of}T00:00:00.000Z` : null,
744
+ observation_date: parsed.as_of,
745
+ unavailable: parsed.as_of ? [] : [`${fund.issuer} ${fund.symbol}: metadata carried no stated date`],
746
+ });
747
+ } catch (error) {
748
+ return gap(`${fund.issuer} metadata: ${String(error?.message || error)}`);
749
+ }
750
+ }